@kontourai/flow-agents 3.12.1 → 4.0.0

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 (1029) hide show
  1. package/.github/workflows/ci.yml +76 -0
  2. package/CHANGELOG.md +41 -0
  3. package/build/generated/capability-declarations.json +177 -0
  4. package/build/src/builder-flow-runtime.d.ts +37 -0
  5. package/build/src/builder-flow-runtime.js +71 -1
  6. package/build/src/builder-gate-action-envelope.d.ts +58 -7
  7. package/build/src/builder-gate-action-envelope.js +160 -28
  8. package/build/src/builder-lifecycle-authority.d.ts +26 -0
  9. package/build/src/builder-lifecycle-authority.js +38 -0
  10. package/build/src/cli/builder-run.js +71 -2
  11. package/build/src/cli/narrative-render.d.ts +95 -0
  12. package/build/src/cli/narrative-render.js +466 -0
  13. package/build/src/cli/narrative-sources.d.ts +2 -0
  14. package/build/src/cli/narrative-sources.js +282 -0
  15. package/build/src/cli/public-contracts.d.ts +257 -0
  16. package/build/src/cli/public-contracts.js +87 -0
  17. package/build/src/cli/workflow-sidecar.d.ts +28 -12
  18. package/build/src/cli/workflow-sidecar.js +599 -180
  19. package/build/src/cli/workflow.js +11 -1
  20. package/build/src/cli.js +8 -0
  21. package/build/src/continuation-driver.d.ts +12 -0
  22. package/build/src/continuation-driver.js +16 -2
  23. package/build/src/continuation-validation.js +324 -8
  24. package/build/src/flow-kit/action-metadata.js +21 -1
  25. package/build/src/index.d.ts +33 -2
  26. package/build/src/index.js +24 -0
  27. package/build/src/lib/capability-declarations.d.ts +83 -0
  28. package/build/src/lib/capability-declarations.js +209 -0
  29. package/build/src/narrative/envelope.d.ts +111 -0
  30. package/build/src/narrative/envelope.js +590 -0
  31. package/build/src/narrative/eval-result.d.ts +60 -0
  32. package/build/src/narrative/eval-result.js +158 -0
  33. package/build/src/narrative/grounding-validator.d.ts +104 -0
  34. package/build/src/narrative/grounding-validator.js +472 -0
  35. package/build/src/narrative/integrity.d.ts +17 -0
  36. package/build/src/narrative/integrity.js +39 -0
  37. package/build/src/narrative/intent-economics.d.ts +42 -0
  38. package/build/src/narrative/intent-economics.js +82 -0
  39. package/build/src/narrative/intent.d.ts +94 -0
  40. package/build/src/narrative/intent.js +132 -0
  41. package/build/src/narrative/policy-filter.d.ts +15 -0
  42. package/build/src/narrative/policy-filter.js +58 -0
  43. package/build/src/narrative/projection.d.ts +58 -0
  44. package/build/src/narrative/projection.js +502 -0
  45. package/build/src/narrative/readers.d.ts +84 -0
  46. package/build/src/narrative/readers.js +406 -0
  47. package/build/src/narrative/render.d.ts +3 -0
  48. package/build/src/narrative/render.js +83 -0
  49. package/build/src/narrative/resolver.d.ts +52 -0
  50. package/build/src/narrative/resolver.js +190 -0
  51. package/build/src/narrative/snapshot.d.ts +90 -0
  52. package/build/src/narrative/snapshot.js +325 -0
  53. package/build/src/narrative/source-ids.d.ts +121 -0
  54. package/build/src/narrative/source-ids.js +201 -0
  55. package/build/src/narrative/statements.d.ts +84 -0
  56. package/build/src/narrative/statements.js +347 -0
  57. package/build/src/narrative/turn-spine.d.ts +19 -0
  58. package/build/src/narrative/turn-spine.js +73 -0
  59. package/build/src/tools/build-universal-bundles.js +8 -0
  60. package/build/src/tools/generate-capability-matrix.d.ts +7 -0
  61. package/build/src/tools/generate-capability-matrix.js +207 -0
  62. package/build/src/tools/validate-source-tree.js +5 -1
  63. package/context/scripts/hooks/stop-goal-fit.js +182 -9
  64. package/context/scripts/hooks/workflow-steering.js +160 -19
  65. package/context/scripts/telemetry/lib/session.sh +3 -0
  66. package/context/scripts/telemetry/lib/transport.sh +12 -3
  67. package/context/scripts/telemetry/telemetry.sh +480 -59
  68. package/dist/base/build/generated/capability-declarations.json +177 -0
  69. package/dist/base/build/package.json +1 -1
  70. package/dist/base/build/src/builder-flow-runtime.d.ts +37 -0
  71. package/dist/base/build/src/builder-flow-runtime.js +71 -1
  72. package/dist/base/build/src/builder-gate-action-envelope.d.ts +58 -7
  73. package/dist/base/build/src/builder-gate-action-envelope.js +160 -28
  74. package/dist/base/build/src/builder-lifecycle-authority.d.ts +26 -0
  75. package/dist/base/build/src/builder-lifecycle-authority.js +38 -0
  76. package/dist/base/build/src/cli/builder-run.js +71 -2
  77. package/dist/base/build/src/cli/narrative-render.d.ts +95 -0
  78. package/dist/base/build/src/cli/narrative-render.js +466 -0
  79. package/dist/base/build/src/cli/narrative-sources.d.ts +2 -0
  80. package/dist/base/build/src/cli/narrative-sources.js +282 -0
  81. package/dist/base/build/src/cli/public-contracts.d.ts +257 -0
  82. package/dist/base/build/src/cli/public-contracts.js +87 -0
  83. package/dist/base/build/src/cli/workflow-sidecar.d.ts +28 -12
  84. package/dist/base/build/src/cli/workflow-sidecar.js +599 -180
  85. package/dist/base/build/src/cli/workflow.js +11 -1
  86. package/dist/base/build/src/cli.js +8 -0
  87. package/dist/base/build/src/continuation-driver.d.ts +12 -0
  88. package/dist/base/build/src/continuation-driver.js +16 -2
  89. package/dist/base/build/src/continuation-validation.js +324 -8
  90. package/dist/base/build/src/flow-kit/action-metadata.js +21 -1
  91. package/dist/base/build/src/index.d.ts +33 -2
  92. package/dist/base/build/src/index.js +24 -0
  93. package/dist/base/build/src/lib/capability-declarations.d.ts +83 -0
  94. package/dist/base/build/src/lib/capability-declarations.js +209 -0
  95. package/dist/base/build/src/narrative/envelope.d.ts +111 -0
  96. package/dist/base/build/src/narrative/envelope.js +590 -0
  97. package/dist/base/build/src/narrative/eval-result.d.ts +60 -0
  98. package/dist/base/build/src/narrative/eval-result.js +158 -0
  99. package/dist/base/build/src/narrative/grounding-validator.d.ts +104 -0
  100. package/dist/base/build/src/narrative/grounding-validator.js +472 -0
  101. package/dist/base/build/src/narrative/integrity.d.ts +17 -0
  102. package/dist/base/build/src/narrative/integrity.js +39 -0
  103. package/dist/base/build/src/narrative/intent-economics.d.ts +42 -0
  104. package/dist/base/build/src/narrative/intent-economics.js +82 -0
  105. package/dist/base/build/src/narrative/intent.d.ts +94 -0
  106. package/dist/base/build/src/narrative/intent.js +132 -0
  107. package/dist/base/build/src/narrative/policy-filter.d.ts +15 -0
  108. package/dist/base/build/src/narrative/policy-filter.js +58 -0
  109. package/dist/base/build/src/narrative/projection.d.ts +58 -0
  110. package/dist/base/build/src/narrative/projection.js +502 -0
  111. package/dist/base/build/src/narrative/readers.d.ts +84 -0
  112. package/dist/base/build/src/narrative/readers.js +406 -0
  113. package/dist/base/build/src/narrative/render.d.ts +3 -0
  114. package/dist/base/build/src/narrative/render.js +83 -0
  115. package/dist/base/build/src/narrative/resolver.d.ts +52 -0
  116. package/dist/base/build/src/narrative/resolver.js +190 -0
  117. package/dist/base/build/src/narrative/snapshot.d.ts +90 -0
  118. package/dist/base/build/src/narrative/snapshot.js +325 -0
  119. package/dist/base/build/src/narrative/source-ids.d.ts +121 -0
  120. package/dist/base/build/src/narrative/source-ids.js +201 -0
  121. package/dist/base/build/src/narrative/statements.d.ts +84 -0
  122. package/dist/base/build/src/narrative/statements.js +347 -0
  123. package/dist/base/build/src/narrative/turn-spine.d.ts +19 -0
  124. package/dist/base/build/src/narrative/turn-spine.js +73 -0
  125. package/dist/base/build/src/tools/build-universal-bundles.js +8 -0
  126. package/dist/base/build/src/tools/generate-capability-matrix.d.ts +7 -0
  127. package/dist/base/build/src/tools/generate-capability-matrix.js +207 -0
  128. package/dist/base/build/src/tools/validate-source-tree.js +5 -1
  129. package/dist/base/context/scripts/hooks/stop-goal-fit.js +182 -9
  130. package/dist/base/context/scripts/hooks/workflow-steering.js +160 -19
  131. package/dist/base/context/scripts/telemetry/lib/session.sh +3 -0
  132. package/dist/base/context/scripts/telemetry/lib/transport.sh +12 -3
  133. package/dist/base/context/scripts/telemetry/telemetry.sh +480 -59
  134. package/dist/base/docs/context-map.md +4 -0
  135. package/dist/base/docs/decisions/index.md +1 -0
  136. package/dist/base/docs/decisions/writer-observed-execution.md +51 -0
  137. package/dist/base/docs/fixture-ownership.md +5 -1
  138. package/dist/base/docs/migrations.md +19 -4
  139. package/dist/base/docs/public-workflow-cli.md +9 -0
  140. package/dist/base/docs/spec/builder-flow-runtime.md +49 -8
  141. package/dist/base/docs/spec/runtime-hook-surface.md +29 -1
  142. package/dist/base/docs/specs/economics-record-contract.md +1 -1
  143. package/dist/base/docs/specs/harness-capability-matrix.md +63 -21
  144. package/dist/base/docs/workflow-usage-guide.md +4 -1
  145. package/dist/base/evals/ci/run-baseline.sh +38 -0
  146. package/dist/base/evals/fixtures/narrative-evals/README.md +62 -0
  147. package/dist/base/evals/fixtures/narrative-evals/corpus.json +129 -0
  148. package/dist/base/evals/fixtures/narrative-evals/scorer.mjs +522 -0
  149. package/dist/base/evals/fixtures/narrative-grounding-validator/corpus.json +20 -0
  150. package/dist/base/evals/fixtures/narrative-grounding-validator/prompt-injection-adversarial.txt +1 -0
  151. package/dist/base/evals/fixtures/narrative-grounding-validator/prompt-injection-control.txt +1 -0
  152. package/dist/base/evals/fixtures/narrative-grounding-validator/scorer.mjs +201 -0
  153. package/dist/base/evals/fixtures/narrative-prose-renderer/corpus.json +15 -0
  154. package/dist/base/evals/fixtures/narrative-prose-renderer/scorer.mjs +201 -0
  155. package/dist/base/evals/fixtures/narrative-sources/expected-capture-completeness.json +13 -0
  156. package/dist/base/evals/fixtures/narrative-sources/flow/runs/run-fixture/report.json +1 -0
  157. package/dist/base/evals/fixtures/narrative-sources/flow/runs/run-fixture/state.json +1 -0
  158. package/dist/base/evals/fixtures/narrative-sources/flow/runs/run-runtime/state.json +1 -0
  159. package/dist/base/evals/fixtures/narrative-sources/repo/created.txt +1 -0
  160. package/dist/base/evals/fixtures/narrative-sources/repo/fixture.txt +1 -0
  161. package/dist/base/evals/fixtures/narrative-sources/session/agents/agent-fixture/events.jsonl +2 -0
  162. package/dist/base/evals/fixtures/narrative-sources/session/agents/nested-worker/events.jsonl +1 -0
  163. package/dist/base/evals/fixtures/narrative-sources/session/trust.bundle +1 -0
  164. package/dist/base/evals/fixtures/narrative-sources/telemetry/full.jsonl +3 -0
  165. package/dist/base/evals/fixtures/narrative-sources/telemetry/runtime.jsonl +4 -0
  166. package/dist/base/evals/fixtures/narrative-sources/transcripts/session.txt +1 -0
  167. package/dist/base/evals/integration/test_capability_declarations.sh +183 -0
  168. package/dist/base/evals/integration/test_economics_record.sh +42 -0
  169. package/dist/base/evals/integration/test_fixture_retirement_audit.sh +2 -2
  170. package/dist/base/evals/integration/test_gate_review_inquiry_records.sh +42 -35
  171. package/dist/base/evals/integration/test_goal_fit_hook.sh +13 -5
  172. package/dist/base/evals/integration/test_goal_fit_narrative_exclusion.sh +68 -0
  173. package/dist/base/evals/integration/test_narrative_evals.sh +174 -0
  174. package/dist/base/evals/integration/test_narrative_grounded_envelope.sh +280 -0
  175. package/dist/base/evals/integration/test_narrative_grounding_validator.sh +166 -0
  176. package/dist/base/evals/integration/test_narrative_intent_annotation.sh +205 -0
  177. package/dist/base/evals/integration/test_narrative_prose_renderer.sh +239 -0
  178. package/dist/base/evals/integration/test_narrative_redaction_failclosed.sh +120 -0
  179. package/dist/base/evals/integration/test_narrative_runtime_projection.sh +146 -0
  180. package/dist/base/evals/integration/test_narrative_source_contract.sh +228 -0
  181. package/dist/base/evals/integration/test_narrative_trust_isolation.sh +408 -0
  182. package/dist/base/evals/integration/test_plain_stop_messaging.sh +91 -0
  183. package/dist/base/evals/integration/test_session_resume_roundtrip.sh +228 -0
  184. package/dist/base/evals/integration/test_telemetry_action_class.sh +265 -0
  185. package/dist/base/evals/integration/test_telemetry_delegation.sh +291 -0
  186. package/dist/base/evals/integration/test_telemetry_sanitize_usage.sh +155 -0
  187. package/dist/base/evals/integration/test_telemetry_task_slug.sh +158 -0
  188. package/dist/base/evals/integration/test_telemetry_tool_outcome.sh +279 -0
  189. package/dist/base/evals/integration/test_workflow_sidecar_writer.sh +111 -2
  190. package/dist/base/evals/run.sh +12 -0
  191. package/dist/base/evals/static/test_library_exports.sh +5 -0
  192. package/dist/base/install.sh +1 -1
  193. package/dist/base/schemas/grounded-execution-narrative.schema.json +292 -0
  194. package/dist/base/schemas/narrative-eval-result.schema.json +205 -0
  195. package/dist/base/schemas/narrative-runtime-projection.schema.json +134 -0
  196. package/dist/base/schemas/narrative-source-manifest.schema.json +294 -0
  197. package/dist/base/scripts/check-hachure-boundary.mjs +81 -0
  198. package/dist/base/scripts/hooks/stop-goal-fit.js +182 -9
  199. package/dist/base/scripts/hooks/workflow-steering.js +160 -19
  200. package/dist/base/scripts/telemetry/economics-record.schema.json +1 -1
  201. package/dist/base/scripts/telemetry/economics-record.sh +47 -3
  202. package/dist/base/scripts/telemetry/lib/session.sh +3 -0
  203. package/dist/base/scripts/telemetry/lib/transport.sh +12 -3
  204. package/dist/base/scripts/telemetry/telemetry.sh +480 -59
  205. package/dist/claude-code/build/generated/capability-declarations.json +177 -0
  206. package/dist/claude-code/build/package.json +1 -1
  207. package/dist/claude-code/build/src/builder-flow-runtime.d.ts +37 -0
  208. package/dist/claude-code/build/src/builder-flow-runtime.js +71 -1
  209. package/dist/claude-code/build/src/builder-gate-action-envelope.d.ts +58 -7
  210. package/dist/claude-code/build/src/builder-gate-action-envelope.js +160 -28
  211. package/dist/claude-code/build/src/builder-lifecycle-authority.d.ts +26 -0
  212. package/dist/claude-code/build/src/builder-lifecycle-authority.js +38 -0
  213. package/dist/claude-code/build/src/cli/builder-run.js +71 -2
  214. package/dist/claude-code/build/src/cli/narrative-render.d.ts +95 -0
  215. package/dist/claude-code/build/src/cli/narrative-render.js +466 -0
  216. package/dist/claude-code/build/src/cli/narrative-sources.d.ts +2 -0
  217. package/dist/claude-code/build/src/cli/narrative-sources.js +282 -0
  218. package/dist/claude-code/build/src/cli/public-contracts.d.ts +257 -0
  219. package/dist/claude-code/build/src/cli/public-contracts.js +87 -0
  220. package/dist/claude-code/build/src/cli/workflow-sidecar.d.ts +28 -12
  221. package/dist/claude-code/build/src/cli/workflow-sidecar.js +599 -180
  222. package/dist/claude-code/build/src/cli/workflow.js +11 -1
  223. package/dist/claude-code/build/src/cli.js +8 -0
  224. package/dist/claude-code/build/src/continuation-driver.d.ts +12 -0
  225. package/dist/claude-code/build/src/continuation-driver.js +16 -2
  226. package/dist/claude-code/build/src/continuation-validation.js +324 -8
  227. package/dist/claude-code/build/src/flow-kit/action-metadata.js +21 -1
  228. package/dist/claude-code/build/src/index.d.ts +33 -2
  229. package/dist/claude-code/build/src/index.js +24 -0
  230. package/dist/claude-code/build/src/lib/capability-declarations.d.ts +83 -0
  231. package/dist/claude-code/build/src/lib/capability-declarations.js +209 -0
  232. package/dist/claude-code/build/src/narrative/envelope.d.ts +111 -0
  233. package/dist/claude-code/build/src/narrative/envelope.js +590 -0
  234. package/dist/claude-code/build/src/narrative/eval-result.d.ts +60 -0
  235. package/dist/claude-code/build/src/narrative/eval-result.js +158 -0
  236. package/dist/claude-code/build/src/narrative/grounding-validator.d.ts +104 -0
  237. package/dist/claude-code/build/src/narrative/grounding-validator.js +472 -0
  238. package/dist/claude-code/build/src/narrative/integrity.d.ts +17 -0
  239. package/dist/claude-code/build/src/narrative/integrity.js +39 -0
  240. package/dist/claude-code/build/src/narrative/intent-economics.d.ts +42 -0
  241. package/dist/claude-code/build/src/narrative/intent-economics.js +82 -0
  242. package/dist/claude-code/build/src/narrative/intent.d.ts +94 -0
  243. package/dist/claude-code/build/src/narrative/intent.js +132 -0
  244. package/dist/claude-code/build/src/narrative/policy-filter.d.ts +15 -0
  245. package/dist/claude-code/build/src/narrative/policy-filter.js +58 -0
  246. package/dist/claude-code/build/src/narrative/projection.d.ts +58 -0
  247. package/dist/claude-code/build/src/narrative/projection.js +502 -0
  248. package/dist/claude-code/build/src/narrative/readers.d.ts +84 -0
  249. package/dist/claude-code/build/src/narrative/readers.js +406 -0
  250. package/dist/claude-code/build/src/narrative/render.d.ts +3 -0
  251. package/dist/claude-code/build/src/narrative/render.js +83 -0
  252. package/dist/claude-code/build/src/narrative/resolver.d.ts +52 -0
  253. package/dist/claude-code/build/src/narrative/resolver.js +190 -0
  254. package/dist/claude-code/build/src/narrative/snapshot.d.ts +90 -0
  255. package/dist/claude-code/build/src/narrative/snapshot.js +325 -0
  256. package/dist/claude-code/build/src/narrative/source-ids.d.ts +121 -0
  257. package/dist/claude-code/build/src/narrative/source-ids.js +201 -0
  258. package/dist/claude-code/build/src/narrative/statements.d.ts +84 -0
  259. package/dist/claude-code/build/src/narrative/statements.js +347 -0
  260. package/dist/claude-code/build/src/narrative/turn-spine.d.ts +19 -0
  261. package/dist/claude-code/build/src/narrative/turn-spine.js +73 -0
  262. package/dist/claude-code/build/src/tools/build-universal-bundles.js +8 -0
  263. package/dist/claude-code/build/src/tools/generate-capability-matrix.d.ts +7 -0
  264. package/dist/claude-code/build/src/tools/generate-capability-matrix.js +207 -0
  265. package/dist/claude-code/build/src/tools/validate-source-tree.js +5 -1
  266. package/dist/claude-code/context/scripts/hooks/stop-goal-fit.js +182 -9
  267. package/dist/claude-code/context/scripts/hooks/workflow-steering.js +160 -19
  268. package/dist/claude-code/context/scripts/telemetry/lib/session.sh +3 -0
  269. package/dist/claude-code/context/scripts/telemetry/lib/transport.sh +12 -3
  270. package/dist/claude-code/context/scripts/telemetry/telemetry.sh +480 -59
  271. package/dist/claude-code/docs/context-map.md +4 -0
  272. package/dist/claude-code/docs/decisions/index.md +1 -0
  273. package/dist/claude-code/docs/decisions/writer-observed-execution.md +51 -0
  274. package/dist/claude-code/docs/fixture-ownership.md +5 -1
  275. package/dist/claude-code/docs/migrations.md +19 -4
  276. package/dist/claude-code/docs/public-workflow-cli.md +9 -0
  277. package/dist/claude-code/docs/spec/builder-flow-runtime.md +49 -8
  278. package/dist/claude-code/docs/spec/runtime-hook-surface.md +29 -1
  279. package/dist/claude-code/docs/specs/economics-record-contract.md +1 -1
  280. package/dist/claude-code/docs/specs/harness-capability-matrix.md +63 -21
  281. package/dist/claude-code/docs/workflow-usage-guide.md +4 -1
  282. package/dist/claude-code/evals/ci/run-baseline.sh +38 -0
  283. package/dist/claude-code/evals/fixtures/narrative-evals/README.md +62 -0
  284. package/dist/claude-code/evals/fixtures/narrative-evals/corpus.json +129 -0
  285. package/dist/claude-code/evals/fixtures/narrative-evals/scorer.mjs +522 -0
  286. package/dist/claude-code/evals/fixtures/narrative-grounding-validator/corpus.json +20 -0
  287. package/dist/claude-code/evals/fixtures/narrative-grounding-validator/prompt-injection-adversarial.txt +1 -0
  288. package/dist/claude-code/evals/fixtures/narrative-grounding-validator/prompt-injection-control.txt +1 -0
  289. package/dist/claude-code/evals/fixtures/narrative-grounding-validator/scorer.mjs +201 -0
  290. package/dist/claude-code/evals/fixtures/narrative-prose-renderer/corpus.json +15 -0
  291. package/dist/claude-code/evals/fixtures/narrative-prose-renderer/scorer.mjs +201 -0
  292. package/dist/claude-code/evals/fixtures/narrative-sources/expected-capture-completeness.json +13 -0
  293. package/dist/claude-code/evals/fixtures/narrative-sources/flow/runs/run-fixture/report.json +1 -0
  294. package/dist/claude-code/evals/fixtures/narrative-sources/flow/runs/run-fixture/state.json +1 -0
  295. package/dist/claude-code/evals/fixtures/narrative-sources/flow/runs/run-runtime/state.json +1 -0
  296. package/dist/claude-code/evals/fixtures/narrative-sources/repo/created.txt +1 -0
  297. package/dist/claude-code/evals/fixtures/narrative-sources/repo/fixture.txt +1 -0
  298. package/dist/claude-code/evals/fixtures/narrative-sources/session/agents/agent-fixture/events.jsonl +2 -0
  299. package/dist/claude-code/evals/fixtures/narrative-sources/session/agents/nested-worker/events.jsonl +1 -0
  300. package/dist/claude-code/evals/fixtures/narrative-sources/session/trust.bundle +1 -0
  301. package/dist/claude-code/evals/fixtures/narrative-sources/telemetry/full.jsonl +3 -0
  302. package/dist/claude-code/evals/fixtures/narrative-sources/telemetry/runtime.jsonl +4 -0
  303. package/dist/claude-code/evals/fixtures/narrative-sources/transcripts/session.txt +1 -0
  304. package/dist/claude-code/evals/integration/test_capability_declarations.sh +183 -0
  305. package/dist/claude-code/evals/integration/test_economics_record.sh +42 -0
  306. package/dist/claude-code/evals/integration/test_fixture_retirement_audit.sh +2 -2
  307. package/dist/claude-code/evals/integration/test_gate_review_inquiry_records.sh +42 -35
  308. package/dist/claude-code/evals/integration/test_goal_fit_hook.sh +13 -5
  309. package/dist/claude-code/evals/integration/test_goal_fit_narrative_exclusion.sh +68 -0
  310. package/dist/claude-code/evals/integration/test_narrative_evals.sh +174 -0
  311. package/dist/claude-code/evals/integration/test_narrative_grounded_envelope.sh +280 -0
  312. package/dist/claude-code/evals/integration/test_narrative_grounding_validator.sh +166 -0
  313. package/dist/claude-code/evals/integration/test_narrative_intent_annotation.sh +205 -0
  314. package/dist/claude-code/evals/integration/test_narrative_prose_renderer.sh +239 -0
  315. package/dist/claude-code/evals/integration/test_narrative_redaction_failclosed.sh +120 -0
  316. package/dist/claude-code/evals/integration/test_narrative_runtime_projection.sh +146 -0
  317. package/dist/claude-code/evals/integration/test_narrative_source_contract.sh +228 -0
  318. package/dist/claude-code/evals/integration/test_narrative_trust_isolation.sh +408 -0
  319. package/dist/claude-code/evals/integration/test_plain_stop_messaging.sh +91 -0
  320. package/dist/claude-code/evals/integration/test_session_resume_roundtrip.sh +228 -0
  321. package/dist/claude-code/evals/integration/test_telemetry_action_class.sh +265 -0
  322. package/dist/claude-code/evals/integration/test_telemetry_delegation.sh +291 -0
  323. package/dist/claude-code/evals/integration/test_telemetry_sanitize_usage.sh +155 -0
  324. package/dist/claude-code/evals/integration/test_telemetry_task_slug.sh +158 -0
  325. package/dist/claude-code/evals/integration/test_telemetry_tool_outcome.sh +279 -0
  326. package/dist/claude-code/evals/integration/test_workflow_sidecar_writer.sh +111 -2
  327. package/dist/claude-code/evals/run.sh +12 -0
  328. package/dist/claude-code/evals/static/test_library_exports.sh +5 -0
  329. package/dist/claude-code/install.sh +1 -1
  330. package/dist/claude-code/schemas/grounded-execution-narrative.schema.json +292 -0
  331. package/dist/claude-code/schemas/narrative-eval-result.schema.json +205 -0
  332. package/dist/claude-code/schemas/narrative-runtime-projection.schema.json +134 -0
  333. package/dist/claude-code/schemas/narrative-source-manifest.schema.json +294 -0
  334. package/dist/claude-code/scripts/check-hachure-boundary.mjs +81 -0
  335. package/dist/claude-code/scripts/hooks/stop-goal-fit.js +182 -9
  336. package/dist/claude-code/scripts/hooks/workflow-steering.js +160 -19
  337. package/dist/claude-code/scripts/telemetry/economics-record.schema.json +1 -1
  338. package/dist/claude-code/scripts/telemetry/economics-record.sh +47 -3
  339. package/dist/claude-code/scripts/telemetry/lib/session.sh +3 -0
  340. package/dist/claude-code/scripts/telemetry/lib/transport.sh +12 -3
  341. package/dist/claude-code/scripts/telemetry/telemetry.sh +480 -59
  342. package/dist/codex/build/generated/capability-declarations.json +177 -0
  343. package/dist/codex/build/package.json +1 -1
  344. package/dist/codex/build/src/builder-flow-runtime.d.ts +37 -0
  345. package/dist/codex/build/src/builder-flow-runtime.js +71 -1
  346. package/dist/codex/build/src/builder-gate-action-envelope.d.ts +58 -7
  347. package/dist/codex/build/src/builder-gate-action-envelope.js +160 -28
  348. package/dist/codex/build/src/builder-lifecycle-authority.d.ts +26 -0
  349. package/dist/codex/build/src/builder-lifecycle-authority.js +38 -0
  350. package/dist/codex/build/src/cli/builder-run.js +71 -2
  351. package/dist/codex/build/src/cli/narrative-render.d.ts +95 -0
  352. package/dist/codex/build/src/cli/narrative-render.js +466 -0
  353. package/dist/codex/build/src/cli/narrative-sources.d.ts +2 -0
  354. package/dist/codex/build/src/cli/narrative-sources.js +282 -0
  355. package/dist/codex/build/src/cli/public-contracts.d.ts +257 -0
  356. package/dist/codex/build/src/cli/public-contracts.js +87 -0
  357. package/dist/codex/build/src/cli/workflow-sidecar.d.ts +28 -12
  358. package/dist/codex/build/src/cli/workflow-sidecar.js +599 -180
  359. package/dist/codex/build/src/cli/workflow.js +11 -1
  360. package/dist/codex/build/src/cli.js +8 -0
  361. package/dist/codex/build/src/continuation-driver.d.ts +12 -0
  362. package/dist/codex/build/src/continuation-driver.js +16 -2
  363. package/dist/codex/build/src/continuation-validation.js +324 -8
  364. package/dist/codex/build/src/flow-kit/action-metadata.js +21 -1
  365. package/dist/codex/build/src/index.d.ts +33 -2
  366. package/dist/codex/build/src/index.js +24 -0
  367. package/dist/codex/build/src/lib/capability-declarations.d.ts +83 -0
  368. package/dist/codex/build/src/lib/capability-declarations.js +209 -0
  369. package/dist/codex/build/src/narrative/envelope.d.ts +111 -0
  370. package/dist/codex/build/src/narrative/envelope.js +590 -0
  371. package/dist/codex/build/src/narrative/eval-result.d.ts +60 -0
  372. package/dist/codex/build/src/narrative/eval-result.js +158 -0
  373. package/dist/codex/build/src/narrative/grounding-validator.d.ts +104 -0
  374. package/dist/codex/build/src/narrative/grounding-validator.js +472 -0
  375. package/dist/codex/build/src/narrative/integrity.d.ts +17 -0
  376. package/dist/codex/build/src/narrative/integrity.js +39 -0
  377. package/dist/codex/build/src/narrative/intent-economics.d.ts +42 -0
  378. package/dist/codex/build/src/narrative/intent-economics.js +82 -0
  379. package/dist/codex/build/src/narrative/intent.d.ts +94 -0
  380. package/dist/codex/build/src/narrative/intent.js +132 -0
  381. package/dist/codex/build/src/narrative/policy-filter.d.ts +15 -0
  382. package/dist/codex/build/src/narrative/policy-filter.js +58 -0
  383. package/dist/codex/build/src/narrative/projection.d.ts +58 -0
  384. package/dist/codex/build/src/narrative/projection.js +502 -0
  385. package/dist/codex/build/src/narrative/readers.d.ts +84 -0
  386. package/dist/codex/build/src/narrative/readers.js +406 -0
  387. package/dist/codex/build/src/narrative/render.d.ts +3 -0
  388. package/dist/codex/build/src/narrative/render.js +83 -0
  389. package/dist/codex/build/src/narrative/resolver.d.ts +52 -0
  390. package/dist/codex/build/src/narrative/resolver.js +190 -0
  391. package/dist/codex/build/src/narrative/snapshot.d.ts +90 -0
  392. package/dist/codex/build/src/narrative/snapshot.js +325 -0
  393. package/dist/codex/build/src/narrative/source-ids.d.ts +121 -0
  394. package/dist/codex/build/src/narrative/source-ids.js +201 -0
  395. package/dist/codex/build/src/narrative/statements.d.ts +84 -0
  396. package/dist/codex/build/src/narrative/statements.js +347 -0
  397. package/dist/codex/build/src/narrative/turn-spine.d.ts +19 -0
  398. package/dist/codex/build/src/narrative/turn-spine.js +73 -0
  399. package/dist/codex/build/src/tools/build-universal-bundles.js +8 -0
  400. package/dist/codex/build/src/tools/generate-capability-matrix.d.ts +7 -0
  401. package/dist/codex/build/src/tools/generate-capability-matrix.js +207 -0
  402. package/dist/codex/build/src/tools/validate-source-tree.js +5 -1
  403. package/dist/codex/context/scripts/hooks/stop-goal-fit.js +182 -9
  404. package/dist/codex/context/scripts/hooks/workflow-steering.js +160 -19
  405. package/dist/codex/context/scripts/telemetry/lib/session.sh +3 -0
  406. package/dist/codex/context/scripts/telemetry/lib/transport.sh +12 -3
  407. package/dist/codex/context/scripts/telemetry/telemetry.sh +480 -59
  408. package/dist/codex/docs/context-map.md +4 -0
  409. package/dist/codex/docs/decisions/index.md +1 -0
  410. package/dist/codex/docs/decisions/writer-observed-execution.md +51 -0
  411. package/dist/codex/docs/fixture-ownership.md +5 -1
  412. package/dist/codex/docs/migrations.md +19 -4
  413. package/dist/codex/docs/public-workflow-cli.md +9 -0
  414. package/dist/codex/docs/spec/builder-flow-runtime.md +49 -8
  415. package/dist/codex/docs/spec/runtime-hook-surface.md +29 -1
  416. package/dist/codex/docs/specs/economics-record-contract.md +1 -1
  417. package/dist/codex/docs/specs/harness-capability-matrix.md +63 -21
  418. package/dist/codex/docs/workflow-usage-guide.md +4 -1
  419. package/dist/codex/evals/ci/run-baseline.sh +38 -0
  420. package/dist/codex/evals/fixtures/narrative-evals/README.md +62 -0
  421. package/dist/codex/evals/fixtures/narrative-evals/corpus.json +129 -0
  422. package/dist/codex/evals/fixtures/narrative-evals/scorer.mjs +522 -0
  423. package/dist/codex/evals/fixtures/narrative-grounding-validator/corpus.json +20 -0
  424. package/dist/codex/evals/fixtures/narrative-grounding-validator/prompt-injection-adversarial.txt +1 -0
  425. package/dist/codex/evals/fixtures/narrative-grounding-validator/prompt-injection-control.txt +1 -0
  426. package/dist/codex/evals/fixtures/narrative-grounding-validator/scorer.mjs +201 -0
  427. package/dist/codex/evals/fixtures/narrative-prose-renderer/corpus.json +15 -0
  428. package/dist/codex/evals/fixtures/narrative-prose-renderer/scorer.mjs +201 -0
  429. package/dist/codex/evals/fixtures/narrative-sources/expected-capture-completeness.json +13 -0
  430. package/dist/codex/evals/fixtures/narrative-sources/flow/runs/run-fixture/report.json +1 -0
  431. package/dist/codex/evals/fixtures/narrative-sources/flow/runs/run-fixture/state.json +1 -0
  432. package/dist/codex/evals/fixtures/narrative-sources/flow/runs/run-runtime/state.json +1 -0
  433. package/dist/codex/evals/fixtures/narrative-sources/repo/created.txt +1 -0
  434. package/dist/codex/evals/fixtures/narrative-sources/repo/fixture.txt +1 -0
  435. package/dist/codex/evals/fixtures/narrative-sources/session/agents/agent-fixture/events.jsonl +2 -0
  436. package/dist/codex/evals/fixtures/narrative-sources/session/agents/nested-worker/events.jsonl +1 -0
  437. package/dist/codex/evals/fixtures/narrative-sources/session/trust.bundle +1 -0
  438. package/dist/codex/evals/fixtures/narrative-sources/telemetry/full.jsonl +3 -0
  439. package/dist/codex/evals/fixtures/narrative-sources/telemetry/runtime.jsonl +4 -0
  440. package/dist/codex/evals/fixtures/narrative-sources/transcripts/session.txt +1 -0
  441. package/dist/codex/evals/integration/test_capability_declarations.sh +183 -0
  442. package/dist/codex/evals/integration/test_economics_record.sh +42 -0
  443. package/dist/codex/evals/integration/test_fixture_retirement_audit.sh +2 -2
  444. package/dist/codex/evals/integration/test_gate_review_inquiry_records.sh +42 -35
  445. package/dist/codex/evals/integration/test_goal_fit_hook.sh +13 -5
  446. package/dist/codex/evals/integration/test_goal_fit_narrative_exclusion.sh +68 -0
  447. package/dist/codex/evals/integration/test_narrative_evals.sh +174 -0
  448. package/dist/codex/evals/integration/test_narrative_grounded_envelope.sh +280 -0
  449. package/dist/codex/evals/integration/test_narrative_grounding_validator.sh +166 -0
  450. package/dist/codex/evals/integration/test_narrative_intent_annotation.sh +205 -0
  451. package/dist/codex/evals/integration/test_narrative_prose_renderer.sh +239 -0
  452. package/dist/codex/evals/integration/test_narrative_redaction_failclosed.sh +120 -0
  453. package/dist/codex/evals/integration/test_narrative_runtime_projection.sh +146 -0
  454. package/dist/codex/evals/integration/test_narrative_source_contract.sh +228 -0
  455. package/dist/codex/evals/integration/test_narrative_trust_isolation.sh +408 -0
  456. package/dist/codex/evals/integration/test_plain_stop_messaging.sh +91 -0
  457. package/dist/codex/evals/integration/test_session_resume_roundtrip.sh +228 -0
  458. package/dist/codex/evals/integration/test_telemetry_action_class.sh +265 -0
  459. package/dist/codex/evals/integration/test_telemetry_delegation.sh +291 -0
  460. package/dist/codex/evals/integration/test_telemetry_sanitize_usage.sh +155 -0
  461. package/dist/codex/evals/integration/test_telemetry_task_slug.sh +158 -0
  462. package/dist/codex/evals/integration/test_telemetry_tool_outcome.sh +279 -0
  463. package/dist/codex/evals/integration/test_workflow_sidecar_writer.sh +111 -2
  464. package/dist/codex/evals/run.sh +12 -0
  465. package/dist/codex/evals/static/test_library_exports.sh +5 -0
  466. package/dist/codex/install.sh +1 -1
  467. package/dist/codex/schemas/grounded-execution-narrative.schema.json +292 -0
  468. package/dist/codex/schemas/narrative-eval-result.schema.json +205 -0
  469. package/dist/codex/schemas/narrative-runtime-projection.schema.json +134 -0
  470. package/dist/codex/schemas/narrative-source-manifest.schema.json +294 -0
  471. package/dist/codex/scripts/check-hachure-boundary.mjs +81 -0
  472. package/dist/codex/scripts/hooks/stop-goal-fit.js +182 -9
  473. package/dist/codex/scripts/hooks/workflow-steering.js +160 -19
  474. package/dist/codex/scripts/telemetry/economics-record.schema.json +1 -1
  475. package/dist/codex/scripts/telemetry/economics-record.sh +47 -3
  476. package/dist/codex/scripts/telemetry/lib/session.sh +3 -0
  477. package/dist/codex/scripts/telemetry/lib/transport.sh +12 -3
  478. package/dist/codex/scripts/telemetry/telemetry.sh +480 -59
  479. package/dist/kiro/build/generated/capability-declarations.json +177 -0
  480. package/dist/kiro/build/package.json +1 -1
  481. package/dist/kiro/build/src/builder-flow-runtime.d.ts +37 -0
  482. package/dist/kiro/build/src/builder-flow-runtime.js +71 -1
  483. package/dist/kiro/build/src/builder-gate-action-envelope.d.ts +58 -7
  484. package/dist/kiro/build/src/builder-gate-action-envelope.js +160 -28
  485. package/dist/kiro/build/src/builder-lifecycle-authority.d.ts +26 -0
  486. package/dist/kiro/build/src/builder-lifecycle-authority.js +38 -0
  487. package/dist/kiro/build/src/cli/builder-run.js +71 -2
  488. package/dist/kiro/build/src/cli/narrative-render.d.ts +95 -0
  489. package/dist/kiro/build/src/cli/narrative-render.js +466 -0
  490. package/dist/kiro/build/src/cli/narrative-sources.d.ts +2 -0
  491. package/dist/kiro/build/src/cli/narrative-sources.js +282 -0
  492. package/dist/kiro/build/src/cli/public-contracts.d.ts +257 -0
  493. package/dist/kiro/build/src/cli/public-contracts.js +87 -0
  494. package/dist/kiro/build/src/cli/workflow-sidecar.d.ts +28 -12
  495. package/dist/kiro/build/src/cli/workflow-sidecar.js +599 -180
  496. package/dist/kiro/build/src/cli/workflow.js +11 -1
  497. package/dist/kiro/build/src/cli.js +8 -0
  498. package/dist/kiro/build/src/continuation-driver.d.ts +12 -0
  499. package/dist/kiro/build/src/continuation-driver.js +16 -2
  500. package/dist/kiro/build/src/continuation-validation.js +324 -8
  501. package/dist/kiro/build/src/flow-kit/action-metadata.js +21 -1
  502. package/dist/kiro/build/src/index.d.ts +33 -2
  503. package/dist/kiro/build/src/index.js +24 -0
  504. package/dist/kiro/build/src/lib/capability-declarations.d.ts +83 -0
  505. package/dist/kiro/build/src/lib/capability-declarations.js +209 -0
  506. package/dist/kiro/build/src/narrative/envelope.d.ts +111 -0
  507. package/dist/kiro/build/src/narrative/envelope.js +590 -0
  508. package/dist/kiro/build/src/narrative/eval-result.d.ts +60 -0
  509. package/dist/kiro/build/src/narrative/eval-result.js +158 -0
  510. package/dist/kiro/build/src/narrative/grounding-validator.d.ts +104 -0
  511. package/dist/kiro/build/src/narrative/grounding-validator.js +472 -0
  512. package/dist/kiro/build/src/narrative/integrity.d.ts +17 -0
  513. package/dist/kiro/build/src/narrative/integrity.js +39 -0
  514. package/dist/kiro/build/src/narrative/intent-economics.d.ts +42 -0
  515. package/dist/kiro/build/src/narrative/intent-economics.js +82 -0
  516. package/dist/kiro/build/src/narrative/intent.d.ts +94 -0
  517. package/dist/kiro/build/src/narrative/intent.js +132 -0
  518. package/dist/kiro/build/src/narrative/policy-filter.d.ts +15 -0
  519. package/dist/kiro/build/src/narrative/policy-filter.js +58 -0
  520. package/dist/kiro/build/src/narrative/projection.d.ts +58 -0
  521. package/dist/kiro/build/src/narrative/projection.js +502 -0
  522. package/dist/kiro/build/src/narrative/readers.d.ts +84 -0
  523. package/dist/kiro/build/src/narrative/readers.js +406 -0
  524. package/dist/kiro/build/src/narrative/render.d.ts +3 -0
  525. package/dist/kiro/build/src/narrative/render.js +83 -0
  526. package/dist/kiro/build/src/narrative/resolver.d.ts +52 -0
  527. package/dist/kiro/build/src/narrative/resolver.js +190 -0
  528. package/dist/kiro/build/src/narrative/snapshot.d.ts +90 -0
  529. package/dist/kiro/build/src/narrative/snapshot.js +325 -0
  530. package/dist/kiro/build/src/narrative/source-ids.d.ts +121 -0
  531. package/dist/kiro/build/src/narrative/source-ids.js +201 -0
  532. package/dist/kiro/build/src/narrative/statements.d.ts +84 -0
  533. package/dist/kiro/build/src/narrative/statements.js +347 -0
  534. package/dist/kiro/build/src/narrative/turn-spine.d.ts +19 -0
  535. package/dist/kiro/build/src/narrative/turn-spine.js +73 -0
  536. package/dist/kiro/build/src/tools/build-universal-bundles.js +8 -0
  537. package/dist/kiro/build/src/tools/generate-capability-matrix.d.ts +7 -0
  538. package/dist/kiro/build/src/tools/generate-capability-matrix.js +207 -0
  539. package/dist/kiro/build/src/tools/validate-source-tree.js +5 -1
  540. package/dist/kiro/context/scripts/hooks/stop-goal-fit.js +182 -9
  541. package/dist/kiro/context/scripts/hooks/workflow-steering.js +160 -19
  542. package/dist/kiro/context/scripts/telemetry/lib/session.sh +3 -0
  543. package/dist/kiro/context/scripts/telemetry/lib/transport.sh +12 -3
  544. package/dist/kiro/context/scripts/telemetry/telemetry.sh +480 -59
  545. package/dist/kiro/docs/context-map.md +4 -0
  546. package/dist/kiro/docs/decisions/index.md +1 -0
  547. package/dist/kiro/docs/decisions/writer-observed-execution.md +51 -0
  548. package/dist/kiro/docs/fixture-ownership.md +5 -1
  549. package/dist/kiro/docs/migrations.md +19 -4
  550. package/dist/kiro/docs/public-workflow-cli.md +9 -0
  551. package/dist/kiro/docs/spec/builder-flow-runtime.md +49 -8
  552. package/dist/kiro/docs/spec/runtime-hook-surface.md +29 -1
  553. package/dist/kiro/docs/specs/economics-record-contract.md +1 -1
  554. package/dist/kiro/docs/specs/harness-capability-matrix.md +63 -21
  555. package/dist/kiro/docs/workflow-usage-guide.md +4 -1
  556. package/dist/kiro/evals/ci/run-baseline.sh +38 -0
  557. package/dist/kiro/evals/fixtures/narrative-evals/README.md +62 -0
  558. package/dist/kiro/evals/fixtures/narrative-evals/corpus.json +129 -0
  559. package/dist/kiro/evals/fixtures/narrative-evals/scorer.mjs +522 -0
  560. package/dist/kiro/evals/fixtures/narrative-grounding-validator/corpus.json +20 -0
  561. package/dist/kiro/evals/fixtures/narrative-grounding-validator/prompt-injection-adversarial.txt +1 -0
  562. package/dist/kiro/evals/fixtures/narrative-grounding-validator/prompt-injection-control.txt +1 -0
  563. package/dist/kiro/evals/fixtures/narrative-grounding-validator/scorer.mjs +201 -0
  564. package/dist/kiro/evals/fixtures/narrative-prose-renderer/corpus.json +15 -0
  565. package/dist/kiro/evals/fixtures/narrative-prose-renderer/scorer.mjs +201 -0
  566. package/dist/kiro/evals/fixtures/narrative-sources/expected-capture-completeness.json +13 -0
  567. package/dist/kiro/evals/fixtures/narrative-sources/flow/runs/run-fixture/report.json +1 -0
  568. package/dist/kiro/evals/fixtures/narrative-sources/flow/runs/run-fixture/state.json +1 -0
  569. package/dist/kiro/evals/fixtures/narrative-sources/flow/runs/run-runtime/state.json +1 -0
  570. package/dist/kiro/evals/fixtures/narrative-sources/repo/created.txt +1 -0
  571. package/dist/kiro/evals/fixtures/narrative-sources/repo/fixture.txt +1 -0
  572. package/dist/kiro/evals/fixtures/narrative-sources/session/agents/agent-fixture/events.jsonl +2 -0
  573. package/dist/kiro/evals/fixtures/narrative-sources/session/agents/nested-worker/events.jsonl +1 -0
  574. package/dist/kiro/evals/fixtures/narrative-sources/session/trust.bundle +1 -0
  575. package/dist/kiro/evals/fixtures/narrative-sources/telemetry/full.jsonl +3 -0
  576. package/dist/kiro/evals/fixtures/narrative-sources/telemetry/runtime.jsonl +4 -0
  577. package/dist/kiro/evals/fixtures/narrative-sources/transcripts/session.txt +1 -0
  578. package/dist/kiro/evals/integration/test_capability_declarations.sh +183 -0
  579. package/dist/kiro/evals/integration/test_economics_record.sh +42 -0
  580. package/dist/kiro/evals/integration/test_fixture_retirement_audit.sh +2 -2
  581. package/dist/kiro/evals/integration/test_gate_review_inquiry_records.sh +42 -35
  582. package/dist/kiro/evals/integration/test_goal_fit_hook.sh +13 -5
  583. package/dist/kiro/evals/integration/test_goal_fit_narrative_exclusion.sh +68 -0
  584. package/dist/kiro/evals/integration/test_narrative_evals.sh +174 -0
  585. package/dist/kiro/evals/integration/test_narrative_grounded_envelope.sh +280 -0
  586. package/dist/kiro/evals/integration/test_narrative_grounding_validator.sh +166 -0
  587. package/dist/kiro/evals/integration/test_narrative_intent_annotation.sh +205 -0
  588. package/dist/kiro/evals/integration/test_narrative_prose_renderer.sh +239 -0
  589. package/dist/kiro/evals/integration/test_narrative_redaction_failclosed.sh +120 -0
  590. package/dist/kiro/evals/integration/test_narrative_runtime_projection.sh +146 -0
  591. package/dist/kiro/evals/integration/test_narrative_source_contract.sh +228 -0
  592. package/dist/kiro/evals/integration/test_narrative_trust_isolation.sh +408 -0
  593. package/dist/kiro/evals/integration/test_plain_stop_messaging.sh +91 -0
  594. package/dist/kiro/evals/integration/test_session_resume_roundtrip.sh +228 -0
  595. package/dist/kiro/evals/integration/test_telemetry_action_class.sh +265 -0
  596. package/dist/kiro/evals/integration/test_telemetry_delegation.sh +291 -0
  597. package/dist/kiro/evals/integration/test_telemetry_sanitize_usage.sh +155 -0
  598. package/dist/kiro/evals/integration/test_telemetry_task_slug.sh +158 -0
  599. package/dist/kiro/evals/integration/test_telemetry_tool_outcome.sh +279 -0
  600. package/dist/kiro/evals/integration/test_workflow_sidecar_writer.sh +111 -2
  601. package/dist/kiro/evals/run.sh +12 -0
  602. package/dist/kiro/evals/static/test_library_exports.sh +5 -0
  603. package/dist/kiro/install.sh +1 -1
  604. package/dist/kiro/schemas/grounded-execution-narrative.schema.json +292 -0
  605. package/dist/kiro/schemas/narrative-eval-result.schema.json +205 -0
  606. package/dist/kiro/schemas/narrative-runtime-projection.schema.json +134 -0
  607. package/dist/kiro/schemas/narrative-source-manifest.schema.json +294 -0
  608. package/dist/kiro/scripts/check-hachure-boundary.mjs +81 -0
  609. package/dist/kiro/scripts/hooks/stop-goal-fit.js +182 -9
  610. package/dist/kiro/scripts/hooks/workflow-steering.js +160 -19
  611. package/dist/kiro/scripts/telemetry/economics-record.schema.json +1 -1
  612. package/dist/kiro/scripts/telemetry/economics-record.sh +47 -3
  613. package/dist/kiro/scripts/telemetry/lib/session.sh +3 -0
  614. package/dist/kiro/scripts/telemetry/lib/transport.sh +12 -3
  615. package/dist/kiro/scripts/telemetry/telemetry.sh +480 -59
  616. package/dist/opencode/build/generated/capability-declarations.json +177 -0
  617. package/dist/opencode/build/package.json +1 -1
  618. package/dist/opencode/build/src/builder-flow-runtime.d.ts +37 -0
  619. package/dist/opencode/build/src/builder-flow-runtime.js +71 -1
  620. package/dist/opencode/build/src/builder-gate-action-envelope.d.ts +58 -7
  621. package/dist/opencode/build/src/builder-gate-action-envelope.js +160 -28
  622. package/dist/opencode/build/src/builder-lifecycle-authority.d.ts +26 -0
  623. package/dist/opencode/build/src/builder-lifecycle-authority.js +38 -0
  624. package/dist/opencode/build/src/cli/builder-run.js +71 -2
  625. package/dist/opencode/build/src/cli/narrative-render.d.ts +95 -0
  626. package/dist/opencode/build/src/cli/narrative-render.js +466 -0
  627. package/dist/opencode/build/src/cli/narrative-sources.d.ts +2 -0
  628. package/dist/opencode/build/src/cli/narrative-sources.js +282 -0
  629. package/dist/opencode/build/src/cli/public-contracts.d.ts +257 -0
  630. package/dist/opencode/build/src/cli/public-contracts.js +87 -0
  631. package/dist/opencode/build/src/cli/workflow-sidecar.d.ts +28 -12
  632. package/dist/opencode/build/src/cli/workflow-sidecar.js +599 -180
  633. package/dist/opencode/build/src/cli/workflow.js +11 -1
  634. package/dist/opencode/build/src/cli.js +8 -0
  635. package/dist/opencode/build/src/continuation-driver.d.ts +12 -0
  636. package/dist/opencode/build/src/continuation-driver.js +16 -2
  637. package/dist/opencode/build/src/continuation-validation.js +324 -8
  638. package/dist/opencode/build/src/flow-kit/action-metadata.js +21 -1
  639. package/dist/opencode/build/src/index.d.ts +33 -2
  640. package/dist/opencode/build/src/index.js +24 -0
  641. package/dist/opencode/build/src/lib/capability-declarations.d.ts +83 -0
  642. package/dist/opencode/build/src/lib/capability-declarations.js +209 -0
  643. package/dist/opencode/build/src/narrative/envelope.d.ts +111 -0
  644. package/dist/opencode/build/src/narrative/envelope.js +590 -0
  645. package/dist/opencode/build/src/narrative/eval-result.d.ts +60 -0
  646. package/dist/opencode/build/src/narrative/eval-result.js +158 -0
  647. package/dist/opencode/build/src/narrative/grounding-validator.d.ts +104 -0
  648. package/dist/opencode/build/src/narrative/grounding-validator.js +472 -0
  649. package/dist/opencode/build/src/narrative/integrity.d.ts +17 -0
  650. package/dist/opencode/build/src/narrative/integrity.js +39 -0
  651. package/dist/opencode/build/src/narrative/intent-economics.d.ts +42 -0
  652. package/dist/opencode/build/src/narrative/intent-economics.js +82 -0
  653. package/dist/opencode/build/src/narrative/intent.d.ts +94 -0
  654. package/dist/opencode/build/src/narrative/intent.js +132 -0
  655. package/dist/opencode/build/src/narrative/policy-filter.d.ts +15 -0
  656. package/dist/opencode/build/src/narrative/policy-filter.js +58 -0
  657. package/dist/opencode/build/src/narrative/projection.d.ts +58 -0
  658. package/dist/opencode/build/src/narrative/projection.js +502 -0
  659. package/dist/opencode/build/src/narrative/readers.d.ts +84 -0
  660. package/dist/opencode/build/src/narrative/readers.js +406 -0
  661. package/dist/opencode/build/src/narrative/render.d.ts +3 -0
  662. package/dist/opencode/build/src/narrative/render.js +83 -0
  663. package/dist/opencode/build/src/narrative/resolver.d.ts +52 -0
  664. package/dist/opencode/build/src/narrative/resolver.js +190 -0
  665. package/dist/opencode/build/src/narrative/snapshot.d.ts +90 -0
  666. package/dist/opencode/build/src/narrative/snapshot.js +325 -0
  667. package/dist/opencode/build/src/narrative/source-ids.d.ts +121 -0
  668. package/dist/opencode/build/src/narrative/source-ids.js +201 -0
  669. package/dist/opencode/build/src/narrative/statements.d.ts +84 -0
  670. package/dist/opencode/build/src/narrative/statements.js +347 -0
  671. package/dist/opencode/build/src/narrative/turn-spine.d.ts +19 -0
  672. package/dist/opencode/build/src/narrative/turn-spine.js +73 -0
  673. package/dist/opencode/build/src/tools/build-universal-bundles.js +8 -0
  674. package/dist/opencode/build/src/tools/generate-capability-matrix.d.ts +7 -0
  675. package/dist/opencode/build/src/tools/generate-capability-matrix.js +207 -0
  676. package/dist/opencode/build/src/tools/validate-source-tree.js +5 -1
  677. package/dist/opencode/context/scripts/hooks/stop-goal-fit.js +182 -9
  678. package/dist/opencode/context/scripts/hooks/workflow-steering.js +160 -19
  679. package/dist/opencode/context/scripts/telemetry/lib/session.sh +3 -0
  680. package/dist/opencode/context/scripts/telemetry/lib/transport.sh +12 -3
  681. package/dist/opencode/context/scripts/telemetry/telemetry.sh +480 -59
  682. package/dist/opencode/docs/context-map.md +4 -0
  683. package/dist/opencode/docs/decisions/index.md +1 -0
  684. package/dist/opencode/docs/decisions/writer-observed-execution.md +51 -0
  685. package/dist/opencode/docs/fixture-ownership.md +5 -1
  686. package/dist/opencode/docs/migrations.md +19 -4
  687. package/dist/opencode/docs/public-workflow-cli.md +9 -0
  688. package/dist/opencode/docs/spec/builder-flow-runtime.md +49 -8
  689. package/dist/opencode/docs/spec/runtime-hook-surface.md +29 -1
  690. package/dist/opencode/docs/specs/economics-record-contract.md +1 -1
  691. package/dist/opencode/docs/specs/harness-capability-matrix.md +63 -21
  692. package/dist/opencode/docs/workflow-usage-guide.md +4 -1
  693. package/dist/opencode/evals/ci/run-baseline.sh +38 -0
  694. package/dist/opencode/evals/fixtures/narrative-evals/README.md +62 -0
  695. package/dist/opencode/evals/fixtures/narrative-evals/corpus.json +129 -0
  696. package/dist/opencode/evals/fixtures/narrative-evals/scorer.mjs +522 -0
  697. package/dist/opencode/evals/fixtures/narrative-grounding-validator/corpus.json +20 -0
  698. package/dist/opencode/evals/fixtures/narrative-grounding-validator/prompt-injection-adversarial.txt +1 -0
  699. package/dist/opencode/evals/fixtures/narrative-grounding-validator/prompt-injection-control.txt +1 -0
  700. package/dist/opencode/evals/fixtures/narrative-grounding-validator/scorer.mjs +201 -0
  701. package/dist/opencode/evals/fixtures/narrative-prose-renderer/corpus.json +15 -0
  702. package/dist/opencode/evals/fixtures/narrative-prose-renderer/scorer.mjs +201 -0
  703. package/dist/opencode/evals/fixtures/narrative-sources/expected-capture-completeness.json +13 -0
  704. package/dist/opencode/evals/fixtures/narrative-sources/flow/runs/run-fixture/report.json +1 -0
  705. package/dist/opencode/evals/fixtures/narrative-sources/flow/runs/run-fixture/state.json +1 -0
  706. package/dist/opencode/evals/fixtures/narrative-sources/flow/runs/run-runtime/state.json +1 -0
  707. package/dist/opencode/evals/fixtures/narrative-sources/repo/created.txt +1 -0
  708. package/dist/opencode/evals/fixtures/narrative-sources/repo/fixture.txt +1 -0
  709. package/dist/opencode/evals/fixtures/narrative-sources/session/agents/agent-fixture/events.jsonl +2 -0
  710. package/dist/opencode/evals/fixtures/narrative-sources/session/agents/nested-worker/events.jsonl +1 -0
  711. package/dist/opencode/evals/fixtures/narrative-sources/session/trust.bundle +1 -0
  712. package/dist/opencode/evals/fixtures/narrative-sources/telemetry/full.jsonl +3 -0
  713. package/dist/opencode/evals/fixtures/narrative-sources/telemetry/runtime.jsonl +4 -0
  714. package/dist/opencode/evals/fixtures/narrative-sources/transcripts/session.txt +1 -0
  715. package/dist/opencode/evals/integration/test_capability_declarations.sh +183 -0
  716. package/dist/opencode/evals/integration/test_economics_record.sh +42 -0
  717. package/dist/opencode/evals/integration/test_fixture_retirement_audit.sh +2 -2
  718. package/dist/opencode/evals/integration/test_gate_review_inquiry_records.sh +42 -35
  719. package/dist/opencode/evals/integration/test_goal_fit_hook.sh +13 -5
  720. package/dist/opencode/evals/integration/test_goal_fit_narrative_exclusion.sh +68 -0
  721. package/dist/opencode/evals/integration/test_narrative_evals.sh +174 -0
  722. package/dist/opencode/evals/integration/test_narrative_grounded_envelope.sh +280 -0
  723. package/dist/opencode/evals/integration/test_narrative_grounding_validator.sh +166 -0
  724. package/dist/opencode/evals/integration/test_narrative_intent_annotation.sh +205 -0
  725. package/dist/opencode/evals/integration/test_narrative_prose_renderer.sh +239 -0
  726. package/dist/opencode/evals/integration/test_narrative_redaction_failclosed.sh +120 -0
  727. package/dist/opencode/evals/integration/test_narrative_runtime_projection.sh +146 -0
  728. package/dist/opencode/evals/integration/test_narrative_source_contract.sh +228 -0
  729. package/dist/opencode/evals/integration/test_narrative_trust_isolation.sh +408 -0
  730. package/dist/opencode/evals/integration/test_plain_stop_messaging.sh +91 -0
  731. package/dist/opencode/evals/integration/test_session_resume_roundtrip.sh +228 -0
  732. package/dist/opencode/evals/integration/test_telemetry_action_class.sh +265 -0
  733. package/dist/opencode/evals/integration/test_telemetry_delegation.sh +291 -0
  734. package/dist/opencode/evals/integration/test_telemetry_sanitize_usage.sh +155 -0
  735. package/dist/opencode/evals/integration/test_telemetry_task_slug.sh +158 -0
  736. package/dist/opencode/evals/integration/test_telemetry_tool_outcome.sh +279 -0
  737. package/dist/opencode/evals/integration/test_workflow_sidecar_writer.sh +111 -2
  738. package/dist/opencode/evals/run.sh +12 -0
  739. package/dist/opencode/evals/static/test_library_exports.sh +5 -0
  740. package/dist/opencode/install.sh +1 -1
  741. package/dist/opencode/schemas/grounded-execution-narrative.schema.json +292 -0
  742. package/dist/opencode/schemas/narrative-eval-result.schema.json +205 -0
  743. package/dist/opencode/schemas/narrative-runtime-projection.schema.json +134 -0
  744. package/dist/opencode/schemas/narrative-source-manifest.schema.json +294 -0
  745. package/dist/opencode/scripts/check-hachure-boundary.mjs +81 -0
  746. package/dist/opencode/scripts/hooks/stop-goal-fit.js +182 -9
  747. package/dist/opencode/scripts/hooks/workflow-steering.js +160 -19
  748. package/dist/opencode/scripts/telemetry/economics-record.schema.json +1 -1
  749. package/dist/opencode/scripts/telemetry/economics-record.sh +47 -3
  750. package/dist/opencode/scripts/telemetry/lib/session.sh +3 -0
  751. package/dist/opencode/scripts/telemetry/lib/transport.sh +12 -3
  752. package/dist/opencode/scripts/telemetry/telemetry.sh +480 -59
  753. package/dist/pi/build/generated/capability-declarations.json +177 -0
  754. package/dist/pi/build/package.json +1 -1
  755. package/dist/pi/build/src/builder-flow-runtime.d.ts +37 -0
  756. package/dist/pi/build/src/builder-flow-runtime.js +71 -1
  757. package/dist/pi/build/src/builder-gate-action-envelope.d.ts +58 -7
  758. package/dist/pi/build/src/builder-gate-action-envelope.js +160 -28
  759. package/dist/pi/build/src/builder-lifecycle-authority.d.ts +26 -0
  760. package/dist/pi/build/src/builder-lifecycle-authority.js +38 -0
  761. package/dist/pi/build/src/cli/builder-run.js +71 -2
  762. package/dist/pi/build/src/cli/narrative-render.d.ts +95 -0
  763. package/dist/pi/build/src/cli/narrative-render.js +466 -0
  764. package/dist/pi/build/src/cli/narrative-sources.d.ts +2 -0
  765. package/dist/pi/build/src/cli/narrative-sources.js +282 -0
  766. package/dist/pi/build/src/cli/public-contracts.d.ts +257 -0
  767. package/dist/pi/build/src/cli/public-contracts.js +87 -0
  768. package/dist/pi/build/src/cli/workflow-sidecar.d.ts +28 -12
  769. package/dist/pi/build/src/cli/workflow-sidecar.js +599 -180
  770. package/dist/pi/build/src/cli/workflow.js +11 -1
  771. package/dist/pi/build/src/cli.js +8 -0
  772. package/dist/pi/build/src/continuation-driver.d.ts +12 -0
  773. package/dist/pi/build/src/continuation-driver.js +16 -2
  774. package/dist/pi/build/src/continuation-validation.js +324 -8
  775. package/dist/pi/build/src/flow-kit/action-metadata.js +21 -1
  776. package/dist/pi/build/src/index.d.ts +33 -2
  777. package/dist/pi/build/src/index.js +24 -0
  778. package/dist/pi/build/src/lib/capability-declarations.d.ts +83 -0
  779. package/dist/pi/build/src/lib/capability-declarations.js +209 -0
  780. package/dist/pi/build/src/narrative/envelope.d.ts +111 -0
  781. package/dist/pi/build/src/narrative/envelope.js +590 -0
  782. package/dist/pi/build/src/narrative/eval-result.d.ts +60 -0
  783. package/dist/pi/build/src/narrative/eval-result.js +158 -0
  784. package/dist/pi/build/src/narrative/grounding-validator.d.ts +104 -0
  785. package/dist/pi/build/src/narrative/grounding-validator.js +472 -0
  786. package/dist/pi/build/src/narrative/integrity.d.ts +17 -0
  787. package/dist/pi/build/src/narrative/integrity.js +39 -0
  788. package/dist/pi/build/src/narrative/intent-economics.d.ts +42 -0
  789. package/dist/pi/build/src/narrative/intent-economics.js +82 -0
  790. package/dist/pi/build/src/narrative/intent.d.ts +94 -0
  791. package/dist/pi/build/src/narrative/intent.js +132 -0
  792. package/dist/pi/build/src/narrative/policy-filter.d.ts +15 -0
  793. package/dist/pi/build/src/narrative/policy-filter.js +58 -0
  794. package/dist/pi/build/src/narrative/projection.d.ts +58 -0
  795. package/dist/pi/build/src/narrative/projection.js +502 -0
  796. package/dist/pi/build/src/narrative/readers.d.ts +84 -0
  797. package/dist/pi/build/src/narrative/readers.js +406 -0
  798. package/dist/pi/build/src/narrative/render.d.ts +3 -0
  799. package/dist/pi/build/src/narrative/render.js +83 -0
  800. package/dist/pi/build/src/narrative/resolver.d.ts +52 -0
  801. package/dist/pi/build/src/narrative/resolver.js +190 -0
  802. package/dist/pi/build/src/narrative/snapshot.d.ts +90 -0
  803. package/dist/pi/build/src/narrative/snapshot.js +325 -0
  804. package/dist/pi/build/src/narrative/source-ids.d.ts +121 -0
  805. package/dist/pi/build/src/narrative/source-ids.js +201 -0
  806. package/dist/pi/build/src/narrative/statements.d.ts +84 -0
  807. package/dist/pi/build/src/narrative/statements.js +347 -0
  808. package/dist/pi/build/src/narrative/turn-spine.d.ts +19 -0
  809. package/dist/pi/build/src/narrative/turn-spine.js +73 -0
  810. package/dist/pi/build/src/tools/build-universal-bundles.js +8 -0
  811. package/dist/pi/build/src/tools/generate-capability-matrix.d.ts +7 -0
  812. package/dist/pi/build/src/tools/generate-capability-matrix.js +207 -0
  813. package/dist/pi/build/src/tools/validate-source-tree.js +5 -1
  814. package/dist/pi/context/scripts/hooks/stop-goal-fit.js +182 -9
  815. package/dist/pi/context/scripts/hooks/workflow-steering.js +160 -19
  816. package/dist/pi/context/scripts/telemetry/lib/session.sh +3 -0
  817. package/dist/pi/context/scripts/telemetry/lib/transport.sh +12 -3
  818. package/dist/pi/context/scripts/telemetry/telemetry.sh +480 -59
  819. package/dist/pi/docs/context-map.md +4 -0
  820. package/dist/pi/docs/decisions/index.md +1 -0
  821. package/dist/pi/docs/decisions/writer-observed-execution.md +51 -0
  822. package/dist/pi/docs/fixture-ownership.md +5 -1
  823. package/dist/pi/docs/migrations.md +19 -4
  824. package/dist/pi/docs/public-workflow-cli.md +9 -0
  825. package/dist/pi/docs/spec/builder-flow-runtime.md +49 -8
  826. package/dist/pi/docs/spec/runtime-hook-surface.md +29 -1
  827. package/dist/pi/docs/specs/economics-record-contract.md +1 -1
  828. package/dist/pi/docs/specs/harness-capability-matrix.md +63 -21
  829. package/dist/pi/docs/workflow-usage-guide.md +4 -1
  830. package/dist/pi/evals/ci/run-baseline.sh +38 -0
  831. package/dist/pi/evals/fixtures/narrative-evals/README.md +62 -0
  832. package/dist/pi/evals/fixtures/narrative-evals/corpus.json +129 -0
  833. package/dist/pi/evals/fixtures/narrative-evals/scorer.mjs +522 -0
  834. package/dist/pi/evals/fixtures/narrative-grounding-validator/corpus.json +20 -0
  835. package/dist/pi/evals/fixtures/narrative-grounding-validator/prompt-injection-adversarial.txt +1 -0
  836. package/dist/pi/evals/fixtures/narrative-grounding-validator/prompt-injection-control.txt +1 -0
  837. package/dist/pi/evals/fixtures/narrative-grounding-validator/scorer.mjs +201 -0
  838. package/dist/pi/evals/fixtures/narrative-prose-renderer/corpus.json +15 -0
  839. package/dist/pi/evals/fixtures/narrative-prose-renderer/scorer.mjs +201 -0
  840. package/dist/pi/evals/fixtures/narrative-sources/expected-capture-completeness.json +13 -0
  841. package/dist/pi/evals/fixtures/narrative-sources/flow/runs/run-fixture/report.json +1 -0
  842. package/dist/pi/evals/fixtures/narrative-sources/flow/runs/run-fixture/state.json +1 -0
  843. package/dist/pi/evals/fixtures/narrative-sources/flow/runs/run-runtime/state.json +1 -0
  844. package/dist/pi/evals/fixtures/narrative-sources/repo/created.txt +1 -0
  845. package/dist/pi/evals/fixtures/narrative-sources/repo/fixture.txt +1 -0
  846. package/dist/pi/evals/fixtures/narrative-sources/session/agents/agent-fixture/events.jsonl +2 -0
  847. package/dist/pi/evals/fixtures/narrative-sources/session/agents/nested-worker/events.jsonl +1 -0
  848. package/dist/pi/evals/fixtures/narrative-sources/session/trust.bundle +1 -0
  849. package/dist/pi/evals/fixtures/narrative-sources/telemetry/full.jsonl +3 -0
  850. package/dist/pi/evals/fixtures/narrative-sources/telemetry/runtime.jsonl +4 -0
  851. package/dist/pi/evals/fixtures/narrative-sources/transcripts/session.txt +1 -0
  852. package/dist/pi/evals/integration/test_capability_declarations.sh +183 -0
  853. package/dist/pi/evals/integration/test_economics_record.sh +42 -0
  854. package/dist/pi/evals/integration/test_fixture_retirement_audit.sh +2 -2
  855. package/dist/pi/evals/integration/test_gate_review_inquiry_records.sh +42 -35
  856. package/dist/pi/evals/integration/test_goal_fit_hook.sh +13 -5
  857. package/dist/pi/evals/integration/test_goal_fit_narrative_exclusion.sh +68 -0
  858. package/dist/pi/evals/integration/test_narrative_evals.sh +174 -0
  859. package/dist/pi/evals/integration/test_narrative_grounded_envelope.sh +280 -0
  860. package/dist/pi/evals/integration/test_narrative_grounding_validator.sh +166 -0
  861. package/dist/pi/evals/integration/test_narrative_intent_annotation.sh +205 -0
  862. package/dist/pi/evals/integration/test_narrative_prose_renderer.sh +239 -0
  863. package/dist/pi/evals/integration/test_narrative_redaction_failclosed.sh +120 -0
  864. package/dist/pi/evals/integration/test_narrative_runtime_projection.sh +146 -0
  865. package/dist/pi/evals/integration/test_narrative_source_contract.sh +228 -0
  866. package/dist/pi/evals/integration/test_narrative_trust_isolation.sh +408 -0
  867. package/dist/pi/evals/integration/test_plain_stop_messaging.sh +91 -0
  868. package/dist/pi/evals/integration/test_session_resume_roundtrip.sh +228 -0
  869. package/dist/pi/evals/integration/test_telemetry_action_class.sh +265 -0
  870. package/dist/pi/evals/integration/test_telemetry_delegation.sh +291 -0
  871. package/dist/pi/evals/integration/test_telemetry_sanitize_usage.sh +155 -0
  872. package/dist/pi/evals/integration/test_telemetry_task_slug.sh +158 -0
  873. package/dist/pi/evals/integration/test_telemetry_tool_outcome.sh +279 -0
  874. package/dist/pi/evals/integration/test_workflow_sidecar_writer.sh +111 -2
  875. package/dist/pi/evals/run.sh +12 -0
  876. package/dist/pi/evals/static/test_library_exports.sh +5 -0
  877. package/dist/pi/install.sh +1 -1
  878. package/dist/pi/schemas/grounded-execution-narrative.schema.json +292 -0
  879. package/dist/pi/schemas/narrative-eval-result.schema.json +205 -0
  880. package/dist/pi/schemas/narrative-runtime-projection.schema.json +134 -0
  881. package/dist/pi/schemas/narrative-source-manifest.schema.json +294 -0
  882. package/dist/pi/scripts/check-hachure-boundary.mjs +81 -0
  883. package/dist/pi/scripts/hooks/stop-goal-fit.js +182 -9
  884. package/dist/pi/scripts/hooks/workflow-steering.js +160 -19
  885. package/dist/pi/scripts/telemetry/economics-record.schema.json +1 -1
  886. package/dist/pi/scripts/telemetry/economics-record.sh +47 -3
  887. package/dist/pi/scripts/telemetry/lib/session.sh +3 -0
  888. package/dist/pi/scripts/telemetry/lib/transport.sh +12 -3
  889. package/dist/pi/scripts/telemetry/telemetry.sh +480 -59
  890. package/docs/context-map.md +4 -0
  891. package/docs/decisions/index.md +1 -0
  892. package/docs/decisions/writer-observed-execution.md +51 -0
  893. package/docs/fixture-ownership.md +5 -1
  894. package/docs/migrations.md +19 -4
  895. package/docs/public-workflow-cli.md +9 -0
  896. package/docs/spec/builder-flow-runtime.md +49 -8
  897. package/docs/spec/runtime-hook-surface.md +29 -1
  898. package/docs/specs/economics-record-contract.md +1 -1
  899. package/docs/specs/harness-capability-matrix.md +63 -21
  900. package/docs/workflow-usage-guide.md +4 -1
  901. package/evals/ci/run-baseline.sh +38 -0
  902. package/evals/fixtures/narrative-evals/README.md +62 -0
  903. package/evals/fixtures/narrative-evals/corpus.json +129 -0
  904. package/evals/fixtures/narrative-evals/scorer.mjs +522 -0
  905. package/evals/fixtures/narrative-grounding-validator/corpus.json +20 -0
  906. package/evals/fixtures/narrative-grounding-validator/prompt-injection-adversarial.txt +1 -0
  907. package/evals/fixtures/narrative-grounding-validator/prompt-injection-control.txt +1 -0
  908. package/evals/fixtures/narrative-grounding-validator/scorer.mjs +201 -0
  909. package/evals/fixtures/narrative-prose-renderer/corpus.json +15 -0
  910. package/evals/fixtures/narrative-prose-renderer/scorer.mjs +201 -0
  911. package/evals/fixtures/narrative-sources/expected-capture-completeness.json +13 -0
  912. package/evals/fixtures/narrative-sources/flow/runs/run-fixture/report.json +1 -0
  913. package/evals/fixtures/narrative-sources/flow/runs/run-fixture/state.json +1 -0
  914. package/evals/fixtures/narrative-sources/flow/runs/run-runtime/state.json +1 -0
  915. package/evals/fixtures/narrative-sources/repo/created.txt +1 -0
  916. package/evals/fixtures/narrative-sources/repo/fixture.txt +1 -0
  917. package/evals/fixtures/narrative-sources/session/agents/agent-fixture/events.jsonl +2 -0
  918. package/evals/fixtures/narrative-sources/session/agents/nested-worker/events.jsonl +1 -0
  919. package/evals/fixtures/narrative-sources/session/trust.bundle +1 -0
  920. package/evals/fixtures/narrative-sources/telemetry/full.jsonl +3 -0
  921. package/evals/fixtures/narrative-sources/telemetry/runtime.jsonl +4 -0
  922. package/evals/fixtures/narrative-sources/transcripts/session.txt +1 -0
  923. package/evals/integration/test_capability_declarations.sh +183 -0
  924. package/evals/integration/test_economics_record.sh +42 -0
  925. package/evals/integration/test_fixture_retirement_audit.sh +2 -2
  926. package/evals/integration/test_gate_review_inquiry_records.sh +42 -35
  927. package/evals/integration/test_goal_fit_hook.sh +13 -5
  928. package/evals/integration/test_goal_fit_narrative_exclusion.sh +68 -0
  929. package/evals/integration/test_narrative_evals.sh +174 -0
  930. package/evals/integration/test_narrative_grounded_envelope.sh +280 -0
  931. package/evals/integration/test_narrative_grounding_validator.sh +166 -0
  932. package/evals/integration/test_narrative_intent_annotation.sh +205 -0
  933. package/evals/integration/test_narrative_prose_renderer.sh +239 -0
  934. package/evals/integration/test_narrative_redaction_failclosed.sh +120 -0
  935. package/evals/integration/test_narrative_runtime_projection.sh +146 -0
  936. package/evals/integration/test_narrative_source_contract.sh +228 -0
  937. package/evals/integration/test_narrative_trust_isolation.sh +408 -0
  938. package/evals/integration/test_plain_stop_messaging.sh +91 -0
  939. package/evals/integration/test_session_resume_roundtrip.sh +228 -0
  940. package/evals/integration/test_telemetry_action_class.sh +265 -0
  941. package/evals/integration/test_telemetry_delegation.sh +291 -0
  942. package/evals/integration/test_telemetry_sanitize_usage.sh +155 -0
  943. package/evals/integration/test_telemetry_task_slug.sh +158 -0
  944. package/evals/integration/test_telemetry_tool_outcome.sh +279 -0
  945. package/evals/integration/test_workflow_sidecar_writer.sh +111 -2
  946. package/evals/run.sh +12 -0
  947. package/evals/static/test_library_exports.sh +5 -0
  948. package/package.json +9 -5
  949. package/schemas/grounded-execution-narrative.schema.json +292 -0
  950. package/schemas/narrative-eval-result.schema.json +205 -0
  951. package/schemas/narrative-runtime-projection.schema.json +134 -0
  952. package/schemas/narrative-source-manifest.schema.json +294 -0
  953. package/scripts/check-hachure-boundary.mjs +81 -0
  954. package/scripts/hooks/stop-goal-fit.js +182 -9
  955. package/scripts/hooks/workflow-steering.js +160 -19
  956. package/scripts/telemetry/economics-record.schema.json +1 -1
  957. package/scripts/telemetry/economics-record.sh +47 -3
  958. package/scripts/telemetry/lib/session.sh +3 -0
  959. package/scripts/telemetry/lib/transport.sh +12 -3
  960. package/scripts/telemetry/telemetry.sh +480 -59
  961. package/src/builder-flow-runtime.ts +102 -1
  962. package/src/builder-gate-action-envelope.ts +212 -35
  963. package/src/builder-lifecycle-authority.ts +48 -0
  964. package/src/cli/builder-flow-runtime.test.mjs +218 -7
  965. package/src/cli/builder-run.ts +71 -1
  966. package/src/cli/capability-declarations.test.mjs +117 -0
  967. package/src/cli/continuation-driver.test.mjs +327 -20
  968. package/src/cli/kit-metadata-security.test.mjs +90 -2
  969. package/src/cli/narrative-arch-isolation.test.mjs +198 -0
  970. package/src/cli/narrative-envelope.test.mjs +367 -0
  971. package/src/cli/narrative-grounding-validator.test.mjs +322 -0
  972. package/src/cli/narrative-intent.test.mjs +177 -0
  973. package/src/cli/narrative-policy-filter.test.mjs +66 -0
  974. package/src/cli/narrative-projection.test.mjs +236 -0
  975. package/src/cli/narrative-readers.test.mjs +196 -0
  976. package/src/cli/narrative-render.test.mjs +221 -0
  977. package/src/cli/narrative-render.ts +589 -0
  978. package/src/cli/narrative-resolver.test.mjs +114 -0
  979. package/src/cli/narrative-snapshot.test.mjs +149 -0
  980. package/src/cli/narrative-source-ids.test.mjs +84 -0
  981. package/src/cli/narrative-sources.ts +276 -0
  982. package/src/cli/narrative-statements.test.mjs +369 -0
  983. package/src/cli/narrative-turn-spine.test.mjs +74 -0
  984. package/src/cli/public-api.test.mjs +17 -0
  985. package/src/cli/public-contracts.ts +95 -0
  986. package/src/cli/sidecar-pure-helpers.test.mjs +216 -24
  987. package/src/cli/trust-bundle-policy-order.test.mjs +134 -1
  988. package/src/cli/workflow-sidecar.ts +563 -167
  989. package/src/cli/workflow.ts +15 -1
  990. package/src/cli/writer-observed-execution.test.mjs +102 -0
  991. package/src/cli.ts +8 -0
  992. package/src/continuation-driver.ts +32 -3
  993. package/src/continuation-validation.ts +336 -8
  994. package/src/flow-kit/action-metadata.ts +20 -1
  995. package/src/index.ts +241 -0
  996. package/src/lib/capability-declarations.ts +261 -0
  997. package/src/narrative/envelope.ts +624 -0
  998. package/src/narrative/eval-result.ts +175 -0
  999. package/src/narrative/grounding-validator.ts +610 -0
  1000. package/src/narrative/integrity.ts +53 -0
  1001. package/src/narrative/intent-economics.ts +119 -0
  1002. package/src/narrative/intent.ts +216 -0
  1003. package/src/narrative/policy-filter.ts +66 -0
  1004. package/src/narrative/projection.ts +530 -0
  1005. package/src/narrative/readers.ts +427 -0
  1006. package/src/narrative/render.ts +82 -0
  1007. package/src/narrative/resolver.ts +195 -0
  1008. package/src/narrative/snapshot.ts +365 -0
  1009. package/src/narrative/source-ids.ts +294 -0
  1010. package/src/narrative/statements.ts +423 -0
  1011. package/src/narrative/turn-spine.ts +99 -0
  1012. package/src/tools/build-universal-bundles.ts +8 -0
  1013. package/src/tools/generate-capability-matrix.ts +215 -0
  1014. package/src/tools/validate-source-tree.ts +5 -1
  1015. package/build/src/cli/sidecar-claim-explain.d.ts +0 -45
  1016. package/build/src/cli/sidecar-claim-explain.js +0 -87
  1017. package/dist/base/build/src/cli/sidecar-claim-explain.d.ts +0 -45
  1018. package/dist/base/build/src/cli/sidecar-claim-explain.js +0 -87
  1019. package/dist/claude-code/build/src/cli/sidecar-claim-explain.d.ts +0 -45
  1020. package/dist/claude-code/build/src/cli/sidecar-claim-explain.js +0 -87
  1021. package/dist/codex/build/src/cli/sidecar-claim-explain.d.ts +0 -45
  1022. package/dist/codex/build/src/cli/sidecar-claim-explain.js +0 -87
  1023. package/dist/kiro/build/src/cli/sidecar-claim-explain.d.ts +0 -45
  1024. package/dist/kiro/build/src/cli/sidecar-claim-explain.js +0 -87
  1025. package/dist/opencode/build/src/cli/sidecar-claim-explain.d.ts +0 -45
  1026. package/dist/opencode/build/src/cli/sidecar-claim-explain.js +0 -87
  1027. package/dist/pi/build/src/cli/sidecar-claim-explain.d.ts +0 -45
  1028. package/dist/pi/build/src/cli/sidecar-claim-explain.js +0 -87
  1029. package/src/cli/sidecar-claim-explain.ts +0 -130
