instar 1.3.533 → 1.3.534

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 (35) hide show
  1. package/dist/commands/server.d.ts.map +1 -1
  2. package/dist/commands/server.js +98 -2
  3. package/dist/commands/server.js.map +1 -1
  4. package/dist/core/EscalationHintStore.d.ts +60 -0
  5. package/dist/core/EscalationHintStore.d.ts.map +1 -0
  6. package/dist/core/EscalationHintStore.js +163 -0
  7. package/dist/core/EscalationHintStore.js.map +1 -0
  8. package/dist/core/ModelTierEscalation.d.ts +10 -0
  9. package/dist/core/ModelTierEscalation.d.ts.map +1 -1
  10. package/dist/core/ModelTierEscalation.js +4 -0
  11. package/dist/core/ModelTierEscalation.js.map +1 -1
  12. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  13. package/dist/core/PostUpdateMigrator.js +15 -0
  14. package/dist/core/PostUpdateMigrator.js.map +1 -1
  15. package/dist/core/TopicProfileTransferCarrier.d.ts +27 -0
  16. package/dist/core/TopicProfileTransferCarrier.d.ts.map +1 -1
  17. package/dist/core/TopicProfileTransferCarrier.js +24 -1
  18. package/dist/core/TopicProfileTransferCarrier.js.map +1 -1
  19. package/dist/scaffold/templates.d.ts.map +1 -1
  20. package/dist/scaffold/templates.js +1 -0
  21. package/dist/scaffold/templates.js.map +1 -1
  22. package/dist/server/AgentServer.d.ts +10 -0
  23. package/dist/server/AgentServer.d.ts.map +1 -1
  24. package/dist/server/AgentServer.js +26 -0
  25. package/dist/server/AgentServer.js.map +1 -1
  26. package/dist/server/routes.d.ts +6 -0
  27. package/dist/server/routes.d.ts.map +1 -1
  28. package/dist/server/routes.js +47 -1
  29. package/dist/server/routes.js.map +1 -1
  30. package/package.json +1 -1
  31. package/src/data/builtin-manifest.json +64 -64
  32. package/src/scaffold/templates.ts +1 -0
  33. package/upgrades/1.3.534.md +44 -0
  34. package/upgrades/side-effects/ws53-escalation-rides-topic.md +52 -0
  35. package/upgrades/1.3.533.md +0 -25
