ouroboros/tests/test_acceptance_skill_lifecycle.py
Ouroboros 09ac51b2f0 acceptance packet / custody: durable omissions dispatch, custody-only settlement receipts, cross-process retirement lock, one packet budget
Fifth authoritative review: leading omissions and recaps with a durable
get_task_result reference are not_materialized_for_reviewer again (dispatchable,
non-resolving) — only source-less rows withhold the panel; contributor receipts
take agent-session settlement exclusively from the final custody replay, and a
missing or unreadable custody row is a typed mismatch instead of trusting the
response's self-report; the skill-history projection discloses rows scanned,
truncation, gap reasons and a canonical source, and an incomplete projection is
non-resolving; settlement publication and last-sibling retirement sit under a
stable project-digest file lock that holds across worker processes; the host's
late acceptance fields enter the packet builder before the single budget
enforcement; ARCHITECTURE names all four window-aware surfaces and documents
SETTLED before registration retirement. loop.py 282 903 bytes (−765). Manifest
untouched.

Co-authored-by: Ouroboros <311266734+ouroboros-agent@users.noreply.github.com>
2026-09-04 03:21:07 +03:00

259 lines
10 KiB
Python

"""The acceptance packet's per-skill lifecycle facts (visibility only).
A task that authored or repaired a skill used to hand the panel no way to see
whether that skill actually reached a reviewed, ready, enabled state. These
facts close that gap. They are facts, never a gate: acceptance judges quality,
never the execution route.
"""
from __future__ import annotations
import json
import pathlib
from ouroboros.review_evidence import build_task_acceptance_evidence
from ouroboros.review_evidence_refs import acceptance_evidence_ref_vocabulary
from ouroboros.skill_loader import find_skill
from ouroboros.skill_readiness import (
_skill_names_from_review_history,
acceptance_skill_lifecycle,
skill_names_touched_by_trace,
skill_readiness_for_execution,
)
from ouroboros.tools.registry import ToolContext
def _write_skill(drive_root: pathlib.Path, name: str) -> pathlib.Path:
skill_dir = drive_root / "skills" / "external" / name
skill_dir.mkdir(parents=True)
(drive_root / "state" / "skills" / name).mkdir(parents=True)
(skill_dir / "SKILL.md").write_text(
f"---\nname: {name}\ntype: instruction\nversion: 0.1.0\n---\nbody\n",
encoding="utf-8",
)
return skill_dir
def _write_history(drive_root: pathlib.Path, name: str, root_task_id: str) -> None:
path = drive_root / "state" / "skills" / name / "review_history.jsonl"
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(
json.dumps({"ts": "2026-09-03T00:00:00+00:00", "status": "pass",
"root_task_id": root_task_id}) + "\n",
encoding="utf-8",
)
projection = drive_root / "state" / "skill_review_root_tasks.jsonl"
with projection.open("a", encoding="utf-8") as handle:
handle.write(json.dumps({"root_task_id": root_task_id, "skill": name}) + "\n")
def _ctx(drive_root: pathlib.Path, tmp_path: pathlib.Path, task_id: str) -> ToolContext:
repo = tmp_path / "repo"
repo.mkdir(exist_ok=True)
return ToolContext(repo_dir=repo, drive_root=drive_root, task_id=task_id)
def test_the_lifecycle_scan_joins_trace_edits_and_this_root_task_history(tmp_path):
drive_root = tmp_path / "drive"
drive_root.mkdir()
_write_skill(drive_root, "edited")
_write_skill(drive_root, "child_authored")
_write_skill(drive_root, "someone_elses")
_write_history(drive_root, "child_authored", "root-1")
_write_history(drive_root, "someone_elses", "root-other")
trace = {"tool_calls": [
{"tool": "write_file", "args": {"path": "data/skills/external/edited/SKILL.md"}},
]}
rows = acceptance_skill_lifecycle(drive_root, trace, "root-1")
names = [row["name"] for row in rows]
assert names == ["edited", "child_authored"]
assert "someone_elses" not in names
def test_the_lifecycle_facts_match_the_readiness_predicate(tmp_path):
drive_root = tmp_path / "drive"
drive_root.mkdir()
_write_skill(drive_root, "alpha")
trace = {"tool_calls": [
{"tool": "write_file", "args": {"bucket": "external", "skill_name": "alpha",
"path": "SKILL.md"}},
]}
row = acceptance_skill_lifecycle(drive_root, trace, "")[0]
skill = find_skill(drive_root, "alpha")
readiness = skill_readiness_for_execution(drive_root, skill)
assert row["name"] == "alpha"
assert row["source"] == skill.source
assert row["review_status"] == skill.review.status
assert row["ready"] is readiness.ready
assert row["blockers"] == readiness.blockers
assert row["enabled"] is bool(skill.enabled)
# An unreviewed, disabled self-authored skill is exactly what the panel
# needs to see, so the row must be honest rather than empty.
assert row["ready"] is False
assert row["blockers"]
def test_a_skill_lifecycle_tool_call_names_the_skill_without_a_payload_edit(tmp_path):
"""A free delegation lane integrates a patch and never calls write_file, so
the lifecycle tools are the only carrier of the name in that shape."""
trace = {"tool_calls": [
{"tool": "skill_review", "args": {"skill": "delegated"}},
{"tool": "skill_preflight", "args": {"skill": "probed"}},
{"tool": "skill_exec", "args": {"skill": "executed", "script": "scripts/run.py"}},
{"tool": "toggle_skill", "args": {"skill": "toggled", "enabled": True}},
{"tool": "submit_skill_to_hub", "args": {
"skill": "published", "confirm_public_submission": True,
}},
{"tool": "edit_text", "args": {
"root": "skill_payload", "bucket": "user_repo",
"skill_name": "user-repo-skill", "path": "SKILL.md",
"old_text": "old", "new_text": "new",
}},
{"tool": "run_command", "args": {"cmd": "ls"}},
{"tool": "run_command", "args": {
"cmd": ["python3", "repair.py"], "cwd": "skill_payload/scripts",
"bucket": "external", "skill_name": "command-edited",
}},
{"tool": "run_script", "args": {
"script": "repair()", "cwd": "skill_payload",
"bucket": "clawhub", "skill_name": "script-edited",
}},
{"tool": "delegate_start", "args": {
"prompt": "repair", "root": "skill_payload",
"bucket": "user_repo", "skill_name": "delegate-edited",
}},
{"tool": "delegate_start", "args": {
"prompt": "ordinary workspace", "skill_name": "not-selected",
}},
]}
assert skill_names_touched_by_trace(trace) == [
"delegated", "probed", "executed", "toggled", "published",
"user-repo-skill", "command-edited", "script-edited", "delegate-edited",
]
def test_real_skill_payload_selectors_feed_lifecycle_rows(tmp_path):
drive_root = tmp_path / "drive"
drive_root.mkdir()
for name in ("command-edited", "script-edited", "delegate-edited"):
_write_skill(drive_root, name)
trace = {"tool_calls": [
{"tool": "run_command", "args": {
"cmd": ["python3", "repair.py"], "cwd": "skill_payload/scripts",
"bucket": "external", "skill_name": "command-edited",
}},
{"tool": "run_script", "args": {
"script": "repair()", "cwd": "skill_payload",
"bucket": "external", "skill_name": "script-edited",
}},
{"tool": "delegate_start", "args": {
"prompt": "repair", "root": "skill_payload",
"bucket": "external", "skill_name": "delegate-edited",
}},
]}
assert [row["name"] for row in acceptance_skill_lifecycle(
drive_root, trace, "root-1",
)] == ["command-edited", "script-edited", "delegate-edited"]
def test_split_root_packet_reads_skill_lifecycle_from_the_canonical_root(tmp_path):
canonical = tmp_path / "canonical"
execution = tmp_path / "execution"
canonical.mkdir()
execution.mkdir()
_write_skill(canonical, "canonical-skill")
ctx = ToolContext(
repo_dir=tmp_path, drive_root=execution, budget_drive_root=canonical,
task_id="task-split-skill",
)
packet = build_task_acceptance_evidence(
ctx,
llm_trace={"tool_calls": [{
"tool": "skill_review", "args": {"skill": "canonical-skill"},
}]},
drive_root=execution,
task_id="task-split-skill",
)
assert packet["skill_lifecycle"][0]["name"] == "canonical-skill"
assert packet["skill_lifecycle"][0].get("present", True) is True
def test_the_packet_carries_the_section_and_the_vocabulary_resolves_it(tmp_path):
drive_root = tmp_path / "drive"
drive_root.mkdir()
_write_skill(drive_root, "alpha")
ctx = _ctx(drive_root, tmp_path, "task-skill")
trace = {"tool_calls": [
{"tool": "write_file", "args": {"path": "data/skills/external/alpha/SKILL.md"},
"status": "ok"},
]}
packet = build_task_acceptance_evidence(
ctx, llm_trace=trace, drive_root=drive_root, task_id="task-skill",
)
assert packet["skill_lifecycle"][0]["name"] == "alpha"
assert packet["__provenance__"]["skill_lifecycle"] == "host_attested"
assert acceptance_evidence_ref_vocabulary(packet)["skill_lifecycle"] == "packet_section"
def test_no_touched_skill_adds_no_section(tmp_path):
drive_root = tmp_path / "drive"
drive_root.mkdir()
ctx = _ctx(drive_root, tmp_path, "task-plain")
packet = build_task_acceptance_evidence(
ctx, llm_trace={"tool_calls": [{"tool": "run_command", "status": "ok"}]},
drive_root=drive_root, task_id="task-plain",
)
assert "skill_lifecycle" not in packet
assert acceptance_skill_lifecycle(drive_root, {"tool_calls": []}, "") == []
assert acceptance_skill_lifecycle(None, {"tool_calls": []}, "") == []
def test_bounded_history_projection_discloses_both_omission_limits(tmp_path):
drive_root = tmp_path / "drive"
drive_root.mkdir()
_write_skill(drive_root, "visible")
projection = drive_root / "state" / "skill_review_root_tasks.jsonl"
projection.parent.mkdir(parents=True, exist_ok=True)
rows = [
json.dumps({
"root_task_id": "other", "skill": f"skill-{index}",
"padding": "x" * 1500,
})
for index in range(800)
]
rows.append(json.dumps({"root_task_id": "root-1", "skill": "visible"}))
projection.write_text("\n".join(rows) + "\n", encoding="utf-8")
history = _skill_names_from_review_history(drive_root, "root-1")
assert history["names"] == ["visible"]
assert history["coverage"]["rows_scanned"] <= 512
assert history["coverage"]["truncated"] is True
assert set(history["coverage"]["gap_reasons"]) >= {
"tail_bytes_truncated", "max_entries_truncated",
}
assert history["coverage"]["source_ref"] == {
"kind": "canonical_jsonl",
"path": "state/skill_review_root_tasks.jsonl",
"reader": "read_file",
}
ctx = _ctx(drive_root, tmp_path, "task-bounded-history")
packet = build_task_acceptance_evidence(
ctx, llm_trace={"tool_calls": [{
"tool": "skill_review", "args": {"skill": "visible"},
}]}, drive_root=drive_root, task_id="task-bounded-history",
)
assert packet["skill_lifecycle_history_coverage"]["truncated"] is True
assert packet["skill_lifecycle_complete"] is False
assert acceptance_evidence_ref_vocabulary(packet)["skill_lifecycle"] == "partial"
partial = next(
row for row in packet["__unresolved_partial_artifacts__"]
if row["tool"] == "skill_lifecycle"
)
assert partial["status"] == "not_materialized_for_reviewer"
assert partial["source_ref"] == history["coverage"]["source_ref"]