Skip to content

refactor(daemon): move the runtime dependency construction to the process root (PR 6) - #3431

Merged
thymikee merged 3 commits into
mainfrom
refactor/daemon-composition-root
Oct 12, 2026
Merged

thymikee merged 3 commits into
mainfrom
refactor/daemon-composition-root

Conversation

@thymikee

@thymikee thymikee commented Oct 11, 2026 •

Copy link
Copy Markdown
Member

Summary

PR 6 of the daemon extraction (#3276 umbrella): the daemon server no longer builds its own dependencies. startDaemonRuntime takes { env, sessionStore, deps: DaemonRuntimeDeps }; everything that names a platform runtime, provider runtime, interactor, host diagnostics, credential fingerprinting, resource cleanup, lifecycle owners, or the platform-services / claim-recovery compositions moves to the new root composition module src/daemon-composition.ts, which src/daemon.ts calls and injects. The operational server keeps startup sequencing, listeners, shutdown, metadata watch, idle reap, and request-handler creation, with the same order on both paths. DaemonProviderCredentials (type only) lands in @agent-device/daemon-contracts/provider-credentials — the #2559 daemon⇄client shared home beside daemon-policy-file.ts; contracts was rejected under R18 (the fingerprint implementation keeps reading the plugin manifest in root, and the type is daemon⇄client vocabulary, not app-domain vocabulary). Runtime tests boot through one shared fixture (src/daemon/__tests__/daemon-runtime-fixture.ts). R76 loses its 8 daemon-runtime rows (only the direct-ios-selector edge from #2273/#2274 remains); ADRs 0019/0022/0033 amended. 30 files.

Validation

Commit c3a81fd. Local: pnpm install --frozen-lockfile && pnpm build, pnpm format, check:quick, check:layering (320/0), check:di-seams, eager-closure budgets (802/0; src/daemon.ts closure 468→469 modules = the new composition file itself, weight 5117→5116 — containment + novelty + flat weight, the split tolerance; every base module stays eager), test-file-size-ratchet, test:integration:provider (213), test:concurrency-torture (4), check:affected --run, daemon-scope unit run (462 files / 3455 tests). pnpm depgraph: spine back-edges 0, type-only inversions 11 (unchanged). Edge query over graph.json from daemon/** nodes to non-packages/ nodes outside the zone: exactly daemon/direct-ios-selector.ts -> platform-runtime-apple-resources.ts [value]. Smoke: node bin/agent-device.mjs devices + session list against the composed daemon, clean daemon stop. Provider integration/coverage run remotely on this head.

View guided diff Turn on auto-fix

@github-actions

github-actions Bot commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Size Report

Metric Base Current Diff
Installed (including dependencies) 5.17 MB 5.17 MB +1.5 kB
Package (unpacked) 5.17 MB 5.17 MB +1.5 kB
Package (download) 1.55 MB 1.55 MB +906 B

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 17.6 ms 18.5 ms +0.9 ms
CLI --help 48.1 ms 51.4 ms +3.3 ms

@thymikee

Copy link
Copy Markdown
Member Author

The daemon refactor looks sound, but one test gap needs fixing before merge. At c3a81fd, 18 of 18 checks pass, and I know of no conflicts.

The test fixture at src/daemon/tests/daemon-runtime-fixture.ts:26 copies the body of the src/daemon.ts IIFE almost word for word. It repeats resolveDaemonPaths, loadDaemonPolicy, new SessionStore, the assertShutdownAllowed derivation and the three session callbacks. About 25 runtime tests now run through this copy, and no test runs the real daemon.ts assembly. Three edits to daemon.ts would leave the suite green: dropping assertShutdownAllowed, so a policy that denies device-shutdown stops being enforced; giving startDaemonRuntime a different SessionStore than the one the deps closures read; or mis-wiring openWebSessionNames. A regression in the production composition root would ship unnoticed, and "exactly as the process root assembles it" in the PR body only describes the copy. The rule: the process root and every runtime test must call one assembly function, and nothing else may build the SessionStore and deps pair. Please move the daemon.ts IIFE body into one exported function beside daemon-composition.ts, for example startComposedDaemonRuntime({ env, exit?, stdout?, stderr?, registerProcessHandlers? }). daemon.ts then only calls it and keeps its catch. The fixture then becomes a re-export or goes away. Then please run the daemon-scope unit tests to confirm they pass through that one function.

Could the pair go away entirely if the server took a factory instead of built deps? The server would keep owning paths, the policy load (with its typed stderr and exit) and the SessionStore. It would call options.composeDeps({ env, baseDir, sessionsDir, daemonPolicy, <session callbacks> }), and the root would pass createDaemonRuntimeDeps, as it already does for createCommandSurface. daemon.ts would shrink to startDaemonRuntime({ composeDeps: createDaemonRuntimeDeps }), tests would pass the same function, and the SessionStore mismatch, the second resolveDaemonPaths and the daemonPolicy round-trip could not be wrong. assertShutdownAllowed would be derived in the server, which already imports daemon-policy. This seems to fit ADR 0019 as amended here, since the server owns sequencing and the root names runtimes. Putting DaemonProviderCredentials in daemon-contracts looks right, because a src/daemon home would add a client-to-daemon type edge against #2559.

Not blocking, take or leave: notifySkippedProviderRuntimes at src/daemon-composition.ts:151 repeats emitDaemonDiagnostic, so exposing skippedProviderRuntimes as data on the deps and letting the server emit provider_runtime_skipped would keep log emission in one place. DaemonRuntimeDeps declares leaseLifecycleProvider and providerRuntimeIds twice, and the server reads some members through deps.requestRouter (lines 382, 446, 484, 703), so each fact could be declared once at the top level. The doc comment in provider-credentials.ts:8 says the client reads this vocabulary, but no daemon-client module does; the real reason is that the root fingerprint module, which the client imports, then has no type edge into src/daemon. Five test files, starting with daemon-runtime-device-claims.test.ts:1, have a platformResourceCleanup import placed above the node: imports and a stray blank line before platformCleanup:, which pnpm format should fix.

I did not run the layering, eager-closure or fallow gates, so the R76/R9 results and the 468 to 469 closure claim rest on the PR body and green CI. I also did not reproduce the live devices and session list smoke run or the provider integration run.

@thymikee
thymikee force-pushed the refactor/daemon-composition-root branch from c3a81fd to 3ebaa58 Compare October 11, 2026 17:42
@thymikee
thymikee marked this pull request as ready for review October 11, 2026 17:42
@thymikee

Copy link
Copy Markdown
Member Author

Rebased onto main (7e5ed4dbd) and took the factory shape; review fixes are in 8cfa76edb (gates commit stays last at 3ebaa5817).

  • One assembly (required): startDaemonRuntime takes composeDeps and keeps the paths, the ADR 0029 policy load (typed stderr + exit 1), the SessionStore, the session callbacks, and the assertShutdownAllowed derivation. daemon.ts is the factory call plus its catch; the fixture is a thin call through the real createDaemonRuntimeDeps, so ~25 tests and the entry now run the same assembly. The second resolveDaemonPaths and the daemonPolicy round-trip are gone. Proof: src/__tests__/daemon-composition-assembly.test.ts boots the real assembly with a policy denying device-shutdown and asserts the server-derived gate throws DAEMON_POLICY_DENIED/rule: capability, the callbacks read the server's own store instance, and an unreadable policy exits 1 before the factory runs. Mutation-checked: dropping the derivation or wiring a second SessionStore both turn it red.
  • skippedProviderRuntimes: now data on the deps; the server emits provider_runtime_skipped through emitDaemonDiagnostic. notifySkippedProviderRuntimes deleted; the existing logged-after-publication test still passes.
  • Declared once: leaseLifecycleProvider/providerRuntimeIds (plus recoverExpiredLease/recoverableProviderIds, the gateway, the cleanup surface, and the claim-recovery factory) moved to the top level of DaemonRuntimeDeps; the server no longer reads them through deps.requestRouter.
  • provider-credentials doc: reason corrected to the root fingerprint module having no type edge into src/daemon.
  • Five test files: import order and stray blank lines fixed (oxfmt doesn't sort imports, so it was done by hand).

Closure stays at main's 468: the entry reaches the composition through a function-scoped await import wrapper. Gates at 3ebaa5817: format, check:quick, check:layering, check:di-seams, eager-closure (802 pass), test-file-size-ratchet, test:integration:provider (213), test:concurrency-torture, check:affected --run all green. Smoke on the built daemon: start, devices, session list, SIGTERM stop with clean metadata removal. (daemon stop's pid-identity verification fails on this host against main's build too — pre-existing, not a regression.)

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 31 files

Reply with feedback, questions, or to request a fix.

View guided diff | Turn on auto-fix | Re-trigger cubic

Comment thread src/__tests__/daemon-composition-assembly.test.ts
Comment thread docs/adr/0022-daemon-platform-runtime-coupling.md Outdated
Comment thread src/__tests__/daemon-composition-assembly.test.ts Outdated
Comment thread docs/adr/0022-daemon-platform-runtime-coupling.md Outdated
@thymikee

Copy link
Copy Markdown
Member Author

At 3ebaa58 I found no blocking problems, and all 19 checks pass. This follows the earlier review at c3a81fd, whose evidence request is now met. I ran no gates myself. The layering, di-seams, eager-closure (the 468-module claim), fallow, provider integration and concurrency torture results rest on your report and green CI. I did not reproduce the built-daemon smoke or the daemon stop pid-identity failure you report on main. I read the assembly test assertions to judge regression validity, but I did not run the mutations you describe.

Not blocking, take or leave: daemonPolicy in DaemonRuntimeDepsInput is passed to the factory at https://github.com/callstack/agent-device/blob/3ebaa58/src/daemon/server/daemon-runtime.ts#L169, but createDaemonRuntimeDeps never reads it. The policy reaches the composition only through assertShutdownAllowed. Dropping the field and the matching argument in the composeDeps call, and keeping the test's gate assertion on assertShutdownAllowed, would remove the dead input.

Four Cubic threads still apply. The P2 on the assembly test not shutting down its runtime in finally is at #3431 (comment). It only leaves a listener open in a failing test and production is unaffected, but please add the shutdown in finally or reply on the thread before merge. Three P3 threads also apply: #3431 (comment), #3431 (comment) and #3431 (comment).

No conflicts are known. Apart from that P2 thread, nothing blocks merge; the three P3 notes are optional.

…cess root

startDaemonRuntime now receives its composed dependencies instead of
building them: provider runtimes, the platform gateway and inventory
gateways, request platform providers, host diagnostics, credential
fingerprinting, the platform lifecycle owners, the platform-services and
claim-recovery compositions, and the command-surface factory all move to
src/daemon-composition.ts, which src/daemon.ts composes and injects. The
operational server keeps startup sequencing, listeners, shutdown, the
metadata watch, idle reap, and request-handler creation, with the same
order on both paths. DaemonProviderCredentials moves to
@agent-device/daemon-contracts/provider-credentials, the #2559 shared
home beside daemon-policy-file; the fingerprint implementation stays in
the root. Tests boot the runtime through one shared fixture.

No production file under src/daemon/ now reads a non-package src/ module
except the direct-ios-selector edge owned by #2273/#2274.
@thymikee
thymikee force-pushed the refactor/daemon-composition-root branch from 3ebaa58 to 458a7de Compare October 12, 2026 06:42
…s as a factory

startDaemonRuntime now takes a composeDeps factory and keeps the daemon
paths, the ADR 0029 policy load (typed stderr and exit included), the
SessionStore, the session callbacks, and the assertShutdownAllowed
derivation in the server, so the process root and every runtime test
share one assembly and the second resolveDaemonPaths and the policy
round-trip go away. Skipped provider runtimes are data on the deps and
the server emits provider_runtime_skipped through emitDaemonDiagnostic;
the lease-lifecycle facts, the runtime gateway, the resource-cleanup
surface, and the claim-recovery factory are declared once at the top
level of DaemonRuntimeDeps. The fixture is a thin call through the real
factory, and a new assembly test proves a dropped shutdown gate, a
mis-wired store, or an unreadable policy cannot pass silently.
The daemon-runtime rows of the R76 inventory no longer describe tree
reality once the composition moved out of src/daemon/, so the stale
entries go and the planted tests retarget the one surviving classified
pair. daemon-composition is declared in the platform-runtime zone (a
composition, not an executable), and ADRs 0019/0022/0033 record where
the composition and the extracted credential type landed.
@thymikee
thymikee force-pushed the refactor/daemon-composition-root branch from 458a7de to 6513b04 Compare October 12, 2026 06:47
@thymikee

Copy link
Copy Markdown
Member Author

Rebased onto main (3417a77), and the open review items are addressed. Head is 6513b04.

  • Cubic P2 (assembly test leaves the runtime running if an assertion fails): both tests now keep the runtime handle outside try and shut it down in finally (9752bee).
  • Unused daemonPolicy input: removed from DaemonRuntimeDepsInput and the composeDeps call. The test still checks the policy gate through assertShutdownAllowed (9752bee).
  • Cubic P3s: the per-test rmSync calls are gone (9752bee). ADR 0022 now says "below", uses the src/daemon/direct-ios-selector.ts path, and credits refactor(daemon): move the runtime dependency construction to the process root (PR 6) #3431 instead of the internal plan label (6513b04, the chore(gates) commit, which stays last).

All four threads are resolved. On the rebased head these pass: build, check:quick, check:layering, the eager-closure and size-ratchet tests, the assembly and daemon-server tests (910), and check:affected --run.

Merge-order note: this PR and #3430 both edit the ADR 0022 amendment and the R76 inventory. #3430 removes the last direct-ios-selector row that this amendment mentions. Whichever merges second needs a small rebase, which I'll do.

@thymikee

Copy link
Copy Markdown
Member Author

Heads-up: #3430 is now stacked on this branch — your composition-edge retirement and its selector-edge retirement together empty the R76 inventory, and it collapses R76 to a plain ban on top of your head.

@thymikee

Copy link
Copy Markdown
Member Author

I reviewed 6513b04 and found no problems in the code. The notes from 3ebaa58 are addressed: daemonPolicy is gone from the deps input, the assembly tests now shut the runtime down in finally, and the ADR 0022 amendment wording and file path are corrected.

All 19 checks pass, and there are no conflicts. The four earlier Cubic threads are fixed at this head and already resolved.

I ran no gates or tests myself. I judged the shutdown behavior by reading daemon-runtime.ts and the test. The layering, inventory, eager-closure and fallow results rest on the green checks and your report. I did not review the upstream files that came in with the rebase.

@thymikee thymikee added the ready-for-human Valid work that needs human implementation, judgment, or maintainer merge label Oct 12, 2026
@thymikee
thymikee added this pull request to stack #3467 October 12, 2026 08:04
@thymikee
thymikee merged commit 1612e33 into main Oct 12, 2026
19 checks passed
@thymikee
thymikee deleted the refactor/daemon-composition-root branch October 12, 2026 09:36
@github-actions

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-10-12 09:36 UTC

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.

1 participant