@iowarp/clio-coder 0.3.3 → 0.3.4

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 (244) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/CONTRIBUTING.md +1 -1
  3. package/README.md +3 -3
  4. package/dist/{acp-P2AQILE2.js → acp-S5R4RR5B.js} +7 -6
  5. package/dist/{agents-72W3BI7I.js → agents-P6DMMVZY.js} +24 -21
  6. package/dist/assets/codewiki.json +1 -1
  7. package/dist/{auth-5TWEIYDN.js → auth-2XCZLPKS.js} +12 -8
  8. package/dist/{chunk-GGXXDWE4.js → chunk-22NAGB7X.js} +2 -2
  9. package/dist/{chunk-2DJ2KNFG.js → chunk-2LZI5CAG.js} +133 -13
  10. package/dist/{chunk-OAO4GE4M.js → chunk-2TZWSW76.js} +2 -2
  11. package/dist/{chunk-OOJYHWRB.js → chunk-34475P3I.js} +2 -2
  12. package/dist/{chunk-4XUGQOHA.js → chunk-35MKKU5R.js} +4 -4
  13. package/dist/{chunk-LM5TQCJZ.js → chunk-3HZ5RWN2.js} +5 -5
  14. package/dist/{chunk-AGYYIBLL.js → chunk-3JLKSKD7.js} +2 -2
  15. package/dist/{chunk-KZWTDYJF.js → chunk-4JUF2NNX.js} +7 -7
  16. package/dist/{chunk-ZDOOVTXZ.js → chunk-4OC57DA6.js} +27 -4
  17. package/dist/chunk-5M54SPOL.js +926 -0
  18. package/dist/{chunk-STBPMHSX.js → chunk-7RXG6QRZ.js} +51 -11
  19. package/dist/{chunk-X6IAEBZR.js → chunk-A2GZF7DC.js} +5 -5
  20. package/dist/{chunk-A3CYT5EX.js → chunk-AD2SYQYC.js} +55 -2
  21. package/dist/chunk-AOCYTWAV.js +449 -0
  22. package/dist/chunk-BEY543CS.js +258 -0
  23. package/dist/{chunk-6N5PTWMY.js → chunk-BP4OYD6A.js} +32 -13
  24. package/dist/chunk-BPGS2WCQ.js +612 -0
  25. package/dist/{chunk-V6RTAOC2.js → chunk-BRXQQJFP.js} +8 -8
  26. package/dist/chunk-CFGTUFWB.js +67 -0
  27. package/dist/{chunk-CBCAPZAA.js → chunk-E25LMLRW.js} +2 -2
  28. package/dist/{chunk-DUYJ5IO6.js → chunk-EDRHSCIE.js} +4 -4
  29. package/dist/{chunk-XBXAASKX.js → chunk-EFADSJET.js} +2 -2
  30. package/dist/{chunk-M6SHUN7Q.js → chunk-FO5ZOVUY.js} +2 -2
  31. package/dist/chunk-FYYLNIL5.js +313 -0
  32. package/dist/{chunk-FNTMWMX5.js → chunk-HV5X7OR2.js} +14 -12
  33. package/dist/{chunk-TZTZS7QK.js → chunk-HXG4IURW.js} +5 -3
  34. package/dist/{chunk-5UFT4SUX.js → chunk-K6WL7QZT.js} +3 -3
  35. package/dist/chunk-K7VKOLQQ.js +15 -0
  36. package/dist/{chunk-BMEMKKIT.js → chunk-KOHPCX4K.js} +2 -2
  37. package/dist/{chunk-OC7FIQPC.js → chunk-KRPY7NTG.js} +10 -7
  38. package/dist/chunk-LL4KHSZI.js +22 -0
  39. package/dist/{chunk-DSELYM6W.js → chunk-MEQ45TQ4.js} +15 -9
  40. package/dist/{chunk-5UUP6MWO.js → chunk-MV3K5QF2.js} +5 -436
  41. package/dist/{chunk-6SGHMWE3.js → chunk-N4CZJQRK.js} +5 -5
  42. package/dist/{chunk-TZK7PACC.js → chunk-NILBFAPG.js} +14 -8
  43. package/dist/chunk-OZNBF4L3.js +23 -0
  44. package/dist/{verify-375KUB3Y.js → chunk-PCZJO5TI.js} +127 -42
  45. package/dist/chunk-QQK64KLB.js +1360 -0
  46. package/dist/{chunk-SRDMMSEP.js → chunk-QQL5RT5M.js} +979 -1619
  47. package/dist/{chunk-LZSJBIVT.js → chunk-QWU7ZBO7.js} +70 -720
  48. package/dist/{chunk-2TLUCQVG.js → chunk-RD5U66HV.js} +3 -3
  49. package/dist/{chunk-OKGUZO2U.js → chunk-SPULKLCF.js} +4 -3
  50. package/dist/{chunk-OQ33BKR3.js → chunk-TTNYS3EA.js} +3 -60
  51. package/dist/chunk-TW3WDMVS.js +677 -0
  52. package/dist/chunk-TZSKNMZG.js +434 -0
  53. package/dist/{chunk-7MNJORFF.js → chunk-UL3WSD3F.js} +6 -1
  54. package/dist/{chunk-PIWWS5BL.js → chunk-UZHIZC5S.js} +7 -7
  55. package/dist/{chunk-UFIIWP2H.js → chunk-VAWWTKDP.js} +8 -8
  56. package/dist/{chunk-COU2UHX6.js → chunk-VEZEGCGW.js} +170 -2
  57. package/dist/{chunk-LW6DSM3M.js → chunk-VMNQ6OZA.js} +98 -202
  58. package/dist/chunk-VSNATDE6.js +122 -0
  59. package/dist/chunk-W6GROXXM.js +69 -0
  60. package/dist/chunk-WPQLXFOZ.js +375 -0
  61. package/dist/{chunk-ORBHGJC5.js → chunk-WR67VIZY.js} +3 -3
  62. package/dist/{chunk-PAJK6MAQ.js → chunk-X6COSD2O.js} +5 -5
  63. package/dist/chunk-ZGVHUX3M.js +66 -0
  64. package/dist/{chunk-LWLEKMDQ.js → chunk-ZYKPLLNQ.js} +510 -547
  65. package/dist/cli/index.js +27 -23
  66. package/dist/{clio-JOU4FXVA.js → clio-J5JIOIDS.js} +7 -6
  67. package/dist/{code-nav-7AX6FYE6.js → code-nav-AXCXSBHX.js} +5 -3
  68. package/dist/{config-XCDVKR23.js → config-OEBMIN2U.js} +37 -27
  69. package/dist/{configure-4GAP54ZW.js → configure-PUQOSIXQ.js} +16 -13
  70. package/dist/{context-5VKGUVJJ.js → context-EKDCKUUZ.js} +82 -7
  71. package/dist/{context-4UOGGLQ5.js → context-MGSE4Z2T.js} +33 -23
  72. package/dist/{context-77FM5DV5.js → context-URSXPBCK.js} +17 -9
  73. package/dist/{context-clear-XXJRLCJJ.js → context-clear-KDAJRNUK.js} +33 -23
  74. package/dist/context-working-set-SBKMPPI2.js +1552 -0
  75. package/dist/{dispatch-runner-QPRDDBDX.js → dispatch-runner-MSWN72NK.js} +43 -29
  76. package/dist/{doctor-HR46URBJ.js → doctor-7BSE27PJ.js} +10 -10
  77. package/dist/{eval-XSSNATB4.js → eval-IZGDOO4H.js} +9 -8
  78. package/dist/{evidence-6HG2PY2B.js → evidence-SR7WXB5B.js} +51 -23
  79. package/dist/{evolve-K7YU3NCY.js → evolve-K7VE2CBX.js} +30 -20
  80. package/dist/{fleet-VY3HHKN6.js → fleet-7XMJNQNF.js} +48 -38
  81. package/dist/{fleet-preflight-DDN536IT.js → fleet-preflight-AQNAH644.js} +3 -3
  82. package/dist/{init-JYGXI3FK.js → init-JGNPAYXT.js} +41 -31
  83. package/dist/{memory-WFZMGYHX.js → memory-4ALKDJ4Q.js} +32 -22
  84. package/dist/{models-I5QWSEOM.js → models-ZMMLFJNN.js} +22 -19
  85. package/dist/{monitor-GE4ID3IA.js → monitor-2F3T5KHP.js} +55 -43
  86. package/dist/{orchestrator-EM5MC3HM.js → orchestrator-ORHT43JB.js} +572 -388
  87. package/dist/{reset-L2FQEE3E.js → reset-NXGTYNUO.js} +4 -3
  88. package/dist/{run-ZU3QMZPZ.js → run-RF4WJGMT.js} +51 -41
  89. package/dist/{share-S5BZQC5I.js → share-UT3W6E4M.js} +5 -4
  90. package/dist/{skills-X5VXCRNQ.js → skills-PSACKC5Q.js} +2 -2
  91. package/dist/{skills-eval-WKIHWTHR.js → skills-eval-WJSI55RZ.js} +34 -24
  92. package/dist/{targets-SNCPI2NR.js → targets-PIIRAOYS.js} +23 -20
  93. package/dist/{terminal-lease-BNAHVHBS.js → terminal-lease-ULWXWNVY.js} +4 -3
  94. package/dist/{upgrade-JQHHPQ4K.js → upgrade-346TZ6AV.js} +18 -17
  95. package/dist/{usage-OR4O5SMZ.js → usage-6KKXR32N.js} +34 -24
  96. package/dist/verifiers-4UUM6TEE.js +1214 -0
  97. package/dist/verify-X5HDROLA.js +25 -0
  98. package/dist/{wiki-generate-UEXP2ARI.js → wiki-generate-7STOCIFZ.js} +42 -31
  99. package/dist/worker/entry.js +33 -24
  100. package/docs/README.md +7 -6
  101. package/docs/acp.md +1 -1
  102. package/docs/alcf-provider.md +1 -1
  103. package/docs/architecture.md +2 -2
  104. package/docs/artifact-versions.md +1 -1
  105. package/docs/built-in-agents.md +1 -1
  106. package/docs/capacity-and-scheduling.md +1 -1
  107. package/docs/commands-and-modes.md +53 -21
  108. package/docs/config-knobs-audit.md +1 -2
  109. package/docs/configuration-and-targets.md +15 -1
  110. package/docs/context-engine.md +64 -12
  111. package/docs/context-working-set.md +194 -0
  112. package/docs/development-pipeline.md +1 -1
  113. package/docs/documentation-coverage.md +5 -5
  114. package/docs/documentation-guide.md +6 -5
  115. package/docs/environment-variables.md +2 -1
  116. package/docs/eval-runner.md +1 -1
  117. package/docs/evals-internal.md +14 -1
  118. package/docs/evidence-and-memory.md +74 -2
  119. package/docs/evolution.md +1 -1
  120. package/docs/exit-codes-and-output.md +1 -1
  121. package/docs/extensions-and-sharing.md +2 -2
  122. package/docs/fleet-dispatch.md +22 -7
  123. package/docs/glossary.md +21 -1
  124. package/docs/installation-and-lifecycle.md +2 -2
  125. package/docs/middleware-and-components.md +1 -1
  126. package/docs/model-catalog.md +7 -9
  127. package/docs/observability.md +4 -4
  128. package/docs/proactive-memory.md +1 -1
  129. package/docs/prompt-envelope-and-tools.md +4 -4
  130. package/docs/provider-adapter-cookbook.md +1 -1
  131. package/docs/release-cut-checklist.md +31 -31
  132. package/docs/safety-model.md +23 -4
  133. package/docs/scientific-validation.md +21 -3
  134. package/docs/session-lifecycle.md +3 -3
  135. package/docs/skills-marketplace.md +1 -1
  136. package/docs/tool-usage.md +79 -12
  137. package/docs/trace-store.md +1 -1
  138. package/docs/troubleshooting.md +1 -1
  139. package/docs/tui-design.md +1 -1
  140. package/docs/worker-dispatch-mechanics.md +11 -1
  141. package/package.json +8 -11
  142. package/skills/meta/clio-test/SKILL.md +20 -17
  143. package/skills/meta/clio-test/evals.md +3 -3
  144. package/skills/meta/clio-test/references/harness.md +35 -6
  145. package/skills/meta/clio-test/references/test-map.md +20 -10
  146. package/skills/registry.yaml +2 -2
  147. package/skills/skill-marketplace.json +1 -1
  148. package/src/cli/context-working-set.ts +513 -0
  149. package/src/cli/context.ts +8 -0
  150. package/src/cli/evidence.ts +20 -2
  151. package/src/cli/index.ts +4 -0
  152. package/src/cli/verifiers.ts +325 -0
  153. package/src/core/bash-exec.ts +39 -14
  154. package/src/core/bus-events.ts +19 -4
  155. package/src/core/config.ts +54 -0
  156. package/src/core/defaults.ts +50 -3
  157. package/src/core/verification-scripts.ts +6 -0
  158. package/src/domains/agents/builtins/verifier.md +3 -0
  159. package/src/domains/config/classify.ts +1 -0
  160. package/src/domains/context/working-set/contract.ts +161 -0
  161. package/src/domains/context/working-set/defaults.ts +28 -0
  162. package/src/domains/context/working-set/engine.ts +203 -0
  163. package/src/domains/context/working-set/fold.ts +62 -0
  164. package/src/domains/context/working-set/horizon.ts +38 -0
  165. package/src/domains/context/working-set/marker.ts +103 -0
  166. package/src/domains/context/working-set/path-index.ts +436 -0
  167. package/src/domains/context/working-set/payload.ts +152 -0
  168. package/src/domains/context/working-set/policies/age-horizon.ts +55 -0
  169. package/src/domains/context/working-set/policies/index.ts +21 -0
  170. package/src/domains/context/working-set/policies/structural.ts +160 -0
  171. package/src/domains/context/working-set/project.ts +132 -0
  172. package/src/domains/context/working-set/protect.ts +109 -0
  173. package/src/domains/context/working-set/recall.ts +177 -0
  174. package/src/domains/context/working-set/replay/controls.ts +112 -0
  175. package/src/domains/context/working-set/replay/load-clio.ts +199 -0
  176. package/src/domains/context/working-set/replay/metrics.ts +185 -0
  177. package/src/domains/context/working-set/replay/reference-graph.ts +79 -0
  178. package/src/domains/context/working-set/replay/report.ts +139 -0
  179. package/src/domains/context/working-set/replay/runner.ts +325 -0
  180. package/src/domains/context/working-set/replay/synthetic.ts +422 -0
  181. package/src/domains/context/working-set/replay/trace.ts +21 -0
  182. package/src/domains/context/working-set/visible.ts +54 -0
  183. package/src/domains/evidence/build.ts +112 -45
  184. package/src/domains/evidence/eval.ts +24 -7
  185. package/src/domains/evidence/index.ts +53 -0
  186. package/src/domains/evidence/ordering.ts +12 -0
  187. package/src/domains/evidence/run-trust.ts +221 -0
  188. package/src/domains/evidence/store.ts +46 -6
  189. package/src/domains/evidence/trust-status.ts +854 -0
  190. package/src/domains/evidence/types.ts +26 -0
  191. package/src/domains/middleware/memory-intervention.ts +3 -0
  192. package/src/domains/middleware/stalled-turn.ts +165 -4
  193. package/src/domains/safety/autonomy.ts +1 -1
  194. package/src/domains/safety/default-path-policy.ts +8 -0
  195. package/src/domains/safety/finish-contract.ts +4 -3
  196. package/src/domains/safety/policy-engine.ts +48 -6
  197. package/src/domains/session/compaction/compact.ts +23 -1
  198. package/src/domains/session/compaction/cut-point.ts +2 -0
  199. package/src/domains/session/compaction/tokens.ts +16 -1
  200. package/src/domains/session/context-ledger.ts +2 -0
  201. package/src/domains/session/entries.ts +107 -1
  202. package/src/domains/session/manager.ts +9 -2
  203. package/src/domains/session/migrations/index.ts +22 -3
  204. package/src/engine/acp/server.ts +3 -0
  205. package/src/engine/agent.ts +18 -1
  206. package/src/engine/session.ts +9 -3
  207. package/src/entry/orchestrator.ts +16 -4
  208. package/src/interactive/chat-loop-messages.ts +18 -6
  209. package/src/interactive/chat-panel.ts +17 -1
  210. package/src/interactive/chat-renderer.ts +30 -21
  211. package/src/interactive/context-meter.ts +10 -0
  212. package/src/interactive/context-overlay.ts +81 -6
  213. package/src/interactive/context-recall-command.ts +110 -0
  214. package/src/interactive/interactive-slash-runtime.ts +37 -1
  215. package/src/interactive/model-session-replay.ts +21 -0
  216. package/src/interactive/overlay-general-openers.ts +6 -0
  217. package/src/interactive/overlay-session-lifecycle.ts +8 -4
  218. package/src/interactive/renderers/tool-execution.ts +18 -2
  219. package/src/interactive/session-transcript.ts +2 -2
  220. package/src/interactive/slash-commands.ts +29 -2
  221. package/src/interactive/turn-context.ts +238 -88
  222. package/src/interactive/turn-middleware.ts +6 -6
  223. package/src/tools/agent-tools.ts +11 -4
  224. package/src/tools/bash.ts +144 -82
  225. package/src/tools/builtin-tool-catalog.ts +11 -5
  226. package/src/tools/context/index.ts +105 -3
  227. package/src/tools/context/surface.ts +3 -2
  228. package/src/tools/core-bootstrap.ts +21 -0
  229. package/src/tools/dispatch-runner.ts +9 -7
  230. package/src/tools/monitor.ts +28 -20
  231. package/src/tools/registry.ts +59 -7
  232. package/src/tools/result-disposition.ts +550 -0
  233. package/src/tools/result-shaping.ts +262 -19
  234. package/src/tools/safe-exec.ts +2 -0
  235. package/src/tools/verify/authoring.ts +1119 -0
  236. package/src/tools/verify/catalog.ts +346 -0
  237. package/src/tools/verify/index.ts +13 -3
  238. package/src/tools/verify/scripts.ts +135 -37
  239. package/src/tools/verify/surface.ts +9 -5
  240. package/src/tools/worker-evidence.ts +35 -12
  241. package/dist/chunk-J7CWMCQD.js +0 -255
  242. package/dist/chunk-T6YILFSB.js +0 -80
  243. package/dist/chunk-VAKQQHWR.js +0 -434
  244. package/dist/chunk-VPAYEGVX.js +0 -184
