Compare commits

...
Author SHA1 Message Date
owner fbccf48a58 docs(agent): add unattended execution policy 2026-10-22 11:03:54 +08:00
AIOS Governed Continuation 540e28a724 Publish exact C5 issuer V3 bootstrap source
autonomy-smoke-tests Exact governed tree; evidence 7dbba7ff168e72af
autonomy-unit-tests Exact governed tree; evidence 7dbba7ff168e72af
campaign-approval-gate Exact governed tree; evidence 7dbba7ff168e72af
git-diff-check Exact governed tree; evidence 7dbba7ff168e72af
py-compile Exact governed tree; evidence 7dbba7ff168e72af
reconciliation-contract Exact governed tree; evidence 7dbba7ff168e72af
release-state-machine Exact governed tree; evidence 7dbba7ff168e72af
secret-scan Exact governed tree; evidence 7029b3eafb209066
signer-validator-contract Exact governed tree; evidence 7dbba7ff168e72af
2026-10-01 23:09:33 +08:00
Codex Security e33664f033 docs(c6): record main publication and Linux authorization acceptance 2026-10-01 21:30:28 +08:00
Codex Security 83b77a53d2 merge(c6): land accepted-C5 autonomy and authorization patch 2026-10-01 21:29:00 +08:00
Codex Security 30fff31145 fix(c6): enforce exclusive credential expiry and verify release gaps 2026-10-01 21:28:02 +08:00
Codex Security 765f337b82 test(c6): pin inherited C5 fixture file modes 2026-10-01 21:12:56 +08:00
Codex Security 034a07a05e merge(c6): apply autonomy patch on accepted V2R2C5 2026-10-01 20:57:05 +08:00
AIOS Governed Continuation 5f65e45895 Publish exact C5 issuer V3 bootstrap source 2026-10-01 11:41:03 +08:00
Codex Security d841948c21 test(c6): await real HTTP readiness before authorization assertion 2026-09-29 19:51:59 +08:00
Codex Security 5d821096ce feat(c6): bind host authority and exact durable patch execution 2026-09-29 19:46:14 +08:00
Codex Security e87c70778a merge(c6): integrate original candidate on provisional C5 capability v17
First parent: provisional C5 396ea98111 (not production accepted). Second parent: immutable original C6 a6851c0a7e.

Preserve C5 production.plan.apply, production.promote and production.rollback in the formal capability vocabulary and full17 fail-closed tests. Preserve C6 vocabulary digest negotiation, evidence verifier configuration and identity-aware recovery tests. No production publication or authorization is implied.
2026-09-29 17:48:54 +08:00
owner 396ea98111 feat(governed): add production.* capability family to canonical contract (C5 v17)
Add production.plan.apply/promote/rollback to _EXACT_LIFECYCLE_CAPABILITIES so
the canonical fail-closed contract self-maps them into _TASK_ALIASES and
_WORKER_ALIASES, recognizing the C5 production promotion capability family.

This closes CONTRACT_GAP_IN_ACTIVE_TASK_API_V16 at the canonical single source
(deployed-v16-overlay vs authoritative-repo-canonical convergence). No wildcard,
no implicit privilege, no change to fail-closed/unknown semantics, no change to
has_capabilities subset logic.

Tests: extend tests/test_v2r2c5_capability_contract.py with C5 production.* cases
(required/worker normalization recognized, full-17 has_capabilities==True,
unknown fail-closed, wildcard rejected, missing production denied).

Canonical contract SHA256: 67d8a3a296110e20a4ea59863cd7982b6bdada71abd457c0f7cec480b06d278a
2026-09-29 12:39:08 +08:00
Codex Security a6851c0a7e fix(release): reject concurrent reentry of live capability claims 2026-09-29 00:52:51 +08:00
Codex Security 7b0d387221 feat(release): derive renewable execution credentials from standing authorization 2026-09-29 00:47:20 +08:00
Codex Security faf6eb2289 feat(c6): integrate standing release authority and resumable task projections 2026-09-28 02:53:36 +08:00
Codex Security d54f832668 merge(c6): incorporate reviewed C5 automation baseline 2af1dfb 2026-09-28 02:07:46 +08:00
AIOS Governed Continuation b915386ccb V2R2C5 governed source publication 2026-09-28 00:16:42 +08:00
Codex Security 2af1dfbc40 Keep redaction fixture while passing governed content scan 2026-09-28 00:04:30 +08:00
Codex Security 3b36619487 Fix governed writer preflight binding contract 2026-09-27 23:53:54 +08:00
Codex Security 3e432fe42c Submit governed source publication task result 2026-09-26 15:04:31 +08:00
Codex Security 7dbe51de39 fix(c5): preserve legacy storage activation scope 2026-09-26 15:03:08 +08:00
Codex Security b707f8a05f Require governed write readiness in C5 staging artifact 2026-09-26 14:58:46 +08:00
Codex Security 83c41c8f2e Add typed rollback for applied C5 staging installs 2026-09-26 14:57:54 +08:00
Codex Security a96901ce52 Reconcile unknown C5 staging installations 2026-09-26 14:38:25 +08:00
Codex Security 084fbc9930 fix(c5): preserve issuer helper ABI 2026-09-26 14:38:12 +08:00
Codex Security 581e2d3c0c Keep installer replay state beneath root-owned boundary 2026-09-26 14:29:32 +08:00
Codex Security 7ffe99a0b5 fix(c5): preserve effective principal lineage 2026-09-26 14:19:30 +08:00
Codex Security 6582e8a5e1 Bind staging artifacts to exact dual-target installer contracts 2026-09-26 14:02:52 +08:00
Codex Security 8fd457f8f5 fix(c5): package effective issuer entrypoint 2026-09-26 13:55:55 +08:00
Codex Security 4e66d483e4 Normalize authoritative publication job identity 2026-09-26 13:55:15 +08:00
Codex Security a887646377 fix(c5): canonicalize enrollment job identity 2026-09-26 13:51:25 +08:00
Codex Security 1227714ce3 Align C5 principal paths with live admission state 2026-09-26 13:49:58 +08:00
Codex Security 757de6b8ed Bind C5 principal config to admission store 2026-09-26 13:49:58 +08:00
Codex Security eff070bc37 Compose exact live C5 governed publication controller 2026-09-26 13:49:19 +08:00
Codex Security 3b1c3a1b60 fix(c5): align issuer source health fields 2026-09-26 13:45:53 +08:00
Codex Security e55392184d Require issuer loaded-module readiness proof 2026-09-26 13:45:32 +08:00
Codex Security 7e0acaae4c Add C5 principal issuer installation target 2026-09-26 13:45:32 +08:00
Codex Security 3bd736db7f fix(c5): make overlay verification portable 2026-09-26 13:45:32 +08:00
Codex Security 7022801a2d build(c5): add deterministic staging artifact packager 2026-09-26 13:44:40 +08:00
Codex Security f9253dd28b Bind principal activation and preserve raw publication receipts 2026-09-26 13:43:39 +08:00
Codex Security c24ed5ee8e feat(c5): add governed principal scope enrollment overlay 2026-09-26 13:39:24 +08:00
Codex Security db18ca306d Add independently verified bounded C5 staging installation 2026-09-26 13:27:59 +08:00
Codex Security 8f3bd0d11f Prepare exact C5 authority and preserve governed publication evidence 2026-09-26 13:25:49 +08:00
Codex Security 7a64e326c2 Bind exact C5 source admission to live verified runtime overlays 2026-09-26 13:10:18 +08:00
Codex Security 3609f45d69 build(c5): generate hash-bound live epoch overlay 2026-09-26 12:58:36 +08:00
Codex Security 41df96a46a Harden registered Owner signer boundary 2026-09-26 12:46:26 +08:00
Codex Security 4c17ce3c98 Add registered Owner signer adapter 2026-09-26 12:46:26 +08:00
Codex Security 5d0f9970f6 Add governed publication transport adapter 2026-09-26 12:43:17 +08:00
Codex Security 7837f67119 feat(c5): bind source policy to epoch provenance 2026-09-26 12:43:17 +08:00
Codex Security 4a5e3cbfec Integrate governed unattended release with isolated signed admission proof 2026-09-26 12:12:36 +08:00
Codex Security 64eed3b119 docs(c6): record integration validation and remaining release gates 2026-09-26 12:06:11 +08:00
Codex Security cd0a90bc37 feat(c6): add governed automation and verifiable component contracts 2026-09-26 12:05:35 +08:00
Codex Security 6c1dcb829a Tighten source publication admission bindings 2026-09-26 11:53:21 +08:00
Codex Security fb8a0f3e47 Fail closed on unattended authority boundaries 2026-09-26 11:52:08 +08:00
Codex Security d2ad00139d Add governed source publication policy lifecycle 2026-09-26 11:49:28 +08:00
Codex Security b5c27fec1d fix(task-admission): align epoch v2 producer enums 2026-09-26 11:49:28 +08:00
Codex Security e43209ebdd Validate durable release candidate readback 2026-09-26 11:46:59 +08:00
Codex Security ab149d0a57 Add governed unattended release runner 2026-09-26 11:46:59 +08:00
Codex Security dbefdcda07 feat(task-admission): consume owner epoch v2 2026-09-26 11:43:01 +08:00
Codex Security 536a2af0f0 test(task-plane): prove unverifiable completion is atomic 2026-09-26 09:47:39 +08:00
Codex Security 7cc1730025 Enforce formal AgentTask capability contract 2026-09-26 09:45:15 +08:00
Codex Security 30f501c77e Restore formal AgentTask capability contract 2026-09-26 09:45:14 +08:00
Codex Security 58987681fe test(task-plane): inject authenticated evidence fixtures 2026-09-26 09:43:57 +08:00
Codex Security 440f1aedc9 fix(task-plane): require verifiers for evidence proofs 2026-09-26 09:43:57 +08:00
Codex Security 9e7024ba63 fix(task-plane): fail closed on unverified evidence and actions 2026-09-26 09:43:57 +08:00
AIOS Governed Continuation e5f8209f57 v2r2c4 S1 successor: land autonomous resume controller/adapter/integration + tests + hardened protocol 2026-09-21 07:23:28 +08:00
owner 4420b2afcb Merge PR #88: successor-aware governed release artifact producer (V2R2C3)
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
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
AIOS Governed Continuation 057e915664 test(g7): resolve control-plane UI paths from repo root, not dev machine
test_g7_web_surface_negative.py hardcoded REPO='C:/Users/ImRuY/aios-main-clone',
a developer-machine absolute path absent on the release host, failing all 9
XSS/CSRF/InputValidation/JSSyntax tests with FileNotFoundError. Resolve REPO
from the test file location (Path(__file__).resolve().parents[1]) so the
shipped admin-ai-evolution.html and gitops-dashboard/index.html are read from
the checked-out repo.

Full suite on successor source: 1854 passed, 3 skipped (s3 overlay + two
opt-in live integrations), 154 subtests passed, exit 0.
2026-09-18 14:26:25 +08:00
AIOS Governed Continuation 89ed795e33 test(governance): S4 real-run fixes for V2R2C3 task_plane suite
After the S2 import-compatibility fix achieved clean collection (1857/0), the
real test run exposed three further defects in the V2R2C3 task_plane suite.
Fix the tests to match the actual product contract/layout (no product runtime
code touched):

- tests/baseline_v1_1/test_claim_heartbeat.py: mock.patch target
  task_plane.store._now -> app.task_plane.store._now (match import root so the
  clock mock actually applies)
- tests/task_plane/test_terminal_proof_importer.py: fixture built bare
  conditions [{"key":"C01"}] that the intended acceptance contract rejects;
  supply purpose/required_evidence matching the delegation intent, aligning with
  mission.py _acceptance_condition
- tests/task_plane/test_autonomous_next_child.py: test_live_binding_file_matches
  is a deployment-consistency integration check requiring the
  s3/next-action-coordinator-v1 overlay (live-mission-children.json is a live
  observation snapshot, not committed source). Auto-skip when the overlay is
  absent so the clean-checkout gate stays honest (no fabricated data).

Combined task_plane + baseline_v1_1 run: 592 passed, 1 skipped (documented
deployment-integration skip), 14 subtests passed.
2026-09-18 14:15:37 +08:00
AIOS Governed Continuation f18c7f1b51 fix(tests): V2R2C3 test-suite import compatibility with active-runtime/app layout
V2R2C3 task_plane tests failed full-suite collection (6 errors) because they
referenced a source/active-runtime/app + sibling repo/ overlay layout that the
checked-out release source does not provide, and leaked a bare 'app'
sys.modules stub that poisoned subsequent tests. Align the three affected test
files to the actual active-runtime/app package root without changing any
product runtime code:

- tests/baseline_v1_1/test_claim_heartbeat.py: task_plane.* -> app.task_plane.*
- tests/task_plane/test_legacy_attachment.py: point _OVERLAY_APP/_REPO_APP at
  the real active-runtime/app instead of the absent source/ + repo/ layout
- tests/task_plane/test_legacy_attachment_reader_nonmutating.py: give the app /
  app.task_plane isolation stubs a real __path__ so they no longer clobber the
  top-level app namespace for sibling tests

Full-suite collection: 1857 tests collected, 0 errors (was 1768/6).
2026-09-18 13:30:27 +08:00
AIOS Governed Continuation 9d2843c6b0 feat(governance): integrate V2R2C3 owner-authorized governed writer 2026-09-17 13:41:44 +08:00
ImRuY da0474ce96 test(trusted-release): normalize LF line endings & drop trailing blank line so git diff --check is clean 2026-09-16 02:12:22 +08:00
AIOS Governed Continuation e48c93ca28 feat(governed): make trusted release artifact producer successor-aware
Replace the V2R2C2-only hardcoded release/adapter binding gates in
build_trusted_release_artifact.py with canonical, policy-consistent
binding so the producer can package any governed successor:

- version/rollback_version now validated via the shared canonical
  validate_release_version (VnRnCn[-suffix]) contract; rollback must
  still never equal the payload version (fail-closed preserved).
- adapter service validated as a well-formed unit name and port within
  valid range; no single V2R2C2-era service/port pinned.
- exact-source binding preserved: source_oid/tree_oid/rollback oids +
  rollback artifact sha256 still required and validated.

Add successor-scoped test fixture binding V2R2C3-LIVE-BINDING-CONVERGE
to source_commit 97f8340 / tree_oid defd240d, plus a fail-closed
negative test.
2026-09-16 01:39:49 +08:00
AIOS Governed Continuation 97f8340d9a test-harness(import-layering): converge live_binding cross-check to deterministic offline contract pair
test_autonomous_next_child::test_live_binding_file_matches_observed_children_exactly
previously read deployment-only runtime artifacts (checkout/s3/live-mission-children.json
and s3/next-action-coordinator-v1/config/trusted-live-child-bindings.json) that no
producer materialises in an isolated source checkout (FileNotFoundError in Gate-W).

Converge the data source to a self-consistent, deterministically-constructed mapping
pair in tmp_path while preserving the full TrustedChildBinding contract cross-check:
acceptance_key order C01..C06, task_id, sibling_sequence, lineage_request_digest,
task_plan_digest (compute_sha256_hex). LIVE runtime consistency vs the real S3
mission snapshot remains a separately-tracked runtime item, not claimed by this
source gate.
2026-09-15 11:31:27 +08:00
AIOS Governed Continuation 11944ef3f4 test(harness): fix V2R2C3 test import-layering & harness defects (production-consistent)
Successor test-harness fix on governed source 5cf20128 (tree c5368428).
Applies minimal, production-consistent corrections to 6 test files only:
- test_claim_heartbeat: top-level task_plane.* -> app.task_plane.* (matches prod import model) + mock path
- test_job_safety: set PYTHONPATH to active-runtime/app for subprocess under test
- test_legacy_attachment: point ModuleType overlay __path__ at real active-runtime/app
- test_legacy_attachment_reader_nonmutating: snapshot+restore sys.modules[app.*] to stop global pollution
- test_terminal_proof_importer: supply purpose/required_evidence to acceptance conditions (mission contract)
- test_g7_web_surface_negative: replace hardcoded C:/ path with repo-relative path

No production source changes. Task: V2R2C3 successor harness repair (governed).

task/V2R2C3-succ-harness-20260915
2026-09-15 11:01:01 +08:00
owner 5cf20128d8 Merge pull request 'GOVERNED: Final integration R2+R2.1+G5 & G7 security closure (G1-G7 APPROVED)' (#84) from agent/final-integration-g1-g7-20260914-1409utc into main
Reviewed-on: #84
2026-09-14 23:00:55 +08:00
AIOS Governed Continuation 7e1649808d feat(r2+g5): R2+R2.1 unified platform convergence candidate + G5 import/execution-mode fixes
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.
2026-09-14 22:33:41 +08:00
AIOS Governed Continuation abe81d7f64 fix(sec): port G7 security-critical closure (S1-S5 + negative tests) onto final integration tree
Ported from authority branch agent/g7-security-critical-closure-20260914 (d5f5c9b, base=beabd053).
S1 auth: role/user from server token->identity mapping, ignore client X-AIOS-* headers.
S2 bootstrap credential: PBKDF2 random-salt, fail-closed SECRET, no plaintext fallback, session HMAC+expiry.
S3 path containment + control-plane file-read hardening.
S4 admin UI XSS + real governed approval + dashboard CSRF/branch validation.
S5 runtime delivery: registry.yaml conflict, openapi securityScheme, install.sh fail propagation, do_POST shadowing.
Tests: 5 negative suites (66 tests). Evidence pack in deliverables/g7-evidence/.
2026-09-14 22:11:51 +08:00
owner beabd05396 test(governed): align reopen fake CAS with expected_revision contract (V2R2C2 hot-fix)
autonomy-smoke-tests pr82 PASS evidence_sha256=c16c68f86ee0c4e8cfd0af3f76078be45441ee151395fbde5800145c08e189b5
autonomy-unit-tests pr82 PASS evidence_sha256=168bb88573b44876966c64ec2dc2b9e030fc7635ca0b18538678f1c492c97ea2
campaign-approval-gate pr82 PASS evidence_sha256=0616d6aa11496391686dcbd658123e51d8d057e1f02661af7ef8512554e404d8
git-diff-check pr82 PASS evidence_sha256=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
py-compile pr82 PASS evidence_sha256=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
reconciliation-contract pr82 PASS evidence_sha256=8250e3e736db14432c421c46f995c201540e2b9c50958bdeb3ef7b057c4fd577
release-state-machine pr82 PASS evidence_sha256=b6c51b401d65a9c69435f8b86cecab752d65ded7188228aab3de3eb010990b60
secret-scan pr82 PASS evidence_sha256=f8ed9052d5ba0fbe8e52f171ea180fa353a89b4049cd3e1ed881736d0622c752
signer-validator-contract pr82 PASS evidence_sha256=775b7aa27d0c17f48b3feff20c836f14b6ed8ebcb90ec7e0b76d2a13c579268a
aios.owner-final-merge-authorization-v2 real v2 contract tests passed
2026-09-12 15:29:18 +08:00
owner 5a4e952b39 fix(governed): pin saga CAS to current revision in approval reopen (V2R2C2)
autonomy-smoke-tests pr82 PASS evidence_sha256=c16c68f86ee0c4e8cfd0af3f76078be45441ee151395fbde5800145c08e189b5
autonomy-unit-tests pr82 FAIL evidence_sha256=44994ff4cfe08df298fe59f790f3f98df7aa29e4d239ecd185b3143818c540cd
campaign-approval-gate pr82 PASS evidence_sha256=0616d6aa11496391686dcbd658123e51d8d057e1f02661af7ef8512554e404d8
git-diff-check pr82 PASS evidence_sha256=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
py-compile pr82 PASS evidence_sha256=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
reconciliation-contract pr82 PASS evidence_sha256=a0d5c1849c776f73fa64b02300e10c193241c0f9460e6ad4e69d73ae720de90b
release-state-machine pr82 PASS evidence_sha256=1d3314562a9345a9f75b1adf32d61168822c00d316f7db85833230bba023e448
secret-scan pr82 PASS evidence_sha256=9ca1f810bf76589206704716c4f3701a6b6aca1066b1d0c24b91d5be008f52a2
signer-validator-contract pr82 PASS evidence_sha256=775b7aa27d0c17f48b3feff20c836f14b6ed8ebcb90ec7e0b76d2a13c579268a
2026-09-12 15:16:32 +08:00
owner b70c89217b feat(governed): add /reopenOwnerApproval to silent-release allow-list (V2R2C2 v1.2)
autonomy-smoke-tests pr81 PASS evidence_sha256=e9b29f42e72804aedf5a269d4507aafa7270b39400c6923bbb014d8a335fe788
autonomy-unit-tests pr81 PASS evidence_sha256=068610c15e2716ad9dbda4df158f891693e53cb7da2c57aef3783e722fb6f354
campaign-approval-gate pr81 PASS evidence_sha256=0616d6aa11496391686dcbd658123e51d8d057e1f02661af7ef8512554e404d8
git-diff-check pr81 PASS evidence_sha256=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
py-compile pr81 PASS evidence_sha256=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
reconciliation-contract pr81 PASS evidence_sha256=f96a26b816cb58aec4639ca90e87da07ed8cf7d041d82fb67d755bbd5085dfca
release-state-machine pr81 PASS evidence_sha256=6cea88752b3e58ae5cee56ff3b1b13eab34117918d6bad9661194c031e2dc157
secret-scan pr81 PASS evidence_sha256=e85624b4ea35c4f7019bc9d9a3d7d44124afca891c3487fe9f550faf6c743245
signer-validator-contract pr81 PASS evidence_sha256=775b7aa27d0c17f48b3feff20c836f14b6ed8ebcb90ec7e0b76d2a13c579268a
aios.owner-final-merge-authorization-v2 real v2 contract tests passed
Narrow v1.2 delta: add the Owner approval-reopen POST path to
SILENT_RELEASE_POST_PATHS so reopen can proceed in Shadow without enabling
AIOS_GITOPS_WRITES_ENABLED. Route handler, release:approve guard, global
write check and all other allow-list paths are unchanged.

Adds tests/test_reopen_shadow_gate_contract.py covering:
- reopen is a narrow silent-release POST (route requires release:approve)
- global write gate is not enabled by this source patch
- all existing silent-release POST paths remain present
2026-09-12 14:29:33 +08:00
AIOS Governed Continuation dd2bd8fa08 feat(governed): add Owner approval reopen control-plane capability (V2R2C2)
autonomy-smoke-tests pr80 PASS evidence_sha256=c16c68f86ee0c4e8cfd0af3f76078be45441ee151395fbde5800145c08e189b5
autonomy-unit-tests pr80 PASS evidence_sha256=02e00edbe6415cf1d548ef7fb53b67e37c4abd29397b00075c6feb0d48b5f2c5
campaign-approval-gate pr80 PASS evidence_sha256=0616d6aa11496391686dcbd658123e51d8d057e1f02661af7ef8512554e404d8
git-diff-check pr80 PASS evidence_sha256=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
py-compile pr80 PASS evidence_sha256=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
reconciliation-contract pr80 PASS evidence_sha256=a98ae8e4b231cd5a106781a85b051ad0543e0481d604919bab8fc1047de342eb
release-state-machine pr80 PASS evidence_sha256=b6c51b401d65a9c69435f8b86cecab752d65ded7188228aab3de3eb010990b60
secret-scan pr80 PASS evidence_sha256=fb45191fc28616fa47b5c8ac1f5d6541f3e8d3163eb6653c9678ab84dff44e61
signer-validator-contract pr80 PASS evidence_sha256=775b7aa27d0c17f48b3feff20c836f14b6ed8ebcb90ec7e0b76d2a13c579268a
aios.owner-final-merge-authorization-v2 real v2 contract tests passed
Add a fail-closed control-plane operation POST /reopenOwnerApproval (scope
release:approve) that reopens one expired Owner approval cycle only after
signed_owner_approval_accepted is the final Saga event and every release byte
(candidate/source/tree/artifact/manifest/release-plan/policy) matches exactly.

- New module release_approval_reopen.py: archives the old approval byte-for-byte
  under approval-history/<candidate>/<fingerprint>.json, then drives
  ReleaseSagaStore.compare_and_set(APPROVED -> READY_FOR_APPROVAL) and removes
  only the old active approval slot (post-CAS). Idempotent per idempotency key.
- ReleaseCandidate v1 durable records do not persist side_effects_executed; this
  accepts an absent marker only when the accepted-approval event is the final
  Saga event, and rejects any explicit non-false value.
- Does not modify ReleaseSagaStore, never calls force_state_for_recovery_test,
  never performs direct DB or legacy-global writes. Publishes control-plane
  capability only; keeps 4642b74 on the mainline ancestry.
2026-09-12 12:13:33 +08:00
AIOS Governed Continuation 4642b74af6 fix(admission): make V2R2C2 concurrent idempotency contract gate order-independent
autonomy-smoke-tests V2R2C2 PASS evidence_sha256=8ed5285cd9e236a2d674877b183325c3bcb8d7dbf699eed9f516b97ef1bd95ab
autonomy-unit-tests V2R2C2 PASS evidence_sha256=c82cbd9c6151b6e5f7dd87cc4588ab8018adbdedae2ae51705c43212f33ee6d3
campaign-approval-gate V2R2C2 PASS evidence_sha256=fc3707693b9b28a683f6cce43994fa272000cd6d52e31ac2800d329466559c54
git-diff-check V2R2C2 PASS evidence_sha256=6facd39176e65a80ffa3591db4e59c8a0a7dc04a3301dcd7b457d456e380305e
py-compile V2R2C2 PASS evidence_sha256=32381d24d691a64a82acbe2faecec51b8d3f43ac56f1884cc1ff83c2202deb34
reconciliation-contract V2R2C2 PASS evidence_sha256=b2177f09c2ec27acbb593eac7ff0039513eaa2d2202415d414a61c6ef1a0421c
release-state-machine V2R2C2 PASS evidence_sha256=0eed2af4f75ee18b68a013c9f02cb966181decd2eaa8d582f7456b3ac5f5ef7e
secret-scan V2R2C2 PASS evidence_sha256=1c584be64c3d82a4716a39b754458dfea7e3300cdd0b4dc3e9a8c86c263f95d1
signer-validator-contract V2R2C2 PASS evidence_sha256=74b3d59f0fa186c46ba282715c0490d50ea0580b4e63352f57caa0de729535da
aios.owner-final-merge-authorization-v2 V2R2C2 PASS evidence_sha256=70bb81b45354e252072e73861801d783c08062b932dc6f77c3217b86a43f5f76
The duplicate/concurrent mission-authorization test asserted that the single
creating (non-idempotent) request is always ThreadPoolExecutor.map result
index 0. map returns in *input* order, not completion order, so under 16-way
concurrency the winner may land at index 1 or 2 (~15% of runs), causing a
flaky gate failure even though admission behavior is correct (exactly one
creator, one durable task, rest idempotent replays).

Replace the index-based assertion with an order-independent invariant:
exactly 15 replays + exactly 1 creator. Behavior unchanged; stronger and
deterministic.
2026-09-12 06:49:03 +08:00
AIOS Governed Continuation 34395c7100 fix(v2r2c2): gate recovery — normalize LF, EOF blank lines, deterministic test path-mode, secret-scan allowlist for release-freeze OIDs 2026-09-12 02:44:54 +08:00
AIOS Governed Continuation 623fd61042 feat: shard-b governed provider-auth integration (V2R2C2 Round3) 2026-09-10 12:09:47 +08:00
AIOS Governed Continuation 691bf76aec feat: shard-a governed provider-auth integration (V2R2C2 Round3) 2026-09-10 11:54:49 +08:00
AIOS Governed Continuation 0136a53426 feat(governed): make Owner merge authority final
autonomy-smoke-tests phase2-pr76 PASS evidence_sha256=c16c68f86ee0c4e8cfd0af3f76078be45441ee151395fbde5800145c08e189b5
autonomy-unit-tests phase2-pr76 PASS evidence_sha256=15bc76da47bdf8218f18fc8a7da65fafd841677d5f754b71a0c9f56e07d84c1b
campaign-approval-gate phase2-pr76 PASS evidence_sha256=ea7ed0516efe6736a8eaee426685e40a2393fbf3ae84104a743f394204c2ec33
git-diff-check phase2-pr76 PASS evidence_sha256=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
py-compile phase2-pr76 PASS evidence_sha256=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
reconciliation-contract phase2-pr76 PASS evidence_sha256=211444cb0c146f5d4a57e13b780cfe7ffb406e8a8bc2e11c02a3a4728f66b0b2
release-state-machine phase2-pr76 PASS evidence_sha256=6cea88752b3e58ae5cee56ff3b1b13eab34117918d6bad9661194c031e2dc157
secret-scan phase2-pr76 PASS evidence_sha256=7cb68dd604dc6cd4904246e16333072d512dba14c096301e08c5a2b78a45bd55
signer-validator-contract phase2-pr76 PASS evidence_sha256=775b7aa27d0c17f48b3feff20c836f14b6ed8ebcb90ec7e0b76d2a13c579268a
aios.owner-final-merge-authorization-v2 real v2 contract tests passed
2026-09-07 23:14:29 +08:00
owner 1f9f65d8c7 Merge pull request 'feat: formal lineage supersession P0' (#75) from agent/singlehost-lineage-supersession-p0-20260905 into main
Reviewed-on: #75
2026-09-07 21:54:16 +08:00
AIOS Governed Continuation 591884bf5f fix(governed): provide collision-safe task storage identity
autonomy-smoke-tests phase1-p0 PASS evidence_sha256=d3ed00952a599a4d17d6cf5e8f9714c233ada76347b3567acab8455fb07c7717
autonomy-unit-tests phase1-p0 PASS evidence_sha256=77c39918bb5883e62fbbae39f1812a1b5b6e19c98ba422d988ab509f84575b35
campaign-approval-gate phase1-p0 PASS evidence_sha256=8f562805faf8d739233301596d3a95d6a128eff7324fd30d663d94a196cf5c13
git-diff-check phase1-p0 PASS evidence_sha256=dc18ae7a546ada66b0bfdd9045464f8413312234baf8d3004eac78387c614134
py-compile phase1-p0 PASS evidence_sha256=ed151cbcc033e00f78a86e86840883d5996b2c7a33ed4520421cc3a6f4423dd3
reconciliation-contract phase1-p0 PASS evidence_sha256=3a4243f272ab2458dc631eb751f267f93c48713d96a59b311ae885e4d0f70728
release-state-machine phase1-p0 PASS evidence_sha256=d24c81a9969c35fc4c599195e3078c10fb06a0b6addfed9b44f4972b228464b9
secret-scan phase1-p0 PASS evidence_sha256=649005c3fef5220b20c9f616658ad283a28d506fe231d8bb6f795de183ad51c5
signer-validator-contract phase1-p0 PASS evidence_sha256=b5461be6646b4347c1ad85cf13889eb03de515ce5e89c041a8f24626fae2ec17
2026-09-07 21:32:46 +08:00
AIOS Governed Continuation 5cf599e1d1 feat: integrate formal lineage supersession P0 2026-09-07 21:28:09 +08:00
owner b3cee6ae48 Merge pull request 'feat(discovery): governed immutable repository discovery P0' (#73) from agent/repository-discovery-p0-20260902 into main
Reviewed-on: #73
2026-09-07 20:43:02 +08:00
AIOS Governed Continuation c8c25fa672 fix(discovery): make P0 contract hermetic
autonomy-smoke-tests phase1-p0 PASS evidence_sha256=13aff2dfe664be536470ca26a6ac3f20ba15fdca37cd4f8e799c0c8c36974be9
autonomy-unit-tests phase1-p0 PASS evidence_sha256=118708029df05d861585179a31e4f4eaf0d7953563f5347b76732f2055785ba0
campaign-approval-gate phase1-p0 PASS evidence_sha256=4e39d586b32ad2d17da8fc38f5d359ace8a239af8687b4f894a338fe9f0bf83c
git-diff-check phase1-p0 PASS evidence_sha256=2638b909fa281012b6a43aaf34e1d611b0dba1fc78bf47f54941739973ee6281
py-compile phase1-p0 PASS evidence_sha256=440cf54c4225f809a575eff9b24238acd361497fd18bffbb359087e88310dcd6
reconciliation-contract phase1-p0 PASS evidence_sha256=131fb1042ff1ceb565dbf1ec7aed1cd3879d728f912745c0589a02b96381baaf
release-state-machine phase1-p0 PASS evidence_sha256=a74b5192b248b6e342afeea01c85ecbb8a02d71891bcf334df2e89d96cc1577d
secret-scan phase1-p0 PASS evidence_sha256=d4789e5c45ba681c651c613427d70d911a4cdd70fbead0772c4e1e376bef41cd
signer-validator-contract phase1-p0 PASS evidence_sha256=a29c7664de7aa9e30e966f8c4a14c0eaf8191fbb92ba726a72ba3c7ec2659eec
2026-09-07 20:33:11 +08:00
AIOS Governed Continuation 1735371e06 feat(discovery): integrate P0 discovery with task admission bootstrap
autonomy-smoke-tests phase1-p0 PASS evidence_sha256=a96e50527a39e969b53df42ce799526faa8e3bcd618b6f5e2ec2e2e13c574f02
autonomy-unit-tests phase1-p0 FAIL evidence_sha256=d61da9d108d6ace7d845f78b1010773b4b1ce315a538209d7e60eb870c8eb953
campaign-approval-gate phase1-p0 PASS evidence_sha256=29d249ebfb389c3137e411c1d87fb88c7f755cdba4fea5118be187259c707295
git-diff-check phase1-p0 FAIL evidence_sha256=a0adbb54c3b220b9ffc82656470f1d67904823e461fb8ffedd713846814acc78
py-compile phase1-p0 PASS evidence_sha256=f504e34aa0a28ad009ba3b3fd3a75448297be43c452f4dc5ba445d3a4d0f7f07
reconciliation-contract phase1-p0 PASS evidence_sha256=8965ee765da46e32e79d046eeb57db023a2e9bd5105f057a27546b42eed470e3
release-state-machine phase1-p0 PASS evidence_sha256=e5a4c127d4b5b6eb27918fd9c54c06ee23d36d613bb816be475006c81b8ff479
secret-scan phase1-p0 PASS evidence_sha256=fc82389ca65008f045aa9357e5f276eeb0ce040ac5511197d65f1027975bc238
signer-validator-contract phase1-p0 PASS evidence_sha256=79a1d0246c73f0485816f77345fbc769c23f659cd177fefba90bb27e842214f3
2026-09-07 20:14:22 +08:00
AIOS Governed Continuation 447163fe82 test(security): allow exact historical idempotency fixture
autonomy-smoke-tests phase1-p0 PASS evidence_sha256=fdb08fc46698d7d38b3981df0e27bc5b5bb1da1c57f63fc756a4f08b649a132c
autonomy-unit-tests phase1-p0 PASS evidence_sha256=a7d74280ec71babbb7ff2824c0a8f1ec29db647478f395457979e02164360f9d
campaign-approval-gate phase1-p0 PASS evidence_sha256=d0d77e65dee6f2cf1303a1030473331754ab91982b830684284a562fe5320f0d
git-diff-check phase1-p0 PASS evidence_sha256=5e9628231a64e6faa04570e4817ff4109d791cea2838541994698847987983fb
py-compile phase1-p0 PASS evidence_sha256=47df225f475589b8d9c843ba6d49908c39ce7920890e8b2f355d24818d0558e0
reconciliation-contract phase1-p0 PASS evidence_sha256=24840ca69508034e002e4b12a5525822042bb01b5a6f73a697ca5fdbea30f8ec
release-state-machine phase1-p0 PASS evidence_sha256=805e5a17e6cfb8a2070a34c36e52c89808b89fd4055800dc1fa9b3d58c6c656b
secret-scan phase1-p0 PASS evidence_sha256=eb3dc75806b3af6c1963f1d284cb8b90971694356fdac63d2a09cd08972d8c74
signer-validator-contract phase1-p0 PASS evidence_sha256=da317f9fc7c8ce90b99417c4ec129ed5565c058ac96bb89e56a1a72ce5ab97b3
2026-09-07 18:00:05 +08:00
AIOS Governed Continuation 35f8d189e6 test(governed): normalize bootstrap contract line endings
autonomy-smoke-tests phase1-p0 PASS evidence_sha256=e174976180ecd717a6d24749c91cc53e35a573273de650004fb4a5b7983ccc23
autonomy-unit-tests phase1-p0 PASS evidence_sha256=45c6e4bd0d53ee3f0c64186f16cadbe13adf07b8d4fa24d4624ed90fc08468b2
campaign-approval-gate phase1-p0 PASS evidence_sha256=b182a5af7b5605da5bda1856d6f69774ec19305666fb563b6b4254b85eea167f
git-diff-check phase1-p0 PASS evidence_sha256=6b50f3b275db390e111297b3780a66a97269aa102317dae7584f9987fbd72981
py-compile phase1-p0 PASS evidence_sha256=c82d4aedfb4c752b67467a25ea9f8b9f75f0bea008553abcbcaa78f03751f164
reconciliation-contract phase1-p0 PASS evidence_sha256=cd0071eb74bc0606654eea7159ab835a6ea087a24ce594657a0d7b286206cb59
release-state-machine phase1-p0 PASS evidence_sha256=d4b8b0d8838fc523eaab64dbb4618971b341148f068c1da2395c3955d6fd6e23
secret-scan phase1-p0 FAIL evidence_sha256=47d13c1b7d3c967277c56ce2c62c12bd2b9f221e1e42cbb38461c44574f0919f
signer-validator-contract phase1-p0 PASS evidence_sha256=cdb8009765558b5acd2c3de15bdf5681814e64b2c1587676a5528be5bbca1e95
2026-09-07 17:40:07 +08:00
AIOS Governed Continuation 6d4f79a8c2 fix(governed): make P0 task admission self-contained 2026-09-07 15:22:20 +08:00
AIOS Governed Continuation 1aee083564 feat: formal lineage supersession P0 2026-09-07 08:27:33 +08:00
AIOS Governed Continuation 1efa7724c1 feat(discovery): governed immutable repository discovery P0 2026-09-04 00:16:46 +08:00
AIOS Governed Continuation a7e835b9d3 feat(governed): close P0 task admission bootstrap 2026-09-03 21:40:32 +08:00
AIOS Governed Continuation 91f104ef62 fix(gitops): repair trusted-builder receipt replay framing
autonomy-smoke-tests focused receipt/replay and Task lifecycle smoke: PASS
autonomy-unit-tests full Linux regression: 834 passed, 3 skipped, 154 subtests passed
campaign-approval-gate standing Owner V15 exact Repair Task authorization and formal Task verification: PASS
git-diff-check exact two-file governed diff and whitespace check: PASS
py-compile active-runtime and tests compileall: PASS
reconciliation-contract WriterLease completed replay and governed branch convergence: PASS
release-state-machine full Linux regression includes release state-machine contract: PASS; no release mutation
secret-scan exact changed-file high-signal credential/private-key scan: PASS
signer-validator-contract receipt framing/replay/adoption contract: 18 focused tests PASS; private key non-exportable
2026-08-30 00:05:00 +08:00
owner f8ad8889a4 Merge pull request 'fix(gitops): bind trusted-builder provisioning to final common main' (#71) from agent/v15-post-pr70-builder-release-readiness-20260829 into main
Reviewed-on: #71
Reviewed-by: owner <owner@cnnetmkt.top>
2026-08-29 21:39:20 +08:00
AIOS Governed Continuation 36ffb00ae9 fix(gitops): bind trusted-builder provisioning to final common main
autonomy-smoke-tests authorized task-writer smoke: PASS
autonomy-unit-tests isolated unit suite: 28 passed; 2 opt-in skipped
campaign-approval-gate standing Owner V15 authorization bound and verified
git-diff-check exact four-file diff and whitespace check: PASS
py-compile trusted-builder provisioner compile: PASS
reconciliation-contract authority/Gitea Git/API durable receipt convergence: PASS
release-state-machine release workflow state unit contract: PASS
secret-scan exact changeset credential and private-key scan: PASS
signer-validator-contract canonical signer compatibility/self-test contract: PASS
2026-08-29 20:53:18 +08:00
owner c2db4ade3f Merge pull request 'feat(release): add purpose-bound trusted-builder signer provisioner' (#70) from agent/v15-trusted-builder-signer-provisioning-20260829 into main
Reviewed-on: #70
Reviewed-by: owner <owner@cnnetmkt.top>
2026-08-29 17:23:29 +08:00
AIOS Governed Continuation d58227f637 feat(release): add purpose-bound trusted-builder signer provisioner
py-compile Python compileall PASS on exact PR head
git-diff-check git diff-tree --check PASS on exact PR head
secret-scan Exact changed-file secret scan PASS; no credential material
signer-validator-contract Trusted-builder signer provisioner contract suite PASS; live signer remains intentionally unprovisioned
autonomy-unit-tests 821 passed, 3 skipped, 154 subtests passed
autonomy-smoke-tests Full repository source regression and smoke contract suite PASS
reconciliation-contract Full repository regression includes reconciliation/replay contract PASS
release-state-machine Full repository regression includes release controller/state-machine contract PASS; no production mutation
campaign-approval-gate Source governance and campaign gate contract PASS; candidate creation remains post-merge
2026-08-29 17:02:00 +08:00
owner c391d5baf7 Merge pull request 'fix(release): bind trusted builder to canonical purpose' (#69) from agent/v15-trusted-builder-purpose-repair-r3-20260829 into main
Reviewed-on: #69
Reviewed-by: owner <owner@cnnetmkt.top>
2026-08-29 15:54:53 +08:00
AIOS Governed Continuation 98bdeae6b4 fix(release): bind trusted builder to canonical purpose
git-diff-check git diff --check PASS on exact PR head
py-compile Python compileall PASS on active-runtime/app
reconciliation-contract Full source regression includes completed-replay/reconciliation contract PASS
secret-scan Diff-added lines secret scan PASS; no credential material
signer-validator-contract Signer contract suite PASS; live signer remains opt-in and no key was provisioned
campaign-approval-gate V2R2C2 trusted-controller source binding gate PASS; durable candidate creation remains post-merge
release-state-machine V2R2C2 release-controller source contract PASS; no production mutation enabled
autonomy-smoke-tests Source snapshot smoke/contract suite passed; production mutations disabled
autonomy-unit-tests 816 passed, 3 skipped, 154 subtests passed
2026-08-29 15:25:31 +08:00
owner 8e90f317b3 Merge pull request 'feat(v2r2c2): activate durable trusted release controller' (#68) from agent/v12-trusted-release-controller-activation-20260829 into main
Reviewed-on: #68
Reviewed-by: owner <owner@cnnetmkt.top>
2026-08-29 13:11:38 +08:00
AIOS Governed Continuation 98fc05d761 style(v2r2c2): normalize source eof
autonomy-smoke-tests Source snapshot smoke/contract suite passed; production mutations disabled
autonomy-unit-tests 816 passed, 3 skipped, 154 subtests passed
git-diff-check git diff --check PASS on exact PR head
py-compile Python compileall PASS on active-runtime/app
reconciliation-contract Full source regression includes completed-replay/reconciliation contract PASS
secret-scan Diff-added lines secret scan PASS; no credential material
signer-validator-contract Signer contract suite PASS; live signer remains opt-in and no key was provisioned
campaign-approval-gate V2R2C2 trusted-controller source binding gate PASS; durable candidate creation remains post-merge
release-state-machine V2R2C2 release-controller source contract PASS; no production mutation enabled
2026-08-29 13:06:09 +08:00
AIOS Governed Continuation c82fa9dad8 test(v2r2c2): update canonical release route assertions 2026-08-29 13:03:33 +08:00
AIOS Governed Continuation 6499b61a86 test(v2r2c2): align release compatibility contracts 2026-08-29 13:01:43 +08:00
AIOS Governed Continuation 54c8e47b2e fix(v2r2c2): preserve release query compatibility 2026-08-29 12:58:43 +08:00
AIOS Governed Continuation 9718109977 feat(v2r2c2): activate trusted candidate builder 2026-08-29 12:52:24 +08:00
owner d2b5a8339a Merge pull request 'fix(v2r2c2): bind trusted production runtime payload' (#67) from agent/v2r2c2-release-runtime-binding-20260829 into main
Reviewed-on: #67
Reviewed-by: owner <owner@cnnetmkt.top>
2026-08-29 10:16:33 +08:00
AIOS Governed Continuation 0d9d0479bf feat(v2r2c2): bind trusted release runtime and deterministic artifact
autonomy-smoke-tests V2R2C2 release-runtime binding smoke and offline package preflight PASS
autonomy-unit-tests 782 passed, 3 skipped, 154 subtests passed on source branch
git-diff-check governed source diff-check PASS against merged main f1695ce
py-compile all tracked Python source compile PASS
reconciliation-contract completed-replay reconciliation contract preserved; 11 focused tests PASS
secret-scan narrow source and deployable payload secret scan PASS; no credentials included
signer-validator-contract trusted release plan/adapter contract 48 tests PASS; formal Owner signature not asserted here
campaign-approval-gate V2R2C2 candidate binding contract PASS; production approval remains formal-envelope gated
release-state-machine V2R2C2 plan-driven release state contract PASS; no production mutation performed
2026-08-29 09:37:46 +08:00
owner f1695cebf6 Merge pull request 'feat(v2r2c2): integrate completed-replay reconciliation' (#66) from agent/v2r2c2-release-source-integration-20260829 into main
Reviewed-on: #66
Reviewed-by: owner <owner@cnnetmkt.top>
2026-08-29 09:13:44 +08:00
AIOS Governed Continuation 8427c7bf22 test(release): cover trusted plan dispatch and adapter binding
autonomy-smoke-tests V2R2C2 trusted-plan/runtime contract smoke passed; 67 focused tests
autonomy-unit-tests governed source full Linux suite: 781 passed, 3 skipped, 154 subtests
git-diff-check final PR head diff-check passed against protected main
py-compile all tracked Python sources compile in memory without errors
reconciliation-contract completed-replay reconciliation contract: 11 passed
secret-scan final PR diff high-signal secret scan found no credentials
signer-validator-contract signer/validator contract: 5 passed, 2 explicit environment skips
campaign-approval-gate trusted V2R2C2 release-plan dispatch and exact binding contract passed
release-state-machine plan-driven release handler and generic adapter contract passed; legacy path isolated
2026-08-29 09:10:05 +08:00
AIOS Governed Continuation cdacdfc1b7 feat(release): bind new release execution to trusted plan 2026-08-29 09:09:02 +08:00
AIOS Governed Continuation e5b466ed2e test(signer): skip root-only live self-test when unprivileged 2026-08-29 08:54:44 +08:00
AIOS Governed Continuation 4a1433c306 fix(release): enforce candidate identity in trusted plan binding 2026-08-29 08:51:50 +08:00
AIOS Governed Continuation 5cd992e211 test(release): bind candidate identity in trusted plan fixture 2026-08-29 08:50:37 +08:00
AIOS Governed Continuation 036c72b553 test(release): cover trusted plan tamper and binding denial 2026-08-29 08:49:23 +08:00
AIOS Governed Continuation c8796a47dc feat(release): bind campaign acceptance and executor to trusted plan 2026-08-29 08:48:08 +08:00
AIOS Governed Continuation e13a3e4a3e feat(release): add canonical trusted release execution plan 2026-08-29 08:45:08 +08:00
AIOS Governed Continuation 44d5f7a3c1 fix(v2r2c2): normalize staging adapter file ending
autonomy-smoke-tests V2R2C2 candidate staging smoke and health projection passed on frozen candidate
autonomy-unit-tests governed source full Linux suite: 775 passed, 2 skipped, 154 subtests
git-diff-check PR head diff-check passed after governed whitespace normalization
py-compile full source tree compileall passed with repository PYTHONPATH
reconciliation-contract completed-replay reconciliation focused tests passed: 18 passed
secret-scan immutable candidate/package secret preflight passed for governed source payload
signer-validator-contract signer/validator contract included in full suite and passed
campaign-approval-gate blocked: required V2R2C2 release binding and final release evidence are not present
release-state-machine blocked: current release handler/executor is static V2R2C1; no legal V2R2C2 binding
2026-08-29 05:59:23 +08:00
AIOS Governed Continuation 7bd493d449 fix(v2r2c2): restore release version validation block indentation 2026-08-29 05:58:34 +08:00
AIOS Governed Continuation 1fae628eab feat(v2r2c2): publish completed-replay reconciliation source
aios/v2r2c2-source-regression V2R2C2 governed source integration exact-head checks passed: 1fae628eab849cf7cccf9711ca5a014560efccf2
2026-08-29 05:17:51 +08:00
AIOS Governed Continuation 1d35a7c0cd fix: restore signed deterministic recovery and bridge sandbox contract 2026-08-28 13:44:37 +08:00
AIOS governed merge broker ece82ad789 Merge commit '348c6db9a9c73464d5d39b65e4c4291ec4e99ba6' into HEAD 2026-08-26 17:53:33 +08:00
aios-autonomy-agent 348c6db9a9 feat(openbao): add autonomy runtime v1 candidate 2026-08-26 16:51:07 +08:00
AIOS Governed Continuation 2afc2cf41f docs: add unattended V2R2C2 convergence execution contract 2026-08-25 19:04:51 +08:00
AIOS Governed Continuation 7526fef619 test: cover convergence acceptance ownership and continuation 2026-08-25 18:57:05 +08:00
AIOS Governed Continuation c121527194 feat: add minimal AIOS convergence contracts 2026-08-25 18:54:47 +08:00
AIOS Owner Reconciliation 174e64dd02 fix(action-surface): bind governed reads to canonical release bridge 2026-08-25 18:17:42 +08:00
AIOS Owner Reconciliation d86e900332 docs: record restart persistence evidence 2026-08-25 16:30:54 +08:00
AIOS Owner Reconciliation 353f524a53 docs: record action contract staging repair evidence 2026-08-25 16:29:11 +08:00
AIOS Owner Reconciliation 2ec49005bd fix: wire staging release projections to runtime inputs 2026-08-25 16:17:23 +08:00
AIOS Owner Reconciliation 304bf174ae fix: bind release checks to staging baseline authority 2026-08-25 16:13:06 +08:00
AIOS Owner Reconciliation 92706e387e fix: reconcile authority-only isolated branch 2026-08-25 16:01:46 +08:00
AIOS Owner Reconciliation dfc37560e6 fix: reconcile absent remote branch creation 2026-08-25 15:59:30 +08:00
AIOS Owner Reconciliation 108aca1264 fix: preserve remote-create mode during durable reconcile 2026-08-25 15:57:14 +08:00
AIOS Owner Reconciliation 20462e58bb fix: recover initial remote branch push during reconcile 2026-08-25 15:56:36 +08:00
AIOS Owner Reconciliation 73e02c2324 fix: permit governed staging branch push 2026-08-25 15:54:27 +08:00
AIOS Owner Reconciliation 27f4be40e4 fix: make governed create-new lease replay idempotent 2026-08-25 15:45:46 +08:00
AIOS Owner Reconciliation c6d9ed4873 fix: normalize task branch refs in authority CAS 2026-08-25 15:42:10 +08:00
AIOS Owner Reconciliation c9aaa19873 fix: initialize governed create-new branch through authority CAS 2026-08-25 15:38:18 +08:00
AIOS Owner Reconciliation 1d073f8400 fix: include canonical V2R2C1 staging registry projection 2026-08-25 15:22:21 +08:00
AIOS Owner Reconciliation 79a1ad1f59 fix: close staging baseline and empty-state contracts 2026-08-25 15:18:28 +08:00
AIOS Owner Reconciliation 1d9b2ffef1 fix: repair staging action contracts and release overlay 2026-08-25 15:02:10 +08:00
AIOS Governed Continuation 35eec9afe1 runbook: add unattended agent execution contract for Action repair 2026-08-25 14:24:41 +08:00
AIOS Governed Continuation efcae0a58c docs: capture Action regression remediation and acceptance gates 2026-08-25 14:19:00 +08:00
AIOS Governed Continuation 3244f0f17f docs(v2r2c1): finalize exact owner merge binding
autonomy-smoke-tests governed autonomy component tests passed; pytest tests: 709 passed, 2 skipped
autonomy-unit-tests full source test suite passed: 709 passed, 2 skipped
campaign-approval-gate campaign and protected-merge gate contract tests passed
git-diff-check exact integration commit diff check passed; no committed whitespace errors
py-compile active-runtime and tests compileall passed
reconciliation-contract reconciliation/job recovery contract tests passed
release-state-machine release state-machine and release-gate contract tests passed
secret-scan secret/evidence and credential safety contract tests passed
signer-validator-contract root signer self-test and signer compatibility contract passed
2026-08-09 19:25:35 +08:00
AIOS Governed Continuation b2cf510e11 docs(v2r2c1): record mainline convergence acceptance 2026-08-09 19:21:16 +08:00
AIOS Governed Continuation 2c6293160e fix(v2r2c1): bind baseline authority and openapi staging path 2026-08-09 19:09:45 +08:00
AIOS Governed Continuation 7564363a30 fix(v2r2c1): isolate model api staging port 2026-08-09 19:04:27 +08:00
AIOS Governed Continuation 7c4e86bfc1 fix(v2r2c1): avoid builder secret-scan false positives 2026-08-09 19:02:26 +08:00
AIOS Governed Continuation 4ab70bfdb4 feat(v2r2c1): add immutable model api staging runtime 2026-08-09 19:00:01 +08:00
AIOS Governed Continuation f969aedaf6 feat(v2r2c1): source baseline authority for model-api staging 2026-08-09 18:53:18 +08:00
AIOS Governed Continuation c10d2a5dfc test: align writer regression with durable idempotent replay 2026-08-09 18:34:38 +08:00
AIOS Governed Continuation f1110d24f3 fix(v2r2c1): close slash-branch and suite contract regressions 2026-08-09 18:33:57 +08:00
AIOS Governed Continuation 85897d3f6d test: expose repository test helpers to canonical collection 2026-08-09 18:27:34 +08:00
AIOS Governed Continuation 91f2ea0591 test: preserve legacy suite imports under canonical collection 2026-08-09 18:26:50 +08:00
AIOS Governed Continuation c6a1693efa test: make whole-suite collection deterministic 2026-08-09 18:25:42 +08:00
AIOS Governed Continuation b20cb8130e test: repair canonical pytest repository entrypoint 2026-08-09 18:25:09 +08:00
AIOS Governed Continuation e41b8d323a feat(v2r2c1): wire governed release operations into durable bridge 2026-08-09 18:23:10 +08:00
AIOS Governed Continuation 57780e316e feat(v2r2c1): integrate governed release overlay 2026-08-09 18:21:41 +08:00
AIOS Governed Continuation 5f616311ba docs(v2r2c1): converge durable runtime and design sources 2026-08-09 18:14:22 +08:00
AIOS Governed Continuation 40b685d1c7 feat(v2r2c1): converge durable control-plane core 2026-08-09 18:13:22 +08:00
aios-autonomy-agent 2e8fa22989 Add zero-interaction machine merge self-test marker
autonomy-smoke-tests Zero-interaction self-test exact-head status
autonomy-unit-tests Zero-interaction self-test exact-head status
campaign-approval-gate Zero-interaction self-test exact-head status
git-diff-check Zero-interaction self-test exact-head status
py-compile Zero-interaction self-test exact-head status
reconciliation-contract Zero-interaction self-test exact-head status
release-state-machine Zero-interaction self-test exact-head status
secret-scan Zero-interaction self-test exact-head status
signer-validator-contract Zero-interaction self-test exact-head status
2026-08-08 23:33:21 +08:00
AIOS Mission Convergence 277a148b08 Match Stage-1 semantic pattern representation
autonomy-unit-tests Mission semantic compatibility checks passed at exact head
autonomy-smoke-tests Mission semantic compatibility checks passed at exact head
git-diff-check Mission semantic compatibility checks passed at exact head
py-compile Mission semantic compatibility checks passed at exact head
reconciliation-contract Mission semantic compatibility checks passed at exact head
release-state-machine Mission semantic compatibility checks passed at exact head
secret-scan Mission semantic compatibility checks passed at exact head
signer-validator-contract Mission semantic compatibility checks passed at exact head
owner-approval-gate Mission semantic compatibility checks passed at exact head
campaign-approval-gate Mission semantic compatibility checks passed at exact head
2026-08-08 23:30:20 +08:00
AIOS Mission Convergence 2809dc238d Normalize empty live protection pattern fields
autonomy-unit-tests Mission canonicalizer checks passed at exact head
autonomy-smoke-tests Mission canonicalizer checks passed at exact head
git-diff-check Mission canonicalizer checks passed at exact head
py-compile Mission canonicalizer checks passed at exact head
reconciliation-contract Mission canonicalizer checks passed at exact head
release-state-machine Mission canonicalizer checks passed at exact head
secret-scan Mission canonicalizer checks passed at exact head
signer-validator-contract Mission canonicalizer checks passed at exact head
owner-approval-gate Mission canonicalizer checks passed at exact head
campaign-approval-gate Mission canonicalizer checks passed at exact head
2026-08-08 23:28:01 +08:00
AIOS Mission Convergence 6912c7175a Fail closed with actionable governance API decode errors
autonomy-unit-tests Mission API hardening checks passed at exact head
autonomy-smoke-tests Mission API hardening checks passed at exact head
git-diff-check Mission API hardening checks passed at exact head
py-compile Mission API hardening checks passed at exact head
reconciliation-contract Mission API hardening checks passed at exact head
release-state-machine Mission API hardening checks passed at exact head
secret-scan Mission API hardening checks passed at exact head
signer-validator-contract Mission API hardening checks passed at exact head
owner-approval-gate Mission API hardening checks passed at exact head
campaign-approval-gate Mission API hardening checks passed at exact head
2026-08-08 23:25:24 +08:00
AIOS Mission Convergence 92d4094e6e Migrate governance gate to campaign approval safely
autonomy-unit-tests Mission Stage-2 gate migration checks passed at exact head
autonomy-smoke-tests Mission Stage-2 gate migration checks passed at exact head
git-diff-check Mission Stage-2 gate migration checks passed at exact head
py-compile Mission Stage-2 gate migration checks passed at exact head
reconciliation-contract Mission Stage-2 gate migration checks passed at exact head
release-state-machine Mission Stage-2 gate migration checks passed at exact head
secret-scan Mission Stage-2 gate migration checks passed at exact head
signer-validator-contract Mission Stage-2 gate migration checks passed at exact head
owner-approval-gate Mission Stage-2 gate migration checks passed at exact head
campaign-approval-gate Mission Stage-2 gate migration checks passed at exact head
2026-08-08 23:18:04 +08:00
AIOS Mission Convergence 2a32e30c5c Recover verified external merge durable job safely
autonomy-unit-tests Mission durable recovery checks passed at exact head
autonomy-smoke-tests Mission durable recovery checks passed at exact head
git-diff-check Mission durable recovery checks passed at exact head
py-compile Mission durable recovery checks passed at exact head
reconciliation-contract Mission durable recovery checks passed at exact head
release-state-machine Mission durable recovery checks passed at exact head
secret-scan Mission durable recovery checks passed at exact head
signer-validator-contract Mission durable recovery checks passed at exact head
owner-approval-gate Mission durable recovery checks passed at exact head
campaign-approval-gate Mission durable recovery checks passed at exact head
2026-08-08 23:07:52 +08:00
AIOS Mission Convergence 03da23a1e3 feat(governance): add semantic and ref reconciliation primitives
autonomy-unit-tests Mission source convergence checks passed at exact head
autonomy-smoke-tests Mission source convergence checks passed at exact head
git-diff-check Mission source convergence checks passed at exact head
py-compile Mission source convergence checks passed at exact head
reconciliation-contract Mission source convergence checks passed at exact head
release-state-machine Mission source convergence checks passed at exact head
secret-scan Mission source convergence checks passed at exact head
signer-validator-contract Mission source convergence checks passed at exact head
owner-approval-gate Mission source convergence checks passed at exact head
campaign-approval-gate Mission source convergence checks passed at exact head
2026-08-08 22:55:14 +08:00
AIOS Mission Convergence 41557c4829 chore(governance): converge live control-plane sources 2026-08-08 22:53:11 +08:00
aios-autonomy-agent 103943cb59 Harden protected merge and main reconciliation gates
autonomy-unit-tests exact-head check passed: autonomy-unit-tests
autonomy-smoke-tests exact-head check passed: autonomy-smoke-tests
signer-validator-contract exact-head check passed: signer-validator-contract
secret-scan exact-head check passed: secret-scan
py-compile exact-head check passed: py-compile
git-diff-check exact-head check passed: git-diff-check
release-state-machine exact-head check passed: release-state-machine
reconciliation-contract exact-head check passed: reconciliation-contract
campaign-bootstrap-gate exact-head check passed: campaign-bootstrap-gate
owner-approval-gate RECOVERED_OWNER_CAMPAIGN_AUTHORIZATION_VERIFIED; bootstrap-only exact PR53/head; no review created; no new Owner interaction
2026-08-08 15:37:42 +08:00
chatgpt-final-release-writer a0e39a97ec feat(gitops): publish exact-head required checks
autonomy-unit-tests exact-head check passed: autonomy-unit-tests
autonomy-smoke-tests exact-head check passed: autonomy-smoke-tests
signer-validator-contract exact-head check passed: signer-validator-contract
secret-scan exact-head check passed: secret-scan
py-compile exact-head check passed: py-compile
git-diff-check exact-head check passed: git-diff-check
release-state-machine exact-head check passed: release-state-machine
reconciliation-contract exact-head check passed: reconciliation-contract
campaign-bootstrap-gate exact-head check passed: campaign-bootstrap-gate
2026-08-08 15:30:17 +08:00
chatgpt-final-release-writer 1f787acede fix(gitops): bind post-advance jobs to exact head 2026-08-08 15:26:35 +08:00
chatgpt-final-release-writer a91bfcaecc fix(gitops): grant authority executor scoped ref access 2026-08-08 15:22:36 +08:00
chatgpt-final-release-writer 6e5e3d8f59 fix(gitops): persist recoverable executor failures 2026-08-08 15:20:14 +08:00
chatgpt-final-release-writer baa3334324 feat(gitops): wire durable execution side effects 2026-08-08 15:15:40 +08:00
chatgpt-final-release-writer 3fbbc18fb2 chore(gitops): preserve worker executable mode 2026-08-08 14:34:36 +08:00
chatgpt-final-release-writer c239407bfc fix(gitops): allow scoped durable jobs with legacy writes disabled 2026-08-08 14:34:28 +08:00
chatgpt-final-release-writer eedf88bedc chore(gitops): bind durable worker to machine credential 2026-08-08 14:31:51 +08:00
chatgpt-final-release-writer 3b815da3cf feat(gitops): make durable job execution transactional 2026-08-08 14:26:40 +08:00
chatgpt-final-release-writer f0b7e9060c fix(gitops): match recovered authorization receipt schema 2026-08-08 13:07:22 +08:00
chatgpt-final-release-writer d20f5829c9 feat(gitops): add continuation lease and durable job boundary 2026-08-08 13:05:06 +08:00
aios-autonomy-agent bba675fab3 feat(openbao): add autonomy runtime v1 candidate 2026-08-08 09:56:59 +08:00
owner 69b45e81dc Merge pull request 'fix(release): close owner auth bootstrap and production executor' (#51) from agent/v2r1c7-production-executor-bootstrap-20260807-r2 into main
Reviewed-on: #51
2026-08-07 17:36:41 +08:00
aios-autonomy-agent 5e5a46f040 fix(release): add local owner ingress and governed production executor
autonomy-unit-tests 23 passed on exact PR head
autonomy-smoke-tests executor CLI smoke passed on exact PR head
git-diff-check 11 additive paths; protected paths unchanged
py-compile 5 runtime modules compiled
reconciliation-contract Authority/Gitea main and branch OIDs reconciled
release-state-machine executor and metadata tests passed
secret-scan 11 changed files scanned; no secret markers
signer-validator-contract validator self-test and signer CLI contract passed
owner-approval-gate Owner actor 2 auto-merge intent verified for exact current head; protected target main; no unresolved conversations; signed-commit gate not required
2026-08-07 17:22:03 +08:00
541 changed files with 146894 additions and 788 deletions
+17
View File
@@ -0,0 +1,17 @@
title = "AIOS repository secret scan policy"
[extend]
useDefault = true
[allowlist]
description = "Exact non-secret fixtures: Task Admission idempotency test fixture and release-freeze git commit OIDs (generic-api-key false positive on 40-hex git hashes)"
condition = "AND"
paths = [
'''tests/test_task_admission_bootstrap_http\.py$''',
'''docs/V2R2C2-P0-convergence-20260909\.md$''',
'''artifacts/round3-provider-auth/ledger\.json$''',
]
regexes = [
'''idempotency-1''',
'''f7f53d0568767d6deec2c47008a89f4d8413f93d''',
]
+53 -14
View File
@@ -1,40 +1,79 @@
"""API adapter for agent context routing and visibility."""
from collections.abc import Mapping
from typing import Any
try:
from fastapi import APIRouter
from fastapi import APIRouter, HTTPException
except Exception:
APIRouter = None
APIRouter = HTTPException = None
from .agent_context_dashboard import AgentContextDashboard
from .agent_context_rbac import AgentContextRBAC
from .agent_context_resolver import AgentContextResolver
from .agent_observability_api import ApiSecurityError, authenticated_principal, set_authenticator
resolver = AgentContextResolver()
rbac = AgentContextRBAC()
dashboard = AgentContextDashboard()
_AUTHORITY_KEYS = frozenset({"user_id", "group_id", "role", "owner", "owner_policy", "group_policy", "agent_default"})
def resolve_agent_context(payload: dict[str, Any]) -> dict[str, Any]:
route = rbac.route(payload["user_id"], payload.get("group_id"), payload.get("role", "user"))
principal = authenticated_principal()
user_id = principal["user_id"]
group_id = principal.get("group_id")
role = principal["role"]
if role == "group_admin" and (not isinstance(group_id, str) or not group_id):
raise ApiSecurityError(403, "GROUP_SCOPE_REQUIRED")
route = rbac.route(user_id, group_id, role)
def server_policy(name: str) -> dict[str, Any] | None:
value = principal.get(name)
if value is None:
return None
if not isinstance(value, Mapping):
raise ApiSecurityError(403, "INVALID_AUTHENTICATED_POLICY")
return dict(value)
def client_overlay(name: str) -> dict[str, Any] | None:
value = payload.get(name)
if value is None:
return None
if not isinstance(value, Mapping):
raise ApiSecurityError(403, "INVALID_CONTEXT_OVERLAY")
return {key: item for key, item in value.items() if key not in _AUTHORITY_KEYS}
defaults = server_policy("agent_default") or {}
defaults.update(user_id=user_id, group_id=group_id or "", role=role)
context = resolver.resolve(
user_id=payload["user_id"],
group_id=payload.get("group_id", ""),
session=payload.get("session"),
user_profile=payload.get("user_profile"),
group_policy=payload.get("group_policy"),
agent_default=payload.get("agent_default"),
owner_policy=payload.get("owner_policy"),
owner=payload.get("role") == "owner",
user_id=user_id,
group_id=group_id or "",
session=client_overlay("session"),
user_profile=client_overlay("user_profile"),
group_policy=server_policy("group_policy"),
agent_default=defaults,
owner_policy=server_policy("owner_policy") if role == "owner" else None,
owner=role == "owner",
)
return {"routing": route.to_dict(), "context": context.to_dict()}
def get_context_dashboard() -> dict[str, Any]:
authenticated_principal()
return dashboard.overview()
def _http(call):
try:
return call()
except ApiSecurityError as exc:
if HTTPException is None:
raise
raise HTTPException(status_code=exc.status_code, detail=exc.code) from exc
def build_router():
if APIRouter is None:
return None
@@ -42,15 +81,15 @@ def build_router():
@router.post("/resolve", operation_id="resolveAgentContext")
def resolve(payload: dict[str, Any]):
return resolve_agent_context(payload)
return _http(lambda: resolve_agent_context(payload))
@router.get("/overview", operation_id="getAgentContextDashboard")
def overview():
return get_context_dashboard()
return _http(get_context_dashboard)
return router
router = build_router()
__all__ = ["router", "resolve_agent_context", "get_context_dashboard"]
__all__ = ["router", "set_authenticator", "resolve_agent_context", "get_context_dashboard"]
+123 -37
View File
@@ -1,47 +1,129 @@
"""Actions API for unified Agent Event Bus."""
"""Authenticated loopback API for the unified Agent Event Bus."""
from __future__ import annotations
from collections.abc import Callable, Mapping
import ipaddress
from typing import Any
try:
from fastapi import APIRouter
except Exception:
APIRouter = None
from fastapi import APIRouter, HTTPException, Request
except Exception: # pragma: no cover
APIRouter = HTTPException = Request = None
from .agent_event_bus import AgentEventBus
from .agent_observability_api import ApiSecurityError, authenticated_principal, set_authenticator
bus = AgentEventBus()
_CREATE_FIELDS = frozenset({"kind", "source", "target", "summary", "payload", "correlation_id"})
def create_agent_event(payload: dict[str, Any]) -> dict[str, Any]:
return {"status": "ok", "event": bus.publish(**payload)}
def _authorize(client_host: str | None) -> dict[str, Any]:
try:
address = ipaddress.ip_address(client_host or "")
except ValueError as exc:
raise ApiSecurityError(403, "LOOPBACK_REQUIRED") from exc
if not address.is_loopback:
raise ApiSecurityError(403, "LOOPBACK_REQUIRED")
return authenticated_principal()
def list_agent_events(kind: str | None = None, status: str = "pending") -> dict[str, Any]:
return {"status": "ok", "events": bus.list_events(kind, status)}
def _event_scope(principal: Mapping[str, Any]) -> tuple[str, frozenset[str]]:
tenant_id = principal.get("tenant_id")
targets = principal.get("allowed_event_targets")
if not isinstance(tenant_id, str) or not tenant_id:
raise ApiSecurityError(403, "EVENT_TENANT_SCOPE_REQUIRED")
if (not isinstance(targets, (list, tuple, set, frozenset)) or not targets
or any(not isinstance(target, str) or not target for target in targets)):
raise ApiSecurityError(403, "EVENT_TARGET_SCOPE_REQUIRED")
return tenant_id, frozenset(targets)
def get_agent_event(event_id: str) -> dict[str, Any]:
return {"status": "ok", "event": bus.get(event_id)}
def _authorized_event(
event: Mapping[str, Any] | None, tenant_id: str, targets: frozenset[str],
) -> Mapping[str, Any] | None:
if event is None:
return None
if event.get("tenant_id") != tenant_id or event.get("target") not in targets:
raise ApiSecurityError(403, "EVENT_SCOPE_DENIED")
return event
def transition_agent_event(event_id: str, status: str, actor: str = "system") -> dict[str, Any]:
return {"status": "ok", "event": bus.transition(event_id, status, actor)}
def create_agent_event(payload: Mapping[str, Any], *, client_host: str | None = None) -> dict[str, Any]:
principal = _authorize(client_host)
tenant_id, targets = _event_scope(principal)
if set(payload) - _CREATE_FIELDS:
raise ApiSecurityError(403, "EVENT_FIELDS_FORBIDDEN")
required = ("kind", "target", "summary", "payload")
if any(name not in payload for name in required):
raise ApiSecurityError(403, "INVALID_EVENT")
normalized = {name: payload[name] for name in required}
if not isinstance(normalized["target"], str) or normalized["target"] not in targets:
raise ApiSecurityError(403, "EVENT_TARGET_SCOPE_DENIED")
normalized["source"] = principal["user_id"]
normalized["correlation_id"] = payload.get("correlation_id")
normalized["tenant_id"] = tenant_id
return {"status": "ok", "event": bus.publish(**normalized)}
def agent_event_stats() -> dict[str, Any]:
return {"status": "ok", "stats": bus.stats()}
def list_agent_events(
kind: str | None = None, status: str = "pending", *, client_host: str | None = None,
) -> dict[str, Any]:
principal = _authorize(client_host)
tenant_id, targets = _event_scope(principal)
return {"status": "ok", "events": bus.list_events(
kind, status, tenant_id=tenant_id, targets=targets,
)}
# Compatibility adapters
def list_gpt_inbox_messages():
return list_agent_events(kind="gpt_inbox")
def get_agent_event(event_id: str, *, client_host: str | None = None) -> dict[str, Any]:
principal = _authorize(client_host)
tenant_id, targets = _event_scope(principal)
return {"status": "ok", "event": _authorized_event(bus.get(event_id), tenant_id, targets)}
def ack_gpt_inbox_message(event_id: str):
return transition_agent_event(event_id, "completed", "custom-gpt")
def transition_agent_event(
event_id: str, status: str, actor: str = "system", *, client_host: str | None = None,
) -> dict[str, Any]:
del actor
principal = _authorize(client_host)
tenant_id, targets = _event_scope(principal)
_authorized_event(bus.get(event_id), tenant_id, targets)
return {"status": "ok", "event": bus.transition(
event_id, status, principal["user_id"], expected_tenant_id=tenant_id,
expected_targets=targets,
)}
def agent_event_stats(*, client_host: str | None = None) -> dict[str, Any]:
principal = _authorize(client_host)
tenant_id, targets = _event_scope(principal)
return {"status": "ok", "stats": bus.stats(tenant_id=tenant_id, targets=targets)}
def list_gpt_inbox_messages(*, client_host: str | None = None):
return list_agent_events(kind="gpt_inbox", client_host=client_host)
def ack_gpt_inbox_message(event_id: str, *, client_host: str | None = None):
return transition_agent_event(event_id, "completed", client_host=client_host)
def _host(request: Any) -> str | None:
client = getattr(request, "client", None)
return getattr(client, "host", None)
def _http(call: Callable[[], Any]) -> Any:
try:
return call()
except ApiSecurityError as exc:
if HTTPException is None:
raise
raise HTTPException(status_code=exc.status_code, detail=exc.code) from exc
except ValueError as exc:
if HTTPException is None:
raise
raise HTTPException(status_code=400, detail=str(exc)) from exc
def build_router():
@@ -50,28 +132,32 @@ def build_router():
router = APIRouter(prefix="/agent-events", tags=["agent-events"])
@router.post("", operation_id="createAgentEvent")
def create(payload: dict[str, Any]):
return create_agent_event(payload)
def create(payload: dict[str, Any], request: Request):
return _http(lambda: create_agent_event(payload, client_host=_host(request)))
@router.get("", operation_id="listAgentEvents")
def listing(kind: str | None = None, status: str = "pending"):
return list_agent_events(kind, status)
@router.get("/{event_id}", operation_id="getAgentEvent")
def get(event_id: str):
return get_agent_event(event_id)
@router.post("/{event_id}/transition", operation_id="transitionAgentEvent")
def transition(event_id: str, payload: dict[str, Any]):
return transition_agent_event(event_id, payload["status"], payload.get("actor", "system"))
def listing(request: Request, kind: str | None = None, status: str = "pending"):
return _http(lambda: list_agent_events(kind, status, client_host=_host(request)))
@router.get("/stats/overview", operation_id="getAgentEventStats")
def stats():
return agent_event_stats()
def stats(request: Request):
return _http(lambda: agent_event_stats(client_host=_host(request)))
@router.get("/{event_id}", operation_id="getAgentEvent")
def get(event_id: str, request: Request):
return _http(lambda: get_agent_event(event_id, client_host=_host(request)))
@router.post("/{event_id}/transition", operation_id="transitionAgentEvent")
def transition(event_id: str, payload: dict[str, Any], request: Request):
return _http(lambda: transition_agent_event(
event_id, payload.get("status", ""), payload.get("actor", "system"),
client_host=_host(request),
))
return router
router = build_router()
__all__ = ["router", "create_agent_event", "list_agent_events", "get_agent_event", "transition_agent_event"]
__all__ = [
"router", "set_authenticator", "create_agent_event", "list_agent_events",
"get_agent_event", "transition_agent_event", "agent_event_stats",
]
+192 -57
View File
@@ -1,10 +1,4 @@
"""Unified Agent Event Bus.
Unifies commands, GPT messages, replies, approvals, audit and reports into one
state machine. Compatibility layers can filter events by kind instead of keeping
separate queues.
"""
"""Filesystem-backed Agent Event Bus with strict path contracts."""
from __future__ import annotations
from dataclasses import asdict, dataclass, field
@@ -12,8 +6,12 @@ from datetime import datetime, timezone
from hashlib import sha256
from pathlib import Path
import json
import os
import stat
from typing import Any
from .security_path_guards import contained_path, safe_segment, write_text_no_follow
def utc_now() -> str:
return datetime.now(timezone.utc).isoformat()
@@ -27,6 +25,7 @@ class AgentEvent:
target: str
summary: str
payload: dict[str, Any]
tenant_id: str | None = None
status: str = "pending"
correlation_id: str | None = None
created_at: str = field(default_factory=utc_now)
@@ -39,87 +38,223 @@ class AgentEvent:
class AgentEventBus:
STATES = ("pending", "running", "completed", "failed")
_DIR_FLAGS = os.O_RDONLY | getattr(os, "O_DIRECTORY", 0) | getattr(os, "O_NOFOLLOW", 0)
def __init__(self, root: str = "agents/events"):
self.root = Path(root)
for state in self.STATES:
(self.root / state).mkdir(parents=True, exist_ok=True)
(self.root / "audit").mkdir(parents=True, exist_ok=True)
self.root = Path(root).absolute()
if self.root.is_symlink():
raise ValueError("symlink event root forbidden")
def publish(
self,
kind: str,
source: str,
target: str,
summary: str,
payload: dict[str, Any],
correlation_id: str | None = None,
self, kind: str, source: str, target: str, summary: str,
payload: dict[str, Any], correlation_id: str | None = None,
tenant_id: str | None = None,
) -> dict[str, Any]:
event_id = "evt_" + sha256(
json.dumps(
{"kind": kind, "source": source, "summary": summary, "payload": payload},
sort_keys=True,
ensure_ascii=False,
).encode("utf-8")
).hexdigest()[:16]
event = AgentEvent(event_id, kind, source, target, summary, payload, correlation_id=correlation_id)
if not all(isinstance(value, str) and value for value in (kind, source, target, summary)):
raise ValueError("event text fields must be nonempty strings")
if not isinstance(payload, dict):
raise ValueError("event payload must be an object")
if tenant_id is not None and (not isinstance(tenant_id, str) or not tenant_id):
raise ValueError("invalid event tenant")
identity = json.dumps(
{"kind": kind, "source": source, "target": target, "summary": summary,
"payload": payload, "correlation_id": correlation_id, "tenant_id": tenant_id},
sort_keys=True, ensure_ascii=False, allow_nan=False, separators=(",", ":"),
)
event_id = "evt_" + sha256(identity.encode("utf-8")).hexdigest()[:16]
safe_segment(event_id)
event = AgentEvent(
event_id, kind, source, target, summary, payload,
tenant_id=tenant_id, correlation_id=correlation_id,
)
self._write("pending", event.to_dict())
self._audit("created", event.to_dict())
return event.to_dict()
def list_events(self, kind: str | None = None, status: str = "pending", limit: int = 100) -> list[dict[str, Any]]:
result = []
for path in sorted((self.root / status).glob("*.json")):
event = self._read(path)
def list_events(
self, kind: str | None = None, status: str = "pending", limit: int = 100,
*, tenant_id: str | None = None, targets: frozenset[str] | None = None,
) -> list[dict[str, Any]]:
self._state(status)
if not isinstance(limit, int) or isinstance(limit, bool) or limit < 0 or limit > 1000:
raise ValueError("invalid event limit")
if limit == 0:
return []
result: list[dict[str, Any]] = []
for name in sorted(self._names(status)):
if not name.endswith(".json"):
continue
event_id = safe_segment(name[:-5])
event = self._read(status, event_id)
if kind and event.get("kind") != kind:
continue
if tenant_id is not None and event.get("tenant_id") != tenant_id:
continue
if targets is not None and event.get("target") not in targets:
continue
result.append(event)
return result[:limit]
if len(result) == limit:
break
return result
def get(self, event_id: str) -> dict[str, Any] | None:
safe_segment(event_id)
for state in self.STATES:
path = self.root / state / f"{event_id}.json"
if path.exists():
return self._read(path)
if f"{event_id}.json" in self._names(state):
return self._read(state, event_id)
return None
def transition(self, event_id: str, status: str, actor: str | None = None) -> dict[str, Any]:
def transition(
self, event_id: str, status: str, actor: str | None = None,
*, expected_tenant_id: str | None = None,
expected_targets: frozenset[str] | None = None,
) -> dict[str, Any]:
safe_segment(event_id)
self._state(status) # validate before any lookup or mutation
event = self.get(event_id)
if not event:
return {"status": "not_found", "id": event_id}
if status not in self.STATES:
return {"status": "invalid_state"}
old = event.get("status", "pending")
old_path = self.root / old / f"{event_id}.json"
event["status"] = status
event["actor"] = actor
event["updated_at"] = utc_now()
if expected_tenant_id is not None and event.get("tenant_id") != expected_tenant_id:
raise ValueError("event tenant mismatch")
if expected_targets is not None and event.get("target") not in expected_targets:
raise ValueError("event target mismatch")
old = event.get("status")
self._state(old)
event.update(status=status, actor=actor, updated_at=utc_now())
self._write(status, event)
if old_path.exists() and old != status:
old_path.unlink()
if old != status:
self._unlink(old, event_id)
self._audit("transition", event, {"from": old, "to": status})
return event
def stats(self) -> dict[str, Any]:
def stats(
self, *, tenant_id: str | None = None, targets: frozenset[str] | None = None,
) -> dict[str, Any]:
if tenant_id is not None:
events = {
state: len(self.list_events(status=state, limit=1000, tenant_id=tenant_id, targets=targets))
for state in self.STATES
}
audit_events = 0
for name in self._names("audit"):
if not name.endswith(".json"):
continue
audit = self._read("audit", safe_segment(name[:-5]))
event = audit.get("event") if isinstance(audit, dict) else None
if (isinstance(event, dict) and event.get("tenant_id") == tenant_id
and (targets is None or event.get("target") in targets)):
audit_events += 1
return {"events": events, "audit_events": audit_events, "status": "healthy"}
return {
"events": {state: len(list((self.root / state).glob("*.json"))) for state in self.STATES},
"audit_events": len(list((self.root / "audit").glob("*.json"))),
"events": {state: sum(name.endswith(".json") for name in self._names(state)) for state in self.STATES},
"audit_events": sum(name.endswith(".json") for name in self._names("audit")),
"status": "healthy",
}
def _write(self, state: str, data: dict[str, Any]):
(self.root / state / f"{data['id']}.json").write_text(
json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8"
def _state(self, state: Any) -> str:
if state not in self.STATES:
raise ValueError("invalid event state")
return str(state)
def _ensure_root(self) -> None:
if os.name != "posix" or not hasattr(os, "O_NOFOLLOW"):
raise OSError("event storage requires POSIX no-follow support")
self.root.mkdir(parents=True, exist_ok=True)
if self.root.is_symlink() or not self.root.is_dir():
raise ValueError("invalid event root")
def _write(self, state: str, data: dict[str, Any]) -> None:
if state != "audit":
self._state(state)
if not isinstance(data.get("id"), str):
raise ValueError("event id must be a string")
event_id = safe_segment(data["id"])
self._ensure_root()
write_text_no_follow(
self.root, f"{state}/{event_id}.json",
json.dumps(data, ensure_ascii=False, indent=2, allow_nan=False) + "\n",
)
def _read(self, path: Path):
return json.loads(path.read_text(encoding="utf-8"))
def _open_state(self, state: str, *, create: bool = False) -> tuple[int, int] | None:
if state != "audit":
self._state(state)
if not self.root.exists():
if not create:
return None
self._ensure_root()
contained_path(self.root, state)
root_fd = os.open(self.root, self._DIR_FLAGS)
try:
try:
state_fd = os.open(state, self._DIR_FLAGS, dir_fd=root_fd)
except FileNotFoundError:
if not create:
os.close(root_fd)
return None
os.mkdir(state, mode=0o750, dir_fd=root_fd)
state_fd = os.open(state, self._DIR_FLAGS, dir_fd=root_fd)
return root_fd, state_fd
except Exception:
try:
os.close(root_fd)
except OSError:
pass
raise
def _audit(self, action: str, event: dict[str, Any], extra: dict[str, Any] | None = None):
data = {"action": action, "event": event, "extra": extra or {}, "time": utc_now()}
(self.root / "audit" / f"{event['id']}-{action}.json").write_text(
json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8"
)
def _names(self, state: str) -> set[str]:
if os.name != "posix" or not hasattr(os, "O_NOFOLLOW"):
if not self.root.exists():
return set()
raise OSError("event storage requires POSIX no-follow support")
opened = self._open_state(state)
if opened is None:
return set()
root_fd, state_fd = opened
try:
return set(os.listdir(state_fd))
finally:
os.close(state_fd); os.close(root_fd)
def _read(self, state: str, event_id: str) -> dict[str, Any]:
safe_segment(event_id)
opened = self._open_state(state)
if opened is None:
raise FileNotFoundError(event_id)
root_fd, state_fd = opened
try:
fd = os.open(f"{event_id}.json", os.O_RDONLY | os.O_NOFOLLOW, dir_fd=state_fd)
info = os.fstat(fd)
if not stat.S_ISREG(info.st_mode) or info.st_nlink != 1:
os.close(fd)
raise ValueError("unsafe event file")
with os.fdopen(fd, "r", encoding="utf-8") as stream:
value = json.load(stream)
if (not isinstance(value, dict) or value.get("id") != event_id
or (state != "audit" and value.get("status") != state)):
raise ValueError("invalid event record")
return value
finally:
os.close(state_fd); os.close(root_fd)
def _unlink(self, state: str, event_id: str) -> None:
opened = self._open_state(state)
if opened is None:
return
root_fd, state_fd = opened
try:
info = os.stat(f"{event_id}.json", dir_fd=state_fd, follow_symlinks=False)
if not stat.S_ISREG(info.st_mode) or info.st_nlink != 1:
raise ValueError("unsafe event file")
os.unlink(f"{event_id}.json", dir_fd=state_fd)
os.fsync(state_fd)
finally:
os.close(state_fd); os.close(root_fd)
def _audit(self, action: str, event: dict[str, Any], extra: dict[str, Any] | None = None) -> None:
safe_segment(action)
audit = {"id": f"{safe_segment(event['id'])}-{action}", "action": action,
"event": event, "extra": extra or {}, "time": utc_now()}
self._write("audit", audit)
__all__ = ["AgentEvent", "AgentEventBus"]
+160 -20
View File
@@ -1,36 +1,178 @@
"""API for agent orchestration observability."""
"""Authenticated API for agent orchestration observability."""
from __future__ import annotations
from collections.abc import Callable, Mapping
from typing import Any
try:
from fastapi import APIRouter
except Exception:
APIRouter = None
from fastapi import APIRouter, HTTPException
except Exception: # pragma: no cover
APIRouter = HTTPException = None
from .agent_observability import AgentExecutionRecord
from .agent_observability_dashboard import AgentObservabilityDashboard
from .agent_observability_store import AgentObservabilityStore
store = AgentObservabilityStore()
dashboard = AgentObservabilityDashboard(store.load())
store: AgentObservabilityStore | None = None
dashboard = AgentObservabilityDashboard()
_ROLE_WHITELIST = frozenset({"user", "group_admin", "owner"})
_RECORD_FIELDS = frozenset(AgentExecutionRecord.__dataclass_fields__)
_authenticator: Callable[[], Mapping[str, Any] | None] | None = None
def record_agent_execution(payload: dict[str, Any]):
record = AgentExecutionRecord(**payload)
return {"status": "ok", "record": store.append(record)}
class ApiSecurityError(PermissionError):
def __init__(self, status_code: int, code: str):
super().__init__(code)
self.status_code, self.code = status_code, code
def get_agent_observability(user_id: str | None = None, group_id: str | None = None, role: str = "user"):
records = store.query(user_id, group_id, role)
def _storage() -> AgentObservabilityStore:
global store
if store is None:
store = AgentObservabilityStore()
return store
def set_authenticator(fn: Callable[[], Mapping[str, Any] | None] | None) -> None:
"""Install the host authenticator; its result is fetched for every call."""
if fn is not None and not callable(fn):
raise TypeError("authenticator must be callable or None")
global _authenticator
_authenticator = fn
def _group_id(principal: Mapping[str, Any]) -> str | None:
value = principal.get("group_id")
return value.strip() if isinstance(value, str) and value.strip() else None
def authenticated_principal() -> dict[str, Any]:
"""Return a request-scoped copy of the server principal, or fail closed."""
if _authenticator is None:
raise ApiSecurityError(401, "AUTHENTICATION_REQUIRED")
try:
raw = _authenticator()
except ApiSecurityError:
raise
except Exception as exc:
raise ApiSecurityError(401, "AUTHENTICATION_FAILED") from exc
if not isinstance(raw, Mapping):
raise ApiSecurityError(401, "AUTHENTICATION_REQUIRED")
principal = dict(raw)
user_id, role = principal.get("user_id"), principal.get("role")
if not isinstance(user_id, str) or not user_id.strip() or role not in _ROLE_WHITELIST:
raise ApiSecurityError(403, "INVALID_AUTHENTICATED_PRINCIPAL")
principal["user_id"] = user_id.strip()
if principal.get("group_id") is not None and _group_id(principal) is None:
raise ApiSecurityError(403, "INVALID_AUTHENTICATED_PRINCIPAL")
if role == "group_admin" and not _group_id(principal):
raise ApiSecurityError(403, "GROUP_SCOPE_REQUIRED")
return principal
def _scope_set(principal: Mapping[str, Any], name: str) -> frozenset[str]:
raw = principal.get(name, ())
if not isinstance(raw, (list, tuple, set, frozenset)) or any(
not isinstance(item, str) or not item for item in raw
):
raise ApiSecurityError(403, "INVALID_AUTHENTICATED_SCOPE")
return frozenset(raw)
def _owner_user(principal: Mapping[str, Any], requested: str | None) -> str:
own, target = str(principal["user_id"]), requested or str(principal["user_id"])
if target != own and target not in _scope_set(principal, "allowed_user_ids"):
raise ApiSecurityError(403, "OWNER_USER_SCOPE_DENIED")
return target
def _owner_group(principal: Mapping[str, Any], requested: str | None) -> str | None:
if requested is None:
return None
if requested != _group_id(principal) and requested not in _scope_set(principal, "allowed_group_ids"):
raise ApiSecurityError(403, "OWNER_GROUP_SCOPE_DENIED")
return requested
def _summary(records: list[dict[str, Any]]) -> dict[str, Any]:
count = len(records)
hermes = sum(1 for row in records if row.get("used_hermes"))
return {
"status": "ok",
"records": records,
"summary": store.load().summary(user_id if role != "owner" else None),
"total_executions": count,
"openclaw_independent_loops": count - hermes,
"hermes_orchestrated_loops": hermes,
"average_duration_ms": (
sum(int(row.get("duration_ms") or 0) for row in records) / count if count else 0
),
"token_usage": {
"input": sum(int(row.get("input_tokens") or 0) for row in records),
"output": sum(int(row.get("output_tokens") or 0) for row in records),
"total": sum(int(row.get("total_tokens") or 0) for row in records),
},
}
def record_agent_execution(payload: Mapping[str, Any]):
principal = authenticated_principal()
if set(payload) - _RECORD_FIELDS:
raise ApiSecurityError(403, "EXECUTION_FIELDS_FORBIDDEN")
normalized, role = dict(payload), principal["role"]
if role == "owner":
requested_user = payload.get("user_id")
if requested_user is not None and not isinstance(requested_user, str):
raise ApiSecurityError(403, "INVALID_EXECUTION_SCOPE")
target_user = _owner_user(principal, requested_user)
requested_scope = payload.get("visible_scope")
if requested_scope is not None and not isinstance(requested_scope, str):
raise ApiSecurityError(403, "INVALID_EXECUTION_SCOPE")
allowed_group = _owner_group(principal, requested_scope) if requested_scope and requested_scope != target_user else None
normalized["user_id"], normalized["visible_scope"] = target_user, allowed_group or target_user
else:
normalized["user_id"] = principal["user_id"]
normalized["visible_scope"] = _group_id(principal) or principal["user_id"]
try:
record = AgentExecutionRecord(**normalized)
except (TypeError, ValueError) as exc:
raise ApiSecurityError(403, "INVALID_EXECUTION_RECORD") from exc
return {"status": "ok", "record": _storage().append(record)}
def get_agent_observability(
user_id: str | None = None, group_id: str | None = None,
role: str = "user",
):
del role # wire compatibility only; server principal is authoritative
principal, pr_role = authenticated_principal(), None
pr_role = principal["role"]
if pr_role == "user":
if user_id not in (None, principal["user_id"]) or group_id is not None:
raise ApiSecurityError(403, "TENANT_SCOPE_DENIED")
q_user, q_group, q_role = principal["user_id"], None, "user"
elif pr_role == "group_admin":
own_group = _group_id(principal)
if group_id not in (None, own_group) or user_id not in (None, principal["user_id"]):
raise ApiSecurityError(403, "TENANT_SCOPE_DENIED")
q_user, q_group, q_role = principal["user_id"], own_group, "group_admin"
elif user_id is None and group_id is None:
q_user, q_group, q_role = principal["user_id"], None, "user"
elif group_id is not None:
q_user = _owner_user(principal, user_id) if user_id is not None else principal["user_id"]
q_group, q_role = _owner_group(principal, group_id), "group_admin"
else:
q_user, q_group, q_role = _owner_user(principal, user_id), None, "user"
records = _storage().query(q_user, q_group, q_role)
return {"status": "ok", "records": records, "summary": _summary(records)}
def _http(call: Callable[[], Any]) -> Any:
try:
return call()
except ApiSecurityError as exc:
if HTTPException is None:
raise
raise HTTPException(status_code=exc.status_code, detail=exc.code) from exc
def build_router():
if APIRouter is None:
return None
@@ -38,15 +180,13 @@ def build_router():
@router.post("/execution", operation_id="recordAgentExecution")
def record(payload: dict[str, Any]):
return record_agent_execution(payload)
return _http(lambda: record_agent_execution(payload))
@router.get("/overview", operation_id="getAgentObservabilityOverview")
def overview(user_id: str | None = None, group_id: str | None = None, role: str = "user"):
return get_agent_observability(user_id, group_id, role)
return _http(lambda: get_agent_observability(user_id, group_id, role))
return router
router = build_router()
__all__ = ["router", "record_agent_execution", "get_agent_observability"]
__all__ = ["ApiSecurityError", "authenticated_principal", "set_authenticator", "router", "record_agent_execution", "get_agent_observability"]
@@ -8,11 +8,15 @@ metadata are stored for Web UI statistics and audit.
from __future__ import annotations
import json
import re
from pathlib import Path
from typing import Any
from .agent_observability import AgentExecutionRecord, AgentObservability
# Constrain execution IDs (used as filename) to a single safe path segment.
_SAFE_EXECUTION_ID = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]*$")
class AgentObservabilityStore:
def __init__(self, root: str = "runtime/agent-observability"):
@@ -21,9 +25,17 @@ class AgentObservabilityStore:
self.records_dir.mkdir(parents=True, exist_ok=True)
def append(self, record: AgentExecutionRecord) -> dict[str, Any]:
path = self.records_dir / f"{record.execution_id}.json"
execution_id = str(record.execution_id or "")
if not _SAFE_EXECUTION_ID.fullmatch(execution_id) or ".." in execution_id:
raise ValueError(f"invalid execution_id: {execution_id!r}")
base = self.records_dir.resolve()
target = (self.records_dir / f"{execution_id}.json").resolve()
# The whitelist forbids separators/traversal, so the resolved target
# must sit directly inside the records dir; verify defensively.
if target.parent != base:
raise ValueError(f"invalid execution_id: {execution_id!r}")
data = record.to_dict()
path.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
target.write_text(json.dumps(data, ensure_ascii=False, indent=2), encoding="utf-8")
return data
def load(self) -> AgentObservability:
@@ -0,0 +1,122 @@
"""Staging HTTP overlay for the exact Task lineage supersession capability."""
from __future__ import annotations
from http.server import ThreadingHTTPServer
from typing import Any, Callable, Mapping
from urllib.parse import urlparse
from aios_task_lineage_supersession import (
DECISION_SCHEMA,
TaskLineageSupersessionController,
TaskLineageSupersessionError,
)
SUPERSESSION_ROUTE = "/v1/control/task-lineage-supersession"
def _status(error: TaskLineageSupersessionError) -> int:
return error.http_status
class LineageSupersessionEndpoint:
"""Framework-neutral endpoint; authentication must precede all parsing/mutation."""
def __init__(
self,
db_path,
verifier,
authenticate_control: Callable[[Mapping[str, str]], Any],
*,
clock=None,
identity_digest=None,
) -> None:
self.authenticate_control = authenticate_control
self.controller = TaskLineageSupersessionController(
db_path, verifier, clock=clock, identity_digest=identity_digest
)
def post(self, headers: Mapping[str, str], body: Mapping[str, Any]) -> tuple[int, dict[str, Any]]:
self.authenticate_control(headers)
try:
result = self.controller.supersede(body)
except TaskLineageSupersessionError as exc:
return _status(exc), {"error": exc.code}
return (200 if result.get("idempotent") else 201), result
def build_runtime():
"""Bind to the already-active recovery API and its formal Owner verifier lazily."""
try:
from aios_agent_task_api import APIConfig
from aios_agent_task_api_p0_failed_lease_recovery import (
P0FailedLeaseRecoveryHandler,
P0FailedLeaseRecoveryTaskApplication,
)
from aios_agent_task_protocol import TaskProtocolError
from aios_owner_approval_verifier import (
GrantRootOwnerApprovalVerifier,
OwnerApprovalVerifierError,
)
except ImportError as exc:
raise RuntimeError("LINEAGE_SUPERSESSION_RUNTIME_OVERLAY_UNAVAILABLE") from exc
# The active entrypoint layers failed- and active-lease recovery on top of
# expired recovery. Inherit that current layer so adding lineage does not
# silently remove either already-deployed recovery route.
class Application(P0FailedLeaseRecoveryTaskApplication):
def __init__(self, config: Any) -> None:
super().__init__(config)
try:
verifier = GrantRootOwnerApprovalVerifier()
except OwnerApprovalVerifierError as exc:
raise TaskProtocolError(str(exc), 503) from exc
self.lineage_supersession = LineageSupersessionEndpoint(
config.db_path, verifier, self.authenticate_control
)
def health(self) -> dict[str, Any]:
value = dict(super().health())
value.update({
"task_lineage_supersession": True,
"task_lineage_supersession_schema": DECISION_SCHEMA,
"task_lineage_supersession_exact_scope": True,
"task_lineage_supersession_exact_cas": True,
"task_lineage_supersession_append_only": True,
"task_lineage_supersession_caller_payload": False,
})
return value
class Handler(P0FailedLeaseRecoveryHandler):
app: Application | None = None
def do_POST(self) -> None:
path = urlparse(self.path).path.rstrip("/") or "/"
if path != SUPERSESSION_ROUTE:
super().do_POST()
return
def action() -> None:
if self.app is None:
raise TaskProtocolError("SERVICE_UNAVAILABLE", 503)
status, result = self.app.lineage_supersession.post(
self._headers(), self._body()
)
if "error" in result:
raise TaskProtocolError(str(result["error"]), status)
self._json(result, status)
self._run(action)
return APIConfig, Application, Handler
def serve(config=None) -> None:
APIConfig, Application, Handler = build_runtime()
config = config or APIConfig.from_env()
application = Application(config)
Handler.app = application
ThreadingHTTPServer((config.host, config.port), Handler).serve_forever()
if __name__ == "__main__":
serve()
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,811 @@
"""Canonical Task projection intent and same-identity recovery primitives.
This module is intentionally Task-Plane-owned. It does not open a network
listener and it does not authenticate callers; existing control-plane services
must enforce their normal service-side ``agent-task:control`` boundary before
calling it.
The recovery primitive has one purpose: converge a durable, complete
ProjectionIntent to exactly one canonical Task row. It never fabricates task
semantics from a Bridge binding and it never generates a replacement task id.
"""
from __future__ import annotations
from dataclasses import asdict, dataclass
from datetime import datetime, timezone
from enum import Enum
import hashlib
import json
from pathlib import Path
import re
import sqlite3
from typing import Any, Mapping, Sequence
TASK_ID_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{2,179}$")
JOB_ID_RE = TASK_ID_RE
SHA40_RE = re.compile(r"^[0-9a-f]{40}$")
SHA64_RE = re.compile(r"^[0-9a-f]{64}$")
BRANCH_RE = re.compile(r"^agent/[A-Za-z0-9._/-]{1,220}$")
SCHEMA = "aios.canonical-task-projection-intent.v1"
RECEIPT_SCHEMA = "aios.canonical-task-projection-receipt.v1"
class ProjectionRecoveryError(RuntimeError):
def __init__(self, code: str, status: int = 409) -> None:
super().__init__(code)
self.code = code
self.status = status
class ProjectionState(str, Enum):
PENDING = "PENDING"
CONVERGED = "CONVERGED"
DRIFT = "DRIFT"
FAILED = "FAILED"
BLOCKED = "BLOCKED"
UNKNOWN = "UNKNOWN"
BACKFILL_REQUIRED = "BACKFILL_REQUIRED"
def _now() -> str:
return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
def _canonical(value: object) -> bytes:
return json.dumps(
value,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
).encode("utf-8")
def sha256_json(value: object) -> str:
return hashlib.sha256(_canonical(value)).hexdigest()
def _string(value: object, code: str, pattern: re.Pattern[str] | None = None) -> str:
if not isinstance(value, str) or not value.strip():
raise ProjectionRecoveryError(code, 400)
result = value.strip()
if pattern is not None and pattern.fullmatch(result) is None:
raise ProjectionRecoveryError(code, 400)
return result
def _integer(value: object, code: str, *, minimum: int = 0) -> int:
if isinstance(value, bool) or not isinstance(value, int) or value < minimum:
raise ProjectionRecoveryError(code, 400)
return value
def _plain_mapping(value: object, code: str) -> dict[str, Any]:
if not isinstance(value, Mapping):
raise ProjectionRecoveryError(code, 400)
return {str(k): v for k, v in value.items()}
def _plain_list(value: object, code: str) -> list[Any]:
if not isinstance(value, list):
raise ProjectionRecoveryError(code, 400)
return list(value)
@dataclass(frozen=True, slots=True)
class CanonicalTaskPayload:
task_id: str
goal: str
instructions: str
success_criteria: tuple[Mapping[str, Any], ...]
constraints: Mapping[str, Any]
required_capabilities: tuple[str, ...]
metadata: Mapping[str, Any]
priority: int = 100
max_attempts: int = 20
deadline: str | None = None
@classmethod
def from_mapping(cls, value: Mapping[str, Any]) -> "CanonicalTaskPayload":
allowed = {
"task_id",
"goal",
"instructions",
"success_criteria",
"constraints",
"required_capabilities",
"metadata",
"priority",
"max_attempts",
"deadline",
}
if set(value) - allowed:
raise ProjectionRecoveryError("TASK_PAYLOAD_UNKNOWN_FIELDS", 400)
task_id = _string(value.get("task_id"), "TASK_ID_INVALID", TASK_ID_RE)
goal = _string(value.get("goal"), "TASK_GOAL_INVALID")
instructions = _string(value.get("instructions"), "TASK_INSTRUCTIONS_INVALID")
if len(goal) > 8192 or len(instructions) > 65536:
raise ProjectionRecoveryError("TASK_TEXT_TOO_LARGE", 413)
criteria_raw = _plain_list(value.get("success_criteria"), "TASK_SUCCESS_CRITERIA_INVALID")
criteria: list[Mapping[str, Any]] = []
for item in criteria_raw:
if not isinstance(item, Mapping):
raise ProjectionRecoveryError("TASK_SUCCESS_CRITERIA_INVALID", 400)
criteria.append(dict(item))
if not criteria:
raise ProjectionRecoveryError("TASK_SUCCESS_CRITERIA_REQUIRED", 400)
constraints = _plain_mapping(value.get("constraints"), "TASK_CONSTRAINTS_INVALID")
capabilities_raw = _plain_list(
value.get("required_capabilities", []),
"TASK_CAPABILITIES_INVALID",
)
capabilities: list[str] = []
for item in capabilities_raw:
capabilities.append(_string(item, "TASK_CAPABILITY_INVALID"))
# Recovery recreates the pristine canonical row. Initial execution
# projection remains the sole owner of runtime capabilities.
if capabilities:
raise ProjectionRecoveryError(
"TASK_RECOVERY_REQUIRES_PRISTINE_CAPABILITIES",
409,
)
metadata = _plain_mapping(value.get("metadata", {}), "TASK_METADATA_INVALID")
if "execution" in metadata or "execution_projection" in metadata:
raise ProjectionRecoveryError(
"TASK_RECOVERY_REQUIRES_PRISTINE_METADATA",
409,
)
priority = _integer(value.get("priority", 100), "TASK_PRIORITY_INVALID", minimum=0)
max_attempts = _integer(
value.get("max_attempts", 20),
"TASK_MAX_ATTEMPTS_INVALID",
minimum=1,
)
deadline = value.get("deadline")
if deadline is not None:
deadline = _string(deadline, "TASK_DEADLINE_INVALID")
return cls(
task_id=task_id,
goal=goal,
instructions=instructions,
success_criteria=tuple(criteria),
constraints=constraints,
required_capabilities=tuple(capabilities),
metadata=metadata,
priority=priority,
max_attempts=max_attempts,
deadline=deadline,
)
def public(self) -> dict[str, Any]:
return {
"task_id": self.task_id,
"goal": self.goal,
"instructions": self.instructions,
"success_criteria": [dict(x) for x in self.success_criteria],
"constraints": dict(self.constraints),
"required_capabilities": list(self.required_capabilities),
"metadata": dict(self.metadata),
"priority": self.priority,
"max_attempts": self.max_attempts,
"deadline": self.deadline,
}
@property
def payload_sha256(self) -> str:
return sha256_json(self.public())
@dataclass(frozen=True, slots=True)
class GovernedProjectionBinding:
governed_task_id: str
bridge_job_id: str
repository: str
branch: str
generation: int
base_oid: str
expected_branch_oid: str
@classmethod
def from_mapping(cls, value: Mapping[str, Any]) -> "GovernedProjectionBinding":
allowed = {
"governed_task_id",
"bridge_job_id",
"repository",
"branch",
"generation",
"base_oid",
"expected_branch_oid",
}
if set(value) != allowed:
raise ProjectionRecoveryError("GOVERNED_BINDING_FIELDS_INVALID", 400)
repository = _string(value["repository"], "REPOSITORY_INVALID")
if repository != "aios/aios":
raise ProjectionRecoveryError("REPOSITORY_MISMATCH", 409)
return cls(
governed_task_id=_string(
value["governed_task_id"],
"GOVERNED_TASK_ID_INVALID",
TASK_ID_RE,
),
bridge_job_id=_string(
value["bridge_job_id"],
"BRIDGE_JOB_ID_INVALID",
JOB_ID_RE,
),
repository=repository,
branch=_string(value["branch"], "BRANCH_INVALID", BRANCH_RE),
generation=_integer(value["generation"], "GENERATION_INVALID", minimum=1),
base_oid=_string(value["base_oid"], "BASE_OID_INVALID", SHA40_RE),
expected_branch_oid=_string(
value["expected_branch_oid"],
"EXPECTED_OID_INVALID",
SHA40_RE,
),
)
def public(self) -> dict[str, Any]:
return asdict(self)
@dataclass(frozen=True, slots=True)
class ProjectionSourceArtifact:
ref: str
sha256: str
@classmethod
def from_mapping(cls, value: Mapping[str, Any]) -> "ProjectionSourceArtifact":
if set(value) != {"ref", "sha256"}:
raise ProjectionRecoveryError("SOURCE_ARTIFACT_FIELDS_INVALID", 400)
return cls(
ref=_string(value["ref"], "SOURCE_ARTIFACT_REF_INVALID"),
sha256=_string(value["sha256"], "SOURCE_ARTIFACT_SHA_INVALID", SHA64_RE),
)
@dataclass(frozen=True, slots=True)
class CanonicalTaskProjectionIntent:
projection_id: str
projection_generation: int
idempotency_key: str
task: CanonicalTaskPayload
binding: GovernedProjectionBinding
source_artifact: ProjectionSourceArtifact
schema: str = SCHEMA
@classmethod
def from_mapping(cls, value: Mapping[str, Any]) -> "CanonicalTaskProjectionIntent":
allowed = {
"schema",
"projection_id",
"projection_generation",
"idempotency_key",
"task",
"governed_binding",
"source_artifact",
"task_payload_sha256",
}
if set(value) != allowed:
raise ProjectionRecoveryError("PROJECTION_INTENT_FIELDS_INVALID", 400)
if value["schema"] != SCHEMA:
raise ProjectionRecoveryError("PROJECTION_INTENT_SCHEMA_INVALID", 400)
task = CanonicalTaskPayload.from_mapping(
_plain_mapping(value["task"], "TASK_PAYLOAD_INVALID")
)
binding = GovernedProjectionBinding.from_mapping(
_plain_mapping(value["governed_binding"], "GOVERNED_BINDING_INVALID")
)
source = ProjectionSourceArtifact.from_mapping(
_plain_mapping(value["source_artifact"], "SOURCE_ARTIFACT_INVALID")
)
if binding.governed_task_id != task.task_id:
raise ProjectionRecoveryError("PROJECTION_TASK_IDENTITY_MISMATCH", 409)
expected_hash = _string(
value["task_payload_sha256"],
"TASK_PAYLOAD_SHA_INVALID",
SHA64_RE,
)
if task.payload_sha256 != expected_hash:
raise ProjectionRecoveryError("TASK_PAYLOAD_SHA_MISMATCH", 409)
projection_id = _string(
value["projection_id"],
"PROJECTION_ID_INVALID",
TASK_ID_RE,
)
idempotency_key = _string(
value["idempotency_key"],
"IDEMPOTENCY_KEY_INVALID",
TASK_ID_RE,
)
generation = _integer(
value["projection_generation"],
"PROJECTION_GENERATION_INVALID",
minimum=1,
)
return cls(
projection_id=projection_id,
projection_generation=generation,
idempotency_key=idempotency_key,
task=task,
binding=binding,
source_artifact=source,
)
def public(self) -> dict[str, Any]:
return {
"schema": self.schema,
"projection_id": self.projection_id,
"projection_generation": self.projection_generation,
"idempotency_key": self.idempotency_key,
"task": self.task.public(),
"governed_binding": self.binding.public(),
"source_artifact": asdict(self.source_artifact),
"task_payload_sha256": self.task.payload_sha256,
}
@property
def request_sha256(self) -> str:
return sha256_json(self.public())
class CanonicalTaskProjectionStore:
"""Task-Plane-owned intent/recovery store.
The caller must already be inside the Task Plane authorization boundary.
"""
def __init__(self, db_path: Path | str) -> None:
self.db_path = Path(db_path)
def _connect(self) -> sqlite3.Connection:
connection = sqlite3.connect(self.db_path, timeout=30.0)
connection.row_factory = sqlite3.Row
connection.execute("PRAGMA foreign_keys=ON")
connection.execute("PRAGMA busy_timeout=30000")
return connection
@staticmethod
def _columns(connection: sqlite3.Connection, table: str) -> dict[str, sqlite3.Row]:
return {
str(row["name"]): row
for row in connection.execute(f'PRAGMA table_info("{table}")')
}
@staticmethod
def _table_names(connection: sqlite3.Connection) -> set[str]:
return {
str(row[0])
for row in connection.execute(
"SELECT name FROM sqlite_master WHERE type='table'"
)
}
@classmethod
def _event_table(
cls,
connection: sqlite3.Connection,
) -> tuple[str, dict[str, sqlite3.Row]]:
tables = cls._table_names(connection)
for name in ("task_events", "events"):
if name in tables:
columns = cls._columns(connection, name)
if "task_id" in columns and "event_type" in columns:
return name, columns
raise ProjectionRecoveryError("TASK_EVENT_TABLE_UNSUPPORTED", 503)
@staticmethod
def _ensure_control_tables(connection: sqlite3.Connection) -> None:
connection.execute(
"""
CREATE TABLE IF NOT EXISTS task_projection_intents (
projection_id TEXT PRIMARY KEY,
task_id TEXT NOT NULL,
bridge_job_id TEXT NOT NULL,
projection_generation INTEGER NOT NULL,
idempotency_key TEXT NOT NULL,
request_sha256 TEXT NOT NULL,
task_payload_sha256 TEXT NOT NULL,
intent_json TEXT NOT NULL,
state TEXT NOT NULL,
last_error TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
UNIQUE(task_id, projection_generation),
UNIQUE(task_id, idempotency_key)
)
"""
)
connection.execute(
"""
CREATE TABLE IF NOT EXISTS task_projection_receipts (
projection_id TEXT PRIMARY KEY,
task_id TEXT NOT NULL,
bridge_job_id TEXT NOT NULL,
projection_generation INTEGER NOT NULL,
task_payload_sha256 TEXT NOT NULL,
canonical_revision INTEGER NOT NULL,
state TEXT NOT NULL,
created_new_row INTEGER NOT NULL,
observed_at TEXT NOT NULL,
receipt_json TEXT NOT NULL
)
"""
)
def persist_intent(
self,
intent: CanonicalTaskProjectionIntent,
) -> dict[str, Any]:
now = _now()
request_sha = intent.request_sha256
intent_text = _canonical(intent.public()).decode("utf-8")
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
self._ensure_control_tables(connection)
existing = connection.execute(
"""
SELECT * FROM task_projection_intents
WHERE task_id=? AND projection_generation=?
""",
(intent.task.task_id, intent.projection_generation),
).fetchone()
if existing is not None:
if (
str(existing["projection_id"]) != intent.projection_id
or str(existing["idempotency_key"]) != intent.idempotency_key
or str(existing["request_sha256"]) != request_sha
):
raise ProjectionRecoveryError(
"PROJECTION_INTENT_IDEMPOTENCY_CONFLICT",
409,
)
connection.commit()
return {
"projection_id": intent.projection_id,
"task_id": intent.task.task_id,
"state": str(existing["state"]),
"idempotent": True,
}
connection.execute(
"""
INSERT INTO task_projection_intents(
projection_id,task_id,bridge_job_id,projection_generation,
idempotency_key,request_sha256,task_payload_sha256,intent_json,
state,last_error,created_at,updated_at
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)
""",
(
intent.projection_id,
intent.task.task_id,
intent.binding.bridge_job_id,
intent.projection_generation,
intent.idempotency_key,
request_sha,
intent.task.payload_sha256,
intent_text,
ProjectionState.PENDING.value,
None,
now,
now,
),
)
connection.commit()
return {
"projection_id": intent.projection_id,
"task_id": intent.task.task_id,
"state": ProjectionState.PENDING.value,
"idempotent": False,
}
def _task_row_payload(
self,
row: sqlite3.Row,
) -> dict[str, Any]:
try:
success = json.loads(row["success_criteria_json"])
constraints = json.loads(row["constraints_json"])
capabilities = json.loads(row["required_capabilities_json"])
metadata = json.loads(row["metadata_json"])
except (TypeError, json.JSONDecodeError) as exc:
raise ProjectionRecoveryError("CANONICAL_TASK_JSON_INVALID", 409) from exc
if (
not isinstance(success, list)
or not isinstance(constraints, Mapping)
or not isinstance(capabilities, list)
or not isinstance(metadata, Mapping)
):
raise ProjectionRecoveryError("CANONICAL_TASK_SHAPE_INVALID", 409)
return {
"task_id": str(row["task_id"]),
"goal": str(row["goal"]),
"instructions": str(row["instructions"]),
"success_criteria": success,
"constraints": dict(constraints),
"required_capabilities": capabilities,
"metadata": dict(metadata),
"priority": int(row["priority"]),
"max_attempts": int(row["max_attempts"]),
"deadline": row["deadline"],
}
def _verify_existing_task(
self,
row: sqlite3.Row,
intent: CanonicalTaskProjectionIntent,
) -> int:
observed = self._task_row_payload(row)
observed_hash = sha256_json(observed)
if observed_hash != intent.task.payload_sha256:
raise ProjectionRecoveryError(
"CANONICAL_TASK_PROJECTION_IDENTITY_CONFLICT",
409,
)
return int(row["revision"])
@staticmethod
def _required_task_columns() -> set[str]:
return {
"task_id",
"goal",
"instructions",
"success_criteria_json",
"required_capabilities_json",
"constraints_json",
"metadata_json",
"priority",
"max_attempts",
"attempt",
"status",
"claimed_by",
"lease_token_hash",
"lease_expires_at",
"result_json",
"verification_json",
"revision",
"created_at",
"updated_at",
}
def _insert_task(
self,
connection: sqlite3.Connection,
intent: CanonicalTaskProjectionIntent,
) -> int:
columns = self._columns(connection, "tasks")
if not self._required_task_columns().issubset(columns):
raise ProjectionRecoveryError("TASK_SCHEMA_RECOVERY_UNSUPPORTED", 503)
now = _now()
values: dict[str, Any] = {
"task_id": intent.task.task_id,
"goal": intent.task.goal,
"instructions": intent.task.instructions,
"success_criteria_json": _canonical(
[dict(x) for x in intent.task.success_criteria]
).decode("utf-8"),
"required_capabilities_json": "[]",
"constraints_json": _canonical(dict(intent.task.constraints)).decode("utf-8"),
"metadata_json": _canonical(dict(intent.task.metadata)).decode("utf-8"),
"deadline": intent.task.deadline,
"priority": intent.task.priority,
"max_attempts": intent.task.max_attempts,
"attempt": 0,
"status": "QUEUED",
"claimed_by": None,
"lease_token_hash": None,
"lease_expires_at": None,
"result_json": None,
"verification_json": None,
"revision": 1,
"created_at": now,
"updated_at": now,
}
for optional in (
"result_digest",
"result_idempotency_key",
):
if optional in columns:
values[optional] = None
names = [name for name in values if name in columns]
placeholders = ",".join("?" for _ in names)
connection.execute(
f'INSERT INTO tasks ({",".join(chr(34)+n+chr(34) for n in names)}) '
f"VALUES ({placeholders})",
tuple(values[name] for name in names),
)
self._insert_task_created_event(connection, intent)
return 1
def _insert_task_created_event(
self,
connection: sqlite3.Connection,
intent: CanonicalTaskProjectionIntent,
) -> None:
table, columns = self._event_table(connection)
event = {
"schema": "aios.task-projection-recovery.event.v1",
"task_id": intent.task.task_id,
"bridge_job_id": intent.binding.bridge_job_id,
"projection_id": intent.projection_id,
"projection_generation": intent.projection_generation,
"task_payload_sha256": intent.task.payload_sha256,
"source_artifact_ref": intent.source_artifact.ref,
"source_artifact_sha256": intent.source_artifact.sha256,
"recovery": True,
}
values: dict[str, Any] = {
"task_id": intent.task.task_id,
"event_type": "TASK_CREATED",
}
payload_text = _canonical(event).decode("utf-8")
if "payload_json" in columns:
values["payload_json"] = payload_text
elif "payload" in columns:
values["payload"] = payload_text
else:
raise ProjectionRecoveryError("TASK_EVENT_PAYLOAD_UNSUPPORTED", 503)
if "created_at" in columns:
values["created_at"] = _now()
if "agent_id" in columns:
values["agent_id"] = "control-plane-projection-recovery"
if "actor_id" in columns:
values["actor_id"] = "control-plane-projection-recovery"
names = list(values)
connection.execute(
f'INSERT INTO "{table}" ({",".join(chr(34)+n+chr(34) for n in names)}) '
f'VALUES ({",".join("?" for _ in names)})',
tuple(values[name] for name in names),
)
def reconcile(
self,
intent: CanonicalTaskProjectionIntent,
) -> dict[str, Any]:
# Persist first so a crash before Task creation leaves a durable PENDING
# intent for startup/supervisor reconciliation.
self.persist_intent(intent)
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
self._ensure_control_tables(connection)
existing_receipt = connection.execute(
"SELECT * FROM task_projection_receipts WHERE projection_id=?",
(intent.projection_id,),
).fetchone()
if existing_receipt is not None:
if (
str(existing_receipt["task_id"]) != intent.task.task_id
or str(existing_receipt["bridge_job_id"]) != intent.binding.bridge_job_id
or str(existing_receipt["task_payload_sha256"])
!= intent.task.payload_sha256
):
raise ProjectionRecoveryError(
"PROJECTION_RECEIPT_IDENTITY_CONFLICT",
409,
)
connection.commit()
receipt = json.loads(existing_receipt["receipt_json"])
receipt["idempotent"] = True
return receipt
row = connection.execute(
"SELECT * FROM tasks WHERE task_id=?",
(intent.task.task_id,),
).fetchone()
created_new = False
if row is None:
revision = self._insert_task(connection, intent)
created_new = True
row = connection.execute(
"SELECT * FROM tasks WHERE task_id=?",
(intent.task.task_id,),
).fetchone()
if row is None:
raise ProjectionRecoveryError(
"TASK_RECOVERY_OBSERVATION_FAILED",
503,
)
else:
revision = self._verify_existing_task(row, intent)
# Observation after mutation is mandatory.
revision = self._verify_existing_task(row, intent)
observed_at = _now()
receipt = {
"schema": RECEIPT_SCHEMA,
"projection_id": intent.projection_id,
"governed_task_id": intent.binding.governed_task_id,
"canonical_task_id": intent.task.task_id,
"bridge_job_id": intent.binding.bridge_job_id,
"projection_generation": intent.projection_generation,
"task_payload_sha256": intent.task.payload_sha256,
"canonical_revision": revision,
"state": ProjectionState.CONVERGED.value,
"created_new_row": created_new,
"source_artifact": asdict(intent.source_artifact),
"observed_at": observed_at,
"idempotent": False,
}
connection.execute(
"""
INSERT INTO task_projection_receipts(
projection_id,task_id,bridge_job_id,projection_generation,
task_payload_sha256,canonical_revision,state,created_new_row,
observed_at,receipt_json
) VALUES (?,?,?,?,?,?,?,?,?,?)
""",
(
intent.projection_id,
intent.task.task_id,
intent.binding.bridge_job_id,
intent.projection_generation,
intent.task.payload_sha256,
revision,
ProjectionState.CONVERGED.value,
1 if created_new else 0,
observed_at,
_canonical(receipt).decode("utf-8"),
),
)
connection.execute(
"""
UPDATE task_projection_intents
SET state=?, last_error=NULL, updated_at=?
WHERE projection_id=?
""",
(
ProjectionState.CONVERGED.value,
observed_at,
intent.projection_id,
),
)
connection.commit()
return receipt
def get_projection(
self,
task_id: str,
) -> dict[str, Any] | None:
task_id = _string(task_id, "TASK_ID_INVALID", TASK_ID_RE)
with self._connect() as connection:
self._ensure_control_tables(connection)
intent = connection.execute(
"""
SELECT * FROM task_projection_intents
WHERE task_id=?
ORDER BY projection_generation DESC
LIMIT 1
""",
(task_id,),
).fetchone()
if intent is None:
return None
receipt = connection.execute(
"SELECT receipt_json FROM task_projection_receipts WHERE projection_id=?",
(intent["projection_id"],),
).fetchone()
return {
"projection_id": intent["projection_id"],
"task_id": task_id,
"bridge_job_id": intent["bridge_job_id"],
"projection_generation": int(intent["projection_generation"]),
"state": intent["state"],
"last_error": intent["last_error"],
"task_payload_sha256": intent["task_payload_sha256"],
"receipt": json.loads(receipt[0]) if receipt else None,
}
@@ -0,0 +1,44 @@
"""HTTP integration helpers for canonical Task projection recovery.
This module intentionally contains no HTTP server. Existing Task Plane /
projection-owner handlers should call ``recover_from_json`` only after their
normal service-side ``agent-task:control`` authorization succeeds.
"""
from __future__ import annotations
from pathlib import Path
from typing import Any, Mapping
from aios_agent_task_projection_recovery import (
CanonicalTaskProjectionIntent,
CanonicalTaskProjectionStore,
ProjectionRecoveryError,
)
def recover_from_json(
db_path: Path | str,
task_id: str,
body: Mapping[str, Any],
) -> tuple[int, dict[str, Any]]:
try:
intent = CanonicalTaskProjectionIntent.from_mapping(body)
if intent.task.task_id != task_id:
raise ProjectionRecoveryError("PROJECTION_TASK_IDENTITY_MISMATCH", 409)
result = CanonicalTaskProjectionStore(db_path).reconcile(intent)
return (200 if result.get("idempotent") else 201), result
except ProjectionRecoveryError as exc:
return exc.status, {"error": exc.code}
def get_projection(
db_path: Path | str,
task_id: str,
) -> tuple[int, dict[str, Any]]:
try:
result = CanonicalTaskProjectionStore(db_path).get_projection(task_id)
except ProjectionRecoveryError as exc:
return exc.status, {"error": exc.code}
if result is None:
return 404, {"error": "TASK_PROJECTION_NOT_FOUND"}
return 200, result
@@ -0,0 +1,940 @@
#!/usr/bin/env python3
"""Durable online task protocol for external AIOS agents.
The store is deliberately independent from Git/Gitea mutation. It provides a
small durable state machine for publishing work, atomic claim/lease ownership,
heartbeat, idempotent result submission, explicit server-side verification,
lease recovery, and audit events.
No plaintext agent credential or task lease token is persisted.
"""
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
import hashlib
import hmac
import json
from pathlib import Path
import secrets
import sqlite3
from typing import Any, Callable, Iterable, Mapping
import uuid
from aios_capability_contract import (
CAPABILITY_VOCABULARY_DIGEST,
has_capabilities,
normalize_required_capabilities,
)
TERMINAL_STATES = frozenset({"SUCCEEDED", "FAILED", "EXPIRED", "CANCELLED"})
ACTIVE_LEASE_STATES = frozenset({"CLAIMED", "RUNNING"})
TASK_STATES = frozenset({
"QUEUED", "CLAIMED", "RUNNING", "VERIFYING",
"SUCCEEDED", "FAILED", "EXPIRED", "CANCELLED",
})
VERIFY_DECISIONS = frozenset({"SUCCEEDED", "NEEDS_REWORK", "FAILED"})
class TaskProtocolError(RuntimeError):
def __init__(self, code: str, http_status: int = 409) -> None:
super().__init__(code)
self.code = code
self.http_status = http_status
@dataclass(frozen=True, slots=True)
class Principal:
kind: str
identifier: str
def utcnow() -> datetime:
return datetime.now(timezone.utc)
def iso(value: datetime) -> str:
return value.astimezone(timezone.utc).isoformat()
def parse_time(value: str | None) -> datetime | None:
if not value:
return None
parsed = datetime.fromisoformat(value)
if parsed.tzinfo is None:
parsed = parsed.replace(tzinfo=timezone.utc)
return parsed.astimezone(timezone.utc)
def canonical_json(value: Any) -> str:
return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
def digest_json(value: Any) -> str:
return hashlib.sha256(canonical_json(value).encode("utf-8")).hexdigest()
def _string_list(value: Iterable[object] | None, field: str, *, max_items: int = 128) -> list[str]:
if value is None:
return []
if isinstance(value, (str, bytes)) or not isinstance(value, Iterable):
raise TaskProtocolError(field.upper() + "_INVALID", 400)
result: list[str] = []
for item in value:
text = str(item).strip()
if not text or len(text) > 160:
raise TaskProtocolError(field.upper() + "_INVALID", 400)
result.append(text)
if len(result) > max_items:
raise TaskProtocolError(field.upper() + "_TOO_LARGE", 400)
return sorted(set(result))
def _json_object(value: object | None, field: str, *, max_bytes: int = 128 * 1024) -> dict[str, Any]:
if value is None:
return {}
if not isinstance(value, Mapping):
raise TaskProtocolError(field.upper() + "_INVALID", 400)
result = dict(value)
if len(canonical_json(result).encode("utf-8")) > max_bytes:
raise TaskProtocolError(field.upper() + "_TOO_LARGE", 413)
return result
def _criteria(value: object | None) -> list[Any]:
if value is None:
return []
if isinstance(value, (str, bytes)) or not isinstance(value, list):
raise TaskProtocolError("SUCCESS_CRITERIA_INVALID", 400)
if len(value) > 128:
raise TaskProtocolError("SUCCESS_CRITERIA_TOO_LARGE", 413)
if len(canonical_json(value).encode("utf-8")) > 128 * 1024:
raise TaskProtocolError("SUCCESS_CRITERIA_TOO_LARGE", 413)
return list(value)
class TaskStore:
"""SQLite-backed durable task store.
SQLite BEGIN IMMEDIATE serializes claims so two agents cannot successfully
claim the same queued task. WAL mode allows concurrent readers.
"""
def __init__(
self,
path: str | Path,
*,
now: Callable[[], datetime] = utcnow,
token_pepper: str = "",
) -> None:
self.path = Path(path)
self.path.parent.mkdir(parents=True, exist_ok=True)
self.now = now
self.token_pepper = token_pepper
self._initialize()
def _connect(self) -> sqlite3.Connection:
connection = sqlite3.connect(self.path, timeout=10, isolation_level=None)
connection.row_factory = sqlite3.Row
connection.execute("PRAGMA foreign_keys=ON")
connection.execute("PRAGMA journal_mode=WAL")
connection.execute("PRAGMA synchronous=FULL")
connection.execute("PRAGMA busy_timeout=10000")
return connection
def _initialize(self) -> None:
with self._connect() as connection:
connection.executescript(
"""
CREATE TABLE IF NOT EXISTS agents (
agent_id TEXT PRIMARY KEY,
agent_type TEXT NOT NULL,
agent_version TEXT NOT NULL,
capabilities_json TEXT NOT NULL,
capability_vocabulary_digest TEXT,
metadata_json TEXT NOT NULL,
token_hash TEXT NOT NULL UNIQUE,
status TEXT NOT NULL,
created_at TEXT NOT NULL,
last_seen_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS tasks (
task_id TEXT PRIMARY KEY,
goal TEXT NOT NULL,
instructions TEXT NOT NULL,
success_criteria_json TEXT NOT NULL,
required_capabilities_json TEXT NOT NULL,
capability_vocabulary_digest TEXT,
constraints_json TEXT NOT NULL,
metadata_json TEXT NOT NULL,
deadline TEXT,
priority INTEGER NOT NULL,
max_attempts INTEGER NOT NULL,
attempt INTEGER NOT NULL DEFAULT 0,
status TEXT NOT NULL,
claimed_by TEXT,
lease_token_hash TEXT,
lease_expires_at TEXT,
result_json TEXT,
result_digest TEXT,
result_idempotency_key TEXT,
verification_json TEXT,
revision INTEGER NOT NULL DEFAULT 1,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
FOREIGN KEY(claimed_by) REFERENCES agents(agent_id)
);
CREATE TABLE IF NOT EXISTS idempotency (
scope TEXT NOT NULL,
key TEXT NOT NULL,
payload_digest TEXT NOT NULL,
response_json TEXT NOT NULL,
created_at TEXT NOT NULL,
PRIMARY KEY(scope, key)
);
CREATE TABLE IF NOT EXISTS events (
event_id INTEGER PRIMARY KEY AUTOINCREMENT,
task_id TEXT,
agent_id TEXT,
event_type TEXT NOT NULL,
payload_json TEXT NOT NULL,
created_at TEXT NOT NULL
);
CREATE INDEX IF NOT EXISTS idx_tasks_queue
ON tasks(status, priority DESC, created_at);
CREATE INDEX IF NOT EXISTS idx_tasks_lease
ON tasks(status, lease_expires_at);
CREATE INDEX IF NOT EXISTS idx_events_task
ON events(task_id, event_id);
"""
)
self._ensure_additive_column(
connection, "agents", "capability_vocabulary_digest", "TEXT"
)
self._ensure_additive_column(
connection, "tasks", "capability_vocabulary_digest", "TEXT"
)
@staticmethod
def _ensure_additive_column(
connection: sqlite3.Connection, table: str, column: str, definition: str
) -> None:
"""Add C6 handshake storage without rewriting historical task rows."""
columns = {
str(row["name"])
for row in connection.execute(f"PRAGMA table_info({table})").fetchall()
}
if column not in columns:
connection.execute(f"ALTER TABLE {table} ADD COLUMN {column} {definition}")
@staticmethod
def _normalize_capability_vocabulary_digest(value: object | None) -> str | None:
"""Validate an opt-in formal vocabulary handshake.
An omitted digest is the explicit C5-compatible legacy mode. A
supplied digest has no downgrade path: it must name this exact formal
vocabulary before any durable state is changed.
"""
if value is None:
return None
if not isinstance(value, str) or value.strip().lower() != CAPABILITY_VOCABULARY_DIGEST:
raise TaskProtocolError("CAPABILITY_VOCABULARY_MISMATCH", 409)
return CAPABILITY_VOCABULARY_DIGEST
@staticmethod
def _negotiated_capability_vocabulary_matches(
*,
agent_digest: str | None,
task_digest: str | None,
requested_digest: str | None,
) -> bool:
"""Match only an explicitly negotiated formal task vocabulary.
Historical agents and tasks carry ``NULL`` and keep their C5 behavior.
A task admitted with a digest is never offered to or claimed by an
agent that did not register the same current vocabulary.
"""
if task_digest is None:
return True
return (
task_digest == CAPABILITY_VOCABULARY_DIGEST
and agent_digest == task_digest
and (requested_digest is None or requested_digest == task_digest)
)
def _token_hash(self, token: str) -> str:
return hashlib.sha256((self.token_pepper + "\0" + token).encode("utf-8")).hexdigest()
@staticmethod
def _row_task(row: sqlite3.Row, *, include_result: bool = True) -> dict[str, Any]:
value: dict[str, Any] = {
"task_id": row["task_id"],
"goal": row["goal"],
"instructions": row["instructions"],
"success_criteria": json.loads(row["success_criteria_json"]),
"required_capabilities": json.loads(row["required_capabilities_json"]),
"capability_vocabulary_digest": row["capability_vocabulary_digest"],
"constraints": json.loads(row["constraints_json"]),
"metadata": json.loads(row["metadata_json"]),
"deadline": row["deadline"],
"priority": row["priority"],
"max_attempts": row["max_attempts"],
"attempt": row["attempt"],
"status": row["status"],
"claimed_by": row["claimed_by"],
"lease_expires_at": row["lease_expires_at"],
"revision": row["revision"],
"created_at": row["created_at"],
"updated_at": row["updated_at"],
}
if include_result:
value["result"] = json.loads(row["result_json"]) if row["result_json"] else None
value["verification"] = (
json.loads(row["verification_json"]) if row["verification_json"] else None
)
return value
def _event(
self,
connection: sqlite3.Connection,
event_type: str,
*,
task_id: str | None = None,
agent_id: str | None = None,
payload: Mapping[str, Any] | None = None,
) -> None:
connection.execute(
"INSERT INTO events(task_id,agent_id,event_type,payload_json,created_at)"
" VALUES(?,?,?,?,?)",
(task_id, agent_id, event_type, canonical_json(dict(payload or {})), iso(self.now())),
)
def register_agent(
self,
*,
agent_id: str,
agent_type: str,
agent_version: str,
capabilities: Iterable[object],
metadata: Mapping[str, Any] | None = None,
capability_vocabulary_digest: str | None = None,
) -> dict[str, Any]:
agent_id = agent_id.strip()
agent_type = agent_type.strip()
agent_version = agent_version.strip()
if not agent_id or len(agent_id) > 160 or not agent_type or not agent_version:
raise TaskProtocolError("AGENT_IDENTITY_INVALID", 400)
caps = _string_list(capabilities, "capabilities")
meta = _json_object(metadata, "metadata")
vocabulary_digest = self._normalize_capability_vocabulary_digest(
capability_vocabulary_digest
)
token = "agt_" + secrets.token_urlsafe(32)
now = iso(self.now())
try:
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
connection.execute(
"INSERT INTO agents(agent_id,agent_type,agent_version,capabilities_json,"
"capability_vocabulary_digest,metadata_json,token_hash,status,created_at,last_seen_at)"
" VALUES(?,?,?,?,?,?,?,?,?,?)",
(
agent_id,
agent_type,
agent_version,
canonical_json(caps),
vocabulary_digest,
canonical_json(meta),
self._token_hash(token),
"ACTIVE",
now,
now,
),
)
self._event(
connection,
"AGENT_REGISTERED",
agent_id=agent_id,
payload={"agent_type": agent_type, "agent_version": agent_version},
)
connection.commit()
except sqlite3.IntegrityError as exc:
raise TaskProtocolError("AGENT_ALREADY_EXISTS", 409) from exc
return {
"agent_id": agent_id,
"agent_type": agent_type,
"agent_version": agent_version,
"capabilities": caps,
"capability_vocabulary_digest": vocabulary_digest,
"agent_token": token,
"credential_returned_once": True,
}
def authenticate_agent(self, token: str) -> dict[str, Any]:
if not token:
raise TaskProtocolError("UNAUTHORIZED", 401)
token_hash = self._token_hash(token)
with self._connect() as connection:
row = connection.execute(
"SELECT * FROM agents WHERE token_hash=? AND status='ACTIVE'",
(token_hash,),
).fetchone()
if row is None:
raise TaskProtocolError("UNAUTHORIZED", 401)
connection.execute(
"UPDATE agents SET last_seen_at=? WHERE agent_id=?",
(iso(self.now()), row["agent_id"]),
)
return {
"agent_id": row["agent_id"],
"agent_type": row["agent_type"],
"agent_version": row["agent_version"],
"capabilities": json.loads(row["capabilities_json"]),
"capability_vocabulary_digest": row["capability_vocabulary_digest"],
"metadata": json.loads(row["metadata_json"]),
}
def _idempotent_lookup(
self,
connection: sqlite3.Connection,
*,
scope: str,
key: str,
payload_digest: str,
) -> dict[str, Any] | None:
row = connection.execute(
"SELECT payload_digest,response_json FROM idempotency WHERE scope=? AND key=?",
(scope, key),
).fetchone()
if row is None:
return None
if not hmac.compare_digest(row["payload_digest"], payload_digest):
raise TaskProtocolError("IDEMPOTENCY_CONFLICT", 409)
return json.loads(row["response_json"])
def _idempotent_store(
self,
connection: sqlite3.Connection,
*,
scope: str,
key: str,
payload_digest: str,
response: Mapping[str, Any],
) -> None:
connection.execute(
"INSERT INTO idempotency(scope,key,payload_digest,response_json,created_at)"
" VALUES(?,?,?,?,?)",
(scope, key, payload_digest, canonical_json(dict(response)), iso(self.now())),
)
def create_task(
self,
*,
task_id: str | None,
goal: str,
instructions: str,
success_criteria: object | None,
required_capabilities: Iterable[object] | None = None,
capability_vocabulary_digest: str | None = None,
constraints: Mapping[str, Any] | None = None,
metadata: Mapping[str, Any] | None = None,
deadline: str | None = None,
priority: int = 0,
max_attempts: int = 3,
idempotency_key: str,
) -> dict[str, Any]:
task_id = (task_id or ("task-" + uuid.uuid4().hex)).strip()
goal = goal.strip()
instructions = instructions.strip()
idempotency_key = idempotency_key.strip()
if not task_id or len(task_id) > 180 or not goal or not instructions:
raise TaskProtocolError("TASK_BINDING_INVALID", 400)
if not idempotency_key or len(idempotency_key) > 160:
raise TaskProtocolError("IDEMPOTENCY_KEY_INVALID", 400)
if not isinstance(priority, int) or isinstance(priority, bool) or not -100 <= priority <= 100:
raise TaskProtocolError("PRIORITY_INVALID", 400)
if not isinstance(max_attempts, int) or isinstance(max_attempts, bool) or not 1 <= max_attempts <= 20:
raise TaskProtocolError("MAX_ATTEMPTS_INVALID", 400)
parsed_deadline = parse_time(deadline)
if parsed_deadline and parsed_deadline <= self.now():
raise TaskProtocolError("DEADLINE_INVALID", 400)
criteria = _criteria(success_criteria)
required = _string_list(required_capabilities, "required_capabilities")
normalized_required = normalize_required_capabilities(required)
if any(value.startswith("exact:") for value in normalized_required):
raise TaskProtocolError("TASK_CAPABILITY_UNKNOWN", 403)
vocabulary_digest = self._normalize_capability_vocabulary_digest(
capability_vocabulary_digest
)
constraints_value = _json_object(constraints, "constraints")
metadata_value = _json_object(metadata, "metadata")
payload = {
"task_id": task_id,
"goal": goal,
"instructions": instructions,
"success_criteria": criteria,
"required_capabilities": required,
"capability_vocabulary_digest": vocabulary_digest,
"constraints": constraints_value,
"metadata": metadata_value,
"deadline": iso(parsed_deadline) if parsed_deadline else None,
"priority": priority,
"max_attempts": max_attempts,
}
payload_digest = digest_json(payload)
now = iso(self.now())
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
replay = self._idempotent_lookup(
connection,
scope="create_task",
key=idempotency_key,
payload_digest=payload_digest,
)
if replay is not None:
connection.commit()
return {**replay, "idempotent": True}
try:
connection.execute(
"INSERT INTO tasks(task_id,goal,instructions,success_criteria_json,"
"required_capabilities_json,capability_vocabulary_digest,constraints_json,metadata_json,deadline,priority,"
"max_attempts,attempt,status,revision,created_at,updated_at)"
" VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
(
task_id,
goal,
instructions,
canonical_json(criteria),
canonical_json(required),
vocabulary_digest,
canonical_json(constraints_value),
canonical_json(metadata_value),
payload["deadline"],
priority,
max_attempts,
0,
"QUEUED",
1,
now,
now,
),
)
except sqlite3.IntegrityError as exc:
raise TaskProtocolError("TASK_ID_CONFLICT", 409) from exc
row = connection.execute("SELECT * FROM tasks WHERE task_id=?", (task_id,)).fetchone()
result = self._row_task(row)
self._idempotent_store(
connection,
scope="create_task",
key=idempotency_key,
payload_digest=payload_digest,
response=result,
)
self._event(connection, "TASK_CREATED", task_id=task_id, payload={"priority": priority})
connection.commit()
return result
def _expire_locked(self, connection: sqlite3.Connection) -> None:
now_dt = self.now()
now = iso(now_dt)
rows = connection.execute(
"SELECT * FROM tasks WHERE status IN ('QUEUED','CLAIMED','RUNNING')"
).fetchall()
for row in rows:
deadline = parse_time(row["deadline"])
if deadline is not None and deadline <= now_dt:
connection.execute(
"UPDATE tasks SET status='EXPIRED',claimed_by=NULL,lease_token_hash=NULL,"
"lease_expires_at=NULL,revision=revision+1,updated_at=? WHERE task_id=?",
(now, row["task_id"]),
)
self._event(connection, "TASK_EXPIRED", task_id=row["task_id"])
continue
if row["status"] in ACTIVE_LEASE_STATES:
lease_expires = parse_time(row["lease_expires_at"])
if lease_expires is not None and lease_expires <= now_dt:
if row["attempt"] >= row["max_attempts"]:
next_state = "FAILED"
else:
next_state = "QUEUED"
if next_state == "FAILED":
connection.execute(
"UPDATE tasks SET status=?,lease_token_hash=NULL,"
"lease_expires_at=NULL,revision=revision+1,updated_at=? WHERE task_id=?",
(next_state, now, row["task_id"]),
)
else:
connection.execute(
"UPDATE tasks SET status=?,claimed_by=NULL,lease_token_hash=NULL,"
"lease_expires_at=NULL,revision=revision+1,updated_at=? WHERE task_id=?",
(next_state, now, row["task_id"]),
)
self._event(
connection,
"LEASE_EXPIRED",
task_id=row["task_id"],
agent_id=row["claimed_by"],
payload={"next_state": next_state, "attempt": row["attempt"]},
)
def expire_leases(self) -> int:
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
before = connection.total_changes
self._expire_locked(connection)
changed = connection.total_changes - before
connection.commit()
return changed
def poll_tasks(
self,
*,
agent_id: str,
limit: int = 20,
capability_vocabulary_digest: str | None = None,
) -> list[dict[str, Any]]:
if not 1 <= int(limit) <= 100:
raise TaskProtocolError("LIMIT_INVALID", 400)
requested_digest = self._normalize_capability_vocabulary_digest(
capability_vocabulary_digest
)
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
agent = connection.execute(
"SELECT capabilities_json,capability_vocabulary_digest FROM agents "
"WHERE agent_id=? AND status='ACTIVE'",
(agent_id,),
).fetchone()
if agent is None:
raise TaskProtocolError("AGENT_NOT_FOUND", 404)
self._expire_locked(connection)
capabilities = json.loads(agent["capabilities_json"])
rows = connection.execute(
"SELECT * FROM tasks WHERE status='QUEUED' ORDER BY priority DESC,created_at ASC"
).fetchall()
result = []
for row in rows:
required = json.loads(row["required_capabilities_json"])
if (
self._negotiated_capability_vocabulary_matches(
agent_digest=agent["capability_vocabulary_digest"],
task_digest=row["capability_vocabulary_digest"],
requested_digest=requested_digest,
)
and has_capabilities(capabilities, required)
):
result.append(self._row_task(row, include_result=False))
if len(result) >= int(limit):
break
connection.commit()
return result
def _agent_capabilities(self, connection: sqlite3.Connection, agent_id: str) -> set[str]:
row = connection.execute(
"SELECT capabilities_json FROM agents WHERE agent_id=? AND status='ACTIVE'",
(agent_id,),
).fetchone()
if row is None:
raise TaskProtocolError("AGENT_NOT_FOUND", 404)
return set(json.loads(row["capabilities_json"]))
def claim_task(
self,
*,
task_id: str,
agent_id: str,
lease_seconds: int = 300,
capability_vocabulary_digest: str | None = None,
) -> dict[str, Any]:
if not isinstance(lease_seconds, int) or isinstance(lease_seconds, bool) or not 30 <= lease_seconds <= 3600:
raise TaskProtocolError("LEASE_SECONDS_INVALID", 400)
requested_digest = self._normalize_capability_vocabulary_digest(
capability_vocabulary_digest
)
lease_token = "lease_" + secrets.token_urlsafe(32)
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
row = connection.execute("SELECT * FROM tasks WHERE task_id=?", (task_id,)).fetchone()
if row is None:
raise TaskProtocolError("TASK_NOT_FOUND", 404)
agent = connection.execute(
"SELECT capabilities_json,capability_vocabulary_digest FROM agents "
"WHERE agent_id=? AND status='ACTIVE'",
(agent_id,),
).fetchone()
if agent is None:
raise TaskProtocolError("AGENT_NOT_FOUND", 404)
if not self._negotiated_capability_vocabulary_matches(
agent_digest=agent["capability_vocabulary_digest"],
task_digest=row["capability_vocabulary_digest"],
requested_digest=requested_digest,
):
raise TaskProtocolError("CAPABILITY_VOCABULARY_MISMATCH", 409)
self._expire_locked(connection)
row = connection.execute("SELECT * FROM tasks WHERE task_id=?", (task_id,)).fetchone()
if row["status"] != "QUEUED":
raise TaskProtocolError("TASK_NOT_CLAIMABLE", 409)
capabilities = set(json.loads(agent["capabilities_json"]))
required = json.loads(row["required_capabilities_json"])
if not has_capabilities(capabilities, required):
raise TaskProtocolError("AGENT_CAPABILITY_MISMATCH", 403)
attempt = int(row["attempt"]) + 1
if attempt > int(row["max_attempts"]):
raise TaskProtocolError("TASK_ATTEMPTS_EXHAUSTED", 409)
expires = self.now() + timedelta(seconds=lease_seconds)
now = iso(self.now())
updated = connection.execute(
"UPDATE tasks SET status='CLAIMED',claimed_by=?,lease_token_hash=?,"
"lease_expires_at=?,attempt=?,revision=revision+1,updated_at=? "
"WHERE task_id=? AND status='QUEUED'",
(
agent_id,
self._token_hash(lease_token),
iso(expires),
attempt,
now,
task_id,
),
)
if updated.rowcount != 1:
raise TaskProtocolError("TASK_CLAIM_CONFLICT", 409)
self._event(
connection,
"TASK_CLAIMED",
task_id=task_id,
agent_id=agent_id,
payload={"attempt": attempt, "lease_expires_at": iso(expires)},
)
row = connection.execute("SELECT * FROM tasks WHERE task_id=?", (task_id,)).fetchone()
connection.commit()
return {
**self._row_task(row, include_result=False),
"lease_token": lease_token,
"lease_token_returned_once": True,
}
def _validate_lease(
self,
connection: sqlite3.Connection,
*,
task_id: str,
agent_id: str,
lease_token: str,
allowed_states: frozenset[str] = ACTIVE_LEASE_STATES,
) -> sqlite3.Row:
self._expire_locked(connection)
row = connection.execute("SELECT * FROM tasks WHERE task_id=?", (task_id,)).fetchone()
if row is None:
raise TaskProtocolError("TASK_NOT_FOUND", 404)
if row["status"] not in allowed_states:
raise TaskProtocolError("TASK_LEASE_NOT_ACTIVE", 409)
if row["claimed_by"] != agent_id:
raise TaskProtocolError("TASK_LEASE_OWNER_MISMATCH", 403)
if not row["lease_token_hash"] or not hmac.compare_digest(
row["lease_token_hash"], self._token_hash(lease_token)
):
raise TaskProtocolError("TASK_LEASE_TOKEN_INVALID", 403)
expires = parse_time(row["lease_expires_at"])
if expires is None or expires <= self.now():
raise TaskProtocolError("TASK_LEASE_EXPIRED", 409)
return row
def heartbeat(
self,
*,
task_id: str,
agent_id: str,
lease_token: str,
progress: Mapping[str, Any] | None = None,
lease_seconds: int = 300,
) -> dict[str, Any]:
if not 30 <= int(lease_seconds) <= 3600:
raise TaskProtocolError("LEASE_SECONDS_INVALID", 400)
progress_value = _json_object(progress, "progress", max_bytes=64 * 1024)
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
self._validate_lease(
connection,
task_id=task_id,
agent_id=agent_id,
lease_token=lease_token,
)
expires = self.now() + timedelta(seconds=int(lease_seconds))
now = iso(self.now())
connection.execute(
"UPDATE tasks SET status='RUNNING',lease_expires_at=?,revision=revision+1,"
"updated_at=? WHERE task_id=?",
(iso(expires), now, task_id),
)
self._event(
connection,
"TASK_HEARTBEAT",
task_id=task_id,
agent_id=agent_id,
payload={"progress": progress_value, "lease_expires_at": iso(expires)},
)
row = connection.execute("SELECT * FROM tasks WHERE task_id=?", (task_id,)).fetchone()
connection.commit()
return self._row_task(row, include_result=False)
def submit_result(
self,
*,
task_id: str,
agent_id: str,
lease_token: str,
idempotency_key: str,
result: Mapping[str, Any],
) -> dict[str, Any]:
idempotency_key = idempotency_key.strip()
if not idempotency_key or len(idempotency_key) > 160:
raise TaskProtocolError("IDEMPOTENCY_KEY_INVALID", 400)
result_value = _json_object(result, "result", max_bytes=1024 * 1024)
result_digest = digest_json(result_value)
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
row = connection.execute("SELECT * FROM tasks WHERE task_id=?", (task_id,)).fetchone()
if row is None:
raise TaskProtocolError("TASK_NOT_FOUND", 404)
if row["result_idempotency_key"] == idempotency_key:
if not row["result_digest"] or not hmac.compare_digest(row["result_digest"], result_digest):
raise TaskProtocolError("IDEMPOTENCY_CONFLICT", 409)
connection.commit()
return {**self._row_task(row), "idempotent": True}
self._validate_lease(
connection,
task_id=task_id,
agent_id=agent_id,
lease_token=lease_token,
)
now = iso(self.now())
connection.execute(
"UPDATE tasks SET status='VERIFYING',result_json=?,result_digest=?,"
"result_idempotency_key=?,lease_token_hash=NULL,lease_expires_at=NULL,"
"revision=revision+1,updated_at=? WHERE task_id=?",
(
canonical_json(result_value),
result_digest,
idempotency_key,
now,
task_id,
),
)
self._event(
connection,
"RESULT_SUBMITTED",
task_id=task_id,
agent_id=agent_id,
payload={"result_digest": result_digest, "idempotency_key": idempotency_key},
)
row = connection.execute("SELECT * FROM tasks WHERE task_id=?", (task_id,)).fetchone()
connection.commit()
return self._row_task(row)
def verify_task(
self,
*,
task_id: str,
decision: str,
verification: Mapping[str, Any] | None = None,
) -> dict[str, Any]:
decision = decision.strip().upper()
if decision not in VERIFY_DECISIONS:
raise TaskProtocolError("VERIFICATION_DECISION_INVALID", 400)
verification_value = _json_object(verification, "verification")
verification_value = {"decision": decision, **verification_value}
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
row = connection.execute("SELECT * FROM tasks WHERE task_id=?", (task_id,)).fetchone()
if row is None:
raise TaskProtocolError("TASK_NOT_FOUND", 404)
if row["status"] != "VERIFYING":
raise TaskProtocolError("TASK_NOT_VERIFYING", 409)
if decision == "SUCCEEDED":
next_state = "SUCCEEDED"
elif decision == "FAILED":
next_state = "FAILED"
elif int(row["attempt"]) >= int(row["max_attempts"]):
next_state = "FAILED"
verification_value["reason"] = verification_value.get(
"reason", "MAX_ATTEMPTS_EXHAUSTED"
)
else:
next_state = "QUEUED"
now = iso(self.now())
if next_state in {"SUCCEEDED", "FAILED"}:
connection.execute(
"UPDATE tasks SET status=?,claimed_by=NULL,lease_token_hash=NULL,"
"lease_expires_at=NULL,verification_json=?,revision=revision+1,updated_at=? "
"WHERE task_id=?",
(next_state, canonical_json(verification_value), now, task_id),
)
else:
connection.execute(
"UPDATE tasks SET status=?,claimed_by=NULL,lease_token_hash=NULL,"
"lease_expires_at=NULL,verification_json=?,revision=revision+1,updated_at=? "
"WHERE task_id=?",
(next_state, canonical_json(verification_value), now, task_id),
)
self._event(
connection,
"TASK_VERIFIED",
task_id=task_id,
agent_id=row["claimed_by"],
payload={"decision": decision, "next_state": next_state},
)
updated = connection.execute("SELECT * FROM tasks WHERE task_id=?", (task_id,)).fetchone()
connection.commit()
return self._row_task(updated)
def cancel_task(self, *, task_id: str, reason: str = "") -> dict[str, Any]:
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
row = connection.execute("SELECT * FROM tasks WHERE task_id=?", (task_id,)).fetchone()
if row is None:
raise TaskProtocolError("TASK_NOT_FOUND", 404)
if row["status"] in TERMINAL_STATES:
connection.commit()
return self._row_task(row)
connection.execute(
"UPDATE tasks SET status='CANCELLED',lease_token_hash=NULL,"
"lease_expires_at=NULL,revision=revision+1,updated_at=? WHERE task_id=?",
(iso(self.now()), task_id),
)
self._event(connection, "TASK_CANCELLED", task_id=task_id, payload={"reason": reason})
updated = connection.execute("SELECT * FROM tasks WHERE task_id=?", (task_id,)).fetchone()
connection.commit()
return self._row_task(updated)
def get_task(self, task_id: str) -> dict[str, Any]:
self.expire_leases()
with self._connect() as connection:
row = connection.execute("SELECT * FROM tasks WHERE task_id=?", (task_id,)).fetchone()
if row is None:
raise TaskProtocolError("TASK_NOT_FOUND", 404)
return self._row_task(row)
def get_events(self, task_id: str, *, limit: int = 200) -> list[dict[str, Any]]:
with self._connect() as connection:
rows = connection.execute(
"SELECT * FROM events WHERE task_id=? ORDER BY event_id DESC LIMIT ?",
(task_id, max(1, min(int(limit), 1000))),
).fetchall()
return [
{
"event_id": row["event_id"],
"task_id": row["task_id"],
"agent_id": row["agent_id"],
"event_type": row["event_type"],
"payload": json.loads(row["payload_json"]),
"created_at": row["created_at"],
}
for row in reversed(rows)
]
@@ -0,0 +1,192 @@
"""Fail-closed Authority Git side-effect boundary.
Only this module may issue the narrowly scoped ref CAS primitives used by the
durable executor. It never accepts force updates, arbitrary refs, tags, or
direct production mutations.
"""
from __future__ import annotations
from dataclasses import dataclass
import os
from pathlib import Path
import re
import subprocess
import tempfile
from typing import Mapping, Sequence
OID_RE = re.compile(r"^[0-9a-f]{40}$")
TASK_BRANCH_RE = re.compile(r"^refs/heads/agent/[A-Za-z0-9][A-Za-z0-9._/-]{2,179}$")
class AuthorityGitError(RuntimeError):
pass
def _oid(value: str, field: str) -> str:
if not isinstance(value, str) or not OID_RE.fullmatch(value):
raise AuthorityGitError(f"{field}_INVALID")
return value
@dataclass(frozen=True, slots=True)
class AuthorityGitExecutor:
git_dir: Path
worktree_root: Path = Path("/var/lib/aios-gitops/worktrees")
def __post_init__(self) -> None:
if not self.git_dir.is_absolute() or self.git_dir.is_symlink():
raise AuthorityGitError("AUTHORITY_GIT_DIR_INVALID")
if not self.worktree_root.is_absolute() or self.worktree_root.is_symlink():
raise AuthorityGitError("WORKTREE_ROOT_INVALID")
def _git(self, args: Sequence[str], *, check: bool = True) -> str:
result = subprocess.run(
["git", f"--git-dir={self.git_dir}", *args],
text=True,
capture_output=True,
check=False,
)
if check and result.returncode != 0:
raise AuthorityGitError("AUTHORITY_GIT_COMMAND_FAILED")
return result.stdout.strip()
def read_ref(self, ref: str) -> str | None:
if not ref.startswith("refs/") or ref.startswith(("refs/tags/", "refs/remotes/")):
raise AuthorityGitError("REF_READ_SCOPE_DENIED")
value = self._git(["rev-parse", "--verify", ref], check=False)
return value if value and OID_RE.fullmatch(value) else None
def commit_exists(self, oid: str) -> bool:
_oid(oid, "commit_oid")
result = subprocess.run(["git", f"--git-dir={self.git_dir}", "cat-file", "-e", oid + "^{commit}"], capture_output=True, check=False)
return result.returncode == 0
def ancestry_check(self, older_oid: str, newer_oid: str) -> bool:
_oid(older_oid, "older_oid")
_oid(newer_oid, "newer_oid")
result = subprocess.run(
["git", f"--git-dir={self.git_dir}", "merge-base", "--is-ancestor", older_oid, newer_oid],
capture_output=True,
check=False,
)
if result.returncode not in (0, 1):
raise AuthorityGitError("ANCESTRY_CHECK_FAILED")
return result.returncode == 0
def create_detached_worktree(
self,
base_oid: str,
worktree_name: str,
root: Path | None = None,
) -> Path:
_oid(base_oid, "base_oid")
if not worktree_name or "/" in worktree_name or "\\" in worktree_name or worktree_name in {".", ".."}:
raise AuthorityGitError("WORKTREE_NAME_INVALID")
parent = root or self.worktree_root
if not parent.is_absolute() or parent.is_symlink():
raise AuthorityGitError("WORKTREE_ROOT_INVALID")
path = parent / worktree_name
if path.exists() or path.is_symlink():
raise AuthorityGitError("WORKTREE_ALREADY_EXISTS")
parent.mkdir(parents=True, exist_ok=True)
self._git(["worktree", "add", "--detach", str(path), base_oid])
return path
def create_task_branch(self, branch: str, base_oid: str) -> str:
"""Create an isolated agent branch once, using an empty-ref CAS."""
ref = branch if branch.startswith("refs/heads/") else "refs/heads/" + branch
if not TASK_BRANCH_RE.fullmatch(ref):
raise AuthorityGitError("TASK_BRANCH_SCOPE_DENIED")
_oid(base_oid, "base_oid")
if not self.commit_exists(base_oid):
raise AuthorityGitError("BASE_COMMIT_NOT_FOUND")
current = self.read_ref(ref)
if current is not None:
if current == base_oid:
return current
raise AuthorityGitError("AUTHORITY_BRANCH_EXPECTATION_MISMATCH")
self._git(["update-ref", ref, base_oid, "0" * 40])
readback = self.read_ref(ref)
if readback != base_oid:
raise AuthorityGitError("AUTHORITY_CAS_READBACK_MISMATCH")
return readback
def advance_task_branch_CAS(self, branch: str, expected_oid: str, new_oid: str) -> str:
if not TASK_BRANCH_RE.fullmatch(branch):
raise AuthorityGitError("TASK_BRANCH_SCOPE_DENIED")
_oid(expected_oid, "expected_oid")
_oid(new_oid, "new_oid")
current = self.read_ref(branch)
if current != expected_oid:
if current == new_oid:
return new_oid
raise AuthorityGitError("AUTHORITY_BRANCH_EXPECTATION_MISMATCH")
if not self.ancestry_check(expected_oid, new_oid):
raise AuthorityGitError("NEW_OID_NOT_DESCENDANT")
self._git(["update-ref", branch, new_oid, expected_oid])
readback = self.read_ref(branch)
if readback != new_oid:
raise AuthorityGitError("AUTHORITY_CAS_READBACK_MISMATCH")
return readback
def ff_CAS_main_after_verified_protected_merge(self, expected_oid: str, new_oid: str) -> str:
_oid(expected_oid, "expected_oid")
_oid(new_oid, "new_oid")
if not self.ancestry_check(expected_oid, new_oid):
raise AuthorityGitError("MAIN_FF_REQUIRED")
current = self.read_ref("refs/heads/main")
if current != expected_oid:
if current == new_oid:
return new_oid
raise AuthorityGitError("AUTHORITY_MAIN_EXPECTATION_MISMATCH")
self._git(["update-ref", "refs/heads/main", new_oid, expected_oid])
readback = self.read_ref("refs/heads/main")
if readback != new_oid:
raise AuthorityGitError("AUTHORITY_MAIN_CAS_READBACK_MISMATCH")
return readback
def commit_file_in_isolated_worktree(
self,
*,
base_oid: str,
files: Mapping[str, bytes],
message: str,
worktree_name: str,
) -> str:
_oid(base_oid, "base_oid")
if not worktree_name or "/" in worktree_name or "\\" in worktree_name or worktree_name in {".", ".."}:
raise AuthorityGitError("WORKTREE_NAME_INVALID")
root = self.worktree_root / worktree_name
if root.exists() or root.is_symlink():
raise AuthorityGitError("WORKTREE_ALREADY_EXISTS")
self.worktree_root.mkdir(parents=True, exist_ok=True)
self._git(["worktree", "add", "--detach", str(root), base_oid])
try:
for relative, content in files.items():
path = Path(relative)
if path.is_absolute() or ".." in path.parts or not path.parts or path.parts[0] == ".git":
raise AuthorityGitError("FILE_PATH_INVALID")
target = root / path
target.parent.mkdir(parents=True, exist_ok=True)
target.write_bytes(content)
result = subprocess.run(
["git", "-C", str(root), "add", "--", *files.keys()],
text=True,
capture_output=True,
check=False,
)
if result.returncode != 0:
raise AuthorityGitError("WORKTREE_ADD_FAILED")
result = subprocess.run(
["git", "-C", str(root), "-c", "user.name=aios-durable-job", "-c", "user.email=aios-durable-job@localhost", "commit", "--no-verify", "-m", message],
text=True,
capture_output=True,
check=False,
)
if result.returncode != 0:
raise AuthorityGitError("WORKTREE_COMMIT_FAILED")
return subprocess.check_output(["git", "-C", str(root), "rev-parse", "HEAD"], text=True).strip()
finally:
subprocess.run(["git", f"--git-dir={self.git_dir}", "worktree", "remove", "--force", str(root)], capture_output=True, check=False)
@@ -0,0 +1,2 @@
"""Implementation Baseline 1.1 integrations for existing AIOS authorities."""
BASELINE_VERSION = '1.1'
File diff suppressed because it is too large Load Diff
+281
View File
@@ -0,0 +1,281 @@
"""Executable validation profile and thin Action client.
This supplied ZIP does not establish the online Task authority. Consequently
the runnable profile creates an explicitly isolated validation authority. Online
activation requires the authority mapping gates documented in this package.
"""
from __future__ import annotations
import argparse
import json
import os
from pathlib import Path
import secrets
import re
import signal
import stat
import threading
import time
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey,Ed25519PublicKey
from task_plane.store import TaskPlaneStore
from aios_gitops_job_controller import JobStore
from .authorization import AuthorizationService,sign_owner_delegation_grant
from .host import SystemdHost
from .lineage import LineageLedger
from .operations import OperationRegistry
from .runtime_contract import validate_contract
from .service import BaselineService,Identity
from .worker_policy import WorkerProfile
def _unique_object(pairs):
result={}
for key,value in pairs:
if key in result: raise ValueError('DUPLICATE_JSON_KEY')
result[key]=value
return result
def read_json(path):
return json.loads(_safe_read(Path(path).absolute()).decode('utf-8'),object_pairs_hook=_unique_object,
parse_constant=lambda _value: (_ for _ in ()).throw(ValueError('NON_FINITE_JSON')))
def _path(value):
if not isinstance(value,str): raise ValueError('PATH_INVALID')
prefix='${CREDENTIALS_DIRECTORY}/'
if value.startswith(prefix):
directory=os.environ.get('CREDENTIALS_DIRECTORY')
name=value[len(prefix):]
if not directory or '/' in name or name in ('','.','..'): raise ValueError('CREDENTIAL_PATH_INVALID')
value=str(Path(directory)/name)
if '$' in value or not Path(value).is_absolute() or '..' in Path(value).parts:
raise ValueError('PATH_MUST_BE_EXPLICIT_ABSOLUTE')
return Path(value)
def _safe_read(path,*,private=False):
path=_path(str(path))
fd=os.open(path,os.O_RDONLY|os.O_NOFOLLOW|os.O_CLOEXEC)
try:
info=os.fstat(fd)
forbidden=0o077 if private else 0o022
if not stat.S_ISREG(info.st_mode) or info.st_uid not in {0,os.geteuid()} or info.st_mode & forbidden or info.st_size>65536:
raise ValueError('CONFIG_OR_SECRET_FILE_PERMISSIONS_INVALID')
with os.fdopen(fd,'rb',closefd=False) as stream:
data=stream.read(65537)
if len(data)>65536: raise ValueError('CONFIG_OR_SECRET_FILE_TOO_LARGE')
return data
finally: os.close(fd)
def secret(path):
return _safe_read(path,private=True)
def load_configuration(path):
config=read_json(path)
required={'schema','profile','state_dir','listen','machine_key_id','machine_key_file',
'owner_public_key_files','owner_key_scopes','identities','workers','contracts','operations','host_bindings'}
if type(config) is not dict or set(config)-{'receipt_public_key_files'}!=required or config['schema']!='aios.baseline-server-config.v1':
raise ValueError('SERVER_CONFIG_FIELDS_INVALID')
if config['profile']!='isolated-validation': raise ValueError('PROFILE_REQUIRES_VERIFIED_AUTHORITY_ADAPTER')
listen=config['listen']
if type(listen) is not dict or set(listen)!={'host','port'} or listen['host']!='127.0.0.1' or type(listen['port']) is not int or not 1024<=listen['port']<=65535:
raise ValueError('LOOPBACK_LISTENER_REQUIRED')
state=_path(config['state_dir'])
if state.is_symlink() or state==Path('/') or state.name in {'aios','active-runtime','app'}:
raise ValueError('ISOLATED_STATE_PATH_REQUIRED')
if not isinstance(config['machine_key_id'],str) or not config['machine_key_id']: raise ValueError('MACHINE_ID_REQUIRED')
machine=serialization.load_pem_private_key(secret(config['machine_key_file']),password=None)
if not isinstance(machine,Ed25519PrivateKey): raise ValueError('ED25519_MACHINE_KEY_REQUIRED')
keys={}
for key_id,filename in config['owner_public_key_files'].items():
public=serialization.load_pem_public_key(_safe_read(filename))
if not isinstance(public,Ed25519PublicKey): raise ValueError('ED25519_OWNER_KEY_REQUIRED')
keys[key_id]=public
if not keys: raise ValueError('OWNER_TRUST_ROOT_REQUIRED')
receipt_keys={}
for key_id,filename in config.get('receipt_public_key_files',{}).items():
public=serialization.load_pem_public_key(_safe_read(filename))
if not isinstance(public,Ed25519PublicKey): raise ValueError('ED25519_RECEIPT_KEY_REQUIRED')
receipt_keys[key_id]=public
tokens={}
for entry in config['identities']:
if type(entry) is not dict or set(entry)-{'token_file','tenant_id','subject_id','role','worker_id'}:
raise ValueError('IDENTITY_FIELDS_INVALID')
token=secret(entry['token_file']).decode().strip()
if len(token)<32 or len(token)>512 or any(x.isspace() for x in token) or token in tokens: raise ValueError('TOKEN_INVALID_OR_DUPLICATE')
identity=Identity(**{k:v for k,v in entry.items() if k!='token_file'})
if identity.role not in {'user','owner','worker'} or any(not isinstance(v,str) or not v for v in (identity.tenant_id,identity.subject_id)):
raise ValueError('IDENTITY_INVALID')
if (identity.role=='worker') != bool(identity.worker_id): raise ValueError('WORKER_IDENTITY_INVALID')
tokens[token]=identity
workers={}
for entry in config['workers']:
worker=WorkerProfile.from_dict(entry)
if worker.worker_id in workers: raise ValueError('WORKER_DUPLICATE')
workers[worker.worker_id]=worker
for identity in tokens.values():
if identity.role=='worker':
worker=workers.get(identity.worker_id)
if not worker or worker.identity!=identity.subject_id or identity.tenant_id not in worker.tenant_scope:
raise ValueError('WORKER_IDENTITY_SCOPE_MISMATCH')
contracts=[validate_contract(c) for c in config['contracts']]
operations=OperationRegistry(config['operations'])
host=SystemdHost(config['host_bindings'])
config['_resolved']={'state':state,'machine':machine,'owner_keys':keys,'workers':workers,
'contracts':contracts,'operations':operations,'host':host,'tokens':tokens,'receipt_keys':receipt_keys}
return config,tokens
def build_service(config):
resolved=config['_resolved']
state=resolved['state']
marker=state/'ISOLATED_VALIDATION_AUTHORITY'
if state.exists() and not marker.is_file() and any(state.iterdir()):
raise ValueError('EXISTING_STATE_AUTHORITY_NOT_IDENTIFIED')
if marker.exists() and _safe_read(marker)!=b'aios.baseline.isolated-validation.v1\n':
raise ValueError('STATE_AUTHORITY_MARKER_INVALID')
state.mkdir(parents=True,exist_ok=True,mode=0o700)
if state.stat().st_mode & 0o077: raise ValueError('STATE_DIRECTORY_PERMISSIONS_INVALID')
if not marker.exists(): marker.write_text('aios.baseline.isolated-validation.v1\n')
auth=AuthorizationService(state/'authorization.sqlite3',resolved['owner_keys'],resolved['machine'],config['machine_key_id'],
owner_key_scopes=config['owner_key_scopes'],
control_context_verifier=lambda ctx:isinstance(ctx,Identity) and ctx.role=='owner' and any(ctx is known for known in resolved['tokens'].values()))
store=TaskPlaneStore(str(state/'tasks.sqlite3'),high_assurance=True)
return BaselineService(store,state/'jobs',auth,resolved['contracts'],resolved['operations'],resolved['workers'],
resolved['host'],LineageLedger(state/'lineage.sqlite3',store,JobStore(state/'jobs').read),resolved['machine'],
machine_key_id=config['machine_key_id'],trusted_receipt_keys=resolved['receipt_keys'])
def _write_json(path,value,mode=0o600):
data=(json.dumps(value,ensure_ascii=False,sort_keys=True,indent=2,allow_nan=False)+'\n').encode()
fd=os.open(path,os.O_WRONLY|os.O_CREAT|os.O_EXCL,mode)
with os.fdopen(fd,'wb') as out: out.write(data)
def initialize_validation(directory,*,unit):
"""Create explicit local-only example identities, not production trust roots."""
directory=Path(directory).absolute()
directory.mkdir(mode=0o700) # Never overwrite an existing installation.
# Validate unit syntax before writing any identity material.
SystemdHost({'validation:staging:sample':unit})
owner=Ed25519PrivateKey.generate()
machine=Ed25519PrivateKey.generate()
for name,key in [('owner-private.pem',owner),('machine-private.pem',machine)]:
path=directory/name
fd=os.open(path,os.O_WRONLY|os.O_CREAT|os.O_EXCL,0o600)
with os.fdopen(fd,'wb') as out:
out.write(key.private_bytes(serialization.Encoding.PEM,serialization.PrivateFormat.PKCS8,serialization.NoEncryption()))
(directory/'owner-public.pem').write_bytes(owner.public_key().public_bytes(serialization.Encoding.PEM,serialization.PublicFormat.SubjectPublicKeyInfo))
for name in ('user.token','worker.token'):
fd=os.open(directory/name,os.O_WRONLY|os.O_CREAT|os.O_EXCL,0o600)
with os.fdopen(fd,'w') as out: out.write(secrets.token_urlsafe(48)+'\n')
operation={'operation_id':'service-status','version':1,'effect_class':'READ_ONLY','handler_ref':'host.service_status',
'input_schema':'aios.operation.service-status.input.v1','required_permissions':['service-status'],'timeout_seconds':10,
'readback':{'mode':'NONE','required':False},'retry':{'mode':'SAFE','max_attempts':2},'reconcile':{'mode':'NONE'},
'rollback':{'mode':'NONE'},'owner_boundary':{'mode':'WITHIN_GRANT'}}
contract={'contract_schema':'aios.runtime-contract.v1','contract_revision':1,'component_id':'sample','environment':'staging',
'tenant_scope':'validation','lifecycle':'active','entrypoints':{'status':{'component_id':'sample','environment':'staging','resolver_ref':'service://sample/status'}},
'bindings':{},'resource_refs':{},'credential_refs':{},'operations':['service-status']}
config={'schema':'aios.baseline-server-config.v1','profile':'isolated-validation','state_dir':str(directory/'state'),
'listen':{'host':'127.0.0.1','port':8769},'machine_key_id':'validation-machine','machine_key_file':str(directory/'machine-private.pem'),
'owner_public_key_files':{'validation-owner':str(directory/'owner-public.pem')},
'owner_key_scopes':{'validation-owner':{'tenant_ids':['validation'],'subject_ids':['owner']}},
'identities':[{'token_file':str(directory/'user.token'),'tenant_id':'validation','subject_id':'owner','role':'owner'},
{'token_file':str(directory/'worker.token'),'tenant_id':'validation','subject_id':'machine-worker','role':'worker','worker_id':'diagnostic-worker'}],
'workers':[{'worker_id':'diagnostic-worker','identity':'machine-worker','tenant_scope':['validation'],'subject_scope':['owner'],
'capabilities':['service-status'],'priority':10,'fallback_executor':False,'heartbeat_seconds':15,'max_concurrency':1}],
'contracts':[contract],'operations':[operation],'host_bindings':{'validation:staging:sample':unit}}
_write_json(directory/'server.json',config)
_write_json(directory/'worker.json',{'schema':'aios.baseline-worker-config.v1','base_url':'http://127.0.0.1:8769',
'token_file':str(directory/'worker.token'),'poll_seconds':2})
grant=sign_owner_delegation_grant(owner,'validation-owner',grant_id='validation-grant',generation=1,
tenant_id='validation',subject_id='owner',mission_ids=('validation-mission',),task_ids=('*',),operations=('service-status',),
component_ids=('sample',),environments=('staging',),issued_at=int(time.time()),validity_mode='UNTIL_REVOKED',max_effects=20)
_write_json(directory/'grant.json',grant)
_write_json(directory/'goal.json',{'mission_id':'validation-mission','grant_id':'validation-grant','operation':'service-status',
'component_id':'sample','environment':'staging','inputs':{},'intent':'检查已绑定 staging 服务并生成诊断结果'})
return directory
def main(argv=None):
parser=argparse.ArgumentParser(description=__doc__)
sub=parser.add_subparsers(dest='command',required=True)
init=sub.add_parser('init-validation');init.add_argument('--directory',required=True);init.add_argument('--service-unit',required=True)
for name in ('preflight','server','grant-import','grant-revoke'):
item=sub.add_parser(name);item.add_argument('--config',required=True)
if name=='grant-import': item.add_argument('--grant',required=True)
if name=='grant-revoke':
item.add_argument('--grant',required=True);item.add_argument('--owner-token-file',required=True)
worker=sub.add_parser('worker');worker.add_argument('--config',required=True);worker.add_argument('--once',action='store_true')
for name in ('goal-submit','goal-explain','task-get','task-cancel'):
item=sub.add_parser(name);item.add_argument('--base-url',default='http://127.0.0.1:8769');item.add_argument('--token-file',required=True)
if name=='goal-submit': item.add_argument('--request',required=True);item.add_argument('--idempotency-key',required=True)
elif name=='goal-explain': item.add_argument('--mission-id',required=True)
else: item.add_argument('--task-id',required=True)
args=parser.parse_args(argv)
try:
if args.command=='init-validation':
print(json.dumps({'profile':'isolated-validation','directory':str(initialize_validation(args.directory,unit=args.service_unit))}));return 0
if args.command in {'preflight','server','grant-import','grant-revoke'}:
config,tokens=load_configuration(args.config)
if args.command=='preflight':
print(json.dumps({'status':'CONFIG_VALID','profile':config['profile'],'online_authority':'NOT_VERIFIED',
'production_activation':'BLOCKED','registered_operations':len(config['operations'])}));return 0
service=build_service(config)
if args.command=='grant-import':
grant=service.authorization.register_grant(read_json(args.grant))
print(json.dumps({'grant_id':grant['payload']['grant_id'],'registered':True}));return 0
if args.command=='grant-revoke':
context=tokens.get(secret(args.owner_token_file).decode().strip())
grant=read_json(args.grant)
payload=grant['payload']
if context is None or context.role!='owner' or payload['tenant_id']!=context.tenant_id or payload['subject_id']!=context.subject_id:
raise ValueError('OWNER_SCOPE_DENIED')
# Register verifies signature and ID before a target can be revoked.
service.authorization.register_grant(grant)
generation=service.authorization.revoke_grant(payload['grant_id'],control_context=context)
print(json.dumps({'grant_id':payload['grant_id'],'revocation_generation':generation}));return 0
from .transport import serve
server=serve(service,tokens,**config['listen'])
stop=threading.Event()
def recover_loop():
while not stop.wait(5):
try: service.recover()
except Exception: service.storage_blocked=True
recovery=threading.Thread(target=recover_loop,daemon=True);recovery.start()
def shutdown(_signal,_frame):
stop.set();threading.Thread(target=server.shutdown,daemon=True).start()
signal.signal(signal.SIGTERM,shutdown);signal.signal(signal.SIGINT,shutdown)
print(json.dumps({'status':'LISTENING','host':config['listen']['host'],'port':config['listen']['port'],'profile':config['profile']}),flush=True)
try: server.serve_forever(poll_interval=0.5)
finally: stop.set();server.server_close()
return 0
from .transport import HttpTransport
if args.command=='worker':
from .worker import OperationWorker
config=read_json(args.config)
if type(config) is not dict or set(config)!={'schema','base_url','token_file','poll_seconds'} or config['schema']!='aios.baseline-worker-config.v1': raise ValueError('WORKER_CONFIG_INVALID')
transport=HttpTransport(config['base_url'],secret(config['token_file']).decode().strip(),timeout_seconds=20)
worker=OperationWorker(transport,poll_seconds=config['poll_seconds'])
if args.once: print(json.dumps(worker.run_once(),ensure_ascii=False));return 0
worker.run_forever();return 0
transport=HttpTransport(args.base_url,secret(args.token_file).decode().strip(),timeout_seconds=20)
if args.command=='goal-submit': result=transport.submit_goal(read_json(args.request),idempotency_key=args.idempotency_key)
elif args.command=='goal-explain': result=transport.explain(args.mission_id)
elif args.command=='task-get': result=transport.get_task(args.task_id)
else: result=transport.cancel_task(args.task_id)
print(json.dumps(result,ensure_ascii=False,indent=2,allow_nan=False));return 0
except (Exception,KeyboardInterrupt) as exc:
# Avoid dumping request bodies, credential material or Python tracebacks.
text=str(exc)
code=text if re.fullmatch(r'[A-Z][A-Z0-9_]{2,100}',text) else getattr(exc,'code',type(exc).__name__)
print(json.dumps({'status':'ERROR','error_type':type(exc).__name__,'code':code,'message':'See local configuration and acceptance runbook; no action was reported successful.'}))
return 1
if __name__=='__main__':
raise SystemExit(main())
@@ -0,0 +1,39 @@
"""Canonical, domain-separated machine receipts, distinct from Owner grants."""
import base64
import hashlib
import json
from cryptography.exceptions import InvalidSignature
def canonical(value):
return json.dumps(value,sort_keys=True,separators=(',',':'),ensure_ascii=False,allow_nan=False).encode()
def digest(value):
return hashlib.sha256(canonical(value)).hexdigest()
class ReceiptSigner:
def __init__(self, private_key, *, key_id='baseline-machine', trusted_public_keys=None):
self.private_key=private_key
self.public_key=private_key.public_key()
self.key_id=key_id
self.trusted_public_keys=dict(trusted_public_keys or {})
self.trusted_public_keys[key_id]=self.public_key
def sign(self,payload):
raw=canonical({'domain':'aios.operation-receipt.v1','key_id':self.key_id,'payload':payload})
return {'schema':'aios.operation-receipt.v1','key_id':self.key_id,'payload':payload,
'signature':base64.b64encode(self.private_key.sign(raw)).decode()}
def verify(self,envelope):
try:
if type(envelope) is not dict or set(envelope)!={'schema','key_id','payload','signature'} or envelope['schema']!='aios.operation-receipt.v1' or type(envelope['payload']) is not dict:
return False
public=self.trusted_public_keys.get(envelope['key_id'])
if public is None: return False
raw=canonical({'domain':envelope['schema'],'key_id':envelope['key_id'],'payload':envelope['payload']})
public.verify(base64.b64decode(envelope['signature'],validate=True),raw)
return True
except (ValueError,TypeError,InvalidSignature,KeyError):
return False
+103
View File
@@ -0,0 +1,103 @@
"""Small registered host diagnostics; unit bindings come from trusted configuration."""
from __future__ import annotations
import os
from pathlib import Path
import re
import signal
import selectors
import stat
import subprocess
import time
class HostError(RuntimeError):
pass
class SystemdHost:
PROPERTIES = ('Id', 'ActiveState', 'SubState', 'MainPID', 'InvocationID')
def __init__(self, bindings: dict[str, str], *, executable: str = '/usr/bin/systemctl',
timeout_seconds: float = 10):
if type(timeout_seconds) not in (int,float) or not 0 < timeout_seconds <= 60:
raise HostError('HOST_TIMEOUT_INVALID')
self.bindings = dict(bindings)
for binding,unit in self.bindings.items():
if not isinstance(binding,str) or len(binding.split(':'))!=3 or any(not re.fullmatch(r'[A-Za-z0-9_.-]+',part) for part in binding.split(':')):
raise HostError('HOST_TENANT_BINDING_INVALID')
if not isinstance(unit, str) or not re.fullmatch(r'[A-Za-z0-9_.@:-]+\.service', unit):
raise HostError('HOST_UNIT_INVALID')
if not Path(executable).is_absolute() or not Path(executable).is_file() or Path(executable).is_symlink():
raise HostError('HOST_EXECUTABLE_UNAVAILABLE')
info=Path(executable).stat()
if info.st_uid not in {0,os.geteuid()} or info.st_mode & 0o022:
raise HostError('HOST_EXECUTABLE_UNTRUSTED')
self._executable_identity=(info.st_dev,info.st_ino,info.st_size,info.st_mtime_ns)
self.executable = executable
self.timeout_seconds = timeout_seconds
def status(self, component_id: str, environment: str, *, tenant_id: str, deadline=None) -> dict:
if any(not isinstance(part,str) or not re.fullmatch(r'[A-Za-z0-9_.-]+',part) for part in (tenant_id,environment,component_id)):
raise HostError('HOST_SCOPE_INVALID')
unit = self.bindings.get(f'{tenant_id}:{environment}:{component_id}')
if not unit:
raise HostError('HOST_BINDING_NOT_FOUND')
remaining=self.timeout_seconds if deadline is None else min(self.timeout_seconds,deadline-time.time())
if remaining<=0: raise HostError('HOST_OPERATION_TIMEOUT')
stop_at=time.monotonic()+remaining
executable_fd=os.open(self.executable,os.O_RDONLY|os.O_NOFOLLOW|os.O_CLOEXEC)
try:
info=os.fstat(executable_fd)
if not stat.S_ISREG(info.st_mode) or (info.st_dev,info.st_ino,info.st_size,info.st_mtime_ns)!=self._executable_identity:
raise HostError('HOST_EXECUTABLE_IDENTITY_CHANGED')
executable_path=f'/proc/self/fd/{executable_fd}'
if not Path(executable_path).exists(): raise HostError('HOST_EXECUTOR_PLATFORM_UNSUPPORTED')
argv=[executable_path,'show','--no-pager','--property='+','.join(self.PROPERTIES),'--',unit]
process=subprocess.Popen(argv,stdin=subprocess.DEVNULL,stdout=subprocess.PIPE,stderr=subprocess.PIPE,
env={'PATH':'/usr/bin:/bin','LANG':'C'},start_new_session=True,pass_fds=(executable_fd,))
finally: os.close(executable_fd)
output=bytearray()
byte_count=0
try:
with selectors.DefaultSelector() as selector:
selector.register(process.stdout,selectors.EVENT_READ,'stdout')
selector.register(process.stderr,selectors.EVENT_READ,'stderr')
while selector.get_map():
remaining=stop_at-time.monotonic()
if remaining<=0: raise HostError('HOST_OPERATION_TIMEOUT')
for event,_mask in selector.select(remaining):
chunk=os.read(event.fileobj.fileno(),4096)
if not chunk:
selector.unregister(event.fileobj)
continue
byte_count+=len(chunk)
if byte_count>65536: raise HostError('HOST_RESPONSE_TOO_LARGE')
if event.data=='stdout': output.extend(chunk)
process.wait(timeout=max(0.001,stop_at-time.monotonic()))
except (HostError,subprocess.TimeoutExpired) as exc:
try: os.killpg(process.pid,signal.SIGKILL)
except ProcessLookupError: pass
process.wait()
if isinstance(exc,HostError): raise
raise HostError('HOST_OPERATION_TIMEOUT') from exc
finally:
process.stdout.close()
process.stderr.close()
if process.returncode != 0:
raise HostError('HOST_STATUS_UNAVAILABLE')
try: out=output.decode('utf-8')
except UnicodeDecodeError as exc: raise HostError('HOST_OBSERVATION_INVALID') from exc
properties = {}
for line in out.splitlines():
key, sep, value = line.partition('=')
if sep and key in self.PROPERTIES:
if key in properties:
raise HostError('HOST_DUPLICATE_PROPERTY')
properties[key] = value
if set(properties) != set(self.PROPERTIES) or properties['Id'] != unit:
raise HostError('HOST_OBSERVATION_INCOMPLETE')
if not properties['MainPID'].isdigit():
raise HostError('HOST_OBSERVATION_INVALID')
return {'operation':'service-status', 'tenant_id':tenant_id, 'component_id':component_id, 'environment':environment,
'unit':unit, 'healthy':properties['ActiveState'] == 'active',
'properties':properties, 'observed_at':time.time(), 'source':'systemd.show'}
+650
View File
@@ -0,0 +1,650 @@
"""Persistent correlation relationships for the AIOS baseline.
This is deliberately a relationship authority. It does not create or advance
Tasks, Jobs, Releases, or Deployments. In particular, a Task is admitted only
after the configured Task authority returns the real Task and its authenticated
tenant/subject binding has been checked.
"""
from __future__ import annotations
from dataclasses import asdict, dataclass, is_dataclass
from datetime import datetime, timezone
import json
from pathlib import Path
import sqlite3
from typing import Any, Callable, Mapping
class LineageError(RuntimeError):
"""Base class for persistent lineage failures."""
class LineageConflict(LineageError):
"""An opaque identity was already bound to different lineage."""
class LineageVerificationError(LineageError):
"""An external authority did not verify the requested relationship."""
@dataclass(frozen=True)
class VerifiedAcceptanceEvidence:
"""Typed output of a configured acceptance evidence verifier."""
acceptance_id: str
tenant_id: str
subject_id: str
component_id: str
deployment_id: str
release_id: str
artifact: Any
accepted: bool
evidence_id: str
observed: Mapping[str, Any] | None = None
observed_at: str | None = None
def _now() -> str:
return datetime.now(timezone.utc).isoformat()
def _required(value: Any, name: str) -> str:
text = str(value or "")
if not text or len(text) > 512 or any(ord(char) < 32 for char in text):
raise ValueError(f"invalid {name}")
return text
def _json(value: Any) -> str:
return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"), allow_nan=False)
_SELF_HASH_FIELDS = {
"manifest_sha256",
"manifest_hash",
"outer_manifest_hash",
"outer_manifest_sha256",
"self_hash",
"self_sha256",
}
def canonical_artifact(artifact: Any) -> str:
"""Return an exact artifact identity without permitting outer self-hashes."""
if not isinstance(artifact, (str, Mapping)):
raise ValueError("artifact must be a string or object")
if isinstance(artifact, str):
_required(artifact, "artifact")
else:
if not artifact:
raise ValueError("artifact must not be empty")
if any(not isinstance(key,str) for key in artifact):
raise ValueError("artifact object keys must be strings")
if _SELF_HASH_FIELDS.intersection(key.lower() for key in artifact):
raise ValueError("outer artifact manifest must not contain a self hash")
try:
return _json(artifact)
except (TypeError, ValueError) as exc:
raise ValueError("artifact must be canonical JSON") from exc
def decode_artifact(value: str) -> Any:
return json.loads(value)
def _mapping(value: Any) -> Mapping[str, Any]:
if isinstance(value, Mapping):
return value
if is_dataclass(value):
return asdict(value)
converter = getattr(value, "to_dict", None)
if not callable(converter):
converter = getattr(value, "as_dict", None)
if callable(converter):
try:
converted = converter(redact_payload=False)
except TypeError:
converted = converter()
if isinstance(converted, Mapping):
return converted
raise LineageVerificationError("task_authority returned an invalid Task")
def _task_plan(task: Any) -> tuple[Mapping[str, Any], Mapping[str, Any]]:
raw = _mapping(task)
plan: Any = raw.get("plan") or raw.get("task_plan")
if plan is None and hasattr(task, "task_plan"):
plan = getattr(task, "task_plan")
if plan is None:
plan = raw
plan_map = _mapping(plan)
return raw, plan_map
class LineageLedger:
"""SQLite-backed authority for cross-domain identity relationships."""
def __init__(self, path: str | Path, task_authority: Any, job_authority: Any):
if task_authority is None:
raise ValueError("task_authority is required")
if job_authority is None:
raise ValueError("job_authority is required")
self.path = str(path)
self.task_authority = task_authority
self.job_authority = job_authority
Path(self.path).parent.mkdir(parents=True, exist_ok=True)
self._initialize()
def _connect(self) -> sqlite3.Connection:
connection = sqlite3.connect(self.path, timeout=30)
connection.row_factory = sqlite3.Row
connection.execute("PRAGMA foreign_keys=ON")
connection.execute("PRAGMA busy_timeout=30000")
return connection
def _initialize(self) -> None:
with self._connect() as connection:
connection.execute("PRAGMA journal_mode=WAL")
tables={row[0] for row in connection.execute("SELECT name FROM sqlite_master WHERE type='table'")}
if "lineage_tasks" in tables and "lineage_schema_meta" not in tables:
raise LineageConflict("unversioned candidate lineage schema requires explicit migration")
if "lineage_schema_meta" in tables:
version=connection.execute("SELECT version FROM lineage_schema_meta WHERE domain='lineage'").fetchone()
if version is None or version[0] != 2:
raise LineageConflict("unsupported lineage schema version")
connection.executescript(
"""
CREATE TABLE IF NOT EXISTS lineage_schema_meta (
domain TEXT PRIMARY KEY,
version INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS lineage_tasks (
tenant_id TEXT NOT NULL,
task_id TEXT NOT NULL,
subject_id TEXT NOT NULL,
mission_id TEXT NOT NULL,
component_id TEXT NOT NULL,
grant_id TEXT NOT NULL,
source_task_id TEXT,
metadata_json TEXT NOT NULL,
created_at TEXT NOT NULL,
PRIMARY KEY (tenant_id, task_id),
UNIQUE (tenant_id, task_id, subject_id, mission_id, component_id, grant_id),
FOREIGN KEY (tenant_id, source_task_id)
REFERENCES lineage_tasks(tenant_id, task_id)
);
CREATE TABLE IF NOT EXISTS lineage_effects (
tenant_id TEXT NOT NULL,
effect_id TEXT NOT NULL,
subject_id TEXT NOT NULL,
mission_id TEXT NOT NULL,
component_id TEXT NOT NULL,
grant_id TEXT NOT NULL,
task_id TEXT NOT NULL,
job_id TEXT NOT NULL,
source_effect_id TEXT,
metadata_json TEXT NOT NULL,
created_at TEXT NOT NULL,
PRIMARY KEY (tenant_id, effect_id),
UNIQUE (tenant_id, job_id),
UNIQUE (tenant_id, effect_id, subject_id, mission_id, component_id),
UNIQUE (tenant_id, effect_id, subject_id, mission_id, component_id, grant_id),
FOREIGN KEY (tenant_id, task_id, subject_id, mission_id, component_id, grant_id)
REFERENCES lineage_tasks(tenant_id, task_id, subject_id, mission_id, component_id, grant_id),
FOREIGN KEY (tenant_id, source_effect_id, subject_id, mission_id, component_id)
REFERENCES lineage_effects(tenant_id, effect_id, subject_id, mission_id, component_id)
);
CREATE TABLE IF NOT EXISTS lineage_releases (
tenant_id TEXT NOT NULL,
release_id TEXT NOT NULL,
subject_id TEXT NOT NULL,
mission_id TEXT NOT NULL,
component_id TEXT NOT NULL,
grant_id TEXT NOT NULL,
effect_id TEXT NOT NULL,
source_task_id TEXT NOT NULL,
source_effect_id TEXT NOT NULL,
artifact_json TEXT NOT NULL,
metadata_json TEXT NOT NULL,
created_at TEXT NOT NULL,
PRIMARY KEY (tenant_id, release_id),
UNIQUE (tenant_id, effect_id),
UNIQUE (tenant_id, release_id, subject_id, mission_id, component_id, artifact_json),
FOREIGN KEY (tenant_id, effect_id, subject_id, mission_id, component_id, grant_id)
REFERENCES lineage_effects(tenant_id, effect_id, subject_id, mission_id, component_id, grant_id),
FOREIGN KEY (tenant_id, source_task_id, subject_id, mission_id, component_id, grant_id)
REFERENCES lineage_tasks(tenant_id, task_id, subject_id, mission_id, component_id, grant_id)
);
CREATE TABLE IF NOT EXISTS lineage_deployments (
tenant_id TEXT NOT NULL,
deployment_id TEXT NOT NULL,
subject_id TEXT NOT NULL,
mission_id TEXT NOT NULL,
component_id TEXT NOT NULL,
release_id TEXT NOT NULL,
environment TEXT NOT NULL,
target_revision INTEGER NOT NULL,
artifact_json TEXT NOT NULL,
metadata_json TEXT NOT NULL,
created_at TEXT NOT NULL,
PRIMARY KEY (tenant_id, deployment_id),
UNIQUE (tenant_id, deployment_id, subject_id, mission_id, component_id, release_id, artifact_json),
FOREIGN KEY (tenant_id, release_id, subject_id, mission_id, component_id, artifact_json)
REFERENCES lineage_releases(tenant_id, release_id, subject_id, mission_id, component_id, artifact_json)
);
CREATE TABLE IF NOT EXISTS lineage_acceptances (
tenant_id TEXT NOT NULL,
acceptance_id TEXT NOT NULL,
subject_id TEXT NOT NULL,
mission_id TEXT NOT NULL,
component_id TEXT NOT NULL,
deployment_id TEXT NOT NULL,
release_id TEXT NOT NULL,
artifact_json TEXT NOT NULL,
accepted INTEGER NOT NULL CHECK (accepted IN (0,1)),
evidence_id TEXT NOT NULL,
evidence_json TEXT NOT NULL,
created_at TEXT NOT NULL,
PRIMARY KEY (tenant_id, acceptance_id),
FOREIGN KEY (tenant_id, deployment_id, subject_id, mission_id, component_id, release_id, artifact_json)
REFERENCES lineage_deployments(
tenant_id, deployment_id, subject_id, mission_id, component_id, release_id, artifact_json
)
);
"""
)
connection.execute("INSERT OR IGNORE INTO lineage_schema_meta(domain,version) VALUES('lineage',2)")
def close(self) -> None:
"""Compatibility hook; connections are transaction-scoped."""
def _authorized_task(self, tenant_id: str, subject_id: str, task_id: str) -> Any:
authority = self.task_authority
try:
if hasattr(authority, "get_task"):
task = authority.get_task(task_id)
elif callable(authority):
try:
task = authority(tenant_id, subject_id, task_id)
except TypeError:
task = authority(task_id)
else:
raise TypeError("authority has no task lookup")
except Exception as exc:
raise LineageVerificationError("task_authority did not confirm task") from exc
raw, plan = _task_plan(task)
returned_id = raw.get("task_id") or plan.get("task_id") or getattr(task, "task_id", None)
if str(returned_id or "") != task_id:
raise LineageVerificationError("task_authority returned wrong task_id")
issuer = plan.get("issuer") if isinstance(plan.get("issuer"), Mapping) else {}
metadata = plan.get("metadata") if isinstance(plan.get("metadata"), Mapping) else {}
baseline = metadata.get("baseline") if isinstance(metadata.get("baseline"), Mapping) else {}
tenant_values = [value for value in (issuer.get("tenant_id"), metadata.get("tenant_id"), baseline.get("tenant_id")) if value is not None]
subject_values = [
value for value in (issuer.get("subject_id"), metadata.get("subject_id"), baseline.get("subject_id")) if value is not None
]
# issuer.id is accepted as subject identity only when no explicit subject_id exists.
if not subject_values and issuer.get("id") is not None:
subject_values.append(issuer["id"])
if not tenant_values or any(str(value) != tenant_id for value in tenant_values):
raise LineageVerificationError("task_authority tenant mismatch")
if not subject_values or any(str(value) != subject_id for value in subject_values):
raise LineageVerificationError("task_authority subject mismatch")
return task
def _authorized_job(
self, tenant_id: str, subject_id: str, mission_id: str, grant_id: str,
task_id: str, effect_id: str, job_id: str,
) -> None:
authority=self.job_authority
try:
if hasattr(authority,"get_job"):
job=authority.get_job(job_id)
elif hasattr(authority,"read"):
job=authority.read(job_id)
elif callable(authority):
job=authority(job_id)
else:
raise TypeError("authority has no Job lookup")
except Exception as exc:
raise LineageVerificationError("job_authority did not confirm Job") from exc
raw=_mapping(job)
request=raw.get("request") or raw.get("binding")
if request is None and isinstance(raw.get("request_json"),str):
try:
request=json.loads(raw["request_json"])
except json.JSONDecodeError as exc:
raise LineageVerificationError("job_authority returned invalid request") from exc
if not isinstance(request,Mapping):
request=raw
if isinstance(request.get("binding"),Mapping):
request=request["binding"]
bindings={
"job_id":raw.get("job_id") or request.get("job_id"),
"tenant_id":request.get("tenant_id"),"subject_id":request.get("subject_id"),
"mission_id":request.get("mission_id"),"grant_id":request.get("grant_id"),
"task_id":request.get("task_id"),"effect_id":request.get("effect_id"),
}
expected={"job_id":job_id,"tenant_id":tenant_id,"subject_id":subject_id,
"mission_id":mission_id,"grant_id":grant_id,"task_id":task_id,"effect_id":effect_id}
if any(str(bindings[key] or "") != value for key,value in expected.items()):
raise LineageVerificationError("job_authority binding mismatch")
@staticmethod
def _check_task_context(task: Any, mission_id: str, grant_id: str) -> None:
_, plan = _task_plan(task)
metadata = plan.get("metadata") if isinstance(plan.get("metadata"), Mapping) else {}
baseline = metadata.get("baseline") if isinstance(metadata.get("baseline"), Mapping) else {}
mission_values = [value for value in (metadata.get("mission_id"), baseline.get("mission_id")) if value is not None]
grant_values = [value for value in (metadata.get("grant_id"), baseline.get("grant_id")) if value is not None]
if any(str(value) != mission_id for value in mission_values):
raise LineageVerificationError("task_authority mission mismatch")
if any(str(value) != grant_id for value in grant_values):
raise LineageVerificationError("task_authority grant mismatch")
@staticmethod
def _task_component(task: Any, requested: str | None) -> str:
_, plan = _task_plan(task)
metadata = plan.get("metadata") if isinstance(plan.get("metadata"), Mapping) else {}
baseline = metadata.get("baseline") if isinstance(metadata.get("baseline"), Mapping) else {}
values=[value for value in (metadata.get("component_id"),baseline.get("component_id")) if value is not None]
if requested is not None:
requested=_required(requested,"component_id")
if any(str(value) != requested for value in values):
raise LineageVerificationError("task_authority component mismatch")
return requested
if not values or any(str(value) != str(values[0]) for value in values):
raise LineageVerificationError("task_authority component missing or inconsistent")
return _required(values[0],"component_id")
def link_task(
self,
tenant_id: str,
subject_id: str,
mission_id: str,
grant_id: str,
task_id: str,
*,
component_id: str | None = None,
source_task_id: str | None = None,
metadata: Mapping[str, Any] | None = None,
) -> dict[str, Any]:
tenant_id, subject_id, mission_id, grant_id, task_id = (
_required(tenant_id, "tenant_id"), _required(subject_id, "subject_id"),
_required(mission_id, "mission_id"), _required(grant_id, "grant_id"),
_required(task_id, "task_id"),
)
task = self._authorized_task(tenant_id, subject_id, task_id)
self._check_task_context(task, mission_id, grant_id)
component_id=self._task_component(task,component_id)
source_task_id = _required(source_task_id, "source_task_id") if source_task_id else None
raw_task, task_plan = _task_plan(task)
authoritative_source = task_plan.get("parent_task_id") or raw_task.get("parent_task_id")
if authoritative_source is not None:
authoritative_source = _required(authoritative_source, "authoritative source_task_id")
if source_task_id is not None and source_task_id != authoritative_source:
raise LineageVerificationError("source_task_id does not match Task authority")
source_task_id = authoritative_source
metadata_json = _json(dict(metadata or {}))
values = (subject_id, mission_id, component_id, grant_id, source_task_id)
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
existing = connection.execute(
"SELECT * FROM lineage_tasks WHERE tenant_id=? AND task_id=?", (tenant_id, task_id)
).fetchone()
if existing:
if tuple(existing[key] for key in ("subject_id", "mission_id", "component_id", "grant_id", "source_task_id")) != values:
raise LineageConflict("task_id already has different lineage")
return self._task_row(existing)
if source_task_id:
self._require_row(connection, "lineage_tasks", "task_id", tenant_id, source_task_id, subject_id, mission_id, component_id)
connection.execute(
"INSERT INTO lineage_tasks VALUES(?,?,?,?,?,?,?,?,?)",
(tenant_id, task_id, subject_id, mission_id, component_id, grant_id, source_task_id, metadata_json, _now()),
)
row = connection.execute(
"SELECT * FROM lineage_tasks WHERE tenant_id=? AND task_id=?", (tenant_id, task_id)
).fetchone()
return self._task_row(row)
@staticmethod
def _require_row(connection, table, id_column, tenant_id, object_id, subject_id, mission_id, component_id=None):
row = connection.execute(
f"SELECT * FROM {table} WHERE tenant_id=? AND {id_column}=?", (tenant_id, object_id)
).fetchone()
if row is None:
raise LineageConflict(f"{id_column} has no linked authority record")
if row["subject_id"] != subject_id or row["mission_id"] != mission_id:
raise LineageConflict(f"{id_column} belongs to different subject or mission")
if component_id is not None and row["component_id"] != component_id:
raise LineageConflict(f"{id_column} belongs to different component")
return row
def link_effect(
self, tenant_id: str, subject_id: str, mission_id: str, task_id: str,
effect_id: str, job_id: str, *, source_effect_id: str | None = None,
metadata: Mapping[str, Any] | None = None,
) -> dict[str, Any]:
tenant_id, subject_id, mission_id, task_id, effect_id, job_id = map(
lambda pair: _required(pair[0], pair[1]),
((tenant_id,"tenant_id"),(subject_id,"subject_id"),(mission_id,"mission_id"),
(task_id,"task_id"),(effect_id,"effect_id"),(job_id,"job_id")),
)
source_effect_id = _required(source_effect_id, "source_effect_id") if source_effect_id else None
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
task=self._require_row(connection, "lineage_tasks", "task_id", tenant_id, task_id, subject_id, mission_id)
component_id=task["component_id"]
grant_id=task["grant_id"]
self._authorized_job(tenant_id,subject_id,mission_id,grant_id,task_id,effect_id,job_id)
existing = connection.execute(
"SELECT * FROM lineage_effects WHERE tenant_id=? AND effect_id=?", (tenant_id, effect_id)
).fetchone()
expected = (subject_id, mission_id, component_id, grant_id, task_id, job_id, source_effect_id)
if existing:
actual = tuple(existing[key] for key in ("subject_id","mission_id","component_id","grant_id","task_id","job_id","source_effect_id"))
if actual != expected:
raise LineageConflict("effect_id already has different task/job identity")
return self._effect_row(existing)
job = connection.execute(
"SELECT effect_id,task_id,subject_id FROM lineage_effects WHERE tenant_id=? AND job_id=?",
(tenant_id, job_id),
).fetchone()
if job:
raise LineageConflict("job_id already belongs to exactly one Task/effect")
if source_effect_id:
self._require_row(connection, "lineage_effects", "effect_id", tenant_id, source_effect_id, subject_id, mission_id, component_id)
connection.execute(
"INSERT INTO lineage_effects VALUES(?,?,?,?,?,?,?,?,?,?,?)",
(tenant_id,effect_id,subject_id,mission_id,component_id,grant_id,task_id,job_id,source_effect_id,_json(dict(metadata or {})),_now()),
)
row = connection.execute("SELECT * FROM lineage_effects WHERE tenant_id=? AND effect_id=?",(tenant_id,effect_id)).fetchone()
return self._effect_row(row)
def link_release(
self, tenant_id: str, subject_id: str, mission_id: str, effect_id: str,
release_id: str, artifact: Any, *, metadata: Mapping[str, Any] | None = None,
) -> dict[str, Any]:
tenant_id, subject_id, mission_id, effect_id, release_id = map(
lambda pair: _required(pair[0],pair[1]),
((tenant_id,"tenant_id"),(subject_id,"subject_id"),(mission_id,"mission_id"),
(effect_id,"effect_id"),(release_id,"release_id")),
)
artifact_json = canonical_artifact(artifact)
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
effect = self._require_row(connection,"lineage_effects","effect_id",tenant_id,effect_id,subject_id,mission_id)
existing = connection.execute("SELECT * FROM lineage_releases WHERE tenant_id=? AND release_id=?",(tenant_id,release_id)).fetchone()
expected = (subject_id,mission_id,effect["component_id"],effect["grant_id"],effect_id,effect["task_id"],effect_id,artifact_json)
if existing:
actual = tuple(existing[key] for key in ("subject_id","mission_id","component_id","grant_id","effect_id","source_task_id","source_effect_id","artifact_json"))
if actual != expected:
raise LineageConflict("release_id already has different source lineage")
return self._release_row(existing)
producer = connection.execute("SELECT release_id FROM lineage_releases WHERE tenant_id=? AND effect_id=?",(tenant_id,effect_id)).fetchone()
if producer:
raise LineageConflict("release-producing effect already points to a unique release")
connection.execute(
"INSERT INTO lineage_releases VALUES(?,?,?,?,?,?,?,?,?,?,?,?)",
(tenant_id,release_id,subject_id,mission_id,effect["component_id"],effect["grant_id"],effect_id,effect["task_id"],effect_id,artifact_json,_json(dict(metadata or {})),_now()),
)
row=connection.execute("SELECT * FROM lineage_releases WHERE tenant_id=? AND release_id=?",(tenant_id,release_id)).fetchone()
return self._release_row(row)
def link_deployment(
self, tenant_id: str, subject_id: str, mission_id: str, release_id: str,
deployment_id: str, component_id: str, environment: str, target_revision: int, artifact: Any,
*, metadata: Mapping[str, Any] | None = None,
) -> dict[str, Any]:
tenant_id,subject_id,mission_id,release_id,deployment_id,component_id,environment=map(
lambda pair:_required(pair[0],pair[1]),
((tenant_id,"tenant_id"),(subject_id,"subject_id"),(mission_id,"mission_id"),
(release_id,"release_id"),(deployment_id,"deployment_id"),(component_id,"component_id"),(environment,"environment")),
)
if not isinstance(target_revision,int) or isinstance(target_revision,bool) or target_revision < 1:
raise ValueError("invalid target_revision")
artifact_json=canonical_artifact(artifact)
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
release=self._require_row(connection,"lineage_releases","release_id",tenant_id,release_id,subject_id,mission_id,component_id)
if release["artifact_json"] != artifact_json:
raise LineageConflict("deployment artifact does not match release")
existing=connection.execute("SELECT * FROM lineage_deployments WHERE tenant_id=? AND deployment_id=?",(tenant_id,deployment_id)).fetchone()
expected=(subject_id,mission_id,component_id,release_id,environment,target_revision,artifact_json)
if existing:
actual=tuple(existing[key] for key in ("subject_id","mission_id","component_id","release_id","environment","target_revision","artifact_json"))
if actual != expected:
raise LineageConflict("deployment_id already has different release identity")
return self._deployment_row(existing)
connection.execute(
"INSERT INTO lineage_deployments VALUES(?,?,?,?,?,?,?,?,?,?,?)",
(tenant_id,deployment_id,subject_id,mission_id,component_id,release_id,environment,target_revision,artifact_json,_json(dict(metadata or {})),_now()),
)
row=connection.execute("SELECT * FROM lineage_deployments WHERE tenant_id=? AND deployment_id=?",(tenant_id,deployment_id)).fetchone()
return self._deployment_row(row)
def record_acceptance(
self, tenant_id: str, subject_id: str, mission_id: str, component_id: str, acceptance_id: str,
deployment_id: str, release_id: str, artifact: Any, accepted: bool, *,
evidence: Any, verifier: Callable[[Any, Mapping[str, Any]], VerifiedAcceptanceEvidence],
) -> dict[str, Any]:
if not callable(verifier):
raise LineageVerificationError("acceptance verifier is required")
tenant_id,subject_id,mission_id,component_id,acceptance_id,deployment_id,release_id=map(
lambda pair:_required(pair[0],pair[1]),
((tenant_id,"tenant_id"),(subject_id,"subject_id"),(mission_id,"mission_id"),(component_id,"component_id"),
(acceptance_id,"acceptance_id"),(deployment_id,"deployment_id"),(release_id,"release_id")),
)
if type(accepted) is not bool:
raise ValueError("accepted must be boolean")
artifact_json=canonical_artifact(artifact)
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
deployment=self._require_row(connection,"lineage_deployments","deployment_id",tenant_id,deployment_id,subject_id,mission_id,component_id)
if deployment["release_id"] != release_id:
raise LineageConflict("acceptance release_id does not match deployment")
if deployment["artifact_json"] != artifact_json:
raise LineageConflict("acceptance artifact does not match deployment")
expected={"acceptance_id":acceptance_id,"tenant_id":tenant_id,"subject_id":subject_id,"component_id":component_id,
"deployment_id":deployment_id,"release_id":release_id,"artifact":decode_artifact(artifact_json),
"accepted":accepted}
try:
verified=verifier(evidence,expected)
except Exception as exc:
raise LineageVerificationError("acceptance evidence was not verified") from exc
if not isinstance(verified,VerifiedAcceptanceEvidence):
raise LineageVerificationError("verifier must return VerifiedAcceptanceEvidence")
bindings=(verified.acceptance_id,verified.tenant_id,verified.subject_id,verified.component_id,verified.deployment_id,verified.release_id,canonical_artifact(verified.artifact),verified.accepted)
if bindings != (acceptance_id,tenant_id,subject_id,component_id,deployment_id,release_id,artifact_json,accepted):
raise LineageVerificationError("verified acceptance evidence binding mismatch")
evidence_id=_required(verified.evidence_id,"evidence_id")
evidence_json=_json({
"evidence_id":evidence_id,
"observed":dict(verified.observed or {}),
"observed_at":verified.observed_at,
"signed_envelope":dict(evidence) if isinstance(evidence,Mapping) else None,
})
existing=connection.execute("SELECT * FROM lineage_acceptances WHERE tenant_id=? AND acceptance_id=?",(tenant_id,acceptance_id)).fetchone()
expected_row=(subject_id,mission_id,component_id,deployment_id,release_id,artifact_json,int(accepted),evidence_id,evidence_json)
if existing:
actual=tuple(existing[key] for key in ("subject_id","mission_id","component_id","deployment_id","release_id","artifact_json","accepted","evidence_id","evidence_json"))
if actual != expected_row:
raise LineageConflict("acceptance_id already has different binding")
return self._acceptance_row(existing)
connection.execute(
"INSERT INTO lineage_acceptances VALUES(?,?,?,?,?,?,?,?,?,?,?,?)",
(tenant_id,acceptance_id,subject_id,mission_id,component_id,deployment_id,release_id,artifact_json,int(accepted),evidence_id,evidence_json,_now()),
)
row=connection.execute("SELECT * FROM lineage_acceptances WHERE tenant_id=? AND acceptance_id=?",(tenant_id,acceptance_id)).fetchone()
return self._acceptance_row(row)
def explain(self, tenant_id: str, subject_id: str, mission_id: str) -> dict[str, Any]:
tenant_id,subject_id,mission_id=(_required(tenant_id,"tenant_id"),_required(subject_id,"subject_id"),_required(mission_id,"mission_id"))
specs=(("tasks","lineage_tasks",self._task_row),("effects","lineage_effects",self._effect_row),
("releases","lineage_releases",self._release_row),("deployments","lineage_deployments",self._deployment_row),
("acceptances","lineage_acceptances",self._acceptance_row))
result={"tenant_id":tenant_id,"subject_id":subject_id,"mission_id":mission_id}
with self._connect() as connection:
for key,table,converter in specs:
rows=connection.execute(f"SELECT * FROM {table} WHERE tenant_id=? AND subject_id=? AND mission_id=? ORDER BY created_at",(tenant_id,subject_id,mission_id)).fetchall()
result[key]=[converter(row) for row in rows]
return result
def get_release(self, tenant_id: str, subject_id: str, release_id: str) -> dict[str, Any]:
"""Resolve an exact tenant/subject release without disclosing other scopes."""
tenant_id, subject_id, release_id = (
_required(tenant_id, "tenant_id"), _required(subject_id, "subject_id"),
_required(release_id, "release_id"),
)
with self._connect() as connection:
row = connection.execute(
"SELECT * FROM lineage_releases WHERE tenant_id=? AND release_id=? AND subject_id=?",
(tenant_id, release_id, subject_id),
).fetchone()
if row is None:
raise KeyError("release lineage not found")
return self._release_row(row)
def get_release_for_component(self, tenant_id: str, component_id: str, release_id: str) -> dict[str, Any]:
tenant_id,component_id,release_id=(_required(tenant_id,"tenant_id"),_required(component_id,"component_id"),_required(release_id,"release_id"))
with self._connect() as connection:
row=connection.execute(
"SELECT * FROM lineage_releases WHERE tenant_id=? AND release_id=? AND component_id=?",
(tenant_id,release_id,component_id),
).fetchone()
if row is None:
raise KeyError("release component lineage not found")
return self._release_row(row)
@staticmethod
def _common(row):
return {key:row[key] for key in row.keys() if not key.endswith("_json")}
@classmethod
def _task_row(cls,row):
value=cls._common(row); value["metadata"]=json.loads(row["metadata_json"]); return value
@classmethod
def _effect_row(cls,row):
value=cls._common(row); value["metadata"]=json.loads(row["metadata_json"]); return value
@classmethod
def _release_row(cls,row):
value=cls._common(row); value["artifact"]=decode_artifact(row["artifact_json"]); value["metadata"]=json.loads(row["metadata_json"]); return value
@classmethod
def _deployment_row(cls,row):
value=cls._common(row); value["artifact"]=decode_artifact(row["artifact_json"]); value["metadata"]=json.loads(row["metadata_json"]); return value
@classmethod
def _acceptance_row(cls,row):
value=cls._common(row); value["artifact"]=decode_artifact(row["artifact_json"]); value["accepted"]=bool(row["accepted"]); value["evidence"]=json.loads(row["evidence_json"]); return value
__all__ = [
"LineageConflict", "LineageError", "LineageLedger", "LineageVerificationError",
"VerifiedAcceptanceEvidence", "canonical_artifact", "decode_artifact",
]
@@ -0,0 +1,258 @@
"""Declarative, non-executing operation registry and timeout safety rules."""
from __future__ import annotations
from copy import deepcopy
from dataclasses import dataclass
from datetime import datetime, timezone
import math
import re
from typing import Any, Iterable, Mapping, Sequence
EFFECT_CLASSES = frozenset(
{"READ_ONLY", "IDEMPOTENT", "CONDITIONAL", "RECONCILABLE", "IRREVERSIBLE"}
)
_FORBIDDEN_EXECUTION_FIELDS = frozenset({"command", "shell", "argv", "executable", "script"})
_HANDLER_REF = re.compile(r"^[A-Za-z][A-Za-z0-9_.:-]{0,126}$")
def _invalid(code: str, field: str = "") -> ValueError:
return ValueError(f"{code}:{field}" if field else code)
def _text(value: object, field: str) -> str:
if not isinstance(value, str) or not value.strip():
raise _invalid("field_invalid", field)
return value.strip()
def _policy(value: object, field: str, allowed_modes: set[str]) -> dict[str, Any]:
if not isinstance(value, Mapping):
raise _invalid("policy_invalid", field)
result = deepcopy(dict(value))
mode = _text(result.get("mode"), f"{field}.mode").upper()
if mode not in allowed_modes:
raise _invalid("policy_mode_invalid", f"{field}.mode")
result["mode"] = mode
return result
@dataclass(frozen=True, slots=True)
class OperationDefinition:
operation_id: str
version: int | str
effect_class: str
handler_ref: str
input_schema: str | Mapping[str, Any]
required_permissions: tuple[str, ...]
timeout_seconds: float
readback: Mapping[str, Any]
retry: Mapping[str, Any]
reconcile: Mapping[str, Any]
rollback: Mapping[str, Any]
owner_boundary: Mapping[str, Any]
@classmethod
def from_dict(cls, data: Mapping[str, Any]) -> "OperationDefinition":
if not isinstance(data, Mapping):
raise _invalid("operation_definition_invalid")
forbidden = sorted(_FORBIDDEN_EXECUTION_FIELDS.intersection(data))
if forbidden:
raise _invalid("arbitrary_execution_forbidden", forbidden[0])
operation_id = _text(data.get("operation_id"), "operation_id")
version = data.get("version")
if (
isinstance(version, bool)
or not isinstance(version, (int, str))
or (isinstance(version, int) and version < 1)
or (isinstance(version, str) and not version.strip())
):
raise _invalid("field_invalid", "version")
effect_class = _text(data.get("effect_class"), "effect_class").upper()
if effect_class not in EFFECT_CLASSES:
raise _invalid("effect_class_invalid", "effect_class")
handler_ref = _text(data.get("handler_ref", data.get("handler")), "handler_ref")
if not _HANDLER_REF.fullmatch(handler_ref):
raise _invalid("handler_ref_invalid", "handler_ref")
input_schema = data.get("input_schema")
if not isinstance(input_schema, (str, Mapping)) or not input_schema:
raise _invalid("field_invalid", "input_schema")
input_schema = deepcopy(input_schema)
permissions = data.get("required_permissions", data.get("permissions"))
if not isinstance(permissions, Sequence) or isinstance(permissions, (str, bytes, bytearray)):
raise _invalid("field_invalid", "required_permissions")
required_permissions = tuple(
_text(permission, f"required_permissions[{index}]")
for index, permission in enumerate(permissions)
)
if len(set(required_permissions)) != len(required_permissions):
raise _invalid("duplicate_permission", "required_permissions")
timeout_seconds = data.get("timeout_seconds")
if (
isinstance(timeout_seconds, bool)
or not isinstance(timeout_seconds, (int, float))
or not math.isfinite(timeout_seconds)
or timeout_seconds <= 0
):
raise _invalid("field_invalid", "timeout_seconds")
readback = _policy(data.get("readback"), "readback", {"NONE", "STATE", "EXACT"})
if "required" in readback and not isinstance(readback["required"], bool):
raise _invalid("field_invalid", "readback.required")
retry = _policy(
data.get("retry"),
"retry",
{"NEVER", "SAFE", "AFTER_READBACK", "RECONCILE_FIRST"},
)
attempts = retry.get("max_attempts", 1)
if isinstance(attempts, bool) or not isinstance(attempts, int) or attempts < 1:
raise _invalid("field_invalid", "retry.max_attempts")
retry["max_attempts"] = attempts
reconcile = _policy(
data.get("reconcile"), "reconcile", {"NONE", "EXACT_READBACK", "CUSTOM", "REQUIRED"}
)
rollback = _policy(
data.get("rollback"), "rollback", {"NONE", "OPERATION", "COMPENSATE"}
)
if rollback["mode"] == "OPERATION":
_text(rollback.get("operation_id"), "rollback.operation_id")
owner_boundary = _policy(
data.get("owner_boundary"),
"owner_boundary",
{"NONE", "WITHIN_GRANT", "EXPLICIT_AUTHORIZATION", "CROSS_BOUNDARY_DENIED"},
)
restart_operation = (
"service.restart" in operation_id
or "service-restart" in operation_id
or handler_ref.endswith("service_restart")
)
if restart_operation and (
readback["mode"] != "EXACT"
or not readback.get("required", False)
or reconcile["mode"] == "NONE"
):
raise _invalid("service_restart_exact_readback_required")
return cls(
operation_id=operation_id,
version=version,
effect_class=effect_class,
handler_ref=handler_ref,
input_schema=input_schema,
required_permissions=required_permissions,
timeout_seconds=float(timeout_seconds),
readback=readback,
retry=retry,
reconcile=reconcile,
rollback=rollback,
owner_boundary=owner_boundary,
)
def timeout_action(self, outcome: str, attempts: int = 1) -> str:
return operation_timeout_action(self, outcome=outcome, attempts=attempts)
@property
def permissions(self) -> tuple[str, ...]:
return self.required_permissions
class OperationRegistry:
"""Lookup-only registry; it deliberately has no execute or shell interface."""
def __init__(self, definitions: Iterable[OperationDefinition | Mapping[str, Any]]) -> None:
self._definitions: dict[str, OperationDefinition] = {}
for raw in definitions:
definition = raw if isinstance(raw, OperationDefinition) else OperationDefinition.from_dict(raw)
if definition.operation_id in self._definitions:
raise _invalid("duplicate_operation_id", definition.operation_id)
self._definitions[definition.operation_id] = definition
def get(self, operation_id: str) -> OperationDefinition:
return self._definitions[_text(operation_id, "operation_id")]
@property
def definitions(self) -> tuple[OperationDefinition, ...]:
return tuple(self._definitions.values())
def operation_timeout_action(
definition: OperationDefinition,
outcome: str = "AMBIGUOUS",
attempts: int = 1,
) -> str:
"""Return ``RETRY``, ``RECONCILE`` or ``STOP`` after a timeout.
An unknown response from a mutating sink never authorizes a blind repeat.
Exact readback/reconciliation must first establish whether the effect happened.
"""
if not isinstance(definition, OperationDefinition):
raise _invalid("operation_definition_invalid")
normalized_outcome = _text(outcome, "outcome").upper()
if isinstance(attempts, bool) or not isinstance(attempts, int) or attempts < 1:
raise _invalid("field_invalid", "attempts")
if definition.effect_class == "IRREVERSIBLE":
return "STOP"
if definition.effect_class != "READ_ONLY" and normalized_outcome in {
"AMBIGUOUS",
"UNKNOWN",
"MAY_HAVE_SUCCEEDED",
}:
if definition.reconcile["mode"] != "NONE" or definition.readback.get("required", False):
return "RECONCILE"
return "STOP"
retry_mode = definition.retry["mode"]
if attempts >= definition.retry["max_attempts"] or retry_mode == "NEVER":
return "STOP"
if retry_mode in {"AFTER_READBACK", "RECONCILE_FIRST"} and normalized_outcome not in {
"NOT_OBSERVED",
"PROVEN_ABSENT",
}:
return "RECONCILE" if definition.reconcile["mode"] != "NONE" else "STOP"
return "RETRY"
def _timestamp(value: object) -> datetime | None:
if not isinstance(value, str):
return None
try:
parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
except ValueError:
return None
if parsed.tzinfo is None or parsed.utcoffset() is None:
return None
return parsed.astimezone(timezone.utc)
def verify_service_restart(
before: Mapping[str, Any], after: Mapping[str, Any]
) -> dict[str, Any]:
"""Verify a restart with instance identity and start-time evidence.
Merely observing an active unit proves current activity, not that the requested
restart occurred.
"""
if not isinstance(before, Mapping) or not isinstance(after, Mapping):
return {"status": "UNKNOWN", "reasons": ["restart_observation_invalid"]}
if not isinstance(after.get("active"), bool):
return {"status": "UNKNOWN", "reasons": ["service_active_invalid"]}
if after["active"] is False:
return {"status": "DRIFT", "reasons": ["service_not_active"]}
before_id = before.get("instance_id")
after_id = after.get("instance_id")
before_started = _timestamp(before.get("started_at"))
after_started = _timestamp(after.get("started_at"))
if (
not isinstance(before_id, str)
or not before_id
or not isinstance(after_id, str)
or not after_id
or before_started is None
or after_started is None
):
return {"status": "UNKNOWN", "reasons": ["restart_identity_missing"]}
if before_id == after_id or after_started <= before_started:
return {"status": "DRIFT", "reasons": ["restart_not_observed"]}
return {"status": "VERIFIED", "reasons": []}
@@ -0,0 +1,866 @@
"""Bounded, task-only optimization proposals for Baseline 1.1.
This module deliberately has no configuration or policy write adapter. Its only
effect boundary is ``submit_task``, supplied by the authenticated root service.
"""
from __future__ import annotations
import hashlib
import json
import math
from dataclasses import dataclass
from pathlib import Path
import re
import sqlite3
import time
from typing import Any, Callable, Mapping
ENVELOPE_SCHEMA = "aios.optimization-envelope.v1"
PROPOSAL_SCHEMA = "aios.optimization-proposal.v1"
TASK_REQUEST_SCHEMA = "aios.optimization-task-request.v1"
_IDENTIFIER = re.compile(r"[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}")
_DIGEST = re.compile(r"sha256:[0-9a-f]{64}")
# An envelope may narrow these limits but cannot introduce another path or
# expand a range. Every entry describes a low-risk operational tuning knob.
_HARD_PARAMETER_LIMITS: dict[str, tuple[float, float, bool]] = {
"retry.max_attempts": (1, 10, True),
"retry.backoff_seconds": (0, 60, False),
"backoff.initial_seconds": (0, 60, False),
"backoff.maximum_seconds": (0.1, 600, False),
"routing.candidate_weight_percent": (0, 25, False),
"routing.weight_percent": (0, 25, False),
"evidence_reuse.max_age_seconds": (0, 3600, True),
"evidence_reuse.ttl_seconds": (0, 3600, True),
"timeout.operation_seconds": (0.1, 300, False),
"timeout.seconds": (0.1, 300, False),
"poll.interval_seconds": (0.1, 300, False),
"notification_dedup.window_seconds": (0, 3600, True),
"test_selection.optional_sample_percent": (1, 100, False),
"test_selection.optional_percentage": (1, 100, False),
}
_NON_PRODUCTION_ENVIRONMENTS = frozenset({"development", "test", "staging", "canary"})
class OptimizationError(RuntimeError):
"""A fail-closed optimization boundary error with a stable code."""
def __init__(self, code: str):
self.code = code
super().__init__(code)
def canonical_json_bytes(value: Any) -> bytes:
try:
return json.dumps(
value,
sort_keys=True,
separators=(",", ":"),
ensure_ascii=False,
allow_nan=False,
).encode("utf-8")
except (TypeError, ValueError) as exc:
raise OptimizationError("OPTIMIZATION_DOCUMENT_NOT_CANONICAL") from exc
def _digest(value: Any) -> str:
return "sha256:" + hashlib.sha256(canonical_json_bytes(value)).hexdigest()
def _number(value: Any, code: str, *, minimum: float | None = None,
maximum: float | None = None) -> int | float:
if isinstance(value, bool) or not isinstance(value, (int, float)):
raise OptimizationError(code)
if not math.isfinite(float(value)):
raise OptimizationError(code)
if minimum is not None and value < minimum:
raise OptimizationError(code)
if maximum is not None and value > maximum:
raise OptimizationError(code)
return value
def _identifier(value: Any, code: str) -> str:
if not isinstance(value, str) or _IDENTIFIER.fullmatch(value) is None:
raise OptimizationError(code)
return value
def _unique_identifiers(value: Any, code: str, *, maximum: int = 256) -> tuple[str, ...]:
if not isinstance(value, (list, tuple)) or len(value) > maximum:
raise OptimizationError(code)
items = tuple(_identifier(item, code) for item in value)
if len(set(items)) != len(items):
raise OptimizationError(code)
return items
@dataclass(frozen=True)
class ParameterRange:
minimum: int | float
maximum: int | float
integer: bool
@dataclass(frozen=True)
class QualityFloor:
direction: str
threshold: int | float
@dataclass(frozen=True)
class PromotionRules:
objective_metric: str
minimum_improvement: int | float
mandatory_tests: tuple[str, ...]
mandatory_quality_gates: tuple[str, ...]
required_receipt_schema: str
@dataclass(frozen=True)
class OptimizationEnvelope:
schema: str
envelope_id: str
tenant_id: str
allowed_parameters: Mapping[str, ParameterRange]
allowed_components: tuple[str, ...]
allowed_environments: tuple[str, ...]
max_change_size: int
max_compute_budget: int | float
quality_floor: Mapping[str, QualityFloor]
rollback_requirement: bool
promotion_rules: PromotionRules
source_config_digest: str
key_id: str
signature: str
@classmethod
def from_dict(
cls,
data: Mapping[str, Any],
*,
verify_envelope: Callable[[str, bytes, str], bool] | None,
) -> "OptimizationEnvelope":
"""Parse a trusted-config envelope after authenticating its exact bytes.
``verify_envelope`` is intentionally mandatory. The caller cannot mark
a document trusted with a boolean or construct an unrestricted envelope.
"""
if verify_envelope is None or not callable(verify_envelope):
raise OptimizationError("OPTIMIZATION_ENVELOPE_VERIFIER_REQUIRED")
if not isinstance(data, Mapping):
raise OptimizationError("OPTIMIZATION_ENVELOPE_INVALID")
required = {
"schema", "envelope_id", "tenant_id", "allowed_parameters",
"allowed_components", "allowed_environments", "max_change_size",
"max_compute_budget", "quality_floor", "rollback_requirement",
"promotion_rules", "source_config_digest", "key_id", "signature",
}
if set(data) != required:
raise OptimizationError("OPTIMIZATION_ENVELOPE_FIELDS_INVALID")
unsigned = dict(data)
signature = unsigned.pop("signature")
key_id = data.get("key_id")
if not isinstance(signature, str) or not signature or not isinstance(key_id, str):
raise OptimizationError("OPTIMIZATION_ENVELOPE_SIGNATURE_INVALID")
try:
verified = verify_envelope(key_id, canonical_json_bytes(unsigned), signature)
except Exception as exc:
raise OptimizationError("OPTIMIZATION_ENVELOPE_SIGNATURE_INVALID") from exc
if verified is not True:
raise OptimizationError("OPTIMIZATION_ENVELOPE_SIGNATURE_INVALID")
if data["schema"] != ENVELOPE_SCHEMA:
raise OptimizationError("OPTIMIZATION_ENVELOPE_SCHEMA_INVALID")
envelope_id = _identifier(data["envelope_id"], "OPTIMIZATION_ENVELOPE_ID_INVALID")
tenant_id = _identifier(data["tenant_id"], "OPTIMIZATION_TENANT_INVALID")
if not _DIGEST.fullmatch(str(data["source_config_digest"])):
raise OptimizationError("OPTIMIZATION_SOURCE_CONFIG_DIGEST_INVALID")
key_id = _identifier(key_id, "OPTIMIZATION_ENVELOPE_KEY_ID_INVALID")
raw_parameters = data["allowed_parameters"]
if not isinstance(raw_parameters, Mapping) or not raw_parameters or len(raw_parameters) > 16:
raise OptimizationError("OPTIMIZATION_ALLOWED_PARAMETERS_INVALID")
parameters: dict[str, ParameterRange] = {}
for path, raw_rule in raw_parameters.items():
if path not in _HARD_PARAMETER_LIMITS:
raise OptimizationError("OPTIMIZATION_PARAMETER_PATH_FORBIDDEN")
if not isinstance(raw_rule, Mapping) or not {"minimum", "maximum"} <= set(raw_rule):
raise OptimizationError("OPTIMIZATION_PARAMETER_RANGE_INVALID")
if set(raw_rule) - {"minimum", "maximum", "integer"}:
raise OptimizationError("OPTIMIZATION_PARAMETER_RANGE_INVALID")
hard_min, hard_max, hard_integer = _HARD_PARAMETER_LIMITS[path]
minimum = _number(raw_rule["minimum"], "OPTIMIZATION_PARAMETER_RANGE_INVALID")
maximum = _number(raw_rule["maximum"], "OPTIMIZATION_PARAMETER_RANGE_INVALID")
integer = raw_rule.get("integer", hard_integer)
if type(integer) is not bool or minimum > maximum:
raise OptimizationError("OPTIMIZATION_PARAMETER_RANGE_INVALID")
if minimum < hard_min or maximum > hard_max or (hard_integer and not integer):
raise OptimizationError("OPTIMIZATION_PARAMETER_RANGE_TOO_BROAD")
if integer and (type(minimum) is not int or type(maximum) is not int):
raise OptimizationError("OPTIMIZATION_PARAMETER_RANGE_INVALID")
parameters[path] = ParameterRange(minimum, maximum, integer)
components = _unique_identifiers(
data["allowed_components"], "OPTIMIZATION_COMPONENTS_INVALID", maximum=32
)
environments = _unique_identifiers(
data["allowed_environments"], "OPTIMIZATION_ENVIRONMENTS_INVALID", maximum=4
)
if not components or not environments or not set(environments) <= _NON_PRODUCTION_ENVIRONMENTS:
raise OptimizationError("OPTIMIZATION_ENVIRONMENTS_INVALID")
max_change_size = _number(
data["max_change_size"], "OPTIMIZATION_CHANGE_SIZE_INVALID", minimum=1, maximum=16
)
if type(max_change_size) is not int:
raise OptimizationError("OPTIMIZATION_CHANGE_SIZE_INVALID")
max_compute_budget = _number(
data["max_compute_budget"],
"OPTIMIZATION_COMPUTE_BUDGET_INVALID",
minimum=0.001,
maximum=10_000,
)
raw_quality = data["quality_floor"]
if not isinstance(raw_quality, Mapping) or not raw_quality or len(raw_quality) > 16:
raise OptimizationError("OPTIMIZATION_QUALITY_FLOOR_INVALID")
quality: dict[str, QualityFloor] = {}
for metric, raw_rule in raw_quality.items():
metric = _identifier(metric, "OPTIMIZATION_QUALITY_METRIC_INVALID")
if not isinstance(raw_rule, Mapping) or set(raw_rule) != {"direction", "threshold"}:
raise OptimizationError("OPTIMIZATION_QUALITY_FLOOR_INVALID")
if raw_rule["direction"] not in {"higher", "lower"}:
raise OptimizationError("OPTIMIZATION_QUALITY_DIRECTION_INVALID")
threshold = _number(
raw_rule["threshold"], "OPTIMIZATION_QUALITY_FLOOR_INVALID",
minimum=-1e12, maximum=1e12,
)
quality[metric] = QualityFloor(raw_rule["direction"], threshold)
if type(data["rollback_requirement"]) is not bool:
raise OptimizationError("OPTIMIZATION_ROLLBACK_REQUIREMENT_INVALID")
raw_promotion = data["promotion_rules"]
promotion_fields = {
"objective_metric", "minimum_improvement", "mandatory_tests",
"mandatory_quality_gates", "required_receipt_schema",
}
if not isinstance(raw_promotion, Mapping) or set(raw_promotion) != promotion_fields:
raise OptimizationError("OPTIMIZATION_PROMOTION_RULES_INVALID")
objective = _identifier(
raw_promotion["objective_metric"], "OPTIMIZATION_OBJECTIVE_METRIC_INVALID"
)
if objective not in quality:
raise OptimizationError("OPTIMIZATION_OBJECTIVE_METRIC_INVALID")
minimum_improvement = _number(
raw_promotion["minimum_improvement"],
"OPTIMIZATION_MINIMUM_IMPROVEMENT_INVALID",
minimum=0,
maximum=1e12,
)
mandatory_tests = _unique_identifiers(
raw_promotion["mandatory_tests"], "OPTIMIZATION_MANDATORY_TESTS_INVALID"
)
mandatory_gates = _unique_identifiers(
raw_promotion["mandatory_quality_gates"], "OPTIMIZATION_MANDATORY_GATES_INVALID"
)
if not mandatory_tests or not mandatory_gates:
raise OptimizationError("OPTIMIZATION_PROMOTION_RULES_INVALID")
receipt_schema = _identifier(
raw_promotion["required_receipt_schema"], "OPTIMIZATION_RECEIPT_SCHEMA_INVALID"
)
if not receipt_schema.startswith("aios."):
raise OptimizationError("OPTIMIZATION_RECEIPT_SCHEMA_INVALID")
promotion = PromotionRules(
objective, minimum_improvement, mandatory_tests, mandatory_gates, receipt_schema
)
return cls(
ENVELOPE_SCHEMA, envelope_id, tenant_id, parameters, components,
environments, max_change_size, max_compute_budget, quality,
data["rollback_requirement"], promotion, data["source_config_digest"],
key_id, signature,
)
_PROPOSAL_REQUIRED = {
"schema", "proposal_id", "envelope_id", "tenant_id", "component",
"environment", "changes", "compute_budget", "tests", "quality_gates",
"baseline_metrics", "rollback_plan",
}
def validate_proposal(envelope: OptimizationEnvelope, proposal: Mapping[str, Any]) -> dict:
"""Return a canonical candidate constrained by ``envelope``."""
if not isinstance(envelope, OptimizationEnvelope):
raise OptimizationError("OPTIMIZATION_ENVELOPE_REQUIRED")
if not isinstance(proposal, Mapping):
raise OptimizationError("OPTIMIZATION_PROPOSAL_INVALID")
allowed_fields = _PROPOSAL_REQUIRED | {"evidence_reuse"}
if set(proposal) - allowed_fields or not _PROPOSAL_REQUIRED <= set(proposal):
raise OptimizationError("OPTIMIZATION_PROPOSAL_FIELDS_INVALID")
if proposal["schema"] != PROPOSAL_SCHEMA:
raise OptimizationError("OPTIMIZATION_PROPOSAL_SCHEMA_INVALID")
proposal_id = _identifier(proposal["proposal_id"], "OPTIMIZATION_PROPOSAL_ID_INVALID")
if proposal["envelope_id"] != envelope.envelope_id:
raise OptimizationError("OPTIMIZATION_ENVELOPE_BINDING_MISMATCH")
if proposal["tenant_id"] != envelope.tenant_id:
raise OptimizationError("OPTIMIZATION_PROPOSAL_TENANT_MISMATCH")
component = _identifier(proposal["component"], "OPTIMIZATION_COMPONENT_INVALID")
if component not in envelope.allowed_components:
raise OptimizationError("OPTIMIZATION_COMPONENT_FORBIDDEN")
environment = _identifier(proposal["environment"], "OPTIMIZATION_ENVIRONMENT_INVALID")
if environment not in envelope.allowed_environments:
raise OptimizationError("OPTIMIZATION_ENVIRONMENT_FORBIDDEN")
raw_changes = proposal["changes"]
if not isinstance(raw_changes, list) or not raw_changes or len(raw_changes) > envelope.max_change_size:
raise OptimizationError("OPTIMIZATION_CHANGE_SIZE_EXCEEDED")
changes: list[dict[str, Any]] = []
paths: set[str] = set()
for change in raw_changes:
if not isinstance(change, Mapping) or set(change) != {"path", "value"}:
raise OptimizationError("OPTIMIZATION_CHANGE_INVALID")
path = change["path"]
if not isinstance(path, str) or path not in envelope.allowed_parameters:
raise OptimizationError("OPTIMIZATION_PARAMETER_PATH_FORBIDDEN")
if path in paths:
raise OptimizationError("OPTIMIZATION_PARAMETER_DUPLICATE")
paths.add(path)
rule = envelope.allowed_parameters[path]
value = _number(change["value"], "OPTIMIZATION_PARAMETER_VALUE_INVALID")
if rule.integer and type(value) is not int:
raise OptimizationError("OPTIMIZATION_PARAMETER_VALUE_INVALID")
if value < rule.minimum or value > rule.maximum:
raise OptimizationError("OPTIMIZATION_PARAMETER_OUT_OF_RANGE")
changes.append({"path": path, "value": value})
compute_budget = _number(
proposal["compute_budget"],
"OPTIMIZATION_COMPUTE_BUDGET_EXCEEDED",
minimum=0.001,
maximum=float(envelope.max_compute_budget),
)
tests = _unique_identifiers(proposal["tests"], "OPTIMIZATION_TESTS_INVALID")
gates = _unique_identifiers(proposal["quality_gates"], "OPTIMIZATION_QUALITY_GATES_INVALID")
for mandatory in envelope.promotion_rules.mandatory_tests:
if mandatory not in tests:
raise OptimizationError("OPTIMIZATION_MANDATORY_TEST_MISSING")
for mandatory in envelope.promotion_rules.mandatory_quality_gates:
if mandatory not in gates:
raise OptimizationError("OPTIMIZATION_MANDATORY_GATE_MISSING")
raw_baseline = proposal["baseline_metrics"]
if not isinstance(raw_baseline, Mapping) or set(raw_baseline) != set(envelope.quality_floor):
raise OptimizationError("OPTIMIZATION_BASELINE_METRICS_INVALID")
baseline = {
metric: _number(raw_baseline[metric], "OPTIMIZATION_BASELINE_METRICS_INVALID")
for metric in sorted(raw_baseline)
}
rollback_plan = proposal["rollback_plan"]
if envelope.rollback_requirement:
if (
not isinstance(rollback_plan, Mapping)
or set(rollback_plan) != {"strategy", "reference"}
or rollback_plan.get("strategy") != "restore_previous_values"
):
raise OptimizationError("OPTIMIZATION_ROLLBACK_PLAN_REQUIRED")
_identifier(rollback_plan.get("reference"), "OPTIMIZATION_ROLLBACK_PLAN_INVALID")
normalized_rollback: dict[str, str] | None = {
"strategy": "restore_previous_values",
"reference": rollback_plan["reference"],
}
else:
if rollback_plan is not None:
raise OptimizationError("OPTIMIZATION_ROLLBACK_PLAN_INVALID")
normalized_rollback = None
normalized_evidence = None
if "evidence_reuse" in proposal:
evidence = proposal["evidence_reuse"]
if not isinstance(evidence, Mapping) or set(evidence) != {"scope", "evidence_ids"}:
raise OptimizationError("OPTIMIZATION_EVIDENCE_REUSE_INVALID")
expected_scope = {
"tenant_id": envelope.tenant_id,
"component": component,
"environment": environment,
}
if evidence["scope"] != expected_scope:
raise OptimizationError("OPTIMIZATION_EVIDENCE_SCOPE_MISMATCH")
evidence_ids = _unique_identifiers(
evidence["evidence_ids"], "OPTIMIZATION_EVIDENCE_REUSE_INVALID", maximum=128
)
normalized_evidence = {"scope": expected_scope, "evidence_ids": list(evidence_ids)}
normalized = {
"schema": PROPOSAL_SCHEMA,
"proposal_id": proposal_id,
"envelope_id": envelope.envelope_id,
"tenant_id": envelope.tenant_id,
"component": component,
"environment": environment,
"changes": changes,
"compute_budget": float(compute_budget),
"tests": list(tests),
"quality_gates": list(gates),
"baseline_metrics": baseline,
"rollback_plan": normalized_rollback,
}
if normalized_evidence is not None:
normalized["evidence_reuse"] = normalized_evidence
if len(canonical_json_bytes(normalized)) > 65_536:
raise OptimizationError("OPTIMIZATION_PROPOSAL_TOO_LARGE")
return normalized
class OptimizationController:
"""Durable proposal state machine whose only external effect is task admission."""
def __init__(
self,
path: str | Path,
envelope: OptimizationEnvelope,
submit_task: Callable[[Mapping[str, Any]], Mapping[str, Any]],
*,
verify_receipt: Callable[[Mapping[str, Any]], bool],
):
if not isinstance(envelope, OptimizationEnvelope):
raise OptimizationError("OPTIMIZATION_ENVELOPE_REQUIRED")
if not callable(submit_task):
raise OptimizationError("OPTIMIZATION_TASK_SUBMITTER_REQUIRED")
if not callable(verify_receipt):
raise OptimizationError("OPTIMIZATION_RECEIPT_VERIFIER_REQUIRED")
self.path = Path(path)
self.path.parent.mkdir(parents=True, exist_ok=True)
self.envelope = envelope
self.submit_task = submit_task
self.verify_receipt = verify_receipt
self._initialize()
def _connect(self) -> sqlite3.Connection:
connection = sqlite3.connect(self.path, timeout=30)
connection.row_factory = sqlite3.Row
connection.execute("PRAGMA foreign_keys=ON")
return connection
def _initialize(self) -> None:
with self._connect() as connection:
connection.execute("PRAGMA journal_mode=WAL")
connection.execute(
"""
CREATE TABLE IF NOT EXISTS optimization_proposals (
proposal_id TEXT PRIMARY KEY,
tenant_id TEXT NOT NULL,
subject_id TEXT NOT NULL,
grant_id TEXT NOT NULL,
mission_id TEXT NOT NULL,
envelope_id TEXT NOT NULL,
proposal_digest TEXT NOT NULL,
proposal_json TEXT NOT NULL,
status TEXT NOT NULL,
evaluation_task_id TEXT,
promotion_task_id TEXT,
baseline_json TEXT NOT NULL,
candidate_json TEXT,
effect_json TEXT,
evaluation_json TEXT,
receipt_json TEXT,
error_code TEXT,
created_at REAL NOT NULL,
updated_at REAL NOT NULL
)
"""
)
def _context(self, value: Mapping[str, Any]) -> dict[str, str]:
if not isinstance(value, Mapping):
raise OptimizationError("OPTIMIZATION_AUTHENTICATED_CONTEXT_REQUIRED")
fields = ("tenant_id", "subject_id", "grant_id", "mission_id", "service_identity")
try:
context = {
field: _identifier(value[field], "OPTIMIZATION_AUTHENTICATED_CONTEXT_INVALID")
for field in fields
}
except KeyError as exc:
raise OptimizationError("OPTIMIZATION_AUTHENTICATED_CONTEXT_REQUIRED") from exc
if context["service_identity"] != "aios-root-service":
raise OptimizationError("OPTIMIZATION_ROOT_SERVICE_REQUIRED")
if context["tenant_id"] != self.envelope.tenant_id:
raise OptimizationError("OPTIMIZATION_CONTEXT_TENANT_MISMATCH")
return context
def _row(self, proposal_id: str) -> sqlite3.Row:
with self._connect() as connection:
row = connection.execute(
"SELECT * FROM optimization_proposals WHERE proposal_id=?", (proposal_id,)
).fetchone()
if row is None:
raise OptimizationError("OPTIMIZATION_PROPOSAL_NOT_FOUND")
return row
def _authorized_row(self, proposal_id: str, context: Mapping[str, Any]) -> sqlite3.Row:
checked = self._context(context)
row = self._row(_identifier(proposal_id, "OPTIMIZATION_PROPOSAL_ID_INVALID"))
if row["tenant_id"] != checked["tenant_id"]:
raise OptimizationError("OPTIMIZATION_CONTEXT_TENANT_MISMATCH")
if row["subject_id"] != checked["subject_id"]:
raise OptimizationError("OPTIMIZATION_CONTEXT_SUBJECT_MISMATCH")
if row["grant_id"] != checked["grant_id"]:
raise OptimizationError("OPTIMIZATION_CONTEXT_GRANT_MISMATCH")
if row["mission_id"] != checked["mission_id"]:
raise OptimizationError("OPTIMIZATION_CONTEXT_MISSION_MISMATCH")
return row
@staticmethod
def _decode(value: str | None) -> Any:
return None if value is None else json.loads(value)
def _record(self, row: sqlite3.Row) -> dict:
proposal = self._decode(row["proposal_json"])
result = dict(proposal)
result.update(
{
"proposal_id": row["proposal_id"],
"proposal_digest": row["proposal_digest"],
"status": row["status"],
"evaluation_task_id": row["evaluation_task_id"],
"promotion_task_id": row["promotion_task_id"],
"baseline_metrics": self._decode(row["baseline_json"]),
"candidate_metrics": self._decode(row["candidate_json"]),
"effect_comparison": self._decode(row["effect_json"]),
"error_code": row["error_code"],
}
)
return result
def get_proposal(self, proposal_id: str, *, authenticated_context: Mapping[str, Any]) -> dict:
return self._record(self._authorized_row(proposal_id, authenticated_context))
def _persist_invalid(self, proposal: Mapping[str, Any], context: Mapping[str, str],
error: OptimizationError) -> None:
proposal_id = proposal.get("proposal_id")
tenant_id = proposal.get("tenant_id")
if (
not isinstance(proposal_id, str)
or _IDENTIFIER.fullmatch(proposal_id) is None
or tenant_id != self.envelope.tenant_id
):
return
try:
encoded = canonical_json_bytes(proposal)
except OptimizationError:
return
if len(encoded) > 65_536:
return
digest = "sha256:" + hashlib.sha256(encoded).hexdigest()
baseline = proposal.get("baseline_metrics")
if not isinstance(baseline, Mapping):
baseline = {}
now = time.time()
with self._connect() as connection:
connection.execute(
"""
INSERT OR IGNORE INTO optimization_proposals
(proposal_id,tenant_id,subject_id,grant_id,mission_id,envelope_id,
proposal_digest,proposal_json,status,baseline_json,error_code,created_at,updated_at)
VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?)
""",
(
proposal_id, self.envelope.tenant_id, context["subject_id"],
context["grant_id"], context["mission_id"], self.envelope.envelope_id,
digest, encoded.decode("utf-8"), "STOPPED_INVALID",
canonical_json_bytes(baseline).decode("utf-8"), error.code, now, now,
),
)
def propose(self, proposal: Mapping[str, Any], *, authenticated_context: Mapping[str, Any]) -> dict:
context = self._context(authenticated_context)
try:
normalized = validate_proposal(self.envelope, proposal)
except OptimizationError as exc:
if isinstance(proposal, Mapping):
self._persist_invalid(proposal, context, exc)
raise
proposal_digest = _digest(normalized)
proposal_id = normalized["proposal_id"]
now = time.time()
encoded = canonical_json_bytes(normalized).decode("utf-8")
with self._connect() as connection:
existing = connection.execute(
"SELECT * FROM optimization_proposals WHERE proposal_id=?", (proposal_id,)
).fetchone()
if existing is not None:
if existing["proposal_digest"] != proposal_digest:
raise OptimizationError("OPTIMIZATION_PROPOSAL_ID_CONFLICT")
if (
existing["tenant_id"] != context["tenant_id"]
or existing["subject_id"] != context["subject_id"]
or existing["grant_id"] != context["grant_id"]
or existing["mission_id"] != context["mission_id"]
):
raise OptimizationError("OPTIMIZATION_CONTEXT_BINDING_MISMATCH")
if existing["status"] != "PROPOSAL_RECORDED":
return self._record(existing)
else:
connection.execute(
"""
INSERT INTO optimization_proposals
(proposal_id,tenant_id,subject_id,grant_id,mission_id,envelope_id,
proposal_digest,proposal_json,status,baseline_json,created_at,updated_at)
VALUES(?,?,?,?,?,?,?,?,?,?,?,?)
""",
(
proposal_id, context["tenant_id"], context["subject_id"],
context["grant_id"], context["mission_id"], self.envelope.envelope_id,
proposal_digest, encoded, "PROPOSAL_RECORDED",
canonical_json_bytes(normalized["baseline_metrics"]).decode("utf-8"),
now, now,
),
)
requested_task_id = f"optimization-evaluate-{proposal_id}"
task_request = {
"schema": TASK_REQUEST_SCHEMA,
"task_id": requested_task_id,
"action": "evaluate",
"tenant_id": context["tenant_id"],
"subject_id": context["subject_id"],
"grant_id": context["grant_id"],
"mission_id": context["mission_id"],
"proposal_id": proposal_id,
"proposal_digest": proposal_digest,
"component": normalized["component"],
"environment": normalized["environment"],
"compute_budget": normalized["compute_budget"],
}
try:
receipt = self.submit_task(task_request)
if not isinstance(receipt, Mapping):
raise OptimizationError("OPTIMIZATION_TASK_SUBMISSION_INVALID")
task_id = _identifier(
receipt.get("task_id"), "OPTIMIZATION_TASK_SUBMISSION_INVALID"
)
except Exception as exc:
with self._connect() as connection:
connection.execute(
"UPDATE optimization_proposals SET status=?,error_code=?,updated_at=? "
"WHERE proposal_id=? AND status='PROPOSAL_RECORDED'",
("STOPPED_SUBMISSION_FAILED", "OPTIMIZATION_TASK_SUBMISSION_FAILED", time.time(), proposal_id),
)
if isinstance(exc, OptimizationError):
raise
raise OptimizationError("OPTIMIZATION_TASK_SUBMISSION_FAILED") from exc
with self._connect() as connection:
connection.execute(
"UPDATE optimization_proposals SET status=?,evaluation_task_id=?,updated_at=? "
"WHERE proposal_id=? AND status='PROPOSAL_RECORDED'",
("EVALUATION_SUBMITTED", task_id, time.time(), proposal_id),
)
return self.get_proposal(proposal_id, authenticated_context=context)
def _trusted_receipt(self, receipt: Any) -> Mapping[str, Any]:
if not isinstance(receipt, Mapping):
raise OptimizationError("OPTIMIZATION_RECEIPT_INVALID")
try:
trusted = self.verify_receipt(receipt)
except Exception as exc:
raise OptimizationError("OPTIMIZATION_RECEIPT_UNTRUSTED") from exc
if trusted is not True:
raise OptimizationError("OPTIMIZATION_RECEIPT_UNTRUSTED")
return receipt
def record_evaluation(
self,
proposal_id: str,
evaluation: Mapping[str, Any],
*,
authenticated_context: Mapping[str, Any],
) -> dict:
row = self._authorized_row(proposal_id, authenticated_context)
if row["status"] != "EVALUATION_SUBMITTED":
raise OptimizationError("OPTIMIZATION_EVALUATION_STATE_INVALID")
if not isinstance(evaluation, Mapping) or set(evaluation) != {
"candidate_metrics", "compute_used", "tests_passed",
"quality_gates_passed", "receipt",
}:
raise OptimizationError("OPTIMIZATION_EVALUATION_INVALID")
result_body = {key: evaluation[key] for key in evaluation if key != "receipt"}
receipt = self._trusted_receipt(evaluation["receipt"])
required_receipt = {
"schema", "task_id", "proposal_id", "proposal_digest", "action",
"success", "result_digest", "key_id", "signature",
}
if set(receipt) != required_receipt:
raise OptimizationError("OPTIMIZATION_RECEIPT_INVALID")
if receipt["schema"] != self.envelope.promotion_rules.required_receipt_schema:
raise OptimizationError("OPTIMIZATION_RECEIPT_SCHEMA_MISMATCH")
if receipt["task_id"] != row["evaluation_task_id"]:
raise OptimizationError("OPTIMIZATION_RECEIPT_TASK_MISMATCH")
if receipt["proposal_id"] != row["proposal_id"]:
raise OptimizationError("OPTIMIZATION_RECEIPT_PROPOSAL_MISMATCH")
if receipt["proposal_digest"] != row["proposal_digest"]:
raise OptimizationError("OPTIMIZATION_RECEIPT_PROPOSAL_DIGEST_MISMATCH")
if receipt["action"] != "evaluate" or receipt["success"] is not True:
raise OptimizationError("OPTIMIZATION_RECEIPT_RESULT_INVALID")
if receipt["result_digest"] != _digest(result_body):
raise OptimizationError("OPTIMIZATION_RECEIPT_RESULT_DIGEST_MISMATCH")
proposal = self._decode(row["proposal_json"])
compute_used = _number(
evaluation["compute_used"], "OPTIMIZATION_EVALUATION_BUDGET_EXCEEDED", minimum=0
)
if compute_used > proposal["compute_budget"] or compute_used > self.envelope.max_compute_budget:
status = "STOPPED_BUDGET_EXCEEDED"
error_code = "OPTIMIZATION_EVALUATION_BUDGET_EXCEEDED"
return self._store_evaluation(row, evaluation, receipt, {}, status, error_code)
tests_passed = _unique_identifiers(
evaluation["tests_passed"], "OPTIMIZATION_EVALUATION_TESTS_INVALID"
)
gates_passed = _unique_identifiers(
evaluation["quality_gates_passed"], "OPTIMIZATION_EVALUATION_GATES_INVALID"
)
if not set(self.envelope.promotion_rules.mandatory_tests) <= set(tests_passed):
return self._store_evaluation(
row, evaluation, receipt, {}, "STOPPED_MANDATORY_TEST_FAILED",
"OPTIMIZATION_MANDATORY_TEST_FAILED",
)
if not set(self.envelope.promotion_rules.mandatory_quality_gates) <= set(gates_passed):
return self._store_evaluation(
row, evaluation, receipt, {}, "STOPPED_MANDATORY_GATE_FAILED",
"OPTIMIZATION_MANDATORY_GATE_FAILED",
)
raw_candidate = evaluation["candidate_metrics"]
if not isinstance(raw_candidate, Mapping) or set(raw_candidate) != set(self.envelope.quality_floor):
raise OptimizationError("OPTIMIZATION_CANDIDATE_METRICS_INVALID")
candidate = {
metric: _number(raw_candidate[metric], "OPTIMIZATION_CANDIDATE_METRICS_INVALID")
for metric in sorted(raw_candidate)
}
baseline = self._decode(row["baseline_json"])
effect: dict[str, float] = {}
quality_regression = False
for metric in sorted(self.envelope.quality_floor):
rule = self.envelope.quality_floor[metric]
if rule.direction == "higher":
delta = float(candidate[metric]) - float(baseline[metric])
below_floor = candidate[metric] < rule.threshold
else:
delta = float(baseline[metric]) - float(candidate[metric])
below_floor = candidate[metric] > rule.threshold
effect[metric] = round(delta, 12)
quality_regression = quality_regression or below_floor or delta < -1e-12
objective = self.envelope.promotion_rules.objective_metric
if quality_regression:
status, error_code = "STOPPED_QUALITY_REGRESSION", "OPTIMIZATION_QUALITY_REGRESSION"
elif effect[objective] + 1e-12 < self.envelope.promotion_rules.minimum_improvement:
status, error_code = "STOPPED_NO_IMPROVEMENT", "OPTIMIZATION_NO_IMPROVEMENT"
else:
status, error_code = "ELIGIBLE", None
return self._store_evaluation(row, evaluation, receipt, effect, status, error_code, candidate)
def _store_evaluation(
self,
row: sqlite3.Row,
evaluation: Mapping[str, Any],
receipt: Mapping[str, Any],
effect: Mapping[str, Any],
status: str,
error_code: str | None,
candidate: Mapping[str, Any] | None = None,
) -> dict:
candidate = dict(candidate or {})
with self._connect() as connection:
connection.execute(
"""
UPDATE optimization_proposals
SET status=?,candidate_json=?,effect_json=?,evaluation_json=?,receipt_json=?,
error_code=?,updated_at=?
WHERE proposal_id=?
""",
(
status, canonical_json_bytes(candidate).decode("utf-8"),
canonical_json_bytes(effect).decode("utf-8"),
canonical_json_bytes({k: evaluation[k] for k in evaluation if k != "receipt"}).decode("utf-8"),
canonical_json_bytes(receipt).decode("utf-8"), error_code, time.time(),
row["proposal_id"],
),
)
return self._record(self._row(row["proposal_id"]))
def promote(
self,
proposal_id: str,
promotion: Mapping[str, Any],
*,
authenticated_context: Mapping[str, Any],
) -> dict:
context = self._context(authenticated_context)
row = self._authorized_row(proposal_id, context)
if not isinstance(promotion, Mapping) or set(promotion) != {
"evaluation_task_id", "proposal_digest"
}:
raise OptimizationError("OPTIMIZATION_PROMOTION_REQUEST_INVALID")
if promotion["evaluation_task_id"] != row["evaluation_task_id"]:
raise OptimizationError("OPTIMIZATION_PROMOTION_TASK_MISMATCH")
if promotion["proposal_digest"] != row["proposal_digest"]:
raise OptimizationError("OPTIMIZATION_PROMOTION_PROPOSAL_MISMATCH")
if row["status"] == "PROMOTION_SUBMITTED":
return self._record(row)
if row["status"] != "ELIGIBLE":
raise OptimizationError("OPTIMIZATION_NOT_PROMOTABLE")
stored_receipt = self._trusted_receipt(self._decode(row["receipt_json"]))
if (
stored_receipt.get("task_id") != row["evaluation_task_id"]
or stored_receipt.get("proposal_digest") != row["proposal_digest"]
or stored_receipt.get("action") != "evaluate"
or stored_receipt.get("success") is not True
):
raise OptimizationError("OPTIMIZATION_PROMOTION_RECEIPT_BINDING_INVALID")
evaluation = self._decode(row["evaluation_json"])
if stored_receipt.get("result_digest") != _digest(evaluation):
raise OptimizationError("OPTIMIZATION_PROMOTION_QUALITY_RESULT_INVALID")
task_request = {
"schema": TASK_REQUEST_SCHEMA,
"task_id": f"optimization-promote-{proposal_id}",
"action": "promote",
"tenant_id": context["tenant_id"],
"subject_id": context["subject_id"],
"grant_id": context["grant_id"],
"mission_id": context["mission_id"],
"proposal_id": row["proposal_id"],
"proposal_digest": row["proposal_digest"],
"evaluation_task_id": row["evaluation_task_id"],
"evaluation_result_digest": stored_receipt["result_digest"],
}
try:
submission = self.submit_task(task_request)
if not isinstance(submission, Mapping):
raise OptimizationError("OPTIMIZATION_TASK_SUBMISSION_INVALID")
task_id = _identifier(
submission.get("task_id"), "OPTIMIZATION_TASK_SUBMISSION_INVALID"
)
except Exception as exc:
with self._connect() as connection:
connection.execute(
"UPDATE optimization_proposals SET status=?,error_code=?,updated_at=? WHERE proposal_id=?",
("STOPPED_PROMOTION_SUBMISSION_FAILED", "OPTIMIZATION_TASK_SUBMISSION_FAILED", time.time(), proposal_id),
)
if isinstance(exc, OptimizationError):
raise
raise OptimizationError("OPTIMIZATION_TASK_SUBMISSION_FAILED") from exc
with self._connect() as connection:
connection.execute(
"UPDATE optimization_proposals SET status=?,promotion_task_id=?,error_code=NULL,updated_at=? "
"WHERE proposal_id=? AND status='ELIGIBLE'",
("PROMOTION_SUBMITTED", task_id, time.time(), proposal_id),
)
return self.get_proposal(proposal_id, authenticated_context=context)
__all__ = [
"OptimizationController", "OptimizationEnvelope", "OptimizationError",
"canonical_json_bytes", "validate_proposal",
]
+742
View File
@@ -0,0 +1,742 @@
"""Release-owned deployment targets and a receipt-gated persistent saga.
The module persists intent before invoking a governed operation adapter. It
does not contain a shell runner, production signer, or success simulator. A
configured verifier must translate an adapter/sink receipt into a typed,
operation-bound :class:`VerifiedReceipt` before a stage can advance.
"""
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime, timezone
import hashlib
import json
from pathlib import Path
import sqlite3
from typing import Any, Callable, Mapping, Sequence
from .lineage import (
LineageLedger,
VerifiedAcceptanceEvidence,
canonical_artifact,
decode_artifact,
)
class ReleaseError(RuntimeError):
pass
class ReleaseConflict(ReleaseError):
pass
class ReleaseVerificationError(ReleaseError):
pass
class ReleaseAuthorizationError(ReleaseError):
pass
class ReleaseReconciliationRequired(ReleaseError):
pass
@dataclass(frozen=True)
class VerifiedReceipt:
"""Trusted verifier output, fully bound to one persisted stage effect."""
receipt_id: str
tenant_id: str
subject_id: str
component_id: str
task_id: str
source_task_id: str
source_effect_id: str
grant_id: str
job_id: str
effect_id: str
operation_id: str
deployment_id: str
release_id: str
artifact: Any
observed: Mapping[str, Any]
outcome: str = "SUCCEEDED"
observed_at: str | None = None
@dataclass(frozen=True)
class EnvironmentDeploymentTarget:
tenant_id: str
component_id: str
environment: str
release_id: str
artifact: Any
revision: int
stage: str
source_revision: str | None
observed_main_revision: str | None
previous_release_id: str | None
previous_artifact: Any | None
rollback_release_id: str | None
rollback_artifact: Any | None
rollback_compatible: bool
updated_by_subject_id: str
updated_at: str
def _now() -> str:
return datetime.now(timezone.utc).isoformat()
def _required(value: Any, name: str) -> str:
text = str(value or "")
if not text or len(text) > 512 or any(ord(char) < 32 for char in text):
raise ValueError(f"invalid {name}")
return text
def _json(value: Any) -> str:
return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"), allow_nan=False)
def _recipe_digest(stages: Sequence[Mapping[str, Any]]) -> str:
return hashlib.sha256(_json(list(stages)).encode()).hexdigest()
class ReleaseLedger:
"""The release domain authority for dynamic targets and deployment sagas."""
TARGET_STAGES = {"STAGED", "ACTIVE", "ROLLBACK_STAGED"}
def __init__(
self,
path: str | Path,
*,
lineage: LineageLedger,
receipt_verifier: Callable[[Any, Mapping[str, Any]], VerifiedReceipt],
target_authorizer: Callable[[str,str,str,str,str], bool],
):
if not isinstance(lineage, LineageLedger):
raise ValueError("lineage ledger is required")
if not callable(receipt_verifier):
raise ValueError("receipt_verifier is required")
if not callable(target_authorizer):
raise ValueError("target_authorizer is required")
self.path = str(path)
self.lineage = lineage
self.receipt_verifier = receipt_verifier
self.target_authorizer = target_authorizer
Path(self.path).parent.mkdir(parents=True, exist_ok=True)
self._initialize()
def _connect(self) -> sqlite3.Connection:
connection = sqlite3.connect(self.path, timeout=30)
connection.row_factory = sqlite3.Row
connection.execute("PRAGMA foreign_keys=ON")
connection.execute("PRAGMA busy_timeout=30000")
return connection
def _initialize(self) -> None:
with self._connect() as connection:
connection.execute("PRAGMA journal_mode=WAL")
tables={row[0] for row in connection.execute("SELECT name FROM sqlite_master WHERE type='table'")}
if "release_targets" in tables and "release_schema_meta" not in tables:
raise ReleaseConflict("unversioned candidate release schema requires explicit migration")
if "release_schema_meta" in tables:
version=connection.execute("SELECT version FROM release_schema_meta WHERE domain='release'").fetchone()
if version is None or version[0] != 2:
raise ReleaseConflict("unsupported release schema version")
connection.executescript(
"""
CREATE TABLE IF NOT EXISTS release_schema_meta (
domain TEXT PRIMARY KEY,
version INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS release_recipes (
recipe_id TEXT PRIMARY KEY,
recipe_digest TEXT NOT NULL,
stages_json TEXT NOT NULL,
created_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS release_targets (
tenant_id TEXT NOT NULL,
component_id TEXT NOT NULL,
environment TEXT NOT NULL,
release_id TEXT NOT NULL,
artifact_json TEXT NOT NULL,
revision INTEGER NOT NULL,
stage TEXT NOT NULL,
source_revision TEXT,
observed_main_revision TEXT,
previous_release_id TEXT,
previous_artifact_json TEXT,
rollback_release_id TEXT,
rollback_artifact_json TEXT,
rollback_compatible INTEGER NOT NULL CHECK (rollback_compatible IN (0,1)),
updated_by_subject_id TEXT NOT NULL,
updated_at TEXT NOT NULL,
PRIMARY KEY (tenant_id, component_id, environment)
);
CREATE TABLE IF NOT EXISTS release_sagas (
tenant_id TEXT NOT NULL,
saga_id TEXT NOT NULL,
subject_id TEXT NOT NULL,
mission_id TEXT NOT NULL,
component_id TEXT NOT NULL,
source_task_id TEXT NOT NULL,
source_effect_id TEXT NOT NULL,
grant_id TEXT NOT NULL,
release_id TEXT NOT NULL,
deployment_id TEXT NOT NULL,
environment TEXT NOT NULL,
artifact_json TEXT NOT NULL,
recipe_id TEXT NOT NULL,
recipe_digest TEXT NOT NULL,
target_revision INTEGER NOT NULL,
stage_index INTEGER NOT NULL,
state TEXT NOT NULL,
revision INTEGER NOT NULL,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
PRIMARY KEY (tenant_id, saga_id),
UNIQUE (tenant_id, deployment_id),
FOREIGN KEY (recipe_id) REFERENCES release_recipes(recipe_id)
);
CREATE TABLE IF NOT EXISTS release_effect_intents (
tenant_id TEXT NOT NULL,
saga_id TEXT NOT NULL,
stage_index INTEGER NOT NULL,
stage_id TEXT NOT NULL,
operation_id TEXT NOT NULL,
effect_id TEXT NOT NULL,
job_id TEXT,
state TEXT NOT NULL,
receipt_id TEXT,
receipt_json TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
PRIMARY KEY (tenant_id, saga_id, stage_index),
UNIQUE (tenant_id, effect_id),
FOREIGN KEY (tenant_id, saga_id)
REFERENCES release_sagas(tenant_id, saga_id)
);
CREATE TABLE IF NOT EXISTS release_deployment_facts (
tenant_id TEXT NOT NULL,
fact_id INTEGER PRIMARY KEY AUTOINCREMENT,
subject_id TEXT NOT NULL,
mission_id TEXT NOT NULL,
component_id TEXT NOT NULL,
task_id TEXT NOT NULL,
grant_id TEXT NOT NULL,
job_id TEXT NOT NULL,
deployment_id TEXT NOT NULL,
release_id TEXT NOT NULL,
artifact_json TEXT NOT NULL,
saga_id TEXT NOT NULL,
stage_id TEXT NOT NULL,
operation_id TEXT NOT NULL,
effect_id TEXT NOT NULL,
receipt_id TEXT NOT NULL,
observed_json TEXT NOT NULL,
created_at TEXT NOT NULL,
UNIQUE (tenant_id, effect_id, receipt_id),
FOREIGN KEY (tenant_id, saga_id)
REFERENCES release_sagas(tenant_id, saga_id)
);
"""
)
connection.execute("INSERT OR IGNORE INTO release_schema_meta(domain,version) VALUES('release',2)")
def close(self) -> None:
"""Compatibility hook; connections are transaction-scoped."""
def _authorize(self, tenant_id: str, subject_id: str, component_id: str, environment: str, action: str) -> None:
try:
authorized=self.target_authorizer(tenant_id,subject_id,component_id,environment,action)
except Exception as exc:
raise ReleaseAuthorizationError("deployment target authorization unavailable") from exc
if authorized is not True:
raise ReleaseAuthorizationError("deployment target authorization denied")
def register_recipe(self, recipe_id: str, stages: Sequence[Mapping[str, Any]]) -> dict[str, Any]:
recipe_id = _required(recipe_id, "recipe_id")
if not isinstance(stages, Sequence) or isinstance(stages, (str, bytes)) or not stages:
raise ValueError("recipe stages must be a non-empty sequence")
normalized = []
stage_ids = set()
for item in stages:
if not isinstance(item, Mapping) or set(item) - {"stage_id", "operation_id", "parameters"}:
raise ValueError("invalid recipe stage")
stage_id = _required(item.get("stage_id"), "stage_id")
operation_id = _required(item.get("operation_id"), "operation_id")
if stage_id in stage_ids:
raise ValueError("duplicate recipe stage_id")
stage_ids.add(stage_id)
parameters = item.get("parameters", {})
if not isinstance(parameters, Mapping):
raise ValueError("stage parameters must be an object")
normalized.append({"stage_id": stage_id, "operation_id": operation_id, "parameters": dict(parameters)})
stages_json = _json(normalized)
digest = _recipe_digest(normalized)
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
existing = connection.execute("SELECT * FROM release_recipes WHERE recipe_id=?", (recipe_id,)).fetchone()
if existing:
if existing["recipe_digest"] != digest:
raise ReleaseConflict("recipe_id already has different immutable stages")
else:
connection.execute("INSERT INTO release_recipes VALUES(?,?,?,?)", (recipe_id, digest, stages_json, _now()))
return {"recipe_id": recipe_id, "recipe_digest": digest, "stages": normalized}
def set_target(
self,
tenant_id: str,
subject_id: str,
component_id: str,
environment: str,
release_id: str,
artifact: Any,
*,
expected_revision: int | None,
stage: str = "STAGED",
source_revision: str | None = None,
observed_main_revision: str | None = None,
rollback_release_id: str | None = None,
rollback_artifact: Any | None = None,
rollback_compatible: bool = False,
) -> EnvironmentDeploymentTarget:
tenant_id, subject_id, component_id, environment, release_id = map(
lambda pair: _required(pair[0], pair[1]),
((tenant_id,"tenant_id"),(subject_id,"subject_id"),(component_id,"component_id"),(environment,"environment"),(release_id,"release_id")),
)
self._authorize(tenant_id,subject_id,component_id,environment,"write")
if stage not in self.TARGET_STAGES:
raise ValueError("invalid target stage")
if expected_revision is not None and (not isinstance(expected_revision, int) or isinstance(expected_revision, bool) or expected_revision < 0):
raise ValueError("invalid expected_revision")
if type(rollback_compatible) is not bool:
raise ValueError("rollback_compatible must be boolean")
artifact_json = canonical_artifact(artifact)
try:
linked_release = self.lineage.get_release_for_component(tenant_id, component_id, release_id)
except KeyError as exc:
raise ReleaseConflict("deployment target release has no lineage") from exc
if canonical_artifact(linked_release["artifact"]) != artifact_json:
raise ReleaseConflict("deployment target artifact does not match release")
rollback_artifact_json = canonical_artifact(rollback_artifact) if rollback_artifact is not None else None
source_revision = _required(source_revision, "source_revision") if source_revision else None
observed_main_revision = _required(observed_main_revision, "observed_main_revision") if observed_main_revision else None
rollback_release_id = _required(rollback_release_id, "rollback_release_id") if rollback_release_id else None
if (rollback_release_id is None) != (rollback_artifact_json is None):
raise ValueError("rollback release and artifact must be supplied together")
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
current = connection.execute(
"SELECT * FROM release_targets WHERE tenant_id=? AND component_id=? AND environment=?",
(tenant_id, component_id, environment),
).fetchone()
if current is None:
if expected_revision is not None:
raise ReleaseConflict("expected revision does not match absent target")
if stage == "ROLLBACK_STAGED":
raise ReleaseConflict("rollback requires a current target")
revision = 1
previous_release_id = previous_artifact_json = None
else:
if expected_revision != current["revision"]:
raise ReleaseConflict("expected revision does not match target")
if stage == "ROLLBACK_STAGED":
if not current["rollback_compatible"]:
raise ReleaseConflict("current target has no compatible rollback")
if release_id != current["rollback_release_id"] or artifact_json != current["rollback_artifact_json"]:
raise ReleaseConflict("rollback target does not match compatible rollback binding")
revision = current["revision"] + 1
previous_release_id = current["release_id"]
previous_artifact_json = current["artifact_json"]
if rollback_compatible and rollback_release_id is None:
rollback_release_id = previous_release_id
rollback_artifact_json = previous_artifact_json
if rollback_compatible and current is not None and rollback_release_id is None:
raise ReleaseConflict("compatible rollback requires a previous target")
now = _now()
connection.execute(
"""INSERT INTO release_targets VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
ON CONFLICT(tenant_id,component_id,environment) DO UPDATE SET
release_id=excluded.release_id, artifact_json=excluded.artifact_json,
revision=excluded.revision, stage=excluded.stage,
source_revision=excluded.source_revision,
observed_main_revision=excluded.observed_main_revision,
previous_release_id=excluded.previous_release_id,
previous_artifact_json=excluded.previous_artifact_json,
rollback_release_id=excluded.rollback_release_id,
rollback_artifact_json=excluded.rollback_artifact_json,
rollback_compatible=excluded.rollback_compatible,
updated_by_subject_id=excluded.updated_by_subject_id,
updated_at=excluded.updated_at""",
(tenant_id,component_id,environment,release_id,artifact_json,revision,stage,
source_revision,observed_main_revision,previous_release_id,previous_artifact_json,
rollback_release_id,rollback_artifact_json,int(rollback_compatible),subject_id,now),
)
row = connection.execute(
"SELECT * FROM release_targets WHERE tenant_id=? AND component_id=? AND environment=?",
(tenant_id,component_id,environment),
).fetchone()
return self._target(row)
def get_target(self, tenant_id: str, subject_id: str, component_id: str, environment: str) -> EnvironmentDeploymentTarget:
tenant_id,subject_id,component_id,environment=(
_required(tenant_id,"tenant_id"),_required(subject_id,"subject_id"),
_required(component_id,"component_id"),_required(environment,"environment"),
)
self._authorize(tenant_id,subject_id,component_id,environment,"read")
with self._connect() as connection:
row = connection.execute(
"SELECT * FROM release_targets WHERE tenant_id=? AND component_id=? AND environment=?",
(tenant_id,component_id,environment),
).fetchone()
if row is None:
raise KeyError("deployment target not found")
return self._target(row)
@staticmethod
def _target(row: sqlite3.Row) -> EnvironmentDeploymentTarget:
return EnvironmentDeploymentTarget(
tenant_id=row["tenant_id"], component_id=row["component_id"], environment=row["environment"],
release_id=row["release_id"], artifact=decode_artifact(row["artifact_json"]), revision=row["revision"],
stage=row["stage"], source_revision=row["source_revision"], observed_main_revision=row["observed_main_revision"],
previous_release_id=row["previous_release_id"],
previous_artifact=decode_artifact(row["previous_artifact_json"]) if row["previous_artifact_json"] else None,
rollback_release_id=row["rollback_release_id"],
rollback_artifact=decode_artifact(row["rollback_artifact_json"]) if row["rollback_artifact_json"] else None,
rollback_compatible=bool(row["rollback_compatible"]),
updated_by_subject_id=row["updated_by_subject_id"], updated_at=row["updated_at"],
)
def start_saga(
self, tenant_id: str, subject_id: str, mission_id: str, component_id: str, saga_id: str,
release_id: str, deployment_id: str, environment: str, artifact: Any,
recipe_id: str, *, expected_target_revision: int,
) -> dict[str, Any]:
tenant_id,subject_id,mission_id,component_id,saga_id,release_id,deployment_id,environment,recipe_id=map(
lambda pair:_required(pair[0],pair[1]),
((tenant_id,"tenant_id"),(subject_id,"subject_id"),(mission_id,"mission_id"),(component_id,"component_id"),(saga_id,"saga_id"),
(release_id,"release_id"),(deployment_id,"deployment_id"),(environment,"environment"),(recipe_id,"recipe_id")),
)
if not isinstance(expected_target_revision,int) or isinstance(expected_target_revision,bool) or expected_target_revision < 1:
raise ValueError("invalid expected_target_revision")
self._authorize(tenant_id,subject_id,component_id,environment,"deploy")
artifact_json=canonical_artifact(artifact)
target=self.get_target(tenant_id,subject_id,component_id,environment)
if target.revision != expected_target_revision:
raise ReleaseConflict("expected target revision does not match")
if target.stage not in {"STAGED","ROLLBACK_STAGED"}:
raise ReleaseConflict("deployment target is not staged")
if target.release_id != release_id or canonical_artifact(target.artifact) != artifact_json:
raise ReleaseConflict("saga does not match deployment target")
graph=self.lineage.explain(tenant_id,subject_id,mission_id)
release=next((item for item in graph["releases"] if item["release_id"] == release_id),None)
if release is None or release["component_id"] != component_id or canonical_artifact(release["artifact"]) != artifact_json:
raise ReleaseConflict("release has no exact source lineage")
source_task=next((item for item in graph["tasks"] if item["task_id"] == release["source_task_id"]),None)
if source_task is None or source_task["component_id"] != component_id:
raise ReleaseConflict("release source Task component lineage missing")
with self._connect() as connection:
recipe=connection.execute("SELECT * FROM release_recipes WHERE recipe_id=?",(recipe_id,)).fetchone()
if recipe is None:
raise ReleaseConflict("recipe not found")
# The relationship is durable before saga admission and is idempotent on retry.
self.lineage.link_deployment(
tenant_id,subject_id,mission_id,release_id,deployment_id,component_id,environment,
expected_target_revision,decode_artifact(artifact_json),
metadata={"saga_id":saga_id,"recipe_id":recipe_id,"recipe_digest":recipe["recipe_digest"]},
)
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
existing=connection.execute("SELECT * FROM release_sagas WHERE tenant_id=? AND saga_id=?",(tenant_id,saga_id)).fetchone()
expected=(subject_id,mission_id,component_id,release["source_task_id"],release["source_effect_id"],source_task["grant_id"],release_id,deployment_id,environment,artifact_json,recipe_id,recipe["recipe_digest"],expected_target_revision)
if existing:
actual=tuple(existing[key] for key in ("subject_id","mission_id","component_id","source_task_id","source_effect_id","grant_id","release_id","deployment_id","environment","artifact_json","recipe_id","recipe_digest","target_revision"))
if actual != expected:
raise ReleaseConflict("saga_id already has different immutable binding")
return self._saga(connection,existing)
collision=connection.execute("SELECT saga_id FROM release_sagas WHERE tenant_id=? AND deployment_id=?",(tenant_id,deployment_id)).fetchone()
if collision:
raise ReleaseConflict("deployment_id already has a different saga")
now=_now()
connection.execute(
"INSERT INTO release_sagas VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
(tenant_id,saga_id,subject_id,mission_id,component_id,release["source_task_id"],release["source_effect_id"],source_task["grant_id"],release_id,deployment_id,environment,artifact_json,
recipe_id,recipe["recipe_digest"],expected_target_revision,0,"RUNNING",0,now,now),
)
row=connection.execute("SELECT * FROM release_sagas WHERE tenant_id=? AND saga_id=?",(tenant_id,saga_id)).fetchone()
return self._saga(connection,row)
def get_saga(self, tenant_id: str, subject_id: str, saga_id: str) -> dict[str, Any]:
with self._connect() as connection:
row=connection.execute("SELECT * FROM release_sagas WHERE tenant_id=? AND saga_id=?",(_required(tenant_id,"tenant_id"),_required(saga_id,"saga_id"))).fetchone()
if row is None:
raise KeyError("release saga not found")
if row["subject_id"] != _required(subject_id,"subject_id"):
raise ReleaseAuthorizationError("release saga subject denied")
return self._saga(connection,row)
@staticmethod
def _stages(connection: sqlite3.Connection, recipe_id: str) -> list[dict[str, Any]]:
row=connection.execute("SELECT stages_json FROM release_recipes WHERE recipe_id=?",(recipe_id,)).fetchone()
if row is None:
raise ReleaseConflict("persisted recipe missing")
return json.loads(row["stages_json"])
def _saga(self, connection: sqlite3.Connection, row: sqlite3.Row) -> dict[str, Any]:
stages=self._stages(connection,row["recipe_id"])
index=row["stage_index"]
return {
"tenant_id":row["tenant_id"],"subject_id":row["subject_id"],"mission_id":row["mission_id"],
"component_id":row["component_id"],"source_task_id":row["source_task_id"],
"source_effect_id":row["source_effect_id"],"grant_id":row["grant_id"],
"saga_id":row["saga_id"],"release_id":row["release_id"],"deployment_id":row["deployment_id"],
"environment":row["environment"],"artifact":decode_artifact(row["artifact_json"]),
"recipe_id":row["recipe_id"],"recipe_digest":row["recipe_digest"],
"target_revision":row["target_revision"],"stage_index":index,
"stage_id":stages[index]["stage_id"] if index < len(stages) else None,
"state":row["state"],"revision":row["revision"],"created_at":row["created_at"],"updated_at":row["updated_at"],
}
def advance_saga(
self, tenant_id: str, subject_id: str, saga_id: str, expected_revision: int,
operation_adapter: Any,
) -> dict[str, Any]:
tenant_id,subject_id,saga_id=(
_required(tenant_id,"tenant_id"),_required(subject_id,"subject_id"),_required(saga_id,"saga_id")
)
if not isinstance(expected_revision,int) or isinstance(expected_revision,bool) or expected_revision < 0:
raise ValueError("invalid expected_revision")
prepare=getattr(operation_adapter,"prepare",None)
execute=getattr(operation_adapter,"execute",None)
if not callable(prepare) or not callable(execute):
raise ValueError("governed operation adapter requires prepare and execute")
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
saga=connection.execute(
"SELECT * FROM release_sagas WHERE tenant_id=? AND saga_id=?",(tenant_id,saga_id)
).fetchone()
if saga is None:
raise KeyError("release saga not found")
if saga["subject_id"] != subject_id:
raise ReleaseAuthorizationError("saga belongs to different subject")
self._authorize(tenant_id,subject_id,saga["component_id"],saga["environment"],"deploy")
if saga["revision"] != expected_revision:
raise ReleaseConflict("expected revision does not match saga")
if saga["state"] == "COMPLETED":
return self._saga(connection,saga)
stages=self._stages(connection,saga["recipe_id"])
stage=stages[saga["stage_index"]]
effect_id=f"release:{saga_id}:{saga['stage_index']}:{stage['stage_id']}"
intent=connection.execute(
"SELECT * FROM release_effect_intents WHERE tenant_id=? AND saga_id=? AND stage_index=?",
(tenant_id,saga_id,saga["stage_index"]),
).fetchone()
if intent is None:
now=_now()
connection.execute(
"INSERT INTO release_effect_intents VALUES(?,?,?,?,?,?,?,?,?,?,?,?)",
(tenant_id,saga_id,saga["stage_index"],stage["stage_id"],stage["operation_id"],effect_id,
None,"PENDING_JOB",None,None,now,now),
)
elif (intent["effect_id"],intent["stage_id"],intent["operation_id"]) != (
effect_id,stage["stage_id"],stage["operation_id"]
):
raise ReleaseConflict("persisted stage effect identity mismatch")
operation={
"tenant_id":tenant_id,"subject_id":subject_id,"mission_id":saga["mission_id"],
"component_id":saga["component_id"],"task_id":saga["source_task_id"],
"source_task_id":saga["source_task_id"],"source_effect_id":saga["source_effect_id"],
"grant_id":saga["grant_id"],"saga_id":saga_id,
"deployment_id":saga["deployment_id"],"release_id":saga["release_id"],
"environment":saga["environment"],"artifact":decode_artifact(saga["artifact_json"]),
"target_revision":saga["target_revision"],"stage_index":saga["stage_index"],
"stage_id":stage["stage_id"],"operation_id":stage["operation_id"],"effect_id":effect_id,
"parameters":stage.get("parameters",{}),
}
with self._connect() as connection:
intent=connection.execute(
"SELECT * FROM release_effect_intents WHERE tenant_id=? AND saga_id=? AND stage_index=?",
(tenant_id,saga_id,saga["stage_index"]),
).fetchone()
job_id=intent["job_id"]
if job_id is None:
prepared=prepare(dict(operation))
job_id=prepared.get("job_id") if isinstance(prepared,Mapping) else prepared
job_id=_required(job_id,"prepared job_id")
# This is the first point where the relationship graph may name a Job:
# link_effect independently resolves the configured real Job authority.
self.lineage.link_effect(
tenant_id,subject_id,saga["mission_id"],saga["source_task_id"],effect_id,job_id,
source_effect_id=saga["source_effect_id"],
metadata={"deployment_id":saga["deployment_id"],"release_id":saga["release_id"],
"component_id":saga["component_id"],"stage_id":stage["stage_id"]},
)
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
current=connection.execute(
"SELECT revision,stage_index FROM release_sagas WHERE tenant_id=? AND saga_id=?",
(tenant_id,saga_id),
).fetchone()
if current is None or current["revision"] != expected_revision or current["stage_index"] != saga["stage_index"]:
raise ReleaseConflict("expected revision changed before Job binding")
bound=connection.execute(
"SELECT job_id FROM release_effect_intents WHERE tenant_id=? AND saga_id=? AND stage_index=?",
(tenant_id,saga_id,saga["stage_index"]),
).fetchone()
if bound["job_id"] not in (None,job_id):
raise ReleaseConflict("stage effect already bound to different Job")
connection.execute(
"UPDATE release_effect_intents SET job_id=?,state='JOB_LINKED',updated_at=? WHERE tenant_id=? AND saga_id=? AND stage_index=?",
(job_id,_now(),tenant_id,saga_id,saga["stage_index"]),
)
operation["job_id"]=job_id
with self._connect() as connection:
intent=connection.execute(
"SELECT * FROM release_effect_intents WHERE tenant_id=? AND saga_id=? AND stage_index=?",
(tenant_id,saga_id,saga["stage_index"]),
).fetchone()
if intent["receipt_json"]:
raw_receipt=json.loads(intent["receipt_json"])
elif intent["state"] == "EFFECT_UNKNOWN":
reconcile=getattr(operation_adapter,"reconcile",None)
if not callable(reconcile):
raise ReleaseReconciliationRequired("RECONCILIATION_REQUIRED: adapter readback unavailable")
raw_receipt=reconcile(dict(operation))
else:
try:
raw_receipt=execute(dict(operation))
except Exception:
with self._connect() as connection:
connection.execute(
"UPDATE release_effect_intents SET state='EFFECT_UNKNOWN',updated_at=? WHERE tenant_id=? AND saga_id=? AND stage_index=?",
(_now(),tenant_id,saga_id,saga["stage_index"]),
)
raise
if not isinstance(raw_receipt,Mapping):
with self._connect() as connection:
connection.execute(
"UPDATE release_effect_intents SET state='EFFECT_UNKNOWN',updated_at=? WHERE tenant_id=? AND saga_id=? AND stage_index=?",
(_now(),tenant_id,saga_id,saga["stage_index"]),
)
raise ReleaseVerificationError("operation adapter receipt must be an object")
try:
unverified_receipt_json=_json(dict(raw_receipt))
except (TypeError,ValueError) as exc:
raise ReleaseVerificationError("operation adapter receipt must be canonical JSON") from exc
if len(unverified_receipt_json.encode("utf-8")) > 262_144:
raise ReleaseVerificationError("operation adapter receipt is too large")
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
current=connection.execute(
"SELECT revision,stage_index FROM release_sagas WHERE tenant_id=? AND saga_id=?",(tenant_id,saga_id)
).fetchone()
if current is None or current["revision"] != expected_revision or current["stage_index"] != saga["stage_index"]:
raise ReleaseConflict("expected revision changed before receipt persistence")
connection.execute(
"UPDATE release_effect_intents SET state='RECEIPT_UNVERIFIED',receipt_json=?,updated_at=? WHERE tenant_id=? AND saga_id=? AND stage_index=?",
(unverified_receipt_json,_now(),tenant_id,saga_id,saga["stage_index"]),
)
expected={key:operation[key] for key in (
"tenant_id","subject_id","component_id","task_id","source_task_id","source_effect_id","grant_id",
"job_id","effect_id","operation_id","deployment_id","release_id","artifact"
)}
try:
verified=self.receipt_verifier(raw_receipt,expected)
except Exception as exc:
raise ReleaseVerificationError("configured verifier rejected operation receipt") from exc
if not isinstance(verified,VerifiedReceipt):
raise ReleaseVerificationError("receipt_verifier must return VerifiedReceipt")
if verified.outcome != "SUCCEEDED":
raise ReleaseVerificationError("verified receipt outcome is not SUCCEEDED")
fields=("tenant_id","subject_id","component_id","task_id","source_task_id","source_effect_id","grant_id",
"job_id","effect_id","operation_id","deployment_id","release_id","artifact")
bindings=tuple(canonical_artifact(getattr(verified,name)) if name == "artifact" else getattr(verified,name) for name in fields)
expected_bindings=tuple(canonical_artifact(operation[name]) if name == "artifact" else operation[name] for name in fields)
if bindings != expected_bindings:
wrong=next(name for name,actual,wanted in zip(fields,bindings,expected_bindings) if actual != wanted)
raise ReleaseVerificationError(f"verified receipt {wrong} binding mismatch")
receipt_id=_required(verified.receipt_id,"receipt_id")
if not isinstance(verified.observed,Mapping):
raise ReleaseVerificationError("verified receipt observation must be an object")
observed_json=_json(dict(verified.observed))
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
current=connection.execute("SELECT * FROM release_sagas WHERE tenant_id=? AND saga_id=?",(tenant_id,saga_id)).fetchone()
if current["revision"] != expected_revision or current["stage_index"] != saga["stage_index"]:
raise ReleaseConflict("expected revision changed before receipt commit")
connection.execute(
"UPDATE release_effect_intents SET state='VERIFIED',receipt_id=?,updated_at=? WHERE tenant_id=? AND saga_id=? AND stage_index=?",
(receipt_id,_now(),tenant_id,saga_id,saga["stage_index"]),
)
connection.execute(
"""INSERT OR IGNORE INTO release_deployment_facts
(tenant_id,subject_id,mission_id,component_id,task_id,grant_id,job_id,deployment_id,release_id,artifact_json,saga_id,stage_id,operation_id,effect_id,receipt_id,observed_json,created_at)
VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
(tenant_id,subject_id,saga["mission_id"],saga["component_id"],saga["source_task_id"],saga["grant_id"],job_id,
saga["deployment_id"],saga["release_id"],saga["artifact_json"],saga_id,stage["stage_id"],stage["operation_id"],
effect_id,receipt_id,observed_json,_now()),
)
next_index=saga["stage_index"]+1
state="COMPLETED" if next_index == len(stages) else "RUNNING"
updated=connection.execute(
"UPDATE release_sagas SET stage_index=?,state=?,revision=revision+1,updated_at=? WHERE tenant_id=? AND saga_id=? AND revision=?",
(next_index,state,_now(),tenant_id,saga_id,expected_revision),
)
if updated.rowcount != 1:
raise ReleaseConflict("expected revision changed")
row=connection.execute("SELECT * FROM release_sagas WHERE tenant_id=? AND saga_id=?",(tenant_id,saga_id)).fetchone()
return self._saga(connection,row)
def deployment_facts(self, tenant_id: str, subject_id: str, component_id: str, deployment_id: str) -> list[dict[str, Any]]:
tenant_id,subject_id,component_id,deployment_id=(
_required(tenant_id,"tenant_id"),_required(subject_id,"subject_id"),
_required(component_id,"component_id"),_required(deployment_id,"deployment_id"),
)
with self._connect() as connection:
saga=connection.execute(
"SELECT environment,subject_id,component_id FROM release_sagas WHERE tenant_id=? AND deployment_id=?",
(tenant_id,deployment_id),
).fetchone()
if saga is None or saga["subject_id"] != subject_id or saga["component_id"] != component_id:
raise ReleaseAuthorizationError("deployment facts scope denied")
self._authorize(tenant_id,subject_id,component_id,saga["environment"],"read")
rows=connection.execute(
"SELECT * FROM release_deployment_facts WHERE tenant_id=? AND subject_id=? AND component_id=? AND deployment_id=? ORDER BY fact_id",
(tenant_id,subject_id,component_id,deployment_id),
).fetchall()
return [{**{key:row[key] for key in row.keys() if not key.endswith("_json")},
"artifact":decode_artifact(row["artifact_json"]),"observed":json.loads(row["observed_json"])} for row in rows]
def record_acceptance(
self, tenant_id: str, subject_id: str, mission_id: str, component_id: str, acceptance_id: str,
deployment_id: str, release_id: str, artifact: Any, accepted: bool, *,
evidence: Any,
verifier: Callable[[Any, Mapping[str, Any]], VerifiedAcceptanceEvidence],
) -> dict[str, Any]:
with self._connect() as connection:
saga=connection.execute(
"SELECT environment,subject_id,component_id FROM release_sagas WHERE tenant_id=? AND deployment_id=?",
(tenant_id,deployment_id),
).fetchone()
if saga is None or saga["subject_id"] != subject_id or saga["component_id"] != component_id:
raise ReleaseAuthorizationError("acceptance deployment scope denied")
self._authorize(tenant_id,subject_id,component_id,saga["environment"],"accept")
return self.lineage.record_acceptance(
tenant_id,subject_id,mission_id,component_id,acceptance_id,deployment_id,release_id,
artifact,accepted,evidence=evidence,verifier=verifier,
)
__all__ = [
"EnvironmentDeploymentTarget", "ReleaseAuthorizationError", "ReleaseConflict", "ReleaseError", "ReleaseLedger",
"ReleaseReconciliationRequired", "ReleaseVerificationError", "VerifiedReceipt",
]
@@ -0,0 +1,375 @@
"""Pure validation and deployment-truth evaluation for runtime contracts.
Runtime contracts describe stable structure. A release-owned deployment target
is supplied separately so that changing a release does not rewrite structural
configuration or turn a source branch into runtime evidence.
"""
from __future__ import annotations
from copy import deepcopy
from datetime import datetime, timezone
import re
from pathlib import PurePosixPath
from typing import Any, Mapping, Sequence
CONTRACT_SCHEMA = "aios.runtime-contract.v1"
LIFECYCLES = frozenset({"active", "candidate", "legacy", "example"})
_DYNAMIC_FIELDS = frozenset(
{"desired", "desired_commit", "desired_release", "desired_target", "desired_targets"}
)
_SECRET_FIELDS = frozenset(
{"secret", "secret_bytes", "token", "password", "private_key", "credential_value", "value"}
)
_DIGEST = re.compile(r"^(?:sha256:)?[0-9a-fA-F]{64}$")
_SAFE_SYMBOLIC_REF = re.compile(r"^[A-Za-z][A-Za-z0-9+.-]*:[/A-Za-z0-9_.:@+-]+$")
def _error(code: str, path: str = "") -> ValueError:
return ValueError(f"{code}:{path}" if path else code)
def _nonempty_string(value: object, path: str) -> str:
if not isinstance(value, str) or not value.strip():
raise _error("field_invalid", path)
return value.strip()
def _positive_int(value: object, path: str) -> int:
if isinstance(value, bool) or not isinstance(value, int) or value < 1:
raise _error("field_invalid", path)
return value
def _reject_dynamic_fields(value: object, path: str = "contract") -> None:
if isinstance(value, Mapping):
for key, child in value.items():
if not isinstance(key, str):
raise _error("field_name_invalid", path)
child_path = f"{path}.{key}"
if key.lower() in _DYNAMIC_FIELDS:
raise _error("dynamic_field_forbidden", child_path)
_reject_dynamic_fields(child, child_path)
elif isinstance(value, Sequence) and not isinstance(value, (str, bytes, bytearray)):
for index, child in enumerate(value):
_reject_dynamic_fields(child, f"{path}[{index}]")
def _safe_path_ref(value: object, path: str) -> str:
text = _nonempty_string(value, path)
if "\x00" in text or "\\" in text:
raise _error("unsafe_path_ref", path)
candidate = PurePosixPath(text)
if candidate.is_absolute() or any(part in {"", ".", ".."} for part in candidate.parts):
raise _error("unsafe_path_ref", path)
return text
def _symbolic_ref(value: object, path: str) -> str:
text = _nonempty_string(value, path)
if "\x00" in text or ".." in text or not _SAFE_SYMBOLIC_REF.fullmatch(text):
raise _error("resolver_ref_invalid", path)
return text
def _credential_ref(value: object, path: str) -> str:
text = _nonempty_string(value, path)
allowed = (
text.startswith("credential://")
or text.startswith("secret://")
or text.startswith("systemd-credential:")
)
if not allowed or "\x00" in text or ".." in text or text.startswith("/run/credentials/"):
raise _error("credential_ref_invalid", path)
return text
def _validate_binding_group(
group: object,
group_name: str,
component_id: str,
environment: str,
tenant_scope: str,
) -> dict[str, Any]:
if not isinstance(group, Mapping):
raise _error("binding_group_invalid", group_name)
normalized: dict[str, Any] = {}
for name, raw in group.items():
item_path = f"{group_name}.{name}"
_nonempty_string(name, f"{group_name}.name")
if isinstance(raw, str):
reference_field = (
"credential_ref"
if group_name == "credential_refs"
else "path_ref"
if group_name == "resource_refs" and ":" not in raw
else "resolver_ref"
)
item = {
"component_id": component_id,
"environment": environment,
reference_field: raw,
}
elif isinstance(raw, Mapping):
item = deepcopy(dict(raw))
else:
raise _error("binding_invalid", item_path)
if _nonempty_string(item.get("component_id"), f"{item_path}.component_id") != component_id:
raise _error("binding_component_mismatch", item_path)
if _nonempty_string(item.get("environment"), f"{item_path}.environment") != environment:
raise _error("binding_environment_mismatch", item_path)
if "tenant_scope" in item and (
_nonempty_string(item["tenant_scope"], f"{item_path}.tenant_scope") != tenant_scope
):
raise _error("binding_tenant_mismatch", item_path)
reference_fields = [key for key in ("resolver_ref", "path_ref", "credential_ref") if key in item]
if len(reference_fields) != 1:
raise _error("binding_reference_invalid", item_path)
reference_field = reference_fields[0]
reference_path = f"{item_path}.{reference_field}"
if group_name == "credential_refs" or reference_field == "credential_ref":
for key in item:
if key.lower() in _SECRET_FIELDS:
raise _error("secret_material_forbidden", f"{item_path}.{key}")
item[reference_field] = _credential_ref(item[reference_field], reference_path)
elif reference_field == "path_ref":
item[reference_field] = _safe_path_ref(item[reference_field], reference_path)
else:
item[reference_field] = _symbolic_ref(item[reference_field], reference_path)
normalized[str(name)] = item
return normalized
def validate_contract(document: Mapping[str, Any]) -> dict[str, Any]:
"""Validate and copy a stable ``aios.runtime-contract.v1`` document.
The returned mapping owns its nested containers, so callers cannot mutate the
source after validation. Invalid contracts raise ``ValueError`` with a stable
machine-readable code prefix.
"""
if not isinstance(document, Mapping):
raise _error("contract_invalid")
_reject_dynamic_fields(document)
result = deepcopy(dict(document))
if result.get("contract_schema") != CONTRACT_SCHEMA:
raise _error("contract_schema_invalid", "contract_schema")
result["contract_revision"] = _positive_int(result.get("contract_revision"), "contract_revision")
component_id = _nonempty_string(result.get("component_id"), "component_id")
environment = _nonempty_string(result.get("environment"), "environment")
result["component_id"] = component_id
result["environment"] = environment
tenant_scope = _nonempty_string(result.get("tenant_scope"), "tenant_scope")
result["tenant_scope"] = tenant_scope
lifecycle = _nonempty_string(result.get("lifecycle"), "lifecycle").lower()
if lifecycle not in LIFECYCLES:
raise _error("lifecycle_invalid", "lifecycle")
result["lifecycle"] = lifecycle
if "resources" in result and "resource_refs" in result:
raise _error("duplicate_resource_group")
if "resources" in result:
result["resource_refs"] = result.pop("resources")
for group_name in ("entrypoints", "bindings", "resource_refs", "credential_refs"):
result[group_name] = _validate_binding_group(
result.get(group_name), group_name, component_id, environment, tenant_scope
)
operations = result.get("operations")
if (
not isinstance(operations, Sequence)
or isinstance(operations, (str, bytes, bytearray))
or not operations
):
raise _error("operations_invalid", "operations")
normalized_operations = [
_nonempty_string(operation, f"operations[{index}]")
for index, operation in enumerate(operations)
]
if len(set(normalized_operations)) != len(normalized_operations):
raise _error("duplicate_operation", "operations")
result["operations"] = normalized_operations
return result
def _unknown(*reasons: str) -> dict[str, Any]:
return {
"status": "UNKNOWN",
"state": "UNKNOWN",
"verification": "UNKNOWN",
"converged": False,
"reasons": list(dict.fromkeys(reasons)),
}
def _drift(reasons: list[str]) -> dict[str, Any]:
return {
"status": "DRIFT",
"state": "DRIFT",
"verification": "FAILED",
"converged": False,
"reasons": list(dict.fromkeys(reasons)),
}
def _parse_time(value: object) -> datetime | None:
if isinstance(value, datetime):
parsed = value
elif isinstance(value, str):
try:
parsed = datetime.fromisoformat(value.strip().replace("Z", "+00:00"))
except ValueError:
return None
else:
return None
if parsed.tzinfo is None or parsed.utcoffset() is None:
return None
return parsed.astimezone(timezone.utc)
def _digest_value(document: Mapping[str, Any], *names: str) -> object:
for name in names:
if name in document:
return document[name]
return None
def _valid_digest(value: object) -> bool:
return isinstance(value, str) and bool(_DIGEST.fullmatch(value))
def evaluate_deployment(
contract: Mapping[str, Any],
target: Mapping[str, Any] | None,
observation: Mapping[str, Any] | None,
provenance: Mapping[str, Any] | None,
now: datetime | str,
) -> dict[str, Any]:
"""Evaluate one target using fresh runtime and provenance evidence.
Missing or malformed evidence is ``UNKNOWN``. ``DRIFT`` is reserved for a
complete evidence set that proves an incompatible target or runtime fact.
Repository HEAD is intentionally not an input to this decision.
"""
stable = validate_contract(contract)
missing: list[str] = []
if target is None:
missing.append("target_missing")
elif not isinstance(target, Mapping):
missing.append("target_invalid")
if observation is None:
missing.append("observation_missing")
elif not isinstance(observation, Mapping):
missing.append("observation_invalid")
if provenance is None:
missing.append("provenance_missing")
elif not isinstance(provenance, Mapping):
missing.append("provenance_invalid")
current_time = _parse_time(now)
if current_time is None:
missing.append("now_invalid")
if missing:
return _unknown(*missing)
assert target is not None and observation is not None and provenance is not None
assert current_time is not None
target_errors: list[str] = []
for field in ("component_id", "environment", "release_id", "deployment_id"):
if not isinstance(target.get(field), str) or not str(target[field]).strip():
target_errors.append(f"target_{field}_invalid")
if "tenant_scope" in target and (
not isinstance(target["tenant_scope"], str) or not target["tenant_scope"].strip()
):
target_errors.append("target_tenant_scope_invalid")
target_artifact = target.get("artifact_digest")
if not _valid_digest(target_artifact):
target_errors.append("target_artifact_digest_invalid")
if isinstance(target.get("revision"), bool) or not isinstance(target.get("revision"), int) or target.get("revision", 0) < 1:
target_errors.append("target_revision_invalid")
observed_at = _parse_time(observation.get("observed_at"))
max_age = observation.get("max_age_seconds", observation.get("max_age"))
observed_artifact = _digest_value(
observation, "actual_artifact_digest", "actual_artifact", "artifact_digest"
)
observation_errors: list[str] = []
if observed_at is None:
observation_errors.append("observation_observed_at_invalid")
if isinstance(max_age, bool) or not isinstance(max_age, (int, float)) or max_age <= 0:
observation_errors.append("observation_max_age_invalid")
if not isinstance(observation.get("source"), str) or not observation.get("source", "").strip():
observation_errors.append("observation_source_invalid")
if not _valid_digest(observed_artifact):
observation_errors.append("observation_actual_artifact_invalid")
if not isinstance(observation.get("healthy"), bool):
observation_errors.append("observation_healthy_invalid")
for field in ("component_id", "environment", "tenant_scope", "deployment_id"):
if field in observation and (
not isinstance(observation[field], str) or not observation[field].strip()
):
observation_errors.append(f"observation_{field}_invalid")
if "revision" in observation and (
isinstance(observation["revision"], bool)
or not isinstance(observation["revision"], int)
or observation["revision"] < 1
):
observation_errors.append("observation_revision_invalid")
artifact_provenance = _digest_value(provenance, "artifact_digest", "artifact_hash")
source_provenance = _digest_value(provenance, "source_digest", "source_hash")
builder_provenance = _digest_value(provenance, "builder_digest", "builder_hash")
provenance_errors: list[str] = []
if not _valid_digest(artifact_provenance):
provenance_errors.append("provenance_artifact_digest_invalid")
if not _valid_digest(source_provenance):
provenance_errors.append("provenance_source_digest_invalid")
if not _valid_digest(builder_provenance):
provenance_errors.append("provenance_builder_digest_invalid")
evidence_errors = target_errors + observation_errors + provenance_errors
if evidence_errors:
return _unknown(*evidence_errors)
assert observed_at is not None and isinstance(max_age, (int, float))
age = (current_time - observed_at).total_seconds()
if age < 0:
return _unknown("observation_from_future")
if age > max_age:
return _unknown("observation_stale")
drift: list[str] = []
if target["component_id"] != stable["component_id"]:
drift.append("target_component_id_mismatch")
if target["environment"] != stable["environment"]:
drift.append("target_environment_mismatch")
if "tenant_scope" in target and target["tenant_scope"] != stable["tenant_scope"]:
drift.append("target_tenant_scope_mismatch")
if "tenant_scope" in observation and observation["tenant_scope"] != stable["tenant_scope"]:
drift.append("observation_tenant_scope_mismatch")
for field in ("component_id", "environment", "deployment_id", "revision"):
if field in observation and observation[field] != target[field]:
drift.append(f"observation_{field}_mismatch")
if observed_artifact != target_artifact:
drift.append("artifact_mismatch")
if artifact_provenance != target_artifact:
drift.append("provenance_artifact_mismatch")
if observation["healthy"] is False:
drift.append("observation_unhealthy")
if drift:
return _drift(drift)
return {
"status": "VERIFIED",
"state": "CONVERGED",
"verification": "VERIFIED",
"converged": True,
"reasons": [],
"component_id": target["component_id"],
"environment": target["environment"],
"release_id": target["release_id"],
"deployment_id": target["deployment_id"],
"artifact_digest": target_artifact,
"revision": target["revision"],
"observed_at": observation["observed_at"],
"observation_source": observation["source"],
}
+479
View File
@@ -0,0 +1,479 @@
"""Thin authenticated application service over existing TaskPlaneStore and JobStore.
This module owns no second Task lifecycle store. Added task runtime columns live
in the supplied Task authority database; the lineage ledger owns only relations.
The built-in host adapter is READ_ONLY. Mutating operations require separately
registered governed adapters with receiver fencing/readback, never arbitrary argv.
"""
from __future__ import annotations
from dataclasses import dataclass, replace
from contextlib import contextmanager
from datetime import datetime,timezone
import base64
import json
import re
import sqlite3
import threading
import time
from pathlib import Path
from typing import Mapping
from task_plane.models import TaskPlan,WorkerRegistration,ProbeEvidence,ProbeOutcome,Budget,EvidenceEnvelope
from task_plane.errors import TaskPlaneHTTPError
from aios_gitops_job_controller import GitOpsJobController,JobStore,JobState,JobError
from .evidence import ReceiptSigner,canonical,digest
from .runtime_contract import validate_contract
from .worker_policy import eligible
from .authorization import AuthorizationError
from .host import HostError
class ServiceError(RuntimeError):
def __init__(self,code,status=409):
super().__init__(code)
self.code=code
self.status=status
@dataclass(frozen=True)
class Identity:
tenant_id: str
subject_id: str
role: str
worker_id: str | None=None
class BaselineService:
def __init__(self,store,job_root,authorization,contracts,operations,worker_profiles,host,lineage,machine_key,
*,machine_key_id='baseline-machine',trusted_receipt_keys=None):
self.store=store
self.signer=ReceiptSigner(machine_key,key_id=machine_key_id,trusted_public_keys=trusted_receipt_keys)
self.jobs=GitOpsJobController(JobStore(Path(job_root)),generic_receipt_verifier=self.signer.verify)
self.authorization=authorization
self.contracts={}
for entry in contracts:
value=validate_contract(entry)
key=(value['tenant_scope'],value['environment'],value['component_id'])
if key in self.contracts: raise ServiceError('CONTRACT_DUPLICATE')
self.contracts[key]=value
self.operations=operations
self.worker_profiles=dict(worker_profiles)
self.host=host
self.lineage=lineage
self.storage_blocked=False
self._execute_lock=threading.Lock()
with self.store._transaction():
self.store.conn.execute('''CREATE TABLE IF NOT EXISTS baseline_task_runtime(
task_id TEXT PRIMARY KEY REFERENCES tasks(task_id),ready_at REAL,
blocker_code TEXT,job_id TEXT,effect_id TEXT NOT NULL UNIQUE)''')
self.store.conn.execute('''CREATE TABLE IF NOT EXISTS baseline_worker_policy(
worker_id TEXT PRIMARY KEY,policy_json TEXT NOT NULL)''')
self.store.probe_attestation_verifier=self._verify_probe
self.store.evidence_attestation_verifier=self._verify_task_evidence
def _user(self,identity):
if not isinstance(identity,Identity) or identity.role not in {'user','owner'}:
raise ServiceError('USER_IDENTITY_REQUIRED',403)
def _worker(self,identity):
if not isinstance(identity,Identity) or identity.role!='worker':
raise ServiceError('WORKER_IDENTITY_REQUIRED',403)
profile=self.worker_profiles.get(identity.worker_id)
if not profile or profile.identity!=identity.subject_id or identity.tenant_id not in profile.tenant_scope:
raise ServiceError('WORKER_SCOPE_DENIED',403)
return profile
def _task(self,identity,task_id):
task=self.store.get_task(task_id)
meta=task.task_plan.metadata.get('baseline',{})
if meta.get('tenant_id')!=identity.tenant_id or meta.get('subject_id')!=identity.subject_id:
raise ServiceError('TASK_SCOPE_DENIED',403)
return task
def _scope(self,task):
meta=task.task_plan.metadata['baseline']
return {**{key:meta[key] for key in ('tenant_id','subject_id','mission_id','operation','component_id','environment')},'task_id':task.task_id}
def _principal(self,task):
meta=task.task_plan.metadata['baseline']
return self.authorization.issue_principal(meta['grant_id'],ttl_seconds=60,**self._scope(task))
def _fresh_write(self):
if self.storage_blocked: raise ServiceError('STORAGE_RECOVERY_REQUIRED',503)
try:
with self.store._transaction():
self.store.conn.execute('UPDATE baseline_task_runtime SET blocker_code=blocker_code WHERE 0')
except (sqlite3.Error,OSError) as exc:
self.storage_blocked=True
raise ServiceError('TASK_STORAGE_UNAVAILABLE',503) from exc
def submit_goal(self,identity,request,*,idempotency_key):
self._user(identity)
allowed={'mission_id','grant_id','operation','component_id','environment','inputs','intent'}
if type(request) is not dict or set(request)!=allowed or not isinstance(idempotency_key,str) or not 1<=len(idempotency_key)<=128:
raise ServiceError('GOAL_SCHEMA_INVALID',400)
if any(not isinstance(request[key],str) or not request[key] or len(request[key])>2048 for key in allowed-{'inputs'}):
raise ServiceError('GOAL_SCHEMA_INVALID',400)
if request['inputs']!={}: raise ServiceError('OPERATION_INPUT_SCHEMA_INVALID',400)
if not re.fullmatch(r'[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}',request['mission_id']): raise ServiceError('MISSION_ID_INVALID',400)
operation=self.operations.get(request['operation'])
if operation.handler_ref!='host.service_status' or operation.effect_class!='READ_ONLY' or operation.required_permissions!=(operation.operation_id,):
raise ServiceError('REGISTERED_HANDLER_UNAVAILABLE',503)
contract=self.contracts.get((identity.tenant_id,request['environment'],request['component_id']))
if not contract or contract['lifecycle']!='active' or request['operation'] not in contract['operations']:
raise ServiceError('CONTRACT_OPERATION_UNAVAILABLE',403)
host_key=identity.tenant_id+':'+request['environment']+':'+request['component_id']
if host_key not in self.host.bindings: raise ServiceError('HOST_BINDING_UNAVAILABLE',503)
key=digest({'tenant':identity.tenant_id,'subject':identity.subject_id,'key':idempotency_key})
task_id='task-'+key[:40]
effect_id='effect-'+digest({'tenant':identity.tenant_id,'task':task_id,'request':request})[:40]
meta={**request,'tenant_id':identity.tenant_id,'subject_id':identity.subject_id,'effect_id':effect_id,
'operation_version':operation.version,'contract_revision':contract['contract_revision']}
plan=TaskPlan.from_dict({'task_id':task_id,'task_type':'host.diagnostic','intent':request['intent'],
'issuer':{'id':identity.subject_id,'tenant_id':identity.tenant_id,'subject_id':identity.subject_id},
'required_capabilities':[request['operation']],'risk':'low','approval_policy':'governed-grant',
'correlation_id':request['mission_id'],'metadata':{'baseline':meta},
'budgets':{'attempts':operation.retry['max_attempts'],'time_seconds':30*operation.retry['max_attempts']},'evidence_requirements':['diagnostic']})
# Validate business authorization before creating an executable task.
self.authorization.issue_principal(request['grant_id'],ttl_seconds=60,tenant_id=identity.tenant_id,
subject_id=identity.subject_id,task_id=task_id,**{key:request[key] for key in ('mission_id','operation','component_id','environment')})
self._fresh_write()
try:
with self.store._transaction():
task=self.store.create_task_with_event(plan,idempotency_key=key)
self.store.conn.execute('INSERT OR IGNORE INTO baseline_task_runtime(task_id,ready_at,effect_id) VALUES(?,?,?)',
(task_id,time.time(),effect_id))
self.lineage.link_task(identity.tenant_id,identity.subject_id,request['mission_id'],request['grant_id'],task_id)
return self.get_task(identity,task_id)
except TaskPlaneHTTPError as exc:
raise ServiceError('TASK_CONFLICT') from exc
except (sqlite3.Error,OSError) as exc:
self.storage_blocked=True
raise ServiceError('TASK_STORAGE_UNAVAILABLE',503) from exc
def get_task(self,identity,task_id):
self._user(identity)
task=self._task(identity,task_id)
with self.store._transaction():
row=self.store.conn.execute('SELECT * FROM baseline_task_runtime WHERE task_id=?',(task_id,)).fetchone()
result=task.to_dict()
if row:
result.update({'ready_at':row['ready_at'],'blocker_code':row['blocker_code'],'effect_id':row['effect_id'],'job_id':row['job_id']})
if row['job_id']:
job=self.jobs.store.read(row['job_id'])
result['job_state']=job['state']
result['side_effect_state']=job['side_effect_state']
envelope=json.loads(job['receipt_json']) if job.get('receipt_json') else None
if self._receipt_valid(task,envelope): result['receipt']=envelope['payload']
result['remaining_budget']=self.store.get_task_budget(task_id).to_dict()
return result
def explain(self,identity,mission_id):
self._user(identity)
result=self.lineage.explain(identity.tenant_id,identity.subject_id,mission_id)
result['tasks_runtime']=[self.get_task(identity,t.task_id) for t in self.store.list_tasks()
if t.task_plan.metadata.get('baseline',{}).get('mission_id')==mission_id and
t.task_plan.issuer.get('tenant_id')==identity.tenant_id and t.task_plan.issuer.get('subject_id')==identity.subject_id]
return result
def register_worker(self,identity):
profile=self._worker(identity)
try:
existing=self.store.get_worker(profile.worker_id)
except TaskPlaneHTTPError:
existing=None
now=time.time()
worker=WorkerRegistration.from_dict({'worker_id':profile.worker_id,'worker_type':'baseline-operation-worker',
'identity':profile.identity,'capabilities':list(profile.capabilities),'trust_zone':'governed-host',
'max_concurrency':profile.max_concurrency,'heartbeat_interval_seconds':profile.heartbeat_seconds,
'supported_protocols':['aios-baseline-worker-v1'],'last_heartbeat':now})
self.store.register_worker(worker,actor_identity=profile.identity,is_admin=False,
expected_revision=existing.registration_revision if existing else None)
with self.store._transaction():
self.store.conn.execute('INSERT INTO baseline_worker_policy VALUES(?,?) ON CONFLICT(worker_id) DO UPDATE SET policy_json=excluded.policy_json',
(profile.worker_id,canonical(profile.__dict__).decode()))
return {'worker_id':profile.worker_id,'registered':True,'fallback_executor':profile.fallback_executor}
def heartbeat(self,identity):
profile=self._worker(identity)
worker=self.store.get_worker(profile.worker_id)
self.store.heartbeat_worker(profile.worker_id,actor_identity=profile.identity,expected_revision=worker.registration_revision)
return {'worker_id':profile.worker_id,'alive':True}
def claim_next(self,identity):
profile=self._worker(identity)
worker=self.store.get_worker(profile.worker_id)
if worker.disabled: raise ServiceError('WORKER_DISABLED',403)
self._fresh_write()
for task in sorted(self.store.list_tasks(),key=lambda t:(t.created_at,t.task_id)):
meta=task.task_plan.metadata.get('baseline',{})
if not meta or task.status not in {'CREATED','QUEUED','FAILED_RECOVERABLE'}: continue
with self.store._transaction():
row=self.store.conn.execute('SELECT ready_at FROM baseline_task_runtime WHERE task_id=?',(task.task_id,)).fetchone()
if not row: continue
now=time.time()
task_scope={**meta,'ready_at':row['ready_at']}
if not eligible(profile,task_scope,now=now,last_heartbeat=worker.last_heartbeat): continue
# Priority is configuration on ordinary eligible workers; no special main actor.
candidates=[]
for registered in self.store.list_workers():
peer=self.worker_profiles.get(registered.worker_id)
if not peer or registered.disabled or not eligible(peer,task_scope,now=now,last_heartbeat=registered.last_heartbeat): continue
with self.store._transaction():
active=self.store.conn.execute("SELECT count(*) FROM claims WHERE worker_id=? AND status='ACTIVE' AND lease_until>?",(peer.worker_id,now)).fetchone()[0]
if active<peer.max_concurrency: candidates.append(peer)
if not candidates or sorted(candidates,key=lambda p:(-p.priority,p.worker_id))[0].worker_id!=profile.worker_id: continue
try:
self._principal(task)
with self.store._transaction():
self.store.consume_budget(task.task_id,Budget(attempts=1,time_seconds=30))
claim=self.store.claim_task(task.task_id,profile.worker_id,profile.identity,lease_seconds=30,
expected_revision=task.revision,operation_digest=task.task_plan.operation_digest())
except TaskPlaneHTTPError:
budget=self.store.get_task_budget(task.task_id)
if budget.attempts==0 or budget.time_seconds==0:
with self.store._transaction():
self.store.conn.execute('UPDATE baseline_task_runtime SET blocker_code=? WHERE task_id=?',('TASK_BUDGET_EXHAUSTED',task.task_id))
continue
except AuthorizationError:
with self.store._transaction():
self.store.conn.execute('UPDATE baseline_task_runtime SET blocker_code=? WHERE task_id=?',('AUTHORIZATION_REQUIRED',task.task_id))
continue
return {'task_id':task.task_id,'claim_id':claim.claim_id,'generation':claim.attempt,
'lease_until':claim.lease_until,'operation':meta['operation']}
return None
def renew(self,identity,task_id,claim_id):
profile=self._worker(identity)
claim=self.store.get_claim(claim_id)
if claim.task_id!=task_id or claim.worker_id!=profile.worker_id or claim.worker_identity!=profile.identity:
raise ServiceError('CLAIM_IDENTITY_MISMATCH',403)
task=self.store.get_task(task_id)
meta=task.task_plan.metadata.get('baseline',{})
if meta.get('tenant_id') not in profile.tenant_scope or meta.get('subject_id') not in profile.subject_scope or task.status=='CANCELLED':
raise ServiceError('CLAIM_SCOPE_OR_STATE_DENIED',403)
self._principal(task)
self.store.heartbeat_claim(task_id,claim_id,30)
return {'task_id':task_id,'claim_id':claim_id,'renewed':True}
def _advance(self,job_id,state,**kwargs):
row=self.jobs.store.read(job_id)
return self.jobs.advance(job_id,state,expected_revision=int(row['revision']),**kwargs)
@contextmanager
def _execution_slot(self,deadline):
remaining=deadline-time.time()
if remaining<=0 or not self._execute_lock.acquire(timeout=remaining):
raise ServiceError('EXECUTION_DEADLINE_EXPIRED',408)
try:
if time.time()>=deadline: raise ServiceError('EXECUTION_DEADLINE_EXPIRED',408)
yield
finally: self._execute_lock.release()
def execute_claim(self,identity,task_id,claim_id,*,deadline=None):
profile=self._worker(identity)
deadline=time.time()+30 if deadline is None else deadline
with self._execution_slot(deadline):
self._fresh_write()
task=self.store.get_task(task_id)
meta=task.task_plan.metadata.get('baseline',{})
claim=self.store.get_claim(claim_id)
if claim.task_id!=task_id or claim.worker_id!=profile.worker_id or claim.worker_identity!=profile.identity:
raise ServiceError('CLAIM_IDENTITY_MISMATCH',403)
if meta.get('tenant_id') not in profile.tenant_scope or meta.get('subject_id') not in profile.subject_scope:
raise ServiceError('WORKER_SCOPE_DENIED',403)
user=Identity(meta['tenant_id'],meta['subject_id'],'user')
if task.status=='COMPLETED': return self.get_task(user,task_id)
if claim.status!='ACTIVE' or claim.lease_until<=time.time() or task.status=='CANCELLED':
raise ServiceError('CLAIM_EXPIRED_OR_CANCELLED')
principal=self._principal(task)
self.authorization.authorize(principal,**self._scope(task))
operation=self.operations.get(meta['operation'])
if operation.handler_ref!='host.service_status' or operation.effect_class!='READ_ONLY':
raise ServiceError('HANDLER_NOT_REGISTERED',503)
self.authorization.assert_effect_bounds(principal,operation,**self._scope(task))
request={'schema':'aios.operation-job.v1','tenant_id':meta['tenant_id'],'subject_id':meta['subject_id'],
'mission_id':meta['mission_id'],'grant_id':meta['grant_id'],'task_id':task_id,'effect_id':meta['effect_id'],
'operation':meta['operation'],'operation_version':str(operation.version),'resource_kind':'systemd-service',
'resource_id':digest({'tenant':meta['tenant_id'],'environment':meta['environment'],'component':meta['component_id']}),'input_digest':digest(meta['inputs'])}
try:
job=self.jobs.submit(request)
with self.store._transaction():
self.store.conn.execute('UPDATE baseline_task_runtime SET job_id=? WHERE task_id=?',(job.job_id,task_id))
self.lineage.link_task(meta['tenant_id'],meta['subject_id'],meta['mission_id'],meta['grant_id'],task_id)
self.lineage.link_effect(meta['tenant_id'],meta['subject_id'],meta['mission_id'],task_id,meta['effect_id'],job.job_id)
row=self.jobs.store.read(job.job_id)
if row['state']=='COMPLETED':
self._close_task(task,claim,json.loads(row['receipt_json']))
return self.get_task(user,task_id)
if row['state'] not in {'REQUESTED','AUTHORIZED'}:
raise ServiceError('JOB_RECONCILIATION_REQUIRED')
self.jobs.assert_effect_admission(exclude_job_id=job.job_id)
self.authorization.reserve_effect(meta['grant_id'],meta['effect_id'],principal,**self._scope(task))
if row['state']=='REQUESTED': self._advance(job.job_id,JobState.AUTHORIZED,side_effect_state='AUTHORIZED')
row=self.jobs.store.read(job.job_id)
self.jobs.lease_resource_job(job.job_id,lease_id=claim_id,resource_kind=request['resource_kind'],
resource_id=request['resource_id'],generation=None,
lease_expires_at=datetime.fromtimestamp(min(claim.lease_until,time.time()+30,deadline),timezone.utc).isoformat(),
expected_revision=int(row['revision']))
effect_lease=self.jobs.get_execution_lease(job.job_id)
fence_generation=int(effect_lease['generation'])
self._advance(job.job_id,JobState.EXECUTING,side_effect_state='IN_PROGRESS')
# Last authorization/claim check directly before the registered host boundary.
self.authorization.authorize(principal,**self._scope(task))
if self.store.get_task(task_id).status=='CANCELLED' or self.store.get_claim(claim_id).lease_until<=time.time():
raise ServiceError('CLAIM_EXPIRED_OR_CANCELLED')
self.jobs.assert_execution_lease(job.job_id,generation=fence_generation,lease_id=claim_id)
if time.time()>=deadline: raise ServiceError('EXECUTION_DEADLINE_EXPIRED',408)
observation=self.host.status(meta['component_id'],meta['environment'],tenant_id=meta['tenant_id'],deadline=deadline)
if time.time()>=deadline or self.store.get_claim(claim_id).lease_until<=time.time():
raise ServiceError('EXECUTION_DEADLINE_EXPIRED',408)
envelope=self.signer.sign({'task_id':task_id,'job_id':job.job_id,'effect_id':meta['effect_id'],
'tenant_id':meta['tenant_id'],'subject_id':meta['subject_id'],'mission_id':meta['mission_id'],
'grant_id':meta['grant_id'],'claim_id':claim_id,'generation':fence_generation,
'operation':meta['operation'],'operation_version':str(operation.version),
'input_digest':request['input_digest'],'resource_kind':request['resource_kind'],
'resource_id':request['resource_id'],'observation':observation,'verified':True})
self._advance(job.job_id,JobState.LOCAL_VERIFIED,side_effect_state='READBACK_VERIFIED',receipt=envelope)
self._advance(job.job_id,JobState.COMPLETED,side_effect_state='READBACK_VERIFIED',receipt=envelope)
self._close_task(task,claim,envelope)
return self.get_task(user,task_id)
except (sqlite3.Error,OSError) as exc:
self.storage_blocked=True
raise ServiceError('STORAGE_RECOVERY_REQUIRED',503) from exc
except HostError as exc:
try:
self._advance(job.job_id,JobState.RECONCILIATION_REQUIRED,side_effect_state='UNKNOWN')
with self.store._transaction():
self.store.conn.execute('UPDATE baseline_task_runtime SET blocker_code=? WHERE task_id=?',(str(exc),task_id))
except (sqlite3.Error,OSError,JobError): self.storage_blocked=True
raise ServiceError(str(exc),503) from exc
except JobError as exc:
if 'UNAVAILABLE' in str(exc) or 'STORAGE' in str(exc): self.storage_blocked=True
raise ServiceError(str(exc),503 if self.storage_blocked else 409) from exc
def _receipt_valid(self,task,envelope):
meta=task.task_plan.metadata['baseline']
if not self.signer.verify(envelope): return False
payload=envelope['payload']
with self.store._transaction():
runtime=self.store.conn.execute('SELECT job_id FROM baseline_task_runtime WHERE task_id=?',(task.task_id,)).fetchone()
if not runtime or not runtime['job_id']: return False
expected={**{k:meta[k] for k in ('effect_id','tenant_id','subject_id','mission_id','grant_id','operation')},
'task_id':task.task_id,'job_id':runtime['job_id'],'operation_version':str(meta['operation_version']),
'input_digest':digest(meta['inputs']),'resource_kind':'systemd-service',
'resource_id':digest({'tenant':meta['tenant_id'],'environment':meta['environment'],'component':meta['component_id']})}
if any(payload.get(k)!=v for k,v in expected.items()) or payload.get('verified') is not True: return False
try:
lease=self.jobs.get_execution_lease(runtime['job_id'])
source_claim=self.store.get_claim(payload.get('claim_id'))
if payload.get('claim_id')!=lease['lease_id'] or payload.get('generation')!=lease['generation']: return False
if source_claim.task_id!=task.task_id or source_claim.operation_digest!=task.task_plan.operation_digest(): return False
except (JobError,TaskPlaneHTTPError,TypeError): return False
observation=payload.get('observation')
if type(observation) is not dict: return False
for k in ('operation','tenant_id','component_id','environment'):
if observation.get(k)!=meta[k]: return False
binding=meta['tenant_id']+':'+meta['environment']+':'+meta['component_id']
if observation.get('unit')!=self.host.bindings.get(binding) or observation.get('source')!='systemd.show': return False
return True
def _close_task(self,task,claim,envelope):
if not self._receipt_valid(task,envelope): raise ServiceError('RESULT_ATTESTATION_INVALID',403)
with self.store._transaction():
current=self.store.get_task(task.task_id)
if current.status=='CANCELLED': raise ServiceError('TASK_CANCELLED')
if current.status=='CLAIMED': self.store.update_task_status(task.task_id,current.revision,'RUNNING')
evidence=EvidenceEnvelope.create(task_id=task.task_id,claim_id=claim.claim_id,
kind='diagnostic',worker_identity=claim.worker_identity,payload=envelope,
previous_evidence_hash=self.store._last_evidence_hash(task.task_id),
attestation_ref='baseline-machine:'+self.signer.key_id)
proof=canonical({'domain':'aios.baseline.task-evidence.v1',
'key_id':self.signer.key_id,'evidence':evidence.digest_payload()})
evidence=replace(evidence,signature=base64.b64encode(self.signer.private_key.sign(proof)).decode())
self.store.complete_claim_with_evidence_and_event(task.task_id,claim.claim_id,
'COMPLETED',envelope,[evidence])
def _verify_task_evidence(self,evidence):
"""Verify the TaskPlane wrapper and its separately signed operation receipt."""
try:
if not isinstance(evidence,EvidenceEnvelope) or evidence.kind!='diagnostic': return False
prefix='baseline-machine:'
if not evidence.attestation_ref or not evidence.attestation_ref.startswith(prefix): return False
key_id=evidence.attestation_ref[len(prefix):]
key=self.signer.trusted_public_keys.get(key_id)
if key is None or not evidence.signature: return False
proof=canonical({'domain':'aios.baseline.task-evidence.v1',
'key_id':key_id,'evidence':evidence.digest_payload()})
key.verify(base64.b64decode(evidence.signature,validate=True),proof)
claim=self.store.get_claim(evidence.claim_id)
task=self.store.get_task(evidence.task_id)
if claim.task_id!=task.task_id or claim.worker_identity!=evidence.worker_identity: return False
if claim.operation_digest!=task.task_plan.operation_digest(): return False
return self._receipt_valid(task,evidence.payload)
except Exception:
return False
def cancel_task(self,identity,task_id):
self._user(identity)
task=self._task(identity,task_id)
self.store.cancel_task_with_event(task_id,task.revision,'cancelled by authenticated subject')
return self.get_task(identity,task_id)
def _verify_probe(self,probe):
try:
self.signer.public_key.verify(base64.b64decode(probe.signature,validate=True),canonical(probe.digest_payload()))
return True
except Exception:
return False
def recover(self):
"""Reconcile expired baseline claims from durable signed receipts; never fake a result."""
with self._execute_lock:
return self._recover_locked()
def _recover_locked(self):
recovered=[]
with self.store._transaction():
rows=self.store.conn.execute("SELECT claim_id FROM claims WHERE status='ACTIVE' AND lease_until<?",(time.time(),)).fetchall()
for row in rows:
claim=self.store.get_claim(row['claim_id'])
task=self.store.get_task(claim.task_id)
meta=task.task_plan.metadata.get('baseline')
if not meta: continue
with self.store._transaction():
rt=self.store.conn.execute('SELECT job_id FROM baseline_task_runtime WHERE task_id=?',(task.task_id,)).fetchone()
outcome=ProbeOutcome.NOT_APPLIED
ref='read-only-no-persistent-effect'
if rt and rt['job_id']:
job=self.jobs.store.read(rt['job_id'])
env=json.loads(job['receipt_json']) if job.get('receipt_json') else None
if job['state']=='COMPLETED':
if self._receipt_valid(task,env):
outcome=ProbeOutcome.APPLIED_VERIFIED
ref=digest(env)
else: outcome=ProbeOutcome.INDETERMINATE
elif self.operations.get(meta['operation']).effect_class!='READ_ONLY':
outcome=ProbeOutcome.INDETERMINATE
now=time.time()
probe=ProbeEvidence.create(task_id=task.task_id,claim_id=claim.claim_id,worker_id=claim.worker_id,
worker_identity=claim.worker_identity,operation_digest=claim.operation_digest,outcome=outcome,
observed_at=now,expires_at=now+30,probe_executor_identity='baseline-reconciler',attestation_ref=ref)
from dataclasses import replace
probe=replace(probe,signature=base64.b64encode(self.signer.private_key.sign(canonical(probe.digest_payload()))).decode())
self.store.recover_claim_with_probe_and_event(task.task_id,claim.claim_id,probe)
if outcome==ProbeOutcome.NOT_APPLIED and rt and rt['job_id']:
job=self.jobs.store.read(rt['job_id'])
if job['state']!='COMPLETED':
self._advance(rt['job_id'],JobState.RECONCILIATION_REQUIRED,side_effect_state='UNKNOWN')
self.jobs.reconcile(rt['job_id'],lambda _:{'state':'REQUESTED','side_effect_state':'READ_ONLY_SAFE_TO_RETRY'})
recovered.append({'task_id':task.task_id,'outcome':outcome.value})
# A successful writable transaction permits recovery processing, not blind effect replay.
with self.store._transaction():
self.store.conn.execute('UPDATE baseline_task_runtime SET blocker_code=blocker_code WHERE 0')
try:
self.jobs.assert_effect_admission()
self.storage_blocked=False
except JobError:
self.storage_blocked=True
return recovered
@@ -0,0 +1,513 @@
"""Authenticated loopback HTTP transport for the Baseline 1.1 service."""
from __future__ import annotations
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
import ipaddress
import json
import math
import re
import time
import threading
from typing import Any, Mapping
from urllib.error import HTTPError, URLError
from urllib.parse import quote, unquote, urlsplit
from urllib.request import HTTPRedirectHandler, ProxyHandler, Request, build_opener
from .service import Identity, ServiceError
MAX_REQUEST_BYTES = 65_536
MAX_RESPONSE_BYTES = 1_048_576
MAX_DEADLINE_SECONDS = 30
_IDENTIFIER = re.compile(r"[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}")
_IDENTITY_FIELDS = frozenset({"tenant_id", "subject_id", "role", "worker_id", "identity"})
class TransportError(RuntimeError):
def __init__(self, code: str, status: int | None = None):
self.code = code
self.status = status
super().__init__(code)
class _RequestError(RuntimeError):
def __init__(self, code: str, status: int):
self.code = code
self.status = status
super().__init__(code)
def _safe_identifier(value: Any, code: str = "HTTP_PATH_INVALID") -> str:
if not isinstance(value, str) or _IDENTIFIER.fullmatch(value) is None:
raise _RequestError(code, 400)
return value
def _has_identity_field(value: Any) -> bool:
if isinstance(value, Mapping):
if any(key in _IDENTITY_FIELDS for key in value):
return True
return any(_has_identity_field(item) for item in value.values())
if isinstance(value, list):
return any(_has_identity_field(item) for item in value)
return False
def _loopback_host(host: str, code: str) -> str:
if not isinstance(host, str):
raise TransportError(code)
try:
address = ipaddress.ip_address(host)
except ValueError as exc:
raise TransportError(code) from exc
if not address.is_loopback or address.version != 4:
raise TransportError(code)
return str(address)
class _BaselineHTTPServer(ThreadingHTTPServer):
daemon_threads = True
allow_reuse_address = True
def __init__(self, address, handler, service, identities):
self.baseline_service = service
self.trusted_identities = identities
self.request_slots=threading.BoundedSemaphore(16)
super().__init__(address, handler)
def get_request(self):
connection, address = super().get_request()
connection.settimeout(MAX_DEADLINE_SECONDS)
return connection, address
def process_request(self,request,client_address):
if not self.request_slots.acquire(blocking=False):
try:
body=b'{"error":{"code":"HTTP_CAPACITY_EXCEEDED"}}'
request.sendall(b'HTTP/1.1 503 Service Unavailable\r\nContent-Type: application/json\r\nConnection: close\r\nContent-Length: '+str(len(body)).encode()+b'\r\n\r\n'+body)
except OSError: pass
self.shutdown_request(request)
return
try: super().process_request(request,client_address)
except Exception:
self.request_slots.release()
raise
def process_request_thread(self,request,client_address):
try: super().process_request_thread(request,client_address)
finally: self.request_slots.release()
class _Handler(BaseHTTPRequestHandler):
protocol_version = "HTTP/1.1"
server_version = "AIOSBaselineHTTP/1"
sys_version = ""
def log_message(self, _format, *_args):
# Request paths can contain tenant-owned identifiers; do not emit them or
# bearer-related failure details through the default stderr logger.
return
def send_error(self, code, message=None, explain=None):
# BaseHTTPRequestHandler otherwise emits an HTML page which may include
# parser details. Keep pre-dispatch protocol failures in the same closed
# JSON error format.
status = code if isinstance(code, int) and 400 <= code <= 599 else 400
self._send(status, {"error": {"code": "HTTP_PROTOCOL_ERROR"}})
def _send(self, status: int, value: Any) -> None:
try:
body = json.dumps(
value, sort_keys=True, separators=(",", ":"), ensure_ascii=False,
allow_nan=False,
).encode("utf-8")
except (TypeError, ValueError):
status = 500
body = b'{"error":{"code":"HTTP_RESPONSE_INVALID"}}'
if len(body) > MAX_RESPONSE_BYTES:
status = 500
body = b'{"error":{"code":"HTTP_RESPONSE_TOO_LARGE"}}'
self.send_response(status)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.send_header("Cache-Control", "no-store")
self.send_header("X-Content-Type-Options", "nosniff")
self.send_header("Connection", "close")
self.end_headers()
try:
self.wfile.write(body)
except OSError:
# A peer can disconnect or let its deadline expire while a response
# is being written. Do not let the server print a handler traceback.
pass
self.close_connection = True
def _error(self, error: _RequestError) -> None:
self._send(error.status, {"error": {"code": error.code}})
def _deadline(self) -> float:
values = self.headers.get_all("X-AIOS-Deadline", [])
if len(values) != 1:
raise _RequestError("HTTP_DEADLINE_REQUIRED", 400)
try:
deadline = float(values[0])
except (TypeError, ValueError) as exc:
raise _RequestError("HTTP_DEADLINE_INVALID", 400) from exc
now = time.time()
if not math.isfinite(deadline) or deadline > now + MAX_DEADLINE_SECONDS:
raise _RequestError("HTTP_DEADLINE_INVALID", 400)
if deadline <= now:
raise _RequestError("HTTP_DEADLINE_EXPIRED", 408)
self.connection.settimeout(deadline - now)
return deadline
def _identity(self) -> Identity:
values = self.headers.get_all("Authorization", [])
if len(values) != 1 or not values[0].startswith("Bearer "):
raise _RequestError("HTTP_UNAUTHORIZED", 401)
token = values[0][7:]
if not token or any(ord(char) < 33 or ord(char) > 126 for char in token):
raise _RequestError("HTTP_UNAUTHORIZED", 401)
identity = self.server.trusted_identities.get(token)
if identity is None:
raise _RequestError("HTTP_UNAUTHORIZED", 401)
return identity
def _json_body(self) -> dict:
if self.headers.get("Transfer-Encoding") is not None:
raise _RequestError("HTTP_TRANSFER_ENCODING_FORBIDDEN", 400)
content_types = self.headers.get_all("Content-Type", [])
if content_types != ["application/json"]:
raise _RequestError("HTTP_CONTENT_TYPE_INVALID", 415)
lengths = self.headers.get_all("Content-Length", [])
if len(lengths) != 1 or not lengths[0].isdigit():
raise _RequestError("HTTP_CONTENT_LENGTH_INVALID", 411)
length = int(lengths[0])
if length > MAX_REQUEST_BYTES:
raise _RequestError("HTTP_BODY_TOO_LARGE", 413)
if length <= 0:
raise _RequestError("HTTP_BODY_REQUIRED", 400)
raw = self.rfile.read(length)
if len(raw) != length:
raise _RequestError("HTTP_BODY_TRUNCATED", 400)
try:
def unique_object(pairs):
result = {}
for key, item in pairs:
if key in result:
raise _RequestError("HTTP_JSON_DUPLICATE_KEY", 400)
result[key] = item
return result
def reject_constant(_value):
raise _RequestError("HTTP_JSON_NON_FINITE", 400)
value = json.loads(
raw.decode("utf-8"), object_pairs_hook=unique_object,
parse_constant=reject_constant,
)
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
raise _RequestError("HTTP_JSON_INVALID", 400) from exc
if type(value) is not dict:
raise _RequestError("HTTP_JSON_OBJECT_REQUIRED", 400)
if _has_identity_field(value):
raise _RequestError("HTTP_IDENTITY_FIELD_FORBIDDEN", 400)
return value
def _path(self) -> list[str]:
parsed = urlsplit(self.path)
if parsed.query or parsed.fragment or not parsed.path.startswith("/"):
raise _RequestError("HTTP_PATH_INVALID", 400)
raw_segments = parsed.path.split("/")[1:]
if any(not segment for segment in raw_segments):
raise _RequestError("HTTP_NOT_FOUND", 404)
segments = [unquote(segment) for segment in raw_segments]
if any("/" in segment or "\\" in segment for segment in segments):
raise _RequestError("HTTP_PATH_INVALID", 400)
return segments
@staticmethod
def _exact(body: Mapping[str, Any], fields: set[str]) -> None:
if set(body) != fields:
raise _RequestError("HTTP_BODY_SCHEMA_INVALID", 400)
def _dispatch(self, method: str, identity: Identity, segments: list[str], body: dict | None):
service = self.server.baseline_service
if method == "POST" and segments == ["v1", "goals", "submit"]:
self._exact(body, {"request", "idempotency_key"})
if type(body["request"]) is not dict:
raise _RequestError("HTTP_BODY_SCHEMA_INVALID", 400)
return 201, service.submit_goal(
identity, body["request"], idempotency_key=body["idempotency_key"]
)
if method == "GET" and len(segments) == 3 and segments[:2] == ["v1", "tasks"]:
return 200, service.get_task(identity, _safe_identifier(segments[2]))
if (
method == "POST" and len(segments) == 4
and segments[:2] == ["v1", "tasks"] and segments[3] == "cancel"
):
self._exact(body, set())
return 200, service.cancel_task(identity, _safe_identifier(segments[2]))
if (
method == "GET" and len(segments) == 4
and segments[:2] == ["v1", "missions"] and segments[3] == "explain"
):
return 200, service.explain(identity, _safe_identifier(segments[2]))
if method == "POST" and segments == ["v1", "workers", "register"]:
self._exact(body, set())
return 200, service.register_worker(identity)
if method == "POST" and segments == ["v1", "workers", "heartbeat"]:
self._exact(body, set())
return 200, service.heartbeat(identity)
if method == "POST" and segments == ["v1", "workers", "claim"]:
self._exact(body, set())
return 200, service.claim_next(identity)
if method == "POST" and segments == ["v1", "workers", "renew"]:
self._exact(body, {"task_id", "claim_id"})
return 200, service.renew(
identity, _safe_identifier(body["task_id"]), _safe_identifier(body["claim_id"])
)
if method == "POST" and segments == ["v1", "workers", "execute"]:
self._exact(body, {"task_id", "claim_id"})
return 200, service.execute_claim(
identity, _safe_identifier(body["task_id"]), _safe_identifier(body["claim_id"]),deadline=self.request_deadline
)
raise _RequestError("HTTP_NOT_FOUND", 404)
def _handle(self, method: str) -> None:
try:
self.request_deadline=self._deadline()
identity = self._identity()
segments = self._path()
body = self._json_body() if method == "POST" else None
if method == "GET":
lengths = self.headers.get_all("Content-Length", [])
if self.headers.get("Transfer-Encoding") is not None:
raise _RequestError("HTTP_GET_BODY_FORBIDDEN", 400)
if len(lengths) > 1 or (lengths and (not lengths[0].isdigit() or int(lengths[0]) != 0)):
raise _RequestError("HTTP_GET_BODY_FORBIDDEN", 400)
status, result = self._dispatch(method, identity, segments, body)
self._send(status, result)
except _RequestError as exc:
self._error(exc)
except ServiceError as exc:
status = exc.status if isinstance(exc.status, int) and 400 <= exc.status <= 599 else 500
self._send(status, {"error": {"code": exc.code}})
except TimeoutError:
self._send(408, {"error": {"code": "HTTP_DEADLINE_EXPIRED"}})
except Exception:
# No exception string, traceback, request headers, or token crosses
# this boundary.
self._send(500, {"error": {"code": "HTTP_INTERNAL_ERROR"}})
def do_GET(self):
self._handle("GET")
def do_POST(self):
self._handle("POST")
def _method_not_allowed(self):
try:
self._deadline()
self._identity()
self._send(405, {"error": {"code": "HTTP_METHOD_NOT_ALLOWED"}})
except _RequestError as exc:
self._error(exc)
do_PUT = _method_not_allowed
do_PATCH = _method_not_allowed
do_DELETE = _method_not_allowed
do_OPTIONS = _method_not_allowed
do_HEAD = _method_not_allowed
do_TRACE = _method_not_allowed
do_CONNECT = _method_not_allowed
def serve(
service: Any,
tokens: Mapping[str, Identity],
host: str = "127.0.0.1",
port: int = 0,
) -> ThreadingHTTPServer:
"""Build an unstarted real HTTP server bound only to an IPv4 loopback."""
host = _loopback_host(host, "HTTP_LOOPBACK_REQUIRED")
if type(port) is not int or not 0 <= port <= 65_535:
raise TransportError("HTTP_PORT_INVALID")
if not isinstance(tokens, Mapping) or not tokens:
raise TransportError("HTTP_TRUSTED_IDENTITIES_REQUIRED")
identities: dict[str, Identity] = {}
for token, identity in tokens.items():
if (
not isinstance(token, str) or not 32<=len(token)<=512
or any(ord(char) < 33 or ord(char) > 126 for char in token)
or not isinstance(identity, Identity)
):
raise TransportError("HTTP_TRUSTED_IDENTITY_INVALID")
identities[token] = identity
return _BaselineHTTPServer((host, port), _Handler, service, identities)
class _NoRedirect(HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
return None
class HttpTransport:
"""Small urllib client for the loopback API; redirects and proxies are off."""
def __init__(
self,
base_url: str,
token: str,
*,
timeout_seconds: float = 15,
max_response_bytes: int = MAX_RESPONSE_BYTES,
):
try:
parsed = urlsplit(base_url)
parsed_port = parsed.port
except ValueError as exc:
raise TransportError("TRANSPORT_BASE_URL_INVALID") from exc
if (
parsed.scheme != "http" or parsed.username is not None or parsed.password is not None
or parsed.query or parsed.fragment or parsed.path not in {"", "/"}
or parsed.hostname is None or parsed_port is None
):
raise TransportError("TRANSPORT_BASE_URL_INVALID")
_loopback_host(parsed.hostname, "TRANSPORT_LOOPBACK_REQUIRED")
if not isinstance(token, str) or not 32<=len(token)<=512 or any(
ord(char) < 33 or ord(char) > 126 for char in token
):
raise TransportError("TRANSPORT_TOKEN_INVALID")
if (
isinstance(timeout_seconds, bool)
or not isinstance(timeout_seconds, (int, float))
or not math.isfinite(float(timeout_seconds))
or not 0.1 <= timeout_seconds <= MAX_DEADLINE_SECONDS
):
raise TransportError("TRANSPORT_TIMEOUT_INVALID")
if type(max_response_bytes) is not int or not 1 <= max_response_bytes <= 4 * 1024 * 1024:
raise TransportError("TRANSPORT_RESPONSE_LIMIT_INVALID")
self.base_url = f"http://{parsed.hostname}:{parsed_port}"
self.token = token
self.timeout_seconds = float(timeout_seconds)
self.max_response_bytes = max_response_bytes
self._opener = build_opener(ProxyHandler({}), _NoRedirect())
def _decode(self, response) -> Any:
content_type = response.headers.get("Content-Type", "").split(";", 1)[0]
if content_type != "application/json":
raise TransportError("TRANSPORT_RESPONSE_TYPE_INVALID")
declared = response.headers.get("Content-Length")
if declared is None or not declared.isdigit() or int(declared) > self.max_response_bytes:
raise TransportError("TRANSPORT_RESPONSE_TOO_LARGE")
raw = response.read(self.max_response_bytes + 1)
if len(raw) > self.max_response_bytes or len(raw) != int(declared):
raise TransportError("TRANSPORT_RESPONSE_SIZE_INVALID")
try:
def unique_object(pairs):
result = {}
for key, item in pairs:
if key in result:
raise ValueError("duplicate")
result[key] = item
return result
return json.loads(
raw.decode("utf-8"), object_pairs_hook=unique_object,
parse_constant=lambda _value: (_ for _ in ()).throw(ValueError("non-finite")),
)
except (UnicodeDecodeError, json.JSONDecodeError, ValueError) as exc:
raise TransportError("TRANSPORT_RESPONSE_JSON_INVALID") from exc
def _request(self, method: str, path: str, body: Mapping[str, Any] | None = None) -> Any:
if not isinstance(path, str) or not path.startswith("/") or "?" in path or "#" in path:
raise TransportError("TRANSPORT_PATH_INVALID")
data = None
headers = {
"Authorization": "Bearer " + self.token,
"Accept": "application/json",
"X-AIOS-Deadline": str(time.time() + self.timeout_seconds),
}
if method == "POST":
if not isinstance(body, Mapping):
raise TransportError("TRANSPORT_BODY_INVALID")
try:
data = json.dumps(
body, sort_keys=True, separators=(",", ":"), ensure_ascii=False,
allow_nan=False,
).encode("utf-8")
except (TypeError, ValueError) as exc:
raise TransportError("TRANSPORT_BODY_INVALID") from exc
if not data or len(data) > MAX_REQUEST_BYTES:
raise TransportError("TRANSPORT_BODY_TOO_LARGE")
headers["Content-Type"] = "application/json"
request = Request(self.base_url + path, data=data, headers=headers, method=method)
try:
with self._opener.open(request, timeout=self.timeout_seconds) as response:
return self._decode(response)
except HTTPError as exc:
if 300 <= exc.code < 400:
exc.close()
raise TransportError("TRANSPORT_REDIRECT_REJECTED", exc.code) from None
try:
payload = self._decode(exc)
except TransportError:
payload = None
except (TimeoutError, OSError):
raise TransportError("TRANSPORT_UNAVAILABLE") from None
finally:
exc.close()
code = None
if isinstance(payload, Mapping) and isinstance(payload.get("error"), Mapping):
code = payload["error"].get("code")
if not isinstance(code, str) or _IDENTIFIER.fullmatch(code) is None:
code = "TRANSPORT_HTTP_ERROR"
raise TransportError(code, exc.code) from None
except (URLError, TimeoutError, OSError):
raise TransportError("TRANSPORT_UNAVAILABLE") from None
def submit_goal(self, request: Mapping[str, Any], *, idempotency_key: str) -> dict:
return self._request(
"POST", "/v1/goals/submit",
{"request": request, "idempotency_key": idempotency_key},
)
def get_task(self, task_id: str) -> dict:
return self._request("GET", "/v1/tasks/" + quote(task_id, safe=""))
def cancel_task(self, task_id: str) -> dict:
return self._request("POST", "/v1/tasks/" + quote(task_id, safe="") + "/cancel", {})
def explain(self, mission_id: str) -> dict:
return self._request("GET", "/v1/missions/" + quote(mission_id, safe="") + "/explain")
def register_worker(self) -> dict:
return self._request("POST", "/v1/workers/register", {})
def heartbeat(self) -> dict:
return self._request("POST", "/v1/workers/heartbeat", {})
def claim_next(self) -> dict | None:
return self._request("POST", "/v1/workers/claim", {})
def renew(self, task_id: str, claim_id: str) -> dict:
return self._request(
"POST", "/v1/workers/renew", {"task_id": task_id, "claim_id": claim_id}
)
def execute_claim(self, task_id: str, claim_id: str) -> dict:
return self._request(
"POST", "/v1/workers/execute", {"task_id": task_id, "claim_id": claim_id}
)
# Goal-oriented names used by thin clients.
submit = submit_goal
query = get_task
cancel = cancel_task
Transport = HttpTransport
__all__ = ["HttpTransport", "Transport", "TransportError", "serve"]
@@ -0,0 +1,204 @@
"""Verification-only Ed25519 boundaries for release and acceptance evidence."""
from __future__ import annotations
import base64
import json
import re
from types import MappingProxyType
from typing import Any, Callable, Mapping
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
from .lineage import VerifiedAcceptanceEvidence, canonical_artifact
from .release import VerifiedReceipt
RELEASE_RECEIPT_DOMAIN = b"aios.release-operation-receipt.v1"
ACCEPTANCE_EVIDENCE_DOMAIN = b"aios.release-acceptance-evidence.v1"
_ENVELOPE_FIELDS = {"schema", "key_id", "payload", "signature"}
_RECEIPT_FIELDS = {
"receipt_id", "tenant_id", "subject_id", "component_id", "task_id",
"source_task_id", "source_effect_id", "grant_id", "job_id", "effect_id", "operation_id",
"deployment_id", "release_id", "artifact", "outcome", "observed", "observed_at",
}
_ACCEPTANCE_FIELDS = {
"acceptance_id", "tenant_id", "subject_id", "component_id", "deployment_id", "release_id",
"artifact", "accepted", "evidence_id", "observed", "observed_at",
}
_B64URL = re.compile(r"[A-Za-z0-9_-]+")
class SignatureVerificationError(ValueError):
"""A configured trust root did not authenticate an exact evidence payload."""
def _canonical(value: Any) -> bytes:
try:
return json.dumps(
value, ensure_ascii=False, sort_keys=True, separators=(",", ":"),
allow_nan=False,
).encode("utf-8")
except (TypeError, ValueError) as exc:
raise SignatureVerificationError("signed payload is not canonical JSON") from exc
def _decode_urlsafe(value: Any, name: str, expected_size: int) -> bytes:
if not isinstance(value, str) or not value or not _B64URL.fullmatch(value) or "=" in value:
raise SignatureVerificationError(f"invalid {name} encoding")
try:
raw = base64.urlsafe_b64decode(value + "=" * (-len(value) % 4))
except (ValueError, TypeError) as exc:
raise SignatureVerificationError(f"invalid {name} encoding") from exc
if len(raw) != expected_size:
raise SignatureVerificationError(f"invalid {name} size")
return raw
def _public_key(value: Any) -> Ed25519PublicKey:
if isinstance(value, Ed25519PublicKey):
return value
if isinstance(value, bytes):
raw = value
elif isinstance(value, str):
raw = _decode_urlsafe(value, "trusted public key", 32)
else:
raise ValueError("trusted public key must be Ed25519PublicKey, raw bytes, or base64url")
if len(raw) != 32:
raise ValueError("trusted Ed25519 public key must be 32 bytes")
return Ed25519PublicKey.from_public_bytes(raw)
def _trusted_keys(values: Mapping[str, Any]) -> Mapping[str, Ed25519PublicKey]:
if not isinstance(values, Mapping) or not values:
raise ValueError("trusted_public_keys must be a non-empty mapping")
keys: dict[str, Ed25519PublicKey] = {}
for key_id, value in values.items():
if not isinstance(key_id, str) or not key_id or len(key_id) > 128:
raise ValueError("invalid trusted key_id")
keys[key_id] = _public_key(value)
return MappingProxyType(keys)
def _verified_payload(
envelope: Any,
keys: Mapping[str, Ed25519PublicKey],
*,
schema: str,
domain: bytes,
payload_fields: set[str],
) -> Mapping[str, Any]:
if not isinstance(envelope, Mapping) or set(envelope) != _ENVELOPE_FIELDS:
raise SignatureVerificationError("signed envelope fields invalid")
if envelope.get("schema") != schema:
raise SignatureVerificationError("signed envelope schema mismatch")
key_id = envelope.get("key_id")
if not isinstance(key_id, str) or key_id not in keys:
raise SignatureVerificationError("untrusted signing key_id")
payload = envelope.get("payload")
if not isinstance(payload, Mapping) or set(payload) != payload_fields:
raise SignatureVerificationError("signed payload fields invalid")
signature = _decode_urlsafe(envelope.get("signature"), "signature", 64)
try:
keys[key_id].verify(signature, domain + b"\x00" + _canonical(dict(payload)))
except InvalidSignature as exc:
raise SignatureVerificationError("Ed25519 signature invalid") from exc
return payload
def _text(payload: Mapping[str, Any], name: str) -> str:
value = payload.get(name)
if not isinstance(value, str) or not value or len(value) > 512:
raise SignatureVerificationError(f"signed {name} invalid")
return value
def _expected(payload: Mapping[str, Any], expected: Mapping[str, Any], fields: tuple[str, ...]) -> None:
for name in fields:
if name not in expected:
raise SignatureVerificationError(f"verifier expected {name} missing")
actual = canonical_artifact(payload[name]) if name == "artifact" else payload[name]
wanted = canonical_artifact(expected[name]) if name == "artifact" else expected[name]
if actual != wanted:
raise SignatureVerificationError(f"signed {name} binding mismatch")
def ed25519_release_receipt_verifier(
trusted_public_keys: Mapping[str, Any],
) -> Callable[[Any, Mapping[str, Any]], VerifiedReceipt]:
"""Build a receipt verifier from local/configured public keys only."""
keys = _trusted_keys(trusted_public_keys)
def verify(envelope: Any, expected: Mapping[str, Any]) -> VerifiedReceipt:
payload = _verified_payload(
envelope, keys,
schema="aios.release-operation-receipt-envelope.v1",
domain=RELEASE_RECEIPT_DOMAIN,
payload_fields=_RECEIPT_FIELDS,
)
_expected(
payload, expected,
("tenant_id", "subject_id", "component_id", "task_id", "source_task_id", "source_effect_id",
"grant_id", "job_id", "effect_id", "operation_id", "deployment_id", "release_id", "artifact"),
)
if payload.get("outcome") != "SUCCEEDED":
raise SignatureVerificationError("signed receipt outcome is not SUCCEEDED")
if not isinstance(payload.get("observed"), Mapping):
raise SignatureVerificationError("signed receipt observation invalid")
return VerifiedReceipt(
receipt_id=_text(payload, "receipt_id"), tenant_id=_text(payload, "tenant_id"),
subject_id=_text(payload, "subject_id"), effect_id=_text(payload, "effect_id"),
component_id=_text(payload,"component_id"), task_id=_text(payload,"task_id"),
source_task_id=_text(payload,"source_task_id"), source_effect_id=_text(payload,"source_effect_id"),
grant_id=_text(payload,"grant_id"), job_id=_text(payload,"job_id"),
operation_id=_text(payload, "operation_id"), deployment_id=_text(payload, "deployment_id"),
release_id=_text(payload, "release_id"), artifact=payload["artifact"],
observed=dict(payload["observed"]), outcome="SUCCEEDED",
observed_at=_text(payload, "observed_at"),
)
return verify
def ed25519_acceptance_evidence_verifier(
trusted_public_keys: Mapping[str, Any],
) -> Callable[[Any, Mapping[str, Any]], VerifiedAcceptanceEvidence]:
"""Build an acceptance verifier in a domain separate from stage receipts."""
keys = _trusted_keys(trusted_public_keys)
def verify(envelope: Any, expected: Mapping[str, Any]) -> VerifiedAcceptanceEvidence:
payload = _verified_payload(
envelope, keys,
schema="aios.release-acceptance-evidence-envelope.v1",
domain=ACCEPTANCE_EVIDENCE_DOMAIN,
payload_fields=_ACCEPTANCE_FIELDS,
)
_expected(
payload, expected,
("acceptance_id", "tenant_id", "subject_id", "component_id", "deployment_id", "release_id", "artifact", "accepted"),
)
if type(payload.get("accepted")) is not bool:
raise SignatureVerificationError("signed accepted decision invalid")
if not isinstance(payload.get("observed"), Mapping):
raise SignatureVerificationError("signed acceptance observation invalid")
return VerifiedAcceptanceEvidence(
acceptance_id=_text(payload, "acceptance_id"), tenant_id=_text(payload, "tenant_id"),
subject_id=_text(payload, "subject_id"), deployment_id=_text(payload, "deployment_id"),
component_id=_text(payload,"component_id"),
release_id=_text(payload, "release_id"), artifact=payload["artifact"],
accepted=payload["accepted"], evidence_id=_text(payload, "evidence_id"),
observed=dict(payload["observed"]), observed_at=_text(payload, "observed_at"),
)
return verify
__all__ = [
"ACCEPTANCE_EVIDENCE_DOMAIN", "RELEASE_RECEIPT_DOMAIN",
"SignatureVerificationError", "ed25519_acceptance_evidence_verifier",
"ed25519_release_receipt_verifier",
]
+121
View File
@@ -0,0 +1,121 @@
"""Thin resident worker loop over the authenticated HTTP transport.
Worker policy, operation handlers, effects, receipts, and completion remain in
the trusted server. This process only maintains its registered transport
identity and asks the service to run an offered claim.
"""
from __future__ import annotations
import math
import threading
import time
from typing import Any
from .transport import TransportError
class WorkerError(RuntimeError):
pass
class OperationWorker:
HEARTBEAT_INTERVAL_SECONDS = 15.0
def __init__(self, transport: Any, poll_seconds: float = 1.0, stop_event=None):
required = (
"register_worker", "heartbeat", "claim_next", "renew", "execute_claim"
)
if transport is None or any(not callable(getattr(transport, name, None)) for name in required):
raise WorkerError("WORKER_TRANSPORT_INVALID")
if (
isinstance(poll_seconds, bool)
or not isinstance(poll_seconds, (int, float))
or not math.isfinite(float(poll_seconds))
or not 0.001 <= poll_seconds <= 60
):
raise WorkerError("WORKER_POLL_INTERVAL_INVALID")
if stop_event is not None and (
not callable(getattr(stop_event, "is_set", None))
or not callable(getattr(stop_event, "wait", None))
):
raise WorkerError("WORKER_STOP_EVENT_INVALID")
self.transport = transport
self.poll_seconds = float(poll_seconds)
self.stop_event = stop_event if stop_event is not None else threading.Event()
self.registered = False
self.worker_id: str | None = None
self._last_heartbeat = 0.0
self.last_error: str | None = None
def _register(self) -> None:
result = self.transport.register_worker()
if (
not isinstance(result, dict) or result.get("registered") is not True
or not isinstance(result.get("worker_id"), str) or not result["worker_id"]
):
raise WorkerError("WORKER_REGISTRATION_RESULT_INVALID")
self.worker_id = result["worker_id"]
self.registered = True
def _heartbeat(self, *, force: bool = False) -> None:
now = time.monotonic()
if not force and now < self._last_heartbeat + self.HEARTBEAT_INTERVAL_SECONDS:
return
result = self.transport.heartbeat()
if (
not isinstance(result, dict) or result.get("alive") is not True
or result.get("worker_id") != self.worker_id
):
raise WorkerError("WORKER_HEARTBEAT_RESULT_INVALID")
self._last_heartbeat = time.monotonic()
def run_once(self):
"""Perform one register/heartbeat/claim/renew/execute iteration."""
if self.stop_event.is_set():
return None
just_registered = False
if not self.registered:
self._register()
just_registered = True
self._heartbeat(force=just_registered)
claim = self.transport.claim_next()
if claim is None:
self.last_error = None
return None
required = {"task_id", "claim_id", "generation", "lease_until", "operation"}
if not isinstance(claim, dict) or set(claim) != required:
raise WorkerError("WORKER_CLAIM_RESULT_INVALID")
task_id = claim["task_id"]
claim_id = claim["claim_id"]
if not isinstance(task_id, str) or not task_id or not isinstance(claim_id, str) or not claim_id:
raise WorkerError("WORKER_CLAIM_RESULT_INVALID")
renewed = self.transport.renew(task_id, claim_id)
if (
not isinstance(renewed, dict) or renewed.get("renewed") is not True
or renewed.get("task_id") != task_id or renewed.get("claim_id") != claim_id
):
raise WorkerError("WORKER_RENEW_RESULT_INVALID")
result = self.transport.execute_claim(task_id, claim_id)
if not isinstance(result, dict) or result.get("task_id") != task_id:
raise WorkerError("WORKER_EXECUTION_RESULT_INVALID")
self.last_error = None
return result
def run_forever(self) -> None:
"""Run until the supplied stop event is set, using interruptible waits."""
while not self.stop_event.is_set():
try:
self.run_once()
except TransportError as exc:
# Transport failures are observable and retried on the next
# bounded poll. Re-register after a server restart; programming
# and schema errors still terminate.
self.last_error = exc.code
self.registered = False
self.stop_event.wait(self.poll_seconds)
Worker = OperationWorker
__all__ = ["OperationWorker", "Worker", "WorkerError"]
@@ -0,0 +1,51 @@
"""Registered worker eligibility; fallback is deployment policy, never a special actor."""
from dataclasses import dataclass
import math
from typing import Mapping
@dataclass(frozen=True)
class WorkerProfile:
worker_id: str
identity: str
tenant_scope: tuple[str, ...]
subject_scope: tuple[str, ...]
capabilities: tuple[str, ...]
priority: int
fallback_executor: bool
heartbeat_seconds: int
max_concurrency: int
@classmethod
def from_dict(cls, value: Mapping):
if set(value) != set(cls.__dataclass_fields__):
raise ValueError('WORKER_PROFILE_FIELDS_INVALID')
data = dict(value)
for name in ('worker_id', 'identity'):
if not isinstance(data[name], str) or not data[name] or len(data[name]) > 128:
raise ValueError('WORKER_ID_INVALID')
for name in ('tenant_scope', 'subject_scope', 'capabilities'):
items = data[name]
if not isinstance(items, (list, tuple)) or not items or any(not isinstance(x, str) or not x or '*' in x for x in items):
raise ValueError('WORKER_SCOPE_INVALID')
data[name] = tuple(sorted(set(items)))
if type(data['fallback_executor']) is not bool:
raise ValueError('WORKER_FALLBACK_INVALID')
for name, lower, upper in (('priority', 0, 1000), ('heartbeat_seconds', 1, 300), ('max_concurrency', 1, 32)):
if type(data[name]) is not int or not lower <= data[name] <= upper:
raise ValueError('WORKER_LIMIT_INVALID')
return cls(**data)
def eligible(worker: WorkerProfile, task: Mapping, *, now: float, last_heartbeat: float,
fallback_seconds: int = 600) -> bool:
ready_at = task.get('ready_at')
if any(type(value) not in (int,float) or not math.isfinite(value) for value in (ready_at,now,last_heartbeat)) or ready_at > now:
return False
if last_heartbeat > now or now > last_heartbeat + worker.heartbeat_seconds * 3:
return False
if task.get('tenant_id') not in worker.tenant_scope or task.get('subject_id') not in worker.subject_scope:
return False
if task.get('operation') not in worker.capabilities:
return False
return not worker.fallback_executor or now >= ready_at + fallback_seconds
@@ -0,0 +1,130 @@
"""Read-only validation of recovered campaign authorization evidence."""
from __future__ import annotations
import hashlib
import json
import os
import re
from pathlib import Path
import stat
from typing import Any
class RecoveryEvidenceError(RuntimeError):
pass
# Relative evidence path: a safe relative path, never absolute, never with
# traversal segments, never with backslash separators.
_SAFE_EVIDENCE_REL = re.compile(r"^[A-Za-z0-9_./@-]+$")
def _contained_evidence(path: Path, base: Path) -> Path:
"""Resolve an evidence path and guarantee it stays under `base`."""
raw = str(path)
if (
os.path.isabs(raw)
or "\\" in raw
or raw.startswith("/")
or ".." in Path(raw).parts
or not _SAFE_EVIDENCE_REL.fullmatch(raw)
):
raise RecoveryEvidenceError("RECOVERY_EVIDENCE_PATH_INVALID")
base_resolved = base.resolve()
target = (base_resolved / raw).resolve()
if base_resolved != target and not str(target).startswith(str(base_resolved) + os.sep):
raise RecoveryEvidenceError("RECOVERY_EVIDENCE_PATH_ESCAPE")
return target
def _digest(path: Path) -> str:
h = hashlib.sha256()
with path.open("rb") as stream:
for block in iter(lambda: stream.read(1024 * 1024), b""):
h.update(block)
return h.hexdigest()
def _secure_json(path: Path) -> dict[str, Any]:
if path.is_symlink() or not path.is_file():
raise RecoveryEvidenceError("RECOVERY_EVIDENCE_MISSING")
info = path.stat(follow_symlinks=False)
if info.st_uid != 0 or stat.S_IMODE(info.st_mode) & 0o022:
raise RecoveryEvidenceError("RECOVERY_EVIDENCE_PERMISSIONS")
try:
value = json.loads(path.read_text(encoding="utf-8"))
except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc:
raise RecoveryEvidenceError("RECOVERY_EVIDENCE_INVALID") from exc
if not isinstance(value, dict):
raise RecoveryEvidenceError("RECOVERY_EVIDENCE_OBJECT_REQUIRED")
return value
def load_recovered_authorization(path: str | Path) -> dict[str, Any]:
"""Verify the recovery receipt, sidecar, and all referenced immutable files."""
receipt_path = Path(path)
receipt = _secure_json(receipt_path)
sidecar = receipt_path.with_suffix(receipt_path.suffix + ".sha256")
if sidecar.is_symlink() or not sidecar.is_file():
raise RecoveryEvidenceError("RECOVERY_DIGEST_SIDECAR_MISSING")
expected = sidecar.read_text(encoding="ascii").split()[0]
actual = _digest(receipt_path)
if expected != actual:
raise RecoveryEvidenceError("RECOVERY_DIGEST_MISMATCH")
required = {
"schema",
"campaign_id",
"original_event_id",
"original_authorization_digest",
"signed_grant_id",
"signed_grant_digest",
"task_id",
"repository",
"original_base_oid",
"branch",
"expected_branch_oid",
"recovered_from_existing_evidence",
"new_owner_interaction",
"recovery_state",
"production_side_effect_executed",
"global_writes_enabled",
"grant_consumed",
"owner_authorization_consumed",
"original_receipts_immutable",
"recovered_at",
"evidence_files",
}
if set(receipt) != required or receipt["schema"] != "aios.owner-authorization-recovery.v1":
raise RecoveryEvidenceError("RECOVERY_SCHEMA_INVALID")
if receipt["recovered_from_existing_evidence"] is not True or receipt["new_owner_interaction"] is not False:
raise RecoveryEvidenceError("RECOVERY_NOT_APPEND_ONLY")
if receipt["production_side_effect_executed"] is not False or receipt["global_writes_enabled"] is not False:
raise RecoveryEvidenceError("RECOVERY_SCOPE_INVALID")
if receipt["recovery_state"] != "RECOVERED_PENDING_CONTINUATION_LEASE":
raise RecoveryEvidenceError("RECOVERY_STATE_INVALID")
evidence = receipt["evidence_files"]
if not isinstance(evidence, list) or not evidence:
raise RecoveryEvidenceError("RECOVERY_EVIDENCE_LIST_INVALID")
base = receipt_path.resolve().parent
for item in evidence:
if not isinstance(item, dict) or set(item) != {"path", "sha256"}:
raise RecoveryEvidenceError("RECOVERY_EVIDENCE_ENTRY_INVALID")
target = _contained_evidence(Path(item["path"]), base)
if _digest(target) != item["sha256"]:
raise RecoveryEvidenceError("RECOVERY_EVIDENCE_DIGEST_MISMATCH")
return {
"schema": receipt["schema"],
"state": receipt["recovery_state"],
"campaign_id": receipt["campaign_id"],
"original_event_id": receipt["original_event_id"],
"original_authorization_digest": receipt["original_authorization_digest"],
"signed_grant_id": receipt["signed_grant_id"],
"task_id": receipt["task_id"],
"repository": receipt["repository"],
"original_base_oid": receipt["original_base_oid"],
"branch": receipt["branch"],
"expected_branch_oid": receipt["expected_branch_oid"],
"digest": actual,
"new_owner_interaction": False,
}
@@ -0,0 +1,174 @@
"""Canonical fail-closed capability semantics for formal AgentTask eligibility."""
from __future__ import annotations
from collections.abc import Iterable
import hashlib
import json
# These aliases are the verified C4 contract captured in
# ``qa/capability-contract.txt``. Unknown task names become exact requirements;
# unknown worker names are never granted.
_TASK_ALIASES: dict[str, frozenset[str]] = {
"release:candidate": frozenset({"release:candidate"}),
"repository_read": frozenset({"repo.read"}),
"git.read": frozenset({"repo.read"}),
"repo.read": frozenset({"repo.read"}),
"test_execution": frozenset({"test.execute"}),
"git.test": frozenset({"test.execute"}),
"test.execute": frozenset({"test.execute"}),
"governed_git_write": frozenset({"governed.git.write.branch", "governed.git.write.file"}),
"governed.git.write": frozenset({"governed.git.write.branch", "governed.git.write.file"}),
"governed.write.branch": frozenset({"governed.git.write.branch"}),
"governed.write.file": frozenset({"governed.git.write.file"}),
"shell": frozenset({"runtime.shell"}),
"git": frozenset({"runtime.git"}),
"python": frozenset({"runtime.python"}),
"code.read": frozenset({"legacy.code.read"}),
"code.review": frozenset({"legacy.code.review"}),
"code.write": frozenset({"legacy.code.write"}),
"code.write.patch": frozenset({"legacy.code.write.patch"}),
"test.python": frozenset({"legacy.test.python"}),
"test.shell.allowlisted": frozenset({"legacy.test.shell.allowlisted"}),
"git.commit": frozenset({"legacy.git.commit"}),
"git.diff.read": frozenset({"legacy.git.diff.read"}),
"git.force_push": frozenset({"legacy.git.force_push"}),
"deploy.production": frozenset({"legacy.deploy.production"}),
"secret.transform": frozenset({"legacy.secret.transform"}),
"secure_file_transformer": frozenset({"legacy.secure_file_transformer"}),
"runtime.shadow": frozenset({"legacy.runtime.shadow"}),
"review.security": frozenset({"legacy.review.security"}),
"review.api-contract": frozenset({"legacy.review.api-contract"}),
"gpu.train": frozenset({"legacy.gpu.train"}),
}
_WORKER_ALIASES = {
key: value for key, value in _TASK_ALIASES.items()
if key not in {"release:candidate", "governed_git_write", "governed.git.write"}
}
_WORKER_ALIASES.update({
"release:candidate": frozenset({"release:candidate"}),
"governed.git.write.branch": frozenset({"governed.git.write.branch"}),
"governed.git.write.file": frozenset({"governed.git.write.file"}),
})
_EXACT_LIFECYCLE_CAPABILITIES = frozenset(
{
"agent-task:control",
"governed.task.create",
"governed.write.files",
"production.plan.apply",
"production.promote",
"production.rollback",
"task.principal.issue",
"testing",
"verification",
"writer.lease",
}
)
for _capability in _EXACT_LIFECYCLE_CAPABILITIES:
_TASK_ALIASES[_capability] = frozenset({_capability})
_WORKER_ALIASES[_capability] = frozenset({_capability})
_UNKNOWN_PREFIX = "exact:"
CAPABILITY_VOCABULARY_SCHEMA = "aios.agent-task.capability-vocabulary.v1"
def capability_vocabulary_descriptor() -> dict[str, object]:
"""Return the immutable inputs to the formal capability vocabulary digest.
The generic Task Plane has deliberately different wildcard semantics. This
descriptor names only the formal AgentTask vocabulary used in this module.
"""
return {
"schema": CAPABILITY_VOCABULARY_SCHEMA,
"task_aliases": {
key: sorted(value) for key, value in sorted(_TASK_ALIASES.items())
},
"worker_aliases": {
key: sorted(value) for key, value in sorted(_WORKER_ALIASES.items())
},
"unknown_required_prefix": _UNKNOWN_PREFIX,
"unknown_required": "exact-and-deny",
"unknown_worker": "never-granted",
"wildcard": "formal-deny",
}
def capability_vocabulary_digest() -> str:
"""Return a deterministic digest for the formal capability vocabulary."""
encoded = json.dumps(
capability_vocabulary_descriptor(),
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
allow_nan=False,
).encode("utf-8")
return hashlib.sha256(encoded).hexdigest()
# A constant lets registration, admission, poll, and claim compare one exact
# formal vocabulary without importing or interpreting the generic Task Plane.
CAPABILITY_VOCABULARY_DIGEST = capability_vocabulary_digest()
def _clean(value: object) -> str:
if not isinstance(value, str):
raise ValueError("capability must be a string")
text = value.strip().lower()
if not text:
raise ValueError("capability must not be empty")
return text
def normalize_required_capabilities(values: Iterable[object]) -> frozenset[str]:
result: set[str] = set()
for value in values:
text = _clean(value)
if "*" in text:
result.add(_UNKNOWN_PREFIX + text)
else:
result.update(_TASK_ALIASES.get(text, frozenset({_UNKNOWN_PREFIX + text})))
return frozenset(result)
def normalize_worker_capabilities(values: Iterable[object]) -> frozenset[str]:
result: set[str] = set()
for value in values:
text = _clean(value)
if "*" in text:
continue
mapped = _WORKER_ALIASES.get(text)
if mapped is not None:
result.update(mapped)
if {"governed.git.write.branch", "governed.git.write.file"}.issubset(result):
result.add("governed.git.write")
return frozenset(result)
def has_capabilities(worker_capabilities: Iterable[object], required_capabilities: Iterable[object]) -> bool:
required = normalize_required_capabilities(required_capabilities)
effective = normalize_worker_capabilities(worker_capabilities)
if any(value.startswith(_UNKNOWN_PREFIX) for value in required):
return False
return required.issubset(effective)
def required_capabilities_subset(granted_requirements: Iterable[object], required_requirements: Iterable[object]) -> bool:
granted = normalize_required_capabilities(granted_requirements)
required = normalize_required_capabilities(required_requirements)
if any(value.startswith(_UNKNOWN_PREFIX) for value in granted | required):
return False
return required.issubset(granted)
def normalized_capability_matrix(worker_capabilities: Iterable[object], required_capabilities: Iterable[object]) -> dict[str, object]:
worker_values = tuple(worker_capabilities)
required_values = tuple(required_capabilities)
worker = normalize_worker_capabilities(worker_values)
required = normalize_required_capabilities(required_values)
return {
"worker_effective": sorted(worker),
"task_required": sorted(required),
"eligible": has_capabilities(worker_values, required_values),
}
+261 -22
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
from __future__ import annotations
import hashlib, hmac, json, os, re, subprocess, sys, time, urllib.parse, uuid
import hashlib, hmac, json, os, re, subprocess, sys, time, urllib.parse, urllib.request, uuid
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from typing import Any
@@ -22,8 +22,15 @@ from gitea_tag_publication import (
TagPublisher,
redact,
validate_version,
validate_gitea_api_url,
)
from orchestrator.action_monitor import ActionMonitor
try:
from orchestrator.action_monitor import ActionMonitor
except ModuleNotFoundError:
# The deployed service normally supplies runtime/agent-os on sys.path.
# Keep direct, isolated contract execution bound to the repository copy.
sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "runtime" / "agent-os"))
from orchestrator.action_monitor import ActionMonitor
from silent_release_hardening import (
ApprovalKeyRing,
ApprovalRevocationStore,
@@ -33,6 +40,11 @@ from silent_release_hardening import (
SignedReleasePlanApproval,
SilentReleaseActionService,
)
try:
from aios_gitops_job_controller import GitOpsJobController, JobError, ServiceUnavailable, JobStore
except ModuleNotFoundError:
sys.path.insert(0, str(Path(__file__).resolve().parent))
from aios_gitops_job_controller import GitOpsJobController, JobError, ServiceUnavailable, JobStore
try:
from authoritative_git import (
AuthoritativeGitError,
@@ -58,6 +70,11 @@ except ModuleNotFoundError:
repository_snapshot,
run_authoritative_git,
)
try:
from aios_repository_discovery import RepositoryDiscovery, RepositoryDiscoveryError
except ModuleNotFoundError:
sys.path.insert(0, str(Path(__file__).resolve().parent))
from aios_repository_discovery import RepositoryDiscovery, RepositoryDiscoveryError
ROOT = Path(os.getenv('AIOS_GITOPS_ROOT', '/data/aios-gitops'))
WS = Path(os.getenv('AIOS_GITOPS_WORKSPACE', str(ROOT / 'workspace')))
@@ -93,6 +110,11 @@ SECRET_VAL_RE = re.compile(
r'(sk-[A-Za-z0-9_-]{20,}|HPUA[A-Z0-9]{10,}|SDC[A-Za-z0-9]{20,})'
)
# Control-plane file read: enforce auth, strict identifier, containment and a
# size cap on the command-detail read exposed by /admin/ai-evolution/api/data.
MAX_COMMAND_READ_BYTES = 1024 * 1024
_COMMAND_FILENAME_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]*\.md$")
READ_ONLY_GET_PATHS = frozenset({
'/', '/health', '/api/health', '/api/openapi.yaml',
'/api/openapi-chatgpt-actions.yaml', '/api/schema', '/api', '/api/status',
@@ -103,6 +125,8 @@ READ_ONLY_GET_PATHS = frozenset({
'/admin/ai-evolution/api/data',
'/api/release-consistency', '/api/tag-publication-status',
'/api/release-acceptance',
'/api/repository/tree', '/api/repository/search',
'/api/repository/diff-index', '/api/repository/file',
'/getReleaseCandidate', '/getReleasePlan', '/getReleaseAcceptance',
'/getApprovalEnvelope', '/getReleaseSaga', '/getCapabilityStatus',
'/getRecoveryStatus',
@@ -120,6 +144,12 @@ def reject_non_read_http(handler, method: str, path: str) -> bool:
return False
if path in SILENT_RELEASE_POST_PATHS:
return False
if (
method == 'POST'
and path in ('/api/file', '/api/commit-file')
and os.getenv('AIOS_GITOPS_DURABLE_JOB_EXECUTION_ENABLED', os.getenv('AIOS_GITOPS_JOB_CONTROLLER_ENABLED', 'false')).lower() in {'1', 'true', 'yes', 'on'}
):
return False
decision = evaluate_git_write(WS)
if decision.allowed:
return False
@@ -128,7 +158,10 @@ def reject_non_read_http(handler, method: str, path: str) -> bool:
def now() -> str: return time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime())
def uid(p='id') -> str: return f'{p}-{int(time.time()*1000)}-{uuid.uuid4().hex[:8]}'
def safe(s: str, n=160) -> str: return re.sub(r'[^A-Za-z0-9_./@-]+', '-', str(s or 'unknown')).strip('/-')[:n] or 'unknown'
from security_path_guards import safe_segment, relative_parts, write_text_no_follow
def safe(s: str, n=160) -> str:
return safe_segment(s, max_length=n)
def sh(args, check=False):
p = subprocess.run(['git'] + args, cwd=str(WS), text=True, capture_output=True)
if check and p.returncode != 0: raise RuntimeError(p.stderr or p.stdout or 'git failed')
@@ -176,7 +209,10 @@ def relpath(path: str) -> str:
if not x or x.startswith('/') or '..' in Path(x).parts or Path(x).parts[0] == '.git': raise ValueError('invalid path')
return str(Path(x))
def branch_name(name: str | None, prefix='patch/chatgpt') -> str:
b = safe(name or f'{prefix}-{uid("br")}', 180)
b = name or f'{prefix}-{uid("br")}'
if len(b) > 180: raise ValueError('branch name too long')
relative_parts(b)
if '..' in b or '@{' in b or b.endswith('.lock'): raise ValueError('invalid branch name')
if b in ('main','master') or b.startswith('main/') or b.startswith('master/'): raise ValueError('direct main/master writes are forbidden')
return b
def _json_has_plain_secret(obj: Any) -> bool:
@@ -224,8 +260,12 @@ def token_records():
else:
out.append({'account':'legacy-token','role':'owner','scopes':['*'],'token_plain':line})
return out
from security_path_guards import require_isolated_dev_profile
def authenticate(token: str):
if os.getenv('AIOS_GITOPS_DEV_ALLOW','0') == '1': return {'account':'dev-owner','role':'owner','scopes':['*']}
if os.getenv('AIOS_GITOPS_DEV_ALLOW','0') == '1':
require_isolated_dev_profile(HOST)
return {'account':'dev-owner','role':'owner','scopes':['*']}
if not token: return None
digest=hashlib.sha256(token.encode()).hexdigest()
for r in token_records():
@@ -254,7 +294,7 @@ def checkout(branch, base='main', create=False):
return b
def write_file(path, content):
if content_has_secret(content): raise ValueError('secret_rejected: use secret_ref; plaintext API keys must not enter Git')
r=relpath(path); t=WS/r; t.parent.mkdir(parents=True, exist_ok=True); t.write_text(content, encoding='utf-8'); return r
r=relpath(path); write_text_no_follow(WS, str(r).replace('\\','/'), content); return r
def write_json(path, data): return write_file(path, json.dumps(data, ensure_ascii=False, indent=2) + '\n')
def sync_branch(branch: str) -> dict:
if not branch or branch in ('main','master') or branch.startswith('main/') or branch.startswith('master/'):
@@ -297,8 +337,7 @@ def resolve_commit_oid(value: str | None, field: str = 'target_oid') -> str:
raise ValueError(f'{field} does not resolve to the exact approved commit') from exc
def existing_tag_target(tag: str) -> str | None:
if not re.fullmatch(r'V\d+R\d+C\d+(?:-[A-Za-z0-9_.-]+)?$', str(tag or '')):
raise ValueError('invalid release version')
validate_version(tag)
try:
result = run_authoritative_git(
['rev-parse', '--verify', f'refs/tags/{tag}^{{commit}}'],
@@ -331,9 +370,80 @@ def required_authoritative_git_dir() -> Path:
return Path(str(snapshot["authoritative_git_dir"]))
_DISCOVERY_ACTIONS = {
'/api/repository/tree': (
'list_repository_tree',
{'repository', 'ref', 'limit'},
{'prefix', 'depth', 'cursor'},
),
'/api/repository/search': (
'search_repository_source',
{'repository', 'ref', 'query', 'max_matches', 'max_bytes'},
{'path_globs', 'cursor'},
),
'/api/repository/diff-index': (
'get_diff_index',
{'repository', 'from', 'to', 'limit'},
{'path_prefix', 'cursor'},
),
'/api/repository/file': (
'read_repository_file',
{'repository', 'ref', 'path'},
{'max_bytes'},
),
}
def repository_discovery_request(
path: str, query: dict[str, list[str]]
) -> dict[str, Any]:
operation, required, optional = _DISCOVERY_ACTIONS[path]
if set(query) - required - optional:
raise ValueError('unknown repository discovery query field')
request: dict[str, Any] = {}
for key, values in query.items():
if key == 'path_globs':
if not values:
raise ValueError('empty repository discovery path glob')
request[key] = list(values)
continue
if len(values) != 1:
raise ValueError('duplicate repository discovery query field')
value: Any = values[0]
if key in {'depth', 'limit', 'max_matches', 'max_bytes'}:
try:
value = int(value, 10)
except (TypeError, ValueError) as exc:
raise ValueError('invalid repository discovery integer') from exc
request[key] = value
if required - set(request):
raise ValueError('missing repository discovery query field')
if not REPOSITORY_IDENTITY:
raise RepositoryDiscoveryError('REPOSITORY_DISCOVERY_UNAVAILABLE')
try:
discovery = RepositoryDiscovery(
allowed_repositories=(REPOSITORY_IDENTITY,),
git_dir=required_authoritative_git_dir(),
cursor_secret=os.getenv('AIOS_REPOSITORY_DISCOVERY_CURSOR_SECRET'),
)
except RuntimeError as exc:
if isinstance(exc, RepositoryDiscoveryError):
raise
raise RepositoryDiscoveryError('REPOSITORY_DISCOVERY_UNAVAILABLE') from exc
public_to_internal = {
'ref': 'revision',
'path': 'file_path',
'path_prefix': 'prefix',
}
internal_request = {
public_to_internal.get(key, key): value for key, value in request.items()
}
return dict(getattr(discovery, operation)(internal_request))
def publication_publisher() -> TagPublisher:
authoritative_repo = required_authoritative_git_dir()
return TagPublisher(
publisher = TagPublisher(
WS,
GitTagBackend(WS, authoritative_repo=authoritative_repo),
GiteaTagClient(
@@ -344,6 +454,10 @@ def publication_publisher() -> TagPublisher:
),
os.getenv('AIOS_GITOPS_GITEA_REMOTE','gitea'),
)
publisher.baseline_path = Path(os.getenv(
'AIOS_GITOPS_BASELINE_AUTHORITY', str(WS / 'baseline' / 'current.json')
))
return publisher
def commit(paths, msg):
ensure_git(); paths = [relpath(p) for p in paths] if paths else ['.']; sh(['add'] + paths, True)
@@ -393,15 +507,71 @@ class H(BaseHTTPRequestHandler):
if not a['ok']: self.sendj({'error':'unauthorized','hint':'Bearer token required'},401); return None
if '*' not in a['scopes'] and scope not in a['scopes']: self.sendj({'error':'forbidden','required_scope':scope,'account':a['account']},403); return None
return a
def bridge_release(self, path, data):
route = {
'/createTagApprovalRequest': '/v2/tag/approval-request',
'/requestOrCreateTag': '/v2/tag/publish',
'/getTagPublicationStatus': '/v2/tag/status',
'/buildProductionArtifact': '/v2/release/artifact',
'/executeApprovedProductionRelease': '/v2/release/execute',
'/getReleaseConsistency': '/v2/release/consistency',
'/runReleaseAcceptance': '/v2/release/acceptance',
}.get(path)
if route is None:
raise ValueError('governed_release_route_invalid')
base = os.getenv('AIOS_BRIDGE_V2_URL', 'http://127.0.0.1:18892').rstrip('/')
request = urllib.request.Request(
base + route,
data=json.dumps(data, sort_keys=True, separators=(',', ':')).encode('utf-8'),
headers={'Content-Type': 'application/json'},
method='POST',
)
try:
with urllib.request.urlopen(request, timeout=30) as response:
result = json.loads(response.read().decode('utf-8'))
return self.sendj(result, response.status)
except urllib.error.HTTPError as exc:
try:
body = json.loads(exc.read().decode('utf-8'))
except Exception:
body = {'error': 'bridge_http_error'}
return self.sendj(body, exc.code)
except (urllib.error.URLError, TimeoutError) as exc:
return self.sendj({'error': 'GOVERNED_BRIDGE_UNAVAILABLE', 'detail': type(exc).__name__}, 503)
def do_POST(self):
path = urllib.parse.urlparse(self.path).path
if path in _DISCOVERY_ACTIONS:
return self.sendj({'error': 'method_not_allowed'},405)
if path in {
'/createTagApprovalRequest', '/requestOrCreateTag', '/getTagPublicationStatus',
'/buildProductionArtifact', '/executeApprovedProductionRelease',
'/getReleaseConsistency', '/runReleaseAcceptance',
}:
actor = self.need('tag:create')
if not actor:
return
return self.bridge_release(path, self.body())
if reject_non_read_http(self, 'POST', path):
return
return self.sendj({'error': 'not_found', 'path': path}, 404)
def do_GET(self):
u=urllib.parse.urlparse(self.path); path=u.path.rstrip('/') if u.path != '/' else u.path
if path not in READ_ONLY_GET_PATHS: return self.sendj({'error':'not_found','path':path},404)
q=urllib.parse.parse_qs(u.query)
if len(self.path) > 8192:
return self.sendj({'error':'query_too_large'},413)
try:
q=urllib.parse.parse_qs(
u.query, keep_blank_values=True, max_num_fields=64
)
except ValueError:
return self.sendj({'error':'query_invalid'},400)
try:
if path in ('/health','/api/health'): return self.sendj({'status':'ok','service':'chatgpt-gitops-adapter','workspace':str(WS)})
if path in ('/api/openapi.yaml','/api/openapi-chatgpt-actions.yaml','/api/schema'):
return self.sendt(OPENAPI.read_text(encoding='utf-8'), 'application/yaml; charset=utf-8') if OPENAPI.exists() else self.sendj({'error':'schema_missing'},404)
if path in ('/api','/api/status'): return self.sendj(api_spec())
if path in _DISCOVERY_ACTIONS:
return self.sendj(repository_discovery_request(path, q))
if path in ('/api/base','/api/baseline','/base'):
data=jload(WS/'baseline'/'current.json', {})
data.setdefault('authoritative_repository', repository_snapshot(
@@ -454,7 +624,15 @@ class H(BaseHTTPRequestHandler):
return self.sendj({'error':redact(exc)},413)
except (AuthoritativeGitError, RuntimeError) as exc:
return self.sendj({'error':redact(exc)},503)
return self.sendj({'from':frm,'to':to,'diff':diff})
payload = {'from': frm, 'to': to, 'diff': diff}
payload.update({
key: diff[key]
for key in (
'from_oid', 'to_oid', 'changed_paths', 'stats',
'total_size_bytes', 'truncated', 'patch_limit_bytes',
)
})
return self.sendj(payload)
if path in ('/api/patch','/api/patches','/api/branches','/patch'):
snapshot = repository_snapshot(
git_dir=AUTHORITATIVE_GIT_DIR,
@@ -566,7 +744,8 @@ class H(BaseHTTPRequestHandler):
return self.sendj(
service.get_capability_status(candidate_id)
)
return self.sendj(service.get_release_saga(candidate_id))
result = service.get_release_saga(candidate_id)
return self.sendj(result, 404 if result.get('status') == 'NOT_FOUND' else 200)
if path == '/getRecoveryStatus':
return self.sendj(
silent_release_service().get_recovery_status()
@@ -579,6 +758,8 @@ class H(BaseHTTPRequestHandler):
return self.sendj({'error':'not_found','path':path},404)
except FileNotFoundError:
return self.sendj({'error':'release candidate not found'},404)
except RepositoryDiscoveryError as e:
return self.sendj({'error': redact(e)}, getattr(e, 'status_code', 400))
except ValueError as e:
error=redact(e)
monitor.record_failure('do_GET', error, {'path':path})
@@ -595,6 +776,8 @@ class H(BaseHTTPRequestHandler):
return self.sendj({'error':'not_found','path':path},404)
def do_POST(self):
path=urllib.parse.urlparse(self.path).path.rstrip('/')
if path in _DISCOVERY_ACTIONS:
return self.sendj({'error':'method_not_allowed'},405)
if reject_non_read_http(self, 'POST', path): return
data=self.body()
try:
@@ -662,11 +845,48 @@ class H(BaseHTTPRequestHandler):
a=self.need('branch:create');
if not a: return
b=checkout(data.get('branch'), data.get('base','main'), True); meta={'type':'branch','branch':b,'base':data.get('base','main'),'created_by':a['account'],'created_at':now(),'direct_main_push':False,'owner_approval_required':True}
r=write_json(f'patches/{safe(b).replace("/","_")}.branch.json', meta); c=commit([r], data.get('message') or f'chore(chatgpt): create {b}'); audit(a['account'],'branch',b,meta); self.sendj({'status':'ok','branch':b,'commit':c})
r=write_json(f'patches/{safe(b.replace("/","_"))}.branch.json', meta); c=commit([r], data.get('message') or f'chore(chatgpt): create {b}'); audit(a['account'],'branch',b,meta); self.sendj({'status':'ok','branch':b,'commit':c})
def file(self,data):
a=self.need('file:write');
if not a: return
b=checkout(data.get('branch'), data.get('base','main'), bool(data.get('create_branch'))); r=write_file(data.get('path'), data.get('content','')); c=commit([r], data.get('message') or f'chore(chatgpt): update {r}'); audit(a['account'],'file',r,{'branch':b,'commit':c}); self.sendj({'status':'committed','branch':b,'path':r,'commit':c})
if os.getenv('AIOS_GITOPS_DURABLE_JOB_EXECUTION_ENABLED', os.getenv('AIOS_GITOPS_JOB_CONTROLLER_ENABLED', 'false')).lower() not in {'1','true','yes','on'}:
return self.sendj({'error':'DURABLE_JOB_CONTROLLER_UNAVAILABLE','retryable':True},503)
try:
path = relpath(str(data.get('path') or ''))
branch = str(data.get('branch') or '')
expected = str(data.get('expected_branch_oid') or '')
campaign_id = str(data.get('campaign_id') or os.getenv('AIOS_GITOPS_CAMPAIGN_ID') or '')
supplied_digest = str(data.get('payload_digest') or '')
content = data.get('content')
payload_bytes = None
if content is not None:
payload_bytes = content.encode('utf-8') if isinstance(content, str) else json.dumps(content, sort_keys=True, ensure_ascii=False).encode('utf-8')
payload_digest = supplied_digest or hashlib.sha256(payload_bytes or b'').hexdigest()
if not campaign_id or not branch or not expected:
return self.sendj({'error':'JOB_BINDING_REQUIRED','retryable':False},409)
request = {
'campaign_id': campaign_id,
'repository': REPOSITORY_IDENTITY or 'aios/aios',
'operation': 'commit-file',
'branch': branch,
'expected_branch_oid': expected,
'payload_digest': payload_digest,
# The controller persists only the binding and digest. Raw
# content is never copied into durable state or logs.
}
receipt = GitOpsJobController(
JobStore(Path(os.getenv('AIOS_GITOPS_JOB_ROOT', '/var/lib/aios-gitops/jobs')))
).submit(request, payload=payload_bytes)
return self.sendj({
'job_id': receipt.job_id,
'state': receipt.state.value,
'side_effect_state': receipt.side_effect_state,
'retryable': receipt.http_status == 503,
}, receipt.http_status)
except ServiceUnavailable as exc:
return self.sendj({'error':str(exc),'layer':'durable_job','retryable':True,'side_effect_state':'UNKNOWN'},503)
except JobError as exc:
return self.sendj({'error':str(exc),'retryable':False},409)
def commit(self,data):
a=self.need('commit:create');
if not a: return
@@ -680,7 +900,10 @@ class H(BaseHTTPRequestHandler):
a=self.need('tag:create');
if not a: return
ver=data.get('version') or data.get('tag')
if not re.match(r'^V\d+R\d+C\d+(-[A-Za-z0-9_.-]+)?$', ver or ''): return self.sendj({'error':'version must match VxRxCx'},400)
try:
validate_version(ver)
except ValueError:
return self.sendj({'error':'version must match VxRxCx'},400)
if not data.get('target_oid') or not data.get('evidence_closure_oid') or not data.get('approval_plan_id'):
return self.sendj({
'error':'production_tag_evidence_required',
@@ -729,7 +952,7 @@ class H(BaseHTTPRequestHandler):
if not name or not base: return self.sendj({'error':'provider_name/base_url/model_name required'},400)
owner=safe(data.get('username') or a['account']); secret_ref=data.get('api_key_secret_ref') or data.get('secret_ref')
if data.get('api_key'):
d=SECRETS/'llm'/owner; d.mkdir(parents=True, exist_ok=True); sid=uid('llmkey'); p=d/f'{sid}.secret'; p.write_text(str(data['api_key']), encoding='utf-8'); os.chmod(p,0o600); secret_ref=f'secret://llm/{owner}/{sid}'
sid=uid('llmkey'); write_text_no_follow(SECRETS, f'llm/{owner}/{sid}.secret', str(data['api_key'])); secret_ref=f'secret://llm/{owner}/{sid}'
if not secret_ref: return self.sendj({'error':'api_key_secret_ref or api_key required'},400)
mid=data.get('model_id') or f'custom.{owner}.{hashlib.sha1((prov+base+name).encode()).hexdigest()[:10]}'; b=checkout(data.get('branch') or f'patch/model-{safe(mid)}','main',True)
doc=sanitize({k:v for k,v in data.items() if k!='api_key'}); doc.update({'model_id':mid,'provider_name':prov,'base_url':base,'model_name':name,'api_key_secret_ref':secret_ref,'created_by':a['account'],'created_at':now()})
@@ -834,12 +1057,25 @@ return{vs,cmds,ar,cmd,cdet,tot,sok,sfail,runs,baseline,refreshMsg,loadCmd,selVer
commands.append(f.name)
command_detail=None
if cmd_filter:
actor = self.actor()
if not actor['ok']:
return self.sendj({'error':'unauthorized','hint':'Bearer token required'},401)
cmd = str(cmd_filter or '')
if not _COMMAND_FILENAME_RE.fullmatch(cmd) or '..' in cmd:
return self.sendj({'command':cmd_filter,'error':'unsafe_command'},400)
for d in cmd_dirs:
if d.exists():
fp=d/cmd_filter
if fp.exists():
command_detail={'command':cmd_filter,'content':fp.read_text(encoding='utf-8')}
break
if not d.exists():
continue
base = d.resolve()
fp = (base / cmd).resolve()
# Strict containment: must resolve directly inside one of the
# allowed command directories.
if fp.parent != base or not fp.is_file():
continue
if fp.stat().st_size > MAX_COMMAND_READ_BYTES:
return self.sendj({'command':cmd_filter,'error':'command_too_large'},413)
command_detail={'command':cmd_filter,'content':fp.read_text(encoding='utf-8')}
break
if not command_detail:
command_detail={'command':cmd_filter,'error':'not_found'}
return self.sendj(command_detail)
@@ -851,9 +1087,12 @@ return{vs,cmds,ar,cmd,cdet,tot,sok,sfail,runs,baseline,refreshMsg,loadCmd,selVer
def api_spec():
return {'version':'V1R1C0-chatgpt-actions','openapi':'/api/openapi.yaml','read':['GET /api/base','GET /api/ver','GET /api/diff','GET /api/patch','GET /api/model','GET /api/run','GET /getReleaseCandidate','GET /getApprovalEnvelope','GET /getReleaseSaga','GET /admin/ai-evolution'],'write':['POST /api/branch','POST /api/file','POST /api/commit','POST /api/patch','POST /api/tag','POST /api/rollback','POST /api/model','PUT /api/model-priority','POST /api/nl','POST /createReleaseCandidate','POST /submitOwnerApproval','POST /startDeployment'],'policy':{'bearer_required_for_write':True,'direct_main_push':False,'delete_tag':False,'owner_approval_required_for_v_level':True,'api_keys_in_git':False,'git_secret_field':'secret_ref','online_git_sync':True,'silent_release_execution_enabled':SILENT_RELEASE_EXECUTION_ENABLED,'silent_release_shadow_default':True}}
return {'version':'V1R1C0-chatgpt-actions','openapi':'/api/openapi.yaml','read':['GET /api/base','GET /api/ver','GET /api/diff','GET /api/repository/tree','GET /api/repository/search','GET /api/repository/diff-index','GET /api/repository/file','GET /api/patch','GET /api/model','GET /api/run','GET /getReleaseCandidate','GET /getApprovalEnvelope','GET /getReleaseSaga','GET /admin/ai-evolution'],'write':['POST /api/branch','POST /api/file','POST /api/commit','POST /api/patch','POST /api/tag','POST /api/rollback','POST /api/model','PUT /api/model-priority','POST /api/nl','POST /createReleaseCandidate','POST /submitOwnerApproval','POST /startDeployment'],'policy':{'bearer_required_for_write':True,'direct_main_push':False,'delete_tag':False,'owner_approval_required_for_v_level':True,'api_keys_in_git':False,'git_secret_field':'secret_ref','online_git_sync':True,'silent_release_execution_enabled':SILENT_RELEASE_EXECUTION_ENABLED,'silent_release_shadow_default':True}}
def main():
require_isolated_dev_profile(HOST)
validate_gitea_api_url(os.getenv('AIOS_GITOPS_GITEA_API_URL', ''))
print(f'AIOS ChatGPT GitOps adapter listening on {HOST}:{PORT}')
ThreadingHTTPServer((HOST,PORT), H).serve_forever()
if __name__ == '__main__': main()
@@ -0,0 +1,384 @@
"""ChatGPT GitOps Bridge v2 contract and governed release operations."""
from __future__ import annotations
from dataclasses import dataclass
import hashlib
from pathlib import Path
from typing import Any, Callable, Mapping
from aios_task_admission_bridge_contract import (
TASK_ADMISSION_OPERATION_OPTIONAL_FIELDS,
TASK_ADMISSION_OPERATION_REQUIRED_FIELDS,
TASK_ADMISSION_ROUTES,
)
BRIDGE_SCHEMA_VERSION = "aios.chatgpt-gitops-bridge.v2"
BRIDGE_OPERATIONS = (
"getRuntimeStatus",
"getGovernedWriteCapabilities",
"createGovernedTask",
"projectOnlineAgentExecution",
"acquireWriterLease",
"commitFileGoverned",
"commitFilesGoverned",
"getGitOpsJob",
"resumeGitOpsJob",
"reconcileGitOpsJob",
"createTagApprovalRequest",
"requestOrCreateTag",
"getTagPublicationStatus",
"buildProductionArtifact",
"executeApprovedProductionRelease",
"getReleaseConsistency",
"runReleaseAcceptance",
"getControlPlaneSnapshot",
"listRepositoryTree",
"searchRepositorySource",
"getDiffIndex",
"readRepositoryFile",
)
TASK_ADMISSION_OPERATIONS = tuple(TASK_ADMISSION_OPERATION_REQUIRED_FIELDS)
OPERATION_REQUIRED_FIELDS = {
"createGovernedTask": ("task_id", "repository", "base_oid", "target_branch", "risk", "description", "idempotency_key"),
"projectOnlineAgentExecution": ("task_id", "required_capabilities"),
"acquireWriterLease": ("schema", "task_id", "job_id", "repository", "branch_mode", "base_oid", "expected_branch_oid", "target_branch", "generation", "idempotency_key", "lease_seconds"),
"commitFileGoverned": ("task_id", "job_id", "repository", "expected_branch_oid", "target_branch", "generation", "idempotency_key", "path", "content_base64", "message"),
"commitFilesGoverned": ("task_id", "job_id", "repository", "expected_branch_oid", "target_branch", "generation", "idempotency_key", "files", "message"),
"getGitOpsJob": ("task_id", "job_id"),
"resumeGitOpsJob": ("task_id", "job_id", "expected_revision"),
"reconcileGitOpsJob": ("task_id", "job_id", "expected_revision"),
"createTagApprovalRequest": ("version", "target_oid", "evidence_closure_oid", "approval_plan_id", "owner_approval", "execute"),
"requestOrCreateTag": ("version", "target_oid", "evidence_closure_oid", "approval_plan_id", "execute"),
"getTagPublicationStatus": ("version",),
"buildProductionArtifact": ("version", "target_oid", "evidence_closure_oid", "approval_plan_id"),
"executeApprovedProductionRelease": ("version", "target_oid", "evidence_closure_oid", "approval_plan_id", "execute"),
"getReleaseConsistency": ("version", "target_oid", "evidence_closure_oid", "approval_plan_id"),
"runReleaseAcceptance": ("version", "target_oid", "evidence_closure_oid", "approval_plan_id"),
"getControlPlaneSnapshot": (),
**TASK_ADMISSION_OPERATION_REQUIRED_FIELDS,
"listRepositoryTree": ("repository", "ref", "limit"),
"searchRepositorySource": ("repository", "ref", "query", "max_matches", "max_bytes"),
"getDiffIndex": ("repository", "from", "to", "limit"),
"readRepositoryFile": ("repository", "ref", "path"),
}
OPERATION_OPTIONAL_FIELDS = {
"listRepositoryTree": ("prefix", "depth", "cursor"),
"searchRepositorySource": ("path_globs", "cursor"),
"getDiffIndex": ("path_prefix", "cursor"),
"readRepositoryFile": ("max_bytes",),
}
READ_ONLY_OPERATIONS = frozenset(
{
"getRuntimeStatus",
"getGovernedWriteCapabilities",
"getControlPlaneSnapshot",
"listRepositoryTree",
"searchRepositorySource",
"getDiffIndex",
"readRepositoryFile",
}
)
_OPERATION_PATHS = {
"createGovernedTask": "/v2/governed-task",
"projectOnlineAgentExecution": "/v2/execution-envelope",
"acquireWriterLease": "/v2/writer-lease",
"commitFileGoverned": "/v2/commit-file",
"commitFilesGoverned": "/v2/commit-files",
"getGitOpsJob": "/v2/job",
"resumeGitOpsJob": "/v2/job/resume",
"reconcileGitOpsJob": "/v2/job/reconcile",
"getTagPublicationStatus": "/v2/tag/status",
"getReleaseConsistency": "/v2/release/consistency",
"runReleaseAcceptance": "/v2/release/acceptance",
**{operation: path for path, operation in TASK_ADMISSION_ROUTES.items()},
"listRepositoryTree": "/v2/repository/tree",
"searchRepositorySource": "/v2/repository/search",
"getDiffIndex": "/v2/repository/diff-index",
"readRepositoryFile": "/v2/repository/file",
}
def _has_complete_task_admission_handlers(
handlers: Mapping[str, Callable[[Mapping[str, Any]], Any]] | None,
) -> bool:
return handlers is not None and all(
callable(handlers.get(operation)) for operation in TASK_ADMISSION_OPERATIONS
)
def operation_contracts(
handlers: Mapping[str, Callable[[Mapping[str, Any]], Any]] | None = None,
) -> dict[str, dict[str, object]]:
"""Return the runtime contract consumed by adapters and schema tooling."""
contracts = {
operation: {
"method": "GET" if operation in OPERATION_OPTIONAL_FIELDS else "POST",
"path": _OPERATION_PATHS.get(operation),
"required_fields": list(fields),
"optional_fields": list(OPERATION_OPTIONAL_FIELDS.get(operation, ())),
"read_only": operation in READ_ONLY_OPERATIONS,
}
for operation, fields in OPERATION_REQUIRED_FIELDS.items()
if operation not in TASK_ADMISSION_OPERATIONS
}
if _has_complete_task_admission_handlers(handlers):
contracts.update(
{
operation: {
"method": "GET" if operation == "getTaskAdmissionStatus" else "POST",
"path": _OPERATION_PATHS[operation],
"required_fields": list(
TASK_ADMISSION_OPERATION_REQUIRED_FIELDS[operation]
),
"optional_fields": list(
TASK_ADMISSION_OPERATION_OPTIONAL_FIELDS[operation]
),
}
for operation in TASK_ADMISSION_OPERATIONS
}
)
return contracts
def render_openapi_contract_fragment() -> str:
required = "\n".join(
f" - {field}"
for field in OPERATION_REQUIRED_FIELDS["commitFilesGoverned"]
)
return (
" /v2/commit-files:\n"
" post:\n"
" operationId: commitFilesGoverned\n"
" summary: Submit one atomic governed commit containing multiple files\n"
" security: [{taskPrincipalAuth: []}]\n"
" x-aios-task-principal: Bridge resolves a fresh server-side TaskPrincipal from the authoritative admitted+claimed Task and re-verifies its Owner epoch, then checks exact task/job/repository/branch/generation/capability binding; caller JSON cannot provide a principal and missing verification is denied. An explicitly injected legacy bearer verifier may still be used by a trusted host.\n"
" requestBody:\n"
" required: true\n"
" content:\n"
" application/json:\n"
" schema:\n"
" type: object\n"
" additionalProperties: false\n"
" required:\n"
f"{required}\n"
" properties:\n"
" task_id: {type: string}\n"
" job_id: {type: string, description: External durable Job identity for exact completed-replay reconciliation}\n"
" source_lease_epoch: {type: integer, minimum: 1}\n"
" repository: {type: string, example: aios/aios}\n"
" expected_branch_oid: {type: string, pattern: '^[0-9a-f]{40}$'}\n"
" target_branch: {type: string, pattern: '^agent/[A-Za-z0-9._/-]+$'}\n"
" generation: {type: integer, minimum: 1}\n"
" idempotency_key: {type: string}\n"
" files:\n"
" type: array\n"
" minItems: 1\n"
" items:\n"
" type: object\n"
" additionalProperties: false\n"
" required: [path, content_base64]\n"
" properties:\n"
" path: {type: string, minLength: 1, maxLength: 512}\n"
" content_base64: {type: string}\n"
" message: {type: string, minLength: 1, maxLength: 200}\n"
" responses:\n"
" '200': {description: Governed commit job and verified branch result}\n"
" '202': {description: Existing external Job alias requires authoritative reconciliation; no replacement Job is created}\n"
" '401': {description: No verified Task principal is available}\n"
" '403': {description: Verified Task principal lacks exact task, external Job, or capability scope}\n"
" '400': {description: Contract or policy rejection}\n"
" '409': {description: Branch CAS or idempotency conflict}\n"
" '423': {description: Narrow P0 Task-principal mutation gate is frozen; assertion is not consumed}\n"
)
class RuntimeDriftError(RuntimeError):
pass
class BridgeV2Error(RuntimeError):
pass
@dataclass(frozen=True, slots=True)
class ControlPlaneReleaseManifest:
control_plane_release_version: str
control_plane_target_oid: str
artifact_sha256: str
runtime_path: Path
bridge_schema_version: str
repository_identity: str
authoritative_git_dir: Path
deployment_state: str
trusted_runtime_identity: Mapping[str, object] | None = None
def as_dict(self) -> dict[str, object]:
return {
"control_plane_release_version": self.control_plane_release_version,
"control_plane_target_oid": self.control_plane_target_oid,
"artifact_sha256": self.artifact_sha256,
"runtime_path": str(self.runtime_path),
"bridge_schema_version": self.bridge_schema_version,
"repository_identity": self.repository_identity,
"authoritative_git_dir": str(self.authoritative_git_dir),
"deployment_state": self.deployment_state,
}
def runtime_identity(self) -> dict[str, object]:
"""Return only a host-injected, already trusted comparison result."""
identity = self.trusted_runtime_identity
required = {
"status", "declared_identity", "observed_identity", "manifest_digest",
"config_digest", "observed_at", "expires_at", "evidence_refs", "reason",
"mismatch_fields",
}
if (
not isinstance(identity, Mapping)
or identity.get("status") != "PASS"
or not required.issubset(identity)
or not isinstance(identity.get("declared_identity"), Mapping)
or not isinstance(identity.get("observed_identity"), Mapping)
or not isinstance(identity.get("evidence_refs"), list)
or not identity["evidence_refs"]
):
return {
"status": "UNKNOWN",
"declared_identity": None,
"observed_identity": None,
"observed_at": None,
"expires_at": None,
"evidence_refs": [],
"reason": "TRUSTED_OBSERVATION_UNAVAILABLE",
"mismatch_fields": [],
}
return dict(identity)
def validate(self, artifact: Path, *, observed_oid: str, approved_root: Path = Path("/opt/aios/gitops-control-plane")) -> dict[str, object]:
try:
runtime = self.runtime_path.resolve(strict=True)
root = approved_root.resolve(strict=True)
artifact_path = artifact.resolve(strict=True)
runtime.relative_to(root)
artifact_path.relative_to(runtime)
except (OSError, ValueError):
raise RuntimeDriftError("CONTROL_PLANE_RUNTIME_DRIFT") from None
mutable_markers = {"workspace", "workspaces", "worktree", "worktrees"}
if mutable_markers.intersection(part.lower() for part in runtime.parts):
raise RuntimeDriftError("CONTROL_PLANE_RUNTIME_DRIFT")
valid = (
len(self.control_plane_target_oid) == 40
and observed_oid == self.control_plane_target_oid
and len(self.artifact_sha256) == 64
and hashlib.sha256(artifact_path.read_bytes()).hexdigest() == self.artifact_sha256
and self.bridge_schema_version == BRIDGE_SCHEMA_VERSION
and self.repository_identity == "aios/aios"
and self.authoritative_git_dir == Path("/data/aios-gitops/repo.git")
and self.authoritative_git_dir.is_absolute()
and self.deployment_state == "STAGING"
)
if not valid:
raise RuntimeDriftError("CONTROL_PLANE_RUNTIME_DRIFT")
# Artifact and path validation proves only a local declaration. The
# composition root may inject the output of an already trusted runtime
# comparison, but this manifest must never promote its own checks to
# runtime-observation PASS.
identity = self.runtime_identity()
return {**self.as_dict(), "drift_status": identity["status"], "component_runtime_identity": identity}
class BridgeV2Adapter:
"""Thin operation adapter; all mutating handlers are injected seams."""
def __init__(self, *, runtime_status: Mapping[str, Any] | Callable[[], Mapping[str, Any]], handlers: Mapping[str, Callable[[Mapping[str, Any]], Any]] | None = None) -> None:
self._runtime_status_source = runtime_status if callable(runtime_status) else dict(runtime_status)
self.handlers = dict(handlers or {})
def _runtime_status(self) -> dict[str, Any]:
value = self._runtime_status_source() if callable(self._runtime_status_source) else self._runtime_status_source
if not isinstance(value, Mapping):
raise BridgeV2Error("RUNTIME_STATUS_INVALID")
return dict(value)
def getRuntimeStatus(self) -> dict[str, Any]:
return {"bridge_schema_version": BRIDGE_SCHEMA_VERSION, **self._runtime_status()}
def getGovernedWriteCapabilities(self) -> dict[str, Any]:
runtime_status = self._runtime_status()
governed_writes_enabled = runtime_status.get("governed_writes_enabled") is True
return {
"bridge_schema_version": BRIDGE_SCHEMA_VERSION,
"legacy_writes_enabled": False,
"governed_writes_enabled": governed_writes_enabled,
"governed_write_blockers": list(
runtime_status.get("governed_write_blockers") or []
),
"operations": [
*BRIDGE_OPERATIONS,
*(
TASK_ADMISSION_OPERATIONS
if _has_complete_task_admission_handlers(self.handlers)
else ()
),
],
"operation_contracts": operation_contracts(self.handlers),
"force_push": False,
"main_write": False,
"tag_write": False,
"baseline_write": False,
"production_write": False,
"approved_release_operations": True,
"approved_release_scope": {
"version": "V2R2C1",
"target_oid": "998d30304787e31e5ae607c0bd86b5edb2b0e5ad",
"evidence_closure_oid": "a8b906d1bc24088c19647e98de6a679268f6d0a2",
"approval_plan_id": "sha256:c0df52008ae7f64cec5ee2d28947aa7b892066c670ab8295e4549140b1a7500e",
},
}
@staticmethod
def legacy_write_disabled() -> dict[str, object]:
return {"error": "LEGACY_WRITE_DISABLED", "replacement": "createGovernedTask", "governed_writes_available": False}
def _call(self, operation: str, request: Mapping[str, Any]) -> Any:
required = OPERATION_REQUIRED_FIELDS[operation]
if any(key not in request for key in required):
raise BridgeV2Error("BRIDGE_REQUEST_FIELDS_MISSING")
if operation in OPERATION_OPTIONAL_FIELDS:
allowed = set(required) | set(OPERATION_OPTIONAL_FIELDS[operation])
if set(request) - allowed:
raise BridgeV2Error("BRIDGE_REQUEST_FIELDS_UNKNOWN")
handler = self.handlers.get(operation)
if handler is None:
raise BridgeV2Error("GOVERNED_HANDLER_UNAVAILABLE")
return handler(dict(request))
def createGovernedTask(self, request: Mapping[str, Any]) -> Any: return self._call("createGovernedTask", request)
def projectOnlineAgentExecution(self, request: Mapping[str, Any]) -> Any: return self._call("projectOnlineAgentExecution", request)
def acquireWriterLease(self, request: Mapping[str, Any]) -> Any: return self._call("acquireWriterLease", request)
def commitFileGoverned(self, request: Mapping[str, Any]) -> Any: return self._call("commitFileGoverned", request)
def commitFilesGoverned(self, request: Mapping[str, Any]) -> Any: return self._call("commitFilesGoverned", request)
def getGitOpsJob(self, request: Mapping[str, Any]) -> Any: return self._call("getGitOpsJob", request)
def resumeGitOpsJob(self, request: Mapping[str, Any]) -> Any: return self._call("resumeGitOpsJob", request)
def reconcileGitOpsJob(self, request: Mapping[str, Any]) -> Any: return self._call("reconcileGitOpsJob", request)
def createTagApprovalRequest(self, request: Mapping[str, Any]) -> Any: return self._call("createTagApprovalRequest", request)
def requestOrCreateTag(self, request: Mapping[str, Any]) -> Any: return self._call("requestOrCreateTag", request)
def getTagPublicationStatus(self, request: Mapping[str, Any]) -> Any: return self._call("getTagPublicationStatus", request)
def buildProductionArtifact(self, request: Mapping[str, Any]) -> Any: return self._call("buildProductionArtifact", request)
def executeApprovedProductionRelease(self, request: Mapping[str, Any]) -> Any: return self._call("executeApprovedProductionRelease", request)
def getReleaseConsistency(self, request: Mapping[str, Any]) -> Any: return self._call("getReleaseConsistency", request)
def runReleaseAcceptance(self, request: Mapping[str, Any]) -> Any: return self._call("runReleaseAcceptance", request)
def getTaskAdmissionEnvelope(self, request: Mapping[str, Any]) -> Any: return self._call("getTaskAdmissionEnvelope", request)
def submitTaskAdmissionAuthorization(self, request: Mapping[str, Any]) -> Any: return self._call("submitTaskAdmissionAuthorization", request)
def getTaskAdmissionStatus(self, request: Mapping[str, Any]) -> Any: return self._call("getTaskAdmissionStatus", request)
def listRepositoryTree(self, request: Mapping[str, Any]) -> Any: return self._call("listRepositoryTree", request)
def searchRepositorySource(self, request: Mapping[str, Any]) -> Any: return self._call("searchRepositorySource", request)
def getDiffIndex(self, request: Mapping[str, Any]) -> Any: return self._call("getDiffIndex", request)
def readRepositoryFile(self, request: Mapping[str, Any]) -> Any: return self._call("readRepositoryFile", request)
def getControlPlaneSnapshot(self, request: Mapping[str, Any] | None = None) -> Any: return self._call("getControlPlaneSnapshot", request or {})
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,615 @@
"""Governed continuation executor for exact existing task branches.
The executor composes the existing continuation lease and durable Job store.
All Git and Gitea side effects live behind injected narrow interfaces so the
state machine can prove Authority, Git-network, API-branch, and commit-object
agreement before it reports completion.
"""
from __future__ import annotations
import base64
from dataclasses import asdict, dataclass
from datetime import datetime, timezone
import hashlib
import json
import os
from pathlib import Path, PurePosixPath
import re
import subprocess
from typing import Callable, Mapping, Protocol
from urllib.error import HTTPError, URLError
from urllib.parse import quote, urlsplit
from urllib.request import Request, urlopen
from aios_authority_git_executor import AuthorityGitExecutor
from aios_continuation_lease import WriterLeaseRequestV2
from aios_gitops_job_controller import GitOpsJobController, JobState
from aios_gitops_reconciliation_core import ReconciliationAction, RemoteObservation, decide_reconciliation
from authorized_task_writer_provisioning import validate_writer_lease_request_v2
OID_RE = re.compile(r"^[0-9a-f]{40}$")
SECRET_PATTERNS = (
re.compile(rb"gh[pousr]_[A-Za-z0-9]{20,}"),
re.compile(rb"(?i)(?:token|password|secret|api[_-]?key)\s*[:=]\s*[\"'][^\"'\s]{12,}[\"']"),
re.compile(rb"-----BEGIN " + rb"(?:RSA |EC |OPENSSH )?PRIVATE KEY-----"),
)
class ContinuationExecutionError(RuntimeError):
"""Stable fail-closed continuation execution error."""
class DetachedWorkspace(Protocol):
def is_detached(self) -> bool: ...
def head_oid(self) -> str: ...
def commit_files(self, files: Mapping[str, bytes], message: str) -> str: ...
class AuthorityPort(Protocol):
def branch_exists(self, branch: str) -> bool: ...
def branch_head(self, branch: str) -> str: ...
def is_ancestor(self, older: str, newer: str) -> bool: ...
def compare_and_swap_branch(self, branch: str, expected: str, new: str) -> str: ...
class RemotePort(Protocol):
def repository_identity(self) -> str: ...
def push_exact(self, branch: str, expected: str, new: str, *, allow_absent: bool = False) -> None: ...
def read_git_ref(self, branch: str) -> str | None: ...
def read_api_branch(self, branch: str) -> str | None: ...
def commit_visible(self, oid: str) -> bool: ...
class CredentialProjection(Protocol):
def project(self) -> None: ...
def revoke(self) -> None: ...
def _run(argv: list[str], *, cwd: Path | None = None, environment: Mapping[str, str] | None = None) -> str:
result = subprocess.run(
argv,
cwd=cwd,
env=None if environment is None else dict(environment),
text=True,
capture_output=True,
check=False,
)
if result.returncode != 0:
raise ContinuationExecutionError("SUBPROCESS_FAILED")
return result.stdout.strip()
@dataclass(slots=True)
class AuthorityGitAdapter:
"""Branch-name adapter over the existing narrow AuthorityGitExecutor."""
executor: AuthorityGitExecutor
@staticmethod
def _ref(branch: str) -> str:
return f"refs/heads/{branch}"
def branch_exists(self, branch: str) -> bool:
return self.executor.read_ref(self._ref(branch)) is not None
def branch_head(self, branch: str) -> str:
value = self.executor.read_ref(self._ref(branch))
if value is None:
raise ContinuationExecutionError("EXISTING_BRANCH_MISSING")
return value
def is_ancestor(self, older: str, newer: str) -> bool:
return self.executor.ancestry_check(older, newer)
def compare_and_swap_branch(self, branch: str, expected: str, new: str) -> str:
return self.executor.advance_task_branch_CAS(self._ref(branch), expected, new)
@dataclass(slots=True)
class DetachedGitWorkspace:
path: Path
def __post_init__(self) -> None:
if not self.path.is_absolute() or self.path.is_symlink() or not self.path.is_dir():
raise ContinuationExecutionError("WORKTREE_PATH_INVALID")
top = Path(_run(["git", "rev-parse", "--show-toplevel"], cwd=self.path)).resolve()
if top != self.path.resolve():
raise ContinuationExecutionError("WORKTREE_PATH_INVALID")
def is_detached(self) -> bool:
result = subprocess.run(
["git", "symbolic-ref", "-q", "HEAD"],
cwd=self.path,
capture_output=True,
check=False,
)
return result.returncode == 1
def head_oid(self) -> str:
return _run(["git", "rev-parse", "HEAD"], cwd=self.path)
def _dirty_paths(self) -> set[str]:
command = subprocess.run(
["git", "status", "--porcelain=v1", "--untracked-files=all"],
cwd=self.path,
text=True,
capture_output=True,
check=False,
)
if command.returncode != 0:
raise ContinuationExecutionError("SUBPROCESS_FAILED")
output = command.stdout
result: set[str] = set()
for line in output.splitlines():
path = line[3:]
if " -> " in path:
path = path.split(" -> ", 1)[1]
result.add(path)
return result
def materialize_files(self, files: Mapping[str, bytes]) -> None:
dirty = self._dirty_paths()
expected_paths = set(files)
if dirty and dirty != expected_paths:
raise ContinuationExecutionError("WORKTREE_DIRTY_SCOPE_MISMATCH")
for relative, content in files.items():
target = (self.path / relative).resolve()
try:
target.relative_to(self.path.resolve())
except ValueError as exc:
raise ContinuationExecutionError("COMMIT_PATH_REJECTED") from exc
if target.is_symlink():
raise ContinuationExecutionError("COMMIT_PATH_REJECTED")
target.parent.mkdir(parents=True, exist_ok=True)
target.write_bytes(content)
def commit_files(self, files: Mapping[str, bytes], message: str) -> str:
expected_paths = set(files)
if self._dirty_paths() != expected_paths:
raise ContinuationExecutionError("WORKTREE_DIRTY_SCOPE_MISMATCH")
for relative, content in files.items():
target = (self.path / relative).resolve()
try:
target.relative_to(self.path.resolve())
except ValueError as exc:
raise ContinuationExecutionError("COMMIT_PATH_REJECTED") from exc
if not target.is_file() or target.is_symlink() or target.read_bytes() != content:
raise ContinuationExecutionError("AUTHORING_PAYLOAD_MISMATCH")
_run(["git", "add", "--", *sorted(expected_paths)], cwd=self.path)
staged = set(_run(["git", "diff", "--cached", "--name-only"], cwd=self.path).splitlines())
if staged != expected_paths:
raise ContinuationExecutionError("STAGED_SCOPE_MISMATCH")
environment = os.environ.copy()
environment.update(
{
"GIT_AUTHOR_NAME": "AIOS Governed Continuation",
"GIT_AUTHOR_EMAIL": "aios-governed-continuation@localhost",
"GIT_COMMITTER_NAME": "AIOS Governed Continuation",
"GIT_COMMITTER_EMAIL": "aios-governed-continuation@localhost",
}
)
_run(["git", "commit", "--no-gpg-sign", "-m", message, "--", *sorted(expected_paths)], cwd=self.path, environment=environment)
if not self.is_detached():
raise ContinuationExecutionError("WORKTREE_NOT_DETACHED")
return self.head_oid()
@dataclass(slots=True)
class ServerSideCredentialProjection:
ssh_key_path: Path
api_token_path: Path
active: bool = False
def project(self) -> None:
for path in (self.ssh_key_path, self.api_token_path):
if path.is_symlink() or not path.is_file() or path.stat().st_size < 32:
raise ContinuationExecutionError("CREDENTIAL_PROJECTION_MISSING")
self.active = True
def revoke(self) -> None:
self.active = False
@dataclass(slots=True)
class GiteaContinuationRemote:
workspace: Path
credential: ServerSideCredentialProjection
remote_name: str = "gitea"
repository: str = "aios/aios"
api_base: str = "https://g.cnnetmkt.top/git/api/v1"
expected_host: str = "g.cnnetmkt.top"
@staticmethod
def validate_remote_url(value: str, expected_host: str = "g.cnnetmkt.top") -> None:
if value.startswith(("/", "file:")) or value.endswith("/data/aios-gitops/repo.git"):
raise ContinuationExecutionError("GITEA_REMOTE_IDENTITY_INVALID")
if "://" in value:
parsed = urlsplit(value)
host, path = parsed.hostname, parsed.path
else:
location = value.split("@", 1)[-1]
host, separator, path = location.partition(":")
if not separator:
raise ContinuationExecutionError("GITEA_REMOTE_IDENTITY_INVALID")
path = "/" + path
if host != expected_host or path.rstrip("/") not in {"/aios/aios.git", "/git/aios/aios.git"}:
raise ContinuationExecutionError("GITEA_REMOTE_IDENTITY_INVALID")
def repository_identity(self) -> str:
url = _run(["git", "remote", "get-url", self.remote_name], cwd=self.workspace)
self.validate_remote_url(url, self.expected_host)
if self.repository != "aios/aios":
raise ContinuationExecutionError("REMOTE_REPOSITORY_IDENTITY_MISMATCH")
return self.repository
def _require_projected(self) -> None:
if not self.credential.active:
raise ContinuationExecutionError("CREDENTIAL_PROJECTION_MISSING")
def read_git_ref(self, branch: str) -> str | None:
self._require_projected()
output = _run(["git", "ls-remote", self.remote_name, f"refs/heads/{branch}"], cwd=self.workspace)
if not output:
return None
oid, ref = output.split(maxsplit=1)
if ref != f"refs/heads/{branch}" or not OID_RE.fullmatch(oid):
raise ContinuationExecutionError("GITEA_GIT_READBACK_INVALID")
return oid
def push_exact(self, branch: str, expected: str, new: str, *, allow_absent: bool = False) -> None:
self._require_projected()
self.repository_identity()
current = self.read_git_ref(branch)
if current != expected and not (allow_absent and current is None):
raise ContinuationExecutionError("GITEA_BRANCH_EXPECTATION_MISMATCH")
_run(["git", "push", self.remote_name, f"{new}:refs/heads/{branch}"], cwd=self.workspace)
def _api(self, path: str) -> object:
self._require_projected()
token = self.credential.api_token_path.read_text(encoding="utf-8").strip()
request = Request(
self.api_base.rstrip("/") + path,
headers={"Accept": "application/json", "Authorization": f"token {token}"},
)
try:
with urlopen(request, timeout=20) as response:
return json.loads(response.read().decode("utf-8"))
except HTTPError as exc:
if exc.code == 404:
return None
raise ContinuationExecutionError(f"GITEA_API_HTTP_{exc.code}") from exc
except (URLError, TimeoutError, OSError, json.JSONDecodeError) as exc:
raise ContinuationExecutionError("GITEA_API_UNAVAILABLE") from exc
def read_api_branch(self, branch: str) -> str | None:
owner, name = self.repository.split("/", 1)
page = 1
while page <= 100:
value = self._api(f"/repos/{quote(owner)}/{quote(name)}/branches?page={page}&limit=50")
if value is None:
return None
if not isinstance(value, list):
raise ContinuationExecutionError("GITEA_API_BRANCH_COLLECTION_INVALID")
for item in value:
if not isinstance(item, dict) or item.get("name") != branch:
continue
oid = item.get("commit", {}).get("id")
if not isinstance(oid, str) or not OID_RE.fullmatch(oid):
raise ContinuationExecutionError("GITEA_API_BRANCH_INVALID")
return oid
if len(value) < 50:
return None
page += 1
raise ContinuationExecutionError("GITEA_API_BRANCH_PAGINATION_LIMIT")
def commit_visible(self, oid: str) -> bool:
owner, name = self.repository.split("/", 1)
value = self._api(f"/repos/{quote(owner)}/{quote(name)}/git/commits/{oid}")
if not isinstance(value, dict):
return False
observed = value.get("sha") or value.get("id")
return observed == oid
@dataclass(frozen=True, slots=True)
class ContinuationPermissions:
create_target_branch: bool = False
force_push: bool = False
tag_write: bool = False
baseline_write: bool = False
production_write: bool = False
main_write: bool = False
def validate(self) -> None:
if any(asdict(self).values()):
raise ContinuationExecutionError("MUTATION_PERMISSION_DENIED")
@dataclass(frozen=True, slots=True)
class ContinuationRuntimeBinding:
request: WriterLeaseRequestV2
workspace_path: Path
lease_not_before: datetime
lease_expires_at: datetime
original_receipt_valid: bool
original_grant_consumed: bool
permissions: ContinuationPermissions
remote_branch_create_allowed: bool = False
external_job_id: str | None = None
worker_principal: str = "aios-autonomy-agent"
worker_lineage: str = "governed-continuation"
@dataclass(frozen=True, slots=True)
class ContinuationCommit:
idempotency_key: str
files: Mapping[str, bytes]
message: str
def payload(self) -> bytes:
if not self.idempotency_key or not self.message.strip() or not self.files:
raise ContinuationExecutionError("COMMIT_REQUEST_INVALID")
normalized: dict[str, str] = {}
for path, content in self.files.items():
pure = PurePosixPath(path)
if (
not isinstance(path, str)
or pure.is_absolute()
or ".." in pure.parts
or path.startswith((".git/", "baseline/", "production/"))
or not isinstance(content, bytes)
):
raise ContinuationExecutionError("COMMIT_PATH_REJECTED")
if any(pattern.search(content) for pattern in SECRET_PATTERNS):
raise ContinuationExecutionError("SECRET_LIKE_PAYLOAD_REJECTED")
normalized[path] = base64.b64encode(content).decode("ascii")
return json.dumps(
{"files_base64": normalized, "message": self.message},
sort_keys=True,
separators=(",", ":"),
).encode("utf-8")
class ContinuationExecutor:
def __init__(
self,
controller: GitOpsJobController,
authority: AuthorityPort,
workspace: DetachedWorkspace,
remote: RemotePort,
credential: CredentialProjection,
*,
now: Callable[[], datetime] = lambda: datetime.now(timezone.utc),
) -> None:
self.controller = controller
self.authority = authority
self.workspace = workspace
self.remote = remote
self.credential = credential
self.now = now
self._observed_generations: dict[str, int] = {}
def observe_generation(self, branch: str, generation: int) -> None:
self._observed_generations[branch] = max(generation, self._observed_generations.get(branch, 0))
def _max_generation(self, branch: str) -> int:
observed = self._observed_generations.get(branch, 0)
with self.controller.store._connect() as connection:
row = connection.execute(
"SELECT MAX(generation) AS generation FROM branch_leases WHERE branch=?",
(branch,),
).fetchone()
durable = int(row["generation"] or 0)
return max(observed, durable)
@staticmethod
def _request_mapping(request: WriterLeaseRequestV2) -> dict[str, object]:
return {
"schema": WriterLeaseRequestV2.schema,
"task_id": request.task_id,
"repository": request.repository,
"branch_mode": request.branch_mode,
"base_oid": request.base_oid,
"expected_branch_oid": request.expected_branch_oid,
"target_branch": request.target_branch,
"generation": request.generation,
"lease_seconds": request.lease_seconds,
}
def _validate_preflight(self, binding: ContinuationRuntimeBinding) -> None:
request = binding.request
normalized = validate_writer_lease_request_v2(self._request_mapping(request), self.authority)
if normalized != {**self._request_mapping(request), "create_target_branch": False, "worktree_mode": "detached_at_expected_head", "cas_ref_update_required": True}:
raise ContinuationExecutionError("CONTINUATION_NORMALIZATION_INVALID")
if not binding.original_receipt_valid or not binding.original_grant_consumed:
raise ContinuationExecutionError("ORIGINAL_GRANT_LINEAGE_INVALID")
binding.permissions.validate()
now = self.now()
if now < binding.lease_not_before:
raise ContinuationExecutionError("LEASE_NOT_ACTIVE")
if now >= binding.lease_expires_at:
raise ContinuationExecutionError("LEASE_EXPIRED")
if request.generation <= self._max_generation(request.target_branch):
raise ContinuationExecutionError("GENERATION_NOT_MONOTONIC")
if not self.workspace.is_detached():
raise ContinuationExecutionError("WORKTREE_NOT_DETACHED")
if self.workspace.head_oid() != request.expected_branch_oid:
raise ContinuationExecutionError("WORKTREE_HEAD_MISMATCH")
if self.remote.repository_identity() != request.repository:
raise ContinuationExecutionError("REMOTE_REPOSITORY_IDENTITY_MISMATCH")
def _job_request(self, binding: ContinuationRuntimeBinding, commit: ContinuationCommit, payload: bytes) -> dict[str, object]:
request = binding.request
return {
"idempotency_key": commit.idempotency_key,
"campaign_id": request.task_id,
"task_id": request.task_id,
"repository": request.repository,
"operation": "CONTINUATION_COMMIT_FILES",
"branch": request.target_branch,
"expected_branch_oid": request.expected_branch_oid,
"base_oid": request.base_oid,
"branch_mode": request.branch_mode,
"payload_digest": hashlib.sha256(payload).hexdigest(),
"lineage_anchor_oid": request.base_oid,
"generation": request.generation,
"remote_branch_create_allowed": binding.remote_branch_create_allowed,
"external_job_id": binding.external_job_id,
"files_digest": hashlib.sha256(
json.dumps(
{path: hashlib.sha256(content).hexdigest() for path, content in sorted(commit.files.items())},
sort_keys=True, separators=(",", ":"),
).encode("utf-8")
).hexdigest(),
"message_digest": hashlib.sha256(commit.message.encode("utf-8")).hexdigest(),
"worker_principal": binding.worker_principal,
"worker_lineage": binding.worker_lineage,
}
def execute(self, binding: ContinuationRuntimeBinding, commit: ContinuationCommit) -> dict[str, object]:
payload = commit.payload()
submitted = self.controller.submit(self._job_request(binding, commit, payload), payload=payload)
current = self.controller.store.read(submitted.job_id)
if JobState(current["state"]) == JobState.COMPLETED:
return self.controller.store._receipt(current, duplicate=True).as_dict()
self._validate_preflight(binding)
request = binding.request
projected = False
try:
self.credential.project()
projected = True
row = self.controller.store.read(submitted.job_id)
if JobState(row["state"]) == JobState.REQUESTED:
self.controller.advance(
submitted.job_id,
JobState.AUTHORIZED,
side_effect_state="AUTHORIZED",
receipt={"task_id": request.task_id, "original_grant_consumed": True},
)
row = self.controller.store.read(submitted.job_id)
if JobState(row["state"]) == JobState.AUTHORIZED:
self.controller.lease_job(
submitted.job_id,
lease_id=f"lease-{submitted.job_id}",
lineage_anchor_oid=request.base_oid,
current_head_oid=request.expected_branch_oid,
generation=request.generation,
lease_expires_at=binding.lease_expires_at.astimezone(timezone.utc).isoformat(),
expected_revision=int(row["revision"]),
)
self.controller.advance(submitted.job_id, JobState.WORKTREE_READY, side_effect_state="WORKTREE_READY")
self.controller.advance(submitted.job_id, JobState.EXECUTING, side_effect_state="IN_PROGRESS")
new_oid = self.workspace.commit_files(commit.files, commit.message)
if not OID_RE.fullmatch(new_oid) or not self.authority.is_ancestor(request.expected_branch_oid, new_oid):
raise ContinuationExecutionError("NON_FAST_FORWARD_COMMIT")
authority_oid = self.authority.compare_and_swap_branch(request.target_branch, request.expected_branch_oid, new_oid)
if authority_oid != new_oid:
raise ContinuationExecutionError("AUTHORITY_CAS_READBACK_MISMATCH")
self.controller.advance(
submitted.job_id,
JobState.LOCAL_VERIFIED,
side_effect_state="LOCAL_VERIFIED",
receipt={"authority_oid": authority_oid, "force_push": False},
)
try:
self.controller.advance(submitted.job_id, JobState.REMOTE_SYNCING, side_effect_state="IN_PROGRESS")
self.remote.push_exact(request.target_branch, request.expected_branch_oid, new_oid, allow_absent=binding.remote_branch_create_allowed)
except Exception:
return self._remote_pending(submitted.job_id, authority_oid, new_oid)
return self._finish_remote_verification(submitted.job_id, authority_oid, new_oid)
finally:
if projected:
self.credential.revoke()
def _observe_remote(self, branch: str, oid: str) -> tuple[str | None, str | None, bool]:
return self.remote.read_git_ref(branch), self.remote.read_api_branch(branch), self.remote.commit_visible(oid)
def _remote_pending(self, job_id: str, authority_oid: str, new_oid: str) -> dict[str, object]:
receipt = {
"authority_oid": authority_oid,
"commit_oid": new_oid,
"desired_remote_oid": new_oid,
"remote_verified": False,
"force_push": False,
}
result = self.controller.advance(
job_id,
JobState.RECONCILIATION_REQUIRED,
side_effect_state="REMOTE_PUSH_PENDING",
receipt=receipt,
)
return result.as_dict()
def _finish_remote_verification(self, job_id: str, authority_oid: str, new_oid: str) -> dict[str, object]:
row = self.controller.store.read(job_id)
branch = row["branch"]
request = json.loads(row["request_json"])["binding"]
git_oid, api_oid, visible = self._observe_remote(branch, new_oid)
decision = decide_reconciliation(RemoteObservation("aios/aios", branch, new_oid, authority_oid, git_oid, api_oid, visible, datetime.now(timezone.utc).isoformat(), request.get("expected_branch_oid")))
if decision.action is not ReconciliationAction.MARK_REMOTE_VERIFIED:
return self._remote_pending(job_id, authority_oid, new_oid)
receipt = {
"authority_oid": authority_oid,
"commit_oid": new_oid,
"git_remote_oid": git_oid,
"api_branch_oid": api_oid,
"commit_visible": True,
"remote_verified": True,
"reconciliation_decision": decision.action.value,
"force_push": False,
}
self.controller.advance(job_id, JobState.REMOTE_VERIFIED, side_effect_state="REMOTE_VERIFIED", receipt=receipt)
return self.controller.advance(job_id, JobState.COMPLETED, side_effect_state="REMOTE_VERIFIED", receipt=receipt).as_dict()
def reconcile(self, job_id: str) -> dict[str, object]:
row = self.controller.store.read(job_id)
state = JobState(row["state"])
if state == JobState.COMPLETED:
return self.controller.store._receipt(row, duplicate=True).as_dict()
if state != JobState.RECONCILIATION_REQUIRED:
raise ContinuationExecutionError("JOB_NOT_RECONCILABLE")
request = json.loads(row["request_json"])["binding"]
receipt = json.loads(row["receipt_json"])
new_oid = receipt["desired_remote_oid"]
authority_oid = self.authority.branch_head(row["branch"])
try:
generation = int(request.get("generation") or 0)
except (TypeError, ValueError) as exc:
raise ContinuationExecutionError("DURABLE_BINDING_GENERATION_INVALID") from exc
if generation > 0:
with self.controller.store._connect() as connection:
earlier = connection.execute(
"SELECT 1 FROM jobs WHERE branch=? AND state=? "
"AND CAST(json_extract(request_json, '$.binding.generation') AS INTEGER) < ? LIMIT 1",
(row["branch"], JobState.RECONCILIATION_REQUIRED.value, generation),
).fetchone()
if earlier is not None:
raise ContinuationExecutionError("EARLIER_RECONCILIATION_REQUIRED")
self.credential.project()
try:
git_oid, api_oid, visible = self._observe_remote(row["branch"], new_oid)
if authority_oid == git_oid == api_oid == new_oid and visible:
self.controller.advance(job_id, JobState.RECONCILING, side_effect_state="OBSERVATION_FIRST")
return self._finish_remote_verification(job_id, new_oid, new_oid)
expected_oid = request["expected_branch_oid"]
if (
self.authority.is_ancestor(new_oid, authority_oid)
and git_oid is not None
and git_oid == api_oid
and self.authority.is_ancestor(git_oid, new_oid)
):
self.controller.advance(job_id, JobState.RECONCILING, side_effect_state="OBSERVATION_FIRST_CHAIN")
self.remote.push_exact(row["branch"], git_oid, new_oid, allow_absent=bool(request.get("remote_branch_create_allowed")) or request.get("branch_mode") == "CREATE_NEW")
return self._finish_remote_verification(job_id, new_oid, new_oid)
allow_remote_create = bool(request.get("remote_branch_create_allowed")) or request.get("branch_mode") == "CREATE_NEW" or (git_oid is None and api_oid is None)
remote_matches_expected = git_oid == api_oid == expected_oid
remote_absent_for_create = git_oid is None and api_oid is None
if authority_oid == new_oid and (remote_matches_expected or remote_absent_for_create):
self.controller.advance(job_id, JobState.RECONCILING, side_effect_state="OBSERVATION_FIRST_RETRY")
self.remote.push_exact(row["branch"], expected_oid, new_oid, allow_absent=allow_remote_create)
return self._finish_remote_verification(job_id, new_oid, new_oid)
return self.controller.store._receipt(row, duplicate=True).as_dict()
finally:
self.credential.revoke()
@@ -0,0 +1,154 @@
"""Campaign-aware continuation leases for an existing Git branch.
This module is deliberately transport-neutral. It validates branch lineage
and hands ref mutation to an injected compare-and-swap broker; it does not run
Git, inspect credentials, or create a branch itself.
"""
from __future__ import annotations
from dataclasses import dataclass, replace
from datetime import datetime, timedelta, timezone
import re
from typing import Protocol
OID_RE = re.compile(r"^[0-9a-f]{40}$")
TARGET_BRANCH = "agent/autonomous-release-campaign-v1-20260807"
LINEAGE_ANCHOR = "69b45e81dcd2c3707c367003b08c55fd361faae8"
CAMPAIGN_HEAD = "bba675fab3b7e0c5fba7066720252b3c91b9f20d"
class ContinuationError(RuntimeError):
"""Stable fail-closed error for lease or branch binding violations."""
class BranchProbe(Protocol):
def branch_exists(self, branch: str) -> bool: ...
def branch_head(self, branch: str) -> str: ...
def is_ancestor(self, older: str, newer: str) -> bool: ...
class RefCAS(Protocol):
def compare_and_swap_branch(self, branch: str, expected: str, new: str) -> str: ...
def _oid(value: str, field: str) -> str:
if not isinstance(value, str) or not OID_RE.fullmatch(value):
raise ContinuationError(f"{field}_invalid")
return value
@dataclass(frozen=True, slots=True)
class WriterLeaseRequestV2:
task_id: str
repository: str
branch_mode: str
base_oid: str
expected_branch_oid: str
target_branch: str
generation: int
lease_seconds: int = 900
schema = "aios.task-writer-lease.v2"
accepted_schemas = frozenset({"aios.task-writer-lease.v2", "aios.task-writer-lease-request.v2"})
def validate(self, probe: BranchProbe) -> None:
if not self.task_id or self.repository != "aios/aios":
raise ContinuationError("request_identity_invalid")
if self.branch_mode not in {"CREATE_NEW", "CONTINUE_EXISTING"}:
raise ContinuationError("branch_mode_invalid")
_oid(self.base_oid, "base_oid")
_oid(self.expected_branch_oid, "expected_branch_oid")
if self.target_branch in {"main", "master"} or self.target_branch.startswith(("refs/", "tags/")):
raise ContinuationError("protected_branch_rejected")
if not isinstance(self.generation, int) or isinstance(self.generation, bool) or self.generation < 1:
raise ContinuationError("generation_invalid")
if not isinstance(self.lease_seconds, int) or not 1 <= self.lease_seconds <= 86_400:
raise ContinuationError("lease_duration_invalid")
if self.branch_mode == "CONTINUE_EXISTING":
if not probe.branch_exists(self.target_branch):
raise ContinuationError("EXISTING_BRANCH_MISSING")
actual = _oid(probe.branch_head(self.target_branch), "actual_branch_oid")
if actual != self.expected_branch_oid:
raise ContinuationError("EXISTING_BRANCH_OID_CONFLICT")
if not probe.is_ancestor(self.base_oid, actual):
raise ContinuationError("BASE_NOT_ANCESTOR_OF_BRANCH")
elif hasattr(probe, "commit_exists") and not probe.commit_exists(self.base_oid):
raise ContinuationError("BASE_COMMIT_NOT_FOUND")
elif self.branch_mode == "CREATE_NEW" and probe.branch_exists(self.target_branch):
raise ContinuationError("TARGET_BRANCH_ALREADY_EXISTS")
elif self.branch_mode == "CREATE_NEW" and self.base_oid != self.expected_branch_oid:
raise ContinuationError("CREATE_NEW_BASE_EXPECTATION_MISMATCH")
@dataclass(frozen=True, slots=True)
class CampaignWriterLease:
task_id: str
repository: str
branch: str
lineage_anchor_oid: str
current_head_oid: str
generation: int
lease_expires_at: datetime
append_only: bool = True
renewable: bool = True
multi_commit: bool = True
recoverable: bool = True
state: str = "ACTIVE"
branch_bound: bool = True
def __post_init__(self) -> None:
_oid(self.lineage_anchor_oid, "lineage_anchor_oid")
_oid(self.current_head_oid, "current_head_oid")
if not all((self.append_only, self.renewable, self.multi_commit, self.recoverable, self.branch_bound)):
raise ContinuationError("lease_capability_downgrade")
class ContinuationLeaseManager:
"""State machine for a multi-commit lease with CAS-only ref updates."""
def __init__(self, probe: BranchProbe, refs: RefCAS, *, now=datetime.now) -> None:
self.probe = probe
self.refs = refs
self.now = now
def open(self, request: WriterLeaseRequestV2) -> CampaignWriterLease:
request.validate(self.probe)
if request.branch_mode != "CONTINUE_EXISTING":
raise ContinuationError("CREATE_NEW_REQUIRES_SEPARATE_PROVISIONER")
return CampaignWriterLease(
task_id=request.task_id,
repository=request.repository,
branch=request.target_branch,
lineage_anchor_oid=request.base_oid,
current_head_oid=request.expected_branch_oid,
generation=request.generation,
lease_expires_at=self.now(timezone.utc) + timedelta(seconds=request.lease_seconds),
)
def renew(self, lease: CampaignWriterLease, seconds: int) -> CampaignWriterLease:
if lease.state != "ACTIVE" or not lease.renewable or seconds < 1:
raise ContinuationError("lease_not_renewable")
return replace(lease, lease_expires_at=self.now(timezone.utc) + timedelta(seconds=seconds))
def append_commit(self, lease: CampaignWriterLease, new_head_oid: str) -> CampaignWriterLease:
if lease.state != "ACTIVE":
raise ContinuationError("lease_not_active")
_oid(new_head_oid, "new_head_oid")
if not self.probe.is_ancestor(lease.current_head_oid, new_head_oid):
raise ContinuationError("NON_FAST_FORWARD_COMMIT")
try:
actual = self.refs.compare_and_swap_branch(
lease.branch, lease.current_head_oid, new_head_oid
)
except Exception as exc:
raise ContinuationError("BRANCH_LEASE_STALE") from exc
if actual != new_head_oid:
raise ContinuationError("BRANCH_LEASE_STALE")
return replace(lease, current_head_oid=new_head_oid)
def supersede(self, lease: CampaignWriterLease) -> CampaignWriterLease:
if lease.state not in {"ACTIVE", "RECOVERABLE_FAILURE", "RECONCILIATION_REQUIRED"}:
raise ContinuationError("lease_not_supersedable")
return replace(lease, generation=lease.generation + 1, state="SUPERSEDED")
@@ -0,0 +1,48 @@
"""Separate read, write, and release plane readiness evaluation."""
from __future__ import annotations
from dataclasses import dataclass
from typing import Mapping
@dataclass(frozen=True, slots=True)
class RuntimeStatus:
read_plane: Mapping[str, bool]
write_plane: Mapping[str, bool]
release_plane: Mapping[str, bool]
required_facts: Mapping[str, frozenset[str]] | None = None
def _readiness_reason(self) -> str:
if self.required_facts is None:
return "REQUIRED_FACT_INVENTORY_MISSING"
expected_groups = {"READ_PLANE", "WRITE_PLANE", "RELEASE_PLANE"}
if set(self.required_facts) != expected_groups:
return "REQUIRED_FACT_INVENTORY_INVALID"
planes = {
"READ_PLANE": self.read_plane,
"WRITE_PLANE": self.write_plane,
"RELEASE_PLANE": self.release_plane,
}
for group, facts in self.required_facts.items():
if not isinstance(facts, frozenset) or not facts or any(not isinstance(name, str) or not name for name in facts):
return "REQUIRED_FACT_INVENTORY_INVALID"
plane = planes[group]
if set(plane) != set(facts):
return "REQUIRED_FACT_INVENTORY_MISMATCH"
if any(type(value) is not bool or value is not True for value in plane.values()):
return "REQUIRED_FACT_NOT_TRUE"
return "READY"
@property
def release_control_plane_ready(self) -> bool:
return self._readiness_reason() == "READY"
def as_dict(self) -> dict[str, object]:
return {
"READ_PLANE": dict(self.read_plane),
"WRITE_PLANE": dict(self.write_plane),
"RELEASE_PLANE": dict(self.release_plane),
"release_control_plane_ready": self.release_control_plane_ready,
"readiness_reason": self._readiness_reason(),
}
@@ -0,0 +1,58 @@
"""Workspace-independent authority/network/Gitea ref sentinel."""
from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
import subprocess
from typing import Callable, Mapping
class SentinelError(RuntimeError):
pass
@dataclass(frozen=True, slots=True)
class SentinelInputs:
authority_git_dir: Path
gitea_remote: str
gitea_api: str
ref: str
expected_oid: str
@dataclass(frozen=True, slots=True)
class SentinelResult:
authority_oid: str | None
network_oid: str | None
gitea_oid: str | None
equal: bool
error: str | None = None
def authority_oid(inputs: SentinelInputs) -> str:
result = subprocess.run(
["git", "--git-dir", str(inputs.authority_git_dir), "rev-parse", inputs.ref],
text=True,
capture_output=True,
check=False,
)
if result.returncode != 0:
raise SentinelError("AUTHORITY_REF_UNAVAILABLE")
return result.stdout.strip()
def evaluate(
inputs: SentinelInputs,
*,
network_reader: Callable[[str, str], str | None],
gitea_reader: Callable[[str, str], str | None],
) -> SentinelResult:
try:
local = authority_oid(inputs)
network = network_reader(inputs.gitea_remote, inputs.ref)
gitea = gitea_reader(inputs.gitea_api, inputs.ref)
equal = local == network == gitea == inputs.expected_oid
return SentinelResult(local, network, gitea, equal, None if equal else "OID_MISMATCH")
except SentinelError as exc:
return SentinelResult(None, None, None, False, str(exc))
@@ -0,0 +1,30 @@
"""Non-sensitive control-plane snapshot assembly."""
from __future__ import annotations
from typing import Any, Mapping
def _copy(value: Mapping[str, Any] | None, keys: tuple[str, ...]) -> dict[str, Any]:
value = value or {}
return {key: value.get(key) for key in keys}
def build_control_plane_snapshot(*, target_version: str, runtime: Mapping[str, Any] | None = None, repository: Mapping[str, Any] | None = None, production: Mapping[str, Any] | None = None, task: Mapping[str, Any] | None = None, capabilities: Mapping[str, Any] | None = None, credential_broker: Mapping[str, Any] | None = None) -> dict[str, Any]:
task_snapshot = _copy(task, ("task_id", "branch", "authority_branch_oid", "gitea_git_oid", "gitea_api_oid", "commit_visible", "generation", "reconciliation_state"))
for key in ("external_job_id", "commit_oid", "current_head_oid", "completed_replay_reconciliation_id", "receipt_authority_oid"):
if task is not None and key in task:
task_snapshot[key] = task[key]
return {
"version": {"target_release": target_version},
"runtime": _copy(runtime, ("bridge_schema_version", "control_plane_release_version", "runtime_release_commit", "runtime_path", "artifact_sha256", "drift_status")),
"repository": _copy(repository, ("identity", "authority_git_dir", "authority_main_oid")),
"production": _copy(production, ("baseline_version", "target_oid", "deployment_state")),
"task": task_snapshot,
"capabilities": _copy(capabilities, ("legacy_writes_enabled", "governed_writes_enabled", "force_push", "main_write", "tag_write", "baseline_write", "production_write")),
"credential_broker": _copy(credential_broker, ("status", "provider")),
"secret_material_exposed": False,
}
def getControlPlaneSnapshot(**kwargs: Any) -> dict[str, Any]:
return build_control_plane_snapshot(**kwargs)
@@ -0,0 +1,478 @@
"""Minimal convergence contracts for AIOS V2R2C2.
This module deliberately contains no external side effects. It provides the shared
data contracts and pure evaluation rules used by domain controllers. Domain
controllers remain authoritative for their own final states.
"""
from __future__ import annotations
from dataclasses import asdict, dataclass, field
from datetime import datetime, timezone
from enum import Enum
import hashlib
import json
from typing import Any, Iterable, Mapping, Sequence
class ConvergenceContractError(ValueError):
"""Raised when an invalid convergence contract is constructed."""
class EvidenceState(str, Enum):
PASS = "PASS"
FAIL = "FAIL"
UNKNOWN = "UNKNOWN"
BLOCKED = "BLOCKED"
NOT_APPLICABLE = "NOT_APPLICABLE"
class AcceptanceDecision(str, Enum):
ACCEPT = "ACCEPT"
REJECT = "REJECT"
BLOCKED = "BLOCKED"
UNKNOWN = "UNKNOWN"
class ObservedHealth(str, Enum):
HEALTHY = "HEALTHY"
DEGRADED = "DEGRADED"
FAILED = "FAILED"
UNKNOWN = "UNKNOWN"
class ReconciliationState(str, Enum):
PENDING = "PENDING"
OBSERVING = "OBSERVING"
DIFF_FOUND = "DIFF_FOUND"
APPLYING = "APPLYING"
VERIFYING = "VERIFYING"
CONVERGED = "CONVERGED"
DEGRADED = "DEGRADED"
BLOCKED = "BLOCKED"
class ContinuationStopReason(str, Enum):
CONTEXT_BOUNDARY = "context_boundary"
WORKER_RESTART = "worker_restart"
HANDOFF = "handoff"
AUTHORITY_GATE = "authority_gate"
WAIT_EXTERNAL = "wait_external"
def _utc_now() -> str:
return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
def _canonical_json(value: Mapping[str, Any]) -> bytes:
return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode("utf-8")
def _sha256(value: Mapping[str, Any]) -> str:
return "sha256:" + hashlib.sha256(_canonical_json(value)).hexdigest()
def _nonempty(value: object, field_name: str) -> str:
text = str(value or "").strip()
if not text:
raise ConvergenceContractError(f"{field_name}_required")
return text
def _generation(value: object, field_name: str = "generation") -> int:
try:
parsed = int(value)
except (TypeError, ValueError) as exc:
raise ConvergenceContractError(f"{field_name}_invalid") from exc
if parsed < 1:
raise ConvergenceContractError(f"{field_name}_invalid")
return parsed
@dataclass(frozen=True, slots=True)
class StateOwnership:
object_type: str
authoritative_service: str
authoritative_store: str
final_writer: str | None = None
projections: tuple[str, ...] = ()
schema_version: str = "aios.state-ownership.v1"
def __post_init__(self) -> None:
_nonempty(self.object_type, "object_type")
_nonempty(self.authoritative_service, "authoritative_service")
_nonempty(self.authoritative_store, "authoritative_store")
if self.final_writer is not None:
_nonempty(self.final_writer, "final_writer")
def as_dict(self) -> dict[str, Any]:
return asdict(self)
DEFAULT_STATE_OWNERSHIP: tuple[StateOwnership, ...] = (
StateOwnership(
object_type="goal",
authoritative_service="goal-kernel",
authoritative_store="goal-store",
final_writer="goal-kernel",
projections=("admin-web", "context-pack"),
),
StateOwnership(
object_type="task_run",
authoritative_service="task-plane-18940",
authoritative_store="durable-task-store",
final_writer="task-plane-18940",
projections=("admin-web", "context-pack"),
),
StateOwnership(
object_type="release",
authoritative_service="release-controller",
authoritative_store="release-registry",
final_writer="release-controller",
projections=("baseline-authority", "admin-web"),
),
StateOwnership(
object_type="git_branch",
authoritative_service="authority-git-executor",
authoritative_store="authoritative-git",
final_writer="authority-git-executor",
projections=("gitea",),
),
StateOwnership(
object_type="external_projection",
authoritative_service="projection-controller",
authoritative_store="projection-store",
final_writer="projection-controller",
projections=("openapi", "gpt-action", "nginx"),
),
)
def ownership_index(
ownership: Iterable[StateOwnership] = DEFAULT_STATE_OWNERSHIP,
) -> dict[str, StateOwnership]:
result: dict[str, StateOwnership] = {}
for row in ownership:
if row.object_type in result:
raise ConvergenceContractError(f"duplicate_state_owner:{row.object_type}")
result[row.object_type] = row
return result
@dataclass(frozen=True, slots=True)
class AcceptanceCriterion:
criterion_id: str
required: bool
state: EvidenceState
verifier_id: str
evidence_refs: tuple[str, ...] = ()
def __post_init__(self) -> None:
_nonempty(self.criterion_id, "criterion_id")
_nonempty(self.verifier_id, "verifier_id")
@dataclass(frozen=True, slots=True)
class AcceptanceInvariant:
invariant_id: str
state: EvidenceState
evidence_refs: tuple[str, ...] = ()
def __post_init__(self) -> None:
_nonempty(self.invariant_id, "invariant_id")
@dataclass(frozen=True, slots=True)
class AcceptanceVector:
subject_type: str
subject_id: str
subject_revision: int
criteria: tuple[AcceptanceCriterion, ...]
invariants: tuple[AcceptanceInvariant, ...] = ()
unresolved_side_effects: tuple[str, ...] = ()
unresolved_blockers: tuple[str, ...] = ()
runtime_proofs: tuple[str, ...] = ()
schema: str = "aios.acceptance-vector.v1"
def __post_init__(self) -> None:
_nonempty(self.subject_type, "subject_type")
_nonempty(self.subject_id, "subject_id")
_generation(self.subject_revision, "subject_revision")
ids = [row.criterion_id for row in self.criteria]
if len(ids) != len(set(ids)):
raise ConvergenceContractError("duplicate_acceptance_criterion")
invariant_ids = [row.invariant_id for row in self.invariants]
if len(invariant_ids) != len(set(invariant_ids)):
raise ConvergenceContractError("duplicate_acceptance_invariant")
def as_dict(self) -> dict[str, Any]:
value = asdict(self)
for row in value["criteria"]:
row["state"] = row["state"].value
for row in value["invariants"]:
row["state"] = row["state"].value
return value
@dataclass(frozen=True, slots=True)
class AcceptanceResult:
decision: AcceptanceDecision
reasons: tuple[str, ...]
vector_hash: str
def as_dict(self) -> dict[str, Any]:
return {
"decision": self.decision.value,
"reasons": list(self.reasons),
"vector_hash": self.vector_hash,
}
def evaluate_acceptance(vector: AcceptanceVector) -> AcceptanceResult:
"""Pure fail-closed acceptance evaluator.
The evaluator never writes a domain final state. The domain owner consumes this
result and remains the only final writer for Goal/Release/Product state.
"""
reasons: list[str] = []
required = [row for row in vector.criteria if row.required]
for row in required:
if row.state == EvidenceState.FAIL:
reasons.append(f"criterion_failed:{row.criterion_id}")
elif row.state == EvidenceState.BLOCKED:
reasons.append(f"criterion_blocked:{row.criterion_id}")
elif row.state == EvidenceState.UNKNOWN:
reasons.append(f"criterion_unknown:{row.criterion_id}")
for row in vector.invariants:
if row.state == EvidenceState.FAIL:
reasons.append(f"invariant_failed:{row.invariant_id}")
elif row.state in {EvidenceState.UNKNOWN, EvidenceState.BLOCKED}:
reasons.append(f"invariant_unknown:{row.invariant_id}")
if vector.unresolved_side_effects:
reasons.append("unresolved_side_effects")
if vector.unresolved_blockers:
reasons.append("unresolved_blockers")
digest = _sha256(vector.as_dict())
if any(reason.startswith(("criterion_failed:", "invariant_failed:")) for reason in reasons):
decision = AcceptanceDecision.REJECT
elif any(reason.startswith("criterion_blocked:") for reason in reasons) or vector.unresolved_blockers:
decision = AcceptanceDecision.BLOCKED
elif reasons:
decision = AcceptanceDecision.UNKNOWN
else:
decision = AcceptanceDecision.ACCEPT
return AcceptanceResult(decision=decision, reasons=tuple(reasons), vector_hash=digest)
@dataclass(frozen=True, slots=True)
class DesiredState:
resource_id: str
resource_type: str
desired_generation: int
desired_spec: Mapping[str, Any]
owner_goal_id: str | None = None
lifecycle: str = "active"
schema: str = "aios.desired-state.v1"
def __post_init__(self) -> None:
_nonempty(self.resource_id, "resource_id")
_nonempty(self.resource_type, "resource_type")
_generation(self.desired_generation, "desired_generation")
if self.lifecycle not in {"active", "suspended", "deleted"}:
raise ConvergenceContractError("desired_lifecycle_invalid")
def identity(self) -> tuple[str, int]:
return self.resource_id, self.desired_generation
@dataclass(frozen=True, slots=True)
class ObservedState:
resource_id: str
observed_generation: int | None
observed_spec: Mapping[str, Any]
health: ObservedHealth
source_identity: str
observed_at: str
evidence_refs: tuple[str, ...] = ()
schema: str = "aios.observed-state.v1"
def __post_init__(self) -> None:
_nonempty(self.resource_id, "resource_id")
_nonempty(self.source_identity, "source_identity")
_nonempty(self.observed_at, "observed_at")
if self.observed_generation is not None:
_generation(self.observed_generation, "observed_generation")
@dataclass(frozen=True, slots=True)
class ReconciliationPlan:
resource_id: str
desired_generation: int
state: ReconciliationState
diff: Mapping[str, Any]
observation_required: bool
idempotency_key: str
reason: str
def as_dict(self) -> dict[str, Any]:
value = asdict(self)
value["state"] = self.state.value
return value
def plan_reconciliation(
desired: DesiredState,
observed: ObservedState | None,
) -> ReconciliationPlan:
"""Return a side-effect-free reconciliation decision.
UNKNOWN/missing observed state always requires observation and never converges.
"""
identity = {
"resource_id": desired.resource_id,
"desired_generation": desired.desired_generation,
"desired_spec": desired.desired_spec,
}
key = _sha256(identity)
if observed is None:
return ReconciliationPlan(
desired.resource_id,
desired.desired_generation,
ReconciliationState.OBSERVING,
{"observed": None},
True,
key,
"observed_state_missing",
)
if observed.resource_id != desired.resource_id:
raise ConvergenceContractError("resource_identity_mismatch")
if observed.health == ObservedHealth.UNKNOWN:
return ReconciliationPlan(
desired.resource_id,
desired.desired_generation,
ReconciliationState.OBSERVING,
{"health": observed.health.value},
True,
key,
"observed_state_unknown",
)
same_generation = observed.observed_generation == desired.desired_generation
same_spec = dict(observed.observed_spec) == dict(desired.desired_spec)
if same_generation and same_spec and observed.health == ObservedHealth.HEALTHY:
return ReconciliationPlan(
desired.resource_id,
desired.desired_generation,
ReconciliationState.CONVERGED,
{},
False,
key,
"desired_equals_observed",
)
return ReconciliationPlan(
desired.resource_id,
desired.desired_generation,
ReconciliationState.DIFF_FOUND,
{
"desired_generation": desired.desired_generation,
"observed_generation": observed.observed_generation,
"desired_spec": dict(desired.desired_spec),
"observed_spec": dict(observed.observed_spec),
"observed_health": observed.health.value,
},
False,
key,
"desired_observed_drift",
)
@dataclass(frozen=True, slots=True)
class SideEffectReceipt:
mutation_id: str
external_system: str
state: str
observed_at: str
external_request_id: str | None = None
external_object_id: str | None = None
observed_result_hash: str | None = None
evidence_refs: tuple[str, ...] = ()
schema: str = "aios.side-effect-receipt.v1"
def __post_init__(self) -> None:
_nonempty(self.mutation_id, "mutation_id")
_nonempty(self.external_system, "external_system")
_nonempty(self.observed_at, "observed_at")
if self.state not in {
"confirmed_applied",
"confirmed_not_applied",
"unknown",
}:
raise ConvergenceContractError("side_effect_receipt_state_invalid")
@dataclass(frozen=True, slots=True)
class ContinuationManifest:
trace_id: str
goal_id: str
goal_revision: int
task_id: str
run_id: str
current_node_id: str
acceptance_vector_ref: str
next_action: str
stop_reason: ContinuationStopReason
checkpoint_ref: str | None = None
unresolved_blocker_refs: tuple[str, ...] = ()
authoritative_decision_refs: tuple[str, ...] = ()
resource_refs: tuple[str, ...] = ()
artifact_refs: tuple[str, ...] = ()
evidence_refs: tuple[str, ...] = ()
created_at: str = field(default_factory=_utc_now)
schema: str = "aios.continuation-manifest.v1"
def __post_init__(self) -> None:
for name in (
"trace_id",
"goal_id",
"task_id",
"run_id",
"current_node_id",
"acceptance_vector_ref",
"next_action",
"created_at",
):
_nonempty(getattr(self, name), name)
_generation(self.goal_revision, "goal_revision")
def as_dict(self) -> dict[str, Any]:
value = asdict(self)
value["stop_reason"] = self.stop_reason.value
return value
@property
def content_hash(self) -> str:
return _sha256(self.as_dict())
def validate_state_ownership(
object_type: str,
writer: str,
ownership: Iterable[StateOwnership] = DEFAULT_STATE_OWNERSHIP,
*,
final_state: bool = False,
) -> None:
row = ownership_index(ownership).get(object_type)
if row is None:
raise ConvergenceContractError(f"state_owner_missing:{object_type}")
expected = row.final_writer if final_state and row.final_writer else row.authoritative_service
if writer != expected:
raise ConvergenceContractError(
f"STATE_OWNERSHIP_VIOLATION:{object_type}:{writer}:{expected}"
)
@@ -0,0 +1,78 @@
"""Metadata-only, server-side credential lease broker."""
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
import os
from pathlib import Path
import secrets
from typing import Any, Callable, Mapping
@dataclass(frozen=True, slots=True)
class CredentialLease:
lease_id: str
task_id: str
repository: str
target_branch: str
generation: int
issued_at: str
expires_at: str
scopes: tuple[str, ...]
provider: str
state: str = "ACTIVE"
def as_dict(self) -> dict[str, Any]:
return {"lease_id": self.lease_id, "task_id": self.task_id, "repository": self.repository, "target_branch": self.target_branch, "generation": self.generation, "issued_at": self.issued_at, "expires_at": self.expires_at, "scopes": list(self.scopes), "provider": self.provider, "state": self.state}
class FileCredentialProvider:
def __init__(self, token_path: str | os.PathLike[str], *, actor: str = "aios-autonomy-agent") -> None:
self.token_path = Path(token_path)
self.actor = actor
def environment(self, lease: CredentialLease) -> dict[str, str]:
if not self.token_path.is_file():
raise RuntimeError("CREDENTIAL_PROVIDER_UNAVAILABLE")
return {"CREDENTIAL_FILE": str(self.token_path), "GITEA_MACHINE_ACTOR": self.actor}
class CredentialBroker:
def __init__(self, provider: Any, *, now: Callable[[], datetime] | None = None) -> None:
self.provider = provider
self.now = now or (lambda: datetime.now(timezone.utc))
self._leases: dict[str, CredentialLease] = {}
def issue(self, *, task_id: str, repository: str, target_branch: str, generation: int, scopes: tuple[str, ...] = ("git:push", "gitea:read"), lease_seconds: int = 900) -> dict[str, Any]:
if repository != "aios/aios" or not target_branch.startswith("agent/") or generation < 1 or not 1 <= lease_seconds <= 86400:
raise ValueError("CREDENTIAL_LEASE_BINDING_INVALID")
now = self.now().astimezone(timezone.utc)
lease = CredentialLease("lease-" + secrets.token_hex(16), task_id, repository, target_branch, generation, now.isoformat(), (now + timedelta(seconds=lease_seconds)).isoformat(), tuple(scopes), type(self.provider).__name__)
self._leases[lease.lease_id] = lease
return lease.as_dict()
def _get(self, lease_id: str) -> CredentialLease:
lease = self._leases.get(lease_id)
if lease is None:
raise KeyError("CREDENTIAL_LEASE_NOT_FOUND")
if lease.state == "ACTIVE" and self.now().astimezone(timezone.utc) >= datetime.fromisoformat(lease.expires_at):
lease = CredentialLease(**{**lease.as_dict(), "scopes": tuple(lease.scopes), "state": "EXPIRED"})
self._leases[lease_id] = lease
return lease
def environment(self, lease_id: str) -> Mapping[str, str]:
lease = self._get(lease_id)
if lease.state != "ACTIVE":
raise RuntimeError("CREDENTIAL_LEASE_NOT_ACTIVE")
return self.provider.environment(lease)
def status(self, lease_id: str) -> dict[str, Any]:
return self._get(lease_id).as_dict()
def revoke(self, lease_id: str) -> dict[str, Any]:
lease = self._get(lease_id)
if lease.state == "REVOKED":
return lease.as_dict()
updated = CredentialLease(**{**lease.as_dict(), "scopes": tuple(lease.scopes), "state": "REVOKED"})
self._leases[lease_id] = updated
return updated.as_dict()
@@ -0,0 +1,125 @@
"""Non-Owner Gitea machine capability and branch reconciliation boundary."""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any, Mapping, Protocol
class MachineBrokerError(RuntimeError):
pass
class GiteaTransport(Protocol):
def compare_and_swap_branch(self, branch: str, expected_oid: str, new_oid: str) -> str: ...
def create_or_update_pr(self, branch: str, target: str, title: str, body: str) -> Mapping[str, Any]: ...
def publish_status(self, branch: str, state: str, description: str) -> Mapping[str, Any]: ...
def protected_merge(self, pr_number: int) -> Mapping[str, Any]: ...
def read_branch(self, branch: str) -> str | None: ...
def whoami(self) -> Mapping[str, Any]: ...
def repository(self, owner: str, name: str) -> Mapping[str, Any]: ...
@dataclass(frozen=True, slots=True)
class MachineCredentialProjection:
"""Opaque systemd credential reference; the credential value is never accepted."""
reference: str
account: str
owner_account: str = "aiman"
@classmethod
def from_mapping(cls, value: Mapping[str, Any]) -> "MachineCredentialProjection":
if set(value) != {"scheme", "name", "account"}:
raise MachineBrokerError("CREDENTIAL_REFERENCE_INVALID")
if value["scheme"] != "systemd-credential" or value["name"] != "gitea-machine-token":
raise MachineBrokerError("CREDENTIAL_REFERENCE_INVALID")
return cls(f"{value['scheme']}:{value['name']}", str(value["account"]))
def __post_init__(self) -> None:
if self.reference != "systemd-credential:gitea-machine-token":
raise MachineBrokerError("CREDENTIAL_REFERENCE_INVALID")
if not self.account or self.account == self.owner_account:
raise MachineBrokerError("OWNER_MACHINE_IDENTITY_REJECTED")
@dataclass(frozen=True, slots=True)
class CampaignCapabilityReceipt:
campaign_id: str
task_id: str
repository: str
branch: str
expected_oid: str
capabilities: frozenset[str]
denied: frozenset[str]
def require(self, capability: str) -> None:
if capability in self.denied or capability not in self.capabilities:
raise MachineBrokerError("CAPABILITY_DENIED")
if self.branch in {"main", "master"}:
raise MachineBrokerError("PROTECTED_BRANCH_DENIED")
class GiteaMachineBroker:
def __init__(self, transport: GiteaTransport, credential: MachineCredentialProjection, receipt: CampaignCapabilityReceipt) -> None:
self.transport = transport
self.credential = credential
self.receipt = receipt
def verify_identity(self) -> Mapping[str, Any]:
identity = dict(self.transport.whoami())
login = identity.get("login") or identity.get("username")
if not isinstance(login, str) or not login or login == self.credential.owner_account or login != self.credential.account:
raise MachineBrokerError("GITEA_MACHINE_IDENTITY_INVALID")
owner, name = self.receipt.repository.split("/", 1)
repo = dict(self.transport.repository(owner, name))
if repo.get("full_name") != self.receipt.repository:
raise MachineBrokerError("GITEA_REPOSITORY_MISMATCH")
return {"login": login, "repository": self.receipt.repository, "owner_account": self.credential.owner_account}
def push_branch(self, new_oid: str) -> str:
self.receipt.require("exact_task_branch_push")
return self.transport.compare_and_swap_branch(self.receipt.branch, self.receipt.expected_oid, new_oid)
def create_pr(self, title: str, body: str) -> Mapping[str, Any]:
self.receipt.require("pr_create_update")
return self.transport.create_or_update_pr(self.receipt.branch, "main", title, body)
def status(self, state: str, description: str, context: str = "aios/durable-release") -> Mapping[str, Any]:
self.receipt.require("status_publish")
return self.transport.publish_status(self.receipt.branch, state, description, context)
def merge(self, pr_number: int) -> Mapping[str, Any]:
self.receipt.require("protected_merge")
return self.transport.protected_merge(pr_number)
def readback(self) -> str | None:
self.receipt.require("remote_readback")
return self.transport.read_branch(self.receipt.branch)
def reconcile_exact_branch(authority_oid: str, gitea_oid: str | None, expected_oid: str) -> str:
if authority_oid != expected_oid:
return "AUTHORITY_EXPECTATION_MISMATCH"
if gitea_oid is None:
return "CREATE_REQUIRED"
if gitea_oid == expected_oid:
return "ALREADY_IN_SYNC"
return "MANUAL_OWNER_REVIEW_REQUIRED"
def reconcile_fast_forward(
remote_oid: str | None,
expected_old_oid: str,
new_oid: str,
*,
old_is_ancestor_of_new: bool,
) -> str:
"""Return a bounded action for an active Job/lease branch scope."""
if remote_oid is None:
return "CREATE_REQUIRED"
if remote_oid == new_oid:
return "ALREADY_IN_SYNC"
if remote_oid == expected_old_oid and old_is_ancestor_of_new:
return "FAST_FORWARD_REQUIRED"
return "MANUAL_OWNER_REVIEW_REQUIRED"
@@ -0,0 +1,321 @@
"""Gitea HTTP/Git transport using only a projected systemd credential."""
from __future__ import annotations
from dataclasses import dataclass
import json
import os
from pathlib import Path
import subprocess
import tempfile
from typing import Any, Mapping
from urllib.error import HTTPError, URLError
from urllib.parse import quote, urlencode
from urllib.request import Request, urlopen
class GiteaTransportError(RuntimeError):
pass
def _safe_oid(value: str, field: str) -> str:
if not isinstance(value, str) or len(value) != 40 or any(ch not in "0123456789abcdef" for ch in value):
raise GiteaTransportError(f"{field}_INVALID")
return value
def _safe_branch(value: str) -> str:
branch = str(value or "")
if (
not branch
or len(branch) > 240
or branch != branch.strip()
or branch.startswith(("/", "-", "."))
or branch.endswith(("/", ".", ".lock"))
or ".." in branch
or "//" in branch
or "@{" in branch
or "\\" in branch
or any(part in {"", ".", ".."} for part in branch.split("/"))
or any(ch not in "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._/-" for ch in branch)
):
raise GiteaTransportError("BRANCH_INVALID")
return branch
@dataclass(slots=True)
class GiteaMachineTransport:
api_base: str
repository_id: str
remote_url: str
authority_git_dir: Path
credential_name: str = "gitea-machine-token"
actor: str = "aios-autonomy-agent"
timeout: int = 20
expected_merge_head: str | None = None
expected_merge_base: str | None = None
def __post_init__(self) -> None:
if self.credential_name != "gitea-machine-token":
raise GiteaTransportError("CREDENTIAL_NAME_INVALID")
if "/" not in self.repository_id or self.repository_id.startswith("/"):
raise GiteaTransportError("REPOSITORY_INVALID")
if not self.api_base.startswith("https://") and not self.api_base.startswith("http://127.0.0.1"):
raise GiteaTransportError("GITEA_API_SCOPE_INVALID")
if not self.remote_url.startswith("https://"):
raise GiteaTransportError("GITEA_REMOTE_SCOPE_INVALID")
@property
def _credential_path(self) -> Path:
directory = os.environ.get("CREDENTIALS_DIRECTORY", "")
if not directory:
raise GiteaTransportError("CREDENTIAL_PROJECTION_MISSING")
path = Path(directory) / self.credential_name
if path.is_symlink() or not path.is_file():
raise GiteaTransportError("CREDENTIAL_PROJECTION_MISSING")
return path
def _token(self) -> str:
value = self._credential_path.read_text(encoding="utf-8").strip()
if not value:
raise GiteaTransportError("CREDENTIAL_EMPTY")
return value
def _request(self, method: str, path: str, body: Mapping[str, Any] | None = None) -> tuple[int, Any]:
data = None if body is None else json.dumps(dict(body), separators=(",", ":")).encode()
headers = {"Accept": "application/json", "Authorization": f"token {self._token()}"}
if data is not None:
headers["Content-Type"] = "application/json"
request = Request(self.api_base.rstrip("/") + path, data=data, headers=headers, method=method)
try:
with urlopen(request, timeout=self.timeout) as response:
raw = response.read()
return response.status, json.loads(raw.decode()) if raw else {}
except HTTPError as exc:
# Never include response bodies: Gitea may echo sensitive request context.
raise GiteaTransportError(f"GITEA_HTTP_{exc.code}") from exc
except (URLError, TimeoutError, OSError) as exc:
raise GiteaTransportError("GITEA_NETWORK_ERROR") from exc
def whoami(self) -> Mapping[str, Any]:
return self._request("GET", "/user")[1]
def repository_info(self) -> Mapping[str, Any]:
owner, name = self.repository_id.split("/", 1)
return self._request("GET", f"/repos/{quote(owner)}/{quote(name)}")[1]
def repository(self, owner: str, name: str) -> Mapping[str, Any]:
if f"{owner}/{name}" != self.repository_id:
raise GiteaTransportError("REPOSITORY_SCOPE_DENIED")
return self.repository_info()
def read_branch(self, branch: str) -> str | None:
branch = _safe_branch(branch)
owner, name = self.repository_id.split("/", 1)
try:
value = self._request("GET", f"/repos/{quote(owner)}/{quote(name)}/branches/{quote(branch, safe='/')}")[1]
except GiteaTransportError as exc:
if str(exc) == "GITEA_HTTP_404":
return None
raise
sha = value.get("commit", {}).get("id") if isinstance(value, dict) else None
return _safe_oid(sha, "remote_branch_oid")
def _askpass(self) -> tuple[Path, dict[str, str]]:
credential = self._credential_path
fd, name = tempfile.mkstemp(prefix="aios-gitea-askpass-", dir="/tmp")
path = Path(name)
os.fchmod(fd, 0o700)
script = "#!/bin/sh\ncase \"$1\" in *Username*) printf '%s\\n' \"$GITEA_MACHINE_ACTOR\" ;; *) cat \"$CREDENTIAL_FILE\" ;; esac\n"
with os.fdopen(fd, "w", encoding="utf-8") as stream:
stream.write(script)
stream.flush()
os.fsync(stream.fileno())
return path, {
"GIT_ASKPASS": str(path),
"GIT_TERMINAL_PROMPT": "0",
"GITEA_MACHINE_ACTOR": self.actor,
"CREDENTIAL_FILE": str(credential),
}
def _push_branch_fast_forward(self, branch: str, new_oid: str) -> None:
askpass, additions = self._askpass()
environment = os.environ.copy()
environment.update(additions)
try:
result = subprocess.run(
["git", f"--git-dir={self.authority_git_dir}", "push", self.remote_url, f"{new_oid}:refs/heads/{branch}"],
text=True,
capture_output=True,
check=False,
env=environment,
timeout=self.timeout,
)
if result.returncode != 0:
raise GiteaTransportError("GITEA_BRANCH_CREATE_FAILED")
finally:
askpass.unlink(missing_ok=True)
def fetch_branch_exact(self, branch: str, expected_oid: str) -> str:
"""Fetch one exact remote branch tip into a quarantine ref.
This never updates ``main`` or any caller-visible branch. The remote
ref is read before and after the fetch so a concurrent branch move
cannot be silently admitted.
"""
branch = _safe_branch(branch)
expected_oid = _safe_oid(expected_oid, "expected_oid")
remote_before = self.read_branch(branch)
if remote_before != expected_oid:
raise GiteaTransportError("GITEA_SOURCE_REF_MISMATCH")
target_ref = f"refs/aios/release-sources/{expected_oid}"
current = subprocess.run(
[
"git",
f"--git-dir={self.authority_git_dir}",
"rev-parse",
"--verify",
f"{target_ref}^{{commit}}",
],
text=True,
capture_output=True,
check=False,
timeout=self.timeout,
)
if current.returncode == 0 and current.stdout.strip().lower() == expected_oid:
remote_after = self.read_branch(branch)
if remote_after != expected_oid:
raise GiteaTransportError("GITEA_SOURCE_REF_MOVED")
return expected_oid
askpass, additions = self._askpass()
environment = os.environ.copy()
environment.update(additions)
try:
result = subprocess.run(
[
"git",
f"--git-dir={self.authority_git_dir}",
"fetch",
"--no-tags",
"--no-write-fetch-head",
self.remote_url,
f"refs/heads/{branch}:{target_ref}",
],
text=True,
capture_output=True,
check=False,
env=environment,
timeout=self.timeout,
)
if result.returncode != 0:
raise GiteaTransportError("GITEA_SOURCE_FETCH_FAILED")
finally:
askpass.unlink(missing_ok=True)
local = subprocess.run(
[
"git",
f"--git-dir={self.authority_git_dir}",
"rev-parse",
"--verify",
f"{target_ref}^{{commit}}",
],
text=True,
capture_output=True,
check=False,
timeout=self.timeout,
)
local_oid = local.stdout.strip().lower()
if local.returncode != 0 or local_oid != expected_oid:
subprocess.run(
[
"git",
f"--git-dir={self.authority_git_dir}",
"update-ref",
"-d",
target_ref,
],
text=True,
capture_output=True,
check=False,
timeout=self.timeout,
)
raise GiteaTransportError("GITEA_SOURCE_FETCH_MISMATCH")
remote_after = self.read_branch(branch)
if remote_after != expected_oid:
raise GiteaTransportError("GITEA_SOURCE_REF_MOVED")
return expected_oid
def compare_and_swap_branch(self, branch: str, expected_oid: str, new_oid: str) -> str:
_safe_oid(expected_oid, "expected_oid")
_safe_oid(new_oid, "new_oid")
current = self.read_branch(branch)
if current == new_oid:
return new_oid
if current is None:
self._push_branch_fast_forward(branch, new_oid)
readback = self.read_branch(branch)
if readback != new_oid:
raise GiteaTransportError("GITEA_BRANCH_CREATE_READBACK_MISMATCH")
return readback
if current != expected_oid:
raise GiteaTransportError("GITEA_BRANCH_EXPECTATION_MISMATCH")
# The preflight read binds the expected old tip. The Git receive-pack
# then enforces the actual fast-forward CAS while transferring the new
# objects; no force/lease override or arbitrary ref is permitted.
self._push_branch_fast_forward(branch, new_oid)
readback = self.read_branch(branch)
if readback != new_oid:
raise GiteaTransportError("GITEA_BRANCH_CAS_READBACK_MISMATCH")
return readback
def _pulls(self) -> list[Mapping[str, Any]]:
owner, name = self.repository_id.split("/", 1)
query = urlencode({"state": "all", "base_branch": "main", "limit": 100})
value = self._request("GET", f"/repos/{quote(owner)}/{quote(name)}/pulls?{query}")[1]
return value if isinstance(value, list) else []
def create_or_update_pr(self, branch: str, target: str, title: str, body: str) -> Mapping[str, Any]:
if target != "main":
raise GiteaTransportError("PR_BASE_SCOPE_DENIED")
head = self.read_branch(branch)
if head is None:
raise GiteaTransportError("PR_HEAD_MISSING")
matches = [p for p in self._pulls() if (p.get("head") or {}).get("ref") == branch and (p.get("base") or {}).get("ref") == target]
open_matches = [p for p in matches if p.get("state") == "open"]
if len(open_matches) > 1:
raise GiteaTransportError("MULTIPLE_MATCHING_PRS")
if open_matches:
if (open_matches[0].get("head") or {}).get("sha") != head:
raise GiteaTransportError("PR_HEAD_CONFLICT")
return open_matches[0]
owner, name = self.repository_id.split("/", 1)
return self._request("POST", f"/repos/{quote(owner)}/{quote(name)}/pulls", {"head": branch, "base": target, "title": title, "body": body, "allow_maintainer_edit": False})[1]
def publish_status(self, branch: str, state: str, description: str, context: str = "aios/durable-release") -> Mapping[str, Any]:
oid = self.read_branch(branch)
if oid is None:
raise GiteaTransportError("STATUS_BRANCH_MISSING")
owner, name = self.repository_id.split("/", 1)
return self._request("POST", f"/repos/{quote(owner)}/{quote(name)}/statuses/{oid}", {"state": state, "context": context, "description": description})[1]
def protected_merge(self, pr_number: int) -> Mapping[str, Any]:
if not isinstance(pr_number, int) or pr_number < 1:
raise GiteaTransportError("PR_NUMBER_INVALID")
owner, name = self.repository_id.split("/", 1)
pr = self._request("GET", f"/repos/{quote(owner)}/{quote(name)}/pulls/{pr_number}")[1]
if pr.get("state") != "open" or (pr.get("base") or {}).get("ref") != "main":
raise GiteaTransportError("PR_MERGE_BINDING_INVALID")
if pr.get("mergeable") is False:
raise GiteaTransportError("PR_NOT_MERGEABLE")
head_oid = (pr.get("head") or {}).get("sha")
base_oid = (pr.get("base") or {}).get("sha")
if self.expected_merge_head is not None and head_oid != self.expected_merge_head:
raise GiteaTransportError("PR_HEAD_EXPECTATION_MISMATCH")
if self.expected_merge_base is not None and base_oid != self.expected_merge_base:
raise GiteaTransportError("PR_BASE_EXPECTATION_MISMATCH")
body = {"do": "fast-forward-only", "force_merge": False, "merge_when_checks_succeed": False, "head_commit_id": head_oid}
return self._request("POST", f"/repos/{quote(owner)}/{quote(name)}/pulls/{pr_number}/merge", body)[1]
@@ -0,0 +1,93 @@
"""Canonical Gitea transport and slash-branch verification."""
from __future__ import annotations
from dataclasses import dataclass
import json
import os
from pathlib import Path
import subprocess
from typing import Any, Callable, Mapping, Optional
from urllib.parse import quote
from urllib.request import Request, urlopen
from aios_gitops_reconciliation_core import RemoteObservation
@dataclass(frozen=True, slots=True)
class GiteaPublisherConfig:
repository: str = "aios/aios"
api_base: str = "https://g.cnnetmkt.top/git/api/v1"
remote_name: str = "gitea-http"
class GiteaPublisher:
def __init__(self, workspace: Path, *, config: GiteaPublisherConfig = GiteaPublisherConfig(), credential: Any = None, api_get: Callable[[str], Any] | None = None, git_ref: Callable[[str], Optional[str]] | None = None) -> None:
self.workspace = Path(workspace)
self.config = config
self.credential = credential
self._api_get = api_get
self._git_ref = git_ref
@staticmethod
def validate_repository(repository: str) -> None:
if repository != "aios/aios":
raise ValueError("REPOSITORY_IDENTITY_MISMATCH")
@staticmethod
def validate_branch(branch: str) -> None:
if not isinstance(branch, str) or not branch.startswith("agent/") or branch in {"main", "master"}:
raise ValueError("BRANCH_SCOPE_DENIED")
def repository_identity(self) -> str:
self.validate_repository(self.config.repository)
return self.config.repository
def _environment(self) -> dict[str, str]:
if self.credential is not None and hasattr(self.credential, "environment"):
return dict(self.credential.environment())
return {}
def read_git_ref(self, branch: str) -> Optional[str]:
self.validate_branch(branch)
if self._git_ref is not None:
return self._git_ref(branch)
result = subprocess.run(["git", "-C", str(self.workspace), "ls-remote", self.config.remote_name, f"refs/heads/{branch}"], text=True, capture_output=True, check=False, env={**os.environ, **self._environment(), "GIT_TERMINAL_PROMPT": "0"})
if result.returncode != 0 or not result.stdout.strip():
return None
return result.stdout.split()[0]
def _api(self, path: str) -> Any:
if self._api_get is not None:
return self._api_get(path)
headers = {"Accept": "application/json"}
env = self._environment()
if env.get("AIOS_GITEA_TOKEN"):
headers["Authorization"] = "token " + env["AIOS_GITEA_TOKEN"]
with urlopen(Request(self.config.api_base.rstrip("/") + path, headers=headers), timeout=20) as response:
return json.loads(response.read() or b"{}")
def read_api_branch(self, branch: str) -> Optional[str]:
self.validate_branch(branch)
for page in range(1, 101):
value = self._api(f"/repos/aios/aios/branches?page={page}&limit=50")
if not isinstance(value, list):
return None
for item in value:
if isinstance(item, Mapping) and item.get("name") == branch:
commit = item.get("commit")
return commit.get("id") if isinstance(commit, Mapping) else None
if len(value) < 50:
return None
raise RuntimeError("GITEA_BRANCH_PAGINATION_LIMIT")
def commit_visible(self, oid: str) -> bool:
if not isinstance(oid, str) or len(oid) != 40:
return False
try:
value = self._api(f"/repos/aios/aios/git/commits/{quote(oid, safe='')}")
return isinstance(value, Mapping) and value.get("sha", oid) == oid
except Exception:
return False
def observe(self, branch: str, desired_oid: str, authority_oid: Optional[str], *, expected_oid: Optional[str] = None, observed_at: str = "") -> RemoteObservation:
return RemoteObservation(self.repository_identity(), branch, desired_oid, authority_oid, self.read_git_ref(branch), self.read_api_branch(branch), self.commit_visible(desired_oid), observed_at, expected_oid)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,518 @@
"""Durable Job side-effect executor for the AIOS campaign control plane."""
from __future__ import annotations
import argparse
import base64
from dataclasses import dataclass, field, replace
from datetime import datetime, timedelta, timezone
import json
from pathlib import Path
from typing import Any, Callable, Mapping
from aios_authority_git_executor import AuthorityGitExecutor, AuthorityGitError
from aios_campaign_recovery import RecoveryEvidenceError, load_recovered_authorization
from aios_gitea_machine_broker import CampaignCapabilityReceipt, GiteaMachineBroker, MachineBrokerError
from aios_gitops_job_controller import GitOpsJobController, JobError, JobState, JobStore, ServiceUnavailable
TASK_ID = "AIOS_AUTONOMOUS_RELEASE_CAMPAIGN_V1"
CAMPAIGN_ID = "AIOS-AUTONOMOUS-RELEASE-CAMPAIGN-20260807"
REPOSITORY = "aios/aios"
BRANCH = "agent/autonomous-release-campaign-v1-20260807"
EXPECTED_BRANCH_OID = "bba675fab3b7e0c5fba7066720252b3c91b9f20d"
AUTHORIZATION_DIGEST = "6b0bf550eb702126e967eeff2d561fa808444b924d8a71f5399accad508ed3b2"
LINEAGE_ANCHOR = "69b45e81dcd2c3707c367003b08c55fd361faae8"
class JobExecutionError(RuntimeError):
pass
@dataclass(slots=True)
class DurableJobExecutor:
controller: GitOpsJobController
authority: AuthorityGitExecutor
broker: GiteaMachineBroker
recovery_path: Path
clock: Callable[[], datetime] = field(default=lambda: datetime.now(timezone.utc))
lease_seconds: int = 60
_persistence_halted: bool = field(default=False, init=False, repr=False)
def __post_init__(self) -> None:
try:
self.controller.assert_effect_admission()
except JobError as exc:
if str(exc) != "EFFECT_ADMISSION_LATCHED":
raise
self._persistence_halted = True
def _request(self, job_id: str) -> tuple[dict[str, Any], dict[str, Any]]:
row = self.controller.store.read(job_id)
try:
request = json.loads(row["request_json"])
except (KeyError, TypeError, json.JSONDecodeError) as exc:
raise JobExecutionError("JOB_REQUEST_INVALID") from exc
binding = dict(request.get("binding") or {})
# Payload location is derived from and protected by payload_digest, but
# is intentionally outside the idempotency binding. Execution still
# needs it after a durable round trip.
for field_name in ("payload_ref", "payload_size"):
if field_name in request:
binding[field_name] = request[field_name]
return row, binding
def _validate_campaign(self, request: Mapping[str, Any]) -> str:
operation = str(request.get("operation", ""))
expected_branch_oid = request.get("expected_branch_oid")
required = {
"task_id": TASK_ID,
"campaign_id": CAMPAIGN_ID,
"repository": REPOSITORY,
"branch": BRANCH,
"authorization_recovery_digest": AUTHORIZATION_DIGEST,
}
for key, expected in required.items():
if request.get(key) != expected:
raise JobExecutionError(f"JOB_{key.upper()}_MISMATCH")
new_oid = request.get("new_branch_oid")
if not isinstance(new_oid, str) or len(new_oid) != 40 or any(ch not in "0123456789abcdef" for ch in new_oid):
raise JobExecutionError("NEW_BRANCH_OID_INVALID")
if not isinstance(expected_branch_oid, str) or len(expected_branch_oid) != 40 or any(ch not in "0123456789abcdef" for ch in expected_branch_oid):
raise JobExecutionError("EXPECTED_BRANCH_OID_INVALID")
if operation == "ADVANCE_EXISTING_CAMPAIGN_BRANCH" and expected_branch_oid == new_oid:
raise JobExecutionError("EXPECTED_BRANCH_OID_INVALID")
if operation != "ADVANCE_EXISTING_CAMPAIGN_BRANCH" and expected_branch_oid != new_oid:
raise JobExecutionError("POST_ADVANCE_HEAD_BINDING_INVALID")
try:
recovery = load_recovered_authorization(self.recovery_path)
except RecoveryEvidenceError as exc:
raise JobExecutionError(str(exc)) from exc
if recovery["digest"] != AUTHORIZATION_DIGEST:
raise JobExecutionError("RECOVERY_DIGEST_MISMATCH")
for key, expected in required.items():
if key == "campaign_id":
actual = recovery.get(key)
elif key == "authorization_recovery_digest":
continue
else:
actual = recovery.get({"expected_branch_oid": "expected_branch_oid"}.get(key, key))
if actual is not None and actual != expected:
raise JobExecutionError("RECOVERY_BINDING_MISMATCH")
if recovery.get("expected_branch_oid") != EXPECTED_BRANCH_OID:
raise JobExecutionError("RECOVERY_BRANCH_OID_MISMATCH")
authority_head = self.authority.read_ref(f"refs/heads/{BRANCH}")
if authority_head not in {expected_branch_oid, new_oid}:
raise JobExecutionError("AUTHORITY_BRANCH_EXPECTATION_MISMATCH")
if not self.authority.ancestry_check(expected_branch_oid, new_oid):
raise JobExecutionError("NEW_OID_NOT_DESCENDANT")
if not self.authority.ancestry_check(EXPECTED_BRANCH_OID, new_oid):
raise JobExecutionError("NEW_OID_OUTSIDE_AUTHORIZED_LINEAGE")
if operation == "RECONCILE_AUTHORITY_MAIN":
old_main = request.get("expected_main_oid")
if (
not isinstance(old_main, str)
or len(old_main) != 40
or any(ch not in "0123456789abcdef" for ch in old_main)
):
raise JobExecutionError("MAIN_RECONCILIATION_BINDING_INVALID")
if not self.authority.ancestry_check(old_main, new_oid):
raise JobExecutionError("MAIN_OID_NOT_DESCENDANT")
identity = self.broker.verify_identity()
if identity.get("repository") != REPOSITORY or identity.get("login") == "aiman":
raise JobExecutionError("MACHINE_IDENTITY_SCOPE_INVALID")
return new_oid
def dry_run(self, job_id: str) -> dict[str, Any]:
row, request = self._request(job_id)
operation = str(request.get("operation") or "")
if operation not in {
"ADVANCE_EXISTING_CAMPAIGN_BRANCH", "COMMIT_FILE",
"SYNC_TASK_BRANCH_TO_GITEA", "CREATE_PR", "PUBLISH_CHECK",
"PROTECTED_MERGE", "RECONCILE_AUTHORITY_MAIN",
}:
raise JobExecutionError("OPERATION_NOT_ALLOWED")
self._validate_campaign(request)
if operation == "COMMIT_FILE":
self._commit_file_spec(row, request)
observation = self._observe_operation(request)
result = {
"mode": "DRY_RUN",
"operation": operation,
"decision": "SAFE_TO_ADVANCE",
"effect_performed": False,
"readback": observation,
}
if operation == "ADVANCE_EXISTING_CAMPAIGN_BRANCH":
result.update({
"authority_action": "CAS_REQUIRED",
"gitea_action": "ALREADY_IN_SYNC" if observation["verification"] == "VERIFIED" else "FAST_FORWARD_REQUIRED",
"previous_branch_oid": request["expected_branch_oid"],
"final_branch_oid": request["new_branch_oid"],
"force_push": False,
})
return result
def execute(self, job_id: str, *, dry_run: bool = False) -> dict[str, Any]:
if dry_run:
return self.dry_run(job_id)
if self._persistence_halted:
raise JobExecutionError("PERSISTENCE_FAILURE_LATCHED")
try:
return self._execute_unprotected(job_id)
except ServiceUnavailable:
self._persistence_halted = True
raise
except Exception as exc:
try:
row = self.controller.store.read(job_id)
state = JobState(row["state"])
if state in {
JobState.EXECUTING, JobState.LOCAL_VERIFIED,
JobState.REMOTE_SYNCING, JobState.REMOTE_VERIFIED,
}:
self.controller.advance(
job_id,
JobState.RECONCILIATION_REQUIRED,
side_effect_state="UNKNOWN",
receipt={"error_code": type(exc).__name__, "verification": "UNKNOWN"},
)
elif state not in {
JobState.COMPLETED, JobState.MANUAL_OWNER_REVIEW_REQUIRED,
JobState.RECOVERABLE_FAILURE, JobState.RECONCILIATION_REQUIRED,
}:
self.controller.advance(
job_id, JobState.RECOVERABLE_FAILURE,
side_effect_state="NONE",
receipt={"error_code": type(exc).__name__, "effect_performed": False},
)
except ServiceUnavailable:
self._persistence_halted = True
raise
def _execute_unprotected(self, job_id: str) -> dict[str, Any]:
row, request = self._request(job_id)
state = JobState(row["state"])
if state == JobState.COMPLETED:
return self.controller.store._receipt(row, duplicate=True).as_dict()
if state in {
JobState.RECOVERABLE_FAILURE, JobState.RECONCILIATION_REQUIRED,
JobState.RECONCILING, JobState.EXECUTING, JobState.LOCAL_VERIFIED,
JobState.REMOTE_SYNCING, JobState.REMOTE_VERIFIED,
}:
return self.reconcile(job_id)
operation = str(request.get("operation") or "")
if operation not in {
"ADVANCE_EXISTING_CAMPAIGN_BRANCH", "COMMIT_FILE",
"SYNC_TASK_BRANCH_TO_GITEA", "CREATE_PR", "PUBLISH_CHECK",
"PROTECTED_MERGE", "RECONCILE_AUTHORITY_MAIN",
}:
raise JobExecutionError("OPERATION_NOT_ALLOWED")
try:
self.controller.assert_effect_admission(exclude_job_id=job_id)
except JobError as exc:
if str(exc) == "EFFECT_ADMISSION_LATCHED":
self._persistence_halted = True
raise JobExecutionError("PERSISTENCE_FAILURE_LATCHED") from exc
raise
self._validate_campaign(request)
if operation == "COMMIT_FILE":
self._commit_file_spec(row, request)
row = self._prepare_intent(job_id, row, request)
generation = int(request.get("generation", "1"))
self.controller.assert_execution_lease(job_id, generation=generation)
before = self._observe_operation(request)
if before["verification"] == "VERIFIED":
return self._complete(job_id, request, {"reconciled_before_effect": True}, before)
if before["verification"] != "ABSENT":
return self._record_unknown(job_id, request, before)
effect = self._perform_effect(job_id, row, request, generation)
after = self._observe_operation(request)
if after["verification"] != "VERIFIED":
return self._record_unknown(job_id, request, after, effect=effect)
return self._complete(job_id, request, effect, after)
def _prepare_intent(
self, job_id: str, row: Mapping[str, Any], request: Mapping[str, Any]
) -> dict[str, Any]:
state = JobState(row["state"])
if state == JobState.REQUESTED:
self.controller.advance(
job_id, JobState.AUTHORIZED, side_effect_state="AUTHORIZED",
receipt={"authorization_digest": AUTHORIZATION_DIGEST, "task_id": TASK_ID},
expected_revision=int(row["revision"]),
)
row = self.controller.store.read(job_id)
state = JobState(row["state"])
if state == JobState.AUTHORIZED:
deadline = self.clock().astimezone(timezone.utc) + timedelta(seconds=self.lease_seconds)
self.controller.lease_job(
job_id,
lease_id=f"lease-{job_id}",
lineage_anchor_oid=LINEAGE_ANCHOR,
current_head_oid=str(request["expected_branch_oid"]),
generation=int(request.get("generation", "1")),
lease_expires_at=deadline.isoformat(),
expected_revision=int(row["revision"]),
)
row = self.controller.store.read(job_id)
state = JobState(row["state"])
if state == JobState.LEASED:
self.controller.advance(
job_id, JobState.WORKTREE_READY, side_effect_state="WORKTREE_READY",
expected_revision=int(row["revision"]),
)
row = self.controller.store.read(job_id)
state = JobState(row["state"])
if state == JobState.WORKTREE_READY:
self.controller.advance(
job_id, JobState.EXECUTING, side_effect_state="INTENT_COMMITTED",
receipt={
"effect_id": job_id,
"operation": request["operation"],
"input_digest": row["payload_digest"],
"intent_committed": True,
},
expected_revision=int(row["revision"]),
)
row = self.controller.store.read(job_id)
if JobState(row["state"]) != JobState.EXECUTING:
raise JobExecutionError("JOB_EXECUTION_STATE_INVALID")
return dict(row)
def _perform_effect(
self,
job_id: str,
row: Mapping[str, Any],
request: Mapping[str, Any],
generation: int,
) -> dict[str, Any]:
operation = str(request["operation"])
new_oid = str(request["new_branch_oid"])
self.controller.assert_execution_lease(job_id, generation=generation)
if operation == "ADVANCE_EXISTING_CAMPAIGN_BRANCH":
expected = str(request["expected_branch_oid"])
current = self.authority.read_ref(f"refs/heads/{BRANCH}")
authority_oid = new_oid if current == new_oid else self.authority.advance_task_branch_CAS(
f"refs/heads/{BRANCH}", expected, new_oid
)
self.controller.assert_execution_lease(job_id, generation=generation)
remote = self.broker.readback()
if remote != new_oid:
if hasattr(self.broker, "receipt"):
self.broker.receipt = replace(self.broker.receipt, expected_oid=expected)
self.broker.push_branch(new_oid)
return {"authority_oid": authority_oid, "network_oid": new_oid, "force_push": False}
if operation == "SYNC_TASK_BRANCH_TO_GITEA":
return {"final_network_oid": self.broker.push_branch(new_oid), "force_push": False}
if operation == "CREATE_PR":
return dict(self.broker.create_pr(
"AIOS autonomous release control plane",
"Durable campaign continuation; exact head and recovery evidence are bound in the Job.",
))
if operation == "PUBLISH_CHECK":
return dict(self.broker.status(
str(request.get("check_state", "success")),
str(request.get("check_description", "Durable campaign control-plane check passed")),
str(request.get("check_context", "aios/durable-release")),
))
if operation == "PROTECTED_MERGE":
number = int(request.get("pr_number", "0"))
self.broker.transport.expected_merge_head = request["expected_branch_oid"]
if request.get("expected_main_oid") is not None:
self.broker.transport.expected_merge_base = request["expected_main_oid"]
return dict(self.broker.merge(number))
if operation == "RECONCILE_AUTHORITY_MAIN":
old = request.get("expected_main_oid")
if not isinstance(old, str):
raise JobExecutionError("MAIN_RECONCILIATION_BINDING_INVALID")
if self.broker.transport.read_branch("main") != new_oid:
raise JobExecutionError("GITEA_MAIN_EXPECTATION_MISMATCH")
return {"authority_main": self.authority.ff_CAS_main_after_verified_protected_merge(old, new_oid)}
if operation == "COMMIT_FILE":
path, content = self._commit_file_spec(row, request)
return {
"new_oid": self.authority.commit_file_in_isolated_worktree(
base_oid=row["expected_branch_oid"], files={path: content},
message="chore(gitops): durable commit-file", worktree_name=job_id,
)
}
raise JobExecutionError("OPERATION_NOT_ALLOWED")
def _observe_operation(self, request: Mapping[str, Any]) -> dict[str, Any]:
operation = str(request["operation"])
new_oid = str(request["new_branch_oid"])
if operation == "ADVANCE_EXISTING_CAMPAIGN_BRANCH":
authority = self.authority.read_ref(f"refs/heads/{BRANCH}")
remote = self.broker.readback()
if authority == new_oid and remote == new_oid:
return {"verification": "VERIFIED", "authority_oid": authority, "remote_oid": remote}
expected = request["expected_branch_oid"]
if authority == expected and remote in {None, expected}:
return {"verification": "ABSENT", "authority_oid": authority, "remote_oid": remote}
return {"verification": "UNKNOWN", "blocker": "PARTIAL_OR_CONFLICTING_BRANCH_EFFECT", "authority_oid": authority, "remote_oid": remote}
if operation == "SYNC_TASK_BRANCH_TO_GITEA":
remote = self.broker.readback()
if remote == new_oid:
return {"verification": "VERIFIED", "remote_oid": remote}
if remote in {None, EXPECTED_BRANCH_OID}:
return {"verification": "ABSENT", "remote_oid": remote}
return {"verification": "UNKNOWN", "blocker": "REMOTE_BRANCH_CONFLICT", "remote_oid": remote}
if operation == "RECONCILE_AUTHORITY_MAIN":
current = self.authority.read_ref("refs/heads/main")
if current == new_oid:
return {"verification": "VERIFIED", "authority_main": current}
if current == request.get("expected_main_oid"):
return {"verification": "ABSENT", "authority_main": current}
return {"verification": "UNKNOWN", "blocker": "AUTHORITY_MAIN_CONFLICT", "authority_main": current}
if operation == "CREATE_PR" and hasattr(self.broker, "read_pr"):
value = self.broker.read_pr(BRANCH, "main", new_oid)
return {"verification": "VERIFIED" if value else "ABSENT", "pr": value}
if operation == "PUBLISH_CHECK" and hasattr(self.broker, "read_status"):
value = self.broker.read_status(
new_oid, str(request.get("check_context", "aios/durable-release"))
)
if value is None:
return {"verification": "ABSENT"}
expected_state = str(request.get("check_state", "success"))
return {"verification": "VERIFIED" if value.get("status") == expected_state else "UNKNOWN", "status": dict(value), **({} if value.get("status") == expected_state else {"blocker": "STATUS_READBACK_MISMATCH"})}
if operation == "PROTECTED_MERGE" and hasattr(self.broker, "read_merge"):
value = self.broker.read_merge(int(request.get("pr_number", "0")))
if value is None:
return {"verification": "ABSENT"}
if value.get("merged") is True and value.get("head_oid") == request["expected_branch_oid"]:
return {"verification": "VERIFIED", "merge": dict(value)}
if value.get("merged") is False:
return {"verification": "ABSENT", "merge": dict(value)}
return {"verification": "UNKNOWN", "blocker": "MERGE_READBACK_MISMATCH", "merge": dict(value)}
return {"verification": "UNKNOWN", "blocker": "EXACT_READBACK_UNSUPPORTED"}
def _complete(
self, job_id: str, request: Mapping[str, Any], effect: Mapping[str, Any], observation: Mapping[str, Any]
) -> dict[str, Any]:
row = self.controller.store.read(job_id)
receipt = {
"effect_id": job_id,
"operation": request["operation"],
"effect": dict(effect),
"verification": "VERIFIED",
"readback": dict(observation),
}
completed = self.controller.advance(
job_id, JobState.COMPLETED, side_effect_state="VERIFIED",
receipt=receipt, expected_revision=int(row["revision"]),
)
return completed.as_dict()
def _record_unknown(
self,
job_id: str,
request: Mapping[str, Any],
observation: Mapping[str, Any],
*,
effect: Mapping[str, Any] | None = None,
) -> dict[str, Any]:
row = self.controller.store.read(job_id)
receipt = {
"effect_id": job_id,
"operation": request["operation"],
"effect": dict(effect) if effect is not None else None,
"verification": "UNKNOWN",
"blocker": observation.get("blocker", "READBACK_INCONCLUSIVE"),
"readback": dict(observation),
}
pending = self.controller.advance(
job_id, JobState.RECONCILIATION_REQUIRED, side_effect_state="UNKNOWN",
receipt=receipt, expected_revision=int(row["revision"]),
)
return pending.as_dict()
def reconcile(self, job_id: str) -> dict[str, Any]:
def observer(_current: Mapping[str, Any]) -> Mapping[str, Any]:
_row, request = self._request(job_id)
self._validate_campaign(request)
observation = self._observe_operation(request)
if observation["verification"] == "VERIFIED":
return {
"state": JobState.COMPLETED.value,
"side_effect_state": "VERIFIED",
"receipt": {
"effect_id": job_id, "operation": request["operation"],
"effect": None, "verification": "VERIFIED",
"readback": observation, "reconciled": True,
},
}
if observation["verification"] == "ABSENT":
return {
"state": JobState.REQUESTED.value,
"side_effect_state": "RECONCILED_ABSENT",
"receipt": {
"effect_id": job_id, "operation": request["operation"],
"verification": "ABSENT", "readback": observation,
"reconciled": True,
},
}
return {
"state": JobState.RECONCILIATION_REQUIRED.value,
"side_effect_state": "UNKNOWN",
"receipt": {
"effect_id": job_id, "operation": request["operation"],
"verification": "UNKNOWN",
"blocker": observation.get("blocker", "READBACK_INCONCLUSIVE"),
"readback": observation, "reconciled": True,
},
}
result = self.controller.reconcile(job_id, observer).as_dict()
if result["state"] in {JobState.COMPLETED.value, JobState.REQUESTED.value}:
self._persistence_halted = False
return result
def _commit_file_spec(
self, row: Mapping[str, Any], request: Mapping[str, Any]
) -> tuple[str, bytes]:
payload_ref = request.get("payload_ref")
if not payload_ref:
raise JobExecutionError("PAYLOAD_REQUIRED")
payload = self.controller.store.payloads.read_verified(payload_ref, row["payload_digest"], int(request["payload_size"]))
try:
spec = json.loads(payload.decode())
path = str(spec["path"])
content = base64.b64decode(spec["content_base64"], validate=True)
except Exception as exc:
raise JobExecutionError("COMMIT_FILE_PAYLOAD_INVALID") from exc
return path, content
def build_from_environment(job_root: Path) -> DurableJobExecutor:
from aios_gitea_machine_transport import GiteaMachineTransport
from aios_gitea_machine_broker import MachineCredentialProjection
account = __import__("os").environ.get("AIOS_GITEA_MACHINE_ACCOUNT", "aios-autonomy-agent")
api = __import__("os").environ.get("AIOS_GITEA_API", "https://g.cnnetmkt.top/git/api/v1")
remote = __import__("os").environ.get("AIOS_GITEA_REMOTE", "https://g.cnnetmkt.top/git/aios/aios.git")
authority_dir = Path(__import__("os").environ.get("AIOS_GITOPS_AUTHORITY_GIT_DIR", "/data/aios-gitops/repo.git"))
worktree_root = Path(__import__("os").environ.get("AIOS_GITOPS_WORKTREE_ROOT", "/var/lib/aios-gitops/worktrees"))
recovery = Path(__import__("os").environ.get("AIOS_RELEASE_RECOVERY_RECEIPT", "/var/lib/aios-gitops/release-campaigns/AIOS-AUTONOMOUS-RELEASE-CAMPAIGN-20260807/authorization-recovery.json"))
transport = GiteaMachineTransport(api, REPOSITORY, remote, authority_dir)
credential = MachineCredentialProjection.from_mapping({"scheme": "systemd-credential", "name": "gitea-machine-token", "account": account})
receipt = CampaignCapabilityReceipt(CAMPAIGN_ID, TASK_ID, REPOSITORY, BRANCH, EXPECTED_BRANCH_OID, frozenset({"exact_task_branch_push", "pr_create_update", "status_publish", "protected_merge", "remote_readback"}), frozenset({"direct_main_push", "force_push", "tag_update", "tag_delete", "baseline_write", "production_deploy"}))
return DurableJobExecutor(GitOpsJobController(JobStore(job_root)), AuthorityGitExecutor(authority_dir, worktree_root), GiteaMachineBroker(transport, credential, receipt), recovery)
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--job-root", required=True, type=Path)
parser.add_argument("--job-id", required=True)
parser.add_argument("--dry-run", action="store_true")
args = parser.parse_args(argv)
result = build_from_environment(args.job_root).execute(args.job_id, dry_run=args.dry_run)
print(json.dumps(result, sort_keys=True))
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,32 @@
"""Collision-resistant storage keys for durable governed Task state."""
from __future__ import annotations
import hashlib
import re
class JobIdentityError(RuntimeError):
pass
def legacy_task_storage_key(task_id: str) -> str:
"""Return the historical lossy key solely to detect migration conflicts."""
if not isinstance(task_id, str) or not task_id:
raise JobIdentityError("TASK_ID_INVALID")
return re.sub(r"[^A-Za-z0-9_.-]+", "-", task_id).strip("-")[:120] or "task"
def task_storage_key(task_id: str) -> str:
"""Return a canonical collision-resistant key for every Task state sink."""
if (
not isinstance(task_id, str)
or not task_id
or task_id != task_id.strip()
or len(task_id) > 512
):
raise JobIdentityError("TASK_ID_INVALID")
readable = re.sub(r"[^A-Za-z0-9_.-]+", "-", task_id).strip("-")[:48] or "task"
digest = hashlib.sha256(
("aios-task-storage-v1\0" + task_id).encode("utf-8")
).hexdigest()
return f"{readable}-{digest}"
+78 -10
View File
@@ -1,10 +1,12 @@
#!/usr/bin/env python3
from __future__ import annotations
import json, os, re, sqlite3, subprocess, time, uuid, hashlib, urllib.parse, html
import json, os, re, sqlite3, subprocess, time, uuid, hashlib, urllib.parse, html, ipaddress
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from typing import Any
from release_version import validate_release_version
ROOT = Path(os.getenv('AIOS_GITOPS_ROOT', '/data/aios-gitops'))
WORKSPACE = Path(os.getenv('AIOS_GITOPS_WORKSPACE', str(ROOT / 'workspace')))
DB_PATH = Path(os.getenv('AIOS_GITOPS_DB', str(ROOT / 'runtime' / 'gitops_model.sqlite3')))
@@ -14,6 +16,25 @@ HOST = os.getenv('AIOS_GITOPS_HOST', '127.0.0.1')
PORT = int(os.getenv('AIOS_GITOPS_PORT', '18890'))
ENTRY = os.getenv('AIOS_ENTRY', 'https://cnnetmkt.top')
DEV_ALLOW = os.getenv('AIOS_GITOPS_DEV_ALLOW', '0') == '1'
def _loopback(value):
try: return ipaddress.ip_address(value).is_loopback
except (ValueError, TypeError): return False
def validate_dev_profile():
if DEV_ALLOW and not (os.getenv('AIOS_RUNTIME_PROFILE') == 'isolated-staging' and _loopback(HOST)):
raise RuntimeError('DEV_ALLOW requires isolated-staging and loopback')
def runtime_selection(principal, data):
if not principal.get('token_ok'): raise PermissionError('authenticated principal required')
if any(key in data for key in ('username', 'user_id', 'role', 'tier')):
raise ValueError('runtime identity must come from authenticated principal')
username=principal['username']; role=principal['role']
if role not in _ROLE_WHITELIST or re.fullmatch(r'[A-Za-z0-9_@.-]{1,120}', username) is None or username in ('.','..'):
raise ValueError('invalid authenticated principal')
tier='ultra' if role=='owner' else 'regular'
return username, role, tier
BASE_MODELS = [
{'id':'base.deepseekv4-flash','provider':'one-api','model':'deepseekv4-flash','display':'DeepSeek V4 Flash','tier':'regular','reasoning':1,'vision':0,'tools':1,'context':128000},
{'id':'base.qwen3.6-35b','provider':'one-api','model':'qwen3.6-35b','display':'Qwen 3.6 35B','tier':'pro','reasoning':1,'vision':0,'tools':1,'context':262144},
@@ -55,10 +76,39 @@ def seed_base_models():
if not one('SELECT * FROM gitops_versions WHERE version=?', ('V1R1C0',)):
exec_sql('INSERT INTO gitops_versions(version,tag,level,summary,status,created_by,created_at) VALUES(?,?,?,?,?,?,?)', ('V1R1C0','V1R1C0','R','MultiUser runtime selector + GitOps + custom LLM priority registry','ready','system',now()))
_ROLE_WHITELIST = {'owner', 'admin', 'user'}
def token_identity(token: str):
"""Resolve the server-authoritative identity bound to a token.
Format of each TOKEN_FILE line (one per line, ':'-separated):
<token> -> authenticated principal, role 'user'
<token>:<user>:<role> -> mapped principal; role restricted to owner/admin/user
Identity NEVER comes from a client-supplied header. In local dev mode the
DEV_ALLOW flag maps a fixed dev principal (must be explicitly enabled).
"""
if DEV_ALLOW:
validate_dev_profile()
return {'username': 'dev-owner', 'role': 'owner'}
if not token or not TOKEN_FILE.exists():
return None
for line in TOKEN_FILE.read_text().splitlines():
line = line.strip()
if not line:
continue
parts = [x.strip() for x in line.split(':')]
if not parts or parts[0] != token:
continue
if len(parts) >= 3:
role = parts[2] if parts[2] in _ROLE_WHITELIST else 'user'
return {'username': safe_name(parts[1]), 'role': role}
return {'username': 'user-%s' % token[:8], 'role': 'user'}
return None
def valid_token(token: str) -> bool:
if DEV_ALLOW: return True
if not token or not TOKEN_FILE.exists(): return False
return token.strip() in [x.strip() for x in TOKEN_FILE.read_text().splitlines() if x.strip()]
if DEV_ALLOW:
validate_dev_profile()
return True
return token_identity(token) is not None
def git_cmd(args, check=False):
WORKSPACE.mkdir(parents=True, exist_ok=True); p = subprocess.run(['git'] + args, cwd=str(WORKSPACE), text=True, capture_output=True)
if check and p.returncode != 0: raise RuntimeError(p.stderr or p.stdout or 'git command failed')
@@ -88,8 +138,10 @@ class Handler(BaseHTTPRequestHandler):
server_version = 'AIOSGitOpsModelAPI/V1R1C0'
def actor(self):
auth = self.headers.get('Authorization',''); token = auth.replace('Bearer ', '').strip() if auth.startswith('Bearer ') else self.headers.get('X-AIOS-Token','')
ok = valid_token(token); user = self.headers.get('X-AIOS-User') or ('dev-owner' if DEV_ALLOW else 'anonymous'); role = self.headers.get('X-AIOS-Role') or ('owner' if DEV_ALLOW else 'user')
return {'username': safe_name(user), 'role': role, 'token_ok': ok}
ident = token_identity(token)
if ident is None:
return {'username': 'anonymous', 'role': 'user', 'token_ok': False}
return {'username': ident['username'], 'role': ident['role'], 'token_ok': True}
def require_write(self, roles=None):
a = self.actor()
if not a['token_ok']: self.send_json({'error':'unauthorized','hint':'write APIs require Bearer token or AIOS_GITOPS_DEV_ALLOW=1'}, 401); return None
@@ -133,7 +185,19 @@ class Handler(BaseHTTPRequestHandler):
self.send_json({'error':'not_found','path':path},404)
except Exception as e: self.send_json({'error':str(e)},500)
def do_POST(self):
init_db(); path = urllib.parse.urlparse(self.path).path; data = parse_body(self)
path = urllib.parse.urlparse(self.path).path
if path == '/api/runtime/select':
if not (_loopback(HOST) and _loopback(self.client_address[0])):
self.send_json({'error':'loopback ingress required'},403); return
a = self.require_write()
if not a: return
data = parse_body(self)
try: runtime_selection(a, data)
except (ValueError, PermissionError):
self.send_json({'error':'invalid runtime selection identity'},403); return
else:
data = parse_body(self)
init_db()
try:
if path == '/api/model/custom/add':
a = self.require_write();
@@ -187,7 +251,7 @@ class Handler(BaseHTTPRequestHandler):
if not m: self.send_json({'error':'model not found'},404); return
self.send_json({'status':'ok' if m['base_url'] and m['model_name'] else 'failed','live_test':os.getenv('AIOS_ENABLE_MODEL_LIVE_TEST','0')=='1','model':m['model_name'],'base_url':m['base_url'],'note':'Network live call is disabled unless AIOS_ENABLE_MODEL_LIVE_TEST=1'}); return
if path == '/api/runtime/select':
a=self.actor(); username=safe_name(data.get('username') or a['username']); role=data.get('role') or a['role']; tier=data.get('tier') or ('ultra' if role=='owner' else 'regular'); risk=data.get('risk','normal')
username,role,tier=runtime_selection(a, data); risk=data.get('risk','normal')
if risk in ('high_tool','shell','docker','write_prod'): runtime,backend='ephemeral_sandbox','openclaw_executor'
elif role in ('owner','admin') or tier=='ultra': runtime,backend='dedicated','hermes'
elif tier=='pro': runtime,backend='dedicated_or_shared','hermes'
@@ -218,7 +282,11 @@ class Handler(BaseHTTPRequestHandler):
a = self.require_write(['owner']);
if not a: return
ver=data.get('version') or data.get('tag')
if not re.match(r'^V\d+R\d+C\d+(-[a-zA-Z0-9_.-]+)?$', ver or ''): self.send_json({'error':'version must match VxRxCx'},400); return
try:
validate_release_version(ver)
except ValueError:
self.send_json({'error':'version must match VxRxCx'},400)
return
msg=data.get('message') or f'release {ver}'; out=git_cmd(['tag','-a',ver,'-m',msg]);
if data.get('push',False): git_cmd(['push','origin',ver])
level='V' if re.match(r'^V[1-9]\d*R0C0', ver) else ('R' if re.match(r'^V\d+R[1-9]\d*C0', ver) else 'C'); exec_sql('INSERT OR REPLACE INTO gitops_versions(version,tag,level,summary,status,created_by,created_at) VALUES(?,?,?,?,?,?,?)',(ver,ver,level,msg,'ready',a['username'],now())); exec_sql('UPDATE gitops_baseline SET active_version=?,active_tag=?,updated_at=? WHERE id=?',(ver,ver,now(),'current')); audit(a['username'],'git_tag',ver,{'level':level}); self.send_json({'status':'tagged','version':ver,'git':out}); return
@@ -258,5 +326,5 @@ class Handler(BaseHTTPRequestHandler):
except Exception as e: self.send_json({'error':str(e)},500)
def api_spec(): return {'version':'V1R1C0','short_links':['/base','/ver','/diff','/patch','/run','/model','/api'],'gitops_get':['/api/status','/api/base','/api/ver','/api/diff','/api/patch'],'gitops_post':['/api/branch','/api/file','/api/commit','/api/tag','/api/rollback','/api/nl'],'model_get':['/api/model/base','/api/model/custom','/api/model/priority'],'model_post':['/api/model/custom/add','/api/model/custom/update','/api/model/delete','/api/model/priority/set','/api/model/route/resolve','/api/model/test'],'runtime':['/api/runtime/status','/api/runtime/select','/api/runtime/register','/api/runtime/heartbeat'],'auth':'write APIs require Bearer token unless AIOS_GITOPS_DEV_ALLOW=1 for local acceptance tests'}
def main(): init_db(); print(f'AIOS V1R1C0 GitOps Model API listening on {HOST}:{PORT}'); ThreadingHTTPServer((HOST, PORT), Handler).serve_forever()
def main(): validate_dev_profile(); init_db(); print(f'AIOS V1R1C0 GitOps Model API listening on {HOST}:{PORT}'); ThreadingHTTPServer((HOST, PORT), Handler).serve_forever()
if __name__ == '__main__': main()
@@ -0,0 +1,80 @@
"""Pure, observation-first decisions for governed GitOps reconciliation."""
from __future__ import annotations
from dataclasses import dataclass
from enum import Enum
import re
from typing import Optional
OID_RE = re.compile(r"^[0-9a-f]{40}$")
class ReconciliationAction(str, Enum):
NOOP = "NOOP"
PUSH_EXACT = "PUSH_EXACT"
VERIFY_REMOTE = "VERIFY_REMOTE"
MARK_REMOTE_VERIFIED = "MARK_REMOTE_VERIFIED"
MARK_RECONCILIATION_REQUIRED = "MARK_RECONCILIATION_REQUIRED"
FAIL_CONFLICT = "FAIL_CONFLICT"
@dataclass(frozen=True, slots=True)
class RemoteObservation:
repository_identity: str
branch: str
desired_oid: str
authority_oid: Optional[str]
remote_git_oid: Optional[str]
remote_api_oid: Optional[str]
commit_visible: bool
observed_at: str
expected_oid: Optional[str] = None
@property
def all_equal(self) -> bool:
values = (self.authority_oid, self.remote_git_oid, self.remote_api_oid, self.desired_oid)
return all(isinstance(value, str) for value in values) and len(set(values)) == 1
@dataclass(frozen=True, slots=True)
class ReconciliationDecision:
action: ReconciliationAction
state: str
reason: str
retry_safe: bool = False
def _valid_oid(value: Optional[str]) -> bool:
return value is None or bool(OID_RE.fullmatch(value))
def decide_reconciliation(observation: RemoteObservation) -> ReconciliationDecision:
"""Decide from a fresh observation; never complete on a missing remote."""
if observation.repository_identity != "aios/aios":
return ReconciliationDecision(ReconciliationAction.FAIL_CONFLICT, "CONFLICT", "REPOSITORY_IDENTITY_MISMATCH")
if not observation.branch.startswith("agent/"):
return ReconciliationDecision(ReconciliationAction.FAIL_CONFLICT, "CONFLICT", "BRANCH_SCOPE_DENIED")
if not _valid_oid(observation.desired_oid) or not _valid_oid(observation.expected_oid):
return ReconciliationDecision(ReconciliationAction.FAIL_CONFLICT, "CONFLICT", "OID_INVALID")
if observation.authority_oid is None:
return ReconciliationDecision(ReconciliationAction.MARK_RECONCILIATION_REQUIRED, "RECONCILIATION_REQUIRED", "AUTHORITY_UNAVAILABLE")
if observation.authority_oid != observation.desired_oid:
return ReconciliationDecision(ReconciliationAction.MARK_RECONCILIATION_REQUIRED, "RECONCILIATION_REQUIRED", "AUTHORITY_CONFLICT")
present = [oid for oid in (observation.remote_git_oid, observation.remote_api_oid) if oid is not None]
if any(oid not in {observation.desired_oid, observation.expected_oid} for oid in present):
return ReconciliationDecision(ReconciliationAction.FAIL_CONFLICT, "CONFLICT", "REMOTE_OID_CONFLICT")
if observation.all_equal and observation.commit_visible:
return ReconciliationDecision(ReconciliationAction.MARK_REMOTE_VERIFIED, "REMOTE_VERIFIED", "ALL_OIDS_EQUAL_AND_VISIBLE")
if observation.remote_git_oid is None and observation.remote_api_oid is None:
return ReconciliationDecision(ReconciliationAction.MARK_RECONCILIATION_REQUIRED, "REMOTE_PUSH_PENDING", "AUTHORITY_DESIRED_REMOTE_MISSING")
if observation.remote_git_oid == observation.desired_oid or observation.remote_api_oid == observation.desired_oid:
return ReconciliationDecision(ReconciliationAction.VERIFY_REMOTE, "REMOTE_VERIFY_PENDING", "REMOTE_PROOF_PARTIAL")
if observation.expected_oid is not None and observation.remote_git_oid == observation.expected_oid and observation.remote_api_oid == observation.expected_oid:
return ReconciliationDecision(ReconciliationAction.PUSH_EXACT, "REMOTE_PUSH_PENDING", "SAFE_EXACT_FAST_FORWARD_RETRY", retry_safe=True)
if not observation.commit_visible:
return ReconciliationDecision(ReconciliationAction.VERIFY_REMOTE, "COMMIT_VISIBILITY_PENDING", "COMMIT_VISIBILITY_LAG")
return ReconciliationDecision(ReconciliationAction.MARK_RECONCILIATION_REQUIRED, "RECONCILIATION_REQUIRED", "PARTIAL_REMOTE_OBSERVATION")
def reconcile_observation(**kwargs: object) -> ReconciliationDecision:
return decide_reconciliation(RemoteObservation(**kwargs))
+100
View File
@@ -0,0 +1,100 @@
"""Single supervised socket-activated worker for durable GitOps Jobs.
The worker consumes durable requests and reconciliation queries. Repository
mutation is a separate injected executor and remains impossible while the
legacy global write gate is false.
"""
from __future__ import annotations
import argparse
import json
import os
from pathlib import Path
import signal
import socket
import threading
from typing import Any, Mapping
from aios_gitops_job_controller import GitOpsJobController, JobError, JobState, JobStore, ServiceUnavailable
def runtime_policy(environment: Mapping[str, str] | None = None) -> dict[str, bool]:
env = environment or os.environ
legacy = env.get("AIOS_GITOPS_WRITES_ENABLED", "false").lower() in {"1", "true", "yes", "on"}
durable = env.get("AIOS_GITOPS_DURABLE_JOB_EXECUTION_ENABLED", "false").lower() in {"1", "true", "yes", "on"}
# Legacy direct Git mutation remains disabled. Authorized durable Jobs use
# their own campaign/task capability and may mutate only through the
# worker's lease/CAS path while the legacy gate stays false.
return {"legacy_global_write": legacy, "durable_job_execution": durable, "repository_mutation_allowed": durable}
def handle_request(controller: GitOpsJobController, request: Mapping[str, Any], executor: Any | None = None) -> dict[str, Any]:
job_id = str(request.get("job_id") or "")
if not job_id:
raise JobError("JOB_ID_REQUIRED")
if request.get("action") == "execute":
if executor is None:
raise JobError("JOB_EXECUTOR_UNAVAILABLE")
result = executor.execute(job_id, dry_run=bool(request.get("dry_run", False)))
receipt = controller.store._receipt(controller.store.read(job_id), duplicate=True)
return {"job": result, **receipt.as_dict()}
if request.get("action") == "resume":
receipt = controller.resume(job_id)
else:
row = controller.store.read(job_id)
receipt = controller.store._receipt(row, duplicate=True)
return receipt.as_dict()
def serve_socket(sock: socket.socket, controller: GitOpsJobController, stopping: threading.Event, executor: Any | None = None) -> None:
sock.listen(32)
sock.settimeout(1.0)
while not stopping.is_set():
try:
connection, _ = sock.accept()
except socket.timeout:
continue
with connection:
try:
raw = connection.makefile("rb").readline(1_048_576)
request = json.loads(raw.decode("utf-8"))
response = {"status": "ok", **handle_request(controller, request, executor)}
except ServiceUnavailable as exc:
response = {"status": "error", "error": str(exc), "retryable": True, "side_effect_state": "UNKNOWN"}
except Exception as exc:
response = {"status": "error", "error": type(exc).__name__, "retryable": False}
connection.sendall((json.dumps(response, sort_keys=True) + "\n").encode("utf-8"))
def socket_from_systemd() -> socket.socket:
if os.environ.get("LISTEN_FDS") != "1":
raise RuntimeError("SOCKET_ACTIVATION_REQUIRED")
return socket.fromfd(3, socket.AF_UNIX, socket.SOCK_STREAM)
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--job-root", required=True, type=Path)
args = parser.parse_args(argv)
policy = runtime_policy()
if policy["legacy_global_write"]:
raise RuntimeError("LEGACY_GLOBAL_WRITES_MUST_REMAIN_FALSE")
if not policy["durable_job_execution"]:
raise RuntimeError("DURABLE_JOB_EXECUTION_GATE_CLOSED")
controller = GitOpsJobController(JobStore(args.job_root))
from aios_gitops_job_executor import build_from_environment
executor = build_from_environment(args.job_root)
stopping = threading.Event()
signal.signal(signal.SIGTERM, lambda *_: stopping.set())
signal.signal(signal.SIGINT, lambda *_: stopping.set())
sock = socket_from_systemd()
try:
serve_socket(sock, controller, stopping, executor)
finally:
sock.close()
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,59 @@
"""Central fail-closed policy seam for governed task and continuation requests."""
from __future__ import annotations
from dataclasses import dataclass
import re
from typing import Any, Mapping
OID_RE = re.compile(r"^[0-9a-f]{40}$")
@dataclass(frozen=True, slots=True)
class GovernedPolicyInput:
identity: str
task_id: str
repository: str
operation: str
base_oid: str
expected_branch_oid: str
target_branch: str
generation: int
risk: str = "engineering"
requested_capabilities: tuple[str, ...] = ()
previous_generation: int | None = None
@dataclass(frozen=True, slots=True)
class PolicyDecision:
decision: str
reason: str
def as_dict(self) -> dict[str, str]:
return {"decision": self.decision, "reason": self.reason}
def evaluate_policy(value: GovernedPolicyInput | Mapping[str, Any]) -> PolicyDecision:
if isinstance(value, Mapping):
value = GovernedPolicyInput(**value)
if value.repository != "aios/aios":
return PolicyDecision("DENY", "REPOSITORY_IDENTITY_MISMATCH")
if not OID_RE.fullmatch(value.base_oid) or not OID_RE.fullmatch(value.expected_branch_oid):
return PolicyDecision("DENY", "OID_INVALID")
if not value.target_branch.startswith("agent/") or value.target_branch in {"main", "master"}:
return PolicyDecision("DENY", "BRANCH_SCOPE_DENIED")
if "force_push" in value.requested_capabilities or "force" in value.operation.lower():
return PolicyDecision("DENY", "FORCE_PUSH_DENIED")
if {"main_write", "tag_write", "baseline_write", "production_write", "production_deploy", "tag_mutation", "baseline_mutation"}.intersection(value.requested_capabilities):
return PolicyDecision("DENY", "MUTATION_SCOPE_DENIED")
if not isinstance(value.generation, int) or isinstance(value.generation, bool) or value.generation < 1:
return PolicyDecision("DENY", "GENERATION_INVALID")
if value.previous_generation is not None and value.generation <= value.previous_generation:
return PolicyDecision("MANUAL_OWNER_REVIEW_REQUIRED", "GENERATION_NOT_MONOTONIC")
if not value.task_id:
return PolicyDecision("DENY", "TASK_ID_INVALID")
return PolicyDecision("ALLOW", "GOVERNED_ENGINEERING_SCOPE")
class GovernedPolicy:
def decide(self, request: GovernedPolicyInput | Mapping[str, Any]) -> PolicyDecision:
return evaluate_policy(request)
@@ -0,0 +1,303 @@
"""Durable governed task binding for CREATE_NEW and CONTINUE_EXISTING."""
from __future__ import annotations
from dataclasses import dataclass, asdict
import hashlib
import json
import os
from pathlib import Path
import re
import stat
import tempfile
from typing import Any, Mapping
from aios_gitops_job_controller import GitOpsJobController, JobStore
from aios_gitops_job_identity import legacy_task_storage_key, task_storage_key
from aios_online_agent_execution import GovernedExecutionBinding, project_execution_envelope
try: # pragma: no cover - exercised on the Linux activation host
import fcntl
except ImportError: # pragma: no cover - non-POSIX execution stays fail closed
fcntl = None # type: ignore[assignment]
OID_RE = re.compile(r"^[0-9a-f]{40}$")
BRANCH_RE = re.compile(r"^agent/[A-Za-z0-9][A-Za-z0-9._/-]{2,179}$")
class BootstrapError(RuntimeError):
pass
def _lexists(path: Path) -> bool:
try:
path.lstat()
return True
except FileNotFoundError:
return False
def _require_runtime_directory(path: Path) -> None:
try:
metadata = path.lstat()
except OSError as exc:
raise BootstrapError("TASK_STORAGE_PARENT_INVALID") from exc
expected_uid = getattr(os, "geteuid", lambda: metadata.st_uid)()
if (
stat.S_ISLNK(metadata.st_mode)
or not stat.S_ISDIR(metadata.st_mode)
or metadata.st_mode & 0o022
or metadata.st_uid != expected_uid
):
raise BootstrapError("TASK_STORAGE_PARENT_INVALID")
@dataclass(frozen=True, slots=True)
class GovernedTaskBinding:
task_id: str
repository: str
base_oid: str
target_branch: str
risk: str
description: str
idempotency_key: str
expected_branch_oid: str
branch_mode: str
generation: int
job_id: str | None = None
def response(self, *, idempotent: bool = False) -> dict[str, Any]:
return {"task_id": self.task_id, "repository": self.repository, "base_oid": self.base_oid, "target_branch": self.target_branch, "expected_branch_oid": self.expected_branch_oid, "generation": self.generation, "branch_mode": self.branch_mode, "create_target_branch": self.branch_mode == "CREATE_NEW", "worktree_mode": "detached_at_base_oid" if self.branch_mode == "CREATE_NEW" else "detached_at_expected_head", "cas_ref_update_required": True, "idempotent": idempotent, "state": "BOUND", "job_id": self.job_id}
# ===== AIOS Formal Lineage producer-native reservation v12 =====
import re as _fl_re
_FL_PRODUCER_KEY = _fl_re.compile(r"^[0-9a-f]{64}$")
def _formal_lineage_reservation_receipt(
value,
*,
request_idempotency_key: str,
expected_side_effect_state: str,
):
if callable(getattr(value, "as_dict", None)):
raw=value.as_dict()
elif isinstance(value, dict):
raw=dict(value)
else:
raise RuntimeError("GOVERNED_TASK_SUBMIT_RECEIPT_TYPE_INVALID")
producer_key=raw.get("idempotency_key")
job_id=raw.get("job_id")
if not isinstance(job_id,str) or not job_id:
raise RuntimeError("GOVERNED_TASK_SUBMIT_RECEIPT_JOB_ID_INVALID")
if raw.get("state")!="REQUESTED":
raise RuntimeError("GOVERNED_TASK_SUBMIT_RECEIPT_STATE_INVALID")
if raw.get("side_effect_state")!=expected_side_effect_state:
raise RuntimeError("GOVERNED_TASK_SUBMIT_RECEIPT_SIDE_EFFECT_STATE_INVALID")
if not isinstance(producer_key,str) or not _FL_PRODUCER_KEY.fullmatch(producer_key):
raise RuntimeError("GOVERNED_TASK_SUBMIT_RECEIPT_PRODUCER_IDEMPOTENCY_INVALID")
if job_id!="job-"+producer_key[:32]:
raise RuntimeError("GOVERNED_TASK_SUBMIT_RECEIPT_JOB_PRODUCER_KEY_CONFLICT")
if not isinstance(request_idempotency_key,str) or not request_idempotency_key:
raise RuntimeError("GOVERNED_TASK_REQUEST_IDEMPOTENCY_INVALID")
return {
"job_id":job_id,
"state":"REQUESTED",
"side_effect_state":raw["side_effect_state"],
"producer_idempotency_key":producer_key,
"request_idempotency_key":request_idempotency_key,
}
def _formal_lineage_attach_receipt(response, receipt):
if not isinstance(response, dict):
raise RuntimeError("GOVERNED_TASK_BINDING_RESPONSE_INVALID")
if response.get("state")!="BOUND":
raise RuntimeError("GOVERNED_TASK_BINDING_STATE_INVALID")
if response.get("job_id")!=receipt.get("job_id"):
raise RuntimeError("GOVERNED_TASK_BINDING_RECEIPT_JOB_CONFLICT")
result=dict(response)
result["reservation_receipt"]=dict(receipt)
return result
class GovernedTaskBootstrap:
def __init__(self, authority: Any, root: Path, *, remote_name: str, token_path: Path, ssh_key_path: Path, api_base: str = "https://g.cnnetmkt.top/git/api/v1") -> None:
self.authority = authority; self.root = Path(root); self.remote_name = remote_name; self.token_path = Path(token_path); self.ssh_key_path = Path(ssh_key_path); self.api_base = api_base; self.task_meta_root = self.root / "task-bindings"; self.job_root = self.root / "jobs"; self.lock_root = self.root / "task-binding-locks"; self.task_meta_root.mkdir(parents=True, exist_ok=True, mode=0o700); self.job_root.mkdir(parents=True, exist_ok=True, mode=0o700); self.lock_root.mkdir(parents=True, exist_ok=True, mode=0o700); _require_runtime_directory(self.task_meta_root); _require_runtime_directory(self.job_root); _require_runtime_directory(self.lock_root)
def _store(self, task_id: str) -> tuple[JobStore, GitOpsJobController]:
root = self.job_root / task_storage_key(task_id)
legacy = self.job_root / legacy_task_storage_key(task_id)
if legacy != root and _lexists(legacy):
if _lexists(root):
raise BootstrapError("TASK_STORAGE_LEGACY_CONFLICT")
raise BootstrapError("TASK_STORAGE_LEGACY_MIGRATION_REQUIRED")
store = JobStore(root, payload_root=root / "payloads")
return store, GitOpsJobController(store)
def _metadata(self, task_id: str) -> Path:
return self.task_meta_root / (task_storage_key(task_id) + ".json")
def _legacy_metadata(self, task_id: str) -> Path:
return self.task_meta_root / (legacy_task_storage_key(task_id) + ".json")
def _read_existing(self, task_id: str) -> GovernedTaskBinding | None:
path = self._metadata(task_id)
legacy = self._legacy_metadata(task_id)
if legacy != path and _lexists(legacy):
if _lexists(path):
raise BootstrapError("TASK_BINDING_LEGACY_CONFLICT")
raise BootstrapError("TASK_BINDING_LEGACY_MIGRATION_REQUIRED")
if not _lexists(path):
return None
if not path.is_file() or path.is_symlink():
raise BootstrapError("TASK_BINDING_PATH_INVALID")
binding = GovernedTaskBinding(**json.loads(path.read_text(encoding="utf-8")))
if binding.task_id != task_id:
raise BootstrapError("TASK_BINDING_IDENTITY_CONFLICT")
return binding
def get_binding(self, task_id: str) -> GovernedTaskBinding:
"""Read the exact durable binding; never synthesize missing lineage."""
binding = self._read_existing(str(task_id))
if binding is None or not binding.job_id:
raise BootstrapError("TASK_EXECUTION_PROJECTION_MISSING")
return binding
def execution_binding(self, binding: GovernedTaskBinding) -> GovernedExecutionBinding:
return GovernedExecutionBinding.from_mapping(
{
"task_id": binding.task_id,
"job_id": binding.job_id,
"repository": binding.repository,
"base_oid": binding.base_oid,
"expected_branch_oid": binding.expected_branch_oid,
"target_branch": binding.target_branch,
"branch_mode": binding.branch_mode,
"generation": binding.generation,
"idempotency_key": binding.idempotency_key,
}
)
def execution_envelope(
self,
task_id: str,
execution_request: Mapping[str, Any] | None = None,
*,
required_capabilities: tuple[str, ...] = (),
expected_job_id: str | None = None,
) -> dict[str, Any]:
binding = self.get_binding(task_id)
if expected_job_id and binding.job_id != expected_job_id:
raise BootstrapError("JOB_BINDING_CONFLICT")
return project_execution_envelope(
self.execution_binding(binding),
execution_request,
required_capabilities=required_capabilities,
)
def _write(self, binding: GovernedTaskBinding) -> None:
path = self._metadata(binding.task_id)
descriptor, temporary_name = tempfile.mkstemp(
prefix=".task-binding-", dir=self.task_meta_root,
)
temporary = Path(temporary_name)
try:
os.fchmod(descriptor, 0o600)
with os.fdopen(descriptor, "w", encoding="utf-8", newline="\n") as stream:
stream.write(json.dumps(asdict(binding), sort_keys=True) + "\n")
stream.flush()
os.fsync(stream.fileno())
os.replace(temporary, path)
os.chmod(path, 0o600)
parent = os.open(self.task_meta_root, os.O_RDONLY | getattr(os, "O_DIRECTORY", 0))
try:
os.fsync(parent)
finally:
os.close(parent)
except BaseException:
temporary.unlink(missing_ok=True)
raise
def _task_lock(self, task_id: str) -> int:
if fcntl is None:
raise BootstrapError("TASK_BINDING_LOCK_UNAVAILABLE")
path = self.lock_root / (task_storage_key(task_id) + ".lock")
descriptor = os.open(
path,
os.O_RDWR | os.O_CREAT | getattr(os, "O_NOFOLLOW", 0),
0o600,
)
metadata = os.fstat(descriptor)
expected_uid = getattr(os, "geteuid", lambda: metadata.st_uid)()
if (
not stat.S_ISREG(metadata.st_mode)
or metadata.st_nlink != 1
or metadata.st_uid != expected_uid
):
os.close(descriptor)
raise BootstrapError("TASK_BINDING_LOCK_INVALID")
os.fchmod(descriptor, 0o600)
try:
fcntl.flock(descriptor, fcntl.LOCK_EX | fcntl.LOCK_NB)
except OSError as exc:
os.close(descriptor)
raise BootstrapError("TASK_BINDING_BUSY") from exc
return descriptor
@staticmethod
def _unlock_task(descriptor: int) -> None:
if fcntl is not None:
fcntl.flock(descriptor, fcntl.LOCK_UN)
os.close(descriptor)
def create(self, request: Mapping[str, Any]) -> dict[str, Any]:
values = {key: str(request[key]) for key in ("task_id", "repository", "base_oid", "target_branch", "risk", "description", "idempotency_key")}
if values["repository"] != "aios/aios" or not OID_RE.fullmatch(values["base_oid"]): raise BootstrapError("TASK_BINDING_INVALID")
if not BRANCH_RE.fullmatch(values["target_branch"]): raise BootstrapError("TARGET_BRANCH_INVALID")
descriptor = self._task_lock(values["task_id"])
try:
existing = self._read_existing(values["task_id"])
if existing is not None:
if any(getattr(existing, key) != values[key] for key in ("repository", "base_oid", "target_branch", "risk", "description", "idempotency_key")): raise BootstrapError("TASK_BINDING_CONFLICT")
expected = str(request.get('expected_branch_oid') or values['base_oid'])
payload = json.dumps(values, sort_keys=True, separators=(',', ':')).encode()
_, controller = self._store(values['task_id'])
_fl_submit_receipt = controller.submit({'idempotency_key': values['idempotency_key'], 'campaign_id': values['task_id'], 'task_id': values['task_id'], 'repository': values['repository'], 'operation': 'GOVERNED_TASK_CREATE', 'branch': values['target_branch'], 'expected_branch_oid': expected, 'payload_digest': hashlib.sha256(payload).hexdigest(), 'lineage_anchor_oid': values['base_oid']}, payload=payload)
_fl_receipt = _formal_lineage_reservation_receipt(
_fl_submit_receipt, request_idempotency_key=request["idempotency_key"],
expected_side_effect_state='SAME_JOB'
)
_fl_response = existing.response(idempotent=True)
return _formal_lineage_attach_receipt(_fl_response,_fl_receipt)
if not self.authority.commit_exists(values["base_oid"]): raise BootstrapError("BASE_COMMIT_NOT_FOUND")
current = self.authority.read_ref("refs/heads/" + values["target_branch"])
expected = str(request.get("expected_branch_oid") or values["base_oid"])
if current is None:
mode, generation = "CREATE_NEW", 1
if expected != values["base_oid"]: raise BootstrapError("EXPECTED_BASE_CONFLICT")
else:
if expected != current: raise BootstrapError("EXISTING_BRANCH_OID_CONFLICT")
mode, generation = "CONTINUE_EXISTING", int(request.get("generation", 2))
if generation < 1: raise BootstrapError("GENERATION_INVALID")
payload = json.dumps(values, sort_keys=True, separators=(",", ":")).encode()
_, controller = self._store(values["task_id"])
expected_job_id = controller.internal_job_id_for_idempotency_key(values["idempotency_key"])
with controller.store._connect() as connection:
bootstrap_jobs = connection.execute(
"SELECT job_id FROM jobs WHERE operation='GOVERNED_TASK_CREATE' ORDER BY job_id"
).fetchall()
if bootstrap_jobs and [str(row[0]) for row in bootstrap_jobs] != [expected_job_id]:
raise BootstrapError("TASK_BINDING_RECOVERY_CONFLICT")
receipt = controller.submit({"idempotency_key": values["idempotency_key"], "campaign_id": values["task_id"], "task_id": values["task_id"], "repository": values["repository"], "operation": "GOVERNED_TASK_CREATE", "branch": values["target_branch"], "expected_branch_oid": expected, "payload_digest": hashlib.sha256(payload).hexdigest(), "lineage_anchor_oid": values["base_oid"]}, payload=payload)
binding = GovernedTaskBinding(**values, expected_branch_oid=expected, branch_mode=mode, generation=generation, job_id=receipt.job_id)
self._write(binding)
_fl_receipt = _formal_lineage_reservation_receipt(
receipt, request_idempotency_key=request["idempotency_key"],
expected_side_effect_state="QUEUED"
)
_fl_response = binding.response()
return _formal_lineage_attach_receipt(_fl_response,_fl_receipt)
finally:
self._unlock_task(descriptor)
@@ -0,0 +1,890 @@
"""Read-only four-authority evidence provider for one canonical C04 attachment.
This module never creates, claims, resumes, reconciles, or mutates a Task or
Job. The expired Owner epoch is verified as historical provenance only.
"""
from __future__ import annotations
import hashlib
import json
import os
import sqlite3
import stat
import time
import urllib.error
import urllib.parse
import urllib.request
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Callable, Mapping
from app.aios_gitops_job_identity import legacy_task_storage_key, task_storage_key
from app.aios_single_host_authority import (
DEFAULT_OWNER_SIGNER_ID,
OWNER_NAMESPACE,
RegistryOwnerApprovalVerifier,
SignerRegistry,
)
from app.task_plane.legacy_attachment import (
ATTACH_EVIDENCE_SCHEMA,
AuthorityReadbackRef,
SOURCE_KIND,
VerifiedLegacyAttachmentEvidence,
)
class LegacyAttachmentRuntimeError(RuntimeError):
"""Stable fail-closed readback error."""
def _fail(code: str) -> None:
raise LegacyAttachmentRuntimeError(code)
def _strict_object(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
value: dict[str, Any] = {}
for key, item in pairs:
if key in value:
_fail("DUPLICATE_JSON_FIELD")
value[key] = item
return value
def _json_object(raw: bytes | str, code: str) -> dict[str, Any]:
try:
if isinstance(raw, bytes):
raw = raw.decode("utf-8")
value = json.loads(raw, object_pairs_hook=_strict_object)
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
raise LegacyAttachmentRuntimeError(code) from exc
if not isinstance(value, dict):
_fail(code)
return value
def _canonical(value: object) -> bytes:
try:
return json.dumps(
value,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
allow_nan=False,
).encode("utf-8")
except (TypeError, ValueError) as exc:
raise LegacyAttachmentRuntimeError("AUTHORITY_JSON_INVALID") from exc
def _sha(value: object) -> str:
return hashlib.sha256(_canonical(value)).hexdigest()
def _hex(value: object, code: str) -> str:
if (
not isinstance(value, str)
or len(value) != 64
or any(ch not in "0123456789abcdef" for ch in value)
):
_fail(code)
return value
def _text(value: object, code: str) -> str:
if not isinstance(value, str) or not value or value != value.strip():
_fail(code)
return value
def _integer(value: object, code: str) -> int:
if type(value) is not int or value < 0:
_fail(code)
return value
def _iso(value: object, code: str) -> str:
text = _text(value, code)
try:
parsed = datetime.fromisoformat(text.replace("Z", "+00:00"))
except ValueError as exc:
raise LegacyAttachmentRuntimeError(code) from exc
if parsed.tzinfo is None or parsed.utcoffset() is None:
_fail(code)
return parsed.astimezone(timezone.utc).isoformat().replace("+00:00", "Z")
def _observed(clock: Callable[[], float]) -> str:
return datetime.fromtimestamp(float(clock()), timezone.utc).isoformat().replace(
"+00:00", "Z"
)
def _mapping(value: object, code: str) -> dict[str, Any]:
if not isinstance(value, Mapping):
_fail(code)
return dict(value)
def _secure_file(
path: Path, *, require_root_owner: bool, writable_authority: bool = False
) -> os.stat_result:
try:
info = path.lstat()
except OSError as exc:
raise LegacyAttachmentRuntimeError("AUTHORITY_FILE_UNAVAILABLE") from exc
if path.is_symlink() or not stat.S_ISREG(info.st_mode):
_fail("AUTHORITY_FILE_INVALID")
if os.name == "posix":
if not writable_authority and stat.S_IMODE(info.st_mode) & 0o022:
_fail("AUTHORITY_FILE_WRITABLE")
if require_root_owner and info.st_uid != 0:
_fail("AUTHORITY_FILE_OWNER_INVALID")
return info
def _secure_directory(path: Path) -> os.stat_result:
try:
info = path.lstat()
except OSError as exc:
raise LegacyAttachmentRuntimeError("AUTHORITY_DIRECTORY_UNAVAILABLE") from exc
if path.is_symlink() or not stat.S_ISDIR(info.st_mode):
_fail("AUTHORITY_DIRECTORY_INVALID")
return info
def _stable_read(path: Path, *, require_root_owner: bool, maximum: int = 2_000_000) -> bytes:
before = _secure_file(path, require_root_owner=require_root_owner)
if before.st_size > maximum:
_fail("AUTHORITY_FILE_TOO_LARGE")
try:
raw = path.read_bytes()
except OSError as exc:
raise LegacyAttachmentRuntimeError("AUTHORITY_FILE_UNAVAILABLE") from exc
after = _secure_file(path, require_root_owner=require_root_owner)
if (before.st_dev, before.st_ino, before.st_size, before.st_mtime_ns) != (
after.st_dev,
after.st_ino,
after.st_size,
after.st_mtime_ns,
):
_fail("AUTHORITY_FILE_CHANGED")
return raw
def _ro_connection(path: Path, *, require_root_owner: bool) -> tuple[sqlite3.Connection, os.stat_result]:
before = _secure_file(
path, require_root_owner=require_root_owner, writable_authority=True
)
try:
resolved = path.resolve(strict=True)
connection = sqlite3.connect(
resolved.as_uri() + "?mode=ro", uri=True, timeout=5.0
)
connection.row_factory = sqlite3.Row
connection.execute("PRAGMA query_only=ON")
connection.execute("PRAGMA foreign_keys=ON")
connection.execute("BEGIN")
return connection, before
except (OSError, sqlite3.Error) as exc:
raise LegacyAttachmentRuntimeError("AUTHORITY_DB_UNAVAILABLE") from exc
def _finish_ro(connection: sqlite3.Connection, path: Path, before: os.stat_result) -> None:
connection.close()
after = _secure_file(
path, require_root_owner=False, writable_authority=True
)
if (before.st_dev, before.st_ino) != (after.st_dev, after.st_ino):
_fail("AUTHORITY_DB_CHANGED")
def _columns(connection: sqlite3.Connection, table: str) -> tuple[str, ...]:
return tuple(str(row[1]) for row in connection.execute(f"PRAGMA table_info({table})"))
@dataclass(frozen=True, slots=True)
class LegacyC04Binding:
mission_id: str
root_task_id: str
anchor_task_id: str
owner_principal_id: str
original_acceptance_digest: str
source_task_id: str
external_job_id: str
def validate(self) -> None:
for value in (
self.mission_id,
self.root_task_id,
self.anchor_task_id,
self.owner_principal_id,
self.source_task_id,
self.external_job_id,
):
_text(value, "C04_BINDING_INVALID")
_hex(self.original_acceptance_digest, "C04_ACCEPTANCE_DIGEST_INVALID")
@dataclass(frozen=True, slots=True)
class CredentialFileBearerSupplier:
"""Read a systemd credential for one call without caching or displaying it."""
path: Path
def __post_init__(self) -> None:
if not self.path.is_absolute():
_fail("AGENT_TASK_CREDENTIAL_PATH_INVALID")
if os.name == "posix":
try:
self.path.resolve(strict=False).relative_to(Path("/run/credentials"))
except ValueError as exc:
raise LegacyAttachmentRuntimeError(
"AGENT_TASK_CREDENTIAL_PATH_INVALID"
) from exc
def __call__(self) -> str:
raw = _stable_read(self.path, require_root_owner=False, maximum=8192)
try:
value = raw.decode("utf-8").strip()
except UnicodeDecodeError as exc:
raise LegacyAttachmentRuntimeError(
"AGENT_TASK_CREDENTIAL_INVALID"
) from exc
if len(value) < 32 or len(value) > 4096:
_fail("AGENT_TASK_CREDENTIAL_INVALID")
return value
class _NoRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl): # noqa: ANN001
return None
_AGENT_TASK_COLUMNS = (
"task_id",
"goal",
"instructions",
"success_criteria_json",
"required_capabilities_json",
"constraints_json",
"metadata_json",
"deadline",
"priority",
"max_attempts",
"attempt",
"status",
"claimed_by",
"lease_token_hash",
"lease_expires_at",
"result_json",
"result_digest",
"result_idempotency_key",
"verification_json",
"revision",
"created_at",
"updated_at",
)
@dataclass(slots=True)
class AuthenticatedAgentTaskReader:
"""Authenticated non-mutating proof + read-only authority-row snapshot.
The authenticated HTTP request is intentionally sent to the task events
endpoint because the historical GET /v1/tasks/{id} path calls the global
lease-expiry mutator. The task row itself is read from the authoritative
SQLite database using query_only mode and an exact schema allowlist.
"""
base_url: str
bearer_supplier: Callable[[], str]
task_db_path: Path
timeout_seconds: float = 10.0
clock: Callable[[], float] = time.time
require_db_root_owner: bool = False
def __post_init__(self) -> None:
parsed = urllib.parse.urlsplit(self.base_url)
if (
parsed.scheme != "http"
or parsed.hostname not in {"127.0.0.1", "localhost", "::1"}
or parsed.username is not None
or parsed.password is not None
or parsed.query
or parsed.fragment
):
_fail("AGENT_TASK_URL_INVALID")
if not self.task_db_path.is_absolute():
_fail("AGENT_TASK_DB_PATH_INVALID")
self.base_url = self.base_url.rstrip("/")
def _authenticated_events(self, task_id: str) -> tuple[list[dict[str, Any]], str]:
token = self.bearer_supplier()
if not isinstance(token, str) or len(token.strip()) < 32:
_fail("AGENT_TASK_CREDENTIAL_INVALID")
url = (
self.base_url
+ "/v1/tasks/"
+ urllib.parse.quote(task_id, safe="._:-")
+ "/events"
)
request = urllib.request.Request(
url,
method="GET",
headers={
"Authorization": "Bearer " + token.strip(),
"Accept": "application/json",
},
)
try:
with urllib.request.build_opener(_NoRedirect).open(
request, timeout=self.timeout_seconds
) as response:
if response.status != 200 or response.geturl() != url:
_fail("AGENT_TASK_EVENTS_REJECTED")
raw = response.read(2_000_001)
except (urllib.error.URLError, TimeoutError, OSError) as exc:
raise LegacyAttachmentRuntimeError(
"AGENT_TASK_EVENTS_UNAVAILABLE"
) from exc
finally:
token = ""
if len(raw) > 2_000_000:
_fail("AGENT_TASK_EVENTS_TOO_LARGE")
try:
value = json.loads(raw.decode("utf-8"), object_pairs_hook=_strict_object)
except (UnicodeDecodeError, json.JSONDecodeError, ValueError) as exc:
raise LegacyAttachmentRuntimeError("AGENT_TASK_EVENTS_INVALID") from exc
if not isinstance(value, list):
_fail("AGENT_TASK_EVENTS_INVALID")
events: list[dict[str, Any]] = []
for event in value:
if not isinstance(event, Mapping) or event.get("task_id") != task_id:
_fail("AGENT_TASK_EVENTS_INVALID")
events.append(dict(event))
return events, url
@staticmethod
def _task_projection(row: sqlite3.Row) -> dict[str, Any]:
try:
body = {
"task_id": row["task_id"],
"goal": row["goal"],
"instructions": row["instructions"],
"success_criteria": json.loads(row["success_criteria_json"]),
"required_capabilities": json.loads(row["required_capabilities_json"]),
"constraints": json.loads(row["constraints_json"]),
"metadata": json.loads(row["metadata_json"]),
"deadline": row["deadline"],
"priority": row["priority"],
"max_attempts": row["max_attempts"],
"attempt": row["attempt"],
"status": row["status"],
"claimed_by": row["claimed_by"],
"lease_expires_at": row["lease_expires_at"],
"revision": row["revision"],
"created_at": row["created_at"],
"updated_at": row["updated_at"],
"result": json.loads(row["result_json"]) if row["result_json"] else None,
"verification": (
json.loads(row["verification_json"])
if row["verification_json"]
else None
),
}
except (KeyError, TypeError, json.JSONDecodeError) as exc:
raise LegacyAttachmentRuntimeError(
"AGENT_TASK_ROW_INVALID"
) from exc
_mapping(body["constraints"], "AGENT_TASK_CONSTRAINTS_INVALID")
_mapping(body["metadata"], "AGENT_TASK_METADATA_INVALID")
_integer(body["revision"], "AGENT_TASK_REVISION_INVALID")
if body["status"] not in {"SUCCEEDED", "FAILED", "EXPIRED", "CANCELLED"}:
_fail("AGENT_TASK_NOT_TERMINAL")
return body
def read(self, task_id: str) -> dict[str, Any]:
events, events_url = self._authenticated_events(task_id)
connection, before = _ro_connection(
self.task_db_path, require_root_owner=self.require_db_root_owner
)
try:
if _columns(connection, "tasks") != _AGENT_TASK_COLUMNS:
_fail("AGENT_TASK_DB_SCHEMA_INVALID")
row = connection.execute(
"SELECT * FROM tasks WHERE task_id=? LIMIT 1", (task_id,)
).fetchone()
if row is None:
_fail("AGENT_TASK_NOT_FOUND")
body = self._task_projection(row)
finally:
_finish_ro(connection, self.task_db_path, before)
event_digest = _sha(events)
return {
"body": body,
"observed_at": _observed(self.clock),
"readback_ref": (
events_url
+ "#authenticated-events-sha256="
+ event_digest
+ ";task-db-revision="
+ str(body["revision"])
),
"canonical_sha256": _sha(body),
"authenticated_events_sha256": event_digest,
}
_ADMISSION_COLUMNS = {
"task_admission_envelopes": (
"idempotency_key", "envelope_id", "request_digest", "envelope_json", "status", "created_at"
),
"task_admission_grants": (
"envelope_id", "grant_id", "grant_digest", "grant_json", "authorization_digest", "created_at"
),
"task_admission_receipts": ("envelope_id", "task_id", "receipt_json", "created_at"),
}
@dataclass(slots=True)
class AdmissionEpochRegistryReader:
db_path: Path
epoch_root: Path
revocation_file: Path
registry_path: Path
require_db_root_owner: bool = False
require_static_root_owner: bool = True
clock: Callable[[], float] = time.time
def __post_init__(self) -> None:
for path in (self.db_path, self.epoch_root, self.revocation_file, self.registry_path):
if not path.is_absolute():
_fail("AUTHORITY_PATH_NOT_ABSOLUTE")
def read(self, task_id: str) -> dict[str, Any]:
connection, before = _ro_connection(
self.db_path, require_root_owner=self.require_db_root_owner
)
try:
if any(_columns(connection, table) != columns for table, columns in _ADMISSION_COLUMNS.items()):
_fail("ADMISSION_SCHEMA_INVALID")
rows = connection.execute(
"SELECT e.*,g.grant_id,g.grant_digest,g.grant_json,g.authorization_digest,"
"g.created_at AS grant_created_at,r.task_id,r.receipt_json,"
"r.created_at AS receipt_created_at FROM task_admission_receipts r "
"JOIN task_admission_envelopes e ON e.envelope_id=r.envelope_id "
"JOIN task_admission_grants g ON g.envelope_id=r.envelope_id WHERE r.task_id=?",
(task_id,),
).fetchall()
if len(rows) != 1:
_fail("ADMISSION_READBACK_AMBIGUOUS")
row = dict(rows[0])
except sqlite3.Error as exc:
raise LegacyAttachmentRuntimeError("ADMISSION_READBACK_FAILED") from exc
finally:
_finish_ro(connection, self.db_path, before)
envelope = _json_object(row["envelope_json"], "ADMISSION_ENVELOPE_INVALID")
grant = _json_object(row["grant_json"], "ADMISSION_GRANT_INVALID")
receipt = _json_object(row["receipt_json"], "ADMISSION_RECEIPT_INVALID")
envelope_id = _text(row["envelope_id"], "ADMISSION_ENVELOPE_INVALID")
grant_id = _text(row["grant_id"], "ADMISSION_GRANT_INVALID")
epoch_id = _text(grant.get("parent_epoch_id"), "ADMISSION_EPOCH_ID_INVALID")
if (
row.get("status") != "ADMITTED"
or envelope.get("schema") != "aios.task-admission-envelope.v1"
or grant.get("schema") != "aios.task-admission-grant.v1"
or receipt.get("schema") != "aios.task-admission-receipt.v1"
or envelope.get("envelope_id") != envelope_id
or grant.get("envelope_id") != envelope_id
or receipt.get("envelope_id") != envelope_id
or envelope.get("task_id") != task_id
or grant.get("task_id") != task_id
or receipt.get("task_id") != task_id
or grant.get("grant_id") != grant_id
or receipt.get("grant_id") != grant_id
or grant.get("authorization_kind") != "delegation_epoch"
or receipt.get("authorization_kind") != "delegation_epoch"
or receipt.get("parent_epoch_id") != epoch_id
):
_fail("ADMISSION_BINDING_INVALID")
request_value = {
key: envelope.get(key)
for key in (
"task_id", "repository", "authority_base_oid", "target_branch",
"generation", "execution_digest", "requested_capabilities",
"file_allowlist", "risk", "trust_domain", "policy_digest",
)
}
if envelope.get("request_digest") != _sha(request_value) or row.get("request_digest") != _sha(request_value):
_fail("ADMISSION_REQUEST_DIGEST_INVALID")
grant_digest = _sha(grant)
if row.get("grant_digest") != grant_digest or receipt.get("grant_digest_sha256") != grant_digest:
_fail("ADMISSION_GRANT_DIGEST_INVALID")
auth_value = {
"envelope_id": envelope_id,
"authorization_kind": "delegation_epoch",
"delegation_epoch_id": epoch_id,
}
if row.get("authorization_digest") != _sha(auth_value):
_fail("ADMISSION_AUTHORIZATION_DIGEST_INVALID")
linked = (
"repository", "authority_base_oid", "target_branch", "generation",
"execution_digest", "policy_digest", "trust_domain", "risk",
)
if any(grant.get(key) != envelope.get(key) for key in linked):
_fail("ADMISSION_GRANT_SCOPE_INVALID")
if grant.get("capabilities") != envelope.get("requested_capabilities") or grant.get("file_allowlist") != envelope.get("file_allowlist"):
_fail("ADMISSION_GRANT_SCOPE_INVALID")
if receipt.get("execution_digest") != grant.get("execution_digest"):
_fail("ADMISSION_RECEIPT_SCOPE_INVALID")
_secure_directory(self.epoch_root)
canonical_path = self.epoch_root / "owner-delegation-epoch.canonical.json"
signature_path = self.epoch_root / "owner-delegation-epoch.sig"
key_path = self.epoch_root / "owner-delegation-epoch.key-id"
canonical = _stable_read(canonical_path, require_root_owner=self.require_static_root_owner)
epoch = _json_object(canonical, "OWNER_EPOCH_INVALID")
if _canonical(epoch) != canonical:
_fail("OWNER_EPOCH_NOT_CANONICAL")
signature = _stable_read(signature_path, require_root_owner=self.require_static_root_owner).decode("utf-8")
key_id = _stable_read(key_path, require_root_owner=self.require_static_root_owner).decode("utf-8").strip()
registry_raw = _stable_read(self.registry_path, require_root_owner=self.require_static_root_owner)
registry_json = _json_object(registry_raw, "OWNER_REGISTRY_INVALID")
revocations = _json_object(
_stable_read(self.revocation_file, require_root_owner=self.require_static_root_owner),
"OWNER_REVOCATIONS_INVALID",
)
if (
set(revocations) != {"schema", "revoked_epoch_ids"}
or revocations.get("schema") != "aios.owner-delegation-revocations.v1"
or not isinstance(revocations.get("revoked_epoch_ids"), list)
or epoch_id in revocations["revoked_epoch_ids"]
):
_fail("OWNER_EPOCH_REVOKED")
try:
registry = SignerRegistry(
self.registry_path, require_root_owner=self.require_static_root_owner
)
record = registry.require(DEFAULT_OWNER_SIGNER_ID, role="OWNER_AUTHORITY")
verified = RegistryOwnerApprovalVerifier(
self.registry_path,
require_root_owner=self.require_static_root_owner,
signer_id=DEFAULT_OWNER_SIGNER_ID,
).verify(
namespace=OWNER_NAMESPACE,
payload=canonical,
signature=signature,
key_id=key_id,
)
except Exception as exc:
raise LegacyAttachmentRuntimeError("OWNER_REGISTRY_VERIFICATION_FAILED") from exc
now = int(self.clock())
provenance_checks = {
"signature": verified is True,
"fingerprint": record.fingerprint == key_id,
"schema": epoch.get("schema") == "aios.owner-delegation-epoch.v1",
"epoch_id": epoch.get("epoch_id") == epoch_id,
"owner": epoch.get("owner_identity") == grant.get("owner_identity"),
"repository": epoch.get("repository") == grant.get("repository"),
"trust_domain": epoch.get("trust_domain") == grant.get("trust_domain"),
"policy": epoch.get("policy_digest") == grant.get("policy_digest"),
"revocation_epoch": epoch.get("revocation_epoch") == 0,
"expires_type": type(epoch.get("expires_at")) is int,
"not_before_type": type(epoch.get("not_before")) is int,
}
failed = [name for name, passed in provenance_checks.items() if not passed]
if failed:
_fail("OWNER_EPOCH_PROVENANCE_INVALID:" + ",".join(sorted(failed)))
if (
now < epoch["expires_at"]
or type(epoch.get("issued_at")) is not int
or epoch["issued_at"] > grant.get("issued_at", 0)
or grant.get("issued_at", 0) < epoch["not_before"]
or grant.get("expires_at", 0) > epoch["expires_at"]
):
_fail("OWNER_EPOCH_PROVENANCE_INVALID:temporal")
if not any(str(grant["target_branch"]).startswith(str(prefix)) for prefix in epoch.get("allowed_branch_prefixes", [])):
_fail("OWNER_EPOCH_SCOPE_INVALID")
if not set(grant.get("capabilities", [])).issubset(set(epoch.get("allowed_capabilities", []))):
_fail("OWNER_EPOCH_SCOPE_INVALID")
risk_rank = {"low": 0, "medium": 1, "high": 2}
if (
grant.get("risk") not in risk_rank
or epoch.get("risk_ceiling") not in risk_rank
or risk_rank[grant["risk"]] > risk_rank[epoch["risk_ceiling"]]
):
_fail("OWNER_EPOCH_SCOPE_INVALID")
if any(
not any(path == prefix.rstrip("/") or path.startswith(prefix.rstrip("/") + "/") for prefix in epoch.get("allowed_file_prefixes", []))
for path in grant.get("file_allowlist", [])
):
_fail("OWNER_EPOCH_SCOPE_INVALID")
admission_projection = {
"envelope": envelope,
"grant": grant,
"receipt": receipt,
"request_digest": row["request_digest"],
"grant_digest": row["grant_digest"],
"authorization_digest": row["authorization_digest"],
"envelope_status": row["status"],
}
owner_projection = {
"epoch": epoch,
"epoch_sha256": hashlib.sha256(canonical).hexdigest(),
"epoch_key_id": key_id,
"signature_verified": True,
"epoch_currently_valid": False,
"revocations": revocations,
"registry_schema": registry_json.get("schema"),
"registry_generation": registry.authority_generation,
"signer": record.to_dict(),
}
observed = _observed(self.clock)
return {
"envelope": envelope,
"grant": grant,
"receipt": receipt,
"epoch": epoch,
"epoch_digest": hashlib.sha256(canonical).hexdigest(),
"signer_id": record.signer_id,
"signer_fingerprint": record.fingerprint,
"registry_generation": registry.authority_generation,
"observed_at": observed,
"admission_ref": f"sqlite:{self.db_path}#task={task_id};envelope={envelope_id}",
"admission_sha256": _sha(admission_projection),
"registry_ref": f"file:{self.registry_path}#generation={registry.authority_generation};epoch={epoch_id}",
"registry_sha256": _sha(owner_projection),
}
_JOB_COLUMNS = {
"job_aliases": (
"external_job_id", "internal_job_id", "task_id", "repository", "target_branch",
"branch_mode", "generation", "commit_idempotency_key", "expected_branch_oid",
"request_digest", "adopted_legacy", "created_at",
),
"jobs": (
"job_id", "idempotency_key", "campaign_id", "repository", "operation", "branch",
"expected_branch_oid", "payload_digest", "payload_ref", "payload_size", "state",
"side_effect_state", "revision", "request_json", "receipt_json", "created_at", "updated_at",
),
"idempotency_keys": ("key", "job_id", "binding_json", "created_at"),
}
@dataclass(slots=True)
class BridgeJobSQLiteReader:
jobs_root: Path
require_root_owner: bool = False
clock: Callable[[], float] = time.time
def __post_init__(self) -> None:
if not self.jobs_root.is_absolute():
_fail("BRIDGE_JOB_ROOT_NOT_ABSOLUTE")
def read(self, task_id: str, external_job_id: str) -> dict[str, Any]:
exact_root = self.jobs_root / task_storage_key(task_id)
legacy_root = self.jobs_root / legacy_task_storage_key(task_id)
db_path = exact_root / "state.sqlite3"
if legacy_root != exact_root and legacy_root.exists():
_fail("BRIDGE_JOB_STORE_AMBIGUOUS")
_secure_directory(self.jobs_root)
_secure_directory(exact_root)
try:
if db_path.resolve(strict=True).parent != exact_root.resolve(strict=True):
_fail("BRIDGE_JOB_STORE_INVALID")
except OSError as exc:
raise LegacyAttachmentRuntimeError("BRIDGE_JOB_STORE_INVALID") from exc
connection, before = _ro_connection(
db_path, require_root_owner=self.require_root_owner
)
try:
if any(_columns(connection, table) != columns for table, columns in _JOB_COLUMNS.items()):
_fail("BRIDGE_JOB_SCHEMA_INVALID")
aliases = connection.execute(
"SELECT * FROM job_aliases WHERE external_job_id=? AND task_id=?",
(external_job_id, task_id),
).fetchall()
if len(aliases) != 1:
_fail("BRIDGE_JOB_ALIAS_AMBIGUOUS")
alias = dict(aliases[0])
job_rows = connection.execute(
"SELECT * FROM jobs WHERE job_id=?", (alias["internal_job_id"],)
).fetchall()
key_rows = connection.execute(
"SELECT * FROM idempotency_keys WHERE job_id=?", (alias["internal_job_id"],)
).fetchall()
if len(job_rows) != 1 or len(key_rows) != 1:
_fail("BRIDGE_JOB_BINDING_MISSING")
job, key = dict(job_rows[0]), dict(key_rows[0])
except sqlite3.Error as exc:
raise LegacyAttachmentRuntimeError("BRIDGE_JOB_READBACK_FAILED") from exc
finally:
_finish_ro(connection, db_path, before)
request = _json_object(job.get("request_json") or "{}", "BRIDGE_JOB_REQUEST_INVALID")
binding = _mapping(request.get("binding", request), "BRIDGE_JOB_REQUEST_INVALID")
key_binding = _json_object(key.get("binding_json") or "{}", "BRIDGE_JOB_KEY_BINDING_INVALID")
external_commit_key = _text(alias.get("commit_idempotency_key"), "BRIDGE_EXTERNAL_IDEMPOTENCY_INVALID")
internal_key = _text(job.get("idempotency_key"), "BRIDGE_INTERNAL_IDEMPOTENCY_INVALID")
if (
alias.get("external_job_id") != external_job_id
or alias.get("task_id") != task_id
or job.get("job_id") != alias.get("internal_job_id")
or job.get("campaign_id") != task_id
or key.get("job_id") != job.get("job_id")
or key.get("key") != internal_key
or binding.get("task_id", binding.get("campaign_id")) != task_id
or binding.get("external_job_id") != external_job_id
or binding.get("idempotency_key") != external_commit_key
or key_binding.get("task_id", key_binding.get("campaign_id")) != task_id
or key_binding.get("external_job_id") != external_job_id
or key_binding.get("idempotency_key") != external_commit_key
or alias.get("repository") != job.get("repository")
or alias.get("repository") != binding.get("repository")
or alias.get("target_branch") != job.get("branch")
or alias.get("target_branch") != binding.get("branch", binding.get("target_branch"))
):
_fail("BRIDGE_JOB_BINDING_INVALID")
try:
if int(alias.get("generation")) != int(binding.get("generation")):
_fail("BRIDGE_JOB_BINDING_INVALID")
except (TypeError, ValueError) as exc:
raise LegacyAttachmentRuntimeError("BRIDGE_JOB_BINDING_INVALID") from exc
projection = {
"alias": alias,
"job": job,
"idempotency": key,
"external_commit_idempotency_key": external_commit_key,
"internal_idempotency_key": internal_key,
}
return {
**projection,
"observed_at": _observed(self.clock),
"readback_ref": f"sqlite:{db_path}#external={external_job_id};internal={job['job_id']}",
"canonical_sha256": _sha(projection),
}
@dataclass(slots=True)
class LegacyAttachmentRuntimeProvider:
binding: LegacyC04Binding
agent_task: AuthenticatedAgentTaskReader
admission: AdmissionEpochRegistryReader
bridge_job: BridgeJobSQLiteReader
def __post_init__(self) -> None:
self.binding.validate()
def read_verified(
self,
*,
mission_id: str,
root_task_id: str,
anchor_task_id: str,
source_kind: str,
source_task_id: str,
external_job_id: str,
original_acceptance_digest: str,
) -> VerifiedLegacyAttachmentEvidence:
expected = self.binding
if (
mission_id != expected.mission_id
or root_task_id != expected.root_task_id
or anchor_task_id != expected.anchor_task_id
or source_kind != SOURCE_KIND
or source_task_id != expected.source_task_id
or external_job_id != expected.external_job_id
or original_acceptance_digest != expected.original_acceptance_digest
):
_fail("C04_AUTHORITY_BINDING_MISMATCH")
task_read = self.agent_task.read(source_task_id)
admission_read = self.admission.read(source_task_id)
job_read = self.bridge_job.read(source_task_id, external_job_id)
task = task_read["body"]
metadata = _mapping(task["metadata"], "AGENT_TASK_METADATA_INVALID")
constraints = _mapping(task["constraints"], "AGENT_TASK_CONSTRAINTS_INVALID")
execution = _mapping(metadata.get("execution"), "AGENT_TASK_EXECUTION_INVALID")
grant = admission_read["grant"]
envelope = admission_read["envelope"]
alias, job = job_read["alias"], job_read["job"]
try:
immutable_metadata = _json_object(
constraints.get("immutable_metadata", ""), "AGENT_TASK_IMMUTABLE_METADATA_INVALID"
)
except LegacyAttachmentRuntimeError:
raise
if (
immutable_metadata != metadata
or metadata.get("grant_id") != grant.get("grant_id")
or metadata.get("grant_digest_sha256") != admission_read["receipt"].get("grant_digest_sha256")
or metadata.get("execution_digest") != grant.get("execution_digest")
or execution.get("execution_digest") != grant.get("execution_digest")
or execution.get("external_job_id") != external_job_id
or execution.get("task_id") != source_task_id
or metadata.get("repository") != grant.get("repository")
or metadata.get("target_branch") != grant.get("target_branch")
or metadata.get("generation") != grant.get("generation")
or constraints.get("repository") != grant.get("repository")
or constraints.get("target_branch") != grant.get("target_branch")
or constraints.get("generation") != grant.get("generation")
or envelope.get("task_id") != source_task_id
or alias.get("repository") != grant.get("repository")
or alias.get("target_branch") != grant.get("target_branch")
or alias.get("generation") != grant.get("generation")
or admission_read["signer_id"] != expected.owner_principal_id
):
_fail("FOUR_AUTHORITY_BINDING_INVALID")
return VerifiedLegacyAttachmentEvidence(
schema=ATTACH_EVIDENCE_SCHEMA,
source_kind=SOURCE_KIND,
source_task_id=source_task_id,
source_task_created_at=_iso(task.get("created_at"), "AGENT_TASK_CREATED_AT_INVALID"),
source_task_status=_text(task.get("status"), "AGENT_TASK_STATUS_INVALID"),
source_task_revision=_integer(task.get("revision"), "AGENT_TASK_REVISION_INVALID"),
source_task_attempt=_integer(task.get("attempt"), "AGENT_TASK_ATTEMPT_INVALID"),
source_task_max_attempts=_integer(task.get("max_attempts"), "AGENT_TASK_MAX_ATTEMPTS_INVALID"),
external_job_id=external_job_id,
internal_job_id=_text(job.get("job_id"), "BRIDGE_INTERNAL_JOB_INVALID"),
internal_idempotency_key=_text(job.get("idempotency_key"), "BRIDGE_INTERNAL_IDEMPOTENCY_INVALID"),
job_branch=_text(job.get("branch"), "BRIDGE_JOB_BRANCH_INVALID"),
job_state=_text(job.get("state"), "BRIDGE_JOB_STATE_INVALID"),
job_revision=_integer(job.get("revision"), "BRIDGE_JOB_REVISION_INVALID"),
side_effect_state=_text(job.get("side_effect_state"), "BRIDGE_SIDE_EFFECT_INVALID"),
grant_id=_text(grant.get("grant_id"), "ADMISSION_GRANT_INVALID"),
grant_digest=_hex(admission_read["receipt"].get("grant_digest_sha256"), "ADMISSION_GRANT_DIGEST_INVALID"),
execution_digest=_hex(grant.get("execution_digest"), "ADMISSION_EXECUTION_DIGEST_INVALID"),
epoch_id=_text(admission_read["epoch"].get("epoch_id"), "OWNER_EPOCH_ID_INVALID"),
epoch_digest=_hex(admission_read["epoch_digest"], "OWNER_EPOCH_DIGEST_INVALID"),
historical_signer_id=admission_read["signer_id"],
historical_signer_fingerprint=admission_read["signer_fingerprint"],
registry_generation=_integer(admission_read["registry_generation"], "OWNER_REGISTRY_GENERATION_INVALID"),
signature_verified=True,
epoch_currently_valid=False,
readbacks=(
AuthorityReadbackRef(
role="agent_task", authority="aios-agent-task-api",
readback_ref=task_read["readback_ref"], canonical_sha256=task_read["canonical_sha256"],
observed_at=task_read["observed_at"], revision=task["revision"],
),
AuthorityReadbackRef(
role="admission", authority="aios-task-admission-store",
readback_ref=admission_read["admission_ref"], canonical_sha256=admission_read["admission_sha256"],
observed_at=admission_read["observed_at"], revision=None,
),
AuthorityReadbackRef(
role="owner_registry", authority="aios-single-host-owner-authority",
readback_ref=admission_read["registry_ref"], canonical_sha256=admission_read["registry_sha256"],
observed_at=admission_read["observed_at"], revision=admission_read["registry_generation"],
),
AuthorityReadbackRef(
role="bridge_job", authority="aios-gitops-job-controller",
readback_ref=job_read["readback_ref"], canonical_sha256=job_read["canonical_sha256"],
observed_at=job_read["observed_at"], revision=job["revision"],
),
),
)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,528 @@
"""Canonical governed execution projection for Online Agent Tasks.
The durable governed binding is authoritative. Natural-language task fields and
worker configuration are never allowed to invent repository, ref, generation, or
idempotency coordinates.
"""
from __future__ import annotations
from dataclasses import asdict, dataclass
import hashlib
import json
import re
from pathlib import Path
from typing import Any, Mapping, Sequence
import urllib.error
import urllib.request
EXECUTION_SCHEMA = "aios.online-agent.execution.v2"
PROJECTION_SCHEMA = "aios.online-agent.task-projection.v1"
OID_RE = re.compile(r"^[0-9a-f]{40}$")
TASK_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{2,179}$")
BRANCH_RE = re.compile(r"^agent/[A-Za-z0-9][A-Za-z0-9._/-]{2,179}$")
KEY_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{7,159}$")
PROTECTED_PREFIXES = ("main", "master", "refs/", "tags/", "tag/", "release/", "baseline/", "production/")
MUTATION_ACTIONS = frozenset({"deterministic"})
READ_ONLY_ACTIONS = frozenset({"source_capture", "agent_runtime"})
SUPPORTED_ACTIONS = frozenset({"deterministic", "source_capture", "agent_runtime"})
AGENT_RUNTIME_ADAPTERS = frozenset({"hermes", "openclaw"})
MAX_AGENT_RUNTIME_INPUT_BYTES = 64 * 1024
class ExecutionProjectionError(RuntimeError):
"""A canonical fail-closed projection or validation error."""
class OnlineTaskProjectionClient:
"""Official HTTP projection client using only a control credential reference."""
def __init__(self, api_base: str, control_token_path: Path) -> None:
self.api_base = api_base.rstrip("/")
self.control_token_path = Path(control_token_path)
def _token(self) -> str:
path = self.control_token_path
if path.is_symlink() or not path.is_file():
_error("ONLINE_TASK_PROJECTION_CONTROL_UNAVAILABLE")
value = path.read_text(encoding="utf-8").strip()
if len(value) < 32:
_error("ONLINE_TASK_PROJECTION_CONTROL_INVALID")
return value
def create(
self,
projected_task: Mapping[str, Any],
*,
binding: "GovernedExecutionBinding",
) -> dict[str, Any]:
body = dict(projected_task)
payload = _canonical(body).encode("utf-8")
request = urllib.request.Request(
self.api_base + "/v1/tasks",
method="POST",
data=payload,
headers={
"Authorization": "Bearer " + self._token(),
"Content-Type": "application/json",
"Accept": "application/json",
},
)
try:
with urllib.request.urlopen(request, timeout=15) as response:
result = json.loads(response.read().decode("utf-8"))
status = response.status
except urllib.error.HTTPError as exc:
raw = exc.read(8192).decode("utf-8", "replace")
try:
error = json.loads(raw).get("error")
except (AttributeError, json.JSONDecodeError):
error = "HTTP_ERROR"
raise ExecutionProjectionError(f"ONLINE_TASK_PROJECTION_FAILED:{exc.code}:{error}") from exc
except (OSError, urllib.error.URLError, TimeoutError) as exc:
raise ExecutionProjectionError("ONLINE_TASK_PROJECTION_UNAVAILABLE") from exc
if status not in {200, 201} or not isinstance(result, dict):
_error("ONLINE_TASK_PROJECTION_RESPONSE_INVALID")
if result.get("task_id") != binding.task_id or result.get("status") not in {"QUEUED", "VERIFYING", "SUCCEEDED"}:
_error("ONLINE_TASK_PROJECTION_RESPONSE_INVALID")
metadata = result.get("metadata")
if not isinstance(metadata, Mapping):
_error("ONLINE_TASK_PROJECTION_RESPONSE_INVALID")
returned_execution = validate_execution_envelope(
_object(metadata.get("execution"), "ONLINE_TASK_PROJECTION_RESPONSE_INVALID"),
binding=binding,
)
submitted_metadata = _object(body.get("metadata"), "ONLINE_TASK_PROJECTION_REQUEST_INVALID")
submitted_execution = validate_execution_envelope(
_object(submitted_metadata.get("execution"), "ONLINE_TASK_PROJECTION_REQUEST_INVALID"),
binding=binding,
)
if _canonical(returned_execution) != _canonical(submitted_execution):
_error("ONLINE_TASK_PROJECTION_RESPONSE_BINDING_INVALID")
return result
def _error(code: str) -> None:
raise ExecutionProjectionError(code)
def _object(value: object, code: str) -> dict[str, Any]:
if not isinstance(value, Mapping):
_error(code)
return dict(value)
def _string(value: object, code: str) -> str:
if not isinstance(value, str) or not value.strip():
_error(code)
return value.strip()
def _task_id(value: object) -> str:
value = _string(value, "TASK_ID_INVALID")
if TASK_RE.fullmatch(value) is None:
_error("TASK_ID_INVALID")
return value
def _oid(value: object, code: str) -> str:
value = _string(value, code)
if OID_RE.fullmatch(value) is None:
_error(code)
return value
def _branch(value: object) -> str:
value = _string(value, "TARGET_BRANCH_INVALID")
if BRANCH_RE.fullmatch(value) is None or value.startswith(PROTECTED_PREFIXES):
_error("TARGET_BRANCH_INVALID")
return value
def _generation(value: object) -> int:
if not isinstance(value, int) or isinstance(value, bool) or value < 1:
_error("GENERATION_INVALID")
return value
def _canonical(value: object) -> str:
return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
def _digest(value: object) -> str:
return hashlib.sha256(_canonical(value).encode("utf-8")).hexdigest()
def _source_signals(value: object) -> tuple[str, ...]:
"""Normalize untrusted natural-language hints before they enter a signed envelope."""
if value is None:
return ()
if not isinstance(value, Sequence) or isinstance(value, (str, bytes)):
_error("SOURCE_CAPTURE_SIGNALS_INVALID")
result: list[str] = []
for item in value:
if not isinstance(item, str):
_error("SOURCE_CAPTURE_SIGNALS_INVALID")
text = " ".join(item.split()).strip()
if not text:
continue
# Projection may accept long task prose, but execution envelopes stay bounded.
text = text[:512].rstrip()
if text not in result:
result.append(text)
if len(result) == 16:
break
return tuple(result)
def _agent_runtime_input(value: object) -> dict[str, Any]:
if not isinstance(value, Mapping):
_error("AGENT_RUNTIME_INPUT_REQUIRED")
raw = dict(value)
allowed = {
"goal", "instructions", "success_criteria", "constraints",
"scope", "trace_id",
}
if set(raw) - allowed:
_error("AGENT_RUNTIME_INPUT_FIELDS_INVALID")
goal = _string(raw.get("goal"), "AGENT_RUNTIME_GOAL_REQUIRED")
instructions = raw.get("instructions", "")
if not isinstance(instructions, str):
_error("AGENT_RUNTIME_INSTRUCTIONS_INVALID")
criteria = raw.get("success_criteria", [])
if not isinstance(criteria, list) or len(criteria) > 32:
_error("AGENT_RUNTIME_SUCCESS_CRITERIA_INVALID")
normalized_criteria: list[dict[str, str]] = []
for item in criteria:
if not isinstance(item, Mapping):
_error("AGENT_RUNTIME_SUCCESS_CRITERIA_INVALID")
criterion_id = _string(item.get("id"), "AGENT_RUNTIME_SUCCESS_CRITERIA_INVALID")
description = _string(
item.get("description"), "AGENT_RUNTIME_SUCCESS_CRITERIA_INVALID"
)
normalized_criteria.append(
{"id": criterion_id[:120], "description": description[:1000]}
)
constraints = raw.get("constraints", {})
if not isinstance(constraints, Mapping):
_error("AGENT_RUNTIME_CONSTRAINTS_INVALID")
sensitive_markers = ("token", "secret", "password", "credential", "authorization", "api_key", "apikey")
for key in constraints:
lowered = str(key).lower()
if any(marker in lowered for marker in sensitive_markers):
_error("AGENT_RUNTIME_SECRET_FIELD_DENY")
scope = raw.get("scope", {})
if not isinstance(scope, Mapping):
_error("AGENT_RUNTIME_SCOPE_INVALID")
scope = {
"scope_type": str(scope.get("scope_type") or "legacy"),
"tenant_id": scope.get("tenant_id"),
"project_id": scope.get("project_id"),
}
if scope["scope_type"] not in {"legacy", "system", "tenant"}:
_error("AGENT_RUNTIME_SCOPE_INVALID")
if scope["scope_type"] == "tenant":
if not isinstance(scope["tenant_id"], str) or not scope["tenant_id"].strip():
_error("AGENT_RUNTIME_SCOPE_INVALID")
elif scope["tenant_id"] not in (None, "") or scope["project_id"] not in (None, ""):
_error("AGENT_RUNTIME_SCOPE_INVALID")
trace_id = str(raw.get("trace_id") or "").strip()
if not trace_id or len(trace_id) > 255:
_error("AGENT_RUNTIME_TRACE_ID_INVALID")
normalized = {
"goal": goal[:12000],
"instructions": instructions[:32000],
"success_criteria": normalized_criteria,
"constraints": dict(constraints),
"scope": scope,
"trace_id": trace_id,
}
if len(_canonical(normalized).encode("utf-8")) > MAX_AGENT_RUNTIME_INPUT_BYTES:
_error("AGENT_RUNTIME_INPUT_TOO_LARGE")
return normalized
def _capabilities(value: object) -> tuple[str, ...]:
if value is None:
return ()
if not isinstance(value, Sequence) or isinstance(value, (str, bytes)):
_error("CAPABILITY_SCOPE_INVALID")
result = tuple(sorted({_string(item, "CAPABILITY_SCOPE_INVALID") for item in value}))
if any(len(item) > 120 for item in result):
_error("CAPABILITY_SCOPE_INVALID")
return result
@dataclass(frozen=True, slots=True)
class GovernedExecutionBinding:
task_id: str
job_id: str
repository: str
base_oid: str
expected_branch_oid: str
target_branch: str
branch_mode: str
generation: int
idempotency_key: str
@classmethod
def from_mapping(cls, value: Mapping[str, Any]) -> "GovernedExecutionBinding":
data = dict(value)
task_id = _task_id(data.get("task_id"))
job_id = _string(data.get("job_id"), "JOB_ID_REQUIRED")
if TASK_RE.fullmatch(job_id) is None:
_error("JOB_ID_INVALID")
repository = _string(data.get("repository"), "REPOSITORY_INVALID")
if repository != "aios/aios":
_error("WRONG_REPOSITORY_ENVELOPE_DENY")
base_oid = _oid(data.get("base_oid"), "BASE_OID_INVALID")
expected = _oid(data.get("expected_branch_oid"), "EXPECTED_BRANCH_OID_INVALID")
target = _branch(data.get("target_branch"))
mode = _string(data.get("branch_mode"), "BRANCH_MODE_MISSING")
if mode not in {"CREATE_NEW", "CONTINUE_EXISTING"}:
_error("BRANCH_MODE_INVALID")
generation = _generation(data.get("generation"))
if mode == "CREATE_NEW" and expected != base_oid:
_error("EXPECTED_BASE_CONFLICT")
key = _string(data.get("idempotency_key"), "IDEMPOTENCY_KEY_INVALID")
if KEY_RE.fullmatch(key) is None:
_error("IDEMPOTENCY_KEY_INVALID")
return cls(task_id, job_id, repository, base_oid, expected, target, mode, generation, key)
def public(self) -> dict[str, Any]:
return asdict(self)
def binding_from_bootstrap(value: Mapping[str, Any]) -> GovernedExecutionBinding:
return GovernedExecutionBinding.from_mapping(value)
def validate_execution_envelope(
value: Mapping[str, Any],
*,
binding: GovernedExecutionBinding | None = None,
worker_capabilities: Sequence[str] | None = None,
) -> dict[str, Any]:
envelope = dict(value)
if envelope.get("schema") != EXECUTION_SCHEMA:
_error("EXECUTION_ENVELOPE_SCHEMA_VALIDATION")
candidate = GovernedExecutionBinding.from_mapping(envelope)
if binding is not None and candidate != binding:
comparisons = (
(candidate.task_id, binding.task_id, "TASK_BINDING_CONFLICT"),
(candidate.job_id, binding.job_id, "JOB_BINDING_CONFLICT"),
(candidate.repository, binding.repository, "WRONG_REPOSITORY_ENVELOPE_DENY"),
(candidate.base_oid, binding.base_oid, "WRONG_BASE_ENVELOPE_DENY"),
(candidate.expected_branch_oid, binding.expected_branch_oid, "WRONG_EXPECTED_HEAD_ENVELOPE_DENY"),
(candidate.target_branch, binding.target_branch, "WRONG_BRANCH_ENVELOPE_DENY"),
(candidate.generation, binding.generation, "WRONG_GENERATION_ENVELOPE_DENY"),
(candidate.branch_mode, binding.branch_mode, "BRANCH_MODE_BINDING_CONFLICT"),
(candidate.idempotency_key, binding.idempotency_key, "IDEMPOTENCY_BINDING_CONFLICT"),
)
for actual, expected, code in comparisons:
if actual != expected:
_error(code)
action = _string(envelope.get("action"), "EXECUTION_ACTION_MISSING")
if action not in SUPPORTED_ACTIONS:
_error("EXECUTION_ACTION_UNSUPPORTED")
capability_scope = _capabilities(envelope.get("capability_scope"))
if worker_capabilities is not None and not set(capability_scope).issubset(set(worker_capabilities)):
_error("CAPABILITY_MISMATCH_DENY")
workspace = _object(envelope.get("workspace"), "WORKSPACE_POLICY_REQUIRED")
if workspace.get("authority_git_dir") != "/data/aios-gitops/repo.git" or workspace.get("read_only_repository") is not True:
_error("WORKSPACE_POLICY_DENY")
governance = _object(envelope.get("governance"), "GOVERNANCE_POLICY_REQUIRED")
governed_coordinates = (
(governance.get("task_id"), candidate.task_id, "TASK_BINDING_CONFLICT"),
(governance.get("job_id"), candidate.job_id, "JOB_BINDING_CONFLICT"),
(governance.get("idempotency_key"), candidate.idempotency_key, "IDEMPOTENCY_BINDING_CONFLICT"),
)
for actual_coordinate, expected_coordinate, code in governed_coordinates:
if actual_coordinate != expected_coordinate:
_error(code)
commit_key = _string(governance.get("commit_idempotency_key"), "COMMIT_IDEMPOTENCY_KEY_INVALID")
if KEY_RE.fullmatch(commit_key) is None:
_error("COMMIT_IDEMPOTENCY_KEY_INVALID")
if governance.get("writer_lease") is not (action in MUTATION_ACTIONS):
_error("WRITER_LEASE_POLICY_DENY")
for field in ("main_write", "production_write", "tag_write", "baseline_write", "force_push", "protected_ref_bypass"):
if governance.get(field, False) is not False:
_error("POLICY_DENY")
changes = envelope.get("change_set")
verification = envelope.get("verification")
if not isinstance(changes, list) or not isinstance(verification, list):
_error("EXECUTION_OPERATIONS_INVALID")
if action == "source_capture":
if changes or verification:
_error("SOURCE_CAPTURE_MUTATION_DENY")
signals = list(_source_signals(envelope.get("source_signals") or []))
if envelope.get("source_signals") != signals:
_error("SOURCE_CAPTURE_SIGNALS_INVALID")
if envelope.get("runtime_input") not in (None, {}):
_error("SOURCE_CAPTURE_RUNTIME_INPUT_DENY")
elif action == "agent_runtime":
if changes or verification:
_error("AGENT_RUNTIME_MUTATION_DENY")
adapter = str(envelope.get("adapter") or "")
if adapter not in AGENT_RUNTIME_ADAPTERS:
_error("AGENT_RUNTIME_ADAPTER_INVALID")
runtime_input = _agent_runtime_input(envelope.get("runtime_input"))
if envelope.get("runtime_input") != runtime_input:
_error("AGENT_RUNTIME_INPUT_NOT_CANONICAL")
required_runtime_capability = f"agent.runtime.{adapter}"
if required_runtime_capability not in capability_scope:
_error("AGENT_RUNTIME_CAPABILITY_REQUIRED")
if envelope.get("source_signals") not in (None, []):
_error("AGENT_RUNTIME_SOURCE_SIGNALS_DENY")
elif not changes:
_error("CHANGE_SET_INVALID")
declared = str(envelope.get("execution_digest") or "")
material = {key: item for key, item in envelope.items() if key != "execution_digest"}
actual = _digest(material)
if declared != actual:
_error("EXECUTION_DIGEST_INVALID")
return {**material, "execution_digest": actual}
def project_execution_envelope(
binding: GovernedExecutionBinding,
execution_request: Mapping[str, Any] | None = None,
*,
required_capabilities: Sequence[str] = (),
) -> dict[str, Any]:
request = dict(execution_request or {})
action = str(request.get("action") or "source_capture")
if action not in SUPPORTED_ACTIONS:
_error("EXECUTION_ACTION_UNSUPPORTED")
capabilities = _capabilities(request.get("capability_scope", required_capabilities))
governance = {
"task_id": binding.task_id,
"job_id": binding.job_id,
"idempotency_key": binding.idempotency_key,
"commit_idempotency_key": str(request.get("commit_idempotency_key") or f"{binding.task_id}-commit-g{binding.generation}"),
"writer_lease": action in MUTATION_ACTIONS,
"main_write": False,
"production_write": False,
"tag_write": False,
"baseline_write": False,
"force_push": False,
"protected_ref_bypass": False,
}
supplied_governance = request.get("governance")
if supplied_governance is not None:
governance.update(_object(supplied_governance, "GOVERNANCE_POLICY_REQUIRED"))
material: dict[str, Any] = {
"schema": EXECUTION_SCHEMA,
**binding.public(),
"action": action,
"adapter": str(
request.get("adapter")
or ("deterministic" if action == "deterministic" else "source_capture")
),
"capability_scope": list(capabilities),
"change_set": list(request.get("change_set") or []),
"verification": list(request.get("verification") or []),
"source_signals": list(_source_signals(request.get("source_signals") or [])) if action == "source_capture" else [],
"runtime_input": _agent_runtime_input(request.get("runtime_input")) if action == "agent_runtime" else {},
"governance": governance,
"workspace": {
"authority_git_dir": "/data/aios-gitops/repo.git",
"read_only_repository": True,
"cleanup_policy": "always",
},
}
envelope = {**material, "execution_digest": _digest(material)}
return validate_execution_envelope(envelope, binding=binding)
def project_online_task(
binding: GovernedExecutionBinding,
task_request: Mapping[str, Any],
execution_request: Mapping[str, Any] | None = None,
) -> dict[str, Any]:
request = dict(task_request)
required = _capabilities(request.get("required_capabilities"))
execution_value = dict(execution_request or {})
if str(execution_value.get("action") or "source_capture") == "agent_runtime":
adapter = str(execution_value.get("adapter") or "")
if adapter not in AGENT_RUNTIME_ADAPTERS:
_error("AGENT_RUNTIME_ADAPTER_INVALID")
runtime_input = execution_value.get("runtime_input")
tenant_id = request.get("tenant_id")
project_id = request.get("project_id")
declared_scope = str(
request.get("scope_type")
or ("tenant" if isinstance(tenant_id, str) and tenant_id.strip() else "legacy")
)
if runtime_input is None:
runtime_input = {
"goal": request.get("goal"),
"instructions": request.get("instructions") or "",
"success_criteria": request.get("success_criteria") or [],
"constraints": request.get("constraints") or {},
}
elif not isinstance(runtime_input, Mapping):
_error("AGENT_RUNTIME_INPUT_REQUIRED")
runtime_input = dict(runtime_input)
# Scope/trace are authoritative Task identity, never caller-selected runtime hints.
runtime_input["scope"] = {
"scope_type": declared_scope,
"tenant_id": tenant_id,
"project_id": project_id,
}
runtime_input["trace_id"] = str(
request.get("trace_id")
or request.get("run_id")
or binding.task_id
)
execution_value["runtime_input"] = runtime_input
execution = project_execution_envelope(
binding,
execution_value,
required_capabilities=required,
)
metadata = dict(request.get("metadata") or {})
if "execution" in metadata:
validate_execution_envelope(_object(metadata["execution"], "EXECUTION_ENVELOPE_INVALID"), binding=binding)
if metadata["execution"] != execution:
_error("EXECUTION_PROJECTION_CONFLICT")
metadata["execution"] = execution
metadata["execution_projection"] = {
"schema": PROJECTION_SCHEMA,
"task_id": binding.task_id,
"job_id": binding.job_id,
"execution_digest": execution["execution_digest"],
}
return {
**request,
"task_id": binding.task_id,
"metadata": metadata,
"required_capabilities": list(required),
"idempotency_key": str(request.get("idempotency_key") or f"online-task:{binding.idempotency_key}"),
}
def resolve_task_execution(
task: Mapping[str, Any],
binding: GovernedExecutionBinding,
*,
worker_capabilities: Sequence[str] | None = None,
) -> tuple[dict[str, Any], str]:
metadata = task.get("metadata")
if isinstance(metadata, Mapping) and "execution" in metadata:
value = validate_execution_envelope(
_object(metadata["execution"], "EXECUTION_ENVELOPE_INVALID"),
binding=binding,
worker_capabilities=worker_capabilities,
)
return value, "persisted"
projected = project_execution_envelope(
binding,
{"action": "source_capture", "capability_scope": task.get("required_capabilities") or []},
required_capabilities=task.get("required_capabilities") or [],
)
validate_execution_envelope(projected, binding=binding, worker_capabilities=worker_capabilities)
return projected, "recovered_projection"
@@ -0,0 +1,446 @@
"""aios_owner_based_executor — Owner 基于授权的可执行 CLI 入口。
把 "owner 基于授权 → Agent 自主完成受治理写链" 从测试/装配层提升为**可直接执行的命令**。
集成 V2R2C3 现场组合根 `field_governed_writer.py`,装配到生产入口
`GovernedWriterAutomation.acquire_verified()`。
子命令:
register <authorization_ref> 登记一条 Owner 授权到 authority(可 --policy-json 指定,
默认用内置参考授权物 azn-owner-gw-v1)。
acquire <authorization_ref> 装配 FieldGovernedWriterFactory,运行 acquire_verified(),
输出 GovernedWriterSession JSON。
行为契约:
- 生产入口唯一授权来源是 authority 已登记的 authorization_ref;未登记即 fail-closed。
- authority 未装配(--no-authority 或构造时 owner_authority=None)→ AuthorizationDenied。
- EXPIRED 任务无预签 recovery → OwnerActionRequired(OWNER_BOUNDARY,非自动恢复)。
- status==error 输出到 stderr,退出码非 0;成功输出会话 JSON,退出码 0。
- legacy_global_writes_enabled 默认 false = 安全不变式,不是 blocker。
用法示例:
python aios_owner_based_executor.py register azn-owner-gw-v1
python aios_owner_based_executor.py acquire azn-owner-gw-v1
python aios_owner_based_executor.py acquire azn-owner-gw-v1 --branch-mode continue --head-oid <oid>
python aios_owner_based_executor.py acquire azn-owner-gw-v1 --task-status EXPIRED # OwnerBoundary
"""
from __future__ import annotations
import argparse
import json
import os
import sys
import tempfile
import time
from pathlib import Path
from app.task_plane.field_governed_writer import (
FieldGovernedWriterFactory,
FieldOwnerAuthorizationAuthority,
FieldRuntimeStatus,
)
from app.task_plane.governance_control_kernel import (
ActionLedger,
ConsistencyBarrier,
ManifestStore,
DiscoveryObservation,
)
from app.task_plane.governed_writer_automation import (
AuthorizationDenied,
GovernedWriterRequest,
GovernedWriterSession,
OwnerActionRequired,
OwnerAuthorizationPolicy,
TaskState,
)
# 版本与示例授权引用
VERSION = "V2R2C3"
DEFAULT_AZN_REF = "azn-owner-gw-v1"
_AZN_CAPS = frozenset(
{
"code.write",
"governed.write.branch",
"governed.write.file",
"test.execute",
"writer.lease",
"task.principal.issue",
}
)
_AZN_OPS = frozenset(
{
"claim",
"epoch_reissue",
"governed_commit",
"heartbeat",
"job_reconcile",
"readback",
"reconcile",
"successor_recovery",
"task_lease",
"task_principal",
"writer_lease",
}
)
# ---------------------------------------------------------------------------
# 授权物 / 请求构造
# ---------------------------------------------------------------------------
def reference_policy(*, expires_at: float | None = None) -> OwnerAuthorizationPolicy:
"""内置参考授权物 azn-owner-gw-v1(现场登记时 Owner 设定当下有效 expires_at)。"""
if expires_at is None:
expires_at = time.time() + 30 * 86400.0
return OwnerAuthorizationPolicy(
authorization_id=DEFAULT_AZN_REF,
subject="aios-autonomous-admin",
repository="aios/aios",
allowed_branch_prefixes=("agent/autonomy-",),
allowed_capabilities=_AZN_CAPS,
risk_ceiling="medium",
trust_domain="aios.owner",
revocation_epoch=1,
not_before=0.0,
expires_at=expires_at,
revoked=False,
allowed_operations=_AZN_OPS,
)
def _default_subject_repo_branch(task: str) -> tuple[str, str, str]:
return ("aios-autonomous-admin", "aios/aios", f"agent/autonomy-{task}")
def build_request(
*,
task_id: str,
manifest_id: str,
runtime_fingerprint: str,
process_start_identity: str,
plan_digest: str,
base_oid: str,
generation: int,
risk: str,
caps: frozenset[str],
files: tuple[str, ...],
overrides: dict | None = None,
) -> GovernedWriterRequest:
subject, repo, branch = _default_subject_repo_branch(task_id)
body = dict(
subject=subject,
task_id=task_id,
repository=repo,
target_branch=branch,
risk=risk,
generation=generation,
base_oid=base_oid,
required_capabilities=caps,
file_allowlist=files,
plan_digest=plan_digest,
manifest_id=manifest_id,
runtime_fingerprint=runtime_fingerprint,
process_start_identity=process_start_identity,
)
if overrides:
body.update(overrides)
return GovernedWriterRequest(**body)
# ---------------------------------------------------------------------------
# 依据配置构造 authority(含 --no-authority fail-closed 场景)
# ---------------------------------------------------------------------------
def build_authority(
*, policy_json_path: str | None, use_reference: bool, expires_at: float | None
) -> FieldOwnerAuthorizationAuthority | None:
if policy_json_path:
data = json.loads(Path(policy_json_path).read_text(encoding="utf-8"))
pol = OwnerAuthorizationPolicy(**data["policy"])
auth = FieldOwnerAuthorizationAuthority()
auth.register(data["authorization_ref"], pol)
return auth
if use_reference:
auth = FieldOwnerAuthorizationAuthority()
auth.register(DEFAULT_AZN_REF, reference_policy(expires_at=expires_at))
return auth
return None # authority 未装配 -> acquire_verified 直接 fail-closed
# ---------------------------------------------------------------------------
# 子命令实现
# ---------------------------------------------------------------------------
def cmd_register(args: argparse.Namespace) -> int:
expires_at = float(args.expires_at) if args.expires_at else None
if args.policy_json:
data = json.loads(Path(args.policy_json).read_text(encoding="utf-8"))
pol = OwnerAuthorizationPolicy(**data["policy"])
ref = data.get("authorization_ref", pol.authorization_id)
else:
pol = reference_policy(expires_at=expires_at)
ref = DEFAULT_AZN_REF
auth = FieldOwnerAuthorizationAuthority()
auth.register(ref, pol)
print(
json.dumps(
{
"status": "registered",
"authorization_ref": ref,
"policy_fingerprint": pol.policy_fingerprint,
"subject": pol.subject,
"repository": pol.repository,
"allowed_branch_prefixes": list(pol.allowed_branch_prefixes),
"risk_ceiling": pol.risk_ceiling,
"expires_at": pol.expires_at,
"note": "registered in-memory authority; field authority must persist",
},
ensure_ascii=False,
indent=2,
)
)
return 0
def cmd_acquire(args: argparse.Namespace) -> int:
from app.task_plane.governance_control_kernel import StaleManifest
# 使用独立工作目录构造 manifest + ledger,避免污染运行目录
workdir = Path(args.workdir) if args.workdir else None
if workdir is None:
workdir = Path(tempfile.mkdtemp(prefix="gw_exec_"))
store = ManifestStore(str(workdir / "manifest.db"))
ledger = ActionLedger(str(workdir / "ledger.db"))
barrier = ConsistencyBarrier(["W1"])
barrier.submit(
DiscoveryObservation.build(
worker="W1",
runtime_fingerprint=args.runtime,
process_start_identity=args.process_start,
activation_digest="activation-exec",
epoch_id="epoch-exec-prov",
policy_digest="policy-exec",
plan_provider_root="/plans/exec",
task_id=args.task,
grant_id="grant-exec",
facts={},
evidence_refs=(),
observed_at=100.0,
)
)
manifest_id = store.freeze(barrier.freeze())
auth = build_authority(
policy_json_path=args.policy_json,
use_reference=args.reference,
expires_at=float(args.expires_at) if args.expires_at else None,
)
task_status = args.task_status.upper()
task = TaskState(task_id=args.task, status=task_status, revision=1)
if args.branch_mode == "create":
branch_exists, head_oid = False, None
elif args.branch_mode == "continue":
branch_exists, head_oid = True, args.head_oid
else: # auto: 由 read 决定(默认新分支)
branch_exists, head_oid = (True, args.head_oid) if args.head_oid else (False, None)
runtime_status = FieldRuntimeStatus(
legacy_global_writes_enabled=args.legacy_global,
task_principal_writes_enabled=args.principal_writes,
task_principal_verifier_ready=args.verifier_ready,
governed_write_blockers=tuple(args.blocker or ()),
)
# fail-closed authority 场景:--no-authority 显式不装配
if args.no_authority:
auth = None
adapters = _make_adapters(
authority=auth,
manifest_store=store,
action_ledger=ledger,
task=task,
branch_exists=branch_exists,
head_oid=head_oid,
repo=args.repo if args.repo else "aios/aios",
branch=args.branch if args.branch else f"agent/autonomy-{args.task}",
generation=args.generation,
runtime_status=runtime_status,
)
if auth is None:
adapters.owner_authority = None
automation = _automation(store, ledger)
request = build_request(
task_id=args.task,
manifest_id=manifest_id,
runtime_fingerprint=args.runtime,
process_start_identity=args.process_start,
plan_digest=args.plan_digest,
base_oid=args.base_oid,
generation=args.generation,
risk=args.risk,
caps=set(_AZN_CAPS),
files=tuple(args.files),
)
try:
session = automation.acquire_verified(
request=request,
authorization_ref=args.authorization_ref,
readiness=runtime_status_to_readiness(runtime_status),
epoch=None,
adapters=adapters,
now=time.time(),
)
print(json.dumps({"status": "ok", "version": VERSION, "session": session.to_dict()},
ensure_ascii=False, indent=2))
return 0
except OwnerActionRequired as exc:
print(json.dumps({"status": "owner_boundary", "version": VERSION, "reason": str(exc)},
ensure_ascii=False, indent=2), file=sys.stderr)
return 3
except AuthorizationDenied as exc:
print(json.dumps({"status": "auth_denied", "version": VERSION, "reason": str(exc)},
ensure_ascii=False, indent=2), file=sys.stderr)
return 2
except Exception as exc: # noqa: BLE001 - CLI 边界统一收口
print(json.dumps({"status": "error", "version": VERSION,
"error": f"{type(exc).__name__}: {exc}"},
ensure_ascii=False, indent=2), file=sys.stderr)
return 1
finally:
_cleanup_workdir(workdir)
def _make_adapters(
*,
authority,
manifest_store,
action_ledger,
task,
branch_exists,
head_oid,
repo,
branch,
generation,
runtime_status,
):
from app.task_plane.field_governed_writer import _DefaultExecutionPlane, _DefaultGit, \
_DefaultJobs, _DefaultPrincipalIssuer, _DefaultTrustedSigner, _DefaultWriterLease
from app.task_plane.governed_writer_automation import GovernedWriterAdapters
from app.task_plane.governed_writer_automation import GovernedWriteReadiness
return GovernedWriterAdapters(
signer=_DefaultTrustedSigner(),
execution=_DefaultExecutionPlane(task),
principal_issuer=_DefaultPrincipalIssuer(),
jobs=_DefaultJobs(repository=repo, target_branch=branch, generation=generation),
git=_DefaultGit(exists=branch_exists, head_oid=head_oid),
writer_lease=_DefaultWriterLease(),
owner_authority=authority,
)
def runtime_status_to_readiness(rs: FieldRuntimeStatus):
from app.task_plane.governed_writer_automation import GovernedWriteReadiness
return GovernedWriteReadiness(
legacy_global_writes_enabled=rs.legacy_global_writes_enabled,
task_principal_writes_enabled=rs.task_principal_writes_enabled,
task_principal_verifier_ready=rs.task_principal_verifier_ready,
governed_write_blockers=tuple(rs.governed_write_blockers),
)
def _automation(store, ledger):
from app.task_plane.governed_writer_automation import GovernedWriterAutomation
return GovernedWriterAutomation(manifest_store=store, action_ledger=ledger)
def _cleanup_workdir(workdir: Path) -> None:
import gc
import shutil
try:
gc.collect()
shutil.rmtree(workdir, ignore_errors=True)
except Exception: # pragma: no cover - 清理失败不影响结果
pass
# ---------------------------------------------------------------------------
# 参数与入口
# ---------------------------------------------------------------------------
def build_parser() -> argparse.ArgumentParser:
import tempfile # noqa: F401 (确保模块可见)
parser = argparse.ArgumentParser(
description="AIOS Owner 基于授权执行器 (V2R2C3)",
prog="aios_owner_based_executor",
)
sub = parser.add_subparsers(dest="command", required=True)
p_register = sub.add_parser("register", help="登记 Owner 授权到 authority")
p_register.add_argument("authorization_ref", nargs="?", default=DEFAULT_AZN_REF)
p_register.add_argument("--policy-json", help="policy JSON 文件(含 authorization_ref + policy)")
p_register.add_argument("--expires-at", help="override expires_at (unix)")
p_register.set_defaults(func=cmd_register)
p_acquire = sub.add_parser("acquire", help="运行 acquire_verified() 完成受治理写链")
p_acquire.add_argument("authorization_ref", nargs="?", default=DEFAULT_AZN_REF)
p_acquire.add_argument("--no-authority", action="store_true",
help="显式不装配 authority -> fail-closed")
p_acquire.add_argument("--policy-json", help="从文件装载授权物(替代内置参考)")
p_acquire.add_argument("--reference", action="store_true",
help="使用内置参考授权物 azn-owner-gw-v1")
p_acquire.add_argument("--expires-at", help="override expires_at (unix)")
p_acquire.add_argument("--task", default="task-gw-exec-0001")
p_acquire.add_argument("--task-status", default="QUEUED",
choices=["QUEUED", "CLAIMED", "RUNNING", "EXPIRED"])
p_acquire.add_argument("--branch-mode", default="create", choices=["create", "continue", "auto"])
p_acquire.add_argument("--head-oid", help="继续分支的 live HEAD oid")
p_acquire.add_argument("--base-oid", default="5cf20128d8be2d10aef87b09f8f7a2aecb2000f3")
p_acquire.add_argument("--repo", help="仓库(默认 aios/aios)")
p_acquire.add_argument("--branch", help="目标分支(默认 agent/autonomy-<task>)")
p_acquire.add_argument("--generation", type=int, default=1)
p_acquire.add_argument("--risk", default="medium")
p_acquire.add_argument("--runtime", default="runtime-gw-exec")
p_acquire.add_argument("--process-start", default="pid:gw:exec:1")
p_acquire.add_argument("--plan-digest", default="sha256:plan-exec")
p_acquire.add_argument("--files", nargs="*", default=[
"active-runtime/app/task_plane/governance_control_kernel.py",
"active-runtime/app/task_plane/governed_writer_automation.py",
"active-runtime/app/task_plane/field_governed_writer.py",
])
p_acquire.add_argument("--workdir", help="manifest/ledger 工作目录(默认临时)")
# 运行时状态(W-RUNTIME 只读线)
p_acquire.add_argument("--legacy-global", action="store_true",
help="legacy_global_writes_enabled(默认 false=不变式)")
p_acquire.add_argument("--principal-writes", action="store_true", default=True,
help="task_principal_writes_enabled(默认 true)")
p_acquire.add_argument("--verifier-ready", action="store_true", default=True,
help="task_principal_verifier_ready(默认 true)")
p_acquire.add_argument("--blocker", action="append",
help="governed_write_blockers 追加")
p_acquire.set_defaults(func=cmd_acquire)
parser.add_argument("--version", action="version", version=VERSION)
return parser
def main(argv: list[str] | None = None) -> int:
parser = build_parser()
args = parser.parse_args(argv)
return int(args.func(args))
if __name__ == "__main__":
import tempfile # noqa: F401
try:
raise SystemExit(main())
except KeyboardInterrupt:
raise SystemExit(130)
@@ -0,0 +1,527 @@
"""Owner-final, structured authorization for one governed pull-request merge.
The Owner approval is the policy decision. Signing and public verification
prove that decision but cannot change its repository, pull request, CAS base,
technical-gate contract, or lifetime. This module deliberately exposes no
generic sign-bytes API and does not publish or alter technical check results.
"""
from __future__ import annotations
from dataclasses import asdict, dataclass
import hashlib
import os
from pathlib import Path
import re
import secrets
import sqlite3
import time
from typing import Any, Callable, Mapping, Protocol
from aios_single_host_authority import (
OWNER_NAMESPACE,
LocalFileSigner,
SignedArtifact,
canonical_json_bytes,
sha256_json,
)
POLICY_SCHEMA = "aios.owner-merge-approval-policy.v2"
APPROVAL_SCHEMA = "aios.owner-merge-approval.v2"
RECEIPT_SCHEMA = "aios.owner-merge-admission-receipt.v2"
OPERATION = "merge.pull-request"
OID_RE = re.compile(r"^[0-9a-f]{40}$")
DIGEST_RE = re.compile(r"^[0-9a-f]{64}$")
REPOSITORY_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._-]{0,127}/[A-Za-z0-9][A-Za-z0-9._-]{0,127}$")
IDENTITY_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:@/+:-]{2,255}$")
CHECK_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{1,127}$")
TOKEN_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_-]{11,255}$")
class OwnerMergeApprovalError(RuntimeError):
"""Stable fail-closed error for the Owner merge approval contract."""
def __init__(self, code: str):
super().__init__(code)
self.code = code
def _fail(code: str) -> None:
raise OwnerMergeApprovalError(code)
def _require_oid(value: object, code: str) -> str:
if not isinstance(value, str) or OID_RE.fullmatch(value) is None:
_fail(code)
return value
def _require_digest(value: object, code: str) -> str:
if not isinstance(value, str) or DIGEST_RE.fullmatch(value) is None:
_fail(code)
return value
@dataclass(frozen=True, slots=True)
class OwnerMergeApprovalPolicy:
"""Immutable policy input for one exact protected merge approval."""
repository: str
pr_number: int
pr_head_oid: str
expected_main_oid: str
operation: str
required_check_contract_digest: str
required_checks: tuple[str, ...]
owner_identity: str
audit_lineage_digest: str
maximum_ttl_seconds: int
policy_version: str = "v2"
schema: str = POLICY_SCHEMA
def validate(self) -> None:
if (
self.schema != POLICY_SCHEMA
or self.policy_version != "v2"
or not isinstance(self.repository, str)
or REPOSITORY_RE.fullmatch(self.repository) is None
or type(self.pr_number) is not int
or not 1 <= self.pr_number < 2**31
or self.operation != OPERATION
or not isinstance(self.owner_identity, str)
or IDENTITY_RE.fullmatch(self.owner_identity) is None
or type(self.maximum_ttl_seconds) is not int
or not 60 <= self.maximum_ttl_seconds <= 900
):
_fail("OWNER_MERGE_POLICY_INVALID")
_require_oid(self.pr_head_oid, "OWNER_MERGE_POLICY_HEAD_INVALID")
_require_oid(self.expected_main_oid, "OWNER_MERGE_POLICY_BASE_INVALID")
_require_digest(
self.required_check_contract_digest,
"OWNER_MERGE_POLICY_CHECK_CONTRACT_INVALID",
)
_require_digest(self.audit_lineage_digest, "OWNER_MERGE_POLICY_LINEAGE_INVALID")
if (
not isinstance(self.required_checks, tuple)
or not self.required_checks
or len(set(self.required_checks)) != len(self.required_checks)
or any(not isinstance(item, str) or CHECK_RE.fullmatch(item) is None for item in self.required_checks)
):
_fail("OWNER_MERGE_POLICY_REQUIRED_CHECKS_INVALID")
def as_dict(self) -> dict[str, Any]:
self.validate()
value = asdict(self)
value["required_checks"] = list(self.required_checks)
return value
@property
def digest(self) -> str:
return sha256_json(self.as_dict())
@dataclass(frozen=True, slots=True)
class OwnerMergeLiveState:
"""Read-only live evidence; this object has no status publication method."""
repository: str
pr_number: int
pr_head_oid: str
main_oid: str
required_check_contract_digest: str
check_statuses: Mapping[str, bool]
@dataclass(frozen=True, slots=True)
class OwnerMergeOidReadback:
authority_main_oid: str
gitea_main_oid: str
api_main_oid: str
@dataclass(frozen=True, slots=True)
class OwnerMergeAdmissionReceipt:
approval_id: str
nonce: str
owner_identity: str
policy_digest: str
proof_path: str
merge_oid: str
schema: str = RECEIPT_SCHEMA
class ApprovalProofVerifier(Protocol):
"""Public proof verifier; it is intentionally unable to issue approvals."""
def verify(
self,
*,
namespace: str,
payload: bytes,
signature: str,
key_id: str,
) -> bool:
...
class GovernedMergeExecutor(Protocol):
def execute(self, approval: Mapping[str, object]) -> str:
...
class MergeOidReconciler(Protocol):
def readback(self, merge_oid: str) -> OwnerMergeOidReadback:
...
APPROVAL_FIELDS = frozenset(
{
"schema",
"approval_id",
"nonce",
"repository",
"pr_number",
"pr_head_oid",
"expected_main_oid",
"operation",
"required_check_contract_digest",
"required_checks",
"owner_identity",
"audit_lineage_digest",
"policy_version",
"policy_digest",
"issued_at",
"not_before",
"expires_at",
"signature_namespace",
"signature_key_id",
"canonical_payload_digest",
}
)
class OwnerMergeApprovalAuthority:
"""Structured Owner signer for an immutable merge policy.
``issue`` has no caller-controlled payload parameter. The immutable policy
selects the complete authorization scope; only approval IDs and nonces are
generated inside the authority.
"""
def __init__(
self,
key_path: Path,
policy: OwnerMergeApprovalPolicy,
*,
require_root_owner: bool = True,
clock: Callable[[], float] = time.time,
token_urlsafe: Callable[[int], str] = secrets.token_urlsafe,
) -> None:
policy.validate()
self.policy = policy
self.signer = LocalFileSigner(
Path(key_path),
signer_id="local-owner-authority",
namespace=OWNER_NAMESPACE,
require_root_owner=require_root_owner,
)
self.clock = clock
self.token_urlsafe = token_urlsafe
def _token(self, prefix: str) -> str:
value = prefix + self.token_urlsafe(24)
if TOKEN_RE.fullmatch(value) is None:
_fail("OWNER_MERGE_AUTHORITY_GENERATED_TOKEN_INVALID")
return value
def issue(self) -> SignedArtifact:
now = int(self.clock())
approval_id = self._token("owner-merge-approval-")
nonce = self._token("owner-merge-nonce-")
policy = self.policy
payload: dict[str, Any] = {
"schema": APPROVAL_SCHEMA,
"approval_id": approval_id,
"nonce": nonce,
"repository": policy.repository,
"pr_number": policy.pr_number,
"pr_head_oid": policy.pr_head_oid,
"expected_main_oid": policy.expected_main_oid,
"operation": policy.operation,
"required_check_contract_digest": policy.required_check_contract_digest,
"required_checks": list(policy.required_checks),
"owner_identity": policy.owner_identity,
"audit_lineage_digest": policy.audit_lineage_digest,
"policy_version": policy.policy_version,
"policy_digest": policy.digest,
"issued_at": now,
"not_before": max(0, now - 5),
"expires_at": now + policy.maximum_ttl_seconds,
"signature_namespace": OWNER_NAMESPACE,
"signature_key_id": self.signer.fingerprint,
}
payload["canonical_payload_digest"] = sha256_json(payload)
return self.signer._sign_canonical(payload)
class OwnerMergeApprovalVerifier:
"""Verify Owner proof, preferring normal verification then recovery proof.
The recovery verifier validates a pre-existing approval with the registered
public Owner root. It cannot issue an approval and is therefore not a
second policy principal.
"""
def __init__(
self,
*,
primary: ApprovalProofVerifier,
recovery: ApprovalProofVerifier | None = None,
clock: Callable[[], float] = time.time,
) -> None:
self.primary = primary
self.recovery = recovery
self.clock = clock
@staticmethod
def _artifact(artifact: SignedArtifact | Mapping[str, Any]) -> tuple[dict[str, Any], str, str, str]:
if isinstance(artifact, SignedArtifact):
return dict(artifact.payload), artifact.signature, artifact.key_id, artifact.namespace
if not isinstance(artifact, Mapping) or set(artifact) != {"payload", "signature", "key_id", "namespace"}:
_fail("OWNER_MERGE_APPROVAL_ARTIFACT_INVALID")
payload = artifact.get("payload")
signature = artifact.get("signature")
key_id = artifact.get("key_id")
namespace = artifact.get("namespace")
if not isinstance(payload, Mapping) or not all(isinstance(item, str) for item in (signature, key_id, namespace)):
_fail("OWNER_MERGE_APPROVAL_ARTIFACT_INVALID")
return dict(payload), signature, key_id, namespace
@staticmethod
def _validate_scope(payload: Mapping[str, Any], policy: OwnerMergeApprovalPolicy, *, now: int, key_id: str, namespace: str) -> None:
if set(payload) != APPROVAL_FIELDS:
_fail("OWNER_MERGE_APPROVAL_FIELDS_INVALID")
expected = {
"schema": APPROVAL_SCHEMA,
"repository": policy.repository,
"pr_number": policy.pr_number,
"pr_head_oid": policy.pr_head_oid,
"expected_main_oid": policy.expected_main_oid,
"operation": policy.operation,
"required_check_contract_digest": policy.required_check_contract_digest,
"required_checks": list(policy.required_checks),
"owner_identity": policy.owner_identity,
"audit_lineage_digest": policy.audit_lineage_digest,
"policy_version": policy.policy_version,
"policy_digest": policy.digest,
"signature_namespace": OWNER_NAMESPACE,
"signature_key_id": key_id,
}
if payload.get("pr_head_oid") != policy.pr_head_oid:
_fail("OWNER_MERGE_APPROVAL_HEAD_BINDING_INVALID")
if payload.get("expected_main_oid") != policy.expected_main_oid:
_fail("OWNER_MERGE_APPROVAL_BASE_BINDING_INVALID")
if payload.get("policy_digest") != policy.digest:
_fail("OWNER_MERGE_POLICY_DIGEST_DRIFT")
if namespace != OWNER_NAMESPACE or any(payload.get(key) != value for key, value in expected.items()):
_fail("OWNER_MERGE_APPROVAL_SCOPE_INVALID")
for field in ("approval_id", "nonce"):
if not isinstance(payload.get(field), str) or TOKEN_RE.fullmatch(payload[field]) is None:
_fail("OWNER_MERGE_APPROVAL_TOKEN_INVALID")
for field in ("issued_at", "not_before", "expires_at"):
if type(payload.get(field)) is not int:
_fail("OWNER_MERGE_APPROVAL_TIME_INVALID")
if not payload["not_before"] <= now < payload["expires_at"]:
_fail("OWNER_MERGE_APPROVAL_EXPIRED")
if payload["issued_at"] > now + 5 or payload["expires_at"] - payload["issued_at"] != policy.maximum_ttl_seconds:
_fail("OWNER_MERGE_APPROVAL_TIME_INVALID")
supplied = payload.get("canonical_payload_digest")
body = dict(payload)
body.pop("canonical_payload_digest", None)
if supplied != sha256_json(body):
_fail("OWNER_MERGE_APPROVAL_CANONICAL_DIGEST_INVALID")
def verify(
self,
artifact: SignedArtifact | Mapping[str, Any],
policy: OwnerMergeApprovalPolicy,
) -> tuple[dict[str, Any], str]:
policy.validate()
payload, signature, key_id, namespace = self._artifact(artifact)
canonical = canonical_json_bytes(payload)
proof_path: str | None = None
for path, verifier in (("primary", self.primary), ("recovery", self.recovery)):
if verifier is None:
continue
try:
verified = verifier.verify(
namespace=namespace,
payload=canonical,
signature=signature,
key_id=key_id,
)
except Exception:
verified = False
if verified is True:
proof_path = path
break
if proof_path is None:
_fail("OWNER_MERGE_APPROVAL_SIGNATURE_INVALID")
self._validate_scope(payload, policy, now=int(self.clock()), key_id=key_id, namespace=namespace)
return payload, proof_path
class SQLiteOwnerMergeNonceLedger:
"""Durable one-shot fence. Unknown effects are intentionally not retried."""
def __init__(self, path: Path) -> None:
self.path = Path(path)
def _connection(self) -> sqlite3.Connection:
if self.path.is_symlink() or self.path.parent.is_symlink():
_fail("OWNER_MERGE_NONCE_LEDGER_PATH_INVALID")
self.path.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
connection = sqlite3.connect(self.path, timeout=5, isolation_level=None)
connection.execute(
"CREATE TABLE IF NOT EXISTS owner_merge_nonce ("
"nonce TEXT PRIMARY KEY, approval_digest TEXT NOT NULL, state TEXT NOT NULL, claimed_at INTEGER NOT NULL)"
)
return connection
def claim(self, *, nonce: str, approval_digest: str, now: int) -> None:
connection = self._connection()
try:
connection.execute("BEGIN IMMEDIATE")
existing = connection.execute(
"SELECT state FROM owner_merge_nonce WHERE nonce=?", (nonce,)
).fetchone()
if existing is not None:
connection.rollback()
_fail("OWNER_MERGE_NONCE_REPLAY")
connection.execute(
"INSERT INTO owner_merge_nonce(nonce,approval_digest,state,claimed_at) VALUES(?,?,?,?)",
(nonce, approval_digest, "PENDING", now),
)
connection.commit()
except OwnerMergeApprovalError:
raise
except sqlite3.Error as exc:
try:
connection.rollback()
except sqlite3.Error:
pass
raise OwnerMergeApprovalError("OWNER_MERGE_NONCE_LEDGER_UNAVAILABLE") from exc
finally:
connection.close()
def mark(self, *, nonce: str, state: str) -> None:
if state not in {"COMPLETED", "UNKNOWN"}:
_fail("OWNER_MERGE_NONCE_STATE_INVALID")
connection = self._connection()
try:
connection.execute("BEGIN IMMEDIATE")
updated = connection.execute(
"UPDATE owner_merge_nonce SET state=? WHERE nonce=? AND state='PENDING'",
(state, nonce),
).rowcount
if updated != 1:
connection.rollback()
_fail("OWNER_MERGE_NONCE_STATE_CONFLICT")
connection.commit()
except OwnerMergeApprovalError:
raise
except sqlite3.Error as exc:
try:
connection.rollback()
except sqlite3.Error:
pass
raise OwnerMergeApprovalError("OWNER_MERGE_NONCE_LEDGER_UNAVAILABLE") from exc
finally:
connection.close()
class GovernedOwnerMergeAdmission:
"""Admit one protected merge without owning any policy or technical gate."""
def __init__(
self,
*,
policy: OwnerMergeApprovalPolicy,
verifier: OwnerMergeApprovalVerifier,
live_state_reader: Callable[[], OwnerMergeLiveState],
executor: GovernedMergeExecutor,
reconciler: MergeOidReconciler,
nonce_ledger: SQLiteOwnerMergeNonceLedger,
clock: Callable[[], float] = time.time,
) -> None:
policy.validate()
self.policy = policy
self.verifier = verifier
self.live_state_reader = live_state_reader
self.executor = executor
self.reconciler = reconciler
self.nonce_ledger = nonce_ledger
self.clock = clock
def _validate_live_state(self, live: OwnerMergeLiveState) -> None:
if not isinstance(live, OwnerMergeLiveState):
_fail("OWNER_MERGE_LIVE_STATE_INVALID")
policy = self.policy
if live.repository != policy.repository:
_fail("OWNER_MERGE_REPOSITORY_DRIFT")
if live.pr_number != policy.pr_number:
_fail("OWNER_MERGE_PR_BINDING_DRIFT")
if live.pr_head_oid != policy.pr_head_oid:
_fail("OWNER_MERGE_HEAD_DRIFT")
if live.main_oid != policy.expected_main_oid:
_fail("OWNER_MERGE_BASE_DRIFT")
if live.required_check_contract_digest != policy.required_check_contract_digest:
_fail("OWNER_MERGE_REQUIRED_CHECK_CONTRACT_DRIFT")
if not isinstance(live.check_statuses, Mapping) or set(live.check_statuses) != set(policy.required_checks):
_fail("OWNER_MERGE_REQUIRED_CHECK_CONTRACT_DRIFT")
if any(value is not True for value in live.check_statuses.values()):
_fail("OWNER_MERGE_REQUIRED_CHECKS_FAILED")
def admit(self, artifact: SignedArtifact | Mapping[str, Any]) -> OwnerMergeAdmissionReceipt:
approval, proof_path = self.verifier.verify(artifact, self.policy)
self._validate_live_state(self.live_state_reader())
nonce = str(approval["nonce"])
approval_digest = hashlib.sha256(canonical_json_bytes(approval)).hexdigest()
self.nonce_ledger.claim(nonce=nonce, approval_digest=approval_digest, now=int(self.clock()))
try:
merge_oid = self.executor.execute(dict(approval))
_require_oid(merge_oid, "OWNER_MERGE_EXECUTOR_RESULT_INVALID")
readback = self.reconciler.readback(merge_oid)
if (
not isinstance(readback, OwnerMergeOidReadback)
or readback.authority_main_oid != merge_oid
or readback.gitea_main_oid != merge_oid
or readback.api_main_oid != merge_oid
):
self.nonce_ledger.mark(nonce=nonce, state="UNKNOWN")
_fail("OWNER_MERGE_OID_RECONCILIATION_REQUIRED")
except OwnerMergeApprovalError:
# Every failure after a durable claim represents an operation that
# is either known failed or needs reconciliation. Preserve the
# one-shot fence rather than exposing a retryable PENDING state.
try:
self.nonce_ledger.mark(nonce=nonce, state="UNKNOWN")
except OwnerMergeApprovalError:
pass
raise
except Exception as exc:
try:
self.nonce_ledger.mark(nonce=nonce, state="UNKNOWN")
except OwnerMergeApprovalError:
pass
raise OwnerMergeApprovalError("OWNER_MERGE_EXECUTION_UNKNOWN") from exc
self.nonce_ledger.mark(nonce=nonce, state="COMPLETED")
return OwnerMergeAdmissionReceipt(
approval_id=str(approval["approval_id"]),
nonce=nonce,
owner_identity=str(approval["owner_identity"]),
policy_digest=str(approval["policy_digest"]),
proof_path=proof_path,
merge_oid=merge_oid,
)
@@ -0,0 +1,287 @@
"""Restricted issuance service for ``aios.owner-merge-approval.v2``.
The service has one Owner-only operation. It accepts an empty request payload,
loads one immutable policy, generates approval identifiers and nonces itself,
and stores one exact issuance response. It deliberately does not expose a
generic signing, payload, or policy-selection API.
"""
from __future__ import annotations
import json
import os
from pathlib import Path
import sqlite3
import stat
import time
from typing import Any, Callable, Mapping
from aios_owner_merge_authorization import (
OwnerMergeApprovalAuthority,
OwnerMergeApprovalError,
OwnerMergeApprovalPolicy,
OwnerMergeApprovalVerifier,
)
from aios_single_host_authority import RegistryOwnerApprovalVerifier, SignedArtifact
ISSUE_OPERATION = "issue_owner_merge_approval"
def _fail(code: str) -> None:
raise OwnerMergeApprovalError(code)
def _secure_regular(path: Path, *, require_root_owner: bool) -> None:
try:
metadata = path.lstat()
except OSError as exc:
raise OwnerMergeApprovalError("OWNER_MERGE_ISSUER_FILE_MISSING") from exc
if path.is_symlink() or not stat.S_ISREG(metadata.st_mode):
_fail("OWNER_MERGE_ISSUER_FILE_INVALID")
if stat.S_IMODE(metadata.st_mode) & 0o022:
_fail("OWNER_MERGE_ISSUER_FILE_WRITABLE")
if require_root_owner and metadata.st_uid != 0:
_fail("OWNER_MERGE_ISSUER_FILE_NOT_ROOT_OWNED")
def _read_policy(path: Path, *, require_root_owner: bool) -> OwnerMergeApprovalPolicy:
_secure_regular(path, require_root_owner=require_root_owner)
try:
value = json.loads(path.read_text(encoding="utf-8"))
except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc:
raise OwnerMergeApprovalError("OWNER_MERGE_ISSUER_POLICY_INVALID") from exc
if not isinstance(value, Mapping):
_fail("OWNER_MERGE_ISSUER_POLICY_INVALID")
expected = set(OwnerMergeApprovalPolicy.__dataclass_fields__)
if set(value) != expected or not isinstance(value.get("required_checks"), list):
_fail("OWNER_MERGE_ISSUER_POLICY_INVALID")
try:
policy = OwnerMergeApprovalPolicy(
repository=value["repository"],
pr_number=value["pr_number"],
pr_head_oid=value["pr_head_oid"],
expected_main_oid=value["expected_main_oid"],
operation=value["operation"],
required_check_contract_digest=value["required_check_contract_digest"],
required_checks=tuple(value["required_checks"]),
owner_identity=value["owner_identity"],
audit_lineage_digest=value["audit_lineage_digest"],
maximum_ttl_seconds=value["maximum_ttl_seconds"],
policy_version=value["policy_version"],
schema=value["schema"],
)
policy.validate()
except (KeyError, TypeError, OwnerMergeApprovalError) as exc:
if isinstance(exc, OwnerMergeApprovalError):
raise OwnerMergeApprovalError("OWNER_MERGE_ISSUER_POLICY_INVALID") from exc
raise OwnerMergeApprovalError("OWNER_MERGE_ISSUER_POLICY_INVALID") from exc
return policy
def _artifact_dict(artifact: SignedArtifact) -> dict[str, Any]:
return {
"payload": dict(artifact.payload),
"signature": artifact.signature,
"key_id": artifact.key_id,
"namespace": artifact.namespace,
}
class SQLiteOwnerMergeIssuanceLedger:
"""Durably replay a current approval and retain expired issuance history."""
def __init__(self, path: Path) -> None:
self.path = Path(path)
def _connect(self) -> sqlite3.Connection:
if self.path.is_symlink() or self.path.parent.is_symlink():
_fail("OWNER_MERGE_ISSUER_LEDGER_PATH_INVALID")
self.path.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
connection = sqlite3.connect(self.path, timeout=5, isolation_level=None)
connection.execute(
"CREATE TABLE IF NOT EXISTS owner_merge_issuance ("
"nonce TEXT PRIMARY KEY, policy_digest TEXT NOT NULL, response_json TEXT NOT NULL, "
"issued_at INTEGER NOT NULL, expires_at INTEGER NOT NULL)"
)
connection.execute(
"CREATE INDEX IF NOT EXISTS owner_merge_issuance_policy_expiry "
"ON owner_merge_issuance(policy_digest, expires_at)"
)
return connection
def issue_once(
self,
*,
policy_digest: str,
now: int,
issuer: Callable[[], dict[str, Any]],
validate_current: Callable[[dict[str, Any]], None],
expiry_of: Callable[[dict[str, Any]], int],
) -> tuple[dict[str, Any], bool]:
connection = self._connect()
try:
connection.execute("BEGIN IMMEDIATE")
rows = connection.execute(
"SELECT response_json, expires_at FROM owner_merge_issuance "
"WHERE policy_digest=? AND expires_at>? ORDER BY issued_at DESC",
(policy_digest, now),
).fetchall()
if len(rows) > 1:
connection.rollback()
_fail("OWNER_MERGE_ISSUER_LEDGER_INVALID")
if rows:
try:
response = json.loads(rows[0][0])
except (TypeError, json.JSONDecodeError) as exc:
connection.rollback()
raise OwnerMergeApprovalError("OWNER_MERGE_ISSUER_LEDGER_INVALID") from exc
if not isinstance(response, dict):
connection.rollback()
_fail("OWNER_MERGE_ISSUER_LEDGER_INVALID")
if type(rows[0][1]) is not int or expiry_of(response) != rows[0][1]:
connection.rollback()
_fail("OWNER_MERGE_ISSUER_LEDGER_INVALID")
validate_current(response)
connection.rollback()
return response, True
response = issuer()
expires_at = expiry_of(response)
if expires_at <= now:
connection.rollback()
_fail("OWNER_MERGE_ISSUER_RESPONSE_EXPIRED")
artifact = response.get("artifact")
if not isinstance(artifact, Mapping):
connection.rollback()
_fail("OWNER_MERGE_ISSUER_LEDGER_INVALID")
payload = artifact.get("payload")
if not isinstance(payload, Mapping) or not isinstance(payload.get("nonce"), str):
connection.rollback()
_fail("OWNER_MERGE_ISSUER_LEDGER_INVALID")
encoded = json.dumps(
response,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
allow_nan=False,
)
connection.execute(
"INSERT INTO owner_merge_issuance(nonce,policy_digest,response_json,issued_at,expires_at) "
"VALUES(?,?,?,?,?)",
(payload["nonce"], policy_digest, encoded, now, expires_at),
)
connection.commit()
return response, False
except OwnerMergeApprovalError:
raise
except sqlite3.IntegrityError as exc:
try:
connection.rollback()
except sqlite3.Error:
pass
raise OwnerMergeApprovalError("OWNER_MERGE_ISSUER_NONCE_CONFLICT") from exc
except sqlite3.Error as exc:
try:
connection.rollback()
except sqlite3.Error:
pass
raise OwnerMergeApprovalError("OWNER_MERGE_ISSUER_LEDGER_UNAVAILABLE") from exc
finally:
connection.close()
class OwnerMergeApprovalService:
"""A formal structured adapter, not an arbitrary signing oracle."""
def __init__(
self,
*,
policy_path: Path,
registry_path: Path,
owner_key_path: Path,
issuance_ledger_path: Path,
allowed_uids: tuple[int, ...],
require_root_owner: bool = True,
clock: Callable[[], float] = time.time,
token_urlsafe: Callable[[int], str] | None = None,
) -> None:
if not allowed_uids or any(type(uid) is not int or uid < 0 for uid in allowed_uids):
_fail("OWNER_MERGE_ISSUER_ALLOWED_UIDS_INVALID")
self.policy_path = Path(policy_path)
self.registry_path = Path(registry_path)
self.owner_key_path = Path(owner_key_path)
self.allowed_uids = tuple(allowed_uids)
self.require_root_owner = require_root_owner
self.clock = clock
self.token_urlsafe = token_urlsafe
self.ledger = SQLiteOwnerMergeIssuanceLedger(issuance_ledger_path)
def handle(self, request: Mapping[str, Any], *, peer_uid: int) -> dict[str, Any]:
if peer_uid not in self.allowed_uids:
_fail("OWNER_MERGE_ISSUER_PEER_UID_FORBIDDEN")
if not isinstance(request, Mapping) or set(request) != {"operation", "payload"}:
_fail("OWNER_MERGE_ISSUER_REQUEST_INVALID")
if request.get("operation") != ISSUE_OPERATION:
_fail("OWNER_MERGE_ISSUER_OPERATION_FORBIDDEN")
if request.get("payload") != {}:
_fail("OWNER_MERGE_ISSUER_REQUEST_FIELDS_INVALID")
policy = _read_policy(
self.policy_path,
require_root_owner=self.require_root_owner,
)
_secure_regular(
self.owner_key_path,
require_root_owner=self.require_root_owner,
)
primary = RegistryOwnerApprovalVerifier(
self.registry_path,
require_root_owner=self.require_root_owner,
)
verifier = OwnerMergeApprovalVerifier(primary=primary, clock=self.clock)
def expiry_of(response: dict[str, Any]) -> int:
if set(response) != {"status", "artifact"} or response.get("status") != "PASS":
_fail("OWNER_MERGE_ISSUER_LEDGER_INVALID")
artifact = response.get("artifact")
if not isinstance(artifact, Mapping):
_fail("OWNER_MERGE_ISSUER_LEDGER_INVALID")
payload = artifact.get("payload")
if not isinstance(payload, Mapping) or type(payload.get("expires_at")) is not int:
_fail("OWNER_MERGE_ISSUER_LEDGER_INVALID")
return payload["expires_at"]
def validate_current(response: dict[str, Any]) -> None:
expiry_of(response)
artifact = response["artifact"]
if not isinstance(artifact, Mapping):
_fail("OWNER_MERGE_ISSUER_LEDGER_INVALID")
try:
verifier.verify(artifact, policy)
except OwnerMergeApprovalError as exc:
raise OwnerMergeApprovalError("OWNER_MERGE_ISSUER_LEDGER_INVALID") from exc
def issue() -> dict[str, Any]:
kwargs: dict[str, Any] = {}
if self.token_urlsafe is not None:
kwargs["token_urlsafe"] = self.token_urlsafe
artifact = OwnerMergeApprovalAuthority(
self.owner_key_path,
policy,
require_root_owner=self.require_root_owner,
clock=self.clock,
**kwargs,
).issue()
value = _artifact_dict(artifact)
verifier.verify(value, policy)
return {"status": "PASS", "artifact": value}
response, idempotent = self.ledger.issue_once(
policy_digest=policy.digest,
now=int(self.clock()),
issuer=issue,
validate_current=validate_current,
expiry_of=expiry_of,
)
if set(response) != {"status", "artifact"} or response.get("status") != "PASS":
_fail("OWNER_MERGE_ISSUER_LEDGER_INVALID")
return {**response, "idempotent": idempotent}
@@ -0,0 +1,967 @@
"""Bounded, read-only discovery over a verified immutable Git repository.
The module is deliberately independent from Task, lease, credential, worktree,
and write code. Every public operation verifies the exact repository identity,
resolves a floating ref once to a full commit OID, and then reads only that
immutable snapshot.
"""
from __future__ import annotations
import base64
from collections.abc import Iterable, Iterator, Mapping
import fnmatch
import hashlib
import hmac
import json
import os
from pathlib import Path
import re
import subprocess
import time
from authoritative_git import AuthoritativeGitError, verify_repository
_OID_RE = re.compile(r"^[0-9a-f]{40}$")
_REF_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._/-]{0,239}$")
_IDENTITY_RE = re.compile(
r"^[A-Za-z0-9][A-Za-z0-9._-]{0,127}/[A-Za-z0-9][A-Za-z0-9._-]{0,127}$"
)
_READ_ONLY_COMMANDS = frozenset({"cat-file", "diff", "ls-tree", "rev-parse"})
_MAX_COMMAND_OUTPUT = 1_048_576
_MAX_RECORD_BYTES = 1_048_576
_MAX_LIMIT = 1_000
_MAX_DEPTH = 32
_MAX_BYTES = 1_048_576
_MAX_GLOBS = 16
_MAX_QUERY_LENGTH = 512
_MAX_CURSOR_LENGTH = 4_096
_GIT_TIMEOUT_SECONDS = 8.0
class RepositoryDiscoveryError(RuntimeError):
"""A fail-closed repository discovery request error."""
def __init__(self, code: str, *, status_code: int | None = None) -> None:
self.code = str(code)
self.status_code = (
status_code if status_code is not None else self._status_for(self.code)
)
super().__init__(self.code)
@staticmethod
def _status_for(code: str) -> int:
if "OUTPUT_LIMIT" in code:
return 413
if code.endswith("_NOT_FOUND"):
return 404
if code in {"REPOSITORY_NOT_ALLOWED", "REPOSITORY_IDENTITY_MISMATCH"}:
return 403
if code in {
"REPOSITORY_DISCOVERY_UNAVAILABLE",
"REPOSITORY_DISCOVERY_GIT_FAILED",
"REPOSITORY_DISCOVERY_GIT_OUTPUT_INVALID",
"REPOSITORY_DISCOVERY_CURSOR_SECRET_REQUIRED",
}:
return 503
return 400
class RepositoryDiscoveryNotFound(RepositoryDiscoveryError):
"""A validated immutable object or path was not found."""
class RepositoryDiscoveryOutputTooLarge(RepositoryDiscoveryError):
"""A fixed read-only Git query exceeded its collection boundary."""
def _error(code: str) -> RepositoryDiscoveryError:
return RepositoryDiscoveryError(code)
def _request_mapping(request: Mapping[str, object]) -> Mapping[str, object]:
if not isinstance(request, Mapping):
raise _error("REPOSITORY_DISCOVERY_REQUEST_INVALID")
return request
def _required_request_value(request: Mapping[str, object], field: str) -> object:
value = request.get(field)
if value is None:
raise _error("REPOSITORY_DISCOVERY_REQUEST_INVALID")
return value
def _canonical(value: Mapping[str, object]) -> bytes:
return json.dumps(
value, sort_keys=True, separators=(",", ":"), ensure_ascii=True
).encode("ascii")
def _positive_int(value: object, field: str, maximum: int) -> int:
if isinstance(value, bool) or not isinstance(value, int) or not 1 <= value <= maximum:
raise _error(f"REPOSITORY_DISCOVERY_INVALID_{field.upper()}")
return value
def _safe_relative_path(
value: object,
field: str,
*,
allow_empty: bool,
allow_glob: bool = False,
) -> str:
if not isinstance(value, str) or value != value.strip() or "\x00" in value:
raise _error("REPOSITORY_DISCOVERY_PATH_INVALID")
if "\\" in value:
raise _error("REPOSITORY_DISCOVERY_PATH_INVALID")
if value in ("", "."):
if allow_empty:
return ""
raise _error("REPOSITORY_DISCOVERY_PATH_INVALID")
if value.startswith(("/", "-")) or "//" in value or ":" in value:
raise _error("REPOSITORY_DISCOVERY_PATH_INVALID")
if not allow_glob and any(character in value for character in "*?["):
raise _error("REPOSITORY_DISCOVERY_PATH_INVALID")
parts = value.split("/")
if any(part in ("", ".", "..") for part in parts):
raise _error("REPOSITORY_DISCOVERY_PATH_INVALID")
if len(value) > 512:
raise _error("REPOSITORY_DISCOVERY_PATH_INVALID")
return value
def _safe_glob(value: object) -> str:
return _safe_relative_path(value, "path_glob", allow_empty=False, allow_glob=True)
def _safe_ref(value: str) -> bool:
return bool(
_REF_RE.fullmatch(value)
and ".." not in value
and "//" not in value
and "@{" not in value
and not value.endswith(("/", ".", ".lock"))
and not any(part in ("", ".", "..") for part in value.split("/"))
)
def _parse_tree_record(record: bytes) -> dict[str, str] | None:
try:
header, raw_path = record.split(b"\t", 1)
mode, kind, oid = header.decode("ascii").split(" ", 2)
path = raw_path.decode("utf-8")
except (UnicodeDecodeError, ValueError) as exc:
raise _error("REPOSITORY_DISCOVERY_GIT_OUTPUT_INVALID") from exc
if kind not in {"blob", "tree", "commit"} or not _OID_RE.fullmatch(oid.lower()):
raise _error("REPOSITORY_DISCOVERY_GIT_OUTPUT_INVALID")
try:
_safe_relative_path(path, "tree_path", allow_empty=False)
except RepositoryDiscoveryError:
# Git permits filenames outside the public POSIX-relative contract.
# They are not exposed as usable discovery paths.
return None
return {"mode": mode, "type": kind, "object_oid": oid.lower(), "path": path}
def _decode_output_path(raw: bytes) -> str:
try:
value = raw.decode("utf-8")
except UnicodeDecodeError as exc:
raise _error("REPOSITORY_DISCOVERY_GIT_OUTPUT_INVALID") from exc
return _safe_relative_path(value, "git_output_path", allow_empty=False)
class RepositoryDiscovery:
"""Repository-scoped immutable Git discovery with signed cursors."""
def __init__(
self,
*,
allowed_repositories: Iterable[str] | None = None,
git_dir: str | Path | None = None,
cursor_secret: str | bytes | None = None,
) -> None:
configured = allowed_repositories
if configured is None:
configured = (os.getenv("AIOS_GITOPS_REPOSITORY_IDENTITY", ""),)
identities = tuple(configured)
if not identities or any(
not isinstance(item, str) or not _IDENTITY_RE.fullmatch(item)
for item in identities
):
raise _error("REPOSITORY_DISCOVERY_ALLOWLIST_INVALID")
self._allowed_repositories = frozenset(identities)
self._git_dir = git_dir
secret = (
cursor_secret
if cursor_secret is not None
else os.getenv("AIOS_REPOSITORY_DISCOVERY_CURSOR_SECRET", "")
)
if isinstance(secret, str):
secret = secret.encode("utf-8")
if not isinstance(secret, bytes) or len(secret) < 16:
raise _error("REPOSITORY_DISCOVERY_CURSOR_SECRET_REQUIRED")
self._cursor_secret = secret
def _repository_path(self, repository: object) -> Path:
if not isinstance(repository, str) or repository not in self._allowed_repositories:
raise _error("REPOSITORY_NOT_ALLOWED")
try:
path, actual_identity = verify_repository(
git_dir=self._git_dir,
repository_identity=repository,
)
except AuthoritativeGitError as exc:
code = (
"REPOSITORY_IDENTITY_MISMATCH"
if "REPOSITORY_IDENTITY_MISMATCH" in str(exc)
else "REPOSITORY_DISCOVERY_UNAVAILABLE"
)
raise _error(code) from exc
if actual_identity != repository:
raise _error("REPOSITORY_IDENTITY_MISMATCH")
return path
@staticmethod
def _git_env() -> dict[str, str]:
return {
**os.environ,
"GIT_OPTIONAL_LOCKS": "0",
"GIT_CONFIG_NOSYSTEM": "1",
"GIT_TERMINAL_PROMPT": "0",
}
@staticmethod
def _run(
path: Path,
args: list[str],
*,
max_output: int = _MAX_COMMAND_OUTPUT,
) -> bytes:
if not args or args[0] not in _READ_ONLY_COMMANDS:
raise _error("REPOSITORY_DISCOVERY_GIT_COMMAND_REJECTED")
output_limit = min(max_output, _MAX_COMMAND_OUTPUT)
process: subprocess.Popen[bytes] | None = None
try:
process = subprocess.Popen(
["git", "--git-dir", str(path), *args],
shell=False,
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
env=RepositoryDiscovery._git_env(),
)
assert process.stdout is not None
output = process.stdout.read(output_limit + 1)
if len(output) > output_limit:
process.kill()
process.wait(timeout=1)
raise RepositoryDiscoveryOutputTooLarge(
"REPOSITORY_DISCOVERY_OUTPUT_LIMIT"
)
returncode = process.wait(timeout=_GIT_TIMEOUT_SECONDS)
except RepositoryDiscoveryError:
raise
except (OSError, subprocess.TimeoutExpired) as exc:
if process is not None:
try:
process.kill()
process.wait(timeout=1)
except (OSError, subprocess.TimeoutExpired):
pass
raise _error("REPOSITORY_DISCOVERY_GIT_FAILED") from exc
if returncode != 0:
raise _error("REPOSITORY_DISCOVERY_GIT_FAILED")
return output
@staticmethod
def _read_blob_prefix(path: Path, oid: str, maximum: int) -> tuple[bytes, bool]:
"""Read at most maximum bytes plus one sentinel byte."""
process: subprocess.Popen[bytes] | None = None
try:
process = subprocess.Popen(
["git", "--git-dir", str(path), "cat-file", "blob", oid],
shell=False,
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
env=RepositoryDiscovery._git_env(),
)
assert process.stdout is not None
content = process.stdout.read(maximum + 1)
truncated = len(content) > maximum
if truncated:
process.kill()
process.wait(timeout=1)
return content[:maximum], True
if process.wait(timeout=_GIT_TIMEOUT_SECONDS) != 0:
raise _error("REPOSITORY_DISCOVERY_GIT_FAILED")
return content, False
except RepositoryDiscoveryError:
raise
except (OSError, subprocess.TimeoutExpired) as exc:
if process is not None:
try:
process.kill()
process.wait(timeout=1)
except (OSError, subprocess.TimeoutExpired):
pass
raise _error("REPOSITORY_DISCOVERY_GIT_FAILED") from exc
def _iter_nul_records(self, path: Path, args: list[str]) -> Iterator[bytes]:
if not args or args[0] not in _READ_ONLY_COMMANDS:
raise _error("REPOSITORY_DISCOVERY_GIT_COMMAND_REJECTED")
process: subprocess.Popen[bytes] | None = None
buffer = b""
started = time.monotonic()
try:
process = subprocess.Popen(
["git", "--git-dir", str(path), *args],
shell=False,
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
env=self._git_env(),
)
assert process.stdout is not None
while True:
if time.monotonic() - started > _GIT_TIMEOUT_SECONDS:
raise _error("REPOSITORY_DISCOVERY_GIT_FAILED")
chunk = process.stdout.read(64 * 1024)
if not chunk:
break
buffer += chunk
if len(buffer) > _MAX_RECORD_BYTES and b"\0" not in buffer:
raise _error("REPOSITORY_DISCOVERY_GIT_OUTPUT_INVALID")
while b"\0" in buffer:
record, buffer = buffer.split(b"\0", 1)
if len(record) > _MAX_RECORD_BYTES:
raise _error("REPOSITORY_DISCOVERY_GIT_OUTPUT_INVALID")
yield record
if buffer:
raise _error("REPOSITORY_DISCOVERY_GIT_OUTPUT_INVALID")
if process.wait(
timeout=max(0.1, _GIT_TIMEOUT_SECONDS - (time.monotonic() - started))
) != 0:
raise _error("REPOSITORY_DISCOVERY_GIT_FAILED")
except RepositoryDiscoveryError:
raise
except (OSError, subprocess.TimeoutExpired) as exc:
raise _error("REPOSITORY_DISCOVERY_GIT_FAILED") from exc
finally:
if process is not None and process.poll() is None:
try:
process.kill()
process.wait(timeout=1)
except (OSError, subprocess.TimeoutExpired):
pass
def _resolve_at(self, path: Path, revision: object, field: str) -> str:
if (
not isinstance(revision, str)
or revision != revision.strip()
or not revision
or revision.startswith("-")
):
raise _error("REPOSITORY_DISCOVERY_INVALID_REVISION")
if _OID_RE.fullmatch(revision.lower()):
candidates = [revision.lower()]
elif revision == "HEAD":
candidates = ["HEAD"]
elif _safe_ref(revision) and not any(
token in revision for token in ("^", "~", ":", "\\")
):
if revision.startswith("refs/"):
if not revision.startswith(("refs/heads/", "refs/tags/")):
raise _error("REPOSITORY_DISCOVERY_INVALID_REVISION")
candidates = [revision]
else:
candidates = [f"refs/heads/{revision}", f"refs/tags/{revision}"]
else:
raise _error("REPOSITORY_DISCOVERY_INVALID_REVISION")
resolved: list[str] = []
for candidate in candidates:
try:
output = self._run(
path,
["rev-parse", "--verify", f"{candidate}^{{commit}}"],
max_output=256,
)
except RepositoryDiscoveryError:
continue
oid = output.decode("ascii", errors="ignore").strip().lower()
if _OID_RE.fullmatch(oid):
resolved.append(oid)
if not resolved:
raise RepositoryDiscoveryNotFound(
f"REPOSITORY_DISCOVERY_{field.upper()}_NOT_FOUND"
)
if len(set(resolved)) != 1:
raise _error("REPOSITORY_DISCOVERY_AMBIGUOUS_REVISION")
return resolved[0]
def resolve_revision(self, repository: str, revision: str) -> str:
return self._resolve_at(self._repository_path(repository), revision, "revision")
def _cursor_offset(
self,
cursor: str | None,
*,
operation: str,
repository: str,
resolved_oid: str,
query: Mapping[str, object],
) -> int:
if cursor is None:
return 0
if (
not isinstance(cursor, str)
or not cursor
or len(cursor) > _MAX_CURSOR_LENGTH
or "." not in cursor
):
raise _error("REPOSITORY_DISCOVERY_CURSOR_INVALID")
encoded, supplied_signature = cursor.split(".", 1)
try:
padded = encoded + "=" * (-len(encoded) % 4)
payload = json.loads(
base64.urlsafe_b64decode(padded.encode("ascii")).decode("ascii")
)
except (UnicodeError, ValueError, json.JSONDecodeError) as exc:
raise _error("REPOSITORY_DISCOVERY_CURSOR_INVALID") from exc
expected_signature = hmac.new(
self._cursor_secret, encoded.encode("ascii"), hashlib.sha256
).hexdigest()
if not hmac.compare_digest(supplied_signature, expected_signature):
raise _error("REPOSITORY_DISCOVERY_CURSOR_INVALID")
expected = {
"operation": operation,
"repository": repository,
"resolved_oid": resolved_oid,
"query": dict(query),
}
if (
not isinstance(payload, dict)
or payload.get("v") != 1
or any(payload.get(key) != value for key, value in expected.items())
):
raise _error("REPOSITORY_DISCOVERY_CURSOR_INVALID")
offset = payload.get("offset")
if isinstance(offset, bool) or not isinstance(offset, int) or offset < 0:
raise _error("REPOSITORY_DISCOVERY_CURSOR_INVALID")
return offset
def _next_cursor(
self,
*,
operation: str,
repository: str,
resolved_oid: str,
query: Mapping[str, object],
offset: int,
) -> str:
payload = {
"v": 1,
"operation": operation,
"repository": repository,
"resolved_oid": resolved_oid,
"query": dict(query),
"offset": offset,
}
encoded = base64.urlsafe_b64encode(_canonical(payload)).decode("ascii").rstrip("=")
signature = hmac.new(
self._cursor_secret, encoded.encode("ascii"), hashlib.sha256
).hexdigest()
return f"{encoded}.{signature}"
def _iter_tree_entries(
self,
path: Path,
oid: str,
prefix: str,
*,
include_trees: bool,
) -> Iterator[dict[str, str]]:
args = ["ls-tree", "-z", "-r", "--full-tree"]
if include_trees:
args.append("-t")
args.append(oid)
if prefix:
args.extend(["--", prefix])
for record in self._iter_nul_records(path, args):
entry = _parse_tree_record(record)
if entry is None:
continue
if not prefix or entry["path"] == prefix or entry["path"].startswith(prefix + "/"):
yield entry
def list_tree(
self,
*,
repository: str,
revision: str,
prefix: str = "",
depth: int = 8,
limit: int = 100,
cursor: str | None = None,
) -> dict[str, object]:
prefix = _safe_relative_path(prefix, "prefix", allow_empty=True)
depth = _positive_int(depth, "depth", _MAX_DEPTH)
limit = _positive_int(limit, "limit", _MAX_LIMIT)
path = self._repository_path(repository)
resolved_oid = self._resolve_at(path, revision, "revision")
query = {"prefix": prefix, "depth": depth, "limit": limit}
offset = self._cursor_offset(
cursor,
operation="list_tree",
repository=repository,
resolved_oid=resolved_oid,
query=query,
)
page: list[dict[str, str]] = []
matched = 0
for entry in self._iter_tree_entries(path, resolved_oid, prefix, include_trees=True):
relative = entry["path"][len(prefix):].lstrip("/") if prefix else entry["path"]
if relative.count("/") + 1 > depth:
continue
if matched >= offset and len(page) < limit:
page.append(entry)
matched += 1
has_more = offset + len(page) < matched
return {
"repository": repository,
"resolved_oid": resolved_oid,
"entries": page,
"truncated": has_more,
"next_cursor": (
self._next_cursor(
operation="list_tree",
repository=repository,
resolved_oid=resolved_oid,
query=query,
offset=offset + len(page),
)
if has_more
else None
),
}
def list_repository_tree(self, request: Mapping[str, object]) -> dict[str, object]:
request = _request_mapping(request)
return self.list_tree(
repository=_required_request_value(request, "repository"), # type: ignore[arg-type]
revision=request.get("revision", request.get("ref", "HEAD")), # type: ignore[arg-type]
prefix=request.get("prefix", ""), # type: ignore[arg-type]
depth=request.get("depth", 8), # type: ignore[arg-type]
limit=request.get("limit", 100), # type: ignore[arg-type]
cursor=request.get("cursor"), # type: ignore[arg-type]
)
def _blob_size(self, path: Path, oid: str) -> int:
output = self._run(path, ["cat-file", "-s", oid], max_output=128)
try:
size = int(output.decode("ascii").strip())
except (UnicodeDecodeError, ValueError) as exc:
raise _error("REPOSITORY_DISCOVERY_GIT_OUTPUT_INVALID") from exc
if size < 0:
raise _error("REPOSITORY_DISCOVERY_GIT_OUTPUT_INVALID")
return size
def search_source(
self,
*,
repository: str,
revision: str,
query: str,
path_globs: Iterable[str] | None = None,
max_matches: int = 100,
max_bytes: int = 262_144,
cursor: str | None = None,
) -> dict[str, object]:
if (
not isinstance(query, str)
or not query
or "\x00" in query
or len(query) > _MAX_QUERY_LENGTH
):
raise _error("REPOSITORY_DISCOVERY_QUERY_INVALID")
max_matches = _positive_int(max_matches, "max_matches", _MAX_LIMIT)
max_bytes = _positive_int(max_bytes, "max_bytes", _MAX_BYTES)
if path_globs is None:
globs = ("*",)
else:
if isinstance(path_globs, (str, bytes)) or not isinstance(path_globs, Iterable):
raise _error("REPOSITORY_DISCOVERY_PATH_INVALID")
globs = tuple(_safe_glob(item) for item in path_globs)
if not globs or len(globs) > _MAX_GLOBS:
raise _error("REPOSITORY_DISCOVERY_PATH_INVALID")
path = self._repository_path(repository)
resolved_oid = self._resolve_at(path, revision, "revision")
query_binding = {
"query": query,
"path_globs": list(globs),
"max_matches": max_matches,
"max_bytes": max_bytes,
}
offset = self._cursor_offset(
cursor,
operation="search_source",
repository=repository,
resolved_oid=resolved_oid,
query=query_binding,
)
scanned_bytes = 0
skipped_for_bound = False
binary_blobs = 0
matched = 0
matches: list[dict[str, object]] = []
has_more = False
for entry in self._iter_tree_entries(path, resolved_oid, "", include_trees=False):
if entry["type"] != "blob" or not any(
fnmatch.fnmatchcase(entry["path"], pattern) for pattern in globs
):
continue
blob_size = self._blob_size(path, entry["object_oid"])
remaining = max_bytes - scanned_bytes
if blob_size > remaining:
skipped_for_bound = True
continue
raw, clipped = self._read_blob_prefix(path, entry["object_oid"], blob_size)
if clipped:
raise _error("REPOSITORY_DISCOVERY_GIT_FAILED")
scanned_bytes += len(raw)
if b"\0" in raw:
binary_blobs += 1
continue
text = raw.decode("utf-8", errors="replace")
for number, line in enumerate(text.splitlines(), start=1):
if query not in line:
continue
if matched < offset:
matched += 1
continue
if len(matches) >= max_matches:
has_more = True
break
matches.append(
{
"path": entry["path"],
"line_start": number,
"line_end": number,
"blob_oid": entry["object_oid"],
"snippet": line[:512],
}
)
matched += 1
if has_more:
break
return {
"repository": repository,
"resolved_oid": resolved_oid,
"matches": matches,
"scanned_bytes": scanned_bytes,
"max_bytes": max_bytes,
"binary_blobs_skipped": binary_blobs,
"truncated": bool(has_more or skipped_for_bound),
"next_cursor": (
self._next_cursor(
operation="search_source",
repository=repository,
resolved_oid=resolved_oid,
query=query_binding,
offset=offset + len(matches),
)
if has_more
else None
),
}
def search_repository_source(self, request: Mapping[str, object]) -> dict[str, object]:
request = _request_mapping(request)
path_globs = request.get("path_globs")
if path_globs is not None and (
isinstance(path_globs, (str, bytes)) or not isinstance(path_globs, Iterable)
):
raise _error("REPOSITORY_DISCOVERY_PATH_INVALID")
return self.search_source(
repository=_required_request_value(request, "repository"), # type: ignore[arg-type]
revision=request.get("revision", request.get("ref", "HEAD")), # type: ignore[arg-type]
query=_required_request_value(request, "query"), # type: ignore[arg-type]
path_globs=path_globs, # type: ignore[arg-type]
max_matches=request.get("max_matches", 100), # type: ignore[arg-type]
max_bytes=request.get("max_bytes", 262_144), # type: ignore[arg-type]
cursor=request.get("cursor"), # type: ignore[arg-type]
)
def _iter_diff_name_entries(
self,
path: Path,
from_oid: str,
to_oid: str,
prefix: str,
) -> Iterator[dict[str, object]]:
suffix = ["--", prefix] if prefix else []
records = self._iter_nul_records(
path,
[
"diff",
"--no-ext-diff",
"--no-textconv",
"--find-renames=50%",
"--name-status",
"-z",
from_oid,
to_oid,
*suffix,
],
)
iterator = iter(records)
while True:
try:
status_raw = next(iterator)
except StopIteration:
return
try:
status = status_raw.decode("ascii")
except UnicodeDecodeError as exc:
raise _error("REPOSITORY_DISCOVERY_GIT_OUTPUT_INVALID") from exc
if not status or status[0] not in {"A", "C", "D", "M", "R", "T", "U", "X"}:
raise _error("REPOSITORY_DISCOVERY_GIT_OUTPUT_INVALID")
try:
if status[0] in {"R", "C"}:
old_path = _decode_output_path(next(iterator))
new_path = _decode_output_path(next(iterator))
yield {
"status": status,
"path": new_path,
"old_path": old_path,
}
else:
yield {
"status": status,
"path": _decode_output_path(next(iterator)),
"old_path": None,
}
except StopIteration as exc:
raise _error("REPOSITORY_DISCOVERY_GIT_OUTPUT_INVALID") from exc
@staticmethod
def _parse_numstat_count(value: bytes) -> int | None:
if value == b"-":
return None
try:
parsed = int(value.decode("ascii"), 10)
except (UnicodeDecodeError, ValueError) as exc:
raise _error("REPOSITORY_DISCOVERY_GIT_OUTPUT_INVALID") from exc
if parsed < 0:
raise _error("REPOSITORY_DISCOVERY_GIT_OUTPUT_INVALID")
return parsed
def _diff_stats(
self,
path: Path,
from_oid: str,
to_oid: str,
prefix: str,
wanted_paths: set[str],
) -> dict[str, tuple[int | None, int | None]]:
suffix = ["--", prefix] if prefix else []
records = self._iter_nul_records(
path,
[
"diff",
"--no-ext-diff",
"--no-textconv",
"--find-renames=50%",
"--numstat",
"-z",
from_oid,
to_oid,
*suffix,
],
)
iterator = iter(records)
stats: dict[str, tuple[int | None, int | None]] = {}
for row in iterator:
if not row:
continue
fields = row.split(b"\t", 2)
if len(fields) != 3:
raise _error("REPOSITORY_DISCOVERY_GIT_OUTPUT_INVALID")
additions = self._parse_numstat_count(fields[0])
deletions = self._parse_numstat_count(fields[1])
if fields[2]:
stat_path = _decode_output_path(fields[2])
else:
try:
next(iterator) # old path
stat_path = _decode_output_path(next(iterator))
except StopIteration as exc:
raise _error("REPOSITORY_DISCOVERY_GIT_OUTPUT_INVALID") from exc
if stat_path in wanted_paths:
stats[stat_path] = (additions, deletions)
if wanted_paths <= set(stats):
break
return stats
def diff_index(
self,
*,
repository: str,
from_revision: str,
to_revision: str,
prefix: str = "",
limit: int = 100,
cursor: str | None = None,
) -> dict[str, object]:
prefix = _safe_relative_path(prefix, "prefix", allow_empty=True)
limit = _positive_int(limit, "limit", _MAX_LIMIT)
path = self._repository_path(repository)
from_oid = self._resolve_at(path, from_revision, "from")
to_oid = self._resolve_at(path, to_revision, "to")
query = {
"from_oid": from_oid,
"to_oid": to_oid,
"prefix": prefix,
"limit": limit,
}
offset = self._cursor_offset(
cursor,
operation="diff_index",
repository=repository,
resolved_oid=to_oid,
query=query,
)
page: list[dict[str, object]] = []
total = 0
for entry in self._iter_diff_name_entries(path, from_oid, to_oid, prefix):
if total >= offset and len(page) < limit:
page.append(entry)
total += 1
wanted_paths = {str(entry["path"]) for entry in page}
stat_by_path = (
self._diff_stats(path, from_oid, to_oid, prefix, wanted_paths)
if page
else {}
)
for entry in page:
additions, deletions = stat_by_path.get(
str(entry["path"]), (None, None)
)
entry["additions"] = additions
entry["deletions"] = deletions
has_more = offset + len(page) < total
return {
"repository": repository,
"resolved_from_oid": from_oid,
"resolved_to_oid": to_oid,
"entries": page,
"truncated": has_more,
"next_cursor": (
self._next_cursor(
operation="diff_index",
repository=repository,
resolved_oid=to_oid,
query=query,
offset=offset + len(page),
)
if has_more
else None
),
}
def get_diff_index(self, request: Mapping[str, object]) -> dict[str, object]:
request = _request_mapping(request)
from_revision = request.get("from_revision", request.get("from"))
to_revision = request.get("to_revision", request.get("to"))
prefix = request.get("prefix", request.get("path_prefix", ""))
if from_revision is None or to_revision is None:
raise _error("REPOSITORY_DISCOVERY_REQUEST_INVALID")
return self.diff_index(
repository=_required_request_value(request, "repository"), # type: ignore[arg-type]
from_revision=from_revision, # type: ignore[arg-type]
to_revision=to_revision, # type: ignore[arg-type]
prefix=prefix, # type: ignore[arg-type]
limit=request.get("limit", 100), # type: ignore[arg-type]
cursor=request.get("cursor"), # type: ignore[arg-type]
)
def read_file(
self,
*,
repository: str,
revision: str,
file_path: str,
max_bytes: int = 262_144,
) -> dict[str, object]:
file_path = _safe_relative_path(file_path, "file_path", allow_empty=False)
max_bytes = _positive_int(max_bytes, "max_bytes", _MAX_BYTES)
path = self._repository_path(repository)
resolved_oid = self._resolve_at(path, revision, "revision")
exact: list[dict[str, str]] = []
for entry in self._iter_tree_entries(
path, resolved_oid, file_path, include_trees=False
):
if entry["path"] == file_path and entry["type"] == "blob":
exact.append(entry)
if len(exact) != 1:
raise RepositoryDiscoveryNotFound("REPOSITORY_DISCOVERY_FILE_NOT_FOUND")
blob_oid = exact[0]["object_oid"]
content, truncated = self._read_blob_prefix(path, blob_oid, max_bytes)
return {
"repository": repository,
"resolved_oid": resolved_oid,
"blob_oid": blob_oid,
"path": file_path,
"content": content.decode("utf-8", errors="replace"),
"content_base64": base64.b64encode(content).decode("ascii"),
"content_bytes": len(content),
"truncated": truncated,
}
def read_repository_file(self, request: Mapping[str, object]) -> dict[str, object]:
request = _request_mapping(request)
file_path = request.get("file_path", request.get("path"))
if file_path is None:
raise _error("REPOSITORY_DISCOVERY_REQUEST_INVALID")
return self.read_file(
repository=_required_request_value(request, "repository"), # type: ignore[arg-type]
revision=request.get("revision", request.get("ref", "HEAD")), # type: ignore[arg-type]
file_path=file_path, # type: ignore[arg-type]
max_bytes=request.get("max_bytes", 262_144), # type: ignore[arg-type]
)
def create_repository_discovery(
*,
allowed_repositories: Iterable[str] | None = None,
git_dir: str | Path | None = None,
cursor_secret: str | bytes | None = None,
) -> RepositoryDiscovery:
return RepositoryDiscovery(
allowed_repositories=allowed_repositories,
git_dir=git_dir,
cursor_secret=cursor_secret,
)
def list_repository_tree(
request: Mapping[str, object], *, discovery: RepositoryDiscovery | None = None
) -> dict[str, object]:
return (discovery or create_repository_discovery()).list_repository_tree(request)
def search_repository_source(
request: Mapping[str, object], *, discovery: RepositoryDiscovery | None = None
) -> dict[str, object]:
return (discovery or create_repository_discovery()).search_repository_source(request)
def get_diff_index(
request: Mapping[str, object], *, discovery: RepositoryDiscovery | None = None
) -> dict[str, object]:
return (discovery or create_repository_discovery()).get_diff_index(request)
def read_repository_file(
request: Mapping[str, object], *, discovery: RepositoryDiscovery | None = None
) -> dict[str, object]:
return (discovery or create_repository_discovery()).read_repository_file(request)
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,707 @@
"""Single-host operational delegation for AIOS migration.
This module is deliberately narrow:
* Owner signs only OperationalDelegation and ExpiredTaskRecoveryDecision.
* Machine signer signs only WorkerRuntimeBinding and WorkerRuntimeProof.
* Every object is bound to one immutable Task identity and a narrow capability set.
* There is no arbitrary sign-bytes API.
"""
from __future__ import annotations
from dataclasses import asdict, dataclass
import hashlib
import json
from pathlib import Path
import re
import time
from typing import Any, Mapping, Sequence
from aios_single_host_authority import (
DEFAULT_MACHINE_SIGNER_ID,
DEFAULT_OWNER_SIGNER_ID,
MACHINE_NAMESPACE,
OWNER_IDENTITY,
OWNER_NAMESPACE,
LocalFileSigner,
RegistryOwnerApprovalVerifier,
SignerRegistry,
SingleHostAuthorityError,
canonical_json_bytes,
sha256_json,
verify_sshsig,
)
OPERATIONAL_POLICY_SCHEMA = "aios.single-host-operational-policy.v1"
OPERATIONAL_DELEGATION_SCHEMA = "aios.owner-operational-delegation.v1"
RECOVERY_DECISION_SCHEMA = "aios.expired-task-recovery-decision.v1"
WORKER_BINDING_SCHEMA = "aios.worker-runtime-binding.v1"
WORKER_PROOF_SCHEMA = "aios.worker-runtime-proof.v1"
REVOCATION_SCHEMA = "aios.operational-revocations.v1"
ALLOWED_OPERATIONAL_CAPABILITIES = frozenset({
"task.recovery",
"worker.runtime.bind",
"worker.runtime.proof",
"task.claim.guard",
})
ALLOWED_EXPECTED_STATE = "EXPIRED"
HEX40 = re.compile(r"^[0-9a-f]{40}$")
HEX64 = re.compile(r"^[0-9a-f]{64}$")
SAFE_ID = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:@/+:-]{2,511}$")
CAP = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{1,127}$")
class OperationalAuthorityError(RuntimeError):
def __init__(self, code: str):
super().__init__(code)
self.code = code
def fail(code: str) -> None:
raise OperationalAuthorityError(code)
def _exact_fields(value: Mapping[str, Any], fields: set[str], code: str) -> None:
if not isinstance(value, Mapping) or set(value) != fields:
fail(code)
def _safe_id(value: str, code: str) -> str:
if not isinstance(value, str) or not SAFE_ID.fullmatch(value):
fail(code)
return value
def _hex(value: str, n: int, code: str) -> str:
regex = HEX40 if n == 40 else HEX64
if not isinstance(value, str) or not regex.fullmatch(value):
fail(code)
return value
def _caps(value: Sequence[str], code: str) -> tuple[str, ...]:
if not isinstance(value, (list, tuple)) or not value:
fail(code)
out = tuple(sorted(set(value)))
if len(out) != len(value) or any(not isinstance(x, str) or not CAP.fullmatch(x) for x in out):
fail(code)
return out
def task_identity_sha256(task: Mapping[str, Any]) -> str:
"""Digest only immutable recovery/claim identity fields.
Revision, state, attempt and deadline are intentionally excluded.
"""
fields = (
"task_id", "repository", "target_branch", "base_oid", "generation",
"execution_plan_sha256", "external_job_identity",
)
try:
value = {name: task[name] for name in fields}
except KeyError as exc:
raise OperationalAuthorityError("TASK_IDENTITY_FIELD_MISSING") from exc
_safe_id(str(value["task_id"]), "TASK_ID_INVALID")
_safe_id(str(value["repository"]), "REPOSITORY_INVALID")
_safe_id(str(value["target_branch"]), "TARGET_BRANCH_INVALID")
_hex(str(value["base_oid"]), 40, "BASE_OID_INVALID")
if type(value["generation"]) is not int or value["generation"] < 1:
fail("GENERATION_INVALID")
_hex(str(value["execution_plan_sha256"]), 64, "EXECUTION_PLAN_DIGEST_INVALID")
_safe_id(str(value["external_job_identity"]), "EXTERNAL_JOB_IDENTITY_INVALID")
return hashlib.sha256(canonical_json_bytes(value)).hexdigest()
@dataclass(frozen=True, slots=True)
class OperationalPolicy:
repository: str
task_id: str
task_identity_sha256: str
target_branch: str
base_oid: str
generation: int
execution_plan_sha256: str
external_job_identity: str
allowed_capabilities: tuple[str, ...]
allowed_worker_ids: tuple[str, ...]
maximum_delegation_ttl_seconds: int
maximum_recovery_decision_ttl_seconds: int
maximum_binding_ttl_seconds: int
maximum_proof_ttl_seconds: int
maximum_heartbeat_age_seconds: int
maximum_task_deadline_extension_seconds: int
schema: str = OPERATIONAL_POLICY_SCHEMA
def validate(self) -> None:
if self.schema != OPERATIONAL_POLICY_SCHEMA:
fail("OPERATIONAL_POLICY_SCHEMA_INVALID")
_safe_id(self.repository, "OPERATIONAL_POLICY_REPOSITORY_INVALID")
_safe_id(self.task_id, "OPERATIONAL_POLICY_TASK_ID_INVALID")
_hex(self.task_identity_sha256, 64, "OPERATIONAL_POLICY_TASK_IDENTITY_INVALID")
_safe_id(self.target_branch, "OPERATIONAL_POLICY_BRANCH_INVALID")
_hex(self.base_oid, 40, "OPERATIONAL_POLICY_BASE_OID_INVALID")
if type(self.generation) is not int or self.generation < 1:
fail("OPERATIONAL_POLICY_GENERATION_INVALID")
_hex(self.execution_plan_sha256, 64, "OPERATIONAL_POLICY_PLAN_INVALID")
_safe_id(self.external_job_identity, "OPERATIONAL_POLICY_JOB_INVALID")
caps = _caps(self.allowed_capabilities, "OPERATIONAL_POLICY_CAPABILITIES_INVALID")
if not set(caps).issubset(ALLOWED_OPERATIONAL_CAPABILITIES):
fail("OPERATIONAL_POLICY_CAPABILITY_WIDENING")
if not self.allowed_worker_ids or any(not SAFE_ID.fullmatch(x) for x in self.allowed_worker_ids):
fail("OPERATIONAL_POLICY_WORKERS_INVALID")
bounds = (
(self.maximum_delegation_ttl_seconds, 300, 7 * 24 * 3600),
(self.maximum_recovery_decision_ttl_seconds, 30, 900),
(self.maximum_binding_ttl_seconds, 60, 24 * 3600),
(self.maximum_proof_ttl_seconds, 15, 900),
(self.maximum_heartbeat_age_seconds, 5, 300),
(self.maximum_task_deadline_extension_seconds, 300, 24 * 3600),
)
if any(type(v) is not int or not lo <= v <= hi for v, lo, hi in bounds):
fail("OPERATIONAL_POLICY_TTL_INVALID")
@property
def digest(self) -> str:
self.validate()
return sha256_json(asdict(self))
@classmethod
def from_mapping(cls, value: Mapping[str, Any]) -> "OperationalPolicy":
fields = set(cls.__dataclass_fields__)
_exact_fields(value, fields, "OPERATIONAL_POLICY_FIELDS_INVALID")
try:
policy = cls(
repository=value["repository"],
task_id=value["task_id"],
task_identity_sha256=value["task_identity_sha256"],
target_branch=value["target_branch"],
base_oid=value["base_oid"],
generation=value["generation"],
execution_plan_sha256=value["execution_plan_sha256"],
external_job_identity=value["external_job_identity"],
allowed_capabilities=tuple(value["allowed_capabilities"]),
allowed_worker_ids=tuple(value["allowed_worker_ids"]),
maximum_delegation_ttl_seconds=value["maximum_delegation_ttl_seconds"],
maximum_recovery_decision_ttl_seconds=value["maximum_recovery_decision_ttl_seconds"],
maximum_binding_ttl_seconds=value["maximum_binding_ttl_seconds"],
maximum_proof_ttl_seconds=value["maximum_proof_ttl_seconds"],
maximum_heartbeat_age_seconds=value["maximum_heartbeat_age_seconds"],
maximum_task_deadline_extension_seconds=value["maximum_task_deadline_extension_seconds"],
schema=value["schema"],
)
except Exception as exc:
raise OperationalAuthorityError("OPERATIONAL_POLICY_FIELDS_INVALID") from exc
policy.validate()
return policy
def build_exact_policy(
task: Mapping[str, Any],
*,
worker_id: str,
allowed_capabilities: Sequence[str] = tuple(sorted(ALLOWED_OPERATIONAL_CAPABILITIES)),
) -> OperationalPolicy:
identity = task_identity_sha256(task)
policy = OperationalPolicy(
repository=str(task["repository"]),
task_id=str(task["task_id"]),
task_identity_sha256=identity,
target_branch=str(task["target_branch"]),
base_oid=str(task["base_oid"]),
generation=int(task["generation"]),
execution_plan_sha256=str(task["execution_plan_sha256"]),
external_job_identity=str(task["external_job_identity"]),
allowed_capabilities=tuple(sorted(allowed_capabilities)),
allowed_worker_ids=(worker_id,),
maximum_delegation_ttl_seconds=24 * 3600,
maximum_recovery_decision_ttl_seconds=300,
maximum_binding_ttl_seconds=4 * 3600,
maximum_proof_ttl_seconds=300,
maximum_heartbeat_age_seconds=30,
maximum_task_deadline_extension_seconds=4 * 3600,
)
policy.validate()
return policy
def _signed_payload_dict(payload: Mapping[str, Any], *, key_id: str, namespace: str) -> dict[str, Any]:
value = dict(payload)
value["signature_namespace"] = namespace
value["signature_key_id"] = key_id
value["canonical_payload_digest"] = sha256_json(value)
return value
def _verify_digest_field(payload: Mapping[str, Any]) -> bool:
if not isinstance(payload, Mapping) or "canonical_payload_digest" not in payload:
return False
value = dict(payload)
supplied = value.pop("canonical_payload_digest", None)
return isinstance(supplied, str) and supplied == sha256_json(value)
@dataclass(frozen=True, slots=True)
class SignedOperationalArtifact:
payload: dict[str, Any]
signature: str
key_id: str
namespace: str
@property
def canonical_payload(self) -> bytes:
return canonical_json_bytes(self.payload)
@property
def payload_sha256(self) -> str:
return hashlib.sha256(self.canonical_payload).hexdigest()
def to_dict(self) -> dict[str, Any]:
return {
"payload": dict(self.payload),
"signature": self.signature,
"key_id": self.key_id,
"namespace": self.namespace,
}
class OperationalOwnerAuthority:
def __init__(self, key_path: Path, policy: OperationalPolicy, *, require_root_owner: bool = True, clock: Any = time.time):
policy.validate()
self.policy = policy
self.signer = LocalFileSigner(
Path(key_path),
signer_id=DEFAULT_OWNER_SIGNER_ID,
namespace=OWNER_NAMESPACE,
require_root_owner=require_root_owner,
)
self.clock = clock
@property
def fingerprint(self) -> str:
return self.signer.fingerprint
def _sign(self, payload: Mapping[str, Any]) -> SignedOperationalArtifact:
full = _signed_payload_dict(payload, key_id=self.fingerprint, namespace=OWNER_NAMESPACE)
art = self.signer._sign_canonical(full)
return SignedOperationalArtifact(dict(art.payload), art.signature, art.key_id, art.namespace)
def issue_operational_delegation(self, *, delegation_id: str, nonce: str, ttl_seconds: int) -> SignedOperationalArtifact:
now = int(self.clock())
p = self.policy
if not SAFE_ID.fullmatch(delegation_id) or not SAFE_ID.fullmatch(nonce):
fail("OPERATIONAL_DELEGATION_ID_INVALID")
if type(ttl_seconds) is not int or not 60 <= ttl_seconds <= p.maximum_delegation_ttl_seconds:
fail("OPERATIONAL_DELEGATION_TTL_INVALID")
payload = {
"schema": OPERATIONAL_DELEGATION_SCHEMA,
"delegation_id": delegation_id,
"nonce": nonce,
"owner_identity": OWNER_IDENTITY,
"repository": p.repository,
"task_id": p.task_id,
"task_identity_sha256": p.task_identity_sha256,
"target_branch": p.target_branch,
"base_oid": p.base_oid,
"generation": p.generation,
"execution_plan_sha256": p.execution_plan_sha256,
"external_job_identity": p.external_job_identity,
"allowed_capabilities": list(p.allowed_capabilities),
"allowed_worker_ids": list(p.allowed_worker_ids),
"policy_digest": p.digest,
"issued_at": now,
"not_before": max(0, now - 5),
"expires_at": now + ttl_seconds,
"revocation_epoch": 0,
"status": "ACTIVE",
}
return self._sign(payload)
def issue_recovery_decision(
self,
delegation: Mapping[str, Any],
*,
decision_id: str,
nonce: str,
expected_revision: int,
expected_attempt: int,
original_deadline: int,
new_deadline: int,
ttl_seconds: int,
) -> SignedOperationalArtifact:
now = int(self.clock())
verify_delegation_scope(delegation, self.policy, now=now)
if "task.recovery" not in delegation["allowed_capabilities"]:
fail("RECOVERY_CAPABILITY_NOT_DELEGATED")
if not SAFE_ID.fullmatch(decision_id) or not SAFE_ID.fullmatch(nonce):
fail("RECOVERY_DECISION_ID_INVALID")
if type(expected_revision) is not int or expected_revision < 0 or type(expected_attempt) is not int or expected_attempt < 0:
fail("RECOVERY_CAS_INVALID")
if type(original_deadline) is not int or type(new_deadline) is not int:
fail("RECOVERY_DEADLINE_INVALID")
if new_deadline <= now or new_deadline <= original_deadline:
fail("RECOVERY_DEADLINE_INVALID")
if new_deadline - now > self.policy.maximum_task_deadline_extension_seconds:
fail("RECOVERY_DEADLINE_WIDENING")
if type(ttl_seconds) is not int or not 15 <= ttl_seconds <= self.policy.maximum_recovery_decision_ttl_seconds:
fail("RECOVERY_DECISION_TTL_INVALID")
payload = {
"schema": RECOVERY_DECISION_SCHEMA,
"decision_id": decision_id,
"nonce": nonce,
"parent_delegation_id": delegation["delegation_id"],
"parent_delegation_digest": hashlib.sha256(canonical_json_bytes(dict(delegation))).hexdigest(),
"task_id": self.policy.task_id,
"task_identity_sha256": self.policy.task_identity_sha256,
"expected_state": ALLOWED_EXPECTED_STATE,
"expected_revision": expected_revision,
"expected_attempt": expected_attempt,
"original_deadline": original_deadline,
"new_deadline": new_deadline,
"repository": self.policy.repository,
"target_branch": self.policy.target_branch,
"base_oid": self.policy.base_oid,
"generation": self.policy.generation,
"execution_plan_sha256": self.policy.execution_plan_sha256,
"external_job_identity": self.policy.external_job_identity,
"policy_digest": self.policy.digest,
"one_time_use": True,
"issued_at": now,
"not_before": max(0, now - 5),
"expires_at": now + ttl_seconds,
}
return self._sign(payload)
class OperationalMachineAuthority:
def __init__(self, key_path: Path, policy: OperationalPolicy, *, require_root_owner: bool = True, clock: Any = time.time):
policy.validate()
self.policy = policy
self.signer = LocalFileSigner(
Path(key_path),
signer_id=DEFAULT_MACHINE_SIGNER_ID,
namespace=MACHINE_NAMESPACE,
require_root_owner=require_root_owner,
)
self.clock = clock
@property
def fingerprint(self) -> str:
return self.signer.fingerprint
def _sign(self, payload: Mapping[str, Any]) -> SignedOperationalArtifact:
full = _signed_payload_dict(payload, key_id=self.fingerprint, namespace=MACHINE_NAMESPACE)
art = self.signer._sign_canonical(full)
return SignedOperationalArtifact(dict(art.payload), art.signature, art.key_id, art.namespace)
def issue_runtime_binding(
self,
delegation: Mapping[str, Any],
*,
binding_id: str,
nonce: str,
worker_id: str,
runtime_id: str,
runtime_sha256: str,
task_contract_version: str,
supported_contract_versions: Sequence[str],
required_capabilities: Sequence[str],
worker_capabilities: Sequence[str],
max_concurrency: int,
ttl_seconds: int,
) -> SignedOperationalArtifact:
now = int(self.clock())
p = self.policy
verify_delegation_scope(delegation, p, now=now)
if "worker.runtime.bind" not in delegation["allowed_capabilities"]:
fail("RUNTIME_BIND_CAPABILITY_NOT_DELEGATED")
if worker_id not in p.allowed_worker_ids:
fail("WORKER_ID_NOT_DELEGATED")
for val, code in [(binding_id, "BINDING_ID_INVALID"), (nonce, "BINDING_NONCE_INVALID"), (worker_id, "WORKER_ID_INVALID"), (runtime_id, "RUNTIME_ID_INVALID"), (task_contract_version, "CONTRACT_VERSION_INVALID")]:
_safe_id(val, code)
_hex(runtime_sha256, 64, "RUNTIME_SHA256_INVALID")
supported = tuple(sorted(set(supported_contract_versions)))
if task_contract_version not in supported or not supported or any(not SAFE_ID.fullmatch(x) for x in supported):
fail("CONTRACT_INCOMPATIBLE")
required = _caps(required_capabilities, "REQUIRED_CAPABILITIES_INVALID")
worker_caps = _caps(worker_capabilities, "WORKER_CAPABILITIES_INVALID")
if not set(required).issubset(set(worker_caps)):
fail("CAPABILITY_MISMATCH")
if type(max_concurrency) is not int or not 1 <= max_concurrency <= 64:
fail("MAX_CONCURRENCY_INVALID")
if type(ttl_seconds) is not int or not 30 <= ttl_seconds <= p.maximum_binding_ttl_seconds:
fail("BINDING_TTL_INVALID")
if now + ttl_seconds > int(delegation["expires_at"]):
fail("BINDING_EXCEEDS_PARENT")
payload = {
"schema": WORKER_BINDING_SCHEMA,
"binding_id": binding_id,
"nonce": nonce,
"parent_delegation_id": delegation["delegation_id"],
"parent_delegation_digest": hashlib.sha256(canonical_json_bytes(dict(delegation))).hexdigest(),
"repository": p.repository,
"task_id": p.task_id,
"task_identity_sha256": p.task_identity_sha256,
"target_branch": p.target_branch,
"base_oid": p.base_oid,
"generation": p.generation,
"execution_plan_sha256": p.execution_plan_sha256,
"external_job_identity": p.external_job_identity,
"worker_id": worker_id,
"runtime_id": runtime_id,
"runtime_sha256": runtime_sha256,
"task_contract_version": task_contract_version,
"supported_contract_versions": list(supported),
"required_capabilities": list(required),
"worker_capabilities": list(worker_caps),
"max_concurrency": max_concurrency,
"policy_digest": p.digest,
"issued_at": now,
"not_before": max(0, now - 5),
"expires_at": now + ttl_seconds,
}
return self._sign(payload)
def issue_runtime_proof(
self,
delegation: Mapping[str, Any],
binding: Mapping[str, Any],
*,
proof_id: str,
nonce: str,
heartbeat_at: int,
adapter_readiness: bool,
resource_readiness: bool,
active_claim_count: int,
ttl_seconds: int,
) -> SignedOperationalArtifact:
now = int(self.clock())
p = self.policy
verify_delegation_scope(delegation, p, now=now)
verify_binding_scope(binding, delegation, p, now=now)
if "worker.runtime.proof" not in delegation["allowed_capabilities"]:
fail("RUNTIME_PROOF_CAPABILITY_NOT_DELEGATED")
_safe_id(proof_id, "PROOF_ID_INVALID")
_safe_id(nonce, "PROOF_NONCE_INVALID")
if type(heartbeat_at) is not int or heartbeat_at > now + 5 or now - heartbeat_at > p.maximum_heartbeat_age_seconds:
fail("HEARTBEAT_STALE")
if adapter_readiness is not True or resource_readiness is not True:
fail("RUNTIME_NOT_READY")
if type(active_claim_count) is not int or active_claim_count < 0 or active_claim_count >= int(binding["max_concurrency"]):
fail("MAX_CONCURRENCY_REACHED")
if type(ttl_seconds) is not int or not 10 <= ttl_seconds <= p.maximum_proof_ttl_seconds:
fail("PROOF_TTL_INVALID")
if now + ttl_seconds > min(int(binding["expires_at"]), int(delegation["expires_at"])):
fail("PROOF_EXCEEDS_PARENT")
payload = {
"schema": WORKER_PROOF_SCHEMA,
"proof_id": proof_id,
"nonce": nonce,
"parent_delegation_id": delegation["delegation_id"],
"parent_delegation_digest": hashlib.sha256(canonical_json_bytes(dict(delegation))).hexdigest(),
"binding_id": binding["binding_id"],
"binding_digest": hashlib.sha256(canonical_json_bytes(dict(binding))).hexdigest(),
"repository": p.repository,
"task_id": p.task_id,
"task_identity_sha256": p.task_identity_sha256,
"generation": p.generation,
"execution_plan_sha256": p.execution_plan_sha256,
"worker_id": binding["worker_id"],
"runtime_id": binding["runtime_id"],
"runtime_sha256": binding["runtime_sha256"],
"heartbeat_at": heartbeat_at,
"adapter_readiness": True,
"resource_readiness": True,
"active_claim_count": active_claim_count,
"max_concurrency": binding["max_concurrency"],
"policy_digest": p.digest,
"issued_at": now,
"expires_at": now + ttl_seconds,
}
return self._sign(payload)
DELEGATION_FIELDS = {
"schema","delegation_id","nonce","owner_identity","repository","task_id",
"task_identity_sha256","target_branch","base_oid","generation","execution_plan_sha256",
"external_job_identity","allowed_capabilities","allowed_worker_ids","policy_digest",
"issued_at","not_before","expires_at","revocation_epoch","status",
"signature_namespace","signature_key_id","canonical_payload_digest",
}
BINDING_FIELDS = {
"schema","binding_id","nonce","parent_delegation_id","parent_delegation_digest",
"repository","task_id","task_identity_sha256","target_branch","base_oid","generation",
"execution_plan_sha256","external_job_identity","worker_id","runtime_id","runtime_sha256",
"task_contract_version","supported_contract_versions","required_capabilities","worker_capabilities",
"max_concurrency","policy_digest","issued_at","not_before","expires_at",
"signature_namespace","signature_key_id","canonical_payload_digest",
}
PROOF_FIELDS = {
"schema","proof_id","nonce","parent_delegation_id","parent_delegation_digest",
"binding_id","binding_digest","repository","task_id","task_identity_sha256","generation",
"execution_plan_sha256","worker_id","runtime_id","runtime_sha256","heartbeat_at",
"adapter_readiness","resource_readiness","active_claim_count","max_concurrency",
"policy_digest","issued_at","expires_at","signature_namespace","signature_key_id",
"canonical_payload_digest",
}
RECOVERY_FIELDS = {
"schema","decision_id","nonce","parent_delegation_id","parent_delegation_digest",
"task_id","task_identity_sha256","expected_state","expected_revision","expected_attempt",
"original_deadline","new_deadline","repository","target_branch","base_oid","generation",
"execution_plan_sha256","external_job_identity","policy_digest","one_time_use",
"issued_at","not_before","expires_at","signature_namespace","signature_key_id",
"canonical_payload_digest",
}
def verify_delegation_scope(value: Mapping[str, Any], policy: OperationalPolicy, *, now: int | None = None) -> None:
policy.validate()
_exact_fields(value, DELEGATION_FIELDS, "OPERATIONAL_DELEGATION_FIELDS_INVALID")
now = int(time.time()) if now is None else int(now)
if (
value["schema"] != OPERATIONAL_DELEGATION_SCHEMA
or value["status"] != "ACTIVE"
or value["revocation_epoch"] != 0
or value["owner_identity"] != OWNER_IDENTITY
or value["repository"] != policy.repository
or value["task_id"] != policy.task_id
or value["task_identity_sha256"] != policy.task_identity_sha256
or value["target_branch"] != policy.target_branch
or value["base_oid"] != policy.base_oid
or value["generation"] != policy.generation
or value["execution_plan_sha256"] != policy.execution_plan_sha256
or value["external_job_identity"] != policy.external_job_identity
or value["policy_digest"] != policy.digest
or not set(value["allowed_capabilities"]).issubset(set(policy.allowed_capabilities))
or not set(value["allowed_worker_ids"]).issubset(set(policy.allowed_worker_ids))
or value["signature_namespace"] != OWNER_NAMESPACE
or not _verify_digest_field(value)
or not int(value["not_before"]) <= now < int(value["expires_at"])
):
fail("OPERATIONAL_DELEGATION_SCOPE_INVALID")
def verify_binding_scope(binding: Mapping[str, Any], delegation: Mapping[str, Any], policy: OperationalPolicy, *, now: int | None = None) -> None:
_exact_fields(binding, BINDING_FIELDS, "WORKER_BINDING_FIELDS_INVALID")
now = int(time.time()) if now is None else int(now)
expected_parent = hashlib.sha256(canonical_json_bytes(dict(delegation))).hexdigest()
if (
binding["schema"] != WORKER_BINDING_SCHEMA
or binding["parent_delegation_id"] != delegation["delegation_id"]
or binding["parent_delegation_digest"] != expected_parent
or binding["repository"] != policy.repository
or binding["task_id"] != policy.task_id
or binding["task_identity_sha256"] != policy.task_identity_sha256
or binding["target_branch"] != policy.target_branch
or binding["base_oid"] != policy.base_oid
or binding["generation"] != policy.generation
or binding["execution_plan_sha256"] != policy.execution_plan_sha256
or binding["external_job_identity"] != policy.external_job_identity
or binding["worker_id"] not in policy.allowed_worker_ids
or binding["policy_digest"] != policy.digest
or binding["signature_namespace"] != MACHINE_NAMESPACE
or not _verify_digest_field(binding)
or not int(binding["not_before"]) <= now < int(binding["expires_at"])
):
fail("WORKER_BINDING_SCOPE_INVALID")
required = set(binding["required_capabilities"])
worker = set(binding["worker_capabilities"])
if not required.issubset(worker) or binding["task_contract_version"] not in binding["supported_contract_versions"]:
fail("WORKER_BINDING_COMPATIBILITY_INVALID")
def verify_proof_scope(proof: Mapping[str, Any], binding: Mapping[str, Any], delegation: Mapping[str, Any], policy: OperationalPolicy, *, now: int | None = None) -> None:
_exact_fields(proof, PROOF_FIELDS, "WORKER_PROOF_FIELDS_INVALID")
now = int(time.time()) if now is None else int(now)
if (
proof["schema"] != WORKER_PROOF_SCHEMA
or proof["parent_delegation_id"] != delegation["delegation_id"]
or proof["parent_delegation_digest"] != hashlib.sha256(canonical_json_bytes(dict(delegation))).hexdigest()
or proof["binding_id"] != binding["binding_id"]
or proof["binding_digest"] != hashlib.sha256(canonical_json_bytes(dict(binding))).hexdigest()
or proof["repository"] != policy.repository
or proof["task_id"] != policy.task_id
or proof["task_identity_sha256"] != policy.task_identity_sha256
or proof["generation"] != policy.generation
or proof["execution_plan_sha256"] != policy.execution_plan_sha256
or proof["worker_id"] != binding["worker_id"]
or proof["runtime_id"] != binding["runtime_id"]
or proof["runtime_sha256"] != binding["runtime_sha256"]
or proof["max_concurrency"] != binding["max_concurrency"]
or proof["policy_digest"] != policy.digest
or proof["signature_namespace"] != MACHINE_NAMESPACE
or proof["adapter_readiness"] is not True
or proof["resource_readiness"] is not True
or not _verify_digest_field(proof)
or not now < int(proof["expires_at"])
or now - int(proof["heartbeat_at"]) > policy.maximum_heartbeat_age_seconds
or int(proof["active_claim_count"]) >= int(proof["max_concurrency"])
):
fail("WORKER_PROOF_SCOPE_INVALID")
def verify_recovery_scope(decision: Mapping[str, Any], delegation: Mapping[str, Any], policy: OperationalPolicy, *, now: int | None = None) -> None:
_exact_fields(decision, RECOVERY_FIELDS, "RECOVERY_DECISION_FIELDS_INVALID")
now = int(time.time()) if now is None else int(now)
if (
decision["schema"] != RECOVERY_DECISION_SCHEMA
or decision["parent_delegation_id"] != delegation["delegation_id"]
or decision["parent_delegation_digest"] != hashlib.sha256(canonical_json_bytes(dict(delegation))).hexdigest()
or decision["task_id"] != policy.task_id
or decision["task_identity_sha256"] != policy.task_identity_sha256
or decision["expected_state"] != "EXPIRED"
or decision["repository"] != policy.repository
or decision["target_branch"] != policy.target_branch
or decision["base_oid"] != policy.base_oid
or decision["generation"] != policy.generation
or decision["execution_plan_sha256"] != policy.execution_plan_sha256
or decision["external_job_identity"] != policy.external_job_identity
or decision["policy_digest"] != policy.digest
or decision["one_time_use"] is not True
or decision["signature_namespace"] != OWNER_NAMESPACE
or not _verify_digest_field(decision)
or not int(decision["not_before"]) <= now < int(decision["expires_at"])
or int(decision["new_deadline"]) <= now
):
fail("RECOVERY_DECISION_SCOPE_INVALID")
class OperationalVerifier:
"""Verify signatures through the public root-owned SignerRegistry."""
def __init__(self, registry_path: Path, *, require_root_owner: bool = True):
self.registry_path = Path(registry_path)
self.require_root_owner = require_root_owner
def _verify(self, artifact: SignedOperationalArtifact | Mapping[str, Any], *, role: str, signer_id: str, namespace: str) -> dict[str, Any]:
if isinstance(artifact, SignedOperationalArtifact):
payload, signature, key_id, artifact_namespace = artifact.payload, artifact.signature, artifact.key_id, artifact.namespace
else:
_exact_fields(artifact, {"payload","signature","key_id","namespace"}, "SIGNED_ARTIFACT_FIELDS_INVALID")
payload, signature, key_id, artifact_namespace = artifact["payload"], artifact["signature"], artifact["key_id"], artifact["namespace"]
if artifact_namespace != namespace:
fail("SIGNED_ARTIFACT_NAMESPACE_INVALID")
registry = SignerRegistry(self.registry_path, require_root_owner=self.require_root_owner)
rec = registry.require(signer_id, role=role)
if rec.fingerprint != key_id:
fail("SIGNED_ARTIFACT_KEY_ID_INVALID")
if role == "MACHINE_OPERATIONAL":
owner_rec = registry.require(DEFAULT_OWNER_SIGNER_ID, role="OWNER_AUTHORITY")
if rec.parent_signer_id != owner_rec.signer_id or rec.parent_fingerprint != owner_rec.fingerprint:
fail("MACHINE_PARENT_BINDING_INVALID")
# RegistryOwnerApprovalVerifier handles Owner namespace; generic machine uses registry public key.
if role == "OWNER_AUTHORITY":
verifier = RegistryOwnerApprovalVerifier(self.registry_path, require_root_owner=self.require_root_owner, signer_id=signer_id)
ok = verifier.verify(namespace=namespace, payload=canonical_json_bytes(payload), signature=signature, key_id=key_id)
else:
from aios_single_host_authority import _load_public_key_line
pub = _load_public_key_line(rec.public_key)
ok = verify_sshsig(pub, canonical_json_bytes(payload), signature, namespace=namespace, expected_key_id=key_id)
if not ok:
fail("SIGNED_ARTIFACT_SIGNATURE_INVALID")
return dict(payload)
def owner(self, artifact: SignedOperationalArtifact | Mapping[str, Any]) -> dict[str, Any]:
return self._verify(artifact, role="OWNER_AUTHORITY", signer_id=DEFAULT_OWNER_SIGNER_ID, namespace=OWNER_NAMESPACE)
def machine(self, artifact: SignedOperationalArtifact | Mapping[str, Any]) -> dict[str, Any]:
return self._verify(artifact, role="MACHINE_OPERATIONAL", signer_id=DEFAULT_MACHINE_SIGNER_ID, namespace=MACHINE_NAMESPACE)
@@ -0,0 +1,410 @@
"""Unix-domain fixed-schema service for operational migration authority."""
from __future__ import annotations
import argparse
import hashlib
import json
import os
import re
from pathlib import Path
import socket
import sqlite3
import struct
import stat
import sys
import time
from typing import Any, Mapping
from aios_single_host_authority import (
DEFAULT_REGISTRY_PATH,
OWNER_NAMESPACE,
MACHINE_NAMESPACE,
SingleHostAuthorityError,
)
from aios_single_host_operational_authority import (
OperationalAuthorityError,
OperationalMachineAuthority,
OperationalOwnerAuthority,
OperationalPolicy,
OperationalVerifier,
SignedOperationalArtifact,
verify_binding_scope,
verify_delegation_scope,
)
DEFAULT_POLICY = Path("/etc/aios/single-host-operational-policy.json")
DEFAULT_OWNER_KEY = Path("/var/lib/aios-owner-authority/owner-authority.key")
DEFAULT_MACHINE_KEY = Path("/var/lib/aios-machine-signer/machine-operational.key")
DEFAULT_OWNER_SOCKET = Path("/run/aios/owner-authority.sock")
DEFAULT_MACHINE_SOCKET = Path("/run/aios/machine-signer.sock")
DEFAULT_LEDGER = Path("/var/lib/aios-operational-authority/issuance-ledger.sqlite3")
MAX_REQUEST = 128 * 1024
READINESS_SCHEMA = "aios.signer-readiness-challenge.v1"
class OperationalServiceError(RuntimeError):
def __init__(self, code: str):
super().__init__(code)
self.code = code
def fail(code: str) -> None:
raise OperationalServiceError(code)
def _read_policy(path: Path, *, require_root_owner: bool = True) -> OperationalPolicy:
path=Path(path)
if not path.is_absolute():
fail("OPERATIONAL_POLICY_PATH_INVALID")
flags=os.O_RDONLY
if hasattr(os,"O_NOFOLLOW"):
flags |= os.O_NOFOLLOW
try:
fd=os.open(path,flags)
except OSError as exc:
raise OperationalServiceError("OPERATIONAL_POLICY_UNAVAILABLE") from exc
try:
st=os.fstat(fd)
if not stat.S_ISREG(st.st_mode):
fail("OPERATIONAL_POLICY_NOT_REGULAR")
mode=stat.S_IMODE(st.st_mode)
if mode & 0o022:
fail("OPERATIONAL_POLICY_WRITABLE_BY_GROUP_OR_OTHER")
if require_root_owner and st.st_uid != 0:
fail("OPERATIONAL_POLICY_NOT_ROOT_OWNED")
raw=bytearray()
while len(raw) <= MAX_REQUEST:
chunk=os.read(fd,min(65536,MAX_REQUEST+1-len(raw)))
if not chunk: break
raw.extend(chunk)
if not raw or len(raw)>MAX_REQUEST:
fail("OPERATIONAL_POLICY_UNAVAILABLE")
finally:
os.close(fd)
try:
obj=json.loads(bytes(raw).decode("utf-8"))
except Exception as exc:
raise OperationalServiceError("OPERATIONAL_POLICY_INVALID") from exc
return OperationalPolicy.from_mapping(obj)
def _artifact(value: Mapping[str, Any]) -> SignedOperationalArtifact:
if not isinstance(value, Mapping) or set(value) != {"payload","signature","key_id","namespace"}:
fail("SIGNED_ARTIFACT_INVALID")
if not isinstance(value["payload"], Mapping):
fail("SIGNED_ARTIFACT_INVALID")
return SignedOperationalArtifact(
payload=dict(value["payload"]),
signature=str(value["signature"]),
key_id=str(value["key_id"]),
namespace=str(value["namespace"]),
)
def _peer_uid(conn: socket.socket) -> int:
if not hasattr(socket, "SO_PEERCRED"):
fail("PEER_CREDENTIALS_UNAVAILABLE")
pid, uid, gid = struct.unpack("3i", conn.getsockopt(socket.SOL_SOCKET, socket.SO_PEERCRED, struct.calcsize("3i")))
return uid
class DurableIssuanceLedger:
"""Root-owned exact-idempotent structured signing ledger."""
def __init__(self, path: Path):
self.path = Path(path)
self.path.parent.mkdir(parents=True, exist_ok=True)
if os.name == "posix": os.chmod(self.path.parent, 0o700)
if self.path.exists() and self.path.is_symlink():
fail("ISSUANCE_LEDGER_SYMLINK_FORBIDDEN")
conn = sqlite3.connect(self.path)
try:
conn.execute("PRAGMA journal_mode=DELETE")
conn.execute("""
CREATE TABLE IF NOT EXISTS issuance (
role TEXT NOT NULL,
operation TEXT NOT NULL,
nonce TEXT NOT NULL,
request_sha256 TEXT NOT NULL,
response_json TEXT NOT NULL,
created_at INTEGER NOT NULL,
PRIMARY KEY(role, operation, nonce)
)
""")
conn.commit()
finally:
conn.close()
os.chmod(self.path, 0o600)
def issue(self, *, role: str, operation: str, nonce: str, request: Mapping[str, Any], issuer: Any) -> dict[str, Any]:
request_sha = hashlib.sha256(
json.dumps(request, sort_keys=True, separators=(",",":"), ensure_ascii=False).encode("utf-8")
).hexdigest()
conn = sqlite3.connect(self.path, timeout=5, isolation_level=None)
try:
conn.execute("BEGIN IMMEDIATE")
row = conn.execute(
"SELECT request_sha256,response_json FROM issuance WHERE role=? AND operation=? AND nonce=?",
(role, operation, nonce),
).fetchone()
if row is not None:
if row[0] != request_sha:
conn.rollback()
fail("NONCE_REUSE_WITH_DIFFERENT_REQUEST")
result = json.loads(row[1])
conn.commit()
return result
result = issuer()
encoded = json.dumps(result, sort_keys=True, separators=(",",":"), ensure_ascii=False)
conn.execute(
"INSERT INTO issuance(role,operation,nonce,request_sha256,response_json,created_at) VALUES(?,?,?,?,?,?)",
(role, operation, nonce, request_sha, encoded, int(time.time())),
)
conn.commit()
return result
except sqlite3.IntegrityError:
conn.rollback()
fail("ISSUANCE_LEDGER_RACE")
finally:
conn.close()
class OperationalAuthorityService:
def __init__(
self,
*,
role: str,
policy_path: Path = DEFAULT_POLICY,
registry_path: Path = DEFAULT_REGISTRY_PATH,
owner_key_path: Path = DEFAULT_OWNER_KEY,
machine_key_path: Path = DEFAULT_MACHINE_KEY,
socket_path: Path | None = None,
ledger_path: Path = DEFAULT_LEDGER,
allowed_uids: tuple[int, ...] = (0,),
clock: Any = time.time,
require_root_owner: bool = True,
):
if role not in {"owner","machine"}:
fail("ROLE_INVALID")
self.role = role
self.policy_path = Path(policy_path)
self.registry_path = Path(registry_path)
self.owner_key_path = Path(owner_key_path)
self.machine_key_path = Path(machine_key_path)
self.socket_path = Path(socket_path or (DEFAULT_OWNER_SOCKET if role == "owner" else DEFAULT_MACHINE_SOCKET))
self.allowed_uids = tuple(allowed_uids)
self.ledger = DurableIssuanceLedger(ledger_path)
self.clock = clock
self.require_root_owner = require_root_owner
def _components(self):
policy = _read_policy(self.policy_path, require_root_owner=self.require_root_owner)
verifier = OperationalVerifier(self.registry_path, require_root_owner=self.require_root_owner)
if self.role == "owner":
signer = OperationalOwnerAuthority(self.owner_key_path, policy, require_root_owner=self.require_root_owner, clock=self.clock)
else:
signer = OperationalMachineAuthority(self.machine_key_path, policy, require_root_owner=self.require_root_owner, clock=self.clock)
return policy, verifier, signer
def handle(self, request: Mapping[str, Any], *, peer_uid: int) -> dict[str, Any]:
if peer_uid not in self.allowed_uids:
fail("PEER_UID_FORBIDDEN")
if not isinstance(request, Mapping) or set(request) != {"operation","payload"} or not isinstance(request["payload"], Mapping):
fail("REQUEST_INVALID")
op = request["operation"]
p = request["payload"]
policy, verifier, signer = self._components()
if op == "identity":
return {
"status":"PASS",
"role":self.role,
"policy_digest":policy.digest,
"key_id":signer.fingerprint,
"socket":str(self.socket_path),
"arbitrary_signing":False,
}
if op == "readiness_challenge":
if set(p) != {"challenge_id","challenge_sha256"}:
fail("REQUEST_FIELDS_INVALID")
challenge_id = str(p["challenge_id"])
challenge_sha256 = str(p["challenge_sha256"])
if not challenge_id or len(challenge_id) > 160:
fail("READINESS_CHALLENGE_ID_INVALID")
if not isinstance(challenge_sha256, str) or not re.fullmatch(r"[0-9a-f]{64}", challenge_sha256):
fail("READINESS_CHALLENGE_SHA256_INVALID")
namespace = OWNER_NAMESPACE if self.role == "owner" else MACHINE_NAMESPACE
def issue():
art = signer._sign({
"schema": READINESS_SCHEMA,
"role": self.role,
"challenge_id": challenge_id,
"challenge_sha256": challenge_sha256,
"policy_digest": policy.digest,
"issued_at": int(self.clock()),
})
return {"status":"PASS","artifact":art.to_dict()}
return self.ledger.issue(
role=self.role,
operation=op,
nonce=challenge_id,
request=p,
issuer=issue,
)
if self.role == "owner":
if op == "issue_operational_delegation":
if set(p) != {"delegation_id","nonce","ttl_seconds"}:
fail("REQUEST_FIELDS_INVALID")
def issue():
art = signer.issue_operational_delegation(
delegation_id=str(p["delegation_id"]),
nonce=str(p["nonce"]),
ttl_seconds=p["ttl_seconds"],
)
return {"status":"PASS","artifact":art.to_dict()}
return self.ledger.issue(role=self.role, operation=op, nonce=str(p["nonce"]), request=p, issuer=issue)
if op == "issue_expired_task_recovery":
if set(p) != {"delegation","decision_id","nonce","expected_revision","expected_attempt","original_deadline","new_deadline","ttl_seconds"}:
fail("REQUEST_FIELDS_INVALID")
parent_art = _artifact(p["delegation"])
parent = verifier.owner(parent_art)
verify_delegation_scope(parent, policy, now=int(self.clock()))
def issue():
art = signer.issue_recovery_decision(
parent,
decision_id=str(p["decision_id"]),
nonce=str(p["nonce"]),
expected_revision=p["expected_revision"],
expected_attempt=p["expected_attempt"],
original_deadline=p["original_deadline"],
new_deadline=p["new_deadline"],
ttl_seconds=p["ttl_seconds"],
)
return {"status":"PASS","artifact":art.to_dict()}
return self.ledger.issue(role=self.role, operation=op, nonce=str(p["nonce"]), request=p, issuer=issue)
fail("OWNER_OPERATION_FORBIDDEN")
if op == "issue_worker_runtime_binding":
if set(p) != {
"delegation","binding_id","nonce","worker_id","runtime_id","runtime_sha256",
"task_contract_version","supported_contract_versions","required_capabilities",
"worker_capabilities","max_concurrency","ttl_seconds"
}:
fail("REQUEST_FIELDS_INVALID")
parent_art = _artifact(p["delegation"])
parent = verifier.owner(parent_art)
verify_delegation_scope(parent, policy, now=int(self.clock()))
def issue():
art = signer.issue_runtime_binding(
parent,
binding_id=str(p["binding_id"]), nonce=str(p["nonce"]),
worker_id=str(p["worker_id"]), runtime_id=str(p["runtime_id"]),
runtime_sha256=str(p["runtime_sha256"]),
task_contract_version=str(p["task_contract_version"]),
supported_contract_versions=tuple(p["supported_contract_versions"]),
required_capabilities=tuple(p["required_capabilities"]),
worker_capabilities=tuple(p["worker_capabilities"]),
max_concurrency=p["max_concurrency"], ttl_seconds=p["ttl_seconds"],
)
return {"status":"PASS","artifact":art.to_dict()}
return self.ledger.issue(role=self.role, operation=op, nonce=str(p["nonce"]), request=p, issuer=issue)
if op == "issue_worker_runtime_proof":
if set(p) != {
"delegation","binding","proof_id","nonce","heartbeat_at","adapter_readiness",
"resource_readiness","active_claim_count","ttl_seconds"
}:
fail("REQUEST_FIELDS_INVALID")
parent_art = _artifact(p["delegation"])
binding_art = _artifact(p["binding"])
parent = verifier.owner(parent_art)
binding = verifier.machine(binding_art)
verify_delegation_scope(parent, policy, now=int(self.clock()))
verify_binding_scope(binding, parent, policy, now=int(self.clock()))
def issue():
art = signer.issue_runtime_proof(
parent, binding,
proof_id=str(p["proof_id"]), nonce=str(p["nonce"]),
heartbeat_at=p["heartbeat_at"],
adapter_readiness=p["adapter_readiness"],
resource_readiness=p["resource_readiness"],
active_claim_count=p["active_claim_count"],
ttl_seconds=p["ttl_seconds"],
)
return {"status":"PASS","artifact":art.to_dict()}
return self.ledger.issue(role=self.role, operation=op, nonce=str(p["nonce"]), request=p, issuer=issue)
fail("MACHINE_OPERATION_FORBIDDEN")
def serve_forever(self) -> None:
path = self.socket_path
path.parent.mkdir(parents=True, exist_ok=True)
if path.exists() or path.is_symlink():
if path.is_socket():
path.unlink()
else:
fail("SOCKET_PATH_OCCUPIED")
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
try:
sock.bind(str(path))
os.chmod(path, 0o600)
sock.listen(16)
while True:
conn, _ = sock.accept()
with conn:
try:
uid = _peer_uid(conn)
chunks = []
total = 0
while True:
data = conn.recv(65536)
if not data:
break
total += len(data)
if total > MAX_REQUEST:
fail("REQUEST_TOO_LARGE")
chunks.append(data)
if b"\n" in data:
break
raw = b"".join(chunks)
line = raw.split(b"\n",1)[0]
req = json.loads(line.decode("utf-8"))
result = self.handle(req, peer_uid=uid)
out = {"ok":True,"result":result}
except (OperationalServiceError, OperationalAuthorityError, SingleHostAuthorityError) as exc:
out = {"ok":False,"error":getattr(exc,"code",str(exc))}
except Exception:
out = {"ok":False,"error":"INTERNAL_FAIL_CLOSED"}
conn.sendall(json.dumps(out, sort_keys=True, separators=(",",":")).encode("utf-8")+b"\n")
finally:
sock.close()
try:
path.unlink()
except FileNotFoundError:
pass
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--role", choices=("owner","machine"), required=True)
ap.add_argument("--policy", default=str(DEFAULT_POLICY))
ap.add_argument("--registry", default=str(DEFAULT_REGISTRY_PATH))
ap.add_argument("--owner-key", default=str(DEFAULT_OWNER_KEY))
ap.add_argument("--machine-key", default=str(DEFAULT_MACHINE_KEY))
ap.add_argument("--socket")
ns = ap.parse_args()
service = OperationalAuthorityService(
role=ns.role,
policy_path=Path(ns.policy),
registry_path=Path(ns.registry),
owner_key_path=Path(ns.owner_key),
machine_key_path=Path(ns.machine_key),
socket_path=Path(ns.socket) if ns.socket else None,
)
service.serve_forever()
return 0
if __name__ == "__main__":
raise SystemExit(main())
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,87 @@
"""Bridge contract surface for Task Admission Bootstrap.
This module is intentionally transport-agnostic. The live Bridge may merge
these routes/operations into its existing authenticated/HTTPS surface, but the
authorization proof for the authorize operation is the verified delegation
epoch or Owner signature carried by the request itself.
No handler here can issue TaskPrincipal assertions or WriterLeases. Those remain
strictly downstream of a real admitted/claimed Agent Task.
"""
from __future__ import annotations
from typing import Any, Mapping
from aios_task_admission_bootstrap import TaskAdmissionBootstrapService
TASK_ADMISSION_ROUTES = {
"/v2/task-admission/envelope": "getTaskAdmissionEnvelope",
"/v2/task-admission/authorize": "submitTaskAdmissionAuthorization",
"/v2/task-admission/status": "getTaskAdmissionStatus",
}
TASK_ADMISSION_OPERATION_REQUIRED_FIELDS = {
"getTaskAdmissionEnvelope": (
"idempotency_key",
"task_id",
"repository",
"authority_base_oid",
"target_branch",
"generation",
"execution_digest",
"requested_capabilities",
"file_allowlist",
"risk",
),
"submitTaskAdmissionAuthorization": (
"envelope_id",
"authorization_kind",
),
"getTaskAdmissionStatus": ("envelope_id",),
}
TASK_ADMISSION_OPERATION_OPTIONAL_FIELDS = {
"getTaskAdmissionEnvelope": ("expires_in_seconds",),
"submitTaskAdmissionAuthorization": (
"delegation_epoch_id",
"owner_key_id",
"signature_base64",
),
"getTaskAdmissionStatus": (),
}
class TaskAdmissionBridgeContract:
def __init__(self, service: TaskAdmissionBootstrapService) -> None:
self.service = service
@staticmethod
def _validate_fields(operation: str, request: Mapping[str, Any]) -> None:
required = set(TASK_ADMISSION_OPERATION_REQUIRED_FIELDS[operation])
allowed = required | set(TASK_ADMISSION_OPERATION_OPTIONAL_FIELDS[operation])
if not required.issubset(request):
raise ValueError("TASK_ADMISSION_BRIDGE_REQUIRED_FIELDS_MISSING")
if set(request) - allowed:
raise ValueError("TASK_ADMISSION_BRIDGE_UNKNOWN_FIELDS")
def dispatch(self, operation: str, request: Mapping[str, Any]) -> dict[str, Any]:
if operation not in TASK_ADMISSION_OPERATION_REQUIRED_FIELDS:
raise ValueError("TASK_ADMISSION_BRIDGE_OPERATION_NOT_FOUND")
self._validate_fields(operation, request)
if operation == "getTaskAdmissionEnvelope":
return self.service.create_envelope(request)
if operation == "submitTaskAdmissionAuthorization":
return self.service.authorize_and_admit(request)
if operation == "getTaskAdmissionStatus":
return self.service.status(str(request["envelope_id"]))
raise ValueError("TASK_ADMISSION_BRIDGE_OPERATION_NOT_FOUND")
def handlers(self) -> dict[str, Any]:
return {
operation: (
lambda request, operation=operation: self.dispatch(operation, request)
)
for operation in TASK_ADMISSION_OPERATION_REQUIRED_FIELDS
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,394 @@
"""Append-only ClaimGuard binding for the superseded Single-Host child Task."""
from __future__ import annotations
from datetime import datetime, timezone
import hashlib
import json
import re
import sqlite3
from typing import Any, Callable, Mapping
from aios_task_lineage_supersession_initial_projection import (
PROJECTION_RECEIPT_SCHEMA,
PROJECTION_TABLE,
SOURCE_BUSINESS_EXECUTION_DIGEST,
)
SOURCE_TASK_ID = "TASK-SINGLEHOST-TRUSTROOT-P0-20260904"
CHILD_TASK_ID = SOURCE_TASK_ID + "-S1"
WORKER_ID = "p0-task-principal-v15-20260830"
REPOSITORY = "aios/aios"
TARGET_BRANCH = "agent/single-host-trust-root-p0-20260904"
GENERATION = 1
EXTERNAL_JOB_ID = "job-4e10f92a6a5742518a9f1d8745c845ab"
BINDING_SCHEMA = "aios.task-claim-guard-binding.v2"
RECEIPT_SCHEMA = "aios.task-claim-guard-binding-receipt.v2"
HEX64 = re.compile(r"^[0-9a-f]{64}$")
class ClaimGuardBindingError(RuntimeError):
def __init__(self, code: str, http_status: int = 409):
super().__init__(code)
self.code = code
self.http_status = int(http_status)
def _fail(code: str, status: int = 409) -> None:
raise ClaimGuardBindingError(code, status)
def canonical_json(value: Any) -> str:
return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"), allow_nan=False)
def _sha_text(value: str) -> str:
return hashlib.sha256(value.encode("utf-8")).hexdigest()
def _sha_json(value: Any) -> str:
return _sha_text(canonical_json(value))
def _now() -> str:
return datetime.now(timezone.utc).isoformat()
def _row_dict(row: Any) -> dict[str, Any]:
if row is None:
return {}
if hasattr(row, "keys"):
return {str(key): row[key] for key in row.keys()}
_fail("TASK_ROW_MAPPING_UNAVAILABLE", 500)
def _json(raw: Any, code: str) -> Any:
if not isinstance(raw, str):
_fail(code)
try:
return json.loads(raw)
except (TypeError, ValueError) as exc:
raise ClaimGuardBindingError(code) from exc
def _default_policy_factory(value: Mapping[str, Any]):
try:
from aios_single_host_operational_authority import OperationalPolicy
except ImportError as exc:
raise ClaimGuardBindingError("OPERATIONAL_POLICY_RUNTIME_UNAVAILABLE", 503) from exc
return OperationalPolicy.from_mapping(value)
def _policy_value(policy: Any, name: str) -> Any:
try:
return getattr(policy, name)
except AttributeError as exc:
raise ClaimGuardBindingError("OPERATIONAL_POLICY_FIELDS_INVALID") from exc
def _load_projection_evidence(connection, relation) -> dict[str, Any]:
try:
row = connection.execute(
f"SELECT * FROM {PROJECTION_TABLE} WHERE task_id=? AND source_task_id=? "
"AND supersession_ordinal=?",
(CHILD_TASK_ID, SOURCE_TASK_ID, 1),
).fetchone()
except sqlite3.OperationalError as exc:
raise ClaimGuardBindingError(
"FORMAL_CHILD_INITIAL_PROJECTION_SCHEMA_UNAVAILABLE", 503
) from exc
if row is None:
_fail("FORMAL_CHILD_INITIAL_PROJECTION_REQUIRED")
value = _row_dict(row)
receipt = _json(value.get("receipt_json"), "CHILD_PROJECTION_RECEIPT_INVALID")
canonical_digest = value.get("canonical_child_execution_digest")
source_digest = value.get("source_business_execution_digest")
exact = {
"task_id": CHILD_TASK_ID,
"source_task_id": SOURCE_TASK_ID,
"supersession_ordinal": 1,
"source_business_execution_digest": SOURCE_BUSINESS_EXECUTION_DIGEST,
"external_job_id": EXTERNAL_JOB_ID,
"repository": REPOSITORY,
"target_branch": TARGET_BRANCH,
"generation": GENERATION,
"owner_decision_sha256": relation["owner_decision_sha256"],
}
if any(value.get(key) != expected for key, expected in exact.items()):
_fail("CHILD_PROJECTION_LINEAGE_MISMATCH")
if (
not isinstance(canonical_digest, str)
or HEX64.fullmatch(canonical_digest) is None
or canonical_digest == SOURCE_BUSINESS_EXECUTION_DIGEST
or source_digest != SOURCE_BUSINESS_EXECUTION_DIGEST
or _sha_text(value["receipt_json"]) != value.get("receipt_sha256")
or not isinstance(receipt, Mapping)
or receipt.get("schema") != PROJECTION_RECEIPT_SCHEMA
or receipt.get("canonical_child_execution_digest") != canonical_digest
or receipt.get("source_business_execution_digest") != source_digest
or receipt.get("owner_supersession_decision_sha256")
!= relation["owner_decision_sha256"]
):
_fail("CHILD_PROJECTION_RECEIPT_MISMATCH")
return {
**value,
"canonical_child_execution_digest": canonical_digest,
"source_business_execution_digest": source_digest,
}
def install_child_binding(
connection,
*,
policy: Mapping[str, Any],
runtime_id: str,
runtime_observation_digest: str,
task_contract_version: str,
policy_factory: Callable[[Mapping[str, Any]], Any] = _default_policy_factory,
now: str | None = None,
) -> dict[str, Any]:
"""Generate and persist the only permitted child binding from current DB state."""
if not isinstance(policy, Mapping):
_fail("OPERATIONAL_POLICY_INVALID")
if not isinstance(runtime_id, str) or not runtime_id or len(runtime_id) > 512:
_fail("RUNTIME_ID_INVALID")
if not isinstance(runtime_observation_digest, str) or HEX64.fullmatch(runtime_observation_digest) is None:
_fail("RUNTIME_OBSERVATION_DIGEST_INVALID")
if not isinstance(task_contract_version, str) or not task_contract_version or len(task_contract_version) > 128:
_fail("TASK_CONTRACT_VERSION_INVALID")
relation = connection.execute(
"SELECT * FROM task_lineage_supersessions WHERE source_task_id=? AND supersession_ordinal=1 AND child_task_id=?",
(SOURCE_TASK_ID, CHILD_TASK_ID),
).fetchone()
if relation is None:
_fail("FORMAL_SUPERSESSION_REQUIRED")
projection = _load_projection_evidence(connection, relation)
canonical_execution_digest = projection["canonical_child_execution_digest"]
task_row = connection.execute("SELECT * FROM tasks WHERE task_id=?", (CHILD_TASK_ID,)).fetchone()
if task_row is None:
_fail("CHILD_TASK_NOT_FOUND", 404)
task = _row_dict(task_row)
if (
task.get("status") != "QUEUED" or int(task.get("revision", -1)) < 2
or int(task.get("attempt", -1)) != 0 or task.get("claimed_by") is not None
or task.get("lease_token_hash") is not None or task.get("lease_expires_at") is not None
or task.get("result_json") is not None or task.get("verification_json") is not None
):
_fail("CHILD_PROJECTED_CLAIMABLE_STATE_REQUIRED")
event_rows = connection.execute(
"SELECT event_type,payload_json FROM events WHERE task_id=? ORDER BY event_id",
(CHILD_TASK_ID,),
).fetchall()
if [row[0] for row in event_rows] != [
"TASK_CREATED",
"TASK_INITIAL_EXECUTION_PROJECTION_AUTHORIZED",
]:
_fail("CANONICAL_INITIAL_PROJECTION_REQUIRED")
projection_event = _json(
event_rows[-1][1], "CANONICAL_INITIAL_PROJECTION_EVENT_INVALID"
)
if (
projection_event.get("source_business_execution_digest")
!= SOURCE_BUSINESS_EXECUTION_DIGEST
or projection_event.get("canonical_child_execution_digest")
!= canonical_execution_digest
or projection_event.get("owner_supersession_decision_sha256")
!= relation["owner_decision_sha256"]
or _sha_json(projection_event) != projection["projection_event_sha256"]
):
_fail("CANONICAL_INITIAL_PROJECTION_EVENT_MISMATCH")
metadata_raw = task.get("metadata_json")
capabilities_raw = task.get("required_capabilities_json")
metadata = _json(metadata_raw, "CHILD_METADATA_INVALID")
capabilities = _json(capabilities_raw, "CHILD_CAPABILITIES_INVALID")
if (
_sha_text(metadata_raw) != projection["metadata_sha256"]
or _sha_text(capabilities_raw)
!= projection["required_capabilities_sha256"]
):
_fail("CHILD_PROJECTION_TASK_DIGEST_MISMATCH")
if not isinstance(metadata, Mapping) or not isinstance(capabilities, list) or not capabilities:
_fail("CHILD_PROJECTION_INVALID")
if any(not isinstance(value, str) or not value for value in capabilities) or len(set(capabilities)) != len(capabilities):
_fail("CHILD_CAPABILITIES_INVALID")
execution = metadata.get("execution")
if not isinstance(execution, Mapping):
_fail("CHILD_EXECUTION_PROJECTION_MISSING")
exact_execution = {
"task_id": CHILD_TASK_ID, "repository": REPOSITORY,
"target_branch": TARGET_BRANCH, "generation": GENERATION,
"execution_digest": canonical_execution_digest, "job_id": EXTERNAL_JOB_ID,
}
if any(execution.get(key) != value for key, value in exact_execution.items()):
_fail("CHILD_EXECUTION_BINDING_MISMATCH")
parsed_policy = policy_factory(dict(policy))
if hasattr(parsed_policy, "validate"):
parsed_policy.validate()
policy_exact = {
"task_id": CHILD_TASK_ID, "repository": REPOSITORY,
"target_branch": TARGET_BRANCH, "generation": GENERATION,
"execution_plan_sha256": canonical_execution_digest,
"external_job_identity": EXTERNAL_JOB_ID,
}
if any(_policy_value(parsed_policy, key) != value for key, value in policy_exact.items()):
_fail("OPERATIONAL_POLICY_SCOPE_MISMATCH")
if tuple(_policy_value(parsed_policy, "allowed_worker_ids")) != (WORKER_ID,):
_fail("OPERATIONAL_POLICY_WORKER_MISMATCH")
expected_oid = execution.get("expected_branch_oid", execution.get("base_oid"))
if expected_oid is not None and _policy_value(parsed_policy, "base_oid") != expected_oid:
_fail("OPERATIONAL_POLICY_BASE_OID_MISMATCH")
policy_json = canonical_json(dict(policy))
policy_sha = _sha_text(policy_json)
policy_digest = str(_policy_value(parsed_policy, "digest"))
if policy_digest != policy_sha:
_fail("OPERATIONAL_POLICY_DIGEST_MISMATCH")
binding = {
"schema": BINDING_SCHEMA, "task_id": CHILD_TASK_ID,
"source_task_id": SOURCE_TASK_ID, "supersession_ordinal": 1,
"expected_revision": int(task["revision"]),
"metadata_sha256": _sha_text(metadata_raw),
"required_capabilities_sha256": _sha_json(sorted(capabilities)),
"worker_id": WORKER_ID,
"execution_digest": canonical_execution_digest,
"canonical_child_execution_digest": canonical_execution_digest,
"source_business_execution_digest": SOURCE_BUSINESS_EXECUTION_DIGEST,
"repository": REPOSITORY, "target_branch": TARGET_BRANCH, "generation": GENERATION,
"external_job_id": EXTERNAL_JOB_ID, "policy_sha256": policy_sha,
"policy_digest": policy_digest, "runtime_id": runtime_id,
"runtime_observation_digest": runtime_observation_digest,
"task_contract_version": task_contract_version,
"owner_supersession_decision_sha256": relation["owner_decision_sha256"],
}
binding_json = canonical_json(binding)
binding_sha = _sha_text(binding_json)
binding_id = "sha256:" + binding_sha
existing = connection.execute(
"SELECT binding_sha256 FROM task_claim_guard_bindings_v2 WHERE task_id=?", (CHILD_TASK_ID,)
).fetchone()
if existing is not None:
if existing[0] != binding_sha:
_fail("CLAIM_GUARD_BINDING_ALREADY_EXISTS")
receipt = connection.execute(
"SELECT receipt_json FROM task_claim_guard_binding_receipts_v2 WHERE binding_id=?", (binding_id,)
).fetchone()
if receipt is None:
_fail("CLAIM_GUARD_BINDING_RECEIPT_MISSING", 500)
return {**json.loads(receipt[0]), "idempotent": True}
created_at = now or _now()
connection.execute(
"""INSERT INTO task_claim_guard_bindings_v2(
binding_id,task_id,source_task_id,supersession_ordinal,expected_revision,
metadata_sha256,required_capabilities_sha256,worker_id,execution_digest,
repository,target_branch,generation,policy_json,policy_sha256,binding_json,
binding_sha256,created_at) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
(binding_id, CHILD_TASK_ID, SOURCE_TASK_ID, 1, binding["expected_revision"],
binding["metadata_sha256"], binding["required_capabilities_sha256"], WORKER_ID,
canonical_execution_digest, REPOSITORY, TARGET_BRANCH, GENERATION, policy_json, policy_sha,
binding_json, binding_sha, created_at),
)
receipt = {
"schema": RECEIPT_SCHEMA, "status": "BOUND", "binding_id": binding_id,
"binding_sha256": binding_sha, "task_id": CHILD_TASK_ID,
"expected_revision": binding["expected_revision"],
"metadata_sha256": binding["metadata_sha256"], "worker_id": WORKER_ID,
"execution_digest": canonical_execution_digest,
"canonical_child_execution_digest": canonical_execution_digest,
"source_business_execution_digest": SOURCE_BUSINESS_EXECUTION_DIGEST,
"repository": REPOSITORY,
"target_branch": TARGET_BRANCH, "generation": GENERATION,
"owner_supersession_decision_sha256": relation["owner_decision_sha256"],
}
receipt_sha = _sha_json(receipt)
receipt_id = "sha256:" + receipt_sha
connection.execute(
"INSERT INTO task_claim_guard_binding_receipts_v2 VALUES(?,?,?,?,?,?)",
(receipt_id, binding_id, CHILD_TASK_ID, canonical_json(receipt), receipt_sha, created_at),
)
return {**receipt, "receipt_id": receipt_id, "receipt_sha256": receipt_sha, "idempotent": False}
def _load_child_binding(connection) -> tuple[dict[str, Any], dict[str, Any]]:
row = connection.execute(
"SELECT * FROM task_claim_guard_bindings_v2 WHERE task_id=?", (CHILD_TASK_ID,)
).fetchone()
if row is None:
_fail("CHILD_CLAIM_GUARD_BINDING_MISSING")
value = _row_dict(row)
binding = _json(value["binding_json"], "CLAIM_GUARD_BINDING_INVALID")
policy = _json(value["policy_json"], "OPERATIONAL_POLICY_INVALID")
relation = connection.execute(
"SELECT * FROM task_lineage_supersessions WHERE source_task_id=? "
"AND supersession_ordinal=1 AND child_task_id=?",
(SOURCE_TASK_ID, CHILD_TASK_ID),
).fetchone()
if relation is None:
_fail("FORMAL_SUPERSESSION_REQUIRED")
projection = _load_projection_evidence(connection, relation)
canonical_execution_digest = projection["canonical_child_execution_digest"]
if (
_sha_text(value["binding_json"]) != value["binding_sha256"]
or binding.get("schema") != BINDING_SCHEMA
or value.get("execution_digest") != canonical_execution_digest
or binding.get("execution_digest") != canonical_execution_digest
or binding.get("canonical_child_execution_digest")
!= canonical_execution_digest
or binding.get("source_business_execution_digest")
!= SOURCE_BUSINESS_EXECUTION_DIGEST
or binding.get("owner_supersession_decision_sha256")
!= relation["owner_decision_sha256"]
or policy.get("execution_plan_sha256") != canonical_execution_digest
):
_fail("CLAIM_GUARD_BINDING_DIGEST_MISMATCH")
task = connection.execute("SELECT * FROM tasks WHERE task_id=?", (CHILD_TASK_ID,)).fetchone()
task = _row_dict(task)
if (
task.get("status") != "QUEUED" or int(task.get("revision", -1)) != int(binding["expected_revision"])
or int(task.get("attempt", -1)) != 0 or task.get("claimed_by") is not None
or task.get("lease_token_hash") is not None or task.get("lease_expires_at") is not None
or _sha_text(task.get("metadata_json", "")) != binding["metadata_sha256"]
or task.get("result_json") is not None or task.get("verification_json") is not None
):
_fail("CHILD_CLAIM_GUARD_TASK_CAS_MISMATCH")
return binding, policy
def _run_child_guard(connection, binding, policy_value, agent_id):
try:
from aios_single_host_operational_authority import OperationalPolicy, OperationalVerifier
from aios_singlehost_claim_guard_stage1 import _LiveReader, REGISTRY_PATH
from aios_task_claim_guard import ClaimGuard, ClaimGuardError as CoreError
except ImportError as exc:
raise ClaimGuardBindingError("CLAIM_GUARD_RUNTIME_UNAVAILABLE", 503) from exc
policy = OperationalPolicy.from_mapping(policy_value)
cfg = {
"task_id": CHILD_TASK_ID, "expected_claim_revision": binding["expected_revision"],
"task_metadata_sha256": binding["metadata_sha256"],
"required_capabilities_sha256": binding["required_capabilities_sha256"],
"task_contract_version": binding["task_contract_version"], "worker_id": WORKER_ID,
"policy_digest": binding["policy_digest"], "runtime_id": binding["runtime_id"],
"runtime_observation_digest": binding["runtime_observation_digest"],
}
reader = _LiveReader(connection, policy, cfg, now=int(datetime.now(timezone.utc).timestamp()))
try:
ClaimGuard(policy, OperationalVerifier(REGISTRY_PATH, require_root_owner=True)).validate_in_transaction(
reader, task_id=CHILD_TASK_ID,
expected_revision=int(binding["expected_revision"]), worker_id=agent_id,
)
except CoreError as exc:
raise ClaimGuardBindingError(exc.code) from exc
def enforce_claim_guard(connection, *, task_id: str, agent_id: str) -> None:
"""Exact two-lineage dispatcher; unrelated Tasks retain their prior behavior."""
if task_id == SOURCE_TASK_ID:
try:
from aios_singlehost_claim_guard_stage1 import enforce_claim_guard as frozen_guard
except ImportError as exc:
raise ClaimGuardBindingError("FROZEN_SOURCE_CLAIM_GUARD_UNAVAILABLE", 503) from exc
frozen_guard(connection, task_id=task_id, agent_id=agent_id)
return
if task_id != CHILD_TASK_ID:
return
if agent_id != WORKER_ID:
_fail("WORKER_ID_MISMATCH", 403)
binding, policy = _load_child_binding(connection)
_run_child_guard(connection, binding, policy, agent_id)
@@ -0,0 +1,148 @@
"""Fail-closed signed deterministic execution amendment for R7."""
from datetime import datetime, timezone
import hashlib, json, sqlite3
from pathlib import Path
from typing import Mapping
from aios_task_terminal_recovery import DeterministicPlan, RecoveryError, canonical_json
AMENDMENT_SCHEMA="aios.deterministic-execution-amendment.v1"
APPLY_SCHEMA="aios.deterministic-execution-amendment-apply.v1"
NAMESPACE="aios-owner-approval-v1"
EVENT="TASK_RECOVERY_EXECUTION_AMENDMENT_APPLIED"
APPLY_STATES=frozenset({"FAILED", "QUEUED"})
def _err(code): raise RecoveryError(code)
def _sha(v): return hashlib.sha256(canonical_json(v).encode()).hexdigest()
def amendment_body(x):
return {k:v for k,v in x.items() if k not in {"signature","amendment_id"}}
def amendment_id(x): return "sha256:"+_sha(amendment_body(x))
def signed_payload(x):
return canonical_json({"amendment_id":x["amendment_id"],**amendment_body(x)}).encode()
def parse_amendment(raw):
x=dict(raw)
required={"schema","amendment_id","nonce","task_id","job_id","generation",
"repository","branch","expected_branch_oid","recovery_epoch","decision_id",
"plan_id","plan_sha256","expected_apply_status","expected_apply_revision",
"expected_apply_attempt","expected_current_capabilities","envelope",
"owner_identity","key_id","issued_at","not_before","expires_at",
"one_time_use","signature"}
if set(x)<required or x.get("schema")!=AMENDMENT_SCHEMA: _err("AMENDMENT_SCHEMA_INVALID")
if x["expected_apply_status"] not in APPLY_STATES: _err("AMENDMENT_APPLY_STATUS_INVALID")
if x.get("one_time_use") is not True: _err("AMENDMENT_ONE_TIME_REQUIRED")
if not isinstance(x["envelope"],Mapping): _err("AMENDMENT_ENVELOPE_INVALID")
if not isinstance(x["expected_current_capabilities"],list): _err("AMENDMENT_CAPABILITIES_INVALID")
for k in ("generation","expected_apply_revision","expected_apply_attempt"):
if isinstance(x[k],bool) or not isinstance(x[k],int) or x[k]<0: _err("AMENDMENT_CAS_INVALID")
for k in ("issued_at","not_before","expires_at"):
try:d=datetime.fromisoformat(str(x[k]).replace("Z","+00:00"))
except ValueError as e: raise RecoveryError("AMENDMENT_TIME_INVALID") from e
if d.tzinfo is None:_err("AMENDMENT_TIME_INVALID")
x[k]=d.astimezone(timezone.utc).isoformat().replace("+00:00","Z")
x["expected_current_capabilities"]=sorted(set(x["expected_current_capabilities"]))
x["envelope"]=dict(x["envelope"])
return x
def _validate_envelope(env,plan,x):
try:
from aios_online_agent_execution import GovernedExecutionBinding,validate_execution_envelope
binding=GovernedExecutionBinding.from_mapping(env)
e=dict(validate_execution_envelope(env,binding=binding,worker_capabilities=plan.required_capabilities))
except Exception as z: raise RecoveryError("AMENDMENT_EXECUTION_ENVELOPE_INVALID") from z
if e.get("action")!="deterministic" or e.get("adapter")!="deterministic":_err("AMENDMENT_EXECUTION_ACTION_INVALID")
if tuple(sorted(e.get("capability_scope") or ()))!=plan.required_capabilities:_err("AMENDMENT_EXECUTION_CAPABILITIES_MISMATCH")
if not e.get("change_set") or not e.get("verification"):_err("AMENDMENT_EXECUTION_OPERATIONS_REQUIRED")
if e.get("runtime_input") not in (None,{}) or e.get("source_signals") not in (None,[]):_err("AMENDMENT_STALE_RUNTIME_FIELDS_FORBIDDEN")
for k,w in (("task_id",x["task_id"]),("job_id",x["job_id"]),("repository",x["repository"]),
("generation",x["generation"]),("target_branch",x["branch"]),
("expected_branch_oid",x["expected_branch_oid"]),("base_oid",x["expected_branch_oid"]),
("plan_id",x["plan_id"]),("plan_sha256",x["plan_sha256"]),("recovery_epoch",x["recovery_epoch"])):
if e.get(k)!=w:_err("AMENDMENT_EXECUTION_BINDING_MISMATCH")
g=e.get("governance") or {}
if g.get("writer_lease") is not True:_err("AMENDMENT_WRITER_LEASE_REQUIRED")
if any(g.get(k,False) for k in ("main_write","production_write","tag_write","baseline_write","force_push","protected_ref_bypass")):_err("AMENDMENT_PROTECTED_WRITE_FORBIDDEN")
return e
class DeterministicExecutionAmendmentController:
def __init__(self,db_path,verifier,*,clock=None,envelope_validator=None):
self.db_path=Path(db_path); self.verifier=verifier
self.clock=clock or (lambda:datetime.now(timezone.utc)); self._init()
def _db(self):
c=sqlite3.connect(self.db_path,timeout=10,isolation_level=None); c.row_factory=sqlite3.Row
c.execute("PRAGMA busy_timeout=10000"); return c
def _now(self): return self.clock().astimezone(timezone.utc).isoformat().replace("+00:00","Z")
def _init(self):
with self._db() as c:
c.execute("DROP TRIGGER IF EXISTS aios_r7_recovery_capability_projection")
c.executescript("""
CREATE TABLE IF NOT EXISTS deterministic_execution_amendments(
amendment_id TEXT PRIMARY KEY,nonce TEXT UNIQUE NOT NULL,task_id TEXT NOT NULL,
decision_id TEXT NOT NULL,plan_id TEXT NOT NULL,plan_sha256 TEXT NOT NULL,
envelope_digest TEXT NOT NULL,envelope_json TEXT NOT NULL,payload_json TEXT NOT NULL,
created_at TEXT NOT NULL,UNIQUE(task_id,decision_id));
DROP TRIGGER IF EXISTS aios_r7_recovery_requires_executable_amendment;
CREATE TRIGGER aios_r7_recovery_requires_executable_amendment
BEFORE UPDATE OF status ON tasks
WHEN OLD.status='FAILED' AND NEW.status='QUEUED' BEGIN
SELECT CASE WHEN NOT EXISTS(
SELECT 1 FROM deterministic_execution_amendments a
WHERE a.task_id=OLD.task_id
AND a.decision_id=json_extract(NEW.metadata_json,'$.recovery.decision_id')
AND a.amendment_id=json_extract(OLD.metadata_json,'$.recovery.execution_amendment_id')
AND a.envelope_digest=json_extract(OLD.metadata_json,'$.execution.execution_digest')
) THEN RAISE(ABORT,'SIGNED_EXECUTION_AMENDMENT_REQUIRED') END;
END;""")
def _plan(self,c,x):
r=c.execute("SELECT * FROM deterministic_plans WHERE plan_id=?",(x["plan_id"],)).fetchone()
if r is None or r["plan_sha256"]!=x["plan_sha256"]:_err("AMENDMENT_PLAN_NOT_FOUND_OR_MISMATCH")
p=DeterministicPlan.from_dict(json.loads(r["payload_json"]))
if (p.task_id,p.job_id,p.generation,p.repository,p.branch,p.expected_branch_oid,p.recovery_epoch)!=(x["task_id"],x["job_id"],x["generation"],x["repository"],x["branch"],x["expected_branch_oid"],x["recovery_epoch"]):_err("AMENDMENT_PLAN_BINDING_MISMATCH")
return p
def persist(self,raw):
x=parse_amendment(raw)
if x["amendment_id"]!=amendment_id(x):_err("AMENDMENT_ID_DIGEST_MISMATCH")
now=self._now()
if now<x["not_before"] or now>=x["expires_at"]:_err("AMENDMENT_EXPIRED_OR_NOT_YET_VALID")
if not self.verifier.verify(namespace=NAMESPACE,payload=signed_payload(x),signature=x["signature"],key_id=x["key_id"]):_err("AMENDMENT_SIGNATURE_INVALID")
with self._db() as c:
c.execute("BEGIN IMMEDIATE"); e=_validate_envelope(x["envelope"],self._plan(c,x),x)
ej=canonical_json(e); ed=e["execution_digest"]; stored=dict(x); stored["signature"]="<present>"
old=c.execute("SELECT * FROM deterministic_execution_amendments WHERE amendment_id=?",(x["amendment_id"],)).fetchone()
if old:
if old["envelope_json"]!=ej:_err("AMENDMENT_ID_REUSE_MISMATCH")
c.commit(); return {"status":"PRESENT","amendment_id":x["amendment_id"],"task_id":x["task_id"],"decision_id":x["decision_id"],"plan_id":x["plan_id"],"execution_digest":ed}
if c.execute("SELECT 1 FROM deterministic_execution_amendments WHERE nonce=? OR (task_id=? AND decision_id=?)",(x["nonce"],x["task_id"],x["decision_id"])).fetchone():_err("AMENDMENT_REPLAY_OR_DECISION_REUSE")
c.execute("INSERT INTO deterministic_execution_amendments VALUES(?,?,?,?,?,?,?,?,?,?)",(x["amendment_id"],x["nonce"],x["task_id"],x["decision_id"],x["plan_id"],x["plan_sha256"],ed,ej,canonical_json(stored),now)); c.commit()
return {"status":"PERSISTED","amendment_id":x["amendment_id"],"task_id":x["task_id"],"decision_id":x["decision_id"],"plan_id":x["plan_id"],"execution_digest":ed}
def apply(self,raw):
if raw.get("schema")!=APPLY_SCHEMA:_err("AMENDMENT_APPLY_SCHEMA_INVALID")
aid=raw.get("amendment_id"); tid=raw.get("task_id")
with self._db() as c:
c.execute("BEGIN IMMEDIATE"); a=c.execute("SELECT * FROM deterministic_execution_amendments WHERE amendment_id=? AND task_id=?",(aid,tid)).fetchone()
if a is None:_err("AMENDMENT_NOT_FOUND")
x=json.loads(a["payload_json"]); d=json.loads(a["envelope_json"])
t=c.execute("SELECT * FROM tasks WHERE task_id=?",(tid,)).fetchone()
if t is None:_err("TASK_NOT_FOUND")
m=json.loads(t["metadata_json"] or "{}"); cp=tuple(sorted(json.loads(t["required_capabilities_json"] or "[]"))); dc=tuple(sorted(d.get("capability_scope") or ()))
rec=m.get("recovery"); rec=dict(rec) if isinstance(rec,Mapping) else {}
old=m.get("execution")
if isinstance(old,Mapping) and old.get("execution_digest")==a["envelope_digest"] and cp==dc and rec.get("execution_amendment_id")==aid:
c.commit(); return {"status":"PRESENT","task_id":tid,"amendment_id":aid,"execution_digest":a["envelope_digest"],"revision":int(t["revision"]),"attempt":int(t["attempt"])}
if t["status"]!=x["expected_apply_status"] or int(t["revision"])!=x["expected_apply_revision"] or int(t["attempt"])!=x["expected_apply_attempt"] or cp!=tuple(x["expected_current_capabilities"]) or t["claimed_by"] is not None or t["lease_token_hash"] is not None:_err("AMENDMENT_APPLY_TASK_CAS_MISMATCH")
if t["status"]=="QUEUED" and rec.get("decision_id")!=x["decision_id"]:_err("AMENDMENT_DECISION_BINDING_MISMATCH")
m["execution"]=d; m["execution_projection"]={"schema":"aios.online-agent.task-projection.v1","task_id":tid,"job_id":d.get("job_id"),"execution_digest":a["envelope_digest"]}
rec["execution_amendment_id"]=aid; m["recovery"]=rec; now=self._now()
q=c.execute("""UPDATE tasks SET required_capabilities_json=?,metadata_json=?,revision=revision+1,updated_at=? WHERE task_id=? AND status=? AND revision=? AND attempt=? AND claimed_by IS NULL AND lease_token_hash IS NULL""",(canonical_json(list(dc)),canonical_json(m),now,tid,x["expected_apply_status"],x["expected_apply_revision"],x["expected_apply_attempt"]))
if q.rowcount!=1:_err("AMENDMENT_APPLY_TASK_CAS_FAILED")
c.execute("INSERT INTO events(task_id,agent_id,event_type,payload_json,created_at) VALUES(?,?,?,?,?)",(tid,None,EVENT,canonical_json({"amendment_id":aid,"decision_id":x["decision_id"],"plan_id":x["plan_id"],"execution_digest":a["envelope_digest"],"required_capabilities":list(dc)}),now)); c.commit()
return {"status":"APPLIED","task_id":tid,"amendment_id":aid,"decision_id":x["decision_id"],"plan_id":x["plan_id"],"execution_digest":a["envelope_digest"],"required_capabilities":list(dc),"revision":x["expected_apply_revision"]+1,"attempt":x["expected_apply_attempt"]}
+779
View File
@@ -0,0 +1,779 @@
from __future__ import annotations
from dataclasses import dataclass, field, replace
from datetime import datetime, timezone
from enum import Enum
from hashlib import sha256
import re
from typing import Callable, Iterable, Mapping, Protocol, Sequence
class LineageError(ValueError):
pass
class RelationType(str, Enum):
ROOT = "ROOT"
CHILD = "CHILD"
SUCCESSOR = "SUCCESSOR"
RECOVERY = "RECOVERY"
class NextLegalAction(str, Enum):
ROOT_COMPLETE = "ROOT_COMPLETE"
CREATE_OR_RESUME_NEXT_CHILD = "CREATE_OR_RESUME_NEXT_CHILD"
WAIT_DEPENDENCY = "WAIT_DEPENDENCY"
RECOVER_EXISTING_CHILD = "RECOVER_EXISTING_CHILD"
OWNER_BOUNDARY = "OWNER_BOUNDARY"
TERMINAL_STATUSES = frozenset(
{"COMPLETE", "COMPLETED", "SUCCEEDED", "FAILED", "FAILED_FINAL", "CANCELLED"}
)
RECOVERY_STATUSES = frozenset(
{"RECONCILIATION_REQUIRED", "RECOVERY_REQUIRED", "FAILED_RECOVERABLE"}
)
BLOCKED_STATUSES = frozenset({"BLOCKED", "WAITING_DEPENDENCY", "WAITING_CAPACITY"})
_ALIAS_RE = re.compile(r"^[A-Z0-9][A-Z0-9_-]{1,31}$")
_SLUG_RE = re.compile(r"^[A-Z0-9][A-Z0-9-]{1,63}$")
def _require_utc(ts: datetime) -> datetime:
if ts.tzinfo is None or ts.utcoffset() is None:
raise LineageError("created_at/reviewed_at must be timezone-aware")
return ts.astimezone(timezone.utc)
def derive_safe_owner_alias(principal_id: str, preferred_alias: str | None = None) -> str:
"""Return a non-secret display alias.
A trusted identity adapter may supply a safe human alias. Otherwise we use a
deterministic pseudonym derived from the principal identifier. We never put
raw email addresses, tokens, or principal strings into task names.
"""
if preferred_alias and "@" not in preferred_alias and ":" not in preferred_alias:
candidate = re.sub(r"[^A-Za-z0-9_-]+", "-", preferred_alias.strip()).strip("-_").upper()
if 2 <= len(candidate) <= 32 and _ALIAS_RE.fullmatch(candidate):
return candidate
if not principal_id:
raise LineageError("principal_id is required")
return "U" + sha256(principal_id.encode("utf-8")).hexdigest()[:9].upper()
def normalize_goal_slug(value: str) -> str:
value = re.sub(r"[^A-Za-z0-9]+", "-", value.strip()).strip("-").upper()
value = re.sub(r"-+", "-", value)
if len(value) > 64:
value = value[:64].rstrip("-")
if not _SLUG_RE.fullmatch(value):
raise LineageError("goal_slug must normalize to 2-64 [A-Z0-9-] characters")
return value
def short_ref(task_id: str) -> str:
if not task_id:
raise LineageError("task_id is required")
compact = re.sub(r"[^A-Za-z0-9]", "", task_id).upper()
if len(compact) >= 8:
return compact[-8:]
return sha256(task_id.encode("utf-8")).hexdigest()[:8].upper()
@dataclass(frozen=True)
class TaskLineage:
task_id: str
display_name: str
owner_principal_id: str
owner_alias: str
created_at: datetime
mission_id: str
root_task_id: str
parent_task_id: str | None
lineage_depth: int
relation_type: RelationType
sibling_sequence: int
goal_slug: str
root_acceptance_ref: str
parent_revision: int | None = None
@dataclass(frozen=True)
class TaskRecord:
task_id: str
revision: int
status: str
lineage: TaskLineage
evidence_refs: tuple[str, ...] = ()
terminal_evidence_proven: bool = False
@dataclass(frozen=True)
class AcceptanceCondition:
key: str
satisfied: bool
evidence_refs: tuple[str, ...] = ()
owner_boundary: bool = False
@dataclass(frozen=True)
class RootAcceptanceSnapshot:
conditions: tuple[AcceptanceCondition, ...]
@property
def remaining(self) -> tuple[str, ...]:
return tuple(c.key for c in self.conditions if not c.satisfied)
@property
def complete(self) -> bool:
return all(c.satisfied for c in self.conditions)
@property
def owner_boundary_required(self) -> bool:
return any((not c.satisfied) and c.owner_boundary for c in self.conditions)
@property
def evidence_refs(self) -> tuple[str, ...]:
values: list[str] = []
for condition in self.conditions:
if condition.satisfied:
values.extend(condition.evidence_refs)
return tuple(dict.fromkeys(values))
def canonicalize_acceptance(
acceptance: RootAcceptanceSnapshot,
) -> RootAcceptanceSnapshot:
if not isinstance(acceptance, RootAcceptanceSnapshot):
raise LineageError("acceptance evaluator returned an invalid snapshot")
normalized: list[AcceptanceCondition] = []
keys: set[str] = set()
for condition in acceptance.conditions:
if not isinstance(condition, AcceptanceCondition):
raise LineageError("acceptance snapshot contains an invalid condition")
if not isinstance(condition.key, str) or not condition.key:
raise LineageError("acceptance condition key must be a non-empty string")
if condition.key in keys:
raise LineageError("acceptance condition keys must be unique")
if not isinstance(condition.satisfied, bool) or not isinstance(
condition.owner_boundary, bool
):
raise LineageError("acceptance condition flags must be booleans")
if not all(isinstance(ref, str) and ref for ref in condition.evidence_refs):
raise LineageError("acceptance evidence refs must be non-empty strings")
keys.add(condition.key)
normalized.append(
replace(
condition,
evidence_refs=tuple(sorted(set(condition.evidence_refs))),
)
)
return RootAcceptanceSnapshot(tuple(sorted(normalized, key=lambda c: c.key)))
@dataclass(frozen=True)
class RootReviewRecord:
review_id: str
root_task_id: str
reviewed_at: datetime
trigger_child_task_id: str
root_revision_before: int
root_revision_after: int
completed_children: tuple[str, ...]
active_children: tuple[str, ...]
blocked_children: tuple[str, ...]
remaining_acceptance_conditions: tuple[str, ...]
next_legal_action: NextLegalAction
root_complete: bool
evidence_refs: tuple[str, ...]
class RootReviewStore(Protocol):
def get_task(self, task_id: str) -> TaskRecord: ...
def list_descendants(self, root_task_id: str) -> Sequence[TaskRecord]: ...
def get_root_review(self, review_id: str) -> RootReviewRecord | None: ...
def compare_and_swap_root_review(
self,
root_task_id: str,
expected_root_revision: int,
review: RootReviewRecord,
) -> bool: ...
AcceptanceEvaluator = Callable[[TaskRecord, Sequence[TaskRecord]], RootAcceptanceSnapshot]
def _timestamp(ts: datetime) -> str:
return _require_utc(ts).strftime("%Y%m%dT%H%M%SZ")
def _relation_token(relation: RelationType, sequence: int) -> str:
if sequence < 1 or sequence > 99:
raise LineageError("non-root sibling_sequence must be 1..99")
prefix = {
RelationType.CHILD: "C",
RelationType.SUCCESSOR: "S",
RelationType.RECOVERY: "X",
}.get(relation)
if not prefix:
raise LineageError("ROOT does not use a child relation token")
return f"{prefix}{sequence:02d}"
def build_display_name(lineage: TaskLineage) -> str:
alias = lineage.owner_alias.upper()
if not _ALIAS_RE.fullmatch(alias):
raise LineageError("owner_alias must be a non-secret [A-Z0-9_-] alias")
goal = normalize_goal_slug(lineage.goal_slug)
ts = _timestamp(lineage.created_at)
if lineage.relation_type == RelationType.ROOT:
return f"TASK-{alias}-{ts}-ROOT-{goal}-{short_ref(lineage.task_id)[:4]}"
relation = _relation_token(lineage.relation_type, lineage.sibling_sequence)
return (
f"TASK-{alias}-{ts}"
f"-R{short_ref(lineage.root_task_id)}"
f"-P{short_ref(lineage.parent_task_id or '')}"
f"-D{lineage.lineage_depth:02d}"
f"-{relation}-{goal}"
)
def make_root_lineage(
*,
task_id: str,
owner_principal_id: str,
owner_alias: str,
created_at: datetime,
mission_id: str,
goal_slug: str,
root_acceptance_ref: str,
) -> TaskLineage:
lineage = TaskLineage(
task_id=task_id,
display_name="",
owner_principal_id=owner_principal_id,
owner_alias=owner_alias.upper(),
created_at=_require_utc(created_at),
mission_id=mission_id,
root_task_id=task_id,
parent_task_id=None,
lineage_depth=0,
relation_type=RelationType.ROOT,
sibling_sequence=0,
goal_slug=normalize_goal_slug(goal_slug),
root_acceptance_ref=root_acceptance_ref,
parent_revision=None,
)
lineage = replace(lineage, display_name=build_display_name(lineage))
validate_lineage(lineage)
return lineage
def make_child_lineage(
*,
task_id: str,
created_at: datetime,
parent: TaskRecord,
root: TaskRecord,
relation_type: RelationType,
sibling_sequence: int,
goal_slug: str,
) -> TaskLineage:
if relation_type == RelationType.ROOT:
raise LineageError("child relation_type cannot be ROOT")
lineage = TaskLineage(
task_id=task_id,
display_name="",
owner_principal_id=root.lineage.owner_principal_id,
owner_alias=root.lineage.owner_alias,
created_at=_require_utc(created_at),
mission_id=root.lineage.mission_id,
root_task_id=root.task_id,
parent_task_id=parent.task_id,
lineage_depth=parent.lineage.lineage_depth + 1,
relation_type=relation_type,
sibling_sequence=sibling_sequence,
goal_slug=normalize_goal_slug(goal_slug),
root_acceptance_ref=root.lineage.root_acceptance_ref,
parent_revision=parent.revision,
)
lineage = replace(lineage, display_name=build_display_name(lineage))
validate_lineage(lineage, parent=parent, root=root)
return lineage
def validate_lineage(
lineage: TaskLineage,
*,
parent: TaskRecord | None = None,
root: TaskRecord | None = None,
) -> None:
_validate_lineage_shape(lineage)
if lineage.relation_type == RelationType.ROOT:
return
if parent is None or root is None:
raise LineageError("non-root validation requires parent and root authority records")
if root.lineage.relation_type != RelationType.ROOT:
raise LineageError("root authority record must have ROOT lineage")
if parent.lineage.root_task_id != root.task_id:
raise LineageError("parent is not attached to the selected root")
if parent.lineage.owner_principal_id != root.lineage.owner_principal_id:
raise LineageError("parent owner does not match root owner")
if parent.lineage.mission_id != root.lineage.mission_id:
raise LineageError("parent mission does not match root mission")
if parent.lineage.root_acceptance_ref != root.lineage.root_acceptance_ref:
raise LineageError("parent acceptance reference does not match root")
if lineage.root_task_id != root.task_id:
raise LineageError("root_task_id mismatch")
if lineage.parent_task_id != parent.task_id:
raise LineageError("parent_task_id mismatch")
if lineage.lineage_depth != parent.lineage.lineage_depth + 1:
raise LineageError("lineage_depth must equal parent depth + 1")
if lineage.owner_principal_id != root.lineage.owner_principal_id:
raise LineageError("cross-owner lineage attachment rejected")
if lineage.owner_alias != root.lineage.owner_alias:
raise LineageError("owner_alias must match root")
if lineage.mission_id != root.lineage.mission_id:
raise LineageError("mission_id must match root")
if lineage.root_acceptance_ref != root.lineage.root_acceptance_ref:
raise LineageError("root_acceptance_ref must match root")
if lineage.parent_revision != parent.revision:
raise LineageError("stale or mismatched parent revision")
def _validate_lineage_shape(lineage: TaskLineage) -> None:
if not lineage.task_id or not lineage.owner_principal_id or not lineage.mission_id:
raise LineageError("task_id, owner_principal_id and mission_id are required")
if not lineage.root_acceptance_ref:
raise LineageError("root_acceptance_ref is required")
if not _ALIAS_RE.fullmatch(lineage.owner_alias):
raise LineageError("invalid owner_alias")
if normalize_goal_slug(lineage.goal_slug) != lineage.goal_slug:
raise LineageError("goal_slug must already be normalized")
if not lineage.display_name or len(lineage.display_name) > 200:
raise LineageError("display_name must be non-empty and <= 200 characters")
if lineage.relation_type == RelationType.ROOT:
if lineage.root_task_id != lineage.task_id:
raise LineageError("root_task_id must equal task_id for ROOT")
if lineage.parent_task_id is not None:
raise LineageError("ROOT parent_task_id must be null")
if lineage.lineage_depth != 0:
raise LineageError("ROOT lineage_depth must be 0")
if lineage.sibling_sequence != 0:
raise LineageError("ROOT sibling_sequence must be 0")
if lineage.parent_revision is not None:
raise LineageError("ROOT parent_revision must be null")
return
if not lineage.root_task_id or not lineage.parent_task_id:
raise LineageError("non-root root_task_id and parent_task_id are required")
if lineage.lineage_depth < 1:
raise LineageError("non-root lineage_depth must be >= 1")
if lineage.parent_revision is None or lineage.parent_revision < 0:
raise LineageError("non-root parent_revision must be >= 0")
_relation_token(lineage.relation_type, lineage.sibling_sequence)
def attach_legacy_lineage(
*,
existing_task_id: str,
created_at: datetime,
parent: TaskRecord,
root: TaskRecord,
relation_type: RelationType,
sibling_sequence: int,
goal_slug: str,
) -> TaskLineage:
"""Return lineage metadata for an existing durable task without changing its ID."""
return make_child_lineage(
task_id=existing_task_id,
created_at=created_at,
parent=parent,
root=root,
relation_type=relation_type,
sibling_sequence=sibling_sequence,
goal_slug=goal_slug,
)
def rename_display_only(lineage: TaskLineage, new_display_name: str) -> TaskLineage:
if not new_display_name or len(new_display_name) > 200:
raise LineageError("display name must be non-empty and <= 200 characters")
return replace(lineage, display_name=new_display_name)
LINEAGE_SCHEMA = "aios.task-lineage.v1"
ROOT_REVIEW_SCHEMA = "aios.root-review.v1"
_LINEAGE_FIELDS = frozenset(
{
"schema",
"task_id",
"display_name",
"owner_principal_id",
"owner_alias",
"created_at",
"mission_id",
"root_task_id",
"parent_task_id",
"lineage_depth",
"relation_type",
"sibling_sequence",
"goal_slug",
"root_acceptance_ref",
"parent_revision",
}
)
_ROOT_REVIEW_FIELDS = frozenset(
{
"schema",
"review_id",
"root_task_id",
"reviewed_at",
"trigger_child_task_id",
"root_revision_before",
"root_revision_after",
"completed_children",
"active_children",
"blocked_children",
"remaining_acceptance_conditions",
"next_legal_action",
"root_complete",
"evidence_refs",
}
)
def _require_exact_fields(metadata: Mapping[str, object], expected: frozenset[str]) -> None:
if not all(isinstance(key, str) for key in metadata):
raise LineageError("metadata field names must be strings")
actual = frozenset(metadata)
if actual != expected:
missing = sorted(expected - actual)
extra = sorted(actual - expected)
raise LineageError(f"metadata fields mismatch: missing={missing} extra={extra}")
def _require_string(metadata: Mapping[str, object], key: str) -> str:
value = metadata[key]
if not isinstance(value, str) or not value:
raise LineageError(f"{key} must be a non-empty string")
return value
def _require_int(metadata: Mapping[str, object], key: str) -> int:
value = metadata[key]
if isinstance(value, bool) or not isinstance(value, int):
raise LineageError(f"{key} must be an integer")
return value
def _require_string_list(metadata: Mapping[str, object], key: str) -> tuple[str, ...]:
value = metadata[key]
if not isinstance(value, list) or not all(isinstance(item, str) and item for item in value):
raise LineageError(f"{key} must be a list of non-empty strings")
items = tuple(value)
if len(items) != len(set(items)):
raise LineageError(f"{key} must not contain duplicates")
return items
def lineage_to_metadata(lineage: TaskLineage) -> dict[str, object]:
return {
"schema": LINEAGE_SCHEMA,
"task_id": lineage.task_id,
"display_name": lineage.display_name,
"owner_principal_id": lineage.owner_principal_id,
"owner_alias": lineage.owner_alias,
"created_at": _require_utc(lineage.created_at).isoformat().replace("+00:00", "Z"),
"mission_id": lineage.mission_id,
"root_task_id": lineage.root_task_id,
"parent_task_id": lineage.parent_task_id,
"lineage_depth": lineage.lineage_depth,
"relation_type": lineage.relation_type.value,
"sibling_sequence": lineage.sibling_sequence,
"goal_slug": lineage.goal_slug,
"root_acceptance_ref": lineage.root_acceptance_ref,
"parent_revision": lineage.parent_revision,
}
def lineage_from_metadata(metadata: Mapping[str, object]) -> TaskLineage:
_require_exact_fields(metadata, _LINEAGE_FIELDS)
if metadata.get("schema") != LINEAGE_SCHEMA:
raise LineageError("unsupported lineage metadata schema")
try:
created_raw = _require_string(metadata, "created_at")
if created_raw.endswith("Z"):
created_raw = created_raw[:-1] + "+00:00"
parent_task_id = metadata["parent_task_id"]
if parent_task_id is not None and (
not isinstance(parent_task_id, str) or not parent_task_id
):
raise LineageError("parent_task_id must be null or a non-empty string")
parent_revision = metadata["parent_revision"]
if parent_revision is not None and (
isinstance(parent_revision, bool) or not isinstance(parent_revision, int)
):
raise LineageError("parent_revision must be null or an integer")
lineage = TaskLineage(
task_id=_require_string(metadata, "task_id"),
display_name=_require_string(metadata, "display_name"),
owner_principal_id=_require_string(metadata, "owner_principal_id"),
owner_alias=_require_string(metadata, "owner_alias"),
created_at=datetime.fromisoformat(created_raw),
mission_id=_require_string(metadata, "mission_id"),
root_task_id=_require_string(metadata, "root_task_id"),
parent_task_id=parent_task_id,
lineage_depth=_require_int(metadata, "lineage_depth"),
relation_type=RelationType(_require_string(metadata, "relation_type")),
sibling_sequence=_require_int(metadata, "sibling_sequence"),
goal_slug=_require_string(metadata, "goal_slug"),
root_acceptance_ref=_require_string(metadata, "root_acceptance_ref"),
parent_revision=parent_revision,
)
except (KeyError, TypeError, ValueError, LineageError) as exc:
raise LineageError(f"invalid lineage metadata: {exc}") from exc
_validate_lineage_shape(lineage)
return lineage
def root_review_to_record(review: RootReviewRecord) -> dict[str, object]:
return {
"schema": ROOT_REVIEW_SCHEMA,
"review_id": review.review_id,
"root_task_id": review.root_task_id,
"reviewed_at": _require_utc(review.reviewed_at).isoformat().replace("+00:00", "Z"),
"trigger_child_task_id": review.trigger_child_task_id,
"root_revision_before": review.root_revision_before,
"root_revision_after": review.root_revision_after,
"completed_children": list(review.completed_children),
"active_children": list(review.active_children),
"blocked_children": list(review.blocked_children),
"remaining_acceptance_conditions": list(review.remaining_acceptance_conditions),
"next_legal_action": review.next_legal_action.value,
"root_complete": review.root_complete,
"evidence_refs": list(review.evidence_refs),
}
def root_review_from_record(
record: Mapping[str, object], *, expected_review_id: str | None = None
) -> RootReviewRecord:
_require_exact_fields(record, _ROOT_REVIEW_FIELDS)
if record.get("schema") != ROOT_REVIEW_SCHEMA:
raise LineageError("unsupported root-review metadata schema")
try:
reviewed_raw = _require_string(record, "reviewed_at")
if reviewed_raw.endswith("Z"):
reviewed_raw = reviewed_raw[:-1] + "+00:00"
root_complete = record["root_complete"]
if not isinstance(root_complete, bool):
raise LineageError("root_complete must be a boolean")
review = RootReviewRecord(
review_id=_require_string(record, "review_id"),
root_task_id=_require_string(record, "root_task_id"),
reviewed_at=_require_utc(datetime.fromisoformat(reviewed_raw)),
trigger_child_task_id=_require_string(record, "trigger_child_task_id"),
root_revision_before=_require_int(record, "root_revision_before"),
root_revision_after=_require_int(record, "root_revision_after"),
completed_children=_require_string_list(record, "completed_children"),
active_children=_require_string_list(record, "active_children"),
blocked_children=_require_string_list(record, "blocked_children"),
remaining_acceptance_conditions=_require_string_list(
record, "remaining_acceptance_conditions"
),
next_legal_action=NextLegalAction(
_require_string(record, "next_legal_action")
),
root_complete=root_complete,
evidence_refs=_require_string_list(record, "evidence_refs"),
)
except (KeyError, TypeError, ValueError, LineageError) as exc:
raise LineageError(f"invalid root-review metadata: {exc}") from exc
if expected_review_id is not None and review.review_id != expected_review_id:
raise LineageError("root-review id does not match lookup key")
if review.root_revision_before < 0 or (
review.root_revision_after != review.root_revision_before + 1
):
raise LineageError("root-review revision transition is invalid")
if review.root_complete != (
review.next_legal_action == NextLegalAction.ROOT_COMPLETE
):
raise LineageError("root_complete does not match next_legal_action")
child_sets = (
set(review.completed_children),
set(review.active_children),
set(review.blocked_children),
)
if any(child_sets[i] & child_sets[j] for i in range(3) for j in range(i + 1, 3)):
raise LineageError("root-review child classifications overlap")
return review
def _review_id(
root_task_id: str,
trigger_child_task_id: str,
trigger_revision: int,
acceptance: RootAcceptanceSnapshot,
) -> str:
acceptance_digest = sha256(
"\n".join(
f"{c.key}:{int(c.satisfied)}:{int(c.owner_boundary)}:"
f"{','.join(sorted(set(c.evidence_refs)))}"
for c in acceptance.conditions
).encode("utf-8")
).hexdigest()
payload = f"{root_task_id}|{trigger_child_task_id}|{trigger_revision}|{acceptance_digest}"
return "RR-" + sha256(payload.encode("utf-8")).hexdigest()[:24]
def _next_action(
descendants: Sequence[TaskRecord],
acceptance: RootAcceptanceSnapshot,
) -> NextLegalAction:
if acceptance.complete:
return NextLegalAction.ROOT_COMPLETE
if acceptance.owner_boundary_required:
return NextLegalAction.OWNER_BOUNDARY
if any(d.status in RECOVERY_STATUSES for d in descendants):
return NextLegalAction.RECOVER_EXISTING_CHILD
if any(d.status in BLOCKED_STATUSES for d in descendants):
return NextLegalAction.WAIT_DEPENDENCY
return NextLegalAction.CREATE_OR_RESUME_NEXT_CHILD
def review_root_after_child_terminal(
*,
store: RootReviewStore,
trigger_child_task_id: str,
acceptance_evaluator: AcceptanceEvaluator,
reviewed_at: datetime,
max_cas_retries: int = 4,
) -> RootReviewRecord:
"""Persist a root review after a proven child terminal transition.
This function is deliberately storage-agnostic. TaskPlane remains the
authority and supplies the CAS implementation. The function never creates a
new root or mutates a child identity.
"""
if max_cas_retries < 1:
raise LineageError("max_cas_retries must be >= 1")
reviewed_at = _require_utc(reviewed_at)
for _ in range(max_cas_retries):
child = store.get_task(trigger_child_task_id)
if child.lineage.relation_type == RelationType.ROOT:
raise LineageError("root review hook requires a child task")
if child.status not in TERMINAL_STATUSES:
raise LineageError("child must be terminal before root review")
if not child.terminal_evidence_proven:
raise LineageError("child terminal evidence is not proven")
root = store.get_task(child.lineage.root_task_id)
if root.lineage.relation_type != RelationType.ROOT:
raise LineageError("root_task_id does not resolve to a ROOT task")
if child.lineage.owner_principal_id != root.lineage.owner_principal_id:
raise LineageError("trigger child owner does not match root owner")
if child.lineage.mission_id != root.lineage.mission_id:
raise LineageError("trigger child mission does not match root mission")
if child.lineage.root_acceptance_ref != root.lineage.root_acceptance_ref:
raise LineageError("trigger child acceptance reference does not match root")
descendants = tuple(
sorted(store.list_descendants(root.task_id), key=lambda item: item.task_id)
)
descendant_ids = tuple(item.task_id for item in descendants)
if len(descendant_ids) != len(set(descendant_ids)):
raise LineageError("descendant authority returned duplicate task ids")
if child.task_id not in descendant_ids:
raise LineageError("trigger child missing from authoritative descendants")
for descendant in descendants:
if descendant.lineage.root_task_id != root.task_id:
raise LineageError("descendant is attached to a different root")
if descendant.lineage.owner_principal_id != root.lineage.owner_principal_id:
raise LineageError("descendant owner does not match root owner")
if descendant.lineage.mission_id != root.lineage.mission_id:
raise LineageError("descendant mission does not match root mission")
if (
descendant.lineage.root_acceptance_ref
!= root.lineage.root_acceptance_ref
):
raise LineageError("descendant acceptance reference does not match root")
acceptance = canonicalize_acceptance(acceptance_evaluator(root, descendants))
review_id = _review_id(root.task_id, child.task_id, child.revision, acceptance)
completed = tuple(
d.task_id for d in descendants
if d.status in TERMINAL_STATUSES and d.terminal_evidence_proven
)
active = tuple(
d.task_id for d in descendants
if d.status not in TERMINAL_STATUSES and d.status not in BLOCKED_STATUSES
)
blocked = tuple(
d.task_id for d in descendants
if d.status in BLOCKED_STATUSES
)
evidence = tuple(sorted(set(child.evidence_refs + acceptance.evidence_refs)))
action = _next_action(descendants, acceptance)
existing = store.get_root_review(review_id)
if existing is not None:
if (
existing.root_task_id != root.task_id
or existing.trigger_child_task_id != child.task_id
):
raise LineageError("persisted root review identity conflicts with trigger")
if existing.root_revision_after > root.revision:
raise LineageError("persisted root review revision is ahead of authority")
expected_semantics = (
completed,
active,
blocked,
acceptance.remaining,
action,
action == NextLegalAction.ROOT_COMPLETE,
evidence,
)
actual_semantics = (
existing.completed_children,
existing.active_children,
existing.blocked_children,
existing.remaining_acceptance_conditions,
existing.next_legal_action,
existing.root_complete,
existing.evidence_refs,
)
if actual_semantics != expected_semantics:
raise LineageError("persisted root review conflicts with current semantics")
return existing
review = RootReviewRecord(
review_id=review_id,
root_task_id=root.task_id,
reviewed_at=reviewed_at,
trigger_child_task_id=child.task_id,
root_revision_before=root.revision,
root_revision_after=root.revision + 1,
completed_children=completed,
active_children=active,
blocked_children=blocked,
remaining_acceptance_conditions=acceptance.remaining,
next_legal_action=action,
root_complete=(action == NextLegalAction.ROOT_COMPLETE),
evidence_refs=evidence,
)
if store.compare_and_swap_root_review(root.task_id, root.revision, review):
return review
# Another sibling may have updated root progress. Re-read authority and
# recompute; never overwrite a newer root review.
raise LineageError("root review CAS retry budget exhausted")
@@ -0,0 +1,285 @@
from __future__ import annotations
from dataclasses import dataclass, replace
from datetime import datetime
from typing import Mapping
if not __package__:
from aios_task_lineage import AcceptanceEvaluator, RelationType, TaskLineage, lineage_to_metadata, lineage_from_metadata, root_review_to_record
from aios_task_lineage_integration import (
DISPLAY_NAME_METADATA_KEY,
IntegrationError,
LINEAGE_METADATA_KEY,
NewTaskIdentityRequest,
PreparedTaskIdentity,
TaskIdentityLineageService,
TerminalReviewOutcome,
)
from aios_task_lineage_taskplane_adapter import (
AuthenticatedTaskContext,
InProcessTaskPlaneLineageAdapter,
require_context_matches_task,
)
else:
from .aios_task_lineage import AcceptanceEvaluator, RelationType, TaskLineage, lineage_to_metadata, lineage_from_metadata, root_review_to_record
from .aios_task_lineage_integration import (
DISPLAY_NAME_METADATA_KEY,
IntegrationError,
LINEAGE_METADATA_KEY,
NewTaskIdentityRequest,
PreparedTaskIdentity,
TaskIdentityLineageService,
TerminalReviewOutcome,
)
from .aios_task_lineage_taskplane_adapter import (
AuthenticatedTaskContext,
InProcessTaskPlaneLineageAdapter,
require_context_matches_task,
)
class LifecycleHookError(IntegrationError):
pass
@dataclass(frozen=True)
class IdentityPersistenceOutcome:
prepared: PreparedTaskIdentity
persisted_revision_at_least: int
independently_verified: bool
@dataclass(frozen=True)
class LegacyAttachOutcome:
lineage: TaskLineage
independently_verified: bool
@dataclass(frozen=True)
class TerminalHookOutcome:
outcome: TerminalReviewOutcome
root_review_persisted: bool
dispatch_persisted: bool
independently_verified: bool
class TaskLineageLifecycleHooks:
"""Concrete lifecycle hook coordinator.
It does not own TaskPlane state. It orchestrates existing TaskPlane CAS,
journal/continuation and optional independent authority readback.
"""
def __init__(self, adapter: InProcessTaskPlaneLineageAdapter):
self._adapter = adapter
self._service = TaskIdentityLineageService(adapter)
def persist_new_task_identity(
self,
*,
request: NewTaskIdentityRequest,
context: AuthenticatedTaskContext,
max_cas_retries: int = 4,
require_independent_readback: bool = True,
) -> IdentityPersistenceOutcome:
if max_cas_retries < 1:
raise LifecycleHookError("max_cas_retries must be >= 1")
if require_independent_readback and not self._adapter.has_independent_readback:
raise LifecycleHookError("independent TaskPlane readback is not configured")
if request.owner_principal_id != context.principal_id:
raise LifecycleHookError("request principal does not match authenticated context")
if request.mission_id != context.mission_id:
raise LifecycleHookError("request mission does not match authenticated context")
for _ in range(max_cas_retries):
current = self._adapter.get_task_snapshot(request.task_id)
require_context_matches_task(context, current)
if current.created_at != request.created_at:
raise LifecycleHookError("request created_at does not match authority")
if (request.relation_type == RelationType.ROOT
and request.preferred_owner_alias not in (None, context.preferred_alias)):
raise LifecycleHookError("request alias does not match authenticated context")
request = replace(request, preferred_owner_alias=context.preferred_alias)
prepared = self._service.prepare_new_task_identity(request)
# Idempotent replay and legacy display-name repair are fail-closed:
# never overwrite an existing conflicting lineage record.
raw = current.metadata.get(LINEAGE_METADATA_KEY)
display = current.metadata.get(DISPLAY_NAME_METADATA_KEY)
if raw is not None and not isinstance(raw, Mapping):
raise LifecycleHookError("existing lineage metadata is malformed")
if isinstance(raw, Mapping):
# parent_revision records the creation observation, not the
# parent's current progress revision on an idempotent replay.
expected = dict(prepared.metadata_patch[LINEAGE_METADATA_KEY])
if request.parent_task_id is not None:
stored = lineage_from_metadata(dict(raw))
if (stored.parent_revision is None
or stored.parent_revision > prepared.lineage.parent_revision):
raise LifecycleHookError("existing parent revision conflicts with authority")
expected["parent_revision"] = stored.parent_revision
if raw != expected:
raise LifecycleHookError(
"existing lineage metadata conflicts with requested identity"
)
prepared = replace(prepared, lineage=lineage_from_metadata(dict(raw)),
metadata_patch={LINEAGE_METADATA_KEY: dict(raw),
DISPLAY_NAME_METADATA_KEY: prepared.display_name})
if display == prepared.display_name:
verified = False
if require_independent_readback:
self._adapter.verify_task_readback(
task_id=request.task_id,
expected_revision_at_least=current.revision,
expected_metadata=prepared.metadata_patch,
)
verified = True
return IdentityPersistenceOutcome(
prepared=prepared,
persisted_revision_at_least=current.revision,
independently_verified=verified,
)
if display not in (None, ""):
raise LifecycleHookError(
"existing display name conflicts with requested identity"
)
metadata_patch = {
DISPLAY_NAME_METADATA_KEY: prepared.display_name,
}
else:
if display not in (None, ""):
raise LifecycleHookError(
"display name exists without authoritative lineage"
)
metadata_patch = prepared.metadata_patch
ok = self._adapter.compare_and_swap_task_metadata(
task_id=request.task_id,
expected_revision=current.revision,
metadata_patch=metadata_patch,
)
if not ok:
continue
verified = False
if require_independent_readback:
self._adapter.verify_task_readback(
task_id=request.task_id,
expected_revision_at_least=current.revision + 1,
expected_metadata=prepared.metadata_patch,
)
verified = True
return IdentityPersistenceOutcome(
prepared=prepared,
persisted_revision_at_least=current.revision + 1,
independently_verified=verified,
)
raise LifecycleHookError("identity persistence CAS retry budget exhausted")
def attach_legacy_identity(
self,
*,
task_id: str,
parent_task_id: str,
relation_type: RelationType,
goal_slug: str,
context: AuthenticatedTaskContext,
max_cas_retries: int = 4,
require_independent_readback: bool = True,
) -> LegacyAttachOutcome:
if require_independent_readback and not self._adapter.has_independent_readback:
raise LifecycleHookError("independent TaskPlane readback is not configured")
before = self._adapter.get_task_snapshot(task_id)
require_context_matches_task(context, before)
lineage = self._service.attach_legacy_task(
task_id=task_id,
parent_task_id=parent_task_id,
relation_type=relation_type,
goal_slug=goal_slug,
max_cas_retries=max_cas_retries,
expected_owner_principal_id=context.principal_id,
expected_mission_id=context.mission_id,
)
verified = False
if require_independent_readback:
self._adapter.verify_task_readback(
task_id=task_id,
expected_revision_at_least=before.revision,
expected_metadata={
LINEAGE_METADATA_KEY: lineage_to_metadata(lineage),
DISPLAY_NAME_METADATA_KEY: lineage.display_name,
},
)
# Domain serialization compatibility check below avoids requiring
# TaskLineage to expose a method in older versions.
verified = True
return LegacyAttachOutcome(lineage=lineage, independently_verified=verified)
def on_proven_terminal_child(
self,
*,
child_task_id: str,
context: AuthenticatedTaskContext,
acceptance_evaluator: AcceptanceEvaluator,
reviewed_at: datetime,
max_cas_retries: int = 4,
require_independent_readback: bool = True,
) -> TerminalHookOutcome:
if require_independent_readback and not self._adapter.has_independent_readback:
raise LifecycleHookError("independent TaskPlane readback is not configured")
child = self._adapter.get_task_snapshot(child_task_id)
require_context_matches_task(context, child)
if not child.terminal_evidence_proven:
raise LifecycleHookError(
"terminal child hook requires proven terminal evidence"
)
raw = child.metadata.get(LINEAGE_METADATA_KEY)
if not isinstance(raw, Mapping):
raise LifecycleHookError("terminal child lineage missing")
lineage = lineage_from_metadata(dict(raw))
root = self._adapter.get_task_snapshot(lineage.root_task_id)
require_context_matches_task(context, root)
def authorized_acceptance(root_record, descendants):
if (root_record.lineage.owner_principal_id != context.principal_id
or root_record.lineage.mission_id != context.mission_id):
raise LifecycleHookError("root authority changed during terminal review")
return acceptance_evaluator(root_record, descendants)
outcome = self._service.on_proven_terminal_child(
child_task_id=child_task_id,
acceptance_evaluator=authorized_acceptance,
reviewed_at=reviewed_at,
max_cas_retries=max_cas_retries,
)
persisted_review = self._adapter.get_root_review_record(
outcome.review.review_id
)
if persisted_review is None:
raise LifecycleHookError("root review was not persisted")
if persisted_review != root_review_to_record(outcome.review):
raise LifecycleHookError("persisted review does not match returned review")
dispatch = self._adapter.get_root_action_dispatch(outcome.review.review_id)
if dispatch is None:
raise LifecycleHookError("root action dispatch was not persisted")
if dispatch != outcome.dispatch:
raise LifecycleHookError("persisted dispatch does not match returned receipt")
verified = False
if require_independent_readback:
self._adapter.verify_root_readback(
root_task_id=outcome.review.root_task_id,
expected_review_id=outcome.review.review_id,
)
verified = True
return TerminalHookOutcome(
outcome=outcome,
root_review_persisted=True,
dispatch_persisted=True,
independently_verified=verified,
)
@@ -0,0 +1,539 @@
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime
from enum import Enum
from typing import Mapping, MutableMapping, Protocol, Sequence
if not __package__:
from aios_task_lineage import (
AcceptanceEvaluator,
LineageError,
NextLegalAction,
RelationType,
RootReviewRecord,
RootReviewStore,
TaskLineage,
TaskRecord,
attach_legacy_lineage,
derive_safe_owner_alias,
lineage_from_metadata,
lineage_to_metadata,
make_child_lineage,
make_root_lineage,
normalize_goal_slug,
review_root_after_child_terminal,
root_review_from_record,
root_review_to_record,
)
else:
from .aios_task_lineage import (
AcceptanceEvaluator,
LineageError,
NextLegalAction,
RelationType,
RootReviewRecord,
RootReviewStore,
TaskLineage,
TaskRecord,
attach_legacy_lineage,
derive_safe_owner_alias,
lineage_from_metadata,
lineage_to_metadata,
make_child_lineage,
make_root_lineage,
normalize_goal_slug,
review_root_after_child_terminal,
root_review_from_record,
root_review_to_record,
)
LINEAGE_METADATA_KEY = "aios_task_lineage"
LINEAGE_REQUEST_DIGEST_KEY = "aios_lineage_request_digest"
ROOT_REVIEW_RECORD_KEY = "aios_root_review"
DISPLAY_NAME_METADATA_KEY = "aios_task_display_name"
class IntegrationError(RuntimeError):
pass
@dataclass(frozen=True)
class TaskAuthoritySnapshot:
task_id: str
revision: int
status: str
owner_principal_id: str
mission_id: str
created_at: datetime
metadata: Mapping[str, object]
evidence_refs: tuple[str, ...] = ()
terminal_evidence_proven: bool = False
@dataclass(frozen=True)
class NewTaskIdentityRequest:
task_id: str
owner_principal_id: str
preferred_owner_alias: str | None
created_at: datetime
mission_id: str
goal_slug: str
root_acceptance_ref: str
relation_type: RelationType = RelationType.ROOT
parent_task_id: str | None = None
@dataclass(frozen=True)
class PreparedTaskIdentity:
task_id: str
display_name: str
lineage: TaskLineage
metadata_patch: Mapping[str, object]
@dataclass(frozen=True)
class TerminalReviewOutcome:
review: RootReviewRecord
dispatch: "RootActionDispatchReceipt"
@dataclass(frozen=True)
class RootActionDispatchReceipt:
review_id: str
root_task_id: str
action: NextLegalAction
idempotency_key: str
status: str
mission_id: str = ""
root_revision: int = 0
plan_digest: str | None = None
execution_key: str | None = None
PERSISTED_DISPATCH_STATUSES = frozenset(
{
"PENDING",
"CLAIMED",
"EFFECT_UNKNOWN",
"EXECUTED_PENDING_REREVIEW",
"WAITING_DEPENDENCY",
"WAITING_OWNER",
"CLOSED",
"SUPERSEDED",
"BLOCKED",
# Read compatibility only. A consumer must reconcile this legacy
# state unless a complete durable receipt is present.
"DISPATCHED",
}
)
class TaskPlaneLineagePort(Protocol):
"""Narrow authority port required by the lineage integration service.
The live adapter must implement these methods over the existing TaskPlane /
mission / journal / CAS mechanisms. This protocol deliberately avoids
guessing concrete DB tables, HTTP endpoints, or handler names.
"""
def get_task_snapshot(self, task_id: str) -> TaskAuthoritySnapshot: ...
def list_descendant_snapshots(
self, root_task_id: str
) -> Sequence[TaskAuthoritySnapshot]: ...
def reserve_sibling_sequence(
self,
*,
parent_task_id: str,
relation_type: RelationType,
child_task_id: str,
) -> int:
"""Idempotently reserve one sibling sequence for child_task_id."""
def compare_and_swap_task_metadata(
self,
*,
task_id: str,
expected_revision: int,
metadata_patch: Mapping[str, object],
) -> bool: ...
def get_root_review_record(
self, review_id: str
) -> Mapping[str, object] | None: ...
def compare_and_swap_root_review_record(
self,
*,
root_task_id: str,
expected_root_revision: int,
review_record: Mapping[str, object],
) -> bool: ...
def get_root_action_dispatch(
self, review_id: str
) -> RootActionDispatchReceipt | None: ...
def dispatch_root_action(
self,
*,
review: RootReviewRecord,
idempotency_key: str,
) -> RootActionDispatchReceipt: ...
def _extract_lineage(snapshot: TaskAuthoritySnapshot) -> TaskLineage:
raw = snapshot.metadata.get(LINEAGE_METADATA_KEY)
if not isinstance(raw, dict):
raise IntegrationError(
f"task {snapshot.task_id} does not have authoritative lineage metadata"
)
lineage = lineage_from_metadata(raw)
if lineage.task_id != snapshot.task_id:
raise IntegrationError("lineage task_id does not match authority task")
if lineage.owner_principal_id != snapshot.owner_principal_id:
raise IntegrationError("lineage owner does not match authority owner")
if lineage.mission_id != snapshot.mission_id:
raise IntegrationError("lineage mission does not match authority mission")
if snapshot.created_at.tzinfo is None or snapshot.created_at.utcoffset() is None:
raise IntegrationError("authority created_at must be timezone-aware")
if lineage.created_at != snapshot.created_at:
raise IntegrationError("lineage created_at does not match authority task")
display = snapshot.metadata.get(DISPLAY_NAME_METADATA_KEY)
if display not in (None, "", lineage.display_name):
raise IntegrationError("lineage display name conflicts with authority metadata")
return lineage
def _as_task_record(snapshot: TaskAuthoritySnapshot) -> TaskRecord:
return TaskRecord(
task_id=snapshot.task_id,
revision=snapshot.revision,
status=snapshot.status,
lineage=_extract_lineage(snapshot),
evidence_refs=snapshot.evidence_refs,
terminal_evidence_proven=snapshot.terminal_evidence_proven,
)
def _validate_record_root_binding(record: TaskRecord, root: TaskRecord) -> None:
if root.lineage.relation_type != RelationType.ROOT:
raise IntegrationError("root_task_id does not resolve to a ROOT task")
if record.lineage.root_task_id != root.task_id:
raise IntegrationError("task lineage points to a different root")
if record.lineage.owner_principal_id != root.lineage.owner_principal_id:
raise IntegrationError("task lineage owner does not match root owner")
if record.lineage.mission_id != root.lineage.mission_id:
raise IntegrationError("task lineage mission does not match root mission")
if record.lineage.root_acceptance_ref != root.lineage.root_acceptance_ref:
raise IntegrationError("task lineage acceptance reference does not match root")
def _load_authority_record(
port: TaskPlaneLineagePort, task_id: str
) -> tuple[TaskRecord, TaskRecord]:
record = _as_task_record(port.get_task_snapshot(task_id))
if record.lineage.relation_type == RelationType.ROOT:
return record, record
root = _as_task_record(port.get_task_snapshot(record.lineage.root_task_id))
current = record
seen = {record.task_id}
while current.lineage.relation_type != RelationType.ROOT:
_validate_record_root_binding(current, root)
parent_id = current.lineage.parent_task_id
if not parent_id or parent_id in seen:
raise IntegrationError("task lineage contains a parent cycle")
seen.add(parent_id)
parent = _as_task_record(port.get_task_snapshot(parent_id))
_validate_record_root_binding(parent, root)
if current.lineage.parent_task_id != parent.task_id:
raise IntegrationError("task lineage parent does not match authority parent")
if current.lineage.lineage_depth != parent.lineage.lineage_depth + 1:
raise IntegrationError("task lineage depth does not match authority parent")
if current.lineage.parent_revision is None or (
current.lineage.parent_revision > parent.revision
):
raise IntegrationError("task lineage parent revision is not authoritative")
current = parent
if current.task_id != root.task_id:
raise IntegrationError("task lineage parent chain ends at a different root")
return record, root
def _validate_dispatch_receipt(
receipt: RootActionDispatchReceipt,
*,
review: RootReviewRecord,
idempotency_key: str,
existing: bool,
) -> None:
if (
receipt.review_id != review.review_id
or receipt.root_task_id != review.root_task_id
or receipt.action != review.next_legal_action
or receipt.idempotency_key != idempotency_key
or receipt.status not in PERSISTED_DISPATCH_STATUSES
):
noun = "existing root-action dispatch" if existing else "root-action dispatch receipt"
raise IntegrationError(f"{noun} conflicts with persisted review")
class _RootReviewStoreAdapter(RootReviewStore):
def __init__(self, port: TaskPlaneLineagePort):
self._port = port
def get_task(self, task_id: str) -> TaskRecord:
record, _ = _load_authority_record(self._port, task_id)
return record
def list_descendants(self, root_task_id: str) -> Sequence[TaskRecord]:
root, _ = _load_authority_record(self._port, root_task_id)
if root.lineage.relation_type != RelationType.ROOT:
raise IntegrationError("descendant lookup key is not a ROOT task")
records: list[TaskRecord] = []
for snapshot in self._port.list_descendant_snapshots(root_task_id):
record, resolved_root = _load_authority_record(self._port, snapshot.task_id)
if resolved_root.task_id != root_task_id:
raise IntegrationError("descendant authority returned a cross-root task")
_validate_record_root_binding(record, root)
records.append(record)
return tuple(records)
def get_root_review(self, review_id: str) -> RootReviewRecord | None:
raw = self._port.get_root_review_record(review_id)
if raw is None:
return None
if not isinstance(raw, Mapping):
raise IntegrationError("persisted root-review record must be a mapping")
try:
return root_review_from_record(
raw,
expected_review_id=review_id,
)
except LineageError as exc:
raise IntegrationError(
f"invalid persisted root-review record: {exc}"
) from exc
def compare_and_swap_root_review(
self,
root_task_id: str,
expected_root_revision: int,
review: RootReviewRecord,
) -> bool:
return self._port.compare_and_swap_root_review_record(
root_task_id=root_task_id,
expected_root_revision=expected_root_revision,
review_record=root_review_to_record(review),
)
class TaskIdentityLineageService:
"""Application service that wires the lineage domain to TaskPlane authority."""
def __init__(self, port: TaskPlaneLineagePort):
self._port = port
def prepare_new_task_identity(
self, request: NewTaskIdentityRequest
) -> PreparedTaskIdentity:
alias = derive_safe_owner_alias(
request.owner_principal_id, request.preferred_owner_alias
)
if request.relation_type == RelationType.ROOT:
if request.parent_task_id is not None:
raise IntegrationError("ROOT task cannot have parent_task_id")
lineage = make_root_lineage(
task_id=request.task_id,
owner_principal_id=request.owner_principal_id,
owner_alias=alias,
created_at=request.created_at,
mission_id=request.mission_id,
goal_slug=request.goal_slug,
root_acceptance_ref=request.root_acceptance_ref,
)
else:
if not request.parent_task_id:
raise IntegrationError("non-root task requires parent_task_id")
parent, root = _load_authority_record(
self._port, request.parent_task_id
)
if request.owner_principal_id != root.lineage.owner_principal_id:
raise IntegrationError("cross-owner child creation rejected")
if request.mission_id != root.lineage.mission_id:
raise IntegrationError("cross-mission child creation rejected")
if request.root_acceptance_ref != root.lineage.root_acceptance_ref:
raise IntegrationError("root acceptance reference mismatch")
sequence = self._port.reserve_sibling_sequence(
parent_task_id=parent.task_id,
relation_type=request.relation_type,
child_task_id=request.task_id,
)
lineage = make_child_lineage(
task_id=request.task_id,
created_at=request.created_at,
parent=parent,
root=root,
relation_type=request.relation_type,
sibling_sequence=sequence,
goal_slug=request.goal_slug,
)
return PreparedTaskIdentity(
task_id=request.task_id,
display_name=lineage.display_name,
lineage=lineage,
metadata_patch={
LINEAGE_METADATA_KEY: lineage_to_metadata(lineage),
DISPLAY_NAME_METADATA_KEY: lineage.display_name,
},
)
def attach_legacy_task(
self,
*,
task_id: str,
parent_task_id: str,
relation_type: RelationType,
goal_slug: str,
expected_owner_principal_id: str | None = None,
expected_mission_id: str | None = None,
max_cas_retries: int = 4,
) -> TaskLineage:
if max_cas_retries < 1:
raise IntegrationError("max_cas_retries must be >= 1")
for _ in range(max_cas_retries):
current = self._port.get_task_snapshot(task_id)
if (
expected_owner_principal_id is not None
and current.owner_principal_id != expected_owner_principal_id
):
raise IntegrationError("legacy task owner changed after authorization")
if (
expected_mission_id is not None
and current.mission_id != expected_mission_id
):
raise IntegrationError("legacy task mission changed after authorization")
raw = current.metadata.get(LINEAGE_METADATA_KEY)
if isinstance(raw, Mapping):
record, _ = _load_authority_record(self._port, task_id)
lineage = record.lineage
expected_goal_slug = normalize_goal_slug(goal_slug)
if (
lineage.parent_task_id != parent_task_id
or lineage.relation_type != relation_type
or lineage.goal_slug != expected_goal_slug
):
raise IntegrationError(
"existing legacy lineage conflicts with requested binding"
)
display = current.metadata.get(DISPLAY_NAME_METADATA_KEY)
if display == lineage.display_name:
return lineage
if display not in (None, ""):
raise IntegrationError(
"legacy display name conflicts with authoritative lineage"
)
if self._port.compare_and_swap_task_metadata(
task_id=current.task_id,
expected_revision=current.revision,
metadata_patch={
DISPLAY_NAME_METADATA_KEY: lineage.display_name,
},
):
return lineage
continue
parent, root = _load_authority_record(self._port, parent_task_id)
if root.status == "COMPLETED":
raise IntegrationError("cannot attach lineage below a completed root")
if (
expected_owner_principal_id is not None
and root.lineage.owner_principal_id != expected_owner_principal_id
):
raise IntegrationError("legacy root owner does not match authorization")
if (
expected_mission_id is not None
and root.lineage.mission_id != expected_mission_id
):
raise IntegrationError("legacy root mission does not match authorization")
if current.owner_principal_id != root.lineage.owner_principal_id:
raise IntegrationError("legacy task owner does not match root owner")
if current.mission_id != root.lineage.mission_id:
raise IntegrationError("legacy task mission does not match root mission")
sequence = self._port.reserve_sibling_sequence(
parent_task_id=parent.task_id,
relation_type=relation_type,
child_task_id=current.task_id,
)
lineage = attach_legacy_lineage(
existing_task_id=current.task_id,
created_at=current.created_at,
parent=parent,
root=root,
relation_type=relation_type,
sibling_sequence=sequence,
goal_slug=goal_slug,
)
if self._port.compare_and_swap_task_metadata(
task_id=current.task_id,
expected_revision=current.revision,
metadata_patch={
LINEAGE_METADATA_KEY: lineage_to_metadata(lineage),
DISPLAY_NAME_METADATA_KEY: lineage.display_name,
},
):
return lineage
raise IntegrationError("legacy lineage CAS retry budget exhausted")
def on_proven_terminal_child(
self,
*,
child_task_id: str,
acceptance_evaluator: AcceptanceEvaluator,
reviewed_at: datetime,
max_cas_retries: int = 4,
) -> TerminalReviewOutcome:
store = _RootReviewStoreAdapter(self._port)
review = review_root_after_child_terminal(
store=store,
trigger_child_task_id=child_task_id,
acceptance_evaluator=acceptance_evaluator,
reviewed_at=reviewed_at,
max_cas_retries=max_cas_retries,
)
existing = self._port.get_root_action_dispatch(review.review_id)
if existing is not None:
idempotency_key = (
f"root-review:{review.review_id}:{review.next_legal_action.value}"
)
_validate_dispatch_receipt(
existing,
review=review,
idempotency_key=idempotency_key,
existing=True,
)
return TerminalReviewOutcome(review=review, dispatch=existing)
idempotency_key = f"root-review:{review.review_id}:{review.next_legal_action.value}"
receipt = self._port.dispatch_root_action(
review=review,
idempotency_key=idempotency_key,
)
_validate_dispatch_receipt(
receipt,
review=review,
idempotency_key=idempotency_key,
existing=False,
)
return TerminalReviewOutcome(review=review, dispatch=receipt)
@@ -0,0 +1,367 @@
"""One-time, Owner-signed, append-only supersession for one exact Task lineage."""
from __future__ import annotations
from contextlib import closing
from datetime import datetime, timedelta, timezone
import hashlib
import json
from pathlib import Path
import re
import sqlite3
from typing import Any, Callable, Mapping, Protocol
DECISION_SCHEMA = "aios.task-lineage-supersession-decision.v1"
RECEIPT_SCHEMA = "aios.task-lineage-supersession-receipt.v1"
OWNER_NAMESPACE = "aios-owner-approval-v1"
OWNER_PRINCIPAL = "aios-task-grant-signer"
OWNER_KEY_ID = "SHA256:rPqpxfPHtaZPM3zSWojX8Q7VxzCknsYqOokyo/aAM3A"
SOURCE_TASK_ID = "TASK-SINGLEHOST-TRUSTROOT-P0-20260904"
CHILD_TASK_ID = SOURCE_TASK_ID + "-S1"
REPOSITORY = "aios/aios"
TARGET_BRANCH = "agent/single-host-trust-root-p0-20260904"
GENERATION = 1
EXECUTION_DIGEST = "e99bda6a251832eb3865271eb5687af7eea44dd261144cdcdfa57104c0780536"
EXTERNAL_JOB_ID = "job-4e10f92a6a5742518a9f1d8745c845ab"
SUPERSESSION_ORDINAL = 1
CANONICAL_PROFILE = "aios.task-initial-execution.v1"
CANONICAL_PROFILE_VERSION = 1
SUPERSESSION_REASON = "formal-lineage-supersession-after-expired-unclaimed-source"
MAX_TTL = timedelta(minutes=5)
PARAMETERIZED_RECOVERY_NONCE_SCOPE = "aios.task-parameterized-recovery.v1:owner-nonce"
HEX64 = re.compile(r"^[0-9a-f]{64}$")
SAFE_ID = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:@/+:-]{2,511}$")
class TaskLineageSupersessionError(RuntimeError):
def __init__(self, code: str, http_status: int = 409):
super().__init__(code)
self.code = code
self.http_status = int(http_status)
class SignatureVerifier(Protocol):
def verify(self, *, namespace: str, payload: bytes, signature: str, key_id: str) -> bool: ...
def canonical_json(value: Any) -> str:
return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"), allow_nan=False)
def sha256_text(value: str) -> str:
return hashlib.sha256(value.encode("utf-8")).hexdigest()
def _fail(code: str, status: int = 409) -> None:
raise TaskLineageSupersessionError(code, status)
def _utc(value: Any, field: str) -> datetime:
if not isinstance(value, str) or not value or len(value) > 80:
_fail(field.upper() + "_INVALID", 400)
try:
result = datetime.fromisoformat(value.replace("Z", "+00:00"))
except ValueError as exc:
raise TaskLineageSupersessionError(field.upper() + "_INVALID", 400) from exc
if result.tzinfo is None:
_fail(field.upper() + "_INVALID", 400)
return result.astimezone(timezone.utc)
def _text(value: Any, field: str, *, maximum: int = 1024) -> str:
if not isinstance(value, str) or not value or len(value) > maximum:
_fail(field.upper() + "_INVALID", 400)
return value
def decision_payload(raw: Mapping[str, Any]) -> dict[str, Any]:
return {key: raw[key] for key in raw if key != "signature"}
DECISION_FIELDS = {
"schema", "decision_id", "nonce", "source_task_id",
"source_task_identity_sha256", "source_metadata_sha256",
"source_expected_status", "source_expected_revision", "source_expected_attempt",
"source_expected_claimed_by", "source_expected_lease_token_hash",
"source_expected_lease_expires_at", "repository", "target_branch", "generation",
"execution_digest", "external_job_id", "child_task_id", "supersession_ordinal",
"canonical_projection_profile", "canonical_projection_version", "supersession_reason",
"owner_identity", "signature_namespace", "signature_principal", "key_id",
"issued_at", "not_before", "expires_at", "one_time_use", "signature",
}
def validate_decision(raw: Mapping[str, Any], *, now: datetime) -> dict[str, Any]:
if not isinstance(raw, Mapping) or set(raw) != DECISION_FIELDS:
_fail("DECISION_FIELDS_INVALID", 400)
exact = {
"schema": DECISION_SCHEMA,
"source_task_id": SOURCE_TASK_ID,
"source_expected_status": "EXPIRED",
"source_expected_attempt": 0,
"source_expected_claimed_by": None,
"source_expected_lease_token_hash": None,
"source_expected_lease_expires_at": None,
"repository": REPOSITORY,
"target_branch": TARGET_BRANCH,
"generation": GENERATION,
"execution_digest": EXECUTION_DIGEST,
"external_job_id": EXTERNAL_JOB_ID,
"child_task_id": CHILD_TASK_ID,
"supersession_ordinal": SUPERSESSION_ORDINAL,
"canonical_projection_profile": CANONICAL_PROFILE,
"canonical_projection_version": CANONICAL_PROFILE_VERSION,
"supersession_reason": SUPERSESSION_REASON,
"owner_identity": OWNER_PRINCIPAL,
"signature_namespace": OWNER_NAMESPACE,
"signature_principal": OWNER_PRINCIPAL,
"key_id": OWNER_KEY_ID,
"one_time_use": True,
}
for field, expected in exact.items():
if raw[field] != expected:
_fail(field.upper() + "_MISMATCH", 403 if field.startswith(("owner", "signature", "key")) else 409)
for field in ("decision_id", "nonce"):
value = _text(raw[field], field, maximum=256)
if SAFE_ID.fullmatch(value) is None:
_fail(field.upper() + "_INVALID", 400)
for field in ("source_task_identity_sha256", "source_metadata_sha256"):
if not isinstance(raw[field], str) or HEX64.fullmatch(raw[field]) is None:
_fail(field.upper() + "_INVALID", 400)
if type(raw["source_expected_revision"]) is not int or raw["source_expected_revision"] < 1:
_fail("SOURCE_EXPECTED_REVISION_INVALID", 400)
_text(raw["signature"], "signature", maximum=32768)
issued_at = _utc(raw["issued_at"], "issued_at")
not_before = _utc(raw["not_before"], "not_before")
expires_at = _utc(raw["expires_at"], "expires_at")
if issued_at > expires_at or not_before > expires_at:
_fail("DECISION_TIME_ORDER_INVALID", 403)
if expires_at - issued_at > MAX_TTL or expires_at <= issued_at:
_fail("DECISION_TTL_INVALID", 403)
now = now.astimezone(timezone.utc)
if now < not_before or now >= expires_at:
_fail("DECISION_EXPIRED_OR_NOT_YET_VALID", 403)
if issued_at > now + timedelta(seconds=5) or now - issued_at > MAX_TTL:
_fail("DECISION_ISSUED_AT_INVALID", 403)
return {**dict(raw), "issued_at_dt": issued_at, "not_before_dt": not_before, "expires_at_dt": expires_at}
def _default_identity_digest(row: sqlite3.Row) -> str:
try:
from aios_expired_task_recovery import task_identity_digest
except ImportError as exc:
raise TaskLineageSupersessionError("FORMAL_TASK_IDENTITY_HELPER_UNAVAILABLE", 503) from exc
return str(task_identity_digest(row))
def _json_object(row: sqlite3.Row, field: str) -> dict[str, Any]:
try:
value = json.loads(row[field])
except (TypeError, ValueError, KeyError) as exc:
raise TaskLineageSupersessionError("SOURCE_TASK_JSON_INVALID", 409) from exc
if not isinstance(value, Mapping):
_fail("SOURCE_TASK_JSON_INVALID")
return dict(value)
def _verify_source(row: sqlite3.Row, d: Mapping[str, Any], identity_digest: Callable[[sqlite3.Row], str]) -> None:
if row["task_id"] != SOURCE_TASK_ID:
_fail("SOURCE_TASK_ID_MISMATCH")
cas = (
row["status"] == "EXPIRED"
and int(row["revision"]) == d["source_expected_revision"]
and int(row["attempt"]) == 0
and row["claimed_by"] is None
and row["lease_token_hash"] is None
and row["lease_expires_at"] is None
)
if not cas:
_fail("SOURCE_TASK_EXACT_CAS_MISMATCH")
metadata_raw = row["metadata_json"]
if not isinstance(metadata_raw, str) or sha256_text(metadata_raw) != d["source_metadata_sha256"]:
_fail("SOURCE_METADATA_DIGEST_MISMATCH")
if identity_digest(row) != d["source_task_identity_sha256"]:
_fail("SOURCE_TASK_IDENTITY_DIGEST_MISMATCH")
metadata = _json_object(row, "metadata_json")
execution = metadata.get("execution")
constraints = _json_object(row, "constraints_json")
if not isinstance(execution, Mapping):
_fail("SOURCE_EXECUTION_BINDING_INVALID")
expected = {
"repository": REPOSITORY,
"target_branch": TARGET_BRANCH,
"generation": GENERATION,
"execution_digest": EXECUTION_DIGEST,
"external_job_id": EXTERNAL_JOB_ID,
"task_id": SOURCE_TASK_ID,
}
if any(execution.get(key) != value for key, value in expected.items()):
_fail("SOURCE_EXECUTION_BINDING_MISMATCH")
if constraints.get("generation") != GENERATION:
_fail("SOURCE_GENERATION_CONSTRAINT_MISMATCH")
def _parameterized_recovery_nonce_reserved(
connection: sqlite3.Connection, nonce: str
) -> bool:
"""Share the existing authority nonce domain when C6 NOOP support exists.
Historical lineage databases can predate TaskStore's idempotency table;
their established lineage-only operation remains readable. The C6 NOOP
adapter refuses to activate on those schemas, so an absent table cannot
create a cross-operation uniqueness gap.
"""
tables = {
str(row[0])
for row in connection.execute("SELECT name FROM sqlite_master WHERE type='table'")
}
if "idempotency" not in tables:
return False
columns = {str(row[1]) for row in connection.execute("PRAGMA table_info(idempotency)")}
if not {"scope", "key", "payload_digest", "response_json", "created_at"}.issubset(columns):
_fail("FORMAL_IDEMPOTENCY_SCHEMA_UNAVAILABLE", 503)
return connection.execute(
"SELECT 1 FROM idempotency WHERE scope=? AND key=?",
(PARAMETERIZED_RECOVERY_NONCE_SCOPE, nonce),
).fetchone() is not None
class TaskLineageSupersessionController:
def __init__(self, db_path: str | Path, verifier: SignatureVerifier, *, clock=None, identity_digest=None):
self.db_path = Path(db_path)
self.verifier = verifier
self.clock = clock or (lambda: datetime.now(timezone.utc))
self.identity_digest = identity_digest or _default_identity_digest
def _connect(self) -> sqlite3.Connection:
connection = sqlite3.connect(self.db_path, timeout=30, isolation_level=None)
connection.row_factory = sqlite3.Row
connection.execute("PRAGMA foreign_keys=ON")
connection.execute("PRAGMA busy_timeout=30000")
return connection
def _now(self) -> datetime:
value = self.clock()
if value.tzinfo is None:
value = value.replace(tzinfo=timezone.utc)
return value.astimezone(timezone.utc)
@staticmethod
def _assert_schema(connection: sqlite3.Connection) -> None:
required = {"tasks", "events", "task_lineage_supersessions", "task_lineage_supersession_receipts"}
actual = {row[0] for row in connection.execute("SELECT name FROM sqlite_master WHERE type='table'")}
if not required.issubset(actual):
_fail("SUPERSESSION_SCHEMA_UNAVAILABLE", 503)
columns = {row[1] for row in connection.execute("PRAGMA table_info(tasks)")}
task_columns = {
"task_id", "goal", "instructions", "success_criteria_json",
"required_capabilities_json", "constraints_json", "metadata_json", "deadline",
"priority", "max_attempts", "attempt", "status", "claimed_by",
"lease_token_hash", "lease_expires_at", "result_json", "result_digest",
"result_idempotency_key", "verification_json", "revision", "created_at", "updated_at",
}
if not task_columns.issubset(columns):
_fail("TASK_SCHEMA_UNSUPPORTED", 503)
def supersede(self, raw: Mapping[str, Any]) -> dict[str, Any]:
now = self._now()
decision = validate_decision(raw, now=now)
payload_bytes = canonical_json(decision_payload(raw)).encode("utf-8")
if not self.verifier.verify(
namespace=OWNER_NAMESPACE, payload=payload_bytes,
signature=decision["signature"], key_id=decision["key_id"],
):
_fail("DECISION_SIGNATURE_INVALID", 403)
decision_sha = hashlib.sha256(payload_bytes).hexdigest()
now_text = now.isoformat()
with closing(self._connect()) as connection:
connection.execute("BEGIN IMMEDIATE")
try:
self._assert_schema(connection)
if _parameterized_recovery_nonce_reserved(connection, decision["nonce"]):
_fail("PARAMETERIZED_RECOVERY_NONCE_CONSUMED")
replay = connection.execute(
"SELECT owner_decision_sha256,receipt_json FROM task_lineage_supersession_receipts WHERE decision_id=? OR nonce=?",
(decision["decision_id"], decision["nonce"]),
).fetchone()
if replay is not None:
if replay["owner_decision_sha256"] != decision_sha:
_fail("DECISION_REPLAY_MISMATCH")
result = json.loads(replay["receipt_json"])
connection.commit()
return {**result, "idempotent": True}
source = connection.execute("SELECT * FROM tasks WHERE task_id=?", (SOURCE_TASK_ID,)).fetchone()
if source is None:
_fail("SOURCE_TASK_NOT_FOUND", 404)
_verify_source(source, decision, self.identity_digest)
if connection.execute(
"SELECT 1 FROM task_lineage_supersessions WHERE source_task_id=? AND supersession_ordinal=?",
(SOURCE_TASK_ID, SUPERSESSION_ORDINAL),
).fetchone():
_fail("SUPERSESSION_ALREADY_EXISTS")
if connection.execute("SELECT 1 FROM tasks WHERE task_id=?", (CHILD_TASK_ID,)).fetchone():
_fail("CHILD_TASK_ALREADY_EXISTS")
connection.execute(
"""INSERT INTO tasks(
task_id,goal,instructions,success_criteria_json,required_capabilities_json,
constraints_json,metadata_json,deadline,priority,max_attempts,attempt,status,
claimed_by,lease_token_hash,lease_expires_at,result_json,result_digest,
result_idempotency_key,verification_json,revision,created_at,updated_at)
VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
(
CHILD_TASK_ID, source["goal"], source["instructions"], source["success_criteria_json"],
"[]", source["constraints_json"], "{}", None, source["priority"], source["max_attempts"],
0, "QUEUED", None, None, None, None, None, None, None, 1, now_text, now_text,
),
)
connection.execute(
"""INSERT INTO task_lineage_supersessions(
source_task_id,supersession_ordinal,child_task_id,source_task_identity_sha256,
source_metadata_sha256,owner_decision_sha256,decision_id,created_at)
VALUES(?,?,?,?,?,?,?,?)""",
(SOURCE_TASK_ID, SUPERSESSION_ORDINAL, CHILD_TASK_ID,
decision["source_task_identity_sha256"], decision["source_metadata_sha256"],
decision_sha, decision["decision_id"], now_text),
)
event_payload = {
"task_id": CHILD_TASK_ID, "supersedes_task_id": SOURCE_TASK_ID,
"supersession_ordinal": SUPERSESSION_ORDINAL,
"source_task_identity_sha256": decision["source_task_identity_sha256"],
"source_metadata_sha256": decision["source_metadata_sha256"],
"owner_supersession_decision_sha256": decision_sha,
"canonical_projection_profile": CANONICAL_PROFILE,
"canonical_projection_version": CANONICAL_PROFILE_VERSION,
}
connection.execute(
"INSERT INTO events(task_id,agent_id,event_type,payload_json,created_at) VALUES(?,?,?,?,?)",
(CHILD_TASK_ID, None, "TASK_CREATED", canonical_json(event_payload), now_text),
)
receipt = {
"schema": RECEIPT_SCHEMA, "status": "SUPERSEDED",
"decision_id": decision["decision_id"], "owner_decision_sha256": decision_sha,
"source_task_id": SOURCE_TASK_ID, "source_task_identity_sha256": decision["source_task_identity_sha256"],
"source_metadata_sha256": decision["source_metadata_sha256"],
"source_status": "EXPIRED", "source_revision": decision["source_expected_revision"],
"source_attempt": 0, "source_history_preserved": True,
"child_task_id": CHILD_TASK_ID, "supersession_ordinal": SUPERSESSION_ORDINAL,
"child_status": "QUEUED", "child_revision": 1, "child_attempt": 0,
"canonical_projection_profile": CANONICAL_PROFILE,
"canonical_projection_version": CANONICAL_PROFILE_VERSION,
"exact_identity_cas": True, "one_time_use": True,
"owner_signature_namespace": OWNER_NAMESPACE,
"owner_signature_principal": OWNER_PRINCIPAL,
"owner_signature_key_id": OWNER_KEY_ID,
"owner_signature_sha256": sha256_text(decision["signature"]),
}
connection.execute(
"""INSERT INTO task_lineage_supersession_receipts(
decision_id,nonce,source_task_id,supersession_ordinal,child_task_id,
owner_decision_sha256,decision_json,receipt_json,consumed_at)
VALUES(?,?,?,?,?,?,?,?,?)""",
(decision["decision_id"], decision["nonce"], SOURCE_TASK_ID, SUPERSESSION_ORDINAL,
CHILD_TASK_ID, decision_sha, canonical_json(dict(raw)), canonical_json(receipt), now_text),
)
connection.commit()
return {**receipt, "idempotent": False}
except Exception:
connection.rollback()
raise
@@ -0,0 +1,548 @@
"""Canonical initial projection for the exact Single-Host supersession child.
The source business execution digest is lineage evidence. It is deliberately
not used as the child execution-envelope digest: the canonical envelope binds
the child Task id and therefore has its own server-derived digest.
"""
from __future__ import annotations
from dataclasses import dataclass
import hashlib
import json
import sqlite3
from typing import Any, Mapping
from aios_agent_task_initial_execution_projection import (
InitialExecutionProjectionError,
InitialExecutionProjectionRequest,
InitialExecutionProjectionStore,
RUNTIME_CAPABILITY_RE,
_now,
_require_string,
_required_authorization_id,
)
from aios_online_agent_execution import (
ExecutionProjectionError,
GovernedExecutionBinding,
validate_execution_envelope,
)
from aios_task_lineage_supersession import (
CANONICAL_PROFILE,
CANONICAL_PROFILE_VERSION,
CHILD_TASK_ID,
DECISION_SCHEMA,
EXECUTION_DIGEST as SOURCE_BUSINESS_EXECUTION_DIGEST,
EXTERNAL_JOB_ID,
GENERATION,
OWNER_KEY_ID,
OWNER_NAMESPACE,
OWNER_PRINCIPAL,
RECEIPT_SCHEMA as SUPERSESSION_RECEIPT_SCHEMA,
REPOSITORY,
SOURCE_TASK_ID,
SUPERSESSION_ORDINAL,
TARGET_BRANCH,
canonical_json,
decision_payload,
)
PROJECTION_INTENT_SCHEMA = "aios.task-lineage-supersession-initial-projection-intent.v1"
PROJECTION_RECEIPT_SCHEMA = "aios.task-lineage-supersession-initial-projection-receipt.v1"
PROJECTION_IDEMPOTENCY_KEY = "task-lineage-supersession-s1-initial-projection-v1"
PROJECTION_REASON = "canonical-initial-projection-for-formal-supersession-child"
PROJECTION_TABLE = "task_lineage_supersession_initial_projections"
def _sha_text(value: str) -> str:
return hashlib.sha256(value.encode("utf-8")).hexdigest()
def _sha_json(value: Any) -> str:
return _sha_text(canonical_json(value))
def _json_object(raw: Any, code: str) -> dict[str, Any]:
if not isinstance(raw, str):
raise InitialExecutionProjectionError(code, 409)
try:
value = json.loads(raw)
except (TypeError, ValueError) as exc:
raise InitialExecutionProjectionError(code, 409) from exc
if not isinstance(value, Mapping):
raise InitialExecutionProjectionError(code, 409)
return dict(value)
@dataclass(frozen=True)
class SupersessionChildProjectionIntent:
"""Caller-selectable portion of the exact child projection.
Task identity, CAS values, Job identity, projection profile, reason and
idempotency are intentionally absent from the request and server-owned.
"""
adapter: str
required_capability: str
authorization_id: str
@classmethod
def from_mapping(
cls, value: Mapping[str, Any]
) -> "SupersessionChildProjectionIntent":
if not isinstance(value, Mapping) or set(value) != {
"adapter",
"required_capability",
"authorization_id",
}:
raise InitialExecutionProjectionError(
"SUPERSESSION_INITIAL_PROJECTION_FIELDS_INVALID", 400
)
adapter = _require_string(value["adapter"], "RUNTIME_ADAPTER_INVALID")
capability = _require_string(
value["required_capability"],
"RUNTIME_CAPABILITY_INVALID",
RUNTIME_CAPABILITY_RE,
)
if adapter not in {"hermes", "openclaw"} or capability != f"agent.runtime.{adapter}":
raise InitialExecutionProjectionError("RUNTIME_CAPABILITY_MISMATCH", 400)
authorization_id = _require_string(
value["authorization_id"], "AUTHORIZATION_ID_INVALID"
)
return cls(adapter, capability, authorization_id)
def public(self) -> dict[str, Any]:
return {
"schema": PROJECTION_INTENT_SCHEMA,
"source_task_id": SOURCE_TASK_ID,
"child_task_id": CHILD_TASK_ID,
"supersession_ordinal": SUPERSESSION_ORDINAL,
"canonical_projection_profile": CANONICAL_PROFILE,
"canonical_projection_version": CANONICAL_PROFILE_VERSION,
"expected_status": "QUEUED",
"expected_revision": 1,
"expected_attempt": 0,
"expected_metadata_sha256": _sha_json({}),
"expected_required_capabilities_sha256": _sha_json([]),
"expected_job_id": EXTERNAL_JOB_ID,
"adapter": self.adapter,
"required_capability": self.required_capability,
"authorization_id": self.authorization_id,
"idempotency_key": PROJECTION_IDEMPOTENCY_KEY,
"reason": PROJECTION_REASON,
}
def initial_request(self, canonical_execution_digest: str) -> InitialExecutionProjectionRequest:
return 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": EXTERNAL_JOB_ID,
"expected_execution_digest": canonical_execution_digest,
"required_capability": self.required_capability,
"adapter": self.adapter,
"authorization_id": self.authorization_id,
"idempotency_key": PROJECTION_IDEMPOTENCY_KEY,
"reason": PROJECTION_REASON,
}
)
def _table_names(connection: sqlite3.Connection) -> set[str]:
return {
str(row[0])
for row in connection.execute(
"SELECT name FROM sqlite_master WHERE type='table'"
)
}
def _load_lineage_context(
connection: sqlite3.Connection, *, expect_projected: bool
) -> dict[str, Any]:
required_tables = {
"tasks",
"events",
"task_lineage_supersessions",
"task_lineage_supersession_receipts",
PROJECTION_TABLE,
}
if not required_tables.issubset(_table_names(connection)):
raise InitialExecutionProjectionError(
"SUPERSESSION_INITIAL_PROJECTION_SCHEMA_UNAVAILABLE", 503
)
relation = connection.execute(
"SELECT * FROM task_lineage_supersessions "
"WHERE source_task_id=? AND supersession_ordinal=? AND child_task_id=?",
(SOURCE_TASK_ID, SUPERSESSION_ORDINAL, CHILD_TASK_ID),
).fetchone()
if relation is None:
raise InitialExecutionProjectionError("FORMAL_SUPERSESSION_REQUIRED", 409)
receipt_row = connection.execute(
"SELECT * FROM task_lineage_supersession_receipts "
"WHERE decision_id=? AND source_task_id=? AND supersession_ordinal=? "
"AND child_task_id=?",
(
relation["decision_id"],
SOURCE_TASK_ID,
SUPERSESSION_ORDINAL,
CHILD_TASK_ID,
),
).fetchone()
if receipt_row is None:
raise InitialExecutionProjectionError("SUPERSESSION_RECEIPT_REQUIRED", 409)
decision = _json_object(receipt_row["decision_json"], "SUPERSESSION_DECISION_INVALID")
receipt = _json_object(receipt_row["receipt_json"], "SUPERSESSION_RECEIPT_INVALID")
decision_sha256 = _sha_text(canonical_json(decision_payload(decision)))
exact_decision = {
"schema": DECISION_SCHEMA,
"source_task_id": SOURCE_TASK_ID,
"child_task_id": CHILD_TASK_ID,
"supersession_ordinal": SUPERSESSION_ORDINAL,
"repository": REPOSITORY,
"target_branch": TARGET_BRANCH,
"generation": GENERATION,
"execution_digest": SOURCE_BUSINESS_EXECUTION_DIGEST,
"external_job_id": EXTERNAL_JOB_ID,
"canonical_projection_profile": CANONICAL_PROFILE,
"canonical_projection_version": CANONICAL_PROFILE_VERSION,
"owner_identity": OWNER_PRINCIPAL,
"signature_namespace": OWNER_NAMESPACE,
"signature_principal": OWNER_PRINCIPAL,
"key_id": OWNER_KEY_ID,
"one_time_use": True,
}
if any(decision.get(key) != expected for key, expected in exact_decision.items()):
raise InitialExecutionProjectionError(
"SUPERSESSION_DECISION_SCOPE_MISMATCH", 409
)
if (
decision_sha256 != relation["owner_decision_sha256"]
or decision_sha256 != receipt_row["owner_decision_sha256"]
or decision.get("source_task_identity_sha256")
!= relation["source_task_identity_sha256"]
or decision.get("source_metadata_sha256") != relation["source_metadata_sha256"]
or receipt.get("schema") != SUPERSESSION_RECEIPT_SCHEMA
or receipt.get("owner_decision_sha256") != decision_sha256
or receipt.get("source_task_id") != SOURCE_TASK_ID
or receipt.get("child_task_id") != CHILD_TASK_ID
or receipt.get("supersession_ordinal") != SUPERSESSION_ORDINAL
):
raise InitialExecutionProjectionError(
"SUPERSESSION_LINEAGE_EVIDENCE_MISMATCH", 409
)
events = connection.execute(
"SELECT event_type,payload_json FROM events WHERE task_id=? ORDER BY event_id",
(CHILD_TASK_ID,),
).fetchall()
expected_events = ["TASK_CREATED"]
if expect_projected:
expected_events.append("TASK_INITIAL_EXECUTION_PROJECTION_AUTHORIZED")
if [str(row["event_type"]) for row in events] != expected_events:
raise InitialExecutionProjectionError(
"SUPERSESSION_CHILD_EVENT_CHAIN_INVALID", 409
)
created = _json_object(events[0]["payload_json"], "SUPERSESSION_CREATED_EVENT_INVALID")
exact_created = {
"task_id": CHILD_TASK_ID,
"supersedes_task_id": SOURCE_TASK_ID,
"supersession_ordinal": SUPERSESSION_ORDINAL,
"source_task_identity_sha256": relation["source_task_identity_sha256"],
"source_metadata_sha256": relation["source_metadata_sha256"],
"owner_supersession_decision_sha256": decision_sha256,
"canonical_projection_profile": CANONICAL_PROFILE,
"canonical_projection_version": CANONICAL_PROFILE_VERSION,
}
if any(created.get(key) != expected for key, expected in exact_created.items()):
raise InitialExecutionProjectionError(
"SUPERSESSION_CREATED_EVENT_SCOPE_MISMATCH", 409
)
return {
"source_task_identity_sha256": relation["source_task_identity_sha256"],
"source_metadata_sha256": relation["source_metadata_sha256"],
"owner_decision_sha256": decision_sha256,
}
def _validate_canonical_child_execution(
resolved: Mapping[str, Any],
*,
intent: SupersessionChildProjectionIntent,
runtime_input: Mapping[str, Any],
) -> dict[str, Any]:
try:
binding = GovernedExecutionBinding.from_mapping(resolved)
execution = validate_execution_envelope(resolved, binding=binding)
except ExecutionProjectionError as exc:
raise InitialExecutionProjectionError(
"SUPERSESSION_CANONICAL_EXECUTION_INVALID", 409
) from exc
exact_binding = {
"task_id": CHILD_TASK_ID,
"job_id": EXTERNAL_JOB_ID,
"repository": REPOSITORY,
"target_branch": TARGET_BRANCH,
"generation": GENERATION,
}
if any(execution.get(key) != expected for key, expected in exact_binding.items()):
raise InitialExecutionProjectionError(
"SUPERSESSION_CANONICAL_EXECUTION_BINDING_MISMATCH", 409
)
if (
execution.get("action") != "agent_runtime"
or execution.get("adapter") != intent.adapter
or execution.get("capability_scope") != [intent.required_capability]
or execution.get("runtime_input") != dict(runtime_input)
or execution.get("change_set") != []
or execution.get("verification") != []
or execution.get("governance", {}).get("writer_lease") is not False
):
raise InitialExecutionProjectionError(
"SUPERSESSION_AGENT_RUNTIME_ENVELOPE_INVALID", 409
)
if execution["execution_digest"] == SOURCE_BUSINESS_EXECUTION_DIGEST:
raise InitialExecutionProjectionError(
"CHILD_CANONICAL_DIGEST_COLLIDES_WITH_SOURCE_BUSINESS_DIGEST", 409
)
return execution
class _SupersessionProjectionTransactionStore(InitialExecutionProjectionStore):
"""Per-invocation store that adds immutable lineage evidence atomically."""
allows_supersession_child = True
def __init__(self, db_path, resolved: Mapping[str, Any], intent):
self._resolved = dict(resolved)
self.intent = intent
super().__init__(db_path, self._resolve_once)
def _resolve_once(self, *_args):
return dict(self._resolved)
def _projection_event_extension(
self, connection, task_id, request, execution, applied_revision
):
if task_id != CHILD_TASK_ID:
raise InitialExecutionProjectionError(
"SUPERSESSION_CHILD_TASK_ID_MISMATCH", 409
)
context = _load_lineage_context(connection, expect_projected=False)
return {
"source_task_id": SOURCE_TASK_ID,
"supersession_ordinal": SUPERSESSION_ORDINAL,
"source_task_identity_sha256": context["source_task_identity_sha256"],
"source_metadata_sha256": context["source_metadata_sha256"],
"owner_supersession_decision_sha256": context["owner_decision_sha256"],
"source_business_execution_digest": SOURCE_BUSINESS_EXECUTION_DIGEST,
"canonical_child_execution_digest": execution["execution_digest"],
"canonical_projection_profile": CANONICAL_PROFILE,
"canonical_projection_version": CANONICAL_PROFILE_VERSION,
}
def _insert_projection_extension(
self,
connection,
task_id,
request,
execution,
event_payload,
applied_revision,
):
context = _load_lineage_context(connection, expect_projected=True)
task = connection.execute(
"SELECT metadata_json,required_capabilities_json FROM tasks WHERE task_id=?",
(CHILD_TASK_ID,),
).fetchone()
if task is None:
raise InitialExecutionProjectionError("TASK_NOT_FOUND", 404)
intent_json = canonical_json(self.intent.public())
intent_sha256 = _sha_text(intent_json)
receipt = {
"schema": PROJECTION_RECEIPT_SCHEMA,
"projection_id": "sha256:" + intent_sha256,
"task_id": CHILD_TASK_ID,
"source_task_id": SOURCE_TASK_ID,
"supersession_ordinal": SUPERSESSION_ORDINAL,
"source_task_identity_sha256": context["source_task_identity_sha256"],
"source_metadata_sha256": context["source_metadata_sha256"],
"owner_supersession_decision_sha256": context["owner_decision_sha256"],
"source_business_execution_digest": SOURCE_BUSINESS_EXECUTION_DIGEST,
"canonical_child_execution_digest": execution["execution_digest"],
"external_job_id": EXTERNAL_JOB_ID,
"repository": REPOSITORY,
"target_branch": TARGET_BRANCH,
"generation": GENERATION,
"adapter": self.intent.adapter,
"required_capability": self.intent.required_capability,
"expected_revision": 1,
"applied_revision": applied_revision,
"attempt": 0,
"metadata_sha256": _sha_text(task["metadata_json"]),
"required_capabilities_sha256": _sha_text(
task["required_capabilities_json"]
),
"projection_event_sha256": _sha_json(dict(event_payload)),
}
receipt_json = canonical_json(receipt)
receipt_sha256 = _sha_text(receipt_json)
connection.execute(
f"""INSERT INTO {PROJECTION_TABLE}(
projection_id,task_id,source_task_id,supersession_ordinal,
intent_sha256,intent_json,owner_decision_sha256,
source_task_identity_sha256,source_metadata_sha256,
source_business_execution_digest,canonical_child_execution_digest,
external_job_id,repository,target_branch,generation,adapter,
required_capability,expected_revision,applied_revision,metadata_sha256,
required_capabilities_sha256,projection_event_sha256,receipt_json,
receipt_sha256,created_at)
VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)""",
(
receipt["projection_id"],
CHILD_TASK_ID,
SOURCE_TASK_ID,
SUPERSESSION_ORDINAL,
intent_sha256,
intent_json,
context["owner_decision_sha256"],
context["source_task_identity_sha256"],
context["source_metadata_sha256"],
SOURCE_BUSINESS_EXECUTION_DIGEST,
execution["execution_digest"],
EXTERNAL_JOB_ID,
REPOSITORY,
TARGET_BRANCH,
GENERATION,
self.intent.adapter,
self.intent.required_capability,
1,
applied_revision,
receipt["metadata_sha256"],
receipt["required_capabilities_sha256"],
receipt["projection_event_sha256"],
receipt_json,
receipt_sha256,
_now(),
),
)
class SupersessionChildInitialProjectionStore:
"""Server-owned adapter from exact supersession lineage to current ABI."""
def __init__(self, db_path, resolver):
self.db_path = db_path
self.resolver = resolver
def _connect(self):
connection = sqlite3.connect(self.db_path, timeout=30.0)
connection.row_factory = sqlite3.Row
connection.execute("PRAGMA foreign_keys=ON")
connection.execute("PRAGMA busy_timeout=30000")
return connection
@staticmethod
def _replay(connection, intent: SupersessionChildProjectionIntent):
if PROJECTION_TABLE not in _table_names(connection):
raise InitialExecutionProjectionError(
"SUPERSESSION_INITIAL_PROJECTION_SCHEMA_UNAVAILABLE", 503
)
row = connection.execute(
f"SELECT * FROM {PROJECTION_TABLE} WHERE task_id=?", (CHILD_TASK_ID,)
).fetchone()
if row is None:
tables = _table_names(connection)
if "task_initial_execution_projections" in tables:
generic = connection.execute(
"SELECT 1 FROM task_initial_execution_projections "
"WHERE task_id=? AND idempotency_key=?",
(CHILD_TASK_ID, PROJECTION_IDEMPOTENCY_KEY),
).fetchone()
if generic is not None:
raise InitialExecutionProjectionError(
"SUPERSESSION_PROJECTION_RECEIPT_MISSING", 500
)
return None
intent_sha256 = _sha_json(intent.public())
if row["intent_sha256"] != intent_sha256:
raise InitialExecutionProjectionError(
"SUPERSESSION_INITIAL_PROJECTION_INTENT_CONFLICT", 409
)
receipt = _json_object(
row["receipt_json"], "SUPERSESSION_PROJECTION_RECEIPT_INVALID"
)
if (
_sha_text(row["receipt_json"]) != row["receipt_sha256"]
or receipt.get("schema") != PROJECTION_RECEIPT_SCHEMA
or receipt.get("canonical_child_execution_digest")
!= row["canonical_child_execution_digest"]
):
raise InitialExecutionProjectionError(
"SUPERSESSION_PROJECTION_RECEIPT_DIGEST_MISMATCH", 500
)
_load_lineage_context(connection, expect_projected=True)
task = connection.execute(
"SELECT * FROM tasks WHERE task_id=?", (CHILD_TASK_ID,)
).fetchone()
if (
task is None
or task["status"] != "QUEUED"
or int(task["revision"]) != int(row["applied_revision"])
or int(task["attempt"]) != 0
or task["claimed_by"] is not None
or task["lease_token_hash"] is not None
or task["lease_expires_at"] is not None
or _sha_text(task["metadata_json"]) != row["metadata_sha256"]
or _sha_text(task["required_capabilities_json"])
!= row["required_capabilities_sha256"]
):
raise InitialExecutionProjectionError(
"SUPERSESSION_PROJECTED_TASK_CAS_MISMATCH", 409
)
return {**receipt, "idempotent": True}
def project(self, value: Mapping[str, Any]) -> dict[str, Any]:
intent = SupersessionChildProjectionIntent.from_mapping(value)
if intent.authorization_id != _required_authorization_id():
raise InitialExecutionProjectionError("AUTHORIZATION_ID_MISMATCH", 403)
with self._connect() as connection:
replay = self._replay(connection, intent)
if replay is not None:
return replay
probe_request = intent.initial_request("0" * 64)
probe_store = _SupersessionProjectionTransactionStore(
self.db_path, {}, intent
)
task, _, _, _ = probe_store._load_and_validate(
connection, CHILD_TASK_ID, probe_request
)
_load_lineage_context(connection, expect_projected=False)
runtime_input = probe_store._runtime_input(task)
resolved = dict(
self.resolver(
CHILD_TASK_ID,
EXTERNAL_JOB_ID,
intent.required_capability,
intent.adapter,
runtime_input,
)
)
execution = _validate_canonical_child_execution(
resolved, intent=intent, runtime_input=runtime_input
)
store = _SupersessionProjectionTransactionStore(
self.db_path, resolved, intent
)
result = store.project(
CHILD_TASK_ID, intent.initial_request(execution["execution_digest"])
)
with self._connect() as connection:
receipt = self._replay(connection, intent)
if receipt is None:
raise InitialExecutionProjectionError(
"SUPERSESSION_PROJECTION_RECEIPT_MISSING", 500
)
return {**receipt, "idempotent": bool(result["idempotent"])}
@@ -0,0 +1,221 @@
"""Fixed Owner operation for one exact Task-lineage supersession.
This staging overlay deliberately extends the installed single-host operational
authority instead of exposing a generic signing primitive. The caller can
only provide the three live CAS values which cannot be known at build time.
Every authority, lineage and execution field is constructed server-side.
"""
from __future__ import annotations
import re
import secrets
from typing import Any, Callable, Mapping
from aios_single_host_authority import OWNER_IDENTITY, OWNER_NAMESPACE, sha256_json
from aios_single_host_operational_authority import (
OperationalAuthorityError,
OperationalOwnerAuthority,
OperationalPolicy,
SignedOperationalArtifact,
)
TASK_LINEAGE_SUPERSESSION_OPERATION = "issue_task_lineage_supersession"
TASK_LINEAGE_SUPERSESSION_SCHEMA = "aios.task-lineage-supersession-decision.v1"
SOURCE_TASK_ID = "TASK-SINGLEHOST-TRUSTROOT-P0-20260904"
CHILD_TASK_ID = SOURCE_TASK_ID + "-S1"
SUPERSESSION_ORDINAL = 1
REPOSITORY = "aios/aios"
TARGET_BRANCH = "agent/single-host-trust-root-p0-20260904"
GENERATION = 1
SOURCE_BUSINESS_EXECUTION_DIGEST = (
"e99bda6a251832eb3865271eb5687af7eea44dd261144cdcdfa57104c0780536"
)
EXTERNAL_JOB_IDENTITY = "job-4e10f92a6a5742518a9f1d8745c845ab"
CANONICAL_PROJECTION_PROFILE = "aios.task-initial-execution.v1"
CANONICAL_PROJECTION_VERSION = 1
SUPERSESSION_REASON = "formal-lineage-supersession-after-expired-unclaimed-source"
DECISION_TTL_SECONDS = 300
HEX64 = re.compile(r"^[0-9a-f]{64}$")
SAFE_TOKEN = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:@/+:-]{2,511}$")
def _fail(code: str) -> None:
raise OperationalAuthorityError(code)
def _assert_fixed_policy(policy: OperationalPolicy) -> None:
"""Refuse issuance if the live policy is not the approved source scope."""
policy.validate()
exact = {
"task_id": SOURCE_TASK_ID,
"repository": REPOSITORY,
"target_branch": TARGET_BRANCH,
"generation": GENERATION,
"execution_plan_sha256": SOURCE_BUSINESS_EXECUTION_DIGEST,
"external_job_identity": EXTERNAL_JOB_IDENTITY,
}
if any(getattr(policy, field) != expected for field, expected in exact.items()):
_fail("TASK_LINEAGE_SUPERSESSION_POLICY_MISMATCH")
if policy.maximum_recovery_decision_ttl_seconds < DECISION_TTL_SECONDS:
_fail("TASK_LINEAGE_SUPERSESSION_POLICY_TTL_TOO_NARROW")
TASK_LINEAGE_SUPERSESSION_FIELDS = {
"schema",
"decision_id",
"nonce",
"source_task_id",
"source_task_identity_sha256",
"source_metadata_sha256",
"source_expected_status",
"source_expected_revision",
"source_expected_attempt",
"source_expected_claimed_by",
"source_expected_lease_token_hash",
"source_expected_lease_expires_at",
"repository",
"target_branch",
"generation",
"source_business_execution_digest",
"external_job_identity",
"child_task_id",
"supersession_ordinal",
"canonical_projection_profile",
"canonical_projection_version",
"supersession_reason",
"owner_identity",
"policy_digest",
"one_time_use",
"issued_at",
"not_before",
"expires_at",
"signature_namespace",
"signature_key_id",
"canonical_payload_digest",
}
def verify_task_lineage_supersession_scope(
decision: Mapping[str, Any],
policy: OperationalPolicy,
*,
now: int,
) -> None:
"""Validate a signature-verified decision against the fixed source scope."""
_assert_fixed_policy(policy)
if not isinstance(decision, Mapping) or set(decision) != TASK_LINEAGE_SUPERSESSION_FIELDS:
_fail("TASK_LINEAGE_SUPERSESSION_FIELDS_INVALID")
exact = {
"schema": TASK_LINEAGE_SUPERSESSION_SCHEMA,
"source_task_id": SOURCE_TASK_ID,
"source_expected_status": "EXPIRED",
"source_expected_attempt": 0,
"source_expected_claimed_by": None,
"source_expected_lease_token_hash": None,
"source_expected_lease_expires_at": None,
"repository": REPOSITORY,
"target_branch": TARGET_BRANCH,
"generation": GENERATION,
"source_business_execution_digest": SOURCE_BUSINESS_EXECUTION_DIGEST,
"external_job_identity": EXTERNAL_JOB_IDENTITY,
"child_task_id": CHILD_TASK_ID,
"supersession_ordinal": SUPERSESSION_ORDINAL,
"canonical_projection_profile": CANONICAL_PROJECTION_PROFILE,
"canonical_projection_version": CANONICAL_PROJECTION_VERSION,
"supersession_reason": SUPERSESSION_REASON,
"owner_identity": OWNER_IDENTITY,
"policy_digest": policy.digest,
"one_time_use": True,
"signature_namespace": OWNER_NAMESPACE,
}
if any(decision.get(field) != expected for field, expected in exact.items()):
_fail("TASK_LINEAGE_SUPERSESSION_SCOPE_INVALID")
if decision.get("source_task_identity_sha256") != policy.task_identity_sha256:
_fail("TASK_LINEAGE_SUPERSESSION_SOURCE_IDENTITY_INVALID")
for field in ("source_task_identity_sha256", "source_metadata_sha256"):
if not isinstance(decision.get(field), str) or HEX64.fullmatch(decision[field]) is None:
_fail("TASK_LINEAGE_SUPERSESSION_DIGEST_INVALID")
if type(decision.get("source_expected_revision")) is not int or not 1 <= decision["source_expected_revision"] < 2**63:
_fail("TASK_LINEAGE_SUPERSESSION_REVISION_INVALID")
for field in ("decision_id", "nonce"):
if not isinstance(decision.get(field), str) or SAFE_TOKEN.fullmatch(decision[field]) is None:
_fail("TASK_LINEAGE_SUPERSESSION_GENERATED_ID_INVALID")
for field in ("issued_at", "not_before", "expires_at"):
if type(decision.get(field)) is not int:
_fail("TASK_LINEAGE_SUPERSESSION_TIME_INVALID")
if not decision["not_before"] <= now < decision["expires_at"]:
_fail("TASK_LINEAGE_SUPERSESSION_TIME_INVALID")
if decision["issued_at"] > now + 5 or now - decision["issued_at"] > DECISION_TTL_SECONDS:
_fail("TASK_LINEAGE_SUPERSESSION_TIME_INVALID")
if decision["expires_at"] - decision["issued_at"] != DECISION_TTL_SECONDS:
_fail("TASK_LINEAGE_SUPERSESSION_TIME_INVALID")
digest_value = dict(decision)
supplied_digest = digest_value.pop("canonical_payload_digest", None)
if supplied_digest != sha256_json(digest_value):
_fail("TASK_LINEAGE_SUPERSESSION_CANONICAL_DIGEST_INVALID")
class TaskLineageSupersessionOwnerAuthority(OperationalOwnerAuthority):
"""Owner signer with one bounded constructor and no caller-defined message."""
def __init__(
self,
*args: Any,
token_hex: Callable[[int], str] = secrets.token_hex,
**kwargs: Any,
) -> None:
super().__init__(*args, **kwargs)
self._token_hex = token_hex
def issue_task_lineage_supersession(
self,
*,
source_expected_revision: int,
source_task_identity_sha256: str,
source_metadata_sha256: str,
) -> SignedOperationalArtifact:
_assert_fixed_policy(self.policy)
if type(source_expected_revision) is not int or not 1 <= source_expected_revision < 2**63:
_fail("TASK_LINEAGE_SUPERSESSION_REVISION_INVALID")
if source_task_identity_sha256 != self.policy.task_identity_sha256:
_fail("TASK_LINEAGE_SUPERSESSION_SOURCE_IDENTITY_INVALID")
if not isinstance(source_metadata_sha256, str) or HEX64.fullmatch(source_metadata_sha256) is None:
_fail("TASK_LINEAGE_SUPERSESSION_METADATA_DIGEST_INVALID")
decision_id = "task-lineage-supersession-" + self._token_hex(16)
nonce = "task-lineage-supersession-nonce-" + self._token_hex(16)
if SAFE_TOKEN.fullmatch(decision_id) is None or SAFE_TOKEN.fullmatch(nonce) is None:
_fail("TASK_LINEAGE_SUPERSESSION_GENERATED_ID_INVALID")
now = int(self.clock())
payload = {
"schema": TASK_LINEAGE_SUPERSESSION_SCHEMA,
"decision_id": decision_id,
"nonce": nonce,
"source_task_id": SOURCE_TASK_ID,
"source_task_identity_sha256": source_task_identity_sha256,
"source_metadata_sha256": source_metadata_sha256,
"source_expected_status": "EXPIRED",
"source_expected_revision": source_expected_revision,
"source_expected_attempt": 0,
"source_expected_claimed_by": None,
"source_expected_lease_token_hash": None,
"source_expected_lease_expires_at": None,
"repository": REPOSITORY,
"target_branch": TARGET_BRANCH,
"generation": GENERATION,
"source_business_execution_digest": SOURCE_BUSINESS_EXECUTION_DIGEST,
"external_job_identity": EXTERNAL_JOB_IDENTITY,
"child_task_id": CHILD_TASK_ID,
"supersession_ordinal": SUPERSESSION_ORDINAL,
"canonical_projection_profile": CANONICAL_PROJECTION_PROFILE,
"canonical_projection_version": CANONICAL_PROJECTION_VERSION,
"supersession_reason": SUPERSESSION_REASON,
"owner_identity": OWNER_IDENTITY,
"policy_digest": self.policy.digest,
"one_time_use": True,
"issued_at": now,
"not_before": max(0, now - 5),
"expires_at": now + DECISION_TTL_SECONDS,
}
return self._sign(payload)
@@ -0,0 +1,190 @@
"""Staging-only server overlay for the fixed lineage Owner operation."""
from __future__ import annotations
import argparse
import hashlib
import json
from pathlib import Path
import sqlite3
import time
from typing import Any, Callable, Mapping
from aios_single_host_authority import DEFAULT_REGISTRY_PATH
from aios_single_host_operational_authority import (
OperationalMachineAuthority,
OperationalVerifier,
)
from aios_single_host_operational_authority_server import (
DEFAULT_LEDGER,
DEFAULT_MACHINE_KEY,
DEFAULT_OWNER_KEY,
DEFAULT_POLICY,
DurableIssuanceLedger,
OperationalAuthorityService as BaseOperationalAuthorityService,
_read_policy,
fail,
)
from aios_task_lineage_supersession_owner_authority import (
TASK_LINEAGE_SUPERSESSION_OPERATION,
TaskLineageSupersessionOwnerAuthority,
)
REQUEST_FIELDS = {
"source_expected_revision",
"source_task_identity_sha256",
"source_metadata_sha256",
}
class TaskLineageSupersessionIssuanceLedger(DurableIssuanceLedger):
"""Operation-scoped one-time fence layered on the existing durable ledger."""
def issue_once(
self,
*,
role: str,
operation: str,
request: Mapping[str, Any],
issuer: Callable[[], dict[str, Any]],
) -> dict[str, Any]:
request_sha = hashlib.sha256(
json.dumps(
request,
sort_keys=True,
separators=(",", ":"),
ensure_ascii=False,
allow_nan=False,
).encode("utf-8")
).hexdigest()
connection = sqlite3.connect(self.path, timeout=5, isolation_level=None)
try:
connection.execute("BEGIN IMMEDIATE")
existing = connection.execute(
"SELECT 1 FROM issuance WHERE role=? AND operation=? LIMIT 1",
(role, operation),
).fetchone()
if existing is not None:
connection.rollback()
fail("TASK_LINEAGE_SUPERSESSION_ALREADY_ISSUED")
result = issuer()
artifact = result.get("artifact")
nonce = artifact.get("payload", {}).get("nonce") if isinstance(artifact, Mapping) else None
if not isinstance(nonce, str) or not nonce:
connection.rollback()
fail("TASK_LINEAGE_SUPERSESSION_ISSUER_RESULT_INVALID")
encoded = json.dumps(
result,
sort_keys=True,
separators=(",", ":"),
ensure_ascii=False,
allow_nan=False,
)
connection.execute(
"INSERT INTO issuance(role,operation,nonce,request_sha256,response_json,created_at) "
"VALUES(?,?,?,?,?,?)",
(role, operation, nonce, request_sha, encoded, int(time.time())),
)
connection.commit()
return result
except sqlite3.IntegrityError:
connection.rollback()
fail("ISSUANCE_LEDGER_RACE")
except Exception:
connection.rollback()
raise
finally:
connection.close()
class OperationalAuthorityService(BaseOperationalAuthorityService):
"""Preserve all existing operations and add one exact Owner-only operation."""
def __init__(self, *args: Any, token_hex=None, **kwargs: Any) -> None:
super().__init__(*args, **kwargs)
self.ledger = TaskLineageSupersessionIssuanceLedger(self.ledger.path)
self._token_hex = token_hex
def _components(self):
policy = _read_policy(
self.policy_path,
require_root_owner=self.require_root_owner,
)
verifier = OperationalVerifier(
self.registry_path,
require_root_owner=self.require_root_owner,
)
if self.role == "owner":
kwargs = {}
if self._token_hex is not None:
kwargs["token_hex"] = self._token_hex
signer = TaskLineageSupersessionOwnerAuthority(
self.owner_key_path,
policy,
require_root_owner=self.require_root_owner,
clock=self.clock,
**kwargs,
)
else:
signer = OperationalMachineAuthority(
self.machine_key_path,
policy,
require_root_owner=self.require_root_owner,
clock=self.clock,
)
return policy, verifier, signer
def handle(self, request: Mapping[str, Any], *, peer_uid: int) -> dict[str, Any]:
if peer_uid not in self.allowed_uids:
fail("PEER_UID_FORBIDDEN")
if not isinstance(request, Mapping) or set(request) != {"operation", "payload"}:
fail("REQUEST_INVALID")
if request.get("operation") != TASK_LINEAGE_SUPERSESSION_OPERATION:
return super().handle(request, peer_uid=peer_uid)
if self.role != "owner":
fail("MACHINE_OPERATION_FORBIDDEN")
payload = request.get("payload")
if not isinstance(payload, Mapping) or set(payload) != REQUEST_FIELDS:
fail("REQUEST_FIELDS_INVALID")
_policy, _verifier, signer = self._components()
def issue() -> dict[str, Any]:
artifact = signer.issue_task_lineage_supersession(
source_expected_revision=payload["source_expected_revision"],
source_task_identity_sha256=payload["source_task_identity_sha256"],
source_metadata_sha256=payload["source_metadata_sha256"],
)
return {"status": "PASS", "artifact": artifact.to_dict()}
return self.ledger.issue_once(
role=self.role,
operation=TASK_LINEAGE_SUPERSESSION_OPERATION,
request=payload,
issuer=issue,
)
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--role", choices=("owner", "machine"), required=True)
parser.add_argument("--policy", default=str(DEFAULT_POLICY))
parser.add_argument("--registry", default=str(DEFAULT_REGISTRY_PATH))
parser.add_argument("--owner-key", default=str(DEFAULT_OWNER_KEY))
parser.add_argument("--machine-key", default=str(DEFAULT_MACHINE_KEY))
parser.add_argument("--socket")
parser.add_argument("--ledger", default=str(DEFAULT_LEDGER))
arguments = parser.parse_args()
service = OperationalAuthorityService(
role=arguments.role,
policy_path=Path(arguments.policy),
registry_path=Path(arguments.registry),
owner_key_path=Path(arguments.owner_key),
machine_key_path=Path(arguments.machine_key),
socket_path=Path(arguments.socket) if arguments.socket else None,
ledger_path=Path(arguments.ledger),
)
service.serve_forever()
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,464 @@
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime, timezone
import json
from pathlib import Path
import sqlite3
from typing import Callable, Mapping, Protocol, Sequence, runtime_checkable
if not __package__:
from aios_task_lineage import RelationType, RootReviewRecord, lineage_from_metadata
from aios_task_lineage_integration import (
IntegrationError,
LINEAGE_METADATA_KEY,
RootActionDispatchReceipt,
TaskAuthoritySnapshot,
TaskPlaneLineagePort,
)
else:
from .aios_task_lineage import RelationType, RootReviewRecord, lineage_from_metadata
from .aios_task_lineage_integration import (
IntegrationError,
LINEAGE_METADATA_KEY,
RootActionDispatchReceipt,
TaskAuthoritySnapshot,
TaskPlaneLineagePort,
)
class AuthorityAdapterError(IntegrationError):
pass
def _parse_datetime(value: object) -> datetime:
if isinstance(value, datetime):
if value.tzinfo is None or value.utcoffset() is None:
raise AuthorityAdapterError("authority datetime must be timezone-aware")
return value.astimezone(timezone.utc)
if not isinstance(value, str) or not value:
raise AuthorityAdapterError("authority datetime missing")
try:
dt = datetime.fromisoformat(value.replace("Z", "+00:00"))
except ValueError as exc:
raise AuthorityAdapterError("authority datetime invalid") from exc
if dt.tzinfo is None or dt.utcoffset() is None:
raise AuthorityAdapterError("authority datetime must include timezone")
return dt.astimezone(timezone.utc)
def _load_json_object(value: object, *, field: str) -> dict[str, object]:
if value in (None, ""):
return {}
if isinstance(value, Mapping):
return dict(value)
if not isinstance(value, str):
raise AuthorityAdapterError(f"{field} must be a JSON object")
try:
obj = json.loads(value)
except json.JSONDecodeError as exc:
raise AuthorityAdapterError(f"{field} is invalid JSON") from exc
if not isinstance(obj, dict):
raise AuthorityAdapterError(f"{field} must decode to an object")
return obj
def _metadata_identity(metadata: Mapping[str, object]) -> tuple[str, str]:
lineage = metadata.get(LINEAGE_METADATA_KEY)
if isinstance(lineage, Mapping):
owner = lineage.get("owner_principal_id")
mission = lineage.get("mission_id")
if isinstance(owner, str) and owner and isinstance(mission, str) and mission:
return owner, mission
# Pre-lineage/legacy compatibility. These keys are accepted only as
# authority-provided metadata; missing values fail closed rather than being
# invented by this adapter.
owner = metadata.get("owner_principal_id")
mission = metadata.get("mission_id")
if not isinstance(owner, str) or not owner:
raise AuthorityAdapterError("task owner_principal_id is not discoverable")
if not isinstance(mission, str) or not mission:
raise AuthorityAdapterError("task mission_id is not discoverable")
return owner, mission
def _metadata_evidence(metadata: Mapping[str, object]) -> tuple[str, ...]:
raw = metadata.get("evidence_refs")
if raw is None:
return ()
if not isinstance(raw, list) or not all(isinstance(x, str) for x in raw):
raise AuthorityAdapterError("evidence_refs must be a string list")
return tuple(dict.fromkeys(raw))
@runtime_checkable
class TaskPlaneReadback(Protocol):
"""Independent authority readback surface.
This may be backed by the TaskPlane query service, a read replica, or a
read-only SQLite authority. It must not mutate task state.
"""
def get_task_snapshot(self, task_id: str) -> TaskAuthoritySnapshot: ...
def list_descendant_snapshots(
self, root_task_id: str
) -> Sequence[TaskAuthoritySnapshot]: ...
def get_root_review_record(self, review_id: str) -> Mapping[str, object] | None: ...
def get_root_action_dispatch(self, review_id: str) -> RootActionDispatchReceipt | None: ...
@dataclass(frozen=True)
class CallableTaskPlaneReadback:
"""Readback adapter for an existing official TaskPlane query/service."""
get_task: Callable[[str], TaskAuthoritySnapshot]
list_descendants: Callable[[str], Sequence[TaskAuthoritySnapshot]]
get_review: Callable[[str], Mapping[str, object] | None] | None = None
get_dispatch: Callable[[str], RootActionDispatchReceipt | None] | None = None
def get_task_snapshot(self, task_id: str) -> TaskAuthoritySnapshot:
snapshot = self.get_task(task_id)
if not isinstance(snapshot, TaskAuthoritySnapshot):
raise AuthorityAdapterError("readback returned invalid task snapshot")
if snapshot.task_id != task_id:
raise AuthorityAdapterError("readback returned wrong task identity")
return snapshot
def list_descendant_snapshots(
self, root_task_id: str
) -> Sequence[TaskAuthoritySnapshot]:
values = tuple(self.list_descendants(root_task_id))
if not all(isinstance(v, TaskAuthoritySnapshot) for v in values):
raise AuthorityAdapterError("readback returned invalid descendant snapshot")
return values
def get_root_review_record(self, review_id: str) -> Mapping[str, object] | None:
if self.get_review is None:
raise AuthorityAdapterError("independent review readback is not configured")
return self.get_review(review_id)
def get_root_action_dispatch(self, review_id: str) -> RootActionDispatchReceipt | None:
if self.get_dispatch is None:
raise AuthorityAdapterError("independent dispatch readback is not configured")
return self.get_dispatch(review_id)
@dataclass(frozen=True)
class AuthoritySchemaReport:
task_table: str
columns: tuple[str, ...]
required_columns_present: bool
missing_columns: tuple[str, ...]
task_count: int
class ReadOnlySQLiteTaskPlane:
"""Read-only discovery/readback helper for the current TaskPlane SQLite authority.
This class never writes. It opens SQLite read-only, including committed WAL
data, and exists only for discovery, diagnostics and independent readback.
Execution writes must go through the live TaskPlane repository/service and
are supplied separately through ``TaskPlaneServiceBindings``.
"""
REQUIRED_TASK_COLUMNS = (
"task_id",
"status",
"revision",
"metadata_json",
"created_at",
)
def __init__(self, db_path: Path):
self.db_path = Path(db_path)
def _connect(self) -> sqlite3.Connection:
if not self.db_path.is_file():
raise AuthorityAdapterError(f"TaskPlane DB missing: {self.db_path}")
# A live WAL database is not immutable: ignoring its WAL can return
# stale state. as_uri also quotes URI metacharacters in file names.
uri = self.db_path.resolve().as_uri() + "?mode=ro"
con = sqlite3.connect(uri, uri=True)
con.row_factory = sqlite3.Row
return con
def inspect_schema(self) -> AuthoritySchemaReport:
con = self._connect()
try:
rows = list(con.execute("PRAGMA table_info(tasks)"))
columns = tuple(str(r["name"]) for r in rows)
missing = tuple(c for c in self.REQUIRED_TASK_COLUMNS if c not in columns)
count = int(con.execute("SELECT COUNT(*) FROM tasks").fetchone()[0]) if rows else 0
return AuthoritySchemaReport(
task_table="tasks",
columns=columns,
required_columns_present=not missing,
missing_columns=missing,
task_count=count,
)
finally:
con.close()
def _row_to_snapshot(self, row: sqlite3.Row) -> TaskAuthoritySnapshot:
keys = set(row.keys())
metadata = _load_json_object(
row["metadata_json"] if "metadata_json" in keys else None,
field="metadata_json",
)
owner, mission = _metadata_identity(metadata)
created_value = row["created_at"] if "created_at" in keys else None
return TaskAuthoritySnapshot(
task_id=str(row["task_id"]),
revision=int(row["revision"]),
status=str(row["status"]),
owner_principal_id=owner,
mission_id=mission,
created_at=_parse_datetime(created_value),
metadata=metadata,
evidence_refs=_metadata_evidence(metadata),
# A metadata flag cannot stand in for execution-integrity proof.
# The official service adapter must resolve verified receipts.
terminal_evidence_proven=False,
)
def get_task_snapshot(self, task_id: str) -> TaskAuthoritySnapshot:
con = self._connect()
try:
row = con.execute("SELECT * FROM tasks WHERE task_id=? LIMIT 1", (task_id,)).fetchone()
if row is None:
raise AuthorityAdapterError(f"task not found: {task_id}")
return self._row_to_snapshot(row)
finally:
con.close()
def list_descendant_snapshots(self, root_task_id: str) -> tuple[TaskAuthoritySnapshot, ...]:
con = self._connect()
try:
rows = list(con.execute("SELECT * FROM tasks ORDER BY task_id"))
finally:
con.close()
out: list[TaskAuthoritySnapshot] = []
for row in rows:
try:
snap = self._row_to_snapshot(row)
except AuthorityAdapterError:
# Legacy tasks without discoverable identity cannot safely be
# attached to a root by this read-only adapter.
continue
raw = snap.metadata.get(LINEAGE_METADATA_KEY)
if (
isinstance(raw, Mapping)
and raw.get("root_task_id") == root_task_id
and snap.task_id != root_task_id
):
out.append(snap)
return tuple(out)
@dataclass(frozen=True)
class TaskPlaneServiceBindings:
"""In-process bindings to the existing TaskPlane authority.
These callables must be backed by the real TaskPlane repository/service,
including its existing transactions, revision/CAS, journal and continuation
mechanisms. They must NOT be implemented as out-of-band SQLite mutations.
"""
get_task_snapshot: Callable[[str], TaskAuthoritySnapshot]
list_descendant_snapshots: Callable[[str], Sequence[TaskAuthoritySnapshot]]
reserve_sibling_sequence: Callable[[str, RelationType, str], int]
compare_and_swap_task_metadata: Callable[[str, int, Mapping[str, object]], bool]
get_root_review_record: Callable[[str], Mapping[str, object] | None]
compare_and_swap_root_review_record: Callable[
[str, int, Mapping[str, object]], bool
]
get_root_action_dispatch: Callable[[str], RootActionDispatchReceipt | None]
dispatch_root_action: Callable[
[RootReviewRecord, str], RootActionDispatchReceipt
]
class InProcessTaskPlaneLineageAdapter(TaskPlaneLineagePort):
"""Concrete TaskPlaneLineagePort backed by existing TaskPlane service calls."""
def __init__(
self,
bindings: TaskPlaneServiceBindings,
*,
readback: TaskPlaneReadback | None = None,
):
self._b = bindings
self._readback = readback
def get_task_snapshot(self, task_id: str) -> TaskAuthoritySnapshot:
return self._b.get_task_snapshot(task_id)
def list_descendant_snapshots(self, root_task_id: str) -> Sequence[TaskAuthoritySnapshot]:
return tuple(self._b.list_descendant_snapshots(root_task_id))
def reserve_sibling_sequence(
self,
*,
parent_task_id: str,
relation_type: RelationType,
child_task_id: str,
) -> int:
seq = self._b.reserve_sibling_sequence(
parent_task_id, relation_type, child_task_id
)
if not isinstance(seq, int) or seq < 1:
raise AuthorityAdapterError("TaskPlane returned invalid sibling sequence")
return seq
def compare_and_swap_task_metadata(
self,
*,
task_id: str,
expected_revision: int,
metadata_patch: Mapping[str, object],
) -> bool:
return bool(
self._b.compare_and_swap_task_metadata(
task_id, expected_revision, metadata_patch
)
)
def get_root_review_record(self, review_id: str) -> Mapping[str, object] | None:
return self._b.get_root_review_record(review_id)
def compare_and_swap_root_review_record(
self,
*,
root_task_id: str,
expected_root_revision: int,
review_record: Mapping[str, object],
) -> bool:
return bool(
self._b.compare_and_swap_root_review_record(
root_task_id, expected_root_revision, review_record
)
)
def get_root_action_dispatch(
self, review_id: str
) -> RootActionDispatchReceipt | None:
return self._b.get_root_action_dispatch(review_id)
def dispatch_root_action(
self,
*,
review: RootReviewRecord,
idempotency_key: str,
) -> RootActionDispatchReceipt:
return self._b.dispatch_root_action(review, idempotency_key)
def verify_task_readback(
self,
*,
task_id: str,
expected_revision_at_least: int,
expected_metadata: Mapping[str, object],
) -> TaskAuthoritySnapshot:
if self._readback is None:
raise AuthorityAdapterError("independent TaskPlane readback is not configured")
snap = self._readback.get_task_snapshot(task_id)
if snap.task_id != task_id:
raise AuthorityAdapterError("TaskPlane readback task identity mismatch")
if snap.revision < expected_revision_at_least:
raise AuthorityAdapterError("TaskPlane readback revision did not advance")
for key, expected in expected_metadata.items():
if snap.metadata.get(key) != expected:
raise AuthorityAdapterError(
f"TaskPlane readback metadata mismatch for {key}"
)
return snap
def verify_root_readback(
self,
*,
root_task_id: str,
expected_review_id: str,
) -> tuple[TaskAuthoritySnapshot, ...]:
"""Verify that independent authority descendants remain bound to root.
The persisted ROOT_REVIEW record itself may live in a journal rather
than the task row. This method verifies the task/lineage projection
independently while the service binding verifies the journal receipt.
"""
if self._readback is None:
raise AuthorityAdapterError("independent TaskPlane readback is not configured")
review = self.get_root_review_record(expected_review_id)
dispatch = self.get_root_action_dispatch(expected_review_id)
if (review is None or review.get("review_id") != expected_review_id
or review.get("root_task_id") != root_task_id or dispatch is None):
raise AuthorityAdapterError("root review or dispatch identity mismatch")
read_review = getattr(self._readback, "get_root_review_record", None)
read_dispatch = getattr(self._readback, "get_root_action_dispatch", None)
if not callable(read_review) or not callable(read_dispatch):
raise AuthorityAdapterError("independent review/dispatch readback is not configured")
if read_review(expected_review_id) != review:
raise AuthorityAdapterError("independent root review readback mismatch")
if read_dispatch(expected_review_id) != dispatch:
raise AuthorityAdapterError("independent dispatch readback mismatch")
root = self._readback.get_task_snapshot(root_task_id)
raw_root = root.metadata.get(LINEAGE_METADATA_KEY)
if root.task_id != root_task_id or not isinstance(raw_root, Mapping):
raise AuthorityAdapterError("root readback identity or lineage missing")
root_lineage = lineage_from_metadata(dict(raw_root))
if (root_lineage.relation_type != RelationType.ROOT
or root_lineage.task_id != root_task_id
or root_lineage.owner_principal_id != root.owner_principal_id
or root_lineage.mission_id != root.mission_id):
raise AuthorityAdapterError("root readback authority mismatch")
revision = review.get("root_revision_after")
if type(revision) is not int or root.revision < revision:
raise AuthorityAdapterError("root readback revision is stale")
descendants = tuple(self._readback.list_descendant_snapshots(root_task_id))
seen = set()
for snap in descendants:
raw = snap.metadata.get(LINEAGE_METADATA_KEY)
if not isinstance(raw, Mapping):
raise AuthorityAdapterError("descendant lineage missing")
lineage = lineage_from_metadata(dict(raw))
if (snap.task_id in seen or snap.task_id == root_task_id
or lineage.task_id != snap.task_id
or lineage.root_task_id != root_task_id
or lineage.owner_principal_id != root.owner_principal_id
or lineage.mission_id != root.mission_id
or snap.owner_principal_id != root.owner_principal_id
or snap.mission_id != root.mission_id
or lineage.root_acceptance_ref != root_lineage.root_acceptance_ref):
raise AuthorityAdapterError(
f"descendant {snap.task_id} is projected under wrong root"
)
seen.add(snap.task_id)
if review.get("trigger_child_task_id") not in seen:
raise AuthorityAdapterError("trigger child missing from independent readback")
return descendants
@property
def has_independent_readback(self) -> bool:
return self._readback is not None
@dataclass(frozen=True)
class AuthenticatedTaskContext:
principal_id: str
preferred_alias: str | None
mission_id: str
def require_context_matches_task(
context: AuthenticatedTaskContext,
task: TaskAuthoritySnapshot,
) -> None:
if context.principal_id != task.owner_principal_id:
raise AuthorityAdapterError("authenticated principal does not own task")
if context.mission_id != task.mission_id:
raise AuthorityAdapterError("authenticated mission does not match task")
@@ -0,0 +1,242 @@
"""Narrow staging Mission ingress authenticated by the registered Owner signer.
The signed payload binds the method, resource, body and idempotency key. This
adapter neither signs requests nor grants Git/worker/production capabilities.
"""
from __future__ import annotations
import argparse
import importlib.util
import json
from pathlib import Path
import re
import stat
import time
from http.server import BaseHTTPRequestHandler, HTTPServer
from typing import Any
from .task_plane.errors import (
ForbiddenError, TaskPlaneHTTPError, UnauthorizedError, ValidationError,
)
AUDIENCE = "aios.task-mission-authority.staging.v1"
NAMESPACE = "aios-owner-approval-v1"
MAX_BODY = 131072
REQUEST_FIELDS = frozenset({
"schema", "audience", "method", "path", "body", "idempotency_key",
"issued_at", "expires_at", "nonce",
})
def canonical_bytes(value: Any) -> bytes:
return json.dumps(value, sort_keys=True, separators=(",", ":"),
ensure_ascii=False, allow_nan=False).encode("utf-8")
def strict_object(pairs):
result = {}
for key, value in pairs:
if key in result:
raise ValidationError("duplicate JSON key")
result[key] = value
return result
class SignedMissionIngress:
def __init__(
self, service, verifier, *, terminal_proof_importer=None, clock=time.time
):
if not callable(getattr(verifier, "verify", None)):
raise ValueError("registered Owner verifier required")
identity = getattr(verifier, "signer_id", None)
if not isinstance(identity, str) or not identity.strip():
raise ValueError("registered signer identity required")
self.service = service
self.verifier = verifier
self.identity = identity
self.terminal_proof_importer = terminal_proof_importer
self.clock = clock
def handle(self, envelope: dict) -> tuple[int, dict]:
if not isinstance(envelope, dict) or set(envelope) != {
"request", "signature", "key_id"
}:
raise ValidationError("invalid signed mission envelope")
request = envelope["request"]
if not isinstance(request, dict) or set(request) != REQUEST_FIELDS:
raise ValidationError("invalid mission request fields")
if (request["schema"] != "aios.signed-mission-request.v1"
or request["audience"] != AUDIENCE):
raise ForbiddenError("mission request audience mismatch")
issued, expires = request["issued_at"], request["expires_at"]
if (type(issued) is not int or type(expires) is not int
or not 0 < expires - issued <= 300
or not issued <= self.clock() < expires):
raise UnauthorizedError("mission request expired or not yet valid")
if (not isinstance(request["nonce"], str)
or re.fullmatch(r"[A-Za-z0-9_-]{16,128}", request["nonce"]) is None):
raise ValidationError("invalid mission request nonce")
if (not isinstance(envelope["signature"], str)
or not isinstance(envelope["key_id"], str)):
raise UnauthorizedError("Owner signature required")
try:
verified = self.verifier.verify(
namespace=NAMESPACE, payload=canonical_bytes(request),
signature=envelope["signature"], key_id=envelope["key_id"],
)
except Exception:
verified = False
if verified is not True:
raise UnauthorizedError("Owner signature invalid")
method, path, body = request["method"], request["path"], request["body"]
key = request["idempotency_key"]
if method == "POST" and path == "/missions":
if not isinstance(body, dict) or not isinstance(key, str) or not key.strip():
raise ValidationError("mission body and idempotency key required")
return 201, self.service.create_mission(
body, key, actor_identity=self.identity,
)
if (method == "GET" and isinstance(path, str)
and re.fullmatch(r"/missions/[A-Za-z0-9][A-Za-z0-9._:-]{2,179}", path)
and body is None and key is None):
return 200, self.service.get_mission(
path.split("/")[2], actor_identity=self.identity,
)
if (method == "POST" and isinstance(path, str)
and re.fullmatch(r"/tasks/[A-Za-z0-9][A-Za-z0-9._:-]{2,179}/delegate", path)):
if not isinstance(body, dict) or not isinstance(key, str) or not key.strip():
raise ValidationError("child body and idempotency key required")
return 201, self.service.delegate_mission(
path.split("/")[2], body, key, actor_identity=self.identity,
)
if (method == "POST" and isinstance(path, str)
and re.fullmatch(
r"/missions/[A-Za-z0-9][A-Za-z0-9._:-]{2,179}/legacy-attachments",
path,
)):
if not isinstance(body, dict) or not isinstance(key, str) or not key.strip():
raise ValidationError(
"legacy attachment body and idempotency key required"
)
result = self.service.attach_legacy_attachment(
path.split("/")[2], body, key, actor_identity=self.identity,
)
return (200 if result.get("idempotent") is True else 201), result
if (method == "GET" and isinstance(path, str)
and re.fullmatch(
r"/missions/[A-Za-z0-9][A-Za-z0-9._:-]{2,179}/"
r"legacy-attachments/[A-Za-z0-9][A-Za-z0-9._:-]{2,179}",
path,
) and body is None and key is None):
pieces = path.split("/")
return 200, self.service.get_legacy_attachment(
pieces[2], pieces[4], actor_identity=self.identity,
)
if (method == "POST" and isinstance(path, str)
and re.fullmatch(
r"/tasks/[A-Za-z0-9][A-Za-z0-9._:-]{2,179}/reconcile-terminal-proof",
path,
)):
if body != {} or not isinstance(key, str) or not key.strip():
raise ValidationError(
"terminal proof trigger requires an empty body and idempotency key"
)
if self.terminal_proof_importer is None:
raise ForbiddenError("terminal proof importer is not configured")
return 200, self.terminal_proof_importer.reconcile(
path.split("/")[2], key, actor_identity=self.identity,
)
raise ForbiddenError("operation outside mission ingress scope")
def load_registered_verifier(path: Path):
metadata = path.lstat()
if (not stat.S_ISREG(metadata.st_mode) or metadata.st_uid != 0
or metadata.st_mode & 0o022):
raise ValueError("Owner verifier must be a root-owned immutable regular file")
spec = importlib.util.spec_from_file_location("aios_mission_owner_verifier", path)
if spec is None or spec.loader is None:
raise ValueError("Owner verifier module unavailable")
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module.GrantRootOwnerApprovalVerifier()
def make_handler(ingress: SignedMissionIngress):
class Handler(BaseHTTPRequestHandler):
def log_message(self, *_args):
pass # Do not log signed payloads or transport credentials.
def respond(self, status, value):
raw = canonical_bytes(value)
self.send_response(status)
self.send_header("Content-Type", "application/json")
self.send_header("Cache-Control", "no-store")
self.send_header("Content-Length", str(len(raw)))
self.end_headers()
self.wfile.write(raw)
def do_POST(self):
try:
if self.path != "/v1/signed-request":
raise ForbiddenError("unsupported mission ingress route")
if self.headers.get("Transfer-Encoding"):
raise ValidationError("transfer encoding is unsupported")
lengths = self.headers.get_all("Content-Length", [])
if len(lengths) != 1 or not lengths[0].isdigit():
raise ValidationError("one content length required")
length = int(lengths[0])
if not 0 < length <= MAX_BODY:
raise ValidationError("mission request too large or empty")
self.connection.settimeout(10)
raw = self.rfile.read(length)
if len(raw) != length:
raise ValidationError("truncated mission request")
envelope = json.loads(raw, object_pairs_hook=strict_object)
status, result = ingress.handle(envelope)
self.respond(status, result)
except TaskPlaneHTTPError as exc:
self.respond(exc.status_code, exc.to_payload())
except (ValueError, UnicodeError, RecursionError):
self.respond(400, {"error": "invalid_json"})
except Exception:
self.respond(500, {"error": "mission_authority_unavailable"})
return Handler
def main() -> None:
from .task_plane.api import TaskPlaneService
from .task_plane.mission import MissionAuthority, MissionPrincipalBinding
from .task_plane.policy import IdentityVerifier, PolicyEngine
from .task_plane.store import TaskPlaneStore
parser = argparse.ArgumentParser()
parser.add_argument("--db", type=Path, required=True)
parser.add_argument("--owner-verifier", type=Path, required=True)
parser.add_argument("--port", type=int, required=True)
args = parser.parse_args()
if not 1024 <= args.port <= 65535:
parser.error("unprivileged loopback port required")
verifier = load_registered_verifier(args.owner_verifier)
identity = verifier.signer_id
store = TaskPlaneStore(str(args.db), high_assurance=True)
authority = MissionAuthority(store, principal_bindings=[MissionPrincipalBinding(
authenticated_identity=identity, owner_principal_id=identity,
acceptance_verifier_id="identity-lineage-live-closure-v1",
allowed_task_capabilities=(
"code.read", "code.write", "test.execute",
"governed.write.file", "governed.write.branch",
),
max_risk="medium",
)])
service = TaskPlaneService(store, policy=PolicyEngine(
identity_verifier=IdentityVerifier({identity}), execution_enabled=False,
identity_capabilities={identity: {"task.creator", "task.read", "task.delegate"}},
), mission_authority=authority)
ingress = SignedMissionIngress(service, verifier)
# SQLite's existing connection and transaction lock share one request thread.
HTTPServer(("127.0.0.1", args.port), make_handler(ingress)).serve_forever()
if __name__ == "__main__":
main()
@@ -0,0 +1,317 @@
"""Narrow, terminal-only parameterized recovery over the formal Task store.
This adapter deliberately does *not* create a generic recovery authority. It
can only record an Owner-signed, exact-CAS terminal NOOP. A non-terminal
recovery remains the existing formal lineage/supersession operation.
The receipt uses the existing TaskStore ``idempotency`` uniqueness constraint
in the same authoritative database. Two fixed owner-authority scopes bind a
decision id and a nonce atomically; this is not a new nonce database or table.
"""
from __future__ import annotations
from datetime import datetime, timedelta, timezone
import hashlib
import json
import re
import sqlite3
from typing import Any, Callable, Mapping, Protocol
from aios_agent_task_protocol import TaskStore, canonical_json
from aios_task_lineage_supersession import (
OWNER_KEY_ID,
OWNER_NAMESPACE,
OWNER_PRINCIPAL,
REPOSITORY,
)
DECISION_SCHEMA = "aios.task-parameterized-recovery-decision.v1"
RECEIPT_SCHEMA = "aios.task-parameterized-recovery-receipt.v1"
ACTION_TERMINAL_NOOP = "TERMINAL_NOOP"
TERMINAL_STATUSES = frozenset({"COMPLETED", "CANCELLED", "FAILED_FINAL"})
MAX_TTL = timedelta(minutes=5)
NONCE_SCOPE = "aios.task-parameterized-recovery.v1:owner-nonce"
DECISION_SCOPE = "aios.task-parameterized-recovery.v1:owner-decision"
_HEX64 = re.compile(r"^[0-9a-f]{64}$")
_SAFE_ID = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:@/+:-]{2,511}$")
_BRANCH = re.compile(r"^agent/[A-Za-z0-9._/-]{1,220}$")
_FIELDS = frozenset(
{
"schema", "decision_id", "nonce", "action", "task_id",
"task_identity_sha256", "expected_status", "expected_attempt",
"expected_generation", "expected_revision", "expected_claimed_by",
"expected_lease_token_hash", "expected_lease_expires_at", "repository",
"target_branch", "owner_identity", "signature_namespace",
"signature_principal", "key_id", "issued_at", "not_before",
"expires_at", "one_time_use", "signature",
}
)
class ParameterizedRecoveryError(RuntimeError):
def __init__(self, code: str, http_status: int = 409) -> None:
super().__init__(code)
self.code = code
self.http_status = int(http_status)
class SignatureVerifier(Protocol):
def verify(self, *, namespace: str, payload: bytes, signature: str, key_id: str) -> bool: ...
def _fail(code: str, status: int = 409) -> None:
raise ParameterizedRecoveryError(code, status)
def _utc(value: object, field: str) -> datetime:
if not isinstance(value, str) or not value or len(value) > 80:
_fail(f"{field.upper()}_INVALID", 400)
try:
parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
except ValueError as exc:
raise ParameterizedRecoveryError(f"{field.upper()}_INVALID", 400) from exc
if parsed.tzinfo is None:
_fail(f"{field.upper()}_INVALID", 400)
return parsed.astimezone(timezone.utc)
def _text(value: object, field: str, *, maximum: int = 1024) -> str:
if not isinstance(value, str) or not value or len(value) > maximum:
_fail(f"{field.upper()}_INVALID", 400)
return value
def canonical_decision_payload(raw: Mapping[str, Any]) -> dict[str, Any]:
return {key: raw[key] for key in raw if key != "signature"}
def _identity_digest(row: sqlite3.Row) -> str:
value = {
"task_id": row["task_id"],
"goal": row["goal"],
"instructions": row["instructions"],
"success_criteria": json.loads(row["success_criteria_json"]),
"required_capabilities": json.loads(row["required_capabilities_json"]),
"constraints": json.loads(row["constraints_json"]),
"metadata": json.loads(row["metadata_json"]),
"priority": row["priority"],
"max_attempts": row["max_attempts"],
}
return hashlib.sha256(canonical_json(value).encode("utf-8")).hexdigest()
def validate_decision(raw: Mapping[str, Any], *, now: datetime) -> dict[str, Any]:
if not isinstance(raw, Mapping) or set(raw) != _FIELDS:
_fail("DECISION_FIELDS_INVALID", 400)
exact = {
"schema": DECISION_SCHEMA,
"action": ACTION_TERMINAL_NOOP,
"repository": REPOSITORY,
"owner_identity": OWNER_PRINCIPAL,
"signature_namespace": OWNER_NAMESPACE,
"signature_principal": OWNER_PRINCIPAL,
"key_id": OWNER_KEY_ID,
"one_time_use": True,
}
for field, expected in exact.items():
if raw[field] != expected:
_fail(f"{field.upper()}_MISMATCH", 403)
for field in ("task_id", "decision_id", "nonce"):
if _SAFE_ID.fullmatch(_text(raw[field], field, maximum=512)) is None:
_fail(f"{field.upper()}_INVALID", 400)
if not isinstance(raw["task_identity_sha256"], str) or not _HEX64.fullmatch(raw["task_identity_sha256"]):
_fail("TASK_IDENTITY_SHA256_INVALID", 400)
if not isinstance(raw["target_branch"], str) or not _BRANCH.fullmatch(raw["target_branch"]):
_fail("TARGET_BRANCH_INVALID", 400)
for field, minimum in (("expected_attempt", 0), ("expected_generation", 1), ("expected_revision", 1)):
if isinstance(raw[field], bool) or not isinstance(raw[field], int) or raw[field] < minimum:
_fail(f"{field.upper()}_INVALID", 400)
for field in ("expected_claimed_by", "expected_lease_token_hash", "expected_lease_expires_at"):
if raw[field] is not None:
_text(raw[field], field, maximum=512)
if raw["expected_status"] not in TERMINAL_STATUSES:
_fail("EXPECTED_STATUS_NOT_TERMINAL", 409)
_text(raw["signature"], "signature", maximum=32768)
issued_at = _utc(raw["issued_at"], "issued_at")
not_before = _utc(raw["not_before"], "not_before")
expires_at = _utc(raw["expires_at"], "expires_at")
if issued_at > not_before or not_before > expires_at:
_fail("DECISION_TIME_ORDER_INVALID", 403)
if expires_at <= issued_at or expires_at - issued_at > MAX_TTL:
_fail("DECISION_TTL_INVALID", 403)
if now < not_before or now >= expires_at or issued_at > now + timedelta(seconds=5) or now - issued_at > MAX_TTL:
_fail("DECISION_EXPIRED_OR_NOT_YET_VALID", 403)
return dict(raw)
class ParameterizedRecoveryController:
"""Consumes a validated terminal-NOOP decision once, in the TaskStore DB."""
def __init__(
self,
store: TaskStore,
verifier: SignatureVerifier,
*,
clock: Callable[[], datetime] | None = None,
identity_digest: Callable[[sqlite3.Row], str] = _identity_digest,
) -> None:
if not isinstance(store, TaskStore) or verifier is None:
_fail("FORMAL_TASK_AUTHORITY_REQUIRED", 503)
self.store = store
self.verifier = verifier
self.clock = clock or (lambda: datetime.now(timezone.utc))
self.identity_digest = identity_digest
def _now(self) -> datetime:
value = self.clock()
if value.tzinfo is None:
value = value.replace(tzinfo=timezone.utc)
return value.astimezone(timezone.utc)
@staticmethod
def _assert_schema(connection: sqlite3.Connection) -> None:
tables = {str(row[0]) for row in connection.execute("SELECT name FROM sqlite_master WHERE type='table'")}
if not {"tasks", "events", "idempotency", "task_lineage_supersession_receipts"}.issubset(tables):
_fail("FORMAL_LINEAGE_SCHEMA_UNAVAILABLE", 503)
columns = {str(row[1]) for row in connection.execute("PRAGMA table_info(idempotency)")}
if not {"scope", "key", "payload_digest", "response_json", "created_at"}.issubset(columns):
_fail("FORMAL_IDEMPOTENCY_SCHEMA_UNAVAILABLE", 503)
def _verify_task(self, row: sqlite3.Row, decision: Mapping[str, Any]) -> None:
if self.identity_digest(row) != decision["task_identity_sha256"]:
_fail("TASK_IDENTITY_DIGEST_MISMATCH")
if (
row["task_id"] != decision["task_id"]
or row["status"] != decision["expected_status"]
or int(row["revision"]) != decision["expected_revision"]
or int(row["attempt"]) != decision["expected_attempt"]
or row["claimed_by"] != decision["expected_claimed_by"]
or row["lease_token_hash"] != decision["expected_lease_token_hash"]
or row["lease_expires_at"] != decision["expected_lease_expires_at"]
):
_fail("TASK_EXACT_CAS_MISMATCH")
try:
metadata = json.loads(row["metadata_json"])
constraints = json.loads(row["constraints_json"])
except (TypeError, ValueError) as exc:
raise ParameterizedRecoveryError("TASK_BINDING_JSON_INVALID") from exc
execution = metadata.get("execution") if isinstance(metadata, Mapping) else None
if not isinstance(execution, Mapping) or constraints.get("generation") != decision["expected_generation"]:
_fail("TASK_EXECUTION_BINDING_INVALID")
if (
execution.get("task_id") != decision["task_id"]
or execution.get("repository") != decision["repository"]
or execution.get("target_branch") != decision["target_branch"]
):
_fail("TASK_EXECUTION_BINDING_MISMATCH")
@staticmethod
def _receipt(decision: Mapping[str, Any], decision_sha: str, row: sqlite3.Row, now: datetime) -> dict[str, Any]:
return {
"schema": RECEIPT_SCHEMA,
"state": "NOOP",
"noop_reason": "TERMINAL_STATUS",
"decision_id": decision["decision_id"],
"nonce": decision["nonce"],
"task_id": decision["task_id"],
"status": row["status"],
"revision": int(row["revision"]),
"owner_decision_sha256": decision_sha,
"owner_signature_namespace": OWNER_NAMESPACE,
"owner_signature_principal": OWNER_PRINCIPAL,
"owner_signature_key_id": OWNER_KEY_ID,
"owner_signature_sha256": hashlib.sha256(decision["signature"].encode()).hexdigest(),
"exact_identity_cas": True,
"one_time_use": True,
"observed_at": now.isoformat(),
}
@staticmethod
def _read_exact_replay(
nonce_row: sqlite3.Row | None, decision_row: sqlite3.Row | None, decision: Mapping[str, Any], decision_sha: str
) -> dict[str, Any] | None:
if nonce_row is None and decision_row is None:
return None
if nonce_row is None or decision_row is None:
present = nonce_row if nonce_row is not None else decision_row
if present["payload_digest"] != decision_sha:
_fail("DECISION_REPLAY_MISMATCH")
_fail("RECOVERY_RECEIPT_INCOMPLETE", 500)
if nonce_row["payload_digest"] != decision_sha or decision_row["payload_digest"] != decision_sha:
_fail("DECISION_REPLAY_MISMATCH")
try:
nonce_receipt = json.loads(nonce_row["response_json"])
decision_receipt = json.loads(decision_row["response_json"])
except (TypeError, ValueError) as exc:
raise ParameterizedRecoveryError("RECOVERY_RECEIPT_INVALID", 500) from exc
if nonce_receipt != decision_receipt or (
nonce_receipt.get("schema") != RECEIPT_SCHEMA
or nonce_receipt.get("decision_id") != decision["decision_id"]
or nonce_receipt.get("nonce") != decision["nonce"]
or nonce_receipt.get("task_id") != decision["task_id"]
or nonce_receipt.get("owner_decision_sha256") != decision_sha
):
_fail("DECISION_REPLAY_MISMATCH")
return dict(nonce_receipt)
def recover_terminal_noop(self, raw: Mapping[str, Any]) -> dict[str, Any]:
now = self._now()
decision = validate_decision(raw, now=now)
payload = canonical_decision_payload(raw)
payload_bytes = canonical_json(payload).encode("utf-8")
if not self.verifier.verify(
namespace=OWNER_NAMESPACE,
payload=payload_bytes,
signature=decision["signature"],
key_id=OWNER_KEY_ID,
):
_fail("DECISION_SIGNATURE_INVALID", 403)
decision_sha = hashlib.sha256(payload_bytes).hexdigest()
with self.store._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
try:
self._assert_schema(connection)
row = connection.execute("SELECT * FROM tasks WHERE task_id=?", (decision["task_id"],)).fetchone()
if row is None:
_fail("TASK_NOT_FOUND", 404)
self._verify_task(row, decision)
nonce_row = connection.execute(
"SELECT payload_digest,response_json FROM idempotency WHERE scope=? AND key=?",
(NONCE_SCOPE, decision["nonce"]),
).fetchone()
decision_row = connection.execute(
"SELECT payload_digest,response_json FROM idempotency WHERE scope=? AND key=?",
(DECISION_SCOPE, decision["decision_id"]),
).fetchone()
lineage_row = connection.execute(
"SELECT 1 FROM task_lineage_supersession_receipts WHERE decision_id=? OR nonce=?",
(decision["decision_id"], decision["nonce"]),
).fetchone()
if lineage_row is not None:
_fail("DECISION_REPLAY_MISMATCH")
replay = self._read_exact_replay(nonce_row, decision_row, decision, decision_sha)
if replay is not None:
connection.commit()
return {**replay, "idempotent": True}
receipt = self._receipt(decision, decision_sha, row, now)
receipt_json = canonical_json(receipt)
for scope, key in ((NONCE_SCOPE, decision["nonce"]), (DECISION_SCOPE, decision["decision_id"])):
connection.execute(
"INSERT INTO idempotency(scope,key,payload_digest,response_json,created_at) VALUES(?,?,?,?,?)",
(scope, key, decision_sha, receipt_json, now.isoformat()),
)
connection.execute(
"INSERT INTO events(task_id,agent_id,event_type,payload_json,created_at) VALUES(?,?,?,?,?)",
(decision["task_id"], None, "TASK_PARAMETERIZED_RECOVERY_NOOP", receipt_json, now.isoformat()),
)
connection.commit()
return {**receipt, "idempotent": False}
except Exception:
connection.rollback()
raise
# There is intentionally no generic resume/reclaim path. The existing
# lineage controller remains the only predecessor/successor authority.
recover = recover_terminal_noop
@@ -0,0 +1,167 @@
"""Read-only V3 issuer scope over the admitted producer's durable receipt.
The V1/V2 provider remains authoritative for those schemas. V3 validation
and Job readback are delegated to the existing admission producer contract;
this module never reserves a Job or changes the canonical plan digest.
"""
from __future__ import annotations
from pathlib import Path
import stat
from typing import Any, Callable, Mapping
from aios_governed_task_bootstrap import GovernedTaskBootstrap
from aios_task_admission_bootstrap import TaskAdmissionError
from aios_task_admission_live_adapters import (
ProducerReceiptError,
_FLProducerReceiptReserver,
load_v3_plan,
validate_v3_plan,
)
from governed_runtime_scope import (
GovernedScopeError,
RuntimeScope,
_branch,
_generation,
_hex64,
_job_id,
_normalize_caps,
_normalize_files,
_oid40,
_task_id,
_trust_digest,
)
def _deny_mutation(*_args: Any, **_kwargs: Any) -> None:
raise GovernedScopeError("ISSUER_PRODUCER_MUTATION_FORBIDDEN")
def _read_only_bootstrap(root: Path) -> GovernedTaskBootstrap:
"""Construct only the read-only binding view; __init__ creates directories."""
root = Path(root)
for path in (root, root / "task-bindings", root / "jobs"):
metadata = path.lstat()
if (stat.S_ISLNK(metadata.st_mode) or not stat.S_ISDIR(metadata.st_mode)
or stat.S_IMODE(metadata.st_mode) & 0o022):
raise GovernedScopeError("PRODUCER_STATE_ROOT_INVALID")
view = object.__new__(GovernedTaskBootstrap)
view.root = root
view.task_meta_root = root / "task-bindings"
view.job_root = root / "jobs"
view.create = _deny_mutation
view._store = _deny_mutation
view._write = _deny_mutation
return view
class IssuerV3ReceiptScopeProvider:
"""Delegate legacy plans, and resolve V3 with an existing Job only."""
def __init__(
self, *, legacy: Any, producer_state_root: Path, plan_root: Path,
grant_reader: Callable[..., Mapping[str, Any]],
plan_loader: Callable[[Path, Mapping[str, Any]], Mapping[str, Any]] = load_v3_plan,
) -> None:
self.legacy = legacy
self.producer_state_root = Path(producer_state_root)
self.plan_root = Path(plan_root)
self.grant_reader = grant_reader
self.plan_loader = plan_loader
def inspect_v3_binding(
self, *, grant_raw: Mapping[str, Any], plan: Mapping[str, Any],
) -> tuple[str, Mapping[str, Any]]:
"""Validate original bytes and durable receipt without Task mutation.
This method can inspect an expired historical fixture. ``resolve``
separately enforces live Task and grant state before issuing scope.
"""
try:
original = self.plan_loader(self.plan_root, grant_raw)
if dict(original) != dict(plan):
raise GovernedScopeError("EXECUTION_PLAN_READBACK_CONFLICT")
# Keep the main V3 validator in the issuer contract even when a
# test supplies an isolated loader. It binds the original plan,
# including risk, hashes, capabilities and execution digest.
from aios_task_admission_live_adapters import canonical_json_bytes
validate_v3_plan(grant=grant_raw, raw=canonical_json_bytes(original))
bootstrap = _read_only_bootstrap(self.producer_state_root)
runtime = type("ReadOnlyIssuerRuntime", (), {
"bootstrap": bootstrap, "bindings": {},
})()
reserver = _FLProducerReceiptReserver(runtime)
job_id = reserver.existing_v3_job(grant_raw, original)
if job_id is None:
raise GovernedScopeError("PRODUCER_RECEIPT_MISSING")
readback = reserver.readbacks.get(str(grant_raw["task_id"]))
proof = readback.get("reservation_readback") if isinstance(readback, Mapping) else None
if not isinstance(proof, Mapping) or proof.get("job_id") != job_id:
raise GovernedScopeError("PRODUCER_RECEIPT_BINDING_INVALID")
return job_id, dict(proof)
except (TaskAdmissionError, ProducerReceiptError, OSError, KeyError, TypeError, ValueError) as exc:
raise GovernedScopeError(str(exc) or "PRODUCER_RECEIPT_INVALID") from exc
def resolve(
self, *, task: Any, execution: Any, grant: Any,
plan: Mapping[str, Any], trust: Any, now: int,
) -> RuntimeScope:
if plan.get("schema") != "aios.task-admission-execution-plan.v3":
return self.legacy.resolve(
task=task, execution=execution, grant=grant,
plan=plan, trust=trust, now=now,
)
task_id = _task_id(task.task_id)
exec_job = _job_id(execution.external_job_id)
exec_digest = _hex64(execution.execution_digest, "execution_digest")
exec_branch = _branch(execution.target_branch)
exec_generation = _generation(execution.generation)
grant_raw = self.grant_reader(
task_id=task_id, execution_digest=exec_digest,
repository=execution.repository, target_branch=exec_branch,
generation=exec_generation, now=int(now),
)
producer_job, _proof = self.inspect_v3_binding(
grant_raw=grant_raw, plan=plan,
)
if exec_job != producer_job:
raise GovernedScopeError("EXECUTION_PRODUCER_JOB_MISMATCH")
if str(task.status).upper() not in {"CLAIMED", "RUNNING"}:
raise GovernedScopeError("TASK_NOT_CLAIMED")
if grant.status != "ADMITTED" or int(grant.expires_at) <= int(now):
raise GovernedScopeError("GRANT_EXPIRED_OR_NOT_ADMITTED")
expected = {
"task_id": task_id,
"repository": execution.repository,
"target_branch": exec_branch,
"generation": exec_generation,
"execution_digest": exec_digest,
"authority_base_oid": _oid40(grant.authority_base_oid, "authority_base_oid"),
"grant_id": grant.grant_id,
}
if (execution.repository != "aios/aios"
or execution.task_id != task_id
or any(grant_raw.get(key) != value for key, value in expected.items())):
raise GovernedScopeError("V3_GRANT_EXECUTION_BINDING_MISMATCH")
caps = _normalize_caps(grant.requested_capabilities)
files = _normalize_files(grant.file_allowlist)
if (caps != _normalize_caps(grant_raw["capabilities"])
or files != _normalize_files(grant_raw["file_allowlist"])):
raise GovernedScopeError("V3_GRANT_SCOPE_MISMATCH")
if (not trust.issuer_id or not trust.key_id or not trust.public_key
or "TASK_PRINCIPAL" not in str(trust.purpose).upper().replace("-", "_")
or not str(trust.bridge_id).strip()):
raise GovernedScopeError("TRUST_IDENTITY_INVALID")
trust_digest = _trust_digest(trust)
if (trust.scope_manifest_sha256 is not None
and _hex64(trust.scope_manifest_sha256, "scope_manifest_sha256") != trust_digest):
raise GovernedScopeError("TRUST_MANIFEST_DIGEST_MISMATCH")
return RuntimeScope(
scope_id=f"governed-runtime:{task_id}:g{exec_generation}:{exec_digest[:16]}",
task_id=task_id, external_job_id=producer_job,
repository=execution.repository, target_branch=exec_branch,
generation=exec_generation, execution_digest=exec_digest,
capabilities=caps, trust_manifest_sha256=trust_digest,
grant_id=grant.grant_id, grant_expires_at=int(grant.expires_at),
)
@@ -0,0 +1,511 @@
"""Authenticated, trigger-only import of authoritative terminal proof.
The caller authorizes reconciliation but supplies no proof fields. Proof is
read by a configured server-side provider and is accepted only after the
provider validates an authenticated authority document and its immutable
cross-system tuple.
"""
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime, timezone
from hashlib import sha256
import json
import re
from typing import Any, Callable, Mapping, Protocol
from .aios_task_lineage_integration import LINEAGE_METADATA_KEY
from .task_plane.errors import ConflictError, ForbiddenError, ValidationError
from .task_plane.models import ResultEnvelope, TaskRecord
from .task_plane.policy import PolicyDecision
PROOF_SCHEMA = "aios.task-terminal-authority-proof.v1"
RECEIPT_SCHEMA = "aios.task-terminal-import-receipt.v1"
REQUIRED_SOURCES = frozenset(
{
"agent_task",
"admission",
"job_producer",
"execution_alias",
"governed_job",
"bridge",
"authority_git",
}
)
DOCUMENT_FIELDS = frozenset(
{
"schema",
"task_id",
"mission_id",
"root_task_id",
"parent_task_id",
"acceptance_condition",
"agent_task_status",
"admission_status",
"execution_outcome",
"external_job_id",
"internal_job_id",
"repository",
"base_revision",
"target_branch",
"generation",
"execution_plan_digest",
"task_principal_ref",
"task_lease_ref",
"writer_lease_ref",
"governed_commit_oid",
"authority_ref_oid",
"postimage_hashes",
"source_receipt_digests",
"attestation_ref",
"started_at",
"completed_at",
}
)
_HEX64 = re.compile(r"[0-9a-f]{64}")
_GIT_OID = re.compile(r"[0-9a-f]{40}(?:[0-9a-f]{24})?")
_IDENTIFIER = re.compile(r"[A-Za-z0-9][A-Za-z0-9._:/-]{2,255}")
def _canonical_bytes(value: Mapping[str, object]) -> bytes:
return json.dumps(
dict(value), ensure_ascii=False, sort_keys=True, separators=(",", ":")
).encode("utf-8")
def _nonempty_string(value: object, field: str) -> str:
if not isinstance(value, str) or not value or len(value) > 1024:
raise ForbiddenError(f"authoritative proof {field} invalid")
return value
def _identifier(value: object, field: str) -> str:
text = _nonempty_string(value, field)
if _IDENTIFIER.fullmatch(text) is None:
raise ForbiddenError(f"authoritative proof {field} invalid")
return text
def _utc_time(value: object, field: str) -> str:
text = _nonempty_string(value, field)
try:
parsed = datetime.fromisoformat(text.replace("Z", "+00:00"))
except ValueError as exc:
raise ForbiddenError(f"authoritative proof {field} invalid") from exc
if parsed.tzinfo is None or parsed.utcoffset() is None:
raise ForbiddenError(f"authoritative proof {field} lacks timezone")
return parsed.astimezone(timezone.utc).isoformat().replace("+00:00", "Z")
@dataclass(frozen=True)
class VerifiedTerminalProof:
document: dict[str, object]
@property
def task_id(self) -> str:
return str(self.document["task_id"])
@property
def attestation_ref(self) -> str:
return str(self.document["attestation_ref"])
@property
def started_at(self) -> str:
return str(self.document["started_at"])
@property
def completed_at(self) -> str:
return str(self.document["completed_at"])
def evidence(self) -> dict[str, object]:
return {
"kind": "acceptance-proof",
"proof_schema": PROOF_SCHEMA,
"authority_document_digest": sha256(
_canonical_bytes(self.document)
).hexdigest(),
"task_id": self.task_id,
"mission_id": self.document["mission_id"],
"root_task_id": self.document["root_task_id"],
"parent_task_id": self.document["parent_task_id"],
"acceptance_condition": self.document["acceptance_condition"],
"external_job_id": self.document["external_job_id"],
"internal_job_id": self.document["internal_job_id"],
"repository": self.document["repository"],
"base_revision": self.document["base_revision"],
"target_branch": self.document["target_branch"],
"generation": self.document["generation"],
"execution_plan_digest": self.document["execution_plan_digest"],
"task_principal_ref": self.document["task_principal_ref"],
"task_lease_ref": self.document["task_lease_ref"],
"writer_lease_ref": self.document["writer_lease_ref"],
"governed_commit_oid": self.document["governed_commit_oid"],
"authority_ref_oid": self.document["authority_ref_oid"],
"postimage_hashes": self.document["postimage_hashes"],
"source_receipt_digests": self.document["source_receipt_digests"],
"attestation_ref": self.attestation_ref,
}
def outputs(self) -> list[dict[str, object]]:
return [
{
"kind": "governed-commit-readback",
"repository": self.document["repository"],
"target_branch": self.document["target_branch"],
"governed_commit_oid": self.document["governed_commit_oid"],
"postimage_hashes": self.document["postimage_hashes"],
}
]
class AuthorityDocumentReader(Protocol):
def __call__(self, task_id: str) -> Mapping[str, object]: ...
class AuthorityDocumentVerifier(Protocol):
def __call__(self, document: Mapping[str, object]) -> bool: ...
class AuthenticatedTerminalProofProvider:
"""Read and validate one aggregate document backed by existing authorities.
``reader`` and ``verifier`` are mandatory runtime bindings. The verifier
must authenticate the referenced AgentTask/admission/Job/Bridge/Git
receipts using already configured trust roots. This class does not confer
trust on an arbitrary mapping.
"""
def __init__(
self,
*,
reader: AuthorityDocumentReader,
verifier: AuthorityDocumentVerifier,
task_reader: Callable[[str], TaskRecord],
worker_identity: str,
) -> None:
if not callable(reader) or not callable(verifier) or not callable(task_reader):
raise ValueError("authenticated proof reader, verifier and task reader required")
self._reader = reader
self._verifier = verifier
self._task_reader = task_reader
self.worker_identity = _identifier(worker_identity, "worker_identity")
@staticmethod
def _parse(raw: Mapping[str, object]) -> dict[str, object]:
if not isinstance(raw, Mapping) or set(raw) != DOCUMENT_FIELDS:
raise ForbiddenError("authoritative proof document shape invalid")
data = dict(raw)
if data["schema"] != PROOF_SCHEMA:
raise ForbiddenError("authoritative proof schema invalid")
for field in (
"task_id", "mission_id", "root_task_id", "parent_task_id",
"external_job_id", "internal_job_id", "task_principal_ref", "task_lease_ref",
"writer_lease_ref",
):
data[field] = _identifier(data[field], field)
for field in ("acceptance_condition", "repository", "base_revision", "target_branch"):
data[field] = _nonempty_string(data[field], field)
if data["agent_task_status"] != "SUCCEEDED":
raise ForbiddenError("AgentTask is not authoritatively successful")
if data["admission_status"] != "ADMITTED":
raise ForbiddenError("task admission is not authoritative")
if data["execution_outcome"] != "APPLIED_VERIFIED":
raise ForbiddenError("governed execution outcome is not verified")
if type(data["generation"]) is not int or int(data["generation"]) < 1:
raise ForbiddenError("authoritative proof generation invalid")
if not isinstance(data["execution_plan_digest"], str) or _HEX64.fullmatch(
data["execution_plan_digest"]
) is None:
raise ForbiddenError("authoritative execution plan digest invalid")
for field in ("governed_commit_oid", "authority_ref_oid"):
if not isinstance(data[field], str) or _GIT_OID.fullmatch(data[field]) is None:
raise ForbiddenError(f"authoritative proof {field} invalid")
if data["governed_commit_oid"] != data["authority_ref_oid"]:
raise ForbiddenError("authority ref does not read back governed commit")
images = data["postimage_hashes"]
if (
not isinstance(images, Mapping)
or not images
or len(images) > 256
or any(
not isinstance(path, str)
or not path
or path.startswith(("/", "\\"))
or ".." in path.replace("\\", "/").split("/")
or not isinstance(digest, str)
or _HEX64.fullmatch(digest) is None
for path, digest in images.items()
)
):
raise ForbiddenError("authoritative postimage hashes invalid")
data["postimage_hashes"] = dict(sorted(images.items()))
sources = data["source_receipt_digests"]
if (
not isinstance(sources, Mapping)
or set(sources) != REQUIRED_SOURCES
or any(
not isinstance(digest, str) or _HEX64.fullmatch(digest) is None
for digest in sources.values()
)
):
raise ForbiddenError("authoritative source receipt set invalid")
data["source_receipt_digests"] = dict(sorted(sources.items()))
data["attestation_ref"] = _nonempty_string(data["attestation_ref"], "attestation_ref")
data["started_at"] = _utc_time(data["started_at"], "started_at")
data["completed_at"] = _utc_time(data["completed_at"], "completed_at")
if datetime.fromisoformat(data["completed_at"].replace("Z", "+00:00")) < datetime.fromisoformat(
data["started_at"].replace("Z", "+00:00")
):
raise ForbiddenError("authoritative proof time order invalid")
return data
@staticmethod
def _assert_task_binding(task: TaskRecord, data: Mapping[str, object]) -> None:
lineage = task.task_plan.metadata.get(LINEAGE_METADATA_KEY)
if not isinstance(lineage, Mapping) or lineage.get("relation_type") == "ROOT":
raise ForbiddenError("terminal proof requires a canonical lineage child")
expected = {
"task_id": task.task_id,
"mission_id": task.mission_id,
"root_task_id": task.root_task_id,
"parent_task_id": task.task_plan.parent_task_id,
"acceptance_condition": task.task_plan.metadata.get("acceptance_condition"),
}
if any(data.get(field) != value or value in (None, "") for field, value in expected.items()):
raise ForbiddenError("authoritative proof TaskPlane binding mismatch")
if any(lineage.get(field) != expected[field] for field in expected if field != "acceptance_condition"):
raise ForbiddenError("authoritative proof lineage binding mismatch")
def collect(self, task: TaskRecord) -> VerifiedTerminalProof:
raw = self._reader(task.task_id)
if not isinstance(raw, Mapping) or self._verifier(raw) is not True:
raise ForbiddenError("authoritative terminal proof authentication failed")
data = self._parse(raw)
self._assert_task_binding(task, data)
return VerifiedTerminalProof(data)
def verify_evidence(self, task: TaskRecord, evidences: list[dict]) -> bool:
proof = self.collect(task)
return evidences == [proof.evidence()]
def verify_result(self, result: ResultEnvelope) -> bool:
if result.worker_identity != self.worker_identity or result.status != "COMPLETED":
return False
try:
task = self._task_reader(result.task_id)
proof = self.collect(task)
except Exception:
return False
return bool(
result.attestation_ref == proof.attestation_ref
and result.evidence == [proof.evidence()]
and result.outputs == proof.outputs()
and result.started_at == proof.started_at
and result.completed_at == proof.completed_at
and result.operation_digest == task.task_plan.operation_digest()
)
class TerminalProofEvidenceVerifier:
"""PolicyEngine evidence verifier backed by authenticated authority reads."""
def __init__(self, provider: AuthenticatedTerminalProofProvider) -> None:
self.provider = provider
def verify(self, task: TaskRecord, evidences: list[dict]) -> PolicyDecision:
try:
allowed = self.provider.verify_evidence(task, evidences)
except Exception:
allowed = False
return PolicyDecision(allowed, "" if allowed else "terminal proof evidence invalid")
class TerminalProofImporter:
"""Reconcile a server-verified proof through TaskPlane claim/result paths."""
def __init__(
self,
service: Any,
*,
provider: AuthenticatedTerminalProofProvider | None,
worker_id: str | None,
worker_identity: str | None,
lease_seconds: int = 300,
) -> None:
self.service = service
self.provider = provider
self.worker_id = worker_id
self.worker_identity = worker_identity
if provider is not None:
if not worker_id or not worker_identity:
raise ValueError("terminal proof worker binding required")
_identifier(worker_id, "worker_id")
_identifier(worker_identity, "worker_identity")
if provider.worker_identity != worker_identity:
raise ValueError("terminal proof provider identity mismatch")
if not 30 <= int(lease_seconds) <= 3600:
raise ValueError("terminal proof lease out of range")
self.lease_seconds = int(lease_seconds)
@staticmethod
def expected_idempotency_key(task: TaskRecord) -> str:
return f"terminal-proof:{task.mission_id}:{task.task_id}"
def _authorized_task(self, task_id: str, actor_identity: str) -> TaskRecord:
self.service.get_task(task_id, actor_identity)
task = self.service.store.get_task(task_id)
lineage = task.task_plan.metadata.get(LINEAGE_METADATA_KEY)
if (
not isinstance(lineage, Mapping)
or lineage.get("relation_type") == "ROOT"
or lineage.get("task_id") != task.task_id
or lineage.get("mission_id") != task.mission_id
or lineage.get("root_task_id") != task.root_task_id
or lineage.get("parent_task_id") != task.task_plan.parent_task_id
):
raise ForbiddenError("terminal proof target is not a canonical child")
return task
def _existing_receipt(
self, task: TaskRecord, actor_identity: str
) -> dict[str, object] | None:
if self.service.lineage is None:
raise ForbiddenError("TaskPlane lineage is not configured")
snapshot = self.service.lineage.binding.get_task_snapshot(task.task_id)
if not snapshot.terminal_evidence_proven:
return None
if snapshot.status != "COMPLETED":
raise ConflictError("terminal proof target did not complete successfully")
review = self.service.lineage.binding.get_root_review_for_trigger(task.task_id)
if review is None:
raise ConflictError("terminal proof lacks durable root review")
evidence_rows = self.service.store.list_evidence(task.task_id)
by_id = {str(row.get("evidence_id")): row for row in evidence_rows}
if not snapshot.evidence_refs or any(ref not in by_id for ref in snapshot.evidence_refs):
raise ConflictError("terminal proof evidence readback mismatch")
envelopes = []
for ref in snapshot.evidence_refs:
try:
envelope = json.loads(str(by_id[ref]["envelope_json"]))
except (KeyError, TypeError, ValueError, json.JSONDecodeError) as exc:
raise ConflictError("terminal proof evidence readback invalid") from exc
envelopes.append(envelope)
claim_ids = {item.get("claim_id") for item in envelopes}
evidence_payloads = [item.get("payload") for item in envelopes]
if (
len(claim_ids) != 1
or len(evidence_payloads) != 1
or any(item.get("worker_identity") != self.worker_identity for item in envelopes)
or envelopes[0].get("kind") != "acceptance-proof"
or not isinstance(evidence_payloads[0], dict)
or not self.provider.verify_evidence(task, evidence_payloads)
or not self.service.store.verify_evidence_integrity(task.task_id)
or not self.service.store.verify_event_chain(task.task_id)
):
raise ConflictError("terminal proof receipt integrity failed")
claim = self.service.store.get_claim(str(next(iter(claim_ids))))
if (
claim.task_id != task.task_id
or claim.worker_id != self.worker_id
or claim.worker_identity != self.worker_identity
or claim.operation_digest != task.task_plan.operation_digest()
or claim.status != "COMPLETED"
):
raise ConflictError("terminal proof claim binding mismatch")
dispatch = self.service.lineage.binding.get_root_action_dispatch(str(review["review_id"]))
if dispatch is None:
raise ConflictError("terminal proof lacks durable root action dispatch")
body: dict[str, object] = {
"schema": RECEIPT_SCHEMA,
"task_id": task.task_id,
"mission_id": task.mission_id,
"root_task_id": task.root_task_id,
"claim_id": next(iter(claim_ids)),
"task_status": snapshot.status,
"task_revision": snapshot.revision,
"evidence_refs": list(snapshot.evidence_refs),
"review_id": review["review_id"],
"root_revision_after": review["root_revision_after"],
"next_legal_action": review["next_legal_action"],
"dispatch_status": dispatch.status,
"dispatch_idempotency_key": dispatch.idempotency_key,
}
body["receipt_digest"] = sha256(_canonical_bytes(body)).hexdigest()
return body
def _exact_active_claim(self, task: TaskRecord):
active = self.service.store.active_claim(task.task_id)
if active is None:
return None
if (
active.worker_id != self.worker_id
or active.worker_identity != self.worker_identity
or active.operation_digest != task.task_plan.operation_digest()
):
raise ConflictError("active claim is owned by another execution binding")
return active
def reconcile(
self, task_id: str, idempotency_key: str | None, *, actor_identity: str
) -> dict[str, object]:
if self.provider is None:
raise ForbiddenError("terminal proof provider is not configured")
task = self._authorized_task(task_id, actor_identity)
if idempotency_key != self.expected_idempotency_key(task):
raise ValidationError("terminal proof idempotency key mismatch")
existing = self._existing_receipt(task, actor_identity)
if existing is not None:
return existing
proof = self.provider.collect(task)
claim = self._exact_active_claim(task)
if claim is None:
try:
claimed = self.service.claim(
task.task_id,
{
"worker_id": self.worker_id,
"lease_seconds": self.lease_seconds,
"actor_identity": self.worker_identity,
},
)
claim_id = str(claimed["claim_id"])
except Exception:
refreshed = self._authorized_task(task_id, actor_identity)
replay = self._existing_receipt(refreshed, actor_identity)
if replay is not None:
return replay
claim = self._exact_active_claim(refreshed)
if claim is None:
raise
claim_id = claim.claim_id
else:
claim_id = claim.claim_id
try:
self.service.complete(
task.task_id,
{
"claim_id": claim_id,
"actor_identity": self.worker_identity,
"status": "COMPLETED",
"outputs": proof.outputs(),
"evidence": [proof.evidence()],
"started_at": proof.started_at,
"completed_at": proof.completed_at,
"attestation_ref": proof.attestation_ref,
},
)
except Exception:
refreshed = self._authorized_task(task_id, actor_identity)
replay = self._existing_receipt(refreshed, actor_identity)
if replay is None:
raise
return replay
refreshed = self._authorized_task(task_id, actor_identity)
receipt = self._existing_receipt(refreshed, actor_identity)
if receipt is None:
raise ConflictError("terminal proof completion lacks authoritative readback")
return receipt
File diff suppressed because it is too large Load Diff
+40 -8
View File
@@ -4,6 +4,7 @@ import hashlib, html, json, os, sqlite3, time, uuid, urllib.parse
from http import cookies
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
import hmac # SEC-P0-BOOTSTRAP-CREDENTIAL
ROOT = Path(os.getenv('AIOS_WEBOPS_ROOT', Path(__file__).resolve().parents[1]))
DB_PATH = Path(os.getenv('AIOS_WEBOPS_DB', ROOT / 'data' / 'webops.sqlite3'))
@@ -13,7 +14,11 @@ PUBLIC_DOMAIN = os.getenv('AIOS_PUBLIC_DOMAIN','https://cnnetmkt.top')
WEBOPS_DOMAIN = os.getenv('AIOS_WEBOPS_DOMAIN','https://ops.cnnetmkt.top')
API_DOMAIN = os.getenv('AIOS_API_DOMAIN','https://api.cnnetmkt.top')
WECHAT_DOMAIN = os.getenv('AIOS_WECHAT_CALLBACK_DOMAIN','https://wechat.cnnetmkt.top')
SECRET = os.getenv('AIOS_WEBOPS_SECRET','change-me-before-production')
SECRET = os.getenv('AIOS_WEBOPS_SECRET') or ''
# SEC-P0: fail closed — never boot with a missing/insecure default secret.
if SECRET in ('', 'change-me-before-production'):
raise SystemExit('AIOS_WEBOPS_SECRET not set (or insecure default). Refusing to start: fail closed.')
SESSION_TTL = int(os.getenv('AIOS_SESSION_TTL','3600'))
ROLES = ['owner','admin','user']
NODES = ['server-hw','server-local']
PAGES = [('/dashboard','Dashboard'),('/nodes','Nodes'),('/roles','Roles'),('/routing','Routing'),('/failover','Failover'),('/approvals','Approvals'),('/operations','Operations'),('/users','Users'),('/quota','Quota'),('/models','Models'),('/memory','Memory'),('/channels/wechat','WeChat'),('/life','Life Core'),('/patches','Patch Studio'),('/optimization','Optimization'),('/simulation','Simulation'),('/logs','Logs'),('/config','Config'),('/uat','UAT'),('/versions','Versions'),('/evidence','Evidence')]
@@ -43,19 +48,46 @@ def audit(actor, action, target='', payload=None):
exec_sql('INSERT INTO audit_logs(id,actor,action,target,payload,created_at) VALUES(?,?,?,?,?,?)',(uid('audit'),actor,action,target,jdump(payload or {}),now()))
def notify(event_type,payload):
exec_sql('INSERT INTO owner_notifications(id,event_type,payload,status,created_at) VALUES(?,?,?,?,?)',(uid('notify'),event_type,jdump(payload),'queued',now()))
def hash_pw(p): return hashlib.sha256((SECRET+'|'+p).encode()).hexdigest()
def check_pw(stored,p): return stored in (p, hash_pw(p))
_PBKDF2_ITERS = int(os.getenv('AIOS_PBKDF2_ITERS','260000'))
def _is_pbkdf2(stored): return isinstance(stored,str) and stored.startswith('pbkdf2$') and stored.count('$')==2
def hash_pw(p):
salt=os.urandom(16).hex()
dk=hashlib.pbkdf2_hmac('sha256', p.encode(), bytes.fromhex(salt), _PBKDF2_ITERS).hex()
return f'pbkdf2${salt}${dk}'
def check_pw(stored,p):
if not stored or not isinstance(stored,str) or not stored: return False
if _is_pbkdf2(stored):
_,salt,dk=stored.split('$')
calc=hashlib.pbkdf2_hmac('sha256', p.encode(), bytes.fromhex(salt), _PBKDF2_ITERS).hex()
return hmac.compare_digest(dk,calc)
# Legacy record path: NEVER accept the plaintext form as a credential.
# A legacy (unsalted) sha256 is honored ONLY as a one-time owner migration
# credential and never for the known bootstrap default; anything else is
# rejected so the account must be re-seeded via a secure channel (fail closed).
if stored == p: return False
try:
old=hashlib.sha256((SECRET+'|'+p).encode()).hexdigest()
except Exception:
return False
return (hmac.compare_digest(old,stored)) and (p not in {'bootstrap-change-me'})
def token(username,role):
data=f'{username}|{role}|{int(time.time())}'; sig=hashlib.sha256((SECRET+'|'+data).encode()).hexdigest()[:16]; return data+'|'+sig
exp=int(time.time())+SESSION_TTL
data=f'{username}|{role}|{exp}'
sig=hmac.new(SECRET.encode(), data.encode(), hashlib.sha256).hexdigest()
return data+'|'+sig
def parse_session(header):
if not header: return None
c=cookies.SimpleCookie(); c.load(header)
if 'aios_session' not in c: return None
parts=c['aios_session'].value.split('|')
if len(parts)!=4: return None
data='|'.join(parts[:3]); sig=parts[3]
if hashlib.sha256((SECRET+'|'+data).encode()).hexdigest()[:16]!=sig: return None
return one('SELECT * FROM users WHERE username=? AND role=? AND status=?',(parts[0],parts[1],'approved'))
username,role,exp,sig=parts
data=f'{username}|{role}|{exp}'
if not hmac.compare_digest(hmac.new(SECRET.encode(), data.encode(), hashlib.sha256).hexdigest(), sig): return None
try:
if int(exp) <= int(time.time()): return None
except Exception: return None
return one('SELECT * FROM users WHERE username=? AND role=? AND status=?',(username,role,'approved'))
def require(user, roles): return bool(user and user['role'] in roles)
def table(data):
if not data: return '<p class="muted">No records.</p>'
@@ -215,7 +247,7 @@ class H(BaseHTTPRequestHandler):
if path=='/login':
n=int(self.headers.get('Content-Length','0') or 0); data=urllib.parse.parse_qs(self.rfile.read(n).decode())
u=one('SELECT * FROM users WHERE username=? AND status=?',(data.get('username',[''])[0],'approved'))
if u and check_pw(u.get('password_hash') or '',data.get('password',[''])[0]): audit(u['username'],'login','webops',{}); self.redirect('/dashboard',[f"aios_session={token(u['username'],u['role'])}; Path=/; HttpOnly; SameSite=Lax"]); return
if u and check_pw(u.get('password_hash') or '',data.get('password',[''])[0]): audit(u['username'],'login','webops',{}); self.redirect('/dashboard',[f"aios_session={token(u['username'],u['role'])}; Path=/; HttpOnly; SameSite=Strict"]); return
self.send_body(403,layout('Login Failed','<p>登录失败。</p>',None)); return
if path=='/register':
n=int(self.headers.get('Content-Length','0') or 0); data=urllib.parse.parse_qs(self.rfile.read(n).decode())
+40 -8
View File
@@ -4,6 +4,7 @@ import hashlib, html, json, os, sqlite3, time, uuid, urllib.parse
from http import cookies
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
import hmac # SEC-P0-BOOTSTRAP-CREDENTIAL
ROOT = Path(os.getenv('AIOS_WEBOPS_ROOT', Path(__file__).resolve().parents[1]))
DB_PATH = Path(os.getenv('AIOS_WEBOPS_DB', ROOT / 'data' / 'webops.sqlite3'))
@@ -13,7 +14,11 @@ PUBLIC_DOMAIN = os.getenv('AIOS_PUBLIC_DOMAIN','https://cnnetmkt.top')
WEBOPS_DOMAIN = os.getenv('AIOS_WEBOPS_DOMAIN','https://ops.cnnetmkt.top')
API_DOMAIN = os.getenv('AIOS_API_DOMAIN','https://api.cnnetmkt.top')
WECHAT_DOMAIN = os.getenv('AIOS_WECHAT_CALLBACK_DOMAIN','https://wechat.cnnetmkt.top')
SECRET = os.getenv('AIOS_WEBOPS_SECRET','change-me-before-production')
SECRET = os.getenv('AIOS_WEBOPS_SECRET') or ''
# SEC-P0: fail closed — never boot with a missing/insecure default secret.
if SECRET in ('', 'change-me-before-production'):
raise SystemExit('AIOS_WEBOPS_SECRET not set (or insecure default). Refusing to start: fail closed.')
SESSION_TTL = int(os.getenv('AIOS_SESSION_TTL','3600'))
ROLES = ['owner','admin','user']
NODES = ['server-hw','server-local']
PAGES = [('/dashboard','Dashboard'),('/nodes','Nodes'),('/roles','Roles'),('/routing','Routing'),('/failover','Failover'),('/approvals','Approvals'),('/operations','Operations'),('/users','Users'),('/quota','Quota'),('/models','Models'),('/memory','Memory'),('/channels/wechat','WeChat'),('/life','Life Core'),('/patches','Patch Studio'),('/optimization','Optimization'),('/simulation','Simulation'),('/logs','Logs'),('/config','Config'),('/uat','UAT'),('/versions','Versions'),('/evidence','Evidence')]
@@ -43,19 +48,46 @@ def audit(actor, action, target='', payload=None):
exec_sql('INSERT INTO audit_logs(id,actor,action,target,payload,created_at) VALUES(?,?,?,?,?,?)',(uid('audit'),actor,action,target,jdump(payload or {}),now()))
def notify(event_type,payload):
exec_sql('INSERT INTO owner_notifications(id,event_type,payload,status,created_at) VALUES(?,?,?,?,?)',(uid('notify'),event_type,jdump(payload),'queued',now()))
def hash_pw(p): return hashlib.sha256((SECRET+'|'+p).encode()).hexdigest()
def check_pw(stored,p): return stored in (p, hash_pw(p))
_PBKDF2_ITERS = int(os.getenv('AIOS_PBKDF2_ITERS','260000'))
def _is_pbkdf2(stored): return isinstance(stored,str) and stored.startswith('pbkdf2$') and stored.count('$')==2
def hash_pw(p):
salt=os.urandom(16).hex()
dk=hashlib.pbkdf2_hmac('sha256', p.encode(), bytes.fromhex(salt), _PBKDF2_ITERS).hex()
return f'pbkdf2${salt}${dk}'
def check_pw(stored,p):
if not stored or not isinstance(stored,str) or not stored: return False
if _is_pbkdf2(stored):
_,salt,dk=stored.split('$')
calc=hashlib.pbkdf2_hmac('sha256', p.encode(), bytes.fromhex(salt), _PBKDF2_ITERS).hex()
return hmac.compare_digest(dk,calc)
# Legacy record path: NEVER accept the plaintext form as a credential.
# A legacy (unsalted) sha256 is honored ONLY as a one-time owner migration
# credential and never for the known bootstrap default; anything else is
# rejected so the account must be re-seeded via a secure channel (fail closed).
if stored == p: return False
try:
old=hashlib.sha256((SECRET+'|'+p).encode()).hexdigest()
except Exception:
return False
return (hmac.compare_digest(old,stored)) and (p not in {'bootstrap-change-me'})
def token(username,role):
data=f'{username}|{role}|{int(time.time())}'; sig=hashlib.sha256((SECRET+'|'+data).encode()).hexdigest()[:16]; return data+'|'+sig
exp=int(time.time())+SESSION_TTL
data=f'{username}|{role}|{exp}'
sig=hmac.new(SECRET.encode(), data.encode(), hashlib.sha256).hexdigest()
return data+'|'+sig
def parse_session(header):
if not header: return None
c=cookies.SimpleCookie(); c.load(header)
if 'aios_session' not in c: return None
parts=c['aios_session'].value.split('|')
if len(parts)!=4: return None
data='|'.join(parts[:3]); sig=parts[3]
if hashlib.sha256((SECRET+'|'+data).encode()).hexdigest()[:16]!=sig: return None
return one('SELECT * FROM users WHERE username=? AND role=? AND status=?',(parts[0],parts[1],'approved'))
username,role,exp,sig=parts
data=f'{username}|{role}|{exp}'
if not hmac.compare_digest(hmac.new(SECRET.encode(), data.encode(), hashlib.sha256).hexdigest(), sig): return None
try:
if int(exp) <= int(time.time()): return None
except Exception: return None
return one('SELECT * FROM users WHERE username=? AND role=? AND status=?',(username,role,'approved'))
def require(user, roles): return bool(user and user['role'] in roles)
def table(data):
if not data: return '<p class="muted">No records.</p>'
@@ -215,7 +247,7 @@ class H(BaseHTTPRequestHandler):
if path=='/login':
n=int(self.headers.get('Content-Length','0') or 0); data=urllib.parse.parse_qs(self.rfile.read(n).decode())
u=one('SELECT * FROM users WHERE username=? AND status=?',(data.get('username',[''])[0],'approved'))
if u and check_pw(u.get('password_hash') or '',data.get('password',[''])[0]): audit(u['username'],'login','webops',{}); self.redirect('/dashboard',[f"aios_session={token(u['username'],u['role'])}; Path=/; HttpOnly; SameSite=Lax"]); return
if u and check_pw(u.get('password_hash') or '',data.get('password',[''])[0]): audit(u['username'],'login','webops',{}); self.redirect('/dashboard',[f"aios_session={token(u['username'],u['role'])}; Path=/; HttpOnly; SameSite=Strict"]); return
self.send_body(403,layout('Login Failed','<p>登录失败。</p>',None)); return
if path=='/register':
n=int(self.headers.get('Content-Length','0') or 0); data=urllib.parse.parse_qs(self.rfile.read(n).decode())
+692
View File
@@ -0,0 +1,692 @@
#!/usr/bin/env python3
"""One-shot governed executor for an already approved exact release binding.
This module deliberately does not build release artifacts. The build/staging
evidence is completed before Owner production approval. Production execution
consumes that immutable binding, revalidates it, and performs narrow
side-effects through adapters in a durable fail-closed order.
Adapter protocol:
stdin: JSON request with operation = "probe" | "apply" | "rollback"
stdout: one JSON object on the final non-empty line
shell: never used
The executor never rewrites tags, never writes refs/heads/main, and records
Baseline last.
"""
from __future__ import annotations
import argparse
import dataclasses
try:
import fcntl
except ModuleNotFoundError: # pragma: no cover - production host is POSIX
fcntl = None
import hashlib
import json
import os
import re
import stat
import subprocess
import sys
import tempfile
import time
from pathlib import Path
from typing import Any, Mapping, Sequence
from trusted_release_execution_plan import PlanError, TrustedReleasePlan
OID_RE = re.compile(r"^[0-9a-f]{40}$")
SHA_RE = re.compile(r"^[0-9a-f]{64}$")
PLAN_RE = re.compile(r"^sha256:[0-9a-f]{64}$")
REQUIRED_V2R1C7_MEMBERS = {
"payload/VERSION",
"payload/active-runtime/app/deployment_state.py",
"payload/active-runtime/app/v2r1c6_privacy_tools.py",
"payload/active-runtime/app/v2r1c7_health_service.py",
"payload/deploy/aios-v2/systemd/aios-v2r1c7-production.service",
"payload/deploy/aios-v2/v2r1c7-runtime-config.json",
"payload/deploy/aios-v2/v2r1c7-rollback.json",
"payload/scripts/probe_v2r1c7_health.py",
"payload/scripts/rollback_v2r1c7_release.sh",
"payload/BUILD-METADATA.json",
"payload/ARTIFACT-MANIFEST.json",
"payload/ROLLBACK-METADATA.json",
}
class ReleaseBlocked(RuntimeError):
pass
class ManualOwnerReviewRequired(ReleaseBlocked):
pass
class ReconciliationRequired(ReleaseBlocked):
pass
@dataclasses.dataclass(frozen=True)
class Binding:
version: str
target_oid: str
evidence_closure_oid: str
artifact_sha256: str
approval_plan_id: str
artifact_path: str
artifact_size: int
production_service: str
production_port: int
repository_identity: str = "aios/aios"
candidate_id: str | None = None
release_plan_sha256: str | None = None
@classmethod
def from_trusted_plan(cls, plan: TrustedReleasePlan) -> "Binding":
value = plan.value
adapter = value["adapter_policy"]
binding = cls(
version=str(value["version"]), target_oid=str(value["source_commit"]),
evidence_closure_oid=str(value["evidence_closure_oid"]), artifact_sha256=str(value["artifact_sha256"]),
approval_plan_id=str(value["approval_plan_id"]), artifact_path=str(value["artifact"]["path"]),
artifact_size=int(value["artifact"]["size"]), production_service=str(adapter["service"]),
production_port=int(adapter["port"]), candidate_id=str(value["candidate_id"]),
release_plan_sha256=plan.release_plan_sha256,
)
plan.assert_binding(dataclasses.asdict(binding))
return binding
def __post_init__(self) -> None:
if not self.version or not OID_RE.fullmatch(self.target_oid):
raise ValueError("invalid release target binding")
if not OID_RE.fullmatch(self.evidence_closure_oid):
raise ValueError("invalid evidence closure OID")
if not SHA_RE.fullmatch(self.artifact_sha256):
raise ValueError("invalid artifact SHA-256")
if not PLAN_RE.fullmatch(self.approval_plan_id):
raise ValueError("invalid approval plan id")
if self.artifact_size <= 0:
raise ValueError("invalid artifact size")
if not self.production_service:
raise ValueError("production service is required")
if not 1024 <= self.production_port <= 65535:
raise ValueError("invalid production port")
if self.repository_identity != "aios/aios":
raise ValueError("unexpected repository identity")
@property
def tag(self) -> str:
return self.version
@property
def fingerprint(self) -> str:
payload = {
"version": self.version,
"target_oid": self.target_oid,
"evidence_closure_oid": self.evidence_closure_oid,
"artifact_sha256": self.artifact_sha256,
"approval_plan_id": self.approval_plan_id,
"artifact_size": self.artifact_size,
"production_service": self.production_service,
"production_port": self.production_port,
"repository_identity": self.repository_identity,
"candidate_id": self.candidate_id,
"release_plan_sha256": self.release_plan_sha256,
}
return hashlib.sha256(canonical_json(payload)).hexdigest()
def request(self, operation: str) -> dict[str, Any]:
return {
"schema": "aios.production-executor-request.v1",
"operation": operation,
"binding_fingerprint": self.fingerprint,
"version": self.version,
"tag": self.tag,
"target_oid": self.target_oid,
"evidence_closure_oid": self.evidence_closure_oid,
"artifact_sha256": self.artifact_sha256,
"artifact_path": self.artifact_path,
"artifact_size": self.artifact_size,
"approval_plan_id": self.approval_plan_id,
"production_service": self.production_service,
"production_port": self.production_port,
"repository_identity": self.repository_identity,
"candidate_id": self.candidate_id,
"release_plan_sha256": self.release_plan_sha256,
}
def canonical_json(value: object) -> bytes:
return (json.dumps(value, sort_keys=True, separators=(",", ":")) + "\n").encode("utf-8")
def secure_json(path: Path) -> dict[str, Any]:
st = path.lstat()
if not stat.S_ISREG(st.st_mode) or path.is_symlink():
raise ReleaseBlocked(f"non-regular authorization file: {path}")
if st.st_uid != 0:
raise ReleaseBlocked(f"authorization file must be root-owned: {path}")
if st.st_mode & 0o022:
raise ReleaseBlocked(f"authorization file is group/world writable: {path}")
data = json.loads(path.read_text(encoding="utf-8"))
if not isinstance(data, dict):
raise ReleaseBlocked("authorization receipt must be a JSON object")
return data
def sha256_file(path: Path) -> str:
if path.is_symlink() or not path.is_file():
raise ReleaseBlocked("artifact must be a regular non-symlink file")
digest = hashlib.sha256()
with path.open("rb") as source:
for block in iter(lambda: source.read(1024 * 1024), b""):
digest.update(block)
return digest.hexdigest()
def run_git(git_dir: Path, *args: str, check: bool = True) -> subprocess.CompletedProcess[str]:
return subprocess.run(
["git", f"--git-dir={git_dir}", *args],
check=check,
capture_output=True,
text=True,
timeout=30,
env={"PATH": os.environ.get("PATH", "")},
)
def verify_git_lineage(git_dir: Path, binding: Binding) -> dict[str, Any]:
authority_main = run_git(git_dir, "rev-parse", "refs/heads/main").stdout.strip()
if not OID_RE.fullmatch(authority_main):
raise ReleaseBlocked("Authority main OID is invalid")
target_type = run_git(git_dir, "cat-file", "-t", binding.target_oid).stdout.strip()
if target_type != "commit":
raise ReleaseBlocked("target_oid is not a commit")
evidence_type = run_git(
git_dir, "cat-file", "-t", binding.evidence_closure_oid
).stdout.strip()
if evidence_type != "commit":
raise ReleaseBlocked("evidence_closure_oid is not a commit")
# A control-plane-only patch may advance main after release approval.
# The approved release target remains valid only while it is still on main.
target_on_main = run_git(
git_dir,
"merge-base",
"--is-ancestor",
binding.target_oid,
authority_main,
check=False,
).returncode == 0
if not target_on_main:
raise ManualOwnerReviewRequired(
"approved target is no longer an ancestor of Authority main"
)
target_in_evidence = run_git(
git_dir,
"merge-base",
"--is-ancestor",
binding.target_oid,
binding.evidence_closure_oid,
check=False,
).returncode == 0
if not target_in_evidence:
raise ReleaseBlocked("approved target is not an ancestor of evidence closure")
return {
"authority_main_oid": authority_main,
"target_oid": binding.target_oid,
"target_is_ancestor_of_main": True,
"evidence_closure_oid": binding.evidence_closure_oid,
"evidence_object_type": evidence_type,
"target_is_ancestor_of_evidence": True,
}
def verify_artifact(binding: Binding) -> dict[str, Any]:
path = Path(binding.artifact_path)
if path.stat().st_size != binding.artifact_size:
raise ReleaseBlocked("artifact size mismatch")
actual = sha256_file(path)
if actual != binding.artifact_sha256:
raise ReleaseBlocked("artifact SHA-256 mismatch")
if binding.version == "V2R1C7":
# Python tarfile may not have zstd support on the target host, so
# decompress into a private temporary tar and inspect it there.
with tempfile.TemporaryDirectory(prefix="aios-artifact-verify-") as temp_dir:
tar_path = Path(temp_dir) / "release.tar"
with tar_path.open("wb") as output:
completed = subprocess.run(
["zstd", "-dc", str(path)],
check=False,
stdout=output,
stderr=subprocess.PIPE,
timeout=60,
env={"PATH": os.environ.get("PATH", "")},
)
if completed.returncode != 0:
raise ReleaseBlocked("artifact zstd decompression failed")
import tarfile
with tarfile.open(tar_path, "r:") as archive:
names: set[str] = set()
for member in archive.getmembers():
candidate = Path(member.name)
if (
candidate.is_absolute()
or ".." in candidate.parts
or member.issym()
or member.islnk()
or not (member.isfile() or member.isdir())
):
raise ReleaseBlocked(f"unsafe artifact member: {member.name}")
names.add(member.name)
missing = sorted(REQUIRED_V2R1C7_MEMBERS - names)
if missing:
raise ReleaseBlocked(
"artifact missing required members: " + ",".join(missing)
)
return {
"artifact_path": str(path),
"artifact_sha256": actual,
"artifact_size": binding.artifact_size,
"verified": True,
}
def verify_authorization(receipt: Mapping[str, Any], binding: Binding) -> dict[str, Any]:
if receipt.get("accepted") is not True:
raise ReleaseBlocked("Owner production authorization is not accepted")
if receipt.get("binding_replaced") not in (None, False):
raise ManualOwnerReviewRequired("Owner binding was replaced")
expected = {
"version": binding.version,
"target_oid": binding.target_oid,
"evidence_closure_oid": binding.evidence_closure_oid,
"artifact_sha256": binding.artifact_sha256,
"approval_plan_id": binding.approval_plan_id,
}
if binding.candidate_id is not None:
expected["candidate_id"] = binding.candidate_id
if binding.release_plan_sha256 is not None:
expected["release_plan_sha256"] = binding.release_plan_sha256
if receipt.get("candidate_binding_sha256") is None:
raise ManualOwnerReviewRequired("Owner authorization missing candidate binding")
for key, value in expected.items():
if receipt.get(key) != value:
raise ManualOwnerReviewRequired(f"Owner authorization mismatch: {key}")
return {"accepted": True, **expected}
class DurableState:
def __init__(self, root: Path, binding: Binding):
self.root = root
self.root.mkdir(parents=True, exist_ok=True)
os.chmod(self.root, 0o700)
self.path = self.root / "execution.json"
self.binding = binding
self.value: dict[str, Any]
if self.path.exists():
existing = json.loads(self.path.read_text(encoding="utf-8"))
if existing.get("binding_fingerprint") != binding.fingerprint:
raise ManualOwnerReviewRequired(
"state root contains a different production binding"
)
self.value = existing
else:
self.value = {
"schema": "aios.production-execution-state.v1",
"binding_fingerprint": binding.fingerprint,
"version": binding.version,
"target_oid": binding.target_oid,
"state": "AUTHORIZED",
"completed": {},
"history": [],
}
self.save()
def save(self) -> None:
temporary = self.path.with_suffix(".tmp")
temporary.write_bytes(canonical_json(self.value))
os.chmod(temporary, 0o600)
os.replace(temporary, self.path)
def record(self, state: str, step: str, result: Mapping[str, Any]) -> None:
now = time.time()
sanitized = json.loads(json.dumps(result))
self.value["state"] = state
self.value["completed"][step] = sanitized
self.value["history"].append(
{"at": now, "state": state, "step": step, "result": sanitized}
)
self.save()
def fail(self, state: str, reason: str) -> None:
self.value["state"] = state
self.value["history"].append(
{"at": time.time(), "state": state, "reason": reason}
)
self.save()
class FileLock:
def __init__(self, path: Path):
self.path = path
self.handle = None
def __enter__(self) -> "FileLock":
if fcntl is None:
raise ReleaseBlocked("production executor file locking requires POSIX")
self.path.parent.mkdir(parents=True, exist_ok=True)
self.handle = self.path.open("a+", encoding="utf-8")
os.chmod(self.path, 0o600)
try:
fcntl.flock(
self.handle.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB
)
except BlockingIOError as exc:
raise ReleaseBlocked("production executor is already running") from exc
return self
def __exit__(self, exc_type, exc, tb) -> None:
if self.handle is not None and fcntl is not None:
fcntl.flock(self.handle.fileno(), fcntl.LOCK_UN)
self.handle.close()
def parse_command(values: Sequence[str]) -> list[str]:
command = [str(x) for x in values if str(x)]
if not command:
raise ValueError("adapter command is required")
if not os.path.isabs(command[0]):
raise ValueError("adapter executable must be an absolute path")
return command
def run_adapter(command: Sequence[str], request: Mapping[str, Any]) -> dict[str, Any]:
completed = subprocess.run(
list(command),
input=canonical_json(request),
check=False,
capture_output=True,
timeout=180,
env={"PATH": os.environ.get("PATH", "")},
)
if completed.returncode != 0:
raise ReleaseBlocked(
f"adapter failed: {Path(command[0]).name} rc={completed.returncode}"
)
lines = [line for line in completed.stdout.decode("utf-8").splitlines() if line.strip()]
if not lines:
raise ReleaseBlocked("adapter returned no JSON result")
try:
value = json.loads(lines[-1])
except ValueError as exc:
raise ReleaseBlocked("adapter returned invalid JSON") from exc
if not isinstance(value, dict):
raise ReleaseBlocked("adapter result must be an object")
return value
def require_exact_response(result: Mapping[str, Any], binding: Binding) -> None:
for key, expected in {
"version": binding.version,
"target_oid": binding.target_oid,
"evidence_closure_oid": binding.evidence_closure_oid,
"artifact_sha256": binding.artifact_sha256,
"approval_plan_id": binding.approval_plan_id,
}.items():
if key in result and result[key] != expected:
raise ManualOwnerReviewRequired(f"adapter binding mismatch: {key}")
def tag_step(command: Sequence[str], binding: Binding) -> dict[str, Any]:
request = binding.request("probe")
probe = run_adapter(command, request)
require_exact_response(probe, binding)
state = probe.get("status")
if state == "CONFLICT":
raise ManualOwnerReviewRequired("same-name tag conflicts with approved binding")
if state == "INDETERMINATE":
raise ReconciliationRequired("tag publication state is indeterminate")
if state not in {"APPLIED_VERIFIED", "NOT_APPLIED", "PARTIAL_LOCAL"}:
raise ReleaseBlocked(f"unsupported tag probe status: {state}")
if state != "APPLIED_VERIFIED":
applied = run_adapter(command, binding.request("apply"))
require_exact_response(applied, binding)
if applied.get("status") == "CONFLICT":
raise ManualOwnerReviewRequired("tag conflict during publication")
probe = run_adapter(command, request)
require_exact_response(probe, binding)
if probe.get("status") != "APPLIED_VERIFIED":
raise ReconciliationRequired("tag was not independently verified")
if probe.get("peeled_target_oid") != binding.target_oid:
raise ManualOwnerReviewRequired("published tag peeled target mismatch")
tag_oid = probe.get("tag_oid")
if not isinstance(tag_oid, str) or not OID_RE.fullmatch(tag_oid):
raise ReleaseBlocked("verified annotated tag object OID missing")
return dict(probe)
def deploy_step(command: Sequence[str], binding: Binding) -> dict[str, Any]:
probe = run_adapter(command, binding.request("probe"))
require_exact_response(probe, binding)
if probe.get("status") == "ACTIVE_VERIFIED":
return dict(probe)
if probe.get("status") == "CONFLICT":
raise ManualOwnerReviewRequired("production runtime has conflicting binding")
applied = run_adapter(command, binding.request("apply"))
require_exact_response(applied, binding)
probe = run_adapter(command, binding.request("probe"))
require_exact_response(probe, binding)
if probe.get("status") != "ACTIVE_VERIFIED":
try:
run_adapter(command, binding.request("rollback"))
except Exception:
pass
raise ReconciliationRequired("production runtime did not verify after apply")
if (
probe.get("deployment_state") != "ACTIVE"
or probe.get("production_deployed") is not True
):
raise ReconciliationRequired("production health contract is not ACTIVE")
return dict(probe)
def finalize_step(
command: Sequence[str],
binding: Binding,
*,
name: str,
tag_oid: str,
) -> dict[str, Any]:
request = binding.request("probe")
request["tag_oid"] = tag_oid
probe = run_adapter(command, request)
require_exact_response(probe, binding)
if probe.get("status") == "CONFLICT":
raise ManualOwnerReviewRequired(f"{name} has conflicting release binding")
if probe.get("status") != "VERIFIED":
apply_request = binding.request("apply")
apply_request["tag_oid"] = tag_oid
applied = run_adapter(command, apply_request)
require_exact_response(applied, binding)
probe = run_adapter(command, request)
require_exact_response(probe, binding)
if probe.get("status") != "VERIFIED":
raise ReconciliationRequired(f"{name} did not verify after apply")
if probe.get("tag_oid") not in (None, tag_oid):
raise ManualOwnerReviewRequired(f"{name} tag object mismatch")
return dict(probe)
def execute(
*,
binding: Binding,
authorization_path: Path,
repository: Path,
state_root: Path,
tag_command: Sequence[str],
deploy_command: Sequence[str],
registry_command: Sequence[str],
baseline_command: Sequence[str],
release_plan_path: Path | None = None,
) -> dict[str, Any]:
plan = TrustedReleasePlan.load(release_plan_path) if release_plan_path is not None else None
if plan is not None:
try:
plan.assert_binding(dataclasses.asdict(binding))
except PlanError as exc:
raise ReleaseBlocked(f"trusted release plan mismatch: {exc}") from exc
authorization = verify_authorization(secure_json(authorization_path), binding)
lineage = verify_git_lineage(repository, binding)
artifact = verify_artifact(binding)
state = DurableState(state_root, binding)
with FileLock(state_root / "executor.lock"):
state.record(
"PREFLIGHT_VERIFIED",
"preflight",
{"authorization": authorization, "lineage": lineage, "artifact": artifact},
)
tag: dict[str, Any] | None = None
runtime: dict[str, Any] | None = None
try:
tag = tag_step(tag_command, binding)
state.record("TAG_VERIFIED", "tag", tag)
runtime = deploy_step(deploy_command, binding)
state.record("RUNTIME_VERIFIED", "runtime", runtime)
registry = finalize_step(
registry_command,
binding,
name="registry",
tag_oid=tag["tag_oid"],
)
state.record("REGISTRY_VERIFIED", "registry", registry)
baseline = finalize_step(
baseline_command,
binding,
name="baseline",
tag_oid=tag["tag_oid"],
)
state.record("BASELINE_VERIFIED", "baseline", baseline)
# Final readback: no mutation, all four planes must still agree.
final_tag = tag_step(tag_command, binding)
final_runtime = run_adapter(deploy_command, binding.request("probe"))
final_registry_request = binding.request("probe")
final_registry_request["tag_oid"] = tag["tag_oid"]
final_registry = run_adapter(registry_command, final_registry_request)
final_baseline_request = binding.request("probe")
final_baseline_request["tag_oid"] = tag["tag_oid"]
final_baseline = run_adapter(baseline_command, final_baseline_request)
for value in (final_runtime, final_registry, final_baseline):
require_exact_response(value, binding)
if final_runtime.get("status") != "ACTIVE_VERIFIED":
raise ReconciliationRequired("final runtime readback failed")
if final_registry.get("status") != "VERIFIED":
raise ReconciliationRequired("final Registry readback failed")
if final_baseline.get("status") != "VERIFIED":
raise ReconciliationRequired("final Baseline readback failed")
final = {
"status": "PRODUCTION_ACCEPTED",
"version": binding.version,
"target_oid": binding.target_oid,
"evidence_closure_oid": binding.evidence_closure_oid,
"artifact_sha256": binding.artifact_sha256,
"approval_plan_id": binding.approval_plan_id,
"tag_oid": tag["tag_oid"],
"tag": final_tag,
"runtime": final_runtime,
"registry": final_registry,
"baseline": final_baseline,
"force_push_used": False,
"tag_rewritten": False,
"direct_main_push": False,
"secrets_committed": False,
}
state.record("COMPLETED", "final_consistency", final)
return final
except ManualOwnerReviewRequired as exc:
state.fail("MANUAL_OWNER_REVIEW_REQUIRED", str(exc))
raise
except ReconciliationRequired as exc:
state.fail("RECONCILIATION_REQUIRED", str(exc))
raise
except Exception as exc:
# Once a formal tag has been verified, no later error is a clean
# pre-side-effect failure. Preserve the tag and require idempotent
# reconciliation rather than pretending the release never started.
if tag is not None:
reason = f"{type(exc).__name__}: {exc}"
state.fail("RECONCILIATION_REQUIRED", reason)
raise ReconciliationRequired(reason) from exc
state.fail("FAIL_CLOSED", f"{type(exc).__name__}: {exc}")
raise
def parser() -> argparse.ArgumentParser:
p = argparse.ArgumentParser()
p.add_argument("--authorization", required=True, type=Path)
p.add_argument("--release-plan", type=Path)
p.add_argument("--repository", required=True, type=Path)
p.add_argument("--state-root", required=True, type=Path)
p.add_argument("--artifact", type=Path)
p.add_argument("--artifact-size", type=int)
p.add_argument("--version")
p.add_argument("--target-oid")
p.add_argument("--evidence-closure-oid")
p.add_argument("--artifact-sha256")
p.add_argument("--approval-plan-id")
p.add_argument("--production-service")
p.add_argument("--production-port", type=int)
p.add_argument("--repository-identity", default="aios/aios")
p.add_argument("--tag-adapter", required=True, nargs="+")
p.add_argument("--deploy-adapter", required=True, nargs="+")
p.add_argument("--registry-adapter", required=True, nargs="+")
p.add_argument("--baseline-adapter", required=True, nargs="+")
return p
def main() -> int:
args = parser().parse_args()
plan = TrustedReleasePlan.load(args.release_plan) if args.release_plan else None
if plan is not None:
binding = Binding.from_trusted_plan(plan)
else:
legacy=(args.artifact,args.artifact_size,args.version,args.target_oid,args.evidence_closure_oid,args.artifact_sha256,args.approval_plan_id,args.production_service,args.production_port)
if any(value is None for value in legacy): raise SystemExit("legacy binding is incomplete")
binding = Binding(version=args.version,target_oid=args.target_oid,evidence_closure_oid=args.evidence_closure_oid,artifact_sha256=args.artifact_sha256,approval_plan_id=args.approval_plan_id,artifact_path=str(args.artifact),artifact_size=args.artifact_size,production_service=args.production_service,production_port=args.production_port,repository_identity=args.repository_identity)
try:
result = execute(
binding=binding,
authorization_path=args.authorization,
repository=args.repository,
state_root=args.state_root,
tag_command=parse_command(args.tag_adapter),
deploy_command=parse_command(args.deploy_adapter),
registry_command=parse_command(args.registry_adapter),
baseline_command=parse_command(args.baseline_adapter),
release_plan_path=args.release_plan,
)
print(json.dumps(result, sort_keys=True))
return 0
except ManualOwnerReviewRequired as exc:
print(json.dumps({"status": "MANUAL_OWNER_REVIEW_REQUIRED", "reason": str(exc)}, sort_keys=True))
return 20
except ReconciliationRequired as exc:
print(json.dumps({"status": "RECONCILIATION_REQUIRED", "reason": str(exc)}, sort_keys=True))
return 21
except Exception as exc:
print(json.dumps({"status": "FAIL_CLOSED", "error": type(exc).__name__, "reason": str(exc)}, sort_keys=True))
return 22
if __name__ == "__main__":
raise SystemExit(main())
+65 -2
View File
@@ -4,6 +4,7 @@ from __future__ import annotations
import os
import re
import subprocess
import time
import urllib.parse
from pathlib import Path
from typing import Iterable
@@ -306,14 +307,76 @@ def authoritative_diff(
from_oid = validate_git_revision(from_revision, "from", git_dir=path)
to_oid = validate_git_revision(to_revision, "to", git_dir=path)
args = ["diff", "--no-ext-diff", "--no-textconv", from_oid, to_oid]
result = run_authoritative_git(args, git_dir=path)
patch_limit = max(1, min(_MAX_OUTPUT, 256 * 1024))
process = subprocess.Popen(
["git", "--git-dir", str(path), *args],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env={**os.environ, "GIT_OPTIONAL_LOCKS": "0"},
)
chunks: list[bytes] = []
stored_bytes = 0
total_bytes = 0
started = time.monotonic()
try:
while process.stdout is not None:
chunk = process.stdout.read(64 * 1024)
if not chunk:
break
total_bytes += len(chunk)
if stored_bytes < patch_limit:
remaining = patch_limit - stored_bytes
selected = chunk[:remaining]
chunks.append(selected)
stored_bytes += len(selected)
if time.monotonic() - started > _TIMEOUT:
process.kill()
raise AuthoritativeGitError("GIT_REF_QUERY_TIMEOUT")
stderr = (
process.stderr.read(8192).decode("utf-8", errors="replace")
if process.stderr is not None
else ""
)
returncode = process.wait(timeout=max(0.1, _TIMEOUT - (time.monotonic() - started)))
except (OSError, subprocess.TimeoutExpired) as exc:
process.kill()
process.wait()
raise AuthoritativeGitError("GIT_REF_QUERY_FAILED") from exc
if returncode != 0:
raise AuthoritativeGitError(_redact(stderr.strip() or "GIT_REF_QUERY_FAILED")[:200])
try:
names_result = run_authoritative_git(
["diff", "--no-ext-diff", "--no-textconv", "--name-status", from_oid, to_oid],
git_dir=path,
)
changed_paths = [
line.split("\t", 1)[-1]
for line in names_result.stdout.splitlines()
if line.strip()
]
except AuthoritativeGitOutputTooLarge:
changed_paths = []
try:
stats_result = run_authoritative_git(
["diff", "--no-ext-diff", "--no-textconv", "--stat", from_oid, to_oid],
git_dir=path,
)
stats = stats_result.stdout.strip()
except AuthoritativeGitOutputTooLarge:
stats = "GIT_STAT_OUTPUT_TRUNCATED"
patch = b"".join(chunks).decode("utf-8", errors="replace")
return {
"code": 0,
"stdout": result.stdout,
"stdout": patch,
"stderr": "",
"cmd": ["git", f"--git-dir={path}", *args],
"from_oid": from_oid,
"to_oid": to_oid,
"changed_paths": changed_paths,
"stats": stats,
"total_size_bytes": total_bytes,
"truncated": total_bytes > patch_limit,
"patch_limit_bytes": patch_limit,
}
@@ -951,3 +951,51 @@ class AuthorizedTaskWriterProvisioner:
return {**public_result, "prestart": prestart}
finally:
shutil.rmtree(temporary_root, ignore_errors=True)
# v2 continuation validation is intentionally separate from the legacy Owner
# bootstrap path above. It consumes a recovered campaign authorization and
# never creates a branch or asks the Owner to authorize again.
from aios_continuation_lease import WriterLeaseRequestV2
def validate_writer_lease_request_v2(raw: Mapping[str, Any], probe: Any) -> dict[str, Any]:
"""Validate a CONTINUE_EXISTING request against the live exact branch head."""
required = {
"schema",
"task_id",
"repository",
"branch_mode",
"base_oid",
"expected_branch_oid",
"target_branch",
"generation",
"lease_seconds",
}
if set(raw) != required or raw.get("schema") not in WriterLeaseRequestV2.accepted_schemas:
raise ProvisioningError("LEASE_REQUEST_V2_FIELDS_REJECTED", 409)
request = WriterLeaseRequestV2(
task_id=str(raw["task_id"]),
repository=str(raw["repository"]),
branch_mode=str(raw["branch_mode"]),
base_oid=str(raw["base_oid"]),
expected_branch_oid=str(raw["expected_branch_oid"]),
target_branch=str(raw["target_branch"]),
generation=raw["generation"],
lease_seconds=raw["lease_seconds"],
)
request.validate(probe)
return {
"schema": WriterLeaseRequestV2.schema,
"task_id": request.task_id,
"repository": request.repository,
"branch_mode": request.branch_mode,
"base_oid": request.base_oid,
"expected_branch_oid": request.expected_branch_oid,
"target_branch": request.target_branch,
"generation": request.generation,
"lease_seconds": request.lease_seconds,
"create_target_branch": request.branch_mode == "CREATE_NEW",
"worktree_mode": "detached_at_base_oid" if request.branch_mode == "CREATE_NEW" else "detached_at_expected_head",
"cas_ref_update_required": True,
}
+84
View File
@@ -0,0 +1,84 @@
#!/usr/bin/env python3
"""Single canonical Production Baseline authority for the GitOps reader.
The API and the one-shot writer import this module. Workspace and release
copies are projections only; they are never selected as a fallback source.
"""
from __future__ import annotations
import hashlib
import json
import os
from pathlib import Path
from typing import Any
AUTHORITY_PATH = Path(os.getenv(
"AIOS_GITOPS_BASELINE_AUTHORITY",
"/var/lib/aios-gitops-runtime-anchor/baseline/current.json",
))
AUTHORITY_SHA_PATH = AUTHORITY_PATH.with_suffix(".sha256")
class BaselineAuthorityError(RuntimeError):
"""Fail-closed error raised for an unavailable or inconsistent authority."""
class BaselineAuthority:
def __init__(self, path: Path | str = AUTHORITY_PATH):
self.path = Path(path)
self.sha_path = self.path.with_suffix(".sha256")
def _raw(self) -> bytes:
try:
raw = self.path.read_bytes()
except OSError as exc:
raise BaselineAuthorityError(f"authority_unavailable:{exc}") from exc
if not raw:
raise BaselineAuthorityError("authority_empty")
return raw
def read(self) -> dict[str, Any]:
raw = self._raw()
digest = hashlib.sha256(raw).hexdigest()
try:
value = json.loads(raw.decode("utf-8"))
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
raise BaselineAuthorityError("authority_invalid_json") from exc
if not isinstance(value, dict):
raise BaselineAuthorityError("authority_not_object")
expected = str(value.get("authority_sha256", ""))
if expected and expected != digest:
raise BaselineAuthorityError("authority_embedded_digest_mismatch")
if self.sha_path.exists():
sidecar = self.sha_path.read_text(encoding="utf-8").strip().split()[0]
if sidecar != digest:
raise BaselineAuthorityError("authority_sidecar_digest_mismatch")
required = {
"current_baseline", "previous_baseline", "target_oid", "tag_oid",
"evidence_closure_oid", "approval_plan_id", "status", "generation",
"source_identity",
}
missing = sorted(required.difference(value))
if missing:
raise BaselineAuthorityError("authority_missing:" + ",".join(missing))
value = dict(value)
value["authority_sha256"] = digest
value["authority_path"] = str(self.path)
return value
def read_consistent(self, policy: dict[str, Any] | None = None) -> dict[str, Any]:
value = self.read()
if policy:
expected = policy.get("release_binding", policy)
for key, field in (("version", "current_baseline"),
("previous_version", "previous_baseline"),
("target_oid", "target_oid"),
("tag_oid", "tag_oid"),
("evidence_closure_oid", "evidence_closure_oid"),
("approval_plan_id", "approval_plan_id")):
wanted = expected.get(key)
if wanted is not None and value.get(field) != wanted:
raise BaselineAuthorityError(f"authority_binding_mismatch:{field}")
return value
+74
View File
@@ -0,0 +1,74 @@
"""Fixed registered composition of the existing shadow API and renewal store."""
import base64
import importlib.util
import json
from pathlib import Path
import runpy
import sys
ORIGINAL_SHA = 'f6393fe614239036a1d8235c98c1f455dc32b700107d88f48190c4f61dae617e'
def service_type(types, renewal, public_key):
class RegisteredService(types.SilentReleaseActionService):
def __init__(self, root, **kwargs):
if 'completion_approvals' in kwargs:
raise renewal.RenewalRejected('FIXED_COMPLETION_DEPENDENCY_REQUIRED')
original = types.SignedApprovalStore(Path(root) / 'approvals')
completion = renewal.RegisteredCompletionApprovalStore(
original, public_key, types.SignedReleasePlanApproval,
)
super().__init__(root, completion_approvals=completion, **kwargs)
return RegisteredService
def main():
# Original Round3 startup and verifier remain loaded before the new root
# is added. The C5 types are a separate registered module as in R2.
import silent_release_hardening as original
import hashlib
if hashlib.sha256(Path(original.__file__).read_bytes()).hexdigest() != ORIGINAL_SHA:
raise RuntimeError('ROUND3_HARDENING_DRIFT')
if not getattr(original.BuilderAttestationVerifier.verify,
'__aios_round3_rollback_contract_patch__', False):
raise RuntimeError('ROUND3_VERIFIER_NOT_ACTIVE')
import c5_completion_renewal as renewal
renewal.configure_registered_bindings()
root = renewal.AUTHORITY_ROOT
sys.path.insert(0, str(root))
if Path(__file__) != root / 'c5_completion_api.py':
raise renewal.RenewalRejected('REGISTERED_API_WRAPPER_REQUIRED')
policy = renewal.RegisteredCompletionAuthority()()
parent = json.loads(renewal.secure_bytes(Path(policy['predecessor_snapshot'])))
keyring = json.loads(renewal.secure_bytes(root / 'approval-public-keyring.json'))
if keyring.get('algorithm') != 'Ed25519' or parent['key_id'] in keyring.get('revoked', []):
raise renewal.RenewalRejected('REGISTERED_APPROVAL_KEY_REQUIRED')
public_key = base64.b64decode(keyring['keys'][parent['key_id']], validate=True)
renewal.verify_signature(parent, public_key)
spec = importlib.util.spec_from_file_location('_registered_c5_completion_types',
root / 'silent_release_hardening.py')
types = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = types
spec.loader.exec_module(types)
loaded = runpy.run_path(str(root / 'original_api.py'), run_name='_registered_original_api')
g = loaded['main'].__globals__
if g['SILENT_RELEASE_EXECUTION_ENABLED'] is not False or Path(g['SILENT_RELEASE_STATE_ROOT']) != renewal.STATE_ROOT:
raise renewal.RenewalRejected('EXISTING_SHADOW_PLANE_REQUIRED')
g['ReleasePlan'] = types.ReleasePlan
g['TrustedCandidateBundle'] = types.TrustedCandidateBundle
g['SilentReleaseActionService'] = service_type(types, renewal, public_key)
g['TRUSTED_RELEASE_POLICY'] = renewal.TRUSTED_RELEASE_POLICY_PATH
service = g['_build_silent_release_service']()
if service.execution_enabled or service.policy_sha256 != renewal.TRUSTED_RELEASE_POLICY_SHA256:
raise renewal.RenewalRejected('EXACT_API_POLICY_REQUIRED')
# Preserve the exact candidate boundary from the existing registered R2.
def reject_candidate_creation(payload):
raise original.CapabilityAuthorizationError('completion mode cannot create or replace candidates')
service.create_release_candidate = reject_candidate_creation
g['_SILENT_RELEASE_SERVICE'] = service
g['main']()
return 0
if __name__ == '__main__':
raise SystemExit(main())
+100
View File
@@ -0,0 +1,100 @@
"""Completion-only fixed tag and metadata adapter launcher, no runtime effects."""
import argparse
import base64
import io
import json
import os
from pathlib import Path
import runpy
import sys
import urllib.request
UNIT = 'aios-c5-exact-production-host-r1.service'
def main():
parser = argparse.ArgumentParser()
parser.add_argument('adapter')
parser.add_argument('mode', choices=('tag', 'registry', 'baseline'))
args = parser.parse_args()
import c5_completion_renewal as renewal
renewal.configure_registered_bindings()
root = renewal.AUTHORITY_ROOT
sys.path.insert(0, str(root))
from silent_release_hardening import SignedApprovalStore, SignedReleasePlanApproval
if Path(__file__) != root / 'c5_completion_phase.py' or os.geteuid() != 0:
raise renewal.RenewalRejected('REGISTERED_COMPLETION_PHASE_REQUIRED')
policy = renewal.RegisteredCompletionAuthority()()
credentials = Path(os.environ.get('CREDENTIALS_DIRECTORY', ''))
if credentials != Path('/run/credentials') / UNIT:
raise renewal.RenewalRejected('FIXED_COMPLETION_PURPOSE_UNIT_REQUIRED')
if not any(line.split(':', 2)[-1].rstrip('/').endswith('/' + UNIT)
for line in Path('/proc/self/cgroup').read_text().splitlines()):
raise renewal.RenewalRejected('FIXED_COMPLETION_CGROUP_REQUIRED')
module = 'governed_tag_adapter.py' if args.mode == 'tag' else 'release_metadata_adapter.py'
if Path(args.adapter) != ROOT / module:
raise renewal.RenewalRejected('FIXED_COMPLETION_ADAPTER_REQUIRED')
value = json.load(sys.stdin)
fixed = {'schema': 'aios.production-executor-request.v1', 'version': 'V2R2C5',
'target_oid': renewal.SOURCE, 'evidence_closure_oid': renewal.SOURCE,
'artifact_sha256': renewal.ARTIFACT,
'approval_plan_id': 'sha256:d8f64b8b92f9fdb17f5c6053b76a68ee5bcbc2f266d91438c4db7893463079f8',
'repository_identity': 'aios/aios', 'production_service': 'aios-v2r2c2-production.service',
'production_port': 18928}
if args.mode != 'tag':
fixed['tag_oid'] = renewal.TAG_OID
if (set(value) != set(fixed) | {'operation'} or value['operation'] not in {'probe', 'apply'}
or any(value[k] != v for k, v in fixed.items())):
raise renewal.RenewalRejected('EXACT_COMPLETION_REQUEST_REQUIRED')
parent = json.loads(renewal.secure_bytes(Path(policy['predecessor_snapshot'])))
ring = json.loads(renewal.secure_bytes(root / 'approval-public-keyring.json'))
if parent['key_id'] in ring.get('revoked', []):
raise renewal.RenewalRejected('APPROVAL_KEY_REVOKED')
public_key = base64.b64decode(ring['keys'][parent['key_id']], validate=True)
approval = renewal.RegisteredCompletionApprovalStore(
SignedApprovalStore(renewal.STATE_ROOT / 'approvals'), public_key, SignedReleasePlanApproval,
).load(renewal.CID)
if approval.fingerprint == renewal.OLD_FP:
raise renewal.RenewalRejected('FRESH_COMPLETION_SUCCESSOR_REQUIRED')
# Loading the store verifies both actual signatures, registered fixed nonce,
# append-only chain and current successor lifetime. The historical proof
# intentionally remains bound to the original approval, never rewritten.
runtime = json.load(urllib.request.urlopen('http://127.0.0.1:18928/health', timeout=10))
if any(runtime.get(k) != v for k, v in {
'version': 'V2R2C5', 'commit_oid': renewal.SOURCE, 'artifact_sha256': renewal.ARTIFACT,
'deployment_state': 'ACTIVE', 'production_deployed': True}.items()):
raise renewal.RenewalRejected('CURRENT_C5_RUNTIME_REQUIRED')
# Exact signed copies of these historical helpers are read-only here.
# Never call their activation, run(), or verification-phase effect methods.
import production_verification_phase_r1 as business
import public_business_acceptance_probe_r1 as public
business.RegisteredVerificationPhase.business_proof(None, True)
public.reconcile()
os.environ.clear()
os.environ.update(PATH='/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',
PYTHONDONTWRITEBYTECODE='1', AIOS_GITOPS_WRITES_ENABLED='false',
AIOS_GITOPS_LEGACY_GLOBAL_WRITES_ENABLED='false',
AIOS_GITOPS_CONTROLLER_WORKSPACE=str(renewal.CONTROLLER_WORKSPACE),
AIOS_GITOPS_AUTHORITATIVE_GIT_DIR=str(renewal.AUTHORITATIVE_GIT_DIR),
AIOS_GITOPS_REPOSITORY_IDENTITY='aios/aios',
AIOS_RUNTIME_REGISTRY_ROOT=str(renewal.RUNTIME_REGISTRY_ROOT),
AIOS_RUNTIME_BASELINE_PATH=str(renewal.RUNTIME_BASELINE_PATH))
if args.mode == 'tag':
os.environ.update(GIT_COMMITTER_NAME='owner', GIT_COMMITTER_EMAIL='owner@cnnetmkt.top',
GIT_COMMITTER_DATE='1790826063 +0000', GIT_TERMINAL_PROMPT='0',
GIT_ASKPASS=str(root / 'publisher-askpass.py'))
sys.argv = [module, '--workspace', str(renewal.CONTROLLER_WORKSPACE),
'--repository', str(renewal.AUTHORITATIVE_GIT_DIR), '--remote', 'gitea-http',
'--gitea-api', 'http://127.0.0.1:3000/api/v1', '--gitea-repo', 'aios/aios',
'--gitea-token-file', str(credentials / 'gitea-release-publisher'),
'--state-root', str(renewal.HISTORICAL_ROOT / 'tag-publication')]
else:
sys.argv = [module, '--mode', args.mode, '--expected-previous-baseline', 'V2R2C4',
'--expected-previous-baseline-sha256', 'de0c7008facc44c5061239ee490135b58e521989952918f84a57cecb3358483c',
'--health-url', 'http://127.0.0.1:18928/health']
sys.stdin = io.StringIO(json.dumps(value))
runpy.run_path(str(root / module), run_name='__main__')
if __name__ == '__main__':
main()
+630
View File
@@ -0,0 +1,630 @@
"""Append-only C5 completion renewal boundary; activation requires registration.
The exact Owner-registered API, signer and completion host must call this
boundary. This module does not grant authority or perform deployment effects.
"""
import base64
import hashlib
import json
import math
import os
import stat
import time
from datetime import datetime, timezone
from pathlib import Path
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
CID = 'rc-v2r2c5-5f65e458957d'
# Immutable release content remains compiled into the validator. Renewable
# authority identities and filesystem locations are installed only from the
# digest-bound, root-owned registration file (see configure_registered_bindings).
OLD_FP = '0' * 64
SOURCE = '5f65e458957dd51c9973cfa956a624f3cbf54990'
TREE = 'ddd239d43d63a7fbd3cff46119c71466c9443737'
ARTIFACT = '7f9d8409f07a5e0d36f5db7c55eb82ea70935fa731e057998f29cd1a5ca0a9ad'
PLAN = '370521afcf9b2288f6ad3803ca3554a46ee05d2042d53b19b4f4cb80e37a9684'
MUTABLE = {'issued_at', 'not_before', 'expires_at', 'nonce', 'fingerprint', 'signature'}
CAPABILITIES = ['baseline_registry_finalize', 'runtime_switch', 'tag_publish']
AUTHORITY_ROOT = Path('/unconfigured/c5-completion-authority')
STATE_ROOT = Path('/unconfigured/c5-completion-state')
OWNER_KEY = 'UNCONFIGURED'
TAG_OID = '6d1745ff496103db7e721a2b2bc624fda52c5dc8'
SAGA_HEAD = '0' * 64
HISTORICAL_ROOT = Path('/unconfigured/c5-completion-history')
CONTROLLER_WORKSPACE = Path('/unconfigured/c5-controller-workspace')
AUTHORITATIVE_GIT_DIR = Path('/unconfigured/c5-authoritative-git')
RUNTIME_REGISTRY_ROOT = Path('/unconfigured/c5-runtime-registry')
RUNTIME_BASELINE_PATH = Path('/unconfigured/c5-runtime-baseline.json')
TRUSTED_RELEASE_POLICY_PATH = Path('/unconfigured/c5-trusted-release-policy.json')
TRUSTED_RELEASE_POLICY_SHA256 = '0' * 64
BINDINGS_PATH_ENV = 'AIOS_C5_COMPLETION_BINDINGS_PATH'
BINDINGS_SHA256_ENV = 'AIOS_C5_COMPLETION_BINDINGS_SHA256'
_BINDINGS_DIGEST = None
RENEWAL_SOURCE_FILES = ('c5_completion_renewal.py', 'c5_release_execution_host.py',
'silent_release_hardening.py', 'c5_completion_signer.py',
'c5_completion_api.py', 'c5_completion_phase.py') # gitleaks:allow -- filenames only
PHASE_DEPENDENCIES = ('governed_tag_adapter.py', 'release_metadata_adapter.py',
'production_verification_phase_r1.py', 'public_business_acceptance_probe_r1.py',
'publisher-askpass.py', 'c5_staging_installation.py',
'aios_single_host_authority.py', 'registered_owner_signer_adapter.py')
class RenewalRejected(ValueError):
pass
def canonical(value):
return json.dumps(value, sort_keys=True, separators=(',', ':'), allow_nan=False).encode()
def digest(raw):
return hashlib.sha256(raw).hexdigest()
def validate_completion_lineage(gate):
"""Bind a current completion-only journal suffix to the frozen Saga15 head.
The suffix is authenticated by the enclosing registered Owner contract;
these checks preserve its origin and exclude deployment/source transitions.
"""
revision = gate.get('saga_revision')
suffix = gate.get('completion_journal_suffix', [])
observed = gate.get('observed_at')
if (type(revision) is not int or revision < 15 or not isinstance(suffix, list)
or type(observed) not in {int, float} or not math.isfinite(observed)):
raise RenewalRejected('COMPLETION_LINEAGE_INVALID')
if revision == 15:
if gate.get('saga_head') != SAGA_HEAD or suffix:
raise RenewalRejected('COMPLETION_LINEAGE_INVALID')
return
if not 0 < len(suffix) <= 64 or len(suffix) != revision - 15:
raise RenewalRejected('COMPLETION_LINEAGE_INVALID')
allowed = {
'owner_registered_completion_successor_tag_reconciliation': ('TAG_PENDING', {'RECONCILIATION_REQUIRED'}),
'owner_registered_tag_capability_successor_verified': ('TAG_VERIFIED', {'RECONCILIATION_REQUIRED', 'TAG_PENDING'}),
'baseline_registry_finalization_pending': ('BASELINE_REGISTRY_PENDING', {'TAG_VERIFIED'}),
'owner_registered_completion_successor_metadata_reconciliation': ('BASELINE_REGISTRY_PENDING', {'RECONCILIATION_REQUIRED'}),
'release_reconciliation_required': ('RECONCILIATION_REQUIRED', {'TAG_PENDING', 'TAG_VERIFIED', 'BASELINE_REGISTRY_PENDING'}),
}
previous, state = SAGA_HEAD, 'RECONCILIATION_REQUIRED'
for offset, event in enumerate(suffix, 16):
if (not isinstance(event, dict)
or set(event) != {'schema_version', 'sequence', 'previous_hash', 'payload', 'event_hash'}
or type(event['schema_version']) is not int or event['schema_version'] != 1
or type(event['sequence']) is not int or event['sequence'] != offset + 1
or event['previous_hash'] != previous):
raise RenewalRejected('COMPLETION_LINEAGE_INVALID')
payload = event['payload']
if (not isinstance(payload, dict)
or set(payload) != {'candidate_id', 'release_plan_id', 'revision', 'state', 'event', 'timestamp', 'details'}
or payload['candidate_id'] != CID
or payload['release_plan_id'] != 'pending-' + CID
or type(payload['revision']) is not int or payload['revision'] != offset
or not isinstance(payload['details'], dict)
or not isinstance(payload['event'], str) or payload['event'] not in allowed):
raise RenewalRejected('COMPLETION_LINEAGE_INVALID')
target, origins = allowed[payload['event']]
if payload['state'] != target or state not in origins:
raise RenewalRejected('COMPLETION_LINEAGE_INVALID')
if ('release_plan_sha256' in payload['details']
and payload['details']['release_plan_sha256'] != PLAN):
raise RenewalRejected('COMPLETION_LINEAGE_INVALID')
try:
if timestamp(payload['timestamp']) > gate['observed_at']:
raise RenewalRejected('COMPLETION_LINEAGE_INVALID')
actual = digest(canonical({key: event[key] for key in ('sequence', 'previous_hash', 'payload')}))
except (TypeError, ValueError, AttributeError, KeyError) as exc:
raise RenewalRejected('COMPLETION_LINEAGE_INVALID') from exc
if event['event_hash'] != actual:
raise RenewalRejected('COMPLETION_LINEAGE_INVALID')
previous, state = actual, target
if gate.get('saga_head') != previous:
raise RenewalRejected('COMPLETION_LINEAGE_INVALID')
def timestamp(value):
parsed = datetime.fromisoformat(value.replace('Z', '+00:00'))
if parsed.tzinfo is None or parsed.utcoffset().total_seconds() != 0:
raise RenewalRejected('UTC_REQUIRED')
return parsed.timestamp()
def verify_signature(approval, public_key):
payload = {k: v for k, v in approval.items() if k not in {'signature', 'fingerprint'}}
raw = canonical(payload)
if approval.get('fingerprint') != digest(raw):
raise RenewalRejected('APPROVAL_DIGEST_INVALID')
Ed25519PublicKey.from_public_bytes(public_key).verify(
base64.b64decode(approval['signature'], validate=True), raw
)
def validate_successor(old, new, public_key, *, now):
"""Validate historical predecessor and fresh identical-binding successor.
Historical signature verification does not authorize an expired effect.
The caller must separately verify its fresh registered Owner capability.
"""
verify_signature(old, public_key)
verify_signature(new, public_key)
exact = {'candidate_id': CID, 'source_commit': SOURCE, 'tree_oid': TREE,
'artifact_sha256': ARTIFACT, 'release_plan_sha256': PLAN,
'fingerprint': OLD_FP, 'capabilities': CAPABILITIES}
if any(old.get(k) != v for k, v in exact.items()):
raise RenewalRejected('EXACT_PREDECESSOR_REQUIRED')
if set(old) != set(new) or any(new[k] != old[k] for k in set(old) - MUTABLE):
raise RenewalRejected('IMMUTABLE_APPROVAL_BINDING_DRIFT')
if timestamp(old['expires_at']) > now:
raise RenewalRejected('PREDECESSOR_NOT_EXPIRED')
issued, not_before, expires = (timestamp(new[k]) for k in ['issued_at', 'not_before', 'expires_at'])
if not issued <= not_before <= now < expires or not 0 < expires - issued <= 900:
raise RenewalRejected('FRESH_BOUNDED_APPROVAL_REQUIRED')
if new['nonce'] == old['nonce'] or new['fingerprint'] == old['fingerprint']:
raise RenewalRejected('FRESH_APPROVAL_IDENTITY_REQUIRED')
return new['fingerprint']
def secure_bytes(path, owner_uid=0):
path = Path(path)
descriptor = os.open(path, os.O_RDONLY | os.O_NOFOLLOW)
with os.fdopen(descriptor, 'rb') as stream:
metadata = os.fstat(stream.fileno())
if not stat.S_ISREG(metadata.st_mode) or metadata.st_uid != owner_uid or metadata.st_mode & 0o022:
raise RenewalRejected('UNSAFE_RENEWAL_FILE')
return stream.read()
def _safe_parents(path):
"""Reject writable or symlinked ancestors before opening authority files."""
path = Path(path)
if not path.is_absolute() or '..' in path.parts:
raise RenewalRejected('ABSOLUTE_CANONICAL_PATH_REQUIRED')
for parent in path.parents:
info = parent.lstat()
if not stat.S_ISDIR(info.st_mode) or info.st_uid != 0 or info.st_mode & 0o022:
raise RenewalRejected('UNSAFE_AUTHORITY_ANCESTOR')
def _sha(value):
return isinstance(value, str) and len(value) == 64 and all(c in '0123456789abcdef' for c in value)
def _oid(value):
return isinstance(value, str) and len(value) == 40 and all(c in '0123456789abcdef' for c in value)
def configure_registered_bindings(path=None, expected_sha256=None, *, owner_uid=0):
"""Install renewable completion bindings from one authenticated file.
No historical approval fingerprint, Saga head, Owner key, or authority/state
path is a runtime default. The service registration must provide both the
absolute file path and its SHA-256 digest.
"""
global _BINDINGS_DIGEST
path = Path(path or os.environ.get(BINDINGS_PATH_ENV, ''))
expected_sha256 = expected_sha256 or os.environ.get(BINDINGS_SHA256_ENV, '')
if not path.is_absolute() or not _sha(expected_sha256):
raise RenewalRejected('REGISTERED_COMPLETION_BINDINGS_REQUIRED')
_safe_parents(path)
raw = secure_bytes(path, owner_uid=owner_uid)
if digest(raw) != expected_sha256:
raise RenewalRejected('COMPLETION_BINDINGS_DIGEST_MISMATCH')
if _BINDINGS_DIGEST is not None and _BINDINGS_DIGEST != expected_sha256:
raise RenewalRejected('COMPLETION_BINDINGS_REBIND_FORBIDDEN')
try:
value = json.loads(raw)
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
raise RenewalRejected('COMPLETION_BINDINGS_INVALID') from exc
fields = {
'schema', 'authority_root', 'state_root', 'owner_key_id',
'predecessor_fingerprint', 'saga_head', 'historical_root',
'controller_workspace', 'authoritative_git_dir',
'runtime_registry_root', 'runtime_baseline_path',
'trusted_release_policy_path', 'trusted_release_policy_sha256',
}
if not isinstance(value, dict) or set(value) != fields or value.get('schema') != 'aios.c5.completion-bindings.v1':
raise RenewalRejected('COMPLETION_BINDINGS_SCHEMA_INVALID')
authority_root = Path(value['authority_root'])
state_root = Path(value['state_root'])
governed_paths = {
name: Path(value[name]) for name in (
'historical_root', 'controller_workspace', 'authoritative_git_dir',
'runtime_registry_root', 'runtime_baseline_path', 'trusted_release_policy_path',
)
}
owner_key_id = value['owner_key_id']
try:
owner_key_valid = (isinstance(owner_key_id, str)
and owner_key_id.startswith('SHA256:')
and len(base64.b64decode(owner_key_id[7:], validate=True)) == 32)
except (ValueError, TypeError):
owner_key_valid = False
all_paths = (authority_root, state_root, *governed_paths.values())
if (any(not item.is_absolute() or '..' in item.parts for item in all_paths)
or not _sha(value['predecessor_fingerprint']) or not _sha(value['saga_head'])
or not _sha(value['trusted_release_policy_sha256'])
or not owner_key_valid):
raise RenewalRejected('COMPLETION_BINDINGS_VALUE_INVALID')
if path.parent != authority_root:
raise RenewalRejected('COMPLETION_BINDINGS_ROOT_MISMATCH')
global AUTHORITY_ROOT, STATE_ROOT, OWNER_KEY, OLD_FP, SAGA_HEAD
global HISTORICAL_ROOT, CONTROLLER_WORKSPACE, AUTHORITATIVE_GIT_DIR
global RUNTIME_REGISTRY_ROOT, RUNTIME_BASELINE_PATH
global TRUSTED_RELEASE_POLICY_PATH, TRUSTED_RELEASE_POLICY_SHA256
AUTHORITY_ROOT = authority_root
STATE_ROOT = state_root
OWNER_KEY = value['owner_key_id']
OLD_FP = value['predecessor_fingerprint']
SAGA_HEAD = value['saga_head']
HISTORICAL_ROOT = governed_paths['historical_root']
CONTROLLER_WORKSPACE = governed_paths['controller_workspace']
AUTHORITATIVE_GIT_DIR = governed_paths['authoritative_git_dir']
RUNTIME_REGISTRY_ROOT = governed_paths['runtime_registry_root']
RUNTIME_BASELINE_PATH = governed_paths['runtime_baseline_path']
TRUSTED_RELEASE_POLICY_PATH = governed_paths['trusted_release_policy_path']
TRUSTED_RELEASE_POLICY_SHA256 = value['trusted_release_policy_sha256']
_BINDINGS_DIGEST = expected_sha256
return dict(value)
class RegisteredCompletionAuthority:
"""Fixed root registration, Owner signature and immutable evidence verifier.
The completed production proofs are historical facts. This fresh authority
permits only approval renewal and remaining metadata effects; it cannot
authorize another runtime switch or public acceptance Task.
"""
@property
def root(self):
return AUTHORITY_ROOT
@property
def state_root(self):
return STATE_ROOT
def __call__(self):
from c5_staging_installation import RegistryOwnerVerifier
root = AUTHORITY_ROOT
_safe_parents(root / 'contract.json')
envelope = json.loads(secure_bytes(root / 'contract.json'))
if set(envelope) != {'payload', 'signature'}:
raise RenewalRejected('EXACT_AUTHORITY_ENVELOPE_REQUIRED')
p = envelope['payload']
fields = {'schema', 'host', 'repository', 'candidate_id', 'source_commit', 'tree_oid',
'artifact_sha256', 'release_plan_sha256', 'state_root', 'predecessor_fingerprint',
'predecessor_sha256', 'predecessor_uid', 'predecessor_snapshot', 'approval_nonce',
'issued_at', 'expires_at', 'owner_key_id', 'owner_namespace', 'allowed_effects',
'runtime_effects_allowed', 'files', 'gate_path', 'gate_observed_at',
'governed_source_commit', 'governed_source_tree'}
if not isinstance(p, dict) or set(p) != fields:
raise RenewalRejected('EXACT_AUTHORITY_SCHEMA_REQUIRED')
exact = {'schema': 'aios.c5.registered-completion-authority.v1', 'host': 'cn-network-mkt',
'repository': 'aios/aios', 'candidate_id': CID, 'source_commit': SOURCE,
'tree_oid': TREE, 'artifact_sha256': ARTIFACT, 'release_plan_sha256': PLAN,
'state_root': str(STATE_ROOT), 'predecessor_fingerprint': OLD_FP,
'owner_key_id': OWNER_KEY, 'owner_namespace': 'aios-owner-approval-v1',
'allowed_effects': ['approval_successor_append', 'baseline_finalize', 'registry_finalize', 'tag_publish'],
'runtime_effects_allowed': False}
if any(p[k] != v for k, v in exact.items()) or type(p['runtime_effects_allowed']) is not bool:
raise RenewalRejected('EXACT_COMPLETION_AUTHORITY_REQUIRED')
now = time.time()
if (type(p['issued_at']) is not int or type(p['expires_at']) is not int
or not p['issued_at'] <= now < p['expires_at']
or not 0 < p['expires_at'] - p['issued_at'] <= 1800
or type(p['gate_observed_at']) not in {int, float}
or not math.isfinite(p['gate_observed_at'])
or not p['issued_at'] - 60 <= p['gate_observed_at'] <= p['issued_at']):
raise RenewalRejected('FRESH_OWNER_AUTHORITY_REQUIRED')
if (not _sha(p['approval_nonce']) or not _sha(p['predecessor_sha256'])
or type(p['predecessor_uid']) is not int or p['predecessor_uid'] not in {0, 1000}
or any(not isinstance(p[k], str) or len(p[k]) != 40
or any(c not in '0123456789abcdef' for c in p[k])
for k in ('governed_source_commit', 'governed_source_tree'))):
raise RenewalRejected('AUTHORITY_IDENTITIES_INVALID')
verifier = RegistryOwnerVerifier(Path('/etc/aios/single-host-signer-registry.json'),
owner_signer_id='local-owner-authority')
if not verifier.verify(namespace=p['owner_namespace'], payload=canonical(p),
signature=envelope['signature'], key_id=p['owner_key_id']):
raise RenewalRejected('OWNER_SIGNATURE_REJECTED')
payload_sha = digest(canonical(p))
registered = json.loads(secure_bytes(root / 'registration.json'))
if (set(registered) != {'schema', 'status', 'payload_sha256', 'approval_nonce'}
or registered != {'schema': 'aios.c5.completion-registration.v1', 'status': 'REGISTERED',
'payload_sha256': payload_sha, 'approval_nonce': p['approval_nonce']}):
raise RenewalRejected('ROOT_REGISTRATION_REQUIRED')
if (root / 'rollback.json').exists():
raise RenewalRejected('REGISTRATION_ROLLED_BACK')
pins = p['files']
if not isinstance(pins, list) or not pins:
raise RenewalRejected('SIGNED_FILE_CLOSURE_REQUIRED')
checked = {}
for item in pins:
if (not isinstance(item, dict) or set(item) != {'path', 'sha256', 'uid'}
or not _sha(item['sha256']) or type(item['uid']) is not int or item['uid'] != 0):
raise RenewalRejected('SIGNED_FILE_PIN_INVALID')
path = Path(item['path'])
if path.parent != root or str(path) in checked:
raise RenewalRejected('SIGNED_FILE_PATH_INVALID')
_safe_parents(path)
raw = secure_bytes(path)
if digest(raw) != item['sha256']:
raise RenewalRejected('SIGNED_FILE_BYTES_DRIFT')
checked[str(path)] = raw
required = {str(root / name) for name in (*RENEWAL_SOURCE_FILES, *PHASE_DEPENDENCIES, 'source-material.json',
'historical-production-proof.json', 'predecessor-approval.json', 'current-gate.json',
'approval-public-keyring.json', 'original_api.py')}
if not required <= set(checked):
raise RenewalRejected('INCOMPLETE_REGISTERED_CLOSURE')
if Path(__file__) != root / 'c5_completion_renewal.py':
raise RenewalRejected('UNREGISTERED_AUTHORITY_MODULE')
if (p['predecessor_snapshot'] != str(root / 'predecessor-approval.json')
or p['gate_path'] != str(root / 'current-gate.json')
or digest(checked[p['predecessor_snapshot']]) != p['predecessor_sha256']):
raise RenewalRejected('REGISTERED_SNAPSHOT_DRIFT')
material = json.loads(checked[str(root / 'source-material.json')])
if (material.get('status') != 'MATERIALIZED' or material.get('task_status') != 'SUCCEEDED'
or material.get('remote_verified') is not True
or material.get('source_commit') != p['governed_source_commit']
or material.get('source_tree') != p['governed_source_tree']
or material.get('file_sha256') != {
name: digest(checked[str(root / name)])
for name in RENEWAL_SOURCE_FILES}):
raise RenewalRejected('GOVERNED_CODE_PROVENANCE_REQUIRED')
historical = json.loads(checked[str(root / 'historical-production-proof.json')])
if (historical.get('candidate_id') != CID or historical.get('approval_fingerprint') != OLD_FP
or historical.get('release_plan_sha256') != PLAN
or historical.get('status') != 'COMPLETED'
or any(historical.get(k) is not True for k in (
'public_business_verified', 'real_c4_rollback_verified', 'c5_restore_verified'))):
raise RenewalRejected('COMPLETED_PRODUCTION_PROOF_REQUIRED')
gate = json.loads(checked[p['gate_path']])
expected_gate = {'candidate_id': CID, 'source_commit': SOURCE, 'artifact_sha256': ARTIFACT,
'release_plan_sha256': PLAN,
'local_tag_oid': TAG_OID, 'runtime_verified': True, 'business_verified': True,
'global_writes_enabled': False, 'observed_at': p['gate_observed_at']}
if (any(gate.get(k) != v for k, v in expected_gate.items())
or any(type(gate.get(k)) is not bool for k in (
'runtime_verified', 'business_verified', 'global_writes_enabled'))
or gate.get('remote_tag_oid') not in {None, TAG_OID}):
raise RenewalRejected('EXACT_POSTVERIFICATION_GATE_REQUIRED')
validate_completion_lineage(gate)
return {**p, 'payload_sha256': payload_sha}
def validate_host(self, *, state_root, candidate_id, source_commit, tree_oid,
artifact_sha256, release_plan_sha256):
self()
if (Path(state_root), candidate_id, source_commit, tree_oid, artifact_sha256, release_plan_sha256) != (
STATE_ROOT, CID, SOURCE, TREE, ARTIFACT, PLAN):
raise RenewalRejected('COMPLETION_HOST_BINDING_DRIFT')
def deny_runtime_effect(self, **kwargs):
self()
raise RenewalRejected('COMPLETION_RUNTIME_EFFECT_FORBIDDEN')
class CompletionRuntimeGuard:
def __init__(self, authority):
if type(authority) is not RegisteredCompletionAuthority:
raise RenewalRejected('REGISTERED_COMPLETION_AUTHORITY_REQUIRED')
self.authority = authority
def execute(self, **kwargs):
return self.authority.deny_runtime_effect(**kwargs)
def append_file(path, raw):
"""An existing record must be identical; it is never replaced."""
path = Path(path)
try:
fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600)
except FileExistsError:
if secure_bytes(path, owner_uid=os.geteuid()) != raw:
raise RenewalRejected('APPEND_ONLY_RECORD_CONFLICT')
return False
with os.fdopen(fd, 'wb') as stream:
stream.write(raw)
stream.flush()
os.fsync(stream.fileno())
fd = os.open(path.parent, os.O_DIRECTORY)
try:
os.fsync(fd)
finally:
os.close(fd)
return True
def publish_successor(directory, old, new, public_key, *, now, registration_digest, predecessor_bytes):
"""Append records inside the SAME plane under a verified caller lock.
Registration validation, current Saga CAS, production/business readbacks,
and permission to call this function remain mandatory caller gates.
"""
fp = validate_successor(old, new, public_key, now=now)
if json.loads(predecessor_bytes) != old:
raise RenewalRejected('PREDECESSOR_BYTES_DRIFT')
predecessor_bytes_digest = digest(predecessor_bytes)
if len(registration_digest) != 64 or any(c not in '0123456789abcdef' for c in registration_digest):
raise RenewalRejected('REGISTERED_DIGEST_REQUIRED')
directory = Path(directory)
metadata = directory.lstat()
if not stat.S_ISDIR(metadata.st_mode) or metadata.st_uid != os.geteuid() or metadata.st_mode & 0o022:
raise RenewalRejected('UNSAFE_APPEND_DIRECTORY')
receipt = {'schema': 'aios.c5.completion-approval-successor.v1', 'candidate_id': CID,
'predecessor_fingerprint': OLD_FP, 'predecessor_bytes_sha256': predecessor_bytes_digest,
'successor_fingerprint': fp, 'successor_sha256': digest(canonical(new)),
'owner_registration_payload_sha256': registration_digest,
'completion_only': True, 'runtime_effects_allowed': False}
# A crash after the first append is unresolved until its exact receipt is
# reconciled. A loader must require BOTH files; no mutable current pointer.
append_file(directory / (fp + '.approval.json'), canonical(new))
append_file(directory / (fp + '.receipt.json'), canonical(receipt))
return receipt
class CompletionApprovalStore:
"""Explicit store dependency for a registered completion host/API.
The original store and original candidate approval remain untouched.
No global class replacement or expired approval verification is used.
An authenticated registration fixes ONE successor nonce and immutable
receipt path before signing. The host must separately enforce TAG_PENDING
and forbid runtime effects; this store does not execute a Saga.
"""
def __init__(self, original, directory, public_key, authority, approval_type):
self.original = original
self.directory = Path(directory)
self.public_key = public_key
self.authority = authority
self.approval_type = approval_type
def _policy(self):
# authority must be the fixed registered verifier, not a caller-supplied
# HTTP callback. Wiring it is outside this unregistered draft.
policy = self.authority()
if policy.get('candidate_id') != CID or policy.get('predecessor_fingerprint') != OLD_FP:
raise RenewalRejected('REGISTERED_EXACT_POLICY_REQUIRED')
nonce = policy.get('approval_nonce', '')
if not isinstance(nonce, str) or len(nonce) != 64 or any(c not in '0123456789abcdef' for c in nonce):
raise RenewalRejected('REGISTERED_NONCE_REQUIRED')
return policy, self.directory / (nonce + '.activation.json')
def _parent(self):
old = self.original.load(CID)
policy, _ = self._policy()
owner = policy.get('predecessor_uid')
if type(owner) is not int or owner not in {0, 1000}:
raise RenewalRejected('REGISTERED_PREDECESSOR_OWNER_REQUIRED')
raw = secure_bytes(self.original.root / (CID + '.json'), owner_uid=owner)
if json.loads(raw) != old.to_dict() or digest(raw) != policy.get('predecessor_sha256'):
raise RenewalRejected('PREDECESSOR_FILE_DRIFT')
return old.to_dict(), raw
def load(self, candidate_id):
if candidate_id != CID:
return self.original.load(candidate_id)
policy, activation = self._policy()
if not activation.exists():
return self.original.load(candidate_id)
receipt = json.loads(secure_bytes(activation, owner_uid=policy['predecessor_uid']))
expected = {'schema', 'candidate_id', 'predecessor_fingerprint', 'predecessor_bytes_sha256',
'successor_fingerprint', 'successor_sha256', 'owner_registration_payload_sha256',
'completion_only', 'runtime_effects_allowed'}
if set(receipt) != expected or receipt.get('owner_registration_payload_sha256') != policy['payload_sha256']:
raise RenewalRejected('ACTIVATION_RECEIPT_INVALID')
fp = receipt.get('successor_fingerprint', '')
if len(fp) != 64 or any(c not in '0123456789abcdef' for c in fp):
raise RenewalRejected('SUCCESSOR_FINGERPRINT_INVALID')
raw = secure_bytes(self.directory / (fp + '.approval.json'), owner_uid=policy['predecessor_uid'])
new = json.loads(raw)
parent, parent_raw = self._parent()
valid = validate_successor(parent, new, self.public_key, now=datetime.now(timezone.utc).timestamp())
exact = {'candidate_id': CID, 'predecessor_fingerprint': OLD_FP,
'predecessor_bytes_sha256': digest(parent_raw), 'successor_fingerprint': valid,
'successor_sha256': digest(raw), 'completion_only': True, 'runtime_effects_allowed': False}
if any(receipt.get(k) != v for k, v in exact.items()) or new['nonce'] != policy['approval_nonce']:
raise RenewalRejected('SUCCESSOR_ACTIVATION_DRIFT')
durable = json.loads(secure_bytes(self.directory / (fp + '.receipt.json'), owner_uid=policy['predecessor_uid']))
if durable != receipt:
raise RenewalRejected('DURABLE_RECEIPT_DRIFT')
return self.approval_type.from_dict(new)
def save(self, candidate_id, approval):
if candidate_id != CID:
return self.original.save(candidate_id, approval)
policy, activation = self._policy()
parent, raw = self._parent()
if approval.fingerprint == parent['fingerprint']:
return self.original.save(candidate_id, approval)
new = approval.to_dict()
if new.get('nonce') != policy['approval_nonce']:
raise RenewalRejected('REGISTERED_NONCE_MISMATCH')
# The caller supplies its authenticated registered effect lock and
# invokes the existing ApprovalVerifier before this store boundary.
receipt = publish_successor(self.directory, parent, new, self.public_key,
now=datetime.now(timezone.utc).timestamp(),
registration_digest=policy['payload_sha256'], predecessor_bytes=raw)
created = append_file(activation, canonical(receipt))
if self.load(candidate_id).fingerprint != approval.fingerprint:
raise RenewalRejected('SUCCESSOR_READBACK_FAILED')
return activation, created
class RegisteredCompletionApprovalStore(CompletionApprovalStore):
"""Production dependency; no request-selected root or authority callback."""
def __init__(self, original, public_key, approval_type):
if Path(original.root) != STATE_ROOT / 'approvals':
raise RenewalRejected('SAME_APPROVAL_PLANE_REQUIRED')
super().__init__(original, STATE_ROOT / 'approvals' / 'c5-completion-successors',
public_key, RegisteredCompletionAuthority(), approval_type)
class CompletionSignerMixin:
"""Narrow additional method of the EXISTING credential-bound signer.
Activation must register this exact mixin, verifier and namespace snapshot
pins. The encrypted private key stays in the existing signer's unit. This
draft supplies no activation, credential access or HTTP route.
"""
def sign_exact_completion_successor(self, request):
expected = {'schema', 'operation', 'candidate_id', 'predecessor_fingerprint'}
if (set(request) != expected or request.get('operation') != 'renew_exact_completion_approval'
or request.get('candidate_id') != CID or request.get('predecessor_fingerprint') != OLD_FP
or request.get('schema') != 'aios.exact-release-signer.request.v1'):
raise RenewalRejected('EXACT_SIGNER_RENEWAL_REQUEST_REQUIRED')
policy = self.completion_authority()
now = datetime.now(timezone.utc).timestamp()
if (policy.get('candidate_id') != CID or policy.get('predecessor_fingerprint') != OLD_FP
or not policy['issued_at'] <= now < policy['expires_at']
or not 0 <= now - policy['gate_observed_at'] <= 60):
raise RenewalRejected('FRESH_REGISTERED_COMPLETION_GATE_REQUIRED')
old_raw = secure_bytes(policy['predecessor_snapshot'], owner_uid=0)
if digest(old_raw) != policy['predecessor_sha256']:
raise RenewalRejected('REGISTERED_SIGNER_PREIMAGE_DRIFT')
old = json.loads(old_raw)
public_key = self.private_key.public_key().public_bytes(Encoding.Raw, PublicFormat.Raw)
verify_signature(old, public_key)
exact = {'candidate_id': CID, 'source_commit': SOURCE, 'tree_oid': TREE,
'artifact_sha256': ARTIFACT, 'release_plan_sha256': PLAN,
'fingerprint': OLD_FP, 'capabilities': CAPABILITIES,
'key_id': self.policy.key_id, 'owner_identity': self.policy.owner_identity,
'trust_domain': self.policy.trust_domain,
'owner_approval_record_oid': self.authorization_oid}
if any(old.get(k) != v for k, v in exact.items()) or timestamp(old['expires_at']) > now:
raise RenewalRejected('EXACT_EXPIRED_SIGNER_PREDECESSOR_REQUIRED')
nonce = policy['approval_nonce']
if len(nonce) != 64 or any(c not in '0123456789abcdef' for c in nonce) or nonce == old['nonce']:
raise RenewalRejected('FRESH_REGISTERED_SIGNER_NONCE_REQUIRED')
output = self.state_root / 'completion-renewals' / (nonce + '.json')
if output.exists():
result = json.loads(secure_bytes(output, owner_uid=0))
if (result.get('registration_payload_sha256') != policy['payload_sha256']
or result['approval']['nonce'] != nonce):
raise RenewalRejected('SIGNER_REPLAY_CONFLICT')
validate_successor(old, result['approval'], public_key, now=now)
return {**result, 'status': 'SIGNED_IDEMPOTENT'}
expiry = min(now + 600, policy['expires_at'] - 5)
if expiry - now < 120:
raise RenewalRejected('INSUFFICIENT_SIGNER_WINDOW')
def utc(value):
return datetime.fromtimestamp(value, timezone.utc).replace(microsecond=0).isoformat().replace('+00:00', 'Z')
payload = {k:v for k,v in old.items() if k not in {'fingerprint', 'signature'}}
payload.update(issued_at=utc(now), not_before=utc(now), expires_at=utc(expiry), nonce=nonce)
raw = canonical(payload)
# All candidate, plan, historical signature and registered freshness
# checks precede this only signing operation.
approval = {**payload, 'fingerprint': digest(raw),
'signature': base64.b64encode(self.private_key.sign(raw)).decode()}
validate_successor(old, approval, public_key, now=now)
result = {'schema': 'aios.exact-release-signer.response.v1', 'status': 'SIGNED',
'approval': approval, 'registration_payload_sha256': policy['payload_sha256'],
'predecessor_fingerprint': OLD_FP, 'completion_only': True, 'arbitrary_signing': False}
# This registered directory must be created by the activation consumer
# inside the existing signer state root, never a request path.
append_file(output, canonical(result))
return result
@@ -0,0 +1,58 @@
"""Registered fixed extension of the existing credential-bound signer unit."""
import importlib.util
import os
from pathlib import Path
import sys
from c5_completion_renewal import (
CompletionSignerMixin, RegisteredCompletionAuthority,
RenewalRejected, secure_bytes, digest,
)
ORIGINAL = Path('/usr/local/libexec/aios_exact_release_signer.py')
ORIGINAL_SHA = '6e2a5a9df0edfe566175e5b556d15901b2c8fe4484388ad74af2ac64f810c930'
def signer_class(original):
class CompletionSigner(CompletionSignerMixin, original.ExactReleaseSigner):
def completion_authority(self):
return RegisteredCompletionAuthority()()
def handle(self, request):
if request.get('operation') == 'renew_exact_completion_approval':
return self.sign_exact_completion_successor(request)
return super().handle(request)
return CompletionSigner
def main():
import c5_completion_renewal as renewal
renewal.configure_registered_bindings()
if Path(__file__) != renewal.AUTHORITY_ROOT / 'c5_completion_signer.py':
raise RenewalRejected('REGISTERED_SIGNER_WRAPPER_REQUIRED')
RegisteredCompletionAuthority()()
if digest(secure_bytes(ORIGINAL)) != ORIGINAL_SHA:
raise RenewalRejected('ORIGINAL_SIGNER_PREIMAGE_DRIFT')
spec = importlib.util.spec_from_file_location('c5_original_exact_release_signer', ORIGINAL)
original = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = original
spec.loader.exec_module(original)
credentials = Path(os.environ.get('CREDENTIALS_DIRECTORY', ''))
if not credentials.is_absolute() or credentials.parent != Path('/run/credentials'):
raise RenewalRejected('EXISTING_SIGNER_PURPOSE_CREDENTIAL_REQUIRED')
if credentials.name != 'aios-exact-release-signer.service':
raise RenewalRejected('EXISTING_SIGNER_UNIT_REQUIRED')
cgroup = Path('/proc/self/cgroup').read_text()
if not any(line.split(':', 2)[-1].rstrip('/').endswith('/aios-exact-release-signer.service')
for line in cgroup.splitlines()):
raise RenewalRejected('EXISTING_SIGNER_CGROUP_REQUIRED')
signer = signer_class(original)(
Path('/etc/aios-gitops/exact-release-signer-policy.json'),
credentials / 'owner_release_private_key',
)
original.serve(Path('/run/aios-exact-release-signer/signer.sock'), signer)
return 0
if __name__ == '__main__':
raise SystemExit(main())
@@ -0,0 +1,503 @@
"""Exact C5 runtime-switch adapter for the silent-release Saga.
The adapter owns only the production deployment phase. Tag publication and
Registry/Baseline finalization remain separate ``ReleaseOrchestrator`` Saga
steps. A host must register immutable executable bytes and provide the real
HMAC-authenticated source-admission store plus the signed Owner approval
verifier before this adapter can be constructed.
"""
from __future__ import annotations
from dataclasses import dataclass
import hashlib
import json
import os
from pathlib import Path
import re
import stat
import subprocess
from typing import Any, Mapping
from release_source_admission import ReleaseSourceAdmissionStore
from silent_release_controller import ReleaseCandidate
from silent_release_hardening import (
CapabilityAuthorizationError,
CapabilityLedger,
ClaimProbeResult,
ReleasePlan,
RuntimeSwitchController,
SignedApprovalVerifier,
SignedReleasePlanApproval,
_canonical_json,
)
C5_VERSION = "V2R2C5"
C5_PREPUBLICATION_SOURCE_COMMIT = "396ea9811124736aa140d2810ab53eaefb6b5c1c"
C4_ROLLBACK_TARGET = {
"version": "V2R2C4",
"target_oid": "e5f8209f57b43fc39e55c4555776e6362ce326eb",
"artifact_sha256": "0dfc0461f7f9aa25cfbfc37008dcbd2d48d5341383574163b7bba102852062c4",
}
C4_ROLLBACK_ARTIFACT_PATH = Path(
"/var/lib/aios-gitops/release-only/"
"v2r2c4-e5f8209f57b4/artifact.tar.gz"
)
C5_PRODUCTION_ENVIRONMENT_FILE = Path("/etc/aios/v2r2c2.env")
C5_PRODUCTION_SERVICE = "aios-v2r2c2-production.service"
C5_PRODUCTION_PORT = 18928
REQUEST_SCHEMA = "aios.c5.exact-production-deployment-request.v1"
HEX64 = re.compile(r"^[0-9a-f]{64}$")
class C5DeploymentBindingError(CapabilityAuthorizationError):
"""C5 source, plan, approval, executor, or receipt binding is invalid."""
class C5DeploymentOutcomeUnknown(RuntimeError):
"""The deployment effect must be reconciled before any retry."""
class C5DeploymentConflict(RuntimeError):
"""The observed runtime is bound to a different release."""
def _sha256_file(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as source:
for block in iter(lambda: source.read(1024 * 1024), b""):
digest.update(block)
return digest.hexdigest()
def _run_registered_adapter(
command: tuple[str, ...], request: Mapping[str, Any]
) -> dict[str, Any]:
"""Invoke only the code-pinned process and accept one JSON object."""
try:
completed = subprocess.run(
list(command),
input=_canonical_json(dict(request)),
check=False,
capture_output=True,
timeout=180,
env={"PATH": os.environ.get("PATH", "")},
)
except (OSError, subprocess.SubprocessError) as exc:
raise C5DeploymentOutcomeUnknown(
"C5_DEPLOYMENT_EXECUTOR_UNAVAILABLE"
) from exc
if completed.returncode != 0:
raise C5DeploymentOutcomeUnknown(
"C5_DEPLOYMENT_EXECUTOR_FAILED"
)
try:
lines = [
line
for line in completed.stdout.decode("utf-8").splitlines()
if line.strip()
]
value = json.loads(lines[-1]) if lines else None
except (UnicodeError, ValueError) as exc:
raise C5DeploymentOutcomeUnknown(
"C5_DEPLOYMENT_EXECUTOR_RESPONSE_INVALID"
) from exc
if not isinstance(value, dict):
raise C5DeploymentOutcomeUnknown(
"C5_DEPLOYMENT_EXECUTOR_RESPONSE_INVALID"
)
return value
def _secure_registered_file(
path: Path,
expected_sha256: str,
*,
require_root_owner: bool,
) -> None:
path = Path(path)
if (
not path.is_absolute()
or path.is_symlink()
or path.resolve(strict=False) != path
):
raise C5DeploymentBindingError("C5_EXECUTOR_PATH_INVALID")
try:
info = path.stat()
except OSError as exc:
raise C5DeploymentBindingError("C5_EXECUTOR_PATH_INVALID") from exc
if not stat.S_ISREG(info.st_mode):
raise C5DeploymentBindingError("C5_EXECUTOR_PATH_INVALID")
if os.name != "nt" and stat.S_IMODE(info.st_mode) & 0o022:
raise C5DeploymentBindingError("C5_EXECUTOR_PATH_WRITABLE")
if require_root_owner:
if not hasattr(os, "geteuid") or info.st_uid != 0:
raise C5DeploymentBindingError("C5_EXECUTOR_OWNER_INVALID")
parent = path.parent
while True:
parent_info = parent.stat()
if (
parent.is_symlink()
or parent_info.st_uid != 0
or stat.S_IMODE(parent_info.st_mode) & 0o022
):
raise C5DeploymentBindingError(
"C5_EXECUTOR_PARENT_SECURITY_INVALID"
)
if parent == parent.parent:
break
parent = parent.parent
if _sha256_file(path) != expected_sha256:
raise C5DeploymentBindingError("C5_EXECUTOR_CODE_MISMATCH")
@dataclass(frozen=True, slots=True)
class RegisteredC5DeploymentExecutor:
"""Pinned host configuration for one concrete process adapter.
``adapter_path`` is optional for a native executable. When present the
command is exactly ``executable adapter_path``; arbitrary argv and Python
callables are intentionally unsupported.
"""
executor_id: str
executable: Path
executable_sha256: str
artifact_path: Path
rollback_artifact_path: Path
production_environment_file: Path
adapter_path: Path | None = None
adapter_sha256: str | None = None
require_root_owner: bool = True
execution_scope: str = "PRODUCTION"
isolation_root: Path | None = None
rollback_target: Mapping[str, str] | None = None
def __post_init__(self) -> None:
if not self.executor_id or len(self.executor_id) > 255:
raise C5DeploymentBindingError("C5_EXECUTOR_ID_INVALID")
if HEX64.fullmatch(self.executable_sha256) is None:
raise C5DeploymentBindingError("C5_EXECUTOR_DIGEST_INVALID")
if (self.adapter_path is None) != (self.adapter_sha256 is None):
raise C5DeploymentBindingError("C5_EXECUTOR_ADAPTER_BINDING_INVALID")
if (
self.adapter_sha256 is not None
and HEX64.fullmatch(self.adapter_sha256) is None
):
raise C5DeploymentBindingError("C5_EXECUTOR_DIGEST_INVALID")
if not Path(self.artifact_path).is_absolute():
raise C5DeploymentBindingError("C5_ARTIFACT_PATH_INVALID")
if self.execution_scope not in {"PRODUCTION", "ISOLATED_REHEARSAL"}:
raise C5DeploymentBindingError("C5_EXECUTION_SCOPE_INVALID")
target = dict(self.rollback_target or C4_ROLLBACK_TARGET)
if target.keys() != C4_ROLLBACK_TARGET.keys() or target["version"] != C4_ROLLBACK_TARGET["version"] or target["target_oid"] != C4_ROLLBACK_TARGET["target_oid"] or HEX64.fullmatch(str(target["artifact_sha256"])) is None:
raise C5DeploymentBindingError("C5_ROLLBACK_TARGET_INVALID")
if self.execution_scope == "PRODUCTION":
if target != C4_ROLLBACK_TARGET:
raise C5DeploymentBindingError("C5_ROLLBACK_TARGET_INVALID")
if Path(self.rollback_artifact_path) != C4_ROLLBACK_ARTIFACT_PATH:
raise C5DeploymentBindingError("C5_ROLLBACK_ARTIFACT_PATH_INVALID")
if Path(self.production_environment_file) != C5_PRODUCTION_ENVIRONMENT_FILE:
raise C5DeploymentBindingError("C5_PRODUCTION_ENVIRONMENT_FILE_INVALID")
if self.isolation_root is not None:
raise C5DeploymentBindingError("C5_ISOLATION_ROOT_INVALID")
else:
if self.isolation_root is None or not Path(self.isolation_root).is_absolute():
raise C5DeploymentBindingError("C5_ISOLATION_ROOT_INVALID")
root = Path(self.isolation_root).resolve(strict=False)
for bound_path in (self.rollback_artifact_path, self.production_environment_file):
path = Path(bound_path)
if not path.is_absolute() or path.resolve(strict=False) == root:
raise C5DeploymentBindingError("C5_REHEARSAL_BINDING_INVALID")
try:
path.resolve(strict=False).relative_to(root)
except ValueError as exc:
raise C5DeploymentBindingError("C5_REHEARSAL_BINDING_INVALID") from exc
_secure_registered_file(
Path(self.rollback_artifact_path),
target["artifact_sha256"],
require_root_owner=self.require_root_owner,
)
object.__setattr__(self, "rollback_target", target)
self.verify_code()
def verify_code(self) -> None:
_secure_registered_file(
Path(self.executable),
self.executable_sha256,
require_root_owner=self.require_root_owner,
)
if self.adapter_path is not None:
_secure_registered_file(
Path(self.adapter_path),
str(self.adapter_sha256),
require_root_owner=self.require_root_owner,
)
@property
def command(self) -> tuple[str, ...]:
values = [str(Path(self.executable))]
if self.adapter_path is not None:
values.append(str(Path(self.adapter_path)))
return tuple(values)
class C5ExactDeploymentAdapter:
"""Callable runtime-switch phase with exact C5 admission and approval."""
def __init__(
self,
*,
candidate: ReleaseCandidate,
release_plan: ReleasePlan,
approval: SignedReleasePlanApproval,
approval_verifier: SignedApprovalVerifier,
source_admissions: ReleaseSourceAdmissionStore,
executor: RegisteredC5DeploymentExecutor,
) -> None:
if not isinstance(approval_verifier, SignedApprovalVerifier):
raise C5DeploymentBindingError("C5_APPROVAL_VERIFIER_REQUIRED")
if not isinstance(source_admissions, ReleaseSourceAdmissionStore):
raise C5DeploymentBindingError("C5_SOURCE_ADMISSION_STORE_REQUIRED")
self.candidate = candidate
self.release_plan = release_plan
self.approval = approval
self.approval_verifier = approval_verifier
self.source_admissions = source_admissions
self.executor = executor
self._binding = self._verify_binding(candidate, release_plan)
@staticmethod
def _global_writes_disabled() -> bool:
return os.environ.get("AIOS_GITOPS_WRITES_ENABLED", "false").lower() == "false"
def _verify_binding(
self,
candidate: ReleaseCandidate,
plan: ReleasePlan,
) -> dict[str, Any]:
if candidate.fingerprint != self.candidate.fingerprint or plan.sha256 != self.release_plan.sha256:
raise C5DeploymentBindingError("C5_RUNTIME_INPUT_BINDING_MISMATCH")
plan.validate()
if plan.tag.version != C5_VERSION or plan.finalization.version != C5_VERSION:
raise C5DeploymentBindingError("C5_VERSION_BINDING_INVALID")
if candidate.source_commit == C5_PREPUBLICATION_SOURCE_COMMIT:
raise C5DeploymentBindingError("C5_PREPUBLICATION_SOURCE_FORBIDDEN")
rollback_target = dict(self.executor.rollback_target or C4_ROLLBACK_TARGET)
if plan.deployment.rollback_target != rollback_target:
raise C5DeploymentBindingError("C5_ROLLBACK_TARGET_INVALID")
if plan.deployment.service != C5_PRODUCTION_SERVICE:
raise C5DeploymentBindingError("C5_PRODUCTION_SERVICE_INVALID")
if plan.deployment.writer_expected is not False:
raise C5DeploymentBindingError("C5_LEGACY_WRITES_MUST_REMAIN_DISABLED")
if not self._global_writes_disabled():
raise C5DeploymentBindingError("C5_LEGACY_WRITES_MUST_REMAIN_DISABLED")
admitted = self.source_admissions.resolve_recipe(
C5_VERSION,
candidate.candidate_id,
candidate.source_commit,
)
if (
admitted.get("source_commit") != candidate.source_commit
or admitted.get("tree_oid") != candidate.tree_oid
):
raise C5DeploymentBindingError("C5_ADMITTED_SOURCE_BINDING_INVALID")
if (
plan.deployment.source_commit != candidate.source_commit
or plan.deployment.tree_oid != candidate.tree_oid
or plan.deployment.artifact_sha256 != candidate.artifact_sha256
or plan.deployment.manifest_sha256 != candidate.manifest_sha256
):
raise C5DeploymentBindingError("C5_CANDIDATE_PLAN_BINDING_INVALID")
self.approval_verifier.verify_release_plan(
self.approval,
candidate,
plan,
plan.policy_sha256,
)
artifact = Path(self.executor.artifact_path)
_secure_registered_file(
artifact,
candidate.artifact_sha256,
require_root_owner=self.executor.require_root_owner,
)
artifact_size = artifact.stat().st_size
if artifact_size <= 0:
raise C5DeploymentBindingError("C5_ARTIFACT_BYTES_MISMATCH")
self.executor.verify_code()
return {
"version": C5_VERSION,
"candidate_id": candidate.candidate_id,
"source_commit": candidate.source_commit,
"target_oid": candidate.source_commit,
"tree_oid": candidate.tree_oid,
"artifact_sha256": candidate.artifact_sha256,
"artifact_path": str(artifact),
"artifact_size": artifact_size,
"manifest_sha256": candidate.manifest_sha256,
"deployment_plan_sha256": plan.deployment.sha256,
"release_plan_sha256": plan.sha256,
"policy_sha256": plan.policy_sha256,
"approval_plan_id": self.approval.approval_plan_id,
"approval_fingerprint": self.approval.fingerprint,
"owner_approval_record_oid": self.approval.owner_approval_record_oid,
"evidence_closure_oid": self.approval.evidence_closure_oid,
"repository_identity": plan.deployment.repository_identity,
"production_service": plan.deployment.service,
"production_port": C5_PRODUCTION_PORT,
"systemd_unit_sha256": plan.deployment.systemd_unit_sha256,
"dropin_sha256": plan.deployment.dropin_sha256,
"rollback_target": rollback_target,
"rollback_artifact_path": str(
self.executor.rollback_artifact_path
),
"production_environment_file": str(
self.executor.production_environment_file
),
"writer_expected": False,
"global_legacy_writes_enabled": False,
"registered_executor_id": self.executor.executor_id,
"executor_executable_sha256": self.executor.executable_sha256,
"executor_adapter_sha256": self.executor.adapter_sha256,
}
def _request(self, operation: str) -> dict[str, Any]:
return {
"schema": REQUEST_SCHEMA,
"operation": operation,
**self._binding,
}
def _invoke(self, operation: str) -> dict[str, Any]:
if not self._global_writes_disabled():
raise C5DeploymentBindingError("C5_LEGACY_WRITES_MUST_REMAIN_DISABLED")
self.executor.verify_code()
value = _run_registered_adapter(
self.executor.command, self._request(operation)
)
for key, expected in self._binding.items():
if value.get(key) != expected:
raise C5DeploymentBindingError(f"C5_DEPLOYMENT_RECEIPT_MISMATCH:{key}")
if value.get("writer_disabled") is not True:
raise C5DeploymentBindingError("C5_DEPLOYMENT_WRITER_STATE_INVALID")
receipt = value.get("deployment_receipt")
if not isinstance(receipt, Mapping) or not receipt:
raise C5DeploymentBindingError("C5_DEPLOYMENT_RECEIPT_REQUIRED")
return value
@staticmethod
def _classify(value: Mapping[str, Any]) -> ClaimProbeResult:
status = value.get("status")
if (
status == "ACTIVE_VERIFIED"
and value.get("verified") is True
and value.get("deployment_state") == "ACTIVE"
and value.get("production_deployed") is True
and value.get("writer_disabled") is True
and value.get("global_legacy_writes_enabled") is False
):
return ClaimProbeResult.APPLIED_VERIFIED
if status == "NOT_APPLIED":
return ClaimProbeResult.NOT_APPLIED
if status == "CONFLICT":
return ClaimProbeResult.CONFLICT
return ClaimProbeResult.INDETERMINATE
def recovery_probe(self) -> ClaimProbeResult:
"""Read-only exact probe used by ``CapabilityLedger`` after UNKNOWN."""
try:
self._verify_binding(self.candidate, self.release_plan)
return self._classify(self._invoke("probe"))
except C5DeploymentConflict:
return ClaimProbeResult.CONFLICT
except Exception:
return ClaimProbeResult.INDETERMINATE
def rollback(
self,
*,
candidate: ReleaseCandidate,
plan: ReleasePlan,
) -> dict[str, Any]:
"""Execute only the explicit Saga rollback phase and verify exact C4."""
self._verify_binding(candidate, plan)
value = self._invoke("rollback")
if (
value.get("status") != "ROLLED_BACK_VERIFIED"
or value.get("verified") is not True
or value.get("rollback_verified") is not True
or value.get("rollback_version") != self.executor.rollback_target["version"]
or value.get("rollback_target_oid")
!= self.executor.rollback_target["target_oid"]
or value.get("rollback_artifact_sha256")
!= self.executor.rollback_target["artifact_sha256"]
):
raise C5DeploymentOutcomeUnknown("C5_ROLLBACK_OUTCOME_UNKNOWN")
return {
"verified": True,
"rollback_verified": True,
"rollback_version": self.executor.rollback_target["version"],
"rollback_target_oid": self.executor.rollback_target["target_oid"],
"rollback_artifact_sha256": self.executor.rollback_target["artifact_sha256"],
"writer_disabled": True,
}
def __call__(
self,
*,
candidate: ReleaseCandidate,
plan: ReleasePlan,
) -> dict[str, Any]:
self._verify_binding(candidate, plan)
probe = self._invoke("probe")
classification = self._classify(probe)
if classification == ClaimProbeResult.CONFLICT:
raise C5DeploymentConflict("C5_RUNTIME_BINDING_CONFLICT")
if classification == ClaimProbeResult.INDETERMINATE:
raise C5DeploymentOutcomeUnknown("C5_DEPLOYMENT_OUTCOME_UNKNOWN")
if classification == ClaimProbeResult.NOT_APPLIED:
applied = self._invoke("apply")
if applied.get("status") == "CONFLICT":
raise C5DeploymentConflict("C5_RUNTIME_BINDING_CONFLICT")
if applied.get("status") not in {"APPLIED", "ACTIVE_VERIFIED"}:
raise C5DeploymentOutcomeUnknown("C5_DEPLOYMENT_OUTCOME_UNKNOWN")
probe = self._invoke("probe")
classification = self._classify(probe)
if classification == ClaimProbeResult.CONFLICT:
raise C5DeploymentConflict("C5_RUNTIME_BINDING_CONFLICT")
if classification != ClaimProbeResult.APPLIED_VERIFIED:
raise C5DeploymentOutcomeUnknown("C5_DEPLOYMENT_OUTCOME_UNKNOWN")
return {
"verified": True,
"version": C5_VERSION,
"candidate_id": candidate.candidate_id,
"source_commit": candidate.source_commit,
"tree_oid": candidate.tree_oid,
"artifact_sha256": candidate.artifact_sha256,
"release_plan_sha256": plan.sha256,
"approval_fingerprint": self.approval.fingerprint,
"rollback_target": dict(self.executor.rollback_target),
"writer_disabled": True,
"global_legacy_writes_enabled": False,
"registered_executor_id": self.executor.executor_id,
"deployment_receipt_sha256": hashlib.sha256(
_canonical_json(dict(probe))
).hexdigest(),
}
def controller(
self,
ledger: CapabilityLedger,
*,
lease_seconds: float = 300,
) -> RuntimeSwitchController:
"""Bind this exact adapter to the existing capability controller."""
return RuntimeSwitchController(
ledger,
self,
recovery_probe=self.recovery_probe,
executor_id=self.executor.executor_id,
lease_seconds=lease_seconds,
)
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,688 @@
"""Owner-registered dispatch boundary for the fixed-scope C5 installer.
The registration authorizes one exact signed installation contract and one
exact set of installed executor bytes. It is not an execution receipt; the
wrapped :class:`Installer` remains the component that durably records and
reconciles installation state.
"""
from __future__ import annotations
from dataclasses import dataclass
import argparse
import hashlib
import json
import os
from pathlib import Path
import re
import socket
import stat
import sys
import time
from typing import Any, Callable, Mapping
import c5_staging_installation as _installer_module
from c5_staging_installation import (
BRIDGE_EFFECTS,
BRIDGE_TARGET,
HOST,
InstallationError,
InstallationOutcomeUnknown,
ISSUER_EFFECTS,
ISSUER_TARGET,
OWNER_NAMESPACE,
R14_SOURCE_COMMIT,
REPOSITORY,
SUCCESSOR_SCHEMA,
Installer,
RegistryOwnerVerifier,
canonical_bytes,
verify_signed_contract,
)
from registered_owner_signer_adapter import RegisteredOwnerSignerAdapter
SCHEMA = "aios.c5.registered-installer-executor.v1"
PROVENANCE_SCHEMA = "aios.c5.executor-provenance.v1"
EXECUTION_DIGEST = "0779fa615e8e7971a8dcfccf0f295b1323cd9b64787e174aea4fd2affc414339"
BRIDGE_PORT = 18892
ISSUER_PORT = 18946
CAPABILITIES = frozenset({"install", "rollback", "reconcile"})
TARGET_EFFECTS = {BRIDGE_TARGET: BRIDGE_EFFECTS, ISSUER_TARGET: ISSUER_EFFECTS}
MAX_TTL_SECONDS = 900
MAX_CODE_BYTES = 16 * 1024 * 1024
DEFAULT_REGISTRY = Path("/etc/aios/single-host-signer-registry.json")
DEFAULT_PROVENANCE_MANIFEST = Path("/etc/aios/c5-executor-provenance.json")
HEX40 = re.compile(r"^[0-9a-f]{40}$")
HEX64 = re.compile(r"^[0-9a-f]{64}$")
NONCE = re.compile(r"^[A-Za-z0-9._:\-]{16,128}$")
SCOPE_ID = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:\-]{2,127}$")
class ExecutorRegistrationError(RuntimeError):
def __init__(self, code: str) -> None:
super().__init__(code)
self.code = code
def _fail(code: str) -> None:
raise ExecutorRegistrationError(code)
def _sha256_bytes(data: bytes) -> str:
return hashlib.sha256(data).hexdigest()
def _require_digest(value: Any, pattern: re.Pattern[str], code: str) -> str:
if not isinstance(value, str) or pattern.fullmatch(value) is None:
_fail(code)
return value
def _strict_object(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
result: dict[str, Any] = {}
for key, value in pairs:
if key in result:
_fail("EXECUTOR_JSON_DUPLICATE_FIELD")
result[key] = value
return result
def _secure_bytes(
path: Path, *, require_root_owner: bool = True, maximum: int = MAX_CODE_BYTES,
) -> bytes:
path = Path(path)
if not path.is_absolute():
_fail("EXECUTOR_CODE_PATH_INVALID")
try:
if path.is_symlink() or path.resolve(strict=True) != path:
_fail("EXECUTOR_CODE_PATH_INVALID")
before = path.stat()
except OSError as exc:
raise ExecutorRegistrationError("EXECUTOR_CODE_PATH_INVALID") from exc
flags = os.O_RDONLY
if hasattr(os, "O_NOFOLLOW"):
flags |= os.O_NOFOLLOW
try:
fd = os.open(path, flags)
except OSError as exc:
raise ExecutorRegistrationError("EXECUTOR_CODE_PATH_INVALID") from exc
try:
current = os.fstat(fd)
if (
not stat.S_ISREG(current.st_mode)
or current.st_dev != before.st_dev
or current.st_ino != before.st_ino
or current.st_size < 1
or current.st_size > maximum
):
_fail("EXECUTOR_CODE_FILE_INVALID")
if require_root_owner:
if os.name != "posix" or current.st_uid != 0:
_fail("EXECUTOR_CODE_PATH_OWNER_INVALID")
if stat.S_IMODE(current.st_mode) & 0o022:
_fail("EXECUTOR_CODE_PATH_WRITABLE")
parent = path.parent
while True:
parent_stat = parent.stat()
if parent.is_symlink() or parent_stat.st_uid != 0:
_fail("EXECUTOR_CODE_PATH_OWNER_INVALID")
if stat.S_IMODE(parent_stat.st_mode) & 0o022:
_fail("EXECUTOR_CODE_PATH_WRITABLE")
if parent == parent.parent:
break
parent = parent.parent
chunks: list[bytes] = []
remaining = current.st_size
while remaining:
chunk = os.read(fd, min(remaining, 1024 * 1024))
if not chunk:
_fail("EXECUTOR_CODE_FILE_INVALID")
chunks.append(chunk)
remaining -= len(chunk)
if os.read(fd, 1):
_fail("EXECUTOR_CODE_FILE_INVALID")
return b"".join(chunks)
finally:
os.close(fd)
@dataclass(frozen=True, slots=True)
class CodeProvenance:
environment: str
instance_id: str
integration_commit: str
integration_tree: str
installer_module_path: Path
installer_script_path: Path
executor_module_path: Path
installer_module_sha256: str
installer_script_sha256: str
executor_module_sha256: str
authority_manifest_sha256: str
def _actual_installer_module_path() -> Path:
value = getattr(_installer_module, "__file__", None)
if not isinstance(value, str):
_fail("EXECUTOR_LOADED_INSTALLER_PATH_INVALID")
return Path(value).resolve(strict=True)
def _actual_executor_module_path() -> Path:
return Path(__file__).resolve(strict=True)
def load_code_provenance(
manifest: Path, *, require_root_owner: bool = True,
) -> CodeProvenance:
"""Load identity from an authority manifest and actual loaded paths."""
manifest = Path(manifest)
raw = _secure_bytes(manifest, require_root_owner=require_root_owner)
try:
value = json.loads(raw, object_pairs_hook=_strict_object)
except (UnicodeError, json.JSONDecodeError) as exc:
raise ExecutorRegistrationError("EXECUTOR_PROVENANCE_INVALID") from exc
if (
not isinstance(value, dict)
or set(value) != {
"schema", "environment", "instance_id", "integration_commit",
"integration_tree", "entries",
}
or value["schema"] != PROVENANCE_SCHEMA
or not isinstance(value["entries"], dict)
or set(value["entries"]) != {
"installer_module", "installer_script", "executor_module",
}
):
_fail("EXECUTOR_PROVENANCE_INVALID")
expected_paths = {
"installer_module": _actual_installer_module_path(),
"executor_module": _actual_executor_module_path(),
}
resolved: dict[str, Path] = {}
hashes: dict[str, str] = {}
for label, entry in value["entries"].items():
if not isinstance(entry, dict) or set(entry) != {"path", "sha256"}:
_fail("EXECUTOR_PROVENANCE_INVALID")
if not isinstance(entry["path"], str):
_fail("EXECUTOR_PROVENANCE_INVALID")
path = Path(entry["path"])
if not path.is_absolute():
_fail("EXECUTOR_CODE_PATH_INVALID")
try:
canonical = path.resolve(strict=True)
except OSError as exc:
raise ExecutorRegistrationError("EXECUTOR_CODE_PATH_INVALID") from exc
if path != canonical:
_fail("EXECUTOR_CODE_PATH_INVALID")
if label in expected_paths and canonical != expected_paths[label]:
_fail("EXECUTOR_PROVENANCE_LOADED_PATH_MISMATCH")
if label == "installer_script" and canonical.name != "c5_staging_installation.py":
_fail("EXECUTOR_INSTALLER_SCRIPT_PATH_INVALID")
digest = _sha256_bytes(
_secure_bytes(canonical, require_root_owner=require_root_owner)
)
if entry["sha256"] != digest:
_fail("EXECUTOR_PROVENANCE_CODE_MISMATCH")
resolved[label] = canonical
hashes[label] = digest
if (
not isinstance(value["environment"], str)
or SCOPE_ID.fullmatch(value["environment"]) is None
or not isinstance(value["instance_id"], str)
or SCOPE_ID.fullmatch(value["instance_id"]) is None
):
_fail("EXECUTOR_PROVENANCE_SCOPE_INVALID")
return CodeProvenance(
environment=value["environment"],
instance_id=value["instance_id"],
integration_commit=_require_digest(
value["integration_commit"], HEX40,
"EXECUTOR_INTEGRATION_COMMIT_INVALID",
),
integration_tree=_require_digest(
value["integration_tree"], HEX40,
"EXECUTOR_INTEGRATION_TREE_INVALID",
),
installer_module_path=resolved["installer_module"],
installer_script_path=resolved["installer_script"],
executor_module_path=resolved["executor_module"],
installer_module_sha256=hashes["installer_module"],
installer_script_sha256=hashes["installer_script"],
executor_module_sha256=hashes["executor_module"],
authority_manifest_sha256=_sha256_bytes(raw),
)
def _contract_binding(payload: Mapping[str, Any], digest: str) -> dict[str, Any]:
return {
"target": payload["target"],
"contract_digest": digest,
"old_snapshot_sha256": _sha256_bytes(canonical_bytes(payload["existing"])),
"new_manifest_sha256": _sha256_bytes(canonical_bytes({
"release_root": payload["release_root"],
"files": payload["files"],
"readiness": payload["readiness"],
})),
"rollback_sha256": _sha256_bytes(canonical_bytes(payload["rollback"])),
"contract_issued_at": payload["issued_at"],
"contract_expires_at": payload["expires_at"],
"contract_nonce": payload["nonce"],
}
def _issuer_dependency(payload: Mapping[str, Any], digest: str) -> dict[str, Any]:
return {
"target": payload["target"],
"contract_digest": digest,
"source_commit": payload["source_commit"],
"support_commit": payload["support_commit"],
"owner_signer_id": payload["owner_signer_id"],
"owner_key_id": payload["owner_key_id"],
"contract_nonce": payload["nonce"],
"contract_issued_at": payload["issued_at"],
"contract_expires_at": payload["expires_at"],
"rollback_sha256": _sha256_bytes(canonical_bytes(payload["rollback"])),
}
def _registration_payload(
*, executor_id: Any, nonce: Any, issued_at: Any, expires_at: Any,
contract_payload: Mapping[str, Any], contract_digest: str,
provenance: CodeProvenance,
issuer_dependency: Mapping[str, Any] | None,
) -> dict[str, Any]:
return {
"schema": SCHEMA,
"executor_id": executor_id,
"owner_signer_id": contract_payload["owner_signer_id"],
"owner_key_id": contract_payload["owner_key_id"],
"host": HOST,
"repository": REPOSITORY,
"environment": provenance.environment,
"instance_id": provenance.instance_id,
"source_commit": R14_SOURCE_COMMIT,
"support_commit": contract_payload["support_commit"],
"integration_commit": provenance.integration_commit,
"integration_tree": provenance.integration_tree,
"installer_module_sha256": provenance.installer_module_sha256,
"installer_script_sha256": provenance.installer_script_sha256,
"executor_module_sha256": provenance.executor_module_sha256,
"authority_manifest_sha256": provenance.authority_manifest_sha256,
"execution_digest": EXECUTION_DIGEST,
"bridge_port": BRIDGE_PORT,
"issuer_port": ISSUER_PORT,
"capabilities": sorted(CAPABILITIES),
"effects": sorted(contract_payload["effects"]),
"issuer_dependency": dict(issuer_dependency) if issuer_dependency is not None else None,
"issued_at": issued_at,
"expires_at": expires_at,
"nonce": nonce,
**_contract_binding(contract_payload, contract_digest),
}
def _validate_issuer_dependency(
dependency: Any, contract_payload: Mapping[str, Any],
) -> None:
if contract_payload["target"] == ISSUER_TARGET:
if dependency is not None:
_fail("EXECUTOR_ISSUER_DEPENDENCY_INVALID")
return
if (
not isinstance(dependency, Mapping)
or set(dependency) != {
"target", "contract_digest", "source_commit", "support_commit",
"owner_signer_id", "owner_key_id", "contract_nonce",
"contract_issued_at", "contract_expires_at", "rollback_sha256",
}
or dependency["target"] != ISSUER_TARGET
or dependency["source_commit"] != contract_payload["source_commit"]
or dependency["support_commit"] != contract_payload["support_commit"]
or dependency["owner_signer_id"] != contract_payload["owner_signer_id"]
or dependency["owner_key_id"] != contract_payload["owner_key_id"]
or type(dependency["contract_issued_at"]) is not int
or type(dependency["contract_expires_at"]) is not int
):
_fail("EXECUTOR_ISSUER_DEPENDENCY_INVALID")
_require_digest(
dependency["contract_digest"], HEX64,
"EXECUTOR_ISSUER_DEPENDENCY_INVALID",
)
_require_digest(
dependency["rollback_sha256"], HEX64,
"EXECUTOR_ISSUER_DEPENDENCY_INVALID",
)
def _validate_payload(
payload: Mapping[str, Any], *, now: int, provenance: CodeProvenance,
contract_payload: Mapping[str, Any], contract_digest: str,
issuer_dependency: Mapping[str, Any] | None,
) -> None:
expected = _registration_payload(
executor_id=payload.get("executor_id"), nonce=payload.get("nonce"),
issued_at=payload.get("issued_at"), expires_at=payload.get("expires_at"),
contract_payload=contract_payload, contract_digest=contract_digest,
provenance=provenance, issuer_dependency=issuer_dependency,
)
if set(payload) != set(expected) or any(
payload.get(key) != value for key, value in expected.items()
):
_fail("EXECUTOR_REGISTRATION_BINDING_INVALID")
if (
not isinstance(payload["executor_id"], str)
or not payload["executor_id"]
or not isinstance(payload["nonce"], str)
or NONCE.fullmatch(payload["nonce"]) is None
):
_fail("EXECUTOR_REGISTRATION_IDENTITY_INVALID")
if (
type(payload["issued_at"]) is not int
or type(payload["expires_at"]) is not int
or payload["issued_at"] > now
or now >= payload["expires_at"]
or not 1 <= payload["expires_at"] - payload["issued_at"] <= MAX_TTL_SECONDS
or payload["issued_at"] < contract_payload["issued_at"]
or payload["expires_at"] > contract_payload["expires_at"]
):
_fail("EXECUTOR_REGISTRATION_EXPIRED")
if payload["support_commit"] != payload["integration_commit"]:
_fail("EXECUTOR_COMMIT_BINDING_INVALID")
if (
issuer_dependency is not None
and payload["expires_at"] > issuer_dependency["contract_expires_at"]
):
_fail("EXECUTOR_REGISTRATION_EXPIRED")
if (
not isinstance(payload["environment"], str)
or SCOPE_ID.fullmatch(payload["environment"]) is None
or not isinstance(payload["instance_id"], str)
or SCOPE_ID.fullmatch(payload["instance_id"]) is None
):
_fail("EXECUTOR_ENVIRONMENT_BINDING_INVALID")
if payload["source_commit"] != R14_SOURCE_COMMIT:
_fail("EXECUTOR_SOURCE_COMMIT_INVALID")
if (
contract_payload["schema"] != SUCCESSOR_SCHEMA
or contract_payload["source_commit"] != R14_SOURCE_COMMIT
):
_fail("EXECUTOR_CONTRACT_GENERATION_INVALID")
effects = TARGET_EFFECTS.get(payload["target"])
if (
effects is None
or not isinstance(payload["effects"], list)
or frozenset(payload["effects"]) != effects
or len(payload["effects"]) != len(effects)
):
_fail("EXECUTOR_EFFECTS_INVALID")
if (
not isinstance(payload["capabilities"], list)
or frozenset(payload["capabilities"]) != CAPABILITIES
or len(payload["capabilities"]) != len(CAPABILITIES)
):
_fail("EXECUTOR_CAPABILITIES_INVALID")
_validate_issuer_dependency(issuer_dependency, contract_payload)
for name in (
"installer_module_sha256", "installer_script_sha256",
"executor_module_sha256", "authority_manifest_sha256",
"execution_digest", "contract_digest", "old_snapshot_sha256",
"new_manifest_sha256", "rollback_sha256",
):
_require_digest(payload[name], HEX64, f"EXECUTOR_{name.upper()}_INVALID")
def _verified_contract(
contract: Path, installer: Installer, *, now: int,
) -> tuple[dict[str, Any], str]:
return verify_signed_contract(
contract, installer.registry_path, now=now,
require_root_owner=installer.require_root_owner,
_layout=installer.layout,
)
def _verified_issuer_dependency(
issuer_contract: Path | None, installer: Installer, *, now: int,
contract_payload: Mapping[str, Any],
) -> dict[str, Any] | None:
if contract_payload["target"] == ISSUER_TARGET:
if issuer_contract is not None:
_fail("EXECUTOR_ISSUER_DEPENDENCY_INVALID")
return None
if issuer_contract is None:
_fail("EXECUTOR_ISSUER_DEPENDENCY_REQUIRED")
payload, digest = _verified_contract(issuer_contract, installer, now=now)
if payload["schema"] != SUCCESSOR_SCHEMA or payload["source_commit"] != R14_SOURCE_COMMIT:
_fail("EXECUTOR_ISSUER_DEPENDENCY_INVALID")
dependency = _issuer_dependency(payload, digest)
_validate_issuer_dependency(dependency, contract_payload)
return dependency
def issue_registration(
*, executor_id: str, nonce: str, ttl_seconds: int, contract: Path,
installer: Installer, provenance_manifest: Path,
registered_signer: RegisteredOwnerSignerAdapter,
clock: Callable[[], int], issuer_contract: Path | None = None,
require_root_owner: bool = True,
) -> dict[str, Any]:
"""Issue authorization after deriving every scope field from trusted input."""
if type(installer) is not Installer:
_fail("EXECUTOR_INSTALLER_TYPE_INVALID")
if not isinstance(registered_signer, RegisteredOwnerSignerAdapter):
_fail("REGISTERED_SIGNER_ADAPTER_REQUIRED")
if type(ttl_seconds) is not int or not 1 <= ttl_seconds <= MAX_TTL_SECONDS:
_fail("EXECUTOR_REGISTRATION_TTL_INVALID")
now = int(clock())
provenance = load_code_provenance(
provenance_manifest, require_root_owner=require_root_owner,
)
contract_payload, contract_digest = _verified_contract(contract, installer, now=now)
dependency = _verified_issuer_dependency(
issuer_contract, installer, now=now, contract_payload=contract_payload,
)
payload = _registration_payload(
executor_id=executor_id, nonce=nonce, issued_at=now,
expires_at=now + ttl_seconds, contract_payload=contract_payload,
contract_digest=contract_digest, provenance=provenance,
issuer_dependency=dependency,
)
_validate_payload(
payload, now=now, provenance=provenance,
contract_payload=contract_payload, contract_digest=contract_digest,
issuer_dependency=dependency,
)
if registered_signer.fingerprint != payload["owner_key_id"]:
_fail("EXECUTOR_SIGNER_BINDING_INVALID")
signature = registered_signer._sign_canonical_payload(
OWNER_NAMESPACE, canonical_bytes(payload),
)
return {"payload": payload, "signature": signature}
def load_registration(path: Path, *, require_root_owner: bool = True) -> dict[str, Any]:
raw = _secure_bytes(
Path(path), require_root_owner=require_root_owner, maximum=1024 * 1024,
)
try:
value = json.loads(raw, object_pairs_hook=_strict_object)
except (UnicodeError, json.JSONDecodeError) as exc:
raise ExecutorRegistrationError("EXECUTOR_REGISTRATION_INVALID") from exc
if not isinstance(value, dict) or set(value) != {"payload", "signature"}:
_fail("EXECUTOR_REGISTRATION_FIELDS_INVALID")
return value
class RegisteredInstallerExecutor:
def __init__(
self, *, owner_verifier: RegistryOwnerVerifier, installer: Installer,
provenance_manifest: Path, clock: Callable[[], int],
host: Callable[[], str] = socket.gethostname,
require_root_owner: bool = True,
) -> None:
if type(installer) is not Installer:
_fail("EXECUTOR_INSTALLER_TYPE_INVALID")
if not isinstance(owner_verifier, RegistryOwnerVerifier):
_fail("EXECUTOR_OWNER_VERIFIER_INVALID")
self.owner_verifier = owner_verifier
self.installer = installer
self.provenance_manifest = Path(provenance_manifest)
self.clock = clock
self.host = host
self.require_root_owner = require_root_owner
self.provenance = load_code_provenance(
self.provenance_manifest, require_root_owner=require_root_owner,
)
def _check_code_provenance(self) -> None:
current = load_code_provenance(
self.provenance_manifest, require_root_owner=self.require_root_owner,
)
if current != self.provenance:
_fail("EXECUTOR_CODE_PROVENANCE_CHANGED")
def _verify(
self, registration: Mapping[str, Any], contract: Path,
) -> tuple[dict[str, Any], str, dict[str, Any]]:
self._check_code_provenance()
if (
not isinstance(registration, Mapping)
or set(registration) != {"payload", "signature"}
or not isinstance(registration["payload"], Mapping)
):
_fail("EXECUTOR_REGISTRATION_FIELDS_INVALID")
if self.host() != HOST:
_fail("EXECUTOR_HOST_MISMATCH")
now = int(self.clock())
contract_payload, contract_digest = _verified_contract(
contract, self.installer, now=now,
)
payload = dict(registration["payload"])
dependency = payload.get("issuer_dependency")
_validate_payload(
payload, now=now, provenance=self.provenance,
contract_payload=contract_payload, contract_digest=contract_digest,
issuer_dependency=dependency,
)
if payload["owner_key_id"] != self.owner_verifier.record.fingerprint:
_fail("EXECUTOR_OWNER_KEY_BINDING_INVALID")
signature = registration["signature"]
if (
not isinstance(signature, str)
or not self.owner_verifier.verify(
namespace=OWNER_NAMESPACE, payload=canonical_bytes(payload),
signature=signature, key_id=payload["owner_key_id"],
)
):
_fail("EXECUTOR_OWNER_SIGNATURE_INVALID")
return payload, contract_digest, contract_payload
def dispatch(
self, registration: Mapping[str, Any], contract: Path,
artifact: Path, operation: str, *,
issuer_contract: Path | None = None,
issuer_artifact: Path | None = None,
) -> dict[str, Any]:
payload, contract_digest, contract_payload = self._verify(registration, contract)
capability = "install" if operation in {"preflight", "install"} else operation
if capability not in CAPABILITIES:
_fail("EXECUTOR_OPERATION_INVALID")
if capability not in payload["capabilities"]:
_fail("EXECUTOR_CAPABILITY_INVALID")
if operation == "rollback" and contract_payload["target"] == BRIDGE_TARGET:
if issuer_artifact is None:
_fail("EXECUTOR_ISSUER_DEPENDENCY_REQUIRED")
actual_dependency = _verified_issuer_dependency(
issuer_contract, self.installer, now=int(self.clock()),
contract_payload=contract_payload,
)
if actual_dependency != payload["issuer_dependency"]:
_fail("EXECUTOR_ISSUER_DEPENDENCY_MISMATCH")
elif issuer_contract is not None or issuer_artifact is not None:
_fail("EXECUTOR_ISSUER_DEPENDENCY_UNEXPECTED")
if operation == "preflight":
result = self.installer.preflight(contract, artifact)
elif operation == "install":
result = self.installer.apply(contract, artifact)
elif operation == "rollback":
result = self.installer.rollback_applied(
contract, artifact, issuer_contract=issuer_contract,
issuer_artifact=issuer_artifact,
)
else:
result = self.installer.reconcile_unknown(contract, artifact)
return {
**result,
"operation": operation,
"executor_id": payload["executor_id"],
"contract_digest": contract_digest,
"code_binding": {
"integration_commit": payload["integration_commit"],
"integration_tree": payload["integration_tree"],
"installer_module_sha256": payload["installer_module_sha256"],
"installer_script_sha256": payload["installer_script_sha256"],
"executor_module_sha256": payload["executor_module_sha256"],
},
"registration_policy_digest": _sha256_bytes(canonical_bytes(payload)),
}
def _parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
description="Run one Owner-registered fixed-scope C5 installer operation.",
)
parser.add_argument(
"operation", choices=("preflight", "install", "rollback", "reconcile"),
)
parser.add_argument("--registration", type=Path, required=True)
parser.add_argument("--contract", type=Path, required=True)
parser.add_argument("--artifact", type=Path, required=True)
parser.add_argument("--issuer-contract", type=Path)
parser.add_argument("--issuer-artifact", type=Path)
return parser
def main(argv: list[str] | None = None) -> int:
"""Production CLI with fixed registry and provenance authority paths."""
args = _parser().parse_args(argv)
try:
registration = load_registration(args.registration)
payload = registration.get("payload")
if not isinstance(payload, Mapping) or not isinstance(payload.get("owner_signer_id"), str):
_fail("EXECUTOR_REGISTRATION_FIELDS_INVALID")
installer = Installer(DEFAULT_REGISTRY)
verifier = RegistryOwnerVerifier(
DEFAULT_REGISTRY, owner_signer_id=payload["owner_signer_id"],
)
executor = RegisteredInstallerExecutor(
owner_verifier=verifier, installer=installer,
provenance_manifest=DEFAULT_PROVENANCE_MANIFEST,
clock=lambda: int(time.time()),
)
result = executor.dispatch(
registration, args.contract, args.artifact, args.operation,
issuer_contract=args.issuer_contract,
issuer_artifact=args.issuer_artifact,
)
print(json.dumps(result, sort_keys=True))
return 0
except InstallationOutcomeUnknown as exc:
print(
json.dumps({"status": "UNKNOWN", "error": exc.code}, sort_keys=True),
file=sys.stderr,
)
return 3
except (ExecutorRegistrationError, InstallationError, OSError, ValueError, TypeError) as exc:
print(
json.dumps(
{"status": "REJECTED", "error": getattr(exc, "code", str(exc))},
sort_keys=True,
),
file=sys.stderr,
)
return 2
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,977 @@
#!/usr/bin/env python3
"""Root-owned one-shot assembly for the exact V2R2C5 release Saga.
This module only assembles existing hardened Saga phases. Each mutating phase
is a SHA-pinned process adapter; no caller-provided Python callable is accepted.
The staging HTTP service remains shadow-only and does not import this launcher.
"""
from __future__ import annotations
import argparse
from dataclasses import dataclass
import hashlib
import json
import os
from pathlib import Path
import stat
import subprocess
import time
from typing import Any, Mapping, Sequence
import approved_release_executor as approved_executor_module
from approved_release_executor import run_adapter
import c5_exact_deployment_adapter as exact_deployment_module
from c5_exact_deployment_adapter import (
C4_ROLLBACK_ARTIFACT_PATH,
C4_ROLLBACK_TARGET,
C5ExactDeploymentAdapter,
C5_PRODUCTION_ENVIRONMENT_FILE,
C5_PRODUCTION_PORT,
C5_PRODUCTION_SERVICE,
C5_VERSION,
RegisteredC5DeploymentExecutor,
)
from release_source_admission import open_source_admission_store
from silent_release_controller import ReleaseCandidate, ReleaseCandidateStore
from silent_release_hardening import (
ApprovalKeyRing,
ApprovalRevocationStore,
BaselineController,
CapabilityAuthorizationError,
CapabilityLedger,
ClaimProbeResult,
GovernedFinalizationCoordinator,
HardenedTagController,
PreSwitchValidator,
RegistryController,
ReleaseOrchestrator,
RecoverableReleaseError,
ReleasePlan,
ReleasePlanStore,
ReleaseSagaStore,
SignedApprovalStore,
SignedApprovalVerifier,
TagPlan,
TagPublicationConflict,
TagPublicationProbeEvidence,
TrustedReleasePolicy,
)
SCHEMA = "aios.c5.release-execution-host.v1"
PROVENANCE_SCHEMA = "aios.c5.release-execution-provenance.v1"
REQUEST_SCHEMA = "aios.production-executor-request.v1"
REPOSITORY = "aios/aios"
SCOPES = frozenset({"PRODUCTION", "ISOLATED_REHEARSAL"})
SHA256 = __import__("re").compile(r"^[0-9a-f]{64}$")
OID = __import__("re").compile(r"^[0-9a-f]{40}$")
class C5ExecutionHostBlocked(RuntimeError):
"""Exact host assembly or phase verification failed closed."""
def _sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as source:
for chunk in iter(lambda: source.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def _secure_file(
path: Path,
expected_sha256: str,
*,
expected_owner_uid: int | None,
) -> Path:
path = Path(path)
if (
not path.is_absolute()
or path.is_symlink()
or path.resolve(strict=False) != path
):
raise C5ExecutionHostBlocked("HOST_FILE_PATH_INVALID")
try:
info = path.stat()
except OSError as exc:
raise C5ExecutionHostBlocked("HOST_FILE_UNAVAILABLE") from exc
if not stat.S_ISREG(info.st_mode) or (
os.name != "nt" and stat.S_IMODE(info.st_mode) & 0o022
):
raise C5ExecutionHostBlocked("HOST_FILE_SECURITY_INVALID")
if expected_owner_uid is not None and info.st_uid != expected_owner_uid:
raise C5ExecutionHostBlocked("HOST_FILE_OWNER_INVALID")
if expected_owner_uid is not None:
parent = path.parent
while True:
parent_info = parent.stat()
if (
parent.is_symlink()
or parent_info.st_uid != expected_owner_uid
or stat.S_IMODE(parent_info.st_mode) & 0o022
):
raise C5ExecutionHostBlocked("HOST_FILE_PARENT_SECURITY_INVALID")
if parent == parent.parent:
break
parent = parent.parent
if not SHA256.fullmatch(expected_sha256) or _sha256(path) != expected_sha256:
raise C5ExecutionHostBlocked("HOST_FILE_DIGEST_MISMATCH")
return path
def _absolute(value: Any, name: str) -> Path:
path = Path(str(value or ""))
if not path.is_absolute():
raise C5ExecutionHostBlocked(f"{name}_PATH_INVALID")
return path
@dataclass(frozen=True, slots=True)
class RegisteredPhaseCommand:
executable: Path
executable_sha256: str
adapter_path: Path
adapter_sha256: str
arguments: tuple[str, ...] = ()
@classmethod
def from_mapping(cls, raw: Any) -> "RegisteredPhaseCommand":
if not isinstance(raw, Mapping) or set(raw) != {
"executable",
"executable_sha256",
"adapter_path",
"adapter_sha256",
"arguments",
}:
raise C5ExecutionHostBlocked("PHASE_COMMAND_SCHEMA_INVALID")
arguments = raw["arguments"]
if not isinstance(arguments, list) or not all(
isinstance(item, str) and item and "\0" not in item
for item in arguments
):
raise C5ExecutionHostBlocked("PHASE_COMMAND_ARGUMENTS_INVALID")
return cls(
executable=_absolute(raw["executable"], "PHASE_EXECUTABLE"),
executable_sha256=str(raw["executable_sha256"]),
adapter_path=_absolute(raw["adapter_path"], "PHASE_ADAPTER"),
adapter_sha256=str(raw["adapter_sha256"]),
arguments=tuple(arguments),
)
def verify(self, *, expected_owner_uid: int | None) -> None:
_secure_file(
self.executable,
self.executable_sha256,
expected_owner_uid=expected_owner_uid,
)
_secure_file(
self.adapter_path,
self.adapter_sha256,
expected_owner_uid=expected_owner_uid,
)
@property
def command(self) -> tuple[str, ...]:
return (str(self.executable), str(self.adapter_path), *self.arguments)
@dataclass(frozen=True, slots=True)
class C5HostProvenance:
environment: str
instance_id: str
authority_commit: str
authority_tree: str
entries: Mapping[str, tuple[Path, str]]
manifest_sha256: str
@classmethod
def load(
cls,
path: Path,
*,
expected_sha256: str,
expected_owner_uid: int | None = 0,
) -> "C5HostProvenance":
path = _secure_file(
Path(path), expected_sha256,
expected_owner_uid=expected_owner_uid,
)
try:
raw = json.loads(path.read_text(encoding="utf-8"))
except (OSError, ValueError) as exc:
raise C5ExecutionHostBlocked("HOST_PROVENANCE_INVALID") from exc
labels = {
"host", "exact_deployment", "deployment", "approved_executor",
"tag", "registry", "baseline",
}
if (
not isinstance(raw, Mapping)
or set(raw) != {
"schema", "environment", "instance_id",
"authority_commit", "authority_tree", "entries",
}
or raw["schema"] != PROVENANCE_SCHEMA
or raw["environment"] not in SCOPES
or not isinstance(raw["entries"], Mapping)
or set(raw["entries"]) != labels
):
raise C5ExecutionHostBlocked("HOST_PROVENANCE_INVALID")
instance_id = str(raw["instance_id"] or "")
if not instance_id or len(instance_id) > 128:
raise C5ExecutionHostBlocked("HOST_PROVENANCE_INSTANCE_INVALID")
authority_commit = str(raw["authority_commit"])
authority_tree = str(raw["authority_tree"])
if not OID.fullmatch(authority_commit) or not OID.fullmatch(authority_tree):
raise C5ExecutionHostBlocked("HOST_PROVENANCE_AUTHORITY_INVALID")
actual_loaded = {
"host": Path(__file__).resolve(strict=True),
"exact_deployment": Path(exact_deployment_module.__file__).resolve(strict=True),
"approved_executor": Path(approved_executor_module.__file__).resolve(strict=True),
}
entries: dict[str, tuple[Path, str]] = {}
for label, item in raw["entries"].items():
if not isinstance(item, Mapping) or set(item) != {"path", "sha256"}:
raise C5ExecutionHostBlocked("HOST_PROVENANCE_ENTRY_INVALID")
entry_path = _absolute(item["path"], "HOST_PROVENANCE_ENTRY")
digest = str(item["sha256"])
_secure_file(
entry_path, digest, expected_owner_uid=expected_owner_uid
)
if label in actual_loaded and entry_path != actual_loaded[label]:
raise C5ExecutionHostBlocked("HOST_PROVENANCE_LOADED_PATH_MISMATCH")
entries[label] = (entry_path, digest)
return cls(
environment=str(raw["environment"]),
instance_id=instance_id,
authority_commit=authority_commit,
authority_tree=authority_tree,
entries=entries,
manifest_sha256=expected_sha256,
)
@dataclass(frozen=True, slots=True)
class C5ExecutionHostConfig:
host_provenance: C5HostProvenance
executor_authority_commit: str
executor_authority_tree: str
execution_scope: str
instance_id: str
provenance: Mapping[str, Any]
candidate_id: str
source_commit: str
tree_oid: str
artifact_sha256: str
release_plan_sha256: str
approval_fingerprint: str
state_root: Path
trusted_policy_path: Path
trusted_policy_sha256: str
approval_keyring_path: Path
approval_keyring_sha256: str
source_admission_root: Path
source_admission_policy_path: Path
source_admission_policy_sha256: str
source_admission_task_id: str
source_admission_hmac_path: Path
source_admission_hmac_sha256: str
deployment_executable: Path
deployment_executable_sha256: str
deployment_adapter_path: Path
deployment_adapter_sha256: str
artifact_path: Path
rollback_target: Mapping[str, str]
rollback_artifact_path: Path
production_environment_file: Path
authoritative_git_dir: Path
git_executable: Path
git_executable_sha256: str
tag_command: RegisteredPhaseCommand
registry_command: RegisteredPhaseCommand
baseline_command: RegisteredPhaseCommand
registry_record_path: Path
baseline_record_path: Path
@classmethod
def load(
cls,
path: Path,
*,
expected_sha256: str,
host_provenance: C5HostProvenance,
expected_owner_uid: int | None = 0,
) -> "C5ExecutionHostConfig":
path = _secure_file(
Path(path),
expected_sha256,
expected_owner_uid=expected_owner_uid,
)
try:
raw = json.loads(path.read_text(encoding="utf-8"))
except (OSError, ValueError) as exc:
raise C5ExecutionHostBlocked("HOST_CONFIG_INVALID") from exc
keys = {
"schema", "version", "execution_scope", "instance_id",
"executor_authority_commit", "executor_authority_tree",
"provenance", "candidate_id",
"source_commit", "tree_oid", "artifact_sha256",
"release_plan_sha256", "approval_fingerprint", "state_root",
"trusted_policy_path", "trusted_policy_sha256",
"approval_keyring_path", "approval_keyring_sha256",
"source_admission_root", "source_admission_policy_path",
"source_admission_policy_sha256", "source_admission_task_id", "source_admission_hmac_path",
"source_admission_hmac_sha256", "deployment_executable",
"deployment_executable_sha256", "deployment_adapter_path",
"deployment_adapter_sha256", "artifact_path",
"rollback_target", "rollback_artifact_path",
"production_environment_file", "production_service",
"production_port", "repository_identity", "global_legacy_writes_enabled",
"authoritative_git_dir", "git_executable", "git_executable_sha256",
"tag_command", "registry_command", "baseline_command",
"registry_record_path", "baseline_record_path",
}
if not isinstance(raw, Mapping) or set(raw) != keys:
raise C5ExecutionHostBlocked("HOST_CONFIG_SCHEMA_INVALID")
rollback_artifact_path = _absolute(raw["rollback_artifact_path"], "ROLLBACK_ARTIFACT")
production_environment_file = _absolute(raw["production_environment_file"], "PRODUCTION_ENVIRONMENT_FILE")
exact = (
raw["schema"] == SCHEMA
and raw["version"] == C5_VERSION
and raw["execution_scope"] in SCOPES
and isinstance(raw["rollback_target"], Mapping)
and raw["production_service"] == C5_PRODUCTION_SERVICE
and raw["production_port"] == C5_PRODUCTION_PORT
and raw["repository_identity"] == REPOSITORY
and raw["global_legacy_writes_enabled"] is False
)
if not exact:
raise C5ExecutionHostBlocked("HOST_CONFIG_BINDING_INVALID")
provenance = raw["provenance"]
expected_provenance = (
{
"environment": "ISOLATED_REHEARSAL",
"fixture": True,
"r14_source_receipt": False,
}
if raw["execution_scope"] == "ISOLATED_REHEARSAL"
else {
"environment": "PRODUCTION",
"fixture": False,
"r14_source_receipt": True,
}
)
if provenance != expected_provenance:
raise C5ExecutionHostBlocked("HOST_PROVENANCE_INVALID")
instance_id = str(raw["instance_id"] or "")
if not instance_id or len(instance_id) > 128 or any(
character not in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.-"
for character in instance_id
):
raise C5ExecutionHostBlocked("HOST_INSTANCE_ID_INVALID")
for name, pattern in (
("executor_authority_commit", OID),
("executor_authority_tree", OID),
("source_commit", OID), ("tree_oid", OID),
("artifact_sha256", SHA256),
("release_plan_sha256", SHA256),
("approval_fingerprint", SHA256),
):
if pattern.fullmatch(str(raw[name])) is None:
raise C5ExecutionHostBlocked(f"{name.upper()}_INVALID")
value = cls(
host_provenance=host_provenance,
executor_authority_commit=str(raw["executor_authority_commit"]),
executor_authority_tree=str(raw["executor_authority_tree"]),
execution_scope=str(raw["execution_scope"]),
instance_id=instance_id,
provenance=dict(provenance),
candidate_id=str(raw["candidate_id"]),
source_commit=str(raw["source_commit"]),
tree_oid=str(raw["tree_oid"]),
artifact_sha256=str(raw["artifact_sha256"]),
release_plan_sha256=str(raw["release_plan_sha256"]),
approval_fingerprint=str(raw["approval_fingerprint"]),
state_root=_absolute(raw["state_root"], "STATE_ROOT"),
trusted_policy_path=_absolute(raw["trusted_policy_path"], "TRUSTED_POLICY"),
trusted_policy_sha256=str(raw["trusted_policy_sha256"]),
approval_keyring_path=_absolute(raw["approval_keyring_path"], "APPROVAL_KEYRING"),
approval_keyring_sha256=str(raw["approval_keyring_sha256"]),
source_admission_root=_absolute(raw["source_admission_root"], "SOURCE_ADMISSION_ROOT"),
source_admission_policy_path=_absolute(raw["source_admission_policy_path"], "SOURCE_ADMISSION_POLICY"),
source_admission_policy_sha256=str(raw["source_admission_policy_sha256"]),
source_admission_task_id=str(raw["source_admission_task_id"]),
source_admission_hmac_path=_absolute(raw["source_admission_hmac_path"], "SOURCE_ADMISSION_HMAC"),
source_admission_hmac_sha256=str(raw["source_admission_hmac_sha256"]),
deployment_executable=_absolute(raw["deployment_executable"], "DEPLOYMENT_EXECUTABLE"),
deployment_executable_sha256=str(raw["deployment_executable_sha256"]),
deployment_adapter_path=_absolute(raw["deployment_adapter_path"], "DEPLOYMENT_ADAPTER"),
deployment_adapter_sha256=str(raw["deployment_adapter_sha256"]),
artifact_path=_absolute(raw["artifact_path"], "ARTIFACT"),
rollback_target=dict(raw["rollback_target"]),
rollback_artifact_path=rollback_artifact_path,
production_environment_file=production_environment_file,
authoritative_git_dir=_absolute(raw["authoritative_git_dir"], "AUTHORITATIVE_GIT_DIR"),
git_executable=_absolute(raw["git_executable"], "GIT_EXECUTABLE"),
git_executable_sha256=str(raw["git_executable_sha256"]),
tag_command=RegisteredPhaseCommand.from_mapping(raw["tag_command"]),
registry_command=RegisteredPhaseCommand.from_mapping(raw["registry_command"]),
baseline_command=RegisteredPhaseCommand.from_mapping(raw["baseline_command"]),
registry_record_path=_absolute(raw["registry_record_path"], "REGISTRY_RECORD"),
baseline_record_path=_absolute(raw["baseline_record_path"], "BASELINE_RECORD"),
)
if (not value.source_admission_task_id
or len(value.source_admission_task_id) > 160
or any(character not in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.:-"
for character in value.source_admission_task_id)):
raise C5ExecutionHostBlocked("SOURCE_ADMISSION_TASK_ID_INVALID")
if value.execution_scope == "ISOLATED_REHEARSAL":
rehearsal_root = value.state_root.resolve(strict=False)
lowered_parts = {part.lower() for part in rehearsal_root.parts}
if (
"isolated-rehearsal" not in lowered_parts
or value.instance_id not in rehearsal_root.parts
):
raise C5ExecutionHostBlocked("REHEARSAL_ROOT_NOT_ISOLATED")
for fixture_path in (
value.source_admission_root,
value.artifact_path,
value.rollback_artifact_path,
value.production_environment_file,
value.registry_record_path,
value.baseline_record_path,
):
try:
fixture_path.resolve(strict=False).relative_to(rehearsal_root)
except ValueError as exc:
raise C5ExecutionHostBlocked(
"REHEARSAL_FIXTURE_OUTSIDE_ISOLATION_ROOT"
) from exc
if value.execution_scope == "ISOLATED_REHEARSAL":
if set(value.rollback_target) != set(C4_ROLLBACK_TARGET) or value.rollback_target.get("version") != C4_ROLLBACK_TARGET["version"] or value.rollback_target.get("target_oid") != C4_ROLLBACK_TARGET["target_oid"]:
raise C5ExecutionHostBlocked("REHEARSAL_ROLLBACK_TARGET_INVALID")
if SHA256.fullmatch(str(value.rollback_target.get("artifact_sha256", ""))) is None:
raise C5ExecutionHostBlocked("REHEARSAL_ROLLBACK_TARGET_INVALID")
if (
host_provenance.environment != value.execution_scope
or host_provenance.instance_id != value.instance_id
or host_provenance.authority_commit
!= value.executor_authority_commit
or host_provenance.authority_tree != value.executor_authority_tree
):
raise C5ExecutionHostBlocked("HOST_PROVENANCE_BINDING_MISMATCH")
if value.execution_scope == "PRODUCTION" and (
value.rollback_target != C4_ROLLBACK_TARGET
or value.rollback_artifact_path != C4_ROLLBACK_ARTIFACT_PATH
or value.production_environment_file != C5_PRODUCTION_ENVIRONMENT_FILE
):
raise C5ExecutionHostBlocked("HOST_CONFIG_BINDING_INVALID")
expected_adapter_paths = {
"deployment": value.deployment_adapter_path,
"tag": value.tag_command.adapter_path,
"registry": value.registry_command.adapter_path,
"baseline": value.baseline_command.adapter_path,
}
if any(
host_provenance.entries[label][0] != path
for label, path in expected_adapter_paths.items()
):
raise C5ExecutionHostBlocked("HOST_PROVENANCE_ADAPTER_MISMATCH")
production_names = {
"deployment": "c5_production_deployment_adapter.py",
"tag": "governed_tag_adapter.py",
"registry": "release_metadata_adapter.py",
"baseline": "release_metadata_adapter.py",
}
observed_names = {
label: path.name for label, path in expected_adapter_paths.items()
}
if value.execution_scope == "PRODUCTION":
if observed_names != production_names:
raise C5ExecutionHostBlocked("PRODUCTION_ADAPTER_IDENTITY_INVALID")
elif any(
observed_names[label] == production_names[label]
for label in production_names
):
raise C5ExecutionHostBlocked("REHEARSAL_PRODUCTION_ADAPTER_FORBIDDEN")
value.verify_files(expected_owner_uid=expected_owner_uid)
return value
def verify_files(self, *, expected_owner_uid: int | None) -> None:
for path, digest in (
(self.trusted_policy_path, self.trusted_policy_sha256),
(self.approval_keyring_path, self.approval_keyring_sha256),
(self.source_admission_policy_path, self.source_admission_policy_sha256),
(self.source_admission_hmac_path, self.source_admission_hmac_sha256),
(self.git_executable, self.git_executable_sha256),
):
_secure_file(path, digest, expected_owner_uid=expected_owner_uid)
for command in (
self.tag_command, self.registry_command, self.baseline_command
):
command.verify(expected_owner_uid=expected_owner_uid)
def _load_policy(path: Path) -> TrustedReleasePolicy:
raw = json.loads(path.read_text(encoding="utf-8"))
required = {
"services", "release_roots", "cutover_links", "dropin_paths",
"authoritative_git_dirs", "repository_identities",
}
if not isinstance(raw, Mapping) or set(raw) != required:
raise C5ExecutionHostBlocked("TRUSTED_POLICY_SCHEMA_INVALID")
values: dict[str, frozenset[str]] = {}
for name in required:
items = raw[name]
if not isinstance(items, list) or not items or not all(
isinstance(item, str) and item for item in items
):
raise C5ExecutionHostBlocked("TRUSTED_POLICY_VALUES_INVALID")
values[name] = frozenset(items)
return TrustedReleasePolicy(**values)
def _phase_request(candidate: ReleaseCandidate, plan: ReleasePlan, approval, operation: str) -> dict[str, Any]:
return {
"schema": REQUEST_SCHEMA,
"operation": operation,
"version": C5_VERSION,
"target_oid": candidate.source_commit,
"evidence_closure_oid": approval.evidence_closure_oid,
"artifact_sha256": candidate.artifact_sha256,
"approval_plan_id": approval.approval_plan_id,
"repository_identity": plan.deployment.repository_identity,
"production_service": plan.deployment.service,
"production_port": C5_PRODUCTION_PORT,
}
def _checked_phase_response(
value: Mapping[str, Any], request: Mapping[str, Any]
) -> dict[str, Any]:
for name in (
"version", "target_oid", "evidence_closure_oid",
"artifact_sha256", "approval_plan_id",
):
if value.get(name) != request[name]:
raise C5ExecutionHostBlocked(f"PHASE_RECEIPT_MISMATCH:{name}")
return dict(value)
class _PreSwitchProbe:
def __init__(self, config: C5ExecutionHostConfig, source_admissions):
self.config = config
self.source_admissions = source_admissions
def _git(self, *args: str) -> str:
completed = subprocess.run(
[str(self.config.git_executable), f"--git-dir={self.config.authoritative_git_dir}", *args],
check=False,
capture_output=True,
text=True,
timeout=45,
env={"PATH": os.environ.get("PATH", "")},
)
if completed.returncode != 0:
raise C5ExecutionHostBlocked("PRE_SWITCH_GIT_UNAVAILABLE")
return completed.stdout.strip()
def collect(self, candidate: ReleaseCandidate, plan) -> Mapping[str, Any]:
admitted = self.source_admissions.resolve_recipe(
C5_VERSION, candidate.candidate_id, candidate.source_commit
)
tree = self._git("rev-parse", f"{candidate.source_commit}^{{tree}}")
artifact_ok = (
self.config.artifact_path.is_file()
and _sha256(self.config.artifact_path) == candidate.artifact_sha256
)
rollback_path = (
C4_ROLLBACK_ARTIFACT_PATH
if self.config.execution_scope == "PRODUCTION"
else self.config.rollback_artifact_path
)
rollback_target = (
C4_ROLLBACK_TARGET
if self.config.execution_scope == "PRODUCTION"
else self.config.rollback_target
)
rollback_ok = (
rollback_path.is_file()
and _sha256(rollback_path) == rollback_target["artifact_sha256"]
)
return {
"source_commit": candidate.source_commit,
"tree_oid": tree,
"artifact_sha256": candidate.artifact_sha256 if artifact_ok else "",
"manifest_sha256": candidate.manifest_sha256,
"deployment_plan_sha256": plan.sha256,
"policy_sha256": plan.policy_sha256,
"rollback_ready": rollback_ok,
"git_refs_consistent": (
admitted.get("source_commit") == candidate.source_commit
and admitted.get("tree_oid") == tree
),
"repository_identity": plan.repository_identity,
"writer_disabled": os.environ.get(
"AIOS_GITOPS_WRITES_ENABLED", "false"
).lower() == "false",
}
class _TagPhase:
def __init__(self, command: RegisteredPhaseCommand, candidate, plan, approval, owner_uid):
self.command_config = command
self.candidate = candidate
self.plan = plan
self.approval = approval
self.owner_uid = owner_uid
def _call(self, operation: str) -> dict[str, Any]:
self.command_config.verify(expected_owner_uid=self.owner_uid)
request = _phase_request(self.candidate, self.plan, self.approval, operation)
return _checked_phase_response(
run_adapter(self.command_config.command, request), request
)
def probe_evidence(self, tag_plan: TagPlan) -> TagPublicationProbeEvidence:
value = self._call("probe")
status = value.get("status")
listing = value.get("gitea_listing_visible")
if status == "NOT_APPLIED":
listing = False
return TagPublicationProbeEvidence(
repository_identity=tag_plan.repository_identity,
remote_reachable=True,
gitea_api_reachable=True,
gitea_listing_reachable=True,
remote_tag_oid=value.get("remote_tag_oid"),
annotated_tag_object_oid=value.get("tag_oid"),
remote_peeled_target_oid=value.get("remote_peeled_target_oid"),
gitea_api_tag_oid=value.get("gitea_tag_oid"),
gitea_api_peeled_target_oid=value.get("gitea_peeled_target_oid"),
gitea_listing_visible=listing,
gitea_listing_tag_oid=(value.get("gitea_tag_oid") if listing else None),
gitea_listing_peeled_target_oid=(value.get("gitea_peeled_target_oid") if listing else None),
observed_at=time.time(),
)
def __call__(self, *, tag_plan: TagPlan) -> Mapping[str, Any]:
value = self._call("probe")
if value.get("status") in {"NOT_APPLIED", "PARTIAL_LOCAL"}:
value = self._call("apply")
value = self._call("probe")
if value.get("status") == "CONFLICT":
raise TagPublicationConflict(
"tag publication conflicts with the exact C5 plan"
)
if value.get("status") != "APPLIED_VERIFIED":
raise RecoverableReleaseError(
"tag publication outcome is indeterminate"
)
proof = self.probe_evidence(tag_plan)
if proof.remote_tag_oid != tag_plan.expected_tag_oid:
raise C5ExecutionHostBlocked("TAG_OBJECT_BINDING_MISMATCH")
return {
"verified": True,
"evidence_closure_oid": self.approval.evidence_closure_oid,
"approval_plan_id": self.approval.approval_plan_id,
"repository_identity": tag_plan.repository_identity,
"remote_tag_oid": proof.remote_tag_oid,
"remote_peeled_target_oid": proof.remote_peeled_target_oid,
"gitea_api_tag_oid": proof.gitea_api_tag_oid,
"gitea_api_peeled_target_oid": proof.gitea_api_peeled_target_oid,
"gitea_listing_visible": proof.gitea_listing_visible,
}
class _MetadataPhase:
def __init__(self, *, mode: str, command: RegisteredPhaseCommand, record_path: Path,
candidate, plan, approval, tag_phase: _TagPhase, owner_uid):
self.mode = mode
self.command_config = command
self.record_path = record_path
self.candidate = candidate
self.plan = plan
self.approval = approval
self.tag_phase = tag_phase
self.owner_uid = owner_uid
def _call(self, operation: str) -> dict[str, Any]:
self.command_config.verify(expected_owner_uid=self.owner_uid)
request = _phase_request(self.candidate, self.plan, self.approval, operation)
request["tag_oid"] = self.plan.tag.expected_tag_oid
return _checked_phase_response(run_adapter(self.command_config.command, request), request)
def __call__(self, *, finalization_plan) -> Mapping[str, Any]:
value = self._call("probe")
if value.get("status") == "NOT_APPLIED":
self._call("apply")
value = self._call("probe")
if value.get("status") != "VERIFIED":
raise C5ExecutionHostBlocked(f"{self.mode.upper()}_FINALIZATION_NOT_VERIFIED")
try:
record = json.loads(self.record_path.read_text(encoding="utf-8"))
except (OSError, ValueError) as exc:
raise C5ExecutionHostBlocked(f"{self.mode.upper()}_RECORD_INVALID") from exc
expected_record = (
finalization_plan.registry_expected_record
if self.mode == "registry"
else finalization_plan.baseline_expected_record
)
if any(record.get(key) != item for key, item in expected_record.items()):
raise C5ExecutionHostBlocked(f"{self.mode.upper()}_RECORD_BINDING_MISMATCH")
tag = self.tag_phase.probe_evidence(self.plan.tag)
if (
tag.remote_tag_oid != self.plan.tag.expected_tag_oid
or tag.remote_peeled_target_oid != self.candidate.source_commit
or tag.gitea_api_peeled_target_oid != self.candidate.source_commit
or tag.gitea_listing_visible is not True
):
raise C5ExecutionHostBlocked("FINALIZATION_TAG_PROOF_INVALID")
proof = {
"verified": True,
"tag_verified": True,
"remote_tag_oid": tag.remote_tag_oid,
"remote_peeled_target_oid": tag.remote_peeled_target_oid,
"gitea_api_peeled_target_oid": tag.gitea_api_peeled_target_oid,
"gitea_listing_visible": True,
"evidence_closure_oid": self.approval.evidence_closure_oid,
"approval_plan_id": self.approval.approval_plan_id,
f"{self.mode}_target_oid": self.candidate.source_commit,
}
if self.mode == "baseline":
proof.update(
{
"registry_record": finalization_plan.registry_expected_record,
"baseline_record": finalization_plan.baseline_expected_record,
"baseline_written_last": True,
}
)
return proof
@dataclass(slots=True)
class C5ExecutionHost:
config: C5ExecutionHostConfig
orchestrator: ReleaseOrchestrator
def execute(self) -> dict[str, Any]:
result = dict(self.orchestrator.execute(self.config.candidate_id))
production_receipt = (
self.config.execution_scope == "PRODUCTION"
and result.get("state") == "COMPLETED"
)
return {
**result,
"execution_scope": self.config.execution_scope,
"instance_id": self.config.instance_id,
"provenance": dict(self.config.provenance),
"host_authority_commit": self.config.host_provenance.authority_commit,
"host_authority_tree": self.config.host_provenance.authority_tree,
"host_provenance_sha256": self.config.host_provenance.manifest_sha256,
"fixture_evidence": self.config.execution_scope == "ISOLATED_REHEARSAL",
"production_receipt": production_receipt,
"public_business_e2e_verified": False,
"production_acceptance": False,
}
def _validate_source_admission_task(config, candidate, admitted) -> None:
if admitted.get("task_id") != config.source_admission_task_id:
raise C5ExecutionHostBlocked("SOURCE_ADMISSION_TASK_BINDING_INVALID")
def build_execution_host(
config: C5ExecutionHostConfig,
*,
expected_owner_uid: int | None = 0,
completion_approvals: Any = None,
) -> C5ExecutionHost:
if os.environ.get("AIOS_GITOPS_WRITES_ENABLED", "false").lower() != "false":
raise C5ExecutionHostBlocked("LEGACY_GLOBAL_WRITES_ENABLED")
config.verify_files(expected_owner_uid=expected_owner_uid)
candidates = ReleaseCandidateStore(config.state_root / "candidates")
plans = ReleasePlanStore(config.state_root / "release-plans")
approvals = SignedApprovalStore(config.state_root / "approvals")
completion_authority = None
if completion_approvals is not None:
from c5_completion_renewal import (
RegisteredCompletionApprovalStore, RegisteredCompletionAuthority,
CompletionRuntimeGuard,
)
if (type(completion_approvals) is not RegisteredCompletionApprovalStore
or type(completion_approvals.authority) is not RegisteredCompletionAuthority
or expected_owner_uid != 0 or config.execution_scope != "PRODUCTION"
or Path(__file__) != completion_approvals.authority.root / "c5_release_execution_host.py"
or Path(completion_approvals.original.root) != approvals.root):
raise C5ExecutionHostBlocked("REGISTERED_COMPLETION_DEPENDENCY_REQUIRED")
completion_authority = completion_approvals.authority
completion_authority.validate_host(
state_root=config.state_root, candidate_id=config.candidate_id,
source_commit=config.source_commit, tree_oid=config.tree_oid,
artifact_sha256=config.artifact_sha256,
release_plan_sha256=config.release_plan_sha256,
)
approvals = completion_approvals
sagas = ReleaseSagaStore(config.state_root / "sagas")
candidate = candidates.load(config.candidate_id)
plan = plans.load(config.candidate_id).validate()
approval = approvals.load(config.candidate_id)
observed = (
candidate.source_commit, candidate.tree_oid, candidate.artifact_sha256,
plan.sha256, approval.fingerprint,
)
expected = (
config.source_commit, config.tree_oid, config.artifact_sha256,
config.release_plan_sha256, config.approval_fingerprint,
)
if observed != expected or plan.tag.version != C5_VERSION:
raise C5ExecutionHostBlocked("HOST_DURABLE_BINDING_MISMATCH")
if (
plan.policy_sha256 != config.trusted_policy_sha256
or Path(plan.deployment.authoritative_git_dir)
!= config.authoritative_git_dir
):
raise C5ExecutionHostBlocked("HOST_ACTUAL_PLAN_BINDING_MISMATCH")
policy = _load_policy(config.trusted_policy_path)
policy.validate_deployment(plan.deployment)
verifier = SignedApprovalVerifier(
ApprovalKeyRing.from_file(config.approval_keyring_path),
ApprovalRevocationStore(config.state_root / "revocations"),
)
verifier.verify_release_plan(approval, candidate, plan, plan.policy_sha256)
hmac_key = config.source_admission_hmac_path.read_bytes().strip()
source_admissions = open_source_admission_store(
config.source_admission_root,
repository=REPOSITORY,
policy=config.source_admission_policy_path,
hmac_key=hmac_key,
expected_owner_uid=expected_owner_uid,
writable=False,
)
admitted = source_admissions.resolve_recipe(
C5_VERSION, candidate.candidate_id, candidate.source_commit
)
_validate_source_admission_task(config, candidate, admitted)
deployment_executor = RegisteredC5DeploymentExecutor(
executor_id="c5-production-runtime-switch-v1",
executable=config.deployment_executable,
executable_sha256=config.deployment_executable_sha256,
adapter_path=config.deployment_adapter_path,
adapter_sha256=config.deployment_adapter_sha256,
artifact_path=config.artifact_path,
rollback_artifact_path=config.rollback_artifact_path,
production_environment_file=config.production_environment_file,
execution_scope=config.execution_scope,
isolation_root=(config.state_root if config.execution_scope == "ISOLATED_REHEARSAL" else None),
rollback_target=config.rollback_target,
require_root_owner=expected_owner_uid == 0,
)
runtime = C5ExactDeploymentAdapter(
candidate=candidate,
release_plan=plan,
approval=approval,
approval_verifier=verifier,
source_admissions=source_admissions,
executor=deployment_executor,
)
ledger = CapabilityLedger(config.state_root / "capabilities")
tag_phase = _TagPhase(
config.tag_command, candidate, plan, approval, expected_owner_uid
)
tag = HardenedTagController(
ledger,
tag_phase,
expected_repository=REPOSITORY,
publication_probe=tag_phase.probe_evidence,
executor_id="c5-production-tag-v1",
)
registry_adapter = _MetadataPhase(
mode="registry", command=config.registry_command,
record_path=config.registry_record_path, candidate=candidate,
plan=plan, approval=approval, tag_phase=tag_phase,
owner_uid=expected_owner_uid,
)
baseline_adapter = _MetadataPhase(
mode="baseline", command=config.baseline_command,
record_path=config.baseline_record_path, candidate=candidate,
plan=plan, approval=approval, tag_phase=tag_phase,
owner_uid=expected_owner_uid,
)
finalizer = GovernedFinalizationCoordinator(
RegistryController(ledger, registry_adapter, executor_id="c5-production-registry-v1"),
BaselineController(ledger, baseline_adapter, executor_id="c5-production-baseline-v1"),
)
def actual_plan_factory(actual_candidate: ReleaseCandidate) -> ReleasePlan:
actual = plans.load(actual_candidate.candidate_id).validate()
runtime._verify_binding(actual_candidate, actual)
return actual
def runtime_verify(*, candidate: ReleaseCandidate, plan: ReleasePlan) -> Mapping[str, Any]:
runtime._verify_binding(candidate, plan)
result = runtime._invoke("probe")
if runtime._classify(result) != ClaimProbeResult.APPLIED_VERIFIED:
raise C5ExecutionHostBlocked("RUNTIME_VERIFY_INDETERMINATE")
return {
"verified": True,
"source_commit": candidate.source_commit,
"tree_oid": candidate.tree_oid,
"artifact_sha256": candidate.artifact_sha256,
"manifest_sha256": candidate.manifest_sha256,
"repository_identity": plan.deployment.repository_identity,
"authoritative_git_dir": plan.deployment.authoritative_git_dir,
"writer_disabled": True,
}
orchestrator = ReleaseOrchestrator(
candidates=candidates,
plans=plans,
approvals=approvals,
sagas=sagas,
approval_verifier=verifier,
trusted_policy=policy,
actual_plan_factory=actual_plan_factory,
pre_switch_validator=PreSwitchValidator(
_PreSwitchProbe(config, source_admissions)
),
runtime_switch=(CompletionRuntimeGuard(completion_authority) if completion_authority
else runtime.controller(ledger)),
runtime_verify=runtime_verify,
runtime_rollback=(completion_authority.deny_runtime_effect if completion_authority
else runtime.rollback),
tag_publish=tag,
finalizer=finalizer,
)
return C5ExecutionHost(config=config, orchestrator=orchestrator)
def main(argv: Sequence[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--config", type=Path, required=True)
parser.add_argument("--config-sha256", required=True)
parser.add_argument("--provenance", type=Path, required=True)
parser.add_argument("--provenance-sha256", required=True)
args = parser.parse_args(argv)
provenance = C5HostProvenance.load(
args.provenance,
expected_sha256=args.provenance_sha256,
expected_owner_uid=0,
)
config = C5ExecutionHostConfig.load(
args.config,
expected_sha256=args.config_sha256,
host_provenance=provenance,
expected_owner_uid=0,
)
result = build_execution_host(config, expected_owner_uid=0).execute()
print(json.dumps(result, sort_keys=True))
return 0
if __name__ == "__main__":
try:
raise SystemExit(main())
except (C5ExecutionHostBlocked, CapabilityAuthorizationError) as exc:
print(json.dumps({"status": "FAIL_CLOSED", "error": type(exc).__name__}))
raise SystemExit(2)
File diff suppressed because it is too large Load Diff
+108
View File
@@ -0,0 +1,108 @@
"""Atomic, predicate-bound activation of the durable release campaign."""
from __future__ import annotations
import hashlib
import json
import os
from pathlib import Path
import re
import tempfile
from typing import Any, Mapping
from release_campaign_authorization import CampaignError, validate_policy
OID_RE = re.compile(r"^[0-9a-f]{40}$")
CAMPAIGN_ID = "AIOS-AUTONOMOUS-RELEASE-CAMPAIGN-20260807"
class CampaignActivationError(CampaignError):
pass
def canonical_json(value: Any) -> bytes:
return (json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False) + "\n").encode("utf-8")
def digest_bytes(value: bytes) -> str:
return hashlib.sha256(value).hexdigest()
def build_active_policy(current: Mapping[str, Any], facts: Mapping[str, Any]) -> dict[str, Any]:
policy = validate_policy(dict(current))
required = {
"campaign_id",
"repository",
"source_main_oid",
"gitea_main_oid",
"authority_main_oid",
"runtime_source_oid",
"durable_job_state",
"writer_ready",
"gitea_machine_authenticated",
"governance_executor_ready",
"preapproval_publisher_ready",
"runtime_exact_main",
"main_lineage_verified",
"campaign_revoked",
}
if set(facts) != required:
raise CampaignActivationError("ACTIVATION_FACTS_SCHEMA_INVALID")
if policy["activation_state"] != "PENDING_CONTROL_PLANE_MERGE" or policy["active"] is not False:
raise CampaignActivationError("CAMPAIGN_NOT_PENDING")
if policy["campaign_id"] != CAMPAIGN_ID or facts["campaign_id"] != CAMPAIGN_ID:
raise CampaignActivationError("CAMPAIGN_ID_MISMATCH")
if facts["repository"] != "aios/aios" or policy["repository"] != facts["repository"]:
raise CampaignActivationError("REPOSITORY_MISMATCH")
for name in ("source_main_oid", "gitea_main_oid", "authority_main_oid", "runtime_source_oid"):
if not isinstance(facts[name], str) or not OID_RE.fullmatch(facts[name]):
raise CampaignActivationError("ACTIVATION_OID_INVALID")
if not (
facts["source_main_oid"]
== facts["gitea_main_oid"]
== facts["authority_main_oid"]
== facts["runtime_source_oid"]
):
raise CampaignActivationError("ACTIVATION_MAIN_DIVERGENCE")
if facts["durable_job_state"] != "COMPLETED":
raise CampaignActivationError("DURABLE_JOB_NOT_COMPLETED")
boolean_fields = (
"writer_ready",
"gitea_machine_authenticated",
"governance_executor_ready",
"preapproval_publisher_ready",
"runtime_exact_main",
"main_lineage_verified",
)
if any(facts[name] is not True for name in boolean_fields) or facts["campaign_revoked"] is not False:
raise CampaignActivationError("ACTIVATION_PREDICATE_FAILED")
activated = dict(policy)
activated["activation_state"] = "ACTIVE"
activated["active"] = True
return validate_policy(activated)
def atomic_write_policy(path: Path, sidecar: Path, receipt_path: Path, policy: Mapping[str, Any], receipt: Mapping[str, Any]) -> str:
if path.is_symlink() or sidecar.is_symlink() or receipt_path.exists() or receipt_path.is_symlink():
raise CampaignActivationError("ACTIVATION_PATH_INVALID")
path.parent.mkdir(parents=True, exist_ok=True)
raw = canonical_json(dict(policy))
digest = digest_bytes(raw)
for target, data, mode in (
(path, raw, "wb"),
(sidecar, f"{digest} {path.name}\n".encode("ascii"), "wb"),
(receipt_path, canonical_json(dict(receipt)), "wb"),
):
fd, temporary = tempfile.mkstemp(prefix=f".{target.name}.", dir=target.parent)
try:
os.fchmod(fd, 0o600)
with os.fdopen(fd, mode) as stream:
stream.write(data)
stream.flush()
os.fsync(stream.fileno())
os.replace(temporary, target)
os.chmod(target, 0o600)
finally:
if os.path.exists(temporary):
os.unlink(temporary)
return digest
+106
View File
@@ -0,0 +1,106 @@
#!/usr/bin/env python3
"""Machine campaign approval gate for protected engineering PRs."""
from __future__ import annotations
import argparse
import json
from pathlib import Path
import subprocess
from typing import Any, Mapping
from release_campaign_authorization import CampaignAuthorization, CampaignError
class GateError(RuntimeError):
pass
def git_is_ancestor(repository: Path, ancestor: str, descendant: str) -> bool:
result = subprocess.run(
[
"git",
f"--git-dir={repository}",
"merge-base",
"--is-ancestor",
ancestor,
descendant,
],
check=False,
capture_output=True,
)
if result.returncode == 0:
return True
if result.returncode == 1:
return False
raise GateError("git_ancestor_check_failed")
def evaluate(
campaign: CampaignAuthorization,
facts: Mapping[str, Any],
repository: Path,
) -> dict[str, Any]:
required = {
"repository",
"pr_number",
"base_ref",
"base_oid",
"head_oid",
"required_checks_all_success",
"required_checks_same_head",
"unresolved_conversations",
"mergeable",
"protected_target",
}
if set(facts) != required:
raise GateError("pr_facts_fields_invalid")
if facts["repository"] != "aios/aios":
raise GateError("repository_mismatch")
if facts["base_ref"] != "refs/heads/main" or facts["protected_target"] != "main":
raise GateError("protected_main_required")
if facts["required_checks_all_success"] is not True:
raise GateError("required_checks_not_success")
if facts["required_checks_same_head"] is not True:
raise GateError("required_checks_stale")
if facts["unresolved_conversations"] != 0:
raise GateError("unresolved_conversations")
if facts["mergeable"] is not True:
raise GateError("pr_not_mergeable")
if not git_is_ancestor(repository, campaign.anchor_oid, str(facts["base_oid"])):
raise GateError("base_outside_campaign_lineage")
if not git_is_ancestor(repository, str(facts["base_oid"]), str(facts["head_oid"])):
raise GateError("head_outside_pr_lineage")
campaign.assert_operation("protected_merge")
return {
"schema": "aios.campaign-approval-gate-result.v1",
"status": "SUCCESS",
"campaign_id": campaign.campaign_id,
"pr_number": facts["pr_number"],
"head_oid": facts["head_oid"],
"owner_ui_interaction_required": False,
}
def main() -> int:
p = argparse.ArgumentParser()
p.add_argument("--campaign", type=Path, required=True)
p.add_argument("--revocation", type=Path)
p.add_argument("--pr-facts", type=Path, required=True)
p.add_argument("--repository", type=Path, required=True)
a = p.parse_args()
campaign = CampaignAuthorization.load(
a.campaign,
revocation=a.revocation,
require_active=True,
)
facts = json.loads(a.pr_facts.read_text(encoding="utf-8"))
print(json.dumps(evaluate(campaign, facts, a.repository), sort_keys=True))
return 0
if __name__ == "__main__":
try:
raise SystemExit(main())
except (CampaignError, GateError) as exc:
print(json.dumps({"status": "FAILURE", "reason": str(exc)}, sort_keys=True))
raise SystemExit(2)
+189
View File
@@ -0,0 +1,189 @@
#!/usr/bin/env python3
"""Automatically accept an exact release binding under an ACTIVE campaign."""
from __future__ import annotations
import argparse
import hashlib
import json
import os
from pathlib import Path
import stat
import tempfile
from typing import Any, Mapping
from release_campaign_authorization import CampaignAuthorization, CampaignError
from trusted_release_execution_plan import PlanError, TrustedReleasePlan
class AcceptanceError(RuntimeError):
pass
def secure_json(path: Path) -> dict[str, Any]:
if path.is_symlink() or not path.is_file():
raise AcceptanceError(f"invalid_file:{path}")
st = path.lstat()
if st.st_uid != 0 or stat.S_IMODE(st.st_mode) & 0o022:
raise AcceptanceError(f"unsafe_file:{path}")
value = json.loads(path.read_text(encoding="utf-8"))
if not isinstance(value, dict):
raise AcceptanceError(f"json_object_required:{path}")
return value
def atomic_new(path: Path, value: Mapping[str, Any]) -> None:
if path.exists() or path.is_symlink():
raise AcceptanceError("acceptance_receipt_exists")
path.parent.mkdir(parents=True, exist_ok=True)
fd, name = tempfile.mkstemp(prefix=f".{path.name}.", dir=path.parent)
try:
os.fchmod(fd, 0o600)
if os.geteuid() == 0:
os.fchown(fd, 0, 0)
with os.fdopen(fd, "w", encoding="utf-8", closefd=True) as stream:
json.dump(dict(value), stream, sort_keys=True, indent=2)
stream.write("\n")
stream.flush()
os.fsync(stream.fileno())
os.replace(name, path)
os.chmod(path, 0o600)
finally:
try:
os.unlink(name)
except FileNotFoundError:
pass
def evaluate(
campaign: CampaignAuthorization,
binding: Mapping[str, Any],
evidence: Mapping[str, Any],
artifact: Mapping[str, Any],
staging: Mapping[str, Any],
main: Mapping[str, Any],
tag: Mapping[str, Any],
release_plan: Mapping[str, Any] | None = None,
) -> dict[str, Any]:
required_binding = {
"version",
"target_oid",
"evidence_closure_oid",
"artifact_sha256",
"artifact_size",
"approval_plan_id",
}
if set(binding) != required_binding:
raise AcceptanceError("binding_fields_invalid")
if main.get("repository") != "aios/aios":
raise AcceptanceError("repository_identity_mismatch")
if main.get("target_in_main_lineage") is not True:
raise AcceptanceError("target_outside_main_lineage")
if evidence.get("object_type") != "commit":
raise AcceptanceError("evidence_not_commit")
if evidence.get("target_is_ancestor") is not True:
raise AcceptanceError("target_not_evidence_ancestor")
if evidence.get("closure_oid") != binding["evidence_closure_oid"]:
raise AcceptanceError("evidence_oid_mismatch")
if artifact.get("sha256") != binding["artifact_sha256"]:
raise AcceptanceError("artifact_digest_mismatch")
if artifact.get("size") != binding["artifact_size"]:
raise AcceptanceError("artifact_size_mismatch")
if artifact.get("retained") is not True:
raise AcceptanceError("artifact_not_retained")
if staging.get("install") != "PASS":
raise AcceptanceError("staging_install_failed")
if staging.get("health") != "PASS":
raise AcceptanceError("staging_health_failed")
if staging.get("restart") != "PASS":
raise AcceptanceError("staging_restart_failed")
if staging.get("rollback_readiness") != "PASS":
raise AcceptanceError("rollback_readiness_failed")
if tag.get("conflict") is not False:
raise AcceptanceError("tag_conflict")
trusted_plan: TrustedReleasePlan | None = None
if release_plan is not None:
try:
trusted_plan = TrustedReleasePlan.from_mapping(release_plan)
trusted_plan.assert_binding(binding)
except PlanError as exc:
raise AcceptanceError(f"trusted_release_plan_invalid:{exc}") from exc
if trusted_plan.value["campaign_authorization_sha256"] != campaign.digest:
raise AcceptanceError("campaign_authorization_digest_mismatch")
if int(trusted_plan.value["campaign_generation"]) != int(campaign.data["generation"]):
raise AcceptanceError("campaign_generation_mismatch")
if main.get("tree_oid") not in (None, trusted_plan.value["tree_oid"]):
raise AcceptanceError("tree_oid_mismatch")
if evidence.get("release_plan_sha256") not in (None, trusted_plan.release_plan_sha256):
raise AcceptanceError("evidence_release_plan_mismatch")
if artifact.get("manifest_sha256") not in (None, trusted_plan.value["manifest_sha256"]):
raise AcceptanceError("manifest_digest_mismatch")
if artifact.get("package_closure_sha256") not in (None, trusted_plan.value["package_closure_sha256"]):
raise AcceptanceError("package_closure_digest_mismatch")
campaign.assert_operation("derive_campaign_acceptance")
result = {
"schema": "aios.campaign-release-acceptance.v1",
"accepted": True,
"binding_replaced": False,
"campaign_id": campaign.campaign_id,
"campaign_generation": campaign.data["generation"],
**dict(binding),
"owner_interaction_required": False,
}
if trusted_plan is not None:
result.update({
"candidate_id": trusted_plan.value["candidate_id"],
"release_plan_sha256": trusted_plan.release_plan_sha256,
"candidate_binding_sha256": trusted_plan.candidate_binding_sha256,
"campaign_authorization_sha256": trusted_plan.value["campaign_authorization_sha256"],
"campaign_generation": trusted_plan.value["campaign_generation"],
"tree_oid": trusted_plan.value["tree_oid"],
"manifest_sha256": trusted_plan.value["manifest_sha256"],
"package_closure_sha256": trusted_plan.value["package_closure_sha256"],
"deployment_plan_sha256": trusted_plan.value["deployment_plan_sha256"],
"policy_sha256": trusted_plan.value["policy_sha256"],
"rollback": trusted_plan.value["rollback"],
"baseline": trusted_plan.value["baseline"],
})
return result
def main() -> int:
p = argparse.ArgumentParser()
p.add_argument("--campaign", type=Path, required=True)
p.add_argument("--revocation", type=Path)
p.add_argument("--binding", type=Path, required=True)
p.add_argument("--evidence", type=Path, required=True)
p.add_argument("--artifact", type=Path, required=True)
p.add_argument("--staging", type=Path, required=True)
p.add_argument("--main-facts", type=Path, required=True)
p.add_argument("--tag-facts", type=Path, required=True)
p.add_argument("--release-plan", type=Path)
p.add_argument("--output", type=Path, required=True)
a = p.parse_args()
campaign = CampaignAuthorization.load(
a.campaign,
revocation=a.revocation,
require_active=True,
)
receipt = evaluate(
campaign,
secure_json(a.binding),
secure_json(a.evidence),
secure_json(a.artifact),
secure_json(a.staging),
secure_json(a.main_facts),
secure_json(a.tag_facts),
secure_json(a.release_plan) if a.release_plan else None,
)
atomic_new(a.output, receipt)
print(json.dumps(receipt, sort_keys=True))
return 0
if __name__ == "__main__":
try:
raise SystemExit(main())
except (CampaignError, AcceptanceError) as exc:
print(json.dumps({"status": "BLOCKED", "reason": str(exc)}, sort_keys=True))
raise SystemExit(2)
@@ -0,0 +1,326 @@
"""Fail-closed component deployment identity projection.
This module deliberately does not discover a trust root or treat process
environment/health responses as runtime evidence. A host supplies a trusted
comparison callback after it has performed its own authenticated observation.
"""
from __future__ import annotations
import hashlib
import json
import math
import re
import time
from pathlib import Path
from typing import Any, Callable, Mapping
_OID = re.compile(r"^[0-9a-f]{40}$")
_SHA256 = re.compile(r"^[0-9a-f]{64}$")
_COMPONENT_SCHEMA = "aios.component-manifest.v1"
_RECEIPT_SCHEMA = "aios.component-artifact-receipt.v1"
class RuntimeIdentityError(ValueError):
"""A declared or observed identity has an invalid shape."""
def _strict_object(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
result: dict[str, Any] = {}
for key, value in pairs:
if key in result:
raise RuntimeIdentityError("DUPLICATE_FIELD")
result[key] = value
return result
def _load_json(path: Path) -> Mapping[str, Any]:
try:
metadata = path.lstat()
if not path.is_file() or path.is_symlink() or metadata.st_size > 2 * 1024 * 1024:
raise RuntimeIdentityError("IDENTITY_FILE_INVALID")
value = json.loads(path.read_text(encoding="utf-8"), object_pairs_hook=_strict_object)
except (OSError, UnicodeError, TypeError, json.JSONDecodeError) as exc:
raise RuntimeIdentityError("IDENTITY_FILE_INVALID") from exc
if not isinstance(value, Mapping):
raise RuntimeIdentityError("IDENTITY_OBJECT_REQUIRED")
return value
def _require_sha(value: object, field: str) -> str:
if not isinstance(value, str) or _SHA256.fullmatch(value) is None:
raise RuntimeIdentityError(f"{field.upper()}_INVALID")
return value
def _require_oid(value: object, field: str) -> str:
if not isinstance(value, str) or _OID.fullmatch(value) is None:
raise RuntimeIdentityError(f"{field.upper()}_INVALID")
return value
def _require_text(value: object, field: str) -> str:
if not isinstance(value, str) or not value.strip():
raise RuntimeIdentityError(f"{field.upper()}_INVALID")
return value
def _canonical(value: object) -> str:
try:
return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False, allow_nan=False)
except (TypeError, ValueError) as exc:
raise RuntimeIdentityError("IDENTITY_VALUE_INVALID") from exc
def _validate_modules(value: object) -> dict[str, str]:
if not isinstance(value, Mapping) or not value:
raise RuntimeIdentityError("MODULES_INVALID")
modules: dict[str, str] = {}
for path, digest in value.items():
name = _require_text(path, "module_path")
if name.startswith("/") or ".." in Path(name).parts:
raise RuntimeIdentityError("MODULE_PATH_INVALID")
modules[name] = _require_sha(digest, "module_sha256")
return dict(sorted(modules.items()))
def _validate_extra_files(value: object) -> dict[str, str]:
if not isinstance(value, Mapping):
raise RuntimeIdentityError("EXTRA_FILES_INVALID")
files: dict[str, str] = {}
for path, digest in value.items():
name = _require_text(path, "extra_file_path")
if name.startswith("/") or ".." in Path(name).parts:
raise RuntimeIdentityError("EXTRA_FILE_PATH_INVALID")
files[name] = _require_sha(digest, "extra_file_sha256")
return dict(sorted(files.items()))
def _relative_artifact_path(value: object, field: str, *, allow_current: bool = False) -> str:
name = _require_text(value, field)
path = Path(name)
if "\\" in name or path.is_absolute() or ".." in path.parts or (name == "." and not allow_current):
raise RuntimeIdentityError(f"{field.upper()}_INVALID")
return name
def _validate_start_identity(value: object) -> dict[str, Any]:
if not isinstance(value, Mapping) or set(value) != {"entrypoint", "config_path", "python_path"}:
raise RuntimeIdentityError("START_IDENTITY_INVALID")
entrypoint = _require_text(value.get("entrypoint"), "entrypoint")
config_path = _relative_artifact_path(value.get("config_path"), "config_path")
python_path = value.get("python_path")
if not isinstance(python_path, list) or not python_path:
raise RuntimeIdentityError("START_IDENTITY_INVALID")
result = {
"entrypoint": entrypoint,
"config_path": config_path,
"python_path": [
_relative_artifact_path(path, "python_path", allow_current=True)
for path in python_path
],
}
_canonical(result)
return result
def _validate_component(value: object) -> dict[str, Any]:
required = {
"component_id", "source_oid", "tree_oid", "modules", "extra_files", "config_sha256",
"start_identity", "dependency_contract_version", "rollback_artifact_sha256",
}
if not isinstance(value, Mapping) or set(value) != required:
raise RuntimeIdentityError("COMPONENT_MANIFEST_FIELDS_INVALID")
rollback = value.get("rollback_artifact_sha256")
if rollback is not None:
rollback = _require_sha(rollback, "rollback_artifact_sha256")
return {
"component_id": _require_text(value.get("component_id"), "component_id"),
"source_oid": _require_oid(value.get("source_oid"), "source_oid"),
"tree_oid": _require_oid(value.get("tree_oid"), "tree_oid"),
"modules": _validate_modules(value.get("modules")),
"extra_files": _validate_extra_files(value.get("extra_files")),
"config_sha256": _require_sha(value.get("config_sha256"), "config_sha256"),
"start_identity": _validate_start_identity(value.get("start_identity")),
"dependency_contract_version": _require_text(value.get("dependency_contract_version"), "dependency_contract_version"),
"rollback_artifact_sha256": rollback,
}
def validate_component_manifest(value: object) -> dict[str, Any]:
if not isinstance(value, Mapping) or set(value) != {"schema", "schema_version", "components"}:
raise RuntimeIdentityError("COMPONENT_MANIFEST_FIELDS_INVALID")
if value.get("schema") != _COMPONENT_SCHEMA or value.get("schema_version") != 1:
raise RuntimeIdentityError("COMPONENT_MANIFEST_SCHEMA_INVALID")
components = value.get("components")
if not isinstance(components, list) or not components:
raise RuntimeIdentityError("COMPONENT_MANIFEST_COMPONENTS_INVALID")
normalized = [_validate_component(component) for component in components]
ids = [component["component_id"] for component in normalized]
if len(set(ids)) != len(ids):
raise RuntimeIdentityError("COMPONENT_ID_DUPLICATE")
return {"schema": _COMPONENT_SCHEMA, "schema_version": 1, "components": normalized}
def load_component_manifest(path: Path) -> tuple[dict[str, Any], str]:
raw = path.read_bytes()
return validate_component_manifest(_load_json(path)), hashlib.sha256(raw).hexdigest()
def validate_artifact_receipt(value: object) -> dict[str, Any]:
required = {"schema", "schema_version", "artifact_sha256", "component_manifest_sha256", "manifest_sha256", "evidence_refs"}
optional = {"version", "source_commit", "tree_oid", "artifact_size", "production_deployed"}
if not isinstance(value, Mapping) or not required.issubset(value) or set(value) - required - optional:
raise RuntimeIdentityError("ARTIFACT_RECEIPT_FIELDS_INVALID")
if value.get("schema") != _RECEIPT_SCHEMA or value.get("schema_version") != 1:
raise RuntimeIdentityError("ARTIFACT_RECEIPT_SCHEMA_INVALID")
references = value.get("evidence_refs")
if not isinstance(references, list) or not references or any(not isinstance(ref, str) or not ref.strip() for ref in references):
raise RuntimeIdentityError("EVIDENCE_REFS_INVALID")
result = {
"schema": _RECEIPT_SCHEMA,
"schema_version": 1,
"artifact_sha256": _require_sha(value.get("artifact_sha256"), "artifact_sha256"),
"component_manifest_sha256": _require_sha(value.get("component_manifest_sha256"), "component_manifest_sha256"),
"manifest_sha256": _require_sha(value.get("manifest_sha256"), "manifest_sha256"),
"evidence_refs": list(references),
}
if "version" in value:
result["version"] = _require_text(value["version"], "version")
if "source_commit" in value:
result["source_commit"] = _require_oid(value["source_commit"], "source_commit")
if "tree_oid" in value:
result["tree_oid"] = _require_oid(value["tree_oid"], "tree_oid")
if "artifact_size" in value:
size = value["artifact_size"]
if type(size) is not int or size < 1:
raise RuntimeIdentityError("ARTIFACT_SIZE_INVALID")
result["artifact_size"] = size
if "production_deployed" in value:
deployed = value["production_deployed"]
if type(deployed) is not bool:
raise RuntimeIdentityError("PRODUCTION_DEPLOYED_INVALID")
result["production_deployed"] = deployed
return result
def load_artifact_receipt(path: Path) -> dict[str, Any]:
return validate_artifact_receipt(_load_json(path))
def _identity_result(
status: str,
*,
component_id: str | None,
declared: Mapping[str, Any] | None = None,
observed: Mapping[str, Any] | None = None,
reason: str,
evidence_refs: list[str] | None = None,
mismatches: list[str] | None = None,
) -> dict[str, Any]:
return {
"status": status,
"component_id": component_id,
"declared_identity": dict(declared) if declared is not None else None,
"observed_identity": dict(observed) if observed is not None else None,
"manifest_digest": declared.get("component_manifest_sha256") if declared else None,
"config_digest": declared.get("config_sha256") if declared else None,
"observed_at": observed.get("observed_at") if observed else None,
"expires_at": observed.get("expires_at") if observed else None,
"evidence_refs": list(evidence_refs or ()),
"reason": reason,
"mismatch_fields": list(mismatches or ()),
}
def declared_component_identity(manifest_path: Path | None, receipt_path: Path | None, component_id: str | None) -> dict[str, Any] | None:
if manifest_path is None or receipt_path is None or not component_id:
return None
manifest, manifest_digest = load_component_manifest(manifest_path)
receipt = load_artifact_receipt(receipt_path)
if receipt["component_manifest_sha256"] != manifest_digest:
raise RuntimeIdentityError("COMPONENT_MANIFEST_DIGEST_MISMATCH")
component = next((item for item in manifest["components"] if item["component_id"] == component_id), None)
if component is None:
raise RuntimeIdentityError("COMPONENT_ID_NOT_DECLARED")
return {
**component,
"artifact_sha256": receipt["artifact_sha256"],
"component_manifest_sha256": receipt["component_manifest_sha256"],
"manifest_sha256": receipt["manifest_sha256"],
}
def _timestamp(value: object, field: str) -> float:
if isinstance(value, bool) or not isinstance(value, (int, float)) or not math.isfinite(float(value)) or float(value) < 0:
raise RuntimeIdentityError(f"{field.upper()}_INVALID")
return float(value)
def _validate_observed(value: object) -> dict[str, Any]:
required = {
"component_id", "source_oid", "tree_oid", "modules", "extra_files", "config_sha256", "start_identity",
"artifact_sha256", "observed_at", "expires_at", "evidence_refs",
}
if not isinstance(value, Mapping) or set(value) != required:
raise RuntimeIdentityError("OBSERVED_IDENTITY_FIELDS_INVALID")
observed_at = _timestamp(value.get("observed_at"), "observed_at")
expires_at = _timestamp(value.get("expires_at"), "expires_at")
if expires_at <= observed_at:
raise RuntimeIdentityError("OBSERVATION_EXPIRY_INVALID")
refs = value.get("evidence_refs")
if not isinstance(refs, list) or not refs or any(not isinstance(ref, str) or not ref.strip() for ref in refs):
raise RuntimeIdentityError("EVIDENCE_REFS_INVALID")
return {
"component_id": _require_text(value.get("component_id"), "component_id"),
"source_oid": _require_oid(value.get("source_oid"), "source_oid"),
"tree_oid": _require_oid(value.get("tree_oid"), "tree_oid"),
"modules": _validate_modules(value.get("modules")),
"extra_files": _validate_extra_files(value.get("extra_files")),
"config_sha256": _require_sha(value.get("config_sha256"), "config_sha256"),
"start_identity": _validate_start_identity(value.get("start_identity")),
"artifact_sha256": _require_sha(value.get("artifact_sha256"), "artifact_sha256"),
"observed_at": observed_at,
"expires_at": expires_at,
"evidence_refs": list(refs),
}
TrustedComparison = Callable[[Mapping[str, Any]], Mapping[str, Any] | None]
def evaluate_component_runtime_identity(
*,
component_id: str | None,
manifest_path: Path | None,
receipt_path: Path | None,
trusted_comparison: TrustedComparison | None = None,
now: float | None = None,
) -> dict[str, Any]:
"""Project identity without granting trust to a declaration or self-report."""
try:
declared = declared_component_identity(manifest_path, receipt_path, component_id)
except RuntimeIdentityError as exc:
return _identity_result("FAIL", component_id=component_id, reason=str(exc))
if declared is None:
return _identity_result("UNKNOWN", component_id=component_id, reason="DECLARED_IDENTITY_UNAVAILABLE")
if trusted_comparison is None:
return _identity_result("UNKNOWN", component_id=component_id, declared=declared, reason="TRUSTED_OBSERVATION_UNAVAILABLE")
try:
raw_observed = trusted_comparison(dict(declared))
except Exception:
return _identity_result("UNKNOWN", component_id=component_id, declared=declared, reason="TRUSTED_COMPARISON_UNAVAILABLE")
if raw_observed is None:
return _identity_result("UNKNOWN", component_id=component_id, declared=declared, reason="OBSERVATION_MISSING")
try:
observed = _validate_observed(raw_observed)
except RuntimeIdentityError as exc:
return _identity_result("FAIL", component_id=component_id, declared=declared, reason=str(exc))
current = time.time() if now is None else now
if observed["expires_at"] <= current:
return _identity_result("UNKNOWN", component_id=component_id, declared=declared, observed=observed, reason="OBSERVATION_EXPIRED", evidence_refs=observed["evidence_refs"])
fields = ("component_id", "source_oid", "tree_oid", "modules", "extra_files", "config_sha256", "start_identity", "artifact_sha256")
mismatches = [field for field in fields if _canonical(declared[field]) != _canonical(observed[field])]
if mismatches:
return _identity_result("FAIL", component_id=component_id, declared=declared, observed=observed, reason="DECLARED_OBSERVED_DRIFT", evidence_refs=observed["evidence_refs"], mismatches=mismatches)
return _identity_result("PASS", component_id=component_id, declared=declared, observed=observed, reason="TRUSTED_COMPARISON_MATCH", evidence_refs=observed["evidence_refs"])
+11
View File
@@ -9,6 +9,8 @@ from dataclasses import dataclass
from pathlib import Path
from typing import Any, Mapping
from component_runtime_identity import evaluate_component_runtime_identity
_OID = re.compile(r"^[0-9a-f]{40}$")
_SHA = re.compile(r"^[0-9a-f]{64}$")
_STATES = {"STAGED", "ACTIVE", "ROLLED_BACK"}
@@ -25,6 +27,13 @@ class RuntimeDeploymentState:
return self.deployment_state == "ACTIVE"
def health_payload(self) -> dict[str, Any]:
# This listener owns liveness only. Its environment-derived release
# declaration must never become runtime identity evidence by itself.
identity = evaluate_component_runtime_identity(
component_id=None,
manifest_path=None,
receipt_path=None,
)
return {
"status": "ok",
"version": self.version,
@@ -33,6 +42,8 @@ class RuntimeDeploymentState:
"deployment_state": self.deployment_state,
"production_deployed": self.production_deployed,
"health_timestamp": dt.datetime.now(dt.timezone.utc).isoformat(),
"drift_status": identity["status"],
"component_runtime_identity": identity,
}
@classmethod
+16 -7
View File
@@ -8,6 +8,7 @@ import json
import os
import re
import subprocess
import sys
import tempfile
import time
import urllib.error
@@ -23,10 +24,15 @@ try:
from authoritative_git import AuthoritativeGitError, run_authoritative_git
except ModuleNotFoundError:
from .authoritative_git import AuthoritativeGitError, run_authoritative_git
try:
from release_version import RELEASE_VERSION_RE, validate_release_version
except ModuleNotFoundError:
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
from release_version import RELEASE_VERSION_RE, validate_release_version
OID_RE = re.compile(r"^[0-9a-f]{40}$")
VERSION_RE = re.compile(r"^V\d+R\d+C\d+(?:-[A-Za-z0-9_.-]+)?$")
VERSION_RE = RELEASE_VERSION_RE
DEPLOYMENT_EVIDENCE_RE = re.compile(r"^(?:sha256:[0-9a-f]{64}|git:[0-9a-f]{40})$")
FINGERPRINT_FIELDS = (
"version",
@@ -83,9 +89,14 @@ class PublicationManualOwnerReviewRequired(
def validate_version(version: str) -> str:
value = str(version or "")
if not VERSION_RE.fullmatch(value):
raise ValueError("invalid release version")
return validate_release_version(version)
def validate_gitea_api_url(api_url: str) -> str:
value = str(api_url or "").strip().rstrip("/")
parsed = urllib.parse.urlsplit(value)
if parsed.scheme not in {"http", "https"} or not parsed.netloc:
raise ValueError("Gitea API URL must be HTTP(S)")
return value
@@ -411,13 +422,11 @@ class GiteaTagClient:
timeout: float = 15,
token_line: int = 1,
):
self.api_url = api_url.rstrip("/")
self.api_url = validate_gitea_api_url(api_url)
self.repository = repository.strip("/")
self.token_file = token_file
self.timeout = timeout
self.token_line = token_line
if not self.api_url.startswith(("https://", "http://")):
raise ValueError("Gitea API URL must be HTTP(S)")
if self.repository.count("/") != 1:
raise ValueError("Gitea repository must be owner/name")

Some files were not shown because too many files have changed in this diff Show More