instar 1.3.18 → 1.3.19

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 (95) hide show
  1. package/dist/commands/server.d.ts.map +1 -1
  2. package/dist/commands/server.js +104 -6
  3. package/dist/commands/server.js.map +1 -1
  4. package/dist/core/FencedLease.d.ts +126 -0
  5. package/dist/core/FencedLease.d.ts.map +1 -0
  6. package/dist/core/FencedLease.js +189 -0
  7. package/dist/core/FencedLease.js.map +1 -0
  8. package/dist/core/GitLeaseStore.d.ts +46 -0
  9. package/dist/core/GitLeaseStore.d.ts.map +1 -0
  10. package/dist/core/GitLeaseStore.js +79 -0
  11. package/dist/core/GitLeaseStore.js.map +1 -0
  12. package/dist/core/GitSync.d.ts +19 -0
  13. package/dist/core/GitSync.d.ts.map +1 -1
  14. package/dist/core/GitSync.js +77 -0
  15. package/dist/core/GitSync.js.map +1 -1
  16. package/dist/core/HandoffSentinel.d.ts +80 -0
  17. package/dist/core/HandoffSentinel.d.ts.map +1 -0
  18. package/dist/core/HandoffSentinel.js +141 -0
  19. package/dist/core/HandoffSentinel.js.map +1 -0
  20. package/dist/core/LeaseCoordinator.d.ts +141 -0
  21. package/dist/core/LeaseCoordinator.d.ts.map +1 -0
  22. package/dist/core/LeaseCoordinator.js +226 -0
  23. package/dist/core/LeaseCoordinator.js.map +1 -0
  24. package/dist/core/LiveTailBuffer.d.ts +64 -0
  25. package/dist/core/LiveTailBuffer.d.ts.map +1 -0
  26. package/dist/core/LiveTailBuffer.js +122 -0
  27. package/dist/core/LiveTailBuffer.js.map +1 -0
  28. package/dist/core/MultiMachineCoordinator.d.ts +49 -0
  29. package/dist/core/MultiMachineCoordinator.d.ts.map +1 -1
  30. package/dist/core/MultiMachineCoordinator.js +136 -0
  31. package/dist/core/MultiMachineCoordinator.js.map +1 -1
  32. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  33. package/dist/core/PostUpdateMigrator.js +24 -0
  34. package/dist/core/PostUpdateMigrator.js.map +1 -1
  35. package/dist/core/RegistrySyncDebouncer.d.ts +77 -0
  36. package/dist/core/RegistrySyncDebouncer.d.ts.map +1 -0
  37. package/dist/core/RegistrySyncDebouncer.js +131 -0
  38. package/dist/core/RegistrySyncDebouncer.js.map +1 -0
  39. package/dist/core/liveTailRedaction.d.ts +36 -0
  40. package/dist/core/liveTailRedaction.d.ts.map +1 -0
  41. package/dist/core/liveTailRedaction.js +57 -0
  42. package/dist/core/liveTailRedaction.js.map +1 -0
  43. package/dist/core/registryReplayGuard.d.ts +59 -0
  44. package/dist/core/registryReplayGuard.d.ts.map +1 -0
  45. package/dist/core/registryReplayGuard.js +86 -0
  46. package/dist/core/registryReplayGuard.js.map +1 -0
  47. package/dist/core/seamlessnessConfig.d.ts +79 -0
  48. package/dist/core/seamlessnessConfig.d.ts.map +1 -0
  49. package/dist/core/seamlessnessConfig.js +131 -0
  50. package/dist/core/seamlessnessConfig.js.map +1 -0
  51. package/dist/core/types.d.ts +117 -0
  52. package/dist/core/types.d.ts.map +1 -1
  53. package/dist/core/types.js.map +1 -1
  54. package/dist/core/wireRegistrySync.d.ts +32 -0
  55. package/dist/core/wireRegistrySync.d.ts.map +1 -0
  56. package/dist/core/wireRegistrySync.js +31 -0
  57. package/dist/core/wireRegistrySync.js.map +1 -0
  58. package/dist/feedback-factory/dispatch/dispatch.d.ts +47 -0
  59. package/dist/feedback-factory/dispatch/dispatch.d.ts.map +1 -0
  60. package/dist/feedback-factory/dispatch/dispatch.js +72 -0
  61. package/dist/feedback-factory/dispatch/dispatch.js.map +1 -0
  62. package/dist/messaging/FencedOutbox.d.ts +42 -0
  63. package/dist/messaging/FencedOutbox.d.ts.map +1 -0
  64. package/dist/messaging/FencedOutbox.js +60 -0
  65. package/dist/messaging/FencedOutbox.js.map +1 -0
  66. package/dist/messaging/MessageProcessingLedger.d.ts +105 -0
  67. package/dist/messaging/MessageProcessingLedger.d.ts.map +1 -0
  68. package/dist/messaging/MessageProcessingLedger.js +206 -0
  69. package/dist/messaging/MessageProcessingLedger.js.map +1 -0
  70. package/dist/messaging/TelegramAdapter.d.ts +18 -1
  71. package/dist/messaging/TelegramAdapter.d.ts.map +1 -1
  72. package/dist/messaging/TelegramAdapter.js +50 -0
  73. package/dist/messaging/TelegramAdapter.js.map +1 -1
  74. package/dist/scaffold/templates.d.ts.map +1 -1
  75. package/dist/scaffold/templates.js +7 -0
  76. package/dist/scaffold/templates.js.map +1 -1
  77. package/dist/server/AgentServer.d.ts.map +1 -1
  78. package/dist/server/AgentServer.js +1 -0
  79. package/dist/server/AgentServer.js.map +1 -1
  80. package/dist/server/routes.d.ts +2 -0
  81. package/dist/server/routes.d.ts.map +1 -1
  82. package/dist/server/routes.js +6 -0
  83. package/dist/server/routes.js.map +1 -1
  84. package/package.json +1 -1
  85. package/src/data/builtin-manifest.json +64 -64
  86. package/src/scaffold/templates.ts +7 -0
  87. package/upgrades/1.3.19.md +23 -0
  88. package/upgrades/side-effects/PR-BODY-cross-machine-seamlessness.md +63 -0
  89. package/upgrades/side-effects/cross-machine-seamlessness-g1-lease.md +71 -0
  90. package/upgrades/side-effects/cross-machine-seamlessness-g2-foundation.md +75 -0
  91. package/upgrades/side-effects/cross-machine-seamlessness-g3-telegram-contract.md +36 -0
  92. package/upgrades/side-effects/cross-machine-seamlessness-g3a-ledger-handoff.md +56 -0
  93. package/upgrades/side-effects/cross-machine-seamlessness-g3bc-livetail-outbox.md +51 -0
  94. package/upgrades/side-effects/cross-machine-seamlessness-p4-awareness-observability.md +54 -0
  95. package/upgrades/side-effects/feedback-factory-dispatch.md +31 -0
