refactor(layering): one parametrized runtime-command-cutover gate (ADR 0019 §8) - #1745
refactor(layering): one parametrized runtime-command-cutover gate (ADR 0019 §8)#1745thymikee wants to merge 8 commits into
Conversation
Size Report
Startup median (7 runs, lower is better):
Top changed chunks:
|
|
4f9af91 to
4a31e6d
Compare
|
Reviewed exact head
The PR is currently draft and MERGEABLE/UNSTABLE. Integration, Maestro, and release-selection lanes are green; Layering, Typecheck, Static, Coverage, Fallow, and several smoke/security lanes are still pending and remain authoritative merge gates. |
|
Prerequisite stack update: #1740 advanced from 8ad1292 to corrected head 4c1c429 after the gate finding. This PR is still based on the old #1740 head. Please replay #1745 onto 4c1c429, resolve the line-sized registry/gate conflicts, and rerun pnpm check:affected --run before merge/readiness. The five downstream ADR 0019 workers are intentionally holding publication until this corrected head is available. |
4a31e6d to
bf67324
Compare
|
All three addressed; rebased onto the fixed #1740 head ( P1 incomplete rows. You're right that all-optional columns made §8's "add a row, inherit the mechanism" untrue — Two real gaps surfaced while filling the mandatory columns: P2 test split. 608 LOC became Validation: 🤖 Addressed by Claude Code |
|
Confirmed new head bf67324 is correctly replayed onto #1740 head 4c1c429 (ahead by one, behind by zero). Fresh CI now has an Integration Tests failure while other lanes continue. Please independently inspect and fix or prove contention per docs/agents/testing.md, rerun pnpm check:affected --run before the next push, and keep this draft. Also note #1740 itself still has the unresolved unknown/computed-target scanner P2, so bf67324 remains provisional even after CI is green. |
|
Re-reviewed exact head
All current CI is green and GitHub is CLEAN/MERGEABLE, but these structural gaps still block readiness. No production behavior defect was found. |
…lper The catalog walked the AST with visitAst from record-runtime-policy-ast.ts, which #1745 renames to cutover-policy-ast.ts -- verified by trial merge: the import fails to resolve there. A guard-integrity check that breaks when a neighbouring policy is renamed is the fragility this rule exists to remove, and a rule catalog has no business depending on a cutover-specific module, so it collects string literals locally instead.
bf67324 to
4dcfa58
Compare
|
All three done; head for re-review is 1. Rule ID. The row and every local reference now read
Either order lands on the same map, so the renumber does not depend on who merges first. 2. Named-operation singularity. You're right that knowing the exact operation set and enforcing only the route left the operations free to be called twice or never. 3. Rebase onto Validation: 🤖 Addressed by Claude Code |
|
Reviewed exact head 4dcfa58. This branch is currently DIRTY/CONFLICTING after #1746, and the exact head restores the old hand-spread rule array and manual layering-test list, including the duplicate [P2] Named-operation completeness is still one-way. This stack also inherits #1740’s confirmed variable-envelope/computed-callee fail-open, so it cannot be ready ahead of a corrected #1740. Preserve the devices R17 / contracts R18 allocation when reconciling #1750. The split test/engine topology sizes themselves are within the project limits. No readiness label. |
4dcfa58 to
897e505
Compare
|
Both fixed; head is Rebase repair. You're right about what my previous head did, and I can see how: I resolved the earlier conflicts by taking my side of
The consolidation is registered as one entry: Symmetric equality. Worth reporting: the duplicate plant failed on first run and caught a real bug in my check — I had written Rule IDs unchanged from the last round: Validation: 🤖 Addressed by Claude Code |
|
Adversarial re-review of exact head [P1] Please address independently, rerun |
|
Reviewed exact head 897e505. The prior local blockers are fixed: the typed LAYERING_RULES registry and scripts/layering/*.test.ts discovery are preserved, each rule is registered once, named/enforced operations are validated symmetrically with duplicate rejection, and the test topology remains within limits. The branch is CLEAN/MERGEABLE and all authoritative CI is green. This is still not ready for human merge review:
Please land/fix #1740 and #1750, then rebase this stack while preserving the registry/glob and R17/R18 allocations. No ready-for-human label yet. |
ADR 0019 §6 (amended): every command descriptor declares its platform-execution
mode explicitly. Adds the `none` mode to `CommandPlatformExecution`, removes the
silent `{ kind: 'legacy' }` default at registry entry, and annotates all 76
descriptors so the migration denominator is machine-readable.
Part of #1739 (wave 0)
`react-devtools start` on a Limrun Android instance dispatches internal `runtime port-reverse`, which reaches a provider device runtime, so ADR 0019 §6 `none` is false for it. Reclassify as `legacy` and add the derived coherence gate that catches delegated platform execution: if a CLI route for command R dispatches command D, R may declare `none` only when D is `none`. Part of #1739 (wave 0)
Subtracting attributed command NAMES let a stray dispatch hide behind a routed one that names the same command, so the gate's totality claim did not hold. Dispatch sites now carry their source offset and attribution subtracts occurrences. Part of #1739 (wave 0)
An unknown literal or computed command target resolved to undefined and never entered the scan, so a dispatch could evade attribution by naming a target the gate could not read. Daemon-send envelopes are now located by their send call and an unresolvable target is reported instead of skipped. Part of #1739 (wave 0)
… seam The syntactic scan recognized only a direct sendToDaemon call whose first argument was an inline object literal, so a variable envelope or a computed callee was omitted from every result. Rather than teach the scanner more shapes, the CLI's injected dispatches now flow through one typed construction point whose route/command pairs are declared, and the gate reads that declaration instead of recovering it from syntax. Part of #1739 (wave 0)
…zed gate ADR 0019 §8: the per-command cutover gates consolidate into one parametrized runtime-command-cutover gate driven by a table of migrated commands. Adding a migrated command adds a row; the mechanism carries one planted-red proof instead of one per command. Part of #1739 (wave 0)
897e505 to
800b6da
Compare
|
Rebased onto #1740's corrected head ( Two things the rebase surfaced, both handled:
Current namespace with this head: R13 → Validation: 🤖 Addressed by Claude Code |
|
Adversarial follow-up is fixed at exact head |
What changed
ADR 0019 §8: "The per-command cutover gates consolidate into one parametrized
runtime-command-cutover gate driven by a table of migrated commands. Adding a unit adds a
row; the parametrized gate carries one planted-red violation for the mechanism, not one
per row. The four existing per-command policy files fold into it."
Deleted (1,161 LOC of policy + 604 LOC of tests):
device-inventory-cutover-policy.ts,logs-runtime-cutover-policy.ts,network-runtime-cutover-policy.ts,record-runtime-cutover-policy.tsand their tests.Added:
runtime-command-cutover-model.ts— the row model: a union discriminated onexecution(
inventory|device-runtime) whose enforcement claims are mandatory per kind, pluscutoverRowDefectsfor the cross-field claims types cannot require. An inventory row mustname its gateway proof; a device-runtime row must name its runtime types, operations, and
singular daemon route; every row declares a
tier, anddurable-resourcerequires alifecycle proof while
request-scopedforbids one. The gate validates each row before itscans the repo, so an under-declared row fails instead of enforcing nothing.
runtime-command-cutover-fixtures.ts— shared test helpers and the planted row.runtime-command-cutover-table.ts— four rows:devices(R17, per refactor(layering): give each colliding rule id its own number #1750),logs(R14),network(R15),record(R16). Rule ids stay per row, so the layering report keepsthe identity each cutover shipped with.
runtime-command-cutover-policy.ts— the one gate. Columns: retired modules/imports,legacy executable names, legacy provider methods,
PlatformPluginfacets, capabilityadmission (
requireCommandSupported, descriptor buckets, static command sets, pluginadmission members), runtime narrowing (widened assertions, non-null repair, bracketed
operation access), and exactly-one route/operation counts.
runtime-command-cutover-extensions.ts— the three assertions that do not generalize,kept as row extensions rather than dropped (see below).
record-runtime-policy-ast.ts→cutover-policy-ast.ts(it now serves every row;RecordRuntimeProductionSource→ProductionSource).record-runtime-mechanics-policy.tsandrecord-runtime-registry-policy.tsstay separatemodules under R16 — they are record-specific structural scans, not cutover rows.
Assertions kept as per-row extensions (they do not generalize)
devicesgateway binding — the handler must importlistDeviceInventoryfrom theneutral owner, must not shadow that binding, and must call it. Binding-identity proof;
no other row routes through a named gateway binding.
logsapp-log session-state ownership — whole-record replacement owner forappLog/appLogFailure.logsis the durable-resource pilot; request-scoped rows ownno session record.
usingdeclarations — not command-specific at all; it rides thelogs row because logs shipped it and R14 is part of the existing report surface. This is
stated in the code.
Two more per-row differences are declared as table columns rather than flattened:
admissionMember(form + file scope).logskeys admission by computed propertyanywhere;
networkbyPUBLIC_COMMANDS.networkinsidesrc/platforms/apple/plugin.ts.recorddeclares none —PUBLIC_COMMANDS.recordis live identifier-only data in thedaemon session-event tables, so that form cannot discriminate for record.
operationNamePattern+nonNullRepairScope: 'any-operation'onlogs, preserving theinput-dependent app-log operation family and the broader daemon-wide non-null scan the
logs policy shipped with (proved by its own test).
Why this is behavior-neutral
No production source changed — this is gate-side only. Detection was preserved
column-by-column and every ported test is a one-to-one copy of a deleted one (31 tests,
each old case still present). Where scopes were unified they were widened, never
narrowed, and the widened gate is green on the real tree:
networklegacy names keep theirsrc/daemon/-only scope (identifier-only use elsewhereis legitimate);
logs/recordkeep their repo-wide scope.(previously
networkscoped them to two files) — a strict superset.'cmd'andPUBLIC_COMMANDS.cmdfor every row (previouslyliteral-only for
network) — a strict superset.TSAsExpression/TSTypeAssertion),which adds old-style casts and stops matching prose. Every existing logs narrowing test,
including the exact-line assertion, still passes.
inventory row (
devices) keeps having no narrowing check, exactly as before.The success-line wording changed (
each migrated command (devices, logs, network, record) keeps exactly one platform-execution path) and violation messages are now parametrized;the rule ids, files, and lines are unchanged in shape.
Row completeness (review follow-up)
Two gaps surfaced while filling the now-mandatory columns, both closed here:
logshad no singular-route proof; it now claimshandleLogsCommand, verifiedsingular in
session-observability.ts.record's daemon-mechanics scan is its lifecycle proof and moved out ofcheck.tsinto the row. Planting a
setTimeoutinrecord-runtime.tsconfirms it fires exactlyonce, not twice. The registry-join scan stays a separate module — it is a descriptor-shape
check, not a cutover claim.
Tests split per review:
runtime-command-cutover-policy.test.ts(166 LOC, generic mechanismplus the row-completeness plants),
runtime-command-cutover-table.test.ts(481 LOC, per-rowacceptance with every ported case intact),
runtime-command-cutover-fixtures.ts(51 LOC).The table test is over the 300 target and under the 500 tripwire; splitting it further would
separate ported cases from the rows they accept.
Rule-ID namespace (#1750 interaction)
The
devicesrow carriesR17 device-inventory-cutoverper #1750's renumber. Validated inboth merge orders with an audit over every
RULEconstant plus the table's per-row ids:platform-package-substratealone. Theremaining R11 collision (
package-boundaries+contracts-implementation-authority) isrefactor(layering): give each colliding rule id its own number #1750's other half and untouched here — collisions go 2 → 1, never up.
6b02dfe0alocally. It conflicts where expected —refactor(layering): give each colliding rule id its own number #1750 edits
device-inventory-cutover-policy.ts, which this PR deletes, plus the check.tsrule-map bullet. Keeping the deletion and both bullets yields a fully unique R1-R18
namespace with
check:layering150/150 green.Singular execution for named operations (review follow-up)
DeviceRuntimeCutoveris now a union: a named-operation row must declaresingularExecution.operations(typedNonEmpty<string>, so it cannot be omitted or leftempty), while a pattern-only row — logs, whose app-log plan selects operations from the
request — declares
operations?: undefinedand proves singularity through its route alone.cutoverRowDefectsadditionally requires the enforced set to cover the named set, sodeclaring three operations and enforcing one is rejected by name. Planted reds cover
enforces-none and enforces-some, plus a positive test that logs is legitimately route-only.
networkandrecordalready enforced their full named sets: no production claim changed.Validation
pnpm check:layering— 141/141 tests (was 136) andLayering guard: OK, all ratchetsunchanged (R6 7, R7 33 fields, R9 46, R10 pins, R11 39 subpaths). No
SessionStatetouch.pnpm check:affected --run— all runnable checks passed;pnpm lint,pnpm format:check,tsc --noEmitclean.the parametrized gate goes red on a planted row across every generalized columnfeeds asynthetic
plantedrow plus violating sources and asserts all thirteen expectedviolations, including the row extension; a sibling test asserts the same row is green once
the command has one execution path, and a third asserts rows are independent.
const widened = runtime as BoundDeviceRuntime; widened.operations["networkDump"]();andRECORDING_BACKENDS_BY_TAG.androidtosrc/daemon/app-log.tsmakescheck.tsexit 1with 6 violations under R14/R15/R16 (
widened logs runtime type assertion,bracketed network operation access,expected one narrowed networkDump call, found 2,legacy recording route RECORDING_BACKENDS_BY_TAG, …). Reverted before commit.Net −167 lines. The gate engine is 466 LOC (under the 500 tripwire, with section banners);
it replaces four 257–335 LOC policy files.
Docs: none needed — no user-facing or CLI surface; ADR 0019 §8 already specifies this gate.
Part of #1739 (wave 0)