The remaining Phase 3 Apple PlatformPlugin work (steps b + d) is now filed as
GitHub issues under umbrella #972, so the standalone progress plan is redundant
and a staleness hazard (it already drifted once re: cost.runnerRoundTrips).
- Remove plans/phase3-platform-plugin-progress.md.
- Repoint its references at the durable sources: perfect-shape.md (x3) and
ADR-0009 now link the Phase 3 tracking issue #972; the plugin.ts step-b facet
note points at ADR-0009 (+ issue #974). Design rationale stays in
perfect-shape.md and ADR-0009; live status lives in the issues.
The Step (c) request-count bullet claimed both the dev-only CI gate AND the
runtime `cost.runnerRoundTrips` surface were removed in #968. #970 restored the
public agent-cost field, so the bullet is stale/misleading for the next
Apple/agent-cost worker.
Split the bullet: the dev-only request-count CI gate (the #966 --debug ndjson
counter + smoke-ios assertion) stays removed (zero runner events on main runs);
the runtime `cost.runnerRoundTrips` agent-cost field (ResponseCost /
buildResponseCost over RUNNER_ROUND_TRIP_PHASES) is a separate pre-existing
surface, restored in #970, and remains part of the agent-cost contract.
* refactor: PlatformPlugin registry foundation + parity tests (Phase 3)
* refactor: trim PlatformPlugin step-a contract to implemented facets
Remove the speculative daemon-owned facets (providers/recording/appLog/perf)
from the PlatformPlugin type. The earlier 'recording' facet baked the
iOS-simulator provider seam (IosSimulatorRecordingRequest) into the contract and
could not represent the Android/web/macOS-runner/iOS-device-runner/stop-path
recording contracts, which need the daemon recording context. The step-a
contract now carries only what this slice implements and parity-tests:
id, platforms, familySelector?, createInteractor, discoverDevices, capability.
The facets are introduced in step (b) as platform-neutral, daemon-owned
wrappers, pinned by table-equivalence parity tests (plan updated).
* refactor: move errors/redaction/device into src/kernel — Phase 5 slice 3
Relocates the foundational primitive trio from src/utils/ into the kernel/ layer
(joining snapshot.ts from slice 2), per the target folder DAG in
plans/perfect-shape.md §5.5. A pure path codemod, no behavior change.
They form a closed cluster — device -> errors -> redaction, with redaction a
leaf — so kernel/ takes no upward dependency, and every importer becomes a clean
downward import toward kernel. errors.ts is the most-imported module in the
tree; device.ts the §5.5-named headliner. Moving all three atomically avoids a
half-state where one would import another across the utils/kernel boundary.
Imports rewritten by a resolve-based codemod (compares each specifier's resolved
path to the moved files, so the unrelated commands/management/device.ts and
other same-named files are untouched): 483 sites across 402 files. The two
platform-descriptor doc comments and the fallow health baseline key for
device.ts are updated to the new path; the contracts-schema-public guard that
asserts the error helpers pull no diagnostics/node: deps now reads kernel/.
Verified: tsc --noEmit, oxfmt + oxlint --deny-warnings, rslib build, full vitest
suite (2877 pass), fallow audit clean (411 changed files), Layering Guard empty;
kernel/ files import only within kernel.
* docs: update guidance references to kernel/{device,errors} after the move
AGENTS.md (Apple-family sync rule + normalizeError), ADR-0009, and
plans/apple-platform-consolidation.md still named the old src/utils/ paths.
Point them at src/kernel/. plans/perfect-shape.md's utils/device.ts mention is
left as-is — it describes the pre-move diagnosis.
Locks the two axis decisions and starts retiring plans/ into ADRs. ADR 0008
(Proposed) records the command-descriptor registry composing domain-owned facets
and deriving the ~10 tables, bound by ADR 0003's four invariants; ADR 0009
(Accepted, groundwork shipped in #896) records the AppleOS leaf axis under one
'apple' Platform. perfect-shape.md links both and marks Phase 0 + Tier-A dedup as
merged.
* docs: add perfect-shape architecture roadmap
Captures the target architecture (two-registry thesis: CommandDescriptor +
PlatformPlugin over a clean folder DAG with a typed-result spine) and a sequenced,
strangler-fig migration path, grounded in a survey of the current codebase.
This PR implements the first two behaviorless Phase-0 items from that roadmap; the
larger registry work is deliberately deferred to later, independently shippable PRs.
* refactor: parametrize RecordingBackend by recording tag
RecordingBackend is now generic over the recording's platform tag, so each
backend's stop() receives an already-narrowed recording. This deletes all five
'recording as Extract<ActiveRecording, { platform: ... }>' casts — the textbook
discriminated-union-narrowing-by-cast anti-pattern — and makes a backend/tag
mismatch unrepresentable.
start() stays wide (DaemonResponse | ActiveRecording) because a device platform
does not map 1:1 to a recording tag (an iOS device resolves to either the 'ios' or
'ios-device-runner' recording). Device resolution returns a stop-less view
(RecordingStartBackend); stop is dispatched per active recording via the new
exhaustive stopActiveRecording(), replacing resolveRecordingBackendForRecording().
Behaviorless: pure type-level change, no runtime behavior change.
* refactor: make capability platform selection exhaustive
isCommandSupportedOnDevice resolved the per-platform capability bucket with an
if/else ladder whose final branch funneled every unmatched platform into
capability.web. That silently absorbs a future Platform with no compile error.
Replace it with selectCapabilityForPlatform(), an exhaustive switch over the
Platform union with a 'never' guard, so adding a new platform is a compile error
here instead of a silent web mis-gate. Identical behavior for all five current
platforms (ios/macos -> apple, android, linux, web).
* docs(adr): amend ADR 0003 for the single-declaration/derivation model
Ratifies the PR review caveat into the ADR itself: the daemon command registry
boundary is about ownership + the predicate interface, not the physical file a trait
is typed in. A derived/projected daemon registry is permitted only if it preserves
four invariants (daemon-owned declaration, unchanged predicate interface, no leakage
into public projections, one declaration per concern enforced by types). The original
decision stands; collapsing daemon policy into a public command registry remains
forbidden.
* docs: refine command axis to facet composition (ADR 0003-aligned)
- §2/§5.2: CommandDescriptor composes domain-owned facets (surface@commands,
capability@core, daemon@src/daemon) and projects them — compose-with, not
collapse-into. Adds the four ADR-0003 invariants.
- §6: mark the two shipped Phase-0 items (generic RecordingBackend<P>, exhaustive
capability selection); link the Apple plan from Phase 3.
- §5.1: Apple as the first PlatformPlugin instance, owning an AppleOS leaf axis.
- §8: before/after diagrams for the command axis + the two-axis summary.
* docs: add apple-platform-consolidation plan (AppleOS leaf axis)
One 'apple' Platform with an AppleOS discriminant (ios/ipados/tvos/watchos/
visionos/macos) rather than six Platform literals (which would collide with the
cross-platform 'target' axis). Captures the 4-investigator survey: ~85% of
platforms/ios is already the OS-agnostic Apple engine; the XCTest runner already
builds ios|macos|tvos; macOS is included as a distinct AppKit leaf (already
entangled). visionOS is scoped net-new work; watchOS is an unsupported sentinel
(XCUITest can't drive it). Before/after diagrams, per-OS readiness, sequencing.