@@ -0,0 +1,52 @@
1
+ # Side-Effects Review — WS5.3 escalation rides the topic (model-tier escalation follows a moved topic)
2
+
3
+ **Spec:** docs/specs/ws53-escalation-rides-topic.md (converged + approved — operator pre-approval, Justin topic 13481). **Parent:** Cross-Machine Coherence — One Agent, Robust Under Degraded Conditions.
4
+ **Ships DARK** behind `models.tierEscalation.ridesTopic` (default false) under the existing `tierEscalation.enabled`. Single-machine installs are a strict no-op.
5
+ **Files:** src/core/EscalationHintStore.ts (new), src/core/ModelTierEscalation.ts, src/core/TopicProfileTransferCarrier.ts, src/server/routes.ts, src/server/AgentServer.ts, src/commands/server.ts, src/scaffold/templates.ts, src/core/PostUpdateMigrator.ts
6
+
7
+ ## What changed
8
+
9
+ 1. **EscalationHintStore.ts (new):** the ephemeral per-topic escalation-hint carrier — `file` / `peek` (no consume) / `consume` (consume-once) / `clear` / `all`. TTL-bounded (default 6h, mirroring `maxEscalationTtlMs`), atomic tmp+rename writes, corrupt/absent file ⇒ no hints (the safe direction). NOT the durable topic profile — its own file `state/model-tier-escalation/rides-topic-hints.json`.
10
+ 2. **ModelTierEscalation.ts:** new `ridesTopic: boolean` field on `TierEscalationConfig`, `ridesTopic: false` in `DEFAULT_TIER_ESCALATION_CONFIG`, and carried by `normalizeTierEscalationConfig` (the read-side add-missing normalizer every consumer goes through).
11
+ 3. **TopicProfileTransferCarrier.ts:** the hint rides the EXISTING `topic-profile-pull` acquire pull. `TopicProfilePullEntry` gains an optional `escalationHint`; the serve handler gains an optional `escalationHintPeek` (peek, not consume); the carrier gains `onEscalationHintLanded`, fired in `applyLanding` AFTER the mandatory ownership recheck (so a hint never drives a re-admit on a non-owner) and independently of the durable-profile present/absent branch (an escalated topic with no pin still carries).
12
+ 4. **routes.ts (`POST /pool/transfer` source leg):** when WS5.3 is enabled and the moving topic has a LIVE session on this machine on an escalated model id, and the topic is not `escalationOverride:'suppress'`, file the ephemeral hint (`trigger:'transfer'` audit label only). Only a REAL move files (noop/already-there never does). New `RouteContext.escalationHints?`. Response carries `escalationHintFiled:true` when one was filed. Imports `escalatedModelIds` + `normalizeTierEscalationConfig`.
13
+ 5. **AgentServer.ts:** constructs the EscalationHintStore inside the existing model-tier block (cheap, file-backed, inert while dark) so the seams are ALIVE on the production init path; passes it to RouteContext; exposes `getEscalationHintStore()`; resets to null in the existing cascade-isolation catch.
14
+ 6. **server.ts:** the destination re-admit driver `_driveEscalationReadmit` (bound after the AgentServer exists) — resolves the topic's resumed session and calls `ModelSwapService.swap(name,'escalated')` (serialized through the orchestrator's per-topic lock); the `topic-profile-pull` serve handler now passes `escalationHintPeek` (config-gated); the carrier wiring passes `onEscalationHintLanded`; `spawnSessionForTopic` consumes the LOCAL hint after the resumed-session spawn (the same-machine target==self arm). `normalizeTierEscalationConfig` promoted to a top-level static import.
15
+ 7. **templates.ts + PostUpdateMigrator.ts:** an "Escalation rides a moved topic (WS5.3)" bullet EXTENDS the existing Model-Tier Escalation section — `generateClaudeMd` (new agents) + the section-install template AND an idempotent content-sniffed additive-bullet patcher (existing agents that already carry the section).
16
+
17
+ ## Blast radius
18
+
19
+ - **The whole path is config-gated, not wiring-gated.** With `tierEscalation.enabled` false (the fleet default), every seam is a strict no-op: the source files nothing, the serve handler peeks nothing, the driver returns early, the local consume is empty. The EscalationHintStore is always constructed (so the feature can be turned on without a restart-to-rewire), but it does nothing until `ridesTopic` AND `enabled` are both true.
20
+ - **No bypass surface.** The destination re-admit is literally `ModelSwapService.swap(name,'escalated')` → `governor.admitEscalation()`. There is no second admit path. The hint only decides whether to CALL swap('escalated'); the answer always comes from the governor's full cost-guard chain. A refusal leaves the session on its default tier — exactly as a fresh escalation request would be refused.
21
+ - **Ownership-gated actuation.** The cross-machine landing fires the re-admit ONLY after the carrier's existing ownership recheck confirms this machine owns the topic. A hint that lands on a re-transferred topic is skipped (audited), never actuated.
22
+ - **No new HTTP route, no new MeshRpc verb.** The hint rides the EXISTING authenticated `topic-profile-pull` pull and the EXISTING `/pool/transfer` — N-machine-safe, no broadcast, no LAN assumption.
23
+
24
+ ## Risk + mitigation
25
+
26
+ - **Risk:** a stale or forged hint drives a wrong escalation. **Mitigation:** the hint's `trigger` label is audit-only — the destination governor re-evaluates from REAL state, so a hint claiming a trigger the topic isn't under still has to pass every real guard. An expired hint (TTL) is treated as absent. Proven by the stale/forged-hint lens test.
27
+ - **Risk:** a topic bounced machine-to-machine escalate-flaps. **Mitigation:** the destination governor's dwell/hysteresis (in ModelSwapService, keyed on session.id) + lease TTL apply to the resumed session as to any escalation. Proven by the dwell lens test.
28
+ - **Risk:** a suppressed topic is re-escalated on arrival. **Mitigation:** double guard — the source files NO hint for a suppress topic, AND `swap()` re-consults the suppress pin at the destination. Proven by the suppress lens test.
29
+ - **Risk:** a carrier/driver/store error fails the transfer or spawn. **Mitigation:** every WS5.3 path is fire-and-forget and try/catch-guarded; the degrade is ALWAYS toward default tier (the cost-reducing, safe direction), audited where it matters. A transfer/spawn never fails because of WS5.3.
30
+
31
+ ## Migration parity
32
+
33
+ - `ridesTopic: false` added to `DEFAULT_TIER_ESCALATION_CONFIG` AND carried by `normalizeTierEscalationConfig` — existing agents get the field automatically on read (config-read through the normalizer, never a literal default written to disk), so no `migrateConfig` change is needed.
34
+ - The CLAUDE.md bullet ships in `generateClaudeMd` (new agents) + an idempotent content-sniffed `migrateClaudeMd` additive-bullet patcher (existing agents). The section heading is UNCHANGED, so the feature-delivery-completeness `featureSections` entry + Codex/Gemini shadow markers stay green (the WS4.2 emptyState precedent: a sub-bullet into an already-tracked section needs no new entry).
35
+
36
+ ## Dark-gate line-map
37
+
38
+ - UNCHANGED. `ridesTopic` is a new key on `DEFAULT_TIER_ESCALATION_CONFIG` in `ModelTierEscalation.ts` (referenced by `ConfigDefaults.ts` via the const), NOT an inline `enabled:` line in `ConfigDefaults.ts`. The dark-gate attributor reads `ConfigDefaults.ts` only and matches `enabled:` lines, so no line shifted. Verified: `node scripts/lint-dev-agent-dark-gate.js` → clean; `tests/unit/lint-dev-agent-dark-gate.test.ts` → 16/16.
39
+
40
+ ## Rollback
41
+
42
+ - Set `models.tierEscalation.ridesTopic: false` (the default) → strict no-op. To fully revert: remove the EscalationHintStore + the `ridesTopic` field + the route source-capture + the carrier hint-carry + the driver + the local consume + the CLAUDE.md bullet. Dark + additive throughout.
43
+
44
+ ## Tests
45
+
46
+ - `tests/unit/escalation-hint-store.test.ts` (13) — the store lifecycle, the serve-handler peek carry, and THE NAMED SAFETY INVARIANT (free-escalation-bypass + suppress + dwell lenses) driven through ModelSwapService against a synthetic governor.
47
+ - `tests/integration/escalation-rides-topic.test.ts` (4) — the cross-machine carry + re-admit end to end against a mock governor: ALLOW → swapped, REFUSE → default (the bypass invariant), owned-elsewhere → no re-admit, no-hint → no-op.
48
+ - `tests/e2e/escalation-rides-topic-lifecycle.test.ts` (3) — Phase-1 feature-is-alive on the production AgentServer init path: the store is constructed + exposed; dark default is inert; `/pool/transfer` answers the honest dark 503. tsc clean; dark-gate 16/16; no-silent-fallbacks + feature-delivery-completeness + docs-coverage green.
49
+
50
+ ## Agent awareness
51
+
52
+ - An "Escalation rides a moved topic (WS5.3)" bullet extends the Model-Tier Escalation section in both `generateClaudeMd` and `migrateClaudeMd` (with a Codex/Gemini-parity shadow marker already covering the parent section). <!-- tracked: ws53-escalation-rides-topic -->
@@ -1,25 +0,0 @@
1
- # Upgrade Guide — vNEXT
2
-
3
- <!-- assembled-by: assemble-next-md -->
4
- <!-- bump: patch -->
5
-
6
- ## What Changed
7
-
8
- The session reaper's shared guard (`ReapGuard`) had two halves that disagreed about a *stale* open commitment. The KILL decision (`evaluate()`) already treats a commitment as abandoned after 8h of topic silence and lets the idle session be reaped. But the RESUME-eligibility decision (`workEvidence()`) counted **any** open commitment as proof of interrupted work — with no staleness gate. So an idle session was killed (commitment stale ⇒ reap) and immediately revived (commitment exists ⇒ resume-eligible), in an endless loop.
9
-
10
- This patch applies the **same 8h staleness gate** to the resume-eligibility probe that the kill decision already uses, so the two halves agree: a stale commitment neither keeps a session alive nor revives it, and a fresh one keeps the session alive so it never needs reviving. Strictly safer — it can only ever revive *less*, never more. Genuine interrupted work (a live build, an active sub-agent, a pending injection, a running process) is untouched.
11
-
12
- ## What to Tell Your User
13
-
14
- Sessions that finished their work but still had an old, untouched promise on the books were being killed and revived over and over. That loop is fixed. You'll see fewer "🪦 your session was shut down — a restart is queued" notices on topics where nothing was actually unfinished. Promises still get followed up on by the commitment system — that part is unchanged.
15
-
16
- ## Summary of New Capabilities
17
-
18
- No new capability — this is a bugfix. It removes a spurious session kill→revive loop driven by stale (long-untouched) open commitments by aligning the reaper guard's resume-eligibility decision with its existing kill-decision staleness rule.
19
-
20
- ## Evidence
21
-
22
- - Live `logs/reap-log.jsonl` (2026-06-13): **13** age-limit reaps with `midWork=true`, every one carrying solely `workEvidence=[open-commitment]`, across 6 topics — and corresponding repeated `reason=age-limit` respawn entries in the resume queue (several doubled per topic). The loop, captured.
23
- - Root cause confirmed in source: `ReapGuard.evaluate()` gates the open-commitment KEEP on `recentUserMessage(topicId, staleCommitmentWindowMs)`; `ReapGuard.workEvidence()` did not.
24
- - New regression tests in `tests/unit/work-evidence.test.ts`: a stale commitment emits no `open-commitment` evidence (and `isMidWork`→false); a fresh one still does; an explicit `evaluate()`/`workEvidence()` consistency assertion; and the `protectOpenCommitments:false` boundary. All 22 work-evidence + 19 reap-guard unit tests green; 164 related reap/resume tests green; `tsc --noEmit` clean.
25
- - Independent second-pass review (lifecycle change): **Concur** — confirmed the age-limit path uses the patched fallback, no legitimate revival is lost, downstream ResumeQueue / live-enqueue / boot-reconciliation re-enqueue (all `midWork`/`evidenceEligible`-gated) all behave correctly.