cool-workflow 0.1.78

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 (193) hide show
  1. package/.claude-plugin/plugin.json +20 -0
  2. package/.codex-plugin/mcp.json +10 -0
  3. package/.codex-plugin/plugin.json +38 -0
  4. package/.mcp.json +10 -0
  5. package/LICENSE +24 -0
  6. package/README.md +638 -0
  7. package/apps/architecture-review/app.json +51 -0
  8. package/apps/architecture-review/workflow.js +116 -0
  9. package/apps/end-to-end-golden-path/app.json +30 -0
  10. package/apps/end-to-end-golden-path/workflow.js +33 -0
  11. package/apps/pr-review-fix-ci/app.json +59 -0
  12. package/apps/pr-review-fix-ci/workflow.js +90 -0
  13. package/apps/release-cut/app.json +54 -0
  14. package/apps/release-cut/workflow.js +82 -0
  15. package/apps/research-synthesis/app.json +50 -0
  16. package/apps/research-synthesis/workflow.js +76 -0
  17. package/apps/workflow-app-framework-demo/app.json +29 -0
  18. package/apps/workflow-app-framework-demo/workflow.js +44 -0
  19. package/dist/agent-config.js +223 -0
  20. package/dist/candidate-scoring.js +715 -0
  21. package/dist/capability-core.js +630 -0
  22. package/dist/capability-dispatcher.js +86 -0
  23. package/dist/capability-registry.js +523 -0
  24. package/dist/cli.js +1276 -0
  25. package/dist/collaboration.js +727 -0
  26. package/dist/commit.js +570 -0
  27. package/dist/contract-migration.js +234 -0
  28. package/dist/coordinator.js +1163 -0
  29. package/dist/daemon.js +44 -0
  30. package/dist/dispatch.js +201 -0
  31. package/dist/drive.js +503 -0
  32. package/dist/error-feedback.js +415 -0
  33. package/dist/evidence-grounding.js +179 -0
  34. package/dist/evidence-reasoning.js +733 -0
  35. package/dist/execution-backend.js +1279 -0
  36. package/dist/harness.js +61 -0
  37. package/dist/mcp-server.js +1615 -0
  38. package/dist/multi-agent-eval.js +857 -0
  39. package/dist/multi-agent-host.js +764 -0
  40. package/dist/multi-agent-operator-ux.js +537 -0
  41. package/dist/multi-agent-trust.js +366 -0
  42. package/dist/multi-agent.js +1173 -0
  43. package/dist/node-snapshot.js +270 -0
  44. package/dist/observability.js +922 -0
  45. package/dist/operator-ux.js +971 -0
  46. package/dist/orchestrator/audit-operations.js +182 -0
  47. package/dist/orchestrator/candidate-operations.js +117 -0
  48. package/dist/orchestrator/cli-options.js +288 -0
  49. package/dist/orchestrator/collaboration-operations.js +86 -0
  50. package/dist/orchestrator/feedback-operations.js +81 -0
  51. package/dist/orchestrator/host-operations.js +78 -0
  52. package/dist/orchestrator/lifecycle-operations.js +462 -0
  53. package/dist/orchestrator/migration-operations.js +44 -0
  54. package/dist/orchestrator/multi-agent-operations.js +362 -0
  55. package/dist/orchestrator/report.js +369 -0
  56. package/dist/orchestrator/topology-operations.js +84 -0
  57. package/dist/orchestrator.js +874 -0
  58. package/dist/pipeline-contract.js +92 -0
  59. package/dist/pipeline-runner.js +285 -0
  60. package/dist/reclamation.js +882 -0
  61. package/dist/result-normalize.js +194 -0
  62. package/dist/run-export.js +64 -0
  63. package/dist/run-registry.js +1347 -0
  64. package/dist/run-state-schema.js +67 -0
  65. package/dist/sandbox-profile.js +471 -0
  66. package/dist/scheduler.js +266 -0
  67. package/dist/scheduling.js +184 -0
  68. package/dist/schema-validate.js +98 -0
  69. package/dist/state-explosion.js +1213 -0
  70. package/dist/state-migrations.js +463 -0
  71. package/dist/state-node.js +301 -0
  72. package/dist/state.js +308 -0
  73. package/dist/telemetry-attestation.js +156 -0
  74. package/dist/telemetry-ledger.js +145 -0
  75. package/dist/topology.js +527 -0
  76. package/dist/triggers.js +159 -0
  77. package/dist/trust-audit.js +475 -0
  78. package/dist/types/blackboard.js +2 -0
  79. package/dist/types/boundary.js +29 -0
  80. package/dist/types/candidate.js +2 -0
  81. package/dist/types/collaboration.js +2 -0
  82. package/dist/types/core.js +2 -0
  83. package/dist/types/drive.js +10 -0
  84. package/dist/types/error-feedback.js +2 -0
  85. package/dist/types/evidence-reasoning.js +2 -0
  86. package/dist/types/execution-backend.js +2 -0
  87. package/dist/types/multi-agent.js +2 -0
  88. package/dist/types/observability.js +2 -0
  89. package/dist/types/pipeline.js +2 -0
  90. package/dist/types/reclamation.js +8 -0
  91. package/dist/types/result.js +2 -0
  92. package/dist/types/run-registry.js +2 -0
  93. package/dist/types/run.js +2 -0
  94. package/dist/types/sandbox.js +2 -0
  95. package/dist/types/schedule.js +2 -0
  96. package/dist/types/state-node.js +2 -0
  97. package/dist/types/topology.js +2 -0
  98. package/dist/types/trust.js +2 -0
  99. package/dist/types/workbench.js +2 -0
  100. package/dist/types/worker.js +2 -0
  101. package/dist/types/workflow-app.js +2 -0
  102. package/dist/types.js +43 -0
  103. package/dist/verifier-registry.js +46 -0
  104. package/dist/verifier.js +78 -0
  105. package/dist/version.js +8 -0
  106. package/dist/workbench-host.js +172 -0
  107. package/dist/workbench.js +190 -0
  108. package/dist/worker-isolation.js +1028 -0
  109. package/dist/workflow-api.js +98 -0
  110. package/dist/workflow-app-framework.js +626 -0
  111. package/docs/agent-delegation-drive.7.md +190 -0
  112. package/docs/agent-framework.md +176 -0
  113. package/docs/candidate-scoring.7.md +106 -0
  114. package/docs/canonical-workflow-apps.7.md +137 -0
  115. package/docs/capability-topology-registry.7.md +168 -0
  116. package/docs/cli-mcp-parity.7.md +373 -0
  117. package/docs/contract-migration-tooling.7.md +123 -0
  118. package/docs/control-plane-scheduling.7.md +110 -0
  119. package/docs/coordinator-blackboard.7.md +183 -0
  120. package/docs/dogfood/architecture-review-cool-workflow.md +16 -0
  121. package/docs/dogfood-one-real-repo.7.md +168 -0
  122. package/docs/durable-state-and-locking.7.md +107 -0
  123. package/docs/end-to-end-golden-path.7.md +117 -0
  124. package/docs/error-feedback.7.md +153 -0
  125. package/docs/evidence-adoption-reasoning-chain.7.md +270 -0
  126. package/docs/execution-backends.7.md +300 -0
  127. package/docs/getting-started.md +99 -0
  128. package/docs/index.md +41 -0
  129. package/docs/mcp-app-surface.7.md +235 -0
  130. package/docs/multi-agent-cli-mcp-surface.7.md +265 -0
  131. package/docs/multi-agent-eval-replay-harness.7.md +302 -0
  132. package/docs/multi-agent-operator-ux.7.md +314 -0
  133. package/docs/multi-agent-runtime-core.7.md +231 -0
  134. package/docs/multi-agent-topologies.7.md +103 -0
  135. package/docs/multi-agent-trust-policy-audit.7.md +154 -0
  136. package/docs/node-snapshot-diff-replay.7.md +135 -0
  137. package/docs/observability-cost-accounting.7.md +194 -0
  138. package/docs/operator-ux.7.md +180 -0
  139. package/docs/pipeline-runner.7.md +136 -0
  140. package/docs/project-index.md +261 -0
  141. package/docs/real-execution-backends.7.md +142 -0
  142. package/docs/release-and-migration.7.md +280 -0
  143. package/docs/release-tooling.7.md +159 -0
  144. package/docs/routines.md +48 -0
  145. package/docs/run-registry-control-plane.7.md +312 -0
  146. package/docs/run-retention-reclamation.7.md +191 -0
  147. package/docs/sandbox-profiles.7.md +137 -0
  148. package/docs/scheduled-tasks.md +80 -0
  149. package/docs/security-trust-hardening.7.md +117 -0
  150. package/docs/state-explosion-management.7.md +264 -0
  151. package/docs/state-node.7.md +96 -0
  152. package/docs/team-collaboration.7.md +207 -0
  153. package/docs/unix-principles.md +192 -0
  154. package/docs/verifier-gated-commit.7.md +140 -0
  155. package/docs/web-desktop-workbench.7.md +215 -0
  156. package/docs/worker-isolation.7.md +167 -0
  157. package/docs/workflow-app-framework.7.md +274 -0
  158. package/manifest/README.md +43 -0
  159. package/manifest/plugin.manifest.json +316 -0
  160. package/manifest/pricing.policy.json +14 -0
  161. package/package.json +79 -0
  162. package/scripts/agents/claude-p-agent.js +104 -0
  163. package/scripts/agents/claude-p-agent.sh +9 -0
  164. package/scripts/agents/cw-attest-keygen.js +55 -0
  165. package/scripts/agents/cw-attest-wrap.js +143 -0
  166. package/scripts/block-unapproved-tag.sh +39 -0
  167. package/scripts/bump-version.js +249 -0
  168. package/scripts/canonical-apps.js +171 -0
  169. package/scripts/cw.js +4 -0
  170. package/scripts/dist-drift-check.js +79 -0
  171. package/scripts/dogfood-architecture-review.js +237 -0
  172. package/scripts/dogfood-release.js +624 -0
  173. package/scripts/forward-ref-docs.js +73 -0
  174. package/scripts/gen-manifests.js +232 -0
  175. package/scripts/golden-path.js +300 -0
  176. package/scripts/mcp-server.js +4 -0
  177. package/scripts/new-feature.js +121 -0
  178. package/scripts/parity-check.js +213 -0
  179. package/scripts/release-check.js +118 -0
  180. package/scripts/release-flow.js +272 -0
  181. package/scripts/release-gate.sh +85 -0
  182. package/scripts/sync-project-index.js +387 -0
  183. package/scripts/validate-run-state-schema.js +126 -0
  184. package/scripts/verify-container-selfref.js +64 -0
  185. package/scripts/version-sync-check.js +237 -0
  186. package/skills/cool-workflow/SKILL.md +162 -0
  187. package/skills/cool-workflow/references/commands.md +282 -0
  188. package/tsconfig.json +16 -0
  189. package/ui/workbench/app.css +76 -0
  190. package/ui/workbench/app.js +159 -0
  191. package/ui/workbench/index.html +32 -0
  192. package/workflows/architecture-review.workflow.js +84 -0
  193. package/workflows/research-synthesis.workflow.js +47 -0
