audit-tools 0.50.20 → 0.51.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/audit/cli/auditStep.d.ts +1 -1
- package/dist/audit/cli/charterExtractionPrompt.d.ts.map +1 -1
- package/dist/audit/cli/charterExtractionPrompt.js +14 -1
- package/dist/audit/cli/charterExtractionPrompt.js.map +1 -1
- package/dist/audit/cli/conceptualDispatch.d.ts.map +1 -1
- package/dist/audit/cli/conceptualDispatch.js +30 -5
- package/dist/audit/cli/conceptualDispatch.js.map +1 -1
- package/dist/audit/cli/dispatch/hostHandoff.d.ts.map +1 -1
- package/dist/audit/cli/dispatch/hostHandoff.js +10 -0
- package/dist/audit/cli/dispatch/hostHandoff.js.map +1 -1
- package/dist/audit/cli/fanoutLanes.d.ts +7 -0
- package/dist/audit/cli/fanoutLanes.d.ts.map +1 -1
- package/dist/audit/cli/fanoutLanes.js +9 -1
- package/dist/audit/cli/fanoutLanes.js.map +1 -1
- package/dist/audit/cli/laneSubmissions.d.ts +38 -27
- package/dist/audit/cli/laneSubmissions.d.ts.map +1 -1
- package/dist/audit/cli/laneSubmissions.js +129 -2
- package/dist/audit/cli/laneSubmissions.js.map +1 -1
- package/dist/audit/cli/nextStepCommand.d.ts.map +1 -1
- package/dist/audit/cli/nextStepCommand.js +44 -10
- package/dist/audit/cli/nextStepCommand.js.map +1 -1
- package/dist/audit/cli/nextStepHelpers.d.ts.map +1 -1
- package/dist/audit/cli/nextStepHelpers.js +34 -4
- package/dist/audit/cli/nextStepHelpers.js.map +1 -1
- package/dist/audit/cli/sampleRunCommand.d.ts +2 -1
- package/dist/audit/cli/sampleRunCommand.d.ts.map +1 -1
- package/dist/audit/contracts/workerSchemas.d.ts +4 -4
- package/dist/audit/contracts/workerSchemas.d.ts.map +1 -1
- package/dist/audit/contracts/workerSchemas.js +12 -6
- package/dist/audit/contracts/workerSchemas.js.map +1 -1
- package/dist/audit/contracts/wrapperResponse.d.ts +7 -7
- package/dist/audit/contracts/wrapperResponse.d.ts.map +1 -1
- package/dist/audit/contracts/wrapperResponse.js +9 -1
- package/dist/audit/contracts/wrapperResponse.js.map +1 -1
- package/dist/audit/extractors/commentDecomposition.d.ts +28 -0
- package/dist/audit/extractors/commentDecomposition.d.ts.map +1 -1
- package/dist/audit/extractors/commentDecomposition.js +117 -0
- package/dist/audit/extractors/commentDecomposition.js.map +1 -1
- package/dist/audit/orchestrator/charterDeltaExecutor.d.ts.map +1 -1
- package/dist/audit/orchestrator/charterDeltaExecutor.js +14 -0
- package/dist/audit/orchestrator/charterDeltaExecutor.js.map +1 -1
- package/dist/audit/orchestrator/charterExtractionExecutor.d.ts +16 -6
- package/dist/audit/orchestrator/charterExtractionExecutor.d.ts.map +1 -1
- package/dist/audit/orchestrator/charterExtractionExecutor.js +127 -9
- package/dist/audit/orchestrator/charterExtractionExecutor.js.map +1 -1
- package/dist/audit/orchestrator/charterPacketArchive.d.ts +51 -0
- package/dist/audit/orchestrator/charterPacketArchive.d.ts.map +1 -0
- package/dist/audit/orchestrator/charterPacketArchive.js +173 -0
- package/dist/audit/orchestrator/charterPacketArchive.js.map +1 -0
- package/dist/audit/orchestrator/charterPackets.d.ts +31 -13
- package/dist/audit/orchestrator/charterPackets.d.ts.map +1 -1
- package/dist/audit/orchestrator/charterPackets.js +433 -79
- package/dist/audit/orchestrator/charterPackets.js.map +1 -1
- package/dist/audit/orchestrator/designReviewPrompt.d.ts +20 -4
- package/dist/audit/orchestrator/designReviewPrompt.d.ts.map +1 -1
- package/dist/audit/orchestrator/designReviewPrompt.js +89 -12
- package/dist/audit/orchestrator/designReviewPrompt.js.map +1 -1
- package/dist/audit/orchestrator/executorRunners.d.ts.map +1 -1
- package/dist/audit/orchestrator/executorRunners.js +17 -1
- package/dist/audit/orchestrator/executorRunners.js.map +1 -1
- package/dist/audit/orchestrator/graphEnrichmentExecutor.d.ts.map +1 -1
- package/dist/audit/orchestrator/graphEnrichmentExecutor.js +8 -1
- package/dist/audit/orchestrator/graphEnrichmentExecutor.js.map +1 -1
- package/dist/audit/orchestrator/state.d.ts.map +1 -1
- package/dist/audit/orchestrator/state.js +21 -14
- package/dist/audit/orchestrator/state.js.map +1 -1
- package/dist/audit/orchestrator/synthesisExecutors.d.ts +8 -0
- package/dist/audit/orchestrator/synthesisExecutors.d.ts.map +1 -1
- package/dist/audit/orchestrator/synthesisExecutors.js +35 -1
- package/dist/audit/orchestrator/synthesisExecutors.js.map +1 -1
- package/dist/audit/reporting/synthesis.d.ts +43 -2
- package/dist/audit/reporting/synthesis.d.ts.map +1 -1
- package/dist/audit/reporting/synthesis.js +448 -15
- package/dist/audit/reporting/synthesis.js.map +1 -1
- package/dist/audit/supervisor/operatorHandoff.d.ts.map +1 -1
- package/dist/audit/supervisor/operatorHandoff.js +14 -6
- package/dist/audit/supervisor/operatorHandoff.js.map +1 -1
- package/dist/audit/systemic/secondOrderAdversaryPrompt.d.ts +2 -1
- package/dist/audit/systemic/secondOrderAdversaryPrompt.d.ts.map +1 -1
- package/dist/audit/systemic/secondOrderAdversaryPrompt.js +13 -3
- package/dist/audit/systemic/secondOrderAdversaryPrompt.js.map +1 -1
- package/dist/audit/types/analyzerCapability.d.ts +35 -11
- package/dist/audit/types/analyzerCapability.d.ts.map +1 -1
- package/dist/audit/types/analyzerCapability.js +83 -5
- package/dist/audit/types/analyzerCapability.js.map +1 -1
- package/dist/audit/types/auditState.d.ts +8 -8
- package/dist/audit/types/charterRegister.d.ts +33 -3
- package/dist/audit/types/charterRegister.d.ts.map +1 -1
- package/dist/audit/types/charterRegister.js +11 -1
- package/dist/audit/types/charterRegister.js.map +1 -1
- package/dist/audit/types/conceptualAdjudication.d.ts +383 -4
- package/dist/audit/types/conceptualAdjudication.d.ts.map +1 -1
- package/dist/audit/types/conceptualAdjudication.js +109 -5
- package/dist/audit/types/conceptualAdjudication.js.map +1 -1
- package/dist/audit/types/designAssessment.d.ts +1 -1
- package/dist/audit/types/designAssessment.d.ts.map +1 -1
- package/dist/audit/types.d.ts +7 -2
- package/dist/audit/types.d.ts.map +1 -1
- package/dist/remediate/intent/intentOrdering.d.ts.map +1 -1
- package/dist/remediate/intent/intentOrdering.js +29 -1
- package/dist/remediate/intent/intentOrdering.js.map +1 -1
- package/dist/remediate/phases/grounding.d.ts +12 -4
- package/dist/remediate/phases/grounding.d.ts.map +1 -1
- package/dist/remediate/phases/grounding.js +23 -60
- package/dist/remediate/phases/grounding.js.map +1 -1
- package/dist/remediate/state/types.d.ts +5 -0
- package/dist/remediate/state/types.d.ts.map +1 -1
- package/dist/shared/analyzers/types.d.ts +17 -6
- package/dist/shared/analyzers/types.d.ts.map +1 -1
- package/dist/shared/analyzers/types.js +6 -15
- package/dist/shared/analyzers/types.js.map +1 -1
- package/dist/shared/decompose/systemicChallenge.d.ts +5 -0
- package/dist/shared/decompose/systemicChallenge.d.ts.map +1 -1
- package/dist/shared/engine/obligationEngine.d.ts +23 -6
- package/dist/shared/engine/obligationEngine.d.ts.map +1 -1
- package/dist/shared/engine/obligationEngine.js +38 -3
- package/dist/shared/engine/obligationEngine.js.map +1 -1
- package/dist/shared/findings/dedupe.d.ts +10 -0
- package/dist/shared/findings/dedupe.d.ts.map +1 -1
- package/dist/shared/findings/dedupe.js +15 -0
- package/dist/shared/findings/dedupe.js.map +1 -1
- package/dist/shared/index.d.ts +14 -6
- package/dist/shared/index.d.ts.map +1 -1
- package/dist/shared/index.js +12 -5
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/measurement/measuredOutcome.d.ts +61 -0
- package/dist/shared/measurement/measuredOutcome.d.ts.map +1 -0
- package/dist/shared/measurement/measuredOutcome.js +109 -0
- package/dist/shared/measurement/measuredOutcome.js.map +1 -0
- package/dist/shared/reporting/findingDisplay.d.ts +14 -2
- package/dist/shared/reporting/findingDisplay.d.ts.map +1 -1
- package/dist/shared/reporting/findingDisplay.js +29 -2
- package/dist/shared/reporting/findingDisplay.js.map +1 -1
- package/dist/shared/reporting/lensCoverage.d.ts +70 -0
- package/dist/shared/reporting/lensCoverage.d.ts.map +1 -0
- package/dist/shared/reporting/lensCoverage.js +108 -0
- package/dist/shared/reporting/lensCoverage.js.map +1 -0
- package/dist/shared/submission/submissionLedger.d.ts +24 -1
- package/dist/shared/submission/submissionLedger.d.ts.map +1 -1
- package/dist/shared/submission/submissionLedger.js +61 -0
- package/dist/shared/submission/submissionLedger.js.map +1 -1
- package/dist/shared/types/charterPacket.d.ts +155 -0
- package/dist/shared/types/charterPacket.d.ts.map +1 -0
- package/dist/shared/types/charterPacket.js +9 -0
- package/dist/shared/types/charterPacket.js.map +1 -0
- package/dist/shared/types/finding.d.ts +275 -40
- package/dist/shared/types/finding.d.ts.map +1 -1
- package/dist/shared/types/finding.js +87 -0
- package/dist/shared/types/finding.js.map +1 -1
- package/dist/shared/validation/citationGrounding.d.ts +115 -0
- package/dist/shared/validation/citationGrounding.d.ts.map +1 -0
- package/dist/shared/validation/citationGrounding.js +347 -0
- package/dist/shared/validation/citationGrounding.js.map +1 -0
- package/dist/shared/validation/findingsReport.d.ts.map +1 -1
- package/dist/shared/validation/findingsReport.js +62 -0
- package/dist/shared/validation/findingsReport.js.map +1 -1
- package/package.json +1 -1
- package/schemas/audit-code-v1alpha1.schema.json +2 -1
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { degradedAnalyzerEntries, } from "../types/analyzerCapability.js";
|
|
2
|
+
import { AUDIT_FINDINGS_CONTRACT_VERSION as SHARED_AUDIT_FINDINGS_CONTRACT_VERSION, AUDITOR_REPORT_MARKER, renderProcessFeedbackSection, renderFindingBlockLines, compareCodeUnits, countBy, deriveLensCoverage, isIngestEvent, reprojectLensCoverage, } from "audit-tools/shared";
|
|
3
|
+
import { resolveIntentLensSelection } from "../orchestrator/lensSelection.js";
|
|
2
4
|
import { buildWorkBlockPartition } from "./workBlocks.js";
|
|
3
5
|
import { mergeFindings } from "./mergeFindings.js";
|
|
4
6
|
import { selectCurrentResults } from "../orchestrator/ledger.js";
|
|
5
7
|
import { assignStableFindingIds } from "./findingIdentity.js";
|
|
8
|
+
const CONCEPTUAL_ATTRIBUTION_IDS = Symbol("conceptual-attribution-ids");
|
|
9
|
+
const CONCEPTUAL_ID_MARKER_PREFIX = "\u0000audit-tools:conceptual-final-id:";
|
|
6
10
|
/**
|
|
7
11
|
* Contract version stamped onto the canonical `audit-findings.json`.
|
|
8
12
|
* Single-sourced from `audit-tools/shared` so the auditor's output and the
|
|
@@ -10,6 +14,47 @@ import { assignStableFindingIds } from "./findingIdentity.js";
|
|
|
10
14
|
* `seam-artifact-ipc-envelope` test).
|
|
11
15
|
*/
|
|
12
16
|
export const AUDIT_FINDINGS_CONTRACT_VERSION = SHARED_AUDIT_FINDINGS_CONTRACT_VERSION;
|
|
17
|
+
function markConceptualFindingIds(designAssessment) {
|
|
18
|
+
const conceptualFindings = designAssessment?.conceptual_findings ?? [];
|
|
19
|
+
if (conceptualFindings.length === 0) {
|
|
20
|
+
return { designAssessment, findingIdByMarker: new Map() };
|
|
21
|
+
}
|
|
22
|
+
const findingIdByMarker = new Map();
|
|
23
|
+
const markedFindings = conceptualFindings.map((finding, index) => {
|
|
24
|
+
const marker = `${CONCEPTUAL_ID_MARKER_PREFIX}${index}:${finding.id}\u0000`;
|
|
25
|
+
findingIdByMarker.set(marker, finding.id);
|
|
26
|
+
return {
|
|
27
|
+
...finding,
|
|
28
|
+
evidence: [...(finding.evidence ?? []), marker],
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
return {
|
|
32
|
+
designAssessment: { ...designAssessment, conceptual_findings: markedFindings },
|
|
33
|
+
findingIdByMarker,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function extractConceptualAttributionIds(mergedFindings, findingIdByMarker) {
|
|
37
|
+
if (findingIdByMarker.size === 0)
|
|
38
|
+
return mergedFindings.map(() => []);
|
|
39
|
+
let recoveredMarkerCount = 0;
|
|
40
|
+
const localIdsByFinding = mergedFindings.map((finding) => {
|
|
41
|
+
const localIds = [];
|
|
42
|
+
const evidence = (finding.evidence ?? []).filter((entry) => {
|
|
43
|
+
const localId = findingIdByMarker.get(entry);
|
|
44
|
+
if (localId === undefined)
|
|
45
|
+
return true;
|
|
46
|
+
localIds.push(localId);
|
|
47
|
+
recoveredMarkerCount += 1;
|
|
48
|
+
return false;
|
|
49
|
+
});
|
|
50
|
+
finding.evidence = evidence;
|
|
51
|
+
return localIds;
|
|
52
|
+
});
|
|
53
|
+
if (recoveredMarkerCount !== findingIdByMarker.size) {
|
|
54
|
+
throw new Error("Conceptual attribution identity markers were not conserved through finding merge.");
|
|
55
|
+
}
|
|
56
|
+
return localIdsByFinding;
|
|
57
|
+
}
|
|
13
58
|
function severityBreakdown(findings) {
|
|
14
59
|
return countBy(findings, (finding) => finding.severity);
|
|
15
60
|
}
|
|
@@ -24,6 +69,15 @@ function lensBreakdown(findings) {
|
|
|
24
69
|
function groundingStatusBreakdown(findings) {
|
|
25
70
|
return countBy(findings, (finding) => finding.grounding?.status);
|
|
26
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Per-status counts of the judge's defect-presence claim. Findings with no
|
|
74
|
+
* status (every deterministic finding — the conceptual pass is the only
|
|
75
|
+
* producer) are skipped by `countBy`, so an empty result means "no finding
|
|
76
|
+
* carried a claim" and the caller omits the field, exactly as for grounding.
|
|
77
|
+
*/
|
|
78
|
+
function verificationStatusBreakdown(findings) {
|
|
79
|
+
return countBy(findings, (finding) => finding.verification_status);
|
|
80
|
+
}
|
|
27
81
|
function runtimeStatusBreakdown(report, taskManifest) {
|
|
28
82
|
const breakdown = countBy(report?.results ?? [], (result) => result.status);
|
|
29
83
|
const resultTaskIds = new Set((report?.results ?? []).map((result) => result.task_id));
|
|
@@ -56,6 +110,7 @@ function formatCountList(summary) {
|
|
|
56
110
|
return parts.length > 0 ? parts.join(", ") : "none";
|
|
57
111
|
}
|
|
58
112
|
export function buildAuditReportModel(params) {
|
|
113
|
+
const markedConceptual = markConceptualFindingIds(params.designAssessment);
|
|
59
114
|
// Re-key the finalized findings with globally-unique, content-addressed ids
|
|
60
115
|
// before anything addresses them by id. mergeFindings emits exactly one
|
|
61
116
|
// finding per file-independent identity (exact normalized lens|category|
|
|
@@ -69,7 +124,20 @@ export function buildAuditReportModel(params) {
|
|
|
69
124
|
// before merging, so a re-dispatched result's fresh findings replace the stale
|
|
70
125
|
// base record they superseded — including findings the re-audit dropped, which
|
|
71
126
|
// a finding-id upsert alone would leave behind.
|
|
72
|
-
const
|
|
127
|
+
const mergedFindings = mergeFindings(selectCurrentResults(params.results), params.runtimeValidationReport, params.externalAnalyzerResults, markedConceptual.designAssessment, params.structureDecomposition, params.charterRegister, params.systemicChallenge);
|
|
128
|
+
const conceptualIdsByFinding = extractConceptualAttributionIds(mergedFindings, markedConceptual.findingIdByMarker);
|
|
129
|
+
const allFindings = assignStableFindingIds(mergedFindings);
|
|
130
|
+
const conceptualAttributionIds = new Map();
|
|
131
|
+
for (const [index, localIds] of conceptualIdsByFinding.entries()) {
|
|
132
|
+
const canonicalId = allFindings[index].id;
|
|
133
|
+
for (const localId of localIds) {
|
|
134
|
+
const existing = conceptualAttributionIds.get(localId);
|
|
135
|
+
if (existing !== undefined && existing !== canonicalId) {
|
|
136
|
+
throw new Error(`Conceptual finding id "${localId}" resolved to multiple canonical findings.`);
|
|
137
|
+
}
|
|
138
|
+
conceptualAttributionIds.set(localId, canonicalId);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
73
141
|
// B4: a tool-executable anchor that REFUTED a claim (status `refuted`, distinct
|
|
74
142
|
// from `ungrounded`) is quarantined-EXCLUDED — kept out of the admitted findings
|
|
75
143
|
// AND the work blocks so a disproven claim never merges as actionable fact. The
|
|
@@ -87,20 +155,48 @@ export function buildAuditReportModel(params) {
|
|
|
87
155
|
sizeIndex: params.sizeIndex,
|
|
88
156
|
});
|
|
89
157
|
const coverage = coverageSummary(params.coverageMatrix);
|
|
158
|
+
// A lens is only `clean` — "asked, and there was nothing there" — when at
|
|
159
|
+
// least one LENS-OPEN channel came back. Per-file audit results are lens-gated
|
|
160
|
+
// by planning (`required_lenses`), and the two design-review passes carry the
|
|
161
|
+
// selection into their prompts and record their own completion. With none of
|
|
162
|
+
// them ingested, a selected lens with no findings was never exercised, and
|
|
163
|
+
// saying `clean` would be exactly the "absence of a finding reads as absence
|
|
164
|
+
// of a defect" claim this field exists to refuse.
|
|
165
|
+
const lensOpenChannelIngested = params.results.length > 0 ||
|
|
166
|
+
params.designAssessment?.contract_reviewed === true ||
|
|
167
|
+
params.designAssessment?.conceptual_reviewed === true;
|
|
168
|
+
const selectedLenses = resolveIntentLensSelection(params.intentCheckpoint?.lens_selection);
|
|
90
169
|
// Count grounding over ALL findings (incl. quarantined-refuted) so the `refuted`
|
|
91
170
|
// tally reflects findings dropped from the admitted set.
|
|
92
171
|
const groundingBreakdown = groundingStatusBreakdown(allFindings);
|
|
172
|
+
const verificationBreakdown = verificationStatusBreakdown(allFindings);
|
|
93
173
|
const model = {
|
|
94
174
|
summary: {
|
|
95
175
|
finding_count: findings.length,
|
|
96
176
|
work_block_count: workBlocks.blocks.length,
|
|
97
177
|
severity_breakdown: severityBreakdown(findings),
|
|
98
178
|
lens_breakdown: lensBreakdown(findings),
|
|
179
|
+
// Presence is guaranteed HERE, at the one boundary that holds both the
|
|
180
|
+
// operator's selection and the produced findings. Absent — deliberately —
|
|
181
|
+
// when no selection resolved: "no limit" and "every lens" are different
|
|
182
|
+
// answers, and inventing a map would be a claim nobody made.
|
|
183
|
+
...(selectedLenses === undefined
|
|
184
|
+
? {}
|
|
185
|
+
: {
|
|
186
|
+
lens_coverage: deriveLensCoverage({
|
|
187
|
+
selectedLenses,
|
|
188
|
+
findings,
|
|
189
|
+
lensOpenChannelIngested,
|
|
190
|
+
}),
|
|
191
|
+
}),
|
|
99
192
|
audited_file_count: coverage.audited_file_count,
|
|
100
193
|
excluded_file_count: coverage.excluded_file_count,
|
|
101
194
|
...(Object.keys(groundingBreakdown).length > 0
|
|
102
195
|
? { grounding_status_breakdown: groundingBreakdown }
|
|
103
196
|
: {}),
|
|
197
|
+
...(Object.keys(verificationBreakdown).length > 0
|
|
198
|
+
? { verification_status_breakdown: verificationBreakdown }
|
|
199
|
+
: {}),
|
|
104
200
|
runtime_validation_status_breakdown: runtimeStatusBreakdown(params.runtimeValidationReport, params.runtimeValidationTaskManifest),
|
|
105
201
|
},
|
|
106
202
|
findings,
|
|
@@ -109,6 +205,9 @@ export function buildAuditReportModel(params) {
|
|
|
109
205
|
work_block_seams: workBlocks.seams,
|
|
110
206
|
...(quarantinedRefuted.length > 0 ? { quarantined_findings: quarantinedRefuted } : {}),
|
|
111
207
|
};
|
|
208
|
+
if (conceptualAttributionIds.size > 0) {
|
|
209
|
+
model[CONCEPTUAL_ATTRIBUTION_IDS] = conceptualAttributionIds;
|
|
210
|
+
}
|
|
112
211
|
return model;
|
|
113
212
|
}
|
|
114
213
|
/**
|
|
@@ -128,6 +227,10 @@ export function buildAuditFindingsReport(model) {
|
|
|
128
227
|
? { quarantined_findings: model.quarantined_findings }
|
|
129
228
|
: {}),
|
|
130
229
|
};
|
|
230
|
+
const conceptualAttributionIds = model[CONCEPTUAL_ATTRIBUTION_IDS];
|
|
231
|
+
if (conceptualAttributionIds) {
|
|
232
|
+
report[CONCEPTUAL_ATTRIBUTION_IDS] = conceptualAttributionIds;
|
|
233
|
+
}
|
|
131
234
|
return report;
|
|
132
235
|
}
|
|
133
236
|
/**
|
|
@@ -182,6 +285,96 @@ export function applyNarrative(report, narrative) {
|
|
|
182
285
|
top_risks: narrative.top_risks,
|
|
183
286
|
};
|
|
184
287
|
}
|
|
288
|
+
/**
|
|
289
|
+
* Name the selected lenses this run did not exercise.
|
|
290
|
+
*
|
|
291
|
+
* Rendered whenever a selected lens produced no findings, with its outcome, so
|
|
292
|
+
* `clean` ("asked, nothing found") and `not_run` ("never asked") stay legible as
|
|
293
|
+
* the different statements they are. Nothing to say — every selected lens
|
|
294
|
+
* produced findings, or no selection was made — renders nothing.
|
|
295
|
+
*/
|
|
296
|
+
function renderUnexercisedLensLine(coverage) {
|
|
297
|
+
const unexercised = (coverage ?? []).filter((entry) => entry.selected && entry.outcome !== "findings");
|
|
298
|
+
if (unexercised.length === 0)
|
|
299
|
+
return [];
|
|
300
|
+
const byOutcome = new Map();
|
|
301
|
+
for (const entry of unexercised) {
|
|
302
|
+
const bucket = byOutcome.get(entry.outcome);
|
|
303
|
+
if (bucket)
|
|
304
|
+
bucket.push(entry.lens);
|
|
305
|
+
else
|
|
306
|
+
byOutcome.set(entry.outcome, [entry.lens]);
|
|
307
|
+
}
|
|
308
|
+
const parts = [...byOutcome.entries()]
|
|
309
|
+
.sort(([left], [right]) => compareCodeUnits(left, right))
|
|
310
|
+
.map(([outcome, lenses]) => `${outcome === "not_run" ? "never exercised" : "exercised, no findings"}: ${lenses.join(", ")}`);
|
|
311
|
+
return [`- Lenses not exercised: ${parts.join("; ")}`];
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* The *Audit Limitations* body for a degraded graph-enrichment channel.
|
|
315
|
+
*
|
|
316
|
+
* Names each analyzer that was ASKED FOR and could not run, with its own note —
|
|
317
|
+
* the honest per-analyzer row that `analyzer_capability.json` has always carried
|
|
318
|
+
* and that, until now, nothing in the tree ever opened. The scalar roll-up is
|
|
319
|
+
* deliberately not the sentence: it cannot say "1932 edges added AND two
|
|
320
|
+
* analyzers degraded", so the entries are listed rather than summarized.
|
|
321
|
+
*/
|
|
322
|
+
function renderAnalyzerDegradationLines(record) {
|
|
323
|
+
const degraded = degradedAnalyzerEntries(record);
|
|
324
|
+
if (degraded.length === 0)
|
|
325
|
+
return [];
|
|
326
|
+
return [
|
|
327
|
+
`${degraded.length} language analyzer(s) were requested and could not run, so parser-grade extraction was OFF for them and the weaker deterministic regex floor was used instead. Findings that depend on the dependency graph are correspondingly weaker for these languages:`,
|
|
328
|
+
"",
|
|
329
|
+
...degraded.map((entry) => `- \`${entry.id}\` (requested \`${entry.setting}\`, resolution \`${entry.resolution}\`)` +
|
|
330
|
+
(entry.note ? ` — ${entry.note}` : "")),
|
|
331
|
+
"",
|
|
332
|
+
];
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* What the charter lanes were actually GIVEN, per evidence class.
|
|
336
|
+
*
|
|
337
|
+
* This renders UNCONDITIONALLY, deliberately diverging from the drift section
|
|
338
|
+
* above where clean is the expected case and presence is the statement. Here the
|
|
339
|
+
* measured case was 0 of 72 source-comment blocks delivered while the register
|
|
340
|
+
* and the report said nothing at all — so a silent section would leave
|
|
341
|
+
* "complete" indistinguishable from "not measured", which is the exact
|
|
342
|
+
* false-green this block exists to close. A run with no charter layer says so.
|
|
343
|
+
*/
|
|
344
|
+
function renderCharterCoverageSection(coverage, archive) {
|
|
345
|
+
// Its OWN top-level section, not a sub-head of "Process Feedback": coverage is
|
|
346
|
+
// a delivery measurement about the tool's inputs, not feedback about the host —
|
|
347
|
+
// and nesting it there would force that heading to render on every report,
|
|
348
|
+
// silently inverting the drift section's "presence is the statement" contract.
|
|
349
|
+
const lines = ["## Evidence Coverage", "", "### Charter evidence coverage", ""];
|
|
350
|
+
if (coverage.length === 0) {
|
|
351
|
+
lines.push("No charter evidence packets were measured in this run (the charter layer " +
|
|
352
|
+
"is opt-in at a `deep` ceiling). This is a stated absence, not a clean bill.", "");
|
|
353
|
+
}
|
|
354
|
+
for (const packet of coverage) {
|
|
355
|
+
lines.push(`- \`${packet.kind}\` channel:`);
|
|
356
|
+
for (const entry of packet.classes) {
|
|
357
|
+
const truncated = entry.truncated > 0 ? `, ${entry.truncated} truncated` : "";
|
|
358
|
+
lines.push(` - ${entry.evidence_class}: ${entry.delivered} of ${entry.named} delivered${truncated}` +
|
|
359
|
+
(entry.omitted.length > 0
|
|
360
|
+
? ` — omitted: ${entry.omitted
|
|
361
|
+
.map((row) => `${row.path} (${row.reason})`)
|
|
362
|
+
.join(", ")}`
|
|
363
|
+
: ""));
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
const unarchived = archive.filter((row) => !row.archived);
|
|
367
|
+
if (unarchived.length > 0) {
|
|
368
|
+
lines.push("", "⚠ Packet retention FAILED for " +
|
|
369
|
+
unarchived
|
|
370
|
+
.map((row) => `\`${row.kind}\` (${row.reason ?? "unstated reason"})`)
|
|
371
|
+
.join(", ") +
|
|
372
|
+
". The lane-asset copy was left in place rather than deleted, so the " +
|
|
373
|
+
"evidence still exists — but it is not in the run's archive.");
|
|
374
|
+
}
|
|
375
|
+
lines.push("");
|
|
376
|
+
return lines;
|
|
377
|
+
}
|
|
185
378
|
/**
|
|
186
379
|
* What happened to this run's submissions, when something went wrong.
|
|
187
380
|
*
|
|
@@ -199,6 +392,71 @@ export function applyNarrative(report, narrative) {
|
|
|
199
392
|
* ambiguity. Totals are sorted by content, never arrival: a derived summary may
|
|
200
393
|
* sort; the ledger file it derives from may not.
|
|
201
394
|
*/
|
|
395
|
+
/**
|
|
396
|
+
* What this run actually DISPATCHED, and how much of it came back.
|
|
397
|
+
*
|
|
398
|
+
* The drift section below renders only when something was refused, so a run in
|
|
399
|
+
* which five lanes silently under-delivered — exit 0, nothing written, then
|
|
400
|
+
* re-dispatched until they succeeded — produced zero `rejected` events and read
|
|
401
|
+
* as clean. This is the other half: dispatched versus delivered, per round, so
|
|
402
|
+
* a lane that reported success and wrote nothing is a printed fact rather than
|
|
403
|
+
* something recoverable only from the host's transcript.
|
|
404
|
+
*
|
|
405
|
+
* A dispatch is DELIVERED when its submission has any terminal record other
|
|
406
|
+
* than a `lane_outcome` of `not_run`: an ingest event (the tool consumed it) or
|
|
407
|
+
* an observed outcome. No terminal row at all is `not_run` too — the absence IS
|
|
408
|
+
* the record, never an inference that something arrived.
|
|
409
|
+
*/
|
|
410
|
+
function renderLaneDeliverySection(events) {
|
|
411
|
+
const dispatched = events.filter((event) => event.kind === "dispatched");
|
|
412
|
+
if (dispatched.length === 0)
|
|
413
|
+
return [];
|
|
414
|
+
const outcomeById = new Map();
|
|
415
|
+
for (const event of events) {
|
|
416
|
+
if (event.kind === "lane_outcome" && event.outcome !== undefined) {
|
|
417
|
+
outcomeById.set(event.submission_id, event.outcome);
|
|
418
|
+
}
|
|
419
|
+
else if (isIngestEvent(event.kind) && !outcomeById.has(event.submission_id)) {
|
|
420
|
+
// The tool ingested it, so it was delivered; the ledger does not record
|
|
421
|
+
// how much was in it, and this must not invent a count.
|
|
422
|
+
outcomeById.set(event.submission_id, "findings");
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
const byRound = new Map();
|
|
426
|
+
for (const event of dispatched) {
|
|
427
|
+
const round = event.round_id ?? "";
|
|
428
|
+
const bucket = byRound.get(round);
|
|
429
|
+
if (bucket)
|
|
430
|
+
bucket.push(event);
|
|
431
|
+
else
|
|
432
|
+
byRound.set(round, [event]);
|
|
433
|
+
}
|
|
434
|
+
const lines = [
|
|
435
|
+
"### Lane dispatch and delivery",
|
|
436
|
+
"",
|
|
437
|
+
`${dispatched.length} lane(s) were dispatched this run. A dispatched lane that wrote nothing at the bound path the tool declared is recorded \`not_run\` — the absence is the record, not an inference.`,
|
|
438
|
+
"",
|
|
439
|
+
];
|
|
440
|
+
// Sorted by content, never arrival: a derived summary may sort; the ledger
|
|
441
|
+
// file it derives from may not.
|
|
442
|
+
for (const round of [...byRound.keys()].sort(compareCodeUnits)) {
|
|
443
|
+
const rows = byRound.get(round);
|
|
444
|
+
const outcomes = rows.map((event) => outcomeById.get(event.submission_id) ?? "not_run");
|
|
445
|
+
const delivered = outcomes.filter((outcome) => outcome !== "not_run").length;
|
|
446
|
+
const tally = new Map();
|
|
447
|
+
for (const outcome of outcomes) {
|
|
448
|
+
tally.set(outcome, (tally.get(outcome) ?? 0) + 1);
|
|
449
|
+
}
|
|
450
|
+
const breakdown = [...tally.entries()]
|
|
451
|
+
.sort(([left], [right]) => compareCodeUnits(left, right))
|
|
452
|
+
.map(([outcome, count]) => `${outcome} ${count}`)
|
|
453
|
+
.join(", ");
|
|
454
|
+
lines.push(`- ${round === "" ? "this run's gate lanes" : `round \`${round}\``}: ` +
|
|
455
|
+
`${delivered} of ${rows.length} delivered (${breakdown})`);
|
|
456
|
+
}
|
|
457
|
+
lines.push("");
|
|
458
|
+
return lines;
|
|
459
|
+
}
|
|
202
460
|
function renderSubmissionDriftSection(events) {
|
|
203
461
|
const refusedIds = new Set(events
|
|
204
462
|
.filter((event) => event.kind === "rejected")
|
|
@@ -208,11 +466,15 @@ function renderSubmissionDriftSection(events) {
|
|
|
208
466
|
.map((event) => event.submission_id));
|
|
209
467
|
if (refusedIds.size === 0 && handRepairedIds.size === 0)
|
|
210
468
|
return [];
|
|
211
|
-
// Trailing state per submission
|
|
212
|
-
// hand recovery is RESOLVED; one that is still
|
|
469
|
+
// Trailing state per submission, over the INGEST events only: a refusal
|
|
470
|
+
// followed by an acceptance or a hand recovery is RESOLVED; one that is still
|
|
471
|
+
// the last word is not. It used to be "everything except `expected`", so a
|
|
472
|
+
// `dispatched` row appended when the still-pending lane was re-materialized
|
|
473
|
+
// would have become the trailing event and made this claim the refusal "was
|
|
474
|
+
// later accepted or re-landed by hand" when nothing had accepted it.
|
|
213
475
|
const trailing = new Map();
|
|
214
476
|
for (const event of events) {
|
|
215
|
-
if (event.kind
|
|
477
|
+
if (!isIngestEvent(event.kind))
|
|
216
478
|
continue;
|
|
217
479
|
trailing.set(event.submission_id, event.kind);
|
|
218
480
|
}
|
|
@@ -255,9 +517,125 @@ function renderSubmissionDriftSection(events) {
|
|
|
255
517
|
function pushFindingBlock(finding, lines) {
|
|
256
518
|
lines.push(...renderFindingBlockLines(finding));
|
|
257
519
|
}
|
|
258
|
-
function
|
|
520
|
+
export function canonicalizeConceptualAttributionIds(report, adjudication, idSource = report) {
|
|
521
|
+
const canonicalIds = new Set([
|
|
522
|
+
...report.findings.map((finding) => finding.id),
|
|
523
|
+
...(report.quarantined_findings ?? []).map((finding) => finding.id),
|
|
524
|
+
]);
|
|
525
|
+
const idMap = idSource[CONCEPTUAL_ATTRIBUTION_IDS];
|
|
526
|
+
const resolve = (id) => {
|
|
527
|
+
if (canonicalIds.has(id))
|
|
528
|
+
return id;
|
|
529
|
+
const canonicalId = idMap?.get(id);
|
|
530
|
+
if (canonicalId !== undefined && canonicalIds.has(canonicalId)) {
|
|
531
|
+
return canonicalId;
|
|
532
|
+
}
|
|
533
|
+
throw new Error(`Conceptual attribution refused: final finding id "${id}" does not resolve ` +
|
|
534
|
+
"to a canonical finding in audit-findings.json.");
|
|
535
|
+
};
|
|
536
|
+
// Canonicalize the carried adjudication itself, not only the markdown text.
|
|
537
|
+
// The fold persists every carried core artifact, so subsequent narrative and
|
|
538
|
+
// resynthesis renders reload canonical targets rather than reviving the
|
|
539
|
+
// judge-local ids that existed before the synthesis boundary.
|
|
540
|
+
const sharesByCanonicalId = new Map();
|
|
541
|
+
for (const share of adjudication.final_finding_shares) {
|
|
542
|
+
const sourceFinalFindingId = share.final_finding_id;
|
|
543
|
+
const canonicalId = resolve(sourceFinalFindingId);
|
|
544
|
+
const entries = sharesByCanonicalId.get(canonicalId) ?? [];
|
|
545
|
+
entries.push({ sourceFinalFindingId, share });
|
|
546
|
+
sharesByCanonicalId.set(canonicalId, entries);
|
|
547
|
+
}
|
|
548
|
+
const resolvedFinalFindingShares = [...sharesByCanonicalId.entries()]
|
|
549
|
+
.sort(([left], [right]) => compareCodeUnits(left, right))
|
|
550
|
+
.map(([canonicalId, entries]) => {
|
|
551
|
+
if (entries.length === 1) {
|
|
552
|
+
return { ...entries[0].share, final_finding_id: canonicalId };
|
|
553
|
+
}
|
|
554
|
+
const orderedEntries = [...entries].sort((left, right) => compareCodeUnits(left.sourceFinalFindingId, right.sourceFinalFindingId));
|
|
555
|
+
const contributions = new Map();
|
|
556
|
+
for (const entry of orderedEntries) {
|
|
557
|
+
for (const contribution of entry.share.contributors) {
|
|
558
|
+
const contributorEntries = contributions.get(contribution.contributor_id) ?? [];
|
|
559
|
+
contributorEntries.push({
|
|
560
|
+
sourceFinalFindingId: entry.sourceFinalFindingId,
|
|
561
|
+
contribution,
|
|
562
|
+
});
|
|
563
|
+
contributions.set(contribution.contributor_id, contributorEntries);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
const orderedContributions = [...contributions.entries()].sort(([left], [right]) => compareCodeUnits(left, right));
|
|
567
|
+
// Each judge-local final has its own 100% allocation. Once several local
|
|
568
|
+
// finals collapse to one canonical finding, the canonical allocation is
|
|
569
|
+
// their equal-weight mean (a missing contributor contributes 0%). Allocate
|
|
570
|
+
// power-of-two micro-percent units by largest remainder: division back to
|
|
571
|
+
// numbers is exact in binary, every result stays bounded, and the common
|
|
572
|
+
// denominator makes the sum exactly 100 rather than approximately 100.
|
|
573
|
+
// Source-local percentages/rationales remain verbatim in rationale below.
|
|
574
|
+
const unitsPerPercent = 2 ** 20;
|
|
575
|
+
const totalUnits = 100 * unitsPerPercent;
|
|
576
|
+
const allocationInputs = orderedContributions.map(([contributorId, contributorEntries]) => {
|
|
577
|
+
const sourcePercent = contributorEntries.reduce((sum, entry) => sum + entry.contribution.contribution_percent, 0);
|
|
578
|
+
return { contributorId, contributorEntries, sourcePercent };
|
|
579
|
+
});
|
|
580
|
+
const totalSourcePercent = allocationInputs.reduce((sum, item) => sum + item.sourcePercent, 0);
|
|
581
|
+
if (!Number.isFinite(totalSourcePercent) || totalSourcePercent <= 0) {
|
|
582
|
+
throw new Error("Conceptual attribution percentage allocation has no positive total.");
|
|
583
|
+
}
|
|
584
|
+
const allocations = allocationInputs.map(({ contributorId, contributorEntries, sourcePercent }) => {
|
|
585
|
+
const exactUnits = (sourcePercent / totalSourcePercent) * totalUnits;
|
|
586
|
+
const units = Math.floor(exactUnits);
|
|
587
|
+
return {
|
|
588
|
+
contributorId,
|
|
589
|
+
contributorEntries,
|
|
590
|
+
units,
|
|
591
|
+
remainder: exactUnits - units,
|
|
592
|
+
};
|
|
593
|
+
});
|
|
594
|
+
const remainingUnits = totalUnits - allocations.reduce((sum, item) => sum + item.units, 0);
|
|
595
|
+
if (remainingUnits < 0 || remainingUnits > allocations.length) {
|
|
596
|
+
throw new Error("Conceptual attribution percentage allocation exceeded bounded remainder.");
|
|
597
|
+
}
|
|
598
|
+
const remainderOrder = [...allocations].sort((left, right) => right.remainder - left.remainder ||
|
|
599
|
+
compareCodeUnits(left.contributorId, right.contributorId));
|
|
600
|
+
for (let index = 0; index < remainingUnits; index += 1) {
|
|
601
|
+
remainderOrder[index].units += 1;
|
|
602
|
+
}
|
|
603
|
+
const contributors = orderedContributions.map(([contributorId, contributorEntries]) => {
|
|
604
|
+
const allocation = allocations.find((item) => item.contributorId === contributorId);
|
|
605
|
+
return {
|
|
606
|
+
contributor_id: contributorId,
|
|
607
|
+
source_candidate_ids: [
|
|
608
|
+
...new Set(contributorEntries.flatMap((entry) => entry.contribution.source_candidate_ids)),
|
|
609
|
+
].sort(compareCodeUnits),
|
|
610
|
+
contribution_percent: allocation.units / unitsPerPercent,
|
|
611
|
+
rationale: contributorEntries
|
|
612
|
+
.map((entry) => `${entry.sourceFinalFindingId} ` +
|
|
613
|
+
`(${entry.contribution.contribution_percent}%): ` +
|
|
614
|
+
entry.contribution.rationale)
|
|
615
|
+
.join(" | "),
|
|
616
|
+
};
|
|
617
|
+
});
|
|
618
|
+
return {
|
|
619
|
+
final_finding_id: canonicalId,
|
|
620
|
+
contributors,
|
|
621
|
+
};
|
|
622
|
+
});
|
|
623
|
+
const resolvedCandidateDispositions = adjudication.candidate_dispositions.map((disposition) => ({
|
|
624
|
+
...disposition,
|
|
625
|
+
target_final_finding_ids: [
|
|
626
|
+
...new Set(disposition.target_final_finding_ids.map(resolve)),
|
|
627
|
+
],
|
|
628
|
+
}));
|
|
629
|
+
// Commit both reference-bearing arrays only after every share and target has
|
|
630
|
+
// resolved. A late unknown disposition target cannot leave half-canonical
|
|
631
|
+
// attribution in the caller's carried bundle.
|
|
632
|
+
adjudication.final_finding_shares = resolvedFinalFindingShares;
|
|
633
|
+
adjudication.candidate_dispositions = resolvedCandidateDispositions;
|
|
634
|
+
}
|
|
635
|
+
function renderConceptualAttributionSection(report, adjudication) {
|
|
259
636
|
if (!adjudication)
|
|
260
637
|
return [];
|
|
638
|
+
canonicalizeConceptualAttributionIds(report, adjudication);
|
|
261
639
|
const contributors = new Map(adjudication.contributors.map((contributor) => [
|
|
262
640
|
contributor.contributor_id,
|
|
263
641
|
contributor,
|
|
@@ -265,7 +643,15 @@ function renderConceptualAttributionSection(adjudication) {
|
|
|
265
643
|
const lines = [
|
|
266
644
|
"## Conceptual Review Attribution",
|
|
267
645
|
"",
|
|
268
|
-
`Round: \`${adjudication.round_id}\`. Contribution and modification percentages are judge-authored semantic estimates; tooling verified references, bounds, complete candidate coverage, and 100% contribution totals.`,
|
|
646
|
+
`Round: \`${adjudication.round_id}\`. Contribution and modification percentages are judge-authored semantic estimates; tooling verified references, bounds, complete candidate coverage, and 100% contribution totals. \`verification_status\` is likewise judge-authored — the tool enforces that a claim is present, consistent and published, never that it is true. Note that a conceptual finding's \`grounded\` verdict certifies component-path existence ONLY; the defect-presence claim is carried by \`verification_status\`, not by grounding.`,
|
|
647
|
+
"",
|
|
648
|
+
// The aggregate the live run had to be obtained by counting the artifact by
|
|
649
|
+
// hand. Both populations are CANDIDATE-scoped here and are not reconciled
|
|
650
|
+
// with the finding-scoped Summary counts: the post-adjudication grounding
|
|
651
|
+
// quarantine can drop a merged candidate's final finding, so they
|
|
652
|
+
// legitimately differ and each is labelled by its population.
|
|
653
|
+
`- Candidate dispositions: ${formatCountList(adjudication.candidate_disposition_breakdown)}`,
|
|
654
|
+
`- Candidate verification: ${formatCountList(adjudication.candidate_verification_status_breakdown)}`,
|
|
269
655
|
"",
|
|
270
656
|
];
|
|
271
657
|
for (const finalShare of adjudication.final_finding_shares) {
|
|
@@ -284,7 +670,7 @@ function renderConceptualAttributionSection(adjudication) {
|
|
|
284
670
|
}
|
|
285
671
|
lines.push("### Candidate dispositions", "");
|
|
286
672
|
for (const disposition of adjudication.candidate_dispositions) {
|
|
287
|
-
lines.push(`- \`${disposition.candidate_id}\` — **${disposition.disposition}**; modified ${disposition.modification_percent}%; targets ${disposition.target_final_finding_ids.join(", ") || "none"}. ${disposition.rationale}`);
|
|
673
|
+
lines.push(`- \`${disposition.candidate_id}\` — **${disposition.disposition}**; \`${disposition.verification_status}\`; modified ${disposition.modification_percent}%; targets ${disposition.target_final_finding_ids.join(", ") || "none"}. ${disposition.rationale}${disposition.verification_note ? ` _Verified:_ ${disposition.verification_note}` : ""}`);
|
|
288
674
|
}
|
|
289
675
|
lines.push("");
|
|
290
676
|
return lines;
|
|
@@ -300,7 +686,12 @@ export function renderAuditReportMarkdown(report, options = {}) {
|
|
|
300
686
|
}
|
|
301
687
|
lines.push("## Summary", "", `- Findings: ${report.summary.finding_count}`, `- Work blocks: ${report.summary.work_block_count}`, `- Severity breakdown: ${formatSeverityList(report.summary.severity_breakdown)}`, ...(report.summary.lens_breakdown && Object.keys(report.summary.lens_breakdown).length > 0
|
|
302
688
|
? [`- Lens breakdown: ${formatCountList(report.summary.lens_breakdown)}`]
|
|
303
|
-
: []),
|
|
689
|
+
: []),
|
|
690
|
+
// The line the breakdown structurally cannot carry: a selected lens with no
|
|
691
|
+
// findings has no key in a `countBy`, so it used to be invisible. Named
|
|
692
|
+
// here rather than in *Audit Limitations* (owner working assumption) — terse
|
|
693
|
+
// and always in front of the reader, beside the counts it qualifies.
|
|
694
|
+
...renderUnexercisedLensLine(report.summary.lens_coverage), ...(report.summary.grounding_status_breakdown &&
|
|
304
695
|
Object.keys(report.summary.grounding_status_breakdown).length > 0
|
|
305
696
|
? [
|
|
306
697
|
`- Grounding (S7): ${formatCountList(report.summary.grounding_status_breakdown)}` +
|
|
@@ -315,6 +706,12 @@ export function renderAuditReportMarkdown(report, options = {}) {
|
|
|
315
706
|
.filter(Boolean)
|
|
316
707
|
.reduce((acc, note, i) => acc + (i === 0 ? " — " : "; ") + note, ""),
|
|
317
708
|
]
|
|
709
|
+
: []), ...(report.summary.verification_status_breakdown &&
|
|
710
|
+
Object.keys(report.summary.verification_status_breakdown).length > 0
|
|
711
|
+
? [
|
|
712
|
+
`- Verification: ${formatCountList(report.summary.verification_status_breakdown)}` +
|
|
713
|
+
" — judge-authored claims about whether each defect is present at HEAD, not tool runs",
|
|
714
|
+
]
|
|
318
715
|
: []), `- Fully audited files: ${report.summary.audited_file_count}`, `- Excluded non-auditable files: ${report.summary.excluded_file_count}`, "");
|
|
319
716
|
if (report.top_risks && report.top_risks.length > 0) {
|
|
320
717
|
lines.push("## Top Risks", "");
|
|
@@ -401,8 +798,12 @@ export function renderAuditReportMarkdown(report, options = {}) {
|
|
|
401
798
|
pushFindingBlock(finding, lines);
|
|
402
799
|
}
|
|
403
800
|
}
|
|
404
|
-
const
|
|
405
|
-
|
|
801
|
+
const ledgerEvents = options.submission_ledger ?? [];
|
|
802
|
+
const driftLines = [
|
|
803
|
+
...renderLaneDeliverySection(ledgerEvents),
|
|
804
|
+
...renderSubmissionDriftSection(ledgerEvents),
|
|
805
|
+
];
|
|
806
|
+
lines.push(...renderConceptualAttributionSection(report, options.conceptual_adjudication));
|
|
406
807
|
const reflections = options.reflections ?? [];
|
|
407
808
|
// Structural capability limitations are report limitations, not process
|
|
408
809
|
// feedback. Keep the machine contract untouched: this only partitions the
|
|
@@ -410,15 +811,32 @@ export function renderAuditReportMarkdown(report, options = {}) {
|
|
|
410
811
|
const capabilityLimitations = reflections.filter((reflection) => reflection.task_id === "audit-capability-preflight" &&
|
|
411
812
|
(reflection.severity === "high" || reflection.severity === "critical"));
|
|
412
813
|
const processFeedback = reflections.filter((reflection) => !capabilityLimitations.includes(reflection));
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
814
|
+
// *Audit Limitations* states what this run could NOT do. It has two sources
|
|
815
|
+
// now, and the section is CREATED by either — it used to be the process-
|
|
816
|
+
// feedback heading renamed, so it existed only when a capability-preflight
|
|
817
|
+
// reflection happened to be present.
|
|
818
|
+
const analyzerLimitationLines = renderAnalyzerDegradationLines(options.analyzer_capability);
|
|
819
|
+
const reflectionLimitationLines = renderProcessFeedbackSection(capabilityLimitations);
|
|
820
|
+
if (reflectionLimitationLines.length > 0) {
|
|
821
|
+
reflectionLimitationLines[0] = "## Audit Limitations";
|
|
822
|
+
}
|
|
823
|
+
const limitationLines = analyzerLimitationLines.length > 0
|
|
824
|
+
? [
|
|
825
|
+
"## Audit Limitations",
|
|
826
|
+
"",
|
|
827
|
+
...analyzerLimitationLines,
|
|
828
|
+
// A reflection-sourced block that follows keeps its own body but must
|
|
829
|
+
// not re-open the heading it is now inside.
|
|
830
|
+
...reflectionLimitationLines.slice(reflectionLimitationLines.length > 0 ? 2 : 0),
|
|
831
|
+
]
|
|
832
|
+
: reflectionLimitationLines;
|
|
416
833
|
const feedbackLines = renderProcessFeedbackSection(processFeedback);
|
|
834
|
+
const coverageLines = renderCharterCoverageSection(options.charter_evidence_coverage ?? [], options.charter_packet_archive ?? []);
|
|
417
835
|
lines.push(...limitationLines, ...feedbackLines);
|
|
418
836
|
if (driftLines.length > 0 && feedbackLines.length === 0) {
|
|
419
837
|
lines.push("## Process Feedback", "");
|
|
420
838
|
}
|
|
421
|
-
lines.push(...driftLines);
|
|
839
|
+
lines.push(...driftLines, ...coverageLines);
|
|
422
840
|
const excludedScope = options.intent_checkpoint?.excluded_scope ?? [];
|
|
423
841
|
if (excludedScope.length > 0) {
|
|
424
842
|
lines.push("## Excluded / Out-of-Scope", "");
|
|
@@ -453,6 +871,7 @@ export function renderAuditReportMarkdown(report, options = {}) {
|
|
|
453
871
|
*/
|
|
454
872
|
export function normalizeExistingFindingsReport(report) {
|
|
455
873
|
const groundingBreakdown = groundingStatusBreakdown(report.findings);
|
|
874
|
+
const verificationBreakdown = verificationStatusBreakdown(report.findings);
|
|
456
875
|
return {
|
|
457
876
|
...report,
|
|
458
877
|
contract_version: AUDIT_FINDINGS_CONTRACT_VERSION,
|
|
@@ -463,9 +882,23 @@ export function normalizeExistingFindingsReport(report) {
|
|
|
463
882
|
work_block_count: report.work_blocks.length,
|
|
464
883
|
severity_breakdown: severityBreakdown(report.findings),
|
|
465
884
|
lens_breakdown: lensBreakdown(report.findings),
|
|
885
|
+
// The SELECTION is carried through — this function has no checkpoint and
|
|
886
|
+
// must not invent one — while the counts and outcomes are re-derived over
|
|
887
|
+
// the same findings `lens_breakdown` is re-counted from. Copying the map
|
|
888
|
+
// untouched beside a re-derived breakdown is how the two come to
|
|
889
|
+
// contradict each other, and the contradiction is refused downstream by
|
|
890
|
+
// `projectApprovedFindings`, which throws.
|
|
891
|
+
...(report.summary.lens_coverage === undefined
|
|
892
|
+
? {}
|
|
893
|
+
: {
|
|
894
|
+
lens_coverage: reprojectLensCoverage(report.summary.lens_coverage, report.findings),
|
|
895
|
+
}),
|
|
466
896
|
...(Object.keys(groundingBreakdown).length > 0
|
|
467
897
|
? { grounding_status_breakdown: groundingBreakdown }
|
|
468
898
|
: {}),
|
|
899
|
+
...(Object.keys(verificationBreakdown).length > 0
|
|
900
|
+
? { verification_status_breakdown: verificationBreakdown }
|
|
901
|
+
: {}),
|
|
469
902
|
},
|
|
470
903
|
};
|
|
471
904
|
}
|