instar 1.3.557 → 1.3.558
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.
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +33 -6
- package/dist/commands/server.js.map +1 -1
- package/dist/config/ConfigDefaults.d.ts.map +1 -1
- package/dist/config/ConfigDefaults.js +50 -27
- package/dist/config/ConfigDefaults.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts +22 -0
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +75 -0
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/devGatedFeatures.d.ts.map +1 -1
- package/dist/core/devGatedFeatures.js +48 -0
- package/dist/core/devGatedFeatures.js.map +1 -1
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +7 -4
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +63 -63
- package/upgrades/1.3.558.md +75 -0
- package/upgrades/side-effects/mm-pool-seamlessness-devgate.md +149 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# Side-Effects — multiMachine.seamlessness coherence flags → dev-gated (topic 13481), sessionPool master HELD
|
|
2
|
+
|
|
3
|
+
**Change (PR2 of the no-dark-on-dev multi-machine directive):** Re-gate the 5
|
|
4
|
+
`multiMachine.seamlessness` coherence flags — `ws3OneVoice` (one-voice election),
|
|
5
|
+
`ws13Reconcile` (ownership reconcile), `ws41DurableAck` (durable cross-machine /ack),
|
|
6
|
+
`ws43RoleGuard` (role-guard-at-spawn), `ws43JournalLease` (journal-lease cutover) — from
|
|
7
|
+
hardcoded `false` in `ConfigDefaults.ts` to the developmentAgent gate (live-on-dev /
|
|
8
|
+
dark-fleet), mirroring the merged `ws44PoolLinks` / `ws44PoolCache` precedent. The
|
|
9
|
+
`ws43JournalLeaseDryRun` sub-knob is computed COHERENTLY with its gate (dev → live/false,
|
|
10
|
+
fleet → dry-run/true). **The `multiMachine.sessionPool.*` master switch and its
|
|
11
|
+
`inboundQueue` / `holdForStability` sub-flags are deliberately HELD** (see #7).
|
|
12
|
+
|
|
13
|
+
**Driver:** Operator directive (Justin, topic 13481, 2026-06-13): "NOTHING should ship
|
|
14
|
+
dark on development agents. Everything fully functional so it actually gets tested and
|
|
15
|
+
doesn't just rot." PR1 (#1151) moved the 7 stateSync memory stores; PR2 is the remaining
|
|
16
|
+
dark-on-dev multi-machine seamlessness layers.
|
|
17
|
+
|
|
18
|
+
**Spec:** `docs/specs/MULTI-MACHINE-SEAMLESSNESS-SPEC.md` (the converged + approved spec the
|
|
19
|
+
seamlessness flags were built against) + `docs/specs/multi-machine-replicated-store-
|
|
20
|
+
foundation.md` (sessionPool context). This is a follow-up gating fix to already-merged
|
|
21
|
+
features, not a new feature.
|
|
22
|
+
|
|
23
|
+
## What was wrong
|
|
24
|
+
|
|
25
|
+
The 5 seamlessness flags shipped with a literal `false` in `ConfigDefaults.ts` (e.g.
|
|
26
|
+
`ws3OneVoice: false`). A written literal force-darks even a development agent (the #1001
|
|
27
|
+
anti-pattern), so the coherence layers never ran ANYWHERE — they could never be dogfooded
|
|
28
|
+
on Echo / the Mini and never graduated toward the fleet. ws44PoolLinks/ws44PoolCache had
|
|
29
|
+
already been corrected to the omitted-gate pattern; these five lagged behind.
|
|
30
|
+
|
|
31
|
+
## What changed
|
|
32
|
+
|
|
33
|
+
- `src/config/ConfigDefaults.ts` — OMIT the hardcoded `false` for all 5 flags (each carries
|
|
34
|
+
a ws44-style "DELIBERATELY OMITTED" comment) so `resolveDevAgentGate` decides at runtime.
|
|
35
|
+
`ws43JournalLeaseDryRun` is ALSO omitted (computed coherently at the consumer). `ws13DryRun`
|
|
36
|
+
stays a plain hardcoded default `true` (it is the in-component "log intended CAS without
|
|
37
|
+
performing it" rung, NOT the dev-gate). The sessionPool block is UNTOUCHED.
|
|
38
|
+
- `src/commands/server.ts` — route each consumer read through `resolveDevAgentGate(...)`:
|
|
39
|
+
- `ws3OneVoice`: `SpeakerElection.enabled` → `resolveDevAgentGate(ws3Cfg().ws3OneVoice, config)`.
|
|
40
|
+
- `ws13Reconcile`: `OwnershipReconciler.enabled` → gated; `dryRun` stays the plain `ws13DryRun !== false` read.
|
|
41
|
+
- `ws43RoleGuard`: `scheduler.setRoleGuard().enabled` → gated.
|
|
42
|
+
- `ws43JournalLease`: `setJournalLeaseCutover().enabled` → gated; `dryRun` →
|
|
43
|
+
`cfg?.ws43JournalLeaseDryRun ?? !resolveDevAgentGate(undefined, config)` (coherent).
|
|
44
|
+
- the heartbeat `seamlessnessFlags` advert resolves journal-lease via the gate, not the raw read.
|
|
45
|
+
- `src/server/routes.ts` — `ws41DurableAckEnabled()` reads through
|
|
46
|
+
`resolveDevAgentGate(..., ctx.config)`, so the route + precedence guard agree.
|
|
47
|
+
- `src/core/devGatedFeatures.ts` — ADD all 5 flags as `DEV_GATED_FEATURES` entries
|
|
48
|
+
(by-name, each with a non-destructive/no-egress justification). A header comment records
|
|
49
|
+
WHY the sessionPool master + sub-flags are NOT moved here (the StageAdvancer stage-gate).
|
|
50
|
+
- `src/core/PostUpdateMigrator.ts` — NEW `migrateConfigSeamlessnessDevGate(config)`: strips
|
|
51
|
+
a default-shaped `false` per flag (and the paired `ws43JournalLeaseDryRun:true` only
|
|
52
|
+
alongside a default-shaped `ws43JournalLease:false`) so the gate resolves on update. An
|
|
53
|
+
operator-set value (explicit `true`, a divergent dryRun) is left ENTIRELY alone. Idempotent.
|
|
54
|
+
Wired into the migrate path with `upgraded`/`skipped` reporting.
|
|
55
|
+
- `tests/unit/lint-dev-agent-dark-gate.test.ts` — EXPECTED attribution map line numbers
|
|
56
|
+
recomputed via the attributor (the 5 removed literals + comment reflow shift sessionPool
|
|
57
|
+
786→809 and cartographer 1100→1123). The 3 sessionPool flags STAY in the map (held,
|
|
58
|
+
hardcoded false). No paths added/removed (the seamlessness flags were never `enabled:`-named).
|
|
59
|
+
|
|
60
|
+
## Decision-point inventory (Phase-4)
|
|
61
|
+
|
|
62
|
+
### 1. Over-block
|
|
63
|
+
A gate that resolved live when it shouldn't. Not possible: `resolveDevAgentGate` resolves
|
|
64
|
+
true ONLY when `developmentAgent: true` (or an explicit `true`), so the fleet is byte-for-byte
|
|
65
|
+
unchanged. Each layer is additionally a strict single-machine no-op (the election never
|
|
66
|
+
engages below 2 online machines; the role-guard never fires when this machine holds the lease;
|
|
67
|
+
the journal-lease cutover requires ≥2 flag-coherent peers).
|
|
68
|
+
|
|
69
|
+
### 2. Under-block
|
|
70
|
+
A consumer left on the raw `=== true` read would keep the feature DARK on a dev agent even
|
|
71
|
+
though ConfigDefaults omits it (the PR1 "still-dark-on-dev = incomplete" failure). Closed by
|
|
72
|
+
grepping ALL consumer sites (5 flags + the heartbeat advert + the dryRun coherence formula)
|
|
73
|
+
and routing every one through the gate, then pinning it with a source-string no-missed-consumer
|
|
74
|
+
wiring test (`tests/unit/seamlessness-dev-gate-wiring.test.ts`, section B) that fails CI if a
|
|
75
|
+
consumer reverts to the raw read.
|
|
76
|
+
|
|
77
|
+
### 3. Level-of-abstraction fit
|
|
78
|
+
The gate is resolved at the SAME construction/route boundary as the ws44 precedent — one
|
|
79
|
+
funnel (`resolveDevAgentGate`) reads the raw config value and the agent's `developmentAgent`
|
|
80
|
+
flag. No new abstraction; the consumers' downstream `enabled`-true semantics are unchanged.
|
|
81
|
+
|
|
82
|
+
### 4. Signal vs authority compliance
|
|
83
|
+
Each layer runs in the SAFE direction and none gains authority from going live-on-dev:
|
|
84
|
+
ws3OneVoice only WITHHOLDS a duplicate send (never fabricates one); ws13Reconcile runs in its
|
|
85
|
+
own dry-run (logs intended CAS, performs none); ws43RoleGuard can only REFUSE a spawn (never
|
|
86
|
+
wrongly spawn); ws41DurableAck persists an intent bound to the AUTHENTICATED operator and the
|
|
87
|
+
owner REVALIDATES at apply time; ws43JournalLease coordinates job claims over durable state
|
|
88
|
+
and the cutover gate guarantees the two mechanisms are never both live for a job set.
|
|
89
|
+
|
|
90
|
+
### 5. Interactions
|
|
91
|
+
- ws43JournalLease ↔ ws43JournalLeaseDryRun: the two MUST resolve coherently or a dev agent
|
|
92
|
+
would run live-but-dry-run (logged, never exercised) — handled by the `?? !resolveDevAgentGate`
|
|
93
|
+
formula at the consumer AND the migration's paired strip.
|
|
94
|
+
- The heartbeat `seamlessnessFlags.ws43JournalLease` advert (what peers read to decide
|
|
95
|
+
coherence) now mirrors the resolved enabled+not-dry-run, so a dev agent honestly advertises
|
|
96
|
+
the capability to a peer.
|
|
97
|
+
- sessionPool HELD: because the seamlessness flags have NO stage-gate, they are independent of
|
|
98
|
+
the sessionPool master — flipping them live-on-dev does not activate the (held) session pool.
|
|
99
|
+
|
|
100
|
+
### 6. External surfaces
|
|
101
|
+
None new. The five features' routes/behaviors already existed; this only changes WHICH agents
|
|
102
|
+
resolve them live. Replication/coordination is between the operator's OWN machines — no
|
|
103
|
+
external egress, no third-party spend, no new API.
|
|
104
|
+
|
|
105
|
+
### 7. Rollback cost & multi-machine posture (the heart)
|
|
106
|
+
- **Rollback:** trivial and per-flag — set an explicit `multiMachine.seamlessness.<flag>: false`
|
|
107
|
+
in `.instar/config.json` to force-dark even a dev agent; an explicit `true` is the fleet-flip.
|
|
108
|
+
No data migration to undo (these are coordination layers, not stores). The 3 sessionPool flags
|
|
109
|
+
need no rollback — they were never flipped.
|
|
110
|
+
- **Multi-machine posture:** these layers EXIST to make the operator's two machines behave as
|
|
111
|
+
one agent. Live-on-dev means Echo (laptop) + the Mac Mini now genuinely exercise one-voice
|
|
112
|
+
election, ownership reconcile, durable cross-machine acks, the spawn-time role-guard, and the
|
|
113
|
+
journal-lease cutover across the real two-machine mesh — the only way they get tested before
|
|
114
|
+
fleet. A single-machine dev agent is a strict no-op for all five (no peers).
|
|
115
|
+
- **sessionPool HELD — the deliberate exception:** `multiMachine.sessionPool.{enabled,
|
|
116
|
+
inboundQueue.enabled, holdForStability.enabled}` were NOT moved to dev-gated. They share a
|
|
117
|
+
SECOND, structurally-enforced gate: `sessionPool.stage` is **StageAdvancer-write-only** (a
|
|
118
|
+
module-private `STAGE_WRITE_TOKEN`, rejected by `stageWriteGuard.ts` for any other writer),
|
|
119
|
+
defaults to `'dark'`, and advances only through an E2E-gated rollout ladder
|
|
120
|
+
(`dark → shadow → live-transfer → rebalance`). The activation expression EVERYWHERE is
|
|
121
|
+
`enabled && stage !== 'dark'` (server.ts:1990, 1998, 14351, 15611; boot-sweep gate 7019-7024).
|
|
122
|
+
So: (a) dev-gating `enabled` alone leaves the pool INERT on a dev agent (`stage` is still
|
|
123
|
+
dark) — a hollow move that fails the directive's "actually gets tested"; and (b) forcing
|
|
124
|
+
`stage` past dark in ConfigDefaults would BYPASS the deliberate cutover discipline the
|
|
125
|
+
StageAdvancer exists to enforce — not a clean, safe-by-default, reversible change. This is a
|
|
126
|
+
genuine collision between the "nothing dark on dev" directive and an explicit structural
|
|
127
|
+
safety invariant, so it is surfaced to the operator rather than forced. **Operator decision
|
|
128
|
+
needed:** advance `sessionPool.stage` on the dev mesh via the StageAdvancer ladder (gated on
|
|
129
|
+
green E2E) if active-active pooling should be dogfooded live; until then it stays dark.
|
|
130
|
+
|
|
131
|
+
## Migration parity
|
|
132
|
+
|
|
133
|
+
`applyDefaults` is add-missing-only deep-merge, so a new agent gets the omitted-flag shape via
|
|
134
|
+
`init`. Existing agents that received the old `false` literals would keep them (explicit values
|
|
135
|
+
are not overwritten) and stay dark even on a dev agent — so `migrateConfigSeamlessnessDevGate`
|
|
136
|
+
strips the exact default-shaped `false` (and paired dryRun) on update. An operator's hand-edited
|
|
137
|
+
value is never touched (reach is not authority). Idempotent (a second run strips nothing).
|
|
138
|
+
|
|
139
|
+
## Tests
|
|
140
|
+
|
|
141
|
+
Unit: `seamlessness-dev-gate-wiring` (13 — resolution + no-missed-consumer source-seam +
|
|
142
|
+
registry coherence), `PostUpdateMigrator-seamlessnessDevGate` (9), `lint-dev-agent-dark-gate`
|
|
143
|
+
(line-map recomputed), `devGatedFeatures-wiring` (74), `feature-delivery-completeness` (99),
|
|
144
|
+
`SpeakerElection` / `OwnershipReconciler` / `ws3-one-voice-wiring`. Integration/E2E: the
|
|
145
|
+
existing `scheduler-role-guard`, `scheduler-journal-lease-cutover`, `attention-remote-ack`
|
|
146
|
+
suites stay green; `attention-remote-ack-alive` E2E gains two dev-gate cases (flag OMITTED +
|
|
147
|
+
developmentAgent:true → route ALIVE; fleet → 503) proving the gate flips the route, not just
|
|
148
|
+
the registry. Typecheck clean (`tsc --noEmit` exit 0); full lint suite clean. The full suite is
|
|
149
|
+
left to CI (the authority).
|