@@ -14,7 +14,15 @@ import { flowAgentsPackageRoot, flowAgentsPackageVersion } from "../lib/package-
14
14
  import { pinnedFlowAgentsCommand } from "../lib/pinned-cli-command.js";
15
15
  import { runObservedCommand } from "../lib/observed-command.js";
16
16
  import { captureReviewWorkspaceSnapshot, startBuilderFlowSession, syncBuilderFlowSession } from "../builder-flow-runtime.js";
17
- import { WORKFLOW_CRITIQUE_STATUSES } from "./public-contracts.js";
17
+ import { NARRATIVE_NAMESPACE_ROOT } from "./narrative-sources.js";
18
+ import {
19
+ EVIDENCE_REF_FIELD_SCHEMAS,
20
+ EVIDENCE_REF_KINDS,
21
+ EVIDENCE_REF_RULES,
22
+ NARRATIVE_PROMOTE_OPERATION,
23
+ WORKFLOW_ACCEPTANCE_STATUSES,
24
+ WORKFLOW_CRITIQUE_STATUSES,
25
+ } from "./public-contracts.js";
18
26
  // #291 Wave 1 Task 1.1 exports: ensure-session's ownership guard reuses the EXACT same
19
27
  // assignment ⋈ liveness join / claim / supersede logic #290 already ships for the
