instar 1.3.714 → 1.3.715

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 (87) hide show
  1. package/dist/commands/server.d.ts.map +1 -1
  2. package/dist/commands/server.js +599 -4
  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 +35 -0
  6. package/dist/config/ConfigDefaults.js.map +1 -1
  7. package/dist/core/CoherenceJournal.d.ts +1 -1
  8. package/dist/core/CoherenceJournal.d.ts.map +1 -1
  9. package/dist/core/CoherenceJournal.js +9 -2
  10. package/dist/core/CoherenceJournal.js.map +1 -1
  11. package/dist/core/FencedLease.d.ts +47 -1
  12. package/dist/core/FencedLease.d.ts.map +1 -1
  13. package/dist/core/FencedLease.js +58 -1
  14. package/dist/core/FencedLease.js.map +1 -1
  15. package/dist/core/LeaseCoordinator.d.ts +25 -1
  16. package/dist/core/LeaseCoordinator.d.ts.map +1 -1
  17. package/dist/core/LeaseCoordinator.js +66 -0
  18. package/dist/core/LeaseCoordinator.js.map +1 -1
  19. package/dist/core/LeaseHandbackReconciler.d.ts +212 -0
  20. package/dist/core/LeaseHandbackReconciler.d.ts.map +1 -0
  21. package/dist/core/LeaseHandbackReconciler.js +416 -0
  22. package/dist/core/LeaseHandbackReconciler.js.map +1 -0
  23. package/dist/core/MeshRpc.d.ts +6 -1
  24. package/dist/core/MeshRpc.d.ts.map +1 -1
  25. package/dist/core/MeshRpc.js +9 -0
  26. package/dist/core/MeshRpc.js.map +1 -1
  27. package/dist/core/MultiMachineCoordinator.d.ts +20 -0
  28. package/dist/core/MultiMachineCoordinator.d.ts.map +1 -1
  29. package/dist/core/MultiMachineCoordinator.js +30 -0
  30. package/dist/core/MultiMachineCoordinator.js.map +1 -1
  31. package/dist/core/OwnershipReconciler.d.ts +42 -2
  32. package/dist/core/OwnershipReconciler.d.ts.map +1 -1
  33. package/dist/core/OwnershipReconciler.js +100 -2
  34. package/dist/core/OwnershipReconciler.js.map +1 -1
  35. package/dist/core/PostUpdateMigrator.d.ts +1 -0
  36. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  37. package/dist/core/PostUpdateMigrator.js +31 -0
  38. package/dist/core/PostUpdateMigrator.js.map +1 -1
  39. package/dist/core/StaleOwnerReleaseEngine.d.ts +290 -0
  40. package/dist/core/StaleOwnerReleaseEngine.d.ts.map +1 -0
  41. package/dist/core/StaleOwnerReleaseEngine.js +645 -0
  42. package/dist/core/StaleOwnerReleaseEngine.js.map +1 -0
  43. package/dist/core/TopicClaimAnnotationStore.d.ts +121 -0
  44. package/dist/core/TopicClaimAnnotationStore.d.ts.map +1 -0
  45. package/dist/core/TopicClaimAnnotationStore.js +234 -0
  46. package/dist/core/TopicClaimAnnotationStore.js.map +1 -0
  47. package/dist/core/devGatedFeatures.d.ts.map +1 -1
  48. package/dist/core/devGatedFeatures.js +11 -0
  49. package/dist/core/devGatedFeatures.js.map +1 -1
  50. package/dist/core/handbackLatch.d.ts +28 -0
  51. package/dist/core/handbackLatch.d.ts.map +1 -0
  52. package/dist/core/handbackLatch.js +73 -0
  53. package/dist/core/handbackLatch.js.map +1 -0
  54. package/dist/core/ropeHealth.d.ts +26 -0
  55. package/dist/core/ropeHealth.d.ts.map +1 -0
  56. package/dist/core/ropeHealth.js +32 -0
  57. package/dist/core/ropeHealth.js.map +1 -0
  58. package/dist/core/types.d.ts +51 -0
  59. package/dist/core/types.d.ts.map +1 -1
  60. package/dist/core/types.js.map +1 -1
  61. package/dist/monitoring/StrandedTopicSentinel.d.ts +5 -0
  62. package/dist/monitoring/StrandedTopicSentinel.d.ts.map +1 -1
  63. package/dist/monitoring/StrandedTopicSentinel.js +8 -0
  64. package/dist/monitoring/StrandedTopicSentinel.js.map +1 -1
  65. package/dist/monitoring/guardManifest.d.ts.map +1 -1
  66. package/dist/monitoring/guardManifest.js +45 -0
  67. package/dist/monitoring/guardManifest.js.map +1 -1
  68. package/dist/monitoring/strandedTopicDecision.d.ts +11 -0
  69. package/dist/monitoring/strandedTopicDecision.d.ts.map +1 -1
  70. package/dist/monitoring/strandedTopicDecision.js +12 -1
  71. package/dist/monitoring/strandedTopicDecision.js.map +1 -1
  72. package/dist/scaffold/templates.d.ts.map +1 -1
  73. package/dist/scaffold/templates.js +2 -1
  74. package/dist/scaffold/templates.js.map +1 -1
  75. package/dist/server/AgentServer.d.ts +9 -0
  76. package/dist/server/AgentServer.d.ts.map +1 -1
  77. package/dist/server/AgentServer.js +2 -0
  78. package/dist/server/AgentServer.js.map +1 -1
  79. package/dist/server/routes.d.ts +9 -0
  80. package/dist/server/routes.d.ts.map +1 -1
  81. package/dist/server/routes.js +76 -0
  82. package/dist/server/routes.js.map +1 -1
  83. package/package.json +1 -1
  84. package/src/data/builtin-manifest.json +65 -65
  85. package/src/scaffold/templates.ts +2 -1
  86. package/upgrades/1.3.715.md +92 -0
  87. package/upgrades/side-effects/u4-stale-owner-release-lease-handback.md +87 -0
