Skip to content

Commit bde5ef5

Browse files
committed
docs(command-registry): state what the descriptor root actually guarantees
`commandDescriptors` claimed to be "proven byte-equal to the live hand tables by `__tests__/parity.test.ts`". Neither operand survives the ADR 0008 migration. The test is now `src/__tests__/command-descriptor-parity.test.ts` (moved by 2ec4e91 #2348); the tables it compared were deleted by the PRs that inverted them (47abc8c #907 daemon routes, 96bc7b1 #908 capability matrix, retired by ea1d6b8 #2089, 607883d #909 batch allowlist, 8ef4e73 #1137 MCP exposure); and TIMEOUT_POLICY_BY_COMMAND, DEVICE_CLAIM_POLICY_BY_COMMAND and COMMAND_DESCRIPTOR_BY_NAME are folds over this array, so byte-equality has no second operand. Record the invariant that does hold and name the check that carries each part, so a future split of this file preserves them. Established by mutation, not by reading: duplicating a descriptor fails owner-files.test.ts, command-descriptor-parity.test.ts and device-claim-policy.test.ts, while renaming one lands on the literal pins (targetIdentityVerification, the timeout and device-claim deviating sets). No new completeness test was owed here, and the compile-time totality guard already covers required traits. Same repair for the `frameworkTier` and `targetIdentityVerification` "the parity test" pointers, the two `@internal Introspection helper used by parity tests` docblocks (whose real consumers are command-descriptor-parity.test.ts and command-surface-metadata.test.ts), the "additive single source" banner that still listed the retired capability facet, and the CommandDescriptor intro, which described the deleted tables as "today" and cited `app-switcher` as unrouted after ADR 0014 gave it a daemon facet.
1 parent b38b1f9 commit bde5ef5

2 files changed

Lines changed: 75 additions & 27 deletions

File tree

‎packages/command-registry/src/registry.ts‎

Lines changed: 57 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -438,10 +438,10 @@ function postActionObservation(command: string): PostActionObservationSupport {
438438
}
439439

440440
// ---------------------------------------------------------------------------
441-
// The additive single source. Each entry carries the command identity facets
442-
// plus whichever daemon, capability, batch, MCP, timeout, observation, and
441+
// The command declaration root (ADR 0008). Each entry carries the command identity
442+
// facets plus whichever daemon, batch, MCP, timeout, observation, and
443443
// platform-dispatch traits that command owns. Public catalog identity and the
444-
// non-public dispatch aliases now live here too; leaf views derive from this
444+
// non-public dispatch aliases live here too; every view derives from this
445445
// array rather than recreating command-name sets.
446446
// ---------------------------------------------------------------------------
447447

@@ -1781,12 +1781,40 @@ const CLI_COMMAND_NAMES = new Set<string>(
17811781
);
17821782

17831783
/**
1784-
* The additive single source of truth (ADR-0008, Phase 1 step 1). Proven
1785-
* byte-equal to the live hand tables by `__tests__/parity.test.ts`.
1784+
* {@link RAW_COMMAND_DESCRIPTORS} normalized: `mcpExposed` and `platformExecution`
1785+
* resolved, `ownerFiles` kept out of the runtime shape. The raw array is the root every
1786+
* projection folds over, directly or through this normalized copy — `CLI_COMMAND_NAMES`
1787+
* and the MCP exposure list here, `COMMAND_OWNER_FILES` in `owner-files.ts`, the catalog
1788+
* records, the daemon registry and batch allowlist in their own modules, and the
1789+
* {@link COMMAND_DESCRIPTOR_BY_NAME}, {@link TIMEOUT_POLICY_BY_COMMAND},
1790+
* {@link DEVICE_CLAIM_POLICY_BY_COMMAND} and {@link RESPONSE_DATA_TRANSFORM_BY_COMMAND}
1791+
* maps below. Those four are keyed by `name`, so a duplicated entry collapses into one
1792+
* rather than conflicting.
17861793
*
1787-
* The `as const` on {@link RAW_COMMAND_DESCRIPTORS} flows through this `.map`,
1788-
* so each entry keeps its literal `name`. That is what makes the {@link Command}
1789-
* union below a precise set of command-name literals rather than `string`.
1794+
* None of those has an independent table left to be byte-equal against: each consumer
1795+
* list became a projection of this array and its hand-authored source went away with it —
1796+
* `47abc8c416` (#907) daemon routes, `96bc7b190c` (#908) capability matrix (retired
1797+
* outright by `ea1d6b8c55` #2089), `607883d66c` (#909) batch allowlist, `8ef4e73408`
1798+
* (#1137) MCP exposure. The test that proved those equivalences is now an invariants-only
1799+
* guard at `src/__tests__/command-descriptor-parity.test.ts` (`2ec4e91b11` #2348).
1800+
*
1801+
* Two kinds of check replace that gate. Required traits are typed: `as const satisfies
1802+
* readonly RawCommandDescriptor[]` on the raw array, `satisfies readonly
1803+
* CommandDescriptor[]` here, and {@link CommandOwnerFileClaimsAreComplete} for owner
1804+
* claims. Classifications are pinned as literal command-name lists, so reclassifying or
1805+
* renaming a command they name means editing that list in the same diff: the device-claim
1806+
* deviating set in
1807+
* `packages/command-registry/src/__tests__/device-claim-policy.test.ts`; the timeout
1808+
* envelopes and budgets in `src/__tests__/command-descriptor-timeout-policy.test.ts` (a
1809+
* declared trait since `b25ef7b024` #1084); and the `targetIdentityVerification` and
1810+
* `'core'` tier sets, plus the tier-iff-public rule, in
1811+
* `src/__tests__/command-descriptor-parity.test.ts`. A duplicated entry is caught by the
1812+
* list comparisons in `packages/command-registry/src/__tests__/owner-files.test.ts` and
1813+
* that parity test.
1814+
*
1815+
* The `as const` on {@link RAW_COMMAND_DESCRIPTORS} flows through this `.map`, so each
1816+
* entry keeps its literal `name`. That is what makes the {@link Command} union below a
1817+
* precise set of command-name literals rather than `string`.
17901818
*/
17911819
export const commandDescriptors = RAW_COMMAND_DESCRIPTORS.map((descriptor) => {
17921820
const platformExecution = readDeclaredPlatformExecution(descriptor);
@@ -1811,7 +1839,14 @@ export const commandDescriptors = RAW_COMMAND_DESCRIPTORS.map((descriptor) => {
18111839
export type Command = (typeof commandDescriptors)[number]['name'];
18121840

18131841
/**
1814-
* @internal Introspection helper used by parity tests.
1842+
* @internal Command names for one catalog group, sorted.
1843+
*
1844+
* Consumed only by `src/__tests__/command-descriptor-parity.test.ts`, which compares
1845+
* these names against the `PUBLIC_COMMANDS` / `INTERNAL_COMMANDS` records `catalog.ts`
1846+
* builds from {@link listDescriptorCatalogEntries}: a duplicated descriptor or a
1847+
* colliding `catalog.key` drops a name from one side of that comparison. Production code
1848+
* reads those records; this flat list is the second side the test checks them against,
1849+
* which is why the helper stays exported.
18151850
*/
18161851
export function listDescriptorCatalogCommandNames<Group extends CommandCatalogGroup>(
18171852
group: Group,
@@ -1961,10 +1996,13 @@ export function resolveCommandRecordsSessionAction(command: string | undefined):
19611996

19621997
/**
19631998
* The declared {@link CommandFrameworkTier} for a public command, or
1964-
* `undefined` for a command that never declares one (every non-public
1965-
* command, by construction — see the parity test). Framework adapters
1966-
* (`agent-device/ai-sdk`, `@agent-device/eve`) read this to build their
1967-
* default tool set instead of hand-listing tool names.
1999+
* `undefined` for a command that never declares one. A tier is declared iff
2000+
* `catalog.group === 'public'`; that rule is a runtime pin, not a type rule —
2001+
* `src/__tests__/command-descriptor-parity.test.ts` asserts it and the exact
2002+
* `'core'` tool set, so a new public command cannot join a framework adapter's
2003+
* default set silently. Framework adapters (`agent-device/ai-sdk`,
2004+
* `@agent-device/eve`) read this to build their default tool set instead of
2005+
* hand-listing tool names.
19682006
*/
19692007
export function resolveCommandFrameworkTier(
19702008
command: string | undefined,
@@ -1994,7 +2032,12 @@ export function resolveCommandRecordingEffect(req: DispatchedCommand): Recording
19942032
}
19952033

19962034
/**
1997-
* @internal Introspection helper used by parity tests.
2035+
* @internal The commands that declare a {@link CommandResponseDataTransform}, with it.
2036+
*
2037+
* Consumed only by `src/commands/__tests__/command-surface-metadata.test.ts`, which
2038+
* checks every transform field against that command's declared input schema. The
2039+
* production projection of the same map is
2040+
* {@link listCommandResponseDataTransformFieldNames}.
19982041
*/
19992042
export function listCommandResponseDataTransforms(): Array<{
20002043
command: string;

‎packages/command-registry/src/types.ts‎

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -156,26 +156,31 @@ export type CommandRecordingEffect =
156156
* dispatch (touch/fill/get/is expect their target present). `post-resolution`
157157
* — the command's own resolution verifies (wait's polling loop, whose
158158
* landmark may legitimately be absent at step start); only decision 3 path 1
159-
* runs up front. Declared on every evidence-carrying command and pinned by
160-
* the parity test, so a new one must choose a phase explicitly.
159+
* runs up front. Declared on every evidence-carrying command and pinned as a literal
160+
* set by `src/__tests__/command-descriptor-parity.test.ts`, so a new one must choose a
161+
* phase explicitly.
161162
*/
162163
export type TargetIdentityVerification = 'pre-dispatch' | 'post-resolution';
163164

164165
/**
165-
* The single additive command-descriptor shape (ADR-0008, Phase 1 step 1).
166+
* The single command-descriptor shape (ADR 0008).
166167
*
167-
* Per command this carries, side-by-side, the facts that today live in three
168-
* separate hand-authored tables:
169-
* - `daemon` — the daemon route + request-policy traits
170-
* (from DAEMON_COMMAND_DESCRIPTORS). Absent for commands that
171-
* have no daemon route (e.g. `app-switcher`, `install-from-source`).
172-
* - `batchable` — whether the command is exposed through `batch`
173-
* (from STRUCTURED_BATCH_COMMAND_NAMES).
174-
* - `mcpExposed` — whether the command is surfaced over MCP.
175-
* - `timeoutPolicy` — the request-envelope budget source + on-timeout daemon
176-
* policy. REQUIRED on every entry — most commands share the
168+
* Per command this carries, side-by-side, the facts its consumer projections are built
169+
* from. Four of them replaced a hand-authored list outright:
170+
* - `daemon` — the daemon route + request-policy traits, projected into
171+
* DAEMON_COMMAND_DESCRIPTORS (#907). Absent for commands with no
172+
* daemon route: the local-cli commands, and `install-from-source`,
173+
* which reaches the daemon through the `install_source` internal command.
174+
* - `batchable` — whether the command is exposed through `batch`, projected into
175+
* STRUCTURED_BATCH_COMMAND_NAMES (#909).
176+
* - `mcpExposed` — whether the command is surfaced over MCP, in place of the
177+
* hand-maintained exclusion list (`8ef4e73408` #1137).
178+
* - `timeoutPolicy` — the request-envelope budget source + on-timeout daemon policy,
179+
* in place of the daemon client's timeout lists (`b25ef7b024` #1084).
180+
* REQUIRED on every entry — most commands share the
177181
* explicit `DEFAULT_TIMEOUT_POLICY` constant, but a new
178182
* command must say so rather than inherit silently.
183+
* The remaining facets are plain declarations their consumers derive from:
179184
* - `postActionObservation` — optional interaction observation trait for
180185
* commands that support `--settle`/`--verify`; consumed by
181186
* command surfaces and timeout policy instead of repeated

0 commit comments

Comments
 (0)