instar 1.3.697 → 1.3.699

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 (74) hide show
  1. package/dist/commands/server.d.ts.map +1 -1
  2. package/dist/commands/server.js +81 -7
  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 +9 -0
  6. package/dist/config/ConfigDefaults.js.map +1 -1
  7. package/dist/core/CoherenceJournal.d.ts +10 -1
  8. package/dist/core/CoherenceJournal.d.ts.map +1 -1
  9. package/dist/core/CoherenceJournal.js +31 -3
  10. package/dist/core/CoherenceJournal.js.map +1 -1
  11. package/dist/core/MachinePoolRegistry.d.ts +13 -0
  12. package/dist/core/MachinePoolRegistry.d.ts.map +1 -1
  13. package/dist/core/MachinePoolRegistry.js +22 -3
  14. package/dist/core/MachinePoolRegistry.js.map +1 -1
  15. package/dist/core/MessagingToneGate.d.ts +38 -0
  16. package/dist/core/MessagingToneGate.d.ts.map +1 -1
  17. package/dist/core/MessagingToneGate.js +30 -3
  18. package/dist/core/MessagingToneGate.js.map +1 -1
  19. package/dist/core/OwnershipApplier.d.ts +21 -0
  20. package/dist/core/OwnershipApplier.d.ts.map +1 -1
  21. package/dist/core/OwnershipApplier.js +60 -5
  22. package/dist/core/OwnershipApplier.js.map +1 -1
  23. package/dist/core/OwnershipReconciler.d.ts +70 -1
  24. package/dist/core/OwnershipReconciler.d.ts.map +1 -1
  25. package/dist/core/OwnershipReconciler.js +177 -7
  26. package/dist/core/OwnershipReconciler.js.map +1 -1
  27. package/dist/core/TopicPinReplicatedStore.d.ts +68 -0
  28. package/dist/core/TopicPinReplicatedStore.d.ts.map +1 -0
  29. package/dist/core/TopicPinReplicatedStore.js +148 -0
  30. package/dist/core/TopicPinReplicatedStore.js.map +1 -0
  31. package/dist/core/TopicPlacementPinStore.d.ts +20 -2
  32. package/dist/core/TopicPlacementPinStore.d.ts.map +1 -1
  33. package/dist/core/TopicPlacementPinStore.js +5 -3
  34. package/dist/core/TopicPlacementPinStore.js.map +1 -1
  35. package/dist/core/ask-when-authorized.d.ts +32 -0
  36. package/dist/core/ask-when-authorized.d.ts.map +1 -0
  37. package/dist/core/ask-when-authorized.js +73 -0
  38. package/dist/core/ask-when-authorized.js.map +1 -0
  39. package/dist/core/bias-to-action-telemetry.d.ts +35 -0
  40. package/dist/core/bias-to-action-telemetry.d.ts.map +1 -0
  41. package/dist/core/bias-to-action-telemetry.js +40 -0
  42. package/dist/core/bias-to-action-telemetry.js.map +1 -0
  43. package/dist/core/devGatedFeatures.d.ts.map +1 -1
  44. package/dist/core/devGatedFeatures.js +12 -0
  45. package/dist/core/devGatedFeatures.js.map +1 -1
  46. package/dist/core/ownershipApplierWiring.d.ts +5 -0
  47. package/dist/core/ownershipApplierWiring.d.ts.map +1 -1
  48. package/dist/core/ownershipApplierWiring.js +3 -0
  49. package/dist/core/ownershipApplierWiring.js.map +1 -1
  50. package/dist/core/standing-authorization.d.ts +72 -0
  51. package/dist/core/standing-authorization.d.ts.map +1 -0
  52. package/dist/core/standing-authorization.js +130 -0
  53. package/dist/core/standing-authorization.js.map +1 -0
  54. package/dist/core/types.d.ts +30 -0
  55. package/dist/core/types.d.ts.map +1 -1
  56. package/dist/core/types.js.map +1 -1
  57. package/dist/messaging/TelegramAdapter.d.ts +12 -0
  58. package/dist/messaging/TelegramAdapter.d.ts.map +1 -1
  59. package/dist/messaging/TelegramAdapter.js +8 -0
  60. package/dist/messaging/TelegramAdapter.js.map +1 -1
  61. package/dist/server/AgentServer.d.ts +2 -0
  62. package/dist/server/AgentServer.d.ts.map +1 -1
  63. package/dist/server/AgentServer.js +1 -0
  64. package/dist/server/AgentServer.js.map +1 -1
  65. package/dist/server/routes.d.ts +2 -0
  66. package/dist/server/routes.d.ts.map +1 -1
  67. package/dist/server/routes.js +132 -1
  68. package/dist/server/routes.js.map +1 -1
  69. package/package.json +1 -1
  70. package/src/data/builtin-manifest.json +47 -47
  71. package/upgrades/1.3.698.md +90 -0
  72. package/upgrades/1.3.699.md +31 -0
  73. package/upgrades/side-effects/bias-to-action.md +126 -0
  74. package/upgrades/side-effects/cross-machine-reconciler-convergence.md +56 -0