@@ -0,0 +1,733 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.EVIDENCE_REASONING_SCHEMA_VERSION = void 0;
7
+ exports.buildEvidenceReasoningReport = buildEvidenceReasoningReport;
8
+ exports.reasoningCriticalNodeIds = reasoningCriticalNodeIds;
9
+ exports.reasoningDir = reasoningDir;
10
+ exports.refreshEvidenceReasoning = refreshEvidenceReasoning;
11
+ exports.loadEvidenceReasoningIndex = loadEvidenceReasoningIndex;
12
+ exports.showEvidenceReasoning = showEvidenceReasoning;
13
+ exports.normalizeEvidenceReasoningForEval = normalizeEvidenceReasoningForEval;
14
+ exports.formatEvidenceReasoningReport = formatEvidenceReasoningReport;
15
+ const node_crypto_1 = __importDefault(require("node:crypto"));
16
+ const node_fs_1 = __importDefault(require("node:fs"));
17
+ const node_path_1 = __importDefault(require("node:path"));
18
+ const state_1 = require("./state");
19
+ const multi_agent_operator_ux_1 = require("./multi-agent-operator-ux");
20
+ const trust_audit_1 = require("./trust-audit");
21
+ const multi_agent_trust_1 = require("./multi-agent-trust");
22
+ // ---------------------------------------------------------------------------
23
+ // Evidence Adoption Reasoning Chain (v0.1.26)
24
+ //
25
+ // This module DERIVES the "why" behind each evidence adoption decision from
26
+ // existing run state. It is the mechanism half of the FreeBSD mechanism/policy
27
+ // split: it captures, fingerprints and renders the recorded rationale; it never
28
+ // decides whether a rationale is *sufficient* (that stays with the verifier /
29
+ // role policy). It never mutates source-of-truth records and never fabricates a
30
+ // rationale — an adoption whose reason cannot be traced renders `unexplained`.
31
+ //
32
+ // The persisted view mirrors the v0.1.25 state-explosion summaries: a derived,
33
+ // versioned, provenance-backed index under .cw/runs/<id>/reasoning/ with a
34
+ // sourceFingerprint and valid|stale|absent freshness, refreshable, never
35
+ // authoritative over raw state.
36
+ // ---------------------------------------------------------------------------
37
+ exports.EVIDENCE_REASONING_SCHEMA_VERSION = 1;
38
+ // ---------------------------------------------------------------------------
39
+ // Derivation
40
+ // ---------------------------------------------------------------------------
41
+ function buildEvidenceReasoningReport(run, options = {}) {
42
+ const operator = (0, multi_agent_operator_ux_1.summarizeMultiAgentOperator)(run);
43
+ const scores = readAllScores(run);
44
+ const auditEvents = (0, trust_audit_1.listTrustAuditEvents)(run);
45
+ const counterfactuals = deriveCounterfactuals(run, scores);
46
+ const chains = operator.evidence
47
+ .map((evidence) => buildChain(run, evidence, { scores, auditEvents, counterfactuals }))
48
+ .sort((left, right) => statusRank(left.evidenceStatus) - statusRank(right.evidenceStatus) || left.id.localeCompare(right.id));
49
+ const totals = summarizeTotals(chains);
50
+ const currentFingerprint = fingerprintChains(chains);
51
+ const persisted = options.index;
52
+ let status = persisted ? "valid" : "absent";
53
+ if (persisted && persisted.sourceFingerprint !== currentFingerprint)
54
+ status = "stale";
55
+ const nextAction = status === "stale" || status === "absent"
56
+ ? `node scripts/cw.js multi-agent reasoning ${run.id} --refresh`
57
+ : totals.unexplained > 0
58
+ ? `node scripts/cw.js multi-agent reasoning ${run.id} --json`
59
+ : `node scripts/cw.js multi-agent evidence ${run.id} --json`;
60
+ return {
61
+ schemaVersion: exports.EVIDENCE_REASONING_SCHEMA_VERSION,
62
+ runId: run.id,
63
+ generatedAt: new Date().toISOString(),
64
+ freshness: {
65
+ status,
66
+ persistedFingerprint: persisted?.sourceFingerprint,
67
+ currentFingerprint
68
+ },
69
+ sourceFingerprint: currentFingerprint,
70
+ totals,
71
+ chains,
72
+ nextAction
73
+ };
74
+ }
75
+ function buildChain(run, evidence, context) {
76
+ const steps = [];
77
+ const sourceRecordIds = new Set();
78
+ const note = (id) => {
79
+ if (id)
80
+ sourceRecordIds.add(id);
81
+ };
82
+ // Walk the adopters/rejecters recorded on the evidence row and classify each
83
+ // by the gate it represents. We never invent gates: a step exists only when a
84
+ // real adopter/decision record references this evidence.
85
+ const adopters = [...evidence.adoptedBy, ...evidence.rejectedBy];
86
+ for (const scoreId of evidence.scoreIds) {
87
+ const score = context.scores.get(scoreId);
88
+ note(scoreId);
89
+ steps.push(buildScoreStep(run, evidence, score, scoreId, context));
90
+ }
91
+ for (const selectionId of evidence.selectionIds) {
92
+ const selection = (run.candidateSelections || []).find((entry) => entry.id === selectionId);
93
+ note(selectionId);
94
+ steps.push(buildSelectionStep(run, evidence, selection, selectionId, context));
95
+ }
96
+ for (const commitId of evidence.commitIds) {
97
+ const commit = (run.commits || []).find((entry) => entry.id === commitId);
98
+ note(commitId);
99
+ const commitStep = buildCommitStep(run, evidence, commit, commitId);
100
+ steps.push(commitStep);
101
+ const verifierStep = buildVerifierStep(run, evidence, commit, commitId);
102
+ if (verifierStep)
103
+ steps.push(verifierStep);
104
+ }
105
+ // Fanin and coordinator-decision adopters (blackboard -> fanin consolidation).
106
+ const faninIds = new Set((run.multiAgent?.fanins || []).map((entry) => entry.id));
107
+ const decisions = run.blackboard?.decisions || [];
108
+ for (const adopter of unique(adopters)) {
109
+ if (faninIds.has(adopter)) {
110
+ note(adopter);
111
+ steps.push(buildFaninStep(run, evidence, adopter, decisions));
112
+ }
113
+ else {
114
+ const decision = decisions.find((entry) => entry.id === adopter);
115
+ if (decision) {
116
+ note(decision.id);
117
+ steps.push(buildDecisionStep(run, evidence, decision));
118
+ }
119
+ }
120
+ }
121
+ // An adopted/rejected item that produced no decision-gate step has a known
122
+ // WHAT but no recorded WHY: fail closed with an explicit unexplained step so
123
+ // the gap is visible rather than silently treated as adopted.
124
+ if (!steps.length && isDecisionStatus(evidence.status)) {
125
+ steps.push(buildUnexplainedStep(evidence));
126
+ }
127
+ const evidenceStatus = mapStatus(evidence.status);
128
+ const rationaleStatus = rollupRationale(steps, evidenceStatus);
129
+ const unexplainedReasons = steps
130
+ .filter((step) => step.rationale.status === "unexplained")
131
+ .map((step) => `${step.gate}: no recorded rationale for ${step.decision} adoption`);
132
+ for (const ref of [evidence.sourceId, ...evidence.candidateIds])
133
+ note(ref);
134
+ return {
135
+ schemaVersion: exports.EVIDENCE_REASONING_SCHEMA_VERSION,
136
+ id: evidence.id,
137
+ ref: evidence.ref,
138
+ evidenceStatus,
139
+ rationaleStatus,
140
+ sourceKind: evidence.sourceKind,
141
+ sourceId: evidence.sourceId,
142
+ steps,
143
+ sourceRecordIds: [...sourceRecordIds].filter(Boolean).sort(),
144
+ unexplainedReasons
145
+ };
146
+ }
147
+ // ---------------------------------------------------------------------------
148
+ // Per-gate steps
149
+ // ---------------------------------------------------------------------------
150
+ function buildScoreStep(run, evidence, score, scoreId, context) {
151
+ const decision = score?.verdict === "fail" ? "rejected" : "adopted";
152
+ const judge = context.auditEvents.find((event) => event.kind === "judge.rationale" &&
153
+ event.decision === "accepted" &&
154
+ (event.scoreId === scoreId || (!event.scoreId && event.candidateId && evidence.candidateIds.includes(event.candidateId))));
155
+ const rationaleText = score?.notes || judgeRationaleText(judge);
156
+ const rationale = rationaleText
157
+ ? {
158
+ status: "explained",
159
+ text: truncate(rationaleText),
160
+ sourceKind: score?.notes ? "score-notes" : "judge-rationale",
161
+ sourceId: score?.notes ? scoreId : judge?.id,
162
+ scoreCriteria: score?.criteria,
163
+ scoreDelta: context.counterfactuals.bestRejectedNormalized !== undefined && score
164
+ ? round(score.normalized - context.counterfactuals.bestRejectedNormalized)
165
+ : undefined
166
+ }
167
+ : unexplainedRationale();
168
+ // AUTHORITY: the judge role that authored the rationale, when recorded; the
169
+ // host that mechanically wrote the score is the fallback actor.
170
+ const auditIds = unique([...collectAuditIds(score), ...(judge ? [judge.id] : [])]);
171
+ return {
172
+ gate: "candidate-score",
173
+ decision,
174
+ basis: basisFor(evidence, { auditEventIds: auditIds, evidenceRefs: scoreEvidenceRefs(score) }),
175
+ authority: roleAuthority(run, judge?.agentRoleId || score?.scorer, judge ? judge.decision === "accepted" : undefined),
176
+ rationale,
177
+ counterfactuals: decision === "adopted" ? context.counterfactuals.forScoreGate : []
178
+ };
179
+ }
180
+ function buildSelectionStep(run, evidence, selection, selectionId, context) {
181
+ const rationaleText = selection?.reason;
182
+ const acceptance = selection?.acceptanceRationale;
183
+ const rationale = rationaleText
184
+ ? {
185
+ status: "explained",
186
+ text: truncate(rationaleText),
187
+ sourceKind: "selection-reason",
188
+ sourceId: selectionId,
189
+ scoreCriteria: acceptance?.scoreCriteria,
190
+ judgeRationaleIds: acceptance?.judgeRationaleIds,
191
+ panelDecisionId: acceptance?.panelDecisionId
192
+ }
193
+ : acceptance
194
+ ? {
195
+ status: "explained",
196
+ text: `commit gate ${acceptance.commitGateResult || "recorded"} with ${acceptance.evidenceCount} evidence ref(s)`,
197
+ sourceKind: "acceptance-rationale",
198
+ sourceId: selectionId,
199
+ scoreCriteria: acceptance.scoreCriteria,
200
+ judgeRationaleIds: acceptance.judgeRationaleIds,
201
+ panelDecisionId: acceptance.panelDecisionId
202
+ }
203
+ : unexplainedRationale();
204
+ // AUTHORITY: the chair role recorded as the author of the candidate-synthesis
205
+ // coordinator decision for this selection; the host is the fallback actor.
206
+ const synthesis = (run.blackboard?.decisions || []).find((entry) => entry.kind === "candidate-synthesis" && entry.subjectIds.includes(selectionId) && entry.author?.kind === "role");
207
+ return {
208
+ gate: "selection",
209
+ decision: "adopted",
210
+ basis: basisFor(evidence, {
211
+ auditEventIds: acceptance?.auditEventIds || [],
212
+ evidenceRefs: (selection?.evidence || []).map(evidenceRef).filter(Boolean)
213
+ }),
214
+ authority: roleAuthority(run, synthesis?.author?.id || selection?.selectedBy, true),
215
+ rationale,
216
+ counterfactuals: context.counterfactuals.forSelectionGate
217
+ };
218
+ }
219
+ function buildCommitStep(run, evidence, commit, commitId) {
220
+ const decision = commit?.verifierGated ? "adopted" : "pending";
221
+ const rationale = commit?.reason
222
+ ? {
223
+ status: "explained",
224
+ text: truncate(commit.reason),
225
+ sourceKind: "commit-reason",
226
+ sourceId: commitId
227
+ }
228
+ : decision === "adopted"
229
+ ? unexplainedRationale()
230
+ : { status: "not-applicable" };
231
+ return {
232
+ gate: "commit",
233
+ decision,
234
+ basis: basisFor(evidence, {
235
+ auditEventIds: commit?.acceptanceRationale?.auditEventIds || [],
236
+ evidenceRefs: (commit?.evidence || []).map(evidenceRef).filter(Boolean)
237
+ }),
238
+ authority: {
239
+ actor: commitId,
240
+ actorKind: "runtime",
241
+ allowed: commit?.verifierGated
242
+ },
243
+ rationale,
244
+ counterfactuals: []
245
+ };
246
+ }
247
+ function buildVerifierStep(run, evidence, commit, commitId) {
248
+ const verifierNodeId = commit?.verifierNodeId;
249
+ if (!verifierNodeId)
250
+ return undefined;
251
+ const gateResult = commit?.acceptanceRationale?.commitGateResult;
252
+ return {
253
+ gate: "verifier",
254
+ decision: commit?.verifierGated ? "adopted" : "pending",
255
+ basis: basisFor(evidence, { auditEventIds: [], evidenceRefs: [] }),
256
+ authority: { actor: verifierNodeId, actorKind: "verifier", allowed: commit?.verifierGated },
257
+ rationale: gateResult
258
+ ? { status: "explained", text: `verifier commit gate ${gateResult}`, sourceKind: "acceptance-rationale", sourceId: commitId }
259
+ : commit?.verifierGated
260
+ ? { status: "explained", text: "verifier-gated commit recorded", sourceKind: "commit-reason", sourceId: commitId }
261
+ : { status: "not-applicable" },
262
+ counterfactuals: []
263
+ };
264
+ }
265
+ function buildFaninStep(run, evidence, faninId, decisions) {
266
+ const fanin = (run.multiAgent?.fanins || []).find((entry) => entry.id === faninId);
267
+ const readiness = decisions.find((entry) => entry.kind === "fanin-readiness" && entry.subjectIds.includes(faninId));
268
+ const adopted = evidence.adoptedBy.includes(faninId);
269
+ const decision = adopted ? "adopted" : "pending";
270
+ let rationale;
271
+ if (readiness?.reason) {
272
+ rationale = { status: "explained", text: truncate(readiness.reason), sourceKind: "coordinator-decision", sourceId: readiness.id };
273
+ }
274
+ else if (fanin && fanin.verifierReady && coverageComplete(fanin, evidence)) {
275
+ rationale = {
276
+ status: "explained",
277
+ text: `fanin ${faninId} ready: required evidence covered under "${fanin.strategy}" strategy`,
278
+ sourceKind: "coordinator-decision",
279
+ sourceId: faninId
280
+ };
281
+ }
282
+ else if (fanin && fanin.blockedReasons.length) {
283
+ rationale = { status: "explained", text: truncate(fanin.blockedReasons[0]), sourceKind: "coordinator-decision", sourceId: faninId };
284
+ }
285
+ else {
286
+ rationale = decision === "adopted" ? unexplainedRationale() : { status: "not-applicable" };
287
+ }
288
+ return {
289
+ gate: "fanin",
290
+ decision,
291
+ basis: basisFor(evidence, { auditEventIds: [], evidenceRefs: [] }),
292
+ authority: { actor: faninId, actorKind: "coordinator", allowed: adopted },
293
+ rationale,
294
+ counterfactuals: []
295
+ };
296
+ }
297
+ function buildDecisionStep(run, evidence, decision) {
298
+ const status = mapDecisionOutcome(decision.outcome);
299
+ return {
300
+ gate: "fanin",
301
+ decision: status,
302
+ basis: basisFor(evidence, { auditEventIds: [], evidenceRefs: decision.evidenceRefs || [] }),
303
+ authority: {
304
+ actor: decision.author?.id || decision.id,
305
+ actorKind: authorKind(decision.author?.kind),
306
+ allowed: decision.outcome === "accepted" || decision.outcome === "ready"
307
+ },
308
+ rationale: decision.reason
309
+ ? { status: "explained", text: truncate(decision.reason), sourceKind: "coordinator-decision", sourceId: decision.id }
310
+ : isDecisionStatus(status)
311
+ ? unexplainedRationale()
312
+ : { status: "not-applicable" },
313
+ counterfactuals: []
314
+ };
315
+ }
316
+ function buildUnexplainedStep(evidence) {
317
+ // The item is marked adopted/rejected but no decision record carries a reason.
318
+ // Render the gap explicitly (fail closed) rather than inferring a rationale.
319
+ return {
320
+ gate: "fanin",
321
+ decision: mapStatus(evidence.status),
322
+ basis: basisFor(evidence, { auditEventIds: [], evidenceRefs: [] }),
323
+ authority: {
324
+ actor: evidence.adoptedBy[0] || evidence.rejectedBy[0] || evidence.sourceId,
325
+ actorKind: actorKindForSource(evidence.sourceKind),
326
+ allowed: evidence.status === "adopted"
327
+ },
328
+ rationale: evidence.reason
329
+ ? { status: "explained", text: truncate(evidence.reason), sourceKind: "coordinator-decision", sourceId: evidence.sourceId }
330
+ : unexplainedRationale(),
331
+ counterfactuals: []
332
+ };
333
+ }
334
+ // ---------------------------------------------------------------------------
335
+ // Counterfactuals
336
+ // ---------------------------------------------------------------------------
337
+ function deriveCounterfactuals(run, scores) {
338
+ const forScoreGate = [];
339
+ const forSelectionGate = [];
340
+ let bestRejectedNormalized;
341
+ for (const candidate of run.candidates || []) {
342
+ if (candidate.status === "rejected" || candidate.status === "failed") {
343
+ forSelectionGate.push({
344
+ ref: candidate.id,
345
+ kind: "candidate",
346
+ status: candidate.status === "failed" ? "rejected" : "rejected",
347
+ reason: candidate.feedbackIds[0] ? `see feedback ${candidate.feedbackIds[0]}` : `candidate ${candidate.id} ${candidate.status}`
348
+ });
349
+ for (const scoreId of candidate.scores || []) {
350
+ const score = scores.get(scoreId);
351
+ if (score && (bestRejectedNormalized === undefined || score.normalized > bestRejectedNormalized)) {
352
+ bestRejectedNormalized = score.normalized;
353
+ }
354
+ }
355
+ }
356
+ }
357
+ for (const [scoreId, score] of scores) {
358
+ if (score.verdict === "fail") {
359
+ forScoreGate.push({
360
+ ref: scoreId,
361
+ kind: "score",
362
+ status: "rejected",
363
+ reason: score.notes ? truncate(score.notes) : `score ${scoreId} verdict=fail (normalized ${round(score.normalized)})`
364
+ });
365
+ }
366
+ }
367
+ for (const decision of run.blackboard?.decisions || []) {
368
+ if (decision.outcome === "rejected" || decision.outcome === "superseded" || decision.outcome === "conflicting") {
369
+ forSelectionGate.push({
370
+ ref: decision.id,
371
+ kind: "decision",
372
+ status: mapDecisionOutcome(decision.outcome),
373
+ reason: decision.reason ? truncate(decision.reason) : `decision ${decision.id} ${decision.outcome}`
374
+ });
375
+ }
376
+ }
377
+ return {
378
+ forScoreGate: forScoreGate.sort(byRef),
379
+ forSelectionGate: forSelectionGate.sort(byRef),
380
+ bestRejectedNormalized
381
+ };
382
+ }
383
+ // ---------------------------------------------------------------------------
384
+ // Compaction exemption
385
+ //
386
+ // FreeBSD tenet — ORTHOGONALITY & COMPOSABILITY: the reasoning chain composes
387
+ // with the existing graph views and must survive compaction. A reasoning step is
388
+ // on the critical path and must NEVER be collapsed into a synthetic summary
389
+ // node. This returns the operator-graph node ids backing every decision-bearing
390
+ // reasoning step of an adopted chain, so state-explosion can protect them.
391
+ // ---------------------------------------------------------------------------
392
+ function reasoningCriticalNodeIds(run) {
393
+ const ids = new Set();
394
+ const faninIds = new Set((run.multiAgent?.fanins || []).map((entry) => entry.id));
395
+ const commitById = new Map((run.commits || []).map((commit) => [commit.id, commit]));
396
+ for (const evidence of (0, multi_agent_operator_ux_1.summarizeMultiAgentOperator)(run).evidence) {
397
+ if (evidence.status !== "adopted")
398
+ continue;
399
+ for (const id of evidence.candidateIds)
400
+ ids.add(`${run.id}:candidate:${id}`);
401
+ for (const id of evidence.scoreIds)
402
+ ids.add(`${run.id}:score:${id}`);
403
+ for (const id of evidence.selectionIds)
404
+ ids.add(`${run.id}:selection:${id}`);
405
+ for (const id of evidence.commitIds) {
406
+ const commit = commitById.get(id);
407
+ ids.add(commit?.stateNodeId || `${run.id}:commit:${id}`);
408
+ }
409
+ for (const adopter of evidence.adoptedBy) {
410
+ if (faninIds.has(adopter))
411
+ ids.add(`${run.id}:multi-agent:fanin:${adopter}`);
412
+ }
413
+ }
414
+ return [...ids].sort();
415
+ }
416
+ // ---------------------------------------------------------------------------
417
+ // Persistence + refresh (mirrors state-explosion summaries discipline)
418
+ // ---------------------------------------------------------------------------
419
+ function reasoningDir(run) {
420
+ return node_path_1.default.join(run.paths.runDir, "reasoning");
421
+ }
422
+ function refreshEvidenceReasoning(run) {
423
+ const report = buildEvidenceReasoningReport(run);
424
+ const dir = reasoningDir(run);
425
+ node_fs_1.default.mkdirSync(dir, { recursive: true });
426
+ const entries = [];
427
+ for (const chain of report.chains) {
428
+ const file = node_path_1.default.join(dir, `chain-${(0, state_1.safeFileName)(chain.id)}.json`);
429
+ (0, state_1.writeJson)(file, chain);
430
+ entries.push({
431
+ id: chain.id,
432
+ path: file,
433
+ evidenceStatus: chain.evidenceStatus,
434
+ rationaleStatus: chain.rationaleStatus,
435
+ sourceFingerprint: fingerprintChains([chain])
436
+ });
437
+ }
438
+ const indexPath = node_path_1.default.join(dir, "index.json");
439
+ const reportPath = node_path_1.default.join(dir, "report.json");
440
+ const index = {
441
+ schemaVersion: exports.EVIDENCE_REASONING_SCHEMA_VERSION,
442
+ runId: run.id,
443
+ id: "evidence-reasoning-index",
444
+ generatedAt: new Date().toISOString(),
445
+ sourceFingerprint: report.sourceFingerprint,
446
+ totals: report.totals,
447
+ entries: entries.sort((a, b) => a.id.localeCompare(b.id)),
448
+ paths: { reasoningDir: dir, indexPath, reportPath },
449
+ nextAction: `node scripts/cw.js multi-agent reasoning ${run.id}`
450
+ };
451
+ (0, state_1.writeJson)(indexPath, index);
452
+ (0, state_1.writeJson)(reportPath, { ...report, freshness: { ...report.freshness, status: "valid", persistedFingerprint: report.sourceFingerprint } });
453
+ return index;
454
+ }
455
+ function loadEvidenceReasoningIndex(run) {
456
+ const indexPath = node_path_1.default.join(reasoningDir(run), "index.json");
457
+ if (!node_fs_1.default.existsSync(indexPath))
458
+ return undefined;
459
+ try {
460
+ const parsed = JSON.parse(node_fs_1.default.readFileSync(indexPath, "utf8"));
461
+ if (!parsed || parsed.id !== "evidence-reasoning-index")
462
+ return undefined;
463
+ return parsed;
464
+ }
465
+ catch {
466
+ return undefined;
467
+ }
468
+ }
469
+ function showEvidenceReasoning(run, options = {}) {
470
+ const index = loadEvidenceReasoningIndex(run);
471
+ const report = buildEvidenceReasoningReport(run, { index });
472
+ if (!options.evidenceId)
473
+ return report;
474
+ const chains = report.chains.filter((chain) => chain.id === options.evidenceId || chain.ref === options.evidenceId);
475
+ return { ...report, chains, totals: summarizeTotals(chains) };
476
+ }
477
+ function normalizeEvidenceReasoningForEval(run) {
478
+ // Derive without the persisted index: a replay run has no reasoning/index.json,
479
+ // so persistence-derived freshness status would drift. Parity is asserted over
480
+ // the derived content (the CLI smoke test covers valid|stale|absent behavior).
481
+ const report = buildEvidenceReasoningReport(run);
482
+ return {
483
+ reasoningFreshness: [
484
+ JSON.stringify({
485
+ sourceFingerprint: report.sourceFingerprint,
486
+ chains: report.totals.chains,
487
+ explained: report.totals.explained,
488
+ unexplained: report.totals.unexplained,
489
+ notApplicable: report.totals.notApplicable,
490
+ adopted: report.totals.adopted,
491
+ rejected: report.totals.rejected
492
+ })
493
+ ],
494
+ reasoningChains: report.chains
495
+ .map((chain) => JSON.stringify({
496
+ id: stripRunId(run, chain.id),
497
+ evidenceStatus: chain.evidenceStatus,
498
+ rationaleStatus: chain.rationaleStatus,
499
+ gates: chain.steps.map((step) => `${step.gate}:${step.decision}:${step.rationale.status}`),
500
+ counterfactuals: chain.steps.reduce((total, step) => total + step.counterfactuals.length, 0)
501
+ }))
502
+ .sort(),
503
+ reasoningUnexplained: report.chains
504
+ .filter((chain) => chain.rationaleStatus === "unexplained")
505
+ .map((chain) => stripRunId(run, chain.id))
506
+ .sort()
507
+ };
508
+ }
509
+ function stripRunId(run, id) {
510
+ return id.startsWith(`${run.id}:`) ? id.slice(run.id.length + 1) : id;
511
+ }
512
+ // ---------------------------------------------------------------------------
513
+ // Human formatting (stable, six-panel-compatible idiom)
514
+ // ---------------------------------------------------------------------------
515
+ function formatEvidenceReasoningReport(report) {
516
+ const lines = [];
517
+ lines.push(`Evidence Adoption Reasoning: ${report.runId}`);
518
+ lines.push(`Freshness: ${report.freshness.status}`);
519
+ lines.push("");
520
+ lines.push("Adoption Rationale");
521
+ lines.push(` chains=${report.totals.chains}; explained=${report.totals.explained}; unexplained=${report.totals.unexplained}; n/a=${report.totals.notApplicable}; adopted=${report.totals.adopted}; rejected=${report.totals.rejected}`);
522
+ lines.push("");
523
+ if (!report.chains.length) {
524
+ lines.push(" none");
525
+ }
526
+ for (const chain of report.chains.slice(0, 60)) {
527
+ lines.push(` [${chain.evidenceStatus}/${chain.rationaleStatus}] ${chain.id} (${chain.ref || chain.sourceKind})`);
528
+ for (const step of chain.steps) {
529
+ const actor = `${step.authority.actorKind}:${step.authority.actor || "unknown"}`;
530
+ const why = step.rationale.status === "explained" ? step.rationale.text : `(${step.rationale.status})`;
531
+ const policy = step.authority.policyRef ? ` policy=${step.authority.policyRef}` : "";
532
+ lines.push(` - ${step.gate} [${step.decision}] by ${actor}${policy}: ${why}`);
533
+ for (const cf of step.counterfactuals.slice(0, 4)) {
534
+ lines.push(` x ${cf.kind} ${cf.ref} [${cf.status}]: ${cf.reason}`);
535
+ }
536
+ }
537
+ for (const reason of chain.unexplainedReasons)
538
+ lines.push(` ! ${reason}`);
539
+ }
540
+ if (report.chains.length > 60)
541
+ lines.push(` ... ${report.chains.length - 60} more`);
542
+ lines.push("");
543
+ lines.push("Next Action");
544
+ lines.push(` ${report.nextAction}`);
545
+ return lines.join("\n");
546
+ }
547
+ // ---------------------------------------------------------------------------
548
+ // Helpers
549
+ // ---------------------------------------------------------------------------
550
+ function basisFor(evidence, extra) {
551
+ return {
552
+ evidenceRefs: unique([evidence.locator || evidence.path || evidence.ref || evidence.id, ...extra.evidenceRefs].filter(Boolean)),
553
+ provenanceSource: provenanceSourceFor(evidence),
554
+ parentEvidenceIds: [],
555
+ auditEventIds: unique(extra.auditEventIds.filter(Boolean))
556
+ };
557
+ }
558
+ function provenanceSourceFor(evidence) {
559
+ const value = evidence.provenanceSource;
560
+ if (value === "cw-validated" || value === "host-attested" || value === "operator-recorded" || value === "runtime-derived") {
561
+ return value;
562
+ }
563
+ return undefined;
564
+ }
565
+ // AUTHORITY resolver: links the actor to its role policy when the actor is a
566
+ // role; never fabricates a policy. allowed comes from the recorded decision.
567
+ function roleAuthority(run, actor, allowed) {
568
+ const role = (run.multiAgent?.roles || []).find((entry) => entry.id === actor);
569
+ const policyRef = role ? (role.policy || (0, multi_agent_trust_1.policyForRole)(role)).policyRef : undefined;
570
+ return {
571
+ actor,
572
+ actorKind: role ? "role" : actor === "multi-agent-host" ? "operator" : actorKindForActor(actor),
573
+ policyRef,
574
+ allowed
575
+ };
576
+ }
577
+ function rollupRationale(steps, evidenceStatus) {
578
+ const decisionSteps = steps.filter((step) => isDecisionStatus(step.decision));
579
+ if (!decisionSteps.length)
580
+ return "not-applicable";
581
+ // Explained only when EVERY decision-bearing step is explained — fail closed.
582
+ if (decisionSteps.some((step) => step.rationale.status === "unexplained"))
583
+ return "unexplained";
584
+ if (decisionSteps.every((step) => step.rationale.status === "explained"))
585
+ return "explained";
586
+ return evidenceStatus === "adopted" ? "unexplained" : "not-applicable";
587
+ }
588
+ function summarizeTotals(chains) {
589
+ const byStatus = {};
590
+ let explained = 0;
591
+ let unexplained = 0;
592
+ let notApplicable = 0;
593
+ let adopted = 0;
594
+ let rejected = 0;
595
+ for (const chain of chains) {
596
+ byStatus[chain.evidenceStatus] = (byStatus[chain.evidenceStatus] || 0) + 1;
597
+ if (chain.rationaleStatus === "explained")
598
+ explained += 1;
599
+ else if (chain.rationaleStatus === "unexplained")
600
+ unexplained += 1;
601
+ else
602
+ notApplicable += 1;
603
+ if (chain.evidenceStatus === "adopted")
604
+ adopted += 1;
605
+ if (chain.evidenceStatus === "rejected")
606
+ rejected += 1;
607
+ }
608
+ return { chains: chains.length, explained, unexplained, notApplicable, adopted, rejected, byStatus };
609
+ }
610
+ function readAllScores(run) {
611
+ const scores = new Map();
612
+ const candidatesDir = run.paths.candidatesDir || node_path_1.default.join(run.paths.runDir, "candidates");
613
+ for (const candidate of run.candidates || []) {
614
+ const dir = node_path_1.default.join(candidatesDir, (0, state_1.safeFileName)(candidate.id), "scores");
615
+ if (!node_fs_1.default.existsSync(dir))
616
+ continue;
617
+ for (const file of node_fs_1.default.readdirSync(dir).filter((entry) => entry.endsWith(".json")).sort()) {
618
+ try {
619
+ const score = JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(dir, file), "utf8"));
620
+ scores.set(score.id, score);
621
+ }
622
+ catch {
623
+ // Unreadable score record: skip; the score gate will fail closed.
624
+ }
625
+ }
626
+ }
627
+ return scores;
628
+ }
629
+ function fingerprintChains(chains) {
630
+ const lines = chains.map((chain) => JSON.stringify([
631
+ chain.id,
632
+ chain.evidenceStatus,
633
+ chain.rationaleStatus,
634
+ chain.steps.map((step) => [step.gate, step.decision, step.rationale.status, step.rationale.sourceId || ""])
635
+ ]));
636
+ const hash = node_crypto_1.default.createHash("sha256");
637
+ hash.update(JSON.stringify([...lines].sort()));
638
+ return `sha256:${hash.digest("hex").slice(0, 32)}`;
639
+ }
640
+ function unexplainedRationale() {
641
+ return { status: "unexplained" };
642
+ }
643
+ function judgeRationaleText(event) {
644
+ const value = event?.metadata?.rationale;
645
+ return typeof value === "string" && value.trim() ? value : undefined;
646
+ }
647
+ function collectAuditIds(score) {
648
+ const ids = [];
649
+ for (const item of score?.evidence || []) {
650
+ for (const id of item.provenance?.auditEventIds || [])
651
+ ids.push(id);
652
+ }
653
+ return ids;
654
+ }
655
+ function scoreEvidenceRefs(score) {
656
+ return (score?.evidence || []).map(evidenceRef).filter(Boolean);
657
+ }
658
+ function evidenceRef(item) {
659
+ return item.locator || item.path || item.summary || item.id || "";
660
+ }
661
+ function coverageComplete(fanin, _evidence) {
662
+ return fanin.evidenceCoverage.length > 0 && fanin.evidenceCoverage.every((entry) => entry.complete);
663
+ }
664
+ function mapStatus(status) {
665
+ return status;
666
+ }
667
+ function mapDecisionOutcome(outcome) {
668
+ if (outcome === "accepted" || outcome === "ready")
669
+ return "adopted";
670
+ if (outcome === "rejected")
671
+ return "rejected";
672
+ if (outcome === "superseded")
673
+ return "superseded";
674
+ if (outcome === "conflicting")
675
+ return "conflicting";
676
+ return "pending";
677
+ }
678
+ function isDecisionStatus(status) {
679
+ return status === "adopted" || status === "rejected" || status === "superseded" || status === "conflicting";
680
+ }
681
+ function authorKind(kind) {
682
+ if (kind === "role" || kind === "group")
683
+ return "role";
684
+ if (kind === "worker")
685
+ return "worker";
686
+ if (kind === "membership")
687
+ return "membership";
688
+ if (kind === "operator")
689
+ return "operator";
690
+ if (kind === "verifier")
691
+ return "verifier";
692
+ if (kind === "coordinator")
693
+ return "coordinator";
694
+ return "runtime";
695
+ }
696
+ function actorKindForActor(actor) {
697
+ if (!actor)
698
+ return "runtime";
699
+ if (actor.includes("worker"))
700
+ return "worker";
701
+ if (actor.includes("membership"))
702
+ return "membership";
703
+ if (actor.includes("verifier"))
704
+ return "verifier";
705
+ return "runtime";
706
+ }
707
+ function actorKindForSource(sourceKind) {
708
+ if (sourceKind === "worker")
709
+ return "worker";
710
+ if (sourceKind === "coordinator")
711
+ return "coordinator";
712
+ if (sourceKind === "verifier")
713
+ return "verifier";
714
+ if (sourceKind === "operator")
715
+ return "operator";
716
+ return "runtime";
717
+ }
718
+ function statusRank(status) {
719
+ return { adopted: 0, pending: 1, missing: 2, conflicting: 3, rejected: 4, superseded: 5, unexplained: 6 }[status];
720
+ }
721
+ function truncate(value) {
722
+ const single = value.replace(/\s+/g, " ").trim();
723
+ return single.length > 200 ? `${single.slice(0, 197)}...` : single;
724
+ }
725
+ function round(value) {
726
+ return Math.round(value * 1000) / 1000;
727
+ }
728
+ function unique(values) {
729
+ return Array.from(new Set(values.filter(Boolean))).sort();
730
+ }
731
+ function byRef(a, b) {
732
+ return a.ref.localeCompare(b.ref);
733
+ }