@topolo/cli 0.13.10 → 0.13.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -0
- package/agent-packs/README.md +53 -0
- package/agent-packs/src/topolo_agent_pack/cli.py +14 -2
- package/agent-packs/src/topolo_agent_pack/lanes.py +158 -5
- package/agent-packs/src/topolo_agent_pack/live.py +421 -2
- package/agent-packs/src/topolo_agent_pack/topolo.py +166 -5
- package/dist/{claude-pack-backend-RMSOPHVQ.js → claude-pack-backend-YEVPAOID.js} +76 -31
- package/dist/index.js +549 -132
- package/package.json +5 -5
|
@@ -606,6 +606,12 @@ class BackendProxy:
|
|
|
606
606
|
|
|
607
607
|
async def qualify_organization(self,thread_id):
|
|
608
608
|
role=self.execution.roles.get(thread_id) if self.execution else None
|
|
609
|
+
record=None
|
|
610
|
+
if role and role.get('dynamicLaneId'):
|
|
611
|
+
from . import lanes
|
|
612
|
+
record=lanes.Registry(self.lib.root,'codex').read(role['dynamicLaneId'])
|
|
613
|
+
if record['plan'].get('continuityTransport')=='native-cli':
|
|
614
|
+
lanes.native_cli_admission(record['plan']['continuity'])
|
|
609
615
|
if role and not self.permission_configuration and (pathlib.Path(self.runtime)/'permissions-operation.json').exists():
|
|
610
616
|
raise PackError('Unresolved native permission operation requires reconciliation before owned execution or grants')
|
|
611
617
|
if not role or not role.get('installationConsent'):
|
|
@@ -635,8 +641,6 @@ class BackendProxy:
|
|
|
635
641
|
raise PackError('Fresh organization policy prohibits the child-only manual verification profile')
|
|
636
642
|
role['organizationPolicyProof']=proof
|
|
637
643
|
if role.get('dynamicLaneId'):
|
|
638
|
-
from .lanes import Registry
|
|
639
|
-
record=Registry(self.lib.root,'codex').read(role['dynamicLaneId'])
|
|
640
644
|
current=await self.dynamic_current(record['plan'])
|
|
641
645
|
configuring=self._lane_configuration_record
|
|
642
646
|
if configuring and configuring['nativeId']==thread_id and self.lane_busy:
|
|
@@ -1010,6 +1014,8 @@ class BackendProxy:
|
|
|
1010
1014
|
self.lane_idle();store=lanes.Registry(self.lib.root,'codex');record=store.read(params['laneId'])
|
|
1011
1015
|
if not record or record['state']!='loaded-context-ACK':
|
|
1012
1016
|
raise PackError('Actual native settings and instruction-source ACK required before bootstrap')
|
|
1017
|
+
if record['plan'].get('continuityTransport')=='native-cli':
|
|
1018
|
+
lanes.native_cli_admission(record['plan']['continuity'])
|
|
1013
1019
|
current=await self.dynamic_current(record['plan'])
|
|
1014
1020
|
if current!={k:record['plan'][k] for k in ['role','intake','ceiling']}:
|
|
1015
1021
|
raise PackError('Current bootstrap source or policy differs')
|
|
@@ -1086,6 +1092,8 @@ class BackendProxy:
|
|
|
1086
1092
|
self.lane_idle();store=lanes.Registry(self.lib.root,'codex');record=store.read(params['laneId'])
|
|
1087
1093
|
if not record or record['plan'].get('verificationProfile')!='manual':
|
|
1088
1094
|
raise PackError('Actual routing proof requires the bound two-child manual verification plan')
|
|
1095
|
+
if record['plan'].get('continuityTransport')=='native-cli':
|
|
1096
|
+
lanes.native_cli_admission(record['plan']['continuity'])
|
|
1089
1097
|
current=await self.dynamic_current(record['plan'])
|
|
1090
1098
|
if current!={k:record['plan'][k] for k in ['role','intake','ceiling']}:
|
|
1091
1099
|
raise PackError('Current source/intake/policy changed before own fixture verification')
|
|
@@ -1149,6 +1157,8 @@ class BackendProxy:
|
|
|
1149
1157
|
self.lane_idle();store=lanes.Registry(self.lib.root,'codex');planned=store.read_plan(params['laneId'])
|
|
1150
1158
|
if store.read(params['laneId']) is not None:
|
|
1151
1159
|
raise PackError('This lane already has an actual native identity')
|
|
1160
|
+
if planned.get('continuityTransport')=='native-cli':
|
|
1161
|
+
lanes.native_cli_admission(planned['continuity'])
|
|
1152
1162
|
current=await self.dynamic_current(planned)
|
|
1153
1163
|
if current!={k:planned[k] for k in ['role','intake','ceiling']}:
|
|
1154
1164
|
raise PackError('Source, intake or policy changed before native creation')
|
|
@@ -1202,6 +1212,9 @@ class BackendProxy:
|
|
|
1202
1212
|
|
|
1203
1213
|
async def dynamic_lane(self, operation, params):
|
|
1204
1214
|
from . import lanes,task_models,policy
|
|
1215
|
+
requested=params.get('request',params.get('plan',{})) if isinstance(params,dict) else {}
|
|
1216
|
+
if isinstance(requested,dict) and (requested.get('continuityTransport')=='native-cli' or requested.get('schemaVersion')==2 or 'continuity' in requested):
|
|
1217
|
+
lanes.native_cli_admission(requested.get('continuity'))
|
|
1205
1218
|
self.lane_idle()
|
|
1206
1219
|
if self.lane_busy:
|
|
1207
1220
|
raise PackError('An owned dynamic lane operation is already active')
|
|
@@ -1254,6 +1267,8 @@ class BackendProxy:
|
|
|
1254
1267
|
record=store.read(params['laneId'])
|
|
1255
1268
|
if not record:
|
|
1256
1269
|
raise PackError('No owned dynamic lane registration')
|
|
1270
|
+
if record['plan'].get('continuityTransport')=='native-cli':
|
|
1271
|
+
lanes.native_cli_admission(record['plan']['continuity'])
|
|
1257
1272
|
current=await self.dynamic_current(record['plan'])
|
|
1258
1273
|
if current!={k:record['plan'][k] for k in ['role','intake','ceiling']}:
|
|
1259
1274
|
raise PackError('Dynamic source, intake or policy drift; no native mutation')
|
|
@@ -1973,3 +1988,407 @@ def bind(lib,runtime):
|
|
|
1973
1988
|
if backup.exists():raise PackError('Cannot preserve existing desktop-home file: '+name)
|
|
1974
1989
|
dest.rename(backup)
|
|
1975
1990
|
dest.symlink_to(expected)
|
|
1991
|
+
|
|
1992
|
+
|
|
1993
|
+
def adoption_require(value,message):
|
|
1994
|
+
if not value:raise PackError('Runtime adoption refused: '+message)
|
|
1995
|
+
|
|
1996
|
+
|
|
1997
|
+
def adoption_keys(value,keys):
|
|
1998
|
+
adoption_require(isinstance(value,dict) and set(value)==set(keys),'closed fields required: '+','.join(keys))
|
|
1999
|
+
|
|
2000
|
+
|
|
2001
|
+
def adoption_json(raw):
|
|
2002
|
+
def unique(pairs):
|
|
2003
|
+
adoption_require(len(pairs)==len({key for key,_ in pairs}),'duplicate JSON fields')
|
|
2004
|
+
return dict(pairs)
|
|
2005
|
+
return json.loads(raw,object_pairs_hook=unique)
|
|
2006
|
+
|
|
2007
|
+
|
|
2008
|
+
def adoption_snapshot(path,read=True,missing=False,profile=None):
|
|
2009
|
+
"""Single-file custody. Credential profiles use read=False exclusively."""
|
|
2010
|
+
import stat
|
|
2011
|
+
if read and profile is not None:adoption_read_guard(profile,[path])
|
|
2012
|
+
path=pathlib.Path(path)
|
|
2013
|
+
adoption_require(path.is_absolute() and path==path.resolve(),'absolute physical path required')
|
|
2014
|
+
parents=[]
|
|
2015
|
+
for parent in path.parents:
|
|
2016
|
+
st=parent.lstat()
|
|
2017
|
+
adoption_require(stat.S_ISDIR(st.st_mode) and st.st_uid in {0,os.getuid()},'unowned parent')
|
|
2018
|
+
parents.append([str(parent),st.st_dev,st.st_ino])
|
|
2019
|
+
try:st=path.lstat()
|
|
2020
|
+
except FileNotFoundError:
|
|
2021
|
+
adoption_require(missing,'required file absent')
|
|
2022
|
+
return None,None
|
|
2023
|
+
adoption_require(stat.S_ISREG(st.st_mode) and st.st_nlink==1 and st.st_uid==os.getuid(),'owned single regular file required')
|
|
2024
|
+
value={'device':st.st_dev,'inode':st.st_ino,'uid':st.st_uid,'mode':st.st_mode&0o777,
|
|
2025
|
+
'bytes':st.st_size,'mtimeNs':st.st_mtime_ns,'ctimeNs':st.st_ctime_ns,'parents':parents}
|
|
2026
|
+
raw=None
|
|
2027
|
+
if read:
|
|
2028
|
+
adoption_require(st.st_size<=128*1024*1024,'file exceeds limit')
|
|
2029
|
+
fd=os.open(path,os.O_RDONLY|os.O_NOFOLLOW)
|
|
2030
|
+
with os.fdopen(fd,'rb') as stream:
|
|
2031
|
+
opened=os.fstat(stream.fileno())
|
|
2032
|
+
adoption_require((opened.st_dev,opened.st_ino,opened.st_uid,opened.st_mode,opened.st_nlink,opened.st_size,opened.st_mtime_ns,opened.st_ctime_ns)==
|
|
2033
|
+
(st.st_dev,st.st_ino,st.st_uid,st.st_mode,st.st_nlink,st.st_size,st.st_mtime_ns,st.st_ctime_ns),'file changed before content read')
|
|
2034
|
+
raw=stream.read()
|
|
2035
|
+
after=path.lstat()
|
|
2036
|
+
adoption_require((after.st_dev,after.st_ino,after.st_size,after.st_mtime_ns,after.st_ctime_ns)==
|
|
2037
|
+
(st.st_dev,st.st_ino,st.st_size,st.st_mtime_ns,st.st_ctime_ns),'file changed while reading')
|
|
2038
|
+
value['sha256']=hashlib.sha256(raw).hexdigest()
|
|
2039
|
+
return value,raw
|
|
2040
|
+
|
|
2041
|
+
|
|
2042
|
+
def adoption_pin(value,profile=None):
|
|
2043
|
+
adoption_keys(value,['path','sha256'])
|
|
2044
|
+
snapshot,raw=adoption_snapshot(value['path'],profile=profile)
|
|
2045
|
+
adoption_require(snapshot['sha256']==value['sha256'],'source or preservation pin changed')
|
|
2046
|
+
return raw
|
|
2047
|
+
|
|
2048
|
+
|
|
2049
|
+
def adoption_profile(profile):
|
|
2050
|
+
adoption_keys(profile,['path','publicPath','metadata','environment','contextKey','principalId'])
|
|
2051
|
+
adoption_require(profile['environment'] in {'production','staging','development'} and
|
|
2052
|
+
isinstance(profile['contextKey'],str) and re.fullmatch(r'org:[A-Za-z0-9_-]+',profile['contextKey']) and
|
|
2053
|
+
isinstance(profile['principalId'],str) and re.fullmatch(r'[A-Za-z0-9_-]+',profile['principalId']), 'explicit private identity tuple required')
|
|
2054
|
+
current,_=adoption_snapshot(profile['path'],read=False)
|
|
2055
|
+
adoption_require(current==profile['metadata'] and current['mode']==0o600,'private profile metadata changed')
|
|
2056
|
+
public,_=adoption_snapshot(profile['publicPath'],read=False,missing=True)
|
|
2057
|
+
adoption_require(profile['path']!=profile['publicPath'] and (public is None or
|
|
2058
|
+
(public['device'],public['inode'])!=(current['device'],current['inode'])),'private/public profile alias')
|
|
2059
|
+
|
|
2060
|
+
|
|
2061
|
+
def adoption_read_guard(profile,paths):
|
|
2062
|
+
"""Classify every declared readable input before any non-manifest content read."""
|
|
2063
|
+
adoption_profile(profile)
|
|
2064
|
+
protected=[(str(pathlib.Path(profile[key]).resolve()),adoption_snapshot(profile[key],read=False,missing=True)[0])
|
|
2065
|
+
for key in ['path','publicPath']]
|
|
2066
|
+
for path in paths:
|
|
2067
|
+
current,_=adoption_snapshot(path,read=False,missing=True)
|
|
2068
|
+
adoption_require(all(str(pathlib.Path(path).resolve())!=name and
|
|
2069
|
+
(current is None or saved is None or (current['device'],current['inode'])!=(saved['device'],saved['inode']))
|
|
2070
|
+
for name,saved in protected),'readable input aliases protected profile; no contents read')
|
|
2071
|
+
|
|
2072
|
+
|
|
2073
|
+
def adoption_runtime_paths(runtime):
|
|
2074
|
+
return [runtime[key]['path'] for key in ['node','cli','mcp']]+[pin['path'] for pin in runtime['cohort'].values()]+[pin['path'] for pin in runtime['adapterFiles']]
|
|
2075
|
+
|
|
2076
|
+
|
|
2077
|
+
def adoption_read_inputs(value):
|
|
2078
|
+
paths=[value['ownerGeneration']['path'],*adoption_runtime_paths(value['runtime']),*[pin['path'] for pin in value['preserve']],
|
|
2079
|
+
*[target['path'] for target in value.get('targets',[])]]
|
|
2080
|
+
if 'launcher' in value:paths.append(value['launcher']['path'])
|
|
2081
|
+
adoption_read_guard(value['profile'],paths)
|
|
2082
|
+
|
|
2083
|
+
|
|
2084
|
+
def adoption_runtime(runtime,profile):
|
|
2085
|
+
adoption_keys(runtime,['node','cli','mcp','cohort','pythonSource','adapterFiles','osEnvironment'])
|
|
2086
|
+
adoption_read_guard(profile,adoption_runtime_paths(runtime))
|
|
2087
|
+
for key in ['node','cli','mcp']:adoption_pin(runtime[key],profile)
|
|
2088
|
+
adoption_keys(runtime['cohort'],['cli','mcp','sdk'])
|
|
2089
|
+
for key,name,version in [('cli','@topolo/cli','0.13.11'),('mcp','@topolo/mcp','0.14.26'),('sdk','@topolo/sdk','0.23.21')]:
|
|
2090
|
+
package=json.loads(adoption_pin(runtime['cohort'][key],profile))
|
|
2091
|
+
adoption_require(package.get('name')==name and package.get('version')==version,'immutable target cohort differs')
|
|
2092
|
+
if key!='sdk':
|
|
2093
|
+
adoption_require(runtime[key]['path']==str(pathlib.Path(runtime['cohort'][key]['path']).parent/'dist/index.js') and
|
|
2094
|
+
package.get('dependencies',{}).get('@topolo/sdk')=='0.23.21','entry/dependency cohort differs')
|
|
2095
|
+
source=pathlib.Path(runtime['pythonSource'])
|
|
2096
|
+
adoption_require(source.is_absolute() and source==source.resolve(),'physical adapter source required')
|
|
2097
|
+
adoption_require(isinstance(runtime['adapterFiles'],list) and len(runtime['adapterFiles'])==3 and
|
|
2098
|
+
{p['path'] for p in runtime['adapterFiles']}=={str(source/'topolo_agent_pack'/n) for n in ['cli.py','live.py','topolo.py']},'exact successor adapter source pins required')
|
|
2099
|
+
for value in runtime['adapterFiles']:adoption_pin(value,profile)
|
|
2100
|
+
env=runtime['osEnvironment']
|
|
2101
|
+
adoption_require(isinstance(env,dict) and {'HOME','PATH'}<=set(env)<=
|
|
2102
|
+
{'HOME','PATH','USER','LOGNAME','LANG','LC_ALL','TMPDIR'} and
|
|
2103
|
+
all(isinstance(v,str) and v and len(v)<=4096 and not any(ord(c)<32 for c in v) for v in env.values()),'closed nonsecret OS environment required')
|
|
2104
|
+
env=dict(env)
|
|
2105
|
+
env.update(TOPOLO_CONFIG_PATH=profile['path'],TOPOLO_ENV=profile['environment'],TOPOLO_CONTEXT=profile['contextKey'],
|
|
2106
|
+
TOPOLO_API_KEY='',TOPOLO_ACCESS_TOKEN='')
|
|
2107
|
+
prefix='TOPOLO_'+profile['environment'].upper()
|
|
2108
|
+
env.update({prefix+'_API_KEY':'',prefix+'_ACCESS_TOKEN':''})
|
|
2109
|
+
return env
|
|
2110
|
+
|
|
2111
|
+
|
|
2112
|
+
def adoption_alias(alias,target):
|
|
2113
|
+
adoption_keys(alias,['path','linkTarget','metadata']);p=pathlib.Path(alias['path']);st=p.lstat()
|
|
2114
|
+
value=[st.st_dev,st.st_ino,st.st_uid,st.st_mode,st.st_size,st.st_mtime_ns,st.st_ctime_ns]
|
|
2115
|
+
adoption_require(p.is_symlink() and os.readlink(p)==alias['linkTarget'] and str(p.resolve())==target and
|
|
2116
|
+
st.st_uid==os.getuid() and value==alias['metadata'],'shared alias changed')
|
|
2117
|
+
|
|
2118
|
+
|
|
2119
|
+
def adoption_config(raw,target,mcp_launcher):
|
|
2120
|
+
"""Registration-only transform; never installs skills or rewrites context."""
|
|
2121
|
+
import tomllib
|
|
2122
|
+
adoption_require(target['format'] in {'json','toml'} and target['rootKey'] in {'mcp_servers','mcpServers'},'unsupported configuration format')
|
|
2123
|
+
config=adoption_json(raw) if target['format']=='json' else tomllib.loads(raw.decode())
|
|
2124
|
+
servers=config.get(target['rootKey'])
|
|
2125
|
+
adoption_require(isinstance(servers,dict),'existing server map required')
|
|
2126
|
+
names=[v['name'] for v in target['transports']]
|
|
2127
|
+
adoption_require(isinstance(target['untouchedServers'],list) and all(isinstance(n,str) for n in target['untouchedServers']) and
|
|
2128
|
+
len(set(target['untouchedServers']))==len(target['untouchedServers']) and
|
|
2129
|
+
not set(names)&set(target['untouchedServers']) and set(servers)==set(names)|set(target['untouchedServers']), 'incomplete physical server map')
|
|
2130
|
+
for transport in target['transports']:
|
|
2131
|
+
name=transport['name']
|
|
2132
|
+
adoption_require(isinstance(name,str) and re.fullmatch(r'[A-Za-z0-9_-]+',name) and isinstance(servers.get(name),dict),'unambiguous existing local transport required')
|
|
2133
|
+
entry=servers[name]
|
|
2134
|
+
adoption_require(isinstance(entry.get('command'),str) and 'url' not in entry and 'env_vars' not in entry,'local explicit transport required')
|
|
2135
|
+
adoption_require(not entry.get('env',{}).get('TOPOLO_AGENT_INSTALLATION_ID'),'installation mode requires a separate explicit adoption')
|
|
2136
|
+
agent=entry.get('env',{}).get('TOPOLO_AGENT_NAME','')
|
|
2137
|
+
adoption_require(isinstance(agent,str) and (not agent or re.fullmatch(r'[A-Za-z0-9_-]+',agent)),'agent name differs')
|
|
2138
|
+
entry['command']=mcp_launcher;entry['args']=[agent] if agent else [];entry['env']={'TOPOLO_AGENT_NAME':agent} if agent else {}
|
|
2139
|
+
if target['format']=='json':return (json.dumps(config,indent=2)+'\n').encode()
|
|
2140
|
+
text=raw.decode()
|
|
2141
|
+
for transport in target['transports']:
|
|
2142
|
+
name=transport['name'];entry=servers[name]
|
|
2143
|
+
# Qualified simple tables only. Quoted/dotted/array-table variants refuse;
|
|
2144
|
+
# they cannot silently coexist with a new duplicate registration.
|
|
2145
|
+
section='[mcp_servers.'+name+']';environment='[mcp_servers.'+name+'.env]'
|
|
2146
|
+
adoption_require(target['rootKey']=='mcp_servers' and text.splitlines().count(section)==1 and
|
|
2147
|
+
text.splitlines().count(environment)<=1,'unsupported TOML registration layout')
|
|
2148
|
+
kept=[];skipping=False
|
|
2149
|
+
for line in text.splitlines():
|
|
2150
|
+
if line.strip() in {section,environment}:skipping=True
|
|
2151
|
+
elif line.lstrip().startswith('['):
|
|
2152
|
+
adoption_require(not line.strip().startswith('[mcp_servers.'+name+'.'),'unsupported nested transport table')
|
|
2153
|
+
skipping=False
|
|
2154
|
+
if not skipping:kept.append(line)
|
|
2155
|
+
values=[]
|
|
2156
|
+
for key,value in entry.items():
|
|
2157
|
+
if key=='env':continue
|
|
2158
|
+
adoption_require(re.fullmatch(r'[A-Za-z0-9_-]+',key) and
|
|
2159
|
+
(isinstance(value,(str,bool,int)) or isinstance(value,list) and all(isinstance(v,str) for v in value)), 'unsupported TOML transport value')
|
|
2160
|
+
values.append(key+' = '+json.dumps(value))
|
|
2161
|
+
text='\n'.join(kept).rstrip()+'\n\n'+section+'\n'+'\n'.join(values)+'\n\n'+environment+'\n'+\
|
|
2162
|
+
'\n'.join(key+' = '+json.dumps(value) for key,value in entry['env'].items())+'\n'
|
|
2163
|
+
adoption_require(tomllib.loads(text)==config,'TOML semantic preservation differs')
|
|
2164
|
+
return text.encode()
|
|
2165
|
+
|
|
2166
|
+
|
|
2167
|
+
def adoption_bootstrap(source,descriptor,runtime):
|
|
2168
|
+
"""Check successor bytes before importing them, including the verifier itself."""
|
|
2169
|
+
pins=[(p['path'],p['sha256']) for p in runtime['runtime']['adapterFiles']]
|
|
2170
|
+
pins.append((str(descriptor),hashlib.sha256(canonical(runtime)+b'\n').hexdigest()))
|
|
2171
|
+
return 'import hashlib,os,pathlib,stat,sys\nsys.dont_write_bytecode=True\nfor name,digest in '+repr(pins)+':\n'+\
|
|
2172
|
+
' p=pathlib.Path(name);s=p.lstat()\n'+\
|
|
2173
|
+
' if p!=p.resolve() or not stat.S_ISREG(s.st_mode) or s.st_uid!=os.getuid() or s.st_nlink!=1:raise SystemExit("Runtime source custody changed")\n'+\
|
|
2174
|
+
' with p.open("rb") as f: actual=hashlib.file_digest(f,"sha256").hexdigest()\n'+\
|
|
2175
|
+
' if actual!=digest:raise SystemExit("Runtime source changed before import")\n'+\
|
|
2176
|
+
'sys.path.insert(0,'+repr(source)+')\n'
|
|
2177
|
+
|
|
2178
|
+
|
|
2179
|
+
def adoption_known_launcher(raw,root,profile=None):
|
|
2180
|
+
import ast
|
|
2181
|
+
lines=raw.decode().splitlines();tail=['from topolo_agent_pack.live import proxy_main','raise SystemExit(proxy_main())']
|
|
2182
|
+
if len(lines)==5 and lines[1]=='import sys' and lines[2].startswith('sys.path.insert(0,'):
|
|
2183
|
+
return lines[0].startswith('#!/') and lines[-2:]==tail
|
|
2184
|
+
assignments=[line for line in lines if line.startswith('os.environ["TOPOLO_AGENT_PACK_RUNTIME"]=')]
|
|
2185
|
+
if len(assignments)!=1:return False
|
|
2186
|
+
descriptor=pathlib.Path(ast.literal_eval(assignments[0].split('=',1)[1]))
|
|
2187
|
+
adoption_require(descriptor.parent==root/'launchers' and descriptor.name.startswith('private-runtime-'),'unknown managed launcher descriptor')
|
|
2188
|
+
_,value=adoption_snapshot(descriptor,profile=profile);runtime=json.loads(value)
|
|
2189
|
+
expected=lines[0]+'\n'+adoption_bootstrap(runtime['runtime']['pythonSource'],descriptor,runtime)+assignments[0]+'\n'+'\n'.join(tail)+'\n'
|
|
2190
|
+
return raw==expected.encode()
|
|
2191
|
+
|
|
2192
|
+
|
|
2193
|
+
def adoption_preflight(store,manifest):
|
|
2194
|
+
adoption_keys(manifest,['schemaVersion','operationId','library','ownerGeneration','consumers','profile','runtime','targets','launcher','preserve'])
|
|
2195
|
+
root=pathlib.Path(store)
|
|
2196
|
+
adoption_require(manifest['schemaVersion']==1 and type(manifest['schemaVersion']) is int and
|
|
2197
|
+
isinstance(manifest['operationId'],str) and re.fullmatch(r'[a-f0-9]{8}-(?:[a-f0-9]{4}-){3}[a-f0-9]{12}',manifest['operationId']) and
|
|
2198
|
+
root.is_absolute() and str(root)==manifest['library'] and root==root.resolve() and root.stat().st_uid==os.getuid(),'owned library/operation required')
|
|
2199
|
+
def inside(path):
|
|
2200
|
+
adoption_require(pathlib.Path(path).is_relative_to(root),'destination outside owned library')
|
|
2201
|
+
adoption_read_inputs(manifest)
|
|
2202
|
+
inside(manifest['ownerGeneration']['path']);adoption_pin(manifest['ownerGeneration'],manifest['profile'])
|
|
2203
|
+
consumers=manifest['consumers']
|
|
2204
|
+
adoption_require(isinstance(consumers,list) and 1<=len(consumers)<=256 and all(isinstance(c,str) and c for c in consumers) and len(set(consumers))==len(consumers),'closed unique consumer set required')
|
|
2205
|
+
adoption_profile(manifest['profile']);adoption_runtime(manifest['runtime'],manifest['profile'])
|
|
2206
|
+
descriptor=root/'launchers'/('private-runtime-'+manifest['operationId']+'.json')
|
|
2207
|
+
mcp_launcher=root/'launchers'/('private-mcp-'+manifest['operationId'])
|
|
2208
|
+
adoption_require(isinstance(manifest['preserve'],list) and 1<=len(manifest['preserve'])<=2048,'preservation pins required')
|
|
2209
|
+
for value in manifest['preserve']:inside(value['path']);adoption_pin(value,manifest['profile'])
|
|
2210
|
+
targets=manifest['targets'];adoption_require(isinstance(targets,list) and 1<=len(targets)<=256,'nonempty effective transport targets required')
|
|
2211
|
+
writes=[];seen=set();pairs=set()
|
|
2212
|
+
for target in targets:
|
|
2213
|
+
adoption_keys(target,['path','before','aliases','format','rootKey','consumers','transports','untouchedServers'])
|
|
2214
|
+
inside(target['path']);before,raw=adoption_snapshot(target['path'],profile=manifest['profile'])
|
|
2215
|
+
adoption_require(before==target['before'] and target['path'] not in seen,'target preimage changed or duplicated')
|
|
2216
|
+
seen.add(target['path'])
|
|
2217
|
+
adoption_require(isinstance(target['consumers'],list) and target['consumers'] and set(target['consumers'])<=set(consumers),'out-of-scope shared consumer')
|
|
2218
|
+
for alias in target['aliases']:
|
|
2219
|
+
inside(alias['path']);adoption_alias(alias,target['path'])
|
|
2220
|
+
adoption_require(isinstance(target['transports'],list) and target['transports'],'effective transport rows required')
|
|
2221
|
+
for transport in target['transports']:
|
|
2222
|
+
adoption_keys(transport,['name','origin','consumers'])
|
|
2223
|
+
pair=(target['path'],transport['name'])
|
|
2224
|
+
adoption_require(pair not in pairs and transport['origin']==target['path'] and isinstance(transport['consumers'],list) and
|
|
2225
|
+
transport['consumers'] and set(transport['consumers'])<=set(target['consumers']),'transport origin/consumer/precedence conflict')
|
|
2226
|
+
pairs.add(pair)
|
|
2227
|
+
writes.append((target['path'],before,adoption_config(raw,target,str(mcp_launcher))))
|
|
2228
|
+
adoption_require(set(consumers)=={c for t in targets for v in t['transports'] for c in v['consumers']},'incomplete effective consumer manifest')
|
|
2229
|
+
launcher=manifest['launcher'];adoption_keys(launcher,['path','before'])
|
|
2230
|
+
adoption_require(launcher['path']==str(root/'launchers/topolo-agent-pack-backend') and launcher['path'] not in seen,'exact owned launcher required')
|
|
2231
|
+
before,raw=adoption_snapshot(launcher['path'],profile=manifest['profile']);adoption_require(before==launcher['before'],'launcher preimage changed')
|
|
2232
|
+
lines=raw.decode().splitlines()
|
|
2233
|
+
adoption_require(adoption_known_launcher(raw,root,manifest['profile']),'unrecognized launcher source')
|
|
2234
|
+
current,_=adoption_snapshot(descriptor,missing=True,profile=manifest['profile']);adoption_require(current is None,'runtime descriptor already exists; reconcile without replay')
|
|
2235
|
+
current,_=adoption_snapshot(mcp_launcher,missing=True,profile=manifest['profile']);adoption_require(current is None,'MCP launcher already exists; reconcile without replay')
|
|
2236
|
+
runtime={'schemaVersion':1,'library':str(root),'ownerGeneration':manifest['ownerGeneration'],'profile':manifest['profile'],
|
|
2237
|
+
'runtime':manifest['runtime'],'preserve':manifest['preserve']}
|
|
2238
|
+
writes.append((str(descriptor),None,canonical(runtime)+b'\n'))
|
|
2239
|
+
source=manifest['runtime']['pythonSource']
|
|
2240
|
+
adoption_require(' ' not in lines[0],'MCP wrapper requires an absolute Python shebang without flags')
|
|
2241
|
+
mcp_code=lines[0]+' -I\n'+adoption_bootstrap(source,descriptor,runtime)+'from topolo_agent_pack.topolo import managed_mcp_main\nraise SystemExit(managed_mcp_main('+repr(str(descriptor))+',sys.argv[1:]))\n'
|
|
2242
|
+
writes.append((str(mcp_launcher),None,mcp_code.encode()))
|
|
2243
|
+
code=lines[0]+'\n'+adoption_bootstrap(source,descriptor,runtime)+'os.environ["TOPOLO_AGENT_PACK_RUNTIME"]='+repr(str(descriptor))+'\nfrom topolo_agent_pack.live import proxy_main\nraise SystemExit(proxy_main())\n'
|
|
2244
|
+
writes.append((launcher['path'],before,code.encode()))
|
|
2245
|
+
protected={manifest['profile']['path'],manifest['profile']['publicPath'],manifest['ownerGeneration']['path'],*[p['path'] for p in manifest['preserve']]}
|
|
2246
|
+
protected.update(pin['path'] for pin in [manifest['runtime'][k] for k in ['node','cli','mcp']]+
|
|
2247
|
+
list(manifest['runtime']['cohort'].values())+manifest['runtime']['adapterFiles'])
|
|
2248
|
+
adoption_require(not protected&{p for p,_,_ in writes},'destination overlaps protected profile/generation/context')
|
|
2249
|
+
return writes
|
|
2250
|
+
|
|
2251
|
+
|
|
2252
|
+
def private_runtime_adoption(store,manifest_path,operation,plan_digest=None,confirm=False):
|
|
2253
|
+
try:return _private_runtime_adoption(store,manifest_path,operation,plan_digest,confirm)
|
|
2254
|
+
except (KeyError,TypeError,AttributeError,SyntaxError,UnicodeError) as error:
|
|
2255
|
+
raise PackError('Runtime adoption refused: malformed typed manifest or owned launcher') from error
|
|
2256
|
+
|
|
2257
|
+
|
|
2258
|
+
def _private_runtime_adoption(store,manifest_path,operation,plan_digest=None,confirm=False):
|
|
2259
|
+
"""Owner-reviewed configuration transaction, NOT native/Auth admission.
|
|
2260
|
+
|
|
2261
|
+
The closed manifest is the owner's effect scope. It does not certify a native
|
|
2262
|
+
inventory, current grants, health, startup or a successful tool result.
|
|
2263
|
+
"""
|
|
2264
|
+
adoption_require(operation in {'plan','apply'} and (operation=='apply' and confirm is True and
|
|
2265
|
+
isinstance(plan_digest,str) and re.fullmatch(r'[a-f0-9]{64}',plan_digest) or
|
|
2266
|
+
operation=='plan' and plan_digest is None and confirm is False),'closed plan/confirmed apply required')
|
|
2267
|
+
manifest_before,raw=adoption_snapshot(manifest_path);adoption_require(len(raw)<=2*1024*1024,'manifest exceeds limit')
|
|
2268
|
+
manifest=adoption_json(raw);writes=adoption_preflight(store,manifest)
|
|
2269
|
+
journal=pathlib.Path(store)/'launchers'/('adoption-'+manifest['operationId']+'.json')
|
|
2270
|
+
adoption_read_guard(manifest['profile'],[journal,*[p+'.'+manifest['operationId']+'.tmp' for p,_,_ in writes]])
|
|
2271
|
+
adoption_require(not journal.exists() and not journal.is_symlink(),'operation already reserved; reconcile without replay')
|
|
2272
|
+
plan={'schemaVersion':1,'operationId':manifest['operationId'],'manifestDigest':hashlib.sha256(raw).hexdigest(),
|
|
2273
|
+
'writes':[{'path':p,'before':before,'afterDigest':hashlib.sha256(value).hexdigest()} for p,before,value in writes],
|
|
2274
|
+
'claim':'CONFIGURATION_ONLY_NO_NATIVE_IDENTITY_HEALTH_PERMISSION_OR_ADOPTION_ACK'}
|
|
2275
|
+
digest=hashlib.sha256(canonical(plan)).hexdigest()
|
|
2276
|
+
if operation=='plan':return {**plan,'planDigest':digest}
|
|
2277
|
+
adoption_require(digest==plan_digest,'reviewed plan changed')
|
|
2278
|
+
profile=manifest['profile']
|
|
2279
|
+
adoption_require(adoption_snapshot(manifest_path,profile=profile)[0]==manifest_before,'manifest changed before reservation')
|
|
2280
|
+
from contextlib import ExitStack
|
|
2281
|
+
with ExitStack() as custody:
|
|
2282
|
+
parent_fd=os.open(journal.parent,os.O_RDONLY|os.O_DIRECTORY|os.O_NOFOLLOW);custody.callback(os.close,parent_fd)
|
|
2283
|
+
fd=os.open(journal.name,os.O_RDWR|os.O_CREAT|os.O_EXCL|os.O_NOFOLLOW,0o600,dir_fd=parent_fd);custody.callback(os.close,fd)
|
|
2284
|
+
def generation(st):
|
|
2285
|
+
return (st.st_dev,st.st_ino,st.st_uid,st.st_mode,st.st_nlink,st.st_size,st.st_mtime_ns,st.st_ctime_ns)
|
|
2286
|
+
def parent_identity(st):
|
|
2287
|
+
return (st.st_dev,st.st_ino,st.st_uid,st.st_mode)
|
|
2288
|
+
parent_generation=parent_identity(os.fstat(parent_fd));journal_generation=generation(os.fstat(fd))
|
|
2289
|
+
journal_parents=adoption_snapshot(journal,read=False)[0]['parents']
|
|
2290
|
+
record={'operationId':manifest['operationId'],'planDigest':digest,'state':'may-have-config-effects','completed':[]}
|
|
2291
|
+
def journal_guard():
|
|
2292
|
+
try:
|
|
2293
|
+
current=adoption_snapshot(journal,read=False)[0];held=generation(os.fstat(fd));named=generation(journal.lstat())
|
|
2294
|
+
parent=parent_identity(os.fstat(parent_fd));named_parent=parent_identity(journal.parent.lstat())
|
|
2295
|
+
except OSError as error:
|
|
2296
|
+
raise PackError('Runtime adoption refused: journal custody changed; may have config effects; retain evidence') from error
|
|
2297
|
+
adoption_require(held==named==journal_generation and held[4]==1 and current['mode']==0o600 and
|
|
2298
|
+
parent==named_parent==parent_generation and current['parents']==journal_parents,
|
|
2299
|
+
'journal custody changed; may have config effects; retain evidence')
|
|
2300
|
+
def checkpoint():
|
|
2301
|
+
nonlocal journal_generation
|
|
2302
|
+
journal_guard();data=canonical(record)+b'\n'
|
|
2303
|
+
os.lseek(fd,0,os.SEEK_SET);os.ftruncate(fd,0)
|
|
2304
|
+
adoption_require(os.write(fd,data)==len(data),'journal checkpoint incomplete; retain evidence');os.fsync(fd)
|
|
2305
|
+
os.lseek(fd,0,os.SEEK_SET)
|
|
2306
|
+
adoption_require(os.read(fd,len(data)+1)==data,'journal checkpoint differs; retain evidence')
|
|
2307
|
+
updated=generation(os.fstat(fd))
|
|
2308
|
+
adoption_require(updated[:5]==journal_generation[:5],'journal descriptor generation changed; retain evidence')
|
|
2309
|
+
journal_generation=updated;journal_guard()
|
|
2310
|
+
def revalidate():
|
|
2311
|
+
adoption_read_inputs(manifest)
|
|
2312
|
+
adoption_pin(manifest['ownerGeneration'],profile);adoption_profile(profile);adoption_runtime(manifest['runtime'],profile)
|
|
2313
|
+
for keep in manifest['preserve']:adoption_pin(keep,profile)
|
|
2314
|
+
for completed in record['completed']:adoption_require(adoption_snapshot(completed['path'],profile=profile)[0]==completed['after'],'completed file changed')
|
|
2315
|
+
for target in manifest['targets']:
|
|
2316
|
+
for alias in target['aliases']:adoption_alias(alias,target['path'])
|
|
2317
|
+
adoption_require(adoption_snapshot(manifest_path,profile=profile)[0]==manifest_before,'manifest changed during apply')
|
|
2318
|
+
journal_guard()
|
|
2319
|
+
checkpoint()
|
|
2320
|
+
# Recheck the whole completed set at both sides of each replacement,
|
|
2321
|
+
# including the last launcher; prepared is never inferred from one file.
|
|
2322
|
+
for path,before,value in writes:
|
|
2323
|
+
revalidate()
|
|
2324
|
+
current,_=adoption_snapshot(path,missing=True,profile=profile);adoption_require(current==before,'destination changed before write')
|
|
2325
|
+
temporary=path+'.'+manifest['operationId']+'.tmp'
|
|
2326
|
+
mode=before['mode'] if before else (0o700 if path.endswith('/private-mcp-'+manifest['operationId']) else 0o600)
|
|
2327
|
+
adoption_read_guard(profile,[temporary])
|
|
2328
|
+
handle=os.open(temporary,os.O_WRONLY|os.O_CREAT|os.O_EXCL|os.O_NOFOLLOW,mode)
|
|
2329
|
+
with os.fdopen(handle,'wb') as stream:
|
|
2330
|
+
temporary_identity=os.fstat(stream.fileno());os.fchmod(stream.fileno(),mode)
|
|
2331
|
+
stream.write(value);stream.flush();os.fsync(stream.fileno())
|
|
2332
|
+
revalidate()
|
|
2333
|
+
temp,actual=adoption_snapshot(temporary,profile=profile)
|
|
2334
|
+
adoption_require((temp['device'],temp['inode'])==(temporary_identity.st_dev,temporary_identity.st_ino) and actual==value,'temporary custody changed; retain journal')
|
|
2335
|
+
adoption_require(adoption_snapshot(manifest_path,profile=profile)[0]==manifest_before,'manifest changed during apply')
|
|
2336
|
+
current,_=adoption_snapshot(path,missing=True,profile=profile);adoption_require(current==before,'destination changed during write; retained temp and journal')
|
|
2337
|
+
journal_guard();os.replace(temporary,path)
|
|
2338
|
+
after,actual=adoption_snapshot(path,profile=profile);adoption_require(actual==value,'afterimage differs; retain journal')
|
|
2339
|
+
record['completed'].append({'path':path,'after':after});revalidate();checkpoint()
|
|
2340
|
+
revalidate();record['state']='configuration-prepared-native-adoption-unverified';checkpoint();journal_guard()
|
|
2341
|
+
return {'operationId':manifest['operationId'],'planDigest':digest,'state':record['state'],'journal':str(journal),'nativeACK':False}
|
|
2342
|
+
|
|
2343
|
+
|
|
2344
|
+
def compile_native_cli_command(value,store,pack,input_path,environment):
|
|
2345
|
+
"""Compile a fixed native tool command; no shell/model text is accepted."""
|
|
2346
|
+
import shlex
|
|
2347
|
+
from . import lanes
|
|
2348
|
+
value=lanes.native_cli_input(value)
|
|
2349
|
+
allowed={'HOME','USER','LOGNAME','PATH','LANG','LC_ALL','TMPDIR','TOPOLO_CONFIG_PATH','TOPOLO_ENV','TOPOLO_CONTEXT'}
|
|
2350
|
+
if (not isinstance(environment,dict) or not set(environment)<=allowed or
|
|
2351
|
+
any(not isinstance(v,str) or len(v)>4096 or any(ord(c)<32 for c in v) for v in environment.values()) or
|
|
2352
|
+
not environment.get('TOPOLO_CONFIG_PATH') or not pathlib.Path(environment['TOPOLO_CONFIG_PATH']).is_absolute() or
|
|
2353
|
+
environment.get('TOPOLO_ENV')!=value['context']['environment'] or environment.get('TOPOLO_CONTEXT')!=value['context']['contextKey']):
|
|
2354
|
+
raise PackError('Exact child-only native-cli environment required; inherited credentials are forbidden')
|
|
2355
|
+
if any(not pathlib.Path(p).is_absolute() for p in [store,input_path]) or pack!=value['sourceRoleKey'].split('/')[0]:
|
|
2356
|
+
raise PackError('Exact owned store, pack and input references required')
|
|
2357
|
+
argv=['/usr/bin/env','-i',*[k+'='+environment[k] for k in sorted(environment)],value['cli']['nodeExecutable'],value['cli']['entry'],
|
|
2358
|
+
'agent-packs','--store',str(store),'remote-context',pack,'--environment',value['context']['environment'],
|
|
2359
|
+
'--context-key',value['context']['contextKey'],'--role',value['context']['logicalId'],'--continuity-input',str(input_path)]
|
|
2360
|
+
return shlex.join(argv)
|
|
2361
|
+
|
|
2362
|
+
|
|
2363
|
+
def native_cli_codex_tool_proof(value,command,result,turn_id,cwd,guide_command,guide_digest):
|
|
2364
|
+
"""Verify actual owned Codex tool DTOs; printed/foreign receipts do not ACK."""
|
|
2365
|
+
import shlex
|
|
2366
|
+
from . import lanes
|
|
2367
|
+
value=lanes.native_cli_input(value);lanes.native_cli_hash(guide_digest)
|
|
2368
|
+
if guide_digest!=value['source']['charterDigest']:
|
|
2369
|
+
raise PackError('Native-cli actual charter read differs from the pinned receipt source')
|
|
2370
|
+
thread=result.get('thread',{});turns=[t for t in thread.get('turns',[]) if t.get('id')==turn_id]
|
|
2371
|
+
if (thread.get('id')!=value['nativeId'] or thread.get('cwd')!=str(cwd) or len(turns)!=1 or
|
|
2372
|
+
turns[0].get('status')!='completed' or turns[0].get('error')):
|
|
2373
|
+
raise PackError('Exact same-child native-cli turn/cwd completion required')
|
|
2374
|
+
receipts=[];reads=[]
|
|
2375
|
+
for item in turns[0].get('items',[]):
|
|
2376
|
+
if item.get('type')!='commandExecution' or item.get('status')!='completed' or type(item.get('exitCode'))is not int or item['exitCode']!=0 or not item.get('id'):
|
|
2377
|
+
continue
|
|
2378
|
+
actual=item.get('command');args=shlex.split(actual) if isinstance(actual,str) else []
|
|
2379
|
+
unwrapped=args[2] if len(args)==3 and args[0] in {'/bin/sh','/bin/bash','/bin/zsh'} and args[1]=='-c' else actual
|
|
2380
|
+
if unwrapped==guide_command and hashlib.sha256(item.get('aggregatedOutput','').encode()).hexdigest()==guide_digest:
|
|
2381
|
+
reads.append(item['id'])
|
|
2382
|
+
if unwrapped!=command:
|
|
2383
|
+
continue
|
|
2384
|
+
try:
|
|
2385
|
+
receipt=json.loads(item.get('aggregatedOutput',''))
|
|
2386
|
+
except (ValueError,TypeError):
|
|
2387
|
+
continue
|
|
2388
|
+
receipt=lanes.native_cli_receipt(value,receipt);receipts.append((item['id'],receipt))
|
|
2389
|
+
if len(receipts)!=1 or len(reads)!=1 or receipts[0][0]==reads[0]:
|
|
2390
|
+
raise PackError('Actual native-cli command/receipt and own charter read require unique successful tool results')
|
|
2391
|
+
identity,receipt=receipts[0]
|
|
2392
|
+
proof={'harness':'codex','nativeId':value['nativeId'],'generation':value['nativeConnectionGeneration'],'turnId':turn_id,
|
|
2393
|
+
'itemId':identity,'commandDigest':lanes.digest(command),'exitCode':0,'receiptDigest':lanes.digest(receipt)}
|
|
2394
|
+
return {'receipt':receipt,'proof':proof,'nativeCharterReadItemId':reads[0]}
|