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
119 changed files with 10186 additions and 10 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
}
@@ -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,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,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"
+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,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)
@@ -0,0 +1,189 @@
from __future__ import annotations
import hashlib
import json
import os
from pathlib import Path
import tempfile
from typing import Any, Iterable
SENSITIVE_FRAGMENTS = (
"authorization",
"cookie",
"credential",
"password",
"private_key",
"secret",
"token",
)
def canonical_json(value: object) -> bytes:
return json.dumps(value, sort_keys=True, separators=(",", ":")).encode("utf-8")
def sha256_json(value: object) -> str:
return hashlib.sha256(canonical_json(value)).hexdigest()
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_state_directory(path: Path) -> None:
if path.is_symlink():
raise RuntimeError("STATE_DIRECTORY_SYMLINK_DENIED")
path.mkdir(parents=True, exist_ok=True, mode=0o750)
path.chmod(0o750)
mode = path.stat().st_mode & 0o777
if mode != 0o750:
raise RuntimeError("STATE_DIRECTORY_MODE_INVALID")
def atomic_write_json(path: Path, value: object) -> None:
ensure_state_directory(path.parent)
raw = json.dumps(redact(value), sort_keys=True, indent=2).encode("utf-8") + b"\n"
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(0o640)
os.replace(temporary, path)
path.chmod(0o640)
directory_fd = os.open(path.parent, os.O_RDONLY)
try:
os.fsync(directory_fd)
finally:
os.close(directory_fd)
def read_json(path: Path) -> dict[str, Any]:
raw = json.loads(path.read_text(encoding="utf-8"))
if not isinstance(raw, dict):
raise RuntimeError("STATE_DOCUMENT_NOT_OBJECT")
return raw
class AuditChain:
def __init__(self, path: Path) -> None:
self.path = path
def _records(self) -> list[dict[str, Any]]:
if not self.path.exists():
return []
records: list[dict[str, Any]] = []
for line in self.path.read_text(encoding="utf-8").splitlines():
if line.strip():
record = json.loads(line)
if not isinstance(record, dict):
raise RuntimeError("AUDIT_RECORD_INVALID")
records.append(record)
return records
def verify(self) -> str:
previous = "0" * 64
for index, record in enumerate(self._records(), 1):
payload = record.get("payload")
if record.get("sequence") != index:
raise RuntimeError("AUDIT_SEQUENCE_INVALID")
if record.get("previous_event_hash") != previous:
raise RuntimeError("AUDIT_PREVIOUS_HASH_INVALID")
payload_hash = sha256_json(payload)
if record.get("payload_hash") != payload_hash:
raise RuntimeError("AUDIT_PAYLOAD_HASH_INVALID")
material = {
"sequence": index,
"previous_event_hash": previous,
"payload_hash": payload_hash,
"transaction_id": record.get("transaction_id"),
}
event_hash = sha256_json(material)
if record.get("event_hash") != event_hash:
raise RuntimeError("AUDIT_EVENT_HASH_INVALID")
previous = event_hash
return previous
def append(self, transaction_id: str, payload: dict[str, Any]) -> dict[str, Any]:
ensure_state_directory(self.path.parent)
previous = self.verify()
sequence = len(self._records()) + 1
clean_payload = redact(payload)
payload_hash = sha256_json(clean_payload)
material = {
"sequence": sequence,
"previous_event_hash": previous,
"payload_hash": payload_hash,
"transaction_id": transaction_id,
}
record = {
**material,
"payload": clean_payload,
"event_hash": sha256_json(material),
}
fd = os.open(self.path, os.O_WRONLY | os.O_APPEND | os.O_CREAT, 0o640)
try:
os.write(fd, canonical_json(record) + b"\n")
os.fsync(fd)
finally:
os.close(fd)
self.path.chmod(0o640)
return record
def has_event(self, transaction_id: str, event: str) -> bool:
self.verify()
return any(
record.get("transaction_id") == transaction_id
and isinstance(record.get("payload"), dict)
and record["payload"].get("event") == event
for record in self._records()
)
class JournalStore:
def __init__(self, root: Path) -> None:
self.root = root
self.wal = root / "wal"
self.receipts = root / "receipts"
ensure_state_directory(root)
ensure_state_directory(self.wal)
ensure_state_directory(self.receipts)
self.audit = AuditChain(root / "audit.jsonl")
def transaction_path(self, operation_id: str) -> Path:
if not operation_id or any(char not in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789._-" for char in operation_id):
raise RuntimeError("OPERATION_ID_INVALID")
return self.wal / f"{operation_id}.json"
def receipt_path(self, operation_id: str) -> Path:
return self.receipts / f"{operation_id}.json"
def load(self, operation_id: str) -> dict[str, Any] | None:
path = self.transaction_path(operation_id)
return read_json(path) if path.exists() else None
def write(self, operation_id: str, value: dict[str, Any]) -> None:
atomic_write_json(self.transaction_path(operation_id), value)
def write_receipt(self, operation_id: str, value: dict[str, Any]) -> None:
atomic_write_json(self.receipt_path(operation_id), value)
def iter_transactions(self) -> Iterable[dict[str, Any]]:
for path in sorted(self.wal.glob("*.json")):
yield read_json(path)
@@ -0,0 +1,47 @@
from __future__ import annotations
from pathlib import Path
try:
import fcntl
except ImportError: # pragma: no cover - exercised by Windows validation
fcntl = None
import msvcrt
class MaintenanceLock:
"""Process fence used only by fixture and later approved activation executors."""
def __init__(self, path: Path) -> None:
self.path = path
self._handle = None
def __enter__(self) -> "MaintenanceLock":
self.path.parent.mkdir(parents=True, exist_ok=True)
self._handle = self.path.open("a+")
if fcntl is not None:
try:
fcntl.flock(self._handle.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
except BlockingIOError:
self._handle.close()
self._handle = None
raise
else:
self._handle.seek(0)
try:
msvcrt.locking(self._handle.fileno(), msvcrt.LK_NBLCK, 1)
except OSError as exc:
self._handle.close()
self._handle = None
raise BlockingIOError("maintenance lock held") from exc
return self
def __exit__(self, exc_type, exc, tb) -> None:
if self._handle is not None:
if fcntl is not None:
fcntl.flock(self._handle.fileno(), fcntl.LOCK_UN)
else:
self._handle.seek(0)
msvcrt.locking(self._handle.fileno(), msvcrt.LK_UNLCK, 1)
self._handle.close()
self._handle = None
@@ -0,0 +1,90 @@
from __future__ import annotations
from dataclasses import dataclass, field
from datetime import datetime, timezone
from enum import Enum
from typing import Any, Mapping
class GovernanceState(str, Enum):
HUMAN_OWNER_APPROVAL_PENDING = "HUMAN_OWNER_APPROVAL_PENDING"
OWNER_APPROVAL_VERIFIED = "OWNER_APPROVAL_VERIFIED"
MACHINE_GATES_VERIFIED = "MACHINE_GATES_VERIFIED"
AUTO_MERGE_READY = "AUTO_MERGE_READY"
OWNER_APPROVED_EXECUTION_READY = "OWNER_APPROVED_EXECUTION_READY"
@dataclass(frozen=True)
class Review:
reviewer: str
reviewer_id: int
state: str
head_oid: str
reviewed_at: datetime
stale: bool = False
dismissed: bool = False
def __post_init__(self) -> None:
if self.reviewed_at.tzinfo is None:
raise ValueError("reviewed_at must be timezone-aware")
@dataclass(frozen=True)
class MachineGate:
context: str
state: str
head_oid: str
observed_at: datetime
def __post_init__(self) -> None:
if self.observed_at.tzinfo is None:
raise ValueError("observed_at must be timezone-aware")
@dataclass(frozen=True)
class ApprovalContext:
current_head_oid: str
requester: str
executor: str
owner: str
owner_id: int
reviews: tuple[Review, ...]
machine_gates: tuple[MachineGate, ...]
required_machine_contexts: tuple[str, ...]
expires_at: datetime
wip: bool
now: datetime = field(default_factory=lambda: datetime.now(timezone.utc))
def __post_init__(self) -> None:
if self.expires_at.tzinfo is None or self.now.tzinfo is None:
raise ValueError("approval timestamps must be timezone-aware")
@dataclass(frozen=True)
class ProtectionSnapshot:
branch: str
required_approvals: int
approval_whitelist: tuple[str, ...]
merge_whitelist: tuple[str, ...]
dismiss_stale_approvals: bool
require_status_checks: bool
status_contexts: tuple[str, ...]
direct_push: bool
force_push: bool
branch_deletion: bool
version: int
def as_dict(self) -> Mapping[str, Any]:
return {
"branch": self.branch,
"required_approvals": self.required_approvals,
"approval_whitelist": list(self.approval_whitelist),
"merge_whitelist": list(self.merge_whitelist),
"dismiss_stale_approvals": self.dismiss_stale_approvals,
"require_status_checks": self.require_status_checks,
"status_contexts": list(self.status_contexts),
"direct_push": self.direct_push,
"force_push": self.force_push,
"branch_deletion": self.branch_deletion,
"version": self.version,
}
@@ -0,0 +1,23 @@
from __future__ import annotations
class OperationDenied(PermissionError):
pass
class DormantOperationGuard:
"""Fail-closed operation boundary for the prerequisite deployment."""
DENIED = frozenset({
"protected_branch.direct_push",
"governance.direct_push",
"tag.direct_write",
"git.force_push",
"production.without_owner",
"branch_protection.live_update",
"single_owner_policy.activate",
})
def authorize(self, operation: str, *, fixture: bool = False) -> None:
if operation in self.DENIED or not fixture:
raise OperationDenied(operation)
@@ -0,0 +1,98 @@
from __future__ import annotations
from dataclasses import dataclass
import hashlib
import json
from typing import Protocol
from .models import ProtectionSnapshot
class CASConflict(RuntimeError):
pass
class DormantViolation(RuntimeError):
pass
def snapshot_sha256(snapshot: ProtectionSnapshot) -> str:
raw = json.dumps(snapshot.as_dict(), sort_keys=True, separators=(",", ":")).encode()
return hashlib.sha256(raw).hexdigest()
class ProtectionStore(Protocol):
fixture: bool
def read(self, branch: str) -> ProtectionSnapshot: ...
def compare_and_swap(
self, branch: str, expected_sha256: str, target: ProtectionSnapshot
) -> bool: ...
@dataclass(frozen=True)
class ProtectionPlan:
branch: str
expected_previous_sha256: str
previous: ProtectionSnapshot
target: ProtectionSnapshot
target_sha256: str
class BranchProtectionCASMigrationAdapter:
"""Candidate adapter. Mutations are restricted to disposable fixtures."""
def plan(
self,
current: ProtectionSnapshot,
target: ProtectionSnapshot,
expected_previous_sha256: str,
) -> ProtectionPlan:
actual = snapshot_sha256(current)
if actual != expected_previous_sha256:
raise CASConflict("EXPECTED_PREVIOUS_SHA256_MISMATCH")
if current.branch != target.branch:
raise CASConflict("BRANCH_BINDING_MISMATCH")
if target.direct_push or target.force_push or target.branch_deletion:
raise DormantViolation("UNSAFE_TARGET_POLICY")
return ProtectionPlan(
current.branch, actual, current, target, snapshot_sha256(target)
)
def apply_fixture(self, store: ProtectionStore, plan: ProtectionPlan) -> ProtectionSnapshot:
if not store.fixture:
raise DormantViolation("LIVE_PROTECTION_MUTATION_DENIED")
if not store.compare_and_swap(
plan.branch, plan.expected_previous_sha256, plan.target
):
raise CASConflict("CONCURRENT_PROTECTION_CHANGE")
return store.read(plan.branch)
def rollback_fixture(self, store: ProtectionStore, plan: ProtectionPlan) -> ProtectionSnapshot:
if not store.fixture:
raise DormantViolation("LIVE_PROTECTION_ROLLBACK_DENIED")
if not store.compare_and_swap(plan.branch, plan.target_sha256, plan.previous):
raise CASConflict("ROLLBACK_CONFLICT")
return store.read(plan.branch)
class MemoryProtectionStore:
fixture = True
def __init__(self, snapshot: ProtectionSnapshot) -> None:
self._snapshot = snapshot
def read(self, branch: str) -> ProtectionSnapshot:
if self._snapshot.branch != branch:
raise KeyError(branch)
return self._snapshot
def compare_and_swap(
self, branch: str, expected_sha256: str, target: ProtectionSnapshot
) -> bool:
if self._snapshot.branch != branch:
return False
if snapshot_sha256(self._snapshot) != expected_sha256:
return False
self._snapshot = target
return True
@@ -0,0 +1,69 @@
from __future__ import annotations
from dataclasses import dataclass
import hashlib
import json
from pathlib import Path
import tempfile
from .models import ProtectionSnapshot
from .protection import MemoryProtectionStore, ProtectionPlan, snapshot_sha256
@dataclass(frozen=True)
class RollbackConfiguration:
branch: str
previous_sha256: str
target_sha256: str
payload: dict
class RollbackConfigurationManager:
def build(self, plan: ProtectionPlan) -> RollbackConfiguration:
return RollbackConfiguration(
plan.branch,
plan.expected_previous_sha256,
plan.target_sha256,
{"previous": plan.previous.as_dict(), "target": plan.target.as_dict()},
)
def install_fixture(self, root: Path, config: RollbackConfiguration) -> Path:
if not root.name.startswith("aios-governance-fixture-"):
raise PermissionError("rollback configuration may only be installed in fixture root")
root.mkdir(parents=True, exist_ok=True)
destination = root / "rollback-configuration.json"
raw = json.dumps(config.payload, sort_keys=True, indent=2).encode() + b"\n"
with tempfile.NamedTemporaryFile(dir=root, prefix=".rollback.", delete=False) as handle:
handle.write(raw)
handle.flush()
temporary = Path(handle.name)
temporary.chmod(0o600)
temporary.replace(destination)
return destination
def verify(self, config: RollbackConfiguration, plan: ProtectionPlan) -> bool:
return (
config.previous_sha256 == snapshot_sha256(plan.previous)
and config.target_sha256 == snapshot_sha256(plan.target)
)
@dataclass
class ProtectionCompensationSaga:
adapter: object
store: MemoryProtectionStore
plan: ProtectionPlan
applied: bool = False
rolled_back: bool = False
def execute_fixture(self) -> ProtectionSnapshot:
result = self.adapter.apply_fixture(self.store, self.plan)
self.applied = True
return result
def compensate_fixture(self) -> ProtectionSnapshot:
if not self.applied:
return self.store.read(self.plan.branch)
result = self.adapter.rollback_fixture(self.store, self.plan)
self.rolled_back = True
return result
@@ -0,0 +1,69 @@
from __future__ import annotations
import argparse
import json
import os
from pathlib import Path
import tempfile
def runtime_state(component: str, prior_invocations: int = 0) -> dict:
return {
"component": component,
"deployment_mode": "DORMANT_SHADOW_ONLY",
"enabled": False,
"active": False,
"production_routing": False,
"production_mutation": False,
"global_gitops_writes_enabled": False,
"prior_invocations": prior_invocations,
"restart_recovery": prior_invocations > 0,
}
def checkpoint_state(component: str, root: Path) -> dict:
root.mkdir(parents=True, exist_ok=True)
destination = root / f"{component}.json"
prior = 0
if destination.exists():
previous = json.loads(destination.read_text(encoding="utf-8"))
if previous.get("component") != component or previous.get("deployment_mode") != "DORMANT_SHADOW_ONLY":
raise RuntimeError("checkpoint identity mismatch")
prior = int(previous.get("invocations", 0))
state = runtime_state(component, prior)
checkpoint = {**state, "invocations": prior + 1}
with tempfile.NamedTemporaryFile("w", encoding="utf-8", dir=root, delete=False) as handle:
json.dump(checkpoint, handle, sort_keys=True)
handle.write("\n")
handle.flush()
temporary = Path(handle.name)
temporary.replace(destination)
return state
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--component", required=True, choices=("owner-review-adapter", "machine-status-producer"))
parser.add_argument("--self-check", action="store_true")
parser.add_argument("--activate", action="store_true")
parser.add_argument("--checkpoint-dir", type=Path)
args = parser.parse_args(argv)
if args.activate:
parser.error("activation is not authorized by the prerequisites candidate")
if os.environ.get("AIOS_SINGLE_OWNER_GOVERNANCE_ENABLED", "false").lower() != "false":
parser.error("candidate must remain disabled")
if os.environ.get("AIOS_SINGLE_OWNER_ACTIVATION_ACTIVE", "false").lower() != "false":
parser.error("activation must remain inactive")
if os.environ.get("AIOS_GITOPS_WRITES_ENABLED", "false").lower() != "false":
parser.error("production mutation must remain disabled")
state = (
checkpoint_state(args.component, args.checkpoint_dir)
if args.checkpoint_dir is not None
else runtime_state(args.component)
)
print(json.dumps(state, sort_keys=True))
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,54 @@
from __future__ import annotations
from dataclasses import dataclass
import json
from pathlib import Path
import tempfile
from .models import GovernanceState
class InvalidTransition(RuntimeError):
pass
_NEXT = {
GovernanceState.HUMAN_OWNER_APPROVAL_PENDING: GovernanceState.OWNER_APPROVAL_VERIFIED,
GovernanceState.OWNER_APPROVAL_VERIFIED: GovernanceState.MACHINE_GATES_VERIFIED,
GovernanceState.MACHINE_GATES_VERIFIED: GovernanceState.AUTO_MERGE_READY,
GovernanceState.AUTO_MERGE_READY: GovernanceState.OWNER_APPROVED_EXECUTION_READY,
}
@dataclass
class GovernanceStateMachine:
state: GovernanceState = GovernanceState.HUMAN_OWNER_APPROVAL_PENDING
def transition(self, target: GovernanceState) -> GovernanceState:
if _NEXT.get(self.state) != target:
raise InvalidTransition(f"{self.state.value}->{target.value}")
self.state = target
return self.state
def restore(self, checkpoint: GovernanceState) -> None:
if checkpoint not in GovernanceState:
raise InvalidTransition("unknown checkpoint")
self.state = checkpoint
def save_fixture(self, path: Path) -> None:
if not any(part.startswith("aios-governance-fixture-") for part in path.resolve().parts):
raise PermissionError("state checkpoint writes are fixture-only")
path.parent.mkdir(parents=True, exist_ok=True)
with tempfile.NamedTemporaryFile("w", encoding="utf-8", dir=path.parent, delete=False) as handle:
json.dump({"state": self.state.value}, handle, sort_keys=True)
handle.write("\n")
handle.flush()
temporary = Path(handle.name)
temporary.replace(path)
@classmethod
def load_fixture(cls, path: Path) -> "GovernanceStateMachine":
if not any(part.startswith("aios-governance-fixture-") for part in path.resolve().parts):
raise PermissionError("state checkpoint reads are fixture-only")
raw = json.loads(path.read_text(encoding="utf-8"))
return cls(GovernanceState(raw["state"]))
@@ -0,0 +1,38 @@
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime, timezone
class DormantStatusProducer(RuntimeError):
pass
@dataclass(frozen=True)
class StatusRecord:
context: str
state: str
head_oid: str
observed_at: str
class MachineStatusProducer:
def __init__(self, *, enabled: bool = False, fixture: bool = False) -> None:
self.enabled = enabled
self.fixture = fixture
def produce(self, context: str, state: str, head_oid: str) -> StatusRecord:
if not self.fixture:
raise DormantStatusProducer("LIVE_STATUS_PUBLICATION_DENIED")
if self.enabled:
raise DormantStatusProducer("DORMANT_CANDIDATE_MUST_NOT_BE_ENABLED")
if context not in {
"aios/machine-gates", "aios/secret-scan", "aios/evidence-closure"
}:
raise ValueError("unknown status context")
if state not in {"success", "failure"} or len(head_oid) != 40:
raise ValueError("invalid status record")
return StatusRecord(
context, state, head_oid,
datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"),
)
@@ -0,0 +1,69 @@
from __future__ import annotations
from dataclasses import dataclass
from .models import ApprovalContext, Review
class ValidationDenied(RuntimeError):
def __init__(self, code: str) -> None:
super().__init__(code)
self.code = code
@dataclass(frozen=True)
class ValidationResult:
owner_review: Review
machine_contexts: tuple[str, ...]
class SingleOwnerApprovalValidator:
"""Pure candidate validator. It has no Gitea or production write path."""
def validate(self, context: ApprovalContext) -> ValidationResult:
if context.now >= context.expires_at:
raise ValidationDenied("APPROVAL_EXPIRED")
if context.wip:
raise ValidationDenied("WIP_BLOCKED")
if context.requester == context.owner:
raise ValidationDenied("REQUESTER_SELF_APPROVAL")
if context.executor == context.owner:
raise ValidationDenied("EXECUTOR_SELF_APPROVAL")
approved = [review for review in context.reviews if review.state == "APPROVED"]
owner_reviews = [
review for review in approved
if review.reviewer == context.owner and review.reviewer_id == context.owner_id
]
if len(owner_reviews) != 1:
raise ValidationDenied("OWNER_APPROVAL_REQUIRED")
owner_review = owner_reviews[0]
if owner_review.stale:
raise ValidationDenied("STALE_OWNER_APPROVAL")
if owner_review.dismissed:
raise ValidationDenied("DISMISSED_OWNER_APPROVAL")
if owner_review.head_oid != context.current_head_oid:
raise ValidationDenied("OWNER_APPROVAL_NOT_LATEST_HEAD")
if owner_review.reviewed_at >= context.expires_at:
raise ValidationDenied("OWNER_APPROVAL_EXPIRED")
if any(
review.state == "APPROVED"
and review.reviewer != context.owner
and review.reviewer_id != context.owner_id
for review in context.reviews
):
# Non-owner approvals may exist for legacy mechanics, but never
# satisfy or replace the human authorization decision.
pass
gates = {gate.context: gate for gate in context.machine_gates}
if set(gates) != set(context.required_machine_contexts):
raise ValidationDenied("MACHINE_GATE_SET_MISMATCH")
for gate in gates.values():
if gate.state != "success":
raise ValidationDenied("MACHINE_GATE_FAILED")
if gate.head_oid != context.current_head_oid:
raise ValidationDenied("MACHINE_GATE_NOT_LATEST_HEAD")
if gate.observed_at >= context.expires_at:
raise ValidationDenied("MACHINE_GATE_EXPIRED")
return ValidationResult(owner_review, tuple(sorted(gates)))
@@ -0,0 +1,16 @@
[build-system]
requires = ["setuptools>=68"]
build-backend = "setuptools.build_meta"
[project]
name = "aios-single-owner-governance"
version = "0.1.0"
requires-python = ">=3.11"
dependencies = []
[project.scripts]
aios-single-owner-governance = "aios_single_owner_governance.activation:main"
[tool.setuptools.packages.find]
where = ["."]
include = ["aios_single_owner_governance*"]

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