# Adding a new twin pack — from zero

The packaging guide — the Formula Cookbook — for a `@volter/twin-<vendor>` PACKAGE: one
self-contained implementation of the platform's protocol aimed at one external upstream
(./architecture.md "Two kinds of thing"). A package is born by the scaffolder, verified by its own
T0, admitted by §9, and then lives a lifecycle recorded in `policy/MAINTAINERS.md` — experimental
→ Maintained → Supported → deprecated (Obsolete) → removed — kept current by catalog-wide
upkeep sweeps (spec drift, SDK/PyPI currency, a live probe) whose per-package results the
status column dispatches: Supported is fixed in the wave, Maintained is queued, Odd Fixes when
it breaks, Orphan is flagged at risk. The operational recipe for creating a pack follows. The **bar** is in
[./architecture.md](./architecture.md) (pack shape, dep hygiene) and [./conformance.md](./conformance.md)
(acceptance criteria); this is the **step-by-step**.

Every pack moves to [Protocol 3](./architecture.md#protocol-3-the-derived-pack), and a new pack is
built to it from the start: its [migration steps](./architecture.md#migrating-a-pack) are the recipe
for a derived pack, with openai, github, stripe, slack, aws's Secrets Manager lane and smtp as its
examples. What follows governs a protocol 2 pack until it moves.

## Your task (if you were told "add the `<vendor>` twin")

This guide is self-driving — a one-line task ("add the `<vendor>` twin") is enough; everything you
need is below. Execute exactly this, end to end, without waiting for further instruction:

1. **Isolate.** Work in your own git worktree. **`git rebase` it onto current local `main` and
   `bun install` BEFORE any work** — worktrees branch from a stale base (the last *pushed* commit),
   which silently causes parallel-rewrite conflicts. Confirm the exemplar packs exist. **Then prove the gate itself is alive on the
   UNTOUCHED base** — `bun test ./scripts/mutation-test.ts` must parse and actually run saboteurs —
   before building anything: one builder inherited a base where `scripts/mutation-test.ts` didn't
   parse, so the mutation gate ran ZERO saboteurs while looking ordinarily red, and every later red
   got blamed on the new pack. A red you inherit is a rebase problem, not your bug — resolve it
   first. Never touch other packs or uncommitted user WIP.
2. **Understand the target** — read "How a twin should work" (next section). Trace the motivating
   application's actual clients and connection paths before choosing the implementation surface.
   A vendor can expose several interfaces to the same state. Record demanded interfaces and
   omissions in the existing descriptor, capability manifest and census; a surface absent from
   those lists is still a gap. An installed adapter is not evidence that the app uses it.
3. **Build** — follow steps 0→8: pick the exemplar by archetype, copy it (§0.5), adapt the layout/
   package.json/index/cli (§1-4), build on the kernel (§5, incl. the subject-id note), author the
   manifest as the *real vendor surface* with failable verifies (§6), do the central wiring — §7,
   **many central wiring points, not one — ask `verify-pack.ts`, do not trust a count**, and
   start it with `bun scripts/scaffold-pack.ts`,
   which mechanizes most of them — and verify per §8. Scope every edit to
   `packages/twin/<vendor>/` + the §7 wiring points (the manifest registry, the mutation `PACKS`
   entry, the four root censuses, the regenerated docs block, your pack descriptor's
   `hosts`/`adoption`/`endpointEnv` fields + the regenerated `pack-facts.json`, and — where they
   apply — the rate-budget rosters + the D8 mirror line, the journey registry).
4. **Self-audit** your manifest (the "Self-audit" section) and fix what it surfaces.
5. **Adversarially review yourself** — §9: spawn an *independent* read-only `Explore` sub-agent to
   refute every `done`; demote/fix anything it can't confirm, and **record its verdict in your
   commit/report** (this step is mandatory — the gate is blind to false-greens; an un-recorded review
   counts as not done). You orchestrate this yourself — it does not require a second human prompt.
6. **Check the acceptance checklist** (§10), commit the final candidate in your worktree, and
   **report any point where this doc was unclear/missing/wrong** (cite the section) so the guide
   keeps improving. Verification is §8's per-merge set — your pack's suites, typecheck, and the
   meta-gates your wiring touched; the full `twin-check.sh` sweep runs only when the owner asks for
   one, so say plainly that your pack's claim is **queued** for that run rather than implying a
   sign-off you did not take.

That's the whole job. The rest of this doc is the detail for each step.

## How a twin should work (the behavioral contract)

Read this first — it's the target the recipe below builds toward. A twin is a **local, stateful,
vendor-faithful replica** of one SaaS API. Your *unmodified* vendor SDK (`stripe`, `@octokit/rest`,
`@linear/sdk`, …) points at it and gets vendor-correct responses. Concretely it must:

- **Use the shared state kernel.** State is a durable **logical log and checkpointed tree** — the shared `@volter/world-core` kernel, never an ad-hoc
  in-memory store. A write enters the kernel’s log; reads use its checkpointed tree.
- **Use the shared World operations.** Refresh observes the vendor into a root's log. Writes
  use the simulated or real state system at the head. A branch holds its own entries over a
  parent position; push lands them in the parent, while deploy performs them against the vendor.
  Fetch extends cached parent history and rebase moves the branch position. See
  [the model](../concepts/the-model.md) for the shared semantics.
- **Be vendor-faithful.** Response shapes match the vendor; invalid input is rejected the way the
  vendor rejects it (same status + error code); **an operation that isn't modeled yet fails like the
  vendor (404/422) — never a fake success.**
- **Fake auth locally.** No real vendor call except in the **connector**, over an **injected client**
  (the real SDK in prod, a fake in tests). The vendor SDK is a test-only dependency.
- **Keep the three kinds of credential apart.** The fake vendor credentials an app presents are
  twin state: validated against the World's tree and seeded through the vendor's wire or an import,
  never derived from where the World sits. A real vendor credential lives only sealed beside a
  real-system root and reaches the vendor through the kernel executor, never pack code or the tree.
  A World token is transport: it grants access to the World and is never a vendor credential
  ([data and keys](../concepts/data-and-keys.md)).
- **A token never rides a deployable entry or a changeset's text.** The fake tokens a twin issues are
  twin state behind the World's token — whoever reads the log can already mint them through `/_twin/*`
  — but an entry that is deployed is offered to the vendor, and a changeset is read by whoever approves
  it: no deployable entry's fields or subject id holds a token or a client secret. Keep the token's
  SHA-256 under a `_`-prefixed bookkeeping type (never deployed, never in a changeset), key rate and
  budget records by that hash, and sign upload URLs with a per-upload secret drawn at init rather than
  the caller's token. (Two packs put a raw bearer into deployable entries.)
- **Budget a rate-limited vendor, fail closed.** If the vendor throttles a live token, every real
  call goes through **one guarded client** with a **persistent** rolling-window spend ledger that
  **throws instead of calling** past a conservative ceiling or during a `Retry-After` cooldown — and
  that survives a restart, so a fresh process gets no fresh allowance. **Raw vendor API calls outside
  the connector are BANNED**; a careless script bypassing it is exactly what a real ~4.5-day Figma
  token lockout was. Retries **off by default**. Prove all of it — cache, quota, backoff — against an
  **injected fake, offline**; never call the live vendor to "check". You do **not** write that
  machinery: it is the kernel's shared `RateBudget` (`@volter/world-core`). Your pack supplies only the
  numbers, as data — `declareRateBudget(vendor, { windowMs, ceiling, defaultWeight, rules, reason })`,
  also hung off `TwinPack.rateBudget` — and applies the guard at the one place a live call is issued.
  Omitting the declaration does **not** mean "no limit": you get the deliberately austere
  `DEFAULT_RATE_BUDGET` instead.

  **Live-fetch the vendor's published limits page during this build before you pick a number**, and
  put the figure, URL, and access date in `reason`. Do not copy a number from a design note, an old
  issue, or another pack's prose: vendor budgets change, and the Fly build found that even this
  repo's circulating notes had already drifted from the current first-party page. You may only be
  more permissive than the fallback when the live first-party source justifies it; if the vendor
  publishes no scalar limit (as Figma, OpenAI and Sentry do not), say exactly that in `reason` and
  stay at or under the fallback — never dress a guess up as a vendor fact.
  `scripts/rate-budget-isolation.test.ts` checks this across all declaring packs at once and holds
  the roster, so a new pack must be added there too.

  Prove the ceiling by **counting calls on an injected fake**: after N calls the fake recorded exactly
  N, and the next attempt throws with the count *unchanged*. "It threw" is not proof — the count is
  what shows nothing reached the vendor. Inject the ledger path in every test, too: a suite that
  spends against the operator's real `~/.volter/<vendor>` file poisons later runs.

  See ./architecture.md **D8**, `packages/world-core/src/rateBudget.ts`, and three worked examples, one per
  shape: `figma/src/figma-budget.ts` (a pack that builds its own HTTP client),
  `notion/src/notion-budget.ts` (a pack that decorates an injected SDK client), and
  `github/src/github-budget.ts` (a vendor whose published scheme is *already* a weighted point
  budget, so the declaration reproduces its own numbers instead of proxying them).
- **Be offline + deterministic.** Every capability `verify()` runs against the local twin with no
  network and a stable, repeatable result.
- **Classify the human surface explicitly.** A vendor people *work in* gets a React mirror reading
  the **same projection** the API serves (API↔UI parity, not a hardcoded shell); a vendor people
  only *call* omits it. A third class exists: when the vendor's own browser page is a required leg
  of its protocol (OAuth consent), serve that page at the vendor route as part of the protocol,
  not as a second dashboard mirror. See "Does this vendor get a mirror?" below; this is the single
  most mis-applied rule in the repo, so decide it there rather than by feel.

**Scope = "100% of what the vendor does."** Every capability is `done` or `todo`; there is no
exclusion list. The capability manifest is the **real vendor surface** as the denominator —
coverage is honest and partial until the twin reaches it.

**Relevant docs:** [README.md](../README.md) (the repo/world model: a twin is a repo, a world is a
monorepo of twins) · [./architecture.md](./architecture.md) (kernel/pack boundaries + dep hygiene,
mechanically enforced) · [./conformance.md](./conformance.md) (the acceptance ladder + the
capture→check→report→gate cycle) · [../concepts/worlds.md](../concepts/worlds.md) (the world runtime + `external`
self-managed services) · [AGENTS.md](../../AGENTS.md) (how the repo is grown: worktree-isolated
builder agents, per-merge verification, the gate's cadence). Reference manifests: `stripe-capabilities.ts` / `linear-capabilities.ts`.

### Does this vendor get a mirror?

**Do not ask "does this vendor have a UI?"** — almost every vendor has one, including OpenAI
(a console) and Google Maps (a cloud dashboard), and asking it that way has produced wrong answers.

**Ask instead: when someone does this vendor's core job, do they open a browser or write code?**
   That question and the census.json `ui` slice's own ("does this twin need an agent-navigable UI sim?")
   are THE SAME question asked of the VENDOR'S product, never of the mirror you happen to have
   built: a read-only mirror is evidence of nothing (that argument would rule `false` for every
   read-only mirror — airtable §9, 2026-08-31). If people do the vendor's core job in a
   browser, `needsUi: true`, with journeys as recorded debt if the mirror isn't there yet.

- A designer *designs in the canvas*. A writer *writes in the page*. A team *talks in the app*. A PM
  *drags tickets on the board*. → **the UI is the product → ship a mirror.**
- An integrator *calls the API*; the vendor's console is incidental tooling for keys, billing, and
  logs, not where the work happens. → **the API is the product → omit the mirror**, and say so in
  the README `## Coverage` + a comment in the manifest; coverage is API + connector.
- A browser redirect is itself a required protocol participant: a human must see vendor-owned UI
  and make a choice before the protocol can continue. → **the vendor UI is part of the protocol →
  serve it at the vendor's real route on the same server and state model**, not as a separate
  dashboard mirror and not as an automatic redirect that bypasses the human leg. Google OAuth is
  the exemplar: the consent screen and `/_twin/consent` post complete the authorization-code flow.
  It still declares UI capabilities, journeys, and a `mirrorMutations` state-builder seam because
  the same data-coupling and visual-review bar applies; `mirror` is only a CLI alias for the same
  protocol server, never a second renderer.

The current split, for calibration — **the rule above governs; this table is the state of the repo,
not an authority**, and two entries are known to contradict it:

| | packs |
|---|---|
| **Mirror** | `calcom` `clerk` `github` `jira` `linear` `posthog` `resend` `sentry` `slack` `stripe` `supabase` — plus `openrouter` and `aws`, which are **legacy outliers, not precedent** (see below) |
| **No mirror (API is the product)** | `algolia` `anthropic` `elevenlabs` `fal` `googlemaps` `inngest` `livekit` `mapbox` `openai` `openweather` `pinecone` `upstash` `replicate` `sendblue` `stream` `svix` `twilio` `vital` `webrisk` |
| **Vendor UI is protocol** | `googleoauth` — consent is served by the protocol server itself; this is not a dashboard mirror |

Three known inconsistencies — cite the rule, not these, when you classify:

- **`openrouter` mirrors but `openai` doesn't**, though both are LLM-completion APIs that the rule
  puts in the same column. Its own census.json `ui` slice records `needsUi:false` ("the API/SDK is
  the real surface"), and §9 already demoted one of its UI verifies. It is a dev-console affordance
  that predates the rule.
- **`aws`** ships an S3 bucket browser only (DynamoDB and Timestream have no UI capabilities at
  all). AWS is unambiguously code-first; the mirror is a debugging affordance, retro-fitted.
- **`polar` is in neither column.** It ships no mirror, yet it is a merchant-of-record billing
  product with a real dashboard — the same shape as `stripe`, which does mirror — and its manifest
  already carries `polar.mirror.ui` as a live `todo`. Treat it as an **open case to re-review**,
  never as precedent for omitting.

Every no-mirror pack must state **why** in its README `## Coverage`, under a `### No UI mirror`
heading. `stream` is the model to copy ("Stream's dashboard is an internal admin console, not a
dev-facing data UI"). All no-mirror packs now carry this; `polar`'s instead says the opposite —
that its missing mirror is a tracked gap and must not be cited as precedent.

**The trap: a thin or read-only REST API is evidence FOR a mirror, not against it.** Figma's REST
API is read + comments only *precisely because* the real work happens on the canvas that the API
can't touch. Reasoning "the API is small, so this is an API-first vendor" inverts the rule and gets
you a twin that omits the product. Notion and Figma both mirror for this reason.

**What a mirror actually is.** A *second renderer over the same projection* — **never** a second app
with its own store, and never a client that keeps state the API can't see. Two files are invariant
across all 13 mirror packs:

- `client/<vendor>-mirror.tsx` (+ `.css`) — the React browser entry, bundled by `Bun.build` and
  memoized at module scope. The memoization is the reason a pack on its own does exactly one build.
  (§9 warns of a ~6-build per-process ceiling behind the meta-test's per-process isolation. Treat
  that as unquantified: a 2026-07-25 measurement ran 26 packs' verifies single-process and got
  byte-identical per-pack numbers, so the ceiling did not bite at that scale. Isolation is cheap
  insurance as the pack count grows — but re-measure before citing the ceiling as a hard
  constraint, and don't diagnose a failure as "the ceiling" without evidence.)
- `src/<vendor>-mirror-ui.ts` — the server half: `buildXxxMirrorClient()`, `createXxxMirrorServer
  ({ root, port })`, `xxxMirrorHtml()`, plus any **pure helpers the client imports** (Bun
  tree-shakes the server-only exports out of the browser bundle — this is what lets a UI verify
  render the mirror's *own* tone/format functions; see `sentry-mirror-ui.ts`).

What varies is **how the browser gets data**, and there are two real archetypes. Pick by transport,
not by taste:

| | packs | how the client reads |
|---|---|---|
| **A. API passthrough** *(default)* | `sentry` `stripe` `clerk` `calcom` `posthog` `supabase` `openrouter` … | non-asset requests fall through to `handleXxxTwinRequest`; the client fetches the **vendor's real API paths** (`sentry-mirror-ui.ts:128-134`) |
| **D. store door** | `resend` `mailgun` `slack` `github` `jira` `linear` `figma` `aws` (S3) | the server declares named deterministic projections (`stores: { mirror: () => … }` on the twin fetch adapter); the client reads `GET /twin/store/<name>` — the ONLY door a mirror may read twin state through — and writes through the vendor's real API. The console never imports the handler (R3). |

**Prefer A.** It is the strongest parity claim available — there is only one code path, so parity
isn't "kept in sync", it cannot diverge. Copying `stripe` (§0) gives you A for free. Reach for D when
the screen needs an aggregate that would otherwise cost the client a dozen round-trips, or when the
vendor's wire format can't be parsed client-side at all (S3's XML — aws declares `buckets`/`objects`
and the browser never sees an XML document); declare it as one or more named stores, and the client
still writes through the vendor's wire. A bespoke `/__mirror/*` route computed in the console's own
process is not a third archetype, it is the impurity D replaces.

Whichever you pick, the **write** path is non-negotiable: UI writes go through the same
`applyXxxWrite` the API handler uses, addressed by the vendor's real endpoint. A mirror with its own
write path has already failed the bar. D is worth studying for how far this is taken — `slack`'s
`mirror` store deliberately refuses to read twin internals and instead **re-enters its own served Web
API** for members/pins/thread-meta (`slack-mirror-state.ts`), so even the aggregate is composed out
of real API responses.

Size the mirror to the **real product**, and only to screens you can data-couple (§6). A small
honest mirror beats a broad fabricated one — an undemonstrable screen is a `todo`, not a `done`.

**Your UI verifies WILL be mutation-tested — write them expecting it.** `scripts/mutation-test.ts`
runs API/write and connector phases, an isolated mirror phase where applicable, and any named
`isolatedMutations` groups. The mirror phase (`mirrorMutations`, TWIN-20/B9) is the sharp one: it
sabotages **your mirror's own state-builder export** (`jiraMirrorState`,
`githubMirrorState`, `slackMirrorState`, …) while leaving the write/handler seam fully real, so
each UI verify's `seed()` genuinely writes and only the render/projection step is dead. A verify
that greps the built bundle survives that and is reported as a SURVIVOR; a data-coupled one goes
red. Declare a `mirrorMutations` seam when your UI verifies READ AROUND the handler (a
direct twin-state reader like `*MirrorState(root)`) — that bypass is what the seam sabotages.
A mirror whose UI verifies all drive the handler (`uiDataCoupled`) needs NO extra seam: the
handler saboteur already reddens them, and an extra seam there proves nothing (this matches
mutation-test.ts's own header; the hubspot build followed the header, correctly). What is NOT enforced
mechanically is the mirror *decision* itself — `architecture.test.ts` never looks at `client/`, so
whether your vendor should have a mirror at all remains a §9 judgment.

**A SEAM MUST SABOTAGE THE ENTRY POINT THE PACK SERVES.** Not merely *an* executor of the right
shape — THE one `serveExport` mounts and the one your manifest drives. If those three ever name
different code, every green you have is about a lane nobody reaches, and nothing will tell you:
linear ran for months with its manifest and its saboteur both pointed at a hand-written subset
schema that `serveExport` did not mount, and 16 `done` capabilities were false while a real
`@linear/sdk` got "not supported by this Linear twin". Repointing the manifest alone turned 16
capabilities red; repointing the SEAM turned up 9 mutation survivors out of 56 that the old seam
had never been able to see. Check the three agree before you trust a single green. A pack should
publish exactly ONE served surface; if you believe yours needs two, that is a §9 argument to make
out loud, not a factory to add quietly.

**How far down the manifest may drive — RULED 2026-09-03, and the answer is "the handler".** Most
packs' verifies call the pack's own request handler; `plain` and `stigg` call it through one more
layer, and linear used to call a private executor beneath it. Those are three different distances
from the wire, and only the third is wrong. The rule is: **a verify drives the handler the fetch
adapter wraps — never a module beneath it.** A private executor can diverge from the served surface
and nothing will say so, which cost linear 16 false `done` capabilities.

Driving the FETCH CLOSURE instead is one notch closer again, and it is deliberately NOT required
in general. What lives only in the adapter — body decoding, status mapping, the manifest door, host
routing — is already exercised for every pack by the invariant matrix, which replays through the
fetch closure `WORKER_VENDORS` builds per vendor, and deeply for five packs by their behavioural
journeys. Migrating ninety manifests to the wire would buy a third copy of that coverage at
enormous cost, and the adapter-level defect this catalog has seen — `expo-server-sdk` gzipping a
request body that `createTwinFetchFromHandler`'s `await request.text()` corrupts — is a §3 grounds
for a hand-written fetch rather than an argument for moving every verify.

**But there is ONE SHAPE where the wire is REQUIRED, and it is not the adapter.** When a pack
RENDERS A RESPONSE TWICE — an in-process collector that assembles the whole answer, and a socket
renderer that emits it as frames — those are two surfaces, and a verify driving the collector is
structurally blind to what the listener sends. Azure's Responses stream emitted `data: [DONE]` on
the wire while its collector dropped that frame; a capability verify could not see the difference,
and the lane's own fix believed it had removed the guard but had not, so the pin stayed GREEN BY
CONSTRUCTION. Only a test against the real listener caught it. **A pack with both paths owes a pack
test on the real server** — this is "one served surface" one level down, because a second rendering
nobody measures is a second answer that can lie. Measured 2026-09-03: fourteen packs render a stream
in both places (ai-gateway, anthropic, cohere, deepseek, gemini, groq, mistral, npm-registry,
openai, openrouter, perplexity, snowflake, tunnel, xai). Linear's own second surface was deleted on 2026-09-03:
once the manifest and the saboteur were repointed, the subset lane had no consumer, no verify and
no saboteur, so nothing in the repo would have noticed it rotting — and it had already drifted
(`Notification` typed as a concrete type where the SDL makes it an interface, `metadata` as
`String` where the vendor has `JSONObject!`, invented input fields real Linear rejects), which
means a developer building against it wrote code the vendor refuses. AN UNSERVED LANE IS NOT FREE:
it is a second answer to "what does this vendor do", and the one nobody measures is the one that
lies. Delete it; keep the state and behaviour half it shares with the served lane.

## 0. Pick the closest exemplar (copy-from BY ARCHETYPE)

Don't start from a blank file — clone the pack whose *shape* matches your vendor, then adapt:

> **Pick the LEANEST exemplar that has your shape.** `stripe` is ~14k lines; a 2026-07-25 notion
> build copied it and deleted ~95%, which is slower and riskier (leftover `Stripe*` identifiers,
> dead domain files) than starting from a small pack. Reach for `stripe` only when you genuinely
> need its breadth (search, expand, deep state-machines). For a lean REST + UI pack, `calcom` is
> the better clone.

> **"OpenAI-compatible" vendors: the REJECTIONS are the fidelity surface.** For a vendor whose
> API mimics another's (groq, together, openrouter-style surfaces), copying the exemplar's
> *permissive* validation is precisely the bug: what distinguishes the real vendor is what it
> REFUSES (groq 400s `n>1` and `logprobs`; closed `service_tier` sets; missing fields the
> template vendor has). Read the vendor's own compatibility page FIRST and model its refusals;
> two of the groq build's three §9 blockers were compatibility-assumption false-greens — the
> twin served `n>1` and `logprobs` that real Groq refuses (OpenAI's permissiveness assumed to
> hold), with verifies cementing both (2026-08-31; the third blocker was a connector
> id-mapping defect, a different class).

| Vendor archetype | Copy from | Why |
|---|---|---|
| REST CRUD + a mirror (lean) | `calcom` | **the usual starting point** — REST + a small data-coupled mirror, without stripe's bulk |
| REST CRUD resources (rich) | `stripe` (list/expand/search) or `clerk` (CRUD + **real crypto**: RS256 JWT + JWKS) | when you need the breadth |
| GraphQL | `linear` | dual schema: hand-written + SDL-derived `BEHAVIOR` map; honest unmodeled-mutation errors |
| Generative (LLM, OpenAI-COMPATIBLE surface) | `groq` / `mistral` / `deepseek` | the anthropic template already adapted to the OpenAI-shaped wire — copy within the protocol family, not across it (deepseek rebuilt the protocol because this row was missing, 2026-08-31); the REJECTIONS remain the fidelity surface |
| Generative (LLM — can't run the model) | `anthropic` / `openai` | deterministic `[twin-stub:…]` content + faithful protocol envelope; the stub IS the answer, filed nowhere as a gap |
| Generative (audio — can't run the voice/acoustic model) | `elevenlabs` | the lean generative-audio exemplar (~1.4k lines, no mirror): real CRUD resources (voices, dictionaries, dubbing, history) + `[twin-stub:tts]`/`[twin-stub:stt]` deterministic stubs under a faithful envelope. For transcription-shaped surfaces (submit→poll lifecycles), `deepgram` / `assemblyai` |
| Protocol-quirky (XML / AWS-JSON + signed requests) | `aws` (its `aws-s3` / `aws-dynamodb` / `aws-timestream` service-areas) | XML + AWS-JSON render + **real SigV4** signing/verify + presigned URLs, over ONE shared `aws-sigv4`/`aws-error`/`aws-credentials` core. NB: AWS is **one vendor twin with service-areas**, not three packs — add a new AWS service as an area here, don't clone a new pack |
| Email / webhooks / local inbox | `resend` | svix HMAC signing + injected offline deliverer + inbox mirror |
| Ingestion → grouping + Web API | `sentry` | SDK ingest endpoint folds into queryable resources |
| Management API + **real** local engine | `supabase` | twin the control plane; run the real data plane via the world-runtime `external` service (don't reimplement) |
| Control API + **real execution plane behind its handler** | `fly`; also study `github`'s git plane | inject a runtime/engine seam so the twinned control plane can drive a real local engine without making that engine a gate prerequisite |

**Real-execution-plane doctrine.** First decide where the real engine belongs. Supabase delegates a
whole independently managed data service to a world-runtime `external` service. Fly cannot: a
Machines API request must launch, inspect, stop, and destroy the engine *behind that request*, so
`FlyContainerRuntime` is injected into the handler. GitHub's git plane is a lighter variation: the
handler drives the local reference `git` implementation and exposes its smart-HTTP seam separately.

For the injected-runtime shape, copy all of Fly's invariants, not just its interface:

- Default to a pure virtual runtime so the ordinary gate needs no daemon. Capability verifies
  inject a recording/failing fake and assert the calls and their arguments; a virtual no-op is not
  proof that the handler drove the plane.
- Reflect engine facts back into twinned state. Fly calls `runtime.inspect()` on reads and folds a
  real exit into the machine state/event ledger. If the engine cannot affect the control-plane
  projection, the two planes can silently disagree.
- Give engine artifacts deterministic, pack-namespaced names, and reap the prior artifact before a
  relaunch/update. Fly's `fly-twin-<machineId>` naming makes cleanup possible, but also means a
  relaunch without `runtime.remove()` fails on a name collision.
- Put the real-daemon proof in a dedicated integration test that checks the daemon, skips **loudly**
  when absent, and states that the rung was not verified. It proves the adapter only and must never
  be counted as a capability `done`; all capability proof remains offline over the injected fake.
- If the local reference engine is a deliberate gate prerequisite, as `git` is for GitHub, fail
  loudly when it cannot spawn and give its protocol plane its own mutation seam and isolated
  kill-list. Do not silently fall back to a fabricated success.

## 0.5 Copy the exemplar safely (mechanical)

Don't hand-create files — clone the chosen exemplar and rename. From the repo root (`sed -i ''` is
macOS; on Linux use `sed -i`):

```bash
VENDOR=polar        # your new vendor (lowercase)
EXEMPLAR=stripe     # the archetype you picked in step 0
Cap() { printf '%s' "$1" | awk '{print toupper(substr($0,1,1)) substr($0,2)}'; }   # polar -> Polar
Up()  { printf '%s' "$1" | tr '[:lower:]' '[:upper:]'; }                           # polar -> POLAR

cp -r "packages/twin/$EXEMPLAR" "packages/twin/$VENDOR"
cd "packages/twin/$VENDOR"
rm -rf node_modules                                   # don't copy the exemplar's installed deps
# rename files: stripe-foo.ts -> polar-foo.ts (src + client)
for f in src/$EXEMPLAR-* client/$EXEMPLAR-*; do [ -e "$f" ] && mv "$f" "${f/$EXEMPLAR-/$VENDOR-}"; done
# rewrite identifiers inside every file -- FOUR case-form pairs, not two: stripe->polar,
# Stripe->Polar, STRIPE->POLAR (the manifest export `STRIPE_CAPABILITIES` matches neither of the
# first two), and -- for multi-word vendors only -- the lower-camel mid-identifier form
# (`scrapeCreators`, `elevenLabs`), which no shell helper derives; see the multi-cap bullet below.
# `grep -rIli` (case-insensitive), or a file whose only mention is the UPPER form is skipped.
grep -rIli "$EXEMPLAR" . | xargs sed -i '' "s/$EXEMPLAR/$VENDOR/g; s/$(Cap "$EXEMPLAR")/$(Cap "$VENDOR")/g; s/$(Up "$EXEMPLAR")/$(Up "$VENDOR")/g"
cd -
```

Then **immediately** sanity-check the rename before writing any new behavior:

```bash
cd packages/twin/$VENDOR && bun install && bunx tsc --noEmit   # must be clean
```

This catches missed renames (collided imports, leftover `Stripe*` identifiers) at the type level
before you build anything. Now adapt: the `pack` descriptor (vendor/bin/resources/specSource), the
`package.json` description + the vendor SDK devDep (**verify the SDK's real npm name** — the
mechanical rename guesses wrong, e.g. PostHog's is `posthog-node`, not `@posthog/node`), and start
replacing the exemplar's domain logic with yours. (Caveat: a vendor whose name is a substring of a common word can over-match — eyeball the
`grep -rIl` list first.)

**Pin the SDK's major deliberately — a vendor SDK's major may track an API major.** `latest` can
speak a different API version than the one you're modeling: `@mendable/firecrawl-js` 4.x speaks
`/v2`, so modeling v1 required pinning `^1`. Check which API paths the SDK's default client hits
before accepting the rename's carried-over version range.

The clone+sed rename also leaves landmines the `tsc` check CANNOT catch — grep the copied pack for
the **archetype's name and hosts** after renaming (`grep -rI 'stripe\|api\.stripe\.com'
packages/twin/$VENDOR`) and read what survives:

- **Order rename seds LONGEST-MATCH-FIRST.** A vendor name that is a substring of a longer
  literal gets partially rewritten, and the hybrid reads plausibly enough to survive skims: a build
  renaming off the `openweather` exemplar turned `api.openweathermap.org` into
  `api.scrapecreatorsmap.org` — a host neither vendor has ever used — and it survived several reads
  before a grep caught it. Rewrite the long forms (hosts, compound literals containing the vendor
  name) before the bare vendor name, then grep for surviving fragments of the exemplar's compounds.
- **A vendor name that is PUNCTUATED in its host does not match the bare name at all.** The
  longest-match rule above assumes the literal contains the vendor string; `calcom` does not appear
  in `api.cal.com`, so renaming off the calcom exemplar leaves every `api.cal.com` literal
  untouched and `tsc` stays silent. Before renaming, write down the exemplar's HOSTS as well as its
  name, and grep for each host separately afterwards.
- **sed rewrites host literals into nonsense.** `api.stripe.com` inside a copied test becomes
  `api.polar.com` — a string, so `tsc` is silent, and the test now asserts against a host neither
  vendor uses. Fix every surviving host/URL literal by hand.
- **Copied budget tests may still assert the ARCHETYPE's documented rate limits** and drive its
  code paths — a green run proves the exemplar's numbers, not your vendor's. Rewrite them against
  your vendor's published limits ("Budget a rate-limited vendor" above), or delete them until you
  do.
- **A command-style API (ONE path, the operation named by a query param or body field) must encode
  the operation in its budget price key.** Pricing by `METHOD /path` alone charges a domain
  PURCHASE the same as a balance read when every command shares `/api3.json` — which is the entire
  thing the weights exist to prevent. Key the priced call as e.g. `GET /api3.json
  command=<command>`, and NORMALIZE the caller-supplied value (lowercase + trim) before the
  anchored rules see it, so an input variation can never override the priced op: `command=REGISTER`
  and `command=register ` both pass the vendor's own dispatch, and either would slip a money-moving
  call past a `command=register$` rule to be priced as a 1-unit read (`dynadot-budget.ts`,
  `dynadotCallWeight`).

⚠️ **Scripting edits with JS `String.replace` instead of sed has its own landmine: `$` is special
in the replacement string.** `$'`, `` $` ``, `$&`, `$n` are substitution patterns, so a replacement
containing one — a bash `$'…'` quote inside the code you're inserting is the classic — silently
corrupts the output (`$'` splices in everything after the match; one builder's scripted edit was
silently truncated this way), with no error and `tsc` none the wiser until the mangled file is
compiled. Escape literal dollars as `$$`, or pass a function — `.replace(pat, () => text)` — which
disables pattern expansion entirely.

⚠️ **Prefer line-oriented tools (sed/awk/perl -pe) for mechanical renames, and run `file` over the
touched sources afterwards.** One build's scripted `replaceAll` pass wrote a source file back
corrupted to BINARY — and every later text check went quietly blind: `grep` silently matches
nothing in a file it classifies as binary, so even the verification greps read as "no survivors".
After any scripted rewrite, `file packages/twin/$VENDOR/src/* packages/twin/$VENDOR/client/* |
grep -v text` must print nothing before you trust a single grep over the tree.

Three vendor shapes the mechanical recipe handles badly — know them before you run it:

- **Multi-cap vendor names.** `Cap()` uppercases only the first letter, so `elevenlabs` →
  `Elevenlabs` and `assemblyai` → `Assemblyai` — but the packs' real identifiers are
  `handleElevenLabsTwinRequest` / `handleAssemblyAITwinRequest`. For these vendors, do the
  identifier half of the rename with explicit sed pairs (or fix it up immediately after) rather
  than trusting `Cap()`; the `tsc --noEmit` sanity check will not catch a *consistently* wrong
  casing, only a mixed one.
- **Hyphenated vendor ids.** `ai-gateway` → `Ai-gateway` is never an identifier you want, and the
  bigger break is the caps-export convention: `scripts/mutation-test.ts`'s `http()` helper derives
  the manifest export as `<NAME>.toUpperCase()_CAPABILITIES`, and `AI-GATEWAY_CAPABILITIES` is not
  a valid identifier. The precedent is `packages/twin/ai-gateway`: export the array under a normal
  identifier and add an ES2022 arbitrary-module-namespace string alias —
  `export { AI_GATEWAY_CAPABILITIES as 'AI-GATEWAY_CAPABILITIES' };`
  (`ai-gateway-capabilities.ts`) — or wire the pack in `PACKS` as a hand-written entry with an
  explicit `capsExport` instead of via `http()` (ai-gateway does both; see the comment above its
  entry).
- **The generative-LLM archetype fits clone+sed poorly.** Most of an LLM pack is domain files —
  stub content, streaming/SSE, scenario scripting, token accounting — that share nothing with a
  different vendor's protocol. Copy the *shell* (package.json, tsconfig, cli, index, the
  capabilities test) and write the domain files fresh, rather than sed-renaming the whole exemplar
  and deleting your way out.

⚠️ **If the vendor has NO canonical SDK, DELETE the devDep line — don't invent one.** The
mechanical rename rewrites the exemplar's `"stripe": "^x"` into `"<vendor>": "^x"`, which for Figma
produced a real-but-unrelated npm package (`"figma": "^22.2.1"`) and broke `bun install` outright.
Vendors with no official client are normal here (figma, most REST/XML vendors). Your fidelity test
then drives the twin over **real-transport `fetch`** instead of an SDK — §10 explicitly allows this,
and it proves the same thing: an unmodified client speaks to the twin.

## 1. File layout

A pack lives at `packages/twin/<vendor>/`. Required files (names are conventional and the gates key
on them):

```
packages/twin/<vendor>/
  package.json
  tsconfig.json                 # copy from any pack verbatim
  README.md                     # MUST have a `## Coverage` section
  client/                       # omit ONLY if the API is the product ("Does this vendor get a mirror?")
    <vendor>-mirror.tsx         # the React mirror UI (browser entry)
    <vendor>-mirror.css
  src/
    index.ts                    # runtime exports + the `pack` descriptor (NO conformance export)
    cli.ts                      # bin: serve | conformance (+ mirror only if the pack has a UI)
    <vendor>-twin.ts            # the request handler (routes → kernel writes/reads)  [+ handleXxxTwinRequest]
    <vendor>-server.ts          # FETCH-FIRST: createXxxTwinFetch (the serve path as a value) + createXxxTwinServer = an async serveHttp around it (the kernel's seam)
    <vendor>-mirror-ui.ts       # (UI vendors only) buildXxxMirrorClient + createXxxMirrorServer
    <vendor>-connector.ts       # pull/push over an INJECTED client (real SDK in prod, fake in tests)
    <vendor>-conformance.ts     # spec/recorded-diff check (dev-only; imported lazily by cli)
    <vendor>-capabilities.ts    # the MANIFEST (the real vendor surface) + xxxCapabilities()
    <vendor>-capabilities.test.ts   # baseline gate (REQUIRED — the meta-test checks it exists)
    <vendor>-twin.test.ts       # unit/integration tests
    ...domain files as needed (e.g. <vendor>-events.ts, <vendor>-ingest.ts, <vendor>-sigv4.ts)
    <vendor>-sdk.integration.test.ts   # drive the REAL vendor SDK at the twin (devDep) — proves fidelity
```

## 2. package.json (copy this shape)

```jsonc
{
  "name": "@volter/twin-<vendor>",
  "version": "0.1.0",
  "description": "Local <Vendor> twin … built on @volter/twin.",
  "author": "Volter (https://github.com/volter-ai)",
  "license": "Apache-2.0",
  "type": "module",
  "exports": { ".": "./src/index.ts" },
  "bin": { "world-<vendor>": "src/cli.ts" },
  "scripts": { "test": "bun test src/*.test.ts", "typecheck": "tsc --noEmit" },
  "dependencies": { "react": "^19.2.7", "react-dom": "^19.2.7" },
  "peerDependencies": { "@volter/world-core": "workspace:*" },
  "devDependencies": {
    "@volter/world-core": "workspace:*",
    "@volter/world-tooling": "workspace:*",
    "<vendor-sdk>": "^x",          // TEST-ONLY — never a runtime dep (a twin fakes auth locally)
    "@types/bun": "^1.2.20", "@types/node": "^24.0.0",
    "@types/react": "^19.2.17", "@types/react-dom": "^19.2.3",
    "typescript": "^5.9.0"
  },
  "engines": { "bun": ">=1.2.0" }
}
```

Rules the architecture guardrail enforces: the vendor SDK is a **devDependency only**; the pack
**`peerDepends`** on `@volter/world-core`; `@volter/world-tooling` is a **devDependency** (conformance is
dev-only).

## 3. index.ts — runtime exports + the `pack` descriptor

Export the runtime surface (`handleXxxTwinRequest`, `createXxxTwinFetch`, `createXxxTwinServer`,
connector fns, events, mirror builders) **and** a `pack: TwinPack` descriptor so tooling can
discover it. **Do NOT re-export the conformance module** (it's dev-only — the guardrail fails if
runtime entrypoints pull it in).

**The serve path is FETCH-FIRST (R12b; [the wire](./architecture.md#the-wire)).** The pack's whole
HTTP surface is a plain `(Request) => Promise<Response>` named `createXxxTwinFetch` — the
catalog's walkers (`scripts/vendor-fetch.ts`), the branch round-trip and shape parity drive it
in-process — and `createXxxTwinServer` is nothing but `await serveHttp({ port, idleTimeout, fetch })`
around the SAME closure: the kernel's HTTP seam ([architecture](./architecture.md#the-serve-seam)),
so the factory is `async` and returns the seam's `{ hostname, port, url, stop }`, its `url` a
`URL` ending in `/`. Never `Bun.serve`: the bin runs under node, and `scripts/pack-hygiene.ts`
refuses a `Bun.serve(` call in a pack that never uses the seam. Build the fetch with the kernel's
ONE adaptation, `createTwinFetchFromHandler` (the manifest door,
body read, header map, `worldNow()` stamp, and JSON/empty-body reply live there once; your pack
contributes VALUES — its manifest, header `extras`, static `handlerOptions`, extra
`responseHeaders`). See `resend-server.ts` for the canonical adapter form and `groq`-style
generative packs for the `manifest`-as-thunk + `scenarioStatus` form. A wire that genuinely
exceeds the common shape replaces the adapter with a hand-written fetch (`openai-server.ts` is
that reference); the grounds are: SSE streaming, multipart adaptation, raw-bytes/non-JSON
responses, **a COMPRESSED REQUEST BODY** (the adapter does `await request.text()`, which corrupts
gzip bytes — and a real SDK may gzip above a size threshold without telling you: `expo-server-sdk`
does it above 1024 bytes, which is invisible in every small test and appears the moment a payload
grows), **and glue-level orchestration** — auth doors in the glue, method-dependent routing
(POST-search-as-read), multi-handler write paths with event emission, or bespoke status
special-casing (`jira-server.ts` is that reference). Two conversion rules with teeth: a pack
whose server serves `/twin/scenario` MUST pass `scenarioStatus` (forgetting it silently swaps the
status JSON for the handler's 404 — no compile error will tell you); and a handler returning
`body: null`/`undefined` on a 2xx served the literal `null` through old glue but serves EMPTY
through the adapter — check before converting, don't discover it on the wire. Exactly ONE export
matching `create*TwinFetch` per index: the catalog's walkers (`scripts/vendor-fetch.ts`) and the
kernel's gates take the first such export to drive the pack in-process. Everything reachable
from the fetch — the serve path is that import graph, transitively — must be runtime-neutral: no
Bun-only APIs, no fs, state only through the kernel's tree readers and write path, never a log row
(`scripts/protocol-2.test.ts` reads the graph and refuses `listEvents`/`listActions`/
`pendingActions`/`appendEvent` on it).

**The real-system adapters are NAMED on the descriptor and anchored by convention**
(`create<Name>TwinFetch` pairs with `perform<Name>Action`, `sync<Name>FromRemote` and
`ingest<Name>Event`, same `<Name>` exactly; jira and slack are the references;
[architecture](./architecture.md#the-real-system-adapters) is the law):

- **`perform<Name>Action(execute, entry, ctx) → PushOutcome`** — perform ONE entry against the
  vendor over the kernel's `RemoteExecute` (the credential applied by the kernel; the pack never
  holds one) and return the id (and url) the vendor minted. `ctx.resolve(type, localId)` is the
  kernel's alias resolver for a reference to a locally minted subject. The kernel's head runs the
  loop — checks, the perform, the landed copy with its receipt — and a pack never confirms by hand.
- **`sync<Name>FromRemote(execute, { root, origin? })`** — the refresh adapter: read-side only,
  every push-side executor method refusing loudly. It OBSERVES through `observeResource` and
  returns a count; it never appends (the kernel folds). A credential the vendor refuses is a thrown
  refusal, never an empty account folded over observed state.
- **`ingest<Name>Event(request, { root, secret })`** — verify the vendor's signature with the
  sealed signing secret and observe what it verified. It MUST fail closed: with a secret present,
  a missing or invalid signature answers 401 and folds NOTHING — the ingest door is keyless and
  internet-facing, so an adapter that ignores the secret re-opens an unauthenticated write lane
  into the observed copy. A loose `ingest*` name is vendor surface, never an adapter (datadog's
  `ingestRumEvent` is RUM wire). §9 reviews check this line item on every new ingest adapter.

The descriptor names them — `stateSystem: { perform, refresh, ingest }` — and `index.ts` calls
`registerPack(pack)` on load; a protocol-2 pack that never registers has no perform adapter, so
push and deploy are dead surface, and `scripts/pack-hygiene.ts` says so. One more wire rule the
kernel enforces for adapter-form packs: a `body: ''` on 204/205/304 serves EMPTY.

**The descriptor is not a nameplate — it is where this pack's world-facing vendor knowledge LIVES.**
The central per-vendor tables (`SDK_TWINS`, `SDK_SCOPE_VENDORS`, `ENV_STEM_VENDORS`,
`VENDOR_WORLD_IDS`, `VENDOR_HOSTS`, `APP_READ_ENDPOINT_ENV`) are overlay targets populated FROM
the descriptors, empty or shrinking toward it; **a pack declares its facts here and never in
them**. `bun scripts/pack-facts.ts` compiles every pack's descriptor into the committed artifact
`packages/world-core/generated/pack-facts.json`, which `inject.cjs` requires and
world-runtime overlays onto those maps. Declaring a fact in **both** homes is not a
merge — it **throws** (§7 points 8, 11, 12). Read the field docstrings in
`packages/world-core/src/packRegistry.ts` before filling these in; they carry the doctrine, and
[architecture](./architecture.md#the-descriptor) holds the protocol-2 half. This is the shape:

```ts
import { referenceField, registerPack, type TwinPack } from '@volter/world-core';
export const pack: TwinPack = {
  vendor: '<vendor>',                     // MUST equal the directory name (pack-facts throws otherwise)
  transport: 'rest',                      // 'rest' | 'graphql' | 'web-api' | 'raw-tcp'
  protocol: '2',                          // the platform protocol major (PROTOCOL_VERSION is 2.0); a pack is born on it
  archetype: 'crud',                      // 'crud' | 'generative' | 'signed-protocol' | 'engine-control' | 'proxy' — pack-facts refuses silence
  bin: 'world-<vendor>',
  resources: [/* the subject types the twin projects — every type a write names */],
  specSource: '<where the manifest's surface came from>',
  description: '…',
  browserRouting: { apiPathPrefix: '/v1/', loaderHost: 'https://api.<vendor>.com' },  // browser SDK only

  // PROTOCOL 2 — the pack is a plugin (architecture.md, "Protocol 2: the pack is a plugin"):
  refresh: { every: '5m', onDemand: { atMost: '30s' } },   // + webhook: true when the vendor pushes to the ingest door
  stateSystem: { perform: perform<Name>Action, refresh: sync<Name>FromRemote /* , ingest: ingest<Name>Event */ },
  roundTrip: { method: 'POST', path: '/v1/things', body: { name: 'round trip' } },   // or a sequence whose LAST write creates
  references: [referenceField('comment', 'thing_id', 'thing')],   // a field that holds another subject's id …
  referenceTrip: { method: 'POST', path: '/v1/things/{{id}}/comments', body: { text: 'round trip' } },  // … and a write that uses it
  shapeParity: 'held',                    // once the write handler and the refresh adapter store the same shape (parityOrigin when the adapter reads its scope from the origin)
  // auth: { in: 'query', name: 'key' },  // only when the vendor does not read a replaced header (architecture D4)
  // engine: { module: 'src/<vendor>-engine.ts' },  // only when state has a second half beside the tree (a git plane, bytes, SQL)

  // Vendor knowledge the kernel must never hardcode, carried as DATA:
  rateBudget: <VENDOR>_RATE_BUDGET,       // the SAME object <vendor>-budget.ts declares (§"Budget a…")
  pullPosture: 'on-demand',               // omitted ⇒ 'on-demand'; 'continuous' needs pullPostureReason
  emitter: <vendor>Emitter,               // DELIVER support, if the pack synthesizes signed events
  conformanceFields: { /* object: { field: type } */ },

  // ADOPTION — how an app repo betrays that it talks to this vendor (§7 point 11).
  adoption: {
    sdks: ['<npm-client>'],               // every official npm client of the surface you model
    pypi: ['<vendor>'],  // the vendor's OFFICIAL Python SDK distribution names (PEP 503) — the Python half of adoption discovery and coverage; [] with a reason when no Python client exists
    scopes: ['@<vendor>/'],               // npm scope prefixes wholly owned by this vendor
    envStems: ['<VENDOR>'],               // credential-env stems: '<VENDOR>' for <VENDOR>_API_KEY et al.
    worldIds: ['<other-world-id>'],       // extra world service ids this vendor answers to
  },

  // INTERCEPTION — the vendor hosts the injector routes here (§7 point 8). `hosts` XOR `hostsNone`.
  hosts: [
    { host: 'api.<vendor>.com' },                        // exact host
    { suffix: '.<vendor>.com' },                         // per-resource-host vendor
    { host: 'www.googleapis.com', pathPattern: '^/youtube/' },  // RegExp SOURCE over the pathname
    { hostPattern: '^api\\.[a-z0-9.-]+\\.<vendor>\\.co$' },  // RegExp SOURCE over the hostname (regional families)
    { host: 'other.<vendor>.com', exclude: true },          // carve a host OUT of the includes above
    { key: '<area>', hostPattern: '^<area>\\.[a-z0-9-]+\\.<vendor>\\.com$' },  // a second routing key (<AREA>_TWIN_URL) this one twin answers under
  ],
  // hostsNone: 'why this pack deliberately has no injector entry',
  // hostsClaimed: { door: '/twin/hosts', note: 'names a person makes answer to the vendor' },  // alongside `hosts`

  // WORLD WIRING — the app-read endpoint env `volter-world init` injects (§7 point 12).
  // `endpointEnv` XOR `endpointEnvNone`.
  endpointEnv: {
    name: '<VENDOR>_BASE_URL',
    templates: { <VENDOR>_EVENT_API_BASE_URL: '${url}' },  // extra vars, optional
    note: 'grounded in <the SDK source/doc that documents this var>',
  },
  // endpointEnvNone: 'why this pack deliberately injects no endpoint env',
};
registerPack(pack);   // on load: the head resolves stateSystemFor('<vendor>') in the process that serves the twin
```

**Every field above is optional except `vendor`, `transport`, `resources` — and `archetype`,
which the type marks optional and `scripts/pack-facts.ts` REFUSES to compile without** (the
serve-family ratification is deliberately unskippable, so an undeclared archetype is RED; only the
scaffolder's closing output used to say so) — and, at protocol 2, `protocol`,
`stateSystem.perform` and `roundTrip`, without which `scripts/protocol-2.test.ts` is RED — but the
two XOR pairs are *rulings*, and silence is not one: fill in exactly one of `hosts`/`hostsNone`
and exactly one of `endpointEnv`/`endpointEnvNone`. Declaring both sides of a pair throws in
`scripts/pack-facts.ts`.

The two exemplars to copy from, one per half:

```ts
// packages/twin/stripe/src/index.ts — ADOPTION + HOSTS (the migration exemplar).
export const pack: TwinPack = {
  vendor: 'stripe',
  rateBudget: RATE_BUDGET,
  transport: 'rest',
  bin: 'world-stripe',
  resources: ['charge', 'customer', 'payment_intent', /* … */],
  specSource: 'test-fixtures/stripe-schemas.json (per-object JSON Schemas from Stripe OpenAPI)',
  description: 'Stripe REST twin — spec-correct shapes, events, dashUI mirror.',
  browserRouting: { apiPathPrefix: '/v1/', loaderHost: 'https://api.stripe.com' },
  adoption: { sdks: ['stripe'], envStems: ['STRIPE', 'NEXTPRIVATESTRIPE'] },
  hosts: [{ host: 'api.stripe.com' }],
  emitter: stripeEmitter,
};
```

```ts
// packages/twin/inngest/src/index.ts — ENDPOINT ENV (the migration exemplar).
export const pack: TwinPack = {
  vendor: 'inngest',
  rateBudget: RATE_BUDGET,
  transport: 'rest',
  bin: 'world-inngest',
  resources: ['event', 'function', 'run', 'step', 'signing_key'],
  endpointEnv: {
    name: 'INNGEST_BASE_URL',
    templates: { INNGEST_EVENT_API_BASE_URL: '${url}' },
    note: 'no injector entry: the inngest SDK is base-URL-configured through INNGEST_BASE_URL / INNGEST_EVENT_API_BASE_URL.',
  },
  specSource: '…',
  description: '…',
  browserRouting: { apiPathPrefix: '/', loaderHost: 'http://localhost:8288' },
};
```

Read stripe's in-line comments before writing your `adoption`: they record what it deliberately does
**not** claim (`stripeconnect`, whose Connect area is unmodeled) and why — a credential this twin
cannot honor is build-surface, not coverage. Over-claiming an env stem makes `covers` report a
world covered that isn't.

**Whenever you edit any of these fields, re-run `bun scripts/pack-facts.ts` and commit
`packages/world-core/generated/pack-facts.json`** — see §7 point 13.

## 4. cli.ts — serve | mirror | conformance

`serve` → `createXxxTwinServer`, `mirror` → `createXxxMirrorServer`. The `conformance` command must
**dynamically `import()`** the conformance module so it never enters the runtime entrypoint graph:

```ts
} else if (cmd === 'conformance') {
  const { checkXxxConformance } = await import('./<vendor>-conformance.ts'); // lazy — dev-only
  …
}
```

## 5. Build on the shared kernel — DON'T reinvent

Reuse `@volter/world-core`: `applyTwinWrite`, `twinResources`, `ownFields`, `subjectHistory` and
`observeResource` (protocol 2), and from `@volter/world-tooling`: `checkCapabilities` /
`assertManifestBaseline` / spec & recorded-diff harnesses. `readOnly` must forbid writes (405).
**Unmodeled ops must fail like the vendor** (4xx) — never fake success. The tree's rules for a
pack — declared resources, `_`-prefixed bookkeeping, `// cache:`/`// counter:` for the one map a
serve module may hold, `ownFields`, tombstones, a sequence as `subjectHistory`, a set as one
subject per member, ids minted from the tree — are [the tree contract](./architecture.md#the-tree-contract);
a lookup by a local id after adoption goes through `resolveSubjectId` once at the request boundary
([alias-aware lookup](./architecture.md#alias-aware-lookup-at-the-request-boundary)); a derived
field the wire serves is stored on the write that changes it, never computed at read
([shape parity](./architecture.md#shape-parity)). The notes below are the working versions.

> **Note on subject ids.** The kernel's `applyTwinWrite` resolves the resource it returns by
> **`(type, id)`** (`packages/world-core/src/serve.ts`), so two resource types sharing an id do NOT
> collide — the kernel does not require type-prefixed ids. Still prefix your ids wherever the real
> vendor does (`flag_1`, `key_1`, `evt_<hash>` — e.g. sentry `rule_`, `key_`; stripe `ch_`):
> vendor-faithful ids are part of the surface. Either way, a failable verify (assert the returned
> `id`/values, §6) is what catches a wrong-resource bug; a status-only one (`status === 201`) does not.
>
> **How you MINT an id matters as much as its shape:** derive the next id from the id-SET already
> in state (scan the projection) or use entropy (`crypto.randomUUID`) — **NEVER a module-level
> counter or a row count**. Three packs' adversarial reviews independently found collision/
> data-loss bugs of exactly this class: a count-mint collides the moment a pulled vendor id sits in
> a gap above the row count — local creates count upward until they reach the pulled id and
> silently clobber it.
>
> **Read vendor fields through `ownFields`.** The tree's resource envelope has `id`, `type` and
> `updatedAt` for the subject. Vendor fields with the same names are retained separately by
> `treeResources`; `ownFields(resource)` restores them. Do not assume a spread or JSON serialization
> of the resource envelope returns the vendor payload. See `packages/world-core/src/log.ts`.
>
> **R12b is module-level.** Keep filesystem and process helpers outside the serve module; an
> unused import still enters that module's dependency graph.
>
> **A vendor whose only READ verb is POST is INVISIBLE to the invariant harness and must declare
> a store door.** `scripts/invariants.ts` decides what a read is with `st.method === 'GET'` and
> nothing else, so a replay whose reads are POSTs (Expo's `getReceipts`, a POST-search-as-read
> API) presents an all-empty read set and the row fails. The store door is not only for write-only
> ingestion twins — it is for any pack whose reads do not travel by GET.
>
> **A local write is an occurrence.** Two calls are two entries even when their content and
> frozen timestamp match. Kernel write IDs include an occurrence ordinal. At-most-once behavior
> is opt-in through `idempotencyKey`, scoped to service, operation, subject and key. A vendor that
> supports idempotent creates must resolve that key before minting a new subject ID.
>
> **Observation and local writes share one tree.** Write only the fields the operation owns;
> an explicit null is a real update. Refresh uses `observeResource`, with complete vendor reads
> and vendor-specific handling of missing resources.
>
> **Landing replaces confirmation suppression.** A landed entry is held by the parent with its
> receipt; reads avoid applying the branch copy twice. Preserve compound projections and vendor-ID
> aliases through the kernel. Verify the tree after landing, including related resources and reads
> addressed by the original local ID. Let the kernel own landing rather than rebuilding suppression
> with a pack-local log fold. The compatibility name `confirmAction` now performs landing; its
> name is not a description of the old confirm-and-suppress model.

## 6. The capability manifest (the heart of it)

`<vendor>-capabilities.ts` is the **real vendor surface as the denominator** (not what you've built
— authored top-down from the vendor's API). Each entry: `{ id, area, title, dimension, tier,
expected, verify? }`. Tier honestly (core → common → niche). A capability counts
**done only when its `verify()` passes**; `verify()` is **offline + deterministic + FAILABLE** — a
real create→read→assert (and assert the vendor's negative 4xx paths). UI capabilities: assert the
built mirror's markers **AND** that the screen is data-coupled to real twin state (start the mirror
server / read the same projection — not marker-only). Anything not modeled yet → `todo`. There is
no other status: a twin is a deterministic, offline model of the vendor's API contract, and "the
real thing" (live model output, real delivery, live data, infrastructure physics, hosted pixels,
real external trust) is never a capability the twin lacks — the labeled stub, the offline
lifecycle, the seeded credential ARE the twin's answer. Never file those as gaps.

`<vendor>-capabilities.test.ts` (REQUIRED) gates honesty via the shared baseline:

```ts
import { assertManifestBaseline } from '@volter/world-tooling';
import { <vendor>Capabilities, <VENDOR>_CAPABILITIES } from './<vendor>-capabilities.ts';
// asserts: 0 regressions, done>0, total>=50, unique ids, todo>0 (never 100%).
```

### Discovering the real vendor surface (so the denominator is honest)

The manifest is the vendor's **real** surface, not a list of what you built — so you have to go find
that surface, top-down:

1. **Read the vendor's API reference / OpenAPI / GraphQL SDL.** For REST vendors, list every resource
   type × every method (Stripe ≈ 80 resources × 4-6 methods). For GraphQL, read the SDL's `Query` +
   `Mutation` + the object fields. Skim the changelog for recent additions. When the vendor
   publishes a **first-party OpenAPI document**, fetch it and ground the denominator in it top-down
   — then **mechanically diff every status code your manifest asserts against the spec's
   responses** (a one-off script is fine); that diff caught five wrong asserted codes in one pack.
   **Run that diff against the FRESHLY FETCHED spec: the committed census fixture carries no status
   codes at all.** It used to carry the first one per operation, which read as the operation's whole
   response set — a 2026-09-03 build diffed against it and called four correct statuses mismatches.
   Carrying one of several is worse than carrying none, because it looks complete, so the derivation
   now carries none and every fixture says so. The fixture is an operation INVENTORY by design
   (path × method is the census denominator); status codes are not what it is for, and the vendor's
   own document is the only oracle for them.
2. **Map to capabilities at ONE granularity and stick to it** — fine-grained per-operation (Stripe:
   `customers.create`, `customers.search`) or per-domain (Linear: `Issues: create`). Match your
   exemplar's style.
3. **Tier by real usage, not by what's easy:** core = first-week-of-every-integration; common =
   frequent; niche = admin/long-tail/power-user. Don't tier up to inflate coverage — if <~50% of
   integrations touch it, it isn't core.
4. **Most entries start `todo`.** It is correct and expected that a new pack reads LOW (e.g. 15 done
   / 120 total). Better a broad honest denominator at 12% than a thin self-portrait at 90%. Growing
   the denominator later (coverage % dropping) is success, not regression.
5. **Every entry is `done` or `todo`. There is no exclusion.** A twin is a deterministic, offline
   model of the vendor's API contract: the app cannot tell a simulated head from a real one, and a
   manifest verify runs offline. So "the real thing" — live model output, real delivery or egress,
   live or global data, infrastructure physics, vendor-hosted pixels, real external trust, real
   human review — is never a capability the twin lacks; the labeled stub, the deterministic
   value, the offline lifecycle and the seeded credential ARE the twin's answer, and the endpoint
   that serves them is `done`. Never file "not real" as a gap. Everything a local twin could serve
   and does not — any wire protocol included — is a `todo`. Nothing is ever "chosen away".

If your pack commits a `<VENDOR>_AREAS` census gated by `assertAreaCensus` (the TWIN-87 pattern —
github, openai, polar, … ), know its limit: it asserts a **two-way bijection between the census
array and the areas your manifest mentions** — a vendor area missing from BOTH passes silently.
The census has teeth only if the array itself is enumerated **top-down from the vendor's docs
nav / OpenAPI tags**; deriving it from your manifest's areas makes the check a tautology. The
denominator audit is the enumeration, not the assertion.

### Writing a FAILABLE verify() (so "done" means done)

`verify()` must FAIL if the capability is broken. The test: *imagine the handler were deleted/returned
`{}` — would this still pass?* If yes, it's a false-green. Rules:

- **A READ-ONLY vendor still has to be seedable — add twin-only control routes.** Several vendors
  expose no write endpoint at all (figma is read + comments; openweather/googlemaps/mapbox/webrisk
  are pure lookup), so "seed through the twin's write path" has no vendor path to use. The
  convention is a small set of **twin-only routes outside the vendor's surface**, which the verify
  seeds through and a real client never calls: `POST /twin/observation` (openweather),
  `POST /v1/_twin/...` (figma, which also arms a deterministic 429 that way). Keep them clearly
  namespaced and out of the capability manifest — they are test scaffolding, not vendor surface,
  and counting them as coverage would be padding.
- **A WRITE-ONLY protocol has the inverse problem — do not invent a vendor read path.** SMTP can
  accept a message but has no verb that returns submitted messages. Its twin-only `/twin/messages`
  inspect sidecar makes local outcomes usable, but stays outside the vendor capability manifest for
  the same reason the seed routes above do. Connector `pull` observes only what the real protocol
  actually reveals: SMTP's 220 banner and EHLO capability list. That is genuine remote state and
  makes the twin advertise the relay it mirrors without pretending the relay has a mailbox API. If
  a write-only target exposes no readable handshake, metadata, or receipt at all, record pull as a
  reasoned impossible connector gap; never fabricate resources merely to satisfy the usual pull
  shape.
- **Fresh state every time — and thread the `root` EVERYWHERE.** Use the pack's `withRoot`/temp-root
  helper (or `mkdtempSync`) so the verify proves the capability *from nothing* — never relies on
  pre-seeded/dirty state. An omitted `root` does not error: the kernel falls back to the operator's
  **real `~/.volter` state dir**, which is gitignored, so nothing catches the leak — the verify
  passes while poisoning later runs. Thread a **fixed `occurredAt`** (the exemplars use a pinned
  constant) so ids/timestamps are deterministic. A verify that REPEATS an identical transition
  no longer needs distinct instants to keep both writes: a local vendor write is an OCCURRENCE
  (two calls are two actions, even in one instant — see §5's kernel note), so the old coin flip
  between `replayed` and a second write is gone. Pin instants anyway, for ORDER: two events that
  should read in sequence deserve distinct `occurredAt` values, and a pinned one makes that chosen
  rather than rolled.
- **…but fresh-root-only verifies structurally CANNOT catch dirty-state bugs** — behavior that only
  breaks *over prior state*. Every stateful pack must also carry at least one **deliberate
  dirty-state verify** (delete→recreate, interleaved minting) that builds up state and then asserts
  the invariant that survives it. This is not hypothetical: three packs' independent §9 reviews
  each found exactly this bug class — gcs re-issued a deleted object's generation on recreate
  (`gcs.generations.overwrite_new_generation` now pins the ratchet across delete+recreate),
  supermemory's sticky tombstone swallowed a re-add and its id mint had to count tombstoned rows
  (`supermemory.documents.readd_after_delete`), and assemblyai's deleted transcript regenerated
  content on a later poll (`assemblyai-twin.ts`, the redaction guard). A manifest whose every
  verify starts from a fresh root has proven nothing about any of these.
- **The kernel's content+millisecond dedupe (§5) needs different medicine per PATH: pin in
  verifies, MOVE in production defaults.** Handler-side, a high-churn twin whose semantics revisit
  values folds a per-write ordinal into the write's fields (`upstash-store.ts`, `rev`).
  Connector-side, pull entry points need a MOVING `occurredAt` default — now, forced strictly
  increasing within the process (`dynadot-connector.ts`, `pollTimestamp()`) — never a pinned
  constant: the kernel hashes an observed event over (`occurredAt` + post-state), so under a fixed
  poll time a vendor value that REVERTS (A→B→A across polls) collides with its own earlier
  observation, and the kernel observation path reports `deltasAppended: 1` while nothing lands — a phantom delta,
  with the projection still serving the stale value. Audit note: `calcom-connector.ts` still ships
  a pinned `PULL_AT` as every pull's default — a known live instance of this hazard, not a pattern
  to copy.
- **A REFUSED pull is NOT an empty account.** A connector that maps a failed or refused vendor
  reply to an empty resource list hands the kernel observation path an empty account to fold OVER real observed
  state. Throw on refusal instead — and mind vendors that answer failures with HTTP 200 plus an
  error envelope, where a status check alone cannot tell refusal from genuine emptiness
  (`dynadot-connector.ts`, pinned by `dynadot.connector.pull_refuses_a_200_failure`).
- **Real create → read → assert VALUES**, not just a status code. ✗ `return res.status === 201`
  (passes on a malformed body). ✓ `return ok(res) && field(res,'email') === 'a@b.test' && id(get) === id(res)`.
- **Assert the negative path the title implies** — the vendor's 4xx for missing/invalid input
  (unknown id → 404, bad enum → 400, wrong state-machine transition → 400). Unmodeled-ops-fail-like-
  the-vendor is a *testable* property.
- **UI capabilities — unless the API is your vendor's product** (settle that with "Does this vendor
  get a mirror?" above; a pack with no mirror has no UI caps at all). When there IS a mirror:
  **every UI capability must be
  DATA-COUPLED** — seed real state through the twin's write path, fetch the SAME api/projection the
  screen reads, optionally render the mirror's own component over it, and assert the seeded value
  survives (use the shared `uiDataCoupled` factory from `@volter/world-tooling`; see `jira.ui.backlog`,
  `sentry.ui.issues`, `posthog.ui.events`). This is not just for the main list/detail screens — TWIN-14
  / B4 killed the old marker-grep escape hatch (`uiHas(...)`, which only proved a screen's literal
  strings survived in the built bundle, not that data flows through the twin) for EVERY UI capability,
  including nav shells, search inputs, and tone/badge pills: a nav shell's honest data-coupling is
  proving every screen it routes to reads live data (see `sentry.ui.nav`); a tone/pill's is proving the
  mirror's own tone-mapping function produces the seeded tone (see `sentry.ui.level_status_pills`,
  `calcom.ui.status_pill`); a search input's is a real filtered fetch (see `sentry.ui.search`) or, when
  the vendor's search truly filters client-side only, asserting the seeded row is in the data source the
  search operates over (see `stripe.ui.search`). The strongest form of "render the mirror's own
  component" is literal: server-render the mirror's REAL exported component over the twin's
  projection (`react-dom/server`'s `renderToStaticMarkup`) and assert the seeded value in the
  emitted HTML — the claim is then about the shipped component, not a lookalike, and many packs
  now do exactly this (dynadot's empty-state verify renders the real component AND proves the
  empty state gives way once state exists). If a screen truly has no seedable data at all (e.g.
  "does the client transpile", or a documented no-recordings replay shell), the honest fallback is a
  structural verify — dynamically import/render the REAL component and assert on its actual output —
  never a bare marker-grep and never a tautology (see `aws.s3.ui.transpiles`,
  `posthog.ui.session_replay`). The mirror's **main view** — the one showing the vendor's core modeled
  data (events, issues, models, …) — is a data screen and is **NEVER "chrome"**; don't lenient-classify
  your main screen as chrome to dodge data-coupling — a §9 review will demote it (it did for
  OpenRouter's `ui.mirror_markers`).
- **Markers must survive minification.** A class built from a template literal (`` `event-${tone}` ``)
  is erased by the bundler — asserting `event-pageview` will fail. Assert **static literal**
  class names or the **pure-function string outputs** the component actually emits (dump the bundle
  once if unsure).

Copy a real exemplar verify rather than inventing the shape: `clerk-capabilities.ts` (CRUD + crypto),
`stripe-capabilities.ts` (search/state-machines/negative paths), `linear-capabilities.ts` (GraphQL).

### Scenario scripting (generative packs)

A generative twin's default stub is generic; a **scenario** lets a caller script the exact
deterministic responses a flow under test needs — a JSON file of ordered `match → response` rules,
first match wins, no match falls back to the normal stub — while the protocol envelope stays
vendor-faithful. Four packs are the precedent: `anthropic-scenario.ts` (scripted assistant turns
incl. `tool_use`, unary + SSE), `gemini-scenario.ts`, `deepgram-scenario.ts`,
`assemblyai-scenario.ts`. The conventions they share:

- **Strict loud-fail load validation** — an invalid scenario file throws at load with what's wrong,
  never a silent ignore-and-stub (a mis-typed rule silently falling back is a fake success).
- **Wired at server construction** (`createXxxTwinServer({ scenarioPath })` / an env var / a CLI
  flag), scoped to the generative endpoint only — never to CRUD/batch surfaces.
- **Kept OUT of the capability manifest.** Scenario support is twin-only scaffolding for eval
  worlds, not vendor surface — counting it would pad the denominator (same rule as §6's twin-only
  seed routes; `deepgram-capabilities.ts` states this in-line). Gate it with a dedicated
  `<vendor>-scenario.test.ts` instead. (`assemblyai` counts a `scenario` area in its manifest —
  treat the manifest-free majority as the convention, not that outlier.)
- **Faults are the kernel's grammar, not the realizer's.** Any handler
  may carry `fault`: `{ kind: 'slow', ms }` holds the answer and then serves (it may still carry
  `respond`); `{ kind: 'status', status, retryAfterSeconds?, message? }` serves a 4xx/5xx in
  THIS vendor's envelope — give the adapter a `renderFault` so a 429 wears the shape the
  vendor's 429 wears (openai-scenario.ts is the reference); `{ kind: 'drop', holdMs? }` never
  answers. Same `once`/`phase`/`scope` as any handler, so a mid-story outage is
  `{ on, fault, once }`. Decide through `engine.serve(req)` (it honors the fault; a hand-written
  path calls `scenarioFaultResult`), never `next()` alone — a realizer that reads `next()` serves
  content through a fault. The load is strict-loud: a status fault is 400..599, never a success.

### The conformance check needs the same teeth: delete the handler, it must go RED

Hold `<vendor>-conformance.ts` to the failable-verify test — *if the handler it certifies were
deleted, would this check fail?* Two packs' §9 reviews independently found deletable-API
false-greens here, in two escalating shapes:

- **Two constants asserting about each other is not a check.** tinybird's first version compared a
  hand-written `implementedEndpoints` array against hand-written expectations in the same file —
  deleting the entire router left it green. The trap generalizes well past conformance files: a
  `verify()` or test that IMPORTS the constant the handler itself serves and asserts equality is
  the same disease in miniature — both sides drift together, so the assertion can never catch the
  value going wrong. Write the expected value as a LITERAL in the check. `firecrawl` does this
  deliberately for its vendor-faithful response URLs and says why in-line: an assertion against the
  handler's own `FIRECRAWL_API_HOST` "would be a tautology that could not catch the host drifting"
  (`firecrawl-budget.test.ts`, allowlist notes).
- **A PARAMETER graduating from refused to modeled is its own hazard, and the doctrine below is
  stated per-OPERATION.** Removing a name from `assertModeledOptions` does not open one hole, it
  opens one per route that reads it: a 2026-09-03 lane took `resourceId` off the refused list and
  silently created three wrong-subject holes at once. When you start honouring a parameter, walk
  EVERY operation that now accepts it and prove each one reads it, rather than accepting it and
  dropping it — which is a fake success wearing a parameter's clothes.
- **A conformance probe whose EXPECTED answer is a 404 may be indistinguishable from the router's
  catch-all.** The trap below is about grading only the router's own miss on the DISPATCH side;
  this is its inverse, and it is a distinct trap. If your probe expects the miss envelope, it
  passes with the whole feature deleted. Make it assert something only the real path can produce.
- **Probing endpoints but grading only "not the router's own miss" has teeth exactly at the
  dispatch and nowhere deeper.** tinybird's replacement checked each probe wasn't the router's
  `Not found:` response — but sub-handlers fall through to their own 405/404, so seven of the
  twenty-one claimed endpoints could lose their handler branch and conformance stayed green; the
  whole Tokens API was deletable branch by branch.

The bar: **one real request per claimed endpoint, asserting the OUTCOME a live handler produces**
— an expected status set plus a predicate over the body — against a throwaway root. When a
vendor resource carries a field name the kernel projection reserves (`type`, `id`,
`updatedAt` — exactly those three, actions.ts), the probe must assert **the field round-trips**, not just the status: mistral's
conversation entries carry a literal `type` the projection silently dropped, and every
status-only check sailed past it. Check the
probe table and claimed snapshot as a two-way bijection, then close the third direction with a
hand-written `ROUTER_SURFACE`: enumerate every method/path pair the router branches on, exercise
it, and fail when it answers anything other than the vendor-shaped not-found envelope without a
matching snapshot claim. Probe⇄snapshot alone is blind to *served-but-unclaimed* surface; the
`ROUTER_SURFACE` pass in `googleoauth-conformance.ts` caught a real extra endpoint. See also
`tinybird-conformance.ts` (`PROBES`), `upstash-conformance.ts`, and the
azureformrecognizer `conformance.endpoint_probe` pattern. A twin whose handler returns `{}` must
fail this; that is the whole point. The router census is itself hand-authored, so §9 still compares
it with the actual dispatch branches — and **every census entry needs a LIVE request proving its
branch is reached**: a `ROUTER_SURFACE` that is a copy of the probe-table keys reads as compliant
while asserting nothing (the groq build wrote exactly that tautology and §9 rejected it). The
census earns its name only when deleting a router branch reddens the entry that names it.

**When you cannot ground the vendor's error CODE, ground the class and say so.** A refusal must
still refuse — an unmodelable code is never a reason to answer 200. Use the nearest code you can
actually cite from a first-party source, state at the seam what you grounded it on, and file a
`todo` to pin the exact one. What you may not do is invent a number: a fabricated `error_code` reads
as first-party fact to every consumer and to every later reader of your own pack, and unlike an
invented ROUTE nothing will ever contradict it. This is the same rule as the surface one below,
applied one level down.

**And know the INVERSE false-green: serving surface the vendor doesn't have.** There is usually no
mechanical oracle for an invented operation, error code, or header — the twin can merely agree with
itself — so the top-down denominator audit and a §9 skeptic must read the surface against the vendor
spec in BOTH directions. But do not overstate that as "absence can never be asserted." When the
vendor documents a **closed parameter set**, a literal allowlist is an oracle: assert the accepted
keys/values biject exactly with that documented set and exercise a value outside it. That technique
is what made Google OAuth's invented authorization-response `iss` revert cell redden. Keep the
expected set literal in the check; importing the router's own constant makes the assertion a
tautology. **When first-party sources conflict, the oracle has a precedence order:** an
OpenAPI/Smithy-GENERATED type outranks a rendered docs example (a prose example *looks* exactly
like a documented closed set and is not one — groq's `ErrorObject` declares ten keys where the
docs example shows two), and where only prose exists, assert *subset-of-generated-type* rather
than exact bijection.

Three other §9-caught precedents: ahrefs answered "twin does not model `history='live'`" on an
endpoint the vendor never gave a `history` parameter (fixed per-endpoint — only an option the
endpoint actually DECLARES can be an unmodeled option, `ahrefs-twin.ts`); figma declines to
fabricate rate-limit response headers the vendor doesn't document (`figma-twin.ts`); plain refuses
to invent a `MutationError` its SDL never defines (`plain-twin.ts`).

## 7. Central wiring — ask the gates, then read this list

> **The count in this heading used to be the point, and it was wrong every time it was written:**
> "exactly ONE place / everything else auto-discovers", then seven, then ten, then eleven, then
> thirteen. The first cost two 2026-07-25 builds a red gate each; six builder runs then tripped
> over rosters the "seven" list omitted; tinybird/upstash surfaced an eleventh; the eleven
> omitted the endpoint env and the pack-facts artifact; and the 2026-09-02 expo build found the
> thirteen-list had gone stale in two places and short in four. A hand-maintained mirror of what
> the gates enforce drifts from the gates. So it is no longer numbered as a claim to completeness.
>
> **THE AUTHORITY IS `bun scripts/verify-pack.ts <vendor>`.** It runs the drift gates and names
> what is unwired, in seconds. Run it early and often while building; treat a red from it as the
> list, and treat what follows as the map that explains each red rather than as the definition of
> the set. If you find a wiring point this section does not name, **REPORT it** — this recipe is
> doctrine-bearing and AGENTS.md reserves it for the orchestrator, who edits it in the same pass
> that reads your report. A build lane that patched it directly would be the one thing this
> section is not allowed to become: many hands, no ruling. (Corrected 2026-09-03 after a brief of
> mine told a lane to edit it and the lane rightly refused.)

The points below are committed censuses, rosters, and — for the descriptor points — **fields on
your own pack descriptor** that a generated, committed artifact publishes:

### Start here: `bun scripts/scaffold-pack.ts`

**A scaffolder exists and it mechanizes most of this section.** It is idempotent — every step is
skip-if-present — so it composes with `scripts/spec-compile.ts` / `scripts/smithy-compile.ts` seeds
and with hand-written files, and re-running it after you have edited is safe:

```bash
bun scripts/scaffold-pack.ts <vendor> --base https://api.<vendor>.com --host api.<vendor>.com \
  [--host <extra>]... --auth-env <VENDOR>_API_KEY [--auth-header <name>] [--sdk <npm-pkg>]... \
  [--spec-kind openapi|smithy|none] [--spec-url <url>]
```

It writes the pack shell (package.json, tsconfig, README, twin/server/connector/conformance/
capabilities + their tests, cli), the **v2 `pack` descriptor** in `src/index.ts` with your
`--host`/`--sdk`/`--auth-env` compiled into `hosts`/`adoption` and a TODO block for the
`endpointEnv` XOR `endpointEnvNone` ruling, the registrations for points 1-2, the censuses for
points 3-5, and then regenerates point 13. It prints one line per wiring point (`wrote` /
`skip (exists)` / `FAILED`).

**What it deliberately does NOT do** is decide anything: every judgment slot is a loud
`SCAFFOLD TODO(A1|A2)` marker (`grep 'SCAFFOLD TODO'` after running), no capability is born `done`,
and it writes **no** `VENDOR_HOSTS` and **no** `SDK_TWINS` entry — those are the legacy homes, and a
fact in both places throws (points 8 and 11). It also does not touch points 6, 7, 9 or 10, and
`assertManifestBaseline` stays RED until you earn a real `done`; that red is the honest state of a
scaffolded pack, not a defect. Copy-from-exemplar (§0) is still the right move for *domain* code —
the scaffolder gets you the wiring, the exemplar gets you the shape.

**Four of the thirteen are CONDITIONAL — and "not applicable" is a decision you make against the
stated rule, never a point you may skim past:** the journey registry (10) binds iff your
census.json `ui` slice declares `needsUi: true`; the `WIRED` budget roster + the D8 mirror line (9)
bind iff your pack declares a budget (a budget-less pack takes the `EXEMPT` roster instead — one of
the two, always); a hand-written `VENDOR_BURST_ANCHOR` figure (9) binds iff your declared ceiling
admits a bigger burst than the fallback's; the `mirrorMutations` seam (2) binds iff you ship a
mirror. Everything else is unconditional.

**1. The manifest registry — NOTHING TO EDIT, and that is the point.**
`scripts/capability-manifests.ts` used to be a hand-written map and this list's one hand-authored
logic edit. It now GLOBS `packages/twin/*` and hard-throws on a vendor dir whose manifest module or
conventionally-named export is missing. So a pack is registered by EXISTING in the right shape:
name the export `<vendor>Capabilities` (and `<VENDOR>_CAPABILITIES`) and you are wired. The failure
mode moved with it — a wrong export NAME is now a throw from the registry, not a silently absent
pack.

**2. The gate file** — `packages/twin/<vendor>/gate.ts` (the gate plane; the scaffolder
births it). Dev-plane TypeScript, never imported by `src/`; `scripts/mutation-test.ts` SCANS every
pack's gate.ts — that scan IS the roster (there is no central array). It exports one `gate: PackGate`
(or `areaGates: NamedPackGate[]` for a multi-area dir like aws/azure) — the vocabulary is
`scripts/gate-kit.ts`:

```ts
export const gate: PackGate = {
  ...httpGate('<vendor>', 'handle<Vendor>TwinRequest', [/* extra mutations */], { /* allow */ }),
  // mirrorMutations / connectorExtraMutations / isolatedMutations — see below
  // burstAnchor: the vendor's documented per-minute allowance, by hand (rate-budget-isolation)
  // conformanceBaseline: this pack's reasoned gap deferrals (twin-conformance)
  // budgetExempt: a budget-less pack's excused live call sites, exact counts
  replay: [ /* the R9 resource-level sequence — below */ ],
  // streamRefusal: a pack that renders text/event-stream declares one pre-frame refusal (R6b) — below
};
```

**The `streamRefusal` declaration (invariant R6b — a refusal refuses on the wire).** If any
source under `src/` renders `text/event-stream` (comments do not count; the manifest does not
count), the matrix requires `gate.streamRefusal` (module-level `export const streamRefusal` in
a multi-area dir, like `replay`): ONE streaming request your vendor refuses BEFORE its first
frame — an unknown model, a missing required field — with `status` pinned only when your own
tests pin the vendor's. The row sends it through the fetch adapter and holds the answer to a
4xx that is not an event stream. The defect it exists for: a server that opens a 200
`text/event-stream` and pushes the refusal out as a lone `data:` frame, which a client that
would have raised on the status silently reads and continues past — seven packs shipped it
before the row existed. Collect the events first, then decide the status, then open the
stream (`openai-server.ts` is the reference shape); a vendor whose stream has no pre-frame
refusal declares a string saying why, and the cell reads n/a. `bun scripts/invariants.ts
--check --explain <vendor>` shows what the cell actually saw.

**The `replay` sequence (invariant R9 at the resource level)** — `gate.replay`, or a module-level
`export const replay` in a multi-area dir (aws, azure: one fetch adapter serves every area). 3–6 steps the harness runs through
your fetch adapter on two fresh roots under ONE pinned world instant: 1–3 realistic writes, then
2–4 reads; every read is byte-compared across the roots and re-served on one root. What the pilot
sweep learned (2026-09-02, ten packs, four real defects):

- **Auth is per-pack and comes first.** The harness sends `authorization: Bearer twin_replay` by
  default and FAILS the row on any step answering ≥300 — a pack with vendor-shaped auth (Postmark's
  server token, Mailgun's Basic `api:<key>`, Notion's `Notion-Version`) puts its own headers on
  every step. Read your `<vendor>-server.ts` for header threading before writing a step.
- **A redirect that IS the vendor's success is declared, exactly.** An OAuth consent post answers
  a 302 to the app's callback carrying the code; the step declares `redirect: '<the exact
  Location>'` and the row fails on any other status or Location. The authorize GET that records
  the request the post settles is marked `writes: true`: it runs as a write, once and never
  compared (a re-serve after the settle mints a new request, as the vendor would). The code and
  the tokens it redeems into are then read off two fresh roots and named literally (tiktok,
  xidentity, googleoauth, paypal).
- **Content type is part of the wire**: a form-encoded vendor (Stripe, Mailgun) overrides the JSON
  default and passes a body STRING.
- **An upload is raw bytes.** A `Uint8Array` body is sent as it is (a video chunk, with the step's
  own `content-type`), and a binary read (the served video) is compared byte for byte, never as
  decoded text. A step's `capture` may read a dotted path into its answer (`data.upload_url`), and a
  filled `path` that is an absolute URL on the replay's origin (an upload URL the twin minted) is
  sent to it as is. `shape` checks a value drawn from entropy against a pattern. On a write's answer
  the shaped fields are the ONLY part checked, since writes are never compared (tiktok).
- **"Is there a read at all?"** is the blocking question. A write-only ingestion twin (SendGrid,
  Mixpanel, Segment) has no read surface — which is exactly why its nondeterminism went unseen — so
  it declares a store door first (`stores` on the adapter; `GET /twin/store/<name>` is the read).
- **A `_twin` seeding route can be the only reachable write** when the vendor's object graph has no
  create-from-nothing (Notion's root arrives by the kernel observation path): use what the pack CAN create from an
  empty world — often its webhook/subscription surface.
- **Make one write depend on another's state** (a booking on a seeded event type, a payment intent
  on a customer) so a read carries derived fields, not echoed input. At least one read must be
  non-empty (an all-empty replay fails the row).
- **A GET-only command API** (Dynadot's api3: register/set/delete are all GET) reads as
  `0 write(s)` to the harness and every step is RE-SERVED, so the sequence must be state-neutral
  (open with a seed, close with the delete) or the second pass sees the world it left behind.
- **A vendor that refuses with HTTP 200** (a `ResponseCode: -1` body) can pass the row on a
  sequence of refusals: read every reply once by hand — the reads must carry DERIVED state
  (a price drawn off a balance, a nameserver block that changed), never an error envelope.
- **A real-time timer is a determinism defect by construction** (sendblue's `setTimeout`
  advancing a delivery 25 ms after the response): served content must be a function of the
  request and stored state, so an async lifecycle is a POLL-FOLD — each read folds the next
  step from the stored row + `req.occurredAt` (assemblyai, azureformrecognizer; svix-runtime's
  "no wall clock, no timers" is the doctrine), and a status callback fires as part of the fold.
- **A secret derived from the root's PATH** (`sha256(root)` into an api key or signing key) is
  environment, not state: identical worlds in different directories disagree. The shape that
  keeps cross-root distinctness honest is a seed generated ONCE per root and PERSISTED as state
  (fal's `signing_key`; the contract's one R9 exception) — the path never enters a served byte.
- **An ordinal alone can resurrect a deleted subject** (twelvelabs: delete frees the name and
  drops the live count, the next create re-mints the dead id and the kernel revives it with its
  old children): a mint probes every id the type has EVER held, soft-deleted included, and
  advances past any taken value — the count is the seed, the probe is the guarantee.
- **The declaration is a checked claim** (R2 resource-level): a declared type nothing creates
  and nothing serves is dropped; a pull-only type the twin serves once pulled is adopted as
  debt, never faked with a seed; everything else the replay extends to.
- **Count-before-insert is safe within a process, not across them**: `applyTwinWrite` has no
  await between the mint and its append, so mint→append is one synchronous tick in-process;
  two processes writing one root at one instant is the only race, and twins-host runs one
  worker per vendor. A seed with NO ordinal (`${instant}:${name}`) is a defect even in-process —
  a frozen clock re-mints the same record and resurrects a consumed token, and a
  replay-detection short-circuit keyed on such a seed silently drops the second same-instant
  action.
- **Every read comes after every write.** The harness re-serves the READ SET ALONE on root A
  after the full sequence, so a read placed between writes sees two worlds (clerk's
  organization list moved by a later invitation's `pending_invitations_count`) and fails
  "differs between two serves of the same state" although nothing is nondeterministic.
- **Gating headers are per-route, not per-pack** (anthropic: Managed Agents routes 400 without
  `anthropic-beta: managed-agents-…`, files/skills 400 WITH it) — read each route's guard.
- **A resource may exist only as a consequence of another write** (cloudflare's
  `durable_object_namespace` is born from a script deploy's `migrations`): the covering step is
  the parent write, and the read proves the child exists.
- **"No literal can authenticate" has one answer**: a credential the world MINTS from entropy
  (never the root path) cannot be named by a gate, so the virgin world adopts the first
  well-formed credential a caller presents over the wire and rejects every other afterwards
  (sendblue's `credential` resource, established under the actions lock; read-only worlds
  accept without persisting) — the replay's first step seeds it, every later step names it, a
  mismatch 401s and reddens the row. A permanently accepted default constant is a backdoor.
- **A GET that moves state is a write, never a read**: a metered read that renders the meter it
  just moved (scrapecreators), a status poll that advances a job (twelvelabs), a key folded in on
  first touch (fal, replicate) — each is marked `writes: true`, runs once and is never compared;
  the types it creates are reached, not adopted.
- **Resource coverage is a finder of R9 defects**: replicate's webhook secret — the world's
  path in a served byte — sat green until the R2 declaration forced its read into a replay.
- **Writes that arm a delivery path go LAST among the writes** (postmark's webhook registered
  after the sends): "all reads after all writes" is necessary, not sufficient — a covering
  write after the arming one makes the harness deliver to the open internet.
- **Gating is per route family AND per credential position**: postmark's account routes take
  a different header.
- **Platform-authored types are (b), not (a)** (supabase's backups, network bans, access
  tokens): no route and no pull creates them — only the real vendor's own scheduler, abuse
  detector or dashboard — yet a serve path reads the stored rows back. The (a) test is
  two-sided: nothing creates it AND nothing serves it. What separates (b) from (c) is whether
  the pack already OWNS an honest `_twin` door for the act (xidentity's rate window → (c)); a
  door that would have to be invented to fabricate the artifact the twin's own gate exists to
  enforce is not honest, so the type stays (b).
- **Disposition (b) by construction**: a type the connector observes through the kernel and a served
  list merges, whose create-mutation the vendor SDL does not model (linear's project/cycle/
  user/workflowState throw UnsupportedOperation; groq's and hubspot's pulled overlays) — a true
  declaration the replay cannot reach; report it, never fabricate a seed for it.
- **The fastest loop** is a throwaway probe mounting `WORKER_VENDORS[vendor]` on two fresh roots
  under a pinned `TWIN_WORLD_CLOCK_FILE` and diffing the bytes — it names the diverging field;
  `invariants.ts --check` only says which read differs. Name the probe after your lane
  (`probe-<worktree suffix>.ts`): the session scratchpad is shared across parallel lanes and a
  role-named file gets overwritten mid-run by a sibling pointing at ITS worktree.
- **`twin-capabilities.ts --write-baselines` is catalog-wide** and ratchets every pack whose done
  count moved (a parallel lane's fly and resend rode along); a lane writes ONLY its own pack:
  `--write-docs <vendor>` for the generated docs and the one `baseline` line in its census.json
  by hand, or revert the collateral in the same commit.
- **A red is a real defect**, fixed the resend way: ids minted from
  `${type}:${occurredAt}:${count-before-insert}` through a stable hash (mint INSIDE the actions
  lock from the ordinal the write takes when two identical events in one millisecond must both
  land); timestamps from `req.occurredAt` (the adapter stamps it from the world clock — a handler
  that reads `new Date()` or `Date.now()` is the defect). Never weaken the harness; never `--adopt`.

One field the seams key on lives in YOUR manifest, not here: each saboteur phase filters `done`
specs by the capability's `dimension`. Declare `dimension: 'connector'` on connector capabilities
(and `'ui'` on mirror ones) — a connector verify declared as `'api'` leaves the connector phase
with a zero-`done` denominator, so its summary reads 0/0/0 and the seam is green having proven
nothing (`scripts/mutation-test.ts`, per-dimension summaries).

Treat the entry's `allow` map as a **bijection with its legitimate survivors**, not a bag of old
exceptions. The runner currently tests only `candidate.id in allow`; a stale key is silently
ignored. Renaming a capability can therefore leave both a dead exception and a newly unallowlisted
survivor. For every key, confirm the capability still exists as `expected: 'done'`, remove that key
temporarily, and confirm the mutation report names that exact id as a survivor; every survivor must
then be hardened or have exactly one specific reason restored. If removing an allow entry changes
nothing, delete it. Perform this audit whenever ids or dimensions change.

The mutation seam can also be an **isolated execution-plane seam** via `isolatedMutations`, not only
the main `mutations`, `mirrorMutations`, or auto-derived connector seam. Use a named group with a
`mustRegress` kill-list when killing all main seams together proves only their union and cannot
attribute teeth to one plane; GitHub's `git-plane` group is the exemplar. Its green control must run
**first**, while the module graph is pristine, before any mock/restore cycle. A capability already
red in that control has proved nothing by staying red under sabotage. `runPack` deliberately runs
this control at its top and only then performs mirror, connector, isolated, and combined phases;
preserve that order when extending the harness.

**3-6. The committed census facts** live in YOUR PACK's `packages/twin/<vendor>/census.json`
(the gate plane) — FIVE unconditional slices in order: `baseline`, `pullAudit`,
`ui`, `spec`, `invariants` (the R1–R14 matrix row — machine-written by
`scripts/invariants.ts --ratchet`, adopted only by `--adopt`). A sixth, `shape` (the sibling-shape
ratchet — `scripts/pack-shape-check.ts --ratchet`), is CONDITIONAL and records debt: it exists only
while the pack carries grandfathered shape misses, and `--ratchet` DELETES it when the last one
clears ("slice dies with its last miss"). A seventh, `hygiene` (`scripts/pack-hygiene.ts
--ratchet`), is conditional by the same law and holds the pack's adopted hygiene misses — a
hand-rolled args helper, a server option the CLI never parses, a bare `Bun.serve` behind a node
bin, an edit outside the pack; a NUL byte, a scaffold marker and an unregistered protocol-2
pack are never adoptable, because each means the pack is unfinished rather than differently
shaped. **A clean pack has five slices and its census.json is correct without a `shape` or
`hygiene` key** — do not go looking for one, and do not write an empty one.
The scaffolder births the file; the checkers (`scripts/spec-census.ts`, `scripts/ui-scope.ts`,
`scripts/pull-audit.ts`, `scripts/twin-capabilities.ts`) scan every pack's census.json and gate a
two-way bijection with the packs on disk — a missing UNCONDITIONAL slice is RED, and so is a
census.json in a kernel dir.

**Two fields of the spec census are hard-required and are named nowhere else in this recipe:**
`scopePaths` (a non-empty array recording the committed curation boundary — every fixture operation
must fall under one of its prefixes) and `fullSpecEstimateOps` (a positive honest estimate of the
vendor's full spec size, for the sampled-fraction disclosure, and it may not be smaller than the
census's own operation count). `spec-census.ts --check` errors by name on both. Note the `spec` slice's `kind` is a **closed enum** —
`openapi | graphql | smithy | protobuf | discovery | none` (`kind: 'none'` REQUIRES a `reason` AND FORBIDS a `url` — the checker rejects both together) —
and "the spec exists but only inside a generated SDK client" is `none` with that stated as the
reason, not an invented kind (`"sdk"` went red for the mistral build). **A published spec that
describes a DIFFERENT HALF of the vendor than your pack serves is also `none`, with the spec named
in the reason.** Expo publishes a complete first-party GraphQL schema, and it covers EAS — the
platform half the push twin deliberately does not serve. Recording it as the pack's spec source
would enrol the pack in a census campaign against a denominator it can never satisfy, so the rule
is: the spec slice names the spec for the surface THIS PACK SERVES, or `none`. Name the other spec
in `notes` so the next builder does not re-derive the search — but check the SDK repo
first: Stainless-generated SDKs commit a `.stats.yml` whose `openapi_spec_url` names the current
first-party spec (anthropic's "none" was wrong for this reason), and that pointer is a real
`openapi` source. A recorded source is only half of Supported; the other half is a COMMITTED CENSUS. Five steps,
in this order, and each one answers a different question:

1. **Get the spec as an OpenAPI-shaped document.** OpenAPI or Swagger: fetch it (`.yaml` parses
   via `Bun.YAML`). A Google **Discovery** document converts —
   `bun scripts/spec-discovery-to-openapi.ts <discovery.json> <out.json> --fetched <date>` (one
   operation per Discovery method, `mediaUpload` paths included — Gemini's real upload endpoint
   is `/upload/v1beta/files` while the plain path 404s). AWS **Smithy** models convert —
   `bun scripts/spec-smithy-to-openapi.ts <out.json> --fetched <date> --area s3=<s3.json>@<host> …`
   (REST operations become paths; JSON-protocol operations share `POST /` and are listed under
   `x-discriminated` by their `X-Amz-Target` value). **Protobuf** services convert —
   `bun scripts/spec-proto-to-openapi.ts <out.json> --style twirp|grpc --host <host> <a.proto> …`
   (one operation per rpc, addressed by the wire convention: twirp's
   `POST /twirp/<package>.<Service>/<Method>`, or the bare `POST /<package>.<Service>/<Method>`
   gRPC, gRPC-web and Connect all use). A pack whose areas publish SEVERAL documents
   passes them together: `--from-file 'blob.json@host,inference.json@host/base'`, each document's
   paths carrying their own server and area tag. Azure's `x-ms-paths` are paths like any other.
2. **Birth it**: `bun scripts/spec-census-bootstrap.ts --vendor <v> --from-file <spec>
   --scope-paths <the prefixes the twin serves> --apply` writes the scoped fixture (+ SOURCE.md),
   the `<v>-spec-census.json` and the census.json registration. It maps against DONE ids only, by
   the rules in `scripts/spec-census-map.ts`, behind a static served-surface probe; it is blind to
   3-letter abbreviations like `msg`. A scope prefix is a PLAIN prefix, so record `/chat/` to keep
   `/chatkit` out. A route several operations share behind a header value gets one entry each and
   the census is keyed `method+path+header-field`. `--remap` REPORTS what the current rules would map
   differently in a landed census, split by whether the committed id has recorded evidence — it
   writes nothing, because evidence, probe verdicts and hand rulings all outrank these rules and a
   tool that could invert that ranking once moved 52 of stripe's recorded claims onto ids their own
   evidence excludes. `--rebuild` starts over and LOSES every hand ruling.
   **THREE KNOWN BLIND SPOTS IN THE RECORDER, so you can tell a tooling limit from a pack fact.**
   It reconstructs an operation coordinate only for HEADER-discriminated entries, so (a) a
   `graphql-root-field` census records NOTHING — every verify's POST keys as the bare endpoint
   (plain: 0 of 532, slack: 0 of 174); and (b) it never reads a `requestKey: "path"` census. Both
   UNDER-record rather than over-claim, which is the safe direction, but write the limit into
   `$recordComment` so the next reader does not mistake an artifact for a gap.

   **A MULTI-SEGMENT path parameter is supported, but you must DECLARE it.** If a parameter's value
   may contain a slash — bitly addresses a link as `bit.ly/000001`, so `/bitlinks/{bitlink}/clicks`
   is reached at `/bitlinks/bit.ly/000001/clicks` — name it in the census's `multiSegmentParams`
   array and the recorder will absorb the extra segments. It is declared rather than inferred on
   purpose: whether a parameter may hold a slash is a fact about the VENDOR, and inferring it from
   path shape was measured minting false evidence in both directions (azure's all-parameter
   `/{containerName}/{blob}` swallowed `/v1/models/gpt-4o` and a `_twin` door; stripe's
   `POST /v1/customers/{customer}` swallowed a cash-balance-transaction create). Undeclared is the
   default and absorbs nothing.

3. **Take evidence**: `bun scripts/spec-census-record.ts --vendor <v> --apply` runs every done
   capability's verify() in a worker whose handler exports are wrapped and records the method+path
   each verify issues and how the twin answered. An operation a passing verify answers 2xx/3xx is
   `capability` by evidence (`recordedBy` names the ids; an unmapped one is promoted, a claim whose
   id never issues it is remapped to an API-dimension owner that does). Its ORPHANS are routes the
   twin serves that the spec does not name — the reverse census, pack→spec drift for the pack's
   next revision. Runs append to `$recordRuns`; `--reset` re-derives under current rules.
4. **Rule what is left, by hand — and AUDIT WHAT THE BOOTSTRAP GUESSED.** The sharp instruction
   first, because it is the one builds keep missing: **every `capability` / `operationCapability`
   row with no `recordedBy` is a GUESS, not evidence.** The bootstrap maps by segment match and
   makes confident false mappings, which the probe reports as AMBIGUOUS rather than wrong, so they
   survive a clean-looking probe run — one 2026-09-02 build hand-ruled 27 of them back. Read every
   such row against the handler before you keep it. Then: a `todo(` id is not a claim — an unbuilt
   operation stays `unmapped` (a `$ruled` marker records a human ruling). `mapping` records what the twin SERVES,
   never what the manifest INTENDS; the plan already lives in the manifest, which is where a plan
   belongs, and a census that counts planned rows as covered inflates its own denominator. So
   `unmapped` holds TWO kinds and `$unmappedComment` must separate them: operations the manifest
   names NOTHING at all (the sharpest signal a census carries — surface nobody has noticed), and
   operations it names only with a `todo(` id. `spec-census --check` now FAILS a mapped row whose
   owner is a `todo(` id, so this is enforced rather than remembered.
   `unmappedBaseline` is a RATCHET: the tools lower it and never raise it, so when a spec refresh
   adds rows the count exceeds the baseline and `spec-census --check` warns until a human triages
   them. Raising it by hand is how a coverage regression becomes normal — map the new rows, rule
   them, or leave the warning standing. **A ruling has a machine form:** `$ruled` on the row
   (`{ kind: 'false-claim' | 'planned', was, date, by }`) — the record and probe tools write it
   when they demote a claim, and a row the manifest names only with a `todo(` id is ruled
   `planned` by hand. The checker warns on UNTRIAGED growth, unmapped minus ruled, so a
   correction stops shouting once it is recorded and a real regression still does.
5. **Ratchet** — and FIRST, the assumption that costs a run: **your `gate.ts` `replay` step paths
   must live in the CENSUS's path space**, i.e. relative to the fixture's `servers[0].url`, not the
   real wire form. A replay written as `/api/v10/channels/{id}` against a census whose paths are
   unversioned matches nothing, every `{param}` falls back to a placeholder, and the run reads
   AMBIGUOUS at scale — ~110 claims in one 2026-09-03 build — which looks like a modelling problem
   and is not. Check one replay path against one census row before you run it.

   `bun scripts/spec-census-probe.ts --vendor <v>` (add `--apply` to write) seeds a
   root with your gate.ts `replay` writes, fills each `{param}` from the replay step with the
   longest shared prefix, sends the fixture's `servers` host and base path plus that step's
   headers, and issues every claim through your fetch adapter. Verdicts: `UNSERVED` (answered
   exactly like a route the twin has never heard of, on a resource whose own `GET …/{id}` answers
   an unknown id differently — demoted on `--apply`), `UNPROVABLE` (same answer, but that resource
   cannot tell an unknown id from an unknown route: add a replay step that reaches the route, or
   rule by hand), `AMBIGUOUS` (a parent lookup answered first — yours to rule), `PLANNED` (served,
   but claimed by a `todo` id — remapped to a done owner). A vendor that rejects a request BEFORE
   routing when a query parameter is missing (Azure's `api-version`) makes every answer identical
   to its baseline: declare `requiredQuery: {"<name>": "<value>"}` in the census and the probe
   sends it. Runs append to `$probeRuns`.

Then `bun scripts/spec-census.ts --check`. Later specs reconcile through
`scripts/spec-refresh.ts`, which defaults to the census's own committed scope (one loader, YAML or
JSON, `?beta=true` variants folded).
**One vendor generation, pinned.** Your fixture pins the generation you
serve; a request naming another (a version header, a dated path) is refused the way the vendor
refuses an unsupported version, never answered in the pinned shape — stripe's `Stripe-Version`
handling is the precedent. Drift is DETECTED on the census campaign's cadence (`spec-refresh`
against a freshly fetched copy; `volter-world audit` shows the pin's age from MAINTAINERS.md's
`last verified`) and never absorbed: a re-pin is a pack revision — re-dossier, re-capture, §9 —
not a version bump.
Rules:

- The file is yours alone — no shared-file splicing. Format: 2-space JSON, trailing newline,
  non-ASCII as `\uXXXX` escapes (the ratchet writer emits that style; keep it so machine
  rewrites stay one-line diffs).
- **The `pullAudit` slice has two teeth the others don't, both literal-grep:** every `gaps[]` id you
  file must exist as a **literal `todo('<id>'…)` call** in a
  `packages/twin/<vendor>/src/*-capabilities.ts` file — the checker greps for the literal string
  (`scripts/pull-audit.ts`), so a todo minted through a `.map(...)`/helper loop is invisible to it
  and reads as an unfiled gap; if your manifest generates entries programmatically, spell the
  pull-gap todos out as literal calls anyway. And every **`pulled[].evidence`** claim must be a
  **literal substring of the connector file(s)** it cites (same checker, `connectorText.includes`)
  — quote a real identifier from the connector (the sync/map function name), never a prose
  description of what it does.
- **The `baseline` slice is the coverage ratchet:** it holds the pack's DONE count, and a
  new pack must seed it — the number you actually measured
  (`bun scripts/manifest-baseline-one.ts <vendor>` prints it), never a guess. It only ratchets
  upward from there; a later drop is a deliberate hand-edit visible in diff review. **Seeding at
  wiring time is NOT the last touch: ratchet once, only after BOTH §9 rounds and their revert
  matrix are complete.** Review moves the done count more than once (Google OAuth moved
  108 → 111 → 113 across its two rounds): a demotion drops it below your seeded floor (red until
  you hand-lower the number in the same commit), and a review-driven fix raises it, leaving a
  stale-low floor with no teeth over the delta. Re-measure after round two settles and commit that
  final post-review number (`bun scripts/twin-capabilities.ts --write-baselines` ratchets up;
  demotions are the hand-edit). Do not call the round-one count final.

**7. The generated docs block** — `README.md` and `./conformance.md` carry coverage tables that
`bun scripts/twin-capabilities.ts --check-docs` verifies are in sync. Regenerate them with
`--write-docs`; never hand-edit inside the generated markers. **This is the one sanctioned
exception to "don't edit repo docs"** — you are editing generated output, not prose.

Docs generation is HERMETIC (gate plane): every row is computed by
`manifest-baseline-one.ts --row-json` in a process that evaluated exactly ONE manifest, and
totals derive from the rows — so a regen structurally cannot move another pack's numbers (the
old single-process generator once spuriously reported Stripe 171 → 169). For your pack use the
SCOPED form, `bun scripts/twin-capabilities.ts --write-docs <vendor>` (what `land.ts --docs`
runs): it recomputes your row, merges it with the committed rows, and re-derives totals. Inspect
`git diff -- README.md ./conformance.md ./architecture.md` anyway: only your row and the derived
totals may move — anything else is a parser/renderer bug to report, never something to commit.

**8. Interception — `hosts` on your `pack` descriptor** (§3). Skip it and zero-edit injection
silently breaks: the world runtime can't route your vendor SDK's traffic to the twin. This used to
be the one wiring point NO gate enforced; after two production incidents (the
openai/posthog/supermemory/xai packs shipped full twins with unreachable hosts),
`scripts/vendor-hosts.test.ts` fails any manifest pack with neither an injector entry nor a stated
`hostsNone` ruling on its descriptor.

**Declare hosts on the descriptor. Do NOT edit `VENDOR_HOSTS`.**

```ts
hosts: [
  { host: 'api.<vendor>.com' },                                // exact host
  { suffix: '.cognitiveservices.azure.com' },                  // per-resource-host vendor
  { host: 'www.googleapis.com', pathPattern: '^/youtube/' },   // a host two vendors share
],
```

A per-resource-host vendor (Azure's `https://<resource>.cognitiveservices.azure.com`,
virtual-hosted buckets, `*.atlassian.net`) needs the **`suffix`** form, not an exact host;
`pathPattern` is a **RegExp source string** applied to the URL pathname, and exists to split a host
two vendors share (the youtube/googleauth case).

`bun scripts/pack-facts.ts` (point 13) compiles these into the committed artifact, and
`inject.cjs` `require()`s that JSON and **builds the `VENDOR_HOSTS` predicate from it** — it must
stay dependency-free preloaded CJS, so it consumes compiled output rather than importing packs.
That is why the descriptor route satisfies `vendor-hosts.test.ts` exactly as a hand entry did: the
gate requires the real injector table, and the table is now assembled from your declaration.

**The hand `VENDOR_HOSTS` table in `inject.cjs` is LEGACY-ONLY.** It shrinks toward empty as packs
migrate, and a vendor present in **both** homes **throws at require time**:

> `inject: vendor "<x>" declares hosts on its pack descriptor AND in the hand VENDOR_HOSTS table — one home per fact; delete the hand entry.`

A broken checkout must refuse to inject rather than half-route, so this is a throw and not a
warning. **A new pack never touches `VENDOR_HOSTS`.** If you are migrating an existing pack, the
edit is a move: add `hosts` to its descriptor and delete its hand entry in the same commit.

**A host no descriptor can list?** A name a person makes answer to the vendor while the World
runs (a custom domain connected to an R2 bucket) is declared as `hostsClaimed: { door, note }`
beside `hosts`: the twin lists the names it answers now at its own `door` (GET, answering
`{ "hosts": [...] }`), and the injector, in apps and in the redirect proxy alike, reads that door
every five seconds and routes each name to the twin as DNS routes it to the vendor. A name any
descriptor's `hosts` covers always answers first, so a claim never takes another pack's host.
cloudflare is the example (`cloudflare-server.ts`, GET /twin/hosts).

**No injector entry at all?** That is a ruling, not an omission — declare `hostsNone: '<why>'`
instead (`hosts` XOR `hostsNone`; declaring both throws in `scripts/pack-facts.ts`). The
descriptor IS the ruling: `scripts/vendor-hosts.test.ts` and `scripts/pack-shape-check.ts` read
it from `generated/pack-facts.json`. Its meaning is precise — "no VENDOR_HOSTS entry under THIS
pack's own vendor key, for the stated reason" (aws's consolidated area keys are other vendor
keys, kept honest by their own gate assertion).

**9. The rate-budget rosters** — every pack lands in exactly ONE of two files. A pack **declaring
a budget** is picked up by `scripts/rate-budget-isolation.test.ts`'s `WIRED` roster, which DERIVES
from the `*-budget.ts` glob and needs no edit (it was a hand-listed roster until it was made
derived; a build following the old instruction goes looking for a list that is not there) — and
mirrors its vendor into
./architecture.md D8's "Wired today" list (the roster is the ground truth; D8 mirrors it). A
**budget-less** pack goes in `scripts/rate-budget-coverage.test.ts`'s `EXEMPT` roster, which makes
silence impossible: every unresolved outbound call site in the pack must be excused **by name**,
with a reason, or the test is RED. And if your declared ceiling admits a **bigger burst than the
fallback's**, the isolation test additionally demands a hand-written `VENDOR_BURST_ANCHOR` entry
(same file): the vendor's own documented per-minute allowance plus its source, with the assertion
arithmetic against that number — an out-bursting pack with no anchor is RED by name. The anchor is
a hand-written number deliberately: the old declaration-derived check was passed by all three
hostile declarations thrown at it, so nothing the declaration itself carries can be the bound.

**10. The journey registry — DERIVED, nothing to edit.** `scripts/ui-journeys.ts`'s
`JOURNEY_TEST_FILES` globs `packages/twin/*/src` for journey files, so a journey is registered by
EXISTING at the conventional path. What still binds is the census side, when your census.json `ui`
slice declares `needsUi: true`: the ui-scope census **hard-fails** a `needsUi` pack with no
journeys, and (the other direction) a journey file no census owns is census drift too.

Know the harness contract before designing the server. `runUiJourney` navigates first to the exact
`server.url` returned by `serve`, then waits for `#root` to contain a child before it invokes your
journey callback. It also installs a request guard that allows only that origin (plus `data:` and
`about:`) and aborts every request or navigation leaving it. A browser-facing protocol twin may
have no app shell at `/`, and an OAuth callback on a second loopback port is therefore unreachable.
Follow `googleoauth-journey.uitest.ts`: make a **journey-only same-origin facade** that supplies the
`/` mount, serves the real twin handler on its vendor paths, and hosts the integrating app's
callback on that same origin. Keep that facade in the journey file so it cannot be mistaken for
pack surface, and use the SDK fidelity test to prove the real cross-origin configuration separately.

**11. Adoption discovery — `adoption` on your `pack` descriptor** (§3). This is how
`volter-world covers` / `init` attribute a repo's signals to your twin. Skip it and `covers`
reports your vendor's SDK as `unknown-sdk` — or, before the scope-sibling guard, produced **no row
at all**: in the Dub blind-adoption run `@upstash/redis` and `@upstash/ratelimit` simply vanished
while their scope-sibling `@upstash/qstash` was mapped, even though the twin existed and was fully
wired everywhere else. That class is exactly what let `@planetscale/database` escape (twin#255).

**Declare adoption on the descriptor. Do NOT hand-edit `SDK_TWINS` / `SDK_SCOPE_VENDORS` /
`ENV_STEM_VENDORS` / `VENDOR_WORLD_IDS`.** And check the OTHER direction of birth: if your
vendor's client or env stem is in the packless-vendor registry
(`packages/world-runtime/known-external-services.json` — "external signal the catalog
knows but has no pack for"), **delete that entry in the same commit** — an entry there dies
the moment the pack is born, and `scripts/packless-claims.test.ts` is RED on any name both
packless-listed and pack-claimed (the registry encoding subtracted nine pack-claimed deps at
birth, and the gate's first runs killed five dep and four stem stale entries the same day).
The gate reads the committed pack-facts artifact and the overlaid maps, so it bites only
after `bun scripts/pack-facts.ts` publishes your descriptor — one more reason point 13's
regeneration is not optional.

```ts
adoption: {
  sdks: ['<npm-client>'],   // every official npm client of the API surface you model
  scopes: ['@<vendor>/'],   // npm scope prefixes whose members ALL belong to this vendor
  envStems: ['<VENDOR>'],   // credential-env stems — 'STRIPE' for STRIPE_SECRET_KEY et al.
  worldIds: ['<other>'],    // extra world service ids this vendor answers to
},
```

Each key maps to exactly one legacy table, and `packages/world-runtime/src/pack-facts.ts`
**overlays** the generated artifact onto them at module init — mutating the same objects every use
site already reads, so nothing downstream changes:

| descriptor key | legacy map | file |
|---|---|---|
| `adoption.sdks` | `SDK_TWINS` | `world-runtime/src/project-inspect.ts` (`overlaySdkTwins`) |
| `adoption.scopes` | `SDK_SCOPE_VENDORS` | `world-runtime/src/covers.ts` (`overlayCoversMaps`) |
| `adoption.envStems` | `ENV_STEM_VENDORS` | `world-runtime/src/covers.ts` (`overlayCoversMaps`) |
| `adoption.worldIds` | `VENDOR_WORLD_IDS` | `world-runtime/src/covers.ts` (`overlayCoversMaps`) |

(`MAPPED_SCOPES` still derives itself.) **Dual declaration THROWS at module init**, not in a test —
loud everywhere, because this drift is what the migration exists to end:

> `pack-facts: sdk "<x>" is declared BOTH on the <vendor> pack descriptor AND in the central SDK_TWINS table — the migration rule is one home per fact. Delete the central entry (the descriptor wins).`

So if the overlay throws while you are wiring, a central entry already claims your name: delete the
central entry, keep the descriptor. Note the stem form — the overlay lowercases and the detector
normalizes (`covers.ts`, `normalizeId`: lowercase, strip non-alphanumerics), so `NEXT_PRIVATE_`
prefixes and punctuation collapse; write the stem the way stripe does (`'NEXTPRIVATESTRIPE'`).

**"NO gate enforces this one" is no longer true** — that claim, carried by this section for two
revisions, was the reason a whole class escaped. `scripts/pack-facts.test.ts` now has three teeth:
**drift** (the committed artifact must equal what the descriptors compile to), **dual declaration**
(asserts the overlay's throw is real, not folklore), and **construction** (a declaring pack's facts
must surface through the SAME overlay the runtime uses, proving "drop the directory in and every
aggregated view sees it" with zero central edits). If you add a new aggregated view, extend that
fixture assertion.

**12. World wiring — `endpointEnv` / `endpointEnvNone` on your `pack` descriptor** (§3). The other
half of interception, and the one the eleven-point list omitted entirely. When the injector cannot
rewrite your vendor's traffic — a WebSocket media plane, a raw-TCP protocol, a base-URL-configured
SDK, a control plane with no fixed vendor host — the way a world points an **unmodified** client at
the twin is the env var the vendor's own SDK documents. `volter-world init` injects it pointing at
the twin's URL:

```ts
endpointEnv: {
  name: 'INNGEST_BASE_URL',
  templates: { INNGEST_EVENT_API_BASE_URL: '${url}' },   // optional extra vars; ${url}/${host}/${port}
  note: 'no injector entry: the inngest SDK is base-URL-configured through INNGEST_BASE_URL / INNGEST_EVENT_API_BASE_URL.',
},
```

`note` is **required** and is the grounding: name the SDK source or doc that documents the var.
`packages/world-runtime/src/pack-facts.ts`'s `overlayEndpointEnv` merges the compiled fact
into `APP_READ_ENDPOINT_ENV` in `world-runtime/src/init.ts` — the legacy table where these reasons
used to live as comments — and **dual declaration throws** there too, so a migrating pack deletes
its `APP_READ_ENDPOINT_ENV` entry in the same commit (inngest is the exemplar; its old entry is now
a one-line tombstone comment in `init.ts`).

**`endpointEnv` XOR `endpointEnvNone`; declaring both throws in `scripts/pack-facts.ts`.** And the
rule that decides which: **never invent a `<VENDOR>_BASE_URL` the app does not read.** A pack that
is base-URL-configurable but has no conventional env var (algolia, pinecone, replicate, fal,
twilio, sendblue, svix, figma, notion) declares `endpointEnvNone` with that reason and the proof
reports it UNINTERCEPTABLE — which is the truth. Inventing a var would make `covers` call the world
covered (any app-read env counts) while the app, reading no such var, still talks to the real
vendor: the exact class of lie the proof exists to catch. Ground-truth caveat: `endpointEnvNone` is
declaration only today — no consumer reads it and no gate demands it, so unlike `hostsNone` there is
no second allowlist to also fill in. Declare it anyway; the ruling is what makes the absence
readable, and it is what the migration converges on.

> **A PULL OBSERVES; IT MAY NOT DESTROY A LOCAL COMMIT.** A connector's pull path appends
> OBSERVATIONS. It must never append a local action that deletes, tombstones or overrides state a
> caller wrote — the vendor is authoritative about the vendor, not about the world's own intent. A
> 2026-09-03 lane wrote a collision guard that auto-resolved by tombstoning the local row: during a
> READ-ONLY sync it orphaned entitlement and price rows keyed by the plan's UUID, silently revoked
> a granted entitlement, and left an action no push could converge. It passed typecheck, the
> suites, the invariants and the mutation gate; only an adversarial reader saw the data loss. The
> right answer to a collision is a LOUD REFUSAL.
>
> The one shape that is legitimate, so you can tell them apart: reviving a subject the world has
> tombstoned but the vendor still returns (`tinybird`, `tremendous`). That honours the vendor
> without destroying anything, and both packs scope it to tombstoned subjects and file the
> unhandled half — deletes the vendor no longer returns — as a named todo rather than papering it.

> **ADDING A RESOURCE TYPE OBLIGES YOUR REPLAY.** R2 requires that after the gate's `replay` runs,
> the world holds at least one subject of EVERY type your descriptor's `resources` declares — the
> declaration is a checked claim, not a wish. So a new `resources` entry silently reddens the
> invariant matrix unless the same commit teaches `gate.ts`'s replay to create one. Two wiring
> points, one edit; nothing else connects them for you.

**13. The generated pack-facts artifact** — `packages/world-core/generated/pack-facts.json`.
Points 8, 11 and 12 are **inert until compiled**. AND THE ORDERING BITES: `scripts/invariants.ts
--check` reads `resources` from the COMPILED artifact, not from your descriptor, so between editing
the descriptor and regenerating you get an R2 red that describes the old declaration and reads
exactly like a real failure. Regenerate first, then believe the matrix. The `hosts` case has its own
disguise: new routes ARE served, but the injector refuses them because it reads the compiled table,
so the failure surfaces as an unrelated-looking SDK error rather than as anything about hosts.
After any descriptor edit:

```bash
bun scripts/pack-facts.ts        # rewrites the artifact
git add packages/world-core/generated/pack-facts.json
```

**14. The support-status row** — `policy/MAINTAINERS.md`. `scripts/maintainers.test.ts` holds it in
bijection with the catalog, so a pack with no row is RED and a pack is **born with a status**
(Supported / Maintained / Odd Fixes / Orphan / Obsolete, plus owner, upstream, spec source, last
verified). AGENTS.md has always said so; this section did not, and the 2026-09-02 expo build found
it the hard way.

**15. The host worker map** — gone with the v1 shell. The host mounts whatever packs a world's
config names; there is no roster to regenerate.

**16 and 17. The two generated reports** — `bun scripts/scorecard.ts` and `bun scripts/ecosystem.ts`.
Both are drift-gated (`scripts/scorecard.test.ts`, `scripts/ecosystem.test.ts`) and both go RED on
a new pack. Re-render and commit them. These are the two most commonly missed, because nothing
about building a pack suggests a catalog-wide report needs rewriting.

**19. The source-text roster — SDL-EXECUTING PACKS ONLY** — `scripts/text-modules.ts`'s
`TEXT_MODULES`, plus its hand-pinned roster assertion in `scripts/text-modules.test.ts`. A pack
that executes a vendored schema (`buildSchema`) renders it into a committed `*.gen.ts` and imports
that; reading the text at runtime instead is an R12b violation the invariants gate catches. Add
both entries in the commit that adds the module. Conditional: it binds iff your serve path
executes or serves a source text.

> **A GraphQL trap worth the paragraph, if you execute an SDL:** graphql-js COERCES a field's
> declared argument defaults into `args` before your resolver sees them, so an argument you never
> sent arrives populated. A guard written as "refuse any non-empty `sorting`" therefore refuses
> every list query the schema gives a default sort — measured at 33 verifies on one build. Compare
> against the field's DECLARED DEFAULT, not against emptiness.

> **Declare the implemented protocol.** `PROTOCOL_VERSION` is `2.0`; current packs declare
> `protocol: '2'` and pass the protocol-2, branch round-trip and applicable shape-parity gates.
> An undeclared pack is treated as protocol 1 and deprecated, not silently upgraded. Consult
> `protocolStanding` in `packages/world-core/src/packRegistry.ts` and the generated index for
> standing. Changing a descriptor alone does not migrate an implementation.
>
> **Pin the package.** `init` emits the installed version; `up` checks that the mounted package
> satisfies it and records resolved version and protocol. `volter-world outdated` and `audit`
> inspect those records.

> **WHICH PACK TO DEEPEN is its own question, and `bun scripts/deepen.ts` answers it.** The ladder
> decides which twin should EXIST; the packs that already do and are shallow carry `todo · core`
> entries, the author's own judgement that an operation is central and unbuilt. Ranking those by
> pile size is measurably the wrong signal: the biggest piles have been packs no measured repo
> demands, while a pack with a third the pile had eight repos wanting it. `deepen.ts` crosses what
> a pack says is missing against what the roster says is wanted, and writes nothing.
>
> **An undemanded `todo · core` is parked, never deleted and never built ahead of demand.** It is
> a true sentence about the vendor, and demand is not a fact about the vendor — deleting it to
> shrink the tail would make the census lie. When `deepen.ts` reports an EMPTY demand head, the
> depth half is done until the ladder is re-censused and demand moves; until then the campaign is
> breadth, the ladder's build queue of uncovered vendors.

**18. The coverage ladder's derived fields** — `bun scripts/ladder.ts --rederive`.
`generated/ladder.json`'s build queue and new-vendor queue are derived against the CATALOG, so a
pack landing makes the committed report stale and `scripts/ladder.test.ts` red. `--rederive`
recomputes those fields from the committed measurements and needs no repo corpus — the
measurements did not change, the catalog did. Do NOT run a full `bun scripts/ladder.ts` to fix
this: without the corpus it measures nothing, and the point of `--rederive` is that a merge should
never need one.

> This point was found on 2026-09-02 by the same merge that rewrote this section — which is the
> argument for the framing above. The list is not the definition of the set; `verify-pack.ts` and
> the drift gates are. When one of them goes red on something not written here, write it here.

Never hand-edit it (the file says so in its own `"//"` key). It is generated rather than scanned at
run time because its consumers either must stay dependency-free preloaded CJS (`inject.cjs`) or are
latency-sensitive CLIs (`volter-world init` p50 is ~450ms across a 100-repo census; importing 70+
pack modules per invocation would dwarf that). The scan happens once, at authoring time.

**A stale artifact is RED in the gate sweep** — `bun scripts/pack-facts.ts --check` fails, and
`scripts/pack-facts.test.ts`'s drift test fails with it. The generator also refuses two authoring
mistakes outright, before any drift check: a descriptor whose `vendor` disagrees with its directory
name ("the directory IS the identity; rename one"), and either XOR pair declared on both sides.

What genuinely does auto-discover: `capability-manifests.test.ts` and `architecture.test.ts` both
scan `packages/twin/*` and enforce shape/dep-hygiene without registration. And **`twin-check.sh`
PACKAGES does NOT need editing** — new packs are gated via the architecture guardrail and the
per-process meta-test, not that array. (Add it only if you want your unit tests in the headline
`[1/4]` loop too.)

## 7.5 Integrating parallel pack branches

When several builder branches land together (ten at once in PR #220), virtually every textual
conflict sits in the §7 wiring points — each branch appended its own entry to the same registries,
rosters and censuses. The resolution is almost always the UNION: keep BOTH sides' entries, in the
order each file's own rules dictate (§7 point 2's `PACKS` ordering; the censuses' insert-additively
rule). Four hazards recur:

- **The dropped-closer signature.** The classic conflict has both sides ending INSIDE their own
  allowlist/roster block and sharing the ONE trailing closer that sits below the conflict: `ours`
  adds entries and stops mid-block, `theirs` does the same, and the `]);`/`};` appears once, after
  `>>>>>>>`. Stripping the markers yields one run-on block missing a closer. The fix is mechanical:
  **replace the `=======` marker line with the closer**, so ours' block is closed and theirs' keeps
  the original trailing one.
- **Shared JSON registries need ENTRY-LEVEL splicing, never marker-stripping** (your pack's own
  `census.json` never conflicts — it is yours alone; this rule survives for any remaining shared
  JSON): deleting conflict markers mangles the neighboring entries' commas and braces. Take one
  side wholesale, then splice the other side's entries in by hand — additively, matching
  neighbors, never a parse → re-stringify of the file.
- **The forked-invariant class — un-catchable by any parse check.** A repo-wide invariant that
  landed on main AFTER your branch forked can be invalidated by facts your branch introduces (and
  vice versa) with NO textual conflict at all: the files merge cleanly; the CLAIM is stale. Both
  post-merge reds in the ten-pack integration were this (commit `7f3c567`): a covers-test fixture
  pinning "a vendor-shaped env var with no pack stays a loud unknown" chose `UPSTASH_VECTOR_REST_URL`
  as its control, which stopped being pack-less the moment the upstashvector branch landed; and a
  budget test's raw-host scan flagged a fixture-builder file that landed on main after the youtube
  branch forked, so its allowlist had never seen it. After the union: **re-run the census suites
  AND actually READ every new-on-main test that touches your files** — `git log --stat
  <fork-point>..main -- scripts/ packages/world-runtime/` names them. A red here usually means
  repointing the invariant's fixture at a control that still holds the pinned property — not
  weakening the invariant.
- **Parse-gate the resolution with `Bun.Transpiler`, not `bun build`.** `bun build` reports
  module-resolution failures alongside — and often instead of — syntax errors, so a leftover
  conflict marker can hide behind an unrelated import complaint. A per-file transpile is an
  unambiguous parse verdict:

  ```bash
  git diff --name-only <fork-point>..HEAD -- '*.ts' '*.tsx' | bun -e '
    const t = new Bun.Transpiler({ loader: "tsx" });
    for await (const f of console) {
      if (!f.trim()) continue;
      try { t.transformSync(await Bun.file(f.trim()).text()); }
      catch (e) { console.error(f, String(e)); process.exitCode = 1; }
    }'
  ```

A merged tree is exactly the "finished whole" the full gate exists for — the union of N green
branches is not green by construction, and the forked-invariant class above is precisely what it
catches. So an integration of several pack branches is the standard case for **asking** for the
full sweep (§8), rather than merging on the per-merge slices alone.

## 8. Verify

**Run this, first and often:**

```bash
bun scripts/verify-pack.ts <vendor>     # T0 — the whole per-merge set, in seconds
```

(One of the steps it runs, `scripts/pack-shape-check.ts`, takes its pack **positionally** —
`bun scripts/pack-shape-check.ts <pack>`, not `--vendor <pack>` — which is worth knowing on the
day you need to run it alone.)

That single command is what AGENTS.md means by per-merge verification: the pack's suites, tsc, the
manifest baseline in its own process, the mutation phase, the census self-checks, sibling shape, and
— since 2026-09-03 — the two generated catalog reports. That last pair was a real hole: a pack change
that moved a capability count or ratcheted a census baseline left `generated/scorecard.json` stale,
so `scripts/scorecard.test.ts` went RED while T0 read GREEN and the lane reported a clean merge. It is also the fastest way to find an unwired §7 point. Since 2026-09-04 it also runs the CATALOG step: the catalog-wide gates a pack change
can break (the vendor-pack architecture guardrails, the derived tables, the ladder, packless
claims, rate budgets, the journey set, the UI conformance baseline), so a locally green lane can
no longer leave a catalog red that only T2 would have named. Two stay T2's for cost: the
capability-manifests meta-test and generated-docs drift — a manifest change regenerates the docs
with `bun scripts/twin-capabilities.ts --write-docs` and commits them. The individual
commands below are what it runs, kept here for when you need one in isolation.

**The behavioural journey lane (five packs, by design).** T0 asks whether each operation is
mapped and answers identically twice; it does not ask whether the twin is RIGHT over a long
arc. That question is a JOURNEY — `packages/twin/<vendor>/journey.ts`, one RL-task-length
story (30+ steps) the runner walks with its beats (ECHO, PERSISTS, ISOLATED/UNKNOWN,
REPLAYABLE, LANDED); the set is capped at five packs, the ones whose state models the rest
borrow, and every walk runs in the scripts sweep. A [derived pack](./architecture.md#journeys) is not
under the cap: its journeys are authored and judged per pack. If your pack is one of them:

```bash
bun scripts/behavior-journey.ts --vendor <vendor> --verbose     # the walk, step by step
bun scripts/behavior-journey.ts --vendor <vendor> --coverage    # what the story has never done
```

`--coverage` is line and function coverage of the pack's STATE-SIMULATION MODULE while the
story runs — the sources under `src/` that write world state, minus pull/push — with the
never-run ranges grouped under the declaration that encloses them. Read it as "the story
never took the reopen branch of `applyIssueWrite`", never as a score to raise; a line that
ran was reached, not proven right (`scripts/journey-kit.ts`, COVERAGE).

**What you run per merge, and what you do NOT.** The full `twin-check.sh` gate is a **whole-repo
sweep, not per-change verification** — running it off the bat for every pack is a massive slowdown
and is not the cadence. Per-merge verification is: **your pack's own suites + typecheck + the
specific meta-gates your change affects + §9's adversarial review, which is mandatory and is the
only thing that catches a false-green.** The full gate runs **only on the owner's explicit ask**;
until then a pack claim **queues** for that run and says so.

```bash
# fresh worktree/checkout? `bun install` at the REPO ROOT first — missing devDeps fail the
# SDK-integration tests AND corrupt seemingly unrelated suites (an unarmed budget declaration
# silently falls back to kernel defaults).
cd packages/twin/<vendor> && bun install && bunx tsc --noEmit && bun test src/*.test.ts
# `git add` the new pack BEFORE trusting its own guard tests — see below.
# then from the repo root — the fast inner loop:
bun test scripts/architecture.test.ts          # pack shape + dep hygiene
bun test scripts/capability-manifests.test.ts  # baseline + discovery + wiring (per-process)
bun scripts/manifest-baseline-one.ts <vendor>  # YOUR pack alone: regressions=0, done>0, total>=50
                                               # (answers "unknown vendor" until §7 point 1 —
                                               #  the manifest-registry import — has landed)
bun test ./scripts/mutation-test.ts            # your verifies must FAIL against a dead twin
bun test scripts/protocol-2.test.ts            # the pack is a plugin: descriptor, serve path, module truth, engine slot
bun test scripts/branch-round-trip.test.ts     # the branch round-trip, every protocol-2 pack (positions, never tree bytes)
bun test scripts/shape-parity.test.ts          # write handler ⇄ refresh adapter (advisory until shapeParity: 'held')
# the meta-gates a new pack's wiring actually touches (§7) — run the ones your change affects:
bun scripts/pack-facts.ts --check              # point 13: descriptor↔artifact drift
bun test scripts/pack-facts.test.ts            # drift + dual declaration + construction
bun test scripts/vendor-hosts.test.ts          # point 8: an injector entry or a stated reason
bun test scripts/rate-budget-isolation.test.ts scripts/rate-budget-coverage.test.ts   # point 9
bun scripts/spec-census.ts --check && bun scripts/ui-scope.ts --check                 # points 3, 4
bun scripts/pull-audit.ts --check                                                     # point 5
bun scripts/ui-journeys.ts                                                            # point 10
bun scripts/twin-capabilities.ts --check-only --check-docs                            # points 6, 7
```

Then §9. Then, **when the owner asks for it**, the full sweep — serialize it even on a quiet
machine; the default fan-out has produced unrelated 60s timeout flakes under load. Each run prints
its own log dir + provenance (worktree + HEAD sha); read THAT dir, never an older run's:

```bash
TWIN_CHECK_CONCURRENCY=1 bash scripts/twin-check.sh --check
```

The `./` in the mutation line is load-bearing: `mutation-test.ts` doesn't match bun's `*.test.*`
discovery pattern, so a bare `bun test scripts/mutation-test.ts` is treated as a **name filter that
matches nothing** — bun prints "filters did not match any test files" and runs zero tests, which a
scrolling terminal reads as a pass. The explicit `./` path makes bun execute the file directly.
(The two `*.test.ts` lines above work either way — they match discovery.)

**A WORKTREE'S `bun test scripts/` IS NOT MAIN'S.** A lane runs in a git worktree with no
`node_modules` of its own, so `@volter/world-core` resolves to a second module instance and a handful of
tests that depend on cross-module identity fail there and only there — `rate-budget-isolation`'s
`RateBudgetError` constructor check is the usual one.
THREE separate lanes on 2026-09-03 reported those as "pre-existing failures on main" while main was
green at 1,600 tests. Before you attribute a red to the base, re-run it in the main checkout or
`bun install` in your worktree; and when you report one, say WHERE you ran it. A wrong claim about
the baseline is worse than the failure, because it teaches the next reader that the sweep is noise.

**`git add` the new pack BEFORE trusting its own guard tests.** This is stated below in terms of
the budget tests, but it is a GENERAL rule about every gate that shells out to git: any test using
`git ls-files` / `git grep` (`trackedPackFiles()`, `filesMentioning()`, the raw-host allowlist
checks) sees only **tracked** files, so on an untracked pack it reports green while checking
nothing. If a guard test passes on a brand-new pack, ask whether it could have failed. Stage the
pack first; only then does its green mean anything.

`twin-check.sh` runs materially more than the inner loop: every pack's unit tests and `tsc`, the
**mutation gate** (API/write, mirror-render, connector, plus named isolated seam groups), the
**committed censuses and rosters** (§7), the capability-baseline ratchet, and a **generated-docs
drift check**. That breadth is exactly why it is not a per-change tool: it re-measures 70+ packs to
tell you about one. Run the affected slices above per merge; leave the sweep for the asked-for run.

**When the full gate IS run, it is run ONCE, at the end, over everything — never twice per pack.**
The earlier framing here (a pre-review sign-off run, then a repeat after §9) is superseded: build
the full slate, land the per-merge verification above, complete §9's rounds and the revert matrix,
ratchet the baseline, and let the ONE comprehensive sweep cover the lot. A gate run is not how you
verify an intermediate change; it is how a finished whole is accepted.

**Freeze the tree for whatever gate run does happen. Do not edit while it runs.** A result over a
moving worktree cannot identify what passed and is void, even if its last line is green. Commit
first, require a clean `git status`, record the exact HEAD printed in the provenance line, and make
no edits until the process exits. If anything changes — even documentation — stop/restart the gate
from zero on the new commit.

**Run the gate ALONE.** Each run writes its logs to its OWN per-invocation directory, printed at
start next to a `provenance` line (worktree path + HEAD sha + mode — match it to your tree before
trusting any log; `TWIN_CHECK_LOG_DIR` overrides the location), and the script prints a loud
WARNING naming the PIDs when a sibling `twin-check.sh` is already running. So concurrent gates can
no longer clobber each other's logs — which used to manufacture spurious timeout failures in packs
nobody touched — but they still share the machine's sockets and cores, and the WARN is not a
refusal. The gate also flakes under its **own** default
concurrency (`CHECK_CONCURRENCY`, 4) on machine-local noise — measured during the tinybird build:
**2 of 5 default runs went red, on a different unrelated pack each time**, and every one was green
both isolated and under `TWIN_CHECK_CONCURRENCY=1 bash scripts/twin-check.sh`. Either way a red in
an unrelated pack is measurement noise, not a finding: reproduce the failing pack solo before
believing it. Use `TWIN_CHECK_CONCURRENCY=1` proactively for the build-sign-off run; default
concurrency remains a faster development probe, not the trustworthy final measurement
(the mechanism — listening-socket pressure handing a probe a stranger's response — is written up in
[./gates.md](./gates.md), "The gate goes red on a different pack each run"). (`capability-manifests.test.ts` now names
this explicitly — a killed baseline runner reports the signal and the standalone reproduce command
rather than a bare "exited null" that reads like a regression.)

### A timeout is a PERFORMANCE bug. Never raise it to go green.

Three separate red gates in one 2026-07-25 session were all the same thing: a per-test timeout
tripping on a test that was slow, not hung. The reflex — raise the number — is wrong, and each
time it would have buried the real defect:

- baseline runners were being SIGKILLed by bun's 5s default (they take 1-4s);
- twin-check's own 10s cap fought its `CHECK_CONCURRENCY=4`;
- UI journeys tripped a 30s cap — and chasing *that* one found the actual bug: journeys launched
  **two** chromiums per test (one thrown away by the availability probe), so ten journeys meant
  ~20 launches. Sharing one browser with a fresh context per journey took the whole suite to
  **under 2s per file**. The 30s cap had been hiding a 15× waste for as long as it existed.

So when a test trips its budget, the question is **"why is this slow?"** — never "what number
makes it pass?" A timeout you raise is a performance regression you agreed to stop measuring.
Budgets in this repo are deliberately tight and say so in their own error messages
(`JOURNEY_TIMEOUT_MS` is 15s against a ~1-2s real run, and warns at half).

### In-process server harnesses must keep the event loop alive and contain handler errors.

When a fidelity test starts the pack's server (through `serveHttp`) in the same process as a real client CLI, spawn
that client **asynchronously** (`Bun.spawn` + `await proc.exited`), never with `spawnSync`. A
synchronous child blocks the same event loop the server needs to answer, so client and server wait
on each other forever. `github-git.integration.test.ts` is the copyable pattern for an unmodified
CLI driving an in-process HTTP server.

Also make the server adapter return a defensive vendor-shaped or generic HTTP 500 when its handler
fails or yields no response; do not let an exception escape the seam's `fetch` callback. Under
`bun test`, a thrown server-handler error can be reported as “unhandled between tests” and then
prevent later `test()` registration in a top-level-await harness, obscuring the original defect.
Webhook delivery is **fire-and-forget** for vendors whose own request succeeds independently of
delivery: catch delivery failures at that boundary and ledger/report them separately, as
`github-server.ts` does. A dead webhook endpoint must not turn the originating vendor write into an
HTTP failure unless the real vendor's protocol says it does.

### The UI rung is certified by a REVIEW AGENT, not by automation.

Every other rung can be decided by an assertion. The UI rung cannot, and pretending otherwise is
what produced this repo's worst false-green.

An assertion can prove seeded data reached the screen. It cannot decide whether the screen is any
**good** — whether it reads as the product, whether the layout survived, whether a "rendered" row is
a legible card or a two-pixel sliver of unstyled text. A mirror can satisfy every locator in a
journey and still be unusable. That judgement needs eyes, so it belongs to the reviewing agent:

**Journeys record themselves.** The step vocabulary lives in the harness — `visible`, `gone`,
`atPath`, `waitForMount`, `clickByName`, imported from `@volter/world-tooling` — and every step
writes a frame, because every step a journey takes is by definition a moment worth seeing. Use
those helpers and your journey produces its own filmstrip; define your own local copies (all four
packs used to) and it records nothing. Frames land in `.ui-certification/` (gitignored) with no
flag to remember.

```bash
bun scripts/ui-certify.ts          # runs every journey, groups each one's filmstrip
# then READ the frames and judge them. If you did not look, you did not certify.
```

A filmstrip, not one shot at the end: a journey that goes board → detail → deep-link asserts three
different screens, and a single final frame hides the two the reviewer most needs to see.

`ui-certify.ts` is deliberately **not** wired into `twin-check.sh`. Automation's job here is to
produce the evidence; grading it is the reviewer's. A checkmark asserting "the UI is fine" that no
one looked behind is exactly the fake-success the twins bar forbids — and this rung has already
been bitten by it: for months every journey silently skipped and reported PASS on any machine whose
playwright revision didn't match its installed chromium, so the top conformance rung certified
itself green while launching no browser at all (2026-07-25).

What that means in practice: UI navigability is signed off in **§9's adversarial review**, by a
reviewer who opened the frames. There is no status check standing behind that — review here is
local, so nothing but the review itself is between a green gate and an unusable mirror. A reviewer
who records a verdict without having opened the screenshots has certified nothing — and the
journeys passing is *evidence*, not a verdict. Ask of each shot: does it read as the real product;
is the seeded data legible on screen rather than merely present in the DOM; is anything collapsed,
overlapping, or invisible.

The journeys themselves are a **liveness** check — they catch a mirror that is broken, never one
that is merely bad. Only a reviewer catches the second, and only by looking.

### A gate must never grade itself green on work it skipped.

The same investigation found the UI-navigability rung — the repo's *top* conformance rung — had
been certifying itself green while running **nothing**: `browserAvailable()` returned false
because playwright's expected chromium revision wasn't installed, and both the per-test guard and
`scripts/ui-journeys.ts` responded by returning success. The suite printed passes having never
launched a browser.

Applies to any capability that can be environmentally unavailable: **absent capability ⇒ FAIL
with the fix command**, never a silent pass. If an environment genuinely can't run it, require a
deliberate, loud opt-out (`TWIN_ALLOW_SKIP_UI_JOURNEYS=1`) that states the rung was not verified —
so a reader can never mistake that run for a verified one. This is the same
never-a-fake-success rule the twins hold vendors to, turned on the gate itself.

## 9. Adversarial review (MANDATORY before merge — not optional)

**And this section is NOT birth-only.** It reads as part of building a pack from zero, and it is
worth as much or more on DEPTH work against an existing one: the 2026-09-03 lane that took stigg
and elevenlabs from 174 to 208 done had 23 confirmed breaks found by two skeptics, several of them
data loss or false greens in code it had just written, including the pull-path tombstoning above and
an id mint that re-issued a deleted id. New capabilities in an old pack get the same two rounds and
the same revert matrix as a new pack does.

**Tell every skeptic WHICH VENDOR VERSION is authoritative.** A reviewer judging vendor fidelity
reads whatever SDK happens to be installed, and that is not always the one the consumer pins: a
2026-09-02 round-one blocker called a route invented because it had been dropped in a later SDK
major, while the major our own consumer pins still ships it. Deleting it, as that review advised,
would have broken the twin for the very app it exists for. Name the authoritative version and the
consumer's pin in the skeptic's brief, and treat "this does not exist" as a claim that needs the
version stated.

The gate proves the twin isn't *broken*; it does NOT prove it isn't *false-green*. Before merging,
**you (the building agent) spawn an independent read-only skeptic as a sub-agent** — a fresh
`Explore` agent context with a refute mandate (this is NOT your own self-assessment; a fresh context
catches what you're blind to). It is **read-only** and is told to **default to "done" being false and
try to REFUTE every `done`** by reading the code. It must NOT edit files or run mutating git — and
**tell it explicitly NOT to run the gate**: its mandate is refutation by READING, and a
skeptic-launched gate run only adds the sibling machine load §8 warns about while proving nothing a
read would not. **Freeze the tree before spawning**: commit, and point every skeptic at that commit
— findings against files you are still editing describe code that no longer exists, and the verdict
you record must name the sha it judged. Have it hunt:

- `verify()` that passes on an empty/dirty workspace (not failable), or asserts only a status code;
- success asserted on a failure path; the vendor's negative 4xx missing;
- `?? []` / `|| {}` masking; a verify that would still pass if the handler body were `return {}`;
- hardcoded UI / marker-only verify on a data screen (claims behavior it can't prove);
- semantic infidelity (wrong status/state-machine/shape vs the real vendor);
- **subject-id collisions** that make a create return the wrong resource (pack-internal id minting
  can still collide within a type — the kernel resolves by `(type, id)`, §5 note). Hunt BOTH
  directions by name: **"create after connector pull"** (a local mint lands on a pulled vendor id)
  AND **"pull after local create"** (a pulled id lands on a minted one) — a namespaced local id
  base (datadog's `EVENT_ID_BASE`) is the clean fix;
- **fixtures shaped like nothing the real SDK ever emits** — a verify or connector test that is
  green over an impossible input has proven fidelity against a client that doesn't exist, and hides
  the bug the SDK's actual shape would hit;
- **omitted interfaces** — start from the motivating app's actual clients and vendor documentation,
  then compare them with the descriptor, manifest and census. Refute completeness by looking for
  surfaces absent from every list, including protocols other than the one already implemented;
- **denominator padding** — newly-added todos that are mis-tiered or fabricated/non-existent surface.

**Two preconditions the 2026-09-16 six-pack batch added, each from a skeptic that passed a
defect it could not have seen.** First, **the skeptic's model is set by the orchestrator, outside
the builder's environment** — a builder spawns its skeptic with the model its own environment maps,
so a builder on a weak model gets a weak skeptic: a fresh context on the same model is a
self-assessment with new eyes, not an independent review. In that batch the builders' own two
rounds passed a protocol-2 `index.ts` that never called `registerPack`, four fine-tune endpoints
answering 200 with nothing stored, and a conformance module still at its scaffold seed; a stronger
model reading the same frozen sha confirmed eleven defects the first day. Second, **a skeptic
reviews only a pack `scripts/pack-hygiene.ts` has passed** (it runs inside T0, §8): a raw NUL byte
in a 1,246-line handler made `grep` treat the file as binary, and every text audit of it — both
skeptic rounds included — returned nothing and read as clean. Refutation by reading cannot see a
file it cannot read.

**Demote or fix anything it can't confirm.** This is the same bar a completeness sweep over an
existing pack clears — a new pack is not exempt from it. Only merge once both independent
rounds find no unresolved false-greens. Then complete the revert matrix and ratchet
the census.json `baseline` slice once (§7 point 6): demotions and review-driven fixes from either round
moved your done count off the number you seeded.

**Expect ~10-15 minutes on a 100+ capability pack — the skeptic has not stalled.** Let it finish.
And run **TWO rounds, with DIFFERENT attack surfaces** — round two is not a re-run of round one:
round one refutes the original `done`s; round two explicitly attacks **round one's fixes** —
hollow pins (a fixture that cannot actually fail against the bug it claims to pin), assertions
satisfied by unrelated markup, a rewritten check whose teeth stop at the dispatch — **their
COMBINATIONS** (two round-one fixes interacting on the same state), and **re-hunts round one's
finding CLASSES in the files round one never opened** — a class found once is a class the whole
pack plausibly has. tinybird's
conformance check needed exactly this second pass: round one caught two constants asserting about
each other; round two caught the replacement grading only the router's own miss, leaving seven
handler branches deletable (§6, conformance teeth). **Budget round two as a full review** — it
historically finds as much as round one. A fix authored under review pressure deserves the same
skepticism as the original `done`.

**Round two's sharpest mandate: attack each fix's NEW REACHABILITY.** A fix does not merely
repair — it changes what executes, and the worst round-two findings have been defects the fixes
*introduced*: groq's round-one "skip unpushable actions instead of aborting" made the rest of
the push sweep reachable, so a local delete fired `DELETE .../files/file_twin_1` — the twin's
own minted id — at the REAL account; mistral's round-one fix left `conversation_entry.delete`
pushable to an endpoint that exists nowhere. (The defect need not be NEW code — groq's
id-mapping bug and even a pin blessing it existed at build time; the round-one fix merely made
them RUNTIME-REACHABLE, which is the point: reachability, not authorship, is what round two
must trace.) For every round-one fix, trace what it made newly reachable BEFORE walking the
matrix below. And build the matrix **per fix, then per capability that NAMES the fix**: the
cohere build's round two left four green cells that were not hollow pins but UNPINNED FIXES —
a fix no capability names walks the matrix invisible (2026-08-31) — when no existing capability claims a fix, ADD one; a cell is hollow BY CONSTRUCTION otherwise (deepseek round one landed a fix that way).

**The skeptic's target is a NAMED, FROZEN SHA — and the matrix is re-walked at the final
head.** Hand each review round one commit hash and do not advance it while that skeptic
reads; fixes land ON TOP and the next round gets its own frozen SHA (this resolves the
apparent tension with committing the green candidate first: commit, freeze, review, fix
forward). A review of a moving tree makes the skeptic cover for the process — mailgun's
lanes said so themselves (2026-08-31). And cells walked incrementally against intermediate
heads prove nothing about the final state: after the LAST fix lands, re-walk EVERY cell at
that head — mailgun's two late hollow pins hid exactly in the gap between an early walk and
the final tree. Two refinements from practice (perplexity, 2026-08-31): a fix found MID-review
lands as an ADDITIVE commit on top of the frozen SHA and is DISCLOSED to the running skeptic
(never rebased under it); and a RETRACTION (removing a claim) is structurally unpinnable —
record its cell as hollow-by-design with the retraction named, the same convention as
dead-code cells.

**Run the revert matrix — MANDATORY, and the single highest-yield technique across every pack
build that has used it. It catches what BOTH skeptics miss.** It is mechanized:
`bun scripts/capability-mutation-sweep.ts <vendor> .` rewrites one line of the pack at a time
(every `if (…)` guard to `if (false)`, every object-literal property to `undefined`), re-runs every
`done` verify in a child per mutation, and prints the `done` capabilities NO localized mutation
reddened — the hollow set — exit 1 if it is not empty; every file is restored byte-identical
(SHA-256-checked) after each mutation, so run it only on a checkout nobody else is writing to.
Its first run on a shipped Supported pack (openrouter, 2026-09-16) found three hollow `done`s T0
had been passing. Read each hollow by hand before demoting: a cell whose behavior is the ABSENCE
of a route (an unmodeled 404 fallthrough) has no line to neuter and reads hollow by method, not
by fact. The hand-walked matrix below is still the bar for the cells the sweep cannot reach. That is not a figure of speech: on
the 2026-09-02 bitly and stigg builds the matrix found a hollow pin that two independent
adversarial rounds had each read past — a fix that was real, and evidence for it that was not.
A skeptic reads for a claim that is wrong; the matrix asks whether anything would notice if it
were. Budget for it as its own pass, not as a formality after the reviews.** For EVERY fix the review produced: re-introduce that fix's bug in a
scratch copy of the tree — the MINIMAL revert, not a blunt sabotage — and minimal means NARROW enough that its own cell is the signal: a one-constant flip that reddens 26 cells is technically red-by-name and tells you almost nothing about the fix under test (deepseek, 2026-08-31) — and run the pack's verifies;
the capability that CLAIMS the fix must go red **by name**. A pin whose cell stays green is hollow;
a cell that goes red must also be read for WHICH assertion fired, because the second hollow-pin
shape is **a pin with no sensitivity to the bug** — the re-introduced defect (or a saboteur used in
its place) is too blunt to reach the NEW assertion, so something upstream reddens the verify and
the matrix cell reads red for the wrong reason. And the COMMONEST cause of a green cell is
neither of those: it is a FIXTURE THAT CANNOT REACH THE DEFECT AT ALL — ids sitting below the
mint base, an A→B→C sequence where the bug needs a revert, a single-batch fixture for a
cross-batch bug. Before calling a pin hollow, ask "does this fixture make the defect
reachable?" (two of hubspot's three green cells and both of cohere's were this, 2026-08-31).
Record the matrix (fix → capability id → reddened by name / hollow) alongside the skeptic
verdicts below.

**After EVERY review round, re-read the pack's own README against the round's outcome.** The
generated docs gate (`--check-docs`) covers only the ROOT README/CONFORMANCE tables — a pack
README is checked by nobody, and hubspot's round-two blocker was round one's retracted
gap still shipping in the pack README. Coverage prose and honesty notes in
`packages/twin/<vendor>/README.md` move in the same commit as the fix they narrate.

The mechanics are load-bearing:

1. **Commit the green candidate first and require a clean worktree.** Every cell starts from that
   exact commit. `git checkout -- <file>` restores from HEAD and will silently wipe uncommitted
   sibling fixes, so never use it as an informal backup.
2. Put the scratch clone under this checkout's `node_modules/.<vendor>-revert/`, which is gitignored
   and excluded from every gate scan. A copy under `/tmp` cannot resolve workspace packages such
   as React and `@volter/world-core`; all cells then report `RUN FAILED` and the matrix misleadingly
   reads 0/N hollow. The nested location can resolve the already-installed parent `node_modules`:

   ```bash
   VENDOR=<vendor>
   REVERT_SCRATCH="node_modules/.${VENDOR}-revert"
   REVERT_BASE="$(git rev-parse HEAD)"
   git clone --shared --no-hardlinks . "$REVERT_SCRATCH"
   git -C "$REVERT_SCRATCH" checkout --detach "$REVERT_BASE"
   bun --cwd "$REVERT_SCRATCH" scripts/manifest-baseline-one.ts "$VENDOR"  # green control
   ```

3. In that scratch clone only, reintroduce **one** minimal defect and run the same one-pack command.
   Read the named regression and the assertion that fired. Restore the touched file with
   `git -C "$REVERT_SCRATCH" checkout -- <path>` before the next cell, then rerun the green
   control. Never edit or restore the primary worktree while walking the matrix.

Run isolated-seam green controls before any sabotage as described in §7 point 2. A control after
mock/restore cycles measures a perturbed module graph and cannot establish that the kill-list was
green before its seam died.

**This step is NOT optional, and the gate cannot replace it.** The gate (tsc + tests + architecture
+ manifest baseline) proves the twin isn't *broken* — it is structurally blind to false-greens: it
cannot tell a data-coupled UI verify from a marker-only one, semantic infidelity from fidelity, or a
thin manifest from the real surface. §9 is the only thing between a green gate and a false-green twin.
So: **record the skeptic's verdict** — paste its per-`done` findings into your commit message / handoff
report. An un-recorded review is treated as *not done*; a reviewer (or the next maintainer) must be
able to see that an independent skeptic actually ran and what it found. (In practice this caught a
real slip — an OpenRouter build whose gate was green shipped two marker-only UI verifies that only the
§9 review flagged.)

> **Measure in isolation, or you'll cry false-green.** Run a pack's verifies via
> `bun scripts/manifest-baseline-one.ts <vendor>` (its own process) — that's the canonical gate.
> Running many packs' verifies in ONE process exhausts the `Bun.build` per-process ceiling and makes
> the later pack's **UI** verifies fail spuriously — that's a measurement artifact, NOT a false-green
> (the `mirrorBundle` is memoized, so a pack on its own does exactly one build and passes). A reviewer
> who runs everything in one process will wrongly report the UI caps as broken.

## 10. Acceptance checklist (from ./conformance.md)

- [ ] Manifest is the real vendor surface (`total >= 50`), tiered honestly, every `done` has a
      passing failable `verify()`, **0 regressions**.
- [ ] A fidelity test drives the twin **unmodified** — the real vendor SDK, or real-transport
      `fetch` for REST/XML vendors with no canonical SDK. It may live in a dedicated
      `<vendor>-sdk.integration.test.ts` OR inside `<vendor>-twin.test.ts` (location varies across
      packs) — what matters is it EXISTS and proves an unmodified client speaks to the twin.
      **"Unmodified" means un-patched, not un-configured:** when the SDK's default API version has
      moved past the one you model (e.g. `@notionhq/client` v5 defaults to `Notion-Version:
      2025-09-03`), pinning the version through the SDK's own public option is legitimate — it is
      what a real integrator pinning a version does. So is pointing the SDK at a plain-HTTP
      endpoint through its own public config (`allowInsecureConnection` and friends on the
      Azure/Google SDKs) — configuration, not modification. Record the pinned version in the README
      `## Coverage`. What's forbidden is monkey-patching the client, hand-rolling requests to dodge
      it, or silently serving a different version than you claim.
- [ ] The human-surface question was settled with "Does this vendor get a mirror?" (NOT by "does a
      UI exist"). **If it mirrors:** real twin state (data-coupled), API↔UI parity via the shared
      `applyXxxWrite`. **If the API is the product:** no mirror and no UI capabilities, with the
      reason stated in the README `## Coverage` + the manifest. **If vendor UI is protocol:** one
      state-coupled renderer at the vendor route, with UI capabilities/journeys but no fabricated
      second dashboard.
- [ ] Connector **pull** over an injected client, idempotent — required when the vendor has readable
      state. For a write-only protocol, pull only a genuine observable such as its banner/
      capability handshake; keep twin-only local readback outside the vendor manifest, and record a
      reasoned impossible gap if no readable signal exists. **Push MAY be a filed `todo()` gap when
      the vendor exposes no write path** — an untickable "push" box for a read-only vendor is not
      the bar.
- [ ] `readOnly` forbids writes; unmodeled ops fail like the vendor.
- [ ] **The `pack` descriptor is COMPLETE** (§3): `vendor` matches the directory, `transport`,
      `resources`, `specSource`, `description`; `rateBudget`/`pullPosture` where they apply; and
      **both XOR rulings made, not skipped** — exactly one of `hosts`/`hostsNone` (§7 point 8) and
      exactly one of `endpointEnv`/`endpointEnvNone` (§7 point 12), each grounded rather than
      invented. `adoption` names every official npm client, scope, credential env stem and extra
      world id — and claims nothing this twin cannot honor.
- [ ] **No hand-table duplicate of a declared fact**: nothing added to `VENDOR_HOSTS`, `SDK_TWINS`,
      `SDK_SCOPE_VENDORS`, `ENV_STEM_VENDORS`, `VENDOR_WORLD_IDS` or `APP_READ_ENDPOINT_ENV` for
      this vendor (dual declaration throws — §7 points 8, 11, 12).
- [ ] **`bun scripts/pack-facts.ts` re-run and `packages/world-core/generated/pack-facts.json`
      committed** after the last descriptor edit; `bun scripts/pack-facts.ts --check` and
      `bun test scripts/pack-facts.test.ts` green (§7 point 13).
- [ ] tsc clean; pack tests green; architecture guardrail + capability meta-test green.
- [ ] `## Coverage` section in the README; every unbuilt surface listed as a todo.
- [ ] The motivating app scenario ran with its unmodified clients inside a World. Verify a
      meaningful write, subsequent read and failure/deletion path. When multiple interfaces
      address the same vendor state, write through each and read through the other; include the
      app behavior that depends on that consistency. Keep the scenario with the project's
      evidence for that app, beside any earlier journey. `covers` establishes connection requirements, not this proof.
      If an interface or workflow remains unsupported, keep that gap explicit and limit the
      completion claim accordingly.
- [ ] §9 independent skeptic was run and **its verdict is recorded** in the commit/handoff report;
      no unresolved false-greens (incl. every primary-data UI verify is data-coupled, not marker-only);
      both rounds and every revert-matrix cell completed before the final baseline ratchet.
- [ ] §8's per-merge set ran green (pack suites + typecheck + the meta-gates this wiring touched),
      and the pack's claim is stated as **queued for the next full gate** — which runs on the
      owner's ask, with `TWIN_CHECK_CONCURRENCY=1` over one clean, committed, unchanged HEAD whose
      provenance SHA is recorded and during which no edit happened.

## Self-audit: is your manifest honest? (run this before calling it done)

The gate catches broken twins; it does NOT catch a *thin* or *false-green* manifest. Audit yourself:

1. **Enumeration.** List your capability ids; open the vendor's API reference; are you missing whole
   resource types / methods / a webhook family? If you listed 50 and they have 150, say so (README
   `## Coverage`: "partial; core modeled; see todos") — don't imply completeness. Re-running the
   MANIFEST-COMPLETENESS AUDIT on your own pack is exactly this step.
2. **Tiering.** For each `core`: would >50% of integrations use it in week one? If not, demote. For
   each `niche`: would you be unsurprised if most users never touch it? If you'd be surprised, promote.
3. **False-green hunt** (per `done`): does its `verify()` start from a fresh root? does it assert
   field *values* (not just a status)? does it assert a negative 4xx? would it still pass if you
   replaced the handler body with `return {}`? For UI: does it prove data-coupling, not just a marker?
   If any answer is wrong, fix the verify or demote to `todo`.
4. **Regression.** `bun scripts/manifest-baseline-one.ts <vendor>` must print `regressions=0` and
   `done>0 total>=50`. Never push with a failing `done` verify — fix it or demote it.
5. **Adversarial pass.** Have an independent READ-ONLY reviewer (`Explore` agent) try to REFUTE each
   `done` and confirm the new todos are honestly tiered (not denominator padding). Demote anything
   unproven. This is mandatory before merge.

## Troubleshooting

| Symptom | Cause → fix |
|---|---|
| `architecture.test.ts`: "vendor SDK imported at runtime" | SDK imported outside a `*.test.ts` → move it into `<vendor>-sdk.integration.test.ts` only (SDK is a devDep). |
| `architecture.test.ts`: conformance re-exported | `index.ts`/`cli.ts` imports the conformance module → remove from `index.ts`; in `cli.ts` use `await import('./<vendor>-conformance.ts')` lazily. |
| `capability-manifests.test.ts`: "no manifest wired" / unknown vendor | there is NO central edit any more — the registry globs `packages/twin/*` and throws on a vendor whose manifest module or conventionally-named export is missing. Fix the EXPORT NAME (`<vendor>Capabilities` and `<VENDOR>_CAPABILITIES`), not a registry. |
| `capability-manifests.test.ts`: missing `<vendor>-capabilities.test.ts` | the meta-test requires it → add the baseline test (copy from the exemplar). |
| baseline fails: `total<50` / `done==0` | thin manifest → enumerate more real surface; prove a few done. |
| baseline fails: "N regressions" | a `done` verify now fails → `bun scripts/manifest-baseline-one.ts <vendor>` to see which; fix or demote. |
| mirror bundle times out under the full suite | `Bun.build` per-process ceiling → expected; the meta-test isolates each manifest in its own process (`scripts/manifest-baseline-one.ts`); test your pack alone. |
| a create returns the WRONG resource (e.g. a project instead of your flag) | the handler is writing/reading the wrong subject — the kernel resolves the returned resource by `(type, id)`, so check the `subjectType`/`subjectId` you pass to `applyTwinWrite` (and that your pack doesn't mint duplicate ids within one type). See §5 note on subject ids. |
| a UI verify passes but the screen is hardcoded | marker-only verify on a data screen → make it data-coupled (§6 UI bar); reserve marker-only for chrome. |

## Note on the scaffold script

This section used to say a scaffold generator was deliberately **not** provided. One exists:
`scripts/scaffold-pack.ts` (§7, "Start here"). The old reasoning still holds for the half it does
not attempt — twins vary enough by archetype (REST/GraphQL/generative/XML-SigV4/
management-wrapper) that a one-size *domain* skeleton would be thin — so the scaffolder is scoped to
the **wiring**, not the twin: pack shell, the v2 `pack` descriptor with your hosts/adoption compiled
in, the registrations, the censuses, and the regenerated pack-facts artifact. Domain code still
comes from copy-from-exemplar (§0).

The bar the old note set is the one it holds itself to, with one honest exception: what it emits is
`architecture.test.ts`-clean and tsc-clean, but `assertManifestBaseline` is **RED** until you earn
the first real `done`, because a scaffold that shipped a `total>=50` manifest stub would be
fabricating a denominator. That red is the line's honest state, not a defect — and every judgment
slot it leaves is a loud `SCAFFOLD TODO(A1|A2)` marker rather than a plausible guess.