@@ -0,0 +1,221 @@
1
+ import { type GateDecisionArtifact, verifyGateDecisionArtifact } from "../dispatch/gate-decisions.js";
2
+ import type { RunEnvelope, RunReceipt } from "../dispatch/types.js";
3
+ import type { FinishContractAssessment, FinishContractEvidenceKind } from "../safety/finish-contract.js";
4
+ import { compareCodepoints as compareStrings } from "./ordering.js";
5
+ import {
6
+ adaptFinishContractCompletionStatus,
7
+ adaptGateDecisionReviewStatus,
8
+ adaptGroundedEvidenceValidationStatus,
9
+ type CanonicalTrustStatus,
10
+ composeTrustStatus,
11
+ inspectRunReceiptTrustStatus,
12
+ isTrustStatusIdentifier,
13
+ type TrustArtifactReference,
14
+ } from "./trust-status.js";
15
+ import { EVIDENCE_VERSION, type EvidenceAuditLinkedRow, type EvidenceTrustStatusFile } from "./types.js";
16
+
17
+ export interface EvidenceRunTrustSource {
18
+ envelope: RunEnvelope;
19
+ receipt: RunReceipt | null;
20
+ }
21
+
22
+ export interface BuildEvidenceTrustStatusInput {
23
+ evidenceId: string;
24
+ runSources: ReadonlyArray<EvidenceRunTrustSource>;
25
+ gateDecisions: ReadonlyArray<GateDecisionArtifact>;
26
+ auditRows: ReadonlyArray<EvidenceAuditLinkedRow>;
27
+ validationEvidence?: ReadonlyMap<string, ReadonlyArray<TrustArtifactReference>>;
28
+ }
29
+
30
+ /**
31
+ * Compose every evidence-linked trust axis at one pure boundary. Both the
32
+ * run/session and eval builders call this function, so the same authenticated
33
+ * receipt, gate decision, and exact finish-contract row cannot project
34
+ * differently merely because it was bundled through a different entry point.
35
+ */
36
+ export function buildEvidenceTrustStatusFile(input: BuildEvidenceTrustStatusInput): EvidenceTrustStatusFile {
37
+ return {
38
+ version: EVIDENCE_VERSION,
39
+ evidenceId: input.evidenceId,
40
+ projection: "canonical",
41
+ runs: input.runSources.map((source) => ({
42
+ runId: source.envelope.id,
43
+ status: composeEvidenceRunTrustStatus({
44
+ evidenceId: input.evidenceId,
45
+ source,
46
+ gateDecisions: input.gateDecisions,
47
+ auditRows: input.auditRows,
48
+ validationEvidence: input.validationEvidence?.get(source.envelope.id) ?? [],
49
+ }),
50
+ })),
51
+ };
52
+ }
53
+
54
+ interface ComposeEvidenceRunTrustInput {
55
+ evidenceId: string;
56
+ source: EvidenceRunTrustSource;
57
+ gateDecisions: ReadonlyArray<GateDecisionArtifact>;
58
+ auditRows: ReadonlyArray<EvidenceAuditLinkedRow>;
59
+ validationEvidence: ReadonlyArray<TrustArtifactReference>;
60
+ }
61
+
62
+ function composeEvidenceRunTrustStatus(input: ComposeEvidenceRunTrustInput): CanonicalTrustStatus {
63
+ const inspection = inspectRunReceiptTrustStatus(input.source.receipt, input.source.envelope);
64
+ // A receipt that was presented and rejected authenticates nothing about the
65
+ // run it names. Its integrity failure stays on `artifactIntegrity`; no other
66
+ // axis may reach a trust-granting state on the strength of that run's own
67
+ // self-reports. A missing or unchecked receipt is not a rejection.
68
+ const receiptRejected = inspection.status.artifactIntegrity.state === "failed";
69
+ let status = inspection.status;
70
+ // Only independently observed executions ground validation. The
71
+ // completion-contract audit row is the run's own self-report: it feeds
72
+ // `completionEvidence` below and nothing else. The cross-axis rules this
73
+ // enforces are listed under "composition rules" in docs/evidence-and-memory.md.
74
+ const grounded = input.validationEvidence.filter((artifact) => isTrustStatusIdentifier(artifact.id));
75
+ if (
76
+ grounded.length > 0 &&
77
+ (status.validationGrounding.state === "absent" || status.validationGrounding.state === "unknown")
78
+ ) {
79
+ status = composeTrustStatus(status, {
80
+ validationGrounding: adaptGroundedEvidenceValidationStatus({
81
+ evidenceId: input.evidenceId,
82
+ runId: input.source.envelope.id,
83
+ artifacts: grounded,
84
+ }),
85
+ });
86
+ }
87
+
88
+ const gate = latestGateDecisionForRun(input.gateDecisions, input.source.envelope.id);
89
+ if (gate !== null) {
90
+ const artifactVerification = verifyGateDecisionArtifact(gate);
91
+ const subject = gate.subjects.find((entry) => entry.runId === input.source.envelope.id);
92
+ const receipt = inspection.integrity.ok ? input.source.receipt : null;
93
+ const verification =
94
+ artifactVerification.ok &&
95
+ subject !== undefined &&
96
+ receipt?.integrity !== undefined &&
97
+ subject.digest === receipt.integrity.digest
98
+ ? ({ ok: true } as const)
99
+ : ({
100
+ ok: false,
101
+ reason: artifactVerification.ok
102
+ ? "gate subject receipt digest unavailable or mismatched"
103
+ : artifactVerification.reason,
104
+ } as const);
105
+ status = composeTrustStatus(status, {
106
+ independentReview: adaptGateDecisionReviewStatus(gate, input.source.envelope.id, verification),
107
+ });
108
+ }
109
+
110
+ const finish = latestFinishContractForRun(input.auditRows, input.source.envelope.id);
111
+ if (finish !== null) {
112
+ const completion = adaptFinishContractCompletionStatus(finish.assessment, {
113
+ sourceId: finish.sourceId,
114
+ artifacts: finish.artifacts,
115
+ });
116
+ status = composeTrustStatus(status, {
117
+ completionEvidence:
118
+ receiptRejected && completion.state === "evidenced"
119
+ ? {
120
+ state: "unknown",
121
+ source: { kind: "finish_contract", id: finish.sourceId },
122
+ authority: { kind: "clio", id: "finish-contract" },
123
+ artifacts: finish.artifacts,
124
+ }
125
+ : completion,
126
+ });
127
+ }
128
+ return status;
129
+ }
130
+
131
+ function latestGateDecisionForRun(
132
+ decisions: ReadonlyArray<GateDecisionArtifact>,
133
+ runId: string,
134
+ ): GateDecisionArtifact | null {
135
+ return (
136
+ decisions
137
+ .filter((decision) => decision.subjects.some((subject) => subject.runId === runId))
138
+ .sort(
139
+ (left, right) =>
140
+ compareStrings(left.createdAt, right.createdAt) || left.cycle - right.cycle || compareStrings(left.id, right.id),
141
+ )
142
+ .at(-1) ?? null
143
+ );
144
+ }
145
+
146
+ interface EvidenceFinishContractProjection {
147
+ sourceId: string;
148
+ assessment: FinishContractAssessment;
149
+ artifacts: TrustArtifactReference[];
150
+ }
151
+
152
+ function latestFinishContractForRun(
153
+ rows: ReadonlyArray<EvidenceAuditLinkedRow>,
154
+ runId: string,
155
+ ): EvidenceFinishContractProjection | null {
156
+ const candidates = rows
157
+ .filter((row) => row.auditKind === "completion_contract" && row.runId === runId && row.confidence === "exact")
158
+ .flatMap((row) => {
159
+ const assessment = finishContractAssessmentFromAudit(row.row);
160
+ if (assessment === null) return [];
161
+ const correlationId = readOptionalString(row.row.correlationId) ?? `${runId}:completion-contract`;
162
+ // A malformed row is dropped from the trust projection rather than
163
+ // thrown out of the whole forensic build. `readAuditRows` already
164
+ // reports the malformed line, so the bundle stays honest about it.
165
+ if (!isTrustStatusIdentifier(correlationId)) return [];
166
+ const turnId = readOptionalString(row.row.turnId);
167
+ const artifacts: TrustArtifactReference[] = [
168
+ { kind: "finish_contract_evidence", id: correlationId },
169
+ ...(turnId === null || !isTrustStatusIdentifier(turnId) ? [] : [{ kind: "session_entry" as const, id: turnId }]),
170
+ ];
171
+ return [{ sourceId: correlationId, assessment, artifacts, timestamp: row.ts ?? "" }];
172
+ })
173
+ .sort(
174
+ (left, right) => compareStrings(left.timestamp, right.timestamp) || compareStrings(left.sourceId, right.sourceId),
175
+ );
176
+ const latest = candidates.at(-1);
177
+ return latest === undefined
178
+ ? null
179
+ : { sourceId: latest.sourceId, assessment: latest.assessment, artifacts: latest.artifacts };
180
+ }
181
+
182
+ function finishContractAssessmentFromAudit(row: Record<string, unknown>): FinishContractAssessment | null {
183
+ const reason = readOptionalString(row.reason);
184
+ const decision = readOptionalString(row.decision);
185
+ const mutatedPaths = Array.isArray(row.mutatedPaths)
186
+ ? row.mutatedPaths.filter((entry): entry is string => typeof entry === "string")
187
+ : [];
188
+ const turnId = readOptionalString(row.turnId);
189
+ const evidenceKinds = Array.isArray(row.evidenceKinds) ? row.evidenceKinds.filter(isFinishContractEvidenceKind) : [];
190
+ const evidence = evidenceKinds.map((kind) => ({
191
+ kind,
192
+ summary: "recorded by the finish contract",
193
+ ...(turnId === null ? {} : { turnId }),
194
+ }));
195
+ if (decision === "ok" && reason === "no_mutation") {
196
+ return { kind: "ok", reason, evidence: [], mutatedPaths };
197
+ }
198
+ if (decision === "ok" && reason === "validation_evidence") {
199
+ return { kind: "ok", reason, evidence, mutatedPaths };
200
+ }
201
+ if (decision === "ok" && reason === "explicit_limitation") {
202
+ return { kind: "ok", reason, evidence: [], mutatedPaths };
203
+ }
204
+ if (decision === "engage" && reason === "unvalidated_mutation") {
205
+ return { kind: "engage", reason, message: "finish contract engaged", evidence: [], mutatedPaths };
206
+ }
207
+ return null;
208
+ }
209
+
210
+ function isFinishContractEvidenceKind(value: unknown): value is FinishContractEvidenceKind {
211
+ return value === "validation_command" || value === "protected_artifact" || value === "dispatch_receipt";
212
+ }
213
+
214
+ /**
215
+ * A blank or whitespace-only optional field is absent, not an identifier. The
216
+ * `??` fallbacks below depend on it, and an empty string would otherwise reach
217
+ * `normalizeArtifactReference` and abort the entire bundle.
218
+ */
219
+ function readOptionalString(value: unknown): string | null {
220
+ return typeof value === "string" && value.trim().length > 0 ? value : null;
221
+ }
@@ -2,8 +2,16 @@ import { readdir, readFile } from "node:fs/promises";
2
2
  import { join } from "node:path";
