instar 1.3.606 → 1.3.608

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/dist/commands/server.d.ts.map +1 -1
  2. package/dist/commands/server.js +333 -32
  3. package/dist/commands/server.js.map +1 -1
  4. package/dist/config/ConfigDefaults.d.ts.map +1 -1
  5. package/dist/config/ConfigDefaults.js +38 -0
  6. package/dist/config/ConfigDefaults.js.map +1 -1
  7. package/dist/coordination/CrossMachineMandate.d.ts +53 -0
  8. package/dist/coordination/CrossMachineMandate.d.ts.map +1 -0
  9. package/dist/coordination/CrossMachineMandate.js +76 -0
  10. package/dist/coordination/CrossMachineMandate.js.map +1 -0
  11. package/dist/core/AccountCredentialShare.d.ts +115 -0
  12. package/dist/core/AccountCredentialShare.d.ts.map +1 -0
  13. package/dist/core/AccountCredentialShare.js +113 -0
  14. package/dist/core/AccountCredentialShare.js.map +1 -0
  15. package/dist/core/AccountFollowMeGrants.d.ts +92 -0
  16. package/dist/core/AccountFollowMeGrants.d.ts.map +1 -0
  17. package/dist/core/AccountFollowMeGrants.js +106 -0
  18. package/dist/core/AccountFollowMeGrants.js.map +1 -0
  19. package/dist/core/CoherenceJournal.d.ts +1 -1
  20. package/dist/core/CoherenceJournal.d.ts.map +1 -1
  21. package/dist/core/CoherenceJournal.js +12 -2
  22. package/dist/core/CoherenceJournal.js.map +1 -1
  23. package/dist/core/PairingEpochManager.d.ts +68 -0
  24. package/dist/core/PairingEpochManager.d.ts.map +1 -0
  25. package/dist/core/PairingEpochManager.js +88 -0
  26. package/dist/core/PairingEpochManager.js.map +1 -0
  27. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  28. package/dist/core/PostUpdateMigrator.js +38 -0
  29. package/dist/core/PostUpdateMigrator.js.map +1 -1
  30. package/dist/core/SecretStore.d.ts +44 -0
  31. package/dist/core/SecretStore.d.ts.map +1 -1
  32. package/dist/core/SecretStore.js +126 -0
  33. package/dist/core/SecretStore.js.map +1 -1
  34. package/dist/core/SecretSync.d.ts.map +1 -1
  35. package/dist/core/SecretSync.js +6 -0
  36. package/dist/core/SecretSync.js.map +1 -1
  37. package/dist/core/SubscriptionAccountMetaReplicatedStore.d.ts +106 -0
  38. package/dist/core/SubscriptionAccountMetaReplicatedStore.d.ts.map +1 -0
  39. package/dist/core/SubscriptionAccountMetaReplicatedStore.js +263 -0
  40. package/dist/core/SubscriptionAccountMetaReplicatedStore.js.map +1 -0
  41. package/dist/core/SubscriptionPool.d.ts +23 -5
  42. package/dist/core/SubscriptionPool.d.ts.map +1 -1
  43. package/dist/core/SubscriptionPool.js +30 -6
  44. package/dist/core/SubscriptionPool.js.map +1 -1
  45. package/dist/core/devGatedFeatures.d.ts.map +1 -1
  46. package/dist/core/devGatedFeatures.js +12 -0
  47. package/dist/core/devGatedFeatures.js.map +1 -1
  48. package/dist/core/types.d.ts +37 -0
  49. package/dist/core/types.d.ts.map +1 -1
  50. package/dist/core/types.js.map +1 -1
  51. package/dist/monitoring/AutonomousLivenessReconciler.d.ts +278 -0
  52. package/dist/monitoring/AutonomousLivenessReconciler.d.ts.map +1 -0
  53. package/dist/monitoring/AutonomousLivenessReconciler.js +660 -0
  54. package/dist/monitoring/AutonomousLivenessReconciler.js.map +1 -0
  55. package/dist/monitoring/ResumeQueue.d.ts +10 -0
  56. package/dist/monitoring/ResumeQueue.d.ts.map +1 -1
  57. package/dist/monitoring/ResumeQueue.js +12 -0
  58. package/dist/monitoring/ResumeQueue.js.map +1 -1
  59. package/dist/scaffold/templates.d.ts.map +1 -1
  60. package/dist/scaffold/templates.js +7 -0
  61. package/dist/scaffold/templates.js.map +1 -1
  62. package/dist/server/AgentServer.d.ts +1 -0
  63. package/dist/server/AgentServer.d.ts.map +1 -1
  64. package/dist/server/AgentServer.js +6 -0
  65. package/dist/server/AgentServer.js.map +1 -1
  66. package/dist/server/routes.d.ts +4 -0
  67. package/dist/server/routes.d.ts.map +1 -1
  68. package/dist/server/routes.js +10 -0
  69. package/dist/server/routes.js.map +1 -1
  70. package/package.json +1 -1
  71. package/src/data/builtin-manifest.json +64 -64
  72. package/src/scaffold/templates.ts +7 -0
  73. package/upgrades/1.3.607.md +25 -0
  74. package/upgrades/1.3.608.md +29 -0
  75. package/upgrades/side-effects/autonomous-liveness-reconciler.md +39 -0
  76. package/upgrades/side-effects/ws52-account-follow-me-pr1.md +74 -0
