Skip to content

feat(catalog): feature 010 Phase F — post-output comparison, PASS at zero FP/FN (T086a, T087–T092) - #92

Merged
mbeacom merged 2 commits into
mainfrom
mbeacom-phase-f-post-output-comparison
Aug 6, 2026
Merged

feat(catalog): feature 010 Phase F — post-output comparison, PASS at zero FP/FN (T086a, T087–T092)#92
mbeacom merged 2 commits into
mainfrom
mbeacom-phase-f-post-output-comparison

Conversation

@mbeacom

@mbeacom mbeacom commented Aug 5, 2026

Copy link
Copy Markdown
Owner

ADR-0020 clause 5 step (b) — the post-output comparison. This is the first evidence that the adapter is correct, not merely well-formed: every prior phase proved structure, and a digest-verified envelope proves integrity rather than correctness.

Result: PASS

24 expected entities · 0 false positives · 0 false negatives · 0 other mismatches.

Derived ownership diffed against expectations that were frozen, hashed, and independently audited before the generator existed. No mismatch was found, so nothing had to be reported and nothing was amended.

Independently re-verified by the coordinating session

Not accepted on report:

  • The comparison was re-run from a clean installPASS — 24 expected entities, 0 false positive(s), 0 false negative(s), 0 other mismatch(es).
  • The ADR-0016 mutation was re-run → genuine FAIL naming the false negative, the false positive, and the corpus-wide pattern-union mismatch. It is not a check that cannot fail.
  • All 24 vendored descriptors were re-hashed independently — git blob SHA-1, SHA-256, and byte length all match the vendor manifest, 24/24.
  • check:freeze-hashes green throughout, and the frozen trees show an empty git status after every run.

The envelope carries 25 entities, not 24

One selected file (workspaces/acr/plugins/acr/catalog-info.yaml) holds a second, unselected document. It carries no overlay, derives nothing, and is recorded as an out-of-set entity rather than a finding — it would have been a false positive had it derived any path. This is the file-count vs entity-document-count distinction showing up in live data.

All three ownership states exercised in the frozen proportions: 22 explicit-paths, 1 explicit-empty, 1 annotation-absent. Every entity provenance: maintainer-overlay.

Frozen trees untouched — confirmed three ways

  1. git status --porcelain over both trees is empty.
  2. bun run check:freeze-hashes green.
  3. expectations-unchanged.json (T091): each frozen hash recomputed now equals the value recorded in the Barrier B checkpoint — written by the independent auditor session, not by the freeze's author. e641ae5e… and f98e6d46…, unchanged.

T086a — vendoring, and a defect in the instruction it was given

24 descriptors vendored verbatim and pristine to specs/010-catalog-backstage/corpus/. No adrkit.io/owned-paths in any descriptor. The overlay is applied in memory into an ephemeral run dir, never on disk — preserving the separation the freeze already maintains, so clause 5's "authored upstream and otherwise unmodified" stays provable by digest and the upstream/maintainer boundary stays legible.

The instruction said "verify each against the digest the freeze records". That digest does not exist. The freeze's only hashes are the two artifact-level contentHash values; there is no per-descriptor digest. Rather than skip the check or invent a hash, verification was done against what the freeze does fix — the commit pin — three independent ways, all aborting:

  1. tree listing confirmed not truncated;
  2. the pin yields exactly 156 exact-basename descriptor files, matching the freeze's own corpusFacts.descriptorFilesExactBasename — the strongest freeze-recorded cross-check available;
  3. each blob's git id recomputed from the bytes that arrived and matched against the pinned tree, cross-checked against git hash-object.

Stated plainly in the vendor manifest and corpus README rather than glossed.

Offline posture (FR-018) preserved: fetching is one-time acquisition, not part of a generation run. The acquisition path is not reachable from the generator.

Observed failing (ADR-0016)

--observe-failing mutates one overlay value, cliclx, one character:

compare-accept-corpus: FAIL — 24 expected entities, 1 false positive(s), 1 false negative(s), 1 other mismatch(es)
  false-negative …adr-backend: expected path was not derived — expected "packages/cli/**"
  false-positive …adr-backend: derived path is not in the frozen expectation — derived "packages/clx/**"
  other-mismatch (corpus-wide): … oracle records 25 patterns, output yields 26

Restored → PASS, exit 0. Retained at negative-cases/comparison-mismatch/. Every kernel failure mode is separately driven by an input built to trip it. T091 additionally drives the amended-then-re-signed case — self-consistent, and so would slip past the drift check alone.

