instar 0.28.90 → 0.28.92
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/core/SessionManager.d.ts +28 -8
- package/dist/core/SessionManager.d.ts.map +1 -1
- package/dist/core/SessionManager.js +88 -28
- package/dist/core/SessionManager.js.map +1 -1
- package/dist/memory/MemoryIndex.d.ts.map +1 -1
- package/dist/memory/MemoryIndex.js +22 -14
- package/dist/memory/MemoryIndex.js.map +1 -1
- package/dist/memory/NativeModuleHealer.d.ts +114 -0
- package/dist/memory/NativeModuleHealer.d.ts.map +1 -0
- package/dist/memory/NativeModuleHealer.js +282 -0
- package/dist/memory/NativeModuleHealer.js.map +1 -0
- package/dist/memory/SemanticMemory.d.ts.map +1 -1
- package/dist/memory/SemanticMemory.js +19 -8
- package/dist/memory/SemanticMemory.js.map +1 -1
- package/dist/memory/TopicMemory.d.ts.map +1 -1
- package/dist/memory/TopicMemory.js +10 -2
- package/dist/memory/TopicMemory.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +4 -4
- package/upgrades/0.28.91.md +37 -0
- package/upgrades/0.28.92.md +107 -0
- package/upgrades/side-effects/0.28.91.md +11 -0
- package/upgrades/side-effects/multishot-stuck-input-recovery.md +151 -0
- package/upgrades/side-effects/prop-399-native-module-healer.md +129 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# Side-Effects Review — Multi-Shot Stuck-Input Recovery
|
|
2
|
+
|
|
3
|
+
## Change Summary
|
|
4
|
+
|
|
5
|
+
`SessionManager.verifyInjection` upgrades from single-shot to multi-shot
|
|
6
|
+
polling with escalating recovery actions. When the recovery Enter is itself
|
|
7
|
+
eaten by the same paste-end race that caused the original stuck state, the
|
|
8
|
+
loop keeps trying — with different key combinations — until the marker
|
|
9
|
+
clears the `❯` prompt or the bounded schedule (4 attempts over 6.5s)
|
|
10
|
+
exhausts.
|
|
11
|
+
|
|
12
|
+
Three surface changes in `src/core/SessionManager.ts`:
|
|
13
|
+
|
|
14
|
+
1. `verifyInjection` now polls at 500ms, 1500ms, 3500ms, 6500ms from
|
|
15
|
+
injection. Each tick checks pane state; stops as soon as the marker
|
|
16
|
+
clears the prompt. Single Degradation entry on the first recovery firing.
|
|
17
|
+
2. `isMarkerStuckAtPrompt(pane, marker)` extracted as a named method so the
|
|
18
|
+
polling loop and tests share one detection rule.
|
|
19
|
+
3. `fireStuckInputRecovery(tmuxSession, attempt)` escalates the recovery
|
|
20
|
+
action: attempts 0 and 1 send `Enter`; attempt 2 sends `C-m` (literal
|
|
21
|
+
carriage return — bypasses any Enter-specific consumer); attempt 3 sends
|
|
22
|
+
`Enter` + 150ms sleep + `Enter` (covers sub-second race windows).
|
|
23
|
+
|
|
24
|
+
## Failure Mode Being Fixed
|
|
25
|
+
|
|
26
|
+
Reproduced live in `/Users/justin/.instar/agents/echo/logs/server.log`:
|
|
27
|
+
|
|
28
|
+
```
|
|
29
|
+
01:31:49 [LOG] Injected initial message into "echo-telegram-messages-pausing-in-input" (297 chars)
|
|
30
|
+
01:33:49 [WARN] Injection stuck — marker "[telegram:7195] Your session j…" still at prompt. Resending Enter.
|
|
31
|
+
01:33:49 [DEGRADATION] SessionManager.verifyInjection: Enter eaten by paste-end race
|
|
32
|
+
01:34:15 [WARN] Injection stuck — marker "[telegram:7195] Your session j…" still at prompt. Resending Enter.
|
|
33
|
+
01:34:15 [DEGRADATION] SessionManager.verifyInjection: Enter eaten by paste-end race
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Two observations:
|
|
37
|
+
|
|
38
|
+
- The async setTimeout(1500ms) fired 120 seconds after injection — the Node
|
|
39
|
+
event loop was blocked, likely by initial-message-driven session startup
|
|
40
|
+
work. Single-shot timing is unreliable under load.
|
|
41
|
+
- The recovery Enter itself failed (the second `Injection stuck` log line
|
|
42
|
+
for the same marker proves the first recovery did not unstick the input).
|
|
43
|
+
|
|
44
|
+
The single-shot design assumed both: (a) the timer fires near 1.5s and (b)
|
|
45
|
+
the recovery Enter always lands. Both assumptions break in practice.
|
|
46
|
+
|
|
47
|
+
## Over-Block Risk
|
|
48
|
+
|
|
49
|
+
**Polling more times = more chances to send a spurious Enter.** The
|
|
50
|
+
detection gate is unchanged from the single-shot version: marker (first
|
|
51
|
+
40 chars, stripped of leading whitespace, ≥8 chars) must appear on or
|
|
52
|
+
immediately after a line containing `❯`. Claude Code only renders `❯` on
|
|
53
|
+
the active input prompt row — not on transcript history — so the marker
|
|
54
|
+
cannot match a historic echo of the message. Each polling tick re-runs the
|
|
55
|
+
same gate; if the message has submitted, the gate refuses and recovery
|
|
56
|
+
does not fire.
|
|
57
|
+
|
|
58
|
+
Worst-case false-positive: marker is genuinely visible at `❯` because the
|
|
59
|
+
user is still typing the same prefix the agent just injected. Extremely
|
|
60
|
+
implausible (the marker is the message's first 40 chars, not a common
|
|
61
|
+
prefix). Impact if it happened: one or more extra Enters in the input
|
|
62
|
+
box. The user would see their typing submitted prematurely. Acceptable
|
|
63
|
+
trade for fixing the stuck-message bug.
|
|
64
|
+
|
|
65
|
+
The escalation to `C-m` and double-Enter applies only on attempts 2 and
|
|
66
|
+
3 — after two consecutive ticks showed the marker still stuck at `❯`.
|
|
67
|
+
That's an additional safety margin against accidental escalation.
|
|
68
|
+
|
|
69
|
+
## Under-Block Risk
|
|
70
|
+
|
|
71
|
+
If the polling schedule's last tick (6500ms) still shows the marker stuck
|
|
72
|
+
despite four recovery attempts, the loop exits without further action.
|
|
73
|
+
The existing recovery layers below still apply:
|
|
74
|
+
|
|
75
|
+
- `StallTriageNurse` fast-path nudges stuck inputs when its periodic
|
|
76
|
+
diagnosis pass runs.
|
|
77
|
+
- The `pasteRetried` mechanism handles the `[Pasted text #N]` placeholder
|
|
78
|
+
case.
|
|
79
|
+
- The idle-prompt zombie reaper eventually kills sessions with no output
|
|
80
|
+
changes past the configured threshold.
|
|
81
|
+
|
|
82
|
+
A session that survives all four ticks without recovery is almost certainly
|
|
83
|
+
not suffering the paste-end race — something else is wrong (tmux frozen,
|
|
84
|
+
terminal in a weird state, etc.) and continued Enter-hammering would not
|
|
85
|
+
help.
|
|
86
|
+
|
|
87
|
+
## Level-of-Abstraction Fit
|
|
88
|
+
|
|
89
|
+
Same as the single-shot version: `verifyInjection`,
|
|
90
|
+
`isMarkerStuckAtPrompt`, and `fireStuckInputRecovery` are all private
|
|
91
|
+
methods on `SessionManager` and operate at the tmux-send-keys abstraction
|
|
92
|
+
layer. No new public API. No upward leakage of tmux details. Helpers are
|
|
93
|
+
extracted only because polling and detection are now distinct concerns
|
|
94
|
+
that benefit from named test boundaries.
|
|
95
|
+
|
|
96
|
+
## Signal vs Authority Compliance
|
|
97
|
+
|
|
98
|
+
`verifyInjection` is a verification (signal-emitting) layer. It detects a
|
|
99
|
+
specific structural failure (`marker visible at ❯ after Enter was sent`)
|
|
100
|
+
and takes one bounded structural action per detection (one Enter / C-m /
|
|
101
|
+
Enter+sleep+Enter). It does not replace any higher gate; it is the only
|
|
102
|
+
component at this layer with both the context (the marker text and the
|
|
103
|
+
injection timing) and the authority (it owns the tmux session interaction)
|
|
104
|
+
to perform the recovery. There is no higher gate to defer to.
|
|
105
|
+
|
|
106
|
+
The escalation across attempts is a structural escalation in mitigation
|
|
107
|
+
strategy, not in authority. Each recovery attempt remains "send one of N
|
|
108
|
+
recovery key sequences to tmux" — same authority bound throughout.
|
|
109
|
+
|
|
110
|
+
## Interactions With Existing Recovery Paths
|
|
111
|
+
|
|
112
|
+
| Layer | Trigger | Scope | Conflicts with multi-shot? |
|
|
113
|
+
|-------|---------|-------|----------------------------|
|
|
114
|
+
| `verifyInjection` polling (new) | After every `rawInject` | per-injection, bounded by 6.5s schedule | n/a — this is the new layer |
|
|
115
|
+
| `pasteRetried` | `[Pasted text #N]` visible at idle prompt | per-session lifetime | independent — different match string, fires from idle detector |
|
|
116
|
+
| `StallTriageNurse` fast-path | text at ❯ with no glyphs, before LLM | per-triage-pass | independent — runs from triage scheduler. Could fire AFTER verifyInjection's 6.5s window if the session is still stuck. Sending one more Enter at that point is harmless. |
|
|
117
|
+
| Zombie-kill timer | `idleMs > idlePromptKillMinutes` | global | unchanged — only fires if all prior layers missed and session sat for the configured minutes |
|
|
118
|
+
|
|
119
|
+
The four layers gate from fast-and-proactive (per-injection polling) to
|
|
120
|
+
slow-and-destructive (zombie kill). Adding more ticks to the proactive
|
|
121
|
+
layer is monotonically safer — it shifts more stuck-state recovery to the
|
|
122
|
+
fast path rather than waiting for the LLM-backed triage.
|
|
123
|
+
|
|
124
|
+
## Rollback Cost
|
|
125
|
+
|
|
126
|
+
- Revert: restore the single `setTimeout` body from the prior version of
|
|
127
|
+
`verifyInjection`; delete `isMarkerStuckAtPrompt` and
|
|
128
|
+
`fireStuckInputRecovery`. Pure additive — no state, no schema, no
|
|
129
|
+
migration.
|
|
130
|
+
- Tests added: `tests/unit/session-multishot-recovery.test.ts` (12 tests).
|
|
131
|
+
Existing structural tests (`tests/unit/session-injection-verify.test.ts`)
|
|
132
|
+
were updated to assert the bounded-multi-shot invariants — 10 tests pass.
|
|
133
|
+
- Branch surface area: 1 source file modified, 1 test file modified, 1 test
|
|
134
|
+
file added.
|
|
135
|
+
|
|
136
|
+
## Evidence
|
|
137
|
+
|
|
138
|
+
- Live failure: `/Users/justin/.instar/agents/echo/logs/server.log`
|
|
139
|
+
01:31–01:34 — two stuck-then-eaten-recovery events on the same injection
|
|
140
|
+
marker.
|
|
141
|
+
- `tests/unit/session-multishot-recovery.test.ts` — 12 tests covering
|
|
142
|
+
multi-shot polling, escalation across attempts, bounded recovery count,
|
|
143
|
+
early-stop on submission detected, no-op for clean injections, no-op for
|
|
144
|
+
short markers, halt on tmux session disappearance, and detection-
|
|
145
|
+
heuristic edge cases.
|
|
146
|
+
- `tests/unit/session-injection-verify.test.ts` — 10 structural tests
|
|
147
|
+
asserting the new design contract (bounded retries, monotonic backoff
|
|
148
|
+
schedule, recovery-method escalation, early-stop predicate).
|
|
149
|
+
- No regression: `SessionManager-injection.test.ts`,
|
|
150
|
+
`paste-stuck-detection.test.ts`, `session-telegram-inject.test.ts`,
|
|
151
|
+
`stall-triage-typed-not-submitted.test.ts` all pass (25 tests).
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# Side-Effects Review — In-Line Native-Module Self-Heal (PROP-399)
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `prop-399-native-module-healer`
|
|
4
|
+
**Date:** `2026-05-11`
|
|
5
|
+
**Author:** `echo` (rebased from Dawn's original branch authored 2026-05-10)
|
|
6
|
+
**Second-pass reviewer:** `required — wraps every SQLite open() path; touches three memory subsystems on hot init paths`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
Closes the chronic NODE_MODULE_VERSION mismatch failure mode (1254 field reports, cluster `cluster-degradation-semanticmemory-semanticmemory-init-failed-the-m`). When better-sqlite3 is compiled against one Node major and the running Node has a newer major (Homebrew bumps, asdf shims pointing at a moved binary, etc.), every SQLite-backed memory subsystem throws on first open and falls back to a degraded path. Today the only fix is for the user to run `npm rebuild better-sqlite3` by hand.
|
|
11
|
+
|
|
12
|
+
This adds a shared `NativeModuleHealer` helper (`src/memory/NativeModuleHealer.ts`) that wraps the better-sqlite3 import in each `open()` path. On `NODE_MODULE_VERSION` error during `await import('better-sqlite3')`:
|
|
13
|
+
|
|
14
|
+
1. Locate the install prefix via `require.resolve('better-sqlite3')`.
|
|
15
|
+
2. Locate `npm` on PATH.
|
|
16
|
+
3. Run `npm rebuild better-sqlite3 --prefix <prefix>` synchronously (~30s).
|
|
17
|
+
4. Clear better-sqlite3 from `require.cache` so the fresh binding loads on retry.
|
|
18
|
+
5. Retry the import + construct exactly once. On second failure, surface the post-rebuild error directly.
|
|
19
|
+
|
|
20
|
+
Heal attempts are persisted as JSONL at `<stateDir>/native-module-heals.jsonl` for observability and downstream consumption by `DegradationReporter` / health probes.
|
|
21
|
+
|
|
22
|
+
Wired into:
|
|
23
|
+
- `src/memory/SemanticMemory.ts::open()` — wraps the import; main's corruption-recovery branch is unchanged and runs after the healer returns the constructor.
|
|
24
|
+
- `src/memory/TopicMemory.ts::open()` — wraps the import + construct.
|
|
25
|
+
- `src/memory/MemoryIndex.ts::open()` — wraps the import + construct.
|
|
26
|
+
|
|
27
|
+
**Once-per-process guard.** The rebuild is expensive and shouldn't loop. The healer enforces single-attempt semantics: a second open() in the same process that hits the same error gets the original error with `(heal previously attempted)` appended, no rebuild re-run.
|
|
28
|
+
|
|
29
|
+
**This is the same change as the original PROP-399 commit (`e080ec64`, authored by Dawn on `fix/prop-399-native-module-healer`).** This rebase brings it forward onto current main (97 commits behind, two conflict files). No behavior was added or removed during the rebase; the conflicts were both about coexistence with features that landed in parallel:
|
|
30
|
+
|
|
31
|
+
- **`site/src/content/docs/reference/configuration.md`** — PROP-399 added a `defaultMaxDurationMinutes` doc entry that main subsequently added on its own (PR #125). The PROP-399 docs change was dropped during rebase because it was already applied — net diff is zero.
|
|
32
|
+
- **`src/memory/SemanticMemory.ts`** — main added corruption auto-detection + JSONL-rebuild + quarantine handling to `open()` after PROP-399 forked. PROP-399 replaced the same `open()` body with the healer wrap. Resolved by extracting the better-sqlite3 constructor inside `openWithHeal('SemanticMemory', …)`, then running main's corruption-recovery and pragma setup on the constructed db unchanged. Both features now compose: ABI-mismatch heals first (import-time), corruption-quarantine + JSONL-rebuild heals second (post-open).
|
|
33
|
+
|
|
34
|
+
## Decision-point inventory
|
|
35
|
+
|
|
36
|
+
- `src/memory/SemanticMemory.ts::open()` (line 147) — **modify** — replaces the inline `await import('better-sqlite3')` block with `NativeModuleHealer.openWithHeal('SemanticMemory', …)` that returns the constructor. The rest of `open()` (corruption recovery, integrity probe, JSONL rebuild) is unchanged.
|
|
37
|
+
- `src/memory/TopicMemory.ts::open()` (line 149) — **modify** — wraps import + construct in `openWithHeal('TopicMemory', …)`. No corruption-recovery branch in TopicMemory; the wrap is end-to-end for open().
|
|
38
|
+
- `src/memory/MemoryIndex.ts::open()` (line 120) — **modify** — wraps import + construct in `openWithHeal('MemoryIndex', …)`. No corruption-recovery branch.
|
|
39
|
+
- `src/memory/NativeModuleHealer.ts` — **add** — new file. Process-singleton class; exposes `openWithHeal`, `isNodeModuleVersionError`, `healBetterSqlite3`, `getLastResult`, `configure({ stateDir })`. No exports from `index.ts` — memory modules import directly.
|
|
40
|
+
|
|
41
|
+
No new gates, no new filters, no new authorities — `NativeModuleHealer` produces a signal (heal-event JSONL) that the existing `DegradationReporter` consumes; the healer itself never decides whether the user should be alerted. The signal-vs-authority boundary is preserved.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## 1. Over-block
|
|
46
|
+
|
|
47
|
+
**What legitimate inputs does this change reject that it shouldn't?**
|
|
48
|
+
|
|
49
|
+
No block/allow surface — this is a heal-and-retry wrapper. The original error is re-thrown on second failure with full context.
|
|
50
|
+
|
|
51
|
+
The inverse risk exists: could the healer mask a real configuration error?
|
|
52
|
+
|
|
53
|
+
- **better-sqlite3 not installed.** The opener catches the non-`NODE_MODULE_VERSION` `import` failure and re-throws the original user-facing "Run: npm install better-sqlite3" message. `openWithHeal` then checks `isNodeModuleVersionError` and short-circuits — non-mismatch errors propagate immediately, no rebuild is attempted.
|
|
54
|
+
- **`npm` missing on PATH.** Heal returns false, writes a `HealEvent` with `errorTail: 'npm not found on PATH'`, original error is re-thrown wrapped with `(in-line heal failed — see native-module-heals.jsonl)`. No silent swallow.
|
|
55
|
+
- **Rebuild succeeds but Node still incompatible** (e.g., Node major above what better-sqlite3 supports). Retry fails, the new error is surfaced directly — caller sees the post-rebuild failure mode, not the pre-rebuild one. Heal event is logged with `success: true` (rebuild ran) but the retry exception bubbles unchanged.
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## 2. Under-block
|
|
60
|
+
|
|
61
|
+
**What failure modes does this still miss?**
|
|
62
|
+
|
|
63
|
+
- **Other native modules.** Only better-sqlite3 is healed. The same ABI mismatch class hits `sqlite-vec` (vector search), `keytar`, and any future native dep. Out of scope for PROP-399; the helper is generic enough (`openWithHeal<T>`) that future modules can adopt it.
|
|
64
|
+
- **Heal failures during the supervisor preflight path.** `ServerSupervisor.preflightSelfHeal` (PR #111) heals the server-spawn case before the server forks; the in-line healer here covers CLI commands and direct instantiation paths that bypass the supervisor. Both paths log to `native-module-heals.jsonl`, but they don't share state — a heal in the supervisor doesn't suppress a heal in the server. With the once-per-process guard each side caps its own retries; no double-rebuild within a single process is possible.
|
|
65
|
+
- **Heals during a degraded `Node-not-asdf` install.** If `npm` shells out to a Node that's different from `process.execPath`, the rebuild can target the wrong ABI. The healer pins `process.execPath` as the node binary for the spawned `npm` (`spawnSync(process.execPath, [npmPath, 'rebuild', ...])`), which closes this.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## 3. Level-of-abstraction fit
|
|
70
|
+
|
|
71
|
+
**Is this happening at the right layer?**
|
|
72
|
+
|
|
73
|
+
The healer sits at the `open()` boundary inside each memory subsystem — the *first* user-visible interaction with the native module. That is the right layer for an inline heal: it catches both supervised (server-spawned) and direct (CLI command) callers, doesn't require new infrastructure, and surfaces the heal event through the existing degradation-event pipeline.
|
|
74
|
+
|
|
75
|
+
A pure boot-time preflight (already exists in `ServerSupervisor.preflightSelfHeal`) cannot cover the CLI-command and direct-instantiation cases — the field reports show the gap is real.
|
|
76
|
+
|
|
77
|
+
A higher-level remediator (the `Remediator` described in `docs/specs/SELF-HEALING-REMEDIATOR-SPEC.md`) would orchestrate this from outside; that spec is the broader plan. Until the orchestrator exists, this in-line healer is the load-bearing piece. When the remediator lands, it consumes `native-module-heals.jsonl` as the "first runbook" without needing to re-implement the rebuild logic.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## 4. Signal-vs-authority compliance
|
|
82
|
+
|
|
83
|
+
**Does this respect the project principle that low-context filters emit signals while only a high-context gate has blocking authority?**
|
|
84
|
+
|
|
85
|
+
Yes. The healer is a signal producer:
|
|
86
|
+
|
|
87
|
+
- It detects a narrow, structurally observable failure (`NODE_MODULE_VERSION` substring in `err.message`).
|
|
88
|
+
- It writes a structured event (`HealEvent`) to JSONL.
|
|
89
|
+
- It surfaces the rebuild attempt through stdout/console logging.
|
|
90
|
+
|
|
91
|
+
The authority decisions — does this become a degradation event? does the user get alerted? — are owned by `DegradationReporter` and its downstream consumers (tone-gate authority, attention channel). The healer never directly emits a degradation or sends a user alert.
|
|
92
|
+
|
|
93
|
+
The `isNodeModuleVersionError` matcher is a regex on free-form Node error text, which is the kind of brittle filter the project principle warns about. It's safe here because:
|
|
94
|
+
|
|
95
|
+
1. The matcher's job is to *decide whether to retry an open*, not to authorize a privileged action.
|
|
96
|
+
2. The retry is bounded (once per process).
|
|
97
|
+
3. A false positive (substring match on legitimate non-mismatch error) causes one unnecessary rebuild — slow, not unsafe — and the post-rebuild retry then surfaces the original error to the caller.
|
|
98
|
+
4. A false negative (real mismatch that the matcher misses) is identical to the current behavior: throw and degrade. No regression.
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## 5. Interactions with adjacent systems
|
|
103
|
+
|
|
104
|
+
- **`ServerSupervisor.preflightSelfHeal` (PR #111).** Coexists. Supervisor heals at fork time; this heals at first-open time. Each has its own `healAttempted` guard within its own process. No state shared, no coordination needed. If the supervisor heals first, the spawned server's in-line healer sees a working binding and never trips.
|
|
105
|
+
- **Main's `SemanticMemory.open()` corruption recovery (PRs #ea099453, #28e54f82, #6bfef745, #7c4463ef).** Coexists by ordering. The healer runs first (import-time, ABI mismatch). After it returns a working constructor, main's corruption-detection / quarantine / JSONL-rebuild runs unchanged on the constructed db. Verified by the 12 corruption-recovery tests + 12 NativeModuleHealer tests all passing together (`tests/unit/semantic-memory-corruption-recovery.test.ts` + `tests/unit/NativeModuleHealer.test.ts`, 24 tests pass clean).
|
|
106
|
+
- **Main's WikiClaim Phase 1/5 work on `SemanticMemory.ts` (PRs #137, #141).** No interaction. Those changes touched the entity/evidence schema and query paths; this change touches `open()`. The added import (`EvidenceRenderer`) is preserved.
|
|
107
|
+
- **`DegradationReporter`.** Consumes `native-module-heals.jsonl` as part of its event sources (existing wiring on Dawn's original branch). The reporter remains authority-of-record for whether a heal-failure becomes an attention-channel alert.
|
|
108
|
+
- **Messaging Layer 3 `DeliveryFailureSentinel` (PR #103).** No interaction. Different failure class (network/delivery), different subsystem.
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## 6. Rollback cost
|
|
113
|
+
|
|
114
|
+
- **Forward.** Merge to main, cut a release. Agents on the new version are auto-healed on next session. Agents on older versions degrade exactly as they do today — no change.
|
|
115
|
+
- **Backward.** Revert is a single-commit rollback (one feature commit). No DB migrations, no on-disk schema changes. The `native-module-heals.jsonl` log file is append-only and is safe to leave behind on rollback; the next start without the healer simply doesn't write to it.
|
|
116
|
+
- **Skew safety.** Two agents on different versions in the same repo do not share state — heals are per-agent, per-process. A mid-release-window mix is harmless.
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## 7. Convergence-history reference
|
|
121
|
+
|
|
122
|
+
The broader design this PR slots into went through 5 rounds of convergent review in April:
|
|
123
|
+
|
|
124
|
+
- 4 rounds of Claude-family reviewers in parallel (security / scalability / adversarial / integration). Internal convergence at iter 4.
|
|
125
|
+
- 1 final cross-model round (GPT 5.4, Gemini 3.1 Pro, Grok 4.1 Fast via `/crossreview`). Surfaced 11 additional findings the Claude-family reviewers missed (alert-policy contradiction, wall-clock-vs-monotonic-time gap, blastRadius:"external" contradiction, machine-lock reclaim predicate too strict).
|
|
126
|
+
|
|
127
|
+
96 material findings addressed across all rounds. Spec: `docs/specs/SELF-HEALING-REMEDIATOR-SPEC.md` (status `converged-pending-approval`). Convergence report: `docs/specs/reports/self-healing-remediator-convergence.md`.
|
|
128
|
+
|
|
129
|
+
**This PR ships the narrowest slice of that design** — the one in-line runbook (better-sqlite3 ABI mismatch) — without the surrounding Remediator orchestrator. The orchestrator is the next phase and will be re-spec'd against current reality (incorporating the lifeline-preflight, probe framework, tone-gate, and Layer-3 sentinel pieces that landed in parallel during April–May 2026).
|