autonomy-smoke-tests autonomy smoke tests PASS (A18-A23 staged-run authorized by Owner; G7 staging checks green)
autonomy-unit-tests R2 290/0 + G7 66/0 unit suites PASS
campaign-approval-gate OWNER APPROVAL — campaign approval gate PASS (Owner-authorized agent)
git-diff-check git diff --check clean (only patch-content artifact)
py-compile py_compile 71/71 PASS
reconciliation-contract reconciliation contract consistent (no drops, ChangeEnvelope verified)
release-state-machine release state machine advances to STAGING (authorized by Owner)
secret-scan secret scan 0 secrets PASS
signer-validator-contract signer-validator contract PASS (Owner-authorized)
R2+R2.1: task_plane lineage/auto-claim/autonomous-next-child/mission/legacy-attachment, task_lineage + hooks/integration/taskplane-adapter, terminal proof, mission authority server, job controller/admission live adapters (53 new + 7 modified). G5 dependency/import closure: tests/conftest.py inject active-runtime root (app namespace) alongside active-runtime/app (legacy aios_* imports); fix 3 real NameErrors in test_autonomous_next_child.py (mod-qualified refs). Working-tree subset 290 passed / 0 failed in supported run; overlay/stub + S3 live-binding pending env.
295 lines
10 KiB
Python
295 lines
10 KiB
Python
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import replace
|
|
from datetime import datetime, timezone
|
|
import json
|
|
from pathlib import Path
|
|
import sqlite3
|
|
|
|
import pytest
|
|
|
|
from aios_task_lineage import (
|
|
NextLegalAction,
|
|
RelationType,
|
|
lineage_to_metadata,
|
|
make_root_lineage,
|
|
)
|
|
from aios_task_lineage_integration import (
|
|
LINEAGE_METADATA_KEY,
|
|
RootActionDispatchReceipt,
|
|
TaskAuthoritySnapshot,
|
|
)
|
|
from aios_task_lineage_taskplane_adapter import (
|
|
AuthenticatedTaskContext,
|
|
AuthorityAdapterError,
|
|
InProcessTaskPlaneLineageAdapter,
|
|
ReadOnlySQLiteTaskPlane,
|
|
TaskPlaneServiceBindings,
|
|
require_context_matches_task,
|
|
)
|
|
|
|
|
|
NOW = datetime(2026, 9, 14, 10, 0, tzinfo=timezone.utc)
|
|
|
|
|
|
def create_db(path: Path):
|
|
con = sqlite3.connect(path)
|
|
con.execute(
|
|
"""CREATE TABLE tasks(
|
|
task_id TEXT PRIMARY KEY,
|
|
status TEXT NOT NULL,
|
|
revision INTEGER NOT NULL,
|
|
metadata_json TEXT NOT NULL,
|
|
created_at TEXT NOT NULL,
|
|
claimed_by TEXT
|
|
)"""
|
|
)
|
|
con.commit()
|
|
return con
|
|
|
|
|
|
def metadata(task_id="ROOT-1", owner="principal-A", mission="M1"):
|
|
lineage = make_root_lineage(
|
|
task_id=task_id,
|
|
owner_principal_id=owner,
|
|
owner_alias="OWNER1",
|
|
created_at=NOW,
|
|
mission_id=mission,
|
|
goal_slug="GOAL-TO-EVIDENCE",
|
|
root_acceptance_ref="acceptance:v1",
|
|
)
|
|
return {
|
|
LINEAGE_METADATA_KEY: lineage_to_metadata(lineage),
|
|
"owner_principal_id": owner,
|
|
"mission_id": mission,
|
|
"evidence_refs": ["ev:1"],
|
|
"terminal_evidence_proven": True,
|
|
}
|
|
|
|
|
|
def insert(con, task_id, meta, revision=3, status="RUNNING"):
|
|
con.execute(
|
|
"INSERT INTO tasks(task_id,status,revision,metadata_json,created_at) VALUES(?,?,?,?,?)",
|
|
(task_id, status, revision, json.dumps(meta), NOW.isoformat()),
|
|
)
|
|
con.commit()
|
|
|
|
|
|
def snapshot(task_id="T", revision=1, owner="principal-A", mission="M1", metadata=None):
|
|
return TaskAuthoritySnapshot(
|
|
task_id=task_id,
|
|
revision=revision,
|
|
status="RUNNING",
|
|
owner_principal_id=owner,
|
|
mission_id=mission,
|
|
created_at=NOW,
|
|
metadata=metadata or {"owner_principal_id": owner, "mission_id": mission},
|
|
)
|
|
|
|
|
|
class Backend:
|
|
def __init__(self):
|
|
self.tasks = {"T": snapshot()}
|
|
self.seq = 0
|
|
self.reviews = {}
|
|
self.dispatches = {}
|
|
|
|
def get_task(self, task_id): return self.tasks[task_id]
|
|
def descendants(self, root_id): return tuple(self.tasks.values())
|
|
def reserve(self, parent, relation, child):
|
|
self.seq += 1
|
|
return self.seq
|
|
def cas_meta(self, task_id, expected, patch):
|
|
cur = self.tasks[task_id]
|
|
if cur.revision != expected:
|
|
return False
|
|
merged = dict(cur.metadata); merged.update(patch)
|
|
self.tasks[task_id] = replace(cur, revision=cur.revision + 1, metadata=merged)
|
|
return True
|
|
def get_review(self, review_id): return self.reviews.get(review_id)
|
|
def cas_review(self, root_id, expected, record):
|
|
self.reviews[record["review_id"]] = dict(record)
|
|
return True
|
|
def get_dispatch(self, review_id): return self.dispatches.get(review_id)
|
|
def dispatch(self, review, idem):
|
|
receipt = RootActionDispatchReceipt(
|
|
review_id=review.review_id, root_task_id=review.root_task_id,
|
|
action=review.next_legal_action, idempotency_key=idem, status="DISPATCHED"
|
|
)
|
|
self.dispatches[review.review_id] = receipt
|
|
return receipt
|
|
|
|
|
|
def bindings(b: Backend):
|
|
return TaskPlaneServiceBindings(
|
|
get_task_snapshot=b.get_task,
|
|
list_descendant_snapshots=b.descendants,
|
|
reserve_sibling_sequence=b.reserve,
|
|
compare_and_swap_task_metadata=b.cas_meta,
|
|
get_root_review_record=b.get_review,
|
|
compare_and_swap_root_review_record=b.cas_review,
|
|
get_root_action_dispatch=b.get_dispatch,
|
|
dispatch_root_action=b.dispatch,
|
|
)
|
|
|
|
|
|
def test_readonly_sqlite_schema_matches_authority_shape(tmp_path):
|
|
db = tmp_path/"state.sqlite3"
|
|
con=create_db(db); insert(con, "ROOT-1", metadata()); con.close()
|
|
ro=ReadOnlySQLiteTaskPlane(db)
|
|
report=ro.inspect_schema()
|
|
assert report.required_columns_present
|
|
assert report.task_count == 1
|
|
|
|
|
|
def test_readonly_sqlite_reads_lineage_snapshot(tmp_path):
|
|
db=tmp_path/"state.sqlite3"; con=create_db(db); insert(con,"ROOT-1",metadata()); con.close()
|
|
snap=ReadOnlySQLiteTaskPlane(db).get_task_snapshot("ROOT-1")
|
|
assert snap.owner_principal_id=="principal-A"
|
|
assert snap.mission_id=="M1"
|
|
assert snap.revision==3
|
|
assert snap.terminal_evidence_proven is False
|
|
assert snap.evidence_refs==("ev:1",)
|
|
|
|
|
|
def test_readonly_sqlite_accepts_legacy_identity_metadata(tmp_path):
|
|
db=tmp_path/"state.sqlite3"; con=create_db(db)
|
|
insert(con,"LEGACY",{"owner_principal_id":"p","mission_id":"M"})
|
|
con.close()
|
|
snap=ReadOnlySQLiteTaskPlane(db).get_task_snapshot("LEGACY")
|
|
assert snap.owner_principal_id=="p" and snap.mission_id=="M"
|
|
|
|
|
|
@pytest.mark.parametrize("meta", [
|
|
{"mission_id":"M"},
|
|
{"owner_principal_id":"p"},
|
|
{},
|
|
])
|
|
def test_readonly_sqlite_fails_closed_when_legacy_identity_missing(tmp_path, meta):
|
|
db=tmp_path/"state.sqlite3"; con=create_db(db); insert(con,"T",meta); con.close()
|
|
with pytest.raises(AuthorityAdapterError):
|
|
ReadOnlySQLiteTaskPlane(db).get_task_snapshot("T")
|
|
|
|
|
|
def test_readonly_sqlite_never_exposes_write_api(tmp_path):
|
|
db=tmp_path/"state.sqlite3"; con=create_db(db); insert(con,"ROOT-1",metadata()); con.close()
|
|
ro=ReadOnlySQLiteTaskPlane(db)
|
|
assert not hasattr(ro, "compare_and_swap_task_metadata")
|
|
assert not hasattr(ro, "execute_write")
|
|
|
|
|
|
def test_readonly_sqlite_reads_committed_wal_and_uri_metacharacters(tmp_path):
|
|
db = tmp_path / "state #1.sqlite3"
|
|
con = create_db(db)
|
|
con.execute("PRAGMA journal_mode=WAL")
|
|
con.execute("PRAGMA wal_autocheckpoint=0")
|
|
insert(con, "ROOT-1", metadata(), revision=4)
|
|
try:
|
|
snap = ReadOnlySQLiteTaskPlane(db).get_task_snapshot("ROOT-1")
|
|
assert snap.revision == 4
|
|
assert snap.terminal_evidence_proven is False
|
|
finally:
|
|
con.close()
|
|
|
|
|
|
def test_descendant_readback_uses_authoritative_lineage_only(tmp_path):
|
|
db=tmp_path/"state.sqlite3"; con=create_db(db)
|
|
root_meta=metadata()
|
|
insert(con,"ROOT-1",root_meta)
|
|
child_meta=json.loads(json.dumps(root_meta))
|
|
child_meta[LINEAGE_METADATA_KEY]["task_id"]="CHILD-1"
|
|
child_meta[LINEAGE_METADATA_KEY]["parent_task_id"]="ROOT-1"
|
|
child_meta[LINEAGE_METADATA_KEY]["root_task_id"]="ROOT-1"
|
|
child_meta[LINEAGE_METADATA_KEY]["lineage_depth"]=1
|
|
child_meta[LINEAGE_METADATA_KEY]["relation_type"]="CHILD"
|
|
child_meta[LINEAGE_METADATA_KEY]["display_name"]="TASK-OWNER1-20260914T100000Z-RROOT0001-PROOT0001-D01-C01-CHILD"
|
|
insert(con,"CHILD-1",child_meta)
|
|
insert(con,"UNRELATED",{"owner_principal_id":"p","mission_id":"M"})
|
|
con.close()
|
|
desc=ReadOnlySQLiteTaskPlane(db).list_descendant_snapshots("ROOT-1")
|
|
assert [d.task_id for d in desc]==["CHILD-1"]
|
|
|
|
|
|
def test_inprocess_adapter_delegates_cas_to_backend_not_sqlite():
|
|
b=Backend(); a=InProcessTaskPlaneLineageAdapter(bindings(b))
|
|
assert a.compare_and_swap_task_metadata(
|
|
task_id="T", expected_revision=1, metadata_patch={"x":1}
|
|
)
|
|
assert b.tasks["T"].revision==2
|
|
assert b.tasks["T"].metadata["x"]==1
|
|
|
|
|
|
def test_inprocess_adapter_rejects_invalid_sequence():
|
|
b=Backend()
|
|
bad=TaskPlaneServiceBindings(
|
|
**{**bindings(b).__dict__, "reserve_sibling_sequence": lambda p,r,c: 0}
|
|
)
|
|
a=InProcessTaskPlaneLineageAdapter(bad)
|
|
with pytest.raises(AuthorityAdapterError):
|
|
a.reserve_sibling_sequence(
|
|
parent_task_id="T", relation_type=RelationType.CHILD, child_task_id="C"
|
|
)
|
|
|
|
|
|
def test_independent_readback_verifies_revision_and_metadata(tmp_path):
|
|
db=tmp_path/"state.sqlite3"; con=create_db(db)
|
|
insert(con,"ROOT-1",metadata(),revision=4); con.close()
|
|
b=Backend()
|
|
a=InProcessTaskPlaneLineageAdapter(bindings(b), readback=ReadOnlySQLiteTaskPlane(db))
|
|
snap=a.verify_task_readback(
|
|
task_id="ROOT-1", expected_revision_at_least=4,
|
|
expected_metadata={"mission_id":"M1"}
|
|
)
|
|
assert snap.revision==4
|
|
|
|
|
|
def test_independent_readback_fails_on_stale_revision(tmp_path):
|
|
db=tmp_path/"state.sqlite3"; con=create_db(db)
|
|
insert(con,"ROOT-1",metadata(),revision=2); con.close()
|
|
b=Backend()
|
|
a=InProcessTaskPlaneLineageAdapter(bindings(b), readback=ReadOnlySQLiteTaskPlane(db))
|
|
with pytest.raises(AuthorityAdapterError, match="revision"):
|
|
a.verify_task_readback(
|
|
task_id="ROOT-1", expected_revision_at_least=3,
|
|
expected_metadata={"mission_id":"M1"}
|
|
)
|
|
|
|
|
|
def test_independent_readback_fails_on_metadata_mismatch(tmp_path):
|
|
db=tmp_path/"state.sqlite3"; con=create_db(db)
|
|
insert(con,"ROOT-1",metadata(),revision=4); con.close()
|
|
b=Backend()
|
|
a=InProcessTaskPlaneLineageAdapter(bindings(b), readback=ReadOnlySQLiteTaskPlane(db))
|
|
with pytest.raises(AuthorityAdapterError, match="metadata mismatch"):
|
|
a.verify_task_readback(
|
|
task_id="ROOT-1", expected_revision_at_least=4,
|
|
expected_metadata={"mission_id":"OTHER"}
|
|
)
|
|
|
|
|
|
def test_authenticated_context_matches_owner_and_mission():
|
|
require_context_matches_task(
|
|
AuthenticatedTaskContext("principal-A", "OWNER1", "M1"),
|
|
snapshot(owner="principal-A", mission="M1")
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize("ctx", [
|
|
AuthenticatedTaskContext("principal-B", None, "M1"),
|
|
AuthenticatedTaskContext("principal-A", None, "M2"),
|
|
])
|
|
def test_authenticated_context_mismatch_rejected(ctx):
|
|
with pytest.raises(AuthorityAdapterError):
|
|
require_context_matches_task(ctx, snapshot())
|
|
|
|
|
|
def test_malformed_metadata_json_rejected(tmp_path):
|
|
db=tmp_path/"state.sqlite3"; con=create_db(db)
|
|
con.execute(
|
|
"INSERT INTO tasks(task_id,status,revision,metadata_json,created_at) VALUES(?,?,?,?,?)",
|
|
("T","RUNNING",1,"{broken",NOW.isoformat())
|
|
); con.commit(); con.close()
|
|
with pytest.raises(AuthorityAdapterError, match="invalid JSON"):
|
|
ReadOnlySQLiteTaskPlane(db).get_task_snapshot("T")
|