AGENTS.md routed request cancellation/progress and diagnostics to
`@agent-device/capture-kit` subpaths that no package exports; both live in
`@agent-device/host-kit/request` and `@agent-device/host-kit/diagnostics`. It also
named `@agent-device/contracts` as an importable seam although that package
publishes no root export, and claimed `src/daemon/handlers/session.ts` was over
budget after that extraction already landed at 242 lines.
Two ADRs carried number 0019. The hop trace has its own claims to make, so it now
numbers 0023, joins the index, and keeps the links from ADR 0019 and ADR 0022.
The Node floor split was undocumented: `engines.node` stays at 22.12 because CI
installs the published tarball on that floor, while contributors need 22.13 for the
pinned pnpm. CONTRIBUTING now says so, and installation.md names the 22.12 floor and
the web backend's Node 24 requirement.
Extend the agent-guidance contract to resolve every `@agent-device/*` specifier
AGENTS.md names against the owning package's `exports`, root included, so neither a
phantom subpath nor a phantom package root can route an agent to a module that does
not exist.
* docs(adr): record the #2278 coupling audit and re-trace entry-to-platform hops (ADR 0022)
* docs(adr): re-trace hop tables with spine side-calls excluded from hop counts
* docs(adr): renumber R74 references to R76 after the main merge
* refactor(capture-kit): relocate snapshot and recording zones into capture-kit
Move the ADR 0019 end-state capture zones into @agent-device/capture-kit:
- src/snapshot/** -> packages/capture-kit/src/snapshot/** (presentation,
freshness, scroll-edge-state, ios-snapshot-runtime, android occlusion)
- src/recording/** -> packages/capture-kit/src/recording/**
- src/core/snapshot-{chrome,state,tree-ingestion,node-lookup}.ts ->
packages/capture-kit/src/
- src/snapshot-quality/ test -> capture-kit presentation tree (directory
retires with its last file)
Pure renames: import re-pointing and gate updates follow in the next commit.
The snapshot-desktop-surface test parks in src/__tests__/ because it pins
the root eager-import-closure walker.
* refactor(capture-kit): re-point capture and recording consumers to the new subpaths
Rewires every consumer of the relocated snapshot/recording modules to the new @agent-device/capture-kit subpath exports, adds the 23 subpath entries to the capture-kit exports map, fixes the moved recording-scripts test's __dirname-relative paths for the deeper location, and records the completed migration in ADR 0019's end state.
* chore(gates): align layering, mutation, fallow and CI gates with the capture-kit relocation
Moves the executable-policy roots, presentation-owner constant, zone ranks, authority fixture, mutation sharding globs, stryker aliases, fallow baselines and the iOS workflow's android-owned paths-ignore entry onto the new packages/capture-kit paths, and extends the planted-red coverage to the new presentation-owner subpath.
* chore: point capture-domain source-of-truth comments at the relocated capture-kit modules
* test: point shutdown recording mock at capture-kit and cover interactor acquisition presentation
* test(capture-kit): update upstream presentation test imports
* chore(gates): follow relocated snapshot assembly in R74
* test(daemon): freeze prewarm deadline assertion clocks
* refactor(daemon): inline the touch sub-switch into the interaction dispatcher
interaction-touch.ts was a 31-line pass-through re-switching on
press/click/longpress/hover/fill only to call the same five handlers
handleInteractionCommands already imports transitively. Inline the five
cases into the existing switch and delete the file, dropping one hop
from the press/click/longpress/hover/fill dispatch path.
Point the response-construction-guard sentinel at
interaction-touch-press.ts (a file that still exists) so it keeps
watching for a hand-rolled responseData branch across the touch
handler set.
* fix(daemon): close ADR-0011 guard gap and refresh the stale hop trace
PR review findings on #2302:
- The ADR-0011 responseData guard scanned files by an
interaction-touch*.ts name prefix, which stopped covering the touch
dispatch switch once it moved into interaction.ts. Added a targeted
second check that extracts the press/click/longpress/hover/fill case
bodies from interaction.ts and scans them for hand-rolled
responseData, without widening the whole-file scan onto
interaction.ts (which also hosts the unrelated `type` command's
legitimate local responseData construction). Planted red: reverted
one switch case to a hand-rolled responseData construction twice
(press, then click) and reran
`npx vitest run src/daemon/interaction/internal/__tests__/interaction-response-construction-guard.test.ts`;
both times the new test failed with the expected
"Hand-rolled interaction responseData found in the touch dispatch
switch" message, then passed clean again after reverting.
- docs/adr/0019-end-state-hop-trace.md pinned itself to a commit and
claimed the press/Android route was untouched by this PR, but the
PR's whole point is merging two of that table's traced files
(interaction.ts, interaction-touch.ts). Merged the corresponding
table rows, renumbered the remaining hops, updated the file/class
summary counts and every downstream hop-range reference in the same
doc, and did the same for the one line in
docs/adr/0019-request-bound-platform-runtime.md that also cited the
now-stale 24-hop figure.
* test(daemon): hold the touch dispatch switch to structural delegation
The ADR-0011 guard extracted each touch case body with a regex that stopped
at the first nested case/default, so a hand-rolled responseData placed after
a nested switch was invisible. Parse interaction.ts with oxc-parser instead
and require every press/click/longpress/hover/fill case to be exactly one
return await <handler>(...) where the handler is imported from an
interaction-touch*.ts module. Inline planted-red cases cover the nested-switch
shape, an inline literal, a foreign callee, and a missing command.
* style(daemon): oxfmt the interaction response construction guard
* test(daemon): fold the guard's delegation check under the complexity threshold
* docs(adr): add ADR-0019 end-state numbers for the daemon composition root
Record the measured daemon top-level file count (199) and entry-to-platform
hop counts (press/Android 38, snapshot/iOS 29) at e624ef9d3f, propose targets
(<=60 files, <=14 hops), and name the src/ zones still awaiting a package
owner, as inputs to a maintainer decision.
* docs(adr): correct ADR-0019 end-state capture-kit inventory and hop counts
PR #2242 review: the capture-kit inventory sentence claimed the package held
only ios-snapshot-engine, but it already owns recording, screenshot/diff,
snapshot quality/occlusion, audio/app-log, and iOS acquisition mechanics per
ADR §1's amendment; replace it with a file-verified inventory of the root
mechanics that actually still lack a package equivalent.
The 38/29 hop counts had no ordered chain, counting definition, or artifact.
Add docs/adr/0019-end-state-hop-trace.md with both routes re-traced
file-by-file at HEAD (24 hops each, matching the independent
maintainability-review-2026-09-02 trace to within one file) and link it from
the ADR. Relabel the <=14 target "proposed, unverified" rather than force a
derivation the snapshot/iOS runner-protocol hops don't clearly support.
* docs(adr): keep hop evidence self-contained
* refactor(platforms): sink the shared src/platforms root files into their substrate homes (#2082 W3)
The shared files left directly under src/platforms move onto a declared
owner: provisioning mechanics (install-source family, toolchain probing,
boot-failure classification, app-resolution caching) form
@agent-device/provision-kit above capture-kit; host mechanics resolve to
host-kit's seams; kernel takes the pure numeric helpers; contracts keeps
vocabulary only.
Settings parsing, command-attempt rendering, and the unsupported-interactor
factory stay with their families rather than pooling in a substrate
package: android and apple settings each own their parsing, and the
unsupported-interactor factory lives in root core with a vega-local copy.
A platforms-root-shape rule rejects any new shared file or directory
appearing directly under src/platforms, and the provision-kit direction
gates (no platform imports in, no capture-kit importer) are planted red.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018VngeKZH6zBuJzNBk5YzUH
* test: cover the family-owned parsers and the unsupported-interactor factories
The settings parsers and the unsupported-interactor factory arrived without
owning tests, so their branches rode on callers. Each now has one: the
appearance/state parsers over every accepted spelling and their rejections,
the attempt summarizer over its arg join and stderr budget, and both
interactor factories over the whole operation surface and the per-instance
label.
Also drops the duplicate ./snapshot-desktop-projection export key that a
rebase left in capture-kit's manifest, where JSON silently keeps the last,
and the root-shape docblock the violation message already states.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018VngeKZH6zBuJzNBk5YzUH
* docs: keep tool directives only in the touched files
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018VngeKZH6zBuJzNBk5YzUH
---------
Co-authored-by: Claude <noreply@anthropic.com>
* refactor: split generic host mechanics into @agent-device/host-kit (#2082 W1)
The shared src/utils closure that blocked the platform-family moves lands
on declared owners: generic host mechanics form a new private
@agent-device/host-kit package between kernel and capture-kit, and
capture-kit keeps capture, snapshot, and recording behavior, depending on
host-kit for the mechanics it needs. tar-stream and yauzl move with the
archive code.
Every seam's exported subpaths are pinned in package-boundaries.test.ts,
the layering model ranks the new zone, R13's allow-list names it, and each
seam carries an exact eager-closure row. ADR-0019's substrate amendment
describes the layout.
Tests that mocked two of the moved modules separately became duplicate
same-seam vi.mock factories, where the second silently replaced the first;
those are merged, and the mocks that production code reaches past are
pinned at their injection points instead.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018VngeKZH6zBuJzNBk5YzUH
* refactor(host-kit): one narrow capability port per export
The four technical barrels (exec/fs/values/request) grouped by category
rather than by capability, so a consumer needing one mechanic evaluated
unrelated ones. Each export is now a single capability over the host
machine: command, process, diagnostics, retry, archive, file, request,
version. A port re-exports only what a consumer of that capability uses,
and every port carries its own eager-closure row.
Most of the old values barrel was never host mechanics. Pure record
readers, config-source values, result text, memoization, async scoping,
coordinate validation, and device-scope parsing touch no process, file, or
environment, so they join kernel's other primitives instead.
Closures fall accordingly: capture-kit's png-worker-client from 20 to 10,
png-resize from 28 to 18, session-teardown from 79 to 68, and the CLI from
386 to 380.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018VngeKZH6zBuJzNBk5YzUH
* chore: drop the migration inventories and trim the touched comments
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018VngeKZH6zBuJzNBk5YzUH
* docs: trim the touched host-kit and mutation-lane comments
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018VngeKZH6zBuJzNBk5YzUH
* docs: keep tool directives only in the touched files
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018VngeKZH6zBuJzNBk5YzUH
* docs: keep tool directives only across the touched tree
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018VngeKZH6zBuJzNBk5YzUH
* fix: point the Swift parity comment at the real TS twin and test
The W1 move rewrote this citation to packages/contracts/src/mobile-snapshot-semantics.ts,
which does not exist: the module went to capture-kit while isTapPointInsideViewport itself
went to packages/contracts/src/snapshot-visibility.ts. The TS test line was left pointing at
the pre-move path. Both now resolve.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018VngeKZH6zBuJzNBk5YzUH
* fix: repoint comment citations at the homes this refactor moved them to
The W1 move left ~20 comment citations pointing at src/utils/*.ts and
src/request/*.ts paths that no longer exist. Each now names the capability
port that owns the symbol, which survives further file moves:
exec -> host-kit/command host-process, owner-identity -> host-kit/process
diagnostics -> host-kit/diagnostics atomic-file, process-lock -> host-kit/file
retry -> host-kit/retry request progress/cancel -> host-kit/request
version -> host-kit/version ttl-memo, source-value, parsing, device-isolation,
keyed-lock, success-text -> kernel subpaths
Comment-only; no closure, budget, or behavior change. ADR citations are left
as written, being dated records of the decision rather than live references.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018VngeKZH6zBuJzNBk5YzUH
---------
Co-authored-by: Claude <noreply@anthropic.com>
* refactor(android): extract adb executor and IME cluster into packages/platform-android (#2041)
Implementation moves behind an injected adb host port (R13-clean); shared
vocabulary moves to contracts (android-touch-plan, android-helper-artifacts)
and kernel (keyed-lock); root keeps thin re-export shims plus the composition
wiring that binds the port. session.ts/session-observability.ts no longer
import platform transport types (opaque unknown, R62 pattern).
* refactor(android): trim shims to consumed surface, break helper-install cycle, add gate coverage
Fallow-clean: shim re-exports carry only names root still consumes;
helper-package-install imports the package subpath directly (no cycle) and
reuses the contracts decision types; transitional R13 table gets planted-red
tests; ADR-0019 records the transitional exception; eager-closure rows added
for the new entry surfaces.
* review: restore maxBuffer on spawn options, scope the transitional test allowance
Adversarial review round 1: AndroidAdbSpawnOptions keeps maxBuffer for spawn-
signature width parity with the pre-move ExecBackgroundOptions; the R13 test
allowance narrows from any test file to the cluster's own src/platforms/android
__tests__ directory, with planted-red coverage for a foreign test file; ADR
text records the scoped allowance.
* review: keep an unbound adb host port loud in listAndroidAdbSerialsQuick
* style: format policy test
* refactor(android): split the extracted adb and IME modules into focused owners
Review: the moved adb-executor.ts (652 lines) and ime-lifecycle.ts (487 lines)
carried three concerns each across the package boundary. The entry subpaths are
now thin surfaces over focused modules — adb: transport vocabulary, failure
classification+enrichment, provider normalization, provider scope/routing,
port-reverse ownership, pull/install transfers; IME: ownership state, on-device
settings record, activation transaction, restore+orphan recovery — every module
under 250 lines, each with colocated tests over an in-memory host-port stub and
a shared fake IME device. R31's fence invariant repoints to ime-activation.ts;
eager-closure pins move to the split shape.
* rebase: reconcile the transitional android table with the #2050 mechanics-facet model
The R13 declaration check now composes both enumerated subpath sets on the
exact-list model #2050 introduced; the policy test fixture declares android's
transitional subpaths the same way it declares apple's runner facet.
* refactor(apple): colocate the XCUITest runner client into packages/platform-apple (#2040)
Moves src/platforms/apple/core/runner/ (34 modules + apple-runner-platform.ts and
the 30 runner test suites) into packages/platform-apple/src/runner/ — Apple
mechanics live in the Apple package. Host capabilities (exec, diagnostics,
retry, process probes, locks, Apple tooling, physical-device control) enter
through the package-owned AppleRunnerHost port; the root composition module
src/platforms/apple/core/runner-client.ts constructs the client exactly once
and re-exposes the bound operations under their historical names.
R13 admits the transitional state deliberately: the family exports its root
façade plus exactly the enumerated ./runner, ./runner/client, and
./runner/test-host subpaths; the ./runner façade subpath is the recorded #1983
seam for unmigrated root consumers; ./runner/client has one composition root
and ./runner/test-host one vitest installer; the runner subtree may own its
cache files and sockets while raw process primitives stay banned. When #1983
completes, the subpaths and every subtree exemption are deleted and the family
returns to a single implementation-lazy façade export.
* docs(adr): model the runner subtree as a durable platform-owned facet
Review correction on #2050: the sunset story attributed the runner-consumer
migration to #1983, which owns snapshot/presentation vocabulary — not the
runner's daemon/root consumers — so that event cannot delete the ./runner
subpaths or the subtree exemptions. Reword ADR-0019, R13, and the gate
comments: the facet is the intended ownership model, its seam is enumerated
and pinned (exact export list, one client composition root, one test-host
installer, raw-process ban, eager-closure pins), and the seam narrows only
if a real runner-consumer migration retires the direct consumers. The
declaration mechanism stays apple-specific until another family needs a
mechanics facet. No behavior change; identifiers and comments only.
* refactor(daemon): migrate audio onto the request-bound device runtime (R60)
Wave 6 closure unit 1 of 2 for #1739. The audio probe leaves legacy execution
for exact-owner runtime facts with the logs/record durable treatment:
- contracts: audio-probe-runtime operations (audioProbeStart/Reattach/Cleanup
for the durable host capture, audioProbeQuery for the stateless web page
probe), audio-runtime-plan action-selected uses + shared positional grammar,
audio-probe-runtime-host seams; two new required unavailable cells.
- capture-kit: one shared host-capture implementation (descriptor codec v1
with cleanup-only recovery, start pipeline waiting on the sampler's first
status publication, live handle, exact-identity recovery operations) used by
both darwin-hosted owners.
- owners: apple and android state their exact capture cells (macOS host only;
the legacy bucket's physical-iOS over-claim becomes a stated refusal), web
owns the page probe, all other families and both providers state refusals.
- daemon: audio-probe.resource.json envelope via the DurableCaptureResource
coordinator, fence-minting admission ledger, session slot becomes the
neutral handle+envelope pair (store-owned under R7), teardown/close finish
through the coordinator, startup recovery registered in the device-claim
reconciler; the handler admits by facts inspection and binds once per plan.
- deleted: the capability bucket, the WEB_QUERY_COMMANDS graft and its
matrix pass, both supportsByDefault closures, src/daemon/audio-probe.ts,
src/platforms/audio-probe-backend.ts, and the macOS backend shim.
- gates: cutover row R60 (durable tier, lifecycle proof on the session slot),
R7/R11 baselines moved for the slot reclassification and new subpaths.
Known parity delta, itemized: status/stop with no active probe now answers
without backend-specific notes (the daemon no longer knows a backend before a
capture starts); the wire shape is otherwise unchanged.
Co-Authored-By: Claude <noreply@anthropic.com>
* refactor(daemon): doctor becomes host-scoped execution behind the host-diagnostics surface (R62)
Wave 6 closure unit 2 of 2 for #1739, carrying the ADR-level discriminator
decision the pre-unit record names: CommandPlatformExecution gains
{ kind: 'host' } — host-scoped diagnostics contributed by platform families
through a neutral surface, binding no device runtime of its own. 'none'
remains barred as a migration target; doctor's device legs already ride the
migrated inventory gateway, facts inspection, and the apps unit's use.
- contracts: host-diagnostics facet (toolchain/device/ambient/warmup methods
over the existing DoctorCheck vocabulary, a per-call context carrying the
neutral inputs, and an opaque provider transport override the one owning
family narrows back); the discriminator assert, entry gate (host is held to
the same no-bucket rule as none), and error text extend to the new kind.
- probes move to their owning families: apple (xcodebuild/xcode-select +
runner-cache warmup), android (adb/SDK/license toolchain, Metro reverse,
orphaned test-IME), harmonyos (hdc), vega (tool provider + VVD inventory
read via the context), web (managed browser census); one shared
first-line probe helper. Wire shapes and check ids are byte-identical.
- composition: createHostDiagnostics with per-family lazy loading, injected
from daemon-runtime through router/chain/session params — the daemon never
imports the composition root, so no transitive platform edge returns.
- daemon: session-doctor.ts keeps orchestration only; its six platform
imports and the three probe-owning modules
(session-doctor-{toolchain,android,web}.ts) are deleted.
- gates: HostCutover row variant with a gateway-identity proof (R17's shape);
R62 row; the descriptor-row completeness test now mandates rows for host
descriptors; ADR 0019 rules-at-a-glance amended.
Co-Authored-By: Claude <noreply@anthropic.com>
* refactor(audio): fold the per-family probe factories into one capture-kit operation set
Post-migration fallow pass over R60/R62: apple and android carried
byte-identical audio-probe operation factories, so the shared
createHostAudioProbeCaptureOperations now lives in capture-kit and both
bind arms call it directly; the family files keep only their stated
capture facts. Also un-exports the plan/recovery symbols nothing
consumes anymore, splits the durable-descriptor field validation out of
the decoder, names the Linux audio refusal by its file's convention, and
drops a stale session-doctor-web mock from the relocated doctor test.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cd7fCKcxuDU3KrXF4MF9rN
* fix(coverage): repoint platform audio/doctor coverage claims at the migrated evidence
The R60/R62 rewrite renamed the daemon audio contract tests and moved the
web doctor suite, which broke the macos-coverage smoke gate on CI and five
sibling coverage claims that only fail on non-darwin hosts. Repoints the
macOS, web, and iOS-simulator manifests at the surviving tests (adding an
iOS-simulator start contract the manifest already named), converts the
Linux audio row from capability-denial to a fact-owned contract backed by
new stated-refusal assertions in the Linux runtime denominator test, and
retires the darwin-dependent capability special-cases: audio admission is
owned by the exact-owner runtime fact now, not the catalog.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cd7fCKcxuDU3KrXF4MF9rN
* fix(daemon): forward hostDiagnostics through the session command route
Adversarial review of the R62 cutover found the wiring gap that broke
every doctor request served through the daemon: handleSessionCommands
re-composed its handler params without the hostDiagnostics gateway, so
requireHostDiagnostics always threw. Forwards it, composes
createHostDiagnostics() in the provider-scenario harness the same way the
daemon runtime does (all nine doctor integration tests pass again), merges
a duplicate test import that failed lint, applies oxfmt to the files the
branch left unformatted, and trims blank-line residue from a deleted
capabilities test.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cd7fCKcxuDU3KrXF4MF9rN
* fix(audio): refuse to publish a capture without an exact process identity
Review P1 on the R60 unit: start permitted resolveManagedProcessIdentity
to yield no marker, recovery then mapped the markerless descriptor to
missing, read a possibly in-progress status file as completed, and
terminalized the durable resource without proving or terminating the
child. The marker is now required end to end: start terminates the helper
and fails when the process exposes no identity, the descriptor codec
rejects markerless bodies so a foreign or corrupted record routes to
manual recovery instead of a guessed outcome, and a planted-red
regression pins that a markerless record with a live status file is never
read as completed or missing.
Also splits the capture-kit audio-probe module along its concerns
(descriptor codec / status reads / cleanup-only recovery / live-process
owner) per the same review, moving the eager-closure budget rows the
three new files cost.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cd7fCKcxuDU3KrXF4MF9rN
* fix(audio): let timed probes complete and never adopt a stale status file
Live exact-head evidence on macOS surfaced two lifecycle defects:
- The helper's runAudioProbe parked the CLI's main thread in a semaphore
while an unstructured Task ran the capture loop; with no run loop ever
spinning in the one-shot process, the loop stalled at its first
Task.sleep suspension, so every timed probe froze after its first
bucket and never completed on its own. The probe is now fully
synchronous: ScreenCaptureKit's completion-handler APIs bridged
through semaphores (the pattern the helper's screenshot path already
uses in production) and a plain Thread.sleep cadence loop.
- startHostAudioProbe accepted a pre-existing audio-probe.json, so
restarting in a session that had already run a probe returned the
previous run's snapshot as the new probe's first status. The status
path is cleared before the spawn — the previous handle's finish() has
already terminated and awaited its process, so any file observed after
the spawn was written by the new helper. A planted-red regression pins
that a stale file is neither adopted nor left behind.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cd7fCKcxuDU3KrXF4MF9rN
* test(audio): complete runtime host fixtures
* fix(audio): surface helper death after a running checkpoint instead of completing it
Review P1 on the live-evidence pass: the live handle discarded the
helper's terminal result, so a sampler that died after publishing a
running checkpoint read as running forever and stop fabricated a normal
stopped completion; marker-missing recovery compounded it by finalizing
any persisted status — a running checkpoint included — as completed.
The handle now observes process.wait: a non-terminal status file plus an
observed exit fails status and stop with the helper's exit detail, so
the durable coordinator records the failed terminal transition and the
record resolves through descriptor cleanup rather than a fabricated
result. Recovery treats only a terminal stopped publication as a
completion; a running checkpoint with the exact PID gone reports
missing, which terminalizes the envelope as already-missing with no
completion metadata. Planted-red regressions cover both: the live handle
with a running checkpoint plus child exit, and a daemon-restart recovery
over an orphaned running checkpoint.
Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Cd7fCKcxuDU3KrXF4MF9rN
---------
Co-authored-by: Claude <noreply@anthropic.com>
* chore(layering): document R12/R18/R19, retire R8, make R9 shrink mandatory (#1781 A6)
The A6 review kept `check:layering` in full (15/15 planted violations fired,
no other enforcer exists) and left four follow-throughs.
R12 bin-alias-fast-path, R18 contracts-implementation-authority and R19
selector-pipeline-ownership were live rules with no ADR or CONTEXT anchor —
they now carry one each, in the same list as R7/R9/R10/R13.
R8 zero-dep-job-closure is retired: no CI job sets `install-deps: false` and
ci.yml records why each keeps it enabled, so the invariant has no subjects.
R11's relative-into-packages exception existed only because a zero-dep closure
cannot coexist with specifier loads, so it retires with R8; the route is now
closed to every caller. R1 was retired the same way at #1490.
R9 was growth-only and merely suggested lowering the ceiling, which is
headroom the next change spends without a number moving. It is now an equality
pin like R6 and the R10 R7 counts, and the committed baseline drops 47 -> 46
(daemon-server ceiling 17 -> 16) to match the measurement.
ADR 0019 §6 now says each runtime-command-cutover row is deleted when that
command's migration is declared closed.
* chore(layering): rename R9 to type-cycle-size now that it fails both ways (#1781 A6)
* refactor(layering): give each colliding rule id its own number
R11 and R13 each named two unrelated rules. report() groups violations by the
rule string and titles every annotation `Layering drift (${rule})`, so a shared
number made the guard's output ambiguous about which rule fired.
Reference counts decided which rule keeps its number. R11 package-boundaries is
named in ~30 places (CONTEXT.md, ADR 0019, testing.md, the mutation and
affected-check configs, four package source comments, its own tests) against one
for the contracts rule; R13 platform-package-substrate is the RULE in three
policy files plus CONTEXT.md, ADR 0019 and model.ts against two for the devices
cutover. Both keepers stay put and the two newest rules move up:
R11 contracts-implementation-authority -> R18
R13 device-inventory-cutover -> R17
R17/R18 follow the namespace's order-of-addition convention (R14 #1701 < R15
#1702 < R16 #1724): device-inventory-cutover landed in #1699 and
contracts-implementation-authority in #1701. #1656 took R19 for
selector-pipeline-ownership on the same reading.
The rule-map header in check.ts is renumbered and reordered back into numeric
order, and gains the R18 entry the contracts rule never had -- without it a
reader looking up an R18 violation finds nothing where they used to find the
wrong rule. deviceInventoryCutoverSummary() was also the only OK-line summary
not leading with its rule number, which is what made the number unreadable from
the success line in the first place.
Also corrects a normative ADR reference. ADR 0019's platform-package import
rules -- contracts-to-platform, sibling-platform, root/daemon, raw-process --
are R13's, as CONTEXT.md:420 already says. The R11 attribution predates
platform-package-policy (#1697, a day before #1699), when R11 was the only
package rule.
* chore(layering): retire the expired R11/R13 collision allowances
KNOWN_RULE_ID_COLLISIONS was opened for exactly the two collisions the previous
commit renames apart, and ruleIdCollisionFailures expires an allowance on
contact: once the collision is gone the entry fails as stale, because a list
still naming it would wave it back through if anyone reintroduced it.
Both entries are therefore deleted in the change that removes the collisions,
leaving the empty list that admits nothing. The namespace is now one-to-one
across R2-R19.
Amends ADR 0019 for migration beyond the adoption checkpoint:
- status: recordings command unit recorded as completed; no further
command unit is authorized by Status — subsequent units are planned,
budgeted, and authorized through the successor tracking issue
- section 6: explicit none platform-execution mode with invariants; the
cutover gate applies to platform-executing descriptors only and rejects
a false none declaration; silent registry-entry mode defaults prohibited
- section 8: move-dominated size accounting, deprecated surfaces die on
legacy at the next major, two evidence tiers, one parametrized cutover
gate, capability buckets deleted per unit
- section 9: one bind per handler, side-effect-free facts inspection,
single defineUse, preferred operations require a recorded measurement
- section 10: cross-cutting facets land with their first consuming unit,
evidence-gated startup recovery, two-phase gateway shutdown, teardown
steps ride their owning domain, all-edge-kinds end-state layering rule
over production daemon modules