Skip to content

refactor(move): extract the daemon server into @agent-device/daemon (11b) - #3546

Draft
thymikee wants to merge 17 commits into
refactor/daemon-runtime-fakesfrom
refactor/daemon-package
Draft

thymikee wants to merge 17 commits into
refactor/daemon-runtime-fakesfrom
refactor/daemon-package

Conversation

@thymikee

@thymikee thymikee commented Oct 12, 2026 •

Copy link
Copy Markdown
Member

What this is

PR 11b of the daemon-extraction plan: an atomic move of src/daemon/ into packages/daemon/src/ as the new workspace package @agent-device/daemon, with every mechanical path rewrite, the rule/config/doc retargets the move requires, and the export façade the #3546 review asked to settle in this PR. Re-run from a hard reset on 11a-4 (refactor/daemon-runtime-fakes @ 980c484 — the re-synced #3544 head, tree-identical to its pre-resync push; the stack sits on current main via #3519 @ df0af2a and #3517 @ e615de3); base retargeted accordingly. Main's daemon-side changes (#3536's /rpc backstop test + server changes, #3547's minted-per-producer ref, #3549's vitest 5 syntax, and the rest) are base content the move absorbs: the codemod moved 669 files, 763 inbound import sites.

Commit topology

Commit Files Hand-written?
refactor(move) — pure codemod: 669 files git mv'd, 763 inbound import sites rewritten (16 named production/script subpaths + one ./testing/* pattern entry decided by the importing file's path), generated manifest + composite tsconfig, root/examples project references, and the exists-at-head path-literal sweep over code/docs/ADRs 961 No — reproducible with one script on a clean tree
refactor(gates) — R11 vi.mock sweep, ratchet + wire-compat rename canonicalization, rule fixture re-anchoring (synthetic coordinates), config retargets (stryker, di-seams, .fallowrc, baselines), new URL re-anchors, seam fixes (dead root vi.mock tunnels dropped — 11a-4 fakes those ports; device-inventory test moved onto the production withAppleToolProvider scope; ownership-scan roots extended over packages/daemon/src); deletes scripts/daemon-edge-residue-scan.mjs — run once before deletion (0 edges), R11 owns that invariant now 49 Yes
refactor(daemon) — the façade: two internal/* suites split into the package, R11 refuses ./testing/* for non-test importers (unit-tested + bite-proved), DAEMON_SOURCE_ROOT adopted by all filter sites, docs/agents trim 24 Yes
docs(adr) — façade policy amendment to ADR 0033 1 Yes
chore(gates) — renamedSince head-ref + renameLimit, wire-compat uses it, fixture-repo rename test 3 Yes
chore(gates) — format re-flow 42 No
test(gates) — fixture-repo git identity + commit.gpgsign in its own config (CI fix) 1 Yes
fix(test) — daemon-stop test mocks the SUT's exact @agent-device/daemon/daemon-stop specifier (mutation-lane resolution) 1 Yes
refactor(daemon) — façade narrowed per the second review: the 82 production modules root tests reached through the wildcard become named subpaths (one spelling each); fixtures keep ./testing/ spelling as 25 named entries + four per-folder ./testing/<dir>/* patterns exposing only __tests__ folders; 150 files re-spelled 151 Yes (codemod-assisted)
fix(gates) — resolveWorkspaceSpecifier owns exports-pattern expansion once for the layering edges, depgraph, mutation ownership, and the Stryker alias table (capture-group aliases, exact-first ordering); R11's test-data refusal keys on the ./testing/ namespace, not a pattern entry; ADR 0033 amendment updated 6 Yes
fix(gates) — pattern alias splices at its known * index (CodeQL String#replace first-occurrence finding) 2 Yes
refactor(daemon) — test-only reach no longer defines production surface: the 72 named entries whose only consumers (import + vi.mock sweep) are tests move to named ./testing/<module> entries, 133 files re-spelled; 16 production subpaths remain 133 Yes (codemod-assisted)
refactor(gates) — namedByExports delegates to resolveWorkspaceSpecifier; the alias table orders its Vite first-match list to Node precedence (exact, then patterns by prefix length desc) with a cross-semantics test over every export key (the ADR/amendment hunks of this commit are already base content after the rebase, so it lands as 1 file) 1 Yes
test(gates) — the named-entry seam proves the specifier resolves (owner note 1): restores the fixture's @agent-device/daemon/session-store spelling and asserts [] in compiled and uncompiled mode, so an unexported spelling can no longer pass vacuously; bite-proved 1 Yes
test(gates) — proves the alias table orders overlapping patterns longest-first (owner note 2): a sandbox manifest declares ./testing/* before ./testing/handlers/* with targets that fill to different trees; deleting patterns.sort fails exactly this case; bite-proved 1 Yes

Acceptance evidence (head a4c6208 on base 980c484 — every item re-run on this lineage)

  • Typecheck: pnpm typecheck → 0 errors.
  • check:layering: green — 319 rule tests pass; guard summary reports R11 holds 28 packages behind 543 exported subpaths with zero undeclared or relative-tunnel routes — EMPTY residue. (The residue scan measured 0 daemon→root edges on the move commit before its supersession by R11.)
  • depgraph: pnpm depgraph + edge scan → 0 edges from packages/daemon/src into root src/.
  • Bite proofs (planted into a production root file, restored after each): value import, dynamic import, vi.mock, and type-only import through @agent-device/daemon/testing/* each fire R11; a relative tunnel into packages/daemon/src fires; a named production subpath does not fire. The ./testing/ refusal is additionally pinned by scripts/layering/package-boundaries-facade.test.ts (4 tests).
  • Test-name parity (vitest json reporter, per-file full titles; base captured from a worktree at 11a-4 @ 980c484): unit-core 13,622 → 13,799, fuzz-worker 11 → 11. Every delta is accounted for: the façade split relocates 13 titles between two file pairs with byte-identical names (touch-response 10, session-open-surface 3); daemon-registration-owner.test.ts swaps 6-for-6 parametrized titles embedding pid/Date.now() stamps; eager-closure-budgets.test.ts grows 854 → 1,031 rows — the suite's test.for entry-surface expansion over the new shape (the daemon package's export subpaths are newly discovered entry surfaces; the session-teardown parameter retargeted to the moved file) — plus the new rename-detection fixture test from this branch's own gates commit. After folding those, every remaining test name occurs exactly the same number of times on both sides — no test lost, gained, or silently relocated.
  • Bundle equivalence: tsdown --sourcemap built on base and head; folding packages/daemon/src/ back onto src/daemon/: daemon-entry closure 124 modules = 124 modules, identical set; whole-build first-party source multiset 1,574 = 1,574 instances, identical.
  • Wire: check:daemon-wire-compat → 191 declarations, 0 changed, 0 removed, 0 added, 0 moved against v0.21.27.
  • pnpm check:package: the publishable package is sound. pnpm check:fallow: clean. check:affected --run: exit 0 — blast-radius analysis over the full diff. gate mutation --modules kernel-errors --shard 1/5: lane passes (94.74%, 72/76 killed, 0 harness errors; envelope pass recorded at pre-parent-swap SHA bf8a98610615, whose tree hash is byte-identical to this head, scope 1,277/1,277 files; scores advisory per gate(mutation): ratchet graduates on stability alone — a 28.75% score can lock in as the permanently gated floor #1457). workspace-aliases.test.ts + config.test.ts: 11/11 pass incl. the cross-semantics alias test over every export key and the two new bite-proved seams. lint + gate-manifest: clean (57 checks / 27 lanes).
  • docs/agents: 39,948 / 40,000 bytes (headroom kept for PR 10; cap untouched).

The façade (settled here, per review)

  • Exports: 16 named production/script subpaths (the importer sweep names exactly these, zero test-only entries) + 97 named ./testing/ entries + 4 per-folder fixture patterns. Every module has exactly one spelling; nothing is reachable two ways.
  • ./testing/ is the whole test-only surface: fixture files (./testing/__tests__/<fixture>), fixture-folder patterns (target templates reconstruct __tests__ folders only), and production modules root tests drive (./testing/<module>). R11 refuses the namespace for any non-test importer — files, package-internal sites, and vi.mock/doMock/importActual — so root production code cannot import daemon internals (./interaction/internal/types et al.) at all.
  • One resolution semantics: resolveWorkspaceSpecifier (scripts/layering/model.ts) implements Node's exports rules (exact first, longest prefix, suffix fills the target *) and owns the layering edges, depgraph, mutation ownership, and R11's named-surface check; the Stryker alias table orders its Vite first-match list to that precedence, and a test asserts the alias hit equals the resolver's target for every export key of every workspace package — a pattern-exported module names the same module id in every lane.

@thymikee

Copy link
Copy Markdown
Member Author

The review of 87969a2 found no blocking code problems, but the evidence is not yet complete, so this is not ready to merge. I could not check the bundle equivalence (124/124 modules), the test-name discovery comparison, or the wire-compat 0-change result, because the scripts and outputs are not in the PR. This review did not build or run any gates, so I could not confirm that the typecheck failures stop at the 39 residue test files.

CI shows no checks on this stacked draft. By your own account, pnpm typecheck (TS6059/TS6307) and check:layering R11 fail at this head. Both failures come from the 64 escape sites in 39 packages/daemon/src test files that the move created, so they overlap with this PR and are not unrelated. The PR also has merge conflicts with main. I reviewed only the five 11b commits 8704ec7..87969a2 against e51dff5, since the base branch was rewritten.

Before merge, land 11a-4, re-run the move on top of it and retarget the base. Then show typecheck and check:layering green with an empty R11 residue, and squash the leftover fixup! commit.

Not blocking, and you can take or leave these: (1) the generated export map in packages/daemon/package.json publishes 68 test-only subpaths, including ./interaction/internal/interaction-touch-response and ./session-lifecycle/internal/session-open-surface, so the rule should be that exports are the production surface plus explicitly named test entries; (2) tagToHeadRenames in scripts/wire-compat/run.ts repeats renamedSince, so giving that helper a head ref would remove the copy; (3) the rename canonicalization in scripts/layering/ratchet-reference.ts and in the wire-compat ledger has no test, and git skips inexact rename detection above diff.renameLimit, so a fixture-repo test with a git mv plus an edit, and an explicit rename limit, would cover it; (4) the path sweep rewrote history in scripts/layering/package-boundaries.test.ts and in docs and ADRs, so #2544 now cites a packages/daemon/src/adapters/maestro path that never existed, and 13 cited packages/daemon/src/*.ts paths do not exist at head, which means historical text should keep its original path and only references to files that exist should be retargeted.

Could you settle the façade now, once 11a-4 lands and the move script can be re-run, instead of recording importer reach? That would mean exporting only the 16 subpaths that production or scripts use, publishing test data as named *-fixtures entries or one ./testing entry that R11 refuses for non-test importers, and moving the two internal/* tests into the package so those modules are not exported. It would also mean moving the daemon-request wire types to @agent-device/daemon-contracts so the client stops depending on the server package, and using one DAEMON_SOURCE_ROOT constant for the gate sites. The first three steps would shrink the export map from 84 to about 20 entries. Would a stage-plan or ADR decision on the façade policy be the right first step?

thymikee added a commit that referenced this pull request Oct 13, 2026
Façade per the #3546 review:
- the two internal/* suites move in-package: session-open-surface splits its
  two buildOpenResult cases into packages/daemon/src (the root-side
  resolveRequestedOpenSurface case stays root where its module lives), and
  interaction-touch-response splits its ten pure builder cases in-package (on
  the sanctioned in-package command-surface fake) while the six --verify
  handler cases stay root-side where the real command surface they exercise
  lives;
- R11 refuses ./testing/* for every non-test importer (root files,
  package-internal files, and vi.mock sites), with a pattern-matching
  named-by-exports check so scripts/ sites resolve through the entry;
- DAEMON_SOURCE_ROOT in tracked-sources.ts becomes the one root every
  layering filter keys on (11 sites, check.ts:588 included);
- docs/agents trims duplication to stay inside the 40 000-byte budget after
  the path lengthening (39 948 measured, headroom kept for PR 10).

Generated with assistance.
@thymikee
thymikee force-pushed the refactor/daemon-package branch from 87969a2 to fdf8b0c Compare October 13, 2026 01:00
thymikee added a commit that referenced this pull request Oct 13, 2026
… repo

Non-blocking #3546 review items: renamedSince gains an optional head ref and an
explicit unlimited diff.renameLimit (a clone-side limit silently disables
inexact detection, which the daemon move is for every file it touched);
wire-compat's tagToHeadRenames now flips the shared helper instead of
re-implementing the diff. A fixture-repo test covers git mv + edit plus a
hostile renameLimit, in both directions the gates consume.

Generated with assistance.
@github-actions

github-actions Bot commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://callstack.github.io/agent-device/pr-preview/pr-3546/

Built to branch gh-pages at 2026-10-13 19:19 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

@thymikee thymikee changed the title refactor(daemon): extract the daemon server into @agent-device/daemon (11b) refactor(move): extract the daemon server into @agent-device/daemon (11b) Oct 13, 2026
@thymikee
thymikee changed the base branch from refactor/daemon-test-data-fakes to refactor/daemon-runtime-fakes October 13, 2026 01:01
@github-actions

github-actions Bot commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Size Report

Metric Base Current Diff
Installed (including dependencies) 5.20 MB 5.20 MB +45 B
Package (unpacked) 5.20 MB 5.20 MB +45 B
Package (download) 1.56 MB 1.56 MB +8 B

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 21.0 ms 19.9 ms -1.2 ms
CLI --help 51.8 ms 51.0 ms -0.7 ms

@thymikee
thymikee force-pushed the refactor/daemon-package branch from fdf8b0c to 3424798 Compare October 13, 2026 01:22
thymikee added a commit that referenced this pull request Oct 13, 2026
Façade per the #3546 review:
- the two internal/* suites move in-package: session-open-surface splits its
  two buildOpenResult cases into packages/daemon/src (the root-side
  resolveRequestedOpenSurface case stays root where its module lives), and
  interaction-touch-response splits its ten pure builder cases in-package (on
  the sanctioned in-package command-surface fake) while the six --verify
  handler cases stay root-side where the real command surface they exercise
  lives;
- R11 refuses ./testing/* for every non-test importer (root files,
  package-internal files, and vi.mock sites), with a pattern-matching
  named-by-exports check so scripts/ sites resolve through the entry;
- DAEMON_SOURCE_ROOT in tracked-sources.ts becomes the one root every
  layering filter keys on (11 sites, check.ts:588 included);
- docs/agents trims duplication to stay inside the 40 000-byte budget after
  the path lengthening (39 948 measured, headroom kept for PR 10).

Generated with assistance.
thymikee added a commit that referenced this pull request Oct 13, 2026
… repo

Non-blocking #3546 review items: renamedSince gains an optional head ref and an
explicit unlimited diff.renameLimit (a clone-side limit silently disables
inexact detection, which the daemon move is for every file it touched);
wire-compat's tagToHeadRenames now flips the shared helper instead of
re-implementing the diff. A fixture-repo test covers git mv + edit plus a
hostile renameLimit, in both directions the gates consume.

Generated with assistance.
thymikee added a commit that referenced this pull request Oct 13, 2026
Façade per the #3546 review:
- the two internal/* suites move in-package: session-open-surface splits its
  two buildOpenResult cases into packages/daemon/src (the root-side
  resolveRequestedOpenSurface case stays root where its module lives), and
  interaction-touch-response splits its ten pure builder cases in-package (on
  the sanctioned in-package command-surface fake) while the six --verify
  handler cases stay root-side where the real command surface they exercise
  lives;
- R11 refuses ./testing/* for every non-test importer (root files,
  package-internal files, and vi.mock sites), with a pattern-matching
  named-by-exports check so scripts/ sites resolve through the entry;
- DAEMON_SOURCE_ROOT in tracked-sources.ts becomes the one root every
  layering filter keys on (11 sites, check.ts:588 included);
- docs/agents trims duplication to stay inside the 40 000-byte budget after
  the path lengthening (39 948 measured, headroom kept for PR 10).

Generated with assistance.
thymikee added a commit that referenced this pull request Oct 13, 2026
… repo

Non-blocking #3546 review items: renamedSince gains an optional head ref and an
explicit unlimited diff.renameLimit (a clone-side limit silently disables
inexact detection, which the daemon move is for every file it touched);
wire-compat's tagToHeadRenames now flips the shared helper instead of
re-implementing the diff. A fixture-repo test covers git mv + edit plus a
hostile renameLimit, in both directions the gates consume.

Generated with assistance.
@thymikee
thymikee force-pushed the refactor/daemon-package branch from 3424798 to e00a91f Compare October 13, 2026 01:45
@thymikee

Copy link
Copy Markdown
Member Author

Move re-run on top of 11a-4 (refactor/daemon-runtime-fakes @ 855bd0f) and base retargeted; full acceptance evidence in the body.

@thymikee

thymikee commented Oct 13, 2026 •

Copy link
Copy Markdown
Member Author

Re-ran the whole move on top of 11a-4 (refactor/daemon-runtime-fakes), retargeted the base, and addressed every finding. Mapping finding → fix (head in the PR body carries the full evidence):

  1. Land on 11a-4, empty R11 residue, typecheck green, squash fixups — Re-ran from a hard reset onto 11a-4 with the updated move script. R11 residue is now EMPTY (the 10 leftover vi.mock tunnels to root platform-runtime-runtime-hints/apple-application-tools turned out to be dead weight once 11a-4 fakes those ports through composition — removed, suites green; the device-inventory root test installs its fake through the production withAppleToolProvider scope via createLocalAppleToolProvider for the current provider shape). pnpm typecheck green; check:layering green (318 rule tests + the guard). All commits re-laid as one codemod commit + hand-written commits; no fixups remain.
  2. Evidence in the PR — Body now carries the bundle-equivalence script logic + output (daemon-entry closure 124 = 124 identical, whole-build first-party multiset 1,571 = 1,571 identical across 399 maps), the test-name discovery comparison (13,447 → 13,480 with every delta classified: the eager-closure test.for expansion over the new entry-surface shape + timestamped parametrized titles; nothing lost or moved), a fresh depgraph (0 package→root edges), the bite-proof transcript, and the wire-compat 0-change output. Nothing temporary is committed.
  3. docs/agents < 40000 with headroom — 39,948 bytes now (trimmed duplication in testing.md, retargeted test-audit.md); the contract test passes with 52 bytes of headroom for PR 10.
  4. Façade — Settled in this PR: exports = 16 named production/script subpaths + ONE ./testing/* entry; R11 refuses ./testing/* for any non-test importer (root files, package-internal files, and vi.mock sites) with a unit test (package-boundaries-facade.test.ts) and planted-escape bite proofs (value/dynamic/vi.mock/type-only all fire). The two internal/* suites moved in-package: session-open-surface split (its three buildOpenResult cases moved; the root-owned resolveRequestedOpenSurface case stayed root), interaction-touch-response split (ten pure-builder cases moved onto the sanctioned in-package fake; the six --verify handler cases stayed root-side where the real command surface they exercise lives). 84 → 17 entries. Policy recorded as an amendment to ADR 0033 (the extraction/collocation ADR). daemon-request wire types → @agent-device/daemon-contracts is noted as the follow-up PR.
  5. DAEMON_SOURCE_ROOT — One constant in scripts/layering/tracked-sources.ts; every layering filter site keys on it (12 modules consume it, including the three scan-root filters a fresh self-review initially caught still carrying string literals).
  6. renamedSince duplication — The helper gained an optional head ref and an explicit unlimited diff.renameLimit; tagToHeadRenames now just flips it.
  7. Rename-canonicalization test — Fixture-repo test: two git mv + edit pairs (inexact renames), asserted through a hostile repo-level diff.renameLimit=1, in both directions the gates consume.
  8. Sweep rewrote history — Fixed the rule in the move script: path literals are retargeted only when the target exists at head (and GitHub permalinks are excluded); synthetic gate-fixture coordinates are retargeted as quoted strings only. Historical text keeps its original path: the ADR-0012/0018/0020/0023 citations to never-existing session-replay*/session-event-* paths, the #2544 maestro note, and the ad-replay-facade-types.ts history note all read src/daemon/... again; only the 13-plus genuinely-moved-file citations were retargeted.

