feat(runtime): register the managed local owner as an exact-only wrapper and add the neutral allocator port - #2259
Conversation
Size Report
npm unpacked components
Startup median (7 runs, lower is better):
Top changed chunks: no changes in the largest emitted chunks. Top changed packed files
|
|
BLOCKED at
The exact-only registry behavior and types are otherwise coherent. |
3d29f60 to
e2978a6
Compare
|
Fixed at e2978a6 (rebased onto #2258's blocking fix, then this fix on top). 1. Lazy-boot cells. Withheld 2. Rebase + Planted-red verified both: reverting the withheld-cells addition fails
|
…per and add the neutral allocator port ADR 0021 foundations, unit 3. Unit 1 added the `managed-local` owner kind and left the gateway's exact-owner arm for it failing closed; this unit gives that arm a registry and the owner it selects. Nothing in production registers a managed owner yet, so both are reached from tests only. - `createComposedPlatformRuntimeGateway` gains a `managedOwners` list that only the `managed-local` arm of `selectExactOwner` reads. `selectOrdinaryProvider`, `inspectFacts` and the ordinary `bind` arm never see it, and `providerModules` pairs one provider-runtime owner with one `ProviderDeviceRuntime`, so ordinary selection cannot reach a managed owner by construction rather than by a check. A duplicate instance is refused at composition. - The wrapper (src/platform-runtime-managed-owner.ts, root zone, no platform imports) binds only under an exact-owner intent naming itself, loads the device's own family owner through the gateway's loader, delegates with an ordinary intent — a family owner refuses a foreign exact owner — and republishes the binding under the managed owner. It does not read the fence: what a managed binding fence proves is the device-claim gate's business. `ownsDevice` returns false. - Twenty cells are withheld as `owner-capability-missing`, enumerated by mechanics rather than by catalog group: the four device-lifecycle cells, the four application cells that boot or shut the device down (`prepareApplicationOpen`, `prepareAppleRunner`, `closeApplication`, `finalizeApplicationClose`), and the twelve durable-capture cells, which a managed binding could never reattach because the family runtime stamps envelopes with its own local owner. The operations are then filtered by those facts, so an operation cannot outlive its own fact. - `@agent-device/contracts/managed-device-allocation` is agent-device's own allocator port: lease request, lookup, supersession, cancellation, renewal, release, activation confirmation, identity status, removal acknowledgement, and the typed environment projection. Types only, named to match the allocator's published contract so the two sides cannot drift, with no dependency on any allocator package. Its only implementation is a scripted fake under `*.fixtures.ts`. - Budgets: the new contracts entry surface is a one-module closure; the `src/platform-runtime.ts` hub moves 47 -> 48 for the wrapper, whose own value imports were already in that closure.
…d trim the allocator port Review findings on the managed local owner. - `deployApp` and `deployMaterializedApp` join the lifecycle group. Both family deployment runtimes ensure device readiness before installing, and `deployAppUse` requires `deployApp` alone — so `install` on a managed binding would have booted the allocator's device with nothing to refuse it. Twenty withheld cells become twenty-two, and the refused-uses test covers `deployAppUse`. - The wrapper's doc comment no longer implies that withholding cells is a complete lifecycle exclusion: several retained Apple cells (screenshot capture, settings, clipboard, application launch) boot the simulator lazily inside the family runtime, where cell selection cannot reach. That is the same class as the pre-binding readiness path, and closing it is a family-runtime change. - `readLeaseEnvironment` leaves the allocator port. It was beyond the vocabulary the contract fixes, and it made the scripted fake carry a real parser whose only test passed with every production line reverted. `ManagedLeaseEnvironment`, `ManagedLeaseEnvironmentKey` and `LeaseEnvironmentError` stay as types; the reader that produces them lands with the unit that first turns a grant into a device. - CONTEXT.md drops an operation enumeration that was already incomplete.
… cells Screenshot capture, settings, clipboard and application launch were retained on a managed binding even though their Apple family-runtime implementations can boot the simulator lazily below cell-selection granularity (screenshot's shutdown-failure retry boot; settings, clipboard and application launch each resolve a local interactor the same way). That preserves rather than blocks the exact bypass ADR-0021 section 3's hard boundary names: managed lifecycle/readiness belongs to the allocator, and no handler path may fall back to direct lifecycle tooling. Withhold captureScreenshot, setSetting, readClipboard, writeClipboard and openApplication alongside the existing withheld groups. The wrapper's doc comment now names the pre-binding readiness gap explicitly as the same class of follow-up, rather than folding it into a retained- cells caveat that no longer applies. MANAGED_RETAINED_OPERATION moves to tapPoint, the cell the fixture-based regression tests now use to prove something survives the wrapper.
c17b4b5 to
a8ecbd2
Compare
a3fd02a to
165d5c7
Compare
|
Code review CLEAN at this exact head. The lifecycle bypass is fixed across all lazy Apple cells, operation facts are filtered correctly, and the gateway regression now proves the transport-composed bind inherited from #2258. Exact-only registration, disposal, duplicate refusal, and the allocator port are coherent. Merge after #2258; two exact-head Smoke lanes are still running. |
|
…per and add the neutral allocator port (#2259) * feat(runtime): register the managed local owner as an exact-only wrapper and add the neutral allocator port ADR 0021 foundations, unit 3. Unit 1 added the `managed-local` owner kind and left the gateway's exact-owner arm for it failing closed; this unit gives that arm a registry and the owner it selects. Nothing in production registers a managed owner yet, so both are reached from tests only. - `createComposedPlatformRuntimeGateway` gains a `managedOwners` list that only the `managed-local` arm of `selectExactOwner` reads. `selectOrdinaryProvider`, `inspectFacts` and the ordinary `bind` arm never see it, and `providerModules` pairs one provider-runtime owner with one `ProviderDeviceRuntime`, so ordinary selection cannot reach a managed owner by construction rather than by a check. A duplicate instance is refused at composition. - The wrapper (src/platform-runtime-managed-owner.ts, root zone, no platform imports) binds only under an exact-owner intent naming itself, loads the device's own family owner through the gateway's loader, delegates with an ordinary intent — a family owner refuses a foreign exact owner — and republishes the binding under the managed owner. It does not read the fence: what a managed binding fence proves is the device-claim gate's business. `ownsDevice` returns false. - Twenty cells are withheld as `owner-capability-missing`, enumerated by mechanics rather than by catalog group: the four device-lifecycle cells, the four application cells that boot or shut the device down (`prepareApplicationOpen`, `prepareAppleRunner`, `closeApplication`, `finalizeApplicationClose`), and the twelve durable-capture cells, which a managed binding could never reattach because the family runtime stamps envelopes with its own local owner. The operations are then filtered by those facts, so an operation cannot outlive its own fact. - `@agent-device/contracts/managed-device-allocation` is agent-device's own allocator port: lease request, lookup, supersession, cancellation, renewal, release, activation confirmation, identity status, removal acknowledgement, and the typed environment projection. Types only, named to match the allocator's published contract so the two sides cannot drift, with no dependency on any allocator package. Its only implementation is a scripted fake under `*.fixtures.ts`. - Budgets: the new contracts entry surface is a one-module closure; the `src/platform-runtime.ts` hub moves 47 -> 48 for the wrapper, whose own value imports were already in that closure. * fix(runtime): withhold the deployment cells from a managed binding and trim the allocator port Review findings on the managed local owner. - `deployApp` and `deployMaterializedApp` join the lifecycle group. Both family deployment runtimes ensure device readiness before installing, and `deployAppUse` requires `deployApp` alone — so `install` on a managed binding would have booted the allocator's device with nothing to refuse it. Twenty withheld cells become twenty-two, and the refused-uses test covers `deployAppUse`. - The wrapper's doc comment no longer implies that withholding cells is a complete lifecycle exclusion: several retained Apple cells (screenshot capture, settings, clipboard, application launch) boot the simulator lazily inside the family runtime, where cell selection cannot reach. That is the same class as the pre-binding readiness path, and closing it is a family-runtime change. - `readLeaseEnvironment` leaves the allocator port. It was beyond the vocabulary the contract fixes, and it made the scripted fake carry a real parser whose only test passed with every production line reverted. `ManagedLeaseEnvironment`, `ManagedLeaseEnvironmentKey` and `LeaseEnvironmentError` stay as types; the reader that produces them lands with the unit that first turns a grant into a device. - CONTEXT.md drops an operation enumeration that was already incomplete. * fix(runtime): withhold the lazily-booting Apple system and screenshot cells Screenshot capture, settings, clipboard and application launch were retained on a managed binding even though their Apple family-runtime implementations can boot the simulator lazily below cell-selection granularity (screenshot's shutdown-failure retry boot; settings, clipboard and application launch each resolve a local interactor the same way). That preserves rather than blocks the exact bypass ADR-0021 section 3's hard boundary names: managed lifecycle/readiness belongs to the allocator, and no handler path may fall back to direct lifecycle tooling. Withhold captureScreenshot, setSetting, readClipboard, writeClipboard and openApplication alongside the existing withheld groups. The wrapper's doc comment now names the pre-binding readiness gap explicitly as the same class of follow-up, rather than folding it into a retained- cells caveat that no longer applies. MANAGED_RETAINED_OPERATION moves to tapPoint, the cell the fixture-based regression tests now use to prove something survives the wrapper. * chore: retrigger CI (stale synchronize event after rebase)
… runtime (#2258) * docs: trim the CONTEXT.md glossary within the guidance byte budget CONTEXT.md sat at 11,992 of its 12,000-byte guidance budget, so no new domain term could be added without first paying for it. - Condense eighteen definitions that had grown past one line (platform leaf, command surface, runtime use, runner command traits, interactor, coordinate-first resolved element activation, parent-owned touch point, guarantee cell, delegation-on-error, ref frame, snapshot producer, snapshot policy facet, capture hint, regular presented-depth frontier, clip fold, AX-unavailable target invalidation, Maestro program, Maestro observation generation). The definitions keep their meaning; only the elaboration is gone. - Move the five test-harness terms of 'Providers and tests' (provider-backed integration scenario, provider transcript, scenario transcript, in-process provider scenario harness, HTTP contract test) to docs/agents/domain.md, which AGENTS.md already routes to for domain vocabulary. None of them names a concept a command or a wire shape carries, and none appears in a test name. CONTEXT.md is 10,517 bytes after this pass. * feat(runtime): add the managed-local owner kind, device-claim rule, and managed binding fence ADR 0021 foundations, unit 1. Nothing registers a managed local owner yet, so every arm below is reached from tests only; the point of the unit is that the arms exist and fail closed. - `RuntimeOwnerRef` gains `{ kind: 'managed-local'; instance }` with `managedLocalRuntimeOwner`: one owner per allocator instance, family-agnostic because the device carries its family. Every owner-kind discrimination becomes an exhaustive switch, so a fourth kind is a type error at each site: the owner key, the unavailable-facts provider mode, the durable envelope decode, and the gateway's provider-mode acceptance and exact-owner selection. - `deviceClaimRuleForOwner` ('ordinary' | 'allocator-held' | 'none') in the new leaf src/daemon/device-claim-rule.ts replaces the boolean `isLocalDeviceClaimTarget`. Both claim gates switch on it, and the admission gate now evaluates it under every device-claim policy: the `transient-exclusive` condition moved inside the ordinary arm, so a managed owner is verified where an ordinary owner would never have touched the store. - `requireAllocatorHeldDeviceClaim` (src/daemon/device-claim-allocator.ts) is the one read-only verifier both gates consult. It never acquires, never locks and never clears; in this unit it can only answer `binding-invalid`, `missing`, or `conflict`, because no allocator-held claim kind exists until unit 2. `allocatorHeldAdmissionError` answers each outcome with its own refusal through an exhaustive switch, so an outcome the verifier learns to produce is a compile error until it is answered. - A missing allocator-held claim refuses with COMMAND_FAILED / `allocator-claim-missing`, `retriable: false`. It is deliberately not a `DeviceClaimConflictReason`: replay retries every conflict reason as infrastructure, and a managed identity no allocator activated is permanent. - `managedBindingFence` / `decodeManagedBindingFence` encode `[requesterId, identityIncarnationId]` as the fence token and the request generation as its generation, so two requesters on one identity incarnation never share a fence. The ids are fenced verbatim, and the decoder accepts a token only if it re-encodes to itself. - Claim admission now receives the binding intent the gateway bound, so an exact-owner fence reaches the gate unchanged. Session open still binds ordinarily and passes an ordinary intent: a managed local owner is therefore refused there structurally, and the Host open route replaces that intent when it lands. - CONTEXT.md: managed local owner, device-claim rule, managed binding fence, request generation, identity incarnation. * fix(daemon): decide allocator-held admission totally instead of by an optional error `allocatorHeldAdmissionError` returned `AppError | undefined`, so its switch without a default was never exhaustiveness-checked: TS2366 fires only when the return type excludes `undefined`, `noImplicitReturns` is off, and oxlint has no exhaustiveness rule. A verifier outcome nobody answered would therefore fall out as `undefined`, which both gates read as an admission — claim admission throws nothing and session open proceeds to open the session on a device it never verified. Replace it with `decideAllocatorHeldAdmission`, returning `{ admitted: true } | { admitted: false; error }`. The return type excludes `undefined`, so dropping an arm is now a compile error at the switch, and a gate asks whether the outcome was admitted rather than whether an error happened to come back. `buildAllocatorHeldRefusal` and the admission gate are projections of that one decision. * docs: restore the meaning five CONTEXT.md definitions lost in the trim The condensing pass shortened these five past the point where they still said what they meant: - Capture hint said 'presented depth' where the term is 'regular presented depth', which is what Regular presented-depth frontier is measured against; the short form read as a different axis. - Clip fold lost both that the interpreter runs inside presentation for every backend and that a platform difference may not enter as a backend exception. Those are the whole rule. - Snapshot policy facet lost the process boundary that makes it host-side at all: runner-side Swift presentation stays separate. - Runner command traits lost 'independently of the public command surface', which is what distinguishes them from the command surface. - Delegation-on-error said 'settles', and Settled observation makes 'settle' a term of its own. CONTEXT.md is 11,674 of its 12,000-byte budget. * docs(daemon): correct the claim-gate and managed-owner comments - The claim-gate docstring claimed there is no other way to obtain device operations. That is true of command handlers, but two daemon-owned recovery paths bind outside the seam: application-lifecycle-recovery.ts (ordinary intent, daemon shutdown) and durable-capture-runtime-recovery.ts (exact-owner intent read back from a durable envelope, which this unit makes able to carry a managed local owner). Name them instead of claiming coverage the seam does not have. - The open path's comment described a session executing under an allocator-held claim, a state this route cannot produce. Say what the `{ kind: 'ordinary' }` literal actually is: the truth of a route that binds ordinarily, which the Host open route replaces with the request's exact intent when it lands. - Name U3 as the unit that fills the exact-owner selection arm, rather than the whole ADR. * fix(runtime): accept transport-composed facts for a managed owner providerModeMatchesOwner's managed-local arm accepted mode === 'local' only, but selectExactOwner's managed-local arm loads the device's local family owner through the same loadLocal a local-family owner uses, so it inherits that owner's provider modes verbatim. A managed binding over a transport-composed local device (e.g. a remote ADB or web-provider transport) would fail bindingContractFailure's facts check and be rejected as an owner/facts mismatch. Accept the same local-family modes the local-family arm already does; still unreachable until U3 registers the exact-only owner, which is where the binding regression test that pins this lives. * feat(runtime): register the managed local owner as an exact-only wrapper and add the neutral allocator port (#2259) * feat(runtime): register the managed local owner as an exact-only wrapper and add the neutral allocator port ADR 0021 foundations, unit 3. Unit 1 added the `managed-local` owner kind and left the gateway's exact-owner arm for it failing closed; this unit gives that arm a registry and the owner it selects. Nothing in production registers a managed owner yet, so both are reached from tests only. - `createComposedPlatformRuntimeGateway` gains a `managedOwners` list that only the `managed-local` arm of `selectExactOwner` reads. `selectOrdinaryProvider`, `inspectFacts` and the ordinary `bind` arm never see it, and `providerModules` pairs one provider-runtime owner with one `ProviderDeviceRuntime`, so ordinary selection cannot reach a managed owner by construction rather than by a check. A duplicate instance is refused at composition. - The wrapper (src/platform-runtime-managed-owner.ts, root zone, no platform imports) binds only under an exact-owner intent naming itself, loads the device's own family owner through the gateway's loader, delegates with an ordinary intent — a family owner refuses a foreign exact owner — and republishes the binding under the managed owner. It does not read the fence: what a managed binding fence proves is the device-claim gate's business. `ownsDevice` returns false. - Twenty cells are withheld as `owner-capability-missing`, enumerated by mechanics rather than by catalog group: the four device-lifecycle cells, the four application cells that boot or shut the device down (`prepareApplicationOpen`, `prepareAppleRunner`, `closeApplication`, `finalizeApplicationClose`), and the twelve durable-capture cells, which a managed binding could never reattach because the family runtime stamps envelopes with its own local owner. The operations are then filtered by those facts, so an operation cannot outlive its own fact. - `@agent-device/contracts/managed-device-allocation` is agent-device's own allocator port: lease request, lookup, supersession, cancellation, renewal, release, activation confirmation, identity status, removal acknowledgement, and the typed environment projection. Types only, named to match the allocator's published contract so the two sides cannot drift, with no dependency on any allocator package. Its only implementation is a scripted fake under `*.fixtures.ts`. - Budgets: the new contracts entry surface is a one-module closure; the `src/platform-runtime.ts` hub moves 47 -> 48 for the wrapper, whose own value imports were already in that closure. * fix(runtime): withhold the deployment cells from a managed binding and trim the allocator port Review findings on the managed local owner. - `deployApp` and `deployMaterializedApp` join the lifecycle group. Both family deployment runtimes ensure device readiness before installing, and `deployAppUse` requires `deployApp` alone — so `install` on a managed binding would have booted the allocator's device with nothing to refuse it. Twenty withheld cells become twenty-two, and the refused-uses test covers `deployAppUse`. - The wrapper's doc comment no longer implies that withholding cells is a complete lifecycle exclusion: several retained Apple cells (screenshot capture, settings, clipboard, application launch) boot the simulator lazily inside the family runtime, where cell selection cannot reach. That is the same class as the pre-binding readiness path, and closing it is a family-runtime change. - `readLeaseEnvironment` leaves the allocator port. It was beyond the vocabulary the contract fixes, and it made the scripted fake carry a real parser whose only test passed with every production line reverted. `ManagedLeaseEnvironment`, `ManagedLeaseEnvironmentKey` and `LeaseEnvironmentError` stay as types; the reader that produces them lands with the unit that first turns a grant into a device. - CONTEXT.md drops an operation enumeration that was already incomplete. * fix(runtime): withhold the lazily-booting Apple system and screenshot cells Screenshot capture, settings, clipboard and application launch were retained on a managed binding even though their Apple family-runtime implementations can boot the simulator lazily below cell-selection granularity (screenshot's shutdown-failure retry boot; settings, clipboard and application launch each resolve a local interactor the same way). That preserves rather than blocks the exact bypass ADR-0021 section 3's hard boundary names: managed lifecycle/readiness belongs to the allocator, and no handler path may fall back to direct lifecycle tooling. Withhold captureScreenshot, setSetting, readClipboard, writeClipboard and openApplication alongside the existing withheld groups. The wrapper's doc comment now names the pre-binding readiness gap explicitly as the same class of follow-up, rather than folding it into a retained- cells caveat that no longer applies. MANAGED_RETAINED_OPERATION moves to tapPoint, the cell the fixture-based regression tests now use to prove something survives the wrapper. * chore: retrigger CI (stale synchronize event after rebase) * fix(runtime): lazy-load the managed owner wrapper to satisfy the eager-closure no-growth gate Main's eager-closure budget gate (the merge-base ratchet) replaced the hand-tracked HUB_BUDGETS map with an automatic no-growth-vs-merge-base check: src/platform-runtime.ts is a hub with no growth allowed at all, not a number bumped by hand with a justifying comment. The static import of createManagedLocalRuntimeOwner in platform-runtime-gateway.ts added one module to that hub's closure (47 -> 48), which now fails scripts/__tests__/eager-closure-budgets.test.ts outright rather than needing a manual bump. Move the value import into loadManaged's dynamic `await import`, matching how the rest of this file's owner loaders defer their leaf modules. Only the managed-local arm reaches this path, so an ordinary bind never pays for it, same as before -- the wrapper module itself was simply the wrong side of the eager/lazy line.
Summary
ADR 0021 foundations, unit 3. Stacked on #2258 (
adr0021/u1-owner-kind); U4 stacks on this.Unit 1 added the
managed-localowner kind and left the gateway's exact-owner arm for it failingclosed. This unit gives that arm a registry, the owner it selects, and agent-device's own allocator
port. Nothing in production registers a managed owner or calls an allocator, so every arm here
is reached from tests only; the point of the unit is that the arms exist and fail closed.
What changes for command authors and platform behavior:
managedOwners, is the only place a managed local owner can be registered,and only the
managed-localarm ofselectExactOwnerreads it. Ordinary selection cannot reacha managed owner by construction rather than by a check.
twenty-seven cells withheld as
owner-capability-missingand the operations filtered by thosefacts (R66). A command author sees the same
narrowDeviceBindingrefusal any unsupported cellproduces.
allocator package dependency, no production call site.
12 files touched, no scope growth beyond the runtime-gateway/contracts surface the unit names;
src/platform-runtime.tsis deliberately not edited (no production producer of managed ownersexists yet, so
createPlatformRuntimeGatewaygains no pass-through).The exact-only registry
createComposedPlatformRuntimeGatewaygains amanagedOwnerslist that only themanaged-localarm of
selectExactOwnerreads.selectOrdinaryProvider,inspectFactsand the ordinarybindarm never see it, and
providerModulespairs oneProviderDeviceRuntimewith oneprovider-runtimeowner — so ordinary selection cannot be handed a managed owner. A@ts-expect-errorpins that: a managed ref is not assignable to a provider module's owner. Aduplicate instance is refused at composition, an unregistered instance still answers
owner-unavailable, and a registered one is composed once.The wrapper
src/platform-runtime-managed-owner.ts(root zone, no platform imports) binds only under anexact-owner intent naming itself, loads the device's own family owner through the gateway's loader,
delegates with
{ kind: 'ordinary' }— a family owner refuses a foreign exact owner — andrepublishes the binding under the managed owner. It does not decode the fence: what a managed
binding fence proves is the device-claim gate's business, and a second reader would be a second
answer.
ownsDevicereturns false.Twenty-seven cells are withheld, enumerated by mechanics rather than by catalog group:
ensureReady,bootTarget,bootTargetHeadless,shutdownTarget, plusdeployAppanddeployMaterializedApp— both family deployment runtimes ensure readiness beforeinstalling, and
deployAppUserequiresdeployAppalone, so nothing else would have refused it;prepareApplicationOpen,prepareAppleRunner,closeApplication,finalizeApplicationClose;which a managed binding could never reattach because the family runtime stamps envelopes with its
own local owner;
captureScreenshot,setSetting,readClipboard,writeClipboard,openApplication— none isdeclared device lifecycle, but each Apple family-runtime implementation can boot the simulator
lazily below cell-selection granularity (screenshot retries through a boot on a shutdown failure;
the other three resolve a local interactor the same lazy way), which is exactly the implicit-boot
fallback ADR-0021 §3 rules out for a managed device.
A managed session cannot be opened in the foundations. A managed binding loses every command
whose runtime use requires a withheld cell:
install(and push-notification delivery),appsandapp-state,device bootanddevice shutdown,openandclose, and the durable capture starts(
logs start,record start, audio probe capture, native perf capture). That is the intended shapeof the foundations — the allocator owns the device's lifecycle — not a gap to work around in daemon
code.
Named out of scope
lazily-booting Apple cells named above are now withheld rather than left as a caveat — an earlier
revision of this PR retained them and disclosed the gap instead of closing it; review caught that
the disclosure preserved the exact bypass ADR-0021 §3 rules out, so the fix withholds them.
session-device-resolution→device-ready→platform-runtime-device-readyboots a devicethrough direct simctl/adb before any binding exists, gated only by provider ownership — below
where a managed binding's cell withholding can reach at all. The exclusion above covers binding
cells only, and the exclusion test says so.
once a managed owner is actually reachable from production (no unit before the allocation-flow
unit can exercise it end to end).
envelopes (adoption compares the envelope's owner key with the binding's). Same family-runtime
change; not attempted here.
under a managed binding — consistent with durable captures being unavailable. The delegation test
pins the choice, so forwarding a local-family exact intent later is a deliberate change.
The allocator port
@agent-device/contracts/managed-device-allocationis agent-device's own interface to amanaged-device allocator: lease request, lookup, supersession, cancellation, renewal, release,
activation confirmation, identity status, and removal acknowledgement. Types only, with no
dependency on any allocator package and no socket code. The member and field names match the
allocator's published contract (
requestGeneration,identityIncarnationId,expectedRequestGeneration,confirmLeaseActivation,getManagedIdentityStatus,acknowledgeManagedIdentityRemoval,admission: 'fail-fast',activation: 'direct' | 'external-fence',LEASE_ENVIRONMENT_INVALID { platform, key }, and the{ platform: 'ios', deviceSetPath } | { platform: 'android', adbServerPort }projection) so the twosides cannot drift.
Shaping notes:
— are modelled where the contract already puts them: a refusal reason and a request state on
LeaseRequestStatus. Protocol-version skew belongs to the unit that opens the socket.ManagedLeaseEnvironmentandLeaseEnvironmentErrorare declared but have no reader yet. Thereader, and the mapping from agent-device's own device families onto the port's
'ios' | 'android'vocabulary, land with the allocation-flow unit that first turns a grant intoa
DeviceInfo. Both carry the repo's one-linefallow-ignore-next-line unused-typeidiom ratherthan a baseline entry.
src/__tests__/test-utils/*.fixtures.ts.Pins moved
packages/contracts/package.json#exports,CONTRACT_EXPORTS, and aFACADE_BUDGETSrow of 1 (types only, so the entry evaluates itself).HUB_BUDGETS['src/platform-runtime.ts']47 → 48, measured: the composed gateway now composes thewrapper, whose own value imports (the owner contract, kernel errors) were already in that closure.
Validation
Evidence gathered at
e2978a6637(rebased onto #2258's blocking fixes; includes the withhold-cellsreview fix below). Published and reported — CI on the head is the authority.
pnpm check:affected --runfail-opens to the full 57-check set (the diff touches tooling files), andthe whole set passed at this commit: format, lint, typecheck, layering, di-seams,
check:fallow --base origin/main(clean over 34 changed files), build, package, integration-node,macOS command coverage, vitest-related, unit + smoke, provider-integration, integration-progress,
replay-compat, daemon-wire-compat, production-exports, bundle-owner-files, command-docs,
agent-guidance, xctest-selection, maestro-conformance, and the gate/mutation/coverage model suites.
The GitHub-authoritative lanes (device replay, Swift/Android helper builds, coverage, mutation, fuzz)
are skipped locally by design.
Two earlier full runs on this branch each aborted at
vitest-relatedon a different test timingout at the 5 s vitest limit under parallel load —
test/integration/provider-scenarios/ios-lifecycle.test.ts(4.9 s in isolation) andsrc/daemon/__tests__/gesture-admission-parity.test.ts(3.3 s in isolation, already flagged by theslow-test gate at a 2.5 s budget). Both pass in isolation, both are timeouts rather than assertion
failures, and neither is on a path this change touches.
check:affectedaborts at the first failedlane, so those two runs did not reach the lanes after
vitest-related; the clean third run did, andCI on the head is what settles it.
Regression tests, each with the revert that makes it fail (all executed as mutations against the
real code; every one went red):
deployApp/deployMaterializedApp,prepareAppleRunner, thedurable-capture cells, or the five lazily-booting cells (
captureScreenshot,setSetting,readClipboard,writeClipboard,openApplication) from the wrapper's list; returningbinding.operationsunfiltered;narrowDeviceBindingrefusals forappsRuntimeUse,appStateUse,bootTargetUse,shutdownTargetUseanddeployAppUse;on a foreign exact owner), dropping the owner rewrite (
bindAndValidatethen rejects), acceptingan ordinary or foreign intent, or not forwarding disposal;
managed-localarm, dropping the composition-timeduplicate check, or composing a fresh wrapper per bind (the second registration is then refused);
providerModeMatchesOwner'smanaged-localarm acceptingtransport-composed(fixed on feat(runtime): add managed-local ownership and the exact-only managed runtime #2258and rebased in here):
accepts a managed binding whose local facts report a transport-composed devicebinds through the gateway with atransport-composedlocal family device and assertssuccess — replacing an earlier version of this test that asserted the opposite (rejection), which
is what review caught: the arm rejected the mode the PR's own known-gap note said it should accept;
No device-facing behavior changes, so no simulator/emulator evidence is owed: nothing in production
can select a managed owner yet.