Two judgement calls

  1. T086 left - [ ]. Vendoring closed its blocker and its limb-2 record was asserting something now false, so the fact and the assertion were corrected — but discharging SC-009 limb 2 means wiring a corpus pass into that close-out, which is T086's work, not T086a's.
  2. evidence/README.md §4 amended additively. Its "no generator output / no comparison harness" absences stopped being true of the directory the moment Phase F deposited. A §4.1 scopes them to the freeze trees, where they still hold verbatim, and records what comparison/ now holds. No original sentence deleted. Flagged because it touches a Barrier B document.

Checks

bun test 1791 pass / 0 fail (was 1721) · typecheck clean · check:deps ok · check:freeze-hashes ok · adr lint 20 records, 0 errors.

Standing

ADR-0014 rung 1 only. This PASS does not assert rung 2 or rung 3, does not ratify 24 as a production sample size, and does not clear ADR-0012 gate 3 as an external claim. Gate 4 remains unmet and not yet testable. No release scheduled, implied, or prepared.

…mparison (T086a, T087–T092)

ADR-0020 clause 5 requires two distinct steps. Step (a) — the pre-output freeze
and its independent audit — landed with Barrier B. This is step (b): run the
assembled generator over the frozen accept corpus and diff derived ownership
against the frozen expectations at zero false positives and zero false negatives.

Result: PASS. 24 expected entities, 0 false positives, 0 false negatives,
0 other mismatches. The frozen trees were not touched, and their hashes are
unchanged from the values T024 recorded before any generator existed.

T086a (new, prerequisite) — vendor the accept corpus. Phase E discovered the
corpus was not materialized: the freeze records metadata, never descriptor files,
and evidence/README.md §4 requires it stay that way. The 24 selected descriptors
are vendored verbatim from backstage/community-plugins at the pinned commit into
specs/010-catalog-backstage/corpus/, each verified by recomputing its git blob id
from the bytes that arrived and matching it against the id the pinned tree
records, with the pin cross-checked against the freeze's own 156-descriptor-file
figure. Any mismatch aborts and writes nothing.

The vendored bytes are pristine: no adrkit.io/owned-paths anywhere in the tree.
The maintainer-authored overlay stays in overlay.json and is applied at
generation time into a temporary directory that is deleted, so clause 5's
"otherwise unmodified" stays provable by digest and data-model.md §10's
upstream/maintainer boundary stays legible by inspection. Acquisition is a
one-time step and is not reachable from the generator or the harness, asserted by
scanning import specifiers rather than promised in prose.

T087 — scripts/compare-accept-corpus.ts, authored after the freeze and its audit
(R5 mechanism 3), with the provenance record that carries the ordering claim.
T088 — the diff itself. The comparison reads both frozen artifacts and refuses to
run if they disagree. Entities outside the expectation set are findings only if
they derive a non-empty path set: the 24 files hold 25 entity documents, and a
descriptor file count is not an entity document count.
T089 — observed failing. One character in one overlay value (cli → clx) yields
exactly one false negative, one false positive, and the corpus-wide union
mismatch; restoring returns PASS. Retained at negative-cases/comparison-mismatch/.
T090 — step (b) recomputes the frozen hashes rather than trusting them, records
hashes that are its own (corpus, overlaid inputs, envelope), and renders its own
verdict. It inherits nothing from step (a).
T091 — the prohibition guard. Every frozen hash is compared against the value the
Barrier B checkpoint recorded, and the amended-then-re-signed case is driven to
show why self-consistency alone would not catch it.
T092 — reporting honesty. A digest establishes integrity; this comparison
establishes agreement with a maintainer-authored expectation set. Neither
establishes that the adapter is correct, and every Phase F artifact is scanned
for affirmative correctness and rung overclaims.

T086 stays unchecked: its SC-009 limb-2 record is corrected to the new fact, but
wiring a corpus pass into that close-out is T086's own remaining work.

ADR-0014 rung 1 only. No release is scheduled, prepared, or implied.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings August 5, 2026 23:35
Comment thread scripts/vendor-accept-corpus.test.ts Fixed

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Hash-guard failures can still record and exit as PASS, and task dependencies and correctness claims are inconsistent.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Pull request overview

Adds Phase F’s frozen-corpus comparison evidence, validating generated ownership against 24 audited expectations.

