instar 1.3.553 → 1.3.554

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.
@@ -0,0 +1,132 @@
1
+ # Side-Effects Review — WS4.3 journal-lease cutover
2
+
3
+ **Spec:** `docs/specs/MULTI-MACHINE-SEAMLESSNESS-SPEC.md` §WS4.3 ("Cutover discipline")
4
+ **Slug:** ws43-journal-lease-cutover
5
+ **Tier:** 2 (multi-machine correctness invariant; safety-invariant risk floor)
6
+ **Flag:** `multiMachine.seamlessness.ws43JournalLease` (default false), `ws43JournalLeaseDryRun` (default true)
7
+
8
+ ## What changed
9
+
10
+ Scheduled-job claim deduplication can now upgrade from the legacy best-effort
11
+ AgentBus broadcast (`JobClaimManager`) to a durable, epoch-fenced lease over the
12
+ replicated journal (`JobLeaseClaimStore`). A single pure decision point
13
+ (`JobLeaseCutoverGate.decideClaimPath`) selects which mechanism a job uses, and
14
+ the scheduler routes its three claim seams (remote-claim check, claim-before-spawn,
15
+ release-on-complete) through that decision. New files: `src/scheduler/
16
+ JobLeaseCutoverGate.ts`, `src/scheduler/JobLeaseClaimStore.ts`. Wired into
17
+ `JobScheduler` (new `setJournalLeaseCutover` injector + `resolveClaimPath`/
18
+ `releaseClaim` helpers) and `server.ts` (the production provider closure reading
19
+ the flag + coordinator lease epoch + pool peers' advertised capability). Config
20
+ defaults + types + the `seamlessnessFlags` heartbeat advert added.
21
+
22
+ ## Phase 1 — Principle check (signal vs authority)
23
+
24
+ This IS a decision point (it gates which claim mechanism runs). It is correctly
25
+ an **authority** because dedup is a correctness mechanism, not a signal — but the
26
+ authority is NARROW and STRUCTURAL: the gate keys ONLY on objective facts (flag
27
+ state, online-peer presence, the boolean capability each peer advertises in its
28
+ authenticated heartbeat) — never on interpreting message content or any brittle
29
+ heuristic. It cannot block a user action; the worst case is "stay on the legacy
30
+ bus path," which is byte-for-byte today's behavior. The gate fails toward the
31
+ conservative side (bus) on any ambiguity (older peer, offline peer, throwing gate
32
+ read). This complies with `docs/signal-vs-authority.md`: a structural ownership/
33
+ coherence check, not a brittle content gate with blocking authority.
34
+
35
+ ## Phase 4 — Review questions
36
+
37
+ 1. **Over-block** — The gate never blocks a job; it only chooses a dedup
38
+ mechanism. The journal lease's epoch fence could in principle refuse OUR claim
39
+ if a peer holds a strictly-newer-epoch lease — but that is the correct
40
+ behavior (the newer-epoch holder is the current lease-holder; we must yield).
41
+ A same/older-epoch peer claim is fenced OUT, so we are never wrongly blocked by
42
+ a stale peer. No legitimate run is wrongly rejected.
43
+
44
+ 2. **Under-block** — The journal-lease path relies on the replicated journal
45
+ actually carrying claim records between machines (`applyRemote`). This PR ships
46
+ the gate + the local epoch-fenced store + the cutover wiring; the journal
47
+ EMISSION/APPLY transport for the claim kind is the existing replication
48
+ substrate's responsibility and is gated behind the same flag-coherence advert
49
+ so an older peer that can't apply the kind keeps the WHOLE pool on the bus (it
50
+ never advertises `ws43JournalLease`). Until the flag is flipped on a fully
51
+ coherent pool, dedup behavior is IDENTICAL to today (bus). The dry-run default
52
+ means even a flag-on coherent pool stays on the bus while logging intended
53
+ claims — so under-block risk during rollout is zero (the bus is the baseline).
54
+
55
+ 3. **Level-of-abstraction fit** — Correct layer. The cutover decision lives in
56
+ the scheduler (which owns the claim seams), the gate is a pure function (no IO,
57
+ independently testable), and the durable lease is its own store mirroring the
58
+ existing `JobClaimManager` shape. The flag-coherence read reuses the existing
59
+ `seamlessnessFlags` heartbeat advert + registry the WS1.1/WS4.4 gates already
60
+ use — no parallel coherence machinery.
61
+
62
+ 4. **Signal vs authority compliance** — Compliant (see Phase 1). Structural
63
+ authority keyed on objective facts; fails conservative; no content heuristics;
64
+ cannot block a user action.
65
+
66
+ 5. **Interactions** — The gate guarantees the journal lease and the bus broadcast
67
+ are NEVER both consulted for the same job in the same evaluation (exactly one
68
+ `path` per decision) — the named migration hazard. The release seam calls
69
+ BOTH stores' `completeClaim` idempotently (each is a no-op when this machine
70
+ doesn't own the slug's record), so a mid-run flag flip between claim and
71
+ complete cannot strand a lease. It composes cleanly with the WS4.3 role-guard
72
+ (a separate spawn-boundary refusal that runs BEFORE the claim seam) — the two
73
+ are orthogonal. No double-fire: `resolveClaimPath` is evaluated once at the
74
+ check seam and the result reused at the claim seam.
75
+
76
+ 6. **External surfaces** — Adds one boolean field (`ws43JournalLease`) to the
77
+ `seamlessnessFlags` heartbeat advert. Additive — older peers omit it (read as
78
+ non-participant, the conservative side). No new HTTP route. No user-visible
79
+ message. The advert is only `true` when the flag is on AND not dry-run, so a
80
+ dry-run machine never advertises participation it isn't actually performing.
81
+
82
+ 7. **Multi-machine posture** — This feature IS multi-machine machinery.
83
+ Posture: **machine-local decision fed by a proxied-on-read coherence view.**
84
+ Each machine independently runs the gate over the peers' advertised capability
85
+ (read live from the registry's heartbeat data), and the journal lease store is
86
+ per-machine but its records replicate via the journal. Invariant-5 flag
87
+ coherence is enforced: the cutover engages pool-wide ONLY when every online
88
+ peer advertises the capability — never a window where one machine leases while
89
+ a peer broadcasts. Single-machine (no peers) is a strict no-op (the gate
90
+ returns `bus`/`single-machine` and the journal store is never touched). The
91
+ epoch fence (coordinator lease epoch) carries ownership across a demotion so a
92
+ demoted machine's stale claim can't steal a job. Designed for N machines, no
93
+ 2-peer assumption.
94
+
95
+ 8. **Rollback cost** — Trivial. The feature is dark (flag default false). Flip
96
+ `multiMachine.seamlessness.ws43JournalLease` off (or leave it absent) and the
97
+ scheduler reverts to the legacy bus path with no migration, no data repair —
98
+ the journal lease store is just an inert local JSON ledger. Dry-run (default)
99
+ is an additional safe rung: flag on but no real cutover. The off-switch is read
100
+ live at each claim boundary, so a config edit + session restart fully reverts.
101
+
102
+ ## Phase 5 — Second-pass review (high-risk: touches scheduler claim/dedup + a "gate")
103
+
104
+ Concern lens applied: could the cutover ever let TWO machines run the same job?
105
+ - Within ONE machine's evaluation, the gate returns exactly one path — verified by
106
+ the `NEVER returns both mechanisms` unit test and the integration/e2e
107
+ `never-both` assertions (coherent → journal only; mixed → bus only).
108
+ - ACROSS machines: the cutover engages pool-wide only under flag coherence (every
109
+ online peer advertises it), so the pool is either all-journal or all-bus for a
110
+ job set — never split. A peer that can't participate keeps everyone on the bus.
111
+ - A demotion race is fenced by the lease epoch (stale-epoch applyRemote rejected;
112
+ same/older-epoch tryClaim refused) — covered by the epoch-fence unit tests.
113
+
114
+ Concur with the review. The load-bearing invariant (no double-run across the
115
+ cutover; no stranded timers on a demoted machine) is structurally enforced by the
116
+ single-decision gate + epoch fence and is covered at all three test tiers.
117
+
118
+ ## Tests
119
+
120
+ - Unit: `tests/unit/job-lease-cutover-gate.test.ts` (9), `tests/unit/
121
+ job-lease-claim-store.test.ts` (8) — gate decision matrix incl. never-both +
122
+ flag coherence + single-machine no-op; epoch fencing + durability.
123
+ - Integration: `tests/integration/scheduler-journal-lease-cutover.test.ts` (6) —
124
+ the scheduler routes through the gate; coherent→journal, mixed→bus, dry-run,
125
+ single-machine, remote-lease skip, live flag flip.
126
+ - E2E "feature is alive": `tests/e2e/scheduler-journal-lease-cutover-alive.test.ts`
127
+ (4) — the EXACT server.ts provider closure (real config + real coordinator
128
+ epoch + real MachinePoolRegistry heartbeat) drives a real triggerJob to take
129
+ the journal lease vs the bus; proves the wiring is live, not a stub.
130
+
131
+ All 27 new tests pass; tsc clean; no-silent-fallbacks, dark-gate line-map, and
132
+ feature-delivery-completeness gates pass; docs-coverage class floor improved.