Skip to content

refactor(layering): one parametrized runtime-command-cutover gate (ADR 0019 §8) - #1745

Draft
thymikee wants to merge 8 commits into
mainfrom
refactor/adr19-parametrized-cutover-gate
Draft

refactor(layering): one parametrized runtime-command-cutover gate (ADR 0019 §8)#1745
thymikee wants to merge 8 commits into
mainfrom
refactor/adr19-parametrized-cutover-gate

Conversation

@thymikee

@thymikee thymikee commented Aug 11, 2026

Copy link
Copy Markdown
Member

Stacked on #1740 (refactor/adr19-platform-execution-discriminator), rebased onto its
current head. Merge order: #1740 first, then this PR gets a final rebase onto main
before merging.
The two are otherwise independent — nothing here reads the new none
discriminator, and no production source changes in this PR.

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.ts and their tests.

Added:

  • runtime-command-cutover-model.ts — the row model: a union discriminated on execution
    (inventory | device-runtime) whose enforcement claims are mandatory per kind, plus
    cutoverRowDefects for the cross-field claims types cannot require. An inventory row must
    name its gateway proof; a device-runtime row must name its runtime types, operations, and
    singular daemon route; every row declares a tier, and durable-resource requires a
    lifecycle proof while request-scoped forbids one. The gate validates each row before it
    scans 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 keeps
    the identity each cutover shipped with.
  • runtime-command-cutover-policy.ts — the one gate. Columns: retired modules/imports,
    legacy executable names, legacy provider methods, PlatformPlugin facets, capability
    admission (requireCommandSupported, descriptor buckets, static command sets, plugin
    admission 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.tscutover-policy-ast.ts (it now serves every row;
    RecordRuntimeProductionSourceProductionSource).

record-runtime-mechanics-policy.ts and record-runtime-registry-policy.ts stay separate
modules under R16 — they are record-specific structural scans, not cutover rows.

Assertions kept as per-row extensions (they do not generalize)

  • devices gateway binding — the handler must import listDeviceInventory from the
    neutral owner, must not shadow that binding, and must call it. Binding-identity proof;
    no other row routes through a named gateway binding.
  • logs app-log session-state ownership — whole-record replacement owner for
    appLog/appLogFailure. logs is the durable-resource pilot; request-scoped rows own
    no session record.
  • source-executed using declarations — not command-specific at all; it rides the
    logs 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). logs keys admission by computed property
    anywhere; network by PUBLIC_COMMANDS.network inside src/platforms/apple/plugin.ts.
    record declares none — PUBLIC_COMMANDS.record is live identifier-only data in the
    daemon session-event tables, so that form cannot discriminate for record.
  • operationNamePattern + nonNullRepairScope: 'any-operation' on logs, preserving the
    input-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:

  • network legacy names keep their src/daemon/-only scope (identifier-only use elsewhere
    is legitimate); logs/record keep their repo-wide scope.
  • descriptor-capability and static-command-set scans now run repo-wide for every row
    (previously network scoped them to two files) — a strict superset.
  • admission accepts 'cmd' and PUBLIC_COMMANDS.cmd for every row (previously
    literal-only for network) — a strict superset.
  • the logs narrowing checks moved from regex to AST (TSAsExpression/TSTypeAssertion),
    which adds old-style casts and stops matching prose. Every existing logs narrowing test,
    including the exact-line assertion, still passes.
  • the narrowing column applies only to rows that name a runtime type or operation, so the
    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:

  • logs had no singular-route proof; it now claims handleLogsCommand, verified
    singular in session-observability.ts.
  • record's daemon-mechanics scan is its lifecycle proof and moved out of check.ts
    into the row. Planting a setTimeout in record-runtime.ts confirms it fires exactly
    once, 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 mechanism
