instar 1.3.552 → 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.
Files changed (50) hide show
  1. package/dist/commands/server.d.ts.map +1 -1
  2. package/dist/commands/server.js +71 -2
  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/PostUpdateMigrator.d.ts +10 -0
  8. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  9. package/dist/core/PostUpdateMigrator.js +60 -0
  10. package/dist/core/PostUpdateMigrator.js.map +1 -1
  11. package/dist/core/devGatedFeatures.d.ts.map +1 -1
  12. package/dist/core/devGatedFeatures.js +6 -0
  13. package/dist/core/devGatedFeatures.js.map +1 -1
  14. package/dist/core/types.d.ts +50 -0
  15. package/dist/core/types.d.ts.map +1 -1
  16. package/dist/core/types.js.map +1 -1
  17. package/dist/scaffold/templates.d.ts.map +1 -1
  18. package/dist/scaffold/templates.js +4 -0
  19. package/dist/scaffold/templates.js.map +1 -1
  20. package/dist/scheduler/JobLeaseClaimStore.d.ts +105 -0
  21. package/dist/scheduler/JobLeaseClaimStore.d.ts.map +1 -0
  22. package/dist/scheduler/JobLeaseClaimStore.js +165 -0
  23. package/dist/scheduler/JobLeaseClaimStore.js.map +1 -0
  24. package/dist/scheduler/JobLeaseCutoverGate.d.ts +73 -0
  25. package/dist/scheduler/JobLeaseCutoverGate.d.ts.map +1 -0
  26. package/dist/scheduler/JobLeaseCutoverGate.js +66 -0
  27. package/dist/scheduler/JobLeaseCutoverGate.js.map +1 -0
  28. package/dist/scheduler/JobScheduler.d.ts +53 -0
  29. package/dist/scheduler/JobScheduler.d.ts.map +1 -1
  30. package/dist/scheduler/JobScheduler.js +136 -20
  31. package/dist/scheduler/JobScheduler.js.map +1 -1
  32. package/dist/server/AgentServer.d.ts +3 -0
  33. package/dist/server/AgentServer.d.ts.map +1 -1
  34. package/dist/server/AgentServer.js +3 -0
  35. package/dist/server/AgentServer.js.map +1 -1
  36. package/dist/server/PoolPollCache.d.ts +136 -0
  37. package/dist/server/PoolPollCache.d.ts.map +1 -0
  38. package/dist/server/PoolPollCache.js +163 -0
  39. package/dist/server/PoolPollCache.js.map +1 -0
  40. package/dist/server/routes.d.ts +9 -0
  41. package/dist/server/routes.d.ts.map +1 -1
  42. package/dist/server/routes.js +51 -8
  43. package/dist/server/routes.js.map +1 -1
  44. package/package.json +1 -1
  45. package/src/data/builtin-manifest.json +65 -65
  46. package/src/scaffold/templates.ts +4 -0
  47. package/upgrades/1.3.553.md +63 -0
  48. package/upgrades/1.3.554.md +79 -0
  49. package/upgrades/side-effects/multi-machine-seamlessness-ws44-pool-cache.md +115 -0
  50. package/upgrades/side-effects/ws43-journal-lease-cutover.md +132 -0