3
3
  import { assertSafeId } from "../../core/safe-id.js";
4
4
  import { type GateDecisionArtifact, verifyGateDecisionArtifact } from "../dispatch/index.js";
5
+ import { compareCodepoints as compareStrings } from "./ordering.js";
5
6
  import { hasRunProvenance, type RunProvenanceView, runProvenanceFromUnknown } from "./provenance.js";
6
- import type { EvidenceFinding, EvidenceFindingsFile, EvidenceInspectable, EvidenceOverview } from "./types.js";
7
+ import { normalizeTrustStatus } from "./trust-status.js";
8
+ import type {
9
+ EvidenceFinding,
10
+ EvidenceFindingsFile,
11
+ EvidenceInspectable,
12
+ EvidenceOverview,
13
+ EvidenceTrustStatusView,
14
+ } from "./types.js";
7
15
 
8
16
  export const EVIDENCE_FILES = [
9
17
  "overview.json",
@@ -14,6 +22,7 @@ export const EVIDENCE_FILES = [
14
22
  "audit-linked.jsonl",
15
23
  "receipt.json",
16
24
  "gate-decisions.json",
25
+ "trust-status.json",
17
26
  "protected-artifacts.json",
18
27
  "findings.json",
19
28
  "findings.md",
@@ -69,7 +78,42 @@ export async function inspectEvidence(dataDir: string, evidenceId: string): Prom
69
78
  }
70
79
  const findingsParsed = parseJson(findingsRaw, `${evidenceId}/findings.json`);
71
80
  const findings = parseFindingsFile(findingsParsed, `${evidenceId}/findings.json`);
72
- return { overview, findings };
81
+ return { overview, findings, trustStatus: await loadEvidenceTrustStatus(dataDir, evidenceId) };
82
+ }
83
+
84
+ /** Load a canonical projection, or make the missing historical boundary explicit. */
85
+ export async function loadEvidenceTrustStatus(dataDir: string, evidenceId: string): Promise<EvidenceTrustStatusView> {
86
+ let raw: string;
87
+ try {
88
+ raw = await readFile(join(evidenceDirectory(dataDir, evidenceId), "trust-status.json"), "utf8");
89
+ } catch (error) {
90
+ if (isMissingFile(error)) {
91
+ return { version: 1, evidenceId, projection: "historical_format", runs: [] };
92
+ }
93
+ throw error;
94
+ }
95
+ const source = `${evidenceId}/trust-status.json`;
96
+ const parsed = parseJson(raw, source);
97
+ if (!isRecord(parsed)) throw new Error(`${source}: expected object`);
98
+ if (parsed.version !== 1) throw new Error(`${source}: expected version 1`);
99
+ if (readString(parsed, source, "evidenceId") !== evidenceId) {
100
+ throw new Error(`${source}.evidenceId: expected ${evidenceId}`);
101
+ }
102
+ if (parsed.projection !== "canonical") throw new Error(`${source}.projection: expected canonical`);
103
+ if (!Array.isArray(parsed.runs)) throw new Error(`${source}.runs: expected array`);
104
+ return {
105
+ version: 1,
106
+ evidenceId,
107
+ projection: "canonical",
108
+ runs: parsed.runs.map((entry, index) => {
109
+ const entrySource = `${source}.runs[${index}]`;
110
+ if (!isRecord(entry)) throw new Error(`${entrySource}: expected object`);
111
+ return {
112
+ runId: readString(entry, entrySource, "runId"),
113
+ status: normalizeTrustStatus(entry.status),
114
+ };
115
+ }),
116
+ };
73
117
  }
74
118
 
75
119
  /** A bundle run whose receipt carries at least one provenance field set. */
@@ -282,10 +326,6 @@ function isRecord(value: unknown): value is Record<string, unknown> {
282
326
  return typeof value === "object" && value !== null && !Array.isArray(value);
283
327
  }
284
328
 
285
- function compareStrings(a: string, b: string): number {
286
- return a.localeCompare(b);
287
- }
288
-
289
329
  export function findingsFile(evidenceId: string, findings: EvidenceFinding[]): EvidenceFindingsFile {
290
330
  return { version: 1, evidenceId, findings };
291
331
  }