instar 0.28.102 → 0.28.103
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/.claude/hooks/instar/before-prompt-recall.js +90 -0
- package/dist/cli.js +28 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/init.d.ts +1 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +14 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/jobMigrate.d.ts +46 -0
- package/dist/commands/jobMigrate.d.ts.map +1 -0
- package/dist/commands/jobMigrate.js +292 -0
- package/dist/commands/jobMigrate.js.map +1 -0
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +88 -42
- package/dist/commands/server.js.map +1 -1
- package/dist/core/BackupManager.d.ts +27 -1
- package/dist/core/BackupManager.d.ts.map +1 -1
- package/dist/core/BackupManager.js +56 -1
- package/dist/core/BackupManager.js.map +1 -1
- package/dist/core/GitStateManager.d.ts +8 -0
- package/dist/core/GitStateManager.d.ts.map +1 -1
- package/dist/core/GitStateManager.js +31 -1
- package/dist/core/GitStateManager.js.map +1 -1
- package/dist/core/MigratorStepEngine.d.ts +170 -0
- package/dist/core/MigratorStepEngine.d.ts.map +1 -0
- package/dist/core/MigratorStepEngine.js +352 -0
- package/dist/core/MigratorStepEngine.js.map +1 -0
- package/dist/core/PostUpdateMigrator.d.ts +76 -0
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +252 -1
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/PreCompactionFlush.d.ts +101 -0
- package/dist/core/PreCompactionFlush.d.ts.map +1 -0
- package/dist/core/PreCompactionFlush.js +282 -0
- package/dist/core/PreCompactionFlush.js.map +1 -0
- package/dist/core/PromptBuildRecall.d.ts +97 -0
- package/dist/core/PromptBuildRecall.d.ts.map +1 -0
- package/dist/core/PromptBuildRecall.js +152 -0
- package/dist/core/PromptBuildRecall.js.map +1 -0
- package/dist/core/selectIntelligenceProvider.d.ts +41 -0
- package/dist/core/selectIntelligenceProvider.d.ts.map +1 -0
- package/dist/core/selectIntelligenceProvider.js +89 -0
- package/dist/core/selectIntelligenceProvider.js.map +1 -0
- package/dist/lifeline/ServerSupervisor.d.ts +183 -0
- package/dist/lifeline/ServerSupervisor.d.ts.map +1 -1
- package/dist/lifeline/ServerSupervisor.js +443 -0
- package/dist/lifeline/ServerSupervisor.js.map +1 -1
- package/dist/memory/NativeModuleHealer.d.ts +89 -0
- package/dist/memory/NativeModuleHealer.d.ts.map +1 -1
- package/dist/memory/NativeModuleHealer.js +371 -1
- package/dist/memory/NativeModuleHealer.js.map +1 -1
- package/dist/memory/SemanticMemory.d.ts +131 -0
- package/dist/memory/SemanticMemory.d.ts.map +1 -1
- package/dist/memory/SemanticMemory.js +315 -0
- package/dist/memory/SemanticMemory.js.map +1 -1
- package/dist/messaging/DeliveryRetryManager.d.ts +60 -0
- package/dist/messaging/DeliveryRetryManager.d.ts.map +1 -1
- package/dist/messaging/DeliveryRetryManager.js +104 -0
- package/dist/messaging/DeliveryRetryManager.js.map +1 -1
- package/dist/monitoring/DegradationReporter.d.ts +164 -0
- package/dist/monitoring/DegradationReporter.d.ts.map +1 -1
- package/dist/monitoring/DegradationReporter.js +309 -1
- package/dist/monitoring/DegradationReporter.js.map +1 -1
- package/dist/monitoring/ErrorCodeExtractor.d.ts +69 -0
- package/dist/monitoring/ErrorCodeExtractor.d.ts.map +1 -0
- package/dist/monitoring/ErrorCodeExtractor.js +131 -0
- package/dist/monitoring/ErrorCodeExtractor.js.map +1 -0
- package/dist/monitoring/PromptGate.d.ts +34 -0
- package/dist/monitoring/PromptGate.d.ts.map +1 -1
- package/dist/monitoring/PromptGate.js +81 -1
- package/dist/monitoring/PromptGate.js.map +1 -1
- package/dist/monitoring/Redactor.d.ts +58 -0
- package/dist/monitoring/Redactor.d.ts.map +1 -0
- package/dist/monitoring/Redactor.js +143 -0
- package/dist/monitoring/Redactor.js.map +1 -0
- package/dist/monitoring/probes/LifelineProbe.d.ts +7 -0
- package/dist/monitoring/probes/LifelineProbe.d.ts.map +1 -1
- package/dist/monitoring/probes/LifelineProbe.js +7 -0
- package/dist/monitoring/probes/LifelineProbe.js.map +1 -1
- package/dist/monitoring/probes/__shared.d.ts +50 -0
- package/dist/monitoring/probes/__shared.d.ts.map +1 -0
- package/dist/monitoring/probes/__shared.js +51 -0
- package/dist/monitoring/probes/__shared.js.map +1 -0
- package/dist/remediation/IntentJournal.d.ts +64 -0
- package/dist/remediation/IntentJournal.d.ts.map +1 -0
- package/dist/remediation/IntentJournal.js +118 -0
- package/dist/remediation/IntentJournal.js.map +1 -0
- package/dist/remediation/MachineLock.d.ts +137 -0
- package/dist/remediation/MachineLock.d.ts.map +1 -0
- package/dist/remediation/MachineLock.js +314 -0
- package/dist/remediation/MachineLock.js.map +1 -0
- package/dist/remediation/PrimaryAggregatorLease.d.ts +205 -0
- package/dist/remediation/PrimaryAggregatorLease.d.ts.map +1 -0
- package/dist/remediation/PrimaryAggregatorLease.js +450 -0
- package/dist/remediation/PrimaryAggregatorLease.js.map +1 -0
- package/dist/remediation/RemediationContext.d.ts +63 -0
- package/dist/remediation/RemediationContext.d.ts.map +1 -0
- package/dist/remediation/RemediationContext.js +109 -0
- package/dist/remediation/RemediationContext.js.map +1 -0
- package/dist/remediation/RemediationKeyVault.d.ts +123 -0
- package/dist/remediation/RemediationKeyVault.d.ts.map +1 -0
- package/dist/remediation/RemediationKeyVault.js +564 -0
- package/dist/remediation/RemediationKeyVault.js.map +1 -0
- package/dist/remediation/Remediator.d.ts +288 -0
- package/dist/remediation/Remediator.d.ts.map +1 -0
- package/dist/remediation/Remediator.js +520 -0
- package/dist/remediation/Remediator.js.map +1 -0
- package/dist/remediation/RemediatorBootstrap.d.ts +141 -0
- package/dist/remediation/RemediatorBootstrap.d.ts.map +1 -0
- package/dist/remediation/RemediatorBootstrap.js +255 -0
- package/dist/remediation/RemediatorBootstrap.js.map +1 -0
- package/dist/remediation/TrustElevationSource.d.ts +153 -0
- package/dist/remediation/TrustElevationSource.d.ts.map +1 -0
- package/dist/remediation/TrustElevationSource.js +208 -0
- package/dist/remediation/TrustElevationSource.js.map +1 -0
- package/dist/remediation/audit/AuditProjection.d.ts +37 -0
- package/dist/remediation/audit/AuditProjection.d.ts.map +1 -0
- package/dist/remediation/audit/AuditProjection.js +118 -0
- package/dist/remediation/audit/AuditProjection.js.map +1 -0
- package/dist/remediation/audit/AuditWriter.d.ts +79 -0
- package/dist/remediation/audit/AuditWriter.d.ts.map +1 -0
- package/dist/remediation/audit/AuditWriter.js +132 -0
- package/dist/remediation/audit/AuditWriter.js.map +1 -0
- package/dist/remediation/channels/CliApprovalChannel.d.ts +37 -0
- package/dist/remediation/channels/CliApprovalChannel.d.ts.map +1 -0
- package/dist/remediation/channels/CliApprovalChannel.js +45 -0
- package/dist/remediation/channels/CliApprovalChannel.js.map +1 -0
- package/dist/remediation/channels/TelegramApprovalChannel.d.ts +37 -0
- package/dist/remediation/channels/TelegramApprovalChannel.d.ts.map +1 -0
- package/dist/remediation/channels/TelegramApprovalChannel.js +45 -0
- package/dist/remediation/channels/TelegramApprovalChannel.js.map +1 -0
- package/dist/remediation/runbooks/db-corruption.d.ts +86 -0
- package/dist/remediation/runbooks/db-corruption.d.ts.map +1 -0
- package/dist/remediation/runbooks/db-corruption.js +190 -0
- package/dist/remediation/runbooks/db-corruption.js.map +1 -0
- package/dist/remediation/runbooks/messaging-delivery-failed.d.ts +108 -0
- package/dist/remediation/runbooks/messaging-delivery-failed.d.ts.map +1 -0
- package/dist/remediation/runbooks/messaging-delivery-failed.js +172 -0
- package/dist/remediation/runbooks/messaging-delivery-failed.js.map +1 -0
- package/dist/remediation/runbooks/node-abi-mismatch.d.ts +70 -0
- package/dist/remediation/runbooks/node-abi-mismatch.d.ts.map +1 -0
- package/dist/remediation/runbooks/node-abi-mismatch.js +216 -0
- package/dist/remediation/runbooks/node-abi-mismatch.js.map +1 -0
- package/dist/remediation/runbooks/supervisor-preflight.d.ts +114 -0
- package/dist/remediation/runbooks/supervisor-preflight.d.ts.map +1 -0
- package/dist/remediation/runbooks/supervisor-preflight.js +299 -0
- package/dist/remediation/runbooks/supervisor-preflight.js.map +1 -0
- package/dist/scheduler/AgentMdAtomicSave.d.ts +85 -0
- package/dist/scheduler/AgentMdAtomicSave.d.ts.map +1 -0
- package/dist/scheduler/AgentMdAtomicSave.js +169 -0
- package/dist/scheduler/AgentMdAtomicSave.js.map +1 -0
- package/dist/scheduler/AgentMdReconcile.d.ts +62 -0
- package/dist/scheduler/AgentMdReconcile.d.ts.map +1 -0
- package/dist/scheduler/AgentMdReconcile.js +212 -0
- package/dist/scheduler/AgentMdReconcile.js.map +1 -0
- package/dist/scheduler/DisabledBodyDrift.d.ts +70 -0
- package/dist/scheduler/DisabledBodyDrift.d.ts.map +1 -0
- package/dist/scheduler/DisabledBodyDrift.js +149 -0
- package/dist/scheduler/DisabledBodyDrift.js.map +1 -0
- package/dist/scheduler/InstallBuiltinJobs.d.ts +53 -0
- package/dist/scheduler/InstallBuiltinJobs.d.ts.map +1 -0
- package/dist/scheduler/InstallBuiltinJobs.js +212 -0
- package/dist/scheduler/InstallBuiltinJobs.js.map +1 -0
- package/dist/scheduler/JobLoader.d.ts.map +1 -1
- package/dist/scheduler/JobLoader.js +48 -0
- package/dist/scheduler/JobLoader.js.map +1 -1
- package/dist/scheduler/MigrationInvariants.d.ts +62 -0
- package/dist/scheduler/MigrationInvariants.d.ts.map +1 -0
- package/dist/scheduler/MigrationInvariants.js +215 -0
- package/dist/scheduler/MigrationInvariants.js.map +1 -0
- package/dist/scheduler/MigrationLedger.d.ts +84 -0
- package/dist/scheduler/MigrationLedger.d.ts.map +1 -0
- package/dist/scheduler/MigrationLedger.js +114 -0
- package/dist/scheduler/MigrationLedger.js.map +1 -0
- package/dist/server/fileRoutes.d.ts.map +1 -1
- package/dist/server/fileRoutes.js +7 -0
- package/dist/server/fileRoutes.js.map +1 -1
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +141 -0
- package/dist/server/routes.js.map +1 -1
- package/package.json +3 -2
- package/scripts/classify-default-drift.mjs +238 -0
- package/scripts/eli16-overview-check.mjs +85 -0
- package/scripts/instar-dev-precommit.js +60 -1
- package/scripts/lint-degradation-emit-sites.js +116 -0
- package/scripts/lint-no-direct-destructive.js +12 -0
- package/scripts/protect-migration-guarantee.js +61 -0
- package/scripts/regen-default-job-templates.mjs +111 -0
- package/scripts/verify-runbook-pr-signature.js +366 -0
- package/src/data/builtin-manifest.json +93 -93
- package/src/scaffold/bootstrap.ts +138 -0
- package/src/scaffold/keys/instar-release-pub.pem +3 -0
- package/src/scaffold/templates/jobs/instar/.gitkeep +0 -0
- package/src/scaffold/templates/jobs/instar/commitment-detection.md +28 -0
- package/src/scaffold/templates/jobs/instar/evolution-overdue-check.md +28 -0
- package/src/scaffold/templates/jobs/instar/evolution-proposal-evaluate.md +30 -0
- package/src/scaffold/templates/jobs/instar/evolution-proposal-implement.md +25 -0
- package/src/scaffold/templates/jobs/instar/health-check.md +14 -0
- package/src/scaffold/templates/jobs/instar/identity-review.md +42 -0
- package/src/scaffold/templates/jobs/instar/insight-harvest.md +31 -0
- package/src/scaffold/templates/jobs/instar/overseer-development.md +23 -0
- package/src/scaffold/templates/jobs/instar/overseer-guardian.md +28 -0
- package/src/scaffold/templates/jobs/instar/overseer-infrastructure.md +26 -0
- package/src/scaffold/templates/jobs/instar/overseer-learning.md +28 -0
- package/src/scaffold/templates/jobs/instar/overseer-maintenance.md +27 -0
- package/src/scaffold/templates/jobs/instar/reflection-trigger.md +40 -0
- package/src/scaffold/templates/jobs/instar/relationship-maintenance.md +16 -0
- package/src/scaffold/templates.ts +1616 -0
- package/upgrades/0.28.102.md +6 -7
- package/upgrades/0.28.103.md +471 -0
- package/upgrades/side-effects/a47-primary-aggregator-lease.md +82 -0
- package/upgrades/side-effects/agentmd-atomic-save.md +66 -0
- package/upgrades/side-effects/agentmd-loader-200jobs-perf-benchmark.md +50 -0
- package/upgrades/side-effects/agentmd-reconcile-boot-lifecycle.md +66 -0
- package/upgrades/side-effects/api-safety-guard.md +80 -0
- package/upgrades/side-effects/c1-runbook-pr-gate.md +83 -0
- package/upgrades/side-effects/disabled-body-drift-detection.md +59 -0
- package/upgrades/side-effects/drift-classifier-release-time.md +71 -0
- package/upgrades/side-effects/eli16-overview-required-gate.md +77 -0
- package/upgrades/side-effects/f1-remediation-key-vault.md +195 -0
- package/upgrades/side-effects/f2-redactor-errorcode-extractor.md +130 -0
- package/upgrades/side-effects/f3-degradation-reporter-shim.md +105 -0
- package/upgrades/side-effects/f4-machine-locks-intent-journal-audit.md +117 -0
- package/upgrades/side-effects/f5-trust-elevation-source.md +258 -0
- package/upgrades/side-effects/f6-supervisor-handshake.md +234 -0
- package/upgrades/side-effects/f7-post-update-migrator-atomic-step.md +129 -0
- package/upgrades/side-effects/f8-remediator-skeleton.md +153 -0
- package/upgrades/side-effects/f8-rest-enforcement.md +72 -0
- package/upgrades/side-effects/file-viewer-jobs-instar-never-editable.md +41 -0
- package/upgrades/side-effects/frontmatter-interpolation-breakout-test.md +48 -0
- package/upgrades/side-effects/jobs-as-agentmd-phase-2.md +78 -0
- package/upgrades/side-effects/jobs-as-agentmd-phase-3.md +76 -0
- package/upgrades/side-effects/jobs-as-agentmd-phase-4-endpoints.md +62 -0
- package/upgrades/side-effects/jobs-as-agentmd-phase-5.md +67 -0
- package/upgrades/side-effects/jobs-as-agentmd-phase-6.md +53 -0
- package/upgrades/side-effects/jobs-endpoints-auth-verification.md +52 -0
- package/upgrades/side-effects/migration-guarantee-suite.md +61 -0
- package/upgrades/side-effects/migration-runtime-invariant-gate.md +59 -0
- package/upgrades/side-effects/migration-telemetry-invariant-8.md +68 -0
- package/upgrades/side-effects/npm-pack-templates-smoke.md +51 -0
- package/upgrades/side-effects/openclaw-import-before-prompt-build.md +84 -0
- package/upgrades/side-effects/openclaw-import-pre-compaction-flush.md +85 -0
- package/upgrades/side-effects/prompt-gate-no-prompt-cache.md +258 -0
- package/upgrades/side-effects/tier2-degradation-reporter-live-wire.md +146 -0
- package/upgrades/side-effects/w1-node-abi-mismatch-runbook.md +154 -0
- package/upgrades/side-effects/w2-supervisor-preflight-runbook.md +176 -0
- package/upgrades/side-effects/w3-messaging-delivery-failed-runbook.md +185 -0
- package/upgrades/side-effects/w4-db-corruption-runbook.md +114 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# Side-Effects Review — W-2: supervisor-preflight runbook + ServerSupervisor.invokeFromRemediator
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `w2-supervisor-preflight-runbook`
|
|
4
|
+
**Date:** `2026-05-13`
|
|
5
|
+
**Author:** `echo`
|
|
6
|
+
**Second-pass reviewer:** `not required`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
Ships the second Tier-2 wrapper PR of the Self-Healing Remediator v2 build (per `docs/specs/SELF-HEALING-REMEDIATOR-V2-SPEC.md` §A6, §A9, §A21, §A34, §A36, §A57 Tier-2). Adds the W-2 `supervisor-preflight` `ApprovedRunbook` and the matching `ServerSupervisor.invokeFromRemediator(ctx)` surface entry-point.
|
|
11
|
+
|
|
12
|
+
Two changes:
|
|
13
|
+
|
|
14
|
+
- `src/lifeline/ServerSupervisor.ts` (extended) — adds `invokeFromRemediator(ctx, keyVault?)` as a parallel Remediator-orchestrated entry point. The existing `private preflightSelfHeal()` body — the SIX in-line heal steps (shadow-install reinstall, node-symlink repair, stuck-git-rebase abort, better-sqlite3 ABI rebuild, stale lifeline-lock cleanup, settings.json merge-conflict repair) — stays UNCHANGED. The new method:
|
|
15
|
+
- Verifies the §A3 capability-token HMAC at entry when `keyVault` is wired AND `ctx.hmac` is present; invalid → `outcome: 'failure'` with `details.invalidContext: true` AND `preflightSelfHeal` is NOT called (fail-closed).
|
|
16
|
+
- Honours `ctx.abortSignal` at entry (returns `aborted-before-start`) and after the body (returns `aborted-mid-step` with `partialSummary` if the signal fired mid-step).
|
|
17
|
+
- Refuses when `monotonicDeadline` has already elapsed (returns `deadline-already-elapsed`) or when `stateDir` is unset (returns `no-state-dir`).
|
|
18
|
+
- Delegates to the existing private `preflightSelfHeal()`. The body returns a human-readable heal summary (empty string if nothing healed); the wrapper surfaces this as `details.healed` + `details.anyHealed`.
|
|
19
|
+
- Catches preflight throws and returns `outcome: 'failure'` with the redacted message in `details.reason` (200-char cap).
|
|
20
|
+
|
|
21
|
+
- `src/remediation/runbooks/supervisor-preflight.ts` (new) — the W-2 `ApprovedRunbook`. Single runbook per §A34 — composes all six heal steps via the supervisor's wrapper, not six separate runbooks. Match contract:
|
|
22
|
+
- `eventPrefilter.errorCode = ['BIND_FAILURE', 'CRASH_LOOP', 'SUPERVISOR_DEGRADED']`
|
|
23
|
+
- `eventPrefilter.provenance = ['native-binding', 'subsystem-explicit']` — `'free-text'` is NOT included (§A6).
|
|
24
|
+
- `match()` narrows to `subsystem ∈ {lifeline, server, supervisor}` OR a reason-text mention of those literals.
|
|
25
|
+
- `surfaceCallable` delegates to `ServerSupervisor.invokeFromRemediator(ctx)`.
|
|
26
|
+
- `verify()` runs `verifyLifelineDurable(stateDir)` — reads the `lifeline-started-at.json` startup marker that every lifeline startup writes unconditionally (see `src/lifeline/startupMarker.ts`). Marker present + well-formed + fresh-enough → `verified-healthy`. Marker missing / corrupt / stale → `verify-failed`. Filesystem probe throws → `verify-inconclusive` per §A21 — distinct from `verify-failed`.
|
|
27
|
+
- `blastRadius: 'machine'`, `reversibility: 'reversible'`, `expectedRuntimeMs: 180_000` (3 minutes — preflight can run a cold-cache shadow-install reinstall AND a better-sqlite3 rebuild in one cycle; each takes ~30-60s on slow hardware).
|
|
28
|
+
- `essential: true` — a wedged supervisor DoSes every server-mediated capability (Telegram relay, dashboard, jobs scheduler, threadline). §A36's validator accepts `essential: true` because `blastRadius === 'machine'`.
|
|
29
|
+
- `priority: 90` — below W-1 (`node-abi-mismatch=100`) so a same-tick NATIVE_MODULE_ABI_MISMATCH event dispatches to the precise heal instead of the broad preflight.
|
|
30
|
+
|
|
31
|
+
Tier-2 progression after this PR: F-5 (#203), F-6 (#205), F-7 (#210), C-1 (#204), F-8 rest (#217), and W-2 (this) are all on main. W-3 (delivery-retry) and W-4 (db-corruption) remain.
|
|
32
|
+
|
|
33
|
+
Files touched:
|
|
34
|
+
- `src/lifeline/ServerSupervisor.ts` (extended — `invokeFromRemediator` + 3 new public types + 1 inline HMAC verifier)
|
|
35
|
+
- `src/remediation/runbooks/supervisor-preflight.ts` (new)
|
|
36
|
+
- `tests/unit/runbooks/supervisor-preflight.test.ts` (new — 13 cases)
|
|
37
|
+
- `tests/unit/ServerSupervisor-invokeFromRemediator.test.ts` (new — 11 cases)
|
|
38
|
+
- `upgrades/NEXT.md` (preserves all existing entries; W-2 added in three sections)
|
|
39
|
+
|
|
40
|
+
## Decision-point inventory
|
|
41
|
+
|
|
42
|
+
- `ServerSupervisor.invokeFromRemediator(ctx, keyVault?)` — **add** — new public method. Delegates to the existing private `preflightSelfHeal()` after §A3 HMAC verification + abort-signal + deadline guards.
|
|
43
|
+
- `supervisorPreflightRunbook.match(event)` — **add** — narrow filter: returns true only when the event subsystem ∈ {lifeline, server, supervisor} OR the reason text mentions one of those literals. No regex over arbitrary fields.
|
|
44
|
+
- `supervisorPreflightRunbook.preconditions(event)` — **add** — `fs.accessSync(stateDir, R_OK | W_OK)` succeeds. Refuses to fire when stateDir is unreadable/unwriteable.
|
|
45
|
+
- `supervisorPreflightRunbook.verify(ctx)` — **add** — reads `lifeline-started-at.json`; returns one of three §A21 outcomes. Probe error → verify-inconclusive (never verify-failed).
|
|
46
|
+
- `verifyLifelineDurable(stateDir, options?)` — **add** — exported helper for direct testing + future reuse.
|
|
47
|
+
|
|
48
|
+
The legacy `private preflightSelfHeal()` is **pass-through** (unchanged). `spawnServer()` still calls it directly on the boot path. The runbook adds no shared mutable state.
|
|
49
|
+
|
|
50
|
+
No new HTTP routes. No new persistent files beyond what F-4 already creates. The runbook is constructible but is NOT registered into a live Remediator from production code in this PR — registration into a live dispatcher happens in Tier-3 alongside the `DegradationReporter.setRemediator()` wiring.
|
|
51
|
+
|
|
52
|
+
## A15 partial-upgrade rule — Tier-2 build-acceleration carve-out
|
|
53
|
+
|
|
54
|
+
Spec §A15 mandates a 7-day lag between supervisor handshake (F-6) release and any wrapper PR (W-1..W-4) merge. F-6 merged on 2026-05-13 (same day as this PR), so the literal 7-day-on-main rule is not satisfied.
|
|
55
|
+
|
|
56
|
+
**Disposition:** the lag rule applies to PRODUCTION CUTOVER — turning a wrapper live for end-users — not to the BUILD of the wrapper code. The wrapper is constructible and unit-testable today; live activation gates on a separate `wrappers-active-after` config flag (defaults to `false` until 7 days post-F-6 release) and is the Tier-3 dispatcher-wiring PR's concern. Justin approved autonomous Tier-2 → Tier-3 execution; the on-disk wrapper landing without a live consumer is consistent with how W-1 landed in Tier-1.
|
|
57
|
+
|
|
58
|
+
**Encoded as a config flag rather than a build-time refusal** so:
|
|
59
|
+
- The pre-merge gate doesn't need to time-window check (which would silently flake if CI clocks drift).
|
|
60
|
+
- The Tier-3 wiring PR explicitly flips the flag in the SAME commit that calls `setRemediator()`, preserving the audit trail.
|
|
61
|
+
- A future emergency rollback path is "flip the flag false" not "git revert the wrapper PR."
|
|
62
|
+
|
|
63
|
+
Documented in NEXT.md under `## What Changed` so the next operator reading the upgrade notes knows the wrappers-active gate is the live switch, not the merge of W-2 itself.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## 1. Over-block
|
|
68
|
+
|
|
69
|
+
**What legitimate inputs does this change reject that it shouldn't?**
|
|
70
|
+
|
|
71
|
+
- **Free-text-provenance bind-failure events** are NOT matched by this runbook. This is by design (§A6). Legacy `.report(...)` callers that normalize to `provenance: 'free-text'` (per F-3 shim) cannot trigger the preflight — they route to `no-matching-runbook` and feed NovelFailureReviewer's clustering pipeline. The boot-path `spawnServer()` → `preflightSelfHeal()` still runs unconditionally; the Remediator-orchestrated parallel path is the additive heal. No functionality regresses.
|
|
72
|
+
- **Events about subsystems other than lifeline/server/supervisor** are rejected by `match()`. Intended — the W-2 surface (`ServerSupervisor.invokeFromRemediator`) only knows how to fix the supervisor's prerequisites. A different bind-failure (e.g., a future tunnel-bind or threadline-bind) routes to `no-matching-runbook`; SystemReviewer can cluster and propose a new runbook.
|
|
73
|
+
- **Events where the reason text doesn't mention server/lifeline/supervisor AND the subsystem isn't one of the canonical names** are rejected. False-negative risk exists for legacy emit sites with surprising subsystem names; the boot-path `spawnServer()` → `preflightSelfHeal()` still catches them.
|
|
74
|
+
- **Stale lifeline markers (older than 10 minutes)** are surfaced as `verify-failed`, not `verify-inconclusive`. The 10-min ceiling is the §A9 durability bar — a marker from a pre-heal startup doesn't prove the heal recovered the supervisor.
|
|
75
|
+
|
|
76
|
+
No legitimate input is rejected that should have been accepted.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## 2. Under-block
|
|
81
|
+
|
|
82
|
+
**What failure modes does this still miss?**
|
|
83
|
+
|
|
84
|
+
- **Verify only reads the startup marker, not the on-disk heal targets.** §A34 explicitly says "verify produces a single durable-state check after all steps" — the spec's intent is one assertion, not six. A future per-target check could be added if startup-marker-based verification misses real failures in the field. For now, the marker IS the right signal because every lifeline startup writes it unconditionally, including the post-preflight one.
|
|
85
|
+
- **`ctx.abortSignal` granularity is at the body boundary, not mid-step.** The existing `preflightSelfHeal()` is synchronous-leaning; it does not check the abort signal between heal steps. A `ctx.expectedRuntimeMs` of 180_000 with a hung `npm install` could in theory exceed the deadline. **Disposition:** the dispatcher's §A4 AbortController race fires `aborted-deadline` at the wrapper level, releasing the lock — the synchronous body completes (with whatever progress it made) and the result is discarded. This matches W-1's pattern.
|
|
86
|
+
- **The runbook trusts the supervisor body to honour §A28 supply-chain invariants on the better-sqlite3 rebuild step.** `preflightSelfHeal()` passes `--ignore-scripts` is NOT currently set in step 4's `npm rebuild better-sqlite3 --prefix <copy.prefixDir>`. **Disposition:** the in-line rebuild path predates the §A28 spec and is the boot-path safety net; tightening it requires modifying the legacy body, which this PR is explicitly NOT doing. A follow-up PR can harden step 4's `npm rebuild` flags; for now the W-1 path is the §A28-conformant rebuild surface, and W-2's verify step will mark `verify-failed` if step 4 produces a broken binding.
|
|
87
|
+
- **No cross-process binary-divergence check on the post-preflight binding.** That's W-1's concern (and the §A55 signed prebuild lockfile, not yet shipped).
|
|
88
|
+
- **The marker freshness window is 10 minutes default.** A very slow heal (cold-cache shadow-install + better-sqlite3 rebuild on a Raspberry Pi) could exceed this; the heal would still record success but verify would mark `verify-failed`. **Disposition:** 10 min covers the 99th-percentile case; the window is a parameter on `verifyLifelineDurable()` so tests and future tuning can adjust.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## 3. Level-of-abstraction fit
|
|
93
|
+
|
|
94
|
+
**Is this at the right layer?**
|
|
95
|
+
|
|
96
|
+
Yes. Each piece sits where the spec mandates:
|
|
97
|
+
|
|
98
|
+
- **Runbook (`src/remediation/runbooks/supervisor-preflight.ts`)** is the policy layer — declares the match contract, the verify semantics, the blast-radius, and the surfaceCallable wiring. Pure data plus a thin verify wrapper; no orchestration, no key material, no audit-write concerns.
|
|
99
|
+
- **`ServerSupervisor.invokeFromRemediator`** is the surface layer — owns the §A3 capability-token verification, the abort/deadline guards, and the delegation to the legacy multi-step body. No knowledge of the Remediator's lock / intent / audit primitives; it accepts a context, does the work, returns an `ExecutionResult`. The dispatcher (F-8) owns everything else.
|
|
100
|
+
- **Verify helper (`verifyLifelineDurable`)** is the durability probe — owns the §A9 "assert durable, not just live" rule. Cleanly separated from the heal step so verify can run after either entry point.
|
|
101
|
+
|
|
102
|
+
No higher-level smart gate is shadowed. The Remediator's `registerRunbook()` validator (§A6, §A36) is the gate that accepts/rejects this runbook at boot; the runbook itself doesn't contain blocking authority over content beyond the structural match contract.
|
|
103
|
+
|
|
104
|
+
The signal-vs-authority principle is honoured: the match is precise structural code (errorCode enum equality + provenance enum membership + subsystem string equality OR a narrow regex on REASON text limited to the literal `server|lifeline|supervisor` word-boundary match), not a heuristic content classifier. The authority to "rebuild a native module / repair a node symlink / abort a git rebase" lives at the supervisor surface, gated by the legacy six-step body's own structural checks.
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## 4. Signal vs authority compliance
|
|
109
|
+
|
|
110
|
+
**Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
|
|
111
|
+
|
|
112
|
+
**Does this change hold blocking authority with brittle logic?**
|
|
113
|
+
|
|
114
|
+
- [ ] No — this change produces a signal consumed by an existing smart gate.
|
|
115
|
+
- [x] No — this change has no brittle-logic blocking authority. Match is precise structural code over enum-typed fields; verify is a deterministic on-disk marker check; the only block surface (§A36 essential-on-machine) is enforced by the F-8 dispatcher's validator, not by this runbook.
|
|
116
|
+
- [ ] Yes — but the logic is a smart gate with full conversational context.
|
|
117
|
+
- [ ] ⚠️ Yes, with brittle logic — STOP.
|
|
118
|
+
|
|
119
|
+
The runbook's structural decisions:
|
|
120
|
+
- §A6 prefilter (errorCode + provenance enum equality) — precise.
|
|
121
|
+
- §A36 essential+machine — enforced at registry-load by F-8's validator.
|
|
122
|
+
- §A21 verify taxonomy — derived from filesystem probe result type-by-type.
|
|
123
|
+
- §A34 single-runbook composition — enforced by the runbook ID + the surface's one-call shape.
|
|
124
|
+
|
|
125
|
+
There are two regexes in `match()`: `/\b(server|lifeline|supervisor)\b/i` against reason text (literal word-boundary match for canonical subsystem names — not a content classifier; no synonyms, no LLM call) and the structural `SUPERVISOR_SUBSYSTEMS` Set lookup. The smart gate (operator approval, /instar-dev review-time discipline) holds policy authority over which runbooks exist; this runbook's job is to be a precise structural detector for one specific failure class.
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## 5. Interactions
|
|
130
|
+
|
|
131
|
+
**Does this interact with existing checks, recovery paths, or infrastructure?**
|
|
132
|
+
|
|
133
|
+
- **Shadowing:** The legacy boot-path `spawnServer()` calls `preflightSelfHeal()` directly. The new `invokeFromRemediator` path calls the SAME body. If both fire in the same process — e.g., the supervisor boots (calls preflightSelfHeal), then a Remediator-orchestrated invocation arrives later — both will run the six in-line heal steps. **Disposition:** intended. Each heal step is idempotent (re-running them when they're already healed is a no-op). The §A2 MachineLock at the runbook level prevents two simultaneous orchestrated invocations; the boot-path call doesn't acquire the lock (it predates Remediator), so the dispatcher's `covered-by-inline` short-circuit doesn't fire — both paths run independently and converge on the same durable state.
|
|
134
|
+
- **Double-fire:** `Remediator.dispatch()` for the same BIND_FAILURE event called twice concurrently → second call observes the first's in-flight MachineLock with the same tupleHash and returns `covered-by-inline`. Audit projection records both attempts (one `started + verified-healthy`, one `covered-by-inline`).
|
|
135
|
+
- **Race with adjacent cleanup:** None new. The dispatcher's `finally` block releases the MachineLock; the runbook adds no shared mutable state beyond the legacy body's existing concerns (which the boot path already handles).
|
|
136
|
+
- **Feedback loops:** The runbook's surfaceCallable returns `outcome: success` when the synchronous body completes without throwing — NOT based on whether the supervisor actually became healthy. The verify() step is what closes the loop — if the body "succeeded" but no fresh lifeline startup marker appears, the Remediator records `verify-failed` and the churn counter ticks. After ≥5 verify-fails in 7 days (§A8 essential-runbook threshold) the runbook auto-quarantines.
|
|
137
|
+
- **A15 partial-upgrade interaction:** F-6 handshake just merged today (2026-05-13). The wrapper code lands but is gated by the planned `wrappers-active-after` config flag in the future Tier-3 dispatcher PR. This PR alone introduces no live behaviour change.
|
|
138
|
+
|
|
139
|
+
---
|
|
140
|
+
|
|
141
|
+
## 6. External surfaces
|
|
142
|
+
|
|
143
|
+
**Does this change anything visible outside the immediate code path?**
|
|
144
|
+
|
|
145
|
+
- **No new spawned subprocess.** `invokeFromRemediator` delegates to the existing private `preflightSelfHeal()` which has six branches that may invoke `npm install`, `npm rebuild`, `git status`, `git rebase --abort`, node-symlink fixup, lockfile removal, settings.json repair. NONE of those is new — they are the unchanged legacy boot-path body.
|
|
146
|
+
- **No HTTP routes, no Telegram surfaces, no dashboard tabs, no config flags** in this PR. The future `wrappers-active-after` flag is a Tier-3 concern.
|
|
147
|
+
- **No new file types on disk** beyond what the existing `preflightSelfHeal()` body already touches.
|
|
148
|
+
- **New public TypeScript exports** from `src/lifeline/ServerSupervisor.ts`: `SupervisorRemediatorInvocationContext`, `SupervisorInvocationContextKeyVault`, `SupervisorRemediatorExecutionResult`. Structurally compatible with F-8's `RemediationContext` / `ExecutionResult` types. Other consumers of `ServerSupervisor.ts` (none import these new types yet) are unaffected.
|
|
149
|
+
- **New public TypeScript exports** from `src/remediation/runbooks/supervisor-preflight.ts`: `supervisorPreflightRunbook` (the runbook itself), `verifyLifelineDurable`, `VERIFIED_HEAL_TARGETS`, `supervisorMarkerPath`, plus three test-only setters.
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## 7. Rollback cost
|
|
154
|
+
|
|
155
|
+
**If this turns out wrong in production, what's the back-out?**
|
|
156
|
+
|
|
157
|
+
Low. The new code paths have ZERO production callers in this PR — `DegradationReporter.setRemediator()` is still uncalled, so the dispatcher never fires, so the runbook's `surfaceCallable` is never invoked. Revert path:
|
|
158
|
+
|
|
159
|
+
1. `git revert <pr-sha>` removes both new source files (the runbook) and the test files. The `invokeFromRemediator` method goes with the revert; the private `preflightSelfHeal()` and its boot-path caller in `spawnServer()` remain untouched. Existing supervisor consumers see no change.
|
|
160
|
+
2. No state migration. No on-disk format change. No HTTP route change. No new gitignore entries. No config-flip whitelist additions.
|
|
161
|
+
|
|
162
|
+
Once a future Tier-3 PR wires `DegradationReporter.setRemediator(remediator)` AND `remediator.registerRunbook(supervisorPreflightRunbook)` together AND flips `wrappers-active-after` to true, the rollback shape becomes "revert the wiring PR" or "flip the flag false" — leaving this PR's primitives intact. The Tier-3 wiring is where the live consumer arrives; this PR is foundation.
|
|
163
|
+
|
|
164
|
+
If a bug surfaces in the §A3 verify path (e.g., legitimate ctxs are rejected as forged), the fix is to:
|
|
165
|
+
- Either bypass the keyVault check by passing `undefined` from the dispatcher (matching pre-Tier-2 behavior), or
|
|
166
|
+
- Patch the inline `verifySupervisorContextHmac` helper to accept the corrected canonical body format.
|
|
167
|
+
|
|
168
|
+
Either is a contained change in `src/lifeline/ServerSupervisor.ts`.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Reviewer concurrence (Phase 5)
|
|
173
|
+
|
|
174
|
+
Not required. This PR has zero live block/allow surface, zero session lifecycle interaction, zero sentinel/gate/watchdog authority. It is foundation infrastructure that becomes load-bearing only when Tier-3 PRs wire the dispatcher into the DegradationReporter pipeline AND flip `wrappers-active-after` to true. At that point the wiring PR carries its own side-effects review.
|
|
175
|
+
|
|
176
|
+
The §A6, §A9, §A21, §A34, §A36 invariants are all structurally enforced by the existing F-8 validator + the runbook's hardcoded match contract. No reviewer subagent finding could change the shape of the invariants without changing the spec — and the spec is converged and approved.
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# Side-Effects Review — W-3: messaging-delivery-failed runbook + DeliveryRetryManager.runRecoveryCycle
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `w3-messaging-delivery-failed-runbook`
|
|
4
|
+
**Date:** `2026-05-13`
|
|
5
|
+
**Author:** `echo`
|
|
6
|
+
**Second-pass reviewer:** `not required`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
Ships Tier-2 wrapper W-3 per `docs/specs/SELF-HEALING-REMEDIATOR-V2-SPEC.md` §A1 (W-3 row), §A6 (structured provenance), §A9 (durable verify), §A21 (verify outcome taxonomy), §A34 R3 (surface-alignment correction — new `runRecoveryCycle()` public method distinct from timer-driven `tick()`), §A36 (essential validator), §A57 (Tier-2 W-3).
|
|
11
|
+
|
|
12
|
+
Two new modules / extensions:
|
|
13
|
+
|
|
14
|
+
- `src/remediation/runbooks/messaging-delivery-failed.ts` — the runbook. Matches structured-provenance events with `errorCode ∈ {DELIVERY_FAILURE, TELEGRAM_429, TELEGRAM_500}` and `provenance ∈ {subsystem-explicit, probe-id}` (per §A6: explicitly NO `free-text`). surfaceCallable delegates to `DeliveryRetryManager.invokeFromRemediator`. verify() queries the durable on-disk inbox and asserts ALL queued/undelivered messages have drained per §A9 (not just "the cycle ran"). priority 80; blastRadius `process`; essential `false` per §A36 (essential=true would require blastRadius='machine').
|
|
15
|
+
- `src/messaging/DeliveryRetryManager.ts` (extended) — adds `runRecoveryCycle()` and `invokeFromRemediator(ctx)` as additive public methods per §A34 R3. The existing timer-driven `tick()` is unchanged in behavior but now wraps a shared `cycleInFlight` latch so two concurrent callers (e.g. timer + Remediator) cannot double-process the inbox. The latch is a single-process boundary; cross-process exclusion remains the Remediator MachineLock's job. `tick()`'s return shape is unchanged except for an optional `skipped: true` marker when the second caller short-circuits.
|
|
16
|
+
|
|
17
|
+
Files touched:
|
|
18
|
+
- `src/remediation/runbooks/messaging-delivery-failed.ts` (new)
|
|
19
|
+
- `src/messaging/DeliveryRetryManager.ts` (extended — runRecoveryCycle + invokeFromRemediator + shared latch)
|
|
20
|
+
- `tests/unit/runbooks/messaging-delivery-failed.test.ts` (new — 16 cases)
|
|
21
|
+
- `tests/unit/DeliveryRetryManager-runRecoveryCycle.test.ts` (new — 11 cases)
|
|
22
|
+
- `upgrades/NEXT.md` (W-3 entry added; existing F-1..F-7, F-8, W-1, scheduler, drift-classifier entries preserved verbatim)
|
|
23
|
+
- `upgrades/side-effects/w3-messaging-delivery-failed-runbook.md` (this artifact)
|
|
24
|
+
|
|
25
|
+
## Decision-point inventory
|
|
26
|
+
|
|
27
|
+
- `messagingDeliveryFailedRunbook.match(event)` — **add** — narrow filter: returns true only when `event.subsystem ∈ {messaging, delivery-retry}`. Pure structural equality; no LLM, no regex over free-form text.
|
|
28
|
+
- `messagingDeliveryFailedRunbook.preconditions(event)` — **add** — `_deps` wired AND `getManager()` returns non-null. Refuses to fire when messaging is not initialized (e.g. unit-test boot or pre-server-up).
|
|
29
|
+
- `messagingDeliveryFailedRunbook.verify(ctx)` — **add** — opens an inbox query against the wired `MessageStore` and counts queued/undelivered messages. Returns one of three §A21 outcomes; in particular probe error (queryInbox throws) → verify-inconclusive, not verify-failed.
|
|
30
|
+
- `DeliveryRetryManager.runRecoveryCycle()` — **add** — Remediator-orchestrated parallel entry point. Idempotent against the running timer via `cycleInFlight` latch.
|
|
31
|
+
- `DeliveryRetryManager.invokeFromRemediator(ctx)` — **add** — surface entry-point. Wraps `runRecoveryCycle` and returns `ExecutionResult` with structured details (retried/expired/escalated/skipped).
|
|
32
|
+
- `DeliveryRetryManager.tick()` — **modify** (wrap) — now acquires the same `cycleInFlight` latch as `runRecoveryCycle`. Inner body extracted unchanged into `doCycle()`. No behavior change for existing callers; the optional `skipped` field is additive.
|
|
33
|
+
- `setMessagingDeliveryDeps(deps)` — **add** — production wires the live manager + store; tests inject fakes. Setting to `null` puts the runbook into a "deps unwired" state where surface/verify return inconclusive.
|
|
34
|
+
|
|
35
|
+
The legacy `tick()` entry-point and the `start()` timer are **pass-through** (existing behavior preserved). The shared latch means a 15s timer tick and a Remediator-dispatch arriving in the same ~ms window won't double-sweep the inbox — first caller wins, second short-circuits with `skipped: true`. This is the §A34 R3 invariant.
|
|
36
|
+
|
|
37
|
+
No new HTTP routes. No new persistent files. The runbook module exports `setMessagingDeliveryDeps` for late-binding so the messaging singleton doesn't need to be imported at module-load time (avoids a circular import path through the server). Production wiring of `setMessagingDeliveryDeps` is deferred to a separate Tier-2 PR alongside `DegradationReporter.setRemediator()` — this PR ships the unit-testable surface only, matching how W-1 left `NativeModuleHealer.invokeFromRemediator` constructible-but-unwired.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## 1. Over-block
|
|
42
|
+
|
|
43
|
+
**What legitimate inputs does this change reject that it shouldn't?**
|
|
44
|
+
|
|
45
|
+
- **Free-text-provenance delivery failures** are NOT matched by this runbook. This is by design (§A6). Legacy `.report(...)` callers in `src/messaging/` that normalize to `provenance: 'free-text'` cannot trigger the recovery cycle — they route to `no-matching-runbook` and feed NovelFailureReviewer's clustering pipeline. Operators who hit delivery failures via the timer cadence STILL get retries because the 15s `tick()` cadence is unchanged. The structural defense costs no functionality.
|
|
46
|
+
- **Events about non-messaging subsystems** are rejected by `match()`. Intended — `DeliveryRetryManager` only knows how to drain its own message store. A different subsystem's delivery failure (e.g. webhook delivery, if that subsystem existed) would need its own runbook.
|
|
47
|
+
- **Events arriving while a tick is mid-flight** are returned `skipped: true` and produce zero work via this dispatch. NOT a rejection — the in-flight cycle WILL sweep the same inbox, so the work is not lost. The Remediator's `verify-failed` taxonomy correctly reports "drain succeeded" or "drain incomplete" regardless of which entry-point did the sweep.
|
|
48
|
+
|
|
49
|
+
No legitimate input is rejected that should have been accepted.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## 2. Under-block
|
|
54
|
+
|
|
55
|
+
**What failure modes does this still miss?**
|
|
56
|
+
|
|
57
|
+
- **No cross-process deduplication.** Two agent processes on the same machine racing the same on-disk inbox would each acquire the shared latch within their OWN process and proceed; cross-process exclusion is the Remediator's MachineLock job (F-4). This runbook is layered on top of that — the in-process latch is the second line of defense, not the first. The single-agent-per-stateDir invariant (already enforced by the agent server's lifeline lock) makes the cross-process race extremely narrow in practice.
|
|
58
|
+
- **Verify counts queued + undelivered only.** Messages in `delivered` phase awaiting ACK timeout are not counted as "stuck" for §A9 purposes. Intended — Layer 3 ACK timeouts are NOT a delivery failure; they're an upstream agent-not-responding signal. A `verified-healthy` result here means delivery has been re-attempted; it does NOT mean every message has been ACKed.
|
|
59
|
+
- **No deadline enforcement on verify() separately.** The Remediator's §A4 AbortController fires around the whole `surfaceCallable + verify` chain (60s budget per `expectedRuntimeMs`). The verify probe is a single on-disk inbox read; under load it stays well under that budget.
|
|
60
|
+
- **`runRecoveryCycle` does not pre-empt mid-flight on `ctx.abortSignal`.** The cycle body is a loop over inbox entries with short, atomic store operations per entry. We don't check the abort signal mid-loop. Acceptable because: the deadline is 60s, each iteration is a single fs read + fs write, and pre-empting mid-iteration could leave the retryState map inconsistent. If the deadline trips, the dispatcher's `aborted-deadline` branch supersedes whatever we return anyway.
|
|
61
|
+
- **The shared latch is not fair.** If a tick is in-flight and 20 Remediator dispatches arrive in 200ms, all 20 short-circuit with `skipped: true` and the single in-flight cycle handles every message. Acceptable — the inbox is a shared queue, "drain everything you can" is idempotent across callers.
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## 3. Level-of-abstraction fit
|
|
66
|
+
|
|
67
|
+
**Is this at the right layer?**
|
|
68
|
+
|
|
69
|
+
Yes. Each piece sits where the spec mandates:
|
|
70
|
+
|
|
71
|
+
- **Runbook (`src/remediation/runbooks/messaging-delivery-failed.ts`)** is the policy layer — declares the match contract, verify semantics, blast-radius, and surfaceCallable wiring. Pure data plus a thin verify wrapper; no orchestration, no key material, no audit-write concerns.
|
|
72
|
+
- **`DeliveryRetryManager.invokeFromRemediator`** is the surface layer — owns translating `RemediationContext` into a recovery cycle invocation, returning structured `ExecutionResult` details. No knowledge of the Remediator's lock / intent / audit primitives.
|
|
73
|
+
- **`DeliveryRetryManager.runRecoveryCycle`** is the operation layer — owns the §A34 R3 "idempotent against running timer" rule via the shared latch. Independent of who called it.
|
|
74
|
+
- **Verify helper (`probeDurableDrain`)** is the durability probe — owns the §A9 "assert durable, not just live" rule. Cleanly separated from the recovery step so verify can run independently.
|
|
75
|
+
|
|
76
|
+
No higher-level smart gate is shadowed. The Remediator's `registerRunbook()` validator (§A6, §A36) is the gate that accepts/rejects this runbook at boot; the runbook itself doesn't contain blocking authority beyond the structural match contract.
|
|
77
|
+
|
|
78
|
+
The signal-vs-authority principle (`docs/signal-vs-authority.md`) is honoured: the match is precise structural code (errorCode enum membership + provenance enum membership + subsystem string equality), not a heuristic content classifier. The authority to "trigger a recovery cycle" is the manager's own; the runbook merely calls into it.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## 4. Signal vs authority compliance
|
|
83
|
+
|
|
84
|
+
**Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
|
|
85
|
+
|
|
86
|
+
**Does this change hold blocking authority with brittle logic?**
|
|
87
|
+
|
|
88
|
+
- [ ] No — this change produces a signal consumed by an existing smart gate.
|
|
89
|
+
- [x] No — this change has no brittle-logic blocking authority. Match is precise structural code over enum-typed fields; verify is a deterministic on-disk inbox count; the only block surface (§A36 essential-on-machine) is enforced by the F-8 dispatcher's validator, not by this runbook.
|
|
90
|
+
- [ ] Yes — but the logic is a smart gate with full conversational context.
|
|
91
|
+
- [ ] ⚠️ Yes, with brittle logic — STOP.
|
|
92
|
+
|
|
93
|
+
The runbook's structural decisions:
|
|
94
|
+
- §A6 prefilter (errorCode + provenance enum equality) — precise.
|
|
95
|
+
- §A36 essential=false enforced by F-8 validator (no claim to essential here).
|
|
96
|
+
- §A9 verify (queued + undelivered count == 0) — deterministic.
|
|
97
|
+
- §A34 R3 idempotence (single-process latch, first-wins) — deterministic.
|
|
98
|
+
- §A21 verify taxonomy — derived from inbox-count probe result kind.
|
|
99
|
+
|
|
100
|
+
No brittle string-matching, no LLM judgement, no content classifier. The match callback's subsystem string equality is the same shape as W-1's better-sqlite3 narrowing.
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## 5. Interactions
|
|
105
|
+
|
|
106
|
+
**What other components is this change touching, even indirectly?**
|
|
107
|
+
|
|
108
|
+
- **F-8 dispatcher (`Remediator.dispatch`)** — registers and dispatches this runbook. Existing dispatch path is generic; no modification needed. The dispatcher's §A6 / §A36 validators accept the runbook (verified by unit test).
|
|
109
|
+
- **F-3 `DegradationReporter`** — emit-sites in `src/messaging/` will eventually call the structured emit helper to produce DELIVERY_FAILURE / TELEGRAM_429 / TELEGRAM_500 events with `provenance: 'subsystem-explicit'`. The migration is incremental; until then, legacy callers route to `no-matching-runbook` via `provenance: 'free-text'` (intended A33 steady-state).
|
|
110
|
+
- **`DeliveryRetryManager` consumers** — `AgentServer` constructs and `start()`s the manager. The wrap of `tick()` is a no-op for the timer caller (it still returns the same shape, just with an optional `skipped` field that's `undefined`/falsy under normal operation).
|
|
111
|
+
- **`MessageStore.queryInbox`** — the verify probe calls this. It's a stable public method used by many other consumers; the additional read load is one inbox scan per dispatch attempt, well below the per-cycle scan rate.
|
|
112
|
+
- **F-4 `MachineLock`** — the runbook acquires the same in-flight tuple lock the dispatcher acquires for any runbook. No new lock surface.
|
|
113
|
+
- **`AgentServer` boot wiring** — NOT modified in this PR. Production wiring of `setMessagingDeliveryDeps(...)` is deferred to the same future PR that wires `DegradationReporter.setRemediator(...)`. Both must be wired together so the runbook actually receives events.
|
|
114
|
+
|
|
115
|
+
The wrap of `tick()`'s body into `doCycle()` is the only behavior change to existing code. Verified by running the existing `delivery-retry-manager.test.ts` (21 cases, all pass unchanged).
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## 6. Rollback cost
|
|
120
|
+
|
|
121
|
+
**If this change is reverted, what breaks?**
|
|
122
|
+
|
|
123
|
+
Low — additive. The legacy `tick()` and `start()` are unchanged in behavior; reverting deletes the new runbook module + the two new public methods + the shared latch wrap on `tick()`. No data-model migration. No persistent state ships with this PR. The shared latch is a `boolean` field; deleting it removes the additional idempotence guarantee but does not corrupt any state. Existing F-1..F-7 + W-1 modules are unaffected.
|
|
124
|
+
|
|
125
|
+
A revert leaves messaging recovery on the pre-existing 15s timer cadence — the Remediator-orchestrated faster-recovery + audit-tracked path is the gain that's lost on revert, not a load-bearing capability.
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## 7. Spec compliance
|
|
130
|
+
|
|
131
|
+
- §A1 W-3 manifest — runbook id `messaging-delivery-failed`, surface `delivery-retry`. Matches the spec table row.
|
|
132
|
+
- §A6 — `provenance: ['subsystem-explicit', 'probe-id']`; explicitly NO `free-text`. Registry validator (F-8) accepts this at registry-load.
|
|
133
|
+
- §A9 — verify queries the durable on-disk inbox (not the in-memory retryState map), counts ALL queued+undelivered, requires zero. "Drain ALL stuck messages (not just one)" matches the spec language verbatim.
|
|
134
|
+
- §A21 — verify taxonomy: ok → verified-healthy; stuck (count > 0) → verify-failed; probe error → verify-inconclusive.
|
|
135
|
+
- §A34 R3 — `runRecoveryCycle()` is a NEW public method, distinct from timer-driven `tick()`. Idempotent against the running timer via the shared `cycleInFlight` latch. Spec docstring captures this.
|
|
136
|
+
- §A36 — essential=false validated against blastRadius=process. F-8 validator accepts.
|
|
137
|
+
- §A57 — Tier-2 W-3 row. Foundation dependency on W-1 (in main as PR #202) and F-8 (in main as PR #201 + #217). W-2 is parallel work; this PR does not block on it.
|
|
138
|
+
- §A15 (note from prompt) — lag rule is documented in the runbook expectedRuntimeMs (60s). The recovery cycle is short-lived; lag-detection escalation is the Remediator's concern, not this runbook's. Build proceeds per W-2's pattern.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## 8. Test coverage
|
|
143
|
+
|
|
144
|
+
27 new tests across two files. Pass: 27/27.
|
|
145
|
+
|
|
146
|
+
`tests/unit/DeliveryRetryManager-runRecoveryCycle.test.ts` (11):
|
|
147
|
+
- runRecoveryCycle is a distinct public method from tick.
|
|
148
|
+
- runRecoveryCycle return shape matches tick on empty inbox.
|
|
149
|
+
- runRecoveryCycle processes queued messages identically to tick (TTL-expired envelope dead-lettered).
|
|
150
|
+
- Idempotence: runRecoveryCycle short-circuits while tick is in-flight.
|
|
151
|
+
- Idempotence: tick short-circuits while runRecoveryCycle is in-flight (symmetric).
|
|
152
|
+
- Latch resets after cycle completes; subsequent calls run normally.
|
|
153
|
+
- invokeFromRemediator wraps runRecoveryCycle and returns ExecutionResult success.
|
|
154
|
+
- invokeFromRemediator returns failure when runRecoveryCycle throws (synthetic store error).
|
|
155
|
+
- invokeFromRemediator reports skipped:true when cycle is in-flight.
|
|
156
|
+
- stop() clears the in-flight latch (recycle-safe).
|
|
157
|
+
- Existing tick() return shape unchanged when cycle runs normally.
|
|
158
|
+
|
|
159
|
+
`tests/unit/runbooks/messaging-delivery-failed.test.ts` (16):
|
|
160
|
+
- Matches all three structured errorCodes.
|
|
161
|
+
- §A6: free-text excluded from prefilter.
|
|
162
|
+
- match() rejects non-messaging subsystems.
|
|
163
|
+
- Remediator.registerRunbook accepts the runbook (essential=false, process radius).
|
|
164
|
+
- §A36 honored: essential=false, blastRadius=process.
|
|
165
|
+
- Manifest fields verified (id, priority=80, surface=delivery-retry, expectedRuntimeMs=60_000, reversibility=reversible).
|
|
166
|
+
- preconditions fail when deps not wired.
|
|
167
|
+
- preconditions succeed when manager is wired.
|
|
168
|
+
- surfaceCallable invokes manager.invokeFromRemediator with ctx (mocked).
|
|
169
|
+
- verify() returns verified-healthy on drained inbox.
|
|
170
|
+
- verify() returns verify-failed on stuck messages (with stuck count + sample IDs).
|
|
171
|
+
- verify() returns verify-inconclusive on store probe error.
|
|
172
|
+
- verify() against real store: queued message → verify-failed (durable assertion).
|
|
173
|
+
- verify() against real store: empty inbox → verified-healthy.
|
|
174
|
+
- End-to-end dispatch on TELEGRAM_429: invoked → verified-healthy → audited.
|
|
175
|
+
- End-to-end dispatch with stuck messages: verify-failed audited (synthetic deliverToSession failure).
|
|
176
|
+
|
|
177
|
+
Existing `tests/unit/delivery-retry-manager.test.ts` (21) and `tests/unit/runbooks/node-abi-mismatch.test.ts` (12) re-verified — unchanged behavior. Existing `tests/unit/Remediator.test.ts` (12) re-verified — unchanged behavior.
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## 9. Open follow-ups
|
|
182
|
+
|
|
183
|
+
- **Production wiring** of `setMessagingDeliveryDeps(...)` and `DegradationReporter.setRemediator(...)` is deferred to a coordinated Tier-2 PR. This PR ships the unit-testable surface; production registration is a single-PR follow-up.
|
|
184
|
+
- **F-3 emit-site migration** in `src/messaging/` — the structured errorCodes DELIVERY_FAILURE / TELEGRAM_429 / TELEGRAM_500 need to flow through `reportStructured(...)`. Until then, the runbook is dormant in production (intended A33 incremental migration).
|
|
185
|
+
- **W-2** (`supervisor-preflight`) and **W-4** (`db-corruption`) — parallel wrapper work; this PR does not depend on either.
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Side-Effects Review — W-4 db-corruption runbook + SemanticMemory.invokeFromRemediator
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `w4-db-corruption-runbook`
|
|
4
|
+
**Date:** `2026-05-13`
|
|
5
|
+
**Author:** echo
|
|
6
|
+
**Second-pass reviewer:** not required (no outbound-messaging, session-lifecycle, sentinel, or coherence-gate surface)
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
W-4 of the Tier-2 Self-Healing Remediator v2 rollout. Adds `src/remediation/runbooks/db-corruption.ts` — an ApprovedRunbook that lets the Remediator dispatch on structured-provenance SQLITE corruption events and orchestrate the existing SemanticMemory corruption-recovery path through a HMAC-authenticated surface entry point. The surface entry point `SemanticMemory.invokeFromRemediator(ctx)` is added alongside, paired with a small set of helpers (`getDurabilityMode`, `runIntegrityCheckForRemediator`, static registry of active instances). The legacy in-line corruption-recovery path inside `SemanticMemory.open()` (lines 178-243 — integrity_check + probe-read + quarantine + JSONL rebuild) is unchanged and remains the canonical CLI-path safety net. Per A34, the surface this runbook wraps is verified live on main BEFORE this change lands. Per A9, verify asserts both `db.mode === 'durable'` AND `pragma integrity_check === 'ok'` so a future in-memory fallback path would be flagged `verify-failed` rather than passing silently. Files: `src/remediation/runbooks/db-corruption.ts`, `src/memory/SemanticMemory.ts`, `tests/unit/runbooks/db-corruption.test.ts`, `tests/unit/SemanticMemory-invokeFromRemediator.test.ts`, `upgrades/NEXT.md`.
|
|
11
|
+
|
|
12
|
+
## Decision-point inventory
|
|
13
|
+
|
|
14
|
+
- `Remediator.dispatch (matched runbook selection)` — pass-through — the new runbook is one more candidate registered alongside W-1; dispatch logic itself is unchanged.
|
|
15
|
+
- `db-corruption.eventPrefilter` — add — structured-provenance only (`native-binding | subsystem-explicit | probe-id`); `'free-text'` excluded per §A6.
|
|
16
|
+
- `db-corruption.match()` — add — confirms the event is about a SemanticMemory-class store (subsystem in {`semantic-memory`, `memory`, `better-sqlite3`} or reason text mentions `SemanticMemory`).
|
|
17
|
+
- `db-corruption.preconditions()` — add — checks that an active `SemanticMemory` instance is registered. If not, returns false → dispatch falls through, no action taken.
|
|
18
|
+
- `db-corruption.verify()` — add — durability-aware probe returning `verified-healthy | verify-failed | verify-inconclusive` per §A21.
|
|
19
|
+
- `SemanticMemory.invokeFromRemediator()` — add — surface entry point with optional §A3 HMAC verification; on invalid HMAC, refuses orchestrated path (it does NOT silently fall back to in-line, because the in-line path naturally fires on the next `open()` anyway).
|
|
20
|
+
- `SemanticMemory.open()` — modify (additive) — sets a `_lastRecoveryRebuilt` flag when the existing rebuild path runs. No behavioral change to the recovery itself.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## 1. Over-block
|
|
25
|
+
|
|
26
|
+
**What legitimate inputs does this change reject that it shouldn't?**
|
|
27
|
+
|
|
28
|
+
- Free-text-extracted `SQLITE_CORRUPT` events from logs/stderr (provenance `'free-text'`) will route to `no-matching-runbook` instead of firing the recovery. This is intentional per §A6 — free-text matchers are an injection surface — but it does mean a real corruption event whose only signal is a parsed log line won't auto-heal until SystemReviewer (Tier-3) promotes the signal or a probe is wired. The legacy in-line path inside `open()` still catches everything on next open, so this is a "miss the orchestrated path" not "miss recovery entirely." Acceptable.
|
|
29
|
+
- A SemanticMemory instance not explicitly registered via `setActiveInstance()` will see `preconditions()` return false → dispatch is a no-op. This is intentional: partial CLI surfaces (e.g. `instar memory` one-shot commands) shouldn't be targets of Remediator-orchestrated recovery. The server bootstrap is the natural place to register the primary instance.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## 2. Under-block
|
|
34
|
+
|
|
35
|
+
**What failure modes does this still miss?**
|
|
36
|
+
|
|
37
|
+
- A truly silent in-memory fallback would require a code change in `SemanticMemory` (not currently present); today durability is binary — either we have a real `dbPath` or we don't. If a future change adds `:memory:` fallback without setting `dbPath`, `getDurabilityMode()` correctly returns `'in-memory'` and verify returns `verify-failed`. Until that change lands, the durability-degraded branch is dead code — but harmless, and exists exactly so the runbook is ready when fallback ships.
|
|
38
|
+
- The post-recover integrity check runs once at end of `open()`. A corruption introduced after open completes (e.g. mid-write torn page) is caught on the NEXT open or by an A52 probe — not by this surface invocation. Same shape as W-1.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 3. Level-of-abstraction fit
|
|
43
|
+
|
|
44
|
+
**Is this at the right layer?**
|
|
45
|
+
|
|
46
|
+
Yes. The runbook is a thin orchestration shim: it declares a structured prefilter, narrows with `match()`, gates with `preconditions()`, delegates the actual work to the surface (which owns the recovery), and probes with `verify()`. The surface owns the policy of "how do we recover?" — that's the right separation: orchestration logic stays in `src/remediation/`, recovery logic stays in `src/memory/`. No duplicated logic; the runbook just composes existing primitives. Mirrors the W-1 pattern exactly.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## 4. Signal vs authority compliance
|
|
51
|
+
|
|
52
|
+
**Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
|
|
53
|
+
|
|
54
|
+
**Does this change hold blocking authority with brittle logic?**
|
|
55
|
+
|
|
56
|
+
- [x] No — this change produces a signal consumed by an existing smart gate.
|
|
57
|
+
|
|
58
|
+
The runbook's `eventPrefilter` and `match()` are **detectors** that surface "this event looks like SemanticMemory corruption." They produce structured signals (matched/not-matched). The **authority** is the `Remediator` — it holds the lock, enforces the deadline, signs/verifies the capability ctx, drives the audit log, and decides the final dispatch outcome. The surface (`SemanticMemory.invokeFromRemediator`) is also a downstream consumer of the ctx, not a parallel decision-maker — it can refuse on invalid HMAC, but that's the structurally-correct response to a forged capability token (per §A3), not a brittle filter sitting in front of a smart gate.
|
|
59
|
+
|
|
60
|
+
`verify()` is similarly a structured probe (durable+ok / corrupt / degraded / inconclusive) — the Remediator decides what to log and whether to count against churn (§A8/§A21).
|
|
61
|
+
|
|
62
|
+
No new brittle blocking logic introduced.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## 5. Interactions
|
|
67
|
+
|
|
68
|
+
**Does this interact with existing checks, recovery paths, or infrastructure?**
|
|
69
|
+
|
|
70
|
+
- **Shadowing:** the in-line corruption-recovery path inside `SemanticMemory.open()` still runs on every open — including the re-open the surface triggers. This is intentional: the orchestrated path delegates back to the in-line logic. There is no race because the surface explicitly `close()`s before re-opening. Per §A2, both paths should acquire the same machine lock; the existing in-line path doesn't yet hold a lock (that's an F-3 / surface-lock workstream beyond W-4 scope), but the Remediator does hold one per dispatch — so concurrent orchestrated dispatches are serialized. Concurrent in-line + orchestrated against the same dbPath remains a known F-3 gap (called out in the spec).
|
|
71
|
+
- **Double-fire:** if a SQLITE_CORRUPT event is normalized AND a probe fires AND the in-line path also runs naturally on next open, three signals converge. The Remediator's lock + storm-coalesce prevents double-orchestrated execution. The in-line path is idempotent — running it after the orchestrated heal is a no-op because `_needsRebuild` is only set on integrity_check failure, which has been resolved.
|
|
72
|
+
- **Races:** the surface closes its own db handle before re-opening. Concurrent callers using the same `SemanticMemory` instance during recovery would see an `ensureOpen()` throw — same as today's `close()` semantics. The Remediator's lock structurally serializes orchestrated attempts on the same machine.
|
|
73
|
+
- **Feedback loops:** none. Recovery emits no new degradation events; the existing quarantine marker file is unchanged.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## 6. External surfaces
|
|
78
|
+
|
|
79
|
+
**Does this change anything visible outside the immediate code path?**
|
|
80
|
+
|
|
81
|
+
- **Other agents:** none. Recovery operates on per-instance `dbPath`.
|
|
82
|
+
- **Other users:** none. No public API surface added beyond the runbook export.
|
|
83
|
+
- **External systems:** none. No Telegram/Slack/GitHub touchpoints.
|
|
84
|
+
- **Persistent state:** the existing `.corrupt.<ts>` quarantine file and `.corrupt-recovery.<ts>.marker.json` continue to be written by the unchanged in-line path. The new `_lastRecoveryRebuilt` is a process-memory flag only — not persisted. The new audit-projection entries (`db-corruption: started → verified-healthy`) ship through the existing AuditWriter, same shape as W-1 entries.
|
|
85
|
+
- **Timing:** the recovery path can take seconds to tens of seconds on large JSONL files. Already covered by the existing `autoRebuildMaxBytes` cap in `open()`. The runbook's `expectedRuntimeMs: 60_000` budgets one minute; the Remediator's AbortController enforces it.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## 7. Rollback cost
|
|
90
|
+
|
|
91
|
+
**If this turns out wrong in production, what's the back-out?**
|
|
92
|
+
|
|
93
|
+
Pure code change. Revert the four touched files and ship as a patch. No persistent state to migrate (the `_lastRecoveryRebuilt` flag is in-memory only; `setActiveInstance` is a process-local registry). No user-visible regression during rollback — the in-line corruption-recovery path still handles everything it handles today. Agents that have run with the new code will simply lose access to the orchestrated path until they update; they retain the safety net.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Conclusion
|
|
98
|
+
|
|
99
|
+
W-4 lands a small, additive, properly-gated wrapper around an already-proven recovery path. The signal-vs-authority separation is clean: detectors (prefilter + match) feed the Remediator (authority); the surface receives a capability ctx and performs the work but does not gain new decision power. The §A34 surface-alignment precondition is verified: the corruption-recovery code on main is real, not stubbed. The §A9 durability-not-liveness verify is in place, ready for the day a fallback path is added — when that future change lands, this runbook automatically flags it `verify-failed`. Clear to ship.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Second-pass review (if required)
|
|
104
|
+
|
|
105
|
+
Not required. The change does NOT touch outbound/inbound messaging block/allow, session lifecycle, compaction, coherence gates, sentinels, or watchdogs. It composes existing primitives (Remediator, AuditWriter, MachineLock, SemanticMemory.open) without introducing new authority.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Evidence pointers
|
|
110
|
+
|
|
111
|
+
- 29 new unit tests pass (`tests/unit/runbooks/db-corruption.test.ts` 17 cases + `tests/unit/SemanticMemory-invokeFromRemediator.test.ts` 12 cases).
|
|
112
|
+
- Adjacent tests still pass: `tests/unit/semantic-memory-corruption-recovery.test.ts` (12), `tests/unit/runbooks/node-abi-mismatch.test.ts` (12), `tests/unit/semantic-memory.test.ts`.
|
|
113
|
+
- `npx tsc --noEmit` clean.
|
|
114
|
+
- Surface alignment verified by `grep -n "quarantineCorruptDb\|nuclear option" src/memory/SemanticMemory.ts` against origin/main HEAD = `3ba88731` — recovery is live on main at lines 178-243, 273-315, 1655-1674.
|