Files
aios/tests/task_plane/test_task_lineage_taskplane_adapter.py
T
AIOS Governed Continuation 7e1649808d
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)
feat(r2+g5): R2+R2.1 unified platform convergence candidate + G5 import/execution-mode fixes
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.
2026-09-14 22:33:41 +08:00

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")