@@ -0,0 +1,126 @@
1
+ # Side-Effects Review — Standing-Authorization signal for B17_FALSE_BLOCKER (bias-to-action)
2
+
3
+ **Version / slug:** `bias-to-action`
4
+ **Date:** `2026-06-29`
5
+ **Author:** `echo`
6
+ **Second-pass reviewer:** `independent reviewer subagent (Phase 5 — REQUIRED: touches the outbound gate authority + a uid/forwarded security path)`
7
+
8
+ ## Summary of the change
9
+
10
+ Feeds the existing `MessagingToneGate` B17_FALSE_BLOCKER rule a **standing-authorization signal** so that "re-asking the operator for an approval they ALREADY granted" is recognized as the false blocker it is — the structural answer to the 2026-06-27 incident where I stopped at "ready for your go-ahead to build" despite a live preapproval. Two cheap, deterministic, deps-injected producers (`detectAskWhenAuthorized` — does the outbound text seek permission?; `resolveStandingAuthorization` — is there a VERIFIED-operator, non-forwarded, in-window grant?) feed the LLM gate, which makes the semantic call (does this grant cover THIS specific, non-FLOOR action?). **Ships OBSERVE-ONLY + dev-gated DARK**: on a dev agent it records a would-fire to `logs/bias-to-action.jsonl` (uid HASH + ask-phrase token, never a raw quote) and changes NO message; the live B17 firing is a separate future `observeOnly:false` operator decision. Files: `src/core/standing-authorization.ts`, `src/core/ask-when-authorized.ts`, `src/core/bias-to-action-telemetry.ts` (new); `src/core/MessagingToneGate.ts` (signal/context fields, render, B17 sub-clause); `src/server/routes.ts` (resolver wiring + observe-only telemetry + lifeline forwarded persistence + context attach); `src/messaging/TelegramAdapter.ts` (LogEntry `forwarded` + both ingress writers); `src/core/types.ts` + `src/config/ConfigDefaults.ts` (config); `src/core/devGatedFeatures.ts` (dev-gate registry); tests + the dark-gate line-map.
11
+
12
+ ## Decision-point inventory
13
+
14
+ - `MessagingToneGate` **B17_FALSE_BLOCKER** rule (LLM authority) — **modify** — gains a STANDING-AUTHORIZATION sub-clause: an "asking for approval" message the existing carve-out would exempt becomes a B17 false blocker WHEN a verified grant plausibly covers THIS exact, non-FLOOR action. Judged by meaning; under-fire bias on every uncertainty. Inert until graduated.
15
+ - `detectAskWhenAuthorized` (signal-producer) — **add** — cheap deterministic "is the agent asking permission?" signal. No authority.
16
+ - `resolveStandingAuthorization` (signal-producer) — **add** — deterministic "is there a verified-operator non-forwarded in-window grant?" resolver. Deps-injected; no authority.
17
+ - `bias-to-action-telemetry.buildBiasToActionWouldFire` (observe-only telemetry) — **add** — pure record builder; no authority, no delivery effect.
18
+ - Telegram message-log `forwarded` field (data) — **add** — explicit boolean on both ingress writers; pure data, no decision surface of its own.
19
+
20
+ ---
21
+
22
+ ## 1. Over-block
23
+
24
+ **Shipped state (observe-only): NONE — the gate verdict is never changed.** In observe-only mode (the default, and the only mode shipping) `standingAuthorization` is NOT attached to the gate context, so B17 behaves exactly as today; the feature only writes a telemetry line.
25
+
26
+ **When graduated (`observeOnly:false`, a future operator decision):** the live risk is suppressing a NEEDED ask — the gate misjudges that an in-scope grant covers an action it doesn't, and tells the agent to act instead of asking. Mitigations baked in: (a) the gate judges by MEANING with full conversational context (a smart authority, not a brittle matcher); (b) a hard FLOOR-action carve-out (irreversible / cost-bearing / out-of-scope / policy-sensitive ALWAYS legitimately needs the ask, even with a live in-scope grant); (c) a decisive UNDER-FIRE bias — any uncertainty about coverage, floor-ness, or grant presence does NOT fire B17 (a needless ask is harmless; suppressing a needed one is the harm); (d) observe-only-first with a named FP-rate exit criterion before graduation.
27
+
28
+ ---
29
+
30
+ ## 2. Under-block
31
+
32
+ A genuine false-blocker ask still PASSES (no B17 fire) whenever the grant cannot be PROVEN: a forwarded-unknown legacy message-log row (no `forwarded` field), a row with a missing/blank uid, a grant outside the recency window, or a topic with no bound verified operator. This is the deliberate fail-safe direction (an unprovable grant never suppresses an ask). Also: the resolver's grant-phrase list is finite, so an operator's idiosyncratic phrasing of a grant may not be recognized — again failing toward sending the ask, never toward suppressing it.
33
+
34
+ ---
35
+
36
+ ## 3. Level-of-abstraction fit
37
+
38
+ Correct by construction. The two new modules are **detectors** (cheap, deterministic, deps-injected, no context, no authority) that FEED the existing **smart authority** (the LLM-backed B17 rule, which already has recent-message context). This is exactly the signal→authority shape `docs/signal-vs-authority.md` prescribes — not a new parallel brittle gate. The resolver re-uses the existing `TopicOperatorStore.asVerifiedOperator` (the authoritative verified-operator binding) and the existing `TelegramAdapter.getTopicHistory` read path rather than re-implementing either.
39
+
40
+ ---
41
+
42
+ ## 4. Signal vs authority compliance
43
+
44
+ **Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
45
+
46
+ - [x] **No — this change produces a signal consumed by an existing smart gate.**
47
+
48
+ `detectAskWhenAuthorized` and `resolveStandingAuthorization` produce signals/context only; they hold zero block authority. The B17 decision stays with the LLM gate, which already reasons over conversational context and carve-outs and judges the new sub-clause BY MEANING (the gate-prompts-judge-by-meaning ratchet still passes). The observe-only telemetry is a pure record producer. No brittle logic anywhere owns a block/allow decision.
49
+
50
+ ---
51
+
52
+ ## 5. Interactions
53
+
54
+ - **Shadowing / citation precedence:** B17's citation precedence (B15 > B16 > B17 > B18) is unchanged. The standing-authorization context can NEVER flip a B1–B7/B15 leak HOLD — D7 renders `evidenceQuote` as boundary-quoted, secret-scrubbed untrusted DATA, and the gate's existing leak rules take precedence. The MessagingToneGate suite (incl. the leak-HOLD regression) passes.
55
+ - **Double-fire:** none. The resolver/telemetry run once per outbound review inside `evaluateOutbound`, alongside the other signal producers, before the single `review()` call.
56
+ - **Races:** the observe-only log write is append-only fire-and-forget; the `forwarded` field is additive to `LogEntry` (both writers persist it; the tail-cache and JSONL scan read it). No shared mutable state with concurrent cleanup.
57
+ - **Feedback loops:** none — the telemetry log is write-only observability; nothing reads it back into the gate.
58
+ - **Forwarded-persistence interaction:** the explicit `forwarded: false` diverges from the adjacent "spread `{forwarded:true}`, omit when false" idiom (TOPIC-PROFILE round-5). Verified the TOPIC-PROFILE forwarded-rejection still reads `forwarded === true` (an explicit `false` is correctly treated as not-forwarded by that path), and the TelegramAdapter suite passes.
59
+
60
+ ---
61
+
62
+ ## 6. External surfaces
63
+
64
+ - **Other agents / users:** none — dev-gated dark on the fleet (`monitoring.biasToAction.enabled` omitted → resolves dark off-dev). No HTTP route added.
65
+ - **Persistent state:** appends to `logs/bias-to-action.jsonl` (machine-local, append-only, observe-only telemetry — uid HASH + ask-phrase token + source enum + grant timestamp; NEVER a raw uid or raw operator quote). Adds a `forwarded` boolean to Telegram message-log rows (additive; other log readers ignore unknown fields).
66
+ - **External systems:** none (no Telegram/Slack/GitHub send; observe-only writes a local log).
67
+ - **Operator surface (Mobile-Complete):** **No operator-facing actions** — no route, no form, no PIN-gated action. The graduation lever is a config flag set by the operator out-of-band, not an in-product action.
68
+
69
+ ---
70
+
71
+ ## 6b. Operator-surface quality
72
+
73
+ **No operator surface — not applicable.** This change stages no `dashboard/*` renderer, approval page, or grant/secret form.
74
+
75
+ ---
76
+
77
+ ## 7. Multi-machine posture (Cross-Machine Coherence)
78
+
79
+ **machine-local BY DESIGN**, with reason: the observe-only telemetry exists to measure THIS detector's false-positive rate on THIS machine's real outbound before any warn/block surface is built — a pure per-machine observability stream (`logs/bias-to-action.jsonl`), exactly like the principalCoherence and raw-text-request observe-only logs. The resolver reads the LOCAL topic's verified-operator binding (`TopicOperatorStore`, already machine-local / topic-holder-authoritative) and the LOCAL message-log history; a topic moved to another machine resolves against the holder's own operator-binding + history (consistent with the existing operator-binding read — the working-set carrier already follows a moved topic's message log).
80
+
81
+ - **User-facing notices?** No — observe-only writes a log; it sends nothing, so no one-voice gating needed.
82
+ - **Durable state stranding on transfer?** The telemetry log is per-machine FP measurement (not behavior), so it intentionally does not follow a topic; the `forwarded` field rides the same message-log the working-set carrier already moves.
83
+ - **URLs across machine boundaries?** None generated.
84
+
85
+ ---
86
+
87
+ ## 8. Rollback cost
88
+
89
+ - **Hot-fix:** pure code + config + one new telemetry log. Revert the branch, ship the next patch. The fleet sees NO behavioral/verdict change (the resolver + grant context + telemetry are dev-gated dark, so no standing-authorization logic runs and no message is ever altered there). The one fleet-wide difference is static: the B17 prompt now carries the STANDING-AUTHORIZATION sub-clause text + the `renderStandingAuthorization` "no verified standing authorization" placeholder — rendered unconditionally like every other static B-rule, and self-neutralizing (present:false → the sub-clause does not apply and the existing approval carve-out stands; the 55-test gate suite incl. leak-HOLD passes against it). Even a dev agent never altered a message (observe-only). So there is NO user-visible regression to roll back.
90
+ - **Data migration:** none required. `logs/bias-to-action.jsonl` is append-only telemetry, safe to leave or delete. The new `forwarded` field on message-log rows is additive and backward-compatible (legacy rows read as forwarded-unknown, which the resolver treats fail-safe).
91
+ - **Agent state repair:** none — no agent needs notification or reset.
92
+ - **User visibility:** none during the rollback window (observe-only, dark on fleet).
93
+
94
+ ---
95
+
96
+ ## Conclusion
97
+
98
+ The change is the minimal, correctly-layered embodiment of the approved spec: two deterministic signal-producers feeding the existing smart B17 authority, shipped observe-only + dev-gated dark so it measures its own false-positive rate before it ever changes a message. The review surfaced and confirmed the three load-bearing safety properties — (1) the standing-authorization context can never flip a leak HOLD, (2) every uncertainty fails toward sending the ask, and (3) a forwarded/unattributable grant never counts — each covered by tests on the real read/write paths. The forwarded-persistence work (explicit `false` on both ingress paths) is the load-bearing security substrate and is wiring-tested through the real `getTopicHistory`. Clear to ship as observe-only; live B17 firing remains a deliberate future operator decision.
99
+
100
+ ---
101
+
102
+ ## Second-pass review (if required)
103
+
104
+ **Reviewer:** independent reviewer subagent (adversarial, security-focused)
105
+ **Independent read of the artifact: CONCUR**
106
+
107
+ The reviewer independently traced all six security-critical claims against the actual code + the wiring/gate tests and could not break any:
108
+
109
+ 1. **Leak-HOLD flip** — Safe. `standingAuthorization` is attached to the gate context ONLY in the `observeOnly === false` branch; in the shipped observe-only/dark state it stays `undefined`. When attached, `renderStandingAuthorization` JSON-encodes the quote in a fresh `AUTH_BOUNDARY_…`, length-bounds to 280, and the route pre-scrubs via `scrubString`; labeled untrusted DATA; citation precedence (B15>B16>B17>B18) untouched. Leak-HOLD regressions pass.
110
+ 2. **Attribution** — Safe. Matched on `String(telegramUserId) === String(verified uid)` (authenticated `msg.from.id`), never `fromUser`/content-name; blank uid is a non-match (no wildcard); the `fromUser` filter excludes the agent's own sends (no self-grant).
111
+ 3. **Forwarded third-party content** — Safe. Resolver counts only strict `forwarded === false`; both ingress writers persist an explicit boolean; legacy rows (no field) fail-safe; the lone downstream `forwarded` consumer (topicProfileIngress) is a truthy check, so explicit `false` doesn't break it.
112
+ 4. **Telemetry leak** — Safe. Emits only ISO time, kind, topicId, source enum, ask-phrase token (agent's own canned phrase, bounded), uid HASH (12 hex), grantedAt. No raw uid, no operator words.
113
+ 5. **Fail-open / inert** — Confirmed. Whole block is try/catch fail-open; telemetry write independently try-caught; on the fleet `resolveDevAgentGate` returns false so nothing runs.
114
+ 6. **Dev-gate** — Correct. ConfigDefaults omits `enabled`; `resolveDevAgentGate` funnel; registered in `DEV_GATED_FEATURES`. Observe-only path confirmed NOT to attach the grant.
115
+
116
+ One non-blocking observation (no fix to code required): the B17 sub-clause prompt text renders fleet-wide even where the resolver is dark — addressed by softening §8's wording above (the fleet sees no behavioral change; the static prompt section is self-neutralizing). Nothing security-critical is broken.
117
+
118
+ ---
119
+
120
+ ## Evidence pointers
121
+
122
+ - Unit: `tests/unit/standing-authorization.test.ts` (11), `tests/unit/ask-when-authorized.test.ts` (16), `tests/unit/bias-to-action-telemetry.test.ts` (6).
123
+ - Wiring-integrity (real read/write paths): `tests/unit/bias-to-action-wiring.test.ts` (8) — forwarded persistence on BOTH ingress paths incl. explicit `false`; resolver fed from the real `getTopicHistory` honoring verified-uid-only + forwarded + no-operator fail-safes.
124
+ - Gate: `tests/unit/MessagingToneGate.test.ts` (55) incl. the leak-HOLD regression + gate-prompts-judge-by-meaning ratchet.
125
+ - Ratchets: `npm run lint` clean (incl. `lint-dev-agent-dark-gate`); `tests/unit/lint-dev-agent-dark-gate.test.ts` line-map updated (+9 shift documented).
126
+ - Spec: `docs/specs/BIAS-TO-ACTION-SPEC.md` (review-convergence + approved:true), ELI16 `docs/specs/BIAS-TO-ACTION-SPEC.eli16.md`.
@@ -0,0 +1,56 @@
1
+ # Side-Effects Review — Cross-Machine Ownership-Reconciler Convergence
2
+
3
+ **Version / slug:** `cross-machine-reconciler-convergence`
4
+ **Date:** 2026-06-30
5
+ **Author:** echo (autonomous pre-approved run, topic 28744)
6
+ **Second-pass reviewer:** echo second-pass subagent (high-risk: ownership/routing decision surface)
7
+
8
+ ## Summary of the change
9
+
10
+ Closes the cross-machine "stuck move" bug: a conversation pinned to machine T while machine S still owns it never converged. Three root causes, all live-verified Laptop↔Mini: (1) a false-positive **clock-skew quarantine** — `refreshPool()` fed the coarse git-synced file heartbeat's timestamp into the live 5-min clock-skew FSM, permanently quarantining the peer (fix: a `coarseHeartbeat` flag so coarse beats refresh liveness but never drive the skew FSM); (2) the **pin (move-intent) never replicated** to the owning machine (fix: a new `topic-pin-record` replicated-record kind on the WS2 rails + `TopicPinReplicatedStore` advisory consumer + reconciler HLC-precedence between local and advisory pins, validated known+online); (3) the **`transferring` handoff signal never replicated** (fix: extend `PlacementData` with `status`/`transferTo`/`timestamp`/`drainInFlight`, thread it at the shared `emitPlacement` helper, and have `OwnershipApplier` materialize the transferring state — validated `transferTo`, epoch-fenced, timestamp-clamped — so the target machine claims). Plus a stuck-transferring `abort-transfer` recovery case, the `/pool/reconciler` observability route, and the **testing-integrity root fix**: `makeSim` rebuilt so every reconciler test runs separate-per-machine stores joined only by a journal pump (the shared-store harness masked this bug for months). Files: `MachinePoolRegistry.ts`, `CoherenceJournal.ts`, `OwnershipApplier.ts`/`ownershipApplierWiring.ts`, `OwnershipReconciler.ts`, `TopicPlacementPinStore.ts`, new `TopicPinReplicatedStore.ts`, `server.ts`, `routes.ts`, `AgentServer.ts`, tests. Decision points touched: the WS1.3 OwnershipReconciler (ownership transfer/claim/abort), the OwnershipApplier (cross-machine ownership materialization), the clock-skew quarantine FSM.
11
+
12
+ ## Decision-point inventory
13
+
14
+ - `OwnershipReconciler.tick()` (ownership transfer/claim/force-claim/abort) — **modify** — now reads an effective pin (local + HLC-ordered advisory) + a new abort-transfer recovery case; still epoch-fenced + death-evidence-gated, never a brittle timer steal.
15
+ - `OwnershipApplier.tick()` (materializes replicated ownership) — **modify** — now materializes `transferring` (validated `transferTo`→downgrade, epoch-fenced, timestamp-clamped), not only `active`.
16
+ - `MachinePoolRegistry.recordHeartbeat()` clock-skew FSM — **modify** — a `coarseHeartbeat` beat no longer drives the FSM (abstains; liveness only).
17
+ - `topic-pin-record` replicated kind + `TopicPinReplicatedStore` — **add** — advisory move-intent replication (HLC, tombstone, quarantine via the WS2 envelope).
18
+ - `GET /pool/reconciler` — **add** — read-only observability (status + per-topic explain); 503 when absent.
19
+
20
+ ## 1. Over-block
21
+
22
+ No block/allow message surface. The closest "reject" behaviors are protective and tightly scoped: a replicated `transferring` whose `transferTo` is unknown/offline/==owner is DOWNGRADED to `active(owner)` (never materialized as un-claimable) — it does not reject a legitimate handoff, it falls back to the safe state; an advisory pin toward an offline/unknown machine is ignored (the move waits, never misroutes). A legitimate handoff to a known+online machine is never downgraded. Over-rejection risk: a brief window where a just-joined machine isn't yet in `machines()` could make a valid advisory pin/transferTo read as "unknown" → the move waits one tick until membership populates (self-healing, not a permanent block).
23
+
24
+ ## 2. Under-block
25
+
26
+ A genuinely STALE peer stream is the in-scope adversary (single-agent model — no hostile tenant). Residual misses, all bounded: a corrupt future-dated `timestamp` is clamped (can't defeat the deadline); a corrupt huge epoch is fenced; a stale pin is HLC-ordered out by a fresher one AND requires an online target. What it does NOT yet cover (documented follow-ups, tracked): `pendingReplacement` honest-pending surfacing for a not-yet-converged advisory move, and an explicit tombstone-on-clear emit from `/pool/transfer` (today a clear relies on a re-pin's higher HLC). These are additive surfacing/cleanup, not correctness holes — convergence still completes; the gap is only the "still moving" UX cue and faster clear propagation. <!-- tracked: CMT-1829 -->
27
+
28
+ ## 3. Level-of-abstraction fit
29
+
30
+ Correct layer. The fixes sit exactly where the decisions already live: the clock-skew abstention is in the registry that owns the FSM; the pin replication rides the EXISTING WS2 replicated-record machinery (HLC envelope, tombstone, quarantine, retention) rather than a new bespoke transport (Round-1 review explicitly rejected a wall-clock `topic-pin` kind); the transferring replication extends the EXISTING placement journal + applier rather than a parallel path. The reconciler consumes signals (advisory pins, machine liveness) and acts within the existing FSM/CAS authority — it does not add a new authority.
31
+
32
+ ## 4. Signal vs authority compliance
33
+
34
+ Compliant. A replicated pin is a **signal** (advisory move-intent). The reconciler's effective-pin merge IS read by every branch (including force-claim Case C when the pin names self), but the force-claim DECISION is gated on death-evidence + quorum derived from machine LIVENESS (`machines()`), never on the advisory pin — a stale/corrupt pin cannot manufacture death evidence, so it can never cause a LIVE-owner seat-steal. The only thing an advisory pin can trigger is the owner's OWN cooperative transfer (owner-gated FSM action). The clock-skew change REMOVES a brittle authority (a coarse file timestamp was wrongly gating placement eligibility). `/pool/reconciler` is observe-only. No brittle check gains blocking authority.
35
+
36
+ ## 5. Interactions
37
+
38
+ The transferring extension threads through the SHARED `emitPlacement` helper so every CAS site (reconciler, drain runner, user-move) emits coherently — avoids the "only the reconciler path threads it" shadow. The applier's owner-anchored equal-epoch tie-break prevents a forged peer `active(e)` from shadowing the true owner's `transferring(e)`. The advisory pin is unioned with the local pin (HLC precedence) so a stale local self-pin can't shadow a fresher replicated intent (the N3 recurrence). No double-fire: the journal op-key dedupes a same-(recordKey,hlc) retry. The abort-transfer is owner-only (FSM-gated), so two machines can't both abort.
39
+
40
+ ## 6. External surfaces
41
+
42
+ New: `GET /pool/reconciler` (Bearer-gated, credential-free — topic ids/machine ids/epochs/decision reasons only). New journal kind `topic-pin-record` (additive — older peers ignore unknown kinds; the feature converges only when BOTH machines are new, a degrade to the pre-fix stuck-move, never a regression). New config flag `multiMachine.seamlessness.ws13PinReplicate` (dev-live/fleet-dark). The cross-machine convergence latency is eventually-consistent (~45–90s) — surfaced honestly. Timing/runtime dependence: convergence depends on the journal replicating (best-effort HTTP pull) — a lost pin record delays (never corrupts) a move; the reconciler retries each tick.
43
+
44
+ ## 7. Multi-machine posture (Cross-Machine Coherence)
45
+
46
+ This change IS the multi-machine feature. Posture per surface: `topic-pin-record` = **replicated** (WS2 rails, HLC; advisory-on-receive, local pin write stays router-authenticated); `topic-placement` status/transferTo = **replicated** (existing placement journal + applier); `TopicPinReplicatedStore` = **machine-local view of replicated data**; `/pool/reconciler` = **proxied-on-read** via the standby (503 when dark); reconciler tick / appliers = **machine-local BY DESIGN** (each reconciles its own view; the journal is the shared arbiter). No single-machine assumption — a single-machine agent is a strict no-op (no peers → no advisory pins, the reconciler `machines()<2` no-ops). User-facing: a non-converging move surfaces via the reconciler explain + (follow-up) pendingReplacement — no silent stuck state.
47
+
48
+ ## 8. Rollback cost
49
+
50
+ Cheap and layered. Everything ships dark behind `ws13Reconcile`/`ws13DryRun` (dev-live/fleet-dark) + `ws13PinReplicate` (independent sub-flag for the pin-replicator). Back-out paths, in order: set `ws13PinReplicate` off (stops pin emission — the topic-pin store entry is absent → the emitter no-ops); set `ws13DryRun` true (reconciler logs but lands no CAS); set `ws13Reconcile` off (reconciler strict no-op). No data migration needed — the new `topic-pin-record` stream is additive and self-prunes (retention rotateKeep:4); the `TopicPin.hlc` field is optional (absent ⇒ derived-from-updatedAt fallback). No agent-state repair. A bad release is a config flip, not a hot-fix.
51
+
52
+ ---
53
+
54
+ ## Second-pass reviewer response
55
+
56
+ **Concur with the review.** An independent reviewer verified every load-bearing safety claim against the code: (1) signal-vs-authority — a replicated pin can only yield a `preferredMachine` the owner transfers TOWARD; Case A fires only when `owner === self` (the owner releasing its own topic), and the force-claim Case C is gated independently on death-evidence + quorum from `machines()` liveness, so a stale/corrupt pin cannot manufacture a live-owner steal (confirmed OwnershipReconciler.ts effectivePins + Case A/C); (2) the applier's `transferTo` validation→downgrade, epoch fence, and timestamp clamp match the code exactly; (3) the coarse-heartbeat abstention genuinely never drives the skew FSM while still refreshing liveness; (4) the threading + route + multi-machine posture + rollback all check out. The one concern raised was a §4 phrasing imprecision ("never reads the advisory store") — corrected above to credit the death-evidence+quorum gate (the actual safety mechanism) rather than overstating store isolation. Not a correctness or safety defect; the code's behavior was already safe.