instar 1.3.508 → 1.3.509
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 +148 -1
- package/dist/commands/server.js.map +1 -1
- package/dist/core/AutonomousSessions.d.ts +8 -0
- package/dist/core/AutonomousSessions.d.ts.map +1 -1
- package/dist/core/AutonomousSessions.js +34 -0
- package/dist/core/AutonomousSessions.js.map +1 -1
- package/dist/core/MeshRpc.d.ts +6 -1
- package/dist/core/MeshRpc.d.ts.map +1 -1
- package/dist/core/MeshRpc.js +7 -0
- package/dist/core/MeshRpc.js.map +1 -1
- package/dist/core/OwnershipReconciler.d.ts.map +1 -1
- package/dist/core/OwnershipReconciler.js +17 -0
- package/dist/core/OwnershipReconciler.js.map +1 -1
- package/dist/core/SessionDrainRunner.d.ts +116 -0
- package/dist/core/SessionDrainRunner.d.ts.map +1 -0
- package/dist/core/SessionDrainRunner.js +162 -0
- package/dist/core/SessionDrainRunner.js.map +1 -0
- package/dist/core/SessionOwnership.d.ts +22 -1
- package/dist/core/SessionOwnership.d.ts.map +1 -1
- package/dist/core/SessionOwnership.js +17 -1
- package/dist/core/SessionOwnership.js.map +1 -1
- package/dist/core/seamlessnessConfig.d.ts +8 -1
- package/dist/core/seamlessnessConfig.d.ts.map +1 -1
- package/dist/core/seamlessnessConfig.js +8 -1
- package/dist/core/seamlessnessConfig.js.map +1 -1
- package/dist/core/types.d.ts +5 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/server/AgentServer.d.ts +8 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +1 -0
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/middleware.d.ts +14 -0
- package/dist/server/middleware.d.ts.map +1 -1
- package/dist/server/middleware.js +17 -0
- package/dist/server/middleware.js.map +1 -1
- package/dist/server/routes.d.ts +11 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +47 -1
- package/dist/server/routes.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +47 -47
- package/upgrades/1.3.509.md +74 -0
- package/upgrades/side-effects/multi-machine-seamlessness-ws12b-drain.md +205 -0
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# Side-Effects Review — WS1.2b drain verb: active-topic transfers COMPLETE
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `multi-machine-seamlessness-ws12b-drain`
|
|
4
|
+
**Date:** `2026-06-12`
|
|
5
|
+
**Author:** `Instar Agent (echo)`
|
|
6
|
+
**Second-pass reviewer:** `independent reviewer subagent (MANDATORY — ownership + session lifecycle; verdict appended below)`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
Implements the WS1.2 drain semantics from the merged MULTI-MACHINE-SEAMLESSNESS-SPEC
|
|
11
|
+
(§WS1.2): a transfer of an ACTIVELY-USED conversation now COMPLETES — the owner
|
|
12
|
+
finishes the in-flight turn (bounded), suspends any autonomous run (the remote WS1.4
|
|
13
|
+
arm), closes its local session, and lands the target's claim, which is exactly the
|
|
14
|
+
moment the durable queue's ownership-contention barrier releases inbound to the new
|
|
15
|
+
owner. Files: `src/core/SessionOwnership.ts` (FSM `abort-transfer` action),
|
|
16
|
+
`src/core/SessionDrainRunner.ts` (new owner-side bounded sequence, all I/O injected),
|
|
17
|
+
`src/core/MeshRpc.ts` (`drain` verb + router-only RBAC + `drain-unauthorized` 403),
|
|
18
|
+
`src/core/OwnershipReconciler.ts` (drain-claim grace — the reconciler backstops a
|
|
19
|
+
dead-mid-drain owner without front-running a live one), `src/core/seamlessnessConfig.ts`
|
|
20
|
+
(SEAMLESSNESS_PROTOCOL_VERSION 1→2 per spec), `src/core/AutonomousSessions.ts`
|
|
21
|
+
(`markAutonomousInterruptedMidTask`), `src/core/types.ts`
|
|
22
|
+
(`seamlessnessFlags.ws12DrainReceive`), `src/commands/server.ts` (runner construction,
|
|
23
|
+
mesh handler, heartbeat advertisement, `_sendDrain` local/remote sender),
|
|
24
|
+
`src/server/AgentServer.ts` + `src/server/routes.ts` (ctx plumbing + the
|
|
25
|
+
`/pool/transfer` drain leg).
|
|
26
|
+
|
|
27
|
+
## Decision-point inventory
|
|
28
|
+
|
|
29
|
+
- `SessionOwnership` FSM — **modify** — adds `abort-transfer` (transferring →
|
|
30
|
+
active(self), owner-only, epoch+1). The emergency-stop escape: an aborted transfer
|
|
31
|
+
leaves NOTHING split, and the epoch bump fences a stale target claim.
|
|
32
|
+
- `MeshRpc` RBAC — **modify** — `drain` is router-only with its own refusal reason.
|
|
33
|
+
Reach ≠ authority: the receiver's CAS re-validates ownership + epoch regardless.
|
|
34
|
+
- `SessionDrainRunner` — **add** — the owner-side drain decision sequence (refuse
|
|
35
|
+
not-owner / stale-epoch / cas-lost; abort on emergency stop EVERY poll; forced
|
|
36
|
+
close only at the bound, with the honest marker + ONE notice).
|
|
37
|
+
- `/pool/transfer` drain leg — **modify** — orders the drain BEFORE the pin when the
|
|
38
|
+
owner can drain; 409 failed-needs-retry ONLY on the emergency-stop abort; every
|
|
39
|
+
other failure degrades to today's pin path (recorded in the response, never blocking).
|
|
40
|
+
- `OwnershipReconciler` — **modify** — transferring-to-me claim held back by the
|
|
41
|
+
drain grace window (45s = bound + slack) so it backstops without front-running.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## 1. Over-block
|
|
46
|
+
|
|
47
|
+
**What legitimate inputs does this reject that it shouldn't?**
|
|
48
|
+
|
|
49
|
+
- A transfer ordered while ownership raced (stale epoch) is refused by the drain and
|
|
50
|
+
DEGRADES to today's pin path — the transfer still happens, just without the drain.
|
|
51
|
+
No new rejection surface for the caller except the deliberate emergency-stop 409.
|
|
52
|
+
- The 409 abort path requires the operator's own emergency stop within the last 120s
|
|
53
|
+
— the only input it "rejects" is the move the operator just halted. A STALE stop
|
|
54
|
+
flag (>120s) never vetoes (the freshness bound exists precisely for this).
|
|
55
|
+
- The drain bound (30s default) force-closes a session mid-turn rather than blocking
|
|
56
|
+
the transfer forever — the spec's explicit choice; the honest notice + the
|
|
57
|
+
`interrupted_mid_task` marker are the compensations. No issue identified beyond
|
|
58
|
+
the spec-accepted forced close.
|
|
59
|
+
|
|
60
|
+
## 2. Under-block
|
|
61
|
+
|
|
62
|
+
**What failure modes does this still miss?**
|
|
63
|
+
|
|
64
|
+
- The spec's full Track-H release gate ("final context flush durably replicated
|
|
65
|
+
before barrier release") is implemented as drain-completion release with the
|
|
66
|
+
target's spawn-time history fetch + working-set pull carrying context — the
|
|
67
|
+
TransferOrchestrator's ledger-flush dependency does not exist yet. Honest bound,
|
|
68
|
+
named in the runner header. <!-- tracked: CMT-1416 -->
|
|
69
|
+
- `sessionQuiet` reads tmux activity (isSessionActivelyWorking); a session that
|
|
70
|
+
*looks* idle mid-thought (long tool call quiescing the pane) can be closed at a
|
|
71
|
+
non-ideal boundary. Mitigated by the resume-side history fetch; same signal the
|
|
72
|
+
reaper already trusts.
|
|
73
|
+
- A drain that lands the claim but whose pin-set then fails on the holder leaves
|
|
74
|
+
ownership@target with no pin — the WS1.3 reconciler converges it (this is the
|
|
75
|
+
exact divergence class it ships for).
|
|
76
|
+
|
|
77
|
+
## 3. Level-of-abstraction fit
|
|
78
|
+
|
|
79
|
+
The runner is a pure injected-deps sequence in core (unit-testable, no transport);
|
|
80
|
+
transport lives in MeshRpc (where every other verb lives); the sender leg lives in
|
|
81
|
+
the route that already owns transfer semantics. The drain BARRIER reuses the queue's
|
|
82
|
+
existing ownership-contention hold — no parallel queueing machinery was built (the
|
|
83
|
+
level-of-abstraction note in the runner header records why TransferOrchestrator was
|
|
84
|
+
NOT used: its ledger deps don't exist; the runner can later become its drain dep).
|
|
85
|
+
|
|
86
|
+
## 4. Signal vs authority compliance
|
|
87
|
+
|
|
88
|
+
The drain verb carries real authority (it closes a session) — so authority is
|
|
89
|
+
LAYERED, never brittle-single-check: RBAC proves the sender may ask (router-only);
|
|
90
|
+
the runner's CAS fence proves the ask still matches reality (owner + exact epoch);
|
|
91
|
+
the FSM proves the transition is legal; the emergency stop preempts at every poll;
|
|
92
|
+
and the forced path is bounded + marked + noticed. A failed/refused drain never
|
|
93
|
+
blocks the transfer — it degrades to the pre-existing path (failure of the new
|
|
94
|
+
machinery cannot deny the old capability).
|
|
95
|
+
|
|
96
|
+
## 5. Interactions
|
|
97
|
+
|
|
98
|
+
- **WS1.3 reconciler:** the transferring-to-me claim now waits out the drain grace
|
|
99
|
+
(45s) so a LIVE drain is never front-run; a dead-mid-drain owner is still
|
|
100
|
+
backstopped (grace expiry → reconciler claim → transfer completes). Tested.
|
|
101
|
+
- **P19 closeout breaker (#1092):** after a drained transfer the local session is
|
|
102
|
+
already closed — the closeout finds nothing; no double-close (terminateSession is
|
|
103
|
+
CAS'd on live status).
|
|
104
|
+
- **Durable queue:** route() queues inbound while status='transferring'
|
|
105
|
+
(ownership-contention) — the drain window IS the barrier; the claim is the release
|
|
106
|
+
point. No new queue states introduced.
|
|
107
|
+
- **Emergency stop:** the abort CAS can lose to an already-landed claim (grace
|
|
108
|
+
raced) — reported honestly as `abort-cas-lost`; the stop's own machinery still
|
|
109
|
+
halts local work.
|
|
110
|
+
- **Double-drain:** a re-delivered drain for the SAME transfer resumes the wait loop
|
|
111
|
+
idempotently (no second CAS); a drain for a DIFFERENT transfer dies at the epoch
|
|
112
|
+
fence.
|
|
113
|
+
|
|
114
|
+
## 6. External surfaces
|
|
115
|
+
|
|
116
|
+
- New mesh verb `drain` (signed, router-only). An OLD peer answers 501 no-handler →
|
|
117
|
+
the sender degrades to today's path; an old SENDER never emits the verb. The
|
|
118
|
+
capability flag (`ws12DrainReceive`, heartbeat-advertised from runner presence)
|
|
119
|
+
means a doomed order is normally never sent at all. SEAMLESSNESS_PROTOCOL_VERSION
|
|
120
|
+
bumped 1→2 per the spec's skew table.
|
|
121
|
+
- `/pool/transfer` response grows `drain` + may now 409 with `failedNeedsRetry` on
|
|
122
|
+
an emergency-stop abort; existing fields unchanged (additive).
|
|
123
|
+
- Timing: the route awaits the drain (≤ bound + slack; remote call capped at 50s) —
|
|
124
|
+
a transfer of an active conversation now takes up to ~30-50s instead of instant
|
|
125
|
+
paperwork. That is the feature: the paperwork-instant transfer was the half-move.
|
|
126
|
+
The route carries its own request-timeout budget (`POOL_TRANSFER_TIMEOUT_MS`
|
|
127
|
+
= 75s, above the 50s remote cap) so the middleware never 408s mid-drain
|
|
128
|
+
(second-pass concern #1).
|
|
129
|
+
|
|
130
|
+
## 7. Multi-machine posture (Cross-Machine Coherence)
|
|
131
|
+
|
|
132
|
+
**Replicated/coordinated by design — this IS the multi-machine path.** Ownership
|
|
133
|
+
moves through the epoch-fenced CAS registry (single-router topology); placement
|
|
134
|
+
history journals on every CAS (call-site pairing); the capability travels in the
|
|
135
|
+
authenticated heartbeat (fixed-size boolean — Phase C: no per-topic inventory, works
|
|
136
|
+
for N cloud VMs); the drain order is a signed mesh verb; takeover-without-consent
|
|
137
|
+
remains impossible (claim requires the FSM's named-target rule or the reconciler's
|
|
138
|
+
evidence gate). Notices: the forced-close notice goes to the TOPIC (the one voice
|
|
139
|
+
for it); no new URL surfaces.
|
|
140
|
+
|
|
141
|
+
## 8. Rollback cost
|
|
142
|
+
|
|
143
|
+
- The sender leg self-disables when no owner advertises the capability — reverting
|
|
144
|
+
the PR (or running mixed-version) restores today's pin path exactly.
|
|
145
|
+
- No durable format changes: the FSM action is additive; protocol version is
|
|
146
|
+
advisory metadata; the autonomous-file marker is an additive frontmatter line.
|
|
147
|
+
- No config flag was added for the drain itself BY DESIGN: it activates only along
|
|
148
|
+
capability advertisement, which exists only where this code runs; the pool's
|
|
149
|
+
master staging (`multiMachine.sessionPool.stage`) still gates the surrounding
|
|
150
|
+
machinery. Worst-case back-out = revert + release (no data migration).
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Post-push CI fix (no-silent-fallbacks ratchet)
|
|
155
|
+
|
|
156
|
+
CI shard 3 failed the no-silent-fallbacks ratchet (469 > 468). Investigation:
|
|
157
|
+
every catch this PR ADDS is tagged `@silent-fallback-ok` (drain emergency-stop
|
|
158
|
+
flag read, runner-not-wired guard, `_sendDrain` RPC catch, /pool/transfer
|
|
159
|
+
drain-leg degrade catch, forced-close notice `.catch`) or is non-matching
|
|
160
|
+
(returns a populated object). The flagged-list set-diff shows NO genuine new
|
|
161
|
+
fallback — the +1 is a detection-window artifact: line shifts in the two edited
|
|
162
|
+
files reshape the heuristic's 20-line catch window so a pre-existing,
|
|
163
|
+
previously-uncounted catch now matches (the exact fragility the file's own
|
|
164
|
+
174→186 and 437→447 bump notes document). Baseline bumped 468→469 with that
|
|
165
|
+
justification (the sanctioned escape valve); the count only decreases from here.
|
|
166
|
+
|
|
167
|
+
## Second-pass review
|
|
168
|
+
|
|
169
|
+
**MANDATORY (ownership + session lifecycle) — independent reviewer, run on Opus 4.8.**
|
|
170
|
+
(The first attempt died on the Fable 5 outage — the reviewer subagent inherited
|
|
171
|
+
the escalated claude-fable-5 model, which Anthropic disabled mid-run under a
|
|
172
|
+
government directive; re-run cleanly on Opus.)
|
|
173
|
+
|
|
174
|
+
**Verdict: CONCERN RAISED (one real defect), everything else CONCURS.**
|
|
175
|
+
|
|
176
|
+
- **Concern #1 (real, was shippable-blocking) — `/pool/transfer` had no
|
|
177
|
+
request-timeout override for the synchronous drain.** The route awaits the
|
|
178
|
+
owner-side drain (up to the 30s bound + the 50s remote-call cap) but inherited
|
|
179
|
+
the 30s middleware default, so a clean drain would 408 mid-handler while the
|
|
180
|
+
handler kept running to completion (landing the claim + pin) — the exact
|
|
181
|
+
"408 while the handler keeps running" class the outbound/parity overrides
|
|
182
|
+
already exist to prevent. The integration tests passed only because they
|
|
183
|
+
inject a synchronous `sendDrain` mock that returns instantly, never exercising
|
|
184
|
+
the bound — a genuine coverage gap.
|
|
185
|
+
**FIXED:** added `POOL_TRANSFER_TIMEOUT_MS = 75_000` and wired `/pool/transfer`
|
|
186
|
+
into `buildRequestTimeoutOverrides()` (clears the 50s remote cap with margin);
|
|
187
|
+
added two assertions to `tests/unit/AgentServer-outbound-timeout.test.ts`
|
|
188
|
+
(the route resolves to the drain budget; the fast sibling `/pool/placement`
|
|
189
|
+
stays on the default). The fix mirrors the exact pattern the reviewer pointed
|
|
190
|
+
to and is verified against the production map + matcher.
|
|
191
|
+
|
|
192
|
+
- **CONCURRED, verified in source (reviewer notes 1–10):** the kill path (topic→
|
|
193
|
+
tmux→record resolution can't touch another topic's session; force-bypass only
|
|
194
|
+
at the bound; missing session = non-fatal skip); nothing-split-on-abort (owner-
|
|
195
|
+
only abort, epoch bump fences a stale target claim); no-theft (runner fences +
|
|
196
|
+
reconciler 45s drain grace + degrade-path never steals a live owner); barrier
|
|
197
|
+
correctness (the owner's target-named claim is FSM-legal via the confirmClaim
|
|
198
|
+
precedent; queue holds while transferring; claim is the release); emergency
|
|
199
|
+
stop (every-poll check, 120s flag freshness bound, 409 + no-pin only on abort);
|
|
200
|
+
fail-degrade (every non-abort failure shape → today's pin path); version skew
|
|
201
|
+
(501 degrade, verb never sent by old senders, honest capability advertisement,
|
|
202
|
+
registry stores + serves seamlessnessFlags); markAutonomousInterruptedMidTask
|
|
203
|
+
(atomic, idempotent, missing-file no-op); double-drain idempotency; RBAC
|
|
204
|
+
router-only with its own 403. 100 tests green across the 6 drain files (+2 in
|
|
205
|
+
the timeout test = 102).
|