Skip to content

Latest commit

 

History

History
316 lines (249 loc) · 13.4 KB

CONTRIBUTING.md

File metadata and controls

316 lines (249 loc) · 13.4 KB

Contributing to stunt

Thanks for your interest in stunt! The fastest way to contribute is an adapter — a local simulator for some public API. Adapters are just a directory of YAML + Starlark + fixtures; no Go knowledge is required to write one.

This guide covers the contribution workflow. For the adapter authoring reference (the adapter.yaml schema, Starlark builtins, rules engine, gRPC/WebSocket), see adapters/README.md.


TL;DR — add an adapter

stunt adapter new myapi-style        # scaffold an adapter
cd myapi-style
# edit adapter.yaml, scripts/*.star, fixtures/*.jsonl
stunt adapter lint ./myapi-style     # MUST pass: synthetic-data guard
stunt adapter test ./myapi-style     # replay traces / conformance (optional)
# add a DISCLAIMER (required for branded APIs — see below)

Then open a PR adding the directory under adapters/ and (optionally) an entry in the catalog.


Ways to contribute

  • An adapter for an API you test against (highest value).
  • A bug fix or feature for the Go core (internal/...).
  • Docs, examples, and test coverage.
  • Reports — open an issue for bugs, missing API coverage, or rough edges.

Code of conduct

Be kind and constructive. Assume good faith. "Unofficial, not affiliated" is the project's posture toward every provider whose API style an adapter mimics — please extend the same respect to people.


Repository layout

cmd/stunt/        # the CLI binary
internal/
  adapter/        # adapter format + loader
  rules/          # declarative rules engine (match/evaluate/faker/template)
  starlark/       # sandboxed Starlark VM
  engine/         # wires it all together; HTTP + gRPC + WebSocket servers
  primitives/     # Collection / KV / Blob / Clock / Identity / Events / ...
  netutil/        # portless.dev-style TLS proxy + CA + /etc/hosts
  contrib/        # contributor tooling: new / import / lint / test / catalog
  adapterdist/    # git adapter distribution
  grpcsim/        # descriptor-driven gRPC server
  manifest/       # stunt.yaml schema
adapters/         # first-party reference adapters (contributions land here)
docs/             # specs + plans

Dev environment

