Compare commits

..
Author SHA1 Message Date
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
227 changed files with 59988 additions and 856 deletions
-28
View File
@@ -1,28 +0,0 @@
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write|Bash",
"hooks": [
{
"type": "command",
"command": "cat >/dev/null || true; CRG=\"C:/Users/ImRuY/.workbuddy-ai/binaries/python/envs/default/Scripts/code-review-graph.exe\"; [ -x \"$CRG\" ] || exit 0; git rev-parse --git-dir >/dev/null 2>&1 && PYTHONUTF8=1 \"$CRG\" update --skip-flows --repo \"$(git rev-parse --show-toplevel 2>/dev/null)\" || true",
"timeout": 60
}
]
}
],
"SessionStart": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "cat >/dev/null || true; CRG=\"C:/Users/ImRuY/.workbuddy-ai/binaries/python/envs/default/Scripts/code-review-graph.exe\"; [ -x \"$CRG\" ] || exit 0; git rev-parse --git-dir >/dev/null 2>&1 && PYTHONUTF8=1 \"$CRG\" status --repo \"$(git rev-parse --show-toplevel 2>/dev/null)\" || echo 'Not a git repo, skipping'",
"timeout": 30
}
]
}
]
}
}
-28
View File
@@ -1,28 +0,0 @@
---
name: debug-issue
description: Systematically debug issues using graph-powered code navigation
---
## Debug Issue
Use the knowledge graph to systematically trace and debug issues.
### Steps
1. Use `semantic_search_nodes_tool` to find code related to the issue.
2. Use `query_graph_tool` with `callers_of` and `callees_of` to trace call chains.
3. Use `get_flow_tool` to see full execution paths through suspected areas.
4. Run `detect_changes_tool` to check if recent changes caused the issue.
5. Use `get_impact_radius_tool` on suspected files to see what else is affected.
### Tips
- Check both callers and callees to understand the full context.
- Look at affected flows to find the entry point that triggers the bug.
- Recent changes are the most common source of new issues.
## Token Efficiency Rules
- Start with `get_minimal_context_tool(task="<your task>")` before other graph tools.
- Use `detail_level="minimal"` on all calls. Only escalate to "standard" when minimal is insufficient.
- Target: complete any review/debug/refactor task in ≤5 tool calls and ≤800 total output tokens.
- Read the implementation and its tests before changing code. The graph narrows scope; it does not replace the source.
@@ -1,29 +0,0 @@
---
name: explore-codebase
description: Navigate and understand codebase structure using the knowledge graph
---
## Explore Codebase
Use the code-review-graph MCP tools to explore and understand the codebase.
### Steps
1. Run `list_graph_stats_tool` to see overall codebase metrics.
2. Run `get_architecture_overview_tool` for high-level community structure.
3. Use `list_communities_tool` to find major modules, then `get_community_tool` for details.
4. Use `semantic_search_nodes_tool` to find specific functions or classes.
5. Use `query_graph_tool` with patterns like `callers_of`, `callees_of`, `imports_of` to trace relationships.
6. Use `list_flows_tool` and `get_flow_tool` to understand execution paths.
### Tips
- Start broad (stats, architecture) then narrow down to specific areas.
- Use `children_of` on a file to see all its functions and classes.
- Use `find_large_functions_tool` to identify complex code.
## Token Efficiency Rules
- Start with `get_minimal_context_tool(task="<your task>")` before other graph tools.
- Use `detail_level="minimal"` on all calls. Only escalate to "standard" when minimal is insufficient.
- Target: complete any review/debug/refactor task in ≤5 tool calls and ≤800 total output tokens.
- Read the implementation and its tests before changing code. The graph narrows scope; it does not replace the source.
@@ -1,29 +0,0 @@
---
name: refactor-safely
description: Plan and execute safe refactoring using dependency analysis
---
## Refactor Safely
Use the knowledge graph to plan and execute refactoring with confidence.
### Steps
1. Use `refactor_tool` with mode="suggest" for community-driven refactoring suggestions.
2. Use `refactor_tool` with mode="dead_code" to find unreferenced code.
3. For renames, use `refactor_tool` with mode="rename" to preview all affected locations.
4. Use `apply_refactor_tool` with the refactor_id to apply renames.
5. After changes, run `detect_changes_tool` to verify the refactoring impact.
### Safety Checks
- Always preview before applying (rename mode gives you an edit list).
- Check `get_impact_radius_tool` before major refactors.
- Use `get_affected_flows_tool` to ensure no critical paths are broken.
- Run `find_large_functions_tool` to identify decomposition targets.
## Token Efficiency Rules
- Start with `get_minimal_context_tool(task="<your task>")` before other graph tools.
- Use `detail_level="minimal"` on all calls. Only escalate to "standard" when minimal is insufficient.
- Target: complete any review/debug/refactor task in ≤5 tool calls and ≤800 total output tokens.
- Read the implementation and its tests before changing code. The graph narrows scope; it does not replace the source.
-30
View File
@@ -1,30 +0,0 @@
---
name: review-changes
description: Perform a structured code review using change detection and impact
---
## Review Changes
Perform a thorough, risk-aware code review using the knowledge graph.
### Steps
1. Run `detect_changes_tool` to get risk-scored change analysis.
2. Run `get_affected_flows_tool` to find impacted execution paths.
3. For each high-risk function, run `query_graph_tool` with pattern="tests_for" to check test coverage.
4. Run `get_impact_radius_tool` to understand the blast radius.
5. For any untested changes, suggest specific test cases.
### Output Format
Provide findings grouped by risk level (high/medium/low) with:
- What changed and why it matters
- Test coverage status
- Suggested improvements
- Overall merge recommendation
## Token Efficiency Rules
- Start with `get_minimal_context_tool(task="<your task>")` before other graph tools.
- Use `detail_level="minimal"` on all calls. Only escalate to "standard" when minimal is insufficient.
- Target: complete any review/debug/refactor task in ≤5 tool calls and ≤800 total output tokens.
- Read the implementation and its tests before changing code. The graph narrows scope; it does not replace the source.
-2
View File
@@ -11,5 +11,3 @@ runtime/tmp/
runtime/cache/
logs/
config/test_write_perms
# Added by code-review-graph
.code-review-graph/
-15
View File
@@ -1,15 +0,0 @@
{
"mcpServers": {
"code-review-graph": {
"command": "C:\\Users\\ImRuY\\.workbuddy-ai\\binaries\\python\\envs\\default\\Scripts\\code-review-graph.exe",
"args": [
"serve"
],
"cwd": "C:\\Users\\ImRuY\\aios-main-clone",
"env": {
"PYTHONUTF8": "1"
},
"type": "stdio"
}
}
}
-45
View File
@@ -1,45 +0,0 @@
<!-- code-review-graph MCP tools -->
## MCP Tools: code-review-graph
**This project has a knowledge graph. Start with the code-review-graph
MCP tools to narrow scope, then read the source.** The graph is cheaper than scanning files and
gives you structural context (callers, dependents, test coverage) that file search cannot.
### When to use graph tools FIRST
- **Exploring code**: `semantic_search_nodes_tool` or `query_graph_tool` instead of Grep
- **Understanding impact**: `get_impact_radius_tool` instead of manually tracing imports
- **Code review**: `detect_changes_tool` + `get_review_context_tool` instead of reading entire files
- **Finding relationships**: `query_graph_tool` with callers_of/callees_of/imports_of/tests_for
- **Architecture questions**: `get_architecture_overview_tool` + `list_communities_tool`
### Verify in the source
- Narrow scope with the graph, then read the source. Do not change code from graph output alone.
- For any non-trivial change, read the implementation and the relevant tests before concluding.
- Verify the exact source when touching behavior, database logic, migrations, retries, fallbacks,
recovery, or compatibility code.
- When the graph and the source disagree, the source wins. The graph may be stale or may not
model that relationship.
- An empty graph result can mean "not indexed" or "not statically visible", not "does not exist".
### Key Tools
| Tool | Use when |
| ------ | ---------- |
| `detect_changes_tool` | Reviewing code changes — gives risk-scored analysis |
| `get_review_context_tool` | Need source snippets for review — token-efficient |
| `get_impact_radius_tool` | Understanding blast radius of a change |
| `get_affected_flows_tool` | Finding which execution paths are impacted |
| `query_graph_tool` | Tracing callers, callees, imports, tests, dependencies |
| `semantic_search_nodes_tool` | Finding functions/classes by name or keyword |
| `get_architecture_overview_tool` | Understanding high-level codebase structure |
| `refactor_tool` | Planning renames, finding dead code |
### Workflow
1. The graph auto-updates on file changes (via hooks).
2. Use `detect_changes_tool` for code review.
3. Use `get_affected_flows_tool` to understand impact.
4. Use `query_graph_tool` pattern="tests_for" to check coverage.
<!-- /code-review-graph MCP tools -->
+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"]
+151 -52
View File
@@ -1,77 +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())
_ROLE_WHITELIST = {"user", "group_admin", "owner"}
_authenticator = None # callable() -> {"user_id":..., "role":...} | None, injected by host
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 set_authenticator(fn):
"""Install a server-side authenticator used to derive the querying tenant.
class ApiSecurityError(PermissionError):
def __init__(self, status_code: int, code: str):
super().__init__(code)
self.status_code, self.code = status_code, code
The callable returns the authenticated principal (dict with "user_id" and
a whitelisted "role") or None for anonymous. The client-supplied role and
user/group ids are NOT trusted to escalate scope.
"""
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 _principal():
if _authenticator is not None:
p = _authenticator()
if isinstance(p, dict) and p.get("role") in _ROLE_WHITELIST:
return p
return None
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 record_agent_execution(payload: dict[str, Any]):
record = AgentExecutionRecord(**payload)
return {"status": "ok", "record": store.append(record)}
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 get_agent_observability(user_id: str | None = None, group_id: str | None = None, role: str = "user"):
# The authenticated principal (tenant) is authoritative; the request params
# role/user_id/group_id can never choose owner or another tenant.
principal = _principal()
pr_user = principal.get("user_id") if principal else None
pr_role = principal.get("role", "user") if principal else "user"
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)
if pr_role == "owner":
# Server-verified owner may browse a requested scope.
q_user, q_group, q_role = user_id, group_id, "owner"
summary_scope = None
elif pr_user:
# Authenticated non-owner: strictly scoped to own tenant.
q_user, q_group, q_role = pr_user, None, "user"
summary_scope = pr_user
else:
# Anonymous: nothing scoped to them.
q_user, q_group, q_role = None, None, "user"
summary_scope = None
records = store.query(q_user, q_group, q_role)
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(summary_scope),
"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
@@ -79,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"]
@@ -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)
]
+36 -7
View File
@@ -6,7 +6,7 @@ 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
from dataclasses import dataclass, replace
from contextlib import contextmanager
from datetime import datetime,timezone
import base64
@@ -18,7 +18,7 @@ import time
from pathlib import Path
from typing import Mapping
from task_plane.models import TaskPlan,WorkerRegistration,ProbeEvidence,ProbeOutcome,Budget
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
@@ -69,6 +69,7 @@ class BaselineService:
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'}:
@@ -379,11 +380,39 @@ class BaselineService:
def _close_task(self,task,claim,envelope):
if not self._receipt_valid(task,envelope): raise ServiceError('RESULT_ATTESTATION_INVALID',403)
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')
self.store.complete_claim(task.task_id,claim.claim_id,'COMPLETED',envelope,
evidence_entries=[('diagnostic',envelope,claim.worker_identity)])
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)
@@ -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),
}
+17 -6
View File
@@ -158,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')
@@ -206,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:
@@ -254,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():
@@ -284,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/'):
@@ -835,7 +845,7 @@ 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
@@ -942,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()})
@@ -1080,6 +1090,7 @@ def api_spec():
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()
+36 -1
View File
@@ -216,6 +216,7 @@ class ControlPlaneReleaseManifest:
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 {
@@ -229,6 +230,35 @@ class ControlPlaneReleaseManifest:
"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)
@@ -254,7 +284,12 @@ class ControlPlaneReleaseManifest:
)
if not valid:
raise RuntimeDriftError("CONTROL_PLANE_RUNTIME_DRIFT")
return {**self.as_dict(), "drift_status": "PASS"}
# 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:
@@ -39,6 +39,7 @@ from aios_governed_task_bootstrap import (
)
from governed_production_release import build_executor_config
from aios_baseline.runtime_contract import evaluate_deployment
from component_runtime_identity import evaluate_component_runtime_identity
MAX_BODY = 2 * 1024 * 1024
TaskPrincipalVerifier = Callable[
@@ -104,6 +105,10 @@ class RuntimeConfig:
health_url: str = "http://127.0.0.1:18927/health"
release_plan_path: Path | None = None
repository_discovery_cursor_secret: str | bytes | None = None
component_manifest_path: Path | None = None
component_receipt_path: Path | None = None
component_id: str | None = None
component_identity_comparison: Callable[[Mapping[str, Any]], Mapping[str, Any] | None] | None = None
def _safe(value: str) -> str:
@@ -236,6 +241,12 @@ class GovernedRuntime:
blockers.append("LEGACY_GLOBAL_WRITES_MUST_REMAIN_DISABLED")
if not task_principal_writes_enabled:
blockers.append("TASK_PRINCIPAL_WRITES_DISABLED")
identity = evaluate_component_runtime_identity(
component_id=self.config.component_id,
manifest_path=self.config.component_manifest_path,
receipt_path=self.config.component_receipt_path,
trusted_comparison=self.config.component_identity_comparison,
)
return {
"status": "active",
"health": 200,
@@ -246,7 +257,8 @@ class GovernedRuntime:
"runtime_path": str(self.config.state_root),
"artifact_sha256": os.environ.get("AIOS_CONTROL_PLANE_ARTIFACT_SHA256"),
"deployment_state": "STAGING",
"drift_status": "PASS",
"drift_status": identity["status"],
"component_runtime_identity": identity,
"repository": self.config.repository,
"repository_identity": self.config.repository,
"authoritative_git_dir": str(self.config.authoritative_git_dir),
@@ -1332,6 +1344,17 @@ def main(
"/var/lib/aios-gitops-runtime-anchor/baseline/current.json",
)),
repository_discovery_cursor_secret=discovery_cursor_secret,
component_manifest_path=(
Path(os.environ["AIOS_COMPONENT_MANIFEST_PATH"])
if os.environ.get("AIOS_COMPONENT_MANIFEST_PATH", "").strip()
else None
),
component_receipt_path=(
Path(os.environ["AIOS_COMPONENT_ARTIFACT_RECEIPT_PATH"])
if os.environ.get("AIOS_COMPONENT_ARTIFACT_RECEIPT_PATH", "").strip()
else None
),
component_id=os.environ.get("AIOS_COMPONENT_ID", "").strip() or None,
)
runtime = GovernedRuntime(config)
# Prefer a server-side TaskPrincipal provider recovered from the same
@@ -1342,21 +1365,39 @@ def main(
BridgeHandler.principal_verifier = task_principal_verifier
if task_admission_handlers is None:
admission_runtime = None
if os.environ.get("AIOS_TASK_ADMISSION_EPOCH_ROOT", "").strip():
from aios_task_admission_live_adapters import (
build_task_admission_runtime_from_environment,
source_activation = os.environ.get("AIOS_SOURCE_PUBLICATION_ACTIVATION_FILE", "").strip()
try:
if os.environ.get("AIOS_TASK_ADMISSION_EPOCH_ROOT", "").strip():
from aios_task_admission_live_adapters import (
build_task_admission_runtime_from_environment,
)
admission_runtime = build_task_admission_runtime_from_environment(
post_admission=runtime.project_admitted_task,
)
elif activation_raw and Path(activation_raw).is_file():
from aios_task_admission_live_adapters import (
build_task_admission_runtime_from_activation_file,
)
admission_runtime = build_task_admission_runtime_from_activation_file(
Path(activation_raw),
post_admission=runtime.project_admitted_task,
)
except TaskAdmissionError as exc:
if not source_activation or str(exc) != "TASK_ADMISSION_EPOCH_EXPIRED":
raise
from source_publication_runtime import expired_admission_runtime
admission_runtime = expired_admission_runtime()
if source_activation:
from source_publication_runtime import (
AdmissionRuntimeRouter, build_source_runtime_from_activation_file,
)
admission_runtime = build_task_admission_runtime_from_environment(
post_admission=runtime.project_admitted_task,
)
elif activation_raw and Path(activation_raw).is_file():
from aios_task_admission_live_adapters import (
build_task_admission_runtime_from_activation_file,
)
admission_runtime = build_task_admission_runtime_from_activation_file(
Path(activation_raw),
post_admission=runtime.project_admitted_task,
if admission_runtime is None or task_principal_verifier is not None:
raise RuntimeError("SOURCE_ACTIVATION_ORDINARY_RUNTIME_REQUIRED")
source_runtime = build_source_runtime_from_activation_file(
Path(source_activation), post_admission=runtime.project_admitted_task,
ordinary_activation_file=Path(activation_raw),
)
admission_runtime = AdmissionRuntimeRouter(admission_runtime, source_runtime).bundle()
if admission_runtime is not None:
task_admission_handlers = admission_runtime.handlers
if BridgeHandler.principal_verifier is None:
@@ -11,10 +11,32 @@ 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 all(self.read_plane.values()) and all(self.write_plane.values()) and all(self.release_plane.values())
return self._readiness_reason() == "READY"
def as_dict(self) -> dict[str, object]:
return {
@@ -22,4 +44,5 @@ class RuntimeStatus:
"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(),
}
+39 -5
View File
@@ -1,6 +1,6 @@
#!/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
@@ -16,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},
@@ -68,6 +87,7 @@ def token_identity(token: str):
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
@@ -85,7 +105,9 @@ def token_identity(token: str):
return None
def valid_token(token: str) -> bool:
if DEV_ALLOW: return True
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)
@@ -163,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();
@@ -217,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'
@@ -292,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()
+6 -1
View File
@@ -486,8 +486,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():
@@ -1303,6 +1307,7 @@ def api_spec():
def main():
require_isolated_dev_profile(HOST)
print(f'AIOS ChatGPT GitOps adapter listening on {HOST}:{PORT}')
ThreadingHTTPServer((HOST,PORT), H).serve_forever()
if __name__ == '__main__': main()
@@ -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)
@@ -51,15 +51,30 @@ AUTHORIZATION_BUNDLE_SCHEMA = "aios.owner-authorization-bundle.v1"
MACHINE_CAPABILITY_SCHEMA = "aios.machine-derived-capability.v1"
BOOTSTRAP_STATE_SCHEMA = "aios.single-host-bootstrap-state.v1"
OWNER_EPOCH_SCHEMA = "aios.owner-delegation-epoch.v2"
SOURCE_PUBLICATION_POLICY_SCHEMA = "aios.source-publication-policy.v1"
SOURCE_PUBLICATION_POLICY_ID = "C5_SOURCE_PUBLICATION_ONLY"
SOURCE_PUBLICATION_CAPABILITIES = frozenset(
{
"agent-task:control",
"governed.task.create",
"governed.write.files",
"task.principal.issue",
"writer.lease",
}
)
DEFAULT_REGISTRY_PATH = Path("/etc/aios/single-host-signer-registry.json")
DEFAULT_OWNER_SIGNER_ID = "local-owner-authority"
DEFAULT_MACHINE_SIGNER_ID = "machine-operational"
_HEX64 = re.compile(r"^[0-9a-f]{64}$")
_HEX40 = re.compile(r"^[0-9a-f]{40}$")
_CAP = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{1,127}$")
_ID = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:@/-]{2,255}$")
_RISK = {"low": 0, "medium": 1, "high": 2}
# ``engineering`` is deliberately between medium and high. Adding this rank
# does not raise any configured ceiling; existing policy files keep their
# existing value (the normal local-owner policy is ``medium``).
_RISK = {"low": 0, "medium": 1, "engineering": 2, "high": 3}
_MAX_JSON = 128 * 1024
_MAX_KEY = 64 * 1024
_MAGIC = b"SSHSIG"
@@ -691,6 +706,30 @@ class OwnerAuthorizationBundle:
return asdict(self)
@dataclass(frozen=True, slots=True)
class SourcePublicationPolicyRequest:
"""One exact, source-only unattended publication authorization request."""
policy_id: str
repository: str
authority_base_oid: str
source_oid: str
source_tree_oid: str
target_agent: str
target_branch: str
generation: int
execution_digest: str
exact_files: tuple[str, ...]
nonce: str
ttl_seconds: int
risk: str = "engineering"
production_impact: str = "none"
renewal: str = "none"
def __post_init__(self) -> None:
object.__setattr__(self, "exact_files", tuple(self.exact_files))
@dataclass(frozen=True, slots=True)
class MachineCapabilityRequest:
capability_id: str
@@ -755,20 +794,28 @@ class LocalOwnerAuthority(LocalFileSigner):
def __init__(
self,
key_path: Path,
key_path: Path | None,
policy: SingleHostOwnerPolicy,
*,
require_root_owner: bool = True,
clock: Any = time.time,
registered_signer: Any | None = None,
) -> None:
super().__init__(
key_path,
key_path or Path("/dev/null"),
signer_id=DEFAULT_OWNER_SIGNER_ID,
namespace=OWNER_NAMESPACE,
require_root_owner=require_root_owner,
)
self.policy = policy
self.clock = clock
self.registered_signer = registered_signer
@property
def fingerprint(self) -> str:
if self.registered_signer is not None:
return str(self.registered_signer.fingerprint)
return super().fingerprint
@staticmethod
def _prefix_narrows(requested: str, allowed: Sequence[str]) -> bool:
@@ -831,6 +878,111 @@ class LocalOwnerAuthority(LocalFileSigner):
value["canonical_payload_digest"] = sha256_json(value)
return self._sign_canonical(value)
def _sign_canonical(self, value: Mapping[str, Any]) -> SignedArtifact:
if self.registered_signer is None:
return super()._sign_canonical(value)
payload = canonical_json_bytes(dict(value))
signature = self.registered_signer._sign_canonical_payload(
self.namespace, payload
)
return SignedArtifact(
payload=dict(value),
canonical_payload=payload,
signature=signature,
key_id=self.fingerprint,
namespace=self.namespace,
)
def issue_source_publication_policy(
self, request: SourcePublicationPolicyRequest
) -> SignedArtifact:
"""Sign one exact C5 source-publication policy.
This is an issuance operation only. It cannot change the configured
owner policy, its ceiling, branch/file allowlists, or trust domain.
"""
now = int(self.clock())
files = tuple(request.exact_files)
def source_scope_narrows(value: str, allowed: Sequence[str]) -> bool:
base = value.rstrip("/")
return any(
base == prefix.rstrip("/")
or base.startswith(prefix.rstrip("/") + "/")
for prefix in allowed
)
if (
request.policy_id != SOURCE_PUBLICATION_POLICY_ID
or request.repository != self.policy.repository
or not _HEX40.fullmatch(request.authority_base_oid)
or not _HEX40.fullmatch(request.source_oid)
or not _HEX40.fullmatch(request.source_tree_oid)
or not _ID.fullmatch(request.target_agent)
or not request.target_agent.startswith("agent/")
or not source_scope_narrows(
request.target_branch, self.policy.allowed_branch_prefixes
)
or not request.target_branch.startswith("agent/")
or "\\" in request.target_branch
or ".." in PurePosixPath(request.target_branch).parts
or request.target_branch.endswith(("/", "."))
or type(request.generation) is not int
or request.generation < 1
or not _HEX64.fullmatch(request.execution_digest)
or not files
or any(
not item
or PurePosixPath(item).is_absolute()
or "\\" in item
or ".." in PurePosixPath(item).parts
or item.startswith(".git")
or not source_scope_narrows(item, self.policy.allowed_file_prefixes)
for item in files
)
or not SOURCE_PUBLICATION_CAPABILITIES.issubset(self.policy.allowed_capabilities)
or len(set(files)) != len(files)
or not _ID.fullmatch(request.nonce)
or request.risk != "engineering"
or request.risk not in _RISK
or _RISK[request.risk] > _RISK[self.policy.risk_ceiling]
or request.production_impact != "none"
or request.renewal != "none"
or type(request.ttl_seconds) is not int
or not 1 <= request.ttl_seconds <= 3600
or request.ttl_seconds > self.policy.maximum_epoch_ttl_seconds
):
_fail("SOURCE_PUBLICATION_REQUEST_WIDENS_POLICY")
value: dict[str, Any] = {
"schema": SOURCE_PUBLICATION_POLICY_SCHEMA,
"policy_id": SOURCE_PUBLICATION_POLICY_ID,
"owner_identity": OWNER_IDENTITY,
"repository": request.repository,
"trust_domain": self.policy.trust_domain,
"policy_digest": self.policy.digest,
"authority_base_oid": request.authority_base_oid,
"source_oid": request.source_oid,
"source_tree_oid": request.source_tree_oid,
"target_agent": request.target_agent,
"target_branch": request.target_branch,
"generation": request.generation,
"execution_digest": request.execution_digest,
"exact_files": list(files),
"capabilities": sorted(SOURCE_PUBLICATION_CAPABILITIES),
"risk": "engineering",
"production_impact": "none",
"renewal": "none",
"nonce": request.nonce,
"issued_at": now,
"not_before": max(0, now - 30),
"expires_at": now + request.ttl_seconds,
"status": "ACTIVE",
"signature_namespace": OWNER_NAMESPACE,
"signature_key_id": self.fingerprint,
}
value["canonical_payload_digest"] = sha256_json(value)
return self._sign_canonical(value)
issue_source_only_policy = issue_source_publication_policy
def issue_authorization_bundle(
self,
*,
@@ -35,6 +35,8 @@ from typing import Any, Callable, Mapping, Protocol, Sequence
SCHEMA_ENVELOPE = "aios.task-admission-envelope.v1"
SCHEMA_GRANT = "aios.task-admission-grant.v1"
SCHEMA_EPOCH = "aios.owner-delegation-epoch.v1"
SCHEMA_EPOCH_V2 = "aios.owner-delegation-epoch.v2"
SUPPORTED_EPOCH_SCHEMAS = frozenset({SCHEMA_EPOCH, SCHEMA_EPOCH_V2})
SCHEMA_RECEIPT = "aios.task-admission-receipt.v1"
OID_RE = re.compile(r"^[0-9a-f]{40}$")
@@ -142,9 +144,30 @@ class OwnerDelegationEpoch:
revocation_epoch: int
policy_digest: str
schema: str = SCHEMA_EPOCH
production_scope: str | None = None
destructive_policy: str | None = None
# Provenance retained by the consumer. These fields are populated by the
# signed-directory provider for v2 (and left at their v1-compatible
# defaults when reading legacy epochs); they are never used to translate or
# re-sign the original artifact.
status: str = ""
signature_namespace: str = ""
signature_key_id: str = ""
canonical_payload_digest: str = ""
canonical_payload: bytes = b""
@property
def issuer(self) -> str:
"""The signed epoch issuer, retained under the legacy identity name."""
return self.owner_identity
@property
def canonical_payload_bytes(self) -> bytes:
return self.canonical_payload
def as_public_dict(self) -> dict[str, Any]:
value = asdict(self)
value.pop("canonical_payload", None)
value["allowed_capabilities"] = sorted(self.allowed_capabilities)
value["allowed_branch_prefixes"] = list(self.allowed_branch_prefixes)
value["allowed_file_prefixes"] = list(self.allowed_file_prefixes)
@@ -309,7 +332,7 @@ def _normalize_capabilities(
def _risk_rank(value: str) -> int:
ranks = {"low": 0, "medium": 1, "high": 2}
ranks = {"low": 0, "medium": 1, "engineering": 2, "high": 3}
try:
return ranks[value]
except KeyError:
@@ -838,7 +861,7 @@ class TaskAdmissionBootstrapService:
now: int,
) -> OwnerDelegationEpoch:
epoch = self.authorization.verify_delegation_epoch(epoch_id)
if epoch.schema != SCHEMA_EPOCH:
if epoch.schema not in SUPPORTED_EPOCH_SCHEMAS:
raise TaskAdmissionError("TASK_ADMISSION_EPOCH_SCHEMA_INVALID", 403)
if epoch.epoch_id != epoch_id:
raise TaskAdmissionError("TASK_ADMISSION_EPOCH_ID_MISMATCH", 403)
@@ -26,6 +26,8 @@ import urllib.request
from aios_task_admission_bootstrap import (
OwnerDelegationEpoch,
SCHEMA_EPOCH,
SCHEMA_EPOCH_V2,
TaskAdmissionError,
TaskAdmissionGrant,
canonical_json_bytes,
@@ -44,6 +46,19 @@ from aios_gitops_job_identity import (
OWNER_NAMESPACE = "aios-owner-approval-v1"
OWNER_SIGNER_IDENTITY = "aios-task-grant-signer"
_EPOCH_V1_FIELDS = frozenset({
"epoch_id", "owner_identity", "trust_domain", "repository",
"allowed_branch_prefixes", "allowed_capabilities", "allowed_file_prefixes",
"risk_ceiling", "issued_at", "not_before", "expires_at",
"revocation_epoch", "policy_digest", "schema",
})
_EPOCH_V2_FIELDS = frozenset({
*_EPOCH_V1_FIELDS,
"business_goal", "production_scope", "destructive_policy",
"signature_namespace", "signature_key_id", "canonical_payload_digest",
"status",
})
_SSH_FINGERPRINT_RE = re.compile(r"^SHA256:[A-Za-z0-9+/]{43}$")
class OfficialTaskTransport(Protocol):
@@ -463,6 +478,7 @@ class TaskAdmissionPrincipalProvider:
class TaskAdmissionRuntimeBundle:
handlers: Mapping[str, Any]
principal_verifier: TaskAdmissionPrincipalProvider
service: Any | None = None
class TaskAdmissionHostError(RuntimeError):
@@ -622,11 +638,15 @@ class SignedEpochDirectoryProvider:
"TASK_ADMISSION_EPOCH_CANONICAL_MISMATCH",
403,
)
if value.get("schema") != "aios.owner-delegation-epoch.v1":
schema = value.get("schema")
if schema not in {SCHEMA_EPOCH, SCHEMA_EPOCH_V2}:
raise TaskAdmissionError(
"TASK_ADMISSION_EPOCH_SCHEMA_INVALID",
403,
)
expected_fields = _EPOCH_V1_FIELDS if schema == SCHEMA_EPOCH else _EPOCH_V2_FIELDS
if set(value) != expected_fields:
raise TaskAdmissionError("TASK_ADMISSION_EPOCH_FIELDS_INVALID", 403)
if value.get("epoch_id") != epoch_id:
raise TaskAdmissionError(
"TASK_ADMISSION_EPOCH_ID_MISMATCH",
@@ -642,7 +662,34 @@ class SignedEpochDirectoryProvider:
if epoch_id in self._revoked_ids():
raise TaskAdmissionError("TASK_ADMISSION_EPOCH_REVOKED", 403)
# v2 is a producer-authored envelope. Validate its self-digest and
# signature binding before the cryptographic verifier sees the exact
# bytes from disk. The digest is over the payload with only the digest
# member removed; no schema translation is permitted.
if schema == SCHEMA_EPOCH_V2:
if (
value.get("status") != "ACTIVE"
or not isinstance(value.get("business_goal"), str)
or not value["business_goal"]
or not isinstance(value.get("production_scope"), str)
or not value["production_scope"]
or not isinstance(value.get("destructive_policy"), str)
or value["destructive_policy"] not in {"forbidden", "rollback-only"}
or value.get("signature_namespace") != OWNER_NAMESPACE
or not isinstance(value.get("signature_key_id"), str)
or not _SSH_FINGERPRINT_RE.fullmatch(value["signature_key_id"])
or not isinstance(value.get("canonical_payload_digest"), str)
or not re.fullmatch(r"[0-9a-f]{64}", value["canonical_payload_digest"])
):
raise TaskAdmissionError("TASK_ADMISSION_EPOCH_V2_BINDING_INVALID", 403)
digest_value = dict(value)
supplied_digest = digest_value.pop("canonical_payload_digest", None)
if sha256_json(digest_value) != supplied_digest:
raise TaskAdmissionError("TASK_ADMISSION_EPOCH_PAYLOAD_DIGEST_INVALID", 403)
key_id = key_id_file.read_text(encoding="utf-8").strip()
if schema == SCHEMA_EPOCH_V2 and key_id != value["signature_key_id"]:
raise TaskAdmissionError("TASK_ADMISSION_EPOCH_KEY_BINDING_INVALID", 403)
armored = signature.read_text(encoding="utf-8")
if self.verifier.verify(
namespace=OWNER_NAMESPACE,
@@ -659,6 +706,23 @@ class SignedEpochDirectoryProvider:
not_before = value.get("not_before")
expires_at = value.get("expires_at")
if (
not isinstance(value.get("epoch_id"), str)
or not value["epoch_id"]
or not isinstance(value.get("owner_identity"), str)
or not value["owner_identity"]
or not isinstance(value.get("trust_domain"), str)
or not value["trust_domain"]
or not isinstance(value.get("repository"), str)
or not value["repository"]
or not isinstance(value.get("risk_ceiling"), str)
or value["risk_ceiling"] not in {"low", "medium", "engineering", "high"}
or not isinstance(value.get("issued_at"), int)
or isinstance(value.get("issued_at"), bool)
or not isinstance(value.get("revocation_epoch"), int)
or isinstance(value.get("revocation_epoch"), bool)
or not isinstance(value.get("policy_digest"), str)
or not value["policy_digest"]
or
not isinstance(not_before, int)
or isinstance(not_before, bool)
or not isinstance(expires_at, int)
@@ -672,13 +736,16 @@ class SignedEpochDirectoryProvider:
files = value.get("allowed_file_prefixes")
if (
not isinstance(branches, list)
or not branches
or not all(isinstance(item, str) and item for item in branches)
or not isinstance(capabilities, list)
or not capabilities
or not all(
isinstance(item, str) and item
for item in capabilities
)
or not isinstance(files, list)
or not files
or not all(isinstance(item, str) and item for item in files)
):
raise TaskAdmissionError(
@@ -701,6 +768,19 @@ class SignedEpochDirectoryProvider:
revocation_epoch=int(value["revocation_epoch"]),
policy_digest=str(value["policy_digest"]),
schema=str(value["schema"]),
production_scope=(
str(value["production_scope"])
if schema == SCHEMA_EPOCH_V2 else None
),
destructive_policy=(
str(value["destructive_policy"])
if schema == SCHEMA_EPOCH_V2 else None
),
status=str(value.get("status", "")),
signature_namespace=str(value.get("signature_namespace", "")),
signature_key_id=str(value.get("signature_key_id", "")),
canonical_payload_digest=str(value.get("canonical_payload_digest", "")),
canonical_payload=payload,
)
@@ -915,6 +995,7 @@ def _build_task_admission_runtime(
task_port=task_port,
epoch_id=epoch.epoch_id,
),
service=service,
)
@@ -27,6 +27,7 @@ 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}$")
@@ -199,6 +200,31 @@ def _verify_source(row: sqlite3.Row, d: Mapping[str, Any], identity_digest: Call
_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)
@@ -251,6 +277,8 @@ class TaskLineageSupersessionController:
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"]),
@@ -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),
)
@@ -24,6 +24,19 @@ class BuilderError(RuntimeError):
"""A stable, non-sensitive error code returned by the builder boundary."""
C5_INTEGRATION_CONTRACT_FIELDS = frozenset({
"integration_source_commit", "integration_tree_oid",
"publication_source_commit", "publication_tree_oid",
"publication_plan_sha256", "repair_source_commit", "repair_tree_oid",
})
C5_INTEGRATION_FILES = {
"active-runtime/app/aios_capability_contract.py": ("publication_source_commit", "67d8a3a296110e20a4ea59863cd7982b6bdada71abd457c0f7cec480b06d278a"),
"tests/test_v2r2c5_capability_contract.py": ("publication_source_commit", "f91af641528ea9a625ca45a46b6c26f5dba568e6b69cba819ca33ab5cba28de3"),
"active-runtime/app/aios_baseline/service.py": ("repair_source_commit", "3c3918aec5503947b48ef4c73e34cac8f0d33fdfb99cfcd5ca8af699b663da72"),
"tests/baseline_v1_1/test_loop.py": ("repair_source_commit", "f867e92b9ec9b4dff248e6ca38280848458a535c610dc6d9fea2b270c29b664a"),
}
def _canonical_json(value: Mapping[str, Any]) -> bytes:
return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=True).encode("utf-8")
@@ -135,6 +148,7 @@ class TrustedCandidateBuilderConfig:
policy_sha256: str
source_admission_resolver: Callable[[str, str | None, str], Mapping[str, str]] | None = None
migration_plan_resolver: Callable[[str, str, str], Any] | None = None
c5_contract: Mapping[str, str] | None = None
@classmethod
def from_policy(cls, policy: Mapping[str, Any]) -> "TrustedCandidateBuilderConfig":
@@ -145,16 +159,52 @@ class TrustedCandidateBuilderConfig:
"allowed_peer_runtime_prefix", "allowed_peer_runtime_suffix",
"required_checks", "evidence_layout", "recipes", "governed_recipes",
}
if set(policy) != required or policy.get("schema") != "aios.trusted-candidate-builder-policy.v1":
is_integration = policy.get("schema") == "aios.trusted-candidate-builder-policy.c5-integration.v1"
is_c5 = is_integration or policy.get("schema") == "aios.trusted-candidate-builder-policy.c5.v1"
if set(policy) != (required | {"c5_contract"} if is_c5 else required) or policy.get("schema") not in {"aios.trusted-candidate-builder-policy.v1", "aios.trusted-candidate-builder-policy.c5.v1", "aios.trusted-candidate-builder-policy.c5-integration.v1"}:
raise BuilderError("POLICY_SCHEMA_INVALID")
recipes = policy.get("recipes")
if not isinstance(recipes, Mapping) or set(recipes) != {"V2R2C2", "V2R2C3"}:
expected_recipes = {"V2R2C2", "V2R2C3", "V2R2C5"} if is_c5 else {"V2R2C2", "V2R2C3"}
if not isinstance(recipes, Mapping) or set(recipes) != expected_recipes:
raise BuilderError("POLICY_RECIPES_INVALID")
c2, c3 = recipes["V2R2C2"], recipes["V2R2C3"]
if not isinstance(c2, Mapping) or set(c2) != {"mode", "task_id", "candidate_id", "base_oid", "source_commit", "version", "purpose"} or c2.get("mode") != "fixed":
raise BuilderError("POLICY_V2R2C2_INVALID")
if not isinstance(c3, Mapping) or set(c3) != {"mode", "derives_from", "version", "purpose"} or c3.get("mode") != "governed-derived-only" or c3.get("derives_from") != "V2R2C2":
raise BuilderError("POLICY_V2R2C3_INVALID")
c5_contract = None
if is_c5:
c5 = recipes["V2R2C5"]
expected_mode = "governed-source-integration-only" if is_integration else "governed-source-admission-only"
if not isinstance(c5, Mapping) or set(c5) != {"mode", "version", "purpose"} or c5.get("mode") != expected_mode or c5.get("version") != "V2R2C5" or c5.get("purpose") != "AIOS_TRUSTED_RELEASE_BUILDER":
raise BuilderError("POLICY_V2R2C5_INVALID")
required_c5 = {"task_id", "authority_base_oid", "accepted_base_oid", "rollback_version", "rollback_target_oid", "rollback_artifact_sha256"}
if is_integration:
required_c5 |= C5_INTEGRATION_CONTRACT_FIELDS
value = policy.get("c5_contract")
if not isinstance(value, Mapping) or set(value) != required_c5:
raise BuilderError("POLICY_V2R2C5_CONTRACT_INVALID")
if any(not isinstance(v, str) for v in value.values()):
raise BuilderError("POLICY_V2R2C5_CONTRACT_INVALID")
c5_contract = dict(value)
oid_fields = {"authority_base_oid", "accepted_base_oid", "rollback_target_oid"}
if is_integration:
oid_fields |= {
"integration_source_commit", "integration_tree_oid",
"publication_source_commit", "publication_tree_oid",
"repair_source_commit", "repair_tree_oid",
}
sha_fields = {"rollback_artifact_sha256"}
if is_integration:
sha_fields.add("publication_plan_sha256")
if (
not c5_contract["task_id"]
or c5_contract["rollback_version"] != "V2R2C4"
or c5_contract["accepted_base_oid"] != c5_contract["rollback_target_oid"]
or any(len(c5_contract[name]) != 40 or any(ch not in "0123456789abcdef" for ch in c5_contract[name]) for name in oid_fields)
or any(len(c5_contract[name]) != 64 or any(ch not in "0123456789abcdef" for ch in c5_contract[name]) for name in sha_fields)
):
raise BuilderError("POLICY_V2R2C5_CONTRACT_INVALID")
if (
policy["repository"] != "aios/aios"
or policy["operation"] != "BuildTrustedCandidateV1"
@@ -210,6 +260,7 @@ class TrustedCandidateBuilderConfig:
recipes={name: dict(value) for name, value in recipes.items()},
governed_recipes={name: dict(value) for name, value in policy["governed_recipes"].items()} if isinstance(policy["governed_recipes"], Mapping) else _invalid_governed_recipes(),
policy_sha256=hashlib.sha256(_canonical_json(dict(policy))).hexdigest(),
c5_contract=c5_contract,
)
def fixed_recipe(self, name: str) -> Mapping[str, str]:
@@ -231,6 +282,36 @@ class TrustedCandidateBuilderConfig:
def resolve_recipe(self, version: str, candidate_id: str | None, source_commit: str) -> Mapping[str, str]:
recipe = self.recipes.get(version)
if recipe is not None:
if recipe.get("mode") in {"governed-source-admission-only", "governed-source-integration-only"}:
if version != "V2R2C5" or self.c5_contract is None:
raise BuilderError("SOURCE_ADMISSION_REQUIRED")
resolver = self.source_admission_resolver
if resolver is None:
raise BuilderError("SOURCE_ADMISSION_REQUIRED")
try:
admitted = dict(resolver(version, candidate_id, source_commit))
except Exception as exc:
raise BuilderError("SOURCE_ADMISSION_REQUIRED") from exc
required = {"mode", "task_id", "candidate_id", "base_oid", "source_commit", "version", "purpose", "tree_oid"}
if set(admitted) != required or admitted.get("mode") != "governed-source-admission" or admitted.get("version") != version or admitted.get("source_commit") != source_commit or admitted.get("purpose") != "AIOS_TRUSTED_RELEASE_BUILDER":
raise BuilderError("SOURCE_ADMISSION_INVALID")
if (not isinstance(admitted.get("candidate_id"), str) or not admitted.get("candidate_id")
or admitted.get("task_id") != self.c5_contract["task_id"]
or admitted.get("base_oid") != self.c5_contract["authority_base_oid"]):
raise BuilderError("SOURCE_ADMISSION_BINDING_INVALID")
effective = str(candidate_id or admitted["candidate_id"])
if effective != admitted["candidate_id"]:
raise BuilderError("REQUEST_RELEASE_BINDING_INVALID")
for name in ("base_oid", "source_commit", "tree_oid"):
value = admitted.get(name)
if not isinstance(value, str) or len(value) != 40 or any(char not in "0123456789abcdef" for char in value.lower()):
raise BuilderError("SOURCE_ADMISSION_INVALID")
if recipe.get("mode") == "governed-source-integration-only" and (
source_commit != self.c5_contract["integration_source_commit"]
or admitted["tree_oid"] != self.c5_contract["integration_tree_oid"]
):
raise BuilderError("SOURCE_INTEGRATION_BINDING_INVALID")
return {**admitted, **self.c5_contract}
resolved = self.fixed_recipe(version)
# Fixed/governed-derived policy recipes remain immutable. A
# source admission can never override one of these identities.
@@ -247,6 +328,8 @@ class TrustedCandidateBuilderConfig:
raise BuilderError("REQUEST_RELEASE_BINDING_INVALID")
return resolved
if self.c5_contract is not None:
raise BuilderError("RECIPE_UNKNOWN")
resolver = self.source_admission_resolver
if resolver is None:
raise BuilderError("RECIPE_UNKNOWN")
@@ -263,8 +346,9 @@ class TrustedCandidateBuilderConfig:
"version",
"purpose",
}
allowed_fields = (required, required | {"tree_oid"}) if version == "V2R2C5" else (required,)
if (
set(admitted) != required
set(admitted) not in allowed_fields
or admitted.get("mode") != "governed-source-admission"
or admitted.get("version") != version
or admitted.get("source_commit") != source_commit
@@ -275,7 +359,7 @@ class TrustedCandidateBuilderConfig:
effective_candidate_id = str(candidate_id or admitted["candidate_id"])
if effective_candidate_id != admitted["candidate_id"]:
raise BuilderError("REQUEST_RELEASE_BINDING_INVALID")
for name in ("base_oid", "source_commit"):
for name in ("base_oid", "source_commit", *(["tree_oid"] if "tree_oid" in admitted else [])):
value = admitted.get(name)
if (
not isinstance(value, str)
@@ -395,7 +479,44 @@ class ServerOwnedEvidenceSource:
raise BuilderError("COLLECTOR_EVIDENCE_INVALID")
return value
def _validate_integration_source(self, recipe: Mapping[str, str]) -> None:
if "integration_source_commit" not in recipe:
return
def read(*arguments: str, binary: bool = False):
try:
result = self._git_runner(
["git", f"--git-dir={self._authority_git_dir}", *arguments],
check=True, capture_output=True, text=not binary, timeout=15,
env={"PATH": os.environ.get("PATH", "")},
)
return result.stdout if binary else result.stdout.strip()
except (OSError, subprocess.SubprocessError) as exc:
raise BuilderError("COLLECTOR_INTEGRATION_UNAVAILABLE") from exc
source = recipe["source_commit"]
if source != recipe["integration_source_commit"]:
raise BuilderError("COLLECTOR_INTEGRATION_MISMATCH")
for commit_field, tree_field in (
("integration_source_commit", "integration_tree_oid"),
("publication_source_commit", "publication_tree_oid"),
("repair_source_commit", "repair_tree_oid"),
):
if read("rev-parse", f"{recipe[commit_field]}^{{tree}}") != recipe[tree_field]:
raise BuilderError("COLLECTOR_INTEGRATION_MISMATCH")
if read("show", "-s", "--format=%P", source) != recipe["authority_base_oid"]:
raise BuilderError("COLLECTOR_INTEGRATION_MISMATCH")
changed = read("diff", "--name-only", recipe["authority_base_oid"], source).splitlines()
if sorted(changed) != sorted(C5_INTEGRATION_FILES):
raise BuilderError("COLLECTOR_INTEGRATION_FILE_SET_MISMATCH")
for path, (component, digest) in C5_INTEGRATION_FILES.items():
integrated = read("show", f"{source}:{path}", binary=True)
original = read("show", f"{recipe[component]}:{path}", binary=True)
if integrated != original or hashlib.sha256(integrated).hexdigest() != digest:
raise BuilderError("COLLECTOR_INTEGRATION_BYTES_MISMATCH")
def __call__(self, recipe_name: str, recipe: Mapping[str, str]) -> Mapping[str, Any]:
self._validate_integration_source(recipe)
try:
tree_result = self._git_runner(
["git", f"--git-dir={self._authority_git_dir}", "rev-parse", f"{recipe['source_commit']}^{{tree}}"],
@@ -412,6 +533,16 @@ class ServerOwnedEvidenceSource:
raise BuilderError("COLLECTOR_EVIDENCE_UNAVAILABLE") from exc
if getattr(ancestry_result, "returncode", 1) != 0:
raise BuilderError("COLLECTOR_GIT_MISMATCH")
if recipe.get("tree_oid") is not None and recipe.get("tree_oid") != tree_oid:
raise BuilderError("COLLECTOR_GIT_MISMATCH")
if recipe.get("accepted_base_oid") is not None:
accepted_ancestry = self._git_runner(
["git", f"--git-dir={self._authority_git_dir}", "merge-base", "--is-ancestor", recipe["accepted_base_oid"], recipe["source_commit"]],
check=False, capture_output=True, text=True, timeout=15,
env={"PATH": os.environ.get("PATH", "")},
)
if getattr(accepted_ancestry, "returncode", 1) != 0:
raise BuilderError("COLLECTOR_GIT_MISMATCH")
receipt = self._json(self._regular_path(f"{recipe_name}/receipt.json"))
if set(receipt) != {"schema", "version", "candidate", "release_plan"}:
@@ -488,6 +619,13 @@ class ServerOwnedEvidenceSource:
or rollback.get("rollback_sha256") != deployment.get("rollback_sha256")
):
raise BuilderError("COLLECTOR_ROLLBACK_MISMATCH")
if recipe.get("rollback_version") is not None:
if deployment.get("rollback_target") != {
"version": recipe["rollback_version"],
"target_oid": recipe["rollback_target_oid"],
"artifact_sha256": recipe["rollback_artifact_sha256"],
}:
raise BuilderError("COLLECTOR_ROLLBACK_MISMATCH")
if (
digests["gitea"] != hashlib.sha256(_canonical_json(dict(gitea))).hexdigest()
or digests["rollback"] != hashlib.sha256(_canonical_json(dict(rollback))).hexdigest()
@@ -18,7 +18,10 @@ from __future__ import annotations
import argparse
import dataclasses
import fcntl
try:
import fcntl
except ModuleNotFoundError: # pragma: no cover - production host is POSIX
fcntl = None
import hashlib
import json
import os
@@ -376,6 +379,8 @@ class FileLock:
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)
@@ -388,7 +393,7 @@ class FileLock:
return self
def __exit__(self, exc_type, exc, tb) -> None:
if self.handle is not None:
if self.handle is not None and fcntl is not None:
fcntl.flock(self.handle.fileno(), fcntl.LOCK_UN)
self.handle.close()
+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
@@ -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
@@ -0,0 +1,379 @@
"""Concrete transport adapter for the governed source publication boundary.
The adapter composes the already existing live writer executor. It owns no
leases, TaskPrincipal, credentials, retry policy, or release state machine.
Those remain server side. ``authenticated_readback`` is deliberately an
injected port: a local commit response is not evidence of an authenticated
source/tree binding.
"""
from __future__ import annotations
from collections.abc import Callable, Mapping
import hashlib
import json
import re
from typing import Any, Protocol
from governed_unattended_release import SourcePublicationIntent
from task_plane.governed_writer_automation import GovernedWriterRequest
class GovernedPublicationError(RuntimeError):
"""Stable fail-closed publication error."""
def __init__(self, code: str):
self.code = code
super().__init__(code)
class GovernedWriterExecutor(Protocol):
def acquire(self, *, request: GovernedWriterRequest, job_id: str) -> Any: ...
def commit_and_readback(
self, *, session: Any, request: GovernedWriterRequest,
files: Mapping[str, bytes], expected_sha256: Mapping[str, str],
message: str,
) -> Mapping[str, Any]: ...
def submit_task_result(
self, *, session: Any, result: Mapping[str, Any], idempotency_key: str,
) -> Mapping[str, Any]: ...
class SourceFileReader(Protocol):
def __call__(self, intent: SourcePublicationIntent) -> Mapping[str, bytes]: ...
class AuthenticatedReadback(Protocol):
def __call__(
self, *, intent: SourcePublicationIntent, request: GovernedWriterRequest,
session: Any, commit_result: Mapping[str, Any],
) -> Mapping[str, Any]: ...
class AdmissionBindingReader(Protocol):
"""Read the server-side task/governed binding after admission."""
def __call__(
self, *, intent: SourcePublicationIntent,
admission_receipt: Mapping[str, Any],
) -> Mapping[str, Any]: ...
class GitTreeFileReader(Protocol):
"""Read bytes from an immutable committed tree object, never a workspace."""
def __call__(self, *, tree_oid: str, path: str) -> bytes: ...
class CommittedTreeSourceReader:
"""Adapt an authoritative git-object reader to the publication port."""
def __init__(self, reader: GitTreeFileReader) -> None:
if not callable(reader):
raise ValueError("git_object_reader_required")
self.reader = reader
def __call__(self, intent: SourcePublicationIntent) -> Mapping[str, bytes]:
return {
path: self.reader(tree_oid=intent.source_tree_oid, path=path)
for path in intent.file_allowlist
}
_OID = re.compile(r"^[0-9a-f]{40}$")
_SHA = re.compile(r"^[0-9a-f]{64}$")
_SAFE_CODE = re.compile(r"^(?:TASK_PRINCIPAL|EXECUTION_PLAN|GOVERNED)_[A-Z0-9_]{3,120}$")
_SAFE_OPERATION = re.compile(r"^[A-Za-z][A-Za-z0-9]{1,63}$")
def _oid(value: Any) -> str:
result = str(value or "").lower()
if not _OID.fullmatch(result):
raise GovernedPublicationError("PUBLICATION_OID_INVALID")
return result
def _sha(value: Any) -> str:
result = str(value or "").lower()
if not _SHA.fullmatch(result):
raise GovernedPublicationError("PUBLICATION_SHA_INVALID")
return result
def _canonical(value: Mapping[str, Any]) -> bytes:
try:
return json.dumps(
dict(value), sort_keys=True, separators=(",", ":"),
ensure_ascii=False, allow_nan=False,
).encode("utf-8")
except (TypeError, ValueError) as exc:
raise GovernedPublicationError("PUBLICATION_EVIDENCE_INVALID") from exc
class GovernedPublicationClient:
"""Run TaskLease -> TaskPrincipal -> WriterLease -> commit through ports.
``writer_executor`` is normally ``LiveGovernedWriterExecutor`` from
``task_plane.live_governed_writer_adapters``. ``source_files`` must return
the exact immutable bytes for the intent's allowlist; it is never allowed
to return a path or a mutable object. The authenticated readback port
must be backed by the authoritative provider/bridge and prove the final
source OID, tree OID, task, branch, base, generation, and execution digest.
"""
def __init__(
self,
*,
writer_executor: GovernedWriterExecutor,
source_files: SourceFileReader | None = None,
git_tree_file: GitTreeFileReader | None = None,
admission_binding: AdmissionBindingReader,
authenticated_readback: AuthenticatedReadback,
subject: str,
manifest_id: str,
runtime_fingerprint: str,
process_start_identity: str,
commit_message: str = "governed source publication",
diagnostic_sink: Callable[[Mapping[str, Any]], None] | None = None,
) -> None:
if (
writer_executor is None
or any(
not callable(getattr(writer_executor, name, None))
for name in (
"acquire", "commit_and_readback", "submit_task_result",
)
)
):
raise ValueError("writer_executor_required")
if source_files is not None:
raise ValueError("workspace_source_reader_forbidden")
if git_tree_file is None:
raise ValueError("git_object_source_reader_required")
if not callable(admission_binding) or not callable(authenticated_readback):
raise ValueError("authenticated_ports_required")
for name, value in (("subject", subject), ("manifest_id", manifest_id),
("runtime_fingerprint", runtime_fingerprint),
("process_start_identity", process_start_identity),
("commit_message", commit_message)):
if not isinstance(value, str) or not value:
raise ValueError(name + "_required")
self.writer_executor = writer_executor
self.source_files = None
self.git_tree_file = git_tree_file
self.admission_binding = admission_binding
self.authenticated_readback = authenticated_readback
self.subject = subject
self.manifest_id = manifest_id
self.runtime_fingerprint = runtime_fingerprint
self.process_start_identity = process_start_identity
self.commit_message = commit_message
if diagnostic_sink is not None and not callable(diagnostic_sink):
raise ValueError("diagnostic_sink_invalid")
self.diagnostic_sink = diagnostic_sink
def _record_uncertain_effect(self, *, exc: Exception, task_id: str,
stage: str) -> None:
"""Emit only allowlisted facts; never serialize an exception or body."""
if self.diagnostic_sink is None:
return
raw_status = getattr(exc, "status", None)
status = raw_status if type(raw_status) is int and 100 <= raw_status <= 599 else None
raw_code = getattr(exc, "error_code", None)
code = raw_code if isinstance(raw_code, str) and _SAFE_CODE.fullmatch(raw_code) else None
raw_operation = getattr(exc, "operation", None)
operation = (
raw_operation if isinstance(raw_operation, str)
and _SAFE_OPERATION.fullmatch(raw_operation) else stage
)
diagnostic = {
"task_id": task_id, "operation": operation,
"http_status": status,
"error_code": (
"transport_unknown" if status is None else (code or "issuer_error_unknown")
),
}
try:
self.diagnostic_sink(diagnostic)
except Exception:
# Diagnostic observation must never change the uncertainty policy.
pass
def _validate_admission(self, intent: SourcePublicationIntent, receipt: Mapping[str, Any]) -> Mapping[str, Any]:
if not isinstance(receipt, Mapping):
raise GovernedPublicationError("PUBLICATION_ADMISSION_INVALID")
for key, expected in (("task_id", intent.task_id), ("authority_base_oid", intent.authority_base_oid),
("target_branch", intent.target_branch), ("generation", intent.generation),
("execution_digest", intent.execution_digest)):
if receipt.get(key) != expected:
raise GovernedPublicationError("PUBLICATION_ADMISSION_BINDING_MISMATCH")
try:
binding = self.admission_binding(intent=intent, admission_receipt=receipt)
except Exception as exc:
raise GovernedPublicationError("PUBLICATION_ADMISSION_BINDING_UNAVAILABLE") from exc
if not isinstance(binding, Mapping):
raise GovernedPublicationError("PUBLICATION_ADMISSION_BINDING_INVALID")
expected = {
"task_id": intent.task_id, "authority_base_oid": intent.authority_base_oid,
"target_branch": intent.target_branch, "generation": intent.generation,
"execution_digest": intent.execution_digest,
}
if any(binding.get(key) != value for key, value in expected.items()):
raise GovernedPublicationError("PUBLICATION_ADMISSION_BINDING_MISMATCH")
job_id = binding.get("job_id")
if not isinstance(job_id, str) or not job_id:
raise GovernedPublicationError("PUBLICATION_JOB_BINDING_MISSING")
return dict(binding)
def _read_files(self, intent: SourcePublicationIntent) -> tuple[dict[str, bytes], dict[str, str]]:
try:
raw = {
path: self.git_tree_file(tree_oid=intent.source_tree_oid, path=path)
for path in intent.file_allowlist
}
except KeyError as exc:
raise GovernedPublicationError("PUBLICATION_FILE_SET_MISMATCH") from exc
except Exception as exc:
raise GovernedPublicationError("PUBLICATION_SOURCE_READ_FAILED") from exc
if not isinstance(raw, Mapping) or set(raw) != set(intent.file_allowlist):
raise GovernedPublicationError("PUBLICATION_FILE_SET_MISMATCH")
files: dict[str, bytes] = {}
hashes: dict[str, str] = {}
for path in intent.file_allowlist:
data = raw.get(path)
if isinstance(data, bytearray):
data = bytes(data)
if not isinstance(data, bytes):
raise GovernedPublicationError("PUBLICATION_FILE_BYTES_REQUIRED")
files[path] = bytes(data)
hashes[path] = hashlib.sha256(files[path]).hexdigest()
return files, hashes
def publish(self, intent: SourcePublicationIntent, admission_receipt: Mapping[str, Any]) -> dict[str, Any]:
if not isinstance(intent, SourcePublicationIntent):
raise GovernedPublicationError("PUBLICATION_INTENT_REQUIRED")
admission_binding = self._validate_admission(intent, admission_receipt)
job_id = str(admission_binding["job_id"])
files, hashes = self._read_files(intent)
request = GovernedWriterRequest(
subject=self.subject, task_id=intent.task_id, repository=intent.repository,
target_branch=intent.target_branch, risk=intent.risk, generation=intent.generation,
base_oid=intent.authority_base_oid,
required_capabilities=frozenset(intent.requested_capabilities),
file_allowlist=tuple(intent.file_allowlist),
plan_digest="sha256:" + intent.execution_digest,
manifest_id=self.manifest_id, runtime_fingerprint=self.runtime_fingerprint,
process_start_identity=self.process_start_identity,
)
stage = "acquireWriterLease"
try:
session = self.writer_executor.acquire(request=request, job_id=job_id)
stage = "commitFiles"
commit_result = self.writer_executor.commit_and_readback(
session=session, request=request, files=files,
expected_sha256=hashes, message=self.commit_message,
)
if not isinstance(commit_result, Mapping):
raise GovernedPublicationError("PUBLICATION_COMMIT_READBACK_INVALID")
readback = self.authenticated_readback(
intent=intent, request=request, session=session,
commit_result=commit_result,
)
except GovernedPublicationError:
raise
except Exception as exc:
# A transport exception after a mutation is indeterminate. Never
# resubmit it here; the caller must reconcile through the existing
# Job/recovery service.
self._record_uncertain_effect(exc=exc, task_id=intent.task_id, stage=stage)
raise GovernedPublicationError("PUBLICATION_EFFECT_UNKNOWN") from exc
if not isinstance(readback, Mapping) or readback.get("authenticated") is not True:
raise GovernedPublicationError("PUBLICATION_AUTHENTICATED_READBACK_REQUIRED")
required = {
"task_id": intent.task_id, "authority_base_oid": intent.authority_base_oid,
"target_branch": intent.target_branch, "generation": intent.generation,
"execution_digest": intent.execution_digest,
}
if any(readback.get(key) != value for key, value in required.items()):
raise GovernedPublicationError("PUBLICATION_READBACK_BINDING_MISMATCH")
for key in ("task_lease", "task_principal", "writer_lease"):
if not isinstance(readback.get(key), Mapping) or readback[key].get("task_id") != intent.task_id:
raise GovernedPublicationError("PUBLICATION_LEASE_CHAIN_MISSING")
source_oid = _oid(readback.get("source_oid"))
tree_oid = _oid(readback.get("tree_oid"))
nested_commit = readback.get("commit_receipt")
if nested_commit is not None and not isinstance(nested_commit, Mapping):
raise GovernedPublicationError("PUBLICATION_COMMIT_BINDING_MISMATCH")
commit_oid = _oid(readback.get("commit_oid") or (nested_commit or {}).get("commit_oid"))
if source_oid != commit_oid or tree_oid != intent.source_tree_oid:
raise GovernedPublicationError("PUBLICATION_SOURCE_TREE_BINDING_MISMATCH")
commit_receipt = readback.get("commit_receipt")
if not isinstance(commit_receipt, Mapping):
raise GovernedPublicationError("PUBLICATION_COMMIT_RECEIPT_REQUIRED")
# The live governed receipt binds commit_oid; the independently read
# Git tree is carried by authenticated readback. Preserve the original
# receipt instead of inventing a tree field absent from its schema.
if (commit_receipt.get("commit_oid") != commit_oid
or ("tree_oid" in commit_receipt and commit_receipt["tree_oid"] != tree_oid)):
raise GovernedPublicationError("PUBLICATION_COMMIT_BINDING_MISMATCH")
publication = {
**dict(readback), "authenticated": True, "task_id": intent.task_id,
"job_id": job_id, "source_oid": source_oid, "tree_oid": tree_oid,
"generation": intent.generation, "target_branch": intent.target_branch,
"authority_base_oid": intent.authority_base_oid,
"execution_digest": intent.execution_digest,
"commit_receipt": dict(commit_receipt),
}
task_result = {
"schema": "aios.governed-source-publication-result.v1",
"task_id": intent.task_id,
"job_id": job_id,
"repository": intent.repository,
"target_branch": intent.target_branch,
"generation": intent.generation,
"execution_digest": intent.execution_digest,
"authority_base_oid": _oid(intent.authority_base_oid),
"candidate_source_oid": _oid(intent.source_commit),
"published_source_oid": source_oid,
"source_tree_oid": tree_oid,
"commit_receipt_sha256": hashlib.sha256(
_canonical(commit_receipt)
).hexdigest(),
}
result_sha256 = hashlib.sha256(_canonical(task_result)).hexdigest()
idempotency_key = "governed-source-result-" + result_sha256
try:
submitted = self.writer_executor.submit_task_result(
session=session, result=task_result,
idempotency_key=idempotency_key,
)
if (
not isinstance(submitted, Mapping)
or submitted.get("task_id") != intent.task_id
or submitted.get("status") != "VERIFYING"
or submitted.get("result") != task_result
):
raise GovernedPublicationError(
"PUBLICATION_TASK_RESULT_READBACK_INVALID"
)
except GovernedPublicationError:
raise
except Exception as exc:
# A lost result response may have changed the durable Task. Never
# re-register, reclaim or retry here.
self._record_uncertain_effect(
exc=exc, task_id=intent.task_id, stage="submitTaskResult",
)
raise GovernedPublicationError("PUBLICATION_EFFECT_UNKNOWN") from exc
publication.update(
task_result=task_result,
task_result_idempotency_key=idempotency_key,
task_result_submission={
"task_id": submitted["task_id"], "status": submitted["status"],
"revision": submitted.get("revision"),
"idempotent": submitted.get("idempotent", False),
"result_sha256": result_sha256,
},
)
return publication
@@ -0,0 +1,405 @@
"""Narrow C5 unattended source-publication policy lifecycle.
The policy is an Owner-signed, exact-scope admission object. It is checked
against the configured Owner policy at issuance and against a live revocation
source for every request. It is not an activation/configuration mechanism
and does not create release or production authority.
"""
from __future__ import annotations
from dataclasses import asdict, dataclass
import hashlib
import re
import time
from pathlib import PurePosixPath
from typing import Any, Callable, Mapping
from aios_single_host_authority import (
OWNER_NAMESPACE,
OWNER_IDENTITY,
SOURCE_PUBLICATION_CAPABILITIES,
SOURCE_PUBLICATION_POLICY_ID,
SOURCE_PUBLICATION_POLICY_SCHEMA,
SignedArtifact,
SingleHostAuthorityError,
SourcePublicationPolicyRequest,
canonical_json_bytes,
sha256_json,
)
_HEX40 = re.compile(r"^[0-9a-f]{40}$")
_HEX64 = re.compile(r"^[0-9a-f]{64}$")
_RISK_ORDER = {"low": 0, "medium": 1, "engineering": 2, "high": 3}
_POLICY_FIELDS = frozenset(
{
"schema", "policy_id", "owner_identity", "repository", "trust_domain",
"policy_digest",
"authority_base_oid", "source_oid", "source_tree_oid", "target_agent",
"target_branch", "generation", "execution_digest", "exact_files",
"capabilities", "risk", "production_impact", "renewal", "nonce",
"issued_at", "not_before", "expires_at", "status", "signature_namespace",
"signature_key_id", "canonical_payload_digest",
}
)
_REQUEST_FIELDS = frozenset(SourcePublicationPolicyRequest.__dataclass_fields__)
# The typed Owner epoch producer uses the configured policy's production_scope.
# The installed C5 source-only policy contract uses ``none`` for production
# impact; keep this exact value bound to the C5 policy rather than accepting an
# arbitrary non-empty scope from a raw signer.
SOURCE_PUBLICATION_EPOCH_PRODUCTION_SCOPE = "none"
SOURCE_PUBLICATION_EPOCH_DESTRUCTIVE_POLICY = "forbidden"
@dataclass(frozen=True, slots=True)
class TaskAdmissionPolicyProjection:
"""The only policy projection exposed after an exact request guard."""
max_risk: str
repository: str
target_branch: str
capabilities: tuple[str, ...]
exact_files: tuple[str, ...]
def to_dict(self) -> dict[str, Any]:
return asdict(self)
def _request_dict(request: SourcePublicationPolicyRequest | Mapping[str, Any]) -> dict[str, Any]:
if isinstance(request, SourcePublicationPolicyRequest):
value = asdict(request)
elif isinstance(request, Mapping):
value = dict(request)
else:
raise TypeError("request must be SourcePublicationPolicyRequest or mapping")
if set(value) != _REQUEST_FIELDS:
raise ValueError("SOURCE_PUBLICATION_REQUEST_FIELDS_INVALID")
if "exact_files" in value:
value["exact_files"] = tuple(value["exact_files"])
return value
class SourcePublicationPolicyVerifier:
"""Strict verifier over the existing Owner registry verifier interface."""
def __init__(
self,
owner_verifier: Any,
*,
revocation_check: Callable[[Mapping[str, Any]], bool] | None,
expected_policy_digest: str | None = None,
clock: Callable[[], float] = time.time,
) -> None:
self.owner_verifier = owner_verifier
self.revocation_check = revocation_check
self.expected_policy_digest = expected_policy_digest
self.clock = clock
def _deny(self, code: str) -> None:
raise SingleHostAuthorityError(code)
def _validate_payload(self, artifact: SignedArtifact) -> dict[str, Any]:
if not isinstance(artifact, SignedArtifact):
self._deny("SOURCE_PUBLICATION_ARTIFACT_INVALID")
payload = dict(artifact.payload)
if set(payload) != _POLICY_FIELDS:
self._deny("SOURCE_PUBLICATION_UNKNOWN_FIELDS")
try:
if artifact.canonical_payload != canonical_json_bytes(payload):
self._deny("SOURCE_PUBLICATION_CANONICAL_BYTES_MISMATCH")
if payload["canonical_payload_digest"] != sha256_json(
{k: v for k, v in payload.items() if k != "canonical_payload_digest"}
):
self._deny("SOURCE_PUBLICATION_PAYLOAD_DIGEST_INVALID")
if payload["schema"] != SOURCE_PUBLICATION_POLICY_SCHEMA:
self._deny("SOURCE_PUBLICATION_SCHEMA_INVALID")
if payload["policy_id"] != SOURCE_PUBLICATION_POLICY_ID:
self._deny("SOURCE_PUBLICATION_POLICY_ID_INVALID")
if payload["owner_identity"] != OWNER_IDENTITY:
self._deny("SOURCE_PUBLICATION_OWNER_INVALID")
if not isinstance(payload["policy_digest"], str) or not _HEX64.fullmatch(payload["policy_digest"]):
self._deny("SOURCE_PUBLICATION_POLICY_DIGEST_INVALID")
if self.expected_policy_digest is not None and payload["policy_digest"] != self.expected_policy_digest:
self._deny("SOURCE_PUBLICATION_POLICY_DIGEST_MISMATCH")
if payload["signature_namespace"] != OWNER_NAMESPACE:
self._deny("SOURCE_PUBLICATION_NAMESPACE_INVALID")
if payload["signature_key_id"] != artifact.key_id or artifact.namespace != OWNER_NAMESPACE:
self._deny("SOURCE_PUBLICATION_ISSUER_MISMATCH")
for name in ("authority_base_oid", "source_oid", "source_tree_oid"):
if not isinstance(payload[name], str) or not _HEX40.fullmatch(payload[name]):
self._deny("SOURCE_PUBLICATION_OID_INVALID")
if not isinstance(payload["execution_digest"], str) or not _HEX64.fullmatch(payload["execution_digest"]):
self._deny("SOURCE_PUBLICATION_EXECUTION_DIGEST_INVALID")
branch = payload["target_branch"]
agent = payload["target_agent"]
if (
not isinstance(branch, str) or not branch.startswith("agent/")
or "\\" in branch or ".." in PurePosixPath(branch).parts
or branch.endswith(("/", "."))
or not isinstance(agent, str) or not agent.startswith("agent/")
):
self._deny("SOURCE_PUBLICATION_TARGET_INVALID")
if payload["risk"] != "engineering" or payload["production_impact"] != "none" or payload["renewal"] != "none":
self._deny("SOURCE_PUBLICATION_SCOPE_INVALID")
if payload["capabilities"] != sorted(SOURCE_PUBLICATION_CAPABILITIES):
self._deny("SOURCE_PUBLICATION_CAPABILITIES_INVALID")
if not isinstance(payload["exact_files"], list) or not payload["exact_files"] or len(set(payload["exact_files"])) != len(payload["exact_files"]):
self._deny("SOURCE_PUBLICATION_FILES_INVALID")
if type(payload["generation"]) is not int or payload["generation"] < 1:
self._deny("SOURCE_PUBLICATION_GENERATION_INVALID")
if type(payload["issued_at"]) is not int or type(payload["not_before"]) is not int or type(payload["expires_at"]) is not int:
self._deny("SOURCE_PUBLICATION_TIME_INVALID")
now = int(self.clock())
if not payload["not_before"] <= payload["issued_at"] <= now < payload["expires_at"]:
self._deny("SOURCE_PUBLICATION_EXPIRED")
if payload["expires_at"] <= payload["issued_at"]:
self._deny("SOURCE_PUBLICATION_LIFETIME_INVALID")
if payload["expires_at"] - payload["issued_at"] > 3600 or payload["status"] != "ACTIVE":
self._deny("SOURCE_PUBLICATION_LIFETIME_INVALID")
if self.revocation_check is None:
self._deny("SOURCE_PUBLICATION_REVOCATION_CHECK_MISSING")
if self.revocation_check(payload) is not True:
self._deny("SOURCE_PUBLICATION_REVOKED")
except SingleHostAuthorityError:
raise
except Exception as exc:
raise SingleHostAuthorityError("SOURCE_PUBLICATION_POLICY_INVALID") from exc
return payload
def verify(
self,
artifact: SignedArtifact,
request: SourcePublicationPolicyRequest | Mapping[str, Any] | None = None,
) -> bool:
try:
payload = self._validate_payload(artifact)
# Verify exactly the bytes supplied by the signed artifact. Do not
# reconstruct or reserialize them before invoking the registry API.
if self.owner_verifier.verify(
namespace=artifact.namespace,
payload=artifact.canonical_payload,
signature=artifact.signature,
key_id=artifact.key_id,
) is not True:
return False
if request is not None:
expected = _request_dict(request)
for field in (
"policy_id", "repository", "authority_base_oid", "source_oid",
"source_tree_oid", "target_agent", "target_branch", "generation",
"execution_digest", "exact_files", "nonce", "risk",
"production_impact", "renewal",
):
actual = tuple(payload[field]) if field == "exact_files" else payload[field]
if actual != expected.get(field):
return False
if int(payload["expires_at"]) - int(payload["issued_at"]) != int(expected["ttl_seconds"]):
return False
return True
except (SingleHostAuthorityError, TypeError, ValueError, KeyError):
return False
except Exception:
return False
def require(
self,
artifact: SignedArtifact,
request: SourcePublicationPolicyRequest | Mapping[str, Any],
) -> dict[str, Any]:
if not self.verify(artifact, request):
raise SingleHostAuthorityError("SOURCE_PUBLICATION_POLICY_DENIED")
return dict(artifact.payload)
def require_source_publication_epoch_scope(epoch: Any, policy_payload: Mapping[str, Any]) -> None:
"""Require v2 epoch provenance to match the signed C5 policy exactly."""
schema = getattr(epoch, "schema", None)
if schema is None and isinstance(epoch, Mapping):
schema = epoch.get("schema")
if schema != "aios.owner-delegation-epoch.v2":
raise SingleHostAuthorityError("SOURCE_PUBLICATION_EPOCH_SCHEMA_REQUIRED")
def field(name: str) -> Any:
if isinstance(epoch, Mapping):
return epoch.get(name)
return getattr(epoch, name, None)
if (
field("production_scope") != SOURCE_PUBLICATION_EPOCH_PRODUCTION_SCOPE
or field("destructive_policy") != SOURCE_PUBLICATION_EPOCH_DESTRUCTIVE_POLICY
or field("policy_digest") != policy_payload.get("policy_digest")
or field("repository") != policy_payload.get("repository")
or field("trust_domain") != policy_payload.get("trust_domain")
):
raise SingleHostAuthorityError("SOURCE_PUBLICATION_EPOCH_SCOPE_MISMATCH")
class ScopedSourcePublicationPolicyAdmission:
"""Rechecks a policy for every exact request before scoped admission."""
def __init__(
self,
verifier: SourcePublicationPolicyVerifier,
*,
bootstrap: Any | None = None,
artifact: SignedArtifact | None = None,
request: SourcePublicationPolicyRequest | Mapping[str, Any] | None = None,
expected_agent_id: str | None = None,
) -> None:
self.verifier = verifier
self.bootstrap = bootstrap
self.artifact = artifact
self.request = request
self.expected_agent_id = expected_agent_id
def _check_bootstrap_binding(self, payload: Mapping[str, Any]) -> None:
if self.bootstrap is None:
return
policy = getattr(self.bootstrap, "policy", None)
digest_provider = getattr(policy, "normalized_policy_digest", None)
digest = digest_provider() if callable(digest_provider) else None
trust_domain = getattr(policy, "trust_domain", None)
if not isinstance(digest, str) or digest != payload.get("policy_digest"):
raise SingleHostAuthorityError("SOURCE_PUBLICATION_BOOTSTRAP_POLICY_DIGEST_MISMATCH")
if not isinstance(trust_domain, str) or trust_domain != payload.get("trust_domain"):
raise SingleHostAuthorityError("SOURCE_PUBLICATION_BOOTSTRAP_TRUST_DOMAIN_MISMATCH")
if self.expected_agent_id is None or payload.get("target_agent") != self.expected_agent_id:
raise SingleHostAuthorityError("SOURCE_PUBLICATION_TARGET_AGENT_MISMATCH")
def authorize(
self,
artifact: SignedArtifact,
request: SourcePublicationPolicyRequest | Mapping[str, Any],
) -> bool:
try:
payload = self.verifier._validate_payload(artifact)
if self.expected_agent_id is not None and payload.get("target_agent") != self.expected_agent_id:
return False
self._check_bootstrap_binding(payload)
except SingleHostAuthorityError:
return False
return self.verifier.verify(artifact, request)
verify = authorize
def _request_for_raw(self, raw: Mapping[str, Any]) -> SourcePublicationPolicyRequest:
if self.artifact is None:
raise SingleHostAuthorityError("SOURCE_PUBLICATION_ARTIFACT_REQUIRED")
expected_task = getattr(self, "expected_task_id", None)
if expected_task is not None and raw.get("task_id") != expected_task:
raise SingleHostAuthorityError("SOURCE_PUBLICATION_PLAN_TASK_MISMATCH")
payload = self.verifier._validate_payload(self.artifact)
self._check_bootstrap_binding(payload)
if self.request is not None:
base = _request_dict(self.request)
else:
base = {
"policy_id": payload["policy_id"], "repository": payload["repository"],
"authority_base_oid": payload["authority_base_oid"], "source_oid": payload["source_oid"],
"source_tree_oid": payload["source_tree_oid"], "target_agent": payload["target_agent"],
"target_branch": payload["target_branch"], "generation": payload["generation"],
"execution_digest": payload["execution_digest"], "exact_files": tuple(payload["exact_files"]),
"nonce": payload["nonce"], "ttl_seconds": payload["expires_at"] - payload["issued_at"],
"risk": payload["risk"], "production_impact": payload["production_impact"], "renewal": payload["renewal"],
}
checks = {
"repository": base["repository"], "authority_base_oid": base["authority_base_oid"],
"target_branch": base["target_branch"], "generation": base["generation"],
"execution_digest": base["execution_digest"], "risk": base["risk"],
}
for field, expected in checks.items():
if raw.get(field) != expected:
raise SingleHostAuthorityError("SOURCE_PUBLICATION_REQUEST_SCOPE_MISMATCH")
caps = tuple(raw.get("requested_capabilities", ()))
if set(caps) != set(base["capabilities"] if "capabilities" in base else payload["capabilities"]):
raise SingleHostAuthorityError("SOURCE_PUBLICATION_CAPABILITY_SCOPE_MISMATCH")
if tuple(raw.get("file_allowlist", ())) != tuple(base["exact_files"]):
raise SingleHostAuthorityError("SOURCE_PUBLICATION_FILE_SCOPE_MISMATCH")
return SourcePublicationPolicyRequest(**base)
def create_envelope(self, raw: Mapping[str, Any]) -> dict[str, Any]:
if self.bootstrap is None:
raise SingleHostAuthorityError("SOURCE_PUBLICATION_BOOTSTRAP_REQUIRED")
request = self._request_for_raw(raw)
if not self.verifier.verify(self.artifact, request):
raise SingleHostAuthorityError("SOURCE_PUBLICATION_POLICY_DENIED")
return self.bootstrap.create_envelope(raw)
def authorize_and_admit(self, raw: Mapping[str, Any]) -> dict[str, Any]:
if self.bootstrap is None or self.artifact is None:
raise SingleHostAuthorityError("SOURCE_PUBLICATION_BOOTSTRAP_REQUIRED")
if raw.get("authorization_kind") != "delegation_epoch":
raise SingleHostAuthorityError("SOURCE_PUBLICATION_OWNER_SIGNATURE_BYPASS_DENIED")
envelope = self.bootstrap._load_envelope(str(raw.get("envelope_id") or ""))
envelope_raw = envelope.as_dict() if hasattr(envelope, "as_dict") else dict(envelope)
request = self._request_for_raw(envelope_raw)
if not self.verifier.verify(self.artifact, request):
raise SingleHostAuthorityError("SOURCE_PUBLICATION_POLICY_DENIED")
epoch = self.bootstrap._verify_epoch(
epoch_id=str(raw.get("delegation_epoch_id") or ""),
envelope=envelope,
now=int(self.verifier.clock()),
)
require_source_publication_epoch_scope(
epoch, self.verifier._validate_payload(self.artifact)
)
return self.bootstrap.authorize_and_admit(raw)
def status(self, raw: Mapping[str, Any] | str) -> Any:
"""Read status only after the same policy and epoch guard as admission."""
if self.bootstrap is None or self.artifact is None:
raise SingleHostAuthorityError("SOURCE_PUBLICATION_BOOTSTRAP_REQUIRED")
if isinstance(raw, str):
raw = {"envelope_id": raw}
envelope = self.bootstrap._load_envelope(str(raw.get("envelope_id") or ""))
envelope_raw = envelope.as_dict() if hasattr(envelope, "as_dict") else dict(envelope)
request = self._request_for_raw(envelope_raw)
self.verifier.require(self.artifact, request)
epoch_id = str(raw.get("delegation_epoch_id") or "")
if not epoch_id:
grant = self.bootstrap.store.get_grant_by_task_id(envelope.task_id)
epoch_id = grant.parent_epoch_id if grant is not None else ""
if not epoch_id or not hasattr(self.bootstrap, "_verify_epoch"):
raise SingleHostAuthorityError("SOURCE_PUBLICATION_STATUS_EPOCH_REQUIRED")
epoch = self.bootstrap._verify_epoch(
epoch_id=epoch_id,
envelope=envelope,
now=int(self.verifier.clock()),
)
require_source_publication_epoch_scope(
epoch, self.verifier._validate_payload(self.artifact)
)
if hasattr(self.bootstrap, "status"):
return self.bootstrap.status(envelope.envelope_id)
return envelope_raw
def reconcile_status(self, envelope_id: str, authorization: Mapping[str, Any]) -> Any:
"""Carry the verified epoch identity when a Task effect has no receipt yet."""
if authorization.get("authorization_kind") != "delegation_epoch":
raise SingleHostAuthorityError("SOURCE_PUBLICATION_OWNER_SIGNATURE_BYPASS_DENIED")
return self.status({"envelope_id": envelope_id,
"delegation_epoch_id": authorization.get("delegation_epoch_id")})
def project_task_admission_policy(
self,
artifact: SignedArtifact,
request: SourcePublicationPolicyRequest | Mapping[str, Any],
) -> TaskAdmissionPolicyProjection:
payload = self.verifier.require(artifact, request)
return TaskAdmissionPolicyProjection(
max_risk="engineering",
repository=payload["repository"],
target_branch=payload["target_branch"],
capabilities=tuple(payload["capabilities"]),
exact_files=tuple(payload["exact_files"]),
)
to_task_admission_policy = project_task_admission_policy
# Short aliases for callers that prefer the lifecycle terminology.
SourcePublicationPolicyAdmission = ScopedSourcePublicationPolicyAdmission
SourcePublicationVerifier = SourcePublicationPolicyVerifier
@@ -0,0 +1,372 @@
"""Thin unattended release runner.
This module is an adapter around the existing admission and silent-release
services. It deliberately owns no release state machine and never creates
approval evidence. The runner only binds immutable source facts, delegates
authority, and validates durable readbacks before moving to the next phase.
"""
from __future__ import annotations
from dataclasses import dataclass
import hashlib
import json
import re
import time
from typing import Any, Callable, Mapping
OID_RE = re.compile(r"^[0-9a-fA-F]{40}$")
SHA256_RE = re.compile(r"^[0-9a-fA-F]{64}$")
IDENT_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.:-]{0,127}$")
RETRYABLE_CODES = {408, 425, 429, 500, 502, 503, 504}
RETRYABLE_NAMES = {
"UNAVAILABLE", "BUSY", "TEMPORARY_FAILURE", "TASK_ADMISSION_IN_PROGRESS",
}
RISKS = {"low", "medium", "engineering", "high"}
class UnattendedReleaseError(RuntimeError):
"""A fail-closed runner error with a stable code."""
def __init__(self, code: str, message: str | None = None):
self.code = code
super().__init__(message or code)
def _canon(value: Any) -> bytes:
return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=True).encode()
def _digest(value: Any) -> str:
return hashlib.sha256(_canon(value)).hexdigest()
def _require_oid(name: str, value: Any) -> str:
value = str(value or "")
if not OID_RE.fullmatch(value):
raise UnattendedReleaseError("INVALID_" + name.upper())
return value.lower()
def _require_sha(name: str, value: Any) -> str:
value = str(value or "")
if not SHA256_RE.fullmatch(value):
raise UnattendedReleaseError("INVALID_" + name.upper())
return value.lower()
@dataclass(frozen=True)
class SourcePublicationIntent:
"""Immutable, deterministic source and execution binding."""
repository: str
source_commit: str
source_tree_oid: str
authority_base_oid: str
target_branch: str
file_allowlist: tuple[str, ...]
requested_capabilities: tuple[str, ...]
risk: str
execution_digest: str
generation: int = 1
event_id: str = "release"
version: str = ""
admission_task_id: str = ""
admission_idempotency_key: str = ""
def __post_init__(self) -> None:
if not self.repository or not isinstance(self.repository, str):
raise UnattendedReleaseError("INVALID_REPOSITORY")
for name in ("source_commit", "source_tree_oid", "authority_base_oid"):
object.__setattr__(self, name, _require_oid(name, getattr(self, name)))
if not isinstance(self.target_branch, str) or not self.target_branch or not self.target_branch.startswith("agent/") or self.target_branch.startswith("-"):
raise UnattendedReleaseError("INVALID_TARGET_BRANCH")
if ".." in self.target_branch or "//" in self.target_branch or "@{" in self.target_branch:
raise UnattendedReleaseError("INVALID_TARGET_BRANCH")
if not isinstance(self.generation, int) or isinstance(self.generation, bool) or self.generation < 1:
raise UnattendedReleaseError("INVALID_GENERATION")
object.__setattr__(self, "execution_digest", _require_sha("execution_digest", self.execution_digest))
if not self.file_allowlist or any(not isinstance(p, str) or not p or p.startswith(("/", "\\")) or ".." in p or "\\" in p or p == ".git" or p.startswith(".git/") for p in self.file_allowlist):
raise UnattendedReleaseError("INVALID_FILE_ALLOWLIST")
if len(set(self.file_allowlist)) != len(self.file_allowlist):
raise UnattendedReleaseError("DUPLICATE_FILE_ALLOWLIST")
if not self.requested_capabilities or any(not isinstance(c, str) or not c or "*" in c for c in self.requested_capabilities):
raise UnattendedReleaseError("INVALID_CAPABILITIES")
if not isinstance(self.risk, str) or self.risk.lower() not in RISKS:
raise UnattendedReleaseError("INVALID_RISK")
object.__setattr__(self, "risk", self.risk.lower())
object.__setattr__(self, "file_allowlist", tuple(sorted(self.file_allowlist)))
object.__setattr__(self, "requested_capabilities", tuple(sorted(set(self.requested_capabilities))))
if not IDENT_RE.fullmatch(self.event_id):
raise UnattendedReleaseError("INVALID_EVENT_ID")
if self.version and not IDENT_RE.fullmatch(self.version):
raise UnattendedReleaseError("INVALID_VERSION")
if bool(self.admission_task_id) != bool(self.admission_idempotency_key):
raise UnattendedReleaseError("INCOMPLETE_ADMISSION_IDENTITY")
for value in (self.admission_task_id, self.admission_idempotency_key):
if value and (not isinstance(value, str) or not IDENT_RE.fullmatch(value)):
raise UnattendedReleaseError("INVALID_ADMISSION_IDENTITY")
def as_dict(self) -> dict[str, Any]:
value = {
"repository": self.repository, "source_commit": self.source_commit.lower(),
"source_tree_oid": self.source_tree_oid.lower(), "authority_base_oid": self.authority_base_oid.lower(),
"target_branch": self.target_branch, "file_allowlist": list(self.file_allowlist),
"requested_capabilities": list(self.requested_capabilities), "risk": self.risk,
"execution_digest": self.execution_digest.lower(), "generation": self.generation,
"event_id": self.event_id, "version": self.version,
}
if self.admission_task_id:
value.update(admission_task_id=self.admission_task_id,
admission_idempotency_key=self.admission_idempotency_key)
return value
@property
def task_id(self) -> str:
return self.admission_task_id or "release-task-" + _digest(self.as_dict())[:32]
@property
def idempotency_key(self) -> str:
return self.admission_idempotency_key or "release-intake-" + _digest({"task_id": self.task_id, "execution_digest": self.execution_digest.lower()})[:32]
def admission_request(self) -> dict[str, Any]:
"""Return the sole normalized request accepted by Task admission."""
return {"idempotency_key": self.idempotency_key, "task_id": self.task_id,
"repository": self.repository, "authority_base_oid": self.authority_base_oid,
"target_branch": self.target_branch, "generation": self.generation,
"execution_digest": self.execution_digest, "requested_capabilities": list(self.requested_capabilities),
"file_allowlist": list(self.file_allowlist), "risk": self.risk}
def _error_code(exc: BaseException) -> str:
code = getattr(exc, "code", None) or getattr(exc, "status", None) or exc.__class__.__name__
value = str(code).upper()
return value if re.fullmatch(r"[A-Z0-9_]{1,100}", value) else "BACKEND_ERROR"
def _bound_receipt(receipt: Any, intent: SourcePublicationIntent) -> bool:
if not isinstance(receipt, Mapping):
return False
expected = {"task_id": intent.task_id, "authority_base_oid": intent.authority_base_oid,
"target_branch": intent.target_branch, "generation": intent.generation,
"execution_digest": intent.execution_digest}
return (receipt.get("schema") == "aios.task-admission-receipt.v1"
and all(_canon(receipt.get(k)) == _canon(v) for k, v in expected.items())
and receipt.get("task_state") in {"QUEUED", "CLAIMED", "RUNNING"}
and all(isinstance(receipt.get(k), str) and receipt[k] for k in ("envelope_id", "grant_id")))
def _retryable(exc: BaseException) -> bool:
status = getattr(exc, "status_code", None) or getattr(exc, "http_status", None) or getattr(exc, "status", None)
if isinstance(status, int):
return status in RETRYABLE_CODES
code = _error_code(exc)
return code in RETRYABLE_NAMES
def _unknown_outcome(exc: BaseException) -> bool:
return isinstance(exc, (TimeoutError, ConnectionError, OSError)) or _error_code(exc) in {"TASK_ADMISSION_TASK_STATE_UNKNOWN", "TIMEOUT", "TIMED_OUT", "CONNECTION_LOST"}
class GovernedUnattendedReleaseRunner:
"""Phase-separated runner; preparation has no release/deployment rights."""
def __init__(self, *, admission: Any, release_service: Any = None,
publication_adapter: Any = None, owner_approval_broker: Callable[[Mapping[str, Any]], Mapping[str, Any]] | None = None,
deployment_enabled: bool = False, deployment_service: Any = None,
max_attempts: int = 3, deadline_seconds: float = 30.0,
environment_mode: str = "SIMULATION",
clock: Callable[[], float] = time.monotonic, sleep: Callable[[float], None] = time.sleep):
if admission is None:
raise ValueError("admission is required")
if max_attempts < 1 or max_attempts > 10 or deadline_seconds <= 0:
raise ValueError("invalid retry bounds")
self.admission = admission
self.environment_mode = str(environment_mode).upper()
if self.environment_mode not in {"SIMULATION", "AUTHORITY"}:
raise ValueError("environment_mode must be SIMULATION or AUTHORITY")
self.release_service = release_service
self.publication_adapter = publication_adapter
self.owner_approval_broker = owner_approval_broker
self.deployment_enabled = bool(deployment_enabled)
self.deployment_service = deployment_service
self.max_attempts = max_attempts
self.deadline_seconds = deadline_seconds
self.clock, self.sleep = clock, sleep
def intake(self, intent: SourcePublicationIntent) -> dict[str, Any]:
if not isinstance(intent, SourcePublicationIntent):
raise UnattendedReleaseError("INTENT_TYPE_REQUIRED")
return {"phase": "PREPARATION", "mode": self.environment_mode, "task_id": intent.task_id,
"idempotency_key": intent.idempotency_key, "intent": intent.as_dict(),
"release_rights": False, "deployment_rights": False}
def admit(self, intent: SourcePublicationIntent, authorization: Mapping[str, Any]) -> dict[str, Any]:
if not isinstance(authorization, Mapping):
raise UnattendedReleaseError("AUTHORIZATION_REQUIRED")
if self.environment_mode == "SIMULATION" and getattr(self.admission, "simulation_only", False) is not True:
return {"phase": "BLOCKED", "mode": self.environment_mode, "task_id": intent.task_id, "error": "AUTHORITY_MODE_REQUIRED"}
raw = intent.admission_request()
deadline = self.clock() + self.deadline_seconds
envelope_id = None
for attempt in range(1, self.max_attempts + 1):
if self.clock() >= deadline:
return {"phase": "RECONCILIATION_REQUIRED" if envelope_id else "BLOCKED", "mode": "AUTHORITY", "task_id": intent.task_id, "error": "ADMISSION_DEADLINE_EXCEEDED"}
try:
created = self.admission.create_envelope(raw)
envelope = created.get("envelope", created) if isinstance(created, Mapping) else {}
envelope_id = envelope.get("envelope_id") or envelope_id
if not isinstance(envelope_id, str) or not envelope_id:
raise UnattendedReleaseError("ENVELOPE_BINDING_MISSING")
auth = dict(authorization)
auth["envelope_id"] = envelope_id
if self.clock() >= deadline:
return self._reconcile(envelope_id, intent, "ADMISSION_DEADLINE_EXCEEDED", authorization)
receipt = self.admission.authorize_and_admit(auth)
if not _bound_receipt(receipt, intent) or receipt["envelope_id"] != envelope_id:
raise UnattendedReleaseError("ADMISSION_RECEIPT_BINDING_MISMATCH")
if self.clock() >= deadline:
return self._reconcile(envelope_id, intent, "ADMISSION_DEADLINE_EXCEEDED", authorization)
return {"phase": "ADMITTED", "mode": self.environment_mode, "attempt": attempt,
"envelope": created, "receipt": receipt}
except Exception as exc:
status_code = getattr(exc, "status_code", None) or getattr(exc, "http_status", None) or getattr(exc, "status", None)
if status_code == 403 or _error_code(exc) in {"403", "FORBIDDEN"}:
return {"phase": "BLOCKED", "mode": "AUTHORITY", "task_id": intent.task_id, "error": _error_code(exc)}
# Once authorization may have run, every failure reconciles.
# A 5xx is never sufficient proof of NOT_APPLIED.
if envelope_id:
return self._reconcile(envelope_id, intent, _error_code(exc), authorization)
if not _retryable(exc) or self.clock() >= deadline or attempt >= self.max_attempts:
return {"phase": "BLOCKED", "mode": "AUTHORITY", "task_id": intent.task_id, "error": _error_code(exc)}
self.sleep(min(0.05 * (2 ** (attempt - 1)), max(0.0, deadline - self.clock())))
raise UnattendedReleaseError("ADMISSION_RETRY_EXHAUSTED")
def _reconcile(self, envelope_id: str | None, intent: SourcePublicationIntent, error: str,
authorization: Mapping[str, Any]) -> dict[str, Any]:
if not envelope_id:
return {"phase": "RECONCILIATION_REQUIRED", "mode": "AUTHORITY", "task_id": intent.task_id, "error": error}
try:
reconcile = getattr(self.admission, "reconcile_status", None)
status = reconcile(envelope_id, authorization) if callable(reconcile) else self.admission.status(envelope_id)
except Exception as exc:
return {"phase": "RECONCILIATION_REQUIRED", "mode": "AUTHORITY", "task_id": intent.task_id, "error": error, "status_error": _error_code(exc)}
state = str(status.get("status") or status.get("task_state") or "").upper() if isinstance(status, Mapping) else ""
if state in {"NOT_APPLIED", "ABSENT", "NOT_FOUND"}:
return {"phase": "RETRYABLE_RECONCILIATION", "mode": "AUTHORITY", "task_id": intent.task_id, "status": status, "error": error}
if state == "ADMITTED" and _bound_receipt(status.get("task"), intent):
return {"phase": "RECONCILED", "mode": self.environment_mode, "task_id": intent.task_id, "status": status, "receipt": status["task"]}
return {"phase": "RECONCILIATION_REQUIRED", "mode": "AUTHORITY", "task_id": intent.task_id, "status": status, "error": error}
def publish(self, intent: SourcePublicationIntent, admission_receipt: Mapping[str, Any]) -> dict[str, Any]:
if self.environment_mode != "AUTHORITY":
return {"phase": "BLOCKED", "mode": self.environment_mode, "error": "AUTHORITY_MODE_REQUIRED"}
if self.publication_adapter is None:
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": "PUBLICATION_BACKEND_MISSING"}
required = ("task_lease", "task_principal", "writer_lease", "commit_receipt", "source_oid", "tree_oid", "authenticated")
try:
if not _bound_receipt(admission_receipt, intent):
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": "ADMISSION_RECEIPT_BINDING_MISMATCH"}
result = self.publication_adapter.publish(intent, admission_receipt)
if not isinstance(result, Mapping) or result.get("authenticated") is not True or any(not result.get(k) for k in required[:-1]):
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": "PUBLICATION_LEASE_CHAIN_MISSING"}
if any(not isinstance(result[k], Mapping) or result[k].get("task_id") != intent.task_id
for k in ("task_lease", "task_principal", "writer_lease")):
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": "PUBLICATION_LEASE_BINDING_MISMATCH"}
commit = result["commit_receipt"]
# Existing continuation receipts carry remote OIDs, while the
# authenticated task/job readback carries scope. Preserve both
# originals; never fabricate missing fields inside a receipt.
optional_bindings = {"task_id": intent.task_id, "tree_oid": result["tree_oid"],
"execution_digest": intent.execution_digest,
"target_branch": intent.target_branch,
"base_oid": intent.authority_base_oid}
if (not isinstance(commit, Mapping) or commit.get("commit_oid") != result["source_oid"]
or any(key in commit and commit[key] != value for key, value in optional_bindings.items())
or result.get("generation") != intent.generation):
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": "PUBLICATION_COMMIT_BINDING_MISMATCH"}
if not OID_RE.fullmatch(str(result["source_oid"])) or str(result["tree_oid"]).lower() != intent.source_tree_oid.lower() or any(str(result.get(k, "")) != str(v) for k, v in (("task_id", intent.task_id), ("authority_base_oid", intent.authority_base_oid), ("target_branch", intent.target_branch), ("execution_digest", intent.execution_digest))):
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": "PUBLICATION_RC_BINDING_MISMATCH"}
return {"phase": "PUBLISHED", "mode": "AUTHORITY", "readback": dict(result)}
except Exception as exc:
return {"phase": "RECONCILIATION_REQUIRED", "mode": "AUTHORITY", "error": _error_code(exc)}
def postpublication_rc(self, *, event_id: str, source_commit: str, version: str = "", source_readback: Mapping[str, Any] | None = None) -> dict[str, Any]:
if self.environment_mode != "AUTHORITY":
return {"phase": "BLOCKED", "mode": self.environment_mode, "error": "AUTHORITY_MODE_REQUIRED"}
if not isinstance(source_readback, Mapping) or source_readback.get("authenticated") is not True or str(source_readback.get("source_commit", "")).lower() != str(source_commit).lower():
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": "SOURCE_READBACK_REQUIRED"}
if self.release_service is None:
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": "RELEASE_BACKEND_MISSING"}
try:
# Only a host-injected authenticated resolver can admit a source.
# The caller's readback object alone is never authorization.
resolver = getattr(self.release_service, "source_admission_resolver", None)
admitted_source = resolver(source_commit) if callable(resolver) else None
if (not isinstance(admitted_source, Mapping) or not admitted_source.get("admission_id")
or admitted_source.get("source_commit") != source_commit
or admitted_source.get("tree_oid") != source_readback.get("tree_oid")):
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": "AUTHENTICATED_SOURCE_ADMISSION_REQUIRED"}
intent = self.release_service.create_release_intent({"event_id": event_id, "source_commit": source_commit, **({"version": version} if version else {})})
candidate_id = str(intent.get("candidate_id") or intent.get("candidate", {}).get("candidate_id") or "")
if not candidate_id:
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": "CANDIDATE_BINDING_MISSING"}
readers = ("get_release_candidate", "get_release_plan", "get_acceptance")
if any(not callable(getattr(self.release_service, name, None)) for name in readers):
return {"phase": "BLOCKED", "mode": "AUTHORITY", "candidate_id": candidate_id, "error": "RC_READBACK_BACKEND_MISSING"}
candidate = self.release_service.get_release_candidate(candidate_id)
plan = self.release_service.get_release_plan(candidate_id)
acceptance = self.release_service.get_acceptance(candidate_id)
deployment = plan.get("deployment") if isinstance(plan, Mapping) else None
if (not isinstance(candidate, Mapping) or not isinstance(plan, Mapping) or not isinstance(deployment, Mapping) or not isinstance(acceptance, Mapping)
or str(candidate.get("candidate_id")) != candidate_id
or str(candidate.get("source_commit")) .lower() != str(source_commit).lower()
or str(plan.get("candidate_id", candidate_id)) != candidate_id
or str(deployment.get("source_commit")) .lower() != str(source_commit).lower()
or str(deployment.get("tree_oid", "")).lower() != str(source_readback.get("tree_oid", "")).lower()
or candidate.get("tree_oid") != deployment.get("tree_oid")
or candidate.get("artifact_sha256") != deployment.get("artifact_sha256")
or candidate.get("manifest_sha256") != deployment.get("manifest_sha256")
or str(acceptance.get("candidate_id")) != candidate_id
or acceptance.get("release_plan_sha256") != plan.get("sha256")
or str(acceptance.get("status")) != "ACCEPTED"):
return {"phase": "BLOCKED", "mode": "AUTHORITY", "candidate_id": candidate_id, "error": "RC_READBACK_BINDING_MISMATCH"}
# Public readback is redacted. Reverify original signed bytes inside
# the existing service, not by reconstructing them from API output.
verify_acceptance = getattr(self.release_service, "verify_release_acceptance", None)
if not callable(verify_acceptance):
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": "BUILDER_ATTESTATION_REQUIRED"}
verified = verify_acceptance(candidate_id)
if (not isinstance(verified, Mapping) or verified.get("status") != "VERIFIED"
or verified.get("candidate_id") != candidate_id
or verified.get("release_plan_sha256") != plan.get("sha256")):
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": "BUILDER_ATTESTATION_NOT_VERIFIED"}
envelope = self.release_service.get_approval_envelope(candidate_id)
if self.owner_approval_broker is None:
return {"phase": "BLOCKED", "mode": "AUTHORITY", "candidate_id": candidate_id, "error": "OWNER_APPROVAL_BACKEND_MISSING"}
approval = self.owner_approval_broker(dict(envelope))
if not isinstance(approval, Mapping):
return {"phase": "BLOCKED", "mode": "AUTHORITY", "candidate_id": candidate_id, "error": "OWNER_APPROVAL_MISSING"}
accepted = self.release_service.submit_owner_approval(dict(approval))
if not isinstance(accepted, Mapping) or accepted.get("status") != "VERIFIED_AND_STORED":
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": "APPROVAL_NOT_VERIFIED"}
return {"phase": "APPROVED", "mode": "AUTHORITY", "candidate_id": candidate_id,
"readback": {"candidate": dict(candidate), "plan": dict(plan), "acceptance": dict(acceptance)},
"approval": accepted}
except Exception as exc:
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": _error_code(exc)}
def deploy(self, candidate_id: str) -> dict[str, Any]:
if (self.environment_mode != "AUTHORITY" or not self.deployment_enabled or self.deployment_service is None
or getattr(self.deployment_service, "execution_enabled", False) is not True):
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": "DEPLOYMENT_NOT_ENABLED"}
try:
result = self.deployment_service.start_deployment(candidate_id)
saga = self.deployment_service.get_release_saga(candidate_id)
if (not isinstance(result, Mapping) or result.get("candidate_id") != candidate_id
or result.get("state") != "COMPLETED" or result.get("status") not in {"VERIFIED", "IDEMPOTENT"}
or not isinstance(saga, Mapping) or saga.get("state") != "COMPLETED"):
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": "DEPLOYMENT_NOT_VERIFIED", "result": dict(result) if isinstance(result, Mapping) else {}}
return {"phase": "DEPLOYED", "mode": "AUTHORITY", "result": dict(result)}
except Exception as exc:
return {"phase": "BLOCKED", "mode": "AUTHORITY", "error": _error_code(exc)}
@@ -0,0 +1,136 @@
"""Local adapter for the root-owned registered Owner signer service.
The adapter intentionally has no public arbitrary-payload signing method.
``LocalOwnerAuthority`` calls the private typed seam after validating and
canonicalizing a structured Owner authorization object.
"""
from __future__ import annotations
import os
from pathlib import Path
import stat
import subprocess
import tempfile
from typing import Any, Callable
DEFAULT_REGISTERED_SIGNER_COMMAND = Path("/usr/local/libexec/aios-grant-signer-adapter")
OWNER_NAMESPACE = "aios-owner-approval-v1"
MAX_SIGNATURE_BYTES = 128 * 1024
MAX_KEY_ID_BYTES = 512
class RegisteredOwnerSignerError(RuntimeError):
pass
class RegisteredOwnerSignerAdapter:
"""Invoke the registered signer without reading or copying private keys."""
def __init__(
self,
command: Path | str = DEFAULT_REGISTERED_SIGNER_COMMAND,
*,
key_id: str,
owner_verifier: Any,
require_root: bool = True,
test_mode: bool = False,
runner: Callable[..., Any] = subprocess.run,
) -> None:
self.command = str(command)
self.key_id = key_id
self.owner_verifier = owner_verifier
self.require_root = require_root
self.test_mode = test_mode
self.runner = runner
if not isinstance(key_id, str) or not key_id:
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_KEY_ID_REQUIRED")
if not test_mode:
self._validate_command()
@property
def fingerprint(self) -> str:
return self.key_id
def _validate_command(self) -> None:
path = Path(self.command)
if not path.is_absolute() or path.is_symlink() or not path.is_file():
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_COMMAND_INVALID")
st = path.stat()
if not hasattr(os, "geteuid") or st.st_uid != os.geteuid():
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_COMMAND_OWNER_INVALID")
if stat.S_IMODE(st.st_mode) & 0o022:
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_COMMAND_PERMISSIONS_INVALID")
def _read_output(self, path: Path, maximum: int) -> bytes:
if path.is_symlink() or not path.is_file():
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_OUTPUT_INVALID")
st = path.stat()
if not self.test_mode and (not hasattr(os, "geteuid") or st.st_uid != os.geteuid()):
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_OUTPUT_OWNER_INVALID")
if st.st_size > maximum:
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_OUTPUT_TOO_LARGE")
return path.read_bytes()
def _sign_canonical_payload(self, namespace: str, payload: bytes) -> str:
"""Private seam used only by typed ``LocalOwnerAuthority`` issuance."""
if namespace != OWNER_NAMESPACE:
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_NAMESPACE_INVALID")
if not isinstance(payload, bytes) or not payload:
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_PAYLOAD_INVALID")
if self.require_root:
if not hasattr(os, "geteuid") or os.geteuid() != 0:
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_ROOT_REQUIRED")
if not self.test_mode:
self._validate_command()
with tempfile.TemporaryDirectory(prefix="aios-owner-sign-") as directory:
root = Path(directory)
input_path = root / "payload"
signature_path = root / "payload.sig"
key_id_path = root / "key-id"
input_path.write_bytes(payload)
try:
result = self.runner(
[
self.command,
"sign",
"--namespace",
namespace,
"--input",
str(input_path),
"--signature-out",
str(signature_path),
"--key-id-out",
str(key_id_path),
],
check=False,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
timeout=30,
)
except Exception as exc:
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_FAILED") from exc
if getattr(result, "returncode", None) != 0:
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_FAILED")
if not signature_path.is_file() or not key_id_path.is_file():
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_OUTPUT_MISSING")
supplied_key_id = self._read_output(key_id_path, MAX_KEY_ID_BYTES).decode("utf-8").strip()
if supplied_key_id != self.key_id:
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_KEY_ID_MISMATCH")
signature = self._read_output(signature_path, MAX_SIGNATURE_BYTES).decode("utf-8")
if not signature.startswith("-----BEGIN SSH SIGNATURE-----"):
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_SIGNATURE_INVALID")
try:
verified = self.owner_verifier.verify(
namespace=namespace,
payload=payload,
signature=signature,
key_id=self.key_id,
)
except Exception as exc:
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_VERIFY_FAILED") from exc
if verified is not True:
raise RegisteredOwnerSignerError("REGISTERED_SIGNER_VERIFY_REJECTED")
return signature
File diff suppressed because it is too large Load Diff
@@ -553,7 +553,7 @@ class ReleaseSourceAdmissionStore:
supplied_candidate = str(candidate_id or "")
if supplied_candidate and supplied_candidate != record["candidate_id"]:
raise ReleaseSourceAdmissionError("SOURCE_ADMISSION_CANDIDATE_MISMATCH")
return {
result = {
"mode": RECIPE_MODE,
"task_id": record["task_id"],
"candidate_id": record["candidate_id"],
@@ -562,6 +562,9 @@ class ReleaseSourceAdmissionStore:
"version": record["version"],
"purpose": record["purpose"],
}
if version == "V2R2C5":
result["tree_oid"] = record["tree_oid"]
return result
@dataclass(slots=True)
@@ -0,0 +1,716 @@
#!/usr/bin/env python3
"""Host-injected broker for short release approvals under standing authority.
The broker deliberately owns no business-authority ledger. Its readers must
return verified current snapshots from the host's durable authority and
release-lineage stores. It only derives, verifies, and persists short-lived
``SignedReleasePlanApproval`` children for the exact local ``ReleasePlan``.
"""
from __future__ import annotations
import dataclasses
import hashlib
import secrets
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
from pathlib import Path, PurePosixPath
from typing import Any, Callable, Mapping, Protocol
from silent_release_controller import CANDIDATE_ID_RE, OID_RE, SHA256_RE, _parse_utc
from silent_release_hardening import (
ALLOWED_CAPABILITIES,
CapabilityAuthorizationError,
ReleasePlan,
SignedReleasePlanApproval,
SilentReleaseActionService,
_canonical_json,
release_file_lock,
)
from task_plane.governance_control_kernel import (
AuthorityDecisionContext,
AuthorityLifecycleState,
ExactScopeState,
OwnerBoundaryReason,
)
_EFFECT_STATES = frozenset(
{"KNOWN_NOT_APPLIED", "KNOWN_IN_PROGRESS", "APPLIED_VERIFIED", "UNKNOWN"}
)
_LINEAGE_KINDS = frozenset({"ROOT", "SUCCESSOR"})
_BINDING_KEYS = frozenset(
{
"authorization_id",
"generation",
"authorization_sha256",
"version",
"repository",
"production_scope",
"authority_base_oid",
"trust_root_sha256",
"actor_id",
"mission_id",
"run_id",
}
)
class StandingAuthorityError(CapabilityAuthorizationError):
"""A current standing-authority or exact-scope check failed closed."""
def __init__(self, code: str, message: str):
super().__init__(message)
self.code = code
def _text(name: str, value: str) -> str:
if not isinstance(value, str) or not value or value != value.strip() or "\x00" in value:
raise ValueError(f"{name} must be a non-empty canonical string")
return value
def _sha(name: str, value: str) -> str:
value = _text(name, value)
if not SHA256_RE.fullmatch(value):
raise ValueError(f"{name} must be a SHA-256 digest")
return value
def _oid(name: str, value: str) -> str:
value = _text(name, value)
if not OID_RE.fullmatch(value):
raise ValueError(f"{name} must be a Git object id")
return value
def _tuple_of_text(name: str, value: tuple[str, ...]) -> tuple[str, ...]:
if not isinstance(value, tuple) or not value:
raise ValueError(f"{name} must be a non-empty tuple")
normalized = tuple(_text(name, item) for item in value)
if len(set(normalized)) != len(normalized):
raise ValueError(f"{name} contains duplicates")
return normalized
def _utc(value: datetime) -> datetime:
if not isinstance(value, datetime) or value.tzinfo is None:
raise ValueError("clock must return a timezone-aware datetime")
return value.astimezone(timezone.utc)
def _format_utc(value: datetime) -> str:
return _utc(value).replace(microsecond=0).isoformat().replace("+00:00", "Z")
@dataclass(frozen=True)
class CanonicalPlanScope:
version: str
repository: str
service: str
release_root: str
cutover_link: str
authoritative_git_dir: str
dropin_relative_path: str
rollback_target: str
required_capabilities: tuple[str, ...]
def __post_init__(self) -> None:
for name in (
"version",
"repository",
"service",
"release_root",
"cutover_link",
"authoritative_git_dir",
"dropin_relative_path",
"rollback_target",
):
_text(name, getattr(self, name))
capabilities = _tuple_of_text(
"required_capabilities", self.required_capabilities
)
if tuple(sorted(capabilities)) != capabilities:
raise ValueError("required_capabilities must be sorted")
object.__setattr__(self, "required_capabilities", capabilities)
@property
def canonical_payload(self) -> dict[str, Any]:
return {
"cutover_link": self.cutover_link,
"authoritative_git_dir": self.authoritative_git_dir,
"dropin_relative_path": self.dropin_relative_path,
"release_root": self.release_root,
"repository": self.repository,
"required_capabilities": list(self.required_capabilities),
"rollback_target": self.rollback_target,
"service": self.service,
"version": self.version,
}
@property
def sha256(self) -> str:
return hashlib.sha256(_canonical_json(self.canonical_payload)).hexdigest()
def canonical_plan_scope(
plan: ReleasePlan, required_capabilities: tuple[str, ...]
) -> CanonicalPlanScope:
"""Derive production scope only from a validated local ReleasePlan."""
plan.validate()
release_path = PurePosixPath(plan.deployment.release_path)
dropin_path = PurePosixPath(plan.deployment.dropin_path)
try:
dropin_relative = dropin_path.relative_to(release_path).as_posix()
except ValueError as exc:
raise ValueError("drop-in path is outside the candidate release path") from exc
if dropin_relative in {"", "."}:
raise ValueError("drop-in path must be a child of the release path")
return CanonicalPlanScope(
version=plan.tag.version,
repository=plan.deployment.repository_identity,
service=plan.deployment.service,
release_root=plan.deployment.release_root,
cutover_link=plan.deployment.cutover_link,
authoritative_git_dir=plan.deployment.authoritative_git_dir,
dropin_relative_path=dropin_relative,
rollback_target=plan.deployment.rollback_path,
required_capabilities=tuple(sorted(required_capabilities)),
)
@dataclass(frozen=True)
class StandingReleaseAuthorization:
authorization_id: str
generation: int
authorization_sha256: str
version: str
repository: str
production_scope: str
authority_base_oid: str
trust_root_sha256: str
trust_domain: str
lineage_root_candidate_fingerprint: str
allowed_capabilities: tuple[str, ...]
rollback_target: str
owner_identity: str
allowed_actor_ids: tuple[str, ...]
mission_id: str
run_id: str
lifecycle_state: AuthorityLifecycleState
acceptance_complete: bool
acceptance_evidence_ref: str | None = None
expires_at: str | None = None
evidence_refs: tuple[str, ...] = ()
def __post_init__(self) -> None:
for name in (
"authorization_id",
"version",
"repository",
"trust_domain",
"rollback_target",
"owner_identity",
"mission_id",
"run_id",
):
_text(name, getattr(self, name))
if (
not isinstance(self.generation, int)
or isinstance(self.generation, bool)
or self.generation < 1
):
raise ValueError("generation must be a positive integer")
_sha("authorization_sha256", self.authorization_sha256)
_sha("production_scope", self.production_scope)
_oid("authority_base_oid", self.authority_base_oid)
_sha("trust_root_sha256", self.trust_root_sha256)
_sha(
"lineage_root_candidate_fingerprint",
self.lineage_root_candidate_fingerprint,
)
capabilities = _tuple_of_text(
"allowed_capabilities", self.allowed_capabilities
)
if not set(capabilities).issubset(ALLOWED_CAPABILITIES):
raise ValueError("allowed_capabilities contains an unsupported capability")
object.__setattr__(self, "allowed_capabilities", capabilities)
object.__setattr__(
self, "allowed_actor_ids", _tuple_of_text("allowed_actor_ids", self.allowed_actor_ids)
)
if not isinstance(self.lifecycle_state, AuthorityLifecycleState):
raise ValueError("lifecycle_state must be AuthorityLifecycleState")
if type(self.acceptance_complete) is not bool:
raise ValueError("acceptance_complete must be a boolean")
if self.acceptance_complete:
_text("acceptance_evidence_ref", self.acceptance_evidence_ref or "")
elif self.acceptance_evidence_ref is not None:
raise ValueError("acceptance evidence is only valid for completed acceptance")
if self.expires_at is not None:
_parse_utc(self.expires_at)
if not isinstance(self.evidence_refs, tuple):
raise ValueError("evidence_refs must be a tuple")
for value in self.evidence_refs:
_text("evidence_ref", value)
@dataclass(frozen=True)
class ReleaseExecutionScope:
release_plan_id: str
candidate_id: str
candidate_fingerprint: str
source_commit: str
tree_oid: str
artifact_sha256: str
manifest_sha256: str
deployment_plan_sha256: str
release_plan_sha256: str
approval_plan_id: str
evidence_closure_oid: str
owner_approval_record_oid: str
authority_base_oid: str
trust_root_sha256: str
lineage_root_candidate_fingerprint: str
actor_id: str
mission_id: str
run_id: str
effect_state: str
lineage_kind: str
predecessor_candidate_fingerprint: str | None = None
successor_lineage_sha256: str | None = None
evidence_refs: tuple[str, ...] = ()
def __post_init__(self) -> None:
for name in (
"release_plan_id",
"candidate_id",
"approval_plan_id",
"actor_id",
"mission_id",
"run_id",
):
_text(name, getattr(self, name))
if not CANDIDATE_ID_RE.fullmatch(self.candidate_id):
raise ValueError("candidate_id is invalid")
for name in ("source_commit", "tree_oid", "evidence_closure_oid", "owner_approval_record_oid", "authority_base_oid"):
_oid(name, getattr(self, name))
for name in (
"candidate_fingerprint",
"artifact_sha256",
"manifest_sha256",
"deployment_plan_sha256",
"release_plan_sha256",
"trust_root_sha256",
"lineage_root_candidate_fingerprint",
):
_sha(name, getattr(self, name))
if self.effect_state not in _EFFECT_STATES:
raise ValueError("effect_state is invalid")
if self.lineage_kind not in _LINEAGE_KINDS:
raise ValueError("lineage_kind is invalid")
if self.lineage_kind == "ROOT":
if self.predecessor_candidate_fingerprint is not None or self.successor_lineage_sha256 is not None:
raise ValueError("root candidate cannot carry successor lineage")
else:
_sha("predecessor_candidate_fingerprint", self.predecessor_candidate_fingerprint or "")
_sha("successor_lineage_sha256", self.successor_lineage_sha256 or "")
if self.predecessor_candidate_fingerprint == self.candidate_fingerprint:
raise ValueError("successor predecessor must be a different candidate")
if not isinstance(self.evidence_refs, tuple):
raise ValueError("evidence_refs must be a tuple")
for value in self.evidence_refs:
_text("evidence_ref", value)
class RegisteredApprovalSigner(Protocol):
@property
def key_id(self) -> str: ...
def sign(
self, unsigned: SignedReleasePlanApproval
) -> SignedReleasePlanApproval | Mapping[str, Any]: ...
class StandingReleaseAuthorizationBroker:
"""Issue and renew exact short approvals from two trusted read layers."""
def __init__(
self,
service: SilentReleaseActionService,
authority_reader: Callable[[], StandingReleaseAuthorization],
execution_scope_reader: Callable[[Mapping[str, Any]], ReleaseExecutionScope],
signer: RegisteredApprovalSigner,
*,
clock: Callable[[], datetime] = lambda: datetime.now(timezone.utc),
execution_ttl_seconds: int = 1200,
renewal_margin_seconds: int = 120,
):
if not callable(authority_reader) or not callable(execution_scope_reader):
raise TypeError("standing authority readers must be callable")
if not callable(getattr(signer, "sign", None)):
raise TypeError("registered approval signer must provide sign()")
if not callable(clock):
raise TypeError("clock must be callable")
if not isinstance(service, SilentReleaseActionService):
raise TypeError("service must be SilentReleaseActionService")
if service.execution_enabled and getattr(signer, "test_only", False) is True:
raise CapabilityAuthorizationError(
"test-only approval signer is forbidden for production execution"
)
if not isinstance(execution_ttl_seconds, int) or isinstance(execution_ttl_seconds, bool) or not 1 <= execution_ttl_seconds <= 3600:
raise ValueError("execution_ttl_seconds must be between 1 and 3600")
if not isinstance(renewal_margin_seconds, int) or isinstance(renewal_margin_seconds, bool) or not 0 <= renewal_margin_seconds < execution_ttl_seconds:
raise ValueError("renewal_margin_seconds must be shorter than the execution TTL")
self.service = service
self.authority_reader = authority_reader
self.execution_scope_reader = execution_scope_reader
self.signer = signer
self.clock = clock
self.execution_ttl_seconds = execution_ttl_seconds
self.renewal_margin_seconds = renewal_margin_seconds
self._lock_root = Path(service.approvals_root) / ".standing-authority-locks"
@staticmethod
def _result(status: str, context: AuthorityDecisionContext, approval=None):
# silent_release_execution imports this module during host composition.
from silent_release_execution import StandingAuthorizationResult
return StandingAuthorizationResult(
status=status, authority_context=context, approval=approval
)
@staticmethod
def _context(
authorization: StandingReleaseAuthorization | None,
scope_state: ExactScopeState,
*,
lifecycle_state: AuthorityLifecycleState | None = None,
boundary: OwnerBoundaryReason | None = None,
) -> AuthorityDecisionContext:
refs = () if authorization is None else authorization.evidence_refs
return AuthorityDecisionContext(
lifecycle_state=lifecycle_state
or (authorization.lifecycle_state if authorization else AuthorityLifecycleState.UNKNOWN),
scope_state=scope_state,
owner_boundary_reason=boundary,
evidence_refs=refs,
)
@staticmethod
def _binding(
authorization: StandingReleaseAuthorization, scope: ReleaseExecutionScope
) -> dict[str, Any]:
return {
"authorization_id": authorization.authorization_id,
"generation": authorization.generation,
"authorization_sha256": authorization.authorization_sha256,
"version": authorization.version,
"repository": authorization.repository,
"production_scope": authorization.production_scope,
"authority_base_oid": authorization.authority_base_oid,
"trust_root_sha256": authorization.trust_root_sha256,
"actor_id": scope.actor_id,
"mission_id": authorization.mission_id,
"run_id": authorization.run_id,
}
def _read(self, envelope: Mapping[str, Any]):
authorization = self.authority_reader()
# The trusted scope reader receives only the locally checked release
# identity. Caller-added envelope keys cannot become authority input.
query_keys = (
"approval_plan_id",
"artifact_sha256",
"candidate_fingerprint",
"candidate_id",
"deployment_plan_sha256",
"evidence_closure_oid",
"manifest_sha256",
"policy_sha256",
"release_plan_sha256",
"required_capabilities",
"schema",
"source_commit",
"tree_oid",
)
scope = self.execution_scope_reader(
{name: envelope[name] for name in query_keys if name in envelope}
)
if not isinstance(authorization, StandingReleaseAuthorization):
raise StandingAuthorityError("AUTHORITY_SNAPSHOT_INVALID", "authority reader returned an invalid snapshot")
if not isinstance(scope, ReleaseExecutionScope):
raise StandingAuthorityError("RELEASE_SCOPE_INVALID", "scope reader returned an invalid snapshot")
return authorization, scope
def _load_plan_truth(self, envelope: Mapping[str, Any]):
candidate_id = str(envelope.get("candidate_id") or "")
if not CANDIDATE_ID_RE.fullmatch(candidate_id):
raise StandingAuthorityError("APPROVAL_ENVELOPE_INVALID", "candidate id is invalid")
candidate = self.service.candidates.load(candidate_id)
plan = self.service.release_plans.load(candidate_id)
required = tuple(sorted(str(item) for item in envelope.get("required_capabilities", ())))
if not required or len(required) != len(set(required)) or not set(required).issubset(ALLOWED_CAPABILITIES):
raise StandingAuthorityError("APPROVAL_ENVELOPE_INVALID", "required capabilities are invalid")
plan_scope = canonical_plan_scope(plan, required)
expected_envelope = {
"candidate_id": candidate.candidate_id,
"candidate_fingerprint": candidate.fingerprint,
"source_commit": candidate.source_commit,
"tree_oid": candidate.tree_oid,
"artifact_sha256": candidate.artifact_sha256,
"manifest_sha256": candidate.manifest_sha256,
"deployment_plan_sha256": plan.deployment.sha256,
"release_plan_sha256": plan.sha256,
"approval_plan_id": plan.tag.approval_plan_id,
"evidence_closure_oid": plan.tag.evidence_closure_oid,
"policy_sha256": self.service.policy_sha256,
}
for name, expected in expected_envelope.items():
if envelope.get(name) != expected:
raise StandingAuthorityError("APPROVAL_ENVELOPE_STALE", f"approval envelope {name} is stale")
return candidate, plan, plan_scope, required
def _assert_current(
self,
authorization: StandingReleaseAuthorization,
scope: ReleaseExecutionScope,
envelope: Mapping[str, Any],
plan_scope: CanonicalPlanScope,
required: tuple[str, ...],
now: datetime,
) -> None:
if authorization.acceptance_complete:
raise StandingAuthorityError("AUTHORITY_ACCEPTED_AND_CLOSED", "trusted release acceptance is complete")
if authorization.lifecycle_state is not AuthorityLifecycleState.ACTIVE:
raise StandingAuthorityError(f"AUTHORITY_{authorization.lifecycle_state.value}", "standing authorization is not active")
if authorization.expires_at is not None and _parse_utc(authorization.expires_at) <= now:
raise StandingAuthorityError("AUTHORITY_EXPIRED", "standing authorization has expired")
if authorization.version != plan_scope.version or authorization.repository != plan_scope.repository:
raise StandingAuthorityError("AUTHORITY_RELEASE_SCOPE_MISMATCH", "release identity is outside standing authority")
if authorization.production_scope != plan_scope.sha256 or authorization.rollback_target != plan_scope.rollback_target:
raise StandingAuthorityError("AUTHORITY_PRODUCTION_SCOPE_MISMATCH", "production or rollback scope drifted")
if not set(required).issubset(authorization.allowed_capabilities):
raise StandingAuthorityError("AUTHORITY_CAPABILITY_DENIED", "required capability is outside standing authority")
exact_scope = (
scope.candidate_id == envelope["candidate_id"]
and scope.candidate_fingerprint == envelope["candidate_fingerprint"]
and scope.source_commit == envelope["source_commit"]
and scope.tree_oid == envelope["tree_oid"]
and scope.artifact_sha256 == envelope["artifact_sha256"]
and scope.manifest_sha256 == envelope["manifest_sha256"]
and scope.deployment_plan_sha256 == envelope["deployment_plan_sha256"]
and scope.release_plan_sha256 == envelope["release_plan_sha256"]
and scope.approval_plan_id == envelope["approval_plan_id"]
and scope.evidence_closure_oid == envelope["evidence_closure_oid"]
and scope.authority_base_oid == authorization.authority_base_oid
and scope.trust_root_sha256 == authorization.trust_root_sha256
and scope.actor_id in authorization.allowed_actor_ids
and scope.mission_id == authorization.mission_id
and scope.run_id == authorization.run_id
)
if not exact_scope:
raise StandingAuthorityError("AUTHORITY_EXACT_SCOPE_MISMATCH", "trusted release execution scope does not match")
durable_release_plan_id = self.service.sagas.load(
scope.candidate_id
).release_plan_id
if scope.release_plan_id != durable_release_plan_id:
raise StandingAuthorityError(
"RELEASE_PLAN_ID_MISMATCH",
"trusted scope does not match the durable Saga release plan",
)
if scope.lineage_root_candidate_fingerprint != authorization.lineage_root_candidate_fingerprint:
raise StandingAuthorityError("AUTHORITY_LINEAGE_ROOT_MISMATCH", "candidate lineage is not rooted in standing authority")
if scope.lineage_kind == "ROOT" and scope.candidate_fingerprint != authorization.lineage_root_candidate_fingerprint:
raise StandingAuthorityError("AUTHORITY_LINEAGE_REQUIRED", "changed candidate requires verified successor lineage")
if scope.effect_state == "UNKNOWN":
raise StandingAuthorityError("RELEASE_EFFECT_UNKNOWN", "release effect must be reconciled before issuance")
def _unsigned(
self,
authorization: StandingReleaseAuthorization,
scope: ReleaseExecutionScope,
plan: ReleasePlan,
required: tuple[str, ...],
now: datetime,
) -> SignedReleasePlanApproval:
expires = now + timedelta(seconds=self.execution_ttl_seconds)
if authorization.expires_at is not None:
expires = min(expires, _parse_utc(authorization.expires_at))
if expires <= now:
raise StandingAuthorityError("AUTHORITY_EXPIRED", "standing authorization cannot cover a new child")
issued_at = _format_utc(now)
return SignedReleasePlanApproval(
release_plan_id=scope.release_plan_id,
candidate_id=scope.candidate_id,
source_commit=scope.source_commit,
tree_oid=scope.tree_oid,
artifact_sha256=scope.artifact_sha256,
manifest_sha256=scope.manifest_sha256,
deployment_plan_sha256=plan.deployment.sha256,
policy_sha256=self.service.policy_sha256,
approval_plan_id=scope.approval_plan_id,
evidence_closure_oid=scope.evidence_closure_oid,
owner_approval_record_oid=scope.owner_approval_record_oid,
capabilities=required,
expires_at=_format_utc(expires),
nonce="standing-" + secrets.token_hex(16),
key_id=_text("signer.key_id", self.signer.key_id),
signature="",
owner_identity=authorization.owner_identity,
trust_domain=authorization.trust_domain,
issued_at=issued_at,
not_before=issued_at,
release_plan_sha256=plan.sha256,
authorization_binding=self._binding(authorization, scope),
)
@staticmethod
def _signed(value: SignedReleasePlanApproval | Mapping[str, Any]) -> SignedReleasePlanApproval:
return value if isinstance(value, SignedReleasePlanApproval) else SignedReleasePlanApproval.from_dict(value)
def _verify_signed(self, approval, candidate, plan, now):
verifier = self.service.approval_verifier
if verifier is None:
raise StandingAuthorityError("APPROVAL_VERIFIER_MISSING", "approval verifier is not configured")
verifier.verify_release_plan(approval, candidate, plan, self.service.policy_sha256, now=now)
def __call__(self, envelope: Mapping[str, Any]):
if not isinstance(envelope, Mapping):
raise TypeError("approval envelope must be a mapping")
candidate_id = str(envelope.get("candidate_id") or "")
lock_name = hashlib.sha256(candidate_id.encode("utf-8")).hexdigest() + ".lock"
lock_path = self._lock_root / lock_name
with release_file_lock(lock_path, blocking=True):
try:
candidate, plan, plan_scope, required = self._load_plan_truth(envelope)
now = _utc(self.clock())
authorization, scope = self._read(envelope)
self._assert_current(authorization, scope, envelope, plan_scope, required, now)
try:
existing = self.service.approvals.load(candidate_id)
except FileNotFoundError:
existing = None
if existing is not None:
# Expiry is the one expected reason an otherwise valid
# short child needs replacement. Verify its signature,
# immutable evidence, key and revocation state at the end
# of its signed window; a future/not-yet-valid or malformed
# child still fails the verifier.
# Expiry is exclusive. When renewing an already expired
# child, verify its signature at the final instant inside
# its original signed window rather than at expiry itself.
old_expiry = _parse_utc(existing.expires_at)
old_verification_time = min(
now, old_expiry - timedelta(microseconds=1)
)
self._verify_signed(existing, candidate, plan, old_verification_time)
self._check_binding(existing, authorization, scope, required)
if _parse_utc(existing.expires_at) - now > timedelta(seconds=self.renewal_margin_seconds):
return self._result("APPROVAL_ISSUED", self._context(authorization, ExactScopeState.IN_SCOPE), existing.to_dict())
unsigned = self._unsigned(authorization, scope, plan, required, now)
signed = self._signed(self.signer.sign(unsigned))
# A signer may only add its signature to the complete request.
if signed.canonical_payload != unsigned.canonical_payload:
raise StandingAuthorityError("SIGNER_MUTATED_APPROVAL", "registered signer changed the approval request")
self._verify_signed(signed, candidate, plan, now)
after_authorization, after_scope = self._read(envelope)
after_now = _utc(self.clock())
self._assert_current(after_authorization, after_scope, envelope, plan_scope, required, after_now)
if after_authorization != authorization or after_scope != scope:
raise StandingAuthorityError("AUTHORITY_CHANGED_DURING_SIGN", "authority changed while the child was signed")
self._verify_signed(signed, candidate, plan, after_now)
if existing is None:
self.service.submit_owner_approval(signed.to_dict())
else:
self.service.renew_owner_approval(candidate_id, signed.to_dict(), expected_fingerprint=existing.fingerprint)
persisted = self.service.approvals.load(candidate_id)
if persisted.fingerprint != signed.fingerprint:
raise StandingAuthorityError("APPROVAL_PERSISTENCE_MISMATCH", "persisted approval differs from signed approval")
return self._result("APPROVAL_ISSUED", self._context(authorization, ExactScopeState.IN_SCOPE), persisted.to_dict())
except StandingAuthorityError as exc:
authorization = locals().get("authorization")
closed_states = {
"AUTHORITY_ACCEPTED_AND_CLOSED": AuthorityLifecycleState.CLOSED,
"AUTHORITY_EXPIRED": AuthorityLifecycleState.CLOSED,
"AUTHORITY_REVOKED": AuthorityLifecycleState.REVOKED,
"AUTHORITY_CLOSED": AuthorityLifecycleState.CLOSED,
"AUTHORITY_CANCELLED": AuthorityLifecycleState.CANCELLED,
}
if exc.code in closed_states:
context = self._context(
authorization,
ExactScopeState.IN_SCOPE,
lifecycle_state=closed_states[exc.code],
)
return self._result("AUTHORIZATION_DENIED", context)
if exc.code in {
"AUTHORITY_UNKNOWN",
"AUTHORITY_SNAPSHOT_INVALID",
"AUTHORITY_CHANGED_DURING_SIGN",
}:
return self._result(
"WAITING_AUTHORITY",
self._context(
authorization,
ExactScopeState.UNKNOWN,
lifecycle_state=AuthorityLifecycleState.UNKNOWN,
),
)
if exc.code.startswith("AUTHORITY_") and exc.code not in {"AUTHORITY_SNAPSHOT_INVALID", "AUTHORITY_CHANGED_DURING_SIGN"}:
goal_change = {
"AUTHORITY_RELEASE_SCOPE_MISMATCH",
"AUTHORITY_PRODUCTION_SCOPE_MISMATCH",
"AUTHORITY_CAPABILITY_DENIED",
"AUTHORITY_LINEAGE_REQUIRED",
}
context = self._context(
authorization,
ExactScopeState.OUT_OF_SCOPE,
boundary=(
OwnerBoundaryReason.GOAL_CHANGE
if exc.code in goal_change
else OwnerBoundaryReason.TRUST_BOUNDARY_CHANGE
),
)
return self._result("AUTHORIZATION_DENIED", context)
context = self._context(authorization, ExactScopeState.UNKNOWN)
return self._result("WAITING_AUTHORITY", context)
except Exception:
return self._result("WAITING_AUTHORITY", self._context(None, ExactScopeState.UNKNOWN))
def _check_binding(self, approval, authorization, scope, required=None):
binding = getattr(approval, "authorization_binding", None)
if not isinstance(binding, Mapping) or set(binding) != _BINDING_KEYS:
raise StandingAuthorityError("AUTHORIZATION_BINDING_INVALID", "approval lacks an exact standing authorization binding")
if dict(binding) != self._binding(authorization, scope):
raise StandingAuthorityError("AUTHORIZATION_BINDING_STALE", "approval standing authorization binding is stale")
if approval.owner_identity != authorization.owner_identity or approval.trust_domain != authorization.trust_domain:
raise StandingAuthorityError("AUTHORIZATION_IDENTITY_MISMATCH", "approval owner or trust binding is stale")
if (
approval.release_plan_id != scope.release_plan_id
or approval.evidence_closure_oid != scope.evidence_closure_oid
or approval.owner_approval_record_oid != scope.owner_approval_record_oid
):
raise StandingAuthorityError(
"AUTHORIZATION_OPERATION_BINDING_MISMATCH",
"approval operation identity is stale",
)
if required is not None and tuple(sorted(approval.capabilities)) != tuple(required):
raise StandingAuthorityError(
"AUTHORIZATION_CAPABILITY_BINDING_MISMATCH",
"approval capability set does not match the exact plan scope",
)
def check_current(self, approval: SignedReleasePlanApproval, capability: str, operation: str) -> None:
if not isinstance(approval, SignedReleasePlanApproval):
raise StandingAuthorityError("APPROVAL_INVALID", "current authority guard requires a signed approval")
_text("operation", operation)
if capability not in approval.capabilities:
raise StandingAuthorityError("CAPABILITY_NOT_GRANTED", "approval does not grant the requested capability")
envelope = self.service.get_approval_envelope(approval.candidate_id)
candidate, plan, plan_scope, required = self._load_plan_truth(envelope)
now = _utc(self.clock())
authorization, scope = self._read(envelope)
self._assert_current(authorization, scope, envelope, plan_scope, required, now)
self._check_binding(approval, authorization, scope, required)
self._verify_signed(approval, candidate, plan, now)
@@ -0,0 +1,91 @@
"""Distinct filename/path contracts and no-follow writes at the filesystem edge."""
from __future__ import annotations
import os
import ipaddress
from pathlib import Path, PurePosixPath
import re
import secrets
import stat
def require_isolated_dev_profile(host: str) -> None:
if os.getenv("AIOS_GITOPS_DEV_ALLOW", "0") != "1":
return
try:
loopback = ipaddress.ip_address(host).is_loopback
except (TypeError, ValueError):
loopback = False
if os.getenv("AIOS_RUNTIME_PROFILE") != "isolated-staging" or not loopback:
raise RuntimeError("DEV_ALLOW requires isolated-staging and loopback")
def safe_segment(value: str, *, max_length: int = 160) -> str:
if (not isinstance(value, str) or not value or len(value) > max_length
or value in (".", "..") or value.endswith((".", " "))
or re.fullmatch(r"[A-Za-z0-9_@.-]+", value) is None
or value.split(".")[0].upper() in {"CON", "PRN", "AUX", "NUL", *[f"COM{i}" for i in range(1, 10)], *[f"LPT{i}" for i in range(1, 10)]}):
raise ValueError("invalid single path segment")
return value
def relative_parts(value: str) -> tuple[str, ...]:
if not isinstance(value, str) or "\\" in value or ":" in value or value.startswith("/"):
raise ValueError("invalid relative path")
parts = tuple(value.split("/"))
for part in parts:
safe_segment(part, max_length=255)
if parts[0] == ".git":
raise ValueError("Git metadata path forbidden")
return parts
def contained_path(root: Path, segment: str) -> Path:
safe_segment(segment)
root = Path(root)
if root.is_symlink():
raise ValueError("symlink root forbidden")
path = root / segment
if path.is_symlink() or not path.resolve().is_relative_to(root.resolve()):
raise ValueError("path escapes root")
return path
def write_text_no_follow(root: Path, relative: str, content: str) -> None:
"""Atomic POSIX write using directory descriptors; no lexical-only fallback."""
parts = relative_parts(relative)
if os.name != "posix" or not hasattr(os, "O_NOFOLLOW"):
raise OSError("secure directory-relative writes require POSIX no-follow support")
root = Path(root)
flags = os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW
current = os.open(root, flags)
temporary = ".c6-" + secrets.token_hex(16)
try:
for part in parts[:-1]:
try:
os.mkdir(part, mode=0o750, dir_fd=current)
except FileExistsError:
pass
child = os.open(part, flags, dir_fd=current)
os.close(current)
current = child
try:
info = os.stat(parts[-1], dir_fd=current, follow_symlinks=False)
if not stat.S_ISREG(info.st_mode) or info.st_nlink != 1:
raise ValueError("nonregular or multiply-linked target forbidden")
except FileNotFoundError:
pass
fd = os.open(temporary, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW,
0o600, dir_fd=current)
try:
with os.fdopen(fd, "w", encoding="utf-8", newline="\n") as stream:
stream.write(content); stream.flush(); os.fsync(stream.fileno())
os.replace(temporary, parts[-1], src_dir_fd=current, dst_dir_fd=current)
os.fsync(current)
finally:
try:
os.unlink(temporary, dir_fd=current)
except FileNotFoundError:
pass
finally:
os.close(current)
+438 -36
View File
@@ -22,6 +22,16 @@ from cryptography.hazmat.primitives.asymmetric.ed25519 import (
)
from silent_release_controller import _sanitize, utc_now
from task_plane.governance_control_kernel import (
AutonomousDecisionEngine,
AuthorityDecisionContext,
AuthorityLifecycleState,
DecisionInput,
DecisionEffectIntent,
ExactScopeState,
NextAction,
OwnerBoundaryReason,
)
from silent_release_hardening import (
BuilderAttestation,
CapabilityAuthorizationError,
@@ -33,9 +43,60 @@ from silent_release_hardening import (
_canonical_json,
_durable_json,
_safe_child,
release_file_lock,
)
@dataclasses.dataclass(frozen=True)
class StandingAuthorizationResult:
"""Typed result from a current authority reader plus formal issuer.
This object is routing evidence, not an authorization token. A result that
carries an approval still has to pass ``SignedApprovalVerifier`` inside the
release service before the saga can advance.
"""
status: str
authority_context: AuthorityDecisionContext
approval: Mapping[str, Any] | None = None
effect_intent: DecisionEffectIntent = DecisionEffectIntent.SIDE_EFFECT
def __post_init__(self) -> None:
allowed = {
"APPROVAL_ISSUED",
"AUTHORIZATION_DENIED",
"OWNER_REQUIRED",
"WAITING_AUTHORITY",
}
if self.status not in allowed:
raise ValueError("invalid standing authorization status")
if not isinstance(self.authority_context, AuthorityDecisionContext):
raise ValueError("authority_context must be AuthorityDecisionContext")
if self.effect_intent is not DecisionEffectIntent.SIDE_EFFECT:
raise ValueError("standing authorization must be a SIDE_EFFECT decision")
if self.status == "APPROVAL_ISSUED":
if not isinstance(self.approval, Mapping):
raise ValueError("issued authorization requires a signed approval")
if (
self.authority_context.lifecycle_state
is not AuthorityLifecycleState.ACTIVE
or self.authority_context.scope_state is not ExactScopeState.IN_SCOPE
or self.authority_context.owner_boundary_reason is not None
):
raise ValueError("issued approval is inconsistent with authority context")
elif self.approval is not None:
raise ValueError("non-issued authorization cannot carry an approval")
if self.status == "OWNER_REQUIRED" and (
self.authority_context.owner_boundary_reason is None
):
raise ValueError("OWNER_REQUIRED needs an explicit boundary reason")
StandingAuthorizationBroker = Callable[
[Mapping[str, Any]], StandingAuthorizationResult | Mapping[str, Any]
]
class BuilderSigner(Protocol):
@property
def key_id(self) -> str:
@@ -262,38 +323,56 @@ class LeaderLock:
def __init__(self, path: Path):
self.path = Path(path)
self.handle = None
self._lock_context = None
def __enter__(self):
self.path.parent.mkdir(parents=True, exist_ok=True)
os.chmod(self.path.parent, 0o700)
self.handle = self.path.open("a+", encoding="utf-8")
os.chmod(self.path, 0o600)
self._lock_context = release_file_lock(self.path, blocking=False)
try:
import fcntl
fcntl.flock(
self.handle.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB
)
self.handle = self._lock_context.__enter__()
except BlockingIOError as exc:
self.handle.close()
self.handle = None
self._lock_context = None
raise RuntimeError("silent release leader is already active") from exc
return self
def __exit__(self, exc_type, exc, tb):
if self.handle is not None:
try:
import fcntl
fcntl.flock(self.handle.fileno(), fcntl.LOCK_UN)
self._lock_context.__exit__(exc_type, exc, tb)
finally:
self.handle.close()
self.handle = None
self._lock_context = None
class PersistentSilentReleaseController:
"""Idempotent main-event controller with durable startup/recovery reports."""
_UNKNOWN_EFFECT_ACTIONS = frozenset(
{
"reconcile_baseline_registry",
"reconcile_release",
"reconcile_tag",
"resume_rollback",
"resume_runtime_switch",
"verify_runtime",
}
)
_RECOVERY_SIDE_EFFECT_STATES = frozenset(
{
"APPROVED",
"PRE_SWITCH_REVALIDATING",
"SWITCH_PENDING",
"SWITCHED",
"RUNTIME_VERIFYING",
"RUNTIME_VERIFIED",
"TAG_PENDING",
"TAG_VERIFIED",
"BASELINE_REGISTRY_PENDING",
"ROLLBACK_PENDING",
"RECONCILIATION_REQUIRED",
}
)
def __init__(
self,
root: Path,
@@ -301,14 +380,42 @@ class PersistentSilentReleaseController:
*,
main_probe: Callable[[], str],
recovery_worker: ReleaseRecoveryWorker | None = None,
owner_approval_broker: StandingAuthorizationBroker | None = None,
decision_engine: AutonomousDecisionEngine | None = None,
):
self.root = Path(root)
self.service = service
self.main_probe = main_probe
self.recovery_worker = recovery_worker
self.owner_approval_broker = owner_approval_broker
self.decision_engine = decision_engine or AutonomousDecisionEngine()
self.leader_path = self.root / "controller.leader.lock"
self.status_path = self.root / "controller-status.json"
def run_once_safely(self) -> dict[str, Any]:
"""Keep read-only diagnostics available when a control tick fails.
A dependency failure never grants write permission or repairs a corrupt
journal. Only a later successful, fully checked tick may advance work.
Exception text can contain credentials, so persist its type only.
"""
try:
return self.run_once()
except Exception as exc:
result = {
"error": type(exc).__name__,
"execution_enabled": self.service.execution_enabled,
"shadow_mode": not self.service.execution_enabled,
# A failed production tick may have reached an adapter before
# losing its acknowledgement. Never assert zero effects here.
"side_effects_executed": (
None if self.service.execution_enabled else False
),
"status": "FAIL_CLOSED",
}
_durable_json(self.status_path, result)
return _sanitize(result)
def startup_verify(self) -> dict[str, Any]:
checked = 0
failed = 0
@@ -329,6 +436,196 @@ class PersistentSilentReleaseController:
_durable_json(self.status_path, status)
return status
@classmethod
def _unknown_effects_pending(cls, recovery: Mapping[str, Any]) -> bool:
return any(
isinstance(item, Mapping)
and item.get("recovery_action") in cls._UNKNOWN_EFFECT_ACTIONS
for item in recovery.get("sagas", ())
)
@classmethod
def _recovery_side_effect_candidates(
cls, recovery: Mapping[str, Any]
) -> tuple[str, ...]:
return tuple(
str(item["candidate_id"])
for item in recovery.get("sagas", ())
if isinstance(item, Mapping)
and item.get("state") in cls._RECOVERY_SIDE_EFFECT_STATES
and item.get("candidate_id")
)
@staticmethod
def _authority_context_payload(
context: AuthorityDecisionContext,
) -> dict[str, Any]:
return {
"lifecycle_state": context.lifecycle_state.value,
"scope_state": context.scope_state.value,
"owner_boundary_reason": (
context.owner_boundary_reason.value
if context.owner_boundary_reason is not None
else None
),
"evidence_refs": list(context.evidence_refs),
}
def _try_standing_authorization(self, candidate_id: str) -> dict[str, Any]:
try:
existing = self.service.approvals.load(candidate_id)
except FileNotFoundError:
existing = None
# Default-off compatibility: an already accepted exact approval may be
# revalidated, but absence of a current broker never derives new
# authority. When a broker is configured it must be consulted on every
# side-effect decision so a still-valid short approval cannot mask a
# revoked/closed root authorization or an exact-scope change.
if self.owner_approval_broker is None and existing is not None:
try:
accepted = self.service.submit_owner_approval(existing.to_dict())
except Exception:
accepted = None
else:
return {
"candidate_id": candidate_id,
"approval_fingerprint": accepted.get("fingerprint"),
"status": "APPROVED",
"idempotent": True,
}
if self.owner_approval_broker is None:
return {
"candidate_id": candidate_id,
"reason_code": "OWNER_APPROVAL_BACKEND_MISSING",
"status": "WAITING_AUTHORITY",
}
envelope = self.service.get_approval_envelope(candidate_id)
try:
issued = self.owner_approval_broker(dict(envelope))
except Exception as exc:
code = str(getattr(exc, "code", "") or type(exc).__name__)
return {
"candidate_id": candidate_id,
"reason_code": code,
"status": "WAITING_AUTHORITY",
}
authority_context = None
decision = None
if isinstance(issued, StandingAuthorizationResult):
authority_context = issued.authority_context
context_payload = self._authority_context_payload(authority_context)
decision = self.decision_engine.decide(
DecisionInput(
goal_id=candidate_id,
admission_ready=True,
effect_intent=DecisionEffectIntent.SIDE_EFFECT,
authority_context=authority_context,
evidence_refs=authority_context.evidence_refs,
)
)
decision_payload = decision.to_dict()
if decision.next_valid_action is NextAction.DENY_EFFECT:
return {
"candidate_id": candidate_id,
"authority_context": context_payload,
"decision": decision_payload,
"reason_code": decision.blocker_code
or "STANDING_AUTHORIZATION_DENIED",
"status": "AUTHORIZATION_DENIED",
}
if decision.next_valid_action is NextAction.OWNER_BOUNDARY:
boundary_reason = decision.owner_boundary_reason
if boundary_reason is None:
return {
"candidate_id": candidate_id,
"authority_context": context_payload,
"decision": decision_payload,
"reason_code": "OWNER_BOUNDARY_REASON_MISSING",
"status": "WAITING_AUTHORITY",
}
return {
"candidate_id": candidate_id,
"authority_context": context_payload,
"decision": decision_payload,
"reason_code": boundary_reason.value,
"status": "OWNER_REQUIRED",
}
if decision.next_valid_action is not NextAction.CREATE_OR_RESUME_NEXT_CHILD:
return {
"candidate_id": candidate_id,
"authority_context": context_payload,
"decision": decision_payload,
"reason_code": decision.blocker_code
or decision.next_valid_action.value,
"status": "WAITING_AUTHORITY",
}
if issued.status != "APPROVAL_ISSUED":
status = (
"AUTHORIZATION_DENIED"
if issued.status == "AUTHORIZATION_DENIED"
else "WAITING_AUTHORITY"
)
return {
"candidate_id": candidate_id,
"authority_context": context_payload,
"decision": decision_payload,
"reason_code": issued.status,
"status": status,
}
approval = dict(issued.approval or {})
elif isinstance(issued, Mapping):
# Backward-compatible C5 callback contract. Mapping coverage does
# not grant authority; submit_owner_approval verifies the signed
# object against the current exact candidate and ReleasePlan.
approval = dict(issued)
else:
return {
"candidate_id": candidate_id,
"reason_code": "OWNER_APPROVAL_MISSING",
"status": "WAITING_AUTHORITY",
}
try:
accepted = self.service.submit_owner_approval(approval)
except Exception as exc:
code = str(getattr(exc, "code", "") or type(exc).__name__)
result = {
"candidate_id": candidate_id,
"reason_code": code,
"status": "WAITING_AUTHORITY",
}
if authority_context is not None:
result["authority_context"] = self._authority_context_payload(
authority_context
)
if decision is not None:
result["decision"] = decision.to_dict()
return result
if accepted.get("status") not in {"VERIFIED_AND_STORED", "IDEMPOTENT"}:
return {
"candidate_id": candidate_id,
"reason_code": "APPROVAL_NOT_VERIFIED",
"status": "WAITING_AUTHORITY",
}
result = {
"candidate_id": candidate_id,
"approval_fingerprint": accepted.get("fingerprint"),
"idempotent": accepted.get("status") == "IDEMPOTENT",
"saga_state": accepted.get("saga_state"),
"status": "APPROVED",
}
if authority_context is not None:
result["authority_context"] = self._authority_context_payload(
authority_context
)
if decision is not None:
result["decision"] = decision.to_dict()
return result
def run_once(self) -> dict[str, Any]:
with LeaderLock(self.leader_path):
startup = self.startup_verify()
@@ -345,26 +642,98 @@ class PersistentSilentReleaseController:
}
_durable_json(self.status_path, result)
return _sanitize(result)
# ReleaseRecoveryWorker can resume an externally visible effect.
# Re-resolve current root lifecycle and exact scope for every such
# candidate before invoking it; executor/verifier checks remain the
# final fence. This also prevents a cached short approval from
# bypassing root revocation during restart recovery.
recovery_status = self.service.get_recovery_status()
unknown_effect = self._unknown_effects_pending(recovery_status)
recovery_authority_decisions: list[dict[str, Any]] = []
recovery_candidates = (
self._recovery_side_effect_candidates(recovery_status)
if self.recovery_worker is not None
else ()
)
for candidate_id in recovery_candidates:
authorization = self._try_standing_authorization(candidate_id)
recovery_authority_decisions.append(authorization)
if authorization.get("status") != "APPROVED":
notification = None
if (
not unknown_effect
and authorization.get("status") == "OWNER_REQUIRED"
):
notification = self.service.ensure_owner_notification(
candidate_id,
reason_code=str(authorization["reason_code"]),
)
result = {
"authority_decision": authorization,
"execution_enabled": self.service.execution_enabled,
"intent": None,
"notification": notification,
"recovery": recovery_status,
"recovery_authority_decisions": (
recovery_authority_decisions
),
"shadow_mode": not self.service.execution_enabled,
"side_effects_executed": False,
"startup": startup,
"status": (
"RECONCILIATION_REQUIRED"
if unknown_effect
else authorization.get("status")
),
}
_durable_json(self.status_path, result)
return _sanitize(result)
recovery = (
self.recovery_worker.run_once()
if self.recovery_worker is not None
else recovery_status
)
recovery_status = self.service.get_recovery_status()
if self._unknown_effects_pending(recovery_status):
result = {
"authority_decision": None,
"execution_enabled": self.service.execution_enabled,
"intent": None,
"notification": None,
"recovery": recovery,
"recovery_authority_decisions": (
recovery_authority_decisions
),
"shadow_mode": not self.service.execution_enabled,
"side_effects_executed": False,
"startup": startup,
"status": "RECONCILIATION_REQUIRED",
}
_durable_json(self.status_path, result)
return _sanitize(result)
source_commit = str(self.main_probe())
event_id = f"main-{source_commit}"
intent = self.service.create_release_intent(
{"event_id": event_id, "source_commit": source_commit}
)
notification = None
authorization = None
if intent.get("candidate_id"):
notification = self.service.ensure_owner_notification(
str(intent["candidate_id"])
)
recovery = (
self.recovery_worker.run_once()
if self.recovery_worker is not None
else self.service.get_recovery_status()
)
candidate_id = str(intent["candidate_id"])
authorization = self._try_standing_authorization(candidate_id)
if authorization.get("status") == "OWNER_REQUIRED":
notification = self.service.ensure_owner_notification(
candidate_id,
reason_code=str(authorization["reason_code"]),
)
result = {
"authority_decision": authorization,
"execution_enabled": self.service.execution_enabled,
"intent": intent,
"notification": notification,
"recovery": recovery,
"recovery_authority_decisions": recovery_authority_decisions,
"shadow_mode": not self.service.execution_enabled,
"side_effects_executed": False,
"startup": startup,
@@ -373,6 +742,51 @@ class PersistentSilentReleaseController:
return _sanitize(result)
def compose_standing_release_controller(
root: Path,
service: SilentReleaseActionService,
*,
main_probe: Callable[[], str],
authority_reader: Callable[[], Any],
execution_scope_reader: Callable[[Mapping[str, Any]], Any],
signer: Any,
recovery_worker: ReleaseRecoveryWorker | None = None,
execution_ttl_seconds: int = 1200,
renewal_margin_seconds: int = 120,
clock: Callable[[], Any] | None = None,
) -> PersistentSilentReleaseController:
"""Compose trusted host adapters into the existing controller and effects.
Readers must return the validated snapshot types defined by
``release_standing_authority``. The signer must already belong to the
service's release approval trust domain. This function neither registers
keys nor performs network/authority reads during cold startup.
"""
from release_standing_authority import StandingReleaseAuthorizationBroker
options = {
"execution_ttl_seconds": execution_ttl_seconds,
"renewal_margin_seconds": renewal_margin_seconds,
}
if clock is not None:
options["clock"] = clock
broker = StandingReleaseAuthorizationBroker(
service,
authority_reader,
execution_scope_reader,
signer,
**options,
)
service.install_current_authority_guard(broker.check_current)
return PersistentSilentReleaseController(
root,
service,
main_probe=main_probe,
recovery_worker=recovery_worker,
owner_approval_broker=broker,
)
class ShadowActionHandler(BaseHTTPRequestHandler):
service: SilentReleaseActionService
controller: PersistentSilentReleaseController
@@ -485,24 +899,12 @@ def serve_shadow(
service,
main_probe=git_main_probe(repository),
)
controller.run_once()
controller.run_once_safely()
stop = threading.Event()
def controller_loop() -> None:
while not stop.wait(poll_seconds):
try:
controller.run_once()
except Exception as exc: # fail closed and keep the API available
_durable_json(
controller.status_path,
{
"error": type(exc).__name__,
"execution_enabled": False,
"shadow_mode": True,
"side_effects_executed": False,
"status": "FAIL_CLOSED",
},
)
controller.run_once_safely()
handler = type(
"ConfiguredShadowActionHandler",
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,271 @@
"""Exact source-only admission composition within the existing Bridge.
This module neither installs itself nor issues authority. A host activation must
pin an Owner-signed policy and an independently verified epoch. The existing
Task admission store, V3 producer, Task Plane and principal verifier are reused.
"""
from __future__ import annotations
import hashlib
import base64
import json
from pathlib import Path
from typing import Any, Mapping
from aios_single_host_authority import (
SignedArtifact, canonical_json_bytes, _open_secure,
)
from aios_task_admission_bootstrap import TaskAdmissionError
from aios_task_admission_bridge_contract import TaskAdmissionBridgeContract
from aios_task_admission_live_adapters import TaskAdmissionRuntimeBundle
from governed_source_publication_policy import (
ScopedSourcePublicationPolicyAdmission, SourcePublicationPolicyVerifier,
)
def _read_object(path: Path) -> tuple[bytes, dict[str, Any]]:
raw = _open_secure(path, private=False, require_root_owner=True, maximum=1024 * 1024)
value = json.loads(raw)
if not isinstance(value, dict):
raise ValueError("SOURCE_ACTIVATION_OBJECT_REQUIRED")
return raw, value
class ScopedPrincipalProvider:
"""Policy revocation also fences principals from already admitted Tasks."""
def __init__(self, guarded: ScopedSourcePublicationPolicyAdmission, delegate: Any):
self.guarded = guarded
self.delegate = delegate
def readiness(self) -> Mapping[str, Any]:
try:
self.guarded.verifier.require(self.guarded.artifact, self.guarded.request)
return self.delegate.readiness()
except Exception as exc:
return {"ready": False, "mode": "source-publication-only", "error": str(exc)}
def __call__(self, transport: Mapping[str, Any], request: Mapping[str, Any]) -> Any:
grant = self.delegate.store.get_grant_by_task_id(str(request.get("task_id") or ""))
if grant is None or grant.parent_epoch_id != self.delegate.epoch_id:
raise TaskAdmissionError("SOURCE_PRINCIPAL_GRANT_REQUIRED", 403)
# status rechecks the signed policy, exact durable envelope and original
# signed epoch. The delegate then verifies the live TaskLease/state.
self.guarded.status({"envelope_id": grant.envelope_id,
"delegation_epoch_id": grant.parent_epoch_id})
return self.delegate(transport, request)
class _AdmissionContext:
"""Metadata only for legacy hosts retaining their signed principal verifier."""
def __init__(self, bundle):
self.store, self.authorization = bundle.store, bundle.authorization
self.task_port, self.epoch_id = bundle.task_port, bundle.epoch_id
def readiness(self):
return {"ready": False, "error": "REGISTERED_SIGNED_PRINCIPAL_VERIFIER_REQUIRED"}
def __call__(self, *_args, **_kwargs):
raise TaskAdmissionError("REGISTERED_SIGNED_PRINCIPAL_VERIFIER_REQUIRED", 403)
def guard_source_runtime(
runtime: TaskAdmissionRuntimeBundle, *, artifact: SignedArtifact,
verifier: SourcePublicationPolicyVerifier, expected_agent_id: str,
) -> TaskAdmissionRuntimeBundle:
service = runtime.service
if service is None:
raise ValueError("SOURCE_ACTIVATION_BOOTSTRAP_REQUIRED")
guarded = ScopedSourcePublicationPolicyAdmission(
verifier, bootstrap=service, artifact=artifact,
expected_agent_id=expected_agent_id,
)
# Build the typed request through the same exact-envelope guard used by the
# HTTP contract, instead of accepting a caller-supplied request projection.
payload = artifact.payload
request = guarded._request_for_raw({
"repository": payload["repository"],
"authority_base_oid": payload["authority_base_oid"],
"target_branch": payload["target_branch"], "generation": payload["generation"],
"execution_digest": payload["execution_digest"], "risk": payload["risk"],
"file_allowlist": payload["exact_files"], "requested_capabilities": payload["capabilities"],
})
guarded.request = request
verifier.require(artifact, request)
principal = runtime.principal_verifier or _AdmissionContext(runtime)
epoch = principal.authorization.verify_delegation_epoch(principal.epoch_id)
# The complete per-envelope check runs again on authorization and every
# principal use; reject a production-capable epoch already at composition.
if (getattr(epoch, "production_scope", None) != "none"
or getattr(epoch, "destructive_policy", None) != "forbidden"):
raise TaskAdmissionError("SOURCE_ACTIVATION_EPOCH_SCOPE_DENIED", 403)
return TaskAdmissionRuntimeBundle(
handlers=TaskAdmissionBridgeContract(guarded).handlers(),
principal_verifier=ScopedPrincipalProvider(guarded, principal),
service=guarded,
)
def build_source_runtime_from_activation_file(path: Path, *, post_admission: Any,
ordinary_activation_file: Path):
from aios_task_admission_live_adapters import (
build_task_admission_runtime_from_activation_file,
load_owner_approval_verifier,
)
_, config = _read_object(path)
if set(config) != {"schema", "policy_artifact", "policy_sha256", "policy_revocations",
"admission_activation", "expected_agent_id"} or config["schema"] != "aios.source-publication-host-activation.v1":
raise ValueError("SOURCE_ACTIVATION_FIELDS_INVALID")
for key in ("policy_artifact", "policy_revocations", "admission_activation"):
value = config[key]
if not isinstance(value, str) or not value.startswith("/") or ".." in Path(value).parts:
raise ValueError("SOURCE_ACTIVATION_PATH_INVALID")
raw, signed = _read_object(Path(config["policy_artifact"]))
if hashlib.sha256(raw).hexdigest() != config["policy_sha256"]:
raise ValueError("SOURCE_ACTIVATION_POLICY_HASH_MISMATCH")
if set(signed) != {"payload", "signature", "key_id", "namespace"}:
raise ValueError("SOURCE_ACTIVATION_ARTIFACT_INVALID")
artifact = SignedArtifact(canonical_payload=canonical_json_bytes(signed["payload"]), **signed)
_, ordinary_config = _read_object(ordinary_activation_file)
_, source_config = _read_object(Path(config["admission_activation"]))
for field in ("state_db", "task_api_url", "task_control_token_file", "owner_verifier_module"):
if not ordinary_config.get(field) or source_config.get(field) != ordinary_config[field]:
raise ValueError("SOURCE_ACTIVATION_CONTROL_PLANE_BINDING_MISMATCH")
def not_revoked(payload: Mapping[str, Any]) -> bool:
_, value = _read_object(Path(config["policy_revocations"]))
if (set(value) != {"schema", "revoked_nonces"}
or value["schema"] != "aios.source-publication-revocations.v1"
or not isinstance(value["revoked_nonces"], list)
or any(not isinstance(nonce, str) or not nonce for nonce in value["revoked_nonces"])):
return False
return payload["nonce"] not in value["revoked_nonces"]
runtime = build_task_admission_runtime_from_activation_file(
Path(config["admission_activation"]), post_admission=post_admission)
verifier = SourcePublicationPolicyVerifier(
load_owner_approval_verifier(Path(source_config["owner_verifier_module"])),
revocation_check=not_revoked,
expected_policy_digest=runtime.service.policy.normalized_policy_digest(),
)
guarded_runtime = guard_source_runtime(runtime, artifact=artifact, verifier=verifier,
expected_agent_id=config["expected_agent_id"])
payload = artifact.payload
plan_path = Path(source_config["execution_plan_dir"]) / (payload["execution_digest"] + ".json")
plan_bytes, plan = _read_object(plan_path)
if (hashlib.sha256(plan_bytes).hexdigest() != payload["execution_digest"]
or canonical_json_bytes(plan) != plan_bytes):
raise ValueError("SOURCE_ACTIVATION_PLAN_DIGEST_MISMATCH")
hashes = plan.get("file_sha256")
if not isinstance(hashes, dict) or set(hashes) != set(payload["exact_files"]):
raise ValueError("SOURCE_ACTIVATION_FILE_HASH_SET_MISMATCH")
for field in ("repository", "authority_base_oid", "target_branch", "generation", "risk"):
if plan.get(field) != payload[field]:
raise ValueError("SOURCE_ACTIVATION_PLAN_SCOPE_MISMATCH")
if sorted(plan.get("capabilities", [])) != sorted(payload["capabilities"]):
raise ValueError("SOURCE_ACTIVATION_PLAN_CAPABILITIES_MISMATCH")
if not isinstance(plan.get("task_id"), str) or not plan["task_id"]:
raise ValueError("SOURCE_ACTIVATION_PLAN_TASK_REQUIRED")
guarded_runtime.service.expected_file_sha256 = dict(hashes)
guarded_runtime.service.expected_task_id = plan["task_id"]
return guarded_runtime
class AdmissionRuntimeRouter:
"""Route an exact branch/epoch without widening the ordinary policy."""
def __init__(self, ordinary: TaskAdmissionRuntimeBundle, source: TaskAdmissionRuntimeBundle):
self.ordinary = ordinary
self.source = source
self.guard = source.service
self.store = self.guard.bootstrap.store
def _source_request(self, operation: str, request: Mapping[str, Any]) -> bool:
if operation == "getTaskAdmissionEnvelope":
return request.get("target_branch") == self.guard.artifact.payload["target_branch"]
envelope_id = str(request.get("envelope_id") or "")
row = self.store.get_envelope(envelope_id)
if row is None:
# Never fall through after finding a source envelope, including
# when it has expired or been revoked.
return False
envelope = self.guard.bootstrap._load_envelope(envelope_id)
return envelope.target_branch == self.guard.artifact.payload["target_branch"]
def handlers(self) -> dict[str, Any]:
def dispatch(operation: str, request: Mapping[str, Any]):
runtime = self.source if self._source_request(operation, request) else self.ordinary
return runtime.handlers[operation](request)
return {op: (lambda request, op=op: dispatch(op, request)) for op in self.source.handlers}
def readiness(self) -> Mapping[str, Any]:
return {"mode": "scoped-admission-router", "ordinary": self.ordinary.principal_verifier.readiness(),
"source_publication": self.source.principal_verifier.readiness()}
def __call__(self, transport: Mapping[str, Any], request: Mapping[str, Any]):
grant = self.store.get_grant_by_task_id(str(request.get("task_id") or ""))
provider = (self.source.principal_verifier if grant is not None
and grant.target_branch == self.guard.artifact.payload["target_branch"]
else self.ordinary.principal_verifier)
return provider(transport, request)
def bundle(self) -> TaskAdmissionRuntimeBundle:
return TaskAdmissionRuntimeBundle(handlers=self.handlers(), principal_verifier=self)
class ExistingPrincipalPolicyFence:
"""Add a C5 policy fence while retaining the registered signed verifier.
Layered staging hosts use a signed TaskPrincipal verifier with their own
storage scope. They must not be migrated to a server attestation merely to
add source admission. Every request still reaches the original verifier.
"""
def __init__(self, delegate: Any, source_runtime: TaskAdmissionRuntimeBundle):
self.delegate = delegate
self.guard = source_runtime.service
def __getattr__(self, name: str):
return getattr(self.delegate, name)
def __call__(self, transport: Mapping[str, Any], request: Mapping[str, Any]):
grant = self.guard.bootstrap.store.get_grant_by_task_id(str(request.get("task_id") or ""))
if grant is not None and grant.target_branch == self.guard.artifact.payload["target_branch"]:
self.guard.status({"envelope_id": grant.envelope_id,
"delegation_epoch_id": grant.parent_epoch_id})
if transport.get("operation") in {"commitFilesGoverned", "commitFileGoverned"}:
expected = getattr(self.guard, "expected_file_sha256", None)
if not isinstance(expected, dict) or not expected:
raise TaskAdmissionError("SOURCE_COMMIT_FILE_HASHES_REQUIRED", 403)
try:
raw = request.get("files")
if raw is None:
raw = {request["path"]: request["content_base64"]}
pairs = list(raw.items()) if isinstance(raw, dict) else [(item["path"], item["content_base64"]) for item in raw]
if len({path for path, _ in pairs}) != len(pairs):
raise ValueError("duplicate path")
actual = {path: hashlib.sha256(base64.b64decode(data, validate=True)).hexdigest()
for path, data in pairs}
except Exception as exc:
raise TaskAdmissionError("SOURCE_COMMIT_FILE_PAYLOAD_INVALID", 403) from exc
if actual != expected:
raise TaskAdmissionError("SOURCE_COMMIT_CONTENT_SCOPE_MISMATCH", 403)
return self.delegate(transport, request)
def expired_admission_runtime() -> TaskAdmissionRuntimeBundle:
"""Keep a verified-expired ordinary epoch denied while serving other routes."""
def deny(*_args: Any, **_kwargs: Any):
raise TaskAdmissionError("TASK_ADMISSION_EPOCH_EXPIRED", 403)
class ClosedPrincipal:
__call__ = staticmethod(deny)
def readiness(self):
return {"ready": False, "error": "TASK_ADMISSION_EPOCH_EXPIRED"}
from aios_task_admission_bridge_contract import TASK_ADMISSION_OPERATION_REQUIRED_FIELDS
return TaskAdmissionRuntimeBundle(
handlers={op: deny for op in TASK_ADMISSION_OPERATION_REQUIRED_FIELDS},
principal_verifier=ClosedPrincipal(),
)
+271 -9
View File
@@ -1,20 +1,257 @@
"""Task Center API."""
"""Authenticated read-only Task Center projections and event transport."""
from __future__ import annotations
import json
from collections.abc import Callable, Mapping
from typing import Any
try:
from fastapi import APIRouter
except Exception:
APIRouter = None
from fastapi import APIRouter, HTTPException, Request
from fastapi.responses import StreamingResponse
except Exception: # pragma: no cover - importable without FastAPI for model tests
APIRouter = HTTPException = Request = StreamingResponse = None
from .task_center import TaskCenter
from .task_dashboard_view import ProjectionCursorExpired, ProjectionCursorTransportLimit, TaskDashboardView
from .task_plane.errors import ForbiddenError, UnauthorizedError
class ProjectionUnavailable(RuntimeError):
"""The host did not install the source and identity bindings required to read."""
task_center = TaskCenter()
dashboard = TaskDashboardView()
_projection_reader: Callable[[Mapping[str, Any]], Mapping[str, Any]] | None = None
_principal_resolver: Callable[[Any], Mapping[str, Any] | None] | None = None
def get_task_overview(tasks: list[dict[str, Any]] | None = None):
return task_center.summarize(tasks)
def set_task_projection_reader(reader: Callable[[Mapping[str, Any]], Mapping[str, Any]] | None) -> None:
"""Install a host-owned reader that returns already-authorized task/event data."""
global _projection_reader
_projection_reader = reader
def set_task_projection_principal_resolver(
resolver: Callable[[Any], Mapping[str, Any] | None] | None,
) -> None:
"""Install the host's authenticated-principal resolver for all projection reads."""
global _principal_resolver
_principal_resolver = resolver
def get_task_overview(
tasks: list[dict[str, Any]] | None = None,
events_by_task: Mapping[str, list[dict[str, Any]]] | None = None,
) -> dict[str, Any]:
"""Project provided durable records; route callers use ``_read_projection``."""
summary = task_center.summarize(tasks)
projection = dashboard.build(tasks, events_by_task)
return {
**summary,
"tasks": projection["tasks"],
"views": projection["views"],
"snapshot_cursor": dashboard.snapshot_cursor(tasks, events_by_task),
}
def get_task_events(
cursor: str | None,
*,
principal: Mapping[str, Any],
limit: int = 100,
) -> dict[str, Any]:
source = _read_projection(principal)
feed = dashboard.event_feed(source["tasks"], source["events_by_task"], cursor, limit=limit)
cards = {card["task_id"]: card for card in dashboard.build(source["tasks"], source["events_by_task"])["tasks"]}
for event in feed["events"]:
card = cards.get(event["entity_id"])
if card is None:
continue
# Event sequence and task revision have different authority domains.
# The client fences entity snapshots by task revision, while resumable
# transport remains governed by the per-task append sequence.
event["event_sequence"] = event["sequence"]
event["event_revision"] = event.get("revision")
event["task_revision"] = card.get("revision")
event["revision"] = card.get("revision")
event["task"] = card
event["related_tasks"] = _ancestor_cards(card, cards)
return feed
def _ancestor_cards(card: Mapping[str, Any], cards: Mapping[str, dict[str, Any]]) -> list[dict[str, Any]]:
related: list[dict[str, Any]] = []
parent_id = card.get("lineage", {}).get("parent_task_id") if isinstance(card.get("lineage"), Mapping) else None
visited: set[str] = set()
while isinstance(parent_id, str) and parent_id and parent_id not in visited:
visited.add(parent_id)
parent = cards.get(parent_id)
if parent is None:
break
related.append(parent)
lineage = parent.get("lineage")
parent_id = lineage.get("parent_task_id") if isinstance(lineage, Mapping) else None
return related
def configure_task_plane_projection(service: Any, principal_resolver: Callable[[Any], Mapping[str, Any] | None]) -> None:
"""Bind this FastAPI router to an existing authorized ``TaskPlaneService``.
The resolver must return a host-authenticated principal containing the
server-derived ``task_plane_identity``. No browser-provided role or scope
participates in these reads.
"""
def reader(principal: Mapping[str, Any]) -> Mapping[str, Any]:
actor = principal.get("task_plane_identity")
if not isinstance(actor, str) or not actor:
raise PermissionError("task projection identity is missing")
return _read_task_plane_snapshot(service, actor)
set_task_projection_principal_resolver(principal_resolver)
set_task_projection_reader(reader)
_TASKPLANE_DISPLAY_STATUSES = {
"CREATED": "pending",
"QUEUED": "pending",
"CLAIMED": "running",
"RUNNING": "running",
"WAITING_CHILD": "running",
"VERIFYING": "running",
"COMPLETED": "completed",
"FAILED_RECOVERABLE": "blocked",
"FAILED_FINAL": "blocked",
"CANCELLED": "blocked",
"MANUAL_REVIEW_REQUIRED": "waiting_approval",
}
def _read_task_plane_snapshot(service: Any, actor: str) -> Mapping[str, Any]:
"""Read an authorized, bounded-consistency TaskPlane projection.
``list_tasks`` and ``get_events`` are separate authorized reads. We only
publish their result when a second task and event-position read confirms
that no visible task revision, membership, or per-task append position
moved while the first snapshot was collected. A busy plane is retried
once and then fails closed rather than emitting a mixed snapshot.
"""
for _attempt in range(2):
raw_tasks = _visible_tasks(service.list_tasks(actor))
task_ids = [task["task_id"] for task in raw_tasks]
events_by_task = {task_id: service.get_events(task_id, actor) for task_id in task_ids}
verification_events = {task_id: service.get_events(task_id, actor) for task_id in task_ids}
verified_tasks = _visible_tasks(service.list_tasks(actor))
if _task_revisions(raw_tasks) == _task_revisions(verified_tasks) and all(
_event_positions(events_by_task[task_id]) == _event_positions(verification_events[task_id])
for task_id in task_ids
):
return {"tasks": raw_tasks, "events_by_task": events_by_task}
raise ProjectionUnavailable("task projection changed during snapshot")
def _visible_tasks(records: Any) -> list[dict[str, Any]]:
if not isinstance(records, list):
raise ProjectionUnavailable("task projection source returned invalid tasks")
tasks: list[dict[str, Any]] = []
for raw in records:
if not isinstance(raw, Mapping) or not isinstance(raw.get("task_id"), str) or not raw["task_id"]:
raise ProjectionUnavailable("task projection source returned invalid task")
source_status = str(raw.get("status") or "").upper()
if source_status not in _TASKPLANE_DISPLAY_STATUSES:
raise ProjectionUnavailable("task projection source returned unknown TaskPlane status")
tasks.append({
**raw,
"source_status": source_status or None,
"status": _TASKPLANE_DISPLAY_STATUSES[source_status],
})
return tasks
def _task_revisions(tasks: list[Mapping[str, Any]]) -> tuple[tuple[str, Any], ...]:
return tuple(sorted((task["task_id"], task.get("revision")) for task in tasks))
def _event_positions(events: Any) -> tuple[tuple[str, int], ...]:
if not isinstance(events, list):
raise ProjectionUnavailable("task projection source returned invalid events")
positions: list[tuple[str, int]] = []
for event in events:
if not isinstance(event, Mapping) or not isinstance(event.get("event_id"), str):
raise ProjectionUnavailable("task projection source returned invalid event")
sequence = event.get("seq")
if not isinstance(sequence, int) or isinstance(sequence, bool) or sequence < 0:
raise ProjectionUnavailable("task projection event has no durable sequence")
positions.append((event["event_id"], sequence))
return tuple(positions)
def _read_projection(principal: Mapping[str, Any]) -> Mapping[str, Any]:
if _projection_reader is None:
raise ProjectionUnavailable("task projection source is not configured")
source = _projection_reader(principal)
if not isinstance(source, Mapping):
raise ProjectionUnavailable("task projection source returned invalid data")
tasks, events = source.get("tasks"), source.get("events_by_task")
if not isinstance(tasks, list) or not isinstance(events, Mapping):
raise ProjectionUnavailable("task projection source returned incomplete data")
return {"tasks": tasks, "events_by_task": events}
def _principal(request: Any) -> Mapping[str, Any]:
if _principal_resolver is None:
raise ProjectionUnavailable("task projection authentication is not configured")
principal = _principal_resolver(request)
if not isinstance(principal, Mapping):
raise PermissionError("task projection authentication failed")
return principal
def _sse(payload: Mapping[str, Any]):
# The bounded response is deliberately resumable. The browser reconnects
# with the returned cursor; polling remains available when it cannot.
for event in payload["events"]:
yield f"id: {event['cursor']}\ndata: {json.dumps(event, ensure_ascii=False, separators=(',', ':'))}\n\n"
yield ": end-of-batch\n\n"
def _http(call: Callable[[], Any]) -> Any:
try:
return call()
except ProjectionCursorTransportLimit as exc:
if HTTPException is None:
raise
raise HTTPException(status_code=413, detail="TASK_EVENT_CURSOR_TOO_LARGE") from exc
except ProjectionCursorExpired as exc:
if HTTPException is None:
raise
raise HTTPException(status_code=410, detail="EVENT_CURSOR_EXPIRED") from exc
except ProjectionUnavailable as exc:
if HTTPException is None:
raise
raise HTTPException(status_code=503, detail="TASK_PROJECTION_UNAVAILABLE") from exc
except UnauthorizedError as exc:
if HTTPException is None:
raise
raise HTTPException(status_code=401, detail="TASK_PROJECTION_UNAUTHORIZED") from exc
except ForbiddenError as exc:
if HTTPException is None:
raise
raise HTTPException(status_code=403, detail="TASK_PROJECTION_DENIED") from exc
except PermissionError as exc:
if HTTPException is None:
raise
raise HTTPException(status_code=403, detail="TASK_PROJECTION_DENIED") from exc
except ValueError as exc:
if HTTPException is None:
raise
raise HTTPException(status_code=400, detail=str(exc)) from exc
def _overview(principal: Mapping[str, Any]) -> dict[str, Any]:
source = _read_projection(principal)
return get_task_overview(source["tasks"], source["events_by_task"])
def build_router():
@@ -23,12 +260,37 @@ def build_router():
router = APIRouter(prefix="/admin/ai-evolution/tasks", tags=["task-center"])
@router.get("/overview", operation_id="getTaskCenterOverview")
def overview():
return get_task_overview()
def overview(request: Request):
return _http(lambda: _overview(_principal(request)))
@router.get("/events", operation_id="streamTaskCenterEvents")
def events(request: Request, cursor: str | None = None, after_cursor: str | None = None, limit: int = 100):
payload = _http(lambda: get_task_events(
_effective_cursor(request, cursor, after_cursor), principal=_principal(request), limit=limit,
))
return StreamingResponse(_sse(payload), media_type="text/event-stream")
@router.get("/events/poll", operation_id="pollTaskCenterEvents")
def poll(request: Request, cursor: str | None = None, after_cursor: str | None = None, limit: int = 100):
return _http(lambda: get_task_events(
_effective_cursor(request, cursor, after_cursor), principal=_principal(request), limit=limit,
))
return router
router = build_router()
__all__ = ["router", "get_task_overview"]
__all__ = [
"router", "get_task_overview", "get_task_events", "set_task_projection_reader",
"set_task_projection_principal_resolver", "configure_task_plane_projection",
"ProjectionUnavailable",
]
def _effective_cursor(request: Any, cursor: str | None, after_cursor: str | None) -> str | None:
header_cursor = request.headers.get("Last-Event-ID") if request is not None else None
supplied = [value for value in (cursor, after_cursor, header_cursor) if value]
if len(set(supplied)) > 1:
raise ValueError("conflicting event cursors")
return supplied[0] if supplied else None
+405 -48
View File
@@ -1,81 +1,438 @@
"""Task Center visualization model."""
"""Read-only Task Center projections built from durable task and event records."""
from __future__ import annotations
from typing import Any
import base64
import json
import zlib
from collections import defaultdict
from typing import Any, Mapping
class ProjectionCursorExpired(ValueError):
"""The requested event cursor no longer names a visible durable event."""
class ProjectionCursorTransportLimit(ValueError):
"""The authorized per-task watermark cannot fit a request/header cursor."""
_MAX_CURSOR_BYTES = 8192
_MAX_CURSOR_PAYLOAD_BYTES = 32768
_INITIAL_INTERACTION_KIND = "initial_request"
_ADDITIONAL_INTERACTION_KINDS = frozenset({"additional_interaction", "follow_up"})
class TaskDashboardView:
def build(self, tasks: list[dict[str, Any]] | None = None) -> dict[str, Any]:
tasks = tasks or []
"""Build the Task Center read model without maintaining another state store."""
def build(
self,
tasks: list[dict[str, Any]] | None = None,
events_by_task: Mapping[str, list[dict[str, Any]]] | None = None,
) -> dict[str, Any]:
raw_tasks = [dict(task) for task in tasks or [] if isinstance(task, Mapping)]
by_id = {
task_id: task for task in raw_tasks
if isinstance((task_id := task.get("task_id")), str) and task_id
}
children = self._children_by_parent(by_id)
cards = [
self._task_card(task, by_id, children, events_by_task)
for task in raw_tasks
]
return {
"module": "task-center",
"route": "/admin/ai-evolution/tasks",
"title": "AIOS Task Center",
"views": {
"kanban": self._kanban(tasks),
"timeline": self._timeline(tasks),
"flow": self._flow(tasks),
"statistics": self._statistics(tasks),
"kanban": self._kanban(cards),
"timeline": self._timeline(cards),
"flow": self._flow(cards),
"statistics": self._statistics(cards),
},
"tasks": [self._task_card(task) for task in tasks],
"tasks": cards,
}
def _task_card(self, task: dict[str, Any]) -> dict[str, Any]:
def event_feed(
self,
tasks: list[dict[str, Any]] | None,
events_by_task: Mapping[str, list[dict[str, Any]]] | None,
cursor: str | None = None,
*,
limit: int = 100,
) -> dict[str, Any]:
"""Return an authorization-filtered, resumable page of durable events."""
if not 1 <= limit <= 100:
raise ValueError("invalid event page limit")
rows = self._event_rows(tasks, events_by_task)
watermarks = self._decode_cursor(cursor) if cursor else {}
self._validate_watermarks(rows, watermarks)
pending = [
row for row in rows
if row["sequence"] > watermarks.get(row["entity_id"], -1)
]
page = pending[:limit]
next_watermarks = dict(watermarks)
emitted_events: list[dict[str, Any]] = []
for row in page:
next_watermarks[row["entity_id"]] = max(next_watermarks.get(row["entity_id"], -1), row["sequence"])
# ``Last-Event-ID`` holds the cursor from the final SSE frame. It
# must therefore include every preceding entity watermark, not just
# the frame's own task, or a reconnect would replay another task.
emitted_events.append({**row, "cursor": self._encode_cursor(next_watermarks)})
next_cursor = self._encode_cursor(next_watermarks) if next_watermarks else None
return {
"task_id": task.get("task_id"),
"title": task.get("title"),
"events": emitted_events,
"cursor": cursor,
"next_cursor": next_cursor,
"has_more": len(pending) > len(page),
}
def snapshot_cursor(
self,
tasks: list[dict[str, Any]] | None,
events_by_task: Mapping[str, list[dict[str, Any]]] | None,
) -> str | None:
rows = self._event_rows(tasks, events_by_task)
watermarks: dict[str, int] = {}
for row in rows:
watermarks[row["entity_id"]] = max(watermarks.get(row["entity_id"], -1), row["sequence"])
return self._encode_cursor(watermarks) if watermarks else None
def _task_card(
self,
task: dict[str, Any],
by_id: Mapping[str, dict[str, Any]],
children: Mapping[str, list[str]],
events_by_task: Mapping[str, list[dict[str, Any]]] | None,
) -> dict[str, Any]:
task_id = task.get("task_id")
progress = task.get("progress") if isinstance(task.get("progress"), Mapping) else {}
parent_task_id = task.get("parent_task_id")
event_result = self._project_events(
task_id,
events_by_task,
complete=task.get("interaction_history_coverage") == "complete",
)
direct_children = list(children.get(task_id, ())) if isinstance(task_id, str) else []
grandchildren = [grandchild for child in direct_children for grandchild in children.get(child, ())]
authorization = self._authorization(task)
return {
"task_id": task_id,
"title": task.get("title") or task.get("display_name"),
"root_task_id": task.get("root_task_id"),
"created_at": task.get("created_at"),
"updated_at": task.get("updated_at"),
"assigned_by": task.get("assigned_by"),
"executor": task.get("executor"),
"status": task.get("status"),
"progress": task.get("progress", {}),
"elapsed_seconds": task.get("elapsed_seconds", 0),
"remaining_steps": task.get("progress", {}).get("remaining_steps", []),
"source_status": task.get("source_status", task.get("status")),
"revision": task.get("revision"),
"progress": progress,
"elapsed_seconds": self._known_nonnegative_int(task.get("elapsed_seconds")),
"remaining_steps": progress.get("remaining_steps", []),
"notification": task.get("notification", {}),
"actions": [
"start",
"update_progress",
"block",
"request_approval",
"approve",
"reject",
"retry",
"complete",
"notify",
"close",
],
"lineage": {
"parent_task_id": parent_task_id if isinstance(parent_task_id, str) and parent_task_id in by_id else None,
"child_task_ids": direct_children,
"grandchild_task_ids": grandchildren,
},
"responsibility": {
"assigned_by": task.get("assigned_by"),
"executor": task.get("executor"),
"owner_principal_id": task.get("owner_principal_id"),
},
"authorization": authorization,
"actions": self._actions(task, authorization),
"interaction_history": event_result,
}
def _kanban(self, tasks):
@staticmethod
def _known_nonnegative_int(value: Any) -> int | None:
return value if isinstance(value, int) and not isinstance(value, bool) and value >= 0 else None
@staticmethod
def _children_by_parent(by_id: Mapping[str, dict[str, Any]]) -> dict[str, list[str]]:
result: dict[str, list[str]] = defaultdict(list)
for task_id, task in by_id.items():
parent_id = task.get("parent_task_id")
if isinstance(parent_id, str) and parent_id in by_id:
result[parent_id].append(task_id)
return {parent: sorted(child_ids) for parent, child_ids in result.items()}
def _project_events(
self,
task_id: Any,
events_by_task: Mapping[str, list[dict[str, Any]]] | None,
*,
complete: bool = False,
) -> dict[str, Any]:
if not isinstance(task_id, str) or events_by_task is None or task_id not in events_by_task:
return self._unknown_history()
raw_events = events_by_task[task_id]
if not isinstance(raw_events, list):
return self._unknown_history()
seen: dict[str, dict[str, Any]] = {}
semantic_ids: dict[tuple[str, str], dict[str, Any]] = {}
events: list[dict[str, Any]] = []
conflicts: list[dict[str, str]] = []
for raw in raw_events:
if not isinstance(raw, Mapping) or not isinstance(raw.get("event_id"), str):
conflicts.append({"kind": "invalid_event", "event_id": ""})
continue
event = dict(raw)
event_id = event["event_id"]
previous = seen.get(event_id)
if previous is not None:
if previous != event:
conflicts.append({"kind": "duplicate_event_id_conflict", "event_id": event_id})
continue
seen[event_id] = event
item = self._event_item(event)
if self._malformed_human_interaction(event):
conflicts.append({"kind": "invalid_human_interaction_evidence", "event_id": event_id})
if self._malformed_metric_evidence(event):
conflicts.append({"kind": "invalid_metric_evidence", "event_id": event_id})
semantic_id = item.get("semantic_id")
if semantic_id:
semantic_namespace = "human_interaction" if item["category"] in {"initial_request", "additional_interaction"} else item["category"]
previous_semantic = semantic_ids.get((semantic_namespace, semantic_id))
if previous_semantic is not None:
if self._semantic_content(previous_semantic) != self._semantic_content(item):
conflicts.append({"kind": "duplicate_semantic_id_conflict", "event_id": event_id})
continue
semantic_ids[(semantic_namespace, semantic_id)] = item
events.append(item)
events.sort(key=lambda event: (event.get("created_at") or "", event["event_id"]))
interactions = sum(event["category"] in {"initial_request", "additional_interaction"} for event in events)
is_exact = complete and not conflicts
return {
"history_status": "partial" if not complete else ("conflicted" if conflicts else "complete"),
"interaction_count": interactions if is_exact else None,
"initial_request_count": sum(event["category"] == "initial_request" for event in events) if is_exact else None,
"additional_interaction_count": sum(event["category"] == "additional_interaction" for event in events) if is_exact else None,
"decision_count": sum(event["category"] == "decision" for event in events) if is_exact else None,
"offered_choice_count": sum(event.get("choice_count", 0) for event in events if event["category"] == "offered_choice") if is_exact else None,
"selection_submission_count": sum(event["category"] == "selection_submission" for event in events) if is_exact else None,
"events": events,
"source_conflicts": conflicts,
}
@staticmethod
def _unknown_history() -> dict[str, Any]:
return {
"history_status": "partial",
"interaction_count": None,
"initial_request_count": None,
"additional_interaction_count": None,
"decision_count": None,
"offered_choice_count": None,
"selection_submission_count": None,
"events": [],
"source_conflicts": [],
}
@staticmethod
def _semantic_content(event: Mapping[str, Any]) -> dict[str, Any]:
return {
key: value for key, value in event.items()
if key not in {"event_id", "sequence", "revision", "created_at"}
}
@staticmethod
def _malformed_human_interaction(event: Mapping[str, Any]) -> bool:
data = event.get("data") if isinstance(event.get("data"), Mapping) else {}
source = str(data.get("source") or event.get("source") or "").upper()
if source != "HUMAN_INTERACTION":
return False
interaction_id = data.get("interaction_id")
kind = data.get("kind")
return not (
isinstance(interaction_id, str) and interaction_id
and isinstance(kind, str)
and (kind == _INITIAL_INTERACTION_KIND or kind in _ADDITIONAL_INTERACTION_KINDS)
)
@staticmethod
def _malformed_metric_evidence(event: Mapping[str, Any]) -> bool:
data = event.get("data") if isinstance(event.get("data"), Mapping) else {}
event_type = str(event.get("type") or "").upper()
if event_type in {"DECISION", "TASK_DECISION", "APPROVAL_DECIDED"}:
return not isinstance(data.get("decision_id"), str) or not data["decision_id"]
if event_type in {"CHOICE_OFFERED", "OPTIONS_OFFERED"}:
choice_count = data.get("choice_count")
return not (
isinstance(data.get("choice_offer_id"), str) and data["choice_offer_id"]
and isinstance(choice_count, int) and not isinstance(choice_count, bool) and choice_count >= 0
)
if event_type in {"CHOICE_SELECTED", "SELECTION_SUBMITTED"}:
return not isinstance(data.get("selection_submission_id"), str) or not data["selection_submission_id"]
return False
@staticmethod
def _event_item(event: Mapping[str, Any]) -> dict[str, Any]:
event_type = str(event.get("type") or "UNKNOWN")
data = event.get("data") if isinstance(event.get("data"), Mapping) else {}
normalized = event_type.upper()
source = str(data.get("source") or event.get("source") or "").upper()
interaction_id = data.get("interaction_id")
decision_id = data.get("decision_id")
selection_id = data.get("selection_submission_id")
kind = data.get("kind")
if source == "HUMAN_INTERACTION" and isinstance(interaction_id, str) and interaction_id and kind == _INITIAL_INTERACTION_KIND:
category = "initial_request"
semantic_id = interaction_id
elif source == "HUMAN_INTERACTION" and isinstance(interaction_id, str) and interaction_id and isinstance(kind, str) and kind in _ADDITIONAL_INTERACTION_KINDS:
category = "additional_interaction"
semantic_id = interaction_id
elif normalized in {"DECISION", "TASK_DECISION", "APPROVAL_DECIDED"} and isinstance(decision_id, str) and decision_id:
category = "decision"
semantic_id = decision_id
elif normalized in {"CHOICE_OFFERED", "OPTIONS_OFFERED"} and isinstance(data.get("choice_offer_id"), str) and isinstance(data.get("choice_count"), int) and not isinstance(data["choice_count"], bool) and data["choice_count"] >= 0:
category = "offered_choice"
semantic_id = data["choice_offer_id"]
elif normalized in {"CHOICE_SELECTED", "SELECTION_SUBMITTED"} and isinstance(selection_id, str) and selection_id:
category = "selection_submission"
semantic_id = selection_id
else:
category = "other"
semantic_id = None
return {
"event_id": event["event_id"],
"sequence": event.get("seq"),
"revision": event.get("revision", event.get("seq")),
"type": event_type,
"category": category,
"semantic_id": semantic_id,
"subject": event.get("subject"),
"created_at": event.get("created_at"),
"decision": data.get("decision") if category == "decision" else None,
"choice_id": data.get("choice_id") if category in {"offered_choice", "selection_submission"} else None,
"choice_count": data.get("choice_count", 0) if category == "offered_choice" else 0,
}
@staticmethod
def _authorization(task: Mapping[str, Any]) -> dict[str, Any]:
raw = task.get("authorization")
raw = raw if isinstance(raw, Mapping) else {}
capabilities = raw.get("capabilities", task.get("capabilities", []))
return {
"owner_gate_required": bool(task.get("owner_gate_required", raw.get("owner_gate_required", False))),
"owner_gate_status": task.get("owner_gate_status", raw.get("owner_gate_status")),
"decision": raw.get("decision"),
"capabilities": list(capabilities) if isinstance(capabilities, (list, tuple, set, frozenset)) else [],
}
@staticmethod
def _actions(task: Mapping[str, Any], authorization: Mapping[str, Any]) -> list[str]:
explicit = task.get("authorized_actions")
if not isinstance(explicit, (list, tuple, set, frozenset)):
return []
allowed = [action for action in explicit if isinstance(action, str)]
capabilities = set(authorization.get("capabilities", []))
decision = authorization.get("decision")
if "approve" in allowed and not (
{"approve", "decision.approve", "task.approve"} & capabilities
and isinstance(decision, Mapping)
and decision.get("status") in {"pending", "eligible"}
):
allowed.remove("approve")
return allowed
@staticmethod
def _kanban(tasks: list[dict[str, Any]]) -> dict[str, list[str]]:
columns = {"pending": [], "running": [], "blocked": [], "waiting_approval": [], "completed": []}
for task in tasks:
columns.setdefault(task.get("status", "pending"), []).append(task.get("task_id"))
return columns
def _timeline(self, tasks):
return [
{
"task_id": task.get("task_id"),
"updated_at": task.get("updated_at"),
"status": task.get("status"),
}
for task in tasks
]
@staticmethod
def _timeline(tasks: list[dict[str, Any]]) -> list[dict[str, Any]]:
return [{"task_id": task.get("task_id"), "status": task.get("status"), "revision": task.get("revision")} for task in tasks]
def _flow(self, tasks):
return {
"nodes": ["created", "assigned", "running", "blocked", "approval", "completed", "closed"],
"task_count": len(tasks),
}
@staticmethod
def _flow(tasks: list[dict[str, Any]]) -> dict[str, Any]:
return {"nodes": ["created", "assigned", "running", "blocked", "approval", "completed", "closed"], "task_count": len(tasks)}
def _statistics(self, tasks):
@staticmethod
def _statistics(tasks: list[dict[str, Any]]) -> dict[str, int]:
return {
"total": len(tasks),
"running": len([t for t in tasks if t.get("status") == "running"]),
"blocked": len([t for t in tasks if t.get("status") == "blocked"]),
"waiting_approval": len([t for t in tasks if t.get("status") == "waiting_approval"]),
"completed": len([t for t in tasks if t.get("status") == "completed"]),
"running": sum(task.get("status") == "running" for task in tasks),
"blocked": sum(task.get("status") == "blocked" for task in tasks),
"waiting_approval": sum(task.get("status") == "waiting_approval" for task in tasks),
"completed": sum(task.get("status") == "completed" for task in tasks),
}
def _event_rows(self, tasks, events_by_task) -> list[dict[str, Any]]:
visible_ids = {task.get("task_id") for task in tasks or [] if isinstance(task, Mapping)}
result: list[dict[str, Any]] = []
for task_id, events in (events_by_task or {}).items():
if task_id not in visible_ids or not isinstance(events, list):
continue
# The stream carries every durable append. Semantic deduplication
# belongs only to human-history metrics: a later conflicting event
# must reach the client so its current task card becomes conflicted.
for event in events:
if not isinstance(event, Mapping) or not isinstance(event.get("event_id"), str):
continue
sequence = event.get("seq")
if not isinstance(sequence, int) or isinstance(sequence, bool) or sequence < 0:
continue
result.append({"event_id": event["event_id"], "entity_id": task_id, "sequence": sequence, "revision": event.get("revision"), "type": event.get("type", "UNKNOWN"), "created_at": event.get("created_at")})
# A per-task append sequence is the cursor authority. Timestamp order
# can be displayed by clients, but cannot drive paging: a backdated
# seq=6 must never be emitted before seq=5 for the same task.
result.sort(key=lambda event: (event["sequence"], event["entity_id"], event["event_id"]))
return result
__all__ = ["TaskDashboardView"]
@staticmethod
def _encode_cursor(watermarks: Mapping[str, int]) -> str:
raw = json.dumps({"v": 2, "after": dict(watermarks)}, sort_keys=True, separators=(",", ":")).encode("utf-8")
if len(raw) > _MAX_CURSOR_PAYLOAD_BYTES:
raise ProjectionCursorTransportLimit("event cursor exceeds transport limit")
token = base64.urlsafe_b64encode(zlib.compress(raw, level=9)).decode("ascii").rstrip("=")
if len(token) > _MAX_CURSOR_BYTES:
raise ProjectionCursorTransportLimit("event cursor exceeds transport limit")
return token
@staticmethod
def _decode_cursor(cursor: str) -> dict[str, int]:
if not isinstance(cursor, str) or not cursor or len(cursor) > _MAX_CURSOR_BYTES:
raise ProjectionCursorExpired("event cursor expired")
try:
padded = cursor + "=" * (-len(cursor) % 4)
encoded = base64.urlsafe_b64decode(padded.encode("ascii"))
decompressor = zlib.decompressobj()
raw = decompressor.decompress(encoded, _MAX_CURSOR_PAYLOAD_BYTES + 1)
if len(raw) > _MAX_CURSOR_PAYLOAD_BYTES:
raise ValueError("cursor payload invalid")
raw += decompressor.flush(_MAX_CURSOR_PAYLOAD_BYTES + 1 - len(raw))
if len(raw) > _MAX_CURSOR_PAYLOAD_BYTES or decompressor.unconsumed_tail or not decompressor.eof:
raise ValueError("cursor payload invalid")
value = json.loads(raw)
except (UnicodeEncodeError, ValueError, zlib.error, json.JSONDecodeError) as exc:
raise ProjectionCursorExpired("event cursor expired") from exc
after = value.get("after") if isinstance(value, Mapping) and value.get("v") == 2 else None
if not isinstance(after, Mapping) or not after or any(
not isinstance(task_id, str) or not task_id or not isinstance(sequence, int)
or isinstance(sequence, bool) or sequence < 0
for task_id, sequence in after.items()
):
raise ProjectionCursorExpired("event cursor expired")
return dict(after)
@staticmethod
def _validate_watermarks(rows: list[dict[str, Any]], watermarks: Mapping[str, int]) -> None:
positions_by_task: dict[str, set[int]] = defaultdict(set)
for row in rows:
positions_by_task[row["entity_id"]].add(row["sequence"])
# A retained sequence floor is not otherwise exposed by TaskPlane.
# Requiring the acknowledged position itself prevents silently skipping
# purged seqs 4..9 when a stale cursor still says seq=3.
if any(sequence not in positions_by_task.get(task_id, set()) for task_id, sequence in watermarks.items()):
raise ProjectionCursorExpired("event cursor expired")
__all__ = ["ProjectionCursorExpired", "ProjectionCursorTransportLimit", "TaskDashboardView"]
+66 -15
View File
@@ -35,7 +35,7 @@ from .models import (
contains_secret_text,
validate_identifier,
)
from .policy import PolicyEngine
from .policy import IdentityVerifier, PolicyEngine
from .root_action_consumer import (
RootActionConsumer,
RootActionExecutor,
@@ -71,7 +71,9 @@ class TaskPlaneService:
root_action_executor: RootActionExecutor | None = None,
):
self.store = store
self.c6_runtime = None
self.policy = policy or PolicyEngine(
identity_verifier=IdentityVerifier({"anon"}),
execution_enabled=False,
identity_capabilities={
"anon": {"task.creator", "task.read", "task.audit"}
@@ -117,6 +119,9 @@ class TaskPlaneService:
else None
)
self.store.probe_attestation_verifier = self.policy.probe_attestation_verifier
self.store.configure_evidence_attestation_verifier(
self.policy.evidence_attestation_verifier
)
def consume_one_root_action(
self,
@@ -412,6 +417,11 @@ class TaskPlaneService:
lease_seconds = _bounded_int(
body.get("lease_seconds", 30), "lease_seconds", 1, 3600
)
if self.c6_runtime is not None and self.c6_runtime.bindings.enabled:
return self.c6_runtime.claim_task(
task_id=task_id, worker_id=worker_id, actor_identity=actor,
lease_seconds=lease_seconds,
).to_dict()
claim = self.lease.claim(
task_id=task_id,
worker_id=worker_id,
@@ -436,6 +446,10 @@ class TaskPlaneService:
lease_seconds = _bounded_int(
body.get("lease_seconds", 30), "lease_seconds", 1, 3600
)
if self.c6_runtime is not None and self.c6_runtime.bindings.enabled:
return self.c6_runtime.renew_claim(task_id=task_id, claim_id=claim_id,
actor_identity=actor, receipt=body.get("claim_receipt"),
lease_seconds=lease_seconds)
claim = self.lease.heartbeat(task_id, claim_id, lease_seconds)
return {"claim_id": claim_id, "lease_until": claim.lease_until}
@@ -538,16 +552,32 @@ class TaskPlaneService:
previous = self.store._last_evidence_hash(task_id)
evidence_entries = []
for evidence in evidences:
envelope = EvidenceEnvelope.create(
task_id=task_id,
claim_id=claim_id,
kind=str(evidence.get("kind") or evidence.get("type") or "generic"),
worker_identity=actor,
payload=evidence,
previous_evidence_hash=previous,
signature=evidence.get("signature"),
attestation_ref=evidence.get("attestation_ref"),
)
if "schema_version" in evidence:
try:
envelope = EvidenceEnvelope.from_dict(evidence)
except ValueError as exc:
raise ValidationError("invalid evidence envelope") from exc
else:
envelope = EvidenceEnvelope.create(
task_id=task_id,
claim_id=claim_id,
kind=str(
evidence.get("kind") or evidence.get("type") or "generic"
),
worker_identity=actor,
payload=evidence.get("payload", evidence),
previous_evidence_hash=previous,
evidence_id=evidence.get("evidence_id"),
created_at=evidence.get("created_at"),
expires_at=evidence.get("expires_at"),
signature=evidence.get("signature"),
attestation_ref=evidence.get("attestation_ref"),
signature_algorithm=evidence.get("signature_algorithm"),
signing_key_id=evidence.get("signing_key_id"),
verifier_identity=evidence.get("verifier_identity"),
authority_domain=evidence.get("authority_domain"),
sequence=evidence.get("sequence"),
)
evidence_entries.append(envelope)
previous = envelope.evidence_digest
final_status = (
@@ -910,8 +940,29 @@ def run_server(
*,
authenticator: Callable[[str], str | None] | None = None,
):
handler = TaskPlaneRequestHandler
handler.service = service
handler.authenticator = authenticator
# A separate handler class prevents one server/tenant from overwriting
# another concurrently running server's service or authenticator.
handler = type("BoundTaskPlaneRequestHandler", (TaskPlaneRequestHandler,), {
"service": service, "authenticator": authenticator,
})
server = ThreadingHTTPServer((host, port), handler)
server.serve_forever()
runtime = service.c6_runtime
stop = __import__("threading").Event()
runner = None
if runtime is not None and runtime.bindings.enabled:
import threading
def run_ticks():
while not stop.is_set():
runtime.tick()
stop.wait(1.0)
runner = threading.Thread(target=run_ticks, name="c6-host-tick", daemon=True)
runner.start()
try:
server.serve_forever()
finally:
stop.set()
if runtime is not None:
runtime.close()
if runner is not None:
runner.join(timeout=2.0)
server.server_close()
@@ -0,0 +1,666 @@
"""C6 ARO Phase A: bounded read-only audit evaluation and immutable reports."""
from __future__ import annotations
from collections.abc import Callable, Mapping
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
from enum import Enum
import hashlib
import json
import math
import os
from pathlib import Path
import re
import tempfile
from typing import Literal, Protocol
from .operation_view import DomainStamp, validate_digest
class Dimension(str, Enum):
FUNCTION = "function"
PERFORMANCE = "performance"
EXCEPTION = "exception"
USAGE = "usage"
CODE_DEFECT = "code_defect"
HISTORY = "history"
DEADLOCK = "deadlock"
LONG_RUNNING = "long_running"
_DIMENSIONS = tuple(Dimension)
_SAFE_REF_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:/@+-]{0,255}$")
_METRICS: dict[Dimension, frozenset[str]] = {
Dimension.FUNCTION: frozenset({"required_checks", "passed_checks"}),
Dimension.PERFORMANCE: frozenset({"duration_p95_ms"}),
Dimension.EXCEPTION: frozenset({"unhandled_exception_count"}),
Dimension.USAGE: frozenset({"operational_prompt_count", "manual_continue_count"}),
Dimension.CODE_DEFECT: frozenset({"validated_open_defect_count"}),
Dimension.HISTORY: frozenset({"same_fingerprint_failure_count"}),
Dimension.DEADLOCK: frozenset({"wait_cycle_count", "oldest_no_progress_seconds"}),
Dimension.LONG_RUNNING: frozenset({"oldest_nonterminal_age_seconds"}),
}
_COUNT_METRICS = frozenset(
{
"required_checks",
"passed_checks",
"unhandled_exception_count",
"operational_prompt_count",
"manual_continue_count",
"validated_open_defect_count",
"same_fingerprint_failure_count",
"wait_cycle_count",
}
)
_THRESHOLDS = frozenset(
{
"max_duration_p95_ms",
"repeated_failure_threshold",
"deadlock_seconds",
"max_nonterminal_age_seconds",
}
)
def _text(value: object, name: str) -> str:
if not isinstance(value, str) or not value or value != value.strip() or "\x00" in value:
raise ValueError(f"{name} must be a canonical non-empty string")
return value
def _safe_ref(value: object) -> str:
ref = _text(value, "evidence_ref")
lowered = ref.casefold()
if (
_SAFE_REF_RE.fullmatch(ref) is None
or ".." in ref
or ref.startswith(("/", "\\"))
or "://" in ref
or any(
marker in lowered
for marker in (
"token=",
"secret=",
"password=",
"cookie=",
"authorization=",
"private_key=",
"bearer ",
)
)
):
raise ValueError("evidence_ref is not an allowed opaque reference")
return ref
def _aware(value: object, name: str) -> datetime:
if not isinstance(value, datetime) or value.tzinfo is None or value.utcoffset() is None:
raise ValueError(f"{name} must be timezone-aware")
return value
def _number(value: object, name: str, *, non_negative: bool = True) -> float:
if isinstance(value, bool) or not isinstance(value, (int, float)):
raise ValueError(f"{name} must be numeric")
result = float(value)
if not math.isfinite(result) or (non_negative and result < 0):
raise ValueError(f"{name} must be finite and non-negative")
return result
@dataclass(frozen=True)
class Sample:
metric: str
value: float
observed_at: datetime
evidence_ref: str
def __post_init__(self) -> None:
_text(self.metric, "metric")
_number(self.value, "sample value")
_aware(self.observed_at, "observed_at")
_safe_ref(self.evidence_ref)
@dataclass(frozen=True)
class DimensionInput:
dimension: Dimension
samples: tuple[Sample, ...]
stamp: DomainStamp
captured_at: datetime
expires_at: datetime
def __post_init__(self) -> None:
if not isinstance(self.dimension, Dimension):
raise ValueError("dimension must be Dimension")
if not isinstance(self.samples, tuple):
raise ValueError("samples must be a tuple")
if any(not isinstance(sample, Sample) for sample in self.samples):
raise ValueError("samples must contain Sample values")
if not isinstance(self.stamp, DomainStamp):
raise ValueError("stamp must be DomainStamp")
captured = _aware(self.captured_at, "captured_at")
expires = _aware(self.expires_at, "expires_at")
if expires <= captured:
raise ValueError("expires_at must be after captured_at")
if any(sample.observed_at > captured for sample in self.samples):
raise ValueError("sample cannot be observed after capture")
def _canonical_datetime(value: datetime) -> str:
return value.astimezone(timezone.utc).isoformat().replace("+00:00", "Z")
def _snapshot_payload(
*,
window_start: datetime,
window_end: datetime,
dimensions: tuple[DimensionInput, ...],
rules_revision: str,
) -> dict[str, object]:
return {
"window_start": _canonical_datetime(window_start),
"window_end": _canonical_datetime(window_end),
"rules_revision": rules_revision,
"dimensions": [
{
"dimension": item.dimension.value,
"stamp": {
"domain": item.stamp.domain,
"object_id": item.stamp.object_id,
"generation": item.stamp.generation,
"revision": item.stamp.revision,
"event_id": item.stamp.event_id,
},
"captured_at": _canonical_datetime(item.captured_at),
"expires_at": _canonical_datetime(item.expires_at),
"samples": [
{
"metric": sample.metric,
"value": sample.value,
"observed_at": _canonical_datetime(sample.observed_at),
"evidence_ref": sample.evidence_ref,
}
for sample in sorted(
item.samples,
key=lambda value: (
value.metric,
value.observed_at,
value.evidence_ref,
),
)
],
}
for item in sorted(
dimensions,
key=lambda value: (
value.dimension.value,
value.stamp.domain,
value.stamp.object_id,
value.stamp.generation,
value.stamp.revision,
value.stamp.event_id,
),
)
],
}
def _digest(value: Mapping[str, object]) -> str:
encoded = json.dumps(
value,
sort_keys=True,
separators=(",", ":"),
ensure_ascii=False,
allow_nan=False,
).encode("utf-8")
return hashlib.sha256(encoded).hexdigest()
@dataclass(frozen=True)
class AuditSnapshot:
snapshot_id: str
window_start: datetime
window_end: datetime
dimensions: tuple[DimensionInput, ...]
rules_revision: str
def __post_init__(self) -> None:
validate_digest(self.snapshot_id)
start = _aware(self.window_start, "window_start")
end = _aware(self.window_end, "window_end")
if end <= start:
raise ValueError("audit window must be increasing")
if not isinstance(self.dimensions, tuple):
raise ValueError("dimensions must be a tuple")
if any(not isinstance(item, DimensionInput) for item in self.dimensions):
raise ValueError("dimensions must contain DimensionInput values")
_text(self.rules_revision, "rules_revision")
expected = _digest(
_snapshot_payload(
window_start=start,
window_end=end,
dimensions=self.dimensions,
rules_revision=self.rules_revision,
)
)
if self.snapshot_id != expected:
raise ValueError("snapshot_id does not match complete canonical inputs")
@classmethod
def create(
cls,
*,
window_start: datetime,
window_end: datetime,
dimensions: tuple[DimensionInput, ...],
rules_revision: str,
) -> "AuditSnapshot":
payload = _snapshot_payload(
window_start=_aware(window_start, "window_start"),
window_end=_aware(window_end, "window_end"),
dimensions=dimensions,
rules_revision=_text(rules_revision, "rules_revision"),
)
return cls(
snapshot_id=_digest(payload),
window_start=window_start,
window_end=window_end,
dimensions=dimensions,
rules_revision=rules_revision,
)
@dataclass(frozen=True)
class RuleLimits:
rules_revision: str
max_snapshot_skew_seconds: int
threshold_by_metric: tuple[tuple[str, float], ...]
def __post_init__(self) -> None:
_text(self.rules_revision, "rules_revision")
if (
isinstance(self.max_snapshot_skew_seconds, bool)
or not isinstance(self.max_snapshot_skew_seconds, int)
or self.max_snapshot_skew_seconds < 0
):
raise ValueError("max_snapshot_skew_seconds must be non-negative int")
if not isinstance(self.threshold_by_metric, tuple):
raise ValueError("threshold_by_metric must be a tuple")
names: list[str] = []
for item in self.threshold_by_metric:
if not isinstance(item, tuple) or len(item) != 2:
raise ValueError("threshold entries must be pairs")
name = _text(item[0], "threshold metric")
if name not in _THRESHOLDS:
raise ValueError("unknown threshold metric")
_number(item[1], "threshold")
names.append(name)
if len(names) != len(set(names)):
raise ValueError("duplicate threshold metric")
@property
def thresholds(self) -> dict[str, float]:
return {name: float(value) for name, value in self.threshold_by_metric}
@dataclass(frozen=True)
class Finding:
dimension: Dimension
status: Literal["CLEAR", "FINDING", "UNKNOWN"]
reason: str
evidence_refs: tuple[str, ...]
def __post_init__(self) -> None:
if not isinstance(self.dimension, Dimension):
raise ValueError("dimension must be Dimension")
if self.status not in ("CLEAR", "FINDING", "UNKNOWN"):
raise ValueError("invalid finding status")
_text(self.reason, "reason")
if not isinstance(self.evidence_refs, tuple):
raise ValueError("evidence_refs must be a tuple")
for ref in self.evidence_refs:
_safe_ref(ref)
@dataclass(frozen=True)
class AuditReport:
report_id: str
snapshot_id: str
rules_revision: str
findings: tuple[Finding, ...]
mutation_count: Literal[0]
def __post_init__(self) -> None:
validate_digest(self.report_id)
validate_digest(self.snapshot_id)
_text(self.rules_revision, "rules_revision")
if not isinstance(self.findings, tuple) or any(
not isinstance(item, Finding) for item in self.findings
):
raise ValueError("findings must be a tuple of Finding")
if type(self.mutation_count) is not int or self.mutation_count != 0:
raise ValueError("ARO Phase A mutation_count must be exactly zero")
def _unknown(dimension: Dimension, reason: str, refs: tuple[str, ...] = ()) -> Finding:
return Finding(dimension, "UNKNOWN", reason, refs)
def _metric_values(item: DimensionInput) -> tuple[dict[str, float] | None, tuple[str, ...]]:
expected = _METRICS[item.dimension]
if not item.samples:
return None, ()
names = [sample.metric for sample in item.samples]
refs = tuple(dict.fromkeys(sample.evidence_ref for sample in item.samples))
if set(names) != expected or len(names) != len(set(names)):
return None, refs
values = {sample.metric: float(sample.value) for sample in item.samples}
for name in _COUNT_METRICS & expected:
if not values[name].is_integer():
return None, refs
return values, refs
def _evaluate_dimension(
item: DimensionInput, thresholds: Mapping[str, float]
) -> Finding:
values, refs = _metric_values(item)
if values is None:
return _unknown(item.dimension, "METRICS_MISSING_DUPLICATE_OR_UNKNOWN", refs)
dimension = item.dimension
if dimension == Dimension.FUNCTION:
required = values["required_checks"]
passed = values["passed_checks"]
if required <= 0 or passed > required:
return _unknown(dimension, "FUNCTION_COUNTS_INVALID", refs)
status = "FINDING" if passed < required else "CLEAR"
return Finding(dimension, status, "REQUIRED_CHECKS_INCOMPLETE" if status == "FINDING" else "ALL_REQUIRED_CHECKS_PASSED", refs)
if dimension == Dimension.PERFORMANCE:
threshold = thresholds.get("max_duration_p95_ms")
if threshold is None:
return _unknown(dimension, "THRESHOLD_MISSING", refs)
status = "FINDING" if values["duration_p95_ms"] > threshold else "CLEAR"
return Finding(dimension, status, "P95_DURATION_EXCEEDED" if status == "FINDING" else "P95_DURATION_WITHIN_LIMIT", refs)
if dimension == Dimension.EXCEPTION:
status = "FINDING" if values["unhandled_exception_count"] > 0 else "CLEAR"
return Finding(dimension, status, "UNHANDLED_EXCEPTIONS_PRESENT" if status == "FINDING" else "NO_UNHANDLED_EXCEPTIONS", refs)
if dimension == Dimension.USAGE:
status = "FINDING" if any(value > 0 for value in values.values()) else "CLEAR"
return Finding(dimension, status, "OPERATIONAL_PROMPT_OR_CONTINUE_PRESENT" if status == "FINDING" else "NO_OPERATIONAL_PROMPT_FRICTION", refs)
if dimension == Dimension.CODE_DEFECT:
status = "FINDING" if values["validated_open_defect_count"] > 0 else "CLEAR"
return Finding(dimension, status, "VALIDATED_OPEN_DEFECTS_PRESENT" if status == "FINDING" else "NO_VALIDATED_OPEN_DEFECTS", refs)
if dimension == Dimension.HISTORY:
threshold = thresholds.get("repeated_failure_threshold")
if threshold is None or threshold <= 0 or not threshold.is_integer():
return _unknown(dimension, "THRESHOLD_MISSING_OR_INVALID", refs)
status = "FINDING" if values["same_fingerprint_failure_count"] >= threshold else "CLEAR"
return Finding(dimension, status, "FAILURE_FINGERPRINT_REPEATED" if status == "FINDING" else "FAILURE_FINGERPRINT_BELOW_LIMIT", refs)
if dimension == Dimension.DEADLOCK:
threshold = thresholds.get("deadlock_seconds")
if threshold is None:
return _unknown(dimension, "THRESHOLD_MISSING", refs)
status = "FINDING" if (
values["wait_cycle_count"] > 0
and values["oldest_no_progress_seconds"] > threshold
) else "CLEAR"
return Finding(dimension, status, "WAIT_CYCLE_WITHOUT_PROGRESS" if status == "FINDING" else "NO_STALE_WAIT_CYCLE", refs)
if dimension == Dimension.LONG_RUNNING:
threshold = thresholds.get("max_nonterminal_age_seconds")
if threshold is None:
return _unknown(dimension, "THRESHOLD_MISSING", refs)
status = "FINDING" if values["oldest_nonterminal_age_seconds"] > threshold else "CLEAR"
return Finding(dimension, status, "NONTERMINAL_AGE_EXCEEDED" if status == "FINDING" else "NONTERMINAL_AGE_WITHIN_LIMIT", refs)
raise AssertionError("unhandled dimension")
def evaluate_snapshot(
snapshot: AuditSnapshot, *, limits: RuleLimits, now: datetime
) -> AuditReport:
"""Evaluate all eight dimensions; missing or inconsistent evidence is UNKNOWN."""
if not isinstance(snapshot, AuditSnapshot) or not isinstance(limits, RuleLimits):
raise TypeError("snapshot and limits must be typed values")
now = _aware(now, "now")
grouped: dict[Dimension, list[DimensionInput]] = {dimension: [] for dimension in _DIMENSIONS}
for item in snapshot.dimensions:
grouped[item.dimension].append(item)
global_reason: str | None = None
if snapshot.rules_revision != limits.rules_revision:
global_reason = "RULES_REVISION_UNKNOWN"
elif snapshot.window_end > now:
global_reason = "AUDIT_WINDOW_NOT_CLOSED"
else:
observed = [sample.observed_at for item in snapshot.dimensions for sample in item.samples]
if observed and (max(observed) - min(observed)).total_seconds() > limits.max_snapshot_skew_seconds:
global_reason = "CROSS_DOMAIN_SNAPSHOT_SKEW"
findings: list[Finding] = []
for dimension in _DIMENSIONS:
items = grouped[dimension]
if global_reason is not None:
refs = tuple(
dict.fromkeys(sample.evidence_ref for item in items for sample in item.samples)
)
findings.append(_unknown(dimension, global_reason, refs))
continue
if len(items) != 1:
findings.append(
_unknown(
dimension,
"DIMENSION_MISSING" if not items else "DIMENSION_CONFLICT",
)
)
continue
item = items[0]
refs = tuple(dict.fromkeys(sample.evidence_ref for sample in item.samples))
if item.captured_at > now or now >= item.expires_at:
findings.append(_unknown(dimension, "DIMENSION_STALE_OR_FUTURE", refs))
continue
if any(
sample.observed_at < snapshot.window_start
or sample.observed_at > snapshot.window_end
for sample in item.samples
):
findings.append(_unknown(dimension, "SAMPLE_OUTSIDE_WINDOW", refs))
continue
findings.append(_evaluate_dimension(item, limits.thresholds))
report_payload = {
"window_start": _canonical_datetime(snapshot.window_start),
"window_end": _canonical_datetime(snapshot.window_end),
"snapshot_id": snapshot.snapshot_id,
"rules_revision": limits.rules_revision,
}
return AuditReport(
report_id=_digest(report_payload),
snapshot_id=snapshot.snapshot_id,
rules_revision=limits.rules_revision,
findings=tuple(findings),
mutation_count=0,
)
class ReadonlyAuditSource(Protocol):
def collect(
self, *, window_start: datetime, window_end: datetime
) -> AuditSnapshot: ...
class AuditReportSink(Protocol):
def put_if_absent(self, report: AuditReport) -> str: ...
def _report_bytes(report: AuditReport) -> bytes:
payload = {
"schema": "aios.aro-readonly-report.v1",
"report_id": report.report_id,
"snapshot_id": report.snapshot_id,
"rules_revision": report.rules_revision,
"mutation_count": report.mutation_count,
"findings": [
{
"dimension": finding.dimension.value,
"status": finding.status,
"reason": finding.reason,
"evidence_refs": list(finding.evidence_refs),
}
for finding in report.findings
],
}
return (
json.dumps(
payload,
sort_keys=True,
separators=(",", ":"),
ensure_ascii=False,
allow_nan=False,
)
+ "\n"
).encode("utf-8")
class FilesystemAuditReportSink:
"""Write one immutable, deterministic report under a host-fixed root."""
def __init__(self, root: Path) -> None:
if not isinstance(root, Path) or not root.is_absolute() or ".." in root.parts:
raise ValueError("audit root must be an absolute fixed path")
self.root = root
self._prepare_root()
def _prepare_root(self) -> None:
existing = self.root
while not existing.exists() and existing != existing.parent:
existing = existing.parent
cursor = existing
while True:
if cursor.is_symlink():
raise ValueError("audit root ancestry must not contain symlinks")
if cursor == cursor.parent:
break
cursor = cursor.parent
self.root.mkdir(parents=True, exist_ok=True)
if self.root.is_symlink() or not self.root.is_dir():
raise ValueError("audit root must be a real directory")
if self.root.resolve(strict=True) != self.root.absolute():
raise ValueError("audit root must not traverse reparse points or symlinks")
def put_if_absent(self, report: AuditReport) -> str:
if not isinstance(report, AuditReport):
raise TypeError("report must be AuditReport")
self._prepare_root()
validate_digest(report.report_id)
body = _report_bytes(report)
target = self.root / f"{report.report_id}.json"
if target.parent != self.root or target.is_symlink():
raise ValueError("REPORT_PATH_INVALID")
if target.exists():
if not target.is_file() or target.read_bytes() != body:
raise ValueError("REPORT_CONFLICT")
return f"aro:{report.report_id}"
file_descriptor, temporary_name = tempfile.mkstemp(
prefix=f".{report.report_id}.", suffix=".tmp", dir=self.root
)
temporary = Path(temporary_name)
try:
with os.fdopen(file_descriptor, "wb") as handle:
handle.write(body)
handle.flush()
os.fsync(handle.fileno())
try:
os.link(temporary, target)
except FileExistsError:
if target.is_symlink() or not target.is_file() or target.read_bytes() != body:
raise ValueError("REPORT_CONFLICT")
if target.read_bytes() != body:
raise ValueError("REPORT_CONFLICT")
finally:
try:
temporary.unlink()
except FileNotFoundError:
pass
return f"aro:{report.report_id}"
class AroReadonlyTick:
"""Default-off in-process tick over the most recently closed UTC window."""
def __init__(
self,
source: ReadonlyAuditSource,
sink: AuditReportSink,
limits: RuleLimits,
interval_seconds: int,
clock: Callable[[], datetime],
*,
enabled: bool = False,
) -> None:
if not hasattr(source, "collect") or not callable(source.collect):
raise ValueError("source must implement read-only collect")
if not hasattr(sink, "put_if_absent") or not callable(sink.put_if_absent):
raise ValueError("sink must implement put_if_absent")
if not isinstance(limits, RuleLimits):
raise ValueError("limits must be RuleLimits")
if isinstance(interval_seconds, bool) or not isinstance(interval_seconds, int) or interval_seconds <= 0:
raise ValueError("interval_seconds must be positive int")
if not callable(clock) or type(enabled) is not bool:
raise ValueError("clock must be callable and enabled must be bool")
self.source = source
self.sink = sink
self.limits = limits
self.interval_seconds = interval_seconds
self.clock = clock
self.enabled = enabled
self._last_window_end: datetime | None = None
def run_due(self) -> str | None:
if not self.enabled:
return None
now = _aware(self.clock(), "clock result").astimezone(timezone.utc)
end_timestamp = int(now.timestamp()) // self.interval_seconds * self.interval_seconds
window_end = datetime.fromtimestamp(end_timestamp, tz=timezone.utc)
window_start = window_end - timedelta(seconds=self.interval_seconds)
if self._last_window_end == window_end:
return None
try:
snapshot = self.source.collect(
window_start=window_start, window_end=window_end
)
if (
not isinstance(snapshot, AuditSnapshot)
or snapshot.window_start != window_start
or snapshot.window_end != window_end
):
raise ValueError("source returned a snapshot for the wrong window")
except Exception:
snapshot = AuditSnapshot.create(
window_start=window_start,
window_end=window_end,
dimensions=(),
rules_revision=self.limits.rules_revision,
)
report = evaluate_snapshot(snapshot, limits=self.limits, now=now)
try:
reference = self.sink.put_if_absent(report)
except Exception:
return None
self._last_window_end = window_end
return reference
__all__ = [
"AroReadonlyTick",
"AuditReport",
"AuditReportSink",
"AuditSnapshot",
"Dimension",
"DimensionInput",
"FilesystemAuditReportSink",
"Finding",
"ReadonlyAuditSource",
"RuleLimits",
"Sample",
"evaluate_snapshot",
]
@@ -0,0 +1,173 @@
"""Narrow production seam from a validated C6 plan to existing acquisition."""
from __future__ import annotations
from dataclasses import replace
import math
import time
from typing import Protocol
from .authorization_planner import (
AuthorizationPlan,
AuthorizationPlanInput,
CapabilityNamespace,
GitManifestRegistry,
RuntimeObservation,
StaticCapabilityManifest,
VerifiedRootSnapshot,
VerifiedRootSnapshotReader,
authorization_input_digest,
authorization_scope_digest,
build_authorization_plan,
verified_root_snapshot_digest,
)
from .governed_writer_automation import (
AuthorizationDenied,
EpochStatus,
GovernedWriterAdapters,
GovernedWriterAutomation,
GovernedWriterRequest,
GovernedWriterSession,
GovernedWriteReadiness,
RecoveryAuthorization,
)
class RuntimeObservationReader(Protocol):
"""Host-owned fresh runtime authority used at writer acquisition."""
def read_runtime_observation(
self,
*,
plan_input: AuthorizationPlanInput,
manifest: StaticCapabilityManifest,
root: VerifiedRootSnapshot | None,
now: float,
) -> RuntimeObservation | None: ...
def acquire_authorized_writer(
*,
automation: GovernedWriterAutomation,
request: GovernedWriterRequest,
authorization_ref: str,
readiness: GovernedWriteReadiness,
epoch: EpochStatus | None,
adapters: GovernedWriterAdapters,
plan: AuthorizationPlan,
plan_input: AuthorizationPlanInput,
root_reader: VerifiedRootSnapshotReader,
runtime_reader: RuntimeObservationReader,
registry: GitManifestRegistry,
expected_registry_oid: str,
expected_manifest_sha256: str,
recovery_authorization: RecoveryAuthorization | None = None,
now: float | None = None,
) -> GovernedWriterSession:
"""Re-read existing verified Root immediately before `acquire_verified`.
This function deliberately has no signer, claim, lease, or direct
`automation.acquire` path. The existing automation remains the sole
composition root for effects.
"""
supplied_now = time.time() if now is None else now
if isinstance(supplied_now, bool):
raise AuthorizationDenied("TIME_INVALID")
try:
current = float(supplied_now)
except (TypeError, ValueError):
raise AuthorizationDenied("TIME_INVALID") from None
if not math.isfinite(current):
raise AuthorizationDenied("TIME_INVALID")
input_digest = authorization_input_digest(plan_input)
scope_digest = authorization_scope_digest(plan_input.scope)
if (plan_input.root_approval_ref != authorization_ref
or plan.input_digest != input_digest
or plan.scope_digest != scope_digest):
raise AuthorizationDenied("PLAN_INPUT_DRIFT")
if (request.subject != plan_input.actor_subject or request.task_id != plan_input.task_id
or request.repository != plan_input.scope.repository
or request.target_branch != plan_input.scope.target_branch
or request.base_oid != plan_input.scope.source_oid
or request.risk != plan_input.scope.risk_level
or request.file_allowlist != plan_input.scope.file_allowlist
or request.plan_digest != input_digest):
raise AuthorizationDenied("WRITER_REQUEST_PLAN_DRIFT")
if any(ref.namespace != CapabilityNamespace.AGENT_TASK for ref in plan_input.required_capabilities):
raise AuthorizationDenied("WRITER_REQUEST_CAPABILITY_NAMESPACE_INVALID")
requested_capabilities = frozenset(ref.token for ref in plan_input.required_capabilities)
if request.required_capabilities != requested_capabilities:
raise AuthorizationDenied("WRITER_REQUEST_CAPABILITY_DRIFT")
# This is a second, explicit frozen-runtime read. The existing
# acquire_verified path repeats it before effects as its own invariant.
automation.manifest_store.assert_current(
manifest_id=request.manifest_id,
runtime_fingerprint=request.runtime_fingerprint,
process_start_identity=request.process_start_identity,
)
manifest = registry.read_manifest(
expected_registry_oid=expected_registry_oid,
expected_manifest_sha256=expected_manifest_sha256,
)
if manifest.manifest_id != request.manifest_id or manifest.declaration_digest != plan.policy_digest:
raise AuthorizationDenied("POLICY_MANIFEST_DRIFT")
root = root_reader.read_verified_root(
authorization_ref=authorization_ref,
root_request_sha256=plan_input.root_request_sha256,
now=current,
)
if root is None or root.status != "VALID" or root.approval_ref != authorization_ref:
raise AuthorizationDenied("ROOT_REVOKED_OR_UNAVAILABLE")
if (root.root_request_sha256 != plan_input.root_request_sha256
or root.subject != plan_input.actor_subject
or root.audience != plan_input.audience
or root.mission_id != plan_input.mission_id
or root.scope != plan_input.scope):
raise AuthorizationDenied("ROOT_SCOPE_DRIFT")
if verified_root_snapshot_digest(root) != plan.root_snapshot_digest:
raise AuthorizationDenied("ROOT_SNAPSHOT_DRIFT")
if not (root.not_before <= current and root.observed_at <= current < root.snapshot_expires_at <= root.expires_at):
raise AuthorizationDenied("ROOT_EXPIRED")
reader = getattr(runtime_reader, "read_runtime_observation", None)
if not callable(reader):
raise AuthorizationDenied("RUNTIME_OBSERVATION_UNAVAILABLE")
try:
runtime = reader(
plan_input=plan_input,
manifest=manifest,
root=root,
now=current,
)
except Exception:
raise AuthorizationDenied("RUNTIME_OBSERVATION_UNAVAILABLE") from None
if not isinstance(runtime, RuntimeObservation):
raise AuthorizationDenied("RUNTIME_OBSERVATION_UNAVAILABLE")
fresh_input = replace(plan_input, now=current)
fresh_plan = build_authorization_plan(
request=fresh_input,
manifest=manifest,
runtime=runtime,
root=root,
)
if fresh_plan.next_action != "EXECUTE_GOVERNED_PLAN":
raise AuthorizationDenied(
fresh_plan.reason_code or "authorization plan is not executable"
)
if (
plan.policy_digest != fresh_plan.policy_digest
or plan.required_authorization != fresh_plan.required_authorization
or plan.scope_digest != fresh_plan.scope_digest
or plan.root_snapshot_digest != fresh_plan.root_snapshot_digest
):
raise AuthorizationDenied("AUTHORIZATION_PLAN_DRIFT")
# `acquire_verified` repeats the existing Owner authority verification
# before its own effect path. Do not substitute a locally reconstructed
# policy or call its test-only `acquire` method here.
return automation.acquire_verified(
request=request,
authorization_ref=authorization_ref,
readiness=readiness,
epoch=epoch,
adapters=adapters,
recovery_authorization=recovery_authorization,
now=current,
)
@@ -0,0 +1,325 @@
"""Read-only C6 authorization planning over existing authority boundaries."""
from __future__ import annotations
from dataclasses import asdict, dataclass
from enum import Enum
import hashlib
import json
import math
from pathlib import Path, PurePosixPath
import re
import subprocess
from typing import Any, Literal, Mapping, Protocol
from aios_capability_contract import normalize_required_capabilities
from .governed_writer_automation import RISK_ORDER
_OID = re.compile(r"^[0-9a-f]{40}$")
_SHA = re.compile(r"^[0-9a-f]{64}$")
_REGISTRY_PATH = "governance/capabilities/registry.v1.json"
class RegistryReadError(ValueError):
pass
class CapabilityNamespace(str, Enum):
AGENT_TASK = "agent-task"
TASK_PLANE_WILDCARD = "task-plane-wildcard"
def _text(value: object, field: str) -> str:
if not isinstance(value, str) or not value or value != value.strip() or "\x00" in value:
raise ValueError(f"{field}_INVALID")
return value
def _oid(value: object, field: str) -> str:
value = _text(value, field)
if _OID.fullmatch(value) is None:
raise ValueError(f"{field}_INVALID")
return value
def _digest(value: object, field: str) -> str:
value = _text(value, field)
if _SHA.fullmatch(value) is None:
raise ValueError(f"{field}_INVALID")
return value
def _finite(value: object, field: str) -> float:
if isinstance(value, bool) or not isinstance(value, (float, int)) or not math.isfinite(float(value)):
raise ValueError(f"{field}_INVALID")
return float(value)
def _canonical(value: object) -> bytes:
return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False, allow_nan=False).encode("utf-8")
def authorization_scope_digest(scope: "ScopeSpec") -> str:
return hashlib.sha256(_canonical({
"repository": scope.repository, "target_branch": scope.target_branch,
"source_oid": scope.source_oid, "file_allowlist": list(scope.file_allowlist),
"budget_units": scope.budget_units, "risk_level": scope.risk_level,
})).hexdigest()
@dataclass(frozen=True)
class CapabilityRef:
namespace: CapabilityNamespace
token: str
def __post_init__(self) -> None:
if not isinstance(self.namespace, CapabilityNamespace):
raise ValueError("CAPABILITY_NAMESPACE_INVALID")
_text(self.token, "capability")
@dataclass(frozen=True)
class ScopeSpec:
repository: str
target_branch: str
source_oid: str
file_allowlist: tuple[str, ...]
budget_units: int
risk_level: str
def __post_init__(self) -> None:
_text(self.repository, "repository"); _text(self.target_branch, "target_branch")
_oid(self.source_oid, "source_oid"); _text(self.risk_level, "risk_level")
if type(self.budget_units) is not int or self.budget_units < 0:
raise ValueError("budget_units_INVALID")
if not self.file_allowlist or len(set(self.file_allowlist)) != len(self.file_allowlist):
raise ValueError("file_allowlist_INVALID")
for path in self.file_allowlist:
if not isinstance(path, str) or not path or path != PurePosixPath(path).as_posix() or PurePosixPath(path).is_absolute() or ".." in PurePosixPath(path).parts:
raise ValueError("file_allowlist_INVALID")
@dataclass(frozen=True)
class RuntimeObservation:
observation_id: str; observed_at: float; expires_at: float; source_revision: str
actor_subject: str; audience: str; operation_id: str; scope_digest: str
manifest_revision: str; manifest_digest: str; executor_installed: bool | None
executor_callable: bool | None; authorization_live: bool | None
preconditions_pass: bool | None; revocation_epoch: int | None
def __post_init__(self) -> None:
for name in ("observation_id", "source_revision", "actor_subject", "audience", "operation_id", "manifest_revision"):
_text(getattr(self, name), name)
_finite(self.observed_at, "observed_at"); _finite(self.expires_at, "expires_at")
if self.expires_at <= self.observed_at: raise ValueError("OBSERVATION_EXPIRY_INVALID")
_digest(self.scope_digest, "scope_digest"); _digest(self.manifest_digest, "manifest_digest")
for name in ("executor_installed", "executor_callable", "authorization_live", "preconditions_pass"):
if getattr(self, name) is not None and type(getattr(self, name)) is not bool: raise ValueError(f"{name}_INVALID")
if self.revocation_epoch is not None and (type(self.revocation_epoch) is not int or self.revocation_epoch < 0): raise ValueError("revocation_epoch_INVALID")
@dataclass(frozen=True)
class VerifiedRootSnapshot:
approval_ref: str; root_request_sha256: str; subject: str; audience: str; mission_id: str
scope: ScopeSpec; capabilities: tuple[CapabilityRef, ...]; nonce: str; not_before: float
expires_at: float; revocation_epoch: int; receipt_ref: str; observed_at: float
snapshot_expires_at: float; status: Literal["VALID", "REVOKED"]
def __post_init__(self) -> None:
for name in ("approval_ref", "subject", "audience", "mission_id", "nonce", "receipt_ref"):
_text(getattr(self, name), name)
_digest(self.root_request_sha256, "root_request_sha256")
for name in ("not_before", "expires_at", "observed_at", "snapshot_expires_at"): _finite(getattr(self, name), name)
if not self.not_before <= self.snapshot_expires_at <= self.expires_at: raise ValueError("ROOT_TIME_INVALID")
if type(self.revocation_epoch) is not int or self.revocation_epoch < 0: raise ValueError("revocation_epoch_INVALID")
if self.status not in ("VALID", "REVOKED"): raise ValueError("ROOT_STATUS_INVALID")
@dataclass(frozen=True)
class StaticCapabilityManifest:
manifest_id: str; revision: str; capabilities: tuple[CapabilityRef, ...]
executor_refs: tuple[str, ...]; source_oid: str; declaration_digest: str
allowed_scopes: tuple[ScopeSpec, ...]
def __post_init__(self) -> None:
_text(self.manifest_id, "manifest_id"); _text(self.revision, "revision")
_oid(self.source_oid, "source_oid"); _digest(self.declaration_digest, "declaration_digest")
if len(set(self.capabilities)) != len(self.capabilities): raise ValueError("CAPABILITY_DUPLICATE")
for ref in self.executor_refs: _text(ref, "executor_ref")
@dataclass(frozen=True)
class AuthorizationPlanInput:
operation_id: str; actor_subject: str; audience: str; mission_id: str; task_id: str
scope: ScopeSpec; required_capabilities: tuple[CapabilityRef, ...]
root_approval_ref: str; root_request_sha256: str; now: float
def __post_init__(self) -> None:
for name in ("operation_id", "actor_subject", "audience", "mission_id", "task_id", "root_approval_ref"):
_text(getattr(self, name), name)
_digest(self.root_request_sha256, "root_request_sha256"); _finite(self.now, "now")
if not self.required_capabilities: raise ValueError("REQUIRED_CAPABILITIES_EMPTY")
@dataclass(frozen=True)
class AuthorizationPlan:
input_digest: str; scope_digest: str; policy_digest: str; required_authorization: str
root_snapshot_digest: str | None
reason_code: str | None; next_action: Literal["EXECUTE_GOVERNED_PLAN", "WAITING_ROOT_AUTHORIZATION", "AUTHORIZATION_REQUIRED", "CAPABILITY_UNAVAILABLE", "PRECONDITION_FAILED", "UNKNOWN"]
evidence_refs: tuple[str, ...]
class GitManifestRegistry(Protocol):
def read_manifest(self, *, expected_registry_oid: str, expected_manifest_sha256: str) -> StaticCapabilityManifest: ...
def read_supersession(self, *, manifest_id: str, revision: str) -> str | None: ...
class VerifiedRootSnapshotReader(Protocol):
def read_verified_root(self, *, authorization_ref: str, root_request_sha256: str, now: float) -> VerifiedRootSnapshot | None: ...
def verified_root_snapshot_digest(root: VerifiedRootSnapshot | None) -> str | None:
return None if root is None else hashlib.sha256(_canonical(asdict(root))).hexdigest()
def authorization_input_digest(request: AuthorizationPlanInput) -> str:
return hashlib.sha256(_canonical(asdict(request))).hexdigest()
def _plan(request: AuthorizationPlanInput, manifest: StaticCapabilityManifest, action: str, reason: str | None, refs: tuple[str, ...], root: VerifiedRootSnapshot | None) -> AuthorizationPlan:
return AuthorizationPlan(
input_digest=authorization_input_digest(request), scope_digest=authorization_scope_digest(request.scope),
policy_digest=manifest.declaration_digest, required_authorization=request.root_approval_ref,
root_snapshot_digest=verified_root_snapshot_digest(root),
reason_code=reason, next_action=action, evidence_refs=refs,
)
def _capabilities_cover(granted: tuple[CapabilityRef, ...], required: tuple[CapabilityRef, ...]) -> tuple[bool, str | None]:
for capability in required:
if capability.namespace == CapabilityNamespace.AGENT_TASK:
if "*" in capability.token:
return False, "CAPABILITY_NAMESPACE_MISMATCH"
normalized = normalize_required_capabilities([capability.token])
if any(value.startswith("exact:") for value in normalized): return False, "CAPABILITY_UNKNOWN"
same_namespace = [item.token for item in granted if item.namespace == CapabilityNamespace.AGENT_TASK]
if not same_namespace and granted:
return False, "CAPABILITY_NAMESPACE_MISMATCH"
effective_grants = normalize_required_capabilities(same_namespace)
if any(value.startswith("exact:") for value in effective_grants):
effective_grants = frozenset(value for value in effective_grants if not value.startswith("exact:"))
if not normalized.issubset(effective_grants): return False, "CAPABILITY_UNAVAILABLE"
elif capability.namespace == CapabilityNamespace.TASK_PLANE_WILDCARD:
if not any(item.namespace == capability.namespace and item.token == capability.token for item in granted): return False, "CAPABILITY_UNAVAILABLE"
return True, None
def _scope_covers(parent: ScopeSpec, child: ScopeSpec) -> bool:
# Use the same risk vocabulary as the authority that will issue the lease.
rank = RISK_ORDER
return (parent.repository == child.repository and parent.target_branch == child.target_branch and parent.source_oid == child.source_oid
and set(child.file_allowlist).issubset(parent.file_allowlist) and child.budget_units <= parent.budget_units
and rank.get(child.risk_level.lower()) is not None
and rank.get(parent.risk_level.lower(), 0) >= rank[child.risk_level.lower()])
def build_authorization_plan(*, request: AuthorizationPlanInput, manifest: StaticCapabilityManifest, runtime: RuntimeObservation, root: VerifiedRootSnapshot | None) -> AuthorizationPlan:
refs = () if root is None else (root.receipt_ref,)
if manifest.source_oid != request.scope.source_oid or not any(_scope_covers(scope, request.scope) for scope in manifest.allowed_scopes):
return _plan(request, manifest, "UNKNOWN", "REGISTRY_DRIFT", refs, root)
allowed, reason = _capabilities_cover(manifest.capabilities, request.required_capabilities)
if not allowed: return _plan(request, manifest, "CAPABILITY_UNAVAILABLE", reason, refs, root)
if root is None: return _plan(request, manifest, "WAITING_ROOT_AUTHORIZATION", "ROOT_MISSING", refs, root)
if root.status == "REVOKED": return _plan(request, manifest, "AUTHORIZATION_REQUIRED", "ROOT_REVOKED", refs, root)
if root.approval_ref != request.root_approval_ref or root.root_request_sha256 != request.root_request_sha256: return _plan(request, manifest, "AUTHORIZATION_REQUIRED", "ROOT_DIGEST_MISMATCH", refs, root)
if root.subject != request.actor_subject or root.audience != request.audience: return _plan(request, manifest, "AUTHORIZATION_REQUIRED", "ROOT_SUBJECT_AUDIENCE_MISMATCH", refs, root)
if root.mission_id != request.mission_id or not _scope_covers(root.scope, request.scope): return _plan(request, manifest, "AUTHORIZATION_REQUIRED", "ROOT_SCOPE_EXCEEDED", refs, root)
allowed, reason = _capabilities_cover(root.capabilities, request.required_capabilities)
if not allowed: return _plan(request, manifest, "AUTHORIZATION_REQUIRED", reason, refs, root)
if not (root.observed_at <= request.now < root.snapshot_expires_at <= root.expires_at) or request.now < root.not_before: return _plan(request, manifest, "WAITING_ROOT_AUTHORIZATION", "ROOT_EXPIRED", refs, root)
if runtime.expires_at <= request.now or runtime.observed_at > request.now: return _plan(request, manifest, "UNKNOWN", "PRECONDITION_UNKNOWN", refs, root)
if runtime.revocation_epoch is None or runtime.revocation_epoch != root.revocation_epoch: return _plan(request, manifest, "UNKNOWN", "EPOCH_REVOKED", refs, root)
if (runtime.actor_subject != request.actor_subject or runtime.audience != request.audience or runtime.operation_id != request.operation_id
or runtime.scope_digest != authorization_scope_digest(request.scope) or runtime.manifest_revision != manifest.revision
or runtime.manifest_digest != manifest.declaration_digest): return _plan(request, manifest, "UNKNOWN", "PLAN_INPUT_DRIFT", refs, root)
if any(value is None for value in (runtime.executor_installed, runtime.executor_callable, runtime.authorization_live, runtime.preconditions_pass)):
return _plan(request, manifest, "UNKNOWN", "PRECONDITION_UNKNOWN", refs, root)
if not all((runtime.executor_installed, runtime.executor_callable, runtime.authorization_live, runtime.preconditions_pass)):
return _plan(request, manifest, "PRECONDITION_FAILED", "PRECONDITION_FAILED", refs, root)
return _plan(request, manifest, "EXECUTE_GOVERNED_PLAN", None, refs, root)
class GitManifestRegistryReader:
"""Fixed-path, immutable Git blob reader; intentionally has no write API."""
def __init__(self, *, configured_repo_root: Path, manifest_id: str) -> None:
self.root = Path(configured_repo_root); self.manifest_id = _text(manifest_id, "manifest_id")
self._last_registry: Mapping[str, Any] | None = None
def _blob(self, oid: str) -> bytes:
_oid(oid, "expected_registry_oid")
if self.root.is_symlink() or not self.root.is_dir(): raise RegistryReadError("REGISTRY_ROOT_INVALID")
result = subprocess.run(["git", "-C", str(self.root), "show", f"{oid}:{_REGISTRY_PATH}"], capture_output=True, timeout=15)
if result.returncode: raise RegistryReadError("REGISTRY_UNAVAILABLE")
return result.stdout
def _registry(self, *, expected_registry_oid: str, expected_manifest_sha256: str) -> Mapping[str, Any]:
raw = self._blob(expected_registry_oid)
if hashlib.sha256(raw).hexdigest() != _digest(expected_manifest_sha256, "expected_manifest_sha256"): raise RegistryReadError("REGISTRY_DRIFT")
try: value = json.loads(raw)
except (UnicodeError, json.JSONDecodeError) as exc: raise RegistryReadError("REGISTRY_INVALID") from exc
if not isinstance(value, Mapping) or set(value) != {"schema", "records", "supersessions"} or value["schema"] != "aios.capability-registry.v1": raise RegistryReadError("REGISTRY_INVALID")
return value
def read_manifest(self, *, expected_registry_oid: str, expected_manifest_sha256: str) -> StaticCapabilityManifest:
registry = self._registry(expected_registry_oid=expected_registry_oid, expected_manifest_sha256=expected_manifest_sha256)
records = registry["records"]
if not isinstance(records, list): raise RegistryReadError("REGISTRY_INVALID")
supersessions = registry["supersessions"]
if not isinstance(supersessions, list): raise RegistryReadError("REGISTRY_INVALID")
record_index: dict[tuple[str, str], Mapping[str, Any]] = {}
for record in records:
if not isinstance(record, Mapping) or not isinstance(record.get("manifest_id"), str) or not isinstance(record.get("revision"), str):
raise RegistryReadError("REGISTRY_INVALID")
key = (record["manifest_id"], record["revision"])
if key in record_index: raise RegistryReadError("REGISTRY_DRIFT")
record_index[key] = record
replaced: dict[tuple[str, str], tuple[str, str]] = {}
for item in supersessions:
if not isinstance(item, Mapping) or set(item) != {"manifest_id", "prior_revision", "new_revision", "evidence_ref"}:
raise RegistryReadError("REGISTRY_INVALID")
try:
manifest_id = _text(item["manifest_id"], "manifest_id")
prior = _text(item["prior_revision"], "prior_revision")
newer = _text(item["new_revision"], "new_revision")
_text(item["evidence_ref"], "evidence_ref")
except ValueError as exc:
raise RegistryReadError("REGISTRY_INVALID") from exc
prior_key, new_key = (manifest_id, prior), (manifest_id, newer)
if prior_key not in record_index or new_key not in record_index or prior_key == new_key or prior_key in replaced:
raise RegistryReadError("REGISTRY_DRIFT")
replaced[prior_key] = new_key
candidates = [record for record in records if isinstance(record, Mapping) and record.get("manifest_id") == self.manifest_id]
if not candidates: raise RegistryReadError("REGISTRY_DRIFT")
current = [record for record in candidates if (self.manifest_id, record.get("revision")) not in replaced]
if len(current) != 1: raise RegistryReadError("REGISTRY_DRIFT")
record = current[0]; required = {"manifest_id", "revision", "capabilities", "executor_refs", "source_oid", "declaration_digest", "allowed_scopes"}
if set(record) != required: raise RegistryReadError("REGISTRY_INVALID")
unsigned = {key: value for key, value in record.items() if key != "declaration_digest"}
if hashlib.sha256(_canonical(unsigned)).hexdigest() != _digest(record["declaration_digest"], "declaration_digest"): raise RegistryReadError("REGISTRY_DRIFT")
try:
manifest = StaticCapabilityManifest(str(record["manifest_id"]), str(record["revision"]), tuple(CapabilityRef(CapabilityNamespace(item["namespace"]), item["token"]) for item in record["capabilities"]), tuple(record["executor_refs"]), str(record["source_oid"]), str(record["declaration_digest"]), tuple(ScopeSpec(**item) for item in record["allowed_scopes"]))
except (KeyError, TypeError, ValueError) as exc: raise RegistryReadError("REGISTRY_INVALID") from exc
self._last_registry = registry
return manifest
def read_supersession(self, *, manifest_id: str, revision: str) -> str | None:
_text(manifest_id, "manifest_id"); _text(revision, "revision")
if self._last_registry is None:
raise RegistryReadError("REGISTRY_READ_REQUIRES_PINNED_DIGEST")
matches = [
item["new_revision"] for item in self._last_registry["supersessions"]
if item["manifest_id"] == manifest_id and item["prior_revision"] == revision
]
if len(matches) > 1:
raise RegistryReadError("REGISTRY_DRIFT")
return matches[0] if matches else None
@@ -0,0 +1,972 @@
"""AIOS-native binding layer for the unattended resume controller.
This module intentionally contains no HTTP client, Unix-socket client, database,
systemd integration, scheduler loop, secret handling, or privileged identity
mutation.
It converts existing AIOS authorities into the generic callbacks defined by
``autonomous_resume_integration``. Field code supplies the current authoritative
implementations for:
- durable Owner authorization/grant projection;
- canonical governed-worker activation/status;
- governed task admission;
- task/claim/TaskLease/TaskPrincipal/WriterLease/CAS ownership;
- bounded source-lane execution and integration;
- Gate0;
- ReleaseReady/deployability;
- PromotionController;
- final production acceptance;
- mission resume lease/checkpoint/effect reconciliation.
The adapter is deterministic and fail-closed. It never fabricates any credential,
principal, lease, fence, task, commit, receipt, approval, or deployment result.
"""
from __future__ import annotations
from dataclasses import dataclass
from enum import Enum
import hashlib
import json
from typing import Mapping, Protocol, Sequence
from .autonomous_resume_controller import (
EffectState,
MissionCheckpoint,
MissionObservation,
MissionPhase,
ResumeLease,
StepResult,
StepStatus,
)
from .autonomous_resume_integration import (
AuthorityCallbacks,
CallableAutonomousExecutionPort,
PhaseCallbacks,
)
def _canonical(value) -> bytes:
return json.dumps(
value,
sort_keys=True,
separators=(",", ":"),
ensure_ascii=False,
allow_nan=False,
).encode("utf-8")
def _digest(value) -> str:
return hashlib.sha256(_canonical(value)).hexdigest()
class WorkerState(str, Enum):
ACTIVE = "ACTIVE"
STARTING = "STARTING"
INACTIVE = "INACTIVE"
BLOCKED = "BLOCKED"
UNKNOWN = "UNKNOWN"
class WorkState(str, Enum):
PENDING = "PENDING"
RUNNING = "RUNNING"
COMPLETE = "COMPLETE"
BLOCKED = "BLOCKED"
UNKNOWN = "UNKNOWN"
class GateState(str, Enum):
PASS = "PASS"
HOLD = "HOLD"
UNKNOWN = "UNKNOWN"
class ReadyState(str, Enum):
READY = "READY"
NOT_READY = "NOT_READY"
UNKNOWN = "UNKNOWN"
class PromotionState(str, Enum):
IN_PROGRESS = "IN_PROGRESS"
VERIFIED = "VERIFIED"
ROLLED_BACK = "ROLLED_BACK"
BLOCKED = "BLOCKED"
UNKNOWN = "UNKNOWN"
class AcceptanceState(str, Enum):
PASS = "PASS"
FAIL_ROLLED_BACK = "FAIL_ROLLED_BACK"
BLOCKED = "BLOCKED"
UNKNOWN = "UNKNOWN"
@dataclass(frozen=True)
class DurableGrantView:
grant_id: str
active: bool
repository: str
allowed_target_prefixes: tuple[str, ...]
max_task_risk: str
expires_at: float | None = None
owner_action_required: bool = False
evidence_refs: tuple[str, ...] = ()
def permits(self, *, repository: str, branch: str, risk: str) -> bool:
if not self.active:
return False
if self.repository != repository:
return False
if not any(branch.startswith(prefix) for prefix in self.allowed_target_prefixes):
return False
rank = {"low": 0, "medium": 1, "engineering": 2, "high": 3}
if risk not in rank or self.max_task_risk not in rank:
return False
return rank[risk] <= rank[self.max_task_risk]
@dataclass(frozen=True)
class TaskLaneSpec:
lane: str
task_id: str
repository: str
target_branch: str
risk: str
description: str
write_set: tuple[str, ...]
def __post_init__(self) -> None:
if not self.lane or not self.task_id or not self.repository:
raise ValueError("lane_task_repository_required")
if not self.target_branch.startswith("agent/"):
raise ValueError("target_branch_must_use_agent_prefix")
if not self.write_set:
raise ValueError("write_set_required")
@dataclass(frozen=True)
class TaskAdmissionRequest:
task_id: str
idempotency_key: str
repository: str
base_oid: str
target_branch: str
expected_branch_oid: str
risk: str
description: str
@dataclass(frozen=True)
class TaskAdmissionReceipt:
task_id: str
generation: int
target_branch: str
persisted: bool
claimable: bool
receipt_ref: str
def __post_init__(self) -> None:
if self.generation < 1:
raise ValueError("task_generation_must_be_positive")
if not self.receipt_ref:
raise ValueError("task_admission_receipt_required")
@dataclass(frozen=True)
class WorkerActivationReceipt:
state: WorkerState
receipt_ref: str | None = None
retry_after_seconds: int | None = None
owner_action_required: bool = False
code: str = "WORKER_STATE"
@dataclass(frozen=True)
class LaneExecutionView:
lane: str
state: WorkState
condition_fingerprint: str
receipt_refs: tuple[str, ...] = ()
retry_after_seconds: int | None = None
code: str = "LANE_STATE"
@dataclass(frozen=True)
class IntegrationView:
state: WorkState
condition_fingerprint: str
receipt_refs: tuple[str, ...] = ()
retry_after_seconds: int | None = None
code: str = "INTEGRATION_STATE"
@dataclass(frozen=True)
class GateView:
state: GateState
condition_fingerprint: str
receipt_refs: tuple[str, ...] = ()
retry_after_seconds: int | None = None
code: str = "GATE_STATE"
@dataclass(frozen=True)
class ReleaseReadyView:
state: ReadyState
condition_fingerprint: str
material_digest: str | None
receipt_refs: tuple[str, ...] = ()
retry_after_seconds: int | None = None
code: str = "READY_STATE"
@dataclass(frozen=True)
class PromotionView:
state: PromotionState
condition_fingerprint: str
material_digest: str | None
receipt_refs: tuple[str, ...] = ()
retry_after_seconds: int | None = None
code: str = "PROMOTION_STATE"
@dataclass(frozen=True)
class AcceptanceView:
state: AcceptanceState
condition_fingerprint: str
receipt_refs: tuple[str, ...] = ()
code: str = "ACCEPTANCE_STATE"
@dataclass(frozen=True)
class MissionEffectView:
effect_state: EffectState
condition_fingerprint: str
requires_reconcile: bool
receipt_refs: tuple[str, ...] = ()
class AiosMissionAuthorities(Protocol):
"""Existing AIOS authority surface consumed by this adapter."""
# Live truth.
def read_head_oid(self, repository: str) -> str: ...
def read_material_digest(self, mission_id: str) -> str: ...
def read_generation(self, mission_id: str) -> int: ...
def read_effect_view(self, mission_id: str) -> MissionEffectView: ...
def owner_action_resolved(self, mission_id: str) -> bool: ...
# Durable mission state / one-winner resume authority.
def load_checkpoint(self, mission_id: str) -> MissionCheckpoint | None: ...
def acquire_resume_lease(
self,
mission_id: str,
generation: int,
idempotency_key: str,
lease_seconds: int,
) -> ResumeLease | None: ...
def persist_checkpoint(
self,
checkpoint: MissionCheckpoint,
lease: ResumeLease,
) -> None: ...
def persist_owner_action_required(
self,
payload: Mapping[str, object],
observation: MissionObservation,
checkpoint: MissionCheckpoint,
lease: ResumeLease,
) -> str: ...
def release_resume_lease(
self,
lease: ResumeLease,
outcome_code: str,
) -> None: ...
def reconcile_effects(
self,
observation: MissionObservation,
checkpoint: MissionCheckpoint,
lease: ResumeLease,
) -> StepResult: ...
# Admission.
def read_durable_grant(self, mission_id: str) -> DurableGrantView: ...
def ensure_canonical_worker(
self,
mission_id: str,
generation: int,
idempotency_key: str,
) -> WorkerActivationReceipt: ...
def create_or_resume_task(
self,
request: TaskAdmissionRequest,
) -> TaskAdmissionReceipt: ...
def read_task_admission(
self,
lane: TaskLaneSpec,
) -> TaskAdmissionReceipt | None: ...
# Phase execution.
def read_lane_execution(
self,
lane: TaskLaneSpec,
task: TaskAdmissionReceipt,
) -> LaneExecutionView: ...
def drive_integration(
self,
lanes: Sequence[TaskLaneSpec],
tasks: Sequence[TaskAdmissionReceipt],
) -> IntegrationView: ...
def run_gate0(self, mission_id: str) -> GateView: ...
def build_release_ready(self, mission_id: str) -> ReleaseReadyView: ...
def run_promotion(self, mission_id: str) -> PromotionView: ...
def run_final_acceptance(self, mission_id: str) -> AcceptanceView: ...
@dataclass(frozen=True)
class AiosUnattendedMissionConfig:
mission_id: str
repository: str
lanes: tuple[TaskLaneSpec, ...]
admission_risk: str = "medium"
def __post_init__(self) -> None:
if not self.mission_id or not self.repository:
raise ValueError("mission_and_repository_required")
if not self.lanes:
raise ValueError("at_least_one_lane_required")
names = [lane.lane for lane in self.lanes]
task_ids = [lane.task_id for lane in self.lanes]
branches = [lane.target_branch for lane in self.lanes]
if len(names) != len(set(names)):
raise ValueError("duplicate_lane")
if len(task_ids) != len(set(task_ids)):
raise ValueError("duplicate_task_id")
if len(branches) != len(set(branches)):
raise ValueError("duplicate_target_branch")
owners: dict[str, str] = {}
for lane in self.lanes:
if lane.repository != self.repository:
raise ValueError("lane_repository_mismatch")
for resource in lane.write_set:
if resource in owners:
raise ValueError(
f"overlapping_lane_resource:{resource}:"
f"{owners[resource]}:{lane.lane}"
)
owners[resource] = lane.lane
class AiosUnattendedCallbackFactory:
"""Construct generic controller callbacks from current AIOS authorities."""
def __init__(
self,
authority: AiosMissionAuthorities,
config: AiosUnattendedMissionConfig,
) -> None:
self.authority = authority
self.config = config
self._admissions: dict[str, TaskAdmissionReceipt] = {}
def build_port(self) -> CallableAutonomousExecutionPort:
return CallableAutonomousExecutionPort(
AuthorityCallbacks(
read_observation=self.read_observation,
load_checkpoint=self.authority.load_checkpoint,
acquire_resume_lease=self.authority.acquire_resume_lease,
persist_checkpoint=self.authority.persist_checkpoint,
persist_owner_action_required=self.authority.persist_owner_action_required,
release_resume_lease=self.authority.release_resume_lease,
reconcile=self.authority.reconcile_effects,
),
PhaseCallbacks(
admission=self.admission,
source_execution=self.source_execution,
gate0=self.gate0,
release_ready=self.release_ready,
promotion=self.promotion,
final_acceptance=self.final_acceptance,
),
)
def read_observation(self, mission_id: str) -> MissionObservation:
if mission_id != self.config.mission_id:
raise ValueError("mission_id_mismatch")
generation = self.authority.read_generation(mission_id)
head_oid = self.authority.read_head_oid(self.config.repository)
material = self.authority.read_material_digest(mission_id)
effect = self.authority.read_effect_view(mission_id)
fingerprint = _digest({
"schema": "aios.unattended-condition.v1",
"mission_id": mission_id,
"generation": generation,
"head_oid": head_oid,
"material_digest": material,
"effect_state": effect.effect_state.value,
"effect_condition": effect.condition_fingerprint,
"requires_reconcile": effect.requires_reconcile,
})
return MissionObservation(
mission_id=mission_id,
generation=generation,
head_oid=head_oid,
material_digest=material,
condition_fingerprint=fingerprint,
effect_state=effect.effect_state,
requires_reconcile=effect.requires_reconcile,
owner_action_resolved=self.authority.owner_action_resolved(mission_id),
evidence_refs=effect.receipt_refs,
)
def admission(
self,
observation: MissionObservation,
checkpoint: MissionCheckpoint,
lease: ResumeLease,
) -> StepResult:
grant = self.authority.read_durable_grant(observation.mission_id)
grant_refs = tuple(grant.evidence_refs)
if not grant.active:
return StepResult(
StepStatus.BLOCKED_OWNER if grant.owner_action_required
else StepStatus.BLOCKED_DETERMINISTIC,
"DURABLE_GRANT_NOT_ACTIVE",
self._phase_fingerprint(
"admission", observation, {"grant_id": grant.grant_id, "active": False}
),
receipt_refs=grant_refs,
owner_action_payload=(
{
"boundary": "DURABLE_GRANT_NOT_ACTIVE",
"grant_id": grant.grant_id,
}
if grant.owner_action_required else None
),
)
for lane in self.config.lanes:
if not grant.permits(
repository=lane.repository,
branch=lane.target_branch,
risk=lane.risk,
):
return StepResult(
StepStatus.BLOCKED_OWNER if grant.owner_action_required
else StepStatus.BLOCKED_DETERMINISTIC,
"DURABLE_GRANT_SCOPE_MISMATCH",
self._phase_fingerprint(
"admission",
observation,
{
"grant_id": grant.grant_id,
"lane": lane.lane,
"branch": lane.target_branch,
"risk": lane.risk,
},
),
receipt_refs=grant_refs,
owner_action_payload=(
{
"boundary": "DURABLE_GRANT_SCOPE_MISMATCH",
"grant_id": grant.grant_id,
"lane": lane.lane,
}
if grant.owner_action_required else None
),
)
worker = self.authority.ensure_canonical_worker(
observation.mission_id,
observation.generation,
self._idempotency(
"worker",
observation.mission_id,
observation.generation,
),
)
worker_refs = self._refs(grant_refs, (worker.receipt_ref or "",))
if worker.state is WorkerState.UNKNOWN:
return StepResult(
StepStatus.RECONCILE,
worker.code,
self._phase_fingerprint(
"worker", observation, {"state": worker.state.value}
),
receipt_refs=worker_refs,
)
if worker.state is WorkerState.BLOCKED:
return StepResult(
StepStatus.BLOCKED_OWNER if worker.owner_action_required
else StepStatus.BLOCKED_DETERMINISTIC,
worker.code,
self._phase_fingerprint(
"worker", observation, {"state": worker.state.value}
),
receipt_refs=worker_refs,
owner_action_payload=(
{"boundary": worker.code}
if worker.owner_action_required else None
),
)
if worker.state in {WorkerState.STARTING, WorkerState.INACTIVE}:
return StepResult(
StepStatus.IN_PROGRESS,
worker.code,
self._phase_fingerprint(
"worker", observation, {"state": worker.state.value}
),
receipt_refs=worker_refs,
retry_after_seconds=worker.retry_after_seconds,
)
if worker.state is WorkerState.ACTIVE and not worker.receipt_ref:
return StepResult(
StepStatus.RECONCILE,
"WORKER_ACTIVE_WITHOUT_RECEIPT",
self._phase_fingerprint(
"worker", observation, {"state": worker.state.value}
),
receipt_refs=worker_refs,
)
task_refs: list[str] = list(worker_refs)
admissions: dict[str, TaskAdmissionReceipt] = {}
for lane in self.config.lanes:
request = TaskAdmissionRequest(
task_id=lane.task_id,
idempotency_key=self._idempotency(
"task",
observation.generation,
lane.task_id,
observation.head_oid,
),
repository=lane.repository,
base_oid=observation.head_oid,
target_branch=lane.target_branch,
expected_branch_oid=observation.head_oid,
risk=lane.risk,
description=lane.description,
)
receipt = self.authority.create_or_resume_task(request)
task_refs.append(receipt.receipt_ref)
receipt_error = self._admission_receipt_error(
lane,
receipt,
observation=observation,
)
if receipt_error is not None:
return StepResult(
StepStatus.RECONCILE,
receipt_error,
self._phase_fingerprint(
"task",
observation,
{"lane": lane.lane, "task_id": lane.task_id},
),
receipt_refs=tuple(task_refs),
)
if not receipt.persisted:
return StepResult(
StepStatus.RECONCILE,
"TASK_PERSISTENCE_UNKNOWN",
self._phase_fingerprint(
"task",
observation,
{"lane": lane.lane, "task_id": lane.task_id},
),
receipt_refs=tuple(task_refs),
)
if not receipt.claimable:
return StepResult(
StepStatus.IN_PROGRESS,
"TASK_NOT_CLAIMABLE_YET",
self._phase_fingerprint(
"task",
observation,
{"lane": lane.lane, "task_id": lane.task_id},
),
receipt_refs=tuple(task_refs),
)
admissions[lane.lane] = receipt
self._admissions = admissions
return StepResult(
StepStatus.SUCCEEDED,
"GOVERNED_TASKS_ADMITTED",
self._phase_fingerprint(
"admission",
observation,
{"tasks": sorted(r.task_id for r in admissions.values())},
),
receipt_refs=tuple(task_refs),
next_phase=MissionPhase.S2_EXECUTION,
)
def source_execution(
self,
observation: MissionObservation,
checkpoint: MissionCheckpoint,
lease: ResumeLease,
) -> StepResult:
recovery_refs: list[str] = []
for lane in self.config.lanes:
if lane.lane in self._admissions:
continue
receipt = self.authority.read_task_admission(lane)
if receipt is None:
return StepResult(
StepStatus.RECONCILE,
"TASK_ADMISSION_NOT_RESOLVED",
self._phase_fingerprint(
"source",
observation,
{"lane": lane.lane, "task_id": lane.task_id},
),
receipt_refs=tuple(recovery_refs),
)
recovery_refs.append(receipt.receipt_ref)
receipt_error = self._admission_receipt_error(
lane,
receipt,
observation=observation,
)
if receipt_error is not None:
return StepResult(
StepStatus.RECONCILE,
receipt_error,
self._phase_fingerprint(
"source",
observation,
{"lane": lane.lane, "task_id": lane.task_id},
),
receipt_refs=tuple(recovery_refs),
)
if not receipt.persisted:
return StepResult(
StepStatus.RECONCILE,
"TASK_PERSISTENCE_UNKNOWN",
self._phase_fingerprint(
"source",
observation,
{"lane": lane.lane, "task_id": lane.task_id},
),
receipt_refs=tuple(recovery_refs),
)
if not receipt.claimable:
return StepResult(
StepStatus.IN_PROGRESS,
"TASK_NOT_CLAIMABLE_YET",
self._phase_fingerprint(
"source",
observation,
{"lane": lane.lane, "task_id": lane.task_id},
),
receipt_refs=tuple(recovery_refs),
)
self._admissions[lane.lane] = receipt
views: list[LaneExecutionView] = []
refs: list[str] = list(recovery_refs)
for lane in self.config.lanes:
view = self.authority.read_lane_execution(
lane,
self._admissions[lane.lane],
)
if view.lane != lane.lane:
return StepResult(
StepStatus.RECONCILE,
"LANE_IDENTITY_MISMATCH",
self._phase_fingerprint(
"source",
observation,
{"expected": lane.lane, "actual": view.lane},
),
receipt_refs=tuple(refs),
)
if view.state is WorkState.COMPLETE and not view.receipt_refs:
return StepResult(
StepStatus.RECONCILE,
"LANE_COMPLETE_WITHOUT_RECEIPT",
view.condition_fingerprint,
receipt_refs=tuple(refs),
)
views.append(view)
refs.extend(view.receipt_refs)
unknown = [v for v in views if v.state is WorkState.UNKNOWN]
if unknown:
return StepResult(
StepStatus.RECONCILE,
unknown[0].code,
self._phase_fingerprint(
"source",
observation,
{"states": [(v.lane, v.state.value) for v in views]},
),
receipt_refs=tuple(refs),
)
blocked = [v for v in views if v.state is WorkState.BLOCKED]
if blocked:
return StepResult(
StepStatus.BLOCKED_DETERMINISTIC,
blocked[0].code,
self._phase_fingerprint(
"source",
observation,
{"states": [(v.lane, v.state.value) for v in views]},
),
receipt_refs=tuple(refs),
)
pending = [
v for v in views
if v.state in {WorkState.PENDING, WorkState.RUNNING}
]
if pending:
retry_values = [
v.retry_after_seconds
for v in pending
if v.retry_after_seconds is not None
]
return StepResult(
StepStatus.IN_PROGRESS,
"SOURCE_LANES_IN_PROGRESS",
self._phase_fingerprint(
"source",
observation,
{"states": [(v.lane, v.state.value) for v in views]},
),
receipt_refs=tuple(refs),
retry_after_seconds=min(retry_values) if retry_values else None,
)
integration = self.authority.drive_integration(
self.config.lanes,
tuple(self._admissions[lane.lane] for lane in self.config.lanes),
)
refs.extend(integration.receipt_refs)
if integration.state is WorkState.UNKNOWN:
return StepResult(
StepStatus.RECONCILE,
integration.code,
integration.condition_fingerprint,
receipt_refs=tuple(refs),
)
if integration.state is WorkState.BLOCKED:
return StepResult(
StepStatus.BLOCKED_DETERMINISTIC,
integration.code,
integration.condition_fingerprint,
receipt_refs=tuple(refs),
)
if integration.state in {WorkState.PENDING, WorkState.RUNNING}:
return StepResult(
StepStatus.IN_PROGRESS,
integration.code,
integration.condition_fingerprint,
receipt_refs=tuple(refs),
retry_after_seconds=integration.retry_after_seconds,
)
if integration.state is WorkState.COMPLETE and not integration.receipt_refs:
return StepResult(
StepStatus.RECONCILE,
"INTEGRATION_COMPLETE_WITHOUT_RECEIPT",
integration.condition_fingerprint,
receipt_refs=tuple(refs),
)
return StepResult(
StepStatus.SUCCEEDED,
"SOURCE_INTEGRATION_COMPLETE",
integration.condition_fingerprint,
receipt_refs=tuple(refs),
next_phase=MissionPhase.GATE0,
)
def gate0(
self,
observation: MissionObservation,
checkpoint: MissionCheckpoint,
lease: ResumeLease,
) -> StepResult:
view = self.authority.run_gate0(observation.mission_id)
if view.state is GateState.UNKNOWN:
status = StepStatus.RECONCILE
elif view.state is GateState.HOLD:
status = StepStatus.BLOCKED_DETERMINISTIC
else:
if not view.receipt_refs:
return StepResult(
StepStatus.RECONCILE,
"GATE0_PASS_WITHOUT_RECEIPT",
view.condition_fingerprint,
)
status = StepStatus.SUCCEEDED
return StepResult(
status,
view.code,
view.condition_fingerprint,
receipt_refs=view.receipt_refs,
retry_after_seconds=view.retry_after_seconds,
next_phase=(
MissionPhase.RELEASE_READY
if status is StepStatus.SUCCEEDED else None
),
)
def release_ready(
self,
observation: MissionObservation,
checkpoint: MissionCheckpoint,
lease: ResumeLease,
) -> StepResult:
view = self.authority.build_release_ready(observation.mission_id)
if view.state is ReadyState.UNKNOWN:
status = StepStatus.RECONCILE
elif view.state is ReadyState.NOT_READY:
status = StepStatus.BLOCKED_DETERMINISTIC
else:
if not view.material_digest:
return StepResult(
StepStatus.RECONCILE,
"READY_WITHOUT_MATERIAL_DIGEST",
view.condition_fingerprint,
receipt_refs=view.receipt_refs,
)
if not view.receipt_refs:
return StepResult(
StepStatus.RECONCILE,
"READY_WITHOUT_RECEIPT",
view.condition_fingerprint,
)
status = StepStatus.SUCCEEDED
return StepResult(
status,
view.code,
view.condition_fingerprint,
receipt_refs=view.receipt_refs,
retry_after_seconds=view.retry_after_seconds,
next_phase=(
MissionPhase.PROMOTION
if status is StepStatus.SUCCEEDED else None
),
material_digest=view.material_digest,
)
def promotion(
self,
observation: MissionObservation,
checkpoint: MissionCheckpoint,
lease: ResumeLease,
) -> StepResult:
view = self.authority.run_promotion(observation.mission_id)
if view.state is PromotionState.UNKNOWN:
status = StepStatus.RECONCILE
elif view.state is PromotionState.IN_PROGRESS:
status = StepStatus.IN_PROGRESS
elif view.state is PromotionState.VERIFIED:
if not view.receipt_refs:
return StepResult(
StepStatus.RECONCILE,
"PROMOTION_VERIFIED_WITHOUT_RECEIPT",
view.condition_fingerprint,
material_digest=view.material_digest,
)
status = StepStatus.SUCCEEDED
elif view.state is PromotionState.ROLLED_BACK:
status = StepStatus.BLOCKED_DETERMINISTIC
else:
status = StepStatus.BLOCKED_DETERMINISTIC
return StepResult(
status,
view.code,
view.condition_fingerprint,
receipt_refs=view.receipt_refs,
retry_after_seconds=view.retry_after_seconds,
next_phase=(
MissionPhase.FINAL_ACCEPTANCE
if status is StepStatus.SUCCEEDED else None
),
material_digest=view.material_digest,
)
def final_acceptance(
self,
observation: MissionObservation,
checkpoint: MissionCheckpoint,
lease: ResumeLease,
) -> StepResult:
view = self.authority.run_final_acceptance(observation.mission_id)
if view.state is AcceptanceState.UNKNOWN:
status = StepStatus.RECONCILE
elif view.state is AcceptanceState.PASS:
if not view.receipt_refs:
return StepResult(
StepStatus.RECONCILE,
"FINAL_ACCEPTANCE_PASS_WITHOUT_RECEIPT",
view.condition_fingerprint,
)
status = StepStatus.SUCCEEDED
else:
status = StepStatus.BLOCKED_DETERMINISTIC
return StepResult(
status,
view.code,
view.condition_fingerprint,
receipt_refs=view.receipt_refs,
next_phase=(
MissionPhase.COMPLETE
if status is StepStatus.SUCCEEDED else None
),
)
@staticmethod
def _admission_receipt_error(
lane: TaskLaneSpec,
receipt: TaskAdmissionReceipt,
*,
observation: MissionObservation,
) -> str | None:
if receipt.task_id != lane.task_id:
return "TASK_IDENTITY_MISMATCH"
if receipt.target_branch != lane.target_branch:
return "TASK_BRANCH_MISMATCH"
if receipt.generation != observation.generation:
return "TASK_GENERATION_MISMATCH"
return None
@staticmethod
def _refs(*groups: Sequence[str]) -> tuple[str, ...]:
seen: set[str] = set()
values: list[str] = []
for group in groups:
for item in group:
if item and item not in seen:
seen.add(item)
values.append(item)
return tuple(values)
@staticmethod
def _idempotency(*parts: object) -> str:
return "aios:auto:" + _digest(
{"schema": "aios.unattended-idempotency.v1", "parts": list(parts)}
)
@staticmethod
def _phase_fingerprint(
phase: str,
observation: MissionObservation,
details: Mapping[str, object],
) -> str:
return _digest({
"schema": "aios.unattended-phase-condition.v1",
"phase": phase,
"mission_id": observation.mission_id,
"generation": observation.generation,
"head_oid": observation.head_oid,
"material_digest": observation.material_digest,
"details": details,
})
@@ -0,0 +1,877 @@
"""Deterministic unattended mission-resume controller for AIOS.
The controller is deliberately small and does not create a second scheduler,
database, identity system, or release control plane.
It executes exactly one reconciliation/progress cycle per ``run_once`` call.
An existing AIOS scheduler/timer/worker loop should invoke it periodically.
All consequential effects are delegated through ``AutonomousExecutionPort`` and
must use the existing AIOS authorities: durable Owner authorization, task
admission, claim/TaskLease/TaskPrincipal, WriterLease/CAS/fencing, Gate0,
ReleaseReady, PromotionController and governed rollback.
The controller's responsibilities are limited to:
- one-winner resume lease acquisition;
- durable checkpoint progression;
- bounded retry/backoff;
- unchanged deterministic blocker suppression;
- UNKNOWN/ambiguous-effect reconciliation before retry;
- non-interactive owner-boundary recording;
- crash-safe phase resumption.
It never manufactures credentials, group membership, principals, leases,
signatures, branch OIDs, or release receipts.
"""
from __future__ import annotations
from dataclasses import asdict, dataclass, field, replace
from enum import Enum
import hashlib
import json
import time
from typing import Any, Callable, Mapping, Protocol, Sequence
class MissionPhase(str, Enum):
S2_ADMISSION = "S2_ADMISSION"
S2_EXECUTION = "S2_EXECUTION"
GATE0 = "GATE0"
RELEASE_READY = "RELEASE_READY"
PROMOTION = "PROMOTION"
FINAL_ACCEPTANCE = "FINAL_ACCEPTANCE"
OWNER_ACTION_REQUIRED = "OWNER_ACTION_REQUIRED"
COMPLETE = "COMPLETE"
class EffectState(str, Enum):
ABSENT = "ABSENT"
ACTIVE = "ACTIVE"
COMPLETED = "COMPLETED"
UNKNOWN = "UNKNOWN"
class StepStatus(str, Enum):
SUCCEEDED = "SUCCEEDED"
IN_PROGRESS = "IN_PROGRESS"
RETRYABLE = "RETRYABLE"
RECONCILE = "RECONCILE"
BLOCKED_DETERMINISTIC = "BLOCKED_DETERMINISTIC"
BLOCKED_OWNER = "BLOCKED_OWNER"
class OutcomeStatus(str, Enum):
COMPLETE = "COMPLETE"
PROGRESSED = "PROGRESSED"
IN_PROGRESS = "IN_PROGRESS"
RETRY_SCHEDULED = "RETRY_SCHEDULED"
WAIT_CONDITION_CHANGE = "WAIT_CONDITION_CHANGE"
ACTIVE_ELSEWHERE = "ACTIVE_ELSEWHERE"
OWNER_ACTION_REQUIRED = "OWNER_ACTION_REQUIRED"
_PHASE_NEXT: dict[MissionPhase, MissionPhase] = {
MissionPhase.S2_ADMISSION: MissionPhase.S2_EXECUTION,
MissionPhase.S2_EXECUTION: MissionPhase.GATE0,
MissionPhase.GATE0: MissionPhase.RELEASE_READY,
MissionPhase.RELEASE_READY: MissionPhase.PROMOTION,
MissionPhase.PROMOTION: MissionPhase.FINAL_ACCEPTANCE,
MissionPhase.FINAL_ACCEPTANCE: MissionPhase.COMPLETE,
}
def _canonical(value: Any) -> bytes:
return json.dumps(
value,
sort_keys=True,
separators=(",", ":"),
ensure_ascii=False,
allow_nan=False,
).encode("utf-8")
def _sha256(value: bytes) -> str:
return hashlib.sha256(value).hexdigest()
@dataclass(frozen=True)
class ResumePolicy:
resume_lease_seconds: int = 120
default_poll_seconds: int = 30
deterministic_block_poll_seconds: int = 300
transient_max_attempts: int = 3
transient_base_delay_seconds: int = 2
transient_max_delay_seconds: int = 60
def __post_init__(self) -> None:
for name in (
"resume_lease_seconds",
"default_poll_seconds",
"deterministic_block_poll_seconds",
"transient_max_attempts",
"transient_base_delay_seconds",
"transient_max_delay_seconds",
):
if getattr(self, name) < 1:
raise ValueError(f"{name}_must_be_positive")
if self.transient_max_delay_seconds < self.transient_base_delay_seconds:
raise ValueError("transient_max_delay_seconds_too_small")
@dataclass(frozen=True)
class MissionObservation:
mission_id: str
generation: int
head_oid: str
material_digest: str
condition_fingerprint: str
effect_state: EffectState = EffectState.ABSENT
requires_reconcile: bool = False
owner_action_resolved: bool = False
evidence_refs: tuple[str, ...] = ()
def __post_init__(self) -> None:
if not self.mission_id:
raise ValueError("mission_id_required")
if self.generation < 1:
raise ValueError("generation_must_be_positive")
if not self.head_oid:
raise ValueError("head_oid_required")
if not self.material_digest:
raise ValueError("material_digest_required")
if not self.condition_fingerprint:
raise ValueError("condition_fingerprint_required")
@dataclass(frozen=True)
class ResumeLease:
lease_id: str
mission_id: str
generation: int
fencing_token: str
expires_at: float
def __post_init__(self) -> None:
if not self.lease_id:
raise ValueError("lease_id_required")
if not self.mission_id:
raise ValueError("mission_id_required")
if self.generation < 1:
raise ValueError("generation_must_be_positive")
if not self.fencing_token:
raise ValueError("fencing_token_required")
@dataclass(frozen=True)
class StepResult:
status: StepStatus
code: str
condition_fingerprint: str
receipt_refs: tuple[str, ...] = ()
retry_after_seconds: int | None = None
next_phase: MissionPhase | None = None
material_digest: str | None = None
owner_action_payload: Mapping[str, Any] | None = None
def __post_init__(self) -> None:
if not self.code:
raise ValueError("step_code_required")
if not self.condition_fingerprint:
raise ValueError("condition_fingerprint_required")
if self.retry_after_seconds is not None and self.retry_after_seconds < 0:
raise ValueError("retry_after_seconds_must_be_non_negative")
@dataclass(frozen=True)
class MissionCheckpoint:
schema: str
mission_id: str
generation: int
phase: MissionPhase
head_oid: str
material_digest: str
last_condition_fingerprint: str
attempt_count: int
blocked_condition_fingerprint: str | None
reconcile_required: bool
frozen_material_digest: str | None
resume_phase_after_owner: MissionPhase | None
receipt_refs: tuple[str, ...]
updated_at: float
revision: int = 1
@classmethod
def initial(
cls,
observation: MissionObservation,
*,
observed_at: float,
) -> "MissionCheckpoint":
return cls(
schema="aios.autonomous-resume-checkpoint.v1",
mission_id=observation.mission_id,
generation=observation.generation,
phase=MissionPhase.S2_ADMISSION,
head_oid=observation.head_oid,
material_digest=observation.material_digest,
last_condition_fingerprint=observation.condition_fingerprint,
attempt_count=0,
blocked_condition_fingerprint=None,
reconcile_required=False,
frozen_material_digest=None,
resume_phase_after_owner=None,
receipt_refs=tuple(observation.evidence_refs),
updated_at=observed_at,
revision=1,
)
def __post_init__(self) -> None:
if self.schema != "aios.autonomous-resume-checkpoint.v1":
raise ValueError("unsupported_checkpoint_schema")
if not self.mission_id:
raise ValueError("mission_id_required")
if self.generation < 1:
raise ValueError("generation_must_be_positive")
if self.attempt_count < 0:
raise ValueError("attempt_count_must_be_non_negative")
if self.revision < 1:
raise ValueError("revision_must_be_positive")
@property
def digest(self) -> str:
payload = asdict(self)
payload["phase"] = self.phase.value
payload["resume_phase_after_owner"] = (
None
if self.resume_phase_after_owner is None
else self.resume_phase_after_owner.value
)
payload["receipt_refs"] = list(self.receipt_refs)
return _sha256(_canonical(payload))
@dataclass(frozen=True)
class ControllerOutcome:
status: OutcomeStatus
mission_id: str
phase: MissionPhase
code: str
next_wake_after_seconds: int | None
checkpoint_digest: str | None
receipt_refs: tuple[str, ...] = ()
class AutonomousExecutionPort(Protocol):
"""Adapter boundary to existing AIOS authorities and executors."""
def read_observation(self, mission_id: str) -> MissionObservation: ...
def load_checkpoint(self, mission_id: str) -> MissionCheckpoint | None: ...
def acquire_resume_lease(
self,
*,
mission_id: str,
generation: int,
idempotency_key: str,
lease_seconds: int,
) -> ResumeLease | None: ...
def step(
self,
*,
phase: MissionPhase,
observation: MissionObservation,
checkpoint: MissionCheckpoint,
lease: ResumeLease,
) -> StepResult: ...
def reconcile(
self,
*,
observation: MissionObservation,
checkpoint: MissionCheckpoint,
lease: ResumeLease,
) -> StepResult: ...
def persist_checkpoint(
self,
checkpoint: MissionCheckpoint,
*,
lease: ResumeLease,
) -> None: ...
def persist_owner_action_required(
self,
payload: Mapping[str, Any],
*,
observation: MissionObservation,
checkpoint: MissionCheckpoint,
lease: ResumeLease,
) -> str: ...
def release_resume_lease(
self,
lease: ResumeLease,
*,
outcome_code: str,
) -> None: ...
class AutonomousResumeController:
"""One-cycle deterministic unattended mission controller."""
def __init__(
self,
port: AutonomousExecutionPort,
*,
policy: ResumePolicy | None = None,
clock: Callable[[], float] | None = None,
) -> None:
self.port = port
self.policy = policy or ResumePolicy()
self.clock = clock or time.time
def run_once(self, mission_id: str) -> ControllerOutcome:
if not mission_id:
raise ValueError("mission_id_required")
now = float(self.clock())
observation = self.port.read_observation(mission_id)
checkpoint = self.port.load_checkpoint(mission_id)
if checkpoint is None:
checkpoint = MissionCheckpoint.initial(observation, observed_at=now)
forced_reconcile = False
else:
checkpoint, forced_reconcile = self._validate_and_refresh_checkpoint(
checkpoint,
observation,
now=now,
)
if checkpoint.phase is MissionPhase.COMPLETE:
return self._outcome(
OutcomeStatus.COMPLETE,
checkpoint,
"MISSION_ALREADY_COMPLETE",
next_wake=None,
)
if checkpoint.phase is MissionPhase.OWNER_ACTION_REQUIRED:
if not observation.owner_action_resolved:
return self._outcome(
OutcomeStatus.OWNER_ACTION_REQUIRED,
checkpoint,
"OWNER_ACTION_STILL_REQUIRED",
next_wake=None,
)
resume_phase = (
checkpoint.resume_phase_after_owner
or MissionPhase.S2_ADMISSION
)
checkpoint = replace(
checkpoint,
phase=resume_phase,
resume_phase_after_owner=None,
attempt_count=0,
blocked_condition_fingerprint=None,
reconcile_required=False,
updated_at=now,
revision=checkpoint.revision + 1,
)
if (
checkpoint.blocked_condition_fingerprint is not None
and checkpoint.blocked_condition_fingerprint
== observation.condition_fingerprint
):
return self._outcome(
OutcomeStatus.WAIT_CONDITION_CHANGE,
checkpoint,
"UNCHANGED_DETERMINISTIC_BLOCKER",
next_wake=self.policy.deterministic_block_poll_seconds,
)
if (
checkpoint.blocked_condition_fingerprint is not None
and checkpoint.blocked_condition_fingerprint
!= observation.condition_fingerprint
):
checkpoint = replace(
checkpoint,
blocked_condition_fingerprint=None,
attempt_count=0,
updated_at=now,
revision=checkpoint.revision + 1,
)
lease = self.port.acquire_resume_lease(
mission_id=observation.mission_id,
generation=observation.generation,
idempotency_key=self._resume_idempotency_key(observation),
lease_seconds=self.policy.resume_lease_seconds,
)
if lease is None:
return self._outcome(
OutcomeStatus.ACTIVE_ELSEWHERE,
checkpoint,
"RESUME_LEASE_HELD_ELSEWHERE",
next_wake=self.policy.default_poll_seconds,
)
try:
self._validate_resume_lease(
lease,
observation=observation,
now=now,
)
except Exception:
self.port.release_resume_lease(
lease,
outcome_code="INVALID_RESUME_LEASE",
)
raise
outcome_code = "UNSET"
try:
reconciling = (
forced_reconcile
or checkpoint.reconcile_required
or observation.requires_reconcile
or observation.effect_state is EffectState.UNKNOWN
)
if reconciling:
result = self.port.reconcile(
observation=observation,
checkpoint=checkpoint,
lease=lease,
)
else:
result = self.port.step(
phase=checkpoint.phase,
observation=observation,
checkpoint=checkpoint,
lease=lease,
)
outcome, new_checkpoint = self._apply_result(
result=result,
observation=observation,
checkpoint=checkpoint,
now=now,
lease=lease,
reconciling=reconciling,
)
outcome_code = outcome.code
self.port.persist_checkpoint(new_checkpoint, lease=lease)
return replace(
outcome,
checkpoint_digest=new_checkpoint.digest,
)
finally:
self.port.release_resume_lease(
lease,
outcome_code=outcome_code,
)
def _validate_and_refresh_checkpoint(
self,
checkpoint: MissionCheckpoint,
observation: MissionObservation,
*,
now: float,
) -> tuple[MissionCheckpoint, bool]:
if checkpoint.mission_id != observation.mission_id:
raise ValueError("checkpoint_mission_mismatch")
if checkpoint.generation > observation.generation:
raise ValueError("checkpoint_generation_ahead_of_authority")
refreshed = checkpoint
forced_reconcile = False
if checkpoint.generation < observation.generation:
refreshed = replace(
checkpoint,
generation=observation.generation,
phase=MissionPhase.S2_ADMISSION,
head_oid=observation.head_oid,
material_digest=observation.material_digest,
last_condition_fingerprint=observation.condition_fingerprint,
attempt_count=0,
blocked_condition_fingerprint=None,
reconcile_required=False,
frozen_material_digest=None,
resume_phase_after_owner=None,
receipt_refs=self._merge_refs(
checkpoint.receipt_refs,
observation.evidence_refs,
),
updated_at=now,
revision=checkpoint.revision + 1,
)
if (
refreshed.phase in {
MissionPhase.RELEASE_READY,
MissionPhase.PROMOTION,
MissionPhase.FINAL_ACCEPTANCE,
}
and refreshed.frozen_material_digest is not None
and refreshed.frozen_material_digest
!= observation.material_digest
):
# Material changed after READY. Promotion/final may already have
# observable side effects, so reconcile before rebuilding PREPARE.
previous_phase = refreshed.phase
force_for_effect = (
previous_phase in {
MissionPhase.PROMOTION,
MissionPhase.FINAL_ACCEPTANCE,
}
and observation.effect_state in {
EffectState.ACTIVE,
EffectState.UNKNOWN,
}
)
refreshed = replace(
refreshed,
phase=MissionPhase.RELEASE_READY,
material_digest=observation.material_digest,
head_oid=observation.head_oid,
frozen_material_digest=None,
attempt_count=0,
blocked_condition_fingerprint=None,
reconcile_required=force_for_effect,
last_condition_fingerprint=observation.condition_fingerprint,
receipt_refs=self._merge_refs(
refreshed.receipt_refs,
observation.evidence_refs,
),
updated_at=now,
revision=refreshed.revision + 1,
)
forced_reconcile = forced_reconcile or force_for_effect
elif (
refreshed.phase in {
MissionPhase.S2_ADMISSION,
MissionPhase.S2_EXECUTION,
MissionPhase.GATE0,
}
and refreshed.head_oid != observation.head_oid
):
# Source moved before READY. Re-enter S2 admission/replan.
refreshed = replace(
refreshed,
phase=MissionPhase.S2_ADMISSION,
head_oid=observation.head_oid,
material_digest=observation.material_digest,
frozen_material_digest=None,
attempt_count=0,
blocked_condition_fingerprint=None,
reconcile_required=False,
last_condition_fingerprint=observation.condition_fingerprint,
receipt_refs=self._merge_refs(
refreshed.receipt_refs,
observation.evidence_refs,
),
updated_at=now,
revision=refreshed.revision + 1,
)
return refreshed, forced_reconcile
def _apply_result(
self,
*,
result: StepResult,
observation: MissionObservation,
checkpoint: MissionCheckpoint,
now: float,
lease: ResumeLease,
reconciling: bool,
) -> tuple[ControllerOutcome, MissionCheckpoint]:
refs = self._merge_refs(
checkpoint.receipt_refs,
observation.evidence_refs,
result.receipt_refs,
)
base = replace(
checkpoint,
head_oid=observation.head_oid,
material_digest=(
result.material_digest
or observation.material_digest
),
last_condition_fingerprint=result.condition_fingerprint,
receipt_refs=refs,
updated_at=now,
revision=checkpoint.revision + 1,
)
if (
result.status is StepStatus.SUCCEEDED
and checkpoint.phase in {
MissionPhase.PROMOTION,
MissionPhase.FINAL_ACCEPTANCE,
}
and checkpoint.frozen_material_digest is not None
and result.material_digest is not None
and result.material_digest != checkpoint.frozen_material_digest
):
new_checkpoint = replace(
base,
phase=MissionPhase.RELEASE_READY,
material_digest=result.material_digest,
frozen_material_digest=None,
reconcile_required=True,
attempt_count=0,
blocked_condition_fingerprint=None,
)
return (
self._outcome(
OutcomeStatus.IN_PROGRESS,
new_checkpoint,
"RECONCILE_REQUIRED:MATERIAL_CHANGED_DURING_PROMOTION",
next_wake=0,
refs=result.receipt_refs,
),
new_checkpoint,
)
if result.status is StepStatus.SUCCEEDED:
next_phase = (
result.next_phase
or _PHASE_NEXT.get(checkpoint.phase, checkpoint.phase)
)
frozen = base.frozen_material_digest
if checkpoint.phase is MissionPhase.RELEASE_READY:
frozen = result.material_digest or observation.material_digest
new_checkpoint = replace(
base,
phase=next_phase,
attempt_count=0,
blocked_condition_fingerprint=None,
reconcile_required=False,
frozen_material_digest=frozen,
)
status = (
OutcomeStatus.COMPLETE
if next_phase is MissionPhase.COMPLETE
else OutcomeStatus.PROGRESSED
)
code = (
"MISSION_COMPLETE"
if next_phase is MissionPhase.COMPLETE
else result.code
)
return (
self._outcome(
status,
new_checkpoint,
code,
next_wake=(
None
if status is OutcomeStatus.COMPLETE
else self.policy.default_poll_seconds
),
refs=result.receipt_refs,
),
new_checkpoint,
)
if result.status is StepStatus.IN_PROGRESS:
new_checkpoint = replace(
base,
attempt_count=0,
blocked_condition_fingerprint=None,
reconcile_required=reconciling,
)
return (
self._outcome(
OutcomeStatus.IN_PROGRESS,
new_checkpoint,
result.code,
next_wake=(
result.retry_after_seconds
if result.retry_after_seconds is not None
else self.policy.default_poll_seconds
),
refs=result.receipt_refs,
),
new_checkpoint,
)
if result.status is StepStatus.RETRYABLE:
attempts = checkpoint.attempt_count + 1
if attempts > self.policy.transient_max_attempts:
new_checkpoint = replace(
base,
attempt_count=attempts,
blocked_condition_fingerprint=result.condition_fingerprint,
reconcile_required=reconciling,
)
return (
self._outcome(
OutcomeStatus.WAIT_CONDITION_CHANGE,
new_checkpoint,
"RETRY_BUDGET_EXHAUSTED",
next_wake=self.policy.deterministic_block_poll_seconds,
refs=result.receipt_refs,
),
new_checkpoint,
)
delay = (
result.retry_after_seconds
if result.retry_after_seconds is not None
else min(
self.policy.transient_base_delay_seconds
* (2 ** (attempts - 1)),
self.policy.transient_max_delay_seconds,
)
)
new_checkpoint = replace(
base,
attempt_count=attempts,
blocked_condition_fingerprint=None,
reconcile_required=reconciling,
)
return (
self._outcome(
OutcomeStatus.RETRY_SCHEDULED,
new_checkpoint,
result.code,
next_wake=delay,
refs=result.receipt_refs,
),
new_checkpoint,
)
if result.status is StepStatus.BLOCKED_DETERMINISTIC:
new_checkpoint = replace(
base,
attempt_count=checkpoint.attempt_count,
blocked_condition_fingerprint=result.condition_fingerprint,
reconcile_required=reconciling,
)
return (
self._outcome(
OutcomeStatus.WAIT_CONDITION_CHANGE,
new_checkpoint,
result.code,
next_wake=self.policy.deterministic_block_poll_seconds,
refs=result.receipt_refs,
),
new_checkpoint,
)
if result.status is StepStatus.BLOCKED_OWNER:
payload = dict(result.owner_action_payload or {})
payload.setdefault("mission_id", observation.mission_id)
payload.setdefault("generation", observation.generation)
payload.setdefault("phase", checkpoint.phase.value)
payload.setdefault("code", result.code)
record_ref = self.port.persist_owner_action_required(
payload,
observation=observation,
checkpoint=checkpoint,
lease=lease,
)
if not record_ref:
raise ValueError("owner_action_record_ref_required")
new_checkpoint = replace(
base,
phase=MissionPhase.OWNER_ACTION_REQUIRED,
resume_phase_after_owner=checkpoint.phase,
attempt_count=0,
blocked_condition_fingerprint=None,
reconcile_required=False,
receipt_refs=self._merge_refs(refs, (record_ref,)),
)
return (
self._outcome(
OutcomeStatus.OWNER_ACTION_REQUIRED,
new_checkpoint,
result.code,
next_wake=None,
refs=(record_ref,),
),
new_checkpoint,
)
if result.status is StepStatus.RECONCILE:
# The next scheduler cycle must reconcile before any retry.
new_checkpoint = replace(
base,
attempt_count=0,
blocked_condition_fingerprint=None,
reconcile_required=True,
)
return (
self._outcome(
OutcomeStatus.IN_PROGRESS,
new_checkpoint,
"RECONCILE_REQUIRED:" + result.code,
next_wake=0,
refs=result.receipt_refs,
),
new_checkpoint,
)
raise AssertionError(f"unsupported_step_status:{result.status}")
@staticmethod
def _validate_resume_lease(
lease: ResumeLease,
*,
observation: MissionObservation,
now: float,
) -> None:
if lease.mission_id != observation.mission_id:
raise ValueError("resume_lease_mission_mismatch")
if lease.generation != observation.generation:
raise ValueError("resume_lease_generation_mismatch")
if lease.expires_at <= now:
raise ValueError("resume_lease_expired")
def _resume_idempotency_key(
self,
observation: MissionObservation,
) -> str:
payload = {
"schema": "aios.resume-effect.v1",
"mission_id": observation.mission_id,
"generation": observation.generation,
}
return "resume:sha256:" + _sha256(_canonical(payload))
@staticmethod
def _merge_refs(*groups: Sequence[str]) -> tuple[str, ...]:
merged: list[str] = []
seen: set[str] = set()
for group in groups:
for item in group:
value = str(item)
if value and value not in seen:
seen.add(value)
merged.append(value)
return tuple(merged)
@staticmethod
def _outcome(
status: OutcomeStatus,
checkpoint: MissionCheckpoint,
code: str,
*,
next_wake: int | None,
refs: Sequence[str] = (),
) -> ControllerOutcome:
return ControllerOutcome(
status=status,
mission_id=checkpoint.mission_id,
phase=checkpoint.phase,
code=code,
next_wake_after_seconds=next_wake,
checkpoint_digest=None,
receipt_refs=tuple(refs),
)
@@ -0,0 +1,209 @@
"""Thin callable adapter for :mod:`autonomous_resume_controller`.
This module contains no transport, daemon, database or scheduler loop. It only
binds current AIOS runtime functions to the stable ``AutonomousExecutionPort``
contract and exposes one scheduler tick.
A field integrator should:
- resolve the live current APIs;
- supply callables that use existing governed authorities;
- register ``AutonomousSchedulerTick.run`` with the existing scheduler/timer.
No additional control plane is created here.
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Callable
from .autonomous_resume_controller import (
AutonomousExecutionPort,
AutonomousResumeController,
ControllerOutcome,
MissionCheckpoint,
MissionObservation,
MissionPhase,
ResumeLease,
StepResult,
)
PhaseHandler = Callable[
[MissionObservation, MissionCheckpoint, ResumeLease],
StepResult,
]
ReconcileHandler = PhaseHandler
@dataclass(frozen=True)
class PhaseCallbacks:
admission: PhaseHandler
source_execution: PhaseHandler
gate0: PhaseHandler
release_ready: PhaseHandler
promotion: PhaseHandler
final_acceptance: PhaseHandler
def for_phase(self, phase: MissionPhase) -> PhaseHandler:
mapping = {
MissionPhase.S2_ADMISSION: self.admission,
MissionPhase.S2_EXECUTION: self.source_execution,
MissionPhase.GATE0: self.gate0,
MissionPhase.RELEASE_READY: self.release_ready,
MissionPhase.PROMOTION: self.promotion,
MissionPhase.FINAL_ACCEPTANCE: self.final_acceptance,
}
try:
return mapping[phase]
except KeyError as exc:
raise ValueError(f"phase_has_no_executor:{phase.value}") from exc
@dataclass(frozen=True)
class AuthorityCallbacks:
read_observation: Callable[[str], MissionObservation]
load_checkpoint: Callable[[str], MissionCheckpoint | None]
acquire_resume_lease: Callable[[str, int, str, int], ResumeLease | None]
persist_checkpoint: Callable[[MissionCheckpoint, ResumeLease], None]
persist_owner_action_required: Callable[
[dict, MissionObservation, MissionCheckpoint, ResumeLease],
str,
]
release_resume_lease: Callable[[ResumeLease, str], None]
reconcile: ReconcileHandler
class CallableAutonomousExecutionPort(AutonomousExecutionPort):
"""Runtime adapter composed from already-governed AIOS callables."""
def __init__(
self,
authority: AuthorityCallbacks,
phases: PhaseCallbacks,
) -> None:
self.authority = authority
self.phases = phases
def read_observation(self, mission_id: str) -> MissionObservation:
value = self.authority.read_observation(mission_id)
if not isinstance(value, MissionObservation):
raise TypeError("read_observation_must_return_MissionObservation")
return value
def load_checkpoint(self, mission_id: str) -> MissionCheckpoint | None:
value = self.authority.load_checkpoint(mission_id)
if value is not None and not isinstance(value, MissionCheckpoint):
raise TypeError("load_checkpoint_must_return_checkpoint_or_none")
return value
def acquire_resume_lease(
self,
*,
mission_id: str,
generation: int,
idempotency_key: str,
lease_seconds: int,
) -> ResumeLease | None:
value = self.authority.acquire_resume_lease(
mission_id,
generation,
idempotency_key,
lease_seconds,
)
if value is not None and not isinstance(value, ResumeLease):
raise TypeError("acquire_resume_lease_must_return_lease_or_none")
return value
def step(
self,
*,
phase: MissionPhase,
observation: MissionObservation,
checkpoint: MissionCheckpoint,
lease: ResumeLease,
) -> StepResult:
handler = self.phases.for_phase(phase)
value = handler(observation, checkpoint, lease)
if not isinstance(value, StepResult):
raise TypeError("phase_handler_must_return_StepResult")
return value
def reconcile(
self,
*,
observation: MissionObservation,
checkpoint: MissionCheckpoint,
lease: ResumeLease,
) -> StepResult:
value = self.authority.reconcile(
observation,
checkpoint,
lease,
)
if not isinstance(value, StepResult):
raise TypeError("reconcile_must_return_StepResult")
return value
def persist_checkpoint(
self,
checkpoint: MissionCheckpoint,
*,
lease: ResumeLease,
) -> None:
self.authority.persist_checkpoint(checkpoint, lease)
def persist_owner_action_required(
self,
payload,
*,
observation: MissionObservation,
checkpoint: MissionCheckpoint,
lease: ResumeLease,
) -> str:
value = self.authority.persist_owner_action_required(
dict(payload),
observation,
checkpoint,
lease,
)
if not isinstance(value, str) or not value:
raise ValueError("owner_action_record_ref_required")
return value
def release_resume_lease(
self,
lease: ResumeLease,
*,
outcome_code: str,
) -> None:
self.authority.release_resume_lease(lease, outcome_code)
@dataclass(frozen=True)
class SchedulerTickResult:
outcome: ControllerOutcome
next_wake_after_seconds: int | None
class AutonomousSchedulerTick:
"""One invocation for an existing scheduler/timer.
This object never sleeps or loops. The host scheduler owns cadence.
"""
def __init__(
self,
controller: AutonomousResumeController,
mission_id: str,
) -> None:
if not mission_id:
raise ValueError("mission_id_required")
self.controller = controller
self.mission_id = mission_id
def run(self) -> SchedulerTickResult:
outcome = self.controller.run_once(self.mission_id)
return SchedulerTickResult(
outcome=outcome,
next_wake_after_seconds=outcome.next_wake_after_seconds,
)
@@ -0,0 +1,462 @@
"""Explicit C6 composition in the existing TaskPlane host, default disabled.
All authorities are host-owned dependencies. No request can register an
executor, construct a worker identity, or turn an observation into authority.
"""
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime, timezone
import threading
from typing import Callable, Literal, Protocol
from aios_capability_contract import has_capabilities as formal_has_capabilities, normalize_required_capabilities
from .aro_readonly import AroReadonlyTick
from .authorization_planner import CapabilityNamespace, CapabilityRef, GitManifestRegistryReader, authorization_scope_digest
from .auto_claim import AutoClaimCoordinator, DiscoveryResult
from .errors import ForbiddenError
from .operation_readback import OperationReaders, collect_closure, collect_observation
from .operation_view import (ClosureObservation, OperationBinding, PreflightResult, ClosureResult,
binding_digest, derive_closure, derive_preflight, reconcile_effect, validate_digest, validate_oid)
from .schedulability import MemoryProofRepository, SchedulabilityService
from .schedulability_store_adapter import (TaskPlaneSchedulabilityAuthority,
TaskPlaneClaimReceiptV3, ScopedLeaseController, validate_task_plane_receipt)
@dataclass(frozen=True)
class AuthenticatedWorker:
worker_id: str
actor_identity: str
authority_receipt_ref: str
expires_at: datetime
def __post_init__(self):
if any(not isinstance(v, str) or not v.strip() for v in
(self.worker_id, self.actor_identity, self.authority_receipt_ref)):
raise ValueError("authenticated worker receipt required")
if not isinstance(self.expires_at, datetime) or self.expires_at.tzinfo is None:
raise ValueError("worker expiry must be timezone aware")
@dataclass(frozen=True)
class OperationCapabilityBinding:
operation_id: str
namespace: CapabilityNamespace
capability_id: str
registry_oid: str
manifest_id: str
manifest_revision: str
def __post_init__(self):
validate_oid(self.registry_oid)
if not isinstance(self.namespace, CapabilityNamespace):
raise ValueError("explicit capability namespace required")
if any(not isinstance(v, str) or not v.strip() for v in
(self.operation_id, self.capability_id, self.manifest_id, self.manifest_revision)):
raise ValueError("operation capability identity required")
@dataclass(frozen=True)
class DurableExecutionIntent:
binding_digest: str
effect_key: str
claim_id: str
receipt_ref: str
def __post_init__(self):
validate_digest(self.binding_digest)
if any(not isinstance(v, str) or not v.strip() for v in
(self.effect_key, self.claim_id, self.receipt_ref)):
raise ValueError("exact durable execution intent required")
class GovernedSourcePatchPilot(Protocol):
"""Existing governed writer/executor adapter, never a general shell port.
Implementation must revalidate Root, revocation, scope, generation and
WriterLease/CAS at write linearization, and persist intent before effect.
Readback uses the same immutable effect key through OperationReaders.
"""
def prepare_intent(self, *, binding: OperationBinding, claim: TaskPlaneClaimReceiptV3) -> DurableExecutionIntent: ...
def execute(self, *, binding: OperationBinding, claim: TaskPlaneClaimReceiptV3,
intent: DurableExecutionIntent) -> None: ...
def reconcile(self, *, binding: OperationBinding,
intent: DurableExecutionIntent) -> None: ...
@dataclass(frozen=True)
class C6Bindings:
claim_authority: TaskPlaneSchedulabilityAuthority
registry_reader: GitManifestRegistryReader
registry_oid: str
registry_digest: str
operation_capabilities: tuple[OperationCapabilityBinding, ...]
operation_readers: OperationReaders
worker_provider: Callable[[], AuthenticatedWorker | None]
operation_provider: Callable[[str, AuthenticatedWorker], OperationBinding]
pilot: GovernedSourcePatchPilot | None = None
aro_tick: AroReadonlyTick | None = None
enabled: bool = False
request_worker_provider: Callable[[str, str], AuthenticatedWorker | None] | None = None
# Readers are injected from the host's existing durable intent and worker
# authority. A provider error is UNKNOWN, never an absent record.
intent_reader: Callable[[OperationBinding], DurableExecutionIntent | None] | None = None
pending_intent_provider: Callable[
[], tuple[tuple[OperationBinding, DurableExecutionIntent], ...]
] | None = None
pending_intent_completion: Callable[
[OperationBinding, DurableExecutionIntent, ClosureObservation], None
] | None = None
worker_authority_verifier: Callable[[AuthenticatedWorker, OperationBinding], bool] | None = None
@dataclass(frozen=True)
class C6TickResult:
status: Literal["DISABLED", "NO_CANDIDATES", "CLAIMED", "OBSERVED", "BLOCKED", "UNKNOWN"]
reason_code: str
discovery: DiscoveryResult | None = None
claim: TaskPlaneClaimReceiptV3 | None = None
preflight: PreflightResult | None = None
closure: ClosureResult | None = None
audit_report_ref: str | None = None
evidence_refs: tuple[str, ...] = ()
class C6Runtime:
def __init__(self, service, bindings: C6Bindings, *, clock=None):
if not isinstance(bindings, C6Bindings) or type(bindings.enabled) is not bool:
raise ValueError("explicit C6 bindings required")
if not isinstance(bindings.claim_authority, TaskPlaneSchedulabilityAuthority) or bindings.claim_authority.store is not service.store:
raise ValueError("C6 must share the host's existing TaskPlane authority")
if not callable(bindings.worker_provider) or not callable(bindings.operation_provider):
raise ValueError("host authenticated worker and operation providers required")
if bindings.enabled and not service.policy.execution_enabled:
raise ValueError("execution policy and explicit trust must be enabled first")
if bindings.pilot is not None and not all(callable(getattr(bindings.pilot, method, None)) for method in ("prepare_intent", "execute", "reconcile")):
raise ValueError("governed source patch pilot required")
if bindings.enabled and bindings.pilot is not None and not all(
callable(value)
for value in (
bindings.intent_reader,
bindings.pending_intent_provider,
bindings.pending_intent_completion,
bindings.worker_authority_verifier,
)
):
raise ValueError(
"enabled C6 pilot requires intent, recovery, and worker authority providers"
)
validate_oid(bindings.registry_oid); validate_digest(bindings.registry_digest)
if not isinstance(bindings.operation_capabilities, tuple) or not bindings.operation_capabilities:
raise ValueError("immutable operation capability map required")
self.service, self.bindings = service, bindings
self.clock = clock or (lambda: datetime.now(timezone.utc))
self._lock = threading.Lock()
self._closed = False
self._pending: OperationBinding | None = None
self._validate_map()
self.schedulability = SchedulabilityService(bindings.claim_authority,
MemoryProofRepository(), clock=lambda: self.clock().timestamp(),
capability_matcher=bindings.claim_authority.capability_matcher)
self.coordinator = AutoClaimCoordinator(self.schedulability)
self.lease_controller = ScopedLeaseController(service.store, bindings.claim_authority.binding)
def _validate_map(self):
manifest = self.bindings.registry_reader.read_manifest(
expected_registry_oid=self.bindings.registry_oid,
expected_manifest_sha256=self.bindings.registry_digest)
seen = set()
for item in self.bindings.operation_capabilities:
key = (item.operation_id, item.namespace)
if key in seen or item.registry_oid != self.bindings.registry_oid or item.manifest_id != manifest.manifest_id or item.manifest_revision != manifest.revision:
raise ValueError("operation capability map revision conflict")
if CapabilityRef(item.namespace, item.capability_id) not in manifest.capabilities:
raise ValueError("operation capability not declared in authoritative manifest")
if item.namespace == CapabilityNamespace.AGENT_TASK and any(
value.startswith("exact:") for value in normalize_required_capabilities([item.capability_id])):
raise ValueError("unknown formal operation capability")
seen.add(key)
return manifest
def _worker(self, worker=None):
worker = self.bindings.worker_provider() if worker is None else worker
if not isinstance(worker, AuthenticatedWorker) or worker.expires_at <= self.clock():
raise ForbiddenError("fresh authenticated worker required")
registered = self.service.store.get_worker(worker.worker_id)
if registered.identity != worker.actor_identity or registered.disabled:
raise ForbiddenError("registered worker identity mismatch")
self.service.policy.assert_allowed("claim", worker.actor_identity)
return worker
def _prepare(self, task_id, worker):
manifest = self._validate_map()
binding = self.bindings.operation_provider(task_id, worker)
if not isinstance(binding, OperationBinding) or binding.task_id != task_id or binding.actor_id != worker.actor_identity or binding.source_oid != manifest.source_oid:
raise ForbiddenError("operation identity mismatch")
if binding.operation_id != "governed_source_patch_apply":
raise ForbiddenError("operation outside C6 pilot")
task = self.service.store.get_task(task_id)
metadata = task.task_plan.metadata
if (metadata.get("c6_operation_binding_digest") != binding_digest(binding)
or metadata.get("generation") != binding.generation
or binding.policy_digest != manifest.declaration_digest
or binding.scope_digest not in {authorization_scope_digest(scope) for scope in manifest.allowed_scopes}):
raise ForbiddenError("operation scope or immutable TaskPlan binding mismatch")
self.service.policy.assert_allowed("claim", worker.actor_identity, task=task)
required = task.task_plan.required_capabilities
if binding.capability_namespace == CapabilityNamespace.AGENT_TASK.value:
registered = self.service.store.get_worker(worker.worker_id)
if (not formal_has_capabilities(registered.capabilities, [binding.capability_id])
or not normalize_required_capabilities([binding.capability_id]).issubset(
normalize_required_capabilities(required))):
raise ForbiddenError("formal capability cannot use wildcard worker authority")
elif binding.capability_id not in required:
raise ForbiddenError("operation capability absent from TaskPlan")
verifier = self.bindings.worker_authority_verifier
if verifier is None or verifier(worker, binding) is not True:
raise ForbiddenError("worker receipt not bound to operation authority")
if not any(item.operation_id == binding.operation_id and item.namespace.value == binding.capability_namespace and item.capability_id == binding.capability_id for item in self.bindings.operation_capabilities):
raise ForbiddenError("operation capability namespace mismatch")
preflight = derive_preflight(collect_observation(binding, self.bindings.operation_readers, now=self.clock()), now=self.clock())
return binding, preflight
def _intent(self, binding):
reader = self.bindings.intent_reader
if reader is None:
raise ForbiddenError("durable execution intent reader required")
intent = reader(binding)
if intent is not None and (not isinstance(intent, DurableExecutionIntent)
or intent.binding_digest != binding_digest(binding)
or intent.effect_key != binding.effect_key):
raise ForbiddenError("durable execution intent binding mismatch")
return intent
def _durable_pending(self):
provider = self.bindings.pending_intent_provider
if provider is None:
raise ForbiddenError("durable pending intent provider required")
values = provider()
if not isinstance(values, tuple) or len(values) > 16:
raise ForbiddenError("bounded durable pending intent enumeration required")
result = []
seen = set()
for value in values:
if not isinstance(value, tuple) or len(value) != 2:
raise ForbiddenError("durable pending intent entry is invalid")
binding, intent = value
if (
not isinstance(binding, OperationBinding)
or not isinstance(intent, DurableExecutionIntent)
or intent.binding_digest != binding_digest(binding)
or intent.effect_key != binding.effect_key
or binding.effect_key in seen
):
raise ForbiddenError("durable pending intent binding mismatch")
seen.add(binding.effect_key)
result.append((binding, intent))
return tuple(result)
def _reconcile_pending(self, binding, intent, worker):
current, _preflight = self._prepare(binding.task_id, worker)
if current != binding or self._intent(binding) != intent:
raise ForbiddenError("durable pending authority changed")
assert self.bindings.pilot is not None
try:
self.bindings.pilot.reconcile(binding=binding, intent=intent)
except Exception:
pass
observation = collect_closure(
binding, self.bindings.operation_readers, now=self.clock()
)
return observation, derive_closure(binding, observation, now=self.clock())
def renew_claim(self, *, task_id, claim_id, actor_identity, receipt, lease_seconds):
if self._closed or not self.bindings.enabled or not isinstance(receipt, dict):
raise ForbiddenError("C6 v3 claim receipt required")
if receipt.get("task_id") != task_id or receipt.get("claim_id") != claim_id:
raise ForbiddenError("C6 claim receipt identity mismatch")
provider = self.bindings.request_worker_provider
worker_id = receipt.get("worker_id")
candidate = provider(worker_id, actor_identity) if provider is not None else None
if candidate is None:
raise ForbiddenError("authenticated request worker required")
worker = self._worker(candidate)
if (worker.worker_id, worker.actor_identity) != (worker_id, actor_identity):
raise ForbiddenError("request worker identity mismatch")
self._prepare(task_id, worker)
return self.lease_controller.renew(receipt, worker_id=worker_id,
actor_identity=actor_identity, lease_seconds=lease_seconds)
def claim_task(self, *, task_id: str, worker_id: str, actor_identity: str, lease_seconds: int = 30):
"""Manual and automatic host claims share this exact authority seam."""
if self._closed or not self.bindings.enabled or self.bindings.pilot is None:
raise ForbiddenError("C6 claim binding disabled or unavailable")
provider = self.bindings.request_worker_provider
if provider is None:
raise ForbiddenError("authenticated request worker provider required")
candidate = provider(worker_id, actor_identity)
if candidate is None:
raise ForbiddenError("authenticated request worker unavailable")
worker = self._worker(candidate)
if (worker.worker_id, worker.actor_identity) != (worker_id, actor_identity):
raise ForbiddenError("worker provider binding mismatch")
binding, preflight = self._prepare(task_id, worker)
return self._claim_prepared(binding, preflight, worker, lease_seconds=lease_seconds)
def _claim_prepared(self, binding, preflight, worker, *, lease_seconds=30):
task_id, worker_id, actor_identity = binding.task_id, worker.worker_id, worker.actor_identity
if preflight.state != "READY":
raise ForbiddenError("C6 preflight: " + preflight.reason_code)
if self._intent(binding) is not None:
raise ForbiddenError("existing execution intent requires readback")
closure = collect_closure(binding, self.bindings.operation_readers, now=self.clock())
if reconcile_effect(closure.effect, binding=binding, now=self.clock()) != "REVALIDATE_BEFORE_RETRY":
raise ForbiddenError("C6 effect must be authoritatively absent before claim")
proof = self.schedulability.preflight(task_id=task_id, worker_id=worker_id,
actor_identity=actor_identity, persist=True)
if proof.observation.scope_digest != binding.scope_digest:
raise ForbiddenError("claim authority scope differs from operation scope")
result = self.coordinator.claim(proof_id=proof.proof_id, worker_id=worker_id,
actor_identity=actor_identity, lease_seconds=lease_seconds)
parsed = validate_task_plane_receipt(result, proof=proof)
return parsed
def tick(self) -> C6TickResult:
if self._closed or not self.bindings.enabled:
return C6TickResult("DISABLED", "C6_DISABLED")
if not self._lock.acquire(blocking=False):
return C6TickResult("BLOCKED", "LOCAL_TICK_IN_PROGRESS")
try:
audit = self.bindings.aro_tick.run_due() if self.bindings.aro_tick else None
if self.bindings.pilot is None:
return C6TickResult("BLOCKED", "EXECUTION_ENTRYPOINT_ABSENT", audit_report_ref=audit)
if self.bindings.pending_intent_provider is None:
return C6TickResult(
"BLOCKED", "DURABLE_PENDING_INTENT_PROVIDER_ABSENT",
audit_report_ref=audit,
)
# Restart recovery is ledger-first. It does not depend on the Task
# remaining discoverable and it never calls execute/resubmits.
pending = self._durable_pending()
if pending:
worker = self._worker()
closures = []
for binding, intent in pending:
observation, closed = self._reconcile_pending(binding, intent, worker)
closures.append(closed)
if closed.outcome == "SUCCEEDED":
assert self.bindings.pending_intent_completion is not None
self.bindings.pending_intent_completion(
binding, intent, observation
)
all_succeeded = all(item.outcome == "SUCCEEDED" for item in closures)
return C6TickResult(
"OBSERVED" if all_succeeded else "UNKNOWN",
"DURABLE_PENDING_INTENTS_RECONCILED"
if all_succeeded
else "DURABLE_PENDING_INTENT_READBACK",
closure=closures[-1],
audit_report_ref=audit,
)
if self._pending is not None:
# ACK loss is sticky. Reconciliation is a distinct, pure
# owner seam: it may observe/reconcile the exact durable action
# but may never acquire a new WriterLease or resend the commit.
intent = self._intent(self._pending)
if intent is None:
return C6TickResult("UNKNOWN", "PENDING_INTENT_UNAVAILABLE",
audit_report_ref=audit)
worker = self._worker()
observation, closure = self._reconcile_pending(
self._pending, intent, worker
)
if closure.outcome == "SUCCEEDED":
assert self.bindings.pending_intent_completion is not None
self.bindings.pending_intent_completion(
self._pending, intent, observation
)
self._pending = None
return C6TickResult("OBSERVED" if closure.outcome == "SUCCEEDED" else "UNKNOWN",
"PENDING_EFFECT_READBACK", closure=closure, audit_report_ref=audit)
worker = self._worker()
discovered = self.coordinator.discover(worker_id=worker.worker_id, actor_identity=worker.actor_identity)
if discovered.proof is None:
return C6TickResult("NO_CANDIDATES" if discovered.candidate_count == 0 else "BLOCKED",
discovered.status, discovery=discovered, audit_report_ref=audit)
task_id = discovered.proof.task_id
binding, preflight = self._prepare(task_id, worker)
if self._intent(binding) is not None:
self._pending = binding
intent = self._intent(binding)
try:
if intent is not None:
self.bindings.pilot.reconcile(binding=binding, intent=intent)
except Exception:
pass
closed = derive_closure(binding, collect_closure(binding,
self.bindings.operation_readers, now=self.clock()), now=self.clock())
return C6TickResult("OBSERVED" if closed.outcome == "SUCCEEDED" else "UNKNOWN",
"DURABLE_INTENT_READBACK", discovered, closure=closed, audit_report_ref=audit)
if preflight.state != "READY":
return C6TickResult("BLOCKED", preflight.reason_code, discovered, preflight=preflight, audit_report_ref=audit)
claim = self._claim_prepared(binding, preflight, worker)
# Re-read every precondition immediately before the effect. The
# executor independently enforces its linearization-point fences.
binding_after, refreshed = self._prepare(task_id, worker)
self._worker(worker)
if binding_after != binding or refreshed.state != "READY" or self._closed:
return C6TickResult("BLOCKED", "PRE_EFFECT_BINDING_CHANGED", discovered, claim, refreshed)
try:
with self.lease_controller.execution_guard(claim.to_dict(),
worker_id=worker.worker_id, actor_identity=worker.actor_identity):
binding_after, refreshed = self._prepare(task_id, self._worker(worker))
effect = collect_closure(binding, self.bindings.operation_readers, now=self.clock()).effect
if (binding_after != binding or refreshed.state != "READY" or self._closed
or reconcile_effect(effect, binding=binding, now=self.clock()) != "REVALIDATE_BEFORE_RETRY"
or self._intent(binding) is not None):
return C6TickResult("BLOCKED", "PRE_EFFECT_AUTHORITY_CHANGED", discovered, claim, refreshed)
self._pending = binding
intent = self.bindings.pilot.prepare_intent(binding=binding, claim=claim)
observed_intent = self._intent(binding)
if (not isinstance(intent, DurableExecutionIntent) or intent != observed_intent
or intent.claim_id != claim.claim_id):
raise ForbiddenError("execution intent not durably observed")
# Intent persistence may take time: acquire a new guard and
# read authority/effect again before calling the governed writer.
with self.lease_controller.execution_guard(claim.to_dict(),
worker_id=worker.worker_id, actor_identity=worker.actor_identity):
after, ready = self._prepare(task_id, self._worker(worker))
effect = collect_closure(binding, self.bindings.operation_readers, now=self.clock()).effect
if (after != binding or ready.state != "READY" or self._closed
or self._intent(binding) != intent
or reconcile_effect(effect, binding=binding, now=self.clock()) != "REVALIDATE_BEFORE_RETRY"):
raise ForbiddenError("pre-effect authority changed during intent persistence")
self.bindings.pilot.execute(binding=binding, claim=claim, intent=intent)
except Exception:
return C6TickResult("UNKNOWN", "EXECUTION_OUTCOME_UNKNOWN", discovered, claim, refreshed)
observed = collect_closure(binding, self.bindings.operation_readers, now=self.clock())
closed = derive_closure(binding, observed, now=self.clock())
if closed.outcome == "SUCCEEDED":
assert self.bindings.pending_intent_completion is not None
self.bindings.pending_intent_completion(binding, intent, observed)
self._pending = None
return C6TickResult("OBSERVED" if closed.outcome == "SUCCEEDED" else "UNKNOWN",
"THREE_LAYER_CLOSURE" if closed.outcome == "SUCCEEDED" else "CLOSURE_INCOMPLETE",
discovered, claim, refreshed, closed, audit)
except Exception:
return C6TickResult("UNKNOWN", "AUTHORITY_OR_OBSERVATION_UNAVAILABLE")
finally:
self._lock.release()
def close(self):
# Local tick lifecycle only. Authority leases are never implicitly
# released or renewed by closing an observer/coordinator.
self._closed = True
def bind_c6(service, bindings: C6Bindings, *, clock=None) -> C6Runtime:
if getattr(service, "c6_runtime", None) is not None:
raise ValueError("C6 runtime already bound")
runtime = C6Runtime(service, bindings, clock=clock)
service.c6_runtime = runtime
return runtime
@@ -0,0 +1,121 @@
"""Pure six-value C6 decision explanation policy.
The result is advice over already-read facts. It is not an authorization,
does not map implicitly to legacy NextAction, and owns no write capability.
"""
from __future__ import annotations
from dataclasses import dataclass
from enum import Enum
from typing import Literal
RootState = Literal["VALID", "MISSING", "EXPIRED", "REVOKED", "UNKNOWN"]
class DecisionKind(str, Enum):
AUTO_FIX = "AUTO_FIX"
PLAN_ONLY = "PLAN_ONLY"
OWNER_APPROVAL_REQUIRED = "OWNER_APPROVAL_REQUIRED"
HARD_BLOCK = "HARD_BLOCK"
WAITING_ROOT_AUTHORIZATION = "WAITING_ROOT_AUTHORIZATION"
BLOCKED_WITH_EVIDENCE = "BLOCKED_WITH_EVIDENCE"
def _evidence_refs(value: object) -> tuple[str, ...]:
if not isinstance(value, tuple):
raise ValueError("evidence_refs must be a tuple")
for ref in value:
if not isinstance(ref, str) or not ref or ref != ref.strip() or "\x00" in ref:
raise ValueError("invalid evidence reference")
return value
@dataclass(frozen=True)
class DecisionInput:
root_state: RootState
facts_complete: bool
scope_covered: bool
policy_denied: bool
proposal_only: bool
executor_ready: bool
aro_phase_a: bool
evidence_refs: tuple[str, ...]
def __post_init__(self) -> None:
if self.root_state not in ("VALID", "MISSING", "EXPIRED", "REVOKED", "UNKNOWN"):
raise ValueError("invalid root_state")
for name in (
"facts_complete",
"scope_covered",
"policy_denied",
"proposal_only",
"executor_ready",
"aro_phase_a",
):
if type(getattr(self, name)) is not bool:
raise ValueError(f"{name} must be bool")
_evidence_refs(self.evidence_refs)
@dataclass(frozen=True)
class Decision:
kind: DecisionKind
reason: str
evidence_refs: tuple[str, ...]
def __post_init__(self) -> None:
if not isinstance(self.kind, DecisionKind):
raise ValueError("kind must be DecisionKind")
if not isinstance(self.reason, str) or not self.reason:
raise ValueError("reason must be non-empty")
_evidence_refs(self.evidence_refs)
def decide(value: DecisionInput) -> Decision:
if not isinstance(value, DecisionInput):
raise TypeError("value must be DecisionInput")
if value.policy_denied or value.root_state == "REVOKED":
return Decision(
DecisionKind.HARD_BLOCK,
"POLICY_DENIED_OR_ROOT_REVOKED",
value.evidence_refs,
)
if not value.facts_complete or value.root_state == "UNKNOWN":
return Decision(
DecisionKind.BLOCKED_WITH_EVIDENCE,
"CRITICAL_FACTS_INCOMPLETE",
value.evidence_refs,
)
if value.root_state in ("MISSING", "EXPIRED"):
return Decision(
DecisionKind.WAITING_ROOT_AUTHORIZATION,
"ROOT_AUTHORIZATION_MISSING_OR_EXPIRED",
value.evidence_refs,
)
if not value.scope_covered:
return Decision(
DecisionKind.OWNER_APPROVAL_REQUIRED,
"SCOPE_OR_BUDGET_REQUIRES_OWNER_DECISION",
value.evidence_refs,
)
if value.proposal_only or value.aro_phase_a:
return Decision(
DecisionKind.PLAN_ONLY,
"NON_EXECUTABLE_PLAN_OR_READONLY_ARO",
value.evidence_refs,
)
if not value.executor_ready:
return Decision(
DecisionKind.BLOCKED_WITH_EVIDENCE,
"GOVERNED_EXECUTOR_NOT_READY",
value.evidence_refs,
)
return Decision(
DecisionKind.AUTO_FIX,
"EXISTING_AUTHORIZED_SCOPE_ELIGIBLE_FOR_GOVERNED_EXECUTION",
value.evidence_refs,
)
__all__ = ["Decision", "DecisionInput", "DecisionKind", "decide"]
+39 -2
View File
@@ -5,6 +5,7 @@ from __future__ import annotations
import hashlib
import json
from .models import EvidenceEnvelope
from .store import TaskPlaneStore
@@ -17,8 +18,44 @@ class EvidenceService:
def __init__(self, store: TaskPlaneStore):
self.store = store
def add(self, task_id: str, kind: str, payload: dict, worker_identity: str) -> str:
return self.store.add_evidence(task_id, kind, payload, worker_identity)
def add(
self,
task_id: str,
kind: str,
payload: dict,
worker_identity: str,
*,
envelope: EvidenceEnvelope | None = None,
claim_id: str | None = None,
evidence_id: str | None = None,
created_at: float | None = None,
expires_at: float | None = None,
signature: str | None = None,
attestation_ref: str | None = None,
signature_algorithm: str | None = None,
signing_key_id: str | None = None,
verifier_identity: str | None = None,
authority_domain: str | None = None,
sequence: int | None = None,
) -> str:
return self.store.add_evidence(
task_id,
kind,
payload,
worker_identity,
envelope=envelope,
claim_id=claim_id,
evidence_id=evidence_id,
created_at=created_at,
expires_at=expires_at,
signature=signature,
attestation_ref=attestation_ref,
signature_algorithm=signature_algorithm,
signing_key_id=signing_key_id,
verifier_identity=verifier_identity,
authority_domain=authority_domain,
sequence=sequence,
)
def list(self, task_id: str) -> list[dict]:
return self.store.list_evidence(task_id)
@@ -0,0 +1,147 @@
"""Authenticated Task Plane evidence envelope adapter.
The adapter deliberately receives key material through an external resolver.
It does not create, persist, or select a production trust root.
"""
from __future__ import annotations
import hashlib
import hmac
import math
import re
import time
from dataclasses import replace
from typing import Callable
from .models import EvidenceEnvelope, canonical_json, compute_sha256_hex
HMAC_SHA256_V1 = "hmac-sha256-v1"
_SIGNATURE_DOMAIN = b"aios.task-plane.evidence-envelope.v1"
_HEX64 = re.compile(r"^[0-9a-f]{64}$")
def _signature_payload(envelope: EvidenceEnvelope) -> bytes:
return (
_SIGNATURE_DOMAIN
+ b"\0"
+ canonical_json(envelope.digest_payload()).encode("utf-8")
)
class HmacEvidenceVerifier:
"""Verify full evidence envelopes using an externally managed HMAC key."""
def __init__(
self,
*,
verifier_identity: str,
key_id: str,
key_resolver: Callable[[str], bytes],
max_age_seconds: int = 300,
now_fn: Callable[[], float] | None = None,
) -> None:
if not verifier_identity or not key_id:
raise ValueError("verifier_identity and key_id are required")
if not callable(key_resolver):
raise ValueError("key_resolver is required")
if (
isinstance(max_age_seconds, bool)
or not isinstance(max_age_seconds, int)
or max_age_seconds <= 0
):
raise ValueError("max_age_seconds must be a positive integer")
self.verifier_identity = verifier_identity
self.key_id = key_id
self._key_resolver = key_resolver
self.max_age_seconds = max_age_seconds
self._now_fn = now_fn or time.time
def __call__(self, envelope: EvidenceEnvelope) -> bool:
try:
now = float(self._now_fn())
finite_timestamps = all(
math.isfinite(value)
for value in (now, envelope.created_at, envelope.expires_at)
)
expected_digest = compute_sha256_hex(envelope.digest_payload())
except (TypeError, ValueError, OverflowError):
return False
if (
not finite_timestamps
or envelope.expires_at <= envelope.created_at
or envelope.signature_algorithm != HMAC_SHA256_V1
or envelope.signing_key_id != self.key_id
or envelope.verifier_identity != self.verifier_identity
or envelope.authority_domain != self.verifier_identity
or not isinstance(envelope.sequence, int)
or isinstance(envelope.sequence, bool)
or envelope.sequence < 1
or not isinstance(envelope.signature, str)
or not _HEX64.fullmatch(envelope.signature)
or envelope.created_at > now
or envelope.expires_at <= now
or envelope.expires_at - envelope.created_at > self.max_age_seconds
or envelope.evidence_digest != expected_digest
):
return False
try:
key = self._key_resolver(self.key_id)
except Exception:
return False
if not isinstance(key, (bytes, bytearray)) or not key:
return False
expected = hmac.new(
bytes(key), _signature_payload(envelope), hashlib.sha256
).hexdigest()
return hmac.compare_digest(envelope.signature, expected)
def sign_evidence_envelope(
envelope: EvidenceEnvelope,
*,
key_id: str,
key: bytes,
verifier_identity: str,
) -> EvidenceEnvelope:
"""Return a signed envelope for an already authorized producer.
Production code should obtain ``key`` from its existing credential provider;
this helper intentionally performs no key generation or storage.
"""
if not isinstance(key, (bytes, bytearray)) or not key:
raise ValueError("key is required")
if not key_id or not verifier_identity:
raise ValueError("key_id and verifier_identity are required")
if envelope.authority_domain != verifier_identity:
raise ValueError("envelope authority_domain must match verifier_identity")
if not math.isfinite(envelope.created_at) or not math.isfinite(
envelope.expires_at
):
raise ValueError("evidence timestamps must be finite")
if envelope.expires_at <= envelope.created_at:
raise ValueError("evidence expiry must follow creation")
if (
not isinstance(envelope.sequence, int)
or isinstance(envelope.sequence, bool)
or envelope.sequence < 1
):
raise ValueError("evidence sequence must be a positive integer")
unsigned = replace(
envelope,
signature=None,
signature_algorithm=HMAC_SHA256_V1,
signing_key_id=key_id,
verifier_identity=verifier_identity,
evidence_digest="",
)
unsigned = replace(
unsigned,
evidence_digest=compute_sha256_hex(unsigned.digest_payload()),
)
signature = hmac.new(
bytes(key), _signature_payload(unsigned), hashlib.sha256
).hexdigest()
return replace(unsigned, signature=signature)
@@ -0,0 +1,401 @@
"""现场(field)组合根:让"Owner 基于授权"真正可接线、可执行。
本模块是 V2 governed-writer 机制的**现场装配层**,职责:
- 实现正式的 `OwnerAuthorizationAuthority`:持有 Owner 登记的授权记录
(authorization_ref -> OwnerAuthorizationPolicy),`verify()` 解析引用、
核验策略、签发 verification receipt;未知引用 / 策略失效一律 fail-closed。
- 提供 `FieldGovernedWriterFactory`(组合根):把 7 个协议适配器(signer /
execution / principal_issuer / jobs / git / writer_lease / owner_authority)
装配到 `GovernedWriterAutomation.acquire_verified()` 生产入口上。
- 提供默认的生产适配器参考实现(`_DefaultOwnerAuthority`),供现场在真实
端点未就绪时先闭环;真实端点就绪后替换对应 adapter 即可,组合根接口不变。
设计原则(与 OWNER_AUTHORIZATION_MODEL.md 的 `aios.owner-authorization.v1` 一致):
1. 生产入口唯一授权来源是 Owner 登记的 `authorization_ref`;Agent 本地自造的
`OwnerAuthorizationPolicy` 不构成 proof(fail-closed)。
2. 长期业务授权(Policy)与短时执行凭证(epoch/lease/principal)分离。
3. `legacy_global_writes_enabled=false` 是安全不变式,不是 governed-writer blocker。
本模块不修改 S6 frozen 9 文件,不触碰 legacy/global 写;只做 governed 写路径的
现场装配。适配器接口全部复用 `governed_writer_automation.py` 的 Protocol。
"""
from __future__ import annotations
import time
from dataclasses import dataclass, field
from typing import Any, Mapping
from app.task_plane.governed_writer_automation import (
AuthorizationDenied,
EpochStatus,
GovernedWriterAdapters,
GovernedWriterAutomation,
GovernedWriterRequest,
GovernedWriterSession,
GovernedWriteReadiness,
JobBindingReceipt,
OwnerAuthorizationPolicy,
RecoveryAuthorization,
TaskLeaseReceipt,
TaskPrincipalReceipt,
TaskState,
VerifiedOwnerAuthorization,
WriterLeaseReceipt,
)
# ---------------------------------------------------------------------------
# 正式 OwnerAuthorizationAuthority 现场实现
# ---------------------------------------------------------------------------
@dataclass
class FieldOwnerAuthorizationAuthority:
"""现场 authority:登记 Owner 授权记录并按引用验证,签发核验回执。
生产语义:
- `register(ref, policy)` 由 Owner/管理员在受信侧登记;登记即"一次授权"。
- `verify(authorization_ref, request)` 解析引用 -> 核验 -> 返回
`VerifiedOwnerAuthorization`(含非空 receipt)。
- 任何未知引用、未登记、策略与请求越界,都抛 `AuthorizationDenied`
(fail-closed:不猜测、不降级)。
"""
_registry: dict[str, OwnerAuthorizationPolicy] = field(default_factory=dict)
_receipt_counter: int = 0
def register(self, authorization_ref: str, policy: OwnerAuthorizationPolicy) -> None:
if authorization_ref != policy.authorization_id:
raise AuthorizationDenied(
"authorization_ref must equal policy.authorization_id"
)
self._registry[authorization_ref] = policy
def contains(self, authorization_ref: str) -> bool:
return authorization_ref in self._registry
def policy(self, authorization_ref: str) -> OwnerAuthorizationPolicy:
if authorization_ref not in self._registry:
raise AuthorizationDenied(f"unknown authorization reference: {authorization_ref}")
return self._registry[authorization_ref]
def verify(
self,
*,
authorization_ref: str,
request: GovernedWriterRequest,
) -> VerifiedOwnerAuthorization:
if authorization_ref not in self._registry:
raise AuthorizationDenied(
f"owner authorization reference not registered: {authorization_ref}"
)
policy = self._registry[authorization_ref]
policy.validate(request, now=time.time())
self._receipt_counter += 1
receipt = f"receipt:owner-{authorization_ref}:{self._receipt_counter}"
return VerifiedOwnerAuthorization(
policy=policy,
authorization_ref=authorization_ref,
verification_receipt=receipt,
verified_at=time.time(),
)
# ---------------------------------------------------------------------------
# 默认生产适配器参考实现(真实端点未就绪时先闭环,接口与协议一一对应)
# ---------------------------------------------------------------------------
class _DefaultTrustedSigner:
"""TrustedSignerAdapter:JIT 重签同 scope 的 epoch(幂等由 ledger 守护)。"""
def __init__(self) -> None:
self._counter = 0
self.calls: list[dict] = []
def reissue_epoch(
self,
*,
owner_policy: OwnerAuthorizationPolicy,
request: GovernedWriterRequest,
prior_epoch: EpochStatus | None,
minimum_remaining_ttl: float,
idempotency_key: str,
) -> EpochStatus:
self._counter += 1
self.calls.append(
{
"key": idempotency_key,
"scope": request.scope_fingerprint,
"policy": owner_policy.policy_fingerprint,
"prior": None if prior_epoch is None else prior_epoch.epoch_id,
}
)
return EpochStatus(
epoch_id=f"epoch-gw-{self._counter}",
scope_fingerprint=request.scope_fingerprint,
expires_at=time.time() + 7200.0,
policy_digest=owner_policy.policy_fingerprint,
)
class _DefaultExecutionPlane:
"""ExecutionPlane:QUEUED 自动 claim + lease;heartbeat 记录。"""
def __init__(self, initial: TaskState) -> None:
self.task = initial
self._lease_counter = 0
self.claims: list = []
self.heartbeats: list = []
def read_task(self, task_id: str) -> TaskState:
if task_id != self.task.task_id:
raise AuthorizationDenied(f"unknown task: {task_id}")
return self.task
def apply_recovery(
self,
*,
task: TaskState,
authorization: RecoveryAuthorization,
idempotency_key: str,
) -> TaskState:
raise AuthorizationDenied("terminal recovery requires a real recovery endpoint")
def claim(
self,
*,
task: TaskState,
actor: str,
idempotency_key: str,
):
from app.task_plane.governed_writer_automation import ClaimReceipt
self.claims.append({"task": task.task_id, "actor": actor, "key": idempotency_key})
return ClaimReceipt(
claim_id=f"claim-gw-{len(self.claims)}",
task_id=task.task_id,
actor=actor,
fencing=len(self.claims),
)
def heartbeat(self, *, claim) -> None:
self.heartbeats.append(claim.claim_id)
def issue_task_lease(
self,
*,
claim,
idempotency_key: str,
) -> TaskLeaseReceipt:
self._lease_counter += 1
return TaskLeaseReceipt(
task_lease_id=f"tasklease-gw-{self._lease_counter}",
task_id=claim.task_id,
token_ref=f"token-gw-{self._lease_counter}",
expires_at=time.time() + 3600.0,
fencing=claim.fencing,
)
class _DefaultPrincipalIssuer:
"""TaskPrincipalIssuer:按 lease 派发 scoped TaskPrincipal。"""
def __init__(self) -> None:
self._counter = 0
def issue(
self,
*,
lease: TaskLeaseReceipt,
requested_capabilities: frozenset[str],
idempotency_key: str,
) -> TaskPrincipalReceipt:
self._counter += 1
return TaskPrincipalReceipt(
principal_id=f"principal-gw-{self._counter}",
task_id=lease.task_id,
capabilities=requested_capabilities,
expires_at=time.time() + 1800.0,
)
class _DefaultJobs:
"""JobAuthority:reconcile 到与请求一致的 job 绑定。"""
def __init__(self, *, repository: str, target_branch: str, generation: int) -> None:
self._repo = repository
self._branch = target_branch
self._gen = generation
self._counter = 0
def read_or_reconcile(
self,
*,
task_id: str,
principal: TaskPrincipalReceipt,
idempotency_key: str,
) -> JobBindingReceipt:
self._counter += 1
return JobBindingReceipt(
job_id=f"job-gw-{self._counter}",
task_id=task_id,
repository=self._repo,
target_branch=self._branch,
generation=self._gen,
)
class _DefaultGit:
"""GitAuthority:只读 branch 读回,决定 CAS 模式。"""
def __init__(self, *, exists: bool, head_oid: str | None = None) -> None:
from app.task_plane.governed_writer_automation import BranchReadback
self._exists = exists
self._head = head_oid
self.calls: list = []
def read_branch(self, *, repository: str, target_branch: str):
from app.task_plane.governed_writer_automation import BranchReadback
self.calls.append({"repo": repository, "branch": target_branch})
return BranchReadback(exists=self._exists, head_oid=self._head)
class _DefaultWriterLease:
"""WriterLeaseAuthority:签发短时 writer lease(CAS 与 base 一致)。"""
def __init__(self) -> None:
self._counter = 0
def acquire(
self,
*,
task_id: str,
job: JobBindingReceipt,
principal: TaskPrincipalReceipt,
repository: str,
target_branch: str,
branch_mode: str,
base_oid: str,
expected_branch_oid: str,
generation: int,
idempotency_key: str,
) -> WriterLeaseReceipt:
self._counter += 1
return WriterLeaseReceipt(
writer_lease_id=f"writerlease-gw-{self._counter}",
task_id=task_id,
job_id=job.job_id,
repository=repository,
target_branch=target_branch,
branch_mode=branch_mode,
expected_branch_oid=expected_branch_oid,
generation=generation,
expires_at=time.time() + 900.0,
fencing=self._counter,
)
# ---------------------------------------------------------------------------
# 组合根:装配 7 适配器 -> 生产入口
# ---------------------------------------------------------------------------
@dataclass
class FieldRuntimeStatus:
"""现场运行时状态:由 W-RUNTIME 只读线提供,用于派生 governed 写就绪。"""
legacy_global_writes_enabled: bool = False
task_principal_writes_enabled: bool = True
task_principal_verifier_ready: bool = True
governed_write_blockers: tuple[str, ...] = ()
@dataclass
class FieldGovernedWriterFactory:
"""组合根:把现场配置装配成 `acquire_verified()` 生产入口可直接执行的对象。
用法(现场接线):
factory = FieldGovernedWriterFactory.build(
authority=owner_authority,
manifest_store=...,
action_ledger=...,
task_initial=TaskState(...),
branch_exists=...,
head_oid=...,
repo=...,
branch=...,
generation=...,
runtime_status=...,
)
session = factory.acquire(request, authorization_ref)
"""
automation: GovernedWriterAutomation
adapters: GovernedWriterAdapters
readiness: GovernedWriteReadiness
owner_authority: FieldOwnerAuthorizationAuthority
@classmethod
def build(
cls,
*,
authority: FieldOwnerAuthorizationAuthority,
manifest_store,
action_ledger,
task_initial: TaskState,
branch_exists: bool,
head_oid: str | None,
repository: str,
target_branch: str,
generation: int,
runtime_status: FieldRuntimeStatus | None = None,
decision_engine=None,
) -> "FieldGovernedWriterFactory":
runtime_status = runtime_status or FieldRuntimeStatus()
adapters = GovernedWriterAdapters(
signer=_DefaultTrustedSigner(),
execution=_DefaultExecutionPlane(task_initial),
principal_issuer=_DefaultPrincipalIssuer(),
jobs=_DefaultJobs(
repository=repository, target_branch=target_branch, generation=generation
),
git=_DefaultGit(exists=branch_exists, head_oid=head_oid),
writer_lease=_DefaultWriterLease(),
owner_authority=authority,
)
automation = GovernedWriterAutomation(
manifest_store=manifest_store,
action_ledger=action_ledger,
decision_engine=decision_engine,
)
readiness = GovernedWriteReadiness(
legacy_global_writes_enabled=runtime_status.legacy_global_writes_enabled,
task_principal_writes_enabled=runtime_status.task_principal_writes_enabled,
task_principal_verifier_ready=runtime_status.task_principal_verifier_ready,
governed_write_blockers=tuple(runtime_status.governed_write_blockers),
)
return cls(
automation=automation,
adapters=adapters,
readiness=readiness,
owner_authority=authority,
)
def acquire(
self,
request: GovernedWriterRequest,
authorization_ref: str,
*,
epoch: EpochStatus | None = None,
recovery_authorization: RecoveryAuthorization | None = None,
now: float | None = None,
minimum_remaining_ttl: float = 1800.0,
) -> GovernedWriterSession:
"""生产入口:先验 Owner 授权,再自动完成受治理写链。"""
return self.automation.acquire_verified(
request=request,
authorization_ref=authorization_ref,
readiness=self.readiness,
epoch=epoch,
adapters=self.adapters,
recovery_authorization=recovery_authorization,
now=now,
minimum_remaining_ttl=minimum_remaining_ttl,
)
@@ -0,0 +1,147 @@
"""Fail-closed candidate-specific governed admission preflight.
Ordinary/global writes are deliberately NOT a prerequisite. This evaluates the
governed Bridge/TaskPrincipal path only.
"""
from __future__ import annotations
from dataclasses import dataclass
from enum import Enum
from typing import Sequence
from .governance_resolver import ResolutionResult, ResolutionState
class AdmissionOperation(str, Enum):
GOVERNED_COMMIT = "GOVERNED_COMMIT"
RECOVER_EXISTING_CHILD = "RECOVER_EXISTING_CHILD"
@dataclass(frozen=True)
class GovernanceFacts:
governed_writes_enabled: bool
task_principal_writes_enabled: bool
recovery_execution_enabled: bool
recovery_shadow_mode: bool
authenticated_principal_id: str | None
target_branch: str | None
branch_protection_known: bool
expected_branch_oid: str | None
generation: int | None
task_lease_id: str | None
task_principal_id: str | None
writer_lease_id: str | None
accepted_file_allowlist: tuple[str, ...] = ()
acceptance_revision: str | None = None
@dataclass(frozen=True)
class AdmissionBlocker:
code: str
detail: str
@dataclass(frozen=True)
class AdmissionResult:
ready: bool
operation: AdmissionOperation
blockers: tuple[AdmissionBlocker, ...]
class AdmissionPreflight:
def evaluate(
self,
*,
resolution: ResolutionResult,
facts: GovernanceFacts,
operation: AdmissionOperation,
) -> AdmissionResult:
blockers: list[AdmissionBlocker] = []
if resolution.state is ResolutionState.NOT_FOUND:
blockers.append(AdmissionBlocker(
"TASK_NOT_RESOLVED", "exact candidate task not found"
))
elif resolution.state is ResolutionState.AMBIGUOUS:
blockers.append(AdmissionBlocker(
"TASK_RESOLUTION_AMBIGUOUS", "multiple exact candidate tasks"
))
elif resolution.state is not ResolutionState.RESOLVED:
blockers.append(AdmissionBlocker(
"TASK_RESOLUTION_INVALID", resolution.detail or "resolver invalid"
))
if not facts.governed_writes_enabled:
blockers.append(AdmissionBlocker(
"GOVERNED_WRITES_DISABLED",
"governed Bridge write path is disabled",
))
if not facts.task_principal_writes_enabled:
blockers.append(AdmissionBlocker(
"TASK_PRINCIPAL_WRITES_DISABLED",
"TaskPrincipal governed writes are disabled",
))
if not facts.authenticated_principal_id:
blockers.append(AdmissionBlocker(
"CALLER_PRINCIPAL_UNRESOLVED",
"authenticated governed caller principal is missing",
))
if not facts.target_branch:
blockers.append(AdmissionBlocker(
"TARGET_BRANCH_UNRESOLVED", "target branch is missing"
))
if not facts.branch_protection_known:
blockers.append(AdmissionBlocker(
"BRANCH_PROTECTION_UNKNOWN",
"branch protection has not been read through authority",
))
if not facts.expected_branch_oid:
blockers.append(AdmissionBlocker(
"EXPECTED_BRANCH_OID_UNRESOLVED",
"expected_branch_oid/CAS preimage is missing",
))
if facts.generation is None:
blockers.append(AdmissionBlocker(
"GENERATION_UNRESOLVED", "governed generation is missing"
))
if not facts.task_lease_id:
blockers.append(AdmissionBlocker(
"TASK_LEASE_MISSING", "TaskLease not established"
))
if not facts.task_principal_id:
blockers.append(AdmissionBlocker(
"TASK_PRINCIPAL_MISSING", "TaskPrincipal not established"
))
if not facts.writer_lease_id:
blockers.append(AdmissionBlocker(
"WRITER_LEASE_MISSING", "WriterLease not established"
))
if not facts.accepted_file_allowlist:
blockers.append(AdmissionBlocker(
"FILE_ALLOWLIST_MISSING", "accepted file allowlist is empty"
))
if not facts.acceptance_revision:
blockers.append(AdmissionBlocker(
"ACCEPTANCE_REVISION_MISSING",
"candidate acceptance revision is missing",
))
if operation is AdmissionOperation.RECOVER_EXISTING_CHILD:
if not facts.recovery_execution_enabled:
blockers.append(AdmissionBlocker(
"RECOVERY_EXECUTION_DISABLED",
"recovery execution path is disabled",
))
if facts.recovery_shadow_mode:
blockers.append(AdmissionBlocker(
"RECOVERY_SHADOW_MODE",
"recovery is still shadow-only",
))
# Ordinary/global write state is intentionally absent from the model.
return AdmissionResult(
ready=not blockers,
operation=operation,
blockers=tuple(blockers),
)
@@ -0,0 +1,132 @@
"""Canonical recovery observation checkpoint and unchanged-blocker budget.
The checkpoint is NOT an authority. It records the last reconciled observation
so agents do not repeatedly prove the same unchanged blocker.
"""
from __future__ import annotations
from dataclasses import asdict, dataclass, replace
import hashlib
import json
import os
from pathlib import Path
from typing import Protocol, Sequence
def _canonical(value) -> str:
return json.dumps(
value, sort_keys=True, separators=(",", ":"), ensure_ascii=False
)
def _digest(value) -> str:
return hashlib.sha256(_canonical(value).encode("utf-8")).hexdigest()
@dataclass(frozen=True)
class RecoveryCheckpoint:
schema: str
goal_id: str
repository: str
source_oid: str
candidate_digest: str
task_id: str | None
mission_id: str | None
root_task_id: str | None
parent_task_id: str | None
principal_id: str | None
blocker_code: str | None
blocker_fingerprint: str | None
next_legal_action: str
last_verified_at: str
evidence_refs: tuple[str, ...]
unchanged_cycles: int
mechanism_repair_required: bool
recovery_idempotency_key: str | None = None
recovery_receipt_ref: str | None = None
recovery_context_digest: str | None = None
def to_dict(self) -> dict:
value = asdict(self)
value["evidence_refs"] = list(self.evidence_refs)
return value
@property
def digest(self) -> str:
return _digest(self.to_dict())
class CheckpointStore(Protocol):
def load(self) -> RecoveryCheckpoint | None: ...
def save(self, checkpoint: RecoveryCheckpoint) -> None: ...
class JsonCheckpointStore:
"""Atomic local observation cache; never used as authorization evidence."""
def __init__(self, path: str | Path) -> None:
self.path = Path(path)
def load(self) -> RecoveryCheckpoint | None:
if not self.path.exists():
return None
raw = json.loads(self.path.read_text(encoding="utf-8"))
raw["evidence_refs"] = tuple(raw.get("evidence_refs") or ())
return RecoveryCheckpoint(**raw)
def save(self, checkpoint: RecoveryCheckpoint) -> None:
self.path.parent.mkdir(parents=True, exist_ok=True)
temp = self.path.with_suffix(self.path.suffix + ".tmp")
temp.write_text(
json.dumps(
checkpoint.to_dict(),
ensure_ascii=False,
indent=2,
sort_keys=True,
) + "\n",
encoding="utf-8",
)
os.replace(temp, self.path)
@dataclass(frozen=True)
class RetryBudget:
unchanged_threshold: int = 3
escalation_eligible_codes: frozenset[str] = frozenset({
"TASK_NOT_RESOLVED",
"TASK_RESOLUTION_AMBIGUOUS",
"CALLER_PRINCIPAL_UNRESOLVED",
"CANONICAL_CHANNEL_MISSING",
"RESOLVER_UNAVAILABLE",
})
def __post_init__(self):
if self.unchanged_threshold < 2 or self.unchanged_threshold > 100:
raise ValueError("unchanged_threshold must be in [2,100]")
@staticmethod
def blocker_fingerprint(code: str, details: Sequence[str]) -> str:
return _digest({
"code": code,
"details": sorted(str(item) for item in details),
})
def observe(
self,
*,
previous: RecoveryCheckpoint | None,
blocker_code: str,
blocker_fingerprint: str,
) -> tuple[int, bool]:
same = (
previous is not None
and previous.blocker_code == blocker_code
and previous.blocker_fingerprint == blocker_fingerprint
)
cycles = (previous.unchanged_cycles + 1) if same else 1
repair = (
blocker_code in self.escalation_eligible_codes
and cycles >= self.unchanged_threshold
)
return cycles, repair
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,118 @@
"""Live driver that converts external surface failures into one checkpoint.
The controller remains authority-neutral. This driver owns the site boundary:
surface availability, binding-contract failures, and poll fallback.
"""
from __future__ import annotations
from .governance_checkpoint import RecoveryCheckpoint, RetryBudget
from .governance_recovery_controller import (
GovernanceEvent,
GovernanceRecoveryController,
ReconcileResult,
)
from .site_adapters import LiveBindingViolation, LiveSurfaceUnavailable
class GovernanceLiveDriver:
def __init__(
self,
controller: GovernanceRecoveryController,
*,
retry_budget: RetryBudget | None = None,
) -> None:
self.controller = controller
self.retry_budget = retry_budget or controller.retry_budget
def _candidate_digest(self) -> str:
query = self.controller.query
return (
query.patch_digest
or query.artifact_sha256
or query.candidate_oid
or ""
)
def _persist_external_blocker(
self,
event: GovernanceEvent,
*,
code: str,
detail: str,
mechanism_repair_required: bool,
) -> ReconcileResult:
previous = self.controller.checkpoints.load()
fingerprint = self.retry_budget.blocker_fingerprint(code, (detail,))
if mechanism_repair_required:
cycles = (
previous.unchanged_cycles + 1
if previous
and previous.blocker_code == code
and previous.blocker_fingerprint == fingerprint
else 1
)
else:
cycles, _ = self.retry_budget.observe(
previous=previous,
blocker_code=code,
blocker_fingerprint=fingerprint,
)
next_action = (
"REPAIR_MECHANISM"
if mechanism_repair_required
else "WAIT_DEPENDENCY"
)
checkpoint = RecoveryCheckpoint(
schema="aios.governance-recovery-checkpoint.v1",
goal_id=self.controller.goal_id,
repository=self.controller.query.repository,
source_oid=self.controller.query.base_oid,
candidate_digest=self._candidate_digest(),
task_id=None,
mission_id=None,
root_task_id=None,
parent_task_id=None,
principal_id=None,
blocker_code=code,
blocker_fingerprint=fingerprint,
next_legal_action=next_action,
last_verified_at=self.controller._timestamp(event),
evidence_refs=(f"event:{event.kind}:{event.revision}",),
unchanged_cycles=cycles,
mechanism_repair_required=mechanism_repair_required,
)
self.controller.checkpoints.save(checkpoint)
return ReconcileResult(
status="BLOCKED",
next_legal_action=next_action,
task_id=None,
blocker_code=code,
mechanism_repair_required=mechanism_repair_required,
)
def on_event(self, event: GovernanceEvent) -> ReconcileResult:
try:
return self.controller.on_event(event)
except LiveSurfaceUnavailable as exc:
return self._persist_external_blocker(
event,
code="LIVE_SURFACE_UNAVAILABLE",
detail=str(exc),
mechanism_repair_required=False,
)
except LiveBindingViolation as exc:
return self._persist_external_blocker(
event,
code="LIVE_BINDING_VIOLATION",
detail=str(exc),
mechanism_repair_required=True,
)
def poll_once(
self, *, revision: str, occurred_at: str
) -> ReconcileResult:
return self.on_event(GovernanceEvent(
kind="POLL_FALLBACK",
revision=revision,
occurred_at=occurred_at,
))
@@ -0,0 +1,474 @@
"""Event-driven governed recovery reconciler with polling fallback.
The controller cannot create a task and cannot bypass admission. It resumes
only an exact, already-existing child through an injected governed executor.
"""
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime, timezone
import hashlib
import json
from typing import Mapping, Protocol
from .governance_admission import (
AdmissionOperation,
AdmissionPreflight,
GovernanceFacts,
)
from .governance_checkpoint import (
CheckpointStore,
RecoveryCheckpoint,
RetryBudget,
)
from .governance_resolver import ExactTaskResolver, ResolutionQuery, ResolutionState
def _canonical(value) -> str:
return json.dumps(
value, sort_keys=True, separators=(",", ":"), ensure_ascii=False
)
def _digest(value) -> str:
return hashlib.sha256(_canonical(value).encode("utf-8")).hexdigest()
@dataclass(frozen=True)
class GovernanceEvent:
kind: str
revision: str
occurred_at: str
@dataclass(frozen=True)
class RecoveryReceipt:
receipt_ref: str
task_id: str
accepted: bool
@dataclass(frozen=True)
class ReconcileResult:
status: str
next_legal_action: str
task_id: str | None
blocker_code: str | None
mechanism_repair_required: bool
receipt_ref: str | None = None
class GovernanceFactsProvider(Protocol):
def snapshot(
self,
*,
task_id: str | None,
actor_identity: str,
operation: AdmissionOperation,
) -> GovernanceFacts: ...
class ExistingChildRecoveryExecutor(Protocol):
def recover_existing_child(
self,
*,
task_id: str,
actor_identity: str,
idempotency_key: str,
facts: GovernanceFacts,
) -> RecoveryReceipt: ...
# Optional at runtime. If implemented, this is preferred after an
# unknown-result write so the controller reconciles before re-executing.
def reconcile_recovery_result(
self,
*,
task_id: str,
actor_identity: str,
idempotency_key: str,
) -> RecoveryReceipt | None: ...
class GovernanceRecoveryController:
def __init__(
self,
*,
goal_id: str,
query: ResolutionQuery,
actor_identity: str,
resolver: ExactTaskResolver,
facts_provider: GovernanceFactsProvider,
executor: ExistingChildRecoveryExecutor,
checkpoint_store: CheckpointStore,
retry_budget: RetryBudget = RetryBudget(),
) -> None:
self.goal_id = goal_id
self.query = query
self.actor_identity = actor_identity
self.resolver = resolver
self.facts_provider = facts_provider
self.executor = executor
self.checkpoints = checkpoint_store
self.retry_budget = retry_budget
self.preflight = AdmissionPreflight()
def _timestamp(self, event: GovernanceEvent) -> str:
return event.occurred_at or datetime.now(timezone.utc).isoformat()
def _save_blocked(
self,
*,
event: GovernanceEvent,
task_id: str | None,
mission_id: str | None,
root_task_id: str | None,
parent_task_id: str | None,
principal_id: str | None,
blocker_code: str,
blocker_details: tuple[str, ...],
) -> ReconcileResult:
previous = self.checkpoints.load()
fingerprint = self.retry_budget.blocker_fingerprint(
blocker_code, blocker_details
)
cycles, repair = self.retry_budget.observe(
previous=previous,
blocker_code=blocker_code,
blocker_fingerprint=fingerprint,
)
next_action = "REPAIR_MECHANISM" if repair else "WAIT_DEPENDENCY"
checkpoint = RecoveryCheckpoint(
schema="aios.governance-recovery-checkpoint.v1",
goal_id=self.goal_id,
repository=self.query.repository,
source_oid=self.query.base_oid,
candidate_digest=(
self.query.patch_digest
or self.query.artifact_sha256
or self.query.candidate_oid
or ""
),
task_id=task_id,
mission_id=mission_id,
root_task_id=root_task_id,
parent_task_id=parent_task_id,
principal_id=principal_id,
blocker_code=blocker_code,
blocker_fingerprint=fingerprint,
next_legal_action=next_action,
last_verified_at=self._timestamp(event),
evidence_refs=(f"event:{event.kind}:{event.revision}",),
unchanged_cycles=cycles,
mechanism_repair_required=repair,
)
self.checkpoints.save(checkpoint)
return ReconcileResult(
status="BLOCKED",
next_legal_action=next_action,
task_id=task_id,
blocker_code=blocker_code,
mechanism_repair_required=repair,
)
def _candidate_digest(self) -> str:
return (
self.query.patch_digest
or self.query.artifact_sha256
or self.query.candidate_oid
or ""
)
def _recovery_context_digest(
self, *, task_id: str, facts: GovernanceFacts
) -> str:
return _digest({
"task_id": task_id,
"repository": self.query.repository,
"candidate": self._candidate_digest(),
"principal": facts.authenticated_principal_id,
"branch": facts.target_branch,
"branch_oid": facts.expected_branch_oid,
"generation": facts.generation,
"task_lease_id": facts.task_lease_id,
"task_principal_id": facts.task_principal_id,
"writer_lease_id": facts.writer_lease_id,
"acceptance_revision": facts.acceptance_revision,
"file_allowlist": sorted(facts.accepted_file_allowlist),
})
def _finish_recovered(
self,
*,
event: GovernanceEvent,
resolved,
facts: GovernanceFacts,
idempotency_key: str,
context_digest: str,
receipt: RecoveryReceipt,
) -> ReconcileResult:
checkpoint = RecoveryCheckpoint(
schema="aios.governance-recovery-checkpoint.v1",
goal_id=self.goal_id,
repository=self.query.repository,
source_oid=self.query.base_oid,
candidate_digest=self._candidate_digest(),
task_id=resolved.task_id,
mission_id=resolved.mission_id,
root_task_id=resolved.root_task_id,
parent_task_id=resolved.parent_task_id,
principal_id=facts.authenticated_principal_id,
blocker_code=None,
blocker_fingerprint=None,
next_legal_action="ROOT_REVIEW",
last_verified_at=self._timestamp(event),
evidence_refs=(
f"event:{event.kind}:{event.revision}",
receipt.receipt_ref,
),
unchanged_cycles=0,
mechanism_repair_required=False,
recovery_idempotency_key=idempotency_key,
recovery_receipt_ref=receipt.receipt_ref,
recovery_context_digest=context_digest,
)
self.checkpoints.save(checkpoint)
return ReconcileResult(
status="RECOVERED",
next_legal_action="ROOT_REVIEW",
task_id=resolved.task_id,
blocker_code=None,
mechanism_repair_required=False,
receipt_ref=receipt.receipt_ref,
)
def reconcile(self, event: GovernanceEvent) -> ReconcileResult:
resolution = self.resolver.resolve(
self.query, actor_identity=self.actor_identity
)
if resolution.state is not ResolutionState.RESOLVED:
code = {
ResolutionState.NOT_FOUND: "TASK_NOT_RESOLVED",
ResolutionState.AMBIGUOUS: "TASK_RESOLUTION_AMBIGUOUS",
ResolutionState.INVALID_QUERY: "TASK_RESOLUTION_INVALID",
}[resolution.state]
details = (
resolution.detail,
*resolution.candidate_task_ids,
)
return self._save_blocked(
event=event,
task_id=None,
mission_id=None,
root_task_id=None,
parent_task_id=None,
principal_id=None,
blocker_code=code,
blocker_details=tuple(str(v) for v in details if v),
)
resolved = resolution.resolved
assert resolved is not None
facts = self.facts_provider.snapshot(
task_id=resolved.task_id,
actor_identity=self.actor_identity,
operation=AdmissionOperation.RECOVER_EXISTING_CHILD,
)
admission = self.preflight.evaluate(
resolution=resolution,
facts=facts,
operation=AdmissionOperation.RECOVER_EXISTING_CHILD,
)
if not admission.ready:
primary = admission.blockers[0]
details = tuple(
f"{blocker.code}:{blocker.detail}"
for blocker in admission.blockers
)
return self._save_blocked(
event=event,
task_id=resolved.task_id,
mission_id=resolved.mission_id,
root_task_id=resolved.root_task_id,
parent_task_id=resolved.parent_task_id,
principal_id=facts.authenticated_principal_id,
blocker_code=primary.code,
blocker_details=details,
)
context_digest = self._recovery_context_digest(
task_id=resolved.task_id, facts=facts
)
previous = self.checkpoints.load()
candidate_digest = self._candidate_digest()
unknown_previous = (
previous is not None
and previous.blocker_code == "RECOVERY_RESULT_UNKNOWN"
and previous.task_id == resolved.task_id
and previous.candidate_digest == candidate_digest
and bool(previous.recovery_idempotency_key)
)
if unknown_previous:
idempotency_key = previous.recovery_idempotency_key
reconcile = getattr(
self.executor, "reconcile_recovery_result", None
)
if callable(reconcile):
receipt = reconcile(
task_id=resolved.task_id,
actor_identity=self.actor_identity,
idempotency_key=idempotency_key,
)
if receipt is not None:
if receipt.accepted:
return self._finish_recovered(
event=event,
resolved=resolved,
facts=facts,
idempotency_key=idempotency_key,
context_digest=(
previous.recovery_context_digest
or context_digest
),
receipt=receipt,
)
return self._save_blocked(
event=event,
task_id=resolved.task_id,
mission_id=resolved.mission_id,
root_task_id=resolved.root_task_id,
parent_task_id=resolved.parent_task_id,
principal_id=facts.authenticated_principal_id,
blocker_code="RECOVERY_NOT_ACCEPTED",
blocker_details=(receipt.receipt_ref,),
)
if (
previous.recovery_context_digest is not None
and previous.recovery_context_digest != context_digest
):
checkpoint = RecoveryCheckpoint(
schema="aios.governance-recovery-checkpoint.v1",
goal_id=self.goal_id,
repository=self.query.repository,
source_oid=self.query.base_oid,
candidate_digest=candidate_digest,
task_id=resolved.task_id,
mission_id=resolved.mission_id,
root_task_id=resolved.root_task_id,
parent_task_id=resolved.parent_task_id,
principal_id=facts.authenticated_principal_id,
blocker_code="RECOVERY_RESULT_UNKNOWN",
blocker_fingerprint=_digest({
"task_id": resolved.task_id,
"idempotency_key": idempotency_key,
"context_changed": True,
}),
next_legal_action="RECOVER_EXISTING_CHILD",
last_verified_at=self._timestamp(event),
evidence_refs=(f"event:{event.kind}:{event.revision}",),
unchanged_cycles=1,
mechanism_repair_required=False,
recovery_idempotency_key=idempotency_key,
recovery_context_digest=(
previous.recovery_context_digest
),
)
self.checkpoints.save(checkpoint)
return ReconcileResult(
status="RESULT_UNKNOWN",
next_legal_action="RECOVER_EXISTING_CHILD",
task_id=resolved.task_id,
blocker_code="RECOVERY_RESULT_UNKNOWN",
mechanism_repair_required=False,
)
else:
idempotency_key = "recover-" + _digest({
"task_id": resolved.task_id,
"repository": self.query.repository,
"candidate": candidate_digest,
"branch_oid": facts.expected_branch_oid,
"generation": facts.generation,
"writer_lease_id": facts.writer_lease_id,
})[:40]
try:
receipt = self.executor.recover_existing_child(
task_id=resolved.task_id,
actor_identity=self.actor_identity,
idempotency_key=idempotency_key,
facts=facts,
)
except Exception:
# Unknown-result write: retain the SAME idempotency key for the next
# reconcile. Never assume the side effect did not happen.
checkpoint = RecoveryCheckpoint(
schema="aios.governance-recovery-checkpoint.v1",
goal_id=self.goal_id,
repository=self.query.repository,
source_oid=self.query.base_oid,
candidate_digest=(
self.query.patch_digest
or self.query.artifact_sha256
or self.query.candidate_oid
or ""
),
task_id=resolved.task_id,
mission_id=resolved.mission_id,
root_task_id=resolved.root_task_id,
parent_task_id=resolved.parent_task_id,
principal_id=facts.authenticated_principal_id,
blocker_code="RECOVERY_RESULT_UNKNOWN",
blocker_fingerprint=_digest({
"task_id": resolved.task_id,
"idempotency_key": idempotency_key,
}),
next_legal_action="RECOVER_EXISTING_CHILD",
last_verified_at=self._timestamp(event),
evidence_refs=(f"event:{event.kind}:{event.revision}",),
unchanged_cycles=1,
mechanism_repair_required=False,
recovery_idempotency_key=idempotency_key,
recovery_context_digest=context_digest,
)
self.checkpoints.save(checkpoint)
return ReconcileResult(
status="RESULT_UNKNOWN",
next_legal_action="RECOVER_EXISTING_CHILD",
task_id=resolved.task_id,
blocker_code="RECOVERY_RESULT_UNKNOWN",
mechanism_repair_required=False,
)
if not receipt.accepted:
return self._save_blocked(
event=event,
task_id=resolved.task_id,
mission_id=resolved.mission_id,
root_task_id=resolved.root_task_id,
parent_task_id=resolved.parent_task_id,
principal_id=facts.authenticated_principal_id,
blocker_code="RECOVERY_NOT_ACCEPTED",
blocker_details=(receipt.receipt_ref,),
)
return self._finish_recovered(
event=event,
resolved=resolved,
facts=facts,
idempotency_key=idempotency_key,
context_digest=context_digest,
receipt=receipt,
)
def on_event(self, event: GovernanceEvent) -> ReconcileResult:
# Event-driven primary path: reconcile immediately in the same call.
return self.reconcile(event)
def poll_once(self, *, revision: str, occurred_at: str) -> ReconcileResult:
# Polling is only a fallback and uses exactly the same reconcile path.
return self.reconcile(GovernanceEvent(
kind="POLL_FALLBACK",
revision=revision,
occurred_at=occurred_at,
))
@@ -0,0 +1,229 @@
"""Read-only exact Task/lineage resolver.
This is an index/projection over existing TaskPlane authority, not a second task
authority. It never creates, claims, mutates, or guesses a task.
The source supplied by the host MUST enforce the caller's existing TaskPlane
read authorization before returning records.
"""
from __future__ import annotations
from dataclasses import dataclass, field
from enum import Enum
from typing import Any, Mapping, Protocol, Sequence
class ResolutionState(str, Enum):
RESOLVED = "RESOLVED"
NOT_FOUND = "NOT_FOUND"
AMBIGUOUS = "AMBIGUOUS"
INVALID_QUERY = "INVALID_QUERY"
@dataclass(frozen=True)
class ResolutionQuery:
repository: str
base_oid: str
task_id: str | None = None
candidate_oid: str | None = None
patch_digest: str | None = None
artifact_sha256: str | None = None
pr_id: str | None = None
release_intent: str | None = None
mission_id: str | None = None
root_task_id: str | None = None
parent_task_id: str | None = None
owner_principal_id: str | None = None
def selectors(self) -> Mapping[str, str]:
values = {
"repository": self.repository,
"base_oid": self.base_oid,
"task_id": self.task_id,
"candidate_oid": self.candidate_oid,
"patch_digest": self.patch_digest,
"artifact_sha256": self.artifact_sha256,
"pr_id": self.pr_id,
"release_intent": self.release_intent,
"mission_id": self.mission_id,
"root_task_id": self.root_task_id,
"parent_task_id": self.parent_task_id,
"owner_principal_id": self.owner_principal_id,
}
return {key: str(value) for key, value in values.items() if value}
def validate(self) -> None:
if not self.repository.strip() or not self.base_oid.strip():
raise ValueError("repository and base_oid required")
strong = (
self.task_id,
self.candidate_oid,
self.patch_digest,
self.artifact_sha256,
self.pr_id,
self.release_intent,
self.mission_id,
self.root_task_id,
)
if not any(value for value in strong):
raise ValueError(
"at least one strong selector required "
"(task/candidate/patch/artifact/pr/intent/mission/root)"
)
@dataclass(frozen=True)
class ResolvedTask:
task_id: str
mission_id: str | None
root_task_id: str | None
parent_task_id: str | None
owner_principal_id: str | None
record: Mapping[str, Any]
matched_selectors: tuple[str, ...]
@dataclass(frozen=True)
class ResolutionResult:
state: ResolutionState
resolved: ResolvedTask | None = None
candidate_task_ids: tuple[str, ...] = ()
detail: str = ""
class TaskResolutionSource(Protocol):
"""Caller-authorized read projection over the existing TaskPlane."""
def get_task(
self, task_id: str, *, actor_identity: str
) -> Mapping[str, Any] | None: ...
def list_tasks(
self, *, actor_identity: str
) -> Sequence[Mapping[str, Any]]: ...
def _mapping(value: Any) -> Mapping[str, Any]:
return value if isinstance(value, Mapping) else {}
def _metadata(record: Mapping[str, Any]) -> Mapping[str, Any]:
plan = _mapping(record.get("plan") or record.get("task_plan"))
merged: dict[str, Any] = {}
for source in (
_mapping(record.get("metadata")),
_mapping(plan.get("metadata")),
):
merged.update(source)
# Some existing lineage metadata is nested.
for key in ("aios_task_lineage", "lineage", "task_lineage"):
nested = _mapping(merged.get(key))
for nkey, value in nested.items():
merged.setdefault(nkey, value)
return merged
def _first(record: Mapping[str, Any], metadata: Mapping[str, Any], *keys: str):
for key in keys:
if key in record and record.get(key) not in (None, ""):
return record.get(key)
if key in metadata and metadata.get(key) not in (None, ""):
return metadata.get(key)
return None
def _projection(record: Mapping[str, Any]) -> Mapping[str, str]:
metadata = _metadata(record)
values = {
"repository": _first(record, metadata, "repository", "repo"),
"base_oid": _first(record, metadata, "base_oid", "source_oid"),
"task_id": _first(record, metadata, "task_id"),
"candidate_oid": _first(record, metadata, "candidate_oid", "head_oid"),
"patch_digest": _first(record, metadata, "patch_digest", "candidate_digest"),
"artifact_sha256": _first(record, metadata, "artifact_sha256", "artifact_digest"),
"pr_id": _first(record, metadata, "pr_id", "pull_request_id"),
"release_intent": _first(record, metadata, "release_intent", "release_intent_id"),
"mission_id": _first(record, metadata, "mission_id"),
"root_task_id": _first(record, metadata, "root_task_id"),
"parent_task_id": _first(record, metadata, "parent_task_id"),
"owner_principal_id": _first(record, metadata, "owner_principal_id"),
}
return {
key: str(value)
for key, value in values.items()
if value not in (None, "")
}
def _matches(record: Mapping[str, Any], selectors: Mapping[str, str]) -> bool:
projection = _projection(record)
# Exact-only: a missing selector never counts as a partial match.
return all(projection.get(key) == value for key, value in selectors.items())
class ExactTaskResolver:
def __init__(self, source: TaskResolutionSource) -> None:
self.source = source
def resolve(
self, query: ResolutionQuery, *, actor_identity: str
) -> ResolutionResult:
try:
query.validate()
except ValueError as exc:
return ResolutionResult(
ResolutionState.INVALID_QUERY, detail=str(exc)
)
selectors = query.selectors()
if query.task_id:
record = self.source.get_task(
query.task_id, actor_identity=actor_identity
)
records = [record] if record is not None else []
else:
records = list(
self.source.list_tasks(actor_identity=actor_identity)
)
matches = [
record
for record in records
if record is not None and _matches(record, selectors)
]
if not matches:
return ResolutionResult(
ResolutionState.NOT_FOUND,
detail="no caller-authorized task exactly matches all selectors",
)
if len(matches) > 1:
ids = sorted(
str(_projection(record).get("task_id") or "")
for record in matches
)
return ResolutionResult(
ResolutionState.AMBIGUOUS,
candidate_task_ids=tuple(ids),
detail="multiple exact matches; resolver refuses to guess",
)
record = matches[0]
projected = _projection(record)
task_id = projected.get("task_id")
if not task_id:
return ResolutionResult(
ResolutionState.NOT_FOUND,
detail="matching record lacks authoritative task_id",
)
resolved = ResolvedTask(
task_id=task_id,
mission_id=projected.get("mission_id"),
root_task_id=projected.get("root_task_id"),
parent_task_id=projected.get("parent_task_id"),
owner_principal_id=projected.get("owner_principal_id"),
record=record,
matched_selectors=tuple(sorted(selectors)),
)
return ResolutionResult(ResolutionState.RESOLVED, resolved=resolved)
@@ -0,0 +1,912 @@
from __future__ import annotations
import hashlib
import json
import time
from dataclasses import asdict, dataclass, field
from enum import Enum
from typing import Any, Mapping, Protocol, Sequence
from .governance_control_kernel import (
ActionKind,
ActionLedger,
AutonomousDecisionEngine,
BlockerClassification,
GovernanceControlError,
ManifestStore,
NextAction,
ProducerKind,
)
class AuthorizationDenied(GovernanceControlError):
pass
class GovernedWriterUnavailable(GovernanceControlError):
pass
class OwnerActionRequired(GovernanceControlError):
pass
class GovernedWriterState(str, Enum):
PREFLIGHT_FROZEN = "PREFLIGHT_FROZEN"
OWNER_AUTHORIZED = "OWNER_AUTHORIZED"
EPOCH_VALID = "EPOCH_VALID"
TASK_CLAIMED = "TASK_CLAIMED"
TASK_LEASED = "TASK_LEASED"
PRINCIPAL_ISSUED = "PRINCIPAL_ISSUED"
JOB_BOUND = "JOB_BOUND"
CAS_VERIFIED = "CAS_VERIFIED"
WRITER_LEASED = "WRITER_LEASED"
RISK_ORDER = {
"low": 10,
"medium": 20,
"engineering": 25,
"high": 30,
"critical": 40,
}
def _canonical(value: Any) -> bytes:
return json.dumps(
value,
sort_keys=True,
separators=(",", ":"),
ensure_ascii=False,
allow_nan=False,
).encode("utf-8")
def _digest(value: Any) -> str:
return "sha256:" + hashlib.sha256(_canonical(value)).hexdigest()
@dataclass(frozen=True)
class OwnerAuthorizationPolicy:
"""Long-lived business authorization, separate from short-lived credentials."""
authorization_id: str
subject: str
repository: str
allowed_branch_prefixes: tuple[str, ...]
allowed_capabilities: frozenset[str]
risk_ceiling: str
trust_domain: str
revocation_epoch: int
not_before: float
expires_at: float | None = None
revoked: bool = False
allowed_operations: frozenset[str] = frozenset(
{
"claim",
"heartbeat",
"task_lease",
"task_principal",
"job_reconcile",
"writer_lease",
"governed_commit",
"readback",
"reconcile",
"epoch_reissue",
"successor_recovery",
}
)
def validate(self, request: "GovernedWriterRequest", *, now: float | None = None) -> None:
now = time.time() if now is None else float(now)
if self.revoked:
raise AuthorizationDenied("owner authorization revoked")
if now < self.not_before:
raise AuthorizationDenied("owner authorization not yet valid")
if self.expires_at is not None and now >= self.expires_at:
raise AuthorizationDenied("owner authorization expired")
if request.subject != self.subject:
raise AuthorizationDenied("subject mismatch")
if request.repository != self.repository:
raise AuthorizationDenied("repository outside authorization")
if not any(request.target_branch.startswith(p) for p in self.allowed_branch_prefixes):
raise AuthorizationDenied("branch outside authorization")
if not request.required_capabilities.issubset(self.allowed_capabilities):
missing = sorted(request.required_capabilities - self.allowed_capabilities)
raise AuthorizationDenied("capability outside authorization: " + ",".join(missing))
ceiling = RISK_ORDER.get(self.risk_ceiling.lower())
requested = RISK_ORDER.get(request.risk.lower())
if ceiling is None or requested is None:
raise AuthorizationDenied("unknown risk level")
if requested > ceiling:
raise AuthorizationDenied("risk exceeds authorization ceiling")
@property
def policy_fingerprint(self) -> str:
return _digest(
{
"authorization_id": self.authorization_id,
"subject": self.subject,
"repository": self.repository,
"allowed_branch_prefixes": list(self.allowed_branch_prefixes),
"allowed_capabilities": sorted(self.allowed_capabilities),
"risk_ceiling": self.risk_ceiling,
"trust_domain": self.trust_domain,
"revocation_epoch": self.revocation_epoch,
"not_before": self.not_before,
"expires_at": self.expires_at,
"allowed_operations": sorted(self.allowed_operations),
}
)
@dataclass(frozen=True)
class GovernedWriterRequest:
subject: str
task_id: str
repository: str
target_branch: str
risk: str
generation: int
base_oid: str
required_capabilities: frozenset[str]
file_allowlist: tuple[str, ...]
plan_digest: str
manifest_id: str
runtime_fingerprint: str
process_start_identity: str
@property
def scope_fingerprint(self) -> str:
return _digest(
{
"subject": self.subject,
"task_id": self.task_id,
"repository": self.repository,
"target_branch": self.target_branch,
"risk": self.risk,
"generation": self.generation,
"base_oid": self.base_oid,
"required_capabilities": sorted(self.required_capabilities),
"file_allowlist": list(self.file_allowlist),
"plan_digest": self.plan_digest,
}
)
@dataclass(frozen=True)
class GovernedWriteReadiness:
"""Unambiguous write-path semantics.
`legacy_global_writes_enabled=False` is a required safety invariant, not a
blocker. `governed_write_path_open` is derived from the scoped writer
prerequisites below and is intentionally independent from any ambiguous
legacy `writes_enabled` field.
"""
legacy_global_writes_enabled: bool
task_principal_writes_enabled: bool
task_principal_verifier_ready: bool
governed_write_blockers: tuple[str, ...] = ()
@property
def blockers(self) -> tuple[str, ...]:
blockers = list(self.governed_write_blockers)
if self.legacy_global_writes_enabled:
blockers.append("LEGACY_GLOBAL_WRITES_MUST_REMAIN_DISABLED")
if not self.task_principal_writes_enabled:
blockers.append("TASK_PRINCIPAL_WRITES_DISABLED")
if not self.task_principal_verifier_ready:
blockers.append("TASK_PRINCIPAL_VERIFIER_NOT_READY")
return tuple(dict.fromkeys(blockers))
@property
def governed_write_path_open(self) -> bool:
return not self.blockers
@classmethod
def from_runtime_status(cls, status: Mapping[str, Any]) -> "GovernedWriteReadiness":
# Deliberately ignore ambiguous `writes_enabled` /
# `AIOS_GITOPS_WRITES_ENABLED`; those describe legacy/global surfaces
# in older control planes and must never be used as the sole governed
# writer readiness signal.
readiness = status.get("task_principal_readiness") or {}
blockers = status.get("governed_write_blockers")
if blockers is None:
blockers = status.get("blockers") or ()
return cls(
legacy_global_writes_enabled=bool(
status.get(
"legacy_global_writes_enabled",
status.get("legacy_writes_enabled", False),
)
),
task_principal_writes_enabled=bool(
status.get("task_principal_writes_enabled", False)
),
task_principal_verifier_ready=bool(
readiness.get("ready", status.get("task_principal_verifier_ready", False))
),
governed_write_blockers=tuple(str(x) for x in blockers),
)
@dataclass(frozen=True)
class EpochStatus:
epoch_id: str
scope_fingerprint: str
expires_at: float
policy_digest: str
def is_valid(self, *, now: float, minimum_remaining_ttl: float) -> bool:
return (self.expires_at - now) >= minimum_remaining_ttl
@dataclass(frozen=True)
class TaskState:
task_id: str
status: str
revision: int
claimed_by: str | None = None
@dataclass(frozen=True)
class ClaimReceipt:
claim_id: str
task_id: str
actor: str
fencing: int
@dataclass(frozen=True)
class TaskLeaseReceipt:
task_lease_id: str
task_id: str
token_ref: str
expires_at: float
fencing: int
@dataclass(frozen=True)
class TaskPrincipalReceipt:
principal_id: str
task_id: str
capabilities: frozenset[str]
expires_at: float
@dataclass(frozen=True)
class JobBindingReceipt:
job_id: str
task_id: str
repository: str
target_branch: str
generation: int
@dataclass(frozen=True)
class BranchReadback:
exists: bool
head_oid: str | None
@dataclass(frozen=True)
class WriterLeaseReceipt:
writer_lease_id: str
task_id: str
job_id: str
repository: str
target_branch: str
branch_mode: str
expected_branch_oid: str
generation: int
expires_at: float
fencing: int
@dataclass(frozen=True)
class GovernedWriterSession:
state: GovernedWriterState
authorization_id: str
policy_fingerprint: str
scope_fingerprint: str
epoch_id: str
task_id: str
claim_id: str
task_lease_id: str
principal_id: str
job_id: str
writer_lease_id: str
branch_mode: str
expected_branch_oid: str
expires_at: float
evidence_refs: tuple[str, ...] = ()
def to_dict(self) -> dict[str, Any]:
result = asdict(self)
result["state"] = self.state.value
return result
@dataclass(frozen=True)
class RecoveryAuthorization:
"""Pre-approved signed recovery/successor authorization reference."""
authorization_ref: str
task_id: str
action: str # TERMINAL_RECOVERY | SUCCESSOR
expires_at: float
class TrustedSignerAdapter(Protocol):
def reissue_epoch(
self,
*,
owner_policy: OwnerAuthorizationPolicy,
request: GovernedWriterRequest,
prior_epoch: EpochStatus | None,
minimum_remaining_ttl: float,
idempotency_key: str,
) -> EpochStatus: ...
class ExecutionPlane(Protocol):
def read_task(self, task_id: str) -> TaskState: ...
def apply_recovery(
self,
*,
task: TaskState,
authorization: RecoveryAuthorization,
idempotency_key: str,
) -> TaskState: ...
def claim(
self,
*,
task: TaskState,
actor: str,
idempotency_key: str,
) -> ClaimReceipt: ...
def heartbeat(self, *, claim: ClaimReceipt) -> None: ...
def issue_task_lease(
self,
*,
claim: ClaimReceipt,
idempotency_key: str,
) -> TaskLeaseReceipt: ...
class TaskPrincipalIssuer(Protocol):
def issue(
self,
*,
lease: TaskLeaseReceipt,
requested_capabilities: frozenset[str],
idempotency_key: str,
) -> TaskPrincipalReceipt: ...
class JobAuthority(Protocol):
def read_or_reconcile(
self,
*,
task_id: str,
principal: TaskPrincipalReceipt,
idempotency_key: str,
) -> JobBindingReceipt: ...
class GitAuthority(Protocol):
def read_branch(self, *, repository: str, target_branch: str) -> BranchReadback: ...
class WriterLeaseAuthority(Protocol):
def acquire(
self,
*,
task_id: str,
job: JobBindingReceipt,
principal: TaskPrincipalReceipt,
repository: str,
target_branch: str,
branch_mode: str,
base_oid: str,
expected_branch_oid: str,
generation: int,
idempotency_key: str,
) -> WriterLeaseReceipt: ...
@dataclass(frozen=True)
class VerifiedOwnerAuthorization:
policy: OwnerAuthorizationPolicy
authorization_ref: str
verification_receipt: str
verified_at: float
class OwnerAuthorizationAuthority(Protocol):
def verify(
self,
*,
authorization_ref: str,
request: GovernedWriterRequest,
) -> VerifiedOwnerAuthorization: ...
@dataclass
class GovernedWriterAdapters:
signer: TrustedSignerAdapter
execution: ExecutionPlane
principal_issuer: TaskPrincipalIssuer
jobs: JobAuthority
git: GitAuthority
writer_lease: WriterLeaseAuthority
owner_authority: OwnerAuthorizationAuthority | None = None
class GovernedWriterAutomation:
"""Owner-policy-driven acquisition of a scoped governed writer capability.
This is the mechanism that removes repeated manual intervention. Long-lived
OwnerAuthorizationPolicy establishes allowed scope. Every concrete effect is
still protected by fresh runtime manifest, short-lived TaskLease,
TaskPrincipal, WriterLease, CAS and idempotency.
"""
def __init__(
self,
*,
manifest_store: ManifestStore,
action_ledger: ActionLedger,
decision_engine: AutonomousDecisionEngine | None = None,
):
self.manifest_store = manifest_store
self.action_ledger = action_ledger
self.decision_engine = decision_engine or AutonomousDecisionEngine()
def _begin_action(
self,
*,
request: GovernedWriterRequest,
suffix: str,
kind: ActionKind,
target: str,
precondition: Mapping[str, Any],
):
return self.action_ledger.begin(
idempotency_key=f"writer:{request.scope_fingerprint}:{suffix}",
manifest_id=request.manifest_id,
phase="GOVERNED_WRITER",
kind=kind,
target=target,
precondition_hash=_digest(precondition),
)
def _ensure_epoch(
self,
*,
request: GovernedWriterRequest,
owner_policy: OwnerAuthorizationPolicy,
epoch: EpochStatus | None,
adapters: GovernedWriterAdapters,
now: float,
minimum_remaining_ttl: float,
) -> EpochStatus:
if (
epoch is not None
and epoch.scope_fingerprint == request.scope_fingerprint
and epoch.is_valid(now=now, minimum_remaining_ttl=minimum_remaining_ttl)
):
return epoch
# Changed scope may not silently inherit an old Owner grant.
if epoch is not None and epoch.scope_fingerprint != request.scope_fingerprint:
raise OwnerActionRequired("epoch scope changed; new Owner authorization required")
ticket = self._begin_action(
request=request,
suffix="epoch",
kind=ActionKind.EPOCH_ISSUANCE,
target="trusted-grant-signer-adapter",
precondition={
"owner_policy": owner_policy.policy_fingerprint,
"scope": request.scope_fingerprint,
"prior_epoch": None if epoch is None else epoch.epoch_id,
},
)
if ticket.reused and ticket.requires_reconcile:
raise GovernedWriterUnavailable("epoch issuance effect unknown; reconcile required")
if ticket.reused and ticket.state.value == "SUCCEEDED":
# The ledger proves the action succeeded, but the concrete receipt
# must be obtained from the signer/readback, not fabricated locally.
raise GovernedWriterUnavailable("epoch already issued; signer readback required")
try:
fresh = adapters.signer.reissue_epoch(
owner_policy=owner_policy,
request=request,
prior_epoch=epoch,
minimum_remaining_ttl=minimum_remaining_ttl,
idempotency_key=ticket.idempotency_key,
)
if fresh.scope_fingerprint != request.scope_fingerprint:
raise GovernedWriterUnavailable("signer returned mismatched scope")
if not fresh.is_valid(now=now, minimum_remaining_ttl=minimum_remaining_ttl):
raise GovernedWriterUnavailable("signer returned insufficient TTL")
self.action_ledger.mark_succeeded(
ticket.action_id,
result={
"epoch_id": fresh.epoch_id,
"expires_at": fresh.expires_at,
"scope_fingerprint": fresh.scope_fingerprint,
},
)
return fresh
except OwnerActionRequired:
self.action_ledger.mark_failed(ticket.action_id, result={"owner_action": True})
raise
except Exception as exc:
self.action_ledger.mark_effect_unknown(
ticket.action_id,
result={"error": f"{type(exc).__name__}:{exc}"},
)
raise
def _ensure_claimable_task(
self,
*,
request: GovernedWriterRequest,
recovery_authorization: RecoveryAuthorization | None,
adapters: GovernedWriterAdapters,
now: float,
) -> TaskState:
task = adapters.execution.read_task(request.task_id)
status = task.status.upper()
if status == "QUEUED":
return task
if status in {"CLAIMED", "RUNNING"}:
return task
if status == "EXPIRED":
if recovery_authorization is None:
raise OwnerActionRequired(
"terminal task expired; owner-signed recovery/successor authorization required"
)
if recovery_authorization.task_id != task.task_id:
raise AuthorizationDenied("recovery authorization task mismatch")
if recovery_authorization.expires_at <= now:
raise AuthorizationDenied("recovery authorization expired")
if recovery_authorization.action not in {"TERMINAL_RECOVERY", "SUCCESSOR"}:
raise AuthorizationDenied("unsupported recovery action")
ticket = self._begin_action(
request=request,
suffix="task-recovery",
kind=ActionKind.GENERIC_MUTATION,
target="agent-task-terminal-recovery",
precondition={
"task_id": task.task_id,
"revision": task.revision,
"status": task.status,
"authorization_ref": recovery_authorization.authorization_ref,
"action": recovery_authorization.action,
},
)
if ticket.reused and ticket.requires_reconcile:
raise GovernedWriterUnavailable("task recovery effect unknown; reconcile required")
try:
recovered = adapters.execution.apply_recovery(
task=task,
authorization=recovery_authorization,
idempotency_key=ticket.idempotency_key,
)
if recovered.status.upper() != "QUEUED":
raise GovernedWriterUnavailable(
"recovery did not return claimable QUEUED task"
)
self.action_ledger.mark_succeeded(
ticket.action_id,
result={
"task_id": recovered.task_id,
"status": recovered.status,
"revision": recovered.revision,
},
)
return recovered
except Exception as exc:
self.action_ledger.mark_effect_unknown(
ticket.action_id,
result={"error": f"{type(exc).__name__}:{exc}"},
)
raise
raise GovernedWriterUnavailable(f"task status not claimable: {task.status}")
def acquire_verified(
self,
*,
request: GovernedWriterRequest,
authorization_ref: str,
readiness: GovernedWriteReadiness,
epoch: EpochStatus | None,
adapters: GovernedWriterAdapters,
recovery_authorization: RecoveryAuthorization | None = None,
now: float | None = None,
minimum_remaining_ttl: float = 1800.0,
) -> GovernedWriterSession:
"""Production entry: verify Owner authorization before any effect.
The field composition root MUST use this method. `acquire()` is an
internal/test hook for already-verified policy objects.
"""
if adapters.owner_authority is None:
raise AuthorizationDenied("OwnerAuthorizationAuthority is not configured")
verified = adapters.owner_authority.verify(
authorization_ref=authorization_ref,
request=request,
)
if verified.authorization_ref != authorization_ref:
raise AuthorizationDenied("owner authorization reference mismatch")
if not verified.verification_receipt:
raise AuthorizationDenied("owner authorization verification receipt missing")
return self.acquire(
request=request,
owner_policy=verified.policy,
readiness=readiness,
epoch=epoch,
adapters=adapters,
recovery_authorization=recovery_authorization,
now=now,
minimum_remaining_ttl=minimum_remaining_ttl,
)
def acquire(
self,
*,
request: GovernedWriterRequest,
owner_policy: OwnerAuthorizationPolicy,
readiness: GovernedWriteReadiness,
epoch: EpochStatus | None,
adapters: GovernedWriterAdapters,
recovery_authorization: RecoveryAuthorization | None = None,
now: float | None = None,
minimum_remaining_ttl: float = 1800.0,
) -> GovernedWriterSession:
now = time.time() if now is None else float(now)
# Gate 1: authoritative frozen runtime.
self.manifest_store.assert_current(
manifest_id=request.manifest_id,
runtime_fingerprint=request.runtime_fingerprint,
process_start_identity=request.process_start_identity,
)
# Gate 2: long-lived Owner policy.
owner_policy.validate(request, now=now)
# Gate 3: scoped writer readiness, deliberately independent from legacy
# global-write switches.
if not readiness.governed_write_path_open:
raise GovernedWriterUnavailable(
"governed writer not ready: " + ",".join(readiness.blockers)
)
# Gate 4: JIT delegation epoch. If unchanged scope is authorized, the
# Agent may use the trusted signer adapter autonomously.
epoch = self._ensure_epoch(
request=request,
owner_policy=owner_policy,
epoch=epoch,
adapters=adapters,
now=now,
minimum_remaining_ttl=minimum_remaining_ttl,
)
# Gate 5: task must be claimable, with Owner recovery only when the
# lifecycle contract explicitly requires a signed terminal recovery.
task = self._ensure_claimable_task(
request=request,
recovery_authorization=recovery_authorization,
adapters=adapters,
now=now,
)
# Claim is idempotent per scope/task revision.
claim_ticket = self._begin_action(
request=request,
suffix=f"claim-r{task.revision}",
kind=ActionKind.CLAIM,
target=f"task:{task.task_id}",
precondition={"task_id": task.task_id, "revision": task.revision, "status": task.status},
)
if claim_ticket.reused and claim_ticket.requires_reconcile:
raise GovernedWriterUnavailable("claim effect unknown; reconcile required")
if task.status.upper() in {"CLAIMED", "RUNNING"}:
raise GovernedWriterUnavailable(
"existing claim must be reconciled into a concrete claim receipt"
)
try:
claim = adapters.execution.claim(
task=task,
actor=request.subject,
idempotency_key=claim_ticket.idempotency_key,
)
adapters.execution.heartbeat(claim=claim)
self.action_ledger.mark_succeeded(
claim_ticket.action_id,
result={"claim_id": claim.claim_id, "fencing": claim.fencing},
)
except Exception as exc:
self.action_ledger.mark_effect_unknown(
claim_ticket.action_id,
result={"error": f"{type(exc).__name__}:{exc}"},
)
raise
lease_ticket = self._begin_action(
request=request,
suffix=f"tasklease-{claim.claim_id}",
kind=ActionKind.GENERIC_MUTATION,
target="task-lease",
precondition={"claim_id": claim.claim_id, "fencing": claim.fencing},
)
try:
task_lease = adapters.execution.issue_task_lease(
claim=claim,
idempotency_key=lease_ticket.idempotency_key,
)
self.action_ledger.mark_succeeded(
lease_ticket.action_id,
result={
"task_lease_id": task_lease.task_lease_id,
"expires_at": task_lease.expires_at,
},
)
except Exception as exc:
self.action_ledger.mark_effect_unknown(
lease_ticket.action_id,
result={"error": f"{type(exc).__name__}:{exc}"},
)
raise
principal_ticket = self._begin_action(
request=request,
suffix=f"principal-{task_lease.task_lease_id}",
kind=ActionKind.GENERIC_MUTATION,
target="task-principal-issuer",
precondition={
"task_lease_id": task_lease.task_lease_id,
"capabilities": sorted(request.required_capabilities),
},
)
try:
principal = adapters.principal_issuer.issue(
lease=task_lease,
requested_capabilities=request.required_capabilities,
idempotency_key=principal_ticket.idempotency_key,
)
if not request.required_capabilities.issubset(principal.capabilities):
raise GovernedWriterUnavailable(
"issued TaskPrincipal lacks required capabilities"
)
self.action_ledger.mark_succeeded(
principal_ticket.action_id,
result={"principal_id": principal.principal_id, "expires_at": principal.expires_at},
)
except Exception as exc:
self.action_ledger.mark_effect_unknown(
principal_ticket.action_id,
result={"error": f"{type(exc).__name__}:{exc}"},
)
raise
job_ticket = self._begin_action(
request=request,
suffix="job",
kind=ActionKind.GENERIC_MUTATION,
target="gitops-job",
precondition={
"task_id": task.task_id,
"principal_id": principal.principal_id,
"plan_digest": request.plan_digest,
},
)
try:
job = adapters.jobs.read_or_reconcile(
task_id=task.task_id,
principal=principal,
idempotency_key=job_ticket.idempotency_key,
)
if (
job.repository != request.repository
or job.target_branch != request.target_branch
or job.generation != request.generation
):
raise GovernedWriterUnavailable("job binding mismatch")
self.action_ledger.mark_succeeded(
job_ticket.action_id,
result={"job_id": job.job_id},
)
except Exception as exc:
self.action_ledger.mark_effect_unknown(
job_ticket.action_id,
result={"error": f"{type(exc).__name__}:{exc}"},
)
raise
branch = adapters.git.read_branch(
repository=request.repository,
target_branch=request.target_branch,
)
if branch.exists:
if not branch.head_oid:
raise GovernedWriterUnavailable("branch exists without readable HEAD")
branch_mode = "CONTINUE_EXISTING"
expected_branch_oid = branch.head_oid
else:
branch_mode = "CREATE_NEW"
expected_branch_oid = request.base_oid
writer_ticket = self._begin_action(
request=request,
suffix=f"writer-{job.job_id}-{expected_branch_oid}",
kind=ActionKind.GENERIC_MUTATION,
target="writer-lease",
precondition={
"job_id": job.job_id,
"principal_id": principal.principal_id,
"branch_mode": branch_mode,
"base_oid": request.base_oid,
"expected_branch_oid": expected_branch_oid,
"generation": request.generation,
},
)
try:
writer = adapters.writer_lease.acquire(
task_id=task.task_id,
job=job,
principal=principal,
repository=request.repository,
target_branch=request.target_branch,
branch_mode=branch_mode,
base_oid=request.base_oid,
expected_branch_oid=expected_branch_oid,
generation=request.generation,
idempotency_key=writer_ticket.idempotency_key,
)
if writer.expected_branch_oid != expected_branch_oid:
raise GovernedWriterUnavailable("WriterLease CAS mismatch")
self.action_ledger.mark_succeeded(
writer_ticket.action_id,
result={
"writer_lease_id": writer.writer_lease_id,
"expires_at": writer.expires_at,
"expected_branch_oid": writer.expected_branch_oid,
},
)
except Exception as exc:
self.action_ledger.mark_effect_unknown(
writer_ticket.action_id,
result={"error": f"{type(exc).__name__}:{exc}"},
)
raise
expires_at = min(
epoch.expires_at,
task_lease.expires_at,
principal.expires_at,
writer.expires_at,
)
return GovernedWriterSession(
state=GovernedWriterState.WRITER_LEASED,
authorization_id=owner_policy.authorization_id,
policy_fingerprint=owner_policy.policy_fingerprint,
scope_fingerprint=request.scope_fingerprint,
epoch_id=epoch.epoch_id,
task_id=task.task_id,
claim_id=claim.claim_id,
task_lease_id=task_lease.task_lease_id,
principal_id=principal.principal_id,
job_id=job.job_id,
writer_lease_id=writer.writer_lease_id,
branch_mode=branch_mode,
expected_branch_oid=expected_branch_oid,
expires_at=expires_at,
)
@@ -0,0 +1,789 @@
"""Fail-closed host adapter for the C6 governed source-patch pilot.
The adapter owns no worker registration, authority database, effect ledger, or
Git mutation implementation. Those are host-owned. It only joins an exact C6
operation binding and v3 TaskPlane claim to an existing durable external
transaction owner.
The transaction owner is a security boundary. Its ``execute_prepared`` method
must durably record the send phase before an external call, call
``before_external_call`` while holding its own lease/fence, acquire and persist
the real WriterLease, enforce expected-OID CAS at the write linearization point,
and leave an ambiguous call in EFFECT_UNKNOWN. A callback by itself is not an
atomic revocation or CAS fence.
``RootActionConsumer`` exposes an exact-key facade only after the host installs
a trusted operation-to-review binding provider. C6 callers cannot synthesize
review_id, mission_id, root_task_id, root_revision, plan digest, or execution
key. The facade reuses the consumer's existing review, fence, and effect rows.
"""
from __future__ import annotations
from collections.abc import Callable, Mapping
from dataclasses import asdict, dataclass
from datetime import datetime, timezone
import hashlib
import json
import math
import re
import time
from typing import Literal, Protocol
from release_host_authority import VerifiedCurrentRecordReader
from .c6_composition import DurableExecutionIntent
from .operation_view import ClosureObservation, OperationBinding, binding_digest, validate_oid
from .patch_contract import ExecutablePatchV1, parse_executable_patch
from .schedulability_store_adapter import TaskPlaneClaimReceiptV3
_ACTION_ID = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:/@+-]{0,191}$")
_DIGEST = re.compile(r"^[0-9a-f]{64}$")
HOST_PATCH_AUTHORITY_PURPOSE = "aios.c6.host-patch-authority.current.v1"
HOST_PATCH_RECONCILE_AUTHORITY_PURPOSE = (
"aios.c6.host-patch-reconcile-authority.current.v1"
)
HOST_PATCH_AUTHORITY_SCHEMA = "aios.c6.host-patch-authority.v1"
_AUTHORITY_RECORD_FIELDS = frozenset(
{
"schema",
"authorization_ref",
"authority_generation",
"task_generation",
"claim_id",
"claim_fence",
"expected_branch_oid",
"binding_digest",
"lifecycle_state",
"scope_state",
"expires_at",
}
)
def _canonical_json_bytes(value: Mapping[str, object]) -> bytes:
return json.dumps(
dict(value), ensure_ascii=False, sort_keys=True, separators=(",", ":")
).encode("utf-8")
ActionState = Literal[
"PREPARED", "IN_PROGRESS", "EFFECT_UNKNOWN", "COMMITTED", "FAILED_FINAL"
]
class HostPatchIntegrationError(RuntimeError):
"""The host binding is incomplete, stale, or contradictory."""
class HostPatchEffectUnknown(HostPatchIntegrationError):
"""The same immutable action must be reconciled, never resubmitted."""
class HostPatchConfigurationError(HostPatchIntegrationError):
"""No production-safe exact-key transaction owner is configured."""
@dataclass(frozen=True)
class HostPatchArtifact:
document: Mapping[str, object]
patch_bytes: bytes
allowed_files: frozenset[str]
commit_message: str
def __post_init__(self) -> None:
if not isinstance(self.document, Mapping):
raise ValueError("patch document provider must return a mapping")
if not isinstance(self.patch_bytes, bytes) or not self.patch_bytes:
raise ValueError("patch provider must return non-empty bytes")
if not isinstance(self.allowed_files, frozenset) or not self.allowed_files:
raise ValueError("patch provider must return a frozen non-empty allowlist")
if (
not isinstance(self.commit_message, str)
or not self.commit_message.strip()
or self.commit_message != self.commit_message.strip()
):
raise ValueError("canonical commit message required")
@dataclass(frozen=True)
class HostPatchAuthority:
"""Fresh host readback before a transaction-owner call.
``claim_fence`` is the TaskPlane receipt revision. It is deliberately
separate from the WriterLease fence returned by the external transaction.
"""
authorization_ref: str
authority_key_id: str
authority_key_fingerprint: str
authority_record_sha256: str
authority_generation: int
task_generation: int
claim_id: str
claim_fence: int
expected_branch_oid: str
expires_at: float
def __post_init__(self) -> None:
if (
not isinstance(self.authorization_ref, str)
or not self.authorization_ref.strip()
or not isinstance(self.authority_key_id, str)
or not self.authority_key_id.strip()
or not isinstance(self.claim_id, str)
or not self.claim_id.strip()
):
raise ValueError("host authority identity required")
if (
not isinstance(self.authority_key_fingerprint, str)
or _DIGEST.fullmatch(self.authority_key_fingerprint) is None
):
raise ValueError("verified host authority key fingerprint required")
if (
not isinstance(self.authority_record_sha256, str)
or _DIGEST.fullmatch(self.authority_record_sha256) is None
):
raise ValueError("verified host authority record digest required")
for name in ("authority_generation", "task_generation", "claim_fence"):
value = getattr(self, name)
if isinstance(value, bool) or not isinstance(value, int) or value < 0:
raise ValueError(f"invalid host authority {name}")
validate_oid(self.expected_branch_oid)
if (
isinstance(self.expires_at, bool)
or not isinstance(self.expires_at, (int, float))
or not math.isfinite(float(self.expires_at))
):
raise ValueError("invalid host authority expiry")
@dataclass(frozen=True)
class HostPatchAction:
"""Readback owned by the existing external transaction boundary."""
action_id: str
state: ActionState
binding_digest: str
effect_key: str
authorization_ref: str
authority_key_id: str
authority_key_fingerprint: str
authority_record_sha256: str
claim_id: str
claim_fence: int
task_generation: int
authority_generation: int
expected_branch_oid: str
claim_receipt: Mapping[str, object]
writer_lease_id: str | None = None
writer_lease_fence: int | None = None
result_oid: str | None = None
receipt_ref: str | None = None
def __post_init__(self) -> None:
if not isinstance(self.action_id, str) or _ACTION_ID.fullmatch(self.action_id) is None:
raise ValueError("canonical immutable action_id required")
if self.state not in {
"PREPARED", "IN_PROGRESS", "EFFECT_UNKNOWN", "COMMITTED", "FAILED_FINAL"
}:
raise ValueError("invalid host patch action state")
if not isinstance(self.binding_digest, str) or _DIGEST.fullmatch(self.binding_digest) is None:
raise ValueError("action binding digest required")
if not isinstance(self.effect_key, str) or not self.effect_key:
raise ValueError("action effect key required")
if (
not isinstance(self.authorization_ref, str)
or not self.authorization_ref
or not isinstance(self.authority_key_id, str)
or not self.authority_key_id
or not isinstance(self.authority_key_fingerprint, str)
or _DIGEST.fullmatch(self.authority_key_fingerprint) is None
or not isinstance(self.authority_record_sha256, str)
or _DIGEST.fullmatch(self.authority_record_sha256) is None
or not isinstance(self.claim_id, str)
or not self.claim_id
or not isinstance(self.claim_receipt, Mapping)
):
raise ValueError("action authority/claim identity required")
for name in ("claim_fence", "task_generation", "authority_generation"):
value = getattr(self, name)
if isinstance(value, bool) or not isinstance(value, int) or value < 0:
raise ValueError(f"invalid action {name}")
validate_oid(self.expected_branch_oid)
if self.writer_lease_fence is not None and (
isinstance(self.writer_lease_fence, bool)
or not isinstance(self.writer_lease_fence, int)
or self.writer_lease_fence < 0
):
raise ValueError("invalid WriterLease fence")
if self.result_oid is not None:
validate_oid(self.result_oid)
if self.state == "COMMITTED":
if (
not self.writer_lease_id
or self.writer_lease_fence is None
or self.result_oid is None
or not self.receipt_ref
):
raise ValueError("committed action lacks WriterLease/OID/receipt binding")
class SignedHostPatchAuthorityReader:
"""Build host authority only from a verified, purpose-bound current record.
The underlying reader enforces a registered key, signature, short lifetime,
exact query hash, and a fresh nonce. This remains a freshness challenge;
the root-action transaction owner still supplies the atomic fence and CAS.
Host configuration must register both effect and reconcile purposes to the
same authority key id/raw-key fingerprint. Reconcile has a distinct signed
query and grants observation only, while prepared identity remains the same
stable authorization semantics across the two purposes.
"""
def __init__(self, current_records: VerifiedCurrentRecordReader) -> None:
if not isinstance(current_records, VerifiedCurrentRecordReader):
raise TypeError("current_records must be VerifiedCurrentRecordReader")
purposes = (
HOST_PATCH_AUTHORITY_PURPOSE,
HOST_PATCH_RECONCILE_AUTHORITY_PURPOSE,
)
try:
key_ids = [current_records.purpose_key_ids[purpose] for purpose in purposes]
fingerprints = [
current_records.purpose_key_fingerprints[purpose]
for purpose in purposes
]
except KeyError as exc:
raise HostPatchConfigurationError(
"effect and reconcile authority purposes must both be registered"
) from exc
if (
any(len(ids) != 1 for ids in key_ids)
or key_ids[0] != key_ids[1]
or set(fingerprints[0]) != set(key_ids[0])
or fingerprints[0] != fingerprints[1]
):
raise HostPatchConfigurationError(
"effect and reconcile purposes require one identical key/fingerprint"
)
self._records = current_records
@staticmethod
def _query(
binding: OperationBinding, claim: Mapping[str, object], *, reconcile: bool
) -> dict[str, object]:
return {
"schema": (
"aios.c6.host-patch-reconcile-authority-query.v1"
if reconcile
else "aios.c6.host-patch-authority-query.v1"
),
"binding": asdict(binding),
"binding_digest": binding_digest(binding),
"claim": dict(claim),
}
def _read(
self, binding: OperationBinding, claim: Mapping[str, object], *, reconcile: bool
) -> HostPatchAuthority:
purpose = (
HOST_PATCH_RECONCILE_AUTHORITY_PURPOSE
if reconcile
else HOST_PATCH_AUTHORITY_PURPOSE
)
verified = self._records.read(
purpose, self._query(binding, claim, reconcile=reconcile)
)
record = verified.record
if frozenset(record) != _AUTHORITY_RECORD_FIELDS:
missing = sorted(_AUTHORITY_RECORD_FIELDS - frozenset(record))
extra = sorted(frozenset(record) - _AUTHORITY_RECORD_FIELDS)
raise HostPatchIntegrationError(
f"host authority record fields are not exact; missing={missing!r} extra={extra!r}"
)
if (
record["schema"] != HOST_PATCH_AUTHORITY_SCHEMA
or record["lifecycle_state"] != "ACTIVE"
or record["scope_state"] != "IN_SCOPE"
or record["binding_digest"] != binding_digest(binding)
or record["task_generation"] != binding.generation
or record["claim_id"] != claim.get("claim_id")
or record["claim_fence"] != claim.get("receipt_revision")
or record["expected_branch_oid"] != binding.source_oid
):
raise HostPatchIntegrationError("signed host authority binding mismatch")
if any(
not isinstance(record[name], str) or not record[name].strip()
for name in (
"authorization_ref", "claim_id", "expected_branch_oid",
"binding_digest", "lifecycle_state", "scope_state",
)
):
raise HostPatchIntegrationError("signed host authority text fields are invalid")
for name in ("authority_generation", "task_generation", "claim_fence"):
if (
isinstance(record[name], bool)
or not isinstance(record[name], int)
or record[name] < 0
):
raise HostPatchIntegrationError(
f"signed host authority {name} is invalid"
)
if (
isinstance(record["expires_at"], bool)
or not isinstance(record["expires_at"], (int, float))
or not math.isfinite(float(record["expires_at"]))
):
raise HostPatchIntegrationError("signed host authority expiry is invalid")
try:
envelope_expiry = datetime.fromisoformat(
verified.expires_at.replace("Z", "+00:00")
).astimezone(timezone.utc).timestamp()
authority = HostPatchAuthority(
authorization_ref=record["authorization_ref"],
authority_key_id=verified.key_id,
authority_key_fingerprint=verified.key_fingerprint,
# The signed envelope and its short observation expiry change
# across fresh reads. Prepared identity binds only the stable
# authorization semantics; freshness is independently checked
# on every read.
authority_record_sha256=hashlib.sha256(
_canonical_json_bytes(
{key: value for key, value in record.items() if key != "expires_at"}
)
).hexdigest(),
authority_generation=record["authority_generation"], # type: ignore[arg-type]
task_generation=record["task_generation"], # type: ignore[arg-type]
claim_id=record["claim_id"],
claim_fence=record["claim_fence"], # type: ignore[arg-type]
expected_branch_oid=record["expected_branch_oid"],
expires_at=min(float(record["expires_at"]), envelope_expiry),
)
except (TypeError, ValueError) as exc:
raise HostPatchIntegrationError("signed host authority record is invalid") from exc
if (
not reconcile
and authority.expires_at
> float(claim.get("lease_until", authority.expires_at))
):
raise HostPatchIntegrationError("host authority outlives the TaskPlane claim")
return authority
def __call__(
self, binding: OperationBinding, claim: TaskPlaneClaimReceiptV3
) -> HostPatchAuthority:
if not isinstance(claim, TaskPlaneClaimReceiptV3):
raise TypeError("v3 TaskPlane claim receipt required")
return self._read(binding, claim.to_dict(), reconcile=False)
def read_for_action(
self, binding: OperationBinding, action: HostPatchAction
) -> HostPatchAuthority:
if not isinstance(action, HostPatchAction):
raise TypeError("host patch action required")
return self._read(binding, action.claim_receipt, reconcile=True)
def signed_host_patch_authority_provider(
current_records: VerifiedCurrentRecordReader,
) -> SignedHostPatchAuthorityReader:
"""Factory used by host composition without weakening reader type checks."""
return SignedHostPatchAuthorityReader(current_records)
class HostPatchTransactionOwner(Protocol):
"""Exact-key facade implemented by an existing durable transaction owner."""
def read_exact(self, effect_key: str) -> HostPatchAction | None: ...
def iter_pending_exact(
self, *, limit: int
) -> tuple[tuple[OperationBinding, HostPatchAction], ...]: ...
def mark_exact_closed(
self, *, binding: OperationBinding, action_id: str,
observation: ClosureObservation
) -> None: ...
def prepare_exact(
self,
*,
binding: OperationBinding,
claim: TaskPlaneClaimReceiptV3,
authority: HostPatchAuthority,
contract: ExecutablePatchV1,
patch_bytes: bytes,
commit_message: str,
) -> HostPatchAction: ...
def reconcile_exact(
self,
*,
action_id: str,
binding: OperationBinding,
authority: HostPatchAuthority,
before_external_call: Callable[[], None],
) -> HostPatchAction: ...
def execute_prepared(
self,
*,
action_id: str,
binding: OperationBinding,
claim: TaskPlaneClaimReceiptV3,
authority: HostPatchAuthority,
contract: ExecutablePatchV1,
patch_bytes: bytes,
commit_message: str,
before_external_call: Callable[[], None],
) -> HostPatchAction: ...
_EXACT_OWNER_METHODS = (
"read_exact", "iter_pending_exact", "mark_exact_closed", "prepare_exact", "reconcile_exact",
"execute_prepared"
)
def require_exact_root_action_owner(service: object) -> HostPatchTransactionOwner:
"""Return an exact-key root-action facade or fail before enabling C6.
``TaskPlaneService.root_actions`` currently exposes ``run_once`` only, so a
stock service is rejected. A production composition may install a facade
backed by the same RootActionConsumer/PublicBridgeRootActionExecutor store;
it must not create another effect ledger.
"""
owner = getattr(service, "root_actions", None)
missing = [name for name in _EXACT_OWNER_METHODS if not callable(getattr(owner, name, None))]
if missing:
raise HostPatchConfigurationError(
"exact root-action transaction owner unavailable: " + ",".join(missing)
)
if getattr(owner, "store", None) is not getattr(service, "store", None):
raise HostPatchConfigurationError(
"exact root-action owner must reuse the host TaskPlane store"
)
configured = getattr(owner, "assert_exact_owner_configured", None)
if not callable(configured):
raise HostPatchConfigurationError(
"exact root-action owner configuration assertion unavailable"
)
try:
configured()
except Exception as exc:
raise HostPatchConfigurationError(
"exact root-action transaction owner is not configured"
) from exc
return owner # type: ignore[return-value]
class HostPatchExecutor:
"""Concrete ``GovernedSourcePatchPilot`` over host-owned formal providers."""
def __init__(
self,
*,
artifact_provider: Callable[[OperationBinding], HostPatchArtifact],
authority_provider: Callable[
[OperationBinding, TaskPlaneClaimReceiptV3], HostPatchAuthority
],
transaction_owner: HostPatchTransactionOwner,
clock: Callable[[], float] | None = None,
) -> None:
if not callable(artifact_provider) or not callable(authority_provider):
raise HostPatchConfigurationError("artifact and authority providers required")
if not callable(getattr(authority_provider, "read_for_action", None)):
raise HostPatchConfigurationError(
"authority provider must support signed restart reconciliation"
)
if any(not callable(getattr(transaction_owner, name, None)) for name in _EXACT_OWNER_METHODS):
raise HostPatchConfigurationError("exact external transaction owner required")
self._artifacts = artifact_provider
self._authority_provider = authority_provider
self._transactions = transaction_owner
self._clock = clock or time.time
def _artifact(self, binding: OperationBinding) -> tuple[HostPatchArtifact, ExecutablePatchV1]:
artifact = self._artifacts(binding)
if not isinstance(artifact, HostPatchArtifact):
raise HostPatchIntegrationError("invalid host patch artifact")
contract = parse_executable_patch(
artifact.document,
patch_bytes=artifact.patch_bytes,
allowed_files=artifact.allowed_files,
expected_binding=binding,
)
return artifact, contract
def _authority(
self, binding: OperationBinding, claim: TaskPlaneClaimReceiptV3
) -> HostPatchAuthority:
value = self._authority_provider(binding, claim)
if not isinstance(value, HostPatchAuthority):
raise HostPatchIntegrationError("invalid host authority readback")
if (
value.claim_id != claim.claim_id
or value.claim_fence != claim.receipt_revision
or value.task_generation != binding.generation
or value.expected_branch_oid != binding.source_oid
or value.expires_at <= float(self._clock())
):
raise HostPatchIntegrationError("stale or mismatched host authority")
return value
@staticmethod
def _validate_action(
action: HostPatchAction,
binding: OperationBinding,
*,
claim_id: str | None = None,
claim_fence: int | None = None,
authority: HostPatchAuthority | None = None,
) -> HostPatchAction:
if not isinstance(action, HostPatchAction):
raise HostPatchIntegrationError("transaction owner returned invalid action")
if (
action.binding_digest != binding_digest(binding)
or action.effect_key != binding.effect_key
or action.task_generation != binding.generation
or action.expected_branch_oid != binding.source_oid
or (claim_id is not None and action.claim_id != claim_id)
or (claim_fence is not None and action.claim_fence != claim_fence)
):
raise HostPatchIntegrationError("external action binding mismatch")
if authority is not None and (
action.authorization_ref != authority.authorization_ref
or action.authority_key_id != authority.authority_key_id
or action.authority_key_fingerprint != authority.authority_key_fingerprint
or action.authority_record_sha256 != authority.authority_record_sha256
or action.authority_generation != authority.authority_generation
or action.claim_id != authority.claim_id
or action.claim_fence != authority.claim_fence
or action.expected_branch_oid != authority.expected_branch_oid
):
raise HostPatchIntegrationError("external action authority changed")
if action.state == "FAILED_FINAL":
raise HostPatchIntegrationError("external action failed final")
return action
def read_intent(self, binding: OperationBinding) -> DurableExecutionIntent | None:
"""C6 ``intent_reader`` wiring backed only by the transaction owner."""
action = self._transactions.read_exact(binding.effect_key)
if action is None:
return None
action = self._validate_action(action, binding)
return DurableExecutionIntent(
binding_digest(binding), binding.effect_key, action.claim_id, action.action_id
)
def pending_intents(
self, *, limit: int = 16
) -> tuple[tuple[OperationBinding, DurableExecutionIntent], ...]:
if isinstance(limit, bool) or not isinstance(limit, int) or not 1 <= limit <= 64:
raise HostPatchConfigurationError("bounded pending intent limit required")
result: list[tuple[OperationBinding, DurableExecutionIntent]] = []
for pending_binding, action in self._transactions.iter_pending_exact(limit=limit):
checked = self._validate_action(action, pending_binding)
if checked.state not in {
"PREPARED", "IN_PROGRESS", "EFFECT_UNKNOWN", "COMMITTED"
}:
raise HostPatchIntegrationError("transaction owner enumerated failed action")
result.append(
(
pending_binding,
DurableExecutionIntent(
binding_digest(pending_binding),
pending_binding.effect_key,
checked.claim_id,
checked.action_id,
),
)
)
return tuple(result)
def mark_intent_closed(
self, binding: OperationBinding, intent: DurableExecutionIntent,
observation: ClosureObservation,
) -> None:
if (
not isinstance(intent, DurableExecutionIntent)
or intent.binding_digest != binding_digest(binding)
or intent.effect_key != binding.effect_key
or not isinstance(observation, ClosureObservation)
):
raise HostPatchIntegrationError("invalid durable closure acknowledgement")
self._transactions.mark_exact_closed(
binding=binding, action_id=intent.receipt_ref, observation=observation
)
def prepare_intent(
self, *, binding: OperationBinding, claim: TaskPlaneClaimReceiptV3
) -> DurableExecutionIntent:
artifact, contract = self._artifact(binding)
authority = self._authority(binding, claim)
action = self._transactions.prepare_exact(
binding=binding,
claim=claim,
authority=authority,
contract=contract,
patch_bytes=artifact.patch_bytes,
commit_message=artifact.commit_message,
)
action = self._validate_action(
action,
binding,
claim_id=claim.claim_id,
claim_fence=claim.receipt_revision,
authority=authority,
)
return DurableExecutionIntent(
binding_digest(binding), binding.effect_key, claim.claim_id, action.action_id
)
def execute(
self,
*,
binding: OperationBinding,
claim: TaskPlaneClaimReceiptV3,
intent: DurableExecutionIntent,
) -> None:
if (
not isinstance(intent, DurableExecutionIntent)
or intent.binding_digest != binding_digest(binding)
or intent.effect_key != binding.effect_key
or intent.claim_id != claim.claim_id
):
raise HostPatchIntegrationError("execution intent binding mismatch")
artifact, contract = self._artifact(binding)
authority = self._authority(binding, claim)
action = self._transactions.read_exact(binding.effect_key)
if action is None or action.action_id != intent.receipt_ref:
raise HostPatchIntegrationError("durable external action unavailable")
action = self._validate_action(
action, binding, claim_id=claim.claim_id,
claim_fence=claim.receipt_revision, authority=authority,
)
def revalidate() -> None:
fresh = self._authority(binding, claim)
if (
fresh.authorization_ref != authority.authorization_ref
or fresh.authority_key_id != authority.authority_key_id
or fresh.authority_key_fingerprint != authority.authority_key_fingerprint
or fresh.authority_record_sha256 != authority.authority_record_sha256
or fresh.authority_generation != authority.authority_generation
or fresh.task_generation != authority.task_generation
or fresh.claim_id != authority.claim_id
or fresh.claim_fence != authority.claim_fence
or fresh.expected_branch_oid != authority.expected_branch_oid
):
raise HostPatchIntegrationError("host authority changed before external call")
observed = self._transactions.reconcile_exact(
action_id=action.action_id,
binding=binding,
authority=authority,
before_external_call=revalidate,
)
observed = self._validate_action(
observed, binding, claim_id=claim.claim_id,
claim_fence=claim.receipt_revision, authority=authority,
)
if observed.action_id != action.action_id:
raise HostPatchIntegrationError("immutable external action_id changed")
if observed.state == "COMMITTED":
return
if observed.state in {"IN_PROGRESS", "EFFECT_UNKNOWN"}:
raise HostPatchEffectUnknown("external effect requires exact-key reconciliation")
if observed.state != "PREPARED":
raise HostPatchIntegrationError("external action is not executable")
completed = self._transactions.execute_prepared(
action_id=action.action_id,
binding=binding,
claim=claim,
authority=authority,
contract=contract,
patch_bytes=artifact.patch_bytes,
commit_message=artifact.commit_message,
before_external_call=revalidate,
)
completed = self._validate_action(
completed, binding, claim_id=claim.claim_id,
claim_fence=claim.receipt_revision, authority=authority,
)
if completed.action_id != action.action_id:
raise HostPatchIntegrationError("immutable external action_id changed")
if completed.state != "COMMITTED":
raise HostPatchEffectUnknown("external transaction did not return committed readback")
def reconcile(
self, *, binding: OperationBinding, intent: DurableExecutionIntent
) -> None:
"""Reconcile a durable intent without acquiring a lease or resending a commit."""
if (
not isinstance(intent, DurableExecutionIntent)
or intent.binding_digest != binding_digest(binding)
or intent.effect_key != binding.effect_key
):
raise HostPatchIntegrationError("reconciliation intent binding mismatch")
action = self._transactions.read_exact(binding.effect_key)
if action is None or action.action_id != intent.receipt_ref:
raise HostPatchIntegrationError("durable external action unavailable")
action = self._validate_action(action, binding, claim_id=intent.claim_id)
reader = self._authority_provider
authority = reader.read_for_action(binding, action) # type: ignore[attr-defined]
if not isinstance(authority, HostPatchAuthority):
raise HostPatchIntegrationError("invalid reconciliation authority readback")
action = self._validate_action(action, binding, authority=authority)
def revalidate() -> None:
fresh = reader.read_for_action(binding, action) # type: ignore[attr-defined]
if (
not isinstance(fresh, HostPatchAuthority)
or fresh.authorization_ref != authority.authorization_ref
or fresh.authority_key_id != authority.authority_key_id
or fresh.authority_key_fingerprint != authority.authority_key_fingerprint
or fresh.authority_record_sha256 != authority.authority_record_sha256
or fresh.authority_generation != authority.authority_generation
or fresh.task_generation != authority.task_generation
or fresh.claim_id != authority.claim_id
or fresh.claim_fence != authority.claim_fence
or fresh.expected_branch_oid != authority.expected_branch_oid
):
raise HostPatchIntegrationError(
"host authority changed before reconciliation call"
)
observed = self._transactions.reconcile_exact(
action_id=action.action_id,
binding=binding,
authority=authority,
before_external_call=revalidate,
)
observed = self._validate_action(observed, binding, authority=authority)
if observed.action_id != action.action_id:
raise HostPatchIntegrationError("immutable external action_id changed")
if observed.state != "COMMITTED":
raise HostPatchEffectUnknown("external effect remains unresolved")
__all__ = [
"HostPatchAction",
"HostPatchArtifact",
"HostPatchAuthority",
"HostPatchConfigurationError",
"HostPatchEffectUnknown",
"HostPatchExecutor",
"HostPatchIntegrationError",
"HostPatchTransactionOwner",
"HOST_PATCH_AUTHORITY_PURPOSE",
"HOST_PATCH_AUTHORITY_SCHEMA",
"SignedHostPatchAuthorityReader",
"require_exact_root_action_owner",
"signed_host_patch_authority_provider",
]
@@ -0,0 +1,113 @@
"""Authenticated host readback for independent verification and acceptance.
The host supplies separate, registered signing identities and current-record
transports. This adapter never creates receipts or treats an ``authenticated``
payload flag as evidence. Missing providers remain UNKNOWN through OperationReaders.
"""
from __future__ import annotations
from collections.abc import Mapping
from dataclasses import asdict, dataclass
from release_host_authority import VerifiedCurrentRecordReader
from .operation_view import OperationBinding, binding_digest, validate_digest
@dataclass(frozen=True)
class ReceiptAuthorityPolicy:
verifier_id: str
verifier_key_id: str
acceptor_id: str
acceptor_key_id: str
acceptance_revision: str
verifier_key_fingerprint: str
acceptor_key_fingerprint: str
def __post_init__(self) -> None:
for name, value in asdict(self).items():
if not isinstance(value, str) or not value or value.strip() != value or "\x00" in value:
raise ValueError(f"invalid {name}")
if self.verifier_id == self.acceptor_id or self.verifier_key_id == self.acceptor_key_id:
raise ValueError("verification and acceptance must use independent identities and keys")
validate_digest(self.verifier_key_fingerprint)
validate_digest(self.acceptor_key_fingerprint)
if self.verifier_key_fingerprint == self.acceptor_key_fingerprint:
raise ValueError("verification and acceptance require independent public keys")
class HostOperationReceiptReaders:
"""Bind verified transport signatures to host-pinned role identities.
Policies are trusted host configuration, never request or receipt fields.
Receipt/result linkage and freshness are also checked by collect_closure /
derive_closure against the separately observed Git effect.
"""
def __init__(
self,
*,
verifier_reader: VerifiedCurrentRecordReader,
acceptance_reader: VerifiedCurrentRecordReader,
policy: ReceiptAuthorityPolicy,
) -> None:
self._verifier = verifier_reader
self._acceptance = acceptance_reader
self._policy = policy
def _read(self, binding: OperationBinding, *, acceptance: bool) -> dict[str, object]:
policy = self._policy
identity = policy.acceptor_id if acceptance else policy.verifier_id
key_id = policy.acceptor_key_id if acceptance else policy.verifier_key_id
if binding.actor_id in (policy.verifier_id, policy.acceptor_id):
raise ValueError("executor cannot verify or accept its own operation")
kind = "acceptance" if acceptance else "verification"
query = {
"binding": asdict(binding),
"binding_digest": binding_digest(binding),
"expected_identity": identity,
"expected_key_id": key_id,
"expected_key_fingerprint": policy.acceptor_key_fingerprint if acceptance else policy.verifier_key_fingerprint,
}
if acceptance:
query["expected_acceptance_revision"] = policy.acceptance_revision
reader = self._acceptance if acceptance else self._verifier
verified = reader.read(f"operation-{kind}", query)
if verified.key_id != key_id:
raise ValueError("receipt signer does not match the pinned role key")
fingerprint = policy.acceptor_key_fingerprint if acceptance else policy.verifier_key_fingerprint
if verified.key_fingerprint != fingerprint:
raise ValueError("receipt signer public key fingerprint mismatch")
document = verified.record
if not isinstance(document, Mapping) or set(document) != {"schema", kind}:
raise ValueError("invalid signed receipt document")
schema = f"aios.operation-{kind}-observation.v1"
if document["schema"] != schema:
raise ValueError("invalid receipt schema")
observation = document[kind]
common = {"binding_digest", "receipt_ref", "result_artifact_digest", "outcome", "observed_at", "expires_at"}
fields = common | (
{"acceptor_id", "acceptance_revision", "verifier_receipt_ref"}
if acceptance else {"verifier_id", "verification_receipt_ref"}
)
if not isinstance(observation, Mapping) or set(observation) != fields:
raise ValueError("invalid signed receipt observation")
if observation["binding_digest"] != binding_digest(binding):
raise ValueError("receipt binding mismatch")
if observation["acceptor_id" if acceptance else "verifier_id"] != identity:
raise ValueError("receipt identity mismatch")
if acceptance and observation["acceptance_revision"] != policy.acceptance_revision:
raise ValueError("acceptance revision mismatch")
result: dict[str, object] = {
"schema": schema,
kind: {**observation, "authenticated": True},
}
if acceptance:
result["expected_acceptor_id"] = policy.acceptor_id
result["expected_acceptance_revision"] = policy.acceptance_revision
return result
def verifier(self, binding: OperationBinding) -> Mapping[str, object]:
return self._read(binding, acceptance=False)
def acceptance(self, binding: OperationBinding) -> Mapping[str, object]:
return self._read(binding, acceptance=True)
@@ -0,0 +1,990 @@
"""Live AIOS protocol adapters for governed-writer execution.
This module closes the gap between the generic governed-writer model and the
live field contracts observed on the V2R2C3 control plane:
- agent-task-api :18940
* POST /v1/agents/register
* GET /v1/tasks/{task_id}
* POST /v1/tasks/{task_id}/claim -> lease_token
* POST /v1/tasks/{task_id}/heartbeat
- task-principal-issuer :18946
* POST /v1/task-principal/assertions
* Authorization: TaskLease <lease-token>
* one assertion per concrete operation + bridge_request
- bridge :18892
* GET /v2/runtime
* POST /v2/writer-lease
* POST /v2/commit-files
* Authorization: TaskPrincipal <operation-assertion>
WriterLease and commit-files share one continuation idempotency key.
The module never prints or returns the agent token, lease token or TaskPrincipal
assertion. Secret values are kept only in an in-memory handle store.
"""
from __future__ import annotations
import base64
import hashlib
import json
import os
import re
import secrets
import subprocess
import time
import urllib.error
import urllib.parse
import urllib.request
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Callable, Mapping, Sequence
from app.task_plane.governed_writer_automation import (
AuthorizationDenied,
GovernedWriteReadiness,
GovernedWriterRequest,
OwnerAuthorizationPolicy,
)
class LiveProtocolError(RuntimeError):
pass
class LiveProtocolAuthError(LiveProtocolError):
pass
class LiveProtocolConflict(LiveProtocolError):
pass
class LiveProtocolUnavailable(LiveProtocolError):
pass
class IssuerResponseError(LiveProtocolError):
"""Safe, structured issuer failure metadata for isolated diagnostics."""
def __init__(self, message: str, *, status: int, error_code: str | None,
operation: str) -> None:
super().__init__(message)
self.status = status
self.error_code = error_code
self.operation = operation
@dataclass(frozen=True)
class HttpResult:
status: int
body: Mapping[str, Any]
class JsonHttpTransport:
"""Small stdlib JSON transport. Authorization values are never logged."""
def request(
self,
method: str,
url: str,
*,
body: Mapping[str, Any] | None = None,
headers: Mapping[str, str] | None = None,
timeout: float = 20.0,
) -> HttpResult:
payload = None
req_headers = {"Accept": "application/json"}
if body is not None:
payload = json.dumps(
dict(body),
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
allow_nan=False,
).encode("utf-8")
req_headers["Content-Type"] = "application/json"
if headers:
req_headers.update({str(k): str(v) for k, v in headers.items()})
req = urllib.request.Request(
url,
data=payload,
headers=req_headers,
method=method.upper(),
)
try:
with urllib.request.urlopen(req, timeout=timeout) as resp:
raw = resp.read()
status = int(resp.status)
except urllib.error.HTTPError as exc:
raw = exc.read()
status = int(exc.code)
except (OSError, urllib.error.URLError) as exc:
raise LiveProtocolUnavailable(
f"{method.upper()} {urllib.parse.urlsplit(url).path}: transport unavailable"
) from exc
if not raw:
obj: Any = {}
else:
try:
obj = json.loads(raw.decode("utf-8"))
except Exception as exc:
raise LiveProtocolError(
f"{method.upper()} {urllib.parse.urlsplit(url).path}: non-JSON response"
) from exc
if not isinstance(obj, Mapping):
raise LiveProtocolError(
f"{method.upper()} {urllib.parse.urlsplit(url).path}: response must be object"
)
return HttpResult(status=status, body=dict(obj))
@dataclass(frozen=True)
class LiveProtocolConfig:
bridge_url: str = "http://127.0.0.1:18892"
agent_task_url: str = "http://127.0.0.1:18940"
principal_issuer_url: str = "http://127.0.0.1:18946"
control_token_file: str = (
"/run/credentials/aios-agent-task-api.service/control_token"
)
authority_git_dir: str = "/data/aios-gitops/repo.git"
issue_schema: str = "aios.task-principal-issue.v1"
agent_type: str = "aios.p0-task-admission-worker"
agent_version: str = "governed-writer-live-v1"
lease_seconds: int = 900
assertion_ttl_seconds: int = 60
minimum_remaining_window_seconds: int = 240
def normalized(self) -> "LiveProtocolConfig":
return LiveProtocolConfig(
bridge_url=self.bridge_url.rstrip("/"),
agent_task_url=self.agent_task_url.rstrip("/"),
principal_issuer_url=self.principal_issuer_url.rstrip("/"),
control_token_file=self.control_token_file,
authority_git_dir=self.authority_git_dir,
issue_schema=self.issue_schema,
agent_type=self.agent_type,
agent_version=self.agent_version,
lease_seconds=int(self.lease_seconds),
assertion_ttl_seconds=int(self.assertion_ttl_seconds),
minimum_remaining_window_seconds=int(
self.minimum_remaining_window_seconds
),
)
class SecretHandleStore:
"""Process-local secret container.
Public receipts contain handles only; secret values are not serialized.
"""
def __init__(self) -> None:
self._values: dict[str, str] = {}
def put(self, kind: str, value: str) -> str:
if not isinstance(value, str) or not value:
raise LiveProtocolAuthError(f"{kind} secret missing")
handle = f"{kind}:{secrets.token_hex(12)}"
self._values[handle] = value
return handle
def get(self, handle: str) -> str:
try:
return self._values[handle]
except KeyError as exc:
raise LiveProtocolAuthError("secret handle not available") from exc
def drop(self, handle: str) -> None:
self._values.pop(handle, None)
@dataclass(frozen=True)
class LiveTask:
task_id: str
status: str
revision: int
deadline_epoch: float | None
raw: Mapping[str, Any]
@dataclass(frozen=True)
class LiveClaim:
task_id: str
claim_ref: str
server_claim_id: str | None
lease_handle: str
fencing_generation: int | None
lease_expires_at: float | None
task_revision: int | None
public_receipt: Mapping[str, Any] = field(default_factory=dict)
@dataclass(frozen=True)
class LiveWriterLease:
# /v2/writer-lease is a validated binding projection. The durable branch
# lease is created only by the continuation executor during commit.
writer_lease_id: str | None
task_id: str
job_id: str
branch_mode: str
expected_branch_oid: str
generation: int
expires_at: float | None
continuation_key: str
raw: Mapping[str, Any]
@dataclass(frozen=True)
class LiveWriterSession:
authorization_id: str
task_id: str
agent_id: str
claim_ref: str
job_id: str
branch_mode: str
expected_branch_oid: str
writer_lease_id: str | None
continuation_key: str
principal_issue_schema: str
agent_token_handle: str = field(repr=False)
lease_handle: str = field(repr=False)
task_lease_receipt: Mapping[str, Any] = field(default_factory=dict)
writer_lease_binding: Mapping[str, Any] = field(default_factory=dict)
def public_dict(self) -> dict[str, Any]:
public = {
"authorization_id": self.authorization_id,
"task_id": self.task_id,
"agent_id": self.agent_id,
"claim_ref": self.claim_ref,
"job_id": self.job_id,
"branch_mode": self.branch_mode,
"expected_branch_oid": self.expected_branch_oid,
"continuation_key": self.continuation_key,
"principal_issue_schema": self.principal_issue_schema,
"writer_lease_binding": dict(self.writer_lease_binding),
}
return public
def _parse_epoch(value: Any) -> float | None:
if value is None:
return None
if isinstance(value, (int, float)):
return float(value)
if isinstance(value, str):
text = value.strip()
if not text:
return None
try:
return float(text)
except ValueError:
pass
# ISO-8601 minimal support.
import datetime as dt
try:
return dt.datetime.fromisoformat(text.replace("Z", "+00:00")).timestamp()
except Exception:
return None
return None
def _safe_ref_from_token(token: str, prefix: str) -> str:
return f"{prefix}:sha256:{hashlib.sha256(token.encode('utf-8')).hexdigest()[:24]}"
class LiveAgentTaskClient:
def __init__(
self,
config: LiveProtocolConfig,
*,
transport: JsonHttpTransport | None = None,
secrets_store: SecretHandleStore | None = None,
) -> None:
self.config = config.normalized()
self.transport = transport or JsonHttpTransport()
self.secrets = secrets_store or SecretHandleStore()
def _control_token(self) -> str:
path = Path(self.config.control_token_file)
try:
token = path.read_text(encoding="utf-8").strip()
except OSError as exc:
raise LiveProtocolAuthError(
"agent-task control token file unavailable"
) from exc
if len(token) < 8:
raise LiveProtocolAuthError("agent-task control token invalid")
return token
def _control_headers(self) -> dict[str, str]:
return {"Authorization": "Bearer " + self._control_token()}
def read_task(self, task_id: str) -> LiveTask:
quoted = urllib.parse.quote(task_id, safe="")
result = self.transport.request(
"GET",
f"{self.config.agent_task_url}/v1/tasks/{quoted}",
headers=self._control_headers(),
timeout=15,
)
if result.status == 404:
raise LiveProtocolConflict("task not found")
if result.status not in (200, 201):
raise LiveProtocolError(f"task read failed HTTP {result.status}")
raw = result.body
task_obj = raw.get("task") if isinstance(raw.get("task"), Mapping) else raw
status = str(task_obj.get("status") or "")
if not status:
raise LiveProtocolError("task readback missing status")
revision = int(task_obj.get("revision") or 0)
return LiveTask(
task_id=str(task_obj.get("task_id") or task_id),
status=status,
revision=revision,
deadline_epoch=_parse_epoch(task_obj.get("deadline")),
raw=dict(task_obj),
)
def register_agent(
self,
*,
task_id: str,
capabilities: Sequence[str],
route: str,
agent_id: str | None = None,
) -> tuple[str, str]:
if agent_id is None:
agent_id = (
"governed-writer-"
+ hashlib.sha256(
(task_id + "\0" + str(time.time_ns())).encode("utf-8")
).hexdigest()[:20]
)
body = {
"agent_id": agent_id,
"agent_type": self.config.agent_type,
"agent_version": self.config.agent_version,
"capabilities": list(capabilities),
"metadata": {"task_id": task_id, "route": route},
}
result = self.transport.request(
"POST",
f"{self.config.agent_task_url}/v1/agents/register",
body=body,
headers=self._control_headers(),
timeout=15,
)
if result.status not in (200, 201):
raise LiveProtocolError(f"agent register failed HTTP {result.status}")
token = result.body.get("agent_token")
if not isinstance(token, str) or len(token) < 8:
raise LiveProtocolAuthError("agent register returned no usable token")
return agent_id, self.secrets.put("agent-token", token)
def claim(
self,
*,
task_id: str,
agent_token_handle: str,
lease_seconds: int | None = None,
) -> LiveClaim:
lease_seconds = int(lease_seconds or self.config.lease_seconds)
quoted = urllib.parse.quote(task_id, safe="")
token = self.secrets.get(agent_token_handle)
result = self.transport.request(
"POST",
f"{self.config.agent_task_url}/v1/tasks/{quoted}/claim",
body={"lease_seconds": lease_seconds},
headers={"Authorization": "Bearer " + token},
timeout=15,
)
if result.status == 409:
raise LiveProtocolConflict(
str(result.body.get("error") or "task not claimable")
)
if result.status not in (200, 201):
raise LiveProtocolError(f"claim failed HTTP {result.status}")
if result.body.get("status") != "CLAIMED":
raise LiveProtocolError("claim response not CLAIMED")
lease_token = result.body.get("lease_token")
if not isinstance(lease_token, str) or len(lease_token) < 8:
raise LiveProtocolAuthError("claim returned no lease token")
server_claim_id = result.body.get("claim_id")
lease_id = result.body.get("lease_id")
public_ref = None
for candidate in (server_claim_id, lease_id):
if isinstance(candidate, str) and candidate:
public_ref = candidate
break
if public_ref is None:
public_ref = _safe_ref_from_token(lease_token, "claim")
lease_handle = self.secrets.put("task-lease", lease_token)
fencing = result.body.get("fencing_generation")
return LiveClaim(
task_id=task_id,
claim_ref=public_ref,
server_claim_id=(
str(server_claim_id)
if isinstance(server_claim_id, str) and server_claim_id
else None
),
lease_handle=lease_handle,
fencing_generation=int(fencing) if fencing is not None else None,
lease_expires_at=_parse_epoch(result.body.get("lease_expires_at")),
task_revision=(
int(result.body["receipt_revision"])
if result.body.get("receipt_revision") is not None
else None
),
public_receipt={k: result.body[k] for k in (
"task_id", "status", "claim_id", "lease_id", "fencing_generation",
"receipt_revision", "lease_expires_at", "attempt", "revision",
) if k in result.body},
)
def heartbeat(
self,
*,
claim: LiveClaim,
agent_token_handle: str,
phase: str,
lease_seconds: int | None = None,
) -> LiveTask:
lease_seconds = int(lease_seconds or self.config.lease_seconds)
quoted = urllib.parse.quote(claim.task_id, safe="")
agent_token = self.secrets.get(agent_token_handle)
lease_token = self.secrets.get(claim.lease_handle)
result = self.transport.request(
"POST",
f"{self.config.agent_task_url}/v1/tasks/{quoted}/heartbeat",
body={
"lease_token": lease_token,
"lease_seconds": lease_seconds,
"progress": {"phase": phase, "status": "running"},
},
headers={"Authorization": "Bearer " + agent_token},
timeout=15,
)
if result.status not in (200, 201):
raise LiveProtocolError(f"heartbeat failed HTTP {result.status}")
if result.body.get("status") not in ("RUNNING", "CLAIMED"):
raise LiveProtocolError("heartbeat did not yield RUNNING/CLAIMED")
task = self.read_task(claim.task_id)
if task.status not in ("RUNNING", "CLAIMED"):
raise LiveProtocolConflict(
f"authoritative task state after heartbeat is {task.status}"
)
return task
def submit_result(
self,
*,
task_id: str,
agent_id: str,
agent_token_handle: str,
lease_handle: str,
idempotency_key: str,
result_value: Mapping[str, Any],
) -> Mapping[str, Any]:
if not isinstance(idempotency_key, str) or not 1 <= len(idempotency_key) <= 160:
raise LiveProtocolError("task result idempotency key invalid")
if not isinstance(result_value, Mapping):
raise LiveProtocolError("task result mapping required")
quoted = urllib.parse.quote(task_id, safe="")
agent_token = self.secrets.get(agent_token_handle)
lease_token = self.secrets.get(lease_handle)
result = self.transport.request(
"POST",
f"{self.config.agent_task_url}/v1/tasks/{quoted}/result",
body={
"lease_token": lease_token,
"idempotency_key": idempotency_key,
"result": dict(result_value),
},
headers={"Authorization": "Bearer " + agent_token},
timeout=15,
)
if result.status == 409:
raise LiveProtocolConflict(
str(result.body.get("error") or "task result conflict")
)
if result.status not in (200, 201):
raise LiveProtocolError(f"task result HTTP {result.status}")
if (
result.body.get("task_id") != task_id
or result.body.get("status") != "VERIFYING"
or result.body.get("claimed_by") != agent_id
or result.body.get("result") != dict(result_value)
):
raise LiveProtocolConflict("task result readback mismatch")
return dict(result.body)
class LiveTaskPrincipalClient:
def __init__(
self,
config: LiveProtocolConfig,
*,
transport: JsonHttpTransport | None = None,
secrets_store: SecretHandleStore,
) -> None:
self.config = config.normalized()
self.transport = transport or JsonHttpTransport()
self.secrets = secrets_store
self.public_receipts: list[dict[str, Any]] = []
def issue_assertion(
self,
*,
task_id: str,
lease_handle: str,
operation: str,
bridge_request: Mapping[str, Any],
request_id: str,
) -> str:
lease_token = self.secrets.get(lease_handle)
body = {
"schema": self.config.issue_schema,
"request_id": request_id,
"task_id": task_id,
"operation": operation,
"bridge_request": dict(bridge_request),
"requested_ttl_seconds": self.config.assertion_ttl_seconds,
}
last: HttpResult | None = None
for attempt in range(2):
result = self.transport.request(
"POST",
f"{self.config.principal_issuer_url}/v1/task-principal/assertions",
body=body,
headers={"Authorization": "TaskLease " + lease_token},
timeout=20,
)
last = result
assertion = result.body.get("assertion")
if result.status in (200, 201) and isinstance(assertion, str) and assertion:
self.public_receipts.append({
"task_id": task_id, "operation": operation, "request_id": request_id,
"assertion_sha256": hashlib.sha256(assertion.encode()).hexdigest(),
"issuer_response": {k: result.body[k] for k in ("jti", "expires_at", "replayed") if k in result.body},
})
return assertion
if not (
attempt == 0
and result.status == 503
and result.body.get("error") == "TASK_PRINCIPAL_ISSUER_UNAVAILABLE"
):
break
time.sleep(1)
status = 0 if last is None else last.status
raw_code = None if last is None else last.body.get("error")
error_code = (
raw_code if isinstance(raw_code, str)
and re.fullmatch(r"(?:TASK_PRINCIPAL|EXECUTION_PLAN|GOVERNED)_[A-Z0-9_]{3,120}", raw_code) else None
)
raise IssuerResponseError(
f"TaskPrincipal assertion issue failed operation={operation} HTTP {status}",
status=status, error_code=error_code, operation=operation,
)
class LiveGitReader:
def __init__(self, git_dir: str) -> None:
self.git_dir = str(git_dir)
def branch_head(self, branch: str) -> str | None:
proc = subprocess.run(
["git", "--git-dir", self.git_dir, "rev-parse", f"refs/heads/{branch}"],
stdout=subprocess.PIPE,
stderr=subprocess.DEVNULL,
text=True,
timeout=15,
)
if proc.returncode != 0:
return None
head = proc.stdout.strip()
if len(head) != 40 or any(c not in "0123456789abcdef" for c in head):
raise LiveProtocolError("git branch HEAD is not a full OID")
return head
def file_sha256(self, oid: str, path: str) -> str:
proc = subprocess.run(
["git", "--git-dir", self.git_dir, "show", f"{oid}:{path}"],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
timeout=20,
)
if proc.returncode != 0:
raise LiveProtocolError(f"git readback failed for {path}")
return hashlib.sha256(proc.stdout).hexdigest()
class LiveBridgeClient:
def __init__(
self,
config: LiveProtocolConfig,
*,
transport: JsonHttpTransport | None = None,
principal_client: LiveTaskPrincipalClient,
) -> None:
self.config = config.normalized()
self.transport = transport or JsonHttpTransport()
self.principal_client = principal_client
def runtime_status(self) -> Mapping[str, Any]:
result = self.transport.request(
"GET", f"{self.config.bridge_url}/v2/runtime", timeout=10
)
if result.status != 200:
raise LiveProtocolUnavailable(f"bridge runtime HTTP {result.status}")
return result.body
def acquire_writer_lease(
self,
*,
task_id: str,
job_id: str,
lease_handle: str,
repository: str,
target_branch: str,
generation: int,
base_oid: str,
expected_branch_oid: str,
branch_mode: str,
continuation_key: str,
) -> LiveWriterLease:
req = {
"schema": "aios.task-writer-lease.v2",
"task_id": task_id,
"job_id": job_id,
"idempotency_key": continuation_key,
"repository": repository,
"branch_mode": branch_mode,
"base_oid": base_oid,
"expected_branch_oid": expected_branch_oid,
"target_branch": target_branch,
"generation": int(generation),
"lease_seconds": int(self.config.lease_seconds),
}
assertion = self.principal_client.issue_assertion(
task_id=task_id,
lease_handle=lease_handle,
operation="acquireWriterLease",
bridge_request=req,
request_id="writer-lease-" + secrets.token_hex(8),
)
result = self.transport.request(
"POST",
f"{self.config.bridge_url}/v2/writer-lease",
body=req,
headers={"Authorization": "TaskPrincipal " + assertion},
timeout=30,
)
if result.status not in (200, 201):
raise LiveProtocolError(f"writer lease HTTP {result.status}")
body = result.body
expected_binding = {
"schema": "aios.task-writer-lease.v2",
"task_id": task_id,
"repository": repository,
"base_oid": base_oid,
"expected_branch_oid": expected_branch_oid,
"target_branch": target_branch,
"generation": int(generation),
"lease_seconds": int(self.config.lease_seconds),
}
for field, expected in expected_binding.items():
if body.get(field) != expected:
raise LiveProtocolConflict(f"writer lease {field} mismatch")
projected_branch_mode = body.get("branch_mode")
allowed_branch_modes = {branch_mode}
# CREATE_NEW is materialized by the Bridge before it returns the
# descriptor, so the returned binding is CONTINUE_EXISTING.
if branch_mode == "CREATE_NEW":
allowed_branch_modes.add("CONTINUE_EXISTING")
if projected_branch_mode not in allowed_branch_modes:
raise LiveProtocolConflict("writer lease branch_mode mismatch")
return LiveWriterLease(
writer_lease_id=None,
task_id=task_id,
job_id=job_id,
branch_mode=str(projected_branch_mode),
expected_branch_oid=expected_branch_oid,
generation=int(generation),
expires_at=None,
continuation_key=continuation_key,
raw=dict(body),
)
def commit_files(
self,
*,
session: LiveWriterSession,
repository: str,
target_branch: str,
generation: int,
files: Mapping[str, bytes],
message: str,
) -> Mapping[str, Any]:
entries = [
{
"path": path,
"content_base64": base64.b64encode(data).decode("ascii"),
}
for path, data in files.items()
]
req = {
"task_id": session.task_id,
"job_id": session.job_id,
"repository": repository,
"expected_branch_oid": session.expected_branch_oid,
"target_branch": target_branch,
"generation": int(generation),
"idempotency_key": session.continuation_key,
"files": entries,
"message": message,
}
assertion = self.principal_client.issue_assertion(
task_id=session.task_id,
lease_handle=session.lease_handle,
operation="commitFilesGoverned",
bridge_request=req,
request_id="commit-files-" + secrets.token_hex(8),
)
result = self.transport.request(
"POST",
f"{self.config.bridge_url}/v2/commit-files",
body=req,
headers={"Authorization": "TaskPrincipal " + assertion},
timeout=45,
)
if result.status not in (200, 201):
raise LiveProtocolError(f"commit-files HTTP {result.status}")
return dict(result.body)
class LiveGovernedWriterExecutor:
"""Execute the real post-admission writer chain.
Prerequisites:
- Owner authorization was already verified by the caller/authority.
- A fresh admission task exists and is QUEUED.
- The task's execution plan already binds the supplied job_id.
- A valid delegation epoch/admission window is already active.
This executor deliberately does not create/rewrite admission plans or sign
Owner epochs. Those remain in the existing admission/signer authority.
"""
def __init__(
self,
*,
config: LiveProtocolConfig,
owner_policy: OwnerAuthorizationPolicy,
authorization_ref: str,
transport: JsonHttpTransport | None = None,
agent_id: str | None = None,
scope_enrollment: Callable[..., Mapping[str, Any]] | None = None,
) -> None:
self.config = config.normalized()
self.owner_policy = owner_policy
self.authorization_ref = authorization_ref
if scope_enrollment is not None and (not callable(scope_enrollment) or not agent_id):
raise ValueError("scope enrollment requires a configured agent identity")
self.agent_id = agent_id
self.scope_enrollment = scope_enrollment
self.transport = transport or JsonHttpTransport()
self.secret_store = SecretHandleStore()
self.agent = LiveAgentTaskClient(
self.config,
transport=self.transport,
secrets_store=self.secret_store,
)
self.principals = LiveTaskPrincipalClient(
self.config,
transport=self.transport,
secrets_store=self.secret_store,
)
self.bridge = LiveBridgeClient(
self.config,
transport=self.transport,
principal_client=self.principals,
)
self.git = LiveGitReader(self.config.authority_git_dir)
def _assert_owner_scope(self, request: GovernedWriterRequest) -> None:
if self.authorization_ref != self.owner_policy.authorization_id:
raise AuthorizationDenied("authorization_ref does not match verified policy")
self.owner_policy.validate(request, now=time.time())
def acquire(
self,
*,
request: GovernedWriterRequest,
job_id: str,
route: str = "owner-authorized-governed-writer",
) -> LiveWriterSession:
self._assert_owner_scope(request)
runtime = self.bridge.runtime_status()
readiness = GovernedWriteReadiness.from_runtime_status(runtime)
if not readiness.governed_write_path_open:
raise LiveProtocolConflict(
"governed write path blocked: " + ",".join(readiness.blockers)
)
if readiness.legacy_global_writes_enabled:
raise LiveProtocolConflict("legacy/global writes must remain disabled")
task = self.agent.read_task(request.task_id)
if task.status != "QUEUED":
raise LiveProtocolConflict(
f"task must be QUEUED before claim; live={task.status}"
)
now = time.time()
if (
task.deadline_epoch is not None
and task.deadline_epoch - now
< self.config.minimum_remaining_window_seconds
):
raise LiveProtocolConflict("task deadline too close to safely claim")
agent_id, agent_token_handle = self.agent.register_agent(
task_id=request.task_id,
capabilities=sorted(request.required_capabilities),
route=route,
agent_id=self.agent_id,
)
claim = self.agent.claim(
task_id=request.task_id,
agent_token_handle=agent_token_handle,
)
running = self.agent.heartbeat(
claim=claim,
agent_token_handle=agent_token_handle,
phase=route,
)
if (
running.deadline_epoch is not None
and running.deadline_epoch - time.time()
< self.config.minimum_remaining_window_seconds
):
raise LiveProtocolConflict(
"remaining governed task window too short after heartbeat"
)
if self.scope_enrollment is not None:
# The existing issuer and Bridge still verify every assertion.
# Enrollment only happens after a real claim has established the
# agent/attempt/lease binding; failures stop before any assertion.
enrollment = self.scope_enrollment(
request=request, job_id=job_id, agent_id=agent_id,
claim=claim, task=running,
)
if (not isinstance(enrollment, Mapping)
or enrollment.get("status") != "ENROLLED"
or enrollment.get("task_id") != request.task_id
or enrollment.get("agent_id") != agent_id
or enrollment.get("job_id") != job_id):
raise LiveProtocolConflict("verified principal scope enrollment required")
live_head = self.git.branch_head(request.target_branch)
if live_head is None:
branch_mode = "CREATE_NEW"
expected_branch_oid = request.base_oid
else:
branch_mode = "CONTINUE_EXISTING"
expected_branch_oid = live_head
continuation_key = (
"gw-cont-"
+ hashlib.sha256(
(request.task_id + "\0" + job_id).encode("utf-8")
).hexdigest()[:32]
)
writer = self.bridge.acquire_writer_lease(
task_id=request.task_id,
job_id=job_id,
lease_handle=claim.lease_handle,
repository=request.repository,
target_branch=request.target_branch,
generation=request.generation,
base_oid=request.base_oid,
expected_branch_oid=expected_branch_oid,
branch_mode=branch_mode,
continuation_key=continuation_key,
)
return LiveWriterSession(
authorization_id=self.authorization_ref,
task_id=request.task_id,
agent_id=agent_id,
claim_ref=claim.claim_ref,
job_id=job_id,
branch_mode=writer.branch_mode,
expected_branch_oid=writer.expected_branch_oid,
writer_lease_id=writer.writer_lease_id,
continuation_key=writer.continuation_key,
principal_issue_schema=self.config.issue_schema,
agent_token_handle=agent_token_handle,
lease_handle=claim.lease_handle,
task_lease_receipt=dict(claim.public_receipt),
writer_lease_binding=dict(writer.raw),
)
def commit_and_readback(
self,
*,
session: LiveWriterSession,
request: GovernedWriterRequest,
files: Mapping[str, bytes],
expected_sha256: Mapping[str, str],
message: str,
) -> Mapping[str, Any]:
self._assert_owner_scope(request)
if set(files) != set(expected_sha256):
raise LiveProtocolConflict("commit file set differs from expected SHA set")
for path, data in files.items():
got = hashlib.sha256(data).hexdigest()
if got != expected_sha256[path]:
raise LiveProtocolConflict(f"pre-commit SHA mismatch: {path}")
response = self.bridge.commit_files(
session=session,
repository=request.repository,
target_branch=request.target_branch,
generation=request.generation,
files=files,
message=message,
)
new_head = self.git.branch_head(request.target_branch)
if new_head is None or new_head == session.expected_branch_oid:
raise LiveProtocolConflict("branch did not advance after governed commit")
readback = {
path: self.git.file_sha256(new_head, path) for path in expected_sha256
}
if readback != dict(expected_sha256):
raise LiveProtocolConflict("governed commit readback SHA mismatch")
return {
"commit_response": response,
"branch_head": new_head,
"readback_sha256": readback,
}
def submit_task_result(
self,
*,
session: LiveWriterSession,
result: Mapping[str, Any],
idempotency_key: str,
) -> Mapping[str, Any]:
return self.agent.submit_result(
task_id=session.task_id,
agent_id=session.agent_id,
agent_token_handle=session.agent_token_handle,
lease_handle=session.lease_handle,
idempotency_key=idempotency_key,
result_value=result,
)
+99 -7
View File
@@ -4,6 +4,7 @@ from __future__ import annotations
import hashlib
import json
import math
import re
import time
import uuid
@@ -74,7 +75,13 @@ def _now_ts() -> float:
def canonical_json(value: Any) -> str:
return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
return json.dumps(
value,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
allow_nan=False,
)
def compute_sha256_hex(value: Any) -> str:
@@ -532,10 +539,16 @@ class EvidenceEnvelope:
worker_identity: str
payload: dict[str, Any]
created_at: float
expires_at: float
previous_evidence_hash: str
evidence_digest: str
signature: str | None = None
attestation_ref: str | None = None
signature_algorithm: str | None = None
signing_key_id: str | None = None
verifier_identity: str | None = None
authority_domain: str | None = None
sequence: int | None = None
@classmethod
def create(
@@ -551,7 +564,17 @@ class EvidenceEnvelope:
attestation_ref: str | None = None,
evidence_id: str | None = None,
created_at: float | None = None,
expires_at: float | None = None,
signature_algorithm: str | None = None,
signing_key_id: str | None = None,
verifier_identity: str | None = None,
authority_domain: str | None = None,
sequence: int | None = None,
) -> "EvidenceEnvelope":
issued_at = float(_now_ts() if created_at is None else created_at)
valid_until = float(issued_at + 300.0 if expires_at is None else expires_at)
if not math.isfinite(issued_at) or not math.isfinite(valid_until):
raise ValueError("evidence timestamps must be finite")
envelope = cls(
schema_version=1,
evidence_id=evidence_id or f"evd-{uuid.uuid4()}",
@@ -560,11 +583,17 @@ class EvidenceEnvelope:
kind=validate_type(kind, "evidence kind"),
worker_identity=str(worker_identity),
payload=redact_value(payload),
created_at=float(created_at or _now_ts()),
created_at=issued_at,
expires_at=valid_until,
previous_evidence_hash=str(previous_evidence_hash or ""),
evidence_digest="",
signature=signature,
attestation_ref=attestation_ref,
signature_algorithm=signature_algorithm,
signing_key_id=signing_key_id,
verifier_identity=verifier_identity,
authority_domain=authority_domain,
sequence=sequence,
)
return dataclass_replace(
envelope, evidence_digest=compute_sha256_hex(envelope.digest_payload())
@@ -580,8 +609,14 @@ class EvidenceEnvelope:
"worker_identity": self.worker_identity,
"payload": self.payload,
"created_at": self.created_at,
"expires_at": self.expires_at,
"previous_evidence_hash": self.previous_evidence_hash,
"attestation_ref": self.attestation_ref,
"signature_algorithm": self.signature_algorithm,
"signing_key_id": self.signing_key_id,
"verifier_identity": self.verifier_identity,
"authority_domain": self.authority_domain,
"sequence": self.sequence,
}
def verify(
@@ -591,18 +626,40 @@ class EvidenceEnvelope:
expected_claim_id: str,
expected_previous_hash: str,
attestation_verifier: Any = None,
now: float | None = None,
) -> bool:
try:
observed_at = float(_now_ts() if now is None else now)
finite_timestamps = all(
math.isfinite(value)
for value in (observed_at, self.created_at, self.expires_at)
)
expected_digest = compute_sha256_hex(self.digest_payload())
except (TypeError, ValueError, OverflowError):
return False
if (
self.schema_version != 1
not finite_timestamps
or self.schema_version != 1
or self.task_id != expected_task_id
or self.claim_id != expected_claim_id
or self.previous_evidence_hash != expected_previous_hash
or self.evidence_digest != compute_sha256_hex(self.digest_payload())
or self.created_at > observed_at
or self.expires_at <= observed_at
or self.expires_at <= self.created_at
or self.evidence_digest != expected_digest
):
return False
if self.signature or self.attestation_ref:
return True if attestation_verifier is None else bool(attestation_verifier(self))
return True
# An unsigned envelope has no proof of origin and must never verify.
if not (self.signature or self.attestation_ref):
return False
# Every proof form requires an injected verifier. A reference or
# signature must never become trusted merely because it is present.
if attestation_verifier is None:
return False
try:
return bool(attestation_verifier(self))
except Exception:
return False
def to_dict(self) -> dict[str, Any]:
return {
@@ -611,6 +668,41 @@ class EvidenceEnvelope:
"signature": self.signature,
}
@classmethod
def from_dict(cls, value: dict[str, Any]) -> "EvidenceEnvelope":
if not isinstance(value, dict):
raise ValueError("evidence envelope must be an object")
try:
envelope = cls(
schema_version=int(value["schema_version"]),
evidence_id=validate_identifier(value["evidence_id"], "evidence_id"),
task_id=validate_identifier(value["task_id"], "task_id"),
claim_id=validate_identifier(value["claim_id"], "claim_id"),
kind=validate_type(value["kind"], "evidence kind"),
worker_identity=str(value["worker_identity"]),
payload=redact_value(value["payload"]),
created_at=float(value["created_at"]),
expires_at=float(value["expires_at"]),
previous_evidence_hash=str(value["previous_evidence_hash"]),
evidence_digest=str(value["evidence_digest"]),
signature=value.get("signature"),
attestation_ref=value.get("attestation_ref"),
signature_algorithm=value.get("signature_algorithm"),
signing_key_id=value.get("signing_key_id"),
verifier_identity=value.get("verifier_identity"),
authority_domain=value.get("authority_domain"),
sequence=(
None if value.get("sequence") is None else int(value["sequence"])
),
)
if not math.isfinite(envelope.created_at) or not math.isfinite(
envelope.expires_at
):
raise ValueError("evidence timestamps must be finite")
return envelope
except (KeyError, TypeError, ValueError) as exc:
raise ValueError("invalid evidence envelope") from exc
class ProbeOutcome(StrEnum):
NOT_APPLIED = "NOT_APPLIED"
@@ -0,0 +1,480 @@
"""Strict adapters from existing authority readers into C6 observations.
Readers are injected by the trusted composition root. They are query-only:
this module has no controller/reconcile, signer, store, or network factory.
"""
from __future__ import annotations
from collections.abc import Callable, Mapping, Sequence
from dataclasses import dataclass
from datetime import datetime
from typing import Any
from .operation_view import (
AcceptanceObservation,
ClosureObservation,
DomainStamp,
EffectObservation,
Fact,
OperationBinding,
OperationObservation,
VerificationObservation,
binding_digest,
)
Reader = Callable[[OperationBinding], Mapping[str, object]]
_STAMP_FIELDS = frozenset({"domain", "object_id", "generation", "revision", "event_id"})
_FACT_FIELDS = frozenset(
{"value", "stamp", "binding_digest", "observed_at", "expires_at", "evidence_refs"}
)
_DECLARATION_FIELDS = frozenset({"schema", "declared", "installed", "callable"})
_TASK_FIELDS = frozenset({"schema", "preconditions"})
_AUTHORIZATION_FIELDS = frozenset({"schema", "authorized"})
_EFFECT_FIELDS = frozenset(
{
"binding_digest",
"outcome",
"receipt_ref",
"result_artifact_digest",
"actor_id",
"stamp",
"observed_at",
"expires_at",
"trust_status",
}
)
_GITOPS_FIELDS = frozenset({"schema", "effect"})
_VERIFICATION_FIELDS = frozenset(
{
"binding_digest",
"receipt_ref",
"result_artifact_digest",
"verifier_id",
"outcome",
"authenticated",
"observed_at",
"expires_at",
"verification_receipt_ref",
}
)
_VERIFIER_FIELDS = frozenset({"schema", "verification"})
_ACCEPTANCE_OBSERVATION_FIELDS = frozenset(
{
"binding_digest",
"receipt_ref",
"result_artifact_digest",
"verifier_receipt_ref",
"acceptance_revision",
"acceptor_id",
"outcome",
"authenticated",
"observed_at",
"expires_at",
}
)
_ACCEPTANCE_FIELDS = frozenset(
{
"schema",
"acceptance",
"expected_acceptor_id",
"expected_acceptance_revision",
}
)
@dataclass(frozen=True)
class OperationReaders:
declaration: Reader | None
task: Reader | None
authorization: Reader | None
gitops: Reader | None
verifier: Reader | None
acceptance: Reader | None
def __post_init__(self) -> None:
for name in (
"declaration",
"task",
"authorization",
"gitops",
"verifier",
"acceptance",
):
value = getattr(self, name)
if value is not None and not callable(value):
raise ValueError(f"{name} reader must be callable or None")
def _exact_mapping(value: object, fields: frozenset[str], name: str) -> Mapping[str, object]:
if not isinstance(value, Mapping) or set(value) != fields:
raise ValueError(f"{name} has unknown or missing fields")
if any(not isinstance(key, str) for key in value):
raise ValueError(f"{name} keys must be strings")
return value
def _text(value: object, name: str) -> str:
if not isinstance(value, str) or not value or value != value.strip() or "\x00" in value:
raise ValueError(f"{name} must be a canonical non-empty string")
return value
def _optional_text(value: object, name: str) -> str | None:
if value is None:
return None
return _text(value, name)
def _datetime(value: object, name: str) -> datetime | None:
if value is None:
return None
if isinstance(value, str):
text = value[:-1] + "+00:00" if value.endswith("Z") else value
try:
value = datetime.fromisoformat(text)
except ValueError as exc:
raise ValueError(f"{name} is not an RFC3339 datetime") from exc
if not isinstance(value, datetime) or value.tzinfo is None or value.utcoffset() is None:
raise ValueError(f"{name} must be timezone-aware")
return value
def _tuple_of_text(value: object, name: str) -> tuple[str, ...]:
if not isinstance(value, (tuple, list)) or isinstance(value, (str, bytes)):
raise ValueError(f"{name} must be an array")
return tuple(_text(item, name) for item in value)
def _stamp(value: object) -> DomainStamp | None:
if value is None:
return None
raw = _exact_mapping(value, _STAMP_FIELDS, "stamp")
return DomainStamp(
domain=_text(raw["domain"], "domain"),
object_id=_text(raw["object_id"], "object_id"),
generation=raw["generation"], # type: ignore[arg-type]
revision=raw["revision"], # type: ignore[arg-type]
event_id=_text(raw["event_id"], "event_id"),
)
def _fact(value: object) -> Fact:
raw = _exact_mapping(value, _FACT_FIELDS, "fact")
return Fact(
value=raw["value"], # type: ignore[arg-type]
stamp=_stamp(raw["stamp"]),
binding_digest=_text(raw["binding_digest"], "binding_digest"),
observed_at=_datetime(raw["observed_at"], "observed_at"),
expires_at=_datetime(raw["expires_at"], "expires_at"),
evidence_refs=_tuple_of_text(raw["evidence_refs"], "evidence_refs"),
)
def _unknown_fact(binding: OperationBinding, code: str) -> Fact:
return Fact(
value="UNKNOWN",
stamp=None,
binding_digest=binding_digest(binding),
observed_at=None,
expires_at=None,
evidence_refs=(f"operation-readback:{code}",),
)
def _fact_candidates(value: object) -> tuple[Fact, ...]:
if isinstance(value, Mapping):
return (_fact(value),)
if isinstance(value, Sequence) and not isinstance(value, (str, bytes, bytearray)):
if not value:
raise ValueError("fact candidate list is empty")
return tuple(_fact(item) for item in value)
raise ValueError("fact must be an object or non-empty candidate list")
def _select_fact(
value: object, binding: OperationBinding, *, expected_domain: str
) -> Fact:
candidates = _fact_candidates(value)
digest = binding_digest(binding)
if any(item.binding_digest != digest for item in candidates):
return _unknown_fact(binding, "BINDING_MISMATCH")
if any(item.stamp is None for item in candidates):
if len(candidates) == 1:
return candidates[0]
return _unknown_fact(binding, "INVALID_CANDIDATE_SET")
stamps = [item.stamp for item in candidates]
assert all(stamp is not None for stamp in stamps)
domains = {(stamp.domain, stamp.object_id) for stamp in stamps if stamp is not None}
if len(domains) != 1 or next(iter(domains))[0] != expected_domain:
return _unknown_fact(binding, "CROSS_DOMAIN_CANDIDATES")
highest = max((stamp.generation, stamp.revision) for stamp in stamps if stamp is not None)
current = [
item
for item in candidates
if item.stamp is not None
and (item.stamp.generation, item.stamp.revision) == highest
]
if len(current) > 1:
first = current[0]
# Event identity may differ for an idempotent duplicate. Every semantic
# field must still agree at the same authority revision.
semantic = (
first.value,
first.binding_digest,
first.observed_at,
first.expires_at,
first.evidence_refs,
)
if any(
(
item.value,
item.binding_digest,
item.observed_at,
item.expires_at,
item.evidence_refs,
)
!= semantic
for item in current[1:]
):
return _unknown_fact(binding, "SAME_REVISION_CONFLICT")
return current[0]
def _read(reader: Reader | None, binding: OperationBinding) -> Mapping[str, object] | None:
if reader is None:
return None
try:
value = reader(binding)
except Exception:
return None
return value if isinstance(value, Mapping) else None
def collect_observation(
binding: OperationBinding,
readers: OperationReaders,
*,
now: datetime,
) -> OperationObservation:
"""Query three authority seams and build a typed preflight observation."""
_datetime(now, "now")
declared = installed = callable_fact = _unknown_fact(binding, "DECLARATION_UNAVAILABLE")
raw = _read(readers.declaration, binding)
if raw is not None:
try:
doc = _exact_mapping(raw, _DECLARATION_FIELDS, "declaration reader result")
if doc["schema"] != "aios.operation-declaration-observation.v1":
raise ValueError("unsupported declaration schema")
declared = _select_fact(
doc["declared"], binding, expected_domain="declaration"
)
installed = _select_fact(
doc["installed"], binding, expected_domain="runtime"
)
callable_fact = _select_fact(
doc["callable"], binding, expected_domain="runtime"
)
except (TypeError, ValueError):
declared = installed = callable_fact = _unknown_fact(
binding, "INVALID_DECLARATION_OBSERVATION"
)
preconditions = _unknown_fact(binding, "TASK_ADAPTER_UNAVAILABLE")
raw = _read(readers.task, binding)
if raw is not None:
try:
doc = _exact_mapping(raw, _TASK_FIELDS, "task reader result")
if doc["schema"] != "aios.operation-task-observation.v1":
raise ValueError("unsupported task schema")
preconditions = _select_fact(
doc["preconditions"], binding, expected_domain="task"
)
except (TypeError, ValueError):
preconditions = _unknown_fact(binding, "INVALID_TASK_OBSERVATION")
authorized = _unknown_fact(binding, "AUTHORIZATION_ADAPTER_UNAVAILABLE")
raw = _read(readers.authorization, binding)
if raw is not None:
try:
doc = _exact_mapping(raw, _AUTHORIZATION_FIELDS, "authorization reader result")
if doc["schema"] != "aios.operation-authorization-observation.v1":
raise ValueError("unsupported authorization schema")
authorized = _select_fact(
doc["authorized"], binding, expected_domain="authorization"
)
except (TypeError, ValueError):
authorized = _unknown_fact(binding, "INVALID_AUTHORIZATION_OBSERVATION")
return OperationObservation(
binding=binding,
declared=declared,
installed=installed,
callable=callable_fact,
authorized=authorized,
preconditions=preconditions,
)
def _unknown_effect(binding: OperationBinding) -> EffectObservation:
return EffectObservation(
binding_digest=binding_digest(binding),
outcome="UNKNOWN",
receipt_ref=None,
result_artifact_digest=None,
actor_id=None,
stamp=None,
observed_at=None,
expires_at=None,
trust_status="UNTRUSTED",
)
def _effect(value: object) -> EffectObservation:
raw = _exact_mapping(value, _EFFECT_FIELDS, "effect")
return EffectObservation(
binding_digest=_text(raw["binding_digest"], "binding_digest"),
outcome=raw["outcome"], # type: ignore[arg-type]
receipt_ref=_optional_text(raw["receipt_ref"], "receipt_ref"),
result_artifact_digest=_optional_text(
raw["result_artifact_digest"], "result_artifact_digest"
),
actor_id=_optional_text(raw["actor_id"], "actor_id"),
stamp=_stamp(raw["stamp"]),
observed_at=_datetime(raw["observed_at"], "observed_at"),
expires_at=_datetime(raw["expires_at"], "expires_at"),
trust_status=raw["trust_status"], # type: ignore[arg-type]
)
def _unknown_verification(binding: OperationBinding) -> VerificationObservation:
return VerificationObservation(
binding_digest=binding_digest(binding),
receipt_ref=None,
result_artifact_digest=None,
verifier_id=None,
outcome="UNAVAILABLE",
authenticated=False,
observed_at=None,
expires_at=None,
verification_receipt_ref=None,
)
def _verification(value: object) -> VerificationObservation:
raw = _exact_mapping(value, _VERIFICATION_FIELDS, "verification")
return VerificationObservation(
binding_digest=_text(raw["binding_digest"], "binding_digest"),
receipt_ref=_optional_text(raw["receipt_ref"], "receipt_ref"),
result_artifact_digest=_optional_text(
raw["result_artifact_digest"], "result_artifact_digest"
),
verifier_id=_optional_text(raw["verifier_id"], "verifier_id"),
outcome=raw["outcome"], # type: ignore[arg-type]
authenticated=raw["authenticated"], # type: ignore[arg-type]
observed_at=_datetime(raw["observed_at"], "observed_at"),
expires_at=_datetime(raw["expires_at"], "expires_at"),
verification_receipt_ref=_optional_text(
raw["verification_receipt_ref"], "verification_receipt_ref"
),
)
def _unknown_acceptance(binding: OperationBinding) -> AcceptanceObservation:
return AcceptanceObservation(
binding_digest=binding_digest(binding),
receipt_ref=None,
result_artifact_digest=None,
verifier_receipt_ref=None,
acceptance_revision=None,
acceptor_id=None,
outcome="UNKNOWN",
authenticated=False,
observed_at=None,
expires_at=None,
)
def _acceptance(value: object) -> AcceptanceObservation:
raw = _exact_mapping(value, _ACCEPTANCE_OBSERVATION_FIELDS, "acceptance")
return AcceptanceObservation(
binding_digest=_text(raw["binding_digest"], "binding_digest"),
receipt_ref=_optional_text(raw["receipt_ref"], "receipt_ref"),
result_artifact_digest=_optional_text(
raw["result_artifact_digest"], "result_artifact_digest"
),
verifier_receipt_ref=_optional_text(
raw["verifier_receipt_ref"], "verifier_receipt_ref"
),
acceptance_revision=_optional_text(
raw["acceptance_revision"], "acceptance_revision"
),
acceptor_id=_optional_text(raw["acceptor_id"], "acceptor_id"),
outcome=raw["outcome"], # type: ignore[arg-type]
authenticated=raw["authenticated"], # type: ignore[arg-type]
observed_at=_datetime(raw["observed_at"], "observed_at"),
expires_at=_datetime(raw["expires_at"], "expires_at"),
)
def collect_closure(
binding: OperationBinding,
readers: OperationReaders,
*,
now: datetime,
) -> ClosureObservation:
"""Read closure claims independently; absent adapters remain UNKNOWN."""
_datetime(now, "now")
effect = _unknown_effect(binding)
raw = _read(readers.gitops, binding)
if raw is not None:
try:
doc = _exact_mapping(raw, _GITOPS_FIELDS, "gitops reader result")
if doc["schema"] != "aios.operation-effect-observation.v1":
raise ValueError("unsupported effect schema")
effect = _effect(doc["effect"])
except (TypeError, ValueError):
effect = _unknown_effect(binding)
verification = _unknown_verification(binding)
raw = _read(readers.verifier, binding)
if raw is not None:
try:
doc = _exact_mapping(raw, _VERIFIER_FIELDS, "verifier reader result")
if doc["schema"] != "aios.operation-verification-observation.v1":
raise ValueError("unsupported verifier schema")
verification = _verification(doc["verification"])
except (TypeError, ValueError):
verification = _unknown_verification(binding)
acceptance = _unknown_acceptance(binding)
expected_acceptor_id = "unresolved-acceptor"
expected_acceptance_revision = "unresolved-revision"
raw = _read(readers.acceptance, binding)
if raw is not None:
try:
doc = _exact_mapping(raw, _ACCEPTANCE_FIELDS, "acceptance reader result")
if doc["schema"] != "aios.operation-acceptance-observation.v1":
raise ValueError("unsupported acceptance schema")
acceptance = _acceptance(doc["acceptance"])
expected_acceptor_id = _text(
doc["expected_acceptor_id"], "expected_acceptor_id"
)
expected_acceptance_revision = _text(
doc["expected_acceptance_revision"],
"expected_acceptance_revision",
)
except (TypeError, ValueError):
acceptance = _unknown_acceptance(binding)
return ClosureObservation(
effect=effect,
verification=verification,
acceptance=acceptance,
expected_acceptor_id=expected_acceptor_id,
expected_acceptance_revision=expected_acceptance_revision,
)
__all__ = ["OperationReaders", "collect_closure", "collect_observation"]
@@ -0,0 +1,547 @@
"""Read-only C6 operation observations and fail-closed reductions.
This module deliberately owns no persistence, signer, lease, or mutation port.
Its digests are local to the C6 operation-binding schema and do not replace any
existing domain encoder.
"""
from __future__ import annotations
from dataclasses import asdict, dataclass
from datetime import datetime
import hashlib
import json
import re
from typing import Literal
FactValue = Literal["TRUE", "FALSE", "UNKNOWN"]
PreflightState = Literal[
"EXECUTION_ENTRYPOINT_ABSENT",
"CAPABILITY_UNAVAILABLE",
"AUTHORIZATION_REQUIRED",
"PRECONDITION_FAILED",
"UNKNOWN",
"READY",
]
EffectOutcome = Literal["COMMITTED", "NOT_COMMITTED", "UNKNOWN"]
VerificationOutcome = Literal["PASS", "FAIL", "UNKNOWN", "UNAVAILABLE"]
AcceptanceOutcome = Literal["ACCEPTED", "REJECTED", "UNKNOWN"]
ClosureOutcome = Literal["SUCCEEDED", "PARTIAL", "OUTCOME_UNKNOWN"]
EffectReconciliation = Literal[
"REBUILD_RECEIPT_REFERENCE",
"REVALIDATE_BEFORE_RETRY",
"OUTCOME_UNKNOWN",
]
_OID_RE = re.compile(r"^[0-9a-f]{40}$")
_DIGEST_RE = re.compile(r"^[0-9a-f]{64}$")
_EFFECT_KEY_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:/@+-]{0,159}$")
_CAPABILITY_NAMESPACES = frozenset({"agent-task", "task-plane-wildcard"})
def _string(value: object, name: str) -> str:
if not isinstance(value, str) or not value or value != value.strip():
raise ValueError(f"{name} must be a non-empty canonical string")
if "\x00" in value:
raise ValueError(f"{name} contains NUL")
return value
def _non_negative_int(value: object, name: str) -> int:
if isinstance(value, bool) or not isinstance(value, int) or value < 0:
raise ValueError(f"{name} must be a non-negative integer")
return value
def _aware_utc(value: object, name: str) -> datetime:
if not isinstance(value, datetime) or value.tzinfo is None:
raise ValueError(f"{name} must be timezone-aware")
if value.utcoffset() is None:
raise ValueError(f"{name} must have a UTC offset")
return value
def validate_oid(value: str) -> str:
if not isinstance(value, str) or _OID_RE.fullmatch(value) is None:
raise ValueError("OID must be 40 lowercase hexadecimal characters")
return value
def validate_digest(value: str) -> str:
if not isinstance(value, str) or _DIGEST_RE.fullmatch(value) is None:
raise ValueError("digest must be 64 lowercase hexadecimal characters")
return value
def validate_effect_key(value: str) -> str:
if not isinstance(value, str) or _EFFECT_KEY_RE.fullmatch(value) is None:
raise ValueError("effect key has invalid length or characters")
return value
@dataclass(frozen=True)
class OperationBinding:
operation_id: str
actor_id: str
tenant_id: str
repository: str
capability_namespace: str
capability_id: str
task_id: str
job_id: str
generation: int
input_kind: str
source_oid: str
patch_digest: str
target_branch: str
scope_digest: str
policy_digest: str
plan_digest: str
effect_key: str
def __post_init__(self) -> None:
for name in (
"operation_id",
"actor_id",
"tenant_id",
"repository",
"capability_id",
"task_id",
"job_id",
"input_kind",
"target_branch",
):
_string(getattr(self, name), name)
if self.capability_namespace not in _CAPABILITY_NAMESPACES:
raise ValueError("unsupported capability namespace")
_non_negative_int(self.generation, "generation")
validate_oid(self.source_oid)
for name in ("patch_digest", "scope_digest", "policy_digest", "plan_digest"):
validate_digest(getattr(self, name))
validate_effect_key(self.effect_key)
def binding_digest(binding: OperationBinding) -> str:
if not isinstance(binding, OperationBinding):
raise TypeError("binding must be OperationBinding")
payload = json.dumps(
asdict(binding),
sort_keys=True,
separators=(",", ":"),
ensure_ascii=False,
allow_nan=False,
).encode("utf-8")
return hashlib.sha256(payload).hexdigest()
@dataclass(frozen=True)
class DomainStamp:
domain: str
object_id: str
generation: int
revision: int
event_id: str
def __post_init__(self) -> None:
_string(self.domain, "domain")
_string(self.object_id, "object_id")
_non_negative_int(self.generation, "generation")
_non_negative_int(self.revision, "revision")
_string(self.event_id, "event_id")
@dataclass(frozen=True)
class Fact:
value: FactValue
stamp: DomainStamp | None
binding_digest: str
observed_at: datetime | None
expires_at: datetime | None
evidence_refs: tuple[str, ...]
def __post_init__(self) -> None:
if self.value not in ("TRUE", "FALSE", "UNKNOWN"):
raise ValueError("invalid fact value")
validate_digest(self.binding_digest)
if not isinstance(self.evidence_refs, tuple):
raise ValueError("evidence_refs must be a tuple")
for ref in self.evidence_refs:
_string(ref, "evidence_ref")
if self.value != "UNKNOWN" and self.stamp is None:
raise ValueError("known fact requires an authority stamp")
if (self.observed_at is None) != (self.expires_at is None):
raise ValueError("fact timestamps must both be present or absent")
if self.observed_at is None:
if self.value != "UNKNOWN":
raise ValueError("known fact requires timestamps")
return
observed_at = _aware_utc(self.observed_at, "observed_at")
expires_at = _aware_utc(self.expires_at, "expires_at")
if expires_at <= observed_at:
raise ValueError("expires_at must be after observed_at")
@dataclass(frozen=True)
class OperationObservation:
binding: OperationBinding
declared: Fact
installed: Fact
callable: Fact
authorized: Fact
preconditions: Fact
@dataclass(frozen=True)
class PreflightResult:
state: PreflightState
next_action: str
reason_code: str
retryable: bool
binding_digest: str
evidence_refs: tuple[str, ...]
def _now(value: datetime) -> datetime:
return _aware_utc(value, "now")
def _fact_invalid_reason(fact: Fact, expected_digest: str, now: datetime) -> str | None:
if fact.binding_digest != expected_digest:
return "BINDING_MISMATCH"
if fact.value == "UNKNOWN":
return "OBSERVATION_UNKNOWN"
if fact.stamp is None or fact.observed_at is None or fact.expires_at is None:
return "INVALID_OBSERVATION"
if fact.observed_at > now:
return "FUTURE_OBSERVATION"
if now >= fact.expires_at:
return "STALE_OBSERVATION"
return None
def derive_preflight(
observation: OperationObservation, *, now: datetime
) -> PreflightResult:
"""Reduce fresh authority observations without invoking any external port."""
now = _now(now)
if not isinstance(observation, OperationObservation):
raise TypeError("observation must be OperationObservation")
digest = binding_digest(observation.binding)
evidence: list[str] = []
frontier = (
(
"declared",
observation.declared,
"EXECUTION_ENTRYPOINT_ABSENT",
"ENGINEERING_CHANGE_REQUIRED",
"EXECUTION_ENTRYPOINT_ABSENT",
),
(
"installed",
observation.installed,
"CAPABILITY_UNAVAILABLE",
"REPAIR_DECLARED_EXECUTOR",
"EXECUTOR_NOT_INSTALLED",
),
(
"callable",
observation.callable,
"CAPABILITY_UNAVAILABLE",
"REPAIR_DECLARED_EXECUTOR",
"EXECUTOR_NOT_CALLABLE",
),
(
"authorized",
observation.authorized,
"AUTHORIZATION_REQUIRED",
"PREPARE_EXACT_AUTH_REQUEST",
"EXACT_AUTHORIZATION_MISSING",
),
(
"preconditions",
observation.preconditions,
"PRECONDITION_FAILED",
"REPAIR_PRECONDITION",
"PRECONDITION_FAILED",
),
)
for name, fact, false_state, false_action, false_reason in frontier:
evidence.extend(fact.evidence_refs)
invalid_reason = _fact_invalid_reason(fact, digest, now)
if invalid_reason is not None:
return PreflightResult(
state="UNKNOWN",
next_action="OBSERVE_OR_RECONCILE",
reason_code=f"{name.upper()}_{invalid_reason}",
retryable=False,
binding_digest=digest,
evidence_refs=tuple(dict.fromkeys(evidence)),
)
if fact.value == "FALSE":
return PreflightResult(
state=false_state, # type: ignore[arg-type]
next_action=false_action,
reason_code=false_reason,
retryable=False,
binding_digest=digest,
evidence_refs=tuple(dict.fromkeys(evidence)),
)
return PreflightResult(
state="READY",
next_action="EXECUTE_GOVERNED_PLAN",
reason_code="ALL_REQUIRED_FACTS_FRESH_AND_TRUE",
retryable=False,
binding_digest=digest,
evidence_refs=tuple(dict.fromkeys(evidence)),
)
@dataclass(frozen=True)
class EffectObservation:
binding_digest: str
outcome: EffectOutcome
receipt_ref: str | None
result_artifact_digest: str | None
actor_id: str | None
stamp: DomainStamp | None
observed_at: datetime | None
expires_at: datetime | None
trust_status: Literal["TRUSTED", "UNTRUSTED", "SHADOW"] = "TRUSTED"
def __post_init__(self) -> None:
validate_digest(self.binding_digest)
if self.outcome not in ("COMMITTED", "NOT_COMMITTED", "UNKNOWN"):
raise ValueError("invalid effect outcome")
if self.trust_status not in ("TRUSTED", "UNTRUSTED", "SHADOW"):
raise ValueError("invalid effect trust status")
for value, name in ((self.receipt_ref, "receipt_ref"), (self.actor_id, "actor_id")):
if value is not None:
_string(value, name)
if self.result_artifact_digest is not None:
validate_digest(self.result_artifact_digest)
if (self.observed_at is None) != (self.expires_at is None):
raise ValueError("effect timestamps must both be present or absent")
if self.observed_at is not None:
observed_at = _aware_utc(self.observed_at, "observed_at")
expires_at = _aware_utc(self.expires_at, "expires_at")
if expires_at <= observed_at:
raise ValueError("expires_at must be after observed_at")
@dataclass(frozen=True)
class VerificationObservation:
binding_digest: str
receipt_ref: str | None
result_artifact_digest: str | None
verifier_id: str | None
outcome: VerificationOutcome
authenticated: bool
observed_at: datetime | None
expires_at: datetime | None
verification_receipt_ref: str | None = None
def __post_init__(self) -> None:
validate_digest(self.binding_digest)
if self.outcome not in ("PASS", "FAIL", "UNKNOWN", "UNAVAILABLE"):
raise ValueError("invalid verification outcome")
if type(self.authenticated) is not bool:
raise ValueError("authenticated must be bool")
for value, name in (
(self.receipt_ref, "receipt_ref"),
(self.verifier_id, "verifier_id"),
(self.verification_receipt_ref, "verification_receipt_ref"),
):
if value is not None:
_string(value, name)
if self.result_artifact_digest is not None:
validate_digest(self.result_artifact_digest)
_validate_optional_times(self.observed_at, self.expires_at, "verification")
@dataclass(frozen=True)
class AcceptanceObservation:
binding_digest: str
receipt_ref: str | None
result_artifact_digest: str | None
verifier_receipt_ref: str | None
acceptance_revision: str | None
acceptor_id: str | None
outcome: AcceptanceOutcome
authenticated: bool
observed_at: datetime | None
expires_at: datetime | None
def __post_init__(self) -> None:
validate_digest(self.binding_digest)
if self.outcome not in ("ACCEPTED", "REJECTED", "UNKNOWN"):
raise ValueError("invalid acceptance outcome")
if type(self.authenticated) is not bool:
raise ValueError("authenticated must be bool")
for value, name in (
(self.receipt_ref, "receipt_ref"),
(self.verifier_receipt_ref, "verifier_receipt_ref"),
(self.acceptance_revision, "acceptance_revision"),
(self.acceptor_id, "acceptor_id"),
):
if value is not None:
_string(value, name)
if self.result_artifact_digest is not None:
validate_digest(self.result_artifact_digest)
_validate_optional_times(self.observed_at, self.expires_at, "acceptance")
def _validate_optional_times(
observed_at: datetime | None, expires_at: datetime | None, prefix: str
) -> None:
if (observed_at is None) != (expires_at is None):
raise ValueError(f"{prefix} timestamps must both be present or absent")
if observed_at is None:
return
observed = _aware_utc(observed_at, "observed_at")
expires = _aware_utc(expires_at, "expires_at")
if expires <= observed:
raise ValueError("expires_at must be after observed_at")
@dataclass(frozen=True)
class ClosureObservation:
effect: EffectObservation
verification: VerificationObservation
acceptance: AcceptanceObservation
expected_acceptor_id: str
expected_acceptance_revision: str
def __post_init__(self) -> None:
_string(self.expected_acceptor_id, "expected_acceptor_id")
_string(self.expected_acceptance_revision, "expected_acceptance_revision")
@dataclass(frozen=True)
class ClosureResult:
execution_recorded: bool
independently_verified: bool
business_accepted: bool
outcome: ClosureOutcome
reason_code: str
def _fresh(
observed_at: datetime | None, expires_at: datetime | None, now: datetime
) -> bool:
return (
observed_at is not None
and expires_at is not None
and observed_at <= now < expires_at
)
def reconcile_effect(
effect: EffectObservation, *, binding: OperationBinding, now: datetime
) -> EffectReconciliation:
"""Choose a safe recovery route; this function never retries the effect."""
now = _now(now)
digest = binding_digest(binding)
if (
effect.binding_digest != digest
or effect.stamp is None
or effect.trust_status != "TRUSTED"
or not _fresh(effect.observed_at, effect.expires_at, now)
):
return "OUTCOME_UNKNOWN"
if effect.outcome == "COMMITTED":
if (
effect.receipt_ref is not None
and effect.result_artifact_digest is not None
and effect.actor_id is not None
):
return "REBUILD_RECEIPT_REFERENCE"
return "OUTCOME_UNKNOWN"
if effect.outcome == "NOT_COMMITTED":
# stamp + fresh trusted authority receipt is the deterministic negative
# observation. Revalidation is still required before any governed retry.
if effect.receipt_ref is not None and effect.result_artifact_digest is None:
return "REVALIDATE_BEFORE_RETRY"
return "OUTCOME_UNKNOWN"
def derive_closure(
binding: OperationBinding,
observation: ClosureObservation,
*,
now: datetime,
) -> ClosureResult:
"""Reduce execution, verification, and acceptance as three distinct claims."""
now = _now(now)
digest = binding_digest(binding)
effect = observation.effect
execution_recorded = (
effect.binding_digest == digest
and effect.outcome == "COMMITTED"
and effect.trust_status == "TRUSTED"
and effect.stamp is not None
and effect.receipt_ref is not None
and effect.result_artifact_digest is not None
and effect.actor_id is not None
and _fresh(effect.observed_at, effect.expires_at, now)
)
if not execution_recorded:
outcome: ClosureOutcome = (
"OUTCOME_UNKNOWN" if effect.outcome == "UNKNOWN" else "PARTIAL"
)
return ClosureResult(False, False, False, outcome, "EXECUTION_NOT_RECORDED")
verification = observation.verification
verifier_receipt_ref = (
verification.verification_receipt_ref or verification.receipt_ref
)
independently_verified = (
verification.binding_digest == digest
and verification.receipt_ref == effect.receipt_ref
and verification.result_artifact_digest == effect.result_artifact_digest
and verification.verifier_id is not None
and verification.verifier_id != effect.actor_id
and verification.outcome == "PASS"
and verification.authenticated is True
and verifier_receipt_ref is not None
and _fresh(verification.observed_at, verification.expires_at, now)
)
if not independently_verified:
return ClosureResult(True, False, False, "PARTIAL", "INDEPENDENT_VERIFICATION_MISSING")
acceptance = observation.acceptance
business_accepted = (
acceptance.binding_digest == digest
and acceptance.receipt_ref == effect.receipt_ref
and acceptance.result_artifact_digest == effect.result_artifact_digest
and acceptance.verifier_receipt_ref == verifier_receipt_ref
and acceptance.acceptance_revision
== observation.expected_acceptance_revision
and acceptance.acceptor_id == observation.expected_acceptor_id
and acceptance.outcome == "ACCEPTED"
and acceptance.authenticated is True
and _fresh(acceptance.observed_at, acceptance.expires_at, now)
)
if not business_accepted:
return ClosureResult(True, True, False, "PARTIAL", "AUTHORIZED_ACCEPTANCE_MISSING")
return ClosureResult(True, True, True, "SUCCEEDED", "THREE_LAYER_CLOSURE_VALID")
__all__ = [
"AcceptanceObservation",
"ClosureObservation",
"ClosureResult",
"DomainStamp",
"EffectObservation",
"Fact",
"OperationBinding",
"OperationObservation",
"PreflightResult",
"VerificationObservation",
"binding_digest",
"derive_closure",
"derive_preflight",
"reconcile_effect",
"validate_digest",
"validate_effect_key",
"validate_oid",
]
@@ -0,0 +1,533 @@
"""Strict, pure validation for the C6 executable source-patch contract.
Validation here proves contract and unified-diff shape only. The governed
executor remains responsible for secure checkout containment, isolated apply,
result-file hashing, live authorization, leases/fences, and CAS.
"""
from __future__ import annotations
from collections.abc import Mapping, Sequence
from dataclasses import dataclass
import hashlib
import json
from pathlib import PurePosixPath
import re
from typing import Literal
from .operation_view import (
OperationBinding,
validate_digest,
validate_effect_key,
validate_oid,
)
_TOP_FIELDS = frozenset(
{
"schema",
"input_kind",
"type",
"target",
"before_hash",
"after_hash",
"operations",
"rollback",
"verification",
"operation_id",
"actor_id",
"tenant_id",
"capability_namespace",
"capability_id",
"task_id",
"job_id",
"generation",
"executor_ref",
"repository",
"base_oid",
"target_branch",
"patch_digest",
"file_digests",
"scope_digest",
"policy_digest",
"plan_digest",
"effect_key",
"rollback_artifact_digest",
"verification_contract_revision",
"acceptance_contract_revision",
}
)
_TARGET_FIELDS = frozenset({"repository", "branch"})
_OPERATION_FIELDS = frozenset({"path", "kind", "before_hash", "after_hash"})
_ROLLBACK_FIELDS = frozenset({"artifact_digest", "expected_after_hash"})
_VERIFICATION_FIELDS = frozenset(
{"revision", "acceptance_revision", "required_checks"}
)
_PROTECTED_PATHS = frozenset({".git", ".gitmodules"})
_DIFF_HEADER_RE = re.compile(r"^diff --git a/([^\s]+) b/([^\s]+)$")
def _exact_mapping(value: object, fields: frozenset[str], name: str) -> Mapping[str, object]:
if not isinstance(value, Mapping) or set(value) != fields:
raise ValueError(f"{name} has unknown or missing fields")
if any(not isinstance(key, str) for key in value):
raise ValueError(f"{name} keys must be strings")
return value
def _text(value: object, name: str) -> str:
if not isinstance(value, str) or not value or value != value.strip() or "\x00" in value:
raise ValueError(f"{name} must be a canonical non-empty string")
return value
def _integer(value: object, name: str) -> int:
if isinstance(value, bool) or not isinstance(value, int) or value < 0:
raise ValueError(f"{name} must be a non-negative integer")
return value
def _path(value: object) -> str:
path = _text(value, "path")
if "\\" in path or ":" in path or path.startswith(("/", "//")):
raise ValueError("path must be repository-relative canonical POSIX")
parsed = PurePosixPath(path)
if path != parsed.as_posix() or any(part in ("", ".", "..") for part in parsed.parts):
raise ValueError("path must be repository-relative canonical POSIX")
if any(part.casefold() == ".git" for part in parsed.parts) or path.casefold() in _PROTECTED_PATHS:
raise ValueError("protected Git metadata is outside patch scope")
return path
def _content_hash(value: object, name: str, *, literal: str | None = None) -> str:
text = _text(value, name)
if literal is not None and text == literal:
return text
return validate_digest(text)
@dataclass(frozen=True)
class PatchTarget:
repository: str
branch: str
def __post_init__(self) -> None:
_text(self.repository, "repository")
_text(self.branch, "branch")
@dataclass(frozen=True)
class PatchFileOperation:
path: str
kind: Literal["ADD", "MODIFY", "DELETE"]
before_hash: str
after_hash: str
def __post_init__(self) -> None:
_path(self.path)
if self.kind not in ("ADD", "MODIFY", "DELETE"):
raise ValueError("unsupported patch operation")
if self.kind == "ADD":
if self.before_hash != "ABSENT":
raise ValueError("ADD before_hash must be ABSENT")
validate_digest(self.after_hash)
elif self.kind == "DELETE":
validate_digest(self.before_hash)
if self.after_hash != "DELETED":
raise ValueError("DELETE after_hash must be DELETED")
else:
validate_digest(self.before_hash)
validate_digest(self.after_hash)
@dataclass(frozen=True)
class RollbackContract:
artifact_digest: str
expected_after_hash: str
def __post_init__(self) -> None:
validate_digest(self.artifact_digest)
validate_digest(self.expected_after_hash)
@dataclass(frozen=True)
class VerificationContract:
revision: str
acceptance_revision: str
required_checks: tuple[str, ...]
def __post_init__(self) -> None:
_text(self.revision, "verification revision")
_text(self.acceptance_revision, "acceptance revision")
if not isinstance(self.required_checks, tuple) or not self.required_checks:
raise ValueError("required_checks must be a non-empty tuple")
if len(set(self.required_checks)) != len(self.required_checks):
raise ValueError("required_checks contains duplicates")
for check in self.required_checks:
_text(check, "required check")
@dataclass(frozen=True)
class ExecutablePatchV1:
schema: Literal["aios.executable-patch.v1"]
input_kind: Literal["EXECUTABLE_PATCH"]
type: Literal["git_patch"]
target: PatchTarget
before_hash: str
after_hash: str
operations: tuple[PatchFileOperation, ...]
rollback: RollbackContract
verification: VerificationContract
operation_id: str
actor_id: str
tenant_id: str
capability_namespace: str
capability_id: str
task_id: str
job_id: str
generation: int
executor_ref: Literal["governed_source_patch_apply"]
repository: str
base_oid: str
target_branch: str
patch_digest: str
file_digests: tuple[tuple[str, str], ...]
scope_digest: str
policy_digest: str
plan_digest: str
effect_key: str
rollback_artifact_digest: str
verification_contract_revision: str
acceptance_contract_revision: str
def _aggregate_hash(
operations: tuple[PatchFileOperation, ...], attribute: Literal["before_hash", "after_hash"]
) -> str:
values = [
{"path": item.path, "hash": getattr(item, attribute)}
for item in sorted(operations, key=lambda operation: operation.path)
]
encoded = json.dumps(
values,
sort_keys=True,
separators=(",", ":"),
ensure_ascii=False,
allow_nan=False,
).encode("utf-8")
return hashlib.sha256(encoded).hexdigest()
def _parse_target(value: object) -> PatchTarget:
raw = _exact_mapping(value, _TARGET_FIELDS, "target")
return PatchTarget(
repository=_text(raw["repository"], "target.repository"),
branch=_text(raw["branch"], "target.branch"),
)
def _parse_operation(value: object) -> PatchFileOperation:
raw = _exact_mapping(value, _OPERATION_FIELDS, "operation")
kind = raw["kind"]
if kind not in ("ADD", "MODIFY", "DELETE"):
raise ValueError("unsupported patch operation")
before = _content_hash(
raw["before_hash"],
"before_hash",
literal="ABSENT" if kind == "ADD" else None,
)
after = _content_hash(
raw["after_hash"],
"after_hash",
literal="DELETED" if kind == "DELETE" else None,
)
return PatchFileOperation(
path=_path(raw["path"]),
kind=kind, # type: ignore[arg-type]
before_hash=before,
after_hash=after,
)
def _parse_operations(value: object) -> tuple[PatchFileOperation, ...]:
if not isinstance(value, (tuple, list)) or isinstance(value, (str, bytes)) or not value:
raise ValueError("operations must be a non-empty array")
operations = tuple(_parse_operation(item) for item in value)
paths = [item.path for item in operations]
if len(paths) != len(set(paths)) or len(paths) != len({item.casefold() for item in paths}):
raise ValueError("operation paths must be unique, including case-folding")
return operations
def _parse_rollback(value: object) -> RollbackContract:
raw = _exact_mapping(value, _ROLLBACK_FIELDS, "rollback")
return RollbackContract(
artifact_digest=validate_digest(_text(raw["artifact_digest"], "artifact_digest")),
expected_after_hash=validate_digest(
_text(raw["expected_after_hash"], "expected_after_hash")
),
)
def _parse_verification(value: object) -> VerificationContract:
raw = _exact_mapping(value, _VERIFICATION_FIELDS, "verification")
checks = raw["required_checks"]
if not isinstance(checks, (list, tuple)) or isinstance(checks, (str, bytes)):
raise ValueError("required_checks must be an array")
return VerificationContract(
revision=_text(raw["revision"], "verification revision"),
acceptance_revision=_text(
raw["acceptance_revision"], "acceptance revision"
),
required_checks=tuple(_text(item, "required check") for item in checks),
)
def _parse_file_digests(value: object) -> tuple[tuple[str, str], ...]:
if not isinstance(value, (list, tuple)) or isinstance(value, (str, bytes)):
raise ValueError("file_digests must be an array of pairs")
pairs: list[tuple[str, str]] = []
for item in value:
if not isinstance(item, (list, tuple)) or len(item) != 2:
raise ValueError("file_digests entries must be path/digest pairs")
path = _path(item[0])
digest = _content_hash(item[1], "file digest", literal="DELETED")
pairs.append((path, digest))
paths = [path for path, _ in pairs]
if len(paths) != len(set(paths)) or len(paths) != len({item.casefold() for item in paths}):
raise ValueError("file_digests paths must be unique")
return tuple(sorted(pairs))
def _strip_patch_path(line: str, prefix: str) -> str:
value = line[len(prefix) :]
if "\t" in value:
raise ValueError("timestamped unified-diff paths are unsupported")
if value == "/dev/null":
return value
expected = "a/" if prefix == "--- " else "b/"
if not value.startswith(expected):
raise ValueError("diff path has unexpected prefix")
return _path(value[2:])
def _actual_diff_operations(patch_bytes: bytes) -> tuple[tuple[str, str], ...]:
if not isinstance(patch_bytes, bytes) or not patch_bytes:
raise ValueError("patch_bytes must be non-empty bytes")
if b"\x00" in patch_bytes:
raise ValueError("binary patches are unsupported")
try:
text = patch_bytes.decode("utf-8")
except UnicodeDecodeError as exc:
raise ValueError("patch must be UTF-8 text") from exc
lines = text.splitlines()
starts = [index for index, line in enumerate(lines) if line.startswith("diff --git ")]
if not starts or any(line for line in lines[: starts[0]]):
raise ValueError("patch must consist only of git unified-diff blocks")
starts.append(len(lines))
actual: list[tuple[str, str]] = []
for position in range(len(starts) - 1):
block = lines[starts[position] : starts[position + 1]]
match = _DIFF_HEADER_RE.fullmatch(block[0])
if match is None:
raise ValueError("quoted, whitespace, or malformed diff paths are unsupported")
old_header_path = _path(match.group(1))
new_header_path = _path(match.group(2))
forbidden = (
"GIT binary patch",
"Binary files ",
"rename from ",
"rename to ",
"copy from ",
"copy to ",
"similarity index ",
"dissimilarity index ",
"old mode ",
"new mode ",
)
if any(line.startswith(forbidden) for line in block[1:]):
raise ValueError("rename, copy, binary, or mode-only patches are unsupported")
mode_lines = [
line
for line in block[1:]
if line.startswith(("new file mode ", "deleted file mode "))
]
if any(not line.endswith("100644") for line in mode_lines):
raise ValueError("symlink, executable, and submodule modes are unsupported")
old_lines = [line for line in block[1:] if line.startswith("--- ")]
new_lines = [line for line in block[1:] if line.startswith("+++ ")]
if len(old_lines) != 1 or len(new_lines) != 1:
raise ValueError("each diff block requires one old and new path")
old_path = _strip_patch_path(old_lines[0], "--- ")
new_path = _strip_patch_path(new_lines[0], "+++ ")
if not any(line.startswith("@@ ") for line in block[1:]):
raise ValueError("mode-only and empty diff blocks are unsupported")
if old_path == "/dev/null":
if new_path != new_header_path or old_header_path != new_header_path:
raise ValueError("ADD paths do not agree")
if mode_lines != ["new file mode 100644"]:
raise ValueError("ADD requires regular-text new file mode")
actual.append((new_path, "ADD"))
elif new_path == "/dev/null":
if old_path != old_header_path or old_header_path != new_header_path:
raise ValueError("DELETE paths do not agree")
if mode_lines != ["deleted file mode 100644"]:
raise ValueError("DELETE requires regular-text deleted file mode")
actual.append((old_path, "DELETE"))
else:
if mode_lines or len({old_path, new_path, old_header_path, new_header_path}) != 1:
raise ValueError("rename or mode-changing MODIFY is unsupported")
actual.append((old_path, "MODIFY"))
paths = [path for path, _ in actual]
if len(paths) != len(set(paths)) or len(paths) != len({item.casefold() for item in paths}):
raise ValueError("diff paths must be unique, including case-folding")
return tuple(sorted(actual))
def validate_patch_shape(patch_bytes: bytes, contract: ExecutablePatchV1) -> None:
actual = _actual_diff_operations(patch_bytes)
declared = tuple(sorted((item.path, item.kind) for item in contract.operations))
if actual != declared:
raise ValueError("actual diff file set or operation kinds do not match contract")
def parse_executable_patch(
raw: Mapping[str, object],
*,
patch_bytes: bytes,
allowed_files: frozenset[str],
expected_binding: OperationBinding,
) -> ExecutablePatchV1:
"""Parse a complete FD-005 contract without applying or executing it."""
document = _exact_mapping(raw, _TOP_FIELDS, "executable patch")
if document["schema"] != "aios.executable-patch.v1":
raise ValueError("unsupported executable patch schema")
if document["input_kind"] != "EXECUTABLE_PATCH":
raise ValueError("PATCH_PROPOSAL is not executable")
if document["type"] != "git_patch":
raise ValueError("unsupported executable patch type")
if document["executor_ref"] != "governed_source_patch_apply":
raise ValueError("executor_ref is not the governed pilot")
if not isinstance(allowed_files, frozenset):
raise ValueError("allowed_files must be a frozen manifest set")
canonical_allowed = frozenset(_path(item) for item in allowed_files)
if len(canonical_allowed) != len(allowed_files) or len(canonical_allowed) != len(
{item.casefold() for item in canonical_allowed}
):
raise ValueError("allowed_files contains aliases or case-fold collisions")
target = _parse_target(document["target"])
operations = _parse_operations(document["operations"])
rollback = _parse_rollback(document["rollback"])
verification = _parse_verification(document["verification"])
file_digests = _parse_file_digests(document["file_digests"])
before_hash = validate_digest(_text(document["before_hash"], "before_hash"))
after_hash = validate_digest(_text(document["after_hash"], "after_hash"))
patch_digest = validate_digest(_text(document["patch_digest"], "patch_digest"))
binding_fields = {
"operation_id": expected_binding.operation_id,
"actor_id": expected_binding.actor_id,
"tenant_id": expected_binding.tenant_id,
"capability_namespace": expected_binding.capability_namespace,
"capability_id": expected_binding.capability_id,
"task_id": expected_binding.task_id,
"job_id": expected_binding.job_id,
"generation": expected_binding.generation,
"input_kind": expected_binding.input_kind,
"repository": expected_binding.repository,
"target_branch": expected_binding.target_branch,
"patch_digest": expected_binding.patch_digest,
"scope_digest": expected_binding.scope_digest,
"policy_digest": expected_binding.policy_digest,
"plan_digest": expected_binding.plan_digest,
"effect_key": expected_binding.effect_key,
}
for name, expected in binding_fields.items():
if document[name] != expected:
raise ValueError(f"binding mismatch: {name}")
generation = _integer(document["generation"], "generation")
if generation != expected_binding.generation:
raise ValueError("binding mismatch: generation")
base_oid = validate_oid(_text(document["base_oid"], "base_oid"))
if base_oid != expected_binding.source_oid:
raise ValueError("binding mismatch: base_oid")
if target.repository != expected_binding.repository or target.branch != expected_binding.target_branch:
raise ValueError("target does not match top-level binding")
actual_patch_digest = hashlib.sha256(patch_bytes).hexdigest()
if patch_digest != actual_patch_digest:
raise ValueError("patch_digest does not match raw patch bytes")
if any(item.path not in canonical_allowed for item in operations):
raise ValueError("operation path is outside allowed_files")
expected_file_digests = tuple(sorted((item.path, item.after_hash) for item in operations))
if file_digests != expected_file_digests:
raise ValueError("file_digests do not match operation result hashes")
if before_hash != _aggregate_hash(operations, "before_hash"):
raise ValueError("before_hash does not match the operation manifest")
if after_hash != _aggregate_hash(operations, "after_hash"):
raise ValueError("after_hash does not match the operation manifest")
if rollback.expected_after_hash != after_hash:
raise ValueError("rollback expected_after_hash mismatch")
rollback_artifact_digest = validate_digest(
_text(document["rollback_artifact_digest"], "rollback_artifact_digest")
)
if rollback_artifact_digest != rollback.artifact_digest:
raise ValueError("rollback_artifact_digest mismatch")
verification_revision = _text(
document["verification_contract_revision"],
"verification_contract_revision",
)
acceptance_revision = _text(
document["acceptance_contract_revision"],
"acceptance_contract_revision",
)
if verification_revision != verification.revision:
raise ValueError("verification_contract_revision mismatch")
if acceptance_revision != verification.acceptance_revision:
raise ValueError("acceptance_contract_revision mismatch")
validate_effect_key(_text(document["effect_key"], "effect_key"))
contract = ExecutablePatchV1(
schema="aios.executable-patch.v1",
input_kind="EXECUTABLE_PATCH",
type="git_patch",
target=target,
before_hash=before_hash,
after_hash=after_hash,
operations=operations,
rollback=rollback,
verification=verification,
operation_id=expected_binding.operation_id,
actor_id=expected_binding.actor_id,
tenant_id=expected_binding.tenant_id,
capability_namespace=expected_binding.capability_namespace,
capability_id=expected_binding.capability_id,
task_id=expected_binding.task_id,
job_id=expected_binding.job_id,
generation=generation,
executor_ref="governed_source_patch_apply",
repository=expected_binding.repository,
base_oid=base_oid,
target_branch=expected_binding.target_branch,
patch_digest=patch_digest,
file_digests=file_digests,
scope_digest=expected_binding.scope_digest,
policy_digest=expected_binding.policy_digest,
plan_digest=expected_binding.plan_digest,
effect_key=expected_binding.effect_key,
rollback_artifact_digest=rollback_artifact_digest,
verification_contract_revision=verification_revision,
acceptance_contract_revision=acceptance_revision,
)
validate_patch_shape(patch_bytes, contract)
return contract
__all__ = [
"ExecutablePatchV1",
"PatchFileOperation",
"PatchTarget",
"RollbackContract",
"VerificationContract",
"parse_executable_patch",
"validate_patch_shape",
]
+39 -6
View File
@@ -54,13 +54,27 @@ class ReadonlyWorkerProfile:
class IdentityVerifier:
def __init__(self, trusted_identities: set[str] | None = None):
self.trusted_identities = trusted_identities
def __init__(
self,
trusted_identities: set[str] | None = None,
*,
verifier_identity: str | None = None,
):
self.trusted_identities = frozenset(trusted_identities or ())
self.verifier_identity = verifier_identity
@property
def is_configured(self) -> bool:
return any(identity != "*" for identity in self.trusted_identities)
def verify(self, identity: str) -> bool:
if self.trusted_identities is None:
return True
return identity in self.trusted_identities
return bool(
identity
and (
identity in self.trusted_identities
or "*" in self.trusted_identities
)
)
class EvidenceVerifier:
@@ -92,6 +106,7 @@ class PolicyEngine:
max_risk: str = "medium",
result_signature_verifier: Callable[[ResultEnvelope], bool] | None = None,
probe_attestation_verifier: Callable[[ProbeEvidence], bool] | None = None,
evidence_attestation_verifier: Callable[[object], bool] | None = None,
identity_capabilities: dict[str, set[str]] | None = None,
admin_identities: set[str] | None = None,
readonly_worker_execution_enabled: bool = False,
@@ -99,12 +114,28 @@ class PolicyEngine:
) -> None:
if max_risk not in RISK_RANK:
raise ValueError("invalid max_risk")
self.identity_verifier = identity_verifier or IdentityVerifier()
if execution_enabled and (
identity_verifier is None or not identity_verifier.is_configured
):
raise ValueError(
"execution_enabled requires an explicit trusted identity verifier"
)
if execution_enabled and evidence_attestation_verifier is None:
raise ValueError(
"execution_enabled requires an evidence attestation verifier"
)
configured_identities = {
identity for identity in (identity_capabilities or {}) if identity
}
self.identity_verifier = identity_verifier or IdentityVerifier(
configured_identities if not execution_enabled else None
)
self.evidence_verifier = evidence_verifier or EvidenceVerifier()
self.execution_enabled = execution_enabled
self.max_risk = max_risk
self.result_signature_verifier = result_signature_verifier
self.probe_attestation_verifier = probe_attestation_verifier
self.evidence_attestation_verifier = evidence_attestation_verifier
self.identity_capabilities = {
identity: set(capabilities)
for identity, capabilities in (identity_capabilities or {}).items()
@@ -195,6 +226,8 @@ class PolicyEngine:
"worker.register": "worker.self_register",
"worker.admin": "worker.admin",
}
if action not in action_capabilities:
return PolicyDecision(False, f"unknown action {action}")
required_capability = action_capabilities.get(action)
if required_capability:
granted = set(
@@ -2,6 +2,8 @@
from __future__ import annotations
import base64
import binascii
import hashlib
import json
import uuid
@@ -39,6 +41,22 @@ def _digest(value: Mapping[str, object]) -> str:
return hashlib.sha256(canonical_json(dict(value)).encode("utf-8")).hexdigest()
def _canonical_observation_value(value: object) -> object:
"""Convert a typed readback observation to stable JSON data."""
if isinstance(value, datetime):
if value.tzinfo is None or value.utcoffset() is None:
raise ConflictError("exact host closure time is not timezone-aware")
return value.astimezone(timezone.utc).isoformat().replace("+00:00", "Z")
if isinstance(value, Mapping):
return {
str(key): _canonical_observation_value(item)
for key, item in value.items()
}
if isinstance(value, (list, tuple)):
return [_canonical_observation_value(item) for item in value]
return value
@dataclass(frozen=True)
class AuthoritativeRootActionPlan:
review_id: str
@@ -265,6 +283,37 @@ class RootActionRunResult:
detail: str | None = None
@dataclass(frozen=True)
class ExactRootActionBinding:
"""Trusted host resolution from a C6 operation to an existing root review."""
review_id: str
mission_id: str
root_task_id: str
root_revision: int
plan_digest: str
execution_key: str
def __post_init__(self) -> None:
if any(
not isinstance(value, str) or not value
for value in (
self.review_id,
self.mission_id,
self.root_task_id,
self.plan_digest,
self.execution_key,
)
):
raise ValueError("exact root action binding identity required")
if (
isinstance(self.root_revision, bool)
or not isinstance(self.root_revision, int)
or self.root_revision < 0
):
raise ValueError("exact root action revision is invalid")
class RootActionExecutor(Protocol):
def reconcile(
self,
@@ -278,7 +327,7 @@ class RootActionExecutor(Protocol):
plan: AuthoritativeRootActionPlan,
*,
before_external_call: Callable[[], None],
) -> None: ...
) -> Mapping[str, object] | None: ...
def commit_effect(
self,
@@ -292,6 +341,7 @@ PlanBuilder = Callable[[RootReviewRecord], AuthoritativeRootActionPlan]
Rereviewer = Callable[
[AuthoritativeRootActionPlan, Mapping[str, object] | None], RootActionRereview
]
ExactReviewBindingProvider = Callable[[object], ExactRootActionBinding]
class RootActionConsumer:
@@ -304,12 +354,14 @@ class RootActionConsumer:
plan_builder: PlanBuilder,
rereviewer: Rereviewer,
executor: RootActionExecutor | None,
exact_review_binding_provider: ExactReviewBindingProvider | None = None,
clock: Callable[[], datetime] | None = None,
) -> None:
) -> Mapping[str, object]:
self.store = store
self._plan_builder = plan_builder
self._rereviewer = rereviewer
self._executor = executor
self._exact_review_binding_provider = exact_review_binding_provider
self._clock = clock or (lambda: datetime.now(tz=timezone.utc))
@staticmethod
@@ -416,9 +468,20 @@ class RootActionConsumer:
raise ConflictError("root action claim renewal CAS lost")
def _claim_next(
self, *, consumer_id: str, now: float, lease_seconds: int
self,
*,
consumer_id: str,
now: float,
lease_seconds: int,
exact_binding: ExactRootActionBinding | None = None,
) -> RootActionClaim | RootActionRunResult | None:
with self.store._transaction():
exact_sql = " AND d.review_id=?" if exact_binding is not None else ""
args: tuple[object, ...] = (
(now, exact_binding.review_id)
if exact_binding is not None
else (now,)
)
rows = self.store.conn.execute(
"SELECT d.*,r.record_json,r.root_revision_after "
"FROM task_root_action_dispatches d JOIN task_root_reviews r "
@@ -426,11 +489,13 @@ class RootActionConsumer:
"WHERE d.status IN ('EFFECT_UNKNOWN','DISPATCHED',"
"'EXECUTED_PENDING_REREVIEW','CLAIMED','PENDING') "
"AND (d.retry_at IS NULL OR d.retry_at<=?) "
+ exact_sql
+ " "
"ORDER BY r.root_revision_after DESC,CASE d.status "
"WHEN 'EFFECT_UNKNOWN' THEN 0 WHEN 'DISPATCHED' THEN 0 "
"WHEN 'EXECUTED_PENDING_REREVIEW' THEN 1 "
"WHEN 'CLAIMED' THEN 2 ELSE 3 END,d.created_at ASC",
(now,),
args,
).fetchall()
for row in rows:
if (
@@ -443,7 +508,11 @@ class RootActionConsumer:
}
and float(row["claim_expires_at"] or 0) > now
):
continue
if not (
exact_binding is not None
and row["claim_owner"] == consumer_id
):
continue
root_task_id = str(row["root_task_id"])
active = self.store.conn.execute(
"SELECT * FROM task_root_action_fences WHERE root_task_id=?",
@@ -488,6 +557,43 @@ class RootActionConsumer:
or plan.action != review.next_legal_action
):
raise ConflictError("root action plan/review authority mismatch")
host_effect = self.store.conn.execute(
"SELECT host_effect_key FROM task_root_action_effects "
"WHERE execution_key=?",
(plan.execution_key,),
).fetchone()
if (
exact_binding is None
and host_effect is not None
and host_effect["host_effect_key"] is not None
):
# A C6-prepared action may only be resumed through the
# exact facade, which supplies fresh signed authority.
continue
if exact_binding is not None and (
plan.review_id != exact_binding.review_id
or plan.mission_id != exact_binding.mission_id
or plan.root_task_id != exact_binding.root_task_id
or plan.root_revision != exact_binding.root_revision
or plan.plan_digest != exact_binding.plan_digest
or plan.execution_key != exact_binding.execution_key
):
raise ConflictError("trusted exact root action binding mismatch")
if (
exact_binding is not None
and row["claim_owner"] == consumer_id
and float(row["claim_expires_at"] or 0) > now
):
return RootActionClaim(
plan=plan,
consumer_id=consumer_id,
claim_token=str(row["claim_token"]),
fence=int(row["claim_fence"]),
lease_expires_at=float(row["claim_expires_at"]),
lease_seconds=lease_seconds,
reconcile_only=row["status"]
in {"EFFECT_UNKNOWN", "DISPATCHED"},
)
if active is not None and active["active_review_id"] not in {
None,
row["review_id"],
@@ -582,7 +688,13 @@ class RootActionConsumer:
return claim
return None
def _prepare_effect(self, claim: RootActionClaim, now: float):
def _prepare_effect(
self,
claim: RootActionClaim,
now: float,
*,
host_binding: Mapping[str, object] | None = None,
):
plan = claim.plan
request_digest = _digest(
{
@@ -635,6 +747,41 @@ class RootActionConsumer:
if effect["receipt_json"] is not None
else None
)
if host_binding is not None:
host_json = canonical_json(dict(host_binding))
host_digest = hashlib.sha256(host_json.encode("utf-8")).hexdigest()
host_effect_key = host_binding.get("effect_key")
if not isinstance(host_effect_key, str) or not host_effect_key:
raise ConflictError("exact host effect key is invalid")
current = self.store.conn.execute(
"SELECT host_effect_key,host_binding_json,host_binding_digest "
"FROM task_root_action_effects WHERE execution_key=?",
(plan.execution_key,),
).fetchone()
if current["host_effect_key"] is None:
try:
changed = self.store.conn.execute(
"UPDATE task_root_action_effects SET host_effect_key=?,"
"host_binding_json=?,host_binding_digest=?,updated_at=? "
"WHERE execution_key=? AND host_effect_key IS NULL",
(
host_effect_key,
host_json,
host_digest,
now,
plan.execution_key,
),
)
except Exception as exc:
raise ConflictError("exact host effect key already bound") from exc
if changed.rowcount != 1:
raise ConflictError("exact host binding CAS lost")
elif (
current["host_effect_key"] != host_effect_key
or current["host_binding_json"] != host_json
or current["host_binding_digest"] != host_digest
):
raise ConflictError("exact host action binding conflict")
status = (
"EXECUTED_PENDING_REREVIEW"
if effect_state == "SUCCEEDED"
@@ -654,6 +801,52 @@ class RootActionConsumer:
)
return effect_state, receipt, effect_phase
def _record_writer_lease(
self,
claim: RootActionClaim,
lease: Mapping[str, object] | None,
now: float,
) -> None:
if lease is None:
return
lease_json = canonical_json(dict(lease))
lease_digest = hashlib.sha256(lease_json.encode("utf-8")).hexdigest()
with self.store._transaction():
changed = self.store.conn.execute(
"UPDATE task_root_action_effects SET writer_lease_json=?,"
"writer_lease_digest=?,updated_at=? WHERE execution_key=? "
"AND writer_lease_json IS NULL",
(lease_json, lease_digest, now, claim.plan.execution_key),
)
if changed.rowcount != 1:
row = self.store.conn.execute(
"SELECT writer_lease_json,writer_lease_digest "
"FROM task_root_action_effects WHERE execution_key=?",
(claim.plan.execution_key,),
).fetchone()
if (
row is None
or row["writer_lease_json"] != lease_json
or row["writer_lease_digest"] != lease_digest
):
raise ConflictError("WriterLease durable binding conflict")
@staticmethod
def _validate_exact_writer_lease(lease: object) -> Mapping[str, object]:
if not isinstance(lease, Mapping):
raise ConflictError("exact host WriterLease identity unavailable")
lease_id = lease.get("writer_lease_id")
fence = lease.get("writer_lease_fence")
if (
not isinstance(lease_id, str)
or not lease_id.strip()
or isinstance(fence, bool)
or not isinstance(fence, int)
or fence < 0
):
raise ConflictError("exact host WriterLease identity unavailable")
return lease
def _transition_effect_phase(
self,
claim: RootActionClaim,
@@ -910,18 +1103,25 @@ class RootActionConsumer:
plan.execution_key,
)
def run_once(
def _run(
self,
*,
consumer_id: str,
now: datetime,
lease_seconds: int = 60,
exact_binding: ExactRootActionBinding | None = None,
host_binding: Mapping[str, object] | None = None,
prepare_only: bool = False,
external_revalidator: Callable[[], None] | None = None,
) -> RootActionRunResult:
if not consumer_id or lease_seconds < 1:
raise ValueError("invalid root action consumer claim")
now_value = self._now(now)
claimed = self._claim_next(
consumer_id=consumer_id, now=now_value, lease_seconds=lease_seconds
consumer_id=consumer_id,
now=now_value,
lease_seconds=lease_seconds,
exact_binding=exact_binding,
)
if claimed is None:
return RootActionRunResult(RootActionRunState.IDLE)
@@ -949,7 +1149,11 @@ class RootActionConsumer:
claim, status="EXECUTED_PENDING_REREVIEW", now=now_value
)
return self._rereview(claim, now_value)
effect_state, receipt, effect_phase = self._prepare_effect(claim, now_value)
effect_state, receipt, effect_phase = (
self._prepare_effect(claim, now_value)
if host_binding is None
else self._prepare_effect(claim, now_value, host_binding=host_binding)
)
if effect_state == "SUPERSEDED":
return RootActionRunResult(
RootActionRunState.CLOSED,
@@ -970,6 +1174,13 @@ class RootActionConsumer:
extra_args=(receipt_json, _digest(receipt)),
)
return self._rereview(claim, now_value)
if prepare_only:
return RootActionRunResult(
RootActionRunState.EXECUTED,
plan.review_id,
plan.execution_key,
"PREPARED",
)
if self._executor is None:
with self.store._transaction():
self._set_dispatch(
@@ -986,9 +1197,10 @@ class RootActionConsumer:
plan.execution_key,
"ROOT_ACTION_EXECUTOR_UNAVAILABLE",
)
before_external_call = lambda: self._assert_live_claim(
claim, now_value, renew=True
)
def before_external_call() -> None:
self._assert_live_claim(claim, now_value, renew=True)
if external_revalidator is not None:
external_revalidator()
if effect_phase == "BEFORE_LEASE":
# Admission and the immutable plan are the authority for this one
# first send. A pre-lease GET cannot prove absence: the live
@@ -997,10 +1209,13 @@ class RootActionConsumer:
claim, expected="BEFORE_LEASE", target="LEASE_INTENT", now=now_value
)
before_external_call()
self._executor.acquire_lease(
lease = self._executor.acquire_lease(
plan, before_external_call=before_external_call
)
before_external_call()
if host_binding is not None:
lease = self._validate_exact_writer_lease(lease)
self._record_writer_lease(claim, lease, now_value)
self._transition_effect_phase(
claim, expected="LEASE_INTENT", target="LEASE_ACQUIRED", now=now_value
)
@@ -1033,6 +1248,594 @@ class RootActionConsumer:
return result
return self._rereview(claim, now_value)
def run_once(
self,
*,
consumer_id: str,
now: datetime,
lease_seconds: int = 60,
) -> RootActionRunResult:
return self._run(
consumer_id=consumer_id,
now=now,
lease_seconds=lease_seconds,
)
def configure_exact_review_binding_provider(
self, provider: ExactReviewBindingProvider
) -> None:
if not callable(provider):
raise TypeError("trusted exact review binding provider required")
if self._exact_review_binding_provider is not None:
raise ConflictError("exact review binding provider already configured")
self._exact_review_binding_provider = provider
def assert_exact_owner_configured(self) -> None:
if self._exact_review_binding_provider is None or self._executor is None:
raise ConflictError("exact root action owner is not configured")
def _exact_resolution(self, binding: object) -> ExactRootActionBinding:
self.assert_exact_owner_configured()
assert self._exact_review_binding_provider is not None
resolved = self._exact_review_binding_provider(binding)
if not isinstance(resolved, ExactRootActionBinding):
raise ConflictError("trusted exact review binding is invalid")
return resolved
def _exact_plan(
self, binding: object, resolved: ExactRootActionBinding
) -> AuthoritativeRootActionPlan:
from .operation_view import OperationBinding
if not isinstance(binding, OperationBinding):
raise TypeError("operation binding required")
row = self.store.conn.execute(
"SELECT d.*,r.record_json FROM task_root_action_dispatches d "
"JOIN task_root_reviews r ON r.review_id=d.review_id "
"WHERE d.review_id=?",
(resolved.review_id,),
).fetchone()
if row is None:
raise ConflictError("trusted exact root review is unavailable")
if row["plan_json"]:
raw = json.loads(str(row["plan_json"]))
if not isinstance(raw, Mapping):
raise ConflictError("persisted root action plan is invalid")
plan = AuthoritativeRootActionPlan.from_dict(raw)
else:
plan = self._plan_builder(self._review(row))
if (
plan.review_id != resolved.review_id
or plan.mission_id != resolved.mission_id
or plan.root_task_id != resolved.root_task_id
or plan.root_revision != resolved.root_revision
or plan.plan_digest != resolved.plan_digest
or plan.execution_key != resolved.execution_key
or plan.target_task_id != binding.task_id
or plan.target_job_id != binding.job_id
or plan.external_idempotency_key != binding.effect_key
):
raise ConflictError("C6 operation/root action authority mismatch")
for name in ("writer_lease_request", "commit_request"):
request = plan.effect_payload.get(name)
if not isinstance(request, Mapping):
raise ConflictError(f"exact root action {name} unavailable")
expected = {
"task_id": binding.task_id,
"job_id": binding.job_id,
"repository": binding.repository,
"expected_branch_oid": binding.source_oid,
"target_branch": binding.target_branch,
"generation": binding.generation,
}
allowed = set(expected)
if name == "writer_lease_request":
expected.update(
{"branch_mode": "CREATE_NEW", "base_oid": binding.source_oid}
)
allowed.update({"branch_mode", "base_oid"})
else:
allowed.update({"files", "message"})
if set(request) != allowed:
raise ConflictError(f"exact root action {name} fields mismatch")
if any(request.get(key) != value for key, value in expected.items()):
raise ConflictError(f"exact root action {name} binding mismatch")
return plan
@staticmethod
def _c6_commit_mutation(
*, binding, contract, patch_bytes: bytes, commit_message: str,
plan: AuthoritativeRootActionPlan,
) -> dict[str, object]:
"""Bind the real Bridge mutation to the signed C6 operation identity."""
from .operation_view import binding_digest
if hashlib.sha256(patch_bytes).hexdigest() != contract.patch_digest:
raise ConflictError("C6 patch bytes digest mismatch")
commit = plan.effect_payload.get("commit_request")
if not isinstance(commit, Mapping):
raise ConflictError("exact root action commit request unavailable")
if commit.get("message") != commit_message:
raise ConflictError("C6 commit message/root mutation mismatch")
raw_files = commit.get("files")
if not isinstance(raw_files, (list, tuple)) or isinstance(
raw_files, (str, bytes)
):
raise ConflictError("C6 exact commit files require Bridge entry array")
actual: list[dict[str, str]] = []
seen: set[str] = set()
seen_folded: set[str] = set()
for raw in raw_files:
if not isinstance(raw, Mapping) or set(raw) != {"path", "content_base64"}:
raise ConflictError("C6 exact commit file entry is invalid")
path = raw.get("path")
encoded = raw.get("content_base64")
if not isinstance(path, str) or not path or not isinstance(encoded, str):
raise ConflictError("C6 exact commit file entry is invalid")
if path in seen or path.casefold() in seen_folded:
raise ConflictError("C6 exact commit files contain duplicate paths")
try:
content = base64.b64decode(encoded.encode("ascii"), validate=True)
except (UnicodeEncodeError, binascii.Error, ValueError) as exc:
raise ConflictError("C6 exact commit file content is invalid") from exc
seen.add(path)
seen_folded.add(path.casefold())
actual.append({"path": path, "sha256": hashlib.sha256(content).hexdigest()})
actual.sort(key=lambda item: item["path"])
expected = [
{"path": path, "sha256": digest}
for path, digest in contract.file_digests
]
if any(item["sha256"] == "DELETED" for item in expected) or actual != expected:
raise ConflictError("C6 patch contract/root files mismatch")
mutation = {
"schema": "aios.c6.commit-mutation.v1",
"files": actual,
"message": commit_message,
}
mutation_digest = hashlib.sha256(
canonical_json(mutation).encode("utf-8")
).hexdigest()
expected_projection = {
"schema": "aios.c6.root-plan-binding.v1",
"operation_binding_digest": binding_digest(binding),
"plan_digest": binding.plan_digest,
"patch_digest": contract.patch_digest,
"commit_mutation_digest": mutation_digest,
}
projection = plan.effect_payload.get("c6_operation_binding")
if not isinstance(projection, Mapping) or dict(projection) != expected_projection:
raise ConflictError("C6 operation/root plan mutation identity mismatch")
return {**mutation, "digest": mutation_digest}
@staticmethod
def _host_binding_document(
*, binding, claim, authority, contract, patch_bytes: bytes, commit_message: str,
plan: AuthoritativeRootActionPlan,
) -> dict[str, object]:
from .operation_view import binding_digest
mutation = RootActionConsumer._c6_commit_mutation(
binding=binding,
contract=contract,
patch_bytes=patch_bytes,
commit_message=commit_message,
plan=plan,
)
return {
"schema": "aios.c6.root-action-host-binding.v2",
"action_id": plan.execution_key,
"operation_binding": asdict(binding),
"binding_digest": binding_digest(binding),
"effect_key": binding.effect_key,
"claim_receipt": claim.to_dict(),
"authorization_ref": authority.authorization_ref,
"authority_key_id": authority.authority_key_id,
"authority_key_fingerprint": authority.authority_key_fingerprint,
"authority_record_sha256": authority.authority_record_sha256,
"authority_generation": authority.authority_generation,
"task_generation": authority.task_generation,
"expected_branch_oid": authority.expected_branch_oid,
"review_id": plan.review_id,
"mission_id": plan.mission_id,
"root_task_id": plan.root_task_id,
"root_revision": plan.root_revision,
"plan_digest": plan.plan_digest,
"root_execution_key": plan.execution_key,
"patch_digest": contract.patch_digest,
"c6_plan_digest": binding.plan_digest,
"commit_mutation_digest": mutation["digest"],
"commit_message_sha256": hashlib.sha256(
commit_message.encode("utf-8")
).hexdigest(),
}
@staticmethod
def _validate_host_document(value: object) -> dict[str, object]:
if not isinstance(value, Mapping):
raise ConflictError("exact host binding is unavailable")
result = dict(value)
if result.get("schema") != "aios.c6.root-action-host-binding.v2":
raise ConflictError("exact host binding schema is invalid")
return result
def _action_from_row(self, row):
from .host_patch_executor import HostPatchAction
raw = str(row["host_binding_json"] or "")
digest = hashlib.sha256(raw.encode("utf-8")).hexdigest()
if not raw or row["host_binding_digest"] != digest:
raise ConflictError("exact host binding digest mismatch")
host = self._validate_host_document(json.loads(raw))
if host.get("effect_key") != row["host_effect_key"]:
raise ConflictError("exact host effect key mismatch")
lease = (
json.loads(str(row["writer_lease_json"]))
if row["writer_lease_json"] is not None
else None
)
receipt = (
json.loads(str(row["receipt_json"]))
if row["receipt_json"] is not None
else None
)
if row["state"] == "SUCCEEDED":
state = "COMMITTED"
elif row["state"] == "BLOCKED":
state = "FAILED_FINAL"
elif row["effect_phase"] == "BEFORE_LEASE":
state = "PREPARED"
else:
state = "EFFECT_UNKNOWN"
lease_id = None
lease_fence = None
if isinstance(lease, Mapping):
lease_id = lease.get("writer_lease_id")
lease_fence = lease.get("writer_lease_fence")
return HostPatchAction(
action_id=str(host["action_id"]),
state=state,
binding_digest=str(host["binding_digest"]),
effect_key=str(host["effect_key"]),
authorization_ref=str(host["authorization_ref"]),
authority_key_id=str(host["authority_key_id"]),
authority_key_fingerprint=str(host["authority_key_fingerprint"]),
authority_record_sha256=str(host["authority_record_sha256"]),
claim_id=str(host["claim_receipt"]["claim_id"]),
claim_fence=int(host["claim_receipt"]["receipt_revision"]),
task_generation=int(host["task_generation"]),
authority_generation=int(host["authority_generation"]),
expected_branch_oid=str(host["expected_branch_oid"]),
claim_receipt=dict(host["claim_receipt"]),
writer_lease_id=(str(lease_id) if lease_id is not None else None),
writer_lease_fence=(int(lease_fence) if lease_fence is not None else None),
result_oid=(
str(receipt["commit_oid"])
if isinstance(receipt, Mapping) and receipt.get("commit_oid") is not None
else None
),
receipt_ref=(
str(row["receipt_digest"])
if row["receipt_digest"] is not None
else None
),
)
def read_exact(self, effect_key: str):
row = self.store.conn.execute(
"SELECT * FROM task_root_action_effects WHERE host_effect_key=?",
(effect_key,),
).fetchone()
return None if row is None else self._action_from_row(row)
def iter_pending_exact(self, *, limit: int = 16):
"""Enumerate durable host intents from the existing effect ledger."""
from .operation_view import OperationBinding
if isinstance(limit, bool) or not isinstance(limit, int) or not 1 <= limit <= 64:
raise ConflictError("bounded exact pending limit required")
rows = self.store.conn.execute(
"SELECT * FROM task_root_action_effects "
"WHERE host_effect_key IS NOT NULL AND host_binding_json IS NOT NULL "
"AND host_closure_digest IS NULL "
"ORDER BY created_at,execution_key LIMIT ?",
(limit,),
).fetchall()
result = []
for row in rows:
action = self._action_from_row(row)
if action.state not in {
"PREPARED", "IN_PROGRESS", "EFFECT_UNKNOWN", "COMMITTED"
}:
continue
raw = self._validate_host_document(
json.loads(str(row["host_binding_json"]))
).get("operation_binding")
if not isinstance(raw, Mapping):
raise ConflictError("pending exact operation binding unavailable")
try:
operation = OperationBinding(**dict(raw))
except (TypeError, ValueError) as exc:
raise ConflictError("pending exact operation binding invalid") from exc
result.append((operation, action))
return tuple(result)
def mark_exact_closed(self, *, binding, action_id: str, observation) -> None:
from .operation_view import (
ClosureObservation,
binding_digest,
derive_closure,
)
if not isinstance(observation, ClosureObservation):
raise ConflictError("typed exact host closure observation required")
observed_at = self._clock()
now = self._now(observed_at)
closure = derive_closure(binding, observation, now=observed_at)
if (
closure.execution_recorded,
closure.independently_verified,
closure.business_accepted,
closure.outcome,
closure.reason_code,
) != (True, True, True, "SUCCEEDED", "THREE_LAYER_CLOSURE_VALID"):
raise ConflictError("exact host closure is incomplete")
with self.store._transaction():
row = self.store.conn.execute(
"SELECT * FROM task_root_action_effects WHERE host_effect_key=?",
(binding.effect_key,),
).fetchone()
if row is None:
raise ConflictError("exact host action unavailable")
action = self._action_from_row(row)
expected_binding_digest = binding_digest(binding)
effect = observation.effect
verification = observation.verification
acceptance = observation.acceptance
verifier_receipt_ref = (
verification.verification_receipt_ref
or verification.receipt_ref
)
if (
action.action_id != action_id
or action.state != "COMMITTED"
or action.binding_digest != expected_binding_digest
or not action.receipt_ref
or effect.binding_digest != expected_binding_digest
or effect.receipt_ref != action.receipt_ref
or effect.result_artifact_digest is None
or verification.binding_digest != expected_binding_digest
or verification.receipt_ref != action.receipt_ref
or verification.result_artifact_digest
!= effect.result_artifact_digest
or not verifier_receipt_ref
or acceptance.binding_digest != expected_binding_digest
or acceptance.receipt_ref != action.receipt_ref
or acceptance.result_artifact_digest
!= effect.result_artifact_digest
or acceptance.verifier_receipt_ref != verifier_receipt_ref
):
raise ConflictError("exact host closure/action binding mismatch")
observation_value = _canonical_observation_value(asdict(observation))
observation_raw = canonical_json(observation_value)
observation_digest = hashlib.sha256(
observation_raw.encode("utf-8")
).hexdigest()
document = {
"schema": "aios.c6.three-layer-closure.v2",
"binding_digest": action.binding_digest,
"effect_key": action.effect_key,
"action_id": action.action_id,
"root_receipt_ref": action.receipt_ref,
"effect_receipt_ref": effect.receipt_ref,
"result_artifact_digest": effect.result_artifact_digest,
"verification_receipt_ref": verifier_receipt_ref,
"verifier_id": verification.verifier_id,
"acceptance_revision": acceptance.acceptance_revision,
"acceptor_id": acceptance.acceptor_id,
"expected_acceptance_revision": (
observation.expected_acceptance_revision
),
"expected_acceptor_id": observation.expected_acceptor_id,
"closure_observation_digest": observation_digest,
"execution_recorded": closure.execution_recorded,
"independently_verified": closure.independently_verified,
"business_accepted": closure.business_accepted,
"outcome": closure.outcome,
"reason_code": closure.reason_code,
}
raw = canonical_json(document)
digest = hashlib.sha256(raw.encode("utf-8")).hexdigest()
changed = self.store.conn.execute(
"UPDATE task_root_action_effects SET host_closure_json=?,"
"host_closure_digest=?,host_closure_observed_at=?,updated_at=? "
"WHERE host_effect_key=? AND state='SUCCEEDED' "
"AND host_binding_json=? AND host_binding_digest=? "
"AND receipt_digest=? AND host_closure_digest IS NULL",
(
raw, digest, now, now, binding.effect_key,
row["host_binding_json"], row["host_binding_digest"],
action.receipt_ref,
),
)
if changed.rowcount != 1:
current = self.store.conn.execute(
"SELECT host_closure_json,host_closure_digest "
"FROM task_root_action_effects WHERE host_effect_key=?",
(binding.effect_key,),
).fetchone()
if (
current is None
or current["host_closure_json"] != raw
or current["host_closure_digest"] != digest
):
raise ConflictError("exact host closure CAS conflict")
@staticmethod
def _assert_action_authority(action, binding, authority) -> None:
from .operation_view import binding_digest
if (
action.binding_digest != binding_digest(binding)
or action.effect_key != binding.effect_key
or action.authorization_ref != authority.authorization_ref
or action.authority_key_id != authority.authority_key_id
or action.authority_key_fingerprint != authority.authority_key_fingerprint
or action.authority_record_sha256 != authority.authority_record_sha256
or action.authority_generation != authority.authority_generation
or action.task_generation != authority.task_generation
or action.claim_id != authority.claim_id
or action.claim_fence != authority.claim_fence
or action.expected_branch_oid != authority.expected_branch_oid
):
raise ConflictError("exact host action authority mismatch")
def prepare_exact(
self,
*,
binding,
claim,
authority,
contract,
patch_bytes: bytes,
commit_message: str,
):
resolved = self._exact_resolution(binding)
plan = self._exact_plan(binding, resolved)
host = self._host_binding_document(
binding=binding,
claim=claim,
authority=authority,
contract=contract,
patch_bytes=patch_bytes,
commit_message=commit_message,
plan=plan,
)
now = datetime.now(tz=timezone.utc)
lease_seconds = max(1, min(60, int(authority.expires_at - now.timestamp())))
result = self._run(
consumer_id=f"c6:{claim.claim_id}",
now=now,
lease_seconds=lease_seconds,
exact_binding=resolved,
host_binding=host,
prepare_only=True,
)
if result.detail != "PREPARED":
raise ConflictError("exact root action was not prepared")
action = self.read_exact(binding.effect_key)
if action is None:
raise ConflictError("prepared exact root action is unavailable")
self._assert_action_authority(action, binding, authority)
return action
def _claim_exact_existing(self, binding, action, authority, now: float):
resolved = self._exact_resolution(binding)
plan = self._exact_plan(binding, resolved)
if plan.execution_key != action.action_id:
raise ConflictError("exact root action identity changed")
claimed = self._claim_next(
consumer_id=f"c6:{action.claim_id}",
now=now,
lease_seconds=60,
exact_binding=resolved,
)
if not isinstance(claimed, RootActionClaim):
raise ConflictError("exact root action claim unavailable")
self._assert_action_authority(action, binding, authority)
return claimed
def reconcile_exact(
self,
*,
action_id: str,
binding,
authority,
before_external_call: Callable[[], None],
):
action = self.read_exact(binding.effect_key)
if action is None or action.action_id != action_id:
raise ConflictError("exact root action unavailable")
self._assert_action_authority(action, binding, authority)
if action.state in {"COMMITTED", "FAILED_FINAL", "PREPARED"}:
return action
now = self._now(self._clock())
claim = self._claim_exact_existing(binding, action, authority, now)
_state, _receipt, phase = self._prepare_effect(claim, now)
if phase in {"BEFORE_LEASE", "LEASE_ACQUIRED"}:
return self.read_exact(binding.effect_key)
if self._executor is None:
raise ConflictError("root action executor unavailable")
def revalidate() -> None:
self._assert_live_claim(claim, now, renew=True)
before_external_call()
revalidate()
observation = self._executor.reconcile(
claim.plan, before_external_call=revalidate
)
revalidate()
result = self._record_observation(claim, observation, now)
if result is None:
self._rereview(claim, now)
observed = self.read_exact(binding.effect_key)
if observed is None:
raise ConflictError("reconciled exact root action is unavailable")
return observed
def execute_prepared(
self,
*,
action_id: str,
binding,
claim,
authority,
contract,
patch_bytes: bytes,
commit_message: str,
before_external_call: Callable[[], None],
):
action = self.read_exact(binding.effect_key)
if action is None or action.action_id != action_id:
raise ConflictError("exact root action unavailable")
self._assert_action_authority(action, binding, authority)
if action.state != "PREPARED":
return action
resolved = self._exact_resolution(binding)
plan = self._exact_plan(binding, resolved)
host = self._host_binding_document(
binding=binding,
claim=claim,
authority=authority,
contract=contract,
patch_bytes=patch_bytes,
commit_message=commit_message,
plan=plan,
)
persisted = self.store.conn.execute(
"SELECT host_binding_json FROM task_root_action_effects "
"WHERE host_effect_key=?",
(binding.effect_key,),
).fetchone()
if persisted is None or persisted["host_binding_json"] != canonical_json(host):
raise ConflictError("prepared exact host binding changed")
self._run(
consumer_id=f"c6:{claim.claim_id}",
now=self._clock(),
lease_seconds=60,
exact_binding=resolved,
host_binding=host,
external_revalidator=before_external_call,
)
completed = self.read_exact(binding.effect_key)
if completed is None:
raise ConflictError("exact root action completion unavailable")
return completed
class PublicBridgeRootActionExecutor:
"""Contract adapter for the authenticated public Bridge operation surface."""
@@ -1329,6 +2132,7 @@ class PublicBridgeRootActionExecutor:
raise ConflictError("Bridge writer lease response binding mismatch")
if "job_id" in result and result["job_id"] != plan.target_job_id:
raise ConflictError("Bridge writer lease response Job mismatch")
return dict(result)
def commit_effect(
self,
@@ -15,7 +15,7 @@ claim transaction / repository operation.
from __future__ import annotations
from dataclasses import dataclass, asdict
from typing import Any, Mapping, Protocol, Sequence
from typing import Any, Callable, Iterable, Mapping, Protocol, Sequence
import hashlib
import time
import uuid
@@ -94,6 +94,7 @@ class ClaimConditions:
authorization_revision: str
scope_digest: str
capacity_limit: int
operation_digest: str | None = None
def to_dict(self) -> dict[str, object]:
return asdict(self)
@@ -121,6 +122,7 @@ class SchedulabilityObservation:
dependencies_satisfied: bool
freeze_blocked: bool
conflicting_lease: bool
operation_digest: str | None = None
@property
def capacity_remaining(self) -> int:
@@ -128,7 +130,7 @@ class SchedulabilityObservation:
def stable_binding(self) -> dict[str, object]:
"""Fields whose change invalidates an earlier proof."""
return {
binding = {
"task_id": self.task_id,
"task_revision": self.task_revision,
"task_generation": self.task_generation,
@@ -149,6 +151,9 @@ class SchedulabilityObservation:
"conflicting_lease": self.conflicting_lease,
"heartbeat_fresh": self.heartbeat_fresh,
}
if self.operation_digest is not None:
binding["operation_digest"] = self.operation_digest
return binding
def claim_conditions(self) -> ClaimConditions:
return ClaimConditions(
@@ -161,6 +166,7 @@ class SchedulabilityObservation:
authorization_revision=self.authorization_revision,
scope_digest=self.scope_digest,
capacity_limit=self.capacity_limit,
operation_digest=self.operation_digest,
)
@@ -194,7 +200,11 @@ class SchedulabilityProof:
if ttl_seconds <= 0 or ttl_seconds > 300:
raise ValueError("schedulability proof ttl must be in (0,300]")
payload = {
"schema": "aios.schedulability-proof.v2",
"schema": (
"aios.schedulability-proof.v3"
if observation.operation_digest is not None
else "aios.schedulability-proof.v2"
),
"proof_id": proof_id or f"sched-{uuid.uuid4()}",
"task_id": _text(task_id, "task_id"),
"worker_id": _text(worker_id, "worker_id"),
@@ -301,12 +311,18 @@ class SchedulabilityService:
clock=time.time,
proof_ttl_seconds: float = 30.0,
heartbeat_grace_factor: float = 3.0,
capability_matcher: Callable[[Iterable[str], Iterable[str]], bool] | None = None,
) -> None:
self.authority = authority
self.proofs = proofs
self.clock = clock
self.proof_ttl_seconds = proof_ttl_seconds
self.heartbeat_grace_factor = heartbeat_grace_factor
if capability_matcher is None:
from .capabilities import has_capabilities
capability_matcher = has_capabilities
self.capability_matcher = capability_matcher
@staticmethod
def _caps(value: object) -> tuple[str, ...]:
@@ -366,9 +382,8 @@ class SchedulabilityService:
blockers.append(capacity_error)
if not fresh:
blockers.append(Blocker("STALE_OR_INACTIVE_WORKER", "worker", "worker heartbeat is not fresh"))
missing = sorted(set(required) - set(offered))
if missing:
blockers.append(Blocker("MISSING_CAPABILITY", "worker", ",".join(missing)))
if not self.capability_matcher(offered, required):
blockers.append(Blocker("MISSING_CAPABILITY", "worker", ",".join(required)))
if not scope_ok:
blockers.append(Blocker("EXACT_SCOPE_MISMATCH", "authorization", "worker/task signed scope mismatch"))
if not auth_allowed:
@@ -403,6 +418,11 @@ class SchedulabilityService:
dependencies_satisfied=deps,
freeze_blocked=freeze,
conflicting_lease=lease,
operation_digest=(
str(task["operation_digest"])
if task.get("operation_digest") is not None
else None
),
)
return obs, tuple(blockers)
@@ -468,7 +488,14 @@ class SchedulabilityService:
return proof
@staticmethod
def _validate_claim_receipt(result: Mapping[str, Any]) -> None:
def _validate_claim_receipt(
result: Mapping[str, Any], proof: SchedulabilityProof
) -> None:
if proof.schema == "aios.schedulability-proof.v3":
from .schedulability_store_adapter import validate_task_plane_receipt
validate_task_plane_receipt(result, proof=proof)
return
required = ("claim_id", "lease_id", "fencing_generation", "receipt_revision")
missing = [name for name in required if result.get(name) in (None, "")]
if missing:
@@ -493,6 +520,11 @@ class SchedulabilityService:
raise SchedulabilityChanged("proof expired")
if proof.worker_id != worker_id or proof.actor_identity != actor_identity:
raise SchedulabilityChanged("proof identity mismatch")
if (
getattr(self.authority, "requires_v3_proof", False)
and proof.schema != "aios.schedulability-proof.v3"
):
raise SchedulabilityChanged("v3 claim authority requires v3 proof")
current, blockers = self.observe(proof.task_id, worker_id, actor_identity)
if blockers:
raise SchedulabilityChanged(
@@ -509,5 +541,5 @@ class SchedulabilityService:
lease_seconds=lease_seconds,
schedulability_proof_digest=proof.proof_digest,
)
self._validate_claim_receipt(result)
self._validate_claim_receipt(result, proof)
return result
@@ -0,0 +1,691 @@
"""Task Plane-backed atomic schedulability authority for the C6 pilot.
This adapter introduces no lease or authority database. It is enabled only
when a host supplies a binding that holds the TaskPlane transaction together
with its real authorization and freeze boundary.
"""
from __future__ import annotations
from contextlib import AbstractContextManager, contextmanager
from dataclasses import asdict, dataclass
from typing import Any, Callable, Iterable, Literal, Mapping, Protocol
import math
import time
from .capabilities import has_capabilities
from .errors import ConflictError, LockedError, ServiceUnavailableError
from .schedulability import ClaimConditions, SchedulabilityChanged, SchedulabilityProof
from .store import TaskPlaneStore
_RECEIPT_SCHEMA = "aios.task-plane-claim-receipt.v3"
_RECEIPT_FIELDS = frozenset(
{
"schema",
"authority_namespace",
"task_id",
"claim_id",
"worker_id",
"worker_identity",
"claimed_revision",
"receipt_revision",
"attempt",
"operation_digest",
"lease_until",
"proof_digest",
}
)
__all__ = [
"AuthorityAtomicBindingUnavailable",
"ClaimAuthorityBinding",
"ClaimTransaction",
"ScopedLeaseController",
"TaskPlaneClaimReceiptV3",
"TaskPlaneSchedulabilityAuthority",
"validate_task_plane_receipt",
]
class AuthorityAtomicBindingUnavailable(ServiceUnavailableError):
code = "AUTHORITY_ATOMIC_BINDING_UNAVAILABLE"
def __init__(self) -> None:
super().__init__(self.code)
class ClaimTransaction(Protocol):
"""A host-owned context that holds the store and authority lock together."""
store: TaskPlaneStore
authority_id: str
def assert_conditions(
self,
task_id: str,
worker_id: str,
actor_identity: str,
expected: ClaimConditions,
) -> None: ...
def assert_lease_holder(
self, task_id: str, claim_id: str, worker_id: str, actor_identity: str
) -> None: ...
def assert_lease_authorized(
self,
task_id: str,
claim_id: str,
worker_id: str,
actor_identity: str,
receipt: TaskPlaneClaimReceiptV3,
) -> None: ...
class ClaimAuthorityBinding(Protocol):
"""Host integration point; request-provided callbacks are never accepted."""
def claim_context(self, store: TaskPlaneStore) -> AbstractContextManager[ClaimTransaction]: ...
def read_authorization(
self, *, task: Mapping[str, Any], worker: Mapping[str, Any], actor_identity: str
) -> Mapping[str, Any]: ...
def exact_scope_digest(
self, *, task: Mapping[str, Any], worker: Mapping[str, Any], actor_identity: str
) -> tuple[bool, str]: ...
def freeze_blocked(self, task: Mapping[str, Any]) -> bool: ...
_BINDING_METHODS = (
"claim_context",
"read_authorization",
"exact_scope_digest",
"freeze_blocked",
)
def _require_binding(store: TaskPlaneStore, binding: ClaimAuthorityBinding) -> None:
"""Reject a partial host wiring before it can produce a lease effect."""
if store is None or binding is None or any(
not callable(getattr(binding, name, None)) for name in _BINDING_METHODS
):
raise AuthorityAtomicBindingUnavailable()
@dataclass(frozen=True)
class TaskPlaneClaimReceiptV3:
schema: Literal["aios.task-plane-claim-receipt.v3"]
authority_namespace: Literal["task-plane"]
task_id: str
claim_id: str
worker_id: str
worker_identity: str
claimed_revision: int
receipt_revision: int
attempt: int
operation_digest: str
lease_until: float
proof_digest: str
def to_dict(self) -> dict[str, object]:
return asdict(self)
def _text(value: object, name: str) -> str:
if not isinstance(value, str) or not value:
raise SchedulabilityChanged(f"invalid v3 receipt {name}")
return value
def _int(value: object, name: str, *, minimum: int = 0) -> int:
if isinstance(value, bool) or not isinstance(value, int) or value < minimum:
raise SchedulabilityChanged(f"invalid v3 receipt {name}")
return value
def _float(value: object, name: str) -> float:
if (
isinstance(value, bool)
or not isinstance(value, (int, float))
or not math.isfinite(float(value))
or float(value) <= 0
):
raise SchedulabilityChanged(f"invalid v3 receipt {name}")
return float(value)
def _parse_receipt(result: Mapping[str, object]) -> TaskPlaneClaimReceiptV3:
if not isinstance(result, Mapping) or set(result) != _RECEIPT_FIELDS:
raise SchedulabilityChanged("invalid v3 claim receipt fields")
if result["schema"] != _RECEIPT_SCHEMA or result["authority_namespace"] != "task-plane":
raise SchedulabilityChanged("invalid v3 claim receipt namespace")
return TaskPlaneClaimReceiptV3(
schema=_RECEIPT_SCHEMA,
authority_namespace="task-plane",
task_id=_text(result["task_id"], "task_id"),
claim_id=_text(result["claim_id"], "claim_id"),
worker_id=_text(result["worker_id"], "worker_id"),
worker_identity=_text(result["worker_identity"], "worker_identity"),
claimed_revision=_int(result["claimed_revision"], "claimed_revision"),
receipt_revision=_int(result["receipt_revision"], "receipt_revision"),
attempt=_int(result["attempt"], "attempt", minimum=1),
operation_digest=_text(result["operation_digest"], "operation_digest"),
lease_until=_float(result["lease_until"], "lease_until"),
proof_digest=_text(result["proof_digest"], "proof_digest"),
)
def validate_task_plane_receipt(
result: Mapping[str, object], *, proof: SchedulabilityProof
) -> TaskPlaneClaimReceiptV3:
"""Validate the C6 receipt without treating it as a WriterLease fence."""
if proof.schema != "aios.schedulability-proof.v3":
raise SchedulabilityChanged("v3 receipt requires v3 proof")
receipt = _parse_receipt(result)
if (
receipt.task_id != proof.task_id
or receipt.worker_id != proof.worker_id
or receipt.worker_identity != proof.observation.worker_identity
or receipt.proof_digest != proof.proof_digest
or receipt.operation_digest != proof.observation.operation_digest
or receipt.receipt_revision <= receipt.claimed_revision
or receipt.lease_until <= proof.created_at
):
raise SchedulabilityChanged("v3 claim receipt does not bind proof")
return receipt
class TaskPlaneSchedulabilityAuthority:
"""Use the existing TaskPlane store as the only claim/lease authority."""
requires_v3_proof = True
def __init__(
self,
store: TaskPlaneStore,
binding: ClaimAuthorityBinding,
capability_matcher: Callable[[Iterable[str], Iterable[str]], bool] = has_capabilities,
) -> None:
_require_binding(store, binding)
if not callable(capability_matcher):
raise TypeError("capability_matcher must be callable")
self.store = store
self.binding = binding
self.capability_matcher = capability_matcher
@staticmethod
def _task_view(task: Any) -> dict[str, Any]:
return {
"task_id": task.task_id,
"revision": task.revision,
"generation": task.task_plan.metadata.get("generation", 0),
"status": task.status,
"required_capabilities": list(task.task_plan.required_capabilities),
"operation_digest": task.task_plan.operation_digest(),
"priority": task.task_plan.metadata.get("priority", 0),
"created_at": task.created_at,
"metadata": dict(task.task_plan.metadata),
}
@staticmethod
def _worker_view(worker: Any) -> dict[str, Any]:
return {
"worker_id": worker.worker_id,
"identity": worker.identity,
"capabilities": list(worker.capabilities),
"status": worker.status,
"disabled": worker.disabled,
"registration_revision": worker.registration_revision,
"max_concurrency": worker.max_concurrency,
"heartbeat_interval_seconds": worker.heartbeat_interval_seconds,
"last_heartbeat": worker.last_heartbeat,
}
def read_task(self, task_id: str) -> Mapping[str, Any]:
return self._task_view(self.store.get_task(task_id))
def list_candidate_tasks(self) -> list[Mapping[str, Any]]:
return [self._task_view(task) for task in self.store.list_tasks()]
def read_worker(self, worker_id: str) -> Mapping[str, Any]:
return self._worker_view(self.store.get_worker(worker_id))
def read_authorization(
self, *, task: Mapping[str, Any], worker: Mapping[str, Any], actor_identity: str
) -> Mapping[str, Any]:
value = self.binding.read_authorization(
task=task, worker=worker, actor_identity=actor_identity
)
if (
not isinstance(value, Mapping)
or not isinstance(value.get("allowed"), bool)
or not isinstance(value.get("authorization_ref"), str)
or not value["authorization_ref"]
or not isinstance(value.get("revision"), str)
or not value["revision"]
):
raise AuthorityAtomicBindingUnavailable()
return value
def active_claim_count(self, worker_id: str) -> int:
with self.store._lock:
return int(
self.store.conn.execute(
"SELECT COUNT(1) FROM claims WHERE worker_id=? AND status='ACTIVE' AND lease_until>?",
(worker_id, time.time()),
).fetchone()[0]
)
def dependencies_satisfied(self, task: Mapping[str, Any]) -> bool:
record = self.store.get_task(str(task["task_id"]))
return all(
self.store.get_task(dependency_id).status == "COMPLETED"
for dependency_id in record.task_plan.dependencies
)
def freeze_blocked(self, task: Mapping[str, Any]) -> bool:
# Only the exact boolean False proves the absence of a freeze. An
# unavailable or malformed authority value must deny an automatic claim.
return self.binding.freeze_blocked(task) is not False
def conflicting_lease(self, task_id: str) -> bool:
return self.store.active_claim(task_id) is not None
def exact_scope_digest(
self, *, task: Mapping[str, Any], worker: Mapping[str, Any], actor_identity: str
) -> tuple[bool, str]:
result = self.binding.exact_scope_digest(
task=task, worker=worker, actor_identity=actor_identity
)
if (
not isinstance(result, tuple)
or len(result) != 2
or not isinstance(result[0], bool)
or not isinstance(result[1], str)
or not result[1]
):
raise AuthorityAtomicBindingUnavailable()
return result
def claim_atomically(
self,
*,
task_id: str,
worker_id: str,
actor_identity: str,
expected: ClaimConditions,
lease_seconds: int,
schedulability_proof_digest: str,
) -> Mapping[str, Any]:
if not isinstance(lease_seconds, int) or isinstance(lease_seconds, bool) or lease_seconds <= 0:
raise SchedulabilityChanged("invalid claim lease duration")
with self.binding.claim_context(self.store) as transaction:
if transaction.store is not self.store or not getattr(transaction, "authority_id", ""):
raise AuthorityAtomicBindingUnavailable()
transaction.assert_conditions(task_id, worker_id, actor_identity, expected)
task = self.store.get_task(task_id)
worker = self.store.get_worker(worker_id)
operation_digest = task.task_plan.operation_digest()
generation = task.task_plan.metadata.get("generation", 0)
task_view = self._task_view(task)
worker_view = self._worker_view(worker)
authorization = self.read_authorization(
task=task_view, worker=worker_view, actor_identity=actor_identity
)
scope = self.exact_scope_digest(
task=task_view, worker=worker_view, actor_identity=actor_identity
)
if (
task.revision != expected.task_revision
or task.status != expected.task_status
or generation != expected.task_generation
or worker.registration_revision != expected.worker_generation
or worker.identity != expected.worker_identity
or worker.max_concurrency != expected.capacity_limit
or (expected.operation_digest is not None and operation_digest != expected.operation_digest)
or not self.capability_matcher(worker.capabilities, task.task_plan.required_capabilities)
or worker.disabled
or worker.status != "active"
or authorization["allowed"] is not True
or authorization["authorization_ref"] != expected.authorization_ref
or authorization["revision"] != expected.authorization_revision
or scope != (True, expected.scope_digest)
or self.freeze_blocked(task_view)
or not all(
self.store.get_task(dependency_id).status == "COMPLETED"
for dependency_id in task.task_plan.dependencies
)
or self.store.active_claim(task_id) is not None
or self.active_claim_count(worker_id) >= worker.max_concurrency
):
raise ConflictError("atomic claim condition changed")
claim_internal = getattr(self.store, "_claim_task_in_transaction", None)
if not callable(claim_internal):
raise AuthorityAtomicBindingUnavailable()
claim = claim_internal(
task_id=task_id,
worker_id=worker_id,
worker_identity=actor_identity,
lease_seconds=lease_seconds,
expected_revision=expected.task_revision,
operation_digest=operation_digest,
proof_digest=schedulability_proof_digest,
expected_binding=expected.to_dict(),
)
post_claim = self.store.get_task(task_id)
receipt = TaskPlaneClaimReceiptV3(
schema=_RECEIPT_SCHEMA,
authority_namespace="task-plane",
task_id=claim.task_id,
claim_id=claim.claim_id,
worker_id=claim.worker_id,
worker_identity=claim.worker_identity,
claimed_revision=claim.task_revision,
receipt_revision=post_claim.revision,
attempt=claim.attempt,
operation_digest=claim.operation_digest,
lease_until=claim.lease_until,
proof_digest=schedulability_proof_digest,
)
return receipt.to_dict()
class ScopedLeaseController:
"""Renew an existing TaskPlane claim; it never creates a second lease."""
def __init__(self, store: TaskPlaneStore, binding: ClaimAuthorityBinding) -> None:
_require_binding(store, binding)
self.store = store
self.binding = binding
def _validate_persisted_claim_evidence(
self,
current: TaskPlaneClaimReceiptV3,
*,
active: Any,
task: Any,
worker: Any,
) -> Mapping[str, object]:
"""Bind a C6 receipt to the durable claim decision, not its caller input."""
# The claim row is created before the task's CLAIMED transition. The
# transition is the only source for a v3 receipt revision; accepting a
# larger caller-provided value would let a legacy claim masquerade as
# a C6 claim.
if current.receipt_revision != active.task_revision + 1:
raise ConflictError("receipt revision does not bind active claim")
expected_task_revision = (
current.receipt_revision
if task.status == "CLAIMED"
else current.receipt_revision + 1
)
if task.revision != expected_task_revision:
raise ConflictError("receipt revision does not bind task state")
# A C6 proof digest is durable only when it appears in the exact
# TASK_CLAIMED event for this claim. The legacy claim path records no
# proof digest, so it cannot be upgraded by assembling v3-shaped JSON.
if not self.store.verify_event_chain(current.task_id):
raise ConflictError("claim event chain verification failed")
claim_events = [
event
for event in self.store.list_events(current.task_id)
if (
event.get("type") == "TASK_CLAIMED"
and isinstance(event.get("data"), Mapping)
and event["data"].get("claim_id") == current.claim_id
and event["data"].get("worker_id") == current.worker_id
and event["data"].get("proof_digest") == current.proof_digest
)
]
if len(claim_events) != 1:
raise ConflictError("receipt proof is not bound to claimed event")
expected_binding = claim_events[0]["data"].get("expected_binding")
required_binding_fields = {
"task_revision",
"task_generation",
"task_status",
"worker_generation",
"worker_identity",
"authorization_ref",
"authorization_revision",
"scope_digest",
"capacity_limit",
"operation_digest",
}
if not isinstance(expected_binding, Mapping) or set(expected_binding) != required_binding_fields:
raise ConflictError("claim lacks durable C6 authority binding")
task_generation = task.task_plan.metadata.get("generation", 0)
if (
isinstance(expected_binding["task_revision"], bool)
or not isinstance(expected_binding["task_revision"], int)
or expected_binding["task_revision"] < 0
or expected_binding["task_revision"] != current.claimed_revision
or expected_binding["task_revision"] != active.task_revision
or isinstance(expected_binding["task_generation"], bool)
or not isinstance(expected_binding["task_generation"], int)
or expected_binding["task_generation"] != task_generation
or expected_binding["task_status"] not in {"CREATED", "QUEUED", "FAILED_RECOVERABLE"}
or isinstance(expected_binding["worker_generation"], bool)
or not isinstance(expected_binding["worker_generation"], int)
or expected_binding["worker_generation"] < 1
or expected_binding["worker_identity"] != current.worker_identity
or isinstance(expected_binding["capacity_limit"], bool)
or not isinstance(expected_binding["capacity_limit"], int)
or expected_binding["capacity_limit"] < 1
or expected_binding["capacity_limit"] != worker.max_concurrency
or not isinstance(expected_binding["operation_digest"], str)
or expected_binding["operation_digest"] != current.operation_digest
or not isinstance(expected_binding["authorization_ref"], str)
or not expected_binding["authorization_ref"]
or not isinstance(expected_binding["authorization_revision"], str)
or not expected_binding["authorization_revision"]
or not isinstance(expected_binding["scope_digest"], str)
or not expected_binding["scope_digest"]
):
raise ConflictError("durable C6 claim binding changed")
# The scheduler decision retains the remaining immutable claim facts.
# Requiring exactly one matching decision also rejects a proof digest
# copied between attempts for the same task.
decision_payload = {
"worker_id": current.worker_id,
"worker_identity": current.worker_identity,
"operation_digest": current.operation_digest,
"attempt": current.attempt,
"proof_digest": current.proof_digest,
}
decision_events = [
event
for event in self.store.list_events(current.task_id)
if event.get("type") == "SCHEDULE_DECISION" and event.get("data") == decision_payload
]
if len(decision_events) != 1:
raise ConflictError("receipt proof is not bound to schedule decision")
self._validate_worker_heartbeat_chain(
bound_revision=expected_binding["worker_generation"],
worker=worker,
claim_actor_identity=current.worker_identity,
)
return expected_binding
def _validate_worker_heartbeat_chain(
self,
*,
bound_revision: object,
worker: Any,
claim_actor_identity: str,
) -> None:
"""Allow only a complete run of ordinary worker heartbeats after claim.
Worker registration revisions advance on every worker heartbeat. The
worker audit stream is therefore the authoritative distinction between
liveness refresh and re-registration or privilege change.
"""
current_revision = worker.registration_revision
if (
isinstance(bound_revision, bool)
or not isinstance(bound_revision, int)
or isinstance(current_revision, bool)
or not isinstance(current_revision, int)
or current_revision < bound_revision
):
raise ConflictError("worker registration revision changed")
if current_revision == bound_revision:
return
audit_task_id = "task-worker-registry"
if not self.store.verify_event_chain(audit_task_id):
raise ConflictError("worker registry event chain verification failed")
expected_revisions = list(range(bound_revision + 1, current_revision + 1))
events = []
for event in self.store.list_events(audit_task_id):
data = event.get("data")
if not isinstance(data, Mapping) or data.get("worker_id") != worker.worker_id:
continue
revision = data.get("registration_revision")
if isinstance(revision, bool) or not isinstance(revision, int) or revision <= bound_revision:
continue
events.append((event, data, revision))
if len(events) != len(expected_revisions):
raise ConflictError("worker registration audit chain incomplete")
for expected_revision, (event, data, revision) in zip(expected_revisions, events):
if (
event.get("type") != "WORKER_HEARTBEAT"
or set(data) != {"worker_id", "actor_identity", "registration_revision"}
or data.get("actor_identity") != claim_actor_identity
or revision != expected_revision
):
raise ConflictError("worker registration changed after C6 claim")
def _validate_active_receipt(
self,
transaction: ClaimTransaction,
current: TaskPlaneClaimReceiptV3,
*,
worker_id: str,
actor_identity: str,
) -> None:
"""Validate a receipt without extending its lease or granting a new one."""
if transaction.store is not self.store or not getattr(transaction, "authority_id", ""):
raise AuthorityAtomicBindingUnavailable()
holder_checker = getattr(transaction, "assert_lease_holder", None)
authorization_checker = getattr(transaction, "assert_lease_authorized", None)
if not callable(holder_checker) or not callable(authorization_checker):
raise AuthorityAtomicBindingUnavailable()
holder_checker(current.task_id, current.claim_id, worker_id, actor_identity)
# ``active_claim`` uses the store's authoritative clock and status
# filter. Do not trust a receipt's former lease expiry by itself.
active = self.store.active_claim(current.task_id)
if active is None:
raise LockedError("lease expired or inactive")
if (
active.claim_id != current.claim_id
or active.task_id != current.task_id
or active.worker_id != worker_id
or active.worker_identity != actor_identity
or active.task_revision != current.claimed_revision
or active.operation_digest != current.operation_digest
or active.attempt != current.attempt
or active.lease_until <= time.time()
):
raise ConflictError("active claim does not match receipt")
task = self.store.get_task(current.task_id)
worker = self.store.get_worker(worker_id)
expected_binding = self._validate_persisted_claim_evidence(
current, active=active, task=task, worker=worker
)
task_view = TaskPlaneSchedulabilityAuthority._task_view(task)
worker_view = TaskPlaneSchedulabilityAuthority._worker_view(worker)
authorization = self.binding.read_authorization(
task=task_view, worker=worker_view, actor_identity=actor_identity
)
scope = self.binding.exact_scope_digest(
task=task_view, worker=worker_view, actor_identity=actor_identity
)
if (
task.status not in {"CLAIMED", "RUNNING"}
or task.task_plan.operation_digest() != current.operation_digest
or not isinstance(authorization, Mapping)
or authorization.get("allowed") is not True
or authorization.get("authorization_ref") != expected_binding["authorization_ref"]
or authorization.get("revision") != expected_binding["authorization_revision"]
or not isinstance(scope, tuple)
or len(scope) != 2
or scope[0] is not True
or not isinstance(scope[1], str)
or not scope[1]
or scope[1] != expected_binding["scope_digest"]
or self.binding.freeze_blocked(task_view) is not False
or worker.disabled
or worker.status != "active"
or worker.identity != current.worker_identity
or not has_capabilities(worker.capabilities, task.task_plan.required_capabilities)
):
raise ConflictError("lease authority changed")
authorization_checker(
current.task_id, current.claim_id, worker_id, actor_identity, current
)
@contextmanager
def execution_guard(
self,
receipt: Mapping[str, object],
*,
worker_id: str,
actor_identity: str,
):
"""Hold the authoritative claim binding through one pilot critical section.
This guard never calls ``heartbeat_claim`` and never makes a new claim.
Its context remains open for the caller's critical section, so another
same-store state change cannot interleave between validation and use.
"""
current = _parse_receipt(receipt)
if current.worker_id != worker_id or current.worker_identity != actor_identity:
raise SchedulabilityChanged("lease holder mismatch")
with self.binding.claim_context(self.store) as transaction:
self._validate_active_receipt(
transaction, current, worker_id=worker_id, actor_identity=actor_identity
)
yield current
def renew(
self,
receipt: Mapping[str, object],
*,
worker_id: str,
actor_identity: str,
lease_seconds: int,
) -> Mapping[str, object]:
current = _parse_receipt(receipt)
if current.worker_id != worker_id or current.worker_identity != actor_identity:
raise SchedulabilityChanged("lease holder mismatch")
with self.binding.claim_context(self.store) as transaction:
self._validate_active_receipt(
transaction, current, worker_id=worker_id, actor_identity=actor_identity
)
renewed = self.store.heartbeat_claim(
current.task_id,
current.claim_id,
lease_seconds,
expected_task_revision=current.claimed_revision,
operation_digest=current.operation_digest,
)
return TaskPlaneClaimReceiptV3(
schema=_RECEIPT_SCHEMA,
authority_namespace="task-plane",
task_id=renewed.task_id,
claim_id=renewed.claim_id,
worker_id=renewed.worker_id,
worker_identity=renewed.worker_identity,
claimed_revision=renewed.task_revision,
receipt_revision=current.receipt_revision,
attempt=renewed.attempt,
operation_digest=renewed.operation_digest,
lease_until=renewed.lease_until,
proof_digest=current.proof_digest,
).to_dict()
@@ -0,0 +1,212 @@
"""S5.1 live authority adapters with exact caller/candidate binding.
These adapters are projections over existing authorities. They do not create
tasks, grants, leases, branches, or recovery authority.
"""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any, Callable, Mapping, Protocol
from .governance_admission import AdmissionOperation, GovernanceFacts
from .governance_recovery_controller import GovernanceEvent
from .governance_resolver import ExactTaskResolver, ResolutionQuery, ResolutionResult
class LiveSurfaceUnavailable(RuntimeError):
def __init__(self, role: str, reason: str) -> None:
self.role = role
self.reason = reason
super().__init__(f"LIVE_SURFACE_UNAVAILABLE[{role}]: {reason}")
class LiveBindingViolation(RuntimeError):
def __init__(self, role: str, reason: str) -> None:
self.role = role
self.reason = reason
super().__init__(f"LIVE_BINDING_VIOLATION[{role}]: {reason}")
class TaskPlaneReadClient(Protocol):
def get_task(
self, task_id: str, *, actor_identity: str
) -> Mapping[str, Any] | None: ...
def list_tasks(
self, *, actor_identity: str
) -> list[Mapping[str, Any]]: ...
class LiveTaskResolverAdapter:
"""Bind ExactTaskResolver to one caller-authorized TaskPlane projection."""
def __init__(
self,
*,
client: TaskPlaneReadClient | None = None,
actor_identity: str | None = None,
bound_repository: str | None = None,
) -> None:
self._client = client
self._actor_identity = actor_identity
self._bound_repository = bound_repository
@property
def live(self) -> bool:
return self._client is not None and bool(self._actor_identity)
def resolve(
self, query: ResolutionQuery, *, actor_identity: str
) -> ResolutionResult:
if not self.live:
raise LiveSurfaceUnavailable(
"A1",
"no caller-authorized TaskPlaneReadClient and bound identity",
)
if actor_identity != self._actor_identity:
raise LiveBindingViolation(
"A1",
"controller actor_identity does not match bound read principal",
)
if (
self._bound_repository
and query.repository != self._bound_repository
):
raise LiveBindingViolation(
"A1",
"query repository is outside the bound authority projection",
)
return ExactTaskResolver(self._client).resolve(
query, actor_identity=self._actor_identity
)
@dataclass(frozen=True)
class GovernedAuthoritySnapshot:
"""One revision-consistent, candidate-specific governed read."""
task_id: str
actor_identity: str
operation: AdmissionOperation
snapshot_revision: str
facts: GovernanceFacts
class GovernedAuthorityReadClient(Protocol):
def read_snapshot(
self,
*,
task_id: str,
actor_identity: str,
operation: AdmissionOperation,
) -> GovernedAuthoritySnapshot: ...
class LiveGovernanceFactsAdapter:
"""Materialize GovernanceFacts from one atomic/revision-consistent read."""
def __init__(
self,
*,
client: GovernedAuthorityReadClient | None = None,
actor_identity: str | None = None,
) -> None:
self._client = client
self._actor_identity = actor_identity
@property
def live(self) -> bool:
return self._client is not None and bool(self._actor_identity)
def snapshot(
self,
*,
task_id: str | None,
actor_identity: str,
operation: AdmissionOperation,
) -> GovernanceFacts:
if not self.live:
raise LiveSurfaceUnavailable(
"A2",
"no candidate-specific governed authority reader and identity",
)
if task_id is None:
raise LiveBindingViolation("A2", "exact task_id required")
if actor_identity != self._actor_identity:
raise LiveBindingViolation(
"A2",
"controller actor_identity does not match bound governed principal",
)
snapshot = self._client.read_snapshot(
task_id=task_id,
actor_identity=actor_identity,
operation=operation,
)
if not snapshot.snapshot_revision:
raise LiveBindingViolation(
"A2", "authority snapshot revision is missing"
)
if snapshot.task_id != task_id:
raise LiveBindingViolation("A2", "authority snapshot task mismatch")
if snapshot.actor_identity != actor_identity:
raise LiveBindingViolation("A2", "authority snapshot actor mismatch")
if snapshot.operation is not operation:
raise LiveBindingViolation("A2", "authority snapshot operation mismatch")
return snapshot.facts
@dataclass(frozen=True)
class LiveGovernanceEvent:
kind: str
revision: str
occurred_at: str
payload: Mapping[str, Any] = field(default_factory=dict)
class GovernanceEventSubscriber(Protocol):
def subscribe(
self, on_event: Callable[[LiveGovernanceEvent], object]
) -> None: ...
def close(self) -> None: ...
class LiveEventSubscriptionAdapter:
"""Translate site events into the core GovernanceEvent contract."""
def __init__(
self,
sink: Callable[[GovernanceEvent], object],
subscriber: GovernanceEventSubscriber | None = None,
) -> None:
self._sink = sink
self._subscriber = subscriber
self._active = False
@property
def live(self) -> bool:
return self._subscriber is not None
def _on_live_event(self, event: LiveGovernanceEvent):
if not isinstance(event, LiveGovernanceEvent):
raise LiveBindingViolation(
"A3", "subscriber emitted an unsupported event type"
)
return self._sink(GovernanceEvent(
kind=event.kind,
revision=event.revision,
occurred_at=event.occurred_at,
))
def start(self) -> None:
if not self.live:
raise LiveSurfaceUnavailable(
"A3",
"no governance event subscriber injected; polling remains fallback",
)
self._subscriber.subscribe(self._on_live_event)
self._active = True
def close(self) -> None:
if self._subscriber is not None:
self._subscriber.close()
self._active = False
+439 -128
View File
@@ -10,7 +10,7 @@ import threading
import time
import uuid
from contextlib import contextmanager
from typing import Any
from typing import Any, Mapping
from .capabilities import has_capabilities
from .errors import (
@@ -26,6 +26,7 @@ from .models import (
ProbeOutcome,
TaskPlan,
TaskRecord,
TaskStateTransitionError,
WorkerClaim,
WorkerRegistration,
canonical_json,
@@ -65,11 +66,38 @@ class TaskPlaneStore:
self.high_assurance = high_assurance
self.fault_injector = None
self.probe_attestation_verifier = None
self.evidence_attestation_verifier = None
self._lock = threading.RLock()
self._tx_state = threading.local()
self._closed = False
self._init_db(db_path)
@staticmethod
def _verifier_identity(verifier: Any) -> str | None:
identity = getattr(verifier, "verifier_identity", None)
return str(identity) if identity else None
def configure_evidence_attestation_verifier(self, verifier: Any) -> Any:
"""Install one evidence verifier without weakening an existing binding."""
current = self.evidence_attestation_verifier
if verifier is None:
return current
if current is None:
self.evidence_attestation_verifier = verifier
return verifier
if current is verifier:
return current
current_identity = self._verifier_identity(current)
candidate_identity = self._verifier_identity(verifier)
if (
current_identity is not None
and candidate_identity is not None
and current_identity == candidate_identity
):
return current
raise ValueError("conflicting evidence attestation verifier identity")
def _init_db(self, path: str) -> None:
if path != ":memory:":
parent = os.path.dirname(os.path.abspath(path))
@@ -410,6 +438,18 @@ class TaskPlaneStore:
# an external request. They must never migrate to a phase
# that permits a new lease or commit.
"effect_phase": "TEXT NOT NULL DEFAULT 'LEGACY_UNKNOWN'",
# C6 exact-owner integration extends this existing effect
# ledger. The versioned binding is immutable and its
# separately indexed effect key permits exact readback;
# it is deliberately not a second action ledger.
"host_effect_key": "TEXT",
"host_binding_json": "TEXT",
"host_binding_digest": "TEXT",
"writer_lease_json": "TEXT",
"writer_lease_digest": "TEXT",
"host_closure_json": "TEXT",
"host_closure_digest": "TEXT",
"host_closure_observed_at": "REAL",
},
)
self.conn.execute(
@@ -436,6 +476,11 @@ class TaskPlaneStore:
"CREATE UNIQUE INDEX IF NOT EXISTS ux_root_review_trigger "
"ON task_root_reviews(trigger_child_task_id, trigger_child_revision)"
)
self.conn.execute(
"CREATE UNIQUE INDEX IF NOT EXISTS ux_root_action_host_effect "
"ON task_root_action_effects(host_effect_key) "
"WHERE host_effect_key IS NOT NULL"
)
self.conn.execute(
"""
CREATE TABLE IF NOT EXISTS task_missions (
@@ -754,22 +799,60 @@ class TaskPlaneStore:
status: str,
failure_reason: str | None = None,
) -> TaskRecord:
with self._transaction():
current = self.get_task(task_id)
if current.revision != expected_revision:
raise ConflictError("task revision mismatch")
current.with_status(status)
outer_depth = getattr(self._tx_state, "depth", 0)
observed: TaskRecord | None = None
try:
with self._transaction():
current = self.get_task(task_id)
observed = current
if current.revision != expected_revision:
raise ConflictError("task revision mismatch")
current.with_status(status)
now = _now()
self.conn.execute(
"""
UPDATE tasks
SET status=?, revision=revision+1, updated_at=?, failure_reason=?
WHERE task_id=?
""",
(status, now, failure_reason, task_id),
now = _now()
self.conn.execute(
"""
UPDATE tasks
SET status=?, revision=revision+1, updated_at=?, failure_reason=?
WHERE task_id=?
""",
(status, now, failure_reason, task_id),
)
return self.get_task(task_id)
except TaskStateTransitionError as exc:
if observed is None:
raise
if outer_depth:
raise
self._append_transition_denial(
task_id, observed, attempted_status=status, error=exc
)
return self.get_task(task_id)
raise
def _append_transition_denial(
self,
task_id: str,
observed: TaskRecord,
*,
attempted_status: str,
error: TaskStateTransitionError,
) -> None:
try:
self.append_event(
task_id,
"TASK_TRANSITION_DENIED",
{
"from_status": observed.status,
"attempted_status": attempted_status,
"observed_revision": observed.revision,
"reason": str(error),
},
subject=f"task/{task_id}",
)
except Exception as audit_error:
raise RuntimeError(
"task transition denied but denial audit persistence failed"
) from audit_error
def register_worker(
self,
@@ -1048,112 +1131,203 @@ class TaskPlaneStore:
lease_seconds: int,
expected_revision: int,
operation_digest: str,
*,
proof_digest: str | None = None,
expected_binding: Mapping[str, object] | None = None,
) -> WorkerClaim:
with self._transaction():
return self._claim_task_in_transaction(
task_id,
worker_id,
worker_identity,
lease_seconds,
expected_revision,
operation_digest,
proof_digest=proof_digest,
expected_binding=expected_binding,
)
def _claim_task_in_transaction(
self,
task_id: str,
worker_id: str,
worker_identity: str,
lease_seconds: int,
expected_revision: int,
operation_digest: str,
*,
proof_digest: str | None = None,
expected_binding: Mapping[str, object] | None = None,
) -> WorkerClaim:
"""Claim after revalidating the supplied binding in the active transaction."""
now = _now()
lease_until = now + max(1, int(lease_seconds))
with self._transaction():
worker = self.get_worker(worker_id)
if worker.disabled:
raise LockedError("worker disabled")
if worker.identity != worker_identity:
raise ConflictError("worker identity mismatch")
worker = self.get_worker(worker_id)
if worker.disabled:
raise LockedError("worker disabled")
if worker.identity != worker_identity:
raise ConflictError("worker identity mismatch")
task = self.get_task(task_id)
if task.revision != expected_revision:
raise ConflictError("task revision mismatch")
if task.status not in {"CREATED", "QUEUED", "FAILED_RECOVERABLE"}:
raise ConflictError(f"task in invalid state for claim: {task.status}")
if task.status == "FAILED_RECOVERABLE" and not self.task_has_retryable_probe(
task_id
task = self.get_task(task_id)
if task.revision != expected_revision:
raise ConflictError("task revision mismatch")
if expected_binding is not None:
task_generation = task.task_plan.metadata.get("generation", 0)
if (
not isinstance(task_generation, int)
or isinstance(task_generation, bool)
or task_generation < 0
):
raise LockedError("recoverable task lacks NOT_APPLIED probe evidence")
if self.active_claim(task_id):
raise ConflictError("task already claimed")
if task.task_plan.data_classification == "credential-secret":
if (
worker.worker_type != "secure_file_transformer"
and "secure_file_transformer" not in worker.capabilities
):
from .errors import ForbiddenError
raise ForbiddenError(
"credential-secret task requires secure file transformer"
)
for dependency_id in task.task_plan.dependencies:
if self.get_task(dependency_id).status != "COMPLETED":
raise LockedError("task dependency incomplete")
required = task.task_plan.required_capabilities
if required and not has_capabilities(worker.capabilities, required):
raise ConflictError("worker lacks required capabilities")
attempts = self.conn.execute(
"SELECT COUNT(1) FROM claims WHERE task_id=? AND status='ACTIVE' AND lease_until>?",
(task_id, now),
).fetchone()[0]
if attempts > 0:
raise ConflictError("existing active claim")
claim_id = f"claim-{uuid.uuid4()}"
active_for_worker = self.conn.execute(
"SELECT COUNT(1) FROM claims WHERE worker_id=? AND status='ACTIVE' AND lease_until>?",
(worker_id, now),
).fetchone()[0]
if active_for_worker >= worker.max_concurrency:
raise LockedError("worker concurrency exceeded")
attempt = self.conn.execute(
"SELECT COUNT(1) FROM claims WHERE task_id=?",
(task_id,),
).fetchone()[0] + 1
self.conn.execute(
"INSERT INTO claims(claim_id, task_id, worker_id, worker_identity, claimed_at, lease_until, attempt, task_revision, operation_digest, status) "
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'ACTIVE')",
(
claim_id,
task_id,
worker_id,
worker_identity,
now,
lease_until,
attempt,
expected_revision,
operation_digest,
),
)
schedule_payload = {
"worker_id": worker_id,
"worker_identity": worker_identity,
"operation_digest": operation_digest,
"attempt": attempt,
raise ConflictError("task generation invalid")
checks = {
"task_revision": task.revision,
"task_generation": task_generation,
"task_status": task.status,
"worker_generation": worker.registration_revision,
"worker_identity": worker.identity,
"capacity_limit": worker.max_concurrency,
"operation_digest": task.task_plan.operation_digest(),
}
existing_decision = self.conn.execute(
"""
SELECT 1 FROM events
WHERE task_id=? AND type='SCHEDULE_DECISION' AND event_data=?
""",
(
task_id,
json.dumps(redact_value(schedule_payload), ensure_ascii=False),
),
).fetchone()
if existing_decision is None:
self.append_event(
task_id,
"SCHEDULE_DECISION",
schedule_payload,
subject=f"task/{task_id}",
for field, actual in checks.items():
if expected_binding.get(field) != actual:
raise ConflictError(f"claim binding {field} mismatch")
if expected_binding.get("operation_digest") != operation_digest:
raise ConflictError("claim binding operation_digest mismatch")
if task.status not in {"CREATED", "QUEUED", "FAILED_RECOVERABLE"}:
raise ConflictError(f"task in invalid state for claim: {task.status}")
if task.status == "FAILED_RECOVERABLE" and not self.task_has_retryable_probe(
task_id
):
raise LockedError("recoverable task lacks NOT_APPLIED probe evidence")
if self.active_claim(task_id):
raise ConflictError("task already claimed")
if task.task_plan.data_classification == "credential-secret":
if (
worker.worker_type != "secure_file_transformer"
and "secure_file_transformer" not in worker.capabilities
):
from .errors import ForbiddenError
raise ForbiddenError(
"credential-secret task requires secure file transformer"
)
self.conn.execute(
"UPDATE tasks SET status='CLAIMED', revision=revision+1, updated_at=? WHERE task_id=?",
(now, task_id),
for dependency_id in task.task_plan.dependencies:
if self.get_task(dependency_id).status != "COMPLETED":
raise LockedError("task dependency incomplete")
required = task.task_plan.required_capabilities
if required and not has_capabilities(worker.capabilities, required):
raise ConflictError("worker lacks required capabilities")
attempts = self.conn.execute(
"SELECT COUNT(1) FROM claims WHERE task_id=? AND status='ACTIVE' AND lease_until>?",
(task_id, now),
).fetchone()[0]
if attempts > 0:
raise ConflictError("existing active claim")
claim_id = f"claim-{uuid.uuid4()}"
active_for_worker = self.conn.execute(
"SELECT COUNT(1) FROM claims WHERE worker_id=? AND status='ACTIVE' AND lease_until>?",
(worker_id, now),
).fetchone()[0]
if active_for_worker >= worker.max_concurrency:
raise LockedError("worker concurrency exceeded")
attempt = self.conn.execute(
"SELECT COUNT(1) FROM claims WHERE task_id=?",
(task_id,),
).fetchone()[0] + 1
self.conn.execute(
"INSERT INTO claims(claim_id, task_id, worker_id, worker_identity, claimed_at, lease_until, attempt, task_revision, operation_digest, status) "
"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, 'ACTIVE')",
(
claim_id,
task_id,
worker_id,
worker_identity,
now,
lease_until,
attempt,
expected_revision,
operation_digest,
),
)
schedule_payload = {
"worker_id": worker_id,
"worker_identity": worker_identity,
"operation_digest": operation_digest,
"attempt": attempt,
}
if proof_digest is not None:
schedule_payload["proof_digest"] = proof_digest
existing_decision = self.conn.execute(
"""
SELECT 1 FROM events
WHERE task_id=? AND type='SCHEDULE_DECISION' AND event_data=?
""",
(
task_id,
json.dumps(redact_value(schedule_payload), ensure_ascii=False),
),
).fetchone()
if existing_decision is None:
self.append_event(
task_id,
"SCHEDULE_DECISION",
schedule_payload,
subject=f"task/{task_id}",
)
self.append_event(task_id, "TASK_CLAIMED", {"claim_id": claim_id, "worker_id": worker_id}, subject=f"task/{task_id}")
return self._row_to_claim(self.conn.execute(
self.conn.execute(
"UPDATE tasks SET status='CLAIMED', revision=revision+1, updated_at=? WHERE task_id=?",
(now, task_id),
)
claimed_payload = {"claim_id": claim_id, "worker_id": worker_id}
if proof_digest is not None:
claimed_payload["proof_digest"] = proof_digest
if expected_binding is not None:
# Preserve only the C6 conditions rechecked above plus the host
# authorization/scope version fields. A later renew/execute must
# bind to this durable decision, rather than caller-shaped receipt
# JSON. Do not serialize arbitrary values accepted by this
# internal compatibility seam.
claimed_payload["expected_binding"] = {
field: expected_binding.get(field)
for field in (
"task_revision",
"task_generation",
"task_status",
"worker_generation",
"worker_identity",
"authorization_ref",
"authorization_revision",
"scope_digest",
"capacity_limit",
"operation_digest",
)
}
self.append_event(
task_id,
"TASK_CLAIMED",
claimed_payload,
subject=f"task/{task_id}",
)
return self._row_to_claim(
self.conn.execute(
"SELECT * FROM claims WHERE claim_id=?",
(claim_id,),
).fetchone())
).fetchone()
)
def heartbeat_claim(self, task_id: str, claim_id: str, lease_seconds: int) -> WorkerClaim:
def heartbeat_claim(
self,
task_id: str,
claim_id: str,
lease_seconds: int,
*,
expected_task_revision: int | None = None,
operation_digest: str | None = None,
) -> WorkerClaim:
now = _now()
lease_until = now + max(1, lease_seconds)
with self._transaction():
@@ -1168,14 +1342,32 @@ class TaskPlaneStore:
raise ConflictError("claim not active")
if claim.lease_until <= now:
raise LockedError("lease expired")
if (
expected_task_revision is not None
and claim.task_revision != expected_task_revision
):
raise ConflictError("claim task revision mismatch")
if (
operation_digest is not None
and claim.operation_digest != operation_digest
):
raise ConflictError("claim operation digest mismatch")
if self.get_task(task_id).status in {"CANCELLED", "COMPLETED", "FAILED_FINAL"}:
raise ConflictError("task is terminal")
updated = self.conn.execute(
"""
UPDATE claims SET lease_until=?
WHERE claim_id=? AND task_id=? AND status='ACTIVE' AND lease_until>?
AND task_revision=? AND operation_digest=?
""",
(lease_until, claim_id, task_id, now),
(
lease_until,
claim_id,
task_id,
now,
claim.task_revision,
claim.operation_digest,
),
)
if updated.rowcount != 1:
raise LockedError("claim changed during heartbeat")
@@ -1408,6 +1600,7 @@ class TaskPlaneStore:
expected_task_id=task_id,
expected_claim_id=claim_id,
expected_previous_hash=previous,
attestation_verifier=self.evidence_attestation_verifier,
):
raise ConflictError("evidence envelope invalid")
self.add_evidence_envelope(envelope)
@@ -1455,16 +1648,26 @@ class TaskPlaneStore:
def cancel_task_with_event(
self, task_id: str, expected_revision: int, reason: str
) -> TaskRecord:
with self._transaction():
updated = self.cancel_task(task_id, expected_revision, reason)
self._fault("cancel.after_state_before_event")
self.append_event(
task_id,
"TASK_CANCELLED",
{"reason": reason},
subject=f"task/{task_id}",
observed: TaskRecord | None = None
try:
with self._transaction():
observed = self.get_task(task_id)
updated = self.cancel_task(task_id, expected_revision, reason)
self._fault("cancel.after_state_before_event")
self.append_event(
task_id,
"TASK_CANCELLED",
{"reason": reason},
subject=f"task/{task_id}",
)
return updated
except TaskStateTransitionError as exc:
if observed is None:
raise
self._append_transition_denial(
task_id, observed, attempted_status="CANCELLED", error=exc
)
return updated
raise
def delegate_task_with_budget_and_events(
self,
@@ -1699,38 +1902,120 @@ class TaskPlaneStore:
prev = event["hash"]
return True
def add_evidence(self, task_id: str, kind: str, payload: dict[str, Any], worker_identity: str) -> str:
def add_evidence(
self,
task_id: str,
kind: str,
payload: dict[str, Any],
worker_identity: str,
*,
envelope: EvidenceEnvelope | None = None,
claim_id: str | None = None,
evidence_id: str | None = None,
created_at: float | None = None,
expires_at: float | None = None,
signature: str | None = None,
attestation_ref: str | None = None,
signature_algorithm: str | None = None,
signing_key_id: str | None = None,
verifier_identity: str | None = None,
authority_domain: str | None = None,
sequence: int | None = None,
) -> str:
"""Add authenticated evidence while retaining the legacy call shape.
Old unsigned calls now fail with a migration error before any write.
Producers may submit a complete envelope or all explicit proof fields.
"""
if envelope is not None:
if (
envelope.task_id != task_id
or envelope.kind != kind
or envelope.worker_identity != worker_identity
or envelope.payload != redact_value(payload)
):
raise ValidationError("evidence envelope arguments mismatch")
self.add_evidence_envelope(envelope)
return envelope.evidence_id
if not (signature or attestation_ref):
raise ValidationError(
"evidence authentication required; submit a complete verified "
"envelope or explicit signature/provider proof"
)
claim = self.active_claim(task_id)
claim_id = claim.claim_id if claim else "claim-unbound-shadow"
envelope = EvidenceEnvelope.create(
effective_claim_id = claim_id or (
claim.claim_id if claim else "claim-unbound-shadow"
)
if claim is not None and effective_claim_id != claim.claim_id:
raise ValidationError("evidence claim does not match active claim")
built = EvidenceEnvelope.create(
task_id=task_id,
claim_id=claim_id,
claim_id=effective_claim_id,
kind=kind,
worker_identity=worker_identity,
payload=payload,
previous_evidence_hash=self._last_evidence_hash(task_id),
evidence_id=evidence_id,
created_at=created_at,
expires_at=expires_at,
signature=signature,
attestation_ref=attestation_ref,
signature_algorithm=signature_algorithm,
signing_key_id=signing_key_id,
verifier_identity=verifier_identity,
authority_domain=authority_domain,
sequence=sequence,
)
self.add_evidence_envelope(envelope)
return envelope.evidence_id
self.add_evidence_envelope(built)
return built.evidence_id
def _last_evidence_hash(self, task_id: str) -> str:
row = self.conn.execute(
"""
SELECT digest FROM evidence WHERE task_id=?
ORDER BY created_at DESC,evidence_id DESC LIMIT 1
ORDER BY rowid DESC LIMIT 1
""",
(task_id,),
).fetchone()
return str(row["digest"]) if row else ""
def _last_evidence_sequence(self, authority_domain: str) -> int | None:
row = self.conn.execute(
"""
SELECT MAX(CAST(json_extract(envelope_json, '$.sequence') AS INTEGER))
FROM evidence
WHERE json_extract(envelope_json, '$.authority_domain')=?
AND json_type(envelope_json, '$.sequence')='integer'
""",
(authority_domain,),
).fetchone()
return None if row is None or row[0] is None else int(row[0])
def add_evidence_envelope(self, envelope: EvidenceEnvelope) -> str:
with self._transaction():
self.get_task(envelope.task_id)
if (envelope.authority_domain is None) != (envelope.sequence is None):
raise ConflictError("evidence authority domain and sequence must be paired")
if envelope.authority_domain is not None:
if (
not envelope.authority_domain
or not isinstance(envelope.sequence, int)
or isinstance(envelope.sequence, bool)
or envelope.sequence < 1
):
raise ConflictError("evidence authority sequence invalid")
last_sequence = self._last_evidence_sequence(
envelope.authority_domain
)
if last_sequence is not None and envelope.sequence <= last_sequence:
raise ConflictError("evidence authority sequence not monotonic")
expected_previous = self._last_evidence_hash(envelope.task_id)
if not envelope.verify(
expected_task_id=envelope.task_id,
expected_claim_id=envelope.claim_id,
expected_previous_hash=expected_previous,
attestation_verifier=self.evidence_attestation_verifier,
):
raise ConflictError("evidence envelope invalid")
self.conn.execute(
@@ -1759,13 +2044,14 @@ class TaskPlaneStore:
task_id = _validated_id(task_id, "task_id")
with self._lock:
rows = self.conn.execute(
"SELECT * FROM evidence WHERE task_id=? ORDER BY created_at ASC",
"SELECT * FROM evidence WHERE task_id=? ORDER BY rowid ASC",
(task_id,),
).fetchall()
return [dict(r) for r in rows]
def verify_evidence_integrity(self, task_id: str) -> bool:
previous = ""
domain_sequences: dict[str, int] = {}
for evidence in self.list_evidence(task_id):
try:
raw = evidence.get("envelope_json")
@@ -1781,10 +2067,24 @@ class TaskPlaneStore:
worker_identity=payload["worker_identity"],
payload=payload["payload"],
created_at=float(payload["created_at"]),
expires_at=float(
payload.get(
"expires_at", float(payload["created_at"]) + 300.0
)
),
previous_evidence_hash=payload["previous_evidence_hash"],
evidence_digest=payload["evidence_digest"],
signature=payload.get("signature"),
attestation_ref=payload.get("attestation_ref"),
signature_algorithm=payload.get("signature_algorithm"),
signing_key_id=payload.get("signing_key_id"),
verifier_identity=payload.get("verifier_identity"),
authority_domain=payload.get("authority_domain"),
sequence=(
None
if payload.get("sequence") is None
else int(payload["sequence"])
),
)
except (KeyError, TypeError, ValueError, json.JSONDecodeError):
return False
@@ -1798,9 +2098,20 @@ class TaskPlaneStore:
expected_task_id=task_id,
expected_claim_id=envelope.claim_id,
expected_previous_hash=previous,
attestation_verifier=self.evidence_attestation_verifier,
)
):
return False
if envelope.authority_domain is not None:
if (
not isinstance(envelope.sequence, int)
or isinstance(envelope.sequence, bool)
or envelope.sequence <= domain_sequences.get(
envelope.authority_domain, 0
)
):
return False
domain_sequences[envelope.authority_domain] = envelope.sequence
previous = envelope.evidence_digest
return True
@@ -26,6 +26,8 @@ import re
import stat
import subprocess
import sys
import tarfile
import tempfile
import time
from datetime import datetime, timezone
from pathlib import Path
@@ -405,51 +407,85 @@ def run_secret_scan_provider(
tree_oid: str,
store_id: str = "secret_scan",
provider_identity: str = "aios.secret-scanner",
artifact_path: Path | None = None,
) -> tuple[Mapping[str, Any], str]:
"""Execute the real secret scanner (gitleaks) and produce a root receipt."""
if not Path(gitleaks_bin).is_file():
raise TrustedProviderExecutionError("SECRET_SCANNER_UNAVAILABLE")
artifact_sha = None
if artifact_path is not None:
artifact_sha = _validate_artifact_scan_target(artifact_path, target)
# --no-git scans the working tree as a filesystem (does not require a .git
# dir), so an exported release checkout can be scanned faithfully.
scanned = _run(
[
gitleaks_bin, "detect",
"--source", str(target),
"--no-git",
"--no-banner",
"--exit-code", "0",
],
cwd=target,
timeout=1800,
)
warnings = 0
# gitleaks exits 1 when findings are present; with --exit-code 0 it returns 0
# but prints findings. Count lines that indicate a finding.
finding_lines = [
ln for ln in scanned.stdout.splitlines()
if ("Finding:" in ln and ":" in ln and "RuleID" not in ln)
] + [
ln for ln in scanned.stderr.splitlines()
if ("Finding:" in ln and ":" in ln and "RuleID" not in ln)
]
artifact_sha = None
if target.is_file():
artifact_sha = hashlib.sha256(target.read_bytes()).hexdigest()
with tempfile.TemporaryDirectory(prefix="aios-secret-report-") as report_root:
report = Path(report_root) / "findings.json"
scanned = _run(
[
gitleaks_bin, "detect",
"--source", str(target),
"--no-git",
"--no-banner",
"--exit-code", "1",
"--report-format", "json", "--report-path", str(report),
],
cwd=target,
timeout=1800,
)
try:
findings = json.loads(report.read_text())
except (OSError, ValueError) as exc:
raise TrustedProviderExecutionError("SECRET_SCANNER_REPORT_INVALID") from exc
if not isinstance(findings, list) or any(not isinstance(v, dict) for v in findings):
raise TrustedProviderExecutionError("SECRET_SCANNER_REPORT_INVALID")
report_sha = hashlib.sha256(report.read_bytes()).hexdigest()
if artifact_path is not None and _validate_artifact_scan_target(artifact_path, target) != artifact_sha:
raise TrustedProviderExecutionError("SECRET_SCAN_ARTIFACT_CHANGED")
# Passed only when the scanner actually ran cleanly (exit 0) AND found zero
# leaks. A scanner crash (non-zero exit) is never a pass.
clean_run = scanned.returncode == 0
result = {
"passed": clean_run and len(finding_lines) == 0,
"passed": clean_run and len(findings) == 0,
"scanner": "gitleaks",
"exit_code": scanned.returncode,
"target": str(target),
"findings": len(finding_lines),
"findings": len(findings),
"report_sha256": report_sha,
"artifact_sha256": artifact_sha,
"clean_run": clean_run,
}
return result, _receipt_ref_for(result, "secret_scan")
def _validate_artifact_scan_target(artifact: Path, target: Path) -> str:
"""Bind a real filesystem scan to every byte of its exact archive payload."""
if artifact.is_symlink() or not artifact.is_file() or target.is_symlink() or not target.is_dir():
raise TrustedProviderExecutionError("SECRET_SCAN_ARTIFACT_TARGET_INVALID")
raw = artifact.read_bytes()
import io
expected: set[str] = set()
try:
with tarfile.open(fileobj=io.BytesIO(raw), mode="r:*") as archive:
members = archive.getmembers()
if not members or len(members) > 20_000 or sum(m.size for m in members) > 2 * 1024**3:
raise TrustedProviderExecutionError("SECRET_SCAN_ARCHIVE_LIMIT")
for member in members:
parts = Path(member.name).parts
if not member.isfile() or member.name.startswith("/") or "\\" in member.name or not parts or any(p in {".", ".."} for p in parts) or member.name in expected:
raise TrustedProviderExecutionError("SECRET_SCAN_ARCHIVE_INVALID")
path = target.joinpath(*parts)
if path.is_symlink() or not path.is_file() or not path.resolve().is_relative_to(target.resolve()):
raise TrustedProviderExecutionError("SECRET_SCAN_PAYLOAD_MISMATCH")
if archive.extractfile(member).read() != path.read_bytes():
raise TrustedProviderExecutionError("SECRET_SCAN_PAYLOAD_MISMATCH")
expected.add(member.name)
except (tarfile.TarError, OSError) as exc:
raise TrustedProviderExecutionError("SECRET_SCAN_ARCHIVE_INVALID") from exc
actual = {p.relative_to(target).as_posix() for p in target.rglob("*") if p.is_file()}
if actual != expected or any(p.is_symlink() for p in target.rglob("*")):
raise TrustedProviderExecutionError("SECRET_SCAN_PAYLOAD_MISMATCH")
return hashlib.sha256(raw).hexdigest()
def _receipt_ref_for(result: Mapping[str, Any], kind: str) -> str:
run_id = hashlib.sha256(canonical_json_bytes(result)).hexdigest()[:16]
return f"run-{run_id}/receipt.json"
@@ -531,6 +567,7 @@ def _run_provider_executor(
target=repo_root,
source_oid=source_oid,
tree_oid=tree_oid,
artifact_path=(Path(os.environ["AIOS_RELEASE_ARTIFACT"]) if os.environ.get("AIOS_RELEASE_ARTIFACT") else None),
)
if kind == "gitea":
gitea_api_url = os.environ.get("AIOS_GITEA_API_URL", "").strip()
@@ -601,9 +638,14 @@ if __name__ == "__main__":
parser.add_argument("--store-root", required=True)
parser.add_argument("--receipt-ref", required=True)
parser.add_argument("--repo-root", default=None, help="Real source checkout to execute against")
parser.add_argument("--artifact-path", type=Path, default=None)
args = parser.parse_args()
if args.repo_root:
os.environ["AIOS_REPO_ROOT"] = args.repo_root
if args.artifact_path:
if args.kind != "secret_scan":
raise SystemExit("ARTIFACT_BINDING_REQUIRES_SECRET_SCAN")
os.environ["AIOS_RELEASE_ARTIFACT"] = str(args.artifact_path)
try:
digest = produce(
args.kind,
@@ -46,12 +46,23 @@ def _paths(binding: Mapping[str, Any]) -> dict[str, Path]:
slug=version.lower()
return {"release_dir":root/(version+"-"+str(binding["target_oid"])[:12]+"-"+str(binding["artifact_sha256"])[:12]),"current_link":Path("/opt/aios")/slug,"env_file":Path("/etc/aios")/(slug+".env"),"state_file":Path("/var/lib/aios")/slug/"deployment-state.json","unit_file":Path("/etc/systemd/system")/(service+".service" if not service.endswith(".service") else service),"health_url":f"http://127.0.0.1:{int(binding['production_port'])}/health"}
def _health(url: str, binding: Mapping[str, Any]) -> dict[str, Any]:
def _health(url: str, binding: Mapping[str, Any], *, runtime_verifier=None) -> dict[str, Any]:
try:
with urllib.request.urlopen(url, timeout=5) as response: body=json.loads(response.read().decode()); status=response.status
except (OSError, ValueError, urllib.error.URLError): return {"status":"UNAVAILABLE"}
exact=(status==200 and body.get("version")==binding["version"] and body.get("commit_oid")==binding["target_oid"] and body.get("artifact_sha256")==binding["artifact_sha256"] and body.get("deployment_state")=="ACTIVE" and body.get("production_deployed") is True)
return {"status":"ACTIVE_VERIFIED" if exact else "CONFLICT", "http_status":status, **body}
if str(binding["version"]).startswith("V2R2C6"):
# Health bodies are untrusted declarations; even a body containing PASS
# cannot certify itself. A host-owned independent verifier must bind
# the response to the exact candidate and fresh observed runtime.
try:
independently_verified = callable(runtime_verifier) and runtime_verifier(binding, body) is True
except Exception:
independently_verified = False
result_status = "ACTIVE_VERIFIED" if exact and independently_verified else "UNKNOWN"
else:
result_status = "ACTIVE_VERIFIED" if exact else "CONFLICT"
return {**body, "status": result_status, "http_status": status}
def _extract(artifact: Path, destination: Path) -> None:
import tarfile
@@ -70,7 +81,13 @@ def probe(value: Mapping[str, Any]) -> dict[str, Any]:
return _health(str(p["health_url"]),binding) if active else {"status":"NOT_APPLIED","version":binding["version"],"target_oid":binding["target_oid"],"artifact_sha256":binding["artifact_sha256"],"production_deployed":False,"deployment_state":"ABSENT"}
def apply(value: Mapping[str, Any]) -> dict[str, Any]:
binding=_binding(value); p=_paths(binding); p["release_dir"].parent.mkdir(parents=True, exist_ok=True); artifact=Path(str(binding["artifact_path"]))
binding=_binding(value)
if str(binding["version"]).startswith("V2R2C6"):
# This adapter installs exactly one legacy health service. A C6
# multi-component payload requires the governed component host binding;
# do not report a health-only installation as a C6 deployment.
raise AdapterBlocked("component_host_binding_required")
p=_paths(binding); p["release_dir"].parent.mkdir(parents=True, exist_ok=True); artifact=Path(str(binding["artifact_path"]))
if artifact.stat().st_size != int(binding["artifact_size"]) or _sha(artifact)!=binding["artifact_sha256"]: raise AdapterBlocked("artifact_binding_mismatch")
marker={"schema":"aios.trusted-runtime-release-binding.v1",**binding}
marker_path=p["release_dir"]/"DEPLOYMENT-BINDING.json"
+6 -1
View File
@@ -78,7 +78,12 @@ def serve(host: str, port: int) -> None:
def main() -> int:
parser=argparse.ArgumentParser(); parser.add_argument("--root",type=Path,default=Path(".")); parser.add_argument("--manifest",type=Path); parser.add_argument("--serve",action="store_true"); parser.add_argument("--host",default="127.0.0.1"); parser.add_argument("--port",type=int,default=18927); args=parser.parse_args()
if args.serve: serve(args.host,args.port); return 0
if args.serve:
# A health listener may report liveness only after its real artifact
# manifest has been verified. This is intentionally not runtime
# identity proof; that needs a separate trusted observation.
verify_manifest(args.root,args.manifest)
serve(args.host,args.port); return 0
print(json.dumps(verify_manifest(args.root,args.manifest),sort_keys=True)); return 0
if __name__ == "__main__": raise SystemExit(main())
+39 -5
View File
@@ -1,6 +1,6 @@
#!/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
@@ -14,6 +14,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},
@@ -66,6 +85,7 @@ def token_identity(token: str):
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
@@ -83,7 +103,9 @@ def token_identity(token: str):
return None
def valid_token(token: str) -> bool:
if DEV_ALLOW: return True
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)
@@ -159,7 +181,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();
@@ -213,7 +247,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'
@@ -268,5 +302,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,30 @@
# C4 capability contract provenance
The C4 checkout has no `aios_capability_contract.py`, although
`active-runtime/app/aios_agent_task_protocol.py` imports it at module scope.
The read-only QA capture used for the missing source closure is:
- source: `C:\\Users\\ImRuY\\.codex-restored\\work\\v2r2c5-release-20260926\\qa\\capability-contract.txt`
- SHA256: `c97c9f8a3cf199a9c0348da1212aa26d161aad8ddaed84db534e46f490f2422f`
- 140 lines
The contract preserves separate task and worker alias maps, maps unknown task
names to `exact:<token>`, ignores unknown worker names, and returns false for
unknown or mismatched eligibility. It is used by formal AgentTask poll and
claim paths only. Generic `task_plane` matching remains outside this lane.
The authoritative runtime overlay is captured separately by QA:
- base SHA256: `953de1d491b4f1bc47c71f502027fd488e6c48ecc49da8a91dc9d8754c320d17`
- overlay SHA256: `7555aa067fc01c6a25276a834dda4924b404f3b0c529ed3f99e9f1d83d18798e`
The consolidated module preserves the base mappings and adds only the seven
explicit lifecycle labels: `agent-task:control`, `governed.task.create`,
`governed.write.files`, `task.principal.issue`, `testing`, `verification`, and
`writer.lease`. It does not depend on an absolute deployed path.
Validation logs:
- `PYTHONPATH=active-runtime/app python -m pytest -q tests/test_v2r2c5_capability_contract.py` -> 6 passed.
- `PYTHONPATH=active-runtime/app;active-runtime python -m pytest -q tests/test_task_admission_bootstrap.py tests/test_task_admission_bootstrap_chain_contract.py` -> 45 passed.
- `tests/test_task_admission_bootstrap_http.py` could not collect on Windows because the existing `gitea_tag_publication.py` imports unavailable `fcntl`; no capability assertion ran in that file.
@@ -0,0 +1,357 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://schemas.aios.invalid/release/v1/authorization-bundle.schema.json",
"type": "object",
"properties": {
"schema_version": {
"const": "1.0.0"
},
"kind": {
"const": "ReleaseAuthorizationBundle"
},
"data_class": {
"$ref": "common.schema.json#/$defs/fixture"
},
"bundle_id": {
"$ref": "common.schema.json#/$defs/id"
},
"bundle_revision": {
"type": "integer",
"minimum": 1
},
"mission_id": {
"$ref": "common.schema.json#/$defs/id"
},
"release_run_id": {
"$ref": "common.schema.json#/$defs/id"
},
"version": {
"type": "string",
"minLength": 1
},
"status": {
"enum": [
"REQUESTED",
"ACTIVE",
"REVOKED",
"COMPLETED"
]
},
"owner_intent_ref": {
"$ref": "common.schema.json#/$defs/digest_ref"
},
"issuer_ref": {
"type": "string",
"minLength": 1
},
"root_grant_ref": {
"anyOf": [
{
"$ref": "common.schema.json#/$defs/digest_ref"
},
{
"type": "null"
}
]
},
"authority_receipt_ref": {
"anyOf": [
{
"$ref": "common.schema.json#/$defs/digest_ref"
},
{
"type": "null"
}
]
},
"scope": {
"type": "object",
"properties": {
"repositories": {
"type": "array",
"items": {
"$ref": "common.schema.json#/$defs/id"
},
"minItems": 1,
"uniqueItems": true
},
"environments": {
"type": "array",
"items": {
"$ref": "common.schema.json#/$defs/id"
},
"minItems": 1,
"uniqueItems": true
},
"component_ids": {
"type": "array",
"items": {
"$ref": "common.schema.json#/$defs/id"
},
"minItems": 1,
"uniqueItems": true
},
"actions": {
"type": "array",
"items": {
"$ref": "common.schema.json#/$defs/action"
},
"minItems": 1,
"uniqueItems": true
}
},
"required": [
"repositories",
"environments",
"component_ids",
"actions"
],
"additionalProperties": false
},
"delegation": {
"type": "object",
"properties": {
"child_tasks": {
"const": true
},
"successor_tasks": {
"const": true
},
"agent_transfer": {
"const": true
},
"scope_must_not_expand": {
"const": true
}
},
"required": [
"child_tasks",
"successor_tasks",
"agent_transfer",
"scope_must_not_expand"
],
"additionalProperties": false
},
"validity": {
"type": "object",
"properties": {
"not_before": {
"$ref": "common.schema.json#/$defs/time"
},
"until": {
"type": "array",
"items": {
"enum": [
"RELEASE_CLOSED",
"REVOKED",
"CANCELLED"
]
},
"uniqueItems": true,
"minItems": 3,
"maxItems": 3
},
"revocation_ref": {
"type": "string",
"minLength": 1
},
"revocation_generation": {
"type": "integer",
"minimum": 1
}
},
"required": [
"not_before",
"until",
"revocation_ref",
"revocation_generation"
],
"additionalProperties": false
},
"credentials": {
"type": "object",
"properties": {
"maximum_ttl_seconds": {
"type": "integer",
"minimum": 1,
"maximum": 3600
},
"renewable_within_goal": {
"const": true
},
"issuer_operation_ref": {
"type": "string",
"minLength": 1
}
},
"required": [
"maximum_ttl_seconds",
"renewable_within_goal",
"issuer_operation_ref"
],
"additionalProperties": false
},
"risk_budget": {
"type": "object",
"properties": {
"max_cumulative_unavailability_seconds": {
"type": "integer",
"minimum": 0
},
"max_error_rate_delta": {
"type": "number",
"minimum": 0,
"maximum": 1
},
"observation_seconds": {
"type": "integer",
"minimum": 1
},
"policy_source_ref": {
"type": "string",
"minLength": 1
}
},
"required": [
"max_cumulative_unavailability_seconds",
"max_error_rate_delta",
"observation_seconds",
"policy_source_ref"
],
"additionalProperties": false
},
"forbidden": {
"type": "array",
"items": {
"enum": [
"delete_database",
"format_storage",
"destroy_unique_backup",
"change_root_trust",
"disable_audit"
]
},
"uniqueItems": true,
"minItems": 5,
"maxItems": 5
},
"owner_required_reasons": {
"type": "array",
"items": {
"$ref": "common.schema.json#/$defs/owner_reason"
},
"uniqueItems": true,
"minItems": 4,
"maxItems": 4
},
"execution_binding": {
"anyOf": [
{
"type": "object",
"properties": {
"contract_id": {
"$ref": "common.schema.json#/$defs/id"
},
"contract_revision": {
"type": "integer",
"minimum": 1
},
"contract_sha256": {
"$ref": "common.schema.json#/$defs/sha256"
},
"content_key": {
"$ref": "common.schema.json#/$defs/sha256"
},
"target_environment": {
"$ref": "common.schema.json#/$defs/id"
},
"task_id": {
"$ref": "common.schema.json#/$defs/id"
},
"attempt_id": {
"$ref": "common.schema.json#/$defs/id"
},
"lease_generation": {
"type": "integer",
"minimum": 1
},
"capability_receipt_ref": {
"$ref": "common.schema.json#/$defs/digest_ref"
},
"expires_at": {
"$ref": "common.schema.json#/$defs/time"
}
},
"required": [
"contract_id",
"contract_revision",
"contract_sha256",
"content_key",
"target_environment",
"task_id",
"attempt_id",
"lease_generation",
"capability_receipt_ref",
"expires_at"
],
"additionalProperties": false
},
{
"type": "null"
}
]
},
"extensions": {
"$ref": "common.schema.json#/$defs/extensions"
}
},
"required": [
"schema_version",
"kind",
"data_class",
"bundle_id",
"bundle_revision",
"mission_id",
"release_run_id",
"version",
"status",
"owner_intent_ref",
"issuer_ref",
"root_grant_ref",
"authority_receipt_ref",
"scope",
"delegation",
"validity",
"credentials",
"risk_budget",
"forbidden",
"owner_required_reasons",
"execution_binding",
"extensions"
],
"additionalProperties": false,
"allOf": [
{
"if": {
"properties": {
"status": {
"const": "ACTIVE"
}
},
"required": [
"status"
]
},
"then": {
"properties": {
"root_grant_ref": {
"$ref": "common.schema.json#/$defs/digest_ref"
},
"authority_receipt_ref": {
"$ref": "common.schema.json#/$defs/digest_ref"
}
}
}
}
]
}
+160
View File
@@ -0,0 +1,160 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://schemas.aios.invalid/release/v1/common.schema.json",
"$comment": "Local identifiers only; no network retrieval. LIVE_CONFIG is a declaration, never proof of authorization.",
"$defs": {
"id": {
"type": "string",
"pattern": "^[A-Za-z0-9][A-Za-z0-9._:/-]{0,199}$"
},
"sha256": {
"type": "string",
"pattern": "^[a-f0-9]{64}$"
},
"oid": {
"type": "string",
"pattern": "^(?:[a-f0-9]{40}|[a-f0-9]{64})$"
},
"time": {
"type": "string",
"format": "date-time"
},
"ref": {
"type": "string",
"minLength": 3
},
"digest_ref": {
"type": "object",
"properties": {
"ref": {
"type": "string",
"minLength": 1
},
"sha256": {
"type": "string",
"pattern": "^[a-f0-9]{64}$"
}
},
"required": [
"ref",
"sha256"
],
"additionalProperties": false
},
"stage": {
"enum": [
"resolve_contract",
"preflight",
"build_test",
"evidence_closure",
"candidate_freeze",
"authorization_check",
"runtime_switch",
"verify",
"tag",
"baseline_registry",
"acceptance",
"close"
]
},
"action": {
"enum": [
"source_update",
"build",
"test",
"staging",
"backup",
"governed_merge",
"production_switch",
"rollback",
"verify",
"tag",
"baseline_update",
"acceptance",
"task_create",
"agent_transfer",
"credential_derive",
"lease_renew",
"evidence_append"
]
},
"actor": {
"type": "object",
"properties": {
"kind": {
"enum": [
"OWNER",
"AGENT",
"SYSTEM"
]
},
"ref": {
"type": "string",
"minLength": 1
}
},
"required": [
"kind",
"ref"
],
"additionalProperties": false
},
"fixture": {
"enum": [
"FIXTURE",
"LIVE_CONFIG"
]
},
"state": {
"enum": [
"CREATED",
"PREPARING",
"RUNNING",
"WAITING_ENVIRONMENT",
"WAITING_CAPACITY",
"WAITING_AUTHORITY",
"WAITING_AGENT",
"WAITING_OWNER",
"RECONCILING",
"RECOVERING",
"VERIFYING",
"FINALIZING",
"ACCEPTED",
"FAILED",
"ROLLED_BACK",
"CLOSED",
"CANCELLED"
]
},
"activity": {
"enum": [
"EXECUTING",
"WAIT_AUTHORITY",
"WAIT_ENVIRONMENT",
"WAIT_AGENT",
"WAIT_REVIEW",
"WAIT_OWNER",
"RECOVERY",
"QUEUED",
"UNKNOWN"
]
},
"owner_reason": {
"enum": [
"GOAL_CHANGE",
"TRUST_BOUNDARY_CHANGE",
"IRREVERSIBLE_OPERATION",
"BUSINESS_RISK_BUDGET_EXCEEDED"
]
},
"extensions": {
"type": "object",
"propertyNames": {
"pattern": "^[a-z][a-z0-9]*(?:[._-][a-z0-9]+)+$"
},
"additionalProperties": {
"type": "object"
}
}
}
}
@@ -0,0 +1,683 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://schemas.aios.invalid/release/v1/release-contract.schema.json",
"type": "object",
"properties": {
"schema_version": {
"const": "1.0.0"
},
"kind": {
"const": "ReleaseContract"
},
"data_class": {
"$ref": "common.schema.json#/$defs/fixture"
},
"contract_id": {
"$ref": "common.schema.json#/$defs/id"
},
"contract_revision": {
"type": "integer",
"minimum": 1
},
"mission_id": {
"$ref": "common.schema.json#/$defs/id"
},
"release_run_id": {
"$ref": "common.schema.json#/$defs/id"
},
"version": {
"type": "string",
"minLength": 1
},
"state": {
"enum": [
"DRAFT",
"RESOLVED",
"FROZEN"
]
},
"target_environment": {
"$ref": "common.schema.json#/$defs/id"
},
"policy_ref": {
"$ref": "common.schema.json#/$defs/digest_ref"
},
"catalog_ref": {
"$ref": "common.schema.json#/$defs/digest_ref"
},
"source": {
"type": "object",
"properties": {
"repository_id": {
"$ref": "common.schema.json#/$defs/id"
},
"authority_ref": {
"type": "string",
"minLength": 1
},
"branch": {
"type": "string",
"minLength": 1
},
"commit_oid": {
"anyOf": [
{
"$ref": "common.schema.json#/$defs/oid"
},
{
"type": "null"
}
]
},
"tree_oid": {
"anyOf": [
{
"$ref": "common.schema.json#/$defs/oid"
},
{
"type": "null"
}
]
},
"governed_commit_oid": {
"anyOf": [
{
"$ref": "common.schema.json#/$defs/oid"
},
{
"type": "null"
}
]
}
},
"required": [
"repository_id",
"authority_ref",
"branch",
"commit_oid",
"tree_oid",
"governed_commit_oid"
],
"additionalProperties": false
},
"build": {
"type": "object",
"properties": {
"definition_ref": {
"$ref": "common.schema.json#/$defs/digest_ref"
},
"output_kind": {
"const": "runtime"
}
},
"required": [
"definition_ref",
"output_kind"
],
"additionalProperties": false
},
"artifact": {
"anyOf": [
{
"type": "object",
"properties": {
"kind": {
"enum": [
"runtime",
"source_archive"
]
},
"ref": {
"type": "string",
"minLength": 1
},
"sha256": {
"$ref": "common.schema.json#/$defs/sha256"
},
"size_bytes": {
"type": "integer",
"minimum": 1
},
"provenance_ref": {
"$ref": "common.schema.json#/$defs/digest_ref"
}
},
"required": [
"kind",
"ref",
"sha256",
"size_bytes",
"provenance_ref"
],
"additionalProperties": false
},
{
"type": "null"
}
]
},
"components": {
"type": "array",
"items": {
"type": "object",
"properties": {
"component_id": {
"$ref": "common.schema.json#/$defs/id"
},
"host_ref": {
"type": "string",
"minLength": 1
},
"service_unit": {
"type": "string",
"minLength": 1
},
"runtime_root": {
"type": "string",
"minLength": 1
},
"bind_address": {
"type": "string",
"minLength": 1
},
"port": {
"type": "integer",
"minimum": 1,
"maximum": 65535
},
"config_ref": {
"$ref": "common.schema.json#/$defs/digest_ref"
},
"credential_refs": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
},
"minItems": 0,
"uniqueItems": true
},
"owner_ref": {
"type": "string",
"minLength": 1
},
"next_owner_ref": {
"anyOf": [
{
"type": "string",
"minLength": 1
},
{
"type": "null"
}
]
},
"health_probe_ref": {
"type": "string",
"minLength": 1
}
},
"required": [
"component_id",
"host_ref",
"service_unit",
"runtime_root",
"bind_address",
"port",
"config_ref",
"credential_refs",
"owner_ref",
"next_owner_ref",
"health_probe_ref"
],
"additionalProperties": false
},
"minItems": 1,
"uniqueItems": true
},
"rollback": {
"type": "object",
"properties": {
"baseline_ref": {
"type": "string",
"minLength": 1
},
"version": {
"type": "string",
"minLength": 1
},
"artifact_sha256": {
"$ref": "common.schema.json#/$defs/sha256"
},
"restore_operation_ref": {
"type": "string",
"minLength": 1
},
"restore_evidence_ref": {
"anyOf": [
{
"$ref": "common.schema.json#/$defs/digest_ref"
},
{
"type": "null"
}
]
},
"data_compatibility": {
"enum": [
"VERIFIED",
"UNKNOWN",
"INCOMPATIBLE"
]
}
},
"required": [
"baseline_ref",
"version",
"artifact_sha256",
"restore_operation_ref",
"restore_evidence_ref",
"data_compatibility"
],
"additionalProperties": false
},
"operations": {
"type": "object",
"properties": {
"resolve_contract": {
"type": "string",
"minLength": 1
},
"preflight": {
"type": "string",
"minLength": 1
},
"build_test": {
"type": "string",
"minLength": 1
},
"evidence_closure": {
"type": "string",
"minLength": 1
},
"candidate_freeze": {
"type": "string",
"minLength": 1
},
"authorization_check": {
"type": "string",
"minLength": 1
},
"runtime_switch": {
"type": "string",
"minLength": 1
},
"verify": {
"type": "string",
"minLength": 1
},
"tag": {
"type": "string",
"minLength": 1
},
"baseline_registry": {
"type": "string",
"minLength": 1
},
"acceptance": {
"type": "string",
"minLength": 1
},
"close": {
"type": "string",
"minLength": 1
}
},
"required": [
"resolve_contract",
"preflight",
"build_test",
"evidence_closure",
"candidate_freeze",
"authorization_check",
"runtime_switch",
"verify",
"tag",
"baseline_registry",
"acceptance",
"close"
],
"additionalProperties": false
},
"risk_budget": {
"type": "object",
"properties": {
"max_cumulative_unavailability_seconds": {
"type": "integer",
"minimum": 0
},
"max_error_rate_delta": {
"type": "number",
"minimum": 0,
"maximum": 1
},
"observation_seconds": {
"type": "integer",
"minimum": 1
},
"policy_source_ref": {
"type": "string",
"minLength": 1
}
},
"required": [
"max_cumulative_unavailability_seconds",
"max_error_rate_delta",
"observation_seconds",
"policy_source_ref"
],
"additionalProperties": false
},
"acceptance": {
"type": "object",
"properties": {
"probe_refs": {
"type": "array",
"items": {
"type": "string",
"minLength": 1
},
"minItems": 1,
"uniqueItems": true
},
"required_receipt_kinds": {
"type": "array",
"items": {
"enum": [
"runtime_identity",
"business_acceptance",
"authorization",
"backup",
"rollback_readiness",
"tag",
"baseline",
"source_lineage"
]
},
"minItems": 8,
"uniqueItems": true
}
},
"required": [
"probe_refs",
"required_receipt_kinds"
],
"additionalProperties": false
},
"evidence": {
"type": "object",
"properties": {
"content_key": {
"anyOf": [
{
"$ref": "common.schema.json#/$defs/sha256"
},
{
"type": "null"
}
]
},
"test_report_ref": {
"anyOf": [
{
"$ref": "common.schema.json#/$defs/digest_ref"
},
{
"type": "null"
}
]
},
"review_report_refs": {
"type": "array",
"items": {
"$ref": "common.schema.json#/$defs/digest_ref"
},
"minItems": 0,
"uniqueItems": true
},
"closure_ref": {
"anyOf": [
{
"$ref": "common.schema.json#/$defs/digest_ref"
},
{
"type": "null"
}
]
}
},
"required": [
"content_key",
"test_report_ref",
"review_report_refs",
"closure_ref"
],
"additionalProperties": false
},
"workload": {
"type": "object",
"properties": {
"base_tree_oid": {
"anyOf": [
{
"$ref": "common.schema.json#/$defs/oid"
},
{
"type": "null"
}
]
},
"files_added": {
"anyOf": [
{
"type": "integer",
"minimum": 0
},
{
"type": "null"
}
]
},
"files_modified": {
"anyOf": [
{
"type": "integer",
"minimum": 0
},
{
"type": "null"
}
]
},
"files_deleted": {
"anyOf": [
{
"type": "integer",
"minimum": 0
},
{
"type": "null"
}
]
},
"lines_added": {
"anyOf": [
{
"type": "integer",
"minimum": 0
},
{
"type": "null"
}
]
},
"lines_deleted": {
"anyOf": [
{
"type": "integer",
"minimum": 0
},
{
"type": "null"
}
]
}
},
"required": [
"base_tree_oid",
"files_added",
"files_modified",
"files_deleted",
"lines_added",
"lines_deleted"
],
"additionalProperties": false
},
"extensions": {
"$ref": "common.schema.json#/$defs/extensions"
}
},
"required": [
"schema_version",
"kind",
"data_class",
"contract_id",
"contract_revision",
"mission_id",
"release_run_id",
"version",
"state",
"target_environment",
"policy_ref",
"catalog_ref",
"source",
"build",
"artifact",
"components",
"rollback",
"operations",
"risk_budget",
"acceptance",
"evidence",
"workload",
"extensions"
],
"additionalProperties": false,
"allOf": [
{
"if": {
"properties": {
"state": {
"enum": [
"RESOLVED",
"FROZEN"
]
}
},
"required": [
"state"
]
},
"then": {
"properties": {
"source": {
"properties": {
"commit_oid": {
"$ref": "common.schema.json#/$defs/oid"
},
"tree_oid": {
"$ref": "common.schema.json#/$defs/oid"
}
}
}
}
}
},
{
"if": {
"properties": {
"state": {
"const": "FROZEN"
}
},
"required": [
"state"
]
},
"then": {
"properties": {
"source": {
"properties": {
"governed_commit_oid": {
"$ref": "common.schema.json#/$defs/oid"
}
}
},
"artifact": {
"type": "object",
"properties": {
"kind": {
"const": "runtime"
},
"ref": {
"type": "string",
"minLength": 1
},
"sha256": {
"$ref": "common.schema.json#/$defs/sha256"
},
"size_bytes": {
"type": "integer",
"minimum": 1
},
"provenance_ref": {
"$ref": "common.schema.json#/$defs/digest_ref"
}
},
"required": [
"kind",
"ref",
"sha256",
"size_bytes",
"provenance_ref"
],
"additionalProperties": false
},
"evidence": {
"properties": {
"content_key": {
"$ref": "common.schema.json#/$defs/sha256"
},
"test_report_ref": {
"$ref": "common.schema.json#/$defs/digest_ref"
},
"closure_ref": {
"$ref": "common.schema.json#/$defs/digest_ref"
}
}
},
"rollback": {
"properties": {
"restore_evidence_ref": {
"$ref": "common.schema.json#/$defs/digest_ref"
},
"data_compatibility": {
"const": "VERIFIED"
}
}
}
}
}
}
]
}
File diff suppressed because it is too large Load Diff
+125
View File
@@ -0,0 +1,125 @@
{
"schema": "aios.component-build.v1",
"components": [
{
"component_id": "formal-agent-task",
"roots": [
"active-runtime/app/aios_agent_task_protocol.py",
"active-runtime/app/aios_agent_task_api_lineage_supersession.py",
"active-runtime/app/aios_task_parameterized_recovery.py"
],
"extra_files": [
"active-runtime/migrations/005_task_lineage_supersession.sql",
"active-runtime/migrations/006_task_lineage_supersession_initial_projection.sql"
],
"entrypoint": "aios_agent_task_protocol:TaskStore",
"dependency_contract_version": "aios.agent-task.capability-contract.v1",
"config": {
"listen_host": "127.0.0.1",
"listen_port": 18940,
"writes_enabled": false,
"credential_refs": [],
"feature_flags": {
"auto_claim": false
}
}
},
{
"component_id": "bridge",
"roots": [
"active-runtime/app/aios_chatgpt_bridge_v2_server.py",
"active-runtime/app/agent_context_api.py",
"active-runtime/app/agent_observability_api.py",
"active-runtime/app/agent_event_api.py",
"active-runtime/app/aios_chatgpt_actions_api.py",
"active-runtime/app/aios_gitops_model_api.py",
"active-runtime/app/aios_model_api_staging.py",
"active-runtime/app/aios_control_plane_runtime_status.py",
"active-runtime/app/governed_unattended_release.py",
"active-runtime/app/silent_release_execution.py",
"active-runtime/app/release_standing_authority.py",
"active-runtime/app/release_host_authority.py",
"active-runtime/app/release_campaign_authorization.py",
"active-runtime/app/owner_authorization_broker.py",
"active-runtime/app/task_center_api.py"
],
"extra_files": [
"deploy/systemd/aios-chatgpt-bridge-v2-staging.service",
"adapters/gitops/aios_gitops_model_api.py",
"contracts/release/v1/common.schema.json",
"contracts/release/v1/release-contract.schema.json",
"contracts/release/v1/authorization-bundle.schema.json",
"contracts/release/v1/release-run-event.schema.json",
"deploy/aios-v2/release-policy.v1.json",
"overlays/user-control-center/portal/gitops-dashboard/app.js",
"overlays/user-control-center/portal/gitops-dashboard/index.html",
"overlays/user-control-center/portal/gitops-dashboard/event_recovery.js",
"overlays/user-control-center/portal/gitops-dashboard/styles.css"
],
"entrypoint": "aios_chatgpt_bridge_v2_server:main",
"dependency_contract_version": "aios.chatgpt-gitops-bridge.v2",
"config": {
"listen_host": "127.0.0.1",
"listen_port": 18892,
"writes_enabled": false,
"credential_refs": [
"gitea-governance-executor-token"
],
"feature_flags": {
"auto_claim": false
}
}
},
{
"component_id": "task-plane",
"roots": [
"active-runtime/app/task_plane/api.py",
"active-runtime/app/task_plane/c6_composition.py",
"active-runtime/app/task_plane/host_patch_executor.py",
"active-runtime/app/task_plane/host_receipt_readers.py",
"active-runtime/app/task_plane/authorization_composition.py",
"active-runtime/app/task_plane/patch_contract.py",
"active-runtime/app/task_plane/evidence_verifier.py",
"active-runtime/app/task_plane/decision_policy.py"
],
"extra_files": [
"scripts/autonomous-release/c6_host_lab.py",
"deploy/aios-v2/c6-host-lab.requirements.lock",
"deploy/aios-v2/c6-host-lab.config.json",
"deploy/aios-v2/c6-host-lab.service"
],
"entrypoint": "app.task_plane.api:run_server",
"dependency_contract_version": "aios.task-plane.v3",
"config": {
"listen_host": "127.0.0.1",
"listen_port": 18940,
"writes_enabled": false,
"credential_refs": [],
"feature_flags": {
"auto_claim": false,
"aro": false
}
}
},
{
"component_id": "health",
"roots": [
"active-runtime/app/v2r2c2_health_service.py"
],
"extra_files": [
"deploy/aios-v2/systemd/aios-v2r2c2-production.service"
],
"entrypoint": "v2r1c6_privacy_tools:main",
"dependency_contract_version": "aios.component-manifest.v1",
"config": {
"listen_host": "127.0.0.1",
"listen_port": 18928,
"writes_enabled": false,
"credential_refs": [],
"feature_flags": {
"runtime_verification": true
}
}
}
]
}
+17
View File
@@ -0,0 +1,17 @@
{
"schema": "aios.c6-host-lab-config.v1",
"activation": "DIAGNOSTIC_ONLY",
"deployable": false,
"production_deployed": false,
"service_type": "simple",
"network_listeners": true,
"listen_mode": "DYNAMIC_LOOPBACK_PORT",
"allowed_lab_root_regex": "^/var/tmp/aios-v2r2c6-lab-[0-9]{8}([-A-Za-z0-9_.]+)?$",
"required_safety_properties": [
"NoNewPrivileges",
"ProtectSystem",
"ProtectHome",
"ReadOnlyPaths",
"ReadWritePaths"
]
}
@@ -0,0 +1,28 @@
# Linux x86_64 CPython 3.12 isolated C6 validation environment; no production readiness claim.
annotated-doc==0.0.5 --hash=sha256:117bac03a25ede5df5440e855b32d556049ca169ead221505badf432fed4b101
annotated-types==0.8.0 --hash=sha256:f072f4d804ea359e4eaf198b1af7a8b0943881a87f31bb764f8bf219bb9419e0
anyio==4.15.1 --hash=sha256:6152fdbbf9a77fdec97731721bebf7c4c44f7c29b424b0065826173efc7ed101
attrs==26.1.0 --hash=sha256:c647aa4a12dfbad9333ca4e71fe62ddc36f4e63b2d260a37a8b83d2f043ac309
cffi==2.1.1 --hash=sha256:c1453022f490d2459a11819d83ad1d586e9ff65a12ac3e705ffebd46d3685dcf
click==8.5.0 --hash=sha256:255bc9599cf7748b4b1a446ccc735421bd08a2ae529a8b88597d3de5664ee360
cryptography==50.0.1 --hash=sha256:51afcfceb15597cf2635068e4ac9a56b2abde622edde17f37d85fd7b5306497a
fastapi==0.141.1 --hash=sha256:bfb91aa2d334c61cb35ba9a116fc123b3d3df31640b801cf57a7a78ec3f603b3
h11==0.16.0 --hash=sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86
idna==3.20 --hash=sha256:ab7ae7122974553370f0bdb919e1a960b2cd1bc1ef0276416d896db81c14582c
iniconfig==2.3.0 --hash=sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12
jsonschema==4.26.0 --hash=sha256:d489f15263b8d200f8387e64b4c3a75f06629559fb73deb8fdfb525f2dab50ce
jsonschema-specifications==2025.9.1 --hash=sha256:98802fee3a11ee76ecaca44429fda8a41bff98b00a0f2838151b113f210cc6fe
packaging==26.3 --hash=sha256:d7193f7c8e4e93f444fde0262bf90af30e16fa0ad0ad44cb553c87339b23cd1c
pluggy==1.6.0 --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746
pycparser==3.0 --hash=sha256:b727414169a36b7d524c1c3e31839a521725078d7b2ff038656844266160a992
pydantic==2.13.5 --hash=sha256:346a034f080da3755d8e9cb5e00e8b07de1d39e4f6e2c87d8ab7cafa0b269a73
pydantic_core==2.46.5 --hash=sha256:0fc5be0abd4a407e200d844b404e33639a554e7bd0d448e7b9ae181be4789ac2
Pygments==2.21.0 --hash=sha256:2363c69b61c4a97c838da3b130dcd6468f4848992b21a82f2a63ec34377137d9
pytest==9.1.1 --hash=sha256:37a86b45efb9a47a61a36449063e8e18d0cab3161329fc099eb21783169c4f0c
PyYAML==6.0.3 --hash=sha256:ba1cc08a7ccde2d2ec775841541641e4548226580ab850948cbfda66a1befcdc
referencing==0.37.0 --hash=sha256:381329a9f99628c9069361716891d34ad94af76e461dcb0335825aecc7692231
rpds-py==2026.6.3 --hash=sha256:ecabd69db66de867690f9797f2f8fa27ba501bbc24540cbdbdc649cd15888ba6
starlette==1.7.0 --hash=sha256:67f8e99895493dd2911a03f11314af6ceebeae4e704bb9f43dfc6a9db151c93e
typing-inspection==0.4.4 --hash=sha256:65b8397ba37ccbce054456aaccddfc91e6e3083c92824df348d96ca832f3f147
typing_extensions==4.16.0 --hash=sha256:481caa481374e813c1b176ada14e97f1f67a4539ce9cfeb3f350d78d6370c2e8
uvicorn==0.54.0 --hash=sha256:505bdb0f318731d45f1f712071fc781a8981f6847a31c902c9f5e652d4f67faf
+18
View File
@@ -0,0 +1,18 @@
[Unit]
Description=AIOS C6 isolated provisional artifact diagnostic
After=network-online.target
[Service]
Type=simple
# Render this reference with scripts/autonomous-release/c6_host_lab.py. The
# generated unit contains the pinned artifact, receipt, runner and contract paths.
NoNewPrivileges=true
# The authorized isolated root is under /var/tmp, so systemd PrivateTmp would
# hide the only permitted runtime path from this service.
PrivateTmp=false
ProtectSystem=strict
ProtectHome=true
RestrictAddressFamilies=AF_UNIX AF_INET
IPAddressDeny=any
IPAddressAllow=127.0.0.1/32
UMask=0077

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