Compare commits

...
Author SHA1 Message Date
codex 8814dff202 Merge pull request '[TASK SCOPED GOVERNANCE BOOTSTRAP RENEWAL] AIOS Two-Stage Owner Campaign Phase A' (#35) from governance/request/two-stage-owner-campaign-v2-phase-a-renewal-20260731T081147Z into governance/approvals 2026-07-31 17:35:54 +08:00
Codex Governance Request ff6743e851 governance: renew two-stage phase-a activation window
aios/branch-protection PR35 Phase A PASS evidence_sha256=47068a6193db3a257d4af7d7c03e5b29e6ab3b7b146e262efb05e704b158bdfc
aios/evidence-closure PR35 Phase A PASS evidence_sha256=247b155c55a47addee9485e9005da7e0069c0c1c5d977e45dadb3f7da9caf514
aios/secret-scan PR35 Phase A PASS evidence_sha256=e9d0646d237cb0e5ee0456e778571ea81f470b621a5317d37c510804332a2672
aios/state-machine PR35 Phase A PASS evidence_sha256=c14af062600f46b708124c854ad1c2aaad67ec47dd76caca8ee1ae71b9af6d09
aios/validator PR35 Phase A PASS evidence_sha256=b4d4f5a559e411bd5173d6d076cb484e3983ef29db14b778ffd49d889ddebcda
governance/authority-mirror PR35 Phase A PASS evidence_sha256=568f582e530d1cea4af3a117ed98e041462ca9011d4bfe2fafe7a67fd4688103
governance/fault-injection PR35 Phase A PASS evidence_sha256=e1a2551809ae5800eb701c0ae9a5862f3dae166a43ffc8acab68aa218728a6b4
governance/runtime-isolation PR35 Phase A PASS evidence_sha256=e5be2a28129c65d020f7221fc2076550d029187b0cc02c7100fb02574787cb12
governance/schema-contract PR35 Phase A PASS evidence_sha256=2b7e282c41a7e8bd02a85181d8483623449021bed3c8d9890aedaf2f550ab0d4
governance/validator-security PR35 Phase A PASS evidence_sha256=1abe11b6fe05a5318f8cf133749d62bd28292d6252f0c61726f7b2efd918c887
2026-07-31 16:17:30 +08:00
codex ebd7439874 Merge pull request '[SINGLE OWNER LIVE ACTIVATION HARDENING] Implementation and Execution' (#33) from governance/request/single-owner-live-activation-hardening-20260729T043149Z into governance/approvals
Reviewed-on: #33
Reviewed-by: gpt <gpt@imruyi.top>
Reviewed-by: codex <codex@imruyi.top>
2026-07-29 12:45:22 +08:00
Codex Agent c41e278c59 governance: approve live activation hardening
aios/branch-protection AIOS governed hardening gate PASS
aios/evidence-closure AIOS governed hardening gate PASS
aios/secret-scan AIOS governed hardening gate PASS
aios/state-machine AIOS governed hardening gate PASS
aios/validator AIOS governed hardening gate PASS
2026-07-29 12:31:49 +08:00
gpt 1071979079 Merge pull request '[SINGLE OWNER LIVE ACTIVATION] Implementation and Execution' (#32) from governance/request/single-owner-governance-live-activation-20260729T033228Z into governance/approvals
Reviewed-on: #32
Reviewed-by: codex <codex@imruyi.top>
Reviewed-by: gpt <gpt@imruyi.top>
2026-07-29 11:42:55 +08:00
Codex Agent 9a158fcab9 governance: request single-owner live activation 2026-07-29 11:32:28 +08:00
gpt 8b7f9d9028 Merge pull request '[OWNER RATIFICATION] PR #30 Single-Owner Atomic Activation' (#31) from governance/request/single-owner-atomic-activation-owner-ratification-20260729T014918Z-e503d96665ee into governance/approvals
Reviewed-on: #31
Reviewed-by: codex <codex@imruyi.top>
Reviewed-by: gpt <gpt@imruyi.top>
2026-07-29 09:57:54 +08:00
AIOS Governance Requester 3aa1872084 governance: request PR30 owner ratification 2026-07-29 09:49:19 +08:00
gpt f6bef90633 Merge pull request '[SINGLE OWNER GOVERNANCE ATOMIC ACTIVATION] Activate Machine-Gated Owner Policy' (#30) from governance/request/single-owner-governance-atomic-activation-20260728T204808Z-4cab1d5829d6 into governance/approvals
Reviewed-on: #30
Reviewed-by: codex <codex@imruyi.top>
Reviewed-by: gpt <gpt@imruyi.top>
2026-07-29 09:25:08 +08:00
AIOS Governance Requester c2eed5b379 governance: request atomic single-owner activation 2026-07-29 04:48:09 +08:00
codex 6e8212608e Merge pull request '[SINGLE OWNER ACTIVATION PREREQUISITES] Safe Atomic Control Plane' (#29) from governance/request/single-owner-governance-activation-prerequisites-20260728T183025127107Z into governance/approvals
Reviewed-on: #29
Reviewed-by: gpt <gpt@imruyi.top>
Reviewed-by: codex <codex@imruyi.top>
2026-07-29 02:49:26 +08:00
AIOS Activation Prerequisites Requester e6a53888fe governance: request activation prerequisites 2026-07-29 02:30:25 +08:00
gpt 2bef44f6bc Merge pull request '[FINAL OWNER RATIFICATION] Single Owner Governance Activation' (#28) from governance/request/single-owner-governance-final-owner-ratification-20260728T173634412838Z into governance/approvals
Reviewed-on: #28
Reviewed-by: codex <codex@imruyi.top>
Reviewed-by: gpt <gpt@imruyi.top>
2026-07-29 01:46:57 +08:00
AIOS Final Owner Ratification Requester a96f36bf72 governance: request final owner ratification 2026-07-29 01:36:35 +08:00
gpt 4e6ef70029 Merge pull request '[OWNER RATIFICATION + CONTROL PLANE] Single Owner Governance Activation' (#27) from governance/request/single-owner-control-plane-deployment-20260728T170740Z-ea7a102dac417c23 into governance/approvals
Reviewed-on: #27
Reviewed-by: codex <codex@imruyi.top>
Reviewed-by: gpt <gpt@imruyi.top>
2026-07-29 01:11:56 +08:00
AIOS Governance Recovery Requester 80feef309f governance: request owner ratification and control-plane deployment 2026-07-29 01:07:41 +08:00
gpt a5489f1b67 Merge pull request '[OWNER BREAK-GLASS GOVERNANCE RESET] Single Owner with Machine Gates' (#26) from governance/request/single-owner-governance-migration-20260728T162246Z into governance/approvals
Reviewed-on: #26
Reviewed-by: codex <codex@imruyi.top>
Reviewed-by: gpt <gpt@imruyi.top>
2026-07-29 00:39:54 +08:00
AIOS Governance Migration Requester 312d3b4830 governance: propose single-owner machine-gated model 2026-07-29 00:22:47 +08:00
gpt ac2dc5c30e Merge pull request '[CONTROL PLANE DEPLOYMENT] AIOS Integration Refresh Provisioner' (#25) from governance/request/integration-refresh-control-plane-deployment-20260728T093428Z into governance/approvals
Reviewed-on: #25
Reviewed-by: codex <codex@imruyi.top>
Reviewed-by: gpt <gpt@imruyi.top>
2026-07-28 18:35:26 +08:00
AIOS Governance Request Publisher 5ef261b265 governance: request integration refresh control-plane deployment 2026-07-28 17:34:29 +08:00
gpt 3a22c3c7d8 Merge pull request '[SOURCE MAPPING EXECUTION RENEWAL] AIOS Integration Refresh Runtime Sources' (#24) from governance/request/integration-refresh-source-mapping-execution-renewal-20260728T061044Z into governance/approvals
Reviewed-on: #24
Reviewed-by: gpt <gpt@imruyi.top>
Reviewed-by: codex <codex@imruyi.top>
2026-07-28 16:12:41 +08:00
chatgpt-gitops-bot 1c9f28a613 governance: renew source mapping execution window 2026-07-28 14:10:44 +08:00
gpt 9514062cf3 Merge pull request '[SOURCE CANONICALIZATION] AIOS Integration Refresh Runtime Sources' (#23) from governance/request/integration-refresh-source-canonicalization-20260727 into governance/approvals
Reviewed-on: #23
Reviewed-by: codex <codex@imruyi.top>
Reviewed-by: gpt <gpt@imruyi.top>
2026-07-28 02:41:45 +08:00
AIOS Governance Automation a701a32cb4 governance(approval): request source secret remediation 2026-07-27 18:36:27 +08:00
codex 45f3226662 Merge pull request '[IMPLEMENTATION APPROVAL RENEWAL] AIOS Integration Refresh Provisioner' (#22) from governance/request/integration-refresh-provisioner-implementation-renewal-20260727 into governance/approvals
Reviewed-on: #22
Reviewed-by: gpt <gpt@imruyi.top>
Reviewed-by: codex <codex@imruyi.top>
2026-07-27 14:07:37 +08:00
AIOS Governance Automation 975d239d40 governance(approval): renew Integration Refresh Provisioner implementation 2026-07-27 13:00:11 +08:00
codex 3bc44accb5 Merge pull request '[IMPLEMENTATION APPROVAL] AIOS Integration Refresh Provisioner' (#21) from governance/request/integration-refresh-provisioner-implementation-20260726 into governance/approvals
Reviewed-on: #21
2026-07-27 03:58:14 +08:00
gva1785094553523a25 3304d87872 governance(approval): request Integration Refresh Provisioner implementation 2026-07-27 03:35:56 +08:00
gitops-bot 875d41c310 Merge pull request 'fix(gitops): harden Codex worker systemd profile' (#19) from patch/isolated-writer-systemd-profile-hardening-20260725 into main 2026-07-25 11:13:31 +08:00
chatgpt-gitops-bot 870ac61dc2 fix(gitops): harden Codex worker systemd profile 2026-07-25 11:08:55 +08:00
gitops-bot 009f276699 Merge pull request 'fix(gitops): add exact-path service policy for isolated writer' (#18) from patch/isolated-writer-exact-path-policy-20260725 into main 2026-07-25 10:19:02 +08:00
chatgpt-gitops-bot 600c717e17 fix(gitops): add exact-path service policy for isolated writer 2026-07-25 10:16:43 +08:00
gitops-bot 02be6d28ed merge(gitops): add constrained writer token policy and atomic commit-file
Source: patch/isolated-writer-token-policy-v2-20260724
Source commit: b1e240dda3
Target base: df099420d2
Production runtime changes: not authorized
Tag/Baseline/Registry operations: not authorized
2026-07-24 23:36:25 +08:00
chatgpt-gitops-bot b1e240dda3 fix(gitops): reject atomic writer force fields 2026-07-24 23:06:22 +08:00
chatgpt-gitops-bot 2dfbe837f4 fix(gitops): bind writer runtime policy v2 artifacts 2026-07-24 23:01:40 +08:00
chatgpt-gitops-bot 80df1e0d87 test(gitops): close writer authz and recovery contract 2026-07-24 22:56:58 +08:00
chatgpt-gitops-bot 7ecf7cbfce feat(gitops): add idempotent atomic commit-file endpoint 2026-07-24 22:56:58 +08:00
chatgpt-gitops-bot bbada7c863 feat(gitops): add constrained writer token policy v2 2026-07-24 22:56:58 +08:00
gitops-bot df099420d2 Merge pull request 'feat(task-plane): add isolated readonly Codex worker policy' (#16) from patch/task-plane-codex-readonly-worker-20260724 into main 2026-07-24 21:51:12 +08:00
chatgpt-gitops-bot 2d802f3d51 feat(task-plane): add isolated readonly Codex worker policy 2026-07-24 21:31:08 +08:00
gitops-bot dd34dfaad2 Merge pull request 'fix(task-plane): authenticate shadow client and retire legacy writer' (#15) from patch/task-plane-shadow-rbac-client-20260724 into main 2026-07-24 20:25:17 +08:00
chatgpt-gitops-bot 346426b6ca fix(task-plane): authenticate shadow client and retire legacy writer 2026-07-24 19:51:16 +08:00
owner 967b90af91 merge(task-plane): add governed multi-agent shadow control plane
Source: patch/aios-agent-task-control-plane-20260724
Source commit: 2458fa5f28
Target base: a8e9786897
Production execution: disabled
Production deployment: not authorized
Production tag operations: not authorized
2026-07-24 13:57:01 +08:00
chatgpt-gitops-bot 2458fa5f28 test(task-plane): cover fail and recovery rollback 2026-07-24 12:50:57 +08:00
chatgpt-gitops-bot 40f2f3ebed fix(task-plane): default deny action rbac 2026-07-24 12:39:28 +08:00
chatgpt-gitops-bot 687112d86c test(task-plane): close shadow merge security contract 2026-07-24 12:32:59 +08:00
chatgpt-gitops-bot f0d1e8479b fix(task-plane): harden writer scheduling and classified tasks 2026-07-24 12:32:58 +08:00
chatgpt-gitops-bot 85b4610eb3 fix(task-plane): enforce worker ownership and recovery probes 2026-07-24 12:32:58 +08:00
chatgpt-gitops-bot a0b5160534 fix(task-plane): bind evidence and mutations atomically 2026-07-24 12:25:42 +08:00
chatgpt-gitops-bot cf57ef6bcc docs(task-plane): declare authenticated error contract 2026-07-24 11:29:00 +08:00
chatgpt-gitops-bot 958e49e2cf style(task-plane): satisfy diff whitespace check 2026-07-24 11:17:23 +08:00
chatgpt-gitops-bot 3f6cabd4ac fix(task-plane): fail closed identity and recovery boundaries 2026-07-24 11:13:30 +08:00
chatgpt-gitops-bot 146d2123a5 test(task-plane): add recovery and multi-agent acceptance 2026-07-24 11:02:15 +08:00
chatgpt-gitops-bot ecd4ee1cc9 feat(task-plane): add API OpenAPI and notifications 2026-07-24 11:02:14 +08:00
chatgpt-gitops-bot eaf6cd739f feat(task-plane): add delegation policy and evidence 2026-07-24 11:02:14 +08:00
chatgpt-gitops-bot d4276e2cf5 feat(task-plane): add OpenClaw Codex Hermes adapters 2026-07-24 11:02:13 +08:00
chatgpt-gitops-bot f7a04ccec6 feat(task-plane): add worker registry lease and heartbeat 2026-07-24 11:02:13 +08:00
chatgpt-gitops-bot 5cf628deca feat(task-plane): add durable task and event models 2026-07-24 11:02:12 +08:00
chatgpt-gitops-bot 9914ebc9d4 test(task-plane): record writer smoke acceptance 2026-07-24 10:20:57 +08:00
chatgpt-gitops-bot 39ffd23335 chore(task-plane): initialize dedicated writer session 2026-07-24 10:20:56 +08:00
AIOS Task Plane Writer 467015e3fc fix(gitops): bound secret token detection 2026-07-24 10:20:16 +08:00
AIOS Task Plane Writer cdefcc18dd fix(gitops): allow task-plane delivery roots 2026-07-24 10:14:18 +08:00
AIOS Task Plane Writer 1594ac9a18 fix(gitops): verify task-plane writer Gitea push 2026-07-24 10:12:10 +08:00
AIOS Task Plane Writer 0ba2cdb63d fix(gitops): harden task-plane writer bootstrap 2026-07-24 10:11:15 +08:00
gitops-bot a8e9786897 Merge pull request 'gitops: Silent Release execution readiness' (#13) from patch/silent-release-execution-readiness-20260724 into main 2026-07-24 03:12:55 +08:00
chatgpt-gitops-bot 58511395c1 feat(gitops): add persistent shadow acceptance worker 2026-07-24 03:06:20 +08:00
chatgpt-gitops-bot d23aafc04c fix(gitops): fail closed shadow startup and evidence logs 2026-07-24 02:56:09 +08:00
chatgpt-gitops-bot 312aeff1ab docs(gitops): add shadow controller delivery contract 2026-07-24 02:52:22 +08:00
chatgpt-gitops-bot 1673575b21 feat(gitops): harden silent release execution readiness 2026-07-24 02:52:22 +08:00
gitops-bot 8b97275ff7 merge(gitops): add shadow silent-release production contract
Source: patch/silent-release-controller-production-contract-20260724
Source commit: 578f0a7e28
Target base: 238742150b
Production execution: disabled
Production deployment: not authorized
Production tag operations: not authorized
2026-07-24 01:47:51 +08:00
chatgpt-gitops-bot 578f0a7e28 fix(gitops): close trusted builder and finalization wiring 2026-07-24 01:25:24 +08:00
chatgpt-gitops-bot 9cc04bf146 test(gitops): close silent release production contract 2026-07-24 01:12:22 +08:00
chatgpt-gitops-bot e0b48dae24 feat(gitops): wire durable one-approval release orchestrator 2026-07-24 01:04:51 +08:00
chatgpt-gitops-bot 9893652270 fix(gitops): bind signed approval to exact release plan 2026-07-24 00:56:58 +08:00
chatgpt-gitops-bot 57234854f7 fix(gitops): add tamper-evident saga and exclusive capability claims 2026-07-24 00:51:00 +08:00
chatgpt-gitops-bot ccd64610b0 test(gitops): close one-approval silent-release acceptance 2026-07-24 00:26:57 +08:00
chatgpt-gitops-bot bad1120d6d feat(gitops): integrate silent-release shadow control plane 2026-07-24 00:26:57 +08:00
chatgpt-gitops-bot c8d2386677 feat(gitops): harden deployment and release finalization controllers 2026-07-24 00:25:57 +08:00
chatgpt-gitops-bot 08897d3968 feat(gitops): add durable release saga and capability ledger 2026-07-24 00:25:57 +08:00
chatgpt-gitops-bot 8497848120 feat(gitops): add signed release-plan approval trust model 2026-07-24 00:25:57 +08:00
79 changed files with 22115 additions and 274 deletions
+152 -4
View File
@@ -24,6 +24,15 @@ from gitea_tag_publication import (
validate_version,
)
from orchestrator.action_monitor import ActionMonitor
from silent_release_hardening import (
ApprovalKeyRing,
ApprovalRevocationStore,
CapabilityAuthorizationError,
SagaConflict,
SignedApprovalVerifier,
SignedReleasePlanApproval,
SilentReleaseActionService,
)
try:
from authoritative_git import (
AuthoritativeGitError,
@@ -60,6 +69,17 @@ OPENAPI = WS / 'docs' / 'gitops' / 'openapi-chatgpt-actions.yaml'
BARE_REPO = Path(os.getenv('AIOS_GITOPS_BARE_REPO', str(ROOT / 'runtime' / 'gitea-bin' / 'data' / 'repositories' / 'aios' / 'aios.git')))
AUTHORITATIVE_GIT_DIR = Path(os.getenv('AIOS_GITOPS_AUTHORITATIVE_GIT_DIR')) if os.getenv('AIOS_GITOPS_AUTHORITATIVE_GIT_DIR') else None
REPOSITORY_IDENTITY = os.getenv('AIOS_GITOPS_REPOSITORY_IDENTITY', '')
SILENT_RELEASE_STATE_ROOT = Path(os.getenv(
'AIOS_SILENT_RELEASE_STATE_ROOT',
'/var/lib/aios-gitops/silent-release',
))
SILENT_RELEASE_EXECUTION_ENABLED = str(os.getenv(
'AIOS_SILENT_RELEASE_EXECUTION_ENABLED', 'false'
)).lower() in {'1', 'true', 'yes', 'on'}
SILENT_RELEASE_PUBLIC_KEYS = Path(os.getenv(
'AIOS_SILENT_RELEASE_PUBLIC_KEYS',
'/etc/aios-gitops/silent-release-owner-public-keys.json',
))
monitor = ActionMonitor()
BASE_MODELS = [
@@ -68,7 +88,10 @@ BASE_MODELS = [
{'id':'base.gpt-5.5','provider':'openai','model':'gpt-5.5','display_name':'GPT-5.5','scope':'global'},
]
SECRET_RE = re.compile(r'(?i)(api[_-]?key|secret[_-]?access[_-]?key|secret|password|token)\s*[:=]\s*["\']?([A-Za-z0-9_./+=:-]{16,})')
SECRET_VAL_RE = re.compile(r'(sk-[A-Za-z0-9_-]{20,}|HPUA[A-Z0-9]{10,}|SDC[A-Za-z0-9]{20,})')
SECRET_VAL_RE = re.compile(
r'(?<![A-Za-z0-9_])'
r'(sk-[A-Za-z0-9_-]{20,}|HPUA[A-Z0-9]{10,}|SDC[A-Za-z0-9]{20,})'
)
READ_ONLY_GET_PATHS = frozenset({
'/', '/health', '/api/health', '/api/openapi.yaml',
@@ -80,11 +103,23 @@ READ_ONLY_GET_PATHS = frozenset({
'/admin/ai-evolution/api/data',
'/api/release-consistency', '/api/tag-publication-status',
'/api/release-acceptance',
'/getReleaseCandidate', '/getReleasePlan', '/getReleaseAcceptance',
'/getApprovalEnvelope', '/getReleaseSaga', '/getCapabilityStatus',
'/getRecoveryStatus',
})
SILENT_RELEASE_POST_PATHS = frozenset({
'/createReleaseCandidate',
'/createReleaseIntent',
'/submitSignedApproval',
'/submitOwnerApproval',
'/startDeployment',
})
def reject_non_read_http(handler, method: str, path: str) -> bool:
if method == 'GET':
return False
if path in SILENT_RELEASE_POST_PATHS:
return False
decision = evaluate_git_write(WS)
if decision.allowed:
return False
@@ -101,6 +136,41 @@ def sh(args, check=False):
def jload(p: Path, default):
try: return json.loads(p.read_text(encoding='utf-8'))
except Exception: return default
def _file_sha256(path: Path) -> str:
digest=hashlib.sha256()
with path.open('rb') as source:
for chunk in iter(lambda: source.read(1024 * 1024), b''):
digest.update(chunk)
return digest.hexdigest()
def _build_silent_release_service() -> SilentReleaseActionService:
verifier=None
if SILENT_RELEASE_PUBLIC_KEYS.is_file():
verifier=SignedApprovalVerifier(
ApprovalKeyRing.from_file(SILENT_RELEASE_PUBLIC_KEYS),
ApprovalRevocationStore(
SILENT_RELEASE_STATE_ROOT / 'revocations'
),
)
policy_path=WS / 'config' / 'gitops' / 'silent-release-policy.json'
policy_sha=_file_sha256(policy_path) if policy_path.is_file() else ''
return SilentReleaseActionService(
SILENT_RELEASE_STATE_ROOT,
execution_enabled=SILENT_RELEASE_EXECUTION_ENABLED,
approval_verifier=verifier,
expected_repository=REPOSITORY_IDENTITY,
policy_sha256=policy_sha,
)
# Construct once during service startup. If production execution is enabled
# without every trusted builder/orchestrator dependency, construction fails
# closed before the HTTP server accepts traffic.
_SILENT_RELEASE_SERVICE = _build_silent_release_service()
def silent_release_service() -> SilentReleaseActionService:
return _SILENT_RELEASE_SERVICE
def relpath(path: str) -> str:
x = str(path or '').replace('\\','/').strip()
if not x or x.startswith('/') or '..' in Path(x).parts or Path(x).parts[0] == '.git': raise ValueError('invalid path')
@@ -468,12 +538,51 @@ class H(BaseHTTPRequestHandler):
if path == '/api/release-acceptance':
request=publication_request_from_query(q)
return self.sendj(publication_publisher().run_release_acceptance(request))
if path in (
'/getReleaseCandidate',
'/getReleasePlan',
'/getReleaseAcceptance',
'/getApprovalEnvelope',
'/getReleaseSaga',
'/getCapabilityStatus',
):
candidate_id=(q.get('candidate_id') or [''])[0]
if not candidate_id:
return self.sendj({'error':'candidate_id is required'},400)
service=silent_release_service()
if path == '/getReleaseCandidate':
return self.sendj(
service.get_release_candidate(candidate_id)
)
if path == '/getApprovalEnvelope':
return self.sendj(
service.get_approval_envelope(candidate_id)
)
if path == '/getReleasePlan':
return self.sendj(service.get_release_plan(candidate_id))
if path == '/getReleaseAcceptance':
return self.sendj(service.get_acceptance(candidate_id))
if path == '/getCapabilityStatus':
return self.sendj(
service.get_capability_status(candidate_id)
)
return self.sendj(service.get_release_saga(candidate_id))
if path == '/getRecoveryStatus':
return self.sendj(
silent_release_service().get_recovery_status()
)
# Admin AI Evolution Dashboard
if path in ('/admin/ai-evolution','/admin/ai-evolution/'):
return self.ai_evolution_page()
if path in ('/admin/ai-evolution/api/data'):
return self.ai_evolution_data()
return self.sendj({'error':'not_found','path':path},404)
except FileNotFoundError:
return self.sendj({'error':'release candidate not found'},404)
except ValueError as e:
error=redact(e)
monitor.record_failure('do_GET', error, {'path':path})
return self.sendj({'error':error},400)
except Exception as e:
error=redact(e)
monitor.record_failure('do_GET', error, {'path':path})
@@ -489,6 +598,32 @@ class H(BaseHTTPRequestHandler):
if reject_non_read_http(self, 'POST', path): return
data=self.body()
try:
if path == '/createReleaseCandidate':
a=self.need('release:candidate')
if not a: return
return self.sendj(
silent_release_service().create_release_candidate(data)
)
if path == '/createReleaseIntent':
a=self.need('release:candidate')
if not a: return
return self.sendj(
silent_release_service().create_release_intent(data)
)
if path in ('/submitOwnerApproval','/submitSignedApproval'):
a=self.need('release:approve')
if not a: return
return self.sendj(
silent_release_service().submit_owner_approval(data)
)
if path == '/startDeployment':
a=self.need('release:deploy')
if not a: return
return self.sendj(
silent_release_service().start_deployment(
str(data.get('candidate_id') or '')
)
)
if path in ('/api/branch','/api/create-branch'): return self.branch(data)
if path in ('/api/file','/api/commit-file'): return self.file(data)
if path == '/api/commit': return self.commit(data)
@@ -499,9 +634,22 @@ class H(BaseHTTPRequestHandler):
if path == '/api/model-priority': return self.model_priority(data)
if path == '/api/nl': return self.nl(data)
return self.sendj({'error':'not_found','path':path},404)
except (ValueError, FileNotFoundError) as e:
error=redact(e)
monitor.record_failure('do_POST', error, {'path':path})
return self.sendj({'error':error},400)
except CapabilityAuthorizationError as e:
error=redact(e)
monitor.record_failure('do_POST', error, {'path':path})
return self.sendj({'error':error},403)
except SagaConflict as e:
error=redact(e)
monitor.record_failure('do_POST', error, {'path':path})
return self.sendj({'error':error},409)
except Exception as e:
monitor.record_failure('do_POST', str(e), {'path':path})
return self.sendj({'error':str(e)},500)
error=redact(e)
monitor.record_failure('do_POST', error, {'path':path})
return self.sendj({'error':error},500)
def do_PATCH(self):
path=urllib.parse.urlparse(self.path).path.rstrip('/')
if reject_non_read_http(self, 'PATCH', path): return
@@ -703,7 +851,7 @@ return{vs,cmds,ar,cmd,cdet,tot,sok,sfail,runs,baseline,refreshMsg,loadCmd,selVer
def api_spec():
return {'version':'V1R1C0-chatgpt-actions','openapi':'/api/openapi.yaml','read':['GET /api/base','GET /api/ver','GET /api/diff','GET /api/patch','GET /api/model','GET /api/run','GET /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'],'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}}
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():
print(f'AIOS ChatGPT GitOps adapter listening on {HOST}:{PORT}')
@@ -0,0 +1,316 @@
"""Task-scoped hardened wrapper around the existing Action API."""
from __future__ import annotations
import json
import os
import subprocess
from pathlib import Path
import urllib.parse
import aios_chatgpt_actions_api as api
import aios_isolated_writer_policy as policy
from isolated_writer_authz import AuthorizationError, authorize
from isolated_writer_commit_file import AtomicCommitFile, CommitFileError, OperationLedger
from isolated_writer_token_registry import TokenRegistry
_reject = api.reject_non_read_http
_write_file = api.write_file
_sync_branch = api.sync_branch
_original_do_get = api.H.do_GET
api.OPENAPI = Path(os.getenv("AIOS_GITOPS_ISOLATED_WRITER_OPENAPI", str(api.OPENAPI)))
def _registry() -> TokenRegistry:
return TokenRegistry(Path(os.environ["AIOS_GITOPS_TOKEN_REGISTRY_V2"]))
def _atomic_writer() -> AtomicCommitFile:
return AtomicCommitFile(
workspace=api.WS,
bare_repo=api.BARE_REPO,
ledger=OperationLedger(Path(os.environ["AIOS_GITOPS_WRITER_OPERATION_LEDGER"])),
remote=os.getenv("AIOS_GITOPS_GITEA_PUSH_REMOTE", ""),
audit=policy.append_audit_record,
)
def _bearer(handler) -> str:
value = handler.headers.get("Authorization", "")
return value[7:].strip() if value.startswith("Bearer ") else ""
def _authorize(handler, *, method: str, path: str, branch=None, target_path=None):
grant = authorize(
_registry(),
_bearer(handler),
method=method,
path=path,
source_ip=str(handler.client_address[0]),
workspace=api.WS,
branch=branch,
target_path=target_path,
)
grant.record["_source_ip"] = str(handler.client_address[0])
return grant
def _authorization_error(handler, exc: AuthorizationError):
return handler.sendj({"error": exc.code}, exc.status)
def _isolated_runtime_status(handler):
branch = api.sh(["rev-parse", "--abbrev-ref", "HEAD"], True)["stdout"]
head = api.sh(["rev-parse", "HEAD"], True)["stdout"]
return handler.sendj({
"service": os.getenv(
"AIOS_GITOPS_SERVICE_AUDIENCE",
"aios-isolated-writer.service",
),
"status": "active",
"runtime_role": os.getenv("AIOS_GITOPS_RUNTIME_ROLE"),
"workspace_role": os.getenv("AIOS_GITOPS_WORKSPACE_ROLE"),
"writes_enabled": os.getenv("AIOS_GITOPS_WRITES_ENABLED", "").lower()
in {"1", "true", "yes", "on"},
"allowed_branch": policy.allowed_branch(),
"approved_base_oid": policy.allowed_base_oid(),
"workspace_branch": branch,
"workspace_head_oid": head,
"repository_identity": os.getenv("AIOS_GITOPS_REPOSITORY_IDENTITY"),
"token_policy_version": 2,
"atomic_commit_file": True,
"path_policy_sha256": policy.configured_path_policy_sha256(),
})
def _isolated_do_get(self):
path = urllib.parse.urlparse(self.path).path.rstrip("/") or "/"
if path in {"/health", "/api/health"}:
return self.sendj({
"status": "ok",
"service": os.getenv(
"AIOS_GITOPS_SERVICE_AUDIENCE",
"aios-isolated-writer.service",
),
})
if path not in {"/api/runtime-status", "/api/baseline", "/api/patches"}:
return self.sendj({"error": "isolated_writer_operation_forbidden"}, 403)
try:
_authorize(self, method="GET", path=path)
except AuthorizationError as exc:
return _authorization_error(self, exc)
if path == "/api/runtime-status":
return _isolated_runtime_status(self)
return _original_do_get(self)
def _read_commit_file_body(handler):
try:
length = int(handler.headers.get("Content-Length", "0"))
except ValueError:
raise CommitFileError("writer_content_length_rejected") from None
if length <= 0:
raise CommitFileError("writer_request_object_required")
if length > 1_100_000:
raise CommitFileError("writer_request_too_large", 413)
raw = handler.rfile.read(length)
try:
value = json.loads(raw.decode("utf-8"))
except (UnicodeDecodeError, json.JSONDecodeError):
raise CommitFileError("writer_request_json_rejected") from None
if not isinstance(value, dict):
raise CommitFileError("writer_request_object_required")
return value
def _isolated_do_post(self):
path = urllib.parse.urlparse(self.path).path.rstrip("/") or "/"
if path != "/api/commit-file":
return self.sendj({"error": "isolated_writer_operation_forbidden"}, 403)
try:
data = _read_commit_file_body(self)
branch = str(data.get("branch") or "")
target_path = str(data.get("path") or "")
grant = _authorize(
self,
method="POST",
path=path,
branch=branch,
target_path=target_path,
)
if api.content_has_secret(str(data.get("content") or "")):
raise CommitFileError("writer_secret_content_rejected", 403)
result = _atomic_writer().execute(grant, data)
return self.sendj(result)
except AuthorizationError as exc:
return _authorization_error(self, exc)
except CommitFileError as exc:
return self.sendj({"error": exc.code}, exc.status)
except PermissionError:
return self.sendj({"error": "isolated_writer_policy_rejected"}, 403)
def reject_non_read_http(handler, method: str, path: str) -> bool:
if _reject(handler, method, path):
return True
try:
policy.validate_endpoint(method, path, api.WS, api.BARE_REPO)
return False
except PermissionError as exc:
handler.sendj({"error": str(exc)}, 403)
return True
def checkout(branch, base="main", create=False):
api.ensure_git()
target = api.branch_name(branch)
policy.validate_branch_request({"branch": target, "base": base})
exists = api.sh(["rev-parse", "--verify", target])["code"] == 0
if exists:
if api.sh(["rev-parse", "--abbrev-ref", "HEAD"], True)["stdout"] != target:
api.sh(["checkout", target], True)
elif create:
api.sh(["checkout", "-b", target, base], True)
else:
raise PermissionError("isolated_writer_branch_missing")
policy.require_service_invariants(api.WS, api.BARE_REPO)
return target
def write_file(path, content):
policy.validate_path(path, api.WS)
return _write_file(path, content)
def sync_branch(branch: str):
policy.validate_sync_branch(branch)
local = _sync_branch(branch)
remote = os.getenv("AIOS_GITOPS_GITEA_PUSH_REMOTE", "").strip()
if remote != "gitea-http":
raise PermissionError("isolated_writer_gitea_remote_rejected")
head = api.sh(["rev-parse", "HEAD"], True)["stdout"]
pushed = subprocess.run(
["git", "push", "--porcelain", remote, f"HEAD:refs/heads/{branch}"],
cwd=api.WS,
text=True,
capture_output=True,
)
if pushed.returncode != 0:
raise RuntimeError("isolated_writer_gitea_push_failed")
verified = subprocess.run(
["git", "ls-remote", "--heads", remote, f"refs/heads/{branch}"],
cwd=api.WS,
text=True,
capture_output=True,
)
if verified.returncode != 0:
raise RuntimeError("isolated_writer_gitea_verification_failed")
fields = verified.stdout.strip().split()
if len(fields) != 2 or fields[0] != head or fields[1] != f"refs/heads/{branch}":
raise RuntimeError("isolated_writer_gitea_oid_mismatch")
return {
"status": "verified",
"target": "authoritative-bare+gitea",
"branch": branch,
"oid": head,
"local": local.get("status"),
}
def commit(paths, msg):
policy.validate_commit_paths(paths, api.WS)
api.ensure_git()
selected = [api.relpath(p) for p in paths] if paths else ["."]
api.sh(["add", *selected], True)
policy.validate_staged_paths(api.WS)
branch = api.sh(["rev-parse", "--abbrev-ref", "HEAD"], True)["stdout"]
if not api.sh(["status", "--short"])["stdout"]:
return {"status": "no_changes", "branch": branch, "sync": sync_branch(branch)}
out = api.sh(["commit", "-m", msg], True)
branch = api.sh(["rev-parse", "--abbrev-ref", "HEAD"], True)["stdout"]
return {
"status": "committed",
"branch": branch,
"rev": api.sh(["rev-parse", "--short", "HEAD"], True)["stdout"],
"git": out,
"sync": sync_branch(branch),
}
def audit(actor, action, target, payload):
policy.append_audit_record(
{
"at": api.now(),
"actor": actor,
"action": action,
"target": target,
"payload": api.sanitize(payload),
}
)
def _validate_request(self, data, validate):
try:
validate(data)
return True
except PermissionError as exc:
self.sendj({"error": str(exc)}, 403)
return False
def _checked_branch(self, data):
if not _validate_request(self, data, policy.validate_branch_request):
return
return _branch(self, data)
def _checked_file(self, data):
data = dict(data)
data.setdefault("branch", policy.allowed_branch())
if not _validate_request(self, data, policy.validate_branch_request):
return
if not _validate_request(
self,
{"path": data.get("path", "")},
lambda item: policy.validate_path(item["path"], api.WS),
):
return
return _file(self, data)
def _checked_commit(self, data):
data = dict(data)
data.setdefault("branch", policy.allowed_branch())
if not _validate_request(self, data, policy.validate_branch_request):
return
return _commit(self, data)
def _checked_patch(self, data):
data = dict(data)
data["branch"] = policy.allowed_branch()
if not _validate_request(self, data, policy.validate_branch_request):
return
return _patch(self, data)
_branch, _file, _commit, _patch = api.H.branch, api.H.file, api.H.commit, api.H.patch
api.reject_non_read_http = reject_non_read_http
api.checkout, api.write_file, api.sync_branch, api.commit, api.audit = (
checkout,
write_file,
sync_branch,
commit,
audit,
)
api.H.branch, api.H.file, api.H.commit, api.H.patch = (
_checked_branch,
_checked_file,
_checked_commit,
_checked_patch,
)
api.H.do_GET = _isolated_do_get
api.H.do_POST = _isolated_do_post
if __name__ == "__main__":
policy.require_service_invariants(api.WS, api.BARE_REPO)
api.ThreadingHTTPServer((api.HOST, api.PORT), api.H).serve_forever()
@@ -0,0 +1,310 @@
"""Fail-closed policy used only by the task-scoped Action writer."""
from __future__ import annotations
import os
import json
import stat
import subprocess
import re
from pathlib import Path, PurePosixPath, PureWindowsPath
from urllib.parse import unquote
import isolated_writer_path_policy as path_policy
WRITE_ENDPOINTS = frozenset({"/api/commit-file"})
ALLOWED_ROOTS = (
"active-runtime",
"docs",
"patches",
"reports",
"scripts",
"test",
"tests",
)
DENIED_ROOTS = ("baseline", "config", "deploy", "deployments", "ops", "runtime", "services")
_TRUE = frozenset({"1", "true", "yes", "on"})
_FALSE = frozenset({"0", "false", "no", "off"})
_DENY_FLAGS = (
"AIOS_GITOPS_ALLOW_MAIN_WRITE",
"AIOS_GITOPS_ALLOW_TAG_WRITE",
"AIOS_GITOPS_ALLOW_BASELINE_WRITE",
"AIOS_GITOPS_ALLOW_REGISTRY_WRITE",
"AIOS_GITOPS_ALLOW_ROLLBACK",
"AIOS_GITOPS_ALLOW_DEPLOYMENT",
)
def enforcement_required() -> bool:
return os.getenv("AIOS_GITOPS_ISOLATED_WRITER_ENFORCEMENT", "") == "required"
def allowed_branch() -> str:
value = os.getenv("AIOS_GITOPS_ALLOWED_BRANCH", "").strip()
if not value:
raise PermissionError("isolated_writer_missing_allowed_branch")
return value
def allowed_base_oid() -> str:
value = os.getenv("AIOS_GITOPS_ALLOWED_BASE_OID", "").strip().lower()
if len(value) != 40 or any(ch not in "0123456789abcdef" for ch in value):
raise PermissionError("isolated_writer_invalid_allowed_base_oid")
return value
def _git(workspace: Path, *args: str) -> str:
result = subprocess.run(["git", *args], cwd=workspace, text=True, capture_output=True)
if result.returncode != 0:
raise PermissionError("isolated_writer_git_validation_failed")
return result.stdout.strip()
def require_service_invariants(workspace: Path, bare_repo: Path) -> None:
if not enforcement_required():
return
if os.getenv("AIOS_GITOPS_WRITES_ENABLED", "").strip().lower() not in _TRUE:
raise PermissionError("isolated_writer_write_gate_required")
if os.getenv("AIOS_GITOPS_RUNTIME_ROLE", "") != "isolated-writer":
raise PermissionError("isolated_writer_role_required")
if os.getenv("AIOS_GITOPS_WORKSPACE_ROLE", "") != "isolated-writer":
raise PermissionError("isolated_writer_workspace_role_required")
if os.getenv("AIOS_GITOPS_TOKEN_POLICY_VERSION_REQUIRED", "") != "2":
raise PermissionError("isolated_writer_token_policy_v2_required")
if not os.getenv("AIOS_GITOPS_SERVICE_AUDIENCE", "").strip():
raise PermissionError("isolated_writer_service_audience_required")
if not os.getenv("AIOS_GITOPS_TOKEN_REGISTRY_V2", "").strip():
raise PermissionError("isolated_writer_token_registry_required")
if not os.getenv("AIOS_GITOPS_WRITER_OPERATION_LEDGER", "").strip():
raise PermissionError("isolated_writer_operation_ledger_required")
path_policy.configured_path_policy()
for name in _DENY_FLAGS:
if os.getenv(name, "").strip().lower() not in _FALSE:
raise PermissionError("isolated_writer_forbidden_capability_flag")
if os.getenv("AIOS_GITOPS_REPOSITORY_IDENTITY", "") != "aios/aios":
raise PermissionError("isolated_writer_repository_identity_rejected")
configured_bare_value = os.getenv("AIOS_GITOPS_AUTHORITATIVE_GIT_DIR", "").strip()
if not configured_bare_value:
raise PermissionError("isolated_writer_authoritative_repo_rejected")
configured_bare = Path(configured_bare_value)
if configured_bare.resolve(strict=True) != bare_repo.resolve(strict=True):
raise PermissionError("isolated_writer_authoritative_repo_rejected")
writer_root_value = os.getenv("AIOS_GITOPS_APPROVED_WRITER_ROOT", "").strip()
if not writer_root_value:
raise PermissionError("isolated_writer_workspace_outside_approved_root")
writer_root = Path(writer_root_value).resolve(strict=True)
lexical_workspace = Path(os.path.abspath(str(workspace)))
canonical_workspace = lexical_workspace.resolve(strict=True)
if lexical_workspace != canonical_workspace:
raise PermissionError("isolated_writer_workspace_symlink_rejected")
try:
canonical_workspace.relative_to(writer_root)
except (ValueError, FileNotFoundError):
raise PermissionError("isolated_writer_workspace_outside_approved_root") from None
if _git(workspace, "rev-parse", "--abbrev-ref", "HEAD") != allowed_branch():
raise PermissionError("isolated_writer_current_branch_rejected")
approved_base = allowed_base_oid()
try:
_git(workspace, "cat-file", "-e", f"{approved_base}^{{commit}}")
subprocess.run(
["git", "merge-base", "--is-ancestor", approved_base, "HEAD"],
cwd=workspace,
check=True,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
except (PermissionError, subprocess.CalledProcessError):
raise PermissionError("isolated_writer_approved_base_not_ancestor") from None
if _git(workspace, "merge-base", approved_base, "HEAD") != approved_base:
raise PermissionError("isolated_writer_merge_base_rejected")
common = Path(_git(workspace, "rev-parse", "--git-common-dir"))
if not common.is_absolute():
common = (workspace / common).resolve()
if common.resolve() != bare_repo.resolve():
raise PermissionError("isolated_writer_common_repo_rejected")
def approved_audit_log_path() -> Path:
root_value = os.getenv("AIOS_GITOPS_APPROVED_AUDIT_ROOT", "").strip()
log_value = os.getenv("AIOS_GITOPS_AUDIT_LOG", "").strip()
if not root_value or not log_value:
raise PermissionError("isolated_writer_audit_path_missing")
lexical_root = Path(os.path.abspath(root_value))
lexical_log = Path(os.path.abspath(log_value))
root = lexical_root.resolve(strict=True)
if lexical_root != root or root.is_symlink() or not root.is_dir():
raise PermissionError("isolated_writer_audit_root_rejected")
try:
lexical_log.relative_to(root)
except ValueError:
raise PermissionError("isolated_writer_audit_path_outside_approved_root") from None
if lexical_log.parent.resolve(strict=True) != lexical_log.parent:
raise PermissionError("isolated_writer_audit_parent_symlink_rejected")
if lexical_log.exists() and lexical_log.is_symlink():
raise PermissionError("isolated_writer_audit_log_symlink_rejected")
os.chmod(root, 0o750)
os.chmod(lexical_log.parent, 0o750)
return lexical_log
def append_audit_record(record: dict) -> None:
path = approved_audit_log_path()
flags = os.O_WRONLY | os.O_APPEND | os.O_CREAT
if hasattr(os, "O_NOFOLLOW"):
flags |= os.O_NOFOLLOW
descriptor = os.open(path, flags, 0o600)
try:
info = os.fstat(descriptor)
if not stat.S_ISREG(info.st_mode):
raise PermissionError("isolated_writer_audit_log_not_regular")
os.fchmod(descriptor, 0o600)
line = json.dumps(
_audit_sanitize(record),
ensure_ascii=False,
sort_keys=True,
separators=(",", ":"),
).encode("utf-8") + b"\n"
os.write(descriptor, line)
os.fsync(descriptor)
finally:
os.close(descriptor)
parent_fd = os.open(path.parent, os.O_RDONLY | getattr(os, "O_DIRECTORY", 0))
try:
os.fsync(parent_fd)
finally:
os.close(parent_fd)
def _audit_sanitize(value):
if isinstance(value, dict):
return {
key: (
"[REDACTED]"
if any(
marker in str(key).lower()
for marker in (
"authorization",
"credential",
"password",
"secret",
"token",
"private_key",
)
)
and str(key).lower() != "token_id"
else _audit_sanitize(inner)
)
for key, inner in value.items()
}
if isinstance(value, list):
return [_audit_sanitize(item) for item in value]
if isinstance(value, str) and re.search(
r"(?i)(authorization\s*:\s*bearer|(?:token|password|secret)\s*[:=])",
value,
):
return "[REDACTED]"
return value
def validate_endpoint(method: str, path: str, workspace: Path, bare_repo: Path) -> None:
if not enforcement_required():
return
if method != "POST" or path not in WRITE_ENDPOINTS:
raise PermissionError("isolated_writer_operation_forbidden")
require_service_invariants(workspace, bare_repo)
def validate_branch_request(data: dict) -> None:
if not enforcement_required():
return
branch = allowed_branch() if "branch" not in data else str(data.get("branch") or "").strip()
if branch != allowed_branch():
raise PermissionError("isolated_writer_branch_rejected")
if str(data.get("base", "main")).strip() != "main":
raise PermissionError("isolated_writer_base_rejected")
if data.get("force") or data.get("force_push"):
raise PermissionError("isolated_writer_force_rejected")
def validate_path(path: str, workspace: Path | None = None) -> str:
if not isinstance(path, str):
raise PermissionError("isolated_writer_path_rejected")
value = path
if not value or value != value.strip() or "\x00" in value or "\\" in value:
raise PermissionError("isolated_writer_path_rejected")
try:
decoded = unquote(value, errors="strict")
except (UnicodeDecodeError, ValueError):
raise PermissionError("isolated_writer_path_rejected") from None
if decoded != value:
raise PermissionError("isolated_writer_path_rejected")
windows = PureWindowsPath(value)
posix = PurePosixPath(value)
parts = value.split("/")
if (
posix.is_absolute()
or windows.is_absolute()
or bool(windows.drive)
or any(part in {"", ".", ".."} for part in parts)
):
raise PermissionError("isolated_writer_path_rejected")
first = parts[0]
service_exact_path = False
if first in DENIED_ROOTS:
try:
path_policy.require_service_path("commit_file.atomic", value)
service_exact_path = True
except path_policy.PathPolicyError as exc:
raise PermissionError(str(exc)) from None
if value != "README.md" and first not in ALLOWED_ROOTS and not service_exact_path:
raise PermissionError("isolated_writer_path_outside_allowed_roots")
if workspace is not None:
lexical_root = Path(os.path.abspath(str(workspace)))
root = lexical_root.resolve(strict=True)
if lexical_root != root:
raise PermissionError("isolated_writer_workspace_symlink_rejected")
current = root
for part in parts:
current = current / part
if current.is_symlink():
raise PermissionError("isolated_writer_path_symlink_rejected")
candidate = (root / value).resolve(strict=False)
try:
candidate.relative_to(root)
except ValueError:
raise PermissionError("isolated_writer_path_escapes_workspace") from None
return value
def validate_content_for_path(path: str, content: str) -> None:
if path.split("/", 1)[0] not in DENIED_ROOTS:
return
try:
path_policy.validate_content("commit_file.atomic", path, content)
except path_policy.PathPolicyError as exc:
raise PermissionError(str(exc)) from None
def configured_path_policy_sha256() -> str | None:
try:
return path_policy.configured_path_policy_sha256()
except path_policy.PathPolicyError as exc:
raise PermissionError(str(exc)) from None
def validate_commit_paths(paths: list[str], workspace: Path | None = None) -> None:
if enforcement_required():
for path in paths:
if str(path).strip() != ".":
validate_path(str(path), workspace)
def validate_staged_paths(workspace: Path) -> None:
if not enforcement_required():
return
for path in _git(workspace, "diff", "--cached", "--name-only").splitlines():
validate_path(path, workspace)
def validate_sync_branch(branch: str) -> None:
if enforcement_required() and branch != allowed_branch():
raise PermissionError("isolated_writer_sync_branch_rejected")
+167
View File
@@ -0,0 +1,167 @@
"""Fail-closed operation authorization for an isolated Git writer."""
from __future__ import annotations
import ipaddress
import os
import re
import subprocess
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
from isolated_writer_token_registry import TokenRegistry
ROUTE_OPERATIONS = {
("GET", "/api/runtime-status"): "runtime.status.read",
("GET", "/api/baseline"): "baseline.read",
("GET", "/api/patches"): "patches.read",
("POST", "/api/commit-file"): "commit_file.atomic",
}
OID_RE = re.compile(r"[0-9a-f]{40}")
class AuthorizationError(PermissionError):
def __init__(self, code: str, status: int = 403):
super().__init__(code)
self.code = code
self.status = status
@dataclass(frozen=True)
class AuthorizedGrant:
record: dict[str, Any]
operation: str
use_count: int
@property
def token_id(self) -> str:
return str(self.record["token_id"])
@property
def task_id(self) -> str:
return str(self.record["task_id"])
def operation_for(method: str, path: str) -> str:
operation = ROUTE_OPERATIONS.get((method.upper(), path))
if not operation:
raise AuthorizationError("isolated_writer_operation_forbidden")
return operation
def _parse_time(value: Any) -> datetime:
try:
parsed = datetime.fromisoformat(str(value).replace("Z", "+00:00"))
except (TypeError, ValueError):
raise AuthorizationError("writer_token_time_invalid") from None
if parsed.tzinfo is None:
raise AuthorizationError("writer_token_time_invalid")
return parsed.astimezone(timezone.utc)
def _git_is_ancestor(workspace: Path, base: str, branch: str) -> bool:
result = subprocess.run(
["git", "merge-base", "--is-ancestor", base, branch],
cwd=workspace,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
return result.returncode == 0
def _validate_grant(
grant: dict[str, Any],
*,
audience: str,
source_ip: str,
operation: str,
branch: str | None,
path: str | None,
workspace: Path,
current_time: datetime,
) -> None:
if int(grant.get("schema_version", 0)) != 2:
raise AuthorizationError("writer_token_policy_v2_required", 401)
if str(grant.get("audience") or "") != audience:
raise AuthorizationError("writer_token_audience_rejected")
try:
peer = ipaddress.ip_address(source_ip)
networks = [ipaddress.ip_network(str(value), strict=True) for value in grant.get("source_cidrs", [])]
except ValueError:
raise AuthorizationError("writer_token_source_rejected") from None
if not networks or not any(peer in network for network in networks):
raise AuthorizationError("writer_token_source_rejected")
created = _parse_time(grant.get("created_at"))
not_before = _parse_time(grant.get("not_before"))
expires = _parse_time(grant.get("expires_at"))
if expires <= not_before or (expires - created).total_seconds() > 1800:
raise AuthorizationError("writer_token_lifetime_rejected")
if current_time < not_before:
raise AuthorizationError("writer_token_not_yet_valid", 401)
if current_time >= expires:
raise AuthorizationError("writer_token_expired", 401)
if bool(grant.get("revoked")):
raise AuthorizationError("writer_token_revoked", 401)
operations = grant.get("allowed_operations")
if not isinstance(operations, list) or operation not in operations:
raise AuthorizationError("writer_token_operation_rejected")
expected_branch = str(grant.get("allowed_branch") or "")
configured_branch = os.getenv("AIOS_GITOPS_ALLOWED_BRANCH", "")
if expected_branch != configured_branch:
raise AuthorizationError("writer_token_branch_rejected")
if branch is not None and branch != expected_branch:
raise AuthorizationError("writer_token_branch_rejected")
if path is not None:
paths = grant.get("allowed_paths")
if not isinstance(paths, list) or path not in paths:
raise AuthorizationError("writer_token_path_rejected")
base = str(grant.get("approved_base_oid") or "").lower()
if not OID_RE.fullmatch(base):
raise AuthorizationError("writer_token_base_rejected")
if not _git_is_ancestor(workspace, base, expected_branch):
raise AuthorizationError("writer_token_base_not_ancestor")
def authorize(
registry: TokenRegistry,
token: str,
*,
method: str,
path: str,
source_ip: str,
workspace: Path,
branch: str | None = None,
target_path: str | None = None,
current_time: datetime | None = None,
) -> AuthorizedGrant:
if os.getenv("AIOS_GITOPS_TOKEN_POLICY_VERSION_REQUIRED", "") != "2":
raise AuthorizationError("writer_token_policy_v2_not_required")
if not token:
raise AuthorizationError("unauthorized", 401)
grant = registry.find(token)
if grant is None:
raise AuthorizationError("unauthorized", 401)
operation = operation_for(method, path)
_validate_grant(
grant,
audience=os.getenv("AIOS_GITOPS_SERVICE_AUDIENCE", ""),
source_ip=source_ip,
operation=operation,
branch=branch,
path=target_path,
workspace=workspace,
current_time=current_time or datetime.now(timezone.utc),
)
limits = grant.get("max_uses")
if not isinstance(limits, dict):
raise AuthorizationError("writer_token_use_limit_missing")
try:
used = registry.consume(str(grant["token_id"]), operation, int(limits[operation]))
except (KeyError, TypeError, ValueError):
raise AuthorizationError("writer_token_use_limit_missing") from None
except PermissionError as exc:
code = str(exc)
raise AuthorizationError(code, 401 if code == "writer_token_revoked" else 403) from None
return AuthorizedGrant(grant, operation, used)
@@ -0,0 +1,403 @@
"""Atomic, idempotent commit-file Saga for the isolated writer."""
from __future__ import annotations
import fcntl
import hashlib
import json
import os
import re
import sqlite3
import subprocess
import tempfile
import threading
from pathlib import Path
from typing import Any
import aios_isolated_writer_policy as policy
OID_RE = re.compile(r"[0-9a-f]{40}")
IDEMPOTENCY_RE = re.compile(r"[A-Za-z0-9][A-Za-z0-9._:-]{7,127}")
STATES = frozenset({
"PENDING", "FILE_WRITTEN", "COMMITTED", "PUSHED", "VERIFIED", "COMPLETED",
"FAILED_RECOVERABLE", "MANUAL_REVIEW_REQUIRED",
})
_LOCKS: dict[str, threading.Lock] = {}
_LOCKS_GUARD = threading.Lock()
_LEDGER_INIT_LOCK = threading.Lock()
class CommitFileError(RuntimeError):
def __init__(self, code: str, status: int = 400):
super().__init__(code)
self.code = code
self.status = status
class SimulatedCrash(RuntimeError):
pass
def canonical_digest(data: dict[str, Any]) -> str:
payload = {
"branch": data["branch"],
"path": data["path"],
"content_sha256": hashlib.sha256(data["content"].encode("utf-8")).hexdigest(),
"message": data["message"],
"expected_head_oid": data["expected_head_oid"],
}
encoded = json.dumps(payload, sort_keys=True, separators=(",", ":")).encode("utf-8")
return hashlib.sha256(encoded).hexdigest()
class OperationLedger:
def __init__(self, path: Path | str):
self.path = Path(path)
self.path.parent.mkdir(parents=True, exist_ok=True)
if self.path.parent.is_symlink() or self.path.is_symlink():
raise PermissionError("writer_operation_ledger_symlink_rejected")
with _LEDGER_INIT_LOCK:
with self.connect() as db:
db.execute(
"""
CREATE TABLE IF NOT EXISTS writer_operations (
operation_id TEXT PRIMARY KEY,
token_id TEXT NOT NULL,
task_id TEXT NOT NULL,
idempotency_key TEXT NOT NULL UNIQUE,
operation_digest TEXT NOT NULL,
branch TEXT NOT NULL,
path TEXT NOT NULL,
content_sha256 TEXT NOT NULL,
pre_head_oid TEXT NOT NULL,
commit_oid TEXT,
push_status TEXT,
verification_status TEXT,
state TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
error_code TEXT
)
"""
)
os.chmod(self.path, 0o600)
def connect(self) -> sqlite3.Connection:
db = sqlite3.connect(self.path, timeout=30)
db.row_factory = sqlite3.Row
db.execute("PRAGMA journal_mode=WAL")
db.execute("PRAGMA synchronous=FULL")
return db
def get_or_create(self, grant: Any, data: dict[str, Any], digest: str) -> dict[str, Any]:
key = data["idempotency_key"]
with self.connect() as db:
db.execute("BEGIN IMMEDIATE")
row = db.execute(
"SELECT * FROM writer_operations WHERE idempotency_key=?", (key,)
).fetchone()
if row:
db.commit()
record = dict(row)
if record["operation_digest"] != digest:
raise CommitFileError("writer_idempotency_conflict", 409)
if (
record["token_id"] != grant.token_id
or record["task_id"] != grant.task_id
):
raise CommitFileError("writer_idempotency_owner_conflict", 409)
return record
operation_id = hashlib.sha256(
f"{grant.task_id}\0{key}".encode("utf-8")
).hexdigest()
content_sha = hashlib.sha256(data["content"].encode("utf-8")).hexdigest()
db.execute(
"""
INSERT INTO writer_operations(
operation_id,token_id,task_id,idempotency_key,operation_digest,
branch,path,content_sha256,pre_head_oid,state
) VALUES(?,?,?,?,?,?,?,?,?,?)
""",
(
operation_id, grant.token_id, grant.task_id, key, digest,
data["branch"], data["path"], content_sha,
data["expected_head_oid"], "PENDING",
),
)
db.commit()
return self.get(key)
def get(self, key: str) -> dict[str, Any]:
with self.connect() as db:
row = db.execute(
"SELECT * FROM writer_operations WHERE idempotency_key=?", (key,)
).fetchone()
if row is None:
raise KeyError(key)
return dict(row)
def update(self, key: str, state: str, **fields: Any) -> dict[str, Any]:
if state not in STATES:
raise ValueError("invalid writer operation state")
allowed = {"commit_oid", "push_status", "verification_status", "error_code"}
assignments = ["state=?", "updated_at=CURRENT_TIMESTAMP"]
values: list[Any] = [state]
for name, value in fields.items():
if name not in allowed:
raise ValueError("invalid ledger field")
assignments.append(f"{name}=?")
values.append(value)
values.append(key)
with self.connect() as db:
db.execute("BEGIN IMMEDIATE")
db.execute(
f"UPDATE writer_operations SET {','.join(assignments)} WHERE idempotency_key=?",
values,
)
db.commit()
return self.get(key)
class AtomicCommitFile:
def __init__(
self,
*,
workspace: Path,
bare_repo: Path,
ledger: OperationLedger,
remote: str,
audit,
crash_after_state: str | None = None,
):
self.workspace = workspace
self.bare_repo = bare_repo
self.ledger = ledger
self.remote = remote
self.audit = audit
self.crash_after_state = crash_after_state
def _git(self, *args: str, check: bool = True) -> str:
result = subprocess.run(
["git", *args], cwd=self.workspace, text=True, capture_output=True
)
if check and result.returncode != 0:
raise CommitFileError("writer_git_operation_failed", 500)
return result.stdout.strip()
def _transition(self, key: str, state: str, **fields: Any) -> dict[str, Any]:
record = self.ledger.update(key, state, **fields)
if self.crash_after_state == state:
raise SimulatedCrash(state)
return record
def _validate(self, data: Any) -> dict[str, Any]:
if not isinstance(data, dict):
raise CommitFileError("writer_request_object_required")
required = ("branch", "path", "content", "message", "expected_head_oid", "idempotency_key")
if any(field not in data for field in required):
raise CommitFileError("writer_request_incomplete")
if data.get("force") or data.get("force_push"):
raise CommitFileError("isolated_writer_force_rejected", 403)
if set(data) != set(required):
raise CommitFileError("writer_request_fields_rejected")
item = {field: data[field] for field in required}
if item["branch"] != policy.allowed_branch():
raise CommitFileError("isolated_writer_branch_rejected", 403)
try:
item["path"] = policy.validate_path(item["path"], self.workspace)
except PermissionError as exc:
raise CommitFileError(str(exc), 403) from None
if not isinstance(item["content"], str) or "\x00" in item["content"]:
raise CommitFileError("writer_content_rejected")
try:
policy.validate_content_for_path(item["path"], item["content"])
except PermissionError as exc:
raise CommitFileError(str(exc), 403) from None
if len(item["content"].encode("utf-8")) > 1_048_576:
raise CommitFileError("writer_content_too_large", 413)
message = item["message"]
if (
not isinstance(message, str)
or not 1 <= len(message) <= 200
or "\x00" in message
or "\n" in message
or "\r" in message
):
raise CommitFileError("writer_message_rejected")
if not isinstance(item["expected_head_oid"], str) or not OID_RE.fullmatch(item["expected_head_oid"]):
raise CommitFileError("writer_expected_head_rejected")
if not isinstance(item["idempotency_key"], str) or not IDEMPOTENCY_RE.fullmatch(item["idempotency_key"]):
raise CommitFileError("writer_idempotency_key_rejected")
return item
def _atomic_write(self, relative: str, content: str) -> None:
target = self.workspace / relative
target.parent.mkdir(parents=True, exist_ok=True)
fd, temporary = tempfile.mkstemp(prefix=".writer-", dir=target.parent)
try:
with os.fdopen(fd, "wb", closefd=True) as stream:
stream.write(content.encode("utf-8"))
stream.flush()
os.fsync(stream.fileno())
os.replace(temporary, target)
parent_fd = os.open(target.parent, os.O_RDONLY | getattr(os, "O_DIRECTORY", 0))
try:
os.fsync(parent_fd)
finally:
os.close(parent_fd)
finally:
if os.path.exists(temporary):
os.unlink(temporary)
def _verify_remote(self, branch: str, oid: str) -> None:
bare = subprocess.run(
["git", "--git-dir", str(self.bare_repo), "rev-parse", f"refs/heads/{branch}"],
text=True, capture_output=True,
)
if bare.returncode != 0 or bare.stdout.strip() != oid:
raise CommitFileError("writer_authoritative_bare_oid_mismatch", 500)
remote = subprocess.run(
["git", "ls-remote", "--heads", self.remote, f"refs/heads/{branch}"],
cwd=self.workspace, text=True, capture_output=True,
)
if remote.returncode != 0:
raise CommitFileError("writer_gitea_verification_failed", 503)
fields = remote.stdout.strip().split()
if len(fields) != 2:
raise CommitFileError("writer_gitea_ref_missing", 503)
if fields[0] != oid:
raise CommitFileError("writer_gitea_oid_conflict", 409)
def execute(self, grant: Any, raw_data: Any) -> dict[str, Any]:
data = self._validate(raw_data)
digest = canonical_digest(data)
branch = data["branch"]
with _LOCKS_GUARD:
lock = _LOCKS.setdefault(branch, threading.Lock())
lock_path = self.ledger.path.parent / f"{hashlib.sha256(branch.encode()).hexdigest()}.lock"
with lock, open(lock_path, "a+b") as branch_lock:
fcntl.flock(branch_lock.fileno(), fcntl.LOCK_EX)
return self._execute_locked(grant, data, digest)
def _execute_locked(self, grant: Any, data: dict[str, Any], digest: str) -> dict[str, Any]:
key = data["idempotency_key"]
record = self.ledger.get_or_create(grant, data, digest)
if record["state"] == "COMPLETED":
return self._result(record, idempotent=True)
if self._git("rev-parse", "--abbrev-ref", "HEAD") != data["branch"]:
record = self._transition(
key, "MANUAL_REVIEW_REQUIRED", error_code="writer_current_branch_conflict"
)
self._audit(grant, data, digest, record, "manual_review_required")
raise CommitFileError("writer_current_branch_conflict", 409)
head = self._git("rev-parse", "HEAD")
commit_oid = record.get("commit_oid")
if head != record["pre_head_oid"] and head != commit_oid:
record = self._transition(
key, "MANUAL_REVIEW_REQUIRED", error_code="writer_branch_head_conflict"
)
self._audit(grant, data, digest, record, "manual_review_required")
raise CommitFileError("writer_branch_head_conflict", 409)
try:
state = record["state"]
if state == "MANUAL_REVIEW_REQUIRED":
raise CommitFileError("writer_manual_review_required", 409)
if state == "FAILED_RECOVERABLE":
if record.get("push_status") == "confirmed":
state = "PUSHED"
else:
state = "COMMITTED" if commit_oid else "FILE_WRITTEN"
if state == "PENDING":
self._atomic_write(data["path"], data["content"])
record = self._transition(key, "FILE_WRITTEN")
state = record["state"]
if state == "FILE_WRITTEN":
actual_sha = hashlib.sha256(
(self.workspace / data["path"]).read_bytes()
).hexdigest()
if actual_sha != record["content_sha256"]:
raise CommitFileError("writer_file_content_conflict", 409)
staged_before = self._git("diff", "--cached", "--name-only", check=False).splitlines()
if staged_before:
raise CommitFileError("writer_extra_staged_files_rejected", 409)
self._git("add", "--", data["path"])
staged = self._git("diff", "--cached", "--name-only").splitlines()
if staged != [data["path"]]:
raise CommitFileError("writer_extra_staged_files_rejected", 409)
self._git("commit", "-m", data["message"])
commit_oid = self._git("rev-parse", "HEAD")
record = self._transition(key, "COMMITTED", commit_oid=commit_oid)
state = record["state"]
if state == "COMMITTED":
commit_oid = str(record["commit_oid"])
pushed = subprocess.run(
["git", "push", "--porcelain", self.remote, f"HEAD:refs/heads/{data['branch']}"],
cwd=self.workspace, text=True, capture_output=True,
)
if pushed.returncode != 0:
self._transition(
key, "FAILED_RECOVERABLE",
push_status="failed", error_code="writer_gitea_push_failed",
)
raise CommitFileError("writer_gitea_push_failed", 503)
record = self._transition(key, "PUSHED", push_status="confirmed")
state = record["state"]
if state == "PUSHED":
commit_oid = str(record["commit_oid"])
self._verify_remote(data["branch"], commit_oid)
record = self._transition(
key, "VERIFIED", verification_status="bare+gitea"
)
state = record["state"]
if state == "VERIFIED":
record = self._transition(key, "COMPLETED", error_code=None)
self._audit(grant, data, digest, record, "completed")
return self._result(record, idempotent=False)
except SimulatedCrash:
raise
except CommitFileError as exc:
latest = self.ledger.get(key)
if latest["state"] not in {"FAILED_RECOVERABLE", "MANUAL_REVIEW_REQUIRED"}:
failure_state = (
"MANUAL_REVIEW_REQUIRED"
if exc.code in {
"writer_gitea_oid_conflict",
"writer_file_content_conflict",
"writer_current_branch_conflict",
}
else "FAILED_RECOVERABLE"
)
latest = self._transition(key, failure_state, error_code=exc.code)
self._audit(grant, data, digest, latest, "failed")
raise
def _audit(self, grant: Any, data: dict[str, Any], digest: str, record: dict[str, Any], result: str) -> None:
self.audit({
"token_id": grant.token_id,
"task_id": grant.task_id,
"audience": grant.record["audience"],
"source_ip": grant.record.get("_source_ip", "socket-peer"),
"operation": grant.operation,
"branch": data["branch"],
"path": data["path"],
"idempotency_key": data["idempotency_key"],
"operation_digest": digest,
"path_policy_sha256": policy.configured_path_policy_sha256(),
"pre_head_oid": record["pre_head_oid"],
"commit_oid": record.get("commit_oid"),
"result": result,
})
@staticmethod
def _result(record: dict[str, Any], *, idempotent: bool) -> dict[str, Any]:
return {
"status": "completed",
"state": record["state"],
"operation_id": record["operation_id"],
"branch": record["branch"],
"path": record["path"],
"commit_oid": record.get("commit_oid"),
"push_status": record.get("push_status"),
"verification_status": record.get("verification_status"),
"idempotent": idempotent,
}
@@ -0,0 +1,422 @@
"""Strict service-level exact-path policy for isolated Git writers."""
from __future__ import annotations
import grp
import hashlib
import json
import os
import re
import stat
from dataclasses import dataclass
from functools import lru_cache
from pathlib import Path, PurePosixPath, PureWindowsPath
from typing import Any
from urllib.parse import unquote
MAX_POLICY_BYTES = 65_536
POLICY_FIELDS = frozenset(
{
"schema_version",
"policy_id",
"audience",
"repository_identity",
"allowed_branch",
"allowed_operations",
}
)
OPERATION_FIELDS = frozenset({"exact_paths", "max_content_bytes", "content_profile"})
SUPPORTED_OPERATIONS = frozenset({"commit_file.atomic"})
SUPPORTED_CONTENT_PROFILES = frozenset({"systemd-codex-review-worker-v1"})
_SECRET_RE = re.compile(
r"(?i)(authorization\s*:|bearer\s+|(?:token|password|secret)\s*[:=])"
)
_SYSTEMD_NAME_RE = re.compile(r"[A-Za-z][A-Za-z0-9]*\Z")
_SYSTEMD_MAX_LINE_BYTES = 4096
_SYSTEMD_ALLOWED_DIRECTIVES = {
"Unit": frozenset({"Description", "After", "Wants"}),
"Service": frozenset(
{
"Type",
"User",
"Group",
"WorkingDirectory",
"Environment",
"ExecStart",
"Restart",
"RestartSec",
"NoNewPrivileges",
"PrivateTmp",
"PrivateDevices",
"ProtectSystem",
"ProtectHome",
"ProtectKernelTunables",
"ProtectKernelModules",
"ProtectControlGroups",
"LockPersonality",
"RestrictSUIDSGID",
"RestrictRealtime",
"MemoryDenyWriteExecute",
"RuntimeDirectory",
"StateDirectory",
"CacheDirectory",
"ReadOnlyPaths",
"ReadWritePaths",
"UMask",
}
),
"Install": frozenset({"WantedBy"}),
}
_SYSTEMD_MULTI_DIRECTIVES = frozenset(
{
("Service", "Environment"),
("Service", "ReadOnlyPaths"),
("Service", "ReadWritePaths"),
}
)
_SYSTEMD_EXACT_SINGLE_VALUES = {
("Unit", "Description"): "AIOS Codex Review/Test Worker",
("Unit", "After"): "network-online.target",
("Unit", "Wants"): "network-online.target",
("Service", "Type"): "simple",
("Service", "User"): "aiman",
("Service", "Group"): "gitops",
(
"Service",
"WorkingDirectory",
): "/usr/libexec/aios-codex-review-test-worker/current",
(
"Service",
"ExecStart",
): "/usr/bin/python3 -m task_plane.workers.codex_review_test",
("Service", "Restart"): "on-failure",
("Service", "RestartSec"): "2",
("Service", "NoNewPrivileges"): "true",
("Service", "PrivateTmp"): "true",
("Service", "PrivateDevices"): "true",
("Service", "ProtectSystem"): "strict",
("Service", "ProtectHome"): "true",
("Service", "ProtectKernelTunables"): "true",
("Service", "ProtectKernelModules"): "true",
("Service", "ProtectControlGroups"): "true",
("Service", "LockPersonality"): "true",
("Service", "RestrictSUIDSGID"): "true",
("Service", "RestrictRealtime"): "true",
("Service", "MemoryDenyWriteExecute"): "true",
("Service", "RuntimeDirectory"): "aios-codex-review-test-worker",
("Service", "StateDirectory"): "aios-codex-review-test-worker",
("Service", "CacheDirectory"): "aios-codex-review-test-worker",
("Service", "UMask"): "0077",
("Install", "WantedBy"): "multi-user.target",
}
_SYSTEMD_EXACT_MULTI_VALUES = {
("Service", "Environment"): (
"AIOS_CODEX_WORKER_MODE=shadow",
"AIOS_TASK_PLANE_EXECUTION_ENABLED=false",
"PYTHONDONTWRITEBYTECODE=1",
),
("Service", "ReadOnlyPaths"): (
"/data/aios-gitops/repo.git",
"/usr/libexec/aios-codex-review-test-worker",
),
("Service", "ReadWritePaths"): (
"/run/aios-codex-review-test-worker",
"/var/lib/aios-codex-review-test-worker",
"/var/cache/aios-codex-review-test-worker",
),
}
class PathPolicyError(PermissionError):
pass
@dataclass(frozen=True)
class OperationPolicy:
exact_paths: tuple[str, ...]
max_content_bytes: int
content_profile: str
@dataclass(frozen=True)
class ServicePathPolicy:
schema_version: int
policy_id: str
audience: str
repository_identity: str
allowed_branch: str
allowed_operations: dict[str, OperationPolicy]
sha256: str
def operation(self, name: str) -> OperationPolicy:
try:
return self.allowed_operations[name]
except KeyError:
raise PathPolicyError("isolated_writer_service_operation_rejected") from None
def require_exact_path(self, operation: str, path: str) -> OperationPolicy:
selected = self.operation(operation)
if path not in selected.exact_paths:
raise PathPolicyError("isolated_writer_service_path_rejected")
return selected
def _canonical_json(value: Any) -> bytes:
return (
json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
+ "\n"
).encode("utf-8")
def validate_relative_path_syntax(path: str) -> str:
if not isinstance(path, str):
raise PathPolicyError("isolated_writer_path_rejected")
if not path or path != path.strip() or "\x00" in path or "\\" in path:
raise PathPolicyError("isolated_writer_path_rejected")
try:
decoded = unquote(path, errors="strict")
except (UnicodeDecodeError, ValueError):
raise PathPolicyError("isolated_writer_path_rejected") from None
if decoded != path:
raise PathPolicyError("isolated_writer_path_rejected")
posix = PurePosixPath(path)
windows = PureWindowsPath(path)
parts = path.split("/")
if (
posix.is_absolute()
or windows.is_absolute()
or bool(windows.drive)
or any(part in {"", ".", ".."} for part in parts)
):
raise PathPolicyError("isolated_writer_path_rejected")
return path
def _require_fields(value: dict[str, Any], allowed: frozenset[str], code: str) -> None:
if set(value) != allowed:
raise PathPolicyError(code)
def _parse_policy(document: Any, canonical: bytes) -> ServicePathPolicy:
if not isinstance(document, dict):
raise PathPolicyError("isolated_writer_path_policy_object_required")
_require_fields(document, POLICY_FIELDS, "isolated_writer_path_policy_fields_rejected")
if document["schema_version"] != 1:
raise PathPolicyError("isolated_writer_path_policy_schema_rejected")
text_fields = (
"policy_id",
"audience",
"repository_identity",
"allowed_branch",
)
if any(
not isinstance(document[field], str) or not document[field].strip()
for field in text_fields
):
raise PathPolicyError("isolated_writer_path_policy_value_rejected")
raw_operations = document["allowed_operations"]
if not isinstance(raw_operations, dict) or not raw_operations:
raise PathPolicyError("isolated_writer_path_policy_operations_rejected")
if not set(raw_operations).issubset(SUPPORTED_OPERATIONS):
raise PathPolicyError("isolated_writer_path_policy_operations_rejected")
operations: dict[str, OperationPolicy] = {}
for name, raw in raw_operations.items():
if not isinstance(raw, dict):
raise PathPolicyError("isolated_writer_path_policy_operation_rejected")
_require_fields(
raw,
OPERATION_FIELDS,
"isolated_writer_path_policy_operation_fields_rejected",
)
paths = raw["exact_paths"]
if (
not isinstance(paths, list)
or not paths
or any(not isinstance(item, str) for item in paths)
):
raise PathPolicyError("isolated_writer_path_policy_paths_rejected")
validated = tuple(validate_relative_path_syntax(item) for item in paths)
if len(set(validated)) != len(validated):
raise PathPolicyError("isolated_writer_path_policy_paths_rejected")
maximum = raw["max_content_bytes"]
if not isinstance(maximum, int) or isinstance(maximum, bool) or not 1 <= maximum <= 1_048_576:
raise PathPolicyError("isolated_writer_path_policy_size_rejected")
profile = raw["content_profile"]
if profile not in SUPPORTED_CONTENT_PROFILES:
raise PathPolicyError("isolated_writer_path_policy_profile_rejected")
operations[name] = OperationPolicy(validated, maximum, profile)
return ServicePathPolicy(
schema_version=1,
policy_id=document["policy_id"],
audience=document["audience"],
repository_identity=document["repository_identity"],
allowed_branch=document["allowed_branch"],
allowed_operations=operations,
sha256=hashlib.sha256(canonical).hexdigest(),
)
def load_path_policy(
path: Path,
*,
expected_owner_uid: int = 0,
expected_group_gid: int | None = None,
expected_parent_mode: int = 0o750,
expected_file_mode: int = 0o640,
) -> ServicePathPolicy:
lexical = Path(os.path.abspath(str(path)))
if lexical.is_symlink():
raise PathPolicyError("isolated_writer_path_policy_symlink_rejected")
try:
parent_info = lexical.parent.stat(follow_symlinks=False)
file_info = lexical.stat(follow_symlinks=False)
except FileNotFoundError:
raise PathPolicyError("isolated_writer_path_policy_missing") from None
if stat.S_ISLNK(parent_info.st_mode) or not stat.S_ISDIR(parent_info.st_mode):
raise PathPolicyError("isolated_writer_path_policy_parent_rejected")
if stat.S_ISLNK(file_info.st_mode) or not stat.S_ISREG(file_info.st_mode):
raise PathPolicyError("isolated_writer_path_policy_file_rejected")
if lexical.resolve(strict=True) != lexical:
raise PathPolicyError("isolated_writer_path_policy_symlink_rejected")
if (
parent_info.st_uid != expected_owner_uid
or stat.S_IMODE(parent_info.st_mode) != expected_parent_mode
):
raise PathPolicyError("isolated_writer_path_policy_parent_permissions_rejected")
if (
file_info.st_uid != expected_owner_uid
or stat.S_IMODE(file_info.st_mode) != expected_file_mode
):
raise PathPolicyError("isolated_writer_path_policy_permissions_rejected")
if expected_group_gid is not None and (
parent_info.st_gid != expected_group_gid or file_info.st_gid != expected_group_gid
):
raise PathPolicyError("isolated_writer_path_policy_group_rejected")
if file_info.st_size <= 0 or file_info.st_size > MAX_POLICY_BYTES:
raise PathPolicyError("isolated_writer_path_policy_file_size_rejected")
raw = lexical.read_bytes()
try:
document = json.loads(raw.decode("utf-8"))
except (UnicodeDecodeError, json.JSONDecodeError):
raise PathPolicyError("isolated_writer_path_policy_json_rejected") from None
canonical = _canonical_json(document)
if raw != canonical:
raise PathPolicyError("isolated_writer_path_policy_not_canonical")
return _parse_policy(document, canonical)
@lru_cache(maxsize=1)
def configured_path_policy() -> ServicePathPolicy | None:
configured = os.getenv("AIOS_GITOPS_PATH_POLICY_FILE", "").strip()
if not configured:
return None
if not Path(configured).is_absolute():
raise PathPolicyError("isolated_writer_path_policy_path_rejected")
try:
gitops_gid = grp.getgrnam("gitops").gr_gid
except KeyError:
raise PathPolicyError("isolated_writer_path_policy_group_rejected") from None
loaded = load_path_policy(Path(configured), expected_group_gid=gitops_gid)
if loaded.audience != os.getenv("AIOS_GITOPS_SERVICE_AUDIENCE", ""):
raise PathPolicyError("isolated_writer_path_policy_audience_rejected")
if loaded.repository_identity != os.getenv("AIOS_GITOPS_REPOSITORY_IDENTITY", ""):
raise PathPolicyError("isolated_writer_path_policy_repository_rejected")
if loaded.allowed_branch != os.getenv("AIOS_GITOPS_ALLOWED_BRANCH", ""):
raise PathPolicyError("isolated_writer_path_policy_branch_rejected")
return loaded
def clear_path_policy_cache() -> None:
configured_path_policy.cache_clear()
def require_service_path(operation: str, path: str) -> OperationPolicy:
loaded = configured_path_policy()
if loaded is None:
raise PathPolicyError("isolated_writer_service_path_policy_required")
return loaded.require_exact_path(operation, path)
def configured_path_policy_sha256() -> str | None:
loaded = configured_path_policy()
return None if loaded is None else loaded.sha256
def validate_content(operation: str, path: str, content: str) -> None:
selected = require_service_path(operation, path)
try:
encoded = content.encode("utf-8", errors="strict")
except (AttributeError, UnicodeEncodeError):
raise PathPolicyError("isolated_writer_systemd_profile_rejected") from None
if len(encoded) > selected.max_content_bytes:
raise PathPolicyError("isolated_writer_service_content_too_large")
if selected.content_profile == "systemd-codex-review-worker-v1":
_validate_systemd_codex_review_worker(content)
return
raise PathPolicyError("isolated_writer_path_policy_profile_rejected")
def _validate_systemd_codex_review_worker(content: str) -> None:
if not isinstance(content, str):
raise PathPolicyError("isolated_writer_systemd_profile_rejected")
try:
content.encode("utf-8", errors="strict")
except UnicodeEncodeError:
raise PathPolicyError("isolated_writer_systemd_profile_rejected") from None
if (
"\x00" in content
or "\r" in content
or _SECRET_RE.search(content)
or not content.endswith("\n")
):
raise PathPolicyError("isolated_writer_systemd_profile_rejected")
sections: set[str] = set()
values: dict[tuple[str, str], list[str]] = {}
section: str | None = None
for line in content.splitlines():
if len(line.encode("utf-8")) > _SYSTEMD_MAX_LINE_BYTES:
raise PathPolicyError("isolated_writer_systemd_profile_rejected")
if not line:
continue
if line[0].isspace() or line.startswith(("#", ";")):
raise PathPolicyError("isolated_writer_systemd_profile_rejected")
if line.startswith("[") and line.endswith("]"):
candidate = line[1:-1]
if (
not _SYSTEMD_NAME_RE.fullmatch(candidate)
or candidate not in _SYSTEMD_ALLOWED_DIRECTIVES
or candidate in sections
):
raise PathPolicyError("isolated_writer_systemd_profile_rejected")
sections.add(candidate)
section = candidate
continue
if section is None or "=" not in line:
raise PathPolicyError("isolated_writer_systemd_profile_rejected")
directive, value = line.split("=", 1)
if (
not _SYSTEMD_NAME_RE.fullmatch(directive)
or directive not in _SYSTEMD_ALLOWED_DIRECTIVES[section]
or not value
or value != value.strip()
):
raise PathPolicyError("isolated_writer_systemd_profile_rejected")
key = (section, directive)
if key in values and key not in _SYSTEMD_MULTI_DIRECTIVES:
raise PathPolicyError("isolated_writer_systemd_profile_rejected")
values.setdefault(key, []).append(value)
if sections != set(_SYSTEMD_ALLOWED_DIRECTIVES):
raise PathPolicyError("isolated_writer_systemd_profile_rejected")
if set(values) != set(_SYSTEMD_EXACT_SINGLE_VALUES) | set(
_SYSTEMD_EXACT_MULTI_VALUES
):
raise PathPolicyError("isolated_writer_systemd_profile_rejected")
for key, expected in _SYSTEMD_EXACT_SINGLE_VALUES.items():
if values.get(key) != [expected]:
raise PathPolicyError("isolated_writer_systemd_profile_rejected")
for key, expected in _SYSTEMD_EXACT_MULTI_VALUES.items():
observed = values.get(key, [])
if len(observed) != len(set(observed)) or tuple(observed) != expected:
raise PathPolicyError("isolated_writer_systemd_profile_rejected")
@@ -0,0 +1,241 @@
"""Digest-only, task-scoped token registry for isolated writers."""
from __future__ import annotations
import argparse
import hashlib
import hmac
import json
import os
import grp
import pwd
import secrets
import sqlite3
import stat
import threading
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
_REGISTRY_INIT_LOCK = threading.Lock()
def utc_now() -> str:
return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
def token_digest(token: str) -> str:
return hashlib.sha256(token.encode("utf-8")).hexdigest()
class TokenRegistry:
def __init__(self, path: Path | str):
self.path = Path(path)
self._prepare()
def _prepare(self) -> None:
self.path.parent.mkdir(parents=True, exist_ok=True)
if self.path.parent.is_symlink() or self.path.is_symlink():
raise PermissionError("writer_token_registry_symlink_rejected")
with _REGISTRY_INIT_LOCK:
with self._connect() as db:
db.executescript(
"""
CREATE TABLE IF NOT EXISTS token_grants (
token_id TEXT PRIMARY KEY,
token_digest TEXT NOT NULL UNIQUE,
grant_json TEXT NOT NULL,
revoked INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS token_uses (
token_id TEXT NOT NULL,
operation TEXT NOT NULL,
use_count INTEGER NOT NULL DEFAULT 0,
PRIMARY KEY(token_id, operation),
FOREIGN KEY(token_id) REFERENCES token_grants(token_id)
);
"""
)
os.chmod(self.path, 0o600)
def _connect(self) -> sqlite3.Connection:
db = sqlite3.connect(self.path, timeout=30, isolation_level=None)
db.row_factory = sqlite3.Row
db.execute("PRAGMA journal_mode=WAL")
db.execute("PRAGMA synchronous=FULL")
db.execute("PRAGMA foreign_keys=ON")
return db
def issue(self, grant: dict[str, Any], token: str | None = None) -> tuple[str, dict[str, Any]]:
value = token or secrets.token_urlsafe(32)
if len(value.encode("utf-8")) < 32:
raise ValueError("writer_token_entropy_insufficient")
record = dict(grant)
record["schema_version"] = 2
record["token_digest"] = token_digest(value)
record["revoked"] = False
record.setdefault("created_at", utc_now())
required = (
"token_id",
"audience",
"task_id",
"source_cidrs",
"allowed_branch",
"approved_base_oid",
"allowed_operations",
"allowed_paths",
"not_before",
"expires_at",
"max_uses",
"issued_by",
)
if any(not record.get(field) for field in required):
raise ValueError("writer_token_grant_incomplete")
encoded = json.dumps(record, sort_keys=True, separators=(",", ":"))
with self._connect() as db:
db.execute("BEGIN IMMEDIATE")
try:
db.execute(
"""
INSERT INTO token_grants(token_id, token_digest, grant_json, revoked, created_at)
VALUES(?,?,?,?,?)
""",
(
str(record["token_id"]),
str(record["token_digest"]),
encoded,
0,
str(record["created_at"]),
),
)
db.execute("COMMIT")
except Exception:
db.execute("ROLLBACK")
raise
return value, record
def find(self, token: str) -> dict[str, Any] | None:
candidate = token_digest(token)
with self._connect() as db:
rows = db.execute(
"SELECT token_digest, grant_json, revoked FROM token_grants"
).fetchall()
match = None
for row in rows:
if hmac.compare_digest(str(row["token_digest"]), candidate):
match = dict(json.loads(str(row["grant_json"])))
match["revoked"] = bool(row["revoked"])
return match
def consume(self, token_id: str, operation: str, maximum: int) -> int:
if maximum < 1:
raise PermissionError("writer_token_operation_exhausted")
with self._connect() as db:
db.execute("BEGIN IMMEDIATE")
try:
row = db.execute(
"SELECT revoked FROM token_grants WHERE token_id=?",
(token_id,),
).fetchone()
if row is None or bool(row["revoked"]):
raise PermissionError("writer_token_revoked")
used_row = db.execute(
"SELECT use_count FROM token_uses WHERE token_id=? AND operation=?",
(token_id, operation),
).fetchone()
used = int(used_row["use_count"]) if used_row else 0
if used >= maximum:
raise PermissionError("writer_token_operation_exhausted")
current = used + 1
db.execute(
"""
INSERT INTO token_uses(token_id, operation, use_count)
VALUES(?,?,?)
ON CONFLICT(token_id, operation)
DO UPDATE SET use_count=excluded.use_count
""",
(token_id, operation, current),
)
db.execute("COMMIT")
return current
except Exception:
db.execute("ROLLBACK")
raise
def revoke(self, token_id: str) -> bool:
with self._connect() as db:
db.execute("BEGIN IMMEDIATE")
changed = db.execute(
"UPDATE token_grants SET revoked=1 WHERE token_id=? AND revoked=0",
(token_id,),
).rowcount
db.execute("COMMIT")
return bool(changed)
def usage(self, token_id: str, operation: str) -> int:
with self._connect() as db:
row = db.execute(
"SELECT use_count FROM token_uses WHERE token_id=? AND operation=?",
(token_id, operation),
).fetchone()
return int(row["use_count"]) if row else 0
def _write_secret_file(
path: Path,
token: str,
*,
owner: str | None = None,
group: str | None = None,
) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
if path.parent.is_symlink() or path.is_symlink():
raise PermissionError("writer_token_file_symlink_rejected")
flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL
if hasattr(os, "O_NOFOLLOW"):
flags |= os.O_NOFOLLOW
fd = os.open(path, flags, 0o600)
try:
if not stat.S_ISREG(os.fstat(fd).st_mode):
raise PermissionError("writer_token_file_not_regular")
if owner is not None or group is not None:
uid = pwd.getpwnam(owner).pw_uid if owner is not None else -1
gid = grp.getgrnam(group).gr_gid if group is not None else -1
os.fchown(fd, uid, gid)
os.write(fd, token.encode("utf-8"))
os.fsync(fd)
finally:
os.close(fd)
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--registry", required=True)
sub = parser.add_subparsers(dest="command", required=True)
issue = sub.add_parser("issue")
issue.add_argument("--grant-file", required=True)
issue.add_argument("--token-file", required=True)
issue.add_argument("--owner")
issue.add_argument("--group")
revoke = sub.add_parser("revoke")
revoke.add_argument("--token-id", required=True)
args = parser.parse_args(argv)
registry = TokenRegistry(args.registry)
if args.command == "issue":
grant = json.loads(Path(args.grant_file).read_text(encoding="utf-8"))
token, record = registry.issue(grant)
_write_secret_file(
Path(args.token_file),
token,
owner=args.owner,
group=args.group,
)
print(json.dumps({"token_id": record["token_id"], "created": True}, sort_keys=True))
return 0
changed = registry.revoke(args.token_id)
print(json.dumps({"token_id": args.token_id, "revoked": changed}, sort_keys=True))
return 0
if __name__ == "__main__":
raise SystemExit(main())
+260 -57
View File
@@ -46,6 +46,57 @@ def redact(value: Any) -> str:
return text
class EnvironmentAllowList:
"""Build a subprocess environment without inheriting process secrets."""
SENSITIVE_NAME_RE = re.compile(
r"(?i)(authorization|credential|password|secret|token|api[_-]?key|"
r"askpass|auth[_-]?sock|ld_preload|pythonpath|database_url)"
)
def __init__(self, allowed_names: set[str] | frozenset[str]):
self.allowed_names = frozenset(str(name) for name in allowed_names)
def build(self, values: dict[str, str]) -> dict[str, str]:
result: dict[str, str] = {}
for name, raw in values.items():
if (
name not in self.allowed_names
or self.SENSITIVE_NAME_RE.search(name)
):
raise ValueError(f"environment variable is not allowed: {name}")
value = str(raw)
if redact(value) != value:
raise ValueError(
f"environment value is not allowed: {name}"
)
result[name] = value
return result
SAFE_PROCESS_ENVIRONMENT = EnvironmentAllowList(
{
"PATH",
"LANG",
"LC_ALL",
"TZ",
"HOME",
"GIT_OPTIONAL_LOCKS",
"PYTHONDONTWRITEBYTECODE",
}
).build(
{
"PATH": "/usr/bin:/bin",
"LANG": "C.UTF-8",
"LC_ALL": "C.UTF-8",
"TZ": "UTC",
"HOME": "/var/empty",
"GIT_OPTIONAL_LOCKS": "0",
"PYTHONDONTWRITEBYTECODE": "1",
}
)
def _sanitize(value: Any) -> Any:
if isinstance(value, dict):
return {
@@ -510,11 +561,16 @@ class DeploymentController:
ledger: ApprovalLedger,
policy: SilentReleasePolicy,
store: ReleaseCandidateStore | None = None,
*,
allow_legacy_unsigned_approval: bool = False,
):
self.deployment = deployment
self.ledger = ledger
self.policy = policy
self.store = store
self.allow_legacy_unsigned_approval = (
allow_legacy_unsigned_approval
)
def _save(self, candidate: ReleaseCandidate) -> None:
if self.store:
@@ -525,6 +581,11 @@ class DeploymentController:
candidate: ReleaseCandidate,
approval: ApprovalEnvelope,
) -> dict[str, Any]:
if not self.allow_legacy_unsigned_approval:
raise ApprovalError(
"legacy unsigned runtime approval is disabled; "
"use SignedReleasePlanApproval"
)
if candidate.state != ReleaseState.READY_FOR_APPROVAL:
raise ApprovalError("candidate is not ready for approval")
if not self.policy.requires_owner("production_runtime_switch"):
@@ -675,7 +736,7 @@ class CommandReleaseBuilder:
text=True,
capture_output=True,
timeout=self.config.timeout,
env={**os.environ, "PYTHONDONTWRITEBYTECODE": "1"},
env=dict(SAFE_PROCESS_ENVIRONMENT),
)
except (OSError, subprocess.TimeoutExpired) as exc:
raise RuntimeError("release build unavailable") from exc
@@ -741,12 +802,25 @@ class TagController:
adapter: TagAdapter,
policy: SilentReleasePolicy,
ledger: ApprovalLedger | None,
*,
allow_legacy_unsigned_approval: bool = False,
):
self.adapter = adapter
self.policy = policy
self.ledger = ledger
self.allow_legacy_unsigned_approval = (
allow_legacy_unsigned_approval
)
def _require_legacy_test_mode(self) -> None:
if not self.allow_legacy_unsigned_approval:
raise ApprovalError(
"legacy unsigned tag approval is disabled; "
"use SignedReleasePlanApproval"
)
def approve(self, envelope: TagApprovalEnvelope) -> TagApprovalEnvelope:
self._require_legacy_test_mode()
if not self.policy.requires_owner("production_tag"):
raise ApprovalError("production tag is not owner governed")
if not self.policy.is_forbidden("tag_rewrite"):
@@ -757,6 +831,7 @@ class TagController:
def execute(
self, approved: TagApprovalEnvelope
) -> dict[str, Any]:
self._require_legacy_test_mode()
approved.validate()
existing = self.adapter.lookup(approved.version)
expected = (approved.tag_oid, approved.target_oid)
@@ -793,6 +868,7 @@ class TagController:
def verify(
self, approved: TagApprovalEnvelope
) -> dict[str, Any]:
self._require_legacy_test_mode()
actual = self.adapter.verify(approved.version)
expected = (approved.tag_oid, approved.target_oid)
if actual != expected:
@@ -837,7 +913,7 @@ class CommandReleaseProbe:
text=True,
capture_output=True,
timeout=300,
env={**os.environ, "GIT_OPTIONAL_LOCKS": "0"},
env=dict(SAFE_PROCESS_ENVIRONMENT),
)
except (OSError, subprocess.TimeoutExpired) as exc:
raise RuntimeError("COMMAND_UNAVAILABLE") from exc
@@ -1000,6 +1076,13 @@ class SystemdDeploymentConfig:
health_url: str
command_prefix: tuple[str, ...] = ()
dropin_name: str = "aios-gitops-model-api-production.conf"
service_allowlist: tuple[str, ...] = ("aios-gitops-model-api",)
dropin_allowlist: tuple[str, ...] = (
"aios-gitops-model-api-production.conf",
)
deployment_lock: Path = Path(
"/run/lock/aios-silent-release-deployment.lock"
)
class SystemdDeploymentAdapter:
@@ -1007,39 +1090,134 @@ class SystemdDeploymentAdapter:
def __init__(self, config: SystemdDeploymentConfig):
self.config = config
self._lock_handle = None
self._restart_evidence: dict[str, Any] = {}
if self.config.service_name not in self.config.service_allowlist:
raise ValueError("systemd service is not allowlisted")
if (
self.config.dropin_name not in self.config.dropin_allowlist
or "/" in self.config.dropin_name
or "\\" in self.config.dropin_name
):
raise ValueError("systemd drop-in is not allowlisted")
def _acquire_lock(self) -> None:
if self._lock_handle is not None:
raise RuntimeError("deployment lock is already held")
path = self.config.deployment_lock
path.parent.mkdir(parents=True, exist_ok=True)
handle = path.open("a+", encoding="utf-8")
try:
import fcntl
fcntl.flock(handle.fileno(), fcntl.LOCK_EX)
except ImportError:
pass
self._lock_handle = handle
def _release_lock(self) -> None:
handle = self._lock_handle
self._lock_handle = None
if handle is None:
return
try:
import fcntl
fcntl.flock(handle.fileno(), fcntl.LOCK_UN)
except ImportError:
pass
handle.close()
def _service_identity(self) -> dict[str, Any]:
result = subprocess.run(
[
*self.config.command_prefix,
"systemctl",
"show",
self.config.service_name,
"--property=MainPID",
"--property=ActiveEnterTimestampMonotonic",
"--value",
],
check=True,
capture_output=True,
text=True,
env=dict(SAFE_PROCESS_ENVIRONMENT),
)
values = result.stdout.splitlines()
if len(values) < 2:
raise RuntimeError("systemd process identity unavailable")
try:
pid = int(values[0])
started = int(values[1])
except ValueError as exc:
raise RuntimeError(
"systemd process identity is invalid"
) from exc
return {"main_pid": pid, "started_monotonic": started}
def atomic_switch(
self, candidate: ReleaseCandidate
) -> dict[str, Any]:
target = (
self.config.release_root
/ candidate.source_commit
/ "deployment"
/ self.config.dropin_name
).resolve(strict=True)
link = self.config.cutover_link
if link.exists() and not link.is_symlink():
raise RuntimeError("cutover path is not a managed symlink")
previous = os.readlink(link) if link.is_symlink() else None
link.parent.mkdir(parents=True, exist_ok=True)
temporary = link.with_name(f".{link.name}.{os.getpid()}.tmp")
if temporary.exists() or temporary.is_symlink():
temporary.unlink()
os.symlink(str(target), temporary)
os.replace(temporary, link)
return {
"previous_target": previous,
"new_target": str(target),
"cutover_link": str(link),
}
self._acquire_lock()
try:
release_root = self.config.release_root.resolve(strict=True)
raw_target = (
release_root
/ candidate.source_commit
/ "deployment"
/ self.config.dropin_name
)
if raw_target.is_symlink():
raise RuntimeError("release drop-in must not be a symlink")
target = raw_target.resolve(strict=True)
if target.parent.parent.parent != release_root:
raise RuntimeError("release drop-in escapes release root")
link = self.config.cutover_link
if link.exists() and not link.is_symlink():
raise RuntimeError("cutover path is not a managed symlink")
previous = os.readlink(link) if link.is_symlink() else None
if previous:
previous_path = Path(previous)
if not previous_path.is_absolute():
previous_path = link.parent / previous_path
try:
previous_path.resolve().relative_to(release_root)
except ValueError as exc:
raise RuntimeError(
"existing cutover symlink escapes release root"
) from exc
link.parent.mkdir(parents=True, exist_ok=True)
temporary = link.with_name(f".{link.name}.{os.getpid()}.tmp")
if temporary.exists() or temporary.is_symlink():
temporary.unlink()
os.symlink(str(target), temporary)
os.replace(temporary, link)
return {
"previous_target": previous,
"new_target": str(target),
"cutover_link": str(link),
}
except Exception:
self._release_lock()
raise
def restart(self) -> None:
if self._lock_handle is None:
raise RuntimeError("deployment lock is not held")
before = self._service_identity()
command = [
*self.config.command_prefix,
"systemctl",
"daemon-reload",
]
subprocess.run(command, check=True, capture_output=True, text=True)
subprocess.run(
command,
check=True,
capture_output=True,
text=True,
env=dict(SAFE_PROCESS_ENVIRONMENT),
)
subprocess.run(
[
*self.config.command_prefix,
@@ -1050,7 +1228,18 @@ class SystemdDeploymentAdapter:
check=True,
capture_output=True,
text=True,
env=dict(SAFE_PROCESS_ENVIRONMENT),
)
after = self._service_identity()
if (
after["main_pid"] <= 0
or after["started_monotonic"]
<= before["started_monotonic"]
):
raise RuntimeError(
"systemd restart did not produce a new start identity"
)
self._restart_evidence = {"before": before, "after": after}
def health_check(
self, candidate: ReleaseCandidate
@@ -1062,44 +1251,58 @@ class SystemdDeploymentAdapter:
payload = json.loads(response.read().decode("utf-8"))
except Exception as exc:
raise RuntimeError("runtime health check unavailable") from exc
return {
"passed": response.status == 200,
"runtime_commit": payload.get("runtime_release_commit"),
"writes_enabled": payload.get("writes_enabled"),
"authoritative_head_oid": payload.get(
"authoritative_head_oid"
),
"repository_identity": payload.get("repository_identity"),
}
try:
return {
"passed": response.status == 200,
"runtime_commit": payload.get("runtime_release_commit"),
"writes_enabled": payload.get("writes_enabled"),
"authoritative_head_oid": payload.get(
"authoritative_head_oid"
),
"repository_identity": payload.get("repository_identity"),
"systemd_restart": _sanitize(self._restart_evidence),
}
finally:
self._release_lock()
def rollback(
self, switch_evidence: dict[str, Any]
) -> dict[str, Any]:
link = self.config.cutover_link
previous = switch_evidence.get("previous_target")
temporary = link.with_name(f".{link.name}.{os.getpid()}.rollback")
if previous:
if temporary.exists() or temporary.is_symlink():
temporary.unlink()
os.symlink(str(previous), temporary)
os.replace(temporary, link)
elif link.is_symlink():
link.unlink()
self.restart()
try:
with urllib.request.urlopen(
self.config.health_url, timeout=15
) as response:
payload = json.loads(response.read().decode("utf-8"))
except Exception as exc:
raise RuntimeError(
"rollback health check unavailable"
) from exc
return {
"passed": response.status == 200,
"writes_enabled": payload.get("writes_enabled"),
"runtime_commit": payload.get("runtime_release_commit"),
}
if self._lock_handle is None:
self._acquire_lock()
link = self.config.cutover_link
previous = switch_evidence.get("previous_target")
temporary = link.with_name(
f".{link.name}.{os.getpid()}.rollback"
)
if previous:
if temporary.exists() or temporary.is_symlink():
temporary.unlink()
os.symlink(str(previous), temporary)
os.replace(temporary, link)
elif link.is_symlink():
link.unlink()
self.restart()
try:
with urllib.request.urlopen(
self.config.health_url, timeout=15
) as response:
payload = json.loads(
response.read().decode("utf-8")
)
except Exception as exc:
raise RuntimeError(
"rollback health check unavailable"
) from exc
return {
"passed": response.status == 200,
"writes_enabled": payload.get("writes_enabled"),
"runtime_commit": payload.get("runtime_release_commit"),
"systemd_restart": _sanitize(self._restart_evidence),
}
finally:
self._release_lock()
def _sha256(path: Path) -> str:
@@ -0,0 +1,564 @@
#!/usr/bin/env python3
"""Execution-readiness adapters and the fail-closed Shadow controller service."""
from __future__ import annotations
import argparse
import base64
import dataclasses
import hashlib
import json
import os
import subprocess
import threading
import time
from http import HTTPStatus
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from typing import Any, Callable, Mapping, Protocol
from urllib.parse import parse_qs, urlparse
from cryptography.hazmat.primitives.asymmetric.ed25519 import (
Ed25519PrivateKey,
)
from silent_release_controller import _sanitize, utc_now
from silent_release_hardening import (
BuilderAttestation,
CapabilityAuthorizationError,
ReleaseRecoveryWorker,
SignedApprovalVerifier,
SignedReleasePlanApproval,
SilentReleaseActionService,
TrustedCandidateBundle,
_canonical_json,
_durable_json,
_safe_child,
)
class BuilderSigner(Protocol):
@property
def key_id(self) -> str:
...
def sign(self, payload: bytes) -> bytes:
...
class LocalTestBuilderSigner:
"""Ephemeral in-memory signer. It is forbidden for production execution."""
test_only = True
def __init__(self, key_id: str, private_key: Ed25519PrivateKey):
self._key_id = str(key_id)
self._private_key = private_key
@property
def key_id(self) -> str:
return self._key_id
@classmethod
def generate(
cls, key_id: str = "shadow-builder-test-key"
) -> "LocalTestBuilderSigner":
return cls(key_id, Ed25519PrivateKey.generate())
@property
def public_key(self):
return self._private_key.public_key()
def sign(self, payload: bytes) -> bytes:
return self._private_key.sign(payload)
class ExternalKmsBuilderSigner:
"""Signer facade whose private operation remains in KMS/HSM."""
def __init__(
self,
key_id: str,
credential_reference: str,
sign_callback: Callable[[str, bytes], bytes],
):
if not str(credential_reference).startswith(
("kms://", "hsm://", "secret-manager://")
):
raise ValueError("production builder requires a KMS/HSM reference")
self._key_id = str(key_id)
self.credential_reference = str(credential_reference)
self._sign_callback = sign_callback
@property
def key_id(self) -> str:
return self._key_id
def sign(self, payload: bytes) -> bytes:
return bytes(self._sign_callback(self.credential_reference, payload))
class SignedTrustedBuilderAdapter:
"""Signs evidence produced by a trusted collector, never caller evidence."""
def __init__(
self,
collector: Callable[[Mapping[str, Any]], TrustedCandidateBundle],
signer: BuilderSigner,
):
self.collector = collector
self.signer = signer
def build(self, intent: Mapping[str, Any]) -> TrustedCandidateBundle:
forbidden = {
"artifact_sha256",
"canary",
"checks",
"manifest_sha256",
"tests",
"tree_oid",
}
if forbidden.intersection(intent):
raise CapabilityAuthorizationError(
"trusted builder rejects caller-supplied evidence"
)
unsigned = self.collector(dict(intent))
if unsigned.attestation is not None:
raise CapabilityAuthorizationError(
"collector cannot self-attest its evidence"
)
candidate = unsigned.candidate
test_sha = hashlib.sha256(
_canonical_json(_sanitize(candidate.tests))
).hexdigest()
canary_sha = hashlib.sha256(
_canonical_json(_sanitize(candidate.canary))
).hexdigest()
attestation_id = "builder-attestation-" + hashlib.sha256(
(
candidate.candidate_id
+ "\0"
+ candidate.source_commit
+ "\0"
+ candidate.artifact_sha256
).encode("utf-8")
).hexdigest()[:24]
attestation = BuilderAttestation(
builder_attestation_id=attestation_id,
builder_key_id=self.signer.key_id,
source_commit=candidate.source_commit,
tree_oid=candidate.tree_oid,
artifact_sha256=candidate.artifact_sha256,
manifest_sha256=candidate.manifest_sha256,
package_closure_sha256=(
unsigned.release_plan.deployment.package_closure_sha256
),
test_evidence_sha256=test_sha,
canary_evidence_sha256=canary_sha,
issued_at=utc_now(),
signature="",
)
attestation = dataclasses.replace(
attestation,
signature=base64.b64encode(
self.signer.sign(attestation.canonical_bytes())
).decode("ascii"),
)
return dataclasses.replace(unsigned, attestation=attestation)
class ApprovalBackendAdapter(Protocol):
@property
def available(self) -> bool:
...
def verify(
self,
approval: SignedReleasePlanApproval,
*,
candidate,
release_plan,
policy_sha256: str,
) -> SignedReleasePlanApproval:
...
class LocalTestApprovalSigner:
"""Test-only approval signer used solely for Shadow acceptance."""
test_only = True
def __init__(self, key_id: str, private_key: Ed25519PrivateKey):
self.key_id = str(key_id)
self.private_key = private_key
@classmethod
def generate(
cls, key_id: str = "shadow-owner-test-key"
) -> "LocalTestApprovalSigner":
return cls(key_id, Ed25519PrivateKey.generate())
def sign(
self, approval: SignedReleasePlanApproval
) -> SignedReleasePlanApproval:
unsigned = dataclasses.replace(
approval, key_id=self.key_id, signature=""
)
return dataclasses.replace(
unsigned,
signature=base64.b64encode(
self.private_key.sign(unsigned.canonical_bytes())
).decode("ascii"),
)
class ProductionApprovalBackendAdapter:
"""Verification-only production adapter configured by a secret/KMS ref."""
def __init__(
self,
verifier: SignedApprovalVerifier,
credential_reference: str,
):
if not str(credential_reference).startswith(
("kms://", "hsm://", "secret-manager://")
):
raise ValueError(
"production approval backend requires a KMS/secret reference"
)
self.verifier = verifier
self.credential_reference = str(credential_reference)
@property
def available(self) -> bool:
return True
def verify(
self,
approval: SignedReleasePlanApproval,
*,
candidate,
release_plan,
policy_sha256: str,
) -> SignedReleasePlanApproval:
return self.verifier.verify_release_plan(
approval, candidate, release_plan, policy_sha256
)
class UnavailableProductionApprovalBackend:
credential_reference = None
@property
def available(self) -> bool:
return False
def verify(self, *args, **kwargs):
raise CapabilityAuthorizationError(
"production approval backend is not configured"
)
class LeaderLock:
def __init__(self, path: Path):
self.path = Path(path)
self.handle = None
def __enter__(self):
self.path.parent.mkdir(parents=True, exist_ok=True)
os.chmod(self.path.parent, 0o700)
self.handle = self.path.open("a+", encoding="utf-8")
os.chmod(self.path, 0o600)
try:
import fcntl
fcntl.flock(
self.handle.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB
)
except BlockingIOError as exc:
self.handle.close()
self.handle = None
raise RuntimeError("silent release leader is already active") from exc
return self
def __exit__(self, exc_type, exc, tb):
if self.handle is not None:
try:
import fcntl
fcntl.flock(self.handle.fileno(), fcntl.LOCK_UN)
finally:
self.handle.close()
self.handle = None
class PersistentSilentReleaseController:
"""Idempotent main-event controller with durable startup/recovery reports."""
def __init__(
self,
root: Path,
service: SilentReleaseActionService,
*,
main_probe: Callable[[], str],
recovery_worker: ReleaseRecoveryWorker | None = None,
):
self.root = Path(root)
self.service = service
self.main_probe = main_probe
self.recovery_worker = recovery_worker
self.leader_path = self.root / "controller.leader.lock"
self.status_path = self.root / "controller-status.json"
def startup_verify(self) -> dict[str, Any]:
checked = 0
failed = 0
for journal in sorted(self.service.sagas.root.glob("*.events.jsonl")):
candidate_id = journal.name[: -len(".events.jsonl")]
saga = self.service.sagas.load(candidate_id)
checked += 1
if not saga.journal_integrity:
failed += 1
status = {
"checked_sagas": checked,
"execution_enabled": self.service.execution_enabled,
"failed_closed": failed,
"shadow_mode": not self.service.execution_enabled,
"side_effects_executed": False,
"status": "READY" if failed == 0 else "FAIL_CLOSED",
}
_durable_json(self.status_path, status)
return status
def run_once(self) -> dict[str, Any]:
with LeaderLock(self.leader_path):
startup = self.startup_verify()
if startup["status"] != "READY":
result = {
"execution_enabled": self.service.execution_enabled,
"intent": None,
"notification": None,
"recovery": None,
"shadow_mode": not self.service.execution_enabled,
"side_effects_executed": False,
"startup": startup,
"status": "FAIL_CLOSED",
}
_durable_json(self.status_path, result)
return _sanitize(result)
source_commit = str(self.main_probe())
event_id = f"main-{source_commit}"
intent = self.service.create_release_intent(
{"event_id": event_id, "source_commit": source_commit}
)
notification = None
if intent.get("candidate_id"):
notification = self.service.ensure_owner_notification(
str(intent["candidate_id"])
)
recovery = (
self.recovery_worker.run_once()
if self.recovery_worker is not None
else self.service.get_recovery_status()
)
result = {
"execution_enabled": self.service.execution_enabled,
"intent": intent,
"notification": notification,
"recovery": recovery,
"shadow_mode": not self.service.execution_enabled,
"side_effects_executed": False,
"startup": startup,
}
_durable_json(self.status_path, result)
return _sanitize(result)
class ShadowActionHandler(BaseHTTPRequestHandler):
service: SilentReleaseActionService
controller: PersistentSilentReleaseController
def log_message(self, fmt, *args):
return
def _json(self, payload: Mapping[str, Any], status=HTTPStatus.OK):
body = json.dumps(_sanitize(dict(payload)), sort_keys=True).encode()
self.send_response(int(status))
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def _body(self) -> dict[str, Any]:
size = int(self.headers.get("Content-Length", "0"))
if size > 1024 * 1024:
raise ValueError("request body is too large")
return json.loads(self.rfile.read(size) or b"{}")
def do_GET(self):
parsed = urlparse(self.path)
query = parse_qs(parsed.query)
candidate_id = str((query.get("candidate_id") or [""])[0])
try:
if parsed.path == "/healthz":
return self._json(
{
"execution_enabled": False,
"shadow_mode": True,
"side_effects_executed": False,
"status": "ok",
}
)
routes = {
"/getReleaseCandidate": self.service.get_release_candidate,
"/getReleasePlan": self.service.get_release_plan,
"/getReleaseAcceptance": self.service.get_acceptance,
"/getReleaseSaga": self.service.get_release_saga,
"/getCapabilityStatus": self.service.get_capability_status,
}
if parsed.path in routes:
return self._json(routes[parsed.path](candidate_id))
if parsed.path == "/getRecoveryStatus":
return self._json(self.service.get_recovery_status())
self._json({"error": "not_found"}, HTTPStatus.NOT_FOUND)
except (ValueError, FileNotFoundError) as exc:
self._json(
{"error": type(exc).__name__}, HTTPStatus.BAD_REQUEST
)
def do_POST(self):
parsed = urlparse(self.path)
try:
body = self._body()
if parsed.path == "/createReleaseIntent":
return self._json(self.service.create_release_intent(body))
if parsed.path in {
"/submitSignedApproval",
"/submitOwnerApproval",
}:
return self._json(self.service.submit_owner_approval(body))
if parsed.path == "/startDeployment":
return self._json(
self.service.start_deployment(
str(body.get("candidate_id") or "")
)
)
self._json({"error": "not_found"}, HTTPStatus.NOT_FOUND)
except (ValueError, FileNotFoundError, CapabilityAuthorizationError) as exc:
self._json(
{"error": type(exc).__name__}, HTTPStatus.BAD_REQUEST
)
def git_main_probe(repository: Path) -> Callable[[], str]:
repository = Path(repository)
def probe() -> str:
completed = subprocess.run(
[
"git",
f"--git-dir={repository}",
"rev-parse",
"refs/heads/main",
],
check=True,
capture_output=True,
text=True,
timeout=15,
env={"PATH": os.environ.get("PATH", "")},
)
return completed.stdout.strip()
return probe
def serve_shadow(
*,
state_root: Path,
repository: Path,
host: str,
port: int,
poll_seconds: float,
) -> None:
service = SilentReleaseActionService.shadow(state_root)
controller = PersistentSilentReleaseController(
state_root,
service,
main_probe=git_main_probe(repository),
)
controller.run_once()
stop = threading.Event()
def controller_loop() -> None:
while not stop.wait(poll_seconds):
try:
controller.run_once()
except Exception as exc: # fail closed and keep the API available
_durable_json(
controller.status_path,
{
"error": type(exc).__name__,
"execution_enabled": False,
"shadow_mode": True,
"side_effects_executed": False,
"status": "FAIL_CLOSED",
},
)
handler = type(
"ConfiguredShadowActionHandler",
(ShadowActionHandler,),
{"service": service, "controller": controller},
)
server = ThreadingHTTPServer((host, port), handler)
api_thread = threading.Thread(
target=server.serve_forever,
name="silent-release-shadow-api",
daemon=False,
)
controller_thread = threading.Thread(
target=controller_loop,
name="silent-release-shadow-controller",
daemon=True,
)
api_thread.start()
controller_thread.start()
try:
while api_thread.is_alive():
api_thread.join(timeout=1)
finally:
stop.set()
server.shutdown()
server.server_close()
controller_thread.join(timeout=max(1.0, poll_seconds + 1.0))
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--state-root", required=True, type=Path)
parser.add_argument("--repository", required=True, type=Path)
parser.add_argument("--host", default="127.0.0.1")
parser.add_argument("--port", default=18891, type=int)
parser.add_argument("--poll-seconds", default=30.0, type=float)
arguments = parser.parse_args()
if arguments.poll_seconds <= 0:
raise SystemExit("poll seconds must be positive")
if os.environ.get("AIOS_GITOPS_WRITES_ENABLED", "false").lower() != "false":
raise SystemExit("GitOps writes must remain disabled")
if (
os.environ.get(
"AIOS_SILENT_RELEASE_EXECUTION_ENABLED", "false"
).lower()
!= "false"
):
raise SystemExit("Silent Release execution must remain disabled")
serve_shadow(
state_root=arguments.state_root,
repository=arguments.repository,
host=arguments.host,
port=arguments.port,
poll_seconds=arguments.poll_seconds,
)
if __name__ == "__main__":
main()
File diff suppressed because it is too large Load Diff
+21
View File
@@ -0,0 +1,21 @@
"""Task Plane runtime package."""
from .models import (
EvidenceEnvelope,
ProbeEvidence,
ProbeOutcome,
TaskPlan,
TaskRecord,
TaskStatus,
TaskStateTransitionError,
)
__all__ = [
"EvidenceEnvelope",
"ProbeEvidence",
"ProbeOutcome",
"TaskPlan",
"TaskRecord",
"TaskStatus",
"TaskStateTransitionError",
]
@@ -0,0 +1,17 @@
"""Adapters for executing tasks via worker backends."""
from .base import WorkerAdapter
from .openclaw import OpenClawAdapter
from .codex import CodexAdapter
from .hermes import HermesAdapter
from .a2a import A2AAdapter
from .systemd_worker import SystemdWorkerAdapter
__all__ = [
"WorkerAdapter",
"OpenClawAdapter",
"CodexAdapter",
"HermesAdapter",
"A2AAdapter",
"SystemdWorkerAdapter",
]
@@ -0,0 +1,31 @@
from __future__ import annotations
from .base import WorkerAdapter
class A2AAdapter(WorkerAdapter):
worker_name = "a2a"
def capabilities(self) -> list[str]:
return ["task.exchange", "agent.handoff"]
def claim(self, task):
return {"worker": self.worker_name, "task_id": task.task_id, "status": "claimed"}
def execute(self, task, claim):
return {"worker": self.worker_name, "task_id": task.task_id, "status": "SHADOW_VERIFIED", "claim_id": claim.claim_id, "execution_mode": "shadow", "production_side_effects": False}
def heartbeat(self, claim):
return {"worker": self.worker_name, "claim_id": claim.claim_id, "status": "alive"}
def report_progress(self, claim, progress):
return {"worker": self.worker_name, "claim_id": claim.claim_id, "progress": progress}
def probe(self, claim):
return {"worker": self.worker_name, "claim_id": claim.claim_id, "status": claim.status}
def cancel(self, claim):
return {"worker": self.worker_name, "claim_id": claim.claim_id, "status": "cancelled"}
def collect_result(self, claim):
return {"worker": self.worker_name, "claim_id": claim.claim_id, "result": "not_collected"}
@@ -0,0 +1,41 @@
"""Base worker adapter contract."""
from __future__ import annotations
import abc
class WorkerAdapter(abc.ABC):
worker_name = "base"
@abc.abstractmethod
def capabilities(self) -> list[str]:
raise NotImplementedError
@abc.abstractmethod
def claim(self, task):
raise NotImplementedError
@abc.abstractmethod
def execute(self, task, claim):
raise NotImplementedError
@abc.abstractmethod
def heartbeat(self, claim):
raise NotImplementedError
@abc.abstractmethod
def report_progress(self, claim, progress):
raise NotImplementedError
@abc.abstractmethod
def probe(self, claim):
raise NotImplementedError
@abc.abstractmethod
def cancel(self, claim):
raise NotImplementedError
@abc.abstractmethod
def collect_result(self, claim):
raise NotImplementedError
@@ -0,0 +1,31 @@
from __future__ import annotations
from .base import WorkerAdapter
class CodexAdapter(WorkerAdapter):
worker_name = "codex"
def capabilities(self) -> list[str]:
return ["code.read", "code.write.patch", "test.python", "review.security"]
def claim(self, task):
return {"worker": self.worker_name, "task_id": task.task_id, "status": "claimed"}
def execute(self, task, claim):
return {"worker": self.worker_name, "task_id": task.task_id, "status": "SHADOW_VERIFIED", "claim_id": claim.claim_id, "execution_mode": "shadow", "production_side_effects": False}
def heartbeat(self, claim):
return {"worker": self.worker_name, "claim_id": claim.claim_id, "status": "alive"}
def report_progress(self, claim, progress):
return {"worker": self.worker_name, "claim_id": claim.claim_id, "progress": progress}
def probe(self, claim):
return {"worker": self.worker_name, "claim_id": claim.claim_id, "status": claim.status}
def cancel(self, claim):
return {"worker": self.worker_name, "claim_id": claim.claim_id, "status": "cancelled"}
def collect_result(self, claim):
return {"worker": self.worker_name, "claim_id": claim.claim_id, "result": "not_collected"}
@@ -0,0 +1,31 @@
from __future__ import annotations
from .base import WorkerAdapter
class HermesAdapter(WorkerAdapter):
worker_name = "hermes"
def capabilities(self) -> list[str]:
return ["architecture.review", "policy.review", "threat.model"]
def claim(self, task):
return {"worker": self.worker_name, "task_id": task.task_id, "status": "claimed"}
def execute(self, task, claim):
return {"worker": self.worker_name, "task_id": task.task_id, "status": "SHADOW_VERIFIED", "claim_id": claim.claim_id, "execution_mode": "shadow", "production_side_effects": False}
def heartbeat(self, claim):
return {"worker": self.worker_name, "claim_id": claim.claim_id, "status": "alive"}
def report_progress(self, claim, progress):
return {"worker": self.worker_name, "claim_id": claim.claim_id, "progress": progress}
def probe(self, claim):
return {"worker": self.worker_name, "claim_id": claim.claim_id, "status": claim.status}
def cancel(self, claim):
return {"worker": self.worker_name, "claim_id": claim.claim_id, "status": "cancelled"}
def collect_result(self, claim):
return {"worker": self.worker_name, "claim_id": claim.claim_id, "result": "not_collected"}
@@ -0,0 +1,31 @@
from __future__ import annotations
from .base import WorkerAdapter
class OpenClawAdapter(WorkerAdapter):
worker_name = "openclaw"
def capabilities(self) -> list[str]:
return ["code.read", "code.write.patch", "test.execute", "runtime.shadow"]
def claim(self, task):
return {"worker": self.worker_name, "task_id": task.task_id, "status": "claimed"}
def execute(self, task, claim):
return {"worker": self.worker_name, "task_id": task.task_id, "status": "SHADOW_VERIFIED", "claim_id": claim.claim_id, "execution_mode": "shadow", "production_side_effects": False}
def heartbeat(self, claim):
return {"worker": self.worker_name, "claim_id": claim.claim_id, "status": "alive"}
def report_progress(self, claim, progress):
return {"worker": self.worker_name, "claim_id": claim.claim_id, "progress": progress}
def probe(self, claim):
return {"worker": self.worker_name, "claim_id": claim.claim_id, "status": claim.status}
def cancel(self, claim):
return {"worker": self.worker_name, "claim_id": claim.claim_id, "status": "cancelled"}
def collect_result(self, claim):
return {"worker": self.worker_name, "claim_id": claim.claim_id, "result": "not_collected"}
@@ -0,0 +1,31 @@
from __future__ import annotations
from .base import WorkerAdapter
class SystemdWorkerAdapter(WorkerAdapter):
worker_name = "systemd_worker"
def capabilities(self) -> list[str]:
return ["systemd.exec", "runtime.shell"]
def claim(self, task):
return {"worker": self.worker_name, "task_id": task.task_id, "status": "claimed"}
def execute(self, task, claim):
return {"worker": self.worker_name, "task_id": task.task_id, "status": "SHADOW_VERIFIED", "claim_id": claim.claim_id, "execution_mode": "shadow", "production_side_effects": False}
def heartbeat(self, claim):
return {"worker": self.worker_name, "claim_id": claim.claim_id, "status": "alive"}
def report_progress(self, claim, progress):
return {"worker": self.worker_name, "claim_id": claim.claim_id, "progress": progress}
def probe(self, claim):
return {"worker": self.worker_name, "claim_id": claim.claim_id, "status": claim.status}
def cancel(self, claim):
return {"worker": self.worker_name, "claim_id": claim.claim_id, "status": "cancelled"}
def collect_result(self, claim):
return {"worker": self.worker_name, "claim_id": claim.claim_id, "result": "not_collected"}
@@ -0,0 +1,27 @@
"""Agent registry abstraction."""
from __future__ import annotations
from .errors import NotFoundError
class AgentRegistry:
def __init__(self):
self._agents: dict[str, dict] = {}
def register(self, agent_id: str, metadata: dict) -> None:
self._agents[agent_id] = {"agent_id": agent_id, **metadata}
def unregister(self, agent_id: str) -> None:
if agent_id not in self._agents:
raise NotFoundError("agent")
del self._agents[agent_id]
def list(self) -> list[dict]:
return list(self._agents.values())
def get(self, agent_id: str) -> dict:
try:
return self._agents[agent_id]
except KeyError:
raise NotFoundError("agent")
+557
View File
@@ -0,0 +1,557 @@
"""Task Plane HTTP API implementation (stdlib-only)."""
from __future__ import annotations
import json
import os
import time
import uuid
import urllib.parse
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from typing import Any, Callable
from .delegation import DelegationEngine
from .errors import (
ConflictError,
ForbiddenError,
NotFoundError,
PayloadTooLargeError,
TaskPlaneHTTPError,
UnauthorizedError,
ValidationError,
)
from .evidence import EvidenceService
from .journal import TaskJournal
from .lease import LeaseManager
from .models import (
EvidenceEnvelope,
ResultEnvelope,
TaskPlan,
TaskRecord,
iso8601_now,
contains_secret_text,
validate_identifier,
)
from .policy import PolicyEngine
from .store import TaskPlaneStore
from .worker_registry import WorkerRegistry
def _json_payload(data: dict[str, Any], status: int = 200) -> tuple[int, bytes]:
body = json.dumps(data, ensure_ascii=False).encode("utf-8")
return status, body
def _bounded_int(value: Any, name: str, minimum: int, maximum: int) -> int:
try:
number = int(value)
except (TypeError, ValueError) as exc:
raise ValidationError(f"invalid {name}") from exc
if not minimum <= number <= maximum:
raise ValidationError(f"invalid {name}")
return number
class TaskPlaneService:
def __init__(self, store: TaskPlaneStore, policy: PolicyEngine | None = None):
self.store = store
self.policy = policy or PolicyEngine(
execution_enabled=False,
identity_capabilities={
"anon": {"task.creator", "task.read", "task.audit"}
},
)
self.journal = TaskJournal(store)
self.lease = LeaseManager(store)
self.workers = WorkerRegistry(store)
self.evidence = EvidenceService(store)
self.delegation = DelegationEngine(store)
self.store.probe_attestation_verifier = self.policy.probe_attestation_verifier
def create_task(self, body: dict[str, Any], idempotency_key: str | None = None) -> dict[str, Any]:
if body.get("parent_task_id"):
raise ForbiddenError("child tasks must use the delegate endpoint")
issuer = body.get("issuer", {})
actor = issuer.get("id", "anon") if isinstance(issuer, dict) else "anon"
self.policy.assert_allowed("create", actor)
try:
plan = TaskPlan.from_dict(body)
except (TypeError, ValueError) as exc:
raise ValidationError("invalid task plan") from exc
task = self.store.create_task_with_event(plan, idempotency_key=idempotency_key)
return task.to_dict()
def list_tasks(self, actor: str | None = None) -> list[dict[str, Any]]:
if actor is not None:
self.policy.assert_allowed("read", actor)
return [task.to_dict(include_task=False) for task in self.store.list_tasks()]
def get_task(self, task_id: str, actor: str | None = None) -> dict[str, Any]:
if actor is not None:
self.policy.assert_allowed("read", actor, task=self.store.get_task(task_id))
return self.store.get_task(task_id).to_dict()
def get_worker(self, worker_id: str, actor: str | None = None) -> dict[str, Any]:
if actor is not None:
self.policy.assert_allowed("read", actor)
return self.workers.get(worker_id).to_dict()
def list_workers(self, actor: str | None = None) -> list[dict[str, Any]]:
if actor is not None:
self.policy.assert_allowed("read", actor)
return [w.to_dict() for w in self.workers.list()]
def register_worker(self, body: dict[str, Any]) -> dict[str, Any]:
actor = str(body.get("actor_identity") or body.get("identity") or "")
is_admin = actor in self.policy.admin_identities
try:
existing = self.store.get_worker(str(body.get("worker_id") or ""))
except NotFoundError:
existing = None
if existing is not None and str(body.get("identity") or "") != existing.identity:
raise ConflictError("worker identity takeover rejected")
self.policy.assert_readonly_worker_registration(actor, body)
self.policy.assert_allowed(
"worker.admin" if is_admin else "worker.register", actor
)
try:
worker = self.workers.register(
body,
actor_identity=actor,
is_admin=is_admin,
expected_revision=body.get("expected_revision"),
)
except (TypeError, ValueError, KeyError) as exc:
raise ValidationError("invalid worker registration") from exc
return worker.to_dict()
def worker_heartbeat(self, worker_id: str, payload: dict[str, Any]) -> dict[str, Any]:
interval = payload.get("heartbeat_interval_seconds")
actor = str(payload.get("actor_identity") or "")
self.policy.assert_readonly_worker_binding(actor, worker_id)
self.policy.assert_allowed("worker.register", actor)
return self.workers.heartbeat(
worker_id,
heartbeat_interval_seconds=interval,
actor_identity=actor,
expected_revision=payload.get("expected_revision"),
).to_dict()
def disable_worker(self, worker_id: str, payload: dict[str, Any]) -> dict[str, Any]:
actor = str(payload.get("actor_identity") or "")
self.policy.assert_allowed("worker.admin", actor)
return self.store.disable_worker(
worker_id,
actor_identity=actor,
expected_revision=int(payload.get("expected_revision")),
reason=str(payload.get("reason") or "disabled by administrator"),
).to_dict()
def restore_worker(self, worker_id: str, payload: dict[str, Any]) -> dict[str, Any]:
actor = str(payload.get("actor_identity") or "")
self.policy.assert_allowed("worker.admin", actor)
return self.store.restore_worker(
worker_id,
actor_identity=actor,
expected_revision=int(payload.get("expected_revision")),
).to_dict()
def claim(self, task_id: str, body: dict[str, Any]) -> dict[str, Any]:
actor = body.get("actor_identity") or body.get("worker_identity") or "worker"
self.policy.assert_allowed("claim", actor)
worker_id = body.get("worker_id")
if not worker_id:
raise ValidationError("worker_id required")
task = self.store.get_task(task_id)
self.policy.assert_readonly_worker_binding(actor, str(worker_id))
self.policy.assert_allowed("claim", actor, task=task)
lease_seconds = _bounded_int(
body.get("lease_seconds", 30), "lease_seconds", 1, 3600
)
claim = self.lease.claim(
task_id=task_id,
worker_id=worker_id,
worker_identity=actor,
lease_seconds=lease_seconds,
expected_revision=task.revision,
operation_digest=task.task_plan.operation_digest(),
)
return {"claim_id": claim.claim_id, **claim.to_dict()}
def heartbeat(self, task_id: str, body: dict[str, Any]) -> dict[str, Any]:
claim_id = body.get("claim_id")
if not claim_id:
raise ValidationError("claim_id required")
actor = body.get("actor_identity") or body.get("worker_identity") or "worker"
task = self.store.get_task(task_id)
claim = self.store.get_claim(claim_id)
self.policy.assert_readonly_worker_binding(actor, claim.worker_id)
self.policy.assert_allowed("heartbeat", actor, task=task)
if claim.worker_identity != actor:
raise ForbiddenError("claim identity mismatch")
lease_seconds = _bounded_int(
body.get("lease_seconds", 30), "lease_seconds", 1, 3600
)
claim = self.lease.heartbeat(task_id, claim_id, lease_seconds)
return {"claim_id": claim_id, "lease_until": claim.lease_until}
def progress(self, task_id: str, body: dict[str, Any]) -> dict[str, Any]:
claim_id = body.get("claim_id")
if not claim_id:
raise ValidationError("claim_id required")
actor = body.get("actor_identity") or body.get("worker_identity") or "worker"
progress = body.get("progress")
stage = body.get("stage", "RUNNING")
message = body.get("message")
if progress is None:
raise ValidationError("progress required")
task = self.store.get_task(task_id)
claim = self.store.get_claim(claim_id)
self.policy.assert_readonly_worker_binding(actor, claim.worker_id)
self.policy.assert_allowed("progress", actor, task=task)
if task.task_plan.data_classification == "credential-secret":
if message not in {None, ""} or contains_secret_text(stage):
raise ValidationError(
"credential-secret progress must contain metadata only"
)
lease_seconds = _bounded_int(
body.get("lease_seconds", 30), "lease_seconds", 1, 3600
)
claim = self.store.record_progress(
task_id,
claim_id,
actor,
progress=progress,
stage=str(stage),
message=None if message is None else str(message),
lease_seconds=lease_seconds,
)
return {"claim_id": claim.claim_id, "status": claim.status, "progress": progress}
def complete(self, task_id: str, body: dict[str, Any]) -> dict[str, Any]:
claim_id = body.get("claim_id")
if not claim_id:
raise ValidationError("claim_id required")
actor = body.get("actor_identity") or body.get("worker_identity") or "worker"
task = self.store.get_task(task_id)
status = body.get("status", "COMPLETED")
outputs = body.get("outputs", [])
evidences = body.get("evidence", [])
if not isinstance(outputs, list):
raise ValidationError("outputs must be list")
if not isinstance(evidences, list):
raise ValidationError("evidence must be list")
if any(not isinstance(ev, dict) for ev in evidences):
raise ValidationError("evidence items must be objects")
claim = self.store.get_claim(claim_id)
self.policy.assert_readonly_worker_binding(actor, claim.worker_id)
self.policy.assert_allowed("result", actor, task=task)
if claim.worker_identity != actor:
raise ForbiddenError("claim identity mismatch")
if task.task_plan.data_classification == "credential-secret":
allowed_result_keys = {
"kind",
"hash",
"sha256",
"count",
"status",
"error_code",
"signature",
"attestation_ref",
}
for entry in [*outputs, *evidences]:
if (
not isinstance(entry, dict)
or set(entry) - allowed_result_keys
or contains_secret_text(entry)
):
raise ValidationError(
"credential-secret result must contain hash/count/status only"
)
result = ResultEnvelope(
task_id=task_id,
claim_id=claim_id,
worker_identity=actor,
status=status,
outputs=outputs,
evidence=evidences,
started_at=str(body.get("started_at") or iso8601_now()),
completed_at=str(body.get("completed_at") or iso8601_now()),
result_digest=body.get("result_digest"),
signature=body.get("signature"),
attestation_ref=body.get("attestation_ref"),
operation_digest=claim.operation_digest,
)
evidence_decision = self.policy.evidence_verifier.verify(task, evidences)
if not evidence_decision.allowed:
raise ForbiddenError(evidence_decision.reason)
result_decision = self.policy.verify_result(result, task)
if not result_decision.allowed:
raise ForbiddenError(result_decision.reason)
result.result_digest = result.computed_digest()
previous = self.store._last_evidence_hash(task_id)
evidence_entries = []
for evidence in evidences:
envelope = EvidenceEnvelope.create(
task_id=task_id,
claim_id=claim_id,
kind=str(evidence.get("kind") or evidence.get("type") or "generic"),
worker_identity=actor,
payload=evidence,
previous_evidence_hash=previous,
signature=evidence.get("signature"),
attestation_ref=evidence.get("attestation_ref"),
)
evidence_entries.append(envelope)
previous = envelope.evidence_digest
self.store.complete_claim_with_evidence_and_event(
task_id,
claim_id,
status
if status
in {"COMPLETED", "FAILED_FINAL", "FAILED_RECOVERABLE", "CANCELLED"}
else "COMPLETED",
result.to_dict(),
evidence_entries,
)
return result.to_dict()
def fail(self, task_id: str, body: dict[str, Any]) -> dict[str, Any]:
claim_id = body.get("claim_id")
if not claim_id:
raise ValidationError("claim_id required")
reason = body.get("reason", "failed")
actor = body.get("actor_identity") or body.get("worker_identity") or "worker"
self.policy.assert_allowed("fail", actor)
task = self.store.get_task(task_id)
self.policy.assert_allowed("fail", actor, task=task)
claim = self.store.get_claim(claim_id)
if claim.worker_identity != actor:
raise ForbiddenError("claim identity mismatch")
status = "FAILED_RECOVERABLE" if body.get("recoverable", False) else "FAILED_FINAL"
self.store.fail_claim_with_event(task_id, claim_id, status, str(reason))
return {"task_id": task_id, "status": status, "reason": reason}
def cancel(self, task_id: str, body: dict[str, Any]) -> dict[str, Any]:
actor = body.get("actor_identity") or body.get("worker_identity") or "owner"
self.policy.assert_allowed("cancel", actor)
reason = body.get("reason", "cancelled")
task = self.store.get_task(task_id)
self.policy.assert_allowed("cancel", actor, task=task)
updated = self.store.cancel_task_with_event(
task_id, task.revision, reason=reason
)
return updated.to_dict()
def delegate(self, task_id: str, body: dict[str, Any]) -> dict[str, Any]:
actor = body.get("actor_identity") or "owner"
self.policy.assert_allowed("delegate", actor)
parent = self.store.get_task(task_id)
self.policy.assert_allowed("delegate", actor, task=parent)
if "task" in body:
try:
child_plan = TaskPlan.from_dict(body["task"])
except (TypeError, ValueError) as exc:
raise ValidationError("invalid child task plan") from exc
else:
plan_body = dict(body)
plan_body.pop("actor_identity", None)
try:
child_plan = TaskPlan.from_dict(plan_body)
except (TypeError, ValueError) as exc:
raise ValidationError("invalid child task plan") from exc
child_plan.parent_task_id = task_id
child = self.delegation.delegate(task_id, child_plan, caller=actor)
return child.to_dict()
def get_events(self, task_id: str, actor: str | None = None) -> list[dict[str, Any]]:
if actor is not None:
self.policy.assert_allowed("audit", actor, task=self.store.get_task(task_id))
self.store.get_task(task_id)
return self.store.list_events(task_id)
def get_evidence(self, task_id: str, actor: str | None = None) -> list[dict[str, Any]]:
if actor is not None:
self.policy.assert_allowed("audit", actor, task=self.store.get_task(task_id))
self.store.get_task(task_id)
return self.evidence.list(task_id)
def get_children(self, task_id: str, actor: str | None = None) -> list[str]:
if actor is not None:
self.policy.assert_allowed("read", actor, task=self.store.get_task(task_id))
self.store.get_task(task_id)
return self.store.list_children(task_id)
class TaskPlaneRequestHandler(BaseHTTPRequestHandler):
service: TaskPlaneService | None = None
authenticator: Callable[[str], str | None] | None = None
max_body_bytes = 1_000_000
def log_message(self, format: str, *args: Any) -> None:
# BaseHTTPRequestHandler logs the raw request target; suppress it so
# attacker-controlled paths or query strings cannot leak credentials.
return
def _json(self) -> tuple[str | None, dict[str, Any]]:
try:
length = int(self.headers.get("Content-Length", "0"))
except ValueError as exc:
raise ValidationError("invalid content length") from exc
if length < 0:
raise ValidationError("invalid content length")
if length > self.max_body_bytes:
raise PayloadTooLargeError("payload exceeds limit")
raw = self.rfile.read(length) if length > 0 else b""
if not raw:
return None, {}
try:
decoded = raw.decode("utf-8")
payload = json.loads(decoded)
if not isinstance(payload, dict):
raise ValidationError("JSON payload must be an object")
return decoded, payload
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
raise ValidationError("invalid JSON payload") from exc
def _response(self, status: int, payload: Any) -> None:
encoded = json.dumps(payload, ensure_ascii=False).encode("utf-8")
self.send_response(status)
self.send_header("Content-Type", "application/json; charset=utf-8")
self.send_header("Content-Length", str(len(encoded)))
self.end_headers()
self.wfile.write(encoded)
def _authenticated_identity(self) -> str:
authenticator = type(self).authenticator
if authenticator is None:
raise UnauthorizedError("authentication unavailable")
header = self.headers.get("Authorization", "")
if not header.startswith("Bearer "):
raise UnauthorizedError("bearer authentication required")
token = header[7:]
if not token or any(ord(ch) < 33 for ch in token):
raise UnauthorizedError("invalid bearer token")
identity = authenticator(token)
if not identity:
raise UnauthorizedError("invalid credentials")
return str(identity)
def _handle(self, handler):
try:
if self.service is None:
raise TaskPlaneHTTPError("service not configured")
result = handler()
if isinstance(result, tuple):
status, payload = result
else:
status, payload = 200, result
self._response(status, payload)
except TaskPlaneHTTPError as exc:
self._response(exc.status_code, exc.to_payload())
except Exception as exc: # defensive
if isinstance(exc, TaskPlaneHTTPError):
raise
self._response(
500,
{"error": "internal_error", "message": "internal server error"},
)
def do_GET(self): # noqa: N802
def route():
path = urllib.parse.urlparse(self.path).path
parts = [p for p in path.split("/") if p]
for part in parts[1:]:
if part not in {"events", "evidence", "children", "heartbeat"}:
try:
validate_identifier(part, "resource identifier")
except ValueError as exc:
raise ValidationError("invalid resource identifier") from exc
identity = self._authenticated_identity()
if len(parts) == 1 and parts[0] == "tasks":
return 200, self.service.list_tasks(identity)
if len(parts) == 2 and parts[0] == "tasks":
task_id = parts[1]
return 200, self.service.get_task(task_id, identity)
if len(parts) == 3 and parts[0] == "tasks" and parts[2] == "events":
return 200, self.service.get_events(parts[1], identity)
if len(parts) == 3 and parts[0] == "tasks" and parts[2] == "evidence":
return 200, self.service.get_evidence(parts[1], identity)
if len(parts) == 3 and parts[0] == "tasks" and parts[2] == "children":
return 200, self.service.get_children(parts[1], identity)
if len(parts) == 1 and parts[0] == "workers":
return 200, self.service.list_workers(identity)
if len(parts) == 2 and parts[0] == "workers":
return 200, self.service.get_worker(parts[1], identity)
raise NotFoundError("unknown endpoint")
self._handle(route)
def do_POST(self): # noqa: N802
def route():
body_text, body = self._json()
identity = self._authenticated_identity()
path = urllib.parse.urlparse(self.path).path
parts = [p for p in path.split("/") if p]
headers = {k.lower(): v for k, v in self.headers.items()}
if len(parts) == 1 and parts[0] == "tasks":
issuer = body.get("issuer")
if issuer and (
not isinstance(issuer, dict) or issuer.get("id") != identity
):
raise ForbiddenError("issuer identity mismatch")
body["issuer"] = {"type": "authenticated", "id": identity}
return 201, self.service.create_task(body or {}, headers.get("idempotency-key"))
if body.get("actor_identity") not in {None, identity}:
raise ForbiddenError("actor identity mismatch")
body["actor_identity"] = identity
if len(parts) == 3 and parts[0] == "tasks" and parts[2] == "claim":
return 200, self.service.claim(parts[1], body or {})
if len(parts) == 3 and parts[0] == "tasks" and parts[2] == "heartbeat":
return 200, self.service.heartbeat(parts[1], body or {})
if len(parts) == 3 and parts[0] == "tasks" and parts[2] == "progress":
return 200, self.service.progress(parts[1], body or {})
if len(parts) == 3 and parts[0] == "tasks" and parts[2] == "result":
return 200, self.service.complete(parts[1], body or {})
if len(parts) == 3 and parts[0] == "tasks" and parts[2] == "fail":
return 200, self.service.fail(parts[1], body or {})
if len(parts) == 3 and parts[0] == "tasks" and parts[2] == "cancel":
return 200, self.service.cancel(parts[1], body or {})
if len(parts) == 3 and parts[0] == "tasks" and parts[2] == "delegate":
return 200, self.service.delegate(parts[1], body or {})
if len(parts) == 2 and parts[0] == "workers" and parts[1] == "register":
if (
body.get("identity") != identity
and identity not in self.service.policy.admin_identities
):
raise ForbiddenError("worker identity mismatch")
return 201, self.service.register_worker(body or {})
if len(parts) == 3 and parts[0] == "workers" and parts[2] == "heartbeat":
return 200, self.service.worker_heartbeat(parts[1], body or {})
if len(parts) == 3 and parts[0] == "workers" and parts[2] == "disable":
return 200, self.service.disable_worker(parts[1], body or {})
if len(parts) == 3 and parts[0] == "workers" and parts[2] == "restore":
return 200, self.service.restore_worker(parts[1], body or {})
raise NotFoundError("unknown endpoint")
self._handle(route)
def run_server(
host: str,
port: int,
service: TaskPlaneService,
*,
authenticator: Callable[[str], str | None] | None = None,
):
handler = TaskPlaneRequestHandler
handler.service = service
handler.authenticator = authenticator
server = ThreadingHTTPServer((host, port), handler)
server.serve_forever()
@@ -0,0 +1,36 @@
"""Capability matcher helpers."""
from __future__ import annotations
from fnmatch import fnmatch
def _normalize_capability(capability: str) -> str:
return capability.strip().lower()
def capability_matches(granted: str, required: str) -> bool:
granted = _normalize_capability(granted)
required = _normalize_capability(required)
if granted == required:
return True
# A granted wildcard such as "test.*" should match required "test.unit"
if "*" in granted:
return fnmatch(required, granted)
# A required wildcard is treated as a suffix/prefix requirement for callers.
if "*" in required:
return fnmatch(granted, required)
return False
def has_capabilities(granted_caps: list[str], required_caps: list[str]) -> bool:
granted = [_normalize_capability(c) for c in granted_caps]
for required in required_caps:
required = _normalize_capability(required)
if not any(capability_matches(g, required) for g in granted):
return False
return True
def normalize_capabilities(caps: list[str]) -> list[str]:
return sorted({_normalize_capability(cap) for cap in caps})
@@ -0,0 +1,87 @@
"""Delegation guardrails."""
from __future__ import annotations
from .errors import ConflictError, ForbiddenError, NotFoundError
from .models import Budget, TaskPlan, TaskRecord
from .store import TaskPlaneStore
class DelegationEngine:
def __init__(self, store: TaskPlaneStore):
self.store = store
def can_delegate(self, parent_id: str, child_plan: TaskPlan) -> bool:
parent = self.store.get_task(parent_id)
if parent.status == "COMPLETED":
raise ConflictError("cannot delegate completed task")
if parent.task_plan.max_delegation_depth <= 0:
raise ConflictError("max delegation depth not configured")
depth = self._depth(parent_id)
if depth >= parent.task_plan.max_delegation_depth:
raise ConflictError("max delegation depth reached")
if child_plan.parent_task_id not in {None, parent_id}:
raise ConflictError("delegation parent mismatch")
current = parent_id
while current:
if child_plan.task_id and current == child_plan.task_id:
raise ConflictError("delegation cycle")
current = self.store.parent_of(current)
return True
def delegate(self, parent_id: str, child_plan: TaskPlan, *, caller: str) -> TaskRecord:
self.can_delegate(parent_id, child_plan)
parent = self.store.get_task(parent_id)
if child_plan.required_capabilities and not set(child_plan.required_capabilities).issubset(
set(parent.task_plan.required_capabilities or [])
):
raise ForbiddenError("child capabilities exceed parent constraints")
risk_rank = {"low": 1, "medium": 2, "high": 3}
if risk_rank.get(child_plan.risk, 99) > risk_rank.get(parent.task_plan.risk, -1):
raise ForbiddenError("child risk exceeds parent constraints")
child_plan.parent_task_id = parent_id
child_plan.correlation_id = parent.task_plan.correlation_id or parent.task_id
child_plan.preferred_workers = parent.task_plan.preferred_workers
child_plan.max_delegation_depth = min(
child_plan.max_delegation_depth,
parent.task_plan.max_delegation_depth,
)
child_plan.approval_policy = parent.task_plan.approval_policy
parent_budget = self.store.get_task_budget(parent_id)
required = Budget()
for name in ("time_seconds", "attempts", "tokens", "cost"):
available = getattr(parent_budget, name)
requested = getattr(child_plan.budgets, name)
if name == "attempts" and requested is None:
requested = 1
if available is not None:
requested = 0 if requested is None else requested
if requested > available:
raise ConflictError(f"budget exceeded for {name}")
setattr(child_plan.budgets, name, requested)
setattr(required, name, requested)
# A retried identical delegation is a read, not a second budget charge.
if child_plan.task_id:
try:
existing = self.store.get_task(child_plan.task_id)
except NotFoundError:
existing = None
if existing is not None:
if (
existing.task_plan.parent_task_id == parent_id
and existing.task_plan.operation_digest()
== child_plan.operation_digest()
):
return existing
raise ConflictError("delegation task_id conflict")
return self.store.delegate_task_with_budget_and_events(
parent_id, child_plan, caller=caller
)
def _depth(self, task_id: str) -> int:
return self.store._compute_depth(task_id)
+71
View File
@@ -0,0 +1,71 @@
"""Error types for task plane operations."""
from __future__ import annotations
class TaskPlaneError(Exception):
"""Base exception for API-compatible failures."""
status_code = 500
error_code = "task_plane_error"
def __init__(self, message: str, *, code: str | None = None, details: dict | None = None):
super().__init__(message)
self.message = message
self.details = details or {}
if code:
self.error_code = code
def to_payload(self) -> dict:
return {"error": self.error_code, "message": self.message, "details": self.details}
class TaskPlaneHTTPError(TaskPlaneError):
"""Base class with explicit HTTP status."""
status_code = 500
class ValidationError(TaskPlaneHTTPError):
status_code = 400
error_code = "validation_error"
class UnauthorizedError(TaskPlaneHTTPError):
status_code = 401
error_code = "unauthorized"
class ForbiddenError(TaskPlaneHTTPError):
status_code = 403
error_code = "forbidden"
class NotFoundError(TaskPlaneHTTPError):
status_code = 404
error_code = "not_found"
class ConflictError(TaskPlaneHTTPError):
status_code = 409
error_code = "conflict"
class PayloadTooLargeError(TaskPlaneHTTPError):
status_code = 413
error_code = "payload_too_large"
class FailedDependencyError(TaskPlaneHTTPError):
status_code = 424
error_code = "failed_dependency"
class LockedError(TaskPlaneHTTPError):
status_code = 423
error_code = "locked"
class ServiceUnavailableError(TaskPlaneHTTPError):
status_code = 503
error_code = "service_unavailable"
+108
View File
@@ -0,0 +1,108 @@
"""CloudEvents-compatible event envelope and durable outbox helpers."""
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime, timezone
from typing import Callable, Protocol
from .store import TaskPlaneStore
@dataclass
class CloudEvent:
specversion: str = "1.0"
id: str = ""
type: str = ""
source: str = "spiffe://aios/task-plane"
subject: str = ""
datacontenttype: str = "application/json"
data: dict | None = None
@property
def time(self) -> str:
return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
def asdict(self) -> dict:
return {
"specversion": self.specversion,
"id": self.id,
"type": self.type,
"source": self.source,
"subject": self.subject,
"time": self.time,
"datacontenttype": self.datacontenttype,
"data": self.data or {},
}
NOT_DELIVERED = "NOT_DELIVERED"
DELIVERED = "DELIVERED"
FAILED_DELIVERY = "FAILED"
class NotificationAdapter(Protocol):
name: str
def deliver(self, envelope: dict) -> str | None:
"""Return a provider receipt, or None when delivery is unconfirmed."""
class CallbackNotificationAdapter:
"""Adapter seam for webhook, NATS, GPT, dashboard, and WeChat transports."""
def __init__(self, name: str, callback: Callable[[dict], str | None]):
self.name = name
self._callback = callback
def deliver(self, envelope: dict) -> str | None:
return self._callback(envelope)
class WebhookAdapter(CallbackNotificationAdapter):
def __init__(self, callback: Callable[[dict], str | None]):
super().__init__("webhook", callback)
class NatsJetStreamAdapter(CallbackNotificationAdapter):
def __init__(self, callback: Callable[[dict], str | None]):
super().__init__("nats-jetstream", callback)
class GPTResultCallbackAdapter(CallbackNotificationAdapter):
def __init__(self, callback: Callable[[dict], str | None]):
super().__init__("gpt-result-callback", callback)
class DashboardAdapter(CallbackNotificationAdapter):
def __init__(self, callback: Callable[[dict], str | None]):
super().__init__("dashboard", callback)
class WeChatGatewayAdapter(CallbackNotificationAdapter):
def __init__(self, callback: Callable[[dict], str | None]):
super().__init__("wechat-gateway", callback)
class DurableOutboxDispatcher:
def __init__(self, store: TaskPlaneStore):
self.store = store
def dispatch(self, message_id: str, adapter: NotificationAdapter) -> dict:
row = self.store.get_outbox_message(message_id)
import json
envelope = json.loads(row["payload"])
try:
receipt = adapter.deliver(envelope)
except Exception:
return self.store.record_outbox_delivery(
message_id,
receipt=None,
error=f"{adapter.name} delivery failed",
)
return self.store.record_outbox_delivery(
message_id,
receipt=str(receipt) if receipt else None,
error=None if receipt else f"{adapter.name} receipt unavailable",
)
+31
View File
@@ -0,0 +1,31 @@
"""Evidence helpers."""
from __future__ import annotations
import hashlib
import json
from .store import TaskPlaneStore
def evidence_digest(payload: dict) -> str:
body = json.dumps(payload, sort_keys=True, separators=(",", ":"))
return hashlib.sha256(body.encode("utf-8")).hexdigest()
class EvidenceService:
def __init__(self, store: TaskPlaneStore):
self.store = store
def add(self, task_id: str, kind: str, payload: dict, worker_identity: str) -> str:
return self.store.add_evidence(task_id, kind, payload, worker_identity)
def list(self, task_id: str) -> list[dict]:
return self.store.list_evidence(task_id)
def verify(self, task_id: str, required_kinds: list[str]) -> bool:
evidences = self.list(task_id)
observed = {e["kind"] for e in evidences}
return set(required_kinds).issubset(
observed
) and self.store.verify_evidence_integrity(task_id)
+22
View File
@@ -0,0 +1,22 @@
"""Event journaling helpers."""
from __future__ import annotations
from .errors import ValidationError
from .store import TaskPlaneStore
class TaskJournal:
def __init__(self, store: TaskPlaneStore):
self.store = store
def append(self, task_id: str, event_type: str, data: dict, *, subject: str | None = None) -> None:
if not event_type:
raise ValidationError("event_type required")
self.store.append_event(task_id, event_type, data, subject=subject)
def list(self, task_id: str) -> list[dict]:
return self.store.list_events(task_id)
def check_chain(self, task_id: str) -> bool:
return self.store.verify_event_chain(task_id)
+32
View File
@@ -0,0 +1,32 @@
"""Claim and lease management helpers."""
from __future__ import annotations
from .errors import LockedError
from .models import ProbeEvidence
from .store import TaskPlaneStore
class LeaseManager:
def __init__(self, store: TaskPlaneStore):
self.store = store
def claim(self, task_id: str, worker_id: str, worker_identity: str, lease_seconds: int, expected_revision: int, operation_digest: str):
return self.store.claim_task(task_id, worker_id, worker_identity, lease_seconds, expected_revision, operation_digest)
def heartbeat(self, task_id: str, claim_id: str, lease_seconds: int):
claim = self.store.heartbeat_claim(task_id, claim_id, lease_seconds)
return claim
def probe(self, claim_id: str):
claim = self.store.get_claim(claim_id)
if claim.status != "ACTIVE":
raise LockedError("claim not active")
return claim
def recover(self, task_id: str, claim_id: str, probe: ProbeEvidence):
return self.store.recover_expired_claim(
task_id,
claim_id,
probe,
)
+829
View File
@@ -0,0 +1,829 @@
"""Core data models for the task plane."""
from __future__ import annotations
import hashlib
import json
import re
import time
import uuid
import urllib.parse
from enum import StrEnum
from dataclasses import dataclass, field
from datetime import datetime, timezone
from typing import Any
SENSITIVE_KEYS = {
"secret",
"password",
"token",
"api_key",
"access_token",
"refresh_token",
"authorization",
"private_key",
"client_secret",
"credential",
}
IDENTIFIER_RE = re.compile(r"[A-Za-z0-9][A-Za-z0-9._:-]{0,127}")
TYPE_RE = re.compile(r"[A-Za-z0-9][A-Za-z0-9._-]{0,127}")
RISK_LEVELS = {"low", "medium", "high"}
DATA_CLASSIFICATIONS = {
"public",
"internal",
"confidential",
"credential-secret",
}
SECRET_TEXT_PATTERNS = (
re.compile(r"(?i)\bAuthorization\s*:\s*Bearer\s+\S+"),
re.compile(r"(?i)\b(?:password|passwd|api[_-]?key|access[_-]?token|secret)\s*[:=]\s*\S+"),
re.compile(r"\bsk-[A-Za-z0-9_-]{12,}\b"),
re.compile(r"\bAKIA[0-9A-Z]{16}\b"),
re.compile(r"-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----"),
)
TASK_PLAN_KEYS = {
"schema_version",
"task_id",
"task_type",
"issuer",
"intent",
"required_capabilities",
"preferred_workers",
"risk",
"approval_policy",
"dependencies",
"max_delegation_depth",
"budgets",
"evidence_requirements",
"parent_task_id",
"correlation_id",
"metadata",
"data_classification",
"execution_zone",
"network_policy",
"logging_policy",
"allowed_input_paths",
"allowed_output_paths",
"output_policy",
}
def _now_ts() -> float:
return time.time()
def canonical_json(value: Any) -> str:
return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
def compute_sha256_hex(value: Any) -> str:
return hashlib.sha256(canonical_json(value).encode("utf-8")).hexdigest()
def contains_secret_text(value: Any) -> bool:
if isinstance(value, str):
return any(pattern.search(value) for pattern in SECRET_TEXT_PATTERNS)
if isinstance(value, dict):
return any(contains_secret_text(item) for item in value.values())
if isinstance(value, (list, tuple)):
return any(contains_secret_text(item) for item in value)
return False
def validate_relative_paths(values: Any, name: str) -> list[str]:
paths = _normalize_list(values)
for value in paths:
decoded = urllib.parse.unquote(value)
parts = value.replace("\\", "/").split("/")
if (
decoded != value
or "\\" in value
or value.startswith("/")
or re.match(r"^[A-Za-z]:", value)
or any(part in {"", ".", ".."} for part in parts)
):
raise ValueError(f"invalid {name}")
return paths
def validate_identifier(value: Any, name: str) -> str:
text = str(value or "")
if (
not IDENTIFIER_RE.fullmatch(text)
or urllib.parse.unquote(text) != text
or any(ord(ch) < 32 or ord(ch) == 127 for ch in text)
):
raise ValueError(f"invalid {name}")
return text
def validate_type(value: Any, name: str) -> str:
text = str(value or "")
if not TYPE_RE.fullmatch(text):
raise ValueError(f"invalid {name}")
return text
def redact_value(value: Any) -> Any:
if isinstance(value, dict):
redacted = {}
for key, inner in value.items():
lowered = str(key).lower()
if lowered in SENSITIVE_KEYS or lowered.endswith(
("_password", "_secret", "_access_token", "_refresh_token", "_private_key")
):
redacted[key] = "[REDACTED]"
else:
redacted[key] = redact_value(inner)
return redacted
if isinstance(value, list):
return [redact_value(v) for v in value]
if isinstance(value, tuple):
return tuple(redact_value(v) for v in value)
return value
class TaskStateTransitionError(ValueError):
pass
class TaskStatus(StrEnum):
CREATED = "CREATED"
QUEUED = "QUEUED"
CLAIMED = "CLAIMED"
RUNNING = "RUNNING"
WAITING_CHILD = "WAITING_CHILD"
VERIFYING = "VERIFYING"
COMPLETED = "COMPLETED"
FAILED_RECOVERABLE = "FAILED_RECOVERABLE"
FAILED_FINAL = "FAILED_FINAL"
CANCELLED = "CANCELLED"
MANUAL_REVIEW_REQUIRED = "MANUAL_REVIEW_REQUIRED"
TASK_STATES = {
status.value for status in TaskStatus
}
TASK_NEXT_STATES = {
"CREATED": {"QUEUED", "CLAIMED", "CANCELLED", "MANUAL_REVIEW_REQUIRED"},
"QUEUED": {"CLAIMED", "CANCELLED", "FAILED_RECOVERABLE", "FAILED_FINAL"},
"CLAIMED": {"RUNNING", "FAILED_RECOVERABLE", "FAILED_FINAL", "CANCELLED"},
"RUNNING": {"VERIFYING", "WAITING_CHILD", "COMPLETED", "FAILED_RECOVERABLE", "FAILED_FINAL", "CANCELLED"},
"WAITING_CHILD": {"RUNNING", "VERIFYING", "FAILED_RECOVERABLE", "FAILED_FINAL", "CANCELLED"},
"VERIFYING": {"COMPLETED", "FAILED_RECOVERABLE", "FAILED_FINAL", "CANCELLED"},
"COMPLETED": set(),
"FAILED_RECOVERABLE": {"CLAIMED", "RUNNING", "CANCELLED"},
"FAILED_FINAL": set(),
"CANCELLED": set(),
"MANUAL_REVIEW_REQUIRED": {"RUNNING", "CANCELLED"},
}
@dataclass
class Budget:
time_seconds: int | None = None
attempts: int | None = None
tokens: int | None = None
cost: float | None = None
@classmethod
def from_dict(cls, data: dict[str, Any] | None) -> "Budget":
if not data:
return cls()
if not isinstance(data, dict) or set(data) - {
"time_seconds",
"attempts",
"tokens",
"cost",
}:
raise ValueError("invalid budget")
for name in ("time_seconds", "attempts", "tokens", "cost"):
value = data.get(name)
if value is not None:
try:
if float(value) < 0:
raise ValueError("budget values must be non-negative")
except (TypeError, ValueError) as exc:
raise ValueError("invalid budget value") from exc
return cls(
time_seconds=_to_positive_int(data.get("time_seconds")),
attempts=_to_positive_int(data.get("attempts")),
tokens=_to_positive_int(data.get("tokens")),
cost=_to_float(data.get("cost")),
)
def to_dict(self) -> dict[str, Any]:
return {
"time_seconds": self.time_seconds,
"attempts": self.attempts,
"tokens": self.tokens,
"cost": self.cost,
}
def merge(self, other: "Budget") -> "Budget":
return Budget(
time_seconds=_first_non_none(self.time_seconds, other.time_seconds),
attempts=_first_non_none(self.attempts, other.attempts),
tokens=_first_non_none(self.tokens, other.tokens),
cost=_first_non_none(self.cost, other.cost),
)
@dataclass
class TaskPlan:
schema_version: int = 1
task_id: str | None = None
task_type: str = "code.change"
issuer: dict[str, Any] = field(default_factory=dict)
intent: str | None = None
required_capabilities: list[str] = field(default_factory=list)
preferred_workers: list[str] = field(default_factory=list)
risk: str = "medium"
approval_policy: str = "patch-only"
dependencies: list[str] = field(default_factory=list)
max_delegation_depth: int = 5
budgets: Budget = field(default_factory=Budget)
evidence_requirements: list[str] = field(default_factory=list)
parent_task_id: str | None = None
correlation_id: str | None = None
metadata: dict[str, Any] = field(default_factory=dict)
data_classification: str = "internal"
execution_zone: str = "isolated-development"
network_policy: str = "controlled"
logging_policy: str = "redacted"
allowed_input_paths: list[str] = field(default_factory=list)
allowed_output_paths: list[str] = field(default_factory=list)
output_policy: str = "standard"
@classmethod
def from_dict(cls, data: dict[str, Any] | None) -> "TaskPlan":
if not isinstance(data, dict):
raise ValueError("task plan payload must be an object")
unknown = set(data) - TASK_PLAN_KEYS
if unknown:
raise ValueError("unknown task plan fields")
if "task_id" not in data or data["task_id"] is None:
data = dict(data)
data["task_id"] = f"task-{uuid.uuid4()}"
risk = str(data.get("risk", "medium"))
if risk not in RISK_LEVELS:
raise ValueError("invalid risk")
issuer = data.get("issuer", {})
if not isinstance(issuer, dict):
raise ValueError("issuer must be an object")
schema_version = int(data.get("schema_version", 1))
if schema_version != 1:
raise ValueError("unsupported schema_version")
max_depth = _to_positive_int(data.get("max_delegation_depth"), default=5)
if max_depth is None or max_depth > 32:
raise ValueError("invalid max_delegation_depth")
classification = str(data.get("data_classification", "internal"))
if classification not in DATA_CLASSIFICATIONS:
raise ValueError("invalid data_classification")
execution_zone = validate_type(
data.get("execution_zone", "isolated-development"), "execution_zone"
)
network_policy = validate_type(
data.get("network_policy", "controlled"), "network_policy"
)
logging_policy = validate_type(
data.get("logging_policy", "redacted"), "logging_policy"
)
output_policy = validate_type(
data.get("output_policy", "standard"), "output_policy"
)
if contains_secret_text(
{
"intent": data.get("intent"),
"metadata": data.get("metadata", {}),
}
):
raise ValueError("task content contains secret material")
if classification == "credential-secret":
if execution_zone != "secure-file-transformer":
raise ValueError("credential-secret requires secure-file-transformer")
if network_policy != "denied":
raise ValueError("credential-secret requires network denied")
if logging_policy != "metadata-only":
raise ValueError("credential-secret requires metadata-only logging")
if output_policy != "hash-count-status-only":
raise ValueError("credential-secret requires restricted output")
return cls(
schema_version=schema_version,
task_id=validate_identifier(data["task_id"], "task_id"),
task_type=validate_type(data.get("task_type", "code.change"), "task_type"),
issuer=issuer,
intent=data.get("intent"),
required_capabilities=_normalize_list(data.get("required_capabilities", [])),
preferred_workers=_normalize_list(data.get("preferred_workers", [])),
risk=risk,
approval_policy=validate_type(
data.get("approval_policy", "patch-only"), "approval_policy"
),
dependencies=_normalize_list(data.get("dependencies", [])),
max_delegation_depth=max_depth,
budgets=Budget.from_dict(data.get("budgets")),
evidence_requirements=_normalize_list(data.get("evidence_requirements", [])),
parent_task_id=data.get("parent_task_id"),
correlation_id=data.get("correlation_id"),
metadata=data.get("metadata", {}) if isinstance(data.get("metadata", {}), dict) else {},
data_classification=classification,
execution_zone=execution_zone,
network_policy=network_policy,
logging_policy=logging_policy,
allowed_input_paths=validate_relative_paths(
data.get("allowed_input_paths", []), "allowed_input_paths"
),
allowed_output_paths=validate_relative_paths(
data.get("allowed_output_paths", []), "allowed_output_paths"
),
output_policy=output_policy,
)
def to_dict(self, *, redact: bool = False) -> dict[str, Any]:
payload = {
"schema_version": self.schema_version,
"task_id": self.task_id,
"task_type": self.task_type,
"issuer": self.issuer,
"intent": self.intent,
"required_capabilities": list(self.required_capabilities),
"preferred_workers": list(self.preferred_workers),
"risk": self.risk,
"approval_policy": self.approval_policy,
"dependencies": list(self.dependencies),
"max_delegation_depth": self.max_delegation_depth,
"budgets": self.budgets.to_dict(),
"evidence_requirements": list(self.evidence_requirements),
"parent_task_id": self.parent_task_id,
"correlation_id": self.correlation_id,
"metadata": dict(self.metadata),
"data_classification": self.data_classification,
"execution_zone": self.execution_zone,
"network_policy": self.network_policy,
"logging_policy": self.logging_policy,
"allowed_input_paths": list(self.allowed_input_paths),
"allowed_output_paths": list(self.allowed_output_paths),
"output_policy": self.output_policy,
}
if redact:
payload = redact_value(payload)
return payload
def operation_digest(self) -> str:
return compute_sha256_hex(self.to_dict())
@dataclass
class TaskRecord:
task_id: str
status: str
revision: int
task_plan: TaskPlan
created_at: float
updated_at: float
root_task_id: str
idempotency_key: str | None = None
failure_reason: str | None = None
def with_status(self, status: str) -> "TaskRecord":
allowed = TASK_NEXT_STATES.get(self.status, set())
if status != self.status and (not allowed and self.status not in {"COMPLETED", "FAILED_FINAL", "CANCELLED"}):
raise TaskStateTransitionError(f"Cannot transition {self.status} -> {status}")
if status not in TASK_STATES:
raise TaskStateTransitionError(f"Invalid status {status}")
if status != self.status and self.status in TASK_NEXT_STATES and status not in TASK_NEXT_STATES[self.status]:
raise TaskStateTransitionError(f"Cannot transition {self.status} -> {status}")
return TaskRecord(
task_id=self.task_id,
status=status,
revision=self.revision + 1,
task_plan=self.task_plan,
created_at=self.created_at,
updated_at=_now_ts(),
root_task_id=self.root_task_id,
idempotency_key=self.idempotency_key,
failure_reason=self.failure_reason,
)
def to_dict(self, *, include_task=True, redact_payload: bool = True) -> dict[str, Any]:
payload = {
"task_id": self.task_id,
"status": self.status,
"revision": self.revision,
"root_task_id": self.root_task_id,
"parent_task_id": self.task_plan.parent_task_id,
"created_at": _fmt_ts(self.created_at),
"updated_at": _fmt_ts(self.updated_at),
"failure_reason": self.failure_reason,
}
if include_task:
payload["plan"] = self.task_plan.to_dict(redact=redact_payload)
if self.idempotency_key:
payload["idempotency_key"] = self.idempotency_key
return payload
@dataclass
class WorkerRegistration:
worker_id: str
worker_type: str
identity: str
capabilities: list[str]
trust_zone: str
max_concurrency: int
heartbeat_interval_seconds: int
supported_protocols: list[str]
registered_at: float
last_heartbeat: float
status: str = "active"
disabled: bool = False
owner_identity: str = ""
registration_revision: int = 1
registration_policy: str = "self"
created_at: float = 0.0
updated_at: float = 0.0
disabled_reason: str | None = None
@classmethod
def from_dict(cls, data: dict[str, Any]) -> "WorkerRegistration":
if not isinstance(data, dict):
raise ValueError("worker registration payload must be an object")
now = _now_ts()
worker_id = validate_identifier(data.get("worker_id"), "worker_id")
worker_type = validate_type(data.get("worker_type"), "worker_type")
identity = str(data.get("identity") or "").strip()
if not identity or any(ord(ch) < 32 for ch in identity):
raise ValueError("invalid identity")
max_concurrency = _to_positive_int(data.get("max_concurrency"), default=1)
heartbeat_interval = _to_positive_int(
data.get("heartbeat_interval_seconds"), default=15
)
if not max_concurrency:
raise ValueError("max_concurrency must be positive")
if not heartbeat_interval:
raise ValueError("heartbeat_interval_seconds must be positive")
return cls(
worker_id=worker_id,
worker_type=worker_type,
identity=identity,
capabilities=_normalize_list(data.get("capabilities", [])),
trust_zone=str(data.get("trust_zone") or "default"),
max_concurrency=max_concurrency,
heartbeat_interval_seconds=heartbeat_interval,
supported_protocols=_normalize_list(
data.get("supported_protocols", ["aios-task-v1"])
),
registered_at=float(data.get("registered_at") or now),
last_heartbeat=float(data.get("last_heartbeat") or now),
status=str(data.get("status") or "active"),
disabled=bool(data.get("disabled", False)),
owner_identity=str(data.get("owner_identity") or identity),
registration_revision=int(data.get("registration_revision") or 1),
registration_policy=str(data.get("registration_policy") or "self"),
created_at=float(data.get("created_at") or data.get("registered_at") or now),
updated_at=float(data.get("updated_at") or now),
disabled_reason=data.get("disabled_reason"),
)
def to_dict(self, redact_payload: bool = True) -> dict[str, Any]:
payload = {
"worker_id": self.worker_id,
"worker_type": self.worker_type,
"identity": self.identity,
"capabilities": list(self.capabilities),
"trust_zone": self.trust_zone,
"max_concurrency": self.max_concurrency,
"heartbeat_interval_seconds": self.heartbeat_interval_seconds,
"supported_protocols": list(self.supported_protocols),
"registered_at": _fmt_ts(self.registered_at),
"last_heartbeat": _fmt_ts(self.last_heartbeat),
"status": self.status,
"disabled": self.disabled,
"owner_identity": self.owner_identity,
"registration_revision": self.registration_revision,
"registration_policy": self.registration_policy,
"created_at": _fmt_ts(self.created_at or self.registered_at),
"updated_at": _fmt_ts(self.updated_at or self.last_heartbeat),
"disabled_reason": self.disabled_reason,
}
if redact_payload:
payload = redact_value(payload)
return payload
@dataclass(frozen=True)
class EvidenceEnvelope:
schema_version: int
evidence_id: str
task_id: str
claim_id: str
kind: str
worker_identity: str
payload: dict[str, Any]
created_at: float
previous_evidence_hash: str
evidence_digest: str
signature: str | None = None
attestation_ref: str | None = None
@classmethod
def create(
cls,
*,
task_id: str,
claim_id: str,
kind: str,
worker_identity: str,
payload: dict[str, Any],
previous_evidence_hash: str,
signature: str | None = None,
attestation_ref: str | None = None,
evidence_id: str | None = None,
created_at: float | None = None,
) -> "EvidenceEnvelope":
envelope = cls(
schema_version=1,
evidence_id=evidence_id or f"evd-{uuid.uuid4()}",
task_id=validate_identifier(task_id, "task_id"),
claim_id=validate_identifier(claim_id, "claim_id"),
kind=validate_type(kind, "evidence kind"),
worker_identity=str(worker_identity),
payload=redact_value(payload),
created_at=float(created_at or _now_ts()),
previous_evidence_hash=str(previous_evidence_hash or ""),
evidence_digest="",
signature=signature,
attestation_ref=attestation_ref,
)
return dataclass_replace(
envelope, evidence_digest=compute_sha256_hex(envelope.digest_payload())
)
def digest_payload(self) -> dict[str, Any]:
return {
"schema_version": self.schema_version,
"evidence_id": self.evidence_id,
"task_id": self.task_id,
"claim_id": self.claim_id,
"kind": self.kind,
"worker_identity": self.worker_identity,
"payload": self.payload,
"created_at": self.created_at,
"previous_evidence_hash": self.previous_evidence_hash,
"attestation_ref": self.attestation_ref,
}
def verify(
self,
*,
expected_task_id: str,
expected_claim_id: str,
expected_previous_hash: str,
attestation_verifier: Any = None,
) -> bool:
if (
self.schema_version != 1
or self.task_id != expected_task_id
or self.claim_id != expected_claim_id
or self.previous_evidence_hash != expected_previous_hash
or self.evidence_digest != compute_sha256_hex(self.digest_payload())
):
return False
if self.signature or self.attestation_ref:
return True if attestation_verifier is None else bool(attestation_verifier(self))
return True
def to_dict(self) -> dict[str, Any]:
return {
**self.digest_payload(),
"evidence_digest": self.evidence_digest,
"signature": self.signature,
}
class ProbeOutcome(StrEnum):
NOT_APPLIED = "NOT_APPLIED"
APPLIED_VERIFIED = "APPLIED_VERIFIED"
INDETERMINATE = "INDETERMINATE"
CONFLICT = "CONFLICT"
@dataclass(frozen=True)
class ProbeEvidence:
schema_version: int
probe_id: str
task_id: str
claim_id: str
worker_id: str
worker_identity: str
operation_digest: str
outcome: ProbeOutcome
observed_at: float
expires_at: float
probe_executor_identity: str
evidence_digest: str
signature: str | None = None
attestation_ref: str | None = None
@classmethod
def create(cls, **data: Any) -> "ProbeEvidence":
outcome = data["outcome"]
if not isinstance(outcome, ProbeOutcome):
outcome = ProbeOutcome(str(outcome))
probe = cls(
schema_version=1,
probe_id=data.get("probe_id") or f"probe-{uuid.uuid4()}",
task_id=validate_identifier(data["task_id"], "task_id"),
claim_id=validate_identifier(data["claim_id"], "claim_id"),
worker_id=validate_identifier(data["worker_id"], "worker_id"),
worker_identity=str(data["worker_identity"]),
operation_digest=str(data["operation_digest"]),
outcome=outcome,
observed_at=float(data["observed_at"]),
expires_at=float(data["expires_at"]),
probe_executor_identity=str(data["probe_executor_identity"]),
evidence_digest="",
signature=data.get("signature"),
attestation_ref=data.get("attestation_ref"),
)
return dataclass_replace(
probe, evidence_digest=compute_sha256_hex(probe.digest_payload())
)
def digest_payload(self) -> dict[str, Any]:
return {
"schema_version": self.schema_version,
"probe_id": self.probe_id,
"task_id": self.task_id,
"claim_id": self.claim_id,
"worker_id": self.worker_id,
"worker_identity": self.worker_identity,
"operation_digest": self.operation_digest,
"outcome": self.outcome.value,
"observed_at": self.observed_at,
"expires_at": self.expires_at,
"probe_executor_identity": self.probe_executor_identity,
"attestation_ref": self.attestation_ref,
}
def verify(self, *, now: float, attestation_verifier: Any) -> bool:
if (
self.schema_version != 1
or self.expires_at <= now
or self.observed_at > now
or self.evidence_digest != compute_sha256_hex(self.digest_payload())
or not (self.signature or self.attestation_ref)
):
return False
return bool(attestation_verifier and attestation_verifier(self))
def to_dict(self) -> dict[str, Any]:
return {
**self.digest_payload(),
"evidence_digest": self.evidence_digest,
"signature": self.signature,
}
def dataclass_replace(value: Any, **changes: Any) -> Any:
from dataclasses import replace
return replace(value, **changes)
@dataclass
class WorkerClaim:
claim_id: str
task_id: str
worker_id: str
worker_identity: str
claimed_at: float
lease_until: float
attempt: int
task_revision: int
operation_digest: str
status: str = "ACTIVE"
def to_dict(self, redact_payload: bool = True) -> dict[str, Any]:
payload = {
"claim_id": self.claim_id,
"task_id": self.task_id,
"worker_id": self.worker_id,
"worker_identity": self.worker_identity,
"claimed_at": _fmt_ts(self.claimed_at),
"lease_until": _fmt_ts(self.lease_until),
"attempt": self.attempt,
"task_revision": self.task_revision,
"operation_digest": self.operation_digest,
"status": self.status,
}
if redact_payload:
payload = redact_value(payload)
return payload
@dataclass
class ResultEnvelope:
task_id: str
claim_id: str
worker_identity: str
status: str
outputs: list[dict[str, Any]]
evidence: list[dict[str, Any]]
started_at: str
completed_at: str
result_digest: str | None = None
signature: str | None = None
attestation_ref: str | None = None
operation_digest: str | None = None
trust_status: str = "untrusted_shadow_result"
def digest_payload(self) -> dict[str, Any]:
return {
"task_id": self.task_id,
"claim_id": self.claim_id,
"worker_identity": self.worker_identity,
"status": self.status,
"outputs": self.outputs,
"evidence": self.evidence,
"started_at": self.started_at,
"completed_at": self.completed_at,
"operation_digest": self.operation_digest,
"attestation_ref": self.attestation_ref,
}
def computed_digest(self) -> str:
return compute_sha256_hex(self.digest_payload())
def to_dict(self, redact_payload: bool = True) -> dict[str, Any]:
payload = {
"task_id": self.task_id,
"claim_id": self.claim_id,
"worker_identity": self.worker_identity,
"status": self.status,
"outputs": self.outputs,
"evidence": self.evidence,
"started_at": self.started_at,
"completed_at": self.completed_at,
"result_digest": self.result_digest,
"signature": self.signature,
"attestation_ref": self.attestation_ref,
"operation_digest": self.operation_digest,
"trust_status": self.trust_status,
}
if redact_payload:
payload = redact_value(payload)
return payload
def _normalize_list(value: Any) -> list[str]:
if not isinstance(value, list):
raise ValueError("expected list")
if len(value) > 128 or any(
not isinstance(v, str)
or not v
or len(v) > 256
or any(ord(ch) < 32 for ch in v)
for v in value
):
raise ValueError("invalid list item")
return list(value)
def _to_positive_int(value: Any, default: int | None = None) -> int | None:
if value is None:
return default
try:
number = int(value)
except (TypeError, ValueError):
return default
return number if number >= 0 else default
def _to_float(value: Any) -> float | None:
if value is None:
return None
try:
return float(value)
except (TypeError, ValueError):
return None
def _first_non_none(primary: Any, fallback: Any) -> Any:
if primary is not None:
return primary
return fallback
def iso8601_now() -> str:
return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
def _fmt_ts(value: float | None) -> str | None:
if value is None:
return None
return datetime.fromtimestamp(value, tz=timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
+226
View File
@@ -0,0 +1,226 @@
"""Policy and verification logic (fail-closed)."""
from __future__ import annotations
from dataclasses import dataclass
from typing import Callable
from .errors import ForbiddenError, LockedError
from .models import ProbeEvidence, ResultEnvelope, TaskRecord
RISK_RANK = {"low": 1, "medium": 2, "high": 3}
READONLY_WORKER_ACTIONS = frozenset(
{"claim", "result", "fail", "progress", "heartbeat"}
)
ALL_ACTION_CAPABILITIES = frozenset(
{
"task.creator",
"worker.self_register",
"worker.admin",
"task.claim",
"task.cancel",
"task.delegate",
"task.read",
"task.audit",
}
)
@dataclass
class PolicyDecision:
allowed: bool
reason: str = ""
@dataclass(frozen=True)
class ReadonlyWorkerProfile:
"""A narrowly-scoped identity allowed to process safe Shadow tasks only."""
worker_id: str
identity: str
capabilities: frozenset[str]
task_types: frozenset[str] = frozenset({"code.review", "test.execute"})
def permits_task(self, task: TaskRecord) -> bool:
plan = task.task_plan
if plan.task_type not in self.task_types or plan.risk != "low":
return False
if plan.data_classification not in {"public", "internal"}:
return False
if plan.metadata.get("production_side_effects") is not False:
return False
return set(plan.required_capabilities).issubset(self.capabilities)
class IdentityVerifier:
def __init__(self, trusted_identities: set[str] | None = None):
self.trusted_identities = trusted_identities
def verify(self, identity: str) -> bool:
if self.trusted_identities is None:
return True
return identity in self.trusted_identities
class EvidenceVerifier:
def __init__(self, mandatory: list[str] | None = None):
self.mandatory = set(mandatory or [])
def verify(self, task: TaskRecord, evidences: list[dict]) -> PolicyDecision:
required = set(task.task_plan.evidence_requirements or [])
if not required and not self.mandatory:
return PolicyDecision(True)
observed = {e.get("kind") for e in evidences}
missing = required.union(self.mandatory) - observed
if missing:
return PolicyDecision(False, f"missing evidence: {', '.join(sorted(missing))}")
return PolicyDecision(True)
class PolicyEngine:
"""
Fail-closed policy engine with minimal but explicit decisions.
"""
def __init__(
self,
*,
identity_verifier: IdentityVerifier | None = None,
evidence_verifier: EvidenceVerifier | None = None,
execution_enabled: bool = False,
max_risk: str = "medium",
result_signature_verifier: Callable[[ResultEnvelope], bool] | None = None,
probe_attestation_verifier: Callable[[ProbeEvidence], bool] | None = None,
identity_capabilities: dict[str, set[str]] | None = None,
admin_identities: set[str] | None = None,
readonly_worker_execution_enabled: bool = False,
readonly_worker_profiles: list[ReadonlyWorkerProfile] | None = None,
) -> None:
if max_risk not in RISK_RANK:
raise ValueError("invalid max_risk")
self.identity_verifier = identity_verifier or IdentityVerifier()
self.evidence_verifier = evidence_verifier or EvidenceVerifier()
self.execution_enabled = execution_enabled
self.max_risk = max_risk
self.result_signature_verifier = result_signature_verifier
self.probe_attestation_verifier = probe_attestation_verifier
self.identity_capabilities = {
identity: set(capabilities)
for identity, capabilities in (identity_capabilities or {}).items()
}
self.admin_identities = set(admin_identities or ())
self.readonly_worker_execution_enabled = readonly_worker_execution_enabled
self.readonly_worker_profiles = {
profile.identity: profile for profile in (readonly_worker_profiles or [])
}
def _readonly_worker_allows(
self, action: str, actor: str, task: TaskRecord | None
) -> bool:
profile = self.readonly_worker_profiles.get(actor)
return bool(
self.readonly_worker_execution_enabled
and action in READONLY_WORKER_ACTIONS
and profile is not None
and (task is None or profile.permits_task(task))
)
def assert_readonly_worker_binding(self, actor: str, worker_id: str) -> None:
profile = self.readonly_worker_profiles.get(actor)
if profile is not None and profile.worker_id != worker_id:
raise ForbiddenError("worker identity binding mismatch")
def assert_readonly_worker_registration(self, actor: str, payload: dict) -> None:
profile = self.readonly_worker_profiles.get(actor)
if profile is None:
return
if (
str(payload.get("worker_id") or "") != profile.worker_id
or str(payload.get("identity") or "") != profile.identity
or set(payload.get("capabilities") or []) != set(profile.capabilities)
or str(payload.get("trust_zone") or "") != "isolated-review"
or int(payload.get("max_concurrency") or 0) != 1
or int(payload.get("heartbeat_interval_seconds") or 0) != 15
):
raise ForbiddenError("readonly worker registration mismatch")
def verify_result(
self, result: ResultEnvelope, task: TaskRecord | None = None
) -> PolicyDecision:
computed = result.computed_digest()
if result.result_digest and result.result_digest != computed:
return PolicyDecision(False, "result digest mismatch")
sensitive = bool(
task
and (
task.task_plan.data_classification
in {"confidential", "credential-secret"}
or any(
capability.startswith(
("code.write", "deploy", "secret", "production")
)
for capability in task.task_plan.required_capabilities
)
)
)
if sensitive and not (result.signature or result.attestation_ref):
return PolicyDecision(False, "trusted result proof required")
if result.signature is not None or result.attestation_ref is not None:
if self.result_signature_verifier is None:
return PolicyDecision(False, "result signature verifier unavailable")
if not self.result_signature_verifier(result):
return PolicyDecision(False, "result signature invalid")
result.trust_status = "trusted_result"
else:
result.trust_status = "untrusted_shadow_result"
return PolicyDecision(True)
def authorize(self, action: str, actor: str, task: TaskRecord | None = None, context: dict | None = None) -> PolicyDecision:
context = context or {}
if not self.identity_verifier.verify(actor):
return PolicyDecision(False, "identity untrusted")
action_capabilities = {
"create": "task.creator",
"read": "task.read",
"audit": "task.audit",
"claim": "task.claim",
"result": "task.claim",
"fail": "task.claim",
"progress": "task.claim",
"heartbeat": "task.claim",
"cancel": "task.cancel",
"delegate": "task.delegate",
"worker.register": "worker.self_register",
"worker.admin": "worker.admin",
}
required_capability = action_capabilities.get(action)
if required_capability:
granted = set(
self.identity_capabilities.get(
actor, self.identity_capabilities.get("*", set())
)
)
if required_capability not in granted:
return PolicyDecision(False, f"action {action} not permitted")
if action in {"claim", "result", "fail", "cancel", "progress", "delegate", "heartbeat"} and not self.execution_enabled:
if not self._readonly_worker_allows(action, actor, task):
return PolicyDecision(False, "execution disabled")
if action == "delegate" and task and task.status not in {"COMPLETED", "FAILED_FINAL", "CANCELLED"}:
risk = str(context.get("risk", task.task_plan.risk))
if RISK_RANK.get(risk, 2) > RISK_RANK.get(self.max_risk, 1):
return PolicyDecision(False, f"risk {risk} exceeds policy")
return PolicyDecision(True)
def assert_allowed(self, action: str, actor: str, task: TaskRecord | None = None, context: dict | None = None):
decision = self.authorize(action, actor, task, context=context)
if not decision.allowed:
if decision.reason == "identity untrusted":
raise ForbiddenError(decision.reason)
if decision.reason == "execution disabled":
raise LockedError("execution disabled")
raise ForbiddenError(decision.reason)
@@ -0,0 +1,76 @@
"""Task scheduler primitives."""
from __future__ import annotations
import time
from .errors import NotFoundError
from .capabilities import has_capabilities
from .models import WorkerClaim
from .store import TaskPlaneStore
class TaskScheduler:
def __init__(self, store: TaskPlaneStore):
self.store = store
def next_queued_task(self, worker_id: str):
worker = self.store.get_worker(worker_id)
tasks = sorted(
self.store.list_tasks(),
key=lambda task: (
-int(task.task_plan.metadata.get("priority", 0)),
task.created_at,
task.task_id,
),
)
for task in tasks:
if task.status not in {"CREATED", "QUEUED", "FAILED_RECOVERABLE"}:
continue
if task.task_plan.task_type == "worker.registry.audit":
continue
if task.status == "FAILED_RECOVERABLE" and not self.store.task_has_retryable_probe(
task.task_id
):
continue
if not has_capabilities(
worker.capabilities, task.task_plan.required_capabilities
):
continue
if any(
self.store.get_task(dependency).status != "COMPLETED"
for dependency in task.task_plan.dependencies
):
continue
if (
task.task_plan.data_classification == "credential-secret"
and worker.worker_type != "secure_file_transformer"
and "secure_file_transformer" not in worker.capabilities
):
continue
return task
return None
def schedule_for_worker(self, worker_id: str, lease_seconds: int = 15) -> WorkerClaim:
worker = self.store.get_worker(worker_id)
if worker.disabled:
from .errors import LockedError
raise LockedError("worker disabled")
if time.time() > worker.last_heartbeat + (
worker.heartbeat_interval_seconds * 3
):
from .errors import LockedError
raise LockedError("worker offline")
task = self.next_queued_task(worker_id)
if task is None:
raise NotFoundError("no queued tasks available")
claim = self.store.claim_task(
task.task_id,
worker_id,
worker.identity,
lease_seconds=lease_seconds,
expected_revision=task.revision,
operation_digest=task.task_plan.operation_digest(),
)
return claim
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,68 @@
"""Worker registry abstraction and read helpers."""
from __future__ import annotations
import time
from .capabilities import has_capabilities
from .errors import NotFoundError
from .models import WorkerRegistration
from .store import TaskPlaneStore
class WorkerRegistry:
def __init__(self, store: TaskPlaneStore):
self.store = store
def register(
self,
payload: dict,
*,
actor_identity: str,
is_admin: bool,
expected_revision: int | None,
) -> WorkerRegistration:
reg = WorkerRegistration.from_dict(payload)
return self.store.register_worker(
reg,
actor_identity=actor_identity,
is_admin=is_admin,
expected_revision=expected_revision,
)
def heartbeat(
self,
worker_id: str,
heartbeat_interval_seconds: int | None = None,
*,
actor_identity: str,
expected_revision: int | None,
) -> WorkerRegistration:
return self.store.heartbeat_worker(
worker_id,
heartbeat_interval_seconds=heartbeat_interval_seconds,
actor_identity=actor_identity,
expected_revision=expected_revision,
)
def list(self):
return self.store.list_workers()
def get(self, worker_id: str) -> WorkerRegistration:
try:
return self.store.get_worker(worker_id)
except NotFoundError:
raise
def can_execute(self, worker_id: str, required_capabilities: list[str]) -> bool:
worker = self.store.get_worker(worker_id)
return has_capabilities(worker.capabilities, required_capabilities)
def is_online(self, worker_id: str, *, now: float | None = None) -> bool:
worker = self.store.get_worker(worker_id)
deadline = worker.last_heartbeat + (worker.heartbeat_interval_seconds * 3)
return not worker.disabled and (time.time() if now is None else now) <= deadline
def stale_workers(self, *, now: float | None = None) -> list[WorkerRegistration]:
current = time.time() if now is None else now
return [worker for worker in self.list() if not self.is_online(worker.worker_id, now=current)]
@@ -0,0 +1,83 @@
{
"approval_request_branch": "governance/request/integration-refresh-control-plane-deployment-20260728T093428Z",
"approval_type": "CONTROL_PLANE_DEPLOYMENT_APPROVAL_REQUEST",
"authorization_lineage": {
"execution_renewal_merge_oid": "3a22c3c7d86930efd4bd93ac05d80ad322050cb9",
"grant_id": "grant-11354281aafdd074df5899a3925dd2a0370cffa9edb06f5c7d7227ce8de00d91",
"source_mapping_approval_merge_oid": "9514062cf33e45941abe4a8c98377e0713b0b3f6"
},
"deployment_status": "NOT_EXECUTED",
"evidence": {
"control_plane_artifact": {
"path": "/var/lib/aios-integration-refresh-source-writer-v2/artifacts/control-plane-patch-112d995545e3e79126a062b2643a9f0c17ae7bd4.tar",
"sha256": "bf101ade1aaf956f40c0bba74ed4b347f5f38d0de6a0c1b7694ae107c4b43c14",
"size_bytes": 13363200
},
"rendered_systemd_config_sha256": "182a3cba48e752798cf24323a8c0249c53d7386371388a8db0435a29c43dc969",
"rollback_artifact": {
"path": "/var/lib/aios-gitops/runtime/tasks/AIOS-INTEGRATION-REFRESH-PROVISIONER-IMPLEMENTATION-20260726/implementation/rollback-to-85426f4f570e4d40b212318d47e52e4aed5cb06a.tar",
"sha256": "f12a2a95e475da5d02eaff99e93615cb0186019ca30edae013ab054a2c6418ce"
},
"source_map": {
"path": "docs/autonomous-release/INTEGRATION_REFRESH_SOURCE_MAP_20260728.json",
"sha256": "01ddd56cfa2a525559e2d174cf7df117a9465ffce44ef25bc7a15b8e74d06bf5"
},
"test_evidence": {
"collection_errors": 0,
"path": "/var/lib/aios-gitops/runtime/tasks/AIOS-INTEGRATION-REFRESH-PROVISIONER-IMPLEMENTATION-20260726/implementation/test-evidence.json",
"sha256": "d8933553f56462a3f1b3040dec989ff0adf717a17728d55ba0fe9e127d93c206",
"tests_failed": 0,
"tests_passed": 41
}
},
"execution_conditions": {
"candidate_only": true,
"integration_unchanged": true,
"main_unchanged": true,
"no_credentials_or_secrets_in_request": true,
"production_baseline_unchanged": true,
"registry_unchanged": true
},
"expected_governance_base_oid": "3a22c3c7d86930efd4bd93ac05d80ad322050cb9",
"expires_at": "2026-07-28T21:34:28Z",
"implementation": {
"commit_oid": "112d995545e3e79126a062b2643a9f0c17ae7bd4",
"tree_oid": "4ed88399fb8e7e4c235413ebbd783c9b6ad3c624"
},
"implementation_branch": "patch/integration-refresh-grant-provisioner-20260726",
"issued_at": "2026-07-28T09:34:28Z",
"prohibited_operations": [
"production-baseline.write",
"production-tag.write",
"main.write",
"integration.write",
"registry.write",
"force-push",
"direct-main-push",
"direct-tag-write"
],
"repository": "aios/aios",
"request_status": "PENDING_HUMAN_APPROVAL",
"requested_operations": [
"control-plane.candidate.deploy",
"control-plane.candidate.verify",
"control-plane.candidate.rollback"
],
"required_controls": [
"Two distinct human approvals, one Codex and one GPT, both APPROVED against the same current approval-request PR head.",
"Re-verify artifact, test evidence, source map, rendered systemd configuration, and rollback artifact paths and SHA-256 digests immediately before execution.",
"AIOS_GITOPS_WRITES_ENABLED=false throughout candidate deployment, verification, and rollback.",
"No force push, direct main write, or direct tag write."
],
"required_reviewers": [
"codex",
"gpt"
],
"schema": "aios.control-plane-deployment-approval-request/v1",
"target_governance": {
"approved_base_oid": "3a22c3c7d86930efd4bd93ac05d80ad322050cb9",
"binding_rule": "Human approvals must bind to the same approval-request PR head; its first parent must equal the active governance base."
},
"task_id": "AIOS-INTEGRATION-REFRESH-PROVISIONER-IMPLEMENTATION-20260726",
"validity_hours": 12
}
@@ -0,0 +1,127 @@
{
"schema": "aios.governance-approval/v1",
"approval_type": "TASK_SCOPED_GOVERNANCE_BOOTSTRAP_POLICY_ACTIVATION",
"approval_plan_id": "AIOS-TSG-BS-PHASE-A-RENEWAL-20260731T081147Z",
"task_id": "AIOS-V2R1C4-TWO-STAGE-OWNER-CAMPAIGN-V2-PHASE-A-RENEWAL",
"repository": "aios/aios",
"issued_at": "2026-07-31T08:11:47Z",
"expires_at": "2026-07-31T20:11:47Z",
"expected_governance_base_oid": "ebd74398743e36523b593fcc395b73945d4924c5",
"expected_governance_base_tree_oid": "ad1255a1127204fad1d1208d8f31dc94fa64e6e1",
"approval_request_branch": "governance/request/two-stage-owner-campaign-v2-phase-a-renewal-20260731T081147Z",
"request_head_binding": "GITEA_EXACT_HEAD_REVIEW_POST_COMMIT",
"human_authorizer": "owner",
"authorization_mode": "GITEA_EXACT_HEAD_REVIEW",
"required_human_approvals": 1,
"allowed_human_approvers": [
"owner"
],
"require_latest_head_approval": true,
"dismiss_stale_approvals": true,
"auto_merge_after_machine_gates": true,
"phase_a_scope": {
"schema_id": "TASK_SCOPED_GOVERNANCE_BOOTSTRAP",
"schema_version": 1,
"activate_schema_and_validator_only": true,
"phase_b_release_authorized": false,
"publisher_authorized": false,
"writer_authorized": false,
"credential_projection_authorized": false,
"tag_authorized": false,
"registry_authorized": false,
"baseline_authorized": false,
"production_deployment_authorized": false
},
"content_bindings": {
"schema_document": {
"path": "services/aios-single-owner-governance/task-scoped-governance-bootstrap-v1.schema.json",
"blob_oid": "9b117667c0d98f995ac059b0e1b5ffed06ca6444",
"sha256": "6ec912a96c8bac5c0d3c0a017abc078cb9567ed54dcb20bb0eadb5926ac54fdd"
},
"validator_policy": {
"path": "services/aios-single-owner-governance/aios_single_owner_governance/task_scoped_bootstrap.py",
"blob_oid": "c18d15f31340f19fc5da87944901bdfb867ab6dc",
"sha256": "de11123b5cd9ccdad4961f1d5d084a8c4a1b2270ce793652ab9c50bb95694eac"
},
"evidence_manifest": {
"path": "evidence/governance-policy/AIOS-TWO-STAGE-OWNER-CAMPAIGN-V2-PHASE-A-20260730T023551Z.json",
"blob_oid": "c3d215b3ea525a25de514c32c5aad91d47e140b4",
"sha256": "1b0a993940b41a4b16724b557f6abd8b6cebb56dc64cc8f813bd605b6a3399c2"
},
"checksums_manifest": {
"path": "evidence/governance-policy/AIOS-TWO-STAGE-OWNER-CAMPAIGN-V2-PHASE-A-20260730T023551Z.sha256",
"blob_oid": "45ed084234dae6a00e69a41cd4f716f46a97d9fb",
"sha256": "51c4b029c0792dc98d41f754e484ba3861cf7604443d9c5f289b3b0af768a41a"
}
},
"offline_candidate_bindings": {
"schema_seed_commit_oid": "8da47545a507cb798d3d6ab30ff8552a2d0a5ecf",
"schema_seed_tree_oid": "991e68577a469657c1c4996814b6634ce5ed752f",
"implementation_commit_oid": "a637a9dbb6e0021cb7dcf0d6b0ca00dfe10dce38",
"implementation_tree_oid": "bf6e0c16fcad7515465bec4aefdd2e77b1e9bef0",
"evidence_closure_commit_oid": "1e4885197f508f7921b148d71e545b3de0029b0a",
"evidence_closure_tree_oid": "e55cf9ef19d1c6804f161de54fd3395a06149807",
"source_diff_sha256": "c06d002db06da6cbf16f9c8321f1acbfd431f009b5127a13fd2cb5d63b278867",
"source_pathset_sha256": "5061402d516c0e9413208ce64e2b52f77d224e6eec1f7b07c5b3a10b58df5eaf",
"implementation_bundle_sha256": "15711b225b9ef41cca0588d21b2b95d7a95ac155efc27fcb8772c435fcdab147",
"evidence_bundle_sha256": "ec97b8460151fc0e7e7ea9ae044ca13a1543c4b79c41a437ef087d3b1c271857"
},
"runtime_dependency_bindings": {
"handoff_zip_sha256": "668239a549b4a2b2094fe9b5104b84703fe8ed75011fd8ed71ed3958a6dc0706",
"installed_wheel_sha256": "abbf616366eb3467d4e651bb5306e22ae5e931b613df5362a0892935c4a1c0c7",
"anchor_service": "aios-campaign-anchor.service",
"anchor_uid": 988,
"runner_uid": 986,
"anchor_database": "/var/lib/aios-campaign-anchor/anchor.sqlite3",
"anchor_socket": "/run/aios-campaign-anchor/anchor.sock",
"AIOS_GITOPS_WRITES_ENABLED": false
},
"required_machine_status_contexts": [
"governance/schema-contract",
"governance/validator-security",
"governance/fault-injection",
"governance/runtime-isolation",
"governance/authority-mirror"
],
"allowed_operations_after_exact_head_owner_approval": [
"observe_governance_pr",
"wait_governance_gates",
"observe_governance_merge",
"observe_validator_activation",
"complete_policy_activation"
],
"denied_operations": [
"main.write",
"integration.write",
"tag.write",
"registry.write",
"baseline.write",
"production.deploy",
"force_push",
"history_rewrite",
"governance.direct_push",
"approval.impersonate",
"publisher.create",
"writer.create",
"credential.project",
"phase_b.execute"
],
"merge_constraints": {
"allowed_modes": [
"FAST_FORWARD_TO_APPROVED_HEAD",
"EXACT_TWO_PARENT_MERGE_WITH_APPROVED_TREE"
],
"force": false,
"plus_refspec": false,
"tags": false,
"prune": false
},
"governance_declarations": {
"AIOS_GITOPS_WRITES_ENABLED": false,
"force_push_used": false,
"tag_rewritten": false,
"direct_main_push": false,
"secrets_committed": false,
"owner_approval_respected": true
}
}
@@ -0,0 +1,79 @@
{
"AIOS_GITOPS_WRITES_ENABLED": false,
"acknowledges_pr30_owner_approval_was_missing": true,
"activation_prerequisites_artifact_sha256": "729077800c6510bf8930c97a78781218d1bce9ec40157aa99fc2bd205466e895",
"activation_prerequisites_commit_oid": "fa038d17b29001e26f1ac88019a407a4b3ebaa7e",
"activation_prerequisites_rollback_artifact_sha256": "00e40eace10a0aae1731eb98851fec71a0c13c48e1e6e5a113e30a4356c2086f",
"activation_prerequisites_tree_oid": "d5ff67f848807ef2c7a695c05cdd0bb5f2644fc2",
"approval_request_branch": "governance/request/single-owner-atomic-activation-owner-ratification-20260729T014918Z-e503d96665ee",
"approval_type": "SINGLE_OWNER_GOVERNANCE_ATOMIC_ACTIVATION_OWNER_RATIFICATION",
"branch_protection_adapter_sha256": "2706dc6095ff639838f72c91a66c1f80711893c8c0498555b388feaacf2db569",
"denied_operations": [
"main.write",
"integration.write",
"tag.write",
"registry.write",
"baseline.write",
"production.deploy",
"force_push",
"history_rewrite",
"governance.direct_push",
"approval.impersonate"
],
"direct_protected_branch_push_allowed": false,
"dismiss_stale_approvals": true,
"dormant_deployment_evidence_sha256": "c4c326784a3d66d62e32c46be3506c801585c06a34e62c9a1f210492a6c1bc06",
"evidence_closure_oid": "5f45139ca5f7f7f3da244141c5115d12f5af58ae",
"evidence_closure_sha256": "53e5f2fdc01a6225048dfdf6eac973e99743f0295f17f3a42f01dce7e7050ab8",
"expected_governance_base_oid": "f6bef90633f428454e85721f353cd82cdf04caf0",
"expires_at": "2026-07-29T13:49:18Z",
"explicitly_allowed_operations_after_exact_head_approvals_and_merge": [
"approval_validator.activate",
"governance_state_machine.activate",
"gitea_branch_protection.atomic_update",
"rollback_configuration.install",
"single_owner_policy.activate",
"break_glass.expire",
"post_activation_tests.execute"
],
"force_push_allowed": false,
"human_authorizer": "owner",
"issued_at": "2026-07-29T01:49:18Z",
"legacy_mechanical_approvals": [
"codex",
"gpt"
],
"merge_mode": "CREATE_MERGE_COMMIT",
"pr30_codex_approval_review_id": 45,
"pr30_codex_reviewed_at": "2026-07-29T01:24:38Z",
"pr30_codex_reviewed_head_oid": "c2eed5b37983a6461783361efb6de43a28f909bb",
"pr30_gpt_approval_review_id": 46,
"pr30_gpt_reviewed_at": "2026-07-29T01:25:02Z",
"pr30_gpt_reviewed_head_oid": "c2eed5b37983a6461783361efb6de43a28f909bb",
"pr30_manifest_path": "approvals/governance-policy/SINGLE-OWNER-GOVERNANCE-ATOMIC-ACTIVATION-20260728T204808Z-4cab1d5829d6.json",
"pr30_merge_tree_oid": "219974f085efe473fcc6ad2170f67312d8e57c54",
"pr30_merged_at": "2026-07-29T01:25:08Z",
"pr30_owner_review_id": 42,
"pr30_owner_review_state_observed": "REQUESTED_NOT_APPROVED",
"pr30_owner_reviewed_head_oid": null,
"pr30_premerge_base_oid": "6e8212608ee310e2fb45f1e802359f4db2e9077b",
"production_deployment_allowed": false,
"ratifies_pr30_manifest_blob_oid": "38c95dde2d3831f8e3c80b342369f1c3e1cbf34c",
"ratifies_pr30_manifest_sha256": "bc19ada0e8eeb47d421a6360cf1d66b6d1129eb6056a447d9ff70ebd5ada1718",
"ratifies_pr30_merge_oid": "f6bef90633f428454e85721f353cd82cdf04caf0",
"ratifies_pr30_request_oid": "c2eed5b37983a6461783361efb6de43a28f909bb",
"ratifies_pr_number": 30,
"require_latest_head_approval": true,
"required_reviewers": [
"owner",
"codex",
"gpt"
],
"rollback_configuration_sha256": "eb44fc5f655f8c30c4b687185055ba3a2725189e19538e3a9a46a2c0cf141b8c",
"schema": "aios.governance-approval/v1",
"state_machine_sha256": "0b08a2c378d143cb9031a25132afc7b7d825bdc03d11efe10610341d4b70167e",
"task_id": "AIOS-PR30-SINGLE-OWNER-ATOMIC-ACTIVATION-OWNER-RATIFICATION-20260729",
"test_evidence_sha256": "42168da7437028440bece3432d8aa38a8722b4db18244423d9daaa380675c45a",
"validator_sha256": "754639846cb15cb997d612a1fe0ceb0224ddef5bbc3ccac9802a3747f122dac1",
"wip_blocks_merge": true
}
@@ -0,0 +1,91 @@
{
"approval_request_branch": "governance/request/single-owner-governance-activation-prerequisites-20260728T183025127107Z",
"approval_type": "SINGLE_OWNER_GOVERNANCE_ACTIVATION_PREREQUISITES",
"binds_final_ratification_manifest_blob_oid": "2865fbec766901eb61fc8aed0542970e951e0d1d",
"binds_final_ratification_manifest_sha256": "216e54503d8e6a3f4c908084e19ce1e6242d3ba15f7b8011a9a9d9f7975e63e7",
"binds_final_ratification_merge_oid": "2bef44f6bc8cf7bc04c959ab629ac95561fa609f",
"binds_mirror_receipt_sha256": "6873133f7233bfed0d3b78b01847ad4251b8b67f3b6db7011efd07664f8d088f",
"expected_governance_base_oid": "2bef44f6bc8cf7bc04c959ab629ac95561fa609f",
"expires_at": "2026-07-29T06:30:25Z",
"explicitly_allowed_after_owner_approval_and_legacy_merge": [
"task_bound_activation_writer_grant.create",
"gitea_owner_review_adapter.create_and_dormant_deploy",
"machine_status_producer.create_and_dormant_deploy",
"machine_status_producer.credential_rotation_and_revocation",
"maintenance_lock.create",
"branch_protection_compensation_saga.create",
"targeted_online_rollback_executor.create",
"disposable_gitea_fixture.create_test_destroy",
"single_owner_activation_prerequisite_tests.execute",
"activation_prerequisite_evidence.create"
],
"implementation_branch": "patch/single-owner-governance-activation-prerequisites-20260729",
"implementation_paths": [
"services/aios-single-owner-governance/**",
"tests/single_owner_governance/**",
"deploy/systemd/aios-governance-*.service",
"deploy/systemd/aios-governance-*.timer"
],
"issued_at": "2026-07-28T18:30:25Z",
"machine_status_contexts": [
"aios/machine-gates",
"aios/secret-scan",
"aios/evidence-closure"
],
"observed_blockers": {
"commit_status_row_count": 0,
"disposable_governance_fixture_present": false,
"gitea_owner_review_validator_present": false,
"gitea_version": "1.22.6",
"native_branch_protection_etag_cas_present": false,
"required_status_context_producers_present": false,
"targeted_online_rollback_executor_present": false
},
"owner_approval_contract": {
"agent_must_not_submit_review": true,
"dismissed": false,
"head_change_invalidates_approval": true,
"only_human_approver": "owner",
"owner_user_id": 2,
"review_state": "APPROVED",
"reviewed_before_expires_at": true,
"reviewed_head_oid_must_equal_current_head_oid": true,
"stale": false
},
"permanent_constraints": {
"AIOS_GITOPS_WRITES_ENABLED": false,
"direct_main_push": false,
"force_push": false,
"history_rewrite": false,
"secret_commit": false,
"tag_rewrite": false
},
"pre_approval_non_authorizations": {
"activation_allowed": false,
"baseline_write_allowed": false,
"branch_protection_change_allowed": false,
"fixture_deployment_allowed": false,
"integration_write_allowed": false,
"main_write_allowed": false,
"merge_allowed": false,
"production_write_allowed": false,
"registry_write_allowed": false,
"tag_write_allowed": false,
"writer_grant_allowed": false
},
"reason": "safe-atomic-activation-prerequisites-absent",
"repository": "aios/aios",
"request_status": "HUMAN_OWNER_APPROVAL_PENDING",
"schema": "aios.single-owner-governance-activation-prerequisites/v1",
"task_id": "AIOS-SINGLE-OWNER-GOVERNANCE-ACTIVATION-PREREQUISITES-20260729",
"wip": true,
"wip_transition_contract": {
"codex_and_gpt_not_requested_before_owner_approval": true,
"remove_wip_without_head_change_only_after_owner_approval": true,
"then_request_legacy_mechanical_reviews": [
"codex",
"gpt"
],
"wip_must_remain_until_owner_exact_head_approval": true
}
}
@@ -0,0 +1,68 @@
{
"AIOS_GITOPS_WRITES_ENABLED": false,
"activation_prerequisites_artifact_sha256": "729077800c6510bf8930c97a78781218d1bce9ec40157aa99fc2bd205466e895",
"activation_prerequisites_commit_oid": "fa038d17b29001e26f1ac88019a407a4b3ebaa7e",
"activation_prerequisites_rollback_artifact_sha256": "00e40eace10a0aae1731eb98851fec71a0c13c48e1e6e5a113e30a4356c2086f",
"activation_prerequisites_tree_oid": "d5ff67f848807ef2c7a695c05cdd0bb5f2644fc2",
"approval_request_branch": "governance/request/single-owner-governance-atomic-activation-20260728T204808Z-4cab1d5829d6",
"approval_type": "SINGLE_OWNER_GOVERNANCE_ATOMIC_ACTIVATION",
"branch_protection_adapter_sha256": "2706dc6095ff639838f72c91a66c1f80711893c8c0498555b388feaacf2db569",
"denied_operations": [
"main.write",
"integration.write",
"tag.write",
"registry.write",
"baseline.write",
"production.deploy",
"force_push",
"history_rewrite",
"governance.direct_push",
"approval.impersonate"
],
"deployment_mode_before_execution": "DORMANT_SHADOW_ONLY",
"direct_protected_branch_push_allowed": false,
"dismiss_stale_approvals": true,
"dormant_deployment_evidence_sha256": "c4c326784a3d66d62e32c46be3506c801585c06a34e62c9a1f210492a6c1bc06",
"evidence_closure_oid": "5f45139ca5f7f7f3da244141c5115d12f5af58ae",
"evidence_closure_sha256": "53e5f2fdc01a6225048dfdf6eac973e99743f0295f17f3a42f01dce7e7050ab8",
"expected_governance_base_oid": "6e8212608ee310e2fb45f1e802359f4db2e9077b",
"expires_at": "2026-07-29T08:48:08Z",
"explicitly_allowed_operations_after_exact_head_approvals_and_merge": [
"approval_validator.activate",
"governance_state_machine.activate",
"gitea_branch_protection.atomic_update",
"rollback_configuration.install",
"single_owner_policy.activate",
"break_glass.expire",
"post_activation_tests.execute"
],
"force_push_allowed": false,
"issued_at": "2026-07-28T20:48:08Z",
"legacy_mechanical_approvals": [
"codex",
"gpt"
],
"machine_gates_required": true,
"merge_mode": "CREATE_MERGE_COMMIT",
"pr26_merge_oid": "a5489f1b67870622162b2daa781bd0237735831a",
"pr27_merge_oid": "4e6ef70029b1d94626e432892f7d3beb8e3e5627",
"pr28_merge_oid": "2bef44f6bc8cf7bc04c959ab629ac95561fa609f",
"pr29_merge_oid": "6e8212608ee310e2fb45f1e802359f4db2e9077b",
"production_deployment_allowed": false,
"require_latest_head_approval": true,
"required_human_authorizer": "owner",
"required_reviewers": [
"owner",
"codex",
"gpt"
],
"rollback_configuration_sha256": "eb44fc5f655f8c30c4b687185055ba3a2725189e19538e3a9a46a2c0cf141b8c",
"rollback_required_on_any_activation_failure": true,
"schema": "aios.governance-approval/v1",
"single_owner_policy_active_before_execution": false,
"state_machine_sha256": "0b08a2c378d143cb9031a25132afc7b7d825bdc03d11efe10610341d4b70167e",
"task_id": "AIOS-SINGLE-OWNER-GOVERNANCE-ATOMIC-ACTIVATION-20260729",
"test_evidence_sha256": "42168da7437028440bece3432d8aa38a8722b4db18244423d9daaa380675c45a",
"validator_sha256": "754639846cb15cb997d612a1fe0ceb0224ddef5bbc3ccac9802a3747f122dac1",
"wip_blocks_merge": true
}
@@ -0,0 +1,268 @@
{
"acknowledges_pr26_owner_approval_was_missing": true,
"approval_request_branch": "governance/request/single-owner-control-plane-deployment-20260728T170740Z-ea7a102dac417c23",
"approval_type": "SINGLE_OWNER_GOVERNANCE_CONTROL_PLANE_DEPLOYMENT",
"control_plane_targets": {
"allowed_human_approvers": [
"owner"
],
"branch_deletion": false,
"direct_protected_branch_push": false,
"dismiss_stale_approvals": true,
"executor_must_not_approve": true,
"force_push": false,
"governance_model": "SINGLE_OWNER_WITH_MACHINE_GATES_V1",
"historical_records_immutable": true,
"machine_gates_required": true,
"policy_effective_at_set_only_after_activation_tests_pass": true,
"protected_branches": [
"governance/approvals",
"main",
"release/**",
"integration/**"
],
"requester_must_not_approve": true,
"require_approval_for_latest_commit": true,
"required_human_approvals": 1,
"required_owner_approvals": 1,
"required_status_contexts": [
"aios/machine-gates",
"aios/secret-scan",
"aios/evidence-closure"
],
"tag_rewrite": false,
"wip_blocks_merge": true
},
"does_not_claim_pr26_met_old_two_natural_person_policy": true,
"expected_authoritative_governance_base_oid": "ac2dc5c30eb35748ef11c98297812729d0fd5828",
"expected_gitea_governance_base_oid": "a5489f1b67870622162b2daa781bd0237735831a",
"expires_at": "2026-07-29T05:07:40Z",
"explicitly_allowed_operations_after_exact_head_approvals_and_merge": [
"approval_mirror.fast_forward_governance_only",
"approval_validator.update",
"state_machine.update",
"gitea_branch_protection.update_via_authenticated_api_compare_and_swap",
"governance_policy.activate",
"rollback_configuration.install",
"governance_acceptance_tests.execute",
"single_owner_governance_activation_receipt.create"
],
"explicitly_denied_operations": [
"force_push",
"plus_refspec",
"tag_fetch",
"prune",
"direct_git_database_write",
"direct_protected_ref_push",
"main_write",
"integration_write",
"tag_write",
"registry_write",
"baseline_write",
"production_write",
"history_rewrite",
"branch_delete",
"secret_read",
"approval_impersonation"
],
"global_constraints": {
"AIOS_GITOPS_WRITES_ENABLED": false,
"direct_main_push": false,
"force_push_used": false,
"owner_approval_respected": true,
"secrets_committed": false,
"tag_rewritten": false
},
"implementation_surface": {
"approval_validator": [
"services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/approval.py",
"services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/models.py",
"services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/policy.py"
],
"runtime": [
"active-runtime/app/silent_release_controller.py",
"active-runtime/app/silent_release_hardening.py"
],
"state_machine": [
"services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/store.py",
"services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/workflow.py",
"services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/release.py"
],
"systemd": [
"deploy/systemd/aios-autonomous-release-candidate.service",
"deploy/systemd/aios-silent-release-controller.service"
]
},
"issued_at": "2026-07-28T17:07:40Z",
"legacy_gitea_protection_gate": {
"account_reviews_do_not_prove_distinct_natural_persons": true,
"both_must_approve_this_exact_head": true,
"current_approval_whitelist": [
"codex",
"gpt"
],
"current_required_approvals": 2
},
"migration_mode": "OWNER_BREAK_GLASS_GOVERNANCE_RESET_RATIFICATION",
"mirror_contract": {
"expected_chain_contains_policy_merge_oid": "a5489f1b67870622162b2daa781bd0237735831a",
"expected_pre_sync_authoritative_oid": "ac2dc5c30eb35748ef11c98297812729d0fd5828",
"fast_forward_only": true,
"force": false,
"fsck_connectivity_only_required": true,
"only_ref": "refs/heads/governance/approvals",
"plus_refspec": false,
"prune": false,
"quarantine_required": true,
"source": "gitea_repository_file_url_only",
"tags": false
},
"owner_break_glass_ratification": {
"attests_reason": "single-maintainer-system",
"attests_scope": "governance-policy-migration-only",
"created_before_expires_at": true,
"dismissed": false,
"required": true,
"review_state": "APPROVED",
"reviewed_head_oid_must_equal_current_pr_head_oid": true,
"reviewer_user_id": 2,
"reviewer_username": "owner",
"stale": false
},
"pre_approval_non_authorizations": {
"approval_validator_update_allowed": false,
"development_allowed": false,
"gitea_branch_protection_update_allowed": false,
"governance_policy_activation_allowed": false,
"merge_allowed": false,
"mirror_allowed": false,
"production_change_allowed": false,
"rollback_install_allowed": false,
"standing_writer_policy_allowed": false,
"state_machine_update_allowed": false
},
"pre_cutover_protection_snapshot": {
"protected_branch": [
{
"approvals_whitelist_team_i_ds": "null",
"approvals_whitelist_user_i_ds": "[42,43]",
"block_on_official_review_requests": 1,
"block_on_outdated_branch": 1,
"block_on_rejected_reviews": 1,
"branch_name": "governance/approvals",
"can_push": 0,
"created_unix": 1785093201,
"dismiss_stale_approvals": 1,
"enable_approvals_whitelist": 1,
"enable_merge_whitelist": 1,
"enable_status_check": 0,
"enable_whitelist": 0,
"id": 1,
"ignore_stale_approvals": 0,
"merge_whitelist_team_i_ds": "null",
"merge_whitelist_user_i_ds": "[42,43]",
"protected_file_patterns": "",
"repo_id": 1,
"require_signed_commits": 0,
"required_approvals": 2,
"status_check_contexts": "[]",
"unprotected_file_patterns": "",
"updated_unix": 1785093422,
"whitelist_deploy_keys": 0,
"whitelist_team_i_ds": "null",
"whitelist_user_i_ds": "null"
},
{
"approvals_whitelist_team_i_ds": "null",
"approvals_whitelist_user_i_ds": "[42,43]",
"block_on_official_review_requests": 1,
"block_on_outdated_branch": 1,
"block_on_rejected_reviews": 1,
"branch_name": "integration/chatgpt-autonomous-completion-20260726",
"can_push": 0,
"created_unix": 1785093201,
"dismiss_stale_approvals": 1,
"enable_approvals_whitelist": 1,
"enable_merge_whitelist": 1,
"enable_status_check": 0,
"enable_whitelist": 0,
"id": 3,
"ignore_stale_approvals": 0,
"merge_whitelist_team_i_ds": "null",
"merge_whitelist_user_i_ds": "[42,43]",
"protected_file_patterns": "",
"repo_id": 1,
"require_signed_commits": 0,
"required_approvals": 2,
"status_check_contexts": "[]",
"unprotected_file_patterns": "",
"updated_unix": 1785093422,
"whitelist_deploy_keys": 0,
"whitelist_team_i_ds": "null",
"whitelist_user_i_ds": "null"
},
{
"approvals_whitelist_team_i_ds": "null",
"approvals_whitelist_user_i_ds": "[42,43]",
"block_on_official_review_requests": 1,
"block_on_outdated_branch": 1,
"block_on_rejected_reviews": 1,
"branch_name": "main",
"can_push": 0,
"created_unix": 1785093201,
"dismiss_stale_approvals": 1,
"enable_approvals_whitelist": 1,
"enable_merge_whitelist": 1,
"enable_status_check": 0,
"enable_whitelist": 0,
"id": 2,
"ignore_stale_approvals": 0,
"merge_whitelist_team_i_ds": "null",
"merge_whitelist_user_i_ds": "[42,43]",
"protected_file_patterns": "",
"repo_id": 1,
"require_signed_commits": 0,
"required_approvals": 2,
"status_check_contexts": "[]",
"unprotected_file_patterns": "",
"updated_unix": 1785093422,
"whitelist_deploy_keys": 0,
"whitelist_team_i_ds": "null",
"whitelist_user_i_ds": "null"
}
],
"protected_tag": []
},
"pre_cutover_protection_snapshot_sha256": "2bc353f34689242cabebf89856d3faab0e1c496b5d33761dfb7cbd47904516f7",
"ratifies_policy_manifest_blob_oid": "65f44007b10fc2a912d69e3f9081c0cfff77dfad",
"ratifies_policy_manifest_path": "approvals/governance-policy/SINGLE-OWNER-GOVERNANCE-MIGRATION-20260728T162246Z.json",
"ratifies_policy_manifest_sha256": "c097475afbe3074e6482f125b635981f8c0284f3d763c926e2a63e1a3ee1c6c3",
"ratifies_policy_merge_oid": "a5489f1b67870622162b2daa781bd0237735831a",
"ratifies_policy_pr_number": 26,
"ratifies_policy_request_oid": "312d3b483049a748675e887a516ec739c81ff474",
"ratifies_policy_tree_oid": "5c6974c058bba6210dcc1bdd0968fb7e6c84a844",
"repository": "aios/aios",
"request_status": "HUMAN_OWNER_AND_LEGACY_PROTECTION_APPROVAL_PENDING",
"required_machine_gates": [
"owner_exact_head_review_valid",
"legacy_protection_reviews_valid",
"ordinary_merge_commit",
"parent_order_verified",
"manifest_add_only",
"quarantine_verification",
"approval_mirror_fast_forward",
"validator_tests",
"state_machine_tests",
"protection_compare_and_swap",
"positive_governance_tests",
"negative_governance_tests",
"secret_scan",
"rollback_integrity",
"production_write_false"
],
"rollback_configuration_path": "/var/lib/aios-gitops/runtime/tasks/AIOS-SINGLE-OWNER-GOVERNANCE-MIGRATION-20260729/rollback-configuration.tar",
"rollback_configuration_sha256": "fc8dcf68aaf53edb0cda45833ee45ba5b8b205791af9073258ca617c8b8187d6",
"schema": "aios.single-owner-governance-control-plane-deployment/v1",
"scope": "pr26-ratification-mirror-and-single-owner-control-plane-deployment-only",
"task_id": "AIOS-SINGLE-OWNER-CONTROL-PLANE-DEPLOYMENT-20260729"
}
@@ -0,0 +1,260 @@
{
"acknowledges_pr26_and_pr27_owner_approval_was_missing": true,
"approval_request_branch": "governance/request/single-owner-governance-final-owner-ratification-20260728T173634412838Z",
"approval_type": "SINGLE_OWNER_GOVERNANCE_FINAL_OWNER_RATIFICATION",
"backup_checksums_sha256": "72e2fe0b03d1011c872bb39a6cff489f08398ac7d52092f720cbbc63bcf6f753",
"backup_root": "/var/lib/aios-gitops/runtime/tasks/AIOS-SINGLE-OWNER-GOVERNANCE-MIGRATION-20260729/pre-migration-backup",
"break_glass_expires_immediately_after_activation": true,
"does_not_claim_old_two_natural_person_compliance": true,
"expires_at": "2026-07-29T05:36:34Z",
"inherits_only_pr27_control_plane_allowlist": [
"approval_mirror.fast_forward_governance_only",
"approval_validator.update",
"state_machine.update",
"gitea_branch_protection.update_via_authenticated_api_compare_and_swap",
"governance_policy.activate",
"rollback_configuration.install",
"governance_acceptance_tests.execute",
"single_owner_governance_activation_receipt.create"
],
"issued_at": "2026-07-28T17:36:34Z",
"legacy_mechanical_protection_gate": {
"account_reviews_do_not_count_as_owner_ratification": true,
"account_reviews_do_not_prove_distinct_natural_persons": true,
"approval_and_merge_whitelist_user_ids": [
42,
43
],
"both_reviews_must_bind_current_request_head": true,
"required_approvals": 2,
"reviewers": [
"codex",
"gpt"
]
},
"merge_contract": {
"first_parent_must_equal_premerge_governance_oid": true,
"force": false,
"merge_mode": "CREATE_MERGE_COMMIT",
"parent_count": 2,
"rebase": false,
"second_parent_must_equal_final_ratification_request_oid": true,
"single_file_add_only": true,
"squash": false
},
"migration_mode": "OWNER_BREAK_GLASS_GOVERNANCE_RESET",
"mirror_contract": {
"expected_authoritative_pre_oid": "ac2dc5c30eb35748ef11c98297812729d0fd5828",
"fast_forward_only": true,
"force": false,
"fsck_connectivity_only_required": true,
"only_ref": "refs/heads/governance/approvals",
"plus_refspec": false,
"prune": false,
"quarantine_required": true,
"required_chain": [
"ac2dc5c30eb35748ef11c98297812729d0fd5828",
"a5489f1b67870622162b2daa781bd0237735831a",
"4e6ef70029b1d94626e432892f7d3beb8e3e5627",
"FINAL_RATIFICATION_MERGE_OID"
],
"source": "gitea_repository_file_url_only",
"tags": false
},
"owner_ratification_contract": {
"agent_must_not_submit_review": true,
"dismissed": false,
"only_valid_human_approver": "owner",
"owner_user_id": 2,
"review_state": "APPROVED",
"review_type_must_be_approved_not_requested": true,
"reviewed_before_expires_at": true,
"reviewed_before_merge": true,
"reviewed_head_oid_must_equal_current_request_head_oid": true,
"stale": false
},
"permanent_constraints": {
"AIOS_GITOPS_WRITES_ENABLED": false,
"direct_main_push": false,
"force_push_used": false,
"owner_approval_respected": true,
"secrets_committed": false,
"tag_rewritten": false
},
"post_merge_activation_preconditions": [
"owner_exact_head_approval_valid",
"legacy_mechanical_reviews_valid",
"ordinary_merge_commit",
"one_file_add_only",
"quarantine_chain_verified",
"approval_mirror_fast_forward_closed",
"rollback_integrity_pass",
"validator_and_state_machine_tests_pass",
"protection_compare_and_swap_pass",
"positive_and_negative_governance_tests_pass",
"production_write_false"
],
"pre_activation_protection_snapshot": {
"protected_branch": [
{
"approvals_whitelist_team_i_ds": "null",
"approvals_whitelist_user_i_ds": "[42,43]",
"block_on_official_review_requests": 1,
"block_on_outdated_branch": 1,
"block_on_rejected_reviews": 1,
"branch_name": "governance/approvals",
"can_push": 0,
"created_unix": 1785093201,
"dismiss_stale_approvals": 1,
"enable_approvals_whitelist": 1,
"enable_merge_whitelist": 1,
"enable_status_check": 0,
"enable_whitelist": 0,
"id": 1,
"ignore_stale_approvals": 0,
"merge_whitelist_team_i_ds": "null",
"merge_whitelist_user_i_ds": "[42,43]",
"protected_file_patterns": "",
"repo_id": 1,
"require_signed_commits": 0,
"required_approvals": 2,
"status_check_contexts": "[]",
"unprotected_file_patterns": "",
"updated_unix": 1785093422,
"whitelist_deploy_keys": 0,
"whitelist_team_i_ds": "null",
"whitelist_user_i_ds": "null"
},
{
"approvals_whitelist_team_i_ds": "null",
"approvals_whitelist_user_i_ds": "[42,43]",
"block_on_official_review_requests": 1,
"block_on_outdated_branch": 1,
"block_on_rejected_reviews": 1,
"branch_name": "integration/chatgpt-autonomous-completion-20260726",
"can_push": 0,
"created_unix": 1785093201,
"dismiss_stale_approvals": 1,
"enable_approvals_whitelist": 1,
"enable_merge_whitelist": 1,
"enable_status_check": 0,
"enable_whitelist": 0,
"id": 3,
"ignore_stale_approvals": 0,
"merge_whitelist_team_i_ds": "null",
"merge_whitelist_user_i_ds": "[42,43]",
"protected_file_patterns": "",
"repo_id": 1,
"require_signed_commits": 0,
"required_approvals": 2,
"status_check_contexts": "[]",
"unprotected_file_patterns": "",
"updated_unix": 1785093422,
"whitelist_deploy_keys": 0,
"whitelist_team_i_ds": "null",
"whitelist_user_i_ds": "null"
},
{
"approvals_whitelist_team_i_ds": "null",
"approvals_whitelist_user_i_ds": "[42,43]",
"block_on_official_review_requests": 1,
"block_on_outdated_branch": 1,
"block_on_rejected_reviews": 1,
"branch_name": "main",
"can_push": 0,
"created_unix": 1785093201,
"dismiss_stale_approvals": 1,
"enable_approvals_whitelist": 1,
"enable_merge_whitelist": 1,
"enable_status_check": 0,
"enable_whitelist": 0,
"id": 2,
"ignore_stale_approvals": 0,
"merge_whitelist_team_i_ds": "null",
"merge_whitelist_user_i_ds": "[42,43]",
"protected_file_patterns": "",
"repo_id": 1,
"require_signed_commits": 0,
"required_approvals": 2,
"status_check_contexts": "[]",
"unprotected_file_patterns": "",
"updated_unix": 1785093422,
"whitelist_deploy_keys": 0,
"whitelist_team_i_ds": "null",
"whitelist_user_i_ds": "null"
}
],
"protected_tag": []
},
"pre_activation_protection_snapshot_sha256": "2bc353f34689242cabebf89856d3faab0e1c496b5d33761dfb7cbd47904516f7",
"pre_approval_non_authorizations": {
"baseline_write_allowed": false,
"branch_protection_update_allowed": false,
"control_plane_activation_allowed": false,
"development_allowed": false,
"integration_write_allowed": false,
"main_write_allowed": false,
"merge_allowed": false,
"mirror_allowed": false,
"production_write_allowed": false,
"registry_write_allowed": false,
"rollback_install_allowed": false,
"standing_writer_allowed": false,
"state_machine_update_allowed": false,
"tag_write_allowed": false,
"validator_update_allowed": false
},
"ratification_scope": "GOVERNANCE_MIGRATION_AND_CONTROL_PLANE_ACTIVATION_ONLY",
"ratifies_pr26_manifest_blob_oid": "65f44007b10fc2a912d69e3f9081c0cfff77dfad",
"ratifies_pr26_manifest_path": "approvals/governance-policy/SINGLE-OWNER-GOVERNANCE-MIGRATION-20260728T162246Z.json",
"ratifies_pr26_manifest_sha256": "c097475afbe3074e6482f125b635981f8c0284f3d763c926e2a63e1a3ee1c6c3",
"ratifies_pr26_merge_oid": "a5489f1b67870622162b2daa781bd0237735831a",
"ratifies_pr26_request_oid": "312d3b483049a748675e887a516ec739c81ff474",
"ratifies_pr26_tree_oid": "5c6974c058bba6210dcc1bdd0968fb7e6c84a844",
"ratifies_pr27_manifest_blob_oid": "77f3c2ab85cba2c30c8c0aa36e591232d0f4c396",
"ratifies_pr27_manifest_path": "approvals/governance-policy/SINGLE-OWNER-GOVERNANCE-CONTROL-PLANE-DEPLOYMENT-20260728T170740Z-ea7a102dac417c23.json",
"ratifies_pr27_manifest_sha256": "2ed09180c0aeadbef2e5d6310efe7c2d0de15e0e87b6135e7a8494cb4f0967c7",
"ratifies_pr27_merge_oid": "4e6ef70029b1d94626e432892f7d3beb8e3e5627",
"ratifies_pr27_request_oid": "80feef309fd8e1db7795f68d5bf0f15ce805b365",
"ratifies_pr27_tree_oid": "85af0d1e88e8484a9c3a0562a81cbc1de09250ae",
"ratifies_pr_numbers": [
26,
27
],
"repository": "aios/aios",
"request_status": "HUMAN_OWNER_APPROVAL_PENDING",
"retroactive_authorization_for_other_operations": false,
"rollback_archive_member_hashes": {
"pre-migration-backup/app.ini": "0693e4fa8296a22533429339faeee2ac1856b6c966c6c1009bb139ab4784f687",
"pre-migration-backup/authoritative-aios.bundle": "4fb4ccc979c3061065cde451af0e733326cb56fa6f860124bb73f153a60907c9",
"pre-migration-backup/authoritative-refs.txt": "85cb664c8edf3902bebfc6c2253a3ee16cff8534f86561ba771a73df160186c6",
"pre-migration-backup/backup-metadata.json": "a38ef6dacb52ae61c4332bf8d087f479fb0d5f51d5f636a132a711bddd6e7732",
"pre-migration-backup/checksums.sha256": "72e2fe0b03d1011c872bb39a6cff489f08398ac7d52092f720cbbc63bcf6f753",
"pre-migration-backup/gitea-aios.bundle": "a343c7437f3a1a2aa842dd9d986a1d3cf80691058faae978565224320ff5ed90",
"pre-migration-backup/gitea-refs.txt": "1792b2c4f1cf1f232704601ee968e16425f926a6eaab1bdcddf273525d046e4d",
"pre-migration-backup/gitea.db": "aadcc1587a05827ed3b2c4ffc2403f26139020cd0328afaa03f7afbf6083a2f1",
"pre-migration-backup/governance-identities.json": "2d7b4e72d1fdf187425092089589547e0d76784c611d00f915a8c212f17d3b15",
"pre-migration-backup/protected-branch.json": "5cc4ae4fe5614baa3d4bb83d71872a4ad1ce3c5e39061e02b336be40d5b5b75d",
"pre-migration-backup/protected-tag.json": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"pre-migration-backup/repository/active-runtime/app/aios_isolated_writer_policy.py": "d86ec23fd5d8b7e9844c0895c50385b6f8f3071c3dac3c0f26c70768ae00a188",
"pre-migration-backup/repository/active-runtime/app/isolated_writer_authz.py": "cfae1cdf70c1dabbe013e9365c308a45f11acf20035881a3bb6143774682bbf0",
"pre-migration-backup/repository/active-runtime/app/isolated_writer_path_policy.py": "c6c9ea8e45dfc03c4703503f4dc9c92f887bbacdbf1c7779a487567eee6c257c",
"pre-migration-backup/repository/active-runtime/app/silent_release_controller.py": "524a0a9961b15c2a35b669d1b89b2142377c13ddd3c721780f374331b7d2b6eb",
"pre-migration-backup/repository/active-runtime/app/silent_release_hardening.py": "524aba7034f2cd89d897fe01e9799c835bae9f11ca738182ce469aa59e05cc1a",
"pre-migration-backup/repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/approval.py": "cc937a81aef881206cb1d6fd2ad1feb1a1c2e0ad91c908095d56fd96402c3373",
"pre-migration-backup/repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/models.py": "aa3b3dd41c03c17e4e116ce444d9f4f779694a9cde684e8329cf892bd319069a",
"pre-migration-backup/repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/policy.py": "24b3f3326e6dcd1a4c376d892c53ad20c68e0dd6f861a3a34e11c9a2bba4c756",
"pre-migration-backup/repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/release.py": "db535e5006f8e4a3ab80c2f689213045c120b01cda9b43ef461a3022096c9d19",
"pre-migration-backup/repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/store.py": "31e1f3b904b0bfc8822d21bfd166a45388f122398d1ec76b803e90ee06dc92f0",
"pre-migration-backup/repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/workflow.py": "5f6a8e3a4d66f47717e95842f2eaa443e3cfa321cb08026cc82d74c4ad353bcf",
"pre-migration-backup/repository/tests/autonomous_release/test_core_contracts.py": "71852070db50a04901c141213cadc987ae17ec678fe27a901fee165b6d1c3e2a",
"pre-migration-backup/repository/tests/autonomous_release/test_external_gates.py": "91cca37eb1d40f0cf9fc38b12e2123045ffebd794a34d6cdef3f1dec470fffd2",
"pre-migration-backup/repository/tests/autonomous_release/test_release_gates.py": "d82b23ac00f5389f1a0de628bda9dff898d33f80d375cf6ff74f1531fd138134",
"pre-migration-backup/repository/tests/autonomous_release/test_state_and_workflow.py": "c0395a901732adb0b2f1402e6a1cf5202d54aa597bcaec9584a7ea7589c53917"
},
"rollback_configuration_path": "/var/lib/aios-gitops/runtime/tasks/AIOS-SINGLE-OWNER-GOVERNANCE-MIGRATION-20260729/rollback-configuration.tar",
"rollback_configuration_sha256": "fc8dcf68aaf53edb0cda45833ee45ba5b8b205791af9073258ca617c8b8187d6",
"schema": "aios.single-owner-governance-final-owner-ratification/v1",
"single_owner_policy_effective_at": "FINAL_RATIFICATION_MERGE_OID",
"task_id": "AIOS-SINGLE-OWNER-GOVERNANCE-FINAL-RATIFICATION-20260729"
}
@@ -0,0 +1,484 @@
{
"allowed_human_approvers": [
"owner"
],
"approval_expires_at": "2026-07-29T16:22:46Z",
"approval_request_branch": "governance/request/single-owner-governance-migration-20260728T162246Z",
"approval_type": "SINGLE_OWNER_GOVERNANCE_POLICY",
"approval_types_migrated_after_policy_effective_at": [
"SOURCE_MAPPING_AND_IMPORT_EXECUTION_RENEWAL",
"CONTROL_PLANE_DEPLOYMENT_APPROVAL",
"DEVELOPMENT_MERGE_APPROVAL",
"RELEASE_CANDIDATE_APPROVAL",
"PRODUCTION_RELEASE_APPROVAL",
"TAG_APPROVAL",
"REGISTRY_BASELINE_PROMOTION_APPROVAL",
"PRODUCTION_DEPLOYMENT_APPROVAL",
"ROLLBACK_APPROVAL"
],
"atomic_migration": {
"backup_checksums_sha256": "72e2fe0b03d1011c872bb39a6cff489f08398ac7d52092f720cbbc63bcf6f753",
"backup_root": "/var/lib/aios-gitops/runtime/tasks/AIOS-SINGLE-OWNER-GOVERNANCE-MIGRATION-20260729/pre-migration-backup",
"direct_database_update": false,
"direct_ref_update": false,
"dual_capable_validator_shadow_required": true,
"identity_snapshot_sha256": "2d7b4e72d1fdf187425092089589547e0d76784c611d00f915a8c212f17d3b15",
"live_protection_canonical_sha256": "2bc353f34689242cabebf89856d3faab0e1c496b5d33761dfb7cbd47904516f7",
"maintenance_lock_required": true,
"old_rules_remain_active_until_owner_approval_and_machine_gates_pass": true,
"protected_branch_snapshot_sha256": "2bc353f34689242cabebf89856d3faab0e1c496b5d33761dfb7cbd47904516f7",
"protection_update_method": "authenticated_admin_API_compare_and_swap",
"reviewable_backup_file_hashes": {
"app.ini": "0693e4fa8296a22533429339faeee2ac1856b6c966c6c1009bb139ab4784f687",
"authoritative-aios.bundle": "4fb4ccc979c3061065cde451af0e733326cb56fa6f860124bb73f153a60907c9",
"authoritative-refs.txt": "85cb664c8edf3902bebfc6c2253a3ee16cff8534f86561ba771a73df160186c6",
"backup-metadata.json": "a38ef6dacb52ae61c4332bf8d087f479fb0d5f51d5f636a132a711bddd6e7732",
"checksums.sha256": "72e2fe0b03d1011c872bb39a6cff489f08398ac7d52092f720cbbc63bcf6f753",
"gitea-aios.bundle": "a343c7437f3a1a2aa842dd9d986a1d3cf80691058faae978565224320ff5ed90",
"gitea-refs.txt": "1792b2c4f1cf1f232704601ee968e16425f926a6eaab1bdcddf273525d046e4d",
"gitea.db": "aadcc1587a05827ed3b2c4ffc2403f26139020cd0328afaa03f7afbf6083a2f1",
"governance-identities.json": "2d7b4e72d1fdf187425092089589547e0d76784c611d00f915a8c212f17d3b15",
"protected-branch.json": "5cc4ae4fe5614baa3d4bb83d71872a4ad1ce3c5e39061e02b336be40d5b5b75d",
"protected-tag.json": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"repository/active-runtime/app/aios_isolated_writer_policy.py": "d86ec23fd5d8b7e9844c0895c50385b6f8f3071c3dac3c0f26c70768ae00a188",
"repository/active-runtime/app/isolated_writer_authz.py": "cfae1cdf70c1dabbe013e9365c308a45f11acf20035881a3bb6143774682bbf0",
"repository/active-runtime/app/isolated_writer_path_policy.py": "c6c9ea8e45dfc03c4703503f4dc9c92f887bbacdbf1c7779a487567eee6c257c",
"repository/active-runtime/app/silent_release_controller.py": "524a0a9961b15c2a35b669d1b89b2142377c13ddd3c721780f374331b7d2b6eb",
"repository/active-runtime/app/silent_release_hardening.py": "524aba7034f2cd89d897fe01e9799c835bae9f11ca738182ce469aa59e05cc1a",
"repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/approval.py": "cc937a81aef881206cb1d6fd2ad1feb1a1c2e0ad91c908095d56fd96402c3373",
"repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/models.py": "aa3b3dd41c03c17e4e116ce444d9f4f779694a9cde684e8329cf892bd319069a",
"repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/policy.py": "24b3f3326e6dcd1a4c376d892c53ad20c68e0dd6f861a3a34e11c9a2bba4c756",
"repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/release.py": "db535e5006f8e4a3ab80c2f689213045c120b01cda9b43ef461a3022096c9d19",
"repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/store.py": "31e1f3b904b0bfc8822d21bfd166a45388f122398d1ec76b803e90ee06dc92f0",
"repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/workflow.py": "5f6a8e3a4d66f47717e95842f2eaa443e3cfa321cb08026cc82d74c4ad353bcf",
"repository/tests/autonomous_release/test_core_contracts.py": "71852070db50a04901c141213cadc987ae17ec678fe27a901fee165b6d1c3e2a",
"repository/tests/autonomous_release/test_external_gates.py": "91cca37eb1d40f0cf9fc38b12e2123045ffebd794a34d6cdef3f1dec470fffd2",
"repository/tests/autonomous_release/test_release_gates.py": "d82b23ac00f5389f1a0de628bda9dff898d33f80d375cf6ff74f1531fd138134",
"repository/tests/autonomous_release/test_state_and_workflow.py": "c0395a901732adb0b2f1402e6a1cf5202d54aa597bcaec9584a7ea7589c53917"
},
"reviewable_pre_migration_governance_identity_snapshot": [
{
"created_unix": 1783321252,
"id": 1,
"is_active": 1,
"is_admin": 0,
"last_login_unix": 0,
"lower_name": "aios",
"prohibit_login": 1,
"type": 0
},
{
"created_unix": 1783335380,
"id": 2,
"is_active": 1,
"is_admin": 1,
"last_login_unix": 1785086126,
"lower_name": "owner",
"prohibit_login": 0,
"type": 0
},
{
"created_unix": 1783411566,
"id": 3,
"is_active": 1,
"is_admin": 1,
"last_login_unix": 0,
"lower_name": "gito",
"prohibit_login": 0,
"type": 0
},
{
"created_unix": 1785078826,
"id": 42,
"is_active": 1,
"is_admin": 0,
"last_login_unix": 1785206253,
"lower_name": "codex",
"prohibit_login": 0,
"type": 0
},
{
"created_unix": 1785078882,
"id": 43,
"is_active": 1,
"is_admin": 0,
"last_login_unix": 1785206380,
"lower_name": "gpt",
"prohibit_login": 0,
"type": 0
}
],
"reviewable_pre_migration_protection_snapshot": {
"protected_branch": [
{
"approvals_whitelist_team_i_ds": "null",
"approvals_whitelist_user_i_ds": "[42,43]",
"block_on_official_review_requests": 1,
"block_on_outdated_branch": 1,
"block_on_rejected_reviews": 1,
"branch_name": "governance/approvals",
"can_push": 0,
"created_unix": 1785093201,
"dismiss_stale_approvals": 1,
"enable_approvals_whitelist": 1,
"enable_merge_whitelist": 1,
"enable_status_check": 0,
"enable_whitelist": 0,
"id": 1,
"ignore_stale_approvals": 0,
"merge_whitelist_team_i_ds": "null",
"merge_whitelist_user_i_ds": "[42,43]",
"protected_file_patterns": "",
"repo_id": 1,
"require_signed_commits": 0,
"required_approvals": 2,
"status_check_contexts": "[]",
"unprotected_file_patterns": "",
"updated_unix": 1785093422,
"whitelist_deploy_keys": 0,
"whitelist_team_i_ds": "null",
"whitelist_user_i_ds": "null"
},
{
"approvals_whitelist_team_i_ds": "null",
"approvals_whitelist_user_i_ds": "[42,43]",
"block_on_official_review_requests": 1,
"block_on_outdated_branch": 1,
"block_on_rejected_reviews": 1,
"branch_name": "integration/chatgpt-autonomous-completion-20260726",
"can_push": 0,
"created_unix": 1785093201,
"dismiss_stale_approvals": 1,
"enable_approvals_whitelist": 1,
"enable_merge_whitelist": 1,
"enable_status_check": 0,
"enable_whitelist": 0,
"id": 3,
"ignore_stale_approvals": 0,
"merge_whitelist_team_i_ds": "null",
"merge_whitelist_user_i_ds": "[42,43]",
"protected_file_patterns": "",
"repo_id": 1,
"require_signed_commits": 0,
"required_approvals": 2,
"status_check_contexts": "[]",
"unprotected_file_patterns": "",
"updated_unix": 1785093422,
"whitelist_deploy_keys": 0,
"whitelist_team_i_ds": "null",
"whitelist_user_i_ds": "null"
},
{
"approvals_whitelist_team_i_ds": "null",
"approvals_whitelist_user_i_ds": "[42,43]",
"block_on_official_review_requests": 1,
"block_on_outdated_branch": 1,
"block_on_rejected_reviews": 1,
"branch_name": "main",
"can_push": 0,
"created_unix": 1785093201,
"dismiss_stale_approvals": 1,
"enable_approvals_whitelist": 1,
"enable_merge_whitelist": 1,
"enable_status_check": 0,
"enable_whitelist": 0,
"id": 2,
"ignore_stale_approvals": 0,
"merge_whitelist_team_i_ds": "null",
"merge_whitelist_user_i_ds": "[42,43]",
"protected_file_patterns": "",
"repo_id": 1,
"require_signed_commits": 0,
"required_approvals": 2,
"status_check_contexts": "[]",
"unprotected_file_patterns": "",
"updated_unix": 1785093422,
"whitelist_deploy_keys": 0,
"whitelist_team_i_ds": "null",
"whitelist_user_i_ds": "null"
}
],
"protected_tag": []
},
"rollback_archive_members": {
"pre-migration-backup/app.ini": "0693e4fa8296a22533429339faeee2ac1856b6c966c6c1009bb139ab4784f687",
"pre-migration-backup/authoritative-aios.bundle": "4fb4ccc979c3061065cde451af0e733326cb56fa6f860124bb73f153a60907c9",
"pre-migration-backup/authoritative-refs.txt": "85cb664c8edf3902bebfc6c2253a3ee16cff8534f86561ba771a73df160186c6",
"pre-migration-backup/backup-metadata.json": "a38ef6dacb52ae61c4332bf8d087f479fb0d5f51d5f636a132a711bddd6e7732",
"pre-migration-backup/checksums.sha256": "72e2fe0b03d1011c872bb39a6cff489f08398ac7d52092f720cbbc63bcf6f753",
"pre-migration-backup/gitea-aios.bundle": "a343c7437f3a1a2aa842dd9d986a1d3cf80691058faae978565224320ff5ed90",
"pre-migration-backup/gitea-refs.txt": "1792b2c4f1cf1f232704601ee968e16425f926a6eaab1bdcddf273525d046e4d",
"pre-migration-backup/gitea.db": "aadcc1587a05827ed3b2c4ffc2403f26139020cd0328afaa03f7afbf6083a2f1",
"pre-migration-backup/governance-identities.json": "2d7b4e72d1fdf187425092089589547e0d76784c611d00f915a8c212f17d3b15",
"pre-migration-backup/protected-branch.json": "5cc4ae4fe5614baa3d4bb83d71872a4ad1ce3c5e39061e02b336be40d5b5b75d",
"pre-migration-backup/protected-tag.json": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"pre-migration-backup/repository/active-runtime/app/aios_isolated_writer_policy.py": "d86ec23fd5d8b7e9844c0895c50385b6f8f3071c3dac3c0f26c70768ae00a188",
"pre-migration-backup/repository/active-runtime/app/isolated_writer_authz.py": "cfae1cdf70c1dabbe013e9365c308a45f11acf20035881a3bb6143774682bbf0",
"pre-migration-backup/repository/active-runtime/app/isolated_writer_path_policy.py": "c6c9ea8e45dfc03c4703503f4dc9c92f887bbacdbf1c7779a487567eee6c257c",
"pre-migration-backup/repository/active-runtime/app/silent_release_controller.py": "524a0a9961b15c2a35b669d1b89b2142377c13ddd3c721780f374331b7d2b6eb",
"pre-migration-backup/repository/active-runtime/app/silent_release_hardening.py": "524aba7034f2cd89d897fe01e9799c835bae9f11ca738182ce469aa59e05cc1a",
"pre-migration-backup/repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/approval.py": "cc937a81aef881206cb1d6fd2ad1feb1a1c2e0ad91c908095d56fd96402c3373",
"pre-migration-backup/repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/models.py": "aa3b3dd41c03c17e4e116ce444d9f4f779694a9cde684e8329cf892bd319069a",
"pre-migration-backup/repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/policy.py": "24b3f3326e6dcd1a4c376d892c53ad20c68e0dd6f861a3a34e11c9a2bba4c756",
"pre-migration-backup/repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/release.py": "db535e5006f8e4a3ab80c2f689213045c120b01cda9b43ef461a3022096c9d19",
"pre-migration-backup/repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/store.py": "31e1f3b904b0bfc8822d21bfd166a45388f122398d1ec76b803e90ee06dc92f0",
"pre-migration-backup/repository/services/aios-autonomous-release-candidate/aios_autonomous_release_candidate/workflow.py": "5f6a8e3a4d66f47717e95842f2eaa443e3cfa321cb08026cc82d74c4ad353bcf",
"pre-migration-backup/repository/tests/autonomous_release/test_core_contracts.py": "71852070db50a04901c141213cadc987ae17ec678fe27a901fee165b6d1c3e2a",
"pre-migration-backup/repository/tests/autonomous_release/test_external_gates.py": "91cca37eb1d40f0cf9fc38b12e2123045ffebd794a34d6cdef3f1dec470fffd2",
"pre-migration-backup/repository/tests/autonomous_release/test_release_gates.py": "d82b23ac00f5389f1a0de628bda9dff898d33f80d375cf6ff74f1531fd138134",
"pre-migration-backup/repository/tests/autonomous_release/test_state_and_workflow.py": "c0395a901732adb0b2f1402e6a1cf5202d54aa597bcaec9584a7ea7589c53917"
},
"rollback_configuration_path": "/var/lib/aios-gitops/runtime/tasks/AIOS-SINGLE-OWNER-GOVERNANCE-MIGRATION-20260729/rollback-configuration.tar",
"rollback_configuration_sha256": "fc8dcf68aaf53edb0cda45833ee45ba5b8b205791af9073258ca617c8b8187d6",
"rollback_restores_protection_before_validator": true
},
"auto_merge_after_owner_approval": false,
"auto_merge_machine_conditions": [
"owner_approval_valid",
"head_oid_unchanged",
"required_checks_pass",
"secret_scan_pass",
"evidence_closure_pass",
"approval_not_expired",
"protected_branch_rules_pass"
],
"automatic_renewal_policy": {
"approval_ref_fast_forward_descendant_required": true,
"changed_condition_fallback": "HUMAN_OWNER_APPROVAL_PENDING",
"secret_scan": "PASS_REQUIRED",
"single_renewal_max_hours": 12,
"status": "PROPOSED_AFTER_MIGRATION",
"total_window_max_days": 7,
"unchanged_fields_required": [
"original_policy_sha256",
"allowed_paths",
"allowed_operations",
"denied_operations",
"source_sha256",
"source_blob_oid"
]
},
"break_glass_executor_may_merge_only_after_owner_approval_machine_gates_and_atomic_cutover_preconditions": true,
"break_glass_expires_at": "2026-07-29T16:22:46Z",
"desired_branch_protection": {
"approval_whitelist_user_ids": [
2
],
"approval_whitelist_usernames": [
"owner"
],
"block_on_official_review_requests": true,
"block_on_outdated_branch": true,
"block_on_rejected_reviews": true,
"branch_deletion": false,
"branch_patterns": [
"governance/approvals",
"main",
"release/**",
"integration/**"
],
"direct_push": false,
"dismiss_stale_approvals": true,
"force_push": false,
"require_status_checks": true,
"required_approvals": 1,
"required_status_contexts": [
"aios/machine-gates",
"aios/secret-scan",
"aios/evidence-closure"
],
"wip_blocks_merge": true
},
"executor_must_not_approve": true,
"expected_governance_base_oid": "ac2dc5c30eb35748ef11c98297812729d0fd5828",
"expires_at": "2026-07-29T16:22:46Z",
"global_constraints": {
"AIOS_GITOPS_WRITES_ENABLED": false,
"approval_impersonation": false,
"direct_main_push": false,
"fake_review": false,
"force_push": false,
"production_change_without_owner_approval": false,
"secret_commit": false,
"tag_rewrite": false
},
"governance_model": "SINGLE_OWNER_WITH_MACHINE_GATES_V1",
"historical_policy": {
"historical_manifests_modified": false,
"old_policy_decoder_retained_read_only": true,
"requests_created_before_policy_effective_at_use_old_policy": true
},
"issued_at": "2026-07-28T16:22:46Z",
"machine_gates": {
"development_branch": [
"allowed_branch",
"allowed_paths",
"compileall",
"targeted_tests",
"secret_scan",
"git_diff_check"
],
"production": [
"owner_approval",
"tag_consistency",
"registry_consistency",
"deployment_plan",
"rollback_plan",
"artifact_integrity",
"production_preflight"
],
"protected_branch_merge": [
"expected_base_oid",
"current_head_oid",
"ordinary_merge_commit",
"parent_order_verified",
"artifact_binding",
"audit_chain_valid"
],
"release": [
"version_manifest",
"explicit_release_target_oid",
"artifact_sha256",
"rollback_artifact_sha256",
"sbom",
"test_evidence",
"evidence_closure"
]
},
"merge_mode": "CREATE_MERGE_COMMIT",
"migration_acceptance_tests": [
"agent_request_pass",
"owner_current_head_approval_pass",
"machine_gates_then_merge_commit_pass",
"agent_self_approval_denied",
"non_owner_approval_denied",
"stale_head_approval_denied",
"wip_merge_denied",
"machine_test_failure_merge_denied",
"secret_scan_failure_merge_denied",
"main_direct_write_denied",
"tag_direct_write_denied",
"force_push_denied",
"missing_tag_target_oid_denied",
"missing_evidence_closure_oid_denied",
"production_without_owner_denied",
"dormant_only_production_activation_denied"
],
"migration_mode": "OWNER_BREAK_GLASS_GOVERNANCE_RESET",
"new_policy": "SINGLE_OWNER_WITH_MACHINE_GATES_V1",
"old_policy": "DUAL_HUMAN_APPROVAL",
"owner_approval_contract": {
"dismissed": false,
"does_not_claim_old_dual_approval_compliance": true,
"head_change_invalidates_owner_approval": true,
"owner_review_is_break_glass_signature": true,
"review_created_before_approval_expires_at": true,
"review_state": "APPROVED",
"reviewed_head_oid_must_equal_current_pr_head_oid": true,
"reviewer_must_equal": "owner",
"stale": false
},
"owner_break_glass_declaration_to_be_attested_by_review": {
"attestation_status": "PENDING_OWNER_REVIEW",
"expires_at": "2026-07-29T16:22:46Z",
"new_policy": "SINGLE_OWNER_WITH_MACHINE_GATES_V1",
"old_policy": "DUAL_HUMAN_APPROVAL",
"owner_acknowledges_old_policy_exception": true,
"reason": "single-maintainer-system",
"scope": "governance-policy-migration-only"
},
"owner_is_only_human_approver": true,
"policy_effective_at": null,
"pre_approval_non_authorizations": {
"approval_validator_update_allowed": false,
"gitea_branch_protection_update_allowed": false,
"merge_allowed": false,
"old_dual_rule_disable_allowed": false,
"production_change_allowed": false,
"state_machine_update_allowed": false,
"writer_credential_issue_allowed": false
},
"prohibited_merge_modes": [
"squash",
"rebase",
"force",
"fast_forward_policy_bypass"
],
"reason": "single-maintainer-system",
"repository": "aios/aios",
"request_status": "HUMAN_OWNER_APPROVAL_PENDING",
"requester_must_not_approve": true,
"requesters": [
"codex-chartgpt-bridge",
"aios-governance-agent",
"aios-release-agent",
"aios-governed-development-writer"
],
"required_distinct_human_approvers": 1,
"required_human_approvals": 1,
"required_owner_approvals": 1,
"resolved_owner": {
"confidence_before_review": "MEDIUM",
"confirmation_rule": "valid APPROVED review by owner on this exact immutable head",
"email_sha256": "f5dc2928f6202c24fa4db8bb33db84e1b77c6eff30d1b95abdadeaae209bc4c9",
"identity_source": [
"gitea_repository_owner_account_aios_is_login_prohibited",
"all_login_enabled_site_admins_enumerated_owner_and_never_logged_in_service_gito",
"only_interactive_site_admin_named_owner",
"gitea_owner_last_login_present",
"historical_governance_records_use_approved_by_role_owner",
"user_declared_single_natural_person_maintainer"
],
"login_enabled_site_admins": [
{
"observed_prior_login": true,
"user_id": 2,
"username": "owner"
},
{
"classification": "service_admin_not_human_approver",
"observed_prior_login": false,
"user_id": 3,
"username": "gito"
}
],
"user_id": 2,
"username": "owner"
},
"schema": "aios.single-owner-governance-policy/v1",
"scope": "governance-policy-migration-only",
"standing_autonomous_development_policy": {
"allowed_without_repeated_owner_approval": [
"patch_branch_create",
"allowlisted_development_write",
"patch_branch_commit_and_push",
"test_execute",
"artifact_build",
"approval_pr_create",
"read_only_evidence",
"task_writer_renewal"
],
"owner_approval_still_required": [
"protected_branch_merge",
"tag",
"registry_update",
"production_baseline_update",
"production_deploy",
"production_rollback",
"governance_policy_change"
],
"status": "PROPOSED_AFTER_MIGRATION"
},
"standing_single_owner_governance_migration_authorization": true,
"state_machine": {
"auto_merge_ready_state": "AUTO_MERGE_READY",
"execution_ready_state": "OWNER_APPROVED_EXECUTION_READY",
"machine_verified_state": "MACHINE_GATES_VERIFIED",
"owner_approval_alone_is_not_execution_ready": true,
"owner_verified_state": "OWNER_APPROVAL_VERIFIED",
"pending_state": "HUMAN_OWNER_APPROVAL_PENDING"
},
"task_id": "AIOS-SINGLE-OWNER-GOVERNANCE-MIGRATION-20260729",
"this_manifest_authorizes_only_governance_policy_migration": true,
"this_manifest_grants_standing_development_authorization": false
}
@@ -0,0 +1,49 @@
{
"schema": "aios.gitea-native-approval/v1",
"approval_type": "IMPLEMENTATION",
"task_id": "AIOS-INTEGRATION-REFRESH-PROVISIONER-IMPLEMENTATION-20260726",
"repository": "aios/aios",
"branch": "patch/integration-refresh-grant-provisioner-20260726",
"base_oid": "875d41c3101093149414292067fd700dc214d4d3",
"actor_identity": "chatgpt-gitops-bot",
"writer_audience": "aios-integration-refresh-provisioner-development-writer",
"risk": "high",
"allowed_paths": [
"active-runtime/app/**",
"tests/test_integration_refresh_provisioner.py",
"tests/test_isolated_writer_integration_merge.py",
"tests/test_gitea_branch_read_projection.py",
"tests/test_isolated_writer_token_policy_v2.py",
"deploy/systemd/aios-integration-refresh-*.service",
"docs/autonomous-release/**"
],
"allowed_operations": [
"repository.source_map.read",
"runtime.unit_metadata.read",
"git.ref.read",
"git.diff.read",
"git.branch.patch.create",
"commit_file.atomic",
"git.push.patch_branch",
"test.execute.allowlisted",
"artifact.build.control_plane_patch",
"evidence.write.task_runtime"
],
"denied_operations": [
"main.write",
"main.merge",
"tag.write",
"tag.delete",
"tag.rewrite",
"force_push",
"history.rewrite",
"baseline.write",
"registry.write",
"production.deploy",
"production.rollback",
"secret.read",
"secret.export",
"bare_ref_direct_write"
],
"expires_at": "2026-07-27T03:35:55Z"
}
@@ -0,0 +1,49 @@
{
"schema": "aios.gitea-native-approval/v1",
"approval_type": "IMPLEMENTATION",
"task_id": "AIOS-INTEGRATION-REFRESH-PROVISIONER-IMPLEMENTATION-20260726",
"repository": "aios/aios",
"branch": "patch/integration-refresh-grant-provisioner-20260726",
"base_oid": "875d41c3101093149414292067fd700dc214d4d3",
"actor_identity": "chatgpt-gitops-bot",
"writer_audience": "aios-integration-refresh-provisioner-development-writer",
"risk": "high",
"allowed_paths": [
"active-runtime/app/**",
"tests/test_integration_refresh_provisioner.py",
"tests/test_isolated_writer_integration_merge.py",
"tests/test_gitea_branch_read_projection.py",
"tests/test_isolated_writer_token_policy_v2.py",
"deploy/systemd/aios-integration-refresh-*.service",
"docs/autonomous-release/**"
],
"allowed_operations": [
"repository.source_map.read",
"runtime.unit_metadata.read",
"git.ref.read",
"git.diff.read",
"git.branch.patch.create",
"commit_file.atomic",
"git.push.patch_branch",
"test.execute.allowlisted",
"artifact.build.control_plane_patch",
"evidence.write.task_runtime"
],
"denied_operations": [
"main.write",
"main.merge",
"tag.write",
"tag.delete",
"tag.rewrite",
"force_push",
"history.rewrite",
"baseline.write",
"registry.write",
"production.deploy",
"production.rollback",
"secret.read",
"secret.export",
"bare_ref_direct_write"
],
"expires_at": "2026-07-27T13:00:11Z"
}
@@ -0,0 +1,73 @@
{
"approval_type": "SINGLE_OWNER_GOVERNANCE_LIVE_ACTIVATION_IMPLEMENTATION_AND_EXECUTION",
"artifact_sha256": "01e0226f0302bb8e2f8d81299393075bf0332c6e22e00877469fb297c51f3802",
"authorization_scope": [
"deploy_live_activation_implementation",
"execute_one_atomic_governance_activation",
"execute_acceptance_tests",
"control_plane_rollback_on_failure",
"write_activation_receipt"
],
"denied_operations": [
"main.write",
"integration.write",
"tag.write",
"registry.write",
"baseline.write",
"production.deploy",
"force_push",
"history_rewrite"
],
"evidence_closure_oid": "c6316943d2485f06f97242df6338ded21de1ee38",
"evidence_closure_sha256": "bd521103c907d6194acbbbd42a947c5af2ccbda91797e5dc75e81887a042becd",
"execution_binding": {
"backend": "sqlite",
"branch": "governance/approvals",
"evidence_closure_oid": "c6316943d2485f06f97242df6338ded21de1ee38",
"executor": "aios-governance-single-owner-activation",
"expected_branch_protection_sha256": "f27992c8024426708d93bce072d231707bae47a927aa2393d566aae64d5ed079",
"expected_gitea_schema_sha256": "3524f4912165939cb746c2c65d70583a909f5abbd00c4e07ae76f7206d49aa54",
"expected_state_machine_sha256": "0b08a2c378d143cb9031a25132afc7b7d825bdc03d11efe10610341d4b70167e",
"expected_validator_sha256": "754639846cb15cb997d612a1fe0ceb0224ddef5bbc3ccac9802a3747f122dac1",
"forbidden_operations": {
"baseline": false,
"force_push": false,
"governance_direct": false,
"history_rewrite": false,
"integration": false,
"main": false,
"production": false,
"registry": false,
"tags": false
},
"operation_id": "single-owner-live-activation-20260729T033228Z",
"repo_id": 1,
"requester": "codex-agent",
"target_branch_protection_sha256": "61b424b0c495eed71c73171c5c4434ac66d074de36886a73ec4c445fd1905db8",
"task_id": "AIOS-SINGLE-OWNER-GOVERNANCE-ACTIVATION-20260729"
},
"execution_binding_sha256": "6b2ed671a94fa83704383728fd2bb2df3b67523b4273100fa371777d984be102",
"expected_governance_base_oid": "8b7f9d9028f6ff9beb225bf85f53dbeaac6830e7",
"expires_at": "2026-07-29T15:32:28Z",
"human_authorizer": "owner",
"implementation_commit_oid": "e1e438564ce8b987a36df0947e6eaa324dfede74",
"implementation_tree_oid": "dd93dc1df10cb8b8fe59b09d3214a9ae6fee671e",
"issued_at": "2026-07-29T03:32:28Z",
"legacy_mechanical_approvals": [
"codex",
"gpt"
],
"machine_gate_contexts": [
"aios/branch-protection",
"aios/evidence-closure",
"aios/secret-scan",
"aios/state-machine",
"aios/validator"
],
"pr30_merge_oid": "f6bef90633f428454e85721f353cd82cdf04caf0",
"pr31_merge_oid": "8b7f9d9028f6ff9beb225bf85f53dbeaac6830e7",
"rollback_artifact_sha256": "ed9307cc2f07767b75866fcb370fa3f98b76c5147e4b7fc49ecf04cf21cf7c31",
"signed_merge_requirement_effective": false,
"task_id": "AIOS-SINGLE-OWNER-GOVERNANCE-ACTIVATION-20260729",
"test_evidence_sha256": "172a7cf812c6b901524138fb72851a9b7d2e895dedc98893407d0bab9b52c6c1"
}
@@ -0,0 +1,171 @@
{
"approval_type": "SINGLE_OWNER_GOVERNANCE_LIVE_ACTIVATION_IMPLEMENTATION_AND_EXECUTION",
"artifact_path": "/var/lib/aios-gitops/runtime/tasks/AIOS-SINGLE-OWNER-GOVERNANCE-ACTIVATION-20260729/artifacts/hardening-71def340e7cc34f4f19918dcf8d3c847abbe4fbf/live-activation-71def340e7cc34f4f19918dcf8d3c847abbe4fbf.tar.gz",
"artifact_sha256": "32bc49c207ec454b6fd81b0484f5b257bb789481b37175995029a2ec78cc62fa",
"authorization_scope": [
"deploy_exact_approved_artifact",
"publish_exact_head_machine_statuses",
"execute_one_atomic_governance_activation",
"verify_and_rollback_exact_transaction"
],
"denied_operations": [
"main.write",
"integration.write",
"tag.write",
"registry.write",
"baseline.write",
"production.deploy",
"force_push",
"history_rewrite"
],
"evidence_closure_blob_oid": "6af6d017f22c4e0c512b0cd62644cdc6ab2135d5",
"evidence_closure_oid": "310ab2af902a9fde9108d227077f433cc743ce7c",
"evidence_closure_path": "evidence/single-owner-governance/AIOS-SINGLE-OWNER-LIVE-ACTIVATION-HARDENING-20260729T043010Z.json",
"evidence_closure_sha256": "e482fe52c8381f765ee508d2de1fe2e77dff52e6e342f01be4ca481ff2f76820",
"execution_binding": {
"artifact_path": "/var/lib/aios-gitops/runtime/tasks/AIOS-SINGLE-OWNER-GOVERNANCE-ACTIVATION-20260729/artifacts/hardening-71def340e7cc34f4f19918dcf8d3c847abbe4fbf/live-activation-71def340e7cc34f4f19918dcf8d3c847abbe4fbf.tar.gz",
"artifact_sha256": "32bc49c207ec454b6fd81b0484f5b257bb789481b37175995029a2ec78cc62fa",
"backend": "sqlite",
"branch": "governance/approvals",
"evidence_closure_oid": "310ab2af902a9fde9108d227077f433cc743ce7c",
"evidence_closure_path": "evidence/single-owner-governance/AIOS-SINGLE-OWNER-LIVE-ACTIVATION-HARDENING-20260729T043010Z.json",
"evidence_closure_sha256": "e482fe52c8381f765ee508d2de1fe2e77dff52e6e342f01be4ca481ff2f76820",
"executor": "aios-governance-single-owner-activation",
"expected_branch_protection_sha256": "f27992c8024426708d93bce072d231707bae47a927aa2393d566aae64d5ed079",
"expected_gitea_schema_sha256": "3524f4912165939cb746c2c65d70583a909f5abbd00c4e07ae76f7206d49aa54",
"expected_state_machine_sha256": "0b08a2c378d143cb9031a25132afc7b7d825bdc03d11efe10610341d4b70167e",
"expected_validator_sha256": "754639846cb15cb997d612a1fe0ceb0224ddef5bbc3ccac9802a3747f122dac1",
"forbidden_operations": {
"baseline": false,
"force_push": false,
"governance_direct": false,
"history_rewrite": false,
"integration": false,
"main": false,
"production": false,
"registry": false,
"tags": false
},
"implementation_commit_oid": "71def340e7cc34f4f19918dcf8d3c847abbe4fbf",
"implementation_tree_oid": "3cc61bfbf69cbe0a24d54a0e53a6c50bcc130066",
"machine_status_producer_id": 13,
"machine_status_producer_name": "gitops-bot",
"operation_id": "single-owner-live-activation-hardening-20260729T043149Z",
"repo_id": 1,
"requester": "codex-agent",
"rollback_artifact_path": "/var/lib/aios-gitops/runtime/tasks/AIOS-SINGLE-OWNER-GOVERNANCE-ACTIVATION-20260729/artifacts/hardening-71def340e7cc34f4f19918dcf8d3c847abbe4fbf/live-activation-rollback-71def340e7cc34f4f19918dcf8d3c847abbe4fbf.tar.gz",
"rollback_artifact_sha256": "4efe1ea73d4fd72e5364fc91a16ad5e9d1e6ebe78d8aa466f7b11c4915b08403",
"runtime_sources": [
{
"git_path": "services/aios-single-owner-governance/aios_single_owner_governance/activation.py",
"runtime_path": "/opt/aios/single-owner-governance/releases/71def340e7cc34f4f19918dcf8d3c847abbe4fbf/services/aios-single-owner-governance/aios_single_owner_governance/activation.py",
"sha256": "48846a58fa3afb2b7c8ffe6e88504a9333863932df867f33ba0321ad3bac6003"
},
{
"git_path": "services/aios-single-owner-governance/aios_single_owner_governance/gitea_protected_branch.py",
"runtime_path": "/opt/aios/single-owner-governance/releases/71def340e7cc34f4f19918dcf8d3c847abbe4fbf/services/aios-single-owner-governance/aios_single_owner_governance/gitea_protected_branch.py",
"sha256": "00775efe3f3d631d88379eff4ed2dcfaf386a2fa43111abe961e75109e80d768"
},
{
"git_path": "services/aios-single-owner-governance/aios_single_owner_governance/journal.py",
"runtime_path": "/opt/aios/single-owner-governance/releases/71def340e7cc34f4f19918dcf8d3c847abbe4fbf/services/aios-single-owner-governance/aios_single_owner_governance/journal.py",
"sha256": "a8f121245e04487d99c99fb03ada83f0c907723f8eb18552872ff46dcf5a7873"
},
{
"git_path": "services/aios-single-owner-governance/aios_single_owner_governance/state_machine.py",
"runtime_path": "/opt/aios/single-owner-governance/releases/71def340e7cc34f4f19918dcf8d3c847abbe4fbf/services/aios-single-owner-governance/aios_single_owner_governance/state_machine.py",
"sha256": "0b08a2c378d143cb9031a25132afc7b7d825bdc03d11efe10610341d4b70167e"
},
{
"git_path": "services/aios-single-owner-governance/aios_single_owner_governance/validator.py",
"runtime_path": "/opt/aios/single-owner-governance/releases/71def340e7cc34f4f19918dcf8d3c847abbe4fbf/services/aios-single-owner-governance/aios_single_owner_governance/validator.py",
"sha256": "754639846cb15cb997d612a1fe0ceb0224ddef5bbc3ccac9802a3747f122dac1"
}
],
"target_branch_protection_sha256": "4713f0494b79103668fbe5bd8fd7da5d60a43e56fb545aa6e7c773f43dc07c10",
"task_id": "AIOS-SINGLE-OWNER-GOVERNANCE-ACTIVATION-20260729",
"test_evidence_path": "/var/lib/aios-gitops/runtime/tasks/AIOS-SINGLE-OWNER-GOVERNANCE-ACTIVATION-20260729/artifacts/hardening-71def340e7cc34f4f19918dcf8d3c847abbe4fbf/test-evidence-71def340e7cc34f4f19918dcf8d3c847abbe4fbf.json",
"test_evidence_sha256": "c1633732767f3f051ffb01404d12d05a6168a4b01b91efdb34a64988d6dee684"
},
"execution_binding_sha256": "63705dd0f5cc25debde132179f96819a844a9703b33c092db4ac48d5680d536a",
"expected_governance_base_oid": "1071979079deab8a4028473d967c2a205c3b111e",
"expires_at": "2026-07-29T16:31:49Z",
"human_authorizer": "owner",
"implementation_commit_oid": "71def340e7cc34f4f19918dcf8d3c847abbe4fbf",
"implementation_tree_oid": "3cc61bfbf69cbe0a24d54a0e53a6c50bcc130066",
"issued_at": "2026-07-29T04:31:49Z",
"legacy_mechanical_approvals": [
"codex",
"gpt"
],
"machine_gate_contexts": [
"aios/branch-protection",
"aios/evidence-closure",
"aios/secret-scan",
"aios/state-machine",
"aios/validator"
],
"machine_status_producer_id": 13,
"machine_status_producer_name": "gitops-bot",
"rollback_artifact_path": "/var/lib/aios-gitops/runtime/tasks/AIOS-SINGLE-OWNER-GOVERNANCE-ACTIVATION-20260729/artifacts/hardening-71def340e7cc34f4f19918dcf8d3c847abbe4fbf/live-activation-rollback-71def340e7cc34f4f19918dcf8d3c847abbe4fbf.tar.gz",
"rollback_artifact_sha256": "4efe1ea73d4fd72e5364fc91a16ad5e9d1e6ebe78d8aa466f7b11c4915b08403",
"runtime_sources": [
{
"git_path": "services/aios-single-owner-governance/aios_single_owner_governance/activation.py",
"runtime_path": "/opt/aios/single-owner-governance/releases/71def340e7cc34f4f19918dcf8d3c847abbe4fbf/services/aios-single-owner-governance/aios_single_owner_governance/activation.py",
"sha256": "48846a58fa3afb2b7c8ffe6e88504a9333863932df867f33ba0321ad3bac6003"
},
{
"git_path": "services/aios-single-owner-governance/aios_single_owner_governance/gitea_protected_branch.py",
"runtime_path": "/opt/aios/single-owner-governance/releases/71def340e7cc34f4f19918dcf8d3c847abbe4fbf/services/aios-single-owner-governance/aios_single_owner_governance/gitea_protected_branch.py",
"sha256": "00775efe3f3d631d88379eff4ed2dcfaf386a2fa43111abe961e75109e80d768"
},
{
"git_path": "services/aios-single-owner-governance/aios_single_owner_governance/journal.py",
"runtime_path": "/opt/aios/single-owner-governance/releases/71def340e7cc34f4f19918dcf8d3c847abbe4fbf/services/aios-single-owner-governance/aios_single_owner_governance/journal.py",
"sha256": "a8f121245e04487d99c99fb03ada83f0c907723f8eb18552872ff46dcf5a7873"
},
{
"git_path": "services/aios-single-owner-governance/aios_single_owner_governance/state_machine.py",
"runtime_path": "/opt/aios/single-owner-governance/releases/71def340e7cc34f4f19918dcf8d3c847abbe4fbf/services/aios-single-owner-governance/aios_single_owner_governance/state_machine.py",
"sha256": "0b08a2c378d143cb9031a25132afc7b7d825bdc03d11efe10610341d4b70167e"
},
{
"git_path": "services/aios-single-owner-governance/aios_single_owner_governance/validator.py",
"runtime_path": "/opt/aios/single-owner-governance/releases/71def340e7cc34f4f19918dcf8d3c847abbe4fbf/services/aios-single-owner-governance/aios_single_owner_governance/validator.py",
"sha256": "754639846cb15cb997d612a1fe0ceb0224ddef5bbc3ccac9802a3747f122dac1"
}
],
"target_branch_protection": {
"approvals_whitelist_team_i_ds": "null",
"approvals_whitelist_user_i_ds": "[2]",
"block_on_official_review_requests": 1,
"block_on_outdated_branch": 1,
"block_on_rejected_reviews": 1,
"branch_name": "governance/approvals",
"can_push": 0,
"created_unix": 1785093201,
"dismiss_stale_approvals": 1,
"enable_approvals_whitelist": 1,
"enable_merge_whitelist": 1,
"enable_status_check": 1,
"enable_whitelist": 0,
"id": 1,
"ignore_stale_approvals": 0,
"merge_whitelist_team_i_ds": "null",
"merge_whitelist_user_i_ds": "[42,43]",
"protected_file_patterns": "",
"repo_id": 1,
"require_signed_commits": 0,
"required_approvals": 1,
"status_check_contexts": "[\"aios/branch-protection\",\"aios/evidence-closure\",\"aios/secret-scan\",\"aios/state-machine\",\"aios/validator\"]",
"unprotected_file_patterns": "",
"updated_unix": 1785299509,
"whitelist_deploy_keys": 0,
"whitelist_team_i_ds": "null",
"whitelist_user_i_ds": "null"
},
"task_id": "AIOS-SINGLE-OWNER-GOVERNANCE-ACTIVATION-20260729",
"test_evidence_path": "/var/lib/aios-gitops/runtime/tasks/AIOS-SINGLE-OWNER-GOVERNANCE-ACTIVATION-20260729/artifacts/hardening-71def340e7cc34f4f19918dcf8d3c847abbe4fbf/test-evidence-71def340e7cc34f4f19918dcf8d3c847abbe4fbf.json",
"test_evidence_sha256": "c1633732767f3f051ffb01404d12d05a6168a4b01b91efdb34a64988d6dee684"
}
@@ -0,0 +1,151 @@
{
"actor_identity": "chatgpt-gitops-bot",
"allowed_operations": [
"runtime.source.read.exact",
"runtime.source.hash",
"runtime.unit_metadata.read",
"package.ownership.read",
"git.object.lookup.by_blob_oid",
"secret.scan.local_source",
"repository.source_import.byte_exact",
"repository.module.create",
"source_map.evidence.write",
"git.branch.patch.create",
"commit_file.atomic",
"git.push.patch_branch",
"test.execute.allowlisted",
"artifact.build.control_plane_patch",
"evidence.write.task_runtime"
],
"allowed_paths": [
"active-runtime/app/aios_autonomous_release_authorization/**",
"active-runtime/app/aios_chatgpt_action_task_router/**",
"active-runtime/app/aios_isolated_writer_api/**",
"tests/test_integration_refresh_provisioner.py",
"tests/test_isolated_writer_integration_merge.py",
"tests/test_gitea_branch_read_projection.py",
"tests/test_isolated_writer_token_policy_v2.py",
"deploy/systemd/aios-integration-refresh-*.service",
"docs/autonomous-release/**"
],
"allowed_runtime_reads": [
"/usr/libexec/aios-autonomous-release-authorization/controller.py",
"/usr/libexec/aios-chatgpt-action-task-router-continuous-20260726/action_task_router.py"
],
"approval_type": "SOURCE_MAPPING_AND_IMPORT",
"canonical_paths": {
"controller": "active-runtime/app/aios_autonomous_release_authorization/controller.py",
"isolated_writer_api": "active-runtime/app/aios_isolated_writer_api/**",
"router": "active-runtime/app/aios_chatgpt_action_task_router/action_task_router.py"
},
"denied_operations": [
"secret.value.return",
"secret.matching_line.return",
"secret.context.return",
"runtime.source.modify",
"runtime.unit.modify",
"arbitrary_path.read",
"secret.read.by_agent",
"secret.export",
"main.write",
"main.merge",
"governance.approvals.direct_write",
"integration.write",
"tag.write",
"tag.delete",
"tag.rewrite",
"force_push",
"history.rewrite",
"bare_ref_direct_write",
"baseline.write",
"registry.write",
"production.deploy",
"production.rollback"
],
"expires_at": "2026-07-27T22:36:27Z",
"extends_approval_merge_oid": "45f3226662965c8d41ec1775e8c8857f2b986397",
"finding_policy": {
"controller_byte_exact_import_allowed": true,
"controller_finding_status": "PASS",
"controller_sanitized_source_creation_allowed": false,
"finding_classification_by_automation": "PROHIBITED",
"isolated_writer_api_module_create_allowed": true,
"matching_source_lines_disclosed": false,
"router_byte_exact_import_allowed": true,
"router_finding_status": "PASS",
"router_sanitized_source_creation_allowed": false,
"scan_decision": "MAPPING_IMPORT_ONLY",
"secret_content_disclosed": false
},
"implementation_base_oid": "875d41c3101093149414292067fd700dc214d4d3",
"implementation_branch": "patch/integration-refresh-grant-provisioner-20260726",
"issued_at": "2026-07-27T10:36:27Z",
"repository": "aios/aios",
"required_controls": {
"controller_finding_must_be_resolved": false,
"controller_import_blob_must_equal_runtime_blob": true,
"controller_import_must_be_byte_exact": true,
"controller_import_sha256_must_equal_runtime_sha256": true,
"dual_human_approval_required": true,
"global_gitops_writes_must_remain_disabled": true,
"new_task_bound_grant_required": true,
"old_implementation_token_auth_status": 401,
"old_implementation_writer_revoked": true,
"old_secret_revocation_must_be_verified": false,
"router_finding_must_be_resolved": false,
"router_import_blob_must_equal_runtime_blob": true,
"router_import_must_be_byte_exact": true,
"router_import_sha256_must_equal_runtime_sha256": true,
"sanitized_source_behavioral_equivalence_required": false,
"sanitized_source_secret_scan_must_pass": false,
"true_positive_requires_rotation": false
},
"risk": "high",
"runtime_source_evidence": {
"controller": {
"finding_count": 0,
"findings": [],
"git_blob_oid": "9c68be36e0b6a18260e3099e3d7b0dcab6d814aa",
"group_gid": 0,
"mode": "0o700",
"mtime_ns": 1785043975531298047,
"owner_uid": 0,
"package_owner": "UNPACKAGED",
"referencing_systemd_units": [
"aios-autonomous-release-authorization-controller.service"
],
"resolved_path": "/usr/libexec/aios-autonomous-release-authorization/controller.py",
"secret_content_returned": false,
"secret_scan": "PASS",
"sha256": "39b532c3058c4ae49ed691d7c66c3b7745a25111fa61039f388c8335fd262c50",
"size": 24204,
"utf8_python": true
},
"isolated_writer_api": {
"canonical_source_path": "NOT_ESTABLISHED",
"runtime_module_status": "ABSENT"
},
"router": {
"finding_count": 0,
"findings": [],
"git_blob_oid": "ee0595ca61a0598f8cb3dc10865a255e21679b2a",
"group_gid": 0,
"mode": "0o644",
"mtime_ns": 1785042222580725999,
"owner_uid": 0,
"package_owner": "UNPACKAGED",
"referencing_systemd_units": [
"aios-chatgpt-autonomous-release-route.service"
],
"resolved_path": "/usr/libexec/aios-chatgpt-action-task-router-continuous-20260726/action_task_router.py",
"secret_content_returned": false,
"secret_scan": "PASS",
"sha256": "738fb7035a6b792115d6467b4d1e4ffd47a8c5d5646f7032ebc68ea587cbd554",
"size": 25952,
"utf8_python": true
}
},
"schema": "aios.gitea-native-approval/v1",
"task_id": "AIOS-INTEGRATION-REFRESH-PROVISIONER-IMPLEMENTATION-20260726",
"writer_audience": "aios-integration-refresh-provisioner-development-writer"
}
@@ -0,0 +1,157 @@
{
"actor_identity": "chatgpt-gitops-bot",
"allowed_operations": [
"runtime.source.read.exact",
"runtime.source.hash",
"runtime.unit_metadata.read",
"package.ownership.read",
"git.object.lookup.by_blob_oid",
"secret.scan.local_source",
"repository.source_import.byte_exact",
"repository.module.create",
"source_map.evidence.write",
"git.branch.patch.create",
"commit_file.atomic",
"git.push.patch_branch",
"test.execute.allowlisted",
"artifact.build.control_plane_patch",
"evidence.write.task_runtime"
],
"allowed_paths": [
"active-runtime/app/aios_autonomous_release_authorization/**",
"active-runtime/app/aios_chatgpt_action_task_router/**",
"active-runtime/app/aios_isolated_writer_api/**",
"tests/test_integration_refresh_provisioner.py",
"tests/test_isolated_writer_integration_merge.py",
"tests/test_gitea_branch_read_projection.py",
"tests/test_isolated_writer_token_policy_v2.py",
"deploy/systemd/aios-integration-refresh-*.service",
"docs/autonomous-release/**"
],
"allowed_runtime_reads": [
"/usr/libexec/aios-autonomous-release-authorization/controller.py",
"/usr/libexec/aios-chatgpt-action-task-router-continuous-20260726/action_task_router.py"
],
"approval_type": "SOURCE_MAPPING_AND_IMPORT_EXECUTION_RENEWAL",
"canonical_paths": {
"controller": "active-runtime/app/aios_autonomous_release_authorization/controller.py",
"isolated_writer_api": "active-runtime/app/aios_isolated_writer_api/**",
"router": "active-runtime/app/aios_chatgpt_action_task_router/action_task_router.py"
},
"denied_operations": [
"secret.value.return",
"secret.matching_line.return",
"secret.context.return",
"runtime.source.modify",
"runtime.unit.modify",
"arbitrary_path.read",
"secret.read.by_agent",
"secret.export",
"main.write",
"main.merge",
"governance.approvals.direct_write",
"integration.write",
"tag.write",
"tag.delete",
"tag.rewrite",
"force_push",
"history.rewrite",
"bare_ref_direct_write",
"baseline.write",
"registry.write",
"production.deploy",
"production.rollback"
],
"expected_governance_base_oid": "9514062cf33e45941abe4a8c98377e0713b0b3f6",
"expires_at": "2026-07-28T18:10:44Z",
"extends_approval_merge_oid": "45f3226662965c8d41ec1775e8c8857f2b986397",
"finding_policy": {
"controller_byte_exact_import_allowed": true,
"controller_finding_status": "PASS",
"controller_sanitized_source_creation_allowed": false,
"finding_classification_by_automation": "PROHIBITED",
"isolated_writer_api_module_create_allowed": true,
"matching_source_lines_disclosed": false,
"router_byte_exact_import_allowed": true,
"router_finding_status": "PASS",
"router_sanitized_source_creation_allowed": false,
"scan_decision": "MAPPING_IMPORT_ONLY",
"secret_content_disclosed": false
},
"implementation_base_oid": "875d41c3101093149414292067fd700dc214d4d3",
"implementation_branch": "patch/integration-refresh-grant-provisioner-20260726",
"issued_at": "2026-07-28T06:10:44Z",
"renews_source_mapping_approval_merge_oid": "9514062cf33e45941abe4a8c98377e0713b0b3f6",
"renews_source_mapping_manifest_blob_oid": "00c5e90e529cb98c7750a5761b0a0d6ccf507174",
"renews_source_mapping_manifest_sha256": "17af8ebc43673c0e46bb9ab1bf44497e8c147f1b4a9d7a8a404edd5c0abd6f21",
"repository": "aios/aios",
"required_controls": {
"controller_finding_must_be_resolved": false,
"controller_import_blob_must_equal_runtime_blob": true,
"controller_import_must_be_byte_exact": true,
"controller_import_sha256_must_equal_runtime_sha256": true,
"dual_human_approval_required": true,
"global_gitops_writes_must_remain_disabled": true,
"new_task_bound_grant_required": true,
"old_implementation_token_auth_status": 401,
"old_implementation_writer_revoked": true,
"old_secret_revocation_must_be_verified": false,
"router_finding_must_be_resolved": false,
"router_import_blob_must_equal_runtime_blob": true,
"router_import_must_be_byte_exact": true,
"router_import_sha256_must_equal_runtime_sha256": true,
"sanitized_source_behavioral_equivalence_required": false,
"sanitized_source_secret_scan_must_pass": false,
"true_positive_requires_rotation": false
},
"risk": "high",
"runtime_source_evidence": {
"controller": {
"finding_count": 0,
"findings": [],
"git_blob_oid": "9c68be36e0b6a18260e3099e3d7b0dcab6d814aa",
"group_gid": 0,
"mode": "0o700",
"mtime_ns": 1785043975531298047,
"owner_uid": 0,
"package_owner": "UNPACKAGED",
"referencing_systemd_units": [
"aios-autonomous-release-authorization-controller.service"
],
"resolved_path": "/usr/libexec/aios-autonomous-release-authorization/controller.py",
"secret_content_returned": false,
"secret_scan": "PASS",
"sha256": "39b532c3058c4ae49ed691d7c66c3b7745a25111fa61039f388c8335fd262c50",
"size": 24204,
"utf8_python": true
},
"isolated_writer_api": {
"canonical_source_path": "NOT_ESTABLISHED",
"runtime_module_status": "ABSENT"
},
"observed_at": "2026-07-28T05:53:01.253909Z",
"router": {
"finding_count": 0,
"findings": [],
"git_blob_oid": "ee0595ca61a0598f8cb3dc10865a255e21679b2a",
"group_gid": 0,
"mode": "0o644",
"mtime_ns": 1785042222580725999,
"owner_uid": 0,
"package_owner": "UNPACKAGED",
"referencing_systemd_units": [
"aios-chatgpt-autonomous-release-route.service"
],
"resolved_path": "/usr/libexec/aios-chatgpt-action-task-router-continuous-20260726/action_task_router.py",
"secret_content_returned": false,
"secret_scan": "PASS",
"sha256": "738fb7035a6b792115d6467b4d1e4ffd47a8c5d5646f7032ebc68ea587cbd554",
"size": 25952,
"utf8_python": true
}
},
"runtime_source_read_projection_evidence_sha256": "23f03d9c6abe6f11e1acda5210b8708696e3b77caa0083b6bfaef30a44b2a35f",
"schema": "aios.gitea-native-approval/v1",
"task_id": "AIOS-INTEGRATION-REFRESH-PROVISIONER-IMPLEMENTATION-20260726",
"writer_audience": "aios-integration-refresh-provisioner-development-writer"
}
+72 -5
View File
@@ -1,5 +1,5 @@
{
"schema_version": 1,
"schema_version": 4,
"automatic": [
"build",
"test",
@@ -8,8 +8,7 @@
"evidence"
],
"owner": [
"production_runtime_switch",
"production_tag"
"signed_release_plan"
],
"forbidden": [
"force_push",
@@ -19,7 +18,8 @@
"registry_mutation"
],
"runtime_deployment": {
"approval_action": "production_runtime_switch",
"approval_action": "signed_release_plan",
"required_capability": "runtime_switch",
"tag_operations_allowed": false,
"baseline_mutation_allowed": false,
"registry_mutation_allowed": false,
@@ -27,10 +27,77 @@
"rollback_required": true
},
"tag_release": {
"approval_action": "production_tag",
"approval_action": "signed_release_plan",
"required_capability": "tag_publish",
"separate_controller": true,
"duplicate_conflict": "fail_closed",
"rewrite_allowed": false,
"force_push_allowed": false
},
"baseline_registry_finalize": {
"approval_action": "signed_release_plan",
"required_capability": "baseline_registry_finalize",
"separate_controller": true,
"remote_tag_verification_required": true,
"gitea_api_verification_required": true,
"registry_baseline_consistency_required": true
},
"approval": {
"algorithm": "Ed25519",
"trust_domain": "aios.production",
"maximum_lifetime_seconds": 3600,
"release_plan_sha256_required": true,
"not_before_required": true,
"replay_protection": true,
"revocation_fail_closed": true,
"key_rotation_supported": true,
"capabilities": [
"runtime_switch",
"tag_publish",
"baseline_registry_finalize"
]
},
"execution": {
"environment_variable": "AIOS_SILENT_RELEASE_EXECUTION_ENABLED",
"default_enabled": false,
"shadow_side_effects_allowed": false,
"durable_saga_required": true,
"pre_switch_revalidation_required": true,
"trusted_candidate_builder_required": true,
"actual_plan_regeneration_required": true,
"startup_fail_closed_on_missing_component": true,
"recovery_worker_shadow_default": true,
"production_approval_backend_required": true,
"production_builder_signer": "kms-hsm-secret-manager-only",
"production_execution_without_backend": "unavailable"
},
"durability": {
"journal": "sha256-hash-chain",
"probe_journal": "sha256-hash-chain",
"exclusive_capability_claims": true,
"side_effect_probe_before_lease_takeover": true,
"side_effect_probe_before_failed_recoverable_retry": true,
"probe_results": [
"NOT_APPLIED",
"APPLIED_VERIFIED",
"INDETERMINATE",
"CONFLICT"
],
"probe_operation_digest_required": true,
"probe_freshness_required": true,
"tamper_result": "MANUAL_OWNER_REVIEW_REQUIRED"
},
"tag_pending": {
"required_observations": [
"remote_git_ref",
"annotated_tag_object",
"peeled_target",
"gitea_api",
"gitea_listing",
"repository_identity"
],
"indeterminate_result": "RECONCILIATION_REQUIRED",
"conflict_result": "MANUAL_OWNER_REVIEW_REQUIRED",
"automatic_reconciliation": false
}
}
@@ -0,0 +1,25 @@
[Unit]
Description=AIOS Silent Release Shadow Controller
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=aiman
Group=gitops
UMask=0077
Environment=AIOS_GITOPS_WRITES_ENABLED=false
Environment=AIOS_SILENT_RELEASE_EXECUTION_ENABLED=false
Environment=PYTHONDONTWRITEBYTECODE=1
ExecStart=/usr/bin/python3 /usr/libexec/aios-silent-release-shadow/current/active-runtime/app/silent_release_execution.py --state-root /var/lib/aios-silent-release-shadow --repository /data/aios-gitops/repo.git --host 127.0.0.1 --port 18891 --poll-seconds 30
Restart=on-failure
RestartSec=5
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/aios-silent-release-shadow
ReadOnlyPaths=/data/aios-gitops/repo.git /usr/libexec/aios-silent-release-shadow
[Install]
WantedBy=multi-user.target
+176 -207
View File
@@ -1,91 +1,80 @@
# AIOS Silent Release Controller Design Report
状态:isolated branch implementation
Production mutation:forbidden
目标流程:自动准备 → 自动验证 → 单次 Owner runtime 授权 → 自动部署/失败回滚
状态:隔离分支实现;生产 mutation 禁止
目标:自动准备 → 自动验证 → 单次 Owner 签名授权 → 分控制器自动执行与验收
## 1. 当前痛点
当前发布依赖多轮人工传递 commit、tree、archive、manifest、drop-in、测试和
canary 证据。证据在不同消息和报告中重复抄写,容易出现:
现有流程需要多轮人工传递 commit、tree、artifact、manifest、测试、canary、drop-in
和回滚证据。证据在不同消息里重复抄写,容易出现:
- artifact 在测试后被 cache 文件污染,closure 数量漂移;
- canary 通过,但批准文本与 runtime 实际字段契约不同;
- Git、Gitea、artifact、systemd 和 rollback 门禁由人手工拼接;
- runtime deployment 与 production tag 容易被误认为同一授权;
- 授权重放、失败回滚和持久化状态缺少统一接口。
- 构建后 artifact、manifest 或 policy 已变化,但旧批准仍被使用;
- Git、Gitea、systemd 与回滚结果由人工拼接,缺少同一一致性快照;
- runtime switch、tag、Baseline、Registry 的授权边界不清;
- 重启后只剩工作树内临时 JSON,无法可靠恢复;
- approval replay、并发 writer、key revoke 和 secret inheritance 缺少统一防线。
## 2. 新架构
```text
ReleaseBuilder adapter
Build / Test / Canary
|
v
SilentReleaseController.prepare(source_commit)
|
+--> ReleaseCandidateStore
ReleaseCandidate + DeploymentPlan
|
v
ReleaseAcceptanceEngine.evaluate(candidate)
|
+--> ReleaseProbe adapter
- local/bare/Gitea main
- repository identity
- artifact/manifest
- tests/canary
- rollback
- authoritative Git binding
ReleaseAcceptanceEngine
|
v
READY_FOR_APPROVAL
|
v one ApprovalEnvelope: production_runtime_switch
DeploymentController.deploy(candidate, approval)
v
SignedReleasePlanApproval (Ed25519)
|
+--> atomic switch
+--> restart only GitOps runtime
+--> health verification
+--> rollback on any failure
+--> runtime_switch capability
| DeploymentController
|
+--> tag_publish capability
| TagController
|
+--> baseline_registry_finalize capability
BaselineRegistryFinalizeController
TagController (separate seam)
|
+--> separate production_tag approval
+--> execute
+--> verify
Durable ReleaseSagaStore + CapabilityLedger + Event Journal
贯穿三个控制器;控制器互相不拥有对方的 mutation API。
```
`SilentReleaseController` 是外部深模块,只暴露 `prepare()` 和 `deploy()`。
构建、探针、systemd 与 tag provider 都是 adapter;复杂实现保持在模块内部。
`CommandReleaseBuilder` 使用无 shell 的 argv 调用确定性构建流水线,并只接受
与请求 source commit 一致的候选 JSON。
一次 Owner 批准是同一个不可变 release plan 的签名,不是一个万能执行入口。三个 capability
分别消费、分别留痕、分别幂等恢复。`force_push`、`tag_rewrite`、`direct_main_push`
永远不是可签发 capability。
## 3. 状态机
```text
BUILDING
|
v
VERIFYING ------------------------> FAILED
|
v
READY_FOR_APPROVAL --------------> FAILED
|
v
APPROVED ------------------------> FAILED
|
v
DEPLOYING -------> VERIFIED
| |
+-------------> ROLLED_BACK
|
+-------------> FAILED (rollback itself failed)
PREPARING
-> VERIFYING
-> READY_FOR_APPROVAL
-> APPROVED
-> PRE_SWITCH_REVALIDATING
-> SWITCH_PENDING
-> SWITCHED
-> RUNTIME_VERIFYING
-> TAG_PENDING
-> TAG_VERIFIED
-> BASELINE_REGISTRY_PENDING
-> COMPLETED
任一可恢复故障 -> RECONCILIATION_REQUIRED
对象冲突 -> MANUAL_OWNER_REVIEW_REQUIRED
切换后失败 -> ROLLBACK_PENDING -> ROLLED_BACK
```
状态不可跳跃。`VERIFIED` 只允许显式进入 `ROLLED_BACK`,终态不能自动回到准备态。
状态快照使用 revision compare-and-set;事件同时写入 append-only journal。writer 崩溃后根据
状态确定唯一恢复动作。并发控制器中只有一个 CAS winner。
## 4. 数据模型
`ReleaseCandidate`:
### ReleaseCandidate
```text
candidate_id
@@ -99,201 +88,181 @@ rollback_ready
git_consistency
gitea_consistency
approval_state
state
failure_reason
created_at
updated_at
```
核心 OID 和 SHA 在构造时验证。候选 fingerprint 绑定 candidate、commit、tree、
artifact 和 manifest,ApprovalEnvelope 必须逐字段匹配。
`AcceptanceEvidence` 是 adapter 与 acceptance engine 之间的 seam,包含三方 main、
repository identity、artifact/manifest、测试、canary、rollback 和 authoritative Git。
## 5. Interface
Python interface:
```python
candidate = controller.prepare(source_commit)
approval = ApprovalEnvelope.for_candidate(
candidate,
approval_id=...,
owner_id=...,
expires_at=...,
)
result = controller.deploy(candidate.candidate_id, approval)
```
建议 HTTP Action adapter:
### DeploymentPlan
```text
POST /api/silent-release/candidates
GET /api/silent-release/candidates/{candidate_id}
POST /api/silent-release/candidates/{candidate_id}/verify
POST /api/silent-release/candidates/{candidate_id}/approve-runtime
POST /api/silent-release/candidates/{candidate_id}/deploy
GET /api/silent-release/candidates/{candidate_id}/acceptance
service
release_path / release_root
dropin_path / dropin_sha256
rollback_path / rollback_sha256
source_commit
artifact_sha256
manifest_sha256
policy_sha256
health_contract
sha256
```
Tag interface 必须位于独立 namespace:
### SignedReleasePlanApproval
```text
POST /api/tag-releases/{version}/approve
POST /api/tag-releases/{version}/execute
GET /api/tag-releases/{version}/verify
release_plan_id
candidate_id
source_commit
tree_oid
artifact_sha256
manifest_sha256
deployment_plan_sha256
policy_sha256
approval_plan_id
evidence_closure_oid
owner_approval_record_oid
capabilities
expires_at
nonce
key_id
signature
fingerprint
```
runtime approval 不得被 TagController 接受;tag approval 不得被
DeploymentController 接受。
签名内容使用 canonical JSON,`signature` 不参与被签名字段。验证包括 Ed25519 签名、过期时间、
公钥状态、approval revoke、candidate/plan/policy 逐字段绑定和 capability allowlist。
## 6. ReleaseAcceptanceEngine
`CommandReleaseProbe` 提供实际只读 adapter:
- local main:指定 local repository;
- bare main:指定 authoritative bare Git;
- Gitea main/identity:认证 API,只从 credential reference 读取 token;
- artifact/manifest:流式 SHA-256 和 JSON 绑定;
- tests/canary:无 shell 的 argv command;
- runtime without `.git`;
- rollback artifact;
- authoritative Git 路径与可读性。
任何命令、Git、Gitea 或证据异常均转为 `FAILED`,不会降级为空成功。
## 7. ApprovalEnvelope
Owner 的 runtime envelope 只授权:
## 5. API
```text
action=production_runtime_switch
POST /createReleaseCandidate
GET /getReleaseCandidate?candidate_id=...
GET /getApprovalEnvelope?candidate_id=...
POST /submitOwnerApproval
POST /startDeployment
GET /getReleaseSaga?candidate_id=...
```
Envelope 绑定:
写接口要求 Bearer scope。`AIOS_SILENT_RELEASE_EXECUTION_ENABLED=false` 是默认值;此时可以
持久化 candidate、生成 approval envelope、保存经验证的 Owner approval 和读取 Saga,
但 `/startDeployment` 只返回 `SHADOW_MODE` 与空 `side_effects`。
- candidate_id/fingerprint;
- source commit/tree;
- artifact/manifest SHA;
- owner、approval ID、签发和过期时间。
## 6. PreSwitchValidator
`ApprovalLedger` 使用 `O_EXCL` 原子消费 approval ID,重放直接拒绝。
切换前重新收集并比较:
生产 tag 使用完全独立的 `TagApprovalEnvelope`:
- source commit 与 tree;
- artifact、manifest、policy 和 deployment plan SHA-256;
- rollback readiness;
- local、authoritative bare 与 Gitea refs;
- repository identity;
- task writer/production writer 边界;
- health contract。
任一变化都抛出:
```text
action=production_tag
version
tag_oid
target_oid
APPROVAL_STALE_REAPPROVAL_REQUIRED
```
## 8. DeploymentController
旧批准不会被“最佳努力”继续使用。
只接受 `READY_FOR_APPROVAL` 候选和完整 runtime envelope:
## 7. systemd 防线
1. 验证 Policy as Code;
2. 原子消费 approval;
3. 原子切换 versioned drop-in symlink;
4. 仅 restart 指定 GitOps runtime;
5. 验证 runtime commit 和 writer=false;
6. 任一异常切回旧 symlink并再次 restart;
7. rollback 后重新执行健康检查并确认 writer=false;
8. rollback 或 rollback health 失败时进入 `FAILED`,禁止伪装成功。
- service 与 drop-in 均为显式 allowlist;
- release 与 drop-in resolved path 必须位于 release root 内;
- 拒绝普通文件被替换为 symlink,拒绝 symlink escape;
- deployment lock 覆盖 switch、restart 和首次 health check;
- restart 前后记录 MainPID 与 ActiveEnterTimestampMonotonic;
- artifact、drop-in、rollback 和 manifest 均按 plan hash 复验;
- 子进程只接收固定 allowlist 环境,不继承 token、password、credential、askpass、
auth socket、`LD_PRELOAD`、`PYTHONPATH` 或数据库连接串。
Deployment adapter 的 interface 中没有 tag、Baseline 或 Registry 方法,因此 runtime
部署路径无法顺带调用这些能力。
## 8. TagController 分离
## 9. TagController 分离
TagController 必须独立完成 approval、execute、verify,并留下:
TagController 的三个动作严格分开:
```text
evidence_closure_oid
approval_plan_id
repository_identity
remote_tag_oid
remote_peeled_target_oid
gitea_api_tag_oid
gitea_listing_visible
```
1. `approve(envelope)`
2. `execute(approved)`
3. `verify(approved)`
同名 tag 的 tag OID 或 peeled target 不同,进入
`MANUAL_OWNER_REVIEW_REQUIRED`;自动流程不 push、不删除、不重写、不 force push。
同名 tag 已存在且 tag OID 或 target OID 不一致时抛出
`DuplicateTagConflict`。接口没有 delete、rewrite 或 force push。
## 9. Baseline / Registry 门禁
只有以下全部一致时,`baseline_registry_finalize` capability 才能完成:
```text
Registry = Baseline = remote tag = Gitea API = expected target
```
UNKNOWN、API 不可达、repository identity 不符或 listing 不可见均 fail closed。
## 10. Policy as Code
`config/gitops/silent-release-policy.json`:
```text
automatic: build, test, artifact, canary, evidence
owner: production_runtime_switch, production_tag
forbidden: force_push, tag_rewrite, direct_main_push,
baseline_mutation, registry_mutation
automatic:
build, test, artifact, canary, evidence
owner:
signed_release_plan
capabilities:
runtime_switch
tag_publish
baseline_registry_finalize
forbidden:
force_push
tag_rewrite
direct_main_push
baseline_mutation
registry_mutation
```
Policy 缺少任一部署禁止项时,DeploymentController 拒绝运行。
最后两个 forbidden 项指“脱离受治理 finalize controller 的直接 mutation”。
## 11. Security model
- fail-closed Git/Gitea/manifest/health adapter;
- credential 只从受控文件读取,不进入模型或持久化候选;
- candidate store 对 Authorization、token、credential、password、secret 字段脱敏;
- 错误文本统一清除 Authorization、token、URL userinfo 和 password;
- approval ID 一次性消费并有过期时间;
- candidate fingerprint 防止对其他 artifact 重放批准;
- runtime 默认 writer=false;
- systemd adapter 只允许管理 symlink,拒绝覆盖普通配置文件;
- no tag rewrite、force push、direct main push;
- runtime deployment 无 Baseline/Registry/tag capability。
- Ed25519 公钥 ring 支持 rotation 和 revoke,私钥不进入 runtime;
- approval plan 可撤销,过期、未知 key、错误签名均 fail closed;
- nonce 与 capability operation 持久化,重复相同请求幂等,不同 fingerprint 重放拒绝;
- Saga snapshot 原子写、journal append-only、CAS 防并发 writer;
- credential 只通过受控 reference 使用,不进入 Git、API 响应或异常;
- 子进程环境从空集合构建,不复制 `os.environ`;
- Shadow 模式默认开启,execution adapter 缺失时不能降级执行;
- runtime、tag、Baseline/Registry 分属三个能力最小化控制器。
## 12. Migration plan
### Phase 1:shadow
1. Shadow:生成 candidate、plan、acceptance、approval package,仅比较现有人工结果。
2. Signed approval:接入 Owner 公钥 ring、撤销源和审计 record;仍不接生产 executor。
3. Canary execution:使用非生产 service/release root 验证锁、PID、health、崩溃恢复和回滚。
4. Controlled production switch:另行 Owner 授权启用 execution flag,只开放 runtime capability。
5. Tag/finalize:分别完成 Gitea 与 Registry/Baseline 生产验收后再启用对应 capability。
- 只生成 ReleaseCandidate 和 acceptance evidence;
- 与现有人工报告并行比较;
- 不接入 production executor。
### Phase 2:approval envelope
- Owner UI/Action 只生成一次性 runtime envelope;
- production 仍由现有受控 executor 执行;
- 验证 approval replay 和过期处理。
### Phase 3:controlled deployment
- 接入 versioned drop-in symlink;
- 先在 canary service 完成自动 switch/restart/rollback;
- production writer 始终为 false。
### Phase 4:silent release
- 自动 build/test/artifact/canary/evidence;
- READY_FOR_APPROVAL 时只通知一次 Owner;
- 获得 runtime envelope 后自动部署和验收;
- TagController 始终保持独立授权。
每个阶段均可退回上一个阶段;不开启下一能力不会影响已经启用的控制器。
## 13. Rollback plan
- 切换前记录旧 symlink target;
- 新 runtime 任一健康、Git、hash、writer 或一致性门禁失败即恢复旧 target;
- 只 restart GitOps runtime;
- rollback 后再次验证旧 runtime、writer=false 和只读 Git-backed API;
- 保留失败候选、脱敏证据和 approval ledger;
- 不使用 Git reset、force push、tag rewrite;
- rollback 不修改 Baseline、Registry 或 production tag。
- switch 前保存旧 symlink target、PID、启动时间、artifact 与 plan fingerprint;
- restart 或 health 任一失败进入 `ROLLBACK_PENDING`;
- 原子恢复旧 target,只 restart 指定 allowlist service;
- 重新验证旧 runtime commit、writer=false 与 repository identity;
- 成功进入 `ROLLED_BACK`,失败进入 `MANUAL_OWNER_REVIEW_REQUIRED`;
- rollback 不修改 Git main、production tag、Baseline 或 Registry;
- 保留脱敏 journal、approval fingerprint 和失败证据。
## 14. 自动测试矩阵
## 14. 测试与外部依赖边界
实现测试覆盖:
自动测试覆盖签名伪造、字段篡改、过期/revoke/key rotation、nonce replay、CAS race、
崩溃恢复、artifact/policy/plan mutation、tag conflict、secret leakage、runtime without
`.git`、rollback 和 shadow no-side-effects。
- artifact mismatch;
- Gitea unavailable;
- authoritative Git unavailable;
- health failure rollback;
- approval replay;
- duplicate tag conflict;
- secret leakage;
- runtime without `.git`;
- state transition;
- policy separation;
- candidate persistence;
- automatic prepare 到单次 Owner gate。
本隔离实现不创建生产 KMS/key、不安装 systemd unit、不部署 Action runtime、不创建 tag,
也不修改 Baseline/Registry。上述动作必须在单独生产批准中执行。
@@ -0,0 +1,107 @@
# AIOS Silent Release Execution Readiness
## Scope and stop point
This change makes the controller execution-ready while deploying only a
localhost-bound Shadow control plane. Both production gates remain false:
```text
AIOS_GITOPS_WRITES_ENABLED=false
AIOS_SILENT_RELEASE_EXECUTION_ENABLED=false
```
Shadow completion is evidence preparation, not a production release. It cannot
write a production tag, switch a business runtime, or mutate Baseline/Registry.
## Recovery state machine
Every expired or `FAILED_RECOVERABLE` capability requires fresh, operation-bound
probe evidence before another adapter may run:
```text
NOT_APPLIED -> one new exclusive claim may retry
APPLIED_VERIFIED -> mark COMPLETED without adapter replay
INDETERMINATE -> RECONCILIATION_REQUIRED; no automatic writes
CONFLICT -> MANUAL_OWNER_REVIEW_REQUIRED; no automatic writes
```
Probe evidence binds the immutable operation digest, is freshness checked, and
is appended to a separate SHA-256 hash-chained journal. Completion still
requires the active claim ID. File locks and leases ensure that concurrent
recovery workers cannot execute the same adapter.
`TAG_PENDING` uses independent remote ref, annotated object, peeled target,
Gitea API, Gitea listing, and repository identity evidence. Exact absence may
roll back a switched runtime. Exact publication continues to finalization.
Unavailable/divergent evidence stops at reconciliation; a different OID or
target stops for Owner review. No branch can force, delete, or rewrite a tag.
## Trusted build and approval
Callers submit only an event ID and exact source commit. Test, canary, artifact,
manifest, rollback, secret scan, Git and Gitea evidence are generated by the
trusted builder. The builder emits an Ed25519 attestation binding:
- builder attestation/key IDs;
- source commit and tree OID;
- artifact, manifest and package-closure SHA-256;
- test and canary evidence SHA-256;
- issuance time and signature.
The controller contains only trusted public keys. Production signing is exposed
through a KMS/HSM/secret-manager adapter; a local private-key adapter is marked
test-only and is used only for ephemeral Shadow acceptance.
Owner approvals are immutable, time-bounded, revocable, trust-domain bound, and
single-use per capability ledger. If the production approval backend reference
is absent, production execution is unavailable while Shadow remains available.
Caller-supplied `owner_id` is never a fallback.
## Persistent controller and API
`aios-silent-release-controller.service` runs independently from the business
GitOps API. It uses a single leader lock, durable intents/candidates/Sagas,
startup journal verification, idempotent main events, one notification record,
and recovery reporting. The final deployed service is report-only Shadow mode.
The Action contract exposes:
- `createReleaseIntent`
- `getReleaseCandidate`
- `getReleasePlan`
- `getReleaseAcceptance`
- `submitSignedApproval`
- `getReleaseSaga`
- `getCapabilityStatus`
- `startDeployment`
- `getRecoveryStatus`
Every Shadow response declares `execution_enabled=false`, `shadow_mode=true`
and `side_effects_executed=false`. `startDeployment` returns a simulated
`SHADOW_COMPLETED` result and never claims production capabilities.
## Migration and rollback
1. Merge through a governed pull request.
2. Build from a clean detached worktree at the exact merge commit.
3. Verify artifact, manifest, package closure, tests, live fixture, no-.git
canary, rollback archive and secret scan.
4. Install a versioned Shadow release without changing the business runtime.
5. Start the separate localhost-only controller with both gates false.
6. Run Shadow candidates and freeze production PID/tag/Baseline/Registry hashes.
7. Remove ephemeral test keys and temporary repositories.
Rollback stops and disables only the Shadow controller and restores/removes its
versioned `current` link. The existing production API, tag set, Baseline and
Registry are not involved.
## Future activation envelope
Activation requires a new one-time Owner approval binding the exact controller
commit, artifact/manifest/policy/unit hashes, trusted key IDs, KMS/secret
references, release and rollback roots, authoritative Git/repository identity,
Shadow candidate/evidence IDs, test results, activation plan and rollback plan.
That approval may only flip
`AIOS_SILENT_RELEASE_EXECUTION_ENABLED=false` to `true`; each real release still
requires its own signed ReleasePlan. This change does not request or consume
that activation approval.
@@ -0,0 +1,84 @@
# Silent Release Controller Production Contract
Status: review-only implementation. Production execution remains disabled.
## Trust boundary
The caller submits only release intent. A configured trusted builder owns ref
validation, build, tests, artifact and manifest hashing, package closure,
canary, rollback readiness, Git/Gitea consistency, and secret scanning. Caller
supplied success evidence is rejected.
The resulting immutable `ReleasePlan` contains deployment, tag, and
finalization plans. One Ed25519 owner signature binds its SHA-256 digest,
candidate evidence, policy digest, owner identity, trust domain, validity
window, evidence lineage, and three narrow capabilities.
## Durable execution
The Saga journal is the source of truth. Every record is sequence-numbered and
SHA-256 hash-chained from a fixed genesis hash. Snapshots are rebuildable
caches. Journal corruption, truncation, reordering, missing records, or hash
mismatch moves the release to `MANUAL_OWNER_REVIEW_REQUIRED`.
Capability execution uses an exclusive, leased claim. A second worker receives
`BUSY`; an expired claim is not reassigned until a capability-specific
side-effect probe reports that the operation was not applied. Runtime, tag,
and finalization remain separately claimed and recoverable.
## Execution order
```text
APPROVED
-> PRE_SWITCH_REVALIDATING
-> SWITCH_PENDING
-> SWITCHED
-> RUNTIME_VERIFYING
-> RUNTIME_VERIFIED
-> TAG_PENDING
-> TAG_VERIFIED
-> BASELINE_REGISTRY_PENDING
-> COMPLETED
```
The actual adapter configuration is regenerated before execution and must
match the signed plan. Trusted allowlists come from controlled policy, not the
request.
A failure before verified tag publication rolls the runtime back. A failure
after the exact tag is verified enters `RECONCILIATION_REQUIRED`; the tag is
never deleted or rewritten. A same-name/different-OID conflict enters
`MANUAL_OWNER_REVIEW_REQUIRED`. Registry/Baseline finalization requires exact
remote and Gitea tag proof, and Baseline is recorded last.
## Action and recovery contract
`submitOwnerApproval` verifies and durably stores the signed plan, then moves
the Saga to `APPROVED`. `startDeployment` loads that durable approval and
ignores caller-supplied approval content. Enabling execution without the
trusted builder, verifier, validator, policy, and orchestrator fails during
service construction.
The recovery worker scans incomplete journals and resumes through the same
claims and verification gates. It never automatically resumes a manual-review
Saga. In shadow mode it only reports state and performs no adapter side
effects.
The deployed Action factory does not enable the legacy caller-evidence path.
If the trusted builder is absent, candidate creation is rejected even while
execution remains in shadow mode. The compatibility path exists only through
the explicit test-only `SilentReleaseActionService.shadow()` constructor.
Finalization is composed from separate `RegistryController` and
`BaselineController` claims. Registry is verified first and Baseline is the
last mutation; a retry reuses completed Registry evidence and never moves
Baseline ahead of tag verification.
## Governance
- `force_push`: forbidden
- `tag_rewrite`: forbidden
- `direct_main_push`: forbidden
- production execution default: disabled
- production tag operations in this change: not executed
- Baseline/Registry mutations in this change: not executed
@@ -0,0 +1,68 @@
# Isolated Writer Exact-Path Policy V1
This policy is an additional, service-owned authorization layer for
`POST /api/commit-file`. It never expands a Token Policy V2 grant.
An atomic write is permitted only when all of the following agree:
1. socket peer, audience, operation, branch, base and exact path in the
Token Policy V2 grant;
2. service audience, repository identity, branch, operation and exact path
in `AIOS_GITOPS_PATH_POLICY_FILE`;
3. lexical path, resolved containment and symlink checks;
4. the configured content-size and content-profile checks;
5. operation digest, expected branch HEAD and durable operation ledger.
Any failed layer returns a fail-closed policy error. Coarse `/api/file` and
`/api/commit` routes remain forbidden.
## File contract
The configured path must be absolute. The parent directory must be
`root:gitops 0750`; the regular, non-symlink file must be
`root:gitops 0640`. JSON must use UTF-8 canonical encoding with sorted keys,
compact separators and one trailing newline. Unknown fields, unsupported
operations or profiles, duplicate paths, oversized policies and
group/world-writable files are rejected during Writer startup.
Example:
```json
{"allowed_branch":"patch/codex-review-test-worker-20260724","allowed_operations":{"commit_file.atomic":{"content_profile":"systemd-codex-review-worker-v1","exact_paths":["deploy/systemd/aios-codex-review-test-worker.service"],"max_content_bytes":32768}},"audience":"aios-codex-review-worker-writer.service","policy_id":"codex-review-worker-path-policy-v1","repository_identity":"aios/aios","schema_version":1}
```
The SHA-256 of the canonical policy is exposed in the isolated runtime
status and written to the operation audit record. The request body cannot
select or override the policy file.
## Content profile
`systemd-codex-review-worker-v1` is an exact, fail-closed unit grammar. The
only sections are `[Unit]`, `[Service]` and `[Install]`. Each section has a
directive allowlist; every single-value directive and every repeated
`Environment`, `ReadOnlyPaths` and `ReadWritePaths` entry must match the
governed Shadow unit contract exactly.
The profile fixes the Python module
`task_plane.workers.codex_review_test`, `User=aiman`, `Group=gitops`,
the controlled `/usr/libexec/aios-codex-review-test-worker/current`
working directory, all hardening switches and the three permitted
environment variables. It grants read access only to the authoritative
bare repository and the versioned worker artifact, and write access only
to the worker runtime, state and cache directories.
Unknown sections or directives are rejected, including lifecycle hooks,
credential injection, environment imports, root images, bind mounts,
capability expansion, devices, sockets and top-level writable paths. The
parser also rejects duplicate sections, duplicate single-value directives,
empty resets, continuation lines, CRLF, NUL, non-UTF-8 surrogate content,
overlong lines and Unicode lookalike directive names. It does not use
`ConfigParser(strict=False)` or silently merge repeated values.
Production credentials, if later authorized, must be supplied by a
separately governed deployment drop-in. They are not permitted in the
source unit or this content profile.
Policy changes require a separate maintenance branch and Owner-reviewed
ordinary merge commit. A task-scoped Writer must not edit its own policy
file.
+253
View File
@@ -51,6 +51,31 @@ components:
path: {type: string, example: docs/gitops/chatgpt-change.md}
content: {type: string}
message: {type: string}
AtomicFileCommitRequest:
type: object
additionalProperties: false
required: [branch, path, content, message, expected_head_oid, idempotency_key]
properties:
branch:
type: string
pattern: '^patch/[A-Za-z0-9._/-]+$'
path:
type: string
minLength: 1
maxLength: 512
content:
type: string
maxLength: 1048576
message:
type: string
minLength: 1
maxLength: 200
expected_head_oid:
type: string
pattern: '^[0-9a-f]{40}$'
idempotency_key:
type: string
pattern: '^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$'
CommitRequest:
type: object
properties:
@@ -135,7 +160,216 @@ components:
properties:
text: {type: string}
branch: {type: string}
DeploymentPlan:
type: object
required: [service, release_path, release_root, cutover_link, dropin_path, dropin_sha256, systemd_unit_sha256, rollback_path, rollback_sha256, source_commit, tree_oid, artifact_sha256, manifest_sha256, package_closure_sha256, authoritative_git_dir, repository_identity, writer_expected, baseline_before_sha256, registry_before_sha256, production_tag_refset_before_sha256, health_contract_version, timeout_policy, retry_policy, policy_sha256, health_contract]
properties:
service: {type: string}
release_path: {type: string}
release_root: {type: string}
cutover_link: {type: string}
dropin_path: {type: string}
dropin_sha256: {type: string, pattern: '^[0-9a-f]{64}$'}
systemd_unit_sha256: {type: string, pattern: '^[0-9a-f]{64}$'}
rollback_path: {type: string}
rollback_sha256: {type: string, pattern: '^[0-9a-f]{64}$'}
source_commit: {type: string, pattern: '^[0-9a-f]{40}$'}
tree_oid: {type: string, pattern: '^[0-9a-f]{40}$'}
artifact_sha256: {type: string, pattern: '^[0-9a-f]{64}$'}
manifest_sha256: {type: string, pattern: '^[0-9a-f]{64}$'}
package_closure_sha256: {type: string, pattern: '^[0-9a-f]{64}$'}
authoritative_git_dir: {type: string}
repository_identity: {type: string}
writer_expected: {type: boolean, enum: [false]}
baseline_before_sha256: {type: string, pattern: '^[0-9a-f]{64}$'}
registry_before_sha256: {type: string, pattern: '^[0-9a-f]{64}$'}
production_tag_refset_before_sha256: {type: string, pattern: '^[0-9a-f]{64}$'}
health_contract_version: {type: string}
timeout_policy: {type: object}
retry_policy: {type: object}
policy_sha256: {type: string, pattern: '^[0-9a-f]{64}$'}
health_contract: {type: object, additionalProperties: true}
ReleaseCandidateInput:
type: object
required: [source_commit, version]
additionalProperties: false
properties:
candidate_id: {type: string, pattern: '^[A-Za-z0-9][A-Za-z0-9_.-]{2,127}$'}
source_commit: {type: string, pattern: '^[0-9a-f]{40}$'}
version: {type: string, pattern: '^V[0-9]+R[0-9]+C[0-9]+(?:[-.][A-Za-z0-9][A-Za-z0-9.-]*)?$'}
ReleaseIntentInput:
type: object
required: [event_id, source_commit]
additionalProperties: false
properties:
event_id: {type: string, pattern: '^[A-Za-z0-9][A-Za-z0-9_.:-]{2,255}$'}
source_commit: {type: string, pattern: '^[0-9a-f]{40}$'}
SignedReleasePlanApproval:
type: object
required: [release_plan_id, candidate_id, source_commit, tree_oid, artifact_sha256, manifest_sha256, deployment_plan_sha256, release_plan_sha256, policy_sha256, approval_plan_id, evidence_closure_oid, owner_approval_record_oid, capabilities, owner_identity, trust_domain, issued_at, not_before, expires_at, nonce, key_id, signature]
properties:
release_plan_id: {type: string}
candidate_id: {type: string}
source_commit: {type: string, pattern: '^[0-9a-f]{40}$'}
tree_oid: {type: string, pattern: '^[0-9a-f]{40}$'}
artifact_sha256: {type: string, pattern: '^[0-9a-f]{64}$'}
manifest_sha256: {type: string, pattern: '^[0-9a-f]{64}$'}
deployment_plan_sha256: {type: string, pattern: '^[0-9a-f]{64}$'}
release_plan_sha256: {type: string, pattern: '^[0-9a-f]{64}$'}
policy_sha256: {type: string, pattern: '^[0-9a-f]{64}$'}
approval_plan_id: {type: string}
evidence_closure_oid: {type: string, pattern: '^[0-9a-f]{40}$'}
owner_approval_record_oid: {type: string, pattern: '^[0-9a-f]{40}$'}
owner_identity: {type: string}
trust_domain: {type: string}
issued_at: {type: string, format: date-time}
not_before: {type: string, format: date-time}
capabilities:
type: array
uniqueItems: true
items:
type: string
enum: [runtime_switch, tag_publish, baseline_registry_finalize]
expires_at: {type: string, format: date-time}
nonce: {type: string, minLength: 16}
key_id: {type: string}
signature: {type: string, format: byte, writeOnly: true}
StartDeploymentRequest:
type: object
required: [candidate_id]
additionalProperties: false
properties:
candidate_id: {type: string}
paths:
/createReleaseIntent:
post:
operationId: createReleaseIntent
summary: Create an idempotent intent; all build evidence comes from the trusted builder
security: [{bearerAuth: []}]
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/ReleaseIntentInput'}
responses:
'200': {description: Intent created or idempotently returned}
'400': {description: Caller evidence or invalid intent rejected}
x-openai-isConsequential: true
/createReleaseCandidate:
post:
operationId: createReleaseCandidate
summary: Submit release intent to the trusted builder; caller evidence is rejected
security: [{bearerAuth: []}]
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/ReleaseCandidateInput'}
responses:
'200': {description: Candidate created or idempotently returned}
'400': {description: Invalid or conflicting candidate evidence}
'401': {description: Unauthorized}
x-openai-isConsequential: true
/getReleaseCandidate:
get:
operationId: getReleaseCandidate
summary: Read a durable release candidate
parameters:
- {in: query, name: candidate_id, required: true, schema: {type: string}}
responses:
'200': {description: Candidate}
'404': {description: Candidate not found}
/getReleasePlan:
get:
operationId: getReleasePlan
parameters:
- {in: query, name: candidate_id, required: true, schema: {type: string}}
responses:
'200': {description: Immutable release plan}
'404': {description: Candidate not found}
/getReleaseAcceptance:
get:
operationId: getReleaseAcceptance
parameters:
- {in: query, name: candidate_id, required: true, schema: {type: string}}
responses:
'200': {description: Builder attestation and acceptance checks}
'404': {description: Candidate not found}
/getApprovalEnvelope:
get:
operationId: getApprovalEnvelope
summary: Generate the immutable owner-signature payload
parameters:
- {in: query, name: candidate_id, required: true, schema: {type: string}}
responses:
'200': {description: Approval envelope fingerprint inputs}
'404': {description: Candidate not found}
/submitOwnerApproval:
post:
operationId: submitOwnerApproval
summary: Verify and durably store a signed owner release plan
security: [{bearerAuth: []}]
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/SignedReleasePlanApproval'}
responses:
'200': {description: Signature verified and approval stored}
'400': {description: Invalid approval}
'403': {description: Signature, key, expiry, or revocation failure}
'409': {description: Conflicting release plan}
x-openai-isConsequential: true
/submitSignedApproval:
post:
operationId: submitSignedApproval
summary: Alias for immutable signed owner approval submission
security: [{bearerAuth: []}]
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/SignedReleasePlanApproval'}
responses:
'200': {description: Signature verified and approval stored}
'403': {description: Production approval backend unavailable or signature invalid}
x-openai-isConsequential: true
/startDeployment:
post:
operationId: startDeployment
summary: Resume the durable approved Saga; caller cannot replace approval
security: [{bearerAuth: []}]
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/StartDeploymentRequest'}
responses:
'200': {description: Shadow result or deployment Saga result}
'403': {description: Execution disabled or approval invalid}
'409': {description: Saga conflict}
x-openai-isConsequential: true
/getReleaseSaga:
get:
operationId: getReleaseSaga
summary: Read durable Saga state and event journal
parameters:
- {in: query, name: candidate_id, required: true, schema: {type: string}}
responses:
'200': {description: Saga snapshot}
'404': {description: Saga not found}
/getCapabilityStatus:
get:
operationId: getCapabilityStatus
parameters:
- {in: query, name: candidate_id, required: true, schema: {type: string}}
responses:
'200': {description: Durable single-use capability records}
/getRecoveryStatus:
get:
operationId: getRecoveryStatus
responses:
'200': {description: Fail-closed recovery classification}
/api/base:
get:
operationId: getBaseline
@@ -219,6 +453,25 @@ paths:
schema: {$ref: '#/components/schemas/FileCommitRequest'}
responses: {'200': {description: File committed}, '400': {description: Secret rejected or invalid path}, '401': {description: Unauthorized}}
x-openai-isConsequential: true
/api/commit-file:
post:
operationId: commitFileAtomic
summary: Atomically write one allowlisted file, commit, push, and verify using Token Policy V2
security: [{bearerAuth: []}]
requestBody:
required: true
content:
application/json:
schema: {$ref: '#/components/schemas/AtomicFileCommitRequest'}
responses:
'200': {description: Completed or idempotently replayed}
'400': {description: Invalid canonical request}
'401': {description: Missing, invalid, expired, or revoked Token Policy V2 grant}
'403': {description: Audience, source, branch, operation, path, or policy denied}
'409': {description: Idempotency conflict or manual review required}
'413': {description: Request too large}
'503': {description: Push or remote verification unavailable; recoverable}
x-openai-isConsequential: true
/api/commit:
post:
operationId: commitChanges
+42
View File
@@ -0,0 +1,42 @@
# AIOS Task Plane
该模块是与任务发起方、Worker 类型和通知后端解耦的持久化任务控制平面。当前交付仅用于隔离开发与 Shadow 验收;默认 `execution_enabled=false`,不会修改生产、tag、Baseline 或 Registry。
## 架构
- `TaskPlaneService`:统一服务边界和 HTTP 路由。
- `TaskPlaneStore`:SQLite WAL、revision/CAS、串行事务、租约、hash-chained journal、evidence 和 durable outbox。
- `WorkerRegistry` / `LeaseManager`:身份绑定、capability、并发额度、心跳、过期探测后恢复。
- `DelegationEngine`:父子 DAG、全局深度、capability/risk/approval/budget 单调约束和幂等重放。
- `PolicyEngine`:默认关闭执行;身份、证据、结果摘要和可插拔签名验签均 fail-closed。
- `WorkerAdapter`:OpenClaw、Codex、Hermes、A2A 和 systemd Worker 的统一契约。默认实现只返回 `SHADOW_VERIFIED`,不执行外部命令。
- `DurableOutboxDispatcher`:Webhook、NATS JetStream、GPT callback、Dashboard 和 WeChat 的回执式通知接口。没有真实回执时始终保留 `NOT_DELIVERED`。
## 状态与恢复
正常路径为 `CREATED → QUEUED/CLAIMED → RUNNING → VERIFYING/COMPLETED`;失败路径使用 `FAILED_RECOVERABLE`、`FAILED_FINAL`、`CANCELLED` 或 `MANUAL_REVIEW_REQUIRED`。每次状态事件都进入同一事务中的 append-only hash chain。
租约过期不会自动重放副作用。控制平面必须先调用 Worker probe,只有明确确认原执行已停止,才可将 claim 标记为 `EXPIRED` 并把任务转为 `FAILED_RECOVERABLE`。结果必须匹配 task、claim、Worker identity、未过期 lease 和 operation digest;重复结果返回冲突。
## 安全模型
- task/worker/claim/message ID 使用 allowlist pattern,拒绝 URL 编码差异、路径分隔符和控制字符。
- HTTP body 最大 1 MB,非对象 JSON、异常 Content-Length 和非法参数返回 400/413。
- 内部异常响应不回显路径、payload 或凭据;默认 HTTP access log 不记录原始 request target。
- secret-like 字段在 durable task、event、evidence 和 outbox 写入前脱敏。
- 结果签名存在但没有配置验签器时拒绝;摘要、evidence 或 event 被篡改时验收失败。
- SQLite 文件创建为 0600;新建状态目录使用 0700。
## 验收
```bash
PYTHONPATH=active-runtime \
bash scripts/task-plane/run_task_plane_acceptance.sh
PYTHONPATH=active-runtime \
python3 scripts/task-plane/create_shadow_tasks.py \
--database reports/task-plane-shadow.sqlite3 \
--report reports/task-plane-shadow.json
```
上述脚本只使用本地 SQLite 和 Shadow adapter,不触发真实 OpenClaw、Codex、Hermes、微信或生产执行。
+314
View File
@@ -0,0 +1,314 @@
openapi: 3.0.3
info:
title: AIOS Task Plane API
version: 1.0.0
description: >
Durable control-plane APIs for task orchestration with fail-closed policy.
Every POST operation requires bearerAuth; read-only GET operations may be
exposed according to the surrounding runtime policy.
servers:
- url: /
paths:
/tasks:
get:
summary: List tasks
responses:
"200":
description: Task list
post:
security: [ { bearerAuth: [] } ]
summary: Create task
parameters:
- in: header
name: Idempotency-Key
required: false
schema:
type: string
minLength: 1
maxLength: 128
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/TaskPlan"
responses:
"201": { description: Created }
"400": { description: Bad request }
"401": { description: Unauthorized }
"403": { description: Forbidden }
"409": { description: Conflict }
"413": { description: Payload too large }
"423": { description: Execution or resource locked }
"503": { description: Authentication or service unavailable }
/tasks/{task_id}:
get:
summary: Get task
parameters:
- $ref: "#/components/parameters/TaskId"
responses:
"200": { description: OK }
"404": { description: Not found }
/tasks/{task_id}/claim:
post:
security: [ { bearerAuth: [] } ]
summary: Claim a task
parameters:
- $ref: "#/components/parameters/TaskId"
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/TaskClaimRequest"
responses:
"200": { description: Claimed }
"401": { description: Unauthorized }
"403": { description: Forbidden }
"404": { description: Not found }
"409": { description: Conflict }
"423": { description: Execution disabled }
/tasks/{task_id}/heartbeat:
post:
security: [ { bearerAuth: [] } ]
summary: Renew task claim lease
parameters: [ $ref: "#/components/parameters/TaskId" ]
responses:
"200": { description: Leased }
"400": { description: Bad request }
"401": { description: Unauthorized }
"403": { description: Forbidden }
"404": { description: Task or claim not found }
"409": { description: Claim conflict }
"413": { description: Payload too large }
"423": { description: Lease or execution locked }
"503": { description: Service unavailable }
/tasks/{task_id}/progress:
post:
security: [ { bearerAuth: [] } ]
summary: Report task progress
parameters: [ $ref: "#/components/parameters/TaskId" ]
responses:
"200": { description: Progress saved }
"400": { description: Bad request }
"401": { description: Unauthorized }
"403": { description: Forbidden }
"404": { description: Task or claim not found }
"409": { description: Claim conflict }
"413": { description: Payload too large }
"423": { description: Lease or execution locked }
"503": { description: Service unavailable }
/tasks/{task_id}/result:
post:
security: [ { bearerAuth: [] } ]
summary: Submit task result
parameters: [ $ref: "#/components/parameters/TaskId" ]
responses:
"200": { description: Result accepted }
"400": { description: Bad request }
"401": { description: Unauthorized }
"403": { description: Result policy rejected }
"404": { description: Task or claim not found }
"409": { description: Duplicate or mismatched result }
"413": { description: Payload too large }
"423": { description: Lease or execution locked }
"503": { description: Verification service unavailable }
/tasks/{task_id}/fail:
post:
security: [ { bearerAuth: [] } ]
summary: Mark task failed
parameters: [ $ref: "#/components/parameters/TaskId" ]
responses:
"200": { description: Failure recorded }
"400": { description: Bad request }
"401": { description: Unauthorized }
"403": { description: Forbidden }
"404": { description: Task or claim not found }
"409": { description: Claim conflict }
"413": { description: Payload too large }
"423": { description: Lease or execution locked }
"503": { description: Service unavailable }
/tasks/{task_id}/cancel:
post:
security: [ { bearerAuth: [] } ]
summary: Cancel task
parameters: [ $ref: "#/components/parameters/TaskId" ]
responses:
"200": { description: Cancelled }
"400": { description: Bad request }
"401": { description: Unauthorized }
"403": { description: Forbidden }
"404": { description: Task not found }
"409": { description: Revision or state conflict }
"413": { description: Payload too large }
"423": { description: Resource locked }
"503": { description: Service unavailable }
/tasks/{task_id}/delegate:
post:
security: [ { bearerAuth: [] } ]
summary: Delegate task
parameters: [ $ref: "#/components/parameters/TaskId" ]
responses:
"200": { description: Delegated }
"400": { description: Bad request }
"401": { description: Unauthorized }
"403": { description: Forbidden }
"404": { description: Parent task not found }
"409": { description: Conflict }
"413": { description: Payload too large }
"423": { description: Execution locked }
"503": { description: Service unavailable }
/workers/register:
post:
security: [ { bearerAuth: [] } ]
summary: Register worker
responses:
"201": { description: Worker registered }
"400": { description: Bad request }
"401": { description: Unauthorized }
"403": { description: Worker identity mismatch }
"409": { description: Registration conflict }
"413": { description: Payload too large }
"423": { description: Resource locked }
"503": { description: Service unavailable }
/workers/{worker_id}/heartbeat:
post:
security: [ { bearerAuth: [] } ]
summary: Worker heartbeat
parameters: [ $ref: "#/components/parameters/WorkerId" ]
responses:
"200": { description: Heartbeat accepted }
"400": { description: Bad request }
"401": { description: Unauthorized }
"403": { description: Forbidden }
"404": { description: Worker not found }
"409": { description: Worker state conflict }
"413": { description: Payload too large }
"423": { description: Worker locked }
"503": { description: Service unavailable }
/workers:
get:
summary: List workers
responses:
"200": { description: Worker list }
/workers/{worker_id}:
get:
summary: Get worker
parameters: [ $ref: "#/components/parameters/WorkerId" ]
responses:
"200": { description: Worker details }
/tasks/{task_id}/events:
get:
summary: Task events
parameters: [ $ref: "#/components/parameters/TaskId" ]
responses:
"200": { description: Event list }
/tasks/{task_id}/evidence:
get:
summary: Task evidence
parameters: [ $ref: "#/components/parameters/TaskId" ]
responses:
"200": { description: Evidence list }
/tasks/{task_id}/children:
get:
summary: Task child tasks
parameters: [ $ref: "#/components/parameters/TaskId" ]
responses:
"200": { description: Child list }
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: opaque
parameters:
TaskId:
name: task_id
in: path
required: true
schema:
type: string
minLength: 1
maxLength: 128
pattern: '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'
WorkerId:
name: worker_id
in: path
required: true
schema:
type: string
minLength: 1
maxLength: 128
pattern: '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'
schemas:
TaskPlan:
type: object
additionalProperties: false
properties:
schema_version: { type: integer, enum: [1] }
task_id:
type: string
minLength: 1
maxLength: 128
pattern: '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'
task_type:
type: string
minLength: 1
maxLength: 128
pattern: '^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$'
issuer: { type: object }
intent: { type: string, maxLength: 16384 }
required_capabilities:
type: array
maxItems: 128
items: { type: string }
preferred_workers:
type: array
maxItems: 64
items: { type: string }
risk: { type: string, enum: [low, medium, high] }
approval_policy:
type: string
maxLength: 128
dependencies:
type: array
maxItems: 128
items: { type: string }
max_delegation_depth: { type: integer, minimum: 0, maximum: 32 }
budgets:
type: object
additionalProperties: false
properties:
time_seconds: { type: integer, minimum: 0 }
attempts: { type: integer, minimum: 0 }
tokens: { type: integer, minimum: 0 }
cost: { type: number, minimum: 0 }
evidence_requirements:
type: array
maxItems: 128
items: { type: string }
parent_task_id:
type: string
maxLength: 128
correlation_id:
type: string
maxLength: 128
metadata:
type: object
TaskClaimRequest:
type: object
additionalProperties: false
required: [worker_id, actor_identity]
properties:
worker_id:
type: string
maxLength: 128
pattern: '^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$'
lease_seconds:
type: integer
minimum: 1
maximum: 3600
actor_identity:
type: string
minLength: 1
maxLength: 512
@@ -0,0 +1,75 @@
{
"evidence_type": "TWO_STAGE_OWNER_CAMPAIGN_V2_PHASE_A_CANDIDATE",
"generated_at": "2026-07-30T02:35:51Z",
"execution_status": "PARTIALLY_COMPLETED",
"blocking_layer": "PHASE_A_OWNER_EXACT_HEAD_APPROVAL",
"repository": "aios/aios",
"governance_base_oid": "ebd74398743e36523b593fcc395b73945d4924c5",
"governance_tree_oid": "ad1255a1127204fad1d1208d8f31dc94fa64e6e1",
"authority_main_oid": "875d41c3101093149414292067fd700dc214d4d3",
"schema_seed_commit_oid": "8da47545a507cb798d3d6ab30ff8552a2d0a5ecf",
"schema_seed_tree_oid": "991e68577a469657c1c4996814b6634ce5ed752f",
"candidate_source_commit_oid": "a637a9dbb6e0021cb7dcf0d6b0ca00dfe10dce38",
"candidate_source_tree_oid": "bf6e0c16fcad7515465bec4aefdd2e77b1e9bef0",
"candidate_diff_sha256": "c06d002db06da6cbf16f9c8321f1acbfd431f009b5127a13fd2cb5d63b278867",
"candidate_files_sha256": "5061402d516c0e9413208ce64e2b52f77d224e6eec1f7b07c5b3a10b58df5eaf",
"candidate_file_count": 44,
"schema_document_path": "services/aios-single-owner-governance/task-scoped-governance-bootstrap-v1.schema.json",
"schema_document_sha256": "6ec912a96c8bac5c0d3c0a017abc078cb9567ed54dcb20bb0eadb5926ac54fdd",
"validator_policy_path": "services/aios-single-owner-governance/aios_single_owner_governance/task_scoped_bootstrap.py",
"validator_policy_sha256": "de11123b5cd9ccdad4961f1d5d084a8c4a1b2270ce793652ab9c50bb95694eac",
"two_stage_policy_schema_sha256": "6b4def7c1eef19c01ef73c43252eb0cd1a87c103e1047249a3cfcc75c7922bf6",
"two_stage_release_schema_sha256": "82ee589dc87db6998ddf0779ce264194cadea0deca66088b0797378f6ce89ad3",
"runtime_observer_sha256": "181e632192f1417a98c1efb35631cf80246759080b2e04243576abcdc7da22af",
"journal_anchor_sha256": "18feb0792b95f1060aa696cc60943379695a246984aecf76098838f9ed37426e",
"package_zip_sha256": "668239a549b4a2b2094fe9b5104b84703fe8ed75011fd8ed71ed3958a6dc0706",
"candidate_bundle_sha256": "15711b225b9ef41cca0588d21b2b95d7a95ac155efc27fcb8772c435fcdab147",
"anchor_install_wheel_sha256": "abbf616366eb3467d4e651bb5306e22ae5e931b613df5362a0892935c4a1c0c7",
"tests": {
"two_stage_campaign": {"passed": 67, "failed": 0, "skipped": 0, "collection_errors": 0},
"single_owner_governance": {"passed": 105, "failed": 0, "subtests_passed": 17, "collection_errors": 0},
"task_scoped_bootstrap": {"passed": 15, "failed": 0, "collection_errors": 0},
"compileall": "PASS",
"quarantine_fsck": "PASS",
"systemd_verify": "PASS_WITH_UNRELATED_HOST_WARNINGS"
},
"anchor": {
"service_status": "active",
"anchor_uid": 988,
"runner_uid": 986,
"uids_distinct": true,
"runner_database_write": "DENIED",
"unauthorized_socket_peer": "DENIED",
"runner_registration_action": "ANCHOR_REQUEST_INVALID",
"journal_mode": "WAL",
"synchronous": "FULL",
"foreign_keys": "ON",
"restart_open_reservation": "PASS",
"selftest_open_reservation_cleanup": "ABORTED"
},
"phase_separation": {
"required_owner_decisions": 2,
"phase_a_authorization": "GITEA_EXACT_HEAD_REVIEW",
"phase_b_authorization": "DETACHED_SSH_SIGNATURE",
"phase_a_release_authority": false,
"one_shot_authority": false
},
"isolation": {
"AIOS_GITOPS_WRITES_ENABLED": false,
"publisher_created": false,
"writer_created": false,
"credential_projection_created": false,
"target_branch_created": false,
"tag_created": false,
"registry_modified": false,
"baseline_modified": false,
"production_modified": false
},
"governance": {
"force_push_used": false,
"tag_rewritten": false,
"direct_main_push": false,
"secrets_committed": false,
"owner_approval_respected": true
}
}
@@ -0,0 +1,44 @@
1f4a83cf91e9aacb6f50bc4fb24d4f745a7a31fd1dd7f2d3d3ed0de05e38234f deploy/systemd/aios-campaign-anchor.service
6b2525d31b8544488bce1544deb39a087cc2d830fb1b16fda26cde6ce430dbd2 deploy/systemd/aios-policy-activation-verification@.service
fad52587775ff5f1c5cc16862c5fb1d910bb097c6b3330c00d75db4f7bef0bbd deploy/systemd/aios-policy-campaign-register@.service
de11123b5cd9ccdad4961f1d5d084a8c4a1b2270ce793652ab9c50bb95694eac services/aios-single-owner-governance/aios_single_owner_governance/task_scoped_bootstrap.py
6ec912a96c8bac5c0d3c0a017abc078cb9567ed54dcb20bb0eadb5926ac54fdd services/aios-single-owner-governance/task-scoped-governance-bootstrap-v1.schema.json
5b6d65ae34b2c3bebc1bde8f38c6b0bb73269336d7d4ee5e3416f79c323134d6 services/aios-two-stage-owner-campaign/ANCHOR_REGISTRATION_CONTRACT.md
8ba5278f96447bf4308813b49fcc433d0cc7a5019cd9e78aeb0d8ada5f00d53b services/aios-two-stage-owner-campaign/README.md
7285527358bc8da1d4bc7ff70162dfc3a734086851b734daac89d6f9b330798d services/aios-two-stage-owner-campaign/SECURITY_DELTA_V2.md
89f03fdf156d35807bf39f2f1dbf304c01e7e9ad64399276fb72c68d795eda65 services/aios-two-stage-owner-campaign/STEP_EXECUTOR_CONTRACT.md
7453177223b62509e4854051a75d53f24d8d1f737986f309cc6ae7a2e721459f services/aios-two-stage-owner-campaign/TWO_STAGE_OWNER_INTERACTION_SPEC.md
9308ee245a28a0f0ff623fbfc8e3961204b8e67194bffe536a9c3485ca126ac7 services/aios-two-stage-owner-campaign/examples/allowed_signers.example
9ac7b7fdeb73a99d4f41bf3410ef16f67091d85f9082075287d388e6e5e37eed services/aios-two-stage-owner-campaign/examples/campaign-anchor.env.example
926f0c0cfb7bd6db0ebab6bcb5a0530057b8d38ae1e0b436a69a12b44039462b services/aios-two-stage-owner-campaign/examples/executor-release.example.json
8d46ec9279eb58c03337d41b63de2abdd9eb0d03f98f0806768dcd309d481ee9 services/aios-two-stage-owner-campaign/examples/policy-activation-verification.example.json
b34d8895fe872ef27c62c759449757def698eb05157daff8badd0c86f64ee18c services/aios-two-stage-owner-campaign/examples/release-campaign-manifest.example.json
a2992f493cf0547e1f4acd3fcd9b20e03c912d8b255e196f640d47e291ec9d8e services/aios-two-stage-owner-campaign/examples/runtime-observer-policy.example.json
150c9a8debdcaa4dbb9fd51817ca8710ef805a32f2f96dc5490d0c4f77f2cb6f services/aios-two-stage-owner-campaign/examples/runtime-observer-release.example.json
675b39d13223ad281a4d14a04e739639e470e4e2a2591b85264ba37f840f2b49 services/aios-two-stage-owner-campaign/payload/scripts/generate_diff_chunks.py
5751764275e626c787dad17c2561edd86b2710ae0b3cfeafeec44d98126d9015 services/aios-two-stage-owner-campaign/payload/services/aios-autonomous-control-plane/aios_autonomous_control_plane/lineage_audit.py
2b501bf32a22e1b10c7254d2268d11aa2424aac5756c52441557c25dbaaaa8d5 services/aios-two-stage-owner-campaign/payload/tests/autonomous_control_plane/test_lineage_audit_v3.py
3f118bc7a4f2bdfdd47e2c86112e87f95162dba4d88c1579337bd1d442ec9832 services/aios-two-stage-owner-campaign/pyproject.toml
6b4def7c1eef19c01ef73c43252eb0cd1a87c103e1047249a3cfcc75c7922bf6 services/aios-two-stage-owner-campaign/schemas/governance-policy-activation-verification-v1.schema.json
82ee589dc87db6998ddf0779ce264194cadea0deca66088b0797378f6ce89ad3 services/aios-two-stage-owner-campaign/schemas/task-scoped-release-campaign-v1.schema.json
d04543b5407dcefc0a41af1b8a65ebc2e62f779582ae959449f1ac37b305eecf services/aios-two-stage-owner-campaign/src/aios_two_stage_campaign/__init__.py
935a1c1166b0c1ea35a82256345000bf2c73ded718d77773bc27a71ecce28f7d services/aios-two-stage-owner-campaign/src/aios_two_stage_campaign/__main__.py
4e317e2fbf912662bfbf57d2f7fe1a9f7fd3d8c00f7ad09602d9e885ee4ef3cc services/aios-two-stage-owner-campaign/src/aios_two_stage_campaign/canonical.py
863be28723553e5bf5df90eda49e96edf8417e765da7b41579f050b04336d480 services/aios-two-stage-owner-campaign/src/aios_two_stage_campaign/cli.py
dae1e0ed0f841d928a1dd5094c64b1f39b3213301c1da747f45d032a18833c1e services/aios-two-stage-owner-campaign/src/aios_two_stage_campaign/errors.py
baf8931566d2c2fb2985d1814b5ded89719bb4b0eba01e94cca9f3690baadeb2 services/aios-two-stage-owner-campaign/src/aios_two_stage_campaign/executor.py
18feb0792b95f1060aa696cc60943379695a246984aecf76098838f9ed37426e services/aios-two-stage-owner-campaign/src/aios_two_stage_campaign/journal_anchor.py
e24765d3c62f9934a921f099e43a002d349a6c9934438da82040e40238070c58 services/aios-two-stage-owner-campaign/src/aios_two_stage_campaign/orchestrator.py
181e632192f1417a98c1efb35631cf80246759080b2e04243576abcdc7da22af services/aios-two-stage-owner-campaign/src/aios_two_stage_campaign/runtime_observer.py
3f3ce81490b6f0893665ba296fc23be904ae0e5dcbcd3b602e25feb2571262b0 services/aios-two-stage-owner-campaign/src/aios_two_stage_campaign/signature.py
c71d85d5df2ed0e5367de9aacd230f586e77f3b4c11cf52960c84bbdeec43eb7 services/aios-two-stage-owner-campaign/src/aios_two_stage_campaign/state_machine.py
75ea8ed1c9b49b1ca3ce452fe229524ebd0ccd802945d3362dd668602ce1f98c services/aios-two-stage-owner-campaign/src/aios_two_stage_campaign/validator.py
b1be7e1ed0fa0e16e041f77200d651cf46f6bb029c89d31f0b286d32c026cb66 services/aios-two-stage-owner-campaign/tests/test_canonical_snapshot.py
0bb956135c90cf71fcb655c4d700c1f71c3442b9e9fddc376959890cdad823de services/aios-two-stage-owner-campaign/tests/test_executor_v2.py
9bb8a9f1c3bd9aaf44db3dd2f5b810eb0cb650da0e5a5f5e2ea50be46f12664d services/aios-two-stage-owner-campaign/tests/test_lineage_payload.py
681c14ddc0be90f94fd3c3d8a6768fe602ec8acd130ed00a8668505d2db57aa2 services/aios-two-stage-owner-campaign/tests/test_orchestrator_v2.py
9edf44fe0129098c31d66f6b6f7ab252879b425e2844969d09e22c3c88e6b77d services/aios-two-stage-owner-campaign/tests/test_runtime_observer.py
204e77c760380f8e29d10fc0afa4227497109d27493245c55a851de6ff69ac86 services/aios-two-stage-owner-campaign/tests/test_signature.py
9bc5dfa237e6bf37ccc90bbc618a807892d323cdd41345717b296838b44cd22a services/aios-two-stage-owner-campaign/tests/test_state_machine.py
a9ef9c11120f480e0cde3ccbf8a816705621506657d8ac10faf7e38fb54bf367 services/aios-two-stage-owner-campaign/tests/test_validator.py
4ec67865474c6c7ebaa7d125490486e8644618b1666cbde597420e4e6fe8f1d4 tests/single_owner_governance/test_task_scoped_bootstrap.py
@@ -0,0 +1,9 @@
{
"type": "branch",
"branch": "patch/aios-agent-task-control-plane-20260724",
"base": "main",
"created_by": "agent-task-plane-writer",
"created_at": "2026-07-24T02:20:56Z",
"direct_main_push": false,
"owner_approval_required": true
}
+10
View File
@@ -0,0 +1,10 @@
{
"execution_status": "PARTIALLY_COMPLETED",
"tests_run": 19,
"failures": 0,
"errors": 0,
"skipped": 0,
"multi_agent_shadow_verified": true,
"production_side_effects": false,
"notification_delivery_status": "NOT_DELIVERED"
}
+53
View File
@@ -0,0 +1,53 @@
{
"root_task_id": "AIOS-AGENT-TASK-CONTROL-PLANE-20260724-SHADOW",
"execution_mode": "shadow_adapter",
"production_side_effects": false,
"tasks": [
{
"task_id": "AIOS-TASK-PLANE-OPENCLAW-ACCEPTANCE",
"parent_task_id": "AIOS-AGENT-TASK-CONTROL-PLANE-20260724-SHADOW",
"task_status": "COMPLETED",
"worker_result": {
"worker": "openclaw",
"task_id": "AIOS-TASK-PLANE-OPENCLAW-ACCEPTANCE",
"status": "SHADOW_VERIFIED",
"claim_id": "claim-093cbf4c-11d5-47a3-97dc-a373a8a53940",
"execution_mode": "shadow",
"production_side_effects": false
},
"result_digest": "b3a8ed4e0a60a0a9364d1eea1a6d30a403da1fb5ff79ff38a489a52227f4116e"
},
{
"task_id": "AIOS-TASK-PLANE-CODEX-REVIEW",
"parent_task_id": "AIOS-AGENT-TASK-CONTROL-PLANE-20260724-SHADOW",
"task_status": "COMPLETED",
"worker_result": {
"worker": "codex",
"task_id": "AIOS-TASK-PLANE-CODEX-REVIEW",
"status": "SHADOW_VERIFIED",
"claim_id": "claim-3cfe4031-27c7-409c-9841-cd5c0d26218d",
"execution_mode": "shadow",
"production_side_effects": false
},
"result_digest": "65fb825869b9b32d923b387796839e57b9ca7053716720861988ceb8d7e9c93a"
},
{
"task_id": "AIOS-TASK-PLANE-HERMES-ARCHITECTURE",
"parent_task_id": "AIOS-AGENT-TASK-CONTROL-PLANE-20260724-SHADOW",
"task_status": "COMPLETED",
"worker_result": {
"worker": "hermes",
"task_id": "AIOS-TASK-PLANE-HERMES-ARCHITECTURE",
"status": "SHADOW_VERIFIED",
"claim_id": "claim-fd7ba481-ab71-47dc-8e91-3655cbef1c40",
"execution_mode": "shadow",
"production_side_effects": false
},
"result_digest": "09c9ce563424b6490b7be1e854d06de786bd243a48b099137a7a2e9cb7035265"
}
],
"event_chains_valid": true,
"notification_delivery_status": [
"NOT_DELIVERED"
]
}
+1
View File
@@ -0,0 +1 @@
{"schema_version":1,"task_id":"AIOS-AGENT-TASK-CONTROL-PLANE-20260724","writer_status":"READY","production_side_effects":false}
+268
View File
@@ -0,0 +1,268 @@
#!/usr/bin/env python3
"""Build a deterministic, secret-scanned Silent Release evidence package."""
from __future__ import annotations
import argparse
import hashlib
import json
import os
import re
import shutil
import subprocess
import tarfile
from pathlib import Path
from typing import Any
OID_RE = re.compile(r"^[0-9a-f]{40}$")
AUTHORIZATION_PATTERN = re.compile(
rb"Authorization:\s*(?:Bearer|token)\s+([A-Za-z0-9._~+/=-]{16,})",
re.I,
)
SECRET_PATTERNS = (
re.compile(rb"(?:ghp|glpat)_[A-Za-z0-9_-]{20,}", re.I),
re.compile(
rb"-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----\r?\n"
rb"[A-Za-z0-9+/=\r\n]{64,}\r?\n"
rb"-----END (?:RSA |EC |OPENSSH )?PRIVATE KEY-----"
),
)
ALLOWED_AUTHORIZATION_PLACEHOLDER_SHA256 = frozenset(
{
"009f7191815ec615d09d0698457d048432e9a73f62ab4f26af1875009d0afbbc",
"599a7f359d1e11124054f8afeae201f2d265b8988d91cb5b7d4dc4c9e2225c30",
"f39dac6cbaba535e2c207cd0cd8f154974223c848f727f98b3564cea569b41cf",
}
)
TARGET_TESTS = (
"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",
)
def sha256(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as source:
for chunk in iter(lambda: source.read(1024 * 1024), b""):
digest.update(chunk)
return digest.hexdigest()
def atomic_json(path: Path, payload: dict[str, Any]) -> None:
temporary = path.with_suffix(path.suffix + ".tmp")
with temporary.open("w", encoding="utf-8", newline="\n") as output:
json.dump(payload, output, sort_keys=True, separators=(",", ":"))
output.write("\n")
output.flush()
os.fsync(output.fileno())
os.replace(temporary, path)
def redact_output(value: str) -> str:
value = re.sub(
r"(?i)(Authorization:\s*(?:Bearer|token)\s+)"
r"[A-Za-z0-9._~+/=-]{8,}",
r"\1<redacted>",
str(value),
)
value = re.sub(
r"-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----.*?"
r"-----END (?:RSA |EC |OPENSSH )?PRIVATE KEY-----",
"<redacted-private-key>",
value,
flags=re.DOTALL,
)
return value
def git(git_dir: Path, *arguments: str) -> str:
completed = subprocess.run(
["git", f"--git-dir={git_dir}", *arguments],
check=True,
capture_output=True,
text=True,
timeout=60,
env={"PATH": os.environ.get("PATH", "")},
)
return completed.stdout.strip()
def run_evidence(
command: list[str],
*,
cwd: Path,
output: Path,
timeout: int,
) -> dict[str, Any]:
completed = subprocess.run(
command,
cwd=cwd,
capture_output=True,
text=True,
timeout=timeout,
env={
"HOME": str(cwd / ".builder-home"),
"PATH": os.environ.get("PATH", ""),
"PYTHONDONTWRITEBYTECODE": "1",
},
)
record = {
"command": command,
"exit_code": completed.returncode,
"stderr": redact_output(completed.stderr[-20000:]),
"stdout": redact_output(completed.stdout[-20000:]),
}
atomic_json(output, record)
if completed.returncode != 0:
raise RuntimeError(f"trusted builder command failed: {command[0]}")
return record
def secret_scan(root: Path) -> dict[str, Any]:
findings: list[str] = []
for path in sorted(item for item in root.rglob("*") if item.is_file()):
if path.stat().st_size > 5 * 1024 * 1024:
continue
data = path.read_bytes()
authorization_tokens = AUTHORIZATION_PATTERN.findall(data)
unsafe_authorization = any(
hashlib.sha256(token.lower()).hexdigest()
not in ALLOWED_AUTHORIZATION_PLACEHOLDER_SHA256
for token in authorization_tokens
)
if unsafe_authorization or any(
pattern.search(data) for pattern in SECRET_PATTERNS
):
findings.append(path.relative_to(root).as_posix())
return {"findings": findings, "passed": not findings}
def build(git_dir: Path, commit: str, output: Path) -> dict[str, Any]:
commit = git(git_dir, "rev-parse", f"{commit}^{{commit}}")
if not OID_RE.fullmatch(commit):
raise ValueError("commit must resolve to a full OID")
tree_oid = git(git_dir, "rev-parse", f"{commit}^{{tree}}")
if output.exists():
raise FileExistsError("artifact output already exists")
output.mkdir(parents=True, mode=0o700)
source_tar = output / "source.tar"
subprocess.run(
[
"git",
f"--git-dir={git_dir}",
"archive",
"--format=tar",
f"--output={source_tar}",
commit,
],
check=True,
timeout=120,
env={"PATH": os.environ.get("PATH", "")},
)
canary = output / "canary-src"
canary.mkdir(mode=0o700)
with tarfile.open(source_tar, "r") as archive:
archive.extractall(canary, filter="data")
if any(path.name == ".git" for path in canary.rglob(".git")):
raise RuntimeError("no-.git canary contains Git metadata")
test_record = run_evidence(
["python3", "-m", "pytest", "-q", *TARGET_TESTS],
cwd=canary,
output=output / "test-evidence.json",
timeout=600,
)
fixture_record = run_evidence(
["bash", "tests/run_live_gitea_fixture.sh"],
cwd=canary,
output=output / "live-fixture-evidence.json",
timeout=600,
)
canary_record = {
"git_directory_present": False,
"live_fixture_exit_code": fixture_record["exit_code"],
"passed": True,
"target_test_exit_code": test_record["exit_code"],
}
atomic_json(output / "canary-evidence.json", canary_record)
scan = secret_scan(canary)
atomic_json(output / "secret-scan.json", scan)
if not scan["passed"]:
raise RuntimeError("secret scan failed")
manifest_files: list[dict[str, Any]] = []
tracked = git(git_dir, "ls-tree", "-r", "--name-only", commit).splitlines()
for relative in tracked:
path = canary / relative
if path.is_file():
manifest_files.append(
{
"path": relative,
"sha256": sha256(path),
"size": path.stat().st_size,
}
)
manifest = {
"commit": commit,
"file_count": len(manifest_files),
"files": manifest_files,
"tree_oid": tree_oid,
}
atomic_json(output / "manifest.json", manifest)
shutil.copyfile(source_tar, output / "rollback-source.tar")
closure = {
"artifact_sha256": sha256(source_tar),
"canary_evidence_sha256": sha256(output / "canary-evidence.json"),
"commit": commit,
"live_fixture_evidence_sha256": sha256(
output / "live-fixture-evidence.json"
),
"manifest_sha256": sha256(output / "manifest.json"),
"no_git_canary": True,
"rollback_artifact_sha256": sha256(
output / "rollback-source.tar"
),
"secret_scan_sha256": sha256(output / "secret-scan.json"),
"test_evidence_sha256": sha256(output / "test-evidence.json"),
"tree_oid": tree_oid,
}
atomic_json(output / "package-closure.json", closure)
closure["package_closure_sha256"] = sha256(
output / "package-closure.json"
)
checksummed = sorted(
path
for path in output.iterdir()
if path.is_file() and path.name != "checksums.sha256"
)
with (output / "checksums.sha256").open(
"w", encoding="utf-8", newline="\n"
) as checksums:
for path in checksummed:
checksums.write(f"{sha256(path)} {path.name}\n")
return closure
def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--git-dir", required=True, type=Path)
parser.add_argument("--commit", required=True)
parser.add_argument("--output", required=True, type=Path)
arguments = parser.parse_args()
result = build(
arguments.git_dir.resolve(),
arguments.commit,
arguments.output.resolve(),
)
print(json.dumps(result, sort_keys=True))
if __name__ == "__main__":
main()
@@ -0,0 +1,64 @@
#!/usr/bin/env python3
"""Deterministic, local-only fault injection for Task Plane recovery."""
from __future__ import annotations
import argparse
import json
import time
from unittest import mock
from app.task_plane.api import TaskPlaneService
from app.task_plane.policy import PolicyEngine
from app.task_plane.store import TaskPlaneStore
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--database", required=True)
args = parser.parse_args()
store = TaskPlaneStore(args.database)
service = TaskPlaneService(
store, policy=PolicyEngine(execution_enabled=True)
)
identity = "spiffe://aios/agent/codex/fault-injection"
service.register_worker(
{
"worker_id": "codex-fault-injection",
"worker_type": "codex",
"identity": identity,
"capabilities": ["test.execute"],
"trust_zone": "isolated-shadow",
"max_concurrency": 1,
"heartbeat_interval_seconds": 1,
"supported_protocols": ["aios-task-v1"],
}
)
task = service.create_task(
{"task_id": "task-fault-expired-lease", "required_capabilities": ["test.execute"]}
)
claim = service.claim(
task["task_id"],
{
"worker_id": "codex-fault-injection",
"actor_identity": identity,
"lease_seconds": 1,
},
)
with mock.patch("app.task_plane.store._now", return_value=time.time() + 5):
recovered = store.recover_expired_claim(
task["task_id"], claim["claim_id"], probe_confirms_stopped=True
)
output = {
"fault": "expired_lease",
"recovered_status": recovered.status,
"event_chain_valid": store.verify_event_chain(task["task_id"]),
"production_side_effects": False,
}
print(json.dumps(output, sort_keys=True))
store.close()
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,470 @@
#!/usr/bin/env python3
"""Run two zero-side-effect Silent Release candidates in Shadow mode."""
from __future__ import annotations
import argparse
import dataclasses
import hashlib
import json
import sys
from datetime import datetime, timedelta, timezone
from pathlib import Path
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import (
Ed25519PrivateKey,
)
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "active-runtime" / "app"))
from silent_release_controller import ReleaseCandidate, ReleaseState # noqa: E402
from silent_release_execution import ( # noqa: E402
LocalTestApprovalSigner,
LocalTestBuilderSigner,
PersistentSilentReleaseController,
SignedTrustedBuilderAdapter,
)
from silent_release_hardening import ( # noqa: E402
ApprovalKeyRing,
ApprovalRevocationStore,
BuilderAttestationVerifier,
DeploymentPlan,
FinalizationPlan,
ReleasePlan,
SignedApprovalVerifier,
SignedReleasePlanApproval,
SilentReleaseActionService,
TagPlan,
TagPublicationProbeEvidence,
TrustedCandidateBundle,
TrustedReleasePolicy,
classify_tag_publication,
)
def sha256_file(path: Path) -> str:
digest = hashlib.sha256()
with path.open("rb") as source:
for block in iter(lambda: source.read(1024 * 1024), b""):
digest.update(block)
return digest.hexdigest()
def write_json(path: Path, value: object) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
temporary = path.with_suffix(path.suffix + ".tmp")
temporary.write_text(
json.dumps(value, indent=2, sort_keys=True) + "\n",
encoding="utf-8",
)
temporary.replace(path)
def oid(seed: str) -> str:
return hashlib.sha1(seed.encode("utf-8")).hexdigest()
def candidate_id(event_id: str, source_commit: str) -> str:
return "rc-shadow-" + hashlib.sha256(
f"{event_id}\0{source_commit}".encode("utf-8")
).hexdigest()[:20]
def make_plan(arguments: argparse.Namespace, candidate: str) -> ReleasePlan:
version = "shadow-" + candidate.removeprefix("rc-shadow-")
tag_oid = oid("tag:" + candidate)
approval_plan_id = "shadow-plan-" + candidate.removeprefix("rc-shadow-")
release_path = str(Path(arguments.release_path))
previous_baseline = arguments.baseline_before_sha256
return ReleasePlan(
deployment=DeploymentPlan(
service="aios-silent-release-controller",
release_path=release_path,
release_root=str(Path(release_path).parent.parent),
cutover_link=str(Path(release_path).parent.parent / "current"),
dropin_path=str(
Path(release_path)
/ "deploy"
/ "systemd"
/ "aios-silent-release-controller.service"
),
dropin_sha256=arguments.systemd_unit_sha256,
systemd_unit_sha256=arguments.systemd_unit_sha256,
rollback_path=str(Path(arguments.rollback_artifact)),
rollback_sha256=arguments.artifact_sha256,
source_commit=arguments.source_commit,
tree_oid=arguments.tree_oid,
artifact_sha256=arguments.artifact_sha256,
manifest_sha256=arguments.manifest_sha256,
package_closure_sha256=arguments.package_closure_sha256,
authoritative_git_dir=str(Path(arguments.repository)),
repository_identity=arguments.repository_identity,
writer_expected=False,
baseline_before_sha256=previous_baseline,
registry_before_sha256=arguments.registry_before_sha256,
production_tag_refset_before_sha256=(
arguments.tag_refset_before_sha256
),
health_contract_version="shadow-health/v1",
timeout_policy={"health_seconds": 10},
retry_policy={"health_attempts": 1},
policy_sha256=arguments.policy_sha256,
health_contract={
"execution_enabled": False,
"shadow_mode": True,
"side_effects_executed": False,
},
),
tag=TagPlan(
version=version,
tag_type="annotated",
expected_tag_oid=tag_oid,
expected_target_oid=arguments.source_commit,
evidence_closure_oid=arguments.source_commit,
approval_plan_id=approval_plan_id,
repository_identity=arguments.repository_identity,
gitea_owner="aios",
gitea_repository="aios",
current_remote_tag_oid=None,
remote_absent_evidence_sha256=hashlib.sha256(
f"shadow-only:{candidate}".encode("utf-8")
).hexdigest(),
verification_contract="shadow-no-tag-write/v1",
),
finalization=FinalizationPlan(
version=version,
expected_tag_oid=tag_oid,
expected_target_oid=arguments.source_commit,
expected_previous_baseline_sha256=previous_baseline,
registry_expected_record={
"shadow_only": True,
"target_oid": arguments.source_commit,
},
baseline_expected_record={
"shadow_only": True,
"target_oid": arguments.source_commit,
},
final_consistency_contract="shadow-no-finalization/v1",
),
policy_sha256=arguments.policy_sha256,
)
def make_candidate(
arguments: argparse.Namespace, event_id: str
) -> ReleaseCandidate:
return ReleaseCandidate(
candidate_id=candidate_id(event_id, arguments.source_commit),
source_commit=arguments.source_commit,
tree_oid=arguments.tree_oid,
artifact_sha256=arguments.artifact_sha256,
manifest_sha256=arguments.manifest_sha256,
tests={"passed": True, "source": "trusted-artifact-evidence"},
canary={"passed": True, "without_git": True},
rollback_ready=True,
git_consistency={"consistent": True},
gitea_consistency={"consistent": True},
approval_state="PENDING",
state=ReleaseState.READY_FOR_APPROVAL,
)
def sign_approval(
service: SilentReleaseActionService,
signer: LocalTestApprovalSigner,
candidate: str,
nonce_suffix: str,
) -> SignedReleasePlanApproval:
release_candidate = service.candidates.load(candidate)
plan = service.release_plans.load(candidate)
now = datetime.now(timezone.utc)
unsigned = SignedReleasePlanApproval(
release_plan_id="shadow-release-" + candidate.removeprefix("rc-"),
candidate_id=candidate,
source_commit=release_candidate.source_commit,
tree_oid=release_candidate.tree_oid,
artifact_sha256=release_candidate.artifact_sha256,
manifest_sha256=release_candidate.manifest_sha256,
deployment_plan_sha256=plan.deployment.sha256,
release_plan_sha256=plan.sha256,
policy_sha256=plan.policy_sha256,
approval_plan_id=plan.tag.approval_plan_id,
evidence_closure_oid=plan.tag.evidence_closure_oid,
owner_approval_record_oid=oid("shadow-owner:" + candidate),
capabilities=(
"runtime_switch",
"tag_publish",
"baseline_registry_finalize",
),
owner_identity="owner:shadow-acceptance-test",
trust_domain="aios.shadow",
issued_at=now.isoformat().replace("+00:00", "Z"),
not_before=now.isoformat().replace("+00:00", "Z"),
expires_at=(now + timedelta(minutes=10))
.isoformat()
.replace("+00:00", "Z"),
nonce="shadow-nonce-" + nonce_suffix,
key_id=signer.key_id,
signature="",
)
return signer.sign(unsigned)
def tag_probe_results(plan: TagPlan) -> dict[str, str]:
exact = TagPublicationProbeEvidence(
repository_identity=plan.repository_identity,
remote_reachable=True,
gitea_api_reachable=True,
gitea_listing_reachable=True,
remote_tag_oid=plan.expected_tag_oid,
annotated_tag_object_oid=plan.expected_tag_oid,
remote_peeled_target_oid=plan.expected_target_oid,
gitea_api_tag_oid=plan.expected_tag_oid,
gitea_api_peeled_target_oid=plan.expected_target_oid,
gitea_listing_visible=True,
gitea_listing_tag_oid=plan.expected_tag_oid,
gitea_listing_peeled_target_oid=plan.expected_target_oid,
observed_at=1,
)
absent = dataclasses.replace(
exact,
remote_tag_oid=None,
annotated_tag_object_oid=None,
remote_peeled_target_oid=None,
gitea_api_tag_oid=None,
gitea_api_peeled_target_oid=None,
gitea_listing_visible=False,
gitea_listing_tag_oid=None,
gitea_listing_peeled_target_oid=None,
)
indeterminate = dataclasses.replace(exact, gitea_api_reachable=False)
conflict = dataclasses.replace(exact, remote_tag_oid=oid("conflict"))
return {
"APPLIED_VERIFIED": classify_tag_publication(exact, plan).value,
"CONFLICT": classify_tag_publication(conflict, plan).value,
"INDETERMINATE": classify_tag_publication(
indeterminate, plan
).value,
"NOT_APPLIED": classify_tag_publication(absent, plan).value,
}
def parser() -> argparse.ArgumentParser:
value = argparse.ArgumentParser()
value.add_argument("--state-root", required=True, type=Path)
value.add_argument("--repository", required=True, type=Path)
value.add_argument("--release-path", required=True, type=Path)
value.add_argument("--rollback-artifact", required=True, type=Path)
value.add_argument("--source-commit", required=True)
value.add_argument("--tree-oid", required=True)
value.add_argument("--artifact-sha256", required=True)
value.add_argument("--manifest-sha256", required=True)
value.add_argument("--package-closure-sha256", required=True)
value.add_argument("--policy-sha256", required=True)
value.add_argument("--systemd-unit-sha256", required=True)
value.add_argument("--baseline-before-sha256", required=True)
value.add_argument("--registry-before-sha256", required=True)
value.add_argument("--tag-refset-before-sha256", required=True)
value.add_argument("--repository-identity", default="aios/aios")
value.add_argument("--output", required=True, type=Path)
return value
def main() -> None:
arguments = parser().parse_args()
if sha256_file(arguments.rollback_artifact) != arguments.artifact_sha256:
raise SystemExit("rollback artifact hash mismatch")
unit_path = (
arguments.release_path
/ "deploy"
/ "systemd"
/ "aios-silent-release-controller.service"
)
if sha256_file(unit_path) != arguments.systemd_unit_sha256:
raise SystemExit("systemd unit hash mismatch")
builder_signer = LocalTestBuilderSigner.generate()
builder_public = builder_signer.public_key.public_bytes(
serialization.Encoding.Raw,
serialization.PublicFormat.Raw,
)
owner_private = Ed25519PrivateKey.generate()
owner_public = owner_private.public_key().public_bytes(
serialization.Encoding.Raw,
serialization.PublicFormat.Raw,
)
owner_signer = LocalTestApprovalSigner(
"shadow-owner-ephemeral", owner_private
)
def collector(intent):
event_id = str(intent["event_id"])
candidate = make_candidate(arguments, event_id)
return TrustedCandidateBundle(
candidate=candidate,
release_plan=make_plan(arguments, candidate.candidate_id),
checks={
"authoritative_git": True,
"artifact": True,
"canary": True,
"gitea": True,
"manifest": True,
"package_closure": True,
"rollback": True,
"secret_scan": True,
"tests": True,
},
)
release_root = str(arguments.release_path.parent.parent)
dropin = str(
arguments.release_path
/ "deploy"
/ "systemd"
/ "aios-silent-release-controller.service"
)
policy = TrustedReleasePolicy(
services=frozenset({"aios-silent-release-controller"}),
release_roots=frozenset({release_root}),
cutover_links=frozenset({str(Path(release_root) / "current")}),
dropin_paths=frozenset({dropin}),
authoritative_git_dirs=frozenset({str(arguments.repository)}),
repository_identities=frozenset(
{arguments.repository_identity}
),
)
verifier = SignedApprovalVerifier(
ApprovalKeyRing({"shadow-owner-ephemeral": owner_public}),
ApprovalRevocationStore(arguments.state_root / "revocations"),
allowed_trust_domains={"aios.shadow"},
)
service = SilentReleaseActionService(
arguments.state_root,
execution_enabled=False,
approval_verifier=verifier,
candidate_builder=SignedTrustedBuilderAdapter(
collector, builder_signer
),
builder_attestation_verifier=BuilderAttestationVerifier(
{builder_signer.key_id: builder_public}
),
trusted_policy=policy,
expected_repository=arguments.repository_identity,
policy_sha256=arguments.policy_sha256,
)
first_event = "main-" + arguments.source_commit
first = service.create_release_intent(
{
"event_id": first_event,
"source_commit": arguments.source_commit,
}
)
first_duplicate = service.create_release_intent(
{
"event_id": first_event,
"source_commit": arguments.source_commit,
}
)
first_notification = service.ensure_owner_notification(
first["candidate_id"]
)
repeated_notification = service.ensure_owner_notification(
first["candidate_id"]
)
first_approval = sign_approval(
service, owner_signer, first["candidate_id"], "candidate-one"
)
first_submit = service.submit_owner_approval(
first_approval.to_dict()
)
first_submit_repeated = service.submit_owner_approval(
first_approval.to_dict()
)
first_start = service.start_deployment(first["candidate_id"])
second = service.create_release_intent(
{
"event_id": "shadow-indeterminate-" + arguments.source_commit,
"source_commit": arguments.source_commit,
}
)
second_plan = service.release_plans.load(second["candidate_id"])
probe_results = tag_probe_results(second_plan.tag)
first_controller = PersistentSilentReleaseController(
arguments.state_root,
service,
main_probe=lambda: arguments.source_commit,
)
controller_first = first_controller.run_once()
controller_restarted = PersistentSilentReleaseController(
arguments.state_root,
service,
main_probe=lambda: arguments.source_commit,
).run_once()
report = {
"approval_backend": "ephemeral-shadow-test-only",
"artifact_sha256": arguments.artifact_sha256,
"builder_attestation": "VERIFIED",
"candidate_count": 2,
"candidates": [
{
"candidate_id": first["candidate_id"],
"duplicate_intent_idempotent": first_duplicate["idempotent"],
"notification_idempotent": (
repeated_notification["idempotent"]
),
"notification_initial_idempotent": (
first_notification["idempotent"]
),
"replayed_approval_status": (
first_submit_repeated["status"]
),
"shadow_start": first_start,
"signed_approval_status": first_submit["status"],
},
{
"candidate_id": second["candidate_id"],
"tag_publication_probe": "INDETERMINATE",
},
],
"controller_restart_idempotent": (
controller_restarted["intent"]["idempotent"]
),
"controller_restart_notification_idempotent": (
controller_restarted["notification"]["idempotent"]
),
"controller_status": controller_first["startup"]["status"],
"execution_enabled": False,
"manifest_sha256": arguments.manifest_sha256,
"package_closure_sha256": arguments.package_closure_sha256,
"private_key_files_written": False,
"production_tag": "NOT_EXECUTED",
"repository_identity": arguments.repository_identity,
"shadow_mode": True,
"side_effects_executed": False,
"source_commit": arguments.source_commit,
"tag_probe_outcomes": probe_results,
"tree_oid": arguments.tree_oid,
}
write_json(arguments.output, report)
print(
json.dumps(
{
"candidate_count": 2,
"execution_enabled": False,
"output": str(arguments.output),
"shadow_mode": True,
"side_effects_executed": False,
"status": "SHADOW_ACCEPTANCE_PASSED",
},
sort_keys=True,
)
)
if __name__ == "__main__":
main()
+180
View File
@@ -0,0 +1,180 @@
#!/usr/bin/env python3
"""Create one authenticated, no-side-effect Task Plane Shadow task.
The client deliberately has no worker, Git, deployment, or credential-writing
capabilities. Its token is accepted only from a protected file reference or
stdin so that it cannot enter the process command line.
"""
from __future__ import annotations
import argparse
import hashlib
import json
import os
import sys
import urllib.error
import urllib.request
from pathlib import Path
from typing import Any
MINIMUM_CLIENT_CAPABILITIES = (
"task.creator",
"task.read",
"task.events.read",
"task.evidence.read",
)
class ShadowClientError(RuntimeError):
"""HTTP failure that intentionally carries no response body."""
def __init__(self, status: int):
super().__init__(f"shadow task request failed with HTTP {status}")
self.status = status
def _request_json(
base_url: str,
path: str,
token: str,
*,
method: str = "GET",
payload: dict[str, Any] | None = None,
idempotency_key: str | None = None,
) -> tuple[int, dict[str, Any]]:
headers = {
"Accept": "application/json",
"Authorization": f"Bearer {token}",
}
data = None
if payload is not None:
headers["Content-Type"] = "application/json"
data = json.dumps(payload, sort_keys=True, separators=(",", ":")).encode("utf-8")
if idempotency_key:
headers["Idempotency-Key"] = idempotency_key
request = urllib.request.Request(
base_url.rstrip("/") + path,
data=data,
headers=headers,
method=method,
)
try:
with urllib.request.urlopen(request, timeout=10) as response:
body = response.read()
parsed = json.loads(body.decode("utf-8")) if body else {}
return response.status, parsed
except urllib.error.HTTPError as error:
# Never read or print an error body: it could contain sensitive data.
raise ShadowClientError(error.code) from None
except urllib.error.URLError as error:
raise ShadowClientError(503) from error
def run_shadow_client(
*,
base_url: str,
token: str,
task_id: str,
idempotency_key: str,
) -> dict[str, Any]:
"""Create and read one deterministic Shadow task without executing it."""
create_status, _ = _request_json(
base_url,
"/tasks",
token,
method="POST",
idempotency_key=idempotency_key,
payload={
"task_id": task_id,
"task_type": "platform.shadow",
"risk": "low",
"approval_policy": "patch-only",
"metadata": {"production_side_effects": False},
},
)
task_status, task = _request_json(base_url, f"/tasks/{task_id}", token)
events_status, events = _request_json(base_url, f"/tasks/{task_id}/events", token)
evidence_status, evidence = _request_json(base_url, f"/tasks/{task_id}/evidence", token)
return {
"task_id": str(task.get("task_id", task_id)),
"status": str(task.get("status", "UNKNOWN")),
"event_count": len(events if isinstance(events, list) else events.get("events", [])),
"evidence_count": len(evidence if isinstance(evidence, list) else evidence.get("evidence", [])),
"http_statuses": {
"create": create_status,
"task": task_status,
"events": events_status,
"evidence": evidence_status,
},
"production_side_effects": False,
}
def _reject_plaintext_token_argument(argv: list[str]) -> None:
if any(argument == "--token" or argument.startswith("--token=") for argument in argv):
raise SystemExit("plaintext token arguments are forbidden")
def _read_token(args: argparse.Namespace) -> str:
if args.token_stdin:
token = sys.stdin.buffer.read().decode("utf-8").strip()
else:
token_file = args.token_file or os.environ.get("AIOS_TASK_PLANE_TOKEN_FILE")
if not token_file:
raise SystemExit("runtime credential reference is required")
try:
token = Path(token_file).read_text(encoding="utf-8").strip()
except OSError:
raise SystemExit("runtime credential reference is unavailable") from None
if not token:
raise SystemExit("runtime credential reference is unavailable")
return token
def _write_report(path: Path, summary: dict[str, Any]) -> str:
encoded = (json.dumps(summary, sort_keys=True, separators=(",", ":")) + "\n").encode("utf-8")
path.parent.mkdir(parents=True, exist_ok=True)
path.write_bytes(encoded)
return hashlib.sha256(encoded).hexdigest()
def main(argv: list[str] | None = None) -> int:
argv = list(sys.argv[1:] if argv is None else argv)
_reject_plaintext_token_argument(argv)
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--base-url", default="http://127.0.0.1:18922")
token_group = parser.add_mutually_exclusive_group()
token_group.add_argument("--token-file", help="protected runtime credential reference")
token_group.add_argument("--token-stdin", action="store_true")
parser.add_argument("--task-id", default="AIOS-TASK-PLANE-SHADOW-CLIENT")
parser.add_argument("--idempotency-key", default="task-plane-shadow-client-v1")
parser.add_argument("--report", required=True, type=Path)
args = parser.parse_args(argv)
token = _read_token(args)
try:
summary = run_shadow_client(
base_url=args.base_url,
token=token,
task_id=args.task_id,
idempotency_key=args.idempotency_key,
)
except ShadowClientError as error:
print(f"http_status={error.status}", file=sys.stderr)
return error.status
report_sha256 = _write_report(args.report, summary)
print(f"task_id={summary['task_id']}")
print(f"status={summary['status']}")
print(f"event_count={summary['event_count']}")
print(f"evidence_count={summary['evidence_count']}")
print("http_statuses=" + json.dumps(summary["http_statuses"], sort_keys=True))
print(f"report_sha256={report_sha256}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,40 @@
#!/usr/bin/env bash
set -euo pipefail
REPORT_PATH="${1:-reports/task-plane-acceptance.json}"
mkdir -p "$(dirname "${REPORT_PATH}")"
export TASKPLANE_REPORT="${REPORT_PATH}"
export PYTHONPATH="${PYTHONPATH:-}:$(pwd)/active-runtime"
python3 - <<'PY'
import io
import json
import sys
import unittest
from pathlib import Path
from contextlib import redirect_stdout
report_path = Path(__import__('os').environ.get("TASKPLANE_REPORT", "reports/task-plane-acceptance.json"))
stream = io.StringIO()
loader = unittest.TestLoader()
suite = loader.discover("tests/task_plane/public_interface", pattern="test_*.py")
runner = unittest.TextTestRunner(stream=sys.stdout, verbosity=2)
result = runner.run(suite)
payload = {
"execution_status": "PARTIALLY_COMPLETED" if result.wasSuccessful() else "FAILED",
"tests_run": result.testsRun,
"failures": len(result.failures),
"errors": len(result.errors),
"skipped": len(result.skipped),
"multi_agent_shadow_verified": result.wasSuccessful(),
"production_side_effects": False,
"notification_delivery_status": "NOT_DELIVERED",
}
report_path.parent.mkdir(parents=True, exist_ok=True)
report_path.write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8")
print(f"acceptance_report={report_path}")
sys.exit(0 if result.wasSuccessful() else 1)
PY
@@ -0,0 +1,323 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "TASK_SCOPED_GOVERNANCE_BOOTSTRAP",
"title": "TASK_SCOPED_GOVERNANCE_BOOTSTRAP observed approval envelope",
"description": "Versioned fail-closed validation envelope assembled from an immutable request plus observed Owner, Git, machine-gate, runtime, and mirror evidence.",
"x-schema-version": 1,
"x-schema-status": "CANDIDATE",
"type": "object",
"additionalProperties": false,
"required": [
"document_kind",
"request_id",
"schema_id",
"schema_version",
"request_created_at",
"request_expires_at",
"requester_identity",
"repository_identity",
"approval_plan_path",
"approval_plan_blob_oid",
"approval_plan_sha256",
"schema_document_sha256",
"validator_policy_sha256",
"required_machine_contexts",
"governance_base_ref",
"governance_base_oid",
"request_head_ref",
"request_head_oid",
"code_base_ref",
"code_base_oid",
"candidate_commit_oid",
"candidate_parent_oid",
"candidate_tree_oid",
"candidate_diff_sha256",
"candidate_files_sha256",
"target_ref",
"target_initial_state",
"provision_publisher",
"provision_writer",
"provision_credential_projection",
"publisher_identity",
"writer_identity",
"credential_secret_ref",
"publisher_scope",
"writer_scope",
"credential_scope",
"observed_preapproval_operations",
"allowed_operations",
"allowed_operations_sha256",
"denied_operations",
"maximum_operation_count",
"maximum_pr_count",
"maximum_branch_count",
"maximum_commit_count",
"allowed_paths",
"allowed_paths_sha256",
"approved_at",
"expires_at",
"maximum_lifetime_seconds",
"auto_revoke",
"required_human_approvals",
"allowed_human_approvers",
"approved_by",
"owner_review_id",
"owner_review_commit_oid",
"approval_plan_id",
"approval_audit_event_id",
"production_runtime_anchor_change",
"production_writes_enabled",
"tag_operations",
"registry_operations",
"baseline_operations",
"deployment_operations",
"force_push_allowed",
"tag_rewrite_allowed",
"direct_main_push_allowed"
],
"properties": {
"document_kind": {"const": "ENVELOPE"},
"request_id": {"type": "string", "minLength": 3, "maxLength": 192, "pattern": "^[A-Za-z0-9][A-Za-z0-9_.:-]+$"},
"schema_id": {"const": "TASK_SCOPED_GOVERNANCE_BOOTSTRAP"},
"schema_version": {"const": 1},
"request_created_at": {"type": "string", "format": "date-time", "pattern": "Z$"},
"request_expires_at": {"type": "string", "format": "date-time", "pattern": "Z$"},
"requester_identity": {"$ref": "#/$defs/identity"},
"repository_identity": {"const": "aios/aios"},
"approval_plan_path": {"type": "string", "pattern": "^approvals/governance-bootstrap/[A-Za-z0-9._-]+\\.json$"},
"approval_plan_blob_oid": {"$ref": "#/$defs/oid"},
"approval_plan_sha256": {"$ref": "#/$defs/sha256"},
"schema_document_sha256": {"$ref": "#/$defs/sha256"},
"validator_policy_sha256": {"$ref": "#/$defs/sha256"},
"required_machine_contexts": {
"type": "array",
"minItems": 5,
"maxItems": 5,
"uniqueItems": true,
"items": {"enum": [
"governance/authority-mirror",
"governance/fault-injection",
"governance/runtime-isolation",
"governance/schema-contract",
"governance/validator-security"
]}
},
"governance_base_ref": {"const": "refs/heads/governance/approvals"},
"governance_base_oid": {"$ref": "#/$defs/oid"},
"request_head_ref": {"type": "string", "pattern": "^refs/heads/governance/request/[A-Za-z0-9._/-]+$"},
"request_head_oid": {"$ref": "#/$defs/oid"},
"code_base_ref": {"type": "string", "pattern": "^refs/heads/patch/[A-Za-z0-9._/-]+$"},
"code_base_oid": {"$ref": "#/$defs/oid"},
"candidate_commit_oid": {"$ref": "#/$defs/oid"},
"candidate_parent_oid": {"$ref": "#/$defs/oid"},
"candidate_tree_oid": {"$ref": "#/$defs/oid"},
"candidate_diff_sha256": {"$ref": "#/$defs/sha256"},
"candidate_files_sha256": {"$ref": "#/$defs/sha256"},
"target_ref": {"type": "string", "pattern": "^refs/heads/patch/[A-Za-z0-9._/-]+$"},
"target_initial_state": {"const": "ABSENT"},
"provision_publisher": {"const": true},
"provision_writer": {"const": true},
"provision_credential_projection": {"const": true},
"publisher_identity": {"$ref": "#/$defs/identity"},
"writer_identity": {"$ref": "#/$defs/identity"},
"credential_secret_ref": {"type": "string", "pattern": "^(systemd-credential|secret-ref)://[A-Za-z0-9_.-]+$"},
"publisher_scope": {
"type": "object",
"additionalProperties": false,
"required": ["repository", "local_code_write", "administrator"],
"properties": {
"repository": {"const": "aios/aios"},
"local_code_write": {"const": false},
"administrator": {"const": false}
}
},
"writer_scope": {
"type": "object",
"additionalProperties": false,
"required": ["repository", "remote_credentials", "remote_push", "run_as_root"],
"properties": {
"repository": {"const": "aios/aios"},
"remote_credentials": {"const": false},
"remote_push": {"const": false},
"run_as_root": {"const": false}
}
},
"credential_scope": {
"type": "object",
"additionalProperties": false,
"required": [
"provider", "projection", "audience", "writer_visible",
"runtime_anchor_visible", "expires_with_request", "revocation_required",
"post_revoke_http_status"
],
"properties": {
"provider": {"const": "systemd"},
"projection": {"const": "LoadCredential"},
"audience": {"$ref": "#/$defs/identity"},
"writer_visible": {"const": false},
"runtime_anchor_visible": {"const": false},
"expires_with_request": {"const": true},
"revocation_required": {"const": true},
"post_revoke_http_status": {"const": 401}
}
},
"observed_preapproval_operations": {
"type": "array",
"minItems": 2,
"maxItems": 2,
"uniqueItems": true,
"items": {"enum": [
"create_governance_request_branch",
"create_governance_approval_pr"
]}
},
"allowed_operations": {
"type": "array",
"minItems": 1,
"uniqueItems": true,
"items": {"enum": [
"provision_task_publisher",
"provision_task_writer",
"provision_task_credential_projection",
"create_approved_code_branch",
"push_approved_code_branch",
"read_remote_refs",
"revoke_task_credentials",
"remove_task_identities"
]}
},
"allowed_operations_sha256": {"$ref": "#/$defs/sha256"},
"denied_operations": {"type": "array", "minItems": 12, "uniqueItems": true, "items": {"type": "string"}},
"maximum_operation_count": {"type": "integer", "minimum": 1, "maximum": 10},
"maximum_pr_count": {"const": 1},
"maximum_branch_count": {"const": 1},
"maximum_commit_count": {"const": 1},
"allowed_paths": {"type": "array", "minItems": 1, "uniqueItems": true, "items": {"type": "string", "minLength": 1}},
"allowed_paths_sha256": {"$ref": "#/$defs/sha256"},
"approved_at": {"type": "string", "format": "date-time", "pattern": "Z$"},
"expires_at": {"type": "string", "format": "date-time", "pattern": "Z$"},
"maximum_lifetime_seconds": {"type": "integer", "minimum": 1, "maximum": 43200},
"auto_revoke": {"const": true},
"required_human_approvals": {"const": 1},
"allowed_human_approvers": {"const": ["owner"]},
"approved_by": {"const": "owner"},
"owner_review_id": {"type": "integer", "minimum": 1},
"owner_review_commit_oid": {"$ref": "#/$defs/oid"},
"approval_plan_id": {"type": "string", "minLength": 3, "maxLength": 192},
"approval_audit_event_id": {"type": "string", "minLength": 3, "maxLength": 192},
"production_runtime_anchor_change": {"const": false},
"production_writes_enabled": {"const": false},
"tag_operations": {"const": false},
"registry_operations": {"const": false},
"baseline_operations": {"const": false},
"deployment_operations": {"const": false},
"force_push_allowed": {"const": false},
"tag_rewrite_allowed": {"const": false},
"direct_main_push_allowed": {"const": false}
},
"$defs": {
"plan": {
"type": "object",
"additionalProperties": false,
"required": [
"document_kind", "schema_id", "schema_version", "payload_sha256",
"payload"
],
"properties": {
"document_kind": {"const": "PLAN"},
"schema_id": {"const": "TASK_SCOPED_GOVERNANCE_BOOTSTRAP_PLAN"},
"schema_version": {"const": 1},
"payload_sha256": {"type": "string", "pattern": "^[0-9a-f]{64}$"},
"payload": {"$ref": "#/$defs/planPayload"}
}
},
"planPayload": {
"type": "object",
"additionalProperties": false,
"required": [
"allowed_human_approvers", "allowed_operations", "allowed_operations_sha256",
"allowed_paths", "allowed_paths_sha256", "approval_plan_id", "approval_plan_path",
"approved_by", "auto_revoke", "baseline_operations", "candidate_commit_oid",
"candidate_diff_sha256", "candidate_files_sha256", "candidate_parent_oid",
"candidate_tree_oid", "code_base_oid", "code_base_ref", "credential_scope",
"credential_secret_ref", "denied_operations", "deployment_operations",
"direct_main_push_allowed", "expires_at", "force_push_allowed",
"governance_base_oid", "governance_base_ref", "maximum_branch_count",
"maximum_commit_count", "maximum_lifetime_seconds", "maximum_operation_count",
"maximum_pr_count", "observed_preapproval_operations",
"production_runtime_anchor_change", "production_writes_enabled",
"provision_credential_projection", "provision_publisher", "provision_writer",
"publisher_identity", "publisher_scope", "registry_operations",
"repository_identity", "request_created_at", "request_expires_at",
"request_head_ref", "request_id", "requester_identity",
"required_human_approvals", "required_machine_contexts",
"schema_document_sha256", "schema_id", "schema_version", "tag_operations",
"tag_rewrite_allowed", "target_initial_state", "target_ref",
"validator_policy_sha256", "writer_identity", "writer_scope"
],
"properties": {
"allowed_human_approvers": {"$ref": "#/properties/allowed_human_approvers"},
"allowed_operations": {"$ref": "#/properties/allowed_operations"},
"allowed_operations_sha256": {"$ref": "#/properties/allowed_operations_sha256"},
"allowed_paths": {"$ref": "#/properties/allowed_paths"},
"allowed_paths_sha256": {"$ref": "#/properties/allowed_paths_sha256"},
"approval_plan_id": {"$ref": "#/properties/approval_plan_id"},
"approval_plan_path": {"$ref": "#/properties/approval_plan_path"},
"approved_by": {"$ref": "#/properties/approved_by"},
"auto_revoke": {"$ref": "#/properties/auto_revoke"},
"baseline_operations": {"$ref": "#/properties/baseline_operations"},
"candidate_commit_oid": {"$ref": "#/properties/candidate_commit_oid"},
"candidate_diff_sha256": {"$ref": "#/properties/candidate_diff_sha256"},
"candidate_files_sha256": {"$ref": "#/properties/candidate_files_sha256"},
"candidate_parent_oid": {"$ref": "#/properties/candidate_parent_oid"},
"candidate_tree_oid": {"$ref": "#/properties/candidate_tree_oid"},
"code_base_oid": {"$ref": "#/properties/code_base_oid"},
"code_base_ref": {"$ref": "#/properties/code_base_ref"},
"credential_scope": {"$ref": "#/properties/credential_scope"},
"credential_secret_ref": {"$ref": "#/properties/credential_secret_ref"},
"denied_operations": {"$ref": "#/properties/denied_operations"},
"deployment_operations": {"$ref": "#/properties/deployment_operations"},
"direct_main_push_allowed": {"$ref": "#/properties/direct_main_push_allowed"},
"expires_at": {"$ref": "#/properties/expires_at"},
"force_push_allowed": {"$ref": "#/properties/force_push_allowed"},
"governance_base_oid": {"$ref": "#/properties/governance_base_oid"},
"governance_base_ref": {"$ref": "#/properties/governance_base_ref"},
"maximum_branch_count": {"$ref": "#/properties/maximum_branch_count"},
"maximum_commit_count": {"$ref": "#/properties/maximum_commit_count"},
"maximum_lifetime_seconds": {"$ref": "#/properties/maximum_lifetime_seconds"},
"maximum_operation_count": {"$ref": "#/properties/maximum_operation_count"},
"maximum_pr_count": {"$ref": "#/properties/maximum_pr_count"},
"observed_preapproval_operations": {"$ref": "#/properties/observed_preapproval_operations"},
"production_runtime_anchor_change": {"$ref": "#/properties/production_runtime_anchor_change"},
"production_writes_enabled": {"$ref": "#/properties/production_writes_enabled"},
"provision_credential_projection": {"$ref": "#/properties/provision_credential_projection"},
"provision_publisher": {"$ref": "#/properties/provision_publisher"},
"provision_writer": {"$ref": "#/properties/provision_writer"},
"publisher_identity": {"$ref": "#/properties/publisher_identity"},
"publisher_scope": {"$ref": "#/properties/publisher_scope"},
"registry_operations": {"$ref": "#/properties/registry_operations"},
"repository_identity": {"$ref": "#/properties/repository_identity"},
"request_created_at": {"$ref": "#/properties/request_created_at"},
"request_expires_at": {"$ref": "#/properties/request_expires_at"},
"request_head_ref": {"$ref": "#/properties/request_head_ref"},
"request_id": {"$ref": "#/properties/request_id"},
"requester_identity": {"$ref": "#/properties/requester_identity"},
"required_human_approvals": {"$ref": "#/properties/required_human_approvals"},
"required_machine_contexts": {"$ref": "#/properties/required_machine_contexts"},
"schema_document_sha256": {"$ref": "#/properties/schema_document_sha256"},
"schema_id": {"$ref": "#/properties/schema_id"},
"schema_version": {"$ref": "#/properties/schema_version"},
"tag_operations": {"$ref": "#/properties/tag_operations"},
"tag_rewrite_allowed": {"$ref": "#/properties/tag_rewrite_allowed"},
"target_initial_state": {"$ref": "#/properties/target_initial_state"},
"target_ref": {"$ref": "#/properties/target_ref"},
"validator_policy_sha256": {"$ref": "#/properties/validator_policy_sha256"},
"writer_identity": {"$ref": "#/properties/writer_identity"},
"writer_scope": {"$ref": "#/properties/writer_scope"}
}
},
"oid": {"type": "string", "pattern": "^[0-9a-f]{40}$"},
"sha256": {"type": "string", "pattern": "^[0-9a-f]{64}$"},
"identity": {"type": "string", "minLength": 3, "maxLength": 128, "pattern": "^[A-Za-z0-9][A-Za-z0-9_.-]+$"}
}
}
@@ -0,0 +1,172 @@
import contextlib
import importlib.util
import io
import json
import sys
import tempfile
import threading
import unittest
import urllib.error
import urllib.request
from http.server import ThreadingHTTPServer
from pathlib import Path
ROOT = Path(__file__).resolve().parents[0]
def _load_client(root: Path):
path = root / "scripts" / "task-plane" / "create_shadow_tasks.py"
spec = importlib.util.spec_from_file_location("shadow_client", path)
module = importlib.util.module_from_spec(spec)
assert spec and spec.loader
spec.loader.exec_module(module)
return module
class ShadowClientRBACPublicInterfaceTests(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.repo_root = Path.cwd()
sys.path.insert(0, str(cls.repo_root / "active-runtime"))
from app.task_plane.api import TaskPlaneRequestHandler, TaskPlaneService
from app.task_plane.policy import IdentityVerifier, PolicyEngine
from app.task_plane.store import TaskPlaneStore
cls.RequestHandler = TaskPlaneRequestHandler
cls.TaskPlaneService = TaskPlaneService
cls.IdentityVerifier = IdentityVerifier
cls.PolicyEngine = PolicyEngine
cls.TaskPlaneStore = TaskPlaneStore
cls.client = _load_client(cls.repo_root)
def _server(self, capabilities):
store = self.TaskPlaneStore(":memory:")
policy = self.PolicyEngine(
identity_verifier=self.IdentityVerifier({"shadow-client"}),
execution_enabled=False,
identity_capabilities={"shadow-client": set(capabilities)},
)
service = self.TaskPlaneService(store, policy=policy)
class Handler(self.RequestHandler):
pass
Handler.service = service
Handler.authenticator = lambda value: "shadow-client" if value == "test-token" else None
server = ThreadingHTTPServer(("127.0.0.1", 0), Handler)
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
self.addCleanup(server.shutdown)
self.addCleanup(server.server_close)
self.addCleanup(thread.join, 2)
return f"http://127.0.0.1:{server.server_port}", store
@staticmethod
def _request(base, path, token=None, body=None):
headers = {"Content-Type": "application/json"}
if token is not None:
headers["Authorization"] = f"Bearer {token}"
req = urllib.request.Request(
base + path,
data=None if body is None else json.dumps(body).encode(),
headers=headers,
method="POST" if body is not None else "GET",
)
return urllib.request.urlopen(req, timeout=3)
def test_missing_credential_returns_401(self):
base, _ = self._server({"task.creator", "task.read", "task.audit"})
with self.assertRaises(urllib.error.HTTPError) as raised:
self._request(base, "/tasks")
self.assertEqual(raised.exception.code, 401)
def test_missing_task_creator_returns_403(self):
base, _ = self._server({"task.read", "task.audit"})
with self.assertRaises(self.client.ShadowClientError) as raised:
self.client.run_shadow_client(
base_url=base,
token="test-token",
task_id="SHADOW-RBAC-NO-CREATE",
idempotency_key="shadow-rbac-no-create-v1",
)
self.assertEqual(raised.exception.status, 403)
def test_minimum_client_is_idempotent_and_cannot_claim(self):
base, store = self._server({"task.creator", "task.read", "task.audit"})
first = self.client.run_shadow_client(
base_url=base,
token="test-token",
task_id="SHADOW-RBAC-IDEMPOTENT",
idempotency_key="shadow-rbac-idempotent-v1",
)
second = self.client.run_shadow_client(
base_url=base,
token="test-token",
task_id="SHADOW-RBAC-IDEMPOTENT",
idempotency_key="shadow-rbac-idempotent-v1",
)
self.assertEqual(first["task_id"], second["task_id"])
self.assertEqual(first["status"], "CREATED")
self.assertEqual(first["event_count"], 1)
self.assertEqual(first["evidence_count"], 0)
self.assertEqual(store.list_workers(), [])
claim_base, _ = self._server({"task.claim"})
with self.assertRaises(urllib.error.HTTPError) as raised:
self._request(
claim_base,
"/tasks/SHADOW-RBAC-IDEMPOTENT/claim",
"test-token",
{"worker_id": "shadow-worker", "lease_seconds": 30},
)
self.assertEqual(raised.exception.code, 423)
def test_forged_request_body_identity_is_rejected(self):
base, _ = self._server({"task.creator", "task.read", "task.audit"})
with self.assertRaises(urllib.error.HTTPError) as raised:
self._request(
base,
"/tasks",
"test-token",
{
"task_id": "SHADOW-RBAC-FORGED",
"actor_identity": "administrator",
},
)
self.assertEqual(raised.exception.code, 400)
def test_default_deny_get_rbac_and_redacted_report(self):
base, _ = self._server({"task.creator", "task.read", "task.audit"})
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
token_file = root / "token"
report = root / "report.json"
token_file.write_text("test-token", encoding="utf-8")
stdout = io.StringIO()
with contextlib.redirect_stdout(stdout):
self.client.main(
[
"--base-url", base,
"--token-file", str(token_file),
"--task-id", "SHADOW-RBAC-REPORT",
"--idempotency-key", "shadow-rbac-report-v1",
"--report", str(report),
]
)
output = stdout.getvalue()
self.assertNotIn("test-token", output)
self.assertNotIn("Authorization", output)
self.assertNotIn("test-token", report.read_text(encoding="utf-8"))
self.assertNotIn("Authorization", report.read_text(encoding="utf-8"))
self.assertIn("report_sha256=", output)
denied_base, _ = self._server({"task.creator"})
with self.assertRaises(urllib.error.HTTPError) as raised:
self._request(denied_base, "/tasks", "test-token")
self.assertEqual(raised.exception.code, 403)
def test_plaintext_token_argument_is_rejected_without_echo(self):
stderr = io.StringIO()
with contextlib.redirect_stderr(stderr):
with self.assertRaises(SystemExit):
self.client.main(["--token", "sentinel-token"])
self.assertNotIn("sentinel-token", stderr.getvalue())
@@ -0,0 +1,215 @@
import os
import sys
import unittest
ROOT = os.getcwd()
if os.path.join(ROOT, "active-runtime") not in sys.path:
sys.path.append(os.path.join(ROOT, "active-runtime"))
from app.task_plane.api import TaskPlaneService
from app.task_plane.policy import (
ALL_ACTION_CAPABILITIES,
IdentityVerifier,
PolicyEngine,
ReadonlyWorkerProfile,
)
def _policy(**kwargs):
return PolicyEngine(
identity_capabilities={"*": set(ALL_ACTION_CAPABILITIES)}, **kwargs
)
from app.task_plane.store import TaskPlaneStore
class TaskPlaneAPIBehaviorTests(unittest.TestCase):
def setUp(self):
self.store = TaskPlaneStore(":memory:")
self.service = TaskPlaneService(self.store, policy=_policy(execution_enabled=False))
def test_task_crud_and_events_chain(self):
task = self.service.create_task(
{
"task_type": "code.change",
"intent": "verify core",
"required_capabilities": ["code.read", "test.python"],
}
)
self.assertIn("task_id", task)
read = self.service.get_task(task["task_id"])
self.assertEqual("CREATED", read["status"])
events = self.service.get_events(task["task_id"])
self.assertEqual(len(events), 1)
def test_execution_disabled_blocks_claim(self):
task = self.service.create_task({"task_type": "code.change"})
self.store.register_worker(
{
"worker_id": "codex-dev-01",
"worker_type": "codex",
"identity": "spiffe://aios/agent/codex/dev-01",
"capabilities": ["code.read", "test.python"],
"trust_zone": "isolated-development",
"max_concurrency": 1,
"heartbeat_interval_seconds": 15,
"supported_protocols": ["aios-task-v1"],
}
)
with self.assertRaises(Exception):
self.service.claim(task["task_id"], {"worker_id": "codex-dev-01", "lease_seconds": 20})
def test_workers_and_events_outbox(self):
policy = _policy(execution_enabled=True)
service = TaskPlaneService(TaskPlaneStore(":memory:"), policy=policy)
service.register_worker(
{
"worker_id": "codex-dev-01",
"worker_type": "codex",
"identity": "spiffe://aios/agent/codex/dev-01",
"capabilities": ["code.read", "test.python", "code.write.patch"],
"trust_zone": "isolated-development",
"max_concurrency": 2,
"heartbeat_interval_seconds": 15,
"supported_protocols": ["aios-task-v1"],
}
)
task = service.create_task({"required_capabilities": ["code.read"], "task_type": "code.change"})
task_record = service.store.get_task(task["task_id"])
identity = "spiffe://aios/agent/codex/dev-01"
claim = service.claim(task["task_id"], {"worker_id": "codex-dev-01", "lease_seconds": 15, "actor_identity": identity})
service.heartbeat(task["task_id"], {"claim_id": claim["claim_id"], "lease_seconds": 20, "actor_identity": identity})
service.progress(task["task_id"], {"claim_id": claim["claim_id"], "progress": 40, "stage": "tests", "message": "running", "actor_identity": identity})
service.complete(task["task_id"], {"claim_id": claim["claim_id"], "status": "COMPLETED", "outputs": [], "evidence": [], "actor_identity": identity})
events = service.get_events(task["task_id"])
self.assertGreater(len(events), 1)
self.assertTrue(service.store.verify_event_chain(task["task_id"]))
self.assertEqual(service.get_children(task["task_id"]), [])
class ReadonlyCodexWorkerPolicyTests(unittest.TestCase):
identity = "spiffe://aios/agent/codex/review-test-01"
worker_id = "codex-review-test-01"
capabilities = frozenset(
{
"code.read",
"git.diff.read",
"code.review",
"review.security",
"review.api-contract",
"test.python",
"test.shell.allowlisted",
"task.progress",
"task.result",
"task.evidence.submit",
}
)
def setUp(self):
profile = ReadonlyWorkerProfile(self.worker_id, self.identity, self.capabilities)
policy = PolicyEngine(
identity_verifier=IdentityVerifier({"anon", self.identity}),
execution_enabled=False,
readonly_worker_execution_enabled=True,
readonly_worker_profiles=[profile],
identity_capabilities={
"anon": {"task.creator"},
self.identity: {"worker.self_register", "task.claim"},
},
)
self.service = TaskPlaneService(TaskPlaneStore(":memory:"), policy=policy)
self.registration = {
"worker_id": self.worker_id,
"worker_type": "codex",
"identity": self.identity,
"capabilities": sorted(self.capabilities),
"trust_zone": "isolated-review",
"max_concurrency": 1,
"heartbeat_interval_seconds": 15,
"supported_protocols": ["aios-task-v1", "a2a"],
"actor_identity": self.identity,
}
self.service.register_worker(self.registration)
def _task(self, *, task_type="code.review", capabilities=None, risk="low"):
return self.service.create_task(
{
"task_type": task_type,
"required_capabilities": capabilities or ["code.read", "code.review"],
"risk": risk,
"metadata": {"production_side_effects": False},
}
)
def test_readonly_profile_can_claim_only_safe_task(self):
task = self._task()
claim = self.service.claim(
task["task_id"],
{"worker_id": self.worker_id, "lease_seconds": 120, "actor_identity": self.identity},
)
self.assertTrue(claim["claim_id"])
def test_readonly_profile_rejects_wrong_worker_or_write_task(self):
task = self._task()
with self.assertRaises(Exception):
self.service.claim(
task["task_id"],
{"worker_id": "other-worker", "lease_seconds": 120, "actor_identity": self.identity},
)
write_task = self._task(task_type="code.change", capabilities=["code.write"])
with self.assertRaises(Exception):
self.service.claim(
write_task["task_id"],
{"worker_id": self.worker_id, "lease_seconds": 120, "actor_identity": self.identity},
)
def test_readonly_registration_cannot_escalate_capabilities(self):
payload = dict(self.registration)
payload["capabilities"] = [*payload["capabilities"], "git.commit"]
with self.assertRaises(Exception):
self.service.register_worker(payload)
class DelegationGuardTests(unittest.TestCase):
def setUp(self):
self.store = TaskPlaneStore(":memory:")
self.policy = _policy(execution_enabled=True)
self.service = TaskPlaneService(self.store, policy=self.policy)
self.parent = self.service.create_task(
{
"task_type": "code.change",
"required_capabilities": ["code.read"],
"budgets": {"attempts": 3, "tokens": 100, "time_seconds": 1000},
"max_delegation_depth": 2,
}
)
def test_delegation_budget_and_depth(self):
child = self.service.delegate(
self.parent["task_id"],
{
"task_type": "code.change",
"required_capabilities": ["code.read"],
"budgets": {"attempts": 1},
"actor_identity": "owner",
},
)
self.assertEqual(child["plan"]["parent_task_id"], self.parent["task_id"])
grandchild = self.service.delegate(
child["task_id"],
{
"task_type": "code.change",
"required_capabilities": ["code.read"],
"budgets": {"attempts": 1},
"actor_identity": "owner",
},
)
with self.assertRaises(Exception):
self.service.delegate(
grandchild["task_id"],
{
"task_type": "code.change",
"required_capabilities": ["code.read"],
"budgets": {"attempts": 1},
"actor_identity": "owner",
},
)
@@ -0,0 +1,381 @@
import json
import http.client
import os
import tempfile
import threading
import time
import unittest
from http.server import ThreadingHTTPServer
from unittest import mock
from app.task_plane.adapters.codex import CodexAdapter
from app.task_plane.adapters.hermes import HermesAdapter
from app.task_plane.adapters.openclaw import OpenClawAdapter
from app.task_plane.api import TaskPlaneRequestHandler, TaskPlaneService
from app.task_plane.errors import ConflictError, LockedError, ValidationError
from app.task_plane.events import DurableOutboxDispatcher, WeChatGatewayAdapter
from app.task_plane.models import ProbeEvidence, ProbeOutcome, TaskPlan
from app.task_plane.policy import ALL_ACTION_CAPABILITIES, PolicyEngine
from app.task_plane.store import TaskPlaneStore
IDENTITY = "spiffe://aios/agent/codex/dev-01"
def _policy(**kwargs):
return PolicyEngine(
identity_capabilities={"*": set(ALL_ACTION_CAPABILITIES)}, **kwargs
)
def register_worker(service):
return service.register_worker(
{
"worker_id": "codex-dev-01",
"worker_type": "codex",
"identity": IDENTITY,
"capabilities": ["code.read", "code.write.patch", "test.python"],
"trust_zone": "isolated-development",
"max_concurrency": 2,
"heartbeat_interval_seconds": 15,
"supported_protocols": ["aios-task-v1"],
}
)
class DurableStoreTests(unittest.TestCase):
def test_idempotency_replay_and_payload_conflict(self):
service = TaskPlaneService(TaskPlaneStore(":memory:"))
first = service.create_task(
{"task_id": "task-idempotent", "intent": "same"}, "idem-1"
)
replay = service.create_task(
{"task_id": "task-idempotent", "intent": "same"}, "idem-1"
)
self.assertEqual(first["task_id"], replay["task_id"])
with self.assertRaises(ConflictError):
service.create_task(
{"task_id": "task-other", "intent": "different"}, "idem-1"
)
def test_cas_rejects_stale_revision(self):
store = TaskPlaneStore(":memory:")
task = store.create_task(TaskPlan.from_dict({"task_id": "task-cas"}))
store.update_task_status(task.task_id, task.revision, "QUEUED")
with self.assertRaises(ConflictError):
store.update_task_status(task.task_id, task.revision, "CANCELLED")
def test_crash_recovery_preserves_task_events_and_outbox(self):
with tempfile.TemporaryDirectory() as directory:
path = os.path.join(directory, "task-plane.sqlite3")
first = TaskPlaneService(TaskPlaneStore(path))
task = first.create_task({"task_id": "task-restart"})
first.store.close()
second = TaskPlaneService(TaskPlaneStore(path))
self.assertEqual("CREATED", second.get_task(task["task_id"])["status"])
self.assertTrue(second.store.verify_event_chain(task["task_id"]))
self.assertEqual(1, len(second.store.list_outbox()))
second.store.close()
def test_event_and_evidence_tamper_are_detected(self):
store = TaskPlaneStore(":memory:")
service = TaskPlaneService(store)
task = service.create_task({"task_id": "task-tamper"})
evidence_id = store.add_evidence(
task["task_id"], "test-report", {"passed": True}, IDENTITY
)
store.conn.execute(
"UPDATE events SET event_data=? WHERE task_id=?",
(json.dumps({"changed": True}), task["task_id"]),
)
store.conn.execute(
"UPDATE evidence SET payload=? WHERE evidence_id=?",
(json.dumps({"passed": False}), evidence_id),
)
self.assertFalse(store.verify_event_chain(task["task_id"]))
self.assertFalse(store.verify_evidence_integrity(task["task_id"]))
def test_secrets_are_redacted_at_rest_and_in_outbox(self):
store = TaskPlaneStore(":memory:")
service = TaskPlaneService(store)
secret = "never-store-this-value"
task = service.create_task(
{
"task_id": "task-redaction",
"metadata": {"access_token": secret, "token_count": 12},
}
)
raw_task = store.conn.execute(
"SELECT task_json FROM tasks WHERE task_id=?", (task["task_id"],)
).fetchone()[0]
raw_outbox = store.conn.execute(
"SELECT payload FROM outbox WHERE task_id=?", (task["task_id"],)
).fetchone()[0]
self.assertNotIn(secret, raw_task + raw_outbox)
self.assertIn("[REDACTED]", raw_task)
self.assertIn("token_count", raw_task)
class ClaimLeaseTests(unittest.TestCase):
def setUp(self):
self.store = TaskPlaneStore(":memory:")
self.service = TaskPlaneService(
self.store, policy=_policy(execution_enabled=True)
)
register_worker(self.service)
def test_only_one_worker_can_claim_concurrently(self):
task = self.service.create_task({"task_id": "task-race"})
revision = self.store.get_task(task["task_id"]).revision
outcomes = []
barrier = threading.Barrier(2)
def attempt():
barrier.wait()
try:
self.store.claim_task(
task["task_id"],
"codex-dev-01",
IDENTITY,
30,
revision,
self.store.get_task(task["task_id"]).task_plan.operation_digest(),
)
outcomes.append("claimed")
except ConflictError:
outcomes.append("conflict")
threads = [threading.Thread(target=attempt) for _ in range(2)]
for thread in threads:
thread.start()
for thread in threads:
thread.join()
self.assertCountEqual(["claimed", "conflict"], outcomes)
def test_expired_lease_requires_probe_before_reassignment(self):
task = self.service.create_task({"task_id": "task-expiry"})
claim = self.service.claim(
task["task_id"],
{
"worker_id": "codex-dev-01",
"actor_identity": IDENTITY,
"lease_seconds": 1,
},
)
future = time.time() + 5
with mock.patch("app.task_plane.store._now", return_value=future):
self.store.probe_attestation_verifier = lambda probe: (
probe.attestation_ref == "trusted://probe/recovery"
)
probe = ProbeEvidence.create(
task_id=task["task_id"],
claim_id=claim["claim_id"],
worker_id="codex-dev-01",
worker_identity=IDENTITY,
operation_digest=claim["operation_digest"],
outcome=ProbeOutcome.NOT_APPLIED,
observed_at=future,
expires_at=future + 60,
probe_executor_identity="spiffe://aios/admin/recovery",
attestation_ref="trusted://probe/recovery",
)
recovered = self.store.recover_claim_with_probe_and_event(
task["task_id"],
claim["claim_id"],
probe,
)
self.assertEqual("FAILED_RECOVERABLE", recovered.status)
retry = self.service.claim(
task["task_id"],
{
"worker_id": "codex-dev-01",
"actor_identity": IDENTITY,
"lease_seconds": 30,
},
)
self.assertEqual(2, retry["attempt"])
def test_wrong_claim_identity_and_duplicate_result_are_rejected(self):
task = self.service.create_task({"task_id": "task-result"})
claim = self.service.claim(
task["task_id"],
{"worker_id": "codex-dev-01", "actor_identity": IDENTITY},
)
with self.assertRaises(Exception):
self.service.complete(
task["task_id"],
{
"claim_id": claim["claim_id"],
"actor_identity": "spiffe://aios/agent/codex/other",
},
)
self.service.progress(
task["task_id"],
{
"claim_id": claim["claim_id"],
"actor_identity": IDENTITY,
"progress": 10,
"stage": "running",
},
)
self.service.complete(
task["task_id"],
{"claim_id": claim["claim_id"], "actor_identity": IDENTITY},
)
with self.assertRaises(ConflictError):
self.service.complete(
task["task_id"],
{"claim_id": claim["claim_id"], "actor_identity": IDENTITY},
)
def test_tampered_result_digest_and_unverifiable_signature_fail_closed(self):
for suffix, extra in (
("digest", {"result_digest": "0" * 64}),
("signature", {"signature": "tampered"}),
):
task = self.service.create_task({"task_id": f"task-{suffix}"})
claim = self.service.claim(
task["task_id"],
{"worker_id": "codex-dev-01", "actor_identity": IDENTITY},
)
self.service.progress(
task["task_id"],
{
"claim_id": claim["claim_id"],
"actor_identity": IDENTITY,
"progress": 10,
"stage": "running",
},
)
with self.assertRaises(Exception):
self.service.complete(
task["task_id"],
{
"claim_id": claim["claim_id"],
"actor_identity": IDENTITY,
**extra,
},
)
self.assertEqual(
"ACTIVE", self.store.get_claim(claim["claim_id"]).status
)
class DelegationAndNotificationTests(unittest.TestCase):
def setUp(self):
self.service = TaskPlaneService(
TaskPlaneStore(":memory:"),
policy=_policy(execution_enabled=True, max_risk="high"),
)
self.parent = self.service.create_task(
{
"task_id": "task-parent",
"task_type": "code.review",
"required_capabilities": ["code.read"],
"risk": "medium",
"approval_policy": "patch-only",
"max_delegation_depth": 1,
"budgets": {"attempts": 1, "tokens": 10},
}
)
def test_capability_risk_budget_and_cycle_escalation_are_rejected(self):
base = {
"actor_identity": "owner",
"task_type": "code.review",
"budgets": {"attempts": 1},
}
for payload in (
{**base, "required_capabilities": ["git.force_push"]},
{**base, "required_capabilities": ["code.read"], "risk": "high"},
{**base, "required_capabilities": ["code.read"], "budgets": {"tokens": 11}},
{
**base,
"task_id": "task-parent",
"required_capabilities": ["code.read"],
},
):
with self.assertRaises(Exception):
self.service.delegate(self.parent["task_id"], payload)
def test_missing_gateway_receipt_remains_not_delivered(self):
message = self.service.store.list_outbox()[0]
dispatcher = DurableOutboxDispatcher(self.service.store)
updated = dispatcher.dispatch(
message["message_id"], WeChatGatewayAdapter(lambda envelope: None)
)
self.assertEqual("NOT_DELIVERED", updated["delivery_status"])
delivered = self.service.store.record_outbox_delivery(
message["message_id"], receipt="gateway-receipt-1"
)
replay = self.service.store.record_outbox_delivery(
message["message_id"], receipt="gateway-receipt-1"
)
self.assertEqual("DELIVERED", delivered["delivery_status"])
self.assertEqual(delivered["receipt"], replay["receipt"])
def test_worker_offline_detection_is_fail_closed(self):
register_worker(self.service)
worker = self.service.store.get_worker("codex-dev-01")
after_deadline = (
worker.last_heartbeat + worker.heartbeat_interval_seconds * 3 + 1
)
self.assertFalse(
self.service.workers.is_online("codex-dev-01", now=after_deadline)
)
def test_path_and_parameter_injection_are_rejected(self):
for value in ("../task", "task/child", "%2e%2e", "task\ninjected", ""):
with self.assertRaises(ValidationError):
self.service.store.get_task(value)
def test_shadow_adapters_only_return_descriptions(self):
task = TaskPlan.from_dict({"task_id": "task-shadow"})
claim = type("Claim", (), {"claim_id": "claim-shadow", "status": "ACTIVE"})()
for adapter in (OpenClawAdapter(), CodexAdapter(), HermesAdapter()):
result = adapter.execute(task, claim)
self.assertEqual("SHADOW_VERIFIED", result["status"])
self.assertFalse(result["production_side_effects"])
self.assertNotIn("command", result)
def test_http_rejects_encoded_path_and_non_object_json(self):
TaskPlaneRequestHandler.service = self.service
TaskPlaneRequestHandler.authenticator = None
server = ThreadingHTTPServer(("127.0.0.1", 0), TaskPlaneRequestHandler)
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
try:
connection = http.client.HTTPConnection(
"127.0.0.1", server.server_address[1], timeout=3
)
connection.request("GET", "/tasks/%2e%2e")
response = connection.getresponse()
payload = response.read().decode()
self.assertEqual(400, response.status)
self.assertNotIn("Traceback", payload)
connection.request(
"POST",
"/tasks",
body="[]",
headers={"Content-Type": "application/json"},
)
response = connection.getresponse()
response.read()
self.assertEqual(400, response.status)
connection.request(
"POST",
"/tasks",
body=json.dumps({"task_id": "task-http-unauthorized"}),
headers={"Content-Type": "application/json"},
)
response = connection.getresponse()
response.read()
self.assertEqual(401, response.status)
finally:
server.shutdown()
server.server_close()
thread.join(timeout=3)
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,892 @@
import dataclasses
import http.client
import json
import multiprocessing
import os
import tempfile
import threading
import time
import unittest
from http.server import ThreadingHTTPServer
from app.task_plane.api import TaskPlaneRequestHandler, TaskPlaneService
from app.task_plane.errors import (
ConflictError,
ForbiddenError,
LockedError,
ValidationError,
)
from app.task_plane.models import (
EvidenceEnvelope,
ProbeEvidence,
ProbeOutcome,
TaskPlan,
)
from app.task_plane.policy import ALL_ACTION_CAPABILITIES, PolicyEngine
from app.task_plane.scheduler import TaskScheduler
from app.task_plane.store import TaskPlaneStore
WORKER_ID = "codex-secure-01"
WORKER_IDENTITY = "spiffe://aios/agent/codex/secure-01"
ADMIN_IDENTITY = "spiffe://aios/admin/task-plane"
def _worker_payload(**overrides):
payload = {
"worker_id": WORKER_ID,
"worker_type": "codex",
"identity": WORKER_IDENTITY,
"owner_identity": WORKER_IDENTITY,
"capabilities": ["code.read", "test.python"],
"trust_zone": "isolated-development",
"max_concurrency": 1,
"heartbeat_interval_seconds": 15,
"supported_protocols": ["aios-task-v1"],
"registration_policy": "self",
"actor_identity": WORKER_IDENTITY,
}
payload.update(overrides)
return payload
def _service(store=None, **policy_kwargs):
grants = {
ADMIN_IDENTITY: {
"task.creator",
"task.read",
"task.audit",
"task.cancel",
"task.delegate",
"task.claim",
"worker.admin",
"worker.self_register",
},
WORKER_IDENTITY: {
"task.read",
"task.claim",
"worker.self_register",
},
}
return TaskPlaneService(
store or TaskPlaneStore(":memory:"),
policy=PolicyEngine(
execution_enabled=True,
identity_capabilities=grants,
admin_identities={ADMIN_IDENTITY},
**policy_kwargs,
),
)
def _claim_for_service(service, *, task_id="task-secure", plan=None):
plan = plan or {
"task_id": task_id,
"issuer": {"type": "agent", "id": ADMIN_IDENTITY},
"required_capabilities": ["code.read"],
"risk": "low",
}
service.register_worker(
_worker_payload(
capabilities=sorted(
{"code.read", "test.python", *plan.get("required_capabilities", [])}
)
)
)
task = service.create_task(plan)
claim = service.claim(
task["task_id"],
{"worker_id": WORKER_ID, "actor_identity": WORKER_IDENTITY},
)
return task, claim
def _process_claim(db_path, task_id, expected_revision, digest, queue):
store = TaskPlaneStore(db_path)
try:
store.claim_task(
task_id,
WORKER_ID,
WORKER_IDENTITY,
30,
expected_revision,
digest,
)
queue.put("claimed")
except Exception as exc:
queue.put(type(exc).__name__)
finally:
store.close()
class EvidenceEnvelopeContractTests(unittest.TestCase):
def setUp(self):
self.base = EvidenceEnvelope.create(
task_id="task-evidence",
claim_id="claim-evidence",
kind="test-report",
worker_identity=WORKER_IDENTITY,
payload={"passed": True},
previous_evidence_hash="",
attestation_ref="trusted://shadow/test-report",
)
def test_digest_binds_complete_envelope_and_order(self):
self.assertTrue(
self.base.verify(
expected_task_id="task-evidence",
expected_claim_id="claim-evidence",
expected_previous_hash="",
)
)
mutations = {
"kind": "secret-scan",
"worker_identity": "spiffe://attacker",
"task_id": "task-other",
"claim_id": "claim-other",
"created_at": self.base.created_at + 1,
"payload": {"passed": False},
"previous_evidence_hash": "f" * 64,
}
for field, value in mutations.items():
with self.subTest(field=field):
self.assertFalse(
dataclasses.replace(self.base, **{field: value}).verify(
expected_task_id="task-evidence",
expected_claim_id="claim-evidence",
expected_previous_hash="",
)
)
def test_store_rejects_cross_task_required_evidence_and_chain_tamper(self):
store = TaskPlaneStore(":memory:")
store.create_task_with_event(
TaskPlan.from_dict({"task_id": "task-evidence"})
)
store.add_evidence_envelope(self.base)
self.assertTrue(store.verify_evidence_integrity("task-evidence"))
store.conn.execute(
"UPDATE evidence SET envelope_json=json_set(envelope_json,'$.task_id','task-other')"
)
self.assertFalse(store.verify_evidence_integrity("task-evidence"))
class AtomicMutationContractTests(unittest.TestCase):
def _fault(self, point):
def inject(observed):
if observed == point:
raise RuntimeError("injected fault")
return inject
def test_create_cancel_and_delegate_roll_back_at_event_boundaries(self):
store = TaskPlaneStore(":memory:")
store.fault_injector = self._fault("create.after_task_before_event")
with self.assertRaises(RuntimeError):
store.create_task_with_event(TaskPlan.from_dict({"task_id": "task-create"}))
self.assertEqual([], store.list_tasks())
store.fault_injector = None
parent = store.create_task_with_event(
TaskPlan.from_dict(
{
"task_id": "task-parent-atomic",
"required_capabilities": ["code.read"],
"budgets": {"attempts": 2},
}
)
)
store.fault_injector = self._fault("cancel.after_state_before_event")
with self.assertRaises(RuntimeError):
store.cancel_task_with_event(parent.task_id, parent.revision, "stop")
self.assertEqual("CREATED", store.get_task(parent.task_id).status)
store.fault_injector = self._fault("delegate.after_budget_before_child")
with self.assertRaises(RuntimeError):
store.delegate_task_with_budget_and_events(
parent.task_id,
TaskPlan.from_dict(
{
"task_id": "task-child-atomic",
"required_capabilities": ["code.read"],
"budgets": {"attempts": 1},
}
),
caller=ADMIN_IDENTITY,
)
self.assertEqual(2, store.get_task_budget(parent.task_id).attempts)
self.assertEqual([], store.list_children(parent.task_id))
def test_complete_fail_and_recover_roll_back_wholly(self):
service = _service()
task, claim = _claim_for_service(service, task_id="task-complete-atomic")
service.progress(
task["task_id"],
{
"claim_id": claim["claim_id"],
"actor_identity": WORKER_IDENTITY,
"progress": 10,
"stage": "running",
},
)
service.store.fault_injector = self._fault(
"complete.after_evidence_before_state"
)
envelope = EvidenceEnvelope.create(
task_id=task["task_id"],
claim_id=claim["claim_id"],
kind="test-report",
worker_identity=WORKER_IDENTITY,
payload={"passed": True},
previous_evidence_hash="",
attestation_ref="trusted://shadow/test",
)
with self.assertRaises(RuntimeError):
service.store.complete_claim_with_evidence_and_event(
task["task_id"],
claim["claim_id"],
"COMPLETED",
{"status": "COMPLETED"},
[envelope],
)
self.assertEqual([], service.store.list_evidence(task["task_id"]))
self.assertEqual("RUNNING", service.store.get_task(task["task_id"]).status)
def test_fail_rolls_back_claim_state_and_event(self):
service = _service()
task, claim = _claim_for_service(service, task_id="task-fail-atomic")
service.store.fault_injector = self._fault("fail.after_state_before_commit")
with self.assertRaises(RuntimeError):
service.store.fail_claim_with_event(
task["task_id"], claim["claim_id"], "FAILED_FINAL", "injected"
)
self.assertEqual("ACTIVE", service.store.get_claim(claim["claim_id"]).status)
self.assertEqual("CLAIMED", service.store.get_task(task["task_id"]).status)
self.assertNotIn(
"TASK_FAILED",
{
event["type"]
for event in service.store.list_events(task["task_id"])
},
)
def test_recovery_rolls_back_probe_state_and_event(self):
service = _service(
probe_attestation_verifier=lambda probe: probe.attestation_ref
== "trusted://probe/atomic"
)
task, claim = _claim_for_service(service, task_id="task-recover-atomic")
service.store.conn.execute(
"UPDATE claims SET lease_until=? WHERE claim_id=?",
(time.time() - 10, claim["claim_id"]),
)
probe = ProbeEvidence.create(
task_id=task["task_id"],
claim_id=claim["claim_id"],
worker_id=WORKER_ID,
worker_identity=WORKER_IDENTITY,
operation_digest=claim["operation_digest"],
outcome=ProbeOutcome.NOT_APPLIED,
observed_at=time.time(),
expires_at=time.time() + 60,
probe_executor_identity=ADMIN_IDENTITY,
attestation_ref="trusted://probe/atomic",
)
service.store.fault_injector = self._fault(
"recover.after_probe_before_state"
)
with self.assertRaises(RuntimeError):
service.store.recover_claim_with_probe_and_event(
task["task_id"], claim["claim_id"], probe
)
self.assertEqual(
0,
service.store.conn.execute(
"SELECT COUNT(*) FROM probe_evidence WHERE probe_id=?",
(probe.probe_id,),
).fetchone()[0],
)
self.assertEqual("ACTIVE", service.store.get_claim(claim["claim_id"]).status)
self.assertEqual("CLAIMED", service.store.get_task(task["task_id"]).status)
class WorkerOwnershipContractTests(unittest.TestCase):
def test_identity_takeover_escalation_and_disabled_reregistration_rejected(self):
service = _service()
registered = service.register_worker(_worker_payload())
revision = registered["registration_revision"]
with self.assertRaises(ConflictError):
service.register_worker(
_worker_payload(
identity="spiffe://aios/attacker",
owner_identity="spiffe://aios/attacker",
actor_identity="spiffe://aios/attacker",
expected_revision=revision,
)
)
with self.assertRaises(ForbiddenError):
service.register_worker(
_worker_payload(
capabilities=["code.read", "deploy.production"],
expected_revision=revision,
)
)
disabled = service.disable_worker(
WORKER_ID,
{
"actor_identity": ADMIN_IDENTITY,
"expected_revision": revision,
"reason": "security review",
},
)
with self.assertRaises(LockedError):
service.register_worker(
_worker_payload(expected_revision=disabled["registration_revision"])
)
def test_worker_cannot_heartbeat_other_identity_and_all_updates_use_cas(self):
service = _service()
worker = service.register_worker(_worker_payload())
with self.assertRaises(ForbiddenError):
service.worker_heartbeat(
WORKER_ID,
{
"actor_identity": "spiffe://aios/attacker",
"expected_revision": worker["registration_revision"],
},
)
with self.assertRaises(ConflictError):
service.worker_heartbeat(
WORKER_ID,
{
"actor_identity": WORKER_IDENTITY,
"expected_revision": worker["registration_revision"] + 1,
},
)
class RecoveryProbeContractTests(unittest.TestCase):
def _expired_claim(self, suffix):
service = _service(
probe_attestation_verifier=lambda probe: probe.attestation_ref
== "trusted://probe/test"
)
task, claim = _claim_for_service(service, task_id=f"task-probe-{suffix}")
service.store.conn.execute(
"UPDATE claims SET lease_until=? WHERE claim_id=?",
(time.time() - 10, claim["claim_id"]),
)
return service, task, claim
def _probe(self, task, claim, outcome, **overrides):
data = {
"task_id": task["task_id"],
"claim_id": claim["claim_id"],
"worker_id": WORKER_ID,
"worker_identity": WORKER_IDENTITY,
"operation_digest": claim["operation_digest"],
"outcome": outcome,
"observed_at": time.time(),
"expires_at": time.time() + 60,
"probe_executor_identity": ADMIN_IDENTITY,
"attestation_ref": "trusted://probe/test",
}
data.update(overrides)
return ProbeEvidence.create(**data)
def test_four_probe_outcomes_have_distinct_fail_closed_transitions(self):
expected = {
ProbeOutcome.NOT_APPLIED: "FAILED_RECOVERABLE",
ProbeOutcome.APPLIED_VERIFIED: "COMPLETED",
ProbeOutcome.INDETERMINATE: "FAILED_RECOVERABLE",
ProbeOutcome.CONFLICT: "MANUAL_REVIEW_REQUIRED",
}
for outcome, status in expected.items():
with self.subTest(outcome=outcome):
service, task, claim = self._expired_claim(outcome.value.lower())
recovered = service.store.recover_claim_with_probe_and_event(
task["task_id"],
claim["claim_id"],
self._probe(task, claim, outcome),
)
self.assertEqual(status, recovered.status)
if outcome == ProbeOutcome.INDETERMINATE:
self.assertFalse(
service.store.task_has_retryable_probe(task["task_id"])
)
def test_forged_expired_or_mismatched_probe_is_rejected(self):
service, task, claim = self._expired_claim("invalid")
valid = self._probe(task, claim, ProbeOutcome.NOT_APPLIED)
for probe in (
dataclasses.replace(valid, evidence_digest="0" * 64),
self._probe(
task,
claim,
ProbeOutcome.NOT_APPLIED,
expires_at=time.time() - 1,
),
self._probe(
task,
claim,
ProbeOutcome.NOT_APPLIED,
operation_digest="f" * 64,
),
):
with self.assertRaises((ForbiddenError, ConflictError, LockedError)):
service.store.recover_claim_with_probe_and_event(
task["task_id"], claim["claim_id"], probe
)
class SchedulerAndClassificationTests(unittest.TestCase):
def test_scheduler_skips_incompatible_and_dependency_blocked_tasks(self):
service = _service()
service.register_worker(_worker_payload())
service.create_task(
{
"task_id": "task-incompatible",
"issuer": {"type": "agent", "id": ADMIN_IDENTITY},
"required_capabilities": ["gpu.train"],
}
)
service.create_task(
{
"task_id": "task-dependency",
"issuer": {"type": "agent", "id": ADMIN_IDENTITY},
}
)
service.create_task(
{
"task_id": "task-blocked",
"issuer": {"type": "agent", "id": ADMIN_IDENTITY},
"required_capabilities": ["code.read"],
"dependencies": ["task-dependency"],
}
)
service.create_task(
{
"task_id": "task-eligible",
"issuer": {"type": "agent", "id": ADMIN_IDENTITY},
"required_capabilities": ["code.read"],
"metadata": {"priority": 100},
}
)
claim = TaskScheduler(service.store).schedule_for_worker(WORKER_ID)
self.assertEqual("task-eligible", claim.task_id)
self.assertEqual(WORKER_IDENTITY, claim.worker_identity)
def test_credential_secret_rejects_generic_worker_and_redacts_free_text(self):
service = _service()
service.register_worker(_worker_payload())
task = service.create_task(
{
"task_id": "task-credential-secret",
"issuer": {"type": "agent", "id": ADMIN_IDENTITY},
"required_capabilities": ["secret.transform"],
"data_classification": "credential-secret",
"execution_zone": "secure-file-transformer",
"network_policy": "denied",
"logging_policy": "metadata-only",
"output_policy": "hash-count-status-only",
}
)
with self.assertRaises(ForbiddenError):
service.claim(
task["task_id"],
{"worker_id": WORKER_ID, "actor_identity": WORKER_IDENTITY},
)
with self.assertRaises(ValueError):
TaskPlan.from_dict(
{
"task_id": "task-secret-leak",
"intent": "Authorization: Bearer super-secret-value",
"data_classification": "credential-secret",
}
)
class ReadRBACAndResultProofTests(unittest.TestCase):
def test_get_evidence_requires_authentication(self):
service = _service()
service.create_task(
{
"task_id": "task-http-read",
"issuer": {"type": "agent", "id": ADMIN_IDENTITY},
}
)
TaskPlaneRequestHandler.service = service
TaskPlaneRequestHandler.authenticator = lambda token: (
ADMIN_IDENTITY if token == "valid" else None
)
server = ThreadingHTTPServer(("127.0.0.1", 0), TaskPlaneRequestHandler)
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
try:
connection = http.client.HTTPConnection(
"127.0.0.1", server.server_address[1], timeout=3
)
connection.request("GET", "/tasks/task-http-read/evidence")
response = connection.getresponse()
response.read()
self.assertEqual(401, response.status)
connection.request(
"GET",
"/tasks/task-http-read/evidence",
headers={"Authorization": "Bearer valid"},
)
response = connection.getresponse()
response.read()
self.assertEqual(200, response.status)
finally:
server.shutdown()
server.server_close()
thread.join(timeout=3)
def test_sensitive_result_requires_worker_proof_but_low_shadow_is_marked(self):
service = _service(result_signature_verifier=lambda result: False)
task, claim = _claim_for_service(
service,
task_id="task-write-proof",
plan={
"task_id": "task-write-proof",
"issuer": {"type": "agent", "id": ADMIN_IDENTITY},
"required_capabilities": ["code.read", "code.write.patch"],
"risk": "medium",
},
)
service.progress(
task["task_id"],
{
"claim_id": claim["claim_id"],
"actor_identity": WORKER_IDENTITY,
"progress": 100,
"stage": "done",
},
)
with self.assertRaises(ForbiddenError):
service.complete(
task["task_id"],
{
"claim_id": claim["claim_id"],
"actor_identity": WORKER_IDENTITY,
"signature": "invalid",
},
)
class SQLiteAndEventIntegrityTests(unittest.TestCase):
def test_event_hash_binds_metadata(self):
store = TaskPlaneStore(":memory:")
task = store.create_task_with_event(
TaskPlan.from_dict({"task_id": "task-event-metadata"})
)
self.assertTrue(store.verify_event_chain(task.task_id))
for column, value in (
("event_id", "evt-forged"),
("subject", "task/other"),
("created_at", 1.0),
):
with self.subTest(column=column):
original = dict(
store.conn.execute(
"SELECT * FROM events WHERE task_id=?", (task.task_id,)
).fetchone()
)
store.conn.execute(
f"UPDATE events SET {column}=? WHERE task_id=?",
(value, task.task_id),
)
self.assertFalse(store.verify_event_chain(task.task_id))
store.conn.execute(
f"UPDATE events SET {column}=? WHERE task_id=?",
(original[column], task.task_id),
)
def test_sqlite_high_assurance_schema_integrity_checkpoint_and_backup(self):
with tempfile.TemporaryDirectory() as directory:
path = os.path.join(directory, "task-plane.sqlite3")
store = TaskPlaneStore(path, high_assurance=True)
self.assertGreaterEqual(store.schema_version(), 2)
self.assertEqual(5000, store.conn.execute("PRAGMA busy_timeout").fetchone()[0])
self.assertEqual(2, store.conn.execute("PRAGMA synchronous").fetchone()[0])
self.assertEqual("ok", store.integrity_check())
store.create_task_with_event(
TaskPlan.from_dict({"task_id": "task-backup"})
)
backup = os.path.join(directory, "backup.sqlite3")
store.checkpoint_and_backup(backup)
self.assertTrue(os.path.exists(backup))
store.close()
def test_multi_process_claim_race_has_single_winner(self):
with tempfile.TemporaryDirectory() as directory:
path = os.path.join(directory, "task-plane.sqlite3")
store = TaskPlaneStore(path)
service = _service(store)
service.register_worker(_worker_payload(max_concurrency=2))
task = service.create_task(
{
"task_id": "task-multiprocess",
"issuer": {"type": "agent", "id": ADMIN_IDENTITY},
"required_capabilities": ["code.read"],
}
)
record = store.get_task(task["task_id"])
queue = multiprocessing.Queue()
processes = [
multiprocessing.Process(
target=_process_claim,
args=(
path,
task["task_id"],
record.revision,
record.task_plan.operation_digest(),
queue,
),
)
for _ in range(2)
]
for process in processes:
process.start()
for process in processes:
process.join(10)
outcomes = [queue.get(timeout=3) for _ in processes]
self.assertEqual(1, outcomes.count("claimed"))
store.close()
class ExplicitSecurityMatrixTests(unittest.TestCase):
def test_evidence_kind_tamper_is_rejected(self):
envelope = EvidenceEnvelope.create(
task_id="task-kind",
claim_id="claim-kind",
kind="test-report",
worker_identity=WORKER_IDENTITY,
payload={"passed": True},
previous_evidence_hash="",
)
self.assertFalse(
dataclasses.replace(envelope, kind="forged").verify(
expected_task_id="task-kind",
expected_claim_id="claim-kind",
expected_previous_hash="",
)
)
def test_worker_admin_restore_is_cas_and_journaled(self):
service = _service()
worker = service.register_worker(_worker_payload())
disabled = service.disable_worker(
WORKER_ID,
{
"actor_identity": ADMIN_IDENTITY,
"expected_revision": worker["registration_revision"],
"reason": "test",
},
)
restored = service.restore_worker(
WORKER_ID,
{
"actor_identity": ADMIN_IDENTITY,
"expected_revision": disabled["registration_revision"],
},
)
self.assertFalse(restored["disabled"])
events = service.store.list_events("task-worker-registry")
self.assertIn("WORKER_RESTORED", {event["type"] for event in events})
def test_disabled_worker_heartbeat_is_rejected(self):
service = _service()
worker = service.register_worker(_worker_payload())
disabled = service.disable_worker(
WORKER_ID,
{
"actor_identity": ADMIN_IDENTITY,
"expected_revision": worker["registration_revision"],
"reason": "test",
},
)
with self.assertRaises(LockedError):
service.worker_heartbeat(
WORKER_ID,
{
"actor_identity": WORKER_IDENTITY,
"expected_revision": disabled["registration_revision"],
},
)
def test_low_risk_unsigned_result_is_explicitly_untrusted(self):
service = _service()
task, claim = _claim_for_service(service, task_id="task-low-shadow")
service.progress(
task["task_id"],
{
"claim_id": claim["claim_id"],
"actor_identity": WORKER_IDENTITY,
"progress": 100,
"stage": "done",
},
)
result = service.complete(
task["task_id"],
{
"claim_id": claim["claim_id"],
"actor_identity": WORKER_IDENTITY,
},
)
self.assertEqual("untrusted_shadow_result", result["trust_status"])
def test_boolean_recovery_contract_is_rejected(self):
service = _service()
task, claim = _claim_for_service(service, task_id="task-no-bool-probe")
service.store.conn.execute(
"UPDATE claims SET lease_until=? WHERE claim_id=?",
(time.time() - 10, claim["claim_id"]),
)
with self.assertRaises(ValidationError):
service.store.recover_expired_claim(
task["task_id"], claim["claim_id"], True
)
def test_scheduler_rejects_offline_worker(self):
service = _service()
service.register_worker(_worker_payload())
service.create_task(
{
"task_id": "task-offline",
"issuer": {"type": "agent", "id": ADMIN_IDENTITY},
"required_capabilities": ["code.read"],
}
)
service.store.conn.execute(
"UPDATE workers SET last_heartbeat=? WHERE worker_id=?",
(time.time() - 1000, WORKER_ID),
)
with self.assertRaises(LockedError):
TaskScheduler(service.store).schedule_for_worker(WORKER_ID)
def test_schedule_decision_event_is_single_for_claim(self):
service = _service()
service.register_worker(_worker_payload())
service.create_task(
{
"task_id": "task-schedule-event",
"issuer": {"type": "agent", "id": ADMIN_IDENTITY},
"required_capabilities": ["code.read"],
}
)
TaskScheduler(service.store).schedule_for_worker(WORKER_ID)
events = service.store.list_events("task-schedule-event")
self.assertEqual(
1, sum(event["type"] == "SCHEDULE_DECISION" for event in events)
)
def _credential_service_and_claim(self):
identity = "spiffe://aios/agent/secure-file-transformer/01"
service = TaskPlaneService(
TaskPlaneStore(":memory:"),
policy=PolicyEngine(
execution_enabled=True,
identity_capabilities={"*": set(ALL_ACTION_CAPABILITIES)},
),
)
service.register_worker(
{
"worker_id": "secure-transformer-01",
"worker_type": "secure_file_transformer",
"identity": identity,
"capabilities": ["secret.transform", "secure_file_transformer"],
"trust_zone": "secure",
"max_concurrency": 1,
"heartbeat_interval_seconds": 15,
"supported_protocols": ["aios-task-v1"],
}
)
task = service.create_task(
{
"task_id": "task-secure-output",
"required_capabilities": ["secret.transform"],
"data_classification": "credential-secret",
"execution_zone": "secure-file-transformer",
"network_policy": "denied",
"logging_policy": "metadata-only",
"output_policy": "hash-count-status-only",
}
)
claim = service.claim(
task["task_id"],
{"worker_id": "secure-transformer-01", "actor_identity": identity},
)
return service, task, claim, identity
def test_credential_progress_free_text_is_rejected(self):
service, task, claim, identity = self._credential_service_and_claim()
with self.assertRaises(ValidationError):
service.progress(
task["task_id"],
{
"claim_id": claim["claim_id"],
"actor_identity": identity,
"progress": 10,
"stage": "running",
"message": "Authorization: Bearer forbidden",
},
)
def test_credential_result_content_is_rejected(self):
service, task, claim, identity = self._credential_service_and_claim()
service.progress(
task["task_id"],
{
"claim_id": claim["claim_id"],
"actor_identity": identity,
"progress": 100,
"stage": "done",
},
)
with self.assertRaises(ValidationError):
service.complete(
task["task_id"],
{
"claim_id": claim["claim_id"],
"actor_identity": identity,
"outputs": [{"content": "forbidden"}],
},
)
def test_action_rbac_denies_read_without_task_read(self):
store = TaskPlaneStore(":memory:")
service = TaskPlaneService(
store,
policy=PolicyEngine(
identity_capabilities={"spiffe://reader/none": set()}
),
)
store.create_task_with_event(TaskPlan.from_dict({"task_id": "task-rbac"}))
with self.assertRaises(ForbiddenError):
service.get_task("task-rbac", "spiffe://reader/none")
def test_default_service_policy_denies_authenticated_read(self):
service = TaskPlaneService(TaskPlaneStore(":memory:"))
service.create_task({"task_id": "task-default-deny"})
with self.assertRaises(ForbiddenError):
service.get_task("task-default-deny", "spiffe://reader/unconfigured")
def test_fail_path_emits_one_task_failed_event(self):
service = _service()
task, claim = _claim_for_service(service, task_id="task-single-fail")
service.fail(
task["task_id"],
{
"claim_id": claim["claim_id"],
"actor_identity": WORKER_IDENTITY,
"reason": "expected test failure",
},
)
events = service.store.list_events(task["task_id"])
self.assertEqual(1, sum(event["type"] == "TASK_FAILED" for event in events))
if __name__ == "__main__":
unittest.main()
@@ -0,0 +1,417 @@
from __future__ import annotations
import json
import os
import tempfile
import unittest
from datetime import datetime, timedelta, timezone
from pathlib import Path
from unittest import mock
import aios_isolated_writer_policy as writer_policy
import isolated_writer_path_policy as path_policy
from isolated_writer_authz import AuthorizationError, _validate_grant
TARGET = "deploy/systemd/aios-codex-review-test-worker.service"
BRANCH = "patch/codex-review-test-worker-20260724"
AUDIENCE = "aios-codex-review-worker-writer.service"
VALID_UNIT = """\
[Unit]
Description=AIOS Codex Review/Test Worker
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=aiman
Group=gitops
WorkingDirectory=/usr/libexec/aios-codex-review-test-worker/current
Environment=AIOS_CODEX_WORKER_MODE=shadow
Environment=AIOS_TASK_PLANE_EXECUTION_ENABLED=false
Environment=PYTHONDONTWRITEBYTECODE=1
ExecStart=/usr/bin/python3 -m task_plane.workers.codex_review_test
Restart=on-failure
RestartSec=2
NoNewPrivileges=true
PrivateTmp=true
PrivateDevices=true
ProtectSystem=strict
ProtectHome=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
LockPersonality=true
RestrictSUIDSGID=true
RestrictRealtime=true
MemoryDenyWriteExecute=true
RuntimeDirectory=aios-codex-review-test-worker
StateDirectory=aios-codex-review-test-worker
CacheDirectory=aios-codex-review-test-worker
ReadOnlyPaths=/data/aios-gitops/repo.git
ReadOnlyPaths=/usr/libexec/aios-codex-review-test-worker
ReadWritePaths=/run/aios-codex-review-test-worker
ReadWritePaths=/var/lib/aios-codex-review-test-worker
ReadWritePaths=/var/cache/aios-codex-review-test-worker
UMask=0077
[Install]
WantedBy=multi-user.target
"""
def document(*, paths=None, **updates):
value = {
"schema_version": 1,
"policy_id": "codex-review-worker-path-policy-v1",
"audience": AUDIENCE,
"repository_identity": "aios/aios",
"allowed_branch": BRANCH,
"allowed_operations": {
"commit_file.atomic": {
"exact_paths": paths or [TARGET],
"max_content_bytes": 32768,
"content_profile": "systemd-codex-review-worker-v1",
}
},
}
value.update(updates)
return value
class ExactPathPolicyTests(unittest.TestCase):
def setUp(self):
self.saved = dict(os.environ)
self.temp = tempfile.TemporaryDirectory()
self.root = Path(self.temp.name)
self.policy_dir = self.root / "policies"
self.policy_dir.mkdir(mode=0o750)
self.policy_file = self.policy_dir / "writer.json"
self.workspace = self.root / "workspace"
self.workspace.mkdir(mode=0o700)
os.environ.update(
{
"AIOS_GITOPS_ISOLATED_WRITER_ENFORCEMENT": "required",
"AIOS_GITOPS_SERVICE_AUDIENCE": AUDIENCE,
"AIOS_GITOPS_REPOSITORY_IDENTITY": "aios/aios",
"AIOS_GITOPS_ALLOWED_BRANCH": BRANCH,
}
)
def tearDown(self):
path_policy.clear_path_policy_cache()
os.environ.clear()
os.environ.update(self.saved)
self.temp.cleanup()
def write_policy(self, value=None):
payload = document() if value is None else value
raw = (
json.dumps(payload, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
+ "\n"
)
self.policy_file.write_text(raw, encoding="utf-8")
self.policy_file.chmod(0o640)
return path_policy.load_path_policy(
self.policy_file,
expected_owner_uid=os.getuid(),
expected_group_gid=os.getgid(),
)
def configured(self, loaded):
return mock.patch.object(
path_policy,
"configured_path_policy",
return_value=loaded,
)
def test_exact_systemd_path_and_exact_token_path_are_allowed(self):
loaded = self.write_policy()
with self.configured(loaded):
self.assertEqual(TARGET, writer_policy.validate_path(TARGET, self.workspace))
writer_policy.validate_content_for_path(TARGET, VALID_UNIT)
def test_token_path_cannot_expand_service_policy(self):
loaded = self.write_policy(document(paths=["deploy/systemd/other.service"]))
with self.configured(loaded), self.assertRaisesRegex(
PermissionError, "isolated_writer_service_path_rejected"
):
writer_policy.validate_path(TARGET, self.workspace)
def test_service_policy_cannot_expand_token_path(self):
now = datetime.now(timezone.utc)
grant = {
"schema_version": 2,
"audience": AUDIENCE,
"source_cidrs": ["127.0.0.1/32"],
"created_at": (now - timedelta(seconds=5)).isoformat(),
"not_before": (now - timedelta(seconds=2)).isoformat(),
"expires_at": (now + timedelta(minutes=5)).isoformat(),
"revoked": False,
"allowed_operations": ["commit_file.atomic"],
"allowed_branch": BRANCH,
"allowed_paths": ["deploy/systemd/other.service"],
"approved_base_oid": "a" * 40,
}
with mock.patch(
"isolated_writer_authz._git_is_ancestor", return_value=True
), self.assertRaisesRegex(AuthorizationError, "writer_token_path_rejected"):
_validate_grant(
grant,
audience=AUDIENCE,
source_ip="127.0.0.1",
operation="commit_file.atomic",
branch=BRANCH,
path=TARGET,
workspace=self.workspace,
current_time=now,
)
def test_other_service_file_is_rejected(self):
loaded = self.write_policy()
with self.configured(loaded), self.assertRaisesRegex(
PermissionError, "isolated_writer_service_path_rejected"
):
writer_policy.validate_path(
"deploy/systemd/other.service", self.workspace
)
def test_path_traversal_is_rejected(self):
for value in ("deploy/systemd/../x", "../deploy/systemd/x"):
with self.subTest(value=value), self.assertRaises(PermissionError):
writer_policy.validate_path(value, self.workspace)
def test_url_encoded_traversal_is_rejected(self):
with self.assertRaises(PermissionError):
writer_policy.validate_path(
"deploy/systemd/%2e%2e/x", self.workspace
)
def test_symlink_policy_file_is_rejected(self):
self.write_policy()
link = self.policy_dir / "link.json"
link.symlink_to(self.policy_file)
with self.assertRaisesRegex(
path_policy.PathPolicyError, "isolated_writer_path_policy_symlink_rejected"
):
path_policy.load_path_policy(
link,
expected_owner_uid=os.getuid(),
expected_group_gid=os.getgid(),
)
def test_world_writable_policy_file_is_rejected(self):
self.write_policy()
self.policy_file.chmod(0o646)
with self.assertRaisesRegex(
path_policy.PathPolicyError,
"isolated_writer_path_policy_permissions_rejected",
):
path_policy.load_path_policy(
self.policy_file,
expected_owner_uid=os.getuid(),
expected_group_gid=os.getgid(),
)
def test_unknown_policy_field_is_rejected(self):
value = document()
value["unexpected"] = True
with self.assertRaisesRegex(
path_policy.PathPolicyError,
"isolated_writer_path_policy_fields_rejected",
):
self.write_policy(value)
def test_audience_mismatch_is_rejected(self):
loaded = self.write_policy()
os.environ["AIOS_GITOPS_PATH_POLICY_FILE"] = str(self.policy_file)
os.environ["AIOS_GITOPS_SERVICE_AUDIENCE"] = "other.service"
path_policy.clear_path_policy_cache()
with mock.patch.object(
path_policy, "load_path_policy", return_value=loaded
), self.assertRaisesRegex(
path_policy.PathPolicyError,
"isolated_writer_path_policy_audience_rejected",
):
path_policy.configured_path_policy()
def test_branch_mismatch_is_rejected(self):
loaded = self.write_policy()
os.environ["AIOS_GITOPS_PATH_POLICY_FILE"] = str(self.policy_file)
os.environ["AIOS_GITOPS_ALLOWED_BRANCH"] = "patch/other"
path_policy.clear_path_policy_cache()
with mock.patch.object(
path_policy, "load_path_policy", return_value=loaded
), self.assertRaisesRegex(
path_policy.PathPolicyError,
"isolated_writer_path_policy_branch_rejected",
):
path_policy.configured_path_policy()
def assert_profile_rejected(self, content):
loaded = self.write_policy()
with self.configured(loaded), self.assertRaisesRegex(
PermissionError, "isolated_writer_systemd_profile_rejected"
):
writer_policy.validate_content_for_path(TARGET, content)
def test_disallowed_execution_and_credential_directives_are_rejected(self):
directives = (
"ExecStartPre=/usr/bin/id",
"ExecStartPost=/usr/bin/id",
"ExecReload=/usr/bin/id",
"ExecStop=/usr/bin/id",
"ExecStopPost=/usr/bin/id",
"ExecCondition=/usr/bin/true",
"EnvironmentFile=/run/worker.env",
"PassEnvironment=PATH",
"LoadCredential=worker:/run/worker",
"SetCredential=worker:value",
"ImportCredential=worker",
"StandardInput=socket",
"StandardOutput=socket",
)
for directive in directives:
with self.subTest(directive=directive):
self.assert_profile_rejected(
VALID_UNIT.replace(
"Restart=on-failure",
f"Restart=on-failure\n{directive}",
)
)
def test_disallowed_namespace_and_capability_directives_are_rejected(self):
directives = (
"RootDirectory=/srv/worker",
"RootImage=/srv/worker.raw",
"ReadWritePaths=/",
"BindPaths=/etc:/etc",
"BindReadOnlyPaths=/etc:/etc",
"TemporaryFileSystem=/etc",
"CapabilityBoundingSet=CAP_SYS_ADMIN",
"AmbientCapabilities=CAP_SYS_ADMIN",
"DeviceAllow=/dev/sda rw",
"SupplementaryGroups=docker",
"Sockets=unsafe.socket",
)
for directive in directives:
with self.subTest(directive=directive):
self.assert_profile_rejected(
VALID_UNIT.replace(
"Restart=on-failure",
f"Restart=on-failure\n{directive}",
)
)
def test_duplicate_single_directives_and_sections_are_rejected(self):
bad_values = (
VALID_UNIT.replace("User=aiman", "User=aiman\nUser=aiman"),
VALID_UNIT.replace(
"ExecStart=/usr/bin/python3 -m task_plane.workers.codex_review_test",
"ExecStart=/usr/bin/python3 -m task_plane.workers.codex_review_test\n"
"ExecStart=/usr/bin/python3 -m task_plane.workers.codex_review_test",
),
VALID_UNIT + "\n[Service]\nType=simple\n",
)
for value in bad_values:
with self.subTest(value=value):
self.assert_profile_rejected(value)
def test_empty_reset_continuation_crlf_nul_and_long_lines_are_rejected(self):
bad_values = (
VALID_UNIT.replace(
"ExecStart=/usr/bin/python3 -m task_plane.workers.codex_review_test",
"ExecStart=",
),
VALID_UNIT.replace(
"Restart=on-failure",
"Restart=on-failure\n continuation-injection",
),
VALID_UNIT.replace("\n", "\r\n"),
VALID_UNIT.replace("Restart=on-failure", "Restart=on-failure\x00"),
VALID_UNIT.replace(
"Description=AIOS Codex Review/Test Worker",
"Description=" + ("A" * 4097),
),
)
for value in bad_values:
with self.subTest(kind=repr(value[:80])):
self.assert_profile_rejected(value)
def test_unknown_environment_and_production_execution_are_rejected(self):
bad_values = (
VALID_UNIT.replace(
"Environment=PYTHONDONTWRITEBYTECODE=1",
"Environment=UNKNOWN_KEY=value",
),
VALID_UNIT.replace(
"AIOS_TASK_PLANE_EXECUTION_ENABLED=false",
"AIOS_TASK_PLANE_EXECUTION_ENABLED=true",
),
VALID_UNIT.replace(
"Environment=PYTHONDONTWRITEBYTECODE=1",
"Environment=Authorization: Bearer placeholder",
),
)
for value in bad_values:
with self.subTest(value=value):
self.assert_profile_rejected(value)
def test_identity_working_directory_and_path_prefix_bypasses_are_rejected(self):
bad_values = (
VALID_UNIT.replace("User=aiman", "User=root"),
VALID_UNIT.replace(
"/usr/libexec/aios-codex-review-test-worker/current",
"/data/aios-gitops/worktrees/worker",
),
VALID_UNIT.replace(
"ReadWritePaths=/run/aios-codex-review-test-worker",
"ReadWritePaths=/etc",
),
VALID_UNIT.replace(
"ReadOnlyPaths=/data/aios-gitops/repo.git",
"ReadOnlyPaths=/data",
),
)
for value in bad_values:
with self.subTest(value=value):
self.assert_profile_rejected(value)
def test_unknown_section_directive_and_unicode_lookalike_are_rejected(self):
bad_values = (
VALID_UNIT.replace("[Install]", "[Socket]"),
VALID_UNIT.replace(
"Restart=on-failure",
"Restart=on-failure\nProtectClock=true",
),
VALID_UNIT.replace("ExecStart=", "\u0415xecStart="),
)
for value in bad_values:
with self.subTest(value=value):
self.assert_profile_rejected(value)
def test_invalid_unicode_surrogate_and_missing_final_newline_are_rejected(self):
for value in (VALID_UNIT + "\ud800", VALID_UNIT.rstrip("\n")):
with self.subTest(value=repr(value[-30:])):
self.assert_profile_rejected(value)
def test_writer_request_cannot_modify_policy_file(self):
with self.assertRaises(PermissionError):
writer_policy.validate_path(
"etc/aios-gitops/writer-path-policies/codex-review-worker.json",
self.workspace,
)
def test_coarse_file_and_commit_endpoints_remain_forbidden(self):
for endpoint in ("/api/file", "/api/commit"):
with self.subTest(endpoint=endpoint), self.assertRaisesRegex(
PermissionError, "isolated_writer_operation_forbidden"
):
writer_policy.validate_endpoint(
"POST", endpoint, self.workspace, self.root
)
if __name__ == "__main__":
unittest.main()
+174
View File
@@ -0,0 +1,174 @@
import os
import sys
import tempfile
import subprocess
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "active-runtime" / "app"))
import aios_isolated_writer_policy as policy
class IsolatedWriterPolicyTests(unittest.TestCase):
def setUp(self):
self.saved = dict(os.environ)
os.environ["AIOS_GITOPS_ISOLATED_WRITER_ENFORCEMENT"] = "required"
os.environ["AIOS_GITOPS_ALLOWED_BRANCH"] = "patch/aios-agent-task-control-plane-20260724"
os.environ["AIOS_GITOPS_ALLOWED_BASE_OID"] = "a8e97868976b0dce17b7cb8487eadc1d9c687622"
os.environ["AIOS_GITOPS_TOKEN_POLICY_VERSION_REQUIRED"] = "2"
os.environ["AIOS_GITOPS_SERVICE_AUDIENCE"] = "test-isolated-writer.service"
os.environ["AIOS_GITOPS_TOKEN_REGISTRY_V2"] = "/tmp/test-writer-token-registry.sqlite3"
os.environ["AIOS_GITOPS_WRITER_OPERATION_LEDGER"] = "/tmp/test-writer-operations.sqlite3"
def tearDown(self):
os.environ.clear()
os.environ.update(self.saved)
def test_only_expected_branch_is_accepted(self):
policy.validate_branch_request({
"branch": os.environ["AIOS_GITOPS_ALLOWED_BRANCH"],
"base": "main",
})
for branch in ("patch/other", "main", ""):
with self.subTest(branch=branch), self.assertRaises(PermissionError):
policy.validate_branch_request({"branch": branch, "base": "main"})
def test_write_paths_are_scoped_and_canonical(self):
with tempfile.TemporaryDirectory() as root:
workspace = Path(root)
self.assertEqual(
policy.validate_path("active-runtime/app/x.py", workspace),
"active-runtime/app/x.py",
)
self.assertEqual(policy.validate_path("docs/x.md", workspace), "docs/x.md")
self.assertEqual(
policy.validate_path("scripts/task-plane/run.sh", workspace),
"scripts/task-plane/run.sh",
)
self.assertEqual(
policy.validate_path("reports/task-plane.json", workspace),
"reports/task-plane.json",
)
rejected = (
"",
"../docs/x.md",
"/docs/x.md",
"docs/../x.md",
"%2e%2e/docs/x.md",
"%2Fdocs/x.md",
r"C:\temp\x",
r"\\server\share\x",
"./docs/x.md",
"docs//x.md",
"docs/./x.md",
"docs/x.md\x00tail",
"baseline/current.json",
)
for value in rejected:
with self.subTest(value=value), self.assertRaises(PermissionError):
policy.validate_path(value, workspace)
@unittest.skipIf(os.name == "nt", "symlink creation requires Windows privilege")
def test_external_symlink_is_rejected(self):
with tempfile.TemporaryDirectory() as root, tempfile.TemporaryDirectory() as outside:
workspace = Path(root)
(workspace / "docs").symlink_to(Path(outside), target_is_directory=True)
with self.assertRaises(PermissionError):
policy.validate_path("docs/escape.md", workspace)
def test_force_and_forbidden_endpoints_are_rejected(self):
with self.assertRaises(PermissionError):
policy.validate_branch_request({
"branch": os.environ["AIOS_GITOPS_ALLOWED_BRANCH"],
"force": True,
})
for endpoint in (
"/api/tag",
"/api/rollback",
"/api/model",
"/api/model-priority",
"/api/baseline",
"/api/registry",
"/api/deploy",
):
self.assertNotIn(endpoint, policy.WRITE_ENDPOINTS)
@unittest.skipIf(os.name == "nt", "authoritative writer runs on POSIX")
def test_approved_base_is_ancestor_even_when_main_drifts(self):
with tempfile.TemporaryDirectory() as root:
workspace = Path(root) / "writer"
workspace.mkdir()
subprocess.run(["git", "init"], cwd=workspace, check=True, capture_output=True)
subprocess.run(
["git", "config", "user.email", "task-plane@example.invalid"],
cwd=workspace,
check=True,
)
subprocess.run(
["git", "config", "user.name", "Task Plane Test"],
cwd=workspace,
check=True,
)
(workspace / "README.md").write_text("base\n", encoding="utf-8")
subprocess.run(["git", "add", "README.md"], cwd=workspace, check=True)
subprocess.run(["git", "commit", "-m", "base"], cwd=workspace, check=True, capture_output=True)
base = subprocess.check_output(
["git", "rev-parse", "HEAD"], cwd=workspace, text=True
).strip()
branch = os.environ["AIOS_GITOPS_ALLOWED_BRANCH"]
subprocess.run(["git", "checkout", "-b", branch], cwd=workspace, check=True, capture_output=True)
(workspace / "README.md").write_text("branch\n", encoding="utf-8")
subprocess.run(["git", "commit", "-am", "branch"], cwd=workspace, check=True, capture_output=True)
os.environ.update(
{
"AIOS_GITOPS_ALLOWED_BASE_OID": base,
"AIOS_GITOPS_WRITES_ENABLED": "true",
"AIOS_GITOPS_RUNTIME_ROLE": "isolated-writer",
"AIOS_GITOPS_WORKSPACE_ROLE": "isolated-writer",
"AIOS_GITOPS_REPOSITORY_IDENTITY": "aios/aios",
"AIOS_GITOPS_AUTHORITATIVE_GIT_DIR": str(workspace / ".git"),
"AIOS_GITOPS_APPROVED_WRITER_ROOT": str(Path(root)),
}
)
for name in policy._DENY_FLAGS:
os.environ[name] = "false"
policy.require_service_invariants(workspace, workspace / ".git")
@unittest.skipIf(os.name == "nt", "POSIX audit mode and O_NOFOLLOW contract")
def test_audit_path_is_contained_durable_and_redacted(self):
with tempfile.TemporaryDirectory() as root:
approved = Path(root) / "approved"
approved.mkdir(mode=0o750)
log = approved / "writer.jsonl"
os.environ["AIOS_GITOPS_APPROVED_AUDIT_ROOT"] = str(approved)
os.environ["AIOS_GITOPS_AUDIT_LOG"] = str(log)
policy.append_audit_record(
{
"action": "commit",
"Authorization": "Bearer must-not-appear",
"payload": "token=must-not-appear",
}
)
text = log.read_text(encoding="utf-8")
self.assertNotIn("must-not-appear", text)
self.assertEqual(0o600, log.stat().st_mode & 0o777)
os.environ["AIOS_GITOPS_AUDIT_LOG"] = str(Path(root) / "escape.jsonl")
with self.assertRaises(PermissionError):
policy.approved_audit_log_path()
class SecretScannerBoundaryTests(unittest.TestCase):
def test_task_control_plane_branch_is_not_an_api_key(self):
import aios_chatgpt_actions_api as api
payload = (
'{"branch":"patch/aios-agent-task-control-plane-20260724",'
'"created_by":"agent-task-plane-writer"}'
)
self.assertFalse(api.content_has_secret(payload))
def test_real_openai_style_key_is_still_rejected(self):
import aios_chatgpt_actions_api as api
payload = '{"value":"sk-abcdefghijklmnopqrstuvwxyz123456"}'
self.assertTrue(api.content_has_secret(payload))
@@ -0,0 +1,471 @@
import hashlib
import json
import os
import sqlite3
import subprocess
import sys
import tempfile
import threading
import time
import unittest
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "active-runtime" / "app"))
import aios_isolated_writer_policy as policy
from isolated_writer_authz import AuthorizationError, authorize, operation_for
from isolated_writer_commit_file import (
AtomicCommitFile,
CommitFileError,
OperationLedger,
SimulatedCrash,
)
from isolated_writer_token_registry import TokenRegistry, token_digest
BRANCH = "patch/codex-review-test-worker-20260724"
AUDIENCE = "aios-codex-review-worker-writer.service"
TASK_ID = "AIOS-CODEX-WRITER-SMOKE-20260724"
TARGET_PATH = "docs/reliability/codex-review-worker-writer-smoke.json"
def run(cwd, *args):
return subprocess.check_output(args, cwd=cwd, text=True).strip()
@dataclass
class DummyGrant:
token_id: str = "test-token-id"
task_id: str = TASK_ID
operation: str = "commit_file.atomic"
record: dict = None
def __post_init__(self):
if self.record is None:
self.record = {"audience": AUDIENCE, "_source_ip": "127.0.0.1"}
class RepositoryCase(unittest.TestCase):
def setUp(self):
self.saved = dict(os.environ)
self.temp = tempfile.TemporaryDirectory()
self.root = Path(self.temp.name)
self.authoritative = self.root / "authoritative.git"
self.gitea = self.root / "gitea.git"
self.seed = self.root / "seed"
self.workspace = self.root / "writer"
subprocess.run(["git", "init", "--bare", str(self.authoritative)], check=True, capture_output=True)
subprocess.run(["git", "init", str(self.seed)], check=True, capture_output=True)
subprocess.run(["git", "config", "user.email", "writer@example.invalid"], cwd=self.seed, check=True)
subprocess.run(["git", "config", "user.name", "Writer Test"], cwd=self.seed, check=True)
(self.seed / "README.md").write_text("base\n", encoding="utf-8")
subprocess.run(["git", "add", "README.md"], cwd=self.seed, check=True)
subprocess.run(["git", "commit", "-m", "base"], cwd=self.seed, check=True, capture_output=True)
subprocess.run(["git", "branch", "-M", "main"], cwd=self.seed, check=True)
subprocess.run(["git", "remote", "add", "origin", str(self.authoritative)], cwd=self.seed, check=True)
subprocess.run(["git", "push", "origin", "main"], cwd=self.seed, check=True, capture_output=True)
self.base = run(self.seed, "git", "rev-parse", "HEAD")
subprocess.run(["git", "checkout", "-b", BRANCH], cwd=self.seed, check=True, capture_output=True)
subprocess.run(["git", "push", "origin", BRANCH], cwd=self.seed, check=True, capture_output=True)
subprocess.run(
["git", "--git-dir", str(self.authoritative), "worktree", "add", str(self.workspace), BRANCH],
check=True,
capture_output=True,
)
subprocess.run(["git", "config", "user.email", "writer@example.invalid"], cwd=self.workspace, check=True)
subprocess.run(["git", "config", "user.name", "Writer Test"], cwd=self.workspace, check=True)
subprocess.run(["git", "clone", "--bare", str(self.authoritative), str(self.gitea)], check=True, capture_output=True)
subprocess.run(["git", "remote", "add", "gitea-http", str(self.gitea)], cwd=self.workspace, check=True)
self.registry = TokenRegistry(self.root / "registry.sqlite3")
os.environ.update({
"AIOS_GITOPS_ALLOWED_BRANCH": BRANCH,
"AIOS_GITOPS_TOKEN_POLICY_VERSION_REQUIRED": "2",
"AIOS_GITOPS_SERVICE_AUDIENCE": AUDIENCE,
"AIOS_GITOPS_ISOLATED_WRITER_ENFORCEMENT": "required",
})
def tearDown(self):
os.environ.clear()
os.environ.update(self.saved)
self.temp.cleanup()
def grant(self, **updates):
now = datetime.now(timezone.utc)
grant = {
"token_id": "smoke-" + hashlib.sha256(os.urandom(16)).hexdigest()[:12],
"audience": AUDIENCE,
"task_id": TASK_ID,
"source_cidrs": ["127.0.0.1/32", "::1/128"],
"allowed_branch": BRANCH,
"approved_base_oid": self.base,
"allowed_operations": [
"runtime.status.read",
"baseline.read",
"patches.read",
"commit_file.atomic",
],
"allowed_paths": [TARGET_PATH],
"not_before": (now - timedelta(seconds=2)).isoformat(),
"expires_at": (now + timedelta(minutes=20)).isoformat(),
"max_uses": {
"runtime.status.read": 2,
"baseline.read": 2,
"patches.read": 2,
"commit_file.atomic": 4,
},
"issued_by": "owner-authorized-local-maintenance-executor",
"created_at": (now - timedelta(seconds=3)).isoformat(),
}
grant.update(updates)
return grant
def issue(self, **updates):
return self.registry.issue(self.grant(**updates))
def auth(self, token, **updates):
args = {
"method": "GET",
"path": "/api/runtime-status",
"source_ip": "127.0.0.1",
"workspace": self.workspace,
}
args.update(updates)
return authorize(self.registry, token, **args)
def request(self, key="writer-smoke-0001", content='{"status":"READY"}\n'):
return {
"branch": BRANCH,
"path": TARGET_PATH,
"content": content,
"message": "chore(task-plane): verify Codex writer endpoint",
"expected_head_oid": run(self.workspace, "git", "rev-parse", "HEAD"),
"idempotency_key": key,
}
def writer(self, crash=None, remote="gitea-http", audit=None):
return AtomicCommitFile(
workspace=self.workspace,
bare_repo=self.authoritative,
ledger=OperationLedger(self.root / "operations.sqlite3"),
remote=remote,
audit=audit or (lambda record: None),
crash_after_state=crash,
)
class TokenPolicyV2Tests(RepositoryCase):
def test_v1_coarse_scope_token_is_rejected(self):
token, record = self.issue()
record["schema_version"] = 1
with self.registry._connect() as db:
db.execute(
"UPDATE token_grants SET grant_json=? WHERE token_id=?",
(json.dumps(record), record["token_id"]),
)
with self.assertRaisesRegex(AuthorizationError, "writer_token_policy_v2_required"):
self.auth(token)
def test_audience_mismatch_is_rejected(self):
token, _ = self.issue(audience="other.service")
with self.assertRaisesRegex(AuthorizationError, "writer_token_audience_rejected"):
self.auth(token)
def test_source_ip_mismatch_is_rejected(self):
token, _ = self.issue(source_cidrs=["192.0.2.0/24"])
with self.assertRaisesRegex(AuthorizationError, "writer_token_source_rejected"):
self.auth(token)
def test_branch_mismatch_is_rejected(self):
token, _ = self.issue(allowed_branch="patch/other")
with self.assertRaisesRegex(AuthorizationError, "writer_token_branch_rejected"):
self.auth(token)
def test_operation_mismatch_is_rejected(self):
token, _ = self.issue(allowed_operations=["baseline.read"])
with self.assertRaisesRegex(AuthorizationError, "writer_token_operation_rejected"):
self.auth(token)
def test_path_mismatch_is_rejected(self):
token, _ = self.issue()
with self.assertRaisesRegex(AuthorizationError, "writer_token_path_rejected"):
self.auth(
token,
method="POST",
path="/api/commit-file",
branch=BRANCH,
target_path="docs/other.json",
)
def test_expired_token_is_rejected(self):
now = datetime.now(timezone.utc)
token, _ = self.issue(
created_at=(now - timedelta(minutes=10)).isoformat(),
not_before=(now - timedelta(minutes=9)).isoformat(),
expires_at=(now - timedelta(seconds=1)).isoformat(),
)
with self.assertRaisesRegex(AuthorizationError, "writer_token_expired"):
self.auth(token)
def test_not_before_token_is_rejected(self):
now = datetime.now(timezone.utc)
token, _ = self.issue(
created_at=now.isoformat(),
not_before=(now + timedelta(minutes=1)).isoformat(),
expires_at=(now + timedelta(minutes=5)).isoformat(),
)
with self.assertRaisesRegex(AuthorizationError, "writer_token_not_yet_valid"):
self.auth(token)
def test_revoked_token_is_rejected(self):
token, grant = self.issue()
self.assertTrue(self.registry.revoke(grant["token_id"]))
with self.assertRaisesRegex(AuthorizationError, "writer_token_revoked"):
self.auth(token)
def test_operation_use_count_is_atomic_and_exhausted(self):
token, _ = self.issue(max_uses={
"runtime.status.read": 1,
"baseline.read": 1,
"patches.read": 1,
"commit_file.atomic": 1,
})
self.auth(token)
with self.assertRaisesRegex(AuthorizationError, "writer_token_operation_exhausted"):
self.auth(token)
def test_concurrent_use_count_never_exceeds_limit(self):
token, _ = self.issue(max_uses={
"runtime.status.read": 2,
"baseline.read": 1,
"patches.read": 1,
"commit_file.atomic": 1,
})
successes, failures = [], []
def invoke():
try:
successes.append(self.auth(token))
except AuthorizationError as exc:
failures.append(exc.code)
threads = [threading.Thread(target=invoke) for _ in range(6)]
for thread in threads:
thread.start()
for thread in threads:
thread.join()
self.assertEqual(2, len(successes))
self.assertEqual(4, failures.count("writer_token_operation_exhausted"))
def test_registry_does_not_store_plaintext_token(self):
token, _ = self.issue()
registry_bytes = b"".join(
item.read_bytes()
for item in self.registry.path.parent.glob(self.registry.path.name + "*")
if item.is_file()
)
self.assertNotIn(token.encode(), registry_bytes)
with self.registry._connect() as db:
stored = db.execute("SELECT token_digest FROM token_grants").fetchone()[0]
self.assertEqual(token_digest(token), stored)
def test_coarse_routes_have_no_operation_mapping(self):
for path in ("/api/file", "/api/commit", "/api/tag", "/api/rollback", "/api/model"):
with self.subTest(path=path), self.assertRaises(AuthorizationError) as captured:
operation_for("POST", path)
self.assertEqual(403, captured.exception.status)
def test_route_mapping_is_fixed_not_request_controlled(self):
self.assertEqual("runtime.status.read", operation_for("GET", "/api/runtime-status"))
self.assertEqual("baseline.read", operation_for("GET", "/api/baseline"))
self.assertEqual("patches.read", operation_for("GET", "/api/patches"))
self.assertEqual("commit_file.atomic", operation_for("POST", "/api/commit-file"))
def test_x_forwarded_for_cannot_replace_socket_peer(self):
token, _ = self.issue(source_cidrs=["203.0.113.9/32"])
with self.assertRaisesRegex(AuthorizationError, "writer_token_source_rejected"):
self.auth(token, source_ip="127.0.0.1")
class AtomicCommitFileTests(RepositoryCase):
def test_commit_file_succeeds_and_verifies_bare_and_gitea(self):
result = self.writer().execute(DummyGrant(), self.request())
self.assertEqual("COMPLETED", result["state"])
self.assertEqual(
result["commit_oid"],
run(self.root, "git", "--git-dir", str(self.gitea), "rev-parse", f"refs/heads/{BRANCH}"),
)
def test_same_idempotency_key_returns_same_commit(self):
request = self.request()
first = self.writer().execute(DummyGrant(), request)
count = run(self.workspace, "git", "rev-list", "--count", "HEAD")
second = self.writer().execute(DummyGrant(), request)
self.assertEqual(first["commit_oid"], second["commit_oid"])
self.assertEqual(count, run(self.workspace, "git", "rev-list", "--count", "HEAD"))
self.assertTrue(second["idempotent"])
def test_same_key_with_different_payload_returns_409(self):
request = self.request()
self.writer().execute(DummyGrant(), request)
request["content"] = '{"status":"DIFFERENT"}\n'
with self.assertRaises(CommitFileError) as captured:
self.writer().execute(DummyGrant(), request)
self.assertEqual(409, captured.exception.status)
def test_same_key_from_different_task_returns_409(self):
request = self.request()
self.writer().execute(DummyGrant(), request)
other = DummyGrant(token_id="other-token", task_id="OTHER-TASK")
with self.assertRaisesRegex(CommitFileError, "writer_idempotency_owner_conflict"):
self.writer().execute(other, request)
def test_file_written_crash_is_recoverable(self):
request = self.request()
with self.assertRaises(SimulatedCrash):
self.writer(crash="FILE_WRITTEN").execute(DummyGrant(), request)
result = self.writer().execute(DummyGrant(), request)
self.assertEqual("COMPLETED", result["state"])
def test_committed_crash_is_recoverable_without_second_commit(self):
request = self.request()
with self.assertRaises(SimulatedCrash):
self.writer(crash="COMMITTED").execute(DummyGrant(), request)
committed = run(self.workspace, "git", "rev-parse", "HEAD")
result = self.writer().execute(DummyGrant(), request)
self.assertEqual(committed, result["commit_oid"])
def test_pushed_crash_only_reverifies(self):
request = self.request()
with self.assertRaises(SimulatedCrash):
self.writer(crash="PUSHED").execute(DummyGrant(), request)
result = self.writer().execute(DummyGrant(), request)
self.assertEqual("COMPLETED", result["state"])
def test_push_failure_is_failed_recoverable(self):
request = self.request()
with self.assertRaisesRegex(CommitFileError, "writer_gitea_push_failed"):
self.writer(remote="missing-remote").execute(DummyGrant(), request)
row = OperationLedger(self.root / "operations.sqlite3").get(request["idempotency_key"])
self.assertEqual("FAILED_RECOVERABLE", row["state"])
def test_branch_head_conflict_requires_manual_review(self):
request = self.request()
ledger = OperationLedger(self.root / "operations.sqlite3")
ledger.get_or_create(DummyGrant(), request, __import__("isolated_writer_commit_file").canonical_digest(request))
(self.workspace / "README.md").write_text("drift\n", encoding="utf-8")
subprocess.run(["git", "commit", "-am", "drift"], cwd=self.workspace, check=True, capture_output=True)
with self.assertRaisesRegex(CommitFileError, "writer_branch_head_conflict"):
self.writer().execute(DummyGrant(), request)
self.assertEqual("MANUAL_REVIEW_REQUIRED", ledger.get(request["idempotency_key"])["state"])
def test_extra_staged_file_is_rejected(self):
(self.workspace / "README.md").write_text("staged\n", encoding="utf-8")
subprocess.run(["git", "add", "README.md"], cwd=self.workspace, check=True)
with self.assertRaisesRegex(CommitFileError, "writer_extra_staged_files_rejected"):
self.writer().execute(DummyGrant(), self.request())
def test_path_traversal_and_protected_roots_are_rejected(self):
for path in (
"../docs/x", "%2e%2e/docs/x", "/etc/x", r"C:\temp\x",
r"\\server\share\x", "baseline/current.json", "runtime/x",
):
request = self.request(key="writer-path-" + hashlib.sha256(path.encode()).hexdigest()[:12])
request["path"] = path
with self.subTest(path=path), self.assertRaises((PermissionError, CommitFileError)):
self.writer().execute(DummyGrant(), request)
def test_audit_contains_ids_but_not_content_or_authorization(self):
records = []
request = self.request(content='{"value":"non-secret-body-marker"}\n')
self.writer(audit=records.append).execute(DummyGrant(), request)
encoded = json.dumps(records)
self.assertIn("test-token-id", encoded)
self.assertNotIn("non-secret-body-marker", encoded)
self.assertNotIn("Authorization", encoded)
def test_concurrent_same_idempotency_creates_one_commit(self):
request = self.request()
results, errors = [], []
def invoke():
try:
results.append(self.writer().execute(DummyGrant(), request))
except Exception as exc:
errors.append(exc)
threads = [threading.Thread(target=invoke) for _ in range(4)]
for thread in threads:
thread.start()
for thread in threads:
thread.join()
self.assertFalse(errors)
self.assertEqual(1, len({item["commit_oid"] for item in results}))
def test_different_operations_are_serialized_by_branch_lock(self):
active = 0
maximum = 0
guard = threading.Lock()
class Probe(AtomicCommitFile):
def _execute_locked(inner, grant, data, digest):
nonlocal active, maximum
with guard:
active += 1
maximum = max(maximum, active)
time.sleep(0.05)
with guard:
active -= 1
return {"state": "PROBED"}
probe = Probe(
workspace=self.workspace,
bare_repo=self.authoritative,
ledger=OperationLedger(self.root / "probe.sqlite3"),
remote="gitea-http",
audit=lambda record: None,
)
requests = [self.request(key=f"writer-lock-{index:04d}") for index in range(3)]
threads = [threading.Thread(target=probe.execute, args=(DummyGrant(), item)) for item in requests]
for thread in threads:
thread.start()
for thread in threads:
thread.join()
self.assertEqual(1, maximum)
def test_invalid_expected_head_and_idempotency_are_rejected(self):
request = self.request()
request["expected_head_oid"] = "short"
with self.assertRaisesRegex(CommitFileError, "writer_expected_head_rejected"):
self.writer().execute(DummyGrant(), request)
request = self.request()
request["idempotency_key"] = "../bad"
with self.assertRaisesRegex(CommitFileError, "writer_idempotency_key_rejected"):
self.writer().execute(DummyGrant(), request)
def test_invalid_message_and_nul_content_are_rejected(self):
request = self.request()
request["message"] = "bad\nmessage"
with self.assertRaisesRegex(CommitFileError, "writer_message_rejected"):
self.writer().execute(DummyGrant(), request)
request = self.request()
request["content"] = "bad\x00content"
with self.assertRaisesRegex(CommitFileError, "writer_content_rejected"):
self.writer().execute(DummyGrant(), request)
def test_force_flags_are_403_and_unknown_fields_are_rejected(self):
for field in ("force", "force_push"):
request = self.request(key=f"writer-force-{field}")
request[field] = True
with self.subTest(field=field), self.assertRaises(CommitFileError) as captured:
self.writer().execute(DummyGrant(), request)
self.assertEqual(403, captured.exception.status)
request = self.request(key="writer-extra-field")
request["operation"] = "commit_file.atomic"
with self.assertRaisesRegex(CommitFileError, "writer_request_fields_rejected"):
self.writer().execute(DummyGrant(), request)
if __name__ == "__main__":
unittest.main()
+8 -1
View File
@@ -154,6 +154,7 @@ class SilentReleaseControllerTests(unittest.TestCase):
ApprovalLedger(Path(td) / "approvals"),
SilentReleasePolicy.default(),
store,
allow_legacy_unsigned_approval=True,
),
store=store,
policy=SilentReleasePolicy.default(),
@@ -248,6 +249,7 @@ class SilentReleaseControllerTests(unittest.TestCase):
deployment,
ApprovalLedger(Path(td) / "approvals"),
SilentReleasePolicy.default(),
allow_legacy_unsigned_approval=True,
)
result = controller.deploy(value, envelope)
self.assertEqual(value.state, ReleaseState.VERIFIED)
@@ -273,6 +275,7 @@ class SilentReleaseControllerTests(unittest.TestCase):
deployment,
ApprovalLedger(Path(td) / "approvals"),
SilentReleasePolicy.default(),
allow_legacy_unsigned_approval=True,
).deploy(value, envelope)
self.assertFalse(result["verified"])
self.assertEqual(value.state, ReleaseState.ROLLED_BACK)
@@ -292,7 +295,10 @@ class SilentReleaseControllerTests(unittest.TestCase):
with tempfile.TemporaryDirectory() as td:
ledger = ApprovalLedger(Path(td) / "approvals")
controller = DeploymentController(
FakeDeployment(), ledger, SilentReleasePolicy.default()
FakeDeployment(),
ledger,
SilentReleasePolicy.default(),
allow_legacy_unsigned_approval=True,
)
controller.deploy(value, envelope)
second = candidate()
@@ -309,6 +315,7 @@ class SilentReleaseControllerTests(unittest.TestCase):
adapter,
SilentReleasePolicy.default(),
ledger=None,
allow_legacy_unsigned_approval=True,
)
envelope = TagApprovalEnvelope(
approval_id="tag-approval-001",
@@ -0,0 +1,728 @@
from __future__ import annotations
import dataclasses
import json
import sys
import threading
import urllib.request
from datetime import datetime, timedelta, timezone
from http.server import ThreadingHTTPServer
from pathlib import Path
import pytest
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import (
Ed25519PrivateKey,
)
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "active-runtime" / "app"))
sys.path.insert(0, str(ROOT / "scripts"))
from silent_release_hardening import ( # noqa: E402
CapabilityAuthorizationError,
CapabilityController,
CapabilityLedger,
ClaimProbeResult,
ApprovalKeyRing,
ApprovalRevocationStore,
BuilderAttestationVerifier,
DeploymentPlan,
FinalizationPlan,
ProbeEvidence,
ReleasePlan,
SignedApprovalVerifier,
SignedReleasePlanApproval,
SilentReleaseActionService,
TagPlan,
TagPublicationProbeEvidence,
TrustedCandidateBundle,
TrustedReleasePolicy,
classify_tag_publication,
)
from silent_release_controller import ReleaseCandidate, ReleaseState # noqa: E402
from silent_release_execution import ( # noqa: E402
LocalTestApprovalSigner,
LocalTestBuilderSigner,
PersistentSilentReleaseController,
ShadowActionHandler,
SignedTrustedBuilderAdapter,
UnavailableProductionApprovalBackend,
)
from build_silent_release_artifact import ( # noqa: E402
redact_output,
secret_scan,
)
COMMIT = "1" * 40
TAG_OID = "2" * 40
def approval() -> SignedReleasePlanApproval:
return SignedReleasePlanApproval(
release_plan_id="release-plan-readiness-001",
candidate_id="rc-readiness-001",
source_commit=COMMIT,
tree_oid="3" * 40,
artifact_sha256="4" * 64,
manifest_sha256="5" * 64,
deployment_plan_sha256="6" * 64,
policy_sha256="7" * 64,
approval_plan_id="owner-plan-readiness-001",
evidence_closure_oid="8" * 40,
owner_approval_record_oid="9" * 40,
capabilities=(
"runtime_switch",
"tag_publish",
"baseline_registry_finalize",
),
expires_at="2099-01-01T00:00:00Z",
nonce="nonce-readiness-000000001",
key_id="owner-key-readiness-1",
signature="test-signature",
)
def tag_plan() -> TagPlan:
return TagPlan(
version="shadow-readiness-001",
tag_type="annotated",
expected_tag_oid=TAG_OID,
expected_target_oid=COMMIT,
evidence_closure_oid="8" * 40,
approval_plan_id="owner-plan-readiness-001",
repository_identity="aios/aios",
gitea_owner="aios",
gitea_repository="aios",
current_remote_tag_oid=None,
remote_absent_evidence_sha256="a" * 64,
verification_contract="git+api+listing/v1",
)
def make_failed_claim(
ledger: CapabilityLedger,
*,
operation: str,
) -> dict:
claimed = ledger.claim(
approval(),
"runtime_switch",
operation=operation,
executor_id="worker-initial",
lease_seconds=10,
now=100,
)
ledger.fail_recoverable(
approval(),
"runtime_switch",
operation=operation,
claim_id=claimed["claim_id"],
reason="adapter_error",
)
return claimed
def test_failed_recoverable_not_applied_retries_once(tmp_path):
ledger = CapabilityLedger(
tmp_path / "ledger", maximum_probe_age_seconds=30
)
make_failed_claim(ledger, operation="runtime-retry")
calls: list[str] = []
controller = CapabilityController(
capability="runtime_switch",
ledger=ledger,
adapter=lambda: calls.append("adapter") or {"verified": True},
recovery_probe=lambda: ClaimProbeResult.NOT_APPLIED,
executor_id="worker-recovery",
)
assert controller.execute(
approval(), operation="runtime-retry"
)["status"] == "EXECUTED"
assert controller.execute(
approval(), operation="runtime-retry"
)["status"] == "IDEMPOTENT"
assert calls == ["adapter"]
def test_failed_recoverable_applied_verified_skips_adapter(tmp_path):
ledger = CapabilityLedger(tmp_path / "ledger")
make_failed_claim(ledger, operation="runtime-applied")
calls: list[str] = []
controller = CapabilityController(
capability="runtime_switch",
ledger=ledger,
adapter=lambda: calls.append("adapter") or {"verified": True},
recovery_probe=lambda: ClaimProbeResult.APPLIED_VERIFIED,
executor_id="worker-recovery",
)
assert controller.execute(
approval(), operation="runtime-applied"
)["status"] == "IDEMPOTENT"
assert calls == []
@pytest.mark.parametrize(
("probe", "expected"),
(
(
ClaimProbeResult.INDETERMINATE,
"RECONCILIATION_REQUIRED",
),
(
ClaimProbeResult.CONFLICT,
"MANUAL_OWNER_REVIEW_REQUIRED",
),
),
)
def test_failed_recoverable_unknown_or_conflict_stops_writes(
tmp_path, probe, expected
):
ledger = CapabilityLedger(tmp_path / "ledger")
make_failed_claim(ledger, operation=f"runtime-{probe.value.lower()}")
calls: list[str] = []
controller = CapabilityController(
capability="runtime_switch",
ledger=ledger,
adapter=lambda: calls.append("adapter") or {"verified": True},
recovery_probe=lambda: probe,
executor_id="worker-recovery",
)
assert controller.execute(
approval(), operation=f"runtime-{probe.value.lower()}"
)["status"] == expected
assert calls == []
def test_probe_must_bind_operation_and_be_fresh(tmp_path):
ledger = CapabilityLedger(
tmp_path / "ledger", maximum_probe_age_seconds=10
)
make_failed_claim(ledger, operation="runtime-freshness")
record = ledger.read(
approval(), "runtime_switch", operation="runtime-freshness"
)
assert record is not None
with pytest.raises(
CapabilityAuthorizationError, match="operation digest"
):
ledger.claim(
approval(),
"runtime_switch",
operation="runtime-freshness",
executor_id="worker-recovery",
lease_seconds=10,
now=120,
side_effect_probe=lambda: ProbeEvidence(
result=ClaimProbeResult.NOT_APPLIED,
operation_digest="0" * 64,
observed_at=120,
),
)
with pytest.raises(CapabilityAuthorizationError, match="freshness"):
ledger.claim(
approval(),
"runtime_switch",
operation="runtime-freshness",
executor_id="worker-recovery",
lease_seconds=10,
now=120,
side_effect_probe=lambda: ProbeEvidence(
result=ClaimProbeResult.NOT_APPLIED,
operation_digest=record["operation_digest"],
observed_at=100,
),
)
def test_probe_journal_is_hash_chained_and_tamper_fails_closed(tmp_path):
ledger = CapabilityLedger(tmp_path / "ledger")
make_failed_claim(ledger, operation="runtime-journal")
ledger.claim(
approval(),
"runtime_switch",
operation="runtime-journal",
executor_id="worker-recovery",
lease_seconds=10,
now=101,
side_effect_probe=lambda: ClaimProbeResult.INDETERMINATE,
)
operation_path = ledger._operation_path(
approval(), "runtime_switch", "runtime-journal"
)
records = ledger.verify_probe_journal(operation_path)
assert records[0]["payload"]["result"] == "INDETERMINATE"
journal = ledger._probe_journal_path(operation_path)
payload = json.loads(journal.read_text(encoding="utf-8"))
payload["payload"]["result"] = "NOT_APPLIED"
journal.write_text(json.dumps(payload) + "\n", encoding="utf-8")
with pytest.raises(Exception, match="hash chain"):
ledger.verify_probe_journal(operation_path)
def tag_evidence(**overrides) -> TagPublicationProbeEvidence:
values = {
"repository_identity": "aios/aios",
"remote_reachable": True,
"gitea_api_reachable": True,
"gitea_listing_reachable": True,
"remote_tag_oid": TAG_OID,
"annotated_tag_object_oid": TAG_OID,
"remote_peeled_target_oid": COMMIT,
"gitea_api_tag_oid": TAG_OID,
"gitea_api_peeled_target_oid": COMMIT,
"gitea_listing_visible": True,
"gitea_listing_tag_oid": TAG_OID,
"gitea_listing_peeled_target_oid": COMMIT,
"observed_at": 100,
}
values.update(overrides)
return TagPublicationProbeEvidence(**values)
def test_tag_probe_exact_absence_is_not_applied():
evidence = tag_evidence(
remote_tag_oid=None,
annotated_tag_object_oid=None,
remote_peeled_target_oid=None,
gitea_api_tag_oid=None,
gitea_api_peeled_target_oid=None,
gitea_listing_visible=False,
gitea_listing_tag_oid=None,
gitea_listing_peeled_target_oid=None,
)
assert (
classify_tag_publication(evidence, tag_plan())
== ClaimProbeResult.NOT_APPLIED
)
def test_tag_probe_exact_publication_is_applied_verified():
assert (
classify_tag_publication(tag_evidence(), tag_plan())
== ClaimProbeResult.APPLIED_VERIFIED
)
def test_tag_probe_unavailable_or_divergent_is_indeterminate():
assert (
classify_tag_publication(
tag_evidence(gitea_api_reachable=False), tag_plan()
)
== ClaimProbeResult.INDETERMINATE
)
assert (
classify_tag_publication(
tag_evidence(gitea_listing_tag_oid=None), tag_plan()
)
== ClaimProbeResult.INDETERMINATE
)
def test_tag_probe_same_name_different_oid_is_conflict():
assert (
classify_tag_publication(
tag_evidence(remote_tag_oid="f" * 40), tag_plan()
)
== ClaimProbeResult.CONFLICT
)
assert (
classify_tag_publication(
tag_evidence(remote_peeled_target_oid="e" * 40), tag_plan()
)
== ClaimProbeResult.CONFLICT
)
def readiness_release_plan(source_commit: str = COMMIT) -> ReleasePlan:
policy_sha = "7" * 64
return ReleasePlan(
deployment=DeploymentPlan(
service="aios-silent-release-controller",
release_path="/usr/libexec/aios-silent-release-shadow/release-1",
release_root="/usr/libexec/aios-silent-release-shadow",
cutover_link="/usr/libexec/aios-silent-release-shadow/current",
dropin_path=(
"/usr/libexec/aios-silent-release-shadow/release-1/"
"deploy/systemd/aios-silent-release-controller.service"
),
dropin_sha256="a" * 64,
systemd_unit_sha256="a" * 64,
rollback_path=(
"/usr/libexec/aios-silent-release-shadow/rollback-1"
),
rollback_sha256="b" * 64,
source_commit=source_commit,
tree_oid="3" * 40,
artifact_sha256="4" * 64,
manifest_sha256="5" * 64,
package_closure_sha256="c" * 64,
authoritative_git_dir="/data/aios-gitops/repo.git",
repository_identity="aios/aios",
writer_expected=False,
baseline_before_sha256="d" * 64,
registry_before_sha256="e" * 64,
production_tag_refset_before_sha256="f" * 64,
health_contract_version="shadow-health/v1",
timeout_policy={"health_seconds": 10},
retry_policy={"health_attempts": 1},
policy_sha256=policy_sha,
health_contract={"shadow_mode": True},
),
tag=TagPlan(
version="shadow-readiness-001",
tag_type="annotated",
expected_tag_oid=TAG_OID,
expected_target_oid=source_commit,
evidence_closure_oid="8" * 40,
approval_plan_id="owner-plan-readiness-001",
repository_identity="aios/aios",
gitea_owner="aios",
gitea_repository="aios",
current_remote_tag_oid=None,
remote_absent_evidence_sha256="a" * 64,
verification_contract="shadow-only/v1",
),
finalization=FinalizationPlan(
version="shadow-readiness-001",
expected_tag_oid=TAG_OID,
expected_target_oid=source_commit,
expected_previous_baseline_sha256="d" * 64,
registry_expected_record={
"shadow": True,
"target_oid": source_commit,
},
baseline_expected_record={
"shadow": True,
"target_oid": source_commit,
},
final_consistency_contract="shadow-simulated/v1",
),
policy_sha256=policy_sha,
)
def readiness_candidate(source_commit: str = COMMIT) -> ReleaseCandidate:
return ReleaseCandidate(
candidate_id="rc-shadow-" + source_commit[:12],
source_commit=source_commit,
tree_oid="3" * 40,
artifact_sha256="4" * 64,
manifest_sha256="5" * 64,
tests={"passed": True, "source": "trusted-builder"},
canary={"passed": True, "without_git": True},
rollback_ready=True,
git_consistency={"consistent": True},
gitea_consistency={"consistent": True},
approval_state="PENDING",
state=ReleaseState.READY_FOR_APPROVAL,
)
def trusted_service(tmp_path):
builder_signer = LocalTestBuilderSigner.generate()
builder_public = builder_signer.public_key.public_bytes(
serialization.Encoding.Raw,
serialization.PublicFormat.Raw,
)
def collector(intent):
source_commit = str(intent["source_commit"])
return TrustedCandidateBundle(
candidate=readiness_candidate(source_commit),
release_plan=readiness_release_plan(source_commit),
checks={
"authoritative_git": True,
"artifact": True,
"canary": True,
"gitea": True,
"manifest": True,
"package_closure": True,
"rollback": True,
"secret_scan": True,
"tests": True,
},
)
owner_private = Ed25519PrivateKey.generate()
owner_public = owner_private.public_key().public_bytes(
serialization.Encoding.Raw,
serialization.PublicFormat.Raw,
)
verifier = SignedApprovalVerifier(
ApprovalKeyRing({"shadow-owner-test-key": owner_public}),
ApprovalRevocationStore(tmp_path / "revocations"),
allowed_trust_domains={"aios.shadow"},
)
policy = TrustedReleasePolicy(
services=frozenset({"aios-silent-release-controller"}),
release_roots=frozenset(
{"/usr/libexec/aios-silent-release-shadow"}
),
cutover_links=frozenset(
{"/usr/libexec/aios-silent-release-shadow/current"}
),
dropin_paths=frozenset(
{
"/usr/libexec/aios-silent-release-shadow/release-1/"
"deploy/systemd/aios-silent-release-controller.service"
}
),
authoritative_git_dirs=frozenset(
{"/data/aios-gitops/repo.git"}
),
repository_identities=frozenset({"aios/aios"}),
)
service = SilentReleaseActionService(
tmp_path / "state",
execution_enabled=False,
approval_verifier=verifier,
candidate_builder=SignedTrustedBuilderAdapter(
collector, builder_signer
),
builder_attestation_verifier=BuilderAttestationVerifier(
{builder_signer.key_id: builder_public}
),
trusted_policy=policy,
expected_repository="aios/aios",
policy_sha256="7" * 64,
)
return service, LocalTestApprovalSigner(
"shadow-owner-test-key", owner_private
)
def signed_shadow_approval(
service: SilentReleaseActionService,
signer: LocalTestApprovalSigner,
candidate_id: str,
) -> SignedReleasePlanApproval:
candidate = service.candidates.load(candidate_id)
plan = service.release_plans.load(candidate_id)
now = datetime.now(timezone.utc)
value = SignedReleasePlanApproval(
release_plan_id="release-plan-shadow-readiness-001",
candidate_id=candidate_id,
source_commit=candidate.source_commit,
tree_oid=candidate.tree_oid,
artifact_sha256=candidate.artifact_sha256,
manifest_sha256=candidate.manifest_sha256,
deployment_plan_sha256=plan.deployment.sha256,
release_plan_sha256=plan.sha256,
policy_sha256=plan.policy_sha256,
approval_plan_id=plan.tag.approval_plan_id,
evidence_closure_oid=plan.tag.evidence_closure_oid,
owner_approval_record_oid="9" * 40,
capabilities=(
"runtime_switch",
"tag_publish",
"baseline_registry_finalize",
),
owner_identity="owner:shadow-test",
trust_domain="aios.shadow",
issued_at=now.isoformat().replace("+00:00", "Z"),
not_before=now.isoformat().replace("+00:00", "Z"),
expires_at=(now + timedelta(minutes=5))
.isoformat()
.replace("+00:00", "Z"),
nonce="nonce-shadow-readiness-000001",
key_id="shadow-owner-test-key",
signature="",
)
return signer.sign(value)
def test_trusted_builder_attestation_and_caller_evidence_gate(tmp_path):
service, _ = trusted_service(tmp_path)
result = service.create_release_intent(
{"event_id": "webhook-main-001", "source_commit": COMMIT}
)
acceptance = service.get_acceptance(result["candidate_id"])
assert acceptance["attestation"]["builder_attestation_id"].startswith(
"builder-attestation-"
)
assert acceptance["checks"]["tests"] is True
with pytest.raises(
CapabilityAuthorizationError, match="caller-supplied evidence"
):
service.create_release_intent(
{
"event_id": "webhook-main-002",
"source_commit": COMMIT,
"tests": {"passed": True},
}
)
def test_builder_attestation_tamper_is_rejected(tmp_path):
service, _ = trusted_service(tmp_path)
bundle = service.candidate_builder.build(
{"event_id": "webhook-main-001", "source_commit": COMMIT}
)
tampered = dataclasses.replace(
bundle.attestation, artifact_sha256="0" * 64
)
with pytest.raises(
CapabilityAuthorizationError, match="does not bind"
):
dataclasses.replace(bundle, attestation=tampered).validate(
service.builder_attestation_verifier
)
def test_production_approval_backend_missing_disables_execution():
backend = UnavailableProductionApprovalBackend()
assert backend.available is False
with pytest.raises(
CapabilityAuthorizationError, match="not configured"
):
backend.verify()
def test_controller_duplicate_webhook_notification_and_restart_are_idempotent(
tmp_path,
):
service, _ = trusted_service(tmp_path)
first_controller = PersistentSilentReleaseController(
tmp_path / "state",
service,
main_probe=lambda: COMMIT,
)
first = first_controller.run_once()
second = first_controller.run_once()
restarted = PersistentSilentReleaseController(
tmp_path / "state",
service,
main_probe=lambda: COMMIT,
).run_once()
assert first["intent"]["candidate_id"] == second["intent"]["candidate_id"]
assert second["intent"]["idempotent"] is True
assert first["notification"]["idempotent"] is False
assert second["notification"]["idempotent"] is True
assert restarted["notification"]["idempotent"] is True
assert restarted["side_effects_executed"] is False
def test_controller_startup_journal_failure_stops_before_main_event(tmp_path):
service, _ = trusted_service(tmp_path)
service.sagas.create(
"rc-shadow-" + COMMIT[:12], "release-plan-shadow-readiness-001"
)
journal = next(service.sagas.root.glob("*.events.jsonl"))
journal.write_text('{"tampered":true}\n', encoding="utf-8")
probes: list[str] = []
result = PersistentSilentReleaseController(
tmp_path / "state",
service,
main_probe=lambda: probes.append("main") or COMMIT,
).run_once()
assert result["status"] == "FAIL_CLOSED"
assert result["intent"] is None
assert result["side_effects_executed"] is False
assert probes == []
def test_action_http_shadow_flow_has_zero_side_effects(tmp_path):
service, signer = trusted_service(tmp_path)
handler = type(
"TestShadowHandler",
(ShadowActionHandler,),
{"service": service, "controller": None},
)
server = ThreadingHTTPServer(("127.0.0.1", 0), handler)
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
base = f"http://127.0.0.1:{server.server_port}"
def post(path, payload):
request = urllib.request.Request(
base + path,
data=json.dumps(payload).encode(),
headers={"Content-Type": "application/json"},
)
with urllib.request.urlopen(request, timeout=5) as response:
return json.load(response)
try:
intent = post(
"/createReleaseIntent",
{"event_id": "http-main-001", "source_commit": COMMIT},
)
candidate_id = intent["candidate_id"]
approval = signed_shadow_approval(service, signer, candidate_id)
accepted = post("/submitSignedApproval", approval.to_dict())
deployed = post(
"/startDeployment", {"candidate_id": candidate_id}
)
with urllib.request.urlopen(
base + "/getReleasePlan?candidate_id=" + candidate_id,
timeout=5,
) as response:
plan = json.load(response)
finally:
server.shutdown()
server.server_close()
thread.join(timeout=5)
assert accepted["status"] == "VERIFIED_AND_STORED"
assert deployed == {
"candidate_id": candidate_id,
"execution_enabled": False,
"shadow_mode": True,
"side_effects": [],
"side_effects_executed": False,
"status": "SHADOW_COMPLETED",
}
assert plan["shadow_mode"] is True
assert plan["side_effects_executed"] is False
def test_secret_scan_redacts_placeholders_but_rejects_real_material(tmp_path):
(tmp_path / "safe.txt").write_text(
"Authorization: Bearer test-placeholder-token-000000",
encoding="utf-8",
)
assert secret_scan(tmp_path) == {"findings": [], "passed": True}
(tmp_path / "unsafe.txt").write_text(
"-----BEGIN "
+ "OPENSSH PRIVATE KEY-----\n"
+ ("A" * 80)
+ "\n-----END "
+ "OPENSSH PRIVATE KEY-----",
encoding="utf-8",
)
result = secret_scan(tmp_path)
assert result["passed"] is False
assert result["findings"] == ["unsafe.txt"]
secret = "live-" + ("A" * 40)
redacted = redact_output("Authorization: Bearer " + secret)
assert secret not in redacted
assert "<redacted>" in redacted
(tmp_path / "unsafe.txt").write_text(
"Authorization: " + "Bearer live-test-looking-secret-000001",
encoding="utf-8",
)
result = secret_scan(tmp_path)
assert result["passed"] is False
assert result["findings"] == ["unsafe.txt"]
def test_systemd_shadow_unit_has_both_gates_and_no_secret_environment():
unit = (
ROOT
/ "deploy"
/ "systemd"
/ "aios-silent-release-controller.service"
).read_text(encoding="utf-8")
assert "AIOS_GITOPS_WRITES_ENABLED=false" in unit
assert "AIOS_SILENT_RELEASE_EXECUTION_ENABLED=false" in unit
assert "--host 127.0.0.1" in unit
assert "PRIVATE_KEY=" not in unit
assert "TOKEN=" not in unit
assert "PASSWORD=" not in unit
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff