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.
Files changed (247) hide show
  1. package/.claude/hooks/instar/before-prompt-recall.js +90 -0
  2. package/dist/cli.js +28 -0
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/init.d.ts +1 -0
  5. package/dist/commands/init.d.ts.map +1 -1
  6. package/dist/commands/init.js +14 -1
  7. package/dist/commands/init.js.map +1 -1
  8. package/dist/commands/jobMigrate.d.ts +46 -0
  9. package/dist/commands/jobMigrate.d.ts.map +1 -0
  10. package/dist/commands/jobMigrate.js +292 -0
  11. package/dist/commands/jobMigrate.js.map +1 -0
  12. package/dist/commands/server.d.ts.map +1 -1
  13. package/dist/commands/server.js +88 -42
  14. package/dist/commands/server.js.map +1 -1
  15. package/dist/core/BackupManager.d.ts +27 -1
  16. package/dist/core/BackupManager.d.ts.map +1 -1
  17. package/dist/core/BackupManager.js +56 -1
  18. package/dist/core/BackupManager.js.map +1 -1
  19. package/dist/core/GitStateManager.d.ts +8 -0
  20. package/dist/core/GitStateManager.d.ts.map +1 -1
  21. package/dist/core/GitStateManager.js +31 -1
  22. package/dist/core/GitStateManager.js.map +1 -1
  23. package/dist/core/MigratorStepEngine.d.ts +170 -0
  24. package/dist/core/MigratorStepEngine.d.ts.map +1 -0
  25. package/dist/core/MigratorStepEngine.js +352 -0
  26. package/dist/core/MigratorStepEngine.js.map +1 -0
  27. package/dist/core/PostUpdateMigrator.d.ts +76 -0
  28. package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
  29. package/dist/core/PostUpdateMigrator.js +252 -1
  30. package/dist/core/PostUpdateMigrator.js.map +1 -1
  31. package/dist/core/PreCompactionFlush.d.ts +101 -0
  32. package/dist/core/PreCompactionFlush.d.ts.map +1 -0
  33. package/dist/core/PreCompactionFlush.js +282 -0
  34. package/dist/core/PreCompactionFlush.js.map +1 -0
  35. package/dist/core/PromptBuildRecall.d.ts +97 -0
  36. package/dist/core/PromptBuildRecall.d.ts.map +1 -0
  37. package/dist/core/PromptBuildRecall.js +152 -0
  38. package/dist/core/PromptBuildRecall.js.map +1 -0
  39. package/dist/core/selectIntelligenceProvider.d.ts +41 -0
  40. package/dist/core/selectIntelligenceProvider.d.ts.map +1 -0
  41. package/dist/core/selectIntelligenceProvider.js +89 -0
  42. package/dist/core/selectIntelligenceProvider.js.map +1 -0
  43. package/dist/lifeline/ServerSupervisor.d.ts +183 -0
  44. package/dist/lifeline/ServerSupervisor.d.ts.map +1 -1
  45. package/dist/lifeline/ServerSupervisor.js +443 -0
  46. package/dist/lifeline/ServerSupervisor.js.map +1 -1
  47. package/dist/memory/NativeModuleHealer.d.ts +89 -0
  48. package/dist/memory/NativeModuleHealer.d.ts.map +1 -1
  49. package/dist/memory/NativeModuleHealer.js +371 -1
  50. package/dist/memory/NativeModuleHealer.js.map +1 -1
  51. package/dist/memory/SemanticMemory.d.ts +131 -0
  52. package/dist/memory/SemanticMemory.d.ts.map +1 -1
  53. package/dist/memory/SemanticMemory.js +315 -0
  54. package/dist/memory/SemanticMemory.js.map +1 -1
  55. package/dist/messaging/DeliveryRetryManager.d.ts +60 -0
  56. package/dist/messaging/DeliveryRetryManager.d.ts.map +1 -1
  57. package/dist/messaging/DeliveryRetryManager.js +104 -0
  58. package/dist/messaging/DeliveryRetryManager.js.map +1 -1
  59. package/dist/monitoring/DegradationReporter.d.ts +164 -0
  60. package/dist/monitoring/DegradationReporter.d.ts.map +1 -1
  61. package/dist/monitoring/DegradationReporter.js +309 -1
  62. package/dist/monitoring/DegradationReporter.js.map +1 -1
  63. package/dist/monitoring/ErrorCodeExtractor.d.ts +69 -0
  64. package/dist/monitoring/ErrorCodeExtractor.d.ts.map +1 -0
  65. package/dist/monitoring/ErrorCodeExtractor.js +131 -0
  66. package/dist/monitoring/ErrorCodeExtractor.js.map +1 -0
  67. package/dist/monitoring/PromptGate.d.ts +34 -0
  68. package/dist/monitoring/PromptGate.d.ts.map +1 -1
  69. package/dist/monitoring/PromptGate.js +81 -1
  70. package/dist/monitoring/PromptGate.js.map +1 -1
  71. package/dist/monitoring/Redactor.d.ts +58 -0
  72. package/dist/monitoring/Redactor.d.ts.map +1 -0
  73. package/dist/monitoring/Redactor.js +143 -0
  74. package/dist/monitoring/Redactor.js.map +1 -0
  75. package/dist/monitoring/probes/LifelineProbe.d.ts +7 -0
  76. package/dist/monitoring/probes/LifelineProbe.d.ts.map +1 -1
  77. package/dist/monitoring/probes/LifelineProbe.js +7 -0
  78. package/dist/monitoring/probes/LifelineProbe.js.map +1 -1
  79. package/dist/monitoring/probes/__shared.d.ts +50 -0
  80. package/dist/monitoring/probes/__shared.d.ts.map +1 -0
  81. package/dist/monitoring/probes/__shared.js +51 -0
  82. package/dist/monitoring/probes/__shared.js.map +1 -0
  83. package/dist/remediation/IntentJournal.d.ts +64 -0
  84. package/dist/remediation/IntentJournal.d.ts.map +1 -0
  85. package/dist/remediation/IntentJournal.js +118 -0
  86. package/dist/remediation/IntentJournal.js.map +1 -0
  87. package/dist/remediation/MachineLock.d.ts +137 -0
  88. package/dist/remediation/MachineLock.d.ts.map +1 -0
  89. package/dist/remediation/MachineLock.js +314 -0
  90. package/dist/remediation/MachineLock.js.map +1 -0
  91. package/dist/remediation/PrimaryAggregatorLease.d.ts +205 -0
  92. package/dist/remediation/PrimaryAggregatorLease.d.ts.map +1 -0
  93. package/dist/remediation/PrimaryAggregatorLease.js +450 -0
  94. package/dist/remediation/PrimaryAggregatorLease.js.map +1 -0
  95. package/dist/remediation/RemediationContext.d.ts +63 -0
  96. package/dist/remediation/RemediationContext.d.ts.map +1 -0
  97. package/dist/remediation/RemediationContext.js +109 -0
  98. package/dist/remediation/RemediationContext.js.map +1 -0
  99. package/dist/remediation/RemediationKeyVault.d.ts +123 -0
  100. package/dist/remediation/RemediationKeyVault.d.ts.map +1 -0
  101. package/dist/remediation/RemediationKeyVault.js +564 -0
  102. package/dist/remediation/RemediationKeyVault.js.map +1 -0
  103. package/dist/remediation/Remediator.d.ts +288 -0
  104. package/dist/remediation/Remediator.d.ts.map +1 -0
  105. package/dist/remediation/Remediator.js +520 -0
  106. package/dist/remediation/Remediator.js.map +1 -0
  107. package/dist/remediation/RemediatorBootstrap.d.ts +141 -0
  108. package/dist/remediation/RemediatorBootstrap.d.ts.map +1 -0
  109. package/dist/remediation/RemediatorBootstrap.js +255 -0
  110. package/dist/remediation/RemediatorBootstrap.js.map +1 -0
  111. package/dist/remediation/TrustElevationSource.d.ts +153 -0
  112. package/dist/remediation/TrustElevationSource.d.ts.map +1 -0
  113. package/dist/remediation/TrustElevationSource.js +208 -0
  114. package/dist/remediation/TrustElevationSource.js.map +1 -0
  115. package/dist/remediation/audit/AuditProjection.d.ts +37 -0
  116. package/dist/remediation/audit/AuditProjection.d.ts.map +1 -0
  117. package/dist/remediation/audit/AuditProjection.js +118 -0
  118. package/dist/remediation/audit/AuditProjection.js.map +1 -0
  119. package/dist/remediation/audit/AuditWriter.d.ts +79 -0
  120. package/dist/remediation/audit/AuditWriter.d.ts.map +1 -0
  121. package/dist/remediation/audit/AuditWriter.js +132 -0
  122. package/dist/remediation/audit/AuditWriter.js.map +1 -0
  123. package/dist/remediation/channels/CliApprovalChannel.d.ts +37 -0
  124. package/dist/remediation/channels/CliApprovalChannel.d.ts.map +1 -0
  125. package/dist/remediation/channels/CliApprovalChannel.js +45 -0
  126. package/dist/remediation/channels/CliApprovalChannel.js.map +1 -0
  127. package/dist/remediation/channels/TelegramApprovalChannel.d.ts +37 -0
  128. package/dist/remediation/channels/TelegramApprovalChannel.d.ts.map +1 -0
  129. package/dist/remediation/channels/TelegramApprovalChannel.js +45 -0
  130. package/dist/remediation/channels/TelegramApprovalChannel.js.map +1 -0
  131. package/dist/remediation/runbooks/db-corruption.d.ts +86 -0
  132. package/dist/remediation/runbooks/db-corruption.d.ts.map +1 -0
  133. package/dist/remediation/runbooks/db-corruption.js +190 -0
  134. package/dist/remediation/runbooks/db-corruption.js.map +1 -0
  135. package/dist/remediation/runbooks/messaging-delivery-failed.d.ts +108 -0
  136. package/dist/remediation/runbooks/messaging-delivery-failed.d.ts.map +1 -0
  137. package/dist/remediation/runbooks/messaging-delivery-failed.js +172 -0
  138. package/dist/remediation/runbooks/messaging-delivery-failed.js.map +1 -0
  139. package/dist/remediation/runbooks/node-abi-mismatch.d.ts +70 -0
  140. package/dist/remediation/runbooks/node-abi-mismatch.d.ts.map +1 -0
  141. package/dist/remediation/runbooks/node-abi-mismatch.js +216 -0
  142. package/dist/remediation/runbooks/node-abi-mismatch.js.map +1 -0
  143. package/dist/remediation/runbooks/supervisor-preflight.d.ts +114 -0
  144. package/dist/remediation/runbooks/supervisor-preflight.d.ts.map +1 -0
  145. package/dist/remediation/runbooks/supervisor-preflight.js +299 -0
  146. package/dist/remediation/runbooks/supervisor-preflight.js.map +1 -0
  147. package/dist/scheduler/AgentMdAtomicSave.d.ts +85 -0
  148. package/dist/scheduler/AgentMdAtomicSave.d.ts.map +1 -0
  149. package/dist/scheduler/AgentMdAtomicSave.js +169 -0
  150. package/dist/scheduler/AgentMdAtomicSave.js.map +1 -0
  151. package/dist/scheduler/AgentMdReconcile.d.ts +62 -0
  152. package/dist/scheduler/AgentMdReconcile.d.ts.map +1 -0
  153. package/dist/scheduler/AgentMdReconcile.js +212 -0
  154. package/dist/scheduler/AgentMdReconcile.js.map +1 -0
  155. package/dist/scheduler/DisabledBodyDrift.d.ts +70 -0
  156. package/dist/scheduler/DisabledBodyDrift.d.ts.map +1 -0
  157. package/dist/scheduler/DisabledBodyDrift.js +149 -0
  158. package/dist/scheduler/DisabledBodyDrift.js.map +1 -0
  159. package/dist/scheduler/InstallBuiltinJobs.d.ts +53 -0
  160. package/dist/scheduler/InstallBuiltinJobs.d.ts.map +1 -0
  161. package/dist/scheduler/InstallBuiltinJobs.js +212 -0
  162. package/dist/scheduler/InstallBuiltinJobs.js.map +1 -0
  163. package/dist/scheduler/JobLoader.d.ts.map +1 -1
  164. package/dist/scheduler/JobLoader.js +48 -0
  165. package/dist/scheduler/JobLoader.js.map +1 -1
  166. package/dist/scheduler/MigrationInvariants.d.ts +62 -0
  167. package/dist/scheduler/MigrationInvariants.d.ts.map +1 -0
  168. package/dist/scheduler/MigrationInvariants.js +215 -0
  169. package/dist/scheduler/MigrationInvariants.js.map +1 -0
  170. package/dist/scheduler/MigrationLedger.d.ts +84 -0
  171. package/dist/scheduler/MigrationLedger.d.ts.map +1 -0
  172. package/dist/scheduler/MigrationLedger.js +114 -0
  173. package/dist/scheduler/MigrationLedger.js.map +1 -0
  174. package/dist/server/fileRoutes.d.ts.map +1 -1
  175. package/dist/server/fileRoutes.js +7 -0
  176. package/dist/server/fileRoutes.js.map +1 -1
  177. package/dist/server/routes.d.ts.map +1 -1
  178. package/dist/server/routes.js +141 -0
  179. package/dist/server/routes.js.map +1 -1
  180. package/package.json +3 -2
  181. package/scripts/classify-default-drift.mjs +238 -0
  182. package/scripts/eli16-overview-check.mjs +85 -0
  183. package/scripts/instar-dev-precommit.js +60 -1
  184. package/scripts/lint-degradation-emit-sites.js +116 -0
  185. package/scripts/lint-no-direct-destructive.js +12 -0
  186. package/scripts/protect-migration-guarantee.js +61 -0
  187. package/scripts/regen-default-job-templates.mjs +111 -0
  188. package/scripts/verify-runbook-pr-signature.js +366 -0
  189. package/src/data/builtin-manifest.json +93 -93
  190. package/src/scaffold/bootstrap.ts +138 -0
  191. package/src/scaffold/keys/instar-release-pub.pem +3 -0
  192. package/src/scaffold/templates/jobs/instar/.gitkeep +0 -0
  193. package/src/scaffold/templates/jobs/instar/commitment-detection.md +28 -0
  194. package/src/scaffold/templates/jobs/instar/evolution-overdue-check.md +28 -0
  195. package/src/scaffold/templates/jobs/instar/evolution-proposal-evaluate.md +30 -0
  196. package/src/scaffold/templates/jobs/instar/evolution-proposal-implement.md +25 -0
  197. package/src/scaffold/templates/jobs/instar/health-check.md +14 -0
  198. package/src/scaffold/templates/jobs/instar/identity-review.md +42 -0
  199. package/src/scaffold/templates/jobs/instar/insight-harvest.md +31 -0
  200. package/src/scaffold/templates/jobs/instar/overseer-development.md +23 -0
  201. package/src/scaffold/templates/jobs/instar/overseer-guardian.md +28 -0
  202. package/src/scaffold/templates/jobs/instar/overseer-infrastructure.md +26 -0
  203. package/src/scaffold/templates/jobs/instar/overseer-learning.md +28 -0
  204. package/src/scaffold/templates/jobs/instar/overseer-maintenance.md +27 -0
  205. package/src/scaffold/templates/jobs/instar/reflection-trigger.md +40 -0
  206. package/src/scaffold/templates/jobs/instar/relationship-maintenance.md +16 -0
  207. package/src/scaffold/templates.ts +1616 -0
  208. package/upgrades/0.28.102.md +6 -7
  209. package/upgrades/0.28.103.md +471 -0
  210. package/upgrades/side-effects/a47-primary-aggregator-lease.md +82 -0
  211. package/upgrades/side-effects/agentmd-atomic-save.md +66 -0
  212. package/upgrades/side-effects/agentmd-loader-200jobs-perf-benchmark.md +50 -0
  213. package/upgrades/side-effects/agentmd-reconcile-boot-lifecycle.md +66 -0
  214. package/upgrades/side-effects/api-safety-guard.md +80 -0
  215. package/upgrades/side-effects/c1-runbook-pr-gate.md +83 -0
  216. package/upgrades/side-effects/disabled-body-drift-detection.md +59 -0
  217. package/upgrades/side-effects/drift-classifier-release-time.md +71 -0
  218. package/upgrades/side-effects/eli16-overview-required-gate.md +77 -0
  219. package/upgrades/side-effects/f1-remediation-key-vault.md +195 -0
  220. package/upgrades/side-effects/f2-redactor-errorcode-extractor.md +130 -0
  221. package/upgrades/side-effects/f3-degradation-reporter-shim.md +105 -0
  222. package/upgrades/side-effects/f4-machine-locks-intent-journal-audit.md +117 -0
  223. package/upgrades/side-effects/f5-trust-elevation-source.md +258 -0
  224. package/upgrades/side-effects/f6-supervisor-handshake.md +234 -0
  225. package/upgrades/side-effects/f7-post-update-migrator-atomic-step.md +129 -0
  226. package/upgrades/side-effects/f8-remediator-skeleton.md +153 -0
  227. package/upgrades/side-effects/f8-rest-enforcement.md +72 -0
  228. package/upgrades/side-effects/file-viewer-jobs-instar-never-editable.md +41 -0
  229. package/upgrades/side-effects/frontmatter-interpolation-breakout-test.md +48 -0
  230. package/upgrades/side-effects/jobs-as-agentmd-phase-2.md +78 -0
  231. package/upgrades/side-effects/jobs-as-agentmd-phase-3.md +76 -0
  232. package/upgrades/side-effects/jobs-as-agentmd-phase-4-endpoints.md +62 -0
  233. package/upgrades/side-effects/jobs-as-agentmd-phase-5.md +67 -0
  234. package/upgrades/side-effects/jobs-as-agentmd-phase-6.md +53 -0
  235. package/upgrades/side-effects/jobs-endpoints-auth-verification.md +52 -0
  236. package/upgrades/side-effects/migration-guarantee-suite.md +61 -0
  237. package/upgrades/side-effects/migration-runtime-invariant-gate.md +59 -0
  238. package/upgrades/side-effects/migration-telemetry-invariant-8.md +68 -0
  239. package/upgrades/side-effects/npm-pack-templates-smoke.md +51 -0
  240. package/upgrades/side-effects/openclaw-import-before-prompt-build.md +84 -0
  241. package/upgrades/side-effects/openclaw-import-pre-compaction-flush.md +85 -0
  242. package/upgrades/side-effects/prompt-gate-no-prompt-cache.md +258 -0
  243. package/upgrades/side-effects/tier2-degradation-reporter-live-wire.md +146 -0
  244. package/upgrades/side-effects/w1-node-abi-mismatch-runbook.md +154 -0
  245. package/upgrades/side-effects/w2-supervisor-preflight-runbook.md +176 -0
  246. package/upgrades/side-effects/w3-messaging-delivery-failed-runbook.md +185 -0
  247. package/upgrades/side-effects/w4-db-corruption-runbook.md +114 -0