@thymikee

Copy link
Copy Markdown
Member Author

Three CI fixes at e00a91f, each at its cause:

  1. eager-closure-budgets hostile-rename fixture — the temp repo only carried its identity via run-scoped -c flags on the base commit, so the caller's later pad/two moved edits commits failed with "Author identity unknown" on CI. mkGitFixtureRepo now writes user.name, user.email, and commit.gpgsign=false into the fixture repo's own config.
  2. session-teardown-import-closure ENOENT — the test still walked src/daemon/session-teardown.ts; it now derives its root from DAEMON_SOURCE_ROOT. Repo-wide sweep for src/daemon/ literals naming files that exist at head: 9 sites fixed (platform-package-policy.test.ts ×5, device-lanes.test.ts, .fallowrc.json, contracts snapshot-runtime.test.ts, maestro invariants.ts, .device-evidence/CHECKLIST.md). Historical citations (ADR text, fallow capture fixture, past-move breadcrumb comments) verified against the move's base tree and left alone.
  3. kernel-errors mutants (daemon stop test) — the test mocked @agent-device/daemon/testing/daemon-stop while the moved SUT imports the exact @agent-device/daemon/daemon-stop. The mutation lane's alias table only holds exact manifest export entries, so in the Stryker sandbox the mock hit a phantom id and the real module loaded → 0 cleanup calls (base branch's relative import/mock pair resolved fine). The mock now uses the SUT's exact specifier.