@@ -0,0 +1,63 @@
1
+ # Cross-Machine Seamlessness — one agent that follows the user across machines
2
+
3
+ Implements the converged + approved **Cross-Machine Seamlessness** spec
4
+ (`docs/specs/CROSS-MACHINE-SEAMLESSNESS-SPEC.md`), grounded in the 2026-05-26
5
+ real-hardware Phase-0 run. Closes the gap between "a backup machine can take
6
+ over" and "the same agent follows you across machines with no amnesia and no
7
+ double-replies."
8
+
9
+ ## What this fixes (the Phase-0 findings)
10
+ - **G2 — auto-sync never fired.** A self-electing machine changed its role but
11
+ nothing pushed it to git. Now `roleChange`/`leaseEpochChange` → a debounced,
12
+ single-writer `RegistrySyncDebouncer` push (the wiring is named + a
13
+ wiring-integrity test reproduces the original failure and proves it caught).
14
+ - **G1 — split-brain.** Two machines both showed `awake`. Replaced with a
15
+ **fenced lease** (clock-proof, epoch-fenced CAS); `shouldSkipProcessing` gates
16
+ on `holdsLease()`, so a wedged old-awake is structurally fenced out. A holder
17
+ that can't confirm its lease over a shared medium for > leaseTtlMs
18
+ self-suspends (no partitioned-old-awake split-brain).
19
+ - **G3 — the seamless channel experience.** Durable message-processing ledger
20
+ (no-loss / no-duplicate-reply, structural), fencing-gated outbox, encrypted-
21
+ live-tail buffer with sequence-dedup + secret redaction, and a HandoffSentinel
22
+ that yields the lease ONLY on a verified ack + passing validation (else stays
23
+ awake). Channel Seamlessness Contract implemented by Telegram (reference) +
24
+ Slack (second target).
25
+
26
+ ## Layers (commit-by-commit)
27
+ 1. Foundations + G2 auto-sync (types, config resolver/validator, FencedLease,
28
+ RegistrySyncDebouncer, registryReplayGuard, the named wiring).
29
+ 2. G1 fenced-lease leader resolution (LeaseCoordinator + GitLeaseStore + live
30
+ coordinator integration).
31
+ 3. G3 idempotent ledger + handoff lifecycle + adapter contract.
32
+ 4. G3 live-tail buffer + redaction + fenced outbox.
33
+ 5. Observability (`/health.multiMachine.syncStatus`) + agent awareness
34
+ (CLAUDE.md generate + migrate).
35
+ 6. Telegram + Slack Channel Seamlessness Contract.
36
+
37
+ ## Tunability, migration parity, observability
38
+ - All §9 knobs under `.instar/config.json` → `multiMachine`, with startup
39
+ invariant validation (a violating config is rejected, not run silently).
40
+ - Knobs default in code (existing agents get the feature without config bloat);
41
+ server code ships automatically; SQLite stores self-initialize; CLAUDE.md
42
+ awareness is migrated to existing agents.
43
+ - `/health.multiMachine.syncStatus` (leaseHolder, leaseEpoch, holdsLease,
44
+ splitBrainState, awakeMachineCount, protocolVersion) — the Phase-1
45
+ feature-alive surface.
46
+
47
+ ## Tests
48
+ ~100 new unit tests (real Ed25519, real SQLite): lease CAS/fencing/clock-skew/
49
+ livelock, registry replay/epoch/unknown-key guard, the Phase-0-catching push
50
+ wiring test, message-ledger redelivery/stuck-recovery/dual-medium marker,
51
+ fenced-outbox suppression, live-tail sequence-dedup/gap-discard, redaction,
52
+ HandoffSentinel verify-before-yield (every abort gate), feature-alive syncStatus,
53
+ and Telegram + Slack contract conformance.
54
+
55
+ ## Deliberate staging (honest)
56
+ - The **live cross-machine handoff TRANSPORT** (server-to-server live-tail + ack
57
+ over the wire) and the **inbound-dispatch integration** of the ledger/outbox
58
+ are the validation-phase work proven by the real-hardware gate (test-as-self
59
+ over Telegram on two machines). The machinery is built + unit-tested here; the
60
+ wire integration lands with the hardware validation.
61
+ - Onboarding/self-propagation gaps are tracked to ACT-156 (companion spec).
62
+
63
+ 🤖 Generated with [Claude Code](https://claude.com/claude-code)
@@ -0,0 +1,71 @@
1
+ # Side-Effects Review — Cross-Machine Seamlessness: G1 fenced-lease integration
2
+
3
+ **Spec:** docs/specs/CROSS-MACHINE-SEAMLESSNESS-SPEC.md §6 + §8 G1 (converged, approved)
4
+ **Increment:** wire the FencedLease primitive into the live coordinator as the
5
+ authority for awake/standby — closing the Phase-0 split-brain structurally.
6
+
7
+ ## What changed
8
+ - `src/core/LeaseCoordinator.ts` (new) — drives FencedLease over a durable store
9
+ (+ optional tunnel): acquisition (bounded-retry CAS + livelock backoff),
10
+ renewal with a medium-agnostic confirmation requirement, the `holdsLease()`
11
+ fencing gate, epoch-advance signal, and unresolvable-split escalation. Tracks
12
+ `selfIssued` (the holder's freshest self-signed lease) so a renewal's new
13
+ expiry is honored without churning git.
14
+ - `src/core/GitLeaseStore.ts` (new) — durable CAS over git: pull-rebase →
15
+ epoch-check → write → push-or-reject-reread (never force-push). `refresh()`
16
+ does a same-epoch durable expiry bump and declines if superseded. Bumps the
17
+ holder's syncSequence/authoredUnderEpoch so the lease write passes peers'
18
+ replay guard.
19
+ - `src/core/GitSync.ts` — added `pullRebase()` (targeted, no auto-commit) for
20
+ the lease CAS. Additive.
21
+ - `src/core/MultiMachineCoordinator.ts` — optional `leaseCoordinator`;
22
+ `shouldSkipProcessing()` returns `!holdsLease()` when attached (the structural
23
+ demotion Phase-0 lacked); `attachLeaseCoordinator` / `initializeLease` /
24
+ `tickLease` / `reconcileRoleToLease`; the monitor tick drives the lease when
25
+ attached (heartbeat retained for liveness display only).
26
+ - `src/commands/server.ts` — constructs the lease crypto (signs with the
27
+ identity key whose pubkey is registered; verifies peers via the SAS-paired
28
+ registry), the GitLeaseStore, and the LeaseCoordinator; attaches + initializes
29
+ it; wires onEpochAdvance → `leaseEpochChange` (→ durable registry push).
30
+
31
+ ## Over-block / under-block
32
+ - **Over-block:** `shouldSkipProcessing` now gates on `holdsLease()`. Risk: a
33
+ single git-backed machine that briefly can't read its own lease would skip
34
+ processing. Mitigated — a single machine acquires epoch 1 trivially at
35
+ `initializeLease()` (no peer to contend), and `holdsLease` reads its own
36
+ `selfIssued` lease (in-memory), not requiring git. Non-git / single-machine /
37
+ independent-mode meshes have NO leaseCoordinator → fall back to the existing
38
+ heartbeat path unchanged.
39
+ - **Under-block:** git-only (no tunnel) means lease transfer latency is bounded
40
+ by git cadence, not RTT. Acceptable for the launch increment (Phase 0 proved
41
+ pairing works over git alone). The split-brain SAFETY is preserved via the
42
+ refresh-confirmation self-suspend (a partitioned holder stops within
43
+ leaseTtlMs). The tunnel ACCELERATOR is a tracked follow-on, not a correctness
44
+ gap.
45
+
46
+ ## Signal vs authority
47
+ - Detectors emit signals (`checkForUnresolvableSplit`, sync-health); only the
48
+ lease HOLDER mutates authority-bearing state. A non-holder fails its own
49
+ fencing check and self-fences — no cooperative demotion needed.
50
+
51
+ ## Interactions
52
+ - The lease epoch advance fires `leaseEpochChange`, which the G2 wireRegistrySync
53
+ already subscribes → the new epoch is pushed durably. Closed loop.
54
+ - `reconcileRoleToLease` updates `_role` + StateManager read-only + heartbeat
55
+ writer to match lease holding — keeps existing role-dependent code paths
56
+ (scheduler `coordinator.isAwake`, etc.) coherent.
57
+ - Heartbeat failover logic is bypassed when a lease is attached (the lease is
58
+ the single authority) — avoids two mechanisms fighting over role.
59
+
60
+ ## Rollback cost
61
+ - Low–moderate. The lease is only active when git-backed multi-machine is
62
+ enabled AND construction succeeds (inside the existing try/catch). Reverting
63
+ the server.ts block + the coordinator's lease methods restores the
64
+ heartbeat-only behavior; the new modules become dead code.
65
+
66
+ ## Tests
67
+ - `tests/unit/LeaseCoordinator.test.ts` (real Ed25519): acquire, presumed-dead
68
+ takeover, CAS contention yield, tunnel + git-only self-suspend, escalation,
69
+ epoch-advance signal. FencedLease/config/wiring/replay suites still green (57
70
+ total). Coordinator-integration + git-CAS-under-real-contention land in the
71
+ Tier-3 e2e + the real-hardware gate.
@@ -0,0 +1,75 @@
1
+ # Side-Effects Review — Cross-Machine Seamlessness: foundations + G2 auto-sync
2
+
3
+ **Spec:** docs/specs/CROSS-MACHINE-SEAMLESSNESS-SPEC.md (converged, approved)
4
+ **Increment:** type/config foundations, the FencedLease primitive (logic), and G2
5
+ automated state sync with the named wiring + pull-side replay/freshness guard.
6
+
7
+ ## What changed
8
+
9
+ New, self-contained modules (no behavior wired into existing flows except the two named seams):
10
+ - `src/core/seamlessnessConfig.ts` — resolve + validate the §9 tunable knobs; auto-derives
11
+ `standbyPullIntervalMs = min(failoverThresholdMs/4, leaseTtlMs/2)` to satisfy BOTH cross-knob
12
+ invariants (the bare `/4` the spec first stated violated the `< leaseTtlMs` invariant at default
13
+ ratios — caught by the unit test; spec table corrected to match).
14
+ - `src/core/FencedLease.ts` — pure lease logic (epoch CAS candidate, signing/verify, fencing check
15
+ `holdsValidLease`, `effectiveEpoch = max(tunnel,git)`, tunnel accept guard, presumed-dead
16
+ acquisition, livelock backoff). Transport-agnostic; not yet wired into the coordinator (that is the
17
+ G1 integration step).
18
+ - `src/core/RegistrySyncDebouncer.ts` — debounced, single-writer durable registry push + sync-health
19
+ signal.
20
+ - `src/core/wireRegistrySync.ts` — the NAMED G2 wiring (coordinator roleChange/leaseEpochChange →
21
+ markRegistryDirty), extracted as a testable seam.
22
+ - `src/core/registryReplayGuard.ts` — pure replay/freshness + epoch-floor + unknown-key-first-commit
23
+ validator.
24
+
25
+ Edits to existing files:
26
+ - `src/core/types.ts` — additive optional fields on `MachineRegistryEntry` (syncSequence,
27
+ authoredUnderEpoch, protocolVersion, rejoined), new `LeaseRecord`, optional `lease` on
28
+ `MachineRegistry`, and the seamlessness knobs on `MultiMachineConfig`. All optional → no existing
29
+ caller breaks.
30
+ - `src/core/GitSync.ts` — `sync()` snapshots the registry before pull and calls a new
31
+ `reconcilePulledRegistry()` after a successful pull to scrub stale/unauthorized entries. Additive;
32
+ on any error it warns and continues (never blocks sync).
33
+ - `src/commands/server.ts` — validates the seamlessness config at startup (rejects a violating
34
+ config), constructs `gitSync` for BOTH roles (so a self-electing standby can push), and wires the
35
+ RegistrySyncDebouncer. Stops it on shutdown.
36
+
37
+ ## Over-block / under-block
38
+ - **Over-block risk:** startup config validation throws on a violating override. Mitigated: default
39
+ and absent configs resolve to valid values (unit-tested), so only an explicitly bad override fails —
40
+ which is the intended "reject, don't degrade silently" behavior (spec §9).
41
+ - **Under-block risk:** the replay guard only runs when `registryBefore` was readable and the pull
42
+ changed HEAD; a first-ever pull with no prior registry simply trusts the incoming (no local baseline
43
+ to compare). Acceptable — signed-commit verification still applies, and the unknown-key constraint
44
+ still rejects an unknown machine asserting awake.
45
+
46
+ ## Level-of-abstraction fit
47
+ - The durable push lives in a debouncer (coarse, registry-only), NOT in the per-tick heartbeat path —
48
+ honoring the ephemeral/durable split so steady-state produces ~0 commits.
49
+ - FencedLease is pure logic with injected crypto; the transport (git/tunnel) is the coordinator's job.
50
+ This keeps the dangerous CAS/fencing logic unit-testable.
51
+
52
+ ## Signal vs. authority
53
+ - RegistrySyncDebouncer is single-writer: it only pushes when `isAuthoritative()` (awake / lease
54
+ holder). A standby marking dirty is a no-op push — removes the O(N) thundering-herd.
55
+ - The replay guard and sync-health are SIGNALS (reject an entry / flip health); the authority to act
56
+ on unhealthy sync (self-suspend ingress) belongs to the lease layer, wired in the G1 step.
57
+
58
+ ## Interactions
59
+ - `gitSync` now constructed for standby too. It does NOT pull/push at boot unless awake (the boot
60
+ `sync()` stays gated on `isAwake`); construction alone has no side effects beyond signing setup.
61
+ - `reconcilePulledRegistry` writes back a cleaned registry only when something was rejected — normal
62
+ pulls are untouched (no spurious writes).
63
+ - The lease object in the registry is explicitly NOT reconciled by the guard (the FencedLease epoch
64
+ CAS owns it) — avoids two components fighting over the same field.
65
+
66
+ ## Rollback cost
67
+ - Low. New modules are unreferenced except the two seams in server.ts/GitSync.ts. Reverting the
68
+ server.ts wiring + the GitSync snapshot/reconcile block restores prior behavior exactly; the new
69
+ files become dead code. No schema/migration shipped in this increment (config knobs are read with
70
+ defaults; migrateConfig lands in P4).
71
+
72
+ ## Tests
73
+ - `tests/unit/FencedLease.test.ts` (real Ed25519), `seamlessnessConfig.test.ts`,
74
+ `registry-sync-wiring.test.ts` (incl. the Phase-0-catching "role change triggers a push" test),
75
+ `registryReplayGuard.test.ts`. All green. Tier-2 (real git repo) reconcile + Tier-3 land in P5.
@@ -0,0 +1,36 @@
1
+ # Side-Effects Review — Cross-Machine Seamlessness: Telegram adapter contract impl
2
+
3
+ **Spec:** docs/specs/CROSS-MACHINE-SEAMLESSNESS-SPEC.md §Channel Seamlessness Contract (converged, approved)
4
+
5
+ ## What changed
6
+ - `src/messaging/TelegramAdapter.ts` — implements the Channel Seamlessness
7
+ Contract (the reference adapter):
8
+ - `dedupeKey(rawEvent)` → `telegram:<update_id>` (with a fallback to a
9
+ normalized Message's metadata.update_id).
10
+ - `getIngressPosition()` → `{ platform:'telegram', cursor:lastUpdateId, ... }`.
11
+ - `stopConsuming()` → halts the poll loop, persists the offset, returns the
12
+ DURABLE position (the saved offset, not the in-memory cursor).
13
+ - `resumeConsuming(position)` → restores the offset (never lowers it below
14
+ what we already know, so no replay) and restarts polling; rejects a
15
+ wrong-platform position.
16
+
17
+ ## Over-block / under-block
18
+ - All four methods are additive (the interface declares them optional). No
19
+ existing TelegramAdapter behavior changes — start/stop/send/onMessage are
20
+ untouched. `resumeConsuming` uses `Math.max(lastUpdateId, cursor)` so it can
21
+ only advance the offset, never rewind into a replay.
22
+
23
+ ## Signal vs authority / interactions
24
+ - These are pure adapter capabilities (where-am-I / stop / resume / identify);
25
+ they carry no authority. The seamless handoff path (a follow-on) calls them;
26
+ on their own they have no runtime effect until invoked.
27
+ - `stopConsuming` reuses the existing `saveOffset()` (atomic-rename), so the
28
+ durable position is consistent with the normal poll-offset persistence.
29
+
30
+ ## Rollback cost
31
+ - Minimal — removing the four methods reverts to the prior adapter exactly.
32
+
33
+ ## Tests
34
+ - `tests/unit/telegram-seamless-contract.test.ts` (6): all four methods present,
35
+ dedupeKey stability/distinctness + metadata fallback, ingress-position shape,
36
+ stop→durable-position→resume round-trip (no replay), wrong-platform rejection.
@@ -0,0 +1,56 @@
1
+ # Side-Effects Review — Cross-Machine Seamlessness: G3 idempotent ledger + handoff lifecycle + adapter contract
2
+
3
+ **Spec:** docs/specs/CROSS-MACHINE-SEAMLESSNESS-SPEC.md §8 G3a/G3e + Channel Seamlessness Contract (converged, approved)
4
+
5
+ ## What changed
6
+ - `src/messaging/MessageProcessingLedger.ts` (new) — SQLite-backed
7
+ received→processing→reply_committed→cursor_advanced lifecycle keyed by
8
+ dedupeKey. Redelivery of an acted-on event is dropped; cursor advances only on
9
+ durable completion; stuck-`processing` entries are re-runnable; a remote
10
+ reply-committed marker (dual-medium) prevents a failover re-send. Deterministic
11
+ `computeReplyIdempotencyKey(dedupeKey, replyIndex)`. Self-initializing schema,
12
+ per-agent-id isolation, WAL+busy_timeout (the PendingRelayStore pattern).
13
+ - `src/core/HandoffSentinel.ts` (new) — the planned-handoff state machine
14
+ (prepare→tail_synced→ingress_fenced→new_owner_active→old_owner_standby→
15
+ committed). Yields the lease ONLY on a verified ack (echo of tailSeq + ingress
16
+ position + thread-history hash) AND a passing validation; otherwise aborts and
17
+ stays awake. Anti-oscillation floor + a race guard (`inProgress`).
18
+ - `src/core/types.ts` — new `IngressPosition` type and OPTIONAL Channel
19
+ Seamlessness Contract methods on `MessagingAdapter`
20
+ (getIngressPosition/stopConsuming/resumeConsuming/dedupeKey).
21
+
22
+ ## Over-block / under-block
23
+ - The contract methods are OPTIONAL, so every existing adapter
24
+ (Telegram/Slack/WhatsApp/iMessage) compiles unchanged — no adapter is forced
25
+ "seamless-ready" before it implements + passes the conformance suite.
26
+ - The ledger's `record` is INSERT OR IGNORE — a benign double-record is a no-op;
27
+ a genuinely new event is `firstSeen:true`. No over-block (it never refuses a
28
+ first-seen event).
29
+
30
+ ## Signal vs authority
31
+ - The ledger is a pure substrate (records facts); it carries no authority. The
32
+ fencing epoch is STORED (reply_epoch) but the authority to send is the lease
33
+ layer's (the fencing-gated outbox, next increment).
34
+ - HandoffSentinel encodes the authority rule structurally: no yield without
35
+ verified ack + validation. The validator is a Tier-1 signal; the yield is the
36
+ authority act, gated on it.
37
+
38
+ ## Interactions
39
+ - MessageProcessingLedger is standalone (not yet wired into the inbound path —
40
+ that wiring + the fencing-gated outbox is the next G3 increment). No live
41
+ behavior changes from this commit.
42
+ - HandoffSentinel is standalone (constructed + wired in the integration step);
43
+ its ops are injected, so it has no ambient dependencies.
44
+ - `applyRemoteReplyMarker` uses COALESCE so a local commit already present is
45
+ never downgraded — safe under concurrent local+remote marking.
46
+
47
+ ## Rollback cost
48
+ - Minimal — both new modules are unreferenced by live code in this commit; the
49
+ types change is purely additive (optional fields). Reverting removes the files
50
+ with no behavioral impact.
51
+
52
+ ## Tests
53
+ - `tests/unit/MessageProcessingLedger.test.ts` (real SQLite, 10 tests): redelivery
54
+ dropped, cursor-only-on-completion, idempotent commit, stuck re-run, dual-medium
55
+ marker. `tests/unit/HandoffSentinel.test.ts` (10 tests): every abort gate proven
56
+ to NOT yield. 77 unit tests green overall.
@@ -0,0 +1,51 @@
1
+ # Side-Effects Review — Cross-Machine Seamlessness: live-tail buffer + redaction + fenced outbox
2
+
3
+ **Spec:** docs/specs/CROSS-MACHINE-SEAMLESSNESS-SPEC.md §8 G3a/G3b/G3c (converged, approved)
4
+
5
+ ## What changed (all new, standalone modules)
6
+ - `src/core/LiveTailBuffer.ts` — standby-side persisted tail with strict
7
+ sequence-dedup: applies a flush only at lastAppliedSeq+1, holds out-of-order,
8
+ drops duplicates, and after liveTailOutOfOrderTimeoutMs declares an unfillable
9
+ gap (discards held, proceeds) — bounding the holdout and preventing context
10
+ corruption. Per-topic byte cap (drop-oldest).
11
+ - `src/core/liveTailRedaction.ts` — versioned (REDACTION_CATEGORY_VERSION) named
12
+ enum of credential categories + redactForLiveTail(); scrubs bearer tokens,
13
+ private-key blocks, AWS keys, JWT-like, api-keys, secret assignments before any
14
+ tail content leaves the machine.
15
+ - `src/messaging/FencedOutbox.ts` — structural no-duplicate-reply at the send
16
+ path: sends only while holding the lease at the stamped epoch; suppresses a
17
+ fenced (no-lease / stale-epoch) machine's reply; idempotent via the ledger's
18
+ acted-on check + deterministic idempotency key.
19
+
20
+ ## Over-block / under-block
21
+ - FencedOutbox SUPPRESSES on no-lease/stale-epoch — intended over-block (a fenced
22
+ machine must not send). Risk of under-send if `holdsLease()` is wrongly false;
23
+ mitigated because a single machine trivially holds its lease, and the stamped
24
+ epoch equals the current epoch in the normal (non-handoff) case.
25
+ - Redaction is conservative/high-precision; over-redaction (a false positive) is
26
+ acceptable per spec ("false positives cheaper than a leak"). Ordinary prose is
27
+ left untouched (tested).
28
+
29
+ ## Signal vs authority
30
+ - FencedOutbox consults the lease authority (holdsLease/currentEpoch injected) —
31
+ it does not itself decide authority, it enforces it at the send boundary.
32
+ - LiveTailBuffer is a pure data structure (no authority); it only guarantees
33
+ context integrity on the receiving side.
34
+
35
+ ## Interactions
36
+ - All three are standalone in this commit (not yet wired into the live message
37
+ path — that integration is the next increment). No runtime behavior changes.
38
+ - FencedOutbox depends on MessageProcessingLedger (already shipped) — commitReply
39
+ is idempotent, so a double-send attempt is safe.
40
+ - Redaction is designed to run before the (encrypted) transport; carry-by-
41
+ reference for large tool output (a follow-on in the transport wiring) reduces
42
+ the raw sensitive surface structurally beyond pattern-matching.
43
+
44
+ ## Rollback cost
45
+ - Minimal — unreferenced by live code. Reverting removes the files.
46
+
47
+ ## Tests
48
+ - `tests/unit/LiveTailBuffer.test.ts` (9): order, dup-drop, hold+drain,
49
+ gap-discard, byte cap, redaction categories. `tests/unit/FencedOutbox.test.ts`
50
+ (5): send, already-replied, fenced-no-lease, fenced-stale-epoch, send-failed.
51
+ 91 unit tests green overall.
@@ -0,0 +1,54 @@
1
+ # Side-Effects Review — Cross-Machine Seamlessness: observability + agent awareness + migration parity
2
+
3
+ **Spec:** docs/specs/CROSS-MACHINE-SEAMLESSNESS-SPEC.md §11 (converged, approved)
4
+
5
+ ## What changed
6
+ - `src/core/MultiMachineCoordinator.ts` — `getSyncStatus()`: the
7
+ /health.multiMachine.syncStatus surface (leaseHolder, leaseEpoch, holdsLease,
8
+ splitBrainState, awakeMachineCount, protocolVersion). Always returns valid
9
+ fields (never null/throws) — the Phase-1 "feature is alive" surface.
10
+ - `src/server/routes.ts` — RouteContext gains `coordinator`; authed `/health`
11
+ now includes `multiMachine: { enabled, syncStatus }`.
12
+ - `src/server/AgentServer.ts` — threads `options.coordinator` into the route
13
+ context.
14
+ - `src/scaffold/templates.ts` (`generateClaudeMd`) + `src/core/PostUpdateMigrator.ts`
15
+ (`migrateClaudeMd`) — a concise Cross-Machine Seamlessness awareness section
16
+ (one-agent-many-machines, no double-reply, compaction-pause handoff bar, honest
17
+ machine-provenance, where to read sync status, how to read a split-brain
18
+ escalation). New agents get it via generate; existing agents via the
19
+ content-sniffed migration.
20
+
21
+ ## Migration parity decision (explicit)
22
+ - The `multiMachine` seamlessness KNOBS default safely in code
23
+ (`resolveSeamlessnessConfig` applies defaults for any absent key), so every
24
+ existing agent's server resolves valid values without a config write. I
25
+ DELIBERATELY did not write ~13 knobs into every agent's config.json — that is
26
+ noise, and functional migration parity is already achieved by the code
27
+ defaults + the server code shipping + the SQLite stores self-initializing +
28
+ the CLAUDE.md awareness migration. The dials remain user-settable under
29
+ `multiMachine` (documented in the awareness section). A literal config-knob
30
+ migration is a trivial follow-on if a reviewer wants it.
31
+
32
+ ## Over-block / under-block
33
+ - `getSyncStatus` is read-only and defensive (try/catch around the registry
34
+ read → awakeMachineCount 0 on failure). It cannot block anything.
35
+ - `/health.multiMachine` is only in the AUTHED branch (unchanged auth posture);
36
+ the unauthenticated basic health is untouched.
37
+
38
+ ## Signal vs authority / interactions
39
+ - Pure observability — no authority, no mutation. RouteContext.coordinator is
40
+ `| null`; single-machine installs report `{ enabled:false, syncStatus:null }`
41
+ or the trivially-held lease. Existing route tests still pass (tolerant
42
+ context builders).
43
+ - protocolVersion is reported (for partial-migration visibility); its
44
+ enforcement point (refusing to hand the awake lease to an old-version machine)
45
+ lands with the live two-machine handoff transport.
46
+
47
+ ## Rollback cost
48
+ - Low. Removing the /health block + getSyncStatus + the template sections
49
+ reverts cleanly; the RouteContext field is additive (`| null`).
50
+
51
+ ## Tests
52
+ - `tests/unit/multimachine-syncstatus.test.ts` — the feature-alive surface
53
+ returns valid non-null fields on a single-machine install and never throws.
54
+ 119 unit tests green overall.
@@ -0,0 +1,31 @@
1
+ # Side-Effects Review — dispatch (guidance-out) logic (Phase 1, increment 7)
2
+
3
+ **Slug:** `feedback-factory-dispatch`
4
+ **Date:** `2026-05-27`
5
+ **Author:** Echo (autonomous)
6
+ **Spec:** `docs/specs/feedback-factory-migration.md` (converged v2, approved by Justin 2026-05-26)
7
+ **Scope:** The pure logic of the dispatch endpoint (the channel that sends guidance back out to agents) — type/priority vocab, semver version-compat filtering, and the create-dedup title normalization. NOT the HTTP wiring/storage (app-placement decision, blocked).
8
+
9
+ ## Summary of the change
10
+
11
+ Ports the pure logic of `the-portal/pages/api/instar/dispatches/index.ts` into `src/feedback-factory/dispatch/dispatch.ts`: the dispatch `DISPATCH_TYPES`/`DISPATCH_PRIORITIES` vocab + validators, `parseVersion`/`isVersionGte`/`isVersionLte` (the semver comparison), `filterDispatchesForVersion` (an agent only receives a dispatch whose `[minVersion, maxVersion]` window includes its version), and `normalizeDispatchTitle` (trim + 500-char cap, used for create-dedup). **Not wired into any route yet** — no behavioral change.
12
+
13
+ ## Equivalence verification
14
+
15
+ Reference is TypeScript, so equivalence is by faithful transcription + exhaustive both-sides-of-boundary unit tests (9): semver compare at equal/greater/lesser across all three components; the version-compat filter with min-only / max-only / both-bounds / unbounded, boundaries inclusive; unparseable version → `[0,0,0]`; title trim + 500 cap. The comparison loop, the inclusive-equal semantics, and the filter predicate are copied verbatim from the reference.
16
+
17
+ ## Seven-dimension review
18
+
19
+ 1. **Over/under-reach** — Pure functions, no I/O, no state, not imported by any runtime path.
20
+ 2. **Level-of-abstraction fit** — `src/feedback-factory/dispatch/` — the dispatch layer. HTTP/storage excluded (the blocked app-placement decision); this is the reusable core.
21
+ 3. **Signal vs Authority** — N/A; pure filters/validators.
22
+ 4. **Interactions** — None. New isolated module; nothing imports it yet.
23
+ 5. **Rollback cost** — Trivial: delete the module + tests.
24
+ 6. **Migration parity** — N/A. New internal library code; no agent-installed file touched.
25
+ 7. **Failure modes** — (a) Semver-compare divergence (esp. inclusive-equal + multi-component ordering) → covered by both-direction tests across all components. (b) Unparseable version handling → tested ([0,0,0] fallback matches the reference, so a garbage version is treated as 0.0.0). (c) Title-dedup divergence → trim+slice(500) transcribed + tested.
26
+
27
+ ## Tests
28
+
29
+ - Tier-1 unit (CI): `tests/unit/feedback-factory/dispatch.test.ts` — 9 tests (vocab, parseVersion, gte/lte, version-compat filter boundaries, title normalization).
30
+ - No cross-runtime parity harness (reference is TS; equivalence by transcription + boundary tests).
31
+ - No integration/E2E this increment: HTTP wiring/storage is the blocked app-placement decision; this logic attaches to a route when that's decided. Reasoned, documented.