From 93361ca3da858d770593f64e76d92f3802632c25 Mon Sep 17 00:00:00 2001 From: OpenCode Date: Fri, 10 Jul 2026 16:13:53 +0700 Subject: [PATCH] feat: Phase Y Y5 phase Y accountability gate schema (YAG-) -- top-level gate asserting CBR+FIA+SDA+PMC; closes Phase Y baseline accountability loop (#858) REQUIRED_Y_COMPONENTS=(CBR,FIA,SDA,PMC), VALID_YAG_VERDICTS (3: accountability_verified/accountability_partial/accountability_not_established). YComponentCheck helper; verdict: accountability_verified (all 4), accountability_partial (2-3), accountability_not_established (0-1). Each artifact_id prefix-validated (CBR-/FIA-/SDA-/PMC-); dry_lab_only=True. 54 tests; BASELINE updated 10710->10764. Completes Phase Y. --- docs/research/NEXT_100_PR_MAP.md | 2 +- .../evidence/phase_y_accountability_gate.py | 156 +++++++++ .../test_phase_y_accountability_gate.py | 311 ++++++++++++++++++ tests/test_test_count_regression.py | 2 +- 4 files changed, 469 insertions(+), 2 deletions(-) create mode 100644 src/openamp_foundry/evidence/phase_y_accountability_gate.py create mode 100644 tests/evidence/test_phase_y_accountability_gate.py diff --git a/docs/research/NEXT_100_PR_MAP.md b/docs/research/NEXT_100_PR_MAP.md index 4cd120dc..f89af834 100644 --- a/docs/research/NEXT_100_PR_MAP.md +++ b/docs/research/NEXT_100_PR_MAP.md @@ -304,4 +304,4 @@ Track and publish structured comparisons between pipeline selections and cheap b | Y2 | Add feature importance audit schema (FIA-) (complete). — src/openamp_foundry/evidence/feature_importance_audit.py: VALID_FIA_VERDICTS (5), VALID_FEATURE_IMPORTANCE_LEVELS (4), VALID_AUDIT_FEATURES (8), DOMINATION_THRESHOLD=0.80; importance_level auto-assigned; top_feature/charge_score/length_score auto-extracted; verdict: charge_dominated when charge_explains_fraction>=0.80; dry_lab_only=True; 50 tests. | Documents which features drove selections and whether charge/length alone explains the result; anti-cheap-explanation gate. | C | | Y3 | Add selection diversity audit schema (SDA-) (complete). — src/openamp_foundry/evidence/selection_diversity_audit.py: VALID_SDA_VERDICTS (4: diverse_panel/moderately_diverse/proximity_driven/insufficient_data), VALID_DIVERSITY_METRICS (4), DIVERSE_PANEL_THRESHOLD=0.10, PROXIMITY_DRIVEN_THRESHOLD=-0.05, MIN_PANEL_SIZE=3; diversity_delta auto-computed; verdict: diverse_panel (delta>=0.10), proximity_driven (delta<=-0.05); dry_lab_only=True; 46 tests. | Tracks sequence diversity of selected panel vs random draw; detects proximity-driven selection masquerading as discovery; required before any novelty claim. | C | | Y4 | Add pipeline maturity certificate schema (PMC-) (complete). — src/openamp_foundry/evidence/pipeline_maturity_certificate.py: VALID_PMC_GRADES (A-D), VALID_PMC_VERDICTS (4: pipeline_validated/pipeline_provisional/pipeline_unvalidated/insufficient_evidence), REQUIRED_PMC_COMPONENTS=(CBR,FIA,SDA); PMCComponentCheck helper; grade A (all 3 superior), B (2), C (1), D (0/none assessed); contributes_to_grade auto-derived from verdict; dry_lab_only=True; 54 tests. | Aggregates CBR/FIA/SDA results into A/B/C/D maturity grade; anchors pre-registration; prevents retroactive interpretation. | C | -| Y5 | Add Phase Y accountability gate (YAG-). | Top-level gate asserting CBR+FIA+SDA+PMC all present; verdict: accountability_verified/accountability_partial/accountability_not_established; closes Phase Y; no external pilot claim is credible without passing this gate. | C | +| Y5 | Add Phase Y accountability gate (YAG-) (complete). — src/openamp_foundry/evidence/phase_y_accountability_gate.py: REQUIRED_Y_COMPONENTS=(CBR,FIA,SDA,PMC), VALID_YAG_VERDICTS (3: accountability_verified/accountability_partial/accountability_not_established); YComponentCheck helper; verdict: accountability_verified (all 4), accountability_partial (2-3), accountability_not_established (0-1); artifact_id prefix-validated per component; dry_lab_only=True; 54 tests. Closes Phase Y. | Top-level gate asserting CBR+FIA+SDA+PMC all present; closes Phase Y; no external pilot claim is credible without passing this gate. | C | diff --git a/src/openamp_foundry/evidence/phase_y_accountability_gate.py b/src/openamp_foundry/evidence/phase_y_accountability_gate.py new file mode 100644 index 00000000..fbeafb0a --- /dev/null +++ b/src/openamp_foundry/evidence/phase_y_accountability_gate.py @@ -0,0 +1,156 @@ +"""YAG- Phase Y accountability gate schema. + +Top-level gate asserting all four Phase Y accountability components are +present: CBR + FIA + SDA + PMC. + +No external pilot claim is credible without passing this gate. +Verdict: accountability_verified / accountability_partial / accountability_not_established. +""" + +from __future__ import annotations + +from dataclasses import dataclass + +REQUIRED_Y_COMPONENTS: tuple[str, ...] = ("CBR", "FIA", "SDA", "PMC") + +VALID_YAG_VERDICTS: frozenset[str] = frozenset({ + "accountability_verified", + "accountability_partial", + "accountability_not_established", +}) + +ACCOUNTABILITY_VERIFIED_REQUIRED_PRESENT: int = 4 +ACCOUNTABILITY_PARTIAL_MIN_PRESENT: int = 2 + + +@dataclass +class YComponentCheck: + component_type: str + artifact_id: str + present: bool + + +@dataclass +class PhaseYAccountabilityGate: + yag_id: str + pipeline_version: str + component_checks: list[YComponentCheck] + n_components_present: int + yag_verdict: str + dry_lab_only: bool + limitations: list[str] + created_at: str + + +def validate_phase_y_accountability_gate(yag: PhaseYAccountabilityGate) -> None: + if not yag.yag_id.startswith("YAG-"): + raise ValueError(f"yag_id must start with 'YAG-': {yag.yag_id!r}") + if not yag.pipeline_version: + raise ValueError("pipeline_version must be non-empty") + if len(yag.component_checks) != len(REQUIRED_Y_COMPONENTS): + raise ValueError( + f"component_checks must have exactly {len(REQUIRED_Y_COMPONENTS)} entries" + ) + for check in yag.component_checks: + if check.component_type not in REQUIRED_Y_COMPONENTS: + raise ValueError( + f"component_type {check.component_type!r} not in REQUIRED_Y_COMPONENTS" + ) + expected_prefix = f"{check.component_type}-" + if check.artifact_id and not check.artifact_id.startswith(expected_prefix): + raise ValueError( + f"artifact_id {check.artifact_id!r} must start with {expected_prefix!r}" + ) + n_present = sum(1 for c in yag.component_checks if c.present) + if yag.n_components_present != n_present: + raise ValueError("n_components_present mismatch") + if yag.yag_verdict not in VALID_YAG_VERDICTS: + raise ValueError( + f"yag_verdict {yag.yag_verdict!r} not in VALID_YAG_VERDICTS" + ) + if not yag.dry_lab_only: + raise ValueError("dry_lab_only must be True") + if not yag.limitations: + raise ValueError("limitations must be non-empty") + if not yag.created_at: + raise ValueError("created_at must be non-empty") + + +def _compute_verdict(n_present: int) -> str: + if n_present >= ACCOUNTABILITY_VERIFIED_REQUIRED_PRESENT: + return "accountability_verified" + if n_present >= ACCOUNTABILITY_PARTIAL_MIN_PRESENT: + return "accountability_partial" + return "accountability_not_established" + + +def build_phase_y_accountability_gate( + *, + yag_id: str, + pipeline_version: str, + cbr_artifact_id: str = "", + fia_artifact_id: str = "", + sda_artifact_id: str = "", + pmc_artifact_id: str = "", + limitations: list[str], + created_at: str, +) -> PhaseYAccountabilityGate: + """Build a PhaseYAccountabilityGate. + + Pass non-empty artifact_id for each component that is present. + Empty artifact_id means the component is absent (present=False). + """ + checks = [ + YComponentCheck( + component_type="CBR", + artifact_id=cbr_artifact_id, + present=bool(cbr_artifact_id), + ), + YComponentCheck( + component_type="FIA", + artifact_id=fia_artifact_id, + present=bool(fia_artifact_id), + ), + YComponentCheck( + component_type="SDA", + artifact_id=sda_artifact_id, + present=bool(sda_artifact_id), + ), + YComponentCheck( + component_type="PMC", + artifact_id=pmc_artifact_id, + present=bool(pmc_artifact_id), + ), + ] + n_present = sum(1 for c in checks if c.present) + verdict = _compute_verdict(n_present) + yag = PhaseYAccountabilityGate( + yag_id=yag_id, + pipeline_version=pipeline_version, + component_checks=checks, + n_components_present=n_present, + yag_verdict=verdict, + dry_lab_only=True, + limitations=limitations, + created_at=created_at, + ) + validate_phase_y_accountability_gate(yag) + return yag + + +def format_phase_y_accountability_gate(yag: PhaseYAccountabilityGate) -> str: + lines = [ + f"Phase Y Accountability Gate — {yag.yag_id}", + f"Pipeline: {yag.pipeline_version}", + f"Verdict: {yag.yag_verdict}", + f"Components present: {yag.n_components_present}/{len(REQUIRED_Y_COMPONENTS)}", + ] + lines.append("Component checks:") + for check in yag.component_checks: + status = "PRESENT" if check.present else "ABSENT" + artifact = f" [{check.artifact_id}]" if check.artifact_id else "" + lines.append(f" {check.component_type}: {status}{artifact}") + lines.append(f"Created: {yag.created_at}") + lines.append(f"Limitations: {'; '.join(yag.limitations)}") + lines.append(f"dry_lab_only: {yag.dry_lab_only}") + return "\n".join(lines) diff --git a/tests/evidence/test_phase_y_accountability_gate.py b/tests/evidence/test_phase_y_accountability_gate.py new file mode 100644 index 00000000..4186008d --- /dev/null +++ b/tests/evidence/test_phase_y_accountability_gate.py @@ -0,0 +1,311 @@ +"""Tests for YAG- Phase Y accountability gate schema.""" + +import pytest +from openamp_foundry.evidence.phase_y_accountability_gate import ( + PhaseYAccountabilityGate, + YComponentCheck, + REQUIRED_Y_COMPONENTS, + VALID_YAG_VERDICTS, + ACCOUNTABILITY_VERIFIED_REQUIRED_PRESENT, + ACCOUNTABILITY_PARTIAL_MIN_PRESENT, + build_phase_y_accountability_gate, + format_phase_y_accountability_gate, + validate_phase_y_accountability_gate, +) + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + + +def _build(**kwargs): + defaults = dict( + yag_id="YAG-001", + pipeline_version="v1.0", + cbr_artifact_id="CBR-001", + fia_artifact_id="FIA-001", + sda_artifact_id="SDA-001", + pmc_artifact_id="PMC-001", + limitations=["dry-lab only"], + created_at="2026-07-10", + ) + defaults.update(kwargs) + return build_phase_y_accountability_gate(**defaults) + + +# --------------------------------------------------------------------------- +# 1. Constants +# --------------------------------------------------------------------------- + + +def test_required_y_components_is_tuple(): + assert isinstance(REQUIRED_Y_COMPONENTS, tuple) + + +def test_required_y_components_contains_cbr(): + assert "CBR" in REQUIRED_Y_COMPONENTS + + +def test_required_y_components_contains_fia(): + assert "FIA" in REQUIRED_Y_COMPONENTS + + +def test_required_y_components_contains_sda(): + assert "SDA" in REQUIRED_Y_COMPONENTS + + +def test_required_y_components_contains_pmc(): + assert "PMC" in REQUIRED_Y_COMPONENTS + + +def test_required_y_components_has_four_entries(): + assert len(REQUIRED_Y_COMPONENTS) == 4 + + +def test_valid_yag_verdicts_is_frozenset(): + assert isinstance(VALID_YAG_VERDICTS, frozenset) + + +def test_valid_yag_verdicts_contains_accountability_verified(): + assert "accountability_verified" in VALID_YAG_VERDICTS + + +def test_valid_yag_verdicts_contains_accountability_partial(): + assert "accountability_partial" in VALID_YAG_VERDICTS + + +def test_valid_yag_verdicts_contains_accountability_not_established(): + assert "accountability_not_established" in VALID_YAG_VERDICTS + + +def test_accountability_verified_required_present(): + assert ACCOUNTABILITY_VERIFIED_REQUIRED_PRESENT == 4 + + +def test_accountability_partial_min_present(): + assert ACCOUNTABILITY_PARTIAL_MIN_PRESENT == 2 + + +# --------------------------------------------------------------------------- +# 2. build – happy paths +# --------------------------------------------------------------------------- + + +def test_build_returns_phase_y_accountability_gate(): + assert isinstance(_build(), PhaseYAccountabilityGate) + + +def test_build_yag_id_stored(): + assert _build().yag_id == "YAG-001" + + +def test_build_pipeline_version_stored(): + assert _build().pipeline_version == "v1.0" + + +def test_build_dry_lab_only_true(): + assert _build().dry_lab_only is True + + +def test_build_all_present_gives_accountability_verified(): + assert _build().yag_verdict == "accountability_verified" + + +def test_build_all_present_n_components_4(): + assert _build().n_components_present == 4 + + +def test_build_three_present_gives_accountability_partial(): + r = _build(pmc_artifact_id="") + assert r.yag_verdict == "accountability_partial" + + +def test_build_two_present_gives_accountability_partial(): + r = _build(sda_artifact_id="", pmc_artifact_id="") + assert r.yag_verdict == "accountability_partial" + + +def test_build_one_present_gives_accountability_not_established(): + r = _build(fia_artifact_id="", sda_artifact_id="", pmc_artifact_id="") + assert r.yag_verdict == "accountability_not_established" + + +def test_build_none_present_gives_accountability_not_established(): + r = _build( + cbr_artifact_id="", + fia_artifact_id="", + sda_artifact_id="", + pmc_artifact_id="", + ) + assert r.yag_verdict == "accountability_not_established" + + +def test_build_none_present_n_components_0(): + r = _build( + cbr_artifact_id="", + fia_artifact_id="", + sda_artifact_id="", + pmc_artifact_id="", + ) + assert r.n_components_present == 0 + + +def test_build_component_checks_are_y_component_check(): + for c in _build().component_checks: + assert isinstance(c, YComponentCheck) + + +def test_build_component_checks_count(): + assert len(_build().component_checks) == 4 + + +def test_build_cbr_present_true(): + r = _build() + cbr_check = next(c for c in r.component_checks if c.component_type == "CBR") + assert cbr_check.present is True + + +def test_build_cbr_absent_false(): + r = _build(cbr_artifact_id="") + cbr_check = next(c for c in r.component_checks if c.component_type == "CBR") + assert cbr_check.present is False + + +def test_build_pmc_artifact_id_stored(): + r = _build() + pmc_check = next(c for c in r.component_checks if c.component_type == "PMC") + assert pmc_check.artifact_id == "PMC-001" + + +def test_build_limitations_stored(): + assert _build().limitations == ["dry-lab only"] + + +def test_build_created_at_stored(): + assert _build().created_at == "2026-07-10" + + +# --------------------------------------------------------------------------- +# 3. validate – rejection cases +# --------------------------------------------------------------------------- + + +def test_validate_rejects_bad_yag_id_prefix(): + with pytest.raises(ValueError, match="YAG-"): + _build(yag_id="BAD-001") + + +def test_validate_rejects_empty_pipeline_version(): + with pytest.raises(ValueError): + _build(pipeline_version="") + + +def test_validate_rejects_wrong_cbr_prefix(): + with pytest.raises(ValueError, match="CBR-"): + _build(cbr_artifact_id="BAD-001") + + +def test_validate_rejects_wrong_fia_prefix(): + with pytest.raises(ValueError, match="FIA-"): + _build(fia_artifact_id="BAD-001") + + +def test_validate_rejects_wrong_sda_prefix(): + with pytest.raises(ValueError, match="SDA-"): + _build(sda_artifact_id="BAD-001") + + +def test_validate_rejects_wrong_pmc_prefix(): + with pytest.raises(ValueError, match="PMC-"): + _build(pmc_artifact_id="BAD-001") + + +def test_validate_rejects_n_components_mismatch(): + yag = _build() + yag.n_components_present = 99 + with pytest.raises(ValueError, match="n_components_present"): + validate_phase_y_accountability_gate(yag) + + +def test_validate_rejects_invalid_yag_verdict(): + yag = _build() + yag.yag_verdict = "UNKNOWN" + with pytest.raises(ValueError, match="yag_verdict"): + validate_phase_y_accountability_gate(yag) + + +def test_validate_rejects_dry_lab_only_false(): + yag = _build() + yag.dry_lab_only = False + with pytest.raises(ValueError, match="dry_lab_only"): + validate_phase_y_accountability_gate(yag) + + +def test_validate_rejects_empty_limitations(): + with pytest.raises(ValueError, match="limitations"): + _build(limitations=[]) + + +def test_validate_rejects_empty_created_at(): + with pytest.raises(ValueError): + _build(created_at="") + + +def test_validate_rejects_wrong_component_count(): + yag = _build() + yag.component_checks = yag.component_checks[:2] + with pytest.raises(ValueError, match="component_checks"): + validate_phase_y_accountability_gate(yag) + + +# --------------------------------------------------------------------------- +# 4. format +# --------------------------------------------------------------------------- + + +def test_format_contains_yag_id(): + assert "YAG-001" in format_phase_y_accountability_gate(_build()) + + +def test_format_contains_pipeline_version(): + assert "v1.0" in format_phase_y_accountability_gate(_build()) + + +def test_format_contains_verdict(): + assert "accountability_verified" in format_phase_y_accountability_gate(_build()) + + +def test_format_contains_cbr(): + assert "CBR" in format_phase_y_accountability_gate(_build()) + + +def test_format_contains_fia(): + assert "FIA" in format_phase_y_accountability_gate(_build()) + + +def test_format_contains_sda(): + assert "SDA" in format_phase_y_accountability_gate(_build()) + + +def test_format_contains_pmc(): + assert "PMC" in format_phase_y_accountability_gate(_build()) + + +def test_format_contains_present_status(): + assert "PRESENT" in format_phase_y_accountability_gate(_build()) + + +def test_format_contains_absent_when_missing(): + assert "ABSENT" in format_phase_y_accountability_gate(_build(pmc_artifact_id="")) + + +def test_format_contains_limitations(): + assert "dry-lab only" in format_phase_y_accountability_gate(_build()) + + +def test_format_contains_dry_lab_only(): + assert "dry_lab_only: True" in format_phase_y_accountability_gate(_build()) + + +def test_format_is_string(): + assert isinstance(format_phase_y_accountability_gate(_build()), str) diff --git a/tests/test_test_count_regression.py b/tests/test_test_count_regression.py index 811291bc..b9afd63c 100644 --- a/tests/test_test_count_regression.py +++ b/tests/test_test_count_regression.py @@ -4,7 +4,7 @@ import sys import math -BASELINE = 10710 +BASELINE = 10764 def test_test_count_regression():