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.
- package/.claude-plugin/plugin.json +20 -0
- package/.codex-plugin/mcp.json +10 -0
- package/.codex-plugin/plugin.json +38 -0
- package/.mcp.json +10 -0
- package/LICENSE +24 -0
- package/README.md +638 -0
- package/apps/architecture-review/app.json +51 -0
- package/apps/architecture-review/workflow.js +116 -0
- package/apps/end-to-end-golden-path/app.json +30 -0
- package/apps/end-to-end-golden-path/workflow.js +33 -0
- package/apps/pr-review-fix-ci/app.json +59 -0
- package/apps/pr-review-fix-ci/workflow.js +90 -0
- package/apps/release-cut/app.json +54 -0
- package/apps/release-cut/workflow.js +82 -0
- package/apps/research-synthesis/app.json +50 -0
- package/apps/research-synthesis/workflow.js +76 -0
- package/apps/workflow-app-framework-demo/app.json +29 -0
- package/apps/workflow-app-framework-demo/workflow.js +44 -0
- package/dist/agent-config.js +223 -0
- package/dist/candidate-scoring.js +715 -0
- package/dist/capability-core.js +630 -0
- package/dist/capability-dispatcher.js +86 -0
- package/dist/capability-registry.js +523 -0
- package/dist/cli.js +1276 -0
- package/dist/collaboration.js +727 -0
- package/dist/commit.js +570 -0
- package/dist/contract-migration.js +234 -0
- package/dist/coordinator.js +1163 -0
- package/dist/daemon.js +44 -0
- package/dist/dispatch.js +201 -0
- package/dist/drive.js +503 -0
- package/dist/error-feedback.js +415 -0
- package/dist/evidence-grounding.js +179 -0
- package/dist/evidence-reasoning.js +733 -0
- package/dist/execution-backend.js +1279 -0
- package/dist/harness.js +61 -0
- package/dist/mcp-server.js +1615 -0
- package/dist/multi-agent-eval.js +857 -0
- package/dist/multi-agent-host.js +764 -0
- package/dist/multi-agent-operator-ux.js +537 -0
- package/dist/multi-agent-trust.js +366 -0
- package/dist/multi-agent.js +1173 -0
- package/dist/node-snapshot.js +270 -0
- package/dist/observability.js +922 -0
- package/dist/operator-ux.js +971 -0
- package/dist/orchestrator/audit-operations.js +182 -0
- package/dist/orchestrator/candidate-operations.js +117 -0
- package/dist/orchestrator/cli-options.js +288 -0
- package/dist/orchestrator/collaboration-operations.js +86 -0
- package/dist/orchestrator/feedback-operations.js +81 -0
- package/dist/orchestrator/host-operations.js +78 -0
- package/dist/orchestrator/lifecycle-operations.js +462 -0
- package/dist/orchestrator/migration-operations.js +44 -0
- package/dist/orchestrator/multi-agent-operations.js +362 -0
- package/dist/orchestrator/report.js +369 -0
- package/dist/orchestrator/topology-operations.js +84 -0
- package/dist/orchestrator.js +874 -0
- package/dist/pipeline-contract.js +92 -0
- package/dist/pipeline-runner.js +285 -0
- package/dist/reclamation.js +882 -0
- package/dist/result-normalize.js +194 -0
- package/dist/run-export.js +64 -0
- package/dist/run-registry.js +1347 -0
- package/dist/run-state-schema.js +67 -0
- package/dist/sandbox-profile.js +471 -0
- package/dist/scheduler.js +266 -0
- package/dist/scheduling.js +184 -0
- package/dist/schema-validate.js +98 -0
- package/dist/state-explosion.js +1213 -0
- package/dist/state-migrations.js +463 -0
- package/dist/state-node.js +301 -0
- package/dist/state.js +308 -0
- package/dist/telemetry-attestation.js +156 -0
- package/dist/telemetry-ledger.js +145 -0
- package/dist/topology.js +527 -0
- package/dist/triggers.js +159 -0
- package/dist/trust-audit.js +475 -0
- package/dist/types/blackboard.js +2 -0
- package/dist/types/boundary.js +29 -0
- package/dist/types/candidate.js +2 -0
- package/dist/types/collaboration.js +2 -0
- package/dist/types/core.js +2 -0
- package/dist/types/drive.js +10 -0
- package/dist/types/error-feedback.js +2 -0
- package/dist/types/evidence-reasoning.js +2 -0
- package/dist/types/execution-backend.js +2 -0
- package/dist/types/multi-agent.js +2 -0
- package/dist/types/observability.js +2 -0
- package/dist/types/pipeline.js +2 -0
- package/dist/types/reclamation.js +8 -0
- package/dist/types/result.js +2 -0
- package/dist/types/run-registry.js +2 -0
- package/dist/types/run.js +2 -0
- package/dist/types/sandbox.js +2 -0
- package/dist/types/schedule.js +2 -0
- package/dist/types/state-node.js +2 -0
- package/dist/types/topology.js +2 -0
- package/dist/types/trust.js +2 -0
- package/dist/types/workbench.js +2 -0
- package/dist/types/worker.js +2 -0
- package/dist/types/workflow-app.js +2 -0
- package/dist/types.js +43 -0
- package/dist/verifier-registry.js +46 -0
- package/dist/verifier.js +78 -0
- package/dist/version.js +8 -0
- package/dist/workbench-host.js +172 -0
- package/dist/workbench.js +190 -0
- package/dist/worker-isolation.js +1028 -0
- package/dist/workflow-api.js +98 -0
- package/dist/workflow-app-framework.js +626 -0
- package/docs/agent-delegation-drive.7.md +190 -0
- package/docs/agent-framework.md +176 -0
- package/docs/candidate-scoring.7.md +106 -0
- package/docs/canonical-workflow-apps.7.md +137 -0
- package/docs/capability-topology-registry.7.md +168 -0
- package/docs/cli-mcp-parity.7.md +373 -0
- package/docs/contract-migration-tooling.7.md +123 -0
- package/docs/control-plane-scheduling.7.md +110 -0
- package/docs/coordinator-blackboard.7.md +183 -0
- package/docs/dogfood/architecture-review-cool-workflow.md +16 -0
- package/docs/dogfood-one-real-repo.7.md +168 -0
- package/docs/durable-state-and-locking.7.md +107 -0
- package/docs/end-to-end-golden-path.7.md +117 -0
- package/docs/error-feedback.7.md +153 -0
- package/docs/evidence-adoption-reasoning-chain.7.md +270 -0
- package/docs/execution-backends.7.md +300 -0
- package/docs/getting-started.md +99 -0
- package/docs/index.md +41 -0
- package/docs/mcp-app-surface.7.md +235 -0
- package/docs/multi-agent-cli-mcp-surface.7.md +265 -0
- package/docs/multi-agent-eval-replay-harness.7.md +302 -0
- package/docs/multi-agent-operator-ux.7.md +314 -0
- package/docs/multi-agent-runtime-core.7.md +231 -0
- package/docs/multi-agent-topologies.7.md +103 -0
- package/docs/multi-agent-trust-policy-audit.7.md +154 -0
- package/docs/node-snapshot-diff-replay.7.md +135 -0
- package/docs/observability-cost-accounting.7.md +194 -0
- package/docs/operator-ux.7.md +180 -0
- package/docs/pipeline-runner.7.md +136 -0
- package/docs/project-index.md +261 -0
- package/docs/real-execution-backends.7.md +142 -0
- package/docs/release-and-migration.7.md +280 -0
- package/docs/release-tooling.7.md +159 -0
- package/docs/routines.md +48 -0
- package/docs/run-registry-control-plane.7.md +312 -0
- package/docs/run-retention-reclamation.7.md +191 -0
- package/docs/sandbox-profiles.7.md +137 -0
- package/docs/scheduled-tasks.md +80 -0
- package/docs/security-trust-hardening.7.md +117 -0
- package/docs/state-explosion-management.7.md +264 -0
- package/docs/state-node.7.md +96 -0
- package/docs/team-collaboration.7.md +207 -0
- package/docs/unix-principles.md +192 -0
- package/docs/verifier-gated-commit.7.md +140 -0
- package/docs/web-desktop-workbench.7.md +215 -0
- package/docs/worker-isolation.7.md +167 -0
- package/docs/workflow-app-framework.7.md +274 -0
- package/manifest/README.md +43 -0
- package/manifest/plugin.manifest.json +316 -0
- package/manifest/pricing.policy.json +14 -0
- package/package.json +79 -0
- package/scripts/agents/claude-p-agent.js +104 -0
- package/scripts/agents/claude-p-agent.sh +9 -0
- package/scripts/agents/cw-attest-keygen.js +55 -0
- package/scripts/agents/cw-attest-wrap.js +143 -0
- package/scripts/block-unapproved-tag.sh +39 -0
- package/scripts/bump-version.js +249 -0
- package/scripts/canonical-apps.js +171 -0
- package/scripts/cw.js +4 -0
- package/scripts/dist-drift-check.js +79 -0
- package/scripts/dogfood-architecture-review.js +237 -0
- package/scripts/dogfood-release.js +624 -0
- package/scripts/forward-ref-docs.js +73 -0
- package/scripts/gen-manifests.js +232 -0
- package/scripts/golden-path.js +300 -0
- package/scripts/mcp-server.js +4 -0
- package/scripts/new-feature.js +121 -0
- package/scripts/parity-check.js +213 -0
- package/scripts/release-check.js +118 -0
- package/scripts/release-flow.js +272 -0
- package/scripts/release-gate.sh +85 -0
- package/scripts/sync-project-index.js +387 -0
- package/scripts/validate-run-state-schema.js +126 -0
- package/scripts/verify-container-selfref.js +64 -0
- package/scripts/version-sync-check.js +237 -0
- package/skills/cool-workflow/SKILL.md +162 -0
- package/skills/cool-workflow/references/commands.md +282 -0
- package/tsconfig.json +16 -0
- package/ui/workbench/app.css +76 -0
- package/ui/workbench/app.js +159 -0
- package/ui/workbench/index.html +32 -0
- package/workflows/architecture-review.workflow.js +84 -0
- 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
|
+
}
|