Files
2026-07-13 13:12:33 +08:00

571 lines
19 KiB
Python

"""Unit tests for opensquilla.skills.proposals_lib."""
from __future__ import annotations
import json
from pathlib import Path
from opensquilla.skills import proposals_lib
SAMPLE_SKILL_MD = """---
name: synth-test-pipeline
description: "Sample synthetic pipeline for proposals_lib tests"
kind: meta
meta_priority: 50
triggers:
- "synth test trigger"
provenance:
origin: opensquilla-user
composition:
steps:
- id: a
skill: summarize
with:
task: "{{ inputs.user_message }}"
---
"""
GATES_PASSING = {
"G1": {"passed": True}, "G2": {"passed": True},
}
SMOKE_PASSING = {
"G3": {"passed": True}, "G4": {"passed": True},
}
def _seed_proposal(home: Path, *, eligible: bool = True) -> str:
result = proposals_lib.write_proposal(
home,
SAMPLE_SKILL_MD,
GATES_PASSING if eligible else {"G1": {"passed": False}},
SMOKE_PASSING,
)
assert result["status"] == "ok"
return result["proposal_id"]
def test_is_valid_proposal_id() -> None:
assert proposals_lib.is_valid_proposal_id("abcd1234") is True
assert proposals_lib.is_valid_proposal_id("ABCD1234") is False # uppercase rejected
assert proposals_lib.is_valid_proposal_id("abcd123") is False # too short
assert proposals_lib.is_valid_proposal_id("../etc/passwd") is False
assert proposals_lib.is_valid_proposal_id("") is False
assert proposals_lib.is_valid_proposal_id(None) is False
def test_write_then_list_then_pending_count(tmp_path: Path) -> None:
home = tmp_path / ".opensquilla"
pid1 = _seed_proposal(home)
pid2 = _seed_proposal(home)
rows = proposals_lib.list_proposals(home)["proposals"]
assert sorted(r["proposal_id"] for r in rows) == sorted([pid1, pid2])
assert all(r["auto_enable_eligible"] for r in rows)
assert proposals_lib.pending_count(home) == {"count": 2}
def test_pending_count_on_empty_home(tmp_path: Path) -> None:
home = tmp_path / "empty"
assert proposals_lib.pending_count(home) == {"count": 0}
def test_list_proposals_surfaces_provenance(tmp_path: Path) -> None:
home = tmp_path / ".opensquilla"
pid = _seed_proposal(home)
# Patch gates.json with provenance
gates_path = home / "proposals" / pid / "gates.json"
gates = json.loads(gates_path.read_text())
gates["provenance"] = {
"triggered_by": "auto_cron",
"chain_hash": "deadbeefcafebabe",
}
gates_path.write_text(json.dumps(gates))
rows = proposals_lib.list_proposals(home)["proposals"]
assert rows[0]["triggered_by"] == "auto_cron"
assert rows[0]["chain_hash"] == "deadbeefcafebabe"
def test_list_proposals_surfaces_auto_enable_decision(tmp_path: Path) -> None:
home = tmp_path / ".opensquilla"
pid = _seed_proposal(home)
gates_path = home / "proposals" / pid / "gates.json"
gates = json.loads(gates_path.read_text())
gates["auto_enable"] = {
"status": "skipped",
"reason": "risk_too_high",
"risk_level": "high",
"max_risk": "low",
}
gates_path.write_text(json.dumps(gates))
rows = proposals_lib.list_proposals(home)["proposals"]
assert rows[0]["auto_enable"] == {
"status": "skipped",
"reason": "risk_too_high",
"risk_level": "high",
"max_risk": "low",
"validation_profile": "unknown",
"skills": [],
"tools": [],
"reasons": [],
}
def test_show_returns_payload(tmp_path: Path) -> None:
home = tmp_path / ".opensquilla"
pid = _seed_proposal(home)
out = proposals_lib.show_proposal(home, pid)
assert out["status"] == "ok"
assert out["proposal_id"] == pid
assert "synth-test-pipeline" in out["skill_md"]
assert out["gates"]["auto_enable_eligible"] is True
def test_full_gated_requires_runtime_e2e_result(tmp_path: Path) -> None:
home = tmp_path / ".opensquilla"
result = proposals_lib.write_proposal(
home,
SAMPLE_SKILL_MD,
GATES_PASSING,
SMOKE_PASSING,
creator_mode="FULL_GATED",
acceptance_result={
"raw": (
"WINNER: orchestrated\n"
"REASONS:\n"
"- candidate has stricter gates\n"
"REGRESSIONS:\n"
"- none\n"
"REQUIRED_IMPROVEMENTS:\n"
"- none\n"
),
},
)
assert result["status"] == "ok"
assert result["auto_enable_eligible"] is False
shown = proposals_lib.show_proposal(home, result["proposal_id"])
assert shown["gates"]["runtime_e2e"]["required"] is True
assert shown["gates"]["runtime_e2e"]["passed"] is False
assert shown["gates"]["runtime_e2e"]["reason"] == "missing_runtime_e2e_result"
accepted = proposals_lib.accept_proposal(home, result["proposal_id"])
assert accepted["status"] == "refused"
assert "gates not all passed" in accepted["reason"]
def test_full_gated_runtime_e2e_blocks_baseline_winner(tmp_path: Path) -> None:
home = tmp_path / ".opensquilla"
result = proposals_lib.write_proposal(
home,
SAMPLE_SKILL_MD,
GATES_PASSING,
SMOKE_PASSING,
creator_mode="FULL_GATED",
acceptance_result={
"raw": (
"WINNER: orchestrated\n"
"REASONS:\n"
"- candidate has stricter gates\n"
"REGRESSIONS:\n"
"- none\n"
"REQUIRED_IMPROVEMENTS:\n"
"- none\n"
),
},
runtime_e2e_result={
"status": "ok",
"passed": False,
"winner": "baseline",
"cases": [
{
"prompt": "please use synth test trigger",
"winner": "baseline",
"regression": "meta answer missed the requested summary",
},
],
},
)
assert result["auto_enable_eligible"] is False
shown = proposals_lib.show_proposal(home, result["proposal_id"])
assert shown["gates"]["runtime_e2e"]["passed"] is False
assert shown["gates"]["runtime_e2e"]["winner"] == "baseline"
def test_full_gated_acceptance_blocks_single_model_winner_even_when_runtime_passes(
tmp_path: Path,
) -> None:
home = tmp_path / ".opensquilla"
result = proposals_lib.write_proposal(
home,
SAMPLE_SKILL_MD,
GATES_PASSING,
SMOKE_PASSING,
creator_mode="FULL_GATED",
acceptance_result={
"raw": (
"WINNER: single-model\n"
"REASONS:\n"
"- baseline SKILL.md reads cleaner\n"
"REGRESSIONS:\n"
"- none\n"
"REQUIRED_IMPROVEMENTS:\n"
"- none\n"
),
},
runtime_e2e_result={
"status": "ok",
"passed": True,
"winner": "meta",
"cases": [
{
"prompt": "please use synth test trigger",
"winner": "meta",
"regression": "",
},
],
},
collision_result="PASS: no trigger collision",
risk_result="RISK: low\nCAPABILITIES:\n- read-only",
)
assert result["auto_enable_eligible"] is False
shown = proposals_lib.show_proposal(home, result["proposal_id"])
assert shown["gates"]["acceptance_compare"]["passed"] is False
assert shown["gates"]["acceptance_compare"]["winner"] == "single-model"
assert shown["gates"]["runtime_e2e"]["passed"] is True
def test_full_gated_acceptance_blocks_low_weighted_quality_score(tmp_path: Path) -> None:
home = tmp_path / ".opensquilla"
result = proposals_lib.write_proposal(
home,
SAMPLE_SKILL_MD,
GATES_PASSING,
SMOKE_PASSING,
creator_mode="FULL_GATED",
acceptance_result={
"raw": (
"WINNER: orchestrated\n"
"QUALITY_SCORE: 0.71\n"
"REASONS:\n"
"- candidate works but lacks output contracts\n"
"REGRESSIONS:\n"
"- weaker final artifact quality\n"
"REQUIRED_IMPROVEMENTS:\n"
"- none\n"
),
},
runtime_e2e_result={
"status": "ok",
"passed": True,
"winner": "meta",
"cases": [{"winner": "meta", "regression": ""}],
},
)
assert result["auto_enable_eligible"] is False
shown = proposals_lib.show_proposal(home, result["proposal_id"])
assert shown["gates"]["acceptance_compare"]["passed"] is False
assert shown["gates"]["acceptance_compare"]["quality_score"] == 0.71
assert "quality score below 0.80" in shown["gates"]["acceptance_compare"]["diagnostics"]
def test_full_gated_blocks_collision_and_high_risk_results(tmp_path: Path) -> None:
home = tmp_path / ".opensquilla"
result = proposals_lib.write_proposal(
home,
SAMPLE_SKILL_MD,
GATES_PASSING,
SMOKE_PASSING,
creator_mode="FULL_GATED",
acceptance_result={
"raw": (
"WINNER: orchestrated\n"
"QUALITY_SCORE: 0.93\n"
"REQUIRED_IMPROVEMENTS:\n"
"- none\n"
),
},
runtime_e2e_result={
"status": "ok",
"passed": True,
"winner": "meta",
"cases": [{"winner": "meta", "regression": ""}],
},
collision_result="REVISE_NEEDED: trigger overlaps with summarize",
risk_result="RISK: high\nCAPABILITIES:\n- shell",
)
assert result["auto_enable_eligible"] is False
shown = proposals_lib.show_proposal(home, result["proposal_id"])
assert shown["gates"]["collision_check"]["passed"] is False
assert shown["gates"]["risk_classify"]["passed"] is False
def test_full_gated_runtime_e2e_allows_meta_winner_when_acceptance_passes(
tmp_path: Path,
) -> None:
home = tmp_path / ".opensquilla"
result = proposals_lib.write_proposal(
home,
SAMPLE_SKILL_MD,
GATES_PASSING,
SMOKE_PASSING,
creator_mode="FULL_GATED",
acceptance_result={
"raw": (
"WINNER: orchestrated\n"
"REASONS:\n"
"- candidate has stricter gates\n"
"REGRESSIONS:\n"
"- none\n"
"REQUIRED_IMPROVEMENTS:\n"
"- none\n"
),
},
runtime_e2e_result={
"status": "ok",
"passed": True,
"winner": "meta",
"cases": [
{
"prompt": "please use synth test trigger",
"winner": "meta",
"regression": "",
},
],
},
collision_result="PASS: no trigger collision",
risk_result="RISK: low\nCAPABILITIES:\n- read-only",
)
assert result["auto_enable_eligible"] is True
shown = proposals_lib.show_proposal(home, result["proposal_id"])
assert shown["gates"]["acceptance_compare"]["passed"] is True
assert shown["gates"]["runtime_e2e"]["passed"] is True
def test_show_rejects_invalid_id(tmp_path: Path) -> None:
home = tmp_path / ".opensquilla"
out = proposals_lib.show_proposal(home, "../etc")
assert out["status"] == "error"
def test_show_missing_proposal(tmp_path: Path) -> None:
home = tmp_path / ".opensquilla"
out = proposals_lib.show_proposal(home, "deadbeef")
assert out["status"] == "error"
assert "not found" in out["reason"]
def test_accept_promotes_to_managed_skills(tmp_path: Path) -> None:
home = tmp_path / ".opensquilla"
pid = _seed_proposal(home)
out = proposals_lib.accept_proposal(home, pid)
assert out["status"] == "ok"
assert out["name"] == "synth-test-pipeline"
moved = home / "skills" / "synth-test-pipeline" / "SKILL.md"
assert moved.is_file()
# Source dir disappears
assert not (home / "proposals" / pid).exists()
def test_list_and_disable_auto_enabled_skill(tmp_path: Path) -> None:
home = tmp_path / ".opensquilla"
pid = _seed_proposal(home)
gates_path = home / "proposals" / pid / "gates.json"
gates = json.loads(gates_path.read_text())
gates["auto_enable"] = {
"status": "enabled",
"proposal_id": pid,
"risk_level": "low",
"max_risk": "low",
"triggered_by": "manual",
"enabled_at_ms": 123,
}
gates_path.write_text(json.dumps(gates))
accepted = proposals_lib.accept_proposal(home, pid)
assert accepted["status"] == "ok"
rows = proposals_lib.list_auto_enabled_skills(home)["skills"]
assert rows == [{
"name": "synth-test-pipeline",
"proposal_id": pid,
"risk_level": "low",
"max_risk": "low",
"triggered_by": "manual",
"enabled_at_ms": 123,
"validation_profile": "unknown",
"skills": [],
"tools": [],
"reasons": [],
}]
out = proposals_lib.disable_auto_enabled_skill(home, "synth-test-pipeline")
assert out["status"] == "ok"
assert out["proposal_id"] == pid
assert not (home / "skills" / "synth-test-pipeline").exists()
assert (home / "proposals" / pid / "SKILL.md").is_file()
disabled_gates = json.loads((home / "proposals" / pid / "gates.json").read_text())
assert disabled_gates["auto_enable"]["status"] == "disabled"
assert disabled_gates["auto_enable"]["previous_status"] == "enabled"
def test_disable_auto_enabled_skill_refuses_manual_skill(tmp_path: Path) -> None:
home = tmp_path / ".opensquilla"
pid = _seed_proposal(home)
accepted = proposals_lib.accept_proposal(home, pid)
assert accepted["status"] == "ok"
out = proposals_lib.disable_auto_enabled_skill(home, "synth-test-pipeline")
assert out["status"] == "refused"
assert "not auto-enabled" in out["reason"]
def test_accept_refuses_when_gates_fail_without_force(tmp_path: Path) -> None:
home = tmp_path / ".opensquilla"
pid = _seed_proposal(home, eligible=False)
out = proposals_lib.accept_proposal(home, pid)
assert out["status"] == "refused"
out2 = proposals_lib.accept_proposal(home, pid, force=True)
assert out2["status"] == "ok"
def test_accept_refuses_when_target_skill_exists(tmp_path: Path) -> None:
home = tmp_path / ".opensquilla"
pid1 = _seed_proposal(home)
proposals_lib.accept_proposal(home, pid1)
pid2 = _seed_proposal(home)
out = proposals_lib.accept_proposal(home, pid2)
assert out["status"] == "refused"
assert "already exists" in out["reason"]
def test_reject_removes_directory(tmp_path: Path) -> None:
home = tmp_path / ".opensquilla"
pid = _seed_proposal(home)
out = proposals_lib.reject_proposal(home, pid)
assert out["status"] == "ok"
assert not (home / "proposals" / pid).exists()
def test_reject_rejects_invalid_id(tmp_path: Path) -> None:
home = tmp_path / ".opensquilla"
out = proposals_lib.reject_proposal(home, "../etc/passwd")
assert out["status"] == "error"
def test_reject_missing_proposal(tmp_path: Path) -> None:
home = tmp_path / ".opensquilla"
out = proposals_lib.reject_proposal(home, "deadbeef")
assert out["status"] == "error"
def test_auto_propose_settings_round_trip(tmp_path: Path) -> None:
home = tmp_path / ".opensquilla"
assert proposals_lib.read_auto_propose_settings(home) == {}
proposals_lib.write_auto_propose_settings(
home, {
"enabled": True,
"on_dream_complete": False,
"auto_enable": True,
"auto_enable_max_risk": "medium",
},
)
out = proposals_lib.read_auto_propose_settings(home)
assert out == {
"enabled": True,
"on_dream_complete": False,
"auto_enable": True,
"auto_enable_max_risk": "medium",
}
def test_auto_propose_settings_drops_unknown_and_bad_types(tmp_path: Path) -> None:
home = tmp_path / ".opensquilla"
# Unknown keys dropped at write time
proposals_lib.write_auto_propose_settings(
home, {
"enabled": True,
"auto_enable_max_risk": "dangerous",
"bogus_key": True,
}, # type: ignore[arg-type]
)
assert proposals_lib.read_auto_propose_settings(home) == {"enabled": True}
# Bad-shape file → empty dict (no exception)
proposals_lib.auto_propose_settings_path(home).write_text("[1,2,3]")
assert proposals_lib.read_auto_propose_settings(home) == {}
def test_write_atomic_under_concurrent_writers(tmp_path: Path) -> None:
"""Writing N proposals should produce N distinct directories — the
atomic-rename guarantees uniqueness even if proposal_ids collide."""
home = tmp_path / ".opensquilla"
ids = []
for _ in range(5):
out = proposals_lib.write_proposal(home, SAMPLE_SKILL_MD, GATES_PASSING, SMOKE_PASSING)
assert out["status"] == "ok"
ids.append(out["proposal_id"])
assert len(set(ids)) == 5 # all distinct
assert proposals_lib.pending_count(home)["count"] == 5
# ── D1: degraded smoke must not yield auto_enable_eligible ──
SMOKE_DEGRADED = {
"G3": {"passed": True, "degraded": True},
"G4": {"passed": True, "degraded": True},
"degraded": True,
}
def test_degraded_smoke_blocks_auto_enable_eligible(tmp_path: Path) -> None:
"""D1: when the smoke runner has no fixture-generator LLM, it falls
back to a deterministic stub and flags the result ``degraded: True``.
G3/G4 still report ``passed: True`` because the deterministic
fixtures self-match by construction, but the candidate has not
been validated against a real model. The eligibility evaluator
must observe ``degraded`` and refuse to auto-enable so an
unattended creator pipeline cannot promote a never-validated
proposal.
The proposal itself still lands (``status == "ok"``) so an operator
can inspect it; only ``auto_enable_eligible`` flips to False."""
home = tmp_path / ".opensquilla"
result = proposals_lib.write_proposal(
home,
SAMPLE_SKILL_MD,
GATES_PASSING,
SMOKE_DEGRADED,
)
assert result["status"] == "ok"
assert result["auto_enable_eligible"] is False
shown = proposals_lib.show_proposal(home, result["proposal_id"])
assert shown["gates"]["auto_enable_eligible"] is False
# Cross-check: the smoke record on disk retains ``degraded`` so an
# auditor can grep for it without re-deriving from the eligibility
# flag.
assert shown["gates"]["smoke"].get("degraded") is True
def test_non_degraded_smoke_still_yields_auto_enable_eligible(
tmp_path: Path,
) -> None:
"""D1 negative control: a smoke result that does NOT carry a
``degraded`` flag (or carries it as False) must still be eligible
when all other gates pass. Without this regression the D1 change
could silently mark every proposal ineligible."""
home = tmp_path / ".opensquilla"
smoke_clean = {
"G3": {"passed": True, "degraded": False},
"G4": {"passed": True, "degraded": False},
"degraded": False,
}
result = proposals_lib.write_proposal(
home,
SAMPLE_SKILL_MD,
GATES_PASSING,
smoke_clean,
)
assert result["status"] == "ok"
assert result["auto_enable_eligible"] is True