@@ -0,0 +1,77 @@
1
+ # Side-Effects Review — ELI16 overview required for every approved spec
2
+
3
+ **Version / slug:** `eli16-overview-required-gate`
4
+ **Date:** 2026-05-13
5
+ **Author:** echo
6
+ **Second-pass reviewer:** not required (structural gate; deterministic; small surface)
7
+
8
+ ## Summary of the change
9
+
10
+ Adds a third structural check to the `/instar-dev` precommit gate and to `/spec-converge`'s convergence-tag writer: every approved spec must ship with a plain-English ELI16 overview at `docs/specs/<slug>.eli16.md` (or a path declared via the spec's `eli16-overview:` frontmatter field). The overview must be at least 800 characters of trimmed content — stubs are refused. Files touched: `scripts/eli16-overview-check.mjs` (new shared module), `scripts/instar-dev-precommit.js`, `skills/spec-converge/scripts/write-convergence-tag.mjs`, `skills/instar-dev/SKILL.md`, `skills/spec-converge/SKILL.md`, `skills/instar-dev/templates/eli16-overview.md` (new template), `tests/unit/eli16-overview-check.test.ts` (new unit test).
11
+
12
+ ## Decision-point inventory
13
+
14
+ - `scripts/instar-dev-precommit.js` Step 7 (new) — **add** — refuses a commit if the spec referenced by the trace has no ELI16 companion or the companion is a stub. Pure file-existence + length check; deterministic; no LLM judgment.
15
+ - `skills/spec-converge/scripts/write-convergence-tag.mjs` (new pre-check) — **add** — refuses to stamp `review-convergence` on a spec without an ELI16 companion. Same deterministic check.
16
+
17
+ ---
18
+
19
+ ## 1. Over-block
20
+
21
+ **What legitimate inputs does this change reject that it shouldn't?**
22
+
23
+ - **Bootstrap exceptions remain intact.** The existing bootstrap-trigger logic for the precommit gate (which lets the gate's own first-ship pass) is upstream of the ELI16 check; bootstrap commits already exit before any spec-tag verification runs. So no over-block on bootstrap.
24
+ - **Out-of-scope commits remain unaffected.** The ELI16 check runs only after `inScopeFiles.length > 0` and after the spec-tag verification, so docs-only or release-note commits never hit it.
25
+ - **Existing approved specs with shipped work.** Forward-only — old specs whose work has already merged are not re-committed against, so the gate doesn't fire on them. If a contributor wants to amend an old spec post-ship, they'll need to add an ELI16; this is the intended behavior.
26
+
27
+ ## 2. Under-block
28
+
29
+ **What failure modes does this still miss?**
30
+
31
+ - An ELI16 file that satisfies the length requirement but contains low-quality / jargon-heavy / non-ELI16 prose. The gate checks file existence and length, not reading-level. This is intentional: deterministic check, no LLM judgment in a precommit hook. Reading-level enforcement happens at the human-review step (the user reads the overview before applying `approved: true`).
32
+ - An ELI16 file that's pure boilerplate copied from the template. The 800-char floor reduces this risk but doesn't eliminate it. Mitigation: the template's section headers are themselves substantive prompts requiring real content; copying without filling-in is hard to disguise at 800+ chars.
33
+
34
+ ## 3. Level-of-abstraction fit
35
+
36
+ **Is this at the right layer? Should a higher or lower layer own it? Does a smarter gate already exist?**
37
+
38
+ Yes — this is at the same layer as the existing review-convergence and approved tag checks. Same deterministic-file-check shape, same precommit + convergence-write hook points, same shared concern (spec quality at handoff time). No smarter gate exists for "is the spec readable by the decider"; the existing gates verify that review *happened*, not that the spec is *legible*.
39
+
40
+ ## 4. Signal vs authority compliance
41
+
42
+ **Does this hold blocking authority with brittle logic, or does it produce a signal?**
43
+
44
+ Authoritative, deterministic, structural. The check is file-existence + content-length — both verifiable, both stable, no LLM/regex/parsing surprises. This is the canonical shape for blocking authority per `docs/signal-vs-authority.md`: structural gates are allowed to block; brittle/heuristic gates produce signals for a smarter gate.
45
+
46
+ ## 5. Interactions
47
+
48
+ **Does it shadow another check, get shadowed, double-fire, race?**
49
+
50
+ - **Order in precommit:** runs after spec-tag verification (`review-convergence`, `approved: true`). If either of those fails, the ELI16 check is never reached — single source of error at a time.
51
+ - **Order in spec-converge:** runs BEFORE the convergence tag is written. Prevents the asymmetric state where a spec is tagged converged but has no overview.
52
+ - **Duplicate check on commit:** both `write-convergence-tag.mjs` (at convergence time) and `instar-dev-precommit.js` (at commit time) verify the ELI16. This is intentional belt-and-suspenders — a spec that lost its ELI16 between convergence and commit (file deleted) is still caught at commit.
53
+ - **No race conditions.** Both checks are synchronous read-only file checks.
54
+
55
+ ## 6. External surfaces
56
+
57
+ **Does it change anything visible to other agents, other users, other systems?**
58
+
59
+ - Other agents using `/instar-dev` against the instar repo will see the new check fire on any commit referencing a spec without an ELI16. Error message is explicit about how to fix (add a sibling file or declare via frontmatter).
60
+ - Users handed a spec by an agent will receive an ELI16 overview by default — this is the user-visible improvement that motivated the gate. Topic 3079 (2026-05-13) was the trigger.
61
+ - No timing dependencies, no conversation state, no runtime conditions. The check sees the filesystem at commit time, period.
62
+
63
+ ## 7. Rollback cost
64
+
65
+ **If this turns out wrong in production, what's the back-out?**
66
+
67
+ Single-line revert in each of two files: comment out the `checkEli16Overview` block in `scripts/instar-dev-precommit.js` (Step 7) and in `skills/spec-converge/scripts/write-convergence-tag.mjs` (pre-check). No data migration. No state mutation. No effect on already-tagged specs. The shared module `scripts/eli16-overview-check.mjs` becomes dead code until removed, but that's harmless. The template at `skills/instar-dev/templates/eli16-overview.md` remains as documentation. The next release picks up the revert automatically — no schema or wire-format change to coordinate.
68
+
69
+ ## Trust elevation
70
+
71
+ Not applicable — no autonomy profile change, no new trust-level surface.
72
+
73
+ ## Side-effects on adjacent systems
74
+
75
+ - `husky/pre-commit` calls `instar-dev-precommit.js`; no change to husky config.
76
+ - The release-cut publish gate is unaffected: ELI16 enforcement is at commit-time, not at release-cut time. Spec authors get the error at the point of authorship, where it's fastest to fix.
77
+ - Backup/sync: no new state files. The ELI16 companion is a regular spec sibling, gitignored or not per the repo's existing `.gitignore`.
@@ -0,0 +1,195 @@
1
+ # Side-Effects Review — F-1 RemediationKeyVault
2
+
3
+ **Version / slug:** `f1-remediation-key-vault`
4
+ **Date:** 2026-05-13
5
+ **Author:** echo (instar-developing agent)
6
+ **Second-pass reviewer:** not required
7
+
8
+ ## Summary of the change
9
+
10
+ This change adds `src/remediation/RemediationKeyVault.ts`, the Tier-1 cryptographic
11
+ foundation for the Self-Healing Remediator (per `docs/specs/SELF-HEALING-REMEDIATOR-V2-SPEC.md`,
12
+ amendments A20, A23, A39, A42, A51, A54, A58, A62). The module exposes a
13
+ per-context, per-scope HKDF-SHA256 leaf-key derivation surface backed by a
14
+ 4-backend secret store (OS keychain → hardware enclave stub → cloud KMS stub →
15
+ env-passphrase + AES-256-GCM flatfile). It also adds
16
+ `tests/unit/RemediationKeyVault.test.ts` (20 tests).
17
+
18
+ Files touched: `src/remediation/RemediationKeyVault.ts` (new),
19
+ `tests/unit/RemediationKeyVault.test.ts` (new), `upgrades/NEXT.md` (release-note
20
+ entry), this artifact.
21
+
22
+ The module is **not yet consumed** by any runtime path. F-2+ wires the Remediator's
23
+ dispatch, probe, in-flight, ledger, and audit surfaces to derive leaf keys via this
24
+ vault. F-1 ships the foundation in isolation so it can be exercised in tests and
25
+ reviewed against the spec before downstream consumers depend on it.
26
+
27
+ ## Decision-point inventory
28
+
29
+ - `RemediationKeyVault.forStateDir / selectBackend` — **add** — selects which secret
30
+ backend (keychain / hardware-enclave / cloud-KMS / env-passphrase) the vault uses.
31
+ This is a configuration decision, not a security gate: it does not block any
32
+ inputs, it picks where keys live.
33
+ - `loadOrInitKeychain / loadOrInitFlatFile` — **add** — fresh-install vs.
34
+ pre-existing-install branching. The fail-closed paths (partial-master,
35
+ install-nonce-missing-on-existing-install) refuse to start the vault, which is
36
+ authority. See section 4 for signal-vs-authority analysis.
37
+ - `deriveLeafKey` — **add** — pure function over (master, nonce, info). No
38
+ decision-point surface; refuses only on `info.length > 1024` (Node HKDF cap).
39
+
40
+ ---
41
+
42
+ ## 1. Over-block
43
+
44
+ **What legitimate inputs does this change reject that it shouldn't?**
45
+
46
+ - `RemediationKeyVault.forStateDir` with no keychain available and no env
47
+ passphrase configured throws `no-backend-available`. Legitimate but
48
+ mis-configured deployments (e.g., a fresh Docker container forgetting to set
49
+ `INSTAR_REMEDIATION_KEY_PASSPHRASE`) will fail to start. This is intentional
50
+ per A62 — the alternative is silently running with no secret store, which is
51
+ worse — but the error message must guide the operator. The error explicitly
52
+ names the missing requirement; downstream callers should surface this to the
53
+ operator via existing alert paths.
54
+
55
+ - `partial master state` (some context masters exist, others don't) refuses to
56
+ load. This will only fire if a manual keychain edit deleted some entries, or
57
+ if a previous `rotateContext` crashed mid-write. Recovery is to delete the
58
+ remaining entries and let the loader mint a fresh install. F-2 should add an
59
+ `instar remediation reset` CLI; for F-1, the operator can use the `security`
60
+ CLI directly.
61
+
62
+ ## 2. Under-block
63
+
64
+ **What failure modes does this still miss?**
65
+
66
+ - F-1's keychain ACL is the OS default ("any process owned by user"), NOT the
67
+ per-binary-path ACL described in A39. A39 specifies
68
+ `SecAccessCreateWithOwnerAndACL` scoped to the agent binary path; this requires
69
+ the macOS `Security.framework` native binding (not exposed via the `security`
70
+ CLI). F-1 ships the multi-backend abstraction; F-2 layers the scoped ACL on top
71
+ via a native module. Same-uid attacker can still read keychain entries on
72
+ macOS with the default ACL. Documented as a follow-up in NEXT.md.
73
+
74
+ - The hardware-enclave and cloud-KMS backends are explicit stubs that return
75
+ `false` from their availability probes. They never fire in F-1; everyone runs
76
+ on keychain or env-passphrase. F-2+ implements detection + key wrapping for
77
+ TPM 2.0 and the three cloud providers.
78
+
79
+ - The flatfile is named `<stateDir>/remediation-keys.age` for forward
80
+ compatibility, but uses Node's built-in AES-256-GCM (scrypt KDF), NOT the `age`
81
+ format. Operators expecting `age decrypt` to work on the file will be surprised.
82
+ The file's JSON payload self-identifies (`version: 1, kdf: 'scrypt'`); the
83
+ naming is documented in the source comment block.
84
+
85
+ - The fresh-install branch mints the install nonce unconditionally when no
86
+ masters exist. A malicious deletion of all keychain entries would let an
87
+ attacker force the vault to mint a NEW install nonce, invalidating all
88
+ previously-signed tokens — but this is the documented A39/A51 recovery shape
89
+ (rotate-install-nonce CLI), not a vulnerability. Callers that need to detect
90
+ "this is supposed to be an existing install" pass `freshInstallGate: false`,
91
+ which refuses to mint.
92
+
93
+ ## 3. Level-of-abstraction fit
94
+
95
+ **Is this at the right layer?**
96
+
97
+ Yes. This module is the cryptographic primitive layer — a pure HKDF derivation
98
+ over a deterministic secret store. It has no awareness of Remediator concepts
99
+ (runbooks, probes, surfaces). Higher-level F-PRs (F-2 capability-token issuer,
100
+ F-3 probe registry, F-4 in-flight lockfile authority) compose this primitive
101
+ into their own authority paths.
102
+
103
+ The 4-backend abstraction deliberately reuses the pattern from
104
+ `src/core/WorktreeKeyVault.ts` (per A39's prior-art note) rather than
105
+ introducing a parallel secret-storage system. The two vaults share the
106
+ keychain-CLI wrappers (`security` on macOS, `secret-tool` on Linux) and the
107
+ flatfile + scrypt + AES-GCM pattern, but are otherwise independent — they store
108
+ different key material under different keychain services
109
+ (`instar.parallel-dev` vs `ai.instar.remediation`).
110
+
111
+ ## 4. Signal vs authority compliance
112
+
113
+ **Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
114
+
115
+ **Does this change hold blocking authority with brittle logic?**
116
+
117
+ - [ ] No — this change produces a signal consumed by an existing smart gate.
118
+ - [x] No — this change has no block/allow surface.
119
+ - [ ] Yes — but the logic is a smart gate with full conversational context.
120
+ - [ ] Yes, with brittle logic — STOP.
121
+
122
+ `RemediationKeyVault` is a cryptographic primitive. It does not classify
123
+ messages, gate dispatch, or filter actions. The only "refusals" it makes are:
124
+
125
+ 1. Backend unavailable + no fallback configured → throw (operator-visible
126
+ configuration error, not a signal-vs-authority decision).
127
+ 2. Partial-master state → throw (state-integrity refusal; the keychain has been
128
+ tampered with or a write crashed mid-flight).
129
+ 3. Install-nonce missing on a pre-existing install → throw per A51 (this is the
130
+ "fail-closed" leg of the documented recovery shape).
131
+
132
+ None of these are heuristic message classifiers; they are deterministic checks
133
+ on persisted state. The signal-vs-authority principle applies to higher-level
134
+ F-PRs that consume the derived leaf keys (capability-token verification,
135
+ probe-event signature verification, etc.). Those are out of scope for F-1.
136
+
137
+ ## 5. Interactions
138
+
139
+ **Does this interact with existing checks, recovery paths, or infrastructure?**
140
+
141
+ - **Shadowing:** No existing path uses `ai.instar.remediation.*` keychain
142
+ entries; this is a fresh namespace. The vault is a peer of, not a successor
143
+ to, `WorktreeKeyVault`.
144
+ - **Race with adjacent cleanup:** `rotateInstallNonce` overwrites the in-memory
145
+ nonce in place to keep references consistent. A concurrent
146
+ `deriveLeafKey` call mid-rotation will observe either the old or new nonce
147
+ (Node single-threaded JS — no torn reads on the Buffer copy). Downstream
148
+ callers needing strict ordering must serialize their rotation through a
149
+ promise lock at their level. F-2 owns this concern.
150
+ - **Heartbeat / lifecycle interaction:** None. The vault does not own any
151
+ timers, intervals, or fs watchers.
152
+
153
+ ## 6. External surfaces
154
+
155
+ **Does this change anything visible to other agents, other users, other systems?**
156
+
157
+ - **Keychain entries added:** `ai.instar.remediation.install-nonce`,
158
+ `ai.instar.remediation.capability`, `ai.instar.remediation.probe`,
159
+ `ai.instar.remediation.inflight`, `ai.instar.remediation.ledger`,
160
+ `ai.instar.remediation.audit`. Six entries per machine. Users may see these
161
+ in `Keychain Access.app`. They are only created when a downstream consumer
162
+ (F-2+) instantiates the vault — F-1 alone does not seed them outside of test
163
+ runs (tests use the env-passphrase flatfile via `tmpdir`).
164
+ - **Flatfile path:** `<stateDir>/remediation-keys.age` when env-passphrase mode
165
+ is active. `0600` permissions. The `.age` suffix is forward-compat naming;
166
+ the inner payload is Node-native AES-GCM.
167
+ - **Env var:** `INSTAR_REMEDIATION_KEY_PASSPHRASE`. Read at vault load time.
168
+ Documented in the source.
169
+
170
+ ## 7. Rollback cost
171
+
172
+ **If this turns out wrong in production, what's the back-out?**
173
+
174
+ F-1 ships an unused module. No runtime path consumes it yet. Rollback is
175
+ `git revert` on the F-1 PR, no data migration, no agent state repair. The six
176
+ keychain entries (when minted by tests on a developer's machine) can be left
177
+ in place; they are inert without a consumer.
178
+
179
+ When F-2+ ships and consumers depend on the vault, rollback cost increases:
180
+ in-flight tokens signed by the F-1 leaves become unverifiable, and rotation
181
+ overlap windows owned by each surface absorb the disruption. F-1's rollback
182
+ cost is bounded by "delete six keychain entries"; downstream F-PRs carry
183
+ their own rollback artifacts.
184
+
185
+ ---
186
+
187
+ ## Spec anchor
188
+
189
+ This change implements amendments **A20** (key segregation), **A23** (replay
190
+ defense via the install-nonce in derivation), **A39** (per-runbook/per-surface
191
+ leaf keys), **A42** (counter persistence is downstream — F-1 only provides the
192
+ leaf-key surface used by the audit-token writer), **A51** (install nonce sealed
193
+ in keychain, NOT a flatfile under the OS-keychain backend), **A54** (HKDF info
194
+ field with fixed-length context tag + length-prefixed scopeId), **A58** (4-backend
195
+ matrix), and **A62** (operating-state matrix for fail-closed behavior).
@@ -0,0 +1,130 @@
1
+ # Side-Effects Review — F-2: Redactor + ErrorCodeExtractor
2
+
3
+ **Version / slug:** `f2-redactor-errorcode-extractor`
4
+ **Date:** `2026-05-13`
5
+ **Author:** `echo`
6
+ **Second-pass reviewer:** `not required`
7
+
8
+ ## Summary of the change
9
+
10
+ Adds two new foundation modules for the Self-Healing Remediator v2 (per spec §A1 Foundation manifest, F-2):
11
+
12
+ - `src/monitoring/Redactor.ts` — centralized redaction pipeline that strips secrets, identifiers, and absolute paths from text before it crosses any persistence, alert, or LLM-prompt boundary. Default rules cover home-dir paths, bearer/Telegram tokens, emails, UUIDs, long hex, IPv4/IPv6, and ≥6-digit numeric IDs. Composable via `extraRules`.
13
+ - `src/monitoring/ErrorCodeExtractor.ts` — extracts a canonical `errorCode` from one of four structured sources and stamps a `provenance` tag (`native-binding | probe-id | subsystem-explicit | free-text`). Implements §A6: the runbook registry validator can call `isAllowedForRunbookMatch` to refuse matchers that would consume free-text-provenance events.
14
+
15
+ Tests: `tests/unit/Redactor.test.ts` (25 cases) + `tests/unit/ErrorCodeExtractor.test.ts` (21 cases). No consumer code yet — F-3 (DegradationReporter migration) and W-* runbook wrappers wire these up.
16
+
17
+ Files touched:
18
+ - `src/monitoring/Redactor.ts` (new)
19
+ - `src/monitoring/ErrorCodeExtractor.ts` (new)
20
+ - `tests/unit/Redactor.test.ts` (new)
21
+ - `tests/unit/ErrorCodeExtractor.test.ts` (new)
22
+ - `upgrades/NEXT.md` (entry append)
23
+
24
+ ## Decision-point inventory
25
+
26
+ - `ErrorCodeExtractor.isAllowedForRunbookMatch` — **add** — gating predicate the registry-load-time validator (built in a later PR) calls to refuse matchers against free-text-provenance events. The predicate itself is pure and side-effect-free.
27
+ - `Redactor.redact` / `Redactor.redactFields` — **add** — content transformation, no block/allow surface. Used by callers to sanitize before crossing trust boundaries.
28
+ - `ErrorCodeExtractor.extract` priority ladder — **add** — chooses which input source becomes the `errorCode` for a NormalizedDegradationEvent. Unverified probe emissions are silently demoted (signal source is untrusted) rather than producing a probe-id provenance.
29
+
30
+ ---
31
+
32
+ ## 1. Over-block
33
+
34
+ **What legitimate inputs does this change reject that it shouldn't?**
35
+
36
+ - `Redactor.redact` may over-redact in two cases: (a) a legitimate ≥6-digit number (e.g. an HTTP status code referenced as a literal `200000`) collapses to `<NUM>` — acceptable because run-of-the-mill error text rarely contains literal big numbers, and false-positive redaction is preferable to leaking a real ID; (b) a real path on disk that happens to look like `/Users/<word>` always redacts, even when no user data is in it. Both are intentional: the redactor is the brittle low-level signal, not the smart authority — false positives are safe, false negatives are not.
37
+ - `ErrorCodeExtractor.extract` does not "reject" any input — it always returns a result. The `isAllowedForRunbookMatch` predicate rejects free-text provenance, which is the entire point of §A6. No legitimate runbook should match on free-text by design (matchers must consume structured sources).
38
+
39
+ ---
40
+
41
+ ## 2. Under-block
42
+
43
+ **What failure modes does this still miss?**
44
+
45
+ - `Redactor` cannot redact what it doesn't pattern-match. Novel secret shapes (a new vendor's API-key format, an unusual UUID variant, base64-only tokens without a `Bearer` prefix) will pass through. Mitigation: `extraRules` lets callers extend per-surface; the F-3 DegradationReporter migration will set a baseline set of patterns; SystemReviewer corpus tests (later phases) catch drift.
46
+ - `ErrorCodeExtractor` will return `provenance: 'free-text'` + `UNKNOWN_ERROR` for any novel failure shape. That's the intended steady state — A26 NovelFailureReviewer clusters these and proposes new runbooks via the /instar-dev path. The under-block here is not a bug; it's the trigger for SystemReviewer.
47
+ - Probe-id provenance is gated on a caller-supplied `verifyProbeSignature` function. If a caller forgets to pass one, the probe emission silently demotes to lower-priority sources. This is fail-closed (an attacker can't shape errorCode by spoofing the probe path) but a caller could mistakenly believe probe emissions are honored when they aren't. F-3 / W-* PRs that wire this up will own the verifier.
48
+
49
+ ---
50
+
51
+ ## 3. Level-of-abstraction fit
52
+
53
+ **Is this at the right layer?**
54
+
55
+ `Redactor` is a **low-level detector** — it produces sanitized text plus a count summary. It does NOT decide whether to alert, persist, or drop an event. That authority belongs to DegradationReporter (F-3) and downstream consumers. Correct level.
56
+
57
+ `ErrorCodeExtractor` is also a **low-level extractor** — pure function over a structured input. It does NOT decide what to do with the extracted code. The `isAllowedForRunbookMatch` predicate is a signal the runbook registry validator (a smart authority, built in a later PR) calls — the extractor itself has no block/allow surface on the event path. Correct level.
58
+
59
+ Neither module re-implements anything that exists today: there is no shared redactor in src/ to replace (the spec §A1 explicitly notes "v1 was never built"), and ErrorCodeExtractor is genuinely new.
60
+
61
+ ---
62
+
63
+ ## 4. Signal vs authority compliance
64
+
65
+ **Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
66
+
67
+ **Does this change hold blocking authority with brittle logic?**
68
+
69
+ - [x] No — this change produces a signal consumed by an existing smart gate.
70
+
71
+ `Redactor` produces sanitized strings + a redaction summary — pure transformation, no block/allow. Callers (DegradationReporter F-3, runbook auditors, SystemReviewer) own any decisions.
72
+
73
+ `ErrorCodeExtractor.extract` is pure extraction. `isAllowedForRunbookMatch` is a predicate the runbook **registry validator** (a smart authority with full context — sees the entire matcher definition, the event corpus, and the runbook lifecycle state) calls. The extractor publishes the signal (the provenance tag); the validator decides whether to load the runbook. This is exactly the signal-vs-authority split §A6 requires.
74
+
75
+ ---
76
+
77
+ ## 5. Interactions
78
+
79
+ **Does this interact with existing checks, recovery paths, or infrastructure?**
80
+
81
+ - **Shadowing:** none today — these modules have no callers in this PR. F-3 will wire `DegradationReporter` to use them; until then they're dead code, by spec design (foundation must land before any wrapper).
82
+ - **Double-fire:** none — pure functions, no side effects.
83
+ - **Races:** none — no shared mutable state. Each `Redactor` instance owns its rule list; `ErrorCodeExtractor` is static.
84
+ - **Feedback loops:** none — these modules don't write to disk, network, or any other surface that could feed back.
85
+ - **Adjacent cleanup:** the existing `DegradationReporter` (untouched in this PR per F-3 scope boundary) continues to produce its legacy `DegradationEvent` shape. F-3 will introduce the back-compat shim per §A33; until then there's zero behavioral change for any current degradation emit-site.
86
+
87
+ ---
88
+
89
+ ## 6. External surfaces
90
+
91
+ **Does this change anything visible outside the immediate code path?**
92
+
93
+ - **Other agents on the same machine:** no — no shared state.
94
+ - **Other users of the install base:** no — modules are not wired into any runtime path in this PR. Behavior on `main` after merge is unchanged for end users.
95
+ - **External systems:** no.
96
+ - **Persistent state:** no — these modules don't read or write any file. (Future callers in F-3 will, but not these modules themselves.)
97
+ - **Timing or runtime conditions:** no — pure synchronous functions, no clocks.
98
+ - **API surface changes:** two new exported modules under `src/monitoring/`. They appear in the public surface of the package, but with no consumers in this PR, no migration risk.
99
+
100
+ ---
101
+
102
+ ## 7. Rollback cost
103
+
104
+ **If this turns out wrong in production, what's the back-out?**
105
+
106
+ - Pure code addition, no consumers. Revert = delete files + revert the NEXT.md entry. No persistent state, no user-visible regression during rollback window. No agent state repair needed.
107
+ - If a default redaction rule produces a false positive that surprises a downstream caller in F-3+, the fix is to refine the regex in `Redactor.ts` and ship a patch. The redactor's API (the `RedactionRule` type + the `redact`/`redactFields` shape) is stable and the cost of a rule tweak is one PR.
108
+ - If a free-text pattern in `ErrorCodeExtractor` produces a misclassification, downstream consumers see `provenance: 'free-text'` and the runbook registry refuses any matcher that would have fired on it. The misclassification surfaces as a clustering signal in SystemReviewer (a later phase), not as a misfired runbook. Fail-safe by design.
109
+
110
+ ---
111
+
112
+ ## Conclusion
113
+
114
+ Pure-foundation PR with two new modules and 46 passing unit tests. No consumers wired in this PR (per spec §A1 ordering: F-2 must land before F-3, and F-3 owns the wiring). Signal-vs-authority compliance is straightforward — both modules are detectors/extractors with no blocking surface; the §A6 provenance gate is a signal consumed by a smart authority (the runbook registry validator) that will be built in a later PR.
115
+
116
+ Clear to ship.
117
+
118
+ ---
119
+
120
+ ## Second-pass review (if required)
121
+
122
+ Not required — this PR has no block/allow surface on outbound/inbound messaging, no session lifecycle touch, no context-exhaustion handling, no coherence/idempotency/trust gates, and is not a sentinel/guard/gate/watchdog.
123
+
124
+ ---
125
+
126
+ ## Evidence pointers
127
+
128
+ - `npx vitest run tests/unit/Redactor.test.ts tests/unit/ErrorCodeExtractor.test.ts` → 46 passed, 0 failed (run during build).
129
+ - `npx tsc --noEmit` → clean.
130
+ - Spec section references: `docs/specs/SELF-HEALING-REMEDIATOR-V2-SPEC.md` §A1 (foundation manifest F-2 ordering), §A6 (errorCode provenance), §A26 (Redactor consumer in NovelFailureReviewer), §A33 (DegradationReporter migration — out of scope, for F-3).
@@ -0,0 +1,105 @@
1
+ # Side-Effects Review — F-3: DegradationReporter normalization shim
2
+
3
+ **Version / slug:** `f3-degradation-reporter-shim`
4
+ **Date:** `2026-05-13`
5
+ **Author:** `echo`
6
+ **Second-pass reviewer:** `not required`
7
+
8
+ ## Summary of the change
9
+
10
+ Ships the F-3 milestone of the Self-Healing Remediator v2 foundation (per `docs/specs/SELF-HEALING-REMEDIATOR-V2-SPEC.md` §A5, §A33, §A50): an **additive, back-compat shim** on `DegradationReporter` that introduces the `NormalizedDegradationEvent` contract, a `reportStructured()` go-forward emit API, a `setRemediator()` hook for the future F-8 dispatcher, and a durable `RestartPending` queue at `<stateDir>/remediation/degradations-queue.jsonl` (1000 entries / 5 MiB cap, drop-and-counter on overflow). All ~103 existing legacy `.report(...)` callers continue to work unchanged; they are normalized internally to `provenance: 'free-text'` and (per §A6) cannot match any runbook prefilter — they will route to `no-matching-runbook` once F-8 ships.
11
+
12
+ A new warning-only lint at `scripts/lint-degradation-emit-sites.js` catalogues every emit site (legacy vs structured) without blocking. F-8 may upgrade it to blocking once a deprecation timeline is agreed.
13
+
14
+ Files touched:
15
+ - `src/monitoring/DegradationReporter.ts` (modify — adds `NormalizedDegradationEvent`, `RemediatorLike`, `reportStructured()`, `setRemediator()`, `_normalize()`, `_setRestartPending()`, `_readRestartPendingQueue()`, `_getQueueDropCount()`, plus durable-queue plumbing)
16
+ - `scripts/lint-degradation-emit-sites.js` (new — warning-only)
17
+ - `tests/unit/degradation-reporter.test.ts` (extend — 9 new tests for the F-3 shim)
18
+ - `upgrades/NEXT.md` (append F-3 entry)
19
+
20
+ ## Decision-point inventory
21
+
22
+ - `DegradationReporter.report` — **modify** — legacy entry point. New behaviour: routes to Remediator (if wired) OR to RestartPending queue (if flag set) OR to legacy alert path (default). Backward-compat: when no Remediator and no RestartPending, behaviour is byte-identical to pre-F-3.
23
+ - `DegradationReporter.reportStructured` — **add** — new emit API for go-forward callers. No legacy alert path — structured callers MUST wire a Remediator (F-8) or their events are recorded-only.
24
+ - `DegradationReporter.setRemediator` — **add** — registration hook for the F-8 dispatcher. No consumer wired in this PR.
25
+ - `DegradationReporter._setRestartPending` — **add** — supervisor-controlled flag that re-routes events to the durable queue. The flag itself is the authority; no brittle inference logic.
26
+ - `DegradationReporter._normalize` — **add** — pure transform from legacy → normalized. Always tags `provenance: 'free-text'`. §A6 already forbids matchers against free-text-provenance events; no new authority is added by the normalization.
27
+ - `scripts/lint-degradation-emit-sites.js` — **add** — warning-only catalogue. Exits 0 always.
28
+
29
+ ---
30
+
31
+ ## 1. Over-block
32
+
33
+ **What legitimate inputs does this change reject that it shouldn't?**
34
+
35
+ - The change has no block/allow surface on the emit path itself — legacy `.report()` still always succeeds in recording an event. The only "rejection" is the queue cap: events emitted during RestartPending past entry 1000 or byte 5 MiB are silently dropped and counted. That's per spec §A5 (drop-and-counter on overflow) and is by design: the alternative (unbounded queue growth during a long restart-pending window) is worse. Operators see the loss via `_getQueueDropCount()` and the sidecar file `degradations-queue.jsonl.drops.json`.
36
+ - `reportStructured()` does NOT fall through to the legacy alert path. Structured callers without a wired Remediator will see their events recorded but not alerted. This is intentional per §A33: legacy alert side-channel is for legacy callers; structured callers belong to the Remediator path.
37
+
38
+ ---
39
+
40
+ ## 2. Under-block
41
+
42
+ **What failure modes does this still miss?**
43
+
44
+ - Legacy callers continue to land at `no-matching-runbook` because their provenance is `free-text` — per §A6 this is the intended steady state, not a gap. The under-block here is exactly what NovelFailureReviewer (A26 — not in this PR) is designed to consume: cluster the unmatched events and propose new runbooks.
45
+ - The `_normalize` errorCode extraction inherits the F-2 free-text regex set. Novel error-text shapes return `LEGACY_DEGRADATION` rather than a more specific code; NovelFailureReviewer (later PR) is the smart layer that proposes new runbooks for those.
46
+ - RestartPending replay does NOT enforce the §A30 5-second wall-time cap or coalescing logic — that authority belongs to the F-8 Remediator dispatcher, which sees the replayed events one by one. F-3 only provides the durable surface.
47
+ - The queue file is per-machine and not synced. If a machine restart-pending-windows and the agent's state-dir is on a different machine after rebalancing, the queue is lost. Acceptable: RestartPending is a process-lifetime concept tied to the lifeline supervisor that owns the file.
48
+
49
+ ---
50
+
51
+ ## 3. Level-of-abstraction fit
52
+
53
+ **Is this at the right layer?**
54
+
55
+ `DegradationReporter` is the right layer for the shim. It's the single emit point for every degradation in the codebase; placing the normalize/dispatch wiring elsewhere would require touching all ~103 emit sites today instead of zero. The shim preserves the legacy emit API exactly and lets the migration be incremental — which is the precise intent of §A33 / §A50.
56
+
57
+ The RestartPending queue lives inside `DegradationReporter` rather than a separate `RestartPendingQueue` module because (a) only this reporter feeds it, (b) the durability story is local to the reporter's stateDir, and (c) F-8 will own the higher-layer replay scheduler. Pulling it out to its own module now would over-fit; pulling it out later if F-8 needs cross-reporter queue management is a refactor with no behaviour change.
58
+
59
+ The lint is at the right layer (a repo-level grep), produces a single warning-only signal, and explicitly defers authority to F-8 — that matches signal-vs-authority.
60
+
61
+ ---
62
+
63
+ ## 4. Signal vs authority compliance
64
+
65
+ **Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
66
+
67
+ **Does this change hold blocking authority with brittle logic?**
68
+
69
+ - [x] No — this change has no block/allow surface.
70
+
71
+ The shim is a routing change, not a decision point. `_normalize()` is a pure transform. `setRemediator()` is a registration hook. `_setRestartPending()` is set by the lifeline supervisor (the higher-layer authority), not inferred by the reporter. The lint script is warning-only and exits 0 always. The §A6 free-text provenance contract IS an authority — refusing runbook matchers — but that authority lives in the runbook registry validator (F-8), not here. F-3 only stamps the provenance; F-8's smart gate enforces the refusal.
72
+
73
+ ---
74
+
75
+ ## 5. Interactions
76
+
77
+ **Does this interact with existing checks, recovery paths, or infrastructure?**
78
+
79
+ - **Shadowing:** The existing legacy alert path (`reportEvent`) still runs when no Remediator is wired. When a Remediator IS wired, the legacy alert path is BYPASSED for new events — that's the point of A33's "the legacy Telegram-alert path fires only on `no-matching-runbook`." F-3 wires the bypass; F-8 wires the round-trip back to `no-matching-runbook → legacy alert`.
80
+ - **Self-heal interaction:** The existing `registerHealer` / `attemptSelfHeal` flow runs inside `reportEvent`. When a Remediator IS wired, healers are NOT invoked — the Remediator is expected to own the heal-or-alert decision. This is acceptable for the F-3 PR because no Remediator is wired today; healers continue to run on every emit. F-8's design must explicitly cover healer composition with runbooks (it does — A50 places healer-vs-runbook precedence on the Remediator side).
81
+ - **Persistence:** The legacy `degradations.json` file continues to receive every event for the health-check API. The new queue file (`remediation/degradations-queue.jsonl`) is independent — events under RestartPending are recorded in both. No double-dispatch risk: queued events flow through replay → dispatch, never through the legacy alert path twice.
82
+ - **Drop-counter sidecar:** `degradations-queue.jsonl.drops.json` is the only new file outside the reporter's `degradations.json`. Backup/sync rules (per A35): the queue lives under `.instar/remediation/` which F-7 will add to `BackupManager`'s exclusion list (per-machine). No backup-sync changes ship in this PR.
83
+
84
+ ---
85
+
86
+ ## 6. External surfaces
87
+
88
+ **Does it change anything visible to other agents, other users, other systems?**
89
+
90
+ - **Behavioural surface:** Zero change for current users. No Remediator is wired in F-3, so every legacy `.report()` call follows the same code path it did pre-F-3. No new files exist on disk until F-8 sets RestartPending, which it cannot yet.
91
+ - **Type surface:** `NormalizedDegradationEvent` and `RemediatorLike` are exported from `src/monitoring/DegradationReporter.ts`. They are additive — existing consumers' imports remain valid. The exported `DegradationEvent` shape is unchanged.
92
+ - **CLI / config / endpoint surface:** None changed.
93
+ - **Tests:** 9 new unit tests under the existing file. No changes to existing tests.
94
+ - **Lint behaviour:** The new lint script is not wired into any existing gate (pre-commit / pre-push / CI). It is a documented manual command per §A33: `node scripts/lint-degradation-emit-sites.js`. Adding it to CI is explicitly deferred to F-8.
95
+
96
+ ---
97
+
98
+ ## 7. Rollback cost
99
+
100
+ **If this turns out wrong in production, what's the back-out?**
101
+
102
+ - **Revert path:** This is a single-PR shim with no consumer (no Remediator wired anywhere). A `git revert <merge-commit>` followed by a patch release rolls back the entire change with no data migration. The `degradations.json` file (legacy) is untouched.
103
+ - **Stuck-state risk:** If F-8 ships partial Remediator wiring and is reverted, any events accumulated in `<stateDir>/remediation/degradations-queue.jsonl` during a RestartPending window would be orphaned. Mitigation: F-3 has no way to set RestartPending (no caller wires it), so the queue file cannot be populated until F-8 ships. F-8's own rollback story must include a queue-drain step.
104
+ - **Compat risk for downstream consumers:** Code outside this PR that imports `DegradationEvent` continues to compile unchanged. Code that imports the new `NormalizedDegradationEvent` would need to bridge to the legacy type — but no such code exists in this PR or on main today.
105
+ - **Worst-case incident:** A bug in `_normalize` could break the legacy `.report()` path. The integration test for "with no remediator set, legacy alert path runs unchanged" catches the obvious regression; a more exotic bug (e.g. Redactor crash) would be caught by the existing legacy tests because `_normalize` is invoked in every `.report()` call. If it slipped through, the back-out is a one-line revert in the `report()` method to skip the normalize-and-route block.
@@ -0,0 +1,117 @@
1
+ # Side-Effects Review — F-4: MachineLock + IntentJournal + audit infrastructure
2
+
3
+ **Version / slug:** `f4-machine-locks-intent-journal-audit`
4
+ **Date:** `2026-05-13`
5
+ **Author:** `echo`
6
+ **Second-pass reviewer:** `not required`
7
+
8
+ ## Summary of the change
9
+
10
+ Foundation Tier-1 primitives for the Self-Healing Remediator v2 spec. Four new modules under `src/remediation/`:
11
+
12
+ - `src/remediation/MachineLock.ts` — In-flight HMAC-locked tuple coordination + heartbeat + SIGKILL-grace stale-reclamation.
13
+ - `src/remediation/IntentJournal.ts` — Append-only intent declaration log.
14
+ - `src/remediation/audit/AuditWriter.ts` — Verified audit-projection append path with token + watermark gating.
15
+ - `src/remediation/audit/AuditProjection.ts` — Read view exposing `Map<runbookId, AuditEntry[]>`.
16
+
17
+ This PR ships the primitives ONLY. No surface (memory-healer, supervisor preflight, delivery-retry, db-corruption) wires into them in this PR. The dispatcher (F-8), runbooks (W-*), and the primary-aggregator lease (A47, Tier-3 scope) consume these in subsequent PRs.
18
+
19
+ The change is foundational infrastructure: it adds capability surface, it does not remove or replace any existing decision point.
20
+
21
+ ## Decision-point inventory
22
+
23
+ - `MachineLock.acquireInFlight()` — **add** — gates whether a tuple is currently being healed; rejects concurrent acquisition until stale (A63) or released.
24
+ - `MachineLock.listInFlight()` — **add** — read primitive; filters out forged lockfiles via HMAC.
25
+ - `AuditWriter.append()` — **add** — gates whether an audit entry is persisted to the projection vs. quarantined to `audit-rejected.jsonl` (token-verify + A42 watermark).
26
+
27
+ All three decision points operate on cryptographic primitives (HMAC verify, monotonic watermark) provided by the caller. They do not contain content classification, heuristics, or "guess intent from text" logic.
28
+
29
+ ---
30
+
31
+ ## 1. Over-block
32
+
33
+ **What legitimate inputs does this change reject that it shouldn't?**
34
+
35
+ - `MachineLock.acquireInFlight()` rejects a second attempt against the same `tupleHash` while the first lock is active. This is the intended contract (A2 lock-bound co-existence) — a second concurrent attempt should be observed as `covered-by-inline` by the caller, not coalesce. Legitimate use is one-attempt-at-a-time per tuple; concurrent attempts are *exactly* what the lock exists to prevent.
36
+ - `AuditWriter` rejects entries whose `auditToken` doesn't verify. By design — forging the token forges authority. Production callers obtain the token from `RemediationContext`, which is issued by the dispatcher at attempt-start.
37
+ - `AuditWriter` rejects entries whose `timestamp < highWatermark` for the same `(subsystem, attemptId)` pair. A legitimate use case for backdated entries doesn't exist in this design — per-attempt audit is forward-monotonic. If a real backdated write becomes a requirement, the design pivots from "drop" to "queue-for-reconciliation"; that's a future amendment, not a bug in this PR.
38
+
39
+ ---
40
+
41
+ ## 2. Under-block
42
+
43
+ **What failure modes does this still miss?**
44
+
45
+ - **Cross-machine collision on `tupleHash`.** The lock is per-machine. Two machines healing the same tuple are not coordinated by F-4. The cross-process attempt ledger (A7, F-2) and the primary-aggregator lease (A47, Tier-3) are the cross-machine coordinations; F-4 is intentionally scoped to single-machine in-flight.
46
+ - **Forged-lock-with-correct-HMAC.** An attacker with the leaf key can forge a lockfile that passes verification. The mitigation lives in `RemediationKeyVault` (F-1): per-context, per-scope leaf-key derivation from OS-keychain-sealed master + install nonce (A20/A39/A51). F-4 trusts the caller-supplied verifier; key-management is F-1's job.
47
+ - **Slow-but-alive surface vs. crashed surface.** A surface whose heartbeats are landing but whose work is genuinely stuck would NOT be reclaimed — the stale-reclamation logic deliberately requires BOTH grace clauses (A63). The Remediator-side deadline (A4, F-8) is the second line of defense; it's not F-4's concern.
48
+ - **Audit-rejected flood from a compromised peer.** If a forger spams `append()` with bad tokens, `audit-rejected.jsonl` grows unbounded. A27/A49's rate-cap + first-5/last-5 forensic flood-summary handles this; that wraps the write path in F-2's `RemediationGc` rather than living inside `AuditWriter`. F-4 produces the durable forensic record; F-2 trims it.
49
+
50
+ ---
51
+
52
+ ## 3. Level-of-abstraction fit
53
+
54
+ **Is this at the right layer?**
55
+
56
+ Yes. These are *primitives*. They expose:
57
+ - A coordination primitive (`MachineLock`) — synchronous semantics, no policy.
58
+ - A durable-witness primitive (`IntentJournal`) — append + read.
59
+ - An authorisation primitive (`AuditWriter` + `AuditProjection`) — verify-and-persist, no semantic interpretation.
60
+
61
+ Policy (when to acquire, when to declare, when to write) lives in the dispatcher (F-8). Key management lives in `RemediationKeyVault` (F-1). The split is the spec-mandated layering.
62
+
63
+ No higher-level gate is being shadowed. The closest prior art (`CoordinationProtocol.ts`) operates over `AgentBus` for cross-machine work coordination — a different concern. F-4's spec-level decision (A56) defers reuse of `CoordinationProtocol`'s lease channel to the Tier-3 primary-aggregator PR; this PR introduces no parallel lease file.
64
+
65
+ ---
66
+
67
+ ## 4. Signal vs authority compliance
68
+
69
+ **Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
70
+
71
+ **Does this change hold blocking authority with brittle logic?**
72
+
73
+ - [x] No — this change produces a signal consumed by an existing smart gate.
74
+ - [x] No — this change has no block/allow surface beyond crypto-verified primitives.
75
+ - [ ] Yes — but the logic is a smart gate with full conversational context.
76
+ - [ ] ⚠️ Yes, with brittle logic — STOP.
77
+
78
+ The "blocks" in this change are HMAC verification and monotonic counter checks. Both are precise, deterministic primitives — not heuristic detectors. The signal-vs-authority principle is about brittle string-matching / pattern detectors holding blocking power; cryptographic verification is the canonical *correct* place to block. The blocking decisions here (forged token → rejected, stale lock → reclaim) are exactly the kind of authority that *should* be hard-coded.
79
+
80
+ The smart gate (Remediator dispatcher in F-8) is the consumer that decides *whether to attempt* a heal given the lock state; F-4 only exposes the state.
81
+
82
+ ---
83
+
84
+ ## 5. Interactions
85
+
86
+ **Does this interact with existing checks, recovery paths, or infrastructure?**
87
+
88
+ - **Shadowing:** None. No existing audit path or lockfile primitive in `src/remediation/` (this is the first module in that directory). Prior art `src/core/CoordinationProtocol.ts` is for cross-machine work avoidance, not in-flight remediation tuples. Prior art `src/core/WorktreeKeyVault.ts` is a sibling of F-1's `RemediationKeyVault`; both share the keychain-fallback pattern but no decision-point overlap.
89
+ - **Race with adjacent cleanup:** `MachineLock.reclaimStale` renames the lockfile into `orphaned/`. A racing `acquireInFlight` for the same tuple would see the rename happen first (the cache-divergence check stat()s before deciding); the post-reclaim acquire writes a fresh file. If the rename and the next acquire interleave on different machines, the per-machine state-dir scoping prevents cross-host interference. If they interleave on the SAME process via concurrent in-process callers, the second caller's `readVerifiedLock` returns `undefined` (cache invalidated, file just renamed) and falls through to the new-acquire path — both end up writing, the second `fs.renameSync` (atomicWriteLock's rename) is the final state. The atomic-rename guarantees one envelope ends up authoritative; the second caller's `attemptId` may "win" silently. Single-process concurrent acquisition of the same tuple is a contract violation, not a supported use case — the dispatcher serialises by tuple.
90
+ - **Double-fire:** `release()` is idempotent (uses a local `released` flag + ENOENT-tolerant unlink). `heartbeat()` throws after release. `append()` is idempotent only in the sense that duplicate entries land twice; the in-memory tail cap absorbs the bloat. The dispatcher is responsible for at-most-once `append` per state-transition.
91
+
92
+ ---
93
+
94
+ ## 6. External surfaces
95
+
96
+ **Does this change anything visible to other agents, other users, other systems?**
97
+
98
+ - New directories on disk: `~/.instar/machine-locks/in-flight/`, `~/.instar/machine-locks/orphaned/`, `<stateDir>/remediation/`. All created lazily by the modules at first use.
99
+ - New file types: `<tupleHash>.lock`, `intent-journal-<machineId>.jsonl`, `audit-projection-<machineId>.jsonl`, `audit-rejected.jsonl`.
100
+ - Per A14, the `audit-projection-*.jsonl` and `intent-journal-*.jsonl` paths are per-machine, not git-synced, not backed up. F-7 (PostUpdateMigrator) adds the gitignore entries and the backup-exclusion config; this PR doesn't ship that wiring — the files simply won't exist yet on any installation that doesn't enable the Remediator surface.
101
+ - No HTTP routes, no Telegram surfaces, no dashboard tabs, no config-flip entries.
102
+
103
+ ---
104
+
105
+ ## 7. Rollback cost
106
+
107
+ **If this turns out wrong in production, what's the back-out?**
108
+
109
+ Trivially low. No consumers of these modules ship in this PR — `MachineLock`, `IntentJournal`, `AuditWriter`, `AuditProjection` are new symbols not yet imported anywhere in `src/`. A `git revert` removes them with zero state-migration cost. The on-disk files (lock, journal, audit-projection) are not created until first call from a consumer; no installation that doesn't add F-1/F-8 will ever materialise them.
110
+
111
+ If a future consumer-PR exposes a bug in one of these primitives, the back-out path is "revert the consumer PR" — these primitives don't run on their own.
112
+
113
+ ---
114
+
115
+ ## Reviewer concurrence (Phase 5)
116
+
117
+ Not required. This change touches no block/allow surface for messaging, no session lifecycle, no coherence/sentinel/gate authority. It is foundational infrastructure with no live consumers in this PR.