Compare commits

...
Author SHA1 Message Date
chatgpt-final-release-writer 473b86a2f1 integrate:v2r1c4-autonomy-control-plane-v2 2026-07-29 17:11:42 +08:00
chatgpt-final-release-writer e5dc9185dd test:scope-single-owner-systemd-contract 2026-07-29 13:48:39 +08:00
chatgpt-final-release-writer 334354557a integrate:v2r1c4-autonomy-handoff 2026-07-29 13:44:17 +08:00
chatgpt-final-release-writer 9917145a64 release:v2r1c4-version-contract 2026-07-29 13:33:36 +08:00
AIOS Governed Development Writer 357041bc3c feat(control-plane): add unified seven-component bootstrap contracts 2026-07-29 13:24:00 +08:00
AIOS Governed Development Writer ba403c0959 merge(v2r1c4): integrate single-owner governance hardening 2026-07-29 13:17:37 +08:00
AIOS Governed Development Writer aa2ffdb5a8 merge(v2r1c4): integrate autonomous release and WeChat candidate 2026-07-29 13:17:37 +08:00
Codex Agent 310ab2af90 evidence: close live activation hardening candidate 2026-07-29 12:30:10 +08:00
Codex Agent 71def340e7 governance: harden single-owner live activation 2026-07-29 12:28:37 +08:00
Codex Agent e1e438564c feat(governance): add fail-closed live activation 2026-07-29 11:27:04 +08:00
AIOS Task-Bound Prerequisites Writer fa038d17b2 governance: add dormant single-owner activation prerequisites 2026-07-29 04:00:00 +08:00
AIOS Task-Bound Writer 112d995545 feat(gitops): implement integration refresh provisioner 2026-07-28 17:23:52 +08:00
AIOS Task-Bound Writer 85426f4f57 chore(source-map): import canonical runtime sources 2026-07-28 17:06:58 +08:00
chatgpt-final-release-writer c294b90fd7 docs(autonomous-release): add final V2R1C4 agent execution contract 2026-07-26 14:24:37 +08:00
chatgpt-final-release-writer 8806aabb52 test(autonomous-release): complete candidate and final acceptance coverage 2026-07-26 14:19:50 +08:00
chatgpt-final-release-writer 3a44188d39 fix(autonomous-release): bind owner approval to exact final release digest 2026-07-26 14:16:15 +08:00
chatgpt-final-release-writer 7a1d704054 test(autonomous-release): verify dormant acceptance contracts 2026-07-26 14:12:16 +08:00
chatgpt-final-release-writer 752691869f feat(autonomous-release): make dormant acceptance runner machine executable 2026-07-26 14:12:09 +08:00
chatgpt-final-release-writer 27c3fe09fa feat(autonomous-release): complete candidate and final dormant acceptance gates 2026-07-26 14:09:47 +08:00
chatgpt-final-release-writer 7484a3b442 feat(autonomous-release): add dormant acceptance contract 2026-07-26 14:05:55 +08:00
chatgpt-final-release-writer 432f88ea6f docs(autonomous-release): add V2R1C4 dormant execution guide 2026-07-26 14:01:38 +08:00
chatgpt-final-release-writer 60af3e4105 feat(autonomous-release): add dormant candidate worker entrypoint 2026-07-26 13:55:20 +08:00
chatgpt-final-release-writer 83376fa777 feat(autonomous-release): add dormant candidate systemd template 2026-07-26 13:51:50 +08:00
chatgpt-final-release-writer eb3f507b64 build(autonomous-release): add deterministic artifact builder script 2026-07-26 13:25:35 +08:00
chatgpt-final-release-writer 6eb5825db8 build(autonomous-release): add candidate package metadata 2026-07-26 13:25:28 +08:00
chatgpt-final-release-writer 0b7539095d docs(autonomous-release): add final execution package 2026-07-26 13:22:24 +08:00
chatgpt-final-release-writer 678d7000b1 docs(autonomous-release): add dormant shadow-only release guide 2026-07-26 13:22:13 +08:00
chatgpt-final-release-writer faadd35c76 test(autonomous-release): verify dormant shadow-only release gate 2026-07-26 12:31:20 +08:00
chatgpt-final-release-writer 28168d3ecd feat(autonomous-release): codify dormant shadow-only release mode 2026-07-26 11:58:02 +08:00
132 changed files with 12020 additions and 599 deletions
+7 -7
View File
@@ -1,7 +1,7 @@
e4a1f86ef92ec1d3ff724eb75cf9c01758c1cc997e1afadc1c8b561bce5b813a FULL_PACKAGE_MANIFEST.json
c59c0288d4d999fbd79a2f275d4fb8c369029cb8bb721f95408308cc51eaa3a2 README.md
1095953d5764d9c72f02e84b1d525e265d3a67a5bdb451ab9ef51a8e8b3032b8 VERSION
ee6e4fc6bc98a590fbc3585b62707e2562115e5a3f722e69a7c464d966bdc458 active-runtime/app/aios_gitops_model_api.py
b090062a92aeb14211bbfc1fcd43b5ee4f315d30054b437f3bcdac657210ee95 VERSION
a664714bc1e13160cdd0dc32de460c65a83a3a95aa5cdad1833b1b650ab90e9d active-runtime/app/aios_gitops_model_api.py
0ffadf6ee1f297842bc44c6e753010fd2b7b02b1f5fb0d7e0676b7bec898db08 active-runtime/app/aios_v5_webops_app.py
0ffadf6ee1f297842bc44c6e753010fd2b7b02b1f5fb0d7e0676b7bec898db08 active-runtime/app/aios_webops_app.py
0eb12e0161371464605577707c84df413b555277933f9eb4488ab38032ff699b active-runtime/migrations/001_webops_core_sqlite.sql
@@ -13,7 +13,7 @@ ee6e4fc6bc98a590fbc3585b62707e2562115e5a3f722e69a7c464d966bdc458 adapters/gitop
013813b58d7844f3860d6f9bb484f84f92487652b573976c474bb35c91df8ae8 adapters/openclaw/openclaw_gitops_commands.py
7a34ccdfdcdc132fe5daf01cd1acd9d9b7adbd0429d575b65e0771962ccedc62 adapters/webops/gitops_model_menu_fragment.html
86b0cb6ee3192565b080672e5305dabba1283486442e630c71244ebac4910916 config/digital_life.yaml
a3872f24e4cd64288758846ecd0b374db4aa41111feec3b17c41438136d6d5d0 config/domains.env
5a27d48db343dcb723bcc1d9d8222aa3f72094c57cc43640aebe6558963bd20f config/domains.env
7a6f82cbddfc5c74bd37229e4f37fe4e73430ae585b4846abd1a80d852f2fed0 config/gitops_short_links.yaml
4139d915bdd733171734d5d5d3411e6fc4f8f202c49af4321d706da176da690b config/model_registry.yaml
8d6202d3da60366c96a208f5e5eed1f09fd36d122f4cabdc038c21ce2e72ea16 config/npm_custom_locations.yaml
@@ -55,7 +55,7 @@ d8cea35eb6c1300d20804ef480312d26dbdc2ad3a2d26b9430c6ca4808155a95 fixes/patch_ru
0eb12e0161371464605577707c84df413b555277933f9eb4488ab38032ff699b migrations/001_webops_core_sqlite.sql
6d61da88e7d6b208e7eee7195b521dc47cb68587f91987d7a0dd1c8801ef168d migrations/002_seed_owner_admin_user.sql
a2c0c7699e1eb46cfdeabfc905cb79b71ed14fb6bcf81ecc99ec47975cf05767 migrations/004_gitops_custom_llm_runtime.sql
b77bce1ca78cede27de6594e25f6ce5bb0c4990c0953eeb79f7e2608d2515c32 package_verify_full.sh
ca49f4165b50171968485ffa5535bc33d2158237ea55af10a3888a5fa0f7a535 package_verify_full.sh
f65b7fba5d6bdac3070ff4309830e286cd6319a80ab2e55263e46022fed2bf7f scripts/apply_migrations.py
bc0452d261def2366cdefd845e62d7177b42806f93ba78294ece29a8a369157e scripts/backup_before_fix.sh
bdb439086c86b3b23f8240cb2bef0f757a6be242d21d13a79eb1e44434efbe2e scripts/create_patch.sh
@@ -63,11 +63,11 @@ bdb439086c86b3b23f8240cb2bef0f757a6be242d21d13a79eb1e44434efbe2e scripts/create
1fdeecf0315f3ab4ec461c3376128054df9ac48e2fe5a3f64a138cd7d710d44d scripts/generate_release_manifest.py
80c6c10f259245681c42932f3f5f9b9a0fbd9cadce6a3f495d82cbdbc8618c73 scripts/healthcheck.sh
3795cadfa6429a434c955913dbdcbf552181c88301a196b4b458f4f5dd206cd8 scripts/init_gitops_baseline.sh
4d54a4ed6d35bbb6e9fe2924f3cfceeea9c3eed898017788f6c5ab8041394a47 scripts/install_v1r1c0_gitops_model_overlay.sh
e76cd986d2387b76439e4a7fe503e48a2de8fc70fb52c45a13641c9d943f108f scripts/install_v1r1c0_gitops_model_overlay.sh
0064539ac1e1bd32dc8062ecc307ad2bc8520895009583a72d46868d1cf44e3c scripts/install_webops_overlay.sh
9fadafc4b4d098c006d159cb3b401eb19cf9ab9c5fa4b5cb109e946501c0b8e1 scripts/owner_wechat_notify_worker.py
e0abd8ad44d4f088108f2046fd9d7bbd4465af50f79bd3b9fcbb72a8c61bee65 scripts/package_contract_check_v1r1c0.sh
abef8df642c6d233af4a03c512876b02b326e19c924020b8dd8cc74236d18817 scripts/release_version.sh
f537c58fc08685601f47fac5c0a3a2856e3ce9806c1918848c87a194a49c28a0 scripts/package_contract_check_v1r1c0.sh
14ab40a7c342e21e8315def14d552dcff4b214d23f1f4be493e9d3fc2cee1a2f scripts/release_version.sh
bbdb3f9992b203ddc2a6a83241052663de4f021d0705724bd9df05acaa99b022 scripts/rollback_last_delta.sh
0814c26ecf6732cf2c24f5f414ad0128a159768cfbbeb21d557274064feb1188 scripts/rollback_version.sh
da84a5cc61c1d54a7d1e96e4b0a7186e192edf3ec12d694fc9d7608c3b1086ef scripts/run_acceptance_with_autofix.sh
+1 -1
View File
@@ -1 +1 @@
V2R1C2
V2R1C4
@@ -0,0 +1,379 @@
#!/usr/bin/env python3
"""Task-scoped continuous authorization controller for the AIOS Candidate writer."""
from __future__ import annotations
import base64, fcntl, hashlib, json, os, pwd, grp, re, socket, sqlite3, stat, subprocess, sys, tempfile, time, urllib.error, urllib.parse, urllib.request
from datetime import datetime, timedelta, timezone
from pathlib import Path, PurePosixPath, PureWindowsPath
from typing import Any
TASK='AIOS-AUTONOMOUS-RELEASE-FULL-IMPLEMENTATION-20260726'
AUTH_ID='authz-aios-autonomous-release-20260726'
BRANCH='patch/chatgpt-autonomous-completion-contract-20260726'
BASE='875d41c3101093149414292067fd700dc214d4d3'
REPO='aios/aios'
WRITER_AUDIENCE='chatgpt-autonomous-release-writer'
ACTOR='chatgpt-gitops-bot'
WRITER_INSTANCE='writer-6e32f321-1240-46f8-9693-0b0631d9c77b'
CHILD_OPERATION_MAX_USES=20
ROOT=Path('/data/aios-gitops')
WORKTREE=ROOT/'worktrees/chatgpt-autonomous-completion-contract-20260726'
BARE=ROOT/'repo.git'
TASK_ROOT=ROOT/'runtime/tasks'/TASK
AUTH_DIR=Path('/etc/aios-gitops/task-authorizations')
PARENT=AUTH_DIR/'authz-aios-autonomous-release-20260726.json'
STATE_DIR=Path('/var/lib/aios-autonomous-release-authorization')
STATE=STATE_DIR/'state.json'
LOCK=STATE_DIR/'controller.lock'
AUDIT=TASK_ROOT/'continuous-authorization.audit.jsonl'
SOCKET_DIR=Path('/run/aios-autonomous-release-authorization')
SOCKET_PATH=SOCKET_DIR/'controller.sock'
PROJECTION=Path('/etc/aios-gitops/credentials/chatgpt-autonomous-release-writer')
REGISTRY=Path('/var/lib/aios-chatgpt-autonomous-release-writer/token-registry.sqlite3')
WRITER_LEDGER=Path('/var/lib/aios-chatgpt-autonomous-release-writer/operation-ledger.sqlite3')
ASKPASS=Path('/usr/libexec/aios-autonomous-release-authorization/gitea_askpass.py')
PATH_POLICY=Path('/etc/aios-gitops/policies-chatgpt-autonomous-release/autonomous-release-path-policy.json')
ROUTE_BINDING=Path('/etc/aios-gitops/action-route-bindings/chatgpt-autonomous-release-20260726.json')
OID_RE=re.compile(r'^[0-9a-f]{40}$')
OP_RE=re.compile(r'^[A-Za-z0-9][A-Za-z0-9._:-]{7,127}$')
TERMINAL={'COMPLETED','CANCELLED','FAILED_FINAL','REVOKED','REVOKED_IDLE_TIMEOUT','REVOKED_WALL_CLOCK','RECONCILIATION_REQUIRED'}
sys.path.insert(0,'/usr/libexec/aios-chatgpt-autonomous-release-writer-fullprofile-8ac2f90f')
from isolated_writer_token_registry import TokenRegistry
def now() -> datetime: return datetime.now(timezone.utc)
def stamp() -> str: return now().replace(microsecond=0).isoformat()
def canonical(value: Any) -> bytes: return json.dumps(value,sort_keys=True,separators=(',',':'),ensure_ascii=False).encode('utf-8')
def fail(code: str, status: int=503, **details: Any) -> None: raise ControlError(code,status,details)
class ControlError(RuntimeError):
def __init__(self,code:str,status:int=503,details:dict[str,Any]|None=None): self.code,self.status,self.details=code,status,details or {}; super().__init__(code)
def owned_regular(path:Path, mode:int) -> os.stat_result:
try: info=os.lstat(path)
except FileNotFoundError: fail('authorization_config_missing')
if not stat.S_ISREG(info.st_mode) or stat.S_ISLNK(info.st_mode) or info.st_uid!=0 or stat.S_IMODE(info.st_mode)&~mode: fail('authorization_config_permissions_rejected')
return info
def load_json(path:Path,mode:int=0o600) -> dict[str,Any]:
owned_regular(path,mode)
try: value=json.loads(path.read_text(encoding='utf-8'))
except Exception: fail('authorization_config_json_rejected')
if not isinstance(value,dict): fail('authorization_config_schema_rejected')
return value
def atomic_json(path:Path,value:dict[str,Any],mode:int=0o600) -> None:
path.parent.mkdir(parents=True,exist_ok=True)
raw=canonical(value)+b'\n'
fd,tmp=tempfile.mkstemp(prefix='.new-',dir=path.parent)
try:
os.fchmod(fd,mode); os.write(fd,raw); os.fsync(fd); os.close(fd); os.chown(tmp,0,0); os.replace(tmp,path)
os.chmod(path,mode)
finally:
try: os.close(fd)
except OSError: pass
if os.path.exists(tmp): os.unlink(tmp)
def append_audit(event:dict[str,Any]) -> None:
TASK_ROOT.mkdir(parents=True,exist_ok=True)
flags=os.O_WRONLY|os.O_CREAT|os.O_APPEND|getattr(os,'O_NOFOLLOW',0)
fd=os.open(AUDIT,flags,0o600)
try: os.write(fd,canonical({'at':stamp(),'task_id':TASK,'secrets_included':False,**event})+b'\n'); os.fsync(fd)
finally: os.close(fd)
def parent() -> dict[str,Any]:
p=load_json(PARENT)
required={'schema_version','development_authorization_id','authorization_type','task_id','actor_identity','audience','repository','exact_branch','approved_base_oid','allowed_path_roots','allowed_exact_paths','allowed_operations','created_at','valid_until','maximum_wall_clock_seconds','idle_timeout_seconds','manual_reapproval_per_commit','maximum_concurrent_writes','path_policy_sha256','writer_instance_id'}
if set(p)!=required or p.get('schema_version')!=1: fail('parent_authorization_schema_rejected')
fixed={'development_authorization_id':AUTH_ID,'authorization_type':'TASK_SCOPED_CONTINUOUS_DEVELOPMENT','task_id':TASK,'actor_identity':ACTOR,'audience':WRITER_AUDIENCE,'repository':REPO,'exact_branch':BRANCH,'approved_base_oid':BASE,'manual_reapproval_per_commit':False,'maximum_concurrent_writes':1,'writer_instance_id':WRITER_INSTANCE}
if any(p.get(k)!=v for k,v in fixed.items()): fail('parent_authorization_binding_rejected')
if p.get('maximum_wall_clock_seconds')!=86400 or p.get('idle_timeout_seconds')!=2700: fail('parent_authorization_lifetime_rejected')
if not isinstance(p.get('allowed_path_roots'),list) or not isinstance(p.get('allowed_exact_paths'),list) or not isinstance(p.get('allowed_operations'),list): fail('parent_authorization_scope_rejected')
raw=PATH_POLICY.read_bytes()
if hashlib.sha256(raw).hexdigest()!=p.get('path_policy_sha256'): fail('parent_authorization_path_policy_changed')
return p
def parse_time(v:Any) -> datetime:
try:
d=datetime.fromisoformat(str(v).replace('Z','+00:00'))
return d.astimezone(timezone.utc)
except Exception: fail('authorization_time_rejected')
def components(path:str)->tuple[str,...]:
if not isinstance(path,str) or not path or path!=path.strip() or '\\' in path or '\x00' in path: fail('authorization_path_rejected',403)
pp=PurePosixPath(path); wp=PureWindowsPath(path)
parts=tuple(path.split('/'))
if pp.is_absolute() or wp.is_absolute() or bool(wp.drive) or any(x in {'','.','..'} for x in parts): fail('authorization_path_rejected',403)
return parts
def path_allowed(p:dict[str,Any],path:str)->bool:
item=components(path)
for root in p['allowed_path_roots']:
r=components(str(root).rstrip('/'))
if item[:len(r)]==r: return True
return any(item==components(x) for x in p['allowed_exact_paths'])
def git(*args:str,cwd:Path|None=None,env:dict[str,str]|None=None)->str:
e=os.environ.copy(); e.update(env or {}); e['GIT_CONFIG_COUNT']='1'; e['GIT_CONFIG_KEY_0']='safe.directory'; e['GIT_CONFIG_VALUE_0']=str(WORKTREE)
r=subprocess.run(['git',*args],cwd=cwd,text=True,capture_output=True,env=e)
if r.returncode: fail('authorization_git_probe_failed',503)
return r.stdout.strip()
def credential_lines()->list[str]:
cp=Path(os.environ.get('CREDENTIALS_DIRECTORY',''))/'gitea_git_http'
try:
info=os.lstat(cp); lines=cp.read_text(encoding='utf-8').splitlines()
except OSError: fail('authorization_gitea_credential_unavailable')
if not stat.S_ISREG(info.st_mode) or stat.S_ISLNK(info.st_mode) or len(lines)!=2 or lines[0]!='gitops-bot' or not lines[1]: fail('authorization_gitea_credential_rejected')
return lines
def remote_oid()->str:
credential_lines()
e={'GIT_ASKPASS':str(ASKPASS),'GIT_TERMINAL_PROMPT':'0'}
out=git('ls-remote','--heads','http://127.0.0.1:3000/aios/aios.git',f'refs/heads/{BRANCH}',cwd=WORKTREE,env=e)
fields=out.split()
if len(fields)!=2 or not OID_RE.fullmatch(fields[0]): fail('authorization_git_ls_remote_rejected')
return fields[0]
def api_oid()->str:
user,password=credential_lines(); branch=urllib.parse.quote(BRANCH,safe='')
encoded=base64.b64encode((user+':'+password).encode()).decode()
request=urllib.request.Request('http://127.0.0.1:3000/api/v1/repos/aios/aios/branches/'+branch,headers={'Authorization':'Basic '+encoded,'Host':'g.cnnetmkt.top'})
try:
with urllib.request.urlopen(request,timeout=10) as response: value=json.loads(response.read())
except Exception: fail('authorization_gitea_api_unavailable')
oid=str(value.get('commit',{}).get('id',''))
if not OID_RE.fullmatch(oid): fail('authorization_gitea_api_oid_rejected')
return oid
def four_way(expected:str)->None:
local=git('rev-parse','HEAD',cwd=WORKTREE)
bare=git('--git-dir',str(BARE),'rev-parse',f'refs/heads/{BRANCH}')
remote=remote_oid(); api=api_oid()
if any(x!=expected for x in (local,bare,remote,api)): fail('RECONCILIATION_REQUIRED',409,current_head_oid=local)
if git('status','--porcelain',cwd=WORKTREE)!='': fail('authorization_worktree_dirty',409)
def state()->dict[str,Any]:
s=load_json(STATE)
required={'schema_version','development_authorization_id','task_id','status','current_head_oid','last_activity_at','writer_child','test_child','inflight','updated_at'}
if set(s)!=required or s.get('schema_version')!=1 or s.get('development_authorization_id')!=AUTH_ID or s.get('task_id')!=TASK or not OID_RE.fullmatch(str(s.get('current_head_oid',''))): fail('authorization_state_schema_rejected')
return s
def save_state(s:dict[str,Any])->None: s['updated_at']=stamp(); atomic_json(STATE,s)
def lock():
STATE_DIR.mkdir(parents=True,exist_ok=True); os.chmod(STATE_DIR,0o700)
f=open(LOCK,'a+b'); os.chmod(LOCK,0o600); fcntl.flock(f.fileno(),fcntl.LOCK_EX); return f
def route_binding_valid(p:dict[str,Any])->None:
b=load_json(ROUTE_BINDING,0o640)
identities=b.get('action_identities')
if b.get('task_id')!=TASK or b.get('writer_instance_id')!=WRITER_INSTANCE or b.get('repository')!=REPO or b.get('branch')!=BRANCH or b.get('approved_base_oid')!=BASE or b.get('path_policy_sha256')!=p.get('path_policy_sha256') or not isinstance(identities,list) or len(identities)!=1 or identities[0].get('account')!=ACTOR:
fail('authorization_route_binding_changed',409)
def task_live(p:dict[str,Any],s:dict[str,Any],*,touch:bool)->None:
route_binding_valid(p)
if s['status'] in TERMINAL: fail('authorization_task_terminal',409)
if now()>=parse_time(p['valid_until']):
s['status']='REVOKED_WALL_CLOCK'; save_state(s); cleanup(s,'wall_clock'); fail('authorization_task_terminal',409)
if (now()-parse_time(s['last_activity_at'])).total_seconds()>p['idle_timeout_seconds']:
s['status']='REVOKED_IDLE_TIMEOUT'; save_state(s); cleanup(s,'idle_timeout'); fail('authorization_task_terminal',409)
if touch: s['last_activity_at']=stamp(); save_state(s)
def projection_token()->str|None:
if not PROJECTION.exists(): return None
try:
info=os.lstat(PROJECTION)
if not stat.S_ISREG(info.st_mode) or stat.S_ISLNK(info.st_mode) or info.st_uid!=0 or stat.S_IMODE(info.st_mode)!=0o600: fail('authorization_projection_rejected')
token=PROJECTION.read_text(encoding='utf-8').strip()
return token if len(token.encode())>=32 else None
except OSError: fail('authorization_projection_rejected')
def write_projection(token:str)->None:
PROJECTION.parent.mkdir(parents=True,exist_ok=True)
fd,tmp=tempfile.mkstemp(prefix='.writer-',dir=PROJECTION.parent)
try:
os.fchmod(fd,0o600); os.write(fd,(token+'\n').encode()); os.fsync(fd); os.close(fd); os.chown(tmp,0,0); os.replace(tmp,PROJECTION); os.chmod(PROJECTION,0o600)
finally:
try: os.close(fd)
except OSError: pass
if os.path.exists(tmp): os.unlink(tmp)
def post_revoke_401(token:str)->None:
req=urllib.request.Request('http://127.0.0.1:19893/api/runtime-status',headers={'Authorization':'Bearer '+token})
try: urllib.request.urlopen(req,timeout=5)
except urllib.error.HTTPError as e:
if e.code==401:return
except Exception: pass
fail('authorization_old_credential_revocation_unverified')
def rotate_child(p:dict[str,Any],s:dict[str,Any],reason:str)->str:
old=projection_token(); registry=TokenRegistry(REGISTRY)
old_record=registry.find(old) if old else None
if old_record and (old_record.get('task_id')!=TASK or old_record.get('development_authorization_id')!=AUTH_ID):
if parse_time(old_record.get('expires_at'))>=now(): fail('authorization_existing_projection_binding_rejected')
if not registry.revoke(str(old_record['token_id'])): fail('authorization_historical_child_revoke_failed')
post_revoke_401(old)
append_audit({'event':'historical_expired_child_revoked','old_credential_http_status':401})
old_record=None
issued=now().replace(microsecond=0); expires=issued+timedelta(minutes=20)
prefixes=[str(x).rstrip('/')+'/' for x in p['allowed_path_roots']]
grant={'token_id':'continuous-child-'+issued.strftime('%Y%m%dT%H%M%SZ')+'-'+os.urandom(3).hex(),'credential_id':'credential-'+issued.strftime('%Y%m%dT%H%M%SZ')+'-'+os.urandom(3).hex(),'development_authorization_id':AUTH_ID,'audience':WRITER_AUDIENCE,'task_id':TASK,'actor_identity':ACTOR,'writer_instance_id':WRITER_INSTANCE,'repository_identity':REPO,'source_cidrs':['127.0.0.1/32'],'allowed_branch':BRANCH,'current_expected_head_oid':s['current_head_oid'],'approved_base_oid':BASE,'allowed_operations':p['allowed_operations'],'allowed_paths':p['allowed_exact_paths'],'allowed_path_prefixes':prefixes,'not_before':issued.isoformat(),'expires_at':expires.isoformat(),'renewable':False,'max_uses':{operation:CHILD_OPERATION_MAX_USES for operation in p['allowed_operations']},'issued_by':'task_authorization_controller'}
token,record=registry.issue(grant)
try:
write_projection(token)
if old_record:
if not registry.revoke(str(old_record['token_id'])): fail('authorization_old_child_revoke_failed')
post_revoke_401(old)
except Exception:
registry.revoke(str(record['token_id']))
try: PROJECTION.unlink()
except FileNotFoundError: pass
raise
s['writer_child']={'credential_id':record['credential_id'],'issued_at':record['not_before'],'expires_at':record['expires_at'],'current_expected_head_oid':s['current_head_oid'],'rotation_reason':reason}
save_state(s); append_audit({'event':'child_grant_rotated','reason':reason,'current_head_oid':s['current_head_oid'],'old_grant_revoked':bool(old_record),'old_credential_http_status':401 if old_record else None})
return token
def child_operation_quota_exhausted(rec:dict[str,Any],registry:TokenRegistry)->bool:
token_id=str(rec.get('token_id',''))
allowed=rec.get('allowed_operations')
limits=rec.get('max_uses')
if not token_id or not isinstance(allowed,list) or not isinstance(limits,dict): fail('authorization_child_quota_schema_rejected')
for operation in allowed:
maximum=limits.get(operation)
if not isinstance(operation,str) or not isinstance(maximum,int) or maximum<1: fail('authorization_child_quota_schema_rejected')
if registry.usage(token_id,operation)>=maximum: return True
return False
def ensure_child(p:dict[str,Any],s:dict[str,Any],reason:str)->str:
token=projection_token(); meta=s.get('writer_child') or {}
expired=True
try: expired=(parse_time(meta.get('expires_at'))-now()).total_seconds()<=300
except Exception: pass
if not token or expired or meta.get('current_expected_head_oid')!=s['current_head_oid']:
return rotate_child(p,s,reason)
registry=TokenRegistry(REGISTRY)
rec=registry.find(token)
if not rec or rec.get('revoked') or rec.get('development_authorization_id')!=AUTH_ID or rec.get('current_expected_head_oid')!=s['current_head_oid']:
return rotate_child(p,s,reason)
if child_operation_quota_exhausted(rec,registry):
return rotate_child(p,s,'operation_quota_exhausted')
return token
def ledger_verified(operation_id:str,commit_oid:str)->None:
with sqlite3.connect(WRITER_LEDGER) as db:
row=db.execute('select operation_id,branch,commit_oid,state,verification_status from writer_operations where operation_id=?',(operation_id,)).fetchone()
if not row or row[1]!=BRANCH or row[2]!=commit_oid or row[3]!='COMPLETED' or row[4]!='bare+gitea': fail('authorization_writer_ledger_rejected',409)
def reconcile(p:dict[str,Any],s:dict[str,Any],previous:str,new:str,operation_id:str)->dict[str,Any]:
inflight=s.get('inflight')
if not isinstance(inflight,dict) or inflight.get('previous_head_oid')!=previous or inflight.get('operation_id')!=operation_id: fail('authorization_inflight_rejected',409)
if previous!=s['current_head_oid'] or not OID_RE.fullmatch(new): fail('authorization_head_transition_rejected',409)
ledger_verified(operation_id,new); four_way(new)
s['current_head_oid']=new; s['inflight']=None; s['last_activity_at']=stamp(); save_state(s)
ensure_child(p,s,'head_advance')
append_audit({'event':'authorization_head_advanced','previous_head_oid':previous,'new_commit_oid':new,'four_way_ref_status':True,'worktree_clean':True})
return {'authorization_current_head_oid':new,'automatic_head_advance':True,'four_way_ref_status':True}
def prepare(p:dict[str,Any],s:dict[str,Any],data:dict[str,Any])->dict[str,Any]:
required={'expected_current_head_oid','operation_id','idempotency_key','exact_path','content_digest','commit_message_digest','branch','path','message'}
if set(data)!=required: fail('authorization_request_schema_rejected',403)
if data['branch']!=BRANCH or data['path']!=data['exact_path'] or not path_allowed(p,str(data['path'])): fail('authorization_path_rejected',403)
if data['expected_current_head_oid']!=s['current_head_oid'] or not OID_RE.fullmatch(str(data['expected_current_head_oid'])): fail('BRANCH_HEAD_CHANGED',409,current_head_oid=s['current_head_oid'])
if not OP_RE.fullmatch(str(data['operation_id'])) or not OP_RE.fullmatch(str(data['idempotency_key'])): fail('authorization_operation_id_rejected',403)
if not all(isinstance(data[k],str) and re.fullmatch(r'[0-9a-f]{64}',data[k]) for k in ('content_digest','commit_message_digest')): fail('authorization_digest_rejected',403)
if hashlib.sha256(data['message'].encode()).hexdigest()!=data['commit_message_digest']: fail('authorization_commit_message_digest_rejected',403)
inflight=s.get('inflight')
if inflight:
if inflight.get('operation_id')==data['operation_id'] and inflight.get('idempotency_key')==data['idempotency_key']: return {'expected_head_oid':s['current_head_oid'],'idempotency_key':data['idempotency_key']}
fail('BRANCH_HEAD_CHANGED',409,current_head_oid=s['current_head_oid'])
four_way(s['current_head_oid'])
s['inflight']={'operation_id':data['operation_id'],'idempotency_key':data['idempotency_key'],'previous_head_oid':s['current_head_oid'],'path':data['path'],'created_at':stamp()}; s['last_activity_at']=stamp(); save_state(s)
append_audit({'event':'write_prepared','operation_id':data['operation_id'],'path':data['path'],'expected_head_oid':s['current_head_oid']})
return {'expected_head_oid':s['current_head_oid'],'idempotency_key':data['idempotency_key']}
def test_authorize(p:dict[str,Any],s:dict[str,Any],operation:str)->dict[str,Any]:
allowed={'pytest_dormant','pytest_autonomous_release','compileall_autonomous_release','git_diff_check'}
if operation not in allowed: fail('candidate_test_executor_operation_rejected',403)
four_way(s['current_head_oid'])
child=s.get('test_child') or {}; refresh=True
try: refresh=(parse_time(child.get('expires_at'))-now()).total_seconds()<=300
except Exception: pass
if refresh:
issued=now().replace(microsecond=0); child={'test_authorization_id':'test-authz-aios-autonomous-release-20260726','grant_id':'test-child-'+issued.strftime('%Y%m%dT%H%M%SZ'),'issued_at':issued.isoformat(),'expires_at':(issued+timedelta(minutes=20)).isoformat()}; s['test_child']=child
s['last_activity_at']=stamp(); save_state(s); append_audit({'event':'test_authorized','operation':operation})
return {'test_authorization_id':child['test_authorization_id'],'operation':operation,'expires_at':child['expires_at']}
def cleanup(s:dict[str,Any],reason:str)->None:
old=projection_token()
if old:
reg=TokenRegistry(REGISTRY); rec=reg.find(old)
if rec: reg.revoke(str(rec['token_id']))
try: PROJECTION.unlink()
except FileNotFoundError: pass
try: post_revoke_401(old)
except ControlError: pass
for unit in ('aios-chatgpt-autonomous-release-route.service','aios-chatgpt-autonomous-release-writer.service'):
subprocess.run(['/usr/bin/systemctl','stop',unit],stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL,timeout=20)
subprocess.run(['/usr/bin/systemctl','stop','aios-autonomous-release-dormant-test-executor@*.service'],stdout=subprocess.DEVNULL,stderr=subprocess.DEVNULL,timeout=20)
try: ROUTE_BINDING.unlink()
except FileNotFoundError: pass
try: SOCKET_PATH.unlink()
except FileNotFoundError: pass
append_audit({'event':'authorization_cleanup','reason':reason,'projection_removed':not PROJECTION.exists(),'route_overlay_removed':not ROUTE_BINDING.exists(),'task_services_stop_requested':True})
def request(data:dict[str,Any])->dict[str,Any]:
op=data.get('op'); f=lock()
try:
p=parent(); s=state(); task_live(p,s,touch=False)
if op=='describe_authorization':
four_way(s['current_head_oid'])
return {'development_authorization_id':AUTH_ID,'task_id':TASK,'actor_identity':ACTOR,'audience':WRITER_AUDIENCE,'repository':REPO,'branch':BRANCH,'approved_base_oid':BASE,'authorization_status':s['status'],'current_head_oid':s['current_head_oid']}
if op=='get_writer_token':
four_way(s['current_head_oid']); token=ensure_child(p,s,'scheduled_or_requested_rotation'); return {'token':token,'current_head_oid':s['current_head_oid']}
if op=='prepare_write':
ensure_child(p,s,'pre_write_quota_check')
return prepare(p,s,data.get('request') if isinstance(data.get('request'),dict) else {})
if op=='reconcile_commit':
r=data.get('result') if isinstance(data.get('result'),dict) else {}; return reconcile(p,s,str(r.get('previous_head_oid','')),str(r.get('new_commit_oid','')),str(r.get('operation_id','')))
if op=='fail_write':
r=data.get('result') if isinstance(data.get('result'),dict) else {}; inflight=s.get('inflight')
if isinstance(inflight,dict) and inflight.get('operation_id')==r.get('operation_id'): s['inflight']=None; save_state(s)
return {'cleared':True}
if op=='authorize_test': return test_authorize(p,s,str(data.get('operation','')))
fail('authorization_operation_rejected',403)
finally:
fcntl.flock(f.fileno(),fcntl.LOCK_UN); f.close()
def peer_allowed(conn:socket.socket)->bool:
try:
import struct
_pid,uid,gid=struct.unpack('3i',conn.getsockopt(socket.SOL_SOCKET,socket.SO_PEERCRED,12))
return uid==pwd.getpwnam('aiman').pw_uid and gid==grp.getgrnam('gitops').gr_gid
except Exception: return False
def serve()->None:
SOCKET_DIR.mkdir(parents=True,exist_ok=True); os.chown(SOCKET_DIR,0,grp.getgrnam('gitops').gr_gid); os.chmod(SOCKET_DIR,0o750)
try: SOCKET_PATH.unlink()
except FileNotFoundError: pass
with socket.socket(socket.AF_UNIX,socket.SOCK_STREAM) as server:
server.bind(str(SOCKET_PATH)); os.chown(SOCKET_PATH,0,grp.getgrnam('gitops').gr_gid); os.chmod(SOCKET_PATH,0o660); server.listen(32); server.settimeout(1.0)
last=0.0
while True:
if time.monotonic()-last>30:
last=time.monotonic()
try:
f=lock(); p=parent(); s=state(); task_live(p,s,touch=False); four_way(s['current_head_oid']); ensure_child(p,s,'scheduled_rotation'); fcntl.flock(f.fileno(),fcntl.LOCK_UN); f.close()
except ControlError as e:
append_audit({'event':'scheduled_check_blocked','error':e.code})
if e.code=='authorization_task_terminal': return
except Exception: append_audit({'event':'scheduled_check_blocked','error':'authorization_controller_internal_error'})
try: conn,_=server.accept()
except socket.timeout: continue
with conn:
if not peer_allowed(conn): conn.sendall(canonical({'ok':False,'status':403,'error':'authorization_peer_rejected'})+b'\n'); continue
try:
raw=conn.recv(131072); data=json.loads(raw.decode()); result=request(data); conn.sendall(canonical({'ok':True,'result':result})+b'\n')
except ControlError as e: conn.sendall(canonical({'ok':False,'status':e.status,'error':e.code,**e.details})+b'\n')
except Exception as exc:
append_audit({'event':'controller_internal_error','error_type':type(exc).__name__})
conn.sendall(canonical({'ok':False,'status':503,'error':'authorization_controller_internal_error'})+b'\n')
if __name__=='__main__': serve()
@@ -0,0 +1,567 @@
#!/usr/bin/env python3
"""Task-bound bridge from the legacy ChatGPT Action to an isolated Writer V2."""
from __future__ import annotations
import hashlib
import hmac
import json
import os
import re
import sqlite3
import socket
import stat
import urllib.error
import urllib.parse
import urllib.request
from datetime import datetime, timezone
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path, PurePosixPath, PureWindowsPath
from typing import Any
CONFIG_PATH = Path(os.environ["AIOS_ACTION_ROUTE_BINDING"])
POLICY_PATH = Path(os.environ["AIOS_ACTION_ROUTE_PATH_POLICY"])
KNOWN_IDENTITIES_PATH = Path(os.environ["AIOS_ACTION_ROUTE_KNOWN_IDENTITIES"])
STATE_PATH = Path(os.environ["AIOS_ACTION_ROUTE_STATE"])
AUDIT_PATH = Path(os.environ["AIOS_ACTION_ROUTE_AUDIT"])
HOST = os.environ.get("AIOS_ACTION_ROUTE_HOST", "127.0.0.1")
PORT = int(os.environ.get("AIOS_ACTION_ROUTE_PORT", "19894"))
EXPECTED_TASK_ID = os.environ["AIOS_ACTION_ROUTE_TASK_ID"]
EXPECTED_ROUTE_HEADER = os.environ["AIOS_ACTION_ROUTE_HEADER"]
AUTHORIZATION_SOCKET = Path(os.environ["AIOS_CONTINUOUS_AUTHORIZATION_SOCKET"])
OID_RE = re.compile(r"[0-9a-f]{40}")
SECRET_RE = re.compile(
r"(?i)(api[_-]?key|secret[_-]?access[_-]?key|secret|password|token)"
r"\s*[:=]\s*[\"']?([A-Za-z0-9_./+=:-]{16,})"
)
SECRET_VALUE_RE = re.compile(
r"(sk-[A-Za-z0-9_-]{20,}|HPUA[A-Z0-9]{10,}|SDC[A-Za-z0-9]{20,}|"
r"-----BEGIN (?:OPENSSH |RSA |EC )?PRIVATE KEY-----)"
)
ALLOWED_REQUEST_FIELDS = frozenset({"base","branch","content","create_branch","message","path","task_id","development_authorization_id","actor_identity","audience","approved_base_oid","expected_current_head_oid","operation_id","idempotency_key","exact_path","content_digest","commit_message_digest"})
class RouteError(RuntimeError):
def __init__(self, code: str, status: int = 400, **details: Any):
super().__init__(code)
self.code = code
self.status = status
self.details = details
def utc_now() -> str:
return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
def canonical_json(value: Any) -> bytes:
return json.dumps(
value, ensure_ascii=False, sort_keys=True, separators=(",", ":")
).encode("utf-8")
def load_root_managed_json(
path: Path, *, expected_mode: int = 0o640
) -> tuple[dict[str, Any], bytes]:
info = os.lstat(path)
if stat.S_ISLNK(info.st_mode) or not stat.S_ISREG(info.st_mode):
raise RouteError("writer_route_config_type_rejected", 503)
if info.st_uid != 0 or stat.S_IMODE(info.st_mode) & ~expected_mode:
raise RouteError("writer_route_config_permissions_rejected", 503)
raw = path.read_bytes()
try:
value = json.loads(raw)
except (UnicodeDecodeError, json.JSONDecodeError):
raise RouteError("writer_route_config_json_rejected", 503) from None
if not isinstance(value, dict):
raise RouteError("writer_route_config_object_required", 503)
return value, raw
def load_binding() -> dict[str, Any]:
binding, _ = load_root_managed_json(CONFIG_PATH)
required = {
"schema_version",
"task_id",
"writer_instance_id",
"repository",
"branch",
"approved_base_oid",
"writer_url",
"path_policy_sha256",
"action_identities",
}
if set(binding) != required or binding.get("schema_version") != 1:
raise RouteError("writer_route_binding_schema_rejected", 503)
if binding["task_id"] != EXPECTED_TASK_ID:
raise RouteError("writer_route_task_binding_rejected", 503)
if binding["repository"] != "aios/aios":
raise RouteError("writer_route_repository_rejected", 503)
if not OID_RE.fullmatch(str(binding["approved_base_oid"])):
raise RouteError("writer_route_base_oid_rejected", 503)
if binding["writer_url"] != "http://127.0.0.1:19893":
raise RouteError("writer_route_upstream_rejected", 503)
identities = binding.get("action_identities")
if not isinstance(identities, list) or len(identities) != 1:
raise RouteError("writer_route_action_identity_rejected", 503)
identity = identities[0]
if (
not isinstance(identity, dict)
or set(identity) != {"account", "token_sha256"}
or identity.get("account") != "chatgpt-gitops-bot"
or not re.fullmatch(
r"[0-9a-f]{64}", str(identity.get("token_sha256", ""))
)
):
raise RouteError("writer_route_action_identity_rejected", 503)
return binding
def allowed_path_scope(binding: dict[str, Any]) -> tuple[set[str], tuple[str, ...]]:
policy, raw = load_root_managed_json(POLICY_PATH)
actual = hashlib.sha256(raw).hexdigest()
if actual != binding["path_policy_sha256"]:
raise RouteError("writer_route_path_policy_digest_mismatch", 503)
if policy.get("allowed_branch") != binding["branch"]:
raise RouteError("writer_route_path_policy_branch_mismatch", 503)
operation = (policy.get("allowed_operations") or {}).get("commit_file.atomic")
paths = operation.get("exact_paths") if isinstance(operation, dict) else None
prefixes = operation.get("prefix_paths") if isinstance(operation, dict) else None
if not isinstance(paths, list) or not paths or not all(
isinstance(x, str) for x in paths
):
raise RouteError("writer_route_path_policy_rejected", 503)
if not isinstance(prefixes, list) or not prefixes or not all(
isinstance(x, str) and x.endswith("/") for x in prefixes
):
raise RouteError("writer_route_path_policy_rejected", 503)
if any(not valid_relative_path(x[:-1]) for x in prefixes):
raise RouteError("writer_route_path_policy_rejected", 503)
if any(not valid_relative_path(x) for x in paths):
raise RouteError("writer_route_path_policy_rejected", 503)
return set(paths), tuple(prefixes)
def valid_relative_path(path: str) -> bool:
if not isinstance(path, str) or not path or path != path.strip():
return False
if "\x00" in path or "\\" in path:
return False
try:
decoded = urllib.parse.unquote(path, errors="strict")
except (UnicodeDecodeError, ValueError):
return False
if decoded != path:
return False
posix = PurePosixPath(path)
windows = PureWindowsPath(path)
return not (
posix.is_absolute()
or windows.is_absolute()
or bool(windows.drive)
or any(part in {"", ".", ".."} for part in path.split("/"))
)
def path_allowed(binding: dict[str, Any], path: str) -> bool:
if not valid_relative_path(path):
return False
exact_paths, prefixes = allowed_path_scope(binding)
return path in exact_paths or any(path.startswith(prefix) for prefix in prefixes)
def bearer(headers: Any) -> str:
value = headers.get("Authorization", "")
if not value.startswith("Bearer "):
raise RouteError("unauthorized", 401)
token = value[7:].strip()
if not token:
raise RouteError("unauthorized", 401)
return token
def authorize_action(headers: Any, binding: dict[str, Any]) -> str:
digest = hashlib.sha256(bearer(headers).encode("utf-8")).hexdigest()
identity = binding["action_identities"][0]
if not hmac.compare_digest(identity["token_sha256"], digest):
known, _ = load_root_managed_json(KNOWN_IDENTITIES_PATH)
identities = known.get("identities")
if known.get("schema_version") != 1 or not isinstance(identities, list):
raise RouteError("writer_route_identity_catalog_rejected", 503)
observed = "unregistered"
allowed_accounts = {
"chatgpt-gitops-bot",
"gitops-write-bot",
"codex-gitops-bot",
}
for candidate in identities:
if not isinstance(candidate, dict) or set(candidate) != {
"account",
"token_sha256",
}:
raise RouteError("writer_route_identity_catalog_rejected", 503)
account = str(candidate.get("account", ""))
token_sha256 = str(candidate.get("token_sha256", ""))
if account not in allowed_accounts or not re.fullmatch(
r"[0-9a-f]{64}", token_sha256
):
raise RouteError("writer_route_identity_catalog_rejected", 503)
if hmac.compare_digest(token_sha256, digest):
observed = account
raise RouteError(
"writer_route_action_identity_rejected",
403,
accepted_action_identity=str(identity["account"]),
observed_action_identity=observed,
identity_alias_accepted=False,
)
return str(identity["account"])
def authorization_call(operation: str, **payload: Any) -> dict[str, Any]:
request = {"op": operation, **payload}
try:
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as client:
client.settimeout(15)
client.connect(str(AUTHORIZATION_SOCKET))
client.sendall(canonical_json(request))
raw = client.recv(131072)
response = json.loads(raw.decode("utf-8"))
except (OSError, TimeoutError, UnicodeDecodeError, json.JSONDecodeError):
raise RouteError("authorization_controller_unavailable", 503) from None
if not isinstance(response, dict) or not response.get("ok"):
if not isinstance(response, dict):
raise RouteError("authorization_controller_response_rejected", 503)
details = {k: v for k, v in response.items() if k not in {"ok", "status", "error"}}
raise RouteError(str(response.get("error") or "authorization_controller_rejected"), int(response.get("status") or 503), **details)
result = response.get("result")
if not isinstance(result, dict):
raise RouteError("authorization_controller_response_rejected", 503)
return result
def writer_token() -> str:
value = authorization_call("get_writer_token").get("token")
if not isinstance(value, str) or len(value.encode("utf-8")) < 32:
raise RouteError("writer_route_credential_rejected", 503)
return value
def writer_call(
binding: dict[str, Any],
method: str,
path: str,
payload: dict[str, Any] | None = None,
) -> tuple[int, dict[str, Any]]:
data = canonical_json(payload) if payload is not None else None
headers = {"Authorization": "Bearer " + writer_token()}
if data is not None:
headers["Content-Type"] = "application/json"
request = urllib.request.Request(
binding["writer_url"] + path, data=data, method=method, headers=headers
)
try:
with urllib.request.urlopen(request, timeout=90) as response:
body = json.loads(response.read())
return response.status, body
except urllib.error.HTTPError as exc:
try:
body = json.loads(exc.read())
except Exception:
body = {"error": "writer_route_upstream_non_json_error"}
return exc.code, body
except (OSError, TimeoutError):
raise RouteError("writer_route_upstream_unavailable", 503) from None
def validate_request(data: Any, binding: dict[str, Any]) -> dict[str, Any]:
if not isinstance(data, dict):
raise RouteError("writer_route_request_object_required")
if not {"branch", "path", "content"}.issubset(data):
raise RouteError("writer_route_request_incomplete")
if not set(data).issubset(ALLOWED_REQUEST_FIELDS):
raise RouteError("writer_route_request_fields_rejected")
if data["branch"] != binding["branch"]:
raise RouteError("writer_route_request_binding_failed", 403)
if data.get("create_branch") not in (None, False):
raise RouteError("writer_route_request_binding_failed", 403)
if data.get("base", "main") not in {"main", binding["approved_base_oid"]}:
raise RouteError("writer_route_request_binding_failed", 403)
path = data["path"]
if not isinstance(path, str) or not path_allowed(binding, path):
raise RouteError("writer_route_request_binding_failed", 403)
content = data["content"]
if not isinstance(content, str) or "\x00" in content or "\r" in content:
raise RouteError("writer_route_content_rejected")
if len(content.encode("utf-8")) > 1_048_576:
raise RouteError("writer_route_content_too_large", 413)
if SECRET_RE.search(content) or SECRET_VALUE_RE.search(content):
raise RouteError("writer_route_secret_content_rejected", 403)
message = data.get("message") or f"chore(chatgpt): update {path}"
if not isinstance(message, str) or not 1 <= len(message) <= 200 or "\x00" in message or "\n" in message or "\r" in message:
raise RouteError("writer_route_message_rejected")
return {"branch": binding["branch"], "path": path, "content": content, "message": message}
def complete_request(data: dict[str, Any], normalized: dict[str, Any], binding: dict[str, Any], account: str) -> dict[str, Any]:
authorization = authorization_call("describe_authorization")
expected = {
"task_id": binding["task_id"],
"development_authorization_id": authorization.get("development_authorization_id"),
"actor_identity": account,
"audience": authorization.get("audience"),
"branch": binding["branch"],
"approved_base_oid": binding["approved_base_oid"],
}
required = {
"task_id": EXPECTED_TASK_ID,
"development_authorization_id": "authz-aios-autonomous-release-20260726",
"actor_identity": "chatgpt-gitops-bot",
"audience": "chatgpt-autonomous-release-writer",
"branch": binding["branch"],
"approved_base_oid": binding["approved_base_oid"],
}
if expected != required or authorization.get("authorization_status") != "ACTIVE" or authorization.get("current_head_oid") is None:
raise RouteError("writer_route_request_binding_failed", 403)
for key, value in required.items():
if key in data and data[key] != value:
raise RouteError("writer_route_request_binding_failed", 403)
if "exact_path" in data and data["exact_path"] != normalized["path"]:
raise RouteError("writer_route_request_binding_failed", 403)
current_head = str(authorization["current_head_oid"])
if not OID_RE.fullmatch(current_head):
raise RouteError("authorization_current_head_rejected", 503)
if "expected_current_head_oid" in data and data["expected_current_head_oid"] != current_head:
raise RouteError("BRANCH_HEAD_CHANGED", 409, current_head_oid=current_head)
content_digest = hashlib.sha256(normalized["content"].encode("utf-8")).hexdigest()
message_digest = hashlib.sha256(normalized["message"].encode("utf-8")).hexdigest()
if "content_digest" in data and data["content_digest"] != content_digest:
raise RouteError("writer_route_request_binding_failed", 403)
if "commit_message_digest" in data and data["commit_message_digest"] != message_digest:
raise RouteError("writer_route_request_binding_failed", 403)
seed = {**required, "expected_current_head_oid": current_head, "path": normalized["path"], "content_digest": content_digest, "commit_message_digest": message_digest}
request_digest = hashlib.sha256(canonical_json(seed)).hexdigest()
idempotency_key = "action-" + request_digest
operation_id = hashlib.sha256((EXPECTED_TASK_ID + "\0" + idempotency_key).encode("utf-8")).hexdigest()
if "idempotency_key" in data and data["idempotency_key"] != idempotency_key:
raise RouteError("writer_route_request_binding_failed", 403)
if "operation_id" in data and data["operation_id"] != operation_id:
raise RouteError("writer_route_request_binding_failed", 403)
return {**normalized, **required, "expected_current_head_oid": current_head, "operation_id": operation_id, "idempotency_key": idempotency_key, "exact_path": normalized["path"], "content_digest": content_digest, "commit_message_digest": message_digest, "request_digest": request_digest}
def current_writer_head(binding: dict[str, Any]) -> str:
status, body = writer_call(binding, "GET", "/api/runtime-status")
if status != 200:
raise RouteError(
"writer_route_runtime_status_failed", 503, upstream_status=status
)
required = {
"service": "chatgpt-autonomous-release-writer",
"writes_enabled": True,
"allowed_branch": binding["branch"],
"approved_base_oid": binding["approved_base_oid"],
"path_policy_sha256": binding["path_policy_sha256"],
}
if any(body.get(key) != value for key, value in required.items()):
raise RouteError("writer_route_runtime_binding_mismatch", 503)
head = str(body.get("workspace_head_oid", ""))
if not OID_RE.fullmatch(head):
raise RouteError("writer_route_runtime_head_rejected", 503)
return head
def operation_binding(
request_digest: str, binding: dict[str, Any]
) -> tuple[str, str]:
STATE_PATH.parent.mkdir(parents=True, exist_ok=True)
with sqlite3.connect(STATE_PATH, timeout=30, isolation_level=None) as db:
db.execute("PRAGMA journal_mode=WAL")
db.execute("PRAGMA synchronous=FULL")
db.execute(
"""
CREATE TABLE IF NOT EXISTS action_operations (
request_digest TEXT PRIMARY KEY,
expected_head_oid TEXT NOT NULL,
idempotency_key TEXT NOT NULL UNIQUE,
created_at TEXT NOT NULL
)
"""
)
row = db.execute(
"SELECT expected_head_oid,idempotency_key FROM action_operations "
"WHERE request_digest=?",
(request_digest,),
).fetchone()
if row:
return str(row[0]), str(row[1])
head = current_writer_head(binding)
key = "action-" + request_digest
with sqlite3.connect(STATE_PATH, timeout=30, isolation_level=None) as db:
db.execute("PRAGMA journal_mode=WAL")
db.execute("PRAGMA synchronous=FULL")
db.execute("BEGIN IMMEDIATE")
db.execute(
"INSERT OR IGNORE INTO action_operations"
"(request_digest,expected_head_oid,idempotency_key,created_at) "
"VALUES(?,?,?,?)",
(request_digest, head, key, utc_now()),
)
row = db.execute(
"SELECT expected_head_oid,idempotency_key FROM action_operations "
"WHERE request_digest=?",
(request_digest,),
).fetchone()
db.execute("COMMIT")
return str(row[0]), str(row[1])
def append_audit(event: dict[str, Any]) -> None:
AUDIT_PATH.parent.mkdir(parents=True, exist_ok=True)
flags = os.O_WRONLY | os.O_CREAT | os.O_APPEND | getattr(os, "O_NOFOLLOW", 0)
fd = os.open(AUDIT_PATH, flags, 0o600)
try:
os.write(fd, canonical_json(event) + b"\n")
os.fsync(fd)
finally:
os.close(fd)
class Handler(BaseHTTPRequestHandler):
server_version = "AIOSActionTaskRouter/1"
def log_message(self, _format: str, *_args: Any) -> None:
return
def send_json(self, status: int, value: dict[str, Any]) -> None:
body = json.dumps(value, ensure_ascii=False, sort_keys=True).encode("utf-8")
self.send_response(status)
self.send_header("Content-Type", "application/json; charset=utf-8")
self.send_header("Content-Length", str(len(body)))
self.send_header("Cache-Control", "no-store")
self.end_headers()
self.wfile.write(body)
def do_GET(self) -> None:
path = urllib.parse.urlparse(self.path).path.rstrip("/") or "/"
if path != "/health":
return self.send_json(404, {"error": "not_found"})
try:
binding = load_binding()
self.send_json(
200,
{
"status": "ok",
"service": "aios-action-task-router",
"task_id": binding["task_id"],
"writer_instance_id": binding["writer_instance_id"],
"branch": binding["branch"],
},
)
except RouteError as exc:
self.send_json(exc.status, {"error": exc.code, **exc.details})
def do_POST(self) -> None:
path = urllib.parse.urlparse(self.path).path.rstrip("/") or "/"
if path != "/api/file":
return self.send_json(404, {"error": "not_found"})
if self.client_address[0] not in {"127.0.0.1", "::1"}:
return self.send_json(403, {"error": "writer_route_source_rejected"})
if self.headers.get("X-AIOS-Route-Task") != EXPECTED_ROUTE_HEADER:
return self.send_json(403, {"error": "writer_route_header_rejected"})
try:
length = int(self.headers.get("Content-Length", "0"))
if not 0 < length <= 1_100_000:
raise RouteError("writer_route_content_length_rejected", 413)
try:
data = json.loads(self.rfile.read(length).decode("utf-8"))
except (UnicodeDecodeError, json.JSONDecodeError):
raise RouteError("writer_route_request_json_rejected") from None
binding = load_binding()
account = authorize_action(self.headers, binding)
normalized = validate_request(data, binding)
normalized = complete_request(data, normalized, binding, account)
prepared = authorization_call("prepare_write", request={key: normalized[key] for key in ("expected_current_head_oid", "operation_id", "idempotency_key", "exact_path", "content_digest", "commit_message_digest", "branch", "path", "message")})
expected_head = str(prepared["expected_head_oid"])
writer_payload = {"branch": normalized["branch"], "path": normalized["path"], "content": normalized["content"], "message": normalized["message"], "expected_head_oid": expected_head, "idempotency_key": str(prepared["idempotency_key"])}
request_digest = hashlib.sha256(canonical_json({"task_id": binding["task_id"], "operation_id": normalized["operation_id"], "idempotency_key": normalized["idempotency_key"], "content_digest": normalized["content_digest"], "commit_message_digest": normalized["commit_message_digest"]})).hexdigest()
status, result = writer_call(
binding, "POST", "/api/commit-file", writer_payload
)
audit = {
"at": utc_now(),
"task_id": binding["task_id"],
"writer_instance_id": binding["writer_instance_id"],
"account": account,
"branch": normalized["branch"],
"path": normalized["path"],
"request_digest": request_digest,
"expected_head_oid": expected_head,
"status": status,
"operation_id": result.get("operation_id"),
"commit_oid": result.get("commit_oid"),
"error": result.get("error"),
"secrets_included": False,
}
append_audit(audit)
if status != 200:
try:
authorization_call("fail_write", result={"operation_id": normalized["operation_id"]})
except RouteError:
pass
return self.send_json(status, result)
advanced = authorization_call("reconcile_commit", result={"previous_head_oid": expected_head, "new_commit_oid": result.get("commit_oid"), "operation_id": result.get("operation_id")})
return self.send_json(
200,
{
"status": "committed",
"branch": normalized["branch"],
"path": normalized["path"],
"commit": {
"status": "committed",
"branch": normalized["branch"],
"rev": result.get("commit_oid"),
"previous_head_oid": expected_head,
"new_tree_oid": result.get("new_tree_oid"),
"push_status": result.get("push_status"),
"four_way_ref_status": advanced.get("four_way_ref_status") is True,
"sync": {"status": "verified", "target": "bare+git-ls-remote+gitea-api"},
},
"writer_instance_id": binding["writer_instance_id"],
"operation_id": result.get("operation_id"),
"idempotent": result.get("idempotent"),
"secrets_included": False,
},
)
except RouteError as exc:
if exc.code == "writer_route_action_identity_rejected":
try:
append_audit(
{
"at": utc_now(),
"task_id": EXPECTED_TASK_ID,
"event": "action_identity_rejected",
"accepted_action_identity": exc.details.get(
"accepted_action_identity"
),
"observed_action_identity": exc.details.get(
"observed_action_identity"
),
"identity_alias_accepted": False,
"secrets_included": False,
}
)
except Exception:
pass
self.send_json(exc.status, {"error": exc.code, **exc.details})
except Exception:
self.send_json(500, {"error": "writer_route_internal_error"})
def main() -> None:
load_binding()
server = ThreadingHTTPServer((HOST, PORT), Handler)
server.serve_forever()
if __name__ == "__main__":
main()
@@ -0,0 +1,23 @@
"""Task-bound Integration Refresh control-plane primitives."""
from .integration_merge_writer import IntegrationMergeWriter, MergeError, MergeRequest
from .integration_refresh import (
AppendOnlyAudit,
Binding,
GiteaBranchReadProjection,
GrantStore,
IntegrationRefreshProvisioner,
ProvisioningError,
)
__all__ = [
"AppendOnlyAudit",
"Binding",
"GiteaBranchReadProjection",
"GrantStore",
"IntegrationMergeWriter",
"IntegrationRefreshProvisioner",
"MergeError",
"MergeRequest",
"ProvisioningError",
]
@@ -0,0 +1,90 @@
"""Constrained integration-only merge writer with rollback and reconciliation states."""
from __future__ import annotations
import subprocess
from dataclasses import dataclass
from pathlib import Path
import re
class MergeError(RuntimeError):
def __init__(self, code: str, status: int = 409):
super().__init__(code); self.code, self.status = code, status
@dataclass(frozen=True)
class MergeRequest:
integration_branch: str
source_branch: str
approved_source_oid: str
class IntegrationMergeWriter:
def __init__(self, workspace: Path | str, remote: str, projection):
self.workspace, self.remote, self.projection = Path(workspace), remote, projection
def _git(self, *args: str, check=True) -> str:
p = subprocess.run(["git", *args], cwd=self.workspace, text=True, capture_output=True)
if check and p.returncode:
raise MergeError("git_operation_failed", 500)
return p.stdout.strip()
def merge(self, request: MergeRequest) -> dict:
def bad(value: str) -> bool:
return (
value == "main"
or value.startswith("main/")
or value == "governance"
or value.startswith("governance/")
or value == "approvals"
or value.startswith("refs/")
or value.startswith("tags/")
)
if (bad(request.integration_branch) or bad(request.source_branch) or
not request.integration_branch.startswith("integration/") or
re.fullmatch(r"[0-9a-fA-F]{40}", request.approved_source_oid) is None):
raise MergeError("integration_merge_scope_rejected", 403)
for branch in (request.integration_branch, request.source_branch):
if subprocess.run(["git", "check-ref-format", "--branch", branch], cwd=self.workspace,
text=True, capture_output=True).returncode:
raise MergeError("integration_merge_scope_rejected", 403)
if self._git("rev-parse", "--abbrev-ref", "HEAD") != request.integration_branch:
raise MergeError("integration_branch_checkout_required")
before = self._git("rev-parse", "HEAD")
remote_before = self.projection(request.integration_branch)
if remote_before != before:
raise MergeError("integration_remote_advanced")
source = self._git("rev-parse", "--verify", f"refs/heads/{request.source_branch}^{{commit}}")
if source != request.approved_source_oid:
raise MergeError("integration_source_not_approved", 403)
attempt = subprocess.run(["git", "merge", "--no-ff", "--no-commit", request.source_branch], cwd=self.workspace, text=True, capture_output=True)
if attempt.returncode:
subprocess.run(["git", "merge", "--abort"], cwd=self.workspace, text=True, capture_output=True)
raise MergeError("integration_merge_conflict")
try:
self._git("commit", "-m", "merge(integration): approved source")
merged = self._git("rev-parse", "HEAD")
parents = self._git("show", "-s", "--format=%P", "HEAD").split()
if parents != [before, source]:
raise MergeError("integration_parent_invariant_failed", 500)
pushed = subprocess.run(["git", "push", "--porcelain", self.remote, f"HEAD:refs/heads/{request.integration_branch}"], cwd=self.workspace, text=True, capture_output=True)
if pushed.returncode:
try:
observed = self.projection(request.integration_branch)
except Exception:
return {"state": "RECONCILIATION_REQUIRED", "pre_merge_oid": before, "merge_oid": merged}
if observed == before:
self._git("reset", "--hard", before)
return {"state": "PUSH_FAILED_RESTORED", "pre_merge_oid": before}
return {"state": "RECONCILIATION_REQUIRED", "pre_merge_oid": before, "merge_oid": merged}
try:
observed = self.projection(request.integration_branch)
except Exception:
return {"state": "RECONCILIATION_REQUIRED", "pre_merge_oid": before, "merge_oid": merged}
if observed != merged:
return {"state": "RECONCILIATION_REQUIRED", "pre_merge_oid": before, "merge_oid": merged}
return {"state": "COMPLETED", "pre_merge_oid": before, "merge_oid": merged}
except MergeError:
if self._git("rev-parse", "HEAD") != before:
self._git("reset", "--hard", before)
raise
@@ -0,0 +1,257 @@
"""Fail-closed, task-bound integration refresh provisioning primitives.
This module deliberately has no HTTP or credential-provider dependency. The
caller supplies a branch-read projection and an apply callback, making the
authorization and recovery boundary independently testable.
"""
from __future__ import annotations
import hashlib
import json
import os
import argparse
import secrets
import sqlite3
import threading
import re
from contextlib import contextmanager
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Callable
class ProvisioningError(RuntimeError):
def __init__(self, code: str, status: int = 403):
super().__init__(code)
self.code, self.status = code, status
def _now() -> datetime:
return datetime.now(timezone.utc)
def _digest(secret: str) -> str:
return hashlib.sha256(secret.encode("utf-8")).hexdigest()
def _canonical(value: dict) -> str:
return hashlib.sha256(json.dumps(value, sort_keys=True, separators=(",", ":")).encode()).hexdigest()
def _is_oid(value: object) -> bool:
return isinstance(value, str) and re.fullmatch(r"[0-9a-fA-F]{40}", value) is not None
@dataclass(frozen=True)
class Binding:
task_id: str
repository: str
branch: str
actor_identity: str
audience: str
paths: tuple[str, ...]
quota: int
class AppendOnlyAudit:
"""Append only redacted JSONL audit, durable before a result is returned."""
def __init__(self, path: Path | str):
self.path = Path(path)
self.path.parent.mkdir(parents=True, exist_ok=True)
self._lock = threading.Lock()
def append(self, event: dict) -> None:
with self._lock:
safe = {key: value for key, value in event.items() if key not in {"token", "credential", "authorization"}}
previous = "0" * 64
if self.path.exists():
lines = self.path.read_text(encoding="utf-8").splitlines()
if lines:
previous = json.loads(lines[-1])["event_hash"]
safe["previous_event_hash"] = previous
safe["event_hash"] = _canonical(safe)
encoded = (json.dumps(safe, sort_keys=True, separators=(",", ":")) + "\n").encode()
fd = os.open(self.path, os.O_APPEND | os.O_CREAT | os.O_WRONLY, 0o600)
try:
os.write(fd, encoded)
os.fsync(fd)
finally:
os.close(fd)
class GrantStore:
"""Digest-only short grant registry; SQLite transactions serialize quota use."""
def __init__(self, path: Path | str):
self.path = Path(path)
self.path.parent.mkdir(parents=True, exist_ok=True)
with self._db() as db:
db.execute("""CREATE TABLE IF NOT EXISTS grants(
token_digest TEXT PRIMARY KEY, binding_json TEXT NOT NULL,
expires_at TEXT NOT NULL, quota INTEGER NOT NULL, uses INTEGER NOT NULL,
revoked INTEGER NOT NULL DEFAULT 0)""")
@contextmanager
def _db(self):
db = sqlite3.connect(self.path, timeout=15, isolation_level=None)
db.row_factory = sqlite3.Row
try:
yield db
finally:
db.close()
def issue(self, binding: Binding, *, ttl_seconds: int = 300) -> str:
if ttl_seconds < 1 or ttl_seconds > 1800 or binding.quota < 1 or not binding.paths:
raise ValueError("invalid_short_grant")
token = secrets.token_urlsafe(32)
payload = json.dumps(binding.__dict__ | {"paths": list(binding.paths)}, sort_keys=True)
with self._db() as db:
db.execute("INSERT INTO grants VALUES(?,?,?,?,?,0)", (_digest(token), payload,
(_now() + timedelta(seconds=ttl_seconds)).isoformat(), binding.quota, 0))
return token
def rotate(self, old_token: str, binding: Binding, *, revocation_probe, ttl_seconds: int = 300) -> str:
self.revoke(old_token)
try: status = revocation_probe(old_token)
except TimeoutError as exc: raise ProvisioningError("revocation_probe_timeout", 503) from exc
if status != 401: raise ProvisioningError("revocation_probe_rejected", 503)
return self.issue(binding, ttl_seconds=ttl_seconds)
def revoke(self, token: str) -> None:
with self._db() as db:
db.execute("BEGIN IMMEDIATE")
db.execute("UPDATE grants SET revoked=1 WHERE token_digest=?", (_digest(token),))
db.execute("COMMIT")
def consume(self, token: str, binding: Binding, path: str) -> None:
with self._db() as db:
db.execute("BEGIN IMMEDIATE")
row = db.execute("SELECT * FROM grants WHERE token_digest=?", (_digest(token),)).fetchone()
if not row or row["revoked"]:
db.execute("ROLLBACK"); raise ProvisioningError("credential_rejected", 401)
if _now() >= datetime.fromisoformat(row["expires_at"]):
db.execute("ROLLBACK"); raise ProvisioningError("credential_expired", 401)
stored = json.loads(row["binding_json"])
requested = binding.__dict__ | {"paths": list(binding.paths)}
if stored != requested or path not in stored["paths"]:
db.execute("ROLLBACK"); raise ProvisioningError("grant_binding_rejected")
if row["uses"] >= row["quota"]:
db.execute("ROLLBACK"); raise ProvisioningError("grant_quota_exhausted")
db.execute("UPDATE grants SET uses=uses+1 WHERE token_digest=?", (_digest(token),))
db.execute("COMMIT")
class GiteaBranchReadProjection:
"""Normalizes a trusted task-scoped Gitea reader; every ambiguity fails closed."""
def __init__(self, reader: Callable[[str, str], dict]):
self.reader = reader
def head(self, repository: str, branch: str) -> str:
try:
reply = self.reader(repository, branch)
except TimeoutError as exc:
raise ProvisioningError("gitea_projection_timeout", 503) from exc
except Exception as exc:
raise ProvisioningError("gitea_projection_unavailable", 503) from exc
if not isinstance(reply, dict) or reply.get("status") in {401, 403}:
raise ProvisioningError("gitea_projection_forbidden", 503)
oid = reply.get("oid")
if reply.get("status") != 200 or not _is_oid(oid):
raise ProvisioningError("gitea_projection_invalid", 503)
return oid.lower()
class IntegrationRefreshProvisioner:
_locks: dict[tuple[str, str], threading.Lock] = {}
_locks_guard = threading.Lock()
def __init__(self, *, grants: GrantStore, projection: GiteaBranchReadProjection,
ledger_path: Path | str, audit: AppendOnlyAudit, approved_binding: Binding):
self.grants, self.projection, self.audit, self.approved_binding = grants, projection, audit, approved_binding
self.ledger_path = Path(ledger_path); self.ledger_path.parent.mkdir(parents=True, exist_ok=True)
with self._ledger() as db:
db.execute("CREATE TABLE IF NOT EXISTS refreshes(operation_id TEXT PRIMARY KEY, digest TEXT NOT NULL, state TEXT NOT NULL, result_oid TEXT)")
@contextmanager
def _ledger(self):
db = sqlite3.connect(self.ledger_path)
try:
yield db
db.commit()
finally:
db.close()
def refresh(self, *, token: str, binding: Binding, path: str, operation_id: str,
target_head_oid: str, apply: Callable[[], str]) -> dict:
if not operation_id or not _is_oid(target_head_oid):
raise ProvisioningError("refresh_request_rejected")
if binding != self.approved_binding:
raise ProvisioningError("grant_binding_rejected")
request = {"binding": binding.__dict__ | {"paths": list(binding.paths)}, "path": path, "target": target_head_oid}
digest = _canonical(request)
with self._locks_guard:
lock = self._locks.setdefault((binding.repository, binding.branch), threading.Lock())
with lock, self._ledger() as db:
db.execute("BEGIN IMMEDIATE")
row = db.execute("SELECT * FROM refreshes WHERE operation_id=?", (operation_id,)).fetchone()
new_operation = row is None
if row:
if row[1] != digest:
db.execute("ROLLBACK"); raise ProvisioningError("operation_id_reuse_rejected", 409)
if row[2] == "COMPLETED":
db.execute("COMMIT")
return {"state": row[2], "operation_id": operation_id, "oid": row[3], "idempotent": True}
if row[2] not in {"PENDING", "RECOVERABLE"}:
db.execute("COMMIT")
return {"state": row[2], "operation_id": operation_id, "oid": row[3], "idempotent": True}
else:
db.execute("INSERT INTO refreshes VALUES(?,?,?,NULL)", (operation_id, digest, "PENDING"))
db.execute("COMMIT")
if new_operation:
self.grants.consume(token, binding, path)
current = self.projection.head(binding.repository, binding.branch)
if current != target_head_oid.lower():
self._state(operation_id, "STALE_TARGET")
raise ProvisioningError("stale_target_head", 409)
try:
oid = apply()
if not _is_oid(oid):
raise ProvisioningError("apply_result_invalid", 500)
# Detect a competing remote advancement before claiming completion.
observed = self.projection.head(binding.repository, binding.branch)
if observed != oid.lower():
self._state(operation_id, "RECONCILIATION_REQUIRED")
raise ProvisioningError("remote_concurrent_advance", 409)
self._state(operation_id, "COMPLETED", oid)
self.audit.append({"operation_id": operation_id, "task_id": binding.task_id, "repository": binding.repository,
"branch": binding.branch, "actor_identity": binding.actor_identity, "audience": binding.audience, "path": path, "state": "COMPLETED"})
return {"state": "COMPLETED", "operation_id": operation_id, "oid": oid, "idempotent": False}
except ProvisioningError:
raise
except Exception as exc:
self._state(operation_id, "RECOVERABLE")
self.audit.append({"operation_id": operation_id, "task_id": binding.task_id, "repository": binding.repository,
"branch": binding.branch, "actor_identity": binding.actor_identity, "audience": binding.audience, "path": path, "state": "RECOVERABLE"})
raise ProvisioningError("refresh_apply_failed", 503) from exc
def _state(self, operation_id: str, state: str, oid: str | None = None) -> None:
with self._ledger() as db:
db.execute("UPDATE refreshes SET state=?, result_oid=COALESCE(?,result_oid) WHERE operation_id=?", (state, oid, operation_id))
def _sandbox_preflight(state_dir: Path) -> int:
if os.environ.get("AIOS_GITOPS_WRITES_ENABLED") != "false":
raise ProvisioningError("global_gitops_writes_must_remain_disabled", 500)
state_dir.mkdir(parents=True, exist_ok=True)
audit = AppendOnlyAudit(state_dir / "preflight-audit.jsonl")
audit.append({"event": "SANDBOX_PREFLIGHT", "writes_enabled": False})
print(json.dumps({"status": "READY", "mode": "sandbox", "writes_enabled": False}, sort_keys=True))
return 0
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--sandbox-preflight", action="store_true", required=True)
parser.add_argument("--state-dir", type=Path, required=True)
options = parser.parse_args()
raise SystemExit(_sandbox_preflight(options.state_dir))
+43
View File
@@ -0,0 +1,43 @@
{
"actor_identity": "codex-chartgpt-bridge",
"allowed_branch_patterns": [
"patch/**",
"feature/**",
"fix/**",
"release-candidate/**"
],
"allowed_operations": [
"commit_file.atomic"
],
"allowed_path_patterns": [
"active-runtime/**",
"services/**",
"deploy/**",
"tests/**",
"docs/**",
"versions/**",
"release/**"
],
"denied_branch_patterns": [
"main",
"master",
"governance/approvals",
"integration/**",
"production/**"
],
"denied_operations": [
"force_push",
"history_rewrite",
"branch_delete",
"tag.write",
"registry.write",
"baseline.write",
"production.deploy",
"approval.submit",
"secret.read"
],
"expires_at": "2027-07-29T00:00:00Z",
"policy_id": "STANDING-GOVERNED-DEVELOPMENT-WRITER-V1",
"repository": "aios/aios",
"writer_audience": "aios-governed-development-writer"
}
+23
View File
@@ -0,0 +1,23 @@
{
"actor_identity": "codex-chartgpt-bridge",
"allowed_operations": [
"commit_file.atomic"
],
"allowed_paths": [
"services/**",
"deploy/**",
"tests/**",
"docs/**",
"release/**"
],
"branch": "patch/v2r1c4-release-completion-20260729",
"expected_base_oid": "<40_HEX_OID>",
"expected_policy_sha256": "<64_HEX_SHA256>",
"expires_at": "2026-08-05T00:00:00Z",
"grant_id": "grant-REPLACE",
"max_operations": 200,
"max_total_bytes": 67108864,
"repository": "aios/aios",
"task_id": "V2R1C4-RELEASE-COMPLETION",
"writer_audience": "aios-governed-development-writer"
}
+7
View File
@@ -0,0 +1,7 @@
{
"authoritative_git_dir": "/data/aios-gitops/repo.git",
"poll_seconds": 5,
"receipts_dir": "/var/lib/aios-gitops/autonomous-control-plane/mirror/receipts",
"requests_dir": "/var/lib/aios-gitops/autonomous-control-plane/mirror/requests",
"source_git_dir": "/data/aios-gitops/runtime/gitea-bin/data/gitea-repositories/aios/aios.git"
}
+19
View File
@@ -0,0 +1,19 @@
{
"socket_path": "/run/aios-gitops/autonomous-control-plane/publisher.sock",
"allowed_peer_uids": [
0
],
"repository": "aios/aios",
"base_branch": "governance/approvals",
"head_prefix": "governance/request/",
"owner_reviewer": "owner",
"gitea_base_url": "http://127.0.0.1:3000",
"remote_url": "http://127.0.0.1:3000/aios/aios.git",
"credential_name": "gitea_http",
"allowed_git_dir_roots": [
"/data/aios-gitops/worktrees",
"/data/aios-gitops/repo.git"
],
"operation_ledger": "/var/lib/aios-gitops/autonomous-control-plane/publisher/operations.sqlite",
"audit_path": "/var/lib/aios-gitops/autonomous-control-plane/publisher/audit.jsonl"
}
+62
View File
@@ -0,0 +1,62 @@
{
"authoritative_git_dir": "/data/aios-gitops/repo.git",
"approval": {
"gitea_db_path": "/data/aios-gitops/runtime/gitea-bin/data/gitea.db",
"repo_id": 1,
"owner_user_id": 2,
"governance_ref": "refs/heads/governance/approvals",
"required_machine_contexts": [
"aios/branch-protection",
"aios/evidence-closure",
"aios/secret-scan",
"aios/state-machine",
"aios/validator"
]
},
"merge_transport": {
"transport": "unix_json",
"socket_path": "/run/aios-gitops/integration-merge-writer.sock",
"timeout_seconds": 120
},
"tag_transport": {
"transport": "http_json",
"url": "http://127.0.0.1:8080/api/create-tag",
"credential_name": "tag_api",
"timeout_seconds": 120,
"expected_statuses": [
200
]
},
"remote_tag": {
"remote_url": "https://g.cnnetmkt.top/git/aios/aios.git",
"gitea_base_url": "https://g.cnnetmkt.top/git",
"repo_owner": "aios",
"repo_name": "aios",
"credential_name": "gitea_http"
},
"deployment": {
"artifact_path": "/var/lib/aios-gitops/artifacts/V2R1C4.tar.zst",
"artifact_manifest_member": "artifact-manifest.json",
"release_root": "/opt/aios/releases",
"current_symlink": "/opt/aios/current",
"previous_symlink": "/opt/aios/previous",
"state_path": "/var/lib/aios-gitops/autonomous-control-plane/release/deployment-state.json",
"service_units": [
"aios-gitops-model-api.service"
],
"health_checks": [
{
"url": "http://127.0.0.1:8080/health",
"expected_status": 200,
"timeout_seconds": 10
}
]
},
"metadata": {
"registry_path": "/var/lib/aios-gitops/release/versions/V2R1C4.json",
"baseline_path": "/var/lib/aios-gitops/release/PRODUCTION_BASELINE",
"registry_plan_path": "/var/lib/aios-gitops/autonomous-control-plane/release/registry-plan.json",
"baseline_plan_path": "/var/lib/aios-gitops/autonomous-control-plane/release/baseline-plan.txt",
"metadata_snapshot_root": "/var/lib/aios-gitops/autonomous-control-plane/release/metadata-snapshots"
}
}
+24
View File
@@ -0,0 +1,24 @@
{
"approval_expires_at": "2026-08-05T00:00:00Z",
"approval_head_oid": "<40_HEX_OID>",
"approval_manifest_path": "approvals/release/V2R1C4-PRODUCTION-RELEASE-ENVELOPE.json",
"approval_manifest_sha256": "<64_HEX_SHA256>",
"approval_merge_oid": "<40_HEX_OID>",
"approval_plan_id": "<OWNER_APPROVED_PLAN_ID>",
"approval_pr_number": 1,
"artifact_sha256": "<64_HEX_SHA256>",
"baseline_plan_sha256": "<64_HEX_SHA256>",
"deployment_plan_sha256": "<64_HEX_SHA256>",
"evidence_closure_oid": "<40_HEX_OID>",
"evidence_closure_sha256": "<64_HEX_SHA256>",
"owner_review_oid": 1,
"registry_plan_sha256": "<64_HEX_SHA256>",
"rollback_artifact_sha256": "<64_HEX_SHA256>",
"rollback_plan_sha256": "<64_HEX_SHA256>",
"sbom_sha256": "<64_HEX_SHA256>",
"tag_plan_sha256": "<64_HEX_SHA256>",
"target_oid": "<40_HEX_TARGET_OID>",
"target_tree_oid": "<40_HEX_TREE_OID>",
"test_evidence_sha256": "<64_HEX_SHA256>",
"version": "V2R1C4"
}
+96
View File
@@ -0,0 +1,96 @@
{
"state_path": "/var/lib/aios-gitops/autonomous-control-plane/release/state.json",
"audit_path": "/var/lib/aios-gitops/autonomous-control-plane/release/audit.jsonl",
"hooks": {
"verify_owner_approval": [
"/usr/bin/aios-release-hook",
"verify_owner_approval",
"--config",
"/etc/aios-gitops/autonomous-control-plane/release-actions.json"
],
"merge_release": [
"/usr/bin/aios-release-hook",
"merge_release",
"--config",
"/etc/aios-gitops/autonomous-control-plane/release-actions.json"
],
"create_tag": [
"/usr/bin/aios-release-hook",
"create_tag",
"--config",
"/etc/aios-gitops/autonomous-control-plane/release-actions.json"
],
"verify_remote_tag": [
"/usr/bin/aios-release-hook",
"verify_remote_tag",
"--config",
"/etc/aios-gitops/autonomous-control-plane/release-actions.json"
],
"deploy": [
"/usr/bin/aios-release-hook",
"deploy",
"--config",
"/etc/aios-gitops/autonomous-control-plane/release-actions.json"
],
"health_check": [
"/usr/bin/aios-release-hook",
"health_check",
"--config",
"/etc/aios-gitops/autonomous-control-plane/release-actions.json"
],
"rollback": [
"/usr/bin/aios-release-hook",
"rollback",
"--config",
"/etc/aios-gitops/autonomous-control-plane/release-actions.json"
],
"verify_previous": [
"/usr/bin/aios-release-hook",
"verify_previous",
"--config",
"/etc/aios-gitops/autonomous-control-plane/release-actions.json"
],
"restore": [
"/usr/bin/aios-release-hook",
"restore",
"--config",
"/etc/aios-gitops/autonomous-control-plane/release-actions.json"
],
"verify_restored": [
"/usr/bin/aios-release-hook",
"verify_restored",
"--config",
"/etc/aios-gitops/autonomous-control-plane/release-actions.json"
],
"update_registry": [
"/usr/bin/aios-release-hook",
"update_registry",
"--config",
"/etc/aios-gitops/autonomous-control-plane/release-actions.json"
],
"update_baseline": [
"/usr/bin/aios-release-hook",
"update_baseline",
"--config",
"/etc/aios-gitops/autonomous-control-plane/release-actions.json"
],
"verify_consistency": [
"/usr/bin/aios-release-hook",
"verify_consistency",
"--config",
"/etc/aios-gitops/autonomous-control-plane/release-actions.json"
],
"snapshot_metadata": [
"/usr/bin/aios-release-hook",
"snapshot_metadata",
"--config",
"/etc/aios-gitops/autonomous-control-plane/release-actions.json"
],
"restore_metadata": [
"/usr/bin/aios-release-hook",
"restore_metadata",
"--config",
"/etc/aios-gitops/autonomous-control-plane/release-actions.json"
]
}
}
+9
View File
@@ -0,0 +1,9 @@
{
"allowed_output_roots": [
"/var/lib/aios-gitops/autonomous-control-plane/projection/releases"
],
"poll_seconds": 5,
"receipts_dir": "/var/lib/aios-gitops/autonomous-control-plane/projection/receipts",
"requests_dir": "/var/lib/aios-gitops/autonomous-control-plane/projection/requests",
"source_git_dir": "/data/aios-gitops/runtime/gitea-bin/data/gitea-repositories/aios/aios.git"
}
+5
View File
@@ -0,0 +1,5 @@
{
"poll_seconds": 10,
"receipts_dir": "/var/lib/aios-gitops/autonomous-control-plane/renewal/receipts",
"requests_dir": "/var/lib/aios-gitops/autonomous-control-plane/renewal/requests"
}
+11
View File
@@ -0,0 +1,11 @@
{
"admin_credential_name": "admin_gitea_http",
"encrypted_credential_path": "/etc/credstore.encrypted/aios-governed-development-writer.gitea_http",
"expected_writer_username": "aios-governed-development-writer",
"gitea_base_url": "http://127.0.0.1:3000",
"grant_path": "/var/lib/aios-gitops/autonomous-control-plane/writer/grant.json",
"receipt_path": "/var/lib/aios-gitops/autonomous-control-plane/writer/revocation-receipt.json",
"revoked_grant_path": "/var/lib/aios-gitops/autonomous-control-plane/writer/grant.revoked.json",
"writer_credential_name": "writer_gitea_http",
"writer_unit": "aios-governed-development-writer.service"
}
+8
View File
@@ -0,0 +1,8 @@
{
"exact_paths": [
"/usr/libexec/aios-autonomous-release-authorization/controller.py",
"/usr/libexec/aios-chatgpt-action-task-router-continuous-20260726/action_task_router.py"
],
"output_path": "/var/lib/aios-gitops/autonomous-control-plane/projection/runtime-source-evidence.json",
"task_id": "V2R1C4-RELEASE-COMPLETION"
}
+8
View File
@@ -0,0 +1,8 @@
{
"gitea_db_path": "/data/aios-gitops/runtime/gitea-bin/data/gitea.db",
"output_dir": "/var/lib/aios-gitops/autonomous-control-plane/watcher/status",
"owner_user_id": 2,
"poll_seconds": 5,
"repo_id": 1,
"requests_dir": "/var/lib/aios-gitops/autonomous-control-plane/watcher/requests"
}
+17
View File
@@ -0,0 +1,17 @@
{
"socket_path": "/run/aios-gitops/autonomous-control-plane/writer.sock",
"allowed_peer_uids": [
0
],
"policy_path": "/etc/aios-gitops/autonomous-control-plane/development-policy.json",
"grant_path": "/var/lib/aios-gitops/autonomous-control-plane/writer/grant.json",
"grant_quota_ledger": "/var/lib/aios-gitops/autonomous-control-plane/writer/grant-quota.sqlite",
"worktree_roots": [
"/data/aios-gitops/worktrees"
],
"credential_name": "gitea_http",
"remote": "origin",
"operation_ledger": "/var/lib/aios-gitops/autonomous-control-plane/writer/operations.sqlite",
"audit_path": "/var/lib/aios-gitops/autonomous-control-plane/writer/audit.jsonl",
"max_request_bytes": 4194304
}
@@ -1,22 +1,22 @@
[Unit]
Description=AIOS Autonomous Release Candidate API (Dormant/Shadow Only)
After=network.target
ConditionPathExists=/opt/aios-autonomous-release-candidate/aios_autonomous_release_candidate
Description=AIOS Autonomous Release Candidate (dormant shadow only)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=aiman
Group=gitops
UMask=0077
WorkingDirectory=/opt/aios-autonomous-release-candidate
Environment=PYTHONDONTWRITEBYTECODE=1
Environment=PYTHONPATH=/opt/aios-autonomous-release-candidate
Environment=AIOS_TASK_PLANE_EXECUTION_ENABLED=false
WorkingDirectory=/data/aios-gitops/worktrees/chatgpt-autonomous-completion-contract-20260726/services/aios-autonomous-release-candidate
Environment=AIOS_GITOPS_WRITES_ENABLED=false
Environment=AIOS_TASK_PLANE_EXECUTION_ENABLED=false
Environment=AIOS_SILENT_RELEASE_EXECUTION_ENABLED=false
Environment=AIOS_PRODUCTION_DEPLOYMENT_ENABLED=false
Environment=AIOS_CANDIDATE_LOOPBACK_HOST=127.0.0.1
Environment=PYTHONDONTWRITEBYTECODE=1
ExecStart=/usr/bin/python3 -m aios_autonomous_release_candidate.service --host 127.0.0.1 --port 19951 --state /var/lib/aios-autonomous-release-candidate/state.sqlite3
StateDirectory=aios-autonomous-release-candidate
RuntimeDirectory=aios-autonomous-release-candidate
Restart=on-failure
RestartSec=2
NoNewPrivileges=true
PrivateTmp=true
PrivateDevices=true
@@ -24,19 +24,19 @@ ProtectSystem=strict
ProtectHome=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectKernelLogs=true
ProtectControlGroups=true
ProtectClock=true
ProtectHostname=true
LockPersonality=true
RestrictSUIDSGID=true
RestrictRealtime=true
MemoryDenyWriteExecute=true
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
IPAddressDeny=any
IPAddressAllow=localhost
ReadOnlyPaths=/opt/aios-autonomous-release-candidate
RuntimeDirectory=aios-autonomous-release-candidate
StateDirectory=aios-autonomous-release-candidate
CacheDirectory=aios-autonomous-release-candidate
ReadOnlyPaths=/data/aios-gitops/worktrees/chatgpt-autonomous-completion-contract-20260726/services/aios-autonomous-release-candidate
ReadWritePaths=/run/aios-autonomous-release-candidate
ReadWritePaths=/var/lib/aios-autonomous-release-candidate
ReadWritePaths=/var/cache/aios-autonomous-release-candidate
UMask=0077
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,31 @@
[Unit]
Description=AIOS autonomous execution renewal controller
After=network-online.target
ConditionPathExists=/etc/aios-gitops/autonomous-control-plane/renewal.json
[Service]
Type=simple
User=root
Group=gitops
Environment=AIOS_GITOPS_WRITES_ENABLED=false
ExecStart=/usr/bin/aios-autonomous-renewal-controller --config /etc/aios-gitops/autonomous-control-plane/renewal.json
NoNewPrivileges=true
PrivateTmp=true
PrivateDevices=true
ProtectHome=true
ProtectSystem=strict
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
ProtectKernelLogs=true
RestrictSUIDSGID=true
RestrictRealtime=true
LockPersonality=true
MemoryDenyWriteExecute=true
UMask=0027
ReadWritePaths=/var/lib/aios-gitops/autonomous-control-plane/renewal /run/aios-gitops/autonomous-control-plane
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,24 @@
[Unit]
Description=AIOS governance approval watcher
After=network-online.target
ConditionPathExists=/etc/aios-gitops/autonomous-control-plane/watcher.json
[Service]
Type=simple
User=root
Group=gitops
Environment=AIOS_GITOPS_WRITES_ENABLED=false
ExecStart=/usr/bin/aios-governance-approval-watcher --config /etc/aios-gitops/autonomous-control-plane/watcher.json
NoNewPrivileges=true
PrivateTmp=true
PrivateDevices=true
ProtectHome=true
ProtectSystem=strict
ReadOnlyPaths=/data/aios-gitops/runtime/gitea-bin/data/gitea.db
ReadWritePaths=/var/lib/aios-gitops/autonomous-control-plane /run/aios-gitops/autonomous-control-plane
RestrictAddressFamilies=AF_UNIX
IPAddressDeny=any
UMask=0027
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,26 @@
[Unit]
Description=AIOS dormant single-owner machine-status prerequisite
RefuseManualStop=no
[Service]
Type=oneshot
User=aiman
Group=gitops
Environment=AIOS_SINGLE_OWNER_GOVERNANCE_ENABLED=false
Environment=AIOS_SINGLE_OWNER_ACTIVATION_ACTIVE=false
Environment=AIOS_GITOPS_WRITES_ENABLED=false
ExecStart=/usr/bin/python3 -m aios_single_owner_governance.service --component machine-status-producer --self-check --checkpoint-dir /var/lib/aios-gitops/runtime/single-owner-governance
NoNewPrivileges=true
PrivateTmp=true
PrivateDevices=true
ProtectSystem=strict
ProtectHome=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
RestrictSUIDSGID=true
LockPersonality=true
MemoryDenyWriteExecute=true
RestrictAddressFamilies=AF_UNIX
ReadOnlyPaths=/data/aios-gitops/repo.git
ReadWritePaths=/var/lib/aios-gitops/runtime/single-owner-governance
@@ -0,0 +1,31 @@
[Unit]
Description=AIOS governance approval mirror worker
After=network-online.target
ConditionPathExists=/etc/aios-gitops/autonomous-control-plane/mirror.json
[Service]
Type=simple
User=root
Group=gitops
Environment=AIOS_GITOPS_WRITES_ENABLED=false
ExecStart=/usr/bin/aios-governance-mirror-worker --config /etc/aios-gitops/autonomous-control-plane/mirror.json
NoNewPrivileges=true
PrivateTmp=true
PrivateDevices=true
ProtectHome=true
ProtectSystem=strict
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
ProtectKernelLogs=true
RestrictSUIDSGID=true
RestrictRealtime=true
LockPersonality=true
MemoryDenyWriteExecute=true
UMask=0027
ReadWritePaths=/data/aios-gitops/repo.git /var/lib/aios-gitops/autonomous-control-plane/mirror /run/aios-gitops/autonomous-control-plane
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,26 @@
[Unit]
Description=AIOS dormant single-owner review adapter prerequisite
RefuseManualStop=no
[Service]
Type=oneshot
User=aiman
Group=gitops
Environment=AIOS_SINGLE_OWNER_GOVERNANCE_ENABLED=false
Environment=AIOS_SINGLE_OWNER_ACTIVATION_ACTIVE=false
Environment=AIOS_GITOPS_WRITES_ENABLED=false
ExecStart=/usr/bin/python3 -m aios_single_owner_governance.service --component owner-review-adapter --self-check --checkpoint-dir /var/lib/aios-gitops/runtime/single-owner-governance
NoNewPrivileges=true
PrivateTmp=true
PrivateDevices=true
ProtectSystem=strict
ProtectHome=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
RestrictSUIDSGID=true
LockPersonality=true
MemoryDenyWriteExecute=true
RestrictAddressFamilies=AF_UNIX
ReadOnlyPaths=/data/aios-gitops/repo.git
ReadWritePaths=/var/lib/aios-gitops/runtime/single-owner-governance
@@ -0,0 +1,27 @@
[Unit]
Description=AIOS governance request publisher
After=network-online.target
ConditionPathExists=/etc/aios-gitops/autonomous-control-plane/publisher.json
[Service]
Type=simple
User=aios-governance-request-publisher
Group=gitops
Environment=AIOS_GITOPS_WRITES_ENABLED=false
ExecStart=/usr/bin/aios-governance-request-publisher --config /etc/aios-gitops/autonomous-control-plane/publisher.json
LoadCredentialEncrypted=gitea_http:/etc/credstore.encrypted/aios-governance-request-publisher.gitea_http
NoNewPrivileges=true
PrivateTmp=true
PrivateDevices=true
ProtectHome=true
ProtectSystem=strict
RestrictSUIDSGID=true
MemoryDenyWriteExecute=true
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
ReadWritePaths=/var/lib/aios-gitops/autonomous-control-plane /run/aios-gitops/autonomous-control-plane
IPAddressAllow=127.0.0.1
IPAddressDeny=any
UMask=0027
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,20 @@
[Unit]
Description=AIOS single-owner governance activation verifier
ConditionPathExists=/etc/aios-gitops/single-owner-activation-bindings.json
[Service]
Type=oneshot
User=root
Group=gitops
Environment=AIOS_GITOPS_WRITES_ENABLED=false
ExecStart=/usr/bin/aios-single-owner-governance --verify --bindings /etc/aios-gitops/single-owner-activation-bindings.json --state-root /var/lib/aios-gitops/governance-activation --lock-path /run/aios-gitops/governance-activation.lock
NoNewPrivileges=true
PrivateTmp=true
ProtectHome=true
ProtectSystem=strict
ReadWritePaths=/var/lib/aios-gitops/governance-activation /run/aios-gitops
RestrictSUIDSGID=true
UMask=0027
# Deliberately not installable. Deployment remains disabled/inactive;
# activation is an explicit approved CLI transaction, never an always-on unit.
@@ -0,0 +1,28 @@
[Unit]
Description=AIOS governed development writer credential revoker
After=network-online.target
ConditionPathExists=/etc/aios-gitops/autonomous-control-plane/development-policy.json
[Service]
Type=oneshot
User=root
Group=gitops
Environment=AIOS_GITOPS_WRITES_ENABLED=false
ExecStart=/usr/bin/aios-development-writer-revoker --config /etc/aios-gitops/autonomous-control-plane/revoker.json
LoadCredentialEncrypted=writer_gitea_http:/etc/credstore.encrypted/aios-governed-development-writer.gitea_http
LoadCredentialEncrypted=admin_gitea_http:/etc/credstore.encrypted/aios-governance-admin.gitea_http
NoNewPrivileges=true
PrivateTmp=true
PrivateDevices=true
ProtectHome=true
ProtectSystem=strict
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
ProtectKernelLogs=true
RestrictSUIDSGID=true
RestrictRealtime=true
LockPersonality=true
MemoryDenyWriteExecute=true
UMask=0027
ReadWritePaths=/var/lib/aios-gitops/autonomous-control-plane/writer /run/aios-gitops/autonomous-control-plane -/etc/credstore.encrypted/aios-governed-development-writer.gitea_http
@@ -0,0 +1,11 @@
[Unit]
Description=AIOS governed development writer revocation timer
[Timer]
OnBootSec=5min
OnUnitActiveSec=5min
AccuracySec=30s
Unit=aios-governed-development-writer-revoker.service
[Install]
WantedBy=timers.target
@@ -0,0 +1,33 @@
[Unit]
Description=AIOS governed development writer
After=network-online.target
ConditionPathExists=/etc/aios-gitops/autonomous-control-plane/development-policy.json
[Service]
Type=simple
User=aios-governed-development-writer
Group=gitops
Environment=AIOS_GITOPS_WRITES_ENABLED=false
ExecStart=/usr/bin/aios-development-writer-server --config /etc/aios-gitops/autonomous-control-plane/writer-server.json
LoadCredentialEncrypted=gitea_http:/etc/credstore.encrypted/aios-governed-development-writer.gitea_http
NoNewPrivileges=true
PrivateTmp=true
PrivateDevices=true
ProtectHome=true
ProtectSystem=strict
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
ProtectKernelLogs=true
RestrictSUIDSGID=true
RestrictRealtime=true
LockPersonality=true
MemoryDenyWriteExecute=true
RestrictAddressFamilies=AF_UNIX AF_INET
IPAddressAllow=127.0.0.1
IPAddressDeny=any
ReadWritePaths=/data/aios-gitops/worktrees /var/lib/aios-gitops/autonomous-control-plane/writer /run/aios-gitops/autonomous-control-plane
UMask=0027
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,23 @@
[Unit]
Description=AIOS Integration Refresh Provisioner (sandbox control plane)
After=network-online.target
[Service]
Type=oneshot
RemainAfterExit=yes
User=aios
Group=aios
Environment=AIOS_GITOPS_WRITES_ENABLED=false
Environment=AIOS_INTEGRATION_REFRESH_MODE=sandbox
Environment=AIOS_CREDENTIAL_PROVIDER=disabled
NoNewPrivileges=yes
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
ReadWritePaths=/var/lib/aios/integration-refresh
RestrictAddressFamilies=AF_UNIX
IPAddressDeny=any
ExecStart=/usr/bin/python3 /opt/aios/active-runtime/app/aios_isolated_writer_api/integration_refresh.py --sandbox-preflight --state-dir /var/lib/aios/integration-refresh
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,31 @@
[Unit]
Description=AIOS exact release object projection
After=network-online.target
ConditionPathExists=/etc/aios-gitops/autonomous-control-plane/release-projection.json
[Service]
Type=simple
User=root
Group=gitops
Environment=AIOS_GITOPS_WRITES_ENABLED=false
ExecStart=/usr/bin/aios-release-object-projector --config /etc/aios-gitops/autonomous-control-plane/release-projection.json
NoNewPrivileges=true
PrivateTmp=true
PrivateDevices=true
ProtectHome=true
ProtectSystem=strict
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
ProtectKernelLogs=true
RestrictSUIDSGID=true
RestrictRealtime=true
LockPersonality=true
MemoryDenyWriteExecute=true
UMask=0027
ReadWritePaths=/var/lib/aios-gitops/autonomous-control-plane/projection /run/aios-gitops/autonomous-control-plane
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target
@@ -0,0 +1,28 @@
[Unit]
Description=AIOS exact runtime source read projection
After=network-online.target
ConditionPathExists=/etc/aios-gitops/autonomous-control-plane/runtime-projection.json
[Service]
Type=oneshot
User=root
Group=gitops
Environment=AIOS_GITOPS_WRITES_ENABLED=false
ExecStart=/usr/bin/aios-runtime-source-projector --config /etc/aios-gitops/autonomous-control-plane/runtime-projection.json
NoNewPrivileges=true
PrivateTmp=true
PrivateDevices=true
ProtectHome=true
ProtectSystem=strict
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
ProtectKernelLogs=true
RestrictSUIDSGID=true
RestrictRealtime=true
LockPersonality=true
MemoryDenyWriteExecute=true
UMask=0027
ReadWritePaths=/var/lib/aios-gitops/autonomous-control-plane/projection /run/aios-gitops/autonomous-control-plane
PrivateNetwork=true
@@ -0,0 +1,29 @@
[Unit]
Description=AIOS V2R1C4 unified governed release envelope executor
After=network-online.target
ConditionPathExists=/etc/aios-gitops/autonomous-control-plane/release-hooks.json
ConditionPathExists=/var/lib/aios-gitops/autonomous-control-plane/release/envelope.json
[Service]
Type=oneshot
User=root
Group=gitops
Environment=AIOS_GITOPS_WRITES_ENABLED=false
ExecStart=/usr/bin/aios-v2r1c4-release-executor --config /etc/aios-gitops/autonomous-control-plane/release-hooks.json --envelope /var/lib/aios-gitops/autonomous-control-plane/release/envelope.json --operation-id v2r1c4-production-release
LoadCredentialEncrypted=gitea_http:/etc/credstore.encrypted/aios-release-executor.gitea_http
LoadCredentialEncrypted=tag_api:/etc/credstore.encrypted/aios-release-executor.tag_api
NoNewPrivileges=true
PrivateTmp=true
PrivateDevices=true
ProtectHome=true
ProtectSystem=strict
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true
ProtectKernelLogs=true
RestrictSUIDSGID=true
RestrictRealtime=true
LockPersonality=true
MemoryDenyWriteExecute=true
UMask=0027
ReadWritePaths=/var/lib/aios-gitops/autonomous-control-plane/release /var/lib/aios-gitops/release /opt/aios /run/aios-gitops/autonomous-control-plane
@@ -0,0 +1,19 @@
d /var/lib/aios-gitops/autonomous-control-plane 0750 root gitops -
d /var/lib/aios-gitops/autonomous-control-plane/writer 0750 root gitops -
d /var/lib/aios-gitops/autonomous-control-plane/mirror 0750 root gitops -
d /var/lib/aios-gitops/autonomous-control-plane/projection 0750 root gitops -
d /var/lib/aios-gitops/autonomous-control-plane/renewal 0750 root gitops -
d /var/lib/aios-gitops/autonomous-control-plane/release 0750 root gitops -
d /run/aios-gitops/autonomous-control-plane 0750 root gitops -
d /var/lib/aios-gitops/autonomous-control-plane/publisher 0750 root gitops -
d /var/lib/aios-gitops/autonomous-control-plane/watcher 0750 root gitops -
d /var/lib/aios-gitops/autonomous-control-plane/watcher/requests 0750 root gitops -
d /var/lib/aios-gitops/autonomous-control-plane/watcher/status 0750 root gitops -
d /var/lib/aios-gitops/autonomous-control-plane/mirror/requests 0750 root gitops -
d /var/lib/aios-gitops/autonomous-control-plane/mirror/receipts 0750 root gitops -
d /var/lib/aios-gitops/autonomous-control-plane/projection/requests 0750 root gitops -
d /var/lib/aios-gitops/autonomous-control-plane/projection/receipts 0750 root gitops -
d /var/lib/aios-gitops/autonomous-control-plane/projection/releases 0750 root gitops -
d /var/lib/aios-gitops/autonomous-control-plane/renewal/requests 0750 root gitops -
d /var/lib/aios-gitops/autonomous-control-plane/renewal/receipts 0750 root gitops -
d /var/lib/aios-gitops/autonomous-control-plane/release/metadata-snapshots 0750 root gitops -
@@ -0,0 +1,4 @@
d /var/lib/aios-gitops/governance-activation 0750 root gitops -
d /var/lib/aios-gitops/governance-activation/wal 0750 root gitops -
d /var/lib/aios-gitops/governance-activation/receipts 0750 root gitops -
d /run/aios-gitops 0750 root gitops -
@@ -0,0 +1,133 @@
# AIOS V2R1C4 Dormant Shadow-Only Release Execution
## Purpose
Deploy verified V2R1C4 code while keeping autonomous execution capabilities disabled.
This release separates code deployment from capability activation. Capability activation requires a later, independently approved release.
## Deployment mode
```text
deployment_mode=dormant_shadow_only
```
Required production flags:
```text
AIOS_GITOPS_WRITES_ENABLED=false
AIOS_TASK_PLANE_EXECUTION_ENABLED=false
AIOS_SILENT_RELEASE_EXECUTION_ENABLED=false
```
Required dispatch transport:
```text
protectedDispatchTransport=sandbox
```
Disabled capabilities:
```text
native_openclaw_enabled=false
native_codex_enabled=false
automatic_release_enabled=false
production_side_effects=false
activation_deferred=true
```
## Release flow
```text
Integration verification
|
v
Owner dormant release approval
|
v
Merge PR
|
v
Read final main merge OID
|
v
Rebuild production artifact
|
v
Deploy dormant runtime
|
v
Create V2R1C4 tag
|
v
Verify Registry and Baseline consistency
```
## Two-stage approval model
### Pre-merge approval
The pre-merge approval binds:
- version
- PR number
- integration OID and tree OID
- candidate artifact and evidence hashes
- deployment mode
- all production execution flags remaining false
It authorizes the governed merge and production artifact rebuild. It does not predeclare the final production target OID.
### Final release approval
After the governed merge, the final approval binds:
- final main merge OID
- final main tree OID
- production artifact, manifest, package closure, and evidence closure
- rollback artifact and production configuration hashes
- V2R1C4 tag target
The integration OID must never be used as the final production target OID.
## Deferred activation
The following are separate activation gates and must not block dormant code deployment:
- OpenClaw native session provider
- Codex app-server provider
- Gitea Admin API automation
- Secret Manager V2
- autonomous production execution
They require a later activation release with separate evidence and approval.
## Hard restrictions
Never:
- write main directly
- force push
- rewrite tags
- export secrets
- enable autonomous production execution without approval
- treat integration OID as production target OID
- update Registry or Baseline before remote and Gitea tag verification
## Dormant production acceptance
A dormant deployment is acceptable only when:
```text
deployment_mode=dormant_shadow_only
production_side_effects=false
protectedDispatchTransport=sandbox
native_openclaw_enabled=false
native_codex_enabled=false
automatic_release_enabled=false
AIOS_GITOPS_WRITES_ENABLED=false
AIOS_TASK_PLANE_EXECUTION_ENABLED=false
AIOS_SILENT_RELEASE_EXECUTION_ENABLED=false
```
The release report must preserve deferred activation blockers rather than falsely marking native-agent or automatic-release capabilities as enabled.
+70 -35
View File
@@ -1,47 +1,82 @@
# AIOS Autonomous Release Execution Package
## Mode
## Release Strategy
Candidate/shadow first. Production changes require an independent release approval.
V2R1C4 uses a two-stage model:
## Flow
1. Dormant code deployment.
2. Capability activation through a separate approved release.
1. Create TaskPlan.
2. Validate policy and approval envelope.
3. Route workers by capability and trust zone.
4. Execute on isolated candidate branch.
5. Collect evidence chain and artifact manifest.
6. Verify release plan.
7. Wait for owner approval.
8. Deploy only after approval.
## Dormant Deployment
## Hard stops
The first production release must keep autonomous execution disabled:
- main writes
- force push
- tag rewrite
- baseline mutation
- registry mutation
- plaintext credential handling
- missing evidence closure
## Required evidence
- git ref closure
- test report
- artifact checksum
- manifest checksum
- credential revoke proof
- production invariant snapshot
## Production activation
The executor must keep:
```
```text
AIOS_GITOPS_WRITES_ENABLED=false
AIOS_TASK_PLANE_EXECUTION_ENABLED=false
AIOS_SILENT_RELEASE_EXECUTION_ENABLED=false
```
until release approval is independently verified.
Required mode:
```text
deployment_mode=dormant_shadow_only
```
Required properties:
```text
production_side_effects=false
native_openclaw_enabled=false
native_codex_enabled=false
automatic_release_enabled=false
```
## Deployment Sequence
```text
Verify Integration
|
v
Owner Dormant Release Approval
|
v
Merge Pull Request
|
v
Read final main merge OID
|
v
Build Production Artifact
|
v
Deploy Dormant Runtime
|
v
Create Version Tag
|
v
Verify Registry and Baseline
```
## Capability Activation (Separate)
The following require separate approval and evidence:
- OpenClaw native provider
- Codex app-server provider
- Gitea Admin API lifecycle
- Secret Manager V2
- Autonomous production execution
## Hard Governance Rules
Never:
- direct write main
- force push
- rewrite tags
- export credentials
- enable autonomous production execution without approval
- use integration OID as production target OID
- update Baseline before remote tag verification
@@ -0,0 +1,35 @@
{
"execution_renewal_manifest_blob_oid": "417f78abb3d795f7b0c04a0894069bd166bb15df",
"execution_renewal_manifest_sha256": "069091eff9c7c46bceccde365da6d07a8538e76bee9e288a57211f3114b94ab3",
"execution_renewal_merge_oid": "3a22c3c7d86930efd4bd93ac05d80ad322050cb9",
"observed_at": "2026-07-28T09:09:27.841968Z",
"runtime_source_evidence_sha256": "23f03d9c6abe6f11e1acda5210b8708696e3b77caa0083b6bfaef30a44b2a35f",
"runtime_source_observed_at": "2026-07-28T05:53:01.253909Z",
"schema": "aios.integration-refresh-source-map/v1",
"source_mapping_approval_merge_oid": "9514062cf33e45941abe4a8c98377e0713b0b3f6",
"source_mapping_manifest_blob_oid": "00c5e90e529cb98c7750a5761b0a0d6ccf507174",
"source_mapping_manifest_sha256": "17af8ebc43673c0e46bb9ab1bf44497e8c147f1b4a9d7a8a404edd5c0abd6f21",
"sources": [
{
"byte_for_byte_equal": true,
"canonical_blob_oid": "9c68be36e0b6a18260e3099e3d7b0dcab6d814aa",
"canonical_commit_oid": "85426f4f570e4d40b212318d47e52e4aed5cb06a",
"canonical_git_path": "active-runtime/app/aios_autonomous_release_authorization/controller.py",
"canonical_tree_oid": "cdbd9bb845b18d4b5224d91aace2656a51eb7c14",
"runtime_blob_oid": "9c68be36e0b6a18260e3099e3d7b0dcab6d814aa",
"runtime_path": "/usr/libexec/aios-autonomous-release-authorization/controller.py",
"runtime_sha256": "39b532c3058c4ae49ed691d7c66c3b7745a25111fa61039f388c8335fd262c50"
},
{
"byte_for_byte_equal": true,
"canonical_blob_oid": "ee0595ca61a0598f8cb3dc10865a255e21679b2a",
"canonical_commit_oid": "85426f4f570e4d40b212318d47e52e4aed5cb06a",
"canonical_git_path": "active-runtime/app/aios_chatgpt_action_task_router/action_task_router.py",
"canonical_tree_oid": "cdbd9bb845b18d4b5224d91aace2656a51eb7c14",
"runtime_blob_oid": "ee0595ca61a0598f8cb3dc10865a255e21679b2a",
"runtime_path": "/usr/libexec/aios-chatgpt-action-task-router-continuous-20260726/action_task_router.py",
"runtime_sha256": "738fb7035a6b792115d6467b4d1e4ffd47a8c5d5646f7032ebc68ea587cbd554"
}
],
"task_id": "AIOS-INTEGRATION-REFRESH-PROVISIONER-IMPLEMENTATION-20260726"
}
@@ -0,0 +1,414 @@
# AIOS V2R1C4 Dormant Release — Final Agent Execution Contract
## 1. Purpose
This document is the machine/operator execution contract for completing the governed V2R1C4 dormant release after Candidate development is frozen.
It does not grant production authority. Every consequential action remains subject to the exact approval, Git reference, artifact, tag, Registry and Baseline gates defined below.
## 2. Release mode
The only permitted deployment mode for V2R1C4 is:
```text
deployment_mode=dormant_shadow_only
```
The following values are mandatory before, during and after deployment:
```text
AIOS_GITOPS_WRITES_ENABLED=false
AIOS_TASK_PLANE_EXECUTION_ENABLED=false
AIOS_SILENT_RELEASE_EXECUTION_ENABLED=false
AIOS_PRODUCTION_DEPLOYMENT_ENABLED=false
production_side_effects=false
native_openclaw_enabled=false
native_codex_enabled=false
automatic_release_enabled=false
activation_deferred=true
protectedDispatchTransport=sandbox
```
Any deviation is a hard stop.
## 3. Authoritative branches
```text
candidate_branch=patch/chatgpt-autonomous-completion-contract-20260726
integration_branch=integration/chatgpt-autonomous-completion-20260726
production_branch=main
version=V2R1C4
pull_request=20
```
Never infer a target OID from workspace HEAD. Always read and record full 40-character OIDs.
## 4. Phase A — Candidate freeze and verification
1. Read the current Candidate OID and tree OID.
2. Verify the Candidate worktree is clean.
3. Verify four-way Candidate reference closure:
- local branch ref;
- authoritative bare ref;
- `git ls-remote` ref;
- Gitea API branch ref.
4. Run the complete test and static validation suite defined in `V2R1C4_TEST_AND_RELEASE_CHECKLIST.md`.
5. Build the deterministic Candidate artifact and manifest from the exact Candidate OID.
6. Record:
- artifact SHA-256;
- manifest SHA-256;
- package closure SHA-256;
- test evidence SHA-256;
- rollback artifact SHA-256;
- production configuration SHA-256.
7. Do not reuse any digest generated from an older Candidate OID.
Failure state:
```text
CANDIDATE_ACCEPTANCE_BLOCKED
```
## 5. Phase B — Integration refresh
1. Refresh the Integration branch from the latest Candidate using a governed non-fast-forward merge.
2. Use:
```text
merge --no-ff
```
3. Do not squash, rebase, force push or rewrite Candidate history.
4. Verify the Candidate OID is reachable from the new Integration OID.
5. Verify four-way Integration reference closure.
6. Run the full Candidate test suite again from the Integration worktree.
7. Rebuild Integration evidence and Candidate package metadata if any tracked file changed during the merge.
Failure state:
```text
INTEGRATION_ACCEPTANCE_BLOCKED
```
## 6. Phase C — Candidate acceptance
Create Candidate acceptance evidence and execute:
```text
python scripts/autonomous-release/run_acceptance.py \
--phase candidate \
--input <candidate-evidence.json> \
--output <candidate-acceptance.json>
```
The result must contain:
```text
phase=candidate
status=READY
production_action_authorized=false
```
The Candidate gate must verify:
```text
candidate_four_way_ref_closure=true
integration_four_way_ref_closure=true
candidate_reachable_from_integration=true
worktree_clean=true
tests_failed=0
tests_passed>0
```
It must also verify all dormant runtime flags remain disabled.
`READY` authorizes review only. It does not authorize merge, deployment, tagging, Registry writes or Baseline writes.
## 7. Phase D — Pre-merge owner approval
Obtain a governed Owner approval plan for the exact Integration package.
The approval subject must bind at least:
```text
version
pull_request
candidate_oid
candidate_tree_oid
integration_oid
integration_tree_oid
candidate_acceptance_digest
candidate_release_digest
artifact_sha256
manifest_sha256
package_closure_sha256
evidence_closure_oid
rollback_artifact_sha256
production_config_sha256
deployment_mode=dormant_shadow_only
all execution flags=false
```
The approval must not predeclare the final production target OID.
Without an Owner-approved plan, stop with:
```text
BLOCKED_EXTERNAL_OWNER_SIGNATURE
```
## 8. Phase E — Governed merge
1. Merge PR #20 only through the governed merge path.
2. Do not push directly to `main`.
3. After merge, read the exact resulting `main` merge OID and tree OID.
4. Verify four-way `main` reference closure.
5. Verify the merged Integration OID is reachable from the final `main` merge OID.
6. Never use the Integration OID as the production target OID.
Failure state:
```text
MAIN_RECONCILIATION_REQUIRED
```
## 9. Phase F — Production artifact rebuild
Rebuild all production artifacts from the exact final `main` merge OID.
Record new values for:
```text
artifact_sha256
manifest_sha256
package_closure_sha256
evidence_closure_oid
rollback_artifact_sha256
production_config_sha256
```
Candidate or Integration artifacts may not be promoted by renaming or metadata substitution. The final artifact must be demonstrably rebuilt from the final production target.
## 10. Phase G — Final release digest and Owner approval
1. Construct the `FinalDormantRelease` object using the final `main` target and rebuilt artifact evidence.
2. Compute its exact `release_digest`.
3. Obtain an Owner approval receipt bound to that exact digest.
4. Record:
```text
approval_plan_id
approval_receipt_sha256
approved_release_digest
owner_approval_verified=true
```
Required equality:
```text
approved_release_digest == FinalDormantRelease.digest()
```
A generic approval, previous approval, Candidate approval or Integration approval is insufficient.
## 11. Phase H — Final acceptance
Execute:
```text
python scripts/autonomous-release/run_acceptance.py \
--phase final \
--input <final-evidence.json> \
--output <final-acceptance.json>
```
The result must contain:
```text
phase=final
status=READY
production_action_authorized=false
```
Required final evidence:
```text
main_four_way_ref_closure=true
target_is_main_head=true
worktree_clean=true
artifact_rebuilt_from_target=true
rollback_verified=true
runtime_boundary_verified=true
owner_approval_verified=true
approved_release_digest=<exact final release digest>
```
The acceptance report is evidence, not an execution credential.
## 12. Phase I — Dormant deployment
Deployment requires a separate production execution approval referencing:
```text
version=V2R1C4
target_oid=<final main merge OID>
evidence_closure_oid=<final evidence closure OID>
approval_plan_id=<approved plan>
final_acceptance_digest=<final acceptance digest>
```
Deploy only the dormant runtime. Before starting services, verify all disabled flags and the sandbox transport in the rendered production configuration.
After start, verify:
```text
services_active=true
loopback_bindings_only=true
production_side_effects=false
write_endpoints_disabled=true
task_plane_execution_disabled=true
silent_release_execution_disabled=true
native_agent_execution_disabled=true
```
If any side-effect-capable path is active, stop services and mark:
```text
DORMANT_RUNTIME_BOUNDARY_VIOLATION
```
## 13. Phase J — Tag governance
Tagging is permitted only after dormant deployment verification and exact Owner approval.
Every tag request must carry:
```text
version=V2R1C4
target_oid=<full final main merge OID>
evidence_closure_oid=<full final evidence closure OID>
approval_plan_id=<approved plan ID>
```
When an existing same-name tag is found:
- identical tag OID and peeled target: treat as idempotent and reverify;
- different tag OID or peeled target: stop with `MANUAL_OWNER_REVIEW_REQUIRED`.
Never force push, delete, move or rewrite a formal tag.
## 14. Phase K — Remote and Gitea tag acceptance
Before any Registry or Baseline update, verify:
```text
local_tag_exists=true
remote_tag_exists=true
gitea_api_tag_exists=true
local_tag_oid == remote_tag_oid
remote_peeled_target_oid == target_oid
gitea_peeled_target_oid == target_oid
repository_identity=aios/aios
```
For annotated tags, verify both the tag object and peeled commit.
Failure state:
```text
REMOTE_PUSH_PENDING
```
or:
```text
RECONCILIATION_REQUIRED
```
## 15. Phase L — Registry and Baseline
Only after remote and Gitea tag acceptance:
1. register or update the V2R1C4 Registry entry;
2. reread the Registry and verify the exact target OID;
3. update the Production Baseline to V2R1C4;
4. reread the Baseline;
5. run final release consistency acceptance.
Required equality:
```text
Registry version
= Production Baseline
= local refs/tags/V2R1C4
= remote refs/tags/V2R1C4
= Gitea API tag
= expected final main target
```
Do not write `PRODUCTION_DEPLOYED` before this equality is proven.
## 16. Rollback
If deployment verification fails:
1. disable the V2R1C4 Candidate services;
2. preserve evidence and logs;
3. submit a governed rollback request with the target version and reason;
4. distinguish `requested`, `approved`, `executed` and `verified` states.
Do not delete tags, rewrite history or independently perform a production rollback.
## 17. Mandatory stop conditions
Stop without consequential execution when any of the following is missing or false:
```text
four_way_ref_closure
clean_worktree
full_test_closure
artifact_digest_verification
rollback_evidence
runtime_boundary_verification
exact_owner_approval
exact_target_oid
exact_evidence_closure_oid
exact_approval_plan_id
remote_tag_verification
gitea_tag_verification
```
## 18. Final report schema
Return:
```text
execution_status=COMPLETED|PARTIALLY_COMPLETED|BLOCKED|FAILED
candidate_oid=<full OID>
integration_oid=<full OID>
main_target_oid=<full OID>
artifact_sha256=<digest>
manifest_sha256=<digest>
package_closure_sha256=<digest>
evidence_closure_oid=<full OID>
candidate_acceptance_status=<status>
final_acceptance_status=<status>
owner_approval_verified=<bool>
dormant_deployment_verified=<bool>
local_tag_verified=<bool>
remote_tag_verified=<bool>
gitea_tag_verified=<bool>
registry_verified=<bool>
baseline_verified=<bool>
release_consistency=<status>
```
Always include:
```text
force_push_used: false
tag_rewritten: false
direct_main_push: false
secrets_committed: false
owner_approval_respected: true
```
@@ -0,0 +1,30 @@
# AIOS V2R1C4 Dormant Release Agent Execution
## Scope
This package is a dormant, shadow-only Candidate release. It does not authorize a production deployment, tag, baseline update, registry update, or a write to main.
## Required runtime boundary
deployment_mode=dormant_shadow_only
AIOS_GITOPS_WRITES_ENABLED=false
AIOS_TASK_PLANE_EXECUTION_ENABLED=false
AIOS_SILENT_RELEASE_EXECUTION_ENABLED=false
production_side_effects=false
## Governed sequence
1. Verify the Integration branch with the Candidate test suite and four-way Git reference closure.
2. Obtain Owner approval for dormant review only.
3. Merge only after a separate production release approval; this document does not grant that approval.
4. Read the resulting main merge OID before building an artifact.
5. Deploy only the dormant Candidate runtime after explicit production approval.
6. Create a tag and update Registry or Baseline only under their separate governed approvals.
## Evidence requirements
The execution agent must retain task ID, commit OID, artifact and manifest digests, test evidence, rollback package reference, and approval receipt metadata. It must not include plaintext credentials or private keys.
## Stop conditions
Stop with NOT_EXECUTED if any approval, four-way reference closure, artifact verification, or deployment gate is absent. Do not infer production authorization from Candidate authorization.
@@ -0,0 +1,65 @@
{
"evidence_type": "FULL_PACKAGE_FILE_MANIFEST_DRIFT_CLOSURE",
"release": "V2R1C4",
"entries": [
{
"path": "VERSION",
"old_sha256": "1095953d5764d9c72f02e84b1d525e265d3a67a5bdb451ab9ef51a8e8b3032b8",
"new_sha256": "b090062a92aeb14211bbfc1fcd43b5ee4f315d30054b437f3bcdac657210ee95",
"source_commit": "9917145a643c7938a1a4f4f72690674f13f4c3dd",
"reason": "V2R1C4 release completion froze the candidate package version",
"resolution": "accept the V2R1C4 candidate content and update only this manifest entry"
},
{
"path": "active-runtime/app/aios_gitops_model_api.py",
"old_sha256": "ee6e4fc6bc98a590fbc3585b62707e2562115e5a3f722e69a7c464d966bdc458",
"new_sha256": "a664714bc1e13160cdd0dc32de460c65a83a3a95aa5cdad1833b1b650ab90e9d",
"source_commit": "45dd78ec796faf9fdfba397e5bab73176aeef3f6",
"reason": "tracked GitOps API gained the governed short-link and webhook endpoint",
"resolution": "accept current tracked content and update only this manifest entry"
},
{
"path": "config/domains.env",
"old_sha256": "a3872f24e4cd64288758846ecd0b374db4aa41111feec3b17c41438136d6d5d0",
"new_sha256": "5a27d48db343dcb723bcc1d9d8222aa3f72094c57cc43640aebe6558963bd20f",
"source_commit": "e7d36ad706473c95a4d4b9ec791120271ed856cd",
"reason": "tracked authentication routing made cnnetmkt Casdoor the primary endpoint",
"resolution": "accept current tracked content and update only this manifest entry"
},
{
"path": "scripts/install_v1r1c0_gitops_model_overlay.sh",
"old_sha256": "4d54a4ed6d35bbb6e9fe2924f3cfceeea9c3eed898017788f6c5ab8041394a47",
"new_sha256": "e76cd986d2387b76439e4a7fe503e48a2de8fc70fb52c45a13641c9d943f108f",
"source_commit": "a2481421589e167680e7d801433758c6a01db832",
"reason": "tracked installer added online Codex patch-branch synchronization",
"resolution": "accept current tracked content and update only this manifest entry"
},
{
"path": "scripts/package_contract_check_v1r1c0.sh",
"old_sha256": "e0abd8ad44d4f088108f2046fd9d7bbd4465af50f79bd3b9fcbb72a8c61bee65",
"new_sha256": "f537c58fc08685601f47fac5c0a3a2856e3ce9806c1918848c87a194a49c28a0",
"source_commit": "9917145a643c7938a1a4f4f72690674f13f4c3dd",
"reason": "release completion generalized the legacy version check without weakening required files or compilation checks",
"resolution": "accept the generalized semantic version check and update only this manifest entry"
},
{
"path": "scripts/release_version.sh",
"old_sha256": "abef8df642c6d233af4a03c512876b02b326e19c924020b8dd8cc74236d18817",
"new_sha256": "14ab40a7c342e21e8315def14d552dcff4b214d23f1f4be493e9d3fc2cee1a2f",
"source_commit": "27a0ae126b96d63c40544174e7a3859e30eba056",
"reason": "tracked release helper closed tag-publication governance gaps",
"resolution": "accept current tracked content and update only this manifest entry"
}
],
"verifier_change": {
"path": "package_verify_full.sh",
"old_sha256": "b77bce1ca78cede27de6594e25f6ce5bb0c4990c0953eeb79f7e2608d2515c32",
"new_sha256": "ca49f4165b50171968485ffa5535bc33d2158237ea55af10a3888a5fa0f7a535",
"source_commit": "V2R1C4 release completion candidate",
"reason": "remove the checksum failure suppression and fail closed when the manifest is absent or mismatched",
"resolution": "update only the verifier self-entry after the six historical drifts were closed"
},
"entry_count": 6,
"whole_manifest_regenerated": false,
"verifier_fail_closed": true
}
@@ -0,0 +1,14 @@
# Agent Integration Plan
1. Verify package SHA-256 and FILE_SHA256.json.
2. Apply added/modified source files to patch/v2r1c4-release-completion-20260729.
3. Do not copy __pycache__, .pytest_cache, TEST_RECEIPT.json, or local package metadata into runtime paths.
4. Preserve existing approved WeChat and single-owner activation Git objects.
5. Install the Python package and verify every pyproject console entrypoint.
6. Render environment-specific JSON configs from config/examples.
7. Use systemd encrypted credentials only; never persist plaintext tokens.
8. Run compileall, all repository tests, systemd unit verification, secret scan, and online copied-Gitea acceptance.
9. Build one autonomous-control-plane Artifact and rollback Artifact.
10. Create one Owner approval for AIOS_AUTONOMOUS_RELEASE_CONTROL_PLANE_BOOTSTRAP.
11. After deployment, complete V2R1C4 code freeze and create one V2R1C4_PRODUCTION_RELEASE_ENVELOPE.
12. Do not split unchanged code/artifact/permission scope into additional approvals.
@@ -0,0 +1,78 @@
{
"capabilities": {
"approval_mirror_worker": "DELIVERED",
"approval_watcher": "DELIVERED",
"artifact_atomic_deployment": "DELIVERED",
"automatic_renewal_controller": "DELIVERED",
"credential_revoker_with_old_token_401": "DELIVERED",
"governance_request_publisher": "DELIVERED",
"governed_development_writer_server": "DELIVERED",
"governed_merge_transport": "DELIVERED",
"governed_tag_transport": "DELIVERED",
"grant_quota_idempotency": "DELIVERED",
"grant_revocation_evidence": "DELIVERED",
"health_rollback_restore": "DELIVERED",
"owner_exact_head_release_approval_verification": "DELIVERED",
"registry_baseline_snapshot_compensation": "DELIVERED",
"release_object_projection": "DELIVERED",
"remote_tag_and_final_consistency": "DELIVERED",
"runtime_source_projection": "DELIVERED",
"task_bound_writer_grant": "DELIVERED",
"unified_release_envelope_executor": "DELIVERED"
},
"delivery_status": "CODE_COMPLETE_OFFLINE_VALIDATED",
"direct_main_push": false,
"entrypoints": {
"aios-autonomous-control-plane": "aios_autonomous_control_plane.cli:main",
"aios-autonomous-renewal-controller": "aios_autonomous_control_plane.renewal_service:main",
"aios-development-writer-revoker": "aios_autonomous_control_plane.revoker:main",
"aios-development-writer-server": "aios_autonomous_control_plane.writer_server:main",
"aios-governance-approval-watcher": "aios_autonomous_control_plane.watcher_service:main",
"aios-governance-mirror-worker": "aios_autonomous_control_plane.mirror_service:main",
"aios-governance-request-publisher": "aios_autonomous_control_plane.publisher:main",
"aios-release-hook": "aios_autonomous_control_plane.release_action:main",
"aios-release-object-projector": "aios_autonomous_control_plane.projection_service:release_main",
"aios-runtime-source-projector": "aios_autonomous_control_plane.projection_service:runtime_main",
"aios-v2r1c4-release-executor": "aios_autonomous_control_plane.release_service:main"
},
"force_push": false,
"global_writes_must_remain_false": true,
"package_version": "0.2.0",
"schema": "aios.code-handoff/v2",
"source": "codex-chartgpt-bridge",
"systemd_units": [
"deploy/systemd/aios-autonomous-renewal-controller.service",
"deploy/systemd/aios-governance-approval-watcher.service",
"deploy/systemd/aios-governance-mirror-worker.service",
"deploy/systemd/aios-governance-request-publisher.service",
"deploy/systemd/aios-governed-development-writer-revoker.service",
"deploy/systemd/aios-governed-development-writer-revoker.timer",
"deploy/systemd/aios-governed-development-writer.service",
"deploy/systemd/aios-release-object-projection.service",
"deploy/systemd/aios-runtime-source-read-projection.service",
"deploy/systemd/aios-v2r1c4-release-envelope.service"
],
"tag_rewrite": false,
"target_branch": "patch/v2r1c4-release-completion-20260729",
"tests": {
"collection_errors": 0,
"compileall": "PASS",
"pytest_failed": 0,
"pytest_passed": 21,
"secret_findings": [
{
"path": "tests/autonomous_control_plane/test_policy_and_writer.py",
"rule": "literal-credential-assignment"
}
],
"secret_findings_classification": {
"tests/autonomous_control_plane/test_policy_and_writer.py": "Deliberate fake api_key assignment used by the negative secret-rejection test."
},
"secret_scan": "PASS_TEST_FIXTURE_ONLY"
},
"wechat_backend": {
"new_code_generated": false,
"reason": "The controlled PR #20 full diff remains too large for this interface; existing exact Git objects must be retained and validated, not re-invented.",
"required_agent_action": "Preserve already integrated approved WeChat code, run its online acceptance suite, and include its exact object IDs in the final release envelope."
}
}
@@ -0,0 +1,84 @@
{
"added_files": [
"AGENT_INTEGRATION_PLAN.md",
"config/examples/grant.json",
"config/examples/mirror.json",
"config/examples/publisher.json",
"config/examples/release-actions.json",
"config/examples/release-envelope.json",
"config/examples/release-hooks.json",
"config/examples/release-projection.json",
"config/examples/renewal.json",
"config/examples/revoker.json",
"config/examples/runtime-projection.json",
"config/examples/watcher.json",
"config/examples/writer-server.json",
"deploy/systemd/aios-v2r1c4-release-envelope.service",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/credentials.py",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/gitea_api.py",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/grant.py",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/mirror_service.py",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/projection_service.py",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/publisher.py",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/release_action.py",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/release_deploy.py",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/release_hooks.py",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/release_service.py",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/release_transport.py",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/renewal_service.py",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/revoker.py",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/socket_service.py",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/watcher_service.py",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/writer_server.py",
"tests/autonomous_control_plane/test_deployment_and_contracts.py",
"tests/autonomous_control_plane/test_services_and_hooks.py"
],
"base_package_sha256": "795feb406541af86f30c1fb31f85e6d7485ee72a9d3ac93351f9a63fe4ad3e8f",
"deleted_files": [
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/__pycache__/__init__.cpython-313.pyc",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/__pycache__/cli.cpython-313.pyc",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/__pycache__/common.cpython-313.pyc",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/__pycache__/ledger.cpython-313.pyc",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/__pycache__/mirror.cpython-313.pyc",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/__pycache__/policy.cpython-313.pyc",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/__pycache__/projection.cpython-313.pyc",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/__pycache__/release.cpython-313.pyc",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/__pycache__/renewal.cpython-313.pyc",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/__pycache__/watcher.cpython-313.pyc",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/__pycache__/writer.cpython-313.pyc",
"tests/.pytest_cache/.gitignore",
"tests/.pytest_cache/CACHEDIR.TAG",
"tests/.pytest_cache/README.md",
"tests/.pytest_cache/v/cache/lastfailed",
"tests/.pytest_cache/v/cache/nodeids",
"tests/__pycache__/conftest.cpython-313-pytest-9.0.2.pyc",
"tests/autonomous_control_plane/__pycache__/test_mirror_projection.cpython-313-pytest-9.0.2.pyc",
"tests/autonomous_control_plane/__pycache__/test_policy_and_writer.cpython-313-pytest-9.0.2.pyc",
"tests/autonomous_control_plane/__pycache__/test_renewal_release.cpython-313-pytest-9.0.2.pyc",
"tests/autonomous_control_plane/__pycache__/test_watcher.cpython-313-pytest-9.0.2.pyc"
],
"deletion_policy": "Only generated caches listed here may be removed. Do not delete repository source files.",
"integration_strategy": "Apply additions and replacements on patch/v2r1c4-release-completion-20260729; preserve existing approved Git objects and user code.",
"modified_files": [
"CODE_HANDOFF_MANIFEST.json",
"INTEGRATION_NOTES.md",
"TEST_RECEIPT.json",
"deploy/systemd/aios-autonomous-renewal-controller.service",
"deploy/systemd/aios-governance-approval-watcher.service",
"deploy/systemd/aios-governance-mirror-worker.service",
"deploy/systemd/aios-governance-request-publisher.service",
"deploy/systemd/aios-governed-development-writer-revoker.service",
"deploy/systemd/aios-governed-development-writer.service",
"deploy/systemd/aios-release-object-projection.service",
"deploy/systemd/aios-runtime-source-read-projection.service",
"deploy/tmpfiles.d/aios-autonomous-control-plane.conf",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/projection.py",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/release.py",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/writer.py",
"services/aios-autonomous-control-plane/pyproject.toml",
"tests/autonomous_control_plane/test_mirror_projection.py",
"tests/autonomous_control_plane/test_renewal_release.py"
],
"schema": "aios.code-handoff-delta/v1",
"target_package_name": "aios_v2r1c4_autonomy_patch_v2_20260729.zip"
}
@@ -0,0 +1,66 @@
{
"AGENT_INTEGRATION_PLAN.md": "0af0bb6c3ed22d30c48e1e9af74540a0f4571dabe5dcf97bc67077d5cca6ea1d",
"CODE_HANDOFF_MANIFEST.json": "b1e3fde19a864671b28945ff21fd6c4f1fe2fd4bb7d78048ba1430d3de286cf9",
"DELTA_FROM_V1.json": "296571cac8fe3117443ad04c045a02203f1bb9eef5284f1704dda9c04e75e9ae",
"INTEGRATION_NOTES.md": "e70df9ca86ed136d193b1773596f7187d6c1f8751f40c133d3e93f017233d05c",
"TEST_RECEIPT.json": "e1e3cd8934d8b79a88bf740575e37c1407ab2a6dbad099a2730476e3b073564a",
"config/examples/development-policy.json": "51ab926a733c9a5173df43a3bf23d25ee3bd2b4743f2005cc458dcef232f62e4",
"config/examples/grant.json": "2dbece7418377880c63108e07155a3e97859f8b24d087ad353749cddf2572ffd",
"config/examples/mirror.json": "41b3948ad34a0a58b5af389d93f9a51241544449b08b90daa93b74ab5d22d292",
"config/examples/publisher.json": "d5198ba71dc87e9863b1fa1fcce0fabcc5d347e508fa2736041772fa68c083ea",
"config/examples/release-actions.json": "08d08fe97f3fdd336cd8f0dbc1c358bb5c9a9b2c7b1f66d5ab0682ea6aa0572e",
"config/examples/release-envelope.json": "6b3f31e75a9ec0d820e304932487c8a7961488d65b98c59a45320c52b3ca7a0f",
"config/examples/release-hooks.json": "6bb755673fee080e968395478cbf3d4697b8afc47d1f791890b528693cfcdbf2",
"config/examples/release-projection.json": "fa898f3fdb877f1c449999a827a9afb8797e026fd574ca7375b386069cf60e3d",
"config/examples/renewal.json": "59c27809e4f066e7b12cd0b57fecf46dedf28640be1c92c6906c1b1c19e7b25a",
"config/examples/revoker.json": "e4eaf3255e444c50ed29e9ee78b5d3284d26d74b55e17d0060b7e285b88b5845",
"config/examples/runtime-projection.json": "4f11d1d15d8daf26b2c6dc7e01d5626315fa7f2ca3c32923f029728a67b86534",
"config/examples/watcher.json": "95d83d7eedc3cbaf43901e9ec313f3cf08d9f5832c02910837187a2e32b9a465",
"config/examples/writer-server.json": "f0b5648f3f8e4e37ad934aaff8c55480a25139b731f676e49adf9ae1a0ebe0e1",
"deploy/systemd/aios-autonomous-renewal-controller.service": "38cf3269cac8e73cc004de50e051f26ea2e1f1a1427682e129b47960210e3c31",
"deploy/systemd/aios-governance-approval-watcher.service": "e48174384113644771eaf893c68e49432fc7b5e8ab4b4fa301d191f079b4e30f",
"deploy/systemd/aios-governance-mirror-worker.service": "1ef26557563b93d411b7a116800ca960dcde69051fd222c64555bf274ec15ca8",
"deploy/systemd/aios-governance-request-publisher.service": "c4a69bea0fc23849aa3307d8939e08c3925c9df445d0e43ef7fb689e285e5f89",
"deploy/systemd/aios-governed-development-writer-revoker.service": "f9128b9a2a4f77bde98f7e8a735babc67e14186860e8cb34eb94f6126dd6f7b5",
"deploy/systemd/aios-governed-development-writer-revoker.timer": "0bf2bc416012b5009ccb9c7b8f76d546fa6485295a926829fd3b26e0c1f071ce",
"deploy/systemd/aios-governed-development-writer.service": "b5d052b9f3cf81bc5587dd0545ac25ae55d29e37c32c760fb9725dced351598e",
"deploy/systemd/aios-release-object-projection.service": "91acf73dcab189aebe7900237f25a3accaa804624c1b4f52a2b477128142ed46",
"deploy/systemd/aios-runtime-source-read-projection.service": "45cf8a5c17fcd6960970ed8f26b02a1d3fdc53874d39c14b4a1d1d89d543679e",
"deploy/systemd/aios-v2r1c4-release-envelope.service": "b9b4ffb3a41cf90256cb970fb300a8e633e91527f19c8a8b8ee1e3cabdda4492",
"deploy/tmpfiles.d/aios-autonomous-control-plane.conf": "d5f3aefe63515b5699fbb81421480015bfa3bc1794ec02b8214a3c21f9cea8f2",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/__init__.py": "52a7864b6a4ac94be8f67100c825813b19ccf889283bbb7dbecbd342b9ec29dc",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/cli.py": "cd42b70c35b2d810433db0df013a439dc7993c45c68ea930a9efd5b5faced84c",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/common.py": "395c669df606ed366774663d4ac229225469c77ab3ff72f6a9efcbcef8031f9e",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/credentials.py": "61322d9f494a63764f1148550853d7ca7bcd9be74451bd3c3cf3cbe6388d2136",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/gitea_api.py": "9b0a7d9db9a4497078d8f4b0bf7622b292b40282204a1e1855e11f1edc93b6de",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/grant.py": "5b1cb302a1365f4a6f03df73f8c5f8a1595304d65e9d7ac129178d51b38dde59",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/ledger.py": "a51eea543d4e44c2faab6b59c7850600908f36090f0a1ddf448c1ca7c21acdbd",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/mirror.py": "20093cf68c0b4db4940d1092b0f09d7638ed210e2739973b1a558a4016208e01",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/mirror_service.py": "a46549c4c8b29006ed2b1aec201e470e88e2f9b347ef2fa81839ac7d8093320b",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/policy.py": "5fc5b50e781cfa47e99164635155a9559d168f1f556d8ca2f1694b57b1b57195",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/projection.py": "6ee91b9b6482d4f10c98aa7e78bd23dc5f9f759c875db2e0a3207cfe27714c4c",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/projection_service.py": "2019ca8e51affcebe6c776c2c3b51a54e1d6134874e3a9b12fc774393023172a",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/publisher.py": "fd65490c1b384b2725315a066715c9822255bbef3c213ab343495c6c96a7e90f",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/release.py": "2579fc38834d77c6370dcdc4269134fe42cf85540efcebf9e4f769ab6cbde200",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/release_action.py": "26254215e032f361737584e2b8eb8367fe9f6930b946511e16f57d8e7f50cee9",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/release_deploy.py": "bfb46b6dfacd88c69cee14c9f482a99a95a7b12f1de4b8778fdcdf1b66cfdbd3",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/release_hooks.py": "10e485d39a4fcfba4323297101f4281e6a0a94a60f6ee64a8e22a5d171d79965",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/release_service.py": "8d67a4b1562879b500120f65fb8f030183b0fb70acee646656dd235f00d0b618",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/release_transport.py": "822257f2c48c1fe861d8766aad2d61302e8b4e223a15b26f3c75c993c30140e2",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/renewal.py": "2272c428999a769218142b27b0a80e938c96b7cf1e553fdfd94873123dd9ad86",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/renewal_service.py": "c7fc5e307bd6eaef35844868cecc530b463088b4f320e531800d8683f3a3733a",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/revoker.py": "6953ec6442635a495ec602fbf6b2815aee40e43ad3790bf972a887bbb2de537d",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/socket_service.py": "a42c80002e8fcea7000ee0f4fd2a437b44b18773edbdbb9dfc088d385d2782e7",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/watcher.py": "eed557d5f6acfe44881bee44dd92fea6e04ac19f33e2dea874c2a4662b04c824",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/watcher_service.py": "db502f08bafad752935180477894bb78d5b36f6261f8af13d8db2d9ecce184bb",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/writer.py": "59b8f8d5c96a455e280de5a12280aca05070f86deb443e8f6fd842d7872dc60f",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/writer_server.py": "1680ca5a053da5b9a7430a616904f474cf27833496f50ead2803227598fd901f",
"services/aios-autonomous-control-plane/pyproject.toml": "4dae4bf9a88d033511a8de6d2871583d36cff868c3e0c6287c78d584933816df",
"tests/autonomous_control_plane/test_deployment_and_contracts.py": "2606242519f4cdf5783dd36708357c6ec9520d66f8f8ec18ef28529e3bd240d4",
"tests/autonomous_control_plane/test_mirror_projection.py": "5dc56cc0b5910d4b83afa4354f0375a2668ed5a328823357dc3ef2819235f26e",
"tests/autonomous_control_plane/test_policy_and_writer.py": "723a6fafcc542de7da028d2a2f76a6eb2b7975d4e6e9d1a23c17973019f74b7c",
"tests/autonomous_control_plane/test_renewal_release.py": "3199b9e87995d43ee97e8e62ebe85ef4f242afb16912bd1551d7570cb7d7474c",
"tests/autonomous_control_plane/test_services_and_hooks.py": "e58bde487ed38dfbb123a37ae43733c28a519d294a7729d01823adf7d2e48dd5",
"tests/autonomous_control_plane/test_watcher.py": "025ab4432522478ab7094751edf8da702b0e7be25d93876115cf9f416b7188b1",
"tests/conftest.py": "32a3df0734d253b070594fce0327b929c8ee0bbb136ea6fa83a2abecb826415b"
}
@@ -0,0 +1,41 @@
# AIOS V2R1C4 Autonomy Patch v2
This package completes the capabilities that were missing from the first handoff.
## Newly delivered
- Idempotent, credential-isolated governance request publisher.
- Authenticated Unix-socket development writer server with SO_PEERCRED checks.
- Root-owned task/branch/repository/path-bound Writer Grant and idempotent quota ledger.
- Writer credential revocation, old-token HTTP 401 proof, encrypted credential removal,
and read-only revoked Grant evidence.
- Persistent watcher, mirror, projection, and renewal service entrypoints.
- Complete release hook implementation:
- Owner exact-head and machine-status validation.
- Approved governance Manifest verification.
- Governed merge and tag transports.
- Remote/Gitea tag verification.
- Safe Artifact extraction and atomic release symlink deployment.
- systemd health checks.
- rollback and restore.
- Registry/Baseline snapshots, compensation, and final consistency.
- Unified V2R1C4 release executor with automatic failure rollback.
- 11 installed console entrypoints and 10 systemd/timer contracts.
## Offline verification
- compileall: PASS
- pytest: 21 passed
- Secret scan: REVIEW
- Path traversal archive test: PASS
- Metadata compensation test: PASS
- Publisher idempotency test: PASS
- Grant replay/quota test: PASS
- Old credential 401 test: PASS
## Integration rule
Apply this package to `patch/v2r1c4-release-completion-20260729`.
Agent should only supply environment configuration, existing governed endpoint
addresses, encrypted credentials, exact Git object IDs, and online acceptance.
Do not redesign or replace the delivered control-plane logic.
@@ -0,0 +1,77 @@
{
"compatibility_adaptations": [
{
"binding": "resolved active Python executable; release hook rejects symlink executables",
"new_sha256": "bd5c90b12f5f128cb3151f83e27748e8c382d1f07672dc67e3a97f5cb7ef5b03",
"old_sha256": "e58bde487ed38dfbb123a37ae43733c28a519d294a7729d01823adf7d2e48dd5",
"path": "tests/autonomous_control_plane/test_services_and_hooks.py",
"product_runtime_modified": false,
"reason": "host Python is 3.12 and package declares requires-python >=3.11; remove fixture-only /usr/bin/python3.13 binding"
}
],
"compiled_cache_files_integrated": false,
"delta_added_count": 32,
"delta_deleted_cache_count": 21,
"delta_modified_count": 18,
"direct_main_push": false,
"force_push_used": false,
"gitea_api_bootstrap_token_status": 401,
"gitea_api_preapply_oid": "e5dc9185dd153efdfb007f9b674197f613c10796",
"gitea_api_reader_token_status": 401,
"global_gitops_writes_enabled": false,
"handoff_metadata_location": "evidence/releases/V2R1C4-code-handoff-v2",
"integrated_runtime_and_test_paths": [
"config/examples/grant.json",
"config/examples/mirror.json",
"config/examples/publisher.json",
"config/examples/release-actions.json",
"config/examples/release-envelope.json",
"config/examples/release-hooks.json",
"config/examples/release-projection.json",
"config/examples/renewal.json",
"config/examples/revoker.json",
"config/examples/runtime-projection.json",
"config/examples/watcher.json",
"config/examples/writer-server.json",
"deploy/systemd/aios-v2r1c4-release-envelope.service",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/credentials.py",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/gitea_api.py",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/grant.py",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/mirror_service.py",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/projection_service.py",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/publisher.py",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/release_action.py",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/release_deploy.py",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/release_hooks.py",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/release_service.py",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/release_transport.py",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/renewal_service.py",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/revoker.py",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/socket_service.py",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/watcher_service.py",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/writer_server.py",
"tests/autonomous_control_plane/test_deployment_and_contracts.py",
"tests/autonomous_control_plane/test_services_and_hooks.py",
"deploy/systemd/aios-autonomous-renewal-controller.service",
"deploy/systemd/aios-governance-approval-watcher.service",
"deploy/systemd/aios-governance-mirror-worker.service",
"deploy/systemd/aios-governance-request-publisher.service",
"deploy/systemd/aios-governed-development-writer-revoker.service",
"deploy/systemd/aios-governed-development-writer.service",
"deploy/systemd/aios-release-object-projection.service",
"deploy/systemd/aios-runtime-source-read-projection.service",
"deploy/tmpfiles.d/aios-autonomous-control-plane.conf",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/projection.py",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/release.py",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/writer.py",
"services/aios-autonomous-control-plane/pyproject.toml",
"tests/autonomous_control_plane/test_mirror_projection.py",
"tests/autonomous_control_plane/test_renewal_release.py"
],
"schema": "aios.code-handoff-integration/v2",
"source_file_hash_entries": 64,
"source_file_hashes_verified": true,
"source_zip_sha256": "7afbcfae0ff692d2b859cd1e7e96536b8e80188667554803da438dcddc292d69",
"tag_rewritten": false,
"v1_modified_base_files_verified": 15
}
@@ -0,0 +1,20 @@
{
"collection_errors": 0,
"compileall_returncode": 0,
"delta_added_files": 32,
"delta_deleted_files": 21,
"delta_modified_files": 18,
"entrypoint_count": 11,
"generated_at": "2026-07-29T08:11:08.888275Z",
"pytest_failed": 0,
"pytest_passed": 21,
"pytest_returncode": 0,
"secret_findings": [
{
"path": "tests/autonomous_control_plane/test_policy_and_writer.py",
"rule": "literal-credential-assignment"
}
],
"secret_scan": "PASS_TEST_FIXTURE_ONLY",
"systemd_contract_count": 10
}
@@ -0,0 +1,104 @@
{
"actions": {
"artifact_built": false,
"baseline_modified": false,
"control_plane_deployed": false,
"core_redesigned_by_agent": false,
"main_modified": false,
"online_write_acceptance_executed": false,
"owner_approval_pr_created": false,
"production_modified": false,
"registry_modified": false,
"tag_created": false
},
"blocking_layer": "CODE_HANDOFF_SECURITY_INVARIANTS",
"candidate_base_oid": "e5dc9185dd153efdfb007f9b674197f613c10796",
"confirmed_security_findings": [
{
"evidence": "release_action.py:53-98",
"id": "P0-OWNER-ENVELOPE-BINDING",
"summary": "approved manifest digest is checked but manifest fields are not parsed and made equal to the release envelope"
},
{
"evidence": "release.py:20-30,52-59",
"id": "P0-UNCONSUMED-RELEASE-DIGESTS",
"summary": "rollback, SBOM, test, evidence and tag/deployment/rollback plan digests are format checked but not verified against objects before side effects"
},
{
"evidence": "release_deploy.py:173-188; release.py:168-170,210-214",
"id": "P0-PARTIAL-DEPLOY-NO-COMPENSATION",
"summary": "deployment side effects occur while phase is TAG_VERIFIED, which is excluded from automatic rollback"
},
{
"evidence": "release_deploy.py:191-212",
"id": "P0-ROLLBACK-TARGET-UNBOUND",
"summary": "previous and restored directories are not constrained to release_root and are not bound to the approved rollback digest"
},
{
"evidence": "policy.py:72-76; grant.py:10-25,62-71; writer.py:94-107; writer_server.py:28-38,64-68",
"id": "P0-WRITER-GRANT-BOUNDARY",
"summary": "expected base ancestry, exact task worktree/common-dir/remote and task credential identity are not closed"
},
{
"evidence": "revoker.py:36-64; aios-governed-development-writer-revoker.timer:4-8",
"id": "P0-REVOKER-EXPIRY-RECOVERY",
"summary": "fixed five-minute timer ignores grant expiry and credential deletion precedes recoverable grant and receipt closure"
},
{
"evidence": "deploy/tmpfiles.d/aios-autonomous-control-plane.conf:1-19",
"id": "P1-SERVICE-DIRECTORY-PERMISSIONS",
"summary": "0750 root:gitops directories are not writable by non-root writer and publisher users"
},
{
"evidence": "release_transport.py:65-76; release_deploy.py:221-228",
"id": "P1-LOOPBACK-URL-PREFIX",
"summary": "string prefix checks accept non-loopback hosts and can forward credentials"
}
],
"deployment_decision": "BLOCKED_PRODUCTION_DEPLOYMENT",
"execution_status": "PARTIALLY_COMPLETED",
"governance": {
"AIOS_GITOPS_WRITES_ENABLED": false,
"direct_main_push": false,
"force_push_used": false,
"owner_approval_respected": true,
"secrets_committed": false,
"tag_rewritten": false
},
"integrity": {
"file_sha256": "64/64 PASS",
"hardcoded_credentials": 0,
"path_traversal_entries": 0,
"secret_scan": "PASS",
"v1_modified_base_files": "15/15 PASS",
"zip_sha256": "7afbcfae0ff692d2b859cd1e7e96536b8e80188667554803da438dcddc292d69"
},
"live_binding_inventory": {
"authoritative_bare": "/data/aios-gitops/repo.git",
"candidate_health_endpoint_127_0_0_1_18890": "PRESENT_HTTP_200_NOT_APPROVED_AS_RELEASE_BINDING",
"configured_health_endpoint_127_0_0_1_8080": "ABSENT",
"configured_tag_endpoint_127_0_0_1_8080": "ABSENT",
"gitea_bare": "/data/aios-gitops/runtime/gitea-bin/data/repositories/aios/aios.git",
"gitea_db": "/data/aios-gitops/runtime/gitea-bin/data/gitea.db",
"gitea_repo_id": 1,
"integration_merge_writer_socket": "ABSENT",
"owner_user_id": 2,
"production_baseline_path": "ABSENT",
"release_root_current_previous": "ABSENT",
"v2r1c4_artifact": "ABSENT",
"v2r1c4_registry": "ABSENT"
},
"observed_at": "2026-07-29T09:11:19.725943+00:00",
"schema": "aios.code-handoff-validation/v2",
"verification": {
"collection_errors": 0,
"compileall": "PASS",
"console_entrypoints": "11/11 load PASS",
"package_tests_after_fixture_adapter": "21 passed",
"package_tests_original_host": "20 passed, 1 failed due fixture-only /usr/bin/python3.13 binding",
"package_version": "0.2.0",
"repository_tests": "660 passed, 1 skipped, 180 subtests passed",
"runtime_dependencies": "0",
"systemd_units": "10/10 staged-root syntax PASS"
}
}
@@ -0,0 +1,23 @@
{
"components": [
"governed-development-writer",
"approval-mirror-worker",
"approval-watcher",
"release-object-projection",
"runtime-source-projection",
"automatic-renewal-controller",
"v2r1c4-release-envelope"
],
"direct_main_push": false,
"force_push": false,
"global_writes_must_remain_false": true,
"schema": "aios.code-handoff/v1",
"source": "codex-chartgpt-bridge",
"tag_rewrite": false,
"target_branch": "patch/v2r1c4-release-completion-20260729",
"wechat_backend": {
"code_generated": false,
"reason": "PR #20 source diff exceeded the available controlled diff interface; do not invent API contracts.",
"required_action": "Import the exact approved PR #20 objects, then apply the release-envelope tests and hardening checks."
}
}
@@ -0,0 +1,54 @@
{
"CODE_HANDOFF_MANIFEST.json": "5723d17bee218d5fac627b439b898fe57506f3a153feab4561013b1f37937c26",
"INTEGRATION_NOTES.md": "e058b51d6c0fabb25abcdff44058a56f5d6adcaf9733b29b9cc4fc10de1603b3",
"TEST_RECEIPT.json": "e8699510eca3b805dac879ba3d6955954ebe17de7cd2142e60c205902627b8cf",
"config/examples/development-policy.json": "51ab926a733c9a5173df43a3bf23d25ee3bd2b4743f2005cc458dcef232f62e4",
"deploy/systemd/aios-autonomous-renewal-controller.service": "1e8825341884c428535ed0e56259c5c5919387d7dba130d36812bbb3312d4e29",
"deploy/systemd/aios-governance-approval-watcher.service": "19d5a1e1b5e89405dc8fbe3332daaa4fd652cc903d12e1a81a3e5fb8cf5ad538",
"deploy/systemd/aios-governance-mirror-worker.service": "8d7ed60961ec19fe72ef183d28c5ce8185decff5000f5f884acfb83a4bf21bbd",
"deploy/systemd/aios-governance-request-publisher.service": "037c709d737677257ac3911285768db5549d557cada7f31f3913131873f93c70",
"deploy/systemd/aios-governed-development-writer-revoker.service": "ac566dd3a01d4b83e44a32fcd78d34355a2cf439c91d3e53f554e65c8e480246",
"deploy/systemd/aios-governed-development-writer-revoker.timer": "0bf2bc416012b5009ccb9c7b8f76d546fa6485295a926829fd3b26e0c1f071ce",
"deploy/systemd/aios-governed-development-writer.service": "2f3061c2fe114243b3bb66031bb50175d65ead7448d8c83082faa2135af7b6cc",
"deploy/systemd/aios-release-object-projection.service": "e5341f89ceb4c362b2ed87a6eba7c8bf150aec293788c1aa997d5dfc689dff44",
"deploy/systemd/aios-runtime-source-read-projection.service": "1a83e23203f7edee365d74d8a825f4a3e1dfea6177b84c3faa6c5dda6c1ee78b",
"deploy/tmpfiles.d/aios-autonomous-control-plane.conf": "9e36e522d87f85a13fd03c68f006e63068cf94bec06525eb420344dc734fa162",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/__init__.py": "52a7864b6a4ac94be8f67100c825813b19ccf889283bbb7dbecbd342b9ec29dc",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/__pycache__/__init__.cpython-313.pyc": "6704553f751bce9e6a544178580eedf7c3a11f58b6f9eab554e1dc53a027020e",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/__pycache__/cli.cpython-313.pyc": "9d827fb60110c05593916050fcd647708886c12c6e9e784533a56f3525dd8c9b",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/__pycache__/common.cpython-313.pyc": "9e6368b69c75abf46cf708267c7dde591b0a6cdd535f87b6022ea9393ac7c95b",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/__pycache__/ledger.cpython-313.pyc": "abf49a51060c8637eb3aae378a86ea3938177b5d442bd93f1f581434a2c45604",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/__pycache__/mirror.cpython-313.pyc": "0dbdfc445d19227346c75815597476e8dc5bbcba6e08a748624bc5813f49dcb9",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/__pycache__/policy.cpython-313.pyc": "75724693bc0810d8e58fb44389afba62cb6a828e8447e76b61751de83d7c5d60",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/__pycache__/projection.cpython-313.pyc": "a09ae9226e4398fe1b469772b06383832d2266134493fa79f19c62fcd3101da2",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/__pycache__/release.cpython-313.pyc": "dafaafbb1076b5e590b0e25eb103cceb5c6ae336fc2db31e014e8cc3fffc7432",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/__pycache__/renewal.cpython-313.pyc": "9438c81cda1987100cf5f6448186688a58fc01ac59333497afefaeb4c080026a",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/__pycache__/watcher.cpython-313.pyc": "4a729399caf5421c688fd7ab37c98fe796a9b9b5361929185fcb2a1ea3ed3456",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/__pycache__/writer.cpython-313.pyc": "75e593baf6864353004edb76abe72f1f566ca77a820c23dcee5703e092c5c621",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/cli.py": "cd42b70c35b2d810433db0df013a439dc7993c45c68ea930a9efd5b5faced84c",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/common.py": "395c669df606ed366774663d4ac229225469c77ab3ff72f6a9efcbcef8031f9e",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/ledger.py": "a51eea543d4e44c2faab6b59c7850600908f36090f0a1ddf448c1ca7c21acdbd",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/mirror.py": "20093cf68c0b4db4940d1092b0f09d7638ed210e2739973b1a558a4016208e01",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/policy.py": "5fc5b50e781cfa47e99164635155a9559d168f1f556d8ca2f1694b57b1b57195",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/projection.py": "3498affdf02fcb51d8a702ff57046ac6d528480ced808b70de1ad56250f61522",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/release.py": "21f4a93a7121c893aaa52bbb31909215a4f64db32673fb10abc61abfdd94897c",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/renewal.py": "2272c428999a769218142b27b0a80e938c96b7cf1e553fdfd94873123dd9ad86",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/watcher.py": "eed557d5f6acfe44881bee44dd92fea6e04ac19f33e2dea874c2a4662b04c824",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/writer.py": "79503f3819469051ffb5f222156891a22981fa62ed37b72ce42da6247ae5792d",
"services/aios-autonomous-control-plane/pyproject.toml": "52200725816a1cdbb5e22b6c477fd80539a11013bcf0d6545d99807b29a0c697",
"tests/.pytest_cache/.gitignore": "3ed731b65d06150c138e2dadb0be0697550888a6b47eb8c45ecc9adba8b8e9bd",
"tests/.pytest_cache/CACHEDIR.TAG": "37dc88ef9a0abeddbe81053a6dd8fdfb13afb613045ea1eb4a5c815a74a3bde4",
"tests/.pytest_cache/README.md": "73fd6fccdd802c419a6b2d983d6c3173b7da97558ac4b589edec2dfe443db9ad",
"tests/.pytest_cache/v/cache/lastfailed": "44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a",
"tests/.pytest_cache/v/cache/nodeids": "bcee695f0c2c1125930b78eae1badd0ffce72495c63bdbb0a44cd50e4d54be8d",
"tests/__pycache__/conftest.cpython-313-pytest-9.0.2.pyc": "10fb2a851650254dd15bbfb89011cd8373d6c7a052d3181d8e258110d2a3f961",
"tests/autonomous_control_plane/__pycache__/test_mirror_projection.cpython-313-pytest-9.0.2.pyc": "3cbb8295357bee399ab2d2c33b08d55cccb8e2cd5568accb9eedcb756b3be4d1",
"tests/autonomous_control_plane/__pycache__/test_policy_and_writer.cpython-313-pytest-9.0.2.pyc": "41573389ecbc16ae8c1b9990db12dd2f5994469584388ebc82cd0aeb345ca36f",
"tests/autonomous_control_plane/__pycache__/test_renewal_release.cpython-313-pytest-9.0.2.pyc": "e7b05c53ba735723d27cedb7275b24450310109b1e752178c54122a534b2775b",
"tests/autonomous_control_plane/__pycache__/test_watcher.cpython-313-pytest-9.0.2.pyc": "5e13e650b803412db0105d3e7f3e84cada3187a170173360c6da0304a193cb1e",
"tests/autonomous_control_plane/test_mirror_projection.py": "818410d5571d36cb23df0db0be444a9c08c705325ecfeeebe88fc0d82dbffcd3",
"tests/autonomous_control_plane/test_policy_and_writer.py": "723a6fafcc542de7da028d2a2f76a6eb2b7975d4e6e9d1a23c17973019f74b7c",
"tests/autonomous_control_plane/test_renewal_release.py": "29dd1031e59da0d3b5a96a4f613f1a110e0feb77f983ef2072b53795212e7ea1",
"tests/autonomous_control_plane/test_watcher.py": "025ab4432522478ab7094751edf8da702b0e7be25d93876115cf9f416b7188b1",
"tests/conftest.py": "32a3df0734d253b070594fce0327b929c8ee0bbb136ea6fa83a2abecb826415b"
}
@@ -0,0 +1,35 @@
# AIOS V2R1C4 Autonomy Patch
This package contains code developed for integration into
`patch/v2r1c4-release-completion-20260729`.
It deliberately does not invent WeChat Backend endpoints or schemas. The
business implementation from PR #20 must be imported from its real Git objects,
then tested against the release envelope. The control-plane code in this package
is complete and independently testable without production credentials.
## Components
- Governed Development Writer with branch/path/operation policy, idempotent
ledger, secret scan, expected-head CAS, push verification and reconciliation.
- Approval Mirror Worker using file quarantine, add-only approvals validation,
fast-forward-only authoritative update and connectivity fsck.
- Gitea SQLite Approval Watcher with exact-head Owner review validation.
- Release Object Projection and root-only Runtime Source Projection.
- Automatic Renewal Controller with immutable-field comparison and bounded
renewal windows.
- Unified V2R1C4 Release Envelope executor covering merge, tag, remote
verification, deploy, health, rollback, restore, Registry, Baseline and final
consistency.
- Hardened systemd units and tmpfiles definitions.
## Agent integration constraints
- Keep `AIOS_GITOPS_WRITES_ENABLED=false`.
- Apply on a non-main branch only.
- Do not copy sample credentials or secrets.
- Resolve actual Gitea repository and database paths dynamically.
- Replace server wrapper placeholders under `/usr/libexec` with thin adapters
calling the supplied Python package.
- Run the supplied tests plus existing repository tests before requesting Owner
approval.
@@ -0,0 +1,44 @@
{
"compiled_cache_files_integrated": false,
"credential_scan": "PASS_TEST_REJECTION_FIXTURE_ONLY",
"direct_main_push": false,
"file_sha256_entries": 52,
"file_sha256_verified": true,
"fixture_value_sha256": "653bb1245e828fcda4fa53fcd5a3def5bd7654e651f54b4132b73d74e64435c4",
"force_push_used": false,
"global_gitops_writes_enabled": false,
"path_traversal_entries": 0,
"schema": "aios.code-handoff-integration/v1",
"source_files_integrated": [
"config/examples/development-policy.json",
"deploy/tmpfiles.d/aios-autonomous-control-plane.conf",
"services/aios-autonomous-control-plane/pyproject.toml",
"tests/conftest.py",
"deploy/systemd/aios-autonomous-renewal-controller.service",
"deploy/systemd/aios-governance-approval-watcher.service",
"deploy/systemd/aios-governance-mirror-worker.service",
"deploy/systemd/aios-governance-request-publisher.service",
"deploy/systemd/aios-governed-development-writer-revoker.service",
"deploy/systemd/aios-governed-development-writer-revoker.timer",
"deploy/systemd/aios-governed-development-writer.service",
"deploy/systemd/aios-release-object-projection.service",
"deploy/systemd/aios-runtime-source-read-projection.service",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/__init__.py",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/cli.py",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/common.py",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/ledger.py",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/mirror.py",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/policy.py",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/projection.py",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/release.py",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/renewal.py",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/watcher.py",
"services/aios-autonomous-control-plane/aios_autonomous_control_plane/writer.py",
"tests/autonomous_control_plane/test_mirror_projection.py",
"tests/autonomous_control_plane/test_policy_and_writer.py",
"tests/autonomous_control_plane/test_renewal_release.py",
"tests/autonomous_control_plane/test_watcher.py"
],
"tag_rewritten": false,
"zip_sha256": "795feb406541af86f30c1fb31f85e6d7485ee72a9d3ac93351f9a63fe4ad3e8f"
}
@@ -0,0 +1,6 @@
{
"compileall_returncode": 0,
"pytest_returncode": 0,
"pytest_stderr": "Spreadsheet runtime warmup failed during python startup\nTraceback (most recent call last):\n File \"/tmp/tmp.yTcnQsZYiA/artifact_tool_v2-2.8.4/artifact_tool/patches/warm_spreadsheet_runtime_on_startup.py\", line 26, in warm_spreadsheet_runtime_on_startup\n File \"/tmp/tmp.yTcnQsZYiA/artifact_tool_v2-2.8.4/artifact_tool/spreadsheet_warmup.py\", line 785, in warm_spreadsheet_runtime\n File \"/tmp/tmp.yTcnQsZYiA/artifact_tool_v2-2.8.4/artifact_tool/spreadsheet_warmup.py\", line 720, in _warm_feature_flows\n File \"/tmp/tmp.yTcnQsZYiA/artifact_tool_v2-2.8.4/artifact_tool/spreadsheet_warmup.py\", line 704, in _warm_collaboration_flows\n File \"/tmp/tmp.yTcnQsZYiA/artifact_tool_v2-2.8.4/artifact_tool/generated/interface/models.py\", line 30820, in hydrate_crdt_from_proto\n File \"/tmp/tmp.yTcnQsZYiA/artifact_tool_v2-2.8.4/artifact_tool/rpc/remote.py\", line 749, in __call__\n File \"/tmp/tmp.yTcnQsZYiA/artifact_tool_v2-2.8.4/artifact_tool/rpc/client.py\", line 150, in call\nartifact_tool.rpc.client.RemoteError: hydrateCrdtFromProto requires an empty collaborative document.\n",
"pytest_stdout": "\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m [100%]\u001b[0m\n\u001b[32m\u001b[32m\u001b[1m10 passed\u001b[0m\u001b[32m in 0.45s\u001b[0m\u001b[0m\n"
}
@@ -0,0 +1,25 @@
{
"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",
"copied_live_db_acceptance": "PASS",
"implementation_commit_oid": "71def340e7cc34f4f19918dcf8d3c847abbe4fbf",
"implementation_tree_oid": "3cc61bfbf69cbe0a24d54a0e53a6c50bcc130066",
"independent_validator": "ALLOW_CANDIDATE",
"production_mutation": false,
"result": "PASS",
"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_source_sha256": {
"services/aios-single-owner-governance/aios_single_owner_governance/activation.py": "48846a58fa3afb2b7c8ffe6e88504a9333863932df867f33ba0321ad3bac6003",
"services/aios-single-owner-governance/aios_single_owner_governance/gitea_protected_branch.py": "00775efe3f3d631d88379eff4ed2dcfaf386a2fa43111abe961e75109e80d768",
"services/aios-single-owner-governance/aios_single_owner_governance/journal.py": "a8f121245e04487d99c99fb03ada83f0c907723f8eb18552872ff46dcf5a7873",
"services/aios-single-owner-governance/aios_single_owner_governance/state_machine.py": "0b08a2c378d143cb9031a25132afc7b7d825bdc03d11efe10610341d4b70167e",
"services/aios-single-owner-governance/aios_single_owner_governance/validator.py": "754639846cb15cb997d612a1fe0ceb0224ddef5bbc3ccac9802a3747f122dac1"
},
"subtests_passed": 17,
"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",
"tests_failed": 0,
"tests_passed": 90
}
+10 -1
View File
@@ -2,6 +2,15 @@
set -euo pipefail
ROOT="$(cd "$(dirname "$0")" && pwd)"
cd "$ROOT"
manifest_log="${TMPDIR:-/tmp}/aios_manifest_check.$$.log"
cleanup() {
rm -f -- "$manifest_log"
}
trap cleanup EXIT
bash scripts/package_contract_check_v1r1c0.sh
if [ -f FULL_PACKAGE_FILE_MANIFEST.sha256 ]; then sha256sum -c FULL_PACKAGE_FILE_MANIFEST.sha256 --ignore-missing >/tmp/aios_manifest_check.log || true; fi
if [ ! -f FULL_PACKAGE_FILE_MANIFEST.sha256 ]; then
echo "FULL_PACKAGE_FILE_MANIFEST.sha256 missing" >&2
exit 1
fi
sha256sum -c FULL_PACKAGE_FILE_MANIFEST.sha256 --ignore-missing >"$manifest_log"
echo "AIOS_V1R1C0_PACKAGE_VERIFY_OK"
+46 -53
View File
@@ -1,68 +1,61 @@
#!/usr/bin/env python3
"""Build a deterministic Candidate artifact without overwriting evidence."""
"""Build deterministic dormant-release candidate artifacts."""
from __future__ import annotations
import argparse
import hashlib
import json
import os
from pathlib import Path
from aios_autonomous_release_candidate.artifact import ArtifactBuilder
import tarfile
def exclusive_write(path: Path, content: bytes, mode: int) -> None:
if path.is_symlink():
raise RuntimeError("artifact output symlink rejected")
flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL
if hasattr(os, "O_NOFOLLOW"):
flags |= os.O_NOFOLLOW
descriptor = os.open(path, flags, mode)
try:
os.write(descriptor, content)
os.fsync(descriptor)
finally:
os.close(descriptor)
def sha256(value: bytes) -> str:
return hashlib.sha256(value).hexdigest()
def build_parser() -> argparse.ArgumentParser:
def build(source: Path, output: Path, source_oid: str, tree_oid: str) -> dict[str, str]:
if not source.exists() or not source.is_dir():
raise SystemExit("source directory missing")
files = []
for item in sorted(source.rglob("*")):
if item.is_file() and ".git" not in item.parts:
files.append(item)
artifact = output / "candidate.tar"
manifest = output / "manifest.json"
output.mkdir(parents=True, exist_ok=True)
with tarfile.open(artifact, "w", format=tarfile.PAX_FORMAT) as archive:
for item in files:
info = archive.gettarinfo(str(item), arcname=str(item.relative_to(source)))
info.mtime = 0
info.uid = 0
info.gid = 0
with item.open("rb") as handle:
archive.addfile(info, handle)
payload = {
"source_oid": source_oid,
"tree_oid": tree_oid,
"artifact_sha256": sha256(artifact.read_bytes()),
"files": {
str(item.relative_to(source)): sha256(item.read_bytes())
for item in files
},
}
manifest.write_text(json.dumps(payload, sort_keys=True, indent=2), encoding="utf-8")
return {
"artifact_sha256": payload["artifact_sha256"],
"manifest_sha256": sha256(manifest.read_bytes()),
}
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--source", required=True, type=Path)
parser.add_argument("--source-oid", required=True)
parser.add_argument("--tree-oid", required=True)
parser.add_argument("--output-directory", required=True, type=Path)
parser.add_argument("--name", default="aios-autonomous-release-candidate")
return parser
def main(argv: list[str] | None = None) -> int:
args = build_parser().parse_args(argv)
output = args.output_directory
output.mkdir(parents=True, exist_ok=True, mode=0o750)
if output.is_symlink():
raise RuntimeError("artifact output directory symlink rejected")
os.chmod(output, output.stat().st_mode & 0o750)
builder = ArtifactBuilder()
built = builder.build_from_directory(args.source_oid, args.tree_oid, args.source)
builder.verify(built)
artifact_path = output / f"{args.name}.tar"
manifest_path = output / f"{args.name}.manifest.json"
exclusive_write(artifact_path, built.content, 0o640)
exclusive_write(manifest_path, built.manifest.as_json_bytes(), 0o640)
print(
json.dumps(
{
"artifact": str(artifact_path),
"manifest": str(manifest_path),
"artifact_sha256": built.manifest.artifact_sha256,
"manifest_sha256": built.manifest.manifest_sha256,
"package_closure_sha256": built.manifest.package_closure_sha256,
"secrets_included": False,
"production_side_effects": False,
},
sort_keys=True,
)
)
parser.add_argument("source", type=Path)
parser.add_argument("output", type=Path)
parser.add_argument("source_oid")
parser.add_argument("tree_oid")
args = parser.parse_args()
print(json.dumps(build(args.source, args.output, args.source_oid, args.tree_oid)))
return 0
+103 -475
View File
@@ -1,513 +1,141 @@
#!/usr/bin/env python3
"""Run Candidate acceptance and emit a non-overwriting JSON evidence record."""
"""Evaluate AIOS V2R1C4 dormant release evidence.
This command performs no Git, network, deployment, tag, Registry or Baseline
write. It evaluates explicit evidence supplied by a governed execution agent
and writes a deterministic JSON decision report.
"""
from __future__ import annotations
import argparse
from datetime import datetime, timedelta, timezone
import hashlib
from dataclasses import dataclass
from datetime import datetime, timezone
import json
import os
from pathlib import Path
import re
import subprocess
import sys
import tempfile
from typing import Sequence
from typing import Any, Mapping
sys.dont_write_bytecode = True
REPOSITORY_ROOT = Path(__file__).resolve().parents[2]
PACKAGE_ROOT = REPOSITORY_ROOT / "services" / "aios-autonomous-release-candidate"
if str(PACKAGE_ROOT) not in sys.path:
sys.path.insert(0, str(PACKAGE_ROOT))
ALLOWED_PREFIXES = (
"services/aios-autonomous-release-candidate/",
"tests/autonomous_release/",
"tests/autonomous-release/",
"scripts/autonomous-release/",
"docs/autonomous-release/",
)
ALLOWED_EXACT = {
"deploy/systemd/aios-autonomous-release-candidate.service",
"deploy/systemd/aios-autonomous-release-worker-candidate.service",
}
SCAN_PREFIXES = (
"services/aios-autonomous-release-candidate",
"tests/autonomous_release",
"tests/autonomous-release",
"scripts/autonomous-release",
"docs/autonomous-release",
from aios_autonomous_release_candidate.acceptance import ( # noqa: E402
AcceptancePhase,
AcceptanceStatus,
DormantAcceptanceGate,
DormantAcceptanceInput,
FinalDormantAcceptanceGate,
FinalDormantAcceptanceInput,
)
from aios_autonomous_release_candidate.models import sha256_json # noqa: E402
def command(repo: Path, argv: Sequence[str], environment: dict[str, str]) -> dict[str, object]:
completed = subprocess.run(
argv,
cwd=repo,
env=environment,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
timeout=300,
check=False,
)
return {
"argv": list(argv),
"exit_code": completed.returncode,
"output": completed.stdout[-12000:],
"passed": completed.returncode == 0,
}
REPORT_SCHEMA = "aios.dormant-release.acceptance/v1"
def text_files(repo: Path):
for prefix in SCAN_PREFIXES:
root = repo / prefix
if not root.exists():
continue
for path in root.rglob("*"):
if path.is_file() and not path.is_symlink() and "__pycache__" not in path.parts:
try:
yield path, path.read_text(encoding="utf-8")
except UnicodeDecodeError:
continue
@dataclass(frozen=True, slots=True)
class RunnerDecision:
phase: str
status: str
report: Mapping[str, Any]
exit_code: int
def scans(repo: Path) -> dict[str, object]:
forbidden_source = (
"GITEA_" + "DATABASE",
"sqlite3.connect(" + "GITEA_" + "DATABASE)",
"SELECT " + "* FROM access_token",
"DELETE " + "FROM access_token",
"gitea admin " + "user generate-access-token",
"ssh-key" + "scan",
)
secret_patterns = (
re.compile(r"sk-[A-Za-z0-9_-]{20,}"),
re.compile(r"-----BEGIN (?:OPENSSH |RSA |EC )?PRIVATE KEY-----"),
re.compile(r"(?i)(?:api[_-]?key|password|token)\s*[:=]\s*['\"]?[A-Za-z0-9_./+=:-]{20,}"),
)
forbidden_hits: list[str] = []
secret_hits: list[str] = []
for path, content in text_files(repo):
relative = path.relative_to(repo).as_posix()
if any(marker in content for marker in forbidden_source):
forbidden_hits.append(relative)
if any(pattern.search(content) for pattern in secret_patterns):
secret_hits.append(relative)
changed = subprocess.run(
["git", "diff", "--name-only", "main...HEAD"],
cwd=repo,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
check=False,
)
changed_paths = tuple(item for item in changed.stdout.splitlines() if item)
production_paths = [
path
for path in changed_paths
if path not in ALLOWED_EXACT and not any(path.startswith(prefix) for prefix in ALLOWED_PREFIXES)
]
return {
"direct_gitea_database_access": False if not forbidden_hits else True,
"forbidden_source_hits": sorted(set(forbidden_hits)),
"secret_hits": sorted(set(secret_hits)),
"production_path_hits": production_paths,
"passed": not forbidden_hits and not secret_hits and not production_paths,
}
def exclusive_json(path: Path, payload: dict[str, object]) -> None:
path.parent.mkdir(parents=True, exist_ok=True, mode=0o750)
flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL
if hasattr(os, "O_NOFOLLOW"):
flags |= os.O_NOFOLLOW
descriptor = os.open(path, flags, 0o640)
def read_json_object(path: Path) -> Mapping[str, Any]:
try:
content = (json.dumps(payload, sort_keys=True, indent=2) + "\n").encode("utf-8")
os.write(descriptor, content)
os.fsync(descriptor)
finally:
os.close(descriptor)
value = json.loads(path.read_text(encoding="utf-8"))
except FileNotFoundError as exc:
raise ValueError(f"input file does not exist: {path}") from exc
except json.JSONDecodeError as exc:
raise ValueError(f"input is not valid JSON: {exc.msg}") from exc
if not isinstance(value, dict):
raise ValueError("input must contain one JSON object")
return value
def git_object_oid(kind: str, content: bytes) -> str:
header = f"{kind} {len(content)}\0".encode("ascii")
return hashlib.sha1(header + content, usedforsecurity=False).hexdigest()
def run_shadow_e2e(repo: Path, package: Path, checks: list[dict[str, object]]) -> dict[str, object]:
"""Drive every Candidate contract without claiming a provider-native session."""
if subprocess.check_output(["git", "status", "--porcelain"], cwd=repo, text=True).strip():
raise RuntimeError("shadow acceptance requires a clean worktree")
sys.path.insert(0, str(package))
from aios_autonomous_release_candidate.adapters import ShadowAdapter
from aios_autonomous_release_candidate.artifact import ArtifactBuilder
from aios_autonomous_release_candidate.credential import (
CredentialBroker,
InMemoryCredentialProvider,
def atomic_write_json(path: Path, payload: Mapping[str, Any]) -> None:
path.parent.mkdir(parents=True, exist_ok=True)
temporary = path.with_suffix(path.suffix + ".tmp")
temporary.write_text(
json.dumps(payload, sort_keys=True, indent=2) + "\n",
encoding="utf-8",
)
from aios_autonomous_release_candidate.errors import ReleaseBlocked
from aios_autonomous_release_candidate.evidence import EvidenceChain
from aios_autonomous_release_candidate.models import (
CredentialRequest,
ReleasePlan,
ResourceBinding,
RiskClass,
TaskPlan,
WorkerDescriptor,
WorkflowStep,
sha256_json,
temporary.chmod(0o600)
temporary.replace(path)
def evaluate(phase: AcceptancePhase, evidence: Mapping[str, Any]) -> RunnerDecision:
if phase is AcceptancePhase.CANDIDATE:
parsed = DormantAcceptanceInput.from_mapping(evidence)
result = DormantAcceptanceGate().evaluate(parsed)
else:
parsed = FinalDormantAcceptanceInput.from_mapping(evidence)
result = FinalDormantAcceptanceGate().evaluate(parsed)
result_payload = dict(result.payload())
report = {
"schema": REPORT_SCHEMA,
"phase": result.phase.value,
"status": result.status.value,
"decision": result_payload,
"input_sha256": sha256_json(evidence),
"production_action_authorized": False,
}
return RunnerDecision(
phase=result.phase.value,
status=result.status.value,
report=report,
exit_code=0 if result.status is AcceptanceStatus.READY else 3,
)
from aios_autonomous_release_candidate.release import ReleaseController
from aios_autonomous_release_candidate.router import RouteRequest, WorkerRouter
from aios_autonomous_release_candidate.store import StateStore
from aios_autonomous_release_candidate.workflow import WorkflowEngine
head = subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=repo, text=True).strip()
tree = subprocess.check_output(
["git", "rev-parse", "HEAD^{tree}"], cwd=repo, text=True
).strip()
task_id = "AIOS-AUTONOMOUS-RELEASE-FULL-IMPLEMENTATION-20260726-SHADOW"
roles = ("hermes", "writer", "codex", "openclaw")
steps = tuple(
WorkflowStep(
step_id=f"{role}-step",
worker_role=role,
required_capabilities=(f"{role}.shadow",),
depends_on=(() if index == 0 else (f"{roles[index - 1]}-step",)),
max_attempts=2,
timeout_seconds=60,
allowed_paths=("services/aios-autonomous-release-candidate",),
)
for index, role in enumerate(roles)
def invalid_report(phase: str, message: str) -> RunnerDecision:
report = {
"schema": REPORT_SCHEMA,
"phase": phase,
"status": AcceptanceStatus.BLOCKED.value,
"decision": {
"blocking_code": "INVALID_ACCEPTANCE_INPUT",
"blocking_message": message,
},
"production_action_authorized": False,
}
return RunnerDecision(
phase=phase,
status=AcceptanceStatus.BLOCKED.value,
report=report,
exit_code=2,
)
binding = ResourceBinding(
repository="aios/aios",
branch="patch/chatgpt-autonomous-completion-contract-20260726",
base_oid=head,
allowed_paths=("services/aios-autonomous-release-candidate",),
)
plan = TaskPlan(
task_id=task_id,
intent="candidate shadow contract closure",
binding=binding,
risk=RiskClass.HIGH,
steps=steps,
evidence_requirements=(
"task_plan",
"worker_route",
"step_result",
"credential_revoked",
"session_closed",
"artifact",
"test_report",
"git_ref",
),
production_side_effects_allowed=False,
)
workers = [
WorkerDescriptor(
worker_id=f"shadow-{role}-worker",
role=role,
capabilities=frozenset({f"{role}.shadow"}),
trust_zone="candidate-shadow",
endpoint_id=f"loopback:{role}",
)
for role in roles
]
router = WorkerRouter(workers)
provider = InMemoryCredentialProvider()
now = datetime.now(timezone.utc)
worker_routes: dict[str, str] = {}
session_ids: dict[str, dict[str, str]] = {}
credential_ids: list[str] = []
credential_revocation: dict[str, dict[str, object]] = {}
with tempfile.TemporaryDirectory(prefix="aios-autonomous-shadow-") as temporary:
store = StateStore(Path(temporary) / "state.sqlite3")
workflow = WorkflowEngine(store)
workflow.plan(plan)
evidence = EvidenceChain(store, task_id)
evidence.append(
"task_plan",
task_id,
{"task_plan_sha256": plan.digest(), "secrets_included": False},
)
broker = CredentialBroker(store, provider)
adapters = {role: ShadowAdapter(role) for role in roles}
while True:
decision = workflow.next_steps(plan)
states = store.list_steps(task_id)
if states and all(item.state.value == "COMPLETED" for item in states.values()):
break
if not decision.ready_steps:
raise RuntimeError("shadow workflow stalled")
for step_id in decision.ready_steps:
step = next(item for item in steps if item.step_id == step_id)
worker = router.route(
RouteRequest(task_id, step.required_capabilities, "candidate-shadow")
)
adapter = adapters[step.worker_role]
session = adapter.create_session({"task_id": task_id, "step_id": step_id})
worker_routes[step_id] = worker.worker_id
session_ids[step_id] = {
"broker_session_id": session.session_id,
"provider_session_id": session.provider_session_id,
"provider": session.provider,
}
evidence.append(
"worker_route",
task_id,
{
"step_id": step_id,
"worker_id": worker.worker_id,
"trust_zone": worker.trust_zone,
"secrets_included": False,
},
)
request = CredentialRequest(
request_id=f"request-{step_id}",
task_id=task_id,
session_id=session.session_id,
worker_id=worker.worker_id,
audience=f"{step.worker_role}-candidate-shadow",
consumer_service="aios-autonomous-release-worker-candidate.service",
binding=binding,
operations=(f"{step.worker_role}.shadow",),
provider=f"{step.worker_role}-candidate-mock",
runtime_user="aiman",
approval_plan_id="candidate-shadow-verifier-20260726",
not_before=now - timedelta(seconds=1),
expires_at=now + timedelta(seconds=299),
nonce=f"nonce-{step_id}",
ttl_seconds=300,
max_uses=1,
)
projection = broker.issue(request, now)
broker.project(projection)
broker.authorize(
projection,
task_id=task_id,
session_id=session.session_id,
worker_id=worker.worker_id,
audience=request.audience,
consumer_service=request.consumer_service,
operation=request.operations[0],
path="services/aios-autonomous-release-candidate",
now=now,
)
workflow.claim(task_id, step_id, worker.worker_id, session.session_id)
workflow.start(task_id, step_id, worker.worker_id, session.session_id, now=now)
workflow.heartbeat(task_id, step_id, worker.worker_id, session.session_id, now=now)
result = adapter.send(session, {"task_id": task_id, "step_id": step_id})
result_digest = sha256_json(result)
evidence.append(
"step_result",
task_id,
{
"step_id": step_id,
"worker_id": worker.worker_id,
"result_sha256": result_digest,
"production_side_effects": False,
"secrets_included": False,
},
)
workflow.complete(task_id, step_id, result_digest=result_digest)
revoked = broker.revoke(projection.credential_id, projection.reference, now)
broker.verify_revoked(projection.credential_id, projection.reference)
credential_ids.append(projection.credential_id)
credential_revocation[projection.credential_id] = {
"state": revoked.state.value,
"post_revocation_auth": "REJECTED",
}
evidence.append(
"credential_revoked",
task_id,
{
"credential_id": projection.credential_id,
"state": revoked.state.value,
"post_revocation_auth": "REJECTED",
"secrets_included": False,
},
)
adapter.close(session)
evidence.append(
"session_closed",
task_id,
{
"session_id": session.session_id,
"provider_session_id": session.provider_session_id,
"secrets_included": False,
},
)
built = ArtifactBuilder().build_from_directory(head, tree, package)
ArtifactBuilder().verify(built)
evidence.append(
"artifact",
task_id,
{
"artifact_sha256": built.manifest.artifact_sha256,
"manifest_sha256": built.manifest.manifest_sha256,
"package_closure_sha256": built.manifest.package_closure_sha256,
"secrets_included": False,
},
)
evidence.append(
"test_report",
task_id,
{
"checks_passed": all(bool(item["passed"]) for item in checks),
"secrets_included": False,
},
)
evidence.append(
"git_ref",
task_id,
{"target_oid": head, "tree_oid": tree, "secrets_included": False},
)
evidence_closure = evidence.close(plan.evidence_requirements)
evidence_bytes = json.dumps(
evidence.export(), sort_keys=True, separators=(",", ":")
).encode("utf-8")
evidence_closure_oid = git_object_oid("blob", evidence_bytes)
tag_body = (
f"object {head}\ntype commit\ntag SHADOW-0.1.0\n"
"tagger AIOS Candidate <candidate@invalid> 0 +0000\n\n"
"Candidate shadow tag object; never publish.\n"
).encode("utf-8")
release_plan = ReleasePlan(
task_id=task_id,
repository="aios/aios",
branch="integration/chatgpt-autonomous-completion-20260726",
version="SHADOW-0.1.0",
target_oid=head,
tree_oid=tree,
artifact_sha256=built.manifest.artifact_sha256,
manifest_sha256=built.manifest.manifest_sha256,
package_closure_sha256=built.manifest.package_closure_sha256,
evidence_closure_oid=evidence_closure_oid,
approval_plan_id="external-owner-release-approval-required",
rollback_approval_plan_id="external-owner-rollback-approval-required",
rollback_artifact_sha256=hashlib.sha256(b"previous-production-artifact").hexdigest(),
expected_tag_oid=git_object_oid("tag", tag_body),
expected_target_oid=head,
production_config_sha256=hashlib.sha256(
b"candidate-shadow-production-disabled"
).hexdigest(),
)
release = ReleaseController()
release.attach_plan(release_plan)
release.mark_built()
release.verify(artifact_ok=True, evidence_ok=True)
release.request_owner_approval()
deployment_without_owner = "NOT_TESTED"
try:
release.start_deployment()
except ReleaseBlocked:
deployment_without_owner = "BLOCKED"
final_states = {
step_id: item.state.value for step_id, item in store.list_steps(task_id).items()
}
return {
"passed": all(state == "COMPLETED" for state in final_states.values())
and deployment_without_owner == "BLOCKED"
and evidence.verify(),
"task_id": task_id,
"step_states": final_states,
"worker_routes": worker_routes,
"session_ids": session_ids,
"credential_ids": credential_ids,
"credential_revocation": credential_revocation,
"evidence_closure": evidence_closure,
"evidence_closure_oid": evidence_closure_oid,
"artifact_sha256": built.manifest.artifact_sha256,
"manifest_sha256": built.manifest.manifest_sha256,
"package_closure_sha256": built.manifest.package_closure_sha256,
"release_state": release.status().state.value,
"deployment_without_owner_approval": deployment_without_owner,
"broker_session_verified": True,
"openclaw_native_child_session_verified": False,
"codex_child_session_verified": False,
"simulation": True,
"human_intervention_count": 0,
"production_side_effects": False,
"main_write": False,
"tag_write": False,
"baseline_write": False,
"registry_write": False,
"secrets_included": False,
}
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser()
parser.add_argument("--repo-root", type=Path)
parser = argparse.ArgumentParser(prog="run_acceptance.py")
parser.add_argument(
"--phase",
required=True,
choices=[phase.value for phase in AcceptancePhase],
)
parser.add_argument("--input", required=True, type=Path)
parser.add_argument("--output", required=True, type=Path)
return parser
def main(argv: list[str] | None = None) -> int:
args = build_parser().parse_args(argv)
repo = (args.repo_root or Path(__file__).resolve().parents[2]).resolve()
package = repo / "services/aios-autonomous-release-candidate"
environment = dict(os.environ)
environment["PYTHONPATH"] = str(package)
environment["PYTHONPYCACHEPREFIX"] = f"/tmp/aios-autonomous-pyc-{os.getpid()}"
checks = [
command(
repo,
[sys.executable, "-m", "pytest", "-p", "no:cacheprovider", "-q", "tests/autonomous_release"],
environment,
),
command(
repo,
[sys.executable, "-m", "pytest", "-p", "no:cacheprovider", "-q", "tests/autonomous-release"],
environment,
),
command(repo, [sys.executable, "-m", "compileall", "-q", str(package)], environment),
command(repo, ["git", "diff", "--check", "main...HEAD"], environment),
]
scan_result = scans(repo)
head = subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=repo, text=True).strip()
main_oid = subprocess.check_output(["git", "rev-parse", "main"], cwd=repo, text=True).strip()
try:
shadow = run_shadow_e2e(repo, package, checks)
except Exception as error:
shadow = {
"passed": False,
"error": type(error).__name__,
"production_side_effects": False,
"secrets_included": False,
}
all_passed = (
all(bool(item["passed"]) for item in checks)
and bool(scan_result["passed"])
and bool(shadow["passed"])
)
report: dict[str, object] = {
"schema_version": 1,
"execution_mode": "candidate-shadow-only",
"candidate_oid": head,
"main_oid": main_oid,
"checks": checks,
"scans": scan_result,
"shadow_e2e": shadow,
"skipped": 0,
"all_passed": all_passed,
"production_side_effects": False,
"main_write": False,
"tag_write": False,
"baseline_write": False,
"registry_write": False,
"secrets_included": False,
}
exclusive_json(args.output, report)
print(json.dumps({"report": str(args.output), "all_passed": all_passed}, sort_keys=True))
return 0 if all_passed else 1
phase = AcceptancePhase(args.phase)
evidence = read_json_object(args.input)
decision = evaluate(phase, evidence)
except (OSError, TypeError, ValueError) as exc:
decision = invalid_report(args.phase, str(exc))
envelope = dict(decision.report)
envelope["generated_at"] = datetime.now(timezone.utc).isoformat()
atomic_write_json(args.output, envelope)
print(json.dumps(envelope, sort_keys=True))
return decision.exit_code
if __name__ == "__main__":
+1 -1
View File
@@ -16,7 +16,7 @@ required=(
"config/runtime_selector.yaml"
)
for f in "${required[@]}"; do test -f "$ROOT/$f" || { echo "MISSING $f"; exit 1; }; done
grep -Eq "^V1R1C[0-9]+$" "$ROOT/VERSION"
grep -Eq "^V[0-9]+R[0-9]+C[0-9]+$" "$ROOT/VERSION"
python3 -m py_compile \
"$ROOT/active-runtime/app/aios_gitops_model_api.py" \
"$ROOT/active-runtime/app/aios_chatgpt_actions_api.py" \
@@ -0,0 +1,28 @@
"""AIOS autonomous control-plane primitives."""
from .mirror import ApprovalMirrorWorker, MirrorError
from .policy import DevelopmentPolicy, PolicyError
from .projection import ReleaseObjectProjector, RuntimeSourceProjector
from .release import ReleaseEnvelope, ReleaseEnvelopeExecutor, ReleaseError
from .renewal import RenewalController, RenewalError
from .watcher import ApprovalSnapshot, GiteaSQLiteApprovalWatcher
from .writer import CommitRequest, GovernedDevelopmentWriter, WriterError
__all__ = [
"ApprovalMirrorWorker",
"ApprovalSnapshot",
"CommitRequest",
"DevelopmentPolicy",
"GiteaSQLiteApprovalWatcher",
"GovernedDevelopmentWriter",
"MirrorError",
"PolicyError",
"ReleaseEnvelope",
"ReleaseEnvelopeExecutor",
"ReleaseError",
"ReleaseObjectProjector",
"RenewalController",
"RenewalError",
"RuntimeSourceProjector",
"WriterError",
]
@@ -0,0 +1,102 @@
from __future__ import annotations
import argparse
import json
import os
from pathlib import Path
from .common import HashChainAudit
from .ledger import OperationLedger
from .mirror import ApprovalMirrorWorker
from .policy import DevelopmentPolicy
from .projection import ReleaseObjectProjector, RuntimeSourceProjector
from .renewal import RenewalController
from .writer import CommitRequest, GovernedDevelopmentWriter
def load_json(path: Path) -> dict:
value = json.loads(path.read_text(encoding="utf-8"))
if not isinstance(value, dict):
raise SystemExit("JSON object required")
return value
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser()
commands = parser.add_subparsers(dest="command", required=True)
writer = commands.add_parser("writer-commit")
writer.add_argument("--policy", type=Path, required=True)
writer.add_argument("--request", type=Path, required=True)
writer.add_argument("--worktree", type=Path, required=True)
writer.add_argument("--remote", required=True)
writer.add_argument("--ledger", type=Path, required=True)
writer.add_argument("--audit", type=Path, required=True)
mirror = commands.add_parser("mirror")
mirror.add_argument("--source-git-dir", type=Path, required=True)
mirror.add_argument("--authoritative-git-dir", type=Path, required=True)
mirror.add_argument("--target-oid", required=True)
runtime = commands.add_parser("runtime-project")
runtime.add_argument("--path", action="append", type=Path, required=True)
runtime.add_argument("--output", type=Path, required=True)
runtime.add_argument("--task-id", required=True)
release = commands.add_parser("release-project")
release.add_argument("--source-git-dir", type=Path, required=True)
release.add_argument("--commit-oid", required=True)
release.add_argument("--tree-oid", required=True)
release.add_argument("--output", type=Path, required=True)
release.add_argument("--path", action="append", required=True)
renew = commands.add_parser("renew")
renew.add_argument("--standing-policy", type=Path, required=True)
renew.add_argument("--previous-manifest", type=Path, required=True)
renew.add_argument("--observation", type=Path, required=True)
return parser
def main(argv: list[str] | None = None) -> int:
args = build_parser().parse_args(argv)
os.environ.setdefault("AIOS_GITOPS_WRITES_ENABLED", "false")
if args.command == "writer-commit":
policy = DevelopmentPolicy.from_mapping(load_json(args.policy))
request_value = load_json(args.request)
content_path = Path(request_value.pop("content_path"))
request = CommitRequest(content=content_path.read_bytes(), **request_value)
result = GovernedDevelopmentWriter(
worktree=args.worktree,
remote=args.remote,
policy=policy,
ledger=OperationLedger(args.ledger),
audit=HashChainAudit(args.audit),
).commit_file(request)
elif args.command == "mirror":
result = ApprovalMirrorWorker(
args.source_git_dir, args.authoritative_git_dir
).mirror(expected_target_oid=args.target_oid).__dict__
elif args.command == "runtime-project":
result = RuntimeSourceProjector(args.path).collect(args.output, args.task_id)
elif args.command == "release-project":
result = ReleaseObjectProjector(args.source_git_dir).project(
commit_oid=args.commit_oid,
expected_tree_oid=args.tree_oid,
output_path=args.output,
allowed_paths=args.path,
).__dict__
result["output_path"] = str(result["output_path"])
else:
result = RenewalController().decide(
standing_policy=load_json(args.standing_policy),
previous_manifest=load_json(args.previous_manifest),
observation=load_json(args.observation),
).__dict__
print(json.dumps(result, sort_keys=True, default=str))
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,221 @@
from __future__ import annotations
from contextlib import contextmanager
from datetime import datetime, timezone
import fcntl
import hashlib
import json
import os
from pathlib import Path, PurePosixPath, PureWindowsPath
import re
import tempfile
from typing import Any, Iterator
OID_RE = re.compile(r"^[0-9a-f]{40}$")
SHA256_RE = re.compile(r"^[0-9a-f]{64}$")
SENSITIVE_FRAGMENTS = (
"authorization", "cookie", "credential", "password", "private_key",
"secret", "token",
)
def canonical_json(value: object) -> bytes:
return json.dumps(
value, sort_keys=True, separators=(",", ":"), ensure_ascii=False
).encode("utf-8")
def sha256_json(value: object) -> str:
return hashlib.sha256(canonical_json(value)).hexdigest()
def sha256_file(path: Path) -> str:
if not path.is_absolute() or not path.is_file() or path.is_symlink():
raise RuntimeError("FILE_IDENTITY_INVALID")
digest = hashlib.sha256()
with path.open("rb") as handle:
for block in iter(lambda: handle.read(1024 * 1024), b""):
digest.update(block)
return digest.hexdigest()
def git_blob_oid(raw: bytes) -> str:
return hashlib.sha1(b"blob " + str(len(raw)).encode() + b"\0" + raw).hexdigest()
def parse_utc(value: str) -> datetime:
try:
parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
except (TypeError, ValueError) as exc:
raise RuntimeError("TIMESTAMP_INVALID") from exc
if parsed.tzinfo is None:
raise RuntimeError("TIMESTAMP_INVALID")
return parsed.astimezone(timezone.utc)
def utc_now() -> datetime:
return datetime.now(timezone.utc)
def require_oid(value: object, field: str = "oid") -> str:
if not isinstance(value, str) or not OID_RE.fullmatch(value):
raise RuntimeError(f"{field.upper()}_INVALID")
return value
def require_sha256(value: object, field: str = "sha256") -> str:
if not isinstance(value, str) or not SHA256_RE.fullmatch(value):
raise RuntimeError(f"{field.upper()}_INVALID")
return value
def safe_relpath(value: object) -> str:
if not isinstance(value, str) or not value or value != value.strip():
raise RuntimeError("RELATIVE_PATH_INVALID")
if "\0" in value or "\\" in value:
raise RuntimeError("RELATIVE_PATH_INVALID")
posix = PurePosixPath(value)
windows = PureWindowsPath(value)
parts = value.split("/")
if (
posix.is_absolute()
or windows.is_absolute()
or windows.drive
or any(part in {"", ".", ".."} for part in parts)
):
raise RuntimeError("RELATIVE_PATH_INVALID")
return value
def redact(value: Any) -> Any:
if isinstance(value, dict):
return {
str(key): (
"[REDACTED]"
if any(fragment in str(key).lower() for fragment in SENSITIVE_FRAGMENTS)
else redact(item)
)
for key, item in value.items()
}
if isinstance(value, list):
return [redact(item) for item in value]
if isinstance(value, tuple):
return [redact(item) for item in value]
return value
def ensure_directory(path: Path, mode: int = 0o750) -> None:
if path.is_symlink():
raise RuntimeError("DIRECTORY_SYMLINK_DENIED")
path.mkdir(parents=True, exist_ok=True, mode=mode)
path.chmod(mode)
def atomic_write(path: Path, raw: bytes, mode: int = 0o640) -> None:
ensure_directory(path.parent)
with tempfile.NamedTemporaryFile(
dir=path.parent, prefix=f".{path.name}.", delete=False
) as handle:
temporary = Path(handle.name)
handle.write(raw)
handle.flush()
os.fsync(handle.fileno())
temporary.chmod(mode)
os.replace(temporary, path)
path.chmod(mode)
directory_fd = os.open(path.parent, os.O_RDONLY)
try:
os.fsync(directory_fd)
finally:
os.close(directory_fd)
def atomic_write_json(path: Path, value: object, mode: int = 0o640) -> None:
atomic_write(
path,
json.dumps(redact(value), sort_keys=True, indent=2).encode("utf-8") + b"\n",
mode,
)
@contextmanager
def exclusive_lock(path: Path) -> Iterator[None]:
ensure_directory(path.parent)
fd = os.open(path, os.O_RDWR | os.O_CREAT | getattr(os, "O_NOFOLLOW", 0), 0o640)
try:
os.fchmod(fd, 0o640)
fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
yield
finally:
fcntl.flock(fd, fcntl.LOCK_UN)
os.close(fd)
class HashChainAudit:
def __init__(self, path: Path):
self.path = path
def records(self) -> list[dict[str, Any]]:
if not self.path.exists():
return []
result = []
for line in self.path.read_text(encoding="utf-8").splitlines():
if line.strip():
item = json.loads(line)
if not isinstance(item, dict):
raise RuntimeError("AUDIT_RECORD_INVALID")
result.append(item)
return result
def verify(self) -> str:
previous = "0" * 64
for sequence, record in enumerate(self.records(), 1):
if record.get("sequence") != sequence:
raise RuntimeError("AUDIT_SEQUENCE_INVALID")
if record.get("previous_event_hash") != previous:
raise RuntimeError("AUDIT_PREVIOUS_HASH_INVALID")
payload = record.get("payload")
payload_hash = sha256_json(payload)
if record.get("payload_hash") != payload_hash:
raise RuntimeError("AUDIT_PAYLOAD_HASH_INVALID")
material = {
"sequence": sequence,
"previous_event_hash": previous,
"payload_hash": payload_hash,
"operation_id": record.get("operation_id"),
}
expected = sha256_json(material)
if record.get("event_hash") != expected:
raise RuntimeError("AUDIT_EVENT_HASH_INVALID")
previous = expected
return previous
def append(self, operation_id: str, payload: dict[str, Any]) -> dict[str, Any]:
ensure_directory(self.path.parent)
previous = self.verify()
sequence = len(self.records()) + 1
clean = redact(payload)
payload_hash = sha256_json(clean)
material = {
"sequence": sequence,
"previous_event_hash": previous,
"payload_hash": payload_hash,
"operation_id": operation_id,
}
record = {
**material,
"event_hash": sha256_json(material),
"payload": clean,
}
fd = os.open(
self.path,
os.O_WRONLY | os.O_APPEND | os.O_CREAT | getattr(os, "O_NOFOLLOW", 0),
0o640,
)
try:
os.write(fd, canonical_json(record) + b"\n")
os.fsync(fd)
finally:
os.close(fd)
self.path.chmod(0o640)
return record
@@ -0,0 +1,46 @@
from __future__ import annotations
import base64, json, os
from pathlib import Path
from typing import Any
class CredentialError(RuntimeError): pass
def credential_path(name: str) -> Path:
directory = os.environ.get("CREDENTIALS_DIRECTORY")
if not directory:
raise CredentialError("CREDENTIALS_DIRECTORY_MISSING")
if not name or "/" in name or name in {".",".."}:
raise CredentialError("CREDENTIAL_NAME_INVALID")
path = Path(directory) / name
if not path.is_file() or path.is_symlink():
raise CredentialError("CREDENTIAL_FILE_INVALID")
return path
def load_credential_json(name: str) -> dict[str, Any]:
try:
value = json.loads(credential_path(name).read_text(encoding="utf-8"))
except json.JSONDecodeError as exc:
raise CredentialError("CREDENTIAL_JSON_INVALID") from exc
if not isinstance(value, dict):
raise CredentialError("CREDENTIAL_JSON_INVALID")
return value
def gitea_credential(name: str) -> tuple[str, str, int | None]:
value = load_credential_json(name)
username, token, token_id = value.get("username"), value.get("token"), value.get("token_id")
if not isinstance(username, str) or not username:
raise CredentialError("CREDENTIAL_USERNAME_INVALID")
if not isinstance(token, str) or len(token) < 16:
raise CredentialError("CREDENTIAL_TOKEN_INVALID")
if token_id is not None and (not isinstance(token_id, int) or token_id < 1):
raise CredentialError("CREDENTIAL_TOKEN_ID_INVALID")
return username, token, token_id
def git_http_auth_env(username: str, token: str) -> dict[str, str]:
encoded = base64.b64encode(f"{username}:{token}".encode()).decode("ascii")
return {
"GIT_CONFIG_COUNT":"1",
"GIT_CONFIG_KEY_0":"http.extraHeader",
"GIT_CONFIG_VALUE_0":f"Authorization: Basic {encoded}",
"GIT_TERMINAL_PROMPT":"0",
}
@@ -0,0 +1,60 @@
from __future__ import annotations
import json, urllib.error, urllib.parse, urllib.request
from typing import Any
class GiteaApiError(RuntimeError):
def __init__(self, code: str, status: int | None=None):
super().__init__(code); self.code=code; self.status=status
class GiteaApi:
def __init__(self, base_url: str, token: str):
self.base_url=base_url.rstrip("/"); self.token=token
def request(self, method: str, path: str, body: dict[str,Any] | None=None,
expected: tuple[int,...]=(200,)) -> Any:
raw=None if body is None else json.dumps(body).encode()
req=urllib.request.Request(self.base_url+path,data=raw,method=method)
req.add_header("Authorization",f"token {self.token}")
req.add_header("Accept","application/json")
if raw is not None: req.add_header("Content-Type","application/json")
try:
with urllib.request.urlopen(req,timeout=20) as resp:
status,payload=resp.status,resp.read()
except urllib.error.HTTPError as exc:
raise GiteaApiError(f"GITEA_HTTP_{exc.code}",exc.code) from exc
except urllib.error.URLError as exc:
raise GiteaApiError("GITEA_UNAVAILABLE") from exc
if status not in expected:
raise GiteaApiError(f"GITEA_HTTP_{status}",status)
if not payload: return None
try: return json.loads(payload)
except json.JSONDecodeError as exc:
raise GiteaApiError("GITEA_RESPONSE_INVALID") from exc
def branch(self, owner: str, repo: str, branch: str) -> dict[str,Any]:
value=self.request("GET",f"/api/v1/repos/{owner}/{repo}/branches/{urllib.parse.quote(branch,safe='')}")
if not isinstance(value,dict): raise GiteaApiError("GITEA_BRANCH_RESPONSE_INVALID")
return value
def list_open_pulls(self, owner: str, repo: str) -> list[dict[str,Any]]:
value=self.request("GET",f"/api/v1/repos/{owner}/{repo}/pulls?state=open&limit=50")
if not isinstance(value,list): raise GiteaApiError("GITEA_PULL_LIST_INVALID")
return [v for v in value if isinstance(v,dict)]
def create_pull(self, owner: str, repo: str, *, head: str, base: str,
title: str, body: str) -> dict[str,Any]:
value=self.request("POST",f"/api/v1/repos/{owner}/{repo}/pulls",
{"head":head,"base":base,"title":title,"body":body},expected=(201,))
if not isinstance(value,dict): raise GiteaApiError("GITEA_PULL_CREATE_INVALID")
return value
def pull(self, owner: str, repo: str, index: int) -> dict[str,Any]:
value=self.request("GET",f"/api/v1/repos/{owner}/{repo}/pulls/{index}")
if not isinstance(value,dict): raise GiteaApiError("GITEA_PULL_RESPONSE_INVALID")
return value
def request_reviewers(self, owner: str, repo: str, index: int, reviewers: list[str]) -> None:
try:
self.request("POST",f"/api/v1/repos/{owner}/{repo}/pulls/{index}/requested_reviewers",
{"reviewers":reviewers},expected=(201,204))
except GiteaApiError as exc:
if exc.status != 422:
raise
pull=self.pull(owner,repo,index)
present={item.get("login") for item in pull.get("requested_reviewers",[]) if isinstance(item,dict)}
if not set(reviewers).issubset(present):
raise
@@ -0,0 +1,122 @@
from __future__ import annotations
from dataclasses import dataclass
from fnmatch import fnmatchcase
import json, sqlite3
from pathlib import Path
from .common import parse_utc, require_oid, safe_relpath, sha256_file, utc_now
class GrantError(RuntimeError): pass
@dataclass(frozen=True)
class WriterGrant:
grant_id: str
task_id: str
repository: str
branch: str
actor_identity: str
writer_audience: str
expected_policy_sha256: str
expected_base_oid: str
allowed_paths: tuple[str, ...]
allowed_operations: tuple[str, ...]
max_operations: int
max_total_bytes: int
expires_at: str
grant_sha256: str
@classmethod
def load(cls, path: Path) -> "WriterGrant":
if not path.is_absolute() or not path.is_file() or path.is_symlink():
raise GrantError("GRANT_FILE_INVALID")
stat = path.stat()
if stat.st_uid != 0 or stat.st_mode & 0o022:
raise GrantError("GRANT_FILE_PERMISSIONS_INVALID")
value = json.loads(path.read_text(encoding="utf-8"))
required = {
"grant_id","task_id","repository","branch","actor_identity",
"writer_audience","expected_policy_sha256","expected_base_oid",
"allowed_paths","allowed_operations","max_operations",
"max_total_bytes","expires_at",
}
if not isinstance(value, dict) or set(value) != required:
raise GrantError("GRANT_SCHEMA_INVALID")
if not all(isinstance(x,str) and x for x in value["allowed_paths"]):
raise GrantError("GRANT_PATHS_INVALID")
if not all(isinstance(x,str) and x for x in value["allowed_operations"]):
raise GrantError("GRANT_OPERATIONS_INVALID")
require_oid(value["expected_base_oid"], "expected_base_oid")
parse_utc(value["expires_at"])
return cls(
grant_id=value["grant_id"], task_id=value["task_id"],
repository=value["repository"], branch=value["branch"],
actor_identity=value["actor_identity"], writer_audience=value["writer_audience"],
expected_policy_sha256=value["expected_policy_sha256"],
expected_base_oid=value["expected_base_oid"],
allowed_paths=tuple(value["allowed_paths"]),
allowed_operations=tuple(value["allowed_operations"]),
max_operations=int(value["max_operations"]),
max_total_bytes=int(value["max_total_bytes"]),
expires_at=value["expires_at"], grant_sha256=sha256_file(path),
)
def validate_request(self, *, task_id: str, repository: str, branch: str,
actor_identity: str, writer_audience: str,
expected_base_oid: str, operation: str, path: str) -> None:
if utc_now() >= parse_utc(self.expires_at):
raise GrantError("GRANT_EXPIRED")
expected = (self.task_id,self.repository,self.branch,self.actor_identity,
self.writer_audience,self.expected_base_oid)
actual = (task_id,repository,branch,actor_identity,writer_audience,expected_base_oid)
if actual != expected:
raise GrantError("GRANT_BINDING_MISMATCH")
normalized = safe_relpath(path)
if operation not in self.allowed_operations:
raise GrantError("GRANT_OPERATION_DENIED")
if not any(fnmatchcase(normalized, pattern) for pattern in self.allowed_paths):
raise GrantError("GRANT_PATH_DENIED")
class GrantQuotaLedger:
def __init__(self, path: Path):
self.path = path
path.parent.mkdir(parents=True, exist_ok=True)
with sqlite3.connect(path) as db:
db.execute("""CREATE TABLE IF NOT EXISTS usage(
grant_id TEXT PRIMARY KEY,
operations INTEGER NOT NULL,
total_bytes INTEGER NOT NULL)""")
db.execute("""CREATE TABLE IF NOT EXISTS consumed_operations(
grant_id TEXT NOT NULL,
operation_id TEXT NOT NULL,
payload_sha256 TEXT NOT NULL,
byte_count INTEGER NOT NULL,
PRIMARY KEY(grant_id,operation_id))""")
def consume(self, grant: WriterGrant, operation_id: str,
payload_sha256: str, byte_count: int) -> bool:
if byte_count < 0 or not operation_id or len(payload_sha256) != 64:
raise GrantError("GRANT_CONSUMPTION_INPUT_INVALID")
with sqlite3.connect(self.path, isolation_level=None) as db:
db.execute("BEGIN IMMEDIATE")
prior = db.execute(
"SELECT payload_sha256,byte_count FROM consumed_operations "
"WHERE grant_id=? AND operation_id=?",
(grant.grant_id,operation_id)).fetchone()
if prior:
db.execute("COMMIT")
if prior != (payload_sha256,byte_count):
raise GrantError("GRANT_OPERATION_ID_PAYLOAD_MISMATCH")
return False
row = db.execute("SELECT operations,total_bytes FROM usage WHERE grant_id=?",
(grant.grant_id,)).fetchone()
operations,total_bytes = row if row else (0,0)
if operations + 1 > grant.max_operations:
db.execute("ROLLBACK"); raise GrantError("GRANT_OPERATION_QUOTA_EXHAUSTED")
if total_bytes + byte_count > grant.max_total_bytes:
db.execute("ROLLBACK"); raise GrantError("GRANT_BYTE_QUOTA_EXHAUSTED")
db.execute("""INSERT INTO usage VALUES(?,?,?)
ON CONFLICT(grant_id) DO UPDATE SET
operations=excluded.operations,total_bytes=excluded.total_bytes""",
(grant.grant_id,operations+1,total_bytes+byte_count))
db.execute("INSERT INTO consumed_operations VALUES(?,?,?,?)",
(grant.grant_id,operation_id,payload_sha256,byte_count))
db.execute("COMMIT")
return True
@@ -0,0 +1,116 @@
from __future__ import annotations
from contextlib import contextmanager
from pathlib import Path
import sqlite3
from typing import Any, Iterator
class LedgerError(RuntimeError):
pass
class OperationLedger:
def __init__(self, path: Path):
self.path = path
self.path.parent.mkdir(parents=True, exist_ok=True)
with self._db() as db:
db.execute("PRAGMA journal_mode=WAL")
db.execute("PRAGMA synchronous=FULL")
db.execute(
"""CREATE TABLE IF NOT EXISTS operations(
operation_id TEXT PRIMARY KEY,
payload_sha256 TEXT NOT NULL,
state TEXT NOT NULL,
previous_oid TEXT,
result_oid TEXT,
result_json TEXT,
updated_unix INTEGER NOT NULL
)"""
)
@contextmanager
def _db(self) -> Iterator[sqlite3.Connection]:
db = sqlite3.connect(self.path, timeout=30, isolation_level=None)
try:
yield db
finally:
db.close()
def get(self, operation_id: str) -> dict[str, Any] | None:
with self._db() as db:
row = db.execute(
"SELECT operation_id,payload_sha256,state,previous_oid,result_oid,"
"result_json,updated_unix FROM operations WHERE operation_id=?",
(operation_id,),
).fetchone()
if not row:
return None
return {
"operation_id": row[0],
"payload_sha256": row[1],
"state": row[2],
"previous_oid": row[3],
"result_oid": row[4],
"result_json": row[5],
"updated_unix": row[6],
}
def prepare(
self, operation_id: str, payload_sha256: str, previous_oid: str, now_unix: int
) -> dict[str, Any]:
with self._db() as db:
db.execute("BEGIN IMMEDIATE")
row = db.execute(
"SELECT payload_sha256,state,previous_oid,result_oid,result_json "
"FROM operations WHERE operation_id=?",
(operation_id,),
).fetchone()
if row:
db.execute("COMMIT")
if row[0] != payload_sha256:
raise LedgerError("OPERATION_ID_PAYLOAD_MISMATCH")
return {
"operation_id": operation_id,
"payload_sha256": row[0],
"state": row[1],
"previous_oid": row[2],
"result_oid": row[3],
"result_json": row[4],
}
db.execute(
"INSERT INTO operations VALUES(?,?,?,?,?,?,?)",
(
operation_id, payload_sha256, "PREPARED", previous_oid,
None, None, now_unix,
),
)
db.execute("COMMIT")
return {
"operation_id": operation_id,
"payload_sha256": payload_sha256,
"state": "PREPARED",
"previous_oid": previous_oid,
"result_oid": None,
"result_json": None,
}
def finish(
self, operation_id: str, state: str, result_oid: str | None,
result_json: str, now_unix: int
) -> None:
if state not in {
"COMPLETED", "FAILED_RESTORED", "RECONCILIATION_REQUIRED", "FAILED"
}:
raise LedgerError("LEDGER_STATE_INVALID")
with self._db() as db:
db.execute("BEGIN IMMEDIATE")
cursor = db.execute(
"UPDATE operations SET state=?,result_oid=?,result_json=?,updated_unix=? "
"WHERE operation_id=?",
(state, result_oid, result_json, now_unix, operation_id),
)
if cursor.rowcount != 1:
db.execute("ROLLBACK")
raise LedgerError("OPERATION_NOT_FOUND")
db.execute("COMMIT")
@@ -0,0 +1,109 @@
from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
import shutil
import subprocess
import tempfile
from typing import Iterable
from .common import require_oid, safe_relpath
class MirrorError(RuntimeError):
pass
@dataclass(frozen=True)
class MirrorResult:
previous_oid: str
target_oid: str
fsck: str
changed_paths: tuple[str, ...]
class ApprovalMirrorWorker:
def __init__(self, source_git_dir: Path, authoritative_git_dir: Path):
self.source_git_dir = source_git_dir
self.authoritative_git_dir = authoritative_git_dir
@staticmethod
def _git(git_dir: Path, *args: str, check: bool = True) -> subprocess.CompletedProcess[str]:
result = subprocess.run(
["git", f"--git-dir={git_dir}", *args],
text=True, capture_output=True
)
if check and result.returncode:
raise MirrorError("GIT_OPERATION_FAILED")
return result
def mirror(
self,
*,
source_ref: str = "refs/heads/governance/approvals",
destination_ref: str = "refs/heads/governance/approvals",
expected_target_oid: str,
allowed_prefixes: Iterable[str] = ("approvals/",),
) -> MirrorResult:
target = require_oid(expected_target_oid, "expected_target_oid")
if source_ref != "refs/heads/governance/approvals":
raise MirrorError("SOURCE_REF_DENIED")
if destination_ref != "refs/heads/governance/approvals":
raise MirrorError("DESTINATION_REF_DENIED")
prefixes = tuple(safe_relpath(prefix.rstrip("/")) + "/" for prefix in allowed_prefixes)
previous = require_oid(
self._git(
self.authoritative_git_dir, "rev-parse", "--verify", destination_ref
).stdout.strip(),
"previous_oid",
)
with tempfile.TemporaryDirectory(prefix="aios-governance-quarantine-") as tmp:
quarantine = Path(tmp) / "quarantine.git"
subprocess.run(["git", "init", "--bare", str(quarantine)], check=True, capture_output=True)
source_url = self.source_git_dir.resolve().as_uri()
fetch = self._git(
quarantine, "fetch", "--no-tags", "--no-write-fetch-head",
source_url, f"{source_ref}:refs/heads/incoming"
)
observed = require_oid(
self._git(quarantine, "rev-parse", "refs/heads/incoming").stdout.strip(),
"observed_target_oid",
)
if observed != target:
raise MirrorError("TARGET_OID_MISMATCH")
ancestor = self._git(
quarantine, "merge-base", "--is-ancestor", previous, target, check=False
)
if ancestor.returncode != 0:
raise MirrorError("NON_FAST_FORWARD_DENIED")
diff = self._git(
quarantine, "diff", "--name-status", previous, target
).stdout.splitlines()
changed = []
for line in diff:
fields = line.split("\t")
if len(fields) != 2 or fields[0] not in {"A", "M"}:
raise MirrorError("GOVERNANCE_DIFF_DENIED")
path = safe_relpath(fields[1])
if not any(path.startswith(prefix) for prefix in prefixes):
raise MirrorError("GOVERNANCE_PATH_DENIED")
changed.append(path)
fsck = self._git(quarantine, "fsck", "--connectivity-only").stdout.strip()
quarantine_url = quarantine.resolve().as_uri()
self._git(
self.authoritative_git_dir, "fetch", "--no-tags", "--no-write-fetch-head",
quarantine_url, target
)
self._git(
self.authoritative_git_dir, "update-ref",
destination_ref, target, previous
)
final = self._git(
self.authoritative_git_dir, "rev-parse", destination_ref
).stdout.strip()
if final != target:
raise MirrorError("MIRROR_VERIFY_FAILED")
self._git(self.authoritative_git_dir, "fsck", "--connectivity-only")
return MirrorResult(previous, target, fsck or "PASS", tuple(changed))
@@ -0,0 +1,22 @@
from __future__ import annotations
import argparse, json, time
from pathlib import Path
from .common import atomic_write_json
from .mirror import ApprovalMirrorWorker
def main(argv: list[str] | None=None) -> int:
p=argparse.ArgumentParser(); p.add_argument("--config",type=Path,required=True); p.add_argument("--once",action="store_true")
args=p.parse_args(argv); cfg=json.loads(args.config.read_text(encoding="utf-8"))
requests_dir,receipts_dir=Path(cfg["requests_dir"]),Path(cfg["receipts_dir"])
worker=ApprovalMirrorWorker(Path(cfg["source_git_dir"]),Path(cfg["authoritative_git_dir"]))
while True:
receipts_dir.mkdir(parents=True,exist_ok=True)
for request_path in sorted(requests_dir.glob("*.json")):
receipt=receipts_dir/f"{request_path.stem}.receipt.json"
if receipt.exists(): continue
req=json.loads(request_path.read_text(encoding="utf-8"))
result=worker.mirror(expected_target_oid=req["expected_target_oid"])
atomic_write_json(receipt,result.__dict__)
if args.once: break
time.sleep(float(cfg.get("poll_seconds",5)))
return 0
@@ -0,0 +1,96 @@
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime
from fnmatch import fnmatchcase
import os
from typing import Any, Mapping
from .common import parse_utc, require_oid, safe_relpath, sha256_json, utc_now
class PolicyError(RuntimeError):
pass
@dataclass(frozen=True)
class DevelopmentPolicy:
policy_id: str
repository: str
allowed_branch_patterns: tuple[str, ...]
denied_branch_patterns: tuple[str, ...]
allowed_path_patterns: tuple[str, ...]
allowed_operations: tuple[str, ...]
denied_operations: tuple[str, ...]
actor_identity: str
writer_audience: str
expires_at: datetime
policy_sha256: str
@classmethod
def from_mapping(cls, value: Mapping[str, Any]) -> "DevelopmentPolicy":
required = {
"policy_id", "repository", "allowed_branch_patterns",
"denied_branch_patterns", "allowed_path_patterns",
"allowed_operations", "denied_operations", "actor_identity",
"writer_audience", "expires_at",
}
if set(value) != required:
raise PolicyError("POLICY_SCHEMA_INVALID")
for key in (
"allowed_branch_patterns", "denied_branch_patterns",
"allowed_path_patterns", "allowed_operations", "denied_operations",
):
if not isinstance(value[key], list) or not all(
isinstance(item, str) and item for item in value[key]
):
raise PolicyError("POLICY_SCHEMA_INVALID")
expires = parse_utc(str(value["expires_at"]))
return cls(
policy_id=str(value["policy_id"]),
repository=str(value["repository"]),
allowed_branch_patterns=tuple(value["allowed_branch_patterns"]),
denied_branch_patterns=tuple(value["denied_branch_patterns"]),
allowed_path_patterns=tuple(value["allowed_path_patterns"]),
allowed_operations=tuple(value["allowed_operations"]),
denied_operations=tuple(value["denied_operations"]),
actor_identity=str(value["actor_identity"]),
writer_audience=str(value["writer_audience"]),
expires_at=expires,
policy_sha256=sha256_json(dict(value)),
)
def validate(
self,
*,
repository: str,
branch: str,
path: str,
operation: str,
actor_identity: str,
writer_audience: str,
expected_base_oid: str,
) -> None:
if os.environ.get("AIOS_GITOPS_WRITES_ENABLED", "false").lower() != "false":
raise PolicyError("GLOBAL_GITOPS_WRITES_MUST_REMAIN_FALSE")
require_oid(expected_base_oid, "expected_base_oid")
if utc_now() >= self.expires_at:
raise PolicyError("POLICY_EXPIRED")
if repository != self.repository:
raise PolicyError("REPOSITORY_DENIED")
if actor_identity != self.actor_identity:
raise PolicyError("ACTOR_IDENTITY_DENIED")
if writer_audience != self.writer_audience:
raise PolicyError("WRITER_AUDIENCE_DENIED")
if any(fnmatchcase(branch, pattern) for pattern in self.denied_branch_patterns):
raise PolicyError("BRANCH_DENIED")
if not any(fnmatchcase(branch, pattern) for pattern in self.allowed_branch_patterns):
raise PolicyError("BRANCH_DENIED")
normalized_path = safe_relpath(path)
if not any(
fnmatchcase(normalized_path, pattern)
for pattern in self.allowed_path_patterns
):
raise PolicyError("PATH_DENIED")
if operation in self.denied_operations or operation not in self.allowed_operations:
raise PolicyError("OPERATION_DENIED")
@@ -0,0 +1,163 @@
from __future__ import annotations
import ast
from dataclasses import dataclass
import hashlib
import json
from pathlib import Path
import re
import shutil
import subprocess
import tempfile
from typing import Iterable
from .common import atomic_write_json, git_blob_oid, require_oid, safe_relpath, sha256_file
SECRET_RULES = {
"private-key": re.compile(rb"-----BEGIN (?:OPENSSH |RSA |EC )?PRIVATE KEY-----"),
"credential-assignment": re.compile(
rb"(?i)\b(?:api[_-]?key|password|secret|token)\s*[:=]\s*['\"]?[A-Za-z0-9_./+=:-]{16,}"
),
}
class ProjectionError(RuntimeError):
pass
@dataclass(frozen=True)
class ProjectedObject:
commit_oid: str
tree_oid: str
output_path: Path
files: tuple[str, ...]
class ReleaseObjectProjector:
def __init__(self, source_git_dir: Path, allowed_output_roots: Iterable[Path] | None = None):
self.source_git_dir = source_git_dir
self.allowed_output_roots = tuple(
root.resolve() for root in (allowed_output_roots or ())
)
@staticmethod
def _git(git_dir: Path, *args: str, check: bool = True) -> subprocess.CompletedProcess[bytes]:
result = subprocess.run(
["git", f"--git-dir={git_dir}", *args],
capture_output=True
)
if check and result.returncode:
raise ProjectionError("GIT_OBJECT_PROJECTION_FAILED")
return result
def project(
self,
*,
commit_oid: str,
expected_tree_oid: str,
output_path: Path,
allowed_paths: Iterable[str],
) -> ProjectedObject:
commit = require_oid(commit_oid, "commit_oid")
tree = require_oid(expected_tree_oid, "expected_tree_oid")
allowed = tuple(safe_relpath(path) for path in allowed_paths)
if not allowed:
raise ProjectionError("ALLOWED_PATHS_REQUIRED")
if not output_path.is_absolute():
raise ProjectionError("OUTPUT_PATH_INVALID")
resolved_output = output_path.resolve()
if output_path.is_symlink():
raise ProjectionError("OUTPUT_PATH_SYMLINK_DENIED")
if self.allowed_output_roots:
if not any(
resolved_output != root
and (resolved_output == root or root in resolved_output.parents)
for root in self.allowed_output_roots
):
raise ProjectionError("OUTPUT_PATH_DENIED")
with tempfile.TemporaryDirectory(prefix="aios-release-projection-") as tmp:
quarantine = Path(tmp) / "objects.git"
subprocess.run(["git", "init", "--bare", str(quarantine)], check=True, capture_output=True)
self._git(
quarantine, "fetch", "--no-tags", "--no-write-fetch-head",
self.source_git_dir.resolve().as_uri(), commit
)
actual_tree = self._git(
quarantine, "rev-parse", f"{commit}^{{tree}}"
).stdout.decode().strip()
if actual_tree != tree:
raise ProjectionError("TREE_OID_MISMATCH")
names = self._git(
quarantine, "ls-tree", "-r", "--name-only", commit
).stdout.decode().splitlines()
selected = tuple(name for name in names if name in allowed)
if set(selected) != set(allowed):
raise ProjectionError("PROJECTED_PATH_MISSING")
if output_path.exists():
if output_path.is_symlink() or not output_path.is_dir():
raise ProjectionError("OUTPUT_PATH_REPLACEMENT_DENIED")
shutil.rmtree(output_path)
output_path.mkdir(parents=True, mode=0o750)
for path in selected:
raw = self._git(
quarantine, "show", f"{commit}:{path}"
).stdout
destination = output_path / path
destination.parent.mkdir(parents=True, exist_ok=True)
destination.write_bytes(raw)
destination.chmod(0o440)
output_path.chmod(0o550)
return ProjectedObject(commit, tree, output_path, selected)
class RuntimeSourceProjector:
def __init__(self, exact_paths: Iterable[Path]):
self.exact_paths = tuple(path.resolve() for path in exact_paths)
if not self.exact_paths:
raise ProjectionError("EXACT_PATHS_REQUIRED")
def collect(self, output_path: Path, task_id: str) -> dict:
records = []
for path in self.exact_paths:
if not path.is_absolute() or not path.is_file() or path.is_symlink():
raise ProjectionError("RUNTIME_PATH_INVALID")
raw = path.read_bytes()
findings = []
for rule_id, pattern in SECRET_RULES.items():
for match in pattern.finditer(raw):
line_number = raw[:match.start()].count(b"\n") + 1
findings.append({
"rule_id": rule_id,
"line_sha256": hashlib.sha256(
raw.splitlines()[line_number - 1]
).hexdigest(),
})
utf8_python = False
try:
text = raw.decode("utf-8")
ast.parse(text)
utf8_python = True
except (UnicodeDecodeError, SyntaxError):
utf8_python = False
records.append({
"resolved_path": str(path),
"size": len(raw),
"sha256": hashlib.sha256(raw).hexdigest(),
"git_blob_oid": git_blob_oid(raw),
"utf8_python": utf8_python,
"secret_scan": "PASS" if not findings else "HIT",
"finding_count": len(findings),
"findings": findings,
})
evidence = {
"schema": "aios.runtime-source-read-projection/v2",
"task_id": task_id,
"projection_mode": "ROOT_EXACT_PATH_READ_ONLY",
"network_used": False,
"credential_used": False,
"secret_content_returned": False,
"runtime_source_modified": False,
"sources": records,
}
atomic_write_json(output_path, evidence)
return evidence
@@ -0,0 +1,34 @@
from __future__ import annotations
import argparse, json, time
from pathlib import Path
from .common import atomic_write_json
from .projection import ReleaseObjectProjector, RuntimeSourceProjector
def release_main(argv: list[str] | None=None) -> int:
p=argparse.ArgumentParser(); p.add_argument("--config",type=Path,required=True); p.add_argument("--once",action="store_true")
args=p.parse_args(argv); cfg=json.loads(args.config.read_text(encoding="utf-8"))
requests_dir,receipts_dir=Path(cfg["requests_dir"]),Path(cfg["receipts_dir"])
projector=ReleaseObjectProjector(
Path(cfg["source_git_dir"]),
[Path(value) for value in cfg["allowed_output_roots"]],
)
while True:
receipts_dir.mkdir(parents=True,exist_ok=True)
for request_path in sorted(requests_dir.glob("*.json")):
receipt=receipts_dir/f"{request_path.stem}.receipt.json"
if receipt.exists(): continue
req=json.loads(request_path.read_text(encoding="utf-8"))
result=projector.project(commit_oid=req["commit_oid"],expected_tree_oid=req["tree_oid"],
output_path=Path(req["output_path"]),allowed_paths=req["allowed_paths"])
value=result.__dict__.copy(); value["output_path"]=str(value["output_path"])
atomic_write_json(receipt,value)
if args.once: break
time.sleep(float(cfg.get("poll_seconds",5)))
return 0
def runtime_main(argv: list[str] | None=None) -> int:
p=argparse.ArgumentParser(); p.add_argument("--config",type=Path,required=True)
args=p.parse_args(argv); cfg=json.loads(args.config.read_text(encoding="utf-8"))
evidence=RuntimeSourceProjector([Path(v) for v in cfg["exact_paths"]]).collect(
Path(cfg["output_path"]),cfg["task_id"])
return 0 if all(x["secret_scan"]=="PASS" for x in evidence["sources"]) else 2
@@ -0,0 +1,88 @@
from __future__ import annotations
import argparse, hashlib, json, os, subprocess, time
from pathlib import Path
from typing import Any
from .common import HashChainAudit, canonical_json, require_oid
from .credentials import gitea_credential, git_http_auth_env
from .gitea_api import GiteaApi
from .ledger import OperationLedger
from .socket_service import JsonLineUnixServer
class PublisherError(RuntimeError): pass
def under(path: Path, roots: list[Path]) -> bool:
resolved=path.resolve()
return any(resolved==r.resolve() or r.resolve() in resolved.parents for r in roots)
class PublisherApplication:
def __init__(self, config: dict[str,Any]):
self.config=config
self.username,self.token,_=gitea_credential(config["credential_name"])
self.api=GiteaApi(config["gitea_base_url"],self.token)
self.ledger=OperationLedger(Path(config["operation_ledger"]))
self.audit=HashChainAudit(Path(config["audit_path"]))
self.git_roots=[Path(v) for v in config["allowed_git_dir_roots"]]
def git(self, git_dir: Path, *args: str) -> subprocess.CompletedProcess[str]:
result=subprocess.run(["git",f"--git-dir={git_dir}",*args],text=True,capture_output=True,
env={**os.environ,**git_http_auth_env(self.username,self.token)})
if result.returncode: raise PublisherError("PUBLISH_GIT_FAILED")
return result
def dispatch(self,value: dict[str,Any],peer: dict[str,int]) -> dict[str,Any]:
required={"operation_id","repository","local_git_dir","source_oid","head_branch",
"base_branch","expected_base_oid","title","body"}
if set(value)!=required: raise PublisherError("REQUEST_SCHEMA_INVALID")
if value["repository"]!=self.config["repository"]: raise PublisherError("REPOSITORY_DENIED")
if value["base_branch"]!=self.config["base_branch"]: raise PublisherError("BASE_BRANCH_DENIED")
if not value["head_branch"].startswith(self.config["head_prefix"]):
raise PublisherError("HEAD_BRANCH_DENIED")
source_oid=require_oid(value["source_oid"],"source_oid")
expected_base_oid=require_oid(value["expected_base_oid"],"expected_base_oid")
git_dir=Path(value["local_git_dir"])
if not git_dir.is_absolute() or not git_dir.is_dir() or git_dir.is_symlink() or not under(git_dir,self.git_roots):
raise PublisherError("LOCAL_GIT_DIR_DENIED")
payload={k:value[k] for k in sorted(value) if k!="body"}
payload["body_sha256"]=hashlib.sha256(value["body"].encode()).hexdigest()
payload_sha=hashlib.sha256(canonical_json(payload)).hexdigest()
existing=self.ledger.get(value["operation_id"])
if existing:
if existing["payload_sha256"]!=payload_sha: raise PublisherError("OPERATION_ID_PAYLOAD_MISMATCH")
if existing["state"]=="COMPLETED": return json.loads(existing["result_json"])
if existing["state"] not in {"PREPARED","RECONCILIATION_REQUIRED"}:
raise PublisherError(existing["state"])
owner,repo=self.config["repository"].split("/",1)
base=self.api.branch(owner,repo,value["base_branch"])
if base.get("commit",{}).get("id")!=expected_base_oid: raise PublisherError("BASE_BRANCH_MOVED")
if existing is None:
self.ledger.prepare(value["operation_id"],payload_sha,expected_base_oid,int(time.time()))
self.git(git_dir,"push","--porcelain",self.config["remote_url"],
f"{source_oid}:refs/heads/{value['head_branch']}")
remote=self.api.branch(owner,repo,value["head_branch"])
if remote.get("commit",{}).get("id")!=source_oid: raise PublisherError("PUBLISHED_HEAD_MISMATCH")
pull=None
for item in self.api.list_open_pulls(owner,repo):
if item.get("head",{}).get("ref")==value["head_branch"] and item.get("base",{}).get("ref")==value["base_branch"]:
pull=item; break
if pull is None:
pull=self.api.create_pull(owner,repo,head=value["head_branch"],base=value["base_branch"],
title=value["title"],body=value["body"])
number=int(pull["number"])
self.api.request_reviewers(owner,repo,number,[self.config["owner_reviewer"]])
result={"operation_id":value["operation_id"],"head_branch":value["head_branch"],
"head_oid":source_oid,"base_branch":value["base_branch"],
"base_oid":expected_base_oid,"pr_number":number,"pr_url":pull.get("html_url"),
"owner_reviewer":self.config["owner_reviewer"],"peer_uid":peer["uid"]}
encoded=json.dumps(result,sort_keys=True)
self.ledger.finish(value["operation_id"],"COMPLETED",source_oid,encoded,int(time.time()))
self.audit.append(value["operation_id"],{"event":"GOVERNANCE_REQUEST_PUBLISHED",**result})
return result
def main(argv: list[str] | None=None) -> int:
p=argparse.ArgumentParser(); p.add_argument("--config",type=Path,required=True)
args=p.parse_args(argv); config=json.loads(args.config.read_text(encoding="utf-8"))
app=PublisherApplication(config)
server=JsonLineUnixServer(Path(config["socket_path"]),
allowed_uids={int(v) for v in config["allowed_peer_uids"]},
dispatch=app.dispatch,max_request_bytes=int(config.get("max_request_bytes",1048576)))
try: server.serve_forever()
finally: server.server_close()
return 0
@@ -0,0 +1,226 @@
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
from typing import Any, Callable, Mapping, Protocol
from .common import HashChainAudit, atomic_write_json, require_oid, require_sha256, sha256_json
class ReleaseError(RuntimeError):
pass
@dataclass(frozen=True)
class ReleaseEnvelope:
version: str
target_oid: str
target_tree_oid: str
artifact_sha256: str
rollback_artifact_sha256: str
sbom_sha256: str
test_evidence_sha256: str
evidence_closure_oid: str
evidence_closure_sha256: str
tag_plan_sha256: str
deployment_plan_sha256: str
rollback_plan_sha256: str
registry_plan_sha256: str
baseline_plan_sha256: str
owner_review_oid: int
approval_merge_oid: str
approval_pr_number: int
approval_head_oid: str
approval_manifest_sha256: str
approval_manifest_path: str
approval_expires_at: str
approval_plan_id: str
@classmethod
def from_mapping(cls, value: Mapping[str, Any]) -> "ReleaseEnvelope":
expected = set(cls.__dataclass_fields__)
if set(value) != expected:
raise ReleaseError("RELEASE_ENVELOPE_SCHEMA_INVALID")
if value["version"] != "V2R1C4":
raise ReleaseError("VERSION_DENIED")
for field in (
"target_oid", "target_tree_oid", "evidence_closure_oid",
"approval_merge_oid", "approval_head_oid",
):
require_oid(value[field], field)
for field in (
"artifact_sha256", "rollback_artifact_sha256", "sbom_sha256",
"test_evidence_sha256", "evidence_closure_sha256", "tag_plan_sha256",
"deployment_plan_sha256", "rollback_plan_sha256",
"registry_plan_sha256", "baseline_plan_sha256",
"approval_manifest_sha256",
):
require_sha256(value[field], field)
if not isinstance(value["owner_review_oid"], int) or value["owner_review_oid"] < 1:
raise ReleaseError("OWNER_REVIEW_OID_INVALID")
if not isinstance(value["approval_pr_number"], int) or value["approval_pr_number"] < 1:
raise ReleaseError("APPROVAL_PR_NUMBER_INVALID")
if not isinstance(value["approval_plan_id"], str) or not value["approval_plan_id"]:
raise ReleaseError("APPROVAL_PLAN_ID_INVALID")
from .common import parse_utc, safe_relpath
approval_path = safe_relpath(value["approval_manifest_path"])
if not approval_path.startswith("approvals/"):
raise ReleaseError("APPROVAL_MANIFEST_PATH_DENIED")
parse_utc(value["approval_expires_at"])
return cls(**value)
class ReleaseHooks(Protocol):
def verify_owner_approval(self, envelope: ReleaseEnvelope) -> None: ...
def merge_release(self, envelope: ReleaseEnvelope) -> str: ...
def create_tag(self, envelope: ReleaseEnvelope, release_oid: str) -> tuple[str, str]: ...
def verify_remote_tag(self, envelope: ReleaseEnvelope, tag_oid: str, release_oid: str) -> None: ...
def deploy(self, envelope: ReleaseEnvelope, release_oid: str) -> None: ...
def health_check(self, envelope: ReleaseEnvelope) -> None: ...
def rollback(self, envelope: ReleaseEnvelope) -> None: ...
def verify_previous(self, envelope: ReleaseEnvelope) -> None: ...
def restore(self, envelope: ReleaseEnvelope) -> None: ...
def verify_restored(self, envelope: ReleaseEnvelope) -> None: ...
def snapshot_metadata(self, envelope: ReleaseEnvelope) -> str: ...
def update_registry(self, envelope: ReleaseEnvelope, release_oid: str, tag_oid: str) -> None: ...
def update_baseline(self, envelope: ReleaseEnvelope, release_oid: str, tag_oid: str) -> None: ...
def restore_metadata(self, envelope: ReleaseEnvelope, snapshot_id: str) -> None: ...
def verify_consistency(self, envelope: ReleaseEnvelope, release_oid: str, tag_oid: str) -> None: ...
class ReleaseEnvelopeExecutor:
PHASES = (
"PREPARED", "OWNER_VERIFIED", "RELEASE_MERGED", "TAG_CREATED",
"TAG_VERIFIED", "DEPLOYED", "HEALTHY", "ROLLED_BACK",
"PREVIOUS_VERIFIED", "RESTORED", "RESTORE_VERIFIED",
"METADATA_SNAPSHOTTED", "REGISTRY_UPDATED", "BASELINE_UPDATED", "COMPLETED",
)
def __init__(self, state_path: Path, audit_path: Path, hooks: ReleaseHooks):
self.state_path = state_path
self.audit = HashChainAudit(audit_path)
self.hooks = hooks
def _load(self) -> dict[str, Any] | None:
if not self.state_path.exists():
return None
value = json.loads(self.state_path.read_text(encoding="utf-8"))
if not isinstance(value, dict):
raise ReleaseError("RELEASE_STATE_INVALID")
return value
def _save(self, state: dict[str, Any]) -> None:
atomic_write_json(self.state_path, state)
self.audit.append(state["operation_id"], {
"event": state["phase"],
"envelope_sha256": state["envelope_sha256"],
"release_oid": state.get("release_oid"),
"tag_oid": state.get("tag_oid"),
})
def execute(self, envelope: ReleaseEnvelope, operation_id: str) -> dict[str, Any]:
envelope_dict = envelope.__dict__
digest = sha256_json(envelope_dict)
state = self._load()
if state:
if state.get("operation_id") != operation_id:
raise ReleaseError("CONCURRENT_RELEASE_OPERATION_DENIED")
if state.get("envelope_sha256") != digest:
raise ReleaseError("OPERATION_ID_PAYLOAD_MISMATCH")
if state.get("phase") == "COMPLETED":
return state
else:
state = {
"operation_id": operation_id,
"envelope_sha256": digest,
"phase": "PREPARED",
"release_oid": None,
"tag_oid": None,
"tag_target_oid": None,
"metadata_snapshot_id": None,
}
self._save(state)
try:
if state["phase"] == "PREPARED":
self.hooks.verify_owner_approval(envelope)
state["phase"] = "OWNER_VERIFIED"; self._save(state)
if state["phase"] == "OWNER_VERIFIED":
state["release_oid"] = require_oid(
self.hooks.merge_release(envelope), "release_oid"
)
if state["release_oid"] != envelope.target_oid:
raise ReleaseError("RELEASE_TARGET_OID_MISMATCH")
state["phase"] = "RELEASE_MERGED"; self._save(state)
if state["phase"] == "RELEASE_MERGED":
tag_oid, target_oid = self.hooks.create_tag(
envelope, state["release_oid"]
)
state["tag_oid"] = require_oid(tag_oid, "tag_oid")
state["tag_target_oid"] = require_oid(target_oid, "tag_target_oid")
if state["tag_target_oid"] != state["release_oid"]:
raise ReleaseError("TAG_TARGET_MISMATCH")
state["phase"] = "TAG_CREATED"; self._save(state)
if state["phase"] == "TAG_CREATED":
self.hooks.verify_remote_tag(envelope, state["tag_oid"], state["release_oid"])
state["phase"] = "TAG_VERIFIED"; self._save(state)
if state["phase"] == "TAG_VERIFIED":
self.hooks.deploy(envelope, state["release_oid"])
state["phase"] = "DEPLOYED"; self._save(state)
if state["phase"] == "DEPLOYED":
self.hooks.health_check(envelope)
state["phase"] = "HEALTHY"; self._save(state)
if state["phase"] == "HEALTHY":
self.hooks.rollback(envelope)
state["phase"] = "ROLLED_BACK"; self._save(state)
if state["phase"] == "ROLLED_BACK":
self.hooks.verify_previous(envelope)
state["phase"] = "PREVIOUS_VERIFIED"; self._save(state)
if state["phase"] == "PREVIOUS_VERIFIED":
self.hooks.restore(envelope)
state["phase"] = "RESTORED"; self._save(state)
if state["phase"] == "RESTORED":
self.hooks.verify_restored(envelope)
state["phase"] = "RESTORE_VERIFIED"; self._save(state)
if state["phase"] == "RESTORE_VERIFIED":
state["metadata_snapshot_id"] = self.hooks.snapshot_metadata(envelope)
if not isinstance(state["metadata_snapshot_id"], str) or not state["metadata_snapshot_id"]:
raise ReleaseError("METADATA_SNAPSHOT_INVALID")
state["phase"] = "METADATA_SNAPSHOTTED"; self._save(state)
if state["phase"] == "METADATA_SNAPSHOTTED":
self.hooks.update_registry(
envelope, state["release_oid"], state["tag_oid"]
)
state["phase"] = "REGISTRY_UPDATED"; self._save(state)
if state["phase"] == "REGISTRY_UPDATED":
self.hooks.update_baseline(
envelope, state["release_oid"], state["tag_oid"]
)
state["phase"] = "BASELINE_UPDATED"; self._save(state)
if state["phase"] == "BASELINE_UPDATED":
self.hooks.verify_consistency(
envelope, state["release_oid"], state["tag_oid"]
)
state["phase"] = "COMPLETED"; self._save(state)
return state
except Exception as exc:
state["last_error"] = type(exc).__name__
state["failed_phase"] = state.get("phase")
if state.get("phase") in {
"DEPLOYED","HEALTHY","ROLLED_BACK","PREVIOUS_VERIFIED",
"RESTORED","RESTORE_VERIFIED","METADATA_SNAPSHOTTED",
"REGISTRY_UPDATED","BASELINE_UPDATED",
}:
try:
if state.get("metadata_snapshot_id"):
self.hooks.restore_metadata(envelope, state["metadata_snapshot_id"])
state["metadata_compensation"] = "PASS"
self.hooks.rollback(envelope)
self.hooks.verify_previous(envelope)
state["automatic_failure_rollback"] = "PASS"
except Exception as rollback_exc:
state["automatic_failure_rollback"] = "FAILED"
state["rollback_error"] = type(rollback_exc).__name__
atomic_write_json(self.state_path, state)
raise
@@ -0,0 +1,321 @@
from __future__ import annotations
import argparse
import hashlib
import json
import os
from pathlib import Path
import subprocess
import sys
from typing import Any
from .common import parse_utc, require_oid, sha256_file, utc_now
from .credentials import gitea_credential, git_http_auth_env
from .gitea_api import GiteaApi
from .release import ReleaseEnvelope, ReleaseError
from .release_deploy import ArtifactDeployment, MetadataManager, _symlink_target
from .release_transport import StructuredTransport
from .watcher import GiteaSQLiteApprovalWatcher
class ReleaseActionError(ReleaseError):
pass
def _git(git_dir: Path, *args: str, env: dict[str, str] | None = None,
check: bool = True) -> subprocess.CompletedProcess[str]:
result = subprocess.run(
["git", f"--git-dir={git_dir}", *args],
text=True,
capture_output=True,
env={"PATH": "/usr/sbin:/usr/bin:/sbin:/bin", **(env or {})},
)
if check and result.returncode:
raise ReleaseActionError("RELEASE_GIT_OPERATION_FAILED")
return result
class ReleaseActions:
def __init__(self, config: dict[str, Any]):
self.config = config
self.authoritative_git_dir = Path(config["authoritative_git_dir"])
if (
not self.authoritative_git_dir.is_absolute()
or not self.authoritative_git_dir.is_dir()
or self.authoritative_git_dir.is_symlink()
):
raise ReleaseActionError("AUTHORITATIVE_GIT_DIR_INVALID")
self.deployment = ArtifactDeployment(
config["deployment"], Path(config["deployment"]["state_path"])
)
self.metadata = MetadataManager(config["metadata"])
def verify_owner_approval(self, envelope: ReleaseEnvelope) -> dict[str, Any]:
if utc_now() >= parse_utc(envelope.approval_expires_at):
raise ReleaseActionError("RELEASE_APPROVAL_EXPIRED")
approval = self.config["approval"]
watcher = GiteaSQLiteApprovalWatcher(
Path(approval["gitea_db_path"]),
repo_id=int(approval["repo_id"]),
owner_user_id=int(approval["owner_user_id"]),
)
required_contexts = tuple(approval["required_machine_contexts"])
snapshot = watcher.snapshot(
envelope.approval_pr_number,
expected_head_oid=envelope.approval_head_oid,
required_machine_contexts=required_contexts,
)
if not snapshot.owner_approved:
raise ReleaseActionError("OWNER_EXACT_HEAD_APPROVAL_MISSING")
if snapshot.owner_review_oid != envelope.owner_review_oid:
raise ReleaseActionError("OWNER_REVIEW_OID_MISMATCH")
if snapshot.stale_reviews or snapshot.dismissed_reviews:
raise ReleaseActionError("APPROVAL_REVIEW_INVALID")
for context in required_contexts:
if snapshot.machine_statuses.get(context) != "success":
raise ReleaseActionError(f"MACHINE_STATUS_NOT_PASS:{context}")
current_ref = approval.get(
"governance_ref", "refs/heads/governance/approvals"
)
ancestor = _git(
self.authoritative_git_dir,
"merge-base", "--is-ancestor", envelope.approval_merge_oid, current_ref,
check=False,
)
if ancestor.returncode != 0:
raise ReleaseActionError("APPROVAL_MERGE_NOT_ACTIVE")
manifest = _git(
self.authoritative_git_dir,
"show", f"{envelope.approval_merge_oid}:{envelope.approval_manifest_path}",
).stdout.encode("utf-8")
if hashlib.sha256(manifest).hexdigest() != envelope.approval_manifest_sha256:
raise ReleaseActionError("APPROVAL_MANIFEST_SHA256_MISMATCH")
return {
"owner_review_oid": snapshot.owner_review_oid,
"approval_head_oid": snapshot.owner_reviewed_head_oid,
"machine_statuses": snapshot.machine_statuses,
"approval_merge_oid": envelope.approval_merge_oid,
}
def merge_release(self, envelope: ReleaseEnvelope) -> dict[str, Any]:
value = StructuredTransport(self.config["merge_transport"]).call({
"operation": "merge_release",
"version": envelope.version,
"target_oid": envelope.target_oid,
"target_tree_oid": envelope.target_tree_oid,
"approval_merge_oid": envelope.approval_merge_oid,
"owner_review_oid": envelope.owner_review_oid,
"evidence_closure_oid": envelope.evidence_closure_oid,
})
release_oid = require_oid(value.get("release_oid"), "release_oid")
if release_oid != envelope.target_oid:
raise ReleaseActionError("RELEASE_TARGET_OID_MISMATCH")
return {"release_oid": release_oid}
def create_tag(self, envelope: ReleaseEnvelope) -> dict[str, Any]:
value = StructuredTransport(self.config["tag_transport"]).call({
"operation": "request_or_create_tag",
"execute": True,
"version": envelope.version,
"target_oid": envelope.target_oid,
"evidence_closure_oid": envelope.evidence_closure_oid,
"approval_plan_id": envelope.approval_plan_id,
"approval_merge_oid": envelope.approval_merge_oid,
})
tag_oid = require_oid(value.get("tag_oid"), "tag_oid")
target_oid = require_oid(
value.get("tag_target_oid") or value.get("target_oid"), "tag_target_oid"
)
if target_oid != envelope.target_oid:
raise ReleaseActionError("TAG_TARGET_OID_MISMATCH")
return {"tag_oid": tag_oid, "tag_target_oid": target_oid}
def verify_remote_tag(
self, envelope: ReleaseEnvelope, tag_oid: str, release_oid: str
) -> dict[str, Any]:
remote = self.config["remote_tag"]
username, token, _ = gitea_credential(remote["credential_name"])
result = subprocess.run(
[
"git", "ls-remote", "--tags", remote["remote_url"],
f"refs/tags/{envelope.version}",
f"refs/tags/{envelope.version}^{{}}",
],
text=True,
capture_output=True,
env={
"PATH": "/usr/sbin:/usr/bin:/sbin:/bin",
**git_http_auth_env(username, token),
},
)
if result.returncode:
raise ReleaseActionError("REMOTE_TAG_QUERY_FAILED")
refs: dict[str, str] = {}
for line in result.stdout.splitlines():
fields = line.split()
if len(fields) == 2:
refs[fields[1]] = require_oid(fields[0], "remote_tag_oid")
direct = refs.get(f"refs/tags/{envelope.version}")
peeled = refs.get(f"refs/tags/{envelope.version}^{{}}")
if direct != tag_oid:
raise ReleaseActionError("REMOTE_TAG_OID_MISMATCH")
remote_target = peeled or direct
if remote_target != release_oid:
raise ReleaseActionError("REMOTE_PEELED_TARGET_MISMATCH")
api = GiteaApi(remote["gitea_base_url"], token)
value = api.request(
"GET",
f"/api/v1/repos/{remote['repo_owner']}/{remote['repo_name']}/tags/{envelope.version}",
)
if not isinstance(value, dict):
raise ReleaseActionError("GITEA_TAG_RESPONSE_INVALID")
api_target = (
value.get("commit", {}).get("sha")
or value.get("commit", {}).get("id")
or value.get("target")
)
if api_target != release_oid:
raise ReleaseActionError("GITEA_TAG_TARGET_MISMATCH")
return {
"tag_oid": direct,
"peeled_target_oid": remote_target,
"gitea_target_oid": api_target,
}
def deploy(self, envelope: ReleaseEnvelope) -> dict[str, Any]:
release_dir = self.deployment.deploy(envelope)
return {"release_dir": str(release_dir)}
def health_check(self, envelope: ReleaseEnvelope) -> dict[str, Any]:
self.deployment.health()
return {"health": "PASS"}
def rollback(self, envelope: ReleaseEnvelope) -> dict[str, Any]:
target = self.deployment.rollback()
return {"rollback_target": str(target)}
def verify_previous(self, envelope: ReleaseEnvelope) -> dict[str, Any]:
previous = _symlink_target(self.deployment.previous_link)
if previous is None:
raise ReleaseActionError("PREVIOUS_RELEASE_MISSING")
self.deployment.verify_current(previous)
self.deployment.health()
return {"previous_release": str(previous), "health": "PASS"}
def restore(self, envelope: ReleaseEnvelope) -> dict[str, Any]:
target = self.deployment.restore()
return {"restore_target": str(target)}
def verify_restored(self, envelope: ReleaseEnvelope) -> dict[str, Any]:
state = json.loads(self.deployment.state_path.read_text(encoding="utf-8"))
target = Path(state["release_dir"])
self.deployment.verify_current(target)
self.deployment.health()
return {"restored_release": str(target), "health": "PASS"}
def snapshot_metadata(self, envelope: ReleaseEnvelope) -> dict[str, Any]:
return {"snapshot_id": self.metadata.snapshot(envelope)}
def update_registry(self, envelope: ReleaseEnvelope) -> dict[str, Any]:
self.metadata.update_registry(envelope)
return {"registry_sha256": sha256_file(self.metadata.registry_path)}
def update_baseline(self, envelope: ReleaseEnvelope) -> dict[str, Any]:
self.metadata.update_baseline(envelope)
return {"baseline_sha256": sha256_file(self.metadata.baseline_path)}
def restore_metadata(
self, envelope: ReleaseEnvelope, snapshot_id: str
) -> dict[str, Any]:
self.metadata.restore(snapshot_id)
return {"metadata_restored": True, "snapshot_id": snapshot_id}
def verify_consistency(
self, envelope: ReleaseEnvelope, tag_oid: str, release_oid: str
) -> dict[str, Any]:
self.metadata.verify_plans_applied(envelope)
state = json.loads(self.deployment.state_path.read_text(encoding="utf-8"))
self.deployment.verify_current(Path(state["release_dir"]))
tag = self.verify_remote_tag(envelope, tag_oid, release_oid)
return {
"release_consistency": "PASS",
"version": envelope.version,
"target_oid": release_oid,
"tag_oid": tag_oid,
**tag,
}
def _validate_config_file(path: Path) -> dict[str, Any]:
if not path.is_absolute() or not path.is_file() or path.is_symlink():
raise ReleaseActionError("RELEASE_ACTION_CONFIG_INVALID")
stat = path.stat()
if stat.st_uid != 0 or stat.st_mode & 0o022:
raise ReleaseActionError("RELEASE_ACTION_CONFIG_PERMISSIONS_INVALID")
value = json.loads(path.read_text(encoding="utf-8"))
if not isinstance(value, dict):
raise ReleaseActionError("RELEASE_ACTION_CONFIG_INVALID")
return value
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument("action", choices=(
"verify_owner_approval", "merge_release", "create_tag",
"verify_remote_tag", "deploy", "health_check", "rollback",
"verify_previous", "restore", "verify_restored", "snapshot_metadata",
"update_registry", "update_baseline", "restore_metadata",
"verify_consistency",
))
parser.add_argument("--config", type=Path, required=True)
args = parser.parse_args(argv)
config = _validate_config_file(args.config)
payload = json.loads(sys.stdin.buffer.read())
if not isinstance(payload, dict) or not isinstance(payload.get("envelope"), dict):
raise ReleaseActionError("RELEASE_HOOK_PAYLOAD_INVALID")
envelope = ReleaseEnvelope.from_mapping(payload["envelope"])
extra = payload.get("extra") or {}
actions = ReleaseActions(config)
if args.action == "verify_owner_approval":
result = actions.verify_owner_approval(envelope)
elif args.action == "merge_release":
result = actions.merge_release(envelope)
elif args.action == "create_tag":
result = actions.create_tag(envelope)
elif args.action == "verify_remote_tag":
result = actions.verify_remote_tag(
envelope,
require_oid(extra.get("tag_oid"), "tag_oid"),
require_oid(extra.get("release_oid"), "release_oid"),
)
elif args.action == "deploy":
result = actions.deploy(envelope)
elif args.action == "health_check":
result = actions.health_check(envelope)
elif args.action == "rollback":
result = actions.rollback(envelope)
elif args.action == "verify_previous":
result = actions.verify_previous(envelope)
elif args.action == "restore":
result = actions.restore(envelope)
elif args.action == "verify_restored":
result = actions.verify_restored(envelope)
elif args.action == "snapshot_metadata":
result = actions.snapshot_metadata(envelope)
elif args.action == "update_registry":
result = actions.update_registry(envelope)
elif args.action == "update_baseline":
result = actions.update_baseline(envelope)
elif args.action == "restore_metadata":
snapshot_id = extra.get("snapshot_id")
if not isinstance(snapshot_id, str) or not snapshot_id:
raise ReleaseActionError("METADATA_SNAPSHOT_ID_INVALID")
result = actions.restore_metadata(envelope, snapshot_id)
else:
result = actions.verify_consistency(
envelope,
require_oid(extra.get("tag_oid"), "tag_oid"),
require_oid(extra.get("release_oid"), "release_oid"),
)
print(json.dumps({"status": "PASS", **result}, sort_keys=True))
return 0
@@ -0,0 +1,330 @@
from __future__ import annotations
import base64
import hashlib
import json
import os
from pathlib import Path, PurePosixPath
import re
import shutil
import subprocess
import tarfile
import tempfile
from typing import Any
import urllib.error
import urllib.request
from .common import atomic_write, atomic_write_json, ensure_directory, safe_relpath, sha256_file
from .release import ReleaseEnvelope, ReleaseError
UNIT_RE = re.compile(r"^[A-Za-z0-9_.@:-]+\.service$")
class DeploymentError(ReleaseError):
pass
def _run_systemctl(*args: str) -> subprocess.CompletedProcess[str]:
result = subprocess.run(
["systemctl", *args],
text=True,
capture_output=True,
env={"PATH": "/usr/sbin:/usr/bin:/sbin:/bin"},
)
if result.returncode:
raise DeploymentError("SYSTEMCTL_FAILED")
return result
def _validate_units(units: list[str]) -> tuple[str, ...]:
if not isinstance(units, list) or not units:
raise DeploymentError("SERVICE_UNITS_INVALID")
values = tuple(units)
if not all(isinstance(unit, str) and UNIT_RE.fullmatch(unit) for unit in values):
raise DeploymentError("SERVICE_UNITS_INVALID")
return values
def _symlink_target(path: Path) -> Path | None:
if not path.exists() and not path.is_symlink():
return None
if not path.is_symlink():
raise DeploymentError("RELEASE_LINK_NOT_SYMLINK")
target = Path(os.readlink(path))
return target if target.is_absolute() else (path.parent / target).resolve()
def _atomic_symlink(link: Path, target: Path) -> None:
ensure_directory(link.parent)
temporary = link.parent / f".{link.name}.tmp-{os.getpid()}"
temporary.unlink(missing_ok=True)
os.symlink(str(target), temporary)
os.replace(temporary, link)
directory_fd = os.open(link.parent, os.O_RDONLY)
try:
os.fsync(directory_fd)
finally:
os.close(directory_fd)
class ArtifactDeployment:
def __init__(self, config: dict[str, Any], state_path: Path):
self.config = config
self.state_path = state_path
self.release_root = Path(config["release_root"]).resolve()
self.current_link = Path(config["current_symlink"])
self.previous_link = Path(config["previous_symlink"])
self.units = _validate_units(config["service_units"])
if not self.release_root.is_absolute():
raise DeploymentError("RELEASE_ROOT_INVALID")
for link in (self.current_link, self.previous_link):
if not link.is_absolute() or link.parent.resolve() != self.release_root.parent:
raise DeploymentError("RELEASE_LINK_PATH_DENIED")
def _artifact(self, envelope: ReleaseEnvelope) -> Path:
path = Path(self.config["artifact_path"])
if not path.is_absolute() or not path.is_file() or path.is_symlink():
raise DeploymentError("ARTIFACT_PATH_INVALID")
if sha256_file(path) != envelope.artifact_sha256:
raise DeploymentError("ARTIFACT_SHA256_MISMATCH")
return path
def _safe_extract(self, archive: Path, destination: Path) -> dict[str, Any]:
with tarfile.open(archive, "r:*") as bundle:
members = bundle.getmembers()
if not members:
raise DeploymentError("ARTIFACT_EMPTY")
for member in members:
pure = PurePosixPath(member.name)
if (
pure.is_absolute()
or any(part in {"", ".", ".."} for part in pure.parts)
or member.issym()
or member.islnk()
or member.isdev()
or member.isfifo()
):
raise DeploymentError("ARTIFACT_MEMBER_DENIED")
bundle.extractall(destination, filter="data")
manifest_path = destination / self.config.get(
"artifact_manifest_member", "artifact-manifest.json"
)
if not manifest_path.is_file() or manifest_path.is_symlink():
raise DeploymentError("ARTIFACT_MANIFEST_MISSING")
value = json.loads(manifest_path.read_text(encoding="utf-8"))
if not isinstance(value, dict) or not isinstance(value.get("files"), dict):
raise DeploymentError("ARTIFACT_MANIFEST_INVALID")
return value
def stage(self, envelope: ReleaseEnvelope) -> Path:
archive = self._artifact(envelope)
release_dir = self.release_root / f"{envelope.version}-{envelope.target_oid[:12]}"
ensure_directory(self.release_root)
if release_dir.exists():
self.verify_release_dir(envelope, release_dir)
return release_dir
with tempfile.TemporaryDirectory(
prefix=".aios-release-stage-", dir=self.release_root
) as temporary_name:
temporary = Path(temporary_name)
manifest = self._safe_extract(archive, temporary)
if manifest.get("version") != envelope.version:
raise DeploymentError("ARTIFACT_VERSION_MISMATCH")
if manifest.get("target_oid") != envelope.target_oid:
raise DeploymentError("ARTIFACT_TARGET_OID_MISMATCH")
for raw_path, expected_sha in manifest["files"].items():
rel = safe_relpath(raw_path)
if not isinstance(expected_sha, str) or len(expected_sha) != 64:
raise DeploymentError("ARTIFACT_FILE_DIGEST_INVALID")
candidate = temporary / rel
if not candidate.is_file() or candidate.is_symlink():
raise DeploymentError("ARTIFACT_FILE_MISSING")
if sha256_file(candidate) != expected_sha:
raise DeploymentError("ARTIFACT_FILE_DIGEST_MISMATCH")
manifest["artifact_sha256"] = envelope.artifact_sha256
atomic_write_json(temporary / ".aios-release-binding.json", manifest)
os.rename(temporary, release_dir)
self.verify_release_dir(envelope, release_dir)
return release_dir
def verify_release_dir(self, envelope: ReleaseEnvelope, release_dir: Path) -> None:
if (
not release_dir.is_dir()
or release_dir.is_symlink()
or self.release_root not in release_dir.resolve().parents
):
raise DeploymentError("RELEASE_DIRECTORY_INVALID")
binding = release_dir / ".aios-release-binding.json"
value = json.loads(binding.read_text(encoding="utf-8"))
if value.get("version") != envelope.version:
raise DeploymentError("RELEASE_BINDING_VERSION_MISMATCH")
if value.get("target_oid") != envelope.target_oid:
raise DeploymentError("RELEASE_BINDING_TARGET_MISMATCH")
if value.get("artifact_sha256") != envelope.artifact_sha256:
raise DeploymentError("RELEASE_BINDING_ARTIFACT_MISMATCH")
for raw_path, expected_sha in value.get("files", {}).items():
candidate = release_dir / safe_relpath(raw_path)
if not candidate.is_file() or candidate.is_symlink():
raise DeploymentError("RELEASE_FILE_MISSING")
if sha256_file(candidate) != expected_sha:
raise DeploymentError("RELEASE_FILE_DIGEST_MISMATCH")
def deploy(self, envelope: ReleaseEnvelope) -> Path:
release_dir = self.stage(envelope)
previous = _symlink_target(self.current_link)
if previous is not None:
_atomic_symlink(self.previous_link, previous)
_atomic_symlink(self.current_link, release_dir)
_run_systemctl("daemon-reload")
for unit in self.units:
_run_systemctl("restart", unit)
atomic_write_json(self.state_path, {
"version": envelope.version,
"target_oid": envelope.target_oid,
"release_dir": str(release_dir),
"previous_dir": str(previous) if previous else None,
"units": list(self.units),
})
return release_dir
def rollback(self) -> Path:
previous = _symlink_target(self.previous_link)
if previous is None or not previous.is_dir():
raise DeploymentError("PREVIOUS_RELEASE_MISSING")
_atomic_symlink(self.current_link, previous)
_run_systemctl("daemon-reload")
for unit in self.units:
_run_systemctl("restart", unit)
return previous
def restore(self) -> Path:
if not self.state_path.is_file():
raise DeploymentError("DEPLOYMENT_STATE_MISSING")
state = json.loads(self.state_path.read_text(encoding="utf-8"))
release_dir = Path(state["release_dir"])
if not release_dir.is_dir():
raise DeploymentError("RESTORE_RELEASE_MISSING")
_atomic_symlink(self.current_link, release_dir)
_run_systemctl("daemon-reload")
for unit in self.units:
_run_systemctl("restart", unit)
return release_dir
def health(self) -> None:
for unit in self.units:
_run_systemctl("is-active", "--quiet", unit)
for check in self.config.get("health_checks", []):
if not isinstance(check, dict):
raise DeploymentError("HEALTH_CHECK_INVALID")
url = check.get("url")
if not isinstance(url, str) or not url.startswith(
("http://127.0.0.1", "https://127.0.0.1")
):
raise DeploymentError("HEALTH_CHECK_URL_DENIED")
request = urllib.request.Request(url, method=check.get("method", "GET"))
try:
with urllib.request.urlopen(
request, timeout=float(check.get("timeout_seconds", 10))
) as response:
status = response.status
max_body = int(check.get("max_body_bytes", 65536))
body = response.read(max_body + 1)
if len(body) > max_body:
raise DeploymentError("HEALTH_CHECK_BODY_TOO_LARGE")
except (urllib.error.HTTPError, urllib.error.URLError) as exc:
raise DeploymentError("HEALTH_CHECK_FAILED") from exc
if status != int(check.get("expected_status", 200)):
raise DeploymentError("HEALTH_CHECK_STATUS_MISMATCH")
expected_sha = check.get("expected_body_sha256")
if expected_sha and hashlib.sha256(body).hexdigest() != expected_sha:
raise DeploymentError("HEALTH_CHECK_BODY_MISMATCH")
def verify_current(self, expected: Path) -> None:
current = _symlink_target(self.current_link)
if current is None or current.resolve() != expected.resolve():
raise DeploymentError("CURRENT_RELEASE_MISMATCH")
class MetadataManager:
def __init__(self, config: dict[str, Any]):
self.registry_path = Path(config["registry_path"])
self.baseline_path = Path(config["baseline_path"])
self.registry_plan_path = Path(config["registry_plan_path"])
self.baseline_plan_path = Path(config["baseline_plan_path"])
self.snapshot_root = Path(config["metadata_snapshot_root"])
for path in (
self.registry_path, self.baseline_path,
self.registry_plan_path, self.baseline_plan_path, self.snapshot_root
):
if not path.is_absolute():
raise DeploymentError("METADATA_PATH_INVALID")
def _verify_plan(self, path: Path, expected_sha: str) -> bytes:
if not path.is_file() or path.is_symlink():
raise DeploymentError("METADATA_PLAN_MISSING")
raw = path.read_bytes()
if hashlib.sha256(raw).hexdigest() != expected_sha:
raise DeploymentError("METADATA_PLAN_SHA256_MISMATCH")
return raw
def snapshot(self, envelope: ReleaseEnvelope) -> str:
ensure_directory(self.snapshot_root)
snapshot_id = f"{envelope.version}-{envelope.approval_merge_oid[:12]}"
destination = self.snapshot_root / snapshot_id
if destination.exists():
return snapshot_id
ensure_directory(destination)
for name, source in (
("registry", self.registry_path),
("baseline", self.baseline_path),
):
if source.exists():
raw = source.read_bytes()
atomic_write(destination / f"{name}.bin", raw)
atomic_write_json(destination / f"{name}.meta.json", {
"exists": True,
"sha256": hashlib.sha256(raw).hexdigest(),
"path": str(source),
})
else:
atomic_write_json(destination / f"{name}.meta.json", {
"exists": False,
"path": str(source),
})
return snapshot_id
def update_registry(self, envelope: ReleaseEnvelope) -> None:
raw = self._verify_plan(self.registry_plan_path, envelope.registry_plan_sha256)
atomic_write(self.registry_path, raw)
def update_baseline(self, envelope: ReleaseEnvelope) -> None:
raw = self._verify_plan(self.baseline_plan_path, envelope.baseline_plan_sha256)
atomic_write(self.baseline_path, raw)
def restore(self, snapshot_id: str) -> None:
if "/" in snapshot_id or "\\" in snapshot_id:
raise DeploymentError("METADATA_SNAPSHOT_ID_INVALID")
destination = self.snapshot_root / safe_relpath(snapshot_id)
if not destination.is_dir():
raise DeploymentError("METADATA_SNAPSHOT_MISSING")
for name, target in (
("registry", self.registry_path),
("baseline", self.baseline_path),
):
meta = json.loads((destination / f"{name}.meta.json").read_text(encoding="utf-8"))
if meta.get("path") != str(target):
raise DeploymentError("METADATA_SNAPSHOT_PATH_MISMATCH")
if meta.get("exists"):
raw = (destination / f"{name}.bin").read_bytes()
if hashlib.sha256(raw).hexdigest() != meta.get("sha256"):
raise DeploymentError("METADATA_SNAPSHOT_DIGEST_MISMATCH")
atomic_write(target, raw)
else:
target.unlink(missing_ok=True)
def verify_plans_applied(self, envelope: ReleaseEnvelope) -> None:
if sha256_file(self.registry_path) != envelope.registry_plan_sha256:
raise DeploymentError("REGISTRY_PLAN_NOT_APPLIED")
if sha256_file(self.baseline_path) != envelope.baseline_plan_sha256:
raise DeploymentError("BASELINE_PLAN_NOT_APPLIED")
@@ -0,0 +1,69 @@
from __future__ import annotations
import json, os, subprocess
from pathlib import Path
from typing import Any
from .common import canonical_json, require_oid
from .release import ReleaseEnvelope, ReleaseError
class ReleaseHookError(ReleaseError): pass
class StructuredHookRunner:
def __init__(self, hook_config: dict[str,Any]):
self.hook_config=hook_config
def command(self,name: str) -> list[str]:
value=self.hook_config.get(name)
if not isinstance(value,list) or not value or not all(isinstance(x,str) and x for x in value):
raise ReleaseHookError(f"HOOK_COMMAND_INVALID:{name}")
executable=Path(value[0])
if not executable.is_absolute() or not executable.is_file() or executable.is_symlink():
raise ReleaseHookError(f"HOOK_EXECUTABLE_INVALID:{name}")
stat=executable.stat()
if stat.st_uid!=0 or stat.st_mode & 0o022:
raise ReleaseHookError(f"HOOK_EXECUTABLE_PERMISSIONS_INVALID:{name}")
return value
def run(self,name: str,envelope: ReleaseEnvelope,extra: dict[str,Any] | None=None) -> dict[str,Any]:
payload={"hook":name,"envelope":envelope.__dict__,"extra":extra or {}}
hook_env={"PATH":"/usr/sbin:/usr/bin:/sbin:/bin","AIOS_GITOPS_WRITES_ENABLED":"false"}
if "CREDENTIALS_DIRECTORY" in os.environ:
hook_env["CREDENTIALS_DIRECTORY"]=os.environ["CREDENTIALS_DIRECTORY"]
result=subprocess.run(self.command(name),input=canonical_json(payload),capture_output=True,
env=hook_env,timeout=600)
if result.returncode: raise ReleaseHookError(f"HOOK_FAILED:{name}")
try: value=json.loads(result.stdout)
except json.JSONDecodeError as exc: raise ReleaseHookError(f"HOOK_RESPONSE_INVALID:{name}") from exc
if not isinstance(value,dict) or value.get("status")!="PASS":
raise ReleaseHookError(f"HOOK_RESPONSE_FAILED:{name}")
return value
class ConfiguredReleaseHooks:
def __init__(self,runner: StructuredHookRunner): self.runner=runner
def verify_owner_approval(self,e): self.runner.run("verify_owner_approval",e)
def merge_release(self,e):
oid=require_oid(self.runner.run("merge_release",e).get("release_oid"),"release_oid")
if oid!=e.target_oid: raise ReleaseHookError("RELEASE_TARGET_OID_MISMATCH")
return oid
def create_tag(self,e,release_oid):
value=self.runner.run("create_tag",e,{"release_oid":release_oid})
return require_oid(value.get("tag_oid"),"tag_oid"),require_oid(value.get("tag_target_oid"),"tag_target_oid")
def verify_remote_tag(self,e,tag_oid,release_oid):
self.runner.run("verify_remote_tag",e,{"tag_oid":tag_oid,"release_oid":release_oid})
def deploy(self,e,release_oid): self.runner.run("deploy",e,{"release_oid":release_oid})
def health_check(self,e): self.runner.run("health_check",e)
def rollback(self,e): self.runner.run("rollback",e)
def verify_previous(self,e): self.runner.run("verify_previous",e)
def restore(self,e): self.runner.run("restore",e)
def verify_restored(self,e): self.runner.run("verify_restored",e)
def snapshot_metadata(self,e):
value=self.runner.run("snapshot_metadata",e)
snapshot_id=value.get("snapshot_id")
if not isinstance(snapshot_id,str) or not snapshot_id:
raise ReleaseHookError("METADATA_SNAPSHOT_INVALID")
return snapshot_id
def update_registry(self,e,release_oid,tag_oid):
self.runner.run("update_registry",e,{"release_oid":release_oid,"tag_oid":tag_oid})
def update_baseline(self,e,release_oid,tag_oid):
self.runner.run("update_baseline",e,{"release_oid":release_oid,"tag_oid":tag_oid})
def restore_metadata(self,e,snapshot_id):
self.runner.run("restore_metadata",e,{"snapshot_id":snapshot_id})
def verify_consistency(self,e,release_oid,tag_oid):
self.runner.run("verify_consistency",e,{"release_oid":release_oid,"tag_oid":tag_oid})
@@ -0,0 +1,30 @@
from __future__ import annotations
import argparse, json
from pathlib import Path
from .release import ReleaseEnvelope, ReleaseEnvelopeExecutor
from .release_hooks import ConfiguredReleaseHooks, StructuredHookRunner
def _load_root_json(path: Path) -> dict:
if not path.is_absolute() or not path.is_file() or path.is_symlink():
raise RuntimeError("RELEASE_INPUT_FILE_INVALID")
stat=path.stat()
if stat.st_uid!=0 or stat.st_mode & 0o022:
raise RuntimeError("RELEASE_INPUT_FILE_PERMISSIONS_INVALID")
value=json.loads(path.read_text(encoding="utf-8"))
if not isinstance(value,dict):
raise RuntimeError("RELEASE_INPUT_JSON_INVALID")
return value
def main(argv: list[str] | None=None) -> int:
p=argparse.ArgumentParser()
p.add_argument("--config",type=Path,required=True)
p.add_argument("--envelope",type=Path,required=True)
p.add_argument("--operation-id",required=True)
args=p.parse_args(argv)
cfg=_load_root_json(args.config)
envelope=ReleaseEnvelope.from_mapping(_load_root_json(args.envelope))
hooks=ConfiguredReleaseHooks(StructuredHookRunner(cfg["hooks"]))
state=ReleaseEnvelopeExecutor(Path(cfg["state_path"]),Path(cfg["audit_path"]),hooks).execute(
envelope,args.operation_id)
print(json.dumps(state,sort_keys=True))
return 0 if state["phase"]=="COMPLETED" else 2
@@ -0,0 +1,96 @@
from __future__ import annotations
import json
from pathlib import Path
import socket
from typing import Any
import urllib.error
import urllib.request
from .credentials import gitea_credential
class ReleaseTransportError(RuntimeError):
pass
class StructuredTransport:
def __init__(self, config: dict[str, Any]):
self.config = config
def call(self, payload: dict[str, Any]) -> dict[str, Any]:
mode = self.config.get("transport")
if mode == "unix_json":
return self._unix_json(payload)
if mode == "http_json":
return self._http_json(payload)
raise ReleaseTransportError("TRANSPORT_MODE_INVALID")
def _unix_json(self, payload: dict[str, Any]) -> dict[str, Any]:
socket_path = Path(self.config["socket_path"])
if not socket_path.is_absolute() or not socket_path.exists() or socket_path.is_symlink():
raise ReleaseTransportError("UNIX_SOCKET_INVALID")
raw = json.dumps(payload, sort_keys=True, separators=(",", ":")).encode() + b"\n"
if len(raw) > int(self.config.get("max_request_bytes", 1024 * 1024)):
raise ReleaseTransportError("UNIX_REQUEST_TOO_LARGE")
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as client:
client.settimeout(float(self.config.get("timeout_seconds", 60)))
client.connect(str(socket_path))
client.sendall(raw)
chunks = []
size = 0
limit = int(self.config.get("max_response_bytes", 1024 * 1024))
while True:
part = client.recv(65536)
if not part:
break
chunks.append(part)
size += len(part)
if size > limit:
raise ReleaseTransportError("UNIX_RESPONSE_TOO_LARGE")
if b"\n" in part:
break
try:
value = json.loads(b"".join(chunks).splitlines()[0])
except (IndexError, json.JSONDecodeError) as exc:
raise ReleaseTransportError("UNIX_RESPONSE_INVALID") from exc
if not isinstance(value, dict):
raise ReleaseTransportError("UNIX_RESPONSE_INVALID")
if value.get("status") == "PASS" and isinstance(value.get("result"), dict):
return value["result"]
if value.get("status") == "PASS":
return value
raise ReleaseTransportError(str(value.get("error") or "UNIX_CALL_FAILED"))
def _http_json(self, payload: dict[str, Any]) -> dict[str, Any]:
url = self.config.get("url")
if not isinstance(url, str) or not url.startswith(("http://127.0.0.1", "https://127.0.0.1")):
raise ReleaseTransportError("HTTP_URL_DENIED")
username, token, _ = gitea_credential(self.config["credential_name"])
raw = json.dumps(payload, sort_keys=True, separators=(",", ":")).encode()
request = urllib.request.Request(url, data=raw, method="POST")
request.add_header("Authorization", f"Bearer {token}")
request.add_header("Content-Type", "application/json")
request.add_header("X-AIOS-Actor", username)
try:
with urllib.request.urlopen(
request, timeout=float(self.config.get("timeout_seconds", 60))
) as response:
status = response.status
body = response.read(int(self.config.get("max_response_bytes", 1024 * 1024)) + 1)
except urllib.error.HTTPError as exc:
raise ReleaseTransportError(f"HTTP_CALL_{exc.code}") from exc
except urllib.error.URLError as exc:
raise ReleaseTransportError("HTTP_CALL_UNAVAILABLE") from exc
if status not in tuple(self.config.get("expected_statuses", [200])):
raise ReleaseTransportError(f"HTTP_CALL_{status}")
if len(body) > int(self.config.get("max_response_bytes", 1024 * 1024)):
raise ReleaseTransportError("HTTP_RESPONSE_TOO_LARGE")
try:
value = json.loads(body)
except json.JSONDecodeError as exc:
raise ReleaseTransportError("HTTP_RESPONSE_INVALID") from exc
if not isinstance(value, dict):
raise ReleaseTransportError("HTTP_RESPONSE_INVALID")
if value.get("status") not in {"PASS", "COMPLETED", "SUCCESS"}:
raise ReleaseTransportError(str(value.get("error") or "HTTP_CALL_FAILED"))
return value.get("result") if isinstance(value.get("result"), dict) else value
@@ -0,0 +1,88 @@
from __future__ import annotations
from dataclasses import dataclass
from datetime import timedelta
from typing import Any, Mapping
from .common import parse_utc, sha256_json, utc_now
class RenewalError(RuntimeError):
pass
@dataclass(frozen=True)
class RenewalDecision:
allowed: bool
reason: str
renewal_number: int
issued_at: str | None
expires_at: str | None
manifest: dict[str, Any] | None
class RenewalController:
IMMUTABLE_FIELDS = (
"policy_sha256",
"allowed_paths",
"allowed_operations",
"denied_operations",
"runtime_sources",
"artifact_sha256",
"target_oid",
)
def decide(
self,
*,
standing_policy: Mapping[str, Any],
previous_manifest: Mapping[str, Any],
observation: Mapping[str, Any],
) -> RenewalDecision:
required = {
"policy_id", "maximum_automatic_renewals", "maximum_total_window_seconds",
"single_renewal_seconds", "effective_at", "expires_at",
}
if not required.issubset(standing_policy):
raise RenewalError("STANDING_POLICY_INVALID")
count = int(previous_manifest.get("automatic_renewal_number", 0))
maximum = int(standing_policy["maximum_automatic_renewals"])
if count >= maximum:
return RenewalDecision(False, "AUTOMATIC_RENEWAL_LIMIT_REACHED", count, None, None, None)
single = int(standing_policy["single_renewal_seconds"])
if single < 1 or single > 12 * 3600:
raise RenewalError("SINGLE_RENEWAL_WINDOW_INVALID")
total = int(standing_policy["maximum_total_window_seconds"])
if total > 7 * 24 * 3600:
raise RenewalError("TOTAL_WINDOW_INVALID")
if observation.get("secret_scan") != "PASS":
return RenewalDecision(False, "SECRET_SCAN_NOT_PASS", count, None, None, None)
if observation.get("approval_ref_fast_forward") is not True:
return RenewalDecision(False, "APPROVAL_REF_NOT_FAST_FORWARD", count, None, None, None)
for field in self.IMMUTABLE_FIELDS:
if previous_manifest.get(field) != observation.get(field):
return RenewalDecision(False, f"IMMUTABLE_FIELD_CHANGED:{field}", count, None, None, None)
start = parse_utc(str(standing_policy["effective_at"]))
now = utc_now()
if now >= parse_utc(str(standing_policy["expires_at"])):
return RenewalDecision(False, "STANDING_POLICY_EXPIRED", count, None, None, None)
if (now - start).total_seconds() >= total:
return RenewalDecision(False, "TOTAL_WINDOW_EXHAUSTED", count, None, None, None)
expiry = min(
now + timedelta(seconds=single),
start + timedelta(seconds=total),
parse_utc(str(standing_policy["expires_at"])),
)
manifest = dict(previous_manifest)
manifest.update({
"approval_type": "AUTONOMOUS_EXECUTION_RENEWAL",
"automatic_renewal_number": count + 1,
"issued_at": now.isoformat().replace("+00:00", "Z"),
"expires_at": expiry.isoformat().replace("+00:00", "Z"),
"renews_manifest_sha256": sha256_json(dict(previous_manifest)),
"standing_policy_id": standing_policy["policy_id"],
})
return RenewalDecision(
True, "PASS", count + 1,
manifest["issued_at"], manifest["expires_at"], manifest,
)
@@ -0,0 +1,23 @@
from __future__ import annotations
import argparse, json, time
from pathlib import Path
from .common import atomic_write_json
from .renewal import RenewalController
def main(argv: list[str] | None=None) -> int:
p=argparse.ArgumentParser(); p.add_argument("--config",type=Path,required=True); p.add_argument("--once",action="store_true")
args=p.parse_args(argv); cfg=json.loads(args.config.read_text(encoding="utf-8"))
requests_dir,receipts_dir=Path(cfg["requests_dir"]),Path(cfg["receipts_dir"])
controller=RenewalController()
while True:
receipts_dir.mkdir(parents=True,exist_ok=True)
for request_path in sorted(requests_dir.glob("*.json")):
receipt=receipts_dir/f"{request_path.stem}.receipt.json"
if receipt.exists(): continue
req=json.loads(request_path.read_text(encoding="utf-8"))
decision=controller.decide(standing_policy=req["standing_policy"],
previous_manifest=req["previous_manifest"],observation=req["observation"])
atomic_write_json(receipt,decision.__dict__)
if args.once: break
time.sleep(float(cfg.get("poll_seconds",10)))
return 0
@@ -0,0 +1,65 @@
from __future__ import annotations
import argparse, json, urllib.error, urllib.request, subprocess
from pathlib import Path
from .common import atomic_write_json, sha256_json, sha256_file
from .credentials import gitea_credential
from .gitea_api import GiteaApi, GiteaApiError
class RevocationError(RuntimeError): pass
def revoke_grant_file(grant_path: Path, revoked_path: Path) -> str:
if not grant_path.is_absolute() or not revoked_path.is_absolute():
raise RevocationError("GRANT_REVOCATION_PATH_INVALID")
if grant_path.parent.resolve() != revoked_path.parent.resolve():
raise RevocationError("GRANT_REVOCATION_PATH_INVALID")
if grant_path.is_symlink() or revoked_path.is_symlink():
raise RevocationError("GRANT_REVOCATION_SYMLINK_DENIED")
if revoked_path.exists():
return sha256_file(revoked_path)
if not grant_path.is_file():
raise RevocationError("GRANT_FILE_MISSING")
digest=sha256_file(grant_path)
grant_path.replace(revoked_path)
revoked_path.chmod(0o440)
return digest
def verify_401(base_url: str, token: str) -> int:
req=urllib.request.Request(base_url.rstrip("/")+"/api/v1/user")
req.add_header("Authorization",f"token {token}")
try: urllib.request.urlopen(req,timeout=15)
except urllib.error.HTTPError as exc:
if exc.code==401: return 401
raise RevocationError(f"OLD_TOKEN_UNEXPECTED_HTTP_{exc.code}") from exc
except urllib.error.URLError as exc: raise RevocationError("OLD_TOKEN_VERIFICATION_UNAVAILABLE") from exc
raise RevocationError("OLD_TOKEN_STILL_AUTHORIZED")
def main(argv: list[str] | None=None) -> int:
p=argparse.ArgumentParser(); p.add_argument("--config",type=Path,required=True)
args=p.parse_args(argv); cfg=json.loads(args.config.read_text(encoding="utf-8"))
writer_username,writer_token,token_id=gitea_credential(cfg["writer_credential_name"])
if token_id is None: raise RevocationError("WRITER_TOKEN_ID_REQUIRED")
admin_username,admin_token,_=gitea_credential(cfg["admin_credential_name"])
if writer_username!=cfg["expected_writer_username"]: raise RevocationError("WRITER_USERNAME_MISMATCH")
unit=cfg["writer_unit"]
if not isinstance(unit,str) or not unit.endswith(".service") or "/" in unit:
raise RevocationError("WRITER_UNIT_INVALID")
subprocess.run(["systemctl","stop",unit],check=True,capture_output=True)
api=GiteaApi(cfg["gitea_base_url"],admin_token)
try: api.request("DELETE",f"/api/v1/users/{writer_username}/tokens/{token_id}",expected=(204,))
except GiteaApiError as exc:
if exc.status!=404: raise
old_status=verify_401(cfg["gitea_base_url"],writer_token)
encrypted=Path(cfg["encrypted_credential_path"])
if encrypted.resolve().parent!=Path("/etc/credstore.encrypted").resolve() or encrypted.is_symlink():
raise RevocationError("ENCRYPTED_CREDENTIAL_PATH_DENIED")
encrypted.unlink(missing_ok=True)
grant_sha256=revoke_grant_file(
Path(cfg["grant_path"]),Path(cfg["revoked_grant_path"])
)
receipt={"writer_unit":unit,"writer_username":writer_username,"token_id":token_id,
"old_token_auth_status":old_status,"encrypted_credential_removed":not encrypted.exists(),
"grant_revoked":True,"grant_sha256":grant_sha256,
"revoked_grant_path":cfg["revoked_grant_path"],
"admin_username_hash":sha256_json({"username":admin_username})}
atomic_write_json(Path(cfg["receipt_path"]),receipt)
return 0
@@ -0,0 +1,53 @@
from __future__ import annotations
import json, os, socket, socketserver, struct
from pathlib import Path
from typing import Any, Callable
from .common import canonical_json, ensure_directory, redact
class SocketServiceError(RuntimeError): pass
def peer_credentials(connection: socket.socket) -> tuple[int, int, int]:
if not hasattr(socket, "SO_PEERCRED"):
raise SocketServiceError("SO_PEERCRED_UNAVAILABLE")
raw = connection.getsockopt(socket.SOL_SOCKET, socket.SO_PEERCRED, struct.calcsize("3i"))
return struct.unpack("3i", raw)
class JsonLineUnixHandler(socketserver.StreamRequestHandler):
def handle(self) -> None:
server = self.server
pid, uid, gid = peer_credentials(self.connection)
if uid not in server.allowed_uids:
self.wfile.write(canonical_json({"status":"DENIED","error":"PEER_UID_DENIED"}) + b"\n")
return
line = self.rfile.readline(server.max_request_bytes + 1)
if not line or len(line) > server.max_request_bytes:
self.wfile.write(canonical_json({"status":"DENIED","error":"REQUEST_SIZE_INVALID"}) + b"\n")
return
try:
value = json.loads(line)
if not isinstance(value, dict):
raise SocketServiceError("REQUEST_SCHEMA_INVALID")
response = server.dispatch(value, {"pid":pid,"uid":uid,"gid":gid})
result = {"status":"PASS","result":redact(response)}
except Exception as exc:
result = {"status":"FAILED","error":str(exc)}
self.wfile.write(canonical_json(result) + b"\n")
class JsonLineUnixServer(socketserver.UnixStreamServer):
allow_reuse_address = True
def __init__(self, socket_path: Path, *, allowed_uids: set[int],
dispatch: Callable[[dict[str, Any], dict[str, int]], dict[str, Any]],
max_request_bytes: int = 4 * 1024 * 1024, socket_mode: int = 0o660):
if not socket_path.is_absolute():
raise SocketServiceError("SOCKET_PATH_INVALID")
ensure_directory(socket_path.parent)
socket_path.unlink(missing_ok=True)
self.allowed_uids = allowed_uids
self.dispatch = dispatch
self.max_request_bytes = max_request_bytes
self.socket_path = socket_path
super().__init__(str(socket_path), JsonLineUnixHandler)
os.chmod(socket_path, socket_mode)
def server_close(self) -> None:
super().server_close()
self.socket_path.unlink(missing_ok=True)
@@ -0,0 +1,99 @@
from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
import sqlite3
from typing import Iterable
from .common import require_oid
class ApprovalError(RuntimeError):
pass
@dataclass(frozen=True)
class ApprovalSnapshot:
pr_number: int
head_oid: str
base_branch: str
merged: bool
merged_oid: str | None
owner_approved: bool
owner_review_oid: int | None
owner_reviewed_head_oid: str | None
stale_reviews: int
dismissed_reviews: int
machine_statuses: dict[str, str]
class GiteaSQLiteApprovalWatcher:
def __init__(self, db_path: Path, *, repo_id: int, owner_user_id: int = 2):
self.db_path = db_path
self.repo_id = repo_id
self.owner_user_id = owner_user_id
def snapshot(
self,
pr_number: int,
*,
expected_head_oid: str,
required_machine_contexts: Iterable[str],
) -> ApprovalSnapshot:
head = require_oid(expected_head_oid, "expected_head_oid")
if not self.db_path.is_absolute() or not self.db_path.is_file() or self.db_path.is_symlink():
raise ApprovalError("GITEA_DB_INVALID")
with sqlite3.connect(f"file:{self.db_path}?mode=ro", uri=True) as db:
db.row_factory = sqlite3.Row
pr = db.execute(
"SELECT pr.issue_id,pr.head_branch,pr.base_branch,pr.has_merged,"
"pr.merged_commit_id FROM pull_request pr "
"JOIN issue i ON i.id=pr.issue_id "
"WHERE i.repo_id=? AND pr.\"index\"=?",
(self.repo_id, pr_number),
).fetchone()
if pr is None:
raise ApprovalError("PR_NOT_FOUND")
owner = db.execute(
"SELECT id,type,commit_id,stale,dismissed FROM review "
"WHERE issue_id=? AND reviewer_id=? ORDER BY id DESC LIMIT 1",
(pr["issue_id"], self.owner_user_id),
).fetchone()
stale = db.execute(
"SELECT COUNT(*) FROM review WHERE issue_id=? AND stale=1",
(pr["issue_id"],),
).fetchone()[0]
dismissed = db.execute(
"SELECT COUNT(*) FROM review WHERE issue_id=? AND dismissed=1",
(pr["issue_id"],),
).fetchone()[0]
status_rows = db.execute(
"SELECT context,state,sha FROM commit_status "
"WHERE repo_id=? AND sha=? ORDER BY id DESC",
(self.repo_id, head),
).fetchall()
statuses: dict[str, str] = {}
required = set(required_machine_contexts)
for row in status_rows:
if row["context"] in required:
statuses.setdefault(row["context"], row["state"])
owner_ok = bool(
owner
and int(owner["type"]) == 1
and owner["commit_id"] == head
and int(owner["stale"]) == 0
and int(owner["dismissed"]) == 0
)
return ApprovalSnapshot(
pr_number=pr_number,
head_oid=head,
base_branch=str(pr["base_branch"]),
merged=bool(pr["has_merged"]),
merged_oid=str(pr["merged_commit_id"]) if pr["merged_commit_id"] else None,
owner_approved=owner_ok,
owner_review_oid=int(owner["id"]) if owner_ok else None,
owner_reviewed_head_oid=str(owner["commit_id"]) if owner_ok else None,
stale_reviews=int(stale),
dismissed_reviews=int(dismissed),
machine_statuses=statuses,
)
@@ -0,0 +1,22 @@
from __future__ import annotations
import argparse, json, time
from pathlib import Path
from .common import atomic_write_json
from .watcher import GiteaSQLiteApprovalWatcher
def main(argv: list[str] | None=None) -> int:
p=argparse.ArgumentParser(); p.add_argument("--config",type=Path,required=True); p.add_argument("--once",action="store_true")
args=p.parse_args(argv); cfg=json.loads(args.config.read_text(encoding="utf-8"))
requests_dir,output_dir=Path(cfg["requests_dir"]),Path(cfg["output_dir"])
watcher=GiteaSQLiteApprovalWatcher(Path(cfg["gitea_db_path"]),
repo_id=int(cfg["repo_id"]),owner_user_id=int(cfg["owner_user_id"]))
while True:
output_dir.mkdir(parents=True,exist_ok=True)
for request_path in sorted(requests_dir.glob("*.json")):
req=json.loads(request_path.read_text(encoding="utf-8"))
snapshot=watcher.snapshot(int(req["pr_number"]),expected_head_oid=req["expected_head_oid"],
required_machine_contexts=req["required_machine_contexts"])
atomic_write_json(output_dir/f"{request_path.stem}.status.json",snapshot.__dict__)
if args.once: break
time.sleep(float(cfg.get("poll_seconds",5)))
return 0
@@ -0,0 +1,236 @@
from __future__ import annotations
from dataclasses import dataclass
import hashlib
import json
import os
from pathlib import Path
import re
import subprocess
import tempfile
import time
from typing import Any
from .common import HashChainAudit, canonical_json, require_oid, safe_relpath
from .ledger import LedgerError, OperationLedger
from .policy import DevelopmentPolicy, PolicyError
SECRET_PATTERNS = (
re.compile(rb"-----BEGIN (?:OPENSSH |RSA |EC )?PRIVATE KEY-----"),
re.compile(rb"(?i)\b(?:api[_-]?key|password|secret|token)\s*[:=]\s*['\"]?[A-Za-z0-9_./+=:-]{16,}"),
)
class WriterError(RuntimeError):
def __init__(self, code: str, status: int = 409):
super().__init__(code)
self.code = code
self.status = status
@dataclass(frozen=True)
class CommitRequest:
task_id: str
operation_id: str
repository: str
branch: str
expected_head_oid: str
expected_base_oid: str
path: str
content: bytes
content_sha256: str
message: str
actor_identity: str
writer_audience: str
class GovernedDevelopmentWriter:
def __init__(
self,
*,
worktree: Path,
remote: str,
policy: DevelopmentPolicy,
ledger: OperationLedger,
audit: HashChainAudit,
git_env: dict[str, str] | None = None,
):
self.worktree = worktree
self.remote = remote
self.policy = policy
self.ledger = ledger
self.audit = audit
self.git_env = dict(git_env or {})
def _git(self, *args: str, check: bool = True) -> subprocess.CompletedProcess[str]:
env = {**os.environ, "GIT_TERMINAL_PROMPT": "0", **self.git_env}
config_count = int(env.get("GIT_CONFIG_COUNT", "0"))
env[f"GIT_CONFIG_KEY_{config_count}"] = "safe.directory"
env[f"GIT_CONFIG_VALUE_{config_count}"] = str(self.worktree)
env["GIT_CONFIG_COUNT"] = str(config_count + 1)
result = subprocess.run(
["git", *args], cwd=self.worktree, text=True, capture_output=True, env=env
)
if check and result.returncode:
raise WriterError("GIT_OPERATION_FAILED", 500)
return result
def _head(self) -> str:
return require_oid(self._git("rev-parse", "HEAD").stdout.strip(), "head_oid")
def _remote_head(self, branch: str) -> str | None:
result = self._git(
"ls-remote", "--heads", self.remote, f"refs/heads/{branch}", check=False
)
if result.returncode:
raise WriterError("REMOTE_PROJECTION_UNAVAILABLE", 503)
fields = result.stdout.split()
if not fields:
return None
if len(fields) != 2:
raise WriterError("REMOTE_PROJECTION_INVALID", 503)
return require_oid(fields[0], "remote_oid")
def _validate_worktree(self, request: CommitRequest) -> None:
if not self.worktree.is_absolute() or not (self.worktree / ".git").exists():
raise WriterError("WORKTREE_INVALID", 500)
current_branch = self._git("branch", "--show-current").stdout.strip()
if current_branch != request.branch:
raise WriterError("BRANCH_CHECKOUT_MISMATCH")
if self._git("status", "--porcelain").stdout.strip():
raise WriterError("WORKTREE_DIRTY")
head = self._head()
if head != request.expected_head_oid:
raise WriterError("BRANCH_HEAD_CHANGED")
remote = self._remote_head(request.branch)
if remote is not None and remote != head:
raise WriterError("REMOTE_BRANCH_HEAD_CHANGED")
@staticmethod
def _secret_scan(content: bytes) -> None:
if any(pattern.search(content) for pattern in SECRET_PATTERNS):
raise WriterError("SECRET_CONTENT_REJECTED", 403)
def commit_file(self, request: CommitRequest) -> dict[str, Any]:
try:
require_oid(request.expected_head_oid, "expected_head_oid")
require_oid(request.expected_base_oid, "expected_base_oid")
normalized_path = safe_relpath(request.path)
if hashlib.sha256(request.content).hexdigest() != request.content_sha256:
raise WriterError("CONTENT_DIGEST_MISMATCH", 403)
if not request.message or "\n" in request.message or "\r" in request.message:
raise WriterError("COMMIT_MESSAGE_INVALID", 403)
self.policy.validate(
repository=request.repository,
branch=request.branch,
path=normalized_path,
operation="commit_file.atomic",
actor_identity=request.actor_identity,
writer_audience=request.writer_audience,
expected_base_oid=request.expected_base_oid,
)
except (RuntimeError, PolicyError) as exc:
if isinstance(exc, WriterError):
raise
raise WriterError(str(exc), 403) from exc
self._secret_scan(request.content)
payload = {
"task_id": request.task_id,
"repository": request.repository,
"branch": request.branch,
"expected_head_oid": request.expected_head_oid,
"expected_base_oid": request.expected_base_oid,
"path": normalized_path,
"content_sha256": request.content_sha256,
"message_sha256": hashlib.sha256(request.message.encode()).hexdigest(),
"actor_identity": request.actor_identity,
"writer_audience": request.writer_audience,
"policy_sha256": self.policy.policy_sha256,
}
payload_sha = hashlib.sha256(canonical_json(payload)).hexdigest()
existing = self.ledger.get(request.operation_id)
if existing is not None:
if existing["payload_sha256"] != payload_sha:
raise WriterError("OPERATION_ID_PAYLOAD_MISMATCH", 409)
if existing["state"] == "COMPLETED":
return json.loads(existing["result_json"])
if existing["state"] in {"FAILED_RESTORED", "RECONCILIATION_REQUIRED", "FAILED"}:
raise WriterError(existing["state"], 409)
self._validate_worktree(request)
record = self.ledger.prepare(
request.operation_id, payload_sha, request.expected_head_oid, int(time.time())
)
target = (self.worktree / normalized_path).resolve()
try:
target.relative_to(self.worktree.resolve())
except ValueError as exc:
raise WriterError("PATH_ESCAPE_DENIED", 403) from exc
target.parent.mkdir(parents=True, exist_ok=True)
previous_content = target.read_bytes() if target.exists() else None
try:
fd, temporary_name = tempfile.mkstemp(prefix=".aios-writer-", dir=target.parent)
temporary = Path(temporary_name)
try:
os.fchmod(fd, 0o640)
os.write(fd, request.content)
os.fsync(fd)
finally:
os.close(fd)
os.replace(temporary, target)
self._git("add", "--", normalized_path)
self._git("commit", "-m", request.message, "--", normalized_path)
commit_oid = self._head()
push = self._git(
"push", "--porcelain", self.remote,
f"HEAD:refs/heads/{request.branch}", check=False
)
if push.returncode:
try:
observed = self._remote_head(request.branch)
except WriterError:
observed = None
if observed == commit_oid:
state = "COMPLETED"
elif observed == request.expected_head_oid:
self._git("reset", "--hard", request.expected_head_oid)
state = "FAILED_RESTORED"
else:
state = "RECONCILIATION_REQUIRED"
else:
observed = self._remote_head(request.branch)
state = "COMPLETED" if observed == commit_oid else "RECONCILIATION_REQUIRED"
result = {
"state": state,
"operation_id": request.operation_id,
"previous_oid": request.expected_head_oid,
"commit_oid": commit_oid,
"remote_oid": observed,
"path": normalized_path,
}
self.ledger.finish(
request.operation_id, state,
commit_oid if state != "FAILED_RESTORED" else None,
json.dumps(result, sort_keys=True), int(time.time()),
)
self.audit.append(request.operation_id, {
"event": "WRITER_COMMIT_RESULT",
**result,
"secrets_included": False,
})
if state != "COMPLETED":
raise WriterError(state)
return result
except Exception:
if self._git("rev-parse", "HEAD", check=False).stdout.strip() != request.expected_head_oid:
self._git("reset", "--hard", request.expected_head_oid, check=False)
if previous_content is None:
target.unlink(missing_ok=True)
else:
target.write_bytes(previous_content)
self._git("reset", "--hard", request.expected_head_oid, check=False)
raise
@@ -0,0 +1,81 @@
from __future__ import annotations
import argparse, base64, json
from pathlib import Path
from typing import Any
from .common import HashChainAudit, safe_relpath
from .credentials import gitea_credential, git_http_auth_env
from .grant import GrantQuotaLedger, WriterGrant
from .ledger import OperationLedger
from .policy import DevelopmentPolicy
from .socket_service import JsonLineUnixServer
from .writer import CommitRequest, GovernedDevelopmentWriter
class WriterServerError(RuntimeError): pass
def under(path: Path, roots: list[Path]) -> bool:
resolved = path.resolve()
return any(resolved == root.resolve() or root.resolve() in resolved.parents for root in roots)
class WriterApplication:
def __init__(self, config: dict[str, Any]):
self.config = config
self.policy = DevelopmentPolicy.from_mapping(
json.loads(Path(config["policy_path"]).read_text(encoding="utf-8")))
self.grant = WriterGrant.load(Path(config["grant_path"]))
if self.grant.expected_policy_sha256 != self.policy.policy_sha256:
raise WriterServerError("GRANT_POLICY_DIGEST_MISMATCH")
self.quota = GrantQuotaLedger(Path(config["grant_quota_ledger"]))
self.worktree_roots = [Path(v) for v in config["worktree_roots"]]
self.username,self.token,_ = gitea_credential(config["credential_name"])
def dispatch(self, value: dict[str, Any], peer: dict[str,int]) -> dict[str,Any]:
required = {"task_id","operation_id","repository","branch","expected_head_oid",
"expected_base_oid","path","content_b64","content_sha256","message",
"actor_identity","writer_audience","worktree"}
if set(value) != required:
raise WriterServerError("REQUEST_SCHEMA_INVALID")
worktree = Path(value["worktree"])
if not worktree.is_absolute() or not under(worktree,self.worktree_roots):
raise WriterServerError("WORKTREE_ROOT_DENIED")
content = base64.b64decode(value["content_b64"], validate=True)
self.grant.validate_request(
task_id=value["task_id"],repository=value["repository"],branch=value["branch"],
actor_identity=value["actor_identity"],writer_audience=value["writer_audience"],
expected_base_oid=value["expected_base_oid"],operation="commit_file.atomic",
path=value["path"])
import hashlib
quota_payload_sha = hashlib.sha256(
json.dumps({
"task_id":value["task_id"],"repository":value["repository"],
"branch":value["branch"],"expected_head_oid":value["expected_head_oid"],
"expected_base_oid":value["expected_base_oid"],"path":value["path"],
"content_sha256":value["content_sha256"],"message":value["message"],
"actor_identity":value["actor_identity"],
"writer_audience":value["writer_audience"],
},sort_keys=True,separators=(",",":")).encode()
).hexdigest()
self.quota.consume(self.grant,value["operation_id"],quota_payload_sha,len(content))
request = CommitRequest(
task_id=value["task_id"],operation_id=value["operation_id"],
repository=value["repository"],branch=value["branch"],
expected_head_oid=value["expected_head_oid"],
expected_base_oid=value["expected_base_oid"],path=safe_relpath(value["path"]),
content=content,content_sha256=value["content_sha256"],message=value["message"],
actor_identity=value["actor_identity"],writer_audience=value["writer_audience"])
result = GovernedDevelopmentWriter(
worktree=worktree,remote=self.config["remote"],policy=self.policy,
ledger=OperationLedger(Path(self.config["operation_ledger"])),
audit=HashChainAudit(Path(self.config["audit_path"])),
git_env=git_http_auth_env(self.username,self.token)).commit_file(request)
return {**result,"peer_uid":peer["uid"],"grant_id":self.grant.grant_id,
"grant_sha256":self.grant.grant_sha256}
def main(argv: list[str] | None=None) -> int:
p=argparse.ArgumentParser(); p.add_argument("--config",type=Path,required=True)
args=p.parse_args(argv); config=json.loads(args.config.read_text(encoding="utf-8"))
app=WriterApplication(config)
server=JsonLineUnixServer(Path(config["socket_path"]),
allowed_uids={int(v) for v in config["allowed_peer_uids"]},
dispatch=app.dispatch,max_request_bytes=int(config.get("max_request_bytes",4194304)))
try: server.serve_forever()
finally: server.server_close()
return 0
@@ -0,0 +1,27 @@
[build-system]
requires = ["setuptools>=68"]
build-backend = "setuptools.build_meta"
[project]
name = "aios-autonomous-control-plane"
version = "0.2.0"
description = "Fail-closed AIOS autonomous development and release control plane"
requires-python = ">=3.11"
dependencies = []
[project.scripts]
aios-autonomous-control-plane = "aios_autonomous_control_plane.cli:main"
aios-development-writer-server = "aios_autonomous_control_plane.writer_server:main"
aios-governance-request-publisher = "aios_autonomous_control_plane.publisher:main"
aios-governance-approval-watcher = "aios_autonomous_control_plane.watcher_service:main"
aios-governance-mirror-worker = "aios_autonomous_control_plane.mirror_service:main"
aios-release-object-projector = "aios_autonomous_control_plane.projection_service:release_main"
aios-runtime-source-projector = "aios_autonomous_control_plane.projection_service:runtime_main"
aios-autonomous-renewal-controller = "aios_autonomous_control_plane.renewal_service:main"
aios-development-writer-revoker = "aios_autonomous_control_plane.revoker:main"
aios-v2r1c4-release-executor = "aios_autonomous_control_plane.release_service:main"
aios-release-hook = "aios_autonomous_control_plane.release_action:main"
[tool.setuptools.packages.find]
where = ["."]
include = ["aios_autonomous_control_plane*"]
@@ -0,0 +1,419 @@
"""Machine-verifiable acceptance contracts for dormant V2R1C4 delivery.
The contracts consume explicit evidence produced by a governed execution agent.
They never infer remote Git consistency, approval validity or artifact
provenance from a local checkout alone.
"""
from __future__ import annotations
from dataclasses import MISSING, dataclass
from enum import Enum
from typing import Any, Mapping
from .dormant import (
DeploymentMode,
DormantReleaseCandidate,
DormantReleaseGate,
ExecutionFlags,
FinalDormantRelease,
)
from .errors import ReleaseBlocked
from .models import is_git_oid, is_sha256, sha256_json
class AcceptanceStatus(str, Enum):
READY = "READY"
BLOCKED = "BLOCKED"
class AcceptancePhase(str, Enum):
CANDIDATE = "candidate"
FINAL = "final"
def _required_field_names(model: type[Any]) -> set[str]:
return {
name
for name, field in model.__dataclass_fields__.items()
if field.default is MISSING and field.default_factory is MISSING
}
def _strict_mapping(model: type[Any], value: Mapping[str, Any]) -> dict[str, Any]:
if not isinstance(value, Mapping):
raise ValueError("acceptance evidence must be a JSON object")
allowed = set(model.__dataclass_fields__)
unknown = sorted(set(value) - allowed)
if unknown:
raise ValueError(f"unknown acceptance fields: {', '.join(unknown)}")
missing = sorted(_required_field_names(model) - set(value))
if missing:
raise ValueError(f"missing acceptance fields: {', '.join(missing)}")
return dict(value)
def _require_bool(name: str, value: Any) -> None:
if not isinstance(value, bool):
raise ValueError(f"{name} must be a boolean")
def _require_non_negative_int(name: str, value: Any) -> None:
if isinstance(value, bool) or not isinstance(value, int) or value < 0:
raise ValueError(f"{name} must be a non-negative integer")
@dataclass(frozen=True, slots=True)
class DormantAcceptanceInput:
"""Immutable pre-merge evidence for the dormant Candidate package."""
version: str
pull_request: int
candidate_oid: str
candidate_tree_oid: str
integration_oid: str
integration_tree_oid: str
artifact_sha256: str
manifest_sha256: str
package_closure_sha256: str
evidence_closure_oid: str
rollback_artifact_sha256: str
production_config_sha256: str
test_evidence_sha256: str
tests_passed: int
tests_failed: int
audited_skips: int
candidate_four_way_ref_closure: bool
integration_four_way_ref_closure: bool
candidate_reachable_from_integration: bool
worktree_clean: bool
gitops_writes_enabled: bool
task_plane_execution_enabled: bool
silent_release_execution_enabled: bool
protected_dispatch_transport: str = "sandbox"
native_openclaw_enabled: bool = False
native_codex_enabled: bool = False
automatic_release_enabled: bool = False
production_side_effects: bool = False
activation_deferred: bool = True
deployment_mode: str = DeploymentMode.DORMANT_SHADOW_ONLY.value
def __post_init__(self) -> None:
if self.version != "V2R1C4":
raise ValueError("version must be V2R1C4")
if isinstance(self.pull_request, bool) or not isinstance(self.pull_request, int) or self.pull_request < 1:
raise ValueError("pull_request must be a positive integer")
for name in (
"candidate_oid",
"candidate_tree_oid",
"integration_oid",
"integration_tree_oid",
"evidence_closure_oid",
):
if not is_git_oid(getattr(self, name)):
raise ValueError(f"{name} must be a full lowercase 40-character Git OID")
for name in (
"artifact_sha256",
"manifest_sha256",
"package_closure_sha256",
"rollback_artifact_sha256",
"production_config_sha256",
"test_evidence_sha256",
):
if not is_sha256(getattr(self, name)):
raise ValueError(f"{name} must be a lowercase SHA-256 digest")
for name in ("tests_passed", "tests_failed", "audited_skips"):
_require_non_negative_int(name, getattr(self, name))
for name in (
"candidate_four_way_ref_closure",
"integration_four_way_ref_closure",
"candidate_reachable_from_integration",
"worktree_clean",
"gitops_writes_enabled",
"task_plane_execution_enabled",
"silent_release_execution_enabled",
"native_openclaw_enabled",
"native_codex_enabled",
"automatic_release_enabled",
"production_side_effects",
"activation_deferred",
):
_require_bool(name, getattr(self, name))
try:
DeploymentMode(self.deployment_mode)
except ValueError as exc:
raise ValueError("deployment_mode is invalid") from exc
@classmethod
def from_mapping(cls, value: Mapping[str, Any]) -> "DormantAcceptanceInput":
return cls(**_strict_mapping(cls, value))
def release_candidate(self) -> DormantReleaseCandidate:
return DormantReleaseCandidate(
version=self.version,
pull_request=self.pull_request,
integration_oid=self.integration_oid,
integration_tree_oid=self.integration_tree_oid,
artifact_sha256=self.artifact_sha256,
manifest_sha256=self.manifest_sha256,
package_closure_sha256=self.package_closure_sha256,
evidence_closure_oid=self.evidence_closure_oid,
rollback_artifact_sha256=self.rollback_artifact_sha256,
production_config_sha256=self.production_config_sha256,
execution_flags=ExecutionFlags(
gitops_writes_enabled=self.gitops_writes_enabled,
task_plane_execution_enabled=self.task_plane_execution_enabled,
silent_release_execution_enabled=self.silent_release_execution_enabled,
),
deployment_mode=DeploymentMode(self.deployment_mode),
protected_dispatch_transport=self.protected_dispatch_transport,
native_openclaw_enabled=self.native_openclaw_enabled,
native_codex_enabled=self.native_codex_enabled,
automatic_release_enabled=self.automatic_release_enabled,
production_side_effects=self.production_side_effects,
activation_deferred=self.activation_deferred,
)
def payload(self) -> Mapping[str, Any]:
return {name: getattr(self, name) for name in self.__dataclass_fields__}
@dataclass(frozen=True, slots=True)
class FinalDormantAcceptanceInput:
"""Post-merge evidence bound to the exact main merge commit and approval."""
candidate: DormantAcceptanceInput
target_oid: str
target_tree_oid: str
artifact_sha256: str
manifest_sha256: str
package_closure_sha256: str
evidence_closure_oid: str
rollback_artifact_sha256: str
production_config_sha256: str
approval_plan_id: str
approval_receipt_sha256: str
approved_release_digest: str
main_four_way_ref_closure: bool
target_is_main_head: bool
worktree_clean: bool
artifact_rebuilt_from_target: bool
rollback_verified: bool
runtime_boundary_verified: bool
owner_approval_verified: bool
def __post_init__(self) -> None:
if not isinstance(self.candidate, DormantAcceptanceInput):
raise ValueError("candidate must be DormantAcceptanceInput")
for name in ("target_oid", "target_tree_oid", "evidence_closure_oid"):
if not is_git_oid(getattr(self, name)):
raise ValueError(f"{name} must be a full lowercase 40-character Git OID")
for name in (
"artifact_sha256",
"manifest_sha256",
"package_closure_sha256",
"rollback_artifact_sha256",
"production_config_sha256",
"approval_receipt_sha256",
"approved_release_digest",
):
if not is_sha256(getattr(self, name)):
raise ValueError(f"{name} must be a lowercase SHA-256 digest")
if not self.approval_plan_id.strip():
raise ValueError("approval_plan_id is required")
for name in (
"main_four_way_ref_closure",
"target_is_main_head",
"worktree_clean",
"artifact_rebuilt_from_target",
"rollback_verified",
"runtime_boundary_verified",
"owner_approval_verified",
):
_require_bool(name, getattr(self, name))
@classmethod
def from_mapping(cls, value: Mapping[str, Any]) -> "FinalDormantAcceptanceInput":
data = _strict_mapping(cls, value)
candidate = data.get("candidate")
if isinstance(candidate, DormantAcceptanceInput):
parsed_candidate = candidate
elif isinstance(candidate, Mapping):
parsed_candidate = DormantAcceptanceInput.from_mapping(candidate)
else:
raise ValueError("candidate must be a JSON object")
data["candidate"] = parsed_candidate
return cls(**data)
def payload(self) -> Mapping[str, Any]:
return {
"candidate": dict(self.candidate.payload()),
**{
name: getattr(self, name)
for name in self.__dataclass_fields__
if name != "candidate"
},
}
@dataclass(frozen=True, slots=True)
class DormantAcceptanceResult:
phase: AcceptancePhase
status: AcceptanceStatus
acceptance_digest: str
release_digest: str | None
blocking_code: str | None
blocking_message: str | None
evidence: Mapping[str, Any]
def payload(self) -> Mapping[str, Any]:
return {
"phase": self.phase.value,
"status": self.status.value,
"acceptance_digest": self.acceptance_digest,
"release_digest": self.release_digest,
"blocking_code": self.blocking_code,
"blocking_message": self.blocking_message,
"evidence": dict(self.evidence),
}
class DormantAcceptanceGate:
"""Fail-closed pre-merge gate for a dormant release candidate."""
def verify(self, evidence: DormantAcceptanceInput) -> DormantReleaseCandidate:
if evidence.candidate_oid == evidence.integration_oid:
raise ReleaseBlocked(
"INTEGRATION_MERGE_REQUIRED",
"integration must be a distinct governed merge commit",
)
if not evidence.candidate_four_way_ref_closure:
raise ReleaseBlocked(
"CANDIDATE_REF_CLOSURE_REQUIRED",
"candidate local, bare, remote and Gitea references are not closed",
)
if not evidence.integration_four_way_ref_closure:
raise ReleaseBlocked(
"INTEGRATION_REF_CLOSURE_REQUIRED",
"integration local, bare, remote and Gitea references are not closed",
)
if not evidence.candidate_reachable_from_integration:
raise ReleaseBlocked(
"CANDIDATE_LINEAGE_REQUIRED",
"candidate is not reachable from the integration commit",
)
if not evidence.worktree_clean:
raise ReleaseBlocked("WORKTREE_NOT_CLEAN", "acceptance worktree contains uncommitted changes")
if evidence.tests_failed:
raise ReleaseBlocked("TEST_FAILURES_PRESENT", "candidate test failures are present")
if evidence.tests_passed < 1:
raise ReleaseBlocked("TEST_EVIDENCE_REQUIRED", "at least one passing test is required")
candidate = evidence.release_candidate()
DormantReleaseGate().verify_candidate(candidate)
return candidate
def evaluate(self, evidence: DormantAcceptanceInput) -> DormantAcceptanceResult:
acceptance_digest = sha256_json(evidence.payload())
try:
candidate = self.verify(evidence)
except (ReleaseBlocked, ValueError) as exc:
return DormantAcceptanceResult(
phase=AcceptancePhase.CANDIDATE,
status=AcceptanceStatus.BLOCKED,
acceptance_digest=acceptance_digest,
release_digest=None,
blocking_code=getattr(exc, "code", "INVALID_ACCEPTANCE_INPUT"),
blocking_message=str(exc),
evidence=evidence.payload(),
)
return DormantAcceptanceResult(
phase=AcceptancePhase.CANDIDATE,
status=AcceptanceStatus.READY,
acceptance_digest=acceptance_digest,
release_digest=candidate.digest(),
blocking_code=None,
blocking_message=None,
evidence=evidence.payload(),
)
class FinalDormantAcceptanceGate:
"""Fail-closed post-merge gate before deployment and tag execution."""
def verify(self, evidence: FinalDormantAcceptanceInput) -> FinalDormantRelease:
candidate = DormantAcceptanceGate().verify(evidence.candidate)
if not evidence.main_four_way_ref_closure:
raise ReleaseBlocked(
"MAIN_REF_CLOSURE_REQUIRED",
"main local, bare, remote and Gitea references are not closed",
)
if not evidence.target_is_main_head:
raise ReleaseBlocked("TARGET_NOT_MAIN_HEAD", "target OID is not the verified main HEAD")
if not evidence.worktree_clean:
raise ReleaseBlocked("WORKTREE_NOT_CLEAN", "final release worktree contains changes")
if not evidence.artifact_rebuilt_from_target:
raise ReleaseBlocked(
"ARTIFACT_REBUILD_REQUIRED",
"production artifact was not rebuilt from the final main target",
)
if not evidence.rollback_verified:
raise ReleaseBlocked("ROLLBACK_EVIDENCE_REQUIRED", "rollback package is not verified")
if not evidence.runtime_boundary_verified:
raise ReleaseBlocked(
"RUNTIME_BOUNDARY_REQUIRED",
"dormant runtime flags and protected transport are not verified",
)
final = DormantReleaseGate().finalize(
candidate,
target_oid=evidence.target_oid,
target_tree_oid=evidence.target_tree_oid,
artifact_sha256=evidence.artifact_sha256,
manifest_sha256=evidence.manifest_sha256,
package_closure_sha256=evidence.package_closure_sha256,
evidence_closure_oid=evidence.evidence_closure_oid,
rollback_artifact_sha256=evidence.rollback_artifact_sha256,
production_config_sha256=evidence.production_config_sha256,
)
if not evidence.owner_approval_verified:
raise ReleaseBlocked("OWNER_APPROVAL_REQUIRED", "exact final release approval is absent")
if evidence.approved_release_digest != final.digest():
raise ReleaseBlocked(
"APPROVAL_SUBJECT_MISMATCH",
"owner approval is not bound to the exact final release digest",
)
return final
def evaluate(self, evidence: FinalDormantAcceptanceInput) -> DormantAcceptanceResult:
acceptance_digest = sha256_json(evidence.payload())
try:
final = self.verify(evidence)
except (ReleaseBlocked, ValueError) as exc:
return DormantAcceptanceResult(
phase=AcceptancePhase.FINAL,
status=AcceptanceStatus.BLOCKED,
acceptance_digest=acceptance_digest,
release_digest=None,
blocking_code=getattr(exc, "code", "INVALID_ACCEPTANCE_INPUT"),
blocking_message=str(exc),
evidence=evidence.payload(),
)
return DormantAcceptanceResult(
phase=AcceptancePhase.FINAL,
status=AcceptanceStatus.READY,
acceptance_digest=acceptance_digest,
release_digest=final.digest(),
blocking_code=None,
blocking_message=None,
evidence=evidence.payload(),
)
__all__ = [
"AcceptancePhase",
"AcceptanceStatus",
"DormantAcceptanceInput",
"FinalDormantAcceptanceInput",
"DormantAcceptanceResult",
"DormantAcceptanceGate",
"FinalDormantAcceptanceGate",
]
@@ -0,0 +1,233 @@
"""Dormant/shadow-only release profile for fast, governed production delivery.
This module separates verified code deployment from capability activation. A
dormant release may install approved code in production while all autonomous
execution, native-agent and automatic-release capabilities remain fail-closed.
Activation is a later, independently approved operation.
"""
from __future__ import annotations
from dataclasses import dataclass
from enum import Enum
from typing import Any, Mapping
from .errors import ReleaseBlocked
from .models import is_git_oid, is_sha256, sha256_json
class DeploymentMode(str, Enum):
DORMANT_SHADOW_ONLY = "dormant_shadow_only"
ACTIVATED_PRODUCTION = "activated_production"
@dataclass(frozen=True, slots=True)
class ExecutionFlags:
"""Production execution switches captured in release evidence."""
gitops_writes_enabled: bool
task_plane_execution_enabled: bool
silent_release_execution_enabled: bool
def dormant(self) -> bool:
return not (
self.gitops_writes_enabled
or self.task_plane_execution_enabled
or self.silent_release_execution_enabled
)
def as_mapping(self) -> Mapping[str, bool]:
return {
"AIOS_GITOPS_WRITES_ENABLED": self.gitops_writes_enabled,
"AIOS_TASK_PLANE_EXECUTION_ENABLED": self.task_plane_execution_enabled,
"AIOS_SILENT_RELEASE_EXECUTION_ENABLED": self.silent_release_execution_enabled,
}
@dataclass(frozen=True, slots=True)
class DormantReleaseCandidate:
"""Immutable pre-merge release description for dormant production delivery."""
version: str
pull_request: int
integration_oid: str
integration_tree_oid: str
artifact_sha256: str
manifest_sha256: str
package_closure_sha256: str
evidence_closure_oid: str
rollback_artifact_sha256: str
production_config_sha256: str
execution_flags: ExecutionFlags
deployment_mode: DeploymentMode = DeploymentMode.DORMANT_SHADOW_ONLY
protected_dispatch_transport: str = "sandbox"
native_openclaw_enabled: bool = False
native_codex_enabled: bool = False
automatic_release_enabled: bool = False
production_side_effects: bool = False
activation_deferred: bool = True
def __post_init__(self) -> None:
if not self.version or self.pull_request < 1:
raise ValueError("version and a positive pull request number are required")
for name in ("integration_oid", "integration_tree_oid", "evidence_closure_oid"):
if not is_git_oid(getattr(self, name)):
raise ValueError(f"{name} must be a full lowercase 40-character Git OID")
for name in (
"artifact_sha256",
"manifest_sha256",
"package_closure_sha256",
"rollback_artifact_sha256",
"production_config_sha256",
):
if not is_sha256(getattr(self, name)):
raise ValueError(f"{name} must be a SHA-256 digest")
def payload(self) -> Mapping[str, Any]:
return {
"version": self.version,
"pull_request": self.pull_request,
"integration_oid": self.integration_oid,
"integration_tree_oid": self.integration_tree_oid,
"artifact_sha256": self.artifact_sha256,
"manifest_sha256": self.manifest_sha256,
"package_closure_sha256": self.package_closure_sha256,
"evidence_closure_oid": self.evidence_closure_oid,
"rollback_artifact_sha256": self.rollback_artifact_sha256,
"production_config_sha256": self.production_config_sha256,
"execution_flags": dict(self.execution_flags.as_mapping()),
"deployment_mode": self.deployment_mode.value,
"protected_dispatch_transport": self.protected_dispatch_transport,
"native_openclaw_enabled": self.native_openclaw_enabled,
"native_codex_enabled": self.native_codex_enabled,
"automatic_release_enabled": self.automatic_release_enabled,
"production_side_effects": self.production_side_effects,
"activation_deferred": self.activation_deferred,
"production_target_oid": "UNRESOLVED",
}
def digest(self) -> str:
return sha256_json(self.payload())
@dataclass(frozen=True, slots=True)
class FinalDormantRelease:
"""Post-merge release binding to the exact production target commit."""
candidate: DormantReleaseCandidate
target_oid: str
target_tree_oid: str
artifact_sha256: str
manifest_sha256: str
package_closure_sha256: str
evidence_closure_oid: str
rollback_artifact_sha256: str
production_config_sha256: str
def __post_init__(self) -> None:
for name in ("target_oid", "target_tree_oid", "evidence_closure_oid"):
if not is_git_oid(getattr(self, name)):
raise ValueError(f"{name} must be a full lowercase 40-character Git OID")
for name in (
"artifact_sha256",
"manifest_sha256",
"package_closure_sha256",
"rollback_artifact_sha256",
"production_config_sha256",
):
if not is_sha256(getattr(self, name)):
raise ValueError(f"{name} must be a SHA-256 digest")
def payload(self) -> Mapping[str, Any]:
return {
**dict(self.candidate.payload()),
"production_target_oid": self.target_oid,
"production_target_tree_oid": self.target_tree_oid,
"artifact_sha256": self.artifact_sha256,
"manifest_sha256": self.manifest_sha256,
"package_closure_sha256": self.package_closure_sha256,
"evidence_closure_oid": self.evidence_closure_oid,
"rollback_artifact_sha256": self.rollback_artifact_sha256,
"production_config_sha256": self.production_config_sha256,
}
def digest(self) -> str:
return sha256_json(self.payload())
class DormantReleaseGate:
"""Validate that a production deployment is dormant and side-effect free."""
_DORMANT_TRANSPORTS = frozenset({"mock", "sandbox"})
def verify_candidate(self, candidate: DormantReleaseCandidate) -> None:
if candidate.deployment_mode is not DeploymentMode.DORMANT_SHADOW_ONLY:
raise ReleaseBlocked("DORMANT_MODE_REQUIRED", "release is not dormant/shadow-only")
if not candidate.execution_flags.dormant():
raise ReleaseBlocked("DORMANT_FLAGS_REQUIRED", "all production execution flags must be false")
if candidate.protected_dispatch_transport not in self._DORMANT_TRANSPORTS:
raise ReleaseBlocked(
"DORMANT_TRANSPORT_REQUIRED",
"production dispatch transport must remain mock or sandbox",
)
if candidate.native_openclaw_enabled or candidate.native_codex_enabled:
raise ReleaseBlocked(
"NATIVE_AGENT_ACTIVATION_FORBIDDEN",
"native agents must remain disabled",
)
if candidate.automatic_release_enabled:
raise ReleaseBlocked(
"AUTOMATIC_RELEASE_FORBIDDEN",
"automatic release must remain disabled",
)
if candidate.production_side_effects:
raise ReleaseBlocked(
"PRODUCTION_SIDE_EFFECT_FORBIDDEN",
"dormant release cannot enable side effects",
)
if not candidate.activation_deferred:
raise ReleaseBlocked(
"ACTIVATION_MUST_BE_DEFERRED",
"capability activation requires a separate release",
)
def finalize(
self,
candidate: DormantReleaseCandidate,
*,
target_oid: str,
target_tree_oid: str,
artifact_sha256: str,
manifest_sha256: str,
package_closure_sha256: str,
evidence_closure_oid: str,
rollback_artifact_sha256: str,
production_config_sha256: str,
) -> FinalDormantRelease:
self.verify_candidate(candidate)
final = FinalDormantRelease(
candidate=candidate,
target_oid=target_oid,
target_tree_oid=target_tree_oid,
artifact_sha256=artifact_sha256,
manifest_sha256=manifest_sha256,
package_closure_sha256=package_closure_sha256,
evidence_closure_oid=evidence_closure_oid,
rollback_artifact_sha256=rollback_artifact_sha256,
production_config_sha256=production_config_sha256,
)
if final.target_oid == candidate.integration_oid:
raise ReleaseBlocked(
"FINAL_TARGET_NOT_MAIN_MERGE",
"final production target must be the governed main merge commit, not the integration commit",
)
return final
__all__ = [
"DeploymentMode",
"ExecutionFlags",
"DormantReleaseCandidate",
"FinalDormantRelease",
"DormantReleaseGate",
]
@@ -0,0 +1,78 @@
"""Dormant candidate worker process.
The worker intentionally performs no autonomous production action. It exists to
exercise lifecycle, heartbeat and shutdown behavior in candidate/shadow mode.
"""
from __future__ import annotations
import argparse
import json
import signal
import sys
import time
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
@dataclass(slots=True)
class WorkerRuntime:
worker_id: str
state_path: Path
heartbeat_seconds: float
running: bool = True
def stop(self, *_args: object) -> None:
self.running = False
def heartbeat(self) -> None:
payload = {
"worker_id": self.worker_id,
"mode": "candidate-shadow-only",
"production_side_effects": False,
"timestamp": datetime.now(timezone.utc).isoformat(),
}
self.state_path.parent.mkdir(parents=True, exist_ok=True)
temporary = self.state_path.with_suffix(self.state_path.suffix + ".tmp")
temporary.write_text(
json.dumps(payload, sort_keys=True, separators=(",", ":")),
encoding="utf-8",
)
temporary.chmod(0o600)
temporary.replace(self.state_path)
def run(self) -> int:
signal.signal(signal.SIGTERM, self.stop)
signal.signal(signal.SIGINT, self.stop)
while self.running:
self.heartbeat()
deadline = time.monotonic() + self.heartbeat_seconds
while self.running and time.monotonic() < deadline:
time.sleep(min(0.25, max(0.0, deadline - time.monotonic())))
self.heartbeat()
return 0
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(prog="aios-autonomous-release-worker")
parser.add_argument("--worker-id", required=True)
parser.add_argument("--state", type=Path, required=True)
parser.add_argument("--heartbeat-seconds", type=float, default=5.0)
return parser
def main(argv: list[str] | None = None) -> int:
args = build_parser().parse_args(argv)
if args.heartbeat_seconds < 1 or args.heartbeat_seconds > 60:
raise SystemExit("heartbeat interval must be between 1 and 60 seconds")
runtime = WorkerRuntime(
worker_id=args.worker_id,
state_path=args.state,
heartbeat_seconds=args.heartbeat_seconds,
)
return runtime.run()
if __name__ == "__main__":
sys.exit(main())
@@ -1,15 +1,12 @@
[build-system]
requires = ["setuptools>=69"]
requires = ["setuptools>=68"]
build-backend = "setuptools.build_meta"
[project]
name = "aios-autonomous-release-candidate"
version = "0.1.0"
description = "Candidate-only governed autonomous release control plane"
readme = "README.md"
description = "Dormant candidate orchestration and release governance for AIOS"
requires-python = ">=3.11"
license = { text = "Proprietary" }
authors = [{ name = "AIOS" }]
dependencies = []
[project.scripts]
@@ -19,7 +16,3 @@ aios-autonomous-release-service = "aios_autonomous_release_candidate.service:mai
[tool.setuptools.packages.find]
where = ["."]
include = ["aios_autonomous_release_candidate*"]
[tool.pytest.ini_options]
addopts = "-ra --strict-config --strict-markers"
testpaths = ["../../tests/autonomous_release"]
@@ -0,0 +1,51 @@
# AIOS Single-Owner Governance Prerequisites
This package is a dormant candidate produced under the PR #29 prerequisite
authorization. It validates exact-head Owner approvals and machine gates,
models protection-policy compare-and-swap with compensation, builds evidence,
and exercises every mutation path only against disposable fixtures.
The prerequisite shadow services remain static and refuse live mutation. This
branch adds a separate, one-shot activation executor with `--plan`,
`--activate`, `--verify`, `--rollback`, and `--status` commands. It is not an
always-on writer and never enables global GitOps writes.
The executor is fail-closed: every consequential command is bound to an exact
approval merge, governance OID, evidence closure, Owner review, component
digests, full branch-protection snapshot, operation ID, and plan ID. It writes
a fsynced WAL before the single-row SQLite CAS, verifies the full applied row,
and only rolls back with a target-to-previous CAS. Audit records are
hash-chained and credentials are redacted.
## Hardening Contract
Fresh activation is authorized only before the approved `expires_at` instant.
An already-recorded `PREPARED` transaction may recover after expiry only to
reach a safe terminal state using the same exact bindings; `--verify` and
`--rollback` remain available for that recorded transaction after expiry.
`ROLLED_BACK` and `FAILED` are terminal states and must never be replayed as a
new activation.
The approved execution binding must pin the implementation commit and its
exact tree, the installed hashes of the five runtime source files
(`activation.py`, `gitea_protected_branch.py`, `journal.py`, `validator.py`,
and `state_machine.py`), and independent SHA-256 values for the deployment
artifact, rollback artifact, and test evidence. It must also bind an evidence
closure that is a commit with the exact direct parent and exactly one approved
closure file, including that file's Git blob OID and SHA-256.
Every required machine status is accepted only when its exact status record,
creator identity, producer provenance, target URL, description digest, head
OID, state, and observation time match the approved binding. A fixture status
or a dormant self-check is not machine-status producer provenance.
This contract hardens validation; it does not authorize deployment. The
existing governed approval rules still apply, and installation remains
disabled and inactive until the required merged approval authorizes one
explicit execution.
Installation must remain disabled and inactive until a merged
`SINGLE_OWNER_GOVERNANCE_LIVE_ACTIVATION_IMPLEMENTATION_AND_EXECUTION`
approval authorizes one explicit execution. The approved runtime paths are
`/var/lib/aios-gitops/governance-activation/` and
`/run/aios-gitops/governance-activation.lock`.
@@ -0,0 +1,115 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"additionalProperties": false,
"properties": {
"activation_approval_merge_oid": {"pattern": "^[0-9a-f]{40}$", "type": "string"},
"activation_approval_pr_number": {"minimum": 1, "type": "integer"},
"activation_manifest_blob_oid": {"pattern": "^[0-9a-f]{40,64}$", "type": "string"},
"activation_manifest_path": {"pattern": "^approvals/.+\\.json$", "type": "string"},
"activation_manifest_sha256": {"pattern": "^[0-9a-f]{64}$", "type": "string"},
"artifact_path": {"pattern": "^/", "type": "string"},
"artifact_sha256": {"pattern": "^[0-9a-f]{64}$", "type": "string"},
"approval_db_path": {"minLength": 1, "type": "string"},
"approval_request_head_oid": {"pattern": "^[0-9a-f]{40}$", "type": "string"},
"backend": {"enum": ["file", "sqlite"]},
"backend_path": {"minLength": 1, "type": "string"},
"branch": {"const": "governance/approvals"},
"evidence_closure_oid": {"pattern": "^[0-9a-f]{40}$", "type": "string"},
"evidence_closure_path": {"pattern": "^(?!/)(?!.*\\.\\.).+", "type": "string"},
"evidence_closure_sha256": {"pattern": "^[0-9a-f]{64}$", "type": "string"},
"evidence_git_dir": {"minLength": 1, "type": "string"},
"executor": {"not": {"const": "owner"}, "type": "string"},
"expected_branch_protection_sha256": {"pattern": "^[0-9a-f]{64}$", "type": "string"},
"expected_gitea_schema_sha256": {"pattern": "^[0-9a-f]{64}$", "type": "string"},
"expected_governance_oid": {"pattern": "^[0-9a-f]{40}$", "type": "string"},
"expected_state_machine_sha256": {"pattern": "^[0-9a-f]{64}$", "type": "string"},
"expected_validator_sha256": {"pattern": "^[0-9a-f]{64}$", "type": "string"},
"expires_at": {"format": "date-time", "type": "string"},
"forbidden_operations": {"additionalProperties": {"const": false}, "type": "object"},
"governance_git_dir": {"minLength": 1, "type": "string"},
"governance_ref": {"const": "refs/heads/governance/approvals"},
"implementation_commit_oid": {"pattern": "^[0-9a-f]{40}$", "type": "string"},
"implementation_tree_oid": {"pattern": "^[0-9a-f]{40}$", "type": "string"},
"machine_status_producer_id": {"minimum": 1, "type": "integer"},
"machine_status_producer_name": {"minLength": 1, "type": "string"},
"machine_checks": {
"additionalProperties": false,
"properties": {
"aios/branch-protection": {"$ref": "#/$defs/machineCheck"},
"aios/evidence-closure": {"$ref": "#/$defs/machineCheck"},
"aios/secret-scan": {"$ref": "#/$defs/machineCheck"},
"aios/state-machine": {"$ref": "#/$defs/machineCheck"},
"aios/validator": {"$ref": "#/$defs/machineCheck"}
},
"required": ["aios/branch-protection", "aios/evidence-closure", "aios/secret-scan", "aios/state-machine", "aios/validator"],
"type": "object"
},
"observed_evidence_closure_oid": {"pattern": "^[0-9a-f]{40}$", "type": "string"},
"observed_governance_oid": {"pattern": "^[0-9a-f]{40}$", "type": "string"},
"observed_owner_review_oid": {"type": ["integer", "string"]},
"observed_state_machine_sha256": {"pattern": "^[0-9a-f]{64}$", "type": "string"},
"observed_validator_sha256": {"pattern": "^[0-9a-f]{64}$", "type": "string"},
"operation_id": {"minLength": 1, "type": "string"},
"owner_review_oid": {"type": ["integer", "string"]},
"repo_id": {"minimum": 1, "type": "integer"},
"requester": {"not": {"const": "owner"}, "type": "string"},
"rollback_artifact_path": {"pattern": "^/", "type": "string"},
"rollback_artifact_sha256": {"pattern": "^[0-9a-f]{64}$", "type": "string"},
"runtime_sources": {
"items": {
"additionalProperties": false,
"properties": {
"git_path": {"minLength": 1, "type": "string"},
"runtime_path": {"pattern": "^/", "type": "string"},
"sha256": {"pattern": "^[0-9a-f]{64}$", "type": "string"}
},
"required": ["git_path", "runtime_path", "sha256"],
"type": "object"
},
"maxItems": 5,
"minItems": 5,
"type": "array"
},
"state_machine_path": {"minLength": 1, "type": "string"},
"target_branch_protection": {"type": "object"},
"task_id": {"minLength": 1, "type": "string"},
"test_evidence_path": {"pattern": "^/", "type": "string"},
"test_evidence_sha256": {"pattern": "^[0-9a-f]{64}$", "type": "string"},
"validator_path": {"minLength": 1, "type": "string"},
"wip": {"const": false}
},
"$defs": {
"machineCheck": {
"additionalProperties": false,
"properties": {
"head_oid": {"pattern": "^[0-9a-f]{40}$", "type": "string"},
"status_id": {"minimum": 1, "type": "integer"},
"creator_id": {"minimum": 1, "type": "integer"},
"creator_login": {"minLength": 1, "type": "string"},
"state": {"const": "success"}
},
"required": ["state", "head_oid", "status_id", "creator_id", "creator_login"],
"type": "object"
}
},
"required": [
"task_id", "operation_id", "expected_governance_oid", "observed_governance_oid",
"expected_validator_sha256", "observed_validator_sha256", "validator_path",
"expected_state_machine_sha256", "observed_state_machine_sha256", "state_machine_path",
"expected_branch_protection_sha256", "expected_gitea_schema_sha256",
"activation_approval_merge_oid", "activation_approval_pr_number", "approval_request_head_oid",
"activation_manifest_path", "activation_manifest_blob_oid", "activation_manifest_sha256",
"evidence_closure_oid", "observed_evidence_closure_oid", "evidence_git_dir",
"evidence_closure_path", "evidence_closure_sha256",
"implementation_commit_oid", "implementation_tree_oid", "runtime_sources",
"artifact_path", "artifact_sha256", "rollback_artifact_path", "rollback_artifact_sha256",
"test_evidence_path", "test_evidence_sha256",
"machine_status_producer_id", "machine_status_producer_name",
"owner_review_oid", "observed_owner_review_oid", "approval_db_path", "expires_at",
"governance_git_dir", "governance_ref", "machine_checks", "forbidden_operations",
"target_branch_protection", "backend", "backend_path", "repo_id", "branch",
"requester", "executor", "wip"
],
"title": "AIOS Single Owner Live Activation Bindings",
"type": "object"
}
@@ -0,0 +1,20 @@
"""Dormant single-owner governance activation prerequisites."""
from .models import (
ApprovalContext,
GovernanceState,
MachineGate,
ProtectionSnapshot,
Review,
)
from .validator import SingleOwnerApprovalValidator, ValidationDenied
__all__ = [
"ApprovalContext",
"GovernanceState",
"MachineGate",
"ProtectionSnapshot",
"Review",
"SingleOwnerApprovalValidator",
"ValidationDenied",
]
@@ -0,0 +1,860 @@
from __future__ import annotations
import argparse
from contextlib import contextmanager
from datetime import datetime, timezone
import hashlib
import json
import os
from pathlib import Path
import re
import sqlite3
import subprocess
from typing import Any, Iterator, Mapping, Protocol
try:
import fcntl
except ImportError: # pragma: no cover
fcntl = None
from .gitea_protected_branch import (
CASConflict,
FileProtectedBranchAdapter,
SQLiteProtectedBranchAdapter,
calculate_snapshot_sha256,
)
from .journal import JournalStore, atomic_write_json, read_json, redact, sha256_json
from . import state_machine as state_machine_module
from . import validator as validator_module
DEFAULT_STATE_ROOT = Path("/var/lib/aios-gitops/governance-activation")
DEFAULT_LOCK_PATH = Path("/run/aios-gitops/governance-activation.lock")
LIVE_GITEA_DB = Path("/data/aios-gitops/runtime/gitea-bin/data/gitea.db")
LIVE_AUTHORITATIVE_GIT = Path("/data/aios-gitops/repo.git")
HEX40 = re.compile(r"^[0-9a-f]{40}$")
HEX64 = re.compile(r"^[0-9a-f]{64}$")
REQUIRED_BINDINGS = {
"task_id",
"operation_id",
"expected_governance_oid",
"expected_validator_sha256",
"expected_state_machine_sha256",
"expected_branch_protection_sha256",
"activation_approval_merge_oid",
"evidence_closure_oid",
"owner_review_oid",
"observed_governance_oid",
"observed_validator_sha256",
"observed_state_machine_sha256",
"observed_evidence_closure_oid",
"observed_owner_review_oid",
"approval_request_head_oid",
"activation_approval_pr_number",
"expires_at",
"validator_path",
"state_machine_path",
"governance_git_dir",
"governance_ref",
"evidence_git_dir",
"approval_db_path",
"expected_gitea_schema_sha256",
"backend",
"backend_path",
"repo_id",
"branch",
"activation_manifest_path",
"activation_manifest_blob_oid",
"activation_manifest_sha256",
"implementation_commit_oid",
"implementation_tree_oid",
"runtime_sources",
"artifact_path",
"artifact_sha256",
"rollback_artifact_path",
"rollback_artifact_sha256",
"test_evidence_path",
"test_evidence_sha256",
"evidence_closure_path",
"evidence_closure_sha256",
"machine_status_producer_id",
"machine_status_producer_name",
}
REQUIRED_MACHINE_CONTEXTS = {
"aios/validator",
"aios/state-machine",
"aios/branch-protection",
"aios/secret-scan",
"aios/evidence-closure",
}
REQUIRED_RUNTIME_GIT_PATHS = {
"services/aios-single-owner-governance/aios_single_owner_governance/activation.py",
"services/aios-single-owner-governance/aios_single_owner_governance/gitea_protected_branch.py",
"services/aios-single-owner-governance/aios_single_owner_governance/journal.py",
"services/aios-single-owner-governance/aios_single_owner_governance/state_machine.py",
"services/aios-single-owner-governance/aios_single_owner_governance/validator.py",
}
class InjectedCrash(BaseException):
pass
class Adapter(Protocol):
def read_current(self) -> dict[str, Any]: ...
def calculate_snapshot_sha256(self, snapshot: Mapping[str, Any]) -> str: ...
def apply_atomic(self, expected_sha256: str, target: Mapping[str, Any]) -> dict[str, Any]: ...
def verify_applied(self, expected_sha256: str) -> bool: ...
def rollback_atomic(self, expected_target_sha256: str, previous: Mapping[str, Any]) -> dict[str, Any]: ...
def verify_rollback(self, expected_sha256: str) -> bool: ...
@contextmanager
def exclusive_lock(path: Path) -> Iterator[None]:
path.parent.mkdir(parents=True, exist_ok=True, mode=0o750)
flags = os.O_RDWR | os.O_CREAT
if hasattr(os, "O_NOFOLLOW"):
flags |= os.O_NOFOLLOW
fd = os.open(path, flags, 0o640)
try:
os.fchmod(fd, 0o640)
if fcntl is not None:
fcntl.flock(fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
yield
finally:
if fcntl is not None:
fcntl.flock(fd, fcntl.LOCK_UN)
os.close(fd)
def _require_hex(name: str, value: object, lengths: tuple[int, ...]) -> None:
if not isinstance(value, str) or len(value) not in lengths or not re.fullmatch(r"[0-9a-f]+", value):
raise RuntimeError(f"{name.upper()}_INVALID")
def _sha256_file(path: Path) -> str:
if not path.is_absolute() or not path.is_file() or path.is_symlink():
raise RuntimeError("VERIFIED_COMPONENT_PATH_INVALID")
digest = hashlib.sha256()
with path.open("rb") as handle:
for block in iter(lambda: handle.read(1024 * 1024), b""):
digest.update(block)
return digest.hexdigest()
def _git(git_dir: Path, *args: str) -> str:
if not git_dir.is_absolute() or not git_dir.exists() or git_dir.is_symlink():
raise RuntimeError("GIT_OBJECT_STORE_INVALID")
result = subprocess.run(
["git", f"--git-dir={git_dir}", *args],
text=True,
capture_output=True,
check=False,
)
if result.returncode != 0:
raise RuntimeError("GIT_EVIDENCE_VERIFICATION_FAILED")
return result.stdout.strip()
def _git_raw(git_dir: Path, *args: str) -> bytes:
if not git_dir.is_absolute() or not git_dir.exists() or git_dir.is_symlink():
raise RuntimeError("GIT_OBJECT_STORE_INVALID")
result = subprocess.run(
["git", f"--git-dir={git_dir}", *args], capture_output=True, check=False
)
if result.returncode != 0:
raise RuntimeError("GIT_EVIDENCE_VERIFICATION_FAILED")
return result.stdout
def _parse_utc(value: object) -> datetime:
if not isinstance(value, str):
raise RuntimeError("EXPIRY_INVALID")
try:
parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
except ValueError as exc:
raise RuntimeError("EXPIRY_INVALID") from exc
if parsed.tzinfo is None:
raise RuntimeError("EXPIRY_INVALID")
return parsed.astimezone(timezone.utc)
def _utc_now() -> datetime:
return datetime.now(timezone.utc)
def _approved_execution_binding(bindings: Mapping[str, Any]) -> dict[str, Any]:
return {
"task_id": bindings["task_id"],
"operation_id": bindings["operation_id"],
"expected_validator_sha256": bindings["expected_validator_sha256"],
"expected_state_machine_sha256": bindings["expected_state_machine_sha256"],
"expected_branch_protection_sha256": bindings["expected_branch_protection_sha256"],
"expected_gitea_schema_sha256": bindings["expected_gitea_schema_sha256"],
"target_branch_protection_sha256": calculate_snapshot_sha256(
bindings["target_branch_protection"]
),
"evidence_closure_oid": bindings["evidence_closure_oid"],
"evidence_closure_path": bindings["evidence_closure_path"],
"evidence_closure_sha256": bindings["evidence_closure_sha256"],
"implementation_commit_oid": bindings["implementation_commit_oid"],
"implementation_tree_oid": bindings["implementation_tree_oid"],
"runtime_sources": bindings["runtime_sources"],
"artifact_path": bindings["artifact_path"],
"artifact_sha256": bindings["artifact_sha256"],
"rollback_artifact_path": bindings["rollback_artifact_path"],
"rollback_artifact_sha256": bindings["rollback_artifact_sha256"],
"test_evidence_path": bindings["test_evidence_path"],
"test_evidence_sha256": bindings["test_evidence_sha256"],
"machine_status_producer_id": bindings["machine_status_producer_id"],
"machine_status_producer_name": bindings["machine_status_producer_name"],
"backend": bindings["backend"],
"repo_id": bindings["repo_id"],
"branch": bindings["branch"],
"requester": bindings["requester"],
"executor": bindings["executor"],
"forbidden_operations": bindings["forbidden_operations"],
}
def _same_path(left: Path, right: Path) -> bool:
try:
return left.resolve(strict=True) == right.resolve(strict=True) and os.path.samefile(left, right)
except (FileNotFoundError, OSError):
return False
def _verify_authority_paths(bindings: Mapping[str, Any]) -> None:
if bindings["backend"] != "sqlite":
return
approval_db = Path(str(bindings["approval_db_path"]))
backend_db = Path(str(bindings["backend_path"]))
if not _same_path(approval_db, backend_db):
raise RuntimeError("APPROVAL_DB_BACKEND_DB_IDENTITY_MISMATCH")
live = _same_path(backend_db, LIVE_GITEA_DB)
if live:
if not _same_path(Path(str(bindings["governance_git_dir"])), LIVE_AUTHORITATIVE_GIT):
raise RuntimeError("AUTHORITATIVE_GOVERNANCE_GIT_REQUIRED")
if not _same_path(Path(str(bindings["evidence_git_dir"])), LIVE_AUTHORITATIVE_GIT):
raise RuntimeError("AUTHORITATIVE_EVIDENCE_GIT_REQUIRED")
return
acceptance_root = os.environ.get("AIOS_GOVERNANCE_ACTIVATION_ACCEPTANCE_ROOT")
if not acceptance_root:
raise RuntimeError("NON_LIVE_SQLITE_BACKEND_DENIED")
root = Path(acceptance_root).resolve(strict=True)
if not str(root).startswith("/run/aios-live-activation-acceptance-"):
raise RuntimeError("ACCEPTANCE_ROOT_INVALID")
for path in (approval_db, backend_db, Path(str(bindings["governance_git_dir"])), Path(str(bindings["evidence_git_dir"]))):
try:
path.resolve(strict=True).relative_to(root)
except (FileNotFoundError, ValueError) as exc:
raise RuntimeError("ACCEPTANCE_AUTHORITY_ESCAPE_DENIED") from exc
def _load_approved_manifest(bindings: Mapping[str, Any], git_dir: Path) -> dict[str, Any]:
path = str(bindings["activation_manifest_path"])
if not path.startswith("approvals/") or not path.endswith(".json") or ".." in Path(path).parts:
raise RuntimeError("ACTIVATION_MANIFEST_PATH_INVALID")
merge_oid = str(bindings["activation_approval_merge_oid"])
blob_oid = _git(git_dir, "rev-parse", f"{merge_oid}:{path}")
if blob_oid != bindings["activation_manifest_blob_oid"]:
raise RuntimeError("ACTIVATION_MANIFEST_BLOB_MISMATCH")
raw_result = subprocess.run(
["git", f"--git-dir={git_dir}", "show", f"{merge_oid}:{path}"],
capture_output=True,
check=False,
)
if raw_result.returncode != 0:
raise RuntimeError("ACTIVATION_MANIFEST_READ_FAILED")
if hashlib.sha256(raw_result.stdout).hexdigest() != bindings["activation_manifest_sha256"]:
raise RuntimeError("ACTIVATION_MANIFEST_SHA256_MISMATCH")
try:
manifest = json.loads(raw_result.stdout)
except (UnicodeDecodeError, json.JSONDecodeError) as exc:
raise RuntimeError("ACTIVATION_MANIFEST_INVALID") from exc
if not isinstance(manifest, dict):
raise RuntimeError("ACTIVATION_MANIFEST_INVALID")
return manifest
def _require_approval_unexpired(bindings: Mapping[str, Any]) -> None:
if _utc_now() >= _parse_utc(bindings["expires_at"]):
raise RuntimeError("APPROVAL_EXPIRED")
def _safe_git_path(value: object) -> str:
path = str(value)
if not path or Path(path).is_absolute() or ".." in Path(path).parts:
raise RuntimeError("APPROVED_GIT_PATH_INVALID")
return path
def _verify_approved_entities(bindings: Mapping[str, Any], git_dir: Path) -> None:
implementation_oid = str(bindings["implementation_commit_oid"])
_git(git_dir, "cat-file", "-e", f"{implementation_oid}^{{commit}}")
if _git(git_dir, "rev-parse", f"{implementation_oid}^{{tree}}") != bindings["implementation_tree_oid"]:
raise RuntimeError("IMPLEMENTATION_TREE_MISMATCH")
sources = bindings["runtime_sources"]
if not isinstance(sources, list) or not sources:
raise RuntimeError("RUNTIME_SOURCES_INVALID")
seen_git: set[str] = set()
seen_runtime: set[str] = set()
loaded_runtime_paths = {
"services/aios-single-owner-governance/aios_single_owner_governance/activation.py": Path(__file__).resolve(),
"services/aios-single-owner-governance/aios_single_owner_governance/gitea_protected_branch.py": Path(__import__(SQLiteProtectedBranchAdapter.__module__, fromlist=["x"]).__file__).resolve(),
"services/aios-single-owner-governance/aios_single_owner_governance/journal.py": Path(__import__(JournalStore.__module__, fromlist=["x"]).__file__).resolve(),
"services/aios-single-owner-governance/aios_single_owner_governance/state_machine.py": Path(state_machine_module.__file__).resolve(),
"services/aios-single-owner-governance/aios_single_owner_governance/validator.py": Path(validator_module.__file__).resolve(),
}
for source in sources:
if not isinstance(source, dict) or set(source) != {"git_path", "runtime_path", "sha256"}:
raise RuntimeError("RUNTIME_SOURCE_BINDING_INVALID")
git_path = _safe_git_path(source["git_path"])
runtime_path = str(source["runtime_path"])
_require_hex("runtime_source_sha256", source["sha256"], (64,))
if git_path in seen_git or runtime_path in seen_runtime:
raise RuntimeError("RUNTIME_SOURCE_BINDING_DUPLICATE")
seen_git.add(git_path)
seen_runtime.add(runtime_path)
if Path(runtime_path).resolve() != loaded_runtime_paths.get(git_path):
raise RuntimeError("RUNTIME_SOURCE_PATH_MISMATCH")
if hashlib.sha256(_git_raw(git_dir, "show", f"{implementation_oid}:{git_path}")).hexdigest() != source["sha256"]:
raise RuntimeError("IMPLEMENTATION_SOURCE_DIGEST_MISMATCH")
if _sha256_file(Path(runtime_path)) != source["sha256"]:
raise RuntimeError("RUNTIME_SOURCE_DIGEST_MISMATCH")
if seen_git != REQUIRED_RUNTIME_GIT_PATHS:
raise RuntimeError("RUNTIME_SOURCE_SET_MISMATCH")
for prefix in ("artifact", "rollback_artifact", "test_evidence"):
if _sha256_file(Path(str(bindings[f"{prefix}_path"]))) != bindings[f"{prefix}_sha256"]:
raise RuntimeError("APPROVED_ENTITY_DIGEST_MISMATCH")
closure_oid = str(bindings["evidence_closure_oid"])
if _git(git_dir, "cat-file", "-t", closure_oid) != "commit":
raise RuntimeError("EVIDENCE_CLOSURE_NOT_COMMIT")
parents = _git(git_dir, "rev-list", "--parents", "-n", "1", closure_oid).split()
if parents != [closure_oid, implementation_oid]:
raise RuntimeError("EVIDENCE_CLOSURE_PARENT_MISMATCH")
closure_path = _safe_git_path(bindings["evidence_closure_path"])
if hashlib.sha256(_git_raw(git_dir, "show", f"{closure_oid}:{closure_path}")).hexdigest() != bindings["evidence_closure_sha256"]:
raise RuntimeError("EVIDENCE_CLOSURE_SHA256_MISMATCH")
if _git(git_dir, "diff", "--name-status", implementation_oid, closure_oid) != f"A\t{closure_path}":
raise RuntimeError("EVIDENCE_CLOSURE_DIFF_INVALID")
def _verify_external_evidence(
bindings: Mapping[str, Any], *, require_unexpired: bool = True
) -> None:
_verify_authority_paths(bindings)
validator_sha = _sha256_file(Path(str(bindings["validator_path"])))
state_machine_sha = _sha256_file(Path(str(bindings["state_machine_path"])))
if validator_sha != bindings["expected_validator_sha256"] or validator_sha != bindings["observed_validator_sha256"]:
raise RuntimeError("VALIDATOR_DIGEST_MISMATCH")
if state_machine_sha != bindings["expected_state_machine_sha256"] or state_machine_sha != bindings["observed_state_machine_sha256"]:
raise RuntimeError("STATE_MACHINE_DIGEST_MISMATCH")
governance_git_dir = Path(str(bindings["governance_git_dir"]))
governance_oid = _git(governance_git_dir, "rev-parse", "--verify", str(bindings["governance_ref"]))
if governance_oid != bindings["expected_governance_oid"] or governance_oid != bindings["observed_governance_oid"]:
raise RuntimeError("WRONG_GOVERNANCE_OID_DENIED")
merge_oid = str(bindings["activation_approval_merge_oid"])
_git(governance_git_dir, "cat-file", "-e", f"{merge_oid}^{{commit}}")
if subprocess.run(
["git", f"--git-dir={governance_git_dir}", "merge-base", "--is-ancestor", merge_oid, governance_oid],
capture_output=True,
check=False,
).returncode != 0:
raise RuntimeError("ACTIVATION_APPROVAL_NOT_IN_GOVERNANCE_REF")
manifest = _load_approved_manifest(bindings, governance_git_dir)
if manifest.get("approval_type") != "SINGLE_OWNER_GOVERNANCE_LIVE_ACTIVATION_IMPLEMENTATION_AND_EXECUTION":
raise RuntimeError("ACTIVATION_APPROVAL_TYPE_MISMATCH")
if manifest.get("execution_binding") != _approved_execution_binding(bindings):
raise RuntimeError("APPROVED_EXECUTION_BINDING_MISMATCH")
if manifest.get("execution_binding_sha256") != sha256_json(manifest["execution_binding"]):
raise RuntimeError("APPROVED_EXECUTION_BINDING_SHA256_MISMATCH")
if manifest.get("expires_at") != bindings["expires_at"]:
raise RuntimeError("APPROVAL_EXPIRY_BINDING_MISMATCH")
manifest_bound_fields = (
"implementation_commit_oid", "implementation_tree_oid", "runtime_sources",
"artifact_path", "artifact_sha256", "rollback_artifact_path",
"rollback_artifact_sha256", "test_evidence_path", "test_evidence_sha256",
"evidence_closure_oid", "evidence_closure_path", "evidence_closure_sha256",
"machine_status_producer_id", "machine_status_producer_name",
)
if any(manifest.get(field) != bindings[field] for field in manifest_bound_fields):
raise RuntimeError("APPROVED_ARTIFACT_BINDING_MISMATCH")
evidence_git_dir = Path(str(bindings["evidence_git_dir"]))
evidence_oid = str(bindings["evidence_closure_oid"])
if evidence_oid != bindings["observed_evidence_closure_oid"]:
raise RuntimeError("WRONG_EVIDENCE_CLOSURE_DENIED")
_verify_approved_entities(bindings, evidence_git_dir)
db_path = Path(str(bindings["approval_db_path"]))
if not db_path.is_absolute() or not db_path.is_file() or db_path.is_symlink():
raise RuntimeError("APPROVAL_DATABASE_INVALID")
expiry = _parse_utc(bindings["expires_at"])
if require_unexpired:
_require_approval_unexpired(bindings)
with sqlite3.connect(f"file:{db_path}?mode=ro", uri=True) as connection:
connection.row_factory = sqlite3.Row
pr = connection.execute(
"SELECT pr.issue_id,pr.has_merged,pr.merged_commit_id,pr.merged_unix,i.is_closed "
"FROM pull_request pr JOIN issue i ON i.id=pr.issue_id "
"WHERE i.repo_id=? AND pr.\"index\"=? AND pr.base_branch=?",
(int(bindings["repo_id"]), int(bindings["activation_approval_pr_number"]), str(bindings["branch"])),
).fetchone()
if pr is None or int(pr["has_merged"]) != 1 or int(pr["is_closed"]) != 1:
raise RuntimeError("ACTIVATION_APPROVAL_PR_NOT_MERGED")
if pr["merged_commit_id"] != bindings["activation_approval_merge_oid"]:
raise RuntimeError("ACTIVATION_APPROVAL_MERGE_MISMATCH")
if int(pr["merged_unix"] or 0) <= 0 or datetime.fromtimestamp(int(pr["merged_unix"]), timezone.utc) > expiry:
raise RuntimeError("ACTIVATION_APPROVAL_MERGED_AFTER_EXPIRY")
review = connection.execute(
"SELECT r.id,r.type,r.reviewer_id,r.commit_id,r.stale,r.dismissed,r.created_unix,u.lower_name "
"FROM review r JOIN user u ON u.id=r.reviewer_id WHERE r.id=? AND r.issue_id=?",
(int(bindings["owner_review_oid"]), int(pr["issue_id"])),
).fetchone()
if review is None or int(review["id"]) != int(bindings["observed_owner_review_oid"]):
raise RuntimeError("WRONG_APPROVAL_OID_DENIED")
if (
int(review["type"]) != 1
or int(review["reviewer_id"]) != 2
or review["lower_name"] != "owner"
or review["commit_id"] != bindings["approval_request_head_oid"]
or int(review["stale"]) != 0
or int(review["dismissed"]) != 0
or datetime.fromtimestamp(int(review["created_unix"]), timezone.utc) >= expiry
):
raise RuntimeError("OWNER_APPROVAL_INVALID")
statuses = connection.execute(
"SELECT cs.id,cs.context,cs.state,cs.sha,cs.creator_id,u.lower_name AS creator_login "
"FROM commit_status cs LEFT JOIN user u ON u.id=cs.creator_id "
"WHERE cs.repo_id=? AND cs.sha=? ORDER BY cs.id DESC",
(int(bindings["repo_id"]), str(bindings["approval_request_head_oid"])),
).fetchall()
observed: dict[str, sqlite3.Row] = {}
for row in statuses:
if row["context"] in REQUIRED_MACHINE_CONTEXTS:
observed.setdefault(row["context"], row)
if set(observed) != REQUIRED_MACHINE_CONTEXTS:
raise RuntimeError("MACHINE_GATE_SET_MISMATCH")
for context, row in observed.items():
if row["state"] != "success" or row["sha"] != bindings["approval_request_head_oid"]:
raise RuntimeError("MACHINE_GATE_FAILED")
if (
int(row["creator_id"] or 0) != int(bindings["machine_status_producer_id"])
or row["creator_login"] != bindings["machine_status_producer_name"]
):
raise RuntimeError("MACHINE_GATE_PRODUCER_MISMATCH")
expected = bindings["machine_checks"][context]
actual = {
"state": row["state"], "head_oid": row["sha"], "status_id": int(row["id"]),
"creator_id": int(row["creator_id"]), "creator_login": row["creator_login"],
}
if expected != actual:
raise RuntimeError("MACHINE_GATE_BINDING_MISMATCH")
def validate_bindings(
bindings: Mapping[str, Any], *, require_unexpired: bool = True
) -> None:
missing = sorted(REQUIRED_BINDINGS - set(bindings))
if missing:
raise RuntimeError("MISSING_BINDINGS:" + ",".join(missing))
for name in (
"expected_governance_oid",
"activation_approval_merge_oid",
"evidence_closure_oid",
"implementation_commit_oid",
"implementation_tree_oid",
):
_require_hex(name, bindings[name], (40,))
for name in (
"expected_validator_sha256",
"expected_state_machine_sha256",
"expected_branch_protection_sha256",
"observed_validator_sha256",
"observed_state_machine_sha256",
"expected_gitea_schema_sha256",
"activation_manifest_sha256",
"artifact_sha256",
"rollback_artifact_sha256",
"test_evidence_sha256",
"evidence_closure_sha256",
):
_require_hex(name, bindings[name], (64,))
_require_hex("activation_manifest_blob_oid", bindings["activation_manifest_blob_oid"], (40, 64))
if not isinstance(bindings["owner_review_oid"], (int, str)) or str(bindings["owner_review_oid"]).strip() == "":
raise RuntimeError("OWNER_REVIEW_OID_INVALID")
if bindings["governance_ref"] != "refs/heads/governance/approvals":
raise RuntimeError("GOVERNANCE_REF_INVALID")
if bindings["branch"] != "governance/approvals":
raise RuntimeError("BRANCH_BINDING_DENIED")
if bindings["backend"] not in {"file", "sqlite"}:
raise RuntimeError("UNKNOWN_PROTECTION_BACKEND")
if bindings["observed_governance_oid"] != bindings["expected_governance_oid"]:
raise RuntimeError("WRONG_GOVERNANCE_OID_DENIED")
if bindings["observed_evidence_closure_oid"] != bindings["evidence_closure_oid"]:
raise RuntimeError("WRONG_EVIDENCE_CLOSURE_DENIED")
if str(bindings["observed_owner_review_oid"]) != str(bindings["owner_review_oid"]):
raise RuntimeError("WRONG_APPROVAL_OID_DENIED")
if bindings.get("requester") == "owner":
raise RuntimeError("REQUESTER_SELF_APPROVAL_DENIED")
if bindings.get("executor") == "owner":
raise RuntimeError("EXECUTOR_SELF_APPROVAL_DENIED")
if bindings.get("wip") is not False:
raise RuntimeError("WIP_BLOCKED")
checks = bindings.get("machine_checks")
if not isinstance(checks, dict) or set(checks) != REQUIRED_MACHINE_CONTEXTS:
raise RuntimeError("MACHINE_GATE_SET_MISMATCH")
if not isinstance(bindings["machine_status_producer_id"], int) or bindings["machine_status_producer_id"] < 1:
raise RuntimeError("MACHINE_STATUS_PRODUCER_INVALID")
if not isinstance(bindings["machine_status_producer_name"], str) or not bindings["machine_status_producer_name"]:
raise RuntimeError("MACHINE_STATUS_PRODUCER_INVALID")
for context, check in checks.items():
if (
not isinstance(check, dict)
or set(check) != {"state", "head_oid", "status_id", "creator_id", "creator_login"}
or check.get("state") != "success"
or check.get("head_oid") != bindings["approval_request_head_oid"]
or not isinstance(check.get("status_id"), int)
or check.get("status_id") < 1
or check.get("creator_id") != bindings["machine_status_producer_id"]
or check.get("creator_login") != bindings["machine_status_producer_name"]
):
raise RuntimeError(f"MACHINE_GATE_FAILED:{context}")
forbidden = bindings.get("forbidden_operations", {})
if any(bool(value) for value in forbidden.values()):
raise RuntimeError("FORBIDDEN_OPERATION_REQUESTED")
if os.environ.get("AIOS_GITOPS_WRITES_ENABLED", "false").lower() != "false":
raise RuntimeError("GLOBAL_GITOPS_WRITES_MUST_REMAIN_FALSE")
if not isinstance(bindings.get("target_branch_protection"), dict):
raise RuntimeError("TARGET_BRANCH_PROTECTION_REQUIRED")
_verify_external_evidence(bindings, require_unexpired=require_unexpired)
def binding_payload_hash(bindings: Mapping[str, Any]) -> str:
stable = dict(bindings)
stable.pop("crash_at", None)
return sha256_json(redact(stable))
def plan_id(bindings: Mapping[str, Any], previous: Mapping[str, Any], target: Mapping[str, Any]) -> str:
stable = dict(bindings)
stable.pop("crash_at", None)
return sha256_json(
{
"bindings": redact(stable),
"previous": dict(previous),
"target": dict(target),
}
)
class ActivationEngine:
def __init__(self, adapter: Adapter, state_root: Path, lock_path: Path) -> None:
self.adapter = adapter
self.journal = JournalStore(state_root)
self.lock_path = lock_path
def plan(self, bindings: Mapping[str, Any]) -> dict[str, Any]:
validate_bindings(bindings)
previous = self.adapter.read_current()
previous_sha = calculate_snapshot_sha256(previous)
if previous_sha != bindings["expected_branch_protection_sha256"]:
raise CASConflict("EXPECTED_BRANCH_PROTECTION_SHA256_MISMATCH")
target = dict(bindings["target_branch_protection"])
# Validate without writing by using the public target validator through a
# disposable comparison: adapters enforce the same checks again in CAS.
from .gitea_protected_branch import validate_target
validate_target(previous, target)
result = {
"status": "PLAN_READY",
"task_id": bindings["task_id"],
"operation_id": bindings["operation_id"],
"plan_id": plan_id(bindings, previous, target),
"payload_hash": binding_payload_hash(bindings),
"previous_sha256": previous_sha,
"target_sha256": calculate_snapshot_sha256(target),
}
return result
def _recover(self, record: dict[str, Any]) -> dict[str, Any]:
current = self.adapter.read_current()
current_sha = calculate_snapshot_sha256(current)
previous_sha = record["previous_sha256"]
target_sha = record["target_sha256"]
if record["phase"] == "PREPARED":
if current_sha == previous_sha:
return {**record, "recovery": "RESUME_BEFORE_APPLY"}
if current_sha == target_sha:
record = {**record, "phase": "APPLIED", "recovery": "RESUME_AFTER_APPLY"}
self.journal.write(record["operation_id"], record)
return record
raise RuntimeError("MANUAL_INTERVENTION_REQUIRED")
if record["phase"] in {"APPLIED", "COMMITTED"} and current_sha not in {target_sha, previous_sha}:
raise RuntimeError("MANUAL_INTERVENTION_REQUIRED")
return record
def _ensure_closure(self, record: dict[str, Any], event: str) -> None:
expected_sha = (
record["target_sha256"] if record["phase"] == "COMMITTED" else record["previous_sha256"]
)
if calculate_snapshot_sha256(self.adapter.read_current()) != expected_sha:
raise RuntimeError("MANUAL_INTERVENTION_REQUIRED")
atomic_write_json(
self.journal.root / "current-snapshot.json",
record["target"] if record["phase"] == "COMMITTED" else record["previous"],
)
atomic_write_json(self.journal.root / "rollback-snapshot.json", record["previous"])
self.journal.write_receipt(record["operation_id"], record)
if not self.journal.audit.has_event(record["operation_id"], event):
payload = {"event": event}
if event == "SINGLE_OWNER_ACTIVATION_COMMITTED":
payload.update(
payload_hash=record["payload_hash"],
target_sha256=record["target_sha256"],
)
self.journal.audit.append(record["operation_id"], payload)
atomic_write_json(self.journal.root / "state.json", record)
def activate(self, bindings: Mapping[str, Any], supplied_plan_id: str) -> dict[str, Any]:
with exclusive_lock(self.lock_path):
self.journal.audit.verify()
operation_id = str(bindings["operation_id"])
payload_hash = binding_payload_hash(bindings)
record = self.journal.load(operation_id)
if record:
if record.get("payload_hash") != payload_hash:
raise RuntimeError("OPERATION_ID_PAYLOAD_MISMATCH_DENIED")
if record.get("plan_id") != supplied_plan_id:
raise RuntimeError("PLAN_ID_MISMATCH")
if record.get("phase") == "ROLLED_BACK":
raise RuntimeError("OPERATION_TERMINAL_ROLLED_BACK")
if record.get("phase") == "FAILED":
raise RuntimeError("OPERATION_TERMINAL_FAILED")
prepared_at = _parse_utc(record.get("prepared_at"))
if prepared_at >= _parse_utc(bindings["expires_at"]):
raise RuntimeError("RECOVERY_AUTHORIZATION_INVALID")
record = self._recover(record)
if record["phase"] == "COMMITTED":
self._ensure_closure(record, "SINGLE_OWNER_ACTIVATION_COMMITTED")
return {"status": "ALREADY_COMMITTED", **record}
else:
validate_bindings(bindings)
planned = self.plan(bindings)
if supplied_plan_id != planned["plan_id"]:
raise RuntimeError("PLAN_ID_MISMATCH")
previous = self.adapter.read_current()
target = dict(bindings["target_branch_protection"])
if record is None:
if calculate_snapshot_sha256(previous) != planned["previous_sha256"]:
raise CASConflict("PREPARED_BOUNDARY_SNAPSHOT_CHANGED")
prepared_at = _utc_now()
if prepared_at >= _parse_utc(bindings["expires_at"]):
raise RuntimeError("APPROVAL_EXPIRED")
record = {
"task_id": bindings["task_id"],
"operation_id": operation_id,
"transaction_id": operation_id,
"payload_hash": payload_hash,
"plan_id": supplied_plan_id,
"phase": "PREPARED",
"prepared_at": prepared_at.isoformat().replace("+00:00", "Z"),
"previous": previous,
"target": target,
"previous_sha256": calculate_snapshot_sha256(previous),
"target_sha256": calculate_snapshot_sha256(target),
"activation_approval_merge_oid": bindings["activation_approval_merge_oid"],
"evidence_closure_oid": bindings["evidence_closure_oid"],
"owner_review_oid": bindings["owner_review_oid"],
}
self.journal.write(operation_id, record)
atomic_write_json(self.journal.root / "current-snapshot.json", previous)
atomic_write_json(self.journal.root / "rollback-snapshot.json", previous)
crash_at = bindings.get("crash_at")
if crash_at == "before_apply":
raise InjectedCrash("before_apply")
try:
current_sha = calculate_snapshot_sha256(self.adapter.read_current())
if current_sha == record["previous_sha256"]:
applied = self.adapter.apply_atomic(record["previous_sha256"], record["target"])
record["target"] = applied
record["target_sha256"] = calculate_snapshot_sha256(applied)
elif current_sha != record["target_sha256"]:
raise CASConflict("CAS_CONFLICT")
if crash_at == "during_apply":
raise InjectedCrash("during_apply")
record["phase"] = "APPLIED"
self.journal.write(operation_id, record)
if crash_at == "after_apply_before_receipt":
raise InjectedCrash("after_apply_before_receipt")
if not self.adapter.verify_applied(record["target_sha256"]):
raise RuntimeError("POST_ACTIVATION_VERIFY_FAILED")
record["phase"] = "COMMITTED"
record["single_owner_policy_active"] = True
record["break_glass_active"] = False
self.journal.write(operation_id, record)
if crash_at == "after_commit_wal":
raise InjectedCrash("after_commit_wal")
self._ensure_closure(record, "SINGLE_OWNER_ACTIVATION_COMMITTED")
return {"status": "ACTIVATED", **record}
except InjectedCrash:
raise
except Exception as exc:
failure = {**record, "phase": "FAILED", "error": type(exc).__name__}
current_sha = calculate_snapshot_sha256(self.adapter.read_current())
if current_sha == record["target_sha256"]:
self.adapter.rollback_atomic(record["target_sha256"], record["previous"])
if not self.adapter.verify_rollback(record["previous_sha256"]):
raise RuntimeError("ROLLBACK_VERIFY_FAILED") from exc
failure["phase"] = "ROLLED_BACK"
self.journal.write(operation_id, failure)
self.journal.write_receipt(operation_id, failure)
self.journal.audit.append(operation_id, {"event": failure["phase"], "error": type(exc).__name__})
raise
def verify(self, bindings: Mapping[str, Any], operation_id: str) -> dict[str, Any]:
record = self.journal.load(operation_id)
if not record:
raise RuntimeError("TRANSACTION_NOT_FOUND")
if record.get("payload_hash") != binding_payload_hash(bindings):
raise RuntimeError("OPERATION_ID_PAYLOAD_MISMATCH_DENIED")
self.journal.audit.verify()
current_sha = calculate_snapshot_sha256(self.adapter.read_current())
passed = record.get("phase") == "COMMITTED" and current_sha == record.get("target_sha256")
if not passed:
raise RuntimeError("ACTIVATION_VERIFY_FAILED")
return {"status": "VERIFIED", "operation_id": operation_id, "target_sha256": current_sha}
def rollback(
self, bindings: Mapping[str, Any], operation_id: str, supplied_plan_id: str
) -> dict[str, Any]:
with exclusive_lock(self.lock_path):
self.journal.audit.verify()
record = self.journal.load(operation_id)
if not record:
raise RuntimeError("TRANSACTION_NOT_FOUND")
if record.get("payload_hash") != binding_payload_hash(bindings):
raise RuntimeError("OPERATION_ID_PAYLOAD_MISMATCH_DENIED")
if record.get("plan_id") != supplied_plan_id:
raise RuntimeError("PLAN_ID_MISMATCH")
for field in (
"activation_approval_merge_oid",
"evidence_closure_oid",
"owner_review_oid",
):
if str(record.get(field)) != str(bindings[field]):
raise RuntimeError("ROLLBACK_AUTHORIZATION_MISMATCH_DENIED")
current_sha = calculate_snapshot_sha256(self.adapter.read_current())
if current_sha == record["previous_sha256"]:
record["phase"] = "ROLLED_BACK"
record["single_owner_policy_active"] = False
self.journal.write(operation_id, record)
self._ensure_closure(record, "ACTIVATION_ROLLED_BACK")
return {"status": "ALREADY_ROLLED_BACK", "operation_id": operation_id}
if current_sha != record["target_sha256"]:
raise RuntimeError("MANUAL_INTERVENTION_REQUIRED")
self.adapter.rollback_atomic(record["target_sha256"], record["previous"])
if not self.adapter.verify_rollback(record["previous_sha256"]):
raise RuntimeError("ROLLBACK_VERIFY_FAILED")
if bindings.get("crash_at") == "after_rollback_cas":
raise InjectedCrash("after_rollback_cas")
record["phase"] = "ROLLED_BACK"
record["single_owner_policy_active"] = False
self.journal.write(operation_id, record)
if bindings.get("crash_at") == "after_rollback_wal":
raise InjectedCrash("after_rollback_wal")
self._ensure_closure(record, "ACTIVATION_ROLLED_BACK")
return {"status": "ROLLED_BACK", "operation_id": operation_id}
def status(self, operation_id: str | None = None) -> dict[str, Any]:
audit_head = self.journal.audit.verify()
if operation_id:
record = self.journal.load(operation_id)
if not record:
raise RuntimeError("TRANSACTION_NOT_FOUND")
return {"status": "OK", "audit_head": audit_head, "transaction": record}
state_path = self.journal.root / "state.json"
return {
"status": "OK",
"audit_head": audit_head,
"state": read_json(state_path) if state_path.exists() else None,
"transactions": list(self.journal.iter_transactions()),
}
def load_bindings(path: Path) -> dict[str, Any]:
return read_json(path)
def adapter_from_bindings(bindings: Mapping[str, Any]) -> Adapter:
backend = bindings.get("backend", "file")
backend_path = Path(str(bindings.get("backend_path", "")))
if not backend_path.is_absolute():
raise RuntimeError("BACKEND_PATH_MUST_BE_ABSOLUTE")
if backend == "file":
return FileProtectedBranchAdapter(backend_path)
if backend == "sqlite":
return SQLiteProtectedBranchAdapter(
backend_path,
repo_id=int(bindings.get("repo_id", 1)),
branch=str(bindings.get("branch", "governance/approvals")),
expected_schema_sha256=str(bindings["expected_gitea_schema_sha256"]),
)
raise RuntimeError("UNKNOWN_PROTECTION_BACKEND")
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser()
action = parser.add_mutually_exclusive_group(required=True)
for name in ("plan", "activate", "verify", "rollback", "status"):
action.add_argument(f"--{name}", action="store_true")
parser.add_argument("--bindings", type=Path)
parser.add_argument("--plan-id")
parser.add_argument("--transaction")
parser.add_argument("--state-root", type=Path, default=DEFAULT_STATE_ROOT)
parser.add_argument("--lock-path", type=Path, default=DEFAULT_LOCK_PATH)
return parser
def main(argv: list[str] | None = None) -> int:
args = build_parser().parse_args(argv)
if args.status and args.bindings is None:
# Status without bindings is intentionally limited to durable local state.
journal = JournalStore(args.state_root)
result = {
"status": "OK",
"audit_head": journal.audit.verify(),
"transactions": list(journal.iter_transactions()),
}
print(json.dumps(result, sort_keys=True))
return 0
if args.bindings is None:
raise SystemExit("--bindings is required")
bindings = load_bindings(args.bindings)
engine = ActivationEngine(adapter_from_bindings(bindings), args.state_root, args.lock_path)
if args.plan:
result = engine.plan(bindings)
elif args.activate:
if not args.plan_id:
raise SystemExit("--plan-id is required")
result = engine.activate(bindings, args.plan_id)
elif args.verify:
result = engine.verify(bindings, args.transaction or str(bindings["operation_id"]))
elif args.rollback:
if not args.plan_id:
raise SystemExit("--plan-id is required")
result = engine.rollback(
bindings, args.transaction or str(bindings["operation_id"]), args.plan_id
)
else:
result = engine.status(args.transaction)
print(json.dumps(redact(result), sort_keys=True))
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,61 @@
from __future__ import annotations
from dataclasses import dataclass
import hashlib
import json
from typing import Mapping
def canonical_json(value: object) -> bytes:
return json.dumps(value, sort_keys=True, separators=(",", ":")).encode()
@dataclass(frozen=True)
class EvidenceClosure:
evidence: Mapping[str, str]
closure_sha256: str
class ActivationEvidenceClosureBuilder:
def build(self, evidence: Mapping[str, str]) -> EvidenceClosure:
required = {
"activation_prerequisites_commit_oid",
"activation_prerequisites_tree_oid",
"artifact_sha256",
"rollback_artifact_sha256",
"validator_sha256",
"state_machine_sha256",
"branch_protection_adapter_sha256",
"rollback_configuration_sha256",
"test_evidence_sha256",
"deployment_manifest_sha256",
"dormant_unit_manifest_sha256",
"dormant_deployment_evidence_sha256",
}
if set(evidence) != required:
raise ValueError("evidence set mismatch")
for value in evidence.values():
if len(value) != 40 and len(value) != 64:
raise ValueError("invalid evidence digest")
return EvidenceClosure(
dict(evidence), hashlib.sha256(canonical_json(evidence)).hexdigest()
)
class ApprovalMirrorStatusVerifier:
def verify(
self,
expected_oid: str,
*,
gitea_oid: str,
quarantine_oid: str,
authoritative_oid: str,
controller_visible_oid: str,
fsck_connectivity: str,
) -> bool:
return (
len(expected_oid) == 40
and {gitea_oid, quarantine_oid, authoritative_oid, controller_visible_oid}
== {expected_oid}
and fsck_connectivity == "PASS"
)
@@ -0,0 +1,26 @@
from __future__ import annotations
from contextlib import AbstractContextManager
from pathlib import Path
import shutil
import tempfile
from .models import ProtectionSnapshot
from .protection import MemoryProtectionStore
class DisposableGiteaFixture(AbstractContextManager):
def __init__(self, initial: ProtectionSnapshot) -> None:
self.root = Path(tempfile.mkdtemp(prefix="aios-governance-fixture-"))
self.protection = MemoryProtectionStore(initial)
self.destroyed = False
def __enter__(self) -> "DisposableGiteaFixture":
return self
def __exit__(self, exc_type, exc, tb) -> None:
resolved = self.root.resolve()
if not resolved.name.startswith("aios-governance-fixture-"):
raise RuntimeError("fixture cleanup path guard failed")
shutil.rmtree(resolved)
self.destroyed = True
@@ -0,0 +1,260 @@
from __future__ import annotations
import json
from pathlib import Path
import sqlite3
import time
from typing import Any, Mapping
from .journal import atomic_write_json, read_json, sha256_json
class CASConflict(RuntimeError):
pass
class UnsafePolicy(RuntimeError):
pass
def calculate_snapshot_sha256(snapshot: Mapping[str, Any]) -> str:
return sha256_json(dict(snapshot))
def validate_target(previous: Mapping[str, Any], target: Mapping[str, Any]) -> None:
if set(previous) != set(target):
raise UnsafePolicy("PROTECTION_ROW_SHAPE_CHANGED")
for field in ("id", "repo_id", "branch_name"):
if previous.get(field) != target.get(field):
raise UnsafePolicy(f"PROTECTION_IDENTITY_CHANGED:{field}")
if target.get("branch_name") != "governance/approvals":
raise UnsafePolicy("BRANCH_BINDING_DENIED")
if int(target.get("can_push", 1)) != 0:
raise UnsafePolicy("DIRECT_PUSH_MUST_REMAIN_DISABLED")
if int(target.get("required_approvals", 0)) != 1:
raise UnsafePolicy("EXACTLY_ONE_APPROVAL_REQUIRED")
if int(target.get("enable_approvals_whitelist", 0)) != 1:
raise UnsafePolicy("APPROVAL_WHITELIST_MUST_REMAIN_ENABLED")
if json.loads(target.get("approvals_whitelist_user_i_ds", "[]")) != [2]:
raise UnsafePolicy("OWNER_APPROVAL_WHITELIST_REQUIRED")
if int(target.get("enable_merge_whitelist", 0)) != 1:
raise UnsafePolicy("MERGE_WHITELIST_MUST_REMAIN_ENABLED")
if target.get("merge_whitelist_user_i_ds") != previous.get("merge_whitelist_user_i_ds"):
raise UnsafePolicy("MERGE_EXECUTOR_WHITELIST_CHANGE_DENIED")
if int(target.get("dismiss_stale_approvals", 0)) != 1:
raise UnsafePolicy("DISMISS_STALE_APPROVALS_REQUIRED")
if int(target.get("enable_status_check", 0)) != 1:
raise UnsafePolicy("STATUS_CHECKS_REQUIRED")
required_contexts = {
"aios/validator",
"aios/state-machine",
"aios/branch-protection",
"aios/secret-scan",
"aios/evidence-closure",
}
try:
contexts = set(json.loads(target.get("status_check_contexts", "[]")))
except (TypeError, ValueError) as exc:
raise UnsafePolicy("STATUS_CHECK_CONTEXTS_INVALID") from exc
if contexts != required_contexts:
raise UnsafePolicy("STATUS_CHECK_CONTEXTS_MISMATCH")
if int(target.get("block_on_rejected_reviews", 0)) != 1:
raise UnsafePolicy("REJECTED_REVIEWS_MUST_BLOCK")
if int(target.get("block_on_outdated_branch", 0)) != 1:
raise UnsafePolicy("OUTDATED_BRANCH_MUST_BLOCK")
if target.get("require_signed_commits") != previous.get("require_signed_commits"):
raise UnsafePolicy("SIGNATURE_POLICY_CHANGE_DENIED")
class FileProtectedBranchAdapter:
def __init__(self, path: Path) -> None:
self.path = path
@staticmethod
def calculate_snapshot_sha256(snapshot: Mapping[str, Any]) -> str:
return calculate_snapshot_sha256(snapshot)
def read_current(self) -> dict[str, Any]:
return read_json(self.path)
def compare_expected(self, expected_sha256: str) -> bool:
return calculate_snapshot_sha256(self.read_current()) == expected_sha256
def apply_atomic(
self, expected_sha256: str, target: Mapping[str, Any]
) -> dict[str, Any]:
previous = self.read_current()
if calculate_snapshot_sha256(previous) != expected_sha256:
raise CASConflict("CAS_CONFLICT")
validate_target(previous, target)
atomic_write_json(self.path, dict(target))
applied = self.read_current()
if calculate_snapshot_sha256(applied) != calculate_snapshot_sha256(target):
raise CASConflict("CAS_VERIFY_FAILED")
return applied
def verify_applied(self, expected_sha256: str) -> bool:
return self.compare_expected(expected_sha256)
def rollback_atomic(
self, expected_target_sha256: str, previous: Mapping[str, Any]
) -> dict[str, Any]:
current = self.read_current()
if calculate_snapshot_sha256(current) != expected_target_sha256:
raise CASConflict("ROLLBACK_CONFLICT")
atomic_write_json(self.path, dict(previous))
restored = self.read_current()
if calculate_snapshot_sha256(restored) != calculate_snapshot_sha256(previous):
raise CASConflict("ROLLBACK_VERIFY_FAILED")
return restored
def verify_rollback(self, expected_sha256: str) -> bool:
return self.compare_expected(expected_sha256)
class SQLiteProtectedBranchAdapter:
ALLOWED_UPDATE_FIELDS = (
"can_push",
"enable_merge_whitelist",
"merge_whitelist_user_i_ds",
"enable_status_check",
"status_check_contexts",
"enable_approvals_whitelist",
"approvals_whitelist_user_i_ds",
"required_approvals",
"block_on_rejected_reviews",
"block_on_official_review_requests",
"block_on_outdated_branch",
"dismiss_stale_approvals",
"ignore_stale_approvals",
"updated_unix",
)
def __init__(
self,
db_path: Path,
*,
repo_id: int = 1,
branch: str = "governance/approvals",
expected_schema_sha256: str,
) -> None:
self.db_path = db_path
self.repo_id = repo_id
self.branch = branch
self.expected_schema_sha256 = expected_schema_sha256
def _connect(self) -> sqlite3.Connection:
connection = sqlite3.connect(self.db_path, timeout=10)
connection.row_factory = sqlite3.Row
return connection
def schema_sha256(self, connection: sqlite3.Connection | None = None) -> str:
own = connection is None
connection = connection or self._connect()
try:
rows = [dict(row) for row in connection.execute("PRAGMA table_info(protected_branch)")]
return sha256_json(rows)
finally:
if own:
connection.close()
def _check_schema(self, connection: sqlite3.Connection) -> None:
if not isinstance(self.expected_schema_sha256, str) or len(self.expected_schema_sha256) != 64:
raise RuntimeError("GITEA_SCHEMA_FINGERPRINT_REQUIRED")
if self.schema_sha256(connection) != self.expected_schema_sha256:
raise RuntimeError("GITEA_SCHEMA_FINGERPRINT_MISMATCH")
def _read(self, connection: sqlite3.Connection) -> dict[str, Any]:
row = connection.execute(
"SELECT * FROM protected_branch WHERE repo_id=? AND branch_name=?",
(self.repo_id, self.branch),
).fetchone()
if row is None:
raise RuntimeError("PROTECTED_BRANCH_ROW_NOT_FOUND")
return dict(row)
def read_current(self) -> dict[str, Any]:
with self._connect() as connection:
self._check_schema(connection)
return self._read(connection)
@staticmethod
def calculate_snapshot_sha256(snapshot: Mapping[str, Any]) -> str:
return calculate_snapshot_sha256(snapshot)
def compare_expected(self, expected_sha256: str) -> bool:
return calculate_snapshot_sha256(self.read_current()) == expected_sha256
def apply_atomic(
self, expected_sha256: str, target: Mapping[str, Any]
) -> dict[str, Any]:
connection = self._connect()
try:
connection.execute("BEGIN IMMEDIATE")
self._check_schema(connection)
previous = self._read(connection)
if calculate_snapshot_sha256(previous) != expected_sha256:
raise CASConflict("CAS_CONFLICT")
validate_target(previous, target)
values = dict(target)
values["updated_unix"] = int(values.get("updated_unix") or time.time())
assignments = ",".join(f"{field}=?" for field in self.ALLOWED_UPDATE_FIELDS)
parameters = [values[field] for field in self.ALLOWED_UPDATE_FIELDS]
parameters.extend((self.repo_id, self.branch))
cursor = connection.execute(
f"UPDATE protected_branch SET {assignments} WHERE repo_id=? AND branch_name=?",
parameters,
)
if cursor.rowcount != 1:
raise CASConflict("CAS_ROWCOUNT_INVALID")
applied = self._read(connection)
expected_target = dict(target)
expected_target["updated_unix"] = values["updated_unix"]
if calculate_snapshot_sha256(applied) != calculate_snapshot_sha256(expected_target):
raise CASConflict("CAS_VERIFY_FAILED")
connection.commit()
return applied
except Exception:
connection.rollback()
raise
finally:
connection.close()
def verify_applied(self, expected_sha256: str) -> bool:
return self.compare_expected(expected_sha256)
def rollback_atomic(
self, expected_target_sha256: str, previous: Mapping[str, Any]
) -> dict[str, Any]:
connection = self._connect()
try:
connection.execute("BEGIN IMMEDIATE")
self._check_schema(connection)
current = self._read(connection)
if calculate_snapshot_sha256(current) != expected_target_sha256:
raise CASConflict("ROLLBACK_CONFLICT")
for field in ("id", "repo_id", "branch_name"):
if current.get(field) != previous.get(field):
raise CASConflict("ROLLBACK_IDENTITY_MISMATCH")
assignments = ",".join(f"{field}=?" for field in self.ALLOWED_UPDATE_FIELDS)
parameters = [previous[field] for field in self.ALLOWED_UPDATE_FIELDS]
parameters.extend((self.repo_id, self.branch))
cursor = connection.execute(
f"UPDATE protected_branch SET {assignments} WHERE repo_id=? AND branch_name=?",
parameters,
)
if cursor.rowcount != 1:
raise CASConflict("ROLLBACK_ROWCOUNT_INVALID")
restored = self._read(connection)
if calculate_snapshot_sha256(restored) != calculate_snapshot_sha256(previous):
raise CASConflict("ROLLBACK_VERIFY_FAILED")
connection.commit()
return restored
except Exception:
connection.rollback()
raise
finally:
connection.close()
def verify_rollback(self, expected_sha256: str) -> bool:
return self.compare_expected(expected_sha256)

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