plus the row-completeness plants), runtime-command-cutover-table.test.ts (481 LOC, per-row
acceptance 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 devices row carries R17 device-inventory-cutover per #1750's renumber. Validated in
both merge orders with an audit over every RULE constant plus the table's per-row ids:

Singular execution for named operations (review follow-up)

DeviceRuntimeCutover is now a union: a named-operation row must declare
singularExecution.operations (typed NonEmpty<string>, so it cannot be omitted or left
empty), while a pattern-only row — logs, whose app-log plan selects operations from the
request — declares operations?: undefined and proves singularity through its route alone.
cutoverRowDefects additionally requires the enforced set to cover the named set, so
declaring 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.
network and record already enforced their full named sets: no production claim changed.

Validation

  • pnpm check:layering — 141/141 tests (was 136) and Layering guard: OK, all ratchets
    unchanged (R6 7, R7 33 fields, R9 46, R10 pins, R11 39 subpaths). No SessionState touch.
  • pnpm check:affected --run — all runnable checks passed; pnpm lint, pnpm format:check,
    tsc --noEmit clean.
  • Planted red (mechanism, in-repo test):
    the parametrized gate goes red on a planted row across every generalized column feeds a
    synthetic planted row plus violating sources and asserts all thirteen expected
    violations, 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.
  • Planted red (live wiring): appending
    const widened = runtime as BoundDeviceRuntime; widened.operations["networkDump"](); and
    RECORDING_BACKENDS_BY_TAG.android to src/daemon/app-log.ts makes check.ts exit 1
    with 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)

@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown

Size Report

Metric Base Current Diff
JS raw 2.18 MB 2.18 MB +2.1 kB
JS gzip 713.0 kB 713.2 kB +211 B
npm tarball 839.1 kB 839.3 kB +268 B
npm unpacked 2.92 MB 2.92 MB +2.1 kB

Startup median (7 runs, lower is better):

Scenario Base Current Diff
CLI --version 26.9 ms 27.4 ms +0.5 ms
CLI --help 66.7 ms 68.4 ms +1.7 ms

Top changed chunks:

Chunk Raw diff Gzip diff
dist/src/sdk-batch-runner.js +1.9 kB +158 B
dist/src/cli.js +187 B +53 B

@github-actions

github-actions Bot commented Aug 11, 2026

Copy link
Copy Markdown
PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://callstack.github.io/agent-device/pr-preview/pr-1745/

Built to branch gh-pages at 2026-08-11 17:11 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

@thymikee
thymikee force-pushed the refactor/adr19-parametrized-cutover-gate branch 2 times, most recently from 4f9af91 to 4a31e6d Compare August 11, 2026 15:52
@thymikee

Copy link
Copy Markdown
Member Author

Reviewed exact head 4a31e6d4. I found no production behavior change, and the four existing cutover claim families appear preserved, but three readiness blockers remain.

  • P1 — incomplete rows can pass silently. MigratedCommandCutover makes every enforcement column optional, while rowViolations simply skips absent columns. A row containing only { rule, command, subject } is green, so ADR 0019 §8’s promise that a new unit “adds a row” and inherits the mechanism is not omission-safe. Please make the row model discriminated/validated with mandatory claims for its execution kind/tier (including mandatory singular execution proof), and add a planted incomplete-row test that fails. The current planted row proves only a fully populated row.
  • P1 — stacked dependency refactor(registry): exhaustive platformExecution discriminator (ADR 0019 §6) #1740 must fix occurrence attribution before rebase. Its CLI-route coherence scan collapses dispatches to command-name sets, so one attributed runtime dispatch can mask a second, unrelated/unattributed runtime occurrence. Track dispatch occurrence identity/location, add the attributed-plus-duplicate-unattributed planted red, land refactor(registry): exhaustive platformExecution discriminator (ADR 0019 §6) #1740, then rebase this PR onto that fixed head.
  • P2 — test module remains over the extraction tripwire. The newly created runtime-command-cutover-policy.test.ts is 608 LOC. Tests are not exempt; split the generic mechanism coverage from coherent per-row/table acceptance coverage into matching modules while preserving shared helpers and the ported cases. Production module sizes are otherwise within budget.

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.

@thymikee

Copy link
Copy Markdown
Member Author

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.

@thymikee
thymikee force-pushed the refactor/adr19-parametrized-cutover-gate branch from 4a31e6d to bf67324 Compare August 11, 2026 17:11
@thymikee