@@ -0,0 +1,29 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ The **Autonomous Liveness Reconciler** — a level-triggered control loop that self-heals an autonomous run whose per-topic state file still says `active: true` (with time remaining) but has **no live session** executing it ("dead but marked active"). Born from the 2026-06-16 incident where an `age-limit` recycle on topic 12476 was tagged `midWork:false`, never offered to the resume queue, and silently died with ~15h left — and nothing watched for the contradiction. Ships **DARK on the fleet** (`monitoring.autonomousLivenessReconciler.enabled` OMITTED in ConfigDefaults → the dev-agent gate resolves it live-on-dev / dark-fleet) and **dryRun-FIRST** on dev (the component code-defaults `dryRun:true` — it only LOGS `would-respawn` + a shadow `would-have-capped` until a deliberate `dryRun:false` flip).
9
+
10
+ This effort ships TWO changes in the same PR (a backstop must not mask a deterministic bug):
11
+
12
+ 1. **Root-cause edge fix (`server.ts` `considerEnqueue`):** at the reap instant, when `getTopicForSession` returns null (the session-name→topic map can be stale/evicted exactly when an `age-limit` recycle should resume), the active-autonomous-run injection branch was silently skipped and the run died. The fix falls back to resolving the topic from the session NAME (the same parse used by the coherence-journal emit) and adopts it ONLY when the run-state file confirms an active run — so the `autonomousRunRemainingForTopic` check still runs and the recycle is offered to the resume queue.
13
+ 2. **The reconciler (`src/monitoring/AutonomousLivenessReconciler.ts`):** a level-triggered backstop for the residual orphaning the edge fix can't cover (a crash, sleep/wake, a missed enqueue, a drainer that gave up). Per tick it reads the active runs (reusing `activeAutonomousJobs` + `autonomousRunRemainingForTopic`), evaluates 8 candidate criteria (active+remaining+CURRENT-generation, not-paused, not-operator-stopped, not-mid-move, owned+lease-held by THIS machine, no-live-session, not-already-spawning, unambiguous-binding), debounces across N ticks + a window, then respawns through the SAME `spawnSessionForTopic` primitive the ResumeQueueDrainer uses — bounded by a timeout, a bounded pressure gate, the same quota/session-count/migration gates, an atomic in-process claim + actuation-instant recheck + post-spawn settle-kill, and a P19 loop brake with SEPARATED redie / spawn-failure counters (the redie cap unified with the queue's resurrection count). The respawned session is tagged `midWork:true` so a later reaper kill is revived by the queue, attacking the incident root cause. All respawn inputs come from authoritative sources (`_topicResumeMap`, the topic-binding registry realpath-jailed to the agent home) — NEVER the untrusted state file; a missing resume UUID / unsafe cwd / ambiguous binding raises ONE deduped attention item and refuses, never spawns against a guess.
14
+
15
+ The drainer's shared dep closures (`topicOwnerElsewhere`, `operatorStopSince`, the quota/session-count/migration gates, the live-session resolver) were **extracted to named consts** and passed by the SAME reference into BOTH the drainer and the reconciler — drift-prevention so "the reaper, the drainer, and the reconciler agree" is structurally true. New read-only route `GET /autonomous/liveness` (503 when dark, Bearer-auth'd, content-free: topic ids + counters + conditions only).
16
+
17
+ ## What to Tell Your User
18
+
19
+ Nothing changes for you yet — this ships off on the fleet and observe-only on the dev agent. Once it's flipped live, if one of my autonomous runs ever loses its live session but still has work + time remaining (a crash, a sleep/wake, a recycle that didn't resume), I'll notice within a couple minutes and quietly bring it back — picking up where it left off — instead of sitting silently dead until you message me. If asked "why did my autonomous run come back by itself?" the answer is: my state said the run was still active but no session was executing it, so the reconciler self-healed it (bounded, capped, and respecting any stop you issued).
20
+
21
+ ## Summary of New Capabilities
22
+
23
+ Level-triggered autonomous-run self-heal (dark/dryRun-first): detects a run marked active with no live session and respawns it conversation-preserving — debounced, lease-gated, quota-gated, bounded by a pressure gate + a P19 cap that gives up loudly, with an atomic-claim + settle-kill that can't bypass an operator stop. Plus the root-cause edge fix that stops a stale session→topic map from silently dropping an `age-limit` recycle. New read surface: `GET /autonomous/liveness`.
24
+
25
+ ## Evidence
26
+
27
+ - 41 unit tests (both sides of each of the 8 criteria; the stable-live-vs-blip debounce reset; the two separated counters incl. the queue-unification; the bounded pressure gate deferring-then-acting/escalating; the atomic-claim settle-kill on a stop-during-spawn; the untrusted-state refusals; generation rejection of an obsolete run; durable cap restore; dryRun would-respawn + shadow would-have-capped), 4 integration tests (`GET /autonomous/liveness` → 401 unauth / 503 dark / 200 wired + wiring-integrity that the route serves the SAME live instance), 10 e2e tests (the Phase-1 "feature is alive" 200-not-503 mirroring the production init path; the seeded orphaned→respawn lifecycle; operator-stopped/paused/mid-move/in-queue/not-lease-holder → NOT respawned; pressure deferral then act; cap → one loud attention item; the root-cause parse + run-state confirmation). All 55 green.
28
+ - `tsc --noEmit` clean; the no-silent-fallbacks + no-empty-catch ratchets pass (every new fail-toward-safe catch is tagged `@silent-fallback-ok` with a real justification); the notification burst-invariant test passes (all give-up surfaces are per-topic-per-episode deduped).
29
+ - Side-effects review artifact: `upgrades/side-effects/autonomous-liveness-reconciler.md`. Spec: `docs/specs/autonomous-liveness-reconciler.md` (converged round 4, approved under standing autonomous authorization).
@@ -0,0 +1,39 @@
1
+ # Side-Effects Review — AutonomousLivenessReconciler (self-heal a run marked active but with no live session)
2
+
3
+ **Spec:** docs/specs/autonomous-liveness-reconciler.md. **Constitutional principle served:** Structure > Willpower + "An autonomous run must outlive its session" — a level-triggered control loop replaces an edge-triggered heuristic that had a single-instant failure window.
4
+ **Ships DARK on the fleet** behind `monitoring.autonomousLivenessReconciler.enabled` (OMITTED in defaults → the dev-agent gate resolves it live-on-dev / dark-on-fleet) **and dryRun-first** (the component code-defaults `dryRun:true`). On the fleet the routes 503 and the loop never constructs/acts. On a dev agent the loop + route are live but only LOG "would respawn" until a deliberate `dryRun:false` flip.
5
+ **Files:** src/monitoring/AutonomousLivenessReconciler.ts (new), src/core/devGatedFeatures.ts, plus (pending in this branch) src/commands/server.ts, src/server/routes.ts, src/server/AgentServer.ts, src/config/ConfigDefaults.ts, src/core/PostUpdateMigrator.ts, src/scaffold/templates.ts, and the integration/e2e tests.
6
+
7
+ ## What changed
8
+
9
+ 1. **AutonomousLivenessReconciler.ts (new):** the level-triggered reconcile loop. Per tick it reads the active autonomous runs (reusing `activeAutonomousJobs` + `autonomousRunRemainingForTopic`), and for each evaluates 7 candidate criteria — active+remaining, NOT paused, NOT operator-stopped, NOT mid-machine-move, owned by THIS machine, NO live session, NOT already in the resume queue. A candidate must persist N consecutive ticks across a debounce window before action. Action = a respawn through the SAME spawn-with-resume primitive the ResumeQueueDrainer uses, bounded by a per-topic rolling-window cap (P19 loop brake) that gives up LOUDLY (one coalesced attention item) and posts one honest self-heal line.
10
+ 2. **devGatedFeatures.ts:** registers `autonomousLivenessReconciler` (configPath `monitoring.autonomousLivenessReconciler.enabled`) so the dev-agent gate resolves it live-on-dev / dark-fleet.
11
+ 3. **(pending) server.ts / AgentServer / routes.ts / ConfigDefaults / migrator / templates:** construct the loop behind the gate reusing the drainer's in-scope dep closures (`liveSessionForTopic`, `topicOwnerElsewhere`, `operatorStopSince`, quota), the `GET /autonomous/liveness` status route, the config block, and the migration + CLAUDE.md awareness.
12
+
13
+ ## Blast radius
14
+
15
+ - **Config-gated + dryRun-first.** Fleet: `enabled` resolves false → the loop is never constructed, the route 503s, zero behavior change. Dev: `enabled` resolves true but `dryRun:true` → the loop runs and LOGS "would respawn" without spawning anything. Only a deliberate `dryRun:false` makes it actuate.
16
+ - **No new outbound path of its own.** A respawn reuses the existing spawn-with-resume primitive; the self-heal notice + the give-up attention item reuse the existing send/aggregated-attention plumbing.
17
+ - **Coordinates with, never races, the ResumeQueue.** Criterion 7 (not-already-queued) + the multi-machine ownership criterion ensure the edge-triggered queue stays first responder; the reconciler only catches what the queue missed, and only on the owning machine.
18
+ - **Cost is O(active runs) per tick** (reads run-state files + a tmux-liveness check), same class as the reaper. No whole-tree walk.
19
+
20
+ ## Risk + mitigation
21
+
22
+ - **Risk:** the reconciler fights the reaper (respawns what was just deliberately killed). **Mitigation:** it acts ONLY when the run-state file still says active+remaining AND no operator-stop/pause is in effect — a deliberate stop sets those, excluding the run. Proven by the operator-stopped / paused unit cases.
23
+ - **Risk:** an infinite respawn loop on a run that keeps dying. **Mitigation:** the per-topic rolling-window cap (default 3/6h, durable across restarts) stops auto-respawn and raises ONE attention item. Proven by the loop-brake unit case.
24
+ - **Risk:** two machines both respawn the same run. **Mitigation:** the ownership criterion (only the owner reconciles) + the debounce window absorbing lease-move lag + the post-transfer closeout. Proven by the owner-elsewhere unit case.
25
+ - **Risk:** a transient gap (mid-recycle) triggers a needless respawn. **Mitigation:** the N-tick debounce across a window + criterion 7 (queue already handling it). Proven by the debounce-reset unit case.
26
+ - **Risk:** spawning under quota pressure. **Mitigation:** a per-respawn quota gate (skip + retry next tick). Proven by the quota unit case.
27
+ - **Direction of failure:** every dep read fails toward NOT acting (err toward "alive" / "owned elsewhere" / "stopped" / "queued"), so an uncertain read suppresses a respawn — the strictly-safe direction (a missed respawn is the status quo; a false respawn is bounded by the cap).
28
+
29
+ ## Migration parity
30
+
31
+ - (pending) `migrateConfig` adds the `monitoring.autonomousLivenessReconciler` block (existence-checked); the CLAUDE.md template gains the awareness section + proactive trigger ("user asks why a run died / didn't come back"). devGatedFeatures registration is in. No hook/skill/settings change.
32
+
33
+ ## Dark-gate line-map
34
+
35
+ - (pending) `ConfigDefaults.ts` adds the `monitoring.autonomousLivenessReconciler` block with `enabled` OMITTED (resolved by the dev-agent gate). `node scripts/lint-dev-agent-dark-gate.js` must stay clean (no hardcoded `enabled:false`); the golden-map drift-canary test will be updated for the new `enabled:`-free block's line shift.
36
+
37
+ ## Rollback
38
+
39
+ - Revert the PR, or set `monitoring.autonomousLivenessReconciler.enabled:false` (or leave dryRun:true). The loop never constructs / never actuates; the durable cap-state file is inert. Byte-identical to pre-PR behavior when off.
@@ -0,0 +1,74 @@
1
+ # Side-Effects Review — WS5.2 Account Follow-Me, PR1 (shared security primitives + metadata kind)
2
+
3
+ **Version / slug:** `ws52-account-follow-me-pr1`
4
+ **Date:** `2026-06-17`
5
+ **Author:** Echo (autonomous)
6
+ **Second-pass reviewer:** REQUIRED (high-risk: credentials, mesh verb + RBAC, a replication JournalKind) — verdict appended below.
7
+ **Spec:** `docs/specs/ws52-account-follow-me-security.md` (converged 2026-06-13, approved)
8
+
9
+ ## Summary of the change
10
+
11
+ PR1 of the WS5.2 build — the SHARED SECURITY PRIMITIVES, with **NO live-credential code path** (spec §9 build-order). It makes cross-machine account/quota sharing possible the ToS-safe way (re-mint per machine; no Claude OAuth token copied). Six primitives + the non-credential metadata kind:
12
+
13
+ Files added:
14
+ - `src/core/AccountCredentialShare.ts` — the distinct `account-credential-share` mesh verb + `AccountCredentialShareHandler` (RBAC gate BEFORE decrypt: recipient-match → mandate-verify → single-use-grant-consume → fail-closed AAD decrypt). Structurally separate from the permissive `secret-share`.
15
+ - `src/coordination/CrossMachineMandate.ts` — `signMandateIssuance`/`verifyMandateIssuance` (asymmetric Ed25519 issuance signature; the existing HMAC proof is machine-local).
16
+ - `src/core/PairingEpochManager.ts` — de-pair X25519 key-rotation + durable epoch anchor (`secretStoreKeyAnchor`, encrypted/keychain-backed, rollback-resistant).
17
+ - `src/core/AccountFollowMeGrants.ts` — single-use grant ledger + per-account sum-of-leases spend ceiling + failover re-derivation + epoch fencing.
18
+ - `src/core/SubscriptionAccountMetaReplicatedStore.ts` — the `subscription-account-meta` JournalKind schema (strict §6.1a whitelist + clamps), allowlist projection (`projectAccountToMeta` strips configHome + credentials), envelope builders, emit-seam interface, registration const.
19
+ - Unit tests: `account-credential-crypto`, `account-credential-share`, `cross-machine-mandate`, `pairing-epoch-manager`, `account-followme-grants`, `subscription-account-meta-store`, `ws52-account-follow-me-wiring` (61 new tests).
20
+
21
+ Files modified:
22
+ - `src/core/SecretStore.ts` — NEW `encryptAccountCredential`/`decryptAccountCredential` (AAD-bound, distinct HKDF info, fail-closed). Existing `encryptForSync`/`decryptFromSync` untouched.
23
+ - `src/core/SecretSync.ts` — `SecretShareHandler.handle` now refuses a non-`secret-share` command (credential-class data must use the distinct verb).
24
+ - `src/core/SubscriptionPool.ts` — R0 header correction + the meta emit seam (`setMetaReplicationEmitter` + emitPut on add/update + emitDelete on remove).
25
+ - `src/core/CoherenceJournal.ts` — `subscription-account-meta` added to JournalKind union, JOURNAL_KINDS, DEFAULT_RETENTION + the per-kind `Record<JournalKind>` literals.
26
+ - `src/commands/server.ts` — registers the kind; injects the accountFollowMe-gated store-flag; wires the emit adapter at SubscriptionPool construction.
27
+ - `src/config/ConfigDefaults.ts` + `src/core/devGatedFeatures.ts` — `multiMachine.accountFollowMe` dev-gated (live-on-dev, dark-on-fleet); `credentialTransport` default empty; `maxFollowMachines` 5.
28
+ - `src/scaffold/templates.ts` + `src/core/PostUpdateMigrator.ts` — awareness section (Agent Awareness + Migration Parity).
29
+
30
+ ## The eight side-effect questions
31
+
32
+ 1. **Over-block — what legitimate inputs does this reject that it shouldn't?**
33
+ The `subscription-account-meta` schema rejects ANY non-whitelisted key (incl. a future `SubscriptionAccount` field). That's deliberate (allowlist), but it means a NEW account field won't replicate until added to both the source-of-truth and this whitelist — a documented, bounded maintenance cost (mirrors every other `*-record` kind). The `SecretShareHandler` now throws on a non-`secret-share` command; the only caller is the legacy secret-sync path which only ever sends `secret-share`, so no legitimate caller is blocked.
34
+
35
+ 2. **Under-block — what failure modes does this still miss?**
36
+ PR1 ships no live-credential path, so Mechanism A's at-rest plaintext residual (the framework's own config-home token, gap 6) is NOT yet addressed — it is explicitly a later-PR concern (active wipe + TTL + provider rotation). The grant ledger's `now()` is injectable but a wall-clock-only TTL can't defend a machine with a tampered clock (accepted; the AAD epoch + single-use grant are the primary defenses, not the TTL).
37
+
38
+ 3. **Level-of-abstraction fit.**
39
+ Correct layers: crypto in `SecretStore` (where the sync crypto lives), the mesh verb + handler as a standalone module (parallel to `SecretSync`, not folded into it — the spec's structural-separation mandate), the JournalKind as a `ReplicatedKindRegistry` store (matching the 8 existing kinds), the emit seam on `SubscriptionPool` (the registry owner). No logic placed above/below where it belongs.
40
+
41
+ 4. **Signal vs authority compliance.**
42
+ The RBAC gate in `AccountCredentialShareHandler` is an AUTHORITY (it gates a credential write), but it is NOT brittle: it delegates the actual authorization to injected seams (`verifyMandate` = the operator-mandate gate; `consumeGrant` = the single-use ledger), and FAILS CLOSED on every uncertainty. It never fabricates an allow. The cross-machine mandate verification is deny-by-default. No brittle check holds blocking authority.
43
+
44
+ 5. **Interactions.**
45
+ Adding a JournalKind cascades to every `Record<JournalKind>` map — all updated (caught by tsc + the existing CoherenceJournal getOwnAdvert test, which was updated). The emit gate reuses the existing `_stateSyncStoresResolved` map (injected entry) so it shares the generic emitter's dark-by-default discipline. The new verb is structurally disjoint from `secret-share` (no shared handler/decryptor). No double-fire: emit fires once per mutation after `save()`.
46
+
47
+ 6. **External surfaces.**
48
+ No new HTTP route. No new outbound egress in PR1 (the emit path is dark on the fleet; live only on a dev agent, and even then only emits the non-credential metadata projection). The metadata projection includes `email` (operator/provider PII, "never a secret") which lands at-rest in same-operator peers' plaintext journal replicas — disclosed in the spec §6.1 and the code comment. configHome + every credential field are stripped by allowlist construction.
49
+
50
+ 7. **Multi-machine posture (Cross-Machine Coherence).**
51
+ This feature IS the multi-machine work. Posture: **replicated** (the `subscription-account-meta` kind over the WS2 journal). The credential itself is **machine-local BY DESIGN** (re-mint per machine; never replicated). Authorization is **operator-rooted, not peer-quorum** — holds at pool size 1/2/N. Single-machine agents are a strict no-op (no peers; the emit gate + dark default). Cross-machine mandate auth uses the asymmetric Ed25519 path (works over the relay where the local HMAC cannot).
52
+
53
+ 8. **Rollback cost.**
54
+ Low. Everything is dark on the fleet (gate `multiMachine.accountFollowMe`, default off via the dev-gate). The JournalKind addition is additive + forward-compat (an old peer that doesn't know the kind drops it; the receive validator is present so a known peer never suspect-flags). Back-out = revert the PR; no data migration (no live credential ever written), no agent-state repair. The R0 header change is documentation-only.
55
+
56
+ ## What's NOT in PR1 (tracked, not deferred-orphan)
57
+
58
+ Per spec §9, PR1 is the primitives + the metadata kind ONLY. The following are the spec's own subsequent build-round items, tracked in the WS5.2 spec + the autonomous task list (topic 13481):
59
+ - Mechanism B enroll-drive (re-mint per machine, operator-mandate-gated) — the live account path.
60
+ - The router `locallyExecutable` gate + revocation (R12) + the offline-wipe escalation.
61
+ - The `?scope=pool` offline-fallback read that surfaces the durable replicated meta when a peer is offline (the live `?scope=pool` HTTP fan-out already serves online peers; the meta is correctly received + stored now).
62
+ - The live-channel proof (a real operator message answered FROM the Mini using an account the Mini enrolled itself).
63
+
64
+ ## Second-pass review
65
+
66
+ **Verdict: Concur with the review.** (Independent reviewer subagent, 2026-06-17.)
67
+
68
+ The reviewer independently read all eight files, ran the seven WS5.2 test files (63/63 pass) and `tsc --noEmit` (clean), and verified the four security focus areas:
69
+ - **(a) No credential decrypt/store without mandate + consumed grant** — the RBAC gate runs strictly before any crypto (type → expiry → recipient-match → verifyMandate deny-by-default → consumeGrant single-use → fail-closed decrypt); every denial returns `{accepted:false}`, never fail-open. The crypto pair is genuinely domain-separated (distinct HKDF info; AAD bound via setAAD with a timing-safe pre-compare; throws on absent/mismatched AAD); a secret-sync blob cannot decrypt as a credential or vice-versa; `SecretShareHandler` refuses credential-class commands; the cross-machine mandate uses an asymmetric Ed25519 signature binding the issuer fingerprint.
70
+ - **(b) configHome / OAuth token cannot cross via the metadata kind** — `projectAccountToMeta` is a strict build-by-copy 7-key allowlist; the receive validator independently rejects non-whitelisted keys (exempting only RESERVED_ENVELOPE_FIELDS). Enforced both ends.
71
+ - **(c) Dark/fleet default genuinely inert** — `resolveDevAgentGate(undefined, fleetConfig)` is false ⇒ no store-flag entry, emitter not constructed, registration inert, `storeCredential` unwired in PR1.
72
+ - **(d) No fail-open bugs** — every security check defaults to deny/throw-into-deny.
73
+
74
+ Two non-blocking observations (both safe-direction, acceptable for the PR1 primitive layer): a forged-AAD payload that ALREADY passed the mandate gate burns its single-use grant on the fail-closed decrypt (a self-limited DoS that requires first passing the operator-mandate gate); and `rotateOnDepair` is load-then-save without an explicit lock (serial single-machine de-pair). Both are noted for the live-path PRs; neither blocks PR1.