diff --git a/docs/design/iter-slm252-lot2-01-not-authorized-20260725.json b/docs/design/iter-slm252-lot2-01-not-authorized-20260725.json new file mode 100644 index 000000000..e07ebc2a2 --- /dev/null +++ b/docs/design/iter-slm252-lot2-01-not-authorized-20260725.json @@ -0,0 +1,26 @@ +{ + "contract_hash": "b1a3a36449cad0aead49593a1894907ab283d44c6c692ce62c549231eacf8ce9", + "contract_id": "causal-supervision-routing-gate-v1", + "issue": { + "activation_requirement": "SLM-251 (LOT1-02) authorizes LOT2 and provides the selected parent, faithful treatment checkpoint/recipe, continued explicit control, curriculum, and fairness manifests.", + "alias": "LOT2-01", + "gate_contract_id": "causal-supervision-routing-gate-v1", + "linear_issue": "SLM-252", + "slug": "lot2-01-supervision-routing", + "title": "Run the causal supervision routing x timing x grounding factorial" + }, + "lot1_01_disposition": "not_authorized", + "lot1_02_disposition": "not_authorized", + "schema_version": "lot_downstream_gate/v1", + "verdict": "not_authorized", + "verdict_rationale": "LOT2-01 (SLM-252) activation requires: SLM-251 (LOT1-02) authorizes LOT2 and provides the selected parent, faithful treatment checkpoint/recipe, continued explicit control, curriculum, and fairness manifests. The LOT1-02 launch gate reports 'not_authorized' (LOT1-01 disposition 'not_authorized'): no faithful K x c model path, curriculum, Stage 0 parent, or continued-explicit control exists, and SLM-249's oracle ceiling is not positive. Closing not_authorized in plan-only/bounded-diagnostic mode; no training, factorial, intervention, or systems code is added by this disposition.", + "version_stamp": { + "code_commit": "e484978344f756a606cc08e0cb5d7961d7447ad8", + "code_dirty": true, + "components": { + "harness.experiments": "v111" + }, + "stamp_schema": "version_stamp/v1", + "stamped_at": "2026-07-25T16:05:28.682401+00:00" + } +} diff --git a/docs/design/iter-slm252-lot2-01-not-authorized-20260725.md b/docs/design/iter-slm252-lot2-01-not-authorized-20260725.md new file mode 100644 index 000000000..88ac57d6a --- /dev/null +++ b/docs/design/iter-slm252-lot2-01-not-authorized-20260725.md @@ -0,0 +1,15 @@ +# SLM-252 LOT2-01 — Downstream-gate disposition (causal-supervision-routing-gate-v1) + +Verdict: **not_authorized** + +LOT2-01 (SLM-252) activation requires: SLM-251 (LOT1-02) authorizes LOT2 and provides the selected parent, faithful treatment checkpoint/recipe, continued explicit control, curriculum, and fairness manifests. The LOT1-02 launch gate reports 'not_authorized' (LOT1-01 disposition 'not_authorized'): no faithful K x c model path, curriculum, Stage 0 parent, or continued-explicit control exists, and SLM-249's oracle ceiling is not positive. Closing not_authorized in plan-only/bounded-diagnostic mode; no training, factorial, intervention, or systems code is added by this disposition. + +## Upstream chain + +- LOT1-02 launch gate (SLM-251): `not_authorized` +- LOT1-01 activation gate (SLM-250): `not_authorized` +- Activation requirement: SLM-251 (LOT1-02) authorizes LOT2 and provides the selected parent, faithful treatment checkpoint/recipe, continued explicit control, curriculum, and fairness manifests. + +## Non-goals honored + +No training campaign, factorial, readout/decoder, intervention, systems measurement, or production default change. This disposition is itself the deliverable while the upstream activation gate is unmet. diff --git a/scripts/evaluate_lot_downstream_gate.py b/scripts/evaluate_lot_downstream_gate.py new file mode 100644 index 000000000..2d8970ed0 --- /dev/null +++ b/scripts/evaluate_lot_downstream_gate.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 +"""Evaluate a LOT2+ issue's downstream activation gate in plan-only mode. + +No training, factorial, intervention, or systems code is loaded or run. +Reads the real committed upstream contract artifacts and emits a per-issue +``LotDownstreamGateV1`` disposition. + +Example: + python -m scripts.evaluate_lot_downstream_gate --issue SLM-252 \ + --out outputs/runs/slm252_downstream_gate +""" + +from __future__ import annotations + +import argparse +import json +from pathlib import Path + +from slm_training.harnesses.experiments.lot_downstream_gate import ( + DOWNSTREAM_ISSUES, + evaluate_downstream_gate, + render_markdown, +) +from slm_training.harnesses.experiments.lot1_01_activation_gate import ( + load_upstream_contract, +) + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser( + description="LOT2+ downstream launch-gate evaluator (plan-only, no campaign code)" + ) + parser.add_argument( + "--issue", + required=True, + choices=sorted(DOWNSTREAM_ISSUES), + help="Linear issue id to evaluate (registry: DOWNSTREAM_ISSUES)", + ) + parser.add_argument( + "--fidelity-contract", + type=Path, + default=Path("docs/design/lotus-openui-fidelity-contract-v1.json"), + help="SLM-248 LotusOpenUIFidelityContractV1 JSON artifact", + ) + parser.add_argument( + "--trace-gate-contract", + type=Path, + default=Path("docs/design/compiler-reasoning-trace-v1.json"), + help="SLM-249 CompilerReasoningTraceGateV1 JSON artifact", + ) + parser.add_argument( + "--out", + type=Path, + default=None, + help="Output directory (default outputs/runs/_downstream_gate)", + ) + args = parser.parse_args(argv) + + spec = DOWNSTREAM_ISSUES[args.issue] + out = args.out or Path("outputs/runs") / f"{args.issue.lower()}_downstream_gate" + + contract = evaluate_downstream_gate( + spec, + load_upstream_contract(args.fidelity_contract), + load_upstream_contract(args.trace_gate_contract), + ) + + out.mkdir(parents=True, exist_ok=True) + stem = spec.slug.replace("-", "_") + (out / f"{stem}_gate.json").write_text( + json.dumps(contract.to_dict(), indent=2, sort_keys=True, default=str) + "\n", + encoding="utf-8", + ) + markdown = render_markdown(contract) + (out / f"{stem}_gate.md").write_text(markdown, encoding="utf-8") + print(markdown) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/src/slm_training/harnesses/experiments/lot_downstream_gate.py b/src/slm_training/harnesses/experiments/lot_downstream_gate.py new file mode 100644 index 000000000..3b96ccfd5 --- /dev/null +++ b/src/slm_training/harnesses/experiments/lot_downstream_gate.py @@ -0,0 +1,186 @@ +"""Generic LOT-project downstream launch-gate evaluator (plan-only closeout). + +Every LOT2/LOT3/LOT4 issue (SLM-252 … SLM-258) declares the same shape of +activation gate: the LOT1 chain must first authorize it — concretely, the +SLM-251 (LOT1-02) launch gate must be met, which itself requires the +SLM-248 transfer authorization and the SLM-249 oracle ceiling (evaluated +by :mod:`slm_training.harnesses.experiments.lot1_02_activation_gate`). +When the upstream chain is unmet, the downstream issue closes +``not_authorized`` in plan-only/bounded-diagnostic mode with no training, +factorial, intervention, or systems code. + +This module evaluates that shared upstream gate once and stamps a +per-issue ``LotDownstreamGateV1`` disposition. It contains no model or +campaign code — evaluating the gate honestly *is* the deliverable while +the prerequisites are unmet. +""" + +from __future__ import annotations + +import hashlib +import json +from dataclasses import asdict, dataclass, field +from typing import Any + +from slm_training.harness_core.versioning import build_version_stamp +from slm_training.harnesses.experiments.lot1_01_activation_gate import ( + AUTHORIZED_WIRING_ONLY, + NOT_AUTHORIZED, +) +from slm_training.harnesses.experiments.lot1_02_activation_gate import ( + evaluate_lot1_02_launch_gate, +) + +__all__ = [ + "DOWNSTREAM_ISSUES", + "GATE_SCHEMA_VERSION", + "PENDING_CAMPAIGN_GATE", + "DownstreamIssueSpec", + "LotDownstreamGateV1", + "evaluate_downstream_gate", + "render_markdown", +] + +GATE_SCHEMA_VERSION = "lot_downstream_gate/v1" + +#: Verdict when the upstream contract chain is met: the issue's own +#: activation still requires the LOT1-02 campaign's published gate +#: (``FaithfulLatentTransferGateV1``), which does not exist until the +#: campaign actually runs. This is never a quality or launch claim. +PENDING_CAMPAIGN_GATE = "upstream_authorized_pending_campaign_gate" + + +@dataclass(frozen=True) +class DownstreamIssueSpec: + """Identity of one LOT2+ issue whose activation rides on the LOT1 chain.""" + + linear_issue: str + alias: str + title: str + gate_contract_id: str + slug: str + activation_requirement: str + + def to_dict(self) -> dict[str, Any]: + return dict(asdict(self)) + + +DOWNSTREAM_ISSUES: dict[str, DownstreamIssueSpec] = { + "SLM-252": DownstreamIssueSpec( + linear_issue="SLM-252", + alias="LOT2-01", + title="Run the causal supervision routing x timing x grounding factorial", + gate_contract_id="causal-supervision-routing-gate-v1", + slug="lot2-01-supervision-routing", + activation_requirement=( + "SLM-251 (LOT1-02) authorizes LOT2 and provides the selected " + "parent, faithful treatment checkpoint/recipe, continued explicit " + "control, curriculum, and fairness manifests." + ), + ), +} + + +@dataclass(frozen=True) +class LotDownstreamGateV1: + """Per-issue downstream disposition derived from the LOT1 launch gate.""" + + schema_version: str = GATE_SCHEMA_VERSION + contract_id: str = "" + issue: DownstreamIssueSpec | None = None + lot1_02_disposition: str = NOT_AUTHORIZED + lot1_01_disposition: str = NOT_AUTHORIZED + verdict: str = NOT_AUTHORIZED + verdict_rationale: str = "" + version_stamp: dict[str, Any] = field(default_factory=dict) + + def to_dict(self) -> dict[str, Any]: + data = dict(asdict(self)) + data["issue"] = self.issue.to_dict() if self.issue is not None else None + data["contract_hash"] = self.contract_hash() + return data + + def contract_hash(self) -> str: + payload = { + "schema_version": self.schema_version, + "contract_id": self.contract_id, + "issue": self.issue.to_dict() if self.issue is not None else None, + "lot1_02_disposition": self.lot1_02_disposition, + "verdict": self.verdict, + } + return hashlib.sha256( + json.dumps(payload, sort_keys=True, default=str).encode("utf-8") + ).hexdigest() + + +def evaluate_downstream_gate( + spec: DownstreamIssueSpec, + fidelity_contract: dict[str, Any], + trace_gate_contract: dict[str, Any], +) -> LotDownstreamGateV1: + """Evaluate one LOT2+ issue's activation against real upstream contracts. + + The verdict is ``not_authorized`` unless the LOT1-02 launch gate is + met; even then it is only ``upstream_authorized_pending_campaign_gate`` + — the issue's own campaign gate must still be published by a real run + before any factorial/intervention/systems work is authorized. + """ + lot1_02 = evaluate_lot1_02_launch_gate(fidelity_contract, trace_gate_contract) + + if lot1_02.verdict == AUTHORIZED_WIRING_ONLY: + verdict = PENDING_CAMPAIGN_GATE + rationale = ( + "The LOT1 contract-level prerequisites are met, but " + f"{spec.alias} ({spec.linear_issue}) still requires the LOT1-02 " + "campaign's published FaithfulLatentTransferGateV1 with a " + "positive authorization before any objective/architecture work " + "is authorized. No campaign has run; this is not a launch claim." + ) + else: + verdict = NOT_AUTHORIZED + rationale = ( + f"{spec.alias} ({spec.linear_issue}) activation requires: " + f"{spec.activation_requirement} The LOT1-02 launch gate reports " + f"'{lot1_02.verdict}' (LOT1-01 disposition " + f"'{lot1_02.lot1_01_disposition}'): no faithful K x c model " + "path, curriculum, Stage 0 parent, or continued-explicit control " + "exists, and SLM-249's oracle ceiling is not positive. Closing " + "not_authorized in plan-only/bounded-diagnostic mode; no " + "training, factorial, intervention, or systems code is added by " + "this disposition." + ) + + return LotDownstreamGateV1( + contract_id=spec.gate_contract_id, + issue=spec, + lot1_02_disposition=lot1_02.verdict, + lot1_01_disposition=lot1_02.lot1_01_disposition, + verdict=verdict, + verdict_rationale=rationale, + version_stamp=build_version_stamp("harness.experiments"), + ) + + +def render_markdown(contract: LotDownstreamGateV1) -> str: + spec = contract.issue + lines = [ + f"# {spec.linear_issue} {spec.alias} — Downstream-gate disposition ({contract.contract_id})", + "", + f"Verdict: **{contract.verdict}**", + "", + contract.verdict_rationale, + "", + "## Upstream chain", + "", + f"- LOT1-02 launch gate (SLM-251): `{contract.lot1_02_disposition}`", + f"- LOT1-01 activation gate (SLM-250): `{contract.lot1_01_disposition}`", + f"- Activation requirement: {spec.activation_requirement}", + "", + "## Non-goals honored", + "", + "No training campaign, factorial, readout/decoder, intervention, " + "systems measurement, or production default change. This disposition " + "is itself the deliverable while the upstream activation gate is unmet.", + "", + ] + return "\n".join(lines) diff --git a/src/slm_training/resources/versions.json b/src/slm_training/resources/versions.json index 7e05f6716..ab75aa7b0 100644 --- a/src/slm_training/resources/versions.json +++ b/src/slm_training/resources/versions.json @@ -1912,7 +1912,7 @@ ] }, "harness.experiments": { - "version": "v117", + "version": "v118", "kind": "harness", "paths": [ "scripts/run_slm298_capacity_context_curriculum.py", @@ -1921,6 +1921,11 @@ "tests/test_scripts/test_run_slm298_capacity_context_curriculum.py" ], "history": [ + { + "version": "v118", + "date": "2026-07-25", + "note": "add generic LOT2+ downstream launch-gate evaluator (lot_downstream_gate.py) closing SLM-252 LOT2-01 not_authorized off the unmet LOT1-02 launch gate; no campaign code" + }, { "version": "v117", "date": "2026-07-25", @@ -1976,6 +1981,11 @@ "date": "2026-07-25", "note": "add SLM-300 AP-015 self-context exposure-bias curriculum manifest, policy-origin-mixture wiring/fixture harness, and mixture-zero legacy-equivalence invariant" }, + { + "version": "v111", + "date": "2026-07-25", + "note": "add generic LOT2+ downstream launch-gate evaluator (lot_downstream_gate.py) closing SLM-252 LOT2-01 not_authorized off the unmet LOT1-02 launch gate; no campaign code" + }, { "version": "v110", "date": "2026-07-25", @@ -9784,6 +9794,25 @@ "note": "initial registration; SLM-251 LOT1-02 launch-gate evaluator closes not_authorized against the real SLM-248/SLM-249 upstream contracts and the LOT1-01 not_authorized disposition" } ] + }, + "harness.experiments.lot_downstream_gate": { + "version": "v1", + "kind": "gate", + "paths": [ + "src/slm_training/harnesses/experiments/lot_downstream_gate.py", + "scripts/evaluate_lot_downstream_gate.py", + "tests/test_harnesses/experiments/test_lot_downstream_gate.py", + "tests/test_scripts/test_evaluate_lot_downstream_gate.py", + "docs/design/iter-slm252-lot2-01-not-authorized-20260725.json", + "docs/design/iter-slm252-lot2-01-not-authorized-20260725.md" + ], + "history": [ + { + "version": "v1", + "date": "2026-07-25", + "note": "initial registration; generic LOT2+ downstream gate closes SLM-252 LOT2-01 not_authorized against the real upstream contracts" + } + ] } } } diff --git a/tests/test_harnesses/experiments/test_lot_downstream_gate.py b/tests/test_harnesses/experiments/test_lot_downstream_gate.py new file mode 100644 index 000000000..8ccfca088 --- /dev/null +++ b/tests/test_harnesses/experiments/test_lot_downstream_gate.py @@ -0,0 +1,99 @@ +"""Tests for slm_training.harnesses.experiments.lot_downstream_gate (SLM-252+).""" + +from __future__ import annotations + +from pathlib import Path + +from slm_training.harnesses.experiments.lot1_01_activation_gate import ( + AUTHORIZED_WIRING_ONLY, + NOT_AUTHORIZED, + REQUIRED_FIDELITY_VERDICT, + REQUIRED_TRACE_VERDICT, + load_upstream_contract, +) +from slm_training.harnesses.experiments.lot_downstream_gate import ( + DOWNSTREAM_ISSUES, + PENDING_CAMPAIGN_GATE, + evaluate_downstream_gate, + render_markdown, +) + +REPO_ROOT = Path(__file__).resolve().parents[3] +FIDELITY_CONTRACT_PATH = REPO_ROOT / "docs/design/lotus-openui-fidelity-contract-v1.json" +TRACE_GATE_CONTRACT_PATH = REPO_ROOT / "docs/design/compiler-reasoning-trace-v1.json" + + +def _real_contracts() -> tuple[dict, dict]: + return ( + load_upstream_contract(FIDELITY_CONTRACT_PATH), + load_upstream_contract(TRACE_GATE_CONTRACT_PATH), + ) + + +def _positive_contracts() -> tuple[dict, dict]: + fidelity = { + "contract_id": "synthetic-fidelity", + "contract_hash": "deadbeef", + "authorization": { + "linear_issue": "SLM-248", + "verdict": REQUIRED_FIDELITY_VERDICT, + "verdict_rationale": "synthetic positive", + }, + } + trace = { + "contract_id": "synthetic-trace", + "contract_hash": "cafef00d", + "gate": { + "linear_issue": "SLM-249", + "verdict": REQUIRED_TRACE_VERDICT, + "verdict_rationale": "synthetic positive", + "allowed_lot1_implementation": [ + "kxc_bounded_implementation: K=6, c=479, stages per contract" + ], + }, + } + return fidelity, trace + + +def test_slm252_real_contracts_are_not_authorized() -> None: + """The currently-committed SLM-248/SLM-249 artifacts must yield + not_authorized for LOT2-01: LOT1-02's launch gate is unmet, so no + parent checkpoint, treatment recipe, control, or curriculum exists. + """ + fidelity, trace = _real_contracts() + contract = evaluate_downstream_gate(DOWNSTREAM_ISSUES["SLM-252"], fidelity, trace) + + assert contract.verdict == NOT_AUTHORIZED + assert contract.lot1_02_disposition == NOT_AUTHORIZED + assert contract.lot1_01_disposition == NOT_AUTHORIZED + assert contract.contract_id == "causal-supervision-routing-gate-v1" + + +def test_positive_upstream_is_pending_not_authorized() -> None: + """Even with a synthetic positive LOT1 chain, a LOT2+ issue is only + upstream_authorized_pending_campaign_gate — its own campaign gate must + still be published by a real run. The evaluator never jumps straight + to a launch/quality authorization. + """ + fidelity, trace = _positive_contracts() + contract = evaluate_downstream_gate(DOWNSTREAM_ISSUES["SLM-252"], fidelity, trace) + + assert contract.verdict == PENDING_CAMPAIGN_GATE + assert contract.lot1_02_disposition == AUTHORIZED_WIRING_ONLY + + +def test_registry_entries_are_well_formed() -> None: + for issue_id, spec in DOWNSTREAM_ISSUES.items(): + assert spec.linear_issue == issue_id + assert spec.gate_contract_id + assert spec.slug + assert spec.activation_requirement + + +def test_render_markdown_covers_chain() -> None: + fidelity, trace = _real_contracts() + contract = evaluate_downstream_gate(DOWNSTREAM_ISSUES["SLM-252"], fidelity, trace) + markdown = render_markdown(contract) + assert "SLM-252" in markdown + assert "not_authorized" in markdown + assert "LOT1-02" in markdown diff --git a/tests/test_scripts/test_evaluate_lot_downstream_gate.py b/tests/test_scripts/test_evaluate_lot_downstream_gate.py new file mode 100644 index 000000000..b7d26517e --- /dev/null +++ b/tests/test_scripts/test_evaluate_lot_downstream_gate.py @@ -0,0 +1,39 @@ +"""Tests for scripts/evaluate_lot_downstream_gate.py (SLM-252+).""" + +from __future__ import annotations + +import json +from pathlib import Path + +from scripts import evaluate_lot_downstream_gate + + +def test_slm252_default_run_reports_not_authorized(tmp_path: Path) -> None: + out = tmp_path / "gate" + rc = evaluate_lot_downstream_gate.main(["--issue", "SLM-252", "--out", str(out)]) + assert rc == 0 + + data = json.loads((out / "lot2_01_supervision_routing_gate.json").read_text()) + assert data["verdict"] == "not_authorized" + assert data["issue"]["linear_issue"] == "SLM-252" + assert (out / "lot2_01_supervision_routing_gate.md").exists() + + +def test_run_against_explicit_contract_paths(tmp_path: Path) -> None: + out = tmp_path / "gate_explicit" + rc = evaluate_lot_downstream_gate.main( + [ + "--issue", + "SLM-252", + "--fidelity-contract", + "docs/design/lotus-openui-fidelity-contract-v1.json", + "--trace-gate-contract", + "docs/design/compiler-reasoning-trace-v1.json", + "--out", + str(out), + ] + ) + assert rc == 0 + markdown = (out / "lot2_01_supervision_routing_gate.md").read_text() + assert "SLM-252" in markdown + assert "not_authorized" in markdown