Skip to content

feat: build daemon command registry from descriptors, delete the hand table — Phase 1 step 2#907

Merged
thymikee merged 1 commit into
mainfrom
feat/command-descriptor-daemon-flip
Jun 27, 2026
Merged

feat: build daemon command registry from descriptors, delete the hand table — Phase 1 step 2#907
thymikee merged 1 commit into
mainfrom
feat/command-descriptor-daemon-flip

Conversation

@thymikee

Copy link
Copy Markdown
Member

What

Phase 1 step 2 of the command-descriptor migration (ADR-0008), stacked on #906 (feat/command-descriptor-registry).

The daemon command registry now builds from the derived descriptors instead of a hand-authored literal:

  • src/daemon/daemon-command-registry.ts — replaced the DAEMON_COMMAND_DESCRIPTORS array literal (plus its private descriptor/descriptors builders and the isRecordingStartRequest/isShardedTestRequest closures, now living in descriptor/registry.ts) with:
    export const DAEMON_COMMAND_DESCRIPTORS: readonly DaemonCommandDescriptor[] =
      deriveDaemonCommandDescriptors(commandDescriptors);
    The hand literal is deleted. The DaemonCommandDescriptor type, the predicate accessors, and buildDaemonCommandRegistry are unchanged and consume DAEMON_COMMAND_DESCRIPTORS as before.
  • src/commands/descriptor/__tests__/parity.test.ts — the slice-1 DAEMON parity assertion would now be a tautology (derived-vs-derived), so it was replaced with an invariant assertion: no duplicate command names, every descriptor has a route, and the derived command set still covers every public command (minus the two intentionally unrouted app-switcher/install-from-source). The capability-matrix and batch-name parity tests are kept as-is (those hand tables still exist for later slices).

Behaviorless

The daemon registry's routes + request-policy traits are identical#906's parity test proved deriveDaemonCommandDescriptors(commandDescriptors) is byte-equal to the deleted literal. No predicate accessor or consumer changed.

Cycle check

No runtime import cycle: daemon-command-registry.ts (value) → descriptor/derive.ts + descriptor/registry.ts (values); the back-edges from derive.ts and descriptor/types.ts to this module's DaemonCommandDescriptor are type-only imports, erased at runtime. tsc --noEmit is clean (exit 0) and a runtime import() of the module loads cleanly (57 descriptors).

Verification

  • tsc -p tsconfig.json --noEmit: exit 0
  • oxfmt --write + oxlint --deny-warnings on changed files: exit 0
  • vitest run daemon: 94 files, 902 tests passed
  • vitest run commands/descriptor: 1 file, 5 tests passed

Stacked on #906 — merge that first.

@github-actions

github-actions Bot commented Jun 27, 2026

Copy link
Copy Markdown

Size Report

Metric Base Current Diff
JS raw 1.4 MB 1.4 MB +5.8 kB
JS gzip 445.5 kB 445.9 kB +421 B
npm tarball 584.6 kB 585.1 kB +409 B
npm unpacked 2.0 MB 2.0 MB +5.8 kB

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 27.2 ms 26.1 ms -1.1 ms
CLI --help 47.1 ms 46.2 ms -0.9 ms

Top changed chunks:

Chunk Raw diff Gzip diff
dist/src/9722.js +5.8 kB +421 B

@thymikee

Copy link
Copy Markdown
Member Author

Current blocker: Layering Guard is failing, and the failure is real rather than flaky.

The job reports two forbidden daemon-to-commands imports in src/daemon/daemon-command-registry.ts:

  • import { deriveDaemonCommandDescriptors } from '../commands/descriptor/derive.ts';
  • import { commandDescriptors } from '../commands/descriptor/registry.ts';

This is the same architecture issue from #906: if the descriptor registry is meant to become a root consumed by daemon, it cannot live in the current src/commands/* layer without either moving the shared descriptor/projection modules or intentionally updating the guard for ADR 0008. Please address that layering decision first, then re-run checks; iOS smoke is also still pending.

@thymikee

Copy link
Copy Markdown
Member Author

Good catch — addressing the layering decision before going further, taking option 1.

I'm relocating the shared descriptor registry + projections out of src/commands/descriptor into a neutral layer the daemon may import: src/core/command-descriptor/. That matches the Layering Guard's stated intent ("keep shared daemon/client contracts outside src/commands") and preserves ADR 0003:

  • The daemon route/policy data is not moved into the public command surfacecore sits below commands/the catalog, so there's no contamination of public identity (the exact thing ADR 0003 rejected).
  • The daemon still owns the predicate interface (isLeaseAdmissionExempt, getDaemonCommandRoute, … stay in daemon-command-registry.ts).
  • The projections stay field-selective, so public projections can't read daemon-only traits.

Relocating across the stack (#906#907#908), re-running the Layering Guard (git grep clean for src/daemon) + tests, and I'll run the pending iOS smoke on the result. Thanks for verifying the parity test locally.

@thymikee thymikee force-pushed the feat/command-descriptor-registry branch from 97b88ff to 0acefbf Compare June 27, 2026 14:02
@thymikee thymikee force-pushed the feat/command-descriptor-daemon-flip branch from 95ce046 to 271b173 Compare June 27, 2026 14:05
@thymikee

Copy link
Copy Markdown
Member Author

Both blockers addressed.

Layering fix: relocated the descriptor registry to src/core/command-descriptor/ across the stack (#906#907#908). The Layering Guard git grep is now EMPTY on #907 and #908 (git grep -n -E "from '.*commands/" -- src/daemon src/platforms → no matches); old src/commands/descriptor/ removed; bases/stacking intact. tsc + full suites green (907 daemon tests on #907, 1001 on #908). (Rebaser note: a plain rebase resurrects the old dir due to differing patch-ids — used git rebase --onto to replay only the unique top commit.)

iOS smoke (booted iPhone 17 Pro, source build of the #908 tip — the genuinely behavior-relevant checkpoint since the daemon registry + capability matrix now derive at runtime):

  • devices → daemon boots with the derived registries; discovery correct.
  • open com.apple.Preferences → routed through the derived daemon registry + passed the derived capability gate → Opened.
  • snapshot → full path (dispatch + capability + ios-runner build/run) → 109 visible nodes, real AX tree (Apple Account / General / Accessibility / Action Button / …).

No regression — the derived daemon-command-registry + capability-matrix behave identically end-to-end.

Base automatically changed from feat/command-descriptor-registry to main June 27, 2026 16:02
@thymikee thymikee force-pushed the feat/command-descriptor-daemon-flip branch from 271b173 to b0368aa Compare June 27, 2026 16:22
@thymikee thymikee merged commit 47abc8c into main Jun 27, 2026
20 checks passed
@thymikee thymikee deleted the feat/command-descriptor-daemon-flip branch June 27, 2026 17:46
@github-actions

Copy link
Copy Markdown
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-06-27 17:47 UTC

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.

1 participant