Skip to content

feat(runtime): type runtimeId off the union and guard the literals (warren-c80e) - #964

Merged
jayminwest merged 3 commits into
jayminwest:mainfrom
luantaraschi:feat/runtime-id-union-and-gate
Aug 18, 2026
Merged

feat(runtime): type runtimeId off the union and guard the literals (warren-c80e)#964
jayminwest merged 3 commits into
jayminwest:mainfrom
luantaraschi:feat/runtime-id-union-and-gate

Conversation

@luantaraschi

Copy link
Copy Markdown
Collaborator

Items 4 and 5 of #846, sent separately as agreed on the issue. Items 1 to 3 landed in #887.

Item 4: type runtimeId off the union

RuntimeId cannot be the type at the dispatch seam. WARREN_EXTRA_RUNTIME_IDS (warren-c4be) lets an operator register ids beyond the canonical list, and the acceptance harness uses it for stub-shell, so an unknown id has to keep flowing through. The documented widening the issue pointed at is AcceptedRuntimeId, which already existed in src/registry/schema.ts.

src/runtime/ had no dependency on src/registry/, and adding one for a type alias reads backwards. So the alias moves to the canonical wire home instead, next to KNOWN_RUNTIME_IDS, and src/registry/schema.ts re-exports it.

src/core/wire.ts sits at its file-size budget, so the runtime-id vocabulary moves into src/core/wire-runtime.ts and wire.ts re-exports the module. That is the same split already done for the actor, inbox and insight vocabularies, and check:wire-types follows export * chains, so all four names stay enforced. The budget for wire.ts drops from 627 to 606.

Sites now typed AcceptedRuntimeId: src/runtime/contract.ts, both k8s entrypoints, and the two src/runtime/local/profile.ts functions the issue did not list.

The "pi" | "claude" mismatch

src/runs/stream/stats.ts declared its own union containing "claude", which is not a runtime id. The issue asks whether that masks anything. It does not: the field is only read into a log payload, at persistInStreamUsage, and the four call sites pass a literal. Nothing branches on it.

What it did cost is log correlation. Filtering the stream logs by runtime id missed every claude-code row, because the tag said claude while the rest of the system says claude-code. The field is now RuntimeId and the three literals are corrected, so the tag agrees with runs.runtime and with the event stream.

Item 5: the lint guard

scripts/check-runtime-ids.ts fails the lint gate when a runtime-id literal is written outside src/runtime/adapters/. It rides inside lint because the check:all manifest is frozen, and also runs standalone as bun run check:runtime-ids.

Two things follow check-wire-types.ts:

  • The enforced literal list is derived from KNOWN_RUNTIME_IDS at run time. A second hand-copied list is the drift class the guard exists to prevent, and it would have gone stale already: sapling left the union while this issue was open.
  • Every exemption is a named file with a reason. There is no wildcard.

The allowlist is split in two, because the entries mean opposite things:

  • DELIBERATE, 11 files that name a runtime because that is the file's job. The seven built-in agent definitions declare the runtime they dispatch onto, the three per-runtime shape records are keyed by runtime id by design, and src/registry/schema.ts declares DEFAULT_RUNTIME_ID.
  • PENDING_ADAPTER, 5 files of runtime-conditional logic that predates the registry: usage-aggregate.ts, stream/bridge.ts, stream/budget.ts, local/drive.ts, local/profile.ts. These are phase-2 candidates, not endorsements. Freezing them means the residue can shrink but not grow.

There is also a stale-entry check: an allowlisted file that no longer writes a literal fails the gate, so an exemption cannot quietly become a wildcard. It earned its place during the work, catching the src/core/wire.ts entry that the vocabulary split had just made pointless.

What I ran

bun run check:all gives 11 of 12 gates green, including lint end to end with the new guard, typecheck, check:size on the lowered budget, check:bundle-size, and check:ci-parity. The twelfth is check:coverage, which also fails on 6bd40e4 untouched on this machine: I am on Windows, and the failures are mkdir and .sh not being executable, sqlite file modes, and path separators. 6bd40e4 fails two gates here, so the branch is not worse than the base on anything.

The split needed one thing the type checker alone would not have caught: src/ui/tsconfig.app.json is a composite project that lists the wire modules file by file, so wire-runtime.ts had to join wire-inbox.ts, wire-insight.ts and wire-actor.ts there. check:bundle-size is what caught it.

Full suite, same machine, both trees: 5122 pass and 101 fail on the branch, 5106 pass and 102 fail on 6bd40e4. Comparing the failing test names between the two, no test fails only on the branch.

Control for the new gate: appending const TEMP_PROBE = "claude-code"; to src/runs/reap/run.ts makes it exit 1, naming that file and line as writing the literal, and makes the repo-level test fail. Reverting turns both green.

scripts/check-runtime-ids.test.ts covers the derivation (including a runtime being removed, and a refusal to guess when the declaration is unreadable), comment lines, substring safety, the adapter and test exemptions, and both allowlist buckets, plus two tests that assert the real repo is clean.

…arren-c80e)

Items 4 and 5 of jayminwest#846. Items 1 to 3 landed in jayminwest#887.