20
28
  // `assignment-provider` CLI, rather than reimplementing a second, parallel join (static ESM
@@ -374,53 +382,22 @@ export async function validateTrustBundle(bundle: unknown): Promise<{ valid: boo
374
382
  return { valid: false, errors: [message], available: true };
375
383
  }
376
384
  }
377
- // Validate a single InquiryRecord against the hachure inquiry-record.schema.json.
378
- // Uses a separate AJV instance compiled against that schema (not the trust-bundle schema).
379
- let _hachureInquiryRecordValidator: ((record: unknown) => { valid: boolean; errors: string[] }) | null | undefined;
380
- function getHachureInquiryRecordValidator(): ((record: unknown) => { valid: boolean; errors: string[] }) | null {
381
- if (_hachureInquiryRecordValidator !== undefined) return _hachureInquiryRecordValidator;
382
- try {
383
- const _require = createRequire(import.meta.url);
384
- const hachureDir = path.dirname(_require.resolve("hachure"));
385
- const schemasDir = path.join(hachureDir, "schemas");
386
- const Ajv = _require("ajv/dist/2020");
387
- const schemas: Record<string, any> = {};
388
- for (const file of fs.readdirSync(schemasDir)) {
389
- if (!file.endsWith(".schema.json")) continue;
390
- schemas[file] = JSON.parse(fs.readFileSync(path.join(schemasDir, file), "utf8"));
391
- }
392
- const inquiryRecordSchema = schemas["inquiry-record.schema.json"];
393
- if (!inquiryRecordSchema) { _hachureInquiryRecordValidator = null; return null; }
394
- const ajv = new Ajv({ strict: false, allErrors: true });
395
- for (const [filename, schema] of Object.entries(schemas)) {
396
- if (filename === "inquiry-record.schema.json") continue;
397
- ajv.addSchema(schema, filename);
398
- }
399
- const validate = ajv.compile(inquiryRecordSchema);
400
- _hachureInquiryRecordValidator = (record: unknown) => {
401
- const valid = validate(record);
402
- if (valid) return { valid: true, errors: [] };
403
- const errors = ((validate as any).errors ?? []).map((err: any) => {
404
- const loc = err.instancePath || err.schemaPath || "";
405
- return `${loc} ${err.message ?? "invalid"}`.trim();
406
- });
407
- return { valid: false, errors };
408
- };
409
- return _hachureInquiryRecordValidator;
410
- } catch {
411
- _hachureInquiryRecordValidator = null;
412
- return null;
413
- }
414
- }
415
385
  /**
416
- * Validate a record against the canonical hachure inquiry-record.schema.json
417
- * (https://kontourai.io/schemas/surface/inquiry-record.schema.json).
418
- * Returns `{ valid, errors, available }`. Fail-open when hachure is not installed.
386
+ * Validate a record against the canonical InquiryRecord schema via
387
+ * @kontourai/surface's `validateInquiryRecord` (symmetric to `validateTrustBundle`).
388
+ * Returns `{ valid, errors, available }`. Fail-open when Surface is unavailable.
419
389
  */
420
- export function validateInquiryRecord(record: unknown): { valid: boolean; errors: string[]; available: boolean } {
421
- const validate = getHachureInquiryRecordValidator();
422
- if (!validate) return { valid: true, errors: [], available: false };
423
- return { ...validate(record), available: true };
390
+ export async function validateInquiryRecord(record: unknown): Promise<{ valid: boolean; errors: string[]; available: boolean }> {
391
+ const m = await tryLoadSurface();
392
+ if (!m || typeof (m as { validateInquiryRecord?: unknown }).validateInquiryRecord !== "function") {
393
+ return { valid: true, errors: [], available: false };
394
+ }
395
+ try {
396
+ (m as unknown as { validateInquiryRecord: (r: unknown) => unknown }).validateInquiryRecord(record);
397
+ return { valid: true, errors: [], available: true };
398
+ } catch (err) {
399
+ return { valid: false, errors: [err instanceof Error ? err.message : String(err)], available: true };
400
+ }
424
401
  }
425
402
  // ─── @kontourai/surface status derivation ────────────────────────────────────
426
403
  // Surface is ESM-only; this module builds to CJS. Load Surface via a fail-open
@@ -471,8 +448,12 @@ type SurfaceModule = {
471
448
  ) => SurfaceInquiryRecord;
472
449
  buildTrustReport: (bundle: Record<string, unknown>, options?: { now?: Date }) => Record<string, unknown>;
473
450
  buildDerivationDrilldown: (report: Record<string, unknown>, claimId: string) => Record<string, unknown>;
451
+ /** #171: consumer-ready claim explanation (Surface >=2.10); composes the drilldown internally, fail-soft. */
452
+ explainClaim: (report: Record<string, unknown>, claimId: string) => import("@kontourai/surface").ClaimExplanation;
474
453
  /** Canonical trust-bundle validator from @kontourai/surface. Throws on invalid input; returns TrustBundle on success. */
475
454
  validateTrustBundle: (input: unknown) => Record<string, unknown>;
455
+ /** Canonical InquiryRecord validator from @kontourai/surface. Throws on invalid input; returns InquiryRecord on success. */
456
+ validateInquiryRecord: (input: unknown) => Record<string, unknown>;
476
457
  /** Freeze a derivation checkpoint from a report. */
477
458
  checkpointFromReport: (report: Record<string, unknown>) => Record<string, unknown>;
478
459
  /** Diff two derivations (prior checkpoint → later report) and emit freshness transition events. */
@@ -533,6 +514,18 @@ function criterionStatusToEventStatus(status: string): string | null {
533
514
  if (status === "accepted_gap") return "assumed";
534
515
  return null; // pending / not_verified → no event → Surface returns "unknown"
535
516
  }
517
+
518
+ const ACCEPTANCE_CRITERION_STATUSES = new Set<string>(WORKFLOW_ACCEPTANCE_STATUSES);
519
+ const CANONICALLY_OBSERVED_ACCEPTANCE_CRITERIA = new WeakSet<object>();
520
+
521
+ function markCanonicallyObservedCriterion<T extends AnyObj>(criterion: T): T {
522
+ CANONICALLY_OBSERVED_ACCEPTANCE_CRITERIA.add(criterion);
523
+ return criterion;
524
+ }
525
+
526
+ function normalizeCriterionStatus(status: unknown): string {
527
+ return typeof status === "string" && ACCEPTANCE_CRITERION_STATUSES.has(status) ? status : "pending";
528
+ }
536
529
  /**
537
530
  * WS8 (ADR 0020): Derive Surface evidence classification (evidenceType + method)
538
531
  * from a workflow check's kind, replacing the previous hardcoded `test_output`.
@@ -635,7 +628,20 @@ export function reduceCaptureLogByCommand(commandLog: AnyObj[] | undefined): Map
635
628
  else if (prev.observedResult === "pass" || result === "pass") merged = "pass";
636
629
  else merged = "ambiguous";
637
630
  }
638
- const mergedExitCode = exitCode !== null ? exitCode : (prev ? prev.exitCode : null);
631
+ // #634 review finding: the exit code must travel with the WINNING status, never a
632
+ // losing entry's — [hook fail exit 1, writer pass exit 0] must fold to fail/1, not
633
+ // fail/0 (contradictory isError:true, exitCode:0 evidence). When both entries carry
634
+ // the winning status, prefer the newer non-null code.
635
+ let mergedExitCode: number | null;
636
+ if (!prev) {
637
+ mergedExitCode = exitCode;
638
+ } else if (merged === result && merged === prev.observedResult) {
639
+ mergedExitCode = exitCode !== null ? exitCode : prev.exitCode;
640
+ } else if (merged === result) {
641
+ mergedExitCode = exitCode;
642
+ } else {
643
+ mergedExitCode = prev.exitCode;
644
+ }
639
645
  captureByCommand.set(key, { observedResult: merged, exitCode: mergedExitCode });
640
646
  }
641
647
  return captureByCommand;
@@ -830,8 +836,6 @@ export async function buildTrustBundle(slug: string, timestamp: string, checks:
830
836
  // "flow-step". Fallback: first non-decision, non-acceptance entry.
831
837
  // check (kind=policy) → expects[] entry whose claimType contains
832
838
  // "compliance" or "policy". Fallback: same as non-policy.
833
- // acceptance criterion → expects[] entry whose subjectType is "flow-step"
834
- // OR claimType contains "tests" OR "compliance". Fallback: first entry.
835
839
  // critique → expects[] entry whose claimType contains "policy"
836
840
  // OR "compliance" AND subjectType is "artifact". Fallback: last entry.
837
841
  //
@@ -841,7 +845,7 @@ export async function buildTrustBundle(slug: string, timestamp: string, checks:
841
845
  // Surface from that evidence (never hand-set).
842
846
  //
843
847
  // Per-gate producibility (ADR 0016 P-d):
844
- // (a) Already handled via subjectType=flow-step preference:
848
+ // (a) Already handled via subjectType=flow-step preference for checks:
845
849
  // builder.verify.tests (verify-gate, subjectType=flow-step)
846
850
  // builder.verify.policy-compliance (verify-gate, kind=policy match)
847
851
  // (b) Producible via fallback (non-decision, non-acceptance, first match):
@@ -858,7 +862,10 @@ export async function buildTrustBundle(slug: string, timestamp: string, checks:
858
862
  // builder.learn.evidence (learn-gate, subjectType=release)
859
863
  // For category (c): record-gate-claim subcommand allows skills to target a specific
860
864
  // expects[] entry by --expectation <id>, bypassing this semantic match entirely.
861
- function matchExpectsEntry(kind: "check" | "acceptance" | "critique", checkKindVal?: string, expectationId?: string): { claimType: string; subjectType: string } | null {
865
+ //
866
+ // Acceptance criteria do not participate in this matching table. They are durable Work Item
867
+ // facts with invariant workflow.acceptance.criterion/flow-step identity across every gate.
868
+ function matchExpectsEntry(kind: "check" | "critique", checkKindVal?: string, expectationId?: string): { claimType: string; subjectType: string } | null {
862
869
  if (!activeStep || activeStep.gateExpects.length === 0) return null;
863
870
  const expects = activeStep.gateExpects;
864
871
  if (kind === "check") {
@@ -891,14 +898,6 @@ export async function buildTrustBundle(slug: string, timestamp: string, checks:
891
898
  if (fallback) return { claimType: fallback.bundle_claim.claimType, subjectType: fallback.bundle_claim.subjectType };
892
899
  return null;
893
900
  }
894
- if (kind === "acceptance") {
895
- const match = expects.find((e) => {
896
- const ct = e.bundle_claim.claimType.toLowerCase();
897
- return e.bundle_claim.subjectType === "flow-step" || ct.includes("tests") || ct.includes("compliance");
898
- });
899
- if (match) return { claimType: match.bundle_claim.claimType, subjectType: match.bundle_claim.subjectType };
900
- return { claimType: expects[0]!.bundle_claim.claimType, subjectType: expects[0]!.bundle_claim.subjectType };
901
- }
902
901
  if (kind === "critique") {
903
902
  const match = expects.find((e) => {
904
903
  const ct = e.bundle_claim.claimType.toLowerCase();
@@ -1093,33 +1092,74 @@ export async function buildTrustBundle(slug: string, timestamp: string, checks:
1093
1092
  if (!criterion.id) continue;
1094
1093
  const subjectId = `${slug}/${criterion.id}`;
1095
1094
  const fieldOrBehavior = String(criterion.description ?? criterion.id);
1095
+ const normalizedCriterionStatus = normalizeCriterionStatus(criterion.status);
1096
1096
  const criterionIdentityVersion = criterion.identity_version === 2 ? 2 : 1;
1097
1097
  const criterionVerifiedAt = criterionIdentityVersion === 2 && typeof criterion.verified_at === "string" ? criterion.verified_at : null;
1098
+ const criterionEvidenceRefs = Array.isArray(criterion.evidence_refs) ? criterion.evidence_refs : [];
1099
+ const commandRefs = criterionEvidenceRefs
1100
+ .filter((ref: AnyObj) => ref?.kind === "command")
1101
+ .map((ref: AnyObj) => commandFromEvidenceRef(ref))
1102
+ .filter((command: string) => command.length > 0);
1103
+ const rawObservedCommands = Array.isArray(criterion._observed_commands) ? criterion._observed_commands as AnyObj[] : [];
1104
+ const observedCommands = rawObservedCommands.filter((observation: AnyObj) =>
1105
+ typeof observation?.command === "string"
1106
+ && commandRefs.includes(observation.command)
1107
+ && observation.exit_code === 0
1108
+ && typeof observation.output_sha256 === "string"
1109
+ && /^[a-f0-9]{64}$/i.test(observation.output_sha256)
1110
+ && Number.isSafeInteger(observation.test_count)
1111
+ && observation.test_count > 0
1112
+ && observation.execution_proof?.kind === "local-process-exit",
1113
+ );
1114
+ const hasObservedCommandProvenance = CANONICALLY_OBSERVED_ACCEPTANCE_CRITERIA.has(criterion)
1115
+ && criterionIdentityVersion === 2
1116
+ && criterionVerifiedAt !== null
1117
+ && Number.isFinite(Date.parse(criterionVerifiedAt))
1118
+ && observedCommands.length === rawObservedCommands.length
1119
+ && observedCommands.length > 0
1120
+ && commandRefs.every((command: string) => observedCommands.some((observation: AnyObj) => observation.command === command));
1121
+ const criterionStatus = normalizedCriterionStatus === "pass" && !hasObservedCommandProvenance
1122
+ ? "pending"
1123
+ : normalizedCriterionStatus;
1124
+ const criterionClaimTimestamp = criterionStatus === "pass" && criterionVerifiedAt ? criterionVerifiedAt : ts;
1098
1125
  const claimId = generateClaimId(subjectId, "flow-agents.workflow", criterionVerifiedAt ? `${fieldOrBehavior}::verified::${criterionVerifiedAt}` : fieldOrBehavior);
1099
- const legacyClaimType = "workflow.acceptance.criterion";
1100
- const policy = ensurePolicy(legacyClaimType, "high", []);
1101
- const evStatus = criterionStatusToEventStatus(String(criterion.status ?? ""));
1126
+ const claimType = "workflow.acceptance.criterion";
1127
+ const criterionEvidenceIds: string[] = [];
1128
+ if (criterionStatus === "pass") {
1129
+ for (const observation of observedCommands) {
1130
+ const evidenceId = `ev:${claimId}:${createHash("sha256").update(observation.command).digest("hex").slice(0, 16)}`;
1131
+ criterionEvidenceIds.push(evidenceId);
1132
+ evidenceItems.push({
1133
+ id: evidenceId,
1134
+ claimId,
1135
+ evidenceType: "test_output",
1136
+ method: "validation",
1137
+ sourceRef: `${slug}/observed-command`,
1138
+ excerptOrSummary: observation.command,
1139
+ observedAt: criterionVerifiedAt,
1140
+ collectedBy: "flow-agents/workflow-sidecar",
1141
+ passing: true,
1142
+ execution: { runner: "bash", label: observation.command, isError: false, exitCode: 0 },
1143
+ });
1144
+ }
1145
+ }
1146
+ const policy = ensurePolicy(claimType, "high", criterionEvidenceIds.length > 0 ? ["test_output"] : []);
1147
+ const evStatus = criterionStatusToEventStatus(criterionStatus);
1102
1148
  const claimEvents: AnyObj[] = [];
1103
1149
  if (evStatus) {
1104
- const evt: AnyObj = { id: `evt:${claimId}`, claimId, status: evStatus, actor: "flow-agents/workflow-sidecar", method: "validation", evidenceIds: [], createdAt: ts, verifiedAt: ts };
1150
+ const evt: AnyObj = { id: `evt:${claimId}`, claimId, status: evStatus, actor: "flow-agents/workflow-sidecar", method: "validation", evidenceIds: criterionEvidenceIds, createdAt: criterionClaimTimestamp, verifiedAt: criterionClaimTimestamp };
1105
1151
  events.push(evt);
1106
1152
  claimEvents.push(evt);
1107
1153
  }
1108
1154
 
1109
- // P-d: declared-only when active flow/step present (shadow retired); no-flow path unchanged.
1110
- const declared = matchExpectsEntry("acceptance");
1111
- if (declared) {
1112
- // Declared kit-typed claim only — no legacy shadow (ADR 0016 P-d).
1113
- const declaredPolicy = ensurePolicy(declared.claimType, "high", []);
1114
- const declaredClaimObj: AnyObj = { id: claimId, subjectType: declared.subjectType, subjectId, facet: "flow-agents.workflow", claimType: declared.claimType, fieldOrBehavior, value: criterion.status, createdAt: ts, updatedAt: ts, impactLevel: "high", verificationPolicyId: declaredPolicy.id, metadata: { origin: "acceptance", criterion: { id: criterion.id, description: criterion.description ?? criterion.id, status: criterion.status, evidence_refs: Array.isArray(criterion.evidence_refs) ? criterion.evidence_refs : [], ...(criterionIdentityVersion === 2 ? { identity_version: 2 } : {}), ...(criterionVerifiedAt ? { verified_at: criterionVerifiedAt } : {}) }, ...(workflowSubjectRef ? { workflow_subject_ref: workflowSubjectRef } : {}) } };
1115
- const { status: declaredStatus } = deriveClaimStatus({ claim: declaredClaimObj as Record<string, unknown>, evidence: [], events: claimEvents as Record<string, unknown>[], policies: [declaredPolicy] as Record<string, unknown>[] });
1116
- claims.push({ ...declaredClaimObj, status: declaredStatus });
1117
- } else {
1118
- // No active flow step — only the workflow.* primary claim (legitimate no-flow fallback path).
1119
- const claimObj: AnyObj = { id: claimId, subjectType: "workflow-acceptance-criterion", subjectId, facet: "flow-agents.workflow", claimType: legacyClaimType, fieldOrBehavior, value: criterion.status, createdAt: ts, updatedAt: ts, impactLevel: "high", verificationPolicyId: policy.id, metadata: { origin: "acceptance", criterion: { id: criterion.id, description: criterion.description ?? criterion.id, status: criterion.status, evidence_refs: Array.isArray(criterion.evidence_refs) ? criterion.evidence_refs : [] } } };
1120
- const { status: derivedStatus } = deriveClaimStatus({ claim: claimObj as Record<string, unknown>, evidence: [], events: claimEvents as Record<string, unknown>[], policies: [policy] as Record<string, unknown>[] });
1121
- claims.push({ ...claimObj, status: derivedStatus });
1122
- }
1155
+ // Acceptance criteria are durable Work Item facts. Their identity must not change when a
1156
+ // bundle is rebuilt at a different active gate. Missing or invalid status is conservatively
1157
+ // projected as pending so direct artifact edits can never manufacture a pass or invalidate
1158
+ // the Hachure claim shape.
1159
+ const claimObj: AnyObj = { id: claimId, subjectType: "flow-step", subjectId, facet: "flow-agents.workflow", claimType, fieldOrBehavior, value: criterionStatus, createdAt: criterionClaimTimestamp, updatedAt: criterionClaimTimestamp, impactLevel: "high", verificationPolicyId: policy.id, metadata: { origin: "acceptance", criterion: { id: criterion.id, description: criterion.description ?? criterion.id, status: criterionStatus, evidence_refs: criterionEvidenceRefs, ...(observedCommands.length > 0 ? { observed_commands: observedCommands } : {}), ...(criterionIdentityVersion === 2 ? { identity_version: 2 } : {}), ...(criterionVerifiedAt ? { verified_at: criterionVerifiedAt } : {}) }, ...(workflowSubjectRef ? { workflow_subject_ref: workflowSubjectRef } : {}) } };
1160
+ const criterionEvidence = evidenceItems.filter((evidence) => evidence.claimId === claimId);
1161
+ const { status: derivedStatus } = deriveClaimStatus({ claim: claimObj as Record<string, unknown>, evidence: criterionEvidence as Record<string, unknown>[], events: claimEvents as Record<string, unknown>[], policies: [policy] as Record<string, unknown>[] });
1162
+ claims.push({ ...claimObj, status: derivedStatus });
1123
1163
  }
1124
1164
 
1125
1165
  // Critique entries → claims + events
@@ -2443,28 +2483,40 @@ function hasNonEmptyString(value: unknown): boolean {
2443
2483
  function hasPositiveInteger(value: unknown): boolean {
2444
2484
  return Number.isInteger(value) && Number(value) >= 1;
2445
2485
  }
2446
- export function validateEvidenceRef(ref: AnyObj, label: string): AnyObj {
2447
- if (!["source", "command", "artifact", "provider", "external"].includes(ref.kind)) die(`${label} entry kind must be one of: source, command, artifact, provider, external`);
2448
- for (const key of Object.keys(ref)) if (!["kind", "url", "file", "line_start", "line_end", "excerpt", "summary"].includes(key)) die(`${label} entries contain unsupported field: ${key}`);
2486
+ export function validateEvidenceRef(ref: AnyObj, label: string, projectRoot = process.cwd()): AnyObj {
2487
+ if (!(EVIDENCE_REF_KINDS as readonly unknown[]).includes(ref.kind)) die(`${label} entry kind must be one of: ${EVIDENCE_REF_KINDS.join(", ")}`);
2488
+ for (const key of Object.keys(ref)) if (!Object.hasOwn(EVIDENCE_REF_FIELD_SCHEMAS, key)) die(`${label} entries contain unsupported field: ${key}`);
2449
2489
  if (ref.url !== undefined && !hasNonEmptyString(ref.url)) die(`${label} entry url must be a non-empty string`);
2450
2490
  if (ref.file !== undefined && !hasNonEmptyString(ref.file)) die(`${label} entry file must be a non-empty string`);
2451
2491
  if (ref.excerpt !== undefined && !hasNonEmptyString(ref.excerpt)) die(`${label} entry excerpt must be a non-empty string`);
2452
2492
  if (ref.summary !== undefined && !hasNonEmptyString(ref.summary)) die(`${label} entry summary must be a non-empty string`);
2453
2493
  if (ref.line_start !== undefined && !hasPositiveInteger(ref.line_start)) die(`${label} entry line_start must be a positive integer`);
2454
2494
  if (ref.line_end !== undefined && !hasPositiveInteger(ref.line_end)) die(`${label} entry line_end must be a positive integer`);
2455
- if (ref.kind === "source" && (!hasNonEmptyString(ref.file) || !hasPositiveInteger(ref.line_start) || !hasPositiveInteger(ref.line_end) || !hasNonEmptyString(ref.excerpt))) die(`${label} source refs require file, line_start, line_end, and excerpt`);
2456
- if (ref.kind === "artifact" && (!hasNonEmptyString(ref.file) && !hasNonEmptyString(ref.url))) die(`${label} artifact refs require file or url`);
2457
- if (ref.kind === "artifact" && (!hasNonEmptyString(ref.summary) && !hasNonEmptyString(ref.excerpt))) die(`${label} artifact refs require summary or excerpt`);
2458
- if (ref.kind === "command" && (!hasNonEmptyString(ref.summary) && !hasNonEmptyString(ref.excerpt) && !hasNonEmptyString(ref.url))) die(`${label} command refs require summary, excerpt, or url`);
2459
- if ((ref.kind === "provider" || ref.kind === "external") && !hasNonEmptyString(ref.url)) die(`${label} ${ref.kind} refs require url`);
2495
+ rejectNarrativeReference(projectRoot, ref.file, `${label} entry file`);
2496
+ rejectNarrativeReference(projectRoot, ref.url, `${label} entry url`);
2497
+ rejectNarrativeReference(projectRoot, ref.excerpt, `${label} entry excerpt`);
2498
+ const rules = EVIDENCE_REF_RULES[ref.kind as keyof typeof EVIDENCE_REF_RULES];
2499
+ for (const rule of rules) {
2500
+ const present = (field: string): boolean => field === "line_start" || field === "line_end"
2501
+ ? hasPositiveInteger(ref[field])
2502
+ : hasNonEmptyString(ref[field]);
2503
+ const valid = rule.mode === "all" ? rule.fields.every(present) : rule.fields.some(present);
2504
+ if (!valid) {
2505
+ const separator = rule.fields.length > 2 ? ", " : " ";
2506
+ const joined = rule.fields.length > 1
2507
+ ? `${rule.fields.slice(0, -1).join(separator)}${rule.fields.length > 2 ? "," : ""} or ${rule.fields.at(-1)}`
2508
+ : rule.fields[0];
2509
+ die(`${label} ${ref.kind} refs require ${rule.mode === "all" && rule.fields.length > 1 ? joined.replace(/ or ([^,]+)$/, " and $1") : joined}`);
2510
+ }
2511
+ }
2460
2512
  return ref;
2461
2513
  }
2462
- export function normalizeEvidenceRefs(raw: unknown, label: string): AnyObj[] {
2514
+ export function normalizeEvidenceRefs(raw: unknown, label: string, projectRoot = process.cwd()): AnyObj[] {
2463
2515
  if (!Array.isArray(raw)) die(`${label} must be an array`);
2464
2516
  return raw.map((ref) => {
2465
2517
  if (typeof ref === "string") die(`${label} entries must be structured evidence reference objects; legacy string refs are not supported`);
2466
2518
  if (!ref || typeof ref !== "object" || Array.isArray(ref)) die(`${label} entries must be objects`);
2467
- return validateEvidenceRef({ ...ref as AnyObj }, label);
2519
+ return validateEvidenceRef({ ...ref as AnyObj }, label, projectRoot);
2468
2520
  });
2469
2521
  }
2470
2522
 
@@ -2478,14 +2530,155 @@ function canonicalProjectRootForSession(dir: string): string {
2478
2530
  return projectRoot;
2479
2531
  }
2480
2532
 
2533
+ // #619: the narrative isolation guard must run for EVERY session layout (canonical,
2534
+ // tmp, or legacy .flow-agents/) without imposing the canonical-session requirement that
2535
+ // canonicalProjectRootForSession enforces. It only needs a best-effort project root to
2536
+ // resolve relative evidence paths; the namespace regex and content-shape checks are
2537
+ // location-independent. It mirrors canonicalProjectRootForSession's path computation for a
2538
+ // canonical .kontourai/flow-agents/<slug> session (project root is three levels up), handles
2539
+ // the legacy .flow-agents/<slug> layout (two levels up), and never dies on any layout.
2540
+ function narrativeGuardRoot(dir: string): string {
2541
+ const artifactRoot = path.dirname(dir);
2542
+ const parentRoot = path.dirname(artifactRoot);
2543
+ if (path.basename(artifactRoot) === "flow-agents" && path.basename(parentRoot) === ".kontourai") {
2544
+ return path.dirname(parentRoot);
2545
+ }
2546
+ if (path.basename(artifactRoot) === ".flow-agents") {
2547
+ return parentRoot;
2548
+ }
2549
+ // Unknown/tmp layout: best-effort. Relative-path resolution may be imperfect, but the
2550
+ // namespace regex and content-shape checks are location-independent, so the isolation
2551
+ // guarantee holds regardless.
2552
+ return artifactRoot;
2553
+ }
2554
+
2481
2555
  function pathIsWithinRoot(candidate: string, root: string): boolean {
2482
2556
  const relative = path.relative(root, candidate);
2483
2557
  return relative === "" || (!relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative));
2484
2558
  }
2485
2559
 
2560
+ export const NARRATIVE_TRUST_ISOLATION_DIAGNOSTIC = "narrative trust isolation (#619): narrative namespace artifacts cannot be used as workflow evidence";
2561
+
2562
+ function decodeNarrativeReference(value: string): string {
2563
+ try { return decodeURIComponent(value); } catch { return value; }
2564
+ }
2565
+
2566
+ function realpathWithExistingPrefix(candidate: string): string {
2567
+ let cursor = path.resolve(candidate);
2568
+ const suffix: string[] = [];
2569
+ while (!fs.existsSync(cursor)) {
2570
+ const parent = path.dirname(cursor);
2571
+ if (parent === cursor) break;
2572
+ suffix.unshift(path.basename(cursor));
2573
+ cursor = parent;
2574
+ }
2575
+ let canonical = cursor;
2576
+ try { canonical = (fs.realpathSync.native ?? fs.realpathSync)(cursor); } catch { /* resolved lexical prefix is the fallback */ }
2577
+ return path.resolve(canonical, ...suffix);
2578
+ }
2579
+
2580
+ export function isNarrativeNamespacePath(projectRoot: string, resolvedPath: string): boolean {
2581
+ const decoded = decodeNarrativeReference(resolvedPath);
2582
+ const normalized = decoded.replaceAll("\\", "/").toLowerCase();
2583
+ if (/(?:^|[^a-z0-9._-])\.kontourai(?:\/[^/\s"'`]+)*\/narrative(?:$|[/\s"'`])/.test(normalized)) return true;
2584
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(decoded) && !decoded.toLowerCase().startsWith("file://")) return false;
2585
+ let local = decoded;
2586
+ if (decoded.toLowerCase().startsWith("file://")) {
2587
+ try { local = fileURLToPath(decoded); } catch { return false; }
2588
+ }
2589
+ const canonicalRoot = realpathWithExistingPrefix(path.resolve(projectRoot, NARRATIVE_NAMESPACE_ROOT)).toLowerCase();
2590
+ const candidate = realpathWithExistingPrefix(path.resolve(projectRoot, local)).toLowerCase();
2591
+ return pathIsWithinRoot(candidate, canonicalRoot);
2592
+ }
2593
+
2594
+ export function isNarrativeArtifactContent(fileBytes: Buffer | string): boolean {
2595
+ const text = Buffer.isBuffer(fileBytes) ? fileBytes.toString("utf8") : fileBytes;
2596
+ try {
2597
+ const parsed: unknown = JSON.parse(text);
2598
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return false;
2599
+ const value = parsed as AnyObj;
2600
+ if (value.schema_version === "grounded-execution-narrative/v1") return true;
2601
+ if (value.schema_version === "grounded-runtime-projection/v1") return true;
2602
+ if (value.schema_version === "1.0"
2603
+ && hasNonEmptyString(value.narrative_id)
2604
+ && hasNonEmptyString(value.captured_at)
2605
+ && value.compiler && typeof value.compiler === "object"
2606
+ && value.capture_completeness && typeof value.capture_completeness === "object"
2607
+ && Array.isArray(value.sources)) return true;
2608
+ return false;
2609
+ } catch {
2610
+ return text.includes("flow-agents-narrative-composer")
2611
+ && text.includes("# Grounded Execution Narrative")
2612
+ && text.includes("## Authority provenance");
2613
+ }
2614
+ }
2615
+
2616
+ function localPathTokens(value: string): string[] {
2617
+ const decoded = decodeNarrativeReference(value);
2618
+ const tokens = decoded.match(/(?:file:\/\/[^\s"'`]+)|(?:[A-Za-z]:[\\/][^\s"'`]+)|(?:\.{0,2}[\\/][^\s"'`]+)|(?:[^\s"'`]+[\\/][^\s"'`]+)/g) ?? [];
2619
+ return tokens.map((token) => token.replace(/^[([{<]+/, "").replace(/[\])}>;,]+$/, ""));
2620
+ }
2621
+
2622
+ function referencesComposedNarrativePath(projectRoot: string, value: string): boolean {
2623
+ const decoded = decodeNarrativeReference(value);
2624
+ const folded = decoded.replaceAll("\\", "/").toLowerCase();
2625
+ if (folded.includes(".kontourai/narrative")) return true;
2626
+
2627
+ // Static shell inspection cannot decide arbitrary runtime variable composition such as
2628
+ // `base=.kontourai; test -f "$base/narrative/..."`. The compensating invariant is that a
2629
+ // command check persists only its command plus exit/observed-output digest; it never reads file
2630
+ // bytes into trust.bundle. Every channel that DOES materialize a file is independently guarded
2631
+ // by canonical path and narrative content shape. Keep that invariant covered end-to-end.
2632
+ const cdPattern = /(?:^|[;&|]\s*)cd\s+(?:"([^"]+)"|'([^']+)'|([^\s;&|]+))\s*&&([\s\S]*)/gi;
2633
+ for (const match of decoded.matchAll(cdPattern)) {
2634
+ const cdTarget = match[1] ?? match[2] ?? match[3] ?? "";
2635
+ const remainder = (match[4] ?? "").replaceAll("\\", "/").toLowerCase();
2636
+ if (!/(?:^|[^a-z0-9._-])narrative\//.test(remainder)) continue;
2637
+ const canonicalTarget = realpathWithExistingPrefix(path.resolve(projectRoot, decodeNarrativeReference(cdTarget)))
2638
+ .replaceAll("\\", "/")
2639
+ .toLowerCase();
2640
+ const canonicalKontourai = realpathWithExistingPrefix(path.resolve(projectRoot, ".kontourai"))
2641
+ .replaceAll("\\", "/")
2642
+ .toLowerCase();
2643
+ if (canonicalTarget === canonicalKontourai
2644
+ || canonicalTarget.startsWith(`${canonicalKontourai}/`)
2645
+ || /(?:^|\/)\.kontourai(?:\/|$)/.test(canonicalTarget)) return true;
2646
+ }
2647
+ return false;
2648
+ }
2649
+
2650
+ function narrativeCandidateFile(projectRoot: string, raw: string): string | null {
2651
+ let decoded = decodeNarrativeReference(raw);
2652
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(decoded)) {
2653
+ if (!decoded.toLowerCase().startsWith("file://")) return null;
2654
+ try { decoded = fileURLToPath(decoded); } catch { return null; }
2655
+ }
2656
+ const candidate = path.isAbsolute(decoded) ? decoded : path.resolve(projectRoot, decoded);
2657
+ try {
2658
+ const canonical = (fs.realpathSync.native ?? fs.realpathSync)(candidate);
2659
+ const stat = fs.statSync(canonical);
2660
+ return stat.isFile() ? canonical : null;
2661
+ } catch { return null; }
2662
+ }
2663
+
2664
+ function rejectNarrativeReference(projectRoot: string, value: unknown, label: string): void {
2665
+ if (typeof value !== "string") return;
2666
+ const decoded = decodeNarrativeReference(value);
2667
+ if (referencesComposedNarrativePath(projectRoot, decoded)) die(`${label}: ${NARRATIVE_TRUST_ISOLATION_DIAGNOSTIC}`);
2668
+ const candidates = [decoded, ...localPathTokens(decoded)];
2669
+ for (const candidate of new Set(candidates)) {
2670
+ if (isNarrativeNamespacePath(projectRoot, candidate)) die(`${label}: ${NARRATIVE_TRUST_ISOLATION_DIAGNOSTIC}`);
2671
+ const file = narrativeCandidateFile(projectRoot, candidate);
2672
+ if (file && isNarrativeArtifactContent(fs.readFileSync(file))) {
2673
+ die(`${label}: ${NARRATIVE_TRUST_ISOLATION_DIAGNOSTIC}`);
2674
+ }
2675
+ }
2676
+ }
2677
+
2486
2678
  function validateLocalEvidenceFile(projectRoot: string, raw: string, label: string): string {
2487
2679
  const candidate = path.resolve(projectRoot, raw);
2488
2680
  if (!pathIsWithinRoot(candidate, projectRoot)) die(`${label} must remain inside the canonical project root`);
2681
+ rejectNarrativeReference(projectRoot, raw, label);
2489
2682
  let stat: fs.Stats;
2490
2683
  try { stat = fs.lstatSync(candidate); } catch { die(`${label} does not exist`); }
2491
2684
  if (stat.isSymbolicLink() || !stat.isFile()) die(`${label} must be a non-symlink regular file`);
@@ -2500,6 +2693,13 @@ function globMatches(pattern: string, relative: string): boolean {
2500
2693
 
2501
2694
  type GateProducer = { id: string; artifactPatterns: string[]; selfProducedTrustSlices: string[] };
2502
2695
 
2696
+ export function rejectOperationBoundExpectation(expectationId: string, operation: string): never {
2697
+ const completion = operation === NARRATIVE_PROMOTE_OPERATION
2698
+ ? "authenticated external narrative provider completion"
2699
+ : "authenticated external ChangeProvider completion";
2700
+ die(`record-gate-claim cannot satisfy operation-bound expectation ${expectationId}; ${operation} requires ${completion}`);
2701
+ }
2702
+
2503
2703
  function expectedGateProducer(flowId: string, stepId: string, expectationId: string): GateProducer {
2504
2704
  const manifest = loadJson(path.join(flowAgentsPackageRoot(), "kits", "builder", "kit.json"));
2505
2705
  const actions = Array.isArray(manifest.flow_step_actions) ? manifest.flow_step_actions as AnyObj[] : [];
@@ -2510,7 +2710,7 @@ function expectedGateProducer(flowId: string, stepId: string, expectationId: str
2510
2710
  : undefined;
2511
2711
  if (binding?.interface === "operation") {
2512
2712
  const operation = typeof binding.operation === "string" ? binding.operation : "the declared external operation";
2513
- die(`record-gate-claim cannot satisfy operation-bound expectation ${expectationId}; ${operation} requires authenticated external ChangeProvider completion`);
2713
+ rejectOperationBoundExpectation(expectationId, operation);
2514
2714
  }
2515
2715
  const skills = Array.isArray(action.skills) ? action.skills.filter((value: unknown): value is string => typeof value === "string") : [];
2516
2716
  const roles = Array.isArray(manifest.skill_roles) ? manifest.skill_roles as AnyObj[] : [];
@@ -2739,6 +2939,102 @@ async function normalizeObservedCommands(commands: string[], projectRoot: string
2739
2939
  return commands.map((command) => byCommand.get(command)!);
2740
2940
  }
2741
2941
 
2942
+ // #634: the canonical writer's own execution is a first-class observation. On hosts whose
2943
+ // PostToolUse capture never surfaces exit codes, every hook-captured entry is "ambiguous",
2944
+ // the independent capture can never confirm a pass, and the verify gate dead-ends. The writer
2945
+ // just ran the command itself — a real process exit, not an inference — so it appends that
2946
+ // observation to the SAME hash-chained command-log, under the SAME lock protocol the capture
2947
+ // hook uses, visibly attributed via source: "canonical-writer-execution". Precedence stays
2948
+ // honest in the fold (reduceCaptureLogByCommand): any observed fail beats any pass, so a
2949
+ // writer pass can lift ambiguity but can never bury a hook-observed failure. The append is
2950
+ // fail-open with a stderr note: losing the supplementary observation must never fail the
2951
+ // gate-claim write itself. Decision record: docs/decisions/writer-observed-execution.md.
2952
+ export const WRITER_OBSERVATION_SOURCE = "canonical-writer-execution";
2953
+ const WRITER_LOCK_RETRY_MS = 5;
2954
+ const WRITER_LOCK_MAX_TRIES = 200;
2955
+ const WRITER_LOCK_STALE_MS = 10000;
2956
+
2957
+ function loadCommandLogChain(): { CHAIN_GENESIS: string; computeChainHash: (prevHash: string, record: AnyObj) => string } {
2958
+ const _req = createRequire(import.meta.url);
2959
+ const chainPath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../../scripts/lib/command-log-chain.js");
2960
+ return _req(chainPath) as { CHAIN_GENESIS: string; computeChainHash: (prevHash: string, record: AnyObj) => string };
2961
+ }
2962
+
2963
+ function writerSleepSync(ms: number): void {
2964
+ try { Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms); }
2965
+ catch { /* SharedArrayBuffer/Atomics unavailable — skip the backoff */ }
2966
+ }
2967
+
2968
+ function writerAcquireLock(lockFile: string): number | null {
2969
+ for (let i = 0; i < WRITER_LOCK_MAX_TRIES; i++) {
2970
+ try {
2971
+ const fd = fs.openSync(lockFile, "wx");
2972
+ try { fs.writeSync(fd, String(process.pid)); } catch { /* pid is advisory only */ }
2973
+ return fd;
2974
+ } catch (err) {
2975
+ if (!err || (err as NodeJS.ErrnoException).code !== "EEXIST") return null;
2976
+ try {
2977
+ const st = fs.statSync(lockFile);
2978
+ if (Date.now() - st.mtimeMs > WRITER_LOCK_STALE_MS) { fs.unlinkSync(lockFile); continue; }
2979
+ } catch { continue; }
2980
+ writerSleepSync(WRITER_LOCK_RETRY_MS);
2981
+ }
2982
+ }
2983
+ return null;
2984
+ }
2985
+
2986
+ function writerReadLastChainState(logFile: string, genesis: string): { seq: number; hash: string } {
2987
+ let raw = "";
2988
+ try { raw = fs.readFileSync(logFile, "utf8"); } catch { return { seq: -1, hash: genesis }; }
2989
+ const lines = raw.split("\n").filter((l) => l.trim());
2990
+ for (let i = lines.length - 1; i >= 0; i--) {
2991
+ let entry: AnyObj;
2992
+ try { entry = JSON.parse(lines[i]!); } catch { continue; }
2993
+ if (entry && entry._chain && typeof entry._chain.hash === "string" && typeof entry._chain.seq === "number") {
2994
+ return { seq: entry._chain.seq, hash: entry._chain.hash };
2995
+ }
2996
+ }
2997
+ return { seq: -1, hash: genesis };
2998
+ }
2999
+
3000
+ export function appendWriterObservedCommands(dir: string, observed: ObservedCommand[], timestamp: string): void {
3001
+ if (observed.length === 0) return;
3002
+ try {
3003
+ const chain = loadCommandLogChain();
3004
+ const logFile = path.join(dir, "command-log.jsonl");
3005
+ const lockFile = `${logFile}.lock`;
3006
+ const fd = writerAcquireLock(lockFile);
3007
+ try {
3008
+ let { seq, hash: prevHash } = writerReadLastChainState(logFile, chain.CHAIN_GENESIS);
3009
+ const lines: string[] = [];
3010
+ for (const entry of observed) {
3011
+ const record: AnyObj = {
3012
+ command: entry.command,
3013
+ observedResult: entry.exit_code === 0 ? "pass" : "fail",
3014
+ exitCode: entry.exit_code,
3015
+ capturedAt: timestamp,
3016
+ source: WRITER_OBSERVATION_SOURCE,
3017
+ writer: {
3018
+ output_sha256: entry.output_sha256,
3019
+ ...(Number.isSafeInteger(entry.test_count) ? { test_count: entry.test_count } : {}),
3020
+ ...(entry.execution_proof ? { execution_proof: entry.execution_proof } : {}),
3021
+ },
3022
+ };
3023
+ seq += 1;
3024
+ const hashValue = chain.computeChainHash(prevHash, record);
3025
+ record._chain = { seq, prevHash, hash: hashValue };
3026
+ prevHash = hashValue;
3027
+ lines.push(JSON.stringify(record));
3028
+ }
3029
+ fs.appendFileSync(logFile, `${lines.join("\n")}\n`);
3030
+ } finally {
3031
+ if (fd !== null) { try { fs.closeSync(fd); } catch { /* closed */ } try { fs.unlinkSync(lockFile); } catch { /* removed */ } }
3032
+ }
3033
+ } catch (error) {
3034
+ process.stderr.write(`[record-gate-claim] writer observation append failed (fail-open, capture unaffected): ${error instanceof Error ? error.message : String(error)}\n`);
3035
+ }
3036
+ }
3037
+
2742
3038
  function requireObservedCommandRefs(refs: AnyObj[], observedCommands: ReadonlySet<string>, label: string, requireAll = false): void {
2743
3039
  const commandRefs = refs.filter((ref) => ref.kind === "command");
2744
3040
  if (commandRefs.length === 0) die(`${label} requires a command evidence ref matching a successful observed command`);
@@ -2751,7 +3047,7 @@ function requireObservedCommandRefs(refs: AnyObj[], observedCommands: ReadonlySe
2751
3047
  }
2752
3048
  }
2753
3049
 
2754
- function completePassingCriteria(existing: AnyObj[], raw: string[], observedCommands: ReadonlySet<string>, verifiedAt: string): AnyObj[] {
3050
+ function completePassingCriteria(existing: AnyObj[], raw: string[], observedCommands: readonly ObservedCommand[], verifiedAt: string, projectRoot: string): AnyObj[] {
2755
3051
  if (raw.length === 0) die("record-gate-claim requires --criterion-json for a passing tests-evidence claim");
2756
3052
  const incoming = raw.map((value) => parseJson(value, "--criterion-json"));
2757
3053
  const expectedById = new Map<string, AnyObj>();
@@ -2764,20 +3060,23 @@ function completePassingCriteria(existing: AnyObj[], raw: string[], observedComm
2764
3060
  if (new Set(ids).size !== ids.length || ids.length !== expectedIds.length || ids.some((id) => !expectedIds.includes(id))) {
2765
3061
  die(`--criterion-json must cover every declared acceptance criterion exactly once (expected: ${expectedIds.join(", ") || "none"}; received: ${ids.join(", ") || "none"})`);
2766
3062
  }
3063
+ const observedCommandNames = new Set(observedCommands.map((observation) => observation.command));
2767
3064
  return incoming.map((criterion, index) => {
2768
3065
  if (Object.keys(criterion).some((key) => !["id", "status", "evidence_refs"].includes(key))) die(`criterion ${ids[index]} may update only id, status, and evidence_refs`);
2769
3066
  if (criterion.status !== "pass") die(`criterion ${ids[index]} must have status pass for a passing tests-evidence claim`);
2770
- const refs = normalizeEvidenceRefs(criterion.evidence_refs, `criterion ${ids[index]} evidence_refs`);
3067
+ const refs = normalizeEvidenceRefs(criterion.evidence_refs, `criterion ${ids[index]} evidence_refs`, projectRoot);
2771
3068
  if (refs.length === 0) die(`criterion ${ids[index]} requires reviewable evidence_refs`);
2772
- requireObservedCommandRefs(refs, observedCommands, `criterion ${ids[index]}`);
2773
- return { ...expectedById.get(ids[index])!, status: "pass", evidence_refs: refs, identity_version: 2, verified_at: verifiedAt };
3069
+ requireObservedCommandRefs(refs, observedCommandNames, `criterion ${ids[index]}`);
3070
+ const referencedCommands = new Set(refs.filter((ref) => ref.kind === "command").map(commandFromEvidenceRef));
3071
+ const criterionObservedCommands = observedCommands.filter((observation) => referencedCommands.has(observation.command));
3072
+ return markCanonicallyObservedCriterion({ ...expectedById.get(ids[index])!, status: "pass", evidence_refs: refs, identity_version: 2, verified_at: verifiedAt, _observed_commands: criterionObservedCommands });
2774
3073
  });
2775
3074
  }
2776
3075
 
2777
3076
  const critiqueStatuses = new Set<string>(WORKFLOW_CRITIQUE_STATUSES);
2778
3077
  const safeCritiqueId = /^[a-z][a-z0-9_-]*$/;
2779
3078
 
2780
- function normalizeCritiqueLanes(raw: string[]): AnyObj[] {
3079
+ function normalizeCritiqueLanes(raw: string[], projectRoot: string): AnyObj[] {
2781
3080
  if (raw.length === 0) die("record-critique requires at least one --lane-json");
2782
3081
  const lanes = raw.map((value, index) => {
2783
3082
  const lane = parseJson(value, "--lane-json");
@@ -2786,7 +3085,7 @@ function normalizeCritiqueLanes(raw: string[]): AnyObj[] {
2786
3085
  if (!safeCritiqueId.test(String(lane.id ?? ""))) die(`--lane-json ${index} id must be a unique safe identifier`);
2787
3086
  if (!critiqueStatuses.has(String(lane.status ?? ""))) die(`--lane-json ${index} status must be one of: pass, fail, not_verified`);
2788
3087
  if (!hasNonEmptyString(lane.summary)) die(`--lane-json ${index} summary must be non-empty`);
2789
- const evidenceRefs = normalizeEvidenceRefs(lane.evidence_refs, `--lane-json ${index} evidence_refs`);
3088
+ const evidenceRefs = normalizeEvidenceRefs(lane.evidence_refs, `--lane-json ${index} evidence_refs`, projectRoot);
2790
3089
  if (evidenceRefs.length === 0) die(`--lane-json ${index} requires structured reviewable evidence_refs`);
2791
3090
  return { id: lane.id, status: lane.status, summary: lane.summary, evidence_refs: evidenceRefs };
2792
3091
  });
@@ -2804,6 +3103,13 @@ function reviewTargetArtifacts(dir: string, rawPaths: string[], label: string):
2804
3103
  }
2805
3104
 
2806
3105
  function reviewTargetArtifactsMatch(dir: string, reviewTarget: unknown): boolean {
3106
+ if (reviewTarget && typeof reviewTarget === "object" && !Array.isArray(reviewTarget)) {
3107
+ const projectRoot = narrativeGuardRoot(dir);
3108
+ const artifacts = (reviewTarget as AnyObj).artifacts;
3109
+ if (Array.isArray(artifacts)) {
3110
+ artifacts.forEach((artifact, index) => rejectNarrativeReference(projectRoot, artifact?.file, `critique review_target artifact ${index}`));
3111
+ }
3112
+ }
2807
3113
  try {
2808
3114
  if (!reviewTarget || typeof reviewTarget !== "object" || Array.isArray(reviewTarget)) return false;
2809
3115
  const artifacts = (reviewTarget as AnyObj).artifacts;
@@ -2869,7 +3175,7 @@ function critiqueIsCleanAndCurrent(dir: string, critique: AnyObj): boolean {
2869
3175
  // BEFORE normalizeCheck runs — see applyGateClaimStamp). This does not weaken new-mint
2870
3176
  // enforcement: a NOVEL gate-claim-* id (not already present at all) is still rejected exactly as
2871
3177
  // before, and superseding a STAMPED existing id is now rejected too.
2872
- export function normalizeCheck(raw: AnyObj, allowGateClaimPrefix = false, existingCheckStampById?: ReadonlyMap<string, boolean>): AnyObj {
3178
+ export function normalizeCheck(raw: AnyObj, allowGateClaimPrefix = false, existingCheckStampById?: ReadonlyMap<string, boolean>, projectRoot = process.cwd()): AnyObj {
2873
3179
  const check = { ...raw };
2874
3180
  if (!check.id || !check.kind || !check.status || !check.summary) die("check requires id, kind, status, and summary");
2875
3181
  if (!allowGateClaimPrefix && typeof check.id === "string" && check.id.startsWith("gate-claim-")) {
@@ -2879,13 +3185,17 @@ export function normalizeCheck(raw: AnyObj, allowGateClaimPrefix = false, existi
2879
3185
  }
2880
3186
  if (!checkKinds.has(check.kind)) die("kind must be one of: build, types, lint, test, command, security, diff, browser, runtime, policy, external");
2881
3187
  if (!checkStatuses.has(check.status)) die("status must be one of: pass, fail, not_verified, skip");
3188
+ rejectNarrativeReference(projectRoot, check.command, `check ${String(check.id)} command`);
2882
3189
  validateRunnableCheckCommand(check, `check ${String(check.id)}`);
2883
- if (Array.isArray(check.standard_refs)) for (const ref of check.standard_refs) if (!["junit", "sarif", "coverage", "veritas"].includes(ref.standard)) die("standard must be one of");
2884
- if (check.artifact_refs) check.artifact_refs = normalizeEvidenceRefs(check.artifact_refs, "artifact_refs");
2885
- if (check.surface_trust_refs) check.surface_trust_refs = normalizeSurfaceRefs(check.surface_trust_refs);
3190
+ if (Array.isArray(check.standard_refs)) for (const ref of check.standard_refs) {
3191
+ if (!["junit", "sarif", "coverage", "veritas"].includes(ref.standard)) die("standard must be one of");
3192
+ rejectNarrativeReference(projectRoot, ref.ref, `check ${String(check.id)} standard_ref`);
3193
+ }
3194
+ if (check.artifact_refs) check.artifact_refs = normalizeEvidenceRefs(check.artifact_refs, "artifact_refs", projectRoot);
3195
+ if (check.surface_trust_refs) check.surface_trust_refs = normalizeSurfaceRefs(check.surface_trust_refs, projectRoot);
2886
3196
  return check;
2887
3197
  }
2888
- function normalizeSurfaceRefs(refs: any): AnyObj[] {
3198
+ function normalizeSurfaceRefs(refs: any, projectRoot: string): AnyObj[] {
2889
3199
  if (!Array.isArray(refs)) die("surface_trust_refs must be an array");
2890
3200
  // Use the cached @kontourai/surface module for advisory inline validation of referenced
2891
3201
  // trust.bundle files. Fail-open when surface is not yet loaded (surface loads on first
@@ -2895,6 +3205,7 @@ function normalizeSurfaceRefs(refs: any): AnyObj[] {
2895
3205
  const keys = JSON.stringify(ref).match(/"([^"]+)":/g) ?? [];
2896
3206
  for (const key of keys.map((k) => k.slice(1, -2))) if (key.toLowerCase().includes("veritas")) die(`unsupported field in Surface trust ref: ${key}`);
2897
3207
  const out = { ...ref };
3208
+ rejectNarrativeReference(projectRoot, out.artifact_ref, "surface trust artifact_ref");
2898
3209
  // trust.bundle is the canonical Hachure-aligned artifact kind; TrustReport/Trust Snapshot are legacy aliases
2899
3210
  if (!["trust.bundle", "TrustReport", "Trust Snapshot"].includes(out.artifact_kind)) die("artifact_kind must be one of: trust.bundle, TrustReport, Trust Snapshot");
2900
3211
  // When surface is loaded, validate the referenced trust artifact if it is a local file.
@@ -2973,23 +3284,25 @@ export function kitIdentityFromBundle(
2973
3284
  // 3. Genuinely unknown — never fallback to "builder".
2974
3285
  return { claimType: "unknown.trust.bundle", kitId: "unknown", subject: "unknown-kit", gateId: "unknown.trust.bundle" };
2975
3286
  }
2976
- function surfaceCheckFromArtifact(file: string, index: number): AnyObj {
2977
- const raw = JSON.parse(read(file));
3287
+ function surfaceCheckFromArtifact(file: string, index: number, projectRoot = process.cwd()): AnyObj {
3288
+ rejectNarrativeReference(projectRoot, file, `--surface-trust-json ${index}`);
3289
+ const resolvedFile = path.isAbsolute(file) ? file : path.resolve(projectRoot, decodeNarrativeReference(file));
3290
+ const raw = JSON.parse(read(resolvedFile));
2978
3291
  const lower = JSON.stringify(raw).toLowerCase();
2979
3292
  // Structurally read kit identity from the bundle — never hardcode "builder".
2980
- const { claimType: bundleClaimType, subject: bundleSubject, gateId: bundleGateId } = kitIdentityFromBundle(raw, file);
3293
+ const { claimType: bundleClaimType, subject: bundleSubject, gateId: bundleGateId } = kitIdentityFromBundle(raw, resolvedFile);
2981
3294
  let ref: AnyObj;
2982
3295
  if (lower.includes("provider") && lower.includes("absent")) {
2983
- ref = { artifact_kind: "trust.bundle", artifact_ref: file, gate_id: "provider.unavailable", claim_type: bundleClaimType, claim_status: "unknown", subject: bundleSubject, freshness: { status: "unknown", summary: "No trust provider is configured" }, authority: { producer: "unknown", summary: "No trust provider is configured" }, integrity: { status: "unknown", summary: "Unknown" }, status: "not_verified", summary: "No trust provider is configured" };
3296
+ ref = { artifact_kind: "trust.bundle", artifact_ref: resolvedFile, gate_id: "provider.unavailable", claim_type: bundleClaimType, claim_status: "unknown", subject: bundleSubject, freshness: { status: "unknown", summary: "No trust provider is configured" }, authority: { producer: "unknown", summary: "No trust provider is configured" }, integrity: { status: "unknown", summary: "Unknown" }, status: "not_verified", summary: "No trust provider is configured" };
2984
3297
  } else if (lower.includes("artifact") && lower.includes("absent")) {
2985
- ref = { artifact_kind: "trust.bundle", artifact_ref: file, gate_id: "artifact.unavailable", claim_type: bundleClaimType, claim_status: "unknown", subject: bundleSubject, freshness: { status: "unknown", summary: "Artifact not readable" }, authority: { producer: "unknown", summary: "Artifact not readable" }, integrity: { status: "unknown", summary: "Artifact not readable" }, status: "not_verified", summary: "artifact not readable" };
3298
+ ref = { artifact_kind: "trust.bundle", artifact_ref: resolvedFile, gate_id: "artifact.unavailable", claim_type: bundleClaimType, claim_status: "unknown", subject: bundleSubject, freshness: { status: "unknown", summary: "Artifact not readable" }, authority: { producer: "unknown", summary: "Artifact not readable" }, integrity: { status: "unknown", summary: "Artifact not readable" }, status: "not_verified", summary: "artifact not readable" };
2986
3299
  } else {
2987
3300
  const claimStatus = lower.includes("rejected") ? "rejected" : "accepted";
2988
3301
  const freshness = lower.includes("stale") ? "stale" : "fresh";
2989
3302
  const producer = lower.includes("missing-authority") ? "unknown" : "surface-local";
2990
3303
  const integrity = lower.includes("mismatch") ? "mismatch" : "matched";
2991
3304
  // Use trust.bundle as the canonical Hachure-aligned artifact_kind for all trust-backed evidence refs
2992
- ref = { artifact_kind: "trust.bundle", artifact_ref: file, gate_id: bundleGateId, claim_type: bundleClaimType, claim_status: claimStatus, subject: bundleSubject, freshness: { status: freshness, summary: freshness === "fresh" ? "fresh" : "not currently verifiable" }, authority: { producer, summary: producer === "unknown" ? "missing authority" : "Local Surface trust producer." }, integrity: { status: integrity, summary: integrity === "matched" ? "matched" : "integrity mismatch" } };
3305
+ ref = { artifact_kind: "trust.bundle", artifact_ref: resolvedFile, gate_id: bundleGateId, claim_type: bundleClaimType, claim_status: claimStatus, subject: bundleSubject, freshness: { status: freshness, summary: freshness === "fresh" ? "fresh" : "not currently verifiable" }, authority: { producer, summary: producer === "unknown" ? "missing authority" : "Local Surface trust producer." }, integrity: { status: integrity, summary: integrity === "matched" ? "matched" : "integrity mismatch" } };
2993
3306
  ref.status = deriveSurfaceStatus(ref);
2994
3307
  ref.summary = ref.status === "pass" ? "accepted" : ref.status === "not_verified" ? "not currently verifiable" : (claimStatus === "rejected" ? "rejected" : producer === "unknown" ? "missing authority" : "integrity mismatch");
2995
3308
  }
@@ -3008,9 +3321,10 @@ function surfaceCheckFromArtifact(file: string, index: number): AnyObj {
3008
3321
  * not the first thing an agent reaches for.
3009
3322
  */
3010
3323
  function validateAcceptanceEvidenceRefs(dir: string, p?: ReturnType<typeof parseArgs>): void {
3011
- if (p?.flags.has("skip-evidence-ref-runnability-guard")) {
3324
+ const projectRoot = narrativeGuardRoot(dir);
3325
+ const skipRunnabilityGuard = p?.flags.has("skip-evidence-ref-runnability-guard") ?? false;
3326
+ if (skipRunnabilityGuard) {
3012
3327
  process.stderr.write("[record-evidence] evidence-ref runnability guard skipped via --skip-evidence-ref-runnability-guard\n");
3013
- return;
3014
3328
  }
3015
3329
  const file = path.join(dir, "acceptance.json");
3016
3330
  if (!fs.existsSync(file)) return;
@@ -3019,7 +3333,8 @@ function validateAcceptanceEvidenceRefs(dir: string, p?: ReturnType<typeof parse
3019
3333
  const { isRunnableCommandText, isAmbiguousAbsenceCommand } = loadRunnableCommandHelper();
3020
3334
  data.criteria.forEach((criterion: AnyObj, index: number) => {
3021
3335
  if (criterion.evidence_refs === undefined) return;
3022
- const refs = normalizeEvidenceRefs(criterion.evidence_refs, `acceptance.criteria[${index}].evidence_refs`);
3336
+ const refs = normalizeEvidenceRefs(criterion.evidence_refs, `acceptance.criteria[${index}].evidence_refs`, projectRoot);
3337
+ if (skipRunnabilityGuard) return;
3023
3338
  // #412 (AC8): a kind:"command" ref's command source (excerpt, falling back to url when that
3024
3339
  // is where the command string lives) must be a literally runnable shell command, not prose
3025
3340
  // describing a manual verification step — reject at RECORD time instead of letting it reach
@@ -3089,8 +3404,17 @@ function requireStampedClaim(claim: AnyObj, dir: string): string {
3089
3404
  }
3090
3405
  return origin;
3091
3406
  }
3407
+
3408
+ function loadTrustBundleForTrustMachinery(dir: string): AnyObj {
3409
+ const bundleFile = path.join(dir, "trust.bundle");
3410
+ if (fs.existsSync(bundleFile) && isNarrativeArtifactContent(fs.readFileSync(bundleFile))) {
3411
+ die(`trust.bundle in ${dir}: ${NARRATIVE_TRUST_ISOLATION_DIAGNOSTIC}; restore a genuine trust.bundle and keep rendered narratives under ${NARRATIVE_NAMESPACE_ROOT}`);
3412
+ }
3413
+ return loadJson(bundleFile);
3414
+ }
3415
+
3092
3416
  function checksFromBundle(dir: string): AnyObj[] {
3093
- const bundle = loadJson(path.join(dir, "trust.bundle"));
3417
+ const bundle = loadTrustBundleForTrustMachinery(dir);
3094
3418
  const allClaims: AnyObj[] = Array.isArray(bundle.claims) ? bundle.claims : [];
3095
3419
  // Validate stamps on every claim up front — any unstamped claim anywhere in the bundle marks
3096
3420
  // it pre-supersession, regardless of whether it is check/acceptance/critique-typed.
@@ -3275,10 +3599,20 @@ function readBundleState(dir: string): { checks: AnyObj[]; criteria: AnyObj[]; c
3275
3599
  const acceptance = loadJson(path.join(dir, "acceptance.json"));
3276
3600
  const bundledCriteria = criteriaFromBundle(dir);
3277
3601
  const acceptedCriteria = Array.isArray(acceptance.criteria) ? acceptance.criteria as AnyObj[] : [];
3602
+ if (acceptedCriteria.some((criterion) => !hasNonEmptyString(criterion?.id) || !hasNonEmptyString(criterion?.description))) {
3603
+ die("acceptance.json contains a criterion without a non-empty id and description — refusing to rebuild trust.bundle without durable acceptance identity");
3604
+ }
3605
+ const acceptedIds = acceptedCriteria.map((criterion) => String(criterion.id));
3606
+ if (new Set(acceptedIds).size !== acceptedIds.length) {
3607
+ die("acceptance.json criterion ids must be unique — refusing to rebuild trust.bundle with ambiguous acceptance identity");
3608
+ }
3278
3609
  const contractSignature = (criteria: AnyObj[]): string => JSON.stringify(criteria.map((criterion) => ({
3279
3610
  id: criterion.id ?? null,
3280
3611
  description: criterion.description ?? null,
3281
3612
  })));
3613
+ // Planning may establish or refine a valid criterion contract after session startup. Once the
3614
+ // signatures agree, the bundle remains authoritative for verified status and observed-command
3615
+ // provenance; artifact status edits alone cannot manufacture completion.
3282
3616
  const criteria = acceptedCriteria.length > 0 && contractSignature(acceptedCriteria) !== contractSignature(bundledCriteria)
3283
3617
  ? acceptedCriteria
3284
3618
  : (bundledCriteria.length > 0 ? bundledCriteria : acceptedCriteria);
@@ -3302,7 +3636,7 @@ function mergeChecksById(existing: AnyObj[], incoming: AnyObj[]): AnyObj[] {
3302
3636
  return [...byId.values()];
3303
3637
  }
3304
3638
  function critiquesFromBundle(dir: string): AnyObj[] {
3305
- const bundle = loadJson(path.join(dir, "trust.bundle"));
3639
+ const bundle = loadTrustBundleForTrustMachinery(dir);
3306
3640
  if (!Array.isArray(bundle.claims)) return [];
3307
3641
  for (const c of bundle.claims) requireStampedClaim(c, dir);
3308
3642
  // A claim is a CRITIQUE when its origin is "critique" (authoritative — see requireStampedClaim
@@ -3328,7 +3662,7 @@ function critiquesFromBundle(dir: string): AnyObj[] {
3328
3662
  }
3329
3663
 
3330
3664
  function criteriaFromBundle(dir: string): AnyObj[] {
3331
- const bundle = loadJson(path.join(dir, "trust.bundle"));
3665
+ const bundle = loadTrustBundleForTrustMachinery(dir);
3332
3666
  if (!Array.isArray(bundle.claims)) return [];
3333
3667
  for (const claim of bundle.claims) requireStampedClaim(claim, dir);
3334
3668
  return bundle.claims
@@ -3336,14 +3670,28 @@ function criteriaFromBundle(dir: string): AnyObj[] {
3336
3670
  .map((claim: AnyObj) => {
3337
3671
  const md = claim.metadata && typeof claim.metadata === "object" && !Array.isArray(claim.metadata) ? claim.metadata as AnyObj : {};
3338
3672
  const saved = md.criterion && typeof md.criterion === "object" && !Array.isArray(md.criterion) ? md.criterion as AnyObj : {};
3339
- return {
3673
+ const criterion = {
3340
3674
  id: typeof saved.id === "string" ? saved.id : String(claim.subjectId || "").split("/").pop(),
3341
3675
  description: typeof saved.description === "string" ? saved.description : (claim.fieldOrBehavior || ""),
3342
3676
  status: typeof saved.status === "string" ? saved.status : (claim.value ?? "not_verified"),
3343
3677
  evidence_refs: Array.isArray(saved.evidence_refs) ? saved.evidence_refs : [],
3678
+ ...(Array.isArray(saved.observed_commands) ? { _observed_commands: saved.observed_commands } : {}),
3344
3679
  ...(typeof saved.verified_at === "string" ? { verified_at: saved.verified_at } : {}),
3345
3680
  ...(saved.identity_version === 2 ? { identity_version: 2 } : {}),
3346
3681
  };
3682
+ const observedCommands = Array.isArray(saved.observed_commands) ? saved.observed_commands as AnyObj[] : [];
3683
+ const evidence = Array.isArray(bundle.evidence) ? bundle.evidence.filter((item: AnyObj) => item?.claimId === claim.id) : [];
3684
+ const event = Array.isArray(bundle.events) ? bundle.events.find((item: AnyObj) => item?.claimId === claim.id && item.status === "verified") : null;
3685
+ const evidenceIds = new Set(evidence.map((item: AnyObj) => item.id));
3686
+ const hasCanonicalEvidence = claim.value === "pass"
3687
+ && claim.status === "verified"
3688
+ && observedCommands.length > 0
3689
+ && evidence.length === observedCommands.length
3690
+ && evidence.every((item: AnyObj) => item.evidenceType === "test_output" && item.passing === true && observedCommands.some((observation: AnyObj) => observation.command === item.execution?.label))
3691
+ && Array.isArray(event?.evidenceIds)
3692
+ && event.evidenceIds.length === evidenceIds.size
3693
+ && event.evidenceIds.every((id: string) => evidenceIds.has(id));
3694
+ return hasCanonicalEvidence ? markCanonicallyObservedCriterion(criterion) : criterion;
3347
3695
  })
3348
3696
  .filter((criterion: AnyObj) => typeof criterion.id === "string" && criterion.id.length > 0);
3349
3697
  }
@@ -3378,7 +3726,11 @@ async function recordEvidence(p: ReturnType<typeof parseArgs>): Promise<number>
3378
3726
  // (not re-read) as _existingState for the compose-safe merge — one readBundleState call.
3379
3727
  const _existingState = readBundleState(dir);
3380
3728
  const _existingCheckStampById = existingCheckStampMap(_existingState.checks);
3381
- const _checksRaw = [...opts(p, "check-json").map((v) => normalizeCheck(parseJson(v, "--check-json"), false, _existingCheckStampById)), ...opts(p, "surface-trust-json").map(surfaceCheckFromArtifact)];
3729
+ const projectRoot = narrativeGuardRoot(dir);
3730
+ const _checksRaw = [
3731
+ ...opts(p, "check-json").map((v) => normalizeCheck(parseJson(v, "--check-json"), false, _existingCheckStampById, projectRoot)),
3732
+ ...opts(p, "surface-trust-json").map((file, index) => surfaceCheckFromArtifact(file, index, projectRoot)),
3733
+ ];
3382
3734
  // WS8 (AC4, iteration 2): a command-backed check reconciles against CI or fails — it can
3383
3735
  // NEVER be waived. Reject --accepted-gap-reason/--waived-by on any check whose evidence
3384
3736
  // classifies as test_output (build/types/lint/test/command, and security/browser/runtime
@@ -3470,6 +3822,7 @@ async function recordCheck(p: ReturnType<typeof parseArgs>, commandArgv: string[
3470
3822
  if (commandArgv?.length && commandString) die("record-check: pass the command via EITHER `-- <command...>` OR --command, not both");
3471
3823
 
3472
3824
  const repoRoot = findRepoRootFromDir(dir);
3825
+ rejectNarrativeReference(repoRoot, commandArgv?.join(" ") ?? commandString, "record-check command");
3473
3826
  let displayCommand: string;
3474
3827
  let exitCode: number | null;
3475
3828
  let stdout = "";
@@ -3548,7 +3901,7 @@ ${stderr}` : ""}`.trim());
3548
3901
  status,
3549
3902
  summary,
3550
3903
  command: displayCommand,
3551
- }, false, _existingCheckStampById);
3904
+ }, false, _existingCheckStampById, repoRoot);
3552
3905
  if (outputSha256) check._output_sha256 = outputSha256;
3553
3906
 
3554
3907
  const _mergedChecks = mergeChecksById(_existingState.checks, [check]);
@@ -3655,10 +4008,19 @@ async function recordGateClaim(p: ReturnType<typeof parseArgs>): Promise<number>
3655
4008
  const mustRunTests = targetExpectation.id === "tests-evidence" && statusVal === "pass";
3656
4009
  const observedCommandRaw = opts(p, "observed-command-json");
3657
4010
  if (mustRunTests && gateCommands.length === 0) die("record-gate-claim requires at least one --command for a passing tests-evidence claim");
3658
- const projectRoot = gateCommands.length > 0 ? canonicalProjectRootForSession(dir) : null;
4011
+ // #619: the narrative evidence-ref guards (validateEvidenceRef / normalizeCheck /
4012
+ // completePassingCriteria) need a non-null, location-independent project root that never
4013
+ // dies on a non-canonical session. normalizeObservedCommands still requires the strict
4014
+ // canonical root, but only when there are commands to normalize.
4015
+ const projectRoot = narrativeGuardRoot(dir);
4016
+ const canonicalRoot = gateCommands.length > 0 ? canonicalProjectRootForSession(dir) : null;
4017
+ for (const command of gateCommands) rejectNarrativeReference(projectRoot, command, "record-gate-claim command");
3659
4018
  const observedCommands = gateCommands.length > 0
3660
- ? await normalizeObservedCommands(gateCommands, projectRoot!, mustRunTests, statusVal)
4019
+ ? await normalizeObservedCommands(gateCommands, canonicalRoot!, mustRunTests, statusVal)
3661
4020
  : [];
4021
+ // #634: persist the writer's real executions into the hash-chained command-log so the
4022
+ // capture fold has a deterministic observation even on exit-code-blind hosts.
4023
+ appendWriterObservedCommands(dir, observedCommands, ts);
3662
4024
  const observedCommandNames = new Set(observedCommands.map((entry) => entry.command));
3663
4025
  let outputSha256: string | null = null;
3664
4026
  if (!mustRunTests && gateCommands.length > 1) die("record-gate-claim accepts repeatable --command only for passing tests-evidence claims");
@@ -3687,7 +4049,7 @@ async function recordGateClaim(p: ReturnType<typeof parseArgs>): Promise<number>
3687
4049
  };
3688
4050
 
3689
4051
  // Include structured evidence refs if provided
3690
- const evidenceRefs: AnyObj[] = opts(p, "evidence-ref-json").map((v) => validateEvidenceRef(parseJson(v, "--evidence-ref-json"), "--evidence-ref-json"));
4052
+ const evidenceRefs: AnyObj[] = opts(p, "evidence-ref-json").map((v) => validateEvidenceRef(parseJson(v, "--evidence-ref-json"), "--evidence-ref-json", projectRoot));
3691
4053
  const producer = expectedGateProducer(exactFlowContext?.flowId ?? activeStep.flowId, activeStep.stepId, targetExpectation.id);
3692
4054
  if (statusVal === "pass") validateReviewableGateEvidence(dir, slug, evidenceRefs, producer, `gate claim ${targetExpectation.id}`);
3693
4055
  if (mustRunTests) requireObservedCommandRefs(evidenceRefs, observedCommandNames, "a passing tests-evidence claim", true);
@@ -3708,7 +4070,7 @@ async function recordGateClaim(p: ReturnType<typeof parseArgs>): Promise<number>
3708
4070
  if (gateCommand) check.command = gateCommand;
3709
4071
  if (observedCommands.length > 0) check._observed_commands = observedCommands;
3710
4072
 
3711
- const checkNormalized = normalizeCheck(check, /* allowGateClaimPrefix */ true);
4073
+ const checkNormalized = normalizeCheck(check, /* allowGateClaimPrefix */ true, undefined, projectRoot);
3712
4074
  if (outputSha256) checkNormalized._output_sha256 = outputSha256;
3713
4075
  // WS8 (ADR 0020): honor the accepted-gap waiver flags for a gate claim too.
3714
4076
  const gateWaiver = parseWaiver(p, ts);
@@ -3721,7 +4083,7 @@ async function recordGateClaim(p: ReturnType<typeof parseArgs>): Promise<number>
3721
4083
  // SAME expectation id supersedes the earlier check for that expectation (mergeChecksById); a
3722
4084
  // gate claim against a different expectation is additive.
3723
4085
  const _existingState = readBundleState(dir);
3724
- const criteria = mustRunTests ? completePassingCriteria(_existingState.criteria, opts(p, "criterion-json"), observedCommandNames, ts) : _existingState.criteria;
4086
+ const criteria = mustRunTests ? completePassingCriteria(_existingState.criteria, opts(p, "criterion-json"), observedCommands, ts, projectRoot) : _existingState.criteria;
3725
4087
  if (mustRunTests) {
3726
4088
  const liveCritiques = _existingState.critiques.filter((critique) => !critique.superseded_by);
3727
4089
  if (liveCritiques.length === 0 || liveCritiques.some((critique) => !critiqueIsCleanAndCurrent(dir, critique))) {
@@ -3778,6 +4140,7 @@ async function promote(p: ReturnType<typeof parseArgs>): Promise<number> {
3778
4140
  // repo-relative paths when the ref lives under the repo, so the claim is portable.
3779
4141
  const targets: string[] = [];
3780
4142
  for (const raw of rawPaths) {
4143
+ rejectNarrativeReference(repoRoot, raw, "promote --evidence-path");
3781
4144
  const abs = path.isAbsolute(raw) ? raw : path.resolve(repoRoot, raw);
3782
4145
  if (!fs.existsSync(abs)) die(`promote --evidence-path does not exist on disk: ${raw} (resolved: ${abs}). Promotion evidence refs must point at durable docs that were actually written.`);
3783
4146
  const rel = path.relative(repoRoot, abs);
@@ -3904,19 +4267,24 @@ async function advanceState(p: ReturnType<typeof parseArgs>): Promise<number> {
3904
4267
  return 0;
3905
4268
  }
3906
4269
 
3907
- export function normalizeFinding(raw: AnyObj): AnyObj {
4270
+ export function normalizeFinding(raw: AnyObj, projectRoot = process.cwd()): AnyObj {
3908
4271
  if (raw.file_refs !== undefined && !Array.isArray(raw.file_refs)) die("file_refs must be an array");
4272
+ for (const ref of raw.file_refs ?? []) {
4273
+ if (typeof ref !== "string") die("file_refs entries must be strings");
4274
+ rejectNarrativeReference(projectRoot, ref, "finding file_ref");
4275
+ }
3909
4276
  return raw;
3910
4277
  }
3911
4278
 
3912
4279
  async function recordCritique(p: ReturnType<typeof parseArgs>): Promise<number> {
3913
4280
  const dir = artifactDirFrom(p.positional[0] || die("artifact directory is required"));
3914
4281
  const slug = taskSlugFor(dir, opt(p, "task-slug"));
4282
+ const projectRoot = canonicalProjectRootForSession(dir);
3915
4283
  const verdict = opt(p, "verdict");
3916
4284
  if (!critiqueStatuses.has(verdict)) die("record-critique requires --verdict pass, fail, or not_verified");
3917
4285
  const summary = opt(p, "summary");
3918
4286
  if (!hasNonEmptyString(summary)) die("record-critique requires --summary");
3919
- const lanes = normalizeCritiqueLanes(opts(p, "lane-json"));
4287
+ const lanes = normalizeCritiqueLanes(opts(p, "lane-json"), projectRoot);
3920
4288
  const reviewArtifacts = reviewTargetArtifacts(dir, opts(p, "artifact-ref"), "record-critique review_target");
3921
4289
  if (verdict === "pass" && (lanes.some((lane) => lane.status !== "pass") || reviewArtifacts.length === 0)) {
3922
4290
  die("a passing critique requires every lane to pass and at least one local reviewed --artifact-ref");
@@ -3946,12 +4314,12 @@ async function recordCritique(p: ReturnType<typeof parseArgs>): Promise<number>
3946
4314
  review_target: {
3947
4315
  artifacts: reviewArtifacts,
3948
4316
  workspace_snapshot: captureReviewWorkspaceSnapshot(
3949
- canonicalProjectRootForSession(dir),
4317
+ projectRoot,
3950
4318
  reviewArtifacts.map((artifact) => ({ file: String(artifact.file), sha256: String(artifact.sha256) })),
3951
4319
  ),
3952
4320
  },
3953
4321
  artifact_refs: reviewArtifacts.map((artifact) => artifact.file),
3954
- findings: opts(p, "finding-json").map((v) => normalizeFinding(parseJson(v, "--finding-json"))),
4322
+ findings: opts(p, "finding-json").map((v) => normalizeFinding(parseJson(v, "--finding-json"), projectRoot)),
3955
4323
  };
3956
4324
  if (critique.verdict === "pass" && critique.findings.some((f: AnyObj) => f.status === "open")) die("required critique must pass");
3957
4325
  // #267/#282: supersede-by-critique-id. The latest write for a critique id wins for
@@ -4020,14 +4388,43 @@ async function importCritique(p: ReturnType<typeof parseArgs>): Promise<number>
4020
4388
  if (verdict !== "pass") die("required critique must pass");
4021
4389
  return result;
4022
4390
  }
4391
+
4392
+ function rejectNarrativeReleaseReferences(projectRoot: string, value: unknown, label: string, referenceField = false): void {
4393
+ if (typeof value === "string") {
4394
+ if (referenceField) rejectNarrativeReference(projectRoot, value, label);
4395
+ return;
4396
+ }
4397
+ if (Array.isArray(value)) {
4398
+ for (const entry of value) rejectNarrativeReleaseReferences(projectRoot, entry, label, referenceField);
4399
+ return;
4400
+ }
4401
+ if (!value || typeof value !== "object") return;
4402
+ for (const [key, entry] of Object.entries(value as AnyObj)) {
4403
+ const isReferenceField = /^(?:refs?|files?|paths?|(?:evidence|artifact|source)_(?:refs?|files?|paths?))$/.test(key);
4404
+ rejectNarrativeReleaseReferences(projectRoot, entry, `${label}.${key}`, isReferenceField);
4405
+ }
4406
+ }
4407
+
4023
4408
  async function recordRelease(p: ReturnType<typeof parseArgs>): Promise<number> {
4024
4409
  const dir = artifactDirFrom(p.positional[0] || die("artifact directory is required"));
4025
4410
  const slug = taskSlugFor(dir, opt(p, "task-slug"));
4411
+ const projectRoot = narrativeGuardRoot(dir);
4026
4412
  const decision = opt(p, "decision");
4027
4413
  if (!["merge", "release", "deploy", "hold", "rollback_required"].includes(decision)) die("decision must be one of: merge, release, deploy, hold, rollback_required");
4028
4414
  const gates = opts(p, "gate-json").map((v) => parseJson(v, "--gate-json"));
4029
4415
  if (["merge", "release", "deploy"].includes(decision) && !gates.some((g) => g.name === decision && g.status === "pass")) die(`positive release decision requires ${decision} gate to pass`);
4030
- const payload = { ...sidecarBase(slug), decision: opt(p, "decision"), updated_at: opt(p, "timestamp", now()), scope: opt(p, "scope"), evidence_ref: opt(p, "evidence-ref"), gates: opts(p, "gate-json").map((v) => parseJson(v, "--gate-json")), rollback_plan: parseJson(opt(p, "rollback-json", '{"status":"not_required","summary":"Not required.","owner":"maintainer"}'), "--rollback-json"), observability_plan: parseJson(opt(p, "observability-json", '{"status":"not_required","summary":"Not required."}'), "--observability-json"), post_deploy_checks: opts(p, "post-deploy-json").map((v) => parseJson(v, "--post-deploy-json")), docs: parseJson(opt(p, "docs-json", '{"status":"not_needed","summary":"Not needed."}'), "--docs-json") };
4416
+ const evidenceRef = opt(p, "evidence-ref");
4417
+ const rollbackPlan = parseJson(opt(p, "rollback-json", '{"status":"not_required","summary":"Not required.","owner":"maintainer"}'), "--rollback-json");
4418
+ const observabilityPlan = parseJson(opt(p, "observability-json", '{"status":"not_required","summary":"Not required."}'), "--observability-json");
4419
+ const postDeployChecks = opts(p, "post-deploy-json").map((v) => parseJson(v, "--post-deploy-json"));
4420
+ const docs = parseJson(opt(p, "docs-json", '{"status":"not_needed","summary":"Not needed."}'), "--docs-json");
4421
+ rejectNarrativeReference(projectRoot, evidenceRef, "release evidence_ref");
4422
+ rejectNarrativeReleaseReferences(projectRoot, gates, "release gates");
4423
+ rejectNarrativeReleaseReferences(projectRoot, rollbackPlan, "release rollback_plan");
4424
+ rejectNarrativeReleaseReferences(projectRoot, observabilityPlan, "release observability_plan");
4425
+ rejectNarrativeReleaseReferences(projectRoot, postDeployChecks, "release post_deploy_checks");
4426
+ rejectNarrativeReleaseReferences(projectRoot, docs, "release docs");
4427
+ const payload = { ...sidecarBase(slug), decision: opt(p, "decision"), updated_at: opt(p, "timestamp", now()), scope: opt(p, "scope"), evidence_ref: evidenceRef, gates, rollback_plan: rollbackPlan, observability_plan: observabilityPlan, post_deploy_checks: postDeployChecks, docs };
4031
4428
  const stateSummary = opt(p, "summary").trim() || `Release readiness recorded for ${decision}.`;
4032
4429
  writeJson(path.join(dir, "release.json"), payload);
4033
4430
  writeState(dir, slug, "delivered", "release", payload.updated_at, stateSummary);
@@ -5386,8 +5783,12 @@ function validateLearningPrevention(prevention: unknown): void {
5386
5783
  if (typeof value.status !== "string" || value.status.length === 0) die("correction.prevention.status is required");
5387
5784
  if (!["open", "completed", "accepted", "deferred", "rejected"].includes(value.status)) die("correction.prevention.status must be one of: open, completed, accepted, deferred, rejected");
5388
5785
  }
5389
- export function normalizeLearning(raw: AnyObj, timestamp: string): AnyObj {
5786
+ export function normalizeLearning(raw: AnyObj, timestamp: string, projectRoot = process.cwd()): AnyObj {
5390
5787
  if (!Array.isArray(raw.source_refs)) die("source_refs must be an array");
5788
+ for (const ref of raw.source_refs) {
5789
+ if (typeof ref !== "string") die("source_refs entries must be strings");
5790
+ rejectNarrativeReference(projectRoot, ref, "learning source_ref");
5791
+ }
5391
5792
  if (!Array.isArray(raw.facts)) die("facts must be an array");
5392
5793
  if (!Array.isArray(raw.routing)) die("routing must be an array");
5393
5794
  if (!["success", "failure", "mixed", "unknown"].includes(raw.outcome)) die("learning outcome must be one of: success, failure, mixed, unknown");
@@ -5398,7 +5799,8 @@ async function recordLearning(p: ReturnType<typeof parseArgs>): Promise<number>
5398
5799
  const dir = artifactDirFrom(p.positional[0] || die("artifact directory is required"));
5399
5800
  const slug = taskSlugFor(dir, opt(p, "task-slug"));
5400
5801
  const timestamp = opt(p, "timestamp", now());
5401
- const records = opts(p, "record-json").map((v) => normalizeLearning(parseJson(v, "--record-json"), timestamp));
5802
+ const projectRoot = narrativeGuardRoot(dir);
5803
+ const records = opts(p, "record-json").map((v) => normalizeLearning(parseJson(v, "--record-json"), timestamp, projectRoot));
5402
5804
  const status = opt(p, "status", "learned");
5403
5805
  if (status === "learned" && records.some((r) => r.routing.some((x: AnyObj) => x.status === "open"))) die("learning status learned cannot have open routing");
5404
5806
  if (status === "learned" && records.some((r) => r.correction === undefined)) die("learning status learned requires every record to include correction.needed");
@@ -5418,7 +5820,7 @@ function evidenceClean(dir: string): boolean {
5418
5820
  // legacy (pre-bundle-era) sessions that never wrote a trust.bundle at all — unrelated to origin
5419
5821
  // stamping. When a trust.bundle IS present, every claim must be stamped (requireStampedClaim);
5420
5822
  // there is no claimType-derivation fallback for an unstamped claim (#268/#344).
5421
- const bundle = loadJson(path.join(dir, "trust.bundle"));
5823
+ const bundle = loadTrustBundleForTrustMachinery(dir);
5422
5824
  if (Array.isArray(bundle.claims)) {
5423
5825
  for (const c of bundle.claims) requireStampedClaim(c, dir);
5424
5826
  const checkClaims = (bundle.claims as AnyObj[]).filter((c: AnyObj) => c && claimOrigin(c) === "check");
@@ -5437,7 +5839,7 @@ function evidenceClean(dir: string): boolean {
5437
5839
  }
5438
5840
  function critiqueClean(dir: string): boolean {
5439
5841
  // trust.bundle is the sole critique artifact. Legacy critique.json must not influence gates.
5440
- const bundle = loadJson(path.join(dir, "trust.bundle"));
5842
+ const bundle = loadTrustBundleForTrustMachinery(dir);
5441
5843
  if (Array.isArray(bundle.claims)) {
5442
5844
  for (const c of bundle.claims) requireStampedClaim(c, dir);
5443
5845
  const critiqueClaims = (bundle.claims as AnyObj[]).filter((c: AnyObj) => {
@@ -5457,9 +5859,14 @@ function assertExistingLearningValid(dir: string): void {
5457
5859
  const file = path.join(dir, "learning.json");
5458
5860
  if (!fs.existsSync(file)) return;
5459
5861
  const data = loadJson(file);
5862
+ const projectRoot = narrativeGuardRoot(dir);
5460
5863
  if (!Array.isArray(data.records)) die("learning records must be an array");
5461
5864
  for (const record of data.records) {
5462
5865
  if (!Array.isArray(record.source_refs)) die("source_refs must be an array");
5866
+ for (const ref of record.source_refs) {
5867
+ if (typeof ref !== "string") die("source_refs entries must be strings");
5868
+ rejectNarrativeReference(projectRoot, ref, "learning source_ref");
5869
+ }
5463
5870
  if (!Array.isArray(record.facts)) die("facts must be an array");
5464
5871
  if (!Array.isArray(record.routing)) die("routing must be an array");
5465
5872
  validateLearningCorrection(record);
@@ -5470,6 +5877,7 @@ async function dogfoodPass(p: ReturnType<typeof parseArgs>): Promise<number> {
5470
5877
  const root = opt(p, "artifact-root") ? path.resolve(opt(p, "artifact-root")) : defaultArtifactRootForRead();
5471
5878
  const dir = path.resolve(opt(p, "artifact-dir") || currentDir(root) || "");
5472
5879
  requireArtifactDirUnderRoot(dir, root);
5880
+ const projectRoot = narrativeGuardRoot(dir);
5473
5881
  assertExistingLearningValid(dir);
5474
5882
  const verdict = opt(p, "verdict");
5475
5883
  if (verdict === "pass") {
@@ -5482,7 +5890,7 @@ async function dogfoodPass(p: ReturnType<typeof parseArgs>): Promise<number> {
5482
5890
  // trust.bundle does not yet exist (loadJson falls back to {}), exactly as
5483
5891
  // recordEvidence/recordCheck rely on.
5484
5892
  const _dogfoodExistingCheckStampById = existingCheckStampMap(readBundleState(dir).checks);
5485
- const checks = opts(p, "check-json").map((v) => normalizeCheck(parseJson(v, "--check-json"), false, _dogfoodExistingCheckStampById));
5893
+ const checks = opts(p, "check-json").map((v) => normalizeCheck(parseJson(v, "--check-json"), false, _dogfoodExistingCheckStampById, projectRoot));
5486
5894
  if (checks.some((c) => c.status !== "pass" && c.status !== "skip")) die("clean evidence requires all non-skipped checks to pass");
5487
5895
  // Phase 4c: evidence check reads from trust.bundle (sole verification artifact); legacy evidence.json fallback in evidenceClean.
5488
5896
  // #268/#344: builder.* check/critique claims count as clean evidence via their authoritative origin stamp.
@@ -5493,13 +5901,13 @@ async function dogfoodPass(p: ReturnType<typeof parseArgs>): Promise<number> {
5493
5901
  if (!_hasBundleEvidence && !_hasLegacyEvidence && !fs.existsSync(path.join(dir, "trust.bundle")) && !fs.existsSync(path.join(dir, "evidence.json")) && checks.length === 0) die("cannot mark clean without passing evidence");
5494
5902
  if (p.flags.has("require-critique") || opt(p, "release-decision")) {
5495
5903
  const newCritiqueVerdict = opt(p, "critique-verdict", "pass");
5496
- for (const value of opts(p, "finding-json")) normalizeFinding(parseJson(value, "--finding-json"));
5904
+ for (const value of opts(p, "finding-json")) normalizeFinding(parseJson(value, "--finding-json"), projectRoot);
5497
5905
  if (newCritiqueVerdict !== "pass") die(opt(p, "release-decision") ? "requires clean critique" : "requires clean critique before recording pass evidence");
5498
5906
  if (!opt(p, "critique-id") && !critiqueClean(dir)) die("requires passing critique");
5499
5907
  if (!critiqueClean(dir) && fs.existsSync(path.join(dir, "trust.bundle"))) die(opt(p, "release-decision") ? "requires clean critique" : "requires clean critique before recording pass evidence");
5500
5908
  }
5501
5909
  }
5502
- const learningRecords = opts(p, "learning-record-json").map((v) => normalizeLearning(parseJson(v, "--learning-record-json"), opt(p, "timestamp", now())));
5910
+ const learningRecords = opts(p, "learning-record-json").map((v) => normalizeLearning(parseJson(v, "--learning-record-json"), opt(p, "timestamp", now()), projectRoot));
5503
5911
  if (opt(p, "learning-status") === "learned" && learningRecords.some((r) => r.routing.some((x: AnyObj) => x.status === "open"))) die("learned status cannot have open learning routing");
5504
5912
  if (opt(p, "learning-status") === "learned" && learningRecords.some((r) => r.correction === undefined)) die("learned status requires every learning record to include correction.needed");
5505
5913
  if (opts(p, "check-json").length) await recordEvidence({ ...p, positional: [dir], opts: { ...p.opts, verdict: [verdict] }, flags: p.flags });
@@ -5655,7 +6063,7 @@ export function gateAdvisoryFix(
5655
6063
  }
5656
6064
 
5657
6065
  /**
5658
- * Build a schema-conformant InquiryRecord for the hachure inquiry-record.schema.json.
6066
+ * Build a schema-conformant InquiryRecord for @kontourai/surface's canonical InquiryRecord schema.
5659
6067
  * Strips Surface-internal fields (identityLinkIds, transitiveRuleIds) from
5660
6068
  * resolutionPath that are valid in the TS type but not in the JSON schema.
5661
6069
  * Sets answer.value to the gate-review value-add: { calibration, advisoryFix, gateFired, sessionSlug }.
@@ -5855,16 +6263,17 @@ async function gateReview(p: ReturnType<typeof parseArgs>): Promise<number> {
5855
6263
 
5856
6264
  const records = buildGateInquiryRecords(bundle, blockSignal, slug, expectedCriterionIds, surface);
5857
6265
 
5858
- // Validate each record against the hachure inquiry-record.schema.json (fail-open)
5859
- const validator = getHachureInquiryRecordValidator();
6266
+ // Validate each record against the canonical InquiryRecord schema via Surface (fail-open)
5860
6267
  let schemaValid = true;
5861
6268
  const validationErrors: string[] = [];
5862
- for (const record of records) {
5863
- if (validator) {
5864
- const result = validator(record);
5865
- if (!result.valid) {
6269
+ const inquiryValidator = (surface as { validateInquiryRecord?: (r: unknown) => unknown } | null)?.validateInquiryRecord;
6270
+ if (typeof inquiryValidator === "function") {
6271
+ for (const record of records) {
6272
+ try {
6273
+ inquiryValidator(record);
6274
+ } catch (err) {
5866
6275
  schemaValid = false;
5867
- validationErrors.push(...result.errors.map((e) => `${record["id"] ?? "?"}: ${e}`));
6276
+ validationErrors.push(`${record["id"] ?? "?"}: ${err instanceof Error ? err.message : String(err)}`);
5868
6277
  }
5869
6278
  }
5870
6279
  }
@@ -5885,7 +6294,7 @@ async function gateReview(p: ReturnType<typeof parseArgs>): Promise<number> {
5885
6294
  .filter(([, n]) => n > 0)
5886
6295
  .map(([k, n]) => `${k}=${n}`)
5887
6296
  .join(", ");
5888
- const schemaTag = validator ? (schemaValid ? " schema:valid" : " schema:INVALID") : " schema:unavailable";
6297
+ const schemaTag = typeof inquiryValidator === "function" ? (schemaValid ? " schema:valid" : " schema:INVALID") : " schema:unavailable";
5889
6298
  console.log(`gate-review: ${records.length} InquiryRecord(s) [${summary}]${schemaTag} → ${outputPath}`);
5890
6299
  return 0;
5891
6300
  }
@@ -6389,14 +6798,13 @@ async function liveness(p: ReturnType<typeof parseArgs>): Promise<number> {
6389
6798
  }
6390
6799
  // ─────────────────────────────────────────────────────────────────────────────
6391
6800
 
6392
- // ─── Claim Lookup — pure helper (promotable to Surface #171) ─────────────────
6393
- // buildClaimExplanation + its types are extracted to ./sidecar-claim-explain.ts
6394
- // (ops#22): a PURE projection (report + bundle + id in, structured explanation out)
6395
- // with no fs/CLI/shared state, unit-tested in isolation. Re-exported here so the
6396
- // library facade (src/index.ts) and the IO `claimLookup` handler below are unchanged.
6397
- export { buildClaimExplanation } from "./sidecar-claim-explain.js";
6398
- export type { ClaimEvidenceItem, ClaimExplanation } from "./sidecar-claim-explain.js";
6399
- import { buildClaimExplanation } from "./sidecar-claim-explain.js";
6801
+ // ─── Claim Lookup — consumes Surface explainClaim (#171 lift complete) ───────
6802
+ // The pure projection this file once hosted (sidecar-claim-explain.ts, ops#22)
6803
+ // was lifted verbatim into @kontourai/surface as explainClaim(report, claimId)
6804
+ // (kontourai/surface#151, 2.10.0) with the drilldown composition folded inside
6805
+ // (same fail-soft semantics the caller-side enrichment had). Types re-export
6806
+ // from Surface so the library facade keeps compiling for downstream importers.
6807
+ export type { ClaimEvidenceItem, ClaimExplanation } from "@kontourai/surface";
6400
6808
 
6401
6809
  /**
6402
6810
  * claim <id> <dir>
@@ -6438,29 +6846,17 @@ Available claim ids:
6438
6846
 
6439
6847
  // Load Surface via tryLoadSurface() (ESM, cached, fail-open pattern)
6440
6848
  const surface = await tryLoadSurface();
6441
- if (!surface || typeof surface.buildTrustReport !== "function" || typeof surface.buildDerivationDrilldown !== "function") {
6442
- process.stderr.write(`[claim] @kontourai/surface unavailable or missing buildTrustReport/buildDerivationDrilldown
6849
+ if (!surface || typeof surface.buildTrustReport !== "function" || typeof surface.explainClaim !== "function") {
6850
+ process.stderr.write(`[claim] @kontourai/surface unavailable or missing buildTrustReport/explainClaim (needs >=2.10)
6443
6851
  `);
6444
6852
  return 0; // fail-open, consistent with gate-review pattern
6445
6853
  }
6446
6854
 
6447
- // Build TrustReport (required buildDerivationDrilldown needs TrustReport, not TrustBundle)
6855
+ // Build TrustReport, then the structured explanation Surface's explainClaim
6856
+ // (#171 lift) composes the derivation drilldown internally, fail-soft, exactly
6857
+ // as the retired local helper + caller-side enrichment did.
6448
6858
  const report = surface.buildTrustReport(bundle as unknown as Record<string, unknown>);
6449
-
6450
- // Build the structured explanation (pure, promotable to #171)
6451
- const explanation = buildClaimExplanation(report, bundle as unknown as Record<string, unknown>, claimId);
6452
-
6453
- // Enrich the why.directInputs/leafClaims/diagnostics from the drilldown
6454
- try {
6455
- const drilldown = surface.buildDerivationDrilldown(report, claimId) as AnyObj;
6456
- if (drilldown) {
6457
- explanation.why.directInputs = Array.isArray(drilldown.directInputs) ? drilldown.directInputs : [];
6458
- explanation.why.leafClaims = Array.isArray(drilldown.leafClaims) ? drilldown.leafClaims : [];
6459
- explanation.why.diagnostics = Array.isArray(drilldown.diagnostics) ? drilldown.diagnostics : [];
6460
- }
6461
- } catch {
6462
- // buildDerivationDrilldown threw (e.g. claim not in report) — proceed without drilldown
6463
- }
6859
+ const explanation = surface.explainClaim(report, claimId) as import("@kontourai/surface").ClaimExplanation;
6464
6860
 
6465
6861
  if (p.flags.has("json")) {
6466
6862
  console.log(JSON.stringify(explanation, null, 2));
@@ -6547,7 +6943,7 @@ Available claim ids:
6547
6943
  if (explanation.why.changeRecords.length > 0) {
6548
6944
  lines.push(` Change records: ${explanation.why.changeRecords.length}`);
6549
6945
  for (const cr of explanation.why.changeRecords) {
6550
- lines.push(` - ${cr.action ?? "?"} at ${cr.at ?? cr.createdAt ?? "?"}`);
6946
+ lines.push(` - ${cr.action ?? "?"} at ${(cr as AnyObj).at ?? cr.createdAt ?? "?"}`);
6551
6947
  }
6552
6948
  }
6553
6949