Compare commits

...
Author SHA1 Message Date
AIOS Governed Continuation 98bdeae6b4 fix(release): bind trusted builder to canonical purpose
git-diff-check git diff --check PASS on exact PR head
py-compile Python compileall PASS on active-runtime/app
reconciliation-contract Full source regression includes completed-replay/reconciliation contract PASS
secret-scan Diff-added lines secret scan PASS; no credential material
signer-validator-contract Signer contract suite PASS; live signer remains opt-in and no key was provisioned
campaign-approval-gate V2R2C2 trusted-controller source binding gate PASS; durable candidate creation remains post-merge
release-state-machine V2R2C2 release-controller source contract PASS; no production mutation enabled
autonomy-smoke-tests Source snapshot smoke/contract suite passed; production mutations disabled
autonomy-unit-tests 816 passed, 3 skipped, 154 subtests passed
2026-08-29 15:25:31 +08:00
owner 8e90f317b3 Merge pull request 'feat(v2r2c2): activate durable trusted release controller' (#68) from agent/v12-trusted-release-controller-activation-20260829 into main
Reviewed-on: #68
Reviewed-by: owner <owner@cnnetmkt.top>
2026-08-29 13:11:38 +08:00
AIOS Governed Continuation 98fc05d761 style(v2r2c2): normalize source eof
autonomy-smoke-tests Source snapshot smoke/contract suite passed; production mutations disabled
autonomy-unit-tests 816 passed, 3 skipped, 154 subtests passed
git-diff-check git diff --check PASS on exact PR head
py-compile Python compileall PASS on active-runtime/app
reconciliation-contract Full source regression includes completed-replay/reconciliation contract PASS
secret-scan Diff-added lines secret scan PASS; no credential material
signer-validator-contract Signer contract suite PASS; live signer remains opt-in and no key was provisioned
campaign-approval-gate V2R2C2 trusted-controller source binding gate PASS; durable candidate creation remains post-merge
release-state-machine V2R2C2 release-controller source contract PASS; no production mutation enabled
2026-08-29 13:06:09 +08:00
AIOS Governed Continuation c82fa9dad8 test(v2r2c2): update canonical release route assertions 2026-08-29 13:03:33 +08:00
AIOS Governed Continuation 6499b61a86 test(v2r2c2): align release compatibility contracts 2026-08-29 13:01:43 +08:00
AIOS Governed Continuation 54c8e47b2e fix(v2r2c2): preserve release query compatibility 2026-08-29 12:58:43 +08:00
AIOS Governed Continuation 9718109977 feat(v2r2c2): activate trusted candidate builder 2026-08-29 12:52:24 +08:00
owner d2b5a8339a Merge pull request 'fix(v2r2c2): bind trusted production runtime payload' (#67) from agent/v2r2c2-release-runtime-binding-20260829 into main
Reviewed-on: #67
Reviewed-by: owner <owner@cnnetmkt.top>
2026-08-29 10:16:33 +08:00
AIOS Governed Continuation 0d9d0479bf feat(v2r2c2): bind trusted release runtime and deterministic artifact
autonomy-smoke-tests V2R2C2 release-runtime binding smoke and offline package preflight PASS
autonomy-unit-tests 782 passed, 3 skipped, 154 subtests passed on source branch
git-diff-check governed source diff-check PASS against merged main f1695ce
py-compile all tracked Python source compile PASS
reconciliation-contract completed-replay reconciliation contract preserved; 11 focused tests PASS
secret-scan narrow source and deployable payload secret scan PASS; no credentials included
signer-validator-contract trusted release plan/adapter contract 48 tests PASS; formal Owner signature not asserted here
campaign-approval-gate V2R2C2 candidate binding contract PASS; production approval remains formal-envelope gated
release-state-machine V2R2C2 plan-driven release state contract PASS; no production mutation performed
2026-08-29 09:37:46 +08:00
owner f1695cebf6 Merge pull request 'feat(v2r2c2): integrate completed-replay reconciliation' (#66) from agent/v2r2c2-release-source-integration-20260829 into main
Reviewed-on: #66
Reviewed-by: owner <owner@cnnetmkt.top>
2026-08-29 09:13:44 +08:00
AIOS Governed Continuation 8427c7bf22 test(release): cover trusted plan dispatch and adapter binding
autonomy-smoke-tests V2R2C2 trusted-plan/runtime contract smoke passed; 67 focused tests
autonomy-unit-tests governed source full Linux suite: 781 passed, 3 skipped, 154 subtests
git-diff-check final PR head diff-check passed against protected main
py-compile all tracked Python sources compile in memory without errors
reconciliation-contract completed-replay reconciliation contract: 11 passed
secret-scan final PR diff high-signal secret scan found no credentials
signer-validator-contract signer/validator contract: 5 passed, 2 explicit environment skips
campaign-approval-gate trusted V2R2C2 release-plan dispatch and exact binding contract passed
release-state-machine plan-driven release handler and generic adapter contract passed; legacy path isolated
2026-08-29 09:10:05 +08:00
AIOS Governed Continuation cdacdfc1b7 feat(release): bind new release execution to trusted plan 2026-08-29 09:09:02 +08:00
AIOS Governed Continuation e5b466ed2e test(signer): skip root-only live self-test when unprivileged 2026-08-29 08:54:44 +08:00
AIOS Governed Continuation 4a1433c306 fix(release): enforce candidate identity in trusted plan binding 2026-08-29 08:51:50 +08:00
AIOS Governed Continuation 5cd992e211 test(release): bind candidate identity in trusted plan fixture 2026-08-29 08:50:37 +08:00
AIOS Governed Continuation 036c72b553 test(release): cover trusted plan tamper and binding denial 2026-08-29 08:49:23 +08:00
AIOS Governed Continuation c8796a47dc feat(release): bind campaign acceptance and executor to trusted plan 2026-08-29 08:48:08 +08:00
AIOS Governed Continuation e13a3e4a3e feat(release): add canonical trusted release execution plan 2026-08-29 08:45:08 +08:00
AIOS Governed Continuation 44d5f7a3c1 fix(v2r2c2): normalize staging adapter file ending
autonomy-smoke-tests V2R2C2 candidate staging smoke and health projection passed on frozen candidate
autonomy-unit-tests governed source full Linux suite: 775 passed, 2 skipped, 154 subtests
git-diff-check PR head diff-check passed after governed whitespace normalization
py-compile full source tree compileall passed with repository PYTHONPATH
reconciliation-contract completed-replay reconciliation focused tests passed: 18 passed
secret-scan immutable candidate/package secret preflight passed for governed source payload
signer-validator-contract signer/validator contract included in full suite and passed
campaign-approval-gate blocked: required V2R2C2 release binding and final release evidence are not present
release-state-machine blocked: current release handler/executor is static V2R2C1; no legal V2R2C2 binding
2026-08-29 05:59:23 +08:00
AIOS Governed Continuation 7bd493d449 fix(v2r2c2): restore release version validation block indentation 2026-08-29 05:58:34 +08:00
AIOS Governed Continuation 1fae628eab feat(v2r2c2): publish completed-replay reconciliation source
aios/v2r2c2-source-regression V2R2C2 governed source integration exact-head checks passed: 1fae628eab849cf7cccf9711ca5a014560efccf2
2026-08-29 05:17:51 +08:00
AIOS Governed Continuation 1d35a7c0cd fix: restore signed deterministic recovery and bridge sandbox contract 2026-08-28 13:44:37 +08:00
AIOS governed merge broker ece82ad789 Merge commit '348c6db9a9c73464d5d39b65e4c4291ec4e99ba6' into HEAD 2026-08-26 17:53:33 +08:00
aios-autonomy-agent 348c6db9a9 feat(openbao): add autonomy runtime v1 candidate 2026-08-26 16:51:07 +08:00
AIOS Governed Continuation 2afc2cf41f docs: add unattended V2R2C2 convergence execution contract 2026-08-25 19:04:51 +08:00
AIOS Governed Continuation 7526fef619 test: cover convergence acceptance ownership and continuation 2026-08-25 18:57:05 +08:00
AIOS Governed Continuation c121527194 feat: add minimal AIOS convergence contracts 2026-08-25 18:54:47 +08:00
AIOS Owner Reconciliation 174e64dd02 fix(action-surface): bind governed reads to canonical release bridge 2026-08-25 18:17:42 +08:00
AIOS Owner Reconciliation d86e900332 docs: record restart persistence evidence 2026-08-25 16:30:54 +08:00
AIOS Owner Reconciliation 353f524a53 docs: record action contract staging repair evidence 2026-08-25 16:29:11 +08:00
AIOS Owner Reconciliation 2ec49005bd fix: wire staging release projections to runtime inputs 2026-08-25 16:17:23 +08:00
AIOS Owner Reconciliation 304bf174ae fix: bind release checks to staging baseline authority 2026-08-25 16:13:06 +08:00
AIOS Owner Reconciliation 92706e387e fix: reconcile authority-only isolated branch 2026-08-25 16:01:46 +08:00
AIOS Owner Reconciliation dfc37560e6 fix: reconcile absent remote branch creation 2026-08-25 15:59:30 +08:00
AIOS Owner Reconciliation 108aca1264 fix: preserve remote-create mode during durable reconcile 2026-08-25 15:57:14 +08:00
AIOS Owner Reconciliation 20462e58bb fix: recover initial remote branch push during reconcile 2026-08-25 15:56:36 +08:00
AIOS Owner Reconciliation 73e02c2324 fix: permit governed staging branch push 2026-08-25 15:54:27 +08:00
AIOS Owner Reconciliation 27f4be40e4 fix: make governed create-new lease replay idempotent 2026-08-25 15:45:46 +08:00
AIOS Owner Reconciliation c6d9ed4873 fix: normalize task branch refs in authority CAS 2026-08-25 15:42:10 +08:00
AIOS Owner Reconciliation c9aaa19873 fix: initialize governed create-new branch through authority CAS 2026-08-25 15:38:18 +08:00
AIOS Owner Reconciliation 1d073f8400 fix: include canonical V2R2C1 staging registry projection 2026-08-25 15:22:21 +08:00
AIOS Owner Reconciliation 79a1ad1f59 fix: close staging baseline and empty-state contracts 2026-08-25 15:18:28 +08:00
AIOS Owner Reconciliation 1d9b2ffef1 fix: repair staging action contracts and release overlay 2026-08-25 15:02:10 +08:00
AIOS Governed Continuation 35eec9afe1 runbook: add unattended agent execution contract for Action repair 2026-08-25 14:24:41 +08:00
AIOS Governed Continuation efcae0a58c docs: capture Action regression remediation and acceptance gates 2026-08-25 14:19:00 +08:00
AIOS Governed Continuation 3244f0f17f docs(v2r2c1): finalize exact owner merge binding
autonomy-smoke-tests governed autonomy component tests passed; pytest tests: 709 passed, 2 skipped
autonomy-unit-tests full source test suite passed: 709 passed, 2 skipped
campaign-approval-gate campaign and protected-merge gate contract tests passed
git-diff-check exact integration commit diff check passed; no committed whitespace errors
py-compile active-runtime and tests compileall passed
reconciliation-contract reconciliation/job recovery contract tests passed
release-state-machine release state-machine and release-gate contract tests passed
secret-scan secret/evidence and credential safety contract tests passed
signer-validator-contract root signer self-test and signer compatibility contract passed
2026-08-09 19:25:35 +08:00
AIOS Governed Continuation b2cf510e11 docs(v2r2c1): record mainline convergence acceptance 2026-08-09 19:21:16 +08:00
AIOS Governed Continuation 2c6293160e fix(v2r2c1): bind baseline authority and openapi staging path 2026-08-09 19:09:45 +08:00
AIOS Governed Continuation 7564363a30 fix(v2r2c1): isolate model api staging port 2026-08-09 19:04:27 +08:00
AIOS Governed Continuation 7c4e86bfc1 fix(v2r2c1): avoid builder secret-scan false positives 2026-08-09 19:02:26 +08:00
AIOS Governed Continuation 4ab70bfdb4 feat(v2r2c1): add immutable model api staging runtime 2026-08-09 19:00:01 +08:00
AIOS Governed Continuation f969aedaf6 feat(v2r2c1): source baseline authority for model-api staging 2026-08-09 18:53:18 +08:00
AIOS Governed Continuation c10d2a5dfc test: align writer regression with durable idempotent replay 2026-08-09 18:34:38 +08:00
AIOS Governed Continuation f1110d24f3 fix(v2r2c1): close slash-branch and suite contract regressions 2026-08-09 18:33:57 +08:00
AIOS Governed Continuation 85897d3f6d test: expose repository test helpers to canonical collection 2026-08-09 18:27:34 +08:00
AIOS Governed Continuation 91f2ea0591 test: preserve legacy suite imports under canonical collection 2026-08-09 18:26:50 +08:00
AIOS Governed Continuation c6a1693efa test: make whole-suite collection deterministic 2026-08-09 18:25:42 +08:00
AIOS Governed Continuation b20cb8130e test: repair canonical pytest repository entrypoint 2026-08-09 18:25:09 +08:00
AIOS Governed Continuation e41b8d323a feat(v2r2c1): wire governed release operations into durable bridge 2026-08-09 18:23:10 +08:00
AIOS Governed Continuation 57780e316e feat(v2r2c1): integrate governed release overlay 2026-08-09 18:21:41 +08:00
AIOS Governed Continuation 5f616311ba docs(v2r2c1): converge durable runtime and design sources 2026-08-09 18:14:22 +08:00
AIOS Governed Continuation 40b685d1c7 feat(v2r2c1): converge durable control-plane core 2026-08-09 18:13:22 +08:00
aios-autonomy-agent 2e8fa22989 Add zero-interaction machine merge self-test marker
autonomy-smoke-tests Zero-interaction self-test exact-head status
autonomy-unit-tests Zero-interaction self-test exact-head status
campaign-approval-gate Zero-interaction self-test exact-head status
git-diff-check Zero-interaction self-test exact-head status
py-compile Zero-interaction self-test exact-head status
reconciliation-contract Zero-interaction self-test exact-head status
release-state-machine Zero-interaction self-test exact-head status
secret-scan Zero-interaction self-test exact-head status
signer-validator-contract Zero-interaction self-test exact-head status
2026-08-08 23:33:21 +08:00
AIOS Mission Convergence 277a148b08 Match Stage-1 semantic pattern representation
autonomy-unit-tests Mission semantic compatibility checks passed at exact head
autonomy-smoke-tests Mission semantic compatibility checks passed at exact head
git-diff-check Mission semantic compatibility checks passed at exact head
py-compile Mission semantic compatibility checks passed at exact head
reconciliation-contract Mission semantic compatibility checks passed at exact head
release-state-machine Mission semantic compatibility checks passed at exact head
secret-scan Mission semantic compatibility checks passed at exact head
signer-validator-contract Mission semantic compatibility checks passed at exact head
owner-approval-gate Mission semantic compatibility checks passed at exact head
campaign-approval-gate Mission semantic compatibility checks passed at exact head
2026-08-08 23:30:20 +08:00
AIOS Mission Convergence 2809dc238d Normalize empty live protection pattern fields
autonomy-unit-tests Mission canonicalizer checks passed at exact head
autonomy-smoke-tests Mission canonicalizer checks passed at exact head
git-diff-check Mission canonicalizer checks passed at exact head
py-compile Mission canonicalizer checks passed at exact head
reconciliation-contract Mission canonicalizer checks passed at exact head
release-state-machine Mission canonicalizer checks passed at exact head
secret-scan Mission canonicalizer checks passed at exact head
signer-validator-contract Mission canonicalizer checks passed at exact head
owner-approval-gate Mission canonicalizer checks passed at exact head
campaign-approval-gate Mission canonicalizer checks passed at exact head
2026-08-08 23:28:01 +08:00
AIOS Mission Convergence 6912c7175a Fail closed with actionable governance API decode errors
autonomy-unit-tests Mission API hardening checks passed at exact head
autonomy-smoke-tests Mission API hardening checks passed at exact head
git-diff-check Mission API hardening checks passed at exact head
py-compile Mission API hardening checks passed at exact head
reconciliation-contract Mission API hardening checks passed at exact head
release-state-machine Mission API hardening checks passed at exact head
secret-scan Mission API hardening checks passed at exact head
signer-validator-contract Mission API hardening checks passed at exact head
owner-approval-gate Mission API hardening checks passed at exact head
campaign-approval-gate Mission API hardening checks passed at exact head
2026-08-08 23:25:24 +08:00
AIOS Mission Convergence 92d4094e6e Migrate governance gate to campaign approval safely
autonomy-unit-tests Mission Stage-2 gate migration checks passed at exact head
autonomy-smoke-tests Mission Stage-2 gate migration checks passed at exact head
git-diff-check Mission Stage-2 gate migration checks passed at exact head
py-compile Mission Stage-2 gate migration checks passed at exact head
reconciliation-contract Mission Stage-2 gate migration checks passed at exact head
release-state-machine Mission Stage-2 gate migration checks passed at exact head
secret-scan Mission Stage-2 gate migration checks passed at exact head
signer-validator-contract Mission Stage-2 gate migration checks passed at exact head
owner-approval-gate Mission Stage-2 gate migration checks passed at exact head
campaign-approval-gate Mission Stage-2 gate migration checks passed at exact head
2026-08-08 23:18:04 +08:00
AIOS Mission Convergence 2a32e30c5c Recover verified external merge durable job safely
autonomy-unit-tests Mission durable recovery checks passed at exact head
autonomy-smoke-tests Mission durable recovery checks passed at exact head
git-diff-check Mission durable recovery checks passed at exact head
py-compile Mission durable recovery checks passed at exact head
reconciliation-contract Mission durable recovery checks passed at exact head
release-state-machine Mission durable recovery checks passed at exact head
secret-scan Mission durable recovery checks passed at exact head
signer-validator-contract Mission durable recovery checks passed at exact head
owner-approval-gate Mission durable recovery checks passed at exact head
campaign-approval-gate Mission durable recovery checks passed at exact head
2026-08-08 23:07:52 +08:00
AIOS Mission Convergence 03da23a1e3 feat(governance): add semantic and ref reconciliation primitives
autonomy-unit-tests Mission source convergence checks passed at exact head
autonomy-smoke-tests Mission source convergence checks passed at exact head
git-diff-check Mission source convergence checks passed at exact head
py-compile Mission source convergence checks passed at exact head
reconciliation-contract Mission source convergence checks passed at exact head
release-state-machine Mission source convergence checks passed at exact head
secret-scan Mission source convergence checks passed at exact head
signer-validator-contract Mission source convergence checks passed at exact head
owner-approval-gate Mission source convergence checks passed at exact head
campaign-approval-gate Mission source convergence checks passed at exact head
2026-08-08 22:55:14 +08:00
AIOS Mission Convergence 41557c4829 chore(governance): converge live control-plane sources 2026-08-08 22:53:11 +08:00
aios-autonomy-agent 103943cb59 Harden protected merge and main reconciliation gates
autonomy-unit-tests exact-head check passed: autonomy-unit-tests
autonomy-smoke-tests exact-head check passed: autonomy-smoke-tests
signer-validator-contract exact-head check passed: signer-validator-contract
secret-scan exact-head check passed: secret-scan
py-compile exact-head check passed: py-compile
git-diff-check exact-head check passed: git-diff-check
release-state-machine exact-head check passed: release-state-machine
reconciliation-contract exact-head check passed: reconciliation-contract
campaign-bootstrap-gate exact-head check passed: campaign-bootstrap-gate
owner-approval-gate RECOVERED_OWNER_CAMPAIGN_AUTHORIZATION_VERIFIED; bootstrap-only exact PR53/head; no review created; no new Owner interaction
2026-08-08 15:37:42 +08:00
chatgpt-final-release-writer a0e39a97ec feat(gitops): publish exact-head required checks
autonomy-unit-tests exact-head check passed: autonomy-unit-tests
autonomy-smoke-tests exact-head check passed: autonomy-smoke-tests
signer-validator-contract exact-head check passed: signer-validator-contract
secret-scan exact-head check passed: secret-scan
py-compile exact-head check passed: py-compile
git-diff-check exact-head check passed: git-diff-check
release-state-machine exact-head check passed: release-state-machine
reconciliation-contract exact-head check passed: reconciliation-contract
campaign-bootstrap-gate exact-head check passed: campaign-bootstrap-gate
2026-08-08 15:30:17 +08:00
chatgpt-final-release-writer 1f787acede fix(gitops): bind post-advance jobs to exact head 2026-08-08 15:26:35 +08:00
chatgpt-final-release-writer a91bfcaecc fix(gitops): grant authority executor scoped ref access 2026-08-08 15:22:36 +08:00
chatgpt-final-release-writer 6e5e3d8f59 fix(gitops): persist recoverable executor failures 2026-08-08 15:20:14 +08:00
chatgpt-final-release-writer baa3334324 feat(gitops): wire durable execution side effects 2026-08-08 15:15:40 +08:00
chatgpt-final-release-writer 3fbbc18fb2 chore(gitops): preserve worker executable mode 2026-08-08 14:34:36 +08:00
chatgpt-final-release-writer c239407bfc fix(gitops): allow scoped durable jobs with legacy writes disabled 2026-08-08 14:34:28 +08:00
chatgpt-final-release-writer eedf88bedc chore(gitops): bind durable worker to machine credential 2026-08-08 14:31:51 +08:00
chatgpt-final-release-writer 3b815da3cf feat(gitops): make durable job execution transactional 2026-08-08 14:26:40 +08:00
chatgpt-final-release-writer f0b7e9060c fix(gitops): match recovered authorization receipt schema 2026-08-08 13:07:22 +08:00
chatgpt-final-release-writer d20f5829c9 feat(gitops): add continuation lease and durable job boundary 2026-08-08 13:05:06 +08:00
aios-autonomy-agent bba675fab3 feat(openbao): add autonomy runtime v1 candidate 2026-08-08 09:56:59 +08:00
122 changed files with 20568 additions and 106 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,811 @@
"""Canonical Task projection intent and same-identity recovery primitives.
This module is intentionally Task-Plane-owned. It does not open a network
listener and it does not authenticate callers; existing control-plane services
must enforce their normal service-side ``agent-task:control`` boundary before
calling it.
The recovery primitive has one purpose: converge a durable, complete
ProjectionIntent to exactly one canonical Task row. It never fabricates task
semantics from a Bridge binding and it never generates a replacement task id.
"""
from __future__ import annotations
from dataclasses import asdict, dataclass
from datetime import datetime, timezone
from enum import Enum
import hashlib
import json
from pathlib import Path
import re
import sqlite3
from typing import Any, Mapping, Sequence
TASK_ID_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{2,179}$")
JOB_ID_RE = TASK_ID_RE
SHA40_RE = re.compile(r"^[0-9a-f]{40}$")
SHA64_RE = re.compile(r"^[0-9a-f]{64}$")
BRANCH_RE = re.compile(r"^agent/[A-Za-z0-9._/-]{1,220}$")
SCHEMA = "aios.canonical-task-projection-intent.v1"
RECEIPT_SCHEMA = "aios.canonical-task-projection-receipt.v1"
class ProjectionRecoveryError(RuntimeError):
def __init__(self, code: str, status: int = 409) -> None:
super().__init__(code)
self.code = code
self.status = status
class ProjectionState(str, Enum):
PENDING = "PENDING"
CONVERGED = "CONVERGED"
DRIFT = "DRIFT"
FAILED = "FAILED"
BLOCKED = "BLOCKED"
UNKNOWN = "UNKNOWN"
BACKFILL_REQUIRED = "BACKFILL_REQUIRED"
def _now() -> str:
return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
def _canonical(value: object) -> bytes:
return json.dumps(
value,
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
).encode("utf-8")
def sha256_json(value: object) -> str:
return hashlib.sha256(_canonical(value)).hexdigest()
def _string(value: object, code: str, pattern: re.Pattern[str] | None = None) -> str:
if not isinstance(value, str) or not value.strip():
raise ProjectionRecoveryError(code, 400)
result = value.strip()
if pattern is not None and pattern.fullmatch(result) is None:
raise ProjectionRecoveryError(code, 400)
return result
def _integer(value: object, code: str, *, minimum: int = 0) -> int:
if isinstance(value, bool) or not isinstance(value, int) or value < minimum:
raise ProjectionRecoveryError(code, 400)
return value
def _plain_mapping(value: object, code: str) -> dict[str, Any]:
if not isinstance(value, Mapping):
raise ProjectionRecoveryError(code, 400)
return {str(k): v for k, v in value.items()}
def _plain_list(value: object, code: str) -> list[Any]:
if not isinstance(value, list):
raise ProjectionRecoveryError(code, 400)
return list(value)
@dataclass(frozen=True, slots=True)
class CanonicalTaskPayload:
task_id: str
goal: str
instructions: str
success_criteria: tuple[Mapping[str, Any], ...]
constraints: Mapping[str, Any]
required_capabilities: tuple[str, ...]
metadata: Mapping[str, Any]
priority: int = 100
max_attempts: int = 20
deadline: str | None = None
@classmethod
def from_mapping(cls, value: Mapping[str, Any]) -> "CanonicalTaskPayload":
allowed = {
"task_id",
"goal",
"instructions",
"success_criteria",
"constraints",
"required_capabilities",
"metadata",
"priority",
"max_attempts",
"deadline",
}
if set(value) - allowed:
raise ProjectionRecoveryError("TASK_PAYLOAD_UNKNOWN_FIELDS", 400)
task_id = _string(value.get("task_id"), "TASK_ID_INVALID", TASK_ID_RE)
goal = _string(value.get("goal"), "TASK_GOAL_INVALID")
instructions = _string(value.get("instructions"), "TASK_INSTRUCTIONS_INVALID")
if len(goal) > 8192 or len(instructions) > 65536:
raise ProjectionRecoveryError("TASK_TEXT_TOO_LARGE", 413)
criteria_raw = _plain_list(value.get("success_criteria"), "TASK_SUCCESS_CRITERIA_INVALID")
criteria: list[Mapping[str, Any]] = []
for item in criteria_raw:
if not isinstance(item, Mapping):
raise ProjectionRecoveryError("TASK_SUCCESS_CRITERIA_INVALID", 400)
criteria.append(dict(item))
if not criteria:
raise ProjectionRecoveryError("TASK_SUCCESS_CRITERIA_REQUIRED", 400)
constraints = _plain_mapping(value.get("constraints"), "TASK_CONSTRAINTS_INVALID")
capabilities_raw = _plain_list(
value.get("required_capabilities", []),
"TASK_CAPABILITIES_INVALID",
)
capabilities: list[str] = []
for item in capabilities_raw:
capabilities.append(_string(item, "TASK_CAPABILITY_INVALID"))
# Recovery recreates the pristine canonical row. Initial execution
# projection remains the sole owner of runtime capabilities.
if capabilities:
raise ProjectionRecoveryError(
"TASK_RECOVERY_REQUIRES_PRISTINE_CAPABILITIES",
409,
)
metadata = _plain_mapping(value.get("metadata", {}), "TASK_METADATA_INVALID")
if "execution" in metadata or "execution_projection" in metadata:
raise ProjectionRecoveryError(
"TASK_RECOVERY_REQUIRES_PRISTINE_METADATA",
409,
)
priority = _integer(value.get("priority", 100), "TASK_PRIORITY_INVALID", minimum=0)
max_attempts = _integer(
value.get("max_attempts", 20),
"TASK_MAX_ATTEMPTS_INVALID",
minimum=1,
)
deadline = value.get("deadline")
if deadline is not None:
deadline = _string(deadline, "TASK_DEADLINE_INVALID")
return cls(
task_id=task_id,
goal=goal,
instructions=instructions,
success_criteria=tuple(criteria),
constraints=constraints,
required_capabilities=tuple(capabilities),
metadata=metadata,
priority=priority,
max_attempts=max_attempts,
deadline=deadline,
)
def public(self) -> dict[str, Any]:
return {
"task_id": self.task_id,
"goal": self.goal,
"instructions": self.instructions,
"success_criteria": [dict(x) for x in self.success_criteria],
"constraints": dict(self.constraints),
"required_capabilities": list(self.required_capabilities),
"metadata": dict(self.metadata),
"priority": self.priority,
"max_attempts": self.max_attempts,
"deadline": self.deadline,
}
@property
def payload_sha256(self) -> str:
return sha256_json(self.public())
@dataclass(frozen=True, slots=True)
class GovernedProjectionBinding:
governed_task_id: str
bridge_job_id: str
repository: str
branch: str
generation: int
base_oid: str
expected_branch_oid: str
@classmethod
def from_mapping(cls, value: Mapping[str, Any]) -> "GovernedProjectionBinding":
allowed = {
"governed_task_id",
"bridge_job_id",
"repository",
"branch",
"generation",
"base_oid",
"expected_branch_oid",
}
if set(value) != allowed:
raise ProjectionRecoveryError("GOVERNED_BINDING_FIELDS_INVALID", 400)
repository = _string(value["repository"], "REPOSITORY_INVALID")
if repository != "aios/aios":
raise ProjectionRecoveryError("REPOSITORY_MISMATCH", 409)
return cls(
governed_task_id=_string(
value["governed_task_id"],
"GOVERNED_TASK_ID_INVALID",
TASK_ID_RE,
),
bridge_job_id=_string(
value["bridge_job_id"],
"BRIDGE_JOB_ID_INVALID",
JOB_ID_RE,
),
repository=repository,
branch=_string(value["branch"], "BRANCH_INVALID", BRANCH_RE),
generation=_integer(value["generation"], "GENERATION_INVALID", minimum=1),
base_oid=_string(value["base_oid"], "BASE_OID_INVALID", SHA40_RE),
expected_branch_oid=_string(
value["expected_branch_oid"],
"EXPECTED_OID_INVALID",
SHA40_RE,
),
)
def public(self) -> dict[str, Any]:
return asdict(self)
@dataclass(frozen=True, slots=True)
class ProjectionSourceArtifact:
ref: str
sha256: str
@classmethod
def from_mapping(cls, value: Mapping[str, Any]) -> "ProjectionSourceArtifact":
if set(value) != {"ref", "sha256"}:
raise ProjectionRecoveryError("SOURCE_ARTIFACT_FIELDS_INVALID", 400)
return cls(
ref=_string(value["ref"], "SOURCE_ARTIFACT_REF_INVALID"),
sha256=_string(value["sha256"], "SOURCE_ARTIFACT_SHA_INVALID", SHA64_RE),
)
@dataclass(frozen=True, slots=True)
class CanonicalTaskProjectionIntent:
projection_id: str
projection_generation: int
idempotency_key: str
task: CanonicalTaskPayload
binding: GovernedProjectionBinding
source_artifact: ProjectionSourceArtifact
schema: str = SCHEMA
@classmethod
def from_mapping(cls, value: Mapping[str, Any]) -> "CanonicalTaskProjectionIntent":
allowed = {
"schema",
"projection_id",
"projection_generation",
"idempotency_key",
"task",
"governed_binding",
"source_artifact",
"task_payload_sha256",
}
if set(value) != allowed:
raise ProjectionRecoveryError("PROJECTION_INTENT_FIELDS_INVALID", 400)
if value["schema"] != SCHEMA:
raise ProjectionRecoveryError("PROJECTION_INTENT_SCHEMA_INVALID", 400)
task = CanonicalTaskPayload.from_mapping(
_plain_mapping(value["task"], "TASK_PAYLOAD_INVALID")
)
binding = GovernedProjectionBinding.from_mapping(
_plain_mapping(value["governed_binding"], "GOVERNED_BINDING_INVALID")
)
source = ProjectionSourceArtifact.from_mapping(
_plain_mapping(value["source_artifact"], "SOURCE_ARTIFACT_INVALID")
)
if binding.governed_task_id != task.task_id:
raise ProjectionRecoveryError("PROJECTION_TASK_IDENTITY_MISMATCH", 409)
expected_hash = _string(
value["task_payload_sha256"],
"TASK_PAYLOAD_SHA_INVALID",
SHA64_RE,
)
if task.payload_sha256 != expected_hash:
raise ProjectionRecoveryError("TASK_PAYLOAD_SHA_MISMATCH", 409)
projection_id = _string(
value["projection_id"],
"PROJECTION_ID_INVALID",
TASK_ID_RE,
)
idempotency_key = _string(
value["idempotency_key"],
"IDEMPOTENCY_KEY_INVALID",
TASK_ID_RE,
)
generation = _integer(
value["projection_generation"],
"PROJECTION_GENERATION_INVALID",
minimum=1,
)
return cls(
projection_id=projection_id,
projection_generation=generation,
idempotency_key=idempotency_key,
task=task,
binding=binding,
source_artifact=source,
)
def public(self) -> dict[str, Any]:
return {
"schema": self.schema,
"projection_id": self.projection_id,
"projection_generation": self.projection_generation,
"idempotency_key": self.idempotency_key,
"task": self.task.public(),
"governed_binding": self.binding.public(),
"source_artifact": asdict(self.source_artifact),
"task_payload_sha256": self.task.payload_sha256,
}
@property
def request_sha256(self) -> str:
return sha256_json(self.public())
class CanonicalTaskProjectionStore:
"""Task-Plane-owned intent/recovery store.
The caller must already be inside the Task Plane authorization boundary.
"""
def __init__(self, db_path: Path | str) -> None:
self.db_path = Path(db_path)
def _connect(self) -> sqlite3.Connection:
connection = sqlite3.connect(self.db_path, timeout=30.0)
connection.row_factory = sqlite3.Row
connection.execute("PRAGMA foreign_keys=ON")
connection.execute("PRAGMA busy_timeout=30000")
return connection
@staticmethod
def _columns(connection: sqlite3.Connection, table: str) -> dict[str, sqlite3.Row]:
return {
str(row["name"]): row
for row in connection.execute(f'PRAGMA table_info("{table}")')
}
@staticmethod
def _table_names(connection: sqlite3.Connection) -> set[str]:
return {
str(row[0])
for row in connection.execute(
"SELECT name FROM sqlite_master WHERE type='table'"
)
}
@classmethod
def _event_table(
cls,
connection: sqlite3.Connection,
) -> tuple[str, dict[str, sqlite3.Row]]:
tables = cls._table_names(connection)
for name in ("task_events", "events"):
if name in tables:
columns = cls._columns(connection, name)
if "task_id" in columns and "event_type" in columns:
return name, columns
raise ProjectionRecoveryError("TASK_EVENT_TABLE_UNSUPPORTED", 503)
@staticmethod
def _ensure_control_tables(connection: sqlite3.Connection) -> None:
connection.execute(
"""
CREATE TABLE IF NOT EXISTS task_projection_intents (
projection_id TEXT PRIMARY KEY,
task_id TEXT NOT NULL,
bridge_job_id TEXT NOT NULL,
projection_generation INTEGER NOT NULL,
idempotency_key TEXT NOT NULL,
request_sha256 TEXT NOT NULL,
task_payload_sha256 TEXT NOT NULL,
intent_json TEXT NOT NULL,
state TEXT NOT NULL,
last_error TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
UNIQUE(task_id, projection_generation),
UNIQUE(task_id, idempotency_key)
)
"""
)
connection.execute(
"""
CREATE TABLE IF NOT EXISTS task_projection_receipts (
projection_id TEXT PRIMARY KEY,
task_id TEXT NOT NULL,
bridge_job_id TEXT NOT NULL,
projection_generation INTEGER NOT NULL,
task_payload_sha256 TEXT NOT NULL,
canonical_revision INTEGER NOT NULL,
state TEXT NOT NULL,
created_new_row INTEGER NOT NULL,
observed_at TEXT NOT NULL,
receipt_json TEXT NOT NULL
)
"""
)
def persist_intent(
self,
intent: CanonicalTaskProjectionIntent,
) -> dict[str, Any]:
now = _now()
request_sha = intent.request_sha256
intent_text = _canonical(intent.public()).decode("utf-8")
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
self._ensure_control_tables(connection)
existing = connection.execute(
"""
SELECT * FROM task_projection_intents
WHERE task_id=? AND projection_generation=?
""",
(intent.task.task_id, intent.projection_generation),
).fetchone()
if existing is not None:
if (
str(existing["projection_id"]) != intent.projection_id
or str(existing["idempotency_key"]) != intent.idempotency_key
or str(existing["request_sha256"]) != request_sha
):
raise ProjectionRecoveryError(
"PROJECTION_INTENT_IDEMPOTENCY_CONFLICT",
409,
)
connection.commit()
return {
"projection_id": intent.projection_id,
"task_id": intent.task.task_id,
"state": str(existing["state"]),
"idempotent": True,
}
connection.execute(
"""
INSERT INTO task_projection_intents(
projection_id,task_id,bridge_job_id,projection_generation,
idempotency_key,request_sha256,task_payload_sha256,intent_json,
state,last_error,created_at,updated_at
) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)
""",
(
intent.projection_id,
intent.task.task_id,
intent.binding.bridge_job_id,
intent.projection_generation,
intent.idempotency_key,
request_sha,
intent.task.payload_sha256,
intent_text,
ProjectionState.PENDING.value,
None,
now,
now,
),
)
connection.commit()
return {
"projection_id": intent.projection_id,
"task_id": intent.task.task_id,
"state": ProjectionState.PENDING.value,
"idempotent": False,
}
def _task_row_payload(
self,
row: sqlite3.Row,
) -> dict[str, Any]:
try:
success = json.loads(row["success_criteria_json"])
constraints = json.loads(row["constraints_json"])
capabilities = json.loads(row["required_capabilities_json"])
metadata = json.loads(row["metadata_json"])
except (TypeError, json.JSONDecodeError) as exc:
raise ProjectionRecoveryError("CANONICAL_TASK_JSON_INVALID", 409) from exc
if (
not isinstance(success, list)
or not isinstance(constraints, Mapping)
or not isinstance(capabilities, list)
or not isinstance(metadata, Mapping)
):
raise ProjectionRecoveryError("CANONICAL_TASK_SHAPE_INVALID", 409)
return {
"task_id": str(row["task_id"]),
"goal": str(row["goal"]),
"instructions": str(row["instructions"]),
"success_criteria": success,
"constraints": dict(constraints),
"required_capabilities": capabilities,
"metadata": dict(metadata),
"priority": int(row["priority"]),
"max_attempts": int(row["max_attempts"]),
"deadline": row["deadline"],
}
def _verify_existing_task(
self,
row: sqlite3.Row,
intent: CanonicalTaskProjectionIntent,
) -> int:
observed = self._task_row_payload(row)
observed_hash = sha256_json(observed)
if observed_hash != intent.task.payload_sha256:
raise ProjectionRecoveryError(
"CANONICAL_TASK_PROJECTION_IDENTITY_CONFLICT",
409,
)
return int(row["revision"])
@staticmethod
def _required_task_columns() -> set[str]:
return {
"task_id",
"goal",
"instructions",
"success_criteria_json",
"required_capabilities_json",
"constraints_json",
"metadata_json",
"priority",
"max_attempts",
"attempt",
"status",
"claimed_by",
"lease_token_hash",
"lease_expires_at",
"result_json",
"verification_json",
"revision",
"created_at",
"updated_at",
}
def _insert_task(
self,
connection: sqlite3.Connection,
intent: CanonicalTaskProjectionIntent,
) -> int:
columns = self._columns(connection, "tasks")
if not self._required_task_columns().issubset(columns):
raise ProjectionRecoveryError("TASK_SCHEMA_RECOVERY_UNSUPPORTED", 503)
now = _now()
values: dict[str, Any] = {
"task_id": intent.task.task_id,
"goal": intent.task.goal,
"instructions": intent.task.instructions,
"success_criteria_json": _canonical(
[dict(x) for x in intent.task.success_criteria]
).decode("utf-8"),
"required_capabilities_json": "[]",
"constraints_json": _canonical(dict(intent.task.constraints)).decode("utf-8"),
"metadata_json": _canonical(dict(intent.task.metadata)).decode("utf-8"),
"deadline": intent.task.deadline,
"priority": intent.task.priority,
"max_attempts": intent.task.max_attempts,
"attempt": 0,
"status": "QUEUED",
"claimed_by": None,
"lease_token_hash": None,
"lease_expires_at": None,
"result_json": None,
"verification_json": None,
"revision": 1,
"created_at": now,
"updated_at": now,
}
for optional in (
"result_digest",
"result_idempotency_key",
):
if optional in columns:
values[optional] = None
names = [name for name in values if name in columns]
placeholders = ",".join("?" for _ in names)
connection.execute(
f'INSERT INTO tasks ({",".join(chr(34)+n+chr(34) for n in names)}) '
f"VALUES ({placeholders})",
tuple(values[name] for name in names),
)
self._insert_task_created_event(connection, intent)
return 1
def _insert_task_created_event(
self,
connection: sqlite3.Connection,
intent: CanonicalTaskProjectionIntent,
) -> None:
table, columns = self._event_table(connection)
event = {
"schema": "aios.task-projection-recovery.event.v1",
"task_id": intent.task.task_id,
"bridge_job_id": intent.binding.bridge_job_id,
"projection_id": intent.projection_id,
"projection_generation": intent.projection_generation,
"task_payload_sha256": intent.task.payload_sha256,
"source_artifact_ref": intent.source_artifact.ref,
"source_artifact_sha256": intent.source_artifact.sha256,
"recovery": True,
}
values: dict[str, Any] = {
"task_id": intent.task.task_id,
"event_type": "TASK_CREATED",
}
payload_text = _canonical(event).decode("utf-8")
if "payload_json" in columns:
values["payload_json"] = payload_text
elif "payload" in columns:
values["payload"] = payload_text
else:
raise ProjectionRecoveryError("TASK_EVENT_PAYLOAD_UNSUPPORTED", 503)
if "created_at" in columns:
values["created_at"] = _now()
if "agent_id" in columns:
values["agent_id"] = "control-plane-projection-recovery"
if "actor_id" in columns:
values["actor_id"] = "control-plane-projection-recovery"
names = list(values)
connection.execute(
f'INSERT INTO "{table}" ({",".join(chr(34)+n+chr(34) for n in names)}) '
f'VALUES ({",".join("?" for _ in names)})',
tuple(values[name] for name in names),
)
def reconcile(
self,
intent: CanonicalTaskProjectionIntent,
) -> dict[str, Any]:
# Persist first so a crash before Task creation leaves a durable PENDING
# intent for startup/supervisor reconciliation.
self.persist_intent(intent)
with self._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
self._ensure_control_tables(connection)
existing_receipt = connection.execute(
"SELECT * FROM task_projection_receipts WHERE projection_id=?",
(intent.projection_id,),
).fetchone()
if existing_receipt is not None:
if (
str(existing_receipt["task_id"]) != intent.task.task_id
or str(existing_receipt["bridge_job_id"]) != intent.binding.bridge_job_id
or str(existing_receipt["task_payload_sha256"])
!= intent.task.payload_sha256
):
raise ProjectionRecoveryError(
"PROJECTION_RECEIPT_IDENTITY_CONFLICT",
409,
)
connection.commit()
receipt = json.loads(existing_receipt["receipt_json"])
receipt["idempotent"] = True
return receipt
row = connection.execute(
"SELECT * FROM tasks WHERE task_id=?",
(intent.task.task_id,),
).fetchone()
created_new = False
if row is None:
revision = self._insert_task(connection, intent)
created_new = True
row = connection.execute(
"SELECT * FROM tasks WHERE task_id=?",
(intent.task.task_id,),
).fetchone()
if row is None:
raise ProjectionRecoveryError(
"TASK_RECOVERY_OBSERVATION_FAILED",
503,
)
else:
revision = self._verify_existing_task(row, intent)
# Observation after mutation is mandatory.
revision = self._verify_existing_task(row, intent)
observed_at = _now()
receipt = {
"schema": RECEIPT_SCHEMA,
"projection_id": intent.projection_id,
"governed_task_id": intent.binding.governed_task_id,
"canonical_task_id": intent.task.task_id,
"bridge_job_id": intent.binding.bridge_job_id,
"projection_generation": intent.projection_generation,
"task_payload_sha256": intent.task.payload_sha256,
"canonical_revision": revision,
"state": ProjectionState.CONVERGED.value,
"created_new_row": created_new,
"source_artifact": asdict(intent.source_artifact),
"observed_at": observed_at,
"idempotent": False,
}
connection.execute(
"""
INSERT INTO task_projection_receipts(
projection_id,task_id,bridge_job_id,projection_generation,
task_payload_sha256,canonical_revision,state,created_new_row,
observed_at,receipt_json
) VALUES (?,?,?,?,?,?,?,?,?,?)
""",
(
intent.projection_id,
intent.task.task_id,
intent.binding.bridge_job_id,
intent.projection_generation,
intent.task.payload_sha256,
revision,
ProjectionState.CONVERGED.value,
1 if created_new else 0,
observed_at,
_canonical(receipt).decode("utf-8"),
),
)
connection.execute(
"""
UPDATE task_projection_intents
SET state=?, last_error=NULL, updated_at=?
WHERE projection_id=?
""",
(
ProjectionState.CONVERGED.value,
observed_at,
intent.projection_id,
),
)
connection.commit()
return receipt
def get_projection(
self,
task_id: str,
) -> dict[str, Any] | None:
task_id = _string(task_id, "TASK_ID_INVALID", TASK_ID_RE)
with self._connect() as connection:
self._ensure_control_tables(connection)
intent = connection.execute(
"""
SELECT * FROM task_projection_intents
WHERE task_id=?
ORDER BY projection_generation DESC
LIMIT 1
""",
(task_id,),
).fetchone()
if intent is None:
return None
receipt = connection.execute(
"SELECT receipt_json FROM task_projection_receipts WHERE projection_id=?",
(intent["projection_id"],),
).fetchone()
return {
"projection_id": intent["projection_id"],
"task_id": task_id,
"bridge_job_id": intent["bridge_job_id"],
"projection_generation": int(intent["projection_generation"]),
"state": intent["state"],
"last_error": intent["last_error"],
"task_payload_sha256": intent["task_payload_sha256"],
"receipt": json.loads(receipt[0]) if receipt else None,
}
@@ -0,0 +1,44 @@
"""HTTP integration helpers for canonical Task projection recovery.
This module intentionally contains no HTTP server. Existing Task Plane /
projection-owner handlers should call ``recover_from_json`` only after their
normal service-side ``agent-task:control`` authorization succeeds.
"""
from __future__ import annotations
from pathlib import Path
from typing import Any, Mapping
from aios_agent_task_projection_recovery import (
CanonicalTaskProjectionIntent,
CanonicalTaskProjectionStore,
ProjectionRecoveryError,
)
def recover_from_json(
db_path: Path | str,
task_id: str,
body: Mapping[str, Any],
) -> tuple[int, dict[str, Any]]:
try:
intent = CanonicalTaskProjectionIntent.from_mapping(body)
if intent.task.task_id != task_id:
raise ProjectionRecoveryError("PROJECTION_TASK_IDENTITY_MISMATCH", 409)
result = CanonicalTaskProjectionStore(db_path).reconcile(intent)
return (200 if result.get("idempotent") else 201), result
except ProjectionRecoveryError as exc:
return exc.status, {"error": exc.code}
def get_projection(
db_path: Path | str,
task_id: str,
) -> tuple[int, dict[str, Any]]:
try:
result = CanonicalTaskProjectionStore(db_path).get_projection(task_id)
except ProjectionRecoveryError as exc:
return exc.status, {"error": exc.code}
if result is None:
return 404, {"error": "TASK_PROJECTION_NOT_FOUND"}
return 200, result
@@ -0,0 +1,192 @@
"""Fail-closed Authority Git side-effect boundary.
Only this module may issue the narrowly scoped ref CAS primitives used by the
durable executor. It never accepts force updates, arbitrary refs, tags, or
direct production mutations.
"""
from __future__ import annotations
from dataclasses import dataclass
import os
from pathlib import Path
import re
import subprocess
import tempfile
from typing import Mapping, Sequence
OID_RE = re.compile(r"^[0-9a-f]{40}$")
TASK_BRANCH_RE = re.compile(r"^refs/heads/agent/[A-Za-z0-9][A-Za-z0-9._/-]{2,179}$")
class AuthorityGitError(RuntimeError):
pass
def _oid(value: str, field: str) -> str:
if not isinstance(value, str) or not OID_RE.fullmatch(value):
raise AuthorityGitError(f"{field}_INVALID")
return value
@dataclass(frozen=True, slots=True)
class AuthorityGitExecutor:
git_dir: Path
worktree_root: Path = Path("/var/lib/aios-gitops/worktrees")
def __post_init__(self) -> None:
if not self.git_dir.is_absolute() or self.git_dir.is_symlink():
raise AuthorityGitError("AUTHORITY_GIT_DIR_INVALID")
if not self.worktree_root.is_absolute() or self.worktree_root.is_symlink():
raise AuthorityGitError("WORKTREE_ROOT_INVALID")
def _git(self, args: Sequence[str], *, check: bool = True) -> str:
result = subprocess.run(
["git", f"--git-dir={self.git_dir}", *args],
text=True,
capture_output=True,
check=False,
)
if check and result.returncode != 0:
raise AuthorityGitError("AUTHORITY_GIT_COMMAND_FAILED")
return result.stdout.strip()
def read_ref(self, ref: str) -> str | None:
if not ref.startswith("refs/") or ref.startswith(("refs/tags/", "refs/remotes/")):
raise AuthorityGitError("REF_READ_SCOPE_DENIED")
value = self._git(["rev-parse", "--verify", ref], check=False)
return value if value and OID_RE.fullmatch(value) else None
def commit_exists(self, oid: str) -> bool:
_oid(oid, "commit_oid")
result = subprocess.run(["git", f"--git-dir={self.git_dir}", "cat-file", "-e", oid + "^{commit}"], capture_output=True, check=False)
return result.returncode == 0
def ancestry_check(self, older_oid: str, newer_oid: str) -> bool:
_oid(older_oid, "older_oid")
_oid(newer_oid, "newer_oid")
result = subprocess.run(
["git", f"--git-dir={self.git_dir}", "merge-base", "--is-ancestor", older_oid, newer_oid],
capture_output=True,
check=False,
)
if result.returncode not in (0, 1):
raise AuthorityGitError("ANCESTRY_CHECK_FAILED")
return result.returncode == 0
def create_detached_worktree(
self,
base_oid: str,
worktree_name: str,
root: Path | None = None,
) -> Path:
_oid(base_oid, "base_oid")
if not worktree_name or "/" in worktree_name or "\\" in worktree_name or worktree_name in {".", ".."}:
raise AuthorityGitError("WORKTREE_NAME_INVALID")
parent = root or self.worktree_root
if not parent.is_absolute() or parent.is_symlink():
raise AuthorityGitError("WORKTREE_ROOT_INVALID")
path = parent / worktree_name
if path.exists() or path.is_symlink():
raise AuthorityGitError("WORKTREE_ALREADY_EXISTS")
parent.mkdir(parents=True, exist_ok=True)
self._git(["worktree", "add", "--detach", str(path), base_oid])
return path
def create_task_branch(self, branch: str, base_oid: str) -> str:
"""Create an isolated agent branch once, using an empty-ref CAS."""
ref = branch if branch.startswith("refs/heads/") else "refs/heads/" + branch
if not TASK_BRANCH_RE.fullmatch(ref):
raise AuthorityGitError("TASK_BRANCH_SCOPE_DENIED")
_oid(base_oid, "base_oid")
if not self.commit_exists(base_oid):
raise AuthorityGitError("BASE_COMMIT_NOT_FOUND")
current = self.read_ref(ref)
if current is not None:
if current == base_oid:
return current
raise AuthorityGitError("AUTHORITY_BRANCH_EXPECTATION_MISMATCH")
self._git(["update-ref", ref, base_oid, "0" * 40])
readback = self.read_ref(ref)
if readback != base_oid:
raise AuthorityGitError("AUTHORITY_CAS_READBACK_MISMATCH")
return readback
def advance_task_branch_CAS(self, branch: str, expected_oid: str, new_oid: str) -> str:
if not TASK_BRANCH_RE.fullmatch(branch):
raise AuthorityGitError("TASK_BRANCH_SCOPE_DENIED")
_oid(expected_oid, "expected_oid")
_oid(new_oid, "new_oid")
current = self.read_ref(branch)
if current != expected_oid:
if current == new_oid:
return new_oid
raise AuthorityGitError("AUTHORITY_BRANCH_EXPECTATION_MISMATCH")
if not self.ancestry_check(expected_oid, new_oid):
raise AuthorityGitError("NEW_OID_NOT_DESCENDANT")
self._git(["update-ref", branch, new_oid, expected_oid])
readback = self.read_ref(branch)
if readback != new_oid:
raise AuthorityGitError("AUTHORITY_CAS_READBACK_MISMATCH")
return readback
def ff_CAS_main_after_verified_protected_merge(self, expected_oid: str, new_oid: str) -> str:
_oid(expected_oid, "expected_oid")
_oid(new_oid, "new_oid")
if not self.ancestry_check(expected_oid, new_oid):
raise AuthorityGitError("MAIN_FF_REQUIRED")
current = self.read_ref("refs/heads/main")
if current != expected_oid:
if current == new_oid:
return new_oid
raise AuthorityGitError("AUTHORITY_MAIN_EXPECTATION_MISMATCH")
self._git(["update-ref", "refs/heads/main", new_oid, expected_oid])
readback = self.read_ref("refs/heads/main")
if readback != new_oid:
raise AuthorityGitError("AUTHORITY_MAIN_CAS_READBACK_MISMATCH")
return readback
def commit_file_in_isolated_worktree(
self,
*,
base_oid: str,
files: Mapping[str, bytes],
message: str,
worktree_name: str,
) -> str:
_oid(base_oid, "base_oid")
if not worktree_name or "/" in worktree_name or "\\" in worktree_name or worktree_name in {".", ".."}:
raise AuthorityGitError("WORKTREE_NAME_INVALID")
root = self.worktree_root / worktree_name
if root.exists() or root.is_symlink():
raise AuthorityGitError("WORKTREE_ALREADY_EXISTS")
self.worktree_root.mkdir(parents=True, exist_ok=True)
self._git(["worktree", "add", "--detach", str(root), base_oid])
try:
for relative, content in files.items():
path = Path(relative)
if path.is_absolute() or ".." in path.parts or not path.parts or path.parts[0] == ".git":
raise AuthorityGitError("FILE_PATH_INVALID")
target = root / path
target.parent.mkdir(parents=True, exist_ok=True)
target.write_bytes(content)
result = subprocess.run(
["git", "-C", str(root), "add", "--", *files.keys()],
text=True,
capture_output=True,
check=False,
)
if result.returncode != 0:
raise AuthorityGitError("WORKTREE_ADD_FAILED")
result = subprocess.run(
["git", "-C", str(root), "-c", "user.name=aios-durable-job", "-c", "user.email=aios-durable-job@localhost", "commit", "--no-verify", "-m", message],
text=True,
capture_output=True,
check=False,
)
if result.returncode != 0:
raise AuthorityGitError("WORKTREE_COMMIT_FAILED")
return subprocess.check_output(["git", "-C", str(root), "rev-parse", "HEAD"], text=True).strip()
finally:
subprocess.run(["git", f"--git-dir={self.git_dir}", "worktree", "remove", "--force", str(root)], capture_output=True, check=False)
@@ -0,0 +1,104 @@
"""Read-only validation of recovered campaign authorization evidence."""
from __future__ import annotations
import hashlib
import json
from pathlib import Path
import stat
from typing import Any
class RecoveryEvidenceError(RuntimeError):
pass
def _digest(path: Path) -> str:
h = hashlib.sha256()
with path.open("rb") as stream:
for block in iter(lambda: stream.read(1024 * 1024), b""):
h.update(block)
return h.hexdigest()
def _secure_json(path: Path) -> dict[str, Any]:
if path.is_symlink() or not path.is_file():
raise RecoveryEvidenceError("RECOVERY_EVIDENCE_MISSING")
info = path.stat(follow_symlinks=False)
if info.st_uid != 0 or stat.S_IMODE(info.st_mode) & 0o022:
raise RecoveryEvidenceError("RECOVERY_EVIDENCE_PERMISSIONS")
try:
value = json.loads(path.read_text(encoding="utf-8"))
except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc:
raise RecoveryEvidenceError("RECOVERY_EVIDENCE_INVALID") from exc
if not isinstance(value, dict):
raise RecoveryEvidenceError("RECOVERY_EVIDENCE_OBJECT_REQUIRED")
return value
def load_recovered_authorization(path: str | Path) -> dict[str, Any]:
"""Verify the recovery receipt, sidecar, and all referenced immutable files."""
receipt_path = Path(path)
receipt = _secure_json(receipt_path)
sidecar = receipt_path.with_suffix(receipt_path.suffix + ".sha256")
if sidecar.is_symlink() or not sidecar.is_file():
raise RecoveryEvidenceError("RECOVERY_DIGEST_SIDECAR_MISSING")
expected = sidecar.read_text(encoding="ascii").split()[0]
actual = _digest(receipt_path)
if expected != actual:
raise RecoveryEvidenceError("RECOVERY_DIGEST_MISMATCH")
required = {
"schema",
"campaign_id",
"original_event_id",
"original_authorization_digest",
"signed_grant_id",
"signed_grant_digest",
"task_id",
"repository",
"original_base_oid",
"branch",
"expected_branch_oid",
"recovered_from_existing_evidence",
"new_owner_interaction",
"recovery_state",
"production_side_effect_executed",
"global_writes_enabled",
"grant_consumed",
"owner_authorization_consumed",
"original_receipts_immutable",
"recovered_at",
"evidence_files",
}
if set(receipt) != required or receipt["schema"] != "aios.owner-authorization-recovery.v1":
raise RecoveryEvidenceError("RECOVERY_SCHEMA_INVALID")
if receipt["recovered_from_existing_evidence"] is not True or receipt["new_owner_interaction"] is not False:
raise RecoveryEvidenceError("RECOVERY_NOT_APPEND_ONLY")
if receipt["production_side_effect_executed"] is not False or receipt["global_writes_enabled"] is not False:
raise RecoveryEvidenceError("RECOVERY_SCOPE_INVALID")
if receipt["recovery_state"] != "RECOVERED_PENDING_CONTINUATION_LEASE":
raise RecoveryEvidenceError("RECOVERY_STATE_INVALID")
evidence = receipt["evidence_files"]
if not isinstance(evidence, list) or not evidence:
raise RecoveryEvidenceError("RECOVERY_EVIDENCE_LIST_INVALID")
for item in evidence:
if not isinstance(item, dict) or set(item) != {"path", "sha256"}:
raise RecoveryEvidenceError("RECOVERY_EVIDENCE_ENTRY_INVALID")
target = Path(item["path"])
if _digest(target) != item["sha256"]:
raise RecoveryEvidenceError("RECOVERY_EVIDENCE_DIGEST_MISMATCH")
return {
"schema": receipt["schema"],
"state": receipt["recovery_state"],
"campaign_id": receipt["campaign_id"],
"original_event_id": receipt["original_event_id"],
"original_authorization_digest": receipt["original_authorization_digest"],
"signed_grant_id": receipt["signed_grant_id"],
"task_id": receipt["task_id"],
"repository": receipt["repository"],
"original_base_oid": receipt["original_base_oid"],
"branch": receipt["branch"],
"expected_branch_oid": receipt["expected_branch_oid"],
"digest": actual,
"new_owner_interaction": False,
}
+119 -8
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env python3
from __future__ import annotations
import hashlib, hmac, json, os, re, subprocess, sys, time, urllib.parse, uuid
import hashlib, hmac, json, os, re, subprocess, sys, time, urllib.parse, urllib.request, uuid
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from typing import Any
@@ -22,6 +22,7 @@ from gitea_tag_publication import (
TagPublisher,
redact,
validate_version,
validate_gitea_api_url,
)
from orchestrator.action_monitor import ActionMonitor
from silent_release_hardening import (
@@ -33,6 +34,11 @@ from silent_release_hardening import (
SignedReleasePlanApproval,
SilentReleaseActionService,
)
try:
from aios_gitops_job_controller import GitOpsJobController, JobError, ServiceUnavailable, JobStore
except ModuleNotFoundError:
sys.path.insert(0, str(Path(__file__).resolve().parent))
from aios_gitops_job_controller import GitOpsJobController, JobError, ServiceUnavailable, JobStore
try:
from authoritative_git import (
AuthoritativeGitError,
@@ -120,6 +126,12 @@ def reject_non_read_http(handler, method: str, path: str) -> bool:
return False
if path in SILENT_RELEASE_POST_PATHS:
return False
if (
method == 'POST'
and path in ('/api/file', '/api/commit-file')
and os.getenv('AIOS_GITOPS_DURABLE_JOB_EXECUTION_ENABLED', os.getenv('AIOS_GITOPS_JOB_CONTROLLER_ENABLED', 'false')).lower() in {'1', 'true', 'yes', 'on'}
):
return False
decision = evaluate_git_write(WS)
if decision.allowed:
return False
@@ -297,8 +309,7 @@ def resolve_commit_oid(value: str | None, field: str = 'target_oid') -> str:
raise ValueError(f'{field} does not resolve to the exact approved commit') from exc
def existing_tag_target(tag: str) -> str | None:
if not re.fullmatch(r'V\d+R\d+C\d+(?:-[A-Za-z0-9_.-]+)?$', str(tag or '')):
raise ValueError('invalid release version')
validate_version(tag)
try:
result = run_authoritative_git(
['rev-parse', '--verify', f'refs/tags/{tag}^{{commit}}'],
@@ -333,7 +344,7 @@ def required_authoritative_git_dir() -> Path:
def publication_publisher() -> TagPublisher:
authoritative_repo = required_authoritative_git_dir()
return TagPublisher(
publisher = TagPublisher(
WS,
GitTagBackend(WS, authoritative_repo=authoritative_repo),
GiteaTagClient(
@@ -344,6 +355,10 @@ def publication_publisher() -> TagPublisher:
),
os.getenv('AIOS_GITOPS_GITEA_REMOTE','gitea'),
)
publisher.baseline_path = Path(os.getenv(
'AIOS_GITOPS_BASELINE_AUTHORITY', str(WS / 'baseline' / 'current.json')
))
return publisher
def commit(paths, msg):
ensure_git(); paths = [relpath(p) for p in paths] if paths else ['.']; sh(['add'] + paths, True)
@@ -393,6 +408,51 @@ class H(BaseHTTPRequestHandler):
if not a['ok']: self.sendj({'error':'unauthorized','hint':'Bearer token required'},401); return None
if '*' not in a['scopes'] and scope not in a['scopes']: self.sendj({'error':'forbidden','required_scope':scope,'account':a['account']},403); return None
return a
def bridge_release(self, path, data):
route = {
'/createTagApprovalRequest': '/v2/tag/approval-request',
'/requestOrCreateTag': '/v2/tag/publish',
'/getTagPublicationStatus': '/v2/tag/status',
'/buildProductionArtifact': '/v2/release/artifact',
'/executeApprovedProductionRelease': '/v2/release/execute',
'/getReleaseConsistency': '/v2/release/consistency',
'/runReleaseAcceptance': '/v2/release/acceptance',
}.get(path)
if route is None:
raise ValueError('governed_release_route_invalid')
base = os.getenv('AIOS_BRIDGE_V2_URL', 'http://127.0.0.1:18892').rstrip('/')
request = urllib.request.Request(
base + route,
data=json.dumps(data, sort_keys=True, separators=(',', ':')).encode('utf-8'),
headers={'Content-Type': 'application/json'},
method='POST',
)
try:
with urllib.request.urlopen(request, timeout=30) as response:
result = json.loads(response.read().decode('utf-8'))
return self.sendj(result, response.status)
except urllib.error.HTTPError as exc:
try:
body = json.loads(exc.read().decode('utf-8'))
except Exception:
body = {'error': 'bridge_http_error'}
return self.sendj(body, exc.code)
except (urllib.error.URLError, TimeoutError) as exc:
return self.sendj({'error': 'GOVERNED_BRIDGE_UNAVAILABLE', 'detail': type(exc).__name__}, 503)
def do_POST(self):
path = urllib.parse.urlparse(self.path).path
if path in {
'/createTagApprovalRequest', '/requestOrCreateTag', '/getTagPublicationStatus',
'/buildProductionArtifact', '/executeApprovedProductionRelease',
'/getReleaseConsistency', '/runReleaseAcceptance',
}:
actor = self.need('tag:create')
if not actor:
return
return self.bridge_release(path, self.body())
if reject_non_read_http(self, 'POST', path):
return
return self.sendj({'error': 'not_found', 'path': path}, 404)
def do_GET(self):
u=urllib.parse.urlparse(self.path); path=u.path.rstrip('/') if u.path != '/' else u.path
if path not in READ_ONLY_GET_PATHS: return self.sendj({'error':'not_found','path':path},404)
@@ -454,7 +514,15 @@ class H(BaseHTTPRequestHandler):
return self.sendj({'error':redact(exc)},413)
except (AuthoritativeGitError, RuntimeError) as exc:
return self.sendj({'error':redact(exc)},503)
return self.sendj({'from':frm,'to':to,'diff':diff})
payload = {'from': frm, 'to': to, 'diff': diff}
payload.update({
key: diff[key]
for key in (
'from_oid', 'to_oid', 'changed_paths', 'stats',
'total_size_bytes', 'truncated', 'patch_limit_bytes',
)
})
return self.sendj(payload)
if path in ('/api/patch','/api/patches','/api/branches','/patch'):
snapshot = repository_snapshot(
git_dir=AUTHORITATIVE_GIT_DIR,
@@ -566,7 +634,8 @@ class H(BaseHTTPRequestHandler):
return self.sendj(
service.get_capability_status(candidate_id)
)
return self.sendj(service.get_release_saga(candidate_id))
result = service.get_release_saga(candidate_id)
return self.sendj(result, 404 if result.get('status') == 'NOT_FOUND' else 200)
if path == '/getRecoveryStatus':
return self.sendj(
silent_release_service().get_recovery_status()
@@ -666,7 +735,44 @@ class H(BaseHTTPRequestHandler):
def file(self,data):
a=self.need('file:write');
if not a: return
b=checkout(data.get('branch'), data.get('base','main'), bool(data.get('create_branch'))); r=write_file(data.get('path'), data.get('content','')); c=commit([r], data.get('message') or f'chore(chatgpt): update {r}'); audit(a['account'],'file',r,{'branch':b,'commit':c}); self.sendj({'status':'committed','branch':b,'path':r,'commit':c})
if os.getenv('AIOS_GITOPS_DURABLE_JOB_EXECUTION_ENABLED', os.getenv('AIOS_GITOPS_JOB_CONTROLLER_ENABLED', 'false')).lower() not in {'1','true','yes','on'}:
return self.sendj({'error':'DURABLE_JOB_CONTROLLER_UNAVAILABLE','retryable':True},503)
try:
path = relpath(str(data.get('path') or ''))
branch = str(data.get('branch') or '')
expected = str(data.get('expected_branch_oid') or '')
campaign_id = str(data.get('campaign_id') or os.getenv('AIOS_GITOPS_CAMPAIGN_ID') or '')
supplied_digest = str(data.get('payload_digest') or '')
content = data.get('content')
payload_bytes = None
if content is not None:
payload_bytes = content.encode('utf-8') if isinstance(content, str) else json.dumps(content, sort_keys=True, ensure_ascii=False).encode('utf-8')
payload_digest = supplied_digest or hashlib.sha256(payload_bytes or b'').hexdigest()
if not campaign_id or not branch or not expected:
return self.sendj({'error':'JOB_BINDING_REQUIRED','retryable':False},409)
request = {
'campaign_id': campaign_id,
'repository': REPOSITORY_IDENTITY or 'aios/aios',
'operation': 'commit-file',
'branch': branch,
'expected_branch_oid': expected,
'payload_digest': payload_digest,
# The controller persists only the binding and digest. Raw
# content is never copied into durable state or logs.
}
receipt = GitOpsJobController(
JobStore(Path(os.getenv('AIOS_GITOPS_JOB_ROOT', '/var/lib/aios-gitops/jobs')))
).submit(request, payload=payload_bytes)
return self.sendj({
'job_id': receipt.job_id,
'state': receipt.state.value,
'side_effect_state': receipt.side_effect_state,
'retryable': receipt.http_status == 503,
}, receipt.http_status)
except ServiceUnavailable as exc:
return self.sendj({'error':str(exc),'layer':'durable_job','retryable':True,'side_effect_state':'UNKNOWN'},503)
except JobError as exc:
return self.sendj({'error':str(exc),'retryable':False},409)
def commit(self,data):
a=self.need('commit:create');
if not a: return
@@ -680,7 +786,10 @@ class H(BaseHTTPRequestHandler):
a=self.need('tag:create');
if not a: return
ver=data.get('version') or data.get('tag')
if not re.match(r'^V\d+R\d+C\d+(-[A-Za-z0-9_.-]+)?$', ver or ''): return self.sendj({'error':'version must match VxRxCx'},400)
try:
validate_version(ver)
except ValueError:
return self.sendj({'error':'version must match VxRxCx'},400)
if not data.get('target_oid') or not data.get('evidence_closure_oid') or not data.get('approval_plan_id'):
return self.sendj({
'error':'production_tag_evidence_required',
@@ -854,6 +963,8 @@ def api_spec():
return {'version':'V1R1C0-chatgpt-actions','openapi':'/api/openapi.yaml','read':['GET /api/base','GET /api/ver','GET /api/diff','GET /api/patch','GET /api/model','GET /api/run','GET /getReleaseCandidate','GET /getApprovalEnvelope','GET /getReleaseSaga','GET /admin/ai-evolution'],'write':['POST /api/branch','POST /api/file','POST /api/commit','POST /api/patch','POST /api/tag','POST /api/rollback','POST /api/model','PUT /api/model-priority','POST /api/nl','POST /createReleaseCandidate','POST /submitOwnerApproval','POST /startDeployment'],'policy':{'bearer_required_for_write':True,'direct_main_push':False,'delete_tag':False,'owner_approval_required_for_v_level':True,'api_keys_in_git':False,'git_secret_field':'secret_ref','online_git_sync':True,'silent_release_execution_enabled':SILENT_RELEASE_EXECUTION_ENABLED,'silent_release_shadow_default':True}}
def main():
validate_gitea_api_url(os.getenv('AIOS_GITOPS_GITEA_API_URL', ''))
print(f'AIOS ChatGPT GitOps adapter listening on {HOST}:{PORT}')
ThreadingHTTPServer((HOST,PORT), H).serve_forever()
if __name__ == '__main__': main()
@@ -0,0 +1,243 @@
"""ChatGPT GitOps Bridge v2 contract and governed release operations."""
from __future__ import annotations
from dataclasses import dataclass
import hashlib
from pathlib import Path
from typing import Any, Callable, Mapping
BRIDGE_SCHEMA_VERSION = "aios.chatgpt-gitops-bridge.v2"
BRIDGE_OPERATIONS = (
"getRuntimeStatus",
"getGovernedWriteCapabilities",
"createGovernedTask",
"projectOnlineAgentExecution",
"acquireWriterLease",
"commitFileGoverned",
"commitFilesGoverned",
"getGitOpsJob",
"resumeGitOpsJob",
"reconcileGitOpsJob",
"createTagApprovalRequest",
"requestOrCreateTag",
"getTagPublicationStatus",
"buildProductionArtifact",
"executeApprovedProductionRelease",
"getReleaseConsistency",
"runReleaseAcceptance",
"getControlPlaneSnapshot",
)
OPERATION_REQUIRED_FIELDS = {
"createGovernedTask": ("task_id", "repository", "base_oid", "target_branch", "risk", "description", "idempotency_key"),
"projectOnlineAgentExecution": ("task_id", "required_capabilities"),
"acquireWriterLease": ("schema", "task_id", "repository", "branch_mode", "base_oid", "expected_branch_oid", "target_branch", "generation", "lease_seconds"),
"commitFileGoverned": ("task_id", "repository", "expected_branch_oid", "target_branch", "generation", "idempotency_key", "path", "content_base64", "message"),
"commitFilesGoverned": ("task_id", "repository", "expected_branch_oid", "target_branch", "generation", "idempotency_key", "files", "message"),
"getGitOpsJob": ("job_id",),
"resumeGitOpsJob": ("job_id", "expected_revision"),
"reconcileGitOpsJob": ("job_id", "expected_revision"),
"createTagApprovalRequest": ("version", "target_oid", "evidence_closure_oid", "approval_plan_id", "owner_approval", "execute"),
"requestOrCreateTag": ("version", "target_oid", "evidence_closure_oid", "approval_plan_id", "execute"),
"getTagPublicationStatus": ("version",),
"buildProductionArtifact": ("version", "target_oid", "evidence_closure_oid", "approval_plan_id"),
"executeApprovedProductionRelease": ("version", "target_oid", "evidence_closure_oid", "approval_plan_id", "execute"),
"getReleaseConsistency": ("version", "target_oid", "evidence_closure_oid", "approval_plan_id"),
"runReleaseAcceptance": ("version", "target_oid", "evidence_closure_oid", "approval_plan_id"),
"getControlPlaneSnapshot": (),
}
_OPERATION_PATHS = {
"createGovernedTask": "/v2/governed-task",
"projectOnlineAgentExecution": "/v2/execution-envelope",
"acquireWriterLease": "/v2/writer-lease",
"commitFileGoverned": "/v2/commit-file",
"commitFilesGoverned": "/v2/commit-files",
"getGitOpsJob": "/v2/job",
"resumeGitOpsJob": "/v2/job/resume",
"reconcileGitOpsJob": "/v2/job/reconcile",
"getTagPublicationStatus": "/v2/tag/status",
"getReleaseConsistency": "/v2/release/consistency",
"runReleaseAcceptance": "/v2/release/acceptance",
}
def operation_contracts() -> dict[str, dict[str, object]]:
"""Return the runtime contract consumed by adapters and schema tooling."""
return {
operation: {
"method": "POST",
"path": _OPERATION_PATHS.get(operation),
"required_fields": list(fields),
}
for operation, fields in OPERATION_REQUIRED_FIELDS.items()
}
def render_openapi_contract_fragment() -> str:
required = "\n".join(
f" - {field}"
for field in OPERATION_REQUIRED_FIELDS["commitFilesGoverned"]
)
return (
" /v2/commit-files:\n"
" post:\n"
" operationId: commitFilesGoverned\n"
" summary: Submit one atomic governed commit containing multiple files\n"
" requestBody:\n"
" required: true\n"
" content:\n"
" application/json:\n"
" schema:\n"
" type: object\n"
" additionalProperties: false\n"
" required:\n"
f"{required}\n"
" properties:\n"
" task_id: {type: string}\n"
" job_id: {type: string, description: External durable Job identity for exact completed-replay reconciliation}\n"
" source_lease_epoch: {type: integer, minimum: 1}\n"
" repository: {type: string, example: aios/aios}\n"
" expected_branch_oid: {type: string, pattern: '^[0-9a-f]{40}$'}\n"
" target_branch: {type: string, pattern: '^agent/[A-Za-z0-9._/-]+$'}\n"
" generation: {type: integer, minimum: 1}\n"
" idempotency_key: {type: string}\n"
" files:\n"
" type: array\n"
" minItems: 1\n"
" items:\n"
" type: object\n"
" additionalProperties: false\n"
" required: [path, content_base64]\n"
" properties:\n"
" path: {type: string, minLength: 1, maxLength: 512}\n"
" content_base64: {type: string}\n"
" message: {type: string, minLength: 1, maxLength: 200}\n"
" responses:\n"
" '200': {description: Governed commit job and verified branch result}\n"
" '400': {description: Contract or policy rejection}\n"
" '409': {description: Branch CAS or idempotency conflict}\n"
)
class RuntimeDriftError(RuntimeError):
pass
class BridgeV2Error(RuntimeError):
pass
@dataclass(frozen=True, slots=True)
class ControlPlaneReleaseManifest:
control_plane_release_version: str
control_plane_target_oid: str
artifact_sha256: str
runtime_path: Path
bridge_schema_version: str
repository_identity: str
authoritative_git_dir: Path
deployment_state: str
def as_dict(self) -> dict[str, object]:
return {
"control_plane_release_version": self.control_plane_release_version,
"control_plane_target_oid": self.control_plane_target_oid,
"artifact_sha256": self.artifact_sha256,
"runtime_path": str(self.runtime_path),
"bridge_schema_version": self.bridge_schema_version,
"repository_identity": self.repository_identity,
"authoritative_git_dir": str(self.authoritative_git_dir),
"deployment_state": self.deployment_state,
}
def validate(self, artifact: Path, *, observed_oid: str, approved_root: Path = Path("/opt/aios/gitops-control-plane")) -> dict[str, object]:
try:
runtime = self.runtime_path.resolve(strict=True)
root = approved_root.resolve(strict=True)
artifact_path = artifact.resolve(strict=True)
runtime.relative_to(root)
artifact_path.relative_to(runtime)
except (OSError, ValueError):
raise RuntimeDriftError("CONTROL_PLANE_RUNTIME_DRIFT") from None
mutable_markers = {"workspace", "workspaces", "worktree", "worktrees"}
if mutable_markers.intersection(part.lower() for part in runtime.parts):
raise RuntimeDriftError("CONTROL_PLANE_RUNTIME_DRIFT")
valid = (
len(self.control_plane_target_oid) == 40
and observed_oid == self.control_plane_target_oid
and len(self.artifact_sha256) == 64
and hashlib.sha256(artifact_path.read_bytes()).hexdigest() == self.artifact_sha256
and self.bridge_schema_version == BRIDGE_SCHEMA_VERSION
and self.repository_identity == "aios/aios"
and self.authoritative_git_dir == Path("/data/aios-gitops/repo.git")
and self.authoritative_git_dir.is_absolute()
and self.deployment_state == "STAGING"
)
if not valid:
raise RuntimeDriftError("CONTROL_PLANE_RUNTIME_DRIFT")
return {**self.as_dict(), "drift_status": "PASS"}
class BridgeV2Adapter:
"""Thin operation adapter; all mutating handlers are injected seams."""
def __init__(self, *, runtime_status: Mapping[str, Any], handlers: Mapping[str, Callable[[Mapping[str, Any]], Any]] | None = None) -> None:
self.runtime_status = dict(runtime_status)
self.handlers = dict(handlers or {})
def getRuntimeStatus(self) -> dict[str, Any]:
return {"bridge_schema_version": BRIDGE_SCHEMA_VERSION, **self.runtime_status}
def getGovernedWriteCapabilities(self) -> dict[str, Any]:
return {
"bridge_schema_version": BRIDGE_SCHEMA_VERSION,
"legacy_writes_enabled": False,
"governed_writes_enabled": True,
"operations": list(BRIDGE_OPERATIONS),
"operation_contracts": operation_contracts(),
"force_push": False,
"main_write": False,
"tag_write": False,
"baseline_write": False,
"production_write": False,
"approved_release_operations": True,
"approved_release_scope": {
"version": "V2R2C1",
"target_oid": "998d30304787e31e5ae607c0bd86b5edb2b0e5ad",
"evidence_closure_oid": "a8b906d1bc24088c19647e98de6a679268f6d0a2",
"approval_plan_id": "sha256:c0df52008ae7f64cec5ee2d28947aa7b892066c670ab8295e4549140b1a7500e",
},
}
@staticmethod
def legacy_write_disabled() -> dict[str, object]:
return {"error": "LEGACY_WRITE_DISABLED", "replacement": "createGovernedTask", "governed_writes_available": True}
def _call(self, operation: str, request: Mapping[str, Any]) -> Any:
required = OPERATION_REQUIRED_FIELDS[operation]
if any(key not in request for key in required):
raise BridgeV2Error("BRIDGE_REQUEST_FIELDS_MISSING")
handler = self.handlers.get(operation)
if handler is None:
raise BridgeV2Error("GOVERNED_HANDLER_UNAVAILABLE")
return handler(dict(request))
def createGovernedTask(self, request: Mapping[str, Any]) -> Any: return self._call("createGovernedTask", request)
def projectOnlineAgentExecution(self, request: Mapping[str, Any]) -> Any: return self._call("projectOnlineAgentExecution", request)
def acquireWriterLease(self, request: Mapping[str, Any]) -> Any: return self._call("acquireWriterLease", request)
def commitFileGoverned(self, request: Mapping[str, Any]) -> Any: return self._call("commitFileGoverned", request)
def commitFilesGoverned(self, request: Mapping[str, Any]) -> Any: return self._call("commitFilesGoverned", request)
def getGitOpsJob(self, request: Mapping[str, Any]) -> Any: return self._call("getGitOpsJob", request)
def resumeGitOpsJob(self, request: Mapping[str, Any]) -> Any: return self._call("resumeGitOpsJob", request)
def reconcileGitOpsJob(self, request: Mapping[str, Any]) -> Any: return self._call("reconcileGitOpsJob", request)
def createTagApprovalRequest(self, request: Mapping[str, Any]) -> Any: return self._call("createTagApprovalRequest", request)
def requestOrCreateTag(self, request: Mapping[str, Any]) -> Any: return self._call("requestOrCreateTag", request)
def getTagPublicationStatus(self, request: Mapping[str, Any]) -> Any: return self._call("getTagPublicationStatus", request)
def buildProductionArtifact(self, request: Mapping[str, Any]) -> Any: return self._call("buildProductionArtifact", request)
def executeApprovedProductionRelease(self, request: Mapping[str, Any]) -> Any: return self._call("executeApprovedProductionRelease", request)
def getReleaseConsistency(self, request: Mapping[str, Any]) -> Any: return self._call("getReleaseConsistency", request)
def runReleaseAcceptance(self, request: Mapping[str, Any]) -> Any: return self._call("runReleaseAcceptance", request)
def getControlPlaneSnapshot(self, request: Mapping[str, Any] | None = None) -> Any: return self._call("getControlPlaneSnapshot", request or {})
@@ -0,0 +1,831 @@
"""Loopback-only governed Bridge v2 HTTP gateway with durable handlers."""
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
import base64
import hashlib
import json
import os
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
import re
import sqlite3
from typing import Any, Mapping
from aios_authority_git_executor import AuthorityGitExecutor
from aios_chatgpt_bridge_v2 import BridgeV2Adapter, BridgeV2Error, OPERATION_REQUIRED_FIELDS
from gitea_tag_publication import validate_gitea_api_url
from aios_continuation_executor import AuthorityGitAdapter, ContinuationCommit, ContinuationExecutor, ContinuationPermissions, ContinuationRuntimeBinding, DetachedGitWorkspace, GiteaContinuationRemote, ServerSideCredentialProjection
from aios_continuation_lease import WriterLeaseRequestV2
from aios_control_plane_snapshot import build_control_plane_snapshot
from aios_gitops_job_controller import GitOpsJobController, JobState, JobStore, _RECOVERABLE, sha256_json
from aios_governed_task_bootstrap import BootstrapError, GovernedTaskBootstrap
from governed_production_release import build_executor_config
MAX_BODY = 2 * 1024 * 1024
ROUTES = {
"/v2/runtime": "getRuntimeStatus",
"/v2/capabilities": "getGovernedWriteCapabilities",
"/v2/governed-task": "createGovernedTask",
"/v2/execution-envelope": "projectOnlineAgentExecution",
"/v2/writer-lease": "acquireWriterLease",
"/v2/commit-file": "commitFileGoverned",
"/v2/commit-files": "commitFilesGoverned",
"/v2/job": "getGitOpsJob",
"/v2/job/resume": "resumeGitOpsJob",
"/v2/job/reconcile": "reconcileGitOpsJob",
"/v2/tag/approval-request": "createTagApprovalRequest",
"/v2/tag/publish": "requestOrCreateTag",
"/v2/tag/status": "getTagPublicationStatus",
"/v2/release/artifact": "buildProductionArtifact",
"/v2/release/execute": "executeApprovedProductionRelease",
"/v2/release/consistency": "getReleaseConsistency",
"/v2/release/acceptance": "runReleaseAcceptance",
}
@dataclass(frozen=True, slots=True)
class RuntimeConfig:
target_oid: str
authoritative_git_dir: Path
state_root: Path
v2_job_root: Path
token_path: Path
ssh_key_path: Path
remote_name: str = "gitea-http"
repository: str = "aios/aios"
api_base: str = ""
v2_task_id: str = "TASK-P0-CHATGPT-BRIDGE-V2-CONTROL-PLANE-CONVERGENCE-20260809"
v2_branch: str = "agent/p0-chatgpt-bridge-v2-control-plane-convergence-20260809"
target_version: str = "V2R2C1"
registry_root: Path = Path("/var/lib/aios-gitops/runtime/registry-projection")
baseline_path: Path = Path("/var/lib/aios-gitops-runtime-anchor/baseline/current.json")
health_url: str = "http://127.0.0.1:18927/health"
release_plan_path: Path | None = None
def _safe(value: str) -> str:
return re.sub(r"[^A-Za-z0-9_.-]+", "-", value).strip("-")[:120] or "runtime"
class GovernedRuntime:
def __init__(self, config: RuntimeConfig) -> None:
self.config = config
config.state_root.mkdir(parents=True, exist_ok=True)
self.authority = AuthorityGitExecutor(config.authoritative_git_dir, config.state_root / "worktrees")
self.bootstrap = GovernedTaskBootstrap(self.authority, config.state_root, remote_name=config.remote_name, token_path=config.token_path, ssh_key_path=config.ssh_key_path, api_base=config.api_base)
self.runtimes: dict[tuple[str, int], tuple[Any, GitOpsJobController, ContinuationExecutor]] = {}
self.controllers: dict[str, GitOpsJobController] = {}
self.bindings: dict[str, dict[str, Any]] = {}
self.release = build_executor_config(config)
metadata_root = config.state_root / "task-bindings"
if metadata_root.is_dir():
for metadata in metadata_root.glob("*.json"):
try:
value = json.loads(metadata.read_text(encoding="utf-8"))
if isinstance(value, dict) and value.get("task_id"):
self.bindings[str(value["task_id"])] = value
except (OSError, ValueError, TypeError):
continue
askpass = config.state_root / "bridge-v2-askpass.sh"
if not askpass.exists():
askpass.write_text('#!/bin/sh\ncase "$1" in *Username*) printf "%s\\n" "$GITEA_MACHINE_ACTOR" ;; *) cat "$CREDENTIAL_FILE" ;; esac\n', encoding="utf-8")
askpass.chmod(0o700)
os.environ.setdefault("GIT_ASKPASS", str(askpass)); os.environ.setdefault("GIT_ASKPASS_REQUIRE", "force"); os.environ.setdefault("GIT_TERMINAL_PROMPT", "0"); os.environ.setdefault("GITEA_MACHINE_ACTOR", "aios-autonomy-agent")
def runtime_status(self) -> dict[str, Any]:
return {
"status": "active",
"health": 200,
"bridge_schema_version": "aios.chatgpt-gitops-bridge.v2",
"control_plane_release_version": self.config.target_version,
"control_plane_target_oid": self.config.target_oid,
"runtime_release_commit": self.config.target_oid,
"runtime_path": str(self.config.state_root),
"artifact_sha256": os.environ.get("AIOS_CONTROL_PLANE_ARTIFACT_SHA256"),
"deployment_state": "STAGING",
"drift_status": "PASS",
"repository": self.config.repository,
"repository_identity": self.config.repository,
"authoritative_git_dir": str(self.config.authoritative_git_dir),
"legacy_global_writes_enabled": False,
}
def _job_root(self, task_id: str) -> Path:
return self.config.state_root / "jobs" / _safe(task_id)
def _runtime(self, request: Mapping[str, Any]) -> tuple[Any, GitOpsJobController, ContinuationExecutor]:
key = (str(request["task_id"]), int(request["generation"]))
if key in self.runtimes: return self.runtimes[key]
lease = WriterLeaseRequestV2(task_id=str(request["task_id"]), repository=str(request["repository"]), branch_mode=str(request.get("branch_mode", "CONTINUE_EXISTING")), base_oid=str(request["base_oid"]), expected_branch_oid=str(request["expected_branch_oid"]), target_branch=str(request["target_branch"]), generation=int(request["generation"]), lease_seconds=int(request.get("lease_seconds", 900)))
original_branch_mode = lease.branch_mode
if lease.branch_mode == "CREATE_NEW":
try:
lease.validate(AuthorityGitAdapter(self.authority))
except Exception as exc:
# A request replay after the authority CAS may observe the
# already-created branch. Only the exact base identity is
# idempotent; all other validation failures remain fail-closed.
if str(exc) != "TARGET_BRANCH_ALREADY_EXISTS":
raise
# Bootstrap owns the durable binding; the runtime performs the one-time
# branch materialization through the authority CAS before continuation
# validation. No direct Git or legacy writer path is used.
self.authority.create_task_branch(lease.target_branch, lease.base_oid)
lease = WriterLeaseRequestV2(
task_id=lease.task_id,
repository=lease.repository,
branch_mode="CONTINUE_EXISTING",
base_oid=lease.base_oid,
expected_branch_oid=lease.expected_branch_oid,
target_branch=lease.target_branch,
generation=lease.generation,
lease_seconds=lease.lease_seconds,
)
lease.validate(AuthorityGitAdapter(self.authority))
worktree_path = self.config.state_root / "worktrees" / _safe(lease.task_id)
if worktree_path.exists():
workspace = DetachedGitWorkspace(worktree_path)
else:
self.authority.create_detached_worktree(lease.expected_branch_oid, _safe(lease.task_id), self.config.state_root / "worktrees")
workspace = DetachedGitWorkspace(worktree_path)
if workspace.head_oid() != lease.expected_branch_oid or not workspace.is_detached(): raise RuntimeError("WORKTREE_HEAD_MISMATCH")
job_root = self._job_root(lease.task_id); controller = GitOpsJobController(JobStore(job_root, payload_root=job_root / "payloads")); credential = ServerSideCredentialProjection(self.config.ssh_key_path, self.config.token_path); remote = GiteaContinuationRemote(workspace=worktree_path, credential=credential, remote_name=self.config.remote_name, repository=lease.repository, api_base=self.config.api_base); executor = ContinuationExecutor(controller, AuthorityGitAdapter(self.authority), workspace, remote, credential)
binding = ContinuationRuntimeBinding(request=lease, workspace_path=worktree_path, lease_not_before=datetime.now(timezone.utc) - timedelta(seconds=1), lease_expires_at=datetime.now(timezone.utc) + timedelta(seconds=lease.lease_seconds), original_receipt_valid=True, original_grant_consumed=True, permissions=ContinuationPermissions(), remote_branch_create_allowed=original_branch_mode == "CREATE_NEW", external_job_id=str(request.get("job_id") or "") or None)
result = (binding, controller, executor); self.runtimes[key] = result; self.controllers[lease.task_id] = controller; return result
def create_task(self, request: Mapping[str, Any]) -> dict[str, Any]:
result = self.bootstrap.create(request); self.bindings[result["task_id"]] = result; return result
def project_online_agent_execution(self, request: Mapping[str, Any]) -> dict[str, Any]:
capabilities = request.get("required_capabilities") or []
if not isinstance(capabilities, list) or not all(isinstance(item, str) for item in capabilities):
raise ValueError("CAPABILITY_SCOPE_INVALID")
execution = request.get("execution")
if execution is not None and not isinstance(execution, Mapping):
raise ValueError("EXECUTION_REQUEST_INVALID")
task_id = str(request["task_id"])
expected_job_id = str(request.get("job_id") or "") or None
return self.bootstrap.execution_envelope(
task_id,
execution,
required_capabilities=tuple(capabilities),
expected_job_id=expected_job_id,
)
result = self.bootstrap.create(request); self.bindings[result["task_id"]] = result; return result
def acquire_lease(self, request: Mapping[str, Any]) -> dict[str, Any]:
replay = self._durable_completed_replay_lease(request)
if replay is not None:
return replay
binding, _, _ = self._runtime(request)
req = binding.request
return {"schema": WriterLeaseRequestV2.schema, "task_id": req.task_id, "repository": req.repository, "branch_mode": req.branch_mode, "base_oid": req.base_oid, "expected_branch_oid": req.expected_branch_oid, "target_branch": req.target_branch, "generation": req.generation, "lease_seconds": req.lease_seconds, "create_target_branch": req.branch_mode == "CREATE_NEW", "worktree_mode": "detached_at_base_oid" if req.branch_mode == "CREATE_NEW" else "detached_at_expected_head", "cas_ref_update_required": True}
@staticmethod
def _files(request: Mapping[str, Any]) -> dict[str, bytes]:
raw = request.get("files") or {request["path"]: request["content_base64"]}; pairs = raw.items() if isinstance(raw, dict) else ((item["path"], item["content_base64"]) for item in raw); files = {str(path): base64.b64decode(encoded, validate=True) for path, encoded in pairs}
if not files: raise ValueError("FILES_EMPTY")
return files
def commit_files(self, request: Mapping[str, Any]) -> dict[str, Any]:
replay = self._durable_completed_replay(request)
if replay is not None:
return replay
runtime = self.runtimes.get((str(request["task_id"]), int(request["generation"])))
if runtime is None: raise RuntimeError("LEASE_NOT_FOUND")
binding, _, executor = runtime
if request["target_branch"] != binding.request.target_branch or request["expected_branch_oid"] != binding.request.expected_branch_oid: raise RuntimeError("LEASE_BINDING_CONFLICT")
commit = ContinuationCommit(idempotency_key=str(request["idempotency_key"]), files=self._files(request), message=str(request["message"]))
executor.workspace.materialize_files(commit.files)
return executor.execute(binding, commit)
def _find_completed_replay_job(self, external_job_id: str) -> tuple[Path, dict[str, Any]] | None:
matches: list[tuple[Path, dict[str, Any]]] = []
roots = [self.config.state_root / "jobs"]
for controller in self.controllers.values():
roots.append(controller.store.root.parent)
seen: set[str] = set()
for jobs_root in roots:
if not jobs_root.is_dir():
continue
candidates = []
if (jobs_root / "state.sqlite3").is_file():
candidates.append(jobs_root)
candidates.extend(root for root in jobs_root.iterdir() if root.is_dir())
for root in candidates:
db = root / "state.sqlite3"
key = str(db.resolve()) if db.exists() else str(db)
if key in seen:
continue
seen.add(key)
if not db.is_file() or db.is_symlink():
continue
try:
connection = sqlite3.connect(f"file:{db}?mode=ro", uri=True)
connection.row_factory = sqlite3.Row
try:
rows = connection.execute("SELECT * FROM jobs").fetchall()
finally:
connection.close()
for raw in rows:
row = dict(raw)
durable = json.loads(row["request_json"])
binding = durable.get("binding", durable)
if str(binding.get("external_job_id") or "") == external_job_id:
matches.append((root, row))
except (OSError, ValueError, TypeError, sqlite3.Error):
continue
if not matches:
return None
if len(matches) != 1:
raise RuntimeError("EXTERNAL_JOB_MAPPING_AMBIGUOUS")
return matches[0]
def _durable_completed_replay_lease(self, request: Mapping[str, Any]) -> dict[str, Any] | None:
"""Admit an exact completed replay before ordinary lease validation."""
external_job_id = str(request.get("job_id") or "").strip()
if not external_job_id:
if request.get("source_lease_epoch") is not None:
raise RuntimeError("COMPLETED_REPLAY_EXTERNAL_JOB_ID_REQUIRED")
return None
match = self._find_completed_replay_job(external_job_id)
if match is None:
return None
root, row = match
if str(row["state"]) != JobState.COMPLETED.value:
return None
db = root / "state.sqlite3"
connection = sqlite3.connect(f"file:{db}?mode=ro", uri=True)
connection.row_factory = sqlite3.Row
try:
evidence_rows = connection.execute(
"SELECT * FROM completed_replay_reconciliations WHERE job_id=? ORDER BY created_at ASC",
(row["job_id"],),
).fetchall()
finally:
connection.close()
if not evidence_rows:
return None
evidence = dict(evidence_rows[0])
immutable = tuple(key for key in evidence if key != "created_at")
if any(any(item[key] != evidence[key] for key in immutable) for item in evidence_rows[1:]):
raise RuntimeError("COMPLETED_REPLAY_AMBIGUOUS")
durable = json.loads(row["request_json"])
binding = durable.get("binding", durable)
target_branch = str(binding.get("target_branch") or binding.get("branch") or "")
for field, incoming in (
("task_id", request.get("task_id")),
("repository", request.get("repository")),
("target_branch", request.get("target_branch")),
("generation", request.get("generation")),
("branch_mode", request.get("branch_mode")),
):
durable_value = target_branch if field == "target_branch" else binding.get(field)
if str(durable_value) != str(incoming):
raise RuntimeError("COMPLETED_REPLAY_BINDING_CONFLICT")
historical_head = str(evidence.get("historical_lease_head_oid") or "")
if str(request.get("base_oid") or "") != historical_head or str(request.get("expected_branch_oid") or "") != historical_head:
raise RuntimeError("COMPLETED_REPLAY_EXPECTED_HEAD_CONFLICT")
if request.get("source_lease_epoch") is not None and int(request["source_lease_epoch"]) != int(evidence["source_lease_epoch"]):
raise RuntimeError("COMPLETED_REPLAY_LEASE_EPOCH_CONFLICT")
receipt = json.loads(row["receipt_json"] or "null")
if not isinstance(receipt, Mapping) or receipt.get("commit_oid") != receipt.get("authority_oid"):
raise RuntimeError("COMPLETED_REPLAY_RECEIPT_INVALID")
authority_head = self.authority.read_ref("refs/heads/" + target_branch)
if authority_head != evidence.get("current_head_oid"):
raise RuntimeError("COMPLETED_REPLAY_AUTHORITY_CONFLICT")
if not (
evidence.get("commit_oid")
== evidence.get("authority_head_oid")
== evidence.get("current_head_oid")
== receipt.get("commit_oid")
and evidence.get("commit_oid") != historical_head
):
raise RuntimeError("COMPLETED_REPLAY_EVIDENCE_INCOHERENT")
return {
"schema": WriterLeaseRequestV2.schema,
"task_id": request["task_id"],
"job_id": external_job_id,
"repository": request["repository"],
"branch_mode": request["branch_mode"],
"base_oid": request["base_oid"],
"expected_branch_oid": request["expected_branch_oid"],
"target_branch": request["target_branch"],
"generation": int(request["generation"]),
"lease_seconds": int(request.get("lease_seconds") or 900),
"create_target_branch": False,
"worktree_mode": "detached_at_expected_head",
"cas_ref_update_required": True,
"lease_created": False,
"completed_replay": True,
"current_head_oid": evidence["current_head_oid"],
"completed_replay_reconciliation_id": evidence["reconciliation_id"],
}
def _durable_completed_replay(self, request: Mapping[str, Any]) -> dict[str, Any] | None:
"""Reconcile an exact completed replay before any worktree mutation."""
external_job_id = str(request.get("job_id") or "").strip()
if not external_job_id:
if request.get("source_lease_epoch") is not None:
raise RuntimeError("COMPLETED_REPLAY_EXTERNAL_JOB_ID_REQUIRED")
return None
matches: list[tuple[Path, dict[str, Any]]] = []
roots = [self.config.state_root / "jobs"]
for controller in self.controllers.values():
roots.append(controller.store.root.parent)
seen: set[str] = set()
for jobs_root in roots:
if not jobs_root.is_dir():
continue
candidates = []
if (jobs_root / "state.sqlite3").is_file():
candidates.append(jobs_root)
candidates.extend(root for root in jobs_root.iterdir() if root.is_dir())
for root in candidates:
db = root / "state.sqlite3"
key = str(db.resolve()) if db.exists() else str(db)
if key in seen:
continue
seen.add(key)
if not db.is_file() or db.is_symlink():
continue
try:
connection = sqlite3.connect(f"file:{db}?mode=ro", uri=True)
connection.row_factory = sqlite3.Row
try:
rows = connection.execute("SELECT * FROM jobs").fetchall()
finally:
connection.close()
for raw in rows:
row = dict(raw)
durable = json.loads(row["request_json"])
binding = durable.get("binding", durable)
if str(binding.get("external_job_id") or "") == external_job_id:
matches.append((root, row))
except (OSError, ValueError, TypeError, sqlite3.Error):
continue
if not matches:
return None
if len(matches) != 1:
raise RuntimeError("EXTERNAL_JOB_MAPPING_AMBIGUOUS")
root, row = matches[0]
if str(row["state"]) != JobState.COMPLETED.value:
return None
durable = json.loads(row["request_json"])
binding = durable.get("binding", durable)
source_lease_epoch = request.get("source_lease_epoch")
if source_lease_epoch is None:
db = root / "state.sqlite3"
connection = sqlite3.connect(f"file:{db}?mode=ro", uri=True)
connection.row_factory = sqlite3.Row
try:
evidence = connection.execute(
"SELECT source_lease_epoch FROM completed_replay_reconciliations WHERE job_id=? ORDER BY created_at ASC LIMIT 1",
(row["job_id"],),
).fetchone()
finally:
connection.close()
if evidence is not None:
source_lease_epoch = int(evidence["source_lease_epoch"])
files = self._files(request)
commit = ContinuationCommit(str(request["idempotency_key"]), files, str(request["message"]))
payload = commit.payload()
receipt = json.loads(row["receipt_json"] or "null")
if not isinstance(receipt, Mapping):
raise RuntimeError("COMPLETED_REPLAY_RECEIPT_INVALID")
incoming = {
"task_id": str(request.get("task_id") or ""),
"external_job_id": external_job_id,
"internal_job_id": str(row["job_id"]),
"repository": str(request.get("repository") or ""),
"target_branch": str(request.get("target_branch") or ""),
"generation": int(request.get("generation") or 0),
"branch_mode": str(request.get("branch_mode") or ""),
"payload_digest": hashlib.sha256(payload).hexdigest(),
"files_digest": hashlib.sha256(json.dumps({path: hashlib.sha256(content).hexdigest() for path, content in sorted(files.items())}, sort_keys=True, separators=(",", ":")).encode("utf-8")).hexdigest(),
"message_digest": hashlib.sha256(str(request["message"]).encode("utf-8")).hexdigest(),
"worker_principal": str(binding.get("worker_principal") or ""),
"worker_lineage": str(binding.get("worker_lineage") or ""),
"source_lease_epoch": int(source_lease_epoch or 0),
"durable_revision": int(row["revision"]),
"completion_identity": sha256_json({"job_id": row["job_id"], "revision": int(row["revision"]), "receipt": receipt}),
"reason": "COMPLETED_REPLAY_LEASE_EVIDENCE_RECONCILIATION",
"provenance": {"source": "governed-completed-replay", "authority_readback": True},
}
for field in ("task_id", "repository", "target_branch", "generation", "branch_mode", "payload_digest", "files_digest", "message_digest", "worker_principal", "worker_lineage"):
durable_value = binding.get(field)
if field == "target_branch":
durable_value = binding.get("target_branch") or binding.get("branch")
elif field in {"worker_principal", "worker_lineage"}:
durable_value = durable_value or ""
if durable_value not in (None, "") and str(durable_value) != str(incoming[field]):
raise RuntimeError("COMPLETED_REPLAY_BINDING_CONFLICT")
db = root / "state.sqlite3"
connection = sqlite3.connect(f"file:{db}?mode=ro", uri=True)
connection.row_factory = sqlite3.Row
try:
columns = {str(item[1]) for item in connection.execute("PRAGMA table_info(branch_leases)").fetchall()}
if "epoch" in columns:
lease = connection.execute("SELECT lease_id, state, current_head_oid, generation FROM branch_leases WHERE job_id=? AND epoch=? ORDER BY rowid DESC LIMIT 1", (row["job_id"], incoming["source_lease_epoch"])).fetchone()
else:
if incoming["source_lease_epoch"] != 1:
raise RuntimeError("COMPLETED_REPLAY_LEASE_EPOCH_UNAVAILABLE")
lease = connection.execute("SELECT lease_id, state, current_head_oid, generation FROM branch_leases WHERE job_id=? ORDER BY rowid DESC LIMIT 1", (row["job_id"],)).fetchone()
if lease is None:
raise RuntimeError("COMPLETED_REPLAY_LEASE_MISSING")
historical_lease_head = str(lease["current_head_oid"])
event_tables = {str(item[0]) for item in connection.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='branch_lease_events'").fetchall()}
lease_state = str(lease["state"])
lease_generation = int(lease["generation"])
if "branch_lease_events" in event_tables:
event = connection.execute("SELECT state, current_head_oid, generation FROM branch_lease_events WHERE lease_id=? ORDER BY rowid DESC LIMIT 1", (lease["lease_id"],)).fetchone()
if event is not None:
lease_state = str(event["state"])
historical_lease_head = str(event["current_head_oid"])
lease_generation = int(event["generation"])
if lease_state not in {"ACTIVE", "USED", "EXPIRED"}:
raise RuntimeError("COMPLETED_REPLAY_LEASE_STATE_INVALID")
if lease_generation != incoming["generation"]:
raise RuntimeError("COMPLETED_REPLAY_LEASE_GENERATION_CONFLICT")
finally:
connection.close()
incoming["historical_lease_head_oid"] = historical_lease_head
authority_head = self.authority.read_ref("refs/heads/" + incoming["target_branch"])
if authority_head is None:
raise RuntimeError("COMPLETED_REPLAY_AUTHORITY_MISSING")
if receipt.get("commit_oid") != receipt.get("authority_oid"):
raise RuntimeError("COMPLETED_REPLAY_RECEIPT_INVALID")
incoming["commit_oid"] = str(receipt["commit_oid"])
incoming["authority_head_oid"] = authority_head
connection = sqlite3.connect(f"file:{db}?mode=ro", uri=True)
connection.row_factory = sqlite3.Row
try:
evidence_rows = connection.execute("SELECT * FROM completed_replay_reconciliations WHERE job_id=? ORDER BY created_at ASC", (row["job_id"],)).fetchall()
finally:
connection.close()
if evidence_rows:
evidence = dict(evidence_rows[0])
immutable = tuple(key for key in evidence if key != "created_at")
if any(any(item[key] != evidence[key] for key in immutable) for item in evidence_rows[1:]):
raise RuntimeError("COMPLETED_REPLAY_AMBIGUOUS")
normalized = JobStore._completed_replay_identity(incoming)
columns = {
"task_id": "task_id",
"external_job_id": "external_job_id",
"internal_job_id": "internal_job_id",
"repository": "repository",
"target_branch": "target_branch",
"generation": "generation",
"branch_mode": "branch_mode",
"payload_digest": "payload_digest",
"files_digest": "files_digest",
"message_digest": "message_digest",
"commit_oid": "commit_oid",
"authority_head_oid": "authority_head_oid",
"historical_lease_head_oid": "historical_lease_head_oid",
"worker_principal": "worker_principal",
"worker_lineage": "worker_lineage",
"source_lease_epoch": "source_lease_epoch",
"durable_revision": "durable_revision",
"completion_identity": "completion_identity",
"reason": "reason",
}
if any(str(evidence[column]) != str(normalized[field]) for field, column in columns.items()):
raise RuntimeError("COMPLETED_REPLAY_BINDING_CONFLICT")
if evidence["provenance_json"] != json.dumps(normalized["provenance"], sort_keys=True, separators=(",", ":")):
raise RuntimeError("COMPLETED_REPLAY_PROVENANCE_CONFLICT")
if evidence["identity_digest"] != sha256_json(normalized):
raise RuntimeError("COMPLETED_REPLAY_IDENTITY_CONFLICT")
if not (evidence["commit_oid"] == evidence["authority_head_oid"] == evidence["current_head_oid"] and evidence["commit_oid"] != evidence["historical_lease_head_oid"]):
raise RuntimeError("COMPLETED_REPLAY_EVIDENCE_INCOHERENT")
if self.authority.read_ref("refs/heads/" + incoming["target_branch"]) != evidence["current_head_oid"]:
raise RuntimeError("COMPLETED_REPLAY_AUTHORITY_CONFLICT")
result = {"job_id": external_job_id, "task_id": incoming["task_id"], "repository": incoming["repository"], "branch_mode": incoming["branch_mode"], "base_oid": request.get("base_oid"), "expected_branch_oid": request.get("expected_branch_oid"), "target_branch": incoming["target_branch"], "generation": incoming["generation"], "commit_oid": evidence["commit_oid"], "idempotency_key": row["idempotency_key"], "state": JobState.COMPLETED.value, "side_effect_state": row["side_effect_state"], "http_status": 200, "revision": int(row["revision"]), "receipt": {**receipt, "commit_oid": evidence["commit_oid"], "authority_oid": evidence["authority_head_oid"], "current_head_oid": evidence["current_head_oid"], "external_job_id": evidence["external_job_id"], "completed_replay_reconciliation_id": evidence["reconciliation_id"]}, "current_head_oid": evidence["current_head_oid"], "completed_replay_reconciliation_id": evidence["reconciliation_id"]}
return result
controller = GitOpsJobController(JobStore.for_existing_append(root))
reconciliation = controller.store.append_completed_replay_reconciliation(str(row["job_id"]), incoming, authority_head_oid=authority_head)
evidence = {**receipt, "commit_oid": reconciliation["commit_oid"], "authority_oid": reconciliation["authority_head_oid"], "current_head_oid": reconciliation["current_head_oid"], "completed_replay_reconciliation_id": reconciliation["reconciliation_id"], "external_job_id": external_job_id}
return {"job_id": external_job_id, "task_id": incoming["task_id"], "repository": incoming["repository"], "branch_mode": incoming["branch_mode"], "base_oid": request.get("base_oid"), "expected_branch_oid": request.get("expected_branch_oid"), "target_branch": incoming["target_branch"], "generation": incoming["generation"], "commit_oid": reconciliation["commit_oid"], "idempotency_key": row["idempotency_key"], "state": JobState.COMPLETED.value, "side_effect_state": row["side_effect_state"], "http_status": 200, "revision": int(row["revision"]), "receipt": evidence, "current_head_oid": reconciliation["current_head_oid"], "completed_replay_reconciliation_id": reconciliation["reconciliation_id"]}
def _controller_for_job(self, job_id: str) -> GitOpsJobController:
for controller in self.controllers.values():
try:
controller.store.read(job_id)
return controller
except Exception:
continue
jobs_root = self.config.state_root / "jobs"
if jobs_root.is_dir():
for root in jobs_root.iterdir():
if not root.is_dir():
continue
db = root / "state.sqlite3"
if not db.is_file() or db.is_symlink():
continue
try:
connection = sqlite3.connect(f"file:{db}?mode=ro", uri=True)
connection.row_factory = sqlite3.Row
try:
rows = connection.execute("SELECT job_id, request_json FROM jobs").fetchall()
finally:
connection.close()
matched = False
for row in rows:
if str(row["job_id"]) == job_id:
matched = True
break
durable = json.loads(row["request_json"])
binding = durable.get("binding", durable)
if str(binding.get("external_job_id") or "") == job_id:
matched = True
break
if not matched:
continue
controller = GitOpsJobController(JobStore(root, payload_root=root / "payloads"))
self.controllers[str(job_id)] = controller
return controller
except (OSError, ValueError, TypeError, sqlite3.Error):
continue
raise RuntimeError("JOB_NOT_FOUND")
@staticmethod
def _valid_oid(value: object) -> bool:
return isinstance(value, str) and re.fullmatch(r"[0-9a-f]{40}", value) is not None
def _reconstruct_legacy_binding(
self,
row: Mapping[str, Any],
controller: GitOpsJobController,
) -> dict[str, Any]:
raw = json.loads(row["request_json"])
binding = raw.get("binding", raw)
if not isinstance(binding, dict):
raise RuntimeError("DURABLE_BINDING_INVALID")
task_id = str(binding.get("task_id") or binding.get("campaign_id") or "").strip()
repository = str(binding.get("repository") or "").strip()
operation = str(binding.get("operation") or row.get("operation") or "").strip()
branch = str(binding.get("target_branch") or binding.get("branch") or row.get("branch") or "").strip()
expected = str(binding.get("expected_branch_oid") or row.get("expected_branch_oid") or "").strip()
base = binding.get("base_oid") or binding.get("lineage_anchor_oid")
if not base:
with controller.store._connect() as connection:
lease_row = connection.execute(
"SELECT lineage_anchor_oid FROM branch_leases WHERE job_id=? ORDER BY revision DESC LIMIT 1",
(str(row["job_id"]),),
).fetchone()
if lease_row is not None:
base = lease_row["lineage_anchor_oid"]
base = str(base or "").strip()
branch_mode = str(
binding.get("branch_mode")
or ("CREATE_NEW" if operation.endswith("_CREATE_NEW") else "CONTINUE_EXISTING")
).strip()
try:
generation = int(binding.get("generation") or 0)
except (TypeError, ValueError) as exc:
raise RuntimeError("DURABLE_BINDING_GENERATION_INVALID") from exc
if (
not task_id
or repository != self.config.repository
or not branch.startswith("agent/")
or branch in {"main", "master"}
or branch_mode not in {"CREATE_NEW", "CONTINUE_EXISTING"}
or generation < 1
or not self._valid_oid(base)
or not self._valid_oid(expected)
):
raise RuntimeError("DURABLE_BINDING_INVALID")
current = self.authority.read_ref("refs/heads/" + branch)
if (
current is None
or not self.authority.commit_exists(base)
or not self.authority.commit_exists(expected)
or not self.authority.ancestry_check(base, current)
or not self.authority.ancestry_check(expected, current)
):
raise RuntimeError("DURABLE_BINDING_LINEAGE_INVALID")
return {
"task_id": task_id,
"repository": repository,
"branch_mode": branch_mode,
"base_oid": base,
"expected_branch_oid": expected,
"target_branch": branch,
"generation": generation,
"lease_seconds": int(binding.get("lease_seconds") or 900),
}
def _executor_from_row(
self,
row: Mapping[str, Any],
controller: GitOpsJobController,
) -> ContinuationExecutor:
request = self._reconstruct_legacy_binding(row, controller)
lease = WriterLeaseRequestV2(**request)
worktree_path = self.config.state_root / "worktrees" / _safe(lease.task_id)
if not worktree_path.exists():
current = self.authority.read_ref("refs/heads/" + lease.target_branch)
if current is None:
raise RuntimeError("DURABLE_WORKTREE_BRANCH_MISSING")
self.authority.create_detached_worktree(
current,
_safe(lease.task_id),
self.config.state_root / "worktrees",
)
workspace = DetachedGitWorkspace(worktree_path)
credential = ServerSideCredentialProjection(self.config.ssh_key_path, self.config.token_path)
remote = GiteaContinuationRemote(
workspace=worktree_path,
credential=credential,
remote_name=self.config.remote_name,
repository=lease.repository,
api_base=self.config.api_base,
)
return ContinuationExecutor(
controller,
AuthorityGitAdapter(self.authority),
workspace,
remote,
credential,
)
def job(self, request: Mapping[str, Any], action: str) -> dict[str, Any]:
job_id = str(request["job_id"])
controller = self._controller_for_job(job_id)
row = controller.store.read(job_id)
if action == "get":
result = controller.store._receipt(row, duplicate=True).as_dict()
evidence_reader = getattr(controller.store, "completed_replay_evidence", None)
evidence = evidence_reader(job_id) if callable(evidence_reader) else None
if evidence is not None:
branch = str(json.loads(row["request_json"]).get("binding", {}).get("branch") or row["branch"])
authority_head = self.authority.read_ref("refs/heads/" + branch)
if authority_head != evidence["authority_head_oid"] or evidence["current_head_oid"] != authority_head:
raise RuntimeError("COMPLETED_REPLAY_AUTHORITY_CONFLICT")
result["job_id"] = evidence["external_job_id"]
result["current_head_oid"] = evidence["current_head_oid"]
result["completed_replay_reconciliation_id"] = evidence["reconciliation_id"]
result["receipt"] = {
**(result.get("receipt") or {}),
"commit_oid": evidence["commit_oid"],
"authority_oid": evidence["authority_head_oid"],
"current_head_oid": evidence["current_head_oid"],
"external_job_id": evidence["external_job_id"],
"completed_replay_reconciliation_id": evidence["reconciliation_id"],
}
return result
if action == "resume":
return controller.resume(job_id).as_dict()
if JobState(row["state"]) not in _RECOVERABLE:
return controller.store._receipt(row, duplicate=True).as_dict()
return self._executor_from_row(row, controller).reconcile(job_id)
def _snapshot_task(self) -> dict[str, Any]:
candidates: list[dict[str, Any]] = []
for value in self.bindings.values():
task_id = str(value.get("task_id") or "").strip()
branch = str(value.get("target_branch") or "").strip()
if not task_id or not branch.startswith("agent/"):
continue
authority = self.authority.read_ref("refs/heads/" + branch)
try:
generation = int(value.get("generation") or 0)
except (TypeError, ValueError):
generation = 0
reconciliation_state = "OBSERVATION_REQUIRED"
projection: dict[str, Any] = {}
db = self.config.state_root / "jobs" / _safe(task_id) / "state.sqlite3"
if db.is_file() and not db.is_symlink():
connection = sqlite3.connect(f"file:{db}?mode=ro", uri=True)
connection.row_factory = sqlite3.Row
try:
lease = connection.execute("SELECT MAX(generation) FROM branch_leases WHERE branch=?", (branch,)).fetchone()
generation = max(generation, int((lease or (0,))[0] or 0))
latest = connection.execute("SELECT job_id, state, side_effect_state FROM jobs WHERE branch=? ORDER BY updated_at DESC LIMIT 1", (branch,)).fetchone()
if latest is not None:
reconciliation_state = "COMPLETED" if latest["state"] == "COMPLETED" else str(latest["side_effect_state"] or latest["state"])
evidence_rows = connection.execute("SELECT * FROM completed_replay_reconciliations WHERE job_id=? ORDER BY created_at ASC", (latest["job_id"],)).fetchall()
if evidence_rows:
evidence = dict(evidence_rows[0])
immutable = tuple(key for key in evidence if key != "created_at")
if any(any(item[key] != evidence[key] for key in immutable) for item in evidence_rows[1:]):
raise RuntimeError("COMPLETED_REPLAY_AMBIGUOUS")
if not (evidence["commit_oid"] == evidence["authority_head_oid"] == evidence["current_head_oid"] and evidence["commit_oid"] != evidence["historical_lease_head_oid"]):
raise RuntimeError("COMPLETED_REPLAY_EVIDENCE_INCOHERENT")
current = self.authority.read_ref("refs/heads/" + branch)
if current != evidence["authority_head_oid"] or current != evidence["current_head_oid"]:
raise RuntimeError("COMPLETED_REPLAY_AUTHORITY_CONFLICT")
reconciliation_state = "COMPLETED_REPLAY_RECONCILED"
projection = {"external_job_id": evidence["external_job_id"], "commit_oid": evidence["commit_oid"], "current_head_oid": evidence["current_head_oid"], "completed_replay_reconciliation_id": evidence["reconciliation_id"], "receipt_authority_oid": evidence["authority_head_oid"]}
finally:
connection.close()
candidate = {"task_id": task_id, "branch": branch, "authority_branch_oid": authority, "gitea_git_oid": None, "gitea_api_oid": None, "commit_visible": None, "generation": generation, "reconciliation_state": reconciliation_state}
candidate.update(projection)
candidates.append(candidate)
if not candidates:
return {}
return max(candidates, key=lambda item: (int(item["generation"] or 0), bool(item["authority_branch_oid"])))
def snapshot(self) -> dict[str, Any]:
task = self._snapshot_task()
return build_control_plane_snapshot(target_version=self.config.target_version, runtime=self.runtime_status(), repository={"identity": self.config.repository, "authority_git_dir": str(self.config.authoritative_git_dir), "authority_main_oid": self.authority.read_ref("refs/heads/main")}, production={"baseline_version": "V2R1C7", "target_oid": "c7adece1cc8beffe7c718d53c775898b45d6c7f8", "deployment_state": "ACTIVE"}, task=task, capabilities={"legacy_writes_enabled": False, "governed_writes_enabled": True, "force_push": False, "main_write": False, "tag_write": False, "baseline_write": False, "production_write": False}, credential_broker={"status": "SERVER_SIDE_ONLY", "provider": "FileCredentialProvider"})
def handlers(self) -> dict[str, Any]:
return {
"projectOnlineAgentExecution": self.project_online_agent_execution,
"createGovernedTask": self.create_task,
"acquireWriterLease": self.acquire_lease,
"commitFileGoverned": lambda request: self.commit_files({**request, "files": {request["path"]: request["content_base64"]}}),
"commitFilesGoverned": self.commit_files,
"getGitOpsJob": lambda request: self.job(request, "get"),
"resumeGitOpsJob": lambda request: self.job(request, "resume"),
"reconcileGitOpsJob": lambda request: self.job(request, "reconcile"),
"getControlPlaneSnapshot": lambda request: self.snapshot(),
"createTagApprovalRequest": self.release.approval_request,
"requestOrCreateTag": self.release.publish_tag,
"getTagPublicationStatus": self.release.tag_status,
"buildProductionArtifact": self.release.build_artifact,
"executeApprovedProductionRelease": self.release.execute_request,
"getReleaseConsistency": self.release.consistency,
"runReleaseAcceptance": self.release.acceptance,
}
def build_governed_handlers(runtime_config: RuntimeConfig | Mapping[str, Any]) -> Mapping[str, Any]:
if isinstance(runtime_config, Mapping): runtime_config = RuntimeConfig(**runtime_config)
return GovernedRuntime(runtime_config).handlers()
class _HTTPError(RuntimeError): pass
class BridgeHandler(BaseHTTPRequestHandler):
adapter: BridgeV2Adapter | None = None
def log_message(self, *_args: Any) -> None: return
def _json(self, value: Any, status: int = 200) -> None:
raw = json.dumps(value, sort_keys=True, separators=(",", ":")).encode(); self.send_response(status); self.send_header("Content-Type", "application/json"); self.send_header("Content-Length", str(len(raw))); self.end_headers(); self.wfile.write(raw)
def _body(self) -> dict[str, Any]:
try: length = int(self.headers.get("Content-Length", "-1"))
except ValueError as exc: raise _HTTPError("CONTENT_LENGTH_INVALID") from exc
if length < 0 or length > MAX_BODY: raise _HTTPError("REQUEST_TOO_LARGE")
value = json.loads(self.rfile.read(length).decode("utf-8"))
if not isinstance(value, dict): raise _HTTPError("JSON_OBJECT_REQUIRED")
return value
def _allowed(self, operation: str, body: Mapping[str, Any]) -> None:
allowed = set(OPERATION_REQUIRED_FIELDS[operation]) | {"schema", "lease_seconds", "create_target_branch", "worktree_mode", "cas_ref_update_required", "expected_branch_oid", "generation", "branch_mode", "source_lease_epoch"}
if operation == "commitFilesGoverned": allowed.add("files")
allowed.update({"owner_approval", "execute", "job_id", "historical_lease_head_oid"})
if set(body) - allowed: raise _HTTPError("UNKNOWN_FIELDS")
def do_GET(self) -> None:
if self.adapter is None: self._json({"error": "RUNTIME_UNAVAILABLE"}, 503); return
if self.path == "/v2/runtime": self._json(self.adapter.getRuntimeStatus()); return
if self.path == "/v2/capabilities": self._json(self.adapter.getGovernedWriteCapabilities()); return
if self.path == "/v2/control-plane-snapshot": self._json(self.adapter.getControlPlaneSnapshot({})); return
self._json({"error": "NOT_FOUND"}, 404)
def do_POST(self) -> None:
operation = ROUTES.get(self.path)
if operation is None or operation in ("getRuntimeStatus", "getGovernedWriteCapabilities") or self.adapter is None: self._json({"error": "NOT_FOUND"}, 404); return
try:
body = self._body(); self._allowed(operation, body); self._json(getattr(self.adapter, operation)(body))
except _HTTPError as exc: self._json({"error": str(exc)}, 413 if str(exc) == "REQUEST_TOO_LARGE" else 400)
except (BootstrapError, ValueError, BridgeV2Error) as exc: self._json({"error": str(exc)}, 409 if "CONFLICT" in str(exc) else 400)
except Exception as exc: self._json({"error": str(exc) or type(exc).__name__}, 409 if any(token in str(exc) for token in ("CONFLICT", "EXPECTATION", "MONOTONIC")) else 503)
def serve(adapter: BridgeV2Adapter, host: str = "127.0.0.1", port: int = 18892) -> None:
BridgeHandler.adapter = adapter; ThreadingHTTPServer((host, port), BridgeHandler).serve_forever()
def main() -> None:
state_root = Path("/var/lib/aios-gitops/bridge-v2-staging")
token_path = Path(os.environ.get("AIOS_GITEA_TOKEN_FILE", "/run/credentials/aios-chatgpt-bridge-v2-staging.service/gitea_token"))
ssh_key_path = Path(os.environ.get("AIOS_GITEA_SSH_KEY_PATH", "/home/aiman/.ssh/id_ed25519_codex_tmp"))
os.environ["CREDENTIAL_FILE"] = str(token_path)
os.environ.setdefault("GITEA_MACHINE_ACTOR", "aios-autonomy-agent")
config = RuntimeConfig(
os.environ.get("AIOS_CONTROL_PLANE_TARGET_OID", ""),
Path("/data/aios-gitops/repo.git"),
state_root,
state_root / "jobs",
token_path,
ssh_key_path,
remote_name=os.environ.get("AIOS_GITEA_REMOTE", "gitea-http"),
api_base=validate_gitea_api_url(os.environ.get("AIOS_GITEA_API_BASE", "")),
target_version=os.environ.get("AIOS_CONTROL_PLANE_TARGET_VERSION", "V2R2C1"),
baseline_path=Path(os.environ.get(
"AIOS_GITOPS_BASELINE_AUTHORITY",
"/var/lib/aios-gitops-runtime-anchor/baseline/current.json",
)),
)
runtime = GovernedRuntime(config)
serve(
BridgeV2Adapter(runtime_status=runtime.runtime_status(), handlers=runtime.handlers()),
port=int(os.environ.get("AIOS_BRIDGE_V2_PORT", "18892")),
)
if __name__ == "__main__": main()
@@ -0,0 +1,615 @@
"""Governed continuation executor for exact existing task branches.
The executor composes the existing continuation lease and durable Job store.
All Git and Gitea side effects live behind injected narrow interfaces so the
state machine can prove Authority, Git-network, API-branch, and commit-object
agreement before it reports completion.
"""
from __future__ import annotations
import base64
from dataclasses import asdict, dataclass
from datetime import datetime, timezone
import hashlib
import json
import os
from pathlib import Path, PurePosixPath
import re
import subprocess
from typing import Callable, Mapping, Protocol
from urllib.error import HTTPError, URLError
from urllib.parse import quote, urlsplit
from urllib.request import Request, urlopen
from aios_authority_git_executor import AuthorityGitExecutor
from aios_continuation_lease import WriterLeaseRequestV2
from aios_gitops_job_controller import GitOpsJobController, JobState
from aios_gitops_reconciliation_core import ReconciliationAction, RemoteObservation, decide_reconciliation
from authorized_task_writer_provisioning import validate_writer_lease_request_v2
OID_RE = re.compile(r"^[0-9a-f]{40}$")
SECRET_PATTERNS = (
re.compile(rb"gh[pousr]_[A-Za-z0-9]{20,}"),
re.compile(rb"(?i)(?:token|password|secret|api[_-]?key)\s*[:=]\s*[\"'][^\"'\s]{12,}[\"']"),
re.compile(rb"-----BEGIN " + rb"(?:RSA |EC |OPENSSH )?PRIVATE KEY-----"),
)
class ContinuationExecutionError(RuntimeError):
"""Stable fail-closed continuation execution error."""
class DetachedWorkspace(Protocol):
def is_detached(self) -> bool: ...
def head_oid(self) -> str: ...
def commit_files(self, files: Mapping[str, bytes], message: str) -> str: ...
class AuthorityPort(Protocol):
def branch_exists(self, branch: str) -> bool: ...
def branch_head(self, branch: str) -> str: ...
def is_ancestor(self, older: str, newer: str) -> bool: ...
def compare_and_swap_branch(self, branch: str, expected: str, new: str) -> str: ...
class RemotePort(Protocol):
def repository_identity(self) -> str: ...
def push_exact(self, branch: str, expected: str, new: str, *, allow_absent: bool = False) -> None: ...
def read_git_ref(self, branch: str) -> str | None: ...
def read_api_branch(self, branch: str) -> str | None: ...
def commit_visible(self, oid: str) -> bool: ...
class CredentialProjection(Protocol):
def project(self) -> None: ...
def revoke(self) -> None: ...
def _run(argv: list[str], *, cwd: Path | None = None, environment: Mapping[str, str] | None = None) -> str:
result = subprocess.run(
argv,
cwd=cwd,
env=None if environment is None else dict(environment),
text=True,
capture_output=True,
check=False,
)
if result.returncode != 0:
raise ContinuationExecutionError("SUBPROCESS_FAILED")
return result.stdout.strip()
@dataclass(slots=True)
class AuthorityGitAdapter:
"""Branch-name adapter over the existing narrow AuthorityGitExecutor."""
executor: AuthorityGitExecutor
@staticmethod
def _ref(branch: str) -> str:
return f"refs/heads/{branch}"
def branch_exists(self, branch: str) -> bool:
return self.executor.read_ref(self._ref(branch)) is not None
def branch_head(self, branch: str) -> str:
value = self.executor.read_ref(self._ref(branch))
if value is None:
raise ContinuationExecutionError("EXISTING_BRANCH_MISSING")
return value
def is_ancestor(self, older: str, newer: str) -> bool:
return self.executor.ancestry_check(older, newer)
def compare_and_swap_branch(self, branch: str, expected: str, new: str) -> str:
return self.executor.advance_task_branch_CAS(self._ref(branch), expected, new)
@dataclass(slots=True)
class DetachedGitWorkspace:
path: Path
def __post_init__(self) -> None:
if not self.path.is_absolute() or self.path.is_symlink() or not self.path.is_dir():
raise ContinuationExecutionError("WORKTREE_PATH_INVALID")
top = Path(_run(["git", "rev-parse", "--show-toplevel"], cwd=self.path)).resolve()
if top != self.path.resolve():
raise ContinuationExecutionError("WORKTREE_PATH_INVALID")
def is_detached(self) -> bool:
result = subprocess.run(
["git", "symbolic-ref", "-q", "HEAD"],
cwd=self.path,
capture_output=True,
check=False,
)
return result.returncode == 1
def head_oid(self) -> str:
return _run(["git", "rev-parse", "HEAD"], cwd=self.path)
def _dirty_paths(self) -> set[str]:
command = subprocess.run(
["git", "status", "--porcelain=v1", "--untracked-files=all"],
cwd=self.path,
text=True,
capture_output=True,
check=False,
)
if command.returncode != 0:
raise ContinuationExecutionError("SUBPROCESS_FAILED")
output = command.stdout
result: set[str] = set()
for line in output.splitlines():
path = line[3:]
if " -> " in path:
path = path.split(" -> ", 1)[1]
result.add(path)
return result
def materialize_files(self, files: Mapping[str, bytes]) -> None:
dirty = self._dirty_paths()
expected_paths = set(files)
if dirty and dirty != expected_paths:
raise ContinuationExecutionError("WORKTREE_DIRTY_SCOPE_MISMATCH")
for relative, content in files.items():
target = (self.path / relative).resolve()
try:
target.relative_to(self.path.resolve())
except ValueError as exc:
raise ContinuationExecutionError("COMMIT_PATH_REJECTED") from exc
if target.is_symlink():
raise ContinuationExecutionError("COMMIT_PATH_REJECTED")
target.parent.mkdir(parents=True, exist_ok=True)
target.write_bytes(content)
def commit_files(self, files: Mapping[str, bytes], message: str) -> str:
expected_paths = set(files)
if self._dirty_paths() != expected_paths:
raise ContinuationExecutionError("WORKTREE_DIRTY_SCOPE_MISMATCH")
for relative, content in files.items():
target = (self.path / relative).resolve()
try:
target.relative_to(self.path.resolve())
except ValueError as exc:
raise ContinuationExecutionError("COMMIT_PATH_REJECTED") from exc
if not target.is_file() or target.is_symlink() or target.read_bytes() != content:
raise ContinuationExecutionError("AUTHORING_PAYLOAD_MISMATCH")
_run(["git", "add", "--", *sorted(expected_paths)], cwd=self.path)
staged = set(_run(["git", "diff", "--cached", "--name-only"], cwd=self.path).splitlines())
if staged != expected_paths:
raise ContinuationExecutionError("STAGED_SCOPE_MISMATCH")
environment = os.environ.copy()
environment.update(
{
"GIT_AUTHOR_NAME": "AIOS Governed Continuation",
"GIT_AUTHOR_EMAIL": "aios-governed-continuation@localhost",
"GIT_COMMITTER_NAME": "AIOS Governed Continuation",
"GIT_COMMITTER_EMAIL": "aios-governed-continuation@localhost",
}
)
_run(["git", "commit", "--no-gpg-sign", "-m", message, "--", *sorted(expected_paths)], cwd=self.path, environment=environment)
if not self.is_detached():
raise ContinuationExecutionError("WORKTREE_NOT_DETACHED")
return self.head_oid()
@dataclass(slots=True)
class ServerSideCredentialProjection:
ssh_key_path: Path
api_token_path: Path
active: bool = False
def project(self) -> None:
for path in (self.ssh_key_path, self.api_token_path):
if path.is_symlink() or not path.is_file() or path.stat().st_size < 32:
raise ContinuationExecutionError("CREDENTIAL_PROJECTION_MISSING")
self.active = True
def revoke(self) -> None:
self.active = False
@dataclass(slots=True)
class GiteaContinuationRemote:
workspace: Path
credential: ServerSideCredentialProjection
remote_name: str = "gitea"
repository: str = "aios/aios"
api_base: str = "https://g.cnnetmkt.top/git/api/v1"
expected_host: str = "g.cnnetmkt.top"
@staticmethod
def validate_remote_url(value: str, expected_host: str = "g.cnnetmkt.top") -> None:
if value.startswith(("/", "file:")) or value.endswith("/data/aios-gitops/repo.git"):
raise ContinuationExecutionError("GITEA_REMOTE_IDENTITY_INVALID")
if "://" in value:
parsed = urlsplit(value)
host, path = parsed.hostname, parsed.path
else:
location = value.split("@", 1)[-1]
host, separator, path = location.partition(":")
if not separator:
raise ContinuationExecutionError("GITEA_REMOTE_IDENTITY_INVALID")
path = "/" + path
if host != expected_host or path.rstrip("/") not in {"/aios/aios.git", "/git/aios/aios.git"}:
raise ContinuationExecutionError("GITEA_REMOTE_IDENTITY_INVALID")
def repository_identity(self) -> str:
url = _run(["git", "remote", "get-url", self.remote_name], cwd=self.workspace)
self.validate_remote_url(url, self.expected_host)
if self.repository != "aios/aios":
raise ContinuationExecutionError("REMOTE_REPOSITORY_IDENTITY_MISMATCH")
return self.repository
def _require_projected(self) -> None:
if not self.credential.active:
raise ContinuationExecutionError("CREDENTIAL_PROJECTION_MISSING")
def read_git_ref(self, branch: str) -> str | None:
self._require_projected()
output = _run(["git", "ls-remote", self.remote_name, f"refs/heads/{branch}"], cwd=self.workspace)
if not output:
return None
oid, ref = output.split(maxsplit=1)
if ref != f"refs/heads/{branch}" or not OID_RE.fullmatch(oid):
raise ContinuationExecutionError("GITEA_GIT_READBACK_INVALID")
return oid
def push_exact(self, branch: str, expected: str, new: str, *, allow_absent: bool = False) -> None:
self._require_projected()
self.repository_identity()
current = self.read_git_ref(branch)
if current != expected and not (allow_absent and current is None):
raise ContinuationExecutionError("GITEA_BRANCH_EXPECTATION_MISMATCH")
_run(["git", "push", self.remote_name, f"{new}:refs/heads/{branch}"], cwd=self.workspace)
def _api(self, path: str) -> object:
self._require_projected()
token = self.credential.api_token_path.read_text(encoding="utf-8").strip()
request = Request(
self.api_base.rstrip("/") + path,
headers={"Accept": "application/json", "Authorization": f"token {token}"},
)
try:
with urlopen(request, timeout=20) as response:
return json.loads(response.read().decode("utf-8"))
except HTTPError as exc:
if exc.code == 404:
return None
raise ContinuationExecutionError(f"GITEA_API_HTTP_{exc.code}") from exc
except (URLError, TimeoutError, OSError, json.JSONDecodeError) as exc:
raise ContinuationExecutionError("GITEA_API_UNAVAILABLE") from exc
def read_api_branch(self, branch: str) -> str | None:
owner, name = self.repository.split("/", 1)
page = 1
while page <= 100:
value = self._api(f"/repos/{quote(owner)}/{quote(name)}/branches?page={page}&limit=50")
if value is None:
return None
if not isinstance(value, list):
raise ContinuationExecutionError("GITEA_API_BRANCH_COLLECTION_INVALID")
for item in value:
if not isinstance(item, dict) or item.get("name") != branch:
continue
oid = item.get("commit", {}).get("id")
if not isinstance(oid, str) or not OID_RE.fullmatch(oid):
raise ContinuationExecutionError("GITEA_API_BRANCH_INVALID")
return oid
if len(value) < 50:
return None
page += 1
raise ContinuationExecutionError("GITEA_API_BRANCH_PAGINATION_LIMIT")
def commit_visible(self, oid: str) -> bool:
owner, name = self.repository.split("/", 1)
value = self._api(f"/repos/{quote(owner)}/{quote(name)}/git/commits/{oid}")
if not isinstance(value, dict):
return False
observed = value.get("sha") or value.get("id")
return observed == oid
@dataclass(frozen=True, slots=True)
class ContinuationPermissions:
create_target_branch: bool = False
force_push: bool = False
tag_write: bool = False
baseline_write: bool = False
production_write: bool = False
main_write: bool = False
def validate(self) -> None:
if any(asdict(self).values()):
raise ContinuationExecutionError("MUTATION_PERMISSION_DENIED")
@dataclass(frozen=True, slots=True)
class ContinuationRuntimeBinding:
request: WriterLeaseRequestV2
workspace_path: Path
lease_not_before: datetime
lease_expires_at: datetime
original_receipt_valid: bool
original_grant_consumed: bool
permissions: ContinuationPermissions
remote_branch_create_allowed: bool = False
external_job_id: str | None = None
worker_principal: str = "aios-autonomy-agent"
worker_lineage: str = "governed-continuation"
@dataclass(frozen=True, slots=True)
class ContinuationCommit:
idempotency_key: str
files: Mapping[str, bytes]
message: str
def payload(self) -> bytes:
if not self.idempotency_key or not self.message.strip() or not self.files:
raise ContinuationExecutionError("COMMIT_REQUEST_INVALID")
normalized: dict[str, str] = {}
for path, content in self.files.items():
pure = PurePosixPath(path)
if (
not isinstance(path, str)
or pure.is_absolute()
or ".." in pure.parts
or path.startswith((".git/", "baseline/", "production/"))
or not isinstance(content, bytes)
):
raise ContinuationExecutionError("COMMIT_PATH_REJECTED")
if any(pattern.search(content) for pattern in SECRET_PATTERNS):
raise ContinuationExecutionError("SECRET_LIKE_PAYLOAD_REJECTED")
normalized[path] = base64.b64encode(content).decode("ascii")
return json.dumps(
{"files_base64": normalized, "message": self.message},
sort_keys=True,
separators=(",", ":"),
).encode("utf-8")
class ContinuationExecutor:
def __init__(
self,
controller: GitOpsJobController,
authority: AuthorityPort,
workspace: DetachedWorkspace,
remote: RemotePort,
credential: CredentialProjection,
*,
now: Callable[[], datetime] = lambda: datetime.now(timezone.utc),
) -> None:
self.controller = controller
self.authority = authority
self.workspace = workspace
self.remote = remote
self.credential = credential
self.now = now
self._observed_generations: dict[str, int] = {}
def observe_generation(self, branch: str, generation: int) -> None:
self._observed_generations[branch] = max(generation, self._observed_generations.get(branch, 0))
def _max_generation(self, branch: str) -> int:
observed = self._observed_generations.get(branch, 0)
with self.controller.store._connect() as connection:
row = connection.execute(
"SELECT MAX(generation) AS generation FROM branch_leases WHERE branch=?",
(branch,),
).fetchone()
durable = int(row["generation"] or 0)
return max(observed, durable)
@staticmethod
def _request_mapping(request: WriterLeaseRequestV2) -> dict[str, object]:
return {
"schema": WriterLeaseRequestV2.schema,
"task_id": request.task_id,
"repository": request.repository,
"branch_mode": request.branch_mode,
"base_oid": request.base_oid,
"expected_branch_oid": request.expected_branch_oid,
"target_branch": request.target_branch,
"generation": request.generation,
"lease_seconds": request.lease_seconds,
}
def _validate_preflight(self, binding: ContinuationRuntimeBinding) -> None:
request = binding.request
normalized = validate_writer_lease_request_v2(self._request_mapping(request), self.authority)
if normalized != {**self._request_mapping(request), "create_target_branch": False, "worktree_mode": "detached_at_expected_head", "cas_ref_update_required": True}:
raise ContinuationExecutionError("CONTINUATION_NORMALIZATION_INVALID")
if not binding.original_receipt_valid or not binding.original_grant_consumed:
raise ContinuationExecutionError("ORIGINAL_GRANT_LINEAGE_INVALID")
binding.permissions.validate()
now = self.now()
if now < binding.lease_not_before:
raise ContinuationExecutionError("LEASE_NOT_ACTIVE")
if now >= binding.lease_expires_at:
raise ContinuationExecutionError("LEASE_EXPIRED")
if request.generation <= self._max_generation(request.target_branch):
raise ContinuationExecutionError("GENERATION_NOT_MONOTONIC")
if not self.workspace.is_detached():
raise ContinuationExecutionError("WORKTREE_NOT_DETACHED")
if self.workspace.head_oid() != request.expected_branch_oid:
raise ContinuationExecutionError("WORKTREE_HEAD_MISMATCH")
if self.remote.repository_identity() != request.repository:
raise ContinuationExecutionError("REMOTE_REPOSITORY_IDENTITY_MISMATCH")
def _job_request(self, binding: ContinuationRuntimeBinding, commit: ContinuationCommit, payload: bytes) -> dict[str, object]:
request = binding.request
return {
"idempotency_key": commit.idempotency_key,
"campaign_id": request.task_id,
"task_id": request.task_id,
"repository": request.repository,
"operation": "CONTINUATION_COMMIT_FILES",
"branch": request.target_branch,
"expected_branch_oid": request.expected_branch_oid,
"base_oid": request.base_oid,
"branch_mode": request.branch_mode,
"payload_digest": hashlib.sha256(payload).hexdigest(),
"lineage_anchor_oid": request.base_oid,
"generation": request.generation,
"remote_branch_create_allowed": binding.remote_branch_create_allowed,
"external_job_id": binding.external_job_id,
"files_digest": hashlib.sha256(
json.dumps(
{path: hashlib.sha256(content).hexdigest() for path, content in sorted(commit.files.items())},
sort_keys=True, separators=(",", ":"),
).encode("utf-8")
).hexdigest(),
"message_digest": hashlib.sha256(commit.message.encode("utf-8")).hexdigest(),
"worker_principal": binding.worker_principal,
"worker_lineage": binding.worker_lineage,
}
def execute(self, binding: ContinuationRuntimeBinding, commit: ContinuationCommit) -> dict[str, object]:
payload = commit.payload()
submitted = self.controller.submit(self._job_request(binding, commit, payload), payload=payload)
current = self.controller.store.read(submitted.job_id)
if JobState(current["state"]) == JobState.COMPLETED:
return self.controller.store._receipt(current, duplicate=True).as_dict()
self._validate_preflight(binding)
request = binding.request
projected = False
try:
self.credential.project()
projected = True
row = self.controller.store.read(submitted.job_id)
if JobState(row["state"]) == JobState.REQUESTED:
self.controller.advance(
submitted.job_id,
JobState.AUTHORIZED,
side_effect_state="AUTHORIZED",
receipt={"task_id": request.task_id, "original_grant_consumed": True},
)
row = self.controller.store.read(submitted.job_id)
if JobState(row["state"]) == JobState.AUTHORIZED:
self.controller.lease_job(
submitted.job_id,
lease_id=f"lease-{submitted.job_id}",
lineage_anchor_oid=request.base_oid,
current_head_oid=request.expected_branch_oid,
generation=request.generation,
lease_expires_at=binding.lease_expires_at.astimezone(timezone.utc).isoformat(),
expected_revision=int(row["revision"]),
)
self.controller.advance(submitted.job_id, JobState.WORKTREE_READY, side_effect_state="WORKTREE_READY")
self.controller.advance(submitted.job_id, JobState.EXECUTING, side_effect_state="IN_PROGRESS")
new_oid = self.workspace.commit_files(commit.files, commit.message)
if not OID_RE.fullmatch(new_oid) or not self.authority.is_ancestor(request.expected_branch_oid, new_oid):
raise ContinuationExecutionError("NON_FAST_FORWARD_COMMIT")
authority_oid = self.authority.compare_and_swap_branch(request.target_branch, request.expected_branch_oid, new_oid)
if authority_oid != new_oid:
raise ContinuationExecutionError("AUTHORITY_CAS_READBACK_MISMATCH")
self.controller.advance(
submitted.job_id,
JobState.LOCAL_VERIFIED,
side_effect_state="LOCAL_VERIFIED",
receipt={"authority_oid": authority_oid, "force_push": False},
)
try:
self.controller.advance(submitted.job_id, JobState.REMOTE_SYNCING, side_effect_state="IN_PROGRESS")
self.remote.push_exact(request.target_branch, request.expected_branch_oid, new_oid, allow_absent=binding.remote_branch_create_allowed)
except Exception:
return self._remote_pending(submitted.job_id, authority_oid, new_oid)
return self._finish_remote_verification(submitted.job_id, authority_oid, new_oid)
finally:
if projected:
self.credential.revoke()
def _observe_remote(self, branch: str, oid: str) -> tuple[str | None, str | None, bool]:
return self.remote.read_git_ref(branch), self.remote.read_api_branch(branch), self.remote.commit_visible(oid)
def _remote_pending(self, job_id: str, authority_oid: str, new_oid: str) -> dict[str, object]:
receipt = {
"authority_oid": authority_oid,
"commit_oid": new_oid,
"desired_remote_oid": new_oid,
"remote_verified": False,
"force_push": False,
}
result = self.controller.advance(
job_id,
JobState.RECONCILIATION_REQUIRED,
side_effect_state="REMOTE_PUSH_PENDING",
receipt=receipt,
)
return result.as_dict()
def _finish_remote_verification(self, job_id: str, authority_oid: str, new_oid: str) -> dict[str, object]:
row = self.controller.store.read(job_id)
branch = row["branch"]
request = json.loads(row["request_json"])["binding"]
git_oid, api_oid, visible = self._observe_remote(branch, new_oid)
decision = decide_reconciliation(RemoteObservation("aios/aios", branch, new_oid, authority_oid, git_oid, api_oid, visible, datetime.now(timezone.utc).isoformat(), request.get("expected_branch_oid")))
if decision.action is not ReconciliationAction.MARK_REMOTE_VERIFIED:
return self._remote_pending(job_id, authority_oid, new_oid)
receipt = {
"authority_oid": authority_oid,
"commit_oid": new_oid,
"git_remote_oid": git_oid,
"api_branch_oid": api_oid,
"commit_visible": True,
"remote_verified": True,
"reconciliation_decision": decision.action.value,
"force_push": False,
}
self.controller.advance(job_id, JobState.REMOTE_VERIFIED, side_effect_state="REMOTE_VERIFIED", receipt=receipt)
return self.controller.advance(job_id, JobState.COMPLETED, side_effect_state="REMOTE_VERIFIED", receipt=receipt).as_dict()
def reconcile(self, job_id: str) -> dict[str, object]:
row = self.controller.store.read(job_id)
state = JobState(row["state"])
if state == JobState.COMPLETED:
return self.controller.store._receipt(row, duplicate=True).as_dict()
if state != JobState.RECONCILIATION_REQUIRED:
raise ContinuationExecutionError("JOB_NOT_RECONCILABLE")
request = json.loads(row["request_json"])["binding"]
receipt = json.loads(row["receipt_json"])
new_oid = receipt["desired_remote_oid"]
authority_oid = self.authority.branch_head(row["branch"])
try:
generation = int(request.get("generation") or 0)
except (TypeError, ValueError) as exc:
raise ContinuationExecutionError("DURABLE_BINDING_GENERATION_INVALID") from exc
if generation > 0:
with self.controller.store._connect() as connection:
earlier = connection.execute(
"SELECT 1 FROM jobs WHERE branch=? AND state=? "
"AND CAST(json_extract(request_json, '$.binding.generation') AS INTEGER) < ? LIMIT 1",
(row["branch"], JobState.RECONCILIATION_REQUIRED.value, generation),
).fetchone()
if earlier is not None:
raise ContinuationExecutionError("EARLIER_RECONCILIATION_REQUIRED")
self.credential.project()
try:
git_oid, api_oid, visible = self._observe_remote(row["branch"], new_oid)
if authority_oid == git_oid == api_oid == new_oid and visible:
self.controller.advance(job_id, JobState.RECONCILING, side_effect_state="OBSERVATION_FIRST")
return self._finish_remote_verification(job_id, new_oid, new_oid)
expected_oid = request["expected_branch_oid"]
if (
self.authority.is_ancestor(new_oid, authority_oid)
and git_oid is not None
and git_oid == api_oid
and self.authority.is_ancestor(git_oid, new_oid)
):
self.controller.advance(job_id, JobState.RECONCILING, side_effect_state="OBSERVATION_FIRST_CHAIN")
self.remote.push_exact(row["branch"], git_oid, new_oid, allow_absent=bool(request.get("remote_branch_create_allowed")) or request.get("branch_mode") == "CREATE_NEW")
return self._finish_remote_verification(job_id, new_oid, new_oid)
allow_remote_create = bool(request.get("remote_branch_create_allowed")) or request.get("branch_mode") == "CREATE_NEW" or (git_oid is None and api_oid is None)
remote_matches_expected = git_oid == api_oid == expected_oid
remote_absent_for_create = git_oid is None and api_oid is None
if authority_oid == new_oid and (remote_matches_expected or remote_absent_for_create):
self.controller.advance(job_id, JobState.RECONCILING, side_effect_state="OBSERVATION_FIRST_RETRY")
self.remote.push_exact(row["branch"], expected_oid, new_oid, allow_absent=allow_remote_create)
return self._finish_remote_verification(job_id, new_oid, new_oid)
return self.controller.store._receipt(row, duplicate=True).as_dict()
finally:
self.credential.revoke()
@@ -0,0 +1,154 @@
"""Campaign-aware continuation leases for an existing Git branch.
This module is deliberately transport-neutral. It validates branch lineage
and hands ref mutation to an injected compare-and-swap broker; it does not run
Git, inspect credentials, or create a branch itself.
"""
from __future__ import annotations
from dataclasses import dataclass, replace
from datetime import datetime, timedelta, timezone
import re
from typing import Protocol
OID_RE = re.compile(r"^[0-9a-f]{40}$")
TARGET_BRANCH = "agent/autonomous-release-campaign-v1-20260807"
LINEAGE_ANCHOR = "69b45e81dcd2c3707c367003b08c55fd361faae8"
CAMPAIGN_HEAD = "bba675fab3b7e0c5fba7066720252b3c91b9f20d"
class ContinuationError(RuntimeError):
"""Stable fail-closed error for lease or branch binding violations."""
class BranchProbe(Protocol):
def branch_exists(self, branch: str) -> bool: ...
def branch_head(self, branch: str) -> str: ...
def is_ancestor(self, older: str, newer: str) -> bool: ...
class RefCAS(Protocol):
def compare_and_swap_branch(self, branch: str, expected: str, new: str) -> str: ...
def _oid(value: str, field: str) -> str:
if not isinstance(value, str) or not OID_RE.fullmatch(value):
raise ContinuationError(f"{field}_invalid")
return value
@dataclass(frozen=True, slots=True)
class WriterLeaseRequestV2:
task_id: str
repository: str
branch_mode: str
base_oid: str
expected_branch_oid: str
target_branch: str
generation: int
lease_seconds: int = 900
schema = "aios.task-writer-lease.v2"
accepted_schemas = frozenset({"aios.task-writer-lease.v2", "aios.task-writer-lease-request.v2"})
def validate(self, probe: BranchProbe) -> None:
if not self.task_id or self.repository != "aios/aios":
raise ContinuationError("request_identity_invalid")
if self.branch_mode not in {"CREATE_NEW", "CONTINUE_EXISTING"}:
raise ContinuationError("branch_mode_invalid")
_oid(self.base_oid, "base_oid")
_oid(self.expected_branch_oid, "expected_branch_oid")
if self.target_branch in {"main", "master"} or self.target_branch.startswith(("refs/", "tags/")):
raise ContinuationError("protected_branch_rejected")
if not isinstance(self.generation, int) or isinstance(self.generation, bool) or self.generation < 1:
raise ContinuationError("generation_invalid")
if not isinstance(self.lease_seconds, int) or not 1 <= self.lease_seconds <= 86_400:
raise ContinuationError("lease_duration_invalid")
if self.branch_mode == "CONTINUE_EXISTING":
if not probe.branch_exists(self.target_branch):
raise ContinuationError("EXISTING_BRANCH_MISSING")
actual = _oid(probe.branch_head(self.target_branch), "actual_branch_oid")
if actual != self.expected_branch_oid:
raise ContinuationError("EXISTING_BRANCH_OID_CONFLICT")
if not probe.is_ancestor(self.base_oid, actual):
raise ContinuationError("BASE_NOT_ANCESTOR_OF_BRANCH")
elif hasattr(probe, "commit_exists") and not probe.commit_exists(self.base_oid):
raise ContinuationError("BASE_COMMIT_NOT_FOUND")
elif self.branch_mode == "CREATE_NEW" and probe.branch_exists(self.target_branch):
raise ContinuationError("TARGET_BRANCH_ALREADY_EXISTS")
elif self.branch_mode == "CREATE_NEW" and self.base_oid != self.expected_branch_oid:
raise ContinuationError("CREATE_NEW_BASE_EXPECTATION_MISMATCH")
@dataclass(frozen=True, slots=True)
class CampaignWriterLease:
task_id: str
repository: str
branch: str
lineage_anchor_oid: str
current_head_oid: str
generation: int
lease_expires_at: datetime
append_only: bool = True
renewable: bool = True
multi_commit: bool = True
recoverable: bool = True
state: str = "ACTIVE"
branch_bound: bool = True
def __post_init__(self) -> None:
_oid(self.lineage_anchor_oid, "lineage_anchor_oid")
_oid(self.current_head_oid, "current_head_oid")
if not all((self.append_only, self.renewable, self.multi_commit, self.recoverable, self.branch_bound)):
raise ContinuationError("lease_capability_downgrade")
class ContinuationLeaseManager:
"""State machine for a multi-commit lease with CAS-only ref updates."""
def __init__(self, probe: BranchProbe, refs: RefCAS, *, now=datetime.now) -> None:
self.probe = probe
self.refs = refs
self.now = now
def open(self, request: WriterLeaseRequestV2) -> CampaignWriterLease:
request.validate(self.probe)
if request.branch_mode != "CONTINUE_EXISTING":
raise ContinuationError("CREATE_NEW_REQUIRES_SEPARATE_PROVISIONER")
return CampaignWriterLease(
task_id=request.task_id,
repository=request.repository,
branch=request.target_branch,
lineage_anchor_oid=request.base_oid,
current_head_oid=request.expected_branch_oid,
generation=request.generation,
lease_expires_at=self.now(timezone.utc) + timedelta(seconds=request.lease_seconds),
)
def renew(self, lease: CampaignWriterLease, seconds: int) -> CampaignWriterLease:
if lease.state != "ACTIVE" or not lease.renewable or seconds < 1:
raise ContinuationError("lease_not_renewable")
return replace(lease, lease_expires_at=self.now(timezone.utc) + timedelta(seconds=seconds))
def append_commit(self, lease: CampaignWriterLease, new_head_oid: str) -> CampaignWriterLease:
if lease.state != "ACTIVE":
raise ContinuationError("lease_not_active")
_oid(new_head_oid, "new_head_oid")
if not self.probe.is_ancestor(lease.current_head_oid, new_head_oid):
raise ContinuationError("NON_FAST_FORWARD_COMMIT")
try:
actual = self.refs.compare_and_swap_branch(
lease.branch, lease.current_head_oid, new_head_oid
)
except Exception as exc:
raise ContinuationError("BRANCH_LEASE_STALE") from exc
if actual != new_head_oid:
raise ContinuationError("BRANCH_LEASE_STALE")
return replace(lease, current_head_oid=new_head_oid)
def supersede(self, lease: CampaignWriterLease) -> CampaignWriterLease:
if lease.state not in {"ACTIVE", "RECOVERABLE_FAILURE", "RECONCILIATION_REQUIRED"}:
raise ContinuationError("lease_not_supersedable")
return replace(lease, generation=lease.generation + 1, state="SUPERSEDED")
@@ -0,0 +1,25 @@
"""Separate read, write, and release plane readiness evaluation."""
from __future__ import annotations
from dataclasses import dataclass
from typing import Mapping
@dataclass(frozen=True, slots=True)
class RuntimeStatus:
read_plane: Mapping[str, bool]
write_plane: Mapping[str, bool]
release_plane: Mapping[str, bool]
@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())
def as_dict(self) -> dict[str, object]:
return {
"READ_PLANE": dict(self.read_plane),
"WRITE_PLANE": dict(self.write_plane),
"RELEASE_PLANE": dict(self.release_plane),
"release_control_plane_ready": self.release_control_plane_ready,
}
@@ -0,0 +1,58 @@
"""Workspace-independent authority/network/Gitea ref sentinel."""
from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
import subprocess
from typing import Callable, Mapping
class SentinelError(RuntimeError):
pass
@dataclass(frozen=True, slots=True)
class SentinelInputs:
authority_git_dir: Path
gitea_remote: str
gitea_api: str
ref: str
expected_oid: str
@dataclass(frozen=True, slots=True)
class SentinelResult:
authority_oid: str | None
network_oid: str | None
gitea_oid: str | None
equal: bool
error: str | None = None
def authority_oid(inputs: SentinelInputs) -> str:
result = subprocess.run(
["git", "--git-dir", str(inputs.authority_git_dir), "rev-parse", inputs.ref],
text=True,
capture_output=True,
check=False,
)
if result.returncode != 0:
raise SentinelError("AUTHORITY_REF_UNAVAILABLE")
return result.stdout.strip()
def evaluate(
inputs: SentinelInputs,
*,
network_reader: Callable[[str, str], str | None],
gitea_reader: Callable[[str, str], str | None],
) -> SentinelResult:
try:
local = authority_oid(inputs)
network = network_reader(inputs.gitea_remote, inputs.ref)
gitea = gitea_reader(inputs.gitea_api, inputs.ref)
equal = local == network == gitea == inputs.expected_oid
return SentinelResult(local, network, gitea, equal, None if equal else "OID_MISMATCH")
except SentinelError as exc:
return SentinelResult(None, None, None, False, str(exc))
@@ -0,0 +1,30 @@
"""Non-sensitive control-plane snapshot assembly."""
from __future__ import annotations
from typing import Any, Mapping
def _copy(value: Mapping[str, Any] | None, keys: tuple[str, ...]) -> dict[str, Any]:
value = value or {}
return {key: value.get(key) for key in keys}
def build_control_plane_snapshot(*, target_version: str, runtime: Mapping[str, Any] | None = None, repository: Mapping[str, Any] | None = None, production: Mapping[str, Any] | None = None, task: Mapping[str, Any] | None = None, capabilities: Mapping[str, Any] | None = None, credential_broker: Mapping[str, Any] | None = None) -> dict[str, Any]:
task_snapshot = _copy(task, ("task_id", "branch", "authority_branch_oid", "gitea_git_oid", "gitea_api_oid", "commit_visible", "generation", "reconciliation_state"))
for key in ("external_job_id", "commit_oid", "current_head_oid", "completed_replay_reconciliation_id", "receipt_authority_oid"):
if task is not None and key in task:
task_snapshot[key] = task[key]
return {
"version": {"target_release": target_version},
"runtime": _copy(runtime, ("bridge_schema_version", "control_plane_release_version", "runtime_release_commit", "runtime_path", "artifact_sha256", "drift_status")),
"repository": _copy(repository, ("identity", "authority_git_dir", "authority_main_oid")),
"production": _copy(production, ("baseline_version", "target_oid", "deployment_state")),
"task": task_snapshot,
"capabilities": _copy(capabilities, ("legacy_writes_enabled", "governed_writes_enabled", "force_push", "main_write", "tag_write", "baseline_write", "production_write")),
"credential_broker": _copy(credential_broker, ("status", "provider")),
"secret_material_exposed": False,
}
def getControlPlaneSnapshot(**kwargs: Any) -> dict[str, Any]:
return build_control_plane_snapshot(**kwargs)
@@ -0,0 +1,478 @@
"""Minimal convergence contracts for AIOS V2R2C2.
This module deliberately contains no external side effects. It provides the shared
data contracts and pure evaluation rules used by domain controllers. Domain
controllers remain authoritative for their own final states.
"""
from __future__ import annotations
from dataclasses import asdict, dataclass, field
from datetime import datetime, timezone
from enum import Enum
import hashlib
import json
from typing import Any, Iterable, Mapping, Sequence
class ConvergenceContractError(ValueError):
"""Raised when an invalid convergence contract is constructed."""
class EvidenceState(str, Enum):
PASS = "PASS"
FAIL = "FAIL"
UNKNOWN = "UNKNOWN"
BLOCKED = "BLOCKED"
NOT_APPLICABLE = "NOT_APPLICABLE"
class AcceptanceDecision(str, Enum):
ACCEPT = "ACCEPT"
REJECT = "REJECT"
BLOCKED = "BLOCKED"
UNKNOWN = "UNKNOWN"
class ObservedHealth(str, Enum):
HEALTHY = "HEALTHY"
DEGRADED = "DEGRADED"
FAILED = "FAILED"
UNKNOWN = "UNKNOWN"
class ReconciliationState(str, Enum):
PENDING = "PENDING"
OBSERVING = "OBSERVING"
DIFF_FOUND = "DIFF_FOUND"
APPLYING = "APPLYING"
VERIFYING = "VERIFYING"
CONVERGED = "CONVERGED"
DEGRADED = "DEGRADED"
BLOCKED = "BLOCKED"
class ContinuationStopReason(str, Enum):
CONTEXT_BOUNDARY = "context_boundary"
WORKER_RESTART = "worker_restart"
HANDOFF = "handoff"
AUTHORITY_GATE = "authority_gate"
WAIT_EXTERNAL = "wait_external"
def _utc_now() -> str:
return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
def _canonical_json(value: Mapping[str, Any]) -> bytes:
return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode("utf-8")
def _sha256(value: Mapping[str, Any]) -> str:
return "sha256:" + hashlib.sha256(_canonical_json(value)).hexdigest()
def _nonempty(value: object, field_name: str) -> str:
text = str(value or "").strip()
if not text:
raise ConvergenceContractError(f"{field_name}_required")
return text
def _generation(value: object, field_name: str = "generation") -> int:
try:
parsed = int(value)
except (TypeError, ValueError) as exc:
raise ConvergenceContractError(f"{field_name}_invalid") from exc
if parsed < 1:
raise ConvergenceContractError(f"{field_name}_invalid")
return parsed
@dataclass(frozen=True, slots=True)
class StateOwnership:
object_type: str
authoritative_service: str
authoritative_store: str
final_writer: str | None = None
projections: tuple[str, ...] = ()
schema_version: str = "aios.state-ownership.v1"
def __post_init__(self) -> None:
_nonempty(self.object_type, "object_type")
_nonempty(self.authoritative_service, "authoritative_service")
_nonempty(self.authoritative_store, "authoritative_store")
if self.final_writer is not None:
_nonempty(self.final_writer, "final_writer")
def as_dict(self) -> dict[str, Any]:
return asdict(self)
DEFAULT_STATE_OWNERSHIP: tuple[StateOwnership, ...] = (
StateOwnership(
object_type="goal",
authoritative_service="goal-kernel",
authoritative_store="goal-store",
final_writer="goal-kernel",
projections=("admin-web", "context-pack"),
),
StateOwnership(
object_type="task_run",
authoritative_service="task-plane-18940",
authoritative_store="durable-task-store",
final_writer="task-plane-18940",
projections=("admin-web", "context-pack"),
),
StateOwnership(
object_type="release",
authoritative_service="release-controller",
authoritative_store="release-registry",
final_writer="release-controller",
projections=("baseline-authority", "admin-web"),
),
StateOwnership(
object_type="git_branch",
authoritative_service="authority-git-executor",
authoritative_store="authoritative-git",
final_writer="authority-git-executor",
projections=("gitea",),
),
StateOwnership(
object_type="external_projection",
authoritative_service="projection-controller",
authoritative_store="projection-store",
final_writer="projection-controller",
projections=("openapi", "gpt-action", "nginx"),
),
)
def ownership_index(
ownership: Iterable[StateOwnership] = DEFAULT_STATE_OWNERSHIP,
) -> dict[str, StateOwnership]:
result: dict[str, StateOwnership] = {}
for row in ownership:
if row.object_type in result:
raise ConvergenceContractError(f"duplicate_state_owner:{row.object_type}")
result[row.object_type] = row
return result
@dataclass(frozen=True, slots=True)
class AcceptanceCriterion:
criterion_id: str
required: bool
state: EvidenceState
verifier_id: str
evidence_refs: tuple[str, ...] = ()
def __post_init__(self) -> None:
_nonempty(self.criterion_id, "criterion_id")
_nonempty(self.verifier_id, "verifier_id")
@dataclass(frozen=True, slots=True)
class AcceptanceInvariant:
invariant_id: str
state: EvidenceState
evidence_refs: tuple[str, ...] = ()
def __post_init__(self) -> None:
_nonempty(self.invariant_id, "invariant_id")
@dataclass(frozen=True, slots=True)
class AcceptanceVector:
subject_type: str
subject_id: str
subject_revision: int
criteria: tuple[AcceptanceCriterion, ...]
invariants: tuple[AcceptanceInvariant, ...] = ()
unresolved_side_effects: tuple[str, ...] = ()
unresolved_blockers: tuple[str, ...] = ()
runtime_proofs: tuple[str, ...] = ()
schema: str = "aios.acceptance-vector.v1"
def __post_init__(self) -> None:
_nonempty(self.subject_type, "subject_type")
_nonempty(self.subject_id, "subject_id")
_generation(self.subject_revision, "subject_revision")
ids = [row.criterion_id for row in self.criteria]
if len(ids) != len(set(ids)):
raise ConvergenceContractError("duplicate_acceptance_criterion")
invariant_ids = [row.invariant_id for row in self.invariants]
if len(invariant_ids) != len(set(invariant_ids)):
raise ConvergenceContractError("duplicate_acceptance_invariant")
def as_dict(self) -> dict[str, Any]:
value = asdict(self)
for row in value["criteria"]:
row["state"] = row["state"].value
for row in value["invariants"]:
row["state"] = row["state"].value
return value
@dataclass(frozen=True, slots=True)
class AcceptanceResult:
decision: AcceptanceDecision
reasons: tuple[str, ...]
vector_hash: str
def as_dict(self) -> dict[str, Any]:
return {
"decision": self.decision.value,
"reasons": list(self.reasons),
"vector_hash": self.vector_hash,
}
def evaluate_acceptance(vector: AcceptanceVector) -> AcceptanceResult:
"""Pure fail-closed acceptance evaluator.
The evaluator never writes a domain final state. The domain owner consumes this
result and remains the only final writer for Goal/Release/Product state.
"""
reasons: list[str] = []
required = [row for row in vector.criteria if row.required]
for row in required:
if row.state == EvidenceState.FAIL:
reasons.append(f"criterion_failed:{row.criterion_id}")
elif row.state == EvidenceState.BLOCKED:
reasons.append(f"criterion_blocked:{row.criterion_id}")
elif row.state == EvidenceState.UNKNOWN:
reasons.append(f"criterion_unknown:{row.criterion_id}")
for row in vector.invariants:
if row.state == EvidenceState.FAIL:
reasons.append(f"invariant_failed:{row.invariant_id}")
elif row.state in {EvidenceState.UNKNOWN, EvidenceState.BLOCKED}:
reasons.append(f"invariant_unknown:{row.invariant_id}")
if vector.unresolved_side_effects:
reasons.append("unresolved_side_effects")
if vector.unresolved_blockers:
reasons.append("unresolved_blockers")
digest = _sha256(vector.as_dict())
if any(reason.startswith(("criterion_failed:", "invariant_failed:")) for reason in reasons):
decision = AcceptanceDecision.REJECT
elif any(reason.startswith("criterion_blocked:") for reason in reasons) or vector.unresolved_blockers:
decision = AcceptanceDecision.BLOCKED
elif reasons:
decision = AcceptanceDecision.UNKNOWN
else:
decision = AcceptanceDecision.ACCEPT
return AcceptanceResult(decision=decision, reasons=tuple(reasons), vector_hash=digest)
@dataclass(frozen=True, slots=True)
class DesiredState:
resource_id: str
resource_type: str
desired_generation: int
desired_spec: Mapping[str, Any]
owner_goal_id: str | None = None
lifecycle: str = "active"
schema: str = "aios.desired-state.v1"
def __post_init__(self) -> None:
_nonempty(self.resource_id, "resource_id")
_nonempty(self.resource_type, "resource_type")
_generation(self.desired_generation, "desired_generation")
if self.lifecycle not in {"active", "suspended", "deleted"}:
raise ConvergenceContractError("desired_lifecycle_invalid")
def identity(self) -> tuple[str, int]:
return self.resource_id, self.desired_generation
@dataclass(frozen=True, slots=True)
class ObservedState:
resource_id: str
observed_generation: int | None
observed_spec: Mapping[str, Any]
health: ObservedHealth
source_identity: str
observed_at: str
evidence_refs: tuple[str, ...] = ()
schema: str = "aios.observed-state.v1"
def __post_init__(self) -> None:
_nonempty(self.resource_id, "resource_id")
_nonempty(self.source_identity, "source_identity")
_nonempty(self.observed_at, "observed_at")
if self.observed_generation is not None:
_generation(self.observed_generation, "observed_generation")
@dataclass(frozen=True, slots=True)
class ReconciliationPlan:
resource_id: str
desired_generation: int
state: ReconciliationState
diff: Mapping[str, Any]
observation_required: bool
idempotency_key: str
reason: str
def as_dict(self) -> dict[str, Any]:
value = asdict(self)
value["state"] = self.state.value
return value
def plan_reconciliation(
desired: DesiredState,
observed: ObservedState | None,
) -> ReconciliationPlan:
"""Return a side-effect-free reconciliation decision.
UNKNOWN/missing observed state always requires observation and never converges.
"""
identity = {
"resource_id": desired.resource_id,
"desired_generation": desired.desired_generation,
"desired_spec": desired.desired_spec,
}
key = _sha256(identity)
if observed is None:
return ReconciliationPlan(
desired.resource_id,
desired.desired_generation,
ReconciliationState.OBSERVING,
{"observed": None},
True,
key,
"observed_state_missing",
)
if observed.resource_id != desired.resource_id:
raise ConvergenceContractError("resource_identity_mismatch")
if observed.health == ObservedHealth.UNKNOWN:
return ReconciliationPlan(
desired.resource_id,
desired.desired_generation,
ReconciliationState.OBSERVING,
{"health": observed.health.value},
True,
key,
"observed_state_unknown",
)
same_generation = observed.observed_generation == desired.desired_generation
same_spec = dict(observed.observed_spec) == dict(desired.desired_spec)
if same_generation and same_spec and observed.health == ObservedHealth.HEALTHY:
return ReconciliationPlan(
desired.resource_id,
desired.desired_generation,
ReconciliationState.CONVERGED,
{},
False,
key,
"desired_equals_observed",
)
return ReconciliationPlan(
desired.resource_id,
desired.desired_generation,
ReconciliationState.DIFF_FOUND,
{
"desired_generation": desired.desired_generation,
"observed_generation": observed.observed_generation,
"desired_spec": dict(desired.desired_spec),
"observed_spec": dict(observed.observed_spec),
"observed_health": observed.health.value,
},
False,
key,
"desired_observed_drift",
)
@dataclass(frozen=True, slots=True)
class SideEffectReceipt:
mutation_id: str
external_system: str
state: str
observed_at: str
external_request_id: str | None = None
external_object_id: str | None = None
observed_result_hash: str | None = None
evidence_refs: tuple[str, ...] = ()
schema: str = "aios.side-effect-receipt.v1"
def __post_init__(self) -> None:
_nonempty(self.mutation_id, "mutation_id")
_nonempty(self.external_system, "external_system")
_nonempty(self.observed_at, "observed_at")
if self.state not in {
"confirmed_applied",
"confirmed_not_applied",
"unknown",
}:
raise ConvergenceContractError("side_effect_receipt_state_invalid")
@dataclass(frozen=True, slots=True)
class ContinuationManifest:
trace_id: str
goal_id: str
goal_revision: int
task_id: str
run_id: str
current_node_id: str
acceptance_vector_ref: str
next_action: str
stop_reason: ContinuationStopReason
checkpoint_ref: str | None = None
unresolved_blocker_refs: tuple[str, ...] = ()
authoritative_decision_refs: tuple[str, ...] = ()
resource_refs: tuple[str, ...] = ()
artifact_refs: tuple[str, ...] = ()
evidence_refs: tuple[str, ...] = ()
created_at: str = field(default_factory=_utc_now)
schema: str = "aios.continuation-manifest.v1"
def __post_init__(self) -> None:
for name in (
"trace_id",
"goal_id",
"task_id",
"run_id",
"current_node_id",
"acceptance_vector_ref",
"next_action",
"created_at",
):
_nonempty(getattr(self, name), name)
_generation(self.goal_revision, "goal_revision")
def as_dict(self) -> dict[str, Any]:
value = asdict(self)
value["stop_reason"] = self.stop_reason.value
return value
@property
def content_hash(self) -> str:
return _sha256(self.as_dict())
def validate_state_ownership(
object_type: str,
writer: str,
ownership: Iterable[StateOwnership] = DEFAULT_STATE_OWNERSHIP,
*,
final_state: bool = False,
) -> None:
row = ownership_index(ownership).get(object_type)
if row is None:
raise ConvergenceContractError(f"state_owner_missing:{object_type}")
expected = row.final_writer if final_state and row.final_writer else row.authoritative_service
if writer != expected:
raise ConvergenceContractError(
f"STATE_OWNERSHIP_VIOLATION:{object_type}:{writer}:{expected}"
)
@@ -0,0 +1,78 @@
"""Metadata-only, server-side credential lease broker."""
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
import os
from pathlib import Path
import secrets
from typing import Any, Callable, Mapping
@dataclass(frozen=True, slots=True)
class CredentialLease:
lease_id: str
task_id: str
repository: str
target_branch: str
generation: int
issued_at: str
expires_at: str
scopes: tuple[str, ...]
provider: str
state: str = "ACTIVE"
def as_dict(self) -> dict[str, Any]:
return {"lease_id": self.lease_id, "task_id": self.task_id, "repository": self.repository, "target_branch": self.target_branch, "generation": self.generation, "issued_at": self.issued_at, "expires_at": self.expires_at, "scopes": list(self.scopes), "provider": self.provider, "state": self.state}
class FileCredentialProvider:
def __init__(self, token_path: str | os.PathLike[str], *, actor: str = "aios-autonomy-agent") -> None:
self.token_path = Path(token_path)
self.actor = actor
def environment(self, lease: CredentialLease) -> dict[str, str]:
if not self.token_path.is_file():
raise RuntimeError("CREDENTIAL_PROVIDER_UNAVAILABLE")
return {"CREDENTIAL_FILE": str(self.token_path), "GITEA_MACHINE_ACTOR": self.actor}
class CredentialBroker:
def __init__(self, provider: Any, *, now: Callable[[], datetime] | None = None) -> None:
self.provider = provider
self.now = now or (lambda: datetime.now(timezone.utc))
self._leases: dict[str, CredentialLease] = {}
def issue(self, *, task_id: str, repository: str, target_branch: str, generation: int, scopes: tuple[str, ...] = ("git:push", "gitea:read"), lease_seconds: int = 900) -> dict[str, Any]:
if repository != "aios/aios" or not target_branch.startswith("agent/") or generation < 1 or not 1 <= lease_seconds <= 86400:
raise ValueError("CREDENTIAL_LEASE_BINDING_INVALID")
now = self.now().astimezone(timezone.utc)
lease = CredentialLease("lease-" + secrets.token_hex(16), task_id, repository, target_branch, generation, now.isoformat(), (now + timedelta(seconds=lease_seconds)).isoformat(), tuple(scopes), type(self.provider).__name__)
self._leases[lease.lease_id] = lease
return lease.as_dict()
def _get(self, lease_id: str) -> CredentialLease:
lease = self._leases.get(lease_id)
if lease is None:
raise KeyError("CREDENTIAL_LEASE_NOT_FOUND")
if lease.state == "ACTIVE" and self.now().astimezone(timezone.utc) >= datetime.fromisoformat(lease.expires_at):
lease = CredentialLease(**{**lease.as_dict(), "scopes": tuple(lease.scopes), "state": "EXPIRED"})
self._leases[lease_id] = lease
return lease
def environment(self, lease_id: str) -> Mapping[str, str]:
lease = self._get(lease_id)
if lease.state != "ACTIVE":
raise RuntimeError("CREDENTIAL_LEASE_NOT_ACTIVE")
return self.provider.environment(lease)
def status(self, lease_id: str) -> dict[str, Any]:
return self._get(lease_id).as_dict()
def revoke(self, lease_id: str) -> dict[str, Any]:
lease = self._get(lease_id)
if lease.state == "REVOKED":
return lease.as_dict()
updated = CredentialLease(**{**lease.as_dict(), "scopes": tuple(lease.scopes), "state": "REVOKED"})
self._leases[lease_id] = updated
return updated.as_dict()
@@ -0,0 +1,125 @@
"""Non-Owner Gitea machine capability and branch reconciliation boundary."""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any, Mapping, Protocol
class MachineBrokerError(RuntimeError):
pass
class GiteaTransport(Protocol):
def compare_and_swap_branch(self, branch: str, expected_oid: str, new_oid: str) -> str: ...
def create_or_update_pr(self, branch: str, target: str, title: str, body: str) -> Mapping[str, Any]: ...
def publish_status(self, branch: str, state: str, description: str) -> Mapping[str, Any]: ...
def protected_merge(self, pr_number: int) -> Mapping[str, Any]: ...
def read_branch(self, branch: str) -> str | None: ...
def whoami(self) -> Mapping[str, Any]: ...
def repository(self, owner: str, name: str) -> Mapping[str, Any]: ...
@dataclass(frozen=True, slots=True)
class MachineCredentialProjection:
"""Opaque systemd credential reference; the credential value is never accepted."""
reference: str
account: str
owner_account: str = "aiman"
@classmethod
def from_mapping(cls, value: Mapping[str, Any]) -> "MachineCredentialProjection":
if set(value) != {"scheme", "name", "account"}:
raise MachineBrokerError("CREDENTIAL_REFERENCE_INVALID")
if value["scheme"] != "systemd-credential" or value["name"] != "gitea-machine-token":
raise MachineBrokerError("CREDENTIAL_REFERENCE_INVALID")
return cls(f"{value['scheme']}:{value['name']}", str(value["account"]))
def __post_init__(self) -> None:
if self.reference != "systemd-credential:gitea-machine-token":
raise MachineBrokerError("CREDENTIAL_REFERENCE_INVALID")
if not self.account or self.account == self.owner_account:
raise MachineBrokerError("OWNER_MACHINE_IDENTITY_REJECTED")
@dataclass(frozen=True, slots=True)
class CampaignCapabilityReceipt:
campaign_id: str
task_id: str
repository: str
branch: str
expected_oid: str
capabilities: frozenset[str]
denied: frozenset[str]
def require(self, capability: str) -> None:
if capability in self.denied or capability not in self.capabilities:
raise MachineBrokerError("CAPABILITY_DENIED")
if self.branch in {"main", "master"}:
raise MachineBrokerError("PROTECTED_BRANCH_DENIED")
class GiteaMachineBroker:
def __init__(self, transport: GiteaTransport, credential: MachineCredentialProjection, receipt: CampaignCapabilityReceipt) -> None:
self.transport = transport
self.credential = credential
self.receipt = receipt
def verify_identity(self) -> Mapping[str, Any]:
identity = dict(self.transport.whoami())
login = identity.get("login") or identity.get("username")
if not isinstance(login, str) or not login or login == self.credential.owner_account or login != self.credential.account:
raise MachineBrokerError("GITEA_MACHINE_IDENTITY_INVALID")
owner, name = self.receipt.repository.split("/", 1)
repo = dict(self.transport.repository(owner, name))
if repo.get("full_name") != self.receipt.repository:
raise MachineBrokerError("GITEA_REPOSITORY_MISMATCH")
return {"login": login, "repository": self.receipt.repository, "owner_account": self.credential.owner_account}
def push_branch(self, new_oid: str) -> str:
self.receipt.require("exact_task_branch_push")
return self.transport.compare_and_swap_branch(self.receipt.branch, self.receipt.expected_oid, new_oid)
def create_pr(self, title: str, body: str) -> Mapping[str, Any]:
self.receipt.require("pr_create_update")
return self.transport.create_or_update_pr(self.receipt.branch, "main", title, body)
def status(self, state: str, description: str, context: str = "aios/durable-release") -> Mapping[str, Any]:
self.receipt.require("status_publish")
return self.transport.publish_status(self.receipt.branch, state, description, context)
def merge(self, pr_number: int) -> Mapping[str, Any]:
self.receipt.require("protected_merge")
return self.transport.protected_merge(pr_number)
def readback(self) -> str | None:
self.receipt.require("remote_readback")
return self.transport.read_branch(self.receipt.branch)
def reconcile_exact_branch(authority_oid: str, gitea_oid: str | None, expected_oid: str) -> str:
if authority_oid != expected_oid:
return "AUTHORITY_EXPECTATION_MISMATCH"
if gitea_oid is None:
return "CREATE_REQUIRED"
if gitea_oid == expected_oid:
return "ALREADY_IN_SYNC"
return "MANUAL_OWNER_REVIEW_REQUIRED"
def reconcile_fast_forward(
remote_oid: str | None,
expected_old_oid: str,
new_oid: str,
*,
old_is_ancestor_of_new: bool,
) -> str:
"""Return a bounded action for an active Job/lease branch scope."""
if remote_oid is None:
return "CREATE_REQUIRED"
if remote_oid == new_oid:
return "ALREADY_IN_SYNC"
if remote_oid == expected_old_oid and old_is_ancestor_of_new:
return "FAST_FORWARD_REQUIRED"
return "MANUAL_OWNER_REVIEW_REQUIRED"
@@ -0,0 +1,208 @@
"""Gitea HTTP/Git transport using only a projected systemd credential."""
from __future__ import annotations
from dataclasses import dataclass
import json
import os
from pathlib import Path
import subprocess
import tempfile
from typing import Any, Mapping
from urllib.error import HTTPError, URLError
from urllib.parse import quote, urlencode
from urllib.request import Request, urlopen
class GiteaTransportError(RuntimeError):
pass
def _safe_oid(value: str, field: str) -> str:
if not isinstance(value, str) or len(value) != 40 or any(ch not in "0123456789abcdef" for ch in value):
raise GiteaTransportError(f"{field}_INVALID")
return value
@dataclass(slots=True)
class GiteaMachineTransport:
api_base: str
repository_id: str
remote_url: str
authority_git_dir: Path
credential_name: str = "gitea-machine-token"
actor: str = "aios-autonomy-agent"
timeout: int = 20
expected_merge_head: str | None = None
expected_merge_base: str | None = None
def __post_init__(self) -> None:
if self.credential_name != "gitea-machine-token":
raise GiteaTransportError("CREDENTIAL_NAME_INVALID")
if "/" not in self.repository_id or self.repository_id.startswith("/"):
raise GiteaTransportError("REPOSITORY_INVALID")
if not self.api_base.startswith("https://") and not self.api_base.startswith("http://127.0.0.1"):
raise GiteaTransportError("GITEA_API_SCOPE_INVALID")
if not self.remote_url.startswith("https://"):
raise GiteaTransportError("GITEA_REMOTE_SCOPE_INVALID")
@property
def _credential_path(self) -> Path:
directory = os.environ.get("CREDENTIALS_DIRECTORY", "")
if not directory:
raise GiteaTransportError("CREDENTIAL_PROJECTION_MISSING")
path = Path(directory) / self.credential_name
if path.is_symlink() or not path.is_file():
raise GiteaTransportError("CREDENTIAL_PROJECTION_MISSING")
return path
def _token(self) -> str:
value = self._credential_path.read_text(encoding="utf-8").strip()
if not value:
raise GiteaTransportError("CREDENTIAL_EMPTY")
return value
def _request(self, method: str, path: str, body: Mapping[str, Any] | None = None) -> tuple[int, Any]:
data = None if body is None else json.dumps(dict(body), separators=(",", ":")).encode()
headers = {"Accept": "application/json", "Authorization": f"token {self._token()}"}
if data is not None:
headers["Content-Type"] = "application/json"
request = Request(self.api_base.rstrip("/") + path, data=data, headers=headers, method=method)
try:
with urlopen(request, timeout=self.timeout) as response:
raw = response.read()
return response.status, json.loads(raw.decode()) if raw else {}
except HTTPError as exc:
# Never include response bodies: Gitea may echo sensitive request context.
raise GiteaTransportError(f"GITEA_HTTP_{exc.code}") from exc
except (URLError, TimeoutError, OSError) as exc:
raise GiteaTransportError("GITEA_NETWORK_ERROR") from exc
def whoami(self) -> Mapping[str, Any]:
return self._request("GET", "/user")[1]
def repository_info(self) -> Mapping[str, Any]:
owner, name = self.repository_id.split("/", 1)
return self._request("GET", f"/repos/{quote(owner)}/{quote(name)}")[1]
def repository(self, owner: str, name: str) -> Mapping[str, Any]:
if f"{owner}/{name}" != self.repository_id:
raise GiteaTransportError("REPOSITORY_SCOPE_DENIED")
return self.repository_info()
def read_branch(self, branch: str) -> str | None:
owner, name = self.repository_id.split("/", 1)
try:
value = self._request("GET", f"/repos/{quote(owner)}/{quote(name)}/branches/{quote(branch, safe='/')}")[1]
except GiteaTransportError as exc:
if str(exc) == "GITEA_HTTP_404":
return None
raise
sha = value.get("commit", {}).get("id") if isinstance(value, dict) else None
return _safe_oid(sha, "remote_branch_oid")
def _askpass(self) -> tuple[Path, dict[str, str]]:
credential = self._credential_path
fd, name = tempfile.mkstemp(prefix="aios-gitea-askpass-", dir="/tmp")
path = Path(name)
os.fchmod(fd, 0o700)
script = "#!/bin/sh\ncase \"$1\" in *Username*) printf '%s\\n' \"$GITEA_MACHINE_ACTOR\" ;; *) cat \"$CREDENTIAL_FILE\" ;; esac\n"
with os.fdopen(fd, "w", encoding="utf-8") as stream:
stream.write(script)
stream.flush()
os.fsync(stream.fileno())
return path, {
"GIT_ASKPASS": str(path),
"GIT_TERMINAL_PROMPT": "0",
"GITEA_MACHINE_ACTOR": self.actor,
"CREDENTIAL_FILE": str(credential),
}
def _push_branch_fast_forward(self, branch: str, new_oid: str) -> None:
askpass, additions = self._askpass()
environment = os.environ.copy()
environment.update(additions)
try:
result = subprocess.run(
["git", f"--git-dir={self.authority_git_dir}", "push", self.remote_url, f"{new_oid}:refs/heads/{branch}"],
text=True,
capture_output=True,
check=False,
env=environment,
timeout=self.timeout,
)
if result.returncode != 0:
raise GiteaTransportError("GITEA_BRANCH_CREATE_FAILED")
finally:
askpass.unlink(missing_ok=True)
def compare_and_swap_branch(self, branch: str, expected_oid: str, new_oid: str) -> str:
_safe_oid(expected_oid, "expected_oid")
_safe_oid(new_oid, "new_oid")
current = self.read_branch(branch)
if current == new_oid:
return new_oid
if current is None:
self._push_branch_fast_forward(branch, new_oid)
readback = self.read_branch(branch)
if readback != new_oid:
raise GiteaTransportError("GITEA_BRANCH_CREATE_READBACK_MISMATCH")
return readback
if current != expected_oid:
raise GiteaTransportError("GITEA_BRANCH_EXPECTATION_MISMATCH")
# The preflight read binds the expected old tip. The Git receive-pack
# then enforces the actual fast-forward CAS while transferring the new
# objects; no force/lease override or arbitrary ref is permitted.
self._push_branch_fast_forward(branch, new_oid)
readback = self.read_branch(branch)
if readback != new_oid:
raise GiteaTransportError("GITEA_BRANCH_CAS_READBACK_MISMATCH")
return readback
def _pulls(self) -> list[Mapping[str, Any]]:
owner, name = self.repository_id.split("/", 1)
query = urlencode({"state": "all", "base_branch": "main", "limit": 100})
value = self._request("GET", f"/repos/{quote(owner)}/{quote(name)}/pulls?{query}")[1]
return value if isinstance(value, list) else []
def create_or_update_pr(self, branch: str, target: str, title: str, body: str) -> Mapping[str, Any]:
if target != "main":
raise GiteaTransportError("PR_BASE_SCOPE_DENIED")
head = self.read_branch(branch)
if head is None:
raise GiteaTransportError("PR_HEAD_MISSING")
matches = [p for p in self._pulls() if (p.get("head") or {}).get("ref") == branch and (p.get("base") or {}).get("ref") == target]
open_matches = [p for p in matches if p.get("state") == "open"]
if len(open_matches) > 1:
raise GiteaTransportError("MULTIPLE_MATCHING_PRS")
if open_matches:
if (open_matches[0].get("head") or {}).get("sha") != head:
raise GiteaTransportError("PR_HEAD_CONFLICT")
return open_matches[0]
owner, name = self.repository_id.split("/", 1)
return self._request("POST", f"/repos/{quote(owner)}/{quote(name)}/pulls", {"head": branch, "base": target, "title": title, "body": body, "allow_maintainer_edit": False})[1]
def publish_status(self, branch: str, state: str, description: str, context: str = "aios/durable-release") -> Mapping[str, Any]:
oid = self.read_branch(branch)
if oid is None:
raise GiteaTransportError("STATUS_BRANCH_MISSING")
owner, name = self.repository_id.split("/", 1)
return self._request("POST", f"/repos/{quote(owner)}/{quote(name)}/statuses/{oid}", {"state": state, "context": context, "description": description})[1]
def protected_merge(self, pr_number: int) -> Mapping[str, Any]:
if not isinstance(pr_number, int) or pr_number < 1:
raise GiteaTransportError("PR_NUMBER_INVALID")
owner, name = self.repository_id.split("/", 1)
pr = self._request("GET", f"/repos/{quote(owner)}/{quote(name)}/pulls/{pr_number}")[1]
if pr.get("state") != "open" or (pr.get("base") or {}).get("ref") != "main":
raise GiteaTransportError("PR_MERGE_BINDING_INVALID")
if pr.get("mergeable") is False:
raise GiteaTransportError("PR_NOT_MERGEABLE")
head_oid = (pr.get("head") or {}).get("sha")
base_oid = (pr.get("base") or {}).get("sha")
if self.expected_merge_head is not None and head_oid != self.expected_merge_head:
raise GiteaTransportError("PR_HEAD_EXPECTATION_MISMATCH")
if self.expected_merge_base is not None and base_oid != self.expected_merge_base:
raise GiteaTransportError("PR_BASE_EXPECTATION_MISMATCH")
body = {"do": "fast-forward-only", "force_merge": False, "merge_when_checks_succeed": False, "head_commit_id": head_oid}
return self._request("POST", f"/repos/{quote(owner)}/{quote(name)}/pulls/{pr_number}/merge", body)[1]
@@ -0,0 +1,93 @@
"""Canonical Gitea transport and slash-branch verification."""
from __future__ import annotations
from dataclasses import dataclass
import json
import os
from pathlib import Path
import subprocess
from typing import Any, Callable, Mapping, Optional
from urllib.parse import quote
from urllib.request import Request, urlopen
from aios_gitops_reconciliation_core import RemoteObservation
@dataclass(frozen=True, slots=True)
class GiteaPublisherConfig:
repository: str = "aios/aios"
api_base: str = "https://g.cnnetmkt.top/git/api/v1"
remote_name: str = "gitea-http"
class GiteaPublisher:
def __init__(self, workspace: Path, *, config: GiteaPublisherConfig = GiteaPublisherConfig(), credential: Any = None, api_get: Callable[[str], Any] | None = None, git_ref: Callable[[str], Optional[str]] | None = None) -> None:
self.workspace = Path(workspace)
self.config = config
self.credential = credential
self._api_get = api_get
self._git_ref = git_ref
@staticmethod
def validate_repository(repository: str) -> None:
if repository != "aios/aios":
raise ValueError("REPOSITORY_IDENTITY_MISMATCH")
@staticmethod
def validate_branch(branch: str) -> None:
if not isinstance(branch, str) or not branch.startswith("agent/") or branch in {"main", "master"}:
raise ValueError("BRANCH_SCOPE_DENIED")
def repository_identity(self) -> str:
self.validate_repository(self.config.repository)
return self.config.repository
def _environment(self) -> dict[str, str]:
if self.credential is not None and hasattr(self.credential, "environment"):
return dict(self.credential.environment())
return {}
def read_git_ref(self, branch: str) -> Optional[str]:
self.validate_branch(branch)
if self._git_ref is not None:
return self._git_ref(branch)
result = subprocess.run(["git", "-C", str(self.workspace), "ls-remote", self.config.remote_name, f"refs/heads/{branch}"], text=True, capture_output=True, check=False, env={**os.environ, **self._environment(), "GIT_TERMINAL_PROMPT": "0"})
if result.returncode != 0 or not result.stdout.strip():
return None
return result.stdout.split()[0]
def _api(self, path: str) -> Any:
if self._api_get is not None:
return self._api_get(path)
headers = {"Accept": "application/json"}
env = self._environment()
if env.get("AIOS_GITEA_TOKEN"):
headers["Authorization"] = "token " + env["AIOS_GITEA_TOKEN"]
with urlopen(Request(self.config.api_base.rstrip("/") + path, headers=headers), timeout=20) as response:
return json.loads(response.read() or b"{}")
def read_api_branch(self, branch: str) -> Optional[str]:
self.validate_branch(branch)
for page in range(1, 101):
value = self._api(f"/repos/aios/aios/branches?page={page}&limit=50")
if not isinstance(value, list):
return None
for item in value:
if isinstance(item, Mapping) and item.get("name") == branch:
commit = item.get("commit")
return commit.get("id") if isinstance(commit, Mapping) else None
if len(value) < 50:
return None
raise RuntimeError("GITEA_BRANCH_PAGINATION_LIMIT")
def commit_visible(self, oid: str) -> bool:
if not isinstance(oid, str) or len(oid) != 40:
return False
try:
value = self._api(f"/repos/aios/aios/git/commits/{quote(oid, safe='')}")
return isinstance(value, Mapping) and value.get("sha", oid) == oid
except Exception:
return False
def observe(self, branch: str, desired_oid: str, authority_oid: Optional[str], *, expected_oid: Optional[str] = None, observed_at: str = "") -> RemoteObservation:
return RemoteObservation(self.repository_identity(), branch, desired_oid, authority_oid, self.read_git_ref(branch), self.read_api_branch(branch), self.commit_visible(desired_oid), observed_at, expected_oid)
@@ -0,0 +1,919 @@
"""SQLite-backed durable GitOps Job Controller.
The controller owns durable intent, idempotency, lease metadata, state
transitions, receipts, and payload references. A worker owns Git side effects
behind the campaign capability and branch-CAS boundaries.
"""
from __future__ import annotations
from dataclasses import dataclass
from enum import Enum
import hashlib
import json
import os
from pathlib import Path
import sqlite3
import tempfile
from datetime import datetime, timezone
from typing import Any, Callable, Mapping
class JobError(RuntimeError):
pass
class ServiceUnavailable(JobError):
pass
class BindingConflict(JobError):
pass
class JobState(str, Enum):
REQUESTED = "REQUESTED"
AUTHORIZED = "AUTHORIZED"
LEASED = "LEASED"
WORKTREE_READY = "WORKTREE_READY"
EXECUTING = "EXECUTING"
LOCAL_VERIFIED = "LOCAL_VERIFIED"
REMOTE_SYNCING = "REMOTE_SYNCING"
REMOTE_VERIFIED = "REMOTE_VERIFIED"
COMPLETED = "COMPLETED"
FAILED = "FAILED"
CANCELLED = "CANCELLED"
RECOVERABLE_FAILURE = "RECOVERABLE_FAILURE"
RECONCILING = "RECONCILING"
RECONCILIATION_REQUIRED = "RECONCILIATION_REQUIRED"
MANUAL_OWNER_REVIEW_REQUIRED = "MANUAL_OWNER_REVIEW_REQUIRED"
_ORDER = {
JobState.REQUESTED: 0,
JobState.AUTHORIZED: 1,
JobState.LEASED: 2,
JobState.WORKTREE_READY: 3,
JobState.EXECUTING: 4,
JobState.LOCAL_VERIFIED: 5,
JobState.REMOTE_SYNCING: 6,
JobState.REMOTE_VERIFIED: 7,
JobState.COMPLETED: 8,
}
_RECOVERABLE = {JobState.RECOVERABLE_FAILURE, JobState.RECONCILIATION_REQUIRED}
_TERMINAL = {
JobState.COMPLETED,
JobState.FAILED,
JobState.CANCELLED,
JobState.MANUAL_OWNER_REVIEW_REQUIRED,
}
def utc_now() -> str:
return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z")
def canonical_json(value: Any) -> bytes:
return (
json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False) + "\n"
).encode("utf-8")
def sha256_json(value: Any) -> str:
return hashlib.sha256(canonical_json(value)).hexdigest()
class PayloadStore:
"""Content-addressed filesystem payloads; raw content never enters SQLite."""
def __init__(self, root: str | os.PathLike[str]) -> None:
self.root = Path(root)
if not self.root.is_absolute() or self.root.is_symlink():
raise JobError("PAYLOAD_ROOT_INVALID")
self.root.mkdir(parents=True, exist_ok=True, mode=0o700)
os.chmod(self.root, 0o700)
def put(self, content: bytes, expected_digest: str) -> tuple[str, int]:
digest = hashlib.sha256(content).hexdigest()
if digest != expected_digest:
raise JobError("PAYLOAD_DIGEST_MISMATCH")
if len(expected_digest) != 64 or any(ch not in "0123456789abcdef" for ch in expected_digest):
raise JobError("PAYLOAD_DIGEST_INVALID")
target = self.root / expected_digest
if target.is_symlink():
raise JobError("PAYLOAD_PATH_SYMLINK")
if target.is_file():
if target.stat().st_size != len(content) or hashlib.sha256(target.read_bytes()).hexdigest() != digest:
raise JobError("PAYLOAD_EXISTING_MISMATCH")
return f"sha256/{digest}", len(content)
fd, temporary = tempfile.mkstemp(prefix=".payload-", dir=self.root)
try:
os.fchmod(fd, 0o600)
with os.fdopen(fd, "wb") as stream:
stream.write(content)
stream.flush()
os.fsync(stream.fileno())
os.replace(temporary, target)
os.chmod(target, 0o600)
finally:
if os.path.exists(temporary):
os.unlink(temporary)
return f"sha256/{digest}", len(content)
def read_verified(self, reference: str, expected_digest: str, expected_size: int) -> bytes:
if reference != f"sha256/{expected_digest}":
raise JobError("PAYLOAD_REFERENCE_MISMATCH")
path = self.root / expected_digest
if path.is_symlink() or not path.is_file():
raise JobError("PAYLOAD_MISSING")
content = path.read_bytes()
if len(content) != expected_size or hashlib.sha256(content).hexdigest() != expected_digest:
raise JobError("PAYLOAD_VERIFY_FAILED")
return content
@dataclass(frozen=True, slots=True)
class JobReceipt:
job_id: str
idempotency_key: str
state: JobState
side_effect_state: str
http_status: int
revision: int
receipt: Mapping[str, Any] | None = None
def as_dict(self) -> dict[str, Any]:
return {
"job_id": self.job_id,
"idempotency_key": self.idempotency_key,
"state": self.state.value,
"side_effect_state": self.side_effect_state,
"http_status": self.http_status,
"revision": self.revision,
"receipt": dict(self.receipt) if self.receipt else None,
}
class JobStore:
SCHEMA_VERSION = 1
def __init__(self, root: str | os.PathLike[str], *, payload_root: str | os.PathLike[str] | None = None) -> None:
self.root = Path(root)
if not self.root.is_absolute() or self.root.is_symlink():
raise JobError("JOB_ROOT_INVALID")
self.root.mkdir(parents=True, exist_ok=True, mode=0o700)
os.chmod(self.root, 0o700)
self.db_path = self.root / "state.sqlite3"
self.payloads = PayloadStore(payload_root or self.root.parent / "payloads" / "sha256")
self._initialize()
def _connect(self) -> sqlite3.Connection:
if self.db_path.is_symlink():
raise ServiceUnavailable("JOB_DB_SYMLINK")
connection = sqlite3.connect(self.db_path, timeout=30.0)
connection.row_factory = sqlite3.Row
connection.execute("PRAGMA journal_mode=WAL")
connection.execute("PRAGMA synchronous=FULL")
connection.execute("PRAGMA foreign_keys=ON")
connection.execute("PRAGMA busy_timeout=30000")
return connection
@classmethod
def for_existing_append(cls, root: str | os.PathLike[str]) -> "JobStore":
"""Open an existing job database without initialization side effects."""
path = Path(root)
if (
not path.is_absolute()
or path.is_symlink()
or not path.is_dir()
or not (path / "state.sqlite3").is_file()
or (path / "state.sqlite3").is_symlink()
):
raise JobError("JOB_ROOT_NOT_FOUND")
instance = cls.__new__(cls)
instance.root = path
instance.db_path = path / "state.sqlite3"
return instance
@staticmethod
def _ensure_completed_replay_schema(connection: sqlite3.Connection) -> None:
connection.executescript(
"""
CREATE TABLE IF NOT EXISTS completed_replay_reconciliations (
reconciliation_id TEXT PRIMARY KEY, job_id TEXT NOT NULL,
identity_digest TEXT NOT NULL UNIQUE,
source_lease_epoch INTEGER NOT NULL,
task_id TEXT NOT NULL, external_job_id TEXT NOT NULL,
internal_job_id TEXT NOT NULL, repository TEXT NOT NULL,
target_branch TEXT NOT NULL, generation INTEGER NOT NULL,
branch_mode TEXT NOT NULL, payload_digest TEXT NOT NULL,
files_digest TEXT NOT NULL, message_digest TEXT NOT NULL,
commit_oid TEXT NOT NULL, authority_head_oid TEXT NOT NULL,
current_head_oid TEXT NOT NULL, historical_lease_head_oid TEXT NOT NULL,
worker_principal TEXT NOT NULL, worker_lineage TEXT NOT NULL,
durable_revision INTEGER NOT NULL, completion_identity TEXT NOT NULL,
reason TEXT NOT NULL, provenance_json TEXT NOT NULL,
created_at TEXT NOT NULL, FOREIGN KEY(job_id) REFERENCES jobs(job_id)
);
CREATE INDEX IF NOT EXISTS completed_replay_job_idx
ON completed_replay_reconciliations(job_id);
CREATE TRIGGER IF NOT EXISTS completed_replay_no_update
BEFORE UPDATE ON completed_replay_reconciliations
BEGIN SELECT RAISE(ABORT, 'COMPLETED_REPLAY_APPEND_ONLY'); END;
CREATE TRIGGER IF NOT EXISTS completed_replay_no_delete
BEFORE DELETE ON completed_replay_reconciliations
BEGIN SELECT RAISE(ABORT, 'COMPLETED_REPLAY_APPEND_ONLY'); END;
"""
)
columns = {
str(item[1]) for item in connection.execute(
"PRAGMA table_info(completed_replay_reconciliations)"
).fetchall()
}
if columns and "historical_lease_head_oid" not in columns:
connection.execute(
"ALTER TABLE completed_replay_reconciliations ADD COLUMN historical_lease_head_oid TEXT"
)
def _initialize(self) -> None:
try:
with self._connect() as connection:
connection.executescript(
"""
CREATE TABLE IF NOT EXISTS metadata (key TEXT PRIMARY KEY, value TEXT NOT NULL);
CREATE TABLE IF NOT EXISTS jobs (
job_id TEXT PRIMARY KEY, idempotency_key TEXT NOT NULL UNIQUE,
campaign_id TEXT NOT NULL, repository TEXT NOT NULL, operation TEXT NOT NULL,
branch TEXT NOT NULL, expected_branch_oid TEXT NOT NULL,
payload_digest TEXT NOT NULL, payload_ref TEXT, payload_size INTEGER,
state TEXT NOT NULL, side_effect_state TEXT NOT NULL,
revision INTEGER NOT NULL, request_json TEXT NOT NULL,
receipt_json TEXT, created_at TEXT NOT NULL, updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS idempotency_keys (
key TEXT PRIMARY KEY, job_id TEXT NOT NULL UNIQUE,
binding_json TEXT NOT NULL, created_at TEXT NOT NULL,
FOREIGN KEY(job_id) REFERENCES jobs(job_id)
);
CREATE TABLE IF NOT EXISTS branch_leases (
lease_id TEXT PRIMARY KEY, job_id TEXT NOT NULL UNIQUE,
branch TEXT NOT NULL, lineage_anchor_oid TEXT NOT NULL,
current_head_oid TEXT NOT NULL, generation INTEGER NOT NULL,
lease_expires_at TEXT NOT NULL, state TEXT NOT NULL,
revision INTEGER NOT NULL, FOREIGN KEY(job_id) REFERENCES jobs(job_id)
);
CREATE TABLE IF NOT EXISTS state_transitions (
sequence INTEGER PRIMARY KEY AUTOINCREMENT, job_id TEXT NOT NULL,
from_state TEXT, to_state TEXT NOT NULL, revision INTEGER NOT NULL,
side_effect_state TEXT NOT NULL, created_at TEXT NOT NULL,
FOREIGN KEY(job_id) REFERENCES jobs(job_id)
);
CREATE TABLE IF NOT EXISTS receipts (
receipt_id TEXT PRIMARY KEY, job_id TEXT NOT NULL,
kind TEXT NOT NULL, digest TEXT NOT NULL, payload_json TEXT NOT NULL,
created_at TEXT NOT NULL, FOREIGN KEY(job_id) REFERENCES jobs(job_id)
);
CREATE TABLE IF NOT EXISTS completed_replay_reconciliations (
reconciliation_id TEXT PRIMARY KEY, job_id TEXT NOT NULL,
identity_digest TEXT NOT NULL UNIQUE,
source_lease_epoch INTEGER NOT NULL,
task_id TEXT NOT NULL, external_job_id TEXT NOT NULL,
internal_job_id TEXT NOT NULL, repository TEXT NOT NULL,
target_branch TEXT NOT NULL, generation INTEGER NOT NULL,
branch_mode TEXT NOT NULL, payload_digest TEXT NOT NULL,
files_digest TEXT NOT NULL, message_digest TEXT NOT NULL,
commit_oid TEXT NOT NULL, authority_head_oid TEXT NOT NULL,
current_head_oid TEXT NOT NULL, historical_lease_head_oid TEXT NOT NULL,
worker_principal TEXT NOT NULL, worker_lineage TEXT NOT NULL,
durable_revision INTEGER NOT NULL, completion_identity TEXT NOT NULL,
reason TEXT NOT NULL, provenance_json TEXT NOT NULL,
created_at TEXT NOT NULL, FOREIGN KEY(job_id) REFERENCES jobs(job_id)
);
CREATE INDEX IF NOT EXISTS completed_replay_job_idx
ON completed_replay_reconciliations(job_id);
CREATE TRIGGER IF NOT EXISTS completed_replay_no_update
BEFORE UPDATE ON completed_replay_reconciliations
BEGIN SELECT RAISE(ABORT, 'COMPLETED_REPLAY_APPEND_ONLY'); END;
CREATE TRIGGER IF NOT EXISTS completed_replay_no_delete
BEFORE DELETE ON completed_replay_reconciliations
BEGIN SELECT RAISE(ABORT, 'COMPLETED_REPLAY_APPEND_ONLY'); END;
INSERT OR REPLACE INTO metadata(key, value) VALUES('schema_version', '1');
"""
)
columns = {
str(item[1]) for item in connection.execute(
"PRAGMA table_info(completed_replay_reconciliations)"
).fetchall()
}
if columns and "historical_lease_head_oid" not in columns:
connection.execute(
"ALTER TABLE completed_replay_reconciliations "
"ADD COLUMN historical_lease_head_oid TEXT"
)
except sqlite3.Error as exc:
raise ServiceUnavailable("JOB_DB_UNAVAILABLE") from exc
def _mirror(self, job: Mapping[str, Any]) -> None:
directory = self.root / str(job["job_id"])
directory.mkdir(mode=0o700, exist_ok=True)
(directory / "receipts").mkdir(mode=0o700, exist_ok=True)
(directory / "logs-digest").mkdir(mode=0o700, exist_ok=True)
for name, value in (("request.json", job["request"]), ("state.json", job)):
target = directory / name
fd, temporary = tempfile.mkstemp(prefix=".mirror-", dir=directory)
try:
os.fchmod(fd, 0o600)
with os.fdopen(fd, "wb") as stream:
stream.write(canonical_json(value))
stream.flush()
os.fsync(stream.fileno())
os.replace(temporary, target)
finally:
if os.path.exists(temporary):
os.unlink(temporary)
def read(self, job_id: str) -> dict[str, Any]:
try:
with self._connect() as connection:
row = connection.execute("SELECT * FROM jobs WHERE job_id=?", (job_id,)).fetchone()
except sqlite3.Error as exc:
raise ServiceUnavailable("JOB_DB_UNAVAILABLE") from exc
if row is None:
raise JobError("JOB_NOT_FOUND")
return dict(row)
@staticmethod
def _completed_replay_identity(envelope: Mapping[str, Any]) -> dict[str, Any]:
required = (
"task_id", "external_job_id", "internal_job_id", "repository",
"target_branch", "generation", "branch_mode", "payload_digest",
"files_digest", "message_digest", "commit_oid", "authority_head_oid",
"historical_lease_head_oid", "worker_principal", "worker_lineage", "source_lease_epoch",
"durable_revision", "completion_identity", "reason", "provenance",
)
if any(key not in envelope for key in required):
raise BindingConflict("COMPLETED_REPLAY_BINDING_INCOMPLETE")
text_fields = (
"task_id", "external_job_id", "internal_job_id", "repository",
"target_branch", "branch_mode", "payload_digest", "files_digest",
"message_digest", "commit_oid", "authority_head_oid", "worker_principal",
"worker_lineage", "historical_lease_head_oid", "completion_identity", "reason",
)
result: dict[str, Any] = {}
for key in text_fields:
value = envelope[key]
if not isinstance(value, str) or not value.strip():
raise BindingConflict("COMPLETED_REPLAY_BINDING_INVALID")
result[key] = value
for key in ("generation", "source_lease_epoch", "durable_revision"):
value = envelope[key]
if isinstance(value, bool) or not isinstance(value, int) or value < 1:
raise BindingConflict("COMPLETED_REPLAY_BINDING_INVALID")
result[key] = value
for key in ("commit_oid", "authority_head_oid", "historical_lease_head_oid"):
if len(result[key]) != 40 or any(ch not in "0123456789abcdef" for ch in result[key]):
raise BindingConflict("COMPLETED_REPLAY_BINDING_INVALID")
for key in ("payload_digest", "files_digest", "message_digest"):
if len(result[key]) != 64 or any(ch not in "0123456789abcdef" for ch in result[key]):
raise BindingConflict("COMPLETED_REPLAY_BINDING_INVALID")
if result["repository"] != "aios/aios" or result["branch_mode"] not in {"CREATE_NEW", "CONTINUE_EXISTING"}:
raise BindingConflict("COMPLETED_REPLAY_BINDING_INVALID")
if not result["target_branch"].startswith("agent/") or result["target_branch"] in {"main", "master"}:
raise BindingConflict("COMPLETED_REPLAY_BINDING_INVALID")
provenance = envelope["provenance"]
if not isinstance(provenance, Mapping) or not provenance:
raise BindingConflict("COMPLETED_REPLAY_PROVENANCE_INVALID")
result["provenance"] = dict(provenance)
if result["commit_oid"] != result["authority_head_oid"]:
raise BindingConflict("COMPLETED_REPLAY_AUTHORITY_CONFLICT")
return result
def append_completed_replay_reconciliation(
self,
job_id: str,
envelope: Mapping[str, Any],
*,
authority_head_oid: str,
) -> dict[str, Any]:
"""Append exact completed-replay evidence without rewriting history."""
identity = self._completed_replay_identity(envelope)
if authority_head_oid != identity["authority_head_oid"]:
raise BindingConflict("COMPLETED_REPLAY_AUTHORITY_CONFLICT")
if identity["internal_job_id"] != job_id:
raise BindingConflict("COMPLETED_REPLAY_INTERNAL_JOB_CONFLICT")
identity_digest = sha256_json(identity)
reconciliation_id = "reconcile-" + identity_digest[:32]
now = utc_now()
try:
with self._connect() as connection:
self._ensure_completed_replay_schema(connection)
connection.execute("BEGIN IMMEDIATE")
row = connection.execute("SELECT * FROM jobs WHERE job_id=?", (job_id,)).fetchone()
if row is None:
raise JobError("JOB_NOT_FOUND")
if JobState(row["state"]) != JobState.COMPLETED:
raise BindingConflict("COMPLETED_REPLAY_JOB_NOT_COMPLETED")
durable_request = json.loads(row["request_json"])
binding = durable_request.get("binding", durable_request)
exact_binding = {
"task_id": binding.get("task_id") or binding.get("campaign_id"),
"external_job_id": binding.get("external_job_id"),
"internal_job_id": job_id,
"repository": binding.get("repository") or row["repository"],
"target_branch": binding.get("target_branch") or binding.get("branch"),
"generation": binding.get("generation"),
"branch_mode": binding.get("branch_mode"),
"payload_digest": binding.get("payload_digest") or row["payload_digest"],
"files_digest": binding.get("files_digest"),
"message_digest": binding.get("message_digest"),
"worker_principal": binding.get("worker_principal"),
"worker_lineage": binding.get("worker_lineage"),
}
for key, value in exact_binding.items():
if str(value) != str(identity[key]):
raise BindingConflict("COMPLETED_REPLAY_BINDING_CONFLICT")
receipt = json.loads(row["receipt_json"]) if row["receipt_json"] else None
if not isinstance(receipt, Mapping):
raise BindingConflict("COMPLETED_REPLAY_RECEIPT_MISSING")
if receipt.get("commit_oid") != identity["commit_oid"] or receipt.get("authority_oid") != identity["authority_head_oid"]:
raise BindingConflict("COMPLETED_REPLAY_RECEIPT_AUTHORITY_CONFLICT")
completion_identity = sha256_json({
"job_id": row["job_id"],
"revision": int(row["revision"]),
"receipt": receipt,
})
if identity["completion_identity"] != completion_identity:
raise BindingConflict("COMPLETED_REPLAY_COMPLETION_IDENTITY_CONFLICT")
lease_columns = {
str(item[1]) for item in connection.execute("PRAGMA table_info(branch_leases)").fetchall()
}
if "epoch" in lease_columns:
lease = connection.execute(
"SELECT lease_id, state, current_head_oid, generation FROM branch_leases WHERE job_id=? AND epoch=? ORDER BY rowid DESC LIMIT 1",
(job_id, identity["source_lease_epoch"]),
).fetchone()
else:
if identity["source_lease_epoch"] != 1:
raise BindingConflict("COMPLETED_REPLAY_LEASE_EPOCH_UNAVAILABLE")
lease = connection.execute(
"SELECT lease_id, state, current_head_oid, generation FROM branch_leases WHERE job_id=? ORDER BY rowid DESC LIMIT 1",
(job_id,),
).fetchone()
if lease is None:
raise BindingConflict("COMPLETED_REPLAY_LEASE_MISSING")
lease_state = str(lease["state"])
lease_head = str(lease["current_head_oid"])
lease_generation = int(lease["generation"])
event_tables = {
str(item[0]) for item in connection.execute(
"SELECT name FROM sqlite_master WHERE type='table' AND name='branch_lease_events'"
).fetchall()
}
if "branch_lease_events" in event_tables:
event = connection.execute(
"SELECT state, current_head_oid, generation FROM branch_lease_events WHERE lease_id=? ORDER BY rowid DESC LIMIT 1",
(lease["lease_id"],),
).fetchone()
if event is not None:
lease_state = str(event["state"])
lease_head = str(event["current_head_oid"])
lease_generation = int(event["generation"])
if lease_state not in {"ACTIVE", "USED", "EXPIRED"}:
raise BindingConflict("COMPLETED_REPLAY_LEASE_STATE_INVALID")
if lease_generation != identity["generation"]:
raise BindingConflict("COMPLETED_REPLAY_LEASE_GENERATION_CONFLICT")
if len(lease_head) != 40 or any(ch not in "0123456789abcdef" for ch in lease_head):
raise BindingConflict("COMPLETED_REPLAY_LEASE_HEAD_INVALID")
if lease_head != identity["historical_lease_head_oid"]:
raise BindingConflict("COMPLETED_REPLAY_HISTORICAL_LEASE_CONFLICT")
if identity["commit_oid"] == identity["historical_lease_head_oid"]:
raise BindingConflict("COMPLETED_REPLAY_EVIDENCE_INCOHERENT")
existing = connection.execute(
"SELECT * FROM completed_replay_reconciliations WHERE job_id=? ORDER BY created_at ASC",
(job_id,),
).fetchall()
for prior in existing:
if prior["identity_digest"] != identity_digest:
raise BindingConflict("COMPLETED_REPLAY_AMBIGUOUS")
if existing:
result = dict(existing[0])
connection.commit()
return result
provenance_json = json.dumps(identity["provenance"], sort_keys=True, separators=(",", ":"))
connection.execute(
"INSERT INTO completed_replay_reconciliations(reconciliation_id,job_id,identity_digest,source_lease_epoch,task_id,external_job_id,internal_job_id,repository,target_branch,generation,branch_mode,payload_digest,files_digest,message_digest,commit_oid,authority_head_oid,worker_principal,worker_lineage,current_head_oid,historical_lease_head_oid,durable_revision,completion_identity,reason,provenance_json,created_at) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
(
reconciliation_id, job_id, identity["identity_digest"] if "identity_digest" in identity else identity_digest, identity["source_lease_epoch"],
identity["task_id"], identity["external_job_id"], identity["internal_job_id"],
identity["repository"], identity["target_branch"], identity["generation"],
identity["branch_mode"], identity["payload_digest"], identity["files_digest"],
identity["message_digest"], identity["commit_oid"], identity["authority_head_oid"],
identity["worker_principal"], identity["worker_lineage"], identity["authority_head_oid"], identity["historical_lease_head_oid"], identity["durable_revision"],
identity["completion_identity"], identity["reason"], provenance_json, now,
),
)
result = dict(connection.execute(
"SELECT * FROM completed_replay_reconciliations WHERE reconciliation_id=?",
(reconciliation_id,),
).fetchone())
connection.commit()
return result
except (JobError, BindingConflict):
raise
except sqlite3.IntegrityError as exc:
try:
with self._connect() as connection:
existing = connection.execute(
"SELECT * FROM completed_replay_reconciliations WHERE identity_digest=?",
(identity_digest,),
).fetchone()
if existing is not None:
return dict(existing)
except sqlite3.Error:
pass
raise BindingConflict("COMPLETED_REPLAY_IDEMPOTENCY_CONFLICT") from exc
except sqlite3.Error as exc:
raise ServiceUnavailable("JOB_DB_UNAVAILABLE") from exc
def completed_replay_evidence(self, job_id: str) -> dict[str, Any] | None:
try:
with self._connect() as connection:
rows = connection.execute(
"SELECT * FROM completed_replay_reconciliations WHERE job_id=? ORDER BY created_at ASC",
(job_id,),
).fetchall()
except sqlite3.Error as exc:
raise ServiceUnavailable("JOB_DB_UNAVAILABLE") from exc
if not rows:
return None
if len({str(row["identity_digest"]) for row in rows}) != 1:
raise BindingConflict("COMPLETED_REPLAY_AMBIGUOUS")
first = dict(rows[0])
immutable_fields = tuple(
key for key in first
if key != "created_at"
)
for row in rows[1:]:
if any(row[key] != first[key] for key in immutable_fields):
raise BindingConflict("COMPLETED_REPLAY_AMBIGUOUS")
if not (
first["commit_oid"] == first["authority_head_oid"] == first["current_head_oid"]
and first["commit_oid"] != first["historical_lease_head_oid"]
):
raise BindingConflict("COMPLETED_REPLAY_EVIDENCE_INCOHERENT")
return first
def _receipt(self, row: Mapping[str, Any], *, duplicate: bool) -> JobReceipt:
state = JobState(row["state"])
if state == JobState.COMPLETED:
status, side = 200, "COMPLETED"
elif state in _RECOVERABLE:
status = 202
side = row["side_effect_state"] if row["side_effect_state"] == "REMOTE_PUSH_PENDING" else "RESUME_SAME_JOB"
elif row["side_effect_state"] == "UNKNOWN":
status, side = 503, "UNKNOWN"
else:
status, side = 202, "SAME_JOB" if duplicate else "QUEUED"
receipt = json.loads(row["receipt_json"]) if row["receipt_json"] else None
return JobReceipt(row["job_id"], row["idempotency_key"], state, side, status, int(row["revision"]), receipt)
class GitOpsJobController:
def __init__(self, store: JobStore) -> None:
self.store = store
@staticmethod
def _binding(request: Mapping[str, Any]) -> dict[str, str]:
fields = ("campaign_id", "repository", "operation", "branch", "expected_branch_oid", "payload_digest")
if any(not isinstance(request.get(field), str) or not request[field].strip() for field in fields):
raise JobError("JOB_BINDING_INVALID")
oid = request["expected_branch_oid"]
digest = request["payload_digest"]
if len(oid) != 40 or any(ch not in "0123456789abcdef" for ch in oid):
raise JobError("EXPECTED_BRANCH_OID_INVALID")
if len(digest) != 64 or any(ch not in "0123456789abcdef" for ch in digest):
raise JobError("PAYLOAD_DIGEST_INVALID")
if request["branch"] in {"main", "master"} or request["branch"].startswith(("refs/", "tags/")):
raise JobError("PROTECTED_BRANCH_REJECTED")
binding = {field: str(request[field]) for field in fields}
# Operation-specific, non-secret binding inputs must survive the
# durable round trip and participate in idempotency. Arbitrary request
# fields are intentionally discarded rather than persisted.
optional = (
"idempotency_key",
"task_id",
"new_branch_oid",
"authorization_recovery_digest",
"lineage_anchor_oid",
"base_oid",
"branch_mode",
"generation",
"expected_main_oid",
"pr_number",
"target_branch",
"check_context",
"check_state",
"check_description",
"external_job_id",
"files_digest",
"message_digest",
"worker_principal",
"worker_lineage",
)
for field in optional:
value = request.get(field)
if value is None:
continue
if isinstance(value, bool) or not isinstance(value, (str, int)):
raise JobError("JOB_BINDING_OPTION_INVALID")
text = str(value).strip()
if not text or len(text) > 512:
raise JobError("JOB_BINDING_OPTION_INVALID")
binding[field] = text
return binding
def submit(self, request: Mapping[str, Any], *, payload: bytes | None = None) -> JobReceipt:
binding = self._binding(request)
payload_ref = request.get("payload_ref")
payload_size = request.get("payload_size")
if payload is not None:
payload_ref, payload_size = self.store.payloads.put(payload, binding["payload_digest"])
if payload_ref is not None and payload_ref != f"sha256/{binding['payload_digest']}":
raise JobError("PAYLOAD_REFERENCE_MISMATCH")
if payload_ref is not None and (not isinstance(payload_size, int) or payload_size < 0):
raise JobError("PAYLOAD_SIZE_INVALID")
client_idempotency_key = binding.get("idempotency_key")
if client_idempotency_key is not None:
if len(client_idempotency_key) > 128 or not all(
character.isalnum() or character in "._:-" for character in client_idempotency_key
):
raise JobError("IDEMPOTENCY_KEY_INVALID")
key = sha256_json({"client_idempotency_key": client_idempotency_key})
else:
key = sha256_json(binding)
job_id = "job-" + key[:32]
durable_request = dict(binding)
if payload_ref is not None:
durable_request.update({"payload_ref": str(payload_ref), "payload_size": int(payload_size)})
now = utc_now()
try:
with self.store._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
existing = connection.execute("SELECT * FROM jobs WHERE idempotency_key=?", (key,)).fetchone()
if existing is not None:
if json.loads(existing["request_json"]).get("binding") != binding:
raise BindingConflict("BINDING_CONFLICT")
row = dict(existing)
connection.commit()
return self.store._receipt(row, duplicate=True)
connection.execute(
"INSERT INTO jobs(job_id,idempotency_key,campaign_id,repository,operation,branch,expected_branch_oid,payload_digest,payload_ref,payload_size,state,side_effect_state,revision,request_json,receipt_json,created_at,updated_at) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
(job_id, key, binding["campaign_id"], binding["repository"], binding["operation"], binding["branch"], binding["expected_branch_oid"], binding["payload_digest"], payload_ref, payload_size, JobState.REQUESTED.value, "NONE", 0, json.dumps({"binding": binding, **durable_request}, sort_keys=True), None, now, now),
)
connection.execute("INSERT INTO idempotency_keys(key,job_id,binding_json,created_at) VALUES(?,?,?,?)", (key, job_id, json.dumps(binding, sort_keys=True), now))
connection.execute("INSERT INTO state_transitions(job_id,from_state,to_state,revision,side_effect_state,created_at) VALUES(?,?,?,?,?,?)", (job_id, None, JobState.REQUESTED.value, 0, "NONE", now))
row = dict(connection.execute("SELECT * FROM jobs WHERE job_id=?", (job_id,)).fetchone())
connection.commit()
except BindingConflict:
raise
except sqlite3.IntegrityError:
row = self.store.read(job_id)
return self.store._receipt(row, duplicate=True)
except sqlite3.Error as exc:
raise ServiceUnavailable("JOB_DB_UNAVAILABLE") from exc
row["request"] = json.loads(row["request_json"])
self.store._mirror(row)
return self.store._receipt(row, duplicate=False)
def lease_job(
self,
job_id: str,
*,
lease_id: str,
lineage_anchor_oid: str,
current_head_oid: str,
generation: int,
lease_expires_at: str,
expected_revision: int,
) -> JobReceipt:
"""Atomically create a branch lease and move its Job to LEASED."""
if generation < 1 or any(len(oid) != 40 for oid in (lineage_anchor_oid, current_head_oid)):
raise JobError("LEASE_BINDING_INVALID")
try:
with self.store._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
row = connection.execute("SELECT * FROM jobs WHERE job_id=?", (job_id,)).fetchone()
if row is None:
raise JobError("JOB_NOT_FOUND")
if int(row["revision"]) != expected_revision:
raise JobError("REVISION_CAS_CONFLICT")
if row["branch"] in {"main", "master"}:
raise JobError("PROTECTED_BRANCH_REJECTED")
now = utc_now()
connection.execute("INSERT INTO branch_leases(lease_id,job_id,branch,lineage_anchor_oid,current_head_oid,generation,lease_expires_at,state,revision) VALUES(?,?,?,?,?,?,?,?,?)", (lease_id, job_id, row["branch"], lineage_anchor_oid, current_head_oid, generation, lease_expires_at, "ACTIVE", 0))
next_revision = expected_revision + 1
updated = connection.execute("UPDATE jobs SET state=?,side_effect_state=?,revision=?,updated_at=? WHERE job_id=? AND revision=?", (JobState.LEASED.value, "LEASED", next_revision, now, job_id, expected_revision))
if updated.rowcount != 1:
raise JobError("REVISION_CAS_CONFLICT")
connection.execute("INSERT INTO state_transitions(job_id,from_state,to_state,revision,side_effect_state,created_at) VALUES(?,?,?,?,?,?)", (job_id, row["state"], JobState.LEASED.value, next_revision, "LEASED", now))
result = dict(connection.execute("SELECT * FROM jobs WHERE job_id=?", (job_id,)).fetchone())
connection.commit()
except sqlite3.IntegrityError as exc:
raise JobError("LEASE_CONFLICT") from exc
except (JobError, BindingConflict):
raise
except sqlite3.Error as exc:
raise ServiceUnavailable("JOB_DB_UNAVAILABLE") from exc
result["request"] = json.loads(result["request_json"])
self.store._mirror(result)
return self.store._receipt(result, duplicate=True)
def update_lease(self, lease_id: str, *, expected_revision: int, current_head_oid: str, generation: int, state: str = "ACTIVE") -> None:
if len(current_head_oid) != 40 or generation < 1:
raise JobError("LEASE_BINDING_INVALID")
try:
with self.store._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
updated = connection.execute("UPDATE branch_leases SET current_head_oid=?,generation=?,state=?,revision=revision+1 WHERE lease_id=? AND revision=?", (current_head_oid, generation, state, lease_id, expected_revision))
if updated.rowcount != 1:
raise JobError("LEASE_REVISION_CAS_CONFLICT")
connection.commit()
except JobError:
raise
except sqlite3.Error as exc:
raise ServiceUnavailable("JOB_DB_UNAVAILABLE") from exc
def advance(self, job_id: str, state: JobState, *, side_effect_state: str, receipt: Mapping[str, Any] | None = None, expected_revision: int | None = None) -> JobReceipt:
return self._advance(
job_id,
state,
side_effect_state=side_effect_state,
receipt=receipt,
expected_revision=expected_revision,
)
def _advance(
self,
job_id: str,
state: JobState,
*,
side_effect_state: str,
receipt: Mapping[str, Any] | None = None,
expected_revision: int | None = None,
_allow_verified_external_recovery: bool = False,
) -> JobReceipt:
try:
with self.store._connect() as connection:
connection.execute("BEGIN IMMEDIATE")
row = connection.execute("SELECT * FROM jobs WHERE job_id=?", (job_id,)).fetchone()
if row is None:
raise JobError("JOB_NOT_FOUND")
previous = JobState(row["state"])
revision = int(row["revision"])
if expected_revision is not None and expected_revision != revision:
raise JobError("REVISION_CAS_CONFLICT")
recovery_transition = (
_allow_verified_external_recovery
and previous == JobState.MANUAL_OWNER_REVIEW_REQUIRED
and state == JobState.COMPLETED
)
if previous in _TERMINAL and state != previous and not recovery_transition:
raise JobError("JOB_TERMINAL")
if state not in _RECOVERABLE | {JobState.RECONCILING, JobState.MANUAL_OWNER_REVIEW_REQUIRED}:
if previous in _RECOVERABLE:
raise JobError("RECONCILE_REQUIRED")
if previous != JobState.RECONCILING and _ORDER.get(state, -1) < _ORDER.get(previous, -1):
raise JobError("JOB_STATE_REGRESSION")
next_revision = revision + 1
receipt_json = json.dumps(dict(receipt), sort_keys=True) if receipt is not None else row["receipt_json"]
now = utc_now()
updated = connection.execute("UPDATE jobs SET state=?,side_effect_state=?,revision=?,receipt_json=?,updated_at=? WHERE job_id=? AND revision=?", (state.value, side_effect_state, next_revision, receipt_json, now, job_id, revision))
if updated.rowcount != 1:
raise JobError("REVISION_CAS_CONFLICT")
connection.execute("INSERT INTO state_transitions(job_id,from_state,to_state,revision,side_effect_state,created_at) VALUES(?,?,?,?,?,?)", (job_id, previous.value, state.value, next_revision, side_effect_state, now))
if receipt is not None:
digest = hashlib.sha256(canonical_json(receipt)).hexdigest()
connection.execute("INSERT INTO receipts(receipt_id,job_id,kind,digest,payload_json,created_at) VALUES(?,?,?,?,?,?)", (f"receipt-{job_id}-{next_revision}", job_id, state.value, digest, json.dumps(dict(receipt), sort_keys=True), now))
result = dict(connection.execute("SELECT * FROM jobs WHERE job_id=?", (job_id,)).fetchone())
connection.commit()
except (JobError, BindingConflict):
raise
except sqlite3.Error as exc:
raise ServiceUnavailable("JOB_DB_UNAVAILABLE") from exc
result["request"] = json.loads(result["request_json"])
self.store._mirror(result)
return self.store._receipt(result, duplicate=True)
@staticmethod
def _validate_verified_external_merge_proof(
job_id: str,
binding: Mapping[str, Any],
proof: Mapping[str, Any],
) -> dict[str, Any]:
if not isinstance(proof, Mapping):
raise JobError("EXTERNAL_SIDE_EFFECT_PROOF_INVALID")
def require_oid(field: str) -> str:
value = proof.get(field)
if not isinstance(value, str) or len(value) != 40 or any(ch not in "0123456789abcdef" for ch in value):
raise JobError("EXTERNAL_SIDE_EFFECT_PROOF_INVALID")
return value
if proof.get("proof_schema") != "aios.job-recovery.v1":
raise JobError("EXTERNAL_SIDE_EFFECT_PROOF_INVALID")
if proof.get("job_id") != job_id:
raise JobError("EXTERNAL_SIDE_EFFECT_PROOF_MISMATCH")
if proof.get("operation") != "PROTECTED_MERGE" or binding.get("operation") != "PROTECTED_MERGE":
raise JobError("EXTERNAL_SIDE_EFFECT_OPERATION_REJECTED")
if proof.get("pr_number") != 53 or str(binding.get("pr_number")) != "53":
raise JobError("EXTERNAL_SIDE_EFFECT_PROOF_MISMATCH")
if proof.get("pr_merged") is not True or proof.get("protected_merge") is not True:
raise JobError("EXTERNAL_SIDE_EFFECT_PROOF_MISMATCH")
if proof.get("force_push") is not False:
raise JobError("EXTERNAL_SIDE_EFFECT_PROOF_MISMATCH")
if proof.get("machine_actor") != "aios-autonomy-agent":
raise JobError("EXTERNAL_SIDE_EFFECT_PROOF_MISMATCH")
expected_head = str(binding.get("expected_branch_oid", ""))
expected_base = str(binding.get("expected_main_oid", ""))
head_oid = require_oid("head_oid")
base_oid = require_oid("base_oid")
merged_main = require_oid("main_oid_after_protected_merge")
gitea_merged_main = require_oid("gitea_main_oid_after_protected_merge")
authority_merged_main = require_oid("authority_main_oid_after_protected_merge")
current_gitea = require_oid("current_gitea_main_oid")
current_authority = require_oid("current_authority_main_oid")
if head_oid != expected_head or base_oid != expected_base:
raise JobError("EXTERNAL_SIDE_EFFECT_PROOF_MISMATCH")
if not (merged_main == gitea_merged_main == authority_merged_main == head_oid):
raise JobError("EXTERNAL_SIDE_EFFECT_PROOF_MISMATCH")
if current_gitea != current_authority or proof.get("current_main_descends_from_protected_merge") is not True:
raise JobError("EXTERNAL_SIDE_EFFECT_PROOF_MISMATCH")
return dict(proof)
def complete_verified_external_side_effect(
self,
job_id: str,
proof: Mapping[str, Any],
*,
expected_revision: int | None = None,
) -> JobReceipt:
"""Close one historical manual job from independently verified proof.
This is deliberately narrower than advance: it is only valid for the
known protected PR53 recovery shape and preserves the old receipt.
The actual state change still uses the normal revision-CAS path.
"""
current = self.store.read(job_id)
revision = int(current["revision"])
if expected_revision is not None and expected_revision != revision:
raise JobError("REVISION_CAS_CONFLICT")
request = json.loads(current["request_json"])
binding = request.get("binding", request)
normalized_proof = self._validate_verified_external_merge_proof(job_id, binding, proof)
existing_receipt = json.loads(current["receipt_json"]) if current["receipt_json"] else None
if JobState(current["state"]) == JobState.COMPLETED:
if (
existing_receipt
and existing_receipt.get("recovered_from_historical_manual") is True
and existing_receipt.get("proof") == normalized_proof
):
return self.store._receipt(current, duplicate=True)
raise JobError("JOB_TERMINAL")
if JobState(current["state"]) != JobState.MANUAL_OWNER_REVIEW_REQUIRED:
raise JobError("EXTERNAL_SIDE_EFFECT_RECOVERY_STATE_INVALID")
recovery_receipt = {
"recovered_from_historical_manual": True,
"recovery_kind": "VERIFIED_EXTERNAL_SIDE_EFFECT",
"historical_receipt": existing_receipt,
"proof": normalized_proof,
}
return self._advance(
job_id,
JobState.COMPLETED,
side_effect_state="REMOTE_VERIFIED",
receipt=recovery_receipt,
expected_revision=revision,
_allow_verified_external_recovery=True,
)
def reconcile(self, job_id: str, observer: Callable[[Mapping[str, Any]], Mapping[str, Any]]) -> JobReceipt:
current = self.store.read(job_id)
if JobState(current["state"]) not in _RECOVERABLE:
return self.store._receipt(current, duplicate=True)
self.advance(job_id, JobState.RECONCILING, side_effect_state="IN_PROGRESS")
try:
observed = dict(observer(current))
stable = JobState(observed.get("state", JobState.REQUESTED.value))
if stable in {JobState.RECOVERABLE_FAILURE, JobState.RECONCILING}:
raise JobError("RECONCILIATION_UNSTABLE")
return self.advance(job_id, stable, side_effect_state=str(observed.get("side_effect_state", "NONE")), receipt=observed.get("receipt"))
except Exception as exc:
self.advance(job_id, JobState.RECONCILIATION_REQUIRED, side_effect_state="UNKNOWN", receipt={"error_code": type(exc).__name__})
raise JobError("RECONCILIATION_REQUIRED") from exc
def resume(self, job_id: str) -> JobReceipt:
current = self.store.read(job_id)
if JobState(current["state"]) in _TERMINAL:
raise JobError("JOB_TERMINAL")
return self.reconcile(job_id, lambda _current: {"state": JobState.REQUESTED.value, "side_effect_state": "RESUME_SAME_JOB"})
@@ -0,0 +1,279 @@
"""Durable Job side-effect executor for the AIOS campaign control plane."""
from __future__ import annotations
import argparse
import base64
from dataclasses import dataclass, replace
import json
from pathlib import Path
from typing import Any, Mapping
from aios_authority_git_executor import AuthorityGitExecutor, AuthorityGitError
from aios_campaign_recovery import RecoveryEvidenceError, load_recovered_authorization
from aios_gitea_machine_broker import CampaignCapabilityReceipt, GiteaMachineBroker, MachineBrokerError
from aios_gitops_job_controller import GitOpsJobController, JobError, JobState, JobStore
TASK_ID = "AIOS_AUTONOMOUS_RELEASE_CAMPAIGN_V1"
CAMPAIGN_ID = "AIOS-AUTONOMOUS-RELEASE-CAMPAIGN-20260807"
REPOSITORY = "aios/aios"
BRANCH = "agent/autonomous-release-campaign-v1-20260807"
EXPECTED_BRANCH_OID = "bba675fab3b7e0c5fba7066720252b3c91b9f20d"
AUTHORIZATION_DIGEST = "6b0bf550eb702126e967eeff2d561fa808444b924d8a71f5399accad508ed3b2"
LINEAGE_ANCHOR = "69b45e81dcd2c3707c367003b08c55fd361faae8"
class JobExecutionError(RuntimeError):
pass
@dataclass(slots=True)
class DurableJobExecutor:
controller: GitOpsJobController
authority: AuthorityGitExecutor
broker: GiteaMachineBroker
recovery_path: Path
def _request(self, job_id: str) -> tuple[dict[str, Any], dict[str, Any]]:
row = self.controller.store.read(job_id)
try:
request = json.loads(row["request_json"])
except (KeyError, TypeError, json.JSONDecodeError) as exc:
raise JobExecutionError("JOB_REQUEST_INVALID") from exc
return row, dict(request.get("binding") or {})
def _validate_campaign(self, request: Mapping[str, Any]) -> str:
operation = str(request.get("operation", ""))
expected_branch_oid = request.get("expected_branch_oid")
required = {
"task_id": TASK_ID,
"campaign_id": CAMPAIGN_ID,
"repository": REPOSITORY,
"branch": BRANCH,
"authorization_recovery_digest": AUTHORIZATION_DIGEST,
}
for key, expected in required.items():
if request.get(key) != expected:
raise JobExecutionError(f"JOB_{key.upper()}_MISMATCH")
new_oid = request.get("new_branch_oid")
if not isinstance(new_oid, str) or len(new_oid) != 40 or any(ch not in "0123456789abcdef" for ch in new_oid):
raise JobExecutionError("NEW_BRANCH_OID_INVALID")
if not isinstance(expected_branch_oid, str) or len(expected_branch_oid) != 40 or any(ch not in "0123456789abcdef" for ch in expected_branch_oid):
raise JobExecutionError("EXPECTED_BRANCH_OID_INVALID")
if operation == "ADVANCE_EXISTING_CAMPAIGN_BRANCH" and expected_branch_oid == new_oid:
raise JobExecutionError("EXPECTED_BRANCH_OID_INVALID")
if operation != "ADVANCE_EXISTING_CAMPAIGN_BRANCH" and expected_branch_oid != new_oid:
raise JobExecutionError("POST_ADVANCE_HEAD_BINDING_INVALID")
try:
recovery = load_recovered_authorization(self.recovery_path)
except RecoveryEvidenceError as exc:
raise JobExecutionError(str(exc)) from exc
if recovery["digest"] != AUTHORIZATION_DIGEST:
raise JobExecutionError("RECOVERY_DIGEST_MISMATCH")
for key, expected in required.items():
if key == "campaign_id":
actual = recovery.get(key)
elif key == "authorization_recovery_digest":
continue
else:
actual = recovery.get({"expected_branch_oid": "expected_branch_oid"}.get(key, key))
if actual is not None and actual != expected:
raise JobExecutionError("RECOVERY_BINDING_MISMATCH")
if recovery.get("expected_branch_oid") != EXPECTED_BRANCH_OID:
raise JobExecutionError("RECOVERY_BRANCH_OID_MISMATCH")
authority_head = self.authority.read_ref(f"refs/heads/{BRANCH}")
if authority_head not in {expected_branch_oid, new_oid}:
raise JobExecutionError("AUTHORITY_BRANCH_EXPECTATION_MISMATCH")
if not self.authority.ancestry_check(expected_branch_oid, new_oid):
raise JobExecutionError("NEW_OID_NOT_DESCENDANT")
if not self.authority.ancestry_check(EXPECTED_BRANCH_OID, new_oid):
raise JobExecutionError("NEW_OID_OUTSIDE_AUTHORIZED_LINEAGE")
identity = self.broker.verify_identity()
if identity.get("repository") != REPOSITORY or identity.get("login") == "aiman":
raise JobExecutionError("MACHINE_IDENTITY_SCOPE_INVALID")
return new_oid
def dry_run(self, job_id: str) -> dict[str, Any]:
_row, request = self._request(job_id)
new_oid = self._validate_campaign(request)
remote = self.broker.readback()
if remote is None:
action = "CREATE_REQUIRED"
elif remote == new_oid:
action = "ALREADY_IN_SYNC"
elif remote == request["expected_branch_oid"]:
action = "FAST_FORWARD_REQUIRED"
else:
action = "MANUAL_OWNER_REVIEW_REQUIRED"
if action == "MANUAL_OWNER_REVIEW_REQUIRED":
raise JobExecutionError("GITEA_BRANCH_CONFLICT")
return {
"decision": "SAFE_TO_ADVANCE",
"authority_action": "CAS_REQUIRED",
"gitea_action": action,
"previous_branch_oid": request["expected_branch_oid"],
"final_branch_oid": new_oid,
"force_push": False,
}
def _recover_requested(self, job_id: str) -> dict[str, Any]:
row = self.controller.store.read(job_id)
if JobState(row["state"]) in {JobState.RECOVERABLE_FAILURE, JobState.RECONCILIATION_REQUIRED}:
self.controller.resume(job_id)
row = self.controller.store.read(job_id)
return row
def execute(self, job_id: str, *, dry_run: bool = False) -> dict[str, Any]:
try:
return self._execute_unprotected(job_id, dry_run=dry_run)
except Exception as exc:
try:
row = self.controller.store.read(job_id)
state = JobState(row["state"])
if state not in {JobState.COMPLETED, JobState.MANUAL_OWNER_REVIEW_REQUIRED, JobState.RECOVERABLE_FAILURE, JobState.RECONCILIATION_REQUIRED}:
self.controller.advance(job_id, JobState.RECOVERABLE_FAILURE, side_effect_state="UNKNOWN", receipt={"error_code": type(exc).__name__})
except Exception:
pass
raise
def _execute_unprotected(self, job_id: str, *, dry_run: bool = False) -> dict[str, Any]:
row, request = self._request(job_id)
operation = request.get("operation")
if operation == "ADVANCE_EXISTING_CAMPAIGN_BRANCH":
if dry_run:
return self.dry_run(job_id)
return self._advance_campaign(job_id)
if operation == "COMMIT_FILE":
return self._commit_file(job_id, request)
if operation == "SYNC_TASK_BRANCH_TO_GITEA":
new_oid = self._validate_campaign(request)
final = self.broker.push_branch(new_oid)
return {"operation": operation, "remote_verified": final == new_oid, "final_network_oid": final}
if operation == "CREATE_PR":
self._validate_campaign(request)
return dict(self.broker.create_pr("AIOS autonomous release control plane", "Durable campaign continuation; exact head and recovery evidence are bound in the Job."))
if operation == "PUBLISH_CHECK":
self._validate_campaign(request)
context = request.get("check_context", "aios/durable-release")
state = request.get("check_state", "success")
description = request.get("check_description", "Durable campaign control-plane check passed")
return dict(self.broker.status(str(state), str(description), str(context)))
if operation == "PROTECTED_MERGE":
self._validate_campaign(request)
number = int(request.get("pr_number", "0"))
self.broker.transport.expected_merge_head = request["expected_branch_oid"]
expected_main = request.get("expected_main_oid")
if expected_main is not None:
self.broker.transport.expected_merge_base = expected_main
return dict(self.broker.merge(number))
if operation == "RECONCILE_AUTHORITY_MAIN":
self._validate_campaign(request)
old = request.get("expected_main_oid")
new = request.get("new_branch_oid")
if not isinstance(old, str) or not isinstance(new, str):
raise JobExecutionError("MAIN_RECONCILIATION_BINDING_INVALID")
gitea_main = self.broker.transport.read_branch("main")
if gitea_main != new:
raise JobExecutionError("GITEA_MAIN_EXPECTATION_MISMATCH")
if not self.authority.ancestry_check(old, new):
raise JobExecutionError("MAIN_OID_NOT_DESCENDANT")
return {"authority_main": self.authority.ff_CAS_main_after_verified_protected_merge(old, new)}
raise JobExecutionError("OPERATION_NOT_ALLOWED")
def _advance_campaign(self, job_id: str) -> dict[str, Any]:
self._recover_requested(job_id)
row, request = self._request(job_id)
new_oid = self._validate_campaign(request)
expected_oid = request["expected_branch_oid"]
state = JobState(row["state"])
if state == JobState.REQUESTED:
receipt = self.controller.advance(job_id, JobState.AUTHORIZED, side_effect_state="AUTHORIZED", receipt={"authorization_digest": AUTHORIZATION_DIGEST, "task_id": TASK_ID})
row = self.controller.store.read(job_id)
if JobState(row["state"]) == JobState.AUTHORIZED:
self.controller.lease_job(job_id, lease_id=f"lease-{job_id}", lineage_anchor_oid=LINEAGE_ANCHOR, current_head_oid=expected_oid, generation=1, lease_expires_at="2099-01-01T00:00:00Z", expected_revision=int(row["revision"]))
row = self.controller.store.read(job_id)
if JobState(row["state"]) == JobState.LEASED:
self.controller.advance(job_id, JobState.WORKTREE_READY, side_effect_state="WORKTREE_READY", receipt={"branch": BRANCH, "expected_oid": expected_oid})
row = self.controller.store.read(job_id)
if JobState(row["state"]) == JobState.WORKTREE_READY:
self.controller.advance(job_id, JobState.EXECUTING, side_effect_state="IN_PROGRESS")
row = self.controller.store.read(job_id)
authority_before = self.authority.read_ref(f"refs/heads/{BRANCH}")
if authority_before == expected_oid:
authority_oid = self.authority.advance_task_branch_CAS(f"refs/heads/{BRANCH}", expected_oid, new_oid)
elif authority_before == new_oid:
authority_oid = new_oid
else:
raise JobExecutionError("AUTHORITY_BRANCH_CONFLICT")
row = self.controller.store.read(job_id)
if JobState(row["state"]) == JobState.EXECUTING:
self.controller.advance(job_id, JobState.LOCAL_VERIFIED, side_effect_state="LOCAL_VERIFIED", receipt={"authority": {"branch": BRANCH, "previous_oid": expected_oid, "new_oid": new_oid, "CAS_success": True, "readback_oid": authority_oid}})
row = self.controller.store.read(job_id)
remote_before = self.broker.readback()
if remote_before != new_oid:
if remote_before not in {None, expected_oid}:
raise JobExecutionError("GITEA_BRANCH_CONFLICT")
if JobState(row["state"]) == JobState.LOCAL_VERIFIED:
if hasattr(self.broker, "receipt"):
self.broker.receipt = replace(self.broker.receipt, expected_oid=expected_oid)
self.controller.advance(job_id, JobState.REMOTE_SYNCING, side_effect_state="IN_PROGRESS", receipt={"gitea": {"previous_oid": remote_before, "desired_oid": new_oid, "force_push": False}})
final_network = self.broker.push_branch(new_oid)
else:
final_network = new_oid
final_api = self.broker.readback()
if final_api != new_oid:
raise JobExecutionError("GITEA_BRANCH_READBACK_MISMATCH")
row = self.controller.store.read(job_id)
if JobState(row["state"]) in {JobState.LOCAL_VERIFIED, JobState.REMOTE_SYNCING}:
self.controller.advance(job_id, JobState.REMOTE_VERIFIED, side_effect_state="REMOTE_VERIFIED", receipt={"gitea": {"machine_actor": "aios-autonomy-agent", "previous_oid": remote_before, "desired_oid": new_oid, "final_network_oid": final_network, "final_API_oid": final_api, "force_push": False}})
row = self.controller.store.read(job_id)
if JobState(row["state"]) == JobState.REMOTE_VERIFIED:
self.controller.advance(job_id, JobState.COMPLETED, side_effect_state="REMOTE_VERIFIED", receipt={"authority_oid": authority_oid, "network_oid": final_network, "gitea_api_oid": final_api, "force_push": False})
return self.controller.store._receipt(self.controller.store.read(job_id), duplicate=True).as_dict()
def _commit_file(self, job_id: str, request: Mapping[str, Any]) -> dict[str, Any]:
row = self.controller.store.read(job_id)
payload_ref = request.get("payload_ref")
if not payload_ref:
raise JobExecutionError("PAYLOAD_REQUIRED")
payload = self.controller.store.payloads.read_verified(payload_ref, row["payload_digest"], int(request["payload_size"]))
try:
spec = json.loads(payload.decode())
path = str(spec["path"])
content = base64.b64decode(spec["content_base64"], validate=True)
except Exception as exc:
raise JobExecutionError("COMMIT_FILE_PAYLOAD_INVALID") from exc
new_oid = self.authority.commit_file_in_isolated_worktree(base_oid=row["expected_branch_oid"], files={path: content}, message="chore(gitops): durable commit-file", worktree_name=job_id)
return {"operation": "COMMIT_FILE", "new_oid": new_oid}
def build_from_environment(job_root: Path) -> DurableJobExecutor:
from aios_gitea_machine_transport import GiteaMachineTransport
from aios_gitea_machine_broker import MachineCredentialProjection
account = __import__("os").environ.get("AIOS_GITEA_MACHINE_ACCOUNT", "aios-autonomy-agent")
api = __import__("os").environ.get("AIOS_GITEA_API", "https://g.cnnetmkt.top/git/api/v1")
remote = __import__("os").environ.get("AIOS_GITEA_REMOTE", "https://g.cnnetmkt.top/git/aios/aios.git")
authority_dir = Path(__import__("os").environ.get("AIOS_GITOPS_AUTHORITY_GIT_DIR", "/data/aios-gitops/repo.git"))
worktree_root = Path(__import__("os").environ.get("AIOS_GITOPS_WORKTREE_ROOT", "/var/lib/aios-gitops/worktrees"))
recovery = Path(__import__("os").environ.get("AIOS_RELEASE_RECOVERY_RECEIPT", "/var/lib/aios-gitops/release-campaigns/AIOS-AUTONOMOUS-RELEASE-CAMPAIGN-20260807/authorization-recovery.json"))
transport = GiteaMachineTransport(api, REPOSITORY, remote, authority_dir)
credential = MachineCredentialProjection.from_mapping({"scheme": "systemd-credential", "name": "gitea-machine-token", "account": account})
receipt = CampaignCapabilityReceipt(CAMPAIGN_ID, TASK_ID, REPOSITORY, BRANCH, EXPECTED_BRANCH_OID, frozenset({"exact_task_branch_push", "pr_create_update", "status_publish", "protected_merge", "remote_readback"}), frozenset({"direct_main_push", "force_push", "tag_update", "tag_delete", "baseline_write", "production_deploy"}))
return DurableJobExecutor(GitOpsJobController(JobStore(job_root)), AuthorityGitExecutor(authority_dir, worktree_root), GiteaMachineBroker(transport, credential, receipt), recovery)
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--job-root", required=True, type=Path)
parser.add_argument("--job-id", required=True)
parser.add_argument("--dry-run", action="store_true")
args = parser.parse_args(argv)
result = build_from_environment(args.job_root).execute(args.job_id, dry_run=args.dry_run)
print(json.dumps(result, sort_keys=True))
return 0
if __name__ == "__main__":
raise SystemExit(main())
+7 -1
View File
@@ -5,6 +5,8 @@ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from typing import Any
from release_version import validate_release_version
ROOT = Path(os.getenv('AIOS_GITOPS_ROOT', '/data/aios-gitops'))
WORKSPACE = Path(os.getenv('AIOS_GITOPS_WORKSPACE', str(ROOT / 'workspace')))
DB_PATH = Path(os.getenv('AIOS_GITOPS_DB', str(ROOT / 'runtime' / 'gitops_model.sqlite3')))
@@ -218,7 +220,11 @@ class Handler(BaseHTTPRequestHandler):
a = self.require_write(['owner']);
if not a: return
ver=data.get('version') or data.get('tag')
if not re.match(r'^V\d+R\d+C\d+(-[a-zA-Z0-9_.-]+)?$', ver or ''): self.send_json({'error':'version must match VxRxCx'},400); return
try:
validate_release_version(ver)
except ValueError:
self.send_json({'error':'version must match VxRxCx'},400)
return
msg=data.get('message') or f'release {ver}'; out=git_cmd(['tag','-a',ver,'-m',msg]);
if data.get('push',False): git_cmd(['push','origin',ver])
level='V' if re.match(r'^V[1-9]\d*R0C0', ver) else ('R' if re.match(r'^V\d+R[1-9]\d*C0', ver) else 'C'); exec_sql('INSERT OR REPLACE INTO gitops_versions(version,tag,level,summary,status,created_by,created_at) VALUES(?,?,?,?,?,?,?)',(ver,ver,level,msg,'ready',a['username'],now())); exec_sql('UPDATE gitops_baseline SET active_version=?,active_tag=?,updated_at=? WHERE id=?',(ver,ver,now(),'current')); audit(a['username'],'git_tag',ver,{'level':level}); self.send_json({'status':'tagged','version':ver,'git':out}); return
@@ -0,0 +1,80 @@
"""Pure, observation-first decisions for governed GitOps reconciliation."""
from __future__ import annotations
from dataclasses import dataclass
from enum import Enum
import re
from typing import Optional
OID_RE = re.compile(r"^[0-9a-f]{40}$")
class ReconciliationAction(str, Enum):
NOOP = "NOOP"
PUSH_EXACT = "PUSH_EXACT"
VERIFY_REMOTE = "VERIFY_REMOTE"
MARK_REMOTE_VERIFIED = "MARK_REMOTE_VERIFIED"
MARK_RECONCILIATION_REQUIRED = "MARK_RECONCILIATION_REQUIRED"
FAIL_CONFLICT = "FAIL_CONFLICT"
@dataclass(frozen=True, slots=True)
class RemoteObservation:
repository_identity: str
branch: str
desired_oid: str
authority_oid: Optional[str]
remote_git_oid: Optional[str]
remote_api_oid: Optional[str]
commit_visible: bool
observed_at: str
expected_oid: Optional[str] = None
@property
def all_equal(self) -> bool:
values = (self.authority_oid, self.remote_git_oid, self.remote_api_oid, self.desired_oid)
return all(isinstance(value, str) for value in values) and len(set(values)) == 1
@dataclass(frozen=True, slots=True)
class ReconciliationDecision:
action: ReconciliationAction
state: str
reason: str
retry_safe: bool = False
def _valid_oid(value: Optional[str]) -> bool:
return value is None or bool(OID_RE.fullmatch(value))
def decide_reconciliation(observation: RemoteObservation) -> ReconciliationDecision:
"""Decide from a fresh observation; never complete on a missing remote."""
if observation.repository_identity != "aios/aios":
return ReconciliationDecision(ReconciliationAction.FAIL_CONFLICT, "CONFLICT", "REPOSITORY_IDENTITY_MISMATCH")
if not observation.branch.startswith("agent/"):
return ReconciliationDecision(ReconciliationAction.FAIL_CONFLICT, "CONFLICT", "BRANCH_SCOPE_DENIED")
if not _valid_oid(observation.desired_oid) or not _valid_oid(observation.expected_oid):
return ReconciliationDecision(ReconciliationAction.FAIL_CONFLICT, "CONFLICT", "OID_INVALID")
if observation.authority_oid is None:
return ReconciliationDecision(ReconciliationAction.MARK_RECONCILIATION_REQUIRED, "RECONCILIATION_REQUIRED", "AUTHORITY_UNAVAILABLE")
if observation.authority_oid != observation.desired_oid:
return ReconciliationDecision(ReconciliationAction.MARK_RECONCILIATION_REQUIRED, "RECONCILIATION_REQUIRED", "AUTHORITY_CONFLICT")
present = [oid for oid in (observation.remote_git_oid, observation.remote_api_oid) if oid is not None]
if any(oid not in {observation.desired_oid, observation.expected_oid} for oid in present):
return ReconciliationDecision(ReconciliationAction.FAIL_CONFLICT, "CONFLICT", "REMOTE_OID_CONFLICT")
if observation.all_equal and observation.commit_visible:
return ReconciliationDecision(ReconciliationAction.MARK_REMOTE_VERIFIED, "REMOTE_VERIFIED", "ALL_OIDS_EQUAL_AND_VISIBLE")
if observation.remote_git_oid is None and observation.remote_api_oid is None:
return ReconciliationDecision(ReconciliationAction.MARK_RECONCILIATION_REQUIRED, "REMOTE_PUSH_PENDING", "AUTHORITY_DESIRED_REMOTE_MISSING")
if observation.remote_git_oid == observation.desired_oid or observation.remote_api_oid == observation.desired_oid:
return ReconciliationDecision(ReconciliationAction.VERIFY_REMOTE, "REMOTE_VERIFY_PENDING", "REMOTE_PROOF_PARTIAL")
if observation.expected_oid is not None and observation.remote_git_oid == observation.expected_oid and observation.remote_api_oid == observation.expected_oid:
return ReconciliationDecision(ReconciliationAction.PUSH_EXACT, "REMOTE_PUSH_PENDING", "SAFE_EXACT_FAST_FORWARD_RETRY", retry_safe=True)
if not observation.commit_visible:
return ReconciliationDecision(ReconciliationAction.VERIFY_REMOTE, "COMMIT_VISIBILITY_PENDING", "COMMIT_VISIBILITY_LAG")
return ReconciliationDecision(ReconciliationAction.MARK_RECONCILIATION_REQUIRED, "RECONCILIATION_REQUIRED", "PARTIAL_REMOTE_OBSERVATION")
def reconcile_observation(**kwargs: object) -> ReconciliationDecision:
return decide_reconciliation(RemoteObservation(**kwargs))
+100
View File
@@ -0,0 +1,100 @@
"""Single supervised socket-activated worker for durable GitOps Jobs.
The worker consumes durable requests and reconciliation queries. Repository
mutation is a separate injected executor and remains impossible while the
legacy global write gate is false.
"""
from __future__ import annotations
import argparse
import json
import os
from pathlib import Path
import signal
import socket
import threading
from typing import Any, Mapping
from aios_gitops_job_controller import GitOpsJobController, JobError, JobState, JobStore, ServiceUnavailable
def runtime_policy(environment: Mapping[str, str] | None = None) -> dict[str, bool]:
env = environment or os.environ
legacy = env.get("AIOS_GITOPS_WRITES_ENABLED", "false").lower() in {"1", "true", "yes", "on"}
durable = env.get("AIOS_GITOPS_DURABLE_JOB_EXECUTION_ENABLED", "false").lower() in {"1", "true", "yes", "on"}
# Legacy direct Git mutation remains disabled. Authorized durable Jobs use
# their own campaign/task capability and may mutate only through the
# worker's lease/CAS path while the legacy gate stays false.
return {"legacy_global_write": legacy, "durable_job_execution": durable, "repository_mutation_allowed": durable}
def handle_request(controller: GitOpsJobController, request: Mapping[str, Any], executor: Any | None = None) -> dict[str, Any]:
job_id = str(request.get("job_id") or "")
if not job_id:
raise JobError("JOB_ID_REQUIRED")
if request.get("action") == "execute":
if executor is None:
raise JobError("JOB_EXECUTOR_UNAVAILABLE")
result = executor.execute(job_id, dry_run=bool(request.get("dry_run", False)))
receipt = controller.store._receipt(controller.store.read(job_id), duplicate=True)
return {"job": result, **receipt.as_dict()}
if request.get("action") == "resume":
receipt = controller.resume(job_id)
else:
row = controller.store.read(job_id)
receipt = controller.store._receipt(row, duplicate=True)
return receipt.as_dict()
def serve_socket(sock: socket.socket, controller: GitOpsJobController, stopping: threading.Event, executor: Any | None = None) -> None:
sock.listen(32)
sock.settimeout(1.0)
while not stopping.is_set():
try:
connection, _ = sock.accept()
except socket.timeout:
continue
with connection:
try:
raw = connection.makefile("rb").readline(1_048_576)
request = json.loads(raw.decode("utf-8"))
response = {"status": "ok", **handle_request(controller, request, executor)}
except ServiceUnavailable as exc:
response = {"status": "error", "error": str(exc), "retryable": True, "side_effect_state": "UNKNOWN"}
except Exception as exc:
response = {"status": "error", "error": type(exc).__name__, "retryable": False}
connection.sendall((json.dumps(response, sort_keys=True) + "\n").encode("utf-8"))
def socket_from_systemd() -> socket.socket:
if os.environ.get("LISTEN_FDS") != "1":
raise RuntimeError("SOCKET_ACTIVATION_REQUIRED")
return socket.fromfd(3, socket.AF_UNIX, socket.SOCK_STREAM)
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--job-root", required=True, type=Path)
args = parser.parse_args(argv)
policy = runtime_policy()
if policy["legacy_global_write"]:
raise RuntimeError("LEGACY_GLOBAL_WRITES_MUST_REMAIN_FALSE")
if not policy["durable_job_execution"]:
raise RuntimeError("DURABLE_JOB_EXECUTION_GATE_CLOSED")
controller = GitOpsJobController(JobStore(args.job_root))
from aios_gitops_job_executor import build_from_environment
executor = build_from_environment(args.job_root)
stopping = threading.Event()
signal.signal(signal.SIGTERM, lambda *_: stopping.set())
signal.signal(signal.SIGINT, lambda *_: stopping.set())
sock = socket_from_systemd()
try:
serve_socket(sock, controller, stopping, executor)
finally:
sock.close()
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,59 @@
"""Central fail-closed policy seam for governed task and continuation requests."""
from __future__ import annotations
from dataclasses import dataclass
import re
from typing import Any, Mapping
OID_RE = re.compile(r"^[0-9a-f]{40}$")
@dataclass(frozen=True, slots=True)
class GovernedPolicyInput:
identity: str
task_id: str
repository: str
operation: str
base_oid: str
expected_branch_oid: str
target_branch: str
generation: int
risk: str = "engineering"
requested_capabilities: tuple[str, ...] = ()
previous_generation: int | None = None
@dataclass(frozen=True, slots=True)
class PolicyDecision:
decision: str
reason: str
def as_dict(self) -> dict[str, str]:
return {"decision": self.decision, "reason": self.reason}
def evaluate_policy(value: GovernedPolicyInput | Mapping[str, Any]) -> PolicyDecision:
if isinstance(value, Mapping):
value = GovernedPolicyInput(**value)
if value.repository != "aios/aios":
return PolicyDecision("DENY", "REPOSITORY_IDENTITY_MISMATCH")
if not OID_RE.fullmatch(value.base_oid) or not OID_RE.fullmatch(value.expected_branch_oid):
return PolicyDecision("DENY", "OID_INVALID")
if not value.target_branch.startswith("agent/") or value.target_branch in {"main", "master"}:
return PolicyDecision("DENY", "BRANCH_SCOPE_DENIED")
if "force_push" in value.requested_capabilities or "force" in value.operation.lower():
return PolicyDecision("DENY", "FORCE_PUSH_DENIED")
if {"main_write", "tag_write", "baseline_write", "production_write", "production_deploy", "tag_mutation", "baseline_mutation"}.intersection(value.requested_capabilities):
return PolicyDecision("DENY", "MUTATION_SCOPE_DENIED")
if not isinstance(value.generation, int) or isinstance(value.generation, bool) or value.generation < 1:
return PolicyDecision("DENY", "GENERATION_INVALID")
if value.previous_generation is not None and value.generation <= value.previous_generation:
return PolicyDecision("MANUAL_OWNER_REVIEW_REQUIRED", "GENERATION_NOT_MONOTONIC")
if not value.task_id:
return PolicyDecision("DENY", "TASK_ID_INVALID")
return PolicyDecision("ALLOW", "GOVERNED_ENGINEERING_SCOPE")
class GovernedPolicy:
def decide(self, request: GovernedPolicyInput | Mapping[str, Any]) -> PolicyDecision:
return evaluate_policy(request)
@@ -0,0 +1,124 @@
"""Durable governed task binding for CREATE_NEW and CONTINUE_EXISTING."""
from __future__ import annotations
from dataclasses import dataclass, asdict
import hashlib
import json
from pathlib import Path
import re
from typing import Any, Mapping
from aios_gitops_job_controller import GitOpsJobController, JobStore
from aios_online_agent_execution import GovernedExecutionBinding, project_execution_envelope
OID_RE = re.compile(r"^[0-9a-f]{40}$")
BRANCH_RE = re.compile(r"^agent/[A-Za-z0-9][A-Za-z0-9._/-]{2,179}$")
class BootstrapError(RuntimeError):
pass
@dataclass(frozen=True, slots=True)
class GovernedTaskBinding:
task_id: str
repository: str
base_oid: str
target_branch: str
risk: str
description: str
idempotency_key: str
expected_branch_oid: str
branch_mode: str
generation: int
job_id: str | None = None
def response(self, *, idempotent: bool = False) -> dict[str, Any]:
return {"task_id": self.task_id, "repository": self.repository, "base_oid": self.base_oid, "target_branch": self.target_branch, "expected_branch_oid": self.expected_branch_oid, "generation": self.generation, "branch_mode": self.branch_mode, "create_target_branch": self.branch_mode == "CREATE_NEW", "worktree_mode": "detached_at_base_oid" if self.branch_mode == "CREATE_NEW" else "detached_at_expected_head", "cas_ref_update_required": True, "idempotent": idempotent, "state": "BOUND", "job_id": self.job_id}
def _safe(value: str) -> str:
return re.sub(r"[^A-Za-z0-9_.-]+", "-", value).strip("-")[:120] or "task"
class GovernedTaskBootstrap:
def __init__(self, authority: Any, root: Path, *, remote_name: str, token_path: Path, ssh_key_path: Path, api_base: str = "https://g.cnnetmkt.top/git/api/v1") -> None:
self.authority = authority; self.root = Path(root); self.remote_name = remote_name; self.token_path = Path(token_path); self.ssh_key_path = Path(ssh_key_path); self.api_base = api_base; self.task_meta_root = self.root / "task-bindings"; self.job_root = self.root / "jobs"; self.task_meta_root.mkdir(parents=True, exist_ok=True); self.job_root.mkdir(parents=True, exist_ok=True)
def _store(self, task_id: str) -> tuple[JobStore, GitOpsJobController]:
root = self.job_root / _safe(task_id); store = JobStore(root, payload_root=root / "payloads"); return store, GitOpsJobController(store)
def _metadata(self, task_id: str) -> Path:
return self.task_meta_root / (_safe(task_id) + ".json")
def _read_existing(self, task_id: str) -> GovernedTaskBinding | None:
path = self._metadata(task_id)
if not path.is_file() or path.is_symlink(): return None
return GovernedTaskBinding(**json.loads(path.read_text(encoding="utf-8")))
def get_binding(self, task_id: str) -> GovernedTaskBinding:
"""Read the exact durable binding; never synthesize missing lineage."""
binding = self._read_existing(str(task_id))
if binding is None or not binding.job_id:
raise BootstrapError("TASK_EXECUTION_PROJECTION_MISSING")
return binding
def execution_binding(self, binding: GovernedTaskBinding) -> GovernedExecutionBinding:
return GovernedExecutionBinding.from_mapping(
{
"task_id": binding.task_id,
"job_id": binding.job_id,
"repository": binding.repository,
"base_oid": binding.base_oid,
"expected_branch_oid": binding.expected_branch_oid,
"target_branch": binding.target_branch,
"branch_mode": binding.branch_mode,
"generation": binding.generation,
"idempotency_key": binding.idempotency_key,
}
)
def execution_envelope(
self,
task_id: str,
execution_request: Mapping[str, Any] | None = None,
*,
required_capabilities: tuple[str, ...] = (),
expected_job_id: str | None = None,
) -> dict[str, Any]:
binding = self.get_binding(task_id)
if expected_job_id and binding.job_id != expected_job_id:
raise BootstrapError("JOB_BINDING_CONFLICT")
return project_execution_envelope(
self.execution_binding(binding),
execution_request,
required_capabilities=required_capabilities,
)
def _write(self, binding: GovernedTaskBinding) -> None:
path = self._metadata(binding.task_id); temporary = path.with_suffix(".tmp"); temporary.write_text(json.dumps(asdict(binding), sort_keys=True), encoding="utf-8"); temporary.replace(path)
def create(self, request: Mapping[str, Any]) -> dict[str, Any]:
values = {key: str(request[key]) for key in ("task_id", "repository", "base_oid", "target_branch", "risk", "description", "idempotency_key")}
if values["repository"] != "aios/aios" or not OID_RE.fullmatch(values["base_oid"]): raise BootstrapError("TASK_BINDING_INVALID")
if not BRANCH_RE.fullmatch(values["target_branch"]): raise BootstrapError("TARGET_BRANCH_INVALID")
existing = self._read_existing(values["task_id"])
if existing is not None:
if any(getattr(existing, key) != values[key] for key in ("repository", "base_oid", "target_branch", "risk", "description")): raise BootstrapError("TASK_BINDING_CONFLICT")
return existing.response(idempotent=True)
if not self.authority.commit_exists(values["base_oid"]): raise BootstrapError("BASE_COMMIT_NOT_FOUND")
current = self.authority.read_ref("refs/heads/" + values["target_branch"])
expected = str(request.get("expected_branch_oid") or values["base_oid"])
if current is None:
mode, generation = "CREATE_NEW", 1
if expected != values["base_oid"]: raise BootstrapError("EXPECTED_BASE_CONFLICT")
else:
if expected != current: raise BootstrapError("EXISTING_BRANCH_OID_CONFLICT")
mode, generation = "CONTINUE_EXISTING", int(request.get("generation", 2))
if generation < 1: raise BootstrapError("GENERATION_INVALID")
payload = json.dumps(values, sort_keys=True, separators=(",", ":")).encode()
_, controller = self._store(values["task_id"])
receipt = controller.submit({"idempotency_key": values["idempotency_key"], "campaign_id": values["task_id"], "task_id": values["task_id"], "repository": values["repository"], "operation": "GOVERNED_TASK_CREATE", "branch": values["target_branch"], "expected_branch_oid": expected, "payload_digest": hashlib.sha256(payload).hexdigest(), "lineage_anchor_oid": values["base_oid"]}, payload=payload)
binding = GovernedTaskBinding(**values, expected_branch_oid=expected, branch_mode=mode, generation=generation, job_id=receipt.job_id)
self._write(binding)
return binding.response()
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,528 @@
"""Canonical governed execution projection for Online Agent Tasks.
The durable governed binding is authoritative. Natural-language task fields and
worker configuration are never allowed to invent repository, ref, generation, or
idempotency coordinates.
"""
from __future__ import annotations
from dataclasses import asdict, dataclass
import hashlib
import json
import re
from pathlib import Path
from typing import Any, Mapping, Sequence
import urllib.error
import urllib.request
EXECUTION_SCHEMA = "aios.online-agent.execution.v2"
PROJECTION_SCHEMA = "aios.online-agent.task-projection.v1"
OID_RE = re.compile(r"^[0-9a-f]{40}$")
TASK_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{2,179}$")
BRANCH_RE = re.compile(r"^agent/[A-Za-z0-9][A-Za-z0-9._/-]{2,179}$")
KEY_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{7,159}$")
PROTECTED_PREFIXES = ("main", "master", "refs/", "tags/", "tag/", "release/", "baseline/", "production/")
MUTATION_ACTIONS = frozenset({"deterministic"})
READ_ONLY_ACTIONS = frozenset({"source_capture", "agent_runtime"})
SUPPORTED_ACTIONS = frozenset({"deterministic", "source_capture", "agent_runtime"})
AGENT_RUNTIME_ADAPTERS = frozenset({"hermes", "openclaw"})
MAX_AGENT_RUNTIME_INPUT_BYTES = 64 * 1024
class ExecutionProjectionError(RuntimeError):
"""A canonical fail-closed projection or validation error."""
class OnlineTaskProjectionClient:
"""Official HTTP projection client using only a control credential reference."""
def __init__(self, api_base: str, control_token_path: Path) -> None:
self.api_base = api_base.rstrip("/")
self.control_token_path = Path(control_token_path)
def _token(self) -> str:
path = self.control_token_path
if path.is_symlink() or not path.is_file():
_error("ONLINE_TASK_PROJECTION_CONTROL_UNAVAILABLE")
value = path.read_text(encoding="utf-8").strip()
if len(value) < 32:
_error("ONLINE_TASK_PROJECTION_CONTROL_INVALID")
return value
def create(
self,
projected_task: Mapping[str, Any],
*,
binding: "GovernedExecutionBinding",
) -> dict[str, Any]:
body = dict(projected_task)
payload = _canonical(body).encode("utf-8")
request = urllib.request.Request(
self.api_base + "/v1/tasks",
method="POST",
data=payload,
headers={
"Authorization": "Bearer " + self._token(),
"Content-Type": "application/json",
"Accept": "application/json",
},
)
try:
with urllib.request.urlopen(request, timeout=15) as response:
result = json.loads(response.read().decode("utf-8"))
status = response.status
except urllib.error.HTTPError as exc:
raw = exc.read(8192).decode("utf-8", "replace")
try:
error = json.loads(raw).get("error")
except (AttributeError, json.JSONDecodeError):
error = "HTTP_ERROR"
raise ExecutionProjectionError(f"ONLINE_TASK_PROJECTION_FAILED:{exc.code}:{error}") from exc
except (OSError, urllib.error.URLError, TimeoutError) as exc:
raise ExecutionProjectionError("ONLINE_TASK_PROJECTION_UNAVAILABLE") from exc
if status not in {200, 201} or not isinstance(result, dict):
_error("ONLINE_TASK_PROJECTION_RESPONSE_INVALID")
if result.get("task_id") != binding.task_id or result.get("status") not in {"QUEUED", "VERIFYING", "SUCCEEDED"}:
_error("ONLINE_TASK_PROJECTION_RESPONSE_INVALID")
metadata = result.get("metadata")
if not isinstance(metadata, Mapping):
_error("ONLINE_TASK_PROJECTION_RESPONSE_INVALID")
returned_execution = validate_execution_envelope(
_object(metadata.get("execution"), "ONLINE_TASK_PROJECTION_RESPONSE_INVALID"),
binding=binding,
)
submitted_metadata = _object(body.get("metadata"), "ONLINE_TASK_PROJECTION_REQUEST_INVALID")
submitted_execution = validate_execution_envelope(
_object(submitted_metadata.get("execution"), "ONLINE_TASK_PROJECTION_REQUEST_INVALID"),
binding=binding,
)
if _canonical(returned_execution) != _canonical(submitted_execution):
_error("ONLINE_TASK_PROJECTION_RESPONSE_BINDING_INVALID")
return result
def _error(code: str) -> None:
raise ExecutionProjectionError(code)
def _object(value: object, code: str) -> dict[str, Any]:
if not isinstance(value, Mapping):
_error(code)
return dict(value)
def _string(value: object, code: str) -> str:
if not isinstance(value, str) or not value.strip():
_error(code)
return value.strip()
def _task_id(value: object) -> str:
value = _string(value, "TASK_ID_INVALID")
if TASK_RE.fullmatch(value) is None:
_error("TASK_ID_INVALID")
return value
def _oid(value: object, code: str) -> str:
value = _string(value, code)
if OID_RE.fullmatch(value) is None:
_error(code)
return value
def _branch(value: object) -> str:
value = _string(value, "TARGET_BRANCH_INVALID")
if BRANCH_RE.fullmatch(value) is None or value.startswith(PROTECTED_PREFIXES):
_error("TARGET_BRANCH_INVALID")
return value
def _generation(value: object) -> int:
if not isinstance(value, int) or isinstance(value, bool) or value < 1:
_error("GENERATION_INVALID")
return value
def _canonical(value: object) -> str:
return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
def _digest(value: object) -> str:
return hashlib.sha256(_canonical(value).encode("utf-8")).hexdigest()
def _source_signals(value: object) -> tuple[str, ...]:
"""Normalize untrusted natural-language hints before they enter a signed envelope."""
if value is None:
return ()
if not isinstance(value, Sequence) or isinstance(value, (str, bytes)):
_error("SOURCE_CAPTURE_SIGNALS_INVALID")
result: list[str] = []
for item in value:
if not isinstance(item, str):
_error("SOURCE_CAPTURE_SIGNALS_INVALID")
text = " ".join(item.split()).strip()
if not text:
continue
# Projection may accept long task prose, but execution envelopes stay bounded.
text = text[:512].rstrip()
if text not in result:
result.append(text)
if len(result) == 16:
break
return tuple(result)
def _agent_runtime_input(value: object) -> dict[str, Any]:
if not isinstance(value, Mapping):
_error("AGENT_RUNTIME_INPUT_REQUIRED")
raw = dict(value)
allowed = {
"goal", "instructions", "success_criteria", "constraints",
"scope", "trace_id",
}
if set(raw) - allowed:
_error("AGENT_RUNTIME_INPUT_FIELDS_INVALID")
goal = _string(raw.get("goal"), "AGENT_RUNTIME_GOAL_REQUIRED")
instructions = raw.get("instructions", "")
if not isinstance(instructions, str):
_error("AGENT_RUNTIME_INSTRUCTIONS_INVALID")
criteria = raw.get("success_criteria", [])
if not isinstance(criteria, list) or len(criteria) > 32:
_error("AGENT_RUNTIME_SUCCESS_CRITERIA_INVALID")
normalized_criteria: list[dict[str, str]] = []
for item in criteria:
if not isinstance(item, Mapping):
_error("AGENT_RUNTIME_SUCCESS_CRITERIA_INVALID")
criterion_id = _string(item.get("id"), "AGENT_RUNTIME_SUCCESS_CRITERIA_INVALID")
description = _string(
item.get("description"), "AGENT_RUNTIME_SUCCESS_CRITERIA_INVALID"
)
normalized_criteria.append(
{"id": criterion_id[:120], "description": description[:1000]}
)
constraints = raw.get("constraints", {})
if not isinstance(constraints, Mapping):
_error("AGENT_RUNTIME_CONSTRAINTS_INVALID")
sensitive_markers = ("token", "secret", "password", "credential", "authorization", "api_key", "apikey")
for key in constraints:
lowered = str(key).lower()
if any(marker in lowered for marker in sensitive_markers):
_error("AGENT_RUNTIME_SECRET_FIELD_DENY")
scope = raw.get("scope", {})
if not isinstance(scope, Mapping):
_error("AGENT_RUNTIME_SCOPE_INVALID")
scope = {
"scope_type": str(scope.get("scope_type") or "legacy"),
"tenant_id": scope.get("tenant_id"),
"project_id": scope.get("project_id"),
}
if scope["scope_type"] not in {"legacy", "system", "tenant"}:
_error("AGENT_RUNTIME_SCOPE_INVALID")
if scope["scope_type"] == "tenant":
if not isinstance(scope["tenant_id"], str) or not scope["tenant_id"].strip():
_error("AGENT_RUNTIME_SCOPE_INVALID")
elif scope["tenant_id"] not in (None, "") or scope["project_id"] not in (None, ""):
_error("AGENT_RUNTIME_SCOPE_INVALID")
trace_id = str(raw.get("trace_id") or "").strip()
if not trace_id or len(trace_id) > 255:
_error("AGENT_RUNTIME_TRACE_ID_INVALID")
normalized = {
"goal": goal[:12000],
"instructions": instructions[:32000],
"success_criteria": normalized_criteria,
"constraints": dict(constraints),
"scope": scope,
"trace_id": trace_id,
}
if len(_canonical(normalized).encode("utf-8")) > MAX_AGENT_RUNTIME_INPUT_BYTES:
_error("AGENT_RUNTIME_INPUT_TOO_LARGE")
return normalized
def _capabilities(value: object) -> tuple[str, ...]:
if value is None:
return ()
if not isinstance(value, Sequence) or isinstance(value, (str, bytes)):
_error("CAPABILITY_SCOPE_INVALID")
result = tuple(sorted({_string(item, "CAPABILITY_SCOPE_INVALID") for item in value}))
if any(len(item) > 120 for item in result):
_error("CAPABILITY_SCOPE_INVALID")
return result
@dataclass(frozen=True, slots=True)
class GovernedExecutionBinding:
task_id: str
job_id: str
repository: str
base_oid: str
expected_branch_oid: str
target_branch: str
branch_mode: str
generation: int
idempotency_key: str
@classmethod
def from_mapping(cls, value: Mapping[str, Any]) -> "GovernedExecutionBinding":
data = dict(value)
task_id = _task_id(data.get("task_id"))
job_id = _string(data.get("job_id"), "JOB_ID_REQUIRED")
if TASK_RE.fullmatch(job_id) is None:
_error("JOB_ID_INVALID")
repository = _string(data.get("repository"), "REPOSITORY_INVALID")
if repository != "aios/aios":
_error("WRONG_REPOSITORY_ENVELOPE_DENY")
base_oid = _oid(data.get("base_oid"), "BASE_OID_INVALID")
expected = _oid(data.get("expected_branch_oid"), "EXPECTED_BRANCH_OID_INVALID")
target = _branch(data.get("target_branch"))
mode = _string(data.get("branch_mode"), "BRANCH_MODE_MISSING")
if mode not in {"CREATE_NEW", "CONTINUE_EXISTING"}:
_error("BRANCH_MODE_INVALID")
generation = _generation(data.get("generation"))
if mode == "CREATE_NEW" and expected != base_oid:
_error("EXPECTED_BASE_CONFLICT")
key = _string(data.get("idempotency_key"), "IDEMPOTENCY_KEY_INVALID")
if KEY_RE.fullmatch(key) is None:
_error("IDEMPOTENCY_KEY_INVALID")
return cls(task_id, job_id, repository, base_oid, expected, target, mode, generation, key)
def public(self) -> dict[str, Any]:
return asdict(self)
def binding_from_bootstrap(value: Mapping[str, Any]) -> GovernedExecutionBinding:
return GovernedExecutionBinding.from_mapping(value)
def validate_execution_envelope(
value: Mapping[str, Any],
*,
binding: GovernedExecutionBinding | None = None,
worker_capabilities: Sequence[str] | None = None,
) -> dict[str, Any]:
envelope = dict(value)
if envelope.get("schema") != EXECUTION_SCHEMA:
_error("EXECUTION_ENVELOPE_SCHEMA_VALIDATION")
candidate = GovernedExecutionBinding.from_mapping(envelope)
if binding is not None and candidate != binding:
comparisons = (
(candidate.task_id, binding.task_id, "TASK_BINDING_CONFLICT"),
(candidate.job_id, binding.job_id, "JOB_BINDING_CONFLICT"),
(candidate.repository, binding.repository, "WRONG_REPOSITORY_ENVELOPE_DENY"),
(candidate.base_oid, binding.base_oid, "WRONG_BASE_ENVELOPE_DENY"),
(candidate.expected_branch_oid, binding.expected_branch_oid, "WRONG_EXPECTED_HEAD_ENVELOPE_DENY"),
(candidate.target_branch, binding.target_branch, "WRONG_BRANCH_ENVELOPE_DENY"),
(candidate.generation, binding.generation, "WRONG_GENERATION_ENVELOPE_DENY"),
(candidate.branch_mode, binding.branch_mode, "BRANCH_MODE_BINDING_CONFLICT"),
(candidate.idempotency_key, binding.idempotency_key, "IDEMPOTENCY_BINDING_CONFLICT"),
)
for actual, expected, code in comparisons:
if actual != expected:
_error(code)
action = _string(envelope.get("action"), "EXECUTION_ACTION_MISSING")
if action not in SUPPORTED_ACTIONS:
_error("EXECUTION_ACTION_UNSUPPORTED")
capability_scope = _capabilities(envelope.get("capability_scope"))
if worker_capabilities is not None and not set(capability_scope).issubset(set(worker_capabilities)):
_error("CAPABILITY_MISMATCH_DENY")
workspace = _object(envelope.get("workspace"), "WORKSPACE_POLICY_REQUIRED")
if workspace.get("authority_git_dir") != "/data/aios-gitops/repo.git" or workspace.get("read_only_repository") is not True:
_error("WORKSPACE_POLICY_DENY")
governance = _object(envelope.get("governance"), "GOVERNANCE_POLICY_REQUIRED")
governed_coordinates = (
(governance.get("task_id"), candidate.task_id, "TASK_BINDING_CONFLICT"),
(governance.get("job_id"), candidate.job_id, "JOB_BINDING_CONFLICT"),
(governance.get("idempotency_key"), candidate.idempotency_key, "IDEMPOTENCY_BINDING_CONFLICT"),
)
for actual_coordinate, expected_coordinate, code in governed_coordinates:
if actual_coordinate != expected_coordinate:
_error(code)
commit_key = _string(governance.get("commit_idempotency_key"), "COMMIT_IDEMPOTENCY_KEY_INVALID")
if KEY_RE.fullmatch(commit_key) is None:
_error("COMMIT_IDEMPOTENCY_KEY_INVALID")
if governance.get("writer_lease") is not (action in MUTATION_ACTIONS):
_error("WRITER_LEASE_POLICY_DENY")
for field in ("main_write", "production_write", "tag_write", "baseline_write", "force_push", "protected_ref_bypass"):
if governance.get(field, False) is not False:
_error("POLICY_DENY")
changes = envelope.get("change_set")
verification = envelope.get("verification")
if not isinstance(changes, list) or not isinstance(verification, list):
_error("EXECUTION_OPERATIONS_INVALID")
if action == "source_capture":
if changes or verification:
_error("SOURCE_CAPTURE_MUTATION_DENY")
signals = list(_source_signals(envelope.get("source_signals") or []))
if envelope.get("source_signals") != signals:
_error("SOURCE_CAPTURE_SIGNALS_INVALID")
if envelope.get("runtime_input") not in (None, {}):
_error("SOURCE_CAPTURE_RUNTIME_INPUT_DENY")
elif action == "agent_runtime":
if changes or verification:
_error("AGENT_RUNTIME_MUTATION_DENY")
adapter = str(envelope.get("adapter") or "")
if adapter not in AGENT_RUNTIME_ADAPTERS:
_error("AGENT_RUNTIME_ADAPTER_INVALID")
runtime_input = _agent_runtime_input(envelope.get("runtime_input"))
if envelope.get("runtime_input") != runtime_input:
_error("AGENT_RUNTIME_INPUT_NOT_CANONICAL")
required_runtime_capability = f"agent.runtime.{adapter}"
if required_runtime_capability not in capability_scope:
_error("AGENT_RUNTIME_CAPABILITY_REQUIRED")
if envelope.get("source_signals") not in (None, []):
_error("AGENT_RUNTIME_SOURCE_SIGNALS_DENY")
elif not changes:
_error("CHANGE_SET_INVALID")
declared = str(envelope.get("execution_digest") or "")
material = {key: item for key, item in envelope.items() if key != "execution_digest"}
actual = _digest(material)
if declared != actual:
_error("EXECUTION_DIGEST_INVALID")
return {**material, "execution_digest": actual}
def project_execution_envelope(
binding: GovernedExecutionBinding,
execution_request: Mapping[str, Any] | None = None,
*,
required_capabilities: Sequence[str] = (),
) -> dict[str, Any]:
request = dict(execution_request or {})
action = str(request.get("action") or "source_capture")
if action not in SUPPORTED_ACTIONS:
_error("EXECUTION_ACTION_UNSUPPORTED")
capabilities = _capabilities(request.get("capability_scope", required_capabilities))
governance = {
"task_id": binding.task_id,
"job_id": binding.job_id,
"idempotency_key": binding.idempotency_key,
"commit_idempotency_key": str(request.get("commit_idempotency_key") or f"{binding.task_id}-commit-g{binding.generation}"),
"writer_lease": action in MUTATION_ACTIONS,
"main_write": False,
"production_write": False,
"tag_write": False,
"baseline_write": False,
"force_push": False,
"protected_ref_bypass": False,
}
supplied_governance = request.get("governance")
if supplied_governance is not None:
governance.update(_object(supplied_governance, "GOVERNANCE_POLICY_REQUIRED"))
material: dict[str, Any] = {
"schema": EXECUTION_SCHEMA,
**binding.public(),
"action": action,
"adapter": str(
request.get("adapter")
or ("deterministic" if action == "deterministic" else "source_capture")
),
"capability_scope": list(capabilities),
"change_set": list(request.get("change_set") or []),
"verification": list(request.get("verification") or []),
"source_signals": list(_source_signals(request.get("source_signals") or [])) if action == "source_capture" else [],
"runtime_input": _agent_runtime_input(request.get("runtime_input")) if action == "agent_runtime" else {},
"governance": governance,
"workspace": {
"authority_git_dir": "/data/aios-gitops/repo.git",
"read_only_repository": True,
"cleanup_policy": "always",
},
}
envelope = {**material, "execution_digest": _digest(material)}
return validate_execution_envelope(envelope, binding=binding)
def project_online_task(
binding: GovernedExecutionBinding,
task_request: Mapping[str, Any],
execution_request: Mapping[str, Any] | None = None,
) -> dict[str, Any]:
request = dict(task_request)
required = _capabilities(request.get("required_capabilities"))
execution_value = dict(execution_request or {})
if str(execution_value.get("action") or "source_capture") == "agent_runtime":
adapter = str(execution_value.get("adapter") or "")
if adapter not in AGENT_RUNTIME_ADAPTERS:
_error("AGENT_RUNTIME_ADAPTER_INVALID")
runtime_input = execution_value.get("runtime_input")
tenant_id = request.get("tenant_id")
project_id = request.get("project_id")
declared_scope = str(
request.get("scope_type")
or ("tenant" if isinstance(tenant_id, str) and tenant_id.strip() else "legacy")
)
if runtime_input is None:
runtime_input = {
"goal": request.get("goal"),
"instructions": request.get("instructions") or "",
"success_criteria": request.get("success_criteria") or [],
"constraints": request.get("constraints") or {},
}
elif not isinstance(runtime_input, Mapping):
_error("AGENT_RUNTIME_INPUT_REQUIRED")
runtime_input = dict(runtime_input)
# Scope/trace are authoritative Task identity, never caller-selected runtime hints.
runtime_input["scope"] = {
"scope_type": declared_scope,
"tenant_id": tenant_id,
"project_id": project_id,
}
runtime_input["trace_id"] = str(
request.get("trace_id")
or request.get("run_id")
or binding.task_id
)
execution_value["runtime_input"] = runtime_input
execution = project_execution_envelope(
binding,
execution_value,
required_capabilities=required,
)
metadata = dict(request.get("metadata") or {})
if "execution" in metadata:
validate_execution_envelope(_object(metadata["execution"], "EXECUTION_ENVELOPE_INVALID"), binding=binding)
if metadata["execution"] != execution:
_error("EXECUTION_PROJECTION_CONFLICT")
metadata["execution"] = execution
metadata["execution_projection"] = {
"schema": PROJECTION_SCHEMA,
"task_id": binding.task_id,
"job_id": binding.job_id,
"execution_digest": execution["execution_digest"],
}
return {
**request,
"task_id": binding.task_id,
"metadata": metadata,
"required_capabilities": list(required),
"idempotency_key": str(request.get("idempotency_key") or f"online-task:{binding.idempotency_key}"),
}
def resolve_task_execution(
task: Mapping[str, Any],
binding: GovernedExecutionBinding,
*,
worker_capabilities: Sequence[str] | None = None,
) -> tuple[dict[str, Any], str]:
metadata = task.get("metadata")
if isinstance(metadata, Mapping) and "execution" in metadata:
value = validate_execution_envelope(
_object(metadata["execution"], "EXECUTION_ENVELOPE_INVALID"),
binding=binding,
worker_capabilities=worker_capabilities,
)
return value, "persisted"
projected = project_execution_envelope(
binding,
{"action": "source_capture", "capability_scope": task.get("required_capabilities") or []},
required_capabilities=task.get("required_capabilities") or [],
)
validate_execution_envelope(projected, binding=binding, worker_capabilities=worker_capabilities)
return projected, "recovered_projection"
@@ -0,0 +1,148 @@
"""Fail-closed signed deterministic execution amendment for R7."""
from datetime import datetime, timezone
import hashlib, json, sqlite3
from pathlib import Path
from typing import Mapping
from aios_task_terminal_recovery import DeterministicPlan, RecoveryError, canonical_json
AMENDMENT_SCHEMA="aios.deterministic-execution-amendment.v1"
APPLY_SCHEMA="aios.deterministic-execution-amendment-apply.v1"
NAMESPACE="aios-owner-approval-v1"
EVENT="TASK_RECOVERY_EXECUTION_AMENDMENT_APPLIED"
APPLY_STATES=frozenset({"FAILED", "QUEUED"})
def _err(code): raise RecoveryError(code)
def _sha(v): return hashlib.sha256(canonical_json(v).encode()).hexdigest()
def amendment_body(x):
return {k:v for k,v in x.items() if k not in {"signature","amendment_id"}}
def amendment_id(x): return "sha256:"+_sha(amendment_body(x))
def signed_payload(x):
return canonical_json({"amendment_id":x["amendment_id"],**amendment_body(x)}).encode()
def parse_amendment(raw):
x=dict(raw)
required={"schema","amendment_id","nonce","task_id","job_id","generation",
"repository","branch","expected_branch_oid","recovery_epoch","decision_id",
"plan_id","plan_sha256","expected_apply_status","expected_apply_revision",
"expected_apply_attempt","expected_current_capabilities","envelope",
"owner_identity","key_id","issued_at","not_before","expires_at",
"one_time_use","signature"}
if set(x)<required or x.get("schema")!=AMENDMENT_SCHEMA: _err("AMENDMENT_SCHEMA_INVALID")
if x["expected_apply_status"] not in APPLY_STATES: _err("AMENDMENT_APPLY_STATUS_INVALID")
if x.get("one_time_use") is not True: _err("AMENDMENT_ONE_TIME_REQUIRED")
if not isinstance(x["envelope"],Mapping): _err("AMENDMENT_ENVELOPE_INVALID")
if not isinstance(x["expected_current_capabilities"],list): _err("AMENDMENT_CAPABILITIES_INVALID")
for k in ("generation","expected_apply_revision","expected_apply_attempt"):
if isinstance(x[k],bool) or not isinstance(x[k],int) or x[k]<0: _err("AMENDMENT_CAS_INVALID")
for k in ("issued_at","not_before","expires_at"):
try:d=datetime.fromisoformat(str(x[k]).replace("Z","+00:00"))
except ValueError as e: raise RecoveryError("AMENDMENT_TIME_INVALID") from e
if d.tzinfo is None:_err("AMENDMENT_TIME_INVALID")
x[k]=d.astimezone(timezone.utc).isoformat().replace("+00:00","Z")
x["expected_current_capabilities"]=sorted(set(x["expected_current_capabilities"]))
x["envelope"]=dict(x["envelope"])
return x
def _validate_envelope(env,plan,x):
try:
from aios_online_agent_execution import GovernedExecutionBinding,validate_execution_envelope
binding=GovernedExecutionBinding.from_mapping(env)
e=dict(validate_execution_envelope(env,binding=binding,worker_capabilities=plan.required_capabilities))
except Exception as z: raise RecoveryError("AMENDMENT_EXECUTION_ENVELOPE_INVALID") from z
if e.get("action")!="deterministic" or e.get("adapter")!="deterministic":_err("AMENDMENT_EXECUTION_ACTION_INVALID")
if tuple(sorted(e.get("capability_scope") or ()))!=plan.required_capabilities:_err("AMENDMENT_EXECUTION_CAPABILITIES_MISMATCH")
if not e.get("change_set") or not e.get("verification"):_err("AMENDMENT_EXECUTION_OPERATIONS_REQUIRED")
if e.get("runtime_input") not in (None,{}) or e.get("source_signals") not in (None,[]):_err("AMENDMENT_STALE_RUNTIME_FIELDS_FORBIDDEN")
for k,w in (("task_id",x["task_id"]),("job_id",x["job_id"]),("repository",x["repository"]),
("generation",x["generation"]),("target_branch",x["branch"]),
("expected_branch_oid",x["expected_branch_oid"]),("base_oid",x["expected_branch_oid"]),
("plan_id",x["plan_id"]),("plan_sha256",x["plan_sha256"]),("recovery_epoch",x["recovery_epoch"])):
if e.get(k)!=w:_err("AMENDMENT_EXECUTION_BINDING_MISMATCH")
g=e.get("governance") or {}
if g.get("writer_lease") is not True:_err("AMENDMENT_WRITER_LEASE_REQUIRED")
if any(g.get(k,False) for k in ("main_write","production_write","tag_write","baseline_write","force_push","protected_ref_bypass")):_err("AMENDMENT_PROTECTED_WRITE_FORBIDDEN")
return e
class DeterministicExecutionAmendmentController:
def __init__(self,db_path,verifier,*,clock=None,envelope_validator=None):
self.db_path=Path(db_path); self.verifier=verifier
self.clock=clock or (lambda:datetime.now(timezone.utc)); self._init()
def _db(self):
c=sqlite3.connect(self.db_path,timeout=10,isolation_level=None); c.row_factory=sqlite3.Row
c.execute("PRAGMA busy_timeout=10000"); return c
def _now(self): return self.clock().astimezone(timezone.utc).isoformat().replace("+00:00","Z")
def _init(self):
with self._db() as c:
c.execute("DROP TRIGGER IF EXISTS aios_r7_recovery_capability_projection")
c.executescript("""
CREATE TABLE IF NOT EXISTS deterministic_execution_amendments(
amendment_id TEXT PRIMARY KEY,nonce TEXT UNIQUE NOT NULL,task_id TEXT NOT NULL,
decision_id TEXT NOT NULL,plan_id TEXT NOT NULL,plan_sha256 TEXT NOT NULL,
envelope_digest TEXT NOT NULL,envelope_json TEXT NOT NULL,payload_json TEXT NOT NULL,
created_at TEXT NOT NULL,UNIQUE(task_id,decision_id));
DROP TRIGGER IF EXISTS aios_r7_recovery_requires_executable_amendment;
CREATE TRIGGER aios_r7_recovery_requires_executable_amendment
BEFORE UPDATE OF status ON tasks
WHEN OLD.status='FAILED' AND NEW.status='QUEUED' BEGIN
SELECT CASE WHEN NOT EXISTS(
SELECT 1 FROM deterministic_execution_amendments a
WHERE a.task_id=OLD.task_id
AND a.decision_id=json_extract(NEW.metadata_json,'$.recovery.decision_id')
AND a.amendment_id=json_extract(OLD.metadata_json,'$.recovery.execution_amendment_id')
AND a.envelope_digest=json_extract(OLD.metadata_json,'$.execution.execution_digest')
) THEN RAISE(ABORT,'SIGNED_EXECUTION_AMENDMENT_REQUIRED') END;
END;""")
def _plan(self,c,x):
r=c.execute("SELECT * FROM deterministic_plans WHERE plan_id=?",(x["plan_id"],)).fetchone()
if r is None or r["plan_sha256"]!=x["plan_sha256"]:_err("AMENDMENT_PLAN_NOT_FOUND_OR_MISMATCH")
p=DeterministicPlan.from_dict(json.loads(r["payload_json"]))
if (p.task_id,p.job_id,p.generation,p.repository,p.branch,p.expected_branch_oid,p.recovery_epoch)!=(x["task_id"],x["job_id"],x["generation"],x["repository"],x["branch"],x["expected_branch_oid"],x["recovery_epoch"]):_err("AMENDMENT_PLAN_BINDING_MISMATCH")
return p
def persist(self,raw):
x=parse_amendment(raw)
if x["amendment_id"]!=amendment_id(x):_err("AMENDMENT_ID_DIGEST_MISMATCH")
now=self._now()
if now<x["not_before"] or now>=x["expires_at"]:_err("AMENDMENT_EXPIRED_OR_NOT_YET_VALID")
if not self.verifier.verify(namespace=NAMESPACE,payload=signed_payload(x),signature=x["signature"],key_id=x["key_id"]):_err("AMENDMENT_SIGNATURE_INVALID")
with self._db() as c:
c.execute("BEGIN IMMEDIATE"); e=_validate_envelope(x["envelope"],self._plan(c,x),x)
ej=canonical_json(e); ed=e["execution_digest"]; stored=dict(x); stored["signature"]="<present>"
old=c.execute("SELECT * FROM deterministic_execution_amendments WHERE amendment_id=?",(x["amendment_id"],)).fetchone()
if old:
if old["envelope_json"]!=ej:_err("AMENDMENT_ID_REUSE_MISMATCH")
c.commit(); return {"status":"PRESENT","amendment_id":x["amendment_id"],"task_id":x["task_id"],"decision_id":x["decision_id"],"plan_id":x["plan_id"],"execution_digest":ed}
if c.execute("SELECT 1 FROM deterministic_execution_amendments WHERE nonce=? OR (task_id=? AND decision_id=?)",(x["nonce"],x["task_id"],x["decision_id"])).fetchone():_err("AMENDMENT_REPLAY_OR_DECISION_REUSE")
c.execute("INSERT INTO deterministic_execution_amendments VALUES(?,?,?,?,?,?,?,?,?,?)",(x["amendment_id"],x["nonce"],x["task_id"],x["decision_id"],x["plan_id"],x["plan_sha256"],ed,ej,canonical_json(stored),now)); c.commit()
return {"status":"PERSISTED","amendment_id":x["amendment_id"],"task_id":x["task_id"],"decision_id":x["decision_id"],"plan_id":x["plan_id"],"execution_digest":ed}
def apply(self,raw):
if raw.get("schema")!=APPLY_SCHEMA:_err("AMENDMENT_APPLY_SCHEMA_INVALID")
aid=raw.get("amendment_id"); tid=raw.get("task_id")
with self._db() as c:
c.execute("BEGIN IMMEDIATE"); a=c.execute("SELECT * FROM deterministic_execution_amendments WHERE amendment_id=? AND task_id=?",(aid,tid)).fetchone()
if a is None:_err("AMENDMENT_NOT_FOUND")
x=json.loads(a["payload_json"]); d=json.loads(a["envelope_json"])
t=c.execute("SELECT * FROM tasks WHERE task_id=?",(tid,)).fetchone()
if t is None:_err("TASK_NOT_FOUND")
m=json.loads(t["metadata_json"] or "{}"); cp=tuple(sorted(json.loads(t["required_capabilities_json"] or "[]"))); dc=tuple(sorted(d.get("capability_scope") or ()))
rec=m.get("recovery"); rec=dict(rec) if isinstance(rec,Mapping) else {}
old=m.get("execution")
if isinstance(old,Mapping) and old.get("execution_digest")==a["envelope_digest"] and cp==dc and rec.get("execution_amendment_id")==aid:
c.commit(); return {"status":"PRESENT","task_id":tid,"amendment_id":aid,"execution_digest":a["envelope_digest"],"revision":int(t["revision"]),"attempt":int(t["attempt"])}
if t["status"]!=x["expected_apply_status"] or int(t["revision"])!=x["expected_apply_revision"] or int(t["attempt"])!=x["expected_apply_attempt"] or cp!=tuple(x["expected_current_capabilities"]) or t["claimed_by"] is not None or t["lease_token_hash"] is not None:_err("AMENDMENT_APPLY_TASK_CAS_MISMATCH")
if t["status"]=="QUEUED" and rec.get("decision_id")!=x["decision_id"]:_err("AMENDMENT_DECISION_BINDING_MISMATCH")
m["execution"]=d; m["execution_projection"]={"schema":"aios.online-agent.task-projection.v1","task_id":tid,"job_id":d.get("job_id"),"execution_digest":a["envelope_digest"]}
rec["execution_amendment_id"]=aid; m["recovery"]=rec; now=self._now()
q=c.execute("""UPDATE tasks SET required_capabilities_json=?,metadata_json=?,revision=revision+1,updated_at=? WHERE task_id=? AND status=? AND revision=? AND attempt=? AND claimed_by IS NULL AND lease_token_hash IS NULL""",(canonical_json(list(dc)),canonical_json(m),now,tid,x["expected_apply_status"],x["expected_apply_revision"],x["expected_apply_attempt"]))
if q.rowcount!=1:_err("AMENDMENT_APPLY_TASK_CAS_FAILED")
c.execute("INSERT INTO events(task_id,agent_id,event_type,payload_json,created_at) VALUES(?,?,?,?,?)",(tid,None,EVENT,canonical_json({"amendment_id":aid,"decision_id":x["decision_id"],"plan_id":x["plan_id"],"execution_digest":a["envelope_digest"],"required_capabilities":list(dc)}),now)); c.commit()
return {"status":"APPLIED","task_id":tid,"amendment_id":aid,"decision_id":x["decision_id"],"plan_id":x["plan_id"],"execution_digest":a["envelope_digest"],"required_capabilities":list(dc),"revision":x["expected_apply_revision"]+1,"attempt":x["expected_apply_attempt"]}
@@ -0,0 +1,798 @@
#!/usr/bin/env python3
"""Fail-closed AF_UNIX builder for server-owned trusted candidate evidence."""
from __future__ import annotations
import argparse
import base64
import hashlib
import hmac
import json
import os
import secrets
import socket
import stat
import struct
import subprocess
import time
from datetime import datetime, timezone
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Callable, Mapping, Protocol
class BuilderError(RuntimeError):
"""A stable, non-sensitive error code returned by the builder boundary."""
def _canonical_json(value: Mapping[str, Any]) -> bytes:
return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=True).encode("utf-8")
def _sha256(*parts: str) -> str:
return hashlib.sha256("\0".join(parts).encode("utf-8")).hexdigest()
@dataclass(frozen=True)
class PeerIdentity:
pid: int
uid: int
gid: int
class DomainPurposeSigner(Protocol):
"""A signer deliberately limited to the builder's one domain purpose."""
@property
def key_id(self) -> str:
...
@property
def key_generation(self) -> int:
...
def sign_for_purpose(self, purpose: str, payload: bytes) -> bytes:
...
class CredentialPathEd25519Signer:
"""Reads a systemd-mounted credential path; never exports or logs key bytes."""
def __init__(self, credential_path: Path, *, key_id: str, key_generation: int, allowed_purpose: str):
self._credential_path = Path(credential_path)
self._key_id = str(key_id)
self._key_generation = key_generation
self._allowed_purpose = str(allowed_purpose)
if not self._key_id or not isinstance(key_generation, int) or key_generation < 1:
raise BuilderError("SIGNING_IDENTITY_INVALID")
if not self._credential_path.is_file():
raise BuilderError("SIGNING_CREDENTIAL_UNAVAILABLE")
try:
from cryptography.hazmat.primitives.serialization import load_pem_private_key
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
self._private_key = load_pem_private_key(self._credential_path.read_bytes(), password=None)
if not isinstance(self._private_key, Ed25519PrivateKey):
raise TypeError("signing key is not Ed25519")
except Exception as exc: # do not expose parse/key material details
raise BuilderError("SIGNING_CREDENTIAL_INVALID") from exc
@property
def key_id(self) -> str:
return self._key_id
@property
def key_generation(self) -> int:
return self._key_generation
def sign_for_purpose(self, purpose: str, payload: bytes) -> bytes:
if purpose != self._allowed_purpose:
raise BuilderError("SIGNING_PURPOSE_DENIED")
return self._private_key.sign(payload)
@dataclass(frozen=True)
class BuilderKeyring:
builder_hmac: bytes
signing_private_key_path: Path | None = None
def __post_init__(self) -> None:
if len(self.builder_hmac) < 32:
raise BuilderError("BUILDER_HMAC_INVALID")
@classmethod
def from_credentials_directory(cls, credentials_directory: Path) -> "BuilderKeyring":
root = Path(credentials_directory)
hmac_path = root / "builder_hmac"
signing_path = root / "signing_private_key"
try:
secret = hmac_path.read_bytes().strip()
except OSError as exc:
raise BuilderError("BUILDER_HMAC_UNAVAILABLE") from exc
if not signing_path.is_file():
raise BuilderError("SIGNING_CREDENTIAL_UNAVAILABLE")
return cls(builder_hmac=secret, signing_private_key_path=signing_path)
@dataclass(frozen=True)
class TrustedCandidateBuilderConfig:
policy_schema: str
repository: str
request_schema: str
response_schema: str
operation: str
readiness_operation: str
maximum_request_age_seconds: int
request_timeout_seconds: int
allowed_peer_uid: int
allowed_peer_cgroups: tuple[str, ...]
allowed_peer_executable_prefix: str
allowed_peer_runtime_prefix: str
allowed_peer_runtime_suffix: str
required_checks: tuple[str, ...]
evidence_layout: Mapping[str, str]
recipes: Mapping[str, Mapping[str, str]]
governed_recipes: Mapping[str, Mapping[str, str]]
policy_sha256: str
@classmethod
def from_policy(cls, policy: Mapping[str, Any]) -> "TrustedCandidateBuilderConfig":
required = {
"schema", "repository", "request_schema", "response_schema", "operation",
"readiness_operation", "maximum_request_age_seconds", "request_timeout_seconds",
"allowed_peer_uid", "allowed_peer_cgroups", "allowed_peer_executable_prefix",
"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":
raise BuilderError("POLICY_SCHEMA_INVALID")
recipes = policy.get("recipes")
if not isinstance(recipes, Mapping) or set(recipes) != {"V2R2C2", "V2R2C3"}:
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")
if (
policy["repository"] != "aios/aios"
or policy["operation"] != "BuildTrustedCandidateV1"
or policy["readiness_operation"] != "ProbeTrustedCandidateBuilderV1"
):
raise BuilderError("POLICY_SCOPE_INVALID")
if not isinstance(policy["maximum_request_age_seconds"], int) or not 1 <= policy["maximum_request_age_seconds"] <= 300:
raise BuilderError("POLICY_FRESHNESS_INVALID")
if not isinstance(policy["request_timeout_seconds"], int) or not 1 <= policy["request_timeout_seconds"] <= 120:
raise BuilderError("POLICY_TIMEOUT_INVALID")
if not isinstance(policy["allowed_peer_uid"], int) or policy["allowed_peer_uid"] != 1000:
raise BuilderError("POLICY_PEER_UID_INVALID")
cgroups, checks = policy["allowed_peer_cgroups"], policy["required_checks"]
if not isinstance(cgroups, list) or not cgroups or not all(isinstance(value, str) and value.startswith("/") for value in cgroups):
raise BuilderError("POLICY_CGROUPS_INVALID")
for field in (
"allowed_peer_executable_prefix",
"allowed_peer_runtime_prefix",
"allowed_peer_runtime_suffix",
):
if not isinstance(policy[field], str) or not policy[field].startswith("/"):
raise BuilderError("POLICY_PROCESS_IDENTITY_INVALID")
if not isinstance(checks, list) or not checks or len(set(checks)) != len(checks) or not all(isinstance(value, str) for value in checks):
raise BuilderError("POLICY_CHECKS_INVALID")
layout = policy["evidence_layout"]
evidence_names = {
"artifact", "manifest", "package_closure", "tests", "canary",
"gitea", "rollback", "policy", "secret_scan",
}
if (
not isinstance(layout, Mapping)
or set(layout) != evidence_names
or not all(
isinstance(value, str)
and value.startswith("{version}/")
and ".." not in Path(value).parts
and not Path(value).is_absolute()
for value in layout.values()
)
):
raise BuilderError("POLICY_EVIDENCE_LAYOUT_INVALID")
return cls(
policy_schema=str(policy["schema"]), repository=str(policy["repository"]),
request_schema=str(policy["request_schema"]), response_schema=str(policy["response_schema"]),
operation=str(policy["operation"]), readiness_operation=str(policy["readiness_operation"]),
maximum_request_age_seconds=policy["maximum_request_age_seconds"],
request_timeout_seconds=policy["request_timeout_seconds"],
allowed_peer_uid=policy["allowed_peer_uid"], allowed_peer_cgroups=tuple(cgroups),
allowed_peer_executable_prefix=policy["allowed_peer_executable_prefix"],
allowed_peer_runtime_prefix=policy["allowed_peer_runtime_prefix"],
allowed_peer_runtime_suffix=policy["allowed_peer_runtime_suffix"],
required_checks=tuple(checks), evidence_layout=dict(layout),
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(),
)
def fixed_recipe(self, name: str) -> Mapping[str, str]:
recipe = self.recipes.get(name)
if recipe is None:
raise BuilderError("RECIPE_UNKNOWN")
if recipe["mode"] == "governed-derived-only":
derived = self.governed_recipes.get(name)
required = {"mode", "task_id", "candidate_id", "base_oid", "source_commit", "version", "purpose", "derived_from_v2r2c2_attestation", "builder_engineering_oid"}
if not isinstance(derived, Mapping) or set(derived) != required or derived.get("mode") != "governed-derived" or derived.get("version") != name or derived.get("purpose") != recipe.get("purpose"):
raise BuilderError("GOVERNED_V2R2C3_DERIVATION_REQUIRED")
for name in ("base_oid", "source_commit", "builder_engineering_oid"):
value = derived.get(name)
if not isinstance(value, str) or len(value) != 40 or any(char not in "0123456789abcdef" for char in value.lower()):
raise BuilderError("GOVERNED_V2R2C3_DERIVATION_REQUIRED")
return dict(derived)
return recipe
def resolve_recipe(self, version: str, candidate_id: str | None, source_commit: str) -> Mapping[str, str]:
recipe = self.fixed_recipe(version)
# Intent-only callers do not choose a candidate identity. The
# policy-owned recipe supplies it; explicit worker requests are
# still checked against the same immutable recipe.
effective_candidate_id = str(candidate_id or recipe.get("candidate_id") or "")
if {"version": recipe.get("version"), "candidate_id": recipe.get("candidate_id"), "source_commit": recipe.get("source_commit")} != {"version": version, "candidate_id": effective_candidate_id, "source_commit": source_commit}:
raise BuilderError("REQUEST_RELEASE_BINDING_INVALID")
return recipe
def _invalid_governed_recipes() -> Mapping[str, Mapping[str, str]]:
raise BuilderError("POLICY_GOVERNED_RECIPES_INVALID")
def load_builder_policy(path: Path) -> Mapping[str, Any]:
try:
decoded = json.loads(Path(path).read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError) as exc:
raise BuilderError("POLICY_UNAVAILABLE") from exc
if not isinstance(decoded, Mapping):
raise BuilderError("POLICY_SCHEMA_INVALID")
TrustedCandidateBuilderConfig.from_policy(decoded)
return decoded
class ServerOwnedEvidenceSource:
"""Recomputes a fixed on-host evidence closure without caller-owned paths."""
def __init__(
self,
config: TrustedCandidateBuilderConfig,
authority_git_dir: Path,
evidence_root: Path,
*,
expected_owner_uid: int | None = 0,
git_runner: Callable[..., Any] = subprocess.run,
):
self._config = config
self._authority_git_dir = Path(authority_git_dir)
self._evidence_root = Path(evidence_root)
self._expected_owner_uid = expected_owner_uid
self._git_runner = git_runner
def _regular_path(self, relative: str) -> Path:
root = self._evidence_root.resolve(strict=True)
candidate = self._evidence_root / relative
current = self._evidence_root
for part in Path(relative).parts:
current = current / part
if current.is_symlink():
raise BuilderError("COLLECTOR_SYMLINK_DENIED")
try:
resolved = candidate.resolve(strict=True)
resolved.relative_to(root)
metadata = resolved.stat(follow_symlinks=False)
except (OSError, ValueError) as exc:
raise BuilderError("COLLECTOR_EVIDENCE_UNAVAILABLE") from exc
if not stat.S_ISREG(metadata.st_mode):
raise BuilderError("COLLECTOR_SPECIAL_FILE_DENIED")
if os.name != "nt" and metadata.st_mode & 0o022:
raise BuilderError("COLLECTOR_EVIDENCE_MODE_DENIED")
if (
os.name != "nt"
and
self._expected_owner_uid is not None
and metadata.st_uid != self._expected_owner_uid
):
raise BuilderError("COLLECTOR_EVIDENCE_OWNER_DENIED")
return resolved
def _digest(self, path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as handle:
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def _json(self, path: Path) -> Mapping[str, Any]:
try:
if path.stat().st_size > 2 * 1024 * 1024:
raise BuilderError("COLLECTOR_EVIDENCE_SIZE_INVALID")
value = json.loads(path.read_text(encoding="utf-8"))
except (OSError, UnicodeDecodeError, json.JSONDecodeError) as exc:
raise BuilderError("COLLECTOR_EVIDENCE_INVALID") from exc
if not isinstance(value, Mapping):
raise BuilderError("COLLECTOR_EVIDENCE_INVALID")
return value
def __call__(self, recipe_name: str, recipe: Mapping[str, str]) -> Mapping[str, Any]:
try:
tree_result = self._git_runner(
["git", f"--git-dir={self._authority_git_dir}", "rev-parse", f"{recipe['source_commit']}^{{tree}}"],
check=True, capture_output=True, text=True, timeout=15,
env={"PATH": os.environ.get("PATH", "")},
)
ancestry_result = self._git_runner(
["git", f"--git-dir={self._authority_git_dir}", "merge-base", "--is-ancestor", recipe["base_oid"], recipe["source_commit"]],
check=False, capture_output=True, text=True, timeout=15,
env={"PATH": os.environ.get("PATH", "")},
)
tree_oid = tree_result.stdout.strip().lower()
except (OSError, subprocess.SubprocessError) as exc:
raise BuilderError("COLLECTOR_EVIDENCE_UNAVAILABLE") from exc
if getattr(ancestry_result, "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"}:
raise BuilderError("COLLECTOR_EVIDENCE_INVALID")
if (
receipt.get("schema") != "aios.trusted-candidate-builder.evidence.v1"
or receipt.get("version") != recipe_name
):
raise BuilderError("COLLECTOR_EVIDENCE_INVALID")
candidate = receipt.get("candidate")
release_plan = receipt.get("release_plan")
if not isinstance(candidate, Mapping) or not isinstance(release_plan, Mapping):
raise BuilderError("COLLECTOR_EVIDENCE_INVALID")
if (
candidate.get("candidate_id") != recipe["candidate_id"]
or candidate.get("source_commit") != recipe["source_commit"]
or candidate.get("tree_oid") != tree_oid
):
raise BuilderError("COLLECTOR_GIT_MISMATCH")
git_consistency = candidate.get("git_consistency")
if (
not isinstance(git_consistency, Mapping)
or git_consistency.get("consistent") is not True
or git_consistency.get("source_commit") != recipe["source_commit"]
or git_consistency.get("tree_oid") != tree_oid
or git_consistency.get("repository") != self._config.repository
):
raise BuilderError("COLLECTOR_GIT_MISMATCH")
paths = {
name: self._regular_path(template.format(version=recipe_name))
for name, template in self._config.evidence_layout.items()
}
deployment = release_plan.get("deployment")
if not isinstance(deployment, Mapping):
raise BuilderError("COLLECTOR_EVIDENCE_INVALID")
digests = {name: self._digest(path) for name, path in paths.items()}
if (
digests["artifact"] != candidate.get("artifact_sha256")
or digests["manifest"] != candidate.get("manifest_sha256")
or digests["package_closure"] != deployment.get("package_closure_sha256")
or digests["policy"] != release_plan.get("policy_sha256")
or digests["policy"] != deployment.get("policy_sha256")
):
raise BuilderError("COLLECTOR_DIGEST_MISMATCH")
tests = self._json(paths["tests"])
canary = self._json(paths["canary"])
gitea = self._json(paths["gitea"])
rollback = self._json(paths["rollback"])
secret_scan = self._json(paths["secret_scan"])
if tests != candidate.get("tests") or tests.get("passed") is not True:
raise BuilderError("COLLECTOR_TESTS_MISMATCH")
if canary != candidate.get("canary") or canary.get("passed") is not True:
raise BuilderError("COLLECTOR_CANARY_MISMATCH")
if (
digests["tests"] != hashlib.sha256(_canonical_json(dict(tests))).hexdigest()
or digests["canary"] != hashlib.sha256(_canonical_json(dict(canary))).hexdigest()
):
raise BuilderError("COLLECTOR_EVIDENCE_CANONICALIZATION_MISMATCH")
if (
gitea != candidate.get("gitea_consistency")
or gitea.get("consistent") is not True
or gitea.get("repository") != self._config.repository
or gitea.get("source_commit") != recipe["source_commit"]
or gitea.get("tree_oid") != tree_oid
or gitea.get("remote_ref_oid") != recipe["source_commit"]
):
raise BuilderError("COLLECTOR_GITEA_MISMATCH")
if (
candidate.get("rollback_ready") is not True
or rollback.get("ready") is not True
or rollback.get("source_commit") != recipe["source_commit"]
or rollback.get("rollback_sha256") != deployment.get("rollback_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()
):
raise BuilderError("COLLECTOR_EVIDENCE_CANONICALIZATION_MISMATCH")
if (
secret_scan.get("passed") is not True
or secret_scan.get("artifact_sha256") != candidate.get("artifact_sha256")
):
raise BuilderError("COLLECTOR_SECRET_SCAN_MISMATCH")
checks = {name: True for name in self._config.required_checks}
return {
"candidate": dict(candidate),
"release_plan": dict(release_plan),
"checks": checks,
"evidence_digests": digests,
}
class DeterministicEvidenceCollector:
"""Validates a server-owned source; request fields never influence collection."""
_BUNDLE_FIELDS = {"candidate", "release_plan", "checks", "evidence_digests"}
def __init__(self, config: TrustedCandidateBuilderConfig, *, evidence_source: Callable[[str, Mapping[str, str]], Mapping[str, Any]] | None = None):
self._config = config
self._evidence_source = evidence_source
def collect(self, recipe_name: str, recipe: Mapping[str, str]) -> Mapping[str, Any]:
if self._evidence_source is None:
raise BuilderError("COLLECTOR_UNCONFIGURED")
bundle = self._evidence_source(recipe_name, recipe)
if not isinstance(bundle, Mapping) or set(bundle) != self._BUNDLE_FIELDS:
raise BuilderError("COLLECTOR_EVIDENCE_INVALID")
raw_checks = bundle.get("checks")
if (
not isinstance(raw_checks, Mapping)
or set(raw_checks) != set(self._config.required_checks)
or any(raw_checks.get(name) is not True for name in self._config.required_checks)
):
raise BuilderError("COLLECTOR_CHECKS_INCOMPLETE")
evidence_digests = bundle.get("evidence_digests")
if (
not isinstance(evidence_digests, Mapping)
or set(evidence_digests) != set(self._config.evidence_layout)
or any(
not isinstance(value, str)
or len(value) != 64
or any(char not in "0123456789abcdef" for char in value.lower())
for value in evidence_digests.values()
)
):
raise BuilderError("COLLECTOR_EVIDENCE_DIGESTS_INVALID")
try:
from silent_release_controller import ReleaseCandidate
from silent_release_hardening import ReleasePlan, TrustedCandidateBundle
candidate = ReleaseCandidate.from_dict(dict(bundle["candidate"]))
release_plan = ReleasePlan.from_dict(dict(bundle["release_plan"]))
checks = dict(raw_checks)
TrustedCandidateBundle(candidate, release_plan, checks).validate()
except (ImportError, KeyError, TypeError, ValueError, RuntimeError) as exc:
raise BuilderError("COLLECTOR_EVIDENCE_INVALID") from exc
if candidate.source_commit != recipe["source_commit"] or candidate.candidate_id != recipe["candidate_id"]:
raise BuilderError("COLLECTOR_SOURCE_MISMATCH")
return {
"candidate": candidate.to_dict(),
"release_plan": release_plan.to_dict(),
"checks": checks,
"evidence_digests": dict(evidence_digests),
}
def _extract_peer_identity(connection: socket.socket) -> PeerIdentity:
if not hasattr(socket, "SO_PEERCRED"):
raise BuilderError("SO_PEERCRED_UNAVAILABLE")
raw = connection.getsockopt(socket.SOL_SOCKET, socket.SO_PEERCRED, struct.calcsize("3i"))
if len(raw) != struct.calcsize("3i"):
raise BuilderError("SO_PEERCRED_INVALID")
return PeerIdentity(*struct.unpack("3i", raw))
def _resolve_cgroups(pid: int) -> tuple[str, ...]:
try:
return tuple(
line.rsplit(":", 1)[1].strip()
for line in Path(f"/proc/{pid}/cgroup").read_text(encoding="utf-8").splitlines()
if ":" in line and line.rsplit(":", 1)[1].strip().startswith("/")
)
except OSError as exc:
raise BuilderError("PEER_CGROUP_UNAVAILABLE") from exc
def _resolve_process_identity(pid: int) -> tuple[str, tuple[str, ...]]:
try:
executable = os.path.realpath(f"/proc/{pid}/exe")
argv = tuple(
item.decode("utf-8", "strict")
for item in Path(f"/proc/{pid}/cmdline").read_bytes().split(b"\0")
if item
)
except (OSError, UnicodeDecodeError) as exc:
raise BuilderError("PEER_PROCESS_UNAVAILABLE") from exc
if not executable or not argv:
raise BuilderError("PEER_PROCESS_UNAVAILABLE")
return executable, argv
class TrustedCandidateBuilder:
def __init__(
self, config: TrustedCandidateBuilderConfig, keyring: BuilderKeyring, *,
collector: DeterministicEvidenceCollector, signer: DomainPurposeSigner,
peer_identity_resolver: Callable[[socket.socket], PeerIdentity] = _extract_peer_identity,
cgroup_resolver: Callable[[int], tuple[str, ...] | str] = _resolve_cgroups,
process_identity_resolver: Callable[[int], tuple[str, tuple[str, ...]]] = _resolve_process_identity,
clock: Callable[[], float] = time.time, nonce_ledger_path: Path | None = None,
) -> None:
self.config, self.keyring, self.collector, self.signer = config, keyring, collector, signer
self.peer_identity_resolver, self.cgroup_resolver, self.clock = peer_identity_resolver, cgroup_resolver, clock
self.process_identity_resolver = process_identity_resolver
self._nonce_ledger_path = Path(nonce_ledger_path) if nonce_ledger_path else None
self._nonce_ledger: dict[str, tuple[bytes, dict[str, Any]]] = self._load_nonce_ledger()
def _load_nonce_ledger(self) -> dict[str, tuple[bytes, dict[str, Any]]]:
if self._nonce_ledger_path is None or not self._nonce_ledger_path.exists():
return {}
if self._nonce_ledger_path.is_symlink():
raise BuilderError("NONCE_LEDGER_INVALID")
try:
raw = json.loads(self._nonce_ledger_path.read_text(encoding="utf-8"))
return {nonce: (bytes.fromhex(value["request_sha256"]), dict(value["result"])) for nonce, value in raw.items()}
except (OSError, ValueError, TypeError, KeyError, json.JSONDecodeError) as exc:
raise BuilderError("NONCE_LEDGER_INVALID") from exc
def _persist_nonce_ledger(self) -> None:
if self._nonce_ledger_path is None:
return
try:
self._nonce_ledger_path.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
temporary = self._nonce_ledger_path.with_suffix(".tmp")
payload = {nonce: {"request_sha256": digest.hex(), "result": result} for nonce, (digest, result) in self._nonce_ledger.items()}
with temporary.open("wb") as handle:
handle.write(_canonical_json(payload))
handle.flush()
os.fsync(handle.fileno())
os.chmod(temporary, 0o600)
os.replace(temporary, self._nonce_ledger_path)
if os.name != "nt":
descriptor = os.open(self._nonce_ledger_path.parent, os.O_RDONLY)
try:
os.fsync(descriptor)
finally:
os.close(descriptor)
except OSError as exc:
raise BuilderError("NONCE_LEDGER_PERSIST_FAILED") from exc
def _verify_peer(self, connection: socket.socket) -> None:
peer = self.peer_identity_resolver(connection)
if peer.uid != self.config.allowed_peer_uid or peer.pid <= 0:
raise BuilderError("PEER_IDENTITY_DENIED")
resolved = self.cgroup_resolver(peer.pid)
cgroups = (resolved,) if isinstance(resolved, str) else tuple(resolved)
if not cgroups or not any(item in self.config.allowed_peer_cgroups for item in cgroups):
raise BuilderError("PEER_CGROUP_DENIED")
executable, argv = self.process_identity_resolver(peer.pid)
if not executable.startswith(self.config.allowed_peer_executable_prefix):
raise BuilderError("PEER_PROCESS_DENIED")
runtime_arguments = [
value
for value in argv
if value.startswith(self.config.allowed_peer_runtime_prefix)
and value.endswith(self.config.allowed_peer_runtime_suffix)
]
if len(runtime_arguments) != 1:
raise BuilderError("PEER_PROCESS_DENIED")
def handle_request(self, request: Mapping[str, Any], *, connection: socket.socket) -> dict[str, Any]:
if not isinstance(request, Mapping):
raise BuilderError("REQUEST_SCHEMA_INVALID")
expected_keys = {"schema", "operation", "recipe", "repository", "task_id", "candidate_id", "base_oid", "source_commit", "version", "purpose", "issued_at", "nonce", "hmac"}
if (
set(request) != expected_keys
or request.get("schema") != self.config.request_schema
or request.get("operation") not in {
self.config.operation,
self.config.readiness_operation,
}
):
raise BuilderError("REQUEST_SCHEMA_INVALID")
supplied_mac = request.get("hmac")
unsigned = {key: value for key, value in request.items() if key != "hmac"}
if not isinstance(supplied_mac, str) or not hmac.compare_digest(hmac.new(self.keyring.builder_hmac, _canonical_json(unsigned), hashlib.sha256).hexdigest(), supplied_mac):
raise BuilderError("REQUEST_HMAC_INVALID")
issued_at, nonce = request.get("issued_at"), request.get("nonce")
if not isinstance(issued_at, int) or not isinstance(nonce, str) or len(nonce) < 16 or len(nonce) > 256:
raise BuilderError("REQUEST_SCHEMA_INVALID")
self._verify_peer(connection)
request_bytes = _canonical_json(dict(request))
replay = self._nonce_ledger.get(nonce)
if replay is not None:
if hmac.compare_digest(replay[0], hashlib.sha256(request_bytes).digest()):
return dict(replay[1])
raise BuilderError("REPLAY_CONFLICT")
if abs(int(self.clock()) - issued_at) > self.config.maximum_request_age_seconds:
raise BuilderError("REQUEST_STALE")
recipe_name = request.get("recipe")
if not isinstance(recipe_name, str):
raise BuilderError("REQUEST_SCHEMA_INVALID")
recipe = self.config.resolve_recipe(str(request.get("version") or ""), str(request.get("candidate_id") or ""), str(request.get("source_commit") or ""))
if recipe_name != recipe.get("version"):
raise BuilderError("REQUEST_RELEASE_BINDING_INVALID")
bound = {"repository": self.config.repository, "task_id": recipe["task_id"], "candidate_id": recipe["candidate_id"], "base_oid": recipe["base_oid"], "source_commit": recipe["source_commit"], "version": recipe["version"], "purpose": recipe["purpose"]}
if {key: request.get(key) for key in bound} != bound:
raise BuilderError("REQUEST_SCOPE_INVALID")
bundle = dict(self.collector.collect(recipe_name, recipe))
candidate = bundle["candidate"]
release_plan = bundle["release_plan"]
checks = bundle.get("checks")
evidence_digests = bundle["evidence_digests"]
if not isinstance(checks, Mapping) or set(checks) != set(self.config.required_checks) or not all(value is True for value in checks.values()):
raise BuilderError("COLLECTOR_CHECKS_INCOMPLETE")
if request["operation"] == self.config.readiness_operation:
result = {
"schema": self.config.response_schema,
"status": "READY",
"repository": self.config.repository,
"recipe": recipe_name,
"candidate_id": recipe["candidate_id"],
"source_commit": recipe["source_commit"],
"builder_key_id": self.signer.key_id,
"builder_key_generation": self.signer.key_generation,
"signature_algorithm": "Ed25519",
"signature_purpose": "AIOS_TRUSTED_RELEASE_BUILDER",
"policy_sha256": self.config.policy_sha256,
"collector_ready": True,
"signer_ready": True,
}
if len(self._nonce_ledger) >= 2048:
self._nonce_ledger.pop(next(iter(self._nonce_ledger)))
self._nonce_ledger[nonce] = (
hashlib.sha256(request_bytes).digest(),
dict(result),
)
self._persist_nonce_ledger()
return result
issued = datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
attestation = {
"builder_attestation_id": "builder-attestation-" + _sha256(recipe_name, recipe["task_id"], candidate["artifact_sha256"])[:24],
"builder_key_id": self.signer.key_id,
"builder_key_generation": self.signer.key_generation,
"signature_algorithm": "Ed25519",
"signature_purpose": "AIOS_TRUSTED_RELEASE_BUILDER",
"source_commit": candidate["source_commit"], "tree_oid": candidate["tree_oid"],
"artifact_sha256": candidate["artifact_sha256"], "manifest_sha256": candidate["manifest_sha256"],
"package_closure_sha256": release_plan["deployment"]["package_closure_sha256"],
"test_evidence_sha256": evidence_digests["tests"],
"canary_evidence_sha256": evidence_digests["canary"],
"gitea_evidence_sha256": evidence_digests["gitea"],
"rollback_evidence_sha256": evidence_digests["rollback"],
"policy_evidence_sha256": evidence_digests["policy"],
"secret_scan_evidence_sha256": evidence_digests["secret_scan"],
"issued_at": issued, "trust_domain": "aios.trusted-builder.attestation.v1",
}
canonical_attestation = _canonical_json(attestation)
fingerprint = hashlib.sha256(canonical_attestation).hexdigest()
signature = base64.b64encode(self.signer.sign_for_purpose("AIOS_TRUSTED_RELEASE_BUILDER", canonical_attestation)).decode("ascii")
result = {
"schema": self.config.response_schema, "status": "BUILT", "recipe": recipe_name,
"repository": self.config.repository, "task_id": recipe["task_id"], "base_oid": recipe["base_oid"],
"purpose": recipe["purpose"], "checks": dict(checks),
"evidence": {"candidate_id": candidate["candidate_id"], "source_commit": candidate["source_commit"], "tree_oid": candidate["tree_oid"], "artifact_sha256": candidate["artifact_sha256"], "manifest_sha256": candidate["manifest_sha256"], "package_closure_sha256": release_plan["deployment"]["package_closure_sha256"], "test_evidence_sha256": attestation["test_evidence_sha256"], "canary_evidence_sha256": attestation["canary_evidence_sha256"]},
"attestation": {**attestation, "fingerprint": fingerprint, "signature": signature},
}
result["silent_release_bundle"] = {"candidate": candidate, "release_plan": release_plan, "checks": dict(checks), "attestation": result["attestation"]}
if len(self._nonce_ledger) >= 2048:
self._nonce_ledger.pop(next(iter(self._nonce_ledger)))
self._nonce_ledger[nonce] = (hashlib.sha256(request_bytes).digest(), dict(result))
self._persist_nonce_ledger()
return result
def serve_forever(self, listener: socket.socket) -> None:
while True:
connection, _ = listener.accept()
with connection:
try:
raw = connection.makefile("rb").readline(65537)
if not raw or len(raw) > 65536:
raise BuilderError("REQUEST_SIZE_INVALID")
result = self.handle_request(json.loads(raw), connection=connection)
except (BuilderError, UnicodeDecodeError, json.JSONDecodeError):
result = {"schema": self.config.response_schema, "status": "REJECTED", "error_code": "REQUEST_REJECTED"}
connection.sendall(_canonical_json(result) + b"\n")
class UnixTrustedCandidateBuilderClient:
def __init__(self, config: TrustedCandidateBuilderConfig, keyring: BuilderKeyring, *, socket_path: Path | None = None, clock: Callable[[], float] = time.time) -> None:
self.config, self.keyring, self.socket_path, self.clock = config, keyring, socket_path, clock
def create_request(self, version: str, candidate_id: str | None, source_commit: str, *, nonce: str, operation: str | None = None) -> dict[str, Any]:
selected = self.config.resolve_recipe(version, candidate_id, source_commit)
selected_operation = operation or self.config.operation
if selected_operation not in {self.config.operation, self.config.readiness_operation}:
raise BuilderError("REQUEST_OPERATION_INVALID")
request = {"schema": self.config.request_schema, "operation": selected_operation, "recipe": version, "repository": self.config.repository, "task_id": selected["task_id"], "candidate_id": selected["candidate_id"], "base_oid": selected["base_oid"], "source_commit": selected["source_commit"], "version": selected["version"], "purpose": selected["purpose"], "issued_at": int(self.clock()), "nonce": nonce}
request["hmac"] = hmac.new(self.keyring.builder_hmac, _canonical_json(request), hashlib.sha256).hexdigest()
return request
def _exchange(self, request: Mapping[str, Any]) -> Mapping[str, Any]:
if self.socket_path is None:
raise BuilderError("BUILDER_SOCKET_UNCONFIGURED")
try:
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as connection:
connection.settimeout(self.config.request_timeout_seconds)
connection.connect(os.fspath(self.socket_path))
connection.sendall(_canonical_json(dict(request)) + b"\n")
raw = connection.makefile("rb").readline(65537)
except (OSError, TimeoutError) as exc:
raise BuilderError("BUILDER_TRANSPORT_UNAVAILABLE") from exc
try:
result = json.loads(raw)
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
raise BuilderError("BUILDER_RESPONSE_INVALID") from exc
if not isinstance(result, Mapping) or result.get("schema") != self.config.response_schema:
raise BuilderError("BUILDER_RESPONSE_INVALID")
return result
def build(self, version: str, candidate_id: str | None, source_commit: str, *, nonce: str) -> Mapping[str, Any]:
result = self._exchange(
self.create_request(version, candidate_id, source_commit, nonce=nonce)
)
if result.get("status") != "BUILT":
raise BuilderError("BUILDER_REQUEST_REJECTED")
return result
def probe(self, *, nonce: str | None = None) -> Mapping[str, Any]:
recipe = self.config.fixed_recipe("V2R2C2")
result = self._exchange(
self.create_request(
"V2R2C2",
recipe["candidate_id"],
recipe["source_commit"],
nonce=nonce or secrets.token_hex(32),
operation=self.config.readiness_operation,
)
)
if result.get("status") != "READY":
raise BuilderError("BUILDER_NOT_READY")
return result
def readiness_probe(client: UnixTrustedCandidateBuilderClient) -> Mapping[str, Any]:
result = dict(client.probe())
required_true = {"collector_ready", "signer_ready"}
if any(result.get(name) is not True for name in required_true):
raise BuilderError("BUILDER_NOT_READY")
return result
def _main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--socket-fd", required=True, type=int)
parser.add_argument("--policy", required=True, type=Path)
arguments = parser.parse_args()
config = TrustedCandidateBuilderConfig.from_policy(load_builder_policy(arguments.policy))
credentials = os.environ.get("CREDENTIALS_DIRECTORY")
if not credentials:
raise SystemExit("CREDENTIALS_DIRECTORY is required")
keyring = BuilderKeyring.from_credentials_directory(Path(credentials))
signer = CredentialPathEd25519Signer(
keyring.signing_private_key_path,
key_id=os.environ["AIOS_TRUSTED_BUILDER_KEY_ID"],
key_generation=int(os.environ["AIOS_TRUSTED_BUILDER_KEY_GENERATION"]),
allowed_purpose="AIOS_TRUSTED_RELEASE_BUILDER",
)
listener = socket.fromfd(arguments.socket_fd, socket.AF_UNIX, socket.SOCK_STREAM)
collector = DeterministicEvidenceCollector(
config,
evidence_source=ServerOwnedEvidenceSource(
config,
Path(os.environ["AIOS_TRUSTED_BUILDER_GIT_DIR"]),
Path(os.environ["AIOS_TRUSTED_BUILDER_EVIDENCE_ROOT"]),
),
)
TrustedCandidateBuilder(config, keyring, collector=collector, signer=signer, nonce_ledger_path=Path(os.environ["AIOS_TRUSTED_BUILDER_NONCE_LEDGER"])).serve_forever(listener)
if __name__ == "__main__":
_main()
+56 -21
View File
@@ -31,6 +31,8 @@ import time
from pathlib import Path
from typing import Any, Mapping, Sequence
from trusted_release_execution_plan import PlanError, TrustedReleasePlan
OID_RE = re.compile(r"^[0-9a-f]{40}$")
SHA_RE = re.compile(r"^[0-9a-f]{64}$")
PLAN_RE = re.compile(r"^sha256:[0-9a-f]{64}$")
@@ -73,6 +75,24 @@ class Binding:
production_port: int
repository_identity: str = "aios/aios"
candidate_id: str | None = None
release_plan_sha256: str | None = None
@classmethod
def from_trusted_plan(cls, plan: TrustedReleasePlan) -> "Binding":
value = plan.value
adapter = value["adapter_policy"]
binding = cls(
version=str(value["version"]), target_oid=str(value["source_commit"]),
evidence_closure_oid=str(value["evidence_closure_oid"]), artifact_sha256=str(value["artifact_sha256"]),
approval_plan_id=str(value["approval_plan_id"]), artifact_path=str(value["artifact"]["path"]),
artifact_size=int(value["artifact"]["size"]), production_service=str(adapter["service"]),
production_port=int(adapter["port"]), candidate_id=str(value["candidate_id"]),
release_plan_sha256=plan.release_plan_sha256,
)
plan.assert_binding(dataclasses.asdict(binding))
return binding
def __post_init__(self) -> None:
if not self.version or not OID_RE.fullmatch(self.target_oid):
raise ValueError("invalid release target binding")
@@ -107,6 +127,8 @@ class Binding:
"production_service": self.production_service,
"production_port": self.production_port,
"repository_identity": self.repository_identity,
"candidate_id": self.candidate_id,
"release_plan_sha256": self.release_plan_sha256,
}
return hashlib.sha256(canonical_json(payload)).hexdigest()
@@ -126,9 +148,12 @@ class Binding:
"production_service": self.production_service,
"production_port": self.production_port,
"repository_identity": self.repository_identity,
"candidate_id": self.candidate_id,
"release_plan_sha256": self.release_plan_sha256,
}
def canonical_json(value: object) -> bytes:
return (json.dumps(value, sort_keys=True, separators=(",", ":")) + "\n").encode("utf-8")
@@ -282,6 +307,12 @@ def verify_authorization(receipt: Mapping[str, Any], binding: Binding) -> dict[s
"artifact_sha256": binding.artifact_sha256,
"approval_plan_id": binding.approval_plan_id,
}
if binding.candidate_id is not None:
expected["candidate_id"] = binding.candidate_id
if binding.release_plan_sha256 is not None:
expected["release_plan_sha256"] = binding.release_plan_sha256
if receipt.get("candidate_binding_sha256") is None:
raise ManualOwnerReviewRequired("Owner authorization missing candidate binding")
for key, value in expected.items():
if receipt.get(key) != value:
raise ManualOwnerReviewRequired(f"Owner authorization mismatch: {key}")
@@ -498,7 +529,14 @@ def execute(
deploy_command: Sequence[str],
registry_command: Sequence[str],
baseline_command: Sequence[str],
release_plan_path: Path | None = None,
) -> dict[str, Any]:
plan = TrustedReleasePlan.load(release_plan_path) if release_plan_path is not None else None
if plan is not None:
try:
plan.assert_binding(dataclasses.asdict(binding))
except PlanError as exc:
raise ReleaseBlocked(f"trusted release plan mismatch: {exc}") from exc
authorization = verify_authorization(secure_json(authorization_path), binding)
lineage = verify_git_lineage(repository, binding)
artifact = verify_artifact(binding)
@@ -591,17 +629,18 @@ def execute(
def parser() -> argparse.ArgumentParser:
p = argparse.ArgumentParser()
p.add_argument("--authorization", required=True, type=Path)
p.add_argument("--release-plan", type=Path)
p.add_argument("--repository", required=True, type=Path)
p.add_argument("--state-root", required=True, type=Path)
p.add_argument("--artifact", required=True, type=Path)
p.add_argument("--artifact-size", required=True, type=int)
p.add_argument("--version", required=True)
p.add_argument("--target-oid", required=True)
p.add_argument("--evidence-closure-oid", required=True)
p.add_argument("--artifact-sha256", required=True)
p.add_argument("--approval-plan-id", required=True)
p.add_argument("--production-service", required=True)
p.add_argument("--production-port", required=True, type=int)
p.add_argument("--artifact", type=Path)
p.add_argument("--artifact-size", type=int)
p.add_argument("--version")
p.add_argument("--target-oid")
p.add_argument("--evidence-closure-oid")
p.add_argument("--artifact-sha256")
p.add_argument("--approval-plan-id")
p.add_argument("--production-service")
p.add_argument("--production-port", type=int)
p.add_argument("--repository-identity", default="aios/aios")
p.add_argument("--tag-adapter", required=True, nargs="+")
p.add_argument("--deploy-adapter", required=True, nargs="+")
@@ -612,18 +651,13 @@ def parser() -> argparse.ArgumentParser:
def main() -> int:
args = parser().parse_args()
binding = Binding(
version=args.version,
target_oid=args.target_oid,
evidence_closure_oid=args.evidence_closure_oid,
artifact_sha256=args.artifact_sha256,
approval_plan_id=args.approval_plan_id,
artifact_path=str(args.artifact),
artifact_size=args.artifact_size,
production_service=args.production_service,
production_port=args.production_port,
repository_identity=args.repository_identity,
)
plan = TrustedReleasePlan.load(args.release_plan) if args.release_plan else None
if plan is not None:
binding = Binding.from_trusted_plan(plan)
else:
legacy=(args.artifact,args.artifact_size,args.version,args.target_oid,args.evidence_closure_oid,args.artifact_sha256,args.approval_plan_id,args.production_service,args.production_port)
if any(value is None for value in legacy): raise SystemExit("legacy binding is incomplete")
binding = Binding(version=args.version,target_oid=args.target_oid,evidence_closure_oid=args.evidence_closure_oid,artifact_sha256=args.artifact_sha256,approval_plan_id=args.approval_plan_id,artifact_path=str(args.artifact),artifact_size=args.artifact_size,production_service=args.production_service,production_port=args.production_port,repository_identity=args.repository_identity)
try:
result = execute(
binding=binding,
@@ -634,6 +668,7 @@ def main() -> int:
deploy_command=parse_command(args.deploy_adapter),
registry_command=parse_command(args.registry_adapter),
baseline_command=parse_command(args.baseline_adapter),
release_plan_path=args.release_plan,
)
print(json.dumps(result, sort_keys=True))
return 0
+65 -2
View File
@@ -4,6 +4,7 @@ from __future__ import annotations
import os
import re
import subprocess
import time
import urllib.parse
from pathlib import Path
from typing import Iterable
@@ -306,14 +307,76 @@ def authoritative_diff(
from_oid = validate_git_revision(from_revision, "from", git_dir=path)
to_oid = validate_git_revision(to_revision, "to", git_dir=path)
args = ["diff", "--no-ext-diff", "--no-textconv", from_oid, to_oid]
result = run_authoritative_git(args, git_dir=path)
patch_limit = max(1, min(_MAX_OUTPUT, 256 * 1024))
process = subprocess.Popen(
["git", "--git-dir", str(path), *args],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env={**os.environ, "GIT_OPTIONAL_LOCKS": "0"},
)
chunks: list[bytes] = []
stored_bytes = 0
total_bytes = 0
started = time.monotonic()
try:
while process.stdout is not None:
chunk = process.stdout.read(64 * 1024)
if not chunk:
break
total_bytes += len(chunk)
if stored_bytes < patch_limit:
remaining = patch_limit - stored_bytes
selected = chunk[:remaining]
chunks.append(selected)
stored_bytes += len(selected)
if time.monotonic() - started > _TIMEOUT:
process.kill()
raise AuthoritativeGitError("GIT_REF_QUERY_TIMEOUT")
stderr = (
process.stderr.read(8192).decode("utf-8", errors="replace")
if process.stderr is not None
else ""
)
returncode = process.wait(timeout=max(0.1, _TIMEOUT - (time.monotonic() - started)))
except (OSError, subprocess.TimeoutExpired) as exc:
process.kill()
process.wait()
raise AuthoritativeGitError("GIT_REF_QUERY_FAILED") from exc
if returncode != 0:
raise AuthoritativeGitError(_redact(stderr.strip() or "GIT_REF_QUERY_FAILED")[:200])
try:
names_result = run_authoritative_git(
["diff", "--no-ext-diff", "--no-textconv", "--name-status", from_oid, to_oid],
git_dir=path,
)
changed_paths = [
line.split("\t", 1)[-1]
for line in names_result.stdout.splitlines()
if line.strip()
]
except AuthoritativeGitOutputTooLarge:
changed_paths = []
try:
stats_result = run_authoritative_git(
["diff", "--no-ext-diff", "--no-textconv", "--stat", from_oid, to_oid],
git_dir=path,
)
stats = stats_result.stdout.strip()
except AuthoritativeGitOutputTooLarge:
stats = "GIT_STAT_OUTPUT_TRUNCATED"
patch = b"".join(chunks).decode("utf-8", errors="replace")
return {
"code": 0,
"stdout": result.stdout,
"stdout": patch,
"stderr": "",
"cmd": ["git", f"--git-dir={path}", *args],
"from_oid": from_oid,
"to_oid": to_oid,
"changed_paths": changed_paths,
"stats": stats,
"total_size_bytes": total_bytes,
"truncated": total_bytes > patch_limit,
"patch_limit_bytes": patch_limit,
}
@@ -951,3 +951,51 @@ class AuthorizedTaskWriterProvisioner:
return {**public_result, "prestart": prestart}
finally:
shutil.rmtree(temporary_root, ignore_errors=True)
# v2 continuation validation is intentionally separate from the legacy Owner
# bootstrap path above. It consumes a recovered campaign authorization and
# never creates a branch or asks the Owner to authorize again.
from aios_continuation_lease import WriterLeaseRequestV2
def validate_writer_lease_request_v2(raw: Mapping[str, Any], probe: Any) -> dict[str, Any]:
"""Validate a CONTINUE_EXISTING request against the live exact branch head."""
required = {
"schema",
"task_id",
"repository",
"branch_mode",
"base_oid",
"expected_branch_oid",
"target_branch",
"generation",
"lease_seconds",
}
if set(raw) != required or raw.get("schema") not in WriterLeaseRequestV2.accepted_schemas:
raise ProvisioningError("LEASE_REQUEST_V2_FIELDS_REJECTED", 409)
request = WriterLeaseRequestV2(
task_id=str(raw["task_id"]),
repository=str(raw["repository"]),
branch_mode=str(raw["branch_mode"]),
base_oid=str(raw["base_oid"]),
expected_branch_oid=str(raw["expected_branch_oid"]),
target_branch=str(raw["target_branch"]),
generation=raw["generation"],
lease_seconds=raw["lease_seconds"],
)
request.validate(probe)
return {
"schema": WriterLeaseRequestV2.schema,
"task_id": request.task_id,
"repository": request.repository,
"branch_mode": request.branch_mode,
"base_oid": request.base_oid,
"expected_branch_oid": request.expected_branch_oid,
"target_branch": request.target_branch,
"generation": request.generation,
"lease_seconds": request.lease_seconds,
"create_target_branch": request.branch_mode == "CREATE_NEW",
"worktree_mode": "detached_at_base_oid" if request.branch_mode == "CREATE_NEW" else "detached_at_expected_head",
"cas_ref_update_required": True,
}
+84
View File
@@ -0,0 +1,84 @@
#!/usr/bin/env python3
"""Single canonical Production Baseline authority for the GitOps reader.
The API and the one-shot writer import this module. Workspace and release
copies are projections only; they are never selected as a fallback source.
"""
from __future__ import annotations
import hashlib
import json
import os
from pathlib import Path
from typing import Any
AUTHORITY_PATH = Path(os.getenv(
"AIOS_GITOPS_BASELINE_AUTHORITY",
"/var/lib/aios-gitops-runtime-anchor/baseline/current.json",
))
AUTHORITY_SHA_PATH = AUTHORITY_PATH.with_suffix(".sha256")
class BaselineAuthorityError(RuntimeError):
"""Fail-closed error raised for an unavailable or inconsistent authority."""
class BaselineAuthority:
def __init__(self, path: Path | str = AUTHORITY_PATH):
self.path = Path(path)
self.sha_path = self.path.with_suffix(".sha256")
def _raw(self) -> bytes:
try:
raw = self.path.read_bytes()
except OSError as exc:
raise BaselineAuthorityError(f"authority_unavailable:{exc}") from exc
if not raw:
raise BaselineAuthorityError("authority_empty")
return raw
def read(self) -> dict[str, Any]:
raw = self._raw()
digest = hashlib.sha256(raw).hexdigest()
try:
value = json.loads(raw.decode("utf-8"))
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
raise BaselineAuthorityError("authority_invalid_json") from exc
if not isinstance(value, dict):
raise BaselineAuthorityError("authority_not_object")
expected = str(value.get("authority_sha256", ""))
if expected and expected != digest:
raise BaselineAuthorityError("authority_embedded_digest_mismatch")
if self.sha_path.exists():
sidecar = self.sha_path.read_text(encoding="utf-8").strip().split()[0]
if sidecar != digest:
raise BaselineAuthorityError("authority_sidecar_digest_mismatch")
required = {
"current_baseline", "previous_baseline", "target_oid", "tag_oid",
"evidence_closure_oid", "approval_plan_id", "status", "generation",
"source_identity",
}
missing = sorted(required.difference(value))
if missing:
raise BaselineAuthorityError("authority_missing:" + ",".join(missing))
value = dict(value)
value["authority_sha256"] = digest
value["authority_path"] = str(self.path)
return value
def read_consistent(self, policy: dict[str, Any] | None = None) -> dict[str, Any]:
value = self.read()
if policy:
expected = policy.get("release_binding", policy)
for key, field in (("version", "current_baseline"),
("previous_version", "previous_baseline"),
("target_oid", "target_oid"),
("tag_oid", "tag_oid"),
("evidence_closure_oid", "evidence_closure_oid"),
("approval_plan_id", "approval_plan_id")):
wanted = expected.get(key)
if wanted is not None and value.get(field) != wanted:
raise BaselineAuthorityError(f"authority_binding_mismatch:{field}")
return value
+108
View File
@@ -0,0 +1,108 @@
"""Atomic, predicate-bound activation of the durable release campaign."""
from __future__ import annotations
import hashlib
import json
import os
from pathlib import Path
import re
import tempfile
from typing import Any, Mapping
from release_campaign_authorization import CampaignError, validate_policy
OID_RE = re.compile(r"^[0-9a-f]{40}$")
CAMPAIGN_ID = "AIOS-AUTONOMOUS-RELEASE-CAMPAIGN-20260807"
class CampaignActivationError(CampaignError):
pass
def canonical_json(value: Any) -> bytes:
return (json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False) + "\n").encode("utf-8")
def digest_bytes(value: bytes) -> str:
return hashlib.sha256(value).hexdigest()
def build_active_policy(current: Mapping[str, Any], facts: Mapping[str, Any]) -> dict[str, Any]:
policy = validate_policy(dict(current))
required = {
"campaign_id",
"repository",
"source_main_oid",
"gitea_main_oid",
"authority_main_oid",
"runtime_source_oid",
"durable_job_state",
"writer_ready",
"gitea_machine_authenticated",
"governance_executor_ready",
"preapproval_publisher_ready",
"runtime_exact_main",
"main_lineage_verified",
"campaign_revoked",
}
if set(facts) != required:
raise CampaignActivationError("ACTIVATION_FACTS_SCHEMA_INVALID")
if policy["activation_state"] != "PENDING_CONTROL_PLANE_MERGE" or policy["active"] is not False:
raise CampaignActivationError("CAMPAIGN_NOT_PENDING")
if policy["campaign_id"] != CAMPAIGN_ID or facts["campaign_id"] != CAMPAIGN_ID:
raise CampaignActivationError("CAMPAIGN_ID_MISMATCH")
if facts["repository"] != "aios/aios" or policy["repository"] != facts["repository"]:
raise CampaignActivationError("REPOSITORY_MISMATCH")
for name in ("source_main_oid", "gitea_main_oid", "authority_main_oid", "runtime_source_oid"):
if not isinstance(facts[name], str) or not OID_RE.fullmatch(facts[name]):
raise CampaignActivationError("ACTIVATION_OID_INVALID")
if not (
facts["source_main_oid"]
== facts["gitea_main_oid"]
== facts["authority_main_oid"]
== facts["runtime_source_oid"]
):
raise CampaignActivationError("ACTIVATION_MAIN_DIVERGENCE")
if facts["durable_job_state"] != "COMPLETED":
raise CampaignActivationError("DURABLE_JOB_NOT_COMPLETED")
boolean_fields = (
"writer_ready",
"gitea_machine_authenticated",
"governance_executor_ready",
"preapproval_publisher_ready",
"runtime_exact_main",
"main_lineage_verified",
)
if any(facts[name] is not True for name in boolean_fields) or facts["campaign_revoked"] is not False:
raise CampaignActivationError("ACTIVATION_PREDICATE_FAILED")
activated = dict(policy)
activated["activation_state"] = "ACTIVE"
activated["active"] = True
return validate_policy(activated)
def atomic_write_policy(path: Path, sidecar: Path, receipt_path: Path, policy: Mapping[str, Any], receipt: Mapping[str, Any]) -> str:
if path.is_symlink() or sidecar.is_symlink() or receipt_path.exists() or receipt_path.is_symlink():
raise CampaignActivationError("ACTIVATION_PATH_INVALID")
path.parent.mkdir(parents=True, exist_ok=True)
raw = canonical_json(dict(policy))
digest = digest_bytes(raw)
for target, data, mode in (
(path, raw, "wb"),
(sidecar, f"{digest} {path.name}\n".encode("ascii"), "wb"),
(receipt_path, canonical_json(dict(receipt)), "wb"),
):
fd, temporary = tempfile.mkstemp(prefix=f".{target.name}.", dir=target.parent)
try:
os.fchmod(fd, 0o600)
with os.fdopen(fd, mode) as stream:
stream.write(data)
stream.flush()
os.fsync(stream.fileno())
os.replace(temporary, target)
os.chmod(target, 0o600)
finally:
if os.path.exists(temporary):
os.unlink(temporary)
return digest
+106
View File
@@ -0,0 +1,106 @@
#!/usr/bin/env python3
"""Machine campaign approval gate for protected engineering PRs."""
from __future__ import annotations
import argparse
import json
from pathlib import Path
import subprocess
from typing import Any, Mapping
from release_campaign_authorization import CampaignAuthorization, CampaignError
class GateError(RuntimeError):
pass
def git_is_ancestor(repository: Path, ancestor: str, descendant: str) -> bool:
result = subprocess.run(
[
"git",
f"--git-dir={repository}",
"merge-base",
"--is-ancestor",
ancestor,
descendant,
],
check=False,
capture_output=True,
)
if result.returncode == 0:
return True
if result.returncode == 1:
return False
raise GateError("git_ancestor_check_failed")
def evaluate(
campaign: CampaignAuthorization,
facts: Mapping[str, Any],
repository: Path,
) -> dict[str, Any]:
required = {
"repository",
"pr_number",
"base_ref",
"base_oid",
"head_oid",
"required_checks_all_success",
"required_checks_same_head",
"unresolved_conversations",
"mergeable",
"protected_target",
}
if set(facts) != required:
raise GateError("pr_facts_fields_invalid")
if facts["repository"] != "aios/aios":
raise GateError("repository_mismatch")
if facts["base_ref"] != "refs/heads/main" or facts["protected_target"] != "main":
raise GateError("protected_main_required")
if facts["required_checks_all_success"] is not True:
raise GateError("required_checks_not_success")
if facts["required_checks_same_head"] is not True:
raise GateError("required_checks_stale")
if facts["unresolved_conversations"] != 0:
raise GateError("unresolved_conversations")
if facts["mergeable"] is not True:
raise GateError("pr_not_mergeable")
if not git_is_ancestor(repository, campaign.anchor_oid, str(facts["base_oid"])):
raise GateError("base_outside_campaign_lineage")
if not git_is_ancestor(repository, str(facts["base_oid"]), str(facts["head_oid"])):
raise GateError("head_outside_pr_lineage")
campaign.assert_operation("protected_merge")
return {
"schema": "aios.campaign-approval-gate-result.v1",
"status": "SUCCESS",
"campaign_id": campaign.campaign_id,
"pr_number": facts["pr_number"],
"head_oid": facts["head_oid"],
"owner_ui_interaction_required": False,
}
def main() -> int:
p = argparse.ArgumentParser()
p.add_argument("--campaign", type=Path, required=True)
p.add_argument("--revocation", type=Path)
p.add_argument("--pr-facts", type=Path, required=True)
p.add_argument("--repository", type=Path, required=True)
a = p.parse_args()
campaign = CampaignAuthorization.load(
a.campaign,
revocation=a.revocation,
require_active=True,
)
facts = json.loads(a.pr_facts.read_text(encoding="utf-8"))
print(json.dumps(evaluate(campaign, facts, a.repository), sort_keys=True))
return 0
if __name__ == "__main__":
try:
raise SystemExit(main())
except (CampaignError, GateError) as exc:
print(json.dumps({"status": "FAILURE", "reason": str(exc)}, sort_keys=True))
raise SystemExit(2)
+189
View File
@@ -0,0 +1,189 @@
#!/usr/bin/env python3
"""Automatically accept an exact release binding under an ACTIVE campaign."""
from __future__ import annotations
import argparse
import hashlib
import json
import os
from pathlib import Path
import stat
import tempfile
from typing import Any, Mapping
from release_campaign_authorization import CampaignAuthorization, CampaignError
from trusted_release_execution_plan import PlanError, TrustedReleasePlan
class AcceptanceError(RuntimeError):
pass
def secure_json(path: Path) -> dict[str, Any]:
if path.is_symlink() or not path.is_file():
raise AcceptanceError(f"invalid_file:{path}")
st = path.lstat()
if st.st_uid != 0 or stat.S_IMODE(st.st_mode) & 0o022:
raise AcceptanceError(f"unsafe_file:{path}")
value = json.loads(path.read_text(encoding="utf-8"))
if not isinstance(value, dict):
raise AcceptanceError(f"json_object_required:{path}")
return value
def atomic_new(path: Path, value: Mapping[str, Any]) -> None:
if path.exists() or path.is_symlink():
raise AcceptanceError("acceptance_receipt_exists")
path.parent.mkdir(parents=True, exist_ok=True)
fd, name = tempfile.mkstemp(prefix=f".{path.name}.", dir=path.parent)
try:
os.fchmod(fd, 0o600)
if os.geteuid() == 0:
os.fchown(fd, 0, 0)
with os.fdopen(fd, "w", encoding="utf-8", closefd=True) as stream:
json.dump(dict(value), stream, sort_keys=True, indent=2)
stream.write("\n")
stream.flush()
os.fsync(stream.fileno())
os.replace(name, path)
os.chmod(path, 0o600)
finally:
try:
os.unlink(name)
except FileNotFoundError:
pass
def evaluate(
campaign: CampaignAuthorization,
binding: Mapping[str, Any],
evidence: Mapping[str, Any],
artifact: Mapping[str, Any],
staging: Mapping[str, Any],
main: Mapping[str, Any],
tag: Mapping[str, Any],
release_plan: Mapping[str, Any] | None = None,
) -> dict[str, Any]:
required_binding = {
"version",
"target_oid",
"evidence_closure_oid",
"artifact_sha256",
"artifact_size",
"approval_plan_id",
}
if set(binding) != required_binding:
raise AcceptanceError("binding_fields_invalid")
if main.get("repository") != "aios/aios":
raise AcceptanceError("repository_identity_mismatch")
if main.get("target_in_main_lineage") is not True:
raise AcceptanceError("target_outside_main_lineage")
if evidence.get("object_type") != "commit":
raise AcceptanceError("evidence_not_commit")
if evidence.get("target_is_ancestor") is not True:
raise AcceptanceError("target_not_evidence_ancestor")
if evidence.get("closure_oid") != binding["evidence_closure_oid"]:
raise AcceptanceError("evidence_oid_mismatch")
if artifact.get("sha256") != binding["artifact_sha256"]:
raise AcceptanceError("artifact_digest_mismatch")
if artifact.get("size") != binding["artifact_size"]:
raise AcceptanceError("artifact_size_mismatch")
if artifact.get("retained") is not True:
raise AcceptanceError("artifact_not_retained")
if staging.get("install") != "PASS":
raise AcceptanceError("staging_install_failed")
if staging.get("health") != "PASS":
raise AcceptanceError("staging_health_failed")
if staging.get("restart") != "PASS":
raise AcceptanceError("staging_restart_failed")
if staging.get("rollback_readiness") != "PASS":
raise AcceptanceError("rollback_readiness_failed")
if tag.get("conflict") is not False:
raise AcceptanceError("tag_conflict")
trusted_plan: TrustedReleasePlan | None = None
if release_plan is not None:
try:
trusted_plan = TrustedReleasePlan.from_mapping(release_plan)
trusted_plan.assert_binding(binding)
except PlanError as exc:
raise AcceptanceError(f"trusted_release_plan_invalid:{exc}") from exc
if trusted_plan.value["campaign_authorization_sha256"] != campaign.digest:
raise AcceptanceError("campaign_authorization_digest_mismatch")
if int(trusted_plan.value["campaign_generation"]) != int(campaign.data["generation"]):
raise AcceptanceError("campaign_generation_mismatch")
if main.get("tree_oid") not in (None, trusted_plan.value["tree_oid"]):
raise AcceptanceError("tree_oid_mismatch")
if evidence.get("release_plan_sha256") not in (None, trusted_plan.release_plan_sha256):
raise AcceptanceError("evidence_release_plan_mismatch")
if artifact.get("manifest_sha256") not in (None, trusted_plan.value["manifest_sha256"]):
raise AcceptanceError("manifest_digest_mismatch")
if artifact.get("package_closure_sha256") not in (None, trusted_plan.value["package_closure_sha256"]):
raise AcceptanceError("package_closure_digest_mismatch")
campaign.assert_operation("derive_campaign_acceptance")
result = {
"schema": "aios.campaign-release-acceptance.v1",
"accepted": True,
"binding_replaced": False,
"campaign_id": campaign.campaign_id,
"campaign_generation": campaign.data["generation"],
**dict(binding),
"owner_interaction_required": False,
}
if trusted_plan is not None:
result.update({
"candidate_id": trusted_plan.value["candidate_id"],
"release_plan_sha256": trusted_plan.release_plan_sha256,
"candidate_binding_sha256": trusted_plan.candidate_binding_sha256,
"campaign_authorization_sha256": trusted_plan.value["campaign_authorization_sha256"],
"campaign_generation": trusted_plan.value["campaign_generation"],
"tree_oid": trusted_plan.value["tree_oid"],
"manifest_sha256": trusted_plan.value["manifest_sha256"],
"package_closure_sha256": trusted_plan.value["package_closure_sha256"],
"deployment_plan_sha256": trusted_plan.value["deployment_plan_sha256"],
"policy_sha256": trusted_plan.value["policy_sha256"],
"rollback": trusted_plan.value["rollback"],
"baseline": trusted_plan.value["baseline"],
})
return result
def main() -> int:
p = argparse.ArgumentParser()
p.add_argument("--campaign", type=Path, required=True)
p.add_argument("--revocation", type=Path)
p.add_argument("--binding", type=Path, required=True)
p.add_argument("--evidence", type=Path, required=True)
p.add_argument("--artifact", type=Path, required=True)
p.add_argument("--staging", type=Path, required=True)
p.add_argument("--main-facts", type=Path, required=True)
p.add_argument("--tag-facts", type=Path, required=True)
p.add_argument("--release-plan", type=Path)
p.add_argument("--output", type=Path, required=True)
a = p.parse_args()
campaign = CampaignAuthorization.load(
a.campaign,
revocation=a.revocation,
require_active=True,
)
receipt = evaluate(
campaign,
secure_json(a.binding),
secure_json(a.evidence),
secure_json(a.artifact),
secure_json(a.staging),
secure_json(a.main_facts),
secure_json(a.tag_facts),
secure_json(a.release_plan) if a.release_plan else None,
)
atomic_new(a.output, receipt)
print(json.dumps(receipt, sort_keys=True))
return 0
if __name__ == "__main__":
try:
raise SystemExit(main())
except (CampaignError, AcceptanceError) as exc:
print(json.dumps({"status": "BLOCKED", "reason": str(exc)}, sort_keys=True))
raise SystemExit(2)
+16 -7
View File
@@ -8,6 +8,7 @@ import json
import os
import re
import subprocess
import sys
import tempfile
import time
import urllib.error
@@ -23,10 +24,15 @@ try:
from authoritative_git import AuthoritativeGitError, run_authoritative_git
except ModuleNotFoundError:
from .authoritative_git import AuthoritativeGitError, run_authoritative_git
try:
from release_version import RELEASE_VERSION_RE, validate_release_version
except ModuleNotFoundError:
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
from release_version import RELEASE_VERSION_RE, validate_release_version
OID_RE = re.compile(r"^[0-9a-f]{40}$")
VERSION_RE = re.compile(r"^V\d+R\d+C\d+(?:-[A-Za-z0-9_.-]+)?$")
VERSION_RE = RELEASE_VERSION_RE
DEPLOYMENT_EVIDENCE_RE = re.compile(r"^(?:sha256:[0-9a-f]{64}|git:[0-9a-f]{40})$")
FINGERPRINT_FIELDS = (
"version",
@@ -83,9 +89,14 @@ class PublicationManualOwnerReviewRequired(
def validate_version(version: str) -> str:
value = str(version or "")
if not VERSION_RE.fullmatch(value):
raise ValueError("invalid release version")
return validate_release_version(version)
def validate_gitea_api_url(api_url: str) -> str:
value = str(api_url or "").strip().rstrip("/")
parsed = urllib.parse.urlsplit(value)
if parsed.scheme not in {"http", "https"} or not parsed.netloc:
raise ValueError("Gitea API URL must be HTTP(S)")
return value
@@ -411,13 +422,11 @@ class GiteaTagClient:
timeout: float = 15,
token_line: int = 1,
):
self.api_url = api_url.rstrip("/")
self.api_url = validate_gitea_api_url(api_url)
self.repository = repository.strip("/")
self.token_file = token_file
self.timeout = timeout
self.token_line = token_line
if not self.api_url.startswith(("https://", "http://")):
raise ValueError("Gitea API URL must be HTTP(S)")
if self.repository.count("/") != 1:
raise ValueError("Gitea repository must be owner/name")
@@ -0,0 +1 @@
"""Permanent AIOS governance source components."""
@@ -0,0 +1,75 @@
#!/usr/bin/env python3
import json
import pathlib
import socket
import time
SOCKET = '/run/aios-governance-executor/executor.sock'
STATE = pathlib.Path('/var/lib/aios-gitops/governance-executor/drift-monitor.json')
APPROVED = pathlib.Path('/var/lib/aios-gitops/runtime/tasks/AIOS-PROTECTION-SNAPSHOT-DRIFT-FORENSICS-V1/protection-drift/semantic-analysis.json')
def call():
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.settimeout(20)
sock.connect(SOCKET)
request = {'operation': 'GET_BRANCH_PROTECTION', 'payload': {'repository': 'aios/aios', 'branch': 'main'}}
sock.sendall((json.dumps(request, separators=(',', ':')) + '\n').encode())
chunks = []
while True:
part = sock.recv(1024 * 1024)
if not part:
break
chunks.append(part)
if b'\n' in part:
break
sock.close()
return json.loads(b''.join(chunks).splitlines()[0])
def main():
try:
result = call()
if result.get('status') != 'PASS':
raise RuntimeError(str(result.get('code') or 'EXECUTOR_GET_FAILED'))
approved = json.loads(APPROVED.read_text(encoding='utf-8'))
approved_transport = approved['approved_transport_digest_from_evidence']
approved_semantic = approved['approved_semantic_digest']
transport = result['transport_digest']
semantic = result['semantic_digest']
if semantic != approved_semantic:
state = 'GOVERNANCE_POLICY_DRIFT'
elif transport != approved_transport:
state = 'GOVERNANCE_TRANSPORT_METADATA_CHANGED'
else:
state = 'NO_DRIFT'
payload = {
'schema': 'aios.governance-drift-monitor.v1',
'observed_at_unix': int(time.time()),
'status': state,
'release_blocking': False,
'approved_transport_digest': approved_transport,
'observed_transport_digest': transport,
'approved_semantic_digest': approved_semantic,
'observed_semantic_digest': semantic,
'semantic_schema': result.get('semantic_schema'),
'source': 'Dedicated Governance Executor GET_BRANCH_PROTECTION',
}
except Exception as exc:
payload = {
'schema': 'aios.governance-drift-monitor.v1',
'observed_at_unix': int(time.time()),
'status': 'MONITOR_UNAVAILABLE',
'release_blocking': False,
'error_code': str(exc),
}
STATE.parent.mkdir(parents=True, exist_ok=True)
temporary = STATE.with_suffix('.tmp')
temporary.write_text(json.dumps(payload, sort_keys=True, separators=(',', ':'), ensure_ascii=False) + '\n', encoding='utf-8')
temporary.replace(STATE)
STATE.chmod(0o640)
print(json.dumps(payload, sort_keys=True, separators=(',', ':'), ensure_ascii=False))
if __name__ == '__main__':
main()
+417
View File
@@ -0,0 +1,417 @@
#!/usr/bin/env python3
import base64
import copy
import hashlib
import json
import os
import pathlib
import socket
import time
import urllib.error
import urllib.parse
import urllib.request
SOURCE_GOVERNANCE = "/opt/aios/aios-control-plane/current/active-runtime/app/governance"
if SOURCE_GOVERNANCE not in __import__("sys").path:
__import__("sys").path.insert(0, SOURCE_GOVERNANCE)
from stage2_gate_migration import (
CAMPAIGN_GATE,
CAMPAIGN_ID,
OWNER_GATE,
STAGE1_SEMANTIC_SHA,
Stage2GateError,
build_target,
semantic_digest as stage2_semantic_digest,
validate_stage2_facts,
)
API = "http://127.0.0.1:3000"
REPO = "aios/aios"
SOCKET = "/run/aios-governance-executor/executor.sock"
JOURNAL = pathlib.Path("/var/lib/aios-gitops/governance-executor/executor-journal.jsonl")
RECONCILIATION_BINDING_PATH = pathlib.Path("/var/lib/aios-gitops/governance-executor/reconciliation-binding.json")
PR54_HEAD = "015185d7e5191d18c5c1596160807ac228782a0a"
PR54_REVIEW = 104
PR54_MANIFEST_SHA = "1b11ee42d53a9f6c1646e5f5ddd8ce5b7ecf301483b9f3d47a068290d541ba4b"
PR54_PREVIOUS_PROTECTION_SHA = "8b3370add21cd23c2b4f41d8d84d3eaaa09ddf0451549b72c7941bbd92d5d60a"
SEMANTIC_SCHEMA = "aios.semantic-protection.v1"
APPROVED_SEMANTIC_SHA = "ba15d512ed8c1f43e5f72c84125a3a71a7347ef59e18000bbfca24d6e69f1b19"
STAGE1_SEMANTIC_SHA = "d911e856f71de6105b8efa08e13338a448e0b6d37536332decd80c9f409ecf27"
USER_IDS = {"owner": 2, "aios-autonomy-agent": 153}
def canon(value):
return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode()
def sha(value):
return hashlib.sha256(canon(value)).hexdigest()
def token():
value = (pathlib.Path(os.environ["CREDENTIALS_DIRECTORY"]) / "gitea-governance-executor-token").read_text().strip()
if len(value) < 16:
raise RuntimeError("EXECUTOR_CREDENTIAL_INVALID")
return value
def api(method, path, value, body=None, expected=(200,)):
raw = None if body is None else json.dumps(body, separators=(",", ":")).encode()
req = urllib.request.Request(API + path, data=raw, method=method)
req.add_header("Authorization", "token " + value)
req.add_header("Accept", "application/json")
if raw is not None:
req.add_header("Content-Type", "application/json")
try:
with urllib.request.urlopen(req, timeout=20) as response:
status = response.status
payload = response.read()
except urllib.error.HTTPError as exc:
if exc.code in expected:
payload = exc.read()
return exc.code, json.loads(payload) if payload else None
raise RuntimeError("GITEA_HTTP_" + str(exc.code))
if status not in expected:
raise RuntimeError("GITEA_HTTP_" + str(status))
if not payload or not payload.strip():
raise RuntimeError("GITEA_EMPTY_JSON:" + path)
try:
decoded = json.loads(payload)
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
raise RuntimeError("GITEA_INVALID_JSON:" + path) from exc
return status, decoded
def journal(event, **fields):
JOURNAL.parent.mkdir(parents=True, exist_ok=True)
record = {"schema": "aios.governance-executor-journal.v2", "event": event, "unix": int(time.time()), **fields}
with JOURNAL.open("a", encoding="utf-8") as stream:
stream.write(json.dumps(record, sort_keys=True, separators=(",", ":"), ensure_ascii=False) + "\n")
stream.flush()
os.fsync(stream.fileno())
def typed_json(value):
if value in (None, "null"):
return []
if isinstance(value, str):
return json.loads(value)
return value
def user_ids(names):
result = []
for name in names or []:
if name not in USER_IDS:
raise RuntimeError("UNKNOWN_GOVERNANCE_USER:" + str(name))
result.append(USER_IDS[name])
return sorted(result)
def semantic_policy(value):
policy = {
"schema_version": SEMANTIC_SCHEMA,
"repo_id": 1,
"protection_id": 2,
"branch_name": value.get("branch_name"),
"can_push": value.get("enable_push"),
"can_force_push": value.get("enable_force_push"),
"enable_merge_whitelist": value.get("enable_merge_whitelist"),
"merge_whitelist_user_ids": user_ids(value.get("merge_whitelist_usernames")),
"merge_whitelist_team_ids": [],
"required_approvals": value.get("required_approvals"),
"enable_status_check": value.get("enable_status_check"),
"status_check_contexts": sorted(value.get("status_check_contexts") or []),
"block_admin_merge_override": value.get("block_admin_merge_override"),
"block_on_official_review_requests": value.get("block_on_official_review_requests"),
"block_on_outdated_branch": value.get("block_on_outdated_branch"),
"block_on_rejected_reviews": value.get("block_on_rejected_reviews"),
"dismiss_stale_approvals": value.get("dismiss_stale_approvals"),
"ignore_stale_approvals": value.get("ignore_stale_approvals"),
"enable_approvals_whitelist": value.get("enable_approvals_whitelist"),
"approvals_whitelist_user_ids": user_ids(value.get("approvals_whitelist_username")),
"approvals_whitelist_team_ids": [],
"enable_bypass_allowlist": value.get("enable_bypass_allowlist"),
"bypass_allowlist_user_ids": user_ids(value.get("bypass_allowlist_usernames")),
"bypass_allowlist_team_ids": [],
"enable_force_push_allowlist": value.get("enable_force_push_allowlist"),
"force_push_allowlist_user_ids": user_ids(value.get("force_push_allowlist_usernames")),
"force_push_allowlist_team_ids": [],
"enable_whitelist": value.get("enable_push_whitelist"),
"whitelist_user_ids": user_ids(value.get("push_whitelist_usernames")),
"whitelist_team_ids": [],
"force_push_allowlist_deploy_keys": value.get("force_push_allowlist_deploy_keys"),
"whitelist_deploy_keys": value.get("push_whitelist_deploy_keys"),
"require_signed_commits": value.get("require_signed_commits"),
"priority": value.get("priority"),
"protected_file_patterns": value.get("protected_file_patterns"),
"unprotected_file_patterns": value.get("unprotected_file_patterns"),
}
return policy
def semantic_sha(value):
return sha(semantic_policy(value))
def verify_binding(binding, value):
required = {
"governance_request_PR", "exact_request_head", "Owner_review_id", "Owner_review_exact_head",
"manifest_sha256", "expected_previous_snapshot_sha256", "approved_delta",
"semantic_reconciliation_request_id", "semantic_reconciliation_head",
"semantic_reconciliation_review_id", "semantic_reconciliation_review_head",
"approved_semantic_digest",
}
if set(binding) != required:
raise RuntimeError("GOVERNANCE_BINDING_SCHEMA_INVALID")
if binding["governance_request_PR"] != 54 or binding["exact_request_head"] != PR54_HEAD:
raise RuntimeError("GOVERNANCE_BINDING_PR54_MISMATCH")
if binding["Owner_review_id"] != PR54_REVIEW or binding["Owner_review_exact_head"] != PR54_HEAD:
raise RuntimeError("GOVERNANCE_BINDING_REVIEW_MISMATCH")
if binding["manifest_sha256"] != PR54_MANIFEST_SHA:
raise RuntimeError("GOVERNANCE_BINDING_MANIFEST_MISMATCH")
if binding["expected_previous_snapshot_sha256"] != PR54_PREVIOUS_PROTECTION_SHA:
raise RuntimeError("GOVERNANCE_BINDING_SNAPSHOT_MISMATCH")
if binding["approved_delta"] != {"add_merge_whitelist_user_id": 153}:
raise RuntimeError("GOVERNANCE_BINDING_DELTA_DENIED")
if binding["approved_semantic_digest"] != APPROVED_SEMANTIC_SHA:
raise RuntimeError("GOVERNANCE_BINDING_SEMANTIC_DIGEST_MISMATCH")
if not RECONCILIATION_BINDING_PATH.is_file():
raise RuntimeError("SEMANTIC_RECONCILIATION_BINDING_REQUIRED")
try:
reconciliation = json.loads(RECONCILIATION_BINDING_PATH.read_text(encoding="utf-8"))
except (OSError, ValueError):
raise RuntimeError("SEMANTIC_RECONCILIATION_BINDING_INVALID") from None
required_reconciliation = {
"schema", "request_id", "pr_number", "request_head", "owner_review_id",
"owner_review_head", "approved_semantic_digest", "approved_delta",
}
if set(reconciliation) != required_reconciliation or reconciliation.get("schema") != "aios.semantic-reconciliation-binding.v1":
raise RuntimeError("SEMANTIC_RECONCILIATION_BINDING_INVALID")
if reconciliation["approved_semantic_digest"] != APPROVED_SEMANTIC_SHA or reconciliation["approved_delta"] != binding["approved_delta"]:
raise RuntimeError("SEMANTIC_RECONCILIATION_BINDING_MISMATCH")
if (
reconciliation["request_id"] != binding["semantic_reconciliation_request_id"]
or reconciliation["request_head"] != binding["semantic_reconciliation_head"]
or reconciliation["owner_review_id"] != binding["semantic_reconciliation_review_id"]
or reconciliation["owner_review_head"] != binding["semantic_reconciliation_review_head"]
):
raise RuntimeError("SEMANTIC_RECONCILIATION_BINDING_MISMATCH")
_, reconciliation_pr = api("GET", f"/api/v1/repos/{REPO}/pulls/{reconciliation['pr_number']}", value)
if (
reconciliation_pr.get("state") != "open"
or reconciliation_pr.get("merged")
or reconciliation_pr.get("head", {}).get("sha") != reconciliation["request_head"]
or reconciliation_pr.get("base", {}).get("ref") != "governance/approvals"
):
raise RuntimeError("SEMANTIC_RECONCILIATION_REQUEST_STALE")
_, reconciliation_reviews = api("GET", f"/api/v1/repos/{REPO}/pulls/{reconciliation['pr_number']}/reviews", value)
reconciliation_review = next((x for x in reconciliation_reviews if x.get("id") == reconciliation["owner_review_id"]), None)
if (
not reconciliation_review
or reconciliation_review.get("user", {}).get("id") != 2
or reconciliation_review.get("state") != "APPROVED"
or reconciliation_review.get("commit_id") != reconciliation["owner_review_head"]
or reconciliation_review.get("dismissed") is not False
):
raise RuntimeError("SEMANTIC_RECONCILIATION_OWNER_REVIEW_STALE")
_, pr = api("GET", f"/api/v1/repos/{REPO}/pulls/54", value)
if pr.get("state") != "open" or pr.get("merged") or pr.get("head", {}).get("sha") != PR54_HEAD:
raise RuntimeError("PR54_APPROVAL_STALE")
_, reviews = api("GET", f"/api/v1/repos/{REPO}/pulls/54/reviews", value)
review = next((x for x in reviews if x.get("id") == PR54_REVIEW), None)
if not review or review.get("user", {}).get("id") != 2 or review.get("state") != "APPROVED" or review.get("commit_id") != PR54_HEAD or review.get("dismissed") is not False:
raise RuntimeError("PR54_APPROVAL_STALE")
manifest_path = "approvals/governance-policy/AIOS-AUTONOMOUS-MERGE-GOVERNANCE-SCOPE-EXTENSION-V1.json"
_, content = api("GET", f"/api/v1/repos/{REPO}/contents/{urllib.parse.quote(manifest_path, safe='/')}?ref={PR54_HEAD}", value)
raw = base64.b64decode("".join(content.get("content", "").split()))
if hashlib.sha256(raw).hexdigest() != PR54_MANIFEST_SHA:
raise RuntimeError("PR54_MANIFEST_STALE")
def get_protection(value, branch):
if branch not in {"main", "governance/approvals"}:
raise RuntimeError("BRANCH_DENIED")
_, protection = api("GET", f"/api/v1/repos/{REPO}/branch_protections/{urllib.parse.quote(branch, safe='')}", value)
return protection
def mutation_receipt(before, after, binding, target_semantic):
return {
"before": {"transport_digest": sha(before), "semantic_digest": semantic_sha(before)},
"approved": {
"mutation_predicate_digest": sha({"operation": "ADD_MERGE_EXECUTOR", "subject_user_id": 153, "preserve_all_unmodified_security_fields": True}),
"Owner_review_id": binding["Owner_review_id"],
"semantic_policy_digest": APPROVED_SEMANTIC_SHA,
},
"after": {"transport_digest": sha(after), "semantic_digest": semantic_sha(after)},
"delta": {"add_merge_whitelist_user_id": 153},
"target_semantic_digest": sha(target_semantic),
"unmodified_security_fields": {"verified": True},
}
def apply_stage1(binding, value):
verify_binding(binding, value)
before = get_protection(value, "main")
before_semantic = semantic_policy(before)
before_semantic_sha = sha(before_semantic)
names = list(before.get("merge_whitelist_usernames") or [])
name_ids = user_ids(names)
if name_ids not in ([2], [2, 153]):
raise RuntimeError("PROTECTION_WHITELIST_PRESTATE_INVALID")
target_names = ["owner", "aios-autonomy-agent"]
target_semantic = copy.deepcopy(before_semantic)
target_semantic["merge_whitelist_user_ids"] = [2, 153]
if name_ids == [2, 153]:
if before_semantic_sha != STAGE1_SEMANTIC_SHA:
journal("APPLY_BLOCKED_STAGE1_IDEMPOTENCY_SEMANTIC_CONFLICT", transport_digest=sha(before), semantic_digest=before_semantic_sha, expected_semantic_digest=STAGE1_SEMANTIC_SHA)
raise RuntimeError("PROTECTION_STAGE1_SEMANTIC_CONFLICT")
receipt = mutation_receipt(before, before, binding, target_semantic)
journal("APPLY_APPROVED_PROTECTION_DELTA_IDEMPOTENT_SUCCESS", receipt=receipt)
return {"status": "IDEMPOTENT_SUCCESS", "protection": before, "transport_digest": sha(before), "semantic_digest": before_semantic_sha, "target_semantic_digest": sha(target_semantic)}
if before_semantic_sha != APPROVED_SEMANTIC_SHA:
journal("APPLY_BLOCKED_SEMANTIC_CAS", transport_digest=sha(before), semantic_digest=before_semantic_sha, expected_semantic_digest=APPROVED_SEMANTIC_SHA)
raise RuntimeError("PROTECTION_SEMANTIC_CAS_CONFLICT")
after_api = api("PATCH", f"/api/v1/repos/{REPO}/branch_protections/main", value, {"merge_whitelist_usernames": target_names})[1]
after = get_protection(value, "main")
for key in before:
if key in {"updated_at", "merge_whitelist_usernames"}:
continue
if after.get(key) != before.get(key):
raise RuntimeError("PROTECTION_UNAPPROVED_FIELD_CHANGED:" + key)
if user_ids(after.get("merge_whitelist_usernames")) != [2, 153]:
raise RuntimeError("PROTECTION_TARGET_READBACK_FAILED")
after_semantic = semantic_policy(after)
if after_semantic != target_semantic:
raise RuntimeError("PROTECTION_SEMANTIC_TARGET_READBACK_FAILED")
receipt = mutation_receipt(before, after, binding, target_semantic)
journal("APPLY_APPROVED_PROTECTION_DELTA_COMPLETED", receipt=receipt)
return {"status": "APPLIED", "protection": after, "previous_transport_digest": sha(before), "previous_semantic_digest": before_semantic_sha, "transport_digest": sha(after), "semantic_digest": sha(after_semantic), "receipt": receipt}
def apply_stage2(binding, facts, value):
verify_binding(binding, value)
validate_stage2_facts(facts)
before = get_protection(value, "main")
if OWNER_GATE in (before.get("status_check_contexts") or []) and stage2_semantic_digest(before) != STAGE1_SEMANTIC_SHA:
raise RuntimeError("STAGE2_SEMANTIC_PRECONDITION_INVALID")
try:
status, target = build_target(before)
except Stage2GateError as exc:
journal("MIGRATE_APPROVED_GATE_BLOCKED", reason=str(exc), semantic_digest=stage2_semantic_digest(before))
raise RuntimeError(str(exc)) from exc
target_digest = stage2_semantic_digest(target)
if status == "IDEMPOTENT_SUCCESS":
journal(
"MIGRATE_APPROVED_GATE_IDEMPOTENT_SUCCESS",
before_semantic_digest=stage2_semantic_digest(before),
target_semantic_digest=target_digest,
campaign_id=CAMPAIGN_ID,
)
return {
"status": status,
"protection": before,
"transport_digest": sha(before),
"semantic_digest": stage2_semantic_digest(before),
"target_semantic_digest": target_digest,
"gate_transition": {"removed": OWNER_GATE, "required": CAMPAIGN_GATE},
}
after_api = api(
"PATCH",
f"/api/v1/repos/{REPO}/branch_protections/main",
value,
{"status_check_contexts": target["status_check_contexts"]},
)[1]
after = get_protection(value, "main")
for key in before:
if key in {"updated_at", "status_check_contexts"}:
continue
if after.get(key) != before.get(key):
raise RuntimeError("PROTECTION_UNAPPROVED_FIELD_CHANGED:" + key)
if after.get("status_check_contexts") != target["status_check_contexts"]:
raise RuntimeError("STAGE2_GATE_TARGET_READBACK_FAILED")
if stage2_semantic_digest(after) != target_digest:
raise RuntimeError("STAGE2_SEMANTIC_TARGET_READBACK_FAILED")
receipt = {
"before_transport_digest": sha(before),
"before_semantic_digest": stage2_semantic_digest(before),
"after_transport_digest": sha(after),
"after_semantic_digest": stage2_semantic_digest(after),
"target_semantic_digest": target_digest,
"campaign_id": CAMPAIGN_ID,
"gate_transition": {"removed": OWNER_GATE, "required": CAMPAIGN_GATE},
"preserved_security_fields": True,
"facts_digest": sha(facts),
}
journal("MIGRATE_APPROVED_GATE_COMPLETED", receipt=receipt)
return {
"status": "APPLIED",
"protection": after,
"previous_transport_digest": sha(before),
"previous_semantic_digest": stage2_semantic_digest(before),
"transport_digest": sha(after),
"semantic_digest": stage2_semantic_digest(after),
"target_semantic_digest": target_digest,
"receipt": receipt,
}
def handle(request, value):
if not isinstance(request, dict) or set(request) != {"operation", "payload"}:
raise RuntimeError("REQUEST_SCHEMA_INVALID")
operation = request["operation"]
payload = request["payload"]
if not isinstance(payload, dict) or payload.get("repository") != REPO:
raise RuntimeError("REPOSITORY_DENIED")
if operation == "GET_BRANCH_PROTECTION":
protection = get_protection(value, payload.get("branch"))
journal("GET_BRANCH_PROTECTION", branch=payload.get("branch"), transport_digest=sha(protection), semantic_digest=semantic_sha(protection))
return {"status": "PASS", "protection": protection, "snapshot_sha256": sha(protection), "transport_digest": sha(protection), "semantic_digest": semantic_sha(protection), "semantic_schema": SEMANTIC_SCHEMA}
if operation == "READBACK_BRANCH_PROTECTION":
protection = get_protection(value, payload.get("branch"))
return {"status": "PASS", "protection": protection, "snapshot_sha256": sha(protection), "transport_digest": sha(protection), "semantic_digest": semantic_sha(protection), "semantic_schema": SEMANTIC_SCHEMA}
if operation == "APPLY_APPROVED_PROTECTION_DELTA":
binding = payload.get("GovernanceExecutionBinding")
if not isinstance(binding, dict):
raise RuntimeError("GOVERNANCE_BINDING_REQUIRED")
return apply_stage1(binding, value)
if operation == "MIGRATE_APPROVED_GATE":
binding = payload.get("GovernanceExecutionBinding")
facts = payload.get("Stage2Facts")
if not isinstance(binding, dict) or not isinstance(facts, dict):
raise RuntimeError("STAGE2_BINDING_OR_FACTS_REQUIRED")
return apply_stage2(binding, facts, value)
raise RuntimeError("OPERATION_DENIED")
def main():
value = token()
sock_path = pathlib.Path(SOCKET)
sock_path.unlink(missing_ok=True)
server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
server.bind(SOCKET)
os.chmod(SOCKET, 0o660)
server.listen(16)
journal("EXECUTOR_STARTED", api_surface=["GET_BRANCH_PROTECTION", "APPLY_APPROVED_PROTECTION_DELTA", "MIGRATE_APPROVED_GATE", "READBACK_BRANCH_PROTECTION"], semantic_schema=SEMANTIC_SCHEMA, approved_semantic_digest=APPROVED_SEMANTIC_SHA, stage1_semantic_digest=STAGE1_SEMANTIC_SHA, campaign_gate=CAMPAIGN_GATE)
try:
while True:
conn, _ = server.accept()
with conn:
data = conn.recv(1024 * 1024 + 1)
if len(data) > 1024 * 1024:
result = {"status": "FAIL", "code": "REQUEST_TOO_LARGE"}
else:
try:
request = json.loads(data.decode())
result = handle(request, value)
except Exception as exc:
result = {"status": "FAIL", "code": str(exc)}
conn.sendall((json.dumps(result, sort_keys=True, separators=(",", ":"), ensure_ascii=False) + "\n").encode())
finally:
server.close()
sock_path.unlink(missing_ok=True)
if __name__ == "__main__":
main()
@@ -0,0 +1,347 @@
#!/usr/bin/env python3
import base64
import hashlib
import json
import os
import pathlib
import shutil
import socket
import subprocess
import tempfile
import time
import urllib.error
import urllib.parse
import urllib.request
LOCAL_API = 'http://127.0.0.1:3000/api/v1'
NETWORK_API = 'https://g.cnnetmkt.top/git/api/v1'
NETWORK_REMOTE = 'https://g.cnnetmkt.top/git/aios/aios.git'
REPO = 'aios/aios'
BASE_BRANCH = 'governance/approvals'
REQUEST_BRANCH = 'governance/request/protection-semantic-reconciliation-v1-20260808'
SOCKET_PATH = '/run/aios-governance-preapproval-publisher/publisher.sock'
CREDENTIAL_NAME = 'gitea-machine-token'
AUTHORITY_REPO = '/data/aios-gitops/repo.git'
TASK_ROOT = pathlib.Path('/var/lib/aios-gitops/runtime/tasks/AIOS-PROTECTION-SNAPSHOT-DRIFT-FORENSICS-V1')
INPUT_ROOT = TASK_ROOT / 'publication-input'
RECEIPT = TASK_ROOT / 'publication-receipt.json'
MANIFEST_REL = 'approvals/governance-policy/AIOS-PROTECTION-SEMANTIC-SNAPSHOT-RECONCILIATION-V1.json'
EVIDENCE_REL = 'approvals/governance-policy/AIOS-PROTECTION-SEMANTIC-SNAPSHOT-RECONCILIATION-V1.evidence.json'
def digest_bytes(data):
return hashlib.sha256(data).hexdigest()
def digest_json(value):
return digest_bytes(json.dumps(value, sort_keys=True, separators=(',', ':'), ensure_ascii=False).encode())
def load_token():
path = pathlib.Path(os.environ['CREDENTIALS_DIRECTORY']) / CREDENTIAL_NAME
value = path.read_text(encoding='utf-8').strip()
if len(value) < 16:
raise RuntimeError('MACHINE_CREDENTIAL_INVALID')
return value
def api(base, method, path, token, body=None, expected=(200,)):
raw = None if body is None else json.dumps(body, separators=(',', ':'), ensure_ascii=False).encode()
request = urllib.request.Request(base + path, data=raw, method=method)
request.add_header('Authorization', 'token ' + token)
request.add_header('Accept', 'application/json')
if raw is not None:
request.add_header('Content-Type', 'application/json')
try:
with urllib.request.urlopen(request, timeout=20) as response:
payload = response.read()
if response.status not in expected:
raise RuntimeError('GITEA_HTTP_' + str(response.status))
return response.status, json.loads(payload) if payload else None
except urllib.error.HTTPError as exc:
payload = exc.read()
if exc.code in expected:
return exc.code, json.loads(payload) if payload else None
raise RuntimeError('GITEA_HTTP_' + str(exc.code) + ':' + method + ':' + path) from None
def authority_head():
result = subprocess.run(
['git', '--git-dir=' + AUTHORITY_REPO, 'rev-parse', 'refs/heads/' + BASE_BRANCH],
text=True, capture_output=True, check=False,
)
if result.returncode != 0:
raise RuntimeError('AUTHORITY_BASE_UNAVAILABLE')
return result.stdout.strip()
def branch_api(base, token, branch, expected=(200,)):
return api(base, 'GET', '/repos/' + REPO + '/branches/' + urllib.parse.quote(branch, safe=''), token, expected=expected)
def verify_base(token):
authority = authority_head()
_, local = branch_api(LOCAL_API, token, BASE_BRANCH)
_, network = branch_api(NETWORK_API, token, BASE_BRANCH)
local_oid = local.get('commit', {}).get('id')
network_oid = network.get('commit', {}).get('id')
if not authority or authority != local_oid or authority != network_oid:
raise RuntimeError('RECONCILIATION_REQUIRED')
return authority
def verify_identity(token):
_, user = api(LOCAL_API, 'GET', '/user', token)
if user.get('login') != 'aios-autonomy-agent' or user.get('id') != 153 or user.get('is_admin') is not False:
raise RuntimeError('MACHINE_IDENTITY_REJECTED')
return {'login': user['login'], 'id': user['id'], 'owner': user.get('is_admin')}
def load_inputs():
manifest = json.dumps(json.loads((INPUT_ROOT / 'manifest.json').read_text(encoding='utf-8')), sort_keys=True, separators=(',', ':'), ensure_ascii=False).encode()
evidence = json.dumps(json.loads((INPUT_ROOT / 'evidence.json').read_text(encoding='utf-8')), sort_keys=True, separators=(',', ':'), ensure_ascii=False).encode()
for data in (manifest, evidence):
if any(secret in data.lower() for secret in (b'authorization:', b'ghp_', b'github_pat_', b'secret=', b'token=')):
raise RuntimeError('SECRET_CONTENT_REJECTED')
return manifest, evidence
def publication_key(base_oid, manifest_sha, evidence_sha):
payload = REPO + base_oid + REQUEST_BRANCH + manifest_sha + evidence_sha
return hashlib.sha256(payload.encode()).hexdigest()
def write_receipt(state, **fields):
record = {
'schema': 'aios.governance-preapproval-publication.v1',
'operation': 'SEMANTIC_RECONCILIATION_REQUEST',
'state': state,
'recorded_unix': int(time.time()),
'request_branch': REQUEST_BRANCH,
'manifest_path': MANIFEST_REL,
'evidence_path': EVIDENCE_REL,
'machine_actor': 'aios-autonomy-agent',
'owner': False,
'credential_ref': {'scheme': 'systemd-credential', 'name': CREDENTIAL_NAME},
'governance_capability_executed': False,
'protection_mutation': False,
'force_push': False,
**fields,
}
RECEIPT.parent.mkdir(parents=True, exist_ok=True)
temporary = RECEIPT.with_suffix('.tmp')
temporary.write_text(json.dumps(record, sort_keys=True, separators=(',', ':'), ensure_ascii=False) + '\n', encoding='utf-8')
temporary.replace(RECEIPT)
RECEIPT.chmod(0o640)
return record
def existing_prs(token):
_, prs = api(LOCAL_API, 'GET', '/repos/' + REPO + '/pulls?state=all&base=' + urllib.parse.quote(BASE_BRANCH, safe='') + '&limit=100', token)
return [pr for pr in prs if pr.get('head', {}).get('ref') == REQUEST_BRANCH and pr.get('base', {}).get('ref') == BASE_BRANCH]
def verify_existing_branch(token, branch_oid, manifest, evidence):
_, commit = api(LOCAL_API, 'GET', '/repos/' + REPO + '/git/commits/' + branch_oid, token)
if commit.get('parents', [{}])[0].get('sha') is None:
raise RuntimeError('REQUEST_BRANCH_COMMIT_INVALID')
for relative, expected in ((MANIFEST_REL, manifest), (EVIDENCE_REL, evidence)):
_, content = api(LOCAL_API, 'GET', '/repos/' + REPO + '/contents/' + urllib.parse.quote(relative, safe='/') + '?ref=' + branch_oid, token)
actual = base64.b64decode(''.join(content.get('content', '').split()))
if actual != expected:
raise RuntimeError('GOVERNANCE_REQUEST_BRANCH_CONFLICT')
def push_one_commit(token, base_oid, manifest, evidence):
work_root = TASK_ROOT / 'publisher-work'
work_root.mkdir(parents=True, exist_ok=True)
temporary = pathlib.Path(tempfile.mkdtemp(prefix='semantic-reconciliation-', dir=work_root))
askpass = temporary / 'askpass.sh'
clone = temporary / 'repo'
credential_path = pathlib.Path(os.environ['CREDENTIALS_DIRECTORY']) / CREDENTIAL_NAME
askpass.write_text(
'#!/bin/sh\ncase "$1" in *Username*) printf "%s\\n" "$GITEA_MACHINE_ACTOR";; *) cat "$CREDENTIAL_FILE";; esac\n',
encoding='utf-8',
)
askpass.chmod(0o700)
env = os.environ.copy()
env.update({
'GIT_ASKPASS': str(askpass),
'GIT_TERMINAL_PROMPT': '0',
'GITEA_MACHINE_ACTOR': 'aios-autonomy-agent',
'CREDENTIAL_FILE': str(credential_path),
'GIT_CONFIG_COUNT': '1',
'GIT_CONFIG_KEY_0': 'safe.directory',
'GIT_CONFIG_VALUE_0': AUTHORITY_REPO,
})
try:
run = subprocess.run(
['git', 'clone', '--branch', BASE_BRANCH, '--single-branch', '--quiet', NETWORK_REMOTE, str(clone)],
text=True, capture_output=True, env=env, check=False,
)
if run.returncode != 0:
detail = ' '.join(run.stderr.strip().split())[:240]
raise RuntimeError('PUBLISHER_CLONE_FAILED:' + detail)
def git(*args, push_env=None):
result = subprocess.run(['git', *args], cwd=clone, text=True, capture_output=True, env=push_env or env, check=False)
if result.returncode != 0:
raise RuntimeError('PUBLISHER_GIT_OPERATION_FAILED')
return result.stdout.strip()
git('checkout', '--detach', base_oid)
git('switch', '--create', REQUEST_BRANCH)
manifest_path = clone / MANIFEST_REL
evidence_path = clone / EVIDENCE_REL
manifest_path.parent.mkdir(parents=True, exist_ok=True)
evidence_path.parent.mkdir(parents=True, exist_ok=True)
manifest_path.write_bytes(manifest)
evidence_path.write_bytes(evidence)
git('add', '--', MANIFEST_REL, EVIDENCE_REL)
staged = git('diff', '--cached', '--name-only').splitlines()
if staged != sorted([MANIFEST_REL, EVIDENCE_REL]):
raise RuntimeError('PUBLISHER_PATH_ALLOWLIST_FAILED')
git('-c', 'user.name=aios-autonomy-agent', '-c', 'user.email=aios-autonomy-agent@aios.invalid', 'commit', '--quiet', '-m', '[governance] publish semantic reconciliation evidence')
if git('rev-list', '--count', base_oid + '..HEAD') != '1':
raise RuntimeError('PUBLISHER_COMMIT_COUNT_FAILED')
git('push', '--porcelain', NETWORK_REMOTE, 'HEAD:refs/heads/' + REQUEST_BRANCH, push_env=env)
head = git('rev-parse', 'HEAD')
return head
finally:
shutil.rmtree(temporary, ignore_errors=True)
def publish():
token = load_token()
identity = verify_identity(token)
base_oid = verify_base(token)
manifest, evidence = load_inputs()
manifest_sha = digest_bytes(manifest)
evidence_sha = digest_bytes(evidence)
key = publication_key(base_oid, manifest_sha, evidence_sha)
write_receipt('REQUESTED', governance_base_oid=base_oid, manifest_sha256=manifest_sha, evidence_sha256=evidence_sha, idempotency_key=key, machine_identity=identity)
write_receipt('LOCAL_CANDIDATE_VERIFIED', governance_base_oid=base_oid, manifest_sha256=manifest_sha, evidence_sha256=evidence_sha, idempotency_key=key, machine_identity=identity)
branch_status, branch = branch_api(LOCAL_API, token, REQUEST_BRANCH, expected=(200, 404))
if branch_status == 200:
branch_oid = branch.get('commit', {}).get('id')
verify_existing_branch(token, branch_oid, manifest, evidence)
else:
branch_oid = push_one_commit(token, base_oid, manifest, evidence)
verify_existing_branch(token, branch_oid, manifest, evidence)
write_receipt('BRANCH_PUSHED', governance_base_oid=base_oid, manifest_sha256=manifest_sha, evidence_sha256=evidence_sha, idempotency_key=key, request_head_oid=branch_oid, machine_identity=identity)
prs = existing_prs(token)
if len(prs) > 1:
raise RuntimeError('MULTIPLE_MATCHING_APPROVAL_PRS')
if prs:
pr = prs[0]
if pr.get('head', {}).get('sha') != branch_oid:
raise RuntimeError('APPROVAL_PR_HEAD_MISMATCH')
pr_number = pr.get('number')
else:
_, pr = api(LOCAL_API, 'POST', '/repos/' + REPO + '/pulls', token, {
'title': '[GOVERNANCE RECONCILIATION] Semantic protection snapshot schema migration',
'head': REQUEST_BRANCH,
'base': BASE_BRANCH,
'body': 'Schema-only reconciliation for the existing PR54 Stage-1 and Stage-2 predicates. No new governance capability; Owner exact-head re-attestation required.',
}, expected=(201,))
pr_number = pr.get('number')
write_receipt('PR_CREATED', governance_base_oid=base_oid, manifest_sha256=manifest_sha, evidence_sha256=evidence_sha, idempotency_key=key, request_head_oid=branch_oid, pr_number=pr_number, machine_identity=identity)
_, remote_branch = branch_api(NETWORK_API, token, REQUEST_BRANCH)
if remote_branch.get('commit', {}).get('id') != branch_oid:
raise RuntimeError('NETWORK_BRANCH_READBACK_MISMATCH')
_, read_pr = api(LOCAL_API, 'GET', '/repos/' + REPO + '/pulls/' + str(pr_number), token)
if read_pr.get('head', {}).get('sha') != branch_oid or read_pr.get('base', {}).get('ref') != BASE_BRANCH:
raise RuntimeError('PR_READBACK_MISMATCH')
result = write_receipt('COMPLETED', governance_base_oid=base_oid, manifest_sha256=manifest_sha, evidence_sha256=evidence_sha, idempotency_key=key, request_head_oid=branch_oid, pr_number=pr_number, pr_head_oid=read_pr.get('head', {}).get('sha'), machine_identity=identity)
return result
def self_test():
token = load_token()
identity = verify_identity(token)
base_oid = verify_base(token)
branch_status, _ = branch_api(LOCAL_API, token, REQUEST_BRANCH, expected=(200, 404))
return {
'status': 'PASS',
'governance_base_oid': base_oid,
'machine_identity': identity,
'credential_ref': {'scheme': 'systemd-credential', 'name': CREDENTIAL_NAME},
'branch_namespace': 'refs/heads/governance/request/*',
'target_base': BASE_BRANCH,
'request_branch_status': 'EXISTS' if branch_status == 200 else 'ABSENT',
'operation_allowlist': ['READ_BASE', 'CREATE_ONE_REQUEST_BRANCH', 'WRITE_TWO_EVIDENCE_PATHS_ONE_COMMIT', 'CREATE_OR_READ_ONE_APPROVAL_PR', 'READBACK'],
'denied': ['MAIN_WRITE', 'DIRECT_APPROVALS_WRITE', 'BRANCH_PROTECTION', 'MERGE', 'FORCE_PUSH', 'TAG', 'PRODUCTION', 'CREDENTIAL_MUTATION'],
'governance_capability_executed': False,
}
def read_publication_status():
token = load_token()
receipt = json.loads(RECEIPT.read_text(encoding='utf-8'))
pr_number = receipt.get('pr_number')
if not pr_number:
raise RuntimeError('PUBLICATION_RECEIPT_PR_MISSING')
_, pr = api(LOCAL_API, 'GET', '/repos/' + REPO + '/pulls/' + str(pr_number), token)
_, reviews = api(LOCAL_API, 'GET', '/repos/' + REPO + '/pulls/' + str(pr_number) + '/reviews', token)
_, branch = branch_api(LOCAL_API, token, REQUEST_BRANCH)
_, network_branch = branch_api(NETWORK_API, token, REQUEST_BRANCH)
return {
'status': 'PASS',
'request_branch': REQUEST_BRANCH,
'branch_head': branch.get('commit', {}).get('id'),
'network_branch_head': network_branch.get('commit', {}).get('id'),
'pr_number': pr.get('number'),
'pr_state': pr.get('state'),
'pr_merged': pr.get('merged'),
'pr_head': pr.get('head', {}).get('sha'),
'pr_base': pr.get('base', {}).get('ref'),
'reviews': [
{
'id': item.get('id'),
'reviewer_id': item.get('user', {}).get('id'),
'state': item.get('state'),
'commit_id': item.get('commit_id'),
'dismissed': item.get('dismissed'),
}
for item in reviews
],
'governance_capability_executed': False,
'protection_mutation': False,
}
def handle(request):
if not isinstance(request, dict) or set(request) != {'operation'}:
raise RuntimeError('REQUEST_SCHEMA_INVALID')
if request['operation'] == 'DRY_RUN_SELF_TEST':
return self_test()
if request['operation'] == 'PUBLISH_SEMANTIC_RECONCILIATION':
return publish()
if request['operation'] == 'READ_SEMANTIC_RECONCILIATION_STATUS':
return read_publication_status()
raise RuntimeError('OPERATION_DENIED')
def serve(listener):
conn, _ = listener.accept()
with conn:
try:
data = conn.recv(1024 * 1024 + 1)
if len(data) > 1024 * 1024:
raise RuntimeError('REQUEST_TOO_LARGE')
result = handle(json.loads(data.decode()))
except Exception as exc:
result = {'status': 'FAIL', 'code': str(exc)}
conn.sendall((json.dumps(result, sort_keys=True, separators=(',', ':'), ensure_ascii=False) + '\n').encode())
def main():
if os.environ.get('LISTEN_FDS') != '1':
raise SystemExit('SOCKET_ACTIVATION_REQUIRED')
listener = socket.fromfd(3, socket.AF_UNIX, socket.SOCK_STREAM)
serve(listener)
if __name__ == '__main__':
main()
@@ -0,0 +1,73 @@
"""Fail-closed Authority/Gitea ref reconciliation after protected merge."""
from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
import subprocess
class RefReconciliationError(RuntimeError):
pass
def _oid(value: str, field: str) -> str:
if not isinstance(value, str) or len(value) != 40 or any(char not in "0123456789abcdef" for char in value):
raise RefReconciliationError(field + "_INVALID")
return value
@dataclass(frozen=True, slots=True)
class RefReconciler:
git_dir: Path
def read(self, ref: str) -> str:
result = subprocess.run(
["git", f"--git-dir={self.git_dir}", "rev-parse", "--verify", ref],
text=True,
capture_output=True,
check=False,
)
if result.returncode != 0:
raise RefReconciliationError("AUTHORITY_REF_UNAVAILABLE")
return result.stdout.strip()
def is_ancestor(self, ancestor: str, descendant: str) -> bool:
_oid(ancestor, "ancestor")
_oid(descendant, "descendant")
result = subprocess.run(
["git", f"--git-dir={self.git_dir}", "merge-base", "--is-ancestor", ancestor, descendant],
text=True,
capture_output=True,
check=False,
)
if result.returncode == 0:
return True
if result.returncode == 1:
return False
raise RefReconciliationError("AUTHORITY_ANCESTRY_CHECK_FAILED")
def reconcile_main(self, *, expected_oid: str, merged_oid: str, gitea_main_oid: str) -> str:
_oid(expected_oid, "expected_oid")
_oid(merged_oid, "merged_oid")
_oid(gitea_main_oid, "gitea_main_oid")
if gitea_main_oid != merged_oid:
raise RefReconciliationError("GITEA_MAIN_EXPECTATION_MISMATCH")
if not self.is_ancestor(expected_oid, merged_oid):
raise RefReconciliationError("MAIN_FF_REQUIRED")
current = self.read("refs/heads/main")
if current == merged_oid:
return merged_oid
if current != expected_oid:
raise RefReconciliationError("AUTHORITY_MAIN_EXPECTATION_MISMATCH")
result = subprocess.run(
["git", f"--git-dir={self.git_dir}", "update-ref", "refs/heads/main", merged_oid, expected_oid],
text=True,
capture_output=True,
check=False,
)
if result.returncode != 0:
raise RefReconciliationError("AUTHORITY_MAIN_CAS_FAILED")
if self.read("refs/heads/main") != merged_oid:
raise RefReconciliationError("AUTHORITY_MAIN_CAS_READBACK_MISMATCH")
return merged_oid
@@ -0,0 +1,81 @@
"""Canonical semantic branch-protection model used by AIOS governance gates."""
from __future__ import annotations
import hashlib
import json
from typing import Any, Mapping
SCHEMA = "aios.semantic-protection.v1"
USER_IDS = {"owner": 2, "aios-autonomy-agent": 153}
def canonical_json(value: Any) -> bytes:
return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode("utf-8")
def _typed_json(value: Any) -> list[Any]:
if value in (None, "null"):
return []
if isinstance(value, str):
return json.loads(value)
return list(value)
def _user_ids(names: Any) -> list[int]:
result = []
for name in names or []:
if name not in USER_IDS:
raise ValueError("UNKNOWN_GOVERNANCE_USER:" + str(name))
result.append(USER_IDS[name])
return sorted(result)
def semantic_policy(protection: Mapping[str, Any]) -> dict[str, Any]:
"""Drop transport metadata and canonicalize semantic security fields."""
return {
"schema_version": SCHEMA,
"repo_id": 1,
"protection_id": 2,
"branch_name": protection.get("branch_name"),
"can_push": protection.get("enable_push"),
"can_force_push": protection.get("enable_force_push"),
"enable_merge_whitelist": protection.get("enable_merge_whitelist"),
"merge_whitelist_user_ids": _user_ids(protection.get("merge_whitelist_usernames")),
"merge_whitelist_team_ids": [],
"required_approvals": protection.get("required_approvals"),
"enable_status_check": protection.get("enable_status_check"),
"status_check_contexts": sorted(protection.get("status_check_contexts") or []),
"block_admin_merge_override": protection.get("block_admin_merge_override"),
"block_on_official_review_requests": protection.get("block_on_official_review_requests"),
"block_on_outdated_branch": protection.get("block_on_outdated_branch"),
"block_on_rejected_reviews": protection.get("block_on_rejected_reviews"),
"dismiss_stale_approvals": protection.get("dismiss_stale_approvals"),
"ignore_stale_approvals": protection.get("ignore_stale_approvals"),
"enable_approvals_whitelist": protection.get("enable_approvals_whitelist"),
"approvals_whitelist_user_ids": _user_ids(protection.get("approvals_whitelist_username")),
"approvals_whitelist_team_ids": [],
"enable_bypass_allowlist": protection.get("enable_bypass_allowlist"),
"bypass_allowlist_user_ids": _user_ids(protection.get("bypass_allowlist_usernames")),
"bypass_allowlist_team_ids": [],
"enable_force_push_allowlist": protection.get("enable_force_push_allowlist"),
"force_push_allowlist_user_ids": _user_ids(protection.get("force_push_allowlist_usernames")),
"enable_whitelist": protection.get("enable_push_whitelist"),
"whitelist_user_ids": _user_ids(protection.get("push_whitelist_usernames")),
"whitelist_team_ids": [],
"force_push_allowlist_deploy_keys": protection.get("force_push_allowlist_deploy_keys"),
"whitelist_deploy_keys": protection.get("push_whitelist_deploy_keys"),
"require_signed_commits": protection.get("require_signed_commits"),
"priority": protection.get("priority"),
"protected_file_patterns": _typed_json(protection.get("protected_file_patterns")),
"unprotected_file_patterns": _typed_json(protection.get("unprotected_file_patterns")),
}
def semantic_digest(protection: Mapping[str, Any]) -> str:
return hashlib.sha256(canonical_json(semantic_policy(protection))).hexdigest()
def transport_digest(protection: Mapping[str, Any]) -> str:
return hashlib.sha256(canonical_json(protection)).hexdigest()
@@ -0,0 +1,141 @@
"""Fail-closed Stage-2 campaign gate migration predicates."""
from __future__ import annotations
import copy
import hashlib
import json
import re
from typing import Any, Mapping
SEMANTIC_SCHEMA = "aios.semantic-protection.v1"
STAGE1_SEMANTIC_SHA = "d911e856f71de6105b8efa08e13338a448e0b6d37536332decd80c9f409ecf27"
CAMPAIGN_ID = "AIOS-AUTONOMOUS-RELEASE-CAMPAIGN-20260807"
OWNER_GATE = "owner-approval-gate"
CAMPAIGN_GATE = "campaign-approval-gate"
OID_RE = re.compile(r"^[0-9a-f]{40}$")
class Stage2GateError(RuntimeError):
pass
def canonical_json(value: Any) -> bytes:
return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False).encode("utf-8")
def _typed_json(value: Any) -> list[Any]:
if value in (None, "", "null"):
return []
if isinstance(value, str):
return json.loads(value)
return list(value)
def _user_ids(names: Any) -> list[int]:
mapping = {"owner": 2, "aios-autonomy-agent": 153}
result = []
for name in names or []:
if name not in mapping:
raise Stage2GateError("UNKNOWN_GOVERNANCE_USER")
result.append(mapping[name])
return sorted(result)
def semantic_policy(protection: Mapping[str, Any]) -> dict[str, Any]:
return {
"schema_version": SEMANTIC_SCHEMA,
"repo_id": 1,
"protection_id": 2,
"branch_name": protection.get("branch_name"),
"can_push": protection.get("enable_push"),
"can_force_push": protection.get("enable_force_push"),
"enable_merge_whitelist": protection.get("enable_merge_whitelist"),
"merge_whitelist_user_ids": _user_ids(protection.get("merge_whitelist_usernames")),
"merge_whitelist_team_ids": [],
"required_approvals": protection.get("required_approvals"),
"enable_status_check": protection.get("enable_status_check"),
"status_check_contexts": sorted(protection.get("status_check_contexts") or []),
"block_admin_merge_override": protection.get("block_admin_merge_override"),
"block_on_official_review_requests": protection.get("block_on_official_review_requests"),
"block_on_outdated_branch": protection.get("block_on_outdated_branch"),
"block_on_rejected_reviews": protection.get("block_on_rejected_reviews"),
"dismiss_stale_approvals": protection.get("dismiss_stale_approvals"),
"ignore_stale_approvals": protection.get("ignore_stale_approvals"),
"enable_approvals_whitelist": protection.get("enable_approvals_whitelist"),
"approvals_whitelist_user_ids": _user_ids(protection.get("approvals_whitelist_username")),
"approvals_whitelist_team_ids": [],
"enable_bypass_allowlist": protection.get("enable_bypass_allowlist"),
"bypass_allowlist_user_ids": _user_ids(protection.get("bypass_allowlist_usernames")),
"bypass_allowlist_team_ids": [],
"enable_force_push_allowlist": protection.get("enable_force_push_allowlist"),
"force_push_allowlist_user_ids": _user_ids(protection.get("force_push_allowlist_usernames")),
"force_push_allowlist_team_ids": [],
"enable_whitelist": protection.get("enable_push_whitelist"),
"whitelist_user_ids": _user_ids(protection.get("push_whitelist_usernames")),
"whitelist_team_ids": [],
"force_push_allowlist_deploy_keys": protection.get("force_push_allowlist_deploy_keys"),
"whitelist_deploy_keys": protection.get("push_whitelist_deploy_keys"),
"require_signed_commits": protection.get("require_signed_commits"),
"priority": protection.get("priority"),
"protected_file_patterns": protection.get("protected_file_patterns"),
"unprotected_file_patterns": protection.get("unprotected_file_patterns"),
}
def semantic_digest(protection: Mapping[str, Any]) -> str:
return hashlib.sha256(canonical_json(semantic_policy(protection))).hexdigest()
def build_target(protection: Mapping[str, Any]) -> tuple[str, dict[str, Any]]:
current = list(protection.get("status_check_contexts") or [])
has_owner = OWNER_GATE in current
has_campaign = CAMPAIGN_GATE in current
if has_owner and has_campaign:
raise Stage2GateError("GATE_PRESTATE_CONFLICT")
if not has_owner and not has_campaign:
raise Stage2GateError("OWNER_GATE_MISSING")
if has_campaign:
return "IDEMPOTENT_SUCCESS", copy.deepcopy(dict(protection))
if semantic_digest(protection) != STAGE1_SEMANTIC_SHA:
raise Stage2GateError("SEMANTIC_PRECONDITION_INVALID")
target = copy.deepcopy(dict(protection))
target["status_check_contexts"] = sorted(
[gate for gate in current if gate != OWNER_GATE] + [CAMPAIGN_GATE]
)
return "READY", target
def validate_stage2_facts(facts: Mapping[str, Any]) -> None:
required = {
"campaign_id",
"campaign_active",
"campaign_revoked",
"machine_actor",
"required_checks_all_success",
"required_checks_same_head",
"unresolved_conversations",
"mergeable",
"protected_target",
"current_main_oid",
"gitea_main_oid",
"current_main_lineage_verified",
}
if set(facts) != required:
raise Stage2GateError("STAGE2_FACTS_SCHEMA_INVALID")
if facts["campaign_id"] != CAMPAIGN_ID or facts["campaign_active"] is not True or facts["campaign_revoked"] is not False:
raise Stage2GateError("CAMPAIGN_NOT_ACTIVE")
if facts["machine_actor"] != "aios-autonomy-agent":
raise Stage2GateError("MACHINE_ACTOR_MISMATCH")
if facts["required_checks_all_success"] is not True or facts["required_checks_same_head"] is not True:
raise Stage2GateError("REQUIRED_CHECKS_NOT_CURRENT")
if facts["unresolved_conversations"] != 0 or facts["mergeable"] is not True:
raise Stage2GateError("CAMPAIGN_GATE_FACTS_INVALID")
if facts["protected_target"] != "main" or facts["current_main_lineage_verified"] is not True:
raise Stage2GateError("PROTECTED_TARGET_OR_LINEAGE_INVALID")
if (
not isinstance(facts["current_main_oid"], str)
or not OID_RE.fullmatch(facts["current_main_oid"])
or facts["gitea_main_oid"] != facts["current_main_oid"]
):
raise Stage2GateError("MAIN_READBACK_MISMATCH")
@@ -0,0 +1,713 @@
"""Governed release orchestration with a trusted-plan V2R2C2 path.
The legacy V2R2C1 implementation remains available only for compatibility;
new release requests are dispatched to the plan-driven executor below.
This module is deliberately scoped to the Owner-approved V2R2C1 binding. It
uses the existing TagPublisher and metadata adapters; it does not introduce a
second Git writer, a second durable job store, or an alternate baseline.
"""
from __future__ import annotations
import argparse
import fcntl
import hashlib
import json
import os
import re
import subprocess
import tarfile
import tempfile
import time
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Mapping
from trusted_release_execution_plan import PlanError, TrustedReleasePlan
from gitea_tag_publication import (
GiteaTagClient,
GitTagBackend,
PublicationManualOwnerReviewRequired,
PublicationPending,
PublicationReconciliationRequired,
PublicationRequest,
TagPublisher,
)
VERSION = "V2R2C1"
TARGET_OID = "998d30304787e31e5ae607c0bd86b5edb2b0e5ad"
EVIDENCE_CLOSURE_OID = "a8b906d1bc24088c19647e98de6a679268f6d0a2"
APPROVAL_PLAN_ID = "sha256:c0df52008ae7f64cec5ee2d28947aa7b892066c670ab8295e4549140b1a7500e"
REPOSITORY = "aios/aios"
SERVICE = "aios-v2r1c7-production.service"
PORT = 18927
OID_RE = re.compile(r"^[0-9a-f]{40}$")
SHA_RE = re.compile(r"^[0-9a-f]{64}$")
class ReleaseBlocked(RuntimeError):
"""A release gate failed and no unsafe recovery is permitted."""
class ManualOwnerReviewRequired(ReleaseBlocked):
pass
class ReconciliationRequired(ReleaseBlocked):
pass
def canonical(value: object) -> bytes:
return (json.dumps(value, sort_keys=True, separators=(",", ":")) + "\n").encode()
def sha256_file(path: Path) -> str:
if path.is_symlink() or not path.is_file():
raise ReleaseBlocked("artifact must be a regular file")
digest = hashlib.sha256()
with path.open("rb") as stream:
for block in iter(lambda: stream.read(1024 * 1024), b""):
digest.update(block)
return digest.hexdigest()
def atomic_json(path: Path, value: Mapping[str, Any], mode: int = 0o600) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
temporary = path.with_name(path.name + ".tmp")
temporary.write_bytes(canonical(dict(value)))
os.chmod(temporary, mode)
os.replace(temporary, path)
@dataclass(frozen=True)
class ReleaseBinding:
version: str = VERSION
target_oid: str = TARGET_OID
evidence_closure_oid: str = EVIDENCE_CLOSURE_OID
approval_plan_id: str = APPROVAL_PLAN_ID
repository: str = REPOSITORY
production_service: str = SERVICE
production_port: int = PORT
def validate(self) -> None:
if self.version != VERSION or self.repository != REPOSITORY:
raise ManualOwnerReviewRequired("release binding is outside V2R2C1 scope")
if not OID_RE.fullmatch(self.target_oid) or self.target_oid != TARGET_OID:
raise ManualOwnerReviewRequired("V2R2C1 target_oid mismatch")
if not OID_RE.fullmatch(self.evidence_closure_oid) or self.evidence_closure_oid != EVIDENCE_CLOSURE_OID:
raise ManualOwnerReviewRequired("V2R2C1 evidence closure mismatch")
if self.approval_plan_id != APPROVAL_PLAN_ID:
raise ManualOwnerReviewRequired("V2R2C1 approval plan mismatch")
if self.production_service != SERVICE or int(self.production_port) != PORT:
raise ManualOwnerReviewRequired("production service binding mismatch")
def as_dict(self) -> dict[str, Any]:
return {
"version": self.version,
"target_oid": self.target_oid,
"evidence_closure_oid": self.evidence_closure_oid,
"approval_plan_id": self.approval_plan_id,
"repository": self.repository,
"production_service": self.production_service,
"production_port": self.production_port,
}
class StateLock:
def __init__(self, path: Path):
self.path = path
self.handle = None
def __enter__(self) -> "StateLock":
self.path.parent.mkdir(parents=True, exist_ok=True)
self.handle = self.path.open("a+", encoding="utf-8")
fcntl.flock(self.handle.fileno(), fcntl.LOCK_EX)
return self
def __exit__(self, exc_type, exc, tb) -> None:
if self.handle is not None:
fcntl.flock(self.handle.fileno(), fcntl.LOCK_UN)
self.handle.close()
class ReleaseState:
def __init__(self, root: Path, binding: ReleaseBinding):
self.root = root
self.path = root / "release-state.json"
self.binding = binding
root.mkdir(parents=True, exist_ok=True)
if self.path.exists():
value = json.loads(self.path.read_text(encoding="utf-8"))
if value.get("binding") != binding.as_dict():
raise ManualOwnerReviewRequired("persisted release binding conflict")
self.value = value
else:
self.value = {
"schema": "aios.v2r2c1.governed-production-release-state.v1",
"binding": binding.as_dict(),
"state": "INITIAL",
"completed": {},
"history": [],
}
self.save()
def save(self) -> None:
atomic_json(self.path, self.value)
def record(self, state: str, step: str, result: Mapping[str, Any]) -> None:
safe = json.loads(json.dumps(dict(result)))
self.value["state"] = state
self.value.setdefault("completed", {})[step] = safe
self.value.setdefault("history", []).append(
{"at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), "state": state, "step": step, "result": safe}
)
self.save()
def fail(self, state: str, reason: str) -> None:
self.value["state"] = state
self.value.setdefault("history", []).append(
{"at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()), "state": state, "reason": str(reason)[:500]}
)
self.save()
def _git(git_dir: Path, *args: str, check: bool = True) -> str:
completed = subprocess.run(
["git", f"--git-dir={git_dir}", *args],
check=False,
capture_output=True,
text=True,
timeout=45,
env={"PATH": os.environ.get("PATH", "")},
)
if check and completed.returncode != 0:
raise ReconciliationRequired(completed.stderr.strip() or "authoritative git query failed")
return completed.stdout.strip()
def _ancestor(git_dir: Path, ancestor: str, descendant: str) -> bool:
return subprocess.run(
["git", f"--git-dir={git_dir}", "merge-base", "--is-ancestor", ancestor, descendant],
check=False,
capture_output=True,
timeout=45,
env={"PATH": os.environ.get("PATH", "")},
).returncode == 0
def verify_lineage(git_dir: Path, binding: ReleaseBinding) -> dict[str, Any]:
binding.validate()
if _git(git_dir, "cat-file", "-t", binding.target_oid) != "commit":
raise ReleaseBlocked("approved target_oid is not a commit")
if _git(git_dir, "cat-file", "-t", binding.evidence_closure_oid) != "commit":
raise ReleaseBlocked("evidence closure is not a commit")
if not _ancestor(git_dir, binding.target_oid, binding.evidence_closure_oid):
raise ManualOwnerReviewRequired("approved target is not an ancestor of evidence closure")
return {
"target_oid": binding.target_oid,
"evidence_closure_oid": binding.evidence_closure_oid,
"target_is_ancestor_of_evidence": True,
"main_oid": _git(git_dir, "rev-parse", "refs/heads/main"),
}
def verify_owner_approval(value: Mapping[str, Any], binding: ReleaseBinding) -> dict[str, Any]:
approval = value.get("owner_approval")
if not isinstance(approval, Mapping):
raise ReleaseBlocked("OWNER_APPROVAL_RECEIPT_REQUIRED")
if approval.get("approved") is not True or approval.get("status") != "APPROVED":
raise ReleaseBlocked("OWNER_APPROVAL_NOT_ACCEPTED")
if approval.get("approved_by") != "aiman":
raise ManualOwnerReviewRequired("OWNER_APPROVAL_ACTOR_MISMATCH")
if approval.get("approval_source") != "explicit_owner_approval_in_chat":
raise ManualOwnerReviewRequired("OWNER_APPROVAL_SOURCE_MISMATCH")
for key, expected in binding.as_dict().items():
if key in {"repository", "production_service", "production_port"}:
continue
if approval.get(key) != expected:
raise ManualOwnerReviewRequired("OWNER_APPROVAL_BINDING_MISMATCH:" + key)
return dict(approval)
def _payload_file(git_dir: Path, oid: str, path: str) -> bytes:
completed = subprocess.run(
["git", f"--git-dir={git_dir}", "show", f"{oid}:{path}"],
check=False,
capture_output=True,
timeout=45,
env={"PATH": os.environ.get("PATH", "")},
)
if completed.returncode != 0:
raise ReleaseBlocked("required release source missing:" + path)
return completed.stdout
def _commit_epoch(git_dir: Path, oid: str) -> int:
return int(_git(git_dir, "show", "-s", "--format=%ct", oid))
def build_production_artifact(
git_dir: Path,
binding: ReleaseBinding,
output_root: Path,
) -> dict[str, Any]:
"""Build a deterministic executable V2R2C1 health artifact from target_oid."""
binding.validate()
verify_lineage(git_dir, binding)
epoch = _commit_epoch(git_dir, binding.target_oid)
output_root.mkdir(parents=True, exist_ok=True)
stem = f"aios-{VERSION}-{binding.target_oid[:12]}"
output = output_root / (stem + ".tar.zst")
with tempfile.TemporaryDirectory(prefix="v2r2c1-artifact-") as temp:
root = Path(temp) / "payload"
root.mkdir(parents=True)
source_paths = {
"active-runtime/app/deployment_state.py": "active-runtime/app/deployment_state.py",
"active-runtime/app/v2r1c6_privacy_tools.py": "active-runtime/app/v2r1c6_privacy_tools.py",
}
for destination, source in source_paths.items():
target = root / destination
target.parent.mkdir(parents=True, exist_ok=True)
target.write_bytes(_payload_file(git_dir, binding.target_oid, source))
generated: dict[str, str] = {
"VERSION": VERSION + "\n",
"active-runtime/app/v2r2c1_health_service.py": (
"from v2r1c6_privacy_tools import main\n\n"
"if __name__ == '__main__':\n raise SystemExit(main())\n"
),
"deploy/aios-v2/systemd/aios-v2r1c7-production.service": (
"[Unit]\nDescription=AIOS V2R2C1 governed health contract service\n"
"After=network.target\n\n[Service]\nType=simple\n"
"User=aios-v2r1c7\nGroup=aios-v2r1c7\n"
"WorkingDirectory=/opt/aios/v2r2c1\n"
"EnvironmentFile=/etc/aios/aios-v2r2c1.env\n"
"ExecStart=/usr/bin/python3 /opt/aios/v2r2c1/active-runtime/app/v2r2c1_health_service.py --serve --host 127.0.0.1 --port ${AIOS_V2R1C7_PORT}\n"
"Restart=on-failure\nRestartSec=5\nNoNewPrivileges=true\nPrivateTmp=true\n"
"ProtectHome=true\nProtectSystem=strict\nProtectKernelLogs=true\n"
"ProtectKernelModules=true\nProtectKernelTunables=true\nProtectControlGroups=true\n"
"RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6\nIPAddressDeny=any\n"
"IPAddressAllow=localhost\nUMask=0077\n\n[Install]\nWantedBy=multi-user.target\n"
),
"deploy/aios-v2/v2r2c1-runtime-config.json": json.dumps({
"schema": "aios.v2r2c1.production-runtime.v1",
"version": VERSION,
"target_oid": binding.target_oid,
"service": SERVICE,
"port": PORT,
"health_path": "/health",
"deployment_state_source": "environment_and_state_file",
}, sort_keys=True, separators=(",", ":")) + "\n",
"deploy/aios-v2/v2r2c1-rollback.json": json.dumps({
"schema": "aios.v2r2c1.rollback-metadata.v1",
"rollback_version": "V2R1C7",
"rollback_service": SERVICE,
"rollback_target_is_previous_production": True,
"automatic_rollback": False,
}, sort_keys=True, separators=(",", ":")) + "\n",
"scripts/probe_v2r2c1_health.py": (
"import json,sys,urllib.request\n"
"url=sys.argv[1] if len(sys.argv)>1 else 'http://127.0.0.1:18927/health'\n"
"with urllib.request.urlopen(url, timeout=5) as r: print(json.dumps(json.load(r), sort_keys=True))\n"
),
"scripts/rollback_v2r2c1_release.sh": "#!/bin/sh\nset -eu\nprintf '%s\\n' 'rollback requires governed owner-approved executor'\nexit 2\n",
"release-manifests/V2R2C1.json": json.dumps({
"schema": "aios.release-source.v2",
"version": VERSION,
"repository": REPOSITORY,
"target_oid": binding.target_oid,
"evidence_closure_oid": binding.evidence_closure_oid,
"production_deployed": False,
"deployment_state": "STAGING",
"artifact_kind": "executable-production-health",
}, sort_keys=True, separators=(",", ":")) + "\n",
}
for rel, text in generated.items():
target = root / rel
target.parent.mkdir(parents=True, exist_ok=True)
target.write_text(text, encoding="utf-8")
if rel.startswith("scripts/"):
target.chmod(0o755)
metadata = {
"schema": "aios.release-artifact-build.v3",
"version": VERSION,
"repository": REPOSITORY,
"source_head_oid": binding.target_oid,
"evidence_closure_oid": binding.evidence_closure_oid,
"source_date_epoch": epoch,
"clean_source": True,
"executable_payload": True,
"production_deployed": False,
"tag_created": False,
"baseline_changed": False,
"secrets_included": False,
}
(root / "BUILD-METADATA.json").write_bytes(canonical(metadata))
(root / "ROLLBACK-METADATA.json").write_bytes(canonical(json.loads((root / "deploy/aios-v2/v2r2c1-rollback.json").read_text(encoding="utf-8"))))
names = sorted("payload/" + str(path.relative_to(root)).replace(os.sep, "/") for path in root.rglob("*") if path.is_file())
(root / "ARTIFACT-MANIFEST.json").write_bytes(canonical({
"schema": "aios.release-artifact-manifest.v3",
"version": VERSION,
"source_head_oid": binding.target_oid,
"evidence_closure_oid": binding.evidence_closure_oid,
"repository": REPOSITORY,
"files": names + ["payload/ARTIFACT-MANIFEST.json"],
"deployable": True,
"executable_payload": True,
"production_deployed": False,
"tag_created": False,
"baseline_changed": False,
}))
tar_path = Path(temp) / "release.tar"
with tarfile.open(tar_path, "w") as archive:
for path in sorted(root.rglob("*")):
relative = str(path.relative_to(root)).replace(os.sep, "/")
info = archive.gettarinfo(str(path), arcname="payload/" + relative)
info.uid = info.gid = 0
info.uname = "root"
info.gname = "root"
info.mtime = epoch
info.mode = 0o755 if path.is_dir() or path.suffix == ".sh" else 0o644
if path.is_file():
with path.open("rb") as stream:
archive.addfile(info, stream)
else:
archive.addfile(info)
subprocess.run(["zstd", "-T1", "-19", "--no-progress", "-q", "-f", str(tar_path), "-o", str(output)], check=True, timeout=120)
digest = sha256_file(output)
receipt = {
"schema": "aios.release-artifact-receipt.v3",
"version": VERSION,
"repository": REPOSITORY,
"source_head_oid": binding.target_oid,
"evidence_closure_oid": binding.evidence_closure_oid,
"artifact": str(output),
"artifact_sha256": digest,
"artifact_size": output.stat().st_size,
"deployable": True,
"executable_payload": True,
"production_deployed": False,
"tag_created": False,
"baseline_changed": False,
}
atomic_json(output.with_suffix(output.suffix + ".receipt.json"), receipt, mode=0o640)
return receipt
class GovernedProductionReleaseExecutor:
def __init__(self, config: Any):
self.config = config
self.binding = ReleaseBinding()
self.binding.validate()
self.root = Path(config.state_root) / "v2r2c1-governed-production-release"
self.state = ReleaseState(self.root, self.binding)
workspace = self.root / "publication-workspace"
workspace.mkdir(parents=True, exist_ok=True)
self.publisher = TagPublisher(
workspace,
GitTagBackend(workspace, authoritative_repo=Path(config.authoritative_git_dir)),
GiteaTagClient(config.api_base, REPOSITORY, Path(config.token_path)),
config.remote_name,
state_root=self.root / "tag-publication",
)
# Release checks must use the same explicitly configured staging baseline
# as the Action/model runtime; do not fall back to the publication workspace.
self.publisher.baseline_path = Path(
os.environ.get("AIOS_GITOPS_BASELINE_AUTHORITY", str(config.baseline_path))
)
# The immutable runtime package is the source of the version projection;
# the mutable state workspace only stores publication state.
self.publisher.versions_root = Path(__file__).resolve().parents[1] / "versions"
def _request(self, request: Mapping[str, Any]) -> PublicationRequest:
binding = self.binding
for key, expected in binding.as_dict().items():
if key in request and request[key] != expected:
raise ManualOwnerReviewRequired("release request binding mismatch:" + key)
return PublicationRequest(binding.version, binding.target_oid, binding.evidence_closure_oid, binding.approval_plan_id)
def approval_request(self, request: Mapping[str, Any]) -> dict[str, Any]:
if request.get("execute") is not False:
raise ReleaseBlocked("TAG_APPROVAL_REQUEST_MUST_BE_DRY_RUN")
owner = verify_owner_approval(request, self.binding)
with StateLock(self.root / "release.lock"):
current = self.state.value.get("approval")
approval_digest = hashlib.sha256(canonical(owner)).hexdigest()
record = {
"schema": "aios.v2r2c1.tag-approval-request.v1",
"binding": self.binding.as_dict(),
"owner_approval": owner,
"owner_approval_digest_sha256": approval_digest,
"execute": False,
"recorded_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
}
if current and current != record:
raise ManualOwnerReviewRequired("persisted Owner approval differs")
self.state.value["approval"] = record
self.state.record("APPROVAL_REQUEST_RECORDED", "tag_approval_request", record)
return {"status": "RECORDED", "execute": False, "binding": self.binding.as_dict(), "owner_approval_digest_sha256": approval_digest}
def _require_approval(self) -> None:
approval = self.state.value.get("approval")
if not isinstance(approval, Mapping) or approval.get("binding") != self.binding.as_dict():
raise ReleaseBlocked("OWNER_APPROVAL_REQUEST_NOT_RECORDED")
def publish_tag(self, request: Mapping[str, Any]) -> dict[str, Any]:
if request.get("execute") is not True:
raise ReleaseBlocked("TAG_PUBLICATION_REQUIRES_EXECUTE_TRUE")
self._require_approval()
publication = self._request(request)
with StateLock(self.root / "release.lock"):
try:
verified = self.publisher._verify_publication(publication, allow_push=True, previously_deployed=False)
except PublicationManualOwnerReviewRequired as exc:
self.state.fail("MANUAL_OWNER_REVIEW_REQUIRED", str(exc))
raise ManualOwnerReviewRequired(str(exc)) from exc
except PublicationPending as exc:
self.state.fail("REMOTE_PUSH_PENDING", str(exc))
raise ReconciliationRequired(str(exc)) from exc
except PublicationReconciliationRequired as exc:
self.state.fail("RECONCILIATION_REQUIRED", str(exc))
raise ReconciliationRequired(str(exc)) from exc
result = dict(verified)
self.state.record("TAG_VERIFIED", "tag", result)
return {"status": "REMOTE_VERIFIED", **result}
def tag_status(self, request: Mapping[str, Any]) -> dict[str, Any]:
self._request(request)
return self.publisher.get_tag_publication_status(VERSION)
def build_artifact(self, request: Mapping[str, Any]) -> dict[str, Any]:
self._require_approval()
self._request(request)
receipt = build_production_artifact(Path(self.config.authoritative_git_dir), self.binding, self.root / "artifacts")
self.state.record("ARTIFACT_VERIFIED", "production_artifact", receipt)
return {"status": "VERIFIED", **receipt}
def execute_request(self, request: Mapping[str, Any]) -> dict[str, Any]:
self._require_approval()
publication = self._request(request)
tag_state = self.state.value.get("completed", {}).get("tag")
if not isinstance(tag_state, Mapping) or tag_state.get("state") not in {"GITEA_API_CONFIRMED", "REMOTE_VERIFIED"}:
raise ReleaseBlocked("REMOTE_TAG_PROOF_REQUIRED")
artifact = self.state.value.get("completed", {}).get("production_artifact")
if not isinstance(artifact, Mapping):
artifact = self.build_artifact(request)
value = {
**self.binding.as_dict(),
"tag_oid": tag_state.get("tag_oid") or tag_state.get("gitea_tag_oid"),
"artifact_path": artifact["artifact"],
"artifact_size": int(artifact["artifact_size"]),
"artifact_sha256": artifact["artifact_sha256"],
}
# The production adapter is privileged and is run through the root
# approved-release service boundary by the caller. This handler is
# still useful for a root-owned staging invocation and remains
# fail-closed when the adapter is not available.
import v2r2c1_production_adapter
runtime = v2r2c1_production_adapter.execute_request({"schema": "aios.production-executor-request.v1", "operation": "apply", **value})
if runtime.get("status") != "ACTIVE_VERIFIED":
self.state.fail("RECONCILIATION_REQUIRED", "production health did not verify")
raise ReconciliationRequired("production health did not verify")
self.state.record("PRODUCTION_VERIFIED", "production", runtime)
return {"status": "PRODUCTION_VERIFIED", "runtime": runtime, "artifact": artifact, "tag_oid": value["tag_oid"]}
def consistency(self, request: Mapping[str, Any]) -> dict[str, Any]:
publication = self._request(request)
result = self.publisher.get_release_consistency(publication)
result["release_binding"] = self.binding.as_dict()
result["production_health"] = _health(self.config.health_url, self.binding, self.state.value.get("completed", {}).get("production_artifact", {}))
result["registry"] = _read_json(Path(self.config.registry_root) / f"{VERSION}.json")
result["baseline"] = _read_json(Path(self.config.baseline_path))
return result
def acceptance(self, request: Mapping[str, Any]) -> dict[str, Any]:
result = self.consistency(request)
result["accepted"] = result.get("status") == "PASS" and result.get("production_health", {}).get("status") == "ACTIVE_VERIFIED"
self.state.record("ACCEPTED" if result["accepted"] else "RECONCILIATION_REQUIRED", "acceptance", result)
return result
def _read_json(path: Path) -> dict[str, Any]:
try:
value = json.loads(path.read_text(encoding="utf-8"))
except FileNotFoundError:
return {"status": "MISSING", "path": str(path)}
if not isinstance(value, dict):
return {"status": "INVALID", "path": str(path)}
return {"status": "PRESENT", "path": str(path), **value}
def _health(url: str, binding: ReleaseBinding, artifact: Mapping[str, Any]) -> dict[str, Any]:
import urllib.request
try:
with urllib.request.urlopen(url, timeout=5) as response:
body = json.loads(response.read().decode("utf-8"))
status = response.status
except Exception as exc:
return {"status": "UNAVAILABLE", "error": type(exc).__name__}
expected_artifact = artifact.get("artifact_sha256")
exact = (
status == 200
and body.get("version") == binding.version
and body.get("commit_oid") == binding.target_oid
and (not expected_artifact or body.get("artifact_sha256") == expected_artifact)
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}
class TrustedPlanReleaseExecutor:
"""Release handler whose identity comes only from an immutable trusted plan.
The bridge may prepare/read evidence and request tag publication, but it does
not perform production side effects. The privileged approved-release
service consumes the same plan and exact Owner approval envelope.
"""
def __init__(self, config: Any):
self.config = config
configured = getattr(config, "release_plan_path", None)
self.plan_path = Path(configured) if configured else None
self.state_root = Path(config.state_root) / "trusted-release"
self.state_root.mkdir(parents=True, exist_ok=True)
self._approval: dict[str, Any] | None = None
self._tag: dict[str, Any] | None = None
def _plan(self) -> TrustedReleasePlan:
path = self.plan_path
if path is None:
env_path = os.environ.get("AIOS_RELEASE_PLAN_PATH", "")
path = Path(env_path) if env_path else None
if path is None:
raise ReleaseBlocked("TRUSTED_RELEASE_PLAN_REQUIRED")
try:
return TrustedReleasePlan.load(path)
except PlanError as exc:
raise ReleaseBlocked(f"TRUSTED_RELEASE_PLAN_INVALID:{exc}") from exc
@staticmethod
def _binding(plan: TrustedReleasePlan) -> dict[str, Any]:
value = plan.value
return {
"version": value["version"],
"target_oid": value["source_commit"],
"evidence_closure_oid": value["evidence_closure_oid"],
"artifact_sha256": value["artifact_sha256"],
"artifact_size": value["artifact"]["size"],
"approval_plan_id": value["approval_plan_id"],
"candidate_id": value["candidate_id"],
"release_plan_sha256": plan.release_plan_sha256,
}
def _request(self, request: Mapping[str, Any]) -> tuple[TrustedReleasePlan, dict[str, Any]]:
plan = self._plan()
binding = self._binding(plan)
for key, expected in binding.items():
if key in request and request[key] != expected:
raise ManualOwnerReviewRequired("trusted release request binding mismatch:" + key)
return plan, binding
def approval_request(self, request: Mapping[str, Any]) -> dict[str, Any]:
if request.get("execute") is not False:
raise ReleaseBlocked("TAG_APPROVAL_REQUEST_MUST_BE_DRY_RUN")
plan, binding = self._request(request)
approval = request.get("owner_approval")
if not isinstance(approval, Mapping) or approval.get("approved") is not True:
raise ReleaseBlocked("OWNER_RELEASE_APPROVAL_REQUIRED")
for key in ("candidate_id", "release_plan_sha256", "approval_plan_id"):
if approval.get(key) != binding[key]:
raise ManualOwnerReviewRequired("OWNER_APPROVAL_BINDING_MISMATCH:" + key)
record = {"plan": plan.as_dict(), "owner_approval": dict(approval)}
digest = hashlib.sha256(canonical(record)).hexdigest()
if self._approval is not None and self._approval != record:
raise ManualOwnerReviewRequired("OWNER_APPROVAL_REPLAY_CONFLICT")
self._approval = record
return {"status": "RECORDED", "execute": False, "release_plan_sha256": plan.release_plan_sha256, "approval_digest_sha256": digest}
def _require_approval(self, plan: TrustedReleasePlan) -> None:
if self._approval is None or self._approval.get("plan") != plan.as_dict():
raise ReleaseBlocked("OWNER_RELEASE_APPROVAL_NOT_RECORDED")
def build_artifact(self, request: Mapping[str, Any]) -> dict[str, Any]:
plan, _ = self._request(request)
artifact = dict(plan.value["artifact"])
path = Path(str(artifact["path"]))
if path.is_symlink() or not path.is_file() or path.stat().st_size != artifact["size"]:
raise ReleaseBlocked("TRUSTED_ARTIFACT_NOT_PRESENT")
actual = hashlib.sha256(path.read_bytes()).hexdigest()
if actual != plan.value["artifact_sha256"]:
raise ReleaseBlocked("TRUSTED_ARTIFACT_DIGEST_MISMATCH")
return {"status": "VERIFIED", "version": plan.value["version"], "source_commit": plan.value["source_commit"], "tree_oid": plan.value["tree_oid"], "artifact": str(path), "artifact_size": artifact["size"], "artifact_sha256": actual, "manifest_sha256": plan.value["manifest_sha256"], "package_closure_sha256": plan.value["package_closure_sha256"], "release_plan_sha256": plan.release_plan_sha256}
def _publisher(self, plan: TrustedReleasePlan) -> TagPublisher:
root = self.state_root / plan.value["candidate_id"]
root.mkdir(parents=True, exist_ok=True)
publisher = TagPublisher(root / "publication-workspace", GitTagBackend(root / "publication-workspace", authoritative_repo=Path(self.config.authoritative_git_dir)), GiteaTagClient(self.config.api_base, "aios/aios", Path(self.config.token_path)), self.config.remote_name, state_root=root / "tag-publication")
publisher.baseline_path = Path(os.environ.get("AIOS_GITOPS_BASELINE_AUTHORITY", str(self.config.baseline_path)))
publisher.versions_root = Path(__file__).resolve().parents[1] / "versions"
return publisher
def publish_tag(self, request: Mapping[str, Any]) -> dict[str, Any]:
if request.get("execute") is not True:
raise ReleaseBlocked("TAG_PUBLICATION_REQUIRES_EXECUTE_TRUE")
plan, _ = self._request(request)
self._require_approval(plan)
publication = PublicationRequest(plan.value["version"], plan.value["source_commit"], plan.value["evidence_closure_oid"], plan.value["approval_plan_id"])
try:
result = self._publisher(plan)._verify_publication(publication, allow_push=True, previously_deployed=False)
except (PublicationManualOwnerReviewRequired, PublicationPending, PublicationReconciliationRequired) as exc:
raise ReconciliationRequired(str(exc)) from exc
self._tag = dict(result)
return {"status": "REMOTE_VERIFIED", **result, "release_plan_sha256": plan.release_plan_sha256}
def tag_status(self, request: Mapping[str, Any]) -> dict[str, Any]:
plan, _ = self._request(request)
return self._publisher(plan).get_tag_publication_status(plan.value["version"])
def execute_request(self, request: Mapping[str, Any]) -> dict[str, Any]:
plan, _ = self._request(request)
self._require_approval(plan)
raise ReleaseBlocked("PRODUCTION_EXECUTION_REQUIRES_APPROVED_RELEASE_EXECUTOR")
def consistency(self, request: Mapping[str, Any]) -> dict[str, Any]:
plan, binding = self._request(request)
result = {"status": "PASS", "release_binding": binding, "trusted_plan": plan.as_dict(), "production_mutation": False}
result["baseline"] = _read_json(Path(self.config.baseline_path))
result["registry"] = _read_json(Path(self.config.registry_root) / f"{plan.value['version']}.json")
return result
def acceptance(self, request: Mapping[str, Any]) -> dict[str, Any]:
result = self.consistency(request)
result["accepted"] = result["status"] == "PASS"
return result
def build_executor_config(config: Any) -> Any:
"""Select legacy compatibility only when no trusted new-release plan exists."""
plan_path = getattr(config, "release_plan_path", None) or os.environ.get("AIOS_RELEASE_PLAN_PATH", "")
target_version = getattr(config, "target_version", "")
if plan_path or target_version != VERSION:
return TrustedPlanReleaseExecutor(config)
return GovernedProductionReleaseExecutor(config)
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("operation", choices=("build-artifact", "status"))
parser.add_argument("--git-dir", type=Path, default=Path("/data/aios-gitops/repo.git"))
parser.add_argument("--output-root", type=Path, default=Path("/var/lib/aios-gitops/approved-artifacts/V2R2C1"))
args = parser.parse_args()
binding = ReleaseBinding()
if args.operation == "build-artifact":
print(json.dumps(build_production_artifact(args.git_dir, binding, args.output_root), sort_keys=True))
else:
print(json.dumps({"binding": binding.as_dict(), "status": "READY"}, sort_keys=True))
return 0
if __name__ == "__main__":
raise SystemExit(main())
+6 -2
View File
@@ -34,6 +34,8 @@ from gitea_tag_publication import (
TagPublisher,
)
from release_version import validate_release_version
OID_RE = re.compile(r"^[0-9a-f]{40}$")
PLAN_RE = re.compile(r"^sha256:[0-9a-f]{64}$")
@@ -44,8 +46,10 @@ def load_request() -> dict[str, Any]:
raise ValueError("request_object_required")
if value.get("schema") != "aios.production-executor-request.v1":
raise ValueError("request_schema_invalid")
if not re.fullmatch(r"V\d+R\d+C\d+(?:-[A-Za-z0-9_.-]+)?", str(value.get("version", ""))):
raise ValueError("version_invalid")
try:
validate_release_version(value.get("version", ""))
except ValueError as exc:
raise ValueError("version_invalid") from exc
for name in ("target_oid", "evidence_closure_oid"):
if not OID_RE.fullmatch(str(value.get(name, ""))):
raise ValueError(f"{name}_invalid")
+225
View File
@@ -0,0 +1,225 @@
#!/usr/bin/env python3
"""AIOS durable autonomous release-campaign authorization.
The campaign receipt represents one explicit Owner policy delegation. It is not
a reusable Owner credential and it is not an unrestricted Git/production token.
Normal release operations are allowed only when all machine gates in the
campaign policy succeed. Formal tag conflicts, non-fast-forward main divergence,
history rewrite, private-signing-material exposure, repository identity conflict
or campaign revocation remain fail-closed/manual-review conditions.
"""
from __future__ import annotations
from dataclasses import dataclass
import hashlib
import json
import os
from pathlib import Path
import re
import stat
from typing import Any, Mapping
OID_RE = re.compile(r"^[0-9a-f]{40}$")
CAMPAIGN_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{3,127}$")
SCHEMA = "aios.release-campaign-authorization.v1"
ALLOWED_ENGINEERING = frozenset({
"create_task_branch",
"create_worktree",
"append_commit",
"push_task_branch",
"repair_task_branch",
"create_pr",
"update_pr",
"run_required_checks",
"publish_truthful_status",
"protected_merge",
})
ALLOWED_RECONCILIATION = frozenset({
"fetch_gitea_main",
"verify_ancestor",
"authority_main_ff_cas",
"refresh_runtime_context",
})
ALLOWED_RELEASE = frozenset({
"deterministic_build",
"retain_artifact",
"materialize_approved_artifact",
"staging_install",
"health_check",
"restart_test",
"rollback_readiness_test",
"create_evidence_commit",
"generate_exact_binding",
"derive_campaign_acceptance",
})
ALLOWED_PRODUCTION = frozenset({
"create_annotated_tag",
"publish_tag",
"verify_tag",
"deploy_exact_artifact",
"verify_production_health",
"update_runtime_registry",
"update_production_baseline",
"update_baseline_sidecar",
})
DENIED = frozenset({
"force_push",
"non_fast_forward_main_write",
"direct_main_push",
"published_history_rewrite",
"formal_tag_rewrite",
"formal_tag_delete",
"overwrite_conflicting_same_name_tag",
"expose_private_signing_material",
"commit_plaintext_secret",
"repository_identity_change",
"weaken_branch_protection",
})
class CampaignError(RuntimeError):
pass
def canonical_json(value: Any) -> bytes:
return (
json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=False)
+ "\n"
).encode("utf-8")
def sha256_bytes(value: bytes) -> str:
return hashlib.sha256(value).hexdigest()
def secure_json(path: Path) -> dict[str, Any]:
if path.is_symlink() or not path.is_file():
raise CampaignError(f"invalid_file:{path}")
st = path.lstat()
if st.st_uid != 0:
raise CampaignError(f"root_owner_required:{path}")
if stat.S_IMODE(st.st_mode) & 0o022:
raise CampaignError(f"group_world_writable_rejected:{path}")
try:
value = json.loads(path.read_text(encoding="utf-8"))
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
raise CampaignError(f"invalid_json:{path}") from exc
if not isinstance(value, dict):
raise CampaignError(f"json_object_required:{path}")
return value
def _exact_set(value: Any, expected: frozenset[str], name: str) -> None:
if not isinstance(value, list) or frozenset(value) != expected:
raise CampaignError(f"{name}_mismatch")
def validate_policy(value: Mapping[str, Any]) -> dict[str, Any]:
required = {
"schema",
"campaign_id",
"generation",
"owner_identity",
"owner_role",
"repository",
"lineage_anchor_oid",
"control_plane_branch",
"activation_state",
"active",
"revoked",
"valid_until_revoked",
"allowed_engineering",
"allowed_reconciliation",
"allowed_release",
"allowed_production",
"denied",
"normal_owner_interaction_required",
}
if set(value) != required:
raise CampaignError("campaign_fields_invalid")
if value["schema"] != SCHEMA:
raise CampaignError("campaign_schema_invalid")
if not isinstance(value["campaign_id"], str) or not CAMPAIGN_RE.fullmatch(value["campaign_id"]):
raise CampaignError("campaign_id_invalid")
if value["generation"] != 1:
raise CampaignError("campaign_generation_invalid")
if value["owner_identity"] != "aiman" or value["owner_role"] != "owner":
raise CampaignError("campaign_owner_invalid")
if value["repository"] != "aios/aios":
raise CampaignError("campaign_repository_invalid")
if not isinstance(value["lineage_anchor_oid"], str) or not OID_RE.fullmatch(value["lineage_anchor_oid"]):
raise CampaignError("campaign_anchor_invalid")
branch = value["control_plane_branch"]
if branch != "agent/autonomous-release-campaign-v1-20260807":
raise CampaignError("campaign_branch_invalid")
if value["activation_state"] not in {"PENDING_CONTROL_PLANE_MERGE", "ACTIVE"}:
raise CampaignError("campaign_activation_state_invalid")
if value["revoked"] is not False or value["valid_until_revoked"] is not True:
raise CampaignError("campaign_lifecycle_invalid")
if value["normal_owner_interaction_required"] is not False:
raise CampaignError("campaign_owner_interaction_policy_invalid")
if value["activation_state"] == "ACTIVE" and value["active"] is not True:
raise CampaignError("campaign_active_state_invalid")
if value["activation_state"] == "PENDING_CONTROL_PLANE_MERGE" and value["active"] is not False:
raise CampaignError("campaign_pending_state_invalid")
_exact_set(value["allowed_engineering"], ALLOWED_ENGINEERING, "allowed_engineering")
_exact_set(value["allowed_reconciliation"], ALLOWED_RECONCILIATION, "allowed_reconciliation")
_exact_set(value["allowed_release"], ALLOWED_RELEASE, "allowed_release")
_exact_set(value["allowed_production"], ALLOWED_PRODUCTION, "allowed_production")
_exact_set(value["denied"], DENIED, "denied")
return dict(value)
@dataclass(frozen=True)
class CampaignAuthorization:
path: Path
data: dict[str, Any]
digest: str
@classmethod
def load(
cls,
path: Path,
*,
sidecar: Path | None = None,
revocation: Path | None = None,
require_active: bool = True,
) -> "CampaignAuthorization":
data = validate_policy(secure_json(path))
raw = path.read_bytes()
digest = sha256_bytes(raw)
sidecar = sidecar or path.with_suffix(path.suffix + ".sha256")
if not sidecar.is_file() or sidecar.is_symlink():
raise CampaignError("campaign_digest_sidecar_missing")
token = sidecar.read_text(encoding="ascii").strip().split()[0]
if token != digest:
raise CampaignError("campaign_digest_sidecar_mismatch")
if revocation and revocation.exists():
revoked = secure_json(revocation)
if revoked.get("campaign_id") == data["campaign_id"] and revoked.get("revoked") is True:
raise CampaignError("campaign_revoked")
if require_active and (data["activation_state"] != "ACTIVE" or data["active"] is not True):
raise CampaignError("campaign_not_active")
return cls(path=path, data=data, digest=digest)
@property
def campaign_id(self) -> str:
return str(self.data["campaign_id"])
@property
def anchor_oid(self) -> str:
return str(self.data["lineage_anchor_oid"])
def assert_operation(self, operation: str) -> None:
if operation in DENIED:
raise CampaignError(f"campaign_operation_denied:{operation}")
universe = (
ALLOWED_ENGINEERING
| ALLOWED_RECONCILIATION
| ALLOWED_RELEASE
| ALLOWED_PRODUCTION
)
if operation not in universe:
raise CampaignError(f"campaign_operation_out_of_scope:{operation}")
+427
View File
@@ -0,0 +1,427 @@
#!/usr/bin/env python3
"""One-interaction bootstrap for the AIOS autonomous release campaign.
A single real local `aiman` login confirmation does two things atomically at the
policy layer:
1. Writes a root-owned, digest-sidecar protected campaign authorization receipt
in PENDING_CONTROL_PLANE_MERGE state. It grants no immediate production side
effect; the campaign becomes ACTIVE only after the campaign control-plane PR
is merged and machine activation verifies lineage and implementation.
2. Invokes the existing OwnerAuthorizationBroker to issue only the narrow
engineering writer needed to implement that control-plane PR.
No reusable Owner token is minted.
"""
from __future__ import annotations
import argparse
import fcntl
import hashlib
import json
import os
from pathlib import Path
import secrets
import socket
import struct
import tempfile
import time
from typing import Any, Mapping
from owner_local_auth_ingress import (
BootstrapError,
canonical_json,
read_frame,
verify_peer_session,
write_frame,
)
from release_campaign_authorization import (
ALLOWED_ENGINEERING,
ALLOWED_PRODUCTION,
ALLOWED_RECONCILIATION,
ALLOWED_RELEASE,
DENIED,
SCHEMA,
validate_policy,
)
BEGIN = "aios.release-campaign-bootstrap.begin.v1"
CHALLENGE = "aios.release-campaign-bootstrap.challenge.v1"
CONFIRM = "aios.release-campaign-bootstrap.confirm.v1"
RESULT = "aios.release-campaign-bootstrap.result.v1"
TASK_ID = "AIOS_AUTONOMOUS_RELEASE_CAMPAIGN_V1"
TARGET_BRANCH = "agent/autonomous-release-campaign-v1-20260807"
REPOSITORY = "aios/aios"
class CampaignBootstrapError(RuntimeError):
pass
def sha256_file(path: Path) -> str:
h = hashlib.sha256()
with path.open("rb") as stream:
for block in iter(lambda: stream.read(1024 * 1024), b""):
h.update(block)
return h.hexdigest()
def atomic_json_new(path: Path, value: Mapping[str, Any]) -> None:
if path.exists() or path.is_symlink():
raise CampaignBootstrapError(f"target_exists:{path}")
path.parent.mkdir(parents=True, exist_ok=True)
fd, name = tempfile.mkstemp(prefix=f".{path.name}.", dir=path.parent)
try:
os.fchmod(fd, 0o600)
if os.geteuid() == 0:
os.fchown(fd, 0, 0)
with os.fdopen(fd, "wb", closefd=True) as stream:
stream.write(canonical_json(dict(value)))
stream.flush()
os.fsync(stream.fileno())
os.replace(name, path)
os.chmod(path, 0o600)
dfd = os.open(path.parent, os.O_RDONLY | os.O_DIRECTORY)
try:
os.fsync(dfd)
finally:
os.close(dfd)
finally:
try:
os.unlink(name)
except FileNotFoundError:
pass
def atomic_sidecar(path: Path, digest: str, target_name: str) -> None:
if path.exists() or path.is_symlink():
raise CampaignBootstrapError(f"sidecar_exists:{path}")
path.parent.mkdir(parents=True, exist_ok=True)
fd, name = tempfile.mkstemp(prefix=f".{path.name}.", dir=path.parent)
try:
os.fchmod(fd, 0o600)
if os.geteuid() == 0:
os.fchown(fd, 0, 0)
with os.fdopen(fd, "w", encoding="ascii", closefd=True) as stream:
stream.write(f"{digest} {target_name}\n")
stream.flush()
os.fsync(stream.fileno())
os.replace(name, path)
os.chmod(path, 0o600)
dfd = os.open(path.parent, os.O_RDONLY | os.O_DIRECTORY)
try:
os.fsync(dfd)
finally:
os.close(dfd)
finally:
try:
os.unlink(name)
except FileNotFoundError:
pass
def writer_request(base_oid: str) -> dict[str, Any]:
return {
"action": "AUTHORIZE_TASK_WRITER_PROVISIONING",
"task_id": TASK_ID,
"repository": REPOSITORY,
"base_ref": "refs/heads/main",
"base_oid": base_oid,
"target_branch": TARGET_BRANCH,
"one_time_use": True,
"expires_in_seconds": 300,
"production_change_authorized": False,
"tag_change_authorized": False,
"baseline_change_authorized": False,
"main_write_authorized": False,
"allowed_operations": [
"create_worktree",
"create_target_branch_if_absent",
"commit_to_target_branch",
"push_target_branch",
],
"denied_operations": [
"direct_main_push",
"force_push",
"tag_create",
"tag_update",
"tag_delete",
"branch_delete",
"repository_admin",
"baseline_write",
"production_deploy",
"production_marker_write",
"history_rewrite",
],
}
def campaign_policy(campaign_id: str, base_oid: str) -> dict[str, Any]:
value = {
"schema": SCHEMA,
"campaign_id": campaign_id,
"generation": 1,
"owner_identity": "aiman",
"owner_role": "owner",
"repository": REPOSITORY,
"lineage_anchor_oid": base_oid,
"control_plane_branch": TARGET_BRANCH,
"activation_state": "PENDING_CONTROL_PLANE_MERGE",
"active": False,
"revoked": False,
"valid_until_revoked": True,
"allowed_engineering": sorted(ALLOWED_ENGINEERING),
"allowed_reconciliation": sorted(ALLOWED_RECONCILIATION),
"allowed_release": sorted(ALLOWED_RELEASE),
"allowed_production": sorted(ALLOWED_PRODUCTION),
"denied": sorted(DENIED),
"normal_owner_interaction_required": False,
}
return validate_policy(value)
def invoke_writer_broker(base_oid: str) -> dict[str, Any]:
from owner_authorization_broker import (
OwnerAuthorizationBroker,
OwnerAuthorizationBrokerConfig,
OwnerPrincipal,
)
config = OwnerAuthorizationBrokerConfig.from_environment()
if "aiman" not in config.allowed_owner_identities:
raise CampaignBootstrapError("aiman_not_allowed_by_live_broker")
if "commit:create" not in config.allowed_intake_scopes:
raise CampaignBootstrapError("commit_create_not_allowed_by_live_broker")
principal = OwnerPrincipal(
identity="aiman",
role="owner",
authenticated_scope="commit:create",
token_scopes=frozenset({"commit:create"}),
credential_id="",
)
return OwnerAuthorizationBroker(config=config).authorize_and_provision(
writer_request(base_oid),
principal,
)
def safe_broker_result(value: Mapping[str, Any]) -> dict[str, Any]:
allowed = {
"status",
"approval_plan_id",
"grant_id",
"grant_digest_sha256",
"branch_oid",
"branch_ref_created",
"grant_consumed",
"owner_authorization_event_id",
"owner_authorization_reference",
"owner_authorization_digest_sha256",
"owner_authorization_consumed",
"owner_authorization_idempotent",
}
lowered = " ".join(str(k).lower() for k in value)
for secret_word in ("token", "password", "private_key", "secret_value", "bearer"):
if secret_word in lowered:
raise CampaignBootstrapError("broker_response_sensitive_field_rejected")
return {k: value[k] for k in sorted(allowed) if k in value}
def serve(
*,
socket_path: Path,
state_root: Path,
base_oid: str,
campaign_id: str,
timeout: int,
) -> int:
if os.geteuid() != 0:
raise CampaignBootstrapError("server_root_required")
if len(base_oid) != 40 or any(c not in "0123456789abcdef" for c in base_oid):
raise CampaignBootstrapError("base_oid_invalid")
policy = campaign_policy(campaign_id, base_oid)
campaign_dir = state_root / campaign_id
campaign_path = campaign_dir / "authorization.json"
sidecar_path = campaign_dir / "authorization.json.sha256"
socket_path.parent.mkdir(parents=True, exist_ok=True)
if socket_path.exists() or socket_path.is_symlink():
raise CampaignBootstrapError("socket_already_exists")
import pwd
owner = pwd.getpwnam("aiman")
os.chown(socket_path.parent, 0, owner.pw_gid)
os.chmod(socket_path.parent, 0o750)
listener = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
try:
listener.bind(str(socket_path))
os.chown(socket_path, 0, owner.pw_gid)
os.chmod(socket_path, 0o660)
listener.listen(1)
listener.settimeout(timeout)
conn, _ = listener.accept()
with conn:
raw = conn.getsockopt(socket.SOL_SOCKET, socket.SO_PEERCRED, struct.calcsize("3i"))
pid, uid, gid = struct.unpack("3i", raw)
peer = verify_peer_session(pid, uid, gid, "aiman")
if read_frame(conn) != {"schema": BEGIN, "action": "BEGIN"}:
raise CampaignBootstrapError("client_begin_invalid")
challenge = secrets.token_hex(12)
issued = time.monotonic()
summary = {
"campaign_id": campaign_id,
"repository": REPOSITORY,
"lineage_anchor_oid": base_oid,
"control_plane_branch": TARGET_BRANCH,
"normal_owner_interaction_after_activation": False,
"allowed_engineering": sorted(ALLOWED_ENGINEERING),
"allowed_reconciliation": sorted(ALLOWED_RECONCILIATION),
"allowed_release": sorted(ALLOWED_RELEASE),
"allowed_production": sorted(ALLOWED_PRODUCTION),
"denied": sorted(DENIED),
"writer_request": writer_request(base_oid),
}
write_frame(
conn,
{
"schema": CHALLENGE,
"challenge": challenge,
"expires_in_seconds": 120,
"summary": summary,
},
)
confirm = read_frame(conn)
phrase = f"AUTHORIZE CAMPAIGN {campaign_id} {challenge}"
if (
confirm.get("schema") != CONFIRM
or confirm.get("challenge") != challenge
or confirm.get("confirmation") != phrase
):
raise CampaignBootstrapError("campaign_confirmation_invalid")
if time.monotonic() - issued > 120:
raise CampaignBootstrapError("campaign_confirmation_expired")
# First issue the narrow engineering writer. If this fails, do not
# persist a campaign authorization that cannot be implemented.
broker = safe_broker_result(invoke_writer_broker(base_oid))
if broker.get("status") != "READY_FOR_CODE_AUTHORING":
raise CampaignBootstrapError("writer_not_ready")
atomic_json_new(campaign_path, policy)
digest = sha256_file(campaign_path)
atomic_sidecar(sidecar_path, digest, campaign_path.name)
receipt = {
"schema": "aios.release-campaign-bootstrap-receipt.v1",
"status": "CAMPAIGN_PENDING_CONTROL_PLANE_MERGE",
"campaign_id": campaign_id,
"campaign_authorization_path": str(campaign_path),
"campaign_authorization_sha256": digest,
"owner_identity": "aiman",
"owner_role": "owner",
"authentication": {
"method": "local_so_peercred_loginuid_tty",
"uid": peer["uid"],
"loginuid": peer["loginuid"],
"tty": peer["tty"],
},
"writer": broker,
"reusable_owner_token_created": False,
"production_side_effect_executed": False,
"completed_at_unix": int(time.time()),
}
receipt_path = campaign_dir / "bootstrap-receipt.json"
atomic_json_new(receipt_path, receipt)
write_frame(
conn,
{
"schema": RESULT,
"ok": True,
"campaign_id": campaign_id,
"campaign_authorization_path": str(campaign_path),
"campaign_authorization_sha256": digest,
"writer": broker,
},
)
return 0
finally:
listener.close()
try:
socket_path.unlink()
except FileNotFoundError:
pass
def client(socket_path: Path) -> int:
import sys
if not sys.stdin.isatty() or not sys.stdout.isatty():
raise CampaignBootstrapError("interactive_tty_required")
conn = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
conn.connect(str(socket_path))
with conn:
write_frame(conn, {"schema": BEGIN, "action": "BEGIN"})
challenge = read_frame(conn)
if challenge.get("schema") != CHALLENGE:
raise CampaignBootstrapError("challenge_invalid")
print("\nAIOS Autonomous Release Campaign authorization")
print(json.dumps(challenge["summary"], indent=2, sort_keys=True))
phrase = (
f"AUTHORIZE CAMPAIGN "
f"{challenge['summary']['campaign_id']} "
f"{challenge['challenge']}"
)
typed = input(f"\nType exactly:\n{phrase}\n> ").strip()
if typed != phrase:
raise CampaignBootstrapError("confirmation_mismatch")
write_frame(
conn,
{
"schema": CONFIRM,
"challenge": challenge["challenge"],
"confirmation": typed,
},
)
result = read_frame(conn)
print(json.dumps(result, indent=2, sort_keys=True))
return 0 if result.get("ok") is True else 3
def main() -> int:
p = argparse.ArgumentParser()
mode = p.add_mutually_exclusive_group(required=True)
mode.add_argument("--serve", action="store_true")
mode.add_argument("--client", action="store_true")
p.add_argument("--socket", type=Path, default=Path("/run/aios-release-campaign/owner.sock"))
p.add_argument(
"--state-root",
type=Path,
default=Path("/var/lib/aios-gitops/release-campaigns"),
)
p.add_argument("--base-oid")
p.add_argument("--campaign-id")
p.add_argument("--timeout", type=int, default=300)
a = p.parse_args()
if a.serve:
if not a.base_oid or not a.campaign_id:
raise CampaignBootstrapError("server_binding_required")
return serve(
socket_path=a.socket,
state_root=a.state_root,
base_oid=a.base_oid,
campaign_id=a.campaign_id,
timeout=a.timeout,
)
return client(a.socket)
if __name__ == "__main__":
try:
raise SystemExit(main())
except (CampaignBootstrapError, BootstrapError) as exc:
print(json.dumps({"status": "BLOCKED", "reason": str(exc)}, sort_keys=True))
raise SystemExit(2)
+113
View File
@@ -0,0 +1,113 @@
#!/usr/bin/env python3
"""Durable state model for the AIOS autonomous release campaign."""
from __future__ import annotations
from dataclasses import dataclass
import json
import os
from pathlib import Path
import tempfile
from typing import Any
STATES = (
"DEVELOPMENT",
"VALIDATING",
"PR_READY",
"MERGING",
"MAIN_RECONCILING",
"POSTMERGE_BUILDING",
"STAGING",
"EVIDENCE_CLOSING",
"BINDING_ACCEPTING",
"TAGGING",
"DEPLOYING",
"VERIFYING",
"REGISTRY_FINALIZING",
"BASELINE_FINALIZING",
"PRODUCTION_DEPLOYED",
)
RECOVERABLE = "RECOVERABLE_FAILURE"
RECONCILIATION = "RECONCILIATION_REQUIRED"
MANUAL = "MANUAL_OWNER_REVIEW_REQUIRED"
NEXT = {STATES[i]: STATES[i + 1] for i in range(len(STATES) - 1)}
MANUAL_REASONS = frozenset({
"formal_same_name_tag_conflict",
"force_push_required",
"published_history_rewrite_required",
"non_fast_forward_main_divergence",
"private_signing_material_exposure_required",
"repository_identity_conflict",
"campaign_revoked",
"operation_outside_campaign_scope",
})
class OrchestratorError(RuntimeError):
pass
@dataclass
class ReleaseState:
version: str
state: str = "DEVELOPMENT"
last_stable_state: str = "DEVELOPMENT"
recoverable_reason: str | None = None
def advance(self) -> None:
if self.state not in NEXT:
raise OrchestratorError(f"cannot_advance:{self.state}")
self.last_stable_state = self.state
self.state = NEXT[self.state]
self.recoverable_reason = None
def recoverable_failure(self, reason: str) -> None:
self.last_stable_state = self.state
self.state = RECOVERABLE
self.recoverable_reason = reason
def reconciliation_required(self, reason: str) -> None:
self.last_stable_state = self.state
self.state = RECONCILIATION
self.recoverable_reason = reason
def manual_review(self, reason: str) -> None:
if reason not in MANUAL_REASONS:
raise OrchestratorError("reason_not_manual_red_line")
self.last_stable_state = self.state
self.state = MANUAL
self.recoverable_reason = reason
def resume(self) -> None:
if self.state not in {RECOVERABLE, RECONCILIATION}:
raise OrchestratorError("state_not_resumable")
self.state = self.last_stable_state
def as_dict(self) -> dict[str, Any]:
return {
"schema": "aios.autonomous-release-state.v1",
"version": self.version,
"state": self.state,
"last_stable_state": self.last_stable_state,
"recoverable_reason": self.recoverable_reason,
"owner_interaction_required": self.state == MANUAL,
}
def atomic_state(path: Path, state: ReleaseState) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
fd, name = tempfile.mkstemp(prefix=f".{path.name}.", dir=path.parent)
try:
os.fchmod(fd, 0o600)
with os.fdopen(fd, "w", encoding="utf-8", closefd=True) as stream:
json.dump(state.as_dict(), stream, sort_keys=True, indent=2)
stream.write("\n")
stream.flush()
os.fsync(stream.fileno())
os.replace(name, path)
finally:
try:
os.unlink(name)
except FileNotFoundError:
pass
@@ -27,6 +27,8 @@ import urllib.request
from pathlib import Path
from typing import Any, Mapping
from release_version import validate_release_version
OID_RE = re.compile(r"^[0-9a-f]{40}$")
SHA_RE = re.compile(r"^[0-9a-f]{64}$")
PLAN_RE = re.compile(r"^sha256:[0-9a-f]{64}$")
@@ -86,8 +88,10 @@ def load_request() -> dict[str, Any]:
raise ValueError("request_schema_invalid")
if value.get("repository_identity") != "aios/aios":
raise ValueError("repository_identity_invalid")
if not re.fullmatch(r"V\d+R\d+C\d+(?:-[A-Za-z0-9_.-]+)?", str(value.get("version", ""))):
raise ValueError("version_invalid")
try:
validate_release_version(value.get("version", ""))
except ValueError as exc:
raise ValueError("version_invalid") from exc
for name in ("target_oid", "evidence_closure_oid"):
if not OID_RE.fullmatch(str(value.get(name, ""))):
raise ValueError(f"{name}_invalid")
@@ -22,11 +22,12 @@ from enum import Enum
from pathlib import Path
from typing import Any, Protocol
from release_version import validate_release_version
OID_RE = re.compile(r"^[0-9a-f]{40}$")
SHA256_RE = re.compile(r"^[0-9a-f]{64}$")
CANDIDATE_ID_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.-]{2,127}$")
VERSION_RE = re.compile(r"^V\d+R\d+C\d+(?:-[A-Za-z0-9_.-]+)?$")
SECRET_PATTERNS = (
re.compile(r"(?i)(authorization\s*[:=]\s*)(?:bearer|token)\s+\S+"),
re.compile(r"(?i)((?:access_)?token\s*[:=]\s*)\S+"),
@@ -769,8 +770,10 @@ class TagApprovalEnvelope:
def validate(self) -> None:
if self.action != "production_tag":
raise ApprovalError("approval does not authorize production tag")
if not VERSION_RE.fullmatch(self.version):
raise ApprovalError("invalid release version")
try:
validate_release_version(self.version)
except ValueError as exc:
raise ApprovalError("invalid release version") from exc
_validate_oid("tag_oid", self.tag_oid)
_validate_oid("target_oid", self.target_oid)
if _parse_utc(self.expires_at) <= datetime.now(timezone.utc):
+603 -32
View File
@@ -9,6 +9,7 @@ from __future__ import annotations
import base64
import hashlib
import hmac
import json
import os
import re
@@ -40,6 +41,8 @@ from silent_release_controller import (
IDENTIFIER_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.:-]{2,255}$")
NONCE_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.:-]{15,255}$")
BUILDER_ATTESTATION_TRUST_DOMAIN = "aios.trusted-builder.attestation.v1"
BUILDER_ATTESTATION_PURPOSE = "AIOS_TRUSTED_RELEASE_BUILDER"
ALLOWED_CAPABILITIES = frozenset(
{
"runtime_switch",
@@ -62,6 +65,10 @@ class CapabilityAuthorizationError(RuntimeError):
"""The signed plan cannot authorize the requested operation."""
class TrustedCandidateBuilderUnavailable(RuntimeError):
"""The purpose-bound builder dependency cannot currently serve a build."""
class StaleApprovalError(CapabilityAuthorizationError):
"""Live release evidence no longer matches the signed approval."""
@@ -222,6 +229,10 @@ JOURNAL_GENESIS_HASH = "0" * 64
def _fsync_directory(path: Path) -> None:
if os.name == "nt":
# Windows does not permit opening a directory with os.open. Durable
# Linux host execution retains the directory fsync below.
return
descriptor = os.open(Path(path), os.O_RDONLY)
try:
os.fsync(descriptor)
@@ -715,6 +726,14 @@ class BuilderAttestation:
canary_evidence_sha256: str
issued_at: str
signature: str
trust_domain: str = BUILDER_ATTESTATION_TRUST_DOMAIN
builder_key_generation: int | None = None
signature_algorithm: str | None = None
signature_purpose: str | None = None
gitea_evidence_sha256: str = ""
rollback_evidence_sha256: str = ""
policy_evidence_sha256: str = ""
secret_scan_evidence_sha256: str = ""
def __post_init__(self) -> None:
_validate_identifier(
@@ -731,7 +750,35 @@ class BuilderAttestation:
"canary_evidence_sha256",
):
_validate_sha(name, getattr(self, name))
for name in (
"gitea_evidence_sha256",
"rollback_evidence_sha256",
"policy_evidence_sha256",
"secret_scan_evidence_sha256",
):
if getattr(self, name):
_validate_sha(name, getattr(self, name))
_parse_utc(self.issued_at)
if self.trust_domain != BUILDER_ATTESTATION_TRUST_DOMAIN:
raise CapabilityAuthorizationError(
"builder attestation trust domain is invalid"
)
metadata = (
self.builder_key_generation,
self.signature_algorithm,
self.signature_purpose,
)
if any(value is not None for value in metadata):
if (
not isinstance(self.builder_key_generation, int)
or isinstance(self.builder_key_generation, bool)
or self.builder_key_generation < 0
or self.signature_algorithm != "Ed25519"
or self.signature_purpose != BUILDER_ATTESTATION_PURPOSE
):
raise CapabilityAuthorizationError(
"builder attestation signing metadata is invalid"
)
@property
def canonical_payload(self) -> dict[str, Any]:
@@ -754,20 +801,290 @@ class BuilderAttestation:
}
@classmethod
def from_dict(cls, payload: Mapping[str, Any]) -> "BuilderAttestation":
def from_dict(
cls,
payload: Mapping[str, Any],
*,
require_fingerprint: bool = False,
) -> "BuilderAttestation":
values = dict(payload)
values.pop("fingerprint", None)
return cls(**values)
supplied_fingerprint = values.pop("fingerprint", None)
# Older local records did not spell out the domain. They are only
# usable when re-signed by the current builder; their original
# signatures cannot validate against this domain-separated payload.
values.setdefault("trust_domain", BUILDER_ATTESTATION_TRUST_DOMAIN)
value = cls(**values)
if supplied_fingerprint is None:
if require_fingerprint:
raise CapabilityAuthorizationError(
"builder attestation digest is required"
)
return value
expected = _validate_sha(
"builder attestation digest", str(supplied_fingerprint)
)
if not hmac.compare_digest(expected, value.fingerprint):
raise CapabilityAuthorizationError(
"builder attestation digest is invalid"
)
return value
@dataclass(frozen=True)
class BuilderAttestationKeyRecord:
"""One purpose-bound public verification key from the host keyring."""
key_id: str
public_key: bytes
algorithm: str
purpose: str
generation: int
not_before: str
expires_at: str
status: str
def __post_init__(self) -> None:
_validate_identifier("builder_key_id", self.key_id)
if self.algorithm != "Ed25519":
raise ValueError("unsupported builder signature algorithm")
if self.purpose != BUILDER_ATTESTATION_PURPOSE:
raise ValueError("builder key purpose is invalid")
if not isinstance(self.generation, int) or isinstance(
self.generation, bool
) or self.generation < 0:
raise ValueError("builder key generation is invalid")
if self.status not in {"ACTIVE", "REVOKED"}:
raise ValueError("builder key status is invalid")
if len(self.public_key) != 32:
raise ValueError("Ed25519 public key must contain 32 bytes")
Ed25519PublicKey.from_public_bytes(self.public_key)
if _parse_utc(self.not_before) >= _parse_utc(self.expires_at):
raise ValueError("builder key validity window is invalid")
@property
def public_key_sha256(self) -> str:
return hashlib.sha256(self.public_key).hexdigest()
def validate_at(self, instant: datetime) -> None:
if self.status != "ACTIVE":
raise CapabilityAuthorizationError(
"builder attestation key is unavailable or revoked"
)
if instant.tzinfo is None:
raise CapabilityAuthorizationError(
"builder attestation verification time is invalid"
)
if not _parse_utc(self.not_before) <= instant <= _parse_utc(
self.expires_at
):
raise CapabilityAuthorizationError(
"builder attestation key is outside its validity window"
)
@classmethod
def from_dict(
cls, payload: Mapping[str, Any]
) -> "BuilderAttestationKeyRecord":
values = dict(payload)
encoded = values.pop("public_key", values.pop("public_key_b64", None))
if not isinstance(encoded, str):
raise ValueError("builder public key is missing")
try:
public_key = base64.b64decode(encoded, validate=True)
except (TypeError, ValueError) as exc:
raise ValueError("builder public key encoding is invalid") from exc
supplied_digest = values.pop(
"public_key_sha256",
values.pop("key_sha256", values.pop("digest", None)),
)
if not isinstance(supplied_digest, str):
raise ValueError("builder public key digest is missing")
digest = _validate_sha("builder public key digest", supplied_digest)
if not hmac.compare_digest(
digest, hashlib.sha256(public_key).hexdigest()
):
raise ValueError("builder public key digest is invalid")
allowed = {
"algorithm",
"purpose",
"key_id",
"generation",
"not_before",
"expires_at",
"status",
}
unknown = set(values).difference(allowed)
if unknown:
raise ValueError("builder key record contains unknown fields")
return cls(public_key=public_key, **values)
class BuilderAttestationKeyRing:
"""Validated, public-only keyring for builder attestations.
The file format deliberately cannot carry Owner approval keys: both the
keyring and every record must declare the builder-attestation purpose.
"""
def __init__(
self,
records: Iterable[BuilderAttestationKeyRecord],
*,
require_attestation_metadata: bool = False,
):
self._records: dict[str, BuilderAttestationKeyRecord] = {}
self._require_attestation_metadata = require_attestation_metadata
for record in records:
if record.key_id in self._records:
raise ValueError("duplicate builder key id")
self._records[record.key_id] = record
@classmethod
def from_public_keys(
cls, public_keys: Mapping[str, bytes]
) -> "BuilderAttestationKeyRing":
"""Compatibility adapter for explicit in-process builder key maps.
This is intentionally not used for host configuration; host keyrings
must use :meth:`from_file` so purpose, digest, validity and status are
checked before the API accepts traffic.
"""
return cls(
BuilderAttestationKeyRecord(
key_id=_validate_identifier("builder_key_id", key_id),
public_key=bytes(public_key),
algorithm="Ed25519",
purpose=BUILDER_ATTESTATION_PURPOSE,
generation=0,
not_before="1970-01-01T00:00:00Z",
expires_at="9999-12-31T23:59:59Z",
status="ACTIVE",
)
for key_id, public_key in public_keys.items()
)
@classmethod
def from_file(cls, path: Path) -> "BuilderAttestationKeyRing":
payload = json.loads(Path(path).read_text(encoding="utf-8"))
if payload.get("algorithm") != "Ed25519":
raise ValueError("unsupported builder keyring algorithm")
if payload.get("purpose") != BUILDER_ATTESTATION_PURPOSE:
raise ValueError("builder keyring purpose is invalid")
raw_records = payload.get("keys")
if not isinstance(raw_records, list) or not raw_records:
raise ValueError("builder keyring keys are invalid")
allowed = {"algorithm", "purpose", "keys", "keyring_sha256"}
if set(payload).difference(allowed):
raise ValueError("builder keyring contains unknown fields")
if "keyring_sha256" not in payload:
raise ValueError("builder keyring digest is missing")
expected = _validate_sha(
"builder keyring digest", str(payload["keyring_sha256"])
)
canonical = _canonical_json(
{
"algorithm": payload["algorithm"],
"purpose": payload["purpose"],
"keys": raw_records,
}
)
if not hmac.compare_digest(
expected, hashlib.sha256(canonical).hexdigest()
):
raise ValueError("builder keyring digest is invalid")
if not all(isinstance(record, Mapping) for record in raw_records):
raise ValueError("builder keyring record is invalid")
return cls(
(
BuilderAttestationKeyRecord.from_dict(record)
for record in raw_records
),
require_attestation_metadata=True,
)
def verify(
self,
key_id: str,
signature: bytes,
message: bytes,
*,
at: datetime,
generation: int | None,
algorithm: str | None,
purpose: str | None,
) -> BuilderAttestationKeyRecord:
record = self._records.get(key_id)
if record is None:
raise CapabilityAuthorizationError(
"builder attestation key is not trusted"
)
record.validate_at(at)
metadata = (generation, algorithm, purpose)
if self._require_attestation_metadata and any(
value is None for value in metadata
):
raise CapabilityAuthorizationError(
"builder attestation signing metadata is required"
)
if any(value is not None for value in metadata) and metadata != (
record.generation,
record.algorithm,
record.purpose,
):
raise CapabilityAuthorizationError(
"builder attestation signing metadata does not match key"
)
try:
Ed25519PublicKey.from_public_bytes(record.public_key).verify(
signature, message
)
except InvalidSignature as exc:
raise CapabilityAuthorizationError(
"builder attestation signature is invalid"
) from exc
return record
def require_active_identity(
self,
*,
key_id: str,
generation: int,
algorithm: str,
purpose: str,
at: datetime | None = None,
) -> BuilderAttestationKeyRecord:
record = self._records.get(key_id)
if record is None:
raise CapabilityAuthorizationError(
"builder attestation key is not trusted"
)
if (
record.generation != generation
or record.algorithm != algorithm
or record.purpose != purpose
):
raise CapabilityAuthorizationError(
"builder attestation key metadata is invalid"
)
record.validate_at(at or datetime.now(timezone.utc))
return record
class BuilderAttestationVerifier:
"""Public-key-only verifier; no builder private material is accepted."""
def __init__(self, public_keys: Mapping[str, bytes]):
self.public_keys = {
_validate_identifier("builder_key_id", key_id): bytes(value)
for key_id, value in public_keys.items()
}
def __init__(
self,
public_keys: BuilderAttestationKeyRing | Mapping[str, bytes],
):
if isinstance(public_keys, BuilderAttestationKeyRing):
self.keyring = public_keys
elif isinstance(public_keys, Mapping):
self.keyring = BuilderAttestationKeyRing.from_public_keys(
public_keys
)
else:
raise TypeError("builder verifier requires a builder keyring")
def verify(
self,
@@ -793,22 +1110,54 @@ class BuilderAttestationVerifier:
raise CapabilityAuthorizationError(
"builder attestation does not bind candidate evidence"
)
key = self.public_keys.get(attestation.builder_key_id)
if key is None:
raise CapabilityAuthorizationError(
"builder attestation key is not trusted"
)
try:
signature = base64.b64decode(
attestation.signature, validate=True
)
Ed25519PublicKey.from_public_bytes(key).verify(
signature, attestation.canonical_bytes()
key_record = self.keyring.verify(
attestation.builder_key_id,
signature,
attestation.canonical_bytes(),
at=_parse_utc(attestation.issued_at),
generation=attestation.builder_key_generation,
algorithm=attestation.signature_algorithm,
purpose=attestation.signature_purpose,
)
except (InvalidSignature, ValueError, TypeError) as exc:
raise CapabilityAuthorizationError(
"builder attestation signature is invalid"
) from exc
if key_record.generation >= 1:
expected_gitea = hashlib.sha256(
_canonical_json(candidate.gitea_consistency)
).hexdigest()
expected_rollback = hashlib.sha256(
_canonical_json(
{
"ready": candidate.rollback_ready,
"rollback_sha256": release_plan.deployment.rollback_sha256,
"source_commit": candidate.source_commit,
}
)
).hexdigest()
production_evidence = (
attestation.gitea_evidence_sha256,
attestation.rollback_evidence_sha256,
attestation.policy_evidence_sha256,
attestation.secret_scan_evidence_sha256,
)
if any(not value for value in production_evidence):
raise CapabilityAuthorizationError(
"builder attestation production evidence is incomplete"
)
if (
attestation.gitea_evidence_sha256 != expected_gitea
or attestation.rollback_evidence_sha256 != expected_rollback
or attestation.policy_evidence_sha256 != release_plan.policy_sha256
):
raise CapabilityAuthorizationError(
"builder attestation production evidence is invalid"
)
return attestation
@@ -3181,9 +3530,12 @@ class SilentReleaseActionService:
builder_attestation_verifier: BuilderAttestationVerifier | None = None,
trusted_policy: TrustedReleasePolicy | None = None,
orchestrator: ReleaseOrchestrator | None = None,
builder_readiness_probe: Callable[[], Mapping[str, Any]] | None = None,
allow_legacy_candidate_input: bool = False,
expected_repository: str = "",
policy_sha256: str = "",
max_builder_attempts: int = 3,
builder_attempt_timeout_seconds: int = 180,
):
self.root = Path(root)
self.execution_enabled = bool(execution_enabled)
@@ -3194,11 +3546,28 @@ class SilentReleaseActionService:
self.builder_attestation_verifier = builder_attestation_verifier
self.trusted_policy = trusted_policy
self.orchestrator = orchestrator
self.builder_readiness_probe = builder_readiness_probe
self.allow_legacy_candidate_input = bool(
allow_legacy_candidate_input
)
self.expected_repository = expected_repository
self.policy_sha256 = policy_sha256
if (
not isinstance(max_builder_attempts, int)
or isinstance(max_builder_attempts, bool)
or not 1 <= max_builder_attempts <= 10
):
raise ValueError("max_builder_attempts must be between 1 and 10")
self.max_builder_attempts = max_builder_attempts
if (
not isinstance(builder_attempt_timeout_seconds, int)
or isinstance(builder_attempt_timeout_seconds, bool)
or not 1 <= builder_attempt_timeout_seconds <= 3600
):
raise ValueError(
"builder_attempt_timeout_seconds must be between 1 and 3600"
)
self.builder_attempt_timeout_seconds = builder_attempt_timeout_seconds
self.candidates = ReleaseCandidateStore(
self.root / "candidates"
)
@@ -3235,10 +3604,26 @@ class SilentReleaseActionService:
+ ", ".join(missing)
)
def get_builder_readiness(self) -> dict[str, Any]:
"""Report dependency readiness without conflating it with API liveness."""
if self.candidate_builder is None:
return {"status": "UNAVAILABLE"}
if self.builder_readiness_probe is None:
# Explicit in-process test/adaptor construction remains usable;
# production wiring provides a probe backed by its credentials.
return {"status": "READY"}
try:
value = dict(self.builder_readiness_probe())
except Exception:
return {"status": "UNAVAILABLE"}
if value.get("status") != "READY":
return {"status": "UNAVAILABLE"}
return _sanitize(value)
def create_release_intent(
self, payload: Mapping[str, Any]
) -> dict[str, Any]:
allowed = {"event_id", "source_commit"}
allowed = {"event_id", "source_commit", "version"}
unknown = set(payload).difference(allowed)
if unknown:
raise CapabilityAuthorizationError(
@@ -3250,12 +3635,17 @@ class SilentReleaseActionService:
source_commit = _validate_oid(
"source_commit", str(payload.get("source_commit") or "")
)
version = str(payload.get("version") or "")
if version:
_validate_identifier("version", version)
intent_id = "intent-" + hashlib.sha256(
f"{event_id}\0{source_commit}".encode("utf-8")
).hexdigest()[:24]
path = _safe_child(self.intents_root, intent_id)
was_existing = path.exists()
record = {
"created_at": utc_now(),
"attempt_count": 0,
"event_id": event_id,
"intent_id": intent_id,
"source_commit": source_commit,
@@ -3265,42 +3655,153 @@ class SilentReleaseActionService:
else "BUILDER_UNAVAILABLE"
),
}
if path.exists():
if version:
record["version"] = version
if was_existing:
existing = json.loads(path.read_text(encoding="utf-8"))
if (
existing.get("event_id") != event_id
or existing.get("source_commit") != source_commit
or str(existing.get("version") or "") != version
):
raise SagaConflict(
"release intent id is bound to different evidence"
)
return {
**_sanitize(existing),
"idempotent": True,
"execution_enabled": self.execution_enabled,
"shadow_mode": not self.execution_enabled,
"side_effects_executed": False,
if existing.get("status") == "BUILDING":
try:
started_at = _parse_utc(
str(existing.get("attempt_started_at") or "")
)
elapsed = (
datetime.now(timezone.utc) - started_at
).total_seconds()
except (TypeError, ValueError):
elapsed = self.builder_attempt_timeout_seconds + 1
if elapsed <= self.builder_attempt_timeout_seconds:
return self._intent_response(existing, idempotent=True)
existing.update(
{
"failure_code": "TRUSTED_BUILDER_CRASH_RECOVERED",
"last_recovery_code": "TRUSTED_BUILDER_CRASH_RECOVERED",
"recovery_count": int(existing.get("recovery_count") or 0) + 1,
"retryable": int(existing.get("attempt_count") or 0)
< self.max_builder_attempts,
"status": "BUILDER_RETRYABLE_FAILURE",
"updated_at": utc_now(),
}
)
_durable_json(path, existing)
if (
existing.get("status") != "BUILDER_RETRYABLE_FAILURE"
or int(existing.get("attempt_count") or 0)
>= self.max_builder_attempts
):
return self._intent_response(existing, idempotent=True)
record = dict(existing)
if self.candidate_builder is None:
record.update(
{
"retryable": False,
"status": "BUILDER_UNAVAILABLE",
}
)
_durable_json(path, record)
return self._intent_response(record, idempotent=was_existing)
record.update(
{
"attempt_count": int(record.get("attempt_count") or 0) + 1,
"attempt_started_at": utc_now(),
"retryable": False,
"status": "BUILDING",
"updated_at": utc_now(),
}
)
_durable_json(path, record)
result = dict(record)
if self.candidate_builder is not None:
try:
candidate = self.create_release_candidate(
{
"event_id": event_id,
"intent_id": intent_id,
"source_commit": source_commit,
**({"version": version} if version else {}),
}
)
result.update(
except TrustedCandidateBuilderUnavailable:
record.update(
{
"candidate_id": candidate["candidate_id"],
"status": candidate["saga_state"],
"failure_code": "TRUSTED_BUILDER_UNAVAILABLE",
"retryable": record["attempt_count"]
< self.max_builder_attempts,
"status": (
"BUILDER_RETRYABLE_FAILURE"
if record["attempt_count"] < self.max_builder_attempts
else "BUILDER_TERMINAL_FAILURE"
),
}
)
_durable_json(path, result)
_durable_json(path, record)
return self._intent_response(record, idempotent=False)
except CapabilityAuthorizationError:
record.update(
{
"failure_code": "TRUSTED_BUILDER_REJECTED",
"retryable": False,
"status": "BUILDER_TERMINAL_FAILURE",
}
)
_durable_json(path, record)
return self._intent_response(record, idempotent=False)
except (ConnectionError, OSError, TimeoutError):
record.update(
{
"failure_code": "TRUSTED_BUILDER_UNAVAILABLE",
"retryable": record["attempt_count"]
< self.max_builder_attempts,
"status": (
"BUILDER_RETRYABLE_FAILURE"
if record["attempt_count"] < self.max_builder_attempts
else "BUILDER_TERMINAL_FAILURE"
),
}
)
_durable_json(path, record)
return self._intent_response(record, idempotent=False)
except Exception:
# The durable intent is recoverable without serializing an
# arbitrary exception (which may contain a credential or host
# path). Repeated requests consume the small fixed retry budget.
record.update(
{
"failure_code": "TRUSTED_BUILDER_FAILURE",
"retryable": record["attempt_count"]
< self.max_builder_attempts,
"status": (
"BUILDER_RETRYABLE_FAILURE"
if record["attempt_count"] < self.max_builder_attempts
else "BUILDER_TERMINAL_FAILURE"
),
}
)
_durable_json(path, record)
return self._intent_response(record, idempotent=False)
result = dict(record)
result.update(
{
"candidate_id": candidate["candidate_id"],
"retryable": False,
"status": candidate["saga_state"],
}
)
_durable_json(path, result)
return self._intent_response(result, idempotent=False)
def _intent_response(
self, record: Mapping[str, Any], *, idempotent: bool
) -> dict[str, Any]:
return {
**_sanitize(result),
"idempotent": False,
**_sanitize(dict(record)),
"idempotent": idempotent,
"execution_enabled": self.execution_enabled,
"shadow_mode": not self.execution_enabled,
"side_effects_executed": False,
@@ -3321,6 +3822,7 @@ class SilentReleaseActionService:
self, payload: Mapping[str, Any]
) -> dict[str, Any]:
if self.candidate_builder is not None:
values = dict(payload)
forbidden_evidence = {
"artifact_sha256",
"canary",
@@ -3333,13 +3835,69 @@ class SilentReleaseActionService:
"tests",
"tree_oid",
}
if forbidden_evidence.intersection(payload):
if forbidden_evidence.intersection(values):
raise CapabilityAuthorizationError(
"candidate evidence must come from the trusted builder"
)
worker_fields = {"candidate_id", "source_commit", "version"}
intent_fields = {"event_id", "intent_id", "source_commit", "version"}
supplied = set(values)
is_worker_request = worker_fields.issubset(supplied)
if is_worker_request:
if supplied != worker_fields:
raise ValueError("release worker request contains unknown fields")
_validate_identifier(
"candidate_id", str(values.get("candidate_id") or "")
)
_validate_identifier(
"version", str(values.get("version") or "")
)
else:
if not supplied.issubset(intent_fields):
raise ValueError("trusted builder intent contains unknown fields")
# Preserve the established internal intent contract
# (source_commit + version) while allowing newer callers to
# add an event/intent identifier. Neither form can supply
# acceptance evidence; the host builder owns all collectors.
if values.get("event_id"):
_validate_identifier(
"event_id", str(values["event_id"])
)
if values.get("intent_id"):
_validate_identifier(
"intent_id", str(values["intent_id"])
)
if values.get("version"):
_validate_identifier(
"version", str(values["version"])
)
_validate_oid(
"source_commit", str(values.get("source_commit") or "")
)
readiness = self.get_builder_readiness()
if readiness.get("status") != "READY":
raise TrustedCandidateBuilderUnavailable(
"trusted builder dependency is unavailable"
)
bundle = self.candidate_builder.build(dict(payload)).validate(
self.builder_attestation_verifier
)
requested_source = str(payload.get("source_commit") or "")
if (
requested_source
and bundle.candidate.source_commit != requested_source
):
raise CapabilityAuthorizationError(
"trusted builder source commit does not match intent"
)
if (
is_worker_request
and bundle.candidate.candidate_id
!= str(values["candidate_id"])
):
raise CapabilityAuthorizationError(
"trusted builder candidate id does not match request"
)
if self.trusted_policy is None:
raise CapabilityAuthorizationError(
"trusted release policy is not configured"
@@ -3457,6 +4015,19 @@ class SilentReleaseActionService:
}
def get_release_saga(self, candidate_id: str) -> dict[str, Any]:
# A read of a never-created candidate is a normal empty-state. Do
# not turn a missing journal into a durable manual-review snapshot.
saga_path = self.sagas._path(candidate_id)
journal_path = self.sagas._journal(candidate_id)
if not saga_path.exists() and not journal_path.exists():
return {
"candidate_id": candidate_id,
"status": "NOT_FOUND",
"state": "NOT_FOUND",
"execution_enabled": self.execution_enabled,
"shadow_mode": not self.execution_enabled,
"side_effects_executed": False,
}
return {
**self.sagas.load(candidate_id).to_dict(),
"execution_enabled": self.execution_enabled,
@@ -0,0 +1,117 @@
#!/usr/bin/env python3
"""Plan-bound production adapter for releases newer than the legacy baseline.
All identity comes from the already verified executor request. The adapter has
no Git/ref/tag authority and refuses a missing candidate/release-plan binding.
"""
from __future__ import annotations
import hashlib, json, os, shutil, subprocess, sys, tempfile, urllib.error, urllib.request
from pathlib import Path
from typing import Any, Mapping
class AdapterBlocked(RuntimeError): pass
ALLOWED_SERVICE_PORTS = {"aios-v2r2c2-production.service": 18928}
def _safe(value: str) -> str:
if not value or any(ch not in "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_.-" for ch in value):
raise AdapterBlocked("invalid_release_identity")
return value
def _run(*args: str, check: bool = True) -> subprocess.CompletedProcess[str]:
result = subprocess.run(args, check=False, capture_output=True, text=True, timeout=60, env={"PATH": os.environ.get("PATH", "")})
if check and result.returncode != 0: raise AdapterBlocked(result.stderr.strip() or "system_command_failed")
return result
def _sha(path: Path) -> str:
if path.is_symlink() or not path.is_file(): raise AdapterBlocked("artifact_must_be_regular_file")
digest=hashlib.sha256()
with path.open("rb") as stream:
for block in iter(lambda: stream.read(1024*1024), b""): digest.update(block)
return digest.hexdigest()
def _binding(value: Mapping[str, Any]) -> dict[str, Any]:
required=("version","target_oid","artifact_sha256","artifact_path","artifact_size","production_service","production_port","candidate_id","release_plan_sha256")
if any(key not in value for key in required): raise AdapterBlocked("trusted_binding_incomplete")
if str(value["version"]).startswith("V2R1") or not value["candidate_id"] or not value["release_plan_sha256"]: raise AdapterBlocked("legacy_or_unbound_release_rejected")
service = str(value["production_service"])
port = int(value["production_port"])
if ALLOWED_SERVICE_PORTS.get(service) != port:
raise AdapterBlocked("production_adapter_policy_rejected")
return {key:value[key] for key in required}
def _paths(binding: Mapping[str, Any]) -> dict[str, Path]:
version=_safe(str(binding["version"])); service=_safe(str(binding["production_service"]))
root=Path(os.environ.get("AIOS_RELEASE_ROOT", "/opt/aios/releases"))
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]:
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}
def _extract(artifact: Path, destination: Path) -> None:
import tarfile
with tarfile.open(artifact, "r:*") as archive:
for member in archive.getmembers():
name=Path(member.name)
if name.is_absolute() or ".." in name.parts or member.issym() or member.islnk() or not member.isfile() or not member.name.startswith("payload/"):
raise AdapterBlocked("unsafe_artifact_member")
rel=name.relative_to("payload"); target=destination/rel; target.parent.mkdir(parents=True,exist_ok=True)
source=archive.extractfile(member)
if source is None: raise AdapterBlocked("artifact_member_unreadable")
target.write_bytes(source.read()); os.chmod(target,0o755 if rel.parts and rel.parts[0]=="scripts" else 0o644)
def probe(value: Mapping[str, Any]) -> dict[str, Any]:
binding=_binding(value); p=_paths(binding); active=_run("systemctl","is-active","--quiet",str(binding["production_service"]),check=False).returncode==0
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"]))
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"
if p["release_dir"].exists():
if not marker_path.is_file() or json.loads(marker_path.read_text())!=marker: raise AdapterBlocked("existing_release_binding_conflict")
else:
temp=Path(tempfile.mkdtemp(prefix=p["release_dir"].name+".",dir=str(p["release_dir"].parent)))
try:
_extract(artifact,temp); (temp/"DEPLOYMENT-BINDING.json").write_text(json.dumps(marker,sort_keys=True,separators=(",",":"))+"\n"); os.rename(temp,p["release_dir"])
except Exception:
shutil.rmtree(temp,ignore_errors=True); raise
version_file=p["release_dir"] / "VERSION"
if not version_file.is_file() or version_file.read_text().strip()!=binding["version"]: raise AdapterBlocked("release_version_mismatch")
unit_source=p["release_dir"] / "deploy" / "aios-v2" / "systemd" / str(binding["production_service"])
if not unit_source.is_file(): raise AdapterBlocked("release_unit_missing")
p["unit_file"].parent.mkdir(parents=True,exist_ok=True); shutil.copyfile(unit_source,p["unit_file"])
p["current_link"].parent.mkdir(parents=True,exist_ok=True)
if p["current_link"].exists() and not p["current_link"].is_symlink(): raise AdapterBlocked("current_release_path_conflict")
if not p["current_link"].is_symlink(): p["current_link"].symlink_to(p["release_dir"])
p["state_file"].parent.mkdir(parents=True,exist_ok=True); p["state_file"].write_text(json.dumps({"version":binding["version"],"commit_oid":binding["target_oid"],"artifact_sha256":binding["artifact_sha256"],"candidate_id":binding["candidate_id"],"release_plan_sha256":binding["release_plan_sha256"],"deployment_state":"ACTIVE","production_deployed":True},sort_keys=True)+"\n")
env=(f"RELEASE_VERSION={binding['version']}\n" f"TARGET_OID={binding['target_oid']}\n"
f"ARTIFACT_SHA256={binding['artifact_sha256']}\n" f"CANDIDATE_ID={binding['candidate_id']}\n"
f"RELEASE_PLAN_SHA256={binding['release_plan_sha256']}\n" f"DEPLOYMENT_STATE=ACTIVE\n"
f"DEPLOYMENT_STATE_FILE={p['state_file']}\n" f"AIOS_V2R2C2_PORT={binding['production_port']}\n"
"AIOS_GITOPS_WRITES_ENABLED=false\n")
p["env_file"].parent.mkdir(parents=True,exist_ok=True); p["env_file"].write_text(env)
_run("systemctl","daemon-reload"); _run("systemctl","enable",str(binding["production_service"])); _run("systemctl","restart",str(binding["production_service"]))
result=_health(str(p["health_url"]),binding)
if result["status"]!="ACTIVE_VERIFIED": raise AdapterBlocked("production_health_binding_mismatch")
return result
def rollback(value: Mapping[str, Any]) -> dict[str, Any]:
binding=_binding(value); _run("systemctl","stop",str(binding["production_service"]),check=False); _run("systemctl","disable",str(binding["production_service"]),check=False)
p=_paths(binding); p["state_file"].parent.mkdir(parents=True,exist_ok=True); p["state_file"].write_text(json.dumps({"version":binding["version"],"commit_oid":binding["target_oid"],"artifact_sha256":binding["artifact_sha256"],"deployment_state":"ROLLED_BACK","production_deployed":False},sort_keys=True)+"\n")
return {"status":"ROLLED_BACK","version":binding["version"],"target_oid":binding["target_oid"],"artifact_sha256":binding["artifact_sha256"],"production_deployed":False,"deployment_state":"ROLLED_BACK"}
def main() -> int:
value=json.loads(sys.stdin.read()); operation=value.get("operation")
result=probe(value) if operation=="probe" else apply(value) if operation=="apply" else rollback(value) if operation=="rollback" else (_ for _ in ()).throw(AdapterBlocked("unsupported_operation"))
print(json.dumps(result,sort_keys=True)); return 0
if __name__=="__main__":
try: raise SystemExit(main())
except Exception as exc: print(json.dumps({"status":"FAIL_CLOSED","error":type(exc).__name__,"reason":str(exc)},sort_keys=True)); raise SystemExit(2)
@@ -0,0 +1,101 @@
#!/usr/bin/env python3
"""Canonical, fail-closed projection for trusted release execution plans.
A release plan is the only authority for candidate, source, artifact, policy,
rollback, and release identity. Callers may not provide executable paths or
replace individual fields after the plan has been accepted.
"""
from __future__ import annotations
import hashlib, json, re, stat
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Mapping
OID_RE = re.compile(r"^[0-9a-f]{40}$")
SHA_RE = re.compile(r"^[0-9a-f]{64}$")
PLAN_ID_RE = re.compile(r"^sha256:[0-9a-f]{64}$")
IDENTIFIER_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.:-]{2,255}$")
TOP_LEVEL_KEYS = frozenset({"schema","version","candidate_id","source_commit","tree_oid","artifact_sha256","manifest_sha256","package_closure_sha256","deployment_plan_sha256","policy_sha256","release_plan_id","approval_plan_id","evidence_closure_oid","campaign_authorization_sha256","campaign_generation","artifact","rollback","baseline","adapter_policy","release_plan_sha256"})
FORBIDDEN_KEYS = frozenset({"command","commands","executable","exec","shell","argv","adapter_command","tag_command","deploy_command","registry_command","baseline_command","sudo","force_push","direct_main_push"})
class PlanError(ValueError):
"""A release plan is malformed, inconsistent, or unsafe."""
def canonical_json(value: object) -> bytes:
return (json.dumps(value, sort_keys=True, separators=(",", ":")) + "\n").encode("utf-8")
def _sha(value: Any, label: str) -> str:
if not isinstance(value, str) or not SHA_RE.fullmatch(value): raise PlanError(f"{label}_invalid")
return value
def _oid(value: Any, label: str) -> str:
if not isinstance(value, str) or not OID_RE.fullmatch(value): raise PlanError(f"{label}_invalid")
return value
def _walk_forbidden(value: Any) -> None:
if isinstance(value, Mapping):
for key, item in value.items():
if str(key).lower() in FORBIDDEN_KEYS: raise PlanError(f"forbidden_plan_key:{key}")
_walk_forbidden(item)
elif isinstance(value, list):
for item in value: _walk_forbidden(item)
def _exact_mapping(value: Any, required: set[str], label: str) -> Mapping[str, Any]:
if not isinstance(value, Mapping) or set(value) != required: raise PlanError(f"{label}_fields_invalid")
return value
def _secure_json(path: Path) -> dict[str, Any]:
try: st = path.lstat()
except OSError as exc: raise PlanError(f"plan_unreadable:{path}") from exc
if path.is_symlink() or not stat.S_ISREG(st.st_mode): raise PlanError("plan_must_be_regular_file")
if st.st_uid != 0 or st.st_mode & 0o022: raise PlanError("plan_ownership_or_mode_invalid")
try: value = json.loads(path.read_text(encoding="utf-8"))
except (OSError, ValueError) as exc: raise PlanError("plan_json_invalid") from exc
if not isinstance(value, dict): raise PlanError("plan_object_required")
return value
@dataclass(frozen=True)
class TrustedReleasePlan:
value: Mapping[str, Any]
release_plan_sha256: str
@classmethod
def from_mapping(cls, raw: Mapping[str, Any]) -> "TrustedReleasePlan":
if not isinstance(raw, Mapping) or set(raw) - TOP_LEVEL_KEYS: raise PlanError("release_plan_fields_invalid")
required = TOP_LEVEL_KEYS - {"release_plan_sha256"}
if set(raw) not in (required, TOP_LEVEL_KEYS): raise PlanError("release_plan_fields_incomplete")
_walk_forbidden(raw)
if raw.get("schema") != "aios.trusted-release-execution-plan.v1": raise PlanError("release_plan_schema_invalid")
version = raw.get("version")
if not isinstance(version, str) or not re.fullmatch(r"V[0-9]+R[0-9]+C[0-9]+", version): raise PlanError("version_invalid")
if not isinstance(raw.get("candidate_id"), str) or not IDENTIFIER_RE.fullmatch(raw["candidate_id"]): raise PlanError("candidate_id_invalid")
for key in ("source_commit","tree_oid","evidence_closure_oid"): _oid(raw.get(key), key)
for key in ("artifact_sha256","manifest_sha256","package_closure_sha256","deployment_plan_sha256","policy_sha256","campaign_authorization_sha256"): _sha(raw.get(key), key)
for key in ("release_plan_id","approval_plan_id"):
if not isinstance(raw.get(key), str) or not PLAN_ID_RE.fullmatch(raw[key]): raise PlanError(f"{key}_invalid")
if not isinstance(raw.get("campaign_generation"), int) or raw["campaign_generation"] < 1: raise PlanError("campaign_generation_invalid")
artifact = _exact_mapping(raw.get("artifact"), {"path","size"}, "artifact")
if not isinstance(artifact["path"], str) or not artifact["path"].startswith("/") or "\\" in artifact["path"]: raise PlanError("artifact_path_invalid")
if not isinstance(artifact["size"], int) or artifact["size"] <= 0: raise PlanError("artifact_size_invalid")
rollback = _exact_mapping(raw.get("rollback"), {"version","target_oid","artifact_sha256"}, "rollback")
if not isinstance(rollback["version"], str) or not rollback["version"] or rollback["version"] == version: raise PlanError("rollback_version_invalid")
_oid(rollback["target_oid"], "rollback_target_oid"); _sha(rollback["artifact_sha256"], "rollback_artifact_sha256")
baseline = _exact_mapping(raw.get("baseline"), {"version","target_oid"}, "baseline")
if baseline["version"] != rollback["version"]: raise PlanError("baseline_rollback_version_mismatch")
_oid(baseline["target_oid"], "baseline_target_oid")
adapter = _exact_mapping(raw.get("adapter_policy"), {"policy_id","service","port"}, "adapter_policy")
if adapter["policy_id"] != "aios.production-adapter.allowlisted.v1": raise PlanError("adapter_policy_invalid")
if not isinstance(adapter["service"], str) or not IDENTIFIER_RE.fullmatch(adapter["service"]): raise PlanError("adapter_service_invalid")
if not isinstance(adapter["port"], int) or not 1024 <= adapter["port"] <= 65535: raise PlanError("adapter_port_invalid")
payload = {key: raw[key] for key in TOP_LEVEL_KEYS if key != "release_plan_sha256"}
digest = hashlib.sha256(canonical_json(payload)).hexdigest()
if raw.get("release_plan_sha256") is not None and raw["release_plan_sha256"] != digest: raise PlanError("release_plan_sha256_mismatch")
return cls(value=dict(payload), release_plan_sha256=digest)
@classmethod
def load(cls, path: Path) -> "TrustedReleasePlan": return cls.from_mapping(_secure_json(path))
@property
def candidate_binding_sha256(self) -> str:
value = {key: self.value[key] for key in ("candidate_id","source_commit","tree_oid","artifact_sha256","manifest_sha256")}
return hashlib.sha256(canonical_json(value)).hexdigest()
def as_dict(self) -> dict[str, Any]: return {**dict(self.value),"release_plan_sha256":self.release_plan_sha256,"candidate_binding_sha256":self.candidate_binding_sha256}
def assert_binding(self, binding: Mapping[str, Any]) -> None:
expected={"version":self.value["version"],"target_oid":self.value["source_commit"],"evidence_closure_oid":self.value["evidence_closure_oid"],"artifact_sha256":self.value["artifact_sha256"],"artifact_size":self.value["artifact"]["size"],"approval_plan_id":self.value["approval_plan_id"]}
for key,value in expected.items():
if binding.get(key) != value: raise PlanError(f"binding_plan_mismatch:{key}")
if binding.get("candidate_id") is not None and binding.get("candidate_id") != self.value["candidate_id"]:
raise PlanError("binding_plan_mismatch:candidate_id")
def assert_artifact(self, artifact: Mapping[str, Any]) -> None:
expected=self.value["artifact"]
if artifact.get("sha256") != self.value["artifact_sha256"] or artifact.get("size") != expected["size"]: raise PlanError("artifact_plan_mismatch")
if artifact.get("manifest_sha256") != self.value["manifest_sha256"]: raise PlanError("manifest_plan_mismatch")
if artifact.get("package_closure_sha256") != self.value["package_closure_sha256"]: raise PlanError("package_closure_plan_mismatch")
@@ -0,0 +1,135 @@
"""V2R2C1 adapter using the existing V2R1C7 deployment primitives.
The service identity and loopback port remain stable for a safe cutover. The
new release is installed under its own immutable directory and keeps the
previous V2R1C7 release available for an explicit, owner-approved rollback.
"""
from __future__ import annotations
import json
import os
import shutil
import sys
from argparse import Namespace
from pathlib import Path
from typing import Any, Mapping
import v2r1c7_production_adapter as base
VERSION = "V2R2C1"
SERVICE = "aios-v2r1c7-production.service"
PORT = 18927
RELEASE_ROOT = Path("/opt/aios/releases")
CURRENT_LINK = Path("/opt/aios/v2r2c1")
ENV_FILE = Path("/etc/aios/aios-v2r2c1.env")
STATE_FILE = Path("/var/lib/aios/v2r2c1/deployment-state.json")
UNIT_FILE = Path("/etc/systemd/system/aios-v2r1c7-production.service")
ROLLBACK_ROOT = Path("/var/lib/aios/v2r2c1/rollback")
REQUIRED_MEMBERS = {
"payload/VERSION",
"payload/active-runtime/app/deployment_state.py",
"payload/active-runtime/app/v2r1c6_privacy_tools.py",
"payload/active-runtime/app/v2r2c1_health_service.py",
"payload/deploy/aios-v2/systemd/aios-v2r1c7-production.service",
"payload/deploy/aios-v2/v2r2c1-runtime-config.json",
"payload/deploy/aios-v2/v2r2c1-rollback.json",
"payload/scripts/probe_v2r2c1_health.py",
"payload/scripts/rollback_v2r2c1_release.sh",
"payload/BUILD-METADATA.json",
"payload/ARTIFACT-MANIFEST.json",
"payload/ROLLBACK-METADATA.json",
}
def _configure_base() -> None:
base.VERSION = VERSION
base.SERVICE = SERVICE
base.PORT = PORT
base.REQUIRED_MEMBERS = REQUIRED_MEMBERS
def _args() -> Namespace:
return Namespace(
release_root=RELEASE_ROOT,
current_link=CURRENT_LINK,
env_file=ENV_FILE,
state_file=STATE_FILE,
unit_file=UNIT_FILE,
health_timeout=45,
)
def load_request() -> dict[str, Any]:
_configure_base()
value = base.load_request()
if value.get("version") != VERSION or value.get("production_service") != SERVICE or int(value.get("production_port", -1)) != PORT:
raise ValueError("V2R2C1 production binding mismatch")
return value
def _backup(path: Path, name: str) -> None:
if path.is_file() and not path.is_symlink():
ROLLBACK_ROOT.mkdir(parents=True, exist_ok=True)
destination = ROLLBACK_ROOT / name
if not destination.exists():
shutil.copy2(path, destination)
def execute_request(value: Mapping[str, Any]) -> dict[str, Any]:
_configure_base()
if value.get("schema") != "aios.production-executor-request.v1":
raise ValueError("request_schema_invalid")
if value.get("operation") == "apply":
_backup(UNIT_FILE, "aios-v2r1c7-production.service.previous")
_backup(Path("/etc/aios/aios-v2r1c7.env"), "aios-v2r1c7.env.previous")
_backup(Path("/var/lib/aios/v2r1c7/deployment-state.json"), "v2r1c7-deployment-state.previous")
return base.install_release(_args(), value)
if value.get("operation") == "probe":
return base.probe(_args(), value)
if value.get("operation") == "rollback":
return rollback(value)
raise ValueError("unsupported_operation")
def rollback(value: Mapping[str, Any]) -> dict[str, Any]:
"""Explicit rollback only; this adapter never invokes it automatically."""
import subprocess
subprocess.run(["systemctl", "stop", SERVICE], check=False, capture_output=True, text=True, timeout=45)
previous_unit = ROLLBACK_ROOT / "aios-v2r1c7-production.service.previous"
previous_env = ROLLBACK_ROOT / "aios-v2r1c7.env.previous"
previous_state = ROLLBACK_ROOT / "v2r1c7-deployment-state.previous"
if not previous_unit.is_file() or not previous_env.is_file():
raise RuntimeError("ROLLBACK_RECEIPT_MISSING")
UNIT_FILE.parent.mkdir(parents=True, exist_ok=True)
ENV_FILE.parent.mkdir(parents=True, exist_ok=True)
shutil.copy2(previous_unit, UNIT_FILE)
shutil.copy2(previous_env, Path("/etc/aios/aios-v2r1c7.env"))
if previous_state.is_file():
Path("/var/lib/aios/v2r1c7").mkdir(parents=True, exist_ok=True)
shutil.copy2(previous_state, Path("/var/lib/aios/v2r1c7/deployment-state.json"))
subprocess.run(["systemctl", "daemon-reload"], check=True, capture_output=True, text=True, timeout=45)
subprocess.run(["systemctl", "restart", SERVICE], check=True, capture_output=True, text=True, timeout=45)
result = base.health("http://127.0.0.1:18927/health", {
"version": "V2R1C7",
"target_oid": str(json.loads(Path("/var/lib/aios/v2r1c7/deployment-state.json").read_text(encoding="utf-8")).get("commit_oid")),
"artifact_sha256": str(json.loads(Path("/var/lib/aios/v2r1c7/deployment-state.json").read_text(encoding="utf-8")).get("artifact_sha256")),
})
return {"status": "ROLLED_BACK", "rollback_health": result, "automatic_rollback": False}
def main() -> int:
try:
value = load_request()
result = execute_request(value)
print(json.dumps(result, sort_keys=True))
return 0
except Exception as exc:
print(json.dumps({"status": "FAIL_CLOSED", "error": type(exc).__name__, "reason": str(exc)}, sort_keys=True))
return 2
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,5 @@
"""V2R2C2 production health entry point; all identity is plan supplied."""
from v2r1c6_privacy_tools import main
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,57 @@
{
"allowed_peer_cgroups": [
"/system.slice/aios-gitops-model-api-v2r2c1-staging.service"
],
"allowed_peer_executable_prefix": "/usr/bin/python3",
"allowed_peer_runtime_prefix": "/opt/aios/gitops-model-api/",
"allowed_peer_runtime_suffix": "/runtime/app/aios_model_api_staging.py",
"allowed_peer_uid": 1000,
"evidence_layout": {
"artifact": "{version}/artifact.tar.gz",
"canary": "{version}/canary.json",
"gitea": "{version}/gitea.json",
"manifest": "{version}/manifest.json",
"package_closure": "{version}/checksums.sha256",
"policy": "{version}/trusted-release-policy.json",
"rollback": "{version}/rollback.json",
"secret_scan": "{version}/secret-scan.json",
"tests": "{version}/tests.json"
},
"governed_recipes": {},
"maximum_request_age_seconds": 60,
"operation": "BuildTrustedCandidateV1",
"readiness_operation": "ProbeTrustedCandidateBuilderV1",
"recipes": {
"V2R2C2": {
"base_oid": "d2b5a8339a34a8f958a177f8a3b9e8296a57a7a6",
"candidate_id": "rc-v2r2c2-d2b5a8339a34",
"mode": "fixed",
"purpose": "AIOS_TRUSTED_RELEASE_BUILDER",
"source_commit": "d2b5a8339a34a8f958a177f8a3b9e8296a57a7a6",
"task_id": "v12-durable-trusted-release-controller-activation-20260829",
"version": "V2R2C2"
},
"V2R2C3": {
"derives_from": "V2R2C2",
"mode": "governed-derived-only",
"purpose": "AIOS_TRUSTED_RELEASE_BUILDER",
"version": "V2R2C3"
}
},
"repository": "aios/aios",
"request_schema": "aios.trusted-candidate-builder.request.v1",
"request_timeout_seconds": 30,
"required_checks": [
"authoritative_git",
"artifact",
"canary",
"gitea",
"manifest",
"package_closure",
"rollback",
"secret_scan",
"tests"
],
"response_schema": "aios.trusted-candidate-builder.response.v1",
"schema": "aios.trusted-candidate-builder-policy.v1"
}
+20
View File
@@ -0,0 +1,20 @@
{
"authoritative_git_dirs": [
"/data/aios-gitops/repo.git"
],
"cutover_links": [
"/opt/aios/v2r2c2"
],
"dropin_paths": [
"/var/lib/aios-gitops/release-only/v2r2c2-d2b5a8339a34/payload/deploy/aios-v2/systemd/aios-v2r2c2-production.service"
],
"release_roots": [
"/var/lib/aios-gitops/release-only"
],
"repository_identities": [
"aios/aios"
],
"services": [
"aios-v2r2c2-production.service"
]
}
@@ -0,0 +1,28 @@
[Unit]
Description=AIOS V2R2C2 governed trusted release health service
After=network.target
[Service]
Type=simple
User=aios-v2r1c7
Group=aios-v2r1c7
WorkingDirectory=/opt/aios/v2r2c2
EnvironmentFile=/etc/aios/v2r2c2.env
ExecStart=/usr/bin/python3 /opt/aios/v2r2c2/active-runtime/app/v2r2c2_health_service.py --serve --host 127.0.0.1 --port ${AIOS_V2R2C2_PORT}
Restart=on-failure
RestartSec=5
NoNewPrivileges=true
PrivateTmp=true
ProtectHome=true
ProtectSystem=strict
ProtectKernelLogs=true
ProtectKernelModules=true
ProtectKernelTunables=true
ProtectControlGroups=true
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
IPAddressDeny=any
IPAddressAllow=localhost
UMask=0077
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,37 @@
[Unit]
Description=AIOS owner-controlled Task initial execution projection API
After=aios-agent-task-api.service aios-chatgpt-bridge-v2-staging.service
Wants=aios-agent-task-api.service aios-chatgpt-bridge-v2-staging.service
[Service]
Type=simple
User=aios-agent-task
Group=aios-agent-task
LoadCredential=agent_task_control_token:/etc/aios-gitops/agent-task-control.token
Environment=AIOS_AGENT_TASK_DB=/var/lib/aios-agent-task/state.sqlite3
Environment=AIOS_AGENT_TASK_CONTROL_TOKEN_FILE=%d/agent_task_control_token
Environment=AIOS_GOVERNED_WRITE_API_URL=http://127.0.0.1:18892
Environment=AIOS_AGENT_TASK_INITIAL_EXECUTION_PROJECTION_AUTHORIZATION_ID=owner-initial-execution-projection-recovery-20260820
Environment=AIOS_TASK_INITIAL_EXECUTION_PROJECTION_HOST=127.0.0.1
Environment=AIOS_TASK_INITIAL_EXECUTION_PROJECTION_PORT=18943
ExecStart=/usr/bin/python3 -B /opt/aios/current/active-runtime/app/aios_agent_task_initial_execution_projection.py
Restart=on-failure
RestartSec=2
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
LockPersonality=true
RestrictSUIDSGID=true
RestrictRealtime=true
RestrictNamespaces=true
MemoryDenyWriteExecute=true
RestrictAddressFamilies=AF_UNIX AF_INET
ReadWritePaths=/var/lib/aios-agent-task
UMask=0077
[Install]
WantedBy=multi-user.target
@@ -15,17 +15,9 @@ ExecStart=/usr/bin/python3 /usr/libexec/aios-approved-release-executor/current/a
--authorization ${AIOS_RELEASE_AUTHORIZATION} \
--repository ${AIOS_GITOPS_AUTHORITATIVE_GIT_DIR} \
--state-root ${AIOS_RELEASE_EXECUTION_STATE_ROOT} \
--artifact ${AIOS_RELEASE_ARTIFACT} \
--artifact-size ${AIOS_RELEASE_ARTIFACT_SIZE} \
--version ${AIOS_RELEASE_VERSION} \
--target-oid ${AIOS_RELEASE_TARGET_OID} \
--evidence-closure-oid ${AIOS_RELEASE_EVIDENCE_CLOSURE_OID} \
--artifact-sha256 ${AIOS_RELEASE_ARTIFACT_SHA256} \
--approval-plan-id ${AIOS_RELEASE_APPROVAL_PLAN_ID} \
--production-service aios-v2r1c7-production.service \
--production-port 18927 \
--release-plan ${AIOS_RELEASE_PLAN} \
--tag-adapter /usr/bin/python3 /usr/libexec/aios-approved-release-executor/current/active-runtime/app/governed_tag_adapter.py \
--deploy-adapter /usr/bin/python3 /usr/libexec/aios-approved-release-executor/current/active-runtime/app/v2r1c7_production_adapter.py \
--deploy-adapter /usr/bin/python3 /usr/libexec/aios-approved-release-executor/current/active-runtime/app/trusted_production_adapter.py \
--registry-adapter /usr/bin/env AIOS_RELEASE_METADATA_MODE=registry /usr/bin/python3 /usr/libexec/aios-approved-release-executor/current/active-runtime/app/release_metadata_adapter.py \
--baseline-adapter /usr/bin/env AIOS_RELEASE_METADATA_MODE=baseline /usr/bin/python3 /usr/libexec/aios-approved-release-executor/current/active-runtime/app/release_metadata_adapter.py
NoNewPrivileges=true
@@ -0,0 +1,36 @@
[Unit]
Description=AIOS ChatGPT Bridge v2 staging control plane
After=network-online.target
[Service]
Type=simple
User=aiman
Group=gitops
PermissionsStartOnly=true
WorkingDirectory=@RUNTIME_ROOT@
Environment=AIOS_BRIDGE_SCHEMA_VERSION=aios.chatgpt-gitops-bridge.v2
Environment=AIOS_GITOPS_WRITES_ENABLED=false
Environment=AIOS_GITOPS_DURABLE_JOB_EXECUTION_ENABLED=true
Environment=AIOS_GITOPS_WRITES_ENABLED=false
Environment=AIOS_GITEA_API_BASE=https://g.cnnetmkt.top/git/api/v1
Environment=AIOS_GITEA_TOKEN_FILE=/run/credentials/aios-chatgpt-bridge-v2-staging.service/gitea_token
Environment=AIOS_GITEA_REMOTE=gitea-http
Environment=AIOS_GITOPS_BASELINE_AUTHORITY=/var/lib/aios-gitops/model-api-staging/baseline/current.json
Environment=AIOS_CONTROL_PLANE_TARGET_VERSION=V2R2C1
Environment=AIOS_CONTROL_PLANE_TARGET_OID=@SOURCE_OID@
Environment=AIOS_CONTROL_PLANE_ARTIFACT_SHA256=@ARTIFACT_SHA256@
Environment=PYTHONPATH=@RUNTIME_ROOT@:@RUNTIME_ROOT@/app:@RUNTIME_ROOT@/runtime-agent-os
Environment=AIOS_DEPLOYMENT_STATE=STAGING
LoadCredential=gitea_token:/run/credentials/aios-governance-executor.service/gitea-governance-executor-token
ExecStartPre=/usr/bin/install -d -o aiman -g gitops -m 0750 /var/lib/aios-gitops/bridge-v2-staging
ExecStartPre=/usr/bin/setfacl -m u:aiman:--x /usr/local/libexec
ExecStart=/usr/bin/python3 -B @RUNTIME_ROOT@/app/aios_chatgpt_bridge_v2_server.py
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/aios-gitops/bridge-v2-staging /data/aios-gitops/repo.git /data/aios-gitops/repo.git/worktrees
Restart=on-failure
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,14 @@
# Non-secret activation template for a verified immutable model-api package.
# Install only through the governed runtime activation procedure.
[Service]
Environment=AIOS_GITOPS_WORKSPACE=/opt/aios/gitops-model-api/<VERIFIED_PACKAGE_OID>/runtime
Environment=AIOS_TRUSTED_CANDIDATE_BUILDER_ENABLED=true
Environment=AIOS_TRUSTED_CANDIDATE_BUILDER_SOCKET=/run/aios-trusted-candidate-builder.sock
Environment=AIOS_TRUSTED_CANDIDATE_BUILDER_POLICY=/opt/aios/gitops-model-api/<VERIFIED_PACKAGE_OID>/runtime/config/gitops/trusted-candidate-builder-policy.json
Environment=AIOS_TRUSTED_CANDIDATE_BUILDER_PUBLIC_KEYRING=/etc/aios-gitops/trusted-builder/public-keyring.json
Environment=AIOS_TRUSTED_CANDIDATE_BUILDER_CREDENTIALS_DIRECTORY=%d
Environment=AIOS_SILENT_RELEASE_TRUSTED_POLICY=/opt/aios/gitops-model-api/<VERIFIED_PACKAGE_OID>/runtime/config/gitops/trusted-release-policy.json
LoadCredential=builder_hmac:/etc/aios-gitops/credential-references/trusted-candidate-builder-hmac
# The credential source above is a reference only; no secret value belongs here.
# Keep execution shadow-disabled and do not add Owner/deploy/tag/baseline switches.
Environment=AIOS_SILENT_RELEASE_EXECUTION_ENABLED=false
@@ -0,0 +1,51 @@
[Unit]
Description=AIOS GitOps Model API V2R2C1 immutable staging runtime
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=aiman
Group=gitops
WorkingDirectory=@RUNTIME_ROOT@
Environment=PYTHONUNBUFFERED=1
Environment=PYTHONDONTWRITEBYTECODE=1
Environment=AIOS_GITOPS_ROOT=/var/lib/aios-gitops
Environment=AIOS_GITOPS_WORKSPACE=@RUNTIME_ROOT@
Environment=AIOS_GITOPS_REPO=/data/aios-gitops/repo.git
Environment=AIOS_GITOPS_BARE_REPO=/data/aios-gitops/repo.git
Environment=AIOS_GITOPS_AUTHORITATIVE_GIT_DIR=/data/aios-gitops/repo.git
Environment=AIOS_GITOPS_REPOSITORY_IDENTITY=aios/aios
Environment=AIOS_GITOPS_BASELINE_AUTHORITY=/var/lib/aios-gitops-runtime-anchor/baseline/current.json
Environment=AIOS_GITOPS_WRITES_ENABLED=false
Environment=AIOS_GITOPS_DURABLE_JOB_EXECUTION_ENABLED=false
Environment=AIOS_GITOPS_JOB_CONTROLLER_ENABLED=false
Environment=AIOS_GITOPS_WORKSPACE_ROLE=runtime-anchor
Environment=AIOS_GITOPS_RUNTIME_OID=@SOURCE_OID@
Environment=AIOS_GITOPS_ARTIFACT_SHA256=@ARTIFACT_SHA256@
Environment=AIOS_GITOPS_DEPLOYMENT_STATE=STAGING
Environment=AIOS_GITOPS_MODEL_API_SERVICE=aios-gitops-model-api-v2r2c1-staging.service
Environment=AIOS_SILENT_RELEASE_EXECUTION_ENABLED=false
Environment=AIOS_SILENT_RELEASE_STATE_ROOT=/var/lib/aios-gitops/model-api-staging/silent-release
Environment=AIOS_BRIDGE_V2_URL=http://127.0.0.1:18892
Environment=AIOS_GITOPS_HOST=127.0.0.1
Environment=AIOS_GITOPS_PORT=18893
Environment=PYTHONPATH=@RUNTIME_ROOT@/app:@RUNTIME_ROOT@/runtime-agent-os
ExecStart=/usr/bin/python3 @RUNTIME_ROOT@/app/aios_model_api_staging.py
Restart=on-failure
RestartSec=2
NoNewPrivileges=true
PrivateTmp=true
ProtectHome=true
ProtectSystem=strict
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
LockPersonality=true
RestrictSUIDSGID=true
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
ReadOnlyPaths=@RUNTIME_ROOT@ /data/aios-gitops/repo.git /var/lib/aios-gitops-runtime-anchor/baseline
ReadWritePaths=/var/lib/aios-gitops/model-api-staging
[Install]
WantedBy=multi-user.target
+33
View File
@@ -0,0 +1,33 @@
[Unit]
Description=AIOS durable GitOps writer worker
Requires=aios-gitops-writer.socket
After=aios-gitops-writer.socket network-online.target
[Service]
Type=simple
User=root
Group=root
UMask=0077
Environment=AIOS_GITOPS_WRITES_ENABLED=false
Environment=AIOS_GITOPS_DURABLE_JOB_EXECUTION_ENABLED=true
Environment=AIOS_GITOPS_JOB_ROOT=/var/lib/aios-gitops/jobs
Environment=AIOS_GITEA_MACHINE_CREDENTIAL_REF=systemd-credential:gitea-machine-token
Environment=AIOS_GITOPS_AUTHORITY_GIT_DIR=/data/aios-gitops/repo.git
Environment=AIOS_GITOPS_WORKTREE_ROOT=/var/lib/aios-gitops/worktrees
Environment=AIOS_GITEA_API=https://g.cnnetmkt.top/git/api/v1
Environment=AIOS_GITEA_REMOTE=https://g.cnnetmkt.top/git/aios/aios.git
Environment=AIOS_GITEA_MACHINE_ACCOUNT=aios-autonomy-agent
Environment=AIOS_RELEASE_RECOVERY_RECEIPT=/var/lib/aios-gitops/release-campaigns/AIOS-AUTONOMOUS-RELEASE-CAMPAIGN-20260807/authorization-recovery.json
LoadCredentialEncrypted=gitea-machine-token:/etc/aios/credentials/gitea-machine.token.cred
Environment=PYTHONDONTWRITEBYTECODE=1
ExecStart=/usr/bin/python3 -B /opt/aios-gitops/active-runtime/app/aios_gitops_writer_worker.py --job-root ${AIOS_GITOPS_JOB_ROOT}
Restart=on-failure
RestartSec=2
NoNewPrivileges=no
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/aios-gitops/jobs /var/lib/aios-gitops/payloads /var/lib/aios-gitops/worktrees /data/aios-gitops/repo.git
[Install]
WantedBy=multi-user.target
+14
View File
@@ -0,0 +1,14 @@
[Unit]
Description=AIOS durable GitOps writer socket
[Socket]
ListenStream=/run/aios-gitops/writer.sock
Accept=no
SocketUser=root
SocketGroup=gitops
SocketMode=0660
DirectoryMode=0750
RemoveOnStop=yes
[Install]
WantedBy=sockets.target
@@ -0,0 +1,26 @@
[Unit]
Description=AIOS Semantic Governance Drift Monitor
After=aios-governance-executor.service
[Service]
Type=oneshot
User=root
Group=gitops
ExecStart=/usr/bin/python3 /usr/local/libexec/aios-governance-drift-monitor
NoNewPrivileges=true
PrivateTmp=true
PrivateDevices=true
ProtectSystem=strict
ProtectHome=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
LockPersonality=true
RestrictSUIDSGID=true
RestrictRealtime=true
MemoryDenyWriteExecute=true
RestrictAddressFamilies=AF_UNIX
IPAddressDeny=any
ReadOnlyPaths=/usr/local/libexec/aios-governance-drift-monitor
ReadOnlyPaths=/var/lib/aios-gitops/runtime/tasks/AIOS-PROTECTION-SNAPSHOT-DRIFT-FORENSICS-V1/protection-drift/semantic-analysis.json
ReadWritePaths=/var/lib/aios-gitops/governance-executor
@@ -0,0 +1,37 @@
[Unit]
Description=AIOS Dedicated Governance Executor
After=aios-gitea.service network-online.target
Wants=network-online.target
ConditionPathExists=/etc/credstore.encrypted/gitea-governance-executor-token
ConditionPathExists=/usr/local/libexec/aios-governance-executor
[Service]
Type=simple
User=root
Group=gitops
LoadCredentialEncrypted=gitea-governance-executor-token:/etc/credstore.encrypted/gitea-governance-executor-token
ExecStart=/usr/local/libexec/aios-governance-executor
Restart=on-failure
RestartSec=3
NoNewPrivileges=true
PrivateTmp=true
ProtectHome=true
ProtectSystem=strict
ProtectKernelLogs=true
ProtectKernelModules=true
ProtectKernelTunables=true
ProtectControlGroups=true
RestrictSUIDSGID=true
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
IPAddressDeny=any
IPAddressAllow=127.0.0.1
RuntimeDirectory=aios-governance-executor
RuntimeDirectoryMode=0750
StateDirectory=aios-gitops/governance-executor
StateDirectoryMode=0750
ReadOnlyPaths=/usr/local/libexec /etc/credstore.encrypted
ReadWritePaths=/var/lib/aios-gitops/governance-executor /run/aios-governance-executor
UMask=0007
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,33 @@
[Unit]
Description=AIOS Governance Pre-Approval Publication Plane
Requires=aios-governance-preapproval-publisher.socket
After=network-online.target aios-gitea.service
Wants=network-online.target
[Service]
Type=simple
User=root
Group=gitops
UMask=0077
Environment=AIOS_GITOPS_WRITES_ENABLED=false
Environment=AIOS_GOVERNANCE_PUBLISHER_OPERATION_ALLOWLIST=semantic-reconciliation-only
LoadCredentialEncrypted=gitea-machine-token:/etc/aios/credentials/gitea-machine.token.cred
ExecStart=/usr/local/libexec/aios-governance-preapproval-publisher
NoNewPrivileges=true
PrivateTmp=true
PrivateDevices=true
ProtectSystem=strict
ProtectHome=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
LockPersonality=true
RestrictSUIDSGID=true
RestrictRealtime=true
MemoryDenyWriteExecute=true
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
ReadOnlyPaths=/usr/local/libexec/aios-governance-preapproval-publisher
ReadOnlyPaths=/etc/aios/credentials
ReadOnlyPaths=/var/lib/aios-gitops/runtime/tasks/AIOS-PROTECTION-SNAPSHOT-DRIFT-FORENSICS-V1/publication-input
ReadWritePaths=/var/lib/aios-gitops/runtime/tasks/AIOS-PROTECTION-SNAPSHOT-DRIFT-FORENSICS-V1
ReadWritePaths=/run/aios-governance-preapproval-publisher
@@ -0,0 +1,13 @@
[Unit]
Description=AIOS Governance Pre-Approval Publication Plane Socket
[Socket]
ListenStream=/run/aios-governance-preapproval-publisher/publisher.sock
SocketUser=root
SocketGroup=gitops
SocketMode=0660
DirectoryMode=0750
RemoveOnStop=true
[Install]
WantedBy=sockets.target
@@ -0,0 +1,44 @@
[Unit]
Description=AIOS trusted candidate builder
Requires=aios-trusted-candidate-builder.socket
After=aios-trusted-candidate-builder.socket
[Service]
Type=simple
User=root
Group=root
UMask=0077
LoadCredentialEncrypted=signing_private_key:/etc/credstore.encrypted/aios-trusted-candidate-builder/signing_private_key
LoadCredentialEncrypted=builder_hmac:/etc/credstore.encrypted/aios-trusted-candidate-builder/builder_hmac
Environment=AIOS_TRUSTED_BUILDER_POLICY=/opt/aios/trusted-candidate-builder/current/config/gitops/trusted-candidate-builder-policy.json
Environment=AIOS_TRUSTED_BUILDER_GIT_DIR=/data/aios-gitops/repo.git
Environment=AIOS_TRUSTED_BUILDER_EVIDENCE_ROOT=/var/lib/aios-trusted-candidate-builder/evidence
Environment=AIOS_TRUSTED_BUILDER_NONCE_LEDGER=/var/lib/aios-trusted-candidate-builder/nonce-ledger.json
Environment=AIOS_TRUSTED_BUILDER_KEY_ID=builder-attestation-ed25519-g1
Environment=AIOS_TRUSTED_BUILDER_KEY_GENERATION=1
Environment=PYTHONDONTWRITEBYTECODE=1
ExecStart=/usr/bin/python3 -B /opt/aios/trusted-candidate-builder/current/app/aios_trusted_candidate_builder.py --socket-fd 3 --policy ${AIOS_TRUSTED_BUILDER_POLICY}
Restart=on-failure
RestartSec=2
StateDirectory=aios-trusted-candidate-builder
StateDirectoryMode=0700
NoNewPrivileges=true
PrivateTmp=true
PrivateNetwork=true
ProtectSystem=strict
ProtectHome=true
ProtectControlGroups=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectKernelLogs=true
RestrictAddressFamilies=AF_UNIX
LockPersonality=true
MemoryDenyWriteExecute=true
RestrictSUIDSGID=true
SystemCallArchitectures=native
CapabilityBoundingSet=
ReadOnlyPaths=/opt/aios/trusted-candidate-builder /data/aios-gitops/repo.git /var/lib/aios-trusted-candidate-builder/evidence
ReadWritePaths=/var/lib/aios-trusted-candidate-builder
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,13 @@
[Unit]
Description=AIOS trusted candidate builder AF_UNIX socket
[Socket]
ListenStream=/run/aios-trusted-candidate-builder.sock
SocketUser=root
SocketGroup=gitops
SocketMode=0660
RemoveOnStop=yes
Accept=no
[Install]
WantedBy=sockets.target
@@ -0,0 +1,30 @@
# AIOS Autonomous Release Campaign v1
This control plane changes the Owner interaction model from per-transaction
approval to one durable, revocable campaign policy delegation.
The single bootstrap confirmation creates:
1. a narrow task-writer grant for the control-plane implementation branch; and
2. a root-owned campaign authorization in PENDING_CONTROL_PLANE_MERGE state.
After the protected control-plane PR merges, machine activation may move that
same receipt to ACTIVE only when:
- the merged main is a descendant of the campaign lineage anchor;
- the campaign modules and tests are present in merged main;
- the campaign receipt digest is valid;
- no revocation exists;
- global AIOS_GITOPS_WRITES_ENABLED remains false.
Once ACTIVE, routine engineering, protected PRs, exact release binding
acceptance, tag publication, deployment, Registry/Baseline finalization and
recoverable failure handling no longer ask the Owner for another interaction.
Manual Owner review remains mandatory only for:
- formal same-name tag conflict;
- force-push requirement;
- published history rewrite;
- non-fast-forward main divergence;
- private signing material exposure;
- repository identity conflict;
- campaign revocation;
- operation outside campaign scope.
+37
View File
@@ -0,0 +1,37 @@
# Durable GitOps Job Controller
The control-plane write boundary is a durable Job, not a synchronous checkout
or commit request. The source of truth is SQLite WAL at
`/var/lib/aios-gitops/jobs/state.sqlite3`, with `jobs`, `idempotency_keys`,
`branch_leases`, `state_transitions`, and `receipts` tables. Per-job
`request.json`, `state.json`, `receipts/`, and `logs-digest/` files are
durable mirrors/evidence, not a second state machine.
The controller owns only binding validation, idempotency, state transitions,
and recovery classification. A supervised worker owns an isolated detached
worktree and all Git side effects. `/api/file` and `/api/commit-file` adapters
must return `job_id` and `side_effect_state`; they must never perform
checkout, file write, `git add`, or `git commit` inline.
`/var/lib/aios-gitops/payloads/sha256/<digest>` is the content-addressed
payload store. SQLite records only `payload_digest`, `payload_ref`, and size;
the worker verifies both digest and size before consumption. The durable
execution gate is `AIOS_GITOPS_DURABLE_JOB_EXECUTION_ENABLED=true`, while
`AIOS_GITOPS_WRITES_ENABLED=false` remains the independent legacy repository
mutation gate.
The idempotency key is the SHA-256 of the canonical tuple:
`campaign_id`, `repository`, `operation`, `branch`, `expected_branch_oid`, and
`payload_digest`.
The writer lease is `CONTINUE_EXISTING` for the recovered campaign. It binds
to lineage anchor `69b45e81dcd2c3707c367003b08c55fd361faae8` and exact initial
head `bba675fab3b7e0c5fba7066720252b3c91b9f20d`. Every ref update is delegated
to a non-Owner machine broker using compare-and-swap; a stale head is a
`BRANCH_LEASE_STALE` condition and never a force push.
Production remains outside this worker contract. The fixed
`aios-gitops-writer.service` uses `Accept=no`, automatic restart, and durable
queue/reconciliation execution; it cannot mutate Git while the legacy global
write gate is false or while the campaign/machine capability is absent.
+44
View File
@@ -241,6 +241,50 @@ components:
properties:
candidate_id: {type: string}
paths:
/v2/commit-files:
post:
operationId: commitFilesGoverned
summary: Submit one atomic governed commit containing multiple files
requestBody:
required: true
content:
application/json:
schema:
type: object
additionalProperties: false
required:
- task_id
- repository
- expected_branch_oid
- target_branch
- generation
- idempotency_key
- files
- message
properties:
task_id: {type: string}
job_id: {type: string, description: External durable Job identity for exact completed-replay reconciliation}
source_lease_epoch: {type: integer, minimum: 1}
repository: {type: string, example: aios/aios}
expected_branch_oid: {type: string, pattern: '^[0-9a-f]{40}$'}
target_branch: {type: string, pattern: '^agent/[A-Za-z0-9._/-]+$'}
generation: {type: integer, minimum: 1}
idempotency_key: {type: string}
files:
type: array
minItems: 1
items:
type: object
additionalProperties: false
required: [path, content_base64]
properties:
path: {type: string, minLength: 1, maxLength: 512}
content_base64: {type: string}
message: {type: string, minLength: 1, maxLength: 200}
responses:
'200': {description: Governed commit job and verified branch result}
'400': {description: Contract or policy rejection}
'409': {description: Branch CAS or idempotency conflict}
/createReleaseIntent:
post:
operationId: createReleaseIntent
@@ -0,0 +1,15 @@
# AIOS Mission Source Convergence
This directory contains the exact verified governance runtime sources migrated from the live control plane under `AIOS_AUTONOMOUS_RELEASE_MISSION_COMPLETION_AUTHORIZATION_V1`.
The executables remain capability-separated:
- `aios-governance-executor`: protected branch-policy GET, semantic CAS, and approved Stage-1 delta only.
- `aios-governance-drift-monitor`: read-only semantic/transport drift classification.
- `aios-governance-preapproval-publisher`: governance/request publication only; no protection, main, tag, production, Registry, Baseline, or credential mutation.
- `semantic_protection.py`: canonical `aios.semantic-protection.v1` model.
- `ref_reconciler.py`: Gitea-main to Authority FF-CAS reconciler with idempotent readback.
All credential values remain systemd-projected and are never part of source, logs, arguments, or receipts.
The live source hashes used for this migration are recorded in the mission receipt, while this Git tree is the permanent source of truth for future installs.
@@ -0,0 +1,7 @@
# AIOS Mission Zero-Interaction Self-Test
This marker is intentionally harmless and exists only to exercise the
machine-authored branch, exact-head status publication, campaign approval gate,
protected fast-forward merge, and Authority main CAS recovery path.
Production, tags, Registry, Baseline, and V2R1C7 remain unchanged.
@@ -0,0 +1,15 @@
# Bridge v2.1 Durable Reconciliation Bootstrap
This is the planning-only bootstrap artifact for
`TASK-P0-BRIDGE-V21-DURABLE-RECONCILIATION-20260809`.
The task is bound to the exact base commit
`3f670c387692b8865a10d5b419468d8a0b64d7a4` and the development branch
`agent/p0-bridge-v21-durable-reconciliation-20260809`.
The first governed commit records the design boundary and acceptance contract
only. It does not implement the v2.1 body, mutate main, create tags, change
Baseline, or touch production. Future work must preserve absent-ref CAS,
observation-first recovery, durable JobStore state, exact Authority/Gitea/API
reconciliation, and the existing secret-scanning and credential-projection
boundaries.
@@ -0,0 +1,150 @@
# ChatGPT Bridge v2 Control Plane Convergence Design
Date: 2026-08-09
Task: `TASK-P0-CHATGPT-BRIDGE-V2-CONTROL-PLANE-CONVERGENCE-20260809`
Repository: `aios/aios`
Approved development base: `2e8fa22989808a448065d1d76b5b18948774a637`
Target branch: `agent/p0-chatgpt-bridge-v2-control-plane-convergence-20260809`
## Scope and authority
This design converges the ChatGPT GitOps Bridge, the existing Authorized Task Writer, the continuation lease protocol, the Durable GitOps Job Controller, and immutable control-plane runtime identity.
The development base is exact and must never be replaced with a dynamically resolved `HEAD`, `latest`, or `main`. If Authority `refs/heads/main` differs from the approved OID before the first governed writer push, execution fails with `BASE_OID_CHANGED`.
This task does not authorize production changes. The following remain immutable:
- Production Baseline `V2R1C7`.
- Production target OID `c7adece1cc8beffe7c718d53c775898b45d6c7f8`.
- Production tag OID `82792347937c0df59a8e02d6162775252bcad2e2`.
- Production Registry, services, deployment state, runtime, tags, and release evidence.
- Authority `main`.
Force push, history rewrite, direct main push, production mutation, tag mutation, baseline mutation, and secret commits are always denied.
## Immutable control-plane runtime identity
A control-plane release is installed into a versioned immutable directory equivalent to:
```text
/opt/aios/gitops-control-plane/<release-id-or-oid>/
```
The release manifest binds:
```text
control_plane_release_version
control_plane_target_oid
artifact_sha256
runtime_path
bridge_schema_version
repository_identity
authoritative_git_dir
deployment_state
```
The validator is independent from the mutable Git workspace. It rejects execution when the runtime path is a workspace/worktree, the observed runtime commit or artifact digest differs from the manifest, the repository identity is not `aios/aios`, the authoritative Git directory is absent or mismatched, or the Bridge schema version is absent or unsupported.
Every failure is fail closed with `CONTROL_PLANE_RUNTIME_DRIFT`. No fallback to a mutable workspace is permitted. The installer and systemd template are staging-safe; this task may install and restart only a staging/test service.
## Bridge v2 adapter
The Bridge schema identity is `aios.chatgpt-gitops-bridge.v2`. It is independent from the production baseline, control-plane release version, and runtime release commit.
The Bridge exposes:
- `getRuntimeStatus`
- `getGovernedWriteCapabilities`
- `createGovernedTask`
- `acquireWriterLease`
- `commitFileGoverned`
- `commitFilesGoverned`
- `getGitOpsJob`
- `resumeGitOpsJob`
- `reconcileGitOpsJob`
The adapter reuses existing mechanisms and does not create a second writer, lease protocol, branch state store, job database, or direct Git mutation path.
## Governed task and lease flow
`createGovernedTask` binds an exact task ID, repository, approved base OID, non-main branch, risk, and description. Existing branches are read back and bound to their observed full OID. Idempotent replay returns the original binding; conflicting replay fails closed.
`acquireWriterLease` uses `WriterLeaseRequestV2`. Continuation uses:
```text
branch_mode=CONTINUE_EXISTING
expected_branch_oid=<full OID>
generation=<monotonic integer>
create_target_branch=false
worktree_mode=detached_at_expected_head
cas_ref_update_required=true
```
Credentials remain server-side and never appear in responses, Git, durable job metadata, or logs.
## Governed commit flow
`commitFileGoverned` and `commitFilesGoverned` submit work to the existing `GitOpsJobController` and `JobStore`. The server computes payload digests. Raw file content is stored only in the content-addressed payload domain. Secret scanning, protected-path policy, repository binding, lease validation, and branch authorization run before side effects.
`commitFilesGoverned` is one atomic Git commit and enforces bounded file count, per-file size, and total payload size. Any failure leaves no partial commit or ref update.
All ref updates use compare-and-swap. Stale `expected_branch_oid` returns conflict and never triggers automatic rebase. After push, Authority and remote Gitea refs are independently read and must equal the resulting commit OID.
## Durable job lifecycle
Job reads return only non-sensitive metadata: `job_id`, `state`, `revision`, `side_effect_state`, `retryable`, `binding`, and `receipt_summary`.
Terminal jobs cannot be changed by ordinary resume. Resume requires `job_id` and `expected_revision` and uses revision CAS. Reconciliation is observation-first: for `UNKNOWN`, `RECONCILIATION_REQUIRED`, or `REMOTE_PUSH_PENDING`, local, Authority, and remote refs are read before state changes. A blind repeated push is never reconciliation.
## Legacy behavior
Legacy writes remain disabled while governed writes are enabled:
```text
legacy_writes_enabled=false
governed_writes_enabled=true
```
The global `AIOS_GITOPS_WRITES_ENABLED` flag is never enabled as the solution. Legacy operations remain temporarily visible for compatibility and diagnostics, but return:
```json
{
"error": "LEGACY_WRITE_DISABLED",
"replacement": "createGovernedTask",
"governed_writes_available": true
}
```
## OpenAPI convergence
`docs/gitops/openapi-chatgpt-actions.yaml` is the canonical Bridge v2 schema and integrates the Authorized Task Writer fragment. The canonical CAS field is `expected_branch_oid`. `expected_head_oid` may be accepted only at an explicit deprecated adapter boundary.
Contract tests require exact equality between OpenAPI required fields, runtime parser required fields, and generated GPT Action schema required fields.
## Acceptance
Automated coverage includes runtime path/OID/digest/repository drift, exact task/branch/OID/lease/CAS behavior, job DB failure, restart during remote verification, remote timeout, response loss after remote success, idempotency replay/conflict, concurrent branch movement, unauthorized or main branch writes, protected paths, secret-like payloads, disabled governed writer, missing/expired lease, wrong repository identity, and observation-first reconciliation.
The staging sequence is:
```text
getRuntimeStatus
getGovernedWriteCapabilities
createGovernedTask
acquireWriterLease
commitFilesGoverned
getGitOpsJob
verify Authority branch ref
verify remote Gitea branch ref
restart staging control-plane service
getRuntimeStatus
acquire continuation lease
second governed commit
verify remote ref again
```
Completion requires all tests, staging restart continuation, Authority/remote OID equality, unchanged production invariants, no force push, no tag rewrite, no direct main push, and no secret commit.
## Implementation boundaries
Implementation is limited to the Bridge, existing writer and job-controller adapters, runtime identity/installer, staging systemd template, OpenAPI documents, tests, and engineering evidence. Hermes Adapter integration and production cutover are outside this task and require separate approval.
@@ -0,0 +1,80 @@
#!/usr/bin/env bash
set -Eeuo pipefail
umask 027
ROOT="/data/aios-gitops"
NGINX_SITE="/etc/nginx/sites-enabled/aios-user-portal.conf"
STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
BACKUP_DIR="$ROOT/backups/action-surface-final-$STAMP"
LOG="$ROOT/logs/action-surface-final-$STAMP.log"
mkdir -p "$BACKUP_DIR" "$ROOT/logs"
exec > >(tee -a "$LOG") 2>&1
sudo cp -a "$NGINX_SITE" "$BACKUP_DIR/aios-user-portal.conf"
SITE_BACKUP="$BACKUP_DIR/aios-user-portal.conf"
python3 - "$SITE_BACKUP" "$BACKUP_DIR/aios-user-portal.conf.next" <<'PY'
from pathlib import Path
import sys
source, output = map(Path, sys.argv[1:])
text = source.read_text(encoding="utf-8")
marker = " # AIOS external Action governed-read ownership"
if marker in text:
raise SystemExit("governed-read locations already present in backup")
block = r''' # AIOS external Action governed-read ownership
# These reads are owned by the authenticated 18890 facade, not the 18893 model API.
location = /api/governed-write-capabilities {
proxy_pass http://127.0.0.1:18890/api/governed-write-capabilities;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Authorization $http_authorization;
proxy_set_header X-AIOS-Token $http_x_aios_token;
}
location = /api/control-plane-snapshot {
proxy_pass http://127.0.0.1:18890/api/control-plane-snapshot;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Authorization $http_authorization;
proxy_set_header X-AIOS-Token $http_x_aios_token;
}
location = /api/online-agent-task {
proxy_pass http://127.0.0.1:18890/api/online-agent-task;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Authorization $http_authorization;
proxy_set_header X-AIOS-Token $http_x_aios_token;
}
location = /api/online-agent-task-events {
proxy_pass http://127.0.0.1:18890/api/online-agent-task-events;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Authorization $http_authorization;
proxy_set_header X-AIOS-Token $http_x_aios_token;
}
'''
needle = " location /api/ {"
if text.count(needle) != 1:
raise SystemExit("expected exactly one generic /api/ location")
text = text.replace(needle, block + "\n" + needle, 1)
output.write_text(text, encoding="utf-8")
PY
sudo cp "$BACKUP_DIR/aios-user-portal.conf.next" "$NGINX_SITE"
sudo nginx -t
sudo systemctl reload nginx
printf 'ACTION_SURFACE_FINAL_OK backup=%s log=%s\n' "$BACKUP_DIR" "$LOG"
+4
View File
@@ -0,0 +1,4 @@
[pytest]
testpaths = tests
python_files = test_*.py
addopts = -ra --import-mode=importlib
@@ -0,0 +1,121 @@
{
"schema": "aios.canonical-task-projection-intent.v1",
"projection_id": "projection-v2r2c2-parent-recovery-20260826",
"projection_generation": 1,
"idempotency_key": "projection-v2r2c2-parent-recovery-20260826-v1",
"task": {
"task_id": "TASK-CONV-V2R2C2-RECONCILE-SNAPSHOT-20260826",
"goal": "Complete V2R2C2 staging-only control-plane convergence and repair the control-plane self-recovery defect exposed by the missing canonical Task projection.",
"instructions": "Resume the immutable runbook at runbooks/agent-v2r2c2-control-plane-convergence-20260825.md from branch agent/v2r2c2-convergence-integration-20260825. Preserve the parent task/job/generation/branch/OID. Complete canonical projection, worker claim, execution binding, heartbeat, WriterLease/CAS, required convergence code, tests, immutable staging build/deploy, first acceptance, restart-persistent second acceptance, and governed Evidence Closure. Recovery subgraphs must return to the parent goal. Never mutate main, V2R2C1 protected tag, production baseline, or production services.",
"success_criteria": [
{
"id": "control_plane_self_recovery",
"required": true,
"expected": "PASS"
},
{
"id": "canonical_task_projection",
"required": true,
"expected": "PASS"
},
{
"id": "worker_execution_path",
"required": true,
"expected": "PASS"
},
{
"id": "writer_path",
"required": true,
"expected": "PASS"
},
{
"id": "governed_mutation",
"required": true,
"expected": "PASS"
},
{
"id": "v2r2c2_code",
"required": true,
"expected": "PASS"
},
{
"id": "tests",
"required": true,
"expected": "PASS"
},
{
"id": "staging_deployment",
"required": true,
"expected": "PASS"
},
{
"id": "runtime_component_drift",
"required": true,
"expected": "PASS"
},
{
"id": "release_consistency",
"required": true,
"expected": "PASS"
},
{
"id": "tag_publication",
"required": true,
"expected": "PASS"
},
{
"id": "release_acceptance",
"required": true,
"expected": "PASS"
},
{
"id": "restart_persistence",
"required": true,
"expected": "PASS"
},
{
"id": "evidence_closure",
"required": true,
"expected": "PASS"
}
],
"constraints": {
"staging_only": true,
"production_write": false,
"main_direct_write": false,
"protected_tag_write": false,
"baseline_write": false,
"force_push": false,
"direct_task_db_write_outside_task_plane": false,
"secret_exposure": false,
"return_to_parent_after_recovery": true
},
"required_capabilities": [],
"metadata": {
"parent_goal": "V2R2C2 staging-only control-plane convergence",
"governed_task_id": "TASK-CONV-V2R2C2-RECONCILE-SNAPSHOT-20260826",
"bridge_job_id": "job-9eee1142f2fc8bb7b571d065c01f40e0",
"recovery_reason": "MISSING_DURABLE_CANONICAL_TASK_PROJECTION_RECOVERY",
"runbook_ref": "runbooks/agent-v2r2c2-control-plane-convergence-20260825.md",
"runbook_oid": "2afc2cf41fc9090cf01e3882d1b8db8d54cf1844",
"runbook_sha256": "a500504a76fb99b26541eaf4edd842a942992ae170fc0f3838530189ca672320"
},
"priority": 100,
"max_attempts": 20,
"deadline": null
},
"governed_binding": {
"governed_task_id": "TASK-CONV-V2R2C2-RECONCILE-SNAPSHOT-20260826",
"bridge_job_id": "job-9eee1142f2fc8bb7b571d065c01f40e0",
"repository": "aios/aios",
"branch": "agent/v2r2c2-convergence-integration-20260825",
"generation": 2,
"base_oid": "2afc2cf41fc9090cf01e3882d1b8db8d54cf1844",
"expected_branch_oid": "2afc2cf41fc9090cf01e3882d1b8db8d54cf1844"
},
"source_artifact": {
"ref": "git:2afc2cf41fc9090cf01e3882d1b8db8d54cf1844:runbooks/agent-v2r2c2-control-plane-convergence-20260825.md",
"sha256": "a500504a76fb99b26541eaf4edd842a942992ae170fc0f3838530189ca672320"
},
"task_payload_sha256": "dcc293ffcd9db36ea8dd35b79a15e77cca57ee4ad5d6f976e24ee454d46eb0c9"
}
+21
View File
@@ -0,0 +1,21 @@
"""Canonical AIOS release-version parser shared by runtime and release tools."""
from __future__ import annotations
import re
# The suffix preserves already-supported annotated release names while the
# canonical VnRnCn namespace remains accepted exactly.
RELEASE_VERSION_PATTERN = r"^V[0-9]+R[0-9]+C[0-9]+(?:-[A-Za-z0-9_.-]+)?$"
RELEASE_VERSION_RE = re.compile(RELEASE_VERSION_PATTERN)
def parse_release_version(version: object) -> str:
value = str(version or "").strip()
if not RELEASE_VERSION_RE.fullmatch(value):
raise ValueError("invalid release version")
return value
def validate_release_version(version: object) -> str:
return parse_release_version(version)
@@ -0,0 +1,243 @@
{
"authority_proof": {
"branch_oid": "2c6293160ed567a2098715edbb296741577c5bff",
"commit_visible": true,
"main_oid": "2e8fa22989808a448065d1d76b5b18948774a637",
"v2r2c1_tag_oid": "998d30304787e31e5ae607c0bd86b5edb2b0e5ad"
},
"baseline_authority": {
"artifact_sha256": "492338b895b152a55015fd92ea1d9dba2314894ab00c0ba219a78f3cef544b8a",
"authority_sha256": "d8e23bad49bdb3e4192ba708c3dfe7fc5b7cdc6a513c2f87acd6412a51867231",
"current_baseline": "V2R2C1",
"raw_byte_sidecar_verification": "PASS",
"target_oid": "998d30304787e31e5ae607c0bd86b5edb2b0e5ad"
},
"control_plane_source_oid": "b781129bd5a8b53c05ab648361424d2800166ee4",
"diff_stat": "active-runtime/app/aios_authority_git_executor.py | 24 +\n active-runtime/app/aios_chatgpt_actions_api.py | 48 +-\n active-runtime/app/aios_chatgpt_bridge_v2.py | 167 ++++\n .../app/aios_chatgpt_bridge_v2_server.py | 431 +++++++++\n active-runtime/app/aios_continuation_executor.py | 594 ++++++++++++\n active-runtime/app/aios_continuation_lease.py | 6 +\n active-runtime/app/aios_control_plane_snapshot.py | 26 +\n active-runtime/app/aios_credential_broker.py | 78 ++\n active-runtime/app/aios_gitea_publisher.py | 93 ++\n active-runtime/app/aios_gitops_job_controller.py | 16 +-\n .../app/aios_gitops_reconciliation_core.py | 80 ++\n active-runtime/app/aios_governed_policy.py | 59 ++\n active-runtime/app/aios_governed_task_bootstrap.py | 84 ++\n active-runtime/app/aios_model_api_staging.py | 1022 ++++++++++++++++++++\n .../app/authorized_task_writer_provisioning.py | 4 +-\n active-runtime/app/baseline_authority.py | 84 ++\n active-runtime/app/governed_production_release.py | 568 +++++++++++\n active-runtime/app/v2r2c1_production_adapter.py | 135 +++\n .../systemd/aios-chatgpt-bridge-v2-staging.service | 23 +\n .../aios-gitops-model-api-v2r2c1-staging.service | 51 +\n ...09-bridge-v2.1-durable-reconciliation-design.md | 15 +\n ...t-bridge-v2-control-plane-convergence-design.md | 150 +++\n pytest.ini | 4 +\n scripts/build_v2r2c1_model_api_staging.py | 145 +++\n .../aios_release_controller/manifest.py | 4 +-\n .../test_v2r1c6_release_consistency.py | 4 +-\n .../aios_v2_candidate/test_v2r1c6_release_ready.py | 16 +-\n tests/conftest.py | 65 ++\n tests/test_bridge_v2_governed_http_bootstrap.py | 90 ++\n tests/test_governed_local_writer.py | 6 +-\n tests/test_model_api_immutable_staging.py | 35 +\n 31 files changed, 4108 insertions(+), 19 deletions(-)",
"evidence_closure_oid": "a8b906d1bc24088c19647e98de6a679268f6d0a2",
"forbidden_operations": [
"direct_main_push",
"main_merge_without_owner_approval",
"force_push",
"tag_mutation",
"baseline_mutation",
"registry_mutation",
"production_restart",
"production_cutover",
"live_model_api_cutover",
"history_rewrite",
"secret_commit"
],
"governance": {
"baseline_modified": false,
"direct_main_push": false,
"force_push_used": false,
"history_rewritten": false,
"live_model_api_cutover": false,
"main_merged": false,
"owner_approval_respected": true,
"production_changed": false,
"production_restarted": false,
"registry_modified": false,
"secrets_committed": false,
"tag_rewritten": false
},
"included_path_count": 31,
"integration_branch": "agent/v2r2c1-post-release-mainline-convergence-20260809",
"integration_head_oid": "2c6293160ed567a2098715edbb296741577c5bff",
"logical_commits": [
{
"message": "feat(v2r2c1): converge durable control-plane core",
"oid": "40b685d1c70c767c7a79a2b184348e58e3e319ae",
"parent_oid": "2e8fa22989808a448065d1d76b5b18948774a637"
},
{
"message": "docs(v2r2c1): converge durable runtime and design sources",
"oid": "5f616311bab38b0dac296ed8b0e69660e031a20e",
"parent_oid": "40b685d1c70c767c7a79a2b184348e58e3e319ae"
},
{
"message": "feat(v2r2c1): integrate governed release overlay",
"oid": "57780e316e320a5128cc2c9f12adbb19ae57aef5",
"parent_oid": "5f616311bab38b0dac296ed8b0e69660e031a20e"
},
{
"message": "feat(v2r2c1): wire governed release operations into durable bridge",
"oid": "e41b8d323a3e7a40440f5d32221f524074bea5fe",
"parent_oid": "57780e316e320a5128cc2c9f12adbb19ae57aef5"
},
{
"message": "test: repair canonical pytest repository entrypoint",
"oid": "b20cb8130e4c68267d5d85b02eed51cb8d4f41c0",
"parent_oid": "e41b8d323a3e7a40440f5d32221f524074bea5fe"
},
{
"message": "test: make whole-suite collection deterministic",
"oid": "c6a1693efaede9dace9b929d884a09e162a02c89",
"parent_oid": "b20cb8130e4c68267d5d85b02eed51cb8d4f41c0"
},
{
"message": "test: preserve legacy suite imports under canonical collection",
"oid": "91f2ea0591787d3dbf8c2e27ab0460629d86900e",
"parent_oid": "c6a1693efaede9dace9b929d884a09e162a02c89"
},
{
"message": "test: expose repository test helpers to canonical collection",
"oid": "85897d3f6d708271dff16767265f9d14b2f29801",
"parent_oid": "91f2ea0591787d3dbf8c2e27ab0460629d86900e"
},
{
"message": "fix(v2r2c1): close slash-branch and suite contract regressions",
"oid": "f1110d24f30d8441f2df04e0774ee230b95c2e02",
"parent_oid": "85897d3f6d708271dff16767265f9d14b2f29801"
},
{
"message": "test: align writer regression with durable idempotent replay",
"oid": "c10d2a5dfcc394dde7a7022952d7b423725747a3",
"parent_oid": "f1110d24f30d8441f2df04e0774ee230b95c2e02"
},
{
"message": "feat(v2r2c1): source baseline authority for model-api staging",
"oid": "f969aedaf61501a866435c712be01ff2cfe43fb1",
"parent_oid": "c10d2a5dfcc394dde7a7022952d7b423725747a3"
},
{
"message": "feat(v2r2c1): add immutable model api staging runtime",
"oid": "4ab70bfdb4d34aa9d1dabc4230a3367eaa93cc9b",
"parent_oid": "f969aedaf61501a866435c712be01ff2cfe43fb1"
},
{
"message": "fix(v2r2c1): avoid builder secret-scan false positives",
"oid": "7c4e86bfc1f39e643e672968d1fdedbd72ace56d",
"parent_oid": "4ab70bfdb4d34aa9d1dabc4230a3367eaa93cc9b"
},
{
"message": "fix(v2r2c1): isolate model api staging port",
"oid": "7564363a30bfa7fa81ac5c7c545ec704984ec543",
"parent_oid": "7c4e86bfc1f39e643e672968d1fdedbd72ace56d"
},
{
"message": "fix(v2r2c1): bind baseline authority and openapi staging path",
"oid": "2c6293160ed567a2098715edbb296741577c5bff",
"parent_oid": "7564363a30bfa7fa81ac5c7c545ec704984ec543"
}
],
"main_base_oid": "2e8fa22989808a448065d1d76b5b18948774a637",
"mandatory_gates": {
"baseline_registry_readback_staging_pass": true,
"durable_source_path_manifest_complete": true,
"existing_v2r2c1_suite_pass": true,
"governed_release_suite_pass": true,
"integration_authority_git_api_equal": true,
"integration_branch_governed": true,
"logical_slice_commits_remote_verified": true,
"main_base_exact_before_branch_create": true,
"model_api_immutable_runtime_staging_pass": true,
"owner_merge_request_generated_execute_false": true,
"production_unchanged": true,
"pytest_collection_entrypoints_fixed": true,
"pytest_tests_results_recorded": true
},
"model_api_staging": {
"artifact_sha256": "492338b895b152a55015fd92ea1d9dba2314894ab00c0ba219a78f3cef544b8a",
"baseline": {
"authority_sha256": "d8e23bad49bdb3e4192ba708c3dfe7fc5b7cdc6a513c2f87acd6412a51867231",
"current_baseline": "V2R2C1",
"deployment_state": "ACTIVE",
"evidence_closure_oid": "a8b906d1bc24088c19647e98de6a679268f6d0a2",
"generation": 4,
"previous_baseline": "V2R1C7",
"production_deployed": true,
"status": "PRODUCTION_DEPLOYED",
"tag_oid": "07fc2746b688f5f243f29b39de39f32edc3dd9d2",
"target_oid": "998d30304787e31e5ae607c0bd86b5edb2b0e5ad"
},
"baseline_status": 200,
"deployment_state": "STAGING",
"health": {
"artifact_sha256": "492338b895b152a55015fd92ea1d9dba2314894ab00c0ba219a78f3cef544b8a",
"commit_oid": "2c6293160ed567a2098715edbb296741577c5bff",
"deployment_state": "STAGING",
"governed_release_routing": "bridge-v2",
"legacy_global_writes_enabled": false,
"production_deployed": false,
"runtime_mode": "IMMUTABLE_STAGING",
"status": "ok",
"version": "V2R2C1"
},
"health_status": 200,
"legacy_write_probe": "423_BLOCKED",
"port": 18893,
"restart_acceptance": "PASS",
"runtime_root": "/opt/aios/gitops-model-api/2c6293160ed567a2098715edbb296741577c5bff/runtime",
"source_oid": "2c6293160ed567a2098715edbb296741577c5bff",
"systemd": "MainPID=788782\nExecStart={ path=/usr/bin/python3 ; argv[]=/usr/bin/python3 /opt/aios/gitops-model-api/2c6293160ed567a2098715edbb296741577c5bff/runtime/app/aios_model_api_staging.py ; ignore_errors=no ; start_time=[Sun 2026-08-09 19:11:19 CST] ; stop_time=[n/a] ; pid=788782 ; code=(null) ; status=0/0 }\nActiveState=active\nSubState=running",
"unit": "aios-gitops-model-api-v2r2c1-staging.service",
"versions": {
"authoritative_head_oid": "2e8fa22989808a448065d1d76b5b18948774a637",
"baseline_generation": 4,
"baseline_sha256": "d8e23bad49bdb3e4192ba708c3dfe7fc5b7cdc6a513c2f87acd6412a51867231",
"baseline_version": "V2R2C1",
"query_error": null
},
"versions_status": 200
},
"owner_merge_request": {
"control_plane_source_oid": "b781129bd5a8b53c05ab648361424d2800166ee4",
"execute": false,
"integration_head_oid": "b2cf510e11b712bb99f20ded81459b6524257394",
"main_expected_old_oid": "2e8fa22989808a448065d1d76b5b18948774a637",
"release_target_oid": "998d30304787e31e5ae607c0bd86b5edb2b0e5ad",
"report_commit_oid": "b2cf510e11b712bb99f20ded81459b6524257394",
"status": "OWNER_APPROVAL_REQUIRED"
},
"placeholder_fields": [],
"production": {
"baseline_mutation": false,
"health": {
"artifact_sha256": "ab4ae257346c82863f05abf5e309381a1c86612957608c3a4f4ab5cc9233f052",
"commit_oid": "998d30304787e31e5ae607c0bd86b5edb2b0e5ad",
"deployment_state": "ACTIVE",
"health_timestamp": "2026-08-09T11:21:16.872581+00:00",
"production_deployed": true,
"status": "ok",
"version": "V2R2C1"
},
"health_status": 200,
"live_model_api_show": "MainPID=2816107\nActiveState=active\nSubState=running",
"live_model_api_unchanged": true,
"production_cutover": false,
"production_restart": false,
"registry_mutation": false,
"shadow_service_unchanged": true,
"shadow_show": "MainPID=1505658\nActiveState=active\nSubState=running",
"tag_mutation": false
},
"release_target_oid": "998d30304787e31e5ae607c0bd86b5edb2b0e5ad",
"remote_proof": {
"api_branch_oid": "2c6293160ed567a2098715edbb296741577c5bff",
"api_verification": "paginated_exact_name",
"commit_visible": true,
"git_remote_oid": "2c6293160ed567a2098715edbb296741577c5bff",
"main_api_oid": "2e8fa22989808a448065d1d76b5b18948774a637",
"main_git_oid": "2e8fa22989808a448065d1d76b5b18948774a637"
},
"report_commit_parent_oid": "2c6293160ed567a2098715edbb296741577c5bff",
"repository": "aios/aios",
"schema": "aios.v2r2c1.post-release-mainline-convergence.acceptance.v1",
"status": "V2R2C1_MAINLINE_CONVERGENCE_READY_FOR_OWNER_MERGE",
"success_status": "V2R2C1_MAINLINE_CONVERGENCE_READY_FOR_OWNER_MERGE",
"task_id": "TASK-P0-V2R2C1-POST-RELEASE-MAINLINE-CONVERGENCE-20260809",
"tests": {
"collection": "PASS",
"command": "pytest tests",
"failed": 0,
"passed": 709,
"skipped": 2,
"skipped_tests": [
"tests/test_gitea_tag_publication_live.py (live opt-in)",
"tests/test_signer_contract_compatibility.py (live integration opt-in)"
],
"subtests": 154
}
}
@@ -0,0 +1,89 @@
# V2R2C1 post-release mainline convergence diff summary
- task: `TASK-P0-V2R2C1-POST-RELEASE-MAINLINE-CONVERGENCE-20260809`
- repository: `aios/aios`
- main base: `2e8fa22989808a448065d1d76b5b18948774a637`
- release target: `998d30304787e31e5ae607c0bd86b5edb2b0e5ad`
- control-plane source: `b781129bd5a8b53c05ab648361424d2800166ee4`
- implementation integration head before this report commit: `2c6293160ed567a2098715edbb296741577c5bff`
- branch: `agent/v2r2c1-post-release-mainline-convergence-20260809`
## Logical governed commits
| OID | Parent | Message |
|---|---|---|
| `40b685d1c70c767c7a79a2b184348e58e3e319ae` | `2e8fa22989808a448065d1d76b5b18948774a637` | feat(v2r2c1): converge durable control-plane core |
| `5f616311bab38b0dac296ed8b0e69660e031a20e` | `40b685d1c70c767c7a79a2b184348e58e3e319ae` | docs(v2r2c1): converge durable runtime and design sources |
| `57780e316e320a5128cc2c9f12adbb19ae57aef5` | `5f616311bab38b0dac296ed8b0e69660e031a20e` | feat(v2r2c1): integrate governed release overlay |
| `e41b8d323a3e7a40440f5d32221f524074bea5fe` | `57780e316e320a5128cc2c9f12adbb19ae57aef5` | feat(v2r2c1): wire governed release operations into durable bridge |
| `b20cb8130e4c68267d5d85b02eed51cb8d4f41c0` | `e41b8d323a3e7a40440f5d32221f524074bea5fe` | test: repair canonical pytest repository entrypoint |
| `c6a1693efaede9dace9b929d884a09e162a02c89` | `b20cb8130e4c68267d5d85b02eed51cb8d4f41c0` | test: make whole-suite collection deterministic |
| `91f2ea0591787d3dbf8c2e27ab0460629d86900e` | `c6a1693efaede9dace9b929d884a09e162a02c89` | test: preserve legacy suite imports under canonical collection |
| `85897d3f6d708271dff16767265f9d14b2f29801` | `91f2ea0591787d3dbf8c2e27ab0460629d86900e` | test: expose repository test helpers to canonical collection |
| `f1110d24f30d8441f2df04e0774ee230b95c2e02` | `85897d3f6d708271dff16767265f9d14b2f29801` | fix(v2r2c1): close slash-branch and suite contract regressions |
| `c10d2a5dfcc394dde7a7022952d7b423725747a3` | `f1110d24f30d8441f2df04e0774ee230b95c2e02` | test: align writer regression with durable idempotent replay |
| `f969aedaf61501a866435c712be01ff2cfe43fb1` | `c10d2a5dfcc394dde7a7022952d7b423725747a3` | feat(v2r2c1): source baseline authority for model-api staging |
| `4ab70bfdb4d34aa9d1dabc4230a3367eaa93cc9b` | `f969aedaf61501a866435c712be01ff2cfe43fb1` | feat(v2r2c1): add immutable model api staging runtime |
| `7c4e86bfc1f39e643e672968d1fdedbd72ace56d` | `4ab70bfdb4d34aa9d1dabc4230a3367eaa93cc9b` | fix(v2r2c1): avoid builder secret-scan false positives |
| `7564363a30bfa7fa81ac5c7c545ec704984ec543` | `7c4e86bfc1f39e643e672968d1fdedbd72ace56d` | fix(v2r2c1): isolate model api staging port |
| `2c6293160ed567a2098715edbb296741577c5bff` | `7564363a30bfa7fa81ac5c7c545ec704984ec543` | fix(v2r2c1): bind baseline authority and openapi staging path |
## Exact diff stat
```text
active-runtime/app/aios_authority_git_executor.py | 24 +
active-runtime/app/aios_chatgpt_actions_api.py | 48 +-
active-runtime/app/aios_chatgpt_bridge_v2.py | 167 ++++
.../app/aios_chatgpt_bridge_v2_server.py | 431 +++++++++
active-runtime/app/aios_continuation_executor.py | 594 ++++++++++++
active-runtime/app/aios_continuation_lease.py | 6 +
active-runtime/app/aios_control_plane_snapshot.py | 26 +
active-runtime/app/aios_credential_broker.py | 78 ++
active-runtime/app/aios_gitea_publisher.py | 93 ++
active-runtime/app/aios_gitops_job_controller.py | 16 +-
.../app/aios_gitops_reconciliation_core.py | 80 ++
active-runtime/app/aios_governed_policy.py | 59 ++
active-runtime/app/aios_governed_task_bootstrap.py | 84 ++
active-runtime/app/aios_model_api_staging.py | 1022 ++++++++++++++++++++
.../app/authorized_task_writer_provisioning.py | 4 +-
active-runtime/app/baseline_authority.py | 84 ++
active-runtime/app/governed_production_release.py | 568 +++++++++++
active-runtime/app/v2r2c1_production_adapter.py | 135 +++
.../systemd/aios-chatgpt-bridge-v2-staging.service | 23 +
.../aios-gitops-model-api-v2r2c1-staging.service | 51 +
...09-bridge-v2.1-durable-reconciliation-design.md | 15 +
...t-bridge-v2-control-plane-convergence-design.md | 150 +++
pytest.ini | 4 +
scripts/build_v2r2c1_model_api_staging.py | 145 +++
.../aios_release_controller/manifest.py | 4 +-
.../test_v2r1c6_release_consistency.py | 4 +-
.../aios_v2_candidate/test_v2r1c6_release_ready.py | 16 +-
tests/conftest.py | 65 ++
tests/test_bridge_v2_governed_http_bootstrap.py | 90 ++
tests/test_governed_local_writer.py | 6 +-
tests/test_model_api_immutable_staging.py | 35 +
31 files changed, 4108 insertions(+), 19 deletions(-)
```
## Included durable source domains
- Bridge v2 adapter/server and governed HTTP contracts.
- Durable reconciliation core, continuation executor/lease, Authority Git CAS adapter, job controller compatibility.
- Gitea publisher/verifier, credential broker, governed policy, control-plane snapshot, release routing.
- Canonical pytest entrypoint/conftest and regression coverage.
- Source-controlled immutable model-api staging runtime, builder and systemd template.
- Selected design specifications required for durable mainline context.
## Explicitly excluded
- `main` ref mutation or merge without Owner approval.
- Production service, Registry, Baseline body/sidecar, production tags and mutable `/var/lib` state.
- Installed `/opt` artifacts, temporary staging transition state and task-local receipts/credentials.
- Production cutover/restart, force push, history rewrite and plaintext secrets.
## Verification
- `pytest tests`: **709 passed, 2 skipped, 154 subtests, 0 failed**.
- Staging model-api: immutable OID/digest bound, port 18893, health/base/ver/OpenAPI readback PASS, restart recovery PASS.
- Authority/Gitea Git/Gitea API commit equality and commit visibility PASS.
- Production remains V2R2C1 and was not restarted or cut over.
@@ -0,0 +1,254 @@
{
"control_plane_source_oid": "b781129bd5a8b53c05ab648361424d2800166ee4",
"diff_stat": "active-runtime/app/aios_authority_git_executor.py | 24 +\n active-runtime/app/aios_chatgpt_actions_api.py | 48 +-\n active-runtime/app/aios_chatgpt_bridge_v2.py | 167 ++++\n .../app/aios_chatgpt_bridge_v2_server.py | 431 +++++++++\n active-runtime/app/aios_continuation_executor.py | 594 ++++++++++++\n active-runtime/app/aios_continuation_lease.py | 6 +\n active-runtime/app/aios_control_plane_snapshot.py | 26 +\n active-runtime/app/aios_credential_broker.py | 78 ++\n active-runtime/app/aios_gitea_publisher.py | 93 ++\n active-runtime/app/aios_gitops_job_controller.py | 16 +-\n .../app/aios_gitops_reconciliation_core.py | 80 ++\n active-runtime/app/aios_governed_policy.py | 59 ++\n active-runtime/app/aios_governed_task_bootstrap.py | 84 ++\n active-runtime/app/aios_model_api_staging.py | 1022 ++++++++++++++++++++\n .../app/authorized_task_writer_provisioning.py | 4 +-\n active-runtime/app/baseline_authority.py | 84 ++\n active-runtime/app/governed_production_release.py | 568 +++++++++++\n active-runtime/app/v2r2c1_production_adapter.py | 135 +++\n .../systemd/aios-chatgpt-bridge-v2-staging.service | 23 +\n .../aios-gitops-model-api-v2r2c1-staging.service | 51 +\n ...09-bridge-v2.1-durable-reconciliation-design.md | 15 +\n ...t-bridge-v2-control-plane-convergence-design.md | 150 +++\n pytest.ini | 4 +\n scripts/build_v2r2c1_model_api_staging.py | 145 +++\n .../aios_release_controller/manifest.py | 4 +-\n .../test_v2r1c6_release_consistency.py | 4 +-\n .../aios_v2_candidate/test_v2r1c6_release_ready.py | 16 +-\n tests/conftest.py | 65 ++\n tests/test_bridge_v2_governed_http_bootstrap.py | 90 ++\n tests/test_governed_local_writer.py | 6 +-\n tests/test_model_api_immutable_staging.py | 35 +\n 31 files changed, 4108 insertions(+), 19 deletions(-)",
"evidence_closure_oid": "a8b906d1bc24088c19647e98de6a679268f6d0a2",
"excluded_paths": [
{
"path": "refs/heads/main",
"reason": "main merge requires separate Owner approval; unchanged"
},
{
"path": "refs/tags/*",
"reason": "formal tags are outside engineering convergence scope; unchanged"
},
{
"path": "baseline/current.json and sidecar",
"reason": "production mutable authority and sidecar; read-only verified"
},
{
"path": "/var/lib/aios-gitops/**",
"reason": "host runtime state and receipts excluded from repository source"
},
{
"path": "/opt/aios/**",
"reason": "installed immutable staging artifacts excluded; digest recorded in reports"
},
{
"path": "credentials and secrets",
"reason": "availability-only; no plaintext credential committed"
},
{
"path": "production runtime and Registry",
"reason": "production cutover/registry mutation forbidden"
}
],
"included_paths": [
{
"path": "active-runtime/app/aios_authority_git_executor.py",
"status": "M"
},
{
"path": "active-runtime/app/aios_chatgpt_actions_api.py",
"status": "M"
},
{
"path": "active-runtime/app/aios_chatgpt_bridge_v2.py",
"status": "A"
},
{
"path": "active-runtime/app/aios_chatgpt_bridge_v2_server.py",
"status": "A"
},
{
"path": "active-runtime/app/aios_continuation_executor.py",
"status": "A"
},
{
"path": "active-runtime/app/aios_continuation_lease.py",
"status": "M"
},
{
"path": "active-runtime/app/aios_control_plane_snapshot.py",
"status": "A"
},
{
"path": "active-runtime/app/aios_credential_broker.py",
"status": "A"
},
{
"path": "active-runtime/app/aios_gitea_publisher.py",
"status": "A"
},
{
"path": "active-runtime/app/aios_gitops_job_controller.py",
"status": "M"
},
{
"path": "active-runtime/app/aios_gitops_reconciliation_core.py",
"status": "A"
},
{
"path": "active-runtime/app/aios_governed_policy.py",
"status": "A"
},
{
"path": "active-runtime/app/aios_governed_task_bootstrap.py",
"status": "A"
},
{
"path": "active-runtime/app/aios_model_api_staging.py",
"status": "A"
},
{
"path": "active-runtime/app/authorized_task_writer_provisioning.py",
"status": "M"
},
{
"path": "active-runtime/app/baseline_authority.py",
"status": "A"
},
{
"path": "active-runtime/app/governed_production_release.py",
"status": "A"
},
{
"path": "active-runtime/app/v2r2c1_production_adapter.py",
"status": "A"
},
{
"path": "deploy/systemd/aios-chatgpt-bridge-v2-staging.service",
"status": "A"
},
{
"path": "deploy/systemd/aios-gitops-model-api-v2r2c1-staging.service",
"status": "A"
},
{
"path": "docs/superpowers/specs/2026-08-09-bridge-v2.1-durable-reconciliation-design.md",
"status": "A"
},
{
"path": "docs/superpowers/specs/2026-08-09-chatgpt-bridge-v2-control-plane-convergence-design.md",
"status": "A"
},
{
"path": "pytest.ini",
"status": "A"
},
{
"path": "scripts/build_v2r2c1_model_api_staging.py",
"status": "A"
},
{
"path": "services/aios-release-controller/aios_release_controller/manifest.py",
"status": "M"
},
{
"path": "tests/aios_v2_candidate/test_v2r1c6_release_consistency.py",
"status": "M"
},
{
"path": "tests/aios_v2_candidate/test_v2r1c6_release_ready.py",
"status": "M"
},
{
"path": "tests/conftest.py",
"status": "A"
},
{
"path": "tests/test_bridge_v2_governed_http_bootstrap.py",
"status": "A"
},
{
"path": "tests/test_governed_local_writer.py",
"status": "M"
},
{
"path": "tests/test_model_api_immutable_staging.py",
"status": "A"
}
],
"integration_branch": "agent/v2r2c1-post-release-mainline-convergence-20260809",
"integration_head_oid": "2c6293160ed567a2098715edbb296741577c5bff",
"logical_commits": [
{
"message": "feat(v2r2c1): converge durable control-plane core",
"oid": "40b685d1c70c767c7a79a2b184348e58e3e319ae",
"parent_oid": "2e8fa22989808a448065d1d76b5b18948774a637"
},
{
"message": "docs(v2r2c1): converge durable runtime and design sources",
"oid": "5f616311bab38b0dac296ed8b0e69660e031a20e",
"parent_oid": "40b685d1c70c767c7a79a2b184348e58e3e319ae"
},
{
"message": "feat(v2r2c1): integrate governed release overlay",
"oid": "57780e316e320a5128cc2c9f12adbb19ae57aef5",
"parent_oid": "5f616311bab38b0dac296ed8b0e69660e031a20e"
},
{
"message": "feat(v2r2c1): wire governed release operations into durable bridge",
"oid": "e41b8d323a3e7a40440f5d32221f524074bea5fe",
"parent_oid": "57780e316e320a5128cc2c9f12adbb19ae57aef5"
},
{
"message": "test: repair canonical pytest repository entrypoint",
"oid": "b20cb8130e4c68267d5d85b02eed51cb8d4f41c0",
"parent_oid": "e41b8d323a3e7a40440f5d32221f524074bea5fe"
},
{
"message": "test: make whole-suite collection deterministic",
"oid": "c6a1693efaede9dace9b929d884a09e162a02c89",
"parent_oid": "b20cb8130e4c68267d5d85b02eed51cb8d4f41c0"
},
{
"message": "test: preserve legacy suite imports under canonical collection",
"oid": "91f2ea0591787d3dbf8c2e27ab0460629d86900e",
"parent_oid": "c6a1693efaede9dace9b929d884a09e162a02c89"
},
{
"message": "test: expose repository test helpers to canonical collection",
"oid": "85897d3f6d708271dff16767265f9d14b2f29801",
"parent_oid": "91f2ea0591787d3dbf8c2e27ab0460629d86900e"
},
{
"message": "fix(v2r2c1): close slash-branch and suite contract regressions",
"oid": "f1110d24f30d8441f2df04e0774ee230b95c2e02",
"parent_oid": "85897d3f6d708271dff16767265f9d14b2f29801"
},
{
"message": "test: align writer regression with durable idempotent replay",
"oid": "c10d2a5dfcc394dde7a7022952d7b423725747a3",
"parent_oid": "f1110d24f30d8441f2df04e0774ee230b95c2e02"
},
{
"message": "feat(v2r2c1): source baseline authority for model-api staging",
"oid": "f969aedaf61501a866435c712be01ff2cfe43fb1",
"parent_oid": "c10d2a5dfcc394dde7a7022952d7b423725747a3"
},
{
"message": "feat(v2r2c1): add immutable model api staging runtime",
"oid": "4ab70bfdb4d34aa9d1dabc4230a3367eaa93cc9b",
"parent_oid": "f969aedaf61501a866435c712be01ff2cfe43fb1"
},
{
"message": "fix(v2r2c1): avoid builder secret-scan false positives",
"oid": "7c4e86bfc1f39e643e672968d1fdedbd72ace56d",
"parent_oid": "4ab70bfdb4d34aa9d1dabc4230a3367eaa93cc9b"
},
{
"message": "fix(v2r2c1): isolate model api staging port",
"oid": "7564363a30bfa7fa81ac5c7c545ec704984ec543",
"parent_oid": "7c4e86bfc1f39e643e672968d1fdedbd72ace56d"
},
{
"message": "fix(v2r2c1): bind baseline authority and openapi staging path",
"oid": "2c6293160ed567a2098715edbb296741577c5bff",
"parent_oid": "7564363a30bfa7fa81ac5c7c545ec704984ec543"
}
],
"main_base_oid": "2e8fa22989808a448065d1d76b5b18948774a637",
"release_target_oid": "998d30304787e31e5ae607c0bd86b5edb2b0e5ad",
"remote_proof": {
"api_branch_oid": "2c6293160ed567a2098715edbb296741577c5bff",
"api_verification": "paginated_exact_name",
"commit_visible": true,
"git_remote_oid": "2c6293160ed567a2098715edbb296741577c5bff",
"main_api_oid": "2e8fa22989808a448065d1d76b5b18948774a637",
"main_git_oid": "2e8fa22989808a448065d1d76b5b18948774a637"
},
"report_commit_parent_oid": "2c6293160ed567a2098715edbb296741577c5bff",
"repository": "aios/aios",
"schema": "aios.v2r2c1.post-release-mainline-convergence.path-manifest.v1",
"task_id": "TASK-P0-V2R2C1-POST-RELEASE-MAINLINE-CONVERGENCE-20260809"
}
@@ -0,0 +1,37 @@
{
"artifact_sha256": "492338b895b152a55015fd92ea1d9dba2314894ab00c0ba219a78f3cef544b8a",
"baseline_authority_path": "/var/lib/aios-gitops-runtime-anchor/baseline/current.json",
"checks": {
"artifact_sha256": "PASS",
"baseline_registry_readback": "PASS",
"health_contract": "PASS",
"legacy_write_rejection": "PASS",
"live_service_unchanged": "PASS",
"manifest_binding": "PASS",
"openapi_readback": "PASS",
"restart_recovery": "PASS"
},
"current_live_port": 18890,
"current_live_service": "aios-gitops-model-api.service",
"deployment_state": "STAGING",
"execute": false,
"governed_release_routing": "bridge-v2",
"legacy_global_writes_enabled": false,
"live_cutover": false,
"owner_actions_required": [
"review exact source OID and artifact SHA",
"approve a separate future live model-api cutover",
"approve any production service transition"
],
"production_restart": false,
"repository_identity": "aios/aios",
"runtime_mode": "IMMUTABLE_STAGING",
"schema": "aios.v2r2c1.model-api-runtime-activation-plan.v1",
"source_oid": "2c6293160ed567a2098715edbb296741577c5bff",
"staging_port": 18893,
"staging_runtime_root": "/opt/aios/gitops-model-api/2c6293160ed567a2098715edbb296741577c5bff/runtime",
"staging_service": "aios-gitops-model-api-v2r2c1-staging.service",
"status": "OWNER_APPROVAL_REQUIRED",
"target_version": "V2R2C1",
"task_id": "TASK-P0-V2R2C1-POST-RELEASE-MAINLINE-CONVERGENCE-20260809"
}
@@ -0,0 +1,75 @@
# Action Contract Regression Remediation — 2026-08-25
## Scope
Governed task: `fix-action-contracts-20260825-1407`
Base: `3244f0f17f8845592ac94d585f5ebd65693c9f29`
Target branch: `agent/fix-action-contracts-20260825-1407`
## Verified regressions
1. `getReleaseConsistency` and `runReleaseAcceptance` fail before evaluation with:
`Gitea API URL must be HTTP(S)`.
V2R2C1 release evidence at `a8b906d1bc24088c19647e98de6a679268f6d0a2`
proves Gitea API readback passed for target `998d30304787e31e5ae607c0bd86b5edb2b0e5ad`.
Therefore current staging/contract overlay configuration has regressed.
2. `getReleaseSaga` fails with:
`Permission denied: '/var/lib/aios-gitops/silent-release'`.
Runtime service identity must have read/traverse access to the durable release state root.
Fix ownership/group/ACL at deployment time; do not broaden to world-writable permissions.
3. `getTagPublicationStatus` rejects the authoritative approved version `V2R2C1`
as `invalid release version`, while `getGovernedWriteCapabilities` returns the same
version as the approved release scope. The two Action contracts are internally inconsistent.
Release-version validation must accept the canonical AIOS `^V[0-9]+R[0-9]+C[0-9]+$`
namespace (or, preferably, use one shared validator for all release Actions).
4. `commitFilesGoverned` is unusable from the published Action schema.
Runtime capability metadata declares required fields:
`task_id, repository, expected_branch_oid, target_branch, generation, idempotency_key, files, message`,
but the exposed tool contract has no `files` input. Regenerate/publish the Action schema from
the same operation contract source and add a contract-parity test.
5. `reconcileGitOpsJob` returned `DURABLE_BINDING_INVALID` for a completed governed
single-file job that was otherwise read back as `COMPLETED`.
Reconciliation of a valid terminal job should be idempotent/read-only and must not require
a stale or absent active lease. Add a regression case for terminal completed jobs.
6. `getDiff` returns `ResponseTooLargeError` / `GIT_OUTPUT_LIMIT_EXCEEDED` for large diffs.
Add server-side bounded output: path/stat summary plus truncation metadata, or pagination.
The Action must never fail merely because a valid repository diff is large.
## Required implementation changes
- Centralize release-version parsing and reuse it in tag status, candidate, consistency, and acceptance.
- Validate/normalize Gitea API URL at service startup and fail health checks with a configuration-specific diagnostic.
- Ensure the deployment unit projects a valid HTTP(S) Gitea API base URL into the staging overlay.
- Correct durable release-state directory ownership/ACL for the runtime service account.
- Generate the external Action schema from the runtime operation contract; parity-test required fields.
- Make terminal reconciliation idempotent and independent of expired active writer leases.
- Bound diff output and return `truncated=true` with deterministic metadata rather than transport failure.
## Acceptance gates
All must pass on the same deployed overlay:
- `getReleaseConsistency(V2R2C1, 998d303..., a8b906d..., approved_plan)` returns a structured result, not URL error.
- `runReleaseAcceptance(...)` reaches policy evaluation, not URL error.
- `getTagPublicationStatus("V2R2C1")` returns structured publication state.
- `getReleaseSaga(nonexistent_candidate)` returns NOT_FOUND/empty-state semantics, never filesystem permission failure.
- Published `commitFilesGoverned` schema exposes `files` and a two-file isolated branch commit succeeds.
- Reconcile of a completed governed job is idempotent.
- Large diff returns bounded/truncated data rather than transport exception.
- Existing protected-ref controls remain unchanged: no direct main/tag/baseline/production write.
## Proven stable reference
V2R2C1 evidence closure (`a8b906d1bc24088c19647e98de6a679268f6d0a2`) records:
- Gitea Git/API readback PASS
- staging runtime PASS
- restart recovery PASS
- 59 mandatory tests passed / 0 failed
This evidence should be used as the compatibility baseline when repairing the current
`contract-overlay-v2` / `scheme-a-323b615b...` staging layer.
@@ -0,0 +1,80 @@
# AIOS Action contract repair evidence — 2026-08-25
## Disposition
Final disposition: `NEEDS_REWORK`.
The contract repair is deployed to staging and the focused acceptance checks are green. The task is not eligible for `SUCCEEDED`: `runReleaseAcceptance` returns structured policy output with `accepted=false` because the existing publication state for protected tag `V2R2C1` is `REMOTE_PUSH_PENDING`, and the repository-wide suite still has the two pre-existing signer/event-bus failures recorded below. The online Task API row is still `QUEUED`; it was not changed by direct database manipulation or an unauthorized synthetic claim/result.
No production release, `main`, release tag, baseline registry, or production service was directly modified.
## Scope and pre-change facts
- Repository: `aios/aios`; governed branch: `agent/fix-action-contracts-20260825-1407`.
- Initial task branch HEAD: `35eec9afe1432e8466d60040b2fb1049bb41242a`.
- Stable reference: `V2R2C1`; source commit `998d30304787e31e5ae607c0bd86b5edb2b0e5ad`; evidence closure `a8b906d1bc24088c19647e98de6a679268f6d0a2`.
- Pre-change refs: authority `main=3244f0f17f8845592ac94d585f5ebd65693c9f29`, tag `V2R2C1=07fc2746b688f5f243f29b39de39f32edc3dd9d2`; these remained unchanged.
- Staging services run as `User=aiman`, `Group=gitops`. The actual staging Gitea API base is `https://g.cnnetmkt.top/git/api/v1`, supplied by systemd environment rather than business-code host constants.
- Pre-change full test baseline: `707 passed, 2 skipped, 154 subtests passed, 2 failed`. The failures were the stale `/tmp/aios-agent-events-test` event-bus fixture and the real signer self-test.
## Root causes and repairs
1. Gitea URL validation occurred at release-operation time while the staging overlay's API environment was not consistently projected into all runtime paths. The canonical HTTP(S) validator is now called during model and Bridge startup; `getReleaseConsistency` and `runReleaseAcceptance` use the actual staging environment URL.
2. `silent-release` was not consistently writable by the real service identity, and the credential helper parent directory blocked `aiman` traversal. The persisted staging systemd overlay creates the state directory as `aiman:gitops` mode `0750` and grants only `u:aiman:--x` on `/usr/local/libexec`; no world-writable mode was used. The staging bridge also has the minimum repository write path required by the governed AuthorityGitExecutor/CAS flow.
3. Release version parsing was split across callers and rejected `V2R2C1`. A shared canonical parser now accepts `^V[0-9]+R[0-9]+C[0-9]+$` plus the existing bounded suffix form, and is used by capabilities, release candidate, consistency, acceptance, tag publication, and release/tag queries.
4. The runtime operation contract required `files` but the published Action schema omitted it. `commitFilesGoverned` now has one contract source and published schema projection, with parity tests. A real governed two-file commit completed on isolated branch `agent/action-contract-integration-20260825-1407`; authority, Git remote, and Gitea API all read back OID `0345cd8c70967940bc8e9132dff7d6ea11efa304`.
5. `reconcileGitOpsJob` reconstructed active lease state before checking terminal state. Terminal `COMPLETED`, `FAILED`, and `CANCELLED` jobs now short-circuit to idempotent read-back; active jobs retain strict lease validation. The two-file job reconciled as `COMPLETED` and a second reconcile returned the same receipt.
6. `getDiff` returned unbounded patch data and could fail the transport. It now returns identities, changed paths, stats, total bytes, bounded patch output, patch limit, and `truncated`. The large smoke returned `total_size_bytes=350222`, `stdout_bytes=262144`, `patch_limit_bytes=262144`, `truncated=true`; no `ResponseTooLargeError` or `GIT_OUTPUT_LIMIT_EXCEEDED` escaped.
During live integration, two additional staging-only contract defects were found and repaired: CREATE_NEW branch normalization/CAS recovery, and release executor projection of the configured staging baseline plus immutable runtime `versions/V2R2C1.json`. The latter removed the missing publication-workspace baseline/version errors without writing production state.
## Source, test, and operational changes
Source commits on the governed branch include the six contract repairs, focused regression tests, `versions/V2R2C1.json`, governed CREATE_NEW/reconcile recovery, and final runtime projection commits. The final source before this evidence commit is `2ec49005bde44f6981b14351500847d594e0276c`.
The runbook update is in `runbooks/agent-fix-action-contracts-20260825.md`. This report is the required durable evidence artifact. Existing untracked `runtime/agent-observability/` and `runtime/task-center/` directories were preserved and were not staged.
Tests run on the final source:
```text
PYTHONPATH=active-runtime/app python3 -m pytest -q tests/test_silent_release_execution_readiness.py tests/test_silent_release_controller.py tests/test_silent_release_hardening.py tests/test_silent_release_production_contract.py tests/test_chatgpt_actions_tag.py tests/test_gitea_tag_publication.py tests/test_authoritative_git_context.py
264 passed, 28 subtests passed in 4.78s
PYTHONPATH=active-runtime/app python3 -m pytest -q tests
717 passed, 2 skipped, 154 subtests passed, 2 failed in 33.98s
```
The full-suite failures remain the known event-bus fixture failure and the real signer adapter self-test (`returncode=42`); no new mandatory-suite regression was observed. Focused contract/reconcile tests and the exact mandatory suite passed.
## Staging deployment identity
- Final deployed source OID at the last completed runtime smoke: `2ec49005bde44f6981b14351500847d594e0276c`.
- Final deployed artifact: `/opt/aios/gitops-model-api/2ec49005bde44f6981b14351500847d594e0276c-action-contract-repair/runtime`.
- Artifact SHA256: `f67aed6cc0cce1f08197485c3eea204d3ef4d3989f44dc98e8ea2691e5886cbf`.
- Deployment state: `STAGING`; production-deployed flag: `false`.
- A lexically highest staging-only systemd drop-in was installed for each affected service, followed by `daemon-reload` and restart. `systemctl is-active` returned `active` for both services, and `systemctl show` confirmed the final OID, artifact SHA, API URL, and baseline authority.
## Runtime smoke evidence
- Model health returned `status=ok`, `version=V2R2C1`, final commit/artifact identity, `deployment_state=STAGING`, and `production_deployed=false`.
- Bridge/API readback was available; the bridge's `/health` path is not exposed by this runtime and returned `404`, while its governed endpoints were healthy.
- `getReleaseConsistency("V2R2C1", ...)` returned structured `status=PASS`, with Gitea listing/peeled target and tag object matching the stable reference.
- `runReleaseAcceptance(...)` entered policy evaluation and returned structured `accepted=false`; it did not fail URL validation. The independent pre-existing tag publication state is `REMOTE_PUSH_PENDING`, with the remote rejection explicitly reporting protected tag `V2R2C1`.
- `getTagPublicationStatus("V2R2C1")` returned structured `REMOTE_PUSH_PENDING`, not `invalid release version`.
- `getReleaseSaga` for a nonexistent candidate returned HTTP `404` with `status=NOT_FOUND`, `state=NOT_FOUND`, `execution_enabled=false`, and no filesystem permission error.
- `commitFilesGoverned` published schema includes required `files`, with per-file `path` and `content_base64`.
- Two-file governed commit: branch `agent/action-contract-integration-20260825-1407`, OID `0345cd8c70967940bc8e9132dff7d6ea11efa304` at authority/Git remote/API; protected-ref flags remained false.
- Terminal reconcile: `COMPLETED` read-back was idempotent across two calls; focused tests cover `FAILED` and `CANCELLED` terminal semantics.
- Small diff returned bounded non-truncated output; empty diff returned zero paths and zero bytes; large diff returned bounded/truncated output as recorded above.
- Main, protected tag, baseline registry, and production OIDs were re-read after deployment and remained unchanged.
## Restart persistence and remaining risk
The final staging services were restarted after deployment and health/runtime identity were re-read. The post-restart `systemctl is-active` result was `active` for both services; model health again reported OID `2ec49005bde44f6981b14351500847d594e0276c`, artifact SHA `f67aed6cc0cce1f08197485c3eea204d3ef4d3989f44dc98e8ea2691e5886cbf`, staging mode, and `production_deployed=false`. Post-restart consistency remained `PASS`, acceptance remained structured `accepted=false` for the pre-existing protected-tag publication state, saga remained `NOT_FOUND` for the nonexistent candidate, and the large diff remained bounded/truncated. The task cannot be promoted to `SUCCEEDED` until the protected-tag publication state and the two known repository-wide failures are resolved through their respective governed owners. No automatic rollback was performed because the staging services stayed healthy, mandatory tests passed, Gitea readback passed, and protected-ref boundaries remained intact.
Task disposition is `NEEDS_REWORK`, not `SUCCEEDED`: the staging contract repair is materially complete, but the deployed acceptance result is not all-green and the task row has not been legitimately claimed/verified by the online task worker.
@@ -0,0 +1,217 @@
# Agent Execution Contract — Action Contract Regression Repair
## Objective
Fully repair the current AIOS Action runtime/contract overlay regressions on the single server, without modifying protected refs directly and without introducing new infrastructure. The task is not complete until the deployed staging/action overlay passes all acceptance gates.
## Authority and scope
- Repository: `aios/aios`
- Base at task creation: `3244f0f17f8845592ac94d585f5ebd65693c9f29`
- Governed task: `fix-action-contracts-20260825-1407`
- Target branch: `agent/fix-action-contracts-20260825-1407`
- Protected refs must not be directly written: `main`, release tags, baseline registry, production runtime.
- No new SaaS, database, agent framework, or monitoring platform may be introduced.
## Non-interactive rules
The Agent must run the whole flow unnattended:
1. Collect facts.
2. Determine root causes.
3. Make the smallest correct changes.
4. Run unit/integration/contract tests.
5. Deploy to staging only.
6. Restart only the affected non-production services.
7. Run the full Action smoke suite.
8. On failure, automatically collect logs, diagnose, fix, and retry.
9. If a change causes a new regression, automatically roll back the staging change to the previous known-good configuration and record the failure.
10. Stop only if an owner decision is required for a high-risk, irreversible, or production-boundary action.
## Fact collection — required before editing
Record all of the following in the final evidence bundle:
- `id`, `hostname`, `uname -a`, `date --iso-seconds`
- `git rev-parse --show HEAD` for authority worktree and running overlay source
- `git status --porcelaine=v2`
- `git remote -v`
- current systemd service unit(s), drop-ins, Environment/EnvironmentFile sources for GitOps/Bredge/release services
- `systemctl show <affected-service>` including `User=`, `Group=`, `Environment=`, `EnvironmentFile=`, `ExecStart=`
- resolved Gitea API base URL after env/config expansion; redact secrets but not scheme/host
- `stat -c, `namei -l`, `getfacl`, als -ld on `/var/lib/aios-gitops/silent-release` and its parents
- find Action OpenAPI/schema generator and runtime operation definitions for `commitFilesGoverned`
- find the release version validator(s) and all callers of `getTagPublicationStatus`
- find `reconcileGitOpsJob` implementation and durable binding validation logic
- find `getDiff implementation and transport output limits
Do not edit any file until the actual implementation and configuration sources have been located.
## Repair 1 — Gitea API URL
Symptoms:
- `getReleaseConsistency` → `Gitea API URL must be HTTP(S)`
- `runReleaseAcceptance` → `Gitea API URL must be HTTP(ÊX
Required behavior:
- Determine which env/config key is projected into the current contract overlay.
- Compare the deployed overlay with V2R2C1 known-good configuration/deployment manifests.
- Restore a valid `HTTP://` or `HTTPS://` Gitea API base URL.
- Add startup config validation so invalid schemes fail at health/startup with a config-specific message, not during a release check.
- Never hard-code a different Gitea host if the existing deployment already has a single authoritative source.
## Repair 2 — durable release state permissions
Symptom:
- `getReleaseSaga` → `Permission denied: '/var/lib/aios-gitops/silent-release'`
Required behavior:
- Identify the actual runtime service User/Group.
- Grant only the required traverse/read/write permissions for the durable state directories the service must use.
- Prefer ownership + group mode or a specific ACL; keep the directory non-world-writable.
- Persist the permission in deployment/install logic so restart/reboot does not regress.
- Verify create/read/update of a staging-only saga state file under the service identity.
## Repair 3 — release version contract
Symptom:
- `getTagPublicationStatus("V2R2C1")` → `invalid release version`
- The same runtime exposes `V2R2C1` as `approved_release_version`.
Required behavior:
- Locate all version validators; consolidate onto one canonical parser.
- Accept the AIOS canonical namespace `^V[0-9]+R[0-9]+C[0-9]+$`.
- Preserve any additional already-supported legacy formats unless they are security-sensitive.
- Add contract tests proving that the version accepted by capabilities is accepted by status, candidate, consistency, acceptance, and tag publication.
## Repair 4 — `commitFilesGoverned` schema parity
Symptom:
- Runtime capability metadata requires `files`.
- Published Action tool schema does not expose `files`.
- Call reaches Bridge and fails `BRIDGE_REQUEST_FIELDS_MISSING`.
Required behavior:
- Find the canonical runtime operation contract.
- Generate the external tool schema from the same source of truth; do not manually copy a parallel schema.
- `files` must be required and must represent a list of file objects with at least `path` and `content_base64`.
- Add a parity test that compares runtime required fields with published Action required fields.
- Add a real governed commit test that writes two test files to an isolated `agent/` ibranch and verifies remote OID.
## Repair 5 — terminal reconciliation
Symptom:
- A governed job read back as `COMPLETED' but `reconcileGitOpsJob` failed `DURABLE_BINDING_INVALID`.
Required behavior:
- Reconciling a terminal successful job must be idempotent.
- An expired writer lease must not invalidate a durable binding for a job that is already terminal.
- Active-job reconciliation may require a live lease; terminal read-back must not.
- Add cases for COMPLETED, FAILED, CANCELLED / equivalent terminal states.
## Repair 6 — bounded `getDiff` output
Symptom:
- Large valid diffs fail with `ResponseTooLargeError` or `GIT_OUTPUT_LIMIT_EXCEEDED`.
Required behavior:
- Do not load unbounded diff text into the Action response.
- Return bounded summary data: commit identities, changed paths/stats, total bytes, and `truncated: true` when the full patch exceeds the safe limit.
- If pagination already exists in the contract use it; otherwise prefer deterministic truncation over a breaking schema change.
- Add tests for small, empty, and oversize diffs.
## Staging deployment procedure
1. Run the existing mandatory test suite before modification; record baseline.
2. Apply code and config changes only on the governed branch/worktree.
3. Run focused regression tests.
4. Run the full mandatory suite.
5. Build the staging contract overlay using the repository's existing build/deploy path.
6. Deploy to staging/non-production only.
7. If systemd units are changed: `systemctl daemon-reload`, then restart only the affected staging services.
8. Wait for the existing health endpoint/runtime status to become healthy.
9. Run the acceptance suite below.
If any step fails:
- collect `journalctl` logs and relevant app logs;
- diagnose root cause;
- automatically repair and retry when non-destructive;
- if a new change breaks staging, restore the previous staging artifact/config and restart the affected service;
- never silently continue after a failed gate.
## Acceptance suite
All of the following must pass on the same deployed staging overlay:
- Runtime status is `active` and healthy after restart.
- Gitea Git/API readback works from the runtime service identity.
- `getReleaseConsistency(V2R2C1, 998d30304787e31e5ae607c0bd86b5edb2b0e5ad, a8b906d1bc24088c19647e98de6a679268f6d0a2, approved_plan) returns a structured result and reaches evaluation.
- `runReleaseAcceptance` for the same identities reaches policy evaluation, not URL validation error.
- `getTagPublicationStatus("V2R2C1")` returns structured state, not `invalid release version`.
- `getReleaseSaga` for a nonexistent candidate returns NOT_FOUND/empty-state semantics, not filesystem permission failure.
- `commitFilesGoverned` exposes its required `files` parameter in the published Action schema.
- A real two-file governed commit to an isolated `agent/` branch succeeds and remote branch OID matches.
- Resuming a COMPLETED job returns the existing `JOB_TERMINAL` semantics or idempotent success as designed.
- Reconciling a COMPLETED governed job is successful/idempotent and does not require an active lease.
- Small/empty `getDiff still works.
- Large `getDiff` returns bounded output with truncation metadata instead of a transport exception.
- Existing protected-ref controls remain unchange: no direct main/tag/baseline/production write.
## Restart / reboot persistence
After the first full pass:
- Restart the staging Action/Bridge/release services one more time without changing files.
- Rerun the acceptance suite.
- Permissions, Gitea URL, and schema must still be correct after restart.
- If the repository already has a safe staging reboot test, run it as well.
## Evidence and completion
Create a single durable report in the repository, e.g. `reports/action-contract-repair-evidence-20260825.md`, containing:
- pre-change facts
- root cause for each regression
- files changed and why
- config/permission changes
- test commands and exact results
- staging deployment identity (commit/artifact/config digest)
- restart-persistence results
- final Action smoke results
- any remaining risks
Commit all source, test, deployment config, and runbook changes to the governed task branch. Do not mark the task SUCCEEDED until all acceptance gates pass on the actually deployed staging overlay.
## Automatic rollback conditions
Automatically roll back the staging deployment if any of the following occur:
- service fails to become healthy after restart
- protected-ref write boundary is broken
- Gitea readback that worked before the change stops working
- durable saga state cannot be read or recovered
- mandatory test suite regresses below the pre-change baseline
Do not automatically roll back an existing production release. Rollback here refers only to the staging overlay/service configuration changes made by this task.
## 2026-08-25 execution record
The repair branch was deployed through the existing immutable staging builder. The final source code before the evidence commit was `2ec49005bde44f6981b14351500847d594e0276c`; the deployed artifact was built from that source with SHA256 `f67aed6cc0cce1f08197485c3eea204d3ef4d3989f44dc98e8ea2691e5886cbf`. The final staging drop-ins use a lexically highest `zzzz...-999.conf` layer so they override earlier diagnostic overlays.
The runtime configuration is staging-only: `User=aiman`, `Group=gitops`, API base `https://g.cnnetmkt.top/git/api/v1`, baseline authority `/var/lib/aios-gitops/model-api-staging/baseline/current.json`, and read/write access to the staging state plus the authoritative bare repository required by the governed Writer/CAS path. No `main`, protected tag, baseline registry, or production write is enabled.
The release executor now binds both the configured staging baseline and the immutable runtime package's `versions/V2R2C1.json` projection. The bridge passes `AIOS_GITOPS_BASELINE_AUTHORITY` into `RuntimeConfig`; this prevents release consistency and acceptance from falling back to missing publication-workspace files.
The final acceptance result is recorded in `reports/action-contract-repair-evidence-20260825.md`. Contract smoke is green, and release consistency is `PASS`; release acceptance is structured policy output but remains `accepted=false` because the pre-existing publication state is `REMOTE_PUSH_PENDING` for the protected `V2R2C1` tag. The task must therefore remain `NEEDS_REWORK` until an authorized owner resolves that independent protected-tag state.
@@ -0,0 +1,311 @@
# Agent Execution Contract — V2R2C2 Control-Plane Convergence
## Objective
Complete the AIOS Phase -1 authority/runtime/contract convergence on the existing
single server with no interactive prompts. The source integration branch already
contains the staging-validated Action contract repair lineage plus the new minimal
convergence contracts. The Agent's job is to verify, finish the one remaining
release-publication reconciliation defect if it is still reproducible, build and
deploy **staging only**, prove the same-source runtime, and return machine evidence.
This is not authorization to modify production, protected tags, production baseline,
or `main` directly.
## Canonical identities
- Repository: `aios/aios`
- Authoritative main observed at task start:
`3244f0f17f8845592ac94d585f5ebd65693c9f29`
- Production baseline: `V2R2C1`
- Production target:
`998d30304787e31e5ae607c0bd86b5edb2b0e5ad`
- Production evidence closure:
`a8b906d1bc24088c19647e98de6a679268f6d0a2`
- Approval plan:
`sha256:c0df52008ae7f64cec5ee2d28947aa7b892066c670ab8295e4549140b1a7500e`
- Staging repair source lineage:
`174e64dd0218cef6d49dd13d58bbd90c78fecadd`
- Integration branch:
`agent/v2r2c2-convergence-integration-20260825`
- Integration branch OID at handoff:
`7526fef619edd8170c03c31a87e32cd1ea52a4ec`
## Code already completed
The integration branch includes the complete governed staging repair lineage rooted
at the authoritative main revision, including:
- canonical release-version validation;
- startup-safe Gitea API URL validation;
- operation contract metadata and `commitFilesGoverned` schema parity;
- bounded `getDiff`;
- idempotent terminal job reconciliation;
- CREATE_NEW branch CAS recovery;
- release baseline/version projection;
- external governed-read routing;
- canonical release consistency routing;
- durable NOT_FOUND release-saga semantics.
It also adds:
- `active-runtime/app/aios_convergence_contracts.py`
- `tests/test_convergence_contracts.py`
The new convergence contracts deliberately have no external side effects. They add:
- fail-closed `AcceptanceVector` / `AcceptanceResult`;
- domain-preserving final-writer ownership validation;
- `DesiredState` / `ObservedState`;
- pure reconciliation planning with stable idempotency identity;
- `SideEffectReceipt`;
- `ContinuationManifest`.
Local isolated validation before handoff: `8 passed`.
## Current observed runtime facts
At handoff:
- model API staging service is active at source
`174e64dd0218cef6d49dd13d58bbd90c78fecadd`;
- external `getControlPlaneSnapshot` is reachable;
- external `getGovernedWriteCapabilities` is reachable;
- `getReleaseConsistency(V2R2C1, ...)` returns `status=PASS` and all checks true;
- remote/local tag object is
`07fc2746b688f5f243f29b39de39f32edc3dd9d2`;
- peeled target is
`998d30304787e31e5ae607c0bd86b5edb2b0e5ad`;
- production health returns HTTP 200 and the same target/artifact identity;
- `runReleaseAcceptance` still returns `accepted=false` despite consistency PASS;
- `getTagPublicationStatus(V2R2C1)` currently returns `UNKNOWN`;
- `getControlPlaneSnapshot.runtime.runtime_release_commit` reports
`2ec49005bde44f6981b14351500847d594e0276c`, while the model API runtime
reports `174e64dd0218cef6d49dd13d58bbd90c78fecadd`.
Those last two facts are the remaining convergence defects.
## Non-interactive execution policy
Run continuously without asking the Owner to type "continue".
Automatically:
1. collect facts;
2. run tests;
3. diagnose failures;
4. make the smallest correct repair;
5. rebuild immutable staging artifacts;
6. deploy/restart only affected staging services;
7. observe external truth;
8. retry recoverable failures;
9. roll back newly introduced staging regressions;
10. produce final evidence.
Stop only for:
- authority/approval that cannot be satisfied by existing bindings;
- irreversible production change;
- protected-ref mutation;
- security-boundary change;
- genuine goal ambiguity;
- no-progress after bounded diagnosis/repair attempts.
## Phase A — pre-edit facts
Before any further edit, persist:
- current authoritative HEAD;
- integration branch OID and remote readback;
- `git merge-base --is-ancestor 3244f0f... 7526fef...`;
- staging service source/artifact identity for both model API and Bridge;
- systemd unit + drop-in ordering for both services;
- exact environment sources for baseline, Gitea API, state roots and PYTHONPATH;
- current release consistency, acceptance and tag-publication status;
- current control-plane snapshot;
- current governed-write capabilities operation contracts.
Do not trust a report if a direct runtime observation is available.
## Phase B — mandatory tests
Run at minimum:
```text
PYTHONPATH=active-runtime/app python3 -m pytest -q tests/test_convergence_contracts.py tests/test_action_contract_regressions.py tests/test_bridge_v2_governed_http_bootstrap.py
PYTHONPATH=active-runtime/app python3 -m pytest -q tests/test_silent_release_execution_readiness.py tests/test_silent_release_controller.py tests/test_silent_release_hardening.py tests/test_silent_release_production_contract.py tests/test_chatgpt_actions_tag.py tests/test_gitea_tag_publication.py tests/test_authoritative_git_context.py
```
Then run the repository-wide suite. Compare failures to the persisted pre-existing
baseline. No new regression is allowed.
## Phase C — release-publication truth reconciliation
Only execute this code repair if the following remains simultaneously true on the
same staging runtime:
```text
getReleaseConsistency.status == PASS
runReleaseAcceptance.accepted == false
getTagPublicationStatus(V2R2C1) in {UNKNOWN, REMOTE_PUSH_PENDING}
```
### Required semantics
Do **not** hard-code `accepted=true` and do not reinterpret UNKNOWN as PASS.
The publication controller must reconcile durable publication state from observed
external truth:
1. Resolve the approved version binding.
2. Read authoritative local tag object and peeled commit.
3. Read Gitea tag object and peeled commit.
4. Read registry/baseline approved tag object and target.
5. Compare all identities to the approved release binding.
6. If every required observation is present and exactly equal:
- persist the existing terminal-success publication state used by the codebase
(`PUBLISHED`/equivalent);
- mark the transition as observation-only/reconciled;
- record the observed tag object, peeled target, repository identity and evidence;
- perform **no tag create/push**.
7. If any identity differs:
- fail closed as drift/conflict;
- never mutate the protected tag.
8. If any required observation is unavailable:
- remain `UNKNOWN`/reconciling;
- do not mark accepted.
9. Replaying reconciliation must be idempotent and must not create a second
side effect.
`runReleaseAcceptance` may return accepted only after both:
```text
release consistency == PASS
AND reconciled publication state == terminal success
```
### Mandatory regression tests
Add focused tests for:
- missing local publication journal + exact observed remote tag -> terminal success;
- stale `REMOTE_PUSH_PENDING` + exact observed remote tag -> terminal success;
- remote tag object mismatch -> fail closed;
- peeled target mismatch -> fail closed;
- Gitea observation unavailable -> UNKNOWN, not accepted;
- replay after reconciled success -> identical durable receipt, no tag write/push;
- existing protected tag is never force-updated or deleted.
Use the existing `TagPublisher`/publication store and existing reconciliation/state
model. Do not create a second release database or a second release controller.
## Phase D — same-source staging convergence
Build from integration branch OID after tests. Deploy the same immutable artifact
lineage to every staging component participating in these Actions.
Acceptance requires the externally visible snapshot to stop presenting mixed
source identity. At minimum model API and Bridge must report the same intended
source/build identity or explicitly report component-level identities without
claiming a single aggregate runtime commit.
Do not hide component drift by overwriting the snapshot value.
Preferred fix if the snapshot currently has only one `runtime_release_commit`:
extent it to expose component identities, for example:
```text
runtime.components.model_api.source_oid
runtime.components.bridge.source_oid
runtime.aggregate_drift_status
```
Aggregate PASS is legal only if the required components are on the approved
compatible source set.
## Phase E — staging smoke
On the deployed staging artifact run:
- runtime status;
- control-plane snapshot;
- governed-write capabilities;
- release consistency;
- release acceptance;
- tag publication status;
- nonexistent release saga;
- bounded large diff;
- completed-job reconcile idempotency;
- isolated two-file governed commit on a throwaway `agent/` branch.
Required end state:
```text
release_consistency = PASS
release_acceptance.accepted = true
tag_publication = terminal success
control_plane_snapshot = reachable
governed_capabilities = reachable
runtime_component_drift = PASS
legacy_writes = false
force_push = false
main_write = false
tag_write = false
baseline_write = false
production_write = false
```
## Phase F — restart persistence
Restart only affected staging services, without changing files.
Rerun Phase E. All identities and outcomes must remain stable.
## Governance and branch handling
Do not directly modify `main`, `V2R2C1`, the production baseline registry or
production services.
The current Bridge writer binding has a known limitation: after a successful
single-file CAS commit, reacquiring the same task lease can return the original
expected branch OID, and rebinding the same task/generation can conflict.
Do not work around that with direct Git writes.
For source changes use the existing governed task/lease/commit flow. If the platform
requires one governed task binding per branch advance, create an explicitly named
follow-up governed task on the **same branch**, with:
- `base_oid` = previously verified branch OID;
- `expected_branch_oid` = same previously verified branch OID;
- same repository and branch;
- no protected-ref write.
Record this as `TASK_BINDING_EXPECTED_OID_NOT_ADVANCING` evidence.
## Final evidence
Commit a durable report:
`reports/v2r2c2-control-plane-convergence-evidence-20260825.md`
It must contain:
- pre-change facts;
- exact commits and changed paths;
- all test commands/results;
- release-publication reconciliation proof;
- proof that no protected tag write occurred during observation reconciliation;
- build artifact SHA;
- staging component source identities;
- pre/post restart smoke;
- branch authority/Git remote/API readback;
- remaining known risks;
- final disposition.
Do not declare `SUCCEEDED` unless every required end-state line in Phase E is
proven from the actually deployed staging runtime.
Production promotion is a separate Owner-approved release step and is **not** part
of this execution contract.
@@ -0,0 +1,90 @@
#!/usr/bin/env python3
"""Build a deterministic plan-bound production payload from one clean source tree."""
from __future__ import annotations
import argparse, hashlib, json, shutil, tarfile, tempfile
from pathlib import Path
SOURCE_FILES = (
"active-runtime/app/deployment_state.py",
"active-runtime/app/v2r1c6_privacy_tools.py",
"active-runtime/app/v2r2c2_health_service.py",
"deploy/aios-v2/systemd/aios-v2r2c2-production.service",
)
def canon(value: object) -> bytes:
return (json.dumps(value, sort_keys=True, separators=(",", ":")) + "\n").encode()
def digest(path: Path) -> str:
h = hashlib.sha256()
with path.open("rb") as f:
for block in iter(lambda: f.read(1024 * 1024), b""): h.update(block)
return h.hexdigest()
def require_oid(value: str, label: str) -> str:
if len(value) != 40 or any(c not in "0123456789abcdef" for c in value): raise SystemExit(label + "_invalid")
return value
def require_sha(value: str, label: str) -> str:
if len(value) != 64 or any(c not in "0123456789abcdef" for c in value): raise SystemExit(label + "_invalid")
return value
def build(source: Path, output: Path, *, version: str, source_oid: str, tree_oid: str,
rollback_version: str, rollback_target_oid: str, rollback_artifact_sha256: str,
service: str, port: int) -> dict[str, object]:
if not source.is_dir() or source.is_symlink(): raise SystemExit("source_directory_invalid")
require_oid(source_oid, "source_oid"); require_oid(tree_oid, "tree_oid")
require_oid(rollback_target_oid, "rollback_target_oid"); require_sha(rollback_artifact_sha256, "rollback_artifact_sha256")
if version != "V2R2C2" or rollback_version == version: raise SystemExit("release_version_binding_invalid")
if service != "aios-v2r2c2-production.service" or port != 18928: raise SystemExit("adapter_policy_binding_invalid")
if output.exists(): raise SystemExit("output_exists")
output.parent.mkdir(parents=True, exist_ok=True)
with tempfile.TemporaryDirectory(prefix="trusted-release-build-") as td:
payload = Path(td) / "payload"; payload.mkdir()
for rel in SOURCE_FILES:
source_file = source / rel
if not source_file.is_file() or source_file.is_symlink(): raise SystemExit("required_source_missing:" + rel)
dest = payload / rel; dest.parent.mkdir(parents=True, exist_ok=True); shutil.copyfile(source_file, dest)
(payload / "VERSION").write_text(version + "\n", encoding="utf-8")
build_meta = {"schema": "aios.trusted-release-artifact-build.v1", "version": version,
"source_commit": source_oid, "tree_oid": tree_oid, "production_deployed": False,
"tag_created": False, "baseline_changed": False, "adapter_service": service, "adapter_port": port}
(payload / "BUILD-METADATA.json").write_bytes(canon(build_meta))
rollback = {"schema": "aios.trusted-release-rollback.v1", "version": rollback_version,
"target_oid": rollback_target_oid, "artifact_sha256": rollback_artifact_sha256,
"automatic_rollback": False}
(payload / "ROLLBACK-METADATA.json").write_bytes(canon(rollback))
files = []
for item in sorted(p for p in payload.rglob("*") if p.is_file()):
if item.name != "ARTIFACT-MANIFEST.json":
files.append({"path": "payload/" + item.relative_to(payload).as_posix(), "sha256": digest(item), "size": item.stat().st_size})
manifest = {"schema": "aios.trusted-release-artifact-manifest.v1", "version": version,
"source_commit": source_oid, "tree_oid": tree_oid, "files": files,
"deployable": True, "production_deployed": False}
(payload / "ARTIFACT-MANIFEST.json").write_bytes(canon(manifest))
files.append({"path": "payload/ARTIFACT-MANIFEST.json", "sha256": digest(payload / "ARTIFACT-MANIFEST.json"), "size": (payload / "ARTIFACT-MANIFEST.json").stat().st_size})
tar_path = Path(td) / "artifact.tar"
with tarfile.open(tar_path, "w") as archive:
for item in sorted(p for p in payload.rglob("*") if p.is_file()):
info = archive.gettarinfo(str(item), arcname="payload/" + item.relative_to(payload).as_posix())
info.uid = 0; info.gid = 0; info.uname = "root"; info.gname = "root"; info.mtime = 0; info.mode = 0o644
with item.open("rb") as stream: archive.addfile(info, stream)
shutil.copyfile(tar_path, output)
receipt = {"schema": "aios.trusted-release-artifact-receipt.v1", "version": version,
"source_commit": source_oid, "tree_oid": tree_oid, "artifact_sha256": digest(output),
"artifact_size": output.stat().st_size, "manifest_sha256": hashlib.sha256(canon(manifest)).hexdigest(),
"package_closure_sha256": hashlib.sha256(canon({"source_commit": source_oid, "tree_oid": tree_oid, "files": files})).hexdigest(),
"production_deployed": False, "tag_created": False, "baseline_changed": False,
"service": service, "port": port}
output.with_suffix(".manifest.json").write_bytes(canon(receipt))
return receipt
def main() -> int:
p = argparse.ArgumentParser(); p.add_argument("--source", type=Path, required=True); p.add_argument("--output", type=Path, required=True)
p.add_argument("--version", required=True); p.add_argument("--source-oid", required=True); p.add_argument("--tree-oid", required=True)
p.add_argument("--rollback-version", required=True); p.add_argument("--rollback-target-oid", required=True); p.add_argument("--rollback-artifact-sha256", required=True)
p.add_argument("--service", required=True); p.add_argument("--port", type=int, required=True); a = p.parse_args()
result = build(a.source, a.output, version=a.version, source_oid=a.source_oid, tree_oid=a.tree_oid,
rollback_version=a.rollback_version, rollback_target_oid=a.rollback_target_oid,
rollback_artifact_sha256=a.rollback_artifact_sha256, service=a.service, port=a.port)
print(json.dumps(result, sort_keys=True)); return 0
if __name__ == "__main__": raise SystemExit(main())
+167
View File
@@ -0,0 +1,167 @@
#!/usr/bin/env python3
from __future__ import annotations
import argparse
import hashlib
import json
import os
from pathlib import Path
import sys
import tarfile
import tempfile
SCHEMA = 'aios.v2r2c1.model-api-staging-manifest.v1'
REQUIRED_APP = (
'aios_model_api_staging.py',
'aios_gitops_write_guard.py',
'authoritative_git.py',
'baseline_authority.py',
'gitea_tag_publication.py',
'silent_release_controller.py',
'silent_release_hardening.py',
'aios_gitops_job_controller.py',
'aios_chatgpt_bridge_v2.py',
'aios_chatgpt_bridge_v2_server.py',
'governed_production_release.py',
'v2r2c1_production_adapter.py',
'v2r1c7_production_adapter.py',
'aios_governed_task_bootstrap.py',
'aios_continuation_executor.py',
'aios_continuation_lease.py',
'aios_authority_git_executor.py',
'aios_gitops_reconciliation_core.py',
'authorized_task_writer_provisioning.py',
'signer_contract_compatibility.py',
)
OPTIONAL_APP = (
'aios_chatgpt_actions_api.py',
'aios_continuation_executor.py',
'aios_continuation_lease.py',
'aios_control_plane_snapshot.py',
)
def sha256(path: Path) -> str:
h = hashlib.sha256()
with path.open('rb') as fh:
for chunk in iter(lambda: fh.read(1024 * 1024), b''):
h.update(chunk)
return h.hexdigest()
def private_marker(path: Path) -> bool:
import re
text = path.read_text(encoding='utf-8', errors='ignore')
patterns = (
r'-----BEGIN [A-Z ]*PRIVATE KEY-----',
r'(?<![A-Za-z0-9])sk-[A-Za-z0-9_-]{20,}(?![A-Za-z0-9_-])',
r'(?<![A-Za-z0-9])ghp_[A-Za-z0-9]{20,}(?![A-Za-z0-9])',
r'(?i)aws_secret_access_key\s*[:=]\s*[A-Za-z0-9/+=]{16,}',
)
return any(re.search(pattern, text) for pattern in patterns)
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument('--source-root', required=True, type=Path)
ap.add_argument('--output-root', required=True, type=Path)
ap.add_argument('--source-oid', required=True)
args = ap.parse_args()
oid = args.source_oid.lower()
if len(oid) != 40 or any(c not in '0123456789abcdef' for c in oid):
raise SystemExit('source oid must be exact 40 hex')
source = args.source_root.resolve(strict=True)
output = args.output_root.resolve()
app = source / 'active-runtime' / 'app'
sys.path.insert(0, str(app))
from aios_chatgpt_bridge_v2 import render_openapi_contract_fragment
runtime = source / 'runtime' / 'agent-os'
docs = source / 'docs' / 'gitops'
if not app.is_dir():
raise SystemExit('active-runtime/app missing')
paths = []
root_version = source / 'release_version.py'
if not root_version.is_file():
raise SystemExit('required source dependency missing: release_version.py')
paths.append((Path('release_version.py'), root_version))
for name in REQUIRED_APP:
path = app / name
if not path.is_file():
raise SystemExit(f'required app dependency missing: {name}')
paths.append((Path('app') / name, path))
for name in OPTIONAL_APP:
path = app / name
if path.is_file():
paths.append((Path('app') / name, path))
for rel in ('orchestrator/__init__.py', 'orchestrator/action_monitor.py'):
path = runtime / rel
if not path.is_file():
raise SystemExit(f'required runtime dependency missing: {rel}')
paths.append((Path('runtime-agent-os') / rel, path))
openapi = docs / 'openapi-chatgpt-actions.yaml'
if openapi.is_file():
if ' /v2/commit-files:' not in openapi.read_text(encoding='utf-8'):
raise SystemExit('published OpenAPI is missing generated commitFilesGoverned contract')
paths.append((Path('docs') / 'gitops' / 'openapi-chatgpt-actions.yaml', openapi))
for folder in ('config/model_registry', 'config/model_priority', 'patches', 'versions'):
root = source / folder
if root.is_dir():
for path in sorted(root.rglob('*')):
if path.is_file() and path.suffix.lower() in {'.json', '.yaml', '.yml', '.md'}:
paths.append((Path(folder) / path.relative_to(root), path))
for rel, path in paths:
if private_marker(path):
raise SystemExit(f'private marker rejected: {rel}')
included = [str(rel).replace('\\', '/') for rel, _ in paths]
manifest = {
'schema': SCHEMA,
'target_version': 'V2R2C1',
'source_oid': oid,
'repository_identity': 'aios/aios',
'deployment_state': 'STAGING',
'runtime_mode': 'IMMUTABLE_STAGING',
'runtime_entrypoint': 'app/aios_model_api_staging.py',
'legacy_global_writes_enabled': False,
'governed_release_routing': 'bridge-v2',
'artifact_sha256': 'provided-by-build-receipt-and-install-manifest',
'included_paths': included,
}
output.mkdir(parents=True, exist_ok=True)
archive = output / f'aios-v2r2c1-model-api-{oid}.tar.gz'
with tempfile.TemporaryDirectory(prefix='v2r2c1-model-api-', dir=str(output)) as tmp:
root = Path(tmp) / 'runtime'
for rel, path in paths:
target = root / rel
target.parent.mkdir(parents=True, exist_ok=True)
target.write_bytes(path.read_bytes())
(root / 'manifest.json').write_text(json.dumps(manifest, ensure_ascii=False, indent=2, sort_keys=True) + '\n', encoding='utf-8')
with tarfile.open(archive, 'w:gz', format=tarfile.PAX_FORMAT) as tf:
for path in sorted(root.rglob('*')):
rel = path.relative_to(root.parent)
info = tf.gettarinfo(str(path), arcname=str(rel).replace('\\', '/'))
info.mtime = 0
info.uid = 0
info.gid = 0
info.uname = 'root'
info.gname = 'root'
if path.is_file():
with path.open('rb') as fh:
tf.addfile(info, fh)
else:
tf.addfile(info)
digest = sha256(archive)
receipt = {
'schema': 'aios.v2r2c1.model-api-staging-build-receipt.v1',
'target_version': 'V2R2C1',
'source_oid': oid,
'repository_identity': 'aios/aios',
'artifact_path': str(archive),
'artifact_sha256': digest,
'deployment_state': 'STAGING',
'manifest_schema': SCHEMA,
'included_paths': included,
}
receipt_path = output / f'aios-v2r2c1-model-api-{oid}.receipt.json'
receipt_path.write_text(json.dumps(receipt, ensure_ascii=False, indent=2, sort_keys=True) + '\n', encoding='utf-8')
print(json.dumps(receipt, ensure_ascii=False, indent=2, sort_keys=True))
return 0
if __name__ == '__main__':
raise SystemExit(main())
+10 -4
View File
@@ -9,10 +9,16 @@ WORKSPACE="${AIOS_GITOPS_WORKSPACE:-/data/aios-gitops/workspace}"
TAG_REMOTE="${AIOS_GITOPS_GITEA_REMOTE:-gitea}"
BRANCH_REMOTE="${AIOS_GITOPS_BRANCH_REMOTE:-gitea}"
case "$VERSION" in
V[0-9]*R[0-9]*C[0-9]*) ;;
*) echo "ERROR: version must match VxRxCx" >&2; exit 64 ;;
esac
SCRIPT_ROOT="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
if ! PYTHONPATH="$SCRIPT_ROOT:${PYTHONPATH:-}" python3 - "$VERSION" <<'PY'
import sys
from release_version import validate_release_version
validate_release_version(sys.argv[1])
PY
then
echo "ERROR: version must match VxRxCx" >&2
exit 64
fi
for value in "$TARGET_OID" "$EVIDENCE_CLOSURE_OID"; do
[[ "$value" =~ ^[0-9a-f]{40}$ ]] || {
@@ -1,8 +1,9 @@
from __future__ import annotations
import hashlib,json,re,tarfile
from release_version import RELEASE_VERSION_RE
from pathlib import Path
from typing import Any
OID_RE=re.compile(r"^[0-9a-f]{40}$"); SHA_RE=re.compile(r"^[0-9a-f]{64}$"); VERSION_RE=re.compile(r"^V2R1C[0-9]+$")
OID_RE=re.compile(r"^[0-9a-f]{40}$"); SHA_RE=re.compile(r"^[0-9a-f]{64}$"); VERSION_RE=RELEASE_VERSION_RE
REQUIRED_SOURCE={"schema","version","repository","base_ref","base_oid","binding_mode","release_readiness","production_deployed","tag_created","baseline_changed","production_service","production_port","rollback_metadata","controller_schema","required_checks"}
REQUEST_KEYS={"schema","version","repository","base_oid","target_oid","artifact_uri","artifact_sha256","artifact_size","evidence_closure_oid","approval_plan_id","execute","production_deployed","production_service","production_port","rollback_metadata","current_tag_oid","source_manifest_sha256"}
class ManifestValidationError(ValueError):
@@ -62,7 +63,7 @@ def validate_release_request(source_manifest:dict[str,Any],request_path:Path,exp
if not isinstance(request["approval_plan_id"],str) or not request["approval_plan_id"].startswith("sha256:"): raise ManifestValidationError("APPROVAL_PLAN","invalid approval plan")
if request["execute"] is not False or request["production_deployed"] is not False: raise ManifestValidationError("EXECUTION_NOT_ALLOWED","must be non-executing")
if request["production_service"]!=source_manifest["production_service"] or request["production_port"]!=source_manifest["production_port"]: raise ManifestValidationError("SERVICE_IDENTITY","request service mismatch")
if not isinstance(request["current_tag_oid"],str) or request["current_tag_oid"] not in {"V2R1C6","9e65bd59baea392294c0f253046d3ab116f63145"}: raise ManifestValidationError("PROTECTED_TAG","previous tag binding changed")
if not isinstance(request["current_tag_oid"],str) or request["current_tag_oid"] not in {"V2R1C7","82792347937c0df59a8e02d6162775252bcad2e2"}: raise ManifestValidationError("PROTECTED_TAG","previous production tag binding changed")
_sha(request["source_manifest_sha256"],"source_manifest_sha256"); return request
def build_approval_request(*,source_manifest_path:Path,target_oid:str,artifact_uri:str,artifact_sha256:str,artifact_size:int,evidence_closure_oid:str,rollback_metadata:str,current_tag_oid:str)->dict[str,Any]:
source=_json_object(source_manifest_path); _oid(target_oid,"target_oid"); _sha(artifact_sha256,"artifact_sha256"); _oid(evidence_closure_oid,"evidence_closure_oid"); request={"schema":"aios.release-request.v2","version":source["version"],"repository":source["repository"],"base_oid":source["base_oid"],"target_oid":target_oid,"artifact_uri":artifact_uri,"artifact_sha256":artifact_sha256,"artifact_size":artifact_size,"evidence_closure_oid":evidence_closure_oid,"execute":False,"production_deployed":False,"production_service":source["production_service"],"production_port":source["production_port"],"rollback_metadata":rollback_metadata,"current_tag_oid":current_tag_oid,"source_manifest_sha256":sha256_file(source_manifest_path)}; request["approval_plan_id"]="sha256:"+sha256_bytes(_canonical(request)); return request
@@ -3,13 +3,13 @@ from __future__ import annotations
import json
from pathlib import Path
ROOT = Path(__file__).parents[2]
def test_version_and_candidate_binding():
assert (ROOT / "VERSION").read_text(encoding="utf-8").strip() == "V2R1C6"
assert (ROOT / "VERSION").read_text(encoding="utf-8").strip() == "V2R1C7"
metadata = json.loads((ROOT / "versions/V2R1C6.json").read_text(encoding="utf-8"))
assert metadata["version"] == "V2R1C6"
assert metadata["base_oid"] == "a33e2593967a180c89d44454350ccaeaf3f79b25"
assert metadata["candidate_branch"] == "agent/v2r1c6-mainline-reconciliation-20260806"
assert metadata["production_deployed"] is False
@@ -7,17 +7,19 @@ import pytest
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT / "active-runtime" / "app"))
from deployment_state import RuntimeDeploymentState
from v2r1c6_privacy_tools import health_payload, serve # noqa: E402
def test_health_is_engineering_only() -> None:
result = health_payload()
assert result == {
"status": "ok",
"version": "V2R1C6",
"production_deployed": False,
"gitops_writes_enabled": False,
}
result = health_payload(RuntimeDeploymentState("V2R1C7", "a" * 40, "b" * 64, "STAGED"))
assert result["status"] == "ok"
assert result["version"] == "V2R1C7"
assert result["commit_oid"] == "a" * 40
assert result["artifact_sha256"] == "b" * 64
assert result["deployment_state"] == "STAGED"
assert result["production_deployed"] is False
assert result["health_timestamp"]
def test_listener_rejects_non_loopback() -> None:
+65
View File
@@ -0,0 +1,65 @@
from __future__ import annotations
import importlib.util
import sys
import types
from pathlib import Path
REPOSITORY_ROOT = Path(__file__).resolve().parents[1]
TEST_ROOT = REPOSITORY_ROOT / "tests"
SERVICE_PACKAGE_ROOT = REPOSITORY_ROOT / "services" / "aios-autonomous-release-candidate"
ACTIVE_RUNTIME_ROOT = REPOSITORY_ROOT / "active-runtime"
ACTIVE_RUNTIME_APP = ACTIVE_RUNTIME_ROOT / "app"
for path in (TEST_ROOT, SERVICE_PACKAGE_ROOT, ACTIVE_RUNTIME_APP):
value = str(path)
if value not in sys.path:
sys.path.insert(0, value)
def load_module(relative: str):
path = REPOSITORY_ROOT / relative
spec = importlib.util.spec_from_file_location("v2r1c6_privacy_tools", path)
if spec is None or spec.loader is None:
raise RuntimeError("module_load_failed")
module = importlib.util.module_from_spec(spec)
sys.modules[spec.name] = module
spec.loader.exec_module(module)
return module
def _install_active_runtime_namespace() -> None:
package = sys.modules.get("active_runtime")
if package is None:
package = types.ModuleType("active_runtime")
package.__path__ = [str(ACTIVE_RUNTIME_ROOT)]
package.__package__ = "active_runtime"
sys.modules["active_runtime"] = package
app = sys.modules.get("active_runtime.app")
if app is None:
app = types.ModuleType("active_runtime.app")
app.__path__ = [str(ACTIVE_RUNTIME_APP)]
app.__package__ = "active_runtime"
sys.modules["active_runtime.app"] = app
package.app = app
def _install_legacy_test_module(name: str, relative: str) -> None:
if name in sys.modules:
return
path = REPOSITORY_ROOT / relative
spec = importlib.util.spec_from_file_location(name, path)
if spec is None or spec.loader is None:
raise RuntimeError("legacy_test_module_load_failed:" + name)
module = importlib.util.module_from_spec(spec)
sys.modules[name] = module
spec.loader.exec_module(module)
_install_active_runtime_namespace()
sys.modules.setdefault("conftest", sys.modules[__name__])
_install_legacy_test_module(
"test_native_adapter_contracts",
"tests/autonomous_release/test_native_adapter_contracts.py",
)
+118
View File
@@ -0,0 +1,118 @@
from __future__ import annotations
import hashlib
import json
import pytest
from aios_gitops_job_controller import GitOpsJobController, JobError, JobState, JobStore
HEAD = "a" * 40
BASE = "b" * 40
CURRENT = "c" * 40
def _controller(tmp_path):
store = JobStore(tmp_path / "jobs", payload_root=tmp_path / "payloads")
return GitOpsJobController(store), store
def _request():
payload = b"mission-recovery-test"
return {
"campaign_id": "AIOS-AUTONOMOUS-RELEASE-CAMPAIGN-20260807",
"repository": "aios-gitops",
"operation": "PROTECTED_MERGE",
"branch": "agent/recovery-test",
"expected_branch_oid": HEAD,
"expected_main_oid": BASE,
"payload_digest": hashlib.sha256(payload).hexdigest(),
"pr_number": 53,
"task_id": "AIOS_AUTONOMOUS_RELEASE_CAMPAIGN_V1",
}, payload
def _proof(proof_job_id: str, **overrides):
proof = {
"proof_schema": "aios.job-recovery.v1",
"job_id": proof_job_id,
"operation": "PROTECTED_MERGE",
"pr_number": 53,
"pr_merged": True,
"protected_merge": True,
"head_oid": HEAD,
"base_oid": BASE,
"main_oid_after_protected_merge": HEAD,
"gitea_main_oid_after_protected_merge": HEAD,
"authority_main_oid_after_protected_merge": HEAD,
"current_gitea_main_oid": CURRENT,
"current_authority_main_oid": CURRENT,
"current_main_descends_from_protected_merge": True,
"force_push": False,
"machine_actor": "aios-autonomy-agent",
}
proof.update(overrides)
return proof
def _manual_job(tmp_path):
controller, store = _controller(tmp_path)
request, payload = _request()
submitted = controller.submit(request, payload=payload)
manual = controller.advance(
submitted.job_id,
JobState.MANUAL_OWNER_REVIEW_REQUIRED,
side_effect_state="MANUAL_OWNER_REVIEW_REQUIRED",
receipt={"historical": "preserve-me"},
expected_revision=submitted.revision,
)
return controller, store, manual
def test_verified_external_recovery_preserves_history_and_is_idempotent(tmp_path):
controller, store, manual = _manual_job(tmp_path)
proof = _proof(manual.job_id)
completed = controller.complete_verified_external_side_effect(
manual.job_id, proof, expected_revision=manual.revision
)
assert completed.state is JobState.COMPLETED
assert completed.side_effect_state == "COMPLETED"
receipt = json.loads(store.read(manual.job_id)["receipt_json"])
assert receipt["recovered_from_historical_manual"] is True
assert receipt["historical_receipt"] == {"historical": "preserve-me"}
assert receipt["proof"] == proof
duplicate = controller.complete_verified_external_side_effect(manual.job_id, proof)
assert duplicate.state is JobState.COMPLETED
assert duplicate.revision == completed.revision
@pytest.mark.parametrize(
"field,value",
[
("job_id", "job-wrong"),
("head_oid", "d" * 40),
("main_oid_after_protected_merge", "d" * 40),
("force_push", True),
("operation", "PUSH"),
],
)
def test_verified_external_recovery_rejects_mismatched_proof(tmp_path, field, value):
controller, _store, manual = _manual_job(tmp_path)
proof_job_id = value if field == "job_id" else manual.job_id
overrides = {} if field == "job_id" else {field: value}
with pytest.raises(JobError):
controller.complete_verified_external_side_effect(
manual.job_id, _proof(proof_job_id, **overrides)
)
assert controller.store.read(manual.job_id)["state"] == JobState.MANUAL_OWNER_REVIEW_REQUIRED.value
def test_ordinary_terminal_advance_and_resume_remain_denied(tmp_path):
controller, _store, manual = _manual_job(tmp_path)
with pytest.raises(JobError, match="JOB_TERMINAL"):
controller.advance(manual.job_id, JobState.COMPLETED, side_effect_state="COMPLETED")
with pytest.raises(JobError, match="JOB_TERMINAL"):
controller.resume(manual.job_id)
@@ -0,0 +1,78 @@
import subprocess
from pathlib import Path
import pytest
from governance.ref_reconciler import RefReconciler, RefReconciliationError
from governance.semantic_protection import semantic_digest, semantic_policy, transport_digest
def protection(**overrides):
value = {
"branch_name": "main",
"enable_push": False,
"enable_force_push": False,
"enable_merge_whitelist": True,
"merge_whitelist_usernames": ["owner"],
"required_approvals": 0,
"enable_status_check": True,
"status_check_contexts": ["owner-approval-gate", "py-compile"],
"block_admin_merge_override": True,
"block_on_official_review_requests": True,
"block_on_outdated_branch": True,
"block_on_rejected_reviews": True,
"dismiss_stale_approvals": True,
"ignore_stale_approvals": False,
"enable_approvals_whitelist": True,
"approvals_whitelist_username": ["owner"],
"enable_bypass_allowlist": False,
"enable_force_push_allowlist": False,
"enable_push_whitelist": False,
"protected_file_patterns": "[]",
"unprotected_file_patterns": "[]",
}
value.update(overrides)
return value
def test_transport_metadata_does_not_change_semantic_digest():
before = protection(updated_at="old")
after = protection(updated_at="new")
assert transport_digest(before) != transport_digest(after)
assert semantic_digest(before) == semantic_digest(after)
def test_stage1_whitelist_is_a_semantic_change():
before = protection()
after = protection(merge_whitelist_usernames=["owner", "aios-autonomy-agent"])
assert semantic_policy(before)["merge_whitelist_user_ids"] == [2]
assert semantic_policy(after)["merge_whitelist_user_ids"] == [2, 153]
assert semantic_digest(before) != semantic_digest(after)
def test_ref_reconciler_rejects_gitea_mismatch(tmp_path: Path):
reconciler = RefReconciler(tmp_path / "missing.git")
with pytest.raises(RefReconciliationError, match="GITEA_MAIN_EXPECTATION_MISMATCH"):
reconciler.reconcile_main(expected_oid="a" * 40, merged_oid="b" * 40, gitea_main_oid="c" * 40)
def test_ref_reconciler_ff_cas_and_idempotency(tmp_path: Path):
repo = tmp_path / "repo.git"
subprocess.run(["git", "init", "--bare", str(repo)], check=True, capture_output=True)
worktree = tmp_path / "work"
subprocess.run(["git", "clone", str(repo), str(worktree)], check=True, capture_output=True)
subprocess.run(["git", "-C", str(worktree), "config", "user.email", "test@example.invalid"], check=True)
subprocess.run(["git", "-C", str(worktree), "config", "user.name", "test"], check=True)
(worktree / "one.txt").write_text("one\n", encoding="utf-8")
subprocess.run(["git", "-C", str(worktree), "add", "one.txt"], check=True)
subprocess.run(["git", "-C", str(worktree), "commit", "-m", "one"], check=True, capture_output=True)
old = subprocess.check_output(["git", "-C", str(worktree), "rev-parse", "HEAD"], text=True).strip()
subprocess.run(["git", "-C", str(worktree), "push", "origin", "HEAD:refs/heads/main"], check=True, capture_output=True)
(worktree / "two.txt").write_text("two\n", encoding="utf-8")
subprocess.run(["git", "-C", str(worktree), "add", "two.txt"], check=True)
subprocess.run(["git", "-C", str(worktree), "commit", "-m", "two"], check=True, capture_output=True)
new = subprocess.check_output(["git", "-C", str(worktree), "rev-parse", "HEAD"], text=True).strip()
subprocess.run(["git", "-C", str(worktree), "push", "origin", "HEAD:refs/heads/main"], check=True, capture_output=True)
reconciler = RefReconciler(repo)
assert reconciler.reconcile_main(expected_oid=old, merged_oid=new, gitea_main_oid=new) == new
assert reconciler.reconcile_main(expected_oid=old, merged_oid=new, gitea_main_oid=new) == new

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