You need Go 1.27.1+. The repo pins it in .mise.toml, and CI pins the same version in .github/workflows/*.yml — keep the two in step, or just ci stops being the gate that ships.

git clone https://github.com/StuntAPI/stunt
cd stunt
go build ./...                 # builds everything
go test ./...                  # runs the suite

Dev-machine note: if go commands fail with a stdlib-version mismatch, prefix them with env -u GOROOT. (A local GOROOT/toolchain misconfiguration on some setups; not required in CI or for end users.)


Writing an adapter

  1. Scaffold it:

    stunt adapter new myapi-style

    This creates adapter.yaml, an example GET /hello endpoint, a sample template, a seed fixture, a Starlark handler, a JSON schema, and a README — all synthetic.

  2. Author it. See adapters/README.md for the full reference: the adapter.yaml schema, the rules engine (templated responses, probabilistic faults, conditional expressions), the Starlark builtins (store_collection, store_kv_*, store_blob, identity_*, events_*), and the gRPC + WebSocket transports.

  3. Import from a spec (optional): jump-start an adapter from an existing API description:

    stunt adapter import openapi  path/to/openapi.json  --dir ./myapi-style
    stunt adapter import har     path/to traces.har     --dir ./myapi-style
    stunt adapter import proto   path/to api.proto      --dir ./myapi-style
  4. Test it locally:

    # point a manifest at it and serve
    echo 'version: 1
    services:
      myapi:
        adapter: ./adapters/myapi-style' > stunt.yaml
    stunt up
    curl http://127.0.0.1:8000/hello

Branded adapters: naming & disclaimer (mandatory)

Adapters that mimic a specific company's API must follow the neutral-naming convention (see adapters/README.md for the full rules):

  1. -style naming for the id, directory, and description — e.g. stripe-style, never stripe. Never claim to be the provider.
  2. A DISCLAIMER file in the adapter dir (copy adapters/DISCLAIMER.template) stating it is not affiliated/endorsed, is for local testing only, and uses synthetic data only.
  3. Synthetic data only — all fixtures/templates are faker-generated. stunt adapter lint must pass clean. This is the core safety property.
  4. Structure, not documentation — reproduce the API shape needed for local testing; do not reproduce the provider's proprietary docs verbatim.
  5. No provider logos/trademarks.

Generic, non-branded adapters (e.g. echo-style) are exempt from -style naming but still must pass stunt adapter lint.


Quality gates (every contribution must pass)

Adapters

stunt adapter lint ./adapters/<your-adapter>      # no findings

lint enforces the synthetic-data invariant: it scans fixtures, templates, and handler scripts for real-looking data (emails, UUIDs, tokens, credit-card numbers, PII field names). All adapter data must be synthetic. If you see a finding, replace the value with a faker or a clearly-fake placeholder.

Go core

If you touch internal/... or cmd/..., the canonical gate is one command:

just ci

That runs build + test -race + vet + fmt-check + mod-tidy + lint-adapters — the exact set a PR must pass. (A hosted CI job invokes the same just ci, so there's one source of truth for "does this ship".) Granular recipes exist too: just test, just vet, just fmt, just fmt-check, just lint-adapters, just smoke. The equivalent raw commands are go test -race ./..., go vet ./..., and gofmt -l . (must be empty).

All gates must pass. -race is non-negotiable for new concurrent code. Tests are host-safe by convention: they use temp dirs, free high ports, file:// git fixtures, and httptest sinks — they never touch ~/.stunt, /etc/hosts, the system trust store, or the real network.

Test conventions

  • Prefer table-driven tests.
  • Integration tests build a temp adapter + start the engine via ServeForTest (free ports) and use real clients (HTTP / gRPC / WebSocket) — mirror the existing *_test.go in internal/engine/.
  • Add a test for every new behavior; fix bugs with a failing test first (TDD).

The conformance matrix

CONFORMANCE.md and conformance/matrix.json are generated. Never edit them by hand — run just conformance-matrix and commit the result. CI regenerates and fails on drift.

just conformance-matrix needs conformance/node/node_modules to exist, so run bun install --frozen-lockfile in conformance/node first. just ci-full does both in order.

Run the Node suites through just conformance-node. It builds a fresh binary and sets STUNT_BIN; the harness refuses to fall back to stunt on PATH, because a stale install would silently become what the matrix attests to. If you see STUNT_BIN is not set, you are running bun test directly — use the recipe.

What the matrix claims, and what it does not:

  • A behavior is listed only if a section marker (// ===== name =====) or a Record(...) call exists. The matrix is derived from source text, not from test results. A skipped or assertion-free test still publishes its name. That gap is known, not closed.
  • verification: VM means the adapter has published engine-level behavior names. It is not derived from a suite file merely existing.
  • missing: and deviations: bullets in conformance/matrix.yaml need no justification. They are a coverage record, not a verification claim, and the ratchet below never guarded them.

Dependency bumps

SDK versions are load-bearing — they are published as the version each adapter was verified against. Dependabot opens a weekly PR per ecosystem; a second workflow regenerates the artifacts the bump invalidates; auto-merge lands it once just ci (ubuntu-latest) is green. main is protected, so a bump cannot skip the gate.

If you bump one by hand, regenerate in the same commit or the freshness gate will fail.

The regeneration workflow commits with GITHUB_TOKEN, and GitHub deliberately starts no workflow from such a push — so it also approves the pull_request run that the regenerated commit parks. Without that approval a bump regenerates its artifacts and then sits red forever. Do not replace it with gh workflow run ci.yml: a dispatched run reports the same check name, and branch protection matches by name and takes the latest, so a green dispatch loses to the stale cancelled one and the PR stays blocked with a passing gate.

Not built yet

Recorded so the absence is legible rather than looking like an oversight:

  • No .NET SDK conformance tier. Deferred, not abandoned. Designed and reviewed over two rounds by four seats (harness, conformance-integrity, security, adversarial), then closed at 5/6/4/6 — below the bar — after a stop rule fired on two counts: the open in-scope HIGH count did not fall between rounds (18 → ~17), and the same failure classes recurred in both.

    The dominant finding, reached independently by three seats: the design derived its evidence from source text instead of attesting it at runtime. Most other clusters followed from that — tier conflation, junk behaviour names, and a harness that could boot the wrong adapter and still report green. Two blockers were independent of it: .NET SDKs auto-discover ambient credentials by default, so a harness would sign and send to real cloud endpoints silently, with no log line; and CPM pins versions rather than hashes with no lockfile, on a repo whose verified SDK surface is Go and Node. The review also conceded five errors of its own, among them a cited commit that does not exist and a causal inversion in its own motivating example.

    The gap is real, not cosmetic. A read-validation defect surfaced through a .NET client while the Go SDK suite was green; a monolingual matrix cannot see that class. Today 36 of 99 adapters carry SDK-tier evidence, and the sources behind those rows are Go modules, Node packages, and vendored official API specs — 25 distinct client entries plus 19 vendored specs. Every one is Go, Node, or a published schema. There is no .NET project in the tree and no .NET client anywhere in the matrix; the only "Microsoft" in it is graph.microsoft.com inside a deviation note.

    Two things must be settled before resuming. The first is a prerequisite the review named, needing no .NET suite at all: a provenance/attestation gate for the existing Go and Node rows, so the matrix's evidence is attested rather than derived. The second is an unverified premise — that roughly 40–46 of the 99 adapters have a first-party .NET client. That count was never produced. An earlier draft's "roughly 60" conflated "has no SDK row today" (63) with "no .NET client exists". If the real number is small, the sweep's premise does not hold and the question changes shape.

  • No GCS or Azure Cosmos adapters. Vyral's Google.Cloud.Storage.V1 and Azure.Cosmos dependencies have no simulator here. Straightforward work, not blocked.

  • No coverage ratchet. The three matrix-integrity bugs it was meant to catch are fixed directly (VM tier derivation, the Discord assertion, the route-count floors), so a gate is not currently load-bearing.


Submitting a PR

  1. Fork & branch from master.
  2. Make your change; keep commits focused (one logical change per commit). Conventional-ish commit subjects are appreciated (feat:, fix:, test:, docs:, chore:) but not strictly enforced.
  3. Run the gates above.
  4. If adding an adapter, include:
    • the adapter directory under adapters/ (with DISCLAIMER if branded),
    • a one-line entry in the adapters/README.md table,
    • confirmation that stunt adapter lint is clean.
  5. Open the PR with a clear description of what and why. Link any related issue.

Review criteria

  • Adapters: lint clean; synthetic data; branded naming + disclaimer; useful coverage of the API's common paths.
  • Core: tests added; -race/vet/gofmt clean; host-safe tests; matches existing style; security-conscious (adapters run sandboxed Starlark — the sandbox is the trust boundary, so never add host I/O to the VM).

Security & trust model

The defining property of stunt is that community adapters are safe to install: adapter logic runs inside a sandboxed Starlark VM with no host I/O (no filesystem, no network, no subprocess), bounded by an execution-step limit. State is mediated through primitives (Collection/KV/Blob/Identity/ Events), not raw host access. When contributing core changes, preserve this boundary — never expose host I/O to adapter scripts.

Adapters must contain only synthetic data (enforced by stunt adapter lint) so installing one never ships real PII or provider secrets.

Licensing

By contributing you agree your contributions are licensed under the project's license (see the repository root). Ensure you have the right to contribute any code or fixtures you submit, and that adapters contain no proprietary provider data or documentation.