@ijfw/memory-server 1.4.4 → 1.5.1

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 (245) hide show
  1. package/bin/ijfw-memorize +14 -7
  2. package/fixtures/team/book.json +6 -6
  3. package/fixtures/team/business.json +146 -20
  4. package/fixtures/team/content.json +6 -6
  5. package/fixtures/team/design.json +148 -20
  6. package/fixtures/team/mixed.json +206 -27
  7. package/fixtures/team/research.json +146 -20
  8. package/fixtures/team/software.json +148 -20
  9. package/fixtures/truncation-corpus/_generate-corpus.js +367 -0
  10. package/fixtures/truncation-corpus/fx-01-clean-exit-01/events.jsonl +2 -0
  11. package/fixtures/truncation-corpus/fx-01-clean-exit-01/intent-journal.jsonl +2 -0
  12. package/fixtures/truncation-corpus/fx-01-clean-exit-01/meta.json +18 -0
  13. package/fixtures/truncation-corpus/fx-01-clean-exit-01/target/.ijfw/state/workflow.json +1 -0
  14. package/fixtures/truncation-corpus/fx-01-clean-exit-02/events.jsonl +2 -0
  15. package/fixtures/truncation-corpus/fx-01-clean-exit-02/intent-journal.jsonl +2 -0
  16. package/fixtures/truncation-corpus/fx-01-clean-exit-02/meta.json +18 -0
  17. package/fixtures/truncation-corpus/fx-01-clean-exit-02/target/.ijfw/state/workflow.json +1 -0
  18. package/fixtures/truncation-corpus/fx-01-clean-exit-03/events.jsonl +2 -0
  19. package/fixtures/truncation-corpus/fx-01-clean-exit-03/intent-journal.jsonl +2 -0
  20. package/fixtures/truncation-corpus/fx-01-clean-exit-03/meta.json +18 -0
  21. package/fixtures/truncation-corpus/fx-01-clean-exit-03/target/.ijfw/state/workflow.json +1 -0
  22. package/fixtures/truncation-corpus/fx-01-clean-exit-04/events.jsonl +2 -0
  23. package/fixtures/truncation-corpus/fx-01-clean-exit-04/intent-journal.jsonl +2 -0
  24. package/fixtures/truncation-corpus/fx-01-clean-exit-04/meta.json +18 -0
  25. package/fixtures/truncation-corpus/fx-01-clean-exit-04/target/.ijfw/state/workflow.json +1 -0
  26. package/fixtures/truncation-corpus/fx-01-clean-exit-05/events.jsonl +2 -0
  27. package/fixtures/truncation-corpus/fx-01-clean-exit-05/intent-journal.jsonl +2 -0
  28. package/fixtures/truncation-corpus/fx-01-clean-exit-05/meta.json +18 -0
  29. package/fixtures/truncation-corpus/fx-01-clean-exit-05/target/.ijfw/state/workflow.json +1 -0
  30. package/fixtures/truncation-corpus/fx-02-mid-overwrite-01/events.jsonl +1 -0
  31. package/fixtures/truncation-corpus/fx-02-mid-overwrite-01/intent-journal.jsonl +3 -0
  32. package/fixtures/truncation-corpus/fx-02-mid-overwrite-01/meta.json +18 -0
  33. package/fixtures/truncation-corpus/fx-02-mid-overwrite-01/snapshots/v-midO-1-advance.json +11 -0
  34. package/fixtures/truncation-corpus/fx-02-mid-overwrite-01/target/.ijfw/state/workflow.json +1 -0
  35. package/fixtures/truncation-corpus/fx-02-mid-overwrite-02/events.jsonl +1 -0
  36. package/fixtures/truncation-corpus/fx-02-mid-overwrite-02/intent-journal.jsonl +3 -0
  37. package/fixtures/truncation-corpus/fx-02-mid-overwrite-02/meta.json +18 -0
  38. package/fixtures/truncation-corpus/fx-02-mid-overwrite-02/snapshots/v-midO-2-advance.json +11 -0
  39. package/fixtures/truncation-corpus/fx-02-mid-overwrite-02/target/.ijfw/state/workflow.json +1 -0
  40. package/fixtures/truncation-corpus/fx-02-mid-overwrite-03/events.jsonl +1 -0
  41. package/fixtures/truncation-corpus/fx-02-mid-overwrite-03/intent-journal.jsonl +3 -0
  42. package/fixtures/truncation-corpus/fx-02-mid-overwrite-03/meta.json +18 -0
  43. package/fixtures/truncation-corpus/fx-02-mid-overwrite-03/snapshots/v-midO-3-advance.json +11 -0
  44. package/fixtures/truncation-corpus/fx-02-mid-overwrite-03/target/.ijfw/state/workflow.json +1 -0
  45. package/fixtures/truncation-corpus/fx-02-mid-overwrite-04/events.jsonl +1 -0
  46. package/fixtures/truncation-corpus/fx-02-mid-overwrite-04/intent-journal.jsonl +3 -0
  47. package/fixtures/truncation-corpus/fx-02-mid-overwrite-04/meta.json +18 -0
  48. package/fixtures/truncation-corpus/fx-02-mid-overwrite-04/snapshots/v-midO-4-advance.json +11 -0
  49. package/fixtures/truncation-corpus/fx-02-mid-overwrite-04/target/.ijfw/state/workflow.json +1 -0
  50. package/fixtures/truncation-corpus/fx-02-mid-overwrite-05/events.jsonl +1 -0
  51. package/fixtures/truncation-corpus/fx-02-mid-overwrite-05/intent-journal.jsonl +3 -0
  52. package/fixtures/truncation-corpus/fx-02-mid-overwrite-05/meta.json +18 -0
  53. package/fixtures/truncation-corpus/fx-02-mid-overwrite-05/snapshots/v-midO-5-advance.json +11 -0
  54. package/fixtures/truncation-corpus/fx-02-mid-overwrite-05/target/.ijfw/state/workflow.json +1 -0
  55. package/fixtures/truncation-corpus/fx-03-mid-append-01/events.jsonl +1 -0
  56. package/fixtures/truncation-corpus/fx-03-mid-append-01/intent-journal.jsonl +3 -0
  57. package/fixtures/truncation-corpus/fx-03-mid-append-01/meta.json +18 -0
  58. package/fixtures/truncation-corpus/fx-03-mid-append-01/target/.ijfw/blackboard/decisions.jsonl +1 -0
  59. package/fixtures/truncation-corpus/fx-03-mid-append-02/events.jsonl +1 -0
  60. package/fixtures/truncation-corpus/fx-03-mid-append-02/intent-journal.jsonl +3 -0
  61. package/fixtures/truncation-corpus/fx-03-mid-append-02/meta.json +18 -0
  62. package/fixtures/truncation-corpus/fx-03-mid-append-02/target/.ijfw/blackboard/decisions.jsonl +1 -0
  63. package/fixtures/truncation-corpus/fx-03-mid-append-03/events.jsonl +1 -0
  64. package/fixtures/truncation-corpus/fx-03-mid-append-03/intent-journal.jsonl +3 -0
  65. package/fixtures/truncation-corpus/fx-03-mid-append-03/meta.json +18 -0
  66. package/fixtures/truncation-corpus/fx-03-mid-append-03/target/.ijfw/blackboard/decisions.jsonl +1 -0
  67. package/fixtures/truncation-corpus/fx-03-mid-append-04/events.jsonl +1 -0
  68. package/fixtures/truncation-corpus/fx-03-mid-append-04/intent-journal.jsonl +3 -0
  69. package/fixtures/truncation-corpus/fx-03-mid-append-04/meta.json +18 -0
  70. package/fixtures/truncation-corpus/fx-03-mid-append-04/target/.ijfw/blackboard/decisions.jsonl +1 -0
  71. package/fixtures/truncation-corpus/fx-03-mid-append-05/events.jsonl +1 -0
  72. package/fixtures/truncation-corpus/fx-03-mid-append-05/intent-journal.jsonl +3 -0
  73. package/fixtures/truncation-corpus/fx-03-mid-append-05/meta.json +18 -0
  74. package/fixtures/truncation-corpus/fx-03-mid-append-05/target/.ijfw/blackboard/decisions.jsonl +1 -0
  75. package/fixtures/truncation-corpus/fx-04-no-events-01/events.jsonl +0 -0
  76. package/fixtures/truncation-corpus/fx-04-no-events-01/intent-journal.jsonl +1 -0
  77. package/fixtures/truncation-corpus/fx-04-no-events-01/meta.json +18 -0
  78. package/fixtures/truncation-corpus/fx-04-no-events-01/snapshots/v-noEv-1-set-phase.json +11 -0
  79. package/fixtures/truncation-corpus/fx-04-no-events-01/target/.ijfw/state/workflow.json +1 -0
  80. package/fixtures/truncation-corpus/fx-04-no-events-02/events.jsonl +0 -0
  81. package/fixtures/truncation-corpus/fx-04-no-events-02/intent-journal.jsonl +1 -0
  82. package/fixtures/truncation-corpus/fx-04-no-events-02/meta.json +18 -0
  83. package/fixtures/truncation-corpus/fx-04-no-events-02/snapshots/v-noEv-2-set-phase.json +11 -0
  84. package/fixtures/truncation-corpus/fx-04-no-events-02/target/.ijfw/state/workflow.json +1 -0
  85. package/fixtures/truncation-corpus/fx-04-no-events-03/events.jsonl +0 -0
  86. package/fixtures/truncation-corpus/fx-04-no-events-03/intent-journal.jsonl +1 -0
  87. package/fixtures/truncation-corpus/fx-04-no-events-03/meta.json +18 -0
  88. package/fixtures/truncation-corpus/fx-04-no-events-03/snapshots/v-noEv-3-set-phase.json +11 -0
  89. package/fixtures/truncation-corpus/fx-04-no-events-03/target/.ijfw/state/workflow.json +1 -0
  90. package/fixtures/truncation-corpus/fx-04-no-events-04/events.jsonl +0 -0
  91. package/fixtures/truncation-corpus/fx-04-no-events-04/intent-journal.jsonl +1 -0
  92. package/fixtures/truncation-corpus/fx-04-no-events-04/meta.json +18 -0
  93. package/fixtures/truncation-corpus/fx-04-no-events-04/snapshots/v-noEv-4-set-phase.json +11 -0
  94. package/fixtures/truncation-corpus/fx-04-no-events-04/target/.ijfw/state/workflow.json +1 -0
  95. package/fixtures/truncation-corpus/fx-04-no-events-05/events.jsonl +0 -0
  96. package/fixtures/truncation-corpus/fx-04-no-events-05/intent-journal.jsonl +1 -0
  97. package/fixtures/truncation-corpus/fx-04-no-events-05/meta.json +18 -0
  98. package/fixtures/truncation-corpus/fx-04-no-events-05/snapshots/v-noEv-5-set-phase.json +11 -0
  99. package/fixtures/truncation-corpus/fx-04-no-events-05/target/.ijfw/state/workflow.json +1 -0
  100. package/fixtures/truncation-corpus/fx-05-error-terminated-01/events.jsonl +2 -0
  101. package/fixtures/truncation-corpus/fx-05-error-terminated-01/intent-journal.jsonl +3 -0
  102. package/fixtures/truncation-corpus/fx-05-error-terminated-01/meta.json +18 -0
  103. package/fixtures/truncation-corpus/fx-05-error-terminated-01/snapshots/v-errT-1-partial.json +11 -0
  104. package/fixtures/truncation-corpus/fx-05-error-terminated-01/target/.ijfw/state/workflow.json +1 -0
  105. package/fixtures/truncation-corpus/fx-05-error-terminated-02/events.jsonl +2 -0
  106. package/fixtures/truncation-corpus/fx-05-error-terminated-02/intent-journal.jsonl +3 -0
  107. package/fixtures/truncation-corpus/fx-05-error-terminated-02/meta.json +18 -0
  108. package/fixtures/truncation-corpus/fx-05-error-terminated-02/target/.ijfw/blackboard/decisions.jsonl +1 -0
  109. package/fixtures/truncation-corpus/fx-05-error-terminated-03/events.jsonl +2 -0
  110. package/fixtures/truncation-corpus/fx-05-error-terminated-03/intent-journal.jsonl +3 -0
  111. package/fixtures/truncation-corpus/fx-05-error-terminated-03/meta.json +18 -0
  112. package/fixtures/truncation-corpus/fx-05-error-terminated-03/snapshots/v-errT-3-partial.json +11 -0
  113. package/fixtures/truncation-corpus/fx-05-error-terminated-03/target/.ijfw/state/workflow.json +1 -0
  114. package/fixtures/truncation-corpus/fx-05-error-terminated-04/events.jsonl +2 -0
  115. package/fixtures/truncation-corpus/fx-05-error-terminated-04/intent-journal.jsonl +3 -0
  116. package/fixtures/truncation-corpus/fx-05-error-terminated-04/meta.json +18 -0
  117. package/fixtures/truncation-corpus/fx-05-error-terminated-04/target/.ijfw/blackboard/decisions.jsonl +1 -0
  118. package/fixtures/truncation-corpus/fx-05-error-terminated-05/events.jsonl +2 -0
  119. package/fixtures/truncation-corpus/fx-05-error-terminated-05/intent-journal.jsonl +3 -0
  120. package/fixtures/truncation-corpus/fx-05-error-terminated-05/meta.json +18 -0
  121. package/fixtures/truncation-corpus/fx-05-error-terminated-05/snapshots/v-errT-5-partial.json +11 -0
  122. package/fixtures/truncation-corpus/fx-05-error-terminated-05/target/.ijfw/state/workflow.json +1 -0
  123. package/package.json +6 -3
  124. package/src/active-extension-writer.js +144 -64
  125. package/src/api-client.js +43 -5
  126. package/src/audit-roster.js +80 -5
  127. package/src/blackboard.js +298 -6
  128. package/src/cli-run.js +33 -5
  129. package/src/codex-agents.js +96 -5
  130. package/src/cost/aggregator.js +39 -9
  131. package/src/cost/pricing.js +57 -0
  132. package/src/cost/readers/gemini.js +1 -1
  133. package/src/cross-audit-chunker.js +189 -0
  134. package/src/cross-dispatcher.js +124 -21
  135. package/src/cross-orchestrator-cli.js +754 -159
  136. package/src/cross-orchestrator.js +1065 -17
  137. package/src/cross-project-search.js +195 -9
  138. package/src/dashboard-client-waves.html +304 -0
  139. package/src/dashboard-client.html +5 -1
  140. package/src/dashboard-server.js +73 -0
  141. package/src/deploy-alerts.js +150 -0
  142. package/src/design/iframe-bridge.js +242 -0
  143. package/src/design-companion.js +144 -0
  144. package/src/dispatch/checkpoint-cli.js +97 -0
  145. package/src/dispatch/colon-syntax.js +81 -1
  146. package/src/dispatch/extension.js +26 -2
  147. package/src/dispatch/registry-cli.js +4 -1
  148. package/src/dispatch/wave-cli.js +201 -6
  149. package/src/dispatch/worktree-cli.js +40 -0
  150. package/src/dispatch-planner.js +97 -2
  151. package/src/dream/runner.mjs +47 -11
  152. package/src/dream/stage-runner.js +40 -0
  153. package/src/dream/state-file.js +102 -0
  154. package/src/extension-installer.js +70 -24
  155. package/src/extension-quota-tracker.js +4 -2
  156. package/src/extension-registry.js +289 -35
  157. package/src/feedback-detector.js +26 -0
  158. package/src/fs-lock.js +259 -7
  159. package/src/gate-result.js +95 -1
  160. package/src/hardware-signer.js +4 -2
  161. package/src/hero-line.js +86 -5
  162. package/src/intent-router.js +35 -0
  163. package/src/lib/a11y-contract.js +117 -0
  164. package/src/lib/atomic-io.js +29 -8
  165. package/src/lib/cache-keepalive.js +150 -0
  166. package/src/lib/jsonl-rotation.js +104 -0
  167. package/src/lib/lighthouse-pillar.js +121 -0
  168. package/src/lib/llm-call.js +121 -0
  169. package/src/lib/playwright-baseline.js +205 -0
  170. package/src/lib/rekor-bridge.js +221 -0
  171. package/src/lib/repo-map.js +392 -0
  172. package/src/lib/shasum-verify.js +164 -0
  173. package/src/lib/sketches-gc.js +132 -0
  174. package/src/lib/tmp-suffix.js +62 -0
  175. package/src/lib/ui-review-runner.js +595 -0
  176. package/src/lib/uispec-drift.js +301 -0
  177. package/src/lib/uispec-intake.js +381 -0
  178. package/src/lib/worktree-guards.js +118 -0
  179. package/src/lib/worktree-recovery.js +100 -0
  180. package/src/memory/auto-linker.js +267 -0
  181. package/src/memory/benchmark.js +498 -0
  182. package/src/memory/dedup.js +126 -0
  183. package/src/memory/embedding-cache.js +136 -0
  184. package/src/memory/fact-extractor.js +168 -0
  185. package/src/memory/fts5.js +65 -1
  186. package/src/memory/migration-runner.js +6 -1
  187. package/src/memory/migrations/004-bitemporal.js +91 -0
  188. package/src/memory/migrations/005-vector-cache.js +61 -0
  189. package/src/memory/migrations/006-obsidian-graph.js +46 -0
  190. package/src/memory/migrations/007-skill-telemetry.js +24 -0
  191. package/src/memory/migrations/008-write-provenance.js +41 -0
  192. package/src/memory/migrations/009-obsidian-backfill.js +50 -0
  193. package/src/memory/obsidian-parser.js +152 -0
  194. package/src/memory/query-dataview.js +86 -0
  195. package/src/memory/search.js +46 -15
  196. package/src/memory/temporal.js +529 -0
  197. package/src/memory/tokenize.js +10 -0
  198. package/src/memory-facts-handler.js +37 -0
  199. package/src/memory-feedback.js +260 -2
  200. package/src/model-refresh.js +292 -0
  201. package/src/observability/cost-anomaly.js +166 -0
  202. package/src/observability/evaluator-checkpoint-contract.js +117 -0
  203. package/src/observability/trace-id.js +163 -0
  204. package/src/orchestrator/agents-md-blackboard.js +152 -0
  205. package/src/orchestrator/checkpoint-contract.md +140 -0
  206. package/src/orchestrator/debug-trident-trigger.js +374 -0
  207. package/src/orchestrator/debug-trident.js +570 -0
  208. package/src/orchestrator/merge-block-aware.js +350 -0
  209. package/src/orchestrator/plan-checker.js +475 -0
  210. package/src/orchestrator/post-done-runner.js +277 -0
  211. package/src/orchestrator/review.js +38 -3
  212. package/src/orchestrator/skill-telemetry-sink.js +29 -0
  213. package/src/orchestrator/skill-telemetry.js +37 -0
  214. package/src/orchestrator/state-events.js +459 -0
  215. package/src/orchestrator/state-sdk.js +1932 -0
  216. package/src/orchestrator/status-protocol.js +84 -17
  217. package/src/orchestrator/subagent-telemetry.js +471 -0
  218. package/src/orchestrator/termination.js +160 -0
  219. package/src/orchestrator/verification-gate.js +200 -16
  220. package/src/orchestrator/wave-state.js +332 -23
  221. package/src/orchestrator/worktree-provision.js +77 -0
  222. package/src/override-resolver.js +5 -3
  223. package/src/override-use-registry.js +111 -5
  224. package/src/receipts.js +36 -4
  225. package/src/recovery/checkpoint.js +56 -3
  226. package/src/recovery/code-fixer.js +961 -0
  227. package/src/recovery/truncation.js +317 -0
  228. package/src/redactor.js +75 -6
  229. package/src/runtime-mediator.js +15 -1
  230. package/src/sanitizer.js +10 -0
  231. package/src/search-hybrid.js +139 -0
  232. package/src/server.js +795 -112
  233. package/src/swarm/worktree.js +27 -4
  234. package/src/swarm-config.js +102 -17
  235. package/src/team/domain-templates/book.json +51 -0
  236. package/src/team/domain-templates/business.json +44 -0
  237. package/src/team/domain-templates/content.json +50 -0
  238. package/src/team/domain-templates/design.json +44 -0
  239. package/src/team/domain-templates/research.json +44 -0
  240. package/src/team/domain-templates/software.json +40 -0
  241. package/src/team/generator.js +440 -3
  242. package/src/team/modify.js +203 -0
  243. package/src/team/schemas.js +48 -0
  244. package/src/update-apply.js +19 -3
  245. package/src/dashboard-charts.js +0 -239