Copy link
Copy Markdown
Member Author

All three addressed; rebased onto the fixed #1740 head (4c1c42979) first so the occurrence-identity change is underneath this one.

P1 incomplete rows. You're right that all-optional columns made §8's "add a row, inherit the mechanism" untrue — {rule, command, subject} enforced nothing and read green. MigratedCommandCutover is now a union discriminated on execution (inventory | device-runtime) with mandatory claims per kind: an inventory row must name the gateway proof that is its singular-execution evidence, and a device-runtime row must name its runtime types, its operations (exact set or family), and its singular daemon route. Rows also declare a mandatory tier, and durable-resource requires a lifecycle proof while request-scoped forbids one. Non-empty tuple types catch "declared but empty" at compile time; cutoverRowDefects catches what types cannot — and reads defensively, since a row omitting whole claim objects is exactly its input. The gate runs it per row before it scans the repo, so an under-declared row reports (<command> cutover row): cutover row declares no ... instead of passing. The planted incomplete row is a permanent test, alongside four narrower defect plants (retires nothing, empty singular execution, durable without lifecycle proof, request-scoped claiming durable machinery) and an assertion that every shipped row is defect-free.

Two real gaps surfaced while filling the mandatory columns: logs had no singular-route proof at all, so it now claims handleLogsCommand (verified singular in session-observability.ts), and record's daemon-mechanics scan became its lifecycle proof, moving out of check.ts into the row — I confirmed by planting a setTimeout in record-runtime.ts that it still fires exactly once, not twice. The registry-join scan stays its own module since it is a descriptor-shape check, not a cutover claim.

P2 test split. 608 LOC became runtime-command-cutover-policy.test.ts (166, generic mechanism + row-completeness plants), runtime-command-cutover-table.test.ts (481, per-row acceptance with every ported case intact), and runtime-command-cutover-fixtures.ts (51, shared helpers and the planted row). 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, so I left it whole and am flagging it rather than hiding it.

Validation: pnpm check:layering 147/147 with the guard green, check:affected --run clean, tsc clean.

🤖 Addressed by Claude Code

@thymikee

Copy link
Copy Markdown
Member Author

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.

@thymikee

Copy link
Copy Markdown
Member Author

Re-reviewed exact head bf67324e. The prior whole-row fail-open and test-size findings are substantially fixed: incomplete top-level claims now fail, the policy/table tests are 166/481 LOC with shared fixtures, and the new logs-route and record-lifecycle proofs strengthen the migrated rows. Three blockers remain:

  • P1 — the branch recreates the R13 collision. Its current base already renumbered device-inventory cutover to R17, but the new table and header still declare R13 device-inventory-cutover. Change the row and all local references to R17 so it does not collide again with R13 platform-package-substrate; keep the namespace uniqueness gate green.
  • P1 — exact named operations can still omit singular-operation enforcement. DeviceRuntimeCutover.singularExecution.operations is optional even when operations.names is an exact nonempty list. A future row can name its runtime operations, omit their exact call-count proof, and pass validation. Require the singular operation set for named-operation claims (preferably the same set), while permitting route-only singularity only for pattern-only families such as logs, and add a planted incomplete-row red.
  • P1 — stacked refactor(registry): exhaustive platformExecution discriminator (ADR 0019 §6) #1740 still drops unknown dispatch targets. Occurrence attribution is now correctly source-offset based and has a duplicate-target regression, but dispatchTargetOf filters through knownCommand; an unknown literal or catalog target returns undefined, never enters allSites, and cannot become unattributed. Capture unknown dispatch occurrences as explicit failures and add a planted routed-known plus stray-unknown case in refactor(registry): exhaustive platformExecution discriminator (ADR 0019 §6) #1740. Land that fix, then rebase this PR.

All current CI is green and GitHub is CLEAN/MERGEABLE, but these structural gaps still block readiness. No production behavior defect was found.

thymikee added a commit that referenced this pull request Aug 11, 2026
…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.
@thymikee
thymikee force-pushed the refactor/adr19-parametrized-cutover-gate branch from bf67324 to 4dcfa58 Compare August 11, 2026 18:31
@thymikee

