Skip to content

refactor(daemon): package-local runtime fakes for the remaining daemon ports [11a-4] - #3544

Draft
thymikee wants to merge 7 commits into
refactor/daemon-test-data-fakesfrom
refactor/daemon-runtime-fakes
Draft

thymikee wants to merge 7 commits into
refactor/daemon-test-data-fakesfrom
refactor/daemon-runtime-fakes

Conversation

@thymikee

@thymikee thymikee commented Oct 12, 2026 •

Copy link
Copy Markdown
Member

What

Daemon-extraction slice 11a-4: eliminates every edge where a file under src/daemon/ imports (or vi.mocks) a non-package module outside src/daemon/. Residue scan: 0 edges (node scripts/daemon-edge-residue-scan.mjs, committed in this PR; definition below).

Stacked on refactor/daemon-test-data-fakes (#3517).

How

Daemon suites now answer platform questions at the daemon's own ports, and every port member is a plain vi.fn spy with a canned default answer that carries no platform rule (owner decision on review):

  • platform-services port (src/daemon/__tests__/platform-services-fixture.ts) — all six members (appleSessionObservation, deviceBootObservation, ensureLocalDeviceReady, resolveRequestedOpenSurface, validateOpenRelaunchTarget, resolveSessionAppBundleIdForTarget) are spies; the former near-verbatim copies of platform-runtime-open-target.ts, platform-runtime-device-boot.ts and platform-runtime-device-ready.ts are deleted, and so is the shared fixtureResolveSessionAppBundleIdForTarget the lifecycle fixture imported.
  • application-lifecycle runtime binding — resolveOpenTarget, prepareApplicationOpen and openApplication are spies whose canned answers delegate to the contracts' canonical direct-lifecycle members (identity pass-through, readiness pause, admitted-hint apply). The macOS frontmost-surface rule and the android stopped-AVD discovery branch are gone. The real composed fixture lives root-side (src/__tests__/daemon-composition/application-lifecycle-runtime-fixture.ts) and is built on the same seam set, so both lanes observe one spy family; the gateway and session-command harness take an injectable lifecycle-operations loader.
  • runtime-hints — hint apply/clear are observed at the fixture's port spies; assertion bodies unchanged.
  • root-module mocks — the four replay-repair platform-runtime-runtime-hints.ts mocks and the session-open-runtime platform-runtime-apple-application-tools.ts mock were dead (no daemon file loads either module; hint apply/clear reach the device through the admitted binding's operations) and are deleted, not retargeted.

Tests that assert what the REAL composed rules answer do not stay in the daemon lane: they move to the composition lane (src/__tests__/daemon-composition/), where the lane's own fixture hands them the root-composed services and the real composed lifecycle. Assertion bodies are copied verbatim; no assertion was edited to fit a fake. The device-ready suite (it pins what the composed readiness port does per device family) whole-file-moves to the composition lane; 19 composition-lane suites that imported the daemon's services fixture now import the composition lane's own real one.

Rule-ownership relocation (this revision, per review)

Sorted with the throwing-fake / canned-success probes against the spy fixture:

  • Relocated to the composition lane (18 tests, names unchanged): session-open-existing open-target/surface cases ×8 → session-lifecycle/session-open-existing.test.ts; open --relaunch target-validation cases ×5 → handlers/session-relaunch-guards.test.ts; device-ready TTL-plus-real-readiness cases ×5 → device/device-ready.test.ts.
  • Kept in the daemon lane, retargeted to the port spies (assertion bodies unchanged): boot-probe answers in request-router-open / request-router-open-claim ×3 now arm observeBootTimeMsFixture; open-target identity ×2 (session-open-runtime, session-open-execution-runtime) answer resolveOpenTargetFixture per case; the app-event lifetime case answers resolveSessionAppBundleIdForTargetFixture; the claim-rollback case injects its failure at prepareApplicationOpenFixture.
  • Dead mocks deleted: 5 (fix for the five remaining root-module mocks).

Per-file test-name parity vs the previous head holds: 2671 names, identical multiset; 18 relocated with their names, 0 renamed, 0 added, 0 dropped.

Residue scan (definition, as requested)

scripts/daemon-edge-residue-scan.mjs walks every .ts/.tsx/.mjs under src/daemon/ and matches each specifier form independently (the earlier ad-hoc scan used one alternation bridging import|export … [\s\S]*? from, which swallowed a vi.mock() specifier sitting between an earlier importOriginal<typeof import(...)> and the file's next static from — that is how five mocks hid under its "0"):

  1. static import/export … from with the keyword anchored at a statement start (line start or ;), so no match ever spans statements (covers import type, multi-line specifier clauses);
  2. side-effect import '…';
  3. dynamic import('…') and require('…') with string or no-substitution template-literal arguments (this also catches importOriginal<typeof import('…')>);
  4. vi.mock / vi.doMock / vi.unmock / vi.importMock / vi.importActual / vi.importOriginal first argument, any position in the file, string or template literal.

A relative specifier that normalizes outside src/daemon/ is an edge; bare specifiers (workspace packages, externals) are allowed. Exit 1 on any edge. Verified: 5 edges on 855bd0f, 0 on this head. Once the daemon is its own package, 11b's R11 package-boundary rule (#3546) enforces this permanently in check:layering; until then this committed scan is the measurement.

Gates

  • unit-core lane (vitest run --project unit-core, full): 1591 files / 13447 passed, 1 skipped, 0 failures; within it daemon lane 316 files / 2306 tests, composition lane 97 files / 638 tests, both 0 failures
  • check:quick, check:layering, check:di-seams, check:gate-manifest, check:fallow, check:affected --run, eager-closure-budgets + test-file-size ratchets, pnpm format: OK

Pure moves / renames dominate the diff; hand-written additions are the port fixtures, the install seam, the composition siblings, and the committed scan.

View guided diff

@github-actions

github-actions Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Size Report

Metric Base Current Diff
Installed (including dependencies) 5.18 MB 5.18 MB 0 B
Package (unpacked) 5.18 MB 5.18 MB 0 B
Package (download) 1.56 MB 1.56 MB -1 B

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 26.3 ms 27.2 ms +0.9 ms
CLI --help 78.3 ms 79.8 ms +1.5 ms

@thymikee

Copy link
Copy Markdown
Member Author

Coordinator: rebased onto the current tip of the PR below (stack sync, no content changes).

@thymikee

Copy link
Copy Markdown
Member Author

The daemon-side test fixtures duplicate root platform rules, and five root-module mocks remain in src/daemon, so this is not ready at 855bd0f. All 14 checks pass, and there are no conflicts.

daemonPlatformServicesFixture in platform-services-fixture.ts re-implements root composition code. fixtureEnsureLocalDeviceReady copies ensureLocalPlatformDeviceReady, fixtureDeviceBootObservation copies platform-runtime-device-boot.ts, and the open-target helpers (fixtureResolveRequestedOpenSurface, fixtureValidateOpenRelaunchTarget, fixtureResolveSessionAppBundleIdForTarget) are near-verbatim copies of platform-runtime-open-target.ts. The copies are not spies, and nothing pins them to production. The Linux and macOS surface rules were already merged into resolveSurfaceFor, and appleSessionObservation became a no-op. So a change to the root open, relaunch or readiness rules can leave the daemon suites green with stale behavior. The rule should be that each platform rule has one implementation and every consumer imports it. Please either move the open-target, surface, readiness and boot rules into the owning package leaves so root and the fixture both import them, or make the services members plain vi.fn spies with canned answers and keep the rule-pinning cases in the composition lane (session-open-surface.test.ts already exists). The lifecycle fixture's resolveOpenTarget, prepareApplicationOpen and hint reapplication need the same treatment.

Issue 11a-4 requires that no src/daemon file imports or vi.mocks a non-package module outside src/daemon, and the PR body reports 0 edges. Five files still do. Four replay-repair tests mock platform-runtime-runtime-hints.ts (transaction.test.ts:45, and the record-exclusion, close-ordering and force variants), and session-open-runtime.test.ts:21 mocks platform-runtime-apple-application-tools.ts. Nothing under src/daemon imports those modules now, so these look like dead mocks. Please delete them, or retarget them to the fixture spies (applyRuntimeHintsFixture, clearRuntimeHintsFixture), then re-run the scan, confirm zero edges and say how the scan is defined.

Could the services members be spies with canned answers, with the lifecycle-ops and services fixtures as thin vi.fn doubles? Cases that assert real open-target, surface or readiness rules would stay in the composition lane. If the daemon suites truly need the real rules, extract them once into package leaves that both src/platform-runtime-*.ts and the fixture import. The first step is to decide whether the rule owner is the package leaves or the composition lane. Either way, the daemon-side twin should go before this slice lands.

I did not run the test lanes, so the 96-file, 624-test composition run and the daemon lane results are the author's. I did not execute the five mocks to confirm they are dead. I also could not find the residue scan in the repo, so the count above comes from my own reading of the import specifiers. Before merge, the five mocks need to go and the duplicated rules need one owner.

@thymikee

Copy link
Copy Markdown
Member Author

@thymikee all three findings addressed on top of 855bd0f (new commits only, 855bd0f..2b85844; #3546/#3520 codemods can re-run on top). Mapping:

1. Duplicated platform rules in daemon fixtures — composition lane owns the rules.

  • daemonPlatformServicesFixture members are now all plain vi.fn spies with canned answers: boot probe → {observed:false,reason:'unobserved'}; readiness → resolves true; surface → existingSurface ?? 'app' (no platform branch); relaunch validation → accept; open-target bundle id → pass through currentAppBundleId; observation members → undefined. Every re-implemented rule deleted: fixtureEnsureLocalDeviceReady, fixtureDeviceBootObservation, fixtureResolveRequestedOpenSurface, fixtureValidateOpenRelaunchTarget, fixtureResolveSessionAppBundleIdForTarget (including the shared export the lifecycle fixture imported). Neither fixture file imports any contract rule helper or platform package any more.
  • Lifecycle fixture: resolveOpenTarget, prepareApplicationOpen, openApplication are spies whose defaults delegate to the contract's canonical direct-lifecycle members (identity pass-through / readiness pause / admitted-hint apply). The macOS frontmost-surface branch and the android stopped-AVD discovery branch are deleted — those were the real owners' rules.
  • Test sort (throwing-fake + canned-success probes; no assertion edited to fit a fake):
    • Moved to src/__tests__/daemon-composition/ (18 tests, names verbatim): session-open-existing open-target/surface cases ×8 → session-lifecycle/session-open-existing.test.ts (real composed services + real composed lifecycle); open --relaunch URL/Android-binary validation ×5 → handlers/session-relaunch-guards.test.ts; device-ready (pins composed readiness per family) whole-file ×5 → device/device-ready.test.ts.
    • Kept daemon-side by retargeting the observation handle (11 cases, assertion bodies unchanged): boot answers → observeBootTimeMsFixture (request-router-open, request-router-open-claim ×3); open-target identity per case → resolveOpenTargetFixture (session-open-runtime, session-open-execution-runtime ×2); app-event lifetime → resolveSessionAppBundleIdForTargetFixture; claim-rollback failure injection → prepareApplicationOpenFixture.
    • The 19 composition-lane suites that had been importing the daemon's services fixture now import the lane's own root-composed one (src/__tests__/daemon-composition/platform-services-fixture.ts).

2. Five remaining root-module mocks — executed, they were dead; deleted.
Nothing under src/daemon loads platform-runtime-runtime-hints.ts or platform-runtime-apple-application-tools.ts any more (hint apply/clear reach the device only through the admitted binding's operations, observed at applyRuntimeHintsFixture/clearRuntimeHintsFixture; the prewarm spy has no caller). The four replay-repair mocks and the session-open-runtime mock are gone, suite still green. Bonus: request-router-open.test.ts was still mocking platform-apple/simulator-boot for a leaf the daemon doesn't load; retargeted to the boot spy.

3. Why the scan said 0 — fixed, and the scan is now in-repo.
The old regex was one alternation: (?:import|export)[\s\S]*?from\s*['"]…['"] | import(...) | vi.mock(...) | importOriginal<typeof import(...)>. Regex alternatives are tried at each position left-to-right, and [\s\S]*? crosses newlines: starting at an earlier import, the first alternative gobbled everything — including the whole vi.mock('../../../platform-runtime-runtime-hints.ts', …) block — up to the file's next from '…' and captured that specifier (an allowed one). The root-mock specifiers were consumed mid-bridge and never reported. I reproduced this: the old scan prints 0 on 855bd0f while the corrected one prints exactly the 5 you listed.
Committed as scripts/daemon-edge-residue-scan.mjs: matches each specifier form independently — statement-anchored static import/export … from (incl. import type, multi-line clauses), side-effect import, dynamic import()/require() with string or no-substitution template args (covers importOriginal<typeof import()>), and vi.mock/doMock/unmock/importMock/importActual/importOriginal first arguments at any position. Relative specifier resolving outside src/daemon = edge; bare specifiers allowed; exit 1 on any edge. Verified 5 edges on 855bd0f, 0 on 2b85844. PR body now carries the definition verbatim, and notes 11b's R11 (#3546) owns this permanently once the daemon is a package.

Lanes/gates. Full unit-core: 1591 files, 13447 passed / 1 skipped / 0 failures. Daemon lane 316 files / 2306 tests and composition lane 97 files / 638 tests: 0 failures. Per-file test-name parity vs 855bd0f: 2671 names identical as a multiset (18 relocated with names, 0 renamed/added/dropped). check:quick, check:layering, check:di-seams, check:gate-manifest, check:fallow (spies kept unexported; scan registered in .fallowrc entries), check:affected --run, eager-closure/size ratchets, pnpm format: all OK.

@thymikee
thymikee added this pull request to stack #3564 October 13, 2026 06:02
apex-agent and others added 7 commits October 13, 2026 08:23
…servation, and platform-services ports in daemon suites

Daemon suites no longer import root platform-runtime modules: the
host-diagnostics, perf-capture, android-observation, and platform-services/
claim-recovery edges are answered by daemon-local port fixtures whose members
are spies or resolve through platform-package leaves (Safari/deep-link rules,
iOS app resolution, android package mechanics), while suites that pin what the
real composed root modules do keep a root-owned composition fixture at
src/__tests__/daemon-composition/.

Relocated to the composition lane: the perf-capture session-resource suite,
readiness spawn suites, the runner-retention trio, the foreign-dead-owner
claim-recovery cases, and three android system-dialog suites converted to port
spies where their assertions were daemon policy.
… in daemon suites

The daemon lane now binds a canned lifecycle fixture built on the contracts
direct-lifecycle binding plus port spies (readiness pause, android discovery,
dispatch, hint apply/clear, runner prepare). The real composed fixture moves to
the root-owned daemon-composition lane, built on the same seam set so both lanes
observe one spy family. Hint-observation suites retarget to the fixture's port
spies (assertion bodies unchanged); suites whose assertions pin what the real
apple/android runtime modules do under composition relocate:
session-relaunch-close and session-open-url-prewarm whole-file, and the
close-finalization, shutdown-gating, recording-finalization, post-open
observation, runtime-hint lazy-evaluation, and android IME cancellation cases
as extracted composition siblings. The gateway and session-command harness take
an injectable lifecycle-operations loader so composition suites build the real
gateway behind the same handler harness.

Scan for daemon -> non-package src/ edges: 0.
…suites

The four replay-repair suites mocked clearRuntimeHintValues out of
platform-runtime-runtime-hints.ts, and session-open-runtime patched
createAppleApplicationTools for a prewarm spy. Nothing under src/daemon
imports either module any more (hint apply/clear reach the device through
the admitted binding's operations, observed at the fixture port spies),
so the mocks answered no call. Deleted; the corrected residue scan now
reports 0 edges.
…th canned spies

Per review: the composition lane owns the platform rules, so the daemon
fixtures stop re-implementing them. Every services/lifecycle member is
now a plain vi.fn spy with a canned default a test overrides per case:

- platform-services-fixture: appleSessionObservation, deviceBootObservation,
  ensureLocalDeviceReady, resolveRequestedOpenSurface,
  validateOpenRelaunchTarget and resolveSessionAppBundleIdForTarget are
  spies; the open-target/surface/readiness/boot rule copies are deleted,
  and the shared fixtureResolveSessionAppBundleIdForTarget the lifecycle
  fixture imported goes with them.
- application-lifecycle-runtime-fixture: resolveOpenTarget,
  prepareApplicationOpen and openApplication are spies delegating to the
  contract's canonical direct-lifecycle members; the macOS frontmost rule
  and the android stopped-AVD discovery branch are gone.

Tests that pinned real rules move to the composition lane unchanged:
8 cases to session-lifecycle/session-open-existing.test.ts, 5 relaunch
guards to handlers/session-relaunch-guards.test.ts, and device-ready
whole-file (it pins what the composed readiness port does per family).
11 cases keep their names and assertion bodies daemon-side, retargeted
to the port spies (boot observation, open-target identity per case,
readiness failure injection). Suites under src/__tests__/daemon-composition
now use the lane's own root-composed services fixture.

Per-file test-name parity holds (2744 names, moved or kept); scan edges 0.
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.
…r the scan entry

Only the spies suites actually observe per case are exported; the rest stay
module-private behind the fixture factory. The residue scan joins the
.fallowrc entry list beside the other hand-run scripts.
…open router suite

request-router-open pinned the reboot-stale claim answers by mocking the
apple simulator-boot leaf, which the daemon no longer loads; both cases now
arm observeBootTimeMsFixture, the port member the claim settlement actually
asks.
@thymikee
thymikee force-pushed the refactor/daemon-runtime-fakes branch from 2b85844 to 4fb9cb3 Compare October 13, 2026 06:24
@thymikee

Copy link
Copy Markdown
Member Author

Coordinator: rebased onto #3517's new head 2354fd0, which now sits on current main. The rebase applied cleanly and none of this PR's commits changed. Verified at this head: scripts/daemon-edge-residue-scan.mjs reports 0 edges, including the tests main added; check:quick passes; the daemon and composition lanes pass (488 files, 3508 tests).

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

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant