Files
aios/tests/task_plane/test_legacy_attachment_reader_nonmutating.py
T
owner 4420b2afcb
autonomy-smoke-tests v2r2c3-pr88 PASS evidence_sha256=e9b29f42e72804aedf5a269d4507aafa7270b39400c6923bbb014d8a335fe788
autonomy-unit-tests v2r2c3-pr88 PASS evidence_sha256=02e00edbe6415cf1d548ef7fb53b67e37c4abd29397b00075c6feb0d48b5f2c5
campaign-approval-gate v2r2c3-pr88 PASS evidence_sha256=0616d6aa11496391686dcbd658123e51d8d057e1f02661af7ef8512554e404d8
git-diff-check v2r2c3-pr88 PASS evidence_sha256=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
py-compile v2r2c3-pr88 PASS evidence_sha256=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
reconciliation-contract v2r2c3-pr88 PASS evidence_sha256=f19e306069acd7ef3294f3455973795233398ffd31ce184ecf8b8b752c42bc02
release-state-machine v2r2c3-pr88 PASS evidence_sha256=1d3314562a9345a9f75b1adf32d61168822c00d316f7db85833230bba023e448
secret-scan v2r2c3-pr88 PASS evidence_sha256=9b853228ca53a5c41c95fa53434cab0179ca2dc42acf2c6d3848745dbd9e2133
signer-validator-contract v2r2c3-pr88 PASS evidence_sha256=775b7aa27d0c17f48b3feff20c836f14b6ed8ebcb90ec7e0b76d2a13c579268a
aios.owner-final-merge-authorization-v2 real v2 contract tests passed
Merge PR #88: successor-aware governed release artifact producer (V2R2C3)
True merge of agent/successor-aware-governed-release-producer-20260916
(tip da0474c) into main 057e915.

Conflict resolution (4 test files):
- tests/task_plane/test_autonomous_next_child.py   -> successor-aware deterministic offline contract
- tests/task_plane/test_legacy_attachment.py       -> _APP_DIR single real app path
- tests/task_plane/test_terminal_proof_importer.py -> explicit purpose on C01 and C06
- tests/test_g7_web_surface_negative.py            -> keep main structure (cosmetic-only change)

Canary V2R2C3-LIVE-BINDING-CONVERGE re-bind: source/tree oid hoisted into
MERGED_SOURCE_OID / MERGED_TREE_OID placeholders (all-zero) pending the
post-merge commit id.

Merge performed by owner@cnnetmkt.top.
2026-09-18 18:00:32 +08:00

222 lines
7.9 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__=[str(ROOT / "active-runtime" / "app")]; 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__=[str(ROOT / "active-runtime" / "app" / "task_plane")]
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
# These stubs are only needed at import time of the runtime module. Pytest runs
# ``importlib`` mode in a single interpreter, so we must not permanently replace
# the real ``app.*`` namespace in ``sys.modules`` -- doing so breaks every later
# ``app.*`` import in the same suite (e.g. app.task_plane.schedulability). Snapshot
# and restore the affected entries around the module load.
_STUB_NAMES = [
"app",
"app.aios_gitops_job_identity",
"app.aios_single_host_authority",
"app.task_plane",
"app.task_plane.legacy_attachment",
]
def _load():
saved={name: sys.modules.get(name) for name in _STUB_NAMES}
try:
_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
finally:
for n in _STUB_NAMES:
if saved[n] is None:
sys.modules.pop(n, None)
else:
sys.modules[n]=saved[n]
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==[]