feat(daemon): add the allocator-held device claim kind - #2263
Conversation
Size Report
npm unpacked components
Startup median (7 runs, lower is better):
Top changed chunks:
Top changed packed files
|
|
BLOCKED at It is Coverage is also an owner-action failure: new Current completed checks are green; iOS is in progress. Size is +3.7 kB unpacked and needs growth justification after the functional gate is fixed. Do not label ready. |
690041c to
0cbc751
Compare
|
Rebased onto #2258's blocking fix and fixed at 0cbc751. Rebase. device-claims.ts no longer writes its claim file directly — it delegates to Same-switch conflict from #2258's rebase. Also carried forward the earlier should-fix rename (
|
0cbc751 to
ff74cdd
Compare
|
BLOCKED. |
|
Re-reviewed at |
ADR 0021 foundations, unit 2. A device an allocator-managed pool owns is held for that identity's
whole pool lifetime, not for a session, so the claim store gains a second record kind whose
principal is an installation instead of a process. Nothing writes one in production yet.
- `AllocatorHeldDeviceClaim` is a SEPARATE record at `schemaVersion: 3` with `kind: 'allocator'`:
`stateDir` + `allocator.instanceId` + `allocator.identityIncarnationId`, and no
ownerPid/ownerStartTime/ownerToken/session/workspace/abandonedAtMs — a record carrying any of
them does not decode. `DeviceClaim` and its v2 files are untouched, so an older daemon never
meets a changed process-owned record and only ever sees a v3 file for a managed identity, which
it reads as an unreadable claim record and fails closed on.
- The managed owner is DERIVED from the recorded allocator instance (`allocatorHeldClaimOwner`),
never stored, so an owner that disagrees with the claim's principal cannot exist.
- `InspectedDeviceClaim` becomes a union: the allocator member carries `allocatorClaim` and
`claim: undefined`. Every clearing surface — ownership match, abandon, stale release, the
startup sweep, acquire-path reconciliation, session close, lease expiry, the shutdown ledger,
and `processOwnsActiveDeviceClaim` — reaches for `claim`, so none of them can be written against
an allocator-held claim, and `DeviceClaimReconciler` stays typed to the process-owned record.
- New classification `allocator-held`: not stale, not owner-releasable. `device status` shows it
in the normal view with its allocator, incarnation and installation; `device release --stale`
refuses it with `allocator-held-owner`.
- The verifier gains `covered` and `incarnation-stale`. A session or transient command whose fence
names the incarnation the claim holds executes under it, acquiring and clearing nothing; a fence
for a re-provisioned identity is refused, because an incarnation is stable for that identity's
pool lifetime.
- The ORDINARY arm of the admission gate now performs a read-only inspection under every policy
but `none` and refuses `DEVICE_IN_USE` / `DEVICE_CLAIM_ALLOCATOR_HELD`: `apps` and `app-state`
boot a device through `ensureReady` exactly as a mutation would. It still acquires nothing, so
the policy/claim-file table is unchanged. That reason is deliberately outside
`DeviceClaimConflictReason`, so replay never retries it as infrastructure. The inspection reads
the record and stops (`readAllocatorHeldClaimFile`): the owner-liveness probe `inspectDeviceClaimFile`
runs costs a host process observation per binding, and this kind has no owner process to observe.
- `acquireAllocatorHeldDeviceClaim` reattaches only on the full principal and never reconciles or
supersedes an ordinary claim (a conflicting ordinary claim prevents publication, ADR 0021 §4);
`releaseAllocatorHeldClaim` takes removal proof and is the only clearing path. Both are
by-design production-unused in this unit, with a comment naming the unit that will call them.
- The record/decoder and the lock/write leaves move out of device-claims.ts into
device-claim-record.ts and device-claim-store.ts, which dissolves the
device-claims <-> device-claim-inspection type cycle and lets the allocator module share the
writer without device-claims.ts ever importing it.
`devices` still reports no `claimedBy` for an allocator-held claim: the public field is
`{ session, workspace }` and this kind has neither.
Wording-only tightening of 41 existing definitions. No term is added, removed or redefined, and the five entries unit 1 added and the three unit 2 touched are left alone. The glossary ends at 11,589 of the 12,000-byte guidance budget, which leaves room for the sibling unit's vocabulary to land without a second trim.
…ublish owner device-claims.ts no longer writes its claim file directly: it delegates every write to writeDeviceClaim in the new device-claim-store.ts, the single writer shared by the process-owned and allocator-held claim kinds. atomic-publish-ownership.test.ts's SIMPLE_PUBLISHERS list still named device-claims.ts, so its source-grep for `publishFileSync` no longer matched anything and the ownership gate failed in CI. Register device-claim-store.ts as the owner instead. Also rename DeviceClaimRecord -> StoredDeviceClaim (and decodeDeviceClaimRecord -> decodeStoredDeviceClaim): the shutdown ledger's own DeviceClaimRecord in daemon-shutdown-report.ts is a different type (one row of what teardown released), and the shared name invited confusing that unrelated type for this module's claim-file record.
The rebase onto adr0021/u1-owner-kind's decideAllocatorHeldAdmission fix carried forward ADMITTED_BY_OUTCOME_STATUS and its outcomes array from before this branch added the covered and incarnation-stale statuses, so the table no longer covered the whole AllocatorHeldClaimAdmission union and TS2739 caught it. Add both: covered admits, incarnation-stale does not.
readAllocatorHeldClaimFile gated on entry.allocatorClaim, which is only set once a record decodes all the way through. A v3-schema record corrupted into also carrying a process principal field fails decodeAllocatorHeldClaim's carriesProcessPrincipal check and decodeStoredDeviceClaim returns null -- exactly like a record that never existed. Ordinary admission's inspectAllocatorHeldDeviceClaim then read that as "no allocator claim" and let observe and every other non-transient-exclusive policy proceed, against a device an allocator may actually hold. A record that declares schemaVersion 3 but fails to decode is not provably a non-allocator record, so it cannot be treated as absent. Add looksLikeAllocatorHeldClaim (device-claim- record.ts) to distinguish "declares the allocator schema, doesn't decode" from every other kind of corruption, give it its own classification (allocator-inconsistent), and have readAllocatorHeldClaimFile return the entry -- not null -- for it. Ordinary admission then refuses through the existing deviceClaimConflictError path (DEVICE_IN_USE / DEVICE_CLAIM_OWNER_UNCERTAIN), same as any other claim it cannot verify. deviceClaimRequiresStaleInspection, deviceClaimOwnerCannotRelease and conflictReason are exhaustive switches over DeviceClaimClassification, so the new member forced a decision at every site rather than one that could be missed: not stale (nothing dead to surface), not owner-releasable (no process proof exists to make), and DEVICE_CLAIM_OWNER_UNCERTAIN like the other undecodable-record classifications, not a permanent-condition reason. Planted-red verified: reverting readAllocatorHeldClaimFile's gate to entry?.allocatorClaim alone makes the new regression fail with error.code 'UNKNOWN' -- admission resolves with no error at all, exactly the silent pass-through this fixes.
The switch exceeded fallow's health gate at 10 cyclomatic / 31.6 CRAP once the allocator-inconsistent case joined it. A Record<DeviceClaimClassification, string> reads as one branch to the complexity walker instead of one per case, while TypeScript still refuses to compile a missing key -- the same exhaustiveness guarantee a switch gave, at a fraction of the counted complexity.
Main gained bytes elsewhere since this stack's own docs-condense commit landed, pushing CONTEXT.md to 12,028. Tighten four of this stack's own entries (managed local owner, request generation, identity incarnation, managed device allocator port) rather than touch anyone else's; 11,947 bytes leaves headroom against the next PR that lands first.
ff74cdd to
db60db6
Compare
|
The malformed allocator-record code blocker is fixed at |
|
The remaining review blockers are resolved at exact head The body now matches the actual 24-file/+1,695/-343 diff and the combined #2258 base. It records the malformed allocator-looking v3 fail-closed behavior and planted-red Code review is clean. Every completed exact-head lane is green; iOS Smoke is the only remaining in-progress gate, so readiness waits on that result. |
|
READY at exact head The final iOS Smoke lane passed, including simulator E2E, gesture replay, and host-focus preservation; the physical-device step was the expected workflow skip. Every exact-head required check is now green, the stack base is current, the PR contract matches the 24-file diff, and the malformed-v3 fail-closed review finding remains pinned by its planted-red regression. |
|
Summary
ADR 0021 allocator-held execution claims, stacked on #2258's managed-local runtime foundation. A managed-pool device is held for the identity's pool lifetime rather than for one daemon process or session.
AllocatorHeldDeviceClaim; the existing schema-v2 process claim is unchanged. The allocator record has an installation/allocator/incarnation principal and cannot carry process, session, workspace, or abandonment fields.device-claim-record.tsanddevice-claim-store.ts. Both claim kinds reuse the existing host-kitpublishFileSyncand per-device process lock; there is no second persistence mechanism.allocator-inconsistentand fail closed instead of being treated as an absent claim. The ordinaryobserveregression was planted red by reverting that classification gate.device release --stalerefuses without changing the file.Nothing writes allocator claims in production yet. The acquire/release functions are the focused store interface for the later activation/removal units; they do not add lifecycle policy, command flags, or a second allocator abstraction.
Scope: 24 files, +1,695/-343 against #2258. Most growth is the split record/store implementation and one-to-one claim-family tests. The npm unpacked increase is 4.0 kB; startup moved by +0.1 ms for
--versionand +2.2 ms for--help, within the seven-run measurement noise. The design removes the old decoder/type cycle and reuses the shared atomic publisher instead of adding local file-publication code.Validation
Exact head:
db60db6761d4936f0f7f0a226fc6d18776501643.pnpm check:affected --run: green at the feature/docs series across format, lint, typecheck, layering, fallow, build, and related tests; the final malformed-record fix is covered by exact-head GitHub CI.