instar 0.28.102 → 0.28.104

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 (255) 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 +109 -1
  48. package/dist/memory/NativeModuleHealer.d.ts.map +1 -1
  49. package/dist/memory/NativeModuleHealer.js +418 -2
  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/TokenLedger.d.ts.map +1 -1
  76. package/dist/monitoring/TokenLedger.js +11 -1
  77. package/dist/monitoring/TokenLedger.js.map +1 -1
  78. package/dist/monitoring/probes/LifelineProbe.d.ts +7 -0
  79. package/dist/monitoring/probes/LifelineProbe.d.ts.map +1 -1
  80. package/dist/monitoring/probes/LifelineProbe.js +7 -0
  81. package/dist/monitoring/probes/LifelineProbe.js.map +1 -1
  82. package/dist/monitoring/probes/__shared.d.ts +50 -0
  83. package/dist/monitoring/probes/__shared.d.ts.map +1 -0
  84. package/dist/monitoring/probes/__shared.js +51 -0
  85. package/dist/monitoring/probes/__shared.js.map +1 -0
  86. package/dist/remediation/IntentJournal.d.ts +64 -0
  87. package/dist/remediation/IntentJournal.d.ts.map +1 -0
  88. package/dist/remediation/IntentJournal.js +118 -0
  89. package/dist/remediation/IntentJournal.js.map +1 -0
  90. package/dist/remediation/MachineLock.d.ts +137 -0
  91. package/dist/remediation/MachineLock.d.ts.map +1 -0
  92. package/dist/remediation/MachineLock.js +314 -0
  93. package/dist/remediation/MachineLock.js.map +1 -0
  94. package/dist/remediation/PrimaryAggregatorLease.d.ts +205 -0
  95. package/dist/remediation/PrimaryAggregatorLease.d.ts.map +1 -0
  96. package/dist/remediation/PrimaryAggregatorLease.js +450 -0
  97. package/dist/remediation/PrimaryAggregatorLease.js.map +1 -0
  98. package/dist/remediation/RemediationContext.d.ts +63 -0
  99. package/dist/remediation/RemediationContext.d.ts.map +1 -0
  100. package/dist/remediation/RemediationContext.js +109 -0
  101. package/dist/remediation/RemediationContext.js.map +1 -0
  102. package/dist/remediation/RemediationKeyVault.d.ts +123 -0
  103. package/dist/remediation/RemediationKeyVault.d.ts.map +1 -0
  104. package/dist/remediation/RemediationKeyVault.js +564 -0
  105. package/dist/remediation/RemediationKeyVault.js.map +1 -0
  106. package/dist/remediation/Remediator.d.ts +288 -0
  107. package/dist/remediation/Remediator.d.ts.map +1 -0
  108. package/dist/remediation/Remediator.js +520 -0
  109. package/dist/remediation/Remediator.js.map +1 -0
  110. package/dist/remediation/RemediatorBootstrap.d.ts +141 -0
  111. package/dist/remediation/RemediatorBootstrap.d.ts.map +1 -0
  112. package/dist/remediation/RemediatorBootstrap.js +255 -0
  113. package/dist/remediation/RemediatorBootstrap.js.map +1 -0
  114. package/dist/remediation/TrustElevationSource.d.ts +153 -0
  115. package/dist/remediation/TrustElevationSource.d.ts.map +1 -0
  116. package/dist/remediation/TrustElevationSource.js +208 -0
  117. package/dist/remediation/TrustElevationSource.js.map +1 -0
  118. package/dist/remediation/audit/AuditProjection.d.ts +37 -0
  119. package/dist/remediation/audit/AuditProjection.d.ts.map +1 -0
  120. package/dist/remediation/audit/AuditProjection.js +118 -0
  121. package/dist/remediation/audit/AuditProjection.js.map +1 -0
  122. package/dist/remediation/audit/AuditWriter.d.ts +79 -0
  123. package/dist/remediation/audit/AuditWriter.d.ts.map +1 -0
  124. package/dist/remediation/audit/AuditWriter.js +132 -0
  125. package/dist/remediation/audit/AuditWriter.js.map +1 -0
  126. package/dist/remediation/channels/CliApprovalChannel.d.ts +37 -0
  127. package/dist/remediation/channels/CliApprovalChannel.d.ts.map +1 -0
  128. package/dist/remediation/channels/CliApprovalChannel.js +45 -0
  129. package/dist/remediation/channels/CliApprovalChannel.js.map +1 -0
  130. package/dist/remediation/channels/TelegramApprovalChannel.d.ts +37 -0
  131. package/dist/remediation/channels/TelegramApprovalChannel.d.ts.map +1 -0
  132. package/dist/remediation/channels/TelegramApprovalChannel.js +45 -0
  133. package/dist/remediation/channels/TelegramApprovalChannel.js.map +1 -0
  134. package/dist/remediation/runbooks/db-corruption.d.ts +86 -0
  135. package/dist/remediation/runbooks/db-corruption.d.ts.map +1 -0
  136. package/dist/remediation/runbooks/db-corruption.js +190 -0
  137. package/dist/remediation/runbooks/db-corruption.js.map +1 -0
  138. package/dist/remediation/runbooks/messaging-delivery-failed.d.ts +108 -0
  139. package/dist/remediation/runbooks/messaging-delivery-failed.d.ts.map +1 -0
  140. package/dist/remediation/runbooks/messaging-delivery-failed.js +172 -0
  141. package/dist/remediation/runbooks/messaging-delivery-failed.js.map +1 -0
  142. package/dist/remediation/runbooks/node-abi-mismatch.d.ts +70 -0
  143. package/dist/remediation/runbooks/node-abi-mismatch.d.ts.map +1 -0
  144. package/dist/remediation/runbooks/node-abi-mismatch.js +216 -0
  145. package/dist/remediation/runbooks/node-abi-mismatch.js.map +1 -0
  146. package/dist/remediation/runbooks/supervisor-preflight.d.ts +114 -0
  147. package/dist/remediation/runbooks/supervisor-preflight.d.ts.map +1 -0
  148. package/dist/remediation/runbooks/supervisor-preflight.js +299 -0
  149. package/dist/remediation/runbooks/supervisor-preflight.js.map +1 -0
  150. package/dist/scheduler/AgentMdAtomicSave.d.ts +85 -0
  151. package/dist/scheduler/AgentMdAtomicSave.d.ts.map +1 -0
  152. package/dist/scheduler/AgentMdAtomicSave.js +169 -0
  153. package/dist/scheduler/AgentMdAtomicSave.js.map +1 -0
  154. package/dist/scheduler/AgentMdReconcile.d.ts +62 -0
  155. package/dist/scheduler/AgentMdReconcile.d.ts.map +1 -0
  156. package/dist/scheduler/AgentMdReconcile.js +212 -0
  157. package/dist/scheduler/AgentMdReconcile.js.map +1 -0
  158. package/dist/scheduler/DisabledBodyDrift.d.ts +70 -0
  159. package/dist/scheduler/DisabledBodyDrift.d.ts.map +1 -0
  160. package/dist/scheduler/DisabledBodyDrift.js +149 -0
  161. package/dist/scheduler/DisabledBodyDrift.js.map +1 -0
  162. package/dist/scheduler/InstallBuiltinJobs.d.ts +53 -0
  163. package/dist/scheduler/InstallBuiltinJobs.d.ts.map +1 -0
  164. package/dist/scheduler/InstallBuiltinJobs.js +212 -0
  165. package/dist/scheduler/InstallBuiltinJobs.js.map +1 -0
  166. package/dist/scheduler/JobLoader.d.ts.map +1 -1
  167. package/dist/scheduler/JobLoader.js +48 -0
  168. package/dist/scheduler/JobLoader.js.map +1 -1
  169. package/dist/scheduler/MigrationInvariants.d.ts +62 -0
  170. package/dist/scheduler/MigrationInvariants.d.ts.map +1 -0
  171. package/dist/scheduler/MigrationInvariants.js +215 -0
  172. package/dist/scheduler/MigrationInvariants.js.map +1 -0
  173. package/dist/scheduler/MigrationLedger.d.ts +84 -0
  174. package/dist/scheduler/MigrationLedger.d.ts.map +1 -0
  175. package/dist/scheduler/MigrationLedger.js +114 -0
  176. package/dist/scheduler/MigrationLedger.js.map +1 -0
  177. package/dist/server/AgentServer.d.ts.map +1 -1
  178. package/dist/server/AgentServer.js +8 -0
  179. package/dist/server/AgentServer.js.map +1 -1
  180. package/dist/server/fileRoutes.d.ts.map +1 -1
  181. package/dist/server/fileRoutes.js +7 -0
  182. package/dist/server/fileRoutes.js.map +1 -1
  183. package/dist/server/routes.d.ts.map +1 -1
  184. package/dist/server/routes.js +141 -0
  185. package/dist/server/routes.js.map +1 -1
  186. package/package.json +3 -2
  187. package/scripts/classify-default-drift.mjs +238 -0
  188. package/scripts/eli16-overview-check.mjs +85 -0
  189. package/scripts/instar-dev-precommit.js +60 -1
  190. package/scripts/lint-degradation-emit-sites.js +116 -0
  191. package/scripts/lint-no-direct-destructive.js +12 -0
  192. package/scripts/protect-migration-guarantee.js +61 -0
  193. package/scripts/regen-default-job-templates.mjs +111 -0
  194. package/scripts/verify-runbook-pr-signature.js +366 -0
  195. package/src/data/builtin-manifest.json +94 -94
  196. package/src/scaffold/bootstrap.ts +138 -0
  197. package/src/scaffold/keys/instar-release-pub.pem +3 -0
  198. package/src/scaffold/templates/jobs/instar/.gitkeep +0 -0
  199. package/src/scaffold/templates/jobs/instar/commitment-detection.md +28 -0
  200. package/src/scaffold/templates/jobs/instar/evolution-overdue-check.md +28 -0
  201. package/src/scaffold/templates/jobs/instar/evolution-proposal-evaluate.md +30 -0
  202. package/src/scaffold/templates/jobs/instar/evolution-proposal-implement.md +25 -0
  203. package/src/scaffold/templates/jobs/instar/health-check.md +14 -0
  204. package/src/scaffold/templates/jobs/instar/identity-review.md +42 -0
  205. package/src/scaffold/templates/jobs/instar/insight-harvest.md +31 -0
  206. package/src/scaffold/templates/jobs/instar/overseer-development.md +23 -0
  207. package/src/scaffold/templates/jobs/instar/overseer-guardian.md +28 -0
  208. package/src/scaffold/templates/jobs/instar/overseer-infrastructure.md +26 -0
  209. package/src/scaffold/templates/jobs/instar/overseer-learning.md +28 -0
  210. package/src/scaffold/templates/jobs/instar/overseer-maintenance.md +27 -0
  211. package/src/scaffold/templates/jobs/instar/reflection-trigger.md +40 -0
  212. package/src/scaffold/templates/jobs/instar/relationship-maintenance.md +16 -0
  213. package/src/scaffold/templates.ts +1616 -0
  214. package/upgrades/0.28.102.md +6 -7
  215. package/upgrades/0.28.103.md +471 -0
  216. package/upgrades/0.28.104.md +77 -0
  217. package/upgrades/side-effects/a47-primary-aggregator-lease.md +82 -0
  218. package/upgrades/side-effects/agentmd-atomic-save.md +66 -0
  219. package/upgrades/side-effects/agentmd-loader-200jobs-perf-benchmark.md +50 -0
  220. package/upgrades/side-effects/agentmd-reconcile-boot-lifecycle.md +66 -0
  221. package/upgrades/side-effects/api-safety-guard.md +80 -0
  222. package/upgrades/side-effects/c1-runbook-pr-gate.md +83 -0
  223. package/upgrades/side-effects/disabled-body-drift-detection.md +59 -0
  224. package/upgrades/side-effects/drift-classifier-release-time.md +71 -0
  225. package/upgrades/side-effects/eli16-overview-required-gate.md +77 -0
  226. package/upgrades/side-effects/f1-remediation-key-vault.md +195 -0
  227. package/upgrades/side-effects/f2-redactor-errorcode-extractor.md +130 -0
  228. package/upgrades/side-effects/f3-degradation-reporter-shim.md +105 -0
  229. package/upgrades/side-effects/f4-machine-locks-intent-journal-audit.md +117 -0
  230. package/upgrades/side-effects/f5-trust-elevation-source.md +258 -0
  231. package/upgrades/side-effects/f6-supervisor-handshake.md +234 -0
  232. package/upgrades/side-effects/f7-post-update-migrator-atomic-step.md +129 -0
  233. package/upgrades/side-effects/f8-remediator-skeleton.md +153 -0
  234. package/upgrades/side-effects/f8-rest-enforcement.md +72 -0
  235. package/upgrades/side-effects/file-viewer-jobs-instar-never-editable.md +41 -0
  236. package/upgrades/side-effects/frontmatter-interpolation-breakout-test.md +48 -0
  237. package/upgrades/side-effects/jobs-as-agentmd-phase-2.md +78 -0
  238. package/upgrades/side-effects/jobs-as-agentmd-phase-3.md +76 -0
  239. package/upgrades/side-effects/jobs-as-agentmd-phase-4-endpoints.md +62 -0
  240. package/upgrades/side-effects/jobs-as-agentmd-phase-5.md +67 -0
  241. package/upgrades/side-effects/jobs-as-agentmd-phase-6.md +53 -0
  242. package/upgrades/side-effects/jobs-endpoints-auth-verification.md +52 -0
  243. package/upgrades/side-effects/migration-guarantee-suite.md +61 -0
  244. package/upgrades/side-effects/migration-runtime-invariant-gate.md +59 -0
  245. package/upgrades/side-effects/migration-telemetry-invariant-8.md +68 -0
  246. package/upgrades/side-effects/npm-pack-templates-smoke.md +51 -0
  247. package/upgrades/side-effects/openclaw-import-before-prompt-build.md +84 -0
  248. package/upgrades/side-effects/openclaw-import-pre-compaction-flush.md +85 -0
  249. package/upgrades/side-effects/prompt-gate-no-prompt-cache.md +258 -0
  250. package/upgrades/side-effects/tier2-degradation-reporter-live-wire.md +146 -0
  251. package/upgrades/side-effects/token-ledger-native-heal.md +136 -0
  252. package/upgrades/side-effects/w1-node-abi-mismatch-runbook.md +154 -0
  253. package/upgrades/side-effects/w2-supervisor-preflight-runbook.md +176 -0
  254. package/upgrades/side-effects/w3-messaging-delivery-failed-runbook.md +185 -0
  255. package/upgrades/side-effects/w4-db-corruption-runbook.md +114 -0
