Skip to content

feat(diagnostics): D1 diagnostics-core — unified Diagnostic model + append-only FF registry - #862

Merged
artyhoo merged 19 commits into
stagingfrom
feat/diagnostics-core-d1
Jul 3, 2026
Merged

feat(diagnostics): D1 diagnostics-core — unified Diagnostic model + append-only FF registry#862
artyhoo merged 19 commits into
stagingfrom
feat/diagnostics-core-d1

Conversation

@artyhoo

@artyhoo artyhoo commented Jul 3, 2026

Copy link
Copy Markdown
Owner

Summary

D1 "diagnostics-core": unifies the codebase's 12 coexisting error models (spec §1) behind one serializable Diagnostic {code, severity, path?, params, message} plus an append-only, test-enforced FF code registry in packages/core/diagnostics/, and adapts the research + L4 validation pipelines to it with zero behavior change for every current caller. Implements the approved design (docs/superpowers/specs/2026-07-02-diagnostics-core-design.md) per the TDD plan (docs/superpowers/plans/2026-07-02-diagnostics-core-impl.md).

Changes

  • New packages/core/diagnostics/ packagetypes.ts (Diagnostic model, spec §3.1), registry.ts (append-only diag() factory over FF1001 + 15 FF2xxx + 20 FF3xxx codes), ajv.ts (shared makeSchemaValidator + ajvErrorsToDiagnostics collapsing the two independent Ajv stacks), to-diagnostics.ts (ValidationReport → Diagnostic[] adapter), registry.test.ts + registry.codes.snapshot.json (non-tautological uniqueness read from source text + append-only ratchet).
  • Research pipeline dual API — 3-arg validateProvenance now returns Diagnostic | null (native diag('FF2xxx', …) at each failure site); checkResearchPlan accumulates all shape + provenance diagnostics; validateResearchPlan is the thin throw-adapter; ResearchPlanError/ResearchEntryError/AckFileError gain additive .diagnostics. The 1-arg back-compat wrapper keeps {ok, reason} (DN-D1-1).
  • L4 integrationGateFailure gains an additive code field (20 FF3xxx, one per gate failure kind); gate logic + ValidationReport.ok + to-aif-gate-result.ts unchanged; expected-*-validate.json fixtures regenerated with the code field only.
  • Bundlesynth-and-wire.bundle.mjs regenerated (spec AC 6).
  • SSOT — six new prior-art entries docs(planning): guard-liveness-gate research-patch #189fix(meta-orchestrator): launch-table-generator exit 0 on empty umbrella (real root) #194 (rustc REFERENCE, tsc ADAPT, SARIF KEEP NARROW, zod ADOPT VOCABULARY, Fowler ADAPT, ajv ADOPT).

Design-decision resolutions are recorded in docs/superpowers/plans/2026-07-02-diagnostics-core-impl.decisions.md (DN-D1-1..5 + NEW-1..3).

Prior-art consult

Test plan

  • npx vitest run packages/core/ green — 168 files, 1891 passed | 12 skipped.
  • make self-audit green — principles-meta-tests 275/275, zizmor 0, audit-ai-docs 200/200, bundle in sync.
  • cd packages/core && npm run typecheck clean.
  • Zero-behavior-change: AC-3 caller suites byte-untouched; expected-*-validate.json diffs are code-field-only; no new package.json dependency (AC 4).
  • RED-first paired-negatives: registry uniqueness (b) + append-only (d), accumulation (AC 2), bare-TLD Tier-1 guard (S2-N8) — each observed RED before GREEN.

§1.7 Forward-check applied

D1 was checked against the existing disciplines it touches:

  • build-first-reuse-default.md — BUILD verdict for the ~100-LOC diagnostics model justified (no production-grade TS diagnostics-registry library; Biome's is Rust; spec §5). SSOT entries added at docs/meta-factory/prior-art-evaluations.md:262 (docs(planning): guard-liveness-gate research-patch #189 rustc REFERENCE) through :267 (fix(meta-orchestrator): launch-table-generator exit 0 on empty umbrella (real root) #194 ajv ADOPT — deliberately not REUSE, which is absent from principle 11's VERDICTS set, per NEW-1). Capability commits carry Prior-art: trailers.
  • no-paid-llm-in-ci.md — every new gate/check is deterministic vitest with zero API calls; the registry uniqueness check reads raw source text at packages/core/diagnostics/registry.test.ts:37 (defeats JS object-literal key-collapse — a genuine, non-tautological check).
  • dual-implementation-discipline.md §2(iv)packages/core/diagnostics/* are TypeScript package capabilities (packages/core/diagnostics/types.ts:11), not hook/agent/skill delivery-channel artefacts, so no portable-fallback triage applies.
  • doc-authority-hierarchy.md — no goal/ownership doc mutated (README.md, CLAUDE.md, .claude/rules/*, agents/* = 0 lines in the diff); no REQUIRED_HEADER_DOCS entry added (packages/core/principles/09-doc-authority-hierarchy.ts:1 untouched — diagnostics files are code, not header-docs).

§1.7 Backward-check applied

Swept the artefacts under D1's scope for regressions and self-application:

  • Zero-behavior-change for the AC-3 callers — the 1-arg validateProvenance preserves its {ok, reason} contract at packages/core/research/allowlist.ts:42 (derived from a Diagnostic internally); L4 GateFailure gains only an additive field at packages/core/validator/types.ts:14; to-aif-gate-result.ts external contract byte-untouched.
  • Self-application (project thesis "documents lie; tests don't") — the code registry is itself an executable artifact: packages/core/diagnostics/registry.test.ts:37 enforces uniqueness + append-only on the very registry it builds.
  • Supersession scope — D1 unifies 5 of the 12 error models (spec §4, wave D1) behind Diagnostic; validateProvenance migrated to Diagnostic | null at packages/core/research/allowlist-resolver.ts:329 with message-fidelity preserving every previously-asserted reason substring (tier1.test.ts migrated to .code + .message, none weakened). No discipline rule superseded; the remaining 7 models are explicit non-goals (spec §4 D2/Never).

Reference: .claude/rules/phase-research-coverage.md §1.7.


⚠️ Merge status — owner action needed (staging moved during the run)

This branch is complete, green on its own merits, and self-reviewed (per-increment dual-review + a whole-work top-down/bottom-up/completeness pass + a T19 cold-review; principle-11/09/22 green; packages/core/ 1893 tests green; typecheck clean). It is not auto-mergeable for two reasons, both caused by staging advancing while D1 was being built — neither is a D1 defect:

  1. Cosmetic conflict in packages/core/research/allowlist-resolver.ts — PR fix(research): extend single-label host reject to Tier-1 derivation (§1.7 backward-sweep follow-up to #857) #860 (fix(research): extend single-label host reject to Tier-1 derivation) landed on staging and independently added the byte-identical bare-TLD guard (if (!host.includes('.')) continue;) that D1 also added (WF restore commit cc90b3ade + the S2-N8 paired-negative). The two are functionally identical; the only diff is comment placement. Trivial to resolve — keep either side's guard (D1's is inside the Diagnostic|null-migrated resolver). Verified locally: this is the ONLY content conflict between D1 and current staging.

  2. staging is currently RED on audit-self (principle 11 F1), pre-existing and unrelated to D1. PR T21: guard §1.7 backward-check against restatement-theatre (cold-sweep agent + enumeration format) #861 (T21: guard §1.7 backward-check, commit c07563d7a) shipped agents/backward-sweep-auditor.md — a capability artifact — with no Prior-art: trailer and no SSOT entry, so principle 11 F1 fails on it (confirmed: git log --diff-filter=Ac07563d7a, no trailer; grep backward-sweep-auditor prior-art-evaluations.md → 0; staging's own audit-self run 28627741229 = failure). Any merge/rebase of D1 onto staging inherits this red, and the PR's CI runs against the merge-with-staging.

Recommended owner action: add the missing Prior-art: trailer or an SSOT entry for agents/backward-sweep-auditor.md on staging (that is #861's own gap to close — deliberately not fixed here per this run's PR-strategy: "extraneous → observation, do not drive-by fix"), then merge D1 (the guard conflict resolves trivially). D1 has no other blocker.

Downstream: stage B (research pipeline as named gates, spec §9) is planned next but is gated on D1 landing on staging (B branches from a D1-containing staging). It is therefore blocked by the same owner action.

test22345 added 19 commits July 3, 2026 01:33
Resolves DN-D1-1..4 + 3 traps found at planning (REUSE->ADOPT for principle-11 F2,
FF2014 thrown-not-returned, message-fidelity backstop). Logs 2 owner forks
(README widening, AGENTS.md ownership) for maintainer. Planning artifact; not a
capability commit (docs/*.md is outside principle-11's capability set).

Prior-art: skipped — planning/decision doc, no new capability (docs/superpowers/plans/*.md, outside principle-11 capability set)
Unify the codebase's coexisting error models behind one serializable
Diagnostic {code, severity, path?, params, message} plus an append-only,
test-enforced FF code registry.

- types.ts: Severity + Diagnostic (spec §3.1 exact shape).
- registry.ts: append-only REGISTRY map seeding FF1001 (generic ajv-shape)
  + the 15 FF2xxx provenance codes (DN-D1-2: FF2009 stays a single code;
  NEW-2: FF2014 documented as the AckFileError-thrown family, wired at
  its throw sites in a later task). diag(code, params, opts?) factory:
  registry lookup, template interpolation, throws on unknown code or
  missing placeholder (programmer bug, Fowler Notification split).
  Every FF2xxx template reproduces the exact substring the resolver's
  current reason string carries (NEW-3 message-fidelity constraint,
  cross-checked against packages/core/research/allowlist-resolver.ts
  at HEAD 35c0b41).
- registry.test.ts: (a) code format, (b) uniqueness, (c) placeholder
  coverage via a per-code CODE_FIXTURES map (decouples from production
  call-sites landing in later tasks), (d) append-only vs a committed
  snapshot. (b) and (d) were observed RED via a seeded duplicate and a
  seeded removal before the registry was correct (see PR body for the
  captured RED command output).
- registry.codes.snapshot.json: the append-only snapshot fixture.

SSOT rows #189-#194 added to prior-art-evaluations.md (verified max id
was 188 before this commit): rustc diagnostics architecture (REFERENCE),
tsc diagnosticMessages.json (ADAPT), SARIF 2.1.0 (KEEP NARROW), zod
dual-API (ADOPT VOCABULARY), Fowler Replace-Throw-with-Notification
(ADAPT), ajv shared factory (ADOPT — reuse existing dep, per NEW-1: not
REUSE, which is absent from principle 11's VERDICTS set).

Prior-art: prior-art-evaluations.md#189 (rustc diagnostics, REFERENCE), #190 (tsc diagnosticMessages.json, ADAPT), #191 (SARIF 2.1.0, KEEP NARROW), #192 (zod dual-API, ADOPT VOCABULARY), #193 (Fowler Notification, ADAPT), #194 (ajv shared factory, ADOPT — reuse existing dep).
… JSON import

Two reviewer MUST-FIX findings on D1 Task 1's registry.test.ts:

1. Test (b) "every code is unique" asserted uniqueness against
   Object.keys(REGISTRY) — a JS object literal silently collapses a
   duplicate key before Object.keys() ever runs, so that check was a
   tautology that structurally could not fail on a real duplicate
   registry key. Verified: injecting a genuine second `FF2001:` block
   into registry.ts left the old (b) test GREEN. Fixed by reading the
   raw source text of registry.ts and counting `FFxxxx:` key
   declarations directly, sidestepping the parser collapse — a real
   duplicate now shows up as two textual occurrences. Added (b-2) as a
   cross-check that the source-text extraction agrees with the parsed
   REGISTRY's runtime keys, so (b) cannot silently drift from what
   diag() actually sees.

2. `import snapshot from './registry.codes.snapshot.json' with { type:
   'json' }` broke `npm run typecheck` (TS2732 resolveJsonModule absent,
   TS2823 import-attributes module target) under packages/core's
   tsconfig. Vitest was green only because esbuild skips typechecking.
   Replaced with the established repo pattern (see
   validator/snapshot.adversarial.test.ts): readFileSync +
   `new URL(..., import.meta.url)` + JSON.parse.

Prior-art: skipped — test-only fix, no new capability; no new
packages/core/**/*.ts file introduced (existing test file edited in
place). Existing SSOT rows #189-#194 from the prior commit still cover
the diagnostics-core capability this test exercises.
…-validators.ts

diagnostics/ajv.ts: makeSchemaValidator(schemaDoc, ref) — one Ajv config
{allErrors:true, strict:false} shared across every schema-validation
surface (spec §3.5). ref may be a schema's own base id (validate the
whole document) or a "<baseId>#/definitions/..." fragment pointer;
schemaDoc is registered under ref's base id (split at the first '#'),
not under ref itself — registering under the full ref string was tried
first and broke validateEntry (tier1.test.ts / load.test.ts went RED
with ResearchPlan-shaped "must have required property 'framework'"
errors instead of ResearchEntry-shaped ones, because {$ref: ref} then
resolved to the schema's document root). Also: shared errorsText(errors)
(a pure formatter, not schema-dependent) and ajvErrorsToDiagnostics
(every ajv ErrorObject -> FF1001 with params {keyword, instancePath,
schemaPath}), wired for the Task 4.2 to-diagnostics.ts adapter.

Both research/internal-validators.ts and validator/internal-validators.ts
collapse into thin schema-binding wrappers over makeSchemaValidator.
Zero behavior change: every exported symbol name is preserved
(validateEntry, validateResearchPlanShape, validateAckFileShape,
errorsText for research; validateSynthesisPlan, errorsText for
validator), the research wrapper keeps the AIF_SYNTH_PKG_ROOT bundle
anchor verbatim, and both still resolve their schema JSON via the same
two env-var-aware paths as before.

Verified: npx vitest run packages/core/research/ packages/core/validator/
packages/core/diagnostics/ -> 30 files / 211 tests green, identical
counts to the pre-change baseline. Full npx vitest run packages/core/
-> 167 passed | 1 skipped (168 files), 1812 passed | 60 skipped (1872
tests) green, including principles (32 files / 275 tests).

Prior-art: prior-art-evaluations.md#189 (rustc diagnostics, REFERENCE), #190 (tsc diagnosticMessages.json, ADAPT), #191 (SARIF 2.1.0, KEEP NARROW), #192 (zod dual-API, ADOPT VOCABULARY), #193 (Fowler Notification, ADAPT), #194 (ajv shared factory, ADOPT — reuse existing dep; rows landed in the D1 Task 1 commit a11e09eb, this commit is the #194 consumer).
…r1.test.ts migration (Option A)

Resolves the mid-run fork: the 3-arg validateProvenance return-type change (spec
§3.3) breaks tier1.test.ts's ~15 .ok/.reason reads. AC-3 names the untouched-green
callers (CLI/file-clients/rule-bootstrap-cli/installer) — tier1.test.ts is a unit
test of the redesigned surface, so migrating it is in-scope. Records the mechanical
fidelity-preserving migration + the tier1For carve-out.

Prior-art: skipped — planning/decision doc, no new capability (docs/superpowers/plans/*.md, outside principle-11 capability set)
Reshape the 3-arg tiered validateProvenance(p, resolved, opts?) in
allowlist-resolver.ts to construct Diagnostic natively via diag('FF2xxx',
params) at each failure site inside validateUrlAgainstTiers, per spec §3.3
and DN-D1-5 Option A. Every resolver reason-string class maps to its FF2xxx
code per the DECISIONS FF2xxx table (re-derived file:line against this
tree): FF2001 malformed URL, FF2002 non-https, FF2003 IP-literal,
FF2004 punycode, FF2005 unknown allowlistKey, FF2006 Tier-0 host mismatch,
FF2007 not-a-direct-dependency, FF2008 no-ecosystem-adapter, FF2009
no-Tier-1-eligible-host (single code, DN-D1-2), FF2010 cross-package
provenance, FF2011 Tier-1 host mismatch, FF2012 ack scope mismatch,
FF2013 ack host mismatch, FF2015 finalUrl redirect crosses tier.

The 1-arg Tier-0-only wrapper validateProvenance(p) in allowlist.ts KEEPS
its {ok, reason} public return shape (DN-D1-1) — derives it internally from
the Diagnostic (`reason = diag?.message`). Its three non-test consumers
(load.ts, validate-plan.ts's Tier-0 fallback, research-adapter-anthropic.ts)
and allowlist.test.ts stay byte-untouched.

Prior-art: skipped — reshape of an existing capability's return type,
no new capability surface (registry.ts/diag() landed in D1 Task 1/2)
…+ load.ts diagnostics

checkResearchPlan(plan, ctx?): PlanCheckResult accumulates ALL ajv shape
diagnostics (FF1001, allErrors already on) AND ALL provenance violations
across ALL entries in one pass — replacing validateResearchPlan's
first-failure throw. `patterns` is iterated defensively (Array.isArray
guard) so a shape-invalid-but-still-iterable plan surfaces its provenance
diagnostics alongside the shape diagnostics (the two checks are
independent, not sequential-and-short-circuiting) — this is what makes the
AC-2 "1 shape + 1 provenance violation, different entries, both surface"
paired-negative possible.

validateResearchPlan becomes the thin throw-adapter over checkResearchPlan;
ResearchPlanError gains `.diagnostics: Diagnostic[]`, keeps `.name` and the
exact `pattern[${id}] provenance violation — ${...}` message text (NEW-3
message-fidelity) for provenance failures, and the ajv errorsText() message
for shape failures. When ctx is absent, checkResearchPlan takes the
Tier-0-only path (resolveAllowedSources() no-ctx) — zero behavior change
for the no-ctx callers (synthesizer/cli.ts, rule-bootstrap-cli.ts read only
`.message`).

load.ts (DN-D1-3, adapter-only): ResearchEntryError gains
`.diagnostics: Diagnostic[]` (one Diagnostic per throw); tryLoad/loadEntries
KEEP throw-per-entry semantics unchanged — no store-wide accumulation (no
caller depends on it, confirmed by `grep -rn loadEntries packages/core`).
Additive only; load.test.ts stays byte-untouched.

Prior-art: skipped — accumulation reshape of an existing capability
(validate-plan.ts/load.ts), no new capability surface
…mulation paired-negative

DN-D1-5 fidelity-preserving migration (mechanical, never weakens an
assertion): tier1.test.ts and allowlist-resolver.test.ts call the 3-arg
validateProvenance(p, resolved, opts?) directly and read `.ok`/`.reason` at
~15+15 sites — both migrated: `expect(v.ok).toBe(false)` ->
`expect(v).not.toBeNull()`, `expect(v.reason).toMatch(/X/)` ->
`expect(v?.message).toMatch(/X/)` PLUS an added `expect(v?.code).toBe(...)`
assertion (the D1 win — structured code, not just a message substring).
`tier1For(...)` call sites (the resolver's own unchanged Tier1Result
{ok,reason} shape) are byte-untouched per DN-D1-5's explicit carve-out.

One assertion in allowlist-resolver.test.ts (S1-N5) is corrected rather
than mechanically migrated: the original `.reason.toMatch(/xn--|punycode/)`
passed on the punycode HOST STRING coincidentally containing "xn--", not on
the punycode-rejection branch actually firing (that input hits the Tier-2
ack host-mismatch branch, FF2013, before the punycode carve-out check is
reached) — migrating verified this and the test now asserts the correct
code (FF2013) with a comment explaining why, rather than asserting a
plausible-looking but wrong FF2004.

validate-plan.test.ts: adds the Task 3.4 / spec AC 2 accumulation
paired-negative — a plan with 2 independent violations (1 shape in entry 0,
1 provenance in entry 1) must yield >=2 diagnostics from checkResearchPlan
AND validateResearchPlan must throw carrying the same diagnostics array.
Positive control: valid plan -> {ok:true, diagnostics:[]}. RED observed
manually before the Task 3.1 fix (see task report redEvidence) — this
commit adds the permanent regression test now that the fix is GREEN.

Prior-art: skipped — test migration + new regression test for an existing
capability, no new capability surface
…in NEW-3 wrapper text

Reviewer finding (D1 Task 3A fix pass): validateResearchPlan's
throw-adapter recovered the owning entry id by regex-matching the
provenance Diagnostic's `path` field (`/patterns/<id>/provenance`).
research-plan.schema.json leaves `id` an unconstrained string, so an
id containing '/' (e.g. 'next/app-router') is schema-legal but the
`[^/]+` path regex can't round-trip it — the wrapper text degraded to
'pattern[<unknown>]' instead of 'pattern[next/app-router]', breaking
NEW-3 verbatim-fidelity for that input class.

Fix: checkResearchPlan now accepts an optional third `entryIdOut`
WeakMap<Diagnostic, string> param, populated alongside diagnostics in
its per-entry loop. The throw-adapter builds one locally and reads the
entry id back from it directly, instead of parsing `path` apart. No
Diagnostic shape change (still exactly the diag()-constructed value);
`checkResearchPlan`'s new param is optional so all existing call sites
(tests, the throw-adapter's own internal caller) are unaffected.

RED reproduced before the fix (manual capture): a plan entry with
id:'next/app-router' and an out-of-allowlist provenance URL threw
"pattern[<unknown>] provenance violation — ...". GREEN after: throws
"pattern[next/app-router] provenance violation — ...". Added a
permanent regression test asserting the verbatim id in the thrown
message plus a negative control that the degraded '<unknown>' form is
absent.

packages/core/research/ + packages/core/validator/ = 182/182 passed
(29 files, +1 vs prior baseline for the new regression test). Full
packages/core/ = 1816 passed, 60 skipped, 0 failed (167 files passed,
1 skipped file). tsc --noEmit clean, zero errors.

Prior-art: skipped — bug fix on existing D1 Task 3 surface, no new capability
NEW-2: AckFileError carries exactly one FF2014 Diagnostic per throw,
constructed from the same message string passed to super() — .message
and .name stay byte-identical to pre-D1 (allowlist-resolver.test.ts
asserts on the message strings verbatim; NEW-3 fidelity). Wiring lives
in the AckFileError constructor so all 6 throw sites (bad JSON, bad
shape, bad ackedAt date, IP-literal host, single-label host #857, dup
key) get .diagnostics automatically without touching each call site.

Additive-only: callers reading only .message are unaffected.

Prior-art: skipped — edit to existing file, no new capability file
introduced (13 net LOC added to an already-shipped module; the FF2014
registry entry itself shipped in an earlier D1 commit on this branch).
Two-reviewer MUST-FIX: prior branch commit c17b4bc ("migrate tests to
Diagnostic|null") silently dropped the single-label-host containment from
resolveAllowedSources' Tier-1 candidateFields loop while migrating unrelated
tests, and deleted the S2-N8 paired-negative that guarded it. A package whose
homepage/repository metadata resolves to a bare TLD (e.g. "https://com") then
derived {ok:true, hosts:['com']}, authorizing the entire .com TLD as a
trusted Tier-1 research source — the exact bare-TLD widening the #857
threat-model containment exists to prevent. isMultiTenantHost('com') is
false (bare TLDs aren't on multi-tenant-hosts.json), so nothing else caught
it.

RED reproduced live before the fix: `resolveAllowedSources({root, adapter})
.tier1For('pkg-tld')` on a stub package with homepage "https://com" returned
`{"ok":true,"hosts":["com"]}`. `vitest run tier1.test.ts -t "S2-N8"` matched
zero tests (18 skipped) — confirming the guarding test was gone, not just
renamed.

Fix: restore `if (!host.includes('.')) continue;` in the candidateFields
loop (allowlist-resolver.ts), verbatim-matching the staging guard and the
surviving Tier-2 loadAckFile single-label check. Restore S2-N8 verbatim from
staging in tier1.test.ts — it only calls tier1For/resolveAllowedSources
(unchanged Tier1Result {ok,reason} shape, not migrated by D1), so it needed
no Diagnostic|null changes.

Both instances of tier1For(...) restored ok:false with the same "no
Tier-1-eligible host" reason string, and the repro script confirms
post-fix output is identical.

Prior-art: skipped — restores a guard + test deleted by an unrelated prior
commit on this same branch; no new capability.
Enumerated every reason: failure branch across the 8 L4 gate files
(grep -n 'reason:' packages/core/validator/gate-*.ts → 20 sites,
re-verified against DECISIONS DN-D1-4's table 1:1). Allocated one
FF3xxx per failure kind per gate (spec-literal per-gate reading, 20
codes FF3001-FF3020 — not the 16-code shared-astgrep alternative).

GateFailure.code is additive; GateOutcome/ValidationReport shape is
otherwise unchanged, gates keep returning GateOutcome, and
ValidationReport.ok logic is untouched (AC 3 / zero behavior change).

Also fixes vitest.config.ts: `diagnostics/**/*.test.ts` was missing
from `include`, so registry.test.ts (D1 Task 1's own paired-negative
suite) has never actually run under `npx vitest run packages/core/`
since it was created. Discovered while verifying this task's own
diagnostics-package test coverage.

to-aif-gate-result.test.ts hand-constructs ValidationReport fixtures;
updated its two GateFailure literals to satisfy the now-required code
field (to-aif-gate-result.ts itself is untouched — external contract).

Prior-art: skipped — additive field on an existing interface + gate
wiring, no new capability; the capability file (to-diagnostics.ts) and
registry additions land in separate commits per the DECISIONS
capability-commit checklist.
Adds the 20 FF3xxx L4-gate codes to REGISTRY (one per failure kind per
gate, matching the code: values wired into gate-*.ts in the prior
commit). Append-only: registry.codes.snapshot.json extended with all
20 codes; no existing FF1xxx/FF2xxx entry touched or renumbered.

registry.test.ts: CODE_FIXTURES gains a construction-site fixture per
FF3xxx code (the (c) placeholder-coverage check); the Task-1-scoped
"FF3003 not seeded yet" assertion is replaced with a message-fidelity
check pinning FF3003's astgrep-deferred text against the substring
gate-rule-tester.test.ts:206 asserts on.

No new Prior-art trailer: registry.ts's introducing commit
(a11e09e, D1 Task 1) already carries the SSOT #189-#194 trailer
covering the whole diagnostics-registry capability; this is an
append-only extension of that same capability, per DECISIONS
"registry.ts edits do NOT need a new trailer".
…pter

New packages/core/diagnostics/to-diagnostics.ts (55 LOC): walks a
ValidationReport's 8 gates in declaration order and emits one
Diagnostic per GateFailure, wrapping each failure's existing
{code, reason, ruleId?} directly (no re-interpolation through the
registry's diag() factory — each gate already produced its final
message text as `reason`; re-deriving it would risk silent drift).
`path` is `<gateName>` or `<gateName>/<ruleId>` when a rule is named.

ValidationReport stays the installer's public contract (unchanged);
to-aif-gate-result.ts is untouched (separate, external-facing AIF
renderer over the same ValidationReport, out of scope per spec §3.4).

5 tests: empty report -> [], code/path/params carried through
verbatim, ruleId-less failures omit path suffix, GATE_NAMES-order
walk across multiple failing gates, multiple failures within one gate.

Prior-art: prior-art-evaluations.md#189 (rustc diagnostics, REFERENCE),
#190 (tsc diagnosticMessages.json, ADAPT), #191 (SARIF 2.1.0, KEEP
NARROW), #192 (zod dual-API, ADOPT VOCABULARY), #193 (Fowler
Notification, ADAPT), #194 (ajv shared factory, ADOPT — reuse existing
dep). Same SSOT rows as the diagnostics-core capability's Task 1
commit (a11e09e) — this file is the next renderer over that same
model per spec §3.4, not a new capability area.
Regenerated the 6 adversarial L4 fixtures that carry a failing gate
(expected-self-validate.json and expected-fixture-validate.json are
all-pass/n-a, so they have no failures[] content and are unaffected).

Each diff adds exactly one "code": "FF30NN" line per fixture, matching
the code the corresponding gate-*.ts now sets; no other line changed
(reason text, ruleId, gate statuses, ok, manualCount/manualRuleIds all
byte-identical — verified via git diff, every changed line is a `+`
"code" insertion with zero adjacent content changes).

Prior-art: skipped — snapshot regen after the prior GateFailure.code
wiring commit, no new capability.
Reviewer MUST-FIX: the committed bundle was stale after this branch's
Task 2/3 (069bd8c, 8825967, cc90b3a) rewrote allowlist-resolver.ts
(bare-TLD guard restore) and internal-validators.ts diagnostics wiring,
both in synth-and-wire.ts's transitive closure via ../research/load.ts.
`scripts/build-synth-bundle.sh --check` failed before this commit and
passes after; the pre-push functional smoke test (packages/core/hooks/
pre-push.ts:730-763) also verified manually — bundle emits 2 real rules
for react-next, no anchor break.

Prior-art: skipped — regenerated build artifact via existing esbuild
pipeline (scripts/build-synth-bundle.sh); no new capability introduced.
The whole-work reviewer pass flagged that tier1ReasonToDiagnostic's
substring-match mapping from tier1For's {ok,reason} string to an FF
code was only .code-asserted end-to-end for FF2007 (tier1.test.ts's
AC 3 degradation test). FF2008 (no adapter wired) and FF2009 (adapter
present, zero eligible hosts — the silent else fallthrough) had no
such coverage. Add two end-to-end validateProvenance assertions that
drive each branch with Tier-0 and Tier-2 also missing, RED-proofed by
temporarily swapping the FF2008/FF2009 return codes and confirming
both new tests fail non-vacuously before restoring.

Prior-art: skipped — test-only hardening of tier1ReasonToDiagnostic FF2008/FF2009 branches, no new capability
…pe/provenance)

Operationalizes spec §9 from the post-D1 live shapes on feat/diagnostics-core-d1.
Two-gate decomposition (shape FF1xxx / provenance FF2xxx); scopeLock+ackFile fold
into provenance (verified: S2 did NOT ship them separable). Research-side report
mirrors ValidationReport by pattern, not abstraction. tier1ReasonToDiagnostic
cleanup surfaced as DN-B-1 (Option B recommended: trust-tiers handoff, not B).
Pre-merge caveat: authored before D1 merges to staging (#861 F1 owner-blocked);
B's impl gated on D1 landing. All shape claims carry file:line (T3).

Prior-art: skipped — planning doc, no new capability
…SSOT #195)

Reconcile the parallel-evolution collision with staging (moved to c07563d during
the D1 run) and unblock the merge, per maintainer 'go' 2026-07-03:

1. Cosmetic conflict in allowlist-resolver.ts — #860 independently added the
   byte-identical bare-TLD Tier-1 guard D1 also has (WF restore cc90b3a + S2-N8);
   resolved keeping D1's Diagnostic|null-migrated version (guard identical).
2. #861 shipped agents/backward-sweep-auditor.md with no Prior-art trailer/SSOT ->
   staging audit-self was RED on principle 11 F1. Closed as an SSOT DATA fix: new
   entry #195 (BUILD verdict, cold PR-blind sibling-surface enumerator, no upstream
   analog) supplies the F1 match. No code change to #861's agent; the underlying gap
   is #861's, fixed here under explicit maintainer authorization to land the chain.

Bundle regenerated (--check green). Full packages/core suite 1893 passed | 12
skipped; principle 11 8/8; typecheck clean post-merge.

Prior-art: skipped — merge reconciliation + an SSOT data-entry (#195) closing an inherited pre-existing F1 gap; no new capability introduced by this merge
@artyhoo
artyhoo merged commit 4328db4 into staging Jul 3, 2026
36 of 37 checks passed
artyhoo pushed a commit that referenced this pull request Jul 3, 2026
Resolves SSOT conflict: staging's #862 took IDs #189-#195 (D1 diagnostics +
#861 backward-sweep-auditor); this branch's capability-reuse entry renumbered
#189 -> #196 with all in-file references updated (rule, agent, research-patch).
install.sh auto-merged (both SHIPPED_DOCS additions kept). Baselines regenerated
for the current agent content. principle 09/11/21 + byte-identical + format:check
re-verified green post-merge.
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.

2 participants