CI Pipeline / best-practices (push) Successful in 8s
Auto-version and Release / release (push) Failing after 14s
CI Pipeline / documentation (push) Successful in 5s
CI Pipeline / lint (push) Failing after 34s
CI Pipeline / canary (push) Successful in 37s
CI Pipeline / type-check (push) Successful in 31s
CI Pipeline / gitea-offline (push) Successful in 16s
CI Pipeline / unit-tests (push) Successful in 1m4s
CI Pipeline / required-checks (push) Successful in 1s
deploy / deploy (push) Successful in 48s
CORRECTION 1: #314 reopened — recall_with_associations mode field defect CONFIRMED in current source and FIXED (mode: association_merged). CORRECTION 3: Gitea credential removed from remote URL (credential helper). TOKEN EXPOSED: yes (in diagnostic output). TOKEN ROTATED: not yet (requires Gitea web UI access). Remote URL now shows username only, no secret. CORRECTION 4: duplicate-release-commit defect ROOT CAUSE: convergence computed warranted bump from commits-since-tag without checking if master version already satisfies the bump. FIX: already-converged guard in version_convergence.py (master version > last tag → no commit). Repo-aware (args.repo passed to read_version/latest_tag). CORRECTION 6: lifecycle terminalization — execution exception now guarantees task terminalizes as failed with evidence (no immortal RUNNING). CORRECTION 7: route instruction task-scoped on RuneTask.route_instruction (NOT shared operator state). Read-only leading-verb override. Isolation tests prove no cross-contamination between interleaved tasks. PL1-01/02/03/05: pl1_records.py (route resolution, proof level, failure class, candidate status), metrics with honest NOT_YET_MEASURABLE semantics, route-instruction learning loop (PE-1789102658-0269 SEED). 1253 tests green; ratchet 352/80 (shrink); henxels clean.
170 lines
6.7 KiB
Python
170 lines
6.7 KiB
Python
"""Tests: RP7 SYNTHESIS executor + gate (#270).
|
|
|
|
Gate semantics: concrete build instructions only; adversarial constraints
|
|
(mitigations, kill-tests) carried into the design; rejected candidates
|
|
cannot be resurrected; readiness invariant enforced before the MiMo call.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import importlib.util
|
|
import json
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
|
|
for name, rel in [
|
|
("research_task_tree", "scripts/research_task_tree.py"),
|
|
("research_stage_executor", "scripts/research_stage_executor.py"),
|
|
]:
|
|
if name not in sys.modules:
|
|
spec = importlib.util.spec_from_file_location(name, ROOT / rel)
|
|
mod = importlib.util.module_from_spec(spec)
|
|
sys.modules[name] = mod
|
|
spec.loader.exec_module(mod)
|
|
|
|
rtt = sys.modules["research_task_tree"]
|
|
rse = sys.modules["research_stage_executor"]
|
|
|
|
|
|
def _make_tree(tmp_path, issue=985):
|
|
t = rtt.ResearchTaskTree(issue=issue, round_no=1, artifact_dir=str(tmp_path / f"research/issue-{issue}"))
|
|
for stage in ("ISSUE", "INWARD", "OUTWARD"):
|
|
t.advance({"test": True})
|
|
t.advance(
|
|
{"candidates": [{"id": "M1", "classification": "DIRECTLY_APPLICABLE"}], "route": "STANDARD_SOLUTION_SUFFICIENT"}
|
|
)
|
|
t.advance(
|
|
{
|
|
"disposition": "SURVIVES_WITH_MITIGATIONS",
|
|
"surviving_candidates": ["M1"],
|
|
"rejected_candidates": [{"id": "BAD1", "reason": "duplicate authority"}],
|
|
"required_mitigations": ["mode labeling", "cache controls"],
|
|
"falsification_tests": ["FT1: mode switch must be labeled"],
|
|
"attacks": [{"id": "A1", "target_candidate": "M1", "dimension": "failure modes", "finding": "x"}],
|
|
}
|
|
)
|
|
assert t.current_stage == "SYNTHESIS"
|
|
return t
|
|
|
|
|
|
def _executor(tree):
|
|
ex = rse.StageExecutor(issue=tree.issue, artifact_dir=tree.artifact_dir, round_no=tree.round)
|
|
ex.tree.state["artifacts"]["ADVERSARIAL"] = tree.state["artifacts"].get("ADVERSARIAL", {})
|
|
return ex
|
|
|
|
|
|
GOOD_SYNTHESIS = {
|
|
"selected_design": "Measurement harness with mode labeling and cache controls",
|
|
"why_this_design_won": "survivors align",
|
|
"rejected_alternatives": [],
|
|
"components_affected": ["src/rune/orchestration/benchmark.py"],
|
|
"authority_boundaries": ["harness reads memory_selector, never writes"],
|
|
"interfaces": ["def run_suite(corpus: list[dict]) -> dict"],
|
|
"data_flow": ["corpus -> harness -> recall -> metrics"],
|
|
"implementation_sequence": ["1. corpus fixture", "2. metric suite", "3. CI job"],
|
|
"invariants": [
|
|
"FT1: mode switch must be labeled",
|
|
"mitigation: mode labeling — every result row records retrieval mode",
|
|
"mitigation: cache controls — result/embedding caches bypassed per measurement",
|
|
],
|
|
"tests_required": ["test_mode_labeling", "test_cache_bypass"],
|
|
"acceptance_criteria": ["Recall@k reported", "p50/p95 latency reported"],
|
|
"failure_modes": ["embedding backend down"],
|
|
"rollback": "harness is additive; delete file to revert",
|
|
"deliberately_excluded": ["qdrant production migration"],
|
|
}
|
|
|
|
|
|
def test_good_synthesis_passes_gate(tmp_path):
|
|
tree = _make_tree(tmp_path)
|
|
ex = _executor(tree)
|
|
artifact = ex._normalize_synthesis_artifact(
|
|
GOOD_SYNTHESIS, {"receipts": [], "event_counts": {}, "elapsed": 0.0}, {"attempt": 1}
|
|
)
|
|
assert ex._validate_synthesis(artifact) == []
|
|
# adversarial constraints carried as provenance
|
|
assert artifact["carried_mitigations"] == ["mode labeling", "cache controls"]
|
|
assert artifact["carried_falsification_tests"] == ["FT1: mode switch must be labeled"]
|
|
assert artifact["adversarial_disposition"] == "SURVIVES_WITH_MITIGATIONS"
|
|
|
|
|
|
def test_missing_rollback_fails_gate(tmp_path):
|
|
tree = _make_tree(tmp_path)
|
|
ex = _executor(tree)
|
|
bad = {**GOOD_SYNTHESIS, "rollback": ""}
|
|
artifact = ex._normalize_synthesis_artifact(
|
|
bad, {"receipts": [], "event_counts": {}, "elapsed": 0.0}, {"attempt": 1}
|
|
)
|
|
assert any("rollback" in e for e in ex._validate_synthesis(artifact))
|
|
|
|
|
|
def test_unconverted_falsification_test_fails_gate(tmp_path):
|
|
tree = _make_tree(tmp_path)
|
|
ex = _executor(tree)
|
|
bad = {**GOOD_SYNTHESIS, "invariants": ["cache bypassed per measurement"]}
|
|
artifact = ex._normalize_synthesis_artifact(
|
|
bad, {"receipts": [], "event_counts": {}, "elapsed": 0.0}, {"attempt": 1}
|
|
)
|
|
errors = ex._validate_synthesis(artifact)
|
|
assert any("FT1" in e and "not converted" in e for e in errors)
|
|
|
|
|
|
def test_dropped_mitigation_fails_gate(tmp_path):
|
|
tree = _make_tree(tmp_path)
|
|
ex = _executor(tree)
|
|
# Remove ALL structural coverage of the cache-controls mitigation
|
|
bad = {
|
|
**GOOD_SYNTHESIS,
|
|
"invariants": ["FT1: mode switch must be labeled"],
|
|
"interfaces": ["def run_suite(corpus: list[dict]) -> dict"],
|
|
"data_flow": ["corpus -> harness -> recall -> metrics"],
|
|
"tests_required": ["test_mode_labeling"],
|
|
"acceptance_criteria": ["Recall@k reported", "p50/p95 latency reported"],
|
|
}
|
|
artifact = ex._normalize_synthesis_artifact(
|
|
bad, {"receipts": [], "event_counts": {}, "elapsed": 0.0}, {"attempt": 1}
|
|
)
|
|
errors = ex._validate_synthesis(artifact)
|
|
assert any("cache controls" in e and "insufficiently represented" in e for e in errors)
|
|
|
|
|
|
def test_rejected_candidate_resurrection_fails_gate(tmp_path):
|
|
tree = _make_tree(tmp_path)
|
|
ex = _executor(tree)
|
|
bad = {**GOOD_SYNTHESIS, "components_affected": ["src/rune/orchestration/benchmark.py", "BAD1_adapter.py"]}
|
|
artifact = ex._normalize_synthesis_artifact(
|
|
bad, {"receipts": [], "event_counts": {}, "elapsed": 0.0}, {"attempt": 1}
|
|
)
|
|
errors = ex._validate_synthesis(artifact)
|
|
assert any("BAD1" in e and "resurrection" in e for e in errors)
|
|
|
|
|
|
def test_not_concrete_fails_gate(tmp_path):
|
|
tree = _make_tree(tmp_path)
|
|
ex = _executor(tree)
|
|
bad = {**GOOD_SYNTHESIS, "interfaces": []}
|
|
artifact = ex._normalize_synthesis_artifact(
|
|
bad, {"receipts": [], "event_counts": {}, "elapsed": 0.0}, {"attempt": 1}
|
|
)
|
|
assert any("interfaces" in e for e in ex._validate_synthesis(artifact))
|
|
|
|
|
|
def test_readiness_invariant_blocks_synthesis_execution(tmp_path):
|
|
"""A stage execution with a broken ADVERSARIAL artifact fails BEFORE any MiMo call."""
|
|
tree = _make_tree(tmp_path)
|
|
ex = _executor(tree)
|
|
ex.tree.state["artifacts"]["ADVERSARIAL"] = {"disposition": "EVIDENCE_DEFICIENT", "surviving_candidates": []}
|
|
result = ex._execute_synthesis({"attempt": 1})
|
|
assert result["status"] == "STAGE_FAILED"
|
|
assert "readiness invariant" in result["error"]
|
|
|
|
|
|
def test_advance_after_synthesis_goes_to_qualify(tmp_path):
|
|
tree = _make_tree(tmp_path)
|
|
tree.advance(GOOD_SYNTHESIS)
|
|
assert tree.current_stage == "QUALIFY"
|