instar 1.3.721 → 1.3.723
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 +28 -1
- package/dist/commands/server.js.map +1 -1
- package/dist/core/SessionManager.d.ts +48 -4
- package/dist/core/SessionManager.d.ts.map +1 -1
- package/dist/core/SessionManager.js +65 -9
- package/dist/core/SessionManager.js.map +1 -1
- package/dist/core/TopicPinFoldView.d.ts +33 -1
- package/dist/core/TopicPinFoldView.d.ts.map +1 -1
- package/dist/core/TopicPinFoldView.js +58 -2
- package/dist/core/TopicPinFoldView.js.map +1 -1
- package/dist/monitoring/SessionReaper.d.ts +9 -0
- package/dist/monitoring/SessionReaper.d.ts.map +1 -1
- package/dist/monitoring/SessionReaper.js +10 -2
- package/dist/monitoring/SessionReaper.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +3 -3
- package/upgrades/1.3.722.md +73 -0
- package/upgrades/1.3.723.md +74 -0
- package/upgrades/side-effects/closeout-lease-carveout-and-firstparty-bootstrap.md +148 -0
- package/upgrades/side-effects/u4-1-poolreference-skew-fix.md +47 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# Side-Effects Review — F8 closeout lease carve-out + F7 first-party bootstrap provenance
|
|
2
|
+
|
|
3
|
+
**Source:** roadmap item 0.6 — findings F8 + F7 of the 2026-07-02 live test-as-self matrix.
|
|
4
|
+
**Tier:** 1 (two narrow, audited fixes to existing machinery; no new routes, no new config, no new stores).
|
|
5
|
+
**Files:** src/core/SessionManager.ts, src/monitoring/SessionReaper.ts, src/commands/server.ts,
|
|
6
|
+
tests/unit/session-manager-terminate.test.ts, tests/unit/session-reaper-topic-moved.test.ts,
|
|
7
|
+
tests/unit/session-reaper-closeout-liveness.test.ts, tests/unit/session-reaper-wiring.test.ts,
|
|
8
|
+
tests/unit/session-manager-first-party-inject.test.ts (new),
|
|
9
|
+
tests/integration/session-lifecycle-reap-wiring.test.ts
|
|
10
|
+
|
|
11
|
+
## What changed
|
|
12
|
+
|
|
13
|
+
1. **F8 — `terminateSession` gains `bypassLeaseForTopicMovedCloseout`** (SessionManager.ts): a
|
|
14
|
+
narrow opt that lifts ONLY the lease-holder gate inside the `origin === 'autonomous'`
|
|
15
|
+
authority cascade. Protected, CAS/in-flight, and EVERY KEEP-guard are unchanged and still
|
|
16
|
+
run. The SessionReaper's `attemptCloseoutTerminate` (the shared topic-moved closeout
|
|
17
|
+
machinery — the ONLY `deps.terminate` callsite on the closeout path) now sets the flag on
|
|
18
|
+
every closeout terminate; the server.ts terminate-dep hop threads it through (dead-dep trap
|
|
19
|
+
guarded by a wiring test).
|
|
20
|
+
2. **F7 — `injectMessage` gains `opts.firstParty: { source }`** (SessionManager.ts): in-process
|
|
21
|
+
provenance for instar's OWN injections. When set, the InputGuard cascade (Layer 1
|
|
22
|
+
provenance / Layer 1.5 patterns / Layer 2 LLM coherence) is skipped, the injection is
|
|
23
|
+
audited to the security log as `first-party-injection`, and the text goes straight to
|
|
24
|
+
`rawInject` (which still strips embedded bracketed-paste markers — the S2 sanitizer is
|
|
25
|
+
downstream of the bypass, so a first-party injection cannot forge paste boundaries either).
|
|
26
|
+
The three session-bootstrap lanes (existing-session reuse, ready-and-inject, still-alive
|
|
27
|
+
fallback) set `firstParty: { source: 'session-bootstrap' }`.
|
|
28
|
+
|
|
29
|
+
## Blast radius — the reap-authority carve-out (the load-bearing review)
|
|
30
|
+
|
|
31
|
+
**What can now be reaped that couldn't before:** exactly one class of session — a LOCAL
|
|
32
|
+
session on a NON-lease-holding machine, terminated AUTONOMOUSLY, whose terminate call carries
|
|
33
|
+
`bypassLeaseForTopicMovedCloseout: true`. Before the fix such a terminate was unconditionally
|
|
34
|
+
`skipped:'not-lease-holder'`. Nothing else changed: on the lease-holding machine the flag is a
|
|
35
|
+
no-op (proven by test), and a flag-less standby terminate keeps today's veto byte-for-byte
|
|
36
|
+
(proven by test).
|
|
37
|
+
|
|
38
|
+
**Why the scope-guard prevents abuse — four independent layers:**
|
|
39
|
+
|
|
40
|
+
1. **In-process-only minting.** The flag is a function parameter on `terminateSession`. No
|
|
41
|
+
HTTP surface reaches it: the operator kill route (`routes.ts`) hardcodes its opts object
|
|
42
|
+
and never spreads request input into it; the remote-close relay stamps `origin:'operator'`
|
|
43
|
+
(a different cascade entirely — operator kills always bypassed the lease gate, unchanged).
|
|
44
|
+
Message content, config values, and peer traffic cannot mint the flag by construction.
|
|
45
|
+
2. **Single mint site, verified non-ownership upstream.** In the whole tree the flag is set
|
|
46
|
+
`true` only inside `SessionReaper.attemptCloseoutTerminate` (pinned by a source-scope
|
|
47
|
+
test), which is reachable ONLY from the two topic-moved closeout paths — both gated on
|
|
48
|
+
`topicOwnerElsewhere` (the pool ownership registry names ANOTHER machine as the topic's
|
|
49
|
+
owner) plus the multi-tick dwell, and on the gated path additionally on remote-owner
|
|
50
|
+
liveness confirmation. "Provably no longer owns" is enforced before the flag exists.
|
|
51
|
+
3. **Only the lease gate lifts.** The carve-out is an `&&` clause on the lease check alone.
|
|
52
|
+
Protected sessions (checked BEFORE the lease gate) still refuse — proven by test. Every
|
|
53
|
+
KEEP-guard (recent-user-message, active-process, active-subagent, relay-lease, open
|
|
54
|
+
commitments…) still runs and still vetoes — proven by tests on recent-user-message,
|
|
55
|
+
active-subagent, and relay-lease. The Part E recent-message bypass is UNCHANGED and still
|
|
56
|
+
only minted on the liveness-confirmed freshest-interaction path (proven by test: lease
|
|
57
|
+
bypass carried while Part E is correctly withheld).
|
|
58
|
+
4. **The honesty layer is preserved.** The P19 veto breaker (5 attempts → loud give-up +
|
|
59
|
+
attention escalation) is untouched. A closeout now vetoed by a genuine KEEP-guard still
|
|
60
|
+
counts vetoes and still gives up loudly. The fix removes only the structural
|
|
61
|
+
always-deny; it does not remove any honest terminal state.
|
|
62
|
+
|
|
63
|
+
**Worst-case abuse analysis:** a compromised or buggy in-process caller could pass the flag on
|
|
64
|
+
a non-closeout terminate. What it buys them: skipping ONE gate (lease) that `origin:'operator'`
|
|
65
|
+
already skips — while protected + all KEEP-guards still apply. The flag grants strictly LESS
|
|
66
|
+
authority than the pre-existing operator origin available to the same in-process callers, so it
|
|
67
|
+
introduces no new privilege class.
|
|
68
|
+
|
|
69
|
+
## Blast radius — the first-party injection bypass (F7)
|
|
70
|
+
|
|
71
|
+
- **What skips the guard:** only text injected through the three session-bootstrap lanes —
|
|
72
|
+
text instar itself composed in-process seconds earlier. The Slack inbound lane
|
|
73
|
+
(server.ts) carries live USER content inside its wrapper and is deliberately NOT tagged;
|
|
74
|
+
TriageOrchestrator's inject dep maps to `sendInput` (never entered the guard); every other
|
|
75
|
+
`injectMessage` caller is untouched and keeps the exact pre-fix cascade.
|
|
76
|
+
- **Unforgeability bar (per the audit):** the guard checks provenance recorded at injection
|
|
77
|
+
time — an in-process parameter — not any string in the text. A content-only forged
|
|
78
|
+
"first-party" claim and a byte-identical copy of the bootstrap template are BOTH proven by
|
|
79
|
+
test to still traverse the full guard cascade and get flagged. There is no marker to
|
|
80
|
+
string-match, hence nothing to forge.
|
|
81
|
+
- **Auditability:** every first-party injection writes a `first-party-injection` event
|
|
82
|
+
(session, source label, 100-char preview) to `state/security.jsonl` — same log the guard's
|
|
83
|
+
own flags land in, so the bypass is visible in exactly the place a security review reads.
|
|
84
|
+
- **Residual risk:** a future developer tags a lane that carries user content. Mitigation:
|
|
85
|
+
the opt's doc comment states the rule (only injector code that AUTHORED the text may set
|
|
86
|
+
it), and the wiring test pins the current tag sites (3, exactly) so any new tag site is a
|
|
87
|
+
visible, reviewed diff.
|
|
88
|
+
|
|
89
|
+
## Risk + mitigation
|
|
90
|
+
|
|
91
|
+
- **Risk (F8):** a stale ownership signal closes a session the machine still owns.
|
|
92
|
+
**Mitigation:** unchanged from the existing closeout design — the flag does not touch the
|
|
93
|
+
ownership/dwell/liveness gates; it only changes what happens AFTER they have all passed.
|
|
94
|
+
A wrongly-closed session is additionally covered by the existing reap-notify + resume-queue
|
|
95
|
+
machinery (the topic gets told; mid-work sessions queue for revival).
|
|
96
|
+
- **Risk (F8):** double-kill race with the new owner. **Mitigation:** the CAS + in-flight
|
|
97
|
+
guard on `terminateSession` is upstream of the carve-out and unchanged; and the session
|
|
98
|
+
being killed is local-only — the new owner's session is on another machine, unreachable by
|
|
99
|
+
this authority by construction.
|
|
100
|
+
- **Risk (F7):** the bootstrap bypass hides a real injection that rode INTO the bootstrap
|
|
101
|
+
composition (e.g. hostile thread history quoted into the context file). **Mitigation:** the
|
|
102
|
+
guard never reviewed the bootstrap-FILE content anyway (it reviews the injected turn, which
|
|
103
|
+
is the short "[IMPORTANT: Read <path>…]" wrapper); the flagged-then-skipped failure mode it
|
|
104
|
+
caused was strictly worse (the session skipped the relay instructions and went dark). The
|
|
105
|
+
content-side defenses (quoting, `<replicated-untrusted-data>` envelopes, coherence gates)
|
|
106
|
+
are unchanged.
|
|
107
|
+
|
|
108
|
+
## Migration parity
|
|
109
|
+
|
|
110
|
+
None required. No config keys, no hook templates, no CLAUDE.md template sections, no skills
|
|
111
|
+
changed. Both fixes are behavior inside existing in-process machinery and activate on the
|
|
112
|
+
next server restart after update, everywhere, with no flags — they fix guards that were
|
|
113
|
+
misfiring against instar's own operations; there is no "on" to gate.
|
|
114
|
+
|
|
115
|
+
## Rollback
|
|
116
|
+
|
|
117
|
+
Revert the commit. Both changes are additive opts on existing methods; removing them restores
|
|
118
|
+
the prior behavior exactly (F8: closeout vetoed on standby machines again; F7: bootstrap
|
|
119
|
+
flagged again). No state migration in either direction.
|
|
120
|
+
|
|
121
|
+
## Tests
|
|
122
|
+
|
|
123
|
+
- `tests/unit/session-manager-terminate.test.ts` (+6) — both sides of the F8 authority
|
|
124
|
+
boundary: no-flag standby veto preserved; flagged closeout lands; protected still refuses;
|
|
125
|
+
recent-user-message + active-subagent KEEP-guards still veto; awake-machine no-op.
|
|
126
|
+
- `tests/unit/session-reaper-topic-moved.test.ts` (+2) — the closeout terminate carries the
|
|
127
|
+
flag (legacy path, with Part E + workEvidence semantics pinned unchanged); regression shape
|
|
128
|
+
of the audit (lease-vetoing authority → first attempt lands, no breaker, no escalation).
|
|
129
|
+
- `tests/unit/session-reaper-closeout-liveness.test.ts` (+2) — gated path carries the flag;
|
|
130
|
+
lease bypass carried even when Part E is correctly withheld.
|
|
131
|
+
- `tests/unit/session-reaper-wiring.test.ts` (+2) — server.ts dead-dep thread-through; the
|
|
132
|
+
flag is minted ONLY inside `attemptCloseoutTerminate` (source-scope pin).
|
|
133
|
+
- `tests/integration/session-lifecycle-reap-wiring.test.ts` (+4) — F8 through the real
|
|
134
|
+
wiring: closeout lands + reap-log honest reason; protected + relay-lease unweakened;
|
|
135
|
+
no-flag veto preserved.
|
|
136
|
+
- `tests/unit/session-manager-first-party-inject.test.ts` (new, 4) — both sides of the F7
|
|
137
|
+
boundary: own bootstrap passes unflagged (no layer runs, audited); content-only forged
|
|
138
|
+
claim still flagged by Layer 1.5; byte-identical template copy still flagged by Layer 2;
|
|
139
|
+
wiring pin on the three tag sites.
|
|
140
|
+
- `npx tsc --noEmit` clean; full unit suite green (Zero-Failure Standard) — see PR.
|
|
141
|
+
|
|
142
|
+
## Agent awareness
|
|
143
|
+
|
|
144
|
+
No CLAUDE.md template change: neither fix adds a capability an agent could invoke — both
|
|
145
|
+
repair internal machinery (the closeout sweeper now succeeds; the bootstrap is no longer
|
|
146
|
+
self-flagged). The existing "Reap-Log" and "Sender-Rejection"-style explain-surfaces already
|
|
147
|
+
cover "why did a session close after a move?" (the reap-log records the honest topic-moved
|
|
148
|
+
reason, unchanged).
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Side-Effects Review — wire the pool-relative skew reference (poolReference) into the U4.1 pin fold
|
|
2
|
+
|
|
3
|
+
**Spec:** docs/specs/u4-1-pin-persistence.md (converged 2026-07-02 + approved) §2C skew gate, composed with docs/specs/multi-machine-replicated-store-foundation.md §3.4 (pool-relative reference) / §10.2-family maxDriftMs clamp / §15 risk-5/risk-6 (maxDriftMs sourcing — BLOCKER-5).
|
|
4
|
+
**Defect:** fb-1d51e996-0a3 (live-reproduced 2026-07-02). PR #1332 shipped `TopicPinFoldView` whose skew gate calls `HybridLogicalClock.receive(hlc, {poolReference})`, but NOTHING supplied the `poolReference` dep. `receive()` deliberately references `max(last.physical, poolReference ?? 0)` and NEVER `now()` (§3.4 — a slow receiver must not quarantine an ahead-but-honest peer), so with the dep unwired a QUIET fold clock's reference froze at its construction seed (server boot). Pins are rare operator events → the pin stream is almost always quiet → ANY honest record authored more than maxDriftMs (default 5min) after boot was falsely quarantined as "skew-ahead", STICKILY (dismissal ≠ re-admission, by design). Pin replication was effectively dead between long-running servers; F6 fired even author-side (the Mini quarantined its OWN fresh PUT after a Laptop bounce).
|
|
5
|
+
**Files:** src/core/TopicPinFoldView.ts, src/commands/server.ts (the single production construction site — grep-verified; routes.ts/AgentServer.ts references are type-only).
|
|
6
|
+
|
|
7
|
+
## What changed
|
|
8
|
+
|
|
9
|
+
1. **`poolReferenceFromCapacities()` (new export, TopicPinFoldView.ts):** the production sourcing helper — `max(nowMs, freshest clock-OK peer heartbeat self-stamp)`. A peer whose `clockSkewStatus !== 'ok'` (the registry's skew FSM already distrusts it) NEVER raises the floor; malformed/absent stamps are ignored; no peers → now alone (single-machine degenerate case: the pool is self).
|
|
10
|
+
2. **`TopicPinFoldView.refresh()`:** computes ONE reference per refresh — `max(now(), poolReference dep ?? 0)` — and passes it on EVERY `receive()`. The fold now floors the reference at its own `now()` regardless of wiring, so a future construction site that forgets the dep can never re-freeze the gate (Structure > Willpower: the component is correct by construction). A THROWING/non-finite dep degrades to the now() floor — never back to the frozen reference, never a fold fault (the fold's "never throws" contract holds).
|
|
11
|
+
3. **`server.ts` wiring:** the fold-view construction supplies `poolReference: () => poolReferenceFromCapacities(Date.now(), machinePoolRegistry?.getCapacities() ?? [])` (late-bound closure — the registry is assigned later in boot and read at fold time; registry fault → `Date.now()`).
|
|
12
|
+
4. **`status().skewReference` (additive):** the LIVE gate floor is exposed on `GET /pool/pin-quarantine`'s fold block, so a frozen reference is diagnosable from the read surface instead of only in a quarantine log line after the damage.
|
|
13
|
+
|
|
14
|
+
## Why now() as a FLOOR is spec-faithful (§3.4)
|
|
15
|
+
|
|
16
|
+
§3.4 forbids the BARE local `now()` AS the reference — its concern is FALSE REJECTION when the receiver's own NTP lags. `now()` inside the max can only RAISE the reference (never lower it), so it can never cause a false rejection; the pool-relative arm (peer heartbeat stamps) is what protects the slow-local-clock case, exactly as §3.4 prescribes ("its own last durable HLC, plus — when available — the observed pool physical time carried in the capacity heartbeat"). The spec's named heartbeat-median primitive does not exist yet (§15 risk-6 tracks derived skew measurement); the freshest clock-OK self-stamp is the available observed-pool signal, and the registry's categorical skew FSM (its real §3.4 purpose) keeps a suspect clock from widening the acceptance window.
|
|
17
|
+
|
|
18
|
+
## Blast radius
|
|
19
|
+
|
|
20
|
+
- **The sticky quarantine is NOT weakened.** The quarantine store's `(key, hlc)` exclusion still runs BEFORE the gate on every fold; ack ≠ re-admission; only supersession by a newer honest record (`pruneSuperseded`) or the explicit `POST /pool/pin-quarantine/readmit` clears an entry. Existing FALSE-POSITIVE entries in the field clear exactly that way — no auto-reclassification of quarantined entries is added (stickiness protects against real poison, and a bulk auto-clear could re-admit a genuinely poisoned record).
|
|
21
|
+
- **The real protection is intact:** a record more than maxDriftMs ahead of the MOVING pool reference is still rejected + stickily quarantined + escalated (P17-deduped). Proven by the moving-reference rejection test.
|
|
22
|
+
- **Gate posture unchanged:** the fold view remains read-only over journal bytes and actuates nothing; ws13 flags/dev-gates untouched; no new config key (the reference sourcing is derived state, not tunable — maxDriftMs remains the existing clamped knob).
|
|
23
|
+
- **`skewReference` is additive observability** on an existing Bearer-gated read; no consumer asserts the exact status shape (grep-verified).
|
|
24
|
+
|
|
25
|
+
## Risk + mitigation
|
|
26
|
+
|
|
27
|
+
- **Risk:** a fast-clocked PEER's heartbeat self-stamp raises the floor and admits a near-poison record. **Mitigation:** stamps from peers the registry's skew FSM distrusts (`divergence-detected-once` / `suspect-clock-removed`) never participate; a tolerated stamp is within the registry's clockSkewToleranceMs of router receive time, so the widening is bounded by an already-accepted tolerance. Proven by the suspect-clock helper test.
|
|
28
|
+
- **Risk:** the dep faults mid-fold and kills pin resolution. **Mitigation:** try/catch at BOTH layers (wiring closure and refresh()) degrading to the moving now() floor; proven by the faulty-dep test.
|
|
29
|
+
- **Risk:** the fix silently un-quarantines existing sticky entries. **Mitigation:** none needed — the sticky set is consulted before the gate; the fix changes only NEW verdicts. Existing false-positive entries clear via supersession on the next accepted PUT/tombstone for that key, or explicit readmit.
|
|
30
|
+
|
|
31
|
+
## Migration parity
|
|
32
|
+
|
|
33
|
+
None required — pure code fix. No config defaults added or changed (no `migrateConfig`), no CLAUDE.md template change (no new operator-facing capability — `skewReference` is a diagnostic field on an existing documented surface), no hook/skill/template change. Verified against the Migration Parity Standard checklist: no agent-installed file is touched.
|
|
34
|
+
|
|
35
|
+
## Rollback
|
|
36
|
+
|
|
37
|
+
Revert the commit. The dep is optional and the fold's now() floor is internal — reverting restores the (broken) frozen-reference behavior with no state migration in either direction; quarantine files remain valid either way.
|
|
38
|
+
|
|
39
|
+
## Tests
|
|
40
|
+
|
|
41
|
+
- `tests/unit/u41-pin-persistence.test.ts` — new describe `pool-relative skew reference — quiet streams accept honest records (fb-1d51e996-0a3)`: (1) honest record 46min after the fold-clock seed on a QUIET stream → ACCEPTED (the live Laptop-quarantines-Mini evidence; FAILS without the fix — verified by stash-revert run: 9/9 new fail, 25/25 pre-existing pass); (2) author-side own-PUT acceptance after a quiet period (the Mini-quarantines-its-own-PUT arm); (3) genuinely future-skewed record vs the MOVING reference → still REJECTED + stickily quarantined + escalated; (4) the wired dep raises the floor when the LOCAL clock lags (§3.4 pool-relative); (5) faulty dep degrades to the now() floor; (6) `status().skewReference` moves with time; (7) `poolReferenceFromCapacities` matrix (degenerate/raise/suspect-clock/malformed); (8) wiring-integrity source-grep: the server.ts construction supplies the dep from the registry helper.
|
|
42
|
+
- `tests/integration/u41-pin-persistence-routes.test.ts` — the quarantine read's fold block exposes a finite `skewReference` within 60s of the live wall clock (not frozen at a seed).
|
|
43
|
+
- Full u41 family green with the fix: 52/52 across unit + integration (routes, answer-complete) + e2e (alive/lease-handover). tsc clean; `npm run lint` clean.
|
|
44
|
+
|
|
45
|
+
## Agent awareness
|
|
46
|
+
|
|
47
|
+
No CLAUDE.md template change — this repairs the documented U4.1 pin-persistence behavior (pins replicate between machines as already described); it adds no new capability or trigger an agent must know about. <!-- tracked: fb-1d51e996-0a3 -->
|