@@ -0,0 +1,367 @@
1
+ /**
2
+ * _generate-corpus.js — DETERMINISTIC generator for the T20 truncation
3
+ * fixture corpus. Each fixture subdirectory carries every artifact the
4
+ * recovery test needs to materialise a real temp project AND verify the
5
+ * post-recovery state:
6
+ *
7
+ * - `meta.json` — fixture descriptor (category, target files,
8
+ * expected final state, expected recovery
9
+ * verdict).
10
+ * - `events.jsonl` — pre-recorded per-subagent event log (T5
11
+ * envelope shape). Empty for `no-events`.
12
+ * - `intent-journal.jsonl` — pre-recorded intent journal (T4 begin/commit
13
+ * records).
14
+ * - `snapshots/<verbId>.json` — pre-recorded snapshot sidecars for
15
+ * overwrite-verb partials.
16
+ * - `target/<rel>` — pre-recorded ON-DISK content of target files
17
+ * AT THE MOMENT OF TRUNCATION (i.e. AFTER a
18
+ * begin's mutation, BEFORE its commit).
19
+ *
20
+ * The test reconstructs a project from these artifacts, runs the recovery
21
+ * module, then asserts that each target file matches the expected final
22
+ * state in `meta.json`.
23
+ *
24
+ * Run once: `node _generate-corpus.js` → writes 25 fixtures.
25
+ * (This script is NOT invoked by the test — it just produces the
26
+ * deterministic fixtures we then check into the repo.)
27
+ */
28
+
29
+ import {
30
+ existsSync, mkdirSync, rmSync, writeFileSync,
31
+ } from 'node:fs';
32
+ import { dirname, join } from 'node:path';
33
+ import { fileURLToPath } from 'node:url';
34
+
35
+ const __dirname = dirname(fileURLToPath(import.meta.url));
36
+
37
+ const TS = '2026-05-20T12:00:00.000Z';
38
+
39
+ // Helpers ----------------------------------------------------------------
40
+
41
+ function writeArtifact(absPath, content) {
42
+ mkdirSync(dirname(absPath), { recursive: true });
43
+ writeFileSync(absPath, content);
44
+ }
45
+
46
+ function asJsonl(records) {
47
+ return records.map((r) => JSON.stringify(r)).join('\n') + (records.length ? '\n' : '');
48
+ }
49
+
50
+ function buildEvent({ seq, verb, subId, verbId, outcome = 'ok' }) {
51
+ return {
52
+ seq,
53
+ verb,
54
+ subagentId: subId,
55
+ ts: TS,
56
+ verbId,
57
+ outcome,
58
+ payloadDigest: `sha256-fixture-${verbId}`,
59
+ };
60
+ }
61
+
62
+ function buildBegin({ verb, verbId, targets, kind, payloadDigest, dedupKey }) {
63
+ const rec = {
64
+ verb, verbId, phase: 'begin', ts: TS, targets,
65
+ payloadDigest: payloadDigest || `sha256-fixture-${verbId}`,
66
+ kind,
67
+ };
68
+ if (dedupKey) rec.dedupKey = dedupKey;
69
+ return rec;
70
+ }
71
+
72
+ function buildCommit({ verb, verbId, kind, payloadDigest, dedupKey }) {
73
+ const rec = {
74
+ verb, verbId, phase: 'commit', ts: TS,
75
+ payloadDigest: payloadDigest || `sha256-fixture-${verbId}`,
76
+ kind,
77
+ };
78
+ if (dedupKey) rec.dedupKey = dedupKey;
79
+ return rec;
80
+ }
81
+
82
+ function writeFixture(id, {
83
+ category, subId, waveId, eventStream, journal, snapshots, targetFiles,
84
+ expectedFinalState, expectedDetection, expectedRecovery, notes,
85
+ }) {
86
+ const dir = join(__dirname, id);
87
+ if (existsSync(dir)) rmSync(dir, { recursive: true, force: true });
88
+ mkdirSync(dir, { recursive: true });
89
+
90
+ // meta.json — the test contract.
91
+ const meta = {
92
+ id, category, waveId, subId,
93
+ notes: notes || '',
94
+ expectedDetection, // { truncated: false|string, reasonContains?: string }
95
+ expectedRecovery, // { recovered: boolean }
96
+ expectedFinalState, // { '<rel>': '<content>' | null (deleted) }
97
+ expectedTerminalVerb: 'subagent.post-done',
98
+ };
99
+ writeArtifact(join(dir, 'meta.json'), `${JSON.stringify(meta, null, 2)}\n`);
100
+
101
+ // events.jsonl
102
+ writeArtifact(join(dir, 'events.jsonl'), asJsonl(eventStream));
103
+
104
+ // intent-journal.jsonl
105
+ writeArtifact(join(dir, 'intent-journal.jsonl'), asJsonl(journal));
106
+
107
+ // snapshots/ — only present when a snapshot was captured
108
+ for (const [verbId, snap] of Object.entries(snapshots || {})) {
109
+ writeArtifact(join(dir, 'snapshots', `${verbId}.json`), `${JSON.stringify(snap, null, 2)}\n`);
110
+ }
111
+
112
+ // target/ — content of mutated files at the moment of truncation
113
+ for (const [rel, content] of Object.entries(targetFiles || {})) {
114
+ if (content === null) continue; // intentionally absent
115
+ writeArtifact(join(dir, 'target', rel), content);
116
+ }
117
+ }
118
+
119
+ // =======================================================================
120
+ // Category 1: clean-exit (5) — full run, terminal verb, no partials.
121
+ // Recovery is essentially a no-op; the expected final state matches the
122
+ // state at truncation time. Tests prove recovery does not corrupt clean
123
+ // runs.
124
+ // =======================================================================
125
+
126
+ for (let i = 1; i <= 5; i += 1) {
127
+ const subId = `cleanA${i}`;
128
+ const verb1Id = `v-clean-${i}-set-phase`;
129
+ const verb2Id = `v-clean-${i}-post-done`;
130
+ const wfRel = '.ijfw/state/workflow.json';
131
+ const wfAfter = JSON.stringify({ phase: `clean-${i}`, ts: TS });
132
+
133
+ writeFixture(`fx-01-clean-exit-${String(i).padStart(2, '0')}`, {
134
+ category: 'clean-exit',
135
+ waveId: `W20-clean-${i}`,
136
+ subId,
137
+ eventStream: [
138
+ buildEvent({ seq: 1, verb: 'workflow.set-phase', subId, verbId: verb1Id }),
139
+ buildEvent({ seq: 2, verb: 'subagent.post-done', subId, verbId: verb2Id }),
140
+ ],
141
+ journal: [
142
+ buildBegin({ verb: 'workflow.set-phase', verbId: verb1Id, targets: [wfRel], kind: 'overwrite' }),
143
+ buildCommit({ verb: 'workflow.set-phase', verbId: verb1Id, kind: 'overwrite' }),
144
+ ],
145
+ snapshots: {}, // overwrite verb committed — sidecar already cleaned
146
+ targetFiles: { [wfRel]: wfAfter }, // post-commit content
147
+ expectedFinalState: { [wfRel]: wfAfter }, // recovery leaves clean state untouched
148
+ expectedDetection: { truncated: false, reasonContains: 'clean exit' },
149
+ expectedRecovery: { recovered: true },
150
+ notes: 'baseline — clean exit with terminal verb; recovery is a no-op',
151
+ });
152
+ }
153
+
154
+ // =======================================================================
155
+ // Category 2: mid-overwrite (5) — overwrite verb began, mutated target,
156
+ // no commit. Snapshot sidecar exists with pre-begin content. Recovery
157
+ // MUST snapshot-roll-back: target restored to pre-begin content.
158
+ // =======================================================================
159
+
160
+ for (let i = 1; i <= 5; i += 1) {
161
+ const subId = `midO${i}`;
162
+ const verb1Id = `v-midO-${i}-set-phase`; // already committed
163
+ const verb2Id = `v-midO-${i}-advance`; // PARTIAL — overwrite verb
164
+ const wfRel = '.ijfw/state/workflow.json';
165
+ const wfBefore = JSON.stringify({ phase: `midO-pre-${i}` });
166
+ const wfHalfApplied = JSON.stringify({ phase: `midO-HALF-${i}`, partial: true });
167
+
168
+ writeFixture(`fx-02-mid-overwrite-${String(i).padStart(2, '0')}`, {
169
+ category: 'mid-overwrite',
170
+ waveId: `W20-midO-${i}`,
171
+ subId,
172
+ eventStream: [
173
+ buildEvent({ seq: 1, verb: 'workflow.set-phase', subId, verbId: verb1Id }),
174
+ // No event for the partial verb's commit — the stream truncates here.
175
+ ],
176
+ journal: [
177
+ buildBegin({ verb: 'workflow.set-phase', verbId: verb1Id, targets: [wfRel], kind: 'overwrite' }),
178
+ buildCommit({ verb: 'workflow.set-phase', verbId: verb1Id, kind: 'overwrite' }),
179
+ // Partial: begin written, target mutated, no commit.
180
+ buildBegin({ verb: 'wave.advance', verbId: verb2Id, targets: [wfRel], kind: 'overwrite' }),
181
+ ],
182
+ snapshots: {
183
+ [verb2Id]: {
184
+ verbId: verb2Id,
185
+ targets: [{
186
+ relPath: wfRel,
187
+ absPath: '<<ABS>>', // test fills in
188
+ existed: true,
189
+ content: wfBefore,
190
+ }],
191
+ },
192
+ },
193
+ // At truncation moment, file holds the half-applied content.
194
+ targetFiles: { [wfRel]: wfHalfApplied },
195
+ // After recovery: target restored to pre-begin content.
196
+ expectedFinalState: { [wfRel]: wfBefore },
197
+ expectedDetection: { truncated: 'open-partial', reasonContains: 'open begin' },
198
+ expectedRecovery: { recovered: true },
199
+ notes: 'overwrite verb partial — snapshot-rollback restores wfBefore',
200
+ });
201
+ }
202
+
203
+ // =======================================================================
204
+ // Category 3: mid-append (5) — append verb began, appended a durable
205
+ // record, no commit. Snapshot is INTENTIONALLY ABSENT (append verbs
206
+ // never write one — §4). Recovery seals the partial; the append stays.
207
+ // =======================================================================
208
+
209
+ for (let i = 1; i <= 5; i += 1) {
210
+ const subId = `midA${i}`;
211
+ const verbAddId = `v-midA-${i}-decision-add`; // PARTIAL — append verb
212
+ const decisionsRel = '.ijfw/blackboard/decisions.jsonl';
213
+ const decisionsAfter = `${JSON.stringify({
214
+ id: `d-${i}`, text: `decision ${i}`, dedupKey: `dk-midA-${i}`,
215
+ })}\n`;
216
+
217
+ writeFixture(`fx-03-mid-append-${String(i).padStart(2, '0')}`, {
218
+ category: 'mid-append',
219
+ waveId: `W20-midA-${i}`,
220
+ subId,
221
+ eventStream: [
222
+ // Append verb's tap event MAY have fired before truncation — include
223
+ // a non-terminal subagent.checkpoint to make detection clearer.
224
+ buildEvent({ seq: 1, verb: 'subagent.checkpoint', subId, verbId: `v-midA-${i}-ckpt` }),
225
+ ],
226
+ journal: [
227
+ // Already-committed append (sealed by a prior commit).
228
+ buildBegin({
229
+ verb: 'subagent.checkpoint', verbId: `v-midA-${i}-ckpt`,
230
+ targets: ['.ijfw/wave-W20-midA-' + i + '/subagent-' + subId + '.checkpoint.json'],
231
+ kind: 'append', dedupKey: `dk-ckpt-midA-${i}`,
232
+ }),
233
+ buildCommit({ verb: 'subagent.checkpoint', verbId: `v-midA-${i}-ckpt`, kind: 'append', dedupKey: `dk-ckpt-midA-${i}` }),
234
+ // Partial append: begin only, no commit.
235
+ buildBegin({
236
+ verb: 'decision.add', verbId: verbAddId, targets: [decisionsRel],
237
+ kind: 'append', dedupKey: `dk-midA-${i}`,
238
+ }),
239
+ ],
240
+ snapshots: {},
241
+ // Truncation moment: the durable append IS on disk.
242
+ targetFiles: { [decisionsRel]: decisionsAfter },
243
+ // After recovery: the append IS PRESERVED (seal-in-place, not rolled back).
244
+ expectedFinalState: { [decisionsRel]: decisionsAfter },
245
+ expectedDetection: { truncated: 'open-partial', reasonContains: 'open begin' },
246
+ expectedRecovery: { recovered: true },
247
+ notes: 'append verb partial — sealed in place; durable append preserved',
248
+ });
249
+ }
250
+
251
+ // =======================================================================
252
+ // Category 4: no-events (5) — subagent dispatched but emitted no events
253
+ // (truncated before its first tap fired). Journal carries an open begin.
254
+ // Recovery snapshot-rolls-back the overwrite partial.
255
+ // =======================================================================
256
+
257
+ for (let i = 1; i <= 5; i += 1) {
258
+ const subId = `noEv${i}`;
259
+ const verbId = `v-noEv-${i}-set-phase`;
260
+ const wfRel = '.ijfw/state/workflow.json';
261
+ const wfBefore = JSON.stringify({ phase: `noEv-pre-${i}` });
262
+ const wfPartial = JSON.stringify({ phase: `noEv-PARTIAL-${i}` });
263
+
264
+ writeFixture(`fx-04-no-events-${String(i).padStart(2, '0')}`, {
265
+ category: 'no-events',
266
+ waveId: `W20-noEv-${i}`,
267
+ subId,
268
+ eventStream: [], // emit truncated before tap
269
+ journal: [
270
+ buildBegin({ verb: 'workflow.set-phase', verbId, targets: [wfRel], kind: 'overwrite' }),
271
+ ],
272
+ snapshots: {
273
+ [verbId]: {
274
+ verbId,
275
+ targets: [{
276
+ relPath: wfRel,
277
+ absPath: '<<ABS>>',
278
+ existed: true,
279
+ content: wfBefore,
280
+ }],
281
+ },
282
+ },
283
+ targetFiles: { [wfRel]: wfPartial },
284
+ expectedFinalState: { [wfRel]: wfBefore },
285
+ expectedDetection: { truncated: 'no-events-open-begin', reasonContains: 'no events' },
286
+ expectedRecovery: { recovered: true },
287
+ notes: 'subagent emitted no events; recovery rolls back open begin',
288
+ });
289
+ }
290
+
291
+ // =======================================================================
292
+ // Category 5: error-terminated (5) — stream ends with outcome:'error'.
293
+ // Journal partial closes via replay. Two flavours: overwrite-partial
294
+ // after error (rollback) + append-partial after error (seal).
295
+ // =======================================================================
296
+
297
+ for (let i = 1; i <= 5; i += 1) {
298
+ const subId = `errT${i}`;
299
+ // Alternate between overwrite-partial (odd i) and append-partial (even).
300
+ const isOverwrite = i % 2 === 1;
301
+ const partialVerbId = `v-errT-${i}-partial`;
302
+ const errEventVerb = isOverwrite ? 'wave.advance' : 'decision.add';
303
+
304
+ if (isOverwrite) {
305
+ const wfRel = '.ijfw/state/workflow.json';
306
+ const wfBefore = JSON.stringify({ phase: `errT-pre-${i}` });
307
+ const wfPartial = JSON.stringify({ phase: `errT-PARTIAL-${i}` });
308
+ writeFixture(`fx-05-error-terminated-${String(i).padStart(2, '0')}`, {
309
+ category: 'error-terminated',
310
+ waveId: `W20-errT-${i}`,
311
+ subId,
312
+ eventStream: [
313
+ buildEvent({ seq: 1, verb: 'workflow.set-phase', subId, verbId: `v-errT-${i}-ok` }),
314
+ buildEvent({ seq: 2, verb: errEventVerb, subId, verbId: partialVerbId, outcome: 'error' }),
315
+ ],
316
+ journal: [
317
+ buildBegin({ verb: 'workflow.set-phase', verbId: `v-errT-${i}-ok`, targets: [wfRel], kind: 'overwrite' }),
318
+ buildCommit({ verb: 'workflow.set-phase', verbId: `v-errT-${i}-ok`, kind: 'overwrite' }),
319
+ buildBegin({ verb: errEventVerb, verbId: partialVerbId, targets: [wfRel], kind: 'overwrite' }),
320
+ ],
321
+ snapshots: {
322
+ [partialVerbId]: {
323
+ verbId: partialVerbId,
324
+ targets: [{
325
+ relPath: wfRel,
326
+ absPath: '<<ABS>>',
327
+ existed: true,
328
+ content: wfBefore,
329
+ }],
330
+ },
331
+ },
332
+ targetFiles: { [wfRel]: wfPartial },
333
+ expectedFinalState: { [wfRel]: wfBefore },
334
+ expectedDetection: { truncated: 'error-terminated', reasonContains: "outcome='error'" },
335
+ expectedRecovery: { recovered: true },
336
+ notes: 'error-terminated with overwrite partial — rollback restores baseline',
337
+ });
338
+ } else {
339
+ const decisionsRel = '.ijfw/blackboard/decisions.jsonl';
340
+ const decisionsAfter = `${JSON.stringify({
341
+ id: `d-errT-${i}`, text: `errT ${i}`, dedupKey: `dk-errT-${i}`,
342
+ })}\n`;
343
+ writeFixture(`fx-05-error-terminated-${String(i).padStart(2, '0')}`, {
344
+ category: 'error-terminated',
345
+ waveId: `W20-errT-${i}`,
346
+ subId,
347
+ eventStream: [
348
+ buildEvent({ seq: 1, verb: 'subagent.checkpoint', subId, verbId: `v-errT-${i}-ckpt` }),
349
+ buildEvent({ seq: 2, verb: errEventVerb, subId, verbId: partialVerbId, outcome: 'error' }),
350
+ ],
351
+ journal: [
352
+ buildBegin({ verb: 'subagent.checkpoint', verbId: `v-errT-${i}-ckpt`, targets: ['.ijfw/wave-W20-errT-' + i + '/subagent-' + subId + '.checkpoint.json'], kind: 'append', dedupKey: `dk-ckpt-errT-${i}` }),
353
+ buildCommit({ verb: 'subagent.checkpoint', verbId: `v-errT-${i}-ckpt`, kind: 'append', dedupKey: `dk-ckpt-errT-${i}` }),
354
+ buildBegin({ verb: errEventVerb, verbId: partialVerbId, targets: [decisionsRel], kind: 'append', dedupKey: `dk-errT-${i}` }),
355
+ ],
356
+ snapshots: {},
357
+ targetFiles: { [decisionsRel]: decisionsAfter },
358
+ expectedFinalState: { [decisionsRel]: decisionsAfter },
359
+ expectedDetection: { truncated: 'error-terminated', reasonContains: "outcome='error'" },
360
+ expectedRecovery: { recovered: true },
361
+ notes: 'error-terminated with append partial — seal-in-place preserves record',
362
+ });
363
+ }
364
+ }
365
+
366
+ // eslint-disable-next-line no-console
367
+ console.log('truncation-corpus: generated 25 fixtures (5 per category × 5 categories)');
@@ -0,0 +1,2 @@
1
+ {"seq":1,"verb":"workflow.set-phase","subagentId":"cleanA1","ts":"2026-05-20T12:00:00.000Z","verbId":"v-clean-1-set-phase","outcome":"ok","payloadDigest":"sha256-fixture-v-clean-1-set-phase"}
2
+ {"seq":2,"verb":"subagent.post-done","subagentId":"cleanA1","ts":"2026-05-20T12:00:00.000Z","verbId":"v-clean-1-post-done","outcome":"ok","payloadDigest":"sha256-fixture-v-clean-1-post-done"}
@@ -0,0 +1,2 @@
1
+ {"verb":"workflow.set-phase","verbId":"v-clean-1-set-phase","phase":"begin","ts":"2026-05-20T12:00:00.000Z","targets":[".ijfw/state/workflow.json"],"payloadDigest":"sha256-fixture-v-clean-1-set-phase","kind":"overwrite"}
2
+ {"verb":"workflow.set-phase","verbId":"v-clean-1-set-phase","phase":"commit","ts":"2026-05-20T12:00:00.000Z","payloadDigest":"sha256-fixture-v-clean-1-set-phase","kind":"overwrite"}
@@ -0,0 +1,18 @@
1
+ {
2
+ "id": "fx-01-clean-exit-01",
3
+ "category": "clean-exit",
4
+ "waveId": "W20-clean-1",
5
+ "subId": "cleanA1",
6
+ "notes": "baseline — clean exit with terminal verb; recovery is a no-op",
7
+ "expectedDetection": {
8
+ "truncated": false,
9
+ "reasonContains": "clean exit"
10
+ },
11
+ "expectedRecovery": {
12
+ "recovered": true
13
+ },
14
+ "expectedFinalState": {
15
+ ".ijfw/state/workflow.json": "{\"phase\":\"clean-1\",\"ts\":\"2026-05-20T12:00:00.000Z\"}"
16
+ },
17
+ "expectedTerminalVerb": "subagent.post-done"
18
+ }
@@ -0,0 +1 @@
1
+ {"phase":"clean-1","ts":"2026-05-20T12:00:00.000Z"}
@@ -0,0 +1,2 @@
1
+ {"seq":1,"verb":"workflow.set-phase","subagentId":"cleanA2","ts":"2026-05-20T12:00:00.000Z","verbId":"v-clean-2-set-phase","outcome":"ok","payloadDigest":"sha256-fixture-v-clean-2-set-phase"}
2
+ {"seq":2,"verb":"subagent.post-done","subagentId":"cleanA2","ts":"2026-05-20T12:00:00.000Z","verbId":"v-clean-2-post-done","outcome":"ok","payloadDigest":"sha256-fixture-v-clean-2-post-done"}
@@ -0,0 +1,2 @@
1
+ {"verb":"workflow.set-phase","verbId":"v-clean-2-set-phase","phase":"begin","ts":"2026-05-20T12:00:00.000Z","targets":[".ijfw/state/workflow.json"],"payloadDigest":"sha256-fixture-v-clean-2-set-phase","kind":"overwrite"}
2
+ {"verb":"workflow.set-phase","verbId":"v-clean-2-set-phase","phase":"commit","ts":"2026-05-20T12:00:00.000Z","payloadDigest":"sha256-fixture-v-clean-2-set-phase","kind":"overwrite"}
@@ -0,0 +1,18 @@
1
+ {
2
+ "id": "fx-01-clean-exit-02",
3
+ "category": "clean-exit",
4
+ "waveId": "W20-clean-2",
5
+ "subId": "cleanA2",
6
+ "notes": "baseline — clean exit with terminal verb; recovery is a no-op",
7
+ "expectedDetection": {
8
+ "truncated": false,
9
+ "reasonContains": "clean exit"
10
+ },
11
+ "expectedRecovery": {
12
+ "recovered": true
13
+ },
14
+ "expectedFinalState": {
15
+ ".ijfw/state/workflow.json": "{\"phase\":\"clean-2\",\"ts\":\"2026-05-20T12:00:00.000Z\"}"
16
+ },
17
+ "expectedTerminalVerb": "subagent.post-done"
18
+ }
@@ -0,0 +1 @@
1
+ {"phase":"clean-2","ts":"2026-05-20T12:00:00.000Z"}
@@ -0,0 +1,2 @@
1
+ {"seq":1,"verb":"workflow.set-phase","subagentId":"cleanA3","ts":"2026-05-20T12:00:00.000Z","verbId":"v-clean-3-set-phase","outcome":"ok","payloadDigest":"sha256-fixture-v-clean-3-set-phase"}
2
+ {"seq":2,"verb":"subagent.post-done","subagentId":"cleanA3","ts":"2026-05-20T12:00:00.000Z","verbId":"v-clean-3-post-done","outcome":"ok","payloadDigest":"sha256-fixture-v-clean-3-post-done"}
@@ -0,0 +1,2 @@
1
+ {"verb":"workflow.set-phase","verbId":"v-clean-3-set-phase","phase":"begin","ts":"2026-05-20T12:00:00.000Z","targets":[".ijfw/state/workflow.json"],"payloadDigest":"sha256-fixture-v-clean-3-set-phase","kind":"overwrite"}
2
+ {"verb":"workflow.set-phase","verbId":"v-clean-3-set-phase","phase":"commit","ts":"2026-05-20T12:00:00.000Z","payloadDigest":"sha256-fixture-v-clean-3-set-phase","kind":"overwrite"}
@@ -0,0 +1,18 @@
1
+ {
2
+ "id": "fx-01-clean-exit-03",
3
+ "category": "clean-exit",
4
+ "waveId": "W20-clean-3",
5
+ "subId": "cleanA3",
6
+ "notes": "baseline — clean exit with terminal verb; recovery is a no-op",
7
+ "expectedDetection": {
8
+ "truncated": false,
9
+ "reasonContains": "clean exit"
10
+ },
11
+ "expectedRecovery": {
12
+ "recovered": true
13
+ },
14
+ "expectedFinalState": {
15
+ ".ijfw/state/workflow.json": "{\"phase\":\"clean-3\",\"ts\":\"2026-05-20T12:00:00.000Z\"}"
16
+ },
17
+ "expectedTerminalVerb": "subagent.post-done"
18
+ }
@@ -0,0 +1 @@
1
+ {"phase":"clean-3","ts":"2026-05-20T12:00:00.000Z"}
@@ -0,0 +1,2 @@
1
+ {"seq":1,"verb":"workflow.set-phase","subagentId":"cleanA4","ts":"2026-05-20T12:00:00.000Z","verbId":"v-clean-4-set-phase","outcome":"ok","payloadDigest":"sha256-fixture-v-clean-4-set-phase"}
2
+ {"seq":2,"verb":"subagent.post-done","subagentId":"cleanA4","ts":"2026-05-20T12:00:00.000Z","verbId":"v-clean-4-post-done","outcome":"ok","payloadDigest":"sha256-fixture-v-clean-4-post-done"}
@@ -0,0 +1,2 @@
1
+ {"verb":"workflow.set-phase","verbId":"v-clean-4-set-phase","phase":"begin","ts":"2026-05-20T12:00:00.000Z","targets":[".ijfw/state/workflow.json"],"payloadDigest":"sha256-fixture-v-clean-4-set-phase","kind":"overwrite"}
2
+ {"verb":"workflow.set-phase","verbId":"v-clean-4-set-phase","phase":"commit","ts":"2026-05-20T12:00:00.000Z","payloadDigest":"sha256-fixture-v-clean-4-set-phase","kind":"overwrite"}
@@ -0,0 +1,18 @@
1
+ {
2
+ "id": "fx-01-clean-exit-04",
3
+ "category": "clean-exit",
4
+ "waveId": "W20-clean-4",
5
+ "subId": "cleanA4",
6
+ "notes": "baseline — clean exit with terminal verb; recovery is a no-op",
7
+ "expectedDetection": {
8
+ "truncated": false,
9
+ "reasonContains": "clean exit"
10
+ },
11
+ "expectedRecovery": {
12
+ "recovered": true
13
+ },
14
+ "expectedFinalState": {
15
+ ".ijfw/state/workflow.json": "{\"phase\":\"clean-4\",\"ts\":\"2026-05-20T12:00:00.000Z\"}"
16
+ },
17
+ "expectedTerminalVerb": "subagent.post-done"
18
+ }
@@ -0,0 +1 @@
1
+ {"phase":"clean-4","ts":"2026-05-20T12:00:00.000Z"}
@@ -0,0 +1,2 @@
1
+ {"seq":1,"verb":"workflow.set-phase","subagentId":"cleanA5","ts":"2026-05-20T12:00:00.000Z","verbId":"v-clean-5-set-phase","outcome":"ok","payloadDigest":"sha256-fixture-v-clean-5-set-phase"}
2
+ {"seq":2,"verb":"subagent.post-done","subagentId":"cleanA5","ts":"2026-05-20T12:00:00.000Z","verbId":"v-clean-5-post-done","outcome":"ok","payloadDigest":"sha256-fixture-v-clean-5-post-done"}
@@ -0,0 +1,2 @@
1
+ {"verb":"workflow.set-phase","verbId":"v-clean-5-set-phase","phase":"begin","ts":"2026-05-20T12:00:00.000Z","targets":[".ijfw/state/workflow.json"],"payloadDigest":"sha256-fixture-v-clean-5-set-phase","kind":"overwrite"}
2
+ {"verb":"workflow.set-phase","verbId":"v-clean-5-set-phase","phase":"commit","ts":"2026-05-20T12:00:00.000Z","payloadDigest":"sha256-fixture-v-clean-5-set-phase","kind":"overwrite"}
@@ -0,0 +1,18 @@
1
+ {
2
+ "id": "fx-01-clean-exit-05",
3
+ "category": "clean-exit",
4
+ "waveId": "W20-clean-5",
5
+ "subId": "cleanA5",
6
+ "notes": "baseline — clean exit with terminal verb; recovery is a no-op",
7
+ "expectedDetection": {
8
+ "truncated": false,
9
+ "reasonContains": "clean exit"
10
+ },
11
+ "expectedRecovery": {
12
+ "recovered": true
13
+ },
14
+ "expectedFinalState": {
15
+ ".ijfw/state/workflow.json": "{\"phase\":\"clean-5\",\"ts\":\"2026-05-20T12:00:00.000Z\"}"
16
+ },
17
+ "expectedTerminalVerb": "subagent.post-done"
18
+ }
@@ -0,0 +1 @@
1
+ {"phase":"clean-5","ts":"2026-05-20T12:00:00.000Z"}
@@ -0,0 +1 @@
1
+ {"seq":1,"verb":"workflow.set-phase","subagentId":"midO1","ts":"2026-05-20T12:00:00.000Z","verbId":"v-midO-1-set-phase","outcome":"ok","payloadDigest":"sha256-fixture-v-midO-1-set-phase"}
@@ -0,0 +1,3 @@
1
+ {"verb":"workflow.set-phase","verbId":"v-midO-1-set-phase","phase":"begin","ts":"2026-05-20T12:00:00.000Z","targets":[".ijfw/state/workflow.json"],"payloadDigest":"sha256-fixture-v-midO-1-set-phase","kind":"overwrite"}
2
+ {"verb":"workflow.set-phase","verbId":"v-midO-1-set-phase","phase":"commit","ts":"2026-05-20T12:00:00.000Z","payloadDigest":"sha256-fixture-v-midO-1-set-phase","kind":"overwrite"}
3
+ {"verb":"wave.advance","verbId":"v-midO-1-advance","phase":"begin","ts":"2026-05-20T12:00:00.000Z","targets":[".ijfw/state/workflow.json"],"payloadDigest":"sha256-fixture-v-midO-1-advance","kind":"overwrite"}
@@ -0,0 +1,18 @@
1
+ {
2
+ "id": "fx-02-mid-overwrite-01",
3
+ "category": "mid-overwrite",
4
+ "waveId": "W20-midO-1",
5
+ "subId": "midO1",
6
+ "notes": "overwrite verb partial — snapshot-rollback restores wfBefore",
7
+ "expectedDetection": {
8
+ "truncated": "open-partial",
9
+ "reasonContains": "open begin"
10
+ },
11
+ "expectedRecovery": {
12
+ "recovered": true
13
+ },
14
+ "expectedFinalState": {
15
+ ".ijfw/state/workflow.json": "{\"phase\":\"midO-pre-1\"}"
16
+ },
17
+ "expectedTerminalVerb": "subagent.post-done"
18
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "verbId": "v-midO-1-advance",
3
+ "targets": [
4
+ {
5
+ "relPath": ".ijfw/state/workflow.json",
6
+ "absPath": "<<ABS>>",
7
+ "existed": true,
8
+ "content": "{\"phase\":\"midO-pre-1\"}"
9
+ }
10
+ ]
11
+ }
@@ -0,0 +1 @@
1
+ {"phase":"midO-HALF-1","partial":true}
@@ -0,0 +1 @@
1
+ {"seq":1,"verb":"workflow.set-phase","subagentId":"midO2","ts":"2026-05-20T12:00:00.000Z","verbId":"v-midO-2-set-phase","outcome":"ok","payloadDigest":"sha256-fixture-v-midO-2-set-phase"}
@@ -0,0 +1,3 @@
1
+ {"verb":"workflow.set-phase","verbId":"v-midO-2-set-phase","phase":"begin","ts":"2026-05-20T12:00:00.000Z","targets":[".ijfw/state/workflow.json"],"payloadDigest":"sha256-fixture-v-midO-2-set-phase","kind":"overwrite"}
2
+ {"verb":"workflow.set-phase","verbId":"v-midO-2-set-phase","phase":"commit","ts":"2026-05-20T12:00:00.000Z","payloadDigest":"sha256-fixture-v-midO-2-set-phase","kind":"overwrite"}
3
+ {"verb":"wave.advance","verbId":"v-midO-2-advance","phase":"begin","ts":"2026-05-20T12:00:00.000Z","targets":[".ijfw/state/workflow.json"],"payloadDigest":"sha256-fixture-v-midO-2-advance","kind":"overwrite"}
@@ -0,0 +1,18 @@
1
+ {
2
+ "id": "fx-02-mid-overwrite-02",
3
+ "category": "mid-overwrite",
4
+ "waveId": "W20-midO-2",
5
+ "subId": "midO2",
6
+ "notes": "overwrite verb partial — snapshot-rollback restores wfBefore",
7
+ "expectedDetection": {
8
+ "truncated": "open-partial",
9
+ "reasonContains": "open begin"
10
+ },
11
+ "expectedRecovery": {
12
+ "recovered": true
13
+ },
14
+ "expectedFinalState": {
15
+ ".ijfw/state/workflow.json": "{\"phase\":\"midO-pre-2\"}"
16
+ },
17
+ "expectedTerminalVerb": "subagent.post-done"
18
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "verbId": "v-midO-2-advance",
3
+ "targets": [
4
+ {
5
+ "relPath": ".ijfw/state/workflow.json",
6
+ "absPath": "<<ABS>>",
7
+ "existed": true,
8
+ "content": "{\"phase\":\"midO-pre-2\"}"
9
+ }
10
+ ]
11
+ }
@@ -0,0 +1 @@
1
+ {"phase":"midO-HALF-2","partial":true}
@@ -0,0 +1 @@
1
+ {"seq":1,"verb":"workflow.set-phase","subagentId":"midO3","ts":"2026-05-20T12:00:00.000Z","verbId":"v-midO-3-set-phase","outcome":"ok","payloadDigest":"sha256-fixture-v-midO-3-set-phase"}
@@ -0,0 +1,3 @@
1
+ {"verb":"workflow.set-phase","verbId":"v-midO-3-set-phase","phase":"begin","ts":"2026-05-20T12:00:00.000Z","targets":[".ijfw/state/workflow.json"],"payloadDigest":"sha256-fixture-v-midO-3-set-phase","kind":"overwrite"}
2
+ {"verb":"workflow.set-phase","verbId":"v-midO-3-set-phase","phase":"commit","ts":"2026-05-20T12:00:00.000Z","payloadDigest":"sha256-fixture-v-midO-3-set-phase","kind":"overwrite"}
3
+ {"verb":"wave.advance","verbId":"v-midO-3-advance","phase":"begin","ts":"2026-05-20T12:00:00.000Z","targets":[".ijfw/state/workflow.json"],"payloadDigest":"sha256-fixture-v-midO-3-advance","kind":"overwrite"}