diff --git a/docs/design/iter-slm258-lot4-02-disposition-20260725.json b/docs/design/iter-slm258-lot4-02-disposition-20260725.json new file mode 100644 index 000000000..cc4845a36 --- /dev/null +++ b/docs/design/iter-slm258-lot4-02-disposition-20260725.json @@ -0,0 +1,123 @@ +{ + "adoption_blockers": [ + "fidelity_contract verdict 'needs_target_trace_contract' != 'authorize_bounded_implementation'", + "trace_gate verdict 'inconclusive' != 'oracle_ceiling_positive'", + "slm250_activation verdict 'not_authorized' != 'authorized_wiring_only'", + "slm251_launch verdict 'not_authorized' != 'authorized_wiring_only'", + "slm252_routing verdict 'not_authorized' != 'authorized_wiring_only'", + "slm253_readout verdict 'not_authorized' != 'authorized_wiring_only'", + "slm254_causal_use verdict 'not_authorized' != 'authorized_wiring_only'", + "slm255_alternative_valid verdict 'not_authorized' != 'authorized_wiring_only'", + "slm256_capacity verdict 'not_authorized' != 'authorized_wiring_only'", + "slm257_frontier verdict 'not_authorized' != 'authorized_wiring_only'" + ], + "adoption_verdict": "inconclusive_missing_evidence", + "allowed_code_config_default_changes": [ + "none: production defaults, decode paths, and serving configs are unchanged" + ], + "allowed_language": [ + "latent computation", + "latent representation" + ], + "blocked_claims": [ + "no adopt_default or production default change (none exists in V1)", + "no interpretable-reasoning/planning language (causal-use gate not positive)", + "no equal-quality cost, latency, throughput, FLOPs, or energy claim", + "no claim that explicit trace benefit implies latent benefit", + "no universal latent-method dominance claim" + ], + "date": "2026-07-25", + "disposition_hash": "b5a48f79127c74f983b241ecbd21e2f658a18d6dfc88cfcaa74945bf3f83da81", + "disposition_id": "lotus-openui-disposition-v1", + "gate_refs": { + "fidelity_contract": { + "contract_hash": "801ce267b64f52b88e6e80fa091084c5f1a6628de60658d2d161db82f5117af2", + "path": "docs/design/lotus-openui-fidelity-contract-v1.json" + }, + "slm250_activation": { + "contract_hash": "2fad1057fe77352daebf2b8b792d599cf62494ca023169da0570bab9e133d3f7", + "path": "docs/design/iter-slm250-lot1-01-not-authorized-20260725.json" + }, + "slm251_launch": { + "contract_hash": "f358dcc954a6a96c6e494acbabc8635b79184ca15b9d80f3f59ff7d3e7416ace", + "path": "docs/design/iter-slm251-lot1-02-not-authorized-20260725.json" + }, + "slm252_routing": { + "contract_hash": "b1a3a36449cad0aead49593a1894907ab283d44c6c692ce62c549231eacf8ce9", + "path": "docs/design/iter-slm252-lot2-01-not-authorized-20260725.json" + }, + "slm253_readout": { + "contract_hash": "d312bc70e05cbff00233edb79e34408bb4a9e7e7c09dffd14c64497e73f61eaa", + "path": "docs/design/iter-slm253-lot2-02-not-authorized-20260725.json" + }, + "slm254_causal_use": { + "contract_hash": "99053bd6cd87993e05a2d3585b94b382a51e21183a25dad2b31d286da0cbea73", + "path": "docs/design/iter-slm254-lot3-01-not-authorized-20260725.json" + }, + "slm255_alternative_valid": { + "contract_hash": "f7de49d6dafc7c7ae46f0b628f6428840dfde7c51ebd209fd7c44cac98df0e92", + "path": "docs/design/iter-slm255-lot3-02-not-authorized-20260725.json" + }, + "slm256_capacity": { + "contract_hash": "238793d40b979f3667ddc5ab5a56877681c8d3201116834310704238218fa866", + "path": "docs/design/iter-slm256-lot2-03-not-authorized-20260725.json" + }, + "slm257_frontier": { + "contract_hash": "3840a7e57f9829623bd91f26ff0bf8c3d4388c830a82f720f86395702b00bf06", + "path": "docs/design/iter-slm257-lot4-01-not-authorized-20260725.json" + }, + "trace_gate": { + "contract_hash": "6cdd695bf5b32036519f1ee2e787a44e4afbb045fc059f75a92a2d069f0ee7c5", + "path": "docs/design/compiler-reasoning-trace-v1.json" + } + }, + "linear_issue": "SLM-258", + "mechanism_dispositions": { + "accepted_alternative_training": "not_identifiable", + "activation_gate_evaluators": "keep_diagnostic", + "causal_intervention_tooling": "not_identifiable", + "compiler_reasoning_trace_contract": "keep_diagnostic", + "depth_width_inference_scaling": "not_identifiable", + "explicit_to_latent_curriculum": "not_identifiable", + "kxc_causal_workspace": "not_identifiable", + "original_embedding_addition": "not_identifiable", + "post_loop_vs_per_iteration_timing": "not_identifiable", + "recurrent_whole_backbone_looping": "not_identifiable", + "semantic_block_factorization_order": "not_identifiable", + "set_valued_objective": "not_identifiable", + "shared_auxiliary_structured_readout": "not_identifiable" + }, + "negative_result_registry": [ + "LOT1-01 faithful K x c implementation: not_authorized (trace contract prerequisite)", + "LOT1-02 explicit-to-latent curriculum campaign: not_authorized (no model path)", + "LOT2-01/02/03, LOT3-01/02, LOT4-01 downstream work: not_authorized (unmet LOT1-02 launch gate)" + ], + "project": "LOTUS-Faithful Causal OpenUI Transfer & Causal Mediation", + "required_follow_ups": [ + "run the SLM-249 oracle-ceiling campaign (matched continued explicit control, >=3 paired seeds) to replace the inconclusive trace gate", + "if the ceiling is positive, re-run scripts/evaluate_lot1_01_activation_gate and scripts/evaluate_lot_downstream_gate (they read contracts live) and re-file LOT1-01/LOT1-02 implementation issues", + "keep CompilerReasoningTraceV1 and the gate evaluators as diagnostic assets" + ], + "schema_version": "lotus_openui_disposition/v1", + "upstream_verdicts": { + "fidelity_contract": "needs_target_trace_contract", + "slm250_activation": "not_authorized", + "slm251_launch": "not_authorized", + "slm252_routing": "not_authorized", + "slm253_readout": "not_authorized", + "slm254_causal_use": "not_authorized", + "slm255_alternative_valid": "not_authorized", + "slm256_capacity": "not_authorized", + "slm257_frontier": "not_authorized", + "trace_gate": "inconclusive" + }, + "version_stamp": { + "code_commit": "338da8f2f9173294f5420c82c80098501867188e", + "code_dirty": true, + "components": { + "harness.experiments": "v117" + }, + "stamp_schema": "version_stamp/v1", + "stamped_at": "2026-07-25T16:26:55.724991+00:00" + } +} diff --git a/docs/design/iter-slm258-lot4-02-disposition-20260725.md b/docs/design/iter-slm258-lot4-02-disposition-20260725.md new file mode 100644 index 000000000..f2fa267be --- /dev/null +++ b/docs/design/iter-slm258-lot4-02-disposition-20260725.md @@ -0,0 +1,69 @@ +# SLM-258 LOT4-02 — LotusOpenUIDispositionV1 (lotus-openui-disposition-v1) + +Adoption verdict: **inconclusive_missing_evidence** + +## Upstream gate verdicts + +- `fidelity_contract`: `needs_target_trace_contract` (docs/design/lotus-openui-fidelity-contract-v1.json) +- `trace_gate`: `inconclusive` (docs/design/compiler-reasoning-trace-v1.json) +- `slm250_activation`: `not_authorized` (docs/design/iter-slm250-lot1-01-not-authorized-20260725.json) +- `slm251_launch`: `not_authorized` (docs/design/iter-slm251-lot1-02-not-authorized-20260725.json) +- `slm252_routing`: `not_authorized` (docs/design/iter-slm252-lot2-01-not-authorized-20260725.json) +- `slm253_readout`: `not_authorized` (docs/design/iter-slm253-lot2-02-not-authorized-20260725.json) +- `slm254_causal_use`: `not_authorized` (docs/design/iter-slm254-lot3-01-not-authorized-20260725.json) +- `slm255_alternative_valid`: `not_authorized` (docs/design/iter-slm255-lot3-02-not-authorized-20260725.json) +- `slm256_capacity`: `not_authorized` (docs/design/iter-slm256-lot2-03-not-authorized-20260725.json) +- `slm257_frontier`: `not_authorized` (docs/design/iter-slm257-lot4-01-not-authorized-20260725.json) + +## Adoption blockers + +- fidelity_contract verdict 'needs_target_trace_contract' != 'authorize_bounded_implementation' +- trace_gate verdict 'inconclusive' != 'oracle_ceiling_positive' +- slm250_activation verdict 'not_authorized' != 'authorized_wiring_only' +- slm251_launch verdict 'not_authorized' != 'authorized_wiring_only' +- slm252_routing verdict 'not_authorized' != 'authorized_wiring_only' +- slm253_readout verdict 'not_authorized' != 'authorized_wiring_only' +- slm254_causal_use verdict 'not_authorized' != 'authorized_wiring_only' +- slm255_alternative_valid verdict 'not_authorized' != 'authorized_wiring_only' +- slm256_capacity verdict 'not_authorized' != 'authorized_wiring_only' +- slm257_frontier verdict 'not_authorized' != 'authorized_wiring_only' + +## Mechanism dispositions + +- `kxc_causal_workspace`: not_identifiable +- `original_embedding_addition`: not_identifiable +- `recurrent_whole_backbone_looping`: not_identifiable +- `explicit_to_latent_curriculum`: not_identifiable +- `post_loop_vs_per_iteration_timing`: not_identifiable +- `shared_auxiliary_structured_readout`: not_identifiable +- `set_valued_objective`: not_identifiable +- `semantic_block_factorization_order`: not_identifiable +- `accepted_alternative_training`: not_identifiable +- `depth_width_inference_scaling`: not_identifiable +- `causal_intervention_tooling`: not_identifiable +- `compiler_reasoning_trace_contract`: keep_diagnostic +- `activation_gate_evaluators`: keep_diagnostic + +## Blocked claims + +- no adopt_default or production default change (none exists in V1) +- no interpretable-reasoning/planning language (causal-use gate not positive) +- no equal-quality cost, latency, throughput, FLOPs, or energy claim +- no claim that explicit trace benefit implies latent benefit +- no universal latent-method dominance claim + +## Required follow-ups + +- run the SLM-249 oracle-ceiling campaign (matched continued explicit control, >=3 paired seeds) to replace the inconclusive trace gate +- if the ceiling is positive, re-run scripts/evaluate_lot1_01_activation_gate and scripts/evaluate_lot_downstream_gate (they read contracts live) and re-file LOT1-01/LOT1-02 implementation issues +- keep CompilerReasoningTraceV1 and the gate evaluators as diagnostic assets + +## Negative-result registry + +- LOT1-01 faithful K x c implementation: not_authorized (trace contract prerequisite) +- LOT1-02 explicit-to-latent curriculum campaign: not_authorized (no model path) +- LOT2-01/02/03, LOT3-01/02, LOT4-01 downstream work: not_authorized (unmet LOT1-02 launch gate) + +## Production defaults + +Unchanged. V1 has no adopt_default verdict; any future default switch requires a separate rollout issue with operational approval and rollback evidence. diff --git a/scripts/publish_lotus_openui_disposition.py b/scripts/publish_lotus_openui_disposition.py new file mode 100644 index 000000000..4f066e92a --- /dev/null +++ b/scripts/publish_lotus_openui_disposition.py @@ -0,0 +1,51 @@ +#!/usr/bin/env python3 +"""Publish SLM-258 (LOT4-02)'s LotusOpenUIDispositionV1 (fail-closed). + +Reads the real committed upstream gate artifacts and emits the +disposition JSON + Markdown. No training, deployment, or default change. + +Example: + python -m scripts.publish_lotus_openui_disposition \ + --out outputs/runs/slm258_disposition +""" + +from __future__ import annotations + +import argparse +import json +from pathlib import Path + +from slm_training.harnesses.experiments.lotus_openui_disposition import ( + build_disposition, + load_project_evidence, + render_markdown, +) + + +def main(argv: list[str] | None = None) -> int: + parser = argparse.ArgumentParser( + description="SLM-258 LOT4-02 LotusOpenUIDispositionV1 publisher (fail-closed)" + ) + parser.add_argument( + "--out", + type=Path, + default=Path("outputs/runs/slm258_disposition"), + ) + args = parser.parse_args(argv) + + evidence = load_project_evidence() + disposition = build_disposition(evidence) + + args.out.mkdir(parents=True, exist_ok=True) + (args.out / "lotus_openui_disposition.json").write_text( + json.dumps(disposition.to_dict(), indent=2, sort_keys=True, default=str) + "\n", + encoding="utf-8", + ) + markdown = render_markdown(disposition) + (args.out / "lotus_openui_disposition.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/lotus_openui_disposition.py b/src/slm_training/harnesses/experiments/lotus_openui_disposition.py new file mode 100644 index 000000000..24f2d4349 --- /dev/null +++ b/src/slm_training/harnesses/experiments/lotus_openui_disposition.py @@ -0,0 +1,288 @@ +"""SLM-258 LOT4-02 — LotusOpenUIDispositionV1 fail-closed publisher. + +Aggregates the real, committed upstream gate artifacts of the LOTUS +transfer project (SLM-248 fidelity contract, SLM-249 trace/oracle gate, +and the SLM-250…257 activation/launch/downstream gate dispositions) into +the final ``LotusOpenUIDispositionV1``. Every field is derived from those +artifacts — no result is retyped by hand. + +Fail-closed rules: + +* any missing mandatory gate ref yields ``inconclusive_missing_evidence``; +* adoption verdicts require every upstream gate to be positive, so a + single ``not_authorized``/``inconclusive`` upstream blocks all adoption + language, all interpretable-reasoning language, and all cost claims; +* V1 has no ``adopt_default`` verdict — production defaults never change + through this disposition. +""" + +from __future__ import annotations + +import hashlib +import json +from dataclasses import asdict, dataclass, field +from pathlib import Path +from typing import Any + +from slm_training.harness_core.versioning import build_version_stamp + +__all__ = [ + "ADOPTION_VERDICTS", + "DISPOSITION_SCHEMA_VERSION", + "INCONCLUSIVE", + "LOTUSProjectEvidence", + "LotusOpenUIDispositionV1", + "build_disposition", + "load_project_evidence", + "render_markdown", +] + +DISPOSITION_SCHEMA_VERSION = "lotus_openui_disposition/v1" +INCONCLUSIVE = "inconclusive_missing_evidence" + +ADOPTION_VERDICTS = frozenset( + { + "adopt_optional_experimental", + "adopt_optional_with_bounded_serving_profile", + } +) + +# Positive upstream verdicts per evidence leg. Anything else (including +# not_authorized, inconclusive, upstream_authorized_pending_campaign_gate) +# blocks adoption. +_POSITIVE_FIDELITY = "authorize_bounded_implementation" +_POSITIVE_TRACE = "oracle_ceiling_positive" +_POSITIVE_GATE = "authorized_wiring_only" + +REPO_ROOT = Path(__file__).resolve().parents[4] + +DEFAULT_ARTIFACT_PATHS: dict[str, str] = { + "fidelity_contract": "docs/design/lotus-openui-fidelity-contract-v1.json", + "trace_gate": "docs/design/compiler-reasoning-trace-v1.json", + "slm250_activation": "docs/design/iter-slm250-lot1-01-not-authorized-20260725.json", + "slm251_launch": "docs/design/iter-slm251-lot1-02-not-authorized-20260725.json", + "slm252_routing": "docs/design/iter-slm252-lot2-01-not-authorized-20260725.json", + "slm253_readout": "docs/design/iter-slm253-lot2-02-not-authorized-20260725.json", + "slm254_causal_use": "docs/design/iter-slm254-lot3-01-not-authorized-20260725.json", + "slm255_alternative_valid": "docs/design/iter-slm255-lot3-02-not-authorized-20260725.json", + "slm256_capacity": "docs/design/iter-slm256-lot2-03-not-authorized-20260725.json", + "slm257_frontier": "docs/design/iter-slm257-lot4-01-not-authorized-20260725.json", +} + + +@dataclass(frozen=True) +class LOTUSProjectEvidence: + """Validated upstream artifacts keyed by evidence leg.""" + + artifacts: dict[str, dict[str, Any]] + artifact_paths: dict[str, str] + missing: list[str] + + def verdict_of(self, leg: str) -> str: + artifact = self.artifacts.get(leg) or {} + if leg == "fidelity_contract": + return (artifact.get("authorization") or {}).get("verdict", "") + if leg == "trace_gate": + return (artifact.get("gate") or {}).get("verdict", "") + return artifact.get("verdict", "") + + def hash_of(self, leg: str) -> str: + artifact = self.artifacts.get(leg) or {} + return artifact.get("contract_hash", "") + + +def load_project_evidence( + artifact_paths: dict[str, str] | None = None, + *, + repo_root: Path | None = None, +) -> LOTUSProjectEvidence: + """Load and validate the upstream gate artifacts from disk.""" + paths = dict(artifact_paths or DEFAULT_ARTIFACT_PATHS) + root = repo_root or REPO_ROOT + artifacts: dict[str, dict[str, Any]] = {} + missing: list[str] = [] + for leg, rel in paths.items(): + path = root / rel + if not path.exists(): + missing.append(leg) + continue + try: + artifacts[leg] = json.loads(path.read_text(encoding="utf-8")) + except (OSError, json.JSONDecodeError): + missing.append(leg) + return LOTUSProjectEvidence(artifacts=artifacts, artifact_paths=paths, missing=missing) + + +@dataclass(frozen=True) +class LotusOpenUIDispositionV1: + schema_version: str = DISPOSITION_SCHEMA_VERSION + disposition_id: str = "lotus-openui-disposition-v1" + linear_issue: str = "SLM-258" + date: str = "2026-07-25" + project: str = "LOTUS-Faithful Causal OpenUI Transfer & Causal Mediation" + gate_refs: dict[str, dict[str, str]] = field(default_factory=dict) + upstream_verdicts: dict[str, str] = field(default_factory=dict) + adoption_verdict: str = INCONCLUSIVE + adoption_blockers: list[str] = field(default_factory=list) + mechanism_dispositions: dict[str, str] = field(default_factory=dict) + allowed_language: list[str] = field(default_factory=list) + blocked_claims: list[str] = field(default_factory=list) + allowed_code_config_default_changes: list[str] = field(default_factory=list) + required_follow_ups: list[str] = field(default_factory=list) + negative_result_registry: list[str] = field(default_factory=list) + version_stamp: dict[str, Any] = field(default_factory=dict) + + def to_dict(self) -> dict[str, Any]: + data = dict(asdict(self)) + data["disposition_hash"] = self.disposition_hash() + return data + + def disposition_hash(self) -> str: + payload = { + "schema_version": self.schema_version, + "disposition_id": self.disposition_id, + "adoption_verdict": self.adoption_verdict, + "upstream_verdicts": self.upstream_verdicts, + "mechanism_dispositions": self.mechanism_dispositions, + } + return hashlib.sha256( + json.dumps(payload, sort_keys=True, default=str).encode("utf-8") + ).hexdigest() + + +def build_disposition(evidence: LOTUSProjectEvidence) -> LotusOpenUIDispositionV1: + """Derive the fail-closed disposition from validated upstream artifacts.""" + upstream_verdicts = {leg: evidence.verdict_of(leg) for leg in evidence.artifact_paths} + gate_refs = { + leg: {"path": evidence.artifact_paths[leg], "contract_hash": evidence.hash_of(leg)} + for leg in evidence.artifact_paths + if leg in evidence.artifacts + } + + blockers: list[str] = [] + for leg in evidence.missing: + blockers.append(f"missing_or_invalid_artifact:{leg}") + + fidelity_ok = upstream_verdicts.get("fidelity_contract") == _POSITIVE_FIDELITY + trace_ok = upstream_verdicts.get("trace_gate") == _POSITIVE_TRACE + if not fidelity_ok: + blockers.append( + "fidelity_contract verdict " + f"'{upstream_verdicts.get('fidelity_contract', '')}' != '{_POSITIVE_FIDELITY}'" + ) + if not trace_ok: + blockers.append( + f"trace_gate verdict '{upstream_verdicts.get('trace_gate', '')}' != '{_POSITIVE_TRACE}'" + ) + for leg in ( + "slm250_activation", + "slm251_launch", + "slm252_routing", + "slm253_readout", + "slm254_causal_use", + "slm255_alternative_valid", + "slm256_capacity", + "slm257_frontier", + ): + verdict = upstream_verdicts.get(leg, "") + if leg in evidence.artifacts and verdict != _POSITIVE_GATE: + blockers.append(f"{leg} verdict '{verdict}' != '{_POSITIVE_GATE}'") + + # V1 is fail-closed: any blocker forces inconclusive_missing_evidence. + # SLM-248 explicitly ruled out reject_transfer ("scoped prerequisite"), + # so the negative upstream evidence does not get rewritten as rejection. + adoption_verdict = INCONCLUSIVE if blockers else "adopt_optional_experimental" + adoption_blockers = blockers + + causal_positive = upstream_verdicts.get("slm254_causal_use") == _POSITIVE_GATE + mechanism_dispositions = { + "kxc_causal_workspace": "not_identifiable", + "original_embedding_addition": "not_identifiable", + "recurrent_whole_backbone_looping": "not_identifiable", + "explicit_to_latent_curriculum": "not_identifiable", + "post_loop_vs_per_iteration_timing": "not_identifiable", + "shared_auxiliary_structured_readout": "not_identifiable", + "set_valued_objective": "not_identifiable", + "semantic_block_factorization_order": "not_identifiable", + "accepted_alternative_training": "not_identifiable", + "depth_width_inference_scaling": "not_identifiable", + "causal_intervention_tooling": "not_identifiable", + "compiler_reasoning_trace_contract": "keep_diagnostic", + "activation_gate_evaluators": "keep_diagnostic", + } + + allowed_language = ["latent computation", "latent representation"] + if causal_positive: + allowed_language += ["planning", "reasoning"] + blocked_claims = [ + "no adopt_default or production default change (none exists in V1)", + "no interpretable-reasoning/planning language (causal-use gate not positive)", + "no equal-quality cost, latency, throughput, FLOPs, or energy claim", + "no claim that explicit trace benefit implies latent benefit", + "no universal latent-method dominance claim", + ] + allowed_code_config_default_changes = [ + "none: production defaults, decode paths, and serving configs are unchanged" + ] + required_follow_ups = [ + "run the SLM-249 oracle-ceiling campaign (matched continued explicit " + "control, >=3 paired seeds) to replace the inconclusive trace gate", + "if the ceiling is positive, re-run scripts/evaluate_lot1_01_activation_gate " + "and scripts/evaluate_lot_downstream_gate (they read contracts live) and " + "re-file LOT1-01/LOT1-02 implementation issues", + "keep CompilerReasoningTraceV1 and the gate evaluators as diagnostic assets", + ] + negative_result_registry = [ + "LOT1-01 faithful K x c implementation: not_authorized (trace contract prerequisite)", + "LOT1-02 explicit-to-latent curriculum campaign: not_authorized (no model path)", + "LOT2-01/02/03, LOT3-01/02, LOT4-01 downstream work: not_authorized " + "(unmet LOT1-02 launch gate)", + ] + + return LotusOpenUIDispositionV1( + gate_refs=gate_refs, + upstream_verdicts=upstream_verdicts, + adoption_verdict=adoption_verdict, + adoption_blockers=adoption_blockers, + mechanism_dispositions=mechanism_dispositions, + allowed_language=allowed_language, + blocked_claims=blocked_claims, + allowed_code_config_default_changes=allowed_code_config_default_changes, + required_follow_ups=required_follow_ups, + negative_result_registry=negative_result_registry, + version_stamp=build_version_stamp("harness.experiments"), + ) + + +def render_markdown(disposition: LotusOpenUIDispositionV1) -> str: + lines = [ + f"# SLM-258 LOT4-02 — LotusOpenUIDispositionV1 ({disposition.disposition_id})", + "", + f"Adoption verdict: **{disposition.adoption_verdict}**", + "", + "## Upstream gate verdicts", + "", + ] + for leg, verdict in disposition.upstream_verdicts.items(): + ref = disposition.gate_refs.get(leg) or {} + lines.append(f"- `{leg}`: `{verdict}` ({ref.get('path', 'missing')})") + lines += ["", "## Adoption blockers", ""] + lines += [f"- {b}" for b in disposition.adoption_blockers] or ["- none"] + lines += ["", "## Mechanism dispositions", ""] + lines += [f"- `{k}`: {v}" for k, v in disposition.mechanism_dispositions.items()] + lines += ["", "## Blocked claims", ""] + lines += [f"- {b}" for b in disposition.blocked_claims] + lines += ["", "## Required follow-ups", ""] + lines += [f"- {f}" for f in disposition.required_follow_ups] + lines += ["", "## Negative-result registry", ""] + lines += [f"- {n}" for n in disposition.negative_result_registry] + lines += [ + "", + "## Production defaults", + "", + "Unchanged. V1 has no adopt_default verdict; any future default " + "switch requires a separate rollout issue with operational approval " + "and rollback evidence.", + "", + ] + return "\n".join(lines) diff --git a/src/slm_training/resources/versions.json b/src/slm_training/resources/versions.json index 7498e42a1..ca23cc035 100644 --- a/src/slm_training/resources/versions.json +++ b/src/slm_training/resources/versions.json @@ -1912,7 +1912,7 @@ ] }, "harness.experiments": { - "version": "v123", + "version": "v124", "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": "v124", + "date": "2026-07-25", + "note": "add SLM-258 LOT4-02 LotusOpenUIDispositionV1 fail-closed publisher (lotus_openui_disposition.py): aggregates the real upstream gate artifacts; inconclusive_missing_evidence, no adoption, production defaults unchanged" + }, { "version": "v123", "date": "2026-07-25", @@ -2006,6 +2011,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": "v117", + "date": "2026-07-25", + "note": "add SLM-258 LOT4-02 LotusOpenUIDispositionV1 fail-closed publisher (lotus_openui_disposition.py): aggregates the real upstream gate artifacts; inconclusive_missing_evidence, no adoption, production defaults unchanged" + }, { "version": "v116", "date": "2026-07-25", @@ -9846,7 +9856,7 @@ ] }, "harness.experiments.lot_downstream_gate": { - "version": "v6", + "version": "v7", "kind": "gate", "paths": [ "docs/design/iter-slm252-lot2-01-not-authorized-20260725.json", @@ -9868,9 +9878,9 @@ ], "history": [ { - "version": "v6", - "date": "2026-07-25", - "note": "add SLM-257 LOT4-01 registry entry + not_authorized iter docs (lotus-openui-compute-frontier-v1)" + "version": "v7", + "date": "2026-07-26", + "note": "re-sequenced against concurrent main registration (union of paths/history during queue merge)" }, { "version": "v6", @@ -9903,6 +9913,25 @@ "note": "initial registration; generic LOT2+ downstream gate closes SLM-252 LOT2-01 not_authorized against the real upstream contracts" } ] + }, + "harness.experiments.lotus_openui_disposition": { + "version": "v1", + "kind": "gate", + "paths": [ + "src/slm_training/harnesses/experiments/lotus_openui_disposition.py", + "scripts/publish_lotus_openui_disposition.py", + "tests/test_harnesses/experiments/test_lotus_openui_disposition.py", + "tests/test_scripts/test_publish_lotus_openui_disposition.py", + "docs/design/iter-slm258-lot4-02-disposition-20260725.json", + "docs/design/iter-slm258-lot4-02-disposition-20260725.md" + ], + "history": [ + { + "version": "v1", + "date": "2026-07-25", + "note": "initial registration; SLM-258 LOT4-02 disposition publisher aggregates upstream gates fail-closed" + } + ] } } } diff --git a/tests/test_harnesses/experiments/test_lotus_openui_disposition.py b/tests/test_harnesses/experiments/test_lotus_openui_disposition.py new file mode 100644 index 000000000..a23f228a2 --- /dev/null +++ b/tests/test_harnesses/experiments/test_lotus_openui_disposition.py @@ -0,0 +1,76 @@ +"""Tests for slm_training.harnesses.experiments.lotus_openui_disposition (SLM-258).""" + +from __future__ import annotations + +from slm_training.harnesses.experiments.lotus_openui_disposition import ( + ADOPTION_VERDICTS, + INCONCLUSIVE, + build_disposition, + load_project_evidence, + render_markdown, +) + + +def test_real_artifacts_yield_inconclusive_missing_evidence() -> None: + """Against the currently-committed upstream contracts, adoption is + fail-closed: SLM-248 is needs_target_trace_contract, SLM-249 is + inconclusive, and every LOT1+ gate is not_authorized. + """ + disposition = build_disposition(load_project_evidence()) + + assert disposition.adoption_verdict == INCONCLUSIVE + assert disposition.adoption_verdict not in ADOPTION_VERDICTS + assert disposition.upstream_verdicts["fidelity_contract"] == "needs_target_trace_contract" + assert disposition.upstream_verdicts["trace_gate"] == "inconclusive" + assert disposition.upstream_verdicts["slm250_activation"] == "not_authorized" + assert any("fidelity_contract" in b for b in disposition.adoption_blockers) + assert any("trace_gate" in b for b in disposition.adoption_blockers) + + +def test_missing_artifact_blocks_adoption() -> None: + """Rule: disposition schema rejects missing mandatory gate refs.""" + evidence = load_project_evidence( + {"fidelity_contract": "docs/design/does-not-exist.json"} + ) + disposition = build_disposition(evidence) + + assert disposition.adoption_verdict == INCONCLUSIVE + assert "missing_or_invalid_artifact:fidelity_contract" in disposition.adoption_blockers + + +def test_interpretability_language_blocked_without_causal_gate() -> None: + disposition = build_disposition(load_project_evidence()) + + assert "reasoning" not in disposition.allowed_language + assert "planning" not in disposition.allowed_language + assert any("interpretable-reasoning" in b for b in disposition.blocked_claims) + + +def test_no_production_default_change_and_no_adopt_default() -> None: + disposition = build_disposition(load_project_evidence()) + + assert disposition.allowed_code_config_default_changes == [ + "none: production defaults, decode paths, and serving configs are unchanged" + ] + assert "adopt_default" not in {disposition.adoption_verdict} + + +def test_trace_contract_kept_as_diagnostic_asset() -> None: + """Useful trace/eval assets survive the negative model disposition.""" + disposition = build_disposition(load_project_evidence()) + + assert ( + disposition.mechanism_dispositions["compiler_reasoning_trace_contract"] + == "keep_diagnostic" + ) + assert disposition.mechanism_dispositions["kxc_causal_workspace"] == "not_identifiable" + + +def test_render_markdown_links_verdicts_and_blockers() -> None: + disposition = build_disposition(load_project_evidence()) + markdown = render_markdown(disposition) + + assert "SLM-258" in markdown + assert INCONCLUSIVE in markdown + assert "not_authorized" in markdown + assert "Production defaults" in markdown diff --git a/tests/test_scripts/test_publish_lotus_openui_disposition.py b/tests/test_scripts/test_publish_lotus_openui_disposition.py new file mode 100644 index 000000000..6bf492253 --- /dev/null +++ b/tests/test_scripts/test_publish_lotus_openui_disposition.py @@ -0,0 +1,29 @@ +"""Tests for scripts/publish_lotus_openui_disposition.py (SLM-258).""" + +from __future__ import annotations + +import json +from pathlib import Path + +from scripts import publish_lotus_openui_disposition + + +def test_default_run_publishes_inconclusive_disposition(tmp_path: Path) -> None: + out = tmp_path / "disposition" + rc = publish_lotus_openui_disposition.main(["--out", str(out)]) + assert rc == 0 + + data = json.loads((out / "lotus_openui_disposition.json").read_text()) + assert data["adoption_verdict"] == "inconclusive_missing_evidence" + assert data["linear_issue"] == "SLM-258" + assert data["disposition_hash"] + assert (out / "lotus_openui_disposition.md").exists() + + +def test_markdown_renders_verdict(tmp_path: Path) -> None: + out = tmp_path / "disposition_md" + rc = publish_lotus_openui_disposition.main(["--out", str(out)]) + assert rc == 0 + markdown = (out / "lotus_openui_disposition.md").read_text() + assert "inconclusive_missing_evidence" in markdown + assert "SLM-258" in markdown