@@ -0,0 +1,92 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: minor -->
5
+
6
+ ## What Changed
7
+
8
+ Two mesh self-healing reconcilers land dark, on one shared lease seam
9
+ (specs: `docs/specs/u4-2-stale-owner-release.md` + `docs/specs/u4-4-lease-handback.md`,
10
+ both converged + approved).
11
+
12
+ **U4.2 — stale-owner release** (`multiMachine.sessionPool.staleOwnerRelease`,
13
+ dev-gated: live-in-dryRun on a development agent, dark on the fleet). The
14
+ CMT-1786 auto-failover, built as the evidence upgrade to the ownership
15
+ reconciler's Case C: when a topic's owner machine is provably dead/dark, the
16
+ serving-lease holder force-claims its topics behind a fail-closed five-evidence
17
+ bar (observer-stamped death staleness — the named `server.ts` rewire off the
18
+ self-reported clock; unreachability on every owner-authenticated transport;
19
+ quorum; a claimant self-connectivity proof; side-effect recency over a
20
+ provably-fresh replicated mirror). New replicated `topic-claim-annotation`
21
+ kind carries the pin suspension, the per-topic claim budget/backoff, and the
22
+ operator's declined-demote across lease movement (epoch-independent — never an
23
+ ownership CAS). Detection/escalation is quorum-hosted so a no-lease-holder mesh
24
+ still reaches the operator; claims stay lease-holder-only. Decision trace at
25
+ `logs/stale-owner-release.jsonl`; soak telemetry at
26
+ `GET /pool/stale-owner-release`.
27
+
28
+ **U4.4 — lease hand-back** (`multiMachine.leaseSelfHeal.preferredCaptainHandback`,
29
+ hard-dark everywhere: action-bearing lease authority, DARK_GATE_EXCLUSIONS like
30
+ its F2/F3/L3 siblings). The missing reconciler for the F4
31
+ `preferredAwakeMachineId` preference: after a failover the lease is handed BACK
32
+ to the preferred captain once continuously healthy (10m hysteresis), at a clean
33
+ boundary (bounded 2h deferral; queued inbound drained before step-down),
34
+ claim-before-release via a holder-signed, epoch-bound, TTL-bounded, single-use
35
+ consent token on the new `handback-offer` mesh verb (holder-only RBAC) — a
36
+ failed claim leaves the holder holding, so zero-holder states are impossible by
37
+ construction. The human always wins: the operator-flip latch
38
+ (`POST /pool/lease-handback/latch`, PIN-gated clear) holds the reconciler fully
39
+ inert. Post-hand-back delivery canary; episode cap counts offers; hand-backs
40
+ feed the churn breaker. Boot refuses `dryRun:false` unless pollFollowsLease is
41
+ live (the lease/ingress-split chokepoint).
42
+
43
+ ## What to Tell Your User
44
+
45
+ Nothing yet — both features ship dark and change nothing on your setup today.
46
+ When they graduate: if one of your machines ever dies with conversations on it,
47
+ I'll be able to take those conversations over safely on another machine instead
48
+ of leaving them stranded until you notice — and after a failover, serving will
49
+ drift back to the machine you prefer once it's genuinely healthy again, without
50
+ you running the manual flip. Any move I make will be logged, bounded, and
51
+ honest, and your manual choices always override the automation.
52
+
53
+ ## Summary of New Capabilities
54
+
55
+ - `GET /pool/stale-owner-release` — U4.2 soak telemetry (attempts, dry-run
56
+ would-claims, refusals by reason, evidence classes, P19 give-ups, probe
57
+ breaker, open episodes). 503 while dark.
58
+ - `GET /pool/lease-handback` — U4.4 reconciler status + operator-latch
59
+ visibility (honest `enabled:false` while hard-dark).
60
+ - `POST /pool/lease-handback/latch` — the operator-flip latch marker (the
61
+ captain-flip playbook's POST step; suppresses automated hand-back).
62
+ - `DELETE /pool/lease-handback/latch` — PIN-gated early clear (re-enabling
63
+ automation against a human decision requires the dashboard PIN).
64
+ - New replicated journal kind `topic-claim-annotation` (additive — old peers
65
+ never sync it); new `handback-offer` MeshCommand (holder-only RBAC,
66
+ fail-closed on legacy peers).
67
+ - Config: `multiMachine.sessionPool.staleOwnerRelease` (dev-gated,
68
+ `enabled` omitted) + `multiMachine.leaseSelfHeal.preferredCaptainHandback`
69
+ (`enabled:false`, `dryRun:true`); both G3 `loadBearing` in the guard
70
+ manifest so a stalled dark posture classifies loudly.
71
+
72
+ ## Evidence
73
+
74
+ Both features are dark, so the evidence is the three-tier suite + ratchets, all
75
+ green locally: 28 unit tests on the U4.2 evidence bar (both sides of every
76
+ predicate: reachable-on-one-transport refuses, single-rope/stale-advert/stale-
77
+ mirror classify ambiguity, bootstrap rule converges after a claimant restart,
78
+ budgets + declined-demote read from the replicated view), 19 on the
79
+ `topic-claim-annotation` store (strict clamps, HLC-highest-wins, epoch
80
+ independence), 20 on the hand-back reconciler (hysteresis, boundary, deferral
81
+ ceiling drain-before-step-down, claim-before-release failure matrix, latch and
82
+ churn/split-brain suppression, episode cap counting offers), 14 on the
83
+ FencedLease consent branch with real Ed25519 keys (absent/expired/replayed/
84
+ reused/wrong-target/wrong-epoch/forged all refuse to the byte-identical legacy
85
+ `held-by-live-peer`), 5 on the LeaseCoordinator consent claim
86
+ (`failed-handback-never-leaves-zero-holders`), 8 reconciler-integration tests
87
+ (engine supersedes legacy Case C; the `case-c-staleness-input-is-observer-stamped`
88
+ wiring ratchet proves the `routerReceivedAt` rewire in server.ts), 5 on
89
+ quorum-hosted stranded escalation, 8 integration route tests (503-dark, §2.9
90
+ counters advancing, PIN-gated latch clear), and 5 feature-alive E2E tests
91
+ through the real AgentServer (200-not-503; dry-run counters advance on a
92
+ synthetic episode/transition; dark → 503).
@@ -0,0 +1,87 @@
1
+ # Side-Effects Review — U4.2 Stale-Owner Release + U4.4 Lease Hand-Back (one shared PR, same lease seam)
2
+
3
+ **Specs:** docs/specs/u4-2-stale-owner-release.md + docs/specs/u4-4-lease-handback.md (both converged + approved — operator preapproval, topic 29836, 2026-07-02; ELI16s + convergence reports committed alongside).
4
+ **Parents:** Cross-Machine Coherence — One Agent, Robust Under Degraded Conditions (U4.2); The User Experience Is the Product (U4.4).
5
+ **Rollout posture:** U4.2 rides the developmentAgent dark-gate in dryRun (`multiMachine.sessionPool.staleOwnerRelease` — `enabled` OMITTED, registered in DEV_GATED_FEATURES; dev-live-in-dryRun, dark fleet). U4.4 ships **HARD-DARK everywhere** (`multiMachine.leaseSelfHeal.preferredCaptainHandback` — `enabled:false` + `dryRun:true`, DARK_GATE_EXCLUSIONS category `action-bearing`, matching its F2/F3/L3 siblings). Single-machine agents are a strict no-op for both.
6
+ **Files:** src/core/StaleOwnerReleaseEngine.ts (new), src/core/TopicClaimAnnotationStore.ts (new), src/core/LeaseHandbackReconciler.ts (new), src/core/handbackLatch.ts (new), src/core/ropeHealth.ts (new), src/core/OwnershipReconciler.ts, src/core/FencedLease.ts, src/core/LeaseCoordinator.ts, src/core/MeshRpc.ts, src/core/MultiMachineCoordinator.ts, src/core/CoherenceJournal.ts, src/core/types.ts, src/core/devGatedFeatures.ts, src/config/ConfigDefaults.ts, src/monitoring/StrandedTopicSentinel.ts, src/monitoring/strandedTopicDecision.ts, src/monitoring/guardManifest.ts, src/server/AgentServer.ts, src/server/routes.ts, src/commands/server.ts, src/scaffold/templates.ts, src/core/PostUpdateMigrator.ts
7
+
8
+ ## What changed
9
+
10
+ ### U4.2 — stale-owner release (the CMT-1786 auto-failover, built as Case C's evidence upgrade)
11
+
12
+ 1. **StaleOwnerReleaseEngine.ts (new):** the §2.2 evidence bar — machine death on the OBSERVER's monotonic clock (the FencedLease-F2 fold-in pattern; never the owner's self-reported wall clock), the R-r2-2 bounded bootstrap rule (never-observed-since-boot → ambiguity → bounded expiry off the durable coarse-heartbeat tie-breaker; a claimant restart can never silently disable auto-failover), all-transport disproof over an owner-authenticated, fresh, multi-transport advert set (single-rope = automatic ambiguity), quorum, claimant self-connectivity proof (2-machine git-less mesh: claim path DISABLED), and side-effect recency over a PROVABLY FRESH replicated mirror (a stale mirror classifies AMBIGUITY). Every ambiguity fails CLOSED. Bounded blast radius: `maxClaimsPerTick`, per-topic replicated claim budget + widening backoff, probe single-flight + memo + breaker, LOUD P19 give-ups (one deduped attention item). Honesty: every stale-detect / probe verdict / would-claim / claim / REFUSAL lands in `logs/stale-owner-release.jsonl`, state-change-gated per episode; ambiguity past 3×TTL escalates ONE per-episode deduped operator item. Tier 0 by explicit §2.8 argument — LLM-free, asserted by test.
13
+ 2. **TopicClaimAnnotationStore.ts (new):** the `topic-claim-annotation` replicated kind (R-r3-1/R-r3-2) — claim suspension + per-topic budget/backoff + the operator's declined-demote, keyed `${topic}:${episodeId}`, epoch-INDEPENDENT (its own HLC via the generic envelope, exactly like `topic-pin-record`; an annotation write can never bump `ownershipEpoch` or fence a live owner). A new registered KIND is additive — pre-U4.2 peers never register it, so there is no unknown-FIELD surface on the placement stream. Registered in CoherenceJournal's JOURNAL_KINDS (static half) + the replicatedKindRegistry at boot (dynamic half, registration INERT; emission gated behind the feature).
14
+ 3. **OwnershipReconciler.ts:** the engine rides `tick()` (one actor, §2.7.6) and gates INDEPENDENTLY of ws13Reconcile; an ACTIVE engine SUPERSEDES the legacy pin-based Case C force-claim (`deferred-no-evidence`); claims funnel through the new `actStaleOwnerForceClaim` stamping the EXTENDED nonce grammar `${self}:stale-owner-release:${sessionKey}:${episodeId}:${now}` (§2.7.5) + the `stale-owner-release` placement emission. `effectivePins()` consults the merged claim-suspension annotations — a pin whose HLC is not strictly newer than the live suspension is `suspended-pending-owner-return` (derived; the pin record is never written), and a fresher operator re-pin WINS. **Fixed during this build:** the suspension read now also applies on the no-advisory-pins early-return path (a local-pins-only mesh would otherwise have fought the claim). Named code fix: LOCAL pins gain the online-gate advisory pins already had (`deferred-target-offline` — never start a transfer toward an offline machine).
15
+ 4. **server.ts (R-r2-5a — the named prerequisite rewire):** Case C's staleness feed switched from `Date.parse(c.selfReportedLastSeen)` to the router/observer-stamped `c.routerReceivedAt` for BOTH the reconciler and the engine (MachinePoolRegistry's §L2 rule: a fast-clocked machine must not appear fresher than it is). Evidence seams wired in the lease-transport block: advert sets from PeerEndpointRecorder provenance ONLY (the git registry's `lastKnownUrl` is deliberately not consulted for disproof), authenticated probes via the identity-verified lease pull, self-proof via a third-peer probe (or durable-authority read in the 2-machine case), evidence-5 mirror freshness stamped on every SUCCESSFUL journal-sync exchange. §2.3 TTL-ordering invariant (`deathEvidenceMs > selfFenceTtlMs + tick + skew`) validated at startup — a violating combination REJECTS boot loudly.
16
+ 5. **StrandedTopicSentinel / strandedTopicDecision (R-r2-1):** the not-lease-holder early no-op is relaxed for DETECTION + ESCALATION only, when the stale-owner feature is active AND this machine observes a majority partition — so an exhausted churn breaker with a dead preferred captain (a no-lease-holder mesh) still reaches the operator. The CLAIM stays lease-holder-only. Default off = today byte-for-byte.
17
+ 5b. **SessionReaper closeout pin-veto yields to a live claim suspension (§2.3 returning-owner teardown):** the WS1.3 pin-conflict veto (`topicPinnedHere`) previously read the RAW local pin — after a U4.2 claim of a PINNED topic, the returned owner's closeout would be vetoed forever by a pin the reconciler itself no longer honors (effectivePins excludes it via the suspension), leaving the claimed topic's session lingering on the returned owner. The wiring now consults the SAME merged suspension view + the shared `claimSuspensionExcludesPin` comparison authority (exported from TopicClaimAnnotationStore; OwnershipReconciler.applyClaimSuspensions refactored onto it — no forked comparison). Feature dark ⇒ the suspension view is empty ⇒ byte-identical veto behavior.
18
+ 6. **`GET /pool/placement` gains `ownershipLeaseState` (§2.9 derivation table):** `held | stale | releasing | claimed`, DERIVED from record status + the engine's open-episode state + the §2.7.5 nonce grammar. (v1 proxy: "live suspension annotation present" is derived from the stale-owner-release nonce on the active record — the nonce persists exactly until the next ownership transition, which is when a cleared suspension would re-CAS anyway; the merged-annotation read stays server-internal.)
19
+ 7. **`GET /pool/stale-owner-release` (§2.9, R-r2-6):** the FD-7-style telemetry the §5 graduation soak is judged on — counters (attempts / claims / would-claims / refusals BY REASON / ambiguities / escalations / P19 give-ups), evidence-class distribution, probe-breaker state, last + open episodes. 503 when dark.
20
+
21
+ ### U4.4 — lease hand-back to the preferred captain
22
+
23
+ 7. **LeaseHandbackReconciler.ts (new):** the missing reconciler for F4's `preferredAwakeMachineId` (the ONE authority — no new field, nothing replicated, nothing in the signed lease record). Holder-only observation rides the existing ~5s lease pull tick (`MultiMachineCoordinator.onLeasePullTick`, throw-guarded). Hysteresis (10m continuous preferred health; ANY unhealthy observation resets), health = heartbeat-fresh AND rope-reachable (the U4.3 snapshot seam — ABSENT reading ⇒ NOT healthy ⇒ defer, R-r2-7) AND lease-eligible AND quota-OK. Clean boundary (no in-flight forwards / no queued inbound / ~90s ingress quiet) with a bounded 2h deferral ceiling → ONE deduped notice + relaxed boundary that DRAINS queued inbound before step-down (R-r2-6 — never abandoned). Suppressed by the operator latch, a LATCHED churn breaker, and split-brain. Episode cap counts OFFERS as well as hand-backs (R-r2-3) → sticky + ONE item; per-target offer backoff widens on decline/timeout; a legacy peer stops re-offers for the episode.
24
+ 8. **FencedLease.ts (R-r2-1):** `HandbackConsentToken` — SIGNED (holder machine key, discriminator-prefixed canonical form so a token can never verify as a lease), epoch-bound, TTL-bounded, single-use. `canAcquire` gains the `handbackOpts` consent branch: grant ONLY when the token verifies against the HOLDER's registered key, names THIS machine, matches the live lease's holder+epoch, is unexpired and unused; ABSENT/invalid/expired/replayed/reused ⇒ the legacy `held-by-live-peer` refusal byte-for-byte.
25
+ 9. **LeaseCoordinator.ts:** `mintHandbackConsent` (holder-only — a non-holder mints null) + `acquireOnHandbackConsent` (claim-before-release: the fenced CAS at epoch+1; the nonce burns on FIRST presentation success-or-not; a failed/lost CAS changes nothing — zero-holder states impossible; FAIL-CLOSED grant discipline: a coincidentally-claimable lease never launders a bad token).
26
+ 10. **MeshRpc.ts (R-r2-2):** the `handback-offer` MeshCommand — RBAC its own default-deny case (only the CURRENT lease holder may send; refusal reason `handback-offer-unauthorized`, 403). server.ts handler: `decideHandbackOffer` typed declines (`accept` / `declined:churn-latched` / `declined:quota` / `declined:legacy-peer`), receiving-side single-use token set, async consent claim + post-hand-back delivery canary (one authenticated mesh round-trip; failure raises ONE loud attention item). Version skew: 403/`no-handler` classifies `declined:legacy-peer` → the sender goes sticky for the episode.
27
+ 11. **handbackLatch.ts (new, R-r2-5):** the machine-local operator-flip latch marker — WRITTEN BY the explicit flip action (never inferred from a transfer's origin). Routes: `GET /pool/lease-handback` (status + latch visibility — answers honestly even while hard-dark), `POST /pool/lease-handback/latch` (Bearer — the playbook's POST step; suppressing automation is the safe direction), `DELETE /pool/lease-handback/latch` (**PIN-gated** via checkMandatePin — clearing re-enables automation against a human decision; the agent's Bearer token is structurally insufficient).
28
+ 12. **ropeHealth.ts (new):** the U4.3 rope-health snapshot SEAM — a provider registry that returns `undefined` (⇒ defer) until U4.3 lands its real provider; tests/E2E register synthetic providers through the same function.
29
+ 13. **§5 enable chokepoint:** `validateHandbackEnableChokepoint` at boot — `dryRun:false` with pollFollowsLease still dry-run REFUSES startup loudly (the lease/ingress split).
30
+
31
+ ### Shared
32
+
33
+ 14. **Config/gating:** ConfigDefaults blocks for both (U4.2 omits `enabled`; U4.4 explicit `enabled:false`+`dryRun:true`); DEV_GATED_FEATURES entry (staleOwnerRelease) + DARK_GATE_EXCLUSIONS entry (preferredCaptainHandback, action-bearing); guardManifest entries for BOTH with `loadBearing:true` + criticalPath + soakWindowDays + declaredLoadBearingAt (G3 — a stalled dark posture classifies loudly per #1318).
34
+ 15. **Agent awareness + migration parity:** `MESH_SELF_HEALING_CLAUDEMD_SECTION` in generateClaudeMd (new agents) + migrateClaudeMd (existing agents, content-sniffed) + a migrateFrameworkShadowCapabilities marker (Codex/Gemini parity). Config defaults ride the generic `applyDefaults` migration path (missing keys added, never overwritten). Site docs: `features/multi-machine.md` mesh-self-healing section + `reference/api.md` /pool inventory.
35
+
36
+ ## Blast radius
37
+
38
+ - **Everything authority-bearing is dark or dry-run.** U4.2 on a dev agent runs the evidence pass + trace + status route LIVE but `dryRun:true` logs would-claims and NEVER lands a CAS; the fleet resolves dark (no engine activity, route 503s). U4.4 is `enabled:false` everywhere — the reconciler is constructed but `observe()` returns at the first gate; the offer handler's receiving side declines (`declined:legacy-peer`) unless enabled AND dryRun:false locally.
39
+ - **Fail-closed on every uncertainty.** Every evidence ambiguity refuses the claim; a mint refusal / offer timeout / lost CAS leaves the current holder holding; an absent rope snapshot defers; an unreadable gate reads as inactive/not-holder.
40
+ - **No new ownership fence.** `ownershipEpoch` stays the only ownership fence; the annotation kind is derived state that can never answer or fence ownership; the lease epoch stays the only serving fence (the consent claim is a normal fenced CAS).
41
+ - **Version skew:** pre-U4.2 peers never register the annotation kind (additive); a pre-U4.4 peer fails the offer closed and the sender goes sticky. No new unauthenticated surface: probes ride the existing signed lease pull; the offer rides the signed MeshRpc envelope + RBAC + the consent token.
42
+
43
+ ## Risk + mitigation
44
+
45
+ - **Risk: false-death claim against a live owner (split-brain).** Mitigation: the five-evidence fail-closed bar with provenance-pinned inputs (observer-stamped staleness, owner-authenticated advert sets), the §2.3 TTL-ordering invariant enforced at boot, dry-run graduation gated on the §5 quantified soak + emission-fence prerequisite. Proven by the evidence-bar unit lenses (reachable-on-one-transport, single-rope ambiguity, stale mirror, self-proof fail, quorum fail).
46
+ - **Risk: claim/transfer-back oscillation between the claim and the topic's pin.** Mitigation: the claim SUSPENDS the pin via the replicated annotation (HLC-ordered; operator re-pin wins) — proven by the suspension lenses including the early-return-path fix found during this build.
47
+ - **Risk: zero-holder after a hand-back.** Mitigation: claim-before-release via the consent token; the holder steps down only on OBSERVING the higher epoch; every failure path keeps the holder holding — proven at the FencedLease, LeaseCoordinator, and reconciler levels.
48
+ - **Risk: automation fights the human's captain flip.** Mitigation: the R-r2-5 latch (written by the flip action, PIN-gated clear) + churn-breaker composition + the episode cap counting offers.
49
+ - **Risk: hand-back strands queued inbound at the relaxed boundary.** Mitigation: R-r2-6 — drain-before-step-down, proven by the deferral-ceiling lens.
50
+
51
+ ## Migration parity
52
+
53
+ - Config: both new blocks ride `getMigrationDefaults`/`applyDefaults` (existence-checked add-missing) — no dedicated migrateConfig needed; U4.2 deliberately OMITS `enabled` so the dev-gate resolves live-on-dev/dark-fleet (no stale-literal risk to strip).
54
+ - CLAUDE.md: `migrateClaudeMd` appends the Mesh Self-Healing section (content-sniffed, idempotent); `migrateFrameworkShadowCapabilities` mirrors it to AGENTS.md/GEMINI.md.
55
+ - Tracked in feature-delivery-completeness (legacyMigratorSections — constant-emitted class) + the shadow-marker list.
56
+
57
+ ## Dark-gate line-map
58
+
59
+ - UPDATED. U4.4's `preferredCaptainHandback.enabled: false` is a NEW attributed line (873) and its ~16-line block shifts every key below the leaseSelfHeal block; U4.2's sessionPool block adds NO attributed path (omitted `enabled`) but shifts keys below it by +19. `tests/unit/lint-dev-agent-dark-gate.test.ts` golden map re-verified via the attributor (24/24 green); `node scripts/lint-dev-agent-dark-gate.js` clean.
60
+
61
+ ## Rollback
62
+
63
+ - U4.2: drop the flag → ownership reverts to explicit-transfer-only + sentinel detection (today); lingering `topic-claim-annotation` records are inert derived state; a suspension clears on the next operator re-pin.
64
+ - U4.4: `enabled:false` (the shipped default) / unset F4 preference → sticky lease, today's behavior; the latch marker is inert data.
65
+
66
+ ## Tests
67
+
68
+ - `tests/unit/StaleOwnerReleaseEngine.test.ts` (28) — the full §2.2 evidence bar both-sides, the R-r2-2 bootstrap rule, arbiter uniqueness, caps/budgets/backoff/declined-demote (replicated), P19 give-ups + probe breaker, one-item-per-episode escalation, dry-run once-per-topic would-claims, the §2.3 validator, the §2.9 ownershipLeaseState table, Tier-0 LLM-free source assertion.
69
+ - `tests/unit/TopicClaimAnnotationStore.test.ts` (19) — strict schema clamps both-sides, additive registration (dual-registry), epoch-independence (no epoch field on the PUT), recordKey jail, HLC-highest-wins merge incl. `declined-demote-and-budget-survive-lease-move`, tombstones.
70
+ - `tests/unit/LeaseHandbackReconciler.test.ts` (20) — hysteresis arm/reset, absent-snapshot defer (R-r2-7 through the REAL ropeHealth seam), each clean-boundary signal, deferral ceiling → ONE notice + drain-before-step-down, claim-before-release failure matrix, legacy-peer episode stop, latch/churn/split-brain suppression, episode cap counting offers, typed offer decision, the §5 enable chokepoint, the latch file lifecycle.
71
+ - `tests/unit/FencedLease-handback-consent.test.ts` (14) — real-Ed25519 token mint/verify/forge, the consent branch fail-closed on every axis (absent/expired/replayed/reused/wrong-target/wrong-epoch/wrong-holder/forged/malformed), `handback-offer` RBAC default-deny.
72
+ - `tests/unit/LeaseCoordinator-handbackConsent.test.ts` (5) — mint holder-only, consent claim at epoch+1 over the shared CAS store, single-use burn, `failed-handback-never-leaves-zero-holders`, non-consent-grant laundering refusal.
73
+ - `tests/unit/OwnershipReconciler-staleOwnerRelease.test.ts` (8) — engine supersedes legacy Case C, independent gating, extended nonce grammar + placement pairing, suspension lenses (`operator-repin-clears-suspension`), `deferred-target-offline`, the `case-c-staleness-input-is-observer-stamped` wiring ratchet over server.ts.
74
+ - `tests/unit/stranded-topic-quorum-hosted-escalation.test.ts` (5) — `no-lease-holder-escalation-hosted-on-any-quorum-member` both sides + quorum-gated fail-closed + single-machine no-op.
75
+ - `tests/integration/stale-owner-release-and-handback-routes.test.ts` (11) — the real createRoutes + authMiddleware: 503-when-dark/absent, the §2.9 counters advancing across a simulated episode, latch POST (Bearer) + PIN-gated DELETE (403 without/with wrong PIN, latch survives), and the /pool/placement ownershipLeaseState derivation (held / stale / claimed).
76
+ - `tests/e2e/stale-owner-release-handback-alive.test.ts` (5) — feature-alive through the REAL AgentServer: stale-owner route 200-not-503 with a live dry-run episode; hand-back reconciler ticking the synthetic preferred-unhealthy→healthy transition (dry-run counters advance); latch levers end-to-end; dark → 503; 401 unauthenticated.
77
+ - Spec-named provenance/teardown lenses (resume completion pass): `working-set-pull-queued-and-resume-proceeds` (a throwing pull queues durably; claim + annotation + disclosure proceed), `forged-advert-set-from-non-owner-rejected` (PeerEndpointRecorder per-entry authenticated binding + the server.ts advert-seam ratchet: getMachineEndpoints only, never lastKnownUrl), `forged-heartbeat-from-non-owner-rejected` (PeerPresencePuller records under the DIALED identity; a body-smuggled foreign machineId never refreshes the foreign machine), `stale-owner-return-tears-down` (the closeout veto-yield ratchets + `claimSuspensionExcludesPin` both-sides unit lenses).
78
+ - Ratchets: dark-gate golden map re-verified; no-silent-fallbacks (every new catch tagged with a named fail-direction justification); feature-delivery parity + shadow markers; docs-coverage route floor restored (new /pool inventory + the multi-machine feature doc); tsc clean.
79
+
80
+ ## Deviations from spec (named, with reasons)
81
+
82
+ - **U4.2 §2.3 emission-fence wiring** (FencedOutbox at the Telegram relay chokepoint) is a declared GRADUATION prerequisite, not part of this dry-run-shipping PR — the spec itself sequences it before `dryRun:false`, and dry-run lands no claims to fence against.
83
+ - **Increment E (reply-committed watermark replication)** ships dark as specced (at-least-once stated honestly in v1); not implemented here.
84
+ - **U4.4 Feature-metrics §4** rides the existing `getFeatureMetricsRecorder` seam (`lease-handback` feature key) with per-event verdictIds rather than a bespoke metrics pipeline — same funnel every other feature uses; in-memory counters mirror every event on the status surface.
85
+ - **The PIN-gated captain-flip route** (the productized flip lever) is not built here; per spec R-r2-5 the latch POST step is the interim playbook path, and the latch write route ships now so the playbook can POST it.
86
+
87
+ <!-- tracked: CMT-1786 -->