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.
200 lines
7.0 KiB
Python
200 lines
7.0 KiB
Python
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import hmac
|
|
import importlib.util
|
|
import json
|
|
import sqlite3
|
|
import sys
|
|
import threading
|
|
import types
|
|
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
|
|
ROOT=Path(__file__).resolve().parents[2]
|
|
MODULE_PATH=ROOT/"active-runtime/app/aios_legacy_attachment_runtime.py"
|
|
TOKEN="control-token-held-only-in-memory-0123456789"
|
|
TASK="legacy-task"
|
|
|
|
|
|
def _install_stubs():
|
|
app=types.ModuleType("app"); app.__path__=[]; sys.modules["app"]=app
|
|
identity=types.ModuleType("app.aios_gitops_job_identity")
|
|
identity.legacy_task_storage_key=lambda value:value
|
|
identity.task_storage_key=lambda value:value
|
|
sys.modules[identity.__name__]=identity
|
|
|
|
auth=types.ModuleType("app.aios_single_host_authority")
|
|
auth.DEFAULT_OWNER_SIGNER_ID="owner"
|
|
auth.OWNER_NAMESPACE="ns"
|
|
auth.RegistryOwnerApprovalVerifier=type("RegistryOwnerApprovalVerifier",(),{})
|
|
auth.SignerRegistry=type("SignerRegistry",(),{})
|
|
sys.modules[auth.__name__]=auth
|
|
|
|
task_plane=types.ModuleType("app.task_plane"); task_plane.__path__=[]
|
|
sys.modules["app.task_plane"]=task_plane
|
|
la=types.ModuleType("app.task_plane.legacy_attachment")
|
|
la.ATTACH_EVIDENCE_SCHEMA="evidence"
|
|
la.SOURCE_KIND="legacy"
|
|
class Dummy:
|
|
def __init__(self,*args,**kwargs): self.args=args; self.kwargs=kwargs
|
|
la.AuthorityReadbackRef=Dummy
|
|
la.VerifiedLegacyAttachmentEvidence=Dummy
|
|
sys.modules[la.__name__]=la
|
|
|
|
|
|
def _load():
|
|
_install_stubs()
|
|
name="app.aios_legacy_attachment_runtime"
|
|
spec=importlib.util.spec_from_file_location(name,MODULE_PATH)
|
|
mod=importlib.util.module_from_spec(spec)
|
|
sys.modules[name]=mod
|
|
spec.loader.exec_module(mod)
|
|
return mod
|
|
|
|
|
|
M=_load()
|
|
|
|
|
|
class Handler(BaseHTTPRequestHandler):
|
|
calls=[]
|
|
def log_message(self,*_args): return
|
|
def do_GET(self):
|
|
Handler.calls.append(self.path)
|
|
if not hmac.compare_digest(self.headers.get("Authorization",""),"Bearer "+TOKEN):
|
|
status, body=401, {"error":"UNAUTHORIZED"}
|
|
elif self.path == f"/v1/tasks/{TASK}/events":
|
|
status, body=200, [{"task_id":TASK,"event_type":"CREATED","revision":1}]
|
|
elif self.path == f"/v1/tasks/{TASK}":
|
|
status, body=500, {"error":"MUTATING_DETAIL_ROUTE_MUST_NOT_BE_USED"}
|
|
else:
|
|
status, body=404, {"error":"NOT_FOUND"}
|
|
raw=json.dumps(body,sort_keys=True,separators=(",",":")).encode()
|
|
self.send_response(status); self.send_header("Content-Type","application/json")
|
|
self.send_header("Content-Length",str(len(raw))); self.end_headers(); self.wfile.write(raw)
|
|
|
|
|
|
class Server:
|
|
def __init__(self):
|
|
Handler.calls=[]
|
|
self.httpd=ThreadingHTTPServer(("127.0.0.1",0),Handler)
|
|
self.thread=threading.Thread(target=self.httpd.serve_forever,daemon=True)
|
|
@property
|
|
def url(self): return f"http://127.0.0.1:{self.httpd.server_port}"
|
|
def __enter__(self): self.thread.start(); return self
|
|
def __exit__(self,*_):
|
|
self.httpd.shutdown(); self.thread.join(); self.httpd.server_close()
|
|
|
|
|
|
def make_db(path: Path, *, status="EXPIRED"):
|
|
cols=M._AGENT_TASK_COLUMNS
|
|
defs=[]
|
|
for name in cols:
|
|
if name in {"priority","max_attempts","attempt","revision"}:
|
|
typ="INTEGER"
|
|
else:
|
|
typ="TEXT"
|
|
defs.append(f"{name} {typ}")
|
|
con=sqlite3.connect(path)
|
|
con.execute("CREATE TABLE tasks ("+",".join(defs)+")")
|
|
values={
|
|
"task_id":TASK,
|
|
"goal":"legacy",
|
|
"instructions":"preserve",
|
|
"success_criteria_json":"[]",
|
|
"required_capabilities_json":"[]",
|
|
"constraints_json":"{}",
|
|
"metadata_json":"{}",
|
|
"deadline":None,
|
|
"priority":0,
|
|
"max_attempts":1,
|
|
"attempt":1,
|
|
"status":status,
|
|
"claimed_by":None,
|
|
"lease_token_hash":"DO-NOT-EXPOSE",
|
|
"lease_expires_at":"2000-01-01T00:00:00Z",
|
|
"result_json":None,
|
|
"result_digest":None,
|
|
"result_idempotency_key":None,
|
|
"verification_json":None,
|
|
"revision":4,
|
|
"created_at":"2026-09-13T18:44:18Z",
|
|
"updated_at":"2026-09-13T18:59:18Z",
|
|
}
|
|
con.execute(
|
|
"INSERT INTO tasks ("+",".join(cols)+") VALUES ("+",".join("?" for _ in cols)+")",
|
|
[values[x] for x in cols],
|
|
)
|
|
# Unrelated row proves reader performs no global lease expiry/update.
|
|
values2=dict(values); values2["task_id"]="unrelated"; values2["status"]="CLAIMED"; values2["revision"]=9
|
|
con.execute(
|
|
"INSERT INTO tasks ("+",".join(cols)+") VALUES ("+",".join("?" for _ in cols)+")",
|
|
[values2[x] for x in cols],
|
|
)
|
|
con.commit(); con.close()
|
|
|
|
|
|
def snapshot(path: Path):
|
|
con=sqlite3.connect(path)
|
|
rows=con.execute("SELECT task_id,status,revision,lease_expires_at FROM tasks ORDER BY task_id").fetchall()
|
|
con.close()
|
|
return rows, hashlib.sha256(path.read_bytes()).hexdigest()
|
|
|
|
|
|
def test_reader_authenticates_via_events_and_reads_task_db_without_mutation(tmp_path):
|
|
db=tmp_path/"agent-task.sqlite3"; make_db(db)
|
|
before=snapshot(db)
|
|
with Server() as server:
|
|
reader=M.AuthenticatedAgentTaskReader(
|
|
server.url, lambda:TOKEN, db, clock=lambda:2_000_000_000,
|
|
require_db_root_owner=False,
|
|
)
|
|
out=reader.read(TASK)
|
|
after=snapshot(db)
|
|
assert before==after
|
|
assert Handler.calls==[f"/v1/tasks/{TASK}/events"]
|
|
assert out["body"]["task_id"]==TASK
|
|
assert out["body"]["status"]=="EXPIRED"
|
|
assert "lease_token_hash" not in out["body"]
|
|
assert "authenticated-events-sha256=" in out["readback_ref"]
|
|
|
|
|
|
def test_reader_rejects_nonterminal_row(tmp_path):
|
|
db=tmp_path/"agent-task.sqlite3"; make_db(db,status="RUNNING")
|
|
with Server() as server:
|
|
reader=M.AuthenticatedAgentTaskReader(
|
|
server.url, lambda:TOKEN, db, clock=lambda:2_000_000_000,
|
|
require_db_root_owner=False,
|
|
)
|
|
with pytest.raises(M.LegacyAttachmentRuntimeError,match="AGENT_TASK_NOT_TERMINAL"):
|
|
reader.read(TASK)
|
|
|
|
|
|
def test_reader_fails_closed_on_schema_drift(tmp_path):
|
|
db=tmp_path/"agent-task.sqlite3"
|
|
con=sqlite3.connect(db); con.execute("CREATE TABLE tasks(task_id TEXT)"); con.commit(); con.close()
|
|
with Server() as server:
|
|
reader=M.AuthenticatedAgentTaskReader(
|
|
server.url, lambda:TOKEN, db, clock=lambda:2_000_000_000,
|
|
require_db_root_owner=False,
|
|
)
|
|
with pytest.raises(M.LegacyAttachmentRuntimeError,match="AGENT_TASK_DB_SCHEMA_INVALID"):
|
|
reader.read(TASK)
|
|
|
|
|
|
def test_reader_rejects_short_bearer_without_http_or_db_change(tmp_path):
|
|
db=tmp_path/"agent-task.sqlite3"; make_db(db)
|
|
before=snapshot(db)
|
|
with Server() as server:
|
|
reader=M.AuthenticatedAgentTaskReader(
|
|
server.url, lambda:"short", db, clock=lambda:2_000_000_000,
|
|
require_db_root_owner=False,
|
|
)
|
|
with pytest.raises(M.LegacyAttachmentRuntimeError,match="AGENT_TASK_CREDENTIAL_INVALID"):
|
|
reader.read(TASK)
|
|
assert snapshot(db)==before
|
|
assert Handler.calls==[]
|