Copy link
Copy Markdown
Member Author

All three done; head for re-review is 4dcfa58ba, rebased onto #1740's 5b4f1e599 (the unknown-target fix).

1. Rule ID. The row and every local reference now read R17 device-inventory-cutover; the check.ts rule-map bullet says devices R17, and no R13 device-inventory string remains anywhere. I validated the namespace in both merge orders with an audit that reads every RULE constant plus the table's per-row ids:

  • refactor(layering): one parametrized runtime-command-cutover gate (ADR 0019 §8) #1745 first (what CI sees now): R13 resolves to platform-package-substrate alone — my half of your collision is gone. R11 still names both package-boundaries and contracts-implementation-authority, which is your other half and untouched by me; collisions go 2 → 1, never up.
  • refactor(layering): give each colliding rule id its own number #1750 first: I test-rebased onto 6b02dfe0a locally. It conflicts exactly where expected — you edit device-inventory-cutover-policy.ts, which this PR deletes (delete/modify), plus the check.ts rule-map bullet. Resolution is to keep the deletion and keep both bullets (your R18 contracts line, my parametrized-gate line, already numbered R17). After that the namespace is fully unique R1-R18 with zero collisions and check:layering is 150/150 green.

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. DeviceRuntimeCutover is now a union of two variants: a named-operation row must declare singularExecution.operations, typed NonEmpty<string> so it cannot be omitted or empty; a pattern-only row (logs, whose app-log plan picks operations from the request) declares operations?: undefined and proves singularity through its route alone. cutoverRowDefects enforces the same claim for as-cast rows and additionally requires the enforced set to cover the named set, so declaring three operations and enforcing one is rejected by name. Three planted reds cover it: enforces-none, enforces-some (names operations it does not enforce exactly once: plantedReattach), and a positive test that the pattern-only logs row is legitimately route-only. network and record already enforced their full named sets, so no production claim changed.

3. Rebase onto 5b4f1e599 is in the pushed head.

Validation: check:layering 150/150 with the guard green, check:affected --run clean, tsc clean. Left in draft — not requesting readiness this round.

🤖 Addressed by Claude Code

@thymikee

Copy link
Copy Markdown
Member Author

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 checkContractsImplementationAuthority(sources) call. The required rebase must preserve #1746’s typed LAYERING_RULES registry and scripts/layering/*.test.ts glob, then register the consolidated cutover exactly once. Only CodeQL ran on this head; authoritative CI evidence is absent.

[P2] Named-operation completeness is still one-way. namedOperationDefects() rejects named operations missing from singularExecution.operations, but extra or duplicate enforced operations pass even though the model says the enforced set must be the named set. Require symmetric set equality plus uniqueness and plant an extra-operation/duplicate red.

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.

@thymikee

Copy link
Copy Markdown
Member Author

Both fixed; head is 897e50553, rebased onto #1740's 09808532b, which itself sits on 057ab1c82 (#1746).

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 check.ts and package.json wholesale, which silently reverted #1746. Redone properly — #1746's artifacts are preserved and verified individually:

  • LAYERING_CONTEXT / LAYERING_RULE_IDS / LAYERING_RULES keyed registry intact; main() still runs Object.values(LAYERING_RULES).flatMap(...).
  • check:layering is back to the scripts/layering/*.test.ts glob, so my three new suites are discovered without registration (157 tests, up from 146 on main).
  • checkContractsImplementationAuthority appears exactly once — grep -c is 1.
  • check-wiring.test.ts passes, so catalog and registry still describe the same set.

The consolidation is registered as one entry: 'runtime-command-cutover' replaces the four keys logs-runtime-cutover, network-runtime-cutover, record-runtime-cutover and device-inventory-cutover, plus 'record-runtime-registry-join' for the descriptor-shape scan that stays its own module. Reporting order and the success line are unchanged.

Symmetric equality. namedOperationDefects now compares the two sets in both directions and rejects duplicates on each side: an unenforced name leaves an operation free to be called twice or never, and an enforced name the row does not claim asserts singularity over an operation the command does not own. Four planted reds: enforces-none, enforces-some, enforces-an-unnamed-operation, and duplicates on both sides.

Worth reporting: the duplicate plant failed on first run and caught a real bug in my check — I had written values.filter((value) => !seen.add(value)), and since Set.add returns the set that predicate is always false, so duplicates were never detected. The test went red exactly as a planted red should, which is the only reason it isn't still there.

Rule IDs unchanged from the last round: devices is R17, and the namespace audit still gives R13 → platform-package-substrate alone. The remaining R11 collision is #1750's contracts half, untouched here; both merge orders were re-validated last round and the reconciliation is unchanged.

Validation: check:layering 157/157 green, check:affected --run clean, tsc clean. Still draft, queued behind #1740.

🤖 Addressed by Claude Code

@thymikee

Copy link
Copy Markdown
Member Author

Adversarial re-review of exact head 897e50553: the prior row-completeness, symmetric named-operation, R17 allocation, and #1746 registry-preservation findings are fixed; current CI is fully green/CLEAN. One new structural blocker remains.

[P1] exactCallViolations() counts every runtime.operations.<operation> call across all src/daemon/** files (scripts/layering/runtime-command-cutover-policy.ts:429-441) without associating the call with this row's handler or the binding narrowed by that handler. An unrelated handler can therefore satisfy a row's required sole call, while a correct later unit that reuses an operation (for example screenRecordingCleanup) becomes a false duplicate. This makes the gate non-compositional precisely when wave 2+ adds rows. Scope singular-operation proof to the declared handler/binding identity, and keep a planted unrelated-handler call that must not satisfy or duplicate the row.

Please address independently, rerun pnpm check:affected --run, and keep draft. The PR also remains provisional behind corrected #1740 and must preserve the rule catalog when reconciling #1750.

@thymikee

thymikee commented Aug 11, 2026

Copy link
Copy Markdown
Member Author

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)
@thymikee
thymikee force-pushed the refactor/adr19-parametrized-cutover-gate branch from 897e505 to 800b6da Compare August 12, 2026 06:33
@thymikee

Copy link
Copy Markdown
Member Author

Rebased onto #1740's corrected head (a63a99f5c) and onto main including #1744. New head: 800b6da83. No local blockers outstanding; still queued behind #1740 and #1750.

Two things the rebase surfaced, both handled:

  • R19 survives. refactor: route selector-resolution structural stages into typed policy #1744's selector-pipeline-ownership is registered alongside the consolidated cutover in LAYERING_RULES; the registry, the glob, and the single contracts-implementation-authority entry are all intact (grep -c is 1). 166 layering tests, green.
  • The new collision gate caught my renumber's other half. KNOWN_RULE_ID_COLLISIONS allowed R13 names device-inventory-cutover and platform-package-substrate, and moving devices to R17 made that allowance stale — the gate failed with "no longer occurs, so the entry admits a collision nobody is fixing. Delete it." Exactly the expire-on-contact behaviour it documents. I deleted the R13 entry and left the R11 one for refactor(layering): give each colliding rule id its own number #1750's R18 rename, so the list still admits only what is actually present.

Current namespace with this head: R13 → platform-package-substrate alone, R17 → device-inventory-cutover, R19 → selector-pipeline-ownership, and R11 still shared pending #1750. When #1750 lands I will rebase again, delete the remaining allowance if #1750 has not already, and report the head.

Validation: check:layering 166/166 with the guard green, check:affected --run clean, tsc clean.

🤖 Addressed by Claude Code

@thymikee

Copy link
Copy Markdown
Member Author

Adversarial follow-up is fixed at exact head 6deff6845. Named runtime operations now declare a lexical owner, and the cutover gate counts each operation only inside that owner subtree; an unrelated daemon call can neither satisfy a missing owner call nor false-duplicate the correct call. The planted test covers both arms. The branch is rebased onto current main, preserves selector ownership/R19, and pnpm check:affected --run && git push passed on the exact head (481 files / 3,967 tests; changed-line and changed-branch coverage 100%). No production or live-device behavior changed.

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