Changes:

  • Vendors and verifies 24 upstream descriptors.
  • Adds comparison, mutation, hash-drift, and honesty checks.
  • Records zero false positives/negatives and updates phase tracking.
File summaries
File Description
specs/010-catalog-backstage/tasks.md Updates Phase F tasks.
specs/010-catalog-backstage/evidence/README.md Documents comparison evidence.
specs/010-catalog-backstage/evidence/negative-cases/comparison-mismatch/README.md Documents the negative case.
specs/010-catalog-backstage/evidence/negative-cases/comparison-mismatch/observed-fail.txt Captures observed failure.
specs/010-catalog-backstage/evidence/negative-cases/comparison-mismatch/restored.observed.txt Captures restored pass.
specs/010-catalog-backstage/evidence/comparison/README.md Summarizes comparison evidence.
specs/010-catalog-backstage/evidence/comparison/diff-report.json Records ownership comparison.
specs/010-catalog-backstage/evidence/comparison/expectations-unchanged.json Records freeze immutability.
specs/010-catalog-backstage/evidence/comparison/harness-provenance.md Records harness ordering.
specs/010-catalog-backstage/evidence/comparison/reporting-honesty.md Defines evidence limitations.
specs/010-catalog-backstage/evidence/comparison/step-b-record.json Records step-B hashes and verdict.
specs/010-catalog-backstage/corpus/README.md Documents vendored corpus.
specs/010-catalog-backstage/corpus/VENDOR-MANIFEST.json Records corpus digests.
specs/010-catalog-backstage/corpus/workspaces/acr/plugins/acr/catalog-info.yaml Vendors upstream descriptor.
specs/010-catalog-backstage/corpus/workspaces/adr/plugins/adr-backend/catalog-info.yaml Vendors upstream descriptor.
specs/010-catalog-backstage/corpus/workspaces/agent-forge/plugins/agent-forge/catalog-info.yaml Vendors upstream descriptor.
specs/010-catalog-backstage/corpus/workspaces/airbrake/plugins/airbrake-backend/catalog-info.yaml Vendors upstream descriptor.
specs/010-catalog-backstage/corpus/workspaces/allure/plugins/allure/catalog-info.yaml Vendors upstream descriptor.
specs/010-catalog-backstage/corpus/workspaces/analytics/plugins/analytics-module-ga4/catalog-info.yaml Vendors upstream descriptor.
specs/010-catalog-backstage/corpus/workspaces/apache-airflow/plugins/apache-airflow/catalog-info.yaml Vendors upstream descriptor.
specs/010-catalog-backstage/corpus/workspaces/apollo-explorer/plugins/apollo-explorer/catalog-info.yaml Vendors upstream descriptor.
specs/010-catalog-backstage/corpus/workspaces/azure-devops/plugins/azure-devops-common/catalog-info.yaml Vendors upstream descriptor.
specs/010-catalog-backstage/corpus/workspaces/azure-sites/plugins/azure-sites-backend/catalog-info.yaml Vendors upstream descriptor.
specs/010-catalog-backstage/corpus/workspaces/badges/plugins/badges-backend/catalog-info.yaml Vendors upstream descriptor.
specs/010-catalog-backstage/corpus/workspaces/bazaar/plugins/bazaar-backend/catalog-info.yaml Vendors upstream descriptor.
specs/010-catalog-backstage/corpus/workspaces/bitbucket-pull-requests/catalog-info.yaml Vendors upstream descriptor.
specs/010-catalog-backstage/corpus/workspaces/bitrise/plugins/bitrise/catalog-info.yaml Vendors upstream descriptor.
specs/010-catalog-backstage/corpus/workspaces/bookmarks/plugins/bookmarks/examples/component/catalog-info.yaml Vendors upstream descriptor.
specs/010-catalog-backstage/corpus/workspaces/catalog/plugins/catalog-backend-module-codeowners/catalog-info.yaml Vendors upstream descriptor.
specs/010-catalog-backstage/corpus/workspaces/checkmarx/plugins/checkmarx-backend/catalog-info.yaml Vendors upstream descriptor.
specs/010-catalog-backstage/corpus/workspaces/cicd-statistics/plugins/cicd-statistics-module-buildkite/catalog-info.yaml Vendors upstream descriptor.
specs/010-catalog-backstage/corpus/workspaces/cloudbuild/plugins/cloudbuild/catalog-info.yaml Vendors upstream descriptor.
specs/010-catalog-backstage/corpus/workspaces/code-climate/plugins/code-climate/catalog-info.yaml Vendors upstream descriptor.
specs/010-catalog-backstage/corpus/workspaces/code-coverage/plugins/code-coverage-backend/catalog-info.yaml Vendors upstream descriptor.
specs/010-catalog-backstage/corpus/workspaces/codescene/plugins/codescene/catalog-info.yaml Vendors upstream descriptor.
specs/010-catalog-backstage/corpus/workspaces/copilot/plugins/copilot-backend/catalog-info.yaml Vendors upstream descriptor.
specs/010-catalog-backstage/corpus/workspaces/cost-insights/plugins/cost-insights-common/catalog-info.yaml Vendors upstream descriptor.
scripts/vendor-accept-corpus.ts Implements verified acquisition.
scripts/vendor-accept-corpus.test.ts Tests corpus verification.
scripts/compare-accept-corpus.ts Implements comparison harness.
scripts/compare-accept-corpus.test.ts Tests comparison and honesty.
scripts/check-freeze-hashes.test.ts Extends immutability checks.
packages/adapters/catalog-backstage/test/sc-009.test.ts Updates corpus availability status.
Review details
  • Files reviewed: 43/43 changed files
  • Comments generated: 7
  • Review effort level: Balanced

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

@@ -1094,7 +1126,7 @@ the barrier exists to prevent.
Discharges: none — implements R5 mechanism 3
Depends: T024, T086
ownership result, and its content is fixed by an upstream commit rather than by
anything derived from the frozen expectations.
Discharges: none — unblocks T088, and unblocks SC-009 limb 2 for T086
Depends: T024, T086
* names: it must contain the forbidden phrases in order to forbid them.
*/
const SCANNED = [
'specs/010-catalog-backstage/evidence/comparison/harness-provenance.md',
// ── T088: the real run, and the report it wrote ───────────────────────────────────────

describe('T088 / SC-011 \u2014 the real comparison over the frozen accept corpus', () => {
test('zero false positives and zero false negatives over all 24 annotated entities', async () => {
barrierSide: 'BEHIND',
discharges: ['FR-057 (step (b) half)'],
step: 'ADR-0020 clause 5 step (b) — post-output comparison',
verdict: report.verdict,
for (const finding of [...falseNegatives, ...falsePositives, ...otherMismatches]) {
console.error(` ${finding.kind} ${finding.canonicalId}: ${finding.reason} — ${finding.detail}`);
}
if (report.verdict === 'FAIL') process.exitCode = 1;
Comment on lines +290 to +294
"whatThisDoesAndDoesNotEstablish": {
"establishes": "That the generator’s derived ownership over this frozen corpus agrees, exactly and in order, with a maintainer-authored expectation set frozen before any generator output existed.",
"doesNotEstablish": [
"It does not establish correctness in any absolute sense. The expectations are the maintainer’s own, hand-derived from frozen contracts; agreement between our implementation and our specification is not independent evidence that either is right.",
"The envelope’s self-digest establishes INTEGRITY, not correctness. A semantically wrong envelope can carry a perfectly valid self-digest (ADR-0020 clause 5, FR-058, SC-012).",
CodeQL flagged `text.includes('https://api.github.com')` under
js/incomplete-url-substring-sanitization at high severity. The security
concern does not apply here - this scans our own source text to check that
exactly one script names the corpus host, and sanitizes nothing - but the
rule is right that substring-matching a URL is a poor way to reason about
hosts, and the guard had a real weakness because of it.

The old check missed `http://api.github.com` and protocol-relative
`//api.github.com`. Matching the hostname as a token catches every scheme,
so this is the stronger guard rather than a differently-spelled one.

The trailing (?![\w.-]) is what keeps it honest: it rejects
`api.github.com.evil.com`, which is the exact defect the CodeQL rule exists
to name and which a substring test would have accepted.

Verified against six cases: the real usage, http, protocol-relative, the
attack shape, an unrelated near-miss, and a clean file - all correct.

Gates: bun test 1791 pass / 0 fail, typecheck clean, check:freeze-hashes ok,
and compare-accept-corpus still PASS at 0 false positives / 0 false
negatives.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@mbeacom
mbeacom merged commit f58aad7 into main Aug 6, 2026
9 checks passed
@mbeacom
mbeacom deleted the mbeacom-phase-f-post-output-comparison branch August 6, 2026 01:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants