npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@sema-agent/client-core

v0.94.0

Published

Client-side session runtime shared by every sema human client (TUI / web / desktop): sema wire frames (AgentEvent) -> CC session vocabulary (SDKMessage) with dual-plane output (transcript/chrome), deterministic transcript ids, lane discipline as a type, a

Readme

@sema-agent/client-core

Client-side session runtime, shared by every sema human client — TUI, web and desktop. It turns sema wire frames (AgentEvent) into CC-shaped session state (SDKMessage transcript messages + ChromeEvent UI events), and it is where the client-side session logic lives, so that each UI is left with rendering and input handling only.

Renamed from @sema-agent/wire-cc-adapter (0.1.x, deprecated — see Migration below).

Integrating a host? Read docs/INTEGRATION-CLIENTS.md first — the formal integration contract for the three consumer hosts (TUI / web / desktop): export-surface map, the event-projection contract, the ack-consumption obligations (which receipt keys may be absent and what absence means), capability-gate duties, the multi-session sessionKey contract, the open-gap ledger, and a new-host checklist. Every section carries an implementation anchor so you can audit it against the source — if the doc is wrong, that is a bug in the doc, so report it.

That file ships inside the tarball, together with CHANGELOG.md and docs/REFACTOR-LEDGER.md (the two the checklist names as required reading before an upgrade), so the link above resolves for someone who installed the package rather than being rewritten onto a repository they cannot read. scripts/run-integration-doc-freshness-test.mjs checks that against the real npm pack manifest.

Design axioms

  1. Every client copies the CC UI/UX — but no CC shape reaches the wire. The CC vocabulary (SDKMessage), chrome events and CC-worded copy are collected here; the wire stays free of them.
  2. The wire carries only neutral, universal UI-plane information — structured results, DAG relations, lifecycle discrimination, ownership filtering: what any human client needs, CC-skinned or not.
  3. The debt this package pays off is an information cut, not a vocabulary problem. In a single-process client (CC) the UI and the engine share memory and UI-plane facts are free; here they cross a wire on which the model was historically the first-class consumer and the human UI second. This package makes that cut an explicit, first-class contract instead of per-bug patches inside one shell.

Scope

Version: 0.94.0

  • Today — the adapter seam, the whole adapt() pipeline (all 14 A-layer arms plus the B/D/E tool-card layers), the notification/caps/model families, the adapter kernel (stream driver
    • downstream projectors), the request-shaping surface for both lanes (interactive and the headless -p print lane), the fleet/panel projections and the subagent wire family, the workflow monitor contract + live source, the model catalog tables, and the HITL family (hitl/: the D-1 approval bridge, the ask-gate stream bridge, the tool-approval wire, plan review, and the pending-approvals push feed).
  • Planned — what is still in the shell: the registry user-plane hand-copies (they retire onto @sema-agent/sdk/registry, not into this package); plus everything that is genuinely host-specific — Ink rendering and keyboard interaction, the print lane's stream-json projector and stderr triage copy, filesystem persistence, pty/terminal, and the signal-path teardown. The web session view (first target consumer) and the desktop client (second) consume the same package.