@@ -0,0 +1,79 @@
1
+ # Upgrade Guide — vNEXT
2
+
3
+ <!-- assembled-by: assemble-next-md -->
4
+ <!-- bump: patch -->
5
+
6
+ ## What Changed
7
+
8
+ - **Scheduled-job claims can upgrade from the best-effort AgentBus broadcast to a
9
+ durable, epoch-fenced lease over the replicated journal.** The decision is made by
10
+ a single pure gate (`JobLeaseCutoverGate.decideClaimPath`): the journal-lease path
11
+ engages ONLY when the flag is on AND the pool is flag-coherent — every ONLINE peer
12
+ advertises the `ws43JournalLease` capability in its heartbeat. An older peer (no
13
+ flags field) or a peer with the flag off keeps the WHOLE pool on the legacy bus.
14
+ - **The cutover guarantees the two claim mechanisms are NEVER both live for the same
15
+ job set** — the named migration hazard the spec's "Cutover discipline" rule closes
16
+ (one machine leasing via the journal while a peer broadcasts via the bus). Each
17
+ scheduler evaluation returns exactly one path; the release seam idempotently closes
18
+ whichever store took the claim.
19
+ - **Epoch fencing** (`JobLeaseClaimStore`): a claim carries the coordinator's lease
20
+ epoch; a stale-epoch peer record is rejected on apply and a same/older-epoch peer
21
+ lease fences our claim out — so a demoted machine's late claim can't steal a job
22
+ from the current lease-holder, and a partition double-run is structurally prevented
23
+ when the journal is reachable. Claim records store METADATA ONLY (machine, slug,
24
+ epoch, timestamps) — never job payloads.
25
+ - Ships **dark** behind `multiMachine.seamlessness.ws43JournalLease` (default false)
26
+ with `ws43JournalLeaseDryRun` default true — the first rollout rung logs intended
27
+ journal claims while the legacy bus path still runs, so a dry-run pool never
28
+ half-migrates. Flag-off / mixed-pool / single-machine = byte-for-byte today's
29
+ behavior (the bus path). The advert is only `true` when the flag is on AND not
30
+ dry-run. Plain seamlessness booleans (read live at the claim boundary), so absence
31
+ yields the safe default without a migration — no migrateConfig entry needed.
32
+
33
+ This completes the WS4.3 workstream alongside the merged jobs read-side (#1104) and
34
+ role-guard-at-spawn (#1147). <!-- tracked: CMT-1416 -->
35
+
36
+ ## What to Tell Your User
37
+
38
+ This is internal multi-machine plumbing that ships turned off, so you will not
39
+ see a change. When it is eventually enabled on a fleet where all your machines
40
+ support it, your scheduled jobs get a more reliable way to avoid two machines
41
+ running the same job at once — a durable, fenced claim instead of a best-effort
42
+ broadcast. Until then, and on any single-machine setup or a mixed fleet, job
43
+ scheduling behaves exactly as it does today. There is nothing to configure and no
44
+ action needed.
45
+
46
+ ## Summary of New Capabilities
47
+
48
+ - WS4.3 journal-lease cutover: scheduled-job claims can upgrade from the AgentBus
49
+ broadcast to a durable, epoch-fenced lease over the replicated journal, engaged
50
+ only when every online peer advertises the capability (flag coherence). A single
51
+ gate guarantees the two claim mechanisms are never both live for one job set.
52
+ Ships dark behind multiMachine.seamlessness.ws43JournalLease (dry-run default);
53
+ single-machine and mixed-pool installs are a strict no-op.
54
+
55
+ ## Evidence
56
+
57
+ - `tests/unit/job-lease-cutover-gate.test.ts` (9): the full decision matrix —
58
+ flag-off → bus; single-machine / all-peers-offline → no-op bus; coherent →
59
+ journal; ONE non-advertising peer → whole pool on bus; older peer (absent flags)
60
+ counts as non-advertising; an offline incoherent peer does NOT block coherence;
61
+ dry-run → bus with journalDryRun flagged; and the explicit "NEVER returns both
62
+ mechanisms" invariant.
63
+ - `tests/unit/job-lease-claim-store.test.ts` (8): take/idempotent-reclaim; a peer's
64
+ live lease blocks our same/older-epoch claim; our strictly-newer epoch supersedes;
65
+ applyRemote rejects a stale-epoch record; no self-spoof; completion releases;
66
+ expired peer lease no longer blocks; durable round-trip across restart.
67
+ - `tests/integration/scheduler-journal-lease-cutover.test.ts` (6): the scheduler
68
+ routes through the gate — coherent→journal (bus NOT called), mixed→bus (journal
69
+ NOT taken), dry-run→bus, single-machine no-op, remote-journal-lease→skip, and a
70
+ live flag flip at the boundary changing the path immediately.
71
+ - `tests/e2e/scheduler-journal-lease-cutover-alive.test.ts` (4): the EXACT server.ts
72
+ provider closure (real config + real MultiMachineCoordinator epoch + real
73
+ MachinePoolRegistry fed by a real heartbeat) drives a real triggerJob — coherent
74
+ takes the journal lease and never the bus; mixed takes the bus and never the
75
+ journal; flag-off no-op; single-machine no-op. Proves the wiring is live, not a stub.
76
+ - `tsc --noEmit` clean; no-silent-fallbacks, the dev-agent dark-gate line-map (+18
77
+ recompute via the attributor), and feature-delivery-completeness all green;
78
+ docs-coverage class floor improved (new symbols documented in under-the-hood.md +
79
+ multi-machine.md).
@@ -0,0 +1,115 @@
1
+ # Side-Effects Review — WS4.4(f): global pool-cache unification
2
+
3
+ **Version / slug:** `multi-machine-seamlessness-ws44-pool-cache`
4
+ **Date:** `2026-06-13`
5
+ **Author:** `Instar Agent (echo)`
6
+ **Spec:** `docs/specs/MULTI-MACHINE-SEAMLESSNESS-SPEC.md` §WS4.4 clause (f) (converged + approved; deferred second half of WS4.4, tracked CMT-1416)
7
+ **Second-pass reviewer:** not-required — pure read-side efficiency/observability; no new authority, no credential-bearing code, no destructive op, no third-party spend.
8
+
9
+ ## Summary of the change
10
+
11
+ WS4.4 clause (f), the deferred second half of WS4.4: every pool-scope dashboard
12
+ surface (`/sessions?scope=pool`, `/jobs?scope=pool`, `/attention?scope=pool`,
13
+ `/guards?scope=pool`, …) used to fan out to every peer machine INDEPENDENTLY — once
14
+ per surface, per client, per poll interval. With a dashboard polling several tabs at
15
+ once, the same peer is hit N times per interval. This unifies all of them onto ONE
16
+ shared per-peer poll cache: one fan-out per interval feeds every pool-scope surface,
17
+ and under CPU load-shed the cache serves last-cached peer data tagged `stale: true`
18
+ instead of re-fanning (load-shed, honestly labeled).
19
+
20
+ New module (pure + dependency-injected, unit-testable):
21
+ - `src/server/PoolPollCache.ts` — `fetchPeer(peerMachineId, routePath, fetcher)`:
22
+ within-TTL cache hit (one fan-out feeds two surfaces), single-flight coalescing of
23
+ concurrent callers, CPU load-shed stale-serve (never from an empty cache — a first
24
+ read always fetches), a failed fetch is NEVER cached (transient errors don't stick),
25
+ per-(peer, route) keying, and a read-only `snapshot()` of live load + counters.
26
+
27
+ Wiring (all behind the dark flag):
28
+ - `routes.ts` — the `/jobs?scope=pool` per-peer fetch routes through `ctx.poolPollCache`
29
+ when wired; `null` (flag dark / single-machine) ⇒ today's direct per-peer fetch
30
+ byte-for-byte. New read-only `GET /pool/poll-cache` observability route (503 when
31
+ dark — the ships-dark contract). A stale-served peer body surfaces an honest
32
+ `pool.stale: true` tag on the merged response.
33
+ - `commands/server.ts` + `AgentServer.ts` — dev-gated construction (resolveDevAgentGate)
34
+ in the mesh-setup block; `seamlessnessFlags.ws44PoolCache` capability advert.
35
+ - `types.ts` / `ConfigDefaults.ts` / `devGatedFeatures.ts` — flag (`ws44PoolCache`,
36
+ DELIBERATELY OMITTED so the dev-gate decides) + `ws44PoolCacheTtlMs` tunable +
37
+ `MachineCapacity.ws44PoolCache` advert field.
38
+ - `PostUpdateMigrator.ts` + `templates.ts` — Migration Parity (config strip-default +
39
+ CLAUDE.md section + shadow-capability markers) + Agent Awareness.
40
+
41
+ ## Decision-point inventory
42
+
43
+ - `/jobs?scope=pool` per-peer fetch through the shared cache — **add** — wired only
44
+ when the dark flag is on; else the existing direct fetch is unchanged.
45
+ - `GET /pool/poll-cache` observability route — **add** — 503 while dark (ships-dark
46
+ contract, like `/pool/queue`); 200 with the snapshot when wired.
47
+ - CPU load-shed stale-serve — **add** — over the load-per-core threshold, serve
48
+ last-cached tagged `stale: true` rather than re-fanning; an empty cache still fetches.
49
+
50
+ ---
51
+
52
+ ## 1. Over-block
53
+ Flag-off (default fleet): zero new behavior — every pool-scope surface keeps its direct
54
+ per-peer fetch byte-for-byte (verified by the "NOT WIRED (cache null)" integration
55
+ test). Flag-on: the 3s default TTL matches the per-route pool caches already in use, so
56
+ no legitimate fresh read is starved. Load-shed only triggers at/above the load-per-core
57
+ threshold (default 1.5, the SessionReaper critical default) AND only when something is
58
+ already cached — a first read is never load-shed.
59
+
60
+ ## 2. Under-block
61
+ Not an authority — this gates nothing. It only decides whether to reuse a recently
62
+ fetched peer body or re-fetch. The worst case is serving a body up to one TTL (or, under
63
+ load-shed, the last-cached) older than a fresh fan-out would produce — and load-shed is
64
+ HONESTLY tagged (`stale: true` on the body, `pool.stale: true` on the merge) so a stale
65
+ serve is never silent. A failed fetch is never cached, so a transient peer error cannot
66
+ stick for the whole TTL.
67
+
68
+ ## 3. Level-of-abstraction fit
69
+ Right layer. `PoolPollCache` is a pure primitive (injected clock + load reader); the
70
+ route change is a thin branch that calls the fetcher directly when the cache is null.
71
+ It rides the existing pool fan-out pattern (resolvePeerUrls + per-peer timeout + failed
72
+ markers) rather than inventing new transport.
73
+
74
+ ## 4. Signal vs authority compliance
75
+ Everything here is a SIGNAL, never an authority: the cache hit / load-shed staleness is
76
+ observability + an efficiency choice; it never gates serving anything, never mutates
77
+ state, and never caches private end-user content (the pool-scope surfaces are
78
+ operator-Bearer reads of mesh METADATA — `/view/:id` private bodies are handled by
79
+ WS4.4 pool-links, which explicitly never caches them).
80
+
81
+ ## 5. Interactions
82
+ - Flag-off path leaves every pool-scope surface identical (no shadowing of the direct
83
+ fetch).
84
+ - Coexists with the per-route payload caches (`jobsPoolCache`, etc.) — those dampen two
85
+ rapid calls to the SAME surface; this shared cache dampens the cross-surface,
86
+ cross-client per-peer fan-out. The integration test proves the shared cache is
87
+ genuinely in the path by asserting on ITS OWN counters (`stats.fetches`,
88
+ `cachedKeys`), not the maskable per-route "1 peer hit".
89
+ - Single-machine installs have no peers ⇒ a strict no-op (the cache is never
90
+ constructed, surfaces never call it).
91
+
92
+ ## 6. Rollback
93
+ Set/omit `multiMachine.seamlessness.ws44PoolCache` (dev-gated; dark on the fleet by
94
+ default). When off, the cache is never constructed and `/pool/poll-cache` 503s — fully
95
+ reverting to the direct per-peer fetch. No data migration, no persisted state to undo.
96
+
97
+ ## Evidence
98
+
99
+ - `tests/unit/pool-poll-cache.test.ts` (9): both sides of every decision boundary —
100
+ within-TTL hit vs expired re-fetch; single-flight coalescing; load-shed over/under
101
+ threshold; load-shed with an EMPTY cache still fetches; a failed fetch is never
102
+ cached; per-(peer, route) keying; snapshot wiring + boundary flip at the threshold.
103
+ - `tests/integration/ws44-pool-cache-route.test.ts` (4): real wiring (real second-server
104
+ peer that COUNTS its `/jobs` hits) — the fan-out routes THROUGH the shared cache
105
+ (asserted via the cache's OWN `stats.fetches` + `cachedKeys`, not the maskable
106
+ per-route "1 hit"); a second poll past the per-route window is served from the shared
107
+ cache WITHOUT re-hitting the peer; the merged body is byte-identical wired vs unwired;
108
+ `GET /pool/poll-cache` 503-when-dark / 200-when-wired.
109
+ - `tests/e2e/pool-poll-cache-alive.test.ts` (3): the feature is ALIVE (200, not 503) on
110
+ the real AgentServer stack; sits behind auth (no Bearer → 401/403); the ships-dark
111
+ default 503s with `{ enabled: false }`.
112
+ - Gate checks green: `tsc --noEmit`; `docs-coverage --check`; `no-silent-fallbacks`
113
+ (the one new fail-closed construction-catch is tagged `@silent-fallback-ok`);
114
+ `feature-delivery-completeness` (new featureSection + both shadow-marker variants);
115
+ `lint-dev-agent-dark-gate` (line-map recomputed for the +17 ConfigDefaults shift).
@@ -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.