Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions packages/artifacts/src/semantic-gates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1229,6 +1229,15 @@ function reportBoundedDedupePreservationIssues(document: unknown, context: Seman

for (const field of Object.keys(finding)) {
if (expectedDisposition === "promoted" && (field === "id" || field === "title")) continue;
// Bounded classification mode instructs the report to enrich the dedupe
// lifecycle record in place and to choose the row's classification, so a
// dedupe row that itself carries `lifecycle` or `triage_classification`
// can never byte-equal its enriched report row — every compliant report
// failed this loop (#911). Both fields already have dedicated
// enrichment-aware validation above: the lifecycle record is compared
// against the authenticated ledger with the exact owned-field set, and
// the row classification must equal the enriched lifecycle record's.
if (field === "lifecycle" || field === "triage_classification") continue;
if (!isDeepStrictEqual(reportEntry.row[field], finding[field])) {
issues.push(
issue(`${reportEntry.path}.${field}`, `Bounded report did not preserve dedupe field ${JSON.stringify(field)}`)
Expand Down
50 changes: 50 additions & 0 deletions packages/artifacts/test/semantic-gates.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3824,6 +3824,56 @@ test("bounded final reports close over every authenticated deduped finding", ()
context
});
assert.equal(droppedOmission.status, "failed", "a dropped false positive still requires one report row");

// A dedupe stage may itself emit rows that already carry a lifecycle record
// and a preliminary classification. Bounded classification mode then
// enriches both in the report row, so the generic preservation loop must not
// demand byte-equality for exactly those two fields — that contradiction
// failed every compliant bounded report (#911). The enriched values remain
// validated by their dedicated checks: owned-field lifecycle preservation
// against the authenticated ledger, and row-vs-lifecycle classification
// equality.
const embeddedFinding = {
...finding,
triage_classification: "undetermined",
lifecycle: structuredClone(lifecycle)
};
const embeddedContext = {
artifactSet: {
severityClassifiedFindings: null,
dedupedFindings: [embeddedFinding],
findingLifecycleLedger: { records: [lifecycle] }
}
};
const enrichedRow = {
...embeddedFinding,
triage_classification: "false-positive",
lifecycle: {
...lifecycle,
triage_classification: "false-positive",
triage_reason: "The source-backed precondition cannot occur.",
demotion_reason: "The candidate is a false positive.",
final_disposition: "dropped"
}
};
assert.equal(
executeSemanticGate("report-severity-classification-preservation", {
document: { issues: [], non_production_outcomes: [enrichedRow] },
context: embeddedContext
}).status,
"passed"
);
const tamperedProse = structuredClone(enrichedRow);
tamperedProse.summary = "Rewritten summary.";
const tampered = executeSemanticGate("report-severity-classification-preservation", {
document: { issues: [], non_production_outcomes: [tamperedProse] },
context: embeddedContext
});
assert.equal(tampered.status, "failed");
assert.ok(
tampered.status === "failed" &&
tampered.issues.some((entry) => /did not preserve dedupe field "summary"/u.test(entry.message))
);
});

test("differential reconciliation diagnostics expose exact expected machine-derived values", () => {
Expand Down