Dependencies

  • Runtime dependencies: diff (jsdiff 9 — zero transitive deps, pure JS, browser-safe). It is the T20 client-side hunk computation; core ruled that hunks belong to the client, so keeping it out of this package would mean three re-implementations (TUI / web / desktop). The portability guard pins the runtime dependency set with an equality check, so this list cannot drift. sideEffects: false.
  • Peers:
    • @sema-agent/agent-types — the CC session vocabulary (SDKMessage and friends). Type-only: it ships no runtime code at all.
    • @sema-agent/sdk — the wire contract. Value-level, not type-only: AgentClient, SseIdleError, probeHealth, APIError and TaskStopConflictError are imported as values in five modules, and so are the three frozen rule-offer vocabularies the approval reader narrows against — the tables live upstream precisely so this package does not keep a second copy that can fall behind. The browser bundle really bundles the SDK through (the portability guard would exit 3 rather than quietly mark it external).
    • The declared floor is >=18.1.0 (raised from >=12.0.1 in 0.94.0: the request-side permission-mode vocabulary, the decision-attribution key type, the cancellation-cause list, the status-derived coarse-code list and the routing-failure denial word are now taken from the SDK itself, all of which it declares from 17.x or 18.x on; before that raised from >=11.3.0 in 0.84.0: the per-session background listing (sessions.background), its capability bit capabilities.background.listFace and the closed background-status vocabulary are typed from 12.x on, and the package reads all three, so it no longer compiles against 11.x; before that raised from >=11.2.1 in 0.80.0: a deny decision may now name its settler — settledBy: "policy" is typed on both the durable decide body and the live respond body from 11.3.0 on, and the gate record's settlement vocabulary carries its thirteenth word policy_refused, which this package reads to place a refusal in the policy bucket rather than the person's; the package now compiles against 11.3.0 and no consumer ships 11.2.x any more, so the older floor lost its witness; before that raised from >=11.0.1 in 0.79.0: capabilities.mcpProbe, the MCP probe face (mcpCapabilities / probeMcp with McpProbeFace) and the write receipt's third liveness arm (stillLive: "unknown") are typed from 11.2.x on, 11.2.1 adds TaskRequest.approverPosture and the mandated approval-frame key to the types and the runtime key anchor, the package now compiles against 11.2.1, and no consumer ships 11.0.x / 11.1.x any more, so the older floor lost its witness; before that raised from >=9.8.1 in 0.78.0: DeniedBy carries its tenth word read_boundary, rules.write answers a stillLive-discriminated body, RemovalLiveness / RuleWriteRequest / RuleWriteResult / RuleWriteBehavior are exported from the SDK root and TaskRequest.excludeAllTools is typed from 11.x on, the package now compiles against 11.0.1, and no consumer ships 9.8.x any more, so the older floor lost its witness; before that raised from >=9.7.1 in 0.75.0: Capabilities.deviceExecutor.management is typed from 9.8.x on, the package now compiles against 9.8.1, and no consumer ships 9.7.x any more, so the older floor lost its witness; before that raised from >=9.6.0 in 0.74.0: Capabilities.approvalsStreamLive / .executionLane, LivePendingRow.frame, the live_* approval-stream events and gates[].toolCallId are typed from 9.7.x on, and no consumer ships 9.6.0 any more, so the older floor lost its witness; before that raised from >=9.4.0 in 0.71.0: the tool_disclosure / tool_progress frames and ToolApprovalFrame.readRootCandidate are typed there; earlier: raised from >=8.8.0 in 0.69.0: the reasoning_end frame and McpStatusPanel.lastLegMcp are typed from 9.4.0 on), and it is witnessed: the guard checks that an actually installed SDK at that line still exports every value-level symbol this package imports and still declares TaskStats.costMicroUsd (the key costOrNull reads). A floor nobody ever ran is a promise, not a contract.
  • Support floor: the oldest server, engine and SDK this package still supports — server 7.106.0, engine 7.35.2, SDK 18.1.0 (SDK raised from 12.0.1 in 0.94.0; server / engine raised from 7.104.0 / 7.33.8 in 0.92.0). The values are declared in scripts/support-floor.json and exported at runtime as SUPPORT_FLOOR; servers below the floor are no longer witnessed, and the readings that existed only for them were retired in the same release.
  • The direction is constitutional: sdk (wire) + agent-types (vocabulary) → this package, and neither depends back.

Signed design

  • Dual-plane output — transcript (persistable CC messages) vs chrome (ephemeral UI events). Criterion: "what should still be visible after a restart goes to transcript; pure-transient goes to chrome."
  • Deterministic transcript ids — derived from wire stable keys (frame id > seq > toolCallId); ctx.uuid() only for keyless synthetic frames. Invariant (guarded): same stream replayed ⇒ same id sequence. Since 0.83.0 the ids are UUID-shaped; only the shape, same-stream determinism and same-key-same-id are promised, not the derivation.
  • Lane discipline as a type — chrome events require a LaneProof; a historical ghost-row bug family is structurally impossible to reintroduce.
  • Field-level round-trip guard — every semantic wire field either maps into the CC message/chrome event or carries an explicit DROPPED annotation; reflective tests turn upstream field additions into compile-time red.

Shipped so far

0.1.0 shipped the seam types, the id derivation, and the first tranche of battle-tested pure functions extracted from the sema CLI shell (workflow lane discrimination, workflow poll envelope/projection, task-notification XML rendering with the <result> discipline, dedup keys).

0.1.2 landed the first half of the adapt() pipeline:

  • createWireToCcAdapter() / adapt(frames, ctx) — AsyncIterable<frame> → AsyncGenerator<AdapterOutput>, covering the pure-projection arms: assistant text / thinking / tool_use (engine-subagent param remap, _sema_* sanitising, reject/cancel/error sentinels, per-response grouping id), live delta coalescing, task_notification XML, diagnostics / steering / workspace-changed attachments, compact boundaries, retry status, workflow completion enqueue.
  • Host-coupled arms (panel store writes, hook fires, queue drops, task-ledger sync) project into additive ChromeEvent arms, each carrying its host consumption duty in the seam.ts comment — the side effect stays with the host, the decision logic moves here.
  • Ledger serialisation (exportLedger / importLedger / importLedgerFromTranscript) so a restarted host reloads the notification dedup state instead of re-feeding the model (the at-least-once completion-inbox hazard).
  • A differential guard: the same frame stream is fed to adapt() and to the CLI's sdkMessagesToCcEvents, asserting field-level equivalence of the transcript plane, the attachment payloads, the concatenated live deltas, and the observable host-store trajectories — plus the replay id invariant. Arms not yet ported are listed in ADAPTER_COVERAGE.todo and pinned per-fixture, so a silently missing arm turns the guard red.

0.2.0 is the repo/name migration: no behaviour change, both guard suites carried over and green (22 + 203 checks).

0.3.0 moved in the dependency-free pure functions and side-channel ledgers (subagent content, engine agent-panel, fleet panel projection, live question store, inline task stats, print tool-result frame, caps cache, tool-label store, task-description projection) with 140 new behaviour checks.

0.4.0 moved in the request-shaping gate family and the notification family:

  • notifications.ts absorbed the whole background-completion subsystem — the process-lifetime dedup ledgers (model-notification vs completion-card are separate key spaces), the idle completion watcher for workflow + background-agent runs, the outstanding-run counter with its subscription, the own-run ledger used for frame ownership, the delivery-failure supplement, and the hook_notice classifier half (ownership fail-closed mirrored from the server).
  • 17 *WireCaps projections (attachments, client context, cloud config, fork, images, mcp, model, permission, prompt profile, retain-background, rewind, self-orchestration, skills, ultracode, web search, agents, plus the wire client factory), the SSE idle triage, the classifier-verdict recogniser, the control router, and the model-budget/catalog/latch/effort rules.
  • New hostEnv.ts: the single host-environment read port. The moved projections used to default their env parameter to process.env, which throws in a browser; they now default to hostEnv() (the very same object under Node, {} where there is no process).
  • 🔴 One host assembly duty: installNotificationQueuePort(). Without it, task-notification delivery is dropped — notificationQueuePortMisses() must stay 0.

0.5.0 moved in the adapter kernel — the stream driver and the downstream projectors — and grew the host context to carry what a non-Node host must supply:

  • AdapterContext gained five optional ports (minor, not breaking): log, probe, signal, setTimer/clearTimer, coalesceIntervalMs. Every one is native in a browser (console, omit, AbortController.signal, setTimeout, a number) — no polyfill. Absent means that capability is off, never a fallback: the kernel does not reach for console, setTimeout or a filesystem probe on its own.
  • adapter/runStream.ts + adapter/downstream/* + the runtime half of adapter/types.ts moved in. The driver used to drag 1,571 files / 26 Node builtins / 412 react files into its transitive closure through two dynamic imports back into the shell; both are now ChromeEvent arms (last_turn_usage, plan_review_park) with their host duty written on the arm. Measured closure after the cut: 6 files, zero Node builtins, zero react.
  • Six stream-driver behaviours landed with it: the turn-open triple-clear (the third clear — stale prompt suggestions — plus the fifth sub-flow cut-out), the 1.5 s IDLE-FLUSH race that surfaces already-generated content while the model is emitting a long tool argument, manual iterator cleanup, the abort branch, and the no-result tail leg.
  • The coalescing cadence is now a host knob. CC itself runs two values in two hosts (its desktop host coalesces single-frame deltas at 16 ms; the 2.1.207 CLI line we track uses 100 ms), so the cadence is host render policy while the merge semantics stay here, in one place.

0.6.0 – 0.10.0 (batches B4 – B8) completed the frame dispatch and moved in everything that is not host-specific. The blow-by-blow lives in the src/index.ts head comment (the source of truth); the short version:

  • 0.6.0 — A-layer dispatch finished (14th arm: task_progress, with row binding, the workflow lane gate and the panel sweeps), the request surface unified into one builder with a field matrix, one credential resolver for all construction points, the compile-time verb façade, and package-level host assembly installHost({log,probe,queue,timers,settings,fs,session}).

  • 0.7.0 — the tool-result card B/D/E layers, structured-output whitelist takeover of the regex reverse-parsers, client-side diff hunks, and the paired ledgers folded back to module scope.

  • 0.8.0 — fleet ledger/projection + the subagent wire family, the headless detach wire (split: decision here, signal-path dispatch stays in the host), the workflow monitor contract + live source, hooksWireCaps on the SettingsPort, liveInitToolFace, the model catalog tables, and MIGRATED_COMPENSATIONS — the ledger of which shell-side compensation was split where, which chrome arm carries the host's half, and when upstream lets it retire.

  • 0.9.0 — the HITL family. hitl/hitlBridge carries the D-1 action binding: the boundCallId + boundInputHash two-tuple is read off the pending row the human saw and echoed back verbatim — never recomputed — and a 409 binding mismatch is a safety stop that is never auto-retried. Both code paths are pinned byte-for-byte by the pure gate. hitl/toolApprovalWire is a split: the gate decisions and both decide legs live here, the three-choice card itself is an ApprovalCardPort the host installs. hitl/askGateWire runs the park → decide → re-attach loop; hitl/planReviewWire closes the seventh and last wireConfig() hand-copy. hitl/approvalsFeed is the one behavioural change in the batch: a pending-approvals push channel over approvals.stream() that falls back to polling and periodically retries the push leg — it deliberately does not take over the D-1 fetch, which must stay authoritative.

  • 0.10.0 — the print lane (-p), the last batch on the migration bus. The request builder is now the only construction point for both lanes: the shell's headless path used to assemble its own TaskRequest literal, so the two lanes had silently drifted apart (REQUEST_FIELD_MATRIX records every remaining difference field-by-field with its reason, plus a gap flag where the difference is an omission rather than a decision). unregisteredRequestKeys makes that matrix executable: the request that actually goes on the wire must account for every key it carries. request/printNotification unifies the task_notification field set — the print exit now shares the interactive lane's single judgement (normalizeTaskNotification) and carries the residual attribution it used to drop (stoppedBy, resumable, partial, exitCode, diagnostics, result, lines, recentSteps, editedFiles, task_type, source, seq, injected), which matters most on the one lane with no human watching. Exactly one lane difference survives, and it is CC's own: status: killed becomes stopped on the print exit. The stream-json projector and the stderr triage copy deliberately stay in the shell.

Known intentional deltas from the CLI are enumerated in ADAPTER_DIVERGENCES. The compensation ledger is a different table (MIGRATED_COMPENSATIONS): the entries there behave identically on both sides — what it records is why a compensation exists and when it can be deleted.

Upgrading to 0.68.0 — read this first (breaking)

This release follows an engine release in which three facts that used to be sometimes absent became always present. Wherever this package had an arm for "the engine did not send it", that arm was either dead code or was quietly reading a positive fact as nothing. The arms are gone; absence now means one of three distinguishable things, and the distinction is the point.

1. Per-turn usage is now always on the wire. The engine emits a zeroed usage object together with an explicit "this turn was not measured" flag instead of omitting usage. So a turn-end frame with no usage at all is no longer a legal shape — it is a contract violation, and this package now says so out loud (through the host's dropped-frame sink, with its own verdict word and its own sentence, because the generic one — this build has no arm for it — would point the reader at the wrong side) rather than silently projecting something. Such a frame is not projected at all, and the run's totals are marked as a lower bound, because dropping the frame must not let a run that genuinely lost an accounting turn report an exact number. The engine floor moves with it: on older engines a real "no usage this turn" frame takes that path. There is deliberately no version-sniffing compatibility read — that would require this package to hold a number it cannot see, and guessing is worse than saying plainly that a frame does not match the contract. One consequence worth knowing: on a turn flagged as unmeasured, the zeroes are a placeholder, and that rule is carried all the way down the chain — the per-turn output count, the per-sub-agent cache-read total, the handle a footer reads, the end-of-turn metric the response-length reducer consumes, and the public per-turn usage reader all withhold the placeholder rather than hand out an exact zero. A non-zero value is never withheld: a placeholder is by construction all zeroes, so anything non-zero was genuinely measured and is passed through as a lower bound. One of those five is a public reader whose meaning therefore changed: the value it returns for nothing known about this turn is now the same before and after the engine upgrade — which is the point, since otherwise one unchanged caller would have silently started reading "this turn cost exactly zero". If you want the raw, judgement-free mapping instead, call the pure mapper beside it.

2. The ending words are now two tables, kept apart by who owns them. The single combined list is removed with no alias and replaced by the engine's closed set of reasons a run ended and the server's set of row states a run can finish in. They share three words but not all of them: one word for something outside stopped it exists only on the server side, and one for it paused and can be resumed exists only on the engine side and means very nearly the opposite of an ending. Merged, a new word on either side looked identical, and the tempting move — folding the unknown word into a known one — is exactly the mistake this package exists to prevent. Both tables are literal tuples with derived types, so a client should derive rather than hand-copy the words. The two predicates keep their names and meanings (and are now type guards); an alias was deliberately not left behind, since one would let a reader keep consuming the merged list and the split would have bought nothing.

3. The "an approval could not be resolved" notice now carries a cause, not a settlement word. These are not two names for one thing: one of the three causes is an in-fold refusal that carries no settlement at all, so the old field was always absent exactly where it mattered most. A "read the new field, else the old one" compatibility read would therefore answer nothing on the one shape that needs it, while letting a client believe it had covered older engines. This package does not do that read, and a standing check keeps anyone from adding one later.

Also breaking, at compile time only: two sentence-minting functions narrowed their parameter from an unvalidated value to the closed set they actually serve. The fallback sentence they carried was structurally unreachable and untrue — it announced "a word newer than this client" for a value that could never arrive — and worse, it made the surface look open, so nobody had put a compile-time fence on the table. With the parameter narrowed, adding a word upstream now fails to compile until the sentence is written. The neighbouring function whose vocabulary really is open keeps its fallback: one rule per surface, not one rule for all.

Everything else in this release is additive and safe to ignore until you want it: a reader for the parked approvals on a workflow record (built so that a credential cannot structurally reach the output, and with no field at all kept distinguishable from an empty list), a closed set for that family's refusal codes, readers for two notice payloads, a direct answer for "has that run released the session yet" that supersedes inferring it from a status word (absent on older engines, where behaviour is byte-identical), a distinctly named key for the sub-agent lower-bound bit that used to collide with the run-level one, and the engine-stop-word to session-vocabulary mapping moved in here so the clients stop each keeping a copy.

Guards

npm install
npm test   # = node scripts/run-client-core-all-gates.mjs — runs every suite below

npm test is a collecting runner, not a && chain: a suite that exits 3 (SKIP, its material is absent) does not short-circuit the ones after it, and the runner prints the three-part verdict itself (FAILED names + skipped names + arithmetic reconciliation). All-SKIP reports exit 3, never 0.

The suite set is a name-equality gate, not a lower bound: the runner cross-checks what it finds on disk against scripts/gates-manifest.json in both directions. A registered suite that is missing is red (a guard was deleted or renamed); a suite on disk that is not registered is red (whoever added it skipped the registration). Adding a guard is therefore two actions in one commit — the run-*-test.mjs file and its entry in gates-manifest.json (the row below is generated from that entry by scripts/gen-registrar-tables.mjs, and scripts/run-registrar-tables-test.mjs reds when the table on disk and the manifest disagree; the public-surface guard still cross-checks the table by name).

| Suite | What it guards | |---|---| | scripts/run-client-core-pure-test.mjs | Consumer-view behaviour of every moved-in module; segment floors (B1–B8 + C) that only move up The non-throwing attachments verdict names a retired key, and gives its reason and way out, from the retirement rule itself; a negative control plants a second retired key in a copy of the build and checks that it is reported by its own name. The configuration refusal registry includes the inline-image refusal code, whose one sentence names no codes or identifiers, recognises only that code, and is checked against both the installed engine (minted in exactly one place) and the receiving server's older engine (not minted at all). The background-agent task-output reader hands the state and error lines that directly follow the status line to optional slots, positionally: lines written inside the agent's own result text never become slots; checked on twelve forms minted by the installed engine's own functions and by the receiving server's older engine. It also hands the result label's parenthetical note and the header bytes not mapped to a named slot to optional slots, with a rebuild invariant (status line, state, error, the unmapped header bytes, the result label and the output rebuild the unwrapped text, up to four named normalizations) checked on those same forms and on a seeded random pool of forged headers. A Bash command that is moved to the background mid-flight (by the user, or by the engine on a timeout or a queued message) is registered for the background panel by the same event and the same record as a model-requested background command: the reader recognises only the structured detached result (never the receipt prose, never the request's background flag), at most one registration is minted per card (a card both readers recognise yields one, taken from the structured result), the package keeps no record of what it has already published (the same task id on a second card is registered again, because only the consumer knows whether it still has the row), the receipt reader and the detached reader both stand down when the structured result says the engine failed to register, and the stream is compared byte for byte against the previous published build on inputs without a detached result (including two receipts for one task id), differing only by that one event on inputs that have one. | | scripts/run-client-core-portability-test.mjs | Kernel / A-layer / index import closures, the runtime-dependency equality gate, barrel reachability, and a real esbuild --platform=browser bundle | | scripts/run-client-core-diff-test.mjs | Differential equivalence against the CLI reference bridge + replay-id invariant + ledger round-trip | | scripts/run-seat-contract-keys-test.mjs | The seat IPC contract: verb list ↔ SPEC ↔ types, element-wise. Since 0.83.4 the seat approval request also carries the four card bits that say whether an approval must be asked and where it came from (mandated, mandate, ruleOffersAbsence — the parked-row form of mandated, so the seat and the card give the same approvalIsMandated answer — and origin, which the resident-posture predicate reads), and they reach the seat through toolPermissionRequestAskBits, which reads them exactly as the card request does — a strict own true, one of the closed mandate words byte for byte, a non-empty own string — so for a host that builds the seat request through that crossing an off-list word, an empty string or an inherited key never lands on the seat; the seat validator does not become a second judge that could drop a safety ask over one bit, which leaves a host that builds the request by hand to apply the same narrow read itself | | scripts/run-approval-frame-keys-test.mjs | The tool-approval frame key mirror, element-wise against the SDK's runtime anchor (one carve-out: AHEAD_OF_ANCHOR entries — keys the server already emits but the SDK anchor has not caught up to — may lead by one generation; the gate turns red the day the SDK catches up, forcing the entry's removal — the register is occupied again — this time by the bit that says a saved allow rule cannot retire a given approval card, carrying both the release that minted it and the byte coordinates that prove it, so the lead is a dated record rather than an exemption; its predecessor left the register the other way, by being retired upstream rather than by the anchor catching up). Beside the key mirror it now guards three further faces of that bit: the closed word table the durable leg reads it through must be the very array object the SDK exports, not a same-looking copy — reference identity, because an equal-contents check still permits a second table that diverges the day upstream adds a member; the one predicate a client is meant to call answers over both legs — the live card's bit and the parked row's absence word, which is all the row carries, since the row has no such bit at all — and answers false for a malformed value exactly as its presence-only siblings do, a strictness the upstream mint shares; and the one sentence minted for it must never point the reader at writing a rule, since a rule written in answer to a mandated question can never take effect where it was written. The same bit's key also has to reach the card request itself, which the SDK's card anchor does not list — a fact that arrives at the package boundary and stops there is the shape of defect this file's guards exist to catch. Since 0.83.2 the frame also mirrors mandate (the word a mandated question stands on) ahead of the SDK anchor, with its own exit condition; the released server carries it from 7.102.0, so the allowance for the server fixture was removed by its own exit condition. The installed engine must declare the member with the six-word closed type, and the key must reach the card request. Since 0.91.0 the frame also mirrors probeReason ahead of the development SDK anchor, with the same exit condition, and the probe-tree comparison reads the newest configured SDK first. | | scripts/run-segment-authority-single-source-test.mjs | The authoritative-segment replacement verdict, single-sourced. text_end.content and the text_delta stream stopped being byte-identical the day the engine started redacting the former through the same filter as the result, so every consumer now has to decide six ways what to do with the segment it has half-emitted — and until this release that decision existed twice: once here for the transcript lane, once in the shell for the print lane, hot-fixed a version apart. The verdict is now one pure function both lanes call, and the guard pins it on the quantity that actually decides the outcome: whether the authoritative text still starts with the bytes that already left, not whether a flush has happened — the latter is a precondition, and anchoring on it withholds a perfectly ordinary answer. Each of the six forms is checked with its counter-case, the prefix length is pinned to UTF-16 code units against a non-ASCII sample whose UTF-8 byte count differs (slicing by bytes leaves the very thing being redacted on screen), and the withheld-segment ledger is compared by normalised equality rather than substring, because a short redaction marker quoted in an unrelated later answer would otherwise suppress that answer entirely. The same file pins the session-level memory-capture declaration to one mint point — the wire value is a single-member closed set, and a consumer that spells it wrong gets a loud refusal rather than a silently dropped privacy request — and pins the SDK URL/health transit to be the same function reference, since wrapping it would discard the one guarantee the transit exists for. A last section strips comments with the TypeScript parser and asserts the second expression has not grown back | | scripts/run-print-bash-iserror-test.mjs | The print lane's Bash is_error authority (structured over regex). A second section pins where the denial classification word lands on this lane: on the message envelope, never inside the tool-result block, because that block is forwarded verbatim to the provider on compaction and a self-minted key there is the shape of an old, real defect. A word outside the upstream table — or an empty string, a non-string, or nothing at all — mints no key rather than a guess, and the word never moves the error flag, because attribution does not decide anything | | scripts/run-bash-benign-exit-interpretation-test.mjs | Benign non-zero Bash exits (returnCodeInterpretation) stay non-errors across all three derivation arms, and the annotation transits to the card | | scripts/run-sdk-floor-test.mjs | The SDK version floor — and, more to the point, that the installed type declarations still carry the keys this package reads — including, from 0.80.0, the three declarations that justify the floor itself: the key naming who settled a refusal on both decision legs, and the thirteenth word in the settlement vocabulary. They are found through the syntax tree rather than by searching text, because this guard's own comment stripper blanks string contents and would have made that check permanently, silently green. From 0.84.0 the floor is 12.0.1 and the guard witnesses the declarations the package now reads: the per-session background listing method, the background.listFace capability bit, and the closed background-status vocabulary that the registry classification switches over exhaustively. Every installed SDK the guard can see at or above the floor must carry those declarations too, so lowering the floor to a line where they do not exist fails. From 0.94.0 the floor is 18.1.0: the shipped declarations name the approval-attribution type and the six-word request permission-mode type that SDK 17 introduced, and the cancellation-cause, status-derived-code and routing-failure values are generated from the same SDK at build time; the guard reads those values on the floor-line SDK it can see. | | scripts/run-engine-caps-ledger-test.mjs | A per-key disposition ledger for GET /v1/capabilities. The SDK's Capabilities grew from 74 keys to 93 in one release and nothing on the board could see it: this package consumes that table through four synchronous readers, and nineteen new positions arriving while the package does not move is exactly the disease shape this repo keeps logging on other axes — the fact is already on the wire, the package boundary is the cell that swallows it, and no client can read it however they write their side. So the ledger is reconciled element-wise against the SDK interface in both directions: a key the SDK added with no ledger row is red (someone must classify it), and a row for a key the SDK removed is red too (a registration that no longer does anything). Each row then has to survive its own claim — a read row names the source file, and the code there (comments stripped) must really mention the key, because prose asserting an alignment is the classic way these guards go hollow; a not_read row must have zero read sites in the tree, so wiring one up while the ledger still says the package ignores it is red rather than invisible. The census behind those two directions recognises five call shapes, each of which really occurs here — a reader whose base argument carries its own parentheses, a direct caps.<key>, a narrowing cast, an own-property read helper, and a *_CAP constant — and proves it on fabricated samples first, since a census that recognises one shape reports "nothing here" for the other four. What the guard deliberately does not judge is whether a position ought to be read: that is a design call, and the ledger only pins that every capability was looked at once by a person and that what they wrote down does not contradict the code | | scripts/run-sql-engine-capability-test.mjs | The SQL-posture read face and the four-state capability reader underneath it. One capability cell here carries four different things, and each one points an operator somewhere else: nothing has been observed yet in this process (a one-shot doctor run is always in that state), the response arrived but cannot say (no store posture, or one that cannot be read), the engine reports a store posture without an SQL seat — this deployment has no SQL backend, which is a positive fact rather than an absence — and a full reading. Fold any two together and the screen states something flatly, confidently, and wrongly, so every positive control here is paired with a control pointing the opposite way, and the four sentences the doctor row can print are checked to be pairwise distinct and non-implying. The reading itself is narrowed no tighter than the mint: txnMode: null is a legal value — two of the three engines always report it that way, and the upstream type note names reading it as "optimistic" as the error — so treating it as malformed would throw away the entire reading for ordinary deployments, which is the same disease this repo logged when a consumer's domain was narrower than the producer's. A response that cannot be parsed clears the cell rather than leaving the previous engine's answer in place, and a separate invalidation port exists for the case the generation latch cannot catch — a same-port respawn whose new probe never succeeded, where the stale reading would otherwise be answered as current fact. Untrusted values (the isolation string is read back from a database server variable) are sanitised and bounded before display, and the bound is applied before escaping so a visible escape never gets cut in half. Finally the export names are themselves a guard: the shell still carries a copy that is meant to go red on the package's same-named export and be swapped out, so renaming anything here would silently disarm that lock. Current engines (≥7.106.0) moved the SQL posture under the always-present store posture (store.sql) and dropped the top-level key, and from 0.92.0 the reader reads store.sql alone (absent there means the engine reports no SQL posture): the top-level position of servers below the support floor is no longer read, a response without a readable store posture is "not reported" — the same answer the other readers sharing that witness give — and an unreadable SQL seat clears the cell; the engine's own projection is replayed to prove every form it mints reads back | | scripts/run-web-search-backend-capability-test.mjs | The deployment-default WebSearch backend read face (capabilities.webSearch.backend, engine ≥7.82.1). Same four-state discipline as the SQL and write-protection cells, with two things that are specific here and therefore guarded: a missing key (an older engine) and an explicit "none" (the engine says this deployment has no default search backend) point an operator in opposite directions — "cannot tell" versus "not configured" — and must never be folded; and the none sentence has to say both halves of the contract at once: the default scenario mounts no WebSearch tool, and a caller-supplied webSearch setting can still mount it on a single-user lane, because the capability advertises the deployment default, not whether this request has search. The backend word is read as an open set — the engine's closed set is typed from its own provider tuple and grows with it, so hand-copying three words here would turn a newly configured backend into "unreadable" (the narrower-than-the-mint disease this repo already logged once). webSearch: null is malformed rather than none (the mint never emits null), extra members never cross, an unparseable response clears the cell, a stale probe generation is dropped, the invalidation port clears to "not observed", and the open-set word is sanitised and bounded before display | | scripts/run-terminal-cause-projection-test.mjs | The 7.64.0 wire reshape, projected. A run's ending stopped being eight parallel flat keys and became one tagged cause (completed \| failed \| blocked \| paused), and a tool call's gate stopped being four orthogonal words and became one record (disposition / settlement? / origin?). Both are read in exactly one place in this package, and this guard pins them at two levels, because the dangerous seam is "the reader was updated, the consumer was not": each terminal arm is checked on the reader and on the subtype / is_error / errors[] the projector actually emits. Two properties carry most of the weight. First, a terminal word this reader does not know is never laundered into an empty success — it lands on an unknown arm carrying the word verbatim, while a payload with no terminal word at all (the mock lane) keeps the success arm exactly as before, which is the one and only case the reader answers null. Second, the three window words (approval_window_expired, denial_limit_window_expired, park_sla_expired) must each be told apart by a different predicate: the previous generation collapsed all three onto one timeout, and re-merging them would throw away the discrimination this reshape just restored. Two byte generations are read by one reader, keyed on the discriminator upstream nailed ("terminal" in result): the current cause form, and the flat form that a current engine still emits on two lanes — replayed persisted bytes, which the service passes through verbatim rather than back-filling, and the service's own rejection envelope. A cause-form payload that also carries stale flat keys must ignore them entirely: keeping one compatibility read is what gives a single fact two sources. The same file also pins the MCP delivery verdict and HTTP status riding the wiring manifest, the four-state write-protection reading (where three of the four states mean cannot tell, and none of them may be printed as "there is no table"), and the park-reopen fetch identity: that predicate is asserted through the real entry point, since the defect being fixed was precisely a call site wired to a different predicate than the one that routed the row there. From 0.80.0 one of those three boundaries flips: the key naming who settled a refusal stopped being a dead byte and became part of the wire, so the check stopped scanning the build output for the word and started reading the request bodies the two decision legs actually send. A refusal attributed to the deployment's own policy carries the word; one attributed to a person, one with no attribution at all, and one carrying a word the vocabulary does not hold carry nothing — the wire has no slot for “a person decided this” other than the key's absence, so inventing one would be minting a word upstream does not have. The allow family never carries it on any of its routes, because that combination is refused before the approval is judged while the side effects of allowing have already landed, and the three refusals nobody was asked about (a card that failed, a user who walked away, an interruption) carry nothing either. A deployment that signs the bodies it accepts does not sign that word, and there is no capability bit to ask beforehand, so a refusal on exactly that ground is answered by re-sending the same decision once with that one key removed — byte-for-byte the same otherwise — rather than letting an optional note take the whole denial down with it. The guard measures that along three axes: the decision still lands and is reported as decided with the attribution handed back and a separate flag saying it never reached the wire; a caller who aborted in between gets no second request; every other refusal code, and every decision that never carried the key, send exactly once. The classification of a second failure is made from what the second body actually carried, not from what the card asked for. From engine 7.104 a synchronous submit that stops at a gate returns the engine result itself plus a three-key receipt: it now carries the tagged cause, so it reads as paused on the cause generation (older engines still send the flat three-key body, which keeps reading as before), and the top-level park word is looked at first, the same order the SDK documents for all three generations, so a body the server says is parked is never read as finished. A parked run row now carries its result too, and the headless reconnect path turns it into the parked terminal frame on the first attempt instead of spending its whole retry budget — both generations are pinned, including an end-to-end drive through the public reconnect entry point. When the caller passes the failure's error carrier and it is a session-history tool_use mismatch, the edit-and-resend reading yields to the recovery sentence that line already carries; the carrier is handed to the same mismatch classifier, so the reading and the classifier never disagree on a carrier, whatever its shape; without a carrier the reading is compared byte for byte with the previous release's package. The machine-readable cause of a blocked ending is pinned as well: when a deployment hook keeps a prompt from being submitted, the blocked reading carries an optional detail with three arms — a known kind with a known cause word, a known kind with a cause word this version does not know, and a kind this version does not know — and a word outside the tables comes back verbatim and is never folded into a known one. An absent or malformed detail is left out entirely rather than guessed at, only own properties are read (planting the same names on the global prototype changes nothing), a flat-plane row and the other endings have no such position, and a reading without a detail is compared byte for byte with the previous release's package. Real engine and server packages from both generations are driven end to end — the engine's four hook endings, the server's redaction of the hook's own words, and the reader — and a per-generation fact pin says which engine and server versions mint the detail. | | scripts/run-auto-mode-unavailable-test.mjs | The fact behind "you are being asked because the auto-mode classifier could not run", and the one place its sentence is minted. The cause table is a copy, reconciled word for word in both directions against the installed engine's own bytes — it narrowed upstream, and the guard follows rather than keeping the old shape: a table checked against something nobody ships any more is the oldest way for a guard to be green and wrong. The retirement is held from both sides — the removed table must really be gone upstream, and the removed reader and word must really be gone here — while the word that left keeps arriving cleanly from an older engine, because the reader takes the cause as an open set: the vocabulary belongs upstream, so a copied list here would discard a legal value the day one is added, and the value discarded is precisely "this outage is a NEW kind". The reader's one exclusion is the word the engine says it never stamps here — the classifier did run and did answer, just outside its contract, so reading it as a failure would invent an event the engine denies. That exclusion used to be derived from a second table which no longer exists; the reason for it never lived in that table, so it is now stated where it actually comes from, pinned as a named set (a magic literal scattered through the reader reds) and cross-checked against the engine's own verdict declaration and against the reader having exactly one such comparison. One reader serves both the live ask and its durable parked twin, since the two carry the same key path and a second copy is how two ledgers drift apart. Absence is pinned as absence — most asks never consulted a classifier at all — and the sentences are checked mutually distinct, prototype-safe, and walked end to end: an unknown word reaches the sentence a person reads (the fallback that names it verbatim) and the status reading (unavailable for this round, never a fallback to "available"), with counter-controls proving neither assertion is vacuous | | scripts/run-engine-notice-catalog-test.mjs | The engine-notice catalog and its audience table. Whether a notice deserves a person's attention is not decided by whether this end happens to have a phrasing for it — that drifts with each client's build order — but by whether the engine minted the code into its own written catalog; the audience row answers the separate question of who the fact is for, since an operations fact pushed at an end user is noise and a user-facing fact buried in an operator log is something withheld from the person who could act on it. Both tables are reconciled against the installed engine's own artefacts in both directions and pinned in lockstep with each other, unknown codes fall back to the conservative operator side, and catalog membership is tested on the raw value so a code carrying control characters cannot impersonate a registered one after sanitizing. The reader for a dropped MCP injection keys on its own code alone and treats a missing session, server or reason as absence rather than throwing at a read site. A reverse pin enforces the upstream's single-mint contract: the engine composes those sentences from the host's facts, so a copy of them appearing in this package's source or build is a second source that would drift, and fails. From 0.84.0 it also covers the reader for the two read-directory grant notices: it recognises only those two codes, needs the tool call id to match a card, passes the rejection reason through as written, and treats only the granted notice as evidence that a directory was added; a granted notice without both the directory and the spelling the engine now holds, or with a scope other than exact, is not read at all, and the scope word is pinned to the engine's type at compile time. The server also mints a few notices of its own through the same channel; those codes live in a second table with their own audiences, kept apart from the engine mirror (which must stay equal to the engine's catalog) and reconciled against the server's published package when one is supplied, so a user-facing server notice is no longer filed under operations. One dispatcher returns the typed facts for every code that has a reader, discriminated by code and tagged with its audience — only the user-audience codes belong on a user surface — and the guard ties the dispatch table to the module's own exported readers in both directions, so a reader cannot be exported without a row and a row cannot be dropped without the guard failing. From 0.85.0 it also covers a third server-minted notice, the one saying that part of a session's saved history could not be read when the session was reopened: it is a user-audience notice, its two counts are read one by one and anything that is not a non-negative integer reads as unknown rather than zero (neither "nothing was skipped" nor "nothing is left" may be invented), and one extra sentence — the context is empty but the turn runs — is given only when the count of entries left is exactly zero. When the server package is supplied, the guard drives the server's own emitter for that notice and reads what it emits back through the dispatcher. From 0.86.0 the catalog grows by six engine notices — a personal rule store that could not be read and was skipped, an auto-mode classification that could not decide and where the call went, a requested permission mode that did not take effect, a permission mode answering a question on the person's behalf, a mode release past a read-deny table, and a legacy checkpoint's mode keys being migrated — each with its own typed reader: branching words (the classifier's cause and destination, the reason a mode did not take effect) are read as closed sets, descriptive words pass through as written, and a malformed notice reads as absent; two of them are checked against notices produced by the engine's own emitters. The unresolvable-ask notice's optional remedy sentence is carried verbatim when it is a non-empty string — never trimmed, rewritten or filtered by cause — and its absence (older engines, other causes, an empty or non-string value) leaves the rest of the view unchanged; a notice without it reads byte-for-byte as before, and the engine's own mint function drives three cases through the dispatcher. A fourth server-minted code, the user-facing notice that a model endpoint could not be connected to, is registered with its audience and has its own reader: the failure code, the endpoint, the remedy sentence and the session are required, the name of the proxy variable is optional, the code and remedy pass through as written, and the server's sentences are never copied into the package. With server fixtures, the server's own decorator mints the notice for a direct and a proxied connection and for every connect-failure code it knows, and stays silent when a response arrived or the call did not end in error; the audience table is reconciled against both fixture generations, a row newer than the older fixture being required to be absent there. From 0.88.0 the catalog grows by one operator notice — a provider request whose tool turn broke the pairing rules was repaired on its way out — with a typed reader: each repair's form word passes through as written (the engine does not publish that word list from its entry point and has announced more words), the call ids are read as a non-empty list, any malformed entry makes the whole notice read as absent, and notices produced by the engine's own repair law and emitter read back field for field. The model-unreachable reader reads only the notice detail's own properties, like the other server-minted readers. From 0.89.0 the engine's own catalog registers those server-minted codes as well — each code carries an audience and a record of who mints it — so the package's separate table for them is retired and both the membership check and the audience lookup answer from the engine's tables alone, the four codes keeping the audiences they had; the guard reconciles the engine's record of server-minted codes against the published server's stream allow-list on the newer server generation and against the older server's own table on the older one, and the generator refuses an engine whose minter table is not in lockstep with its catalog. Two user-facing notices gain typed readers: an incomplete tool turn (cause, count, session and run are required, the call ids are optional and copied, and a cause word the package does not know passes through as written) and user input a run may not have used — one reader for the steer and follow-up codes, the kind decided by the code, the count required, the cause passed through as written and never filled in when absent — plus one user-facing sentence that names only the count, does not claim the input went unused when the server could not tell, and falls back to a cautious sentence for any cause it does not recognise. Notices produced by the engine's and the server's own emitters read back field for field, and every reader answers the same for frozen and unfrozen input. Every reader and the by-code entry point read a notice's code and detail as own properties only: a notice whose code or detail sits on its prototype, or on a polluted global prototype, reads as nothing through all three doors alike. From 0.90.0 the catalog is eighty-five notices: the engine retired the notice for the one-off migration of old parked rows, so the catalog, audience and minter tables lose a row, its fact reader and named type are gone from the public surface, and the code is treated like any code outside the catalog (the dispatcher answers nothing, the catalog check answers false, the audience stays the conservative default). The unused-input reader also reads the child-session field carried by the copy a parent session receives when a sub-agent ends with inputs unused — taken from the engine's real minting site — and the user-facing sentence says so for the two definite shapes, with no identifier in it; every malformed form of the field drops only that field. The register now holds the allow-list notice under its 0.92.0 engine name with the user audience; the earlier engine name is not registered and falls to the operator default, which is the audience it had on the engines that minted it. Since 0.94.0 the user-input-not-used reader takes its lane and count key from the generated lane table rather than from two hand-written codes, and the catalogue expectations follow the installed engine generation. From 0.94.0 the allow-list notice has a fact reader: each entry that reads as nothing comes back with its list source, its machine value exactly as the server sent it (control characters included), a display form produced by the package's single untrusted-text exit, its reason and the optional skill, list, verdict, current-name and value-type positions; a malformed entry list makes the whole reading absent. The model-unreachable reader carries the leg's run id as an optional taskId when the server sends one, and drops only that key when it is unusable. | | scripts/run-tool-roster-projection-test.mjs | The leg's tool roster — what the engine says it actually mounted and what face each tool wears — replacing three word lists that were only ever an estimate taken from one traffic capture against one pinned engine. The reader copies the engine's own all-or-nothing discipline: a roster whose row cannot be read, or whose declared count disagrees with the rows, is dropped whole rather than handed over short, because a consumer reading a short roster concludes the missing tools are not mounted — the upstream says in as many words that this is worse than sending nothing. A malformed face on a row (path target, render hints) drops only that face, since a face is not an identity. Shims are built strictly from roster rows and never guessed from a tool's name, and an axis that cannot be read stays absent rather than defaulting to false or never, which would render "unknown" as "safe". For run-time changes the guard pins the one hard rule in the contract: a digest that does not match is not a rejection — the carried roster is the new state regardless and only the summary becomes unusable, because refusing the swap would leave the consumer holding a stale roster forever. One reading here answers a question that the terminal state structurally cannot: whether this run was assembled with any file-and-shell tools at all. The engine's terminal vocabulary says a run finished, not whether the work got done, so an orchestrator that waits for the end and then guesses has nothing to guess from — while the assembly manifest already said it at the start, one row per mounted instance with the single condition that mounted it. The reading is three-state and both folds are refused: a roster that is readable and carries no such row is the engine stating a fact, while no roster at all is not that fact — the static half of a manifest never carries one, and an older engine reports rosters without naming the mount condition at all, where an empty count would be a statement about the reader rather than about the run. Those two are kept apart in the reason the reading carries, and the wording for every unknown case is checked never to claim the run had no tools. The same roster now decides the tool list on the first line of a non-interactive run: the host holds that line until the roster arrives and lists exactly what the engine mounted at the start of the run, in mount order. The guard runs a real assembly frame through the projection into the decision, and pins that the host falls back to the estimate only once the roster is known not to be coming — a manifest without one, an unreadable one, model output or the run's end arriving first — rather than on a timer alone (model activity counts, including a model call that is still waiting or retrying; an error line the stream synthesizes when a run fails before assembly counts as the run ending), that a sub-run's manifest is never mistaken for the run's own, that an empty roster is taken as the engine's answer rather than as silence, and that the wait bound covers both sequential default budgets the engine gives an external tool server to connect and list its tools. The holding logic itself lives in the package as a small per-run gate — buffer, decide once, release the held messages in arrival order, then pass through — and the guard drives real stream output through it to pin that the release happens exactly once, at the manifest, releasing exactly the held prefix. The ordering itself also lives in the package as a stream wrapper, and the guard checks the final output a consumer reads: the first line is always the tool-list line, a message that arrives while that line is still being built comes after it, a timer firing races nothing out of order, a source that ends or fails before the decision still gets its first line and held messages out before the error, and an early exit closes the source. From 0.84.0 the roster-derived sentence source no longer throws on a value it does not recognise, including a reading of the manifest's hands section passed by mistake: it answers the same "not stated" sentence as the hands reader, from one shared source, and its six known sentences do not change. From 0.86.0 that first line also says where its tool list came from, in two added keys the wrapper writes onto the host's own line object in place: a source of engine-roster, estimate or host-static, and, only for an estimate, which of the four reasons made the roster known not to be coming. The guard checks every decision path end to end, that the host's object keeps its identity and every other key byte for byte, that stale values of the same two keys left by the host are replaced (a stale reason is removed rather than left behind), and that a host line that cannot be written is passed through untouched instead of failing the first line. | | scripts/run-permission-rule-issue-codes-test.mjs | The rule-lint refusal codes an engine reports when it will not compile a permission rule. The SDK publishes neither a schema nor a type for them, so the package mints the table from the engine's own bytes and the guard pays the cost of that copy instead of leaving it to somebody remembering: it parses the codes the engine actually mints and reconciles them against the table in both directions, so a code added upstream (the user would see a bare code) and a code only the package believes in (a branch that can never fire) both fail. It also reconciles the table plus a small retired ledger against the engine's declared union, which is deliberately not the same set — one member was renamed and its old name is still declared — so reviving a code the engine will never mint again is impossible and a future stale member shows up immediately. Sentences are pinned one per code, mutually distinct, and split by family: a rule that is wrong and a rule that is legal but unsupported on this lane are different next steps and may not share a sentence. The engine's own message rides along as prose — sanitized and capped after escaping, never matched on The set of codes the engine actually mints now also counts declared codes minted outside the rule compiler (the allow layer reports an entry that is not a string with one). Codes an engine release retired but older engines still mint are found by comparing the receiving server's bundled engine with the installed one rather than by a hand-kept list; each keeps the exact sentence it had before, and the codes that replaced them each get their own sentence with no machine identifiers in it. Since 0.94.0 the base-path refusal sentence is checked to hold on both engine generations (base missing, or declared but not a usable absolute path), with the premise run on the installed engine. | | scripts/run-gate-vocabulary-test.mjs | The two gate vocabularies — who denied a call (DeniedBy, the installed engine's ten words) and who asked about it (AskOrigin, eleven) — together with the one place their sentences are minted, so the same denial does not read three different ways across three clients. The tables are copies, not opinions: the asker table is reconciled in both directions against the lists the installed SDK and engine packages publish, and the denier table is exactly the installed engine's own list (from 0.92.0 the one word only engines below the support floor still sent is no longer in it), checked so that every word the SDK declares is present except that retired word, and every word ahead of the SDK comes from the engine — a word added upstream (nobody renders it, the user sees a bare code) and a word only the package believes i