@@ -0,0 +1,129 @@
1
+ # Side-Effects Review — F-7: PostUpdateMigrator atomic-step + announceOnce primitives (Tier-2)
2
+
3
+ **Version / slug:** `f7-post-update-migrator-atomic-step`
4
+ **Date:** `2026-05-13`
5
+ **Author:** `echo`
6
+ **Second-pass reviewer:** `not required`
7
+
8
+ ## Summary of the change
9
+
10
+ Ships F-7 from the Self-Healing Remediator v2 spec (`docs/specs/SELF-HEALING-REMEDIATOR-V2-SPEC.md` §R1 Upgrade invariants + §A35 backup/sync wiring + §A50 hook-shape corrections + §A57 Tier-2 phase manifest).
11
+
12
+ Two new primitives plus the A35 hook-shape changes:
13
+
14
+ 1. **`MigratorStepEngine`** — atomic, idempotent, run-once-per-version migration steps. Steps are registered by name + version. The engine writes a ledger at `<stateDir>/migrator-steps-completed.json` keyed by `<version>:<step-name>`. A step failure records `outcome: 'failed'` and does NOT roll back prior steps or block subsequent steps — each step is self-contained.
15
+
16
+ 2. **`AnnouncementManager`** — `announceOnce(id, message, channel)` primitive. Emits a message exactly once per id, then never again. Ledger at `<stateDir>/announcements-shown.json`. Used for surfacing migration completions and structural-change notices without spamming the user across restarts.
17
+
18
+ 3. **A35 hook-shape — `GitStateManager.DEFAULT_GITIGNORE` extension.** Const literal now contains five remediation runtime path globs (`remediation/system-reviewer-state-*.json`, `remediation/inbox-*.jsonl`, `remediation/audit-projection-*.jsonl`, `remediation/cross-process-attempts-*.jsonl`, `remediation/llm-raw-*.jsonl`). Exported as `REMEDIATION_GITIGNORE_ENTRIES` for the F-7 atomic step to use as the source-of-truth list.
19
+
20
+ 4. **A35 hook-shape — `BackupManager` exclusion list with feature-flag gate.** New const `REMEDIATION_EXCLUDED_PATH_PREFIXES` (exported) — the five remediation path prefixes. New optional constructor arg `isRemediationEnabled?: () => boolean` parallels the existing `isIntegratedBeingEnabled` gate. When the gate is ON, the prefixes drop any user-added `includeFiles` entry that begins with a remediation prefix. When OFF (or absent), the prefixes are inactive — strictly additive, no back-compat break.
21
+
22
+ Files touched:
23
+ - `src/core/MigratorStepEngine.ts` (add)
24
+ - `src/core/PostUpdateMigrator.ts` (modify — add `registerStep` + `runPendingSteps` + lazy engine field; existing constructor + all 15 existing migration methods unchanged)
25
+ - `src/core/GitStateManager.ts` (modify — extend `DEFAULT_GITIGNORE` const literal; export `REMEDIATION_GITIGNORE_ENTRIES`)
26
+ - `src/core/BackupManager.ts` (modify — add `REMEDIATION_EXCLUDED_PATH_PREFIXES` const + export; add 5th optional constructor arg `isRemediationEnabled`; gate logic in `resolveIncludedFiles`)
27
+ - `tests/unit/PostUpdateMigrator-atomicStep.test.ts` (add — 8 cases)
28
+ - `tests/unit/AnnouncementManager.test.ts` (add — 5 cases)
29
+ - `tests/unit/PostUpdateMigrator-a35-remediationPaths.test.ts` (add — 5 cases)
30
+ - `upgrades/NEXT.md` (modify — preserves all existing entries; adds A35 + announceOnce entries)
31
+
32
+ ## Decision-point inventory
33
+
34
+ - `PostUpdateMigrator.registerStep()` — **add** — delegates to lazy `MigratorStepEngine`. Existing 15 `migrate*` methods + `migrate()` orchestrator untouched.
35
+ - `PostUpdateMigrator.runPendingSteps()` — **add** — async; returns `{ steps: [{name, outcome, details}] }`. Never throws.
36
+ - `MigratorStepEngine.registerStep()` — rejects duplicate names, missing version/run.
37
+ - `MigratorStepEngine.runPendingSteps()` — three skip reasons: `already-recorded:<outcome>`, `future-version:<step.version> > <toVersion>`, and explicit `'skipped'` from the step itself. Failed steps record `outcome: 'failed'` and DO NOT retry (operator must clear the ledger entry to retry — surfaces the failure in the report).
38
+ - `MigratorStepEngine.writeLedger()` — atomic temp-file → fsync → rename, `0600` perms.
39
+ - `AnnouncementManager.announceOnce()` — three-input validation (id, message, channel); records BEFORE sink-invoke so a flaky sink cannot cause duplicate emission. Sink throws are swallowed (ledger entry stands).
40
+ - `GitStateManager.DEFAULT_GITIGNORE` const literal — **modify** — append remediation block. Backwards-compatible: previously private const is now exported (no internal call sites broken).
41
+ - `BackupManager` constructor — **modify** — append 5th optional argument. All four existing call sites pass ≤4 args and continue to work.
42
+ - `BackupManager.resolveIncludedFiles()` — **modify** — adds gated exclusion loop. When gate is OFF (the default — every existing caller), behavior is identical to pre-PR.
43
+
44
+ No new HTTP routes. No new daemon processes. No new file types beyond two JSON ledgers in `<stateDir>`.
45
+
46
+ ---
47
+
48
+ ## 1. Over-block
49
+
50
+ **What legitimate inputs does this change reject that it shouldn't?**
51
+
52
+ - **A future-version step is silently skipped.** Intended. The ledger is NOT written when the step's version > `toVersion`, so the next upgrade boundary that crosses the threshold runs the step.
53
+ - **A failed step is NOT auto-retried on the next `runPendingSteps()` invocation.** This could be seen as over-blocking — a transient failure (disk full, network blip) gets recorded as `failed` and stays that way. Trade-off accepted because the alternative (unbounded retry on every update) lets a broken step DoS every release. Operator-facing mitigation: the failed entry surfaces in the migration report; clearing the ledger key allows retry.
54
+ - **The `BackupManager` remediation-prefix gate, when ON, drops includeFiles entries whose paths begin with a remediation prefix.** A user who manually adds `.instar/remediation/system-reviewer-state-mine.json` to `config.backup.includeFiles` will see it silently dropped. Mitigated by: (a) the gate defaults to OFF until remediation is enabled; (b) the prefixes are documented in code + spec; (c) this is the explicit spec requirement (A14 "Backed up? no" rows).
55
+
56
+ No unintended over-blocks.
57
+
58
+ ---
59
+
60
+ ## 2. Under-block
61
+
62
+ **What failure modes does this still miss?**
63
+
64
+ - **No cross-machine coordination of step execution.** Two machines updating to the same version in parallel each run their own steps independently. The ledger is per-machine. This is the correct model for per-machine state (gitignore lives in `<stateDir>/.gitignore` which is per-machine via the `.instar` git repo), but a future cross-machine migration would need additional coordination.
65
+ - **Step `run()` is not deadline-enforced.** A hung step could block subsequent steps. The engine relies on step authors to be well-behaved. Real-world steps are file I/O against `<stateDir>` and complete in <50ms; a step that calls external APIs should add its own timeout. Worst case: the migrator hangs, the next update boundary sees the same situation; the user can `kill` the process and the ledger remains consistent.
66
+ - **The `announceOnce` sink does not retry.** If a sink fails (e.g. Telegram offline), the announcement is recorded as shown but never delivered. This is the deliberate signal-vs-authority separation: the brittle sink layer does not have authority over the ledger. A future enhancement could add a separate delivery queue; F-7 keeps the primitive minimal.
67
+ - **Ledger corruption is treated as empty.** If `migrator-steps-completed.json` becomes corrupt (truncated, bad JSON), the engine falls back to an empty ledger. This is intentional — the alternative (refuse to migrate) would be worse. Cost: a corrupt ledger causes one round of step re-runs; each step is idempotent by spec.
68
+
69
+ ---
70
+
71
+ ## 3. Level-of-abstraction fit
72
+
73
+ **Is this at the right layer?**
74
+
75
+ Yes. The two primitives sit at the `src/core/` layer — the same layer as `PostUpdateMigrator`, `GitStateManager`, `BackupManager`. They compose on top of `node:fs` only; no dependency on the Remediator (Tier-2 surfaces will register their own steps via `migrator.registerStep(...)` after import). The A35 const-literal extensions sit where the const lives — `GitStateManager` for the gitignore, `BackupManager` for the exclusion prefixes. No new plugin/register API was introduced per A50's explicit ruling.
76
+
77
+ The `PostUpdateMigrator` class is the right owner for the public-facing `registerStep`/`runPendingSteps` methods even though the engine logic lives in `MigratorStepEngine` — the existing class is already the documented entry point for "thing that runs on update boundary". Tier-2/Tier-3 surfaces (S-2, S-3) will reach for `PostUpdateMigrator` first.
78
+
79
+ ---
80
+
81
+ ## 4. Signal-vs-authority compliance
82
+
83
+ The brittle/low-context layers (file I/O ledger reads, sink invocations) are signal layers. The higher-context layer (the `runPendingSteps` orchestrator) is the authority. Specifically:
84
+
85
+ - A corrupt ledger does NOT block migration — the ledger is treated as a signal ("here's what we believe happened"), and the engine recovers by treating it as empty. Authority over "should this step run" lives in the in-memory step list + the version comparison.
86
+ - A failing sink does NOT block `announceOnce` from recording the announcement — sink delivery is signal; the ledger record is authority over "have we already attempted to surface this".
87
+ - A failing step does NOT block other steps — the per-step try/catch isolates signals; the engine retains authority over the run sequence.
88
+
89
+ This matches the §A50 / signal-vs-authority principle in MEMORY.md.
90
+
91
+ ---
92
+
93
+ ## 5. Interactions with existing surfaces
94
+
95
+ - **`PostUpdateMigrator.migrate()` is untouched.** Existing callers (`src/update/UpdateService.ts`, `src/commands/init.ts`, etc.) continue to work — they call `migrate()` and never touch the new methods.
96
+ - **`GitStateManager.init()`** — writes `DEFAULT_GITIGNORE` only when no `.gitignore` exists. Existing repos with `.gitignore` already on disk are unaffected. New repos get the remediation entries pre-included.
97
+ - **`BackupManager` callers** — 14 existing call sites pass at most 4 constructor args. The new 5th arg is optional + defaults to no-op behavior. Verified by running the full `backup-manager.test.ts` (44 cases) and `BackupManager-sharedState.test.ts` (3 cases) — all pass unchanged.
98
+ - **`upgrades/NEXT.md`** — only appended to (preserving the W-1, F-8, F-3, F-4, F-1..F-2 entries already on main).
99
+
100
+ ---
101
+
102
+ ## 6. Rollback cost
103
+
104
+ Low. The two new modules (`MigratorStepEngine.ts`, the new methods on `PostUpdateMigrator.ts`) are dormant unless something calls `registerStep` — and nothing calls it in this PR. Tier-2 surfaces will be the first consumers. Reverting requires:
105
+
106
+ 1. `git revert` the PR. No data migration. No format changes. Existing `migrate()` behavior is untouched.
107
+ 2. If any agent has already created `migrator-steps-completed.json` or `announcements-shown.json` files, they become unused leftovers — no harm. They can be deleted at leisure.
108
+
109
+ The A35 const-literal changes are inline edits to existing files; revert restores the prior literal. The `BackupManager`'s 5th constructor arg is optional and unused by every current caller, so removing it has zero blast radius.
110
+
111
+ ---
112
+
113
+ ## Tests
114
+
115
+ - `tests/unit/PostUpdateMigrator-atomicStep.test.ts` — 8 cases covering: step runs once and records completion; step is skipped on subsequent runs; failed step records failure and doesn't block other steps; future-version step skipped without ledger entry; state persists across instances; semver compare correctness.
116
+ - `tests/unit/AnnouncementManager.test.ts` — 5 cases covering: first-true / subsequent-false; independent ids; persistence across instances; sink-throw does not cause re-emission; input validation.
117
+ - `tests/unit/PostUpdateMigrator-a35-remediationPaths.test.ts` — 5 cases covering: `DEFAULT_GITIGNORE` contains all five remediation entries; `REMEDIATION_EXCLUDED_PATH_PREFIXES` exported correctly; gate ON drops remediation entries; gate OFF preserves them; absent callback = back-compat (gate OFF).
118
+
119
+ All 18 new tests pass. All 76 pre-existing `PostUpdateMigrator-*` tests pass unchanged. All 47 `BackupManager*` tests pass unchanged. TypeScript clean (`tsc --noEmit`).
120
+
121
+ ---
122
+
123
+ ## Gate trace
124
+
125
+ - side-effects review: this document
126
+ - NEXT.md: amended, preserving every prior entry
127
+ - spec citation: `docs/specs/SELF-HEALING-REMEDIATOR-V2-SPEC.md` §R1 + §A35 + §A50 + §A57
128
+ - worktree: `/tmp/instar-f7-post-update-migrator` (branch `f7-post-update-migrator`)
129
+ - no bypass; no `--no-verify`
@@ -0,0 +1,153 @@
1
+ # Side-Effects Review — F-8: Remediator orchestrator skeleton (Tier-1 subset)
2
+
3
+ **Version / slug:** `f8-remediator-skeleton`
4
+ **Date:** `2026-05-13`
5
+ **Author:** `echo`
6
+ **Second-pass reviewer:** `not required`
7
+
8
+ ## Summary of the change
9
+
10
+ Ships the Tier-1 subset of F-8 from the Self-Healing Remediator v2 spec (`docs/specs/SELF-HEALING-REMEDIATOR-V2-SPEC.md` §A1 manifest, §A2 lock-bound co-existence, §A3 capability-token context, §A4 deadline enforcement, §A6 errorCode-provenance gate, §A21 verify-failed strict typing, §A36 essential-runbook validator, §A57 phase tiering).
11
+
12
+ New module: `src/remediation/Remediator.ts`. A single orchestrator class plus the surface types `ApprovedRunbook`, `RemediationContext`, `ExecutionResult`, `VerifyOutcome`, `BlastRadius`, `Reversibility`, `DispatchOutcome`.
13
+
14
+ What this PR DOES include (per A57's Tier-1 carve-out):
15
+ - Runbook registration with §A6 + §A36 registry-load-time refusal.
16
+ - Dispatch loop: match by `eventPrefilter` (errorCode + provenance) + `match()`; refuse `provenance: 'free-text'` events at the matcher; pick highest-priority candidate.
17
+ - Lock acquisition via the F-4 `MachineLock` using `tupleHash = sha256(runbookId + signatureHash)`. Existing in-flight lock with same tuple → `covered-by-inline`.
18
+ - Intent journal declaration via the F-4 `IntentJournal` (durable witness before any mutation).
19
+ - `RemediationContext` carrying `attemptId, runbookId, lockHandle, auditToken (leaf from F-1 keyVault), abortSignal, expiresAt, monotonicDeadline`.
20
+ - Deadline enforcement (§A4): an `AbortController` fires at `expectedRuntimeMs`; the dispatcher races the surface against the timer; on deadline, the result is `aborted-deadline` and the lock is released in the `finally` block.
21
+ - Verify-outcome wiring (§A21): verify is only called when surfaceCallable returned `outcome: 'success'`; verify-THROW is mapped to `verify-inconclusive` (probe error), never `verify-failed`.
22
+ - Audit-append via the F-4 `AuditWriter` at every state transition (`started`, `verified-healthy | verify-failed | verify-inconclusive`, `aborted-deadline`, `no-matching-runbook`, `covered-by-inline`).
23
+
24
+ What this PR explicitly does NOT include (per A57 Tier-2 carve-out, deferred):
25
+ - Trust elevation source.
26
+ - Probe authentication (A40, A52).
27
+ - Capability-token HMAC enforcement on the surface side (A3, A23, A42).
28
+ - Supervisor handshake (A15).
29
+ - Runbook registry validation against a signed manifest (A56, A66).
30
+ - Child-process SIGTERM/SIGKILL escalation — surfaces (W-1..W-4) implement that; the skeleton's AbortSignal is the in-process enforcement handle only.
31
+
32
+ Files touched:
33
+ - `src/remediation/Remediator.ts` (add)
34
+ - `tests/unit/Remediator.test.ts` (add — 12 cases)
35
+ - `upgrades/NEXT.md` (modify — preserves F-1..F-4 + Phase 4/5 + ELI16 + API-safety entries)
36
+
37
+ ## Decision-point inventory
38
+
39
+ - `Remediator.registerRunbook()` — **add** — registry-load-time gate; rejects free-text-provenance prefilters (§A6) and essential-on-non-machine (§A36). Hard error: throws on misconfig so boot fails fast.
40
+ - `Remediator.dispatch()` — **add** — orchestration entry; produces a `DispatchOutcome` discriminated union. Five terminal states. Does not throw on policy rejections; throws only on infrastructure failures (lock acquire error not covered by `covered-by-inline`).
41
+ - Internal: `matchRunbook()` — defends-in-depth against `provenance: 'free-text'` events even if a runbook somehow registered such a prefilter.
42
+ - Internal: deadline race via `Promise.race(racePromise, deadlinePromise)` — the AbortController is the surface-facing signal; the race is the dispatcher-side authority.
43
+
44
+ No new HTTP routes. No new persistent files beyond what F-4 already creates (audit-projection-*, intent-journal-*, machine-locks/*). The skeleton consumes those primitives; it does not create new file types.
45
+
46
+ ---
47
+
48
+ ## 1. Over-block
49
+
50
+ **What legitimate inputs does this change reject that it shouldn't?**
51
+
52
+ - **`provenance: 'free-text'` events never match.** This is by design (§A6). Legacy `.report(...)` callers normalize through DegradationReporter's F-3 shim to `free-text` provenance and route to `no-matching-runbook` (which is fine — they still produce an audit entry; SystemReviewer clusters them for novel-failure proposals). The "rejection" is exactly what §A6 mandates as the structural defense against attacker-shaped error text.
53
+ - **Concurrent dispatch against the same tuple while in-flight → `covered-by-inline`.** Intended (§A2). The second caller is not silently dropped — it receives a structured outcome naming the existing attempt. The caller can subscribe to the audit log for the in-flight attempt's terminal outcome.
54
+ - **Essential on non-machine blast radius rejected at register-time.** Intended (§A36). A boot-time misconfig fails fast rather than allowing a non-machine runbook to skip auto-quarantine (which would be a DoS vector).
55
+ - **Verify probe error → `verify-inconclusive`.** Intended (§A21). The spec explicitly carves out probe error / timeout / unsigned payload as `verify-inconclusive` so attackers cannot DoS a working heal by making the verify probe fail.
56
+
57
+ There is no legitimate path this PR over-blocks. The four rejections above are all structural defenses with no false-positive surface.
58
+
59
+ ---
60
+
61
+ ## 2. Under-block
62
+
63
+ **What failure modes does this still miss?**
64
+
65
+ - **No capability-token HMAC enforcement on the surface side.** A surface that receives `RemediationContext` could theoretically be invoked by a forged context (no token verification on the surface). Tier-2 fixes this with A3 + A23 + A42. The Tier-1 skeleton's mitigation: only the dispatcher creates contexts, and the surface package is co-installed with the dispatcher; in this PR there are no real surface consumers yet (W-1 ships the first one). Forging requires in-process code-exec, at which point all bets are off anyway.
66
+ - **No probe authentication.** Verify outcomes from the runbook's `verify()` callback are trusted by-signature only insofar as the runbook author wrote them correctly. A40 will harden this with per-probe leaf-key signatures over the verify envelope. Tier-1 trusts the runbook code itself.
67
+ - **No cross-machine coordination.** The lock is per-machine. Two machines healing the same tuple are not coordinated by F-8. Cross-process attempt ledger (A7) and primary-aggregator lease (A47) handle that at Tier-2/Tier-3.
68
+ - **No child-process abort escalation.** A surface that spawns a child process and ignores `abortSignal` won't be force-killed by F-8; the dispatcher only signals. Child-process SIGTERM/SIGKILL ladder is W-1's concern. Mitigation: the lock IS released by the dispatcher's `finally` block, so the orphaned child does not block a re-dispatch.
69
+ - **No deadline enforcement on `verify()` itself.** If a surface succeeds but verify hangs, the same AbortController fires and the dispatcher returns `aborted-deadline` (the deadline race is around the entire `surfaceCallable + verify` chain). However, verify is not given a separate deadline budget. Tier-2 may refine this.
70
+
71
+ ---
72
+
73
+ ## 3. Level-of-abstraction fit
74
+
75
+ **Is this at the right layer?**
76
+
77
+ Yes. The Remediator is the orchestrator — its job is *to compose* the F-1..F-4 primitives. It owns:
78
+ - Match policy (which runbook fires for which event).
79
+ - Coordination policy (lock acquire, tuple compute, covered-by-inline detection).
80
+ - Deadline policy (AbortController + timer race).
81
+ - Verify-outcome interpretation (success/failure/inconclusive mapping).
82
+
83
+ It does NOT own:
84
+ - Key material (F-1).
85
+ - Lock-file format (F-4 MachineLock).
86
+ - Intent durability semantics (F-4 IntentJournal).
87
+ - Audit-token verification (F-4 AuditWriter's injected verifier).
88
+ - Surface implementations (W-*).
89
+
90
+ Each of those concerns lives in its rightful module. The dispatcher is the thin glue layer that the spec mandates.
91
+
92
+ No higher-level smart gate is shadowed. The dispatch decision is structural (does the event match this runbook's prefilter?), not heuristic. The signal-vs-authority principle is honoured because matching is deterministic and the authority is bounded to "which surfaceCallable to invoke" — a runbook-author-controlled invariant, not a content-classifier's call.
93
+
94
+ ---
95
+
96
+ ## 4. Signal vs authority compliance
97
+
98
+ **Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
99
+
100
+ **Does this change hold blocking authority with brittle logic?**
101
+
102
+ - [x] No — this change has no block/allow surface beyond structural primitives (errorCode equality, provenance enum membership, runbook id presence).
103
+ - [ ] No — this change produces a signal consumed by an existing smart gate.
104
+ - [ ] Yes — but the logic is a smart gate with full conversational context.
105
+ - [ ] ⚠️ Yes, with brittle logic — STOP.
106
+
107
+ The match function `eventPrefilter.errorCode.includes(event.errorCode) && eventPrefilter.provenance.includes(event.provenance) && runbook.match(event)` is precise, not heuristic. There is no regex, no string-pattern matching, no LLM call. The only "intelligence" is the runbook author's `match()` callback, which is structural code shipped in `src/remediation/runbooks/*` (none exist yet — W-* PRs add them).
108
+
109
+ The blocking decisions held by the dispatcher (free-text rejection, essential validator, deadline enforcement) are all over precise structural inputs:
110
+ - §A6 free-text rejection: enum equality.
111
+ - §A36 essential validator: enum equality + boolean.
112
+ - §A4 deadline: monotonic clock comparison.
113
+
114
+ These are exactly the kind of authority that *should* be hard-coded structurally. The smart gate (the runbook author + the user who approves runbook PRs through `/instar-dev`) holds policy authority over which runbooks exist; the dispatcher's job is to enforce structural invariants on registration and dispatch.
115
+
116
+ ---
117
+
118
+ ## 5. Interactions
119
+
120
+ **Does this interact with existing checks, recovery paths, or infrastructure?**
121
+
122
+ - **Shadowing:** None. There is no existing dispatcher in `src/remediation/`. The closest prior art is the in-line `NativeModuleHealer` path inside the existing native-binding error handling — that path remains untouched in this PR. W-1 ships the wrapper that makes the inline healer addressable from F-8 *as a runbook*, at which point §A2's lock-bound co-existence rule kicks in (both the inline and the F-8 path acquire the same MachineLock tuple; only one runs).
123
+ - **Race with adjacent cleanup:** The dispatcher's `finally` block always releases the lock. If `acquireInFlight` succeeded but the surface throws before `surfaceCallable` is invoked (currently impossible — the dispatcher calls it directly), the lock would still be released. The release is idempotent (F-4 contract).
124
+ - **Double-fire:** `dispatch()` for the same event called twice concurrently → second call observes the first's lock and returns `covered-by-inline`. `dispatch()` called sequentially → second call proceeds (the first released its lock).
125
+ - **DegradationReporter wiring:** F-3 ships `setRemediator(remediatorLike)`. THIS PR does not call `setRemediator()` from anywhere — the dispatcher is constructible but not yet wired into the DegradationReporter pipeline. A subsequent PR (or W-1) wires the dispatcher into the reporter, at which point legacy events route through `dispatch()`. Until then, the dispatcher only fires when called explicitly (e.g., from tests).
126
+ - **Audit-projection consumer:** F-4's `AuditProjection` reads from the same file the dispatcher writes to. Live: as soon as the dispatcher is invoked, `AuditProjection.recentByRunbook()` reflects the new entries. No coupling beyond the file path contract.
127
+
128
+ ---
129
+
130
+ ## 6. External surfaces
131
+
132
+ **Does this change anything visible to other agents, other users, other systems?**
133
+
134
+ - No HTTP routes, no Telegram surfaces, no dashboard tabs, no config-flip entries, no CLI commands.
135
+ - No new file types on disk. The dispatcher consumes the F-4 primitives' files (`<stateDir>/machine-locks/in-flight/*.lock`, `<stateDir>/remediation/intent-journal-*.jsonl`, `<stateDir>/remediation/audit-projection-*.jsonl`, `<stateDir>/remediation/audit-rejected.jsonl`). Files only materialize when the dispatcher is actually invoked.
136
+ - The default exported class is a new symbol. No existing imports break.
137
+ - The DegradationReporter `RemediatorLike` interface declared in F-3 is structurally compatible with the new `Remediator.dispatch()` signature: both accept `NormalizedDegradationEvent` and return `Promise`. The dispatcher's return type is richer (`DispatchOutcome`), which is a widening from F-3's `Promise<void>` — backwards-compatible.
138
+
139
+ ---
140
+
141
+ ## 7. Rollback cost
142
+
143
+ **If this turns out wrong in production, what's the back-out?**
144
+
145
+ Trivially low. The dispatcher is a new symbol that is not yet imported by any production code path. `git revert` of this PR removes the class and the test file with zero state migration cost. The on-disk artifacts (audit-projection, intent-journal, machine-locks) are F-4's responsibility; the revert doesn't touch them, and they remain consumable by whichever future PR re-introduces the dispatcher.
146
+
147
+ If a future consumer-PR (W-1) wires the dispatcher into `setRemediator()` and a bug surfaces, the back-out path is "revert the consumer PR" — the dispatcher itself never runs without a caller. The fail-safe shape of the design means a dispatcher bug cannot break legacy `.report(...)` flow (which never reaches the dispatcher absent a `setRemediator()` call).
148
+
149
+ ---
150
+
151
+ ## Reviewer concurrence (Phase 5)
152
+
153
+ Not required. This change introduces no live consumers, no block/allow surface for messaging, no session lifecycle, no coherence/sentinel/gate authority. It is foundation infrastructure that becomes load-bearing only once W-1 wires a real runbook. At that point W-1 carries its own side-effects review covering the dispatcher's first production caller.
@@ -0,0 +1,72 @@
1
+ # Side-effects review — F-8 rest of Tier-2 enforcement
2
+
3
+ **Spec**: `docs/specs/SELF-HEALING-REMEDIATOR-V2-SPEC.md` (§A3, §A23, §A40, §A42, §A52, §A57 Tier-2 carve-outs)
4
+
5
+ **Scope**: Wires the security enforcement that the Tier-1 F-8 skeleton (PR #201) deferred — capability-token HMAC on dispatched RemediationContext, probe-source-binding + scope check at dispatch entry, trust-elevation source consult on lifecycle transitions, supervisor-handshake closure (`RegisteredRemediator` implementation + `requestPlannedRestart`), and one smoke-test probe scope export.
6
+
7
+ ---
8
+
9
+ ## 1. What changes about the running system
10
+
11
+ Nothing observable today. Every new constructor option (`trustSource`, `serverSupervisor`, `probeSourceRegistry`) and every new method (`canTransition`, `requestPlannedRestart`, `getCapabilityLeafKey`, `onRestartComplete`) is OPT-IN. The default `Remediator` shape unchanged for existing Tier-1 / W-1 tests. No production code instantiates a `Remediator` yet; `DegradationReporter.setRemediator()` is still uncalled.
12
+
13
+ The one structural surface change is on `NormalizedDegradationEvent`: a new optional `source.probeSignature` field is added. This is additive and serializes to `undefined` for every existing emit site.
14
+
15
+ ## 2. Over-block / under-block
16
+
17
+ **Over-block risk** — when a `probeSourceRegistry` IS wired, every `provenance: 'probe-id'` event must carry a verified envelope inside the declared scope. An emit-site that forgot to sign is silently routed to `no-matching-runbook` with reason `probe-event-unsigned`. The Tier-3 fleet migration is the rollout for this; F-8-rest only wires ONE probe (`LifelineProbe`) and the registry is not constructed at all in production paths yet. So today: zero over-block.
18
+
19
+ **Under-block risk** — when the registry is UNWIRED, no probe enforcement happens. A malicious or buggy emit-site could mint a `provenance: 'probe-id'` event with no signature and the Tier-1 path would dispatch normally. The spec accepts this for Tier-2 (the registry-wiring step is the Tier-3 fleet rollout — see §A57). The Tier-2 unit tests assert presence of the gate; production wiring is the next-tier work item.
20
+
21
+ **Capability-token verify under-block** — `NativeModuleHealer.invokeFromRemediator` only enforces when BOTH a `keyVault` is passed AND `ctx.hmac` is set. Existing W-1 callers (test fixtures) pass neither, so they continue to work. Production wiring (a future PR) will always pass both. This is intentional backward compat per the user request "the in-line `openWithHeal` path MUST stay working."
22
+
23
+ ## 3. Level-of-abstraction fit
24
+
25
+ - `RemediationContext` sign/verify lives in `src/remediation/RemediationContext.ts` — same directory as the consumers, no cross-tree dep.
26
+ - The surface-side verify in `NativeModuleHealer` deliberately INLINES the canonical-body byte layout (it does NOT import from `src/remediation/`) so the legacy `openWithHeal` CLI path stays usable on installs without the remediation tree. A small chunk of byte-layout duplication is the explicit price; the test fixture asserts both implementations agree.
27
+ - Probe-source binding lives in `src/monitoring/probes/__shared.ts` — co-located with the consumers (probe modules) per A52.
28
+ - The `DefaultProbeSourceRegistry` impl + canonical envelope helper live in `Remediator.ts` so the wire format has ONE owner; tests can also stub the interface.
29
+
30
+ ## 4. Signal-vs-authority compliance
31
+
32
+ - The Remediator REMAINS the authority layer (it makes block / allow decisions).
33
+ - Probes are SIGNAL emitters — they sign their envelopes, declare their scope, but the Remediator decides what to do with each event.
34
+ - `TrustElevationSource` is the authority for lifecycle transitions; the Remediator delegates without re-interpreting.
35
+ - `ServerSupervisor` retains authority over restart sequencing; the Remediator only REQUESTS via HMAC-signed payload — the supervisor's existing fail-closed checks (handshake-version match, staleness window, blast-radius allowlist, HMAC verify) are unchanged.
36
+
37
+ No new authority migrated to a low-context filter.
38
+
39
+ ## 5. Interactions with existing behavior
40
+
41
+ - **`openWithHeal` (legacy)** — Unchanged. Doesn't construct `invokeFromRemediator` ctx, so the new verification path is skipped entirely. Verified by `tests/unit/NativeModuleHealer.test.ts` (12 tests pass unchanged).
42
+ - **W-1 runbook fixture path** — `invokeFromRemediator(ctx)` without keyVault → §A3 verification skipped → existing behavior. 12 `NativeModuleHealer-invokeFromRemediator` tests pass unchanged.
43
+ - **F-4 audit primitives** — No change. The existing `tokenVerifier` injection is still the authoritative gate for audit writes; F-8-rest does not modify the audit pipeline.
44
+ - **F-5 TrustElevationSource** — Used through its public `canTransition()` only. The 25 source-side tests pass unchanged.
45
+ - **F-6 ServerSupervisor handshake** — The Remediator now CLOSES the loop by implementing `RegisteredRemediator`. The 9 supervisor-handshake tests pass unchanged.
46
+ - **F-7 PostUpdateMigrator** — Untouched.
47
+
48
+ ## 6. Rollback cost
49
+
50
+ Pure additive surfaces. Reverting the PR:
51
+ - Drops `signRemediationContext` / `verifyRemediationContext` / `canonicalProbeEnvelopeBody` / `DefaultProbeSourceRegistry`.
52
+ - Drops the three optional `RemediatorOptions` fields + the new methods.
53
+ - Drops `__verifyScope` from `LifelineProbe` (one const export).
54
+ - Drops the `hmac` field on `RemediationContext` (optional, no caller required to set).
55
+ - Drops the `source.probeSignature` field on `NormalizedDegradationEvent` (optional, no emit site sets it).
56
+ - Drops 21 new tests.
57
+
58
+ No data-format migrations, no on-disk schema change, no live consumer wired. Rollback cost: low.
59
+
60
+ ## 7. Test plan
61
+
62
+ - 7 unit tests on `signRemediationContext` / `verifyRemediationContext` round-trip + every tamper / mismatch failure path.
63
+ - 10 unit tests on Remediator F-8-rest enforcement (probe-source binding 4 cases, ctx signed by dispatch, trust-source delegation 3 cases, canonical-envelope determinism 2 cases).
64
+ - 4 unit tests on NativeModuleHealer §A3 enforcement (valid hmac runs, forged hmac falls back, aborted fallback short-circuits, no-keyVault legacy compat).
65
+ - All 57 pre-existing Tier-2 / W-1 / Tier-1 tests in `Remediator.test.ts`, `runbooks/node-abi-mismatch.test.ts`, `NativeModuleHealer-invokeFromRemediator.test.ts`, `NativeModuleHealer.test.ts`, `ServerSupervisor-handshake.test.ts` pass unchanged.
66
+ - All 25 `TrustElevationSource.test.ts` + 4 `AuditWriter.test.ts` tests pass unchanged.
67
+
68
+ Total: 107 / 107 remediation-side tests green.
69
+
70
+ ## Second-pass review
71
+
72
+ Not required (this PR introduces no new dispatch logic that would change live-system behavior — every change is gated behind opt-in constructor options that are unwired in production).
@@ -0,0 +1,41 @@
1
+ # Side-effects review — File Viewer extends never-editable to .instar/jobs/instar/
2
+
3
+ ## What changed
4
+
5
+ Two-line spec compliance item from INSTAR-JOBS-AS-AGENTMD §Decision Points:
6
+
7
+ - `src/server/fileRoutes.ts:NEVER_EDITABLE_PREFIXES` adds `.instar/jobs/instar/`. Any `PATCH /api/files/config` attempt to declare an `editablePath` under that prefix is rejected with HTTP 400 + "never editable" error. Direct save attempts on a file under that path are rejected the same way.
8
+ - Migrator-emitted CLAUDE.md doc string (`PostUpdateMigrator.ts` line 1501) updated to list `.instar/jobs/instar/` alongside the existing never-editable namespaces.
9
+
10
+ ## Why
11
+
12
+ The `.instar/jobs/instar/` namespace is owned by the update process and signed against the bundled lock-file. A Dashboard editor that lets the operator edit a default's body would (a) be overwritten on next update, (b) break the body-hash verification, and (c) bypass the spec's intended override flow (fork to `.instar/jobs/user/` via the Override action).
13
+
14
+ ## Side-effects review
15
+
16
+ ### 1. Over-block / under-block
17
+
18
+ - **Over-block:** none for users — the override flow is the documented path for customizing a default. Hand-edits in the file viewer were never useful here (they'd revert on update).
19
+ - **Under-block:** the prefix check is exact-startswith. A path like `.instar/jobs/instar.lock.json` is NOT under `.instar/jobs/instar/` (different filename), so the lock-file itself remains read-accessible. That's correct: the lock-file is JSON that the operator may want to view for debugging.
20
+
21
+ ### 2. Level-of-abstraction fit
22
+
23
+ One entry in a closed-set array. Zero new code paths.
24
+
25
+ ### 3. Signal-vs-authority compliance
26
+
27
+ `NEVER_EDITABLE_PREFIXES` is the authority for "what can be edited via the Dashboard." Adding to the list strengthens, not weakens, the structural property.
28
+
29
+ ### 4. Interactions
30
+
31
+ - **Phase 2 installBuiltinJobs** — already writes `.instar/jobs/instar/<slug>.md` on init + update; the never-editable list is the matching read-only enforcement.
32
+ - **Phase 4 Dashboard UI rewrite (future)** — the future override/unfork UI lives at a different path; not affected by this change.
33
+ - **File Viewer config UI** — config save endpoint already rejects never-editable prefixes; this addition is automatically enforced there.
34
+
35
+ ### 5. Rollback cost
36
+
37
+ Trivial. Remove one line + revert one CLAUDE.md doc-string line.
38
+
39
+ ## Test coverage
40
+
41
+ `tests/e2e/file-viewer-e2e.test.ts` adds one case in the existing "rejects editablePaths targeting <never-editable>" cluster — same shape as the `node_modules/` test. The pre-existing file-viewer-e2e suite covers the read/write enforcement that the new prefix automatically inherits.
@@ -0,0 +1,48 @@
1
+ # Side-effects review — Frontmatter interpolation breakout test
2
+
3
+ ## What changed
4
+
5
+ New unit test `tests/unit/scheduler/JobScheduler.frontmatter-breakout.test.ts` asserts the structural defense the spec calls out:
6
+
7
+ > Frontmatter-field interpolation breakout — any frontmatter field interpolated into prompt must pass slug regex or be JSON-stringified; breakout payload tested.
8
+
9
+ The current implementation already meets the spec: `JobScheduler.buildPrompt(job)` for `execute.type: "agentmd"` does NOT interpolate any frontmatter field into the prompt. The body is the prompt; framing constants (notification protocol, view metadata) come from source-level string literals, not from job data.
10
+
11
+ This test ASSERTS that property so a future refactor introducing frontmatter interpolation cannot land without (a) breaking this test and (b) forcing the author to think about breakout surface.
12
+
13
+ ## Side-effects review
14
+
15
+ ### 1. Over-block / under-block
16
+
17
+ - **Over-block:** none — the test asserts what the code already does. Pass rate today: 100%.
18
+ - **Under-block:** the test covers four breakout categories (shell injection, template injection, prompt injection, null-byte / control characters) plus an explicit "rendered prompt contains body verbatim" assertion plus a slug-regex defense assertion. If a future refactor introduces a NEW breakout surface I didn't anticipate, the four-category coverage still catches the common shapes.
19
+
20
+ ### 2. Level-of-abstraction fit
21
+
22
+ Pure unit test. Calls `JobScheduler.buildPrompt` (private, accessed via type-cast escape — same pattern as other internal tests in the suite). No new code paths, no new behavior.
23
+
24
+ ### 3. Signal-vs-authority compliance
25
+
26
+ The test is a signal that the no-interpolation property is intact. The authority is `buildPrompt` itself — which is the single point that decides what string becomes the spawn's `--prompt` argument.
27
+
28
+ ### 4. Interactions
29
+
30
+ - **Phase 1b agentmd dispatch** — the test covers the same path Phase 1b shipped.
31
+ - **Phase 2 templates** — the templates themselves are markdown bodies; this test asserts that even if someone shipped a malicious frontmatter alongside a benign body, the malicious content never reaches the spawn.
32
+
33
+ ### 5. Rollback cost
34
+
35
+ Trivial. Single test file. Delete to revert.
36
+
37
+ ## Test coverage
38
+
39
+ 6 cases in `tests/unit/scheduler/JobScheduler.frontmatter-breakout.test.ts`:
40
+
41
+ 1. Shell-injection frontmatter payload absent from rendered prompt
42
+ 2. Template-injection frontmatter payload absent
43
+ 3. Prompt-injection frontmatter payload absent
44
+ 4. Null-byte / control-character payloads absent
45
+ 5. Body bytes are present verbatim; frontmatter bytes are not
46
+ 6. Slug regex rejects all breakout-shaped slugs (defense-in-depth)
47
+
48
+ All 6 pass locally. Lint + type-check pass.
@@ -0,0 +1,78 @@
1
+ # Side-effects review — Phase 2 (default job conversion + asset packaging)
2
+
3
+ ## What changed
4
+
5
+ Phase 2 converts the 14 prompt-type instar default jobs from inline JSON strings in `getDefaultJobs()` to standalone markdown templates that can be reviewed, diffed, and verified at runtime against the signed lock-file. Script-type and skill-type defaults stay as legacy `getDefaultJobs()` entries — agentmd format is only appropriate for prompt-bearing jobs.
6
+
7
+ New artifacts:
8
+
9
+ - **`src/scaffold/templates/jobs/instar/<slug>.md`** — 14 shipped templates, one per prompt-type default. Body is the EXACT bytes of `execute.value`. Frontmatter captures name, description, schedule, priority, expectedDurationMinutes, model, enabled, tags, optional gate, toolAllowlist:"*", unrestrictedTools:true. The "*" + unrestrictedTools:true pair preserves today's full-tool behavior for every shipped default (no behavioral regression). Phase 4 will narrow these per slug.
10
+ - **`scripts/regen-default-job-templates.mjs`** — generator that reads `getDefaultJobs()` and writes templates. Run on every default-job edit to keep templates in sync with the canonical source.
11
+ - **`src/scheduler/InstallBuiltinJobs.ts`** — new `installBuiltinJobs()` function. Reads templates from `<packageRoot>/dist/scaffold/...` (prod) or `<packageRoot>/src/scaffold/...` (dev), writes per-slug bodies to `.instar/jobs/instar/<slug>.md`, per-slug manifests to `.instar/jobs/schedule/<slug>.json`, copies the signed lock-file from `dist/jobs/instar.lock.json` to `.instar/jobs/instar.lock.json`, and the bundled public key to `.instar/keys/instar-release-pub.pem`.
12
+ - **`getDefaultJobs` export** — was a non-exported helper; now exported so the regen script can call it without re-parsing init.ts.
13
+ - **PostUpdateMigrator.migrateBuiltinJobs** — invokes installBuiltinJobs on every update. Failures are reported in the migration result, never fatal.
14
+ - **init.ts** — fresh-install path also invokes installBuiltinJobs (right after installBuiltinSkills, before CLAUDE.md write).
15
+ - **package.json#files** — `src/scaffold` added so the templates ship in the npm tarball.
16
+
17
+ ## Side-effects review (mandatory gate)
18
+
19
+ ### 1. Over-block / under-block
20
+
21
+ - **Over-block:** none. `installBuiltinJobs` overwrites `.instar/jobs/instar/<slug>.md` on every update — this is the contract for instar-namespace files (the namespace IS managed by updates per the spec's `Concrete Paths`). The user namespace (`.instar/jobs/user/`) is structurally untouched.
22
+ - **Under-block:** the operator-disabled state on a default is preserved across updates (the per-slug manifest's `enabled` and `disabledAtBodyHash` are read from the existing manifest before being rewritten). A retired default has its body removed and its manifest marked retired+disabled+timestamped, so Phase 4 Dashboard can surface it.
23
+
24
+ ### 2. Level-of-abstraction fit
25
+
26
+ `installBuiltinJobs` is a pure file-system shaper. It does not invoke the scheduler, does not touch state.json, does not emit events. It writes files and returns a report. This keeps it composable: both init and PostUpdateMigrator call it; tests run it against a synthetic workspace; future Phase 5 `instar jobs migrate` will also call it as part of the migration sequence.
27
+
28
+ The destructive operations (template removal on retire, stale lock-file cleanup) go through `SafeFsExecutor.safeUnlinkSync` with explicit operation strings, satisfying the destructive-tool funnel gate.
29
+
30
+ ### 3. Signal-vs-authority compliance
31
+
32
+ The signed lock-file remains the trust authority — `installBuiltinJobs` is just the courier. It copies the lock-file from `<packageRoot>/dist/jobs/` to `.instar/jobs/` without inspecting or modifying its contents. The runtime verifier (Phase 1c-runtime) reads the lock-file and decides trust. Phase 2 does NOT touch the lockTrust decision path.
33
+
34
+ ### 4. Interactions
35
+
36
+ - **Phase 1c-runtime** — the bundled public key copy step keeps the verifier able to validate lock-files on every update (no chicken-and-egg if the public key is rotated mid-rollout).
37
+ - **Phase 1c-build** — when a signing key is configured, `npm run build` produces `dist/jobs/instar.lock.json`; `installBuiltinJobs` copies it. When no signing key is configured (current pre-GHA-secret state), the signer skips lock-file generation; `installBuiltinJobs` removes any stale lock-file from agent disk so the runtime sees `state: absent` → `lockTrust=untrusted-no-lockfile`.
38
+ - **Legacy `jobs.json` seeding** — init.ts still calls `getDefaultJobs()` and writes the legacy `jobs.json`. The JobLoader already handles `schedule/<slug>.json` shadowing `jobs.json` entries with the same slug — no conflict. Phase 3 will migrate `jobs.json` entries that match an agentmd template to `origin:instar` form and drop the body; Phase 5 auto-runs this.
39
+ - **`origin:instar` namespace ownership** — invariant 4 of the Seamless Migration Guarantee (PR #180) is verified by the `NEVER touches .instar/jobs/user/` test plus the symlink-refusal test.
40
+ - **Idempotency** — installer is idempotent. Run-twice test asserts the on-disk content is byte-stable across repeated invocations.
41
+
42
+ ### 5. Rollback cost
43
+
44
+ Trivial. Removing `installBuiltinJobs` call from init + PostUpdateMigrator restores prior behavior. Agents that already have `.instar/jobs/instar/` populated keep the files (the loader handles them via Phase 1a path); no on-disk corruption.
45
+
46
+ ### 6. Seamless Migration Guarantee compliance
47
+
48
+ This PR ships the Seamless Migration Guarantee fixtures' first hook: installer behavior under the `pristine` shape (fresh agent, default-only state). The dedicated guarantee suite (`tests/integration/migration-guarantee.test.ts`) lands in Phase 3 with the migration script. Phase 2's installer tests assert:
49
+
50
+ - Invariant 4 (user namespace untouched) — `NEVER touches .instar/jobs/user/` test
51
+ - Symlink refusal (defense-in-depth for invariant 4) — `refuses to install if .instar/jobs/user/ is a symlink`
52
+ - Idempotency boundary for the installer — `is idempotent — running twice produces the same on-disk state`
53
+ - Retired-default flow (no orphan state) — `retires a default that is removed from the shipped templates`
54
+
55
+ ## Test coverage
56
+
57
+ `tests/unit/scheduler/InstallBuiltinJobs.test.ts` — 10 cases:
58
+
59
+ 1. Installs each shipped template + per-slug manifest
60
+ 2. Port-sentinel substitution (`:-4042}` → `:-<agentPort>}`)
61
+ 3. Preserves operator-disabled state on update
62
+ 4. Retires defaults removed from templates
63
+ 5. NEVER touches `.instar/jobs/user/` (invariant 4)
64
+ 6. Refuses install if user dir is a symlink (defense-in-depth)
65
+ 7. Copies signed lock-file + bundled public key when present
66
+ 8. Removes stale lock-file when no signed file ships
67
+ 9. Returns error when templates dir is absent or empty
68
+ 10. Idempotent — run-twice produces byte-stable on-disk state
69
+
70
+ All 10 tests pass locally.
71
+
72
+ ## What is NOT in this PR
73
+
74
+ - **Drift classifier** — the spec mentions populating `significantChanges` in the lock-file at build time. That's a Phase 4 Dashboard concern; the lock-file format already accommodates it (optional field). Will land with the Dashboard rewrite.
75
+ - **`instar jobs migrate` CLI** — Phase 3.
76
+ - **Auto-migration on update** — Phase 5 (PostUpdateMigrator will auto-run the migration script when `jobs.json` + no `.migration-complete.json`).
77
+ - **Full Seamless Migration Guarantee suite** — Phase 3 ships the suite with the migration script.
78
+ - **Per-slug narrow allowlists** — every shipped default currently has `toolAllowlist: "*"`. Narrowing is a per-slug review Phase 4 will run.
@@ -0,0 +1,76 @@
1
+ # Side-effects review — Phase 3 (`instar jobs migrate`)
2
+
3
+ ## What changed
4
+
5
+ `instar job migrate` ships as the operator-initiated path for converting a legacy `jobs.json`-only agent to the new per-slug-manifest layout. Idempotent. Reversible via `--abandon`. Non-destructive on `jobs.json` (only writes a backup and the new `.instar/jobs/...` tree).
6
+
7
+ New files:
8
+
9
+ - **`src/commands/jobMigrate.ts`** — pure function `jobsMigrate(opts)`. Reads `jobs.json`, classifies each entry against the shipped templates (body-match SHA-256 with normalize), writes per-slug bodies to `.instar/jobs/instar/` (for matched defaults) or `.instar/jobs/user/` (for user jobs and forked defaults), writes per-slug manifests to `.instar/jobs/schedule/`, and writes a backup at `.instar/jobs.json.pre-migrate-<ts>`.
10
+ - **CLI subcommand** — `instar job migrate [--default-action=fork|rename|skip|fail] [--report] [--abandon]`. Mirrors the spec's `instar jobs migrate` invocation contract.
11
+ - **11 unit tests** — `tests/unit/commands/jobMigrate.test.ts`.
12
+
13
+ Behavior matrix:
14
+
15
+ | Pre-migration entry shape | Classification | What gets written |
16
+ |---|---|---|
17
+ | Slug ∈ shipped + body matches (normalize SHA-256) | `migrated-instar` | per-slug manifest only (body lives in `src/scaffold/templates/jobs/instar/`) |
18
+ | Slug ∈ shipped + body differs | `near-miss-default` + `--default-action` | fork to user OR rename to `<slug>-user` OR skip OR abort (`fail`) |
19
+ | Slug ∉ shipped | `kept-user` | per-slug body in `user/` + manifest |
20
+ | `execute.type !== 'prompt'` (script/skill) | `skipped` | nothing (legacy path keeps them) |
21
+
22
+ ## Side-effects review (mandatory gate)
23
+
24
+ ### 1. Over-block / under-block
25
+
26
+ - **Over-block:** none on first migration. `--default-action=fail` (the default) is the safety-first choice — it refuses to write ANY partial state if a near-miss is detected, leaving `jobs.json` untouched. The operator must explicitly choose fork/rename/skip via flag (or via the future Dashboard interactive prompt) to proceed past a near-miss.
27
+ - **Under-block:** the script does NOT auto-write `.migration-complete.json`. The release-cut gate (spec §Migration completion predicate) requires the operator to confirm via Dashboard. Phase 3's responsibility ends at "all the new files are in place." Phase 4 builds the confirm UI.
28
+
29
+ ### 2. Level-of-abstraction fit
30
+
31
+ `jobsMigrate` is a pure function on the file system + a structured outcome record. It DOES NOT invoke the scheduler, DOES NOT emit state events, DOES NOT modify `jobs.json` (only reads + backs up). This keeps it testable in a synthetic workspace and lets Phase 5's PostUpdateMigrator wrap it cleanly with auto-run + Dashboard banner emission.
32
+
33
+ The destructive operations (`--abandon` rm of `schedule/` and `instar/`; old abandonment marker cleanup) route through `SafeFsExecutor.safeRmSync` / `safeUnlinkSync` with explicit operation strings.
34
+
35
+ ### 3. Signal-vs-authority compliance
36
+
37
+ The CLI sub-command is a signal (operator says "I want to migrate"). The migration script is the authority on routing each entry (does the slug+body match a shipped default? → which destination?). The release-cut gate is the higher authority that will eventually allow `jobs.json` to be deleted. This PR ships the signal + the routing; Phase 4 ships the operator-confirm step that flips the release-cut gate's check from "no" to "yes."
38
+
39
+ ### 4. Interactions
40
+
41
+ - **Phase 1a JobLoader** — already shadows `jobs.json` entries with `.instar/jobs/schedule/` entries on same-slug. So immediately after migrate, the scheduler reads from the new path while the old `jobs.json` remains for back-compat. Zero scheduling drift during the transition window.
42
+ - **Phase 2 installBuiltinJobs** — already wrote `.instar/jobs/instar/<slug>.md` for every shipped default on every update. Migrate complements this by ALSO writing the per-slug manifest at `.instar/jobs/schedule/<slug>.json` based on the operator's pre-migration `jobs.json` (preserves cron/enabled state from the operator's setup).
43
+ - **Phase 1c-runtime** — the per-slug body's normalize SHA-256 is what the lock-file verifier uses. Migration uses the SAME normalize function (CRLF→LF, ZWSP/ZWNJ/ZWJ/BOM strip, trimEnd + single newline) via inline code that mirrors `AgentMdLockFile.ts`. The roundtrip is structural.
44
+ - **PostUpdateMigrator** — Phase 3 deliberately does NOT auto-run migrate. The spec says Phase 5 ships that.
45
+ - **Release-cut gate** — Phase 3 does NOT auto-write `.migration-complete.json`. Phase 4 Dashboard ships the operator confirm button.
46
+
47
+ ### 5. Rollback cost
48
+
49
+ `--abandon` is the one-button rollback. It removes the new `schedule/` and `instar/` directories, writes `.migration-abandoned.json`, and leaves `jobs.json` intact. The next scheduler boot reads `jobs.json` as before. Tested by `--abandon removes schedule/ and writes the abandonment marker`.
50
+
51
+ ### 6. Seamless Migration Guarantee compliance
52
+
53
+ This PR fills in the guarantee suite's CLI-path coverage (spec §Seamless Migration Guarantee Rollout interaction). Asserted invariants:
54
+
55
+ - Invariant 1 (zero job loss) — `non-prompt entries (script/skill) are skipped (legacy path keeps them)` + every prompt entry appears in `outcome.perEntry` with a non-failed action OR an explicit error
56
+ - Invariant 2 (zero schedule drift) — `migrates a body-matched default to origin:instar with manifest only` asserts schedule field preserved verbatim
57
+ - Invariant 5 (one-button rollback) — `--abandon removes schedule/ and writes the abandonment marker`
58
+ - Invariant 9 (fail-closed on any failure) — `refuses to proceed on near-miss when --default-action=fail` writes no partial state
59
+ - Backup invariant (rollback anchor) — `writes a pre-migrate backup of jobs.json before any destructive operation`
60
+ - Idempotency — `is idempotent — re-running produces stable on-disk state`
61
+
62
+ The Dashboard-path coverage (invariants 4, 7, 8) lands with Phase 4. The PostUpdateMigrator-path coverage (full guarantee on update) lands with Phase 5.
63
+
64
+ ## Test coverage
65
+
66
+ `tests/unit/commands/jobMigrate.test.ts` — 11 cases.
67
+
68
+ All pass locally. Lint + type-check pass.
69
+
70
+ ## What is NOT in this PR
71
+
72
+ - **PostUpdateMigrator auto-run** — Phase 5.
73
+ - **Dashboard "migration available" attention notice** — Phase 4.
74
+ - **Dashboard "Confirm migration complete" button** — Phase 4.
75
+ - **Release-cut gate enforcement of `.migration-complete.json`** — Phase 4 / Phase 5.
76
+ - **Interactive three-choice prompt for near-miss** — would need TTY detection + readline; the `--default-action` flag covers the non-interactive use case. Interactive UX lands with Phase 4 Dashboard.