check:quick, check:layering, both failing suites, and gate mutation --modules kernel-errors --shard 1/5 (94.74%, 0 harness errors) all pass.

thymikee added a commit that referenced this pull request Oct 13, 2026
The review could not find the scan the PR body reported, and the previous
ad-hoc copy under-reported edges: its single alternation bridged
`import|export … [\s\S]*? from`, so a vi.mock() specifier sitting between an
importOriginal type-import and the file's next static from clause was
swallowed by the earlier match and never reported. Five mocks of root
platform-runtime modules went unnoticed under it.

This scan matches every specifier form independently — statement-anchored
static import/export, side-effect import, dynamic import()/require(),
vi.mock/doMock/unmock/importMock/importActual/importOriginal with string or
template-literal arguments, and importOriginal<typeof import()> — and exits
1 on any relative edge leaving src/daemon. Run: node
scripts/daemon-edge-residue-scan.mjs. 11b's R11 package-boundary rule
(#3546) makes it permanent once the daemon is its own package.
thymikee added a commit that referenced this pull request Oct 13, 2026
Façade per the #3546 review:
- the two internal/* suites move in-package: session-open-surface splits its
  two buildOpenResult cases into packages/daemon/src (the root-side
  resolveRequestedOpenSurface case stays root where its module lives), and
  interaction-touch-response splits its ten pure builder cases in-package (on
  the sanctioned in-package command-surface fake) while the six --verify
  handler cases stay root-side where the real command surface they exercise
  lives;
- R11 refuses ./testing/* for every non-test importer (root files,
  package-internal files, and vi.mock sites), with a pattern-matching
  named-by-exports check so scripts/ sites resolve through the entry;
- DAEMON_SOURCE_ROOT in tracked-sources.ts becomes the one root every
  layering filter keys on (11 sites, check.ts:588 included);
- docs/agents trims duplication to stay inside the 40 000-byte budget after
  the path lengthening (39 948 measured, headroom kept for PR 10).

Generated with assistance.
thymikee added a commit that referenced this pull request Oct 13, 2026
… repo

Non-blocking #3546 review items: renamedSince gains an optional head ref and an
explicit unlimited diff.renameLimit (a clone-side limit silently disables
inexact detection, which the daemon move is for every file it touched);
wire-compat's tagToHeadRenames now flips the shared helper instead of
re-implementing the diff. A fixture-repo test covers git mv + edit plus a
hostile renameLimit, in both directions the gates consume.

Generated with assistance.
@thymikee
thymikee force-pushed the refactor/daemon-package branch from 88f6e42 to b03b59e Compare October 13, 2026 05:31
@thymikee

Copy link
Copy Markdown
Member Author

Re-run on 11a-4: base is now refactor/daemon-runtime-fakes @ 2b858440c (green), head force-pushed (with lease) to b03b59e2f — move regenerated via the codemod, hand-written commits cherry-picked, residue scan run (0 edges) then deleted in the gates commit. PR 10 (#3520): re-run your codemod on this head.

@thymikee
thymikee added this pull request to stack #3564 October 13, 2026 06:02
thymikee added a commit that referenced this pull request Oct 13, 2026
The review could not find the scan the PR body reported, and the previous
ad-hoc copy under-reported edges: its single alternation bridged
`import|export … [\s\S]*? from`, so a vi.mock() specifier sitting between an
importOriginal type-import and the file's next static from clause was
swallowed by the earlier match and never reported. Five mocks of root
platform-runtime modules went unnoticed under it.

This scan matches every specifier form independently — statement-anchored
static import/export, side-effect import, dynamic import()/require(),
vi.mock/doMock/unmock/importMock/importActual/importOriginal with string or
template-literal arguments, and importOriginal<typeof import()> — and exits
1 on any relative edge leaving src/daemon. Run: node
scripts/daemon-edge-residue-scan.mjs. 11b's R11 package-boundary rule
(#3546) makes it permanent once the daemon is its own package.
@thymikee

Copy link
Copy Markdown
Member Author

Reviewed b03b59e. It fixes the mutation-lane failure from the earlier review, and I found no new code problem that needs a change before merge. All 43 checks pass.

The PR still conflicts with its base. refactor/daemon-runtime-fakes was rebased to 4fb9cb3 (it now includes #3550, #3532 and #3521), so the move needs to be re-run there.

Not blocking, take or leave: the new wildcard entry at packages/daemon/package.json:98 is the first in the repo. The gates that read the exports map look up exact keys, so @agent-device/daemon/testing/X resolves to nothing in the layering edges, depgraph and mutation ownership, and the Stryker alias in workspace-aliases.ts skips it too. About 60 vi.mock('@agent-device/daemon/testing/...') sites can therefore miss the mock in the mutation sandbox, the same failure b03b59e fixed in daemon.test.ts. If the wildcard stays, resolving pattern entries once in specifierTargetsOf / workspaceSpecifierTargets (package-boundaries.ts:483) would let all four consumers inherit it. Separately, the ADR 0033 amendment (line 191) says ./testing/* publishes test data and that internal/ modules are reached only by in-package tests, but the manifest exposes all of src/, and root tests import five production internal/ modules through it, including interaction-touch-response.

Could ./testing/* be narrowed to fixtures only, for example ./src/__tests__/*.ts and per-folder equivalents, or named *-fixtures entries? The production modules that root tests need would then become named subpaths, or their tests would move into the package. That drops the second spelling for five named-export modules and keeps the gates exact-key. If the wide wildcard is the intended design, what made the narrower export unworkable?

Next step: re-run the move on 4fb9cb3 to clear the conflict, and settle in the ADR 0033 amendment whether ./testing/* exposes fixtures only or all modules.

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Oct 13, 2026
thymikee added a commit that referenced this pull request Oct 13, 2026
The review could not find the scan the PR body reported, and the previous
ad-hoc copy under-reported edges: its single alternation bridged
`import|export … [\s\S]*? from`, so a vi.mock() specifier sitting between an
importOriginal type-import and the file's next static from clause was
swallowed by the earlier match and never reported. Five mocks of root
platform-runtime modules went unnoticed under it.

This scan matches every specifier form independently — statement-anchored
static import/export, side-effect import, dynamic import()/require(),
vi.mock/doMock/unmock/importMock/importActual/importOriginal with string or
template-literal arguments, and importOriginal<typeof import()> — and exits
1 on any relative edge leaving src/daemon. Run: node
scripts/daemon-edge-residue-scan.mjs. 11b's R11 package-boundary rule
(#3546) makes it permanent once the daemon is its own package.
thymikee added a commit that referenced this pull request Oct 13, 2026
Façade per the #3546 review:
- the two internal/* suites move in-package: session-open-surface splits its
  two buildOpenResult cases into packages/daemon/src (the root-side
  resolveRequestedOpenSurface case stays root where its module lives), and
  interaction-touch-response splits its ten pure builder cases in-package (on
  the sanctioned in-package command-surface fake) while the six --verify
  handler cases stay root-side where the real command surface they exercise
  lives;
- R11 refuses ./testing/* for every non-test importer (root files,
  package-internal files, and vi.mock sites), with a pattern-matching
  named-by-exports check so scripts/ sites resolve through the entry;
- DAEMON_SOURCE_ROOT in tracked-sources.ts becomes the one root every
  layering filter keys on (11 sites, check.ts:588 included);
- docs/agents trims duplication to stay inside the 40 000-byte budget after
  the path lengthening (39 948 measured, headroom kept for PR 10).

Generated with assistance.
thymikee added a commit that referenced this pull request Oct 13, 2026
… repo

Non-blocking #3546 review items: renamedSince gains an optional head ref and an
explicit unlimited diff.renameLimit (a clone-side limit silently disables
inexact detection, which the daemon move is for every file it touched);
wire-compat's tagToHeadRenames now flips the shared helper instead of
re-implementing the diff. A fixture-repo test covers git mv + edit plus a
hostile renameLimit, in both directions the gates consume.

Generated with assistance.
thymikee added a commit that referenced this pull request Oct 13, 2026
…attern module

resolveWorkspaceSpecifier (layering model) mirrors Node's longest-prefix
pattern semantics and is the single expansion owner for the layering edges,
the depgraph, mutation ownership, and the Stryker alias table — a pattern key
held as a literal matched nothing, so every concrete ./testing/<dir>/ import
resolved to nowhere in those gates and could miss its vi.mock in the mutation
sandbox (#3546 review). Aliases now emit exact entries before capture-group
patterns, matching Vite's first-match order. R11's test-data refusal keys on
the ./testing/ namespace, not a pattern entry, so narrowing the surface to
exact keys cannot smuggle a fixture past the test-file rule. ADR-0033
amendment records the settled policy.
@thymikee
thymikee force-pushed the refactor/daemon-package branch from a4c6208 to 0b9d8d0 Compare October 13, 2026 18:26
thymikee added a commit that referenced this pull request Oct 13, 2026
A subpath only tests import is not production surface (#3546 review): the
scan of every import and vi.mock site outside the package names exactly 16
subpaths with production/script consumers — the move-map set — while the
other 72 are test-only, so they move under ./testing/<module> with their
importers re-spelled. R11's test-file refusal already guards the whole
./testing/ namespace, so root production code can no longer import daemon
internals like ./interaction/internal/types at all.
thymikee added a commit that referenced this pull request Oct 13, 2026
…nd the alias table

namedByExports now calls resolveWorkspaceSpecifier instead of its private
prefix scan, and the Stryker alias table orders its Vite first-match list to
Node's precedence — exact entries, then patterns by prefix length descending —
with a test asserting the alias a probe hits equals resolveWorkspaceSpecifier's
target for EVERY export key of every workspace package. The ADR-0033 amendment
says what actually owns what: the resolver owns the rules, the alias table
inherits its order. (#3546 review round 3)
thymikee added a commit that referenced this pull request Oct 13, 2026
The inline targets map gave `@agent-device/demo/testing/*` the target
`packages/demo/src/*.ts` while the planted manifest declares
`./src/shadow/*.ts`, so both patterns filled `testing/handlers/x` to the
same path and the cross-check passed whichever alias fired (#3546 review).
Aligned to the manifest's shadow tree, the two spellings resolve
differently and a shortest-prefix resolver fails the second assertion —
bite-proved by flipping the sort to ascending, which now fails exactly
the overlap case and passes nothing else about it.

Generated with assistance.
@thymikee

Copy link
Copy Markdown
Member Author

Re-run on 11a-4: base is refactor/daemon-runtime-fakes @ 0fa7ba689 (#3544 ready; stack on current main via #3519/#3517). Head force-pushed (with lease) to 0b9d8d0e8. The move was regenerated with the codemod over the new #3544 (670 files — picks up the lifecycle-loader-as-parameter harness, the composition-fixture Android discovery seam, and the moved support/unavailable-device-runtime-gateway), then all 16 hand-written commits were replayed; the only content delta from the previous lineage is the facade-naming commit gaining the named ./testing/__tests__/support/unavailable-device-runtime-gateway entry that #3544's move made necessary. The owner's alias-target note is a new commit on top (0b9d8d0e8). Acceptance re-run on this lineage: typecheck 0 errors, layering OK with zero R11 routes (545 subpaths), depgraph 0 package→root edges, discovery parity 11,599→11,600 fully classified (9 composition tests relocated into the package 1:1 by the facade commit + 1 new gate test), bundle equivalence 401/401 chunks with an identical 1,576-instance first-party source multiset (one chunk renamed by the naming heuristic, identical source sets and consumers), wire-compat protocol-unchanged vs v0.21.27 (191 declarations, 0 changed/removed/added), package/gate-manifest/DI-seams checks green, mutation lane 67/67, and the replayed composition suites touched by the rebase pass (66 tests across 7 files).

@thymikee

Copy link
Copy Markdown
Member Author

Re-run on the updated 11a-4: base is refactor/daemon-runtime-fakes @ 1b376a47d (#3544 after the cubic-review fixes). Head force-pushed (with lease) to 67845e3cd. The move was regenerated over the new #3544 (670 files; absorbs the review-fix commits — the fixture relaunch order, the gateway signal/owner fixes and the composition-test changes arrive as base content through the move and the facade re-derivation), then all 16 hand commits were replayed; range-diff vs the previous lineage shows the only deltas are base content (no new resolutions needed — the previous facade-naming amendment already covers support/unavailable-device-runtime-gateway). Acceptance re-run: typecheck 0, layering OK with zero R11 routes (545 subpaths), depgraph 0 package→root edges, discovery parity 11,599 → 11,600 fully classified (the same 1:1 composition→package relocations + 1 new gate test), bundle equivalence 401 chunks with an identical 1,576-instance first-party multiset (the one chunk rename is the naming heuristic again, identical source sets), wire-compat protocol-unchanged vs v0.21.27, package/gate-manifest/DI-seams green, mutation lane 67/67, and the nine suites #3544's fixes touched pass through the move (69 tests).

Pure codemod: git mv src/daemon -> packages/daemon/src (670 files), inbound
imports rewritten to @agent-device/daemon/<subpath> specifiers with the export
facade split up front (16 named production/script entries plus one ./testing/*
pattern entry for test-file importers), generated manifest and composite
tsconfig, root/examples project references, and the exists-at-head path-literal
sweep across code, docs, and ADRs (historical text keeps its original path).

Reproducible with node /tmp/11b-move-v2.mjs on a clean tree.

Generated with assistance.
…daemon package

Hand-written retargets the move requires: R11 gains a vi.mock/doMock/mockActual/
unmock/importActual literal sweep (the AST parser never visits call expressions
yet the loader resolves those specifiers); ratchet-reference and wire-compat gain
git-rename canonicalization so a bulk git mv cannot zero a ratchet or outrun the
released ledger; rule/config paths (stryker, di-seams, .fallowrc, fallow-health
and test-file-size baselines, oxlint) retargeted; new URL seams re-anchored.

Deletes scripts/daemon-edge-residue-scan.mjs: it measured daemon escape edges
while src/daemon was still root code, and R11 owns that invariant permanently
once the daemon is a package (run once before deletion: 0 edges).

Post-move seam fixes: the replay-repair and session-open-runtime suites dropped
their root runtime-hints/apple-application-tools vi.mock tunnels — 11a-4's port
fakes already cover those ports through composition — and the device-inventory
root test installs its fake tool provider through the production
withAppleToolProvider scope instead of a package-internal module mock.

Generated with assistance.
Façade per the #3546 review:
- the two internal/* suites move in-package: session-open-surface splits its
  two buildOpenResult cases into packages/daemon/src (the root-side
  resolveRequestedOpenSurface case stays root where its module lives), and
  interaction-touch-response splits its ten pure builder cases in-package (on
  the sanctioned in-package command-surface fake) while the six --verify
  handler cases stay root-side where the real command surface they exercise
  lives;
- R11 refuses ./testing/* for every non-test importer (root files,
  package-internal files, and vi.mock sites), with a pattern-matching
  named-by-exports check so scripts/ sites resolve through the entry;
- DAEMON_SOURCE_ROOT in tracked-sources.ts becomes the one root every
  layering filter keys on (11 sites, check.ts:588 included);
- docs/agents trims duplication to stay inside the 40 000-byte budget after
  the path lengthening (39 948 measured, headroom kept for PR 10).

Generated with assistance.
Amends the collocation/extraction ADR with the 11b decision: named exports for
production/script consumers only, one ./testing/* pattern entry that R11 refuses
for non-test importers, internal modules reached only by in-package tests, and
the daemon-request wire-types move noted as the follow-up.

Generated with assistance.
… repo

Non-blocking #3546 review items: renamedSince gains an optional head ref and an
explicit unlimited diff.renameLimit (a clone-side limit silently disables
inexact detection, which the daemon move is for every file it touched);
wire-compat's tagToHeadRenames now flips the shared helper instead of
re-implementing the diff. A fixture-repo test covers git mv + edit plus a
hostile renameLimit, in both directions the gates consume.

Generated with assistance.
… own config

The fixture committed only its base with run-scoped -c identity flags, so
every later commit a caller makes (the pad commit in the hostile-renameLimit
case) resolves identity from the runner's global config and dies with
'Author identity unknown' on CI. Write user.name/user.email/commit.gpgsign
into the temp repo instead so all commits are self-contained.
The move swapped the SUT to '@agent-device/daemon/daemon-stop' while the test
mocked the './testing/*' pattern alias. Vitest resolves both to one file in
the unit lane, so it passed there, but the mutation lane aliases only the
manifest's exact export entries (tracked-manifests), so inside the Stryker
sandbox the mock specifier resolved to a phantom id, the real module loaded,
and the dry run failed on zero cleanup calls. Mock the exact specifier the
SUT imports; the type import follows so the pair cannot drift.
…rowing the facade

Collapses the ./testing/* wildcard the export map carried: the 82 production
modules reached from root tests through it become named subpaths (one spelling
per module), and fixtures keep their ./testing/ spelling as named entries or
per-folder ./testing/<dir>/* patterns that expose only __tests__ folders.
…attern module

resolveWorkspaceSpecifier (layering model) mirrors Node's longest-prefix
pattern semantics and is the single expansion owner for the layering edges,
the depgraph, mutation ownership, and the Stryker alias table — a pattern key
held as a literal matched nothing, so every concrete ./testing/<dir>/ import
resolved to nowhere in those gates and could miss its vi.mock in the mutation
sandbox (#3546 review). Aliases now emit exact entries before capture-group
patterns, matching Vite's first-match order. R11's test-data refusal keys on
the ./testing/ namespace, not a pattern entry, so narrowing the surface to
exact keys cannot smuggle a fixture past the test-file rule. ADR-0033
amendment records the settled policy.
…lacing it

CodeQL flagged String#replace's first-occurrence semantics: Node fixes the
exports target to exactly one `*`, so the splice is exact by construction and
the intent reads off the index.
…t moved

The teardown closure test still walked srcRoot + daemon/session-teardown.ts,
so all three of its cases ENOENTed once the move landed; it now derives its
root from DAEMON_SOURCE_ROOT so a future daemon move cannot strand it again.
Swept the remaining literals that referred to files existing at head: the
platform-policy planted daemon paths (the mechanics-facet allow entry named a
daemon test that now lives at handlers/__tests__/session.test.ts), the
device-lanes unit-test sample, the fallowrc test-consumer citation (the
target-evidence test moved to packages/selectors), the contracts
wait-runtime and maestro session-test-runtime citations (both moved out of
the daemon earlier), and the CHECKLIST activation claim belongs to
interaction-capture-disclosure.test.ts. Citations to files that never
existed at the move's base or are historical captures were left alone.
A subpath only tests import is not production surface (#3546 review): the
scan of every import and vi.mock site outside the package names exactly 16
subpaths with production/script consumers — the move-map set — while the
other 72 are test-only, so they move under ./testing/<module> with their
importers re-spelled. R11's test-file refusal already guards the whole
./testing/ namespace, so root production code can no longer import daemon
internals like ./interaction/internal/types at all.
…nd the alias table

namedByExports now calls resolveWorkspaceSpecifier instead of its private
prefix scan, and the Stryker alias table orders its Vite first-match list to
Node's precedence — exact entries, then patterns by prefix length descending —
with a test asserting the alias a probe hits equals resolveWorkspaceSpecifier's
target for EVERY export key of every workspace package. The ADR-0033 amendment
says what actually owns what: the resolver owns the rules, the alias table
inherits its order. (#3546 review round 3)
The codemod had re-spelled the named-entry assertion to a ./testing/
specifier, which the fixture manifest does not name in that shape, and a
compiled site skips resolution — so the [] it asserted passed even for a
spelling no entry exports. Restore the fixture's named ./session-store
spelling and assert [] in both compiled modes: only the uncompiled pass
can report 'not named', so its [] is the non-vacuity proof. Bite-proved:
probing an unexported spelling fails exactly that case.
…t-first

The exports-semantics cross-check iterates the REAL manifest keys, whose
declaration order happens to hide a missing patterns.sort — deleting it left
every lane test green. Plant the hostile order instead: a sandbox package
declaring ./testing/* before ./testing/handlers/*, targets that fill to
different trees so the probe names which alias fired, and check that the
first-matched alias equals resolveWorkspaceSpecifier's longest-prefix target.
Bite-proved: removing patterns.sort from workspace-aliases.ts fails exactly
the new case; restoring it passes 5/5.
The inline targets map gave `@agent-device/demo/testing/*` the target
`packages/demo/src/*.ts` while the planted manifest declares
`./src/shadow/*.ts`, so both patterns filled `testing/handlers/x` to the
same path and the cross-check passed whichever alias fired (#3546 review).
Aligned to the manifest's shadow tree, the two spellings resolve
differently and a shortest-prefix resolver fails the second assertion —
bite-proved by flipping the sort to ascending, which now fails exactly
the overlap case and passes nothing else about it.

Generated with assistance.
@thymikee

Copy link
Copy Markdown
Member Author

Re-run on the new #3544 head (7f9a4c6d2). The content below changed (#3517's ratchet fix added scripts/layering/test-only-fixture-subpaths.test.ts and trimmed package-boundaries.test.ts), so the move was regenerated rather than re-pointed: git reset --hard 7f9a4c6d2, node /tmp/11b-move-v2.mjs, lockfile amend into the move commit, then all 16 hand commits cherry-picked — zero conflicts. New head: 9cbd655a1.

Range-diff vs the previous lineage (1b376a47d..67845e3cd vs 7f9a4c6d2..9cbd655a1): all 17 commits patch-identical. End-tree delta vs the old head is exactly the two base files above — the move codemod retargeted nothing else (the new test's src/daemon/... strings are in-memory fixture paths under the packages/daemon-side narrative and stay byte-identical, matching how the same file behaves at its base).

Acceptance at the new head:

  • typecheck + lint green; layering suite 322 pass; check.ts green with an empty R11 residue (28 packages behind the facades).
  • depgraph: 0 package→root escapes; gate-manifest ok; DI-seams 14 pass.
  • wire-compat: protocol unchanged vs v0.21.27 — 191 declarations, 0 changed/removed/added/moved.
  • mutation workspace-aliases.test.ts 5 pass (alias table incl. the longest-first overlap pin).
  • discovery parity: 11,599 → 11,600 test names; deltas are the 9 known relocations into packages/daemon/src/** + the 1 new gate test from the base (refactor(daemon): package-local runtime fakes for the remaining daemon ports [11a-4] #3544's fixture-repo rename test). The scripts/ test-module split is node-test territory and invisible to this list; its lane is check:layering above.
  • bundle equivalence (tsdown --sourcemap, canonicalized move): 401 entries both sides, whole-build first-party multiset identical 1,576/1,576; sole chunk delta is the known session-replay-vars / session-replay-runtime-failure-response rename.
  • sanity shard through the move: 104 files / 656 tests pass (incl. the suites refactor(daemon): package-local runtime fakes for the remaining daemon ports [11a-4] #3544's review fixes touch).

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-human Valid work that needs human implementation, judgment, or maintainer merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants