334 lines
12 KiB
Python
334 lines
12 KiB
Python
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import json
|
|
from pathlib import Path
|
|
import sqlite3
|
|
import sys
|
|
|
|
import pytest
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
sys.path.insert(0, str(ROOT / "active-runtime" / "app"))
|
|
|
|
from aios_agent_task_initial_execution_projection import (
|
|
InitialExecutionProjectionError,
|
|
InitialExecutionProjectionRequest,
|
|
InitialExecutionProjectionStore,
|
|
)
|
|
from aios_online_agent_execution import (
|
|
GovernedExecutionBinding,
|
|
project_execution_envelope,
|
|
)
|
|
import aios_task_lineage_supersession as supersession
|
|
from aios_task_lineage_supersession_initial_projection import (
|
|
PROJECTION_TABLE,
|
|
SOURCE_BUSINESS_EXECUTION_DIGEST,
|
|
SupersessionChildInitialProjectionStore,
|
|
SupersessionChildProjectionIntent,
|
|
)
|
|
from test_task_lineage_supersession import (
|
|
controller,
|
|
create_db,
|
|
decision,
|
|
snapshot,
|
|
)
|
|
|
|
|
|
AUTHORIZATION = "owner-supersession-child-initial-projection-20260905"
|
|
CAPABILITY = "agent.runtime.openclaw"
|
|
BASE_OID = "a" * 40
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _authorization(monkeypatch):
|
|
monkeypatch.setenv(
|
|
"AIOS_AGENT_TASK_INITIAL_EXECUTION_PROJECTION_AUTHORIZATION_ID",
|
|
AUTHORIZATION,
|
|
)
|
|
|
|
|
|
def _sha_json(value):
|
|
return hashlib.sha256(
|
|
json.dumps(value, sort_keys=True, separators=(",", ":")).encode()
|
|
).hexdigest()
|
|
|
|
|
|
def _apply_projection_migration(path: Path) -> None:
|
|
sql = (
|
|
ROOT
|
|
/ "active-runtime"
|
|
/ "migrations"
|
|
/ "006_task_lineage_supersession_initial_projection.sql"
|
|
).read_text()
|
|
connection = sqlite3.connect(path)
|
|
connection.executescript(sql)
|
|
connection.commit()
|
|
connection.close()
|
|
|
|
|
|
def _superseded_db(tmp_path: Path) -> tuple[Path, tuple, dict]:
|
|
path = create_db(tmp_path)
|
|
_apply_projection_migration(path)
|
|
source_before = snapshot(path)
|
|
supersession_receipt = controller(path).supersede(decision(path))
|
|
return path, source_before, supersession_receipt
|
|
|
|
|
|
def _intent(**changes):
|
|
value = {
|
|
"adapter": "openclaw",
|
|
"required_capability": CAPABILITY,
|
|
"authorization_id": AUTHORIZATION,
|
|
}
|
|
value.update(changes)
|
|
return value
|
|
|
|
|
|
def _resolver(task_id, job_id, capability, adapter, runtime_input):
|
|
binding = GovernedExecutionBinding.from_mapping(
|
|
{
|
|
"task_id": task_id,
|
|
"job_id": job_id,
|
|
"repository": supersession.REPOSITORY,
|
|
"base_oid": BASE_OID,
|
|
"expected_branch_oid": BASE_OID,
|
|
"target_branch": supersession.TARGET_BRANCH,
|
|
"branch_mode": "CREATE_NEW",
|
|
"generation": supersession.GENERATION,
|
|
"idempotency_key": "singlehost-lineage-s1-execution-envelope-v1",
|
|
}
|
|
)
|
|
return project_execution_envelope(
|
|
binding,
|
|
{
|
|
"action": "agent_runtime",
|
|
"adapter": adapter,
|
|
"capability_scope": [capability],
|
|
"runtime_input": runtime_input,
|
|
},
|
|
)
|
|
|
|
|
|
def test_real_projector_abi_derives_distinct_child_digest_and_lineage_receipt(tmp_path):
|
|
path, source_before, supersession_receipt = _superseded_db(tmp_path)
|
|
result = SupersessionChildInitialProjectionStore(path, _resolver).project(
|
|
_intent()
|
|
)
|
|
assert result["idempotent"] is False
|
|
assert result["source_business_execution_digest"] == SOURCE_BUSINESS_EXECUTION_DIGEST
|
|
assert result["canonical_child_execution_digest"] != SOURCE_BUSINESS_EXECUTION_DIGEST
|
|
assert result["owner_supersession_decision_sha256"] == supersession_receipt["owner_decision_sha256"]
|
|
assert snapshot(path) == source_before
|
|
|
|
connection = sqlite3.connect(path)
|
|
connection.row_factory = sqlite3.Row
|
|
child = connection.execute(
|
|
"SELECT * FROM tasks WHERE task_id=?", (supersession.CHILD_TASK_ID,)
|
|
).fetchone()
|
|
assert (child["status"], child["revision"], child["attempt"]) == (
|
|
"QUEUED",
|
|
2,
|
|
0,
|
|
)
|
|
assert child["claimed_by"] is None
|
|
metadata = json.loads(child["metadata_json"])
|
|
execution = metadata["execution"]
|
|
assert execution["task_id"] == supersession.CHILD_TASK_ID
|
|
assert execution["job_id"] == supersession.EXTERNAL_JOB_ID
|
|
assert execution["repository"] == supersession.REPOSITORY
|
|
assert execution["target_branch"] == supersession.TARGET_BRANCH
|
|
assert execution["generation"] == supersession.GENERATION
|
|
assert execution["execution_digest"] == result["canonical_child_execution_digest"]
|
|
assert execution["execution_digest"] != supersession.EXECUTION_DIGEST
|
|
assert execution["action"] == "agent_runtime"
|
|
assert execution["change_set"] == []
|
|
assert execution["verification"] == []
|
|
assert json.loads(child["required_capabilities_json"]) == [CAPABILITY]
|
|
|
|
events = connection.execute(
|
|
"SELECT event_type,payload_json FROM events WHERE task_id=? ORDER BY event_id",
|
|
(supersession.CHILD_TASK_ID,),
|
|
).fetchall()
|
|
assert [row["event_type"] for row in events] == [
|
|
"TASK_CREATED",
|
|
"TASK_INITIAL_EXECUTION_PROJECTION_AUTHORIZED",
|
|
]
|
|
projected = json.loads(events[-1]["payload_json"])
|
|
assert projected["source_business_execution_digest"] == supersession.EXECUTION_DIGEST
|
|
assert projected["canonical_child_execution_digest"] == execution["execution_digest"]
|
|
assert projected["owner_supersession_decision_sha256"] == supersession_receipt["owner_decision_sha256"]
|
|
|
|
durable = connection.execute(
|
|
f"SELECT * FROM {PROJECTION_TABLE} WHERE task_id=?",
|
|
(supersession.CHILD_TASK_ID,),
|
|
).fetchone()
|
|
assert durable["source_business_execution_digest"] == supersession.EXECUTION_DIGEST
|
|
assert durable["canonical_child_execution_digest"] == execution["execution_digest"]
|
|
assert durable["expected_revision"] == 1
|
|
assert durable["applied_revision"] == 2
|
|
with pytest.raises(sqlite3.IntegrityError, match="APPEND_ONLY"):
|
|
connection.execute(
|
|
f"UPDATE {PROJECTION_TABLE} SET adapter='hermes' WHERE task_id=?",
|
|
(supersession.CHILD_TASK_ID,),
|
|
)
|
|
connection.rollback()
|
|
connection.close()
|
|
|
|
|
|
def test_exact_replay_needs_no_bridge_resolution(tmp_path):
|
|
path, _, _ = _superseded_db(tmp_path)
|
|
first = SupersessionChildInitialProjectionStore(path, _resolver).project(_intent())
|
|
|
|
def unavailable(*_args):
|
|
raise AssertionError("resolver must not run for exact durable replay")
|
|
|
|
second = SupersessionChildInitialProjectionStore(path, unavailable).project(
|
|
_intent()
|
|
)
|
|
assert second["idempotent"] is True
|
|
assert second["canonical_child_execution_digest"] == first["canonical_child_execution_digest"]
|
|
|
|
|
|
def test_generic_initial_projection_cannot_project_a_formal_lineage_child(tmp_path):
|
|
path, _, _ = _superseded_db(tmp_path)
|
|
execution = _resolver(
|
|
supersession.CHILD_TASK_ID,
|
|
supersession.EXTERNAL_JOB_ID,
|
|
CAPABILITY,
|
|
"openclaw",
|
|
{
|
|
"goal": "Build trust root",
|
|
"instructions": "Use the governed writer.",
|
|
"success_criteria": [{"id": "complete", "description": "complete"}],
|
|
"constraints": {"generation": 1, "production_write": False},
|
|
"scope": {"scope_type": "legacy", "tenant_id": None, "project_id": None},
|
|
"trace_id": supersession.CHILD_TASK_ID,
|
|
},
|
|
)
|
|
request = InitialExecutionProjectionRequest.from_mapping(
|
|
{
|
|
"expected_status": "QUEUED",
|
|
"expected_revision": 1,
|
|
"expected_attempt": 0,
|
|
"expected_metadata_sha256": _sha_json({}),
|
|
"expected_required_capabilities_sha256": _sha_json([]),
|
|
"expected_job_id": supersession.EXTERNAL_JOB_ID,
|
|
"expected_execution_digest": execution["execution_digest"],
|
|
"required_capability": CAPABILITY,
|
|
"adapter": "openclaw",
|
|
"authorization_id": AUTHORIZATION,
|
|
"idempotency_key": "forbidden-generic-child-projection-v1",
|
|
"reason": "must use formal lineage projection endpoint",
|
|
}
|
|
)
|
|
with pytest.raises(
|
|
InitialExecutionProjectionError,
|
|
match="SUPERSESSION_INITIAL_PROJECTION_ENDPOINT_REQUIRED",
|
|
):
|
|
InitialExecutionProjectionStore(path, _resolver).project(
|
|
supersession.CHILD_TASK_ID, request
|
|
)
|
|
connection = sqlite3.connect(path)
|
|
child = connection.execute(
|
|
"SELECT revision,metadata_json FROM tasks WHERE task_id=?",
|
|
(supersession.CHILD_TASK_ID,),
|
|
).fetchone()
|
|
assert child == (1, "{}")
|
|
assert connection.execute(
|
|
"SELECT count(*) FROM events WHERE task_id=?",
|
|
(supersession.CHILD_TASK_ID,),
|
|
).fetchone()[0] == 1
|
|
connection.close()
|
|
|
|
|
|
def test_intent_rejects_arbitrary_child_payload_and_mismatched_adapter():
|
|
with pytest.raises(
|
|
InitialExecutionProjectionError,
|
|
match="SUPERSESSION_INITIAL_PROJECTION_FIELDS_INVALID",
|
|
):
|
|
SupersessionChildProjectionIntent.from_mapping(
|
|
{**_intent(), "child_payload": {"metadata": {"execution_digest": "x"}}}
|
|
)
|
|
with pytest.raises(
|
|
InitialExecutionProjectionError, match="RUNTIME_CAPABILITY_MISMATCH"
|
|
):
|
|
SupersessionChildProjectionIntent.from_mapping(
|
|
_intent(adapter="hermes")
|
|
)
|
|
|
|
|
|
def test_wrong_canonical_binding_fails_without_task_mutation(tmp_path):
|
|
path, _, _ = _superseded_db(tmp_path)
|
|
|
|
def wrong_binding(_task_id, job_id, capability, adapter, runtime_input):
|
|
return _resolver(
|
|
supersession.SOURCE_TASK_ID,
|
|
job_id,
|
|
capability,
|
|
adapter,
|
|
runtime_input,
|
|
)
|
|
|
|
with pytest.raises(
|
|
InitialExecutionProjectionError,
|
|
match="SUPERSESSION_CANONICAL_EXECUTION_BINDING_MISMATCH",
|
|
):
|
|
SupersessionChildInitialProjectionStore(path, wrong_binding).project(
|
|
_intent()
|
|
)
|
|
connection = sqlite3.connect(path)
|
|
child = connection.execute(
|
|
"SELECT revision,metadata_json,required_capabilities_json FROM tasks "
|
|
"WHERE task_id=?",
|
|
(supersession.CHILD_TASK_ID,),
|
|
).fetchone()
|
|
assert child == (1, "{}", "[]")
|
|
assert connection.execute(f"SELECT count(*) FROM {PROJECTION_TABLE}").fetchone()[0] == 0
|
|
connection.close()
|
|
|
|
|
|
def test_projection_receipt_failure_rolls_back_task_event_and_generic_receipt(tmp_path):
|
|
path, _, _ = _superseded_db(tmp_path)
|
|
connection = sqlite3.connect(path)
|
|
connection.execute(
|
|
f"""CREATE TRIGGER force_projection_receipt_failure
|
|
BEFORE INSERT ON {PROJECTION_TABLE} BEGIN
|
|
SELECT RAISE(ABORT, 'FORCED_PROJECTION_RECEIPT_FAILURE');
|
|
END"""
|
|
)
|
|
connection.commit()
|
|
connection.close()
|
|
with pytest.raises(sqlite3.IntegrityError, match="FORCED_PROJECTION_RECEIPT_FAILURE"):
|
|
SupersessionChildInitialProjectionStore(path, _resolver).project(_intent())
|
|
connection = sqlite3.connect(path)
|
|
child = connection.execute(
|
|
"SELECT revision,metadata_json,required_capabilities_json FROM tasks "
|
|
"WHERE task_id=?",
|
|
(supersession.CHILD_TASK_ID,),
|
|
).fetchone()
|
|
assert child == (1, "{}", "[]")
|
|
assert connection.execute(
|
|
"SELECT count(*) FROM events WHERE task_id=?",
|
|
(supersession.CHILD_TASK_ID,),
|
|
).fetchone()[0] == 1
|
|
assert connection.execute(
|
|
"SELECT count(*) FROM task_initial_execution_projections WHERE task_id=?",
|
|
(supersession.CHILD_TASK_ID,),
|
|
).fetchone()[0] == 0
|
|
assert connection.execute(f"SELECT count(*) FROM {PROJECTION_TABLE}").fetchone()[0] == 0
|
|
connection.close()
|
|
|
|
|
|
def test_projection_migration_is_idempotent(tmp_path):
|
|
path = create_db(tmp_path)
|
|
_apply_projection_migration(path)
|
|
_apply_projection_migration(path)
|
|
connection = sqlite3.connect(path)
|
|
assert connection.execute(
|
|
"SELECT count(*) FROM sqlite_master WHERE type='table' AND name=?",
|
|
(PROJECTION_TABLE,),
|
|
).fetchone()[0] == 1
|
|
connection.close()
|