From b9caa53ec84360b3dd4d8b301a406a7ac5a42882 Mon Sep 17 00:00:00 2001 From: mbx30 <212453881+mberrys@users.noreply.github.com> Date: Thu, 24 Sep 2026 18:04:24 -0700 Subject: [PATCH 1/3] feat(core): define revision-bound inspection receipt (#16) --- LoopLibCore/sources/pdfpreflightverdict.cpp | 195 ++++++++++++++++++ LoopLibCore/sources/pdfpreflightverdict.h | 34 +++ UnitTests/tst_preflightverdicttest.cpp | 147 +++++++++++++ ...-issue-16-inspection-receipt.evidence.yaml | 27 +++ changes/codex-issue-16-inspection-receipt.md | 4 + docs/PREFLIGHT_VERDICT.md | 19 ++ 6 files changed, 426 insertions(+) create mode 100644 changes/codex-issue-16-inspection-receipt.evidence.yaml create mode 100644 changes/codex-issue-16-inspection-receipt.md diff --git a/LoopLibCore/sources/pdfpreflightverdict.cpp b/LoopLibCore/sources/pdfpreflightverdict.cpp index 2703882b5..c6ec558a7 100644 --- a/LoopLibCore/sources/pdfpreflightverdict.cpp +++ b/LoopLibCore/sources/pdfpreflightverdict.cpp @@ -28,10 +28,13 @@ #include "preflightprofileresolver.h" #include +#include #include +#include #include #include +#include namespace pdf { @@ -483,6 +486,198 @@ PreflightVerdict reducePreflightVerdict(const PreflightResult& result, return verdict; } +bool buildPreflightInspectionReceipt(const PreflightResult& result, + const PreflightProfileData& profile, + const PDFRevisionIdentity& revision, + const PDFEvidenceGraph& evidence, + PreflightInspectionReceipt& receipt, + QString& errorMessage) +{ + receipt = {}; + if (!isPDFSha256(result.documentRevisionDigest) || !isPDFSha256(profile.effectiveDigest) || + result.effectiveProfileDigest.compare(profile.effectiveDigest, Qt::CaseInsensitive) != 0 || + !revision.isValid()) + { + errorMessage = QStringLiteral("Inspection receipt requires a valid input digest, matching effective profile digest and document revision."); + return false; + } + if ((!evidence.artifact.sha256.isEmpty() && + evidence.artifact.sha256.compare(result.documentRevisionDigest, Qt::CaseInsensitive) != 0) || + (evidence.revision.isValid() && evidence.revision != revision)) + { + errorMessage = QStringLiteral("Inspection evidence belongs to a different input or revision."); + return false; + } + + PreflightInspectionReceipt candidate; + candidate.inputDigest = result.documentRevisionDigest.toLower(); + candidate.revision = revision; + candidate.effectiveProfileDigest = profile.effectiveDigest.toLower(); + candidate.profileIdentity = result.profileIdentity.isEmpty() ? profile.profileIdentity : result.profileIdentity; + candidate.coverageScope = result.coverageScope; + candidate.verdict = reducePreflightVerdict(result, &profile); + + bool incompleteCoverage = false; + QSet declaredChecks; + const auto appendCheck = [&](const QString& id, bool required) + { + PreflightReceiptCheck check; + check.id = id; + check.required = required; + const auto status = std::find_if(result.checkStatuses.cbegin(), result.checkStatuses.cend(), + [&id](const PreflightCheckStatus& value) + { return value.id == id; }); + if (status != result.checkStatuses.cend()) + { + check.status = status->status; + check.reason = status->reason; + const bool unique = std::find_if(std::next(status), result.checkStatuses.cend(), + [&id](const PreflightCheckStatus& value) + { return value.id == id; }) == result.checkStatuses.cend(); + check.complete = unique && status->budgetKind.isEmpty() && + (status->status == QLatin1String("ok") || + status->status == QLatin1String("warning") || + status->status == QLatin1String("failed")); + } + if (!check.complete) + { + incompleteCoverage = true; + QString reason = check.reason; + if (reason.isEmpty()) + { + reason = check.status.isEmpty() ? QStringLiteral("no status") : check.status; + } + candidate.limitations.append(QStringLiteral("Check '%1' did not complete: %2") + .arg(id, reason)); + } + candidate.checks.append(std::move(check)); + }; + + for (const PreflightCheckConfig& check : profile.checks) + { + if (!check.enabled) + { + continue; + } + if (check.id.isEmpty() || declaredChecks.contains(check.id)) + { + errorMessage = QStringLiteral("Inspection profile has an empty or duplicate enabled check ID."); + return false; + } + declaredChecks.insert(check.id); + appendCheck(check.id, check.required); + } + if (profile.pdfx.has_value()) + { + appendCheck(QStringLiteral("pdfx"), true); + } + if (candidate.checks.isEmpty() || candidate.coverageScope.isEmpty()) + { + incompleteCoverage = true; + candidate.limitations.append(QStringLiteral("Inspection check coverage or scope was not recorded.")); + } + for (const PreflightCheckStatus& status : result.checkStatuses) + { + if (!declaredChecks.contains(status.id) && status.id != QLatin1String("pdfx") && + (!status.budgetKind.isEmpty() || status.status == QLatin1String("incomplete") || + status.status == QLatin1String("unsupported") || status.status == QLatin1String("skipped") || + status.status == QLatin1String("not_inspected"))) + { + incompleteCoverage = true; + candidate.limitations.append(QStringLiteral("Inspection status '%1' did not complete.").arg(status.id)); + } + } + + QSet evidenceIds; + int fidelityRank = 3; + for (const PDFEvidenceRecord& record : evidence.records) + { + if ((!record.artifact.sha256.isEmpty() && + record.artifact.sha256.compare(result.documentRevisionDigest, Qt::CaseInsensitive) != 0) || + (record.revision.isValid() && record.revision != revision)) + { + errorMessage = QStringLiteral("Inspection evidence record belongs to a different input or revision."); + return false; + } + if (!record.id.isEmpty()) + { + evidenceIds.insert(record.id); + } + if (!record.incompleteReason.isEmpty()) + { + incompleteCoverage = true; + candidate.limitations.append(record.incompleteReason); + } + if (record.fidelity == QLatin1String("catalog")) + { + fidelityRank = std::min(fidelityRank, 1); + } + else if (record.fidelity == QLatin1String("sampled")) + { + fidelityRank = std::min(fidelityRank, 2); + } + else if (record.fidelity != QLatin1String("exact")) + { + fidelityRank = 0; + incompleteCoverage = true; + candidate.limitations.append(QStringLiteral("Evidence fidelity is unsupported or unknown.")); + } + } + const auto appendFindingEvidence = [&evidenceIds](const QList& findings) + { + for (const PreflightFinding& finding : findings) + { + for (const QString& id : finding.evidenceIds) + { + if (!id.isEmpty()) + { + evidenceIds.insert(id); + } + } + } + }; + appendFindingEvidence(result.errors); + appendFindingEvidence(result.warnings); + candidate.evidenceRefs = evidenceIds.values(); + candidate.evidenceRefs.sort(); + candidate.fidelity = evidence.records.isEmpty() ? QStringLiteral("not-recorded") + : fidelityRank == 3 ? QStringLiteral("exact") + : fidelityRank == 2 ? QStringLiteral("sampled") + : fidelityRank == 1 ? QStringLiteral("catalog") + : QStringLiteral("unsupported"); + if (!evidence.isComplete()) + { + incompleteCoverage = true; + candidate.limitations.append(evidence.incompleteReason.isEmpty() + ? QStringLiteral("Evidence collection did not complete.") + : evidence.incompleteReason); + } + const QString coverageClaim = candidate.coverageScope.value(QStringLiteral("claim")).toString(); + if (!coverageClaim.isEmpty()) + { + candidate.limitations.append(coverageClaim); + } + candidate.limitations.removeDuplicates(); + if (candidate.verdict.isPass() && incompleteCoverage) + { + candidate.verdict.state = PreflightVerdictState::Incomplete; + candidate.verdict.reasonCode = QStringLiteral("receipt-evidence-incomplete"); + candidate.verdict.reason = QStringLiteral("Required inspection coverage or evidence did not complete."); + } + + const QJsonObject identityData{ + { QStringLiteral("kind"), QStringLiteral("loop.inspection-receipt-identity.v1") }, + { QStringLiteral("input_digest"), candidate.inputDigest }, + { QStringLiteral("effective_profile_digest"), candidate.effectiveProfileDigest }, + { QStringLiteral("coverage_scope"), candidate.coverageScope } + }; + candidate.identity = QString::fromLatin1( + QCryptographicHash::hash(canonicalJson(identityData), QCryptographicHash::Sha256).toHex()); + receipt = std::move(candidate); + errorMessage.clear(); + return true; +} + PDFOperationResult runMandatoryPostflight(PDFDocument* document, const QString& profilePath, PreflightVerdict* verdictOut, diff --git a/LoopLibCore/sources/pdfpreflightverdict.h b/LoopLibCore/sources/pdfpreflightverdict.h index 5199c27ef..f1b506af8 100644 --- a/LoopLibCore/sources/pdfpreflightverdict.h +++ b/LoopLibCore/sources/pdfpreflightverdict.h @@ -77,6 +77,40 @@ LOOPLIBCORESHARED_EXPORT QString preflightGateFailureMessage(const QString& file LOOPLIBCORESHARED_EXPORT PreflightVerdict reducePreflightVerdict(const PreflightResult& result, const PreflightProfileData* effectiveProfile = nullptr); +struct LOOPLIBCORESHARED_EXPORT PreflightReceiptCheck +{ + QString id; + bool required = false; + bool complete = false; + QString status; + QString reason; +}; + +struct LOOPLIBCORESHARED_EXPORT PreflightInspectionReceipt +{ + QString identity; + QString inputDigest; + PDFRevisionIdentity revision; + QString effectiveProfileDigest; + QJsonObject profileIdentity; + QJsonObject coverageScope; + QList checks; + QStringList evidenceRefs; + QString fidelity; + QStringList limitations; + PreflightVerdict verdict; +}; + +/// Binds one Core result to its input revision and evidence. The identity is +/// stable for the same input, effective profile and coverage policy; a missing +/// required check or unsupported evidence cannot produce PASS. +LOOPLIBCORESHARED_EXPORT bool buildPreflightInspectionReceipt(const PreflightResult& result, + const PreflightProfileData& profile, + const PDFRevisionIdentity& revision, + const PDFEvidenceGraph& evidence, + PreflightInspectionReceipt& receipt, + QString& errorMessage); + /// The single Core planner used by step postflight, check selection and impact /// qualification. An operation-wide/uncertain declaration cannot be narrowed /// by page-local repair targets. diff --git a/UnitTests/tst_preflightverdicttest.cpp b/UnitTests/tst_preflightverdicttest.cpp index c46fcb804..de34b105b 100644 --- a/UnitTests/tst_preflightverdicttest.cpp +++ b/UnitTests/tst_preflightverdicttest.cpp @@ -63,6 +63,10 @@ private slots: void incompleteInspectionWithoutFindings_isNotPass(); void cancellationMarkedIncomplete_isNotPass(); void requiredCheckMissingStatus_isIncomplete(); + void receiptIdentity_matchesGoldenVector(); + void receiptTerminalStates_data(); + void receiptTerminalStates(); + void receiptRejectsMismatchedProvenance(); void processExitCodes_matchPdfToolContract(); void budgetExceeded_neverAllowsCertificate(); void operatorSummary_distinguishesIncompleteFromPass(); @@ -136,6 +140,33 @@ pdf::PreflightResult budgetExceededResult() return result; } +struct ReceiptFixture +{ + pdf::PreflightResult result; + pdf::PreflightProfileData profile; + pdf::PDFRevisionIdentity revision; + pdf::PDFEvidenceGraph evidence; + + ReceiptFixture() + { + result.documentRevisionDigest = QString(64, QLatin1Char('a')); + result.effectiveProfileDigest = QString(64, QLatin1Char('b')); + result.coverageScope = QJsonObject{ + { QStringLiteral("claim"), QStringLiteral("Limited to enabled checks.") }, + { QStringLiteral("enabled_checks"), QJsonArray{ QStringLiteral("bleed") } } + }; + result.checkStatuses.append(makeCheckStatus(QStringLiteral("bleed"), QStringLiteral("ok"))); + profile.effectiveDigest = result.effectiveProfileDigest; + pdf::PreflightCheckConfig check; + check.id = QStringLiteral("bleed"); + check.enabled = true; + check.required = true; + profile.checks.append(check); + revision.document.documentId = QStringLiteral("receipt-fixture"); + revision.documentRevision = 1; + } +}; + /// A translator with no .qm file behind it: it answers one message in the /// Core verdict context, which is exactly what a shipped catalogue would do. class StubVerdictTranslator final : public QTranslator @@ -966,6 +997,122 @@ void PreflightVerdictTest::requiredCheckMissingStatus_isIncomplete() QVERIFY(!verdict.isPass()); } +void PreflightVerdictTest::receiptIdentity_matchesGoldenVector() +{ + ReceiptFixture fixture; + pdf::PDFEvidenceRecord record; + record.id = QStringLiteral("evidence-2"); + record.fidelity = QStringLiteral("sampled"); + fixture.evidence.records.append(record); + pdf::PreflightFinding warning; + warning.evidenceIds.append(QStringLiteral("evidence-1")); + fixture.result.warnings.append(warning); + + pdf::PreflightInspectionReceipt receipt; + QString error; + QVERIFY2(pdf::buildPreflightInspectionReceipt(fixture.result, fixture.profile, fixture.revision, + fixture.evidence, receipt, error), + qPrintable(error)); + QCOMPARE(receipt.identity, QStringLiteral("8e94a7fefac162eafa4a20516692fb281c28978788b9d91d4df3397061933a8b")); + QCOMPARE(receipt.verdict.state, pdf::PreflightVerdictState::Pass); + QCOMPARE(receipt.checks.size(), 1); + QVERIFY(receipt.checks.first().complete); + QCOMPARE(receipt.evidenceRefs, (QStringList{ QStringLiteral("evidence-1"), QStringLiteral("evidence-2") })); + QCOMPARE(receipt.fidelity, QStringLiteral("sampled")); + QCOMPARE(receipt.limitations, QStringList{ QStringLiteral("Limited to enabled checks.") }); + + fixture.revision.documentRevision = 2; + pdf::PreflightInspectionReceipt later; + QVERIFY2(pdf::buildPreflightInspectionReceipt(fixture.result, fixture.profile, fixture.revision, + fixture.evidence, later, error), + qPrintable(error)); + QCOMPARE(later.identity, receipt.identity); + QCOMPARE(later.revision.documentRevision, pdf::DocumentRevision(2)); + + fixture.profile.effectiveDigest = QString(64, QLatin1Char('c')); + fixture.result.effectiveProfileDigest = fixture.profile.effectiveDigest; + pdf::PreflightInspectionReceipt changedPolicy; + QVERIFY2(pdf::buildPreflightInspectionReceipt(fixture.result, fixture.profile, fixture.revision, + fixture.evidence, changedPolicy, error), + qPrintable(error)); + QCOMPARE(changedPolicy.identity, QStringLiteral("931952b4c72f56e67fa371c94eb01bec4383cab251286a3c675c7d8dcada11f8")); +} + +void PreflightVerdictTest::receiptTerminalStates_data() +{ + QTest::addColumn("caseName"); + QTest::addColumn("expected"); + QTest::newRow("pass") << QStringLiteral("pass") << pdf::PreflightVerdictState::Pass; + QTest::newRow("fail") << QStringLiteral("fail") << pdf::PreflightVerdictState::Fail; + QTest::newRow("missing-required") << QStringLiteral("missing-required") << pdf::PreflightVerdictState::Incomplete; + QTest::newRow("unsupported") << QStringLiteral("unsupported") << pdf::PreflightVerdictState::Incomplete; + QTest::newRow("budget-limited") << QStringLiteral("budget-limited") << pdf::PreflightVerdictState::Incomplete; + QTest::newRow("cancelled") << QStringLiteral("cancelled") << pdf::PreflightVerdictState::Incomplete; + QTest::newRow("parser-error") << QStringLiteral("parser-error") << pdf::PreflightVerdictState::Error; +} + +void PreflightVerdictTest::receiptTerminalStates() +{ + QFETCH(QString, caseName); + QFETCH(pdf::PreflightVerdictState, expected); + ReceiptFixture fixture; + if (caseName == QLatin1String("fail")) + { + fixture.result.errors.append(blockingFinding()); + } + else if (caseName == QLatin1String("missing-required")) + { + fixture.result.checkStatuses.clear(); + } + else if (caseName == QLatin1String("unsupported")) + { + fixture.result.checkStatuses.first().status = QStringLiteral("unsupported"); + } + else if (caseName == QLatin1String("budget-limited")) + { + fixture.result.checkStatuses.first().budgetKind = QStringLiteral("raster-pixels"); + } + else if (caseName == QLatin1String("cancelled")) + { + fixture.result.errorCode = QStringLiteral("cancelled"); + } + else if (caseName == QLatin1String("parser-error")) + { + fixture.result.errorCode = QStringLiteral("parser-error"); + } + + pdf::PreflightInspectionReceipt receipt; + QString error; + QVERIFY2(pdf::buildPreflightInspectionReceipt(fixture.result, fixture.profile, fixture.revision, + fixture.evidence, receipt, error), + qPrintable(error)); + QCOMPARE(receipt.verdict.state, expected); + QCOMPARE(receipt.verdict.isPass(), expected == pdf::PreflightVerdictState::Pass); + if (expected == pdf::PreflightVerdictState::Incomplete) + { + QVERIFY(!receipt.verdict.allowsCertificateIssuance()); + } +} + +void PreflightVerdictTest::receiptRejectsMismatchedProvenance() +{ + ReceiptFixture fixture; + fixture.evidence.artifact.sha256 = QString(64, QLatin1Char('c')); + pdf::PreflightInspectionReceipt receipt; + QString error; + QVERIFY(!pdf::buildPreflightInspectionReceipt(fixture.result, fixture.profile, fixture.revision, + fixture.evidence, receipt, error)); + QVERIFY(!error.isEmpty()); + QVERIFY(receipt.identity.isEmpty()); + + fixture.evidence.artifact.sha256.clear(); + fixture.evidence.revision = fixture.revision; + fixture.evidence.revision.documentRevision = 2; + QVERIFY(!pdf::buildPreflightInspectionReceipt(fixture.result, fixture.profile, fixture.revision, + fixture.evidence, receipt, error)); + QVERIFY(receipt.identity.isEmpty()); +} + void PreflightVerdictTest::processExitCodes_matchPdfToolContract() { QCOMPARE(pdf::preflightVerdictProcessExitCode(pdf::PreflightVerdictState::Pass), 0); diff --git a/changes/codex-issue-16-inspection-receipt.evidence.yaml b/changes/codex-issue-16-inspection-receipt.evidence.yaml new file mode 100644 index 000000000..0e016ba80 --- /dev/null +++ b/changes/codex-issue-16-inspection-receipt.evidence.yaml @@ -0,0 +1,27 @@ +format_version: 1 +kind: evidence +claims: + - id: core-inspection-receipt + evidence: + - unit:agent-policy:core + - unit:UnitTestsPreflightVerdict + - architecture:agent-policy:core + - packaging:linux-build + - packaging:windows-build + - id: preflight-coverage-and-verdict + evidence: + - unit:agent-policy:preflight + - unit:UnitTestsPreflightChecks + - unit:UnitTestsPreflightEngine + - unit:UnitTestsPreflightInteraction + - unit:UnitTestsPreflightVerdict + - unit:UnitTestsPreflightProfileResolver + - unit:UnitTestsProfileIdentity + - unit:UnitTestsOperatorAcceptance + - integration:UnitTestsPreflightCorpus + - integration:UnitTestsPreflightWorkflowAcceptance + - differential:UnitTestsStandardOracle + - architecture:loop-preflight/testdata/fixtures + - architecture:docs/generated/preflight-check-catalog.json + - architecture:docs/generated/preflight-corpus-coverage.json +unresolved: [] diff --git a/changes/codex-issue-16-inspection-receipt.md b/changes/codex-issue-16-inspection-receipt.md new file mode 100644 index 000000000..ac064051f --- /dev/null +++ b/changes/codex-issue-16-inspection-receipt.md @@ -0,0 +1,4 @@ +Category: added +Audience: integrators +Breaking-Change: no +Summary: Add a revision-bound typed Core inspection receipt with stable input-policy identity, explicit coverage and evidence limitations, and fail-closed verdicts. diff --git a/docs/PREFLIGHT_VERDICT.md b/docs/PREFLIGHT_VERDICT.md index d8bdee88c..5cad6a70e 100644 --- a/docs/PREFLIGHT_VERDICT.md +++ b/docs/PREFLIGHT_VERDICT.md @@ -40,3 +40,22 @@ postflight, Action List step results, the Editor controller, and the certificate gate consume this same contract. New surfaces must call the Core reducer or consume the normalized `verdict` object; they must not infer status from `errors.isEmpty()` or `findings.isEmpty()`. + +## Typed inspection receipt + +`buildPreflightInspectionReceipt()` projects a Core result, its effective +profile, the current `PDFRevisionIdentity`, and the evidence graph into one +`PreflightInspectionReceipt`. The receipt carries the exact input digest, +revision, profile identity and digest, coverage scope and per-check completion, +sorted evidence IDs, weakest recorded evidence fidelity, explicit limitations, +and the canonical four-state verdict. It is an in-memory Core contract; the +preflight report and evidence-bundle schemas are unchanged. + +The receipt identity is SHA-256 over canonical JSON containing the input digest, +effective profile digest, and evaluated coverage scope, with a versioned kind. +It is stable when the same bytes and policy are inspected again, even if the +session revision counter changes. A missing, duplicate, unsupported, skipped, +or budget-limited check status, incomplete evidence, or unsupported fidelity +prevents a PASS receipt. A definite blocking finding remains FAIL, with any +coverage limitation still visible. Mismatched input, profile, or revision +provenance rejects receipt construction before publication. From 2bd7d2a992fae672ede39e74d05d3ff91dffeee7 Mon Sep 17 00:00:00 2001 From: mbx30 <212453881+mberrys@users.noreply.github.com> Date: Thu, 24 Sep 2026 18:19:01 -0700 Subject: [PATCH 2/3] fix(agent): run mapped CTest with Release configuration --- changes/codex-issue-16-inspection-receipt.evidence.yaml | 6 +++++- changes/codex-issue-16-inspection-receipt.md | 2 +- scripts/agent/check-change.py | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/changes/codex-issue-16-inspection-receipt.evidence.yaml b/changes/codex-issue-16-inspection-receipt.evidence.yaml index 0e016ba80..9461215a1 100644 --- a/changes/codex-issue-16-inspection-receipt.evidence.yaml +++ b/changes/codex-issue-16-inspection-receipt.evidence.yaml @@ -6,6 +6,9 @@ claims: - unit:agent-policy:core - unit:UnitTestsPreflightVerdict - architecture:agent-policy:core + - architecture:docs/generated/architecture-catalog.json + - security:scripts/ci/check_source_integrity.py + - differential:UnitTestsConversionOracle - packaging:linux-build - packaging:windows-build - id: preflight-coverage-and-verdict @@ -24,4 +27,5 @@ claims: - architecture:loop-preflight/testdata/fixtures - architecture:docs/generated/preflight-check-catalog.json - architecture:docs/generated/preflight-corpus-coverage.json -unresolved: [] +unresolved: + - core:scripts/ci/check_independent_validation_gate.py diff --git a/changes/codex-issue-16-inspection-receipt.md b/changes/codex-issue-16-inspection-receipt.md index ac064051f..8afef67f7 100644 --- a/changes/codex-issue-16-inspection-receipt.md +++ b/changes/codex-issue-16-inspection-receipt.md @@ -1,4 +1,4 @@ Category: added Audience: integrators Breaking-Change: no -Summary: Add a revision-bound typed Core inspection receipt with stable input-policy identity, explicit coverage and evidence limitations, and fail-closed verdicts. +Summary: Add a revision-bound typed Core inspection receipt with stable identity and fail-closed verdicts, and fix the mapped CTest gate for multi-config builds. diff --git a/scripts/agent/check-change.py b/scripts/agent/check-change.py index aa9698c3f..8140733d3 100644 --- a/scripts/agent/check-change.py +++ b/scripts/agent/check-change.py @@ -488,7 +488,7 @@ def main() -> int: add_clang_tidy_checks(evidence, sources, build_dir, dry_run=args.dry_run) if tests: expression = "^(" + "|".join(re.escape(test) for test in tests) + ")$" - add_result(evidence, "focused_tests", ["ctest", "--test-dir", str(build_dir), "--output-on-failure", "-R", expression], ROOT, args.dry_run) + add_result(evidence, "focused_tests", ["ctest", "--test-dir", str(build_dir), "-C", "Release", "--output-on-failure", "-R", expression], ROOT, args.dry_run) report = { "format_version": 1, From 874c953bd45f989c7881ad08b5480be9cf9c1dfc Mon Sep 17 00:00:00 2001 From: mbx30 <212453881+mberrys@users.noreply.github.com> Date: Thu, 24 Sep 2026 18:28:24 -0700 Subject: [PATCH 3/3] docs(evidence): bind agent-policy proof lane for receipt PR --- changes/codex-issue-16-inspection-receipt.evidence.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/changes/codex-issue-16-inspection-receipt.evidence.yaml b/changes/codex-issue-16-inspection-receipt.evidence.yaml index 9461215a1..2a7cc4b8a 100644 --- a/changes/codex-issue-16-inspection-receipt.evidence.yaml +++ b/changes/codex-issue-16-inspection-receipt.evidence.yaml @@ -27,5 +27,10 @@ claims: - architecture:loop-preflight/testdata/fixtures - architecture:docs/generated/preflight-check-catalog.json - architecture:docs/generated/preflight-corpus-coverage.json + - id: agent-policy-gate + evidence: + - unit:scripts/agent/test_architecture_contracts.py + - architecture:scripts/agent/check-architecture.py + - architecture:scripts/agent/generate-adapters.py unresolved: - core:scripts/ci/check_independent_validation_gate.py