RuntimeId cannot be the type at the dispatch seam, because
WARREN_EXTRA_RUNTIME_IDS lets an operator register ids beyond the
canonical list and the acceptance harness uses it for stub-shell. The
documented widening is AcceptedRuntimeId, which moves from
src/registry/schema.ts to the canonical wire home so src/runtime/ does
not have to depend on src/registry/ for a type alias.

wire.ts sits at its file-size budget, so the runtime-id vocabulary moves
to src/core/wire-runtime.ts and wire.ts re-exports it, the same split
already done for the actor, inbox and insight vocabularies. The budget
drops from 627 to 606, and src/ui/tsconfig.app.json lists the new module
alongside the other three.

src/runs/stream/stats.ts declared its own union containing "claude",
which is not a runtime id. It is read only into a log payload, so
nothing branched on it, but filtering stream logs by runtime missed
every claude-code row. The field is now RuntimeId and the three call
sites are corrected.

scripts/check-runtime-ids.ts fails lint when a runtime-id literal is
written outside src/runtime/adapters/. The literal list is derived from
KNOWN_RUNTIME_IDS at run time rather than hand-copied, and the allowlist
is split between files that name a runtime on purpose and the older
conditional code phase 2 absorbs. A stale-entry check keeps an exemption
from quietly becoming a wildcard.
@luantaraschi

Copy link
Copy Markdown
Collaborator Author

The ci-postgres failure here does not come from this branch. It is a same-day regression on main.

#963 renamed the columns in src/db/migrations/0049_sad_mandroid.sql:

ALTER TABLE `runs` RENAME COLUMN "burrow_id" TO "sandbox_id";
ALTER TABLE `runs` RENAME COLUMN "burrow_run_id" TO "sandbox_run_id";

The same commit updated the sqlite arm of src/db/migrations/drop-placement-tables.test.ts to match, at lines 49 and 50. The postgres arm of that same file was left on the old names, at lines 142 and 143:

expect(cols.has("burrow_id")).toBe(true);
expect(cols.has("burrow_run_id")).toBe(true);

That arm is test.skipIf(!isPostgresTestEnabled()), so it only runs under the ci-postgres workflow. The regular ci job skips it, and so does a local bun test, which is how it merged green.

Run #282 on warren/run_3gt7t2q3095d failed the same assertion at 06:03 today, roughly four hours before this PR existed, so it is not specific to this branch either.

I can send the two-line fix as its own PR if you want it from me, or leave it with you.

@luantaraschi

Copy link
Copy Markdown
Collaborator Author

Heads up on the audit in 929a6b9: it closed warren-c80e as realized by pl-3007, but items 4 and 5 of #846 are not in main yet. I checked at f40a15f before rebasing:

  • src/runtime/contract.ts:73 is still runtimeId: string, and both src/runtime/local/profile.ts functions too. Nothing is typed AcceptedRuntimeId outside src/registry/schema.ts, which already had it.
  • src/core/wire-runtime.ts does not exist, so the alias has not moved to the wire home.
  • src/runs/stream/stats.ts:118 still declares readonly runtime: "pi" | "claude", which is the mismatch that made the stream logs disagree with runs.runtime.
  • scripts/check-runtime-ids.ts does not exist, so there is no guard on runtime-id literals.

The PRs the audit lists (#947, #950, #955, #959, #963) are the burrow excision and the wire vocabulary rename, which is a different piece of work. Items 1 to 3 did land in #887, so I think the two got folded together by accident.

I have rebased this onto main so it is no longer behind. Happy to reopen warren-c80e, or to leave it closed and let this PR stand on #846 alone, whichever fits your tracker better.

One thing that is not from this PR: ci-postgres was failing on the previous head with drop workers + burrows placement tables (warren-3743) > postgres: migrated schema drops both tables and retains runs columns, which is the warren-3743 migration and not anything this branch touches. The rebase should pick up whatever main did about it.

@luantaraschi

Copy link
Copy Markdown
Collaborator Author

Correction to my last note: the rebase did not clear ci-postgres, and it was not going to. The failure is on main itself, not on this branch. It is the postgres arm of drop-placement-tables.test.ts still asserting burrow_id and burrow_run_id, which #963 renamed to sandbox_id and sandbox_run_id. #965 is the two-line fix for that, and I have just rebased it too.

So the merge order that gets both green is #965 first, then this one.

@jayminwest
jayminwest merged commit 78ae7a6 into jayminwest:main Aug 18, 2026
5 checks passed
jayminwest added a commit that referenced this pull request Aug 18, 2026
…erge session)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
jayminwest added a commit that referenced this pull request Aug 18, 2026
…5); ROADMAP: v0.18.0 campaign in flight (pl-a37b)

The 'no bwrap => 503' test relied on the host platform: checkBwrap
self-skips on non-Linux, so the assertion only held on Linux hosts and
failed standalone on macOS. Pin platform: "linux" through the existing
deps.platform seam.

ROADMAP: record the v0.18.0 any-setup campaign (pl-a37b) as in flight,
mark the AgentRuntimeAdapter phase-1 slice complete (PR #964).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants