# Use-case registry — the machine-checkable definition of abicheck's
# application/library ABI-API change use cases.
#
# This is the SOURCE OF TRUTH behind the human-readable scorecard in
# docs/contribute/usecase-coverage-evaluation.md. Each entry is validated by
# tests/test_usecase_registry.py, which enforces that:
#   - every entry has a known `axis` and `status`;
#   - `complete` / `partial` / `modeled` entries cite evidence whose paths
#     actually exist in the repo (so coverage claims cannot silently rot);
#   - `partial` / `modeled` / `planned` entries carry a `gap` id, `next_steps`,
#     and a `plan:` pointing at a real plan file under docs/contribute/plans/;
#   - `by_design_excluded` entries explain themselves in `note`.
#
# To extend coverage: add or update an entry here, point `evidence` at the
# real test/module/example that backs it, and the test keeps everyone honest.
#
# status legend:
#   complete            fully supported and validated
#   partial             works, but with documented caveats/gaps
#   modeled             code/parsers exist but NOT validated end-to-end in CI
#   planned             not implemented; has a tracked plan (gap + next_steps)
#   by_design_excluded  deliberate non-goal (see docs/contribute/goals.md)
#
# axis legend: change_class | archetype | platform | workflow | reporting | toolchain

schema_version: 1

use_cases:
  # ── change_class ──────────────────────────────────────────────────────────
  - id: UC-CHANGE-taxonomy
    axis: change_class
    name: ABI/API change taxonomy (396 ChangeKinds, 5-tier policy)
    status: complete
    evidence:
      modules: [abicheck/change_registry.py, abicheck/checker_policy.py]
      tests: [tests/test_changekind_completeness.py]
      examples: [examples/ground_truth.json]

  - id: UC-CHANGE-semver-recommendation
    axis: change_class
    name: Release recommendation (semver bump + SONAME action)
    status: complete
    evidence:
      modules: [abicheck/semver.py]
      tests: [tests/test_semver_recommendation.py]

  - id: UC-CHANGE-inline-ns-version
    axis: change_class
    name: Inline-namespace version-stamp normalization (ICU-style)
    status: partial
    gap: G15
    plan: docs/contribute/plans/g15-inline-namespace-version.md
    evidence:
      modules: [abicheck/versioned_symbol_scheme.py, abicheck/post_processing.py]
      tests: [tests/test_versioned_symbol_scheme.py]
    next_steps: >
      DONE (detector half, field eval P08): the versioned-symbol-scheme recogniser
      emits one advisory `versioned_symbol_scheme_detected` (RISK) when a strong
      majority of removed symbols reappear as added symbols differing only by a
      numeric version token (ICU `u_*_NN`). Additive — it explains the churn and
      never downgrades the artifact-proven removals (authority rule).
      STILL PLANNED: (1) normalize symbol keys *through* the detected token and
      re-diff so the surface collapses to the real delta (ICU 73→74: BREAKING/6288
      → +34/-0), behind an opt-in suppression preset; (2) cross-check the token
      against the SONAME and still surface the soname bump as the relink signal;
      (3) extend tokens to libstdc++ versioned namespaces / Abseil `lts_<date>`.

  # ── archetype ─────────────────────────────────────────────────────────────
  - id: UC-ARCH-c-library
    axis: archetype
    name: Pure-C shared library (extern "C", SONAME-versioned)
    status: complete
    evidence:
      examples: [examples/ground_truth.json]
    note: 35 pure-C example pairs (examples/case*/v1.c).

  - id: UC-ARCH-cpp-library
    axis: archetype
    name: C++ library (templates, vtables, inline namespaces)
    status: complete
    evidence:
      examples: [examples/ground_truth.json]
    note: 52 C++ example pairs (examples/case*/v1.cpp).

  - id: UC-ARCH-plugin
    axis: archetype
    name: Plugin host↔plugin load contract (dlopen)
    status: complete
    note: >
      G5 closed: first-class host-contract check
      (appcompat.check_plugin_host_contract / scope_diff_to_required_symbols)
      answers "does plugin v2 still satisfy host H's required entrypoints?" —
      the plugin-load mirror of appcompat, reusing its consumer-scoping.
      ADR-043 folded the standalone `plugin-check` CLI into
      `compare --required-symbol`/`--required-symbols` (repeatable flag or a
      newline-delimited manifest file), scoping the comparison to an explicit
      required-entrypoint contract instead of the full diff, with the
      plugin_abi policy as the default. Validated end-to-end through
      snapshot-driven scenario tests and CLI tests; the
      host-safe-vs-host-breaking distinction (a library-wide BREAKING drop the
      host never resolves stays COMPATIBLE for the host) is asserted. A compiled
      host/plugin binary demo in examples/ remains optional (needs the
      integration toolchain) and is not required for the capability.
    evidence:
      modules: [abicheck/appcompat.py, abicheck/cli_compare_helpers.py]
      tests: [tests/test_workflow_scenarios.py, tests/test_appcompat.py]
      docs: [docs/use/plugin-systems.md]

  - id: UC-ARCH-header-only
    axis: archetype
    name: Header-only / inline-only libraries
    status: planned
    gap: G4
    plan: docs/contribute/plans/g4-header-ast-extractor.md
    evidence:
      modules: [abicheck/dumper_castxml.py]
    next_steps: >
      Add a libclang-based header-AST extractor alongside castxml to unblock
      concept tightening, hidden friends, and user-ctor mangled names
      (dormant fixtures: cases 78/105/106/111).

  - id: UC-ARCH-cython-api
    axis: archetype
    name: Cython API/ABI surface (.pxd + __pyx_capi__ capsule table)
    status: planned
    gap: G25
    plan: docs/contribute/plans/g25-cython-api-abi-frontend.md
    next_steps: >
      Add a CythonSurface extractor (abicheck/cython_api.py) covering
      distributed .pxd declarations and __pyx_capi__ capsule signatures, plus
      a diff_cython_api.py detector emitting cython_capi_signature_changed
      and siblings. Neither G14 (native C-ABI) nor G23 (Python-level API) see
      a capsule signature change — SciPy's own bespoke __pyx_capi__
      regression test is the motivating precedent (scikit-learn, statsmodels
      compile against these capsules). See the SciPy/Scientific-Python
      Roadmap for full context.

  - id: UC-ARCH-python-api
    axis: archetype
    name: CPython extension Python-level API surface
    status: complete
    plan: docs/contribute/plans/g23-python-level-api-diff.md
    note: >
      G23 closed (static path). Beyond G14's native C-ABI contract, abicheck now
      recovers the Python-level API a module exposes to import — top-level
      functions, classes, methods, and their signatures (arg names/kinds/defaults
      and type annotations) — statically from a sibling .pyi type stub (parsed
      with ast, never imported/executed). A diff_python_api detector emits 15
      python_api_* ChangeKinds from an order-, kind-, and protocol-aware
      signature diff (not a name-set diff): removed/added function/class/method,
      parameter removed/added-required/renamed, default removed, parameter- and
      return-type changed, parameter binding/order changed, callable kind changed
      (def↔async def, method↔property/static/class), and overload removed —
      routed through the existing verdict/reporter machinery,
      so a single `compare` surfaces both a byte-identical binary's native-ABI
      state and a Python-signature break the export table cannot see. Attached in
      both `dump` and `compare`; degrades honestly (surface absent) when no stub
      ships. Out of scope for this pass: the opt-in runtime-introspection and
      docstring/__text_signature__ fallbacks, and an examples/ catalog fixture
      (the compiled-binary example harness does not run .pyi pairs) — the
      headline "C-ABI-identical yet Python-API-broken" scenario is proven by
      tests/test_python_api.py::test_break_invisible_to_c_abi_is_caught. The
      recovered surface also acts as a public-contract oracle
      (post_processing.DemoteOffPythonSurface): native C/C++ API-content findings
      on an extension's internal symbols/types are demoted off-surface (an
      extension exports only PyInit_), while python_api_* and the native
      load-contract findings stay authoritative — measured as a first-class
      evidence layer by a python-api axis in the FP-rate gate and an L2-only
      signal in the per-tier accuracy gate.
    evidence:
      modules: [abicheck/python_api.py, abicheck/diff_python_api.py, abicheck/post_processing.py]
      tests: [tests/test_python_api.py, tests/test_fp_rate_gate.py, tests/test_tier_accuracy_gate.py]
      docs: [docs/use/python-extensions.md]

  - id: UC-ARCH-kernel-btf
    axis: archetype
    name: Kernel / eBPF modules (BTF/CTF)
    status: complete
    note: >
      G6 (kernel half) closed. The "module vs vmlinux BTF" workflow runs
      end-to-end through compare: real BTF bytes → parse_btf_from_bytes →
      layout detectors → BREAKING, with a CTF mirror scenario
      (parse_ctf_from_bytes). A committed BTF-blob example
      (examples/case121_kernel_btf_struct_field_added) carries v1.btf/v2.btf and
      a ground_truth.json entry; resolve_input now ingests bare BTF/CTF blobs by
      magic so `abicheck compare a.btf b.btf` works toolchain-free. A real
      `.BTF`-section fixture is validated via `gcc -gbtf`
      (tests/test_btf_integration.py, integration-marked). Full kernel-module
      __ksymtab namespace analysis beyond BTF type layout stays out of scope.
    evidence:
      modules: [abicheck/btf_metadata.py, abicheck/ctf_metadata.py, abicheck/service.py]
      tests: [tests/test_btf_metadata.py, tests/test_workflow_kernel_accel.py, tests/test_btf_integration.py]
      examples: [examples/case121_kernel_btf_struct_field_added]
      docs: [docs/use/kernel-btf.md]

  - id: UC-ARCH-sycl
    axis: archetype
    name: SYCL / heterogeneous accelerator stacks (PI/UR)
    status: complete
    note: >
      G6 (accelerator half) closed. SYCL plugin-interface detection is driven
      through the standard compare + report path at the workflow level for BOTH
      interface generations: a dropped PI entrypoint (libpi_*.so) and a dropped
      UR adapter entrypoint (libur_adapter_*.so) each → BREAKING and reach the
      JSON/Markdown reports (tests/test_workflow_kernel_accel.py). CUDA device
      code (.cubin/PTX) stays deferred by design.
    evidence:
      modules: [abicheck/sycl_metadata.py, abicheck/diff_sycl.py]
      tests: [tests/test_diff_sycl.py, tests/test_workflow_kernel_accel.py]
      examples: [examples/case82_sycl_overload_set_removed]
      docs: [docs/use/kernel-btf.md]

  - id: UC-ARCH-static-lib
    axis: archetype
    name: Static libraries (.a / .lib)
    status: by_design_excluded
    note: >
      G8 decision (option A): static/import library archives are a non-goal.
      abicheck compares single linkable images (shared libraries + objects); an
      `ar` archive (.a / .lib, magic `!<arch>\n`) has no runtime ABI surface
      (no SONAME, no dynamic symbol table, no symbol versioning). The CLI now
      detects archives and fails with actionable guidance (extract members or
      compare the shared library) instead of a misleading "unknown format"
      error. See docs/contribute/goals.md (Non-goals),
      docs/learn/limitations.md, and the plan
      docs/contribute/plans/g8-static-libraries.md.
    evidence:
      modules: [abicheck/binary_utils.py, abicheck/service.py]
      tests: [tests/test_compare_input_modes.py, tests/test_service_unit.py]
      docs: [docs/learn/limitations.md]

  - id: UC-ARCH-ffi-consumers
    axis: archetype
    name: FFI consumers in other languages (Rust/Go/Python)
    status: by_design_excluded
    note: >
      The C ABI such consumers bind to is covered; first-class support for
      non-C/C++ languages is a stated non-goal (docs/contribute/goals.md).

  # ── platform ──────────────────────────────────────────────────────────────
  - id: UC-PLAT-linux-elf
    axis: platform
    name: Linux ELF (the CI-validated baseline)
    status: complete
    evidence:
      modules: [abicheck/elf_metadata.py]
      examples: [examples/ground_truth.json]

  - id: UC-PLAT-windows-pe
    axis: platform
    name: Windows PE/COFF + PDB (MSVC / MinGW)
    status: complete
    note: >
      G1 (Windows half) closed. The `compare` workflow is validated end-to-end
      on native PE binaries in CI: the `cross-platform-e2e` lane builds DLLs
      with MinGW gcc and drives `abicheck compare` directly (binary↔binary),
      asserting BREAKING on a removed export and COMPATIBLE on identical builds
      (tests/test_cross_platform_integration.py). The MSVC+PDB lane
      (windows-msvc) now asserts both struct-growth (PDB layout) and
      exported-function removal (PE export table) verdicts. PDB struct-layout
      extraction depth on some MSVC versions remains best-effort (tests skip
      rather than fail when a layout can't be parsed), and the example-catalog
      platform tags stay a deliberate subset of Linux (see
      tests/test_platform_coverage_honesty.py).
    evidence:
      modules: [abicheck/pe_metadata.py, abicheck/pdb_metadata.py]
      tests: [tests/test_pe_metadata_unit.py, tests/test_pdb_metadata.py, tests/test_msvc_pdb_e2e.py, tests/test_cross_platform_integration.py]
      docs: [docs/reference/platforms.md]

  - id: UC-PLAT-macos-macho
    axis: platform
    name: macOS Mach-O (x86-64 / ARM64)
    status: complete
    note: >
      G1 (macOS half) closed. The `cross-platform-e2e` CI lane (macos-latest)
      builds .dylib files with Apple clang and drives `abicheck compare`
      directly on native Mach-O binaries, asserting BREAKING on a removed
      export and COMPATIBLE on identical builds
      (tests/test_cross_platform_integration.py). AArch64 AAPCS64 by-value
      aggregate passing (HFA/HVA, the 16-byte small-struct → indirect boundary)
      is modeled by macho_metadata.classify_aapcs64_aggregate and unit-tested in
      tests/test_macos_arm64_abi.py; the calling-convention divergence from SysV
      x86-64 is documented in docs/reference/platforms.md. Example-catalog
      platform tags remain a deliberate subset of Linux
      (tests/test_platform_coverage_honesty.py).
    evidence:
      modules: [abicheck/macho_metadata.py]
      tests: [tests/test_macho_metadata_unit.py, tests/test_macos_arm64_abi.py, tests/test_cross_platform_integration.py]
      docs: [docs/reference/platforms.md]

  - id: UC-PLAT-arch-guard
    axis: platform
    name: Cross-architecture comparison guardrail (ELF e_machine)
    status: complete
    note: >
      G13 closed. The ELF snapshot captures e_machine, EI_CLASS, and
      endianness (PE/Mach-O already carried an equivalent machine field); a
      mismatch is a hard guard rather than a false-green
      COMPATIBLE_WITH_RISK verdict — ELF_MACHINE_CHANGED and
      ELF_CLASS_CHANGED dominate the verdict (BREAKING_KINDS), with
      ELF_ENDIANNESS_CHANGED and ELF_ABI_FLAGS_CHANGED covering byte-order and
      decoded float-ABI/EABI drift. Same-architecture comparisons are
      unaffected.
    evidence:
      modules: [abicheck/elf_metadata.py, abicheck/checker_policy.py, abicheck/diff_platform_elf_dynamic.py]
      tests: [tests/test_g23_elf_facts.py, tests/test_changekind_completeness.py]

  # ── workflow ──────────────────────────────────────────────────────────────
  - id: UC-WF-compare
    axis: workflow
    name: Pairwise compare (CI PR gate)
    status: complete
    evidence:
      tests: [tests/test_reporter.py]
      examples: [examples/ground_truth.json]

  - id: UC-WF-appcompat
    axis: workflow
    name: Application compatibility (consumer-scoped)
    status: complete
    evidence:
      modules: [abicheck/appcompat.py]
      tests: [tests/test_appcompat.py, tests/test_workflow_scenarios.py]

  - id: UC-WF-baseline
    axis: workflow
    name: Baseline pinning / registry
    status: complete
    note: >
      ADR-043: the baseline registry (push/pull/list/delete) was removed
      outright — a baseline is simply an old dump/library/directory/package
      passed directly to `compare OLD NEW` or `scan ARTIFACT --against OLD`.
      Pinning is the caller's own responsibility (a path, a URL-fetched file,
      a release asset) rather than a managed registry.
    evidence:
      modules: [abicheck/cli_scan.py, abicheck/cli_compare_helpers.py]
      tests: [tests/test_cli_scan.py, tests/test_compare_dispatch.py]

  - id: UC-WF-debian-symbols
    axis: workflow
    name: Debian symbols file generate/validate/diff
    status: complete
    evidence:
      modules: [abicheck/debian_symbols.py]
      tests: [tests/test_debian_symbols.py]

  - id: UC-WF-abicc-compat
    axis: workflow
    name: ABICC drop-in replacement
    status: complete
    evidence:
      modules: [abicheck/compat/cli.py]
      tests: [tests/test_abicc_parity.py]

  - id: UC-WF-stack-deps
    axis: workflow
    name: Full-stack dependency / sysroot validation
    status: complete
    evidence:
      modules: [abicheck/cli_stack.py, abicheck/resolver.py]
      tests: [tests/test_stack_checker.py]

  - id: UC-WF-mcp
    axis: workflow
    name: MCP server (AI-agent integration)
    status: by_design_excluded
    note: >
      An experimental MCP server shipped and was later removed. Agent
      integrations now use the CLI (structured JSON/SARIF output) or the
      typed Python API (abicheck.service) directly — see
      docs/use/python-api.md.

  - id: UC-WF-bundle
    axis: workflow
    name: Multi-library bundle / cohort analysis
    status: complete
    note: >
      Linux/ELF only by design (ADR-018/ADR-023 — no DT_NEEDED/.gnu.version_*
      equivalent elsewhere); cross-platform bundle analysis is tracked under
      G1 / UC-PLAT-*. All bundle detectors run through compare-release; case84
      is validated end-to-end. bundle_soname_skew is opt-in via
      --bundle-cohort PREFIX so independent libraries are never inferred to be
      co-versioned from their filenames. Caveat: wheels whose vendored
      dependencies carry auditwheel/delocate content-hash sonames are not yet
      paired across rebuilds — that topology is tracked separately as
      UC-WF-wheel-vendored (G9).
    evidence:
      modules: [abicheck/bundle.py, abicheck/diff_cpp_patterns.py]
      tests: [tests/test_bundle.py, tests/test_cpp_pattern_detectors.py]
      examples: [examples/case84_bundle_soname_skew]

  - id: UC-WF-oneshot-deep
    axis: workflow
    name: One-shot deep compare (auto-collect L3–L5) + CLI usability
    status: partial
    gap: G21
    plan: docs/contribute/plans/g21-oneshot-deep-compare.md
    evidence:
      modules: [abicheck/cli.py, abicheck/cli_help.py, abicheck/cli_dump_helpers.py]
      tests: [tests/test_depth_vocabulary.py, tests/test_cov95_cli.py]
    next_steps: >
      Superseded: the standalone `deep-compare` orchestrator was removed and
      folded into `compare`. `compare old.so new.so --sources old=./src1
      --sources new=./src2 --max` collects L3-L5 inline from each raw source
      tree (a pre-built `collect` pack is accepted too) and embeds it in the
      per-side snapshot before comparing — the one-shot deep route, now on
      `compare`. The text below is retained as historical context for G21.
      Shipped in PR #422: the --depth headers|build|graph|source|full dial
      (--max = --depth full) on dump reusing the scan_levels vocabulary/mapping;
      the one-shot `deep-compare` orchestrator (cli_max.py) that dumps both sides
      with --sources at --depth then compares, collapsing the six-stage
      dump→collect→merge→compare sequence the oneDAL eval required into one
      command; option collapsing (rich-click groups M1, presets M2, vocab
      unification M5); a cross-platform --gcc-option; and a fail-loud signal on
      an empty requested layer (including empty-PARTIAL). The strict-mode honesty
      half (empty requested L4 => skipped) also shipped. Remaining (deferred as
      explicit opt-in per the P09 "warn, don't guess" design): header/source
      auto-discovery (G21.2) and compile_commands.json auto-synthesis (G21.6) —
      both guess inputs, so they must stay opt-in flags, not defaults.

  - id: UC-WF-cli-contract
    axis: workflow
    name: CLI interface contract, config balance, and extension policy
    status: complete
    gap: G22
    plan: docs/contribute/plans/g22-cli-consolidation.md
    evidence:
      modules: [abicheck/api_types.py, abicheck/service.py, abicheck/cli_options.py, abicheck/buildsource/inline.py]
      tests: [tests/test_cli_contract.py, tests/test_api_types.py, tests/test_config_rebalance.py]
      docs: [docs/contribute/adr/037-cli-interface-contract.md, docs/contribute/plans/g22-cli-consolidation.md]
    next_steps: >
      ADR-037. Formalises the CLI/API as three tiers (core/service/front-end)
      with the service layer as the only chokepoint, typed CompareRequest
      dataclasses (mirroring ADR-035 ScanRequest), one decorator per shared
      option family, a single --depth vocabulary (dropping the "evidence"
      naming and the user-facing L5 graph rung), folding compare-release and
      deep-compare into compare, renaming --header-backend to --ast-frontend,
      a CLI/config rebalance into .abicheck.yml, an explicit exit-code scheme,
      and a cli-contract CI gate. Backward-compat mechanism designed but left
      advisory until 1.0 (pre-1.0, breaking invocations is acceptable now).
      Phased in plans/g22-cli-consolidation.md (P1 chokepoint + typed request,
      P2 decorators, P3 depth vocab, P4 command fold, P5 config rebalance,
      P6 ast-frontend/MCP-name-map/validation, P7 deprecation scaffolding).

  - id: UC-WF-wheel-vendored
    axis: workflow
    name: manylinux/auditwheel vendored-library pairing (hashed sonames)
    status: complete
    gap: G9
    plan: docs/contribute/plans/g9-wheel-vendored-matching.md
    note: >
      Filename-based pairing: strip_vendor_hash() normalizes the
      auditwheel/delocate content-hash suffix (-[0-9a-f]{6,16}) before
      compare-release's matching pass (_canonical_library_key), so a bundled
      libpng16-<hash>.so.16.x now pairs across rebuilds instead of showing as
      removed+added, while a real break in the paired dependency (e.g. a
      pyzmq-style libsodium SONAME major bump) still surfaces. The plan's
      "AND soname" half is done too: bundle.py's cohort-scoped SONAME-skew
      detector (_members()) and diff_cpp_patterns.bundle_members_from_directory
      now strip_vendor_hash() the DT_SONAME they read directly off the ELF
      (not just the filename), so BundleMember.soname always carries the
      canonical logical SONAME regardless of a hash-suffix rebuild.
    evidence:
      modules: [
        abicheck/cli_helpers_compare.py,
        abicheck/bundle.py,
        abicheck/diff_cpp_patterns.py,
      ]
      tests: [
        tests/test_compare_release.py,
        tests/test_bundle.py,
        tests/test_cov95_diff_cpp_patterns.py,
      ]

  - id: UC-WF-stable-abi-subset
    axis: workflow
    name: CPython Limited-API / abi3 import-contract conformance
    status: complete
    gap: G14
    plan: docs/contribute/plans/g14-stable-abi-subset.md
    evidence:
      modules: [
        abicheck/python_ext.py,
        abicheck/stable_abi.py,
        abicheck/stable_abi_data.py,
        abicheck/diff_python.py,
        abicheck/cli_scan.py,
      ]
      tests: [tests/test_python_ext.py]
    notes: >
      An abi3 module's compatibility surface is the set of CPython C-API symbols
      it IMPORTS, not what it exports. python_ext recognises extension modules
      (Cython/pybind11/nanobind/C) from the PyInit_* export and Py* import
      surface; stable_abi classifies each import against the Limited-API allowlist
      for a target Py_LIMITED_API floor; diff_python raises
      PYTHON_STABLE_ABI_VIOLATION (a new private _Py* import in an abi3 module) at
      compare time. Interpreter-floor conformance is checked by the
      `abicheck scan --abi3` audit (diff_python.audit_stable_abi_imports; the
      user supplies the target floor, which a bare .abi3.so does not carry),
      which audits a single module and surfaces a violation the export-table view
      would call COMPATIBLE (advisory, or gated via
      --crosscheck python_stable_abi_violation=error).

  - id: UC-WF-audit
    axis: workflow
    name: Single-binary ABI audit / lint (no baseline)
    status: planned
    gap: G11
    plan: docs/contribute/plans/g11-single-binary-audit.md
    next_steps: >
      Add an `audit`/`dump --lint` driver running the single-snapshot subset of
      detectors (executable stack, insecure RPATH/RUNPATH, missing SONAME,
      unversioned symbols, internal-looking globals) so a library author can
      scan before their first release. Substrate already on the snapshot model;
      only the one-sided driver + hygiene rules are missing (F3).

  - id: UC-WF-security-hardening
    axis: workflow
    name: Security-hardening drift scan (checksec across releases)
    status: complete
    note: >
      G12 closed. The ELF snapshot now captures the full checksec-equivalent
      surface — RELRO (none/partial/full), BIND_NOW, PIE, stack-canary,
      FORTIFY_SOURCE, and writable+executable (W^X) segments — alongside the
      pre-existing executable_stack. Weakening transitions emit dedicated
      COMPATIBLE_WITH_RISK kinds (relro_weakened, pie_disabled,
      stack_canary_removed, fortify_source_weakened, writable_executable_segment).
      A shipped, turnkey policy (abicheck/policies/security.yaml) is referenced
      by name as `--policy-file security` and promotes all hardening kinds to
      break. Validated end-to-end: real hardened-vs-unhardened .so parsing
      (integration), the diff detectors and policy gating (unit), and the
      built-in policy resolution.
    evidence:
      modules: [abicheck/elf_metadata.py, abicheck/diff_platform.py, abicheck/checker_policy.py, abicheck/policies/security.yaml]
      tests: [tests/test_diff_platform_deep.py, tests/test_elf_metadata_unit.py, tests/test_policy_file.py, tests/test_elf_parse_integration.py]
      docs: [docs/use/security-hardening.md]

  - id: UC-WF-probe-matrix
    axis: workflow
    name: Build-configuration matrix (probe harness)
    status: complete
    note: >
      G2 closed. Matrix findings fold into `compare`/`compare-release` via
      `--probe-matrix old=... --probe-matrix new=...`. Both build-config kinds are now
      proven end-to-end through the mainline command:
      CXX_STANDARD_FLOOR_RAISED and — after the relocatable-object symbol-surface
      fix — API_DEPENDS_ON_CONSUMER_ENV. parse_elf_metadata now falls back to
      `.symtab` when a `.o` has no `.dynsym`, so a probe object's defined global
      symbols are captured and the env-dependence detector fires over the real
      compiled surface (tests/test_probe_examples.py +
      tests/test_elf_object_surface.py).
    evidence:
      modules: [abicheck/probe_harness.py, abicheck/diff_build_config.py, abicheck/elf_metadata.py]
      tests: [tests/test_probe_harness.py, tests/test_probe_examples.py, tests/test_elf_object_surface.py]
      examples: [examples/probes/onedpl.yaml, examples/probes/cxx_standard.yaml, examples/probes/feature_macro.yaml]

  # ── reporting ─────────────────────────────────────────────────────────────
  - id: UC-REP-json
    axis: reporting
    name: JSON report (versioned schema)
    status: complete
    evidence:
      modules: [abicheck/reporter.py, abicheck/schemas/__init__.py]
      tests: [tests/test_reporter.py, tests/test_report_schema.py]

  - id: UC-REP-sarif
    axis: reporting
    name: SARIF 2.1.0 (GitHub Code Scanning)
    status: complete
    evidence:
      modules: [abicheck/sarif.py]
      tests: [tests/test_sarif.py]

  - id: UC-REP-junit
    axis: reporting
    name: JUnit XML (CI dashboards)
    status: complete
    evidence:
      modules: [abicheck/junit_report.py]
      tests: [tests/test_junit_report.py]

  - id: UC-REP-markdown-html
    axis: reporting
    name: Markdown / HTML reports
    status: complete
    note: >
      Structural coverage across verdict tiers and the major sections (summary,
      severity groups, impact, release recommendation, confidence) plus HTML
      escaping is asserted in tests/test_report_sections.py; appcompat and
      stack-check render paths are exercised by the workflow E2E tests.
    evidence:
      modules: [abicheck/reporter.py, abicheck/html_report.py]
      tests: [tests/test_format_compliance.py, tests/test_sprint9_html.py, tests/test_report_sections.py]

  # ── toolchain / language standard ───────────────────────────────────────────
  - id: UC-TC-glibc-floor
    axis: toolchain
    name: Platform baseline floor (manylinux glibc requirement)
    status: complete
    gap: G10
    plan: docs/contribute/plans/g10-glibc-floor-check.md
    evidence:
      modules: [
        abicheck/diff_versioning.py,
        abicheck/checker_policy.py,
        abicheck/change_registry_coverage.py,
        abicheck/package.py,
      ]
      tests: [tests/test_environment_drift.py, tests/test_package.py]
    note: >
      check_platform_baseline_floor() (diff_versioning.py) compares the max
      GLIBC_2.x in elf.versions_required (plus the implied floor from
      DT_RELR, glibc >= 2.36) against a declared floor and emits
      platform_baseline_floor_raised (new deployment-RISK ChangeKind) — fires
      even with no old/new delta, unlike the existing runtime_floor_raised
      contract. Declared via --env-matrix's existing runtime_floors
      (ADR-020b) — no dedicated CLI flag; runtime_floors keys are matched
      case-insensitively, same as apply_runtime_floor_contract.
      package.parse_manylinux_glibc_floor() derives the floor from a
      manylinux wheel tag (PEP 600 + legacy aliases) for programmatic/future
      auto-derivation use — not yet wired into any CLI path.
      compare-release (directory/package release fan-out) does not support
      --env-matrix yet, so this check does not run there either. Both gaps
      are deliberately out of scope for G10 (see its plan's acceptance
      criteria, which this entry's evidence satisfies in full) and are
      tracked as the multi-platform follow-up under G27
      (UC-TC-wheel-deployment-claims): auto-deriving the floor from a
      compared wheel's own tag and enforcing it end-to-end in a single
      `compare old.whl new.whl` invocation, alongside the GLIBCXX/CXXABI/
      musllinux/macOS/Windows extensions.

  - id: UC-TC-numpy-capi-envelope
    axis: toolchain
    name: NumPy C-API build/target compatibility envelope
    status: partial
    gap: G26
    plan: docs/contribute/plans/g26-numpy-capi-envelope.md
    evidence:
      modules: [
        abicheck/numpy_capi.py,
        abicheck/diff_numpy_capi.py,
        abicheck/change_registry_numpy.py,
        abicheck/package.py,
      ]
      tests: [
        tests/test_numpy_capi.py,
        tests/test_diff_numpy_capi.py,
        tests/test_package.py,
      ]
    next_steps: >
      DONE: NPY_TARGET_VERSION and _ARRAY_API/_UFUNC_API consumption
      extracted from binary evidence (rodata/string scanning, no header
      needed — numpy_capi.extract_numpy_capi_surface), cross-checked
      against the declared numpy requirement range in wheel metadata
      (package.parse_wheel_numpy_requirement). Emits
      numpy_capi_consumption_added/removed, numpy_target_floor_raised
      (wired into compare() unconditionally),
      numpy_metadata_understates_required_version, and
      numpy_abi_major_incompatible (both via the standalone
      check_numpy_metadata_contract, mirroring G10's
      parse_manylinux_glibc_floor — not auto-wired into the CLI compare
      path). NumPy's capsule-based C-API is invisible to ordinary
      symbol-table diffing.
      STILL PLANNED: the raw NPY_ABI_VERSION/NPY_API_VERSION hex constants
      (confirmed during implementation to need disassembly to recover — a
      new heavy dependency out of scope, same reasoning as G4),
      numpy_build_runtime_contract_mismatch,
      numpy_deprecated_c_api_reintroduced (NPY_NO_DEPRECATED_API leaves no
      runtime-visible trace at all), numpy_api_used_above_declared_floor,
      a rendered support-envelope string, and a compiled examples/ fixture
      pair. See the plan's "Out of scope" section for the full breakdown.

  - id: UC-TC-wheel-deployment-claims
    axis: toolchain
    name: Wheel tag / deployment-claim verification (multi-platform)
    status: partial
    gap: G27
    plan: docs/contribute/plans/g27-wheel-deployment-verification.md
    evidence:
      modules: [
        abicheck/diff_versioning.py,
        abicheck/diff_wheel_deployment.py,
        abicheck/package.py,
        abicheck/change_registry_wheel.py,
      ]
      tests: [
        tests/test_environment_drift.py,
        tests/test_diff_wheel_deployment.py,
        tests/test_package.py,
      ]
    next_steps: >
      DONE: check_platform_baseline_floor (G10) generalized from GLIBC-only
      to also check GLIBCXX_*/CXXABI_* independently, each against its own
      declared runtime_floors entry (diff_versioning.py). New
      check_musllinux_glibc_dependency flags a musllinux-tagged binary that
      requires any glibc-flavoured versioned symbol at all — a yes/no
      compatibility check, not a numeric floor, since musl carries no
      symbol-versioning namespace to compare against. New
      check_macos_deployment_target_floor (diff_wheel_deployment.py) is the
      macOS half of G10's idea: LC_VERSION_MIN_MACOSX/LC_BUILD_VERSION vs. a
      declared MACOS_DEPLOYMENT_TARGET floor, same runtime_floors/
      --env-matrix mechanism. package.py gained
      parse_musllinux_floor/parse_macos_deployment_target_floor tag parsers
      alongside G10's parse_manylinux_glibc_floor (all three share
      _wheel_platform_tag_segment). New ChangeKinds:
      musllinux_glibc_dependency_detected, macos_deployment_target_raised.
      check_wheel_tag_architecture_mismatch (diff_wheel_deployment.py) is
      the wheel-tag-claim counterpart to G13's elf_machine_changed/
      macho_cpu_type_changed (which compare two arbitrary binaries, not a
      binary against its own wheel's filename promise): a single-arch wheel
      tag (package.parse_wheel_architecture_claim, wrapping the existing
      _platform_machine_from_wheel_filename) vs. the binary's own ELF
      e_machine/Mach-O cpu_type, declared via runtime_floors["WHEEL_ARCH"] —
      new ChangeKind wheel_tag_architecture_mismatch (BREAKING, since a
      mismatch means the wheel cannot load on its claimed architecture at
      all, not merely a deployment risk).
      check_wheel_rpath_not_portable flags a non-$ORIGIN-relative RPATH/
      RUNPATH entry (RISK) — the classic "works in CI, ImportError on the
      user's machine" bug auditwheel/delocate exist to fix.
      check_wheel_closure_dependency_violation flags a DT_NEEDED entry
      matching auditwheel/delocate's vendored content-hash naming
      convention (reusing G9's strip_vendor_hash pattern) with no
      $ORIGIN-relative RPATH/RUNPATH to ever find it (BREAKING) —
      deliberately narrower than a general "dependency outside the
      permitted closure" check (which would need a real per-manylinux/
      musllinux-tag allowed-SONAME policy to avoid false positives on
      legitimately-present system libraries; see the plan's "Out of scope"
      for why that full policy replication is deferred). Both gated on a
      dedicated runtime_floors["WHEEL_CONTEXT"] opt-in key rather than any
      declared floor, since GLIBC/GLIBCXX/CXXABI are a general-purpose
      ADR-020b mechanism unrelated to wheel packaging and an ordinary
      non-wheel DSO declaring one must not get wheel-portability findings
      it never opted into (Codex review). Both ELF/Linux only; both new
      ChangeKinds: wheel_rpath_not_portable, wheel_closure_dependency_violation.
      STILL PLANNED: end-to-end CLI auto-derivation (a single
      `compare old.whl new.whl` deriving runtime_floors from the *new*
      wheel's own filename tag with no --env-matrix flag needed — today all
      of the checks above, including G10's original GLIBC one, still require
      an explicit --env-matrix declaration); Windows UCRT/MSVC-runtime
      requirement checking; CPU-ISA-baseline (dispatch-symbol-name
      heuristic) detection; the full per-tag allowed-dependency policy (the
      general wheel-closure check, beyond the vendored-hash-specific case
      above); macOS RPATH/@loader_path portability; a compiled examples/
      fixture pair. See the plan's "Out of scope" and the
      SciPy/Scientific-Python Roadmap for full context.

  - id: UC-TC-dual-abi
    axis: toolchain
    name: libstdc++ dual ABI flip (_GLIBCXX_USE_CXX11_ABI)
    status: complete
    evidence:
      examples: [examples/case104_glibcxx_dual_abi_flip]

  - id: UC-TC-flag-drift
    axis: toolchain
    name: Toolchain flag drift (DW_AT_producer)
    status: complete
    evidence:
      modules: [abicheck/diff_build_config.py]
      examples: [examples/case103_toolchain_flag_drift]

  - id: UC-TC-modern-cxx-types
    axis: toolchain
    name: Integer model / char8_t / _BitInt / atomic / ABI tags
    status: complete
    evidence:
      modules: [abicheck/diff_integer_model.py, abicheck/diff_char8t.py, abicheck/diff_bit_int.py, abicheck/diff_atomic.py]
      examples: [examples/case112_lp64_ilp64]

  - id: UC-TC-header-scope-robustness
    axis: toolchain
    name: Header-scoped source-mode robustness on stock host toolchains
    status: complete
    gap: G16
    plan: docs/contribute/plans/g16-header-scope-toolchain-robustness.md
    evidence:
      modules: [abicheck/dumper.py, abicheck/errors.py]
      tests: [
        tests/test_castxml_toolchain_robustness.py,
        tests/test_header_scope_toolchain.py,
      ]
    note: >
      Header-scoped scans (the castxml source path) are how abicheck separates
      public source API from private/internal surface, but in the 2026-06
      real-world cron the scoped re-run aborted before any comparison in 21 issue
      records — always the same host-toolchain parse failures, never an abicheck
      logic bug: glibc sized-float types (`unknown type name '_Float32'`,
      `_Float64`/`_Float128`), a GCC 13 libstdc++ `__assume__` attribute, and
      `--lang c` rejecting `extern "C"` headers guarded by `#ifdef __cplusplus`.
      `_castxml_failure_hint` classifies all three stderr signatures into a
      single actionable remediation, and on a sized-float /
      `__assume__` failure `_castxml_version_note` probes `castxml --version` and
      folds in the recommended Clang floor (`_RECOMMENDED_CLANG_MAJOR` = 18) so
      the user is told exactly what to upgrade — unit-tested in
      `tests/test_castxml_toolchain_robustness.py`. A `-D_FloatN` preprocessor
      shim was prototyped and REJECTED: glibc's own `typedef float _Float32;`
      fallback would be rewritten into `typedef float float;` (PR review). The
      durable cure is a castxml built against a newer Clang, or the libclang
      extractor (G4). Per-side L2 backend selection
      (`--old-ast-frontend`/`--new-ast-frontend` on `compare`,
      each inheriting `--ast-frontend`) so a release whose new headers need the
      host toolchain parses on clang while the old release keeps the castxml schema
      reference — the backend mirror of `--header old=`/`--header new=`
      (tests/test_cli_new_features.py::TestPerSideHeaderBackend). A recognised
      host-toolchain signature now raises the dedicated `HeaderToolchainError`
      (a `SnapshotError` subclass, so existing `except SnapshotError` handling
      is unaffected) so callers can branch on "this failure carries an
      actionable remediation" vs. an opaque castxml error.
      `tests/test_header_scope_toolchain.py` (integration) runs
      `_castxml_dump` over a real `<math.h>`-including header on the CI host
      and asserts either outcome is handled cleanly: parse succeeds, or it
      degrades to `HeaderToolchainError` with the hint — never a bare
      unclassified `SnapshotError`.

  - id: UC-TC-cxx-standard-floor
    axis: toolchain
    name: C++ standard floor raised (per-consumer source break)
    status: complete
    note: >
      Surfaced through the mainline `compare`/`compare-release` command via
      `--probe-matrix old=... --probe-matrix new=...`; the finding reaches the verdict and
      the JSON/SARIF output (proven in tests/test_probe_examples.py).
    evidence:
      examples: [examples/case98_cxx_standard_floor_raised, examples/probes/cxx_standard.yaml]
      tests: [tests/test_probe_examples.py, tests/test_diff_build_config.py]

  # ── workflow: real-world validation corpus (field-eval G17) ────────────────
  - id: UC-WORKFLOW-real-world-corpus
    axis: workflow
    name: Real-world upstream-library validation corpus (conda-forge)
    status: partial
    gap: G17
    plan: docs/contribute/plans/g17-real-world-corpus.md
    evidence:
      modules: [eval/runner.py, eval/condafetch.py]
      tests: [eval/manifest.yaml, eval/results/latest.json]
    next_steps: >
      Reproducible benchmark (eval/) of abicheck against real conda-forge
      libraries: a curated manifest with expected verdicts, a runner that fetches
      + scans, and a generated report. DONE: binary (L0/L1) tier — verdicts match
      the manifest 'expect'. PLANNED: (1) a source (L3/L4/L5) tier in the runner
      that clones at the tag and runs `dump --sources`; (2) a scheduled CI lane
      that fails on verdict drift; (3) corpus growth to Rust/Go/Qt/Boost and
      win-64/osx-64 subdirs.

  # ── toolchain: Bazel build-evidence (field-eval P21 / G18) ─────────────────
  - id: UC-TC-bazel-build-evidence
    axis: toolchain
    name: Bazel-built C++ project build evidence (L3)
    status: modeled
    gap: G18
    plan: docs/contribute/plans/g18-bazel-build-evidence.md
    evidence:
      modules: [abicheck/buildsource/adapters/bazel.py]
    next_steps: >
      The cquery/aquery jsonproto adapter exists but is unvalidated end-to-end on
      a real Bazel C++ project (field-eval P21: oneDAL is Bazel + makefile, no
      CMake, so its L3/L4/L5 path was unreachable without the full Intel
      toolchain). Capture a pre-recorded `bazel aquery --output=jsonproto` from a
      small Bazel C++ project, add it as a non-executing fixture + test asserting
      non-empty BuildEvidence, then flip to partial/complete.

  - id: UC-TC-make-auto-query
    axis: toolchain
    name: GNU Make / EPICS-style zero-config build evidence (L3)
    status: complete
    note: >
      `--sources` on a Make project now runs a fixed GNU Make dry-run query
      (`-B -n -k -w`) when no compile DB exists, selects a GNU launcher by
      version-probing the resolved executable path, cleanly skips BSD/non-GNU
      make, and scrapes recursive directory markers from make/gmake/gnumake/
      mingw32-make transcript spellings. CI unit tests cover launcher selection,
      non-GNU fallback/skip, resolved-path execution, and directory-marker
      parsing; PR #464 also carries a pvxs real-world proof with
      compile_commands.json temporarily removed.
    evidence:
      modules: [abicheck/buildsource/build_query.py, abicheck/buildsource/adapters/make.py]
      tests: [tests/test_build_query_inference.py, tests/test_make_adapter.py]
      docs: [docs/use/scan-levels.md, docs/learn/build-source-data.md]

  # ── PR-tier source intelligence & cross-source validation (ADR-035 / G19) ──
  - id: UC-WORKFLOW-pr-source-tier
    axis: workflow
    name: Always-on compiler-free PR pre-scan + deterministic level selection
    status: complete
    gap: G19
    plan: docs/contribute/plans/g19-pr-source-intelligence.md
    evidence:
      modules:
        - abicheck/buildsource/pattern_scan.py
        - abicheck/buildsource/preprocessor_scan.py
        - abicheck/buildsource/risk.py
        - abicheck/buildsource/scan_levels.py
        - abicheck/cli_scan.py
      tests:
        - tests/test_pattern_scan.py
        - tests/test_preprocessor_scan.py
        - tests/test_risk.py
        - tests/test_scan_levels.py
        - tests/test_cli_scan.py
        - tests/test_perf_binary_scan.py
        - tests/test_scenarios.py
    next_steps: >
      ADR-035 D2/D3. DONE: the compiler-free lexical pattern pre-scan
      (`buildsource/pattern_scan.py`); and the deterministic `scan` orchestrator
      (`cli_scan.py`) — classify → always-on tier (pattern S3 + crosscheck D4) →
      the pinned level (`--mode` preset or explicit `--source-method`/`--depth`,
      resolved by `buildsource/scan_levels.py`) → one coverage-annotated report,
      with `--baseline` comparison and a `--budget` failure guard (exit 5 on
      overflow, never shrinks scope). The numeric risk score (`buildsource/risk.py`,
      tunable `risk_rules`) changes depth ONLY under `--source-method auto`
      (opt-in). Reuses the existing collect-mode/replay-scope machinery; emits
      RISK/API_BREAK only (authority rule). The D7 POI work-list
      (`buildsource/poi.py`) focuses the expensive scan (Phase 3b, done). The S2
      preprocessor pre-scan (`buildsource/preprocessor_scan.py`) — per-TU ABI
      macro-value capture + divergence and public-header private/generated-header
      leak detection, conditional on a compile DB + `clang -E` — is done and wired
      into `scan` with honest coverage. Depth semantics are tracked end-to-end:
      the binary rung stays artifact-only (L0/L1) even when matrix-wide headers,
      sources, or compile DB flags are present, with a slow native-ELF perf guard
      and scenario catalog entry preventing source-pattern/L3 work from leaking
      back into `--depth binary`. Phase 1 (D2) complete.

  - id: UC-CHANGE-crosscheck-hygiene
    axis: change_class
    name: Cross-source validation findings (intra-version ABI hygiene)
    status: complete
    gap: G19
    plan: docs/contribute/plans/g19-pr-source-intelligence.md
    evidence:
      modules:
        - abicheck/buildsource/crosscheck.py
      tests:
        - tests/test_crosscheck.py
    next_steps: >
      ADR-035 D4. DONE: the intra-version cross-source engine
      (`buildsource/crosscheck.py`, `run_crosschecks`) and its four
      correctly-partitioned ChangeKinds — exported_not_public (RISK),
      public_not_exported (RISK), header_build_context_mismatch (API_BREAK),
      private_header_leak (RISK) — which diff ONE merged snapshot's evidence
      sources against each other, with per-check coverage rows and the §6.8
      provider-agreement matrix; each skips (never false-positives) when its
      evidence is absent and is never BREAKING (authority rule). REMAINS:
      odr_type_variant and public_to_internal_dependency; wiring the engine into
      the Phase-3 `scan`/`audit` orchestrator and the `crosschecks:` severity
      config; and FP-rate-gate corpus cases before any check is promoted to gate.

  - id: UC-WORKFLOW-single-release-audit
    axis: workflow
    name: Single-release ABI-hygiene audit (no baseline compare)
    status: complete
    gap: G19
    plan: docs/contribute/plans/g19-pr-source-intelligence.md
    evidence:
      modules:
        - abicheck/cli_scan.py
        - abicheck/buildsource/crosscheck.py
      tests:
        - tests/test_scan_estimate.py
        - tests/test_crosscheck.py
    next_steps: >
      ADR-035 D8. DONE: `scan --audit` runs the D2 pattern facts + D4 cross-checks
      intra-version with no baseline and renders the hygiene catalog (accidental
      ABI surface, public-not-exported, header/build-context mismatch,
      private-header leaks + advisory pattern facts), severity-mapped via
      `--crosscheck KEY=LEVEL` (`_audit_exit_code`: RISK advisory, API_BREAK →
      exit 2, promoted checks gate). REMAINS: `surface-report` reuse and the deeper one-time checks
      (ODR variants, visibility/versioning hygiene, RTTI-for-internal-types) once
      their ChangeKinds land (Phase 2 tail).

  - id: UC-WORKFLOW-evidence-directed-scope
    axis: workflow
    name: Evidence-directed scan focusing (binary/header facts steer source scan)
    status: complete
    gap: G19
    plan: docs/contribute/plans/g19-pr-source-intelligence.md
    evidence:
      modules:
        - abicheck/buildsource/poi.py
        - abicheck/cli_scan.py
      tests:
        - tests/test_poi.py
    next_steps: >
      ADR-035 D7. DONE: `buildsource/poi.py` (`build_points_of_interest`) computes
      a points-of-interest work-list from the changed-path floor + pattern-scan
      escalation triggers + L0/L1/L2 export deltas + risk score (reverse of the
      explain-finding localization walk); the floor is unconditional (risk/deltas
      only add, never drop a changed TU). `scan` runs the pattern pre-scan first
      and feeds `poi.changed_paths()` into the source-replay scope. The
      `scan --baseline` path now reads a cheap, header-free L0 view of both sides
      (`_load_exports_for_poi`) so the export-delta walk runs live, and
      `resolve_symbol_tus` turns the resulting symbol POIs into declaring TUs
      (via the baseline's cached L5 graph) that join the replay seed *and* the
      `crosscheck` changed-path work-list. REMAINS: symbol→TU resolution only
      fires when the baseline carries an L5 graph (the full-depth ADR-035 D9
      baseline); a shallow baseline still focuses on changed paths only.

  - id: UC-TC-build-emitted-facts
    axis: toolchain
    name: Build-emitted source facts (artifact protocol + Clang plugin/wrapper)
    status: complete
    gap: G19
    plan: docs/contribute/plans/g19-pr-source-intelligence.md
    evidence:
      modules:
        - abicheck/buildsource/inputs_pack.py
        - abicheck/buildsource/inputs_emit.py
        - abicheck/cc_wrapper.py
        - contrib/abicheck-clang-plugin/README.md
      tests:
        - tests/test_inputs_pack.py
        - tests/test_inputs_emit.py
    next_steps: >
      ADR-035 D5, complete. The `abicheck_inputs/` dump/facts artifact protocol
      (normalized source_facts/*.jsonl canonical → L4, build/compile_commands.json
      → L3; raw AST forensic-only) is ingested without re-running a frontend via
      the existing `merge` (`ingest_inputs_pack`, dir input auto-detected). The
      supported portable producer is the `abicheck-cc` compiler wrapper
      (pass-through compile + best-effort castxml/clang extraction →
      `inputs_emit`); the Clang plugin (`contrib/abicheck-clang-plugin/`) is the
      optional optimization that removes the second frontend pass, with GCC/MSVC
      fallbacks documented. Lets vendor/closed builds contribute exact-build-context
      facts without shipping sources; replay stays the portable default.

  - id: UC-REPORTING-scan-coverage-estimate
    axis: reporting
    name: Scan coverage/confidence report + per-project cost estimate
    status: complete
    gap: G19
    plan: docs/contribute/plans/g19-pr-source-intelligence.md
    evidence:
      modules:
        - abicheck/cli_scan.py
        - abicheck/service.py
      tests:
        - tests/test_cli_scan.py
        - tests/test_scan_estimate.py
    next_steps: >
      ADR-035 D9/D10. DONE: `scan` emits one report (text/JSON) carrying the
      per-tier coverage table (intrinsic L0-L2 + pattern-scan S3 + L3/L4/L5 pack
      coverage + per-crosscheck rows + the POI focus summary), the resolved level,
      and the risk score, so a partial scan is legible (never a bare "source scan
      failed"). DONE: the typed `ScanRequest`/`CostEstimate`/`LayerResult`/`Budget`
      API + `service.estimate_scan()` (probes TU count from the compile DB /
      source tree + header fan-out), surfaced as `scan --estimate` — a
      dry-run that scans nothing. REMAINS: the full
      `ScanResult`/`run_scan` refactor (move the `cli_scan` orchestration body into
      `service.py`) and the provider-agreement/confidence matrix rendered by
      reporter/PR-comment/SARIF.

  # ── Source-scan & cross-source example corpus (ADR-035 / G20) ──────────────
  - id: UC-WORKFLOW-audit-example-corpus
    axis: workflow
    name: Single-release audit example corpus (no baseline)
    status: partial
    gap: G20
    plan: docs/contribute/plans/g20-source-scan-example-catalog.md
    evidence:
      modules: [abicheck/buildsource/crosscheck.py, scripts/gen_g20_fixtures.py]
      tests: [tests/test_g20_catalog.py]
      examples:
        - examples/case143_audit_accidental_export
        - examples/case144_audit_private_header_leak
        - examples/case145_audit_unversioned_export
        - examples/case146_audit_rtti_for_internal
        - examples/case147_scan_depth_ladder
    next_steps: >
      ADR-035 D8 (G20.1). DONE: the Phase 0 `ground_truth.json` v4 schema
      (`mode: audit`, canonical `expected_kinds`, `provider_assertions`,
      `fixtures`) plus five single-build catalog cases (143-147) reaching a
      verdict from one artifact with no baseline — `exported_not_public`,
      `private_header_leak`, `unversioned_exported_symbol`,
      `rtti_for_internal_type`, and the depth-ladder case. Each ships a committed
      `snapshot.abi.json` validated compiler-free by `tests/test_g20_catalog.py`.
      The depth-ladder legibility property (case147) is now asserted compiler-free
      as provider escalation — the same input flags `private_header_leak` from
      `public_header_ast` alone at shallow depth and gains the `source_index`
      corroboration with the L5 graph attached
      (`test_g20_catalog.py::test_depth_ladder_deepens_corroboration_same_input`).
      REMAINING: a live S3->S5 depth-ladder run under the `integration` (castxml)
      lane to show the real per-depth *timing* (the compiler-free test proves the
      coverage escalation, not the cost), and Flow-2 `abicheck_inputs/` pack
      fixtures alongside the committed snapshots. See plan Phase 1.

  - id: UC-CHANGE-crosscheck-example-corpus
    axis: change_class
    name: Cross-source corroboration example corpus (combination beats one source)
    status: partial
    gap: G20
    plan: docs/contribute/plans/g20-source-scan-example-catalog.md
    evidence:
      modules: [abicheck/buildsource/crosscheck.py]
      tests: [tests/test_xcheck_scenarios.py, tests/test_g20_catalog.py]
      examples:
        - examples/case148_xcheck_header_build_mismatch
        - examples/case149_xcheck_odr_variant
        - examples/case150_xcheck_export_public_pair
        - examples/case151_xcheck_provider_matrix
    next_steps: >
      ADR-035 D4 (G20.2). DONE: four `examples/caseNN` cases whose finding is
      invisible/ambiguous to any single source and resolves only by crosschecking
      two — `header_build_context_mismatch` (L2 macros vs L3 flags),
      `odr_type_variant` (L4 layout vs layout), the bidirectional
      `exported_not_public`/`public_not_exported` pair, and the provider-agreement
      matrix (§6.8) asserting the recorded provider list differs for the rich vs
      thin corroboration. `tests/test_xcheck_scenarios.py` adds the clean negative
      counterpart per finding (FP-rate gate stays 0/0). REMAINING: deriving a
      per-finding confidence tag from provider count is a separate reporting
      enhancement, deliberately out of scope here. See plan Phase 2.

  - id: UC-WORKFLOW-focusing-example-corpus
    axis: workflow
    name: Evidence-directed focusing scenarios (sources steer sources)
    status: partial
    gap: G20
    plan: docs/contribute/plans/g20-source-scan-example-catalog.md
    evidence:
      modules: [abicheck/buildsource/poi.py, abicheck/buildsource/source_link.py]
      tests: [tests/test_poi_scenarios.py, tests/test_source_evidence_integrity.py]
    next_steps: >
      ADR-035 D7 (G20.3). DONE: test-only scenario suites
      (`tests/test_poi_scenarios.py`, `tests/test_source_evidence_integrity.py`)
      asserting on the scan plan, not just the verdict — an export delta targeting
      the changed symbol, an exported template instantiation seed, the D7
      changed-path floor (a mis-weighted `risk_rules` cannot drop a changed TU),
      and the D4 unlinked-source-evidence integrity guard (the oneDAL shape: many
      exports, TUs parsed, zero matched symbols reported as degraded, never clean).
      ALSO DONE: the §3.3 `_layers_from_coverage` plumbing — the L4 source-link
      boundary integrity counters (`exported_symbols`/`matched_symbols`/
      `unmatched_symbols` + `facts`) now ride the crosscheck coverage row
      (`crosscheck._coverage_row`/`_CheckOutput`) onto the rendered
      `ScanResult` layer (`service_scan.LayerResult.counters`), so a degraded
      link is named on the report even when ODR runs clean
      (`test_source_evidence_integrity.py::test_integrity_counters_surface_on_rendered_scan_layers`).
      REMAINING: nothing in this entry. See plan Phase 3.
