@topolo/cli 0.13.10 → 0.13.11

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 CHANGED
@@ -436,3 +436,15 @@ context start, fixture start, retirement, and positive permission forwarding
436
436
  remain closed before native access. A valid private profile reference does not
437
437
  lift this boundary. Source fixtures and descriptor reads do not establish
438
438
  private MCP adoption or actual task completion.
439
+
440
+ Dynamic lane plans can explicitly select the `native-cli` continuity transport.
441
+ Its closed `remote-context --continuity-input` operation produces a bounded
442
+ public identity/context receipt with real response request IDs. The native
443
+ thread's own command or Bash result and charter reads must independently match
444
+ that receipt; an external shell result does not establish native context.
445
+
446
+ Current native startup, credential refresh authority and Bash result producers
447
+ remain unqualified. Native CLI effects therefore refuse with
448
+ `native_cli_startup_unqualified` before client/helper/native access. Private MCP
449
+ startup retains its existing refusal. Version 1 plans stay readable and do not
450
+ silently become version 2 CLI plans.
@@ -669,3 +669,33 @@ context start, fixture start, retirement, and positive permission forwarding
669
669
  remain closed before native access. A valid private profile reference does not
670
670
  lift this boundary. Source fixtures and descriptor reads do not establish
671
671
  private MCP adoption or actual task completion.
672
+
673
+ ### Explicit native CLI continuity
674
+
675
+ Version 2 dynamic plans may name `continuityTransport: native-cli`; version 1
676
+ plans retain their existing behavior. The original intake relation is logical
677
+ routing. It does not invent native parentage for a peer session.
678
+
679
+ The existing public `remote-context` command accepts `--continuity-input` with
680
+ one owned, closed input file. The operation uses an absolute pinned Node/CLI,
681
+ selected profile metadata and an isolated child environment. Its receipt projects
682
+ public principal, organization, Agent/version, Agent/Bytes scope, source digests,
683
+ nonce and actual successful response request IDs. Raw stderr, response bodies,
684
+ credentials and environment dumps are excluded. Missing or ambiguous response
685
+ metadata is incomplete evidence.
686
+
687
+ A receipt itself cannot acknowledge native context. Verification requires the
688
+ same owning native query, its actual model/effort acknowledgement, exact command,
689
+ turn/tool identity, successful completion/exit and matching result within the
690
+ original request history boundary, plus genuine reads of the own charter/context.
691
+ Only the exact bootstrap command may receive bootstrap permission through the
692
+ original intake. Other work also requires verified context, current policy,
693
+ actual tool outcome and separate settlement. Approval forwarding alone is not
694
+ operation completion.
695
+
696
+ Current startup provenance, profile refresh authority and native Bash completion
697
+ contracts are unavailable. Effects stay closed with
698
+ `native_cli_startup_unqualified`; an unsupported result contract stays
699
+ `native_cli_tool_result_unqualified`. Input references, hashes or JSON flags do
700
+ not grant admission. Private MCP is a separate unsupported transport and is never
701
+ a fallback. Source fixtures do not activate either native harness.
@@ -344,6 +344,7 @@ def parser():
344
344
  q.add_argument('--ownership',choices=['user','organization'],required=True);q.add_argument('--bytes-scope',choices=['private','org'],required=True);q.add_argument('--instance')
345
345
  q.add_argument('--reconcile-local-context',action='store_true',help='Explicitly reconcile a recorded partial context attachment; never replays cloud writes')
346
346
  q.add_argument('--private-profile',action='store_true',help='Bind the explicitly selected protected profile metadata for native child helpers')
347
+ if command=='remote-context':q.add_argument('--continuity-input',help='Closed native-cli continuity input; runtime authority must independently qualify before any client')
347
348
  if command=='remember':
348
349
  q.add_argument('--content',required=True);q.add_argument('--kind',choices=['instruction','knowledge','durable'],required=True)
349
350
  q.add_argument('--provenance',required=True);q.add_argument('--visibility',choices=['private','shared'],default='private')
@@ -385,7 +386,10 @@ def main(argv=None):
385
386
  lib=Library(args.store)
386
387
  if args.command in {'connect','remote-context','remember','artifact','artifact-get','remote-recover'}:
387
388
  from . import topolo
388
- with topolo.connection_lock(lib,args):result=topolo.dispatch(lib,args)
389
+ if args.command=='remote-context' and args.continuity_input:
390
+ result=topolo.native_cli_continuity_read(lib,args)
391
+ else:
392
+ with topolo.connection_lock(lib,args):result=topolo.dispatch(lib,args)
389
393
  emit(result)
390
394
  return 0
391
395
  if args.command=='lane' and args.operation=='serve':
@@ -9,6 +9,9 @@ import hashlib
9
9
  import os
10
10
  import pathlib
11
11
  import stat
12
+ import re
13
+ import uuid
14
+ import time
12
15
  from contextlib import contextmanager
13
16
 
14
17
  from .storage import PackError, atomic_json, canonical, read_json, safe_id
@@ -51,7 +54,7 @@ def _role(value, harness):
51
54
 
52
55
  def plan(request, role, intake, resolution, ceiling):
53
56
  if (not isinstance(request, dict) or not {'laneId','logicalOriginThreadId','sourceRoleKey','task'}<=set(request) or
54
- set(request)-{'laneId','logicalOriginThreadId','sourceRoleKey','task','verificationProfile'}):
57
+ set(request)-{'laneId','logicalOriginThreadId','sourceRoleKey','task','verificationProfile','continuityTransport','continuity'}):
55
58
  raise PackError('Dynamic lane request has unsupported fields')
56
59
  if request.get('verificationProfile') not in {None,'manual'}:
57
60
  raise PackError('Only a separately bound narrower manual verification profile is supported')
@@ -68,7 +71,12 @@ def plan(request, role, intake, resolution, ceiling):
68
71
  raise PackError('Dynamic lane has no exact current policy ceiling and intake route')
69
72
  for key in ['policyRevision', 'policyDigest', 'profile']:
70
73
  _identity(ceiling[key], key)
71
- return {'format': 'topolo-dynamic-lane-plan', 'schemaVersion': 1,
74
+ explicit_cli='continuityTransport' in request or 'continuity' in request
75
+ if explicit_cli:
76
+ if request.get('continuityTransport')!='native-cli' or set(request.get('continuity',{}))!={'cli','admissionReferences'}:
77
+ raise PackError('Explicit closed native-cli continuity transport required; no fallback')
78
+ native_cli_configuration(request['continuity'])
79
+ result={'format': 'topolo-dynamic-lane-plan', 'schemaVersion': 2 if explicit_cli else 1,
72
80
  'laneId': request['laneId'], 'harness': harness,
73
81
  'logicalOriginThreadId': request['logicalOriginThreadId'],
74
82
  'role': copy.deepcopy(role), 'intake': copy.deepcopy(intake),
@@ -78,6 +86,10 @@ def plan(request, role, intake, resolution, ceiling):
78
86
  'nativeParentId': intake['nativeId'] if harness == 'claude' else None,
79
87
  'nativeParentAvailability': 'actual-harness-intake' if harness == 'claude' else 'UNAVAILABLE',
80
88
  'state': 'planned', 'executionAuthorized': False}
89
+ if explicit_cli:
90
+ result.update(continuityTransport='native-cli',continuity=copy.deepcopy(request['continuity']),
91
+ nativeParentId=None,nativeParentAvailability='actual-native-peer')
92
+ return result
81
93
 
82
94
 
83
95
  def adopt(planned, receipt, native, current):
@@ -110,7 +122,7 @@ def adopt(planned, receipt, native, current):
110
122
  _identity(native.get('title'),'actual child title')
111
123
  if not pathlib.Path(receipt['cwd']).is_absolute():
112
124
  raise PackError('Created lane requires its exact absolute owned workspace')
113
- return {'format': 'topolo-dynamic-lane', 'schemaVersion': 1,
125
+ return {'format': 'topolo-dynamic-lane', 'schemaVersion': planned['schemaVersion'],
114
126
  'laneId': planned['laneId'], 'harness': planned['harness'], 'nativeId': receipt['nativeId'],
115
127
  'plan': copy.deepcopy(planned), 'planDigest': digest(planned),
116
128
  'creationReceipt': copy.deepcopy(receipt), 'creationReceiptDigest': digest(receipt),
@@ -139,6 +151,8 @@ def acknowledge_context(record, context_digest):
139
151
 
140
152
 
141
153
  def request_permission(record, request_fingerprint, request_id, generation):
154
+ if record['plan'].get('continuityTransport')=='native-cli' and record.get('nativeCliContext',{}).get('state')!='native-cli-context-ACK':
155
+ raise PackError('Native-cli work requires verified same-query public context receipt; bootstrap is separately scoped')
142
156
  if record['state'] != 'loaded-context-ACK' or generation != record['nativeConnectionGeneration']:
143
157
  raise PackError('Dynamic permission request has no current configured connection')
144
158
  _identity(request_fingerprint, 'native request fingerprint'); _identity(request_id, 'native request identity')
@@ -263,7 +277,7 @@ class Registry:
263
277
  if path.stat().st_size > 131072:
264
278
  raise PackError('Dynamic lane record exceeds its bounded schema')
265
279
  record = read_json(path)
266
- if (record.get('format') != 'topolo-dynamic-lane' or record.get('schemaVersion') != 1 or
280
+ if (record.get('format') != 'topolo-dynamic-lane' or record.get('schemaVersion') not in {1,2} or not native_cli_record_version(record) or
267
281
  record.get('laneId') != lane_id or record.get('harness') != self.harness or
268
282
  record.get('state') not in STATES or
269
283
  digest(record.get('plan')) != record.get('planDigest') or
@@ -297,7 +311,7 @@ class Registry:
297
311
 
298
312
  def write(self, record, before_digest):
299
313
  lane_id = record['laneId']
300
- if (record.get('format') != 'topolo-dynamic-lane' or record.get('schemaVersion') != 1 or
314
+ if (record.get('format') != 'topolo-dynamic-lane' or record.get('schemaVersion') not in {1,2} or not native_cli_record_version(record) or
301
315
  record['harness'] != self.harness or record['state'] not in STATES or
302
316
  digest(record['plan']) != record['planDigest'] or
303
317
  digest(record['creationReceipt']) != record['creationReceiptDigest']):
@@ -647,6 +661,14 @@ class ClaudeLifecycle:
647
661
  def dispatch(self,action,payload):
648
662
  if not isinstance(payload,dict):
649
663
  raise PackError('Closed native helper object required')
664
+ candidates=[payload.get('request'),payload.get('plan'),payload.get('operation',{}).get('record',{}).get('plan') if isinstance(payload.get('operation'),dict) and isinstance(payload.get('operation',{}).get('record',{}),dict) else None]
665
+ for candidate in candidates:
666
+ if isinstance(candidate,dict) and (candidate.get('continuityTransport')=='native-cli' or candidate.get('schemaVersion')==2 or 'continuity' in candidate):
667
+ native_cli_admission(candidate.get('continuity'))
668
+ if isinstance(payload.get('laneId'),str):
669
+ existing=self.store.read(payload['laneId'])
670
+ if existing and existing['plan'].get('continuityTransport')=='native-cli':
671
+ native_cli_admission(existing['plan']['continuity'])
650
672
  base={'models'}
651
673
  shapes={
652
674
  'peek':{'sourceRoleKey'},'plan':{'request','nativeIds'},'adopt':{'plan','receipt','native'},
@@ -747,3 +769,134 @@ def install_model_configuration(lib,pack,document,before_digest):
747
769
  raise PackError('Dynamic configuration readback differs')
748
770
  return {'state':'dynamic-task-model-intent-installed','pack':pack,'configurationDigest':digest(document),
749
771
  'nativeAvailabilityVerified':False,'existingRoleSettingsChanged':False,'modelTurnsStarted':False}
772
+
773
+
774
+ # Explicit native-query CLI continuity. These validators do not grant authority.
775
+ NATIVE_CLI_CONTEXT_FIELDS={'principalId','organizationId','contextKey','installationId','logicalId','agentId',
776
+ 'agentVersionId','agentWorkspaceId','bytesWorkspaceId','environment','bytesScope'}
777
+ NATIVE_CLI_SOURCE_FIELDS={'packRevision','planDigest','bindingDigest','policyDigest','charterDigest','contextDigest'}
778
+ NATIVE_CLI_CLI_FIELDS={'nodeExecutable','nodeDigest','entry','entryDigest','version'}
779
+ NATIVE_CLI_AUTHORITY_FIELDS={'startupEvidencePath','startupEvidenceDigest','profileAuthorityPath','profileAuthorityDigest','expiresAt'}
780
+ NATIVE_CLI_INPUT_FIELDS={'format','schemaVersion','operationId','nonce','transport','harness','laneId','nativeId',
781
+ 'sourceRoleKey','intakeKey','intakeNativeId','nativeConnectionGeneration','context','source','cli','admissionReferences','deadline'}
782
+ NATIVE_CLI_RECEIPT_FIELDS={'format','schemaVersion','operationId','nonce','transport','harness','laneId','nativeId',
783
+ 'nativeConnectionGeneration','context','source','cli','responses','claim'}
784
+
785
+
786
+ def native_cli_closed(value,fields,label):
787
+ if not isinstance(value,dict) or set(value)!=fields:
788
+ raise PackError('Closed native-cli '+label+' required')
789
+
790
+
791
+ def native_cli_hash(value):
792
+ if not isinstance(value,str) or not re.fullmatch('[a-f0-9]{64}',value):
793
+ raise PackError('Exact native-cli source digest required')
794
+
795
+
796
+ def native_cli_configuration(value):
797
+ native_cli_closed(value,{'cli','admissionReferences'},'configuration')
798
+ cli=value['cli'];refs=value['admissionReferences']
799
+ native_cli_closed(cli,NATIVE_CLI_CLI_FIELDS,'CLI identity')
800
+ native_cli_closed(refs,NATIVE_CLI_AUTHORITY_FIELDS,'authority references')
801
+ for path in [cli['nodeExecutable'],cli['entry'],refs['startupEvidencePath'],refs['profileAuthorityPath']]:
802
+ if not isinstance(path,str) or len(path)>4096 or not pathlib.Path(path).is_absolute() or any(ord(c)<32 for c in path):
803
+ raise PackError('Exact absolute native-cli path reference required')
804
+ for value in [cli['nodeDigest'],cli['entryDigest'],refs['startupEvidenceDigest'],refs['profileAuthorityDigest']]:
805
+ native_cli_hash(value)
806
+ if not isinstance(cli['version'],str) or not re.fullmatch(r'0\.13\.\d+',cli['version']):
807
+ raise PackError('Immutable native-cli version required')
808
+ if type(refs['expiresAt']) is not int or refs['expiresAt']<=time.time():
809
+ raise PackError('Current native-cli authority reference required; reference alone is not authority')
810
+
811
+
812
+ def native_cli_input(value):
813
+ native_cli_closed(value,NATIVE_CLI_INPUT_FIELDS,'input')
814
+ if (value['format']!='topolo-native-cli-continuity-input' or type(value['schemaVersion']) is not int or value['schemaVersion']!=1 or
815
+ value['transport']!='native-cli' or value['harness'] not in {'codex','claude'}):
816
+ raise PackError('Explicit versioned native-cli input required')
817
+ try:
818
+ if str(uuid.UUID(value['operationId']))!=value['operationId']:
819
+ raise ValueError()
820
+ except (ValueError,TypeError,AttributeError) as error:
821
+ raise PackError('Exact native-cli operation UUID required') from error
822
+ native_cli_hash(value['nonce']);safe_id(value['laneId'])
823
+ for key in ['nativeId','sourceRoleKey','intakeKey','intakeNativeId','nativeConnectionGeneration']:
824
+ _identity(value[key],key)
825
+ if '/intakes/' not in value['intakeKey'] or value['nativeId']==value['intakeNativeId']:
826
+ raise PackError('Exact independent child and original intake required')
827
+ context=value['context'];source=value['source']
828
+ native_cli_closed(context,NATIVE_CLI_CONTEXT_FIELDS,'public context');native_cli_closed(source,NATIVE_CLI_SOURCE_FIELDS,'source')
829
+ for entry in context.values():
830
+ _identity(entry,'public context')
831
+ if (context['environment'] not in {'production','staging','development'} or context['bytesScope'] not in {'private','org'} or
832
+ context['contextKey']!='org:'+context['organizationId']):
833
+ raise PackError('Exact native-cli environment, organization and Bytes scope required')
834
+ _identity(source['packRevision'],'pack revision')
835
+ for key in NATIVE_CLI_SOURCE_FIELDS-{'packRevision'}:
836
+ native_cli_hash(source[key])
837
+ native_cli_configuration({'cli':value['cli'],'admissionReferences':value['admissionReferences']})
838
+ if type(value['deadline']) is not int or value['deadline']<=time.time() or value['deadline']>value['admissionReferences']['expiresAt']:
839
+ raise PackError('Current bounded native-cli operation deadline required')
840
+ return copy.deepcopy(value)
841
+
842
+
843
+ def native_cli_admission(_value):
844
+ # No supported current startup/refresh authority producer is installed.
845
+ # Never turn hashes, a caller boolean or JSON evidence references into one.
846
+ raise PackError('native_cli_startup_unqualified: current startup, profile refresh authority and native result contracts are unavailable')
847
+
848
+
849
+ def native_cli_receipt(expected,receipt):
850
+ expected=native_cli_input(expected);native_cli_closed(receipt,NATIVE_CLI_RECEIPT_FIELDS,'receipt')
851
+ if (receipt['format']!='topolo-native-cli-continuity-receipt' or type(receipt['schemaVersion']) is not int or receipt['schemaVersion']!=1 or
852
+ receipt['claim']!='PUBLIC_IDENTITY_CONTEXT_RESPONSE_ONLY_NATIVE_ATTRIBUTION_REQUIRED'):
853
+ raise PackError('Native-cli public receipt format differs')
854
+ for key in NATIVE_CLI_RECEIPT_FIELDS-{'format','schemaVersion','responses','claim'}:
855
+ if receipt[key]!=expected[key]:
856
+ raise PackError('Native-cli receipt identity/source/operation differs')
857
+ native_cli_closed(receipt['responses'],{'identity','context'},'responses');seen=set()
858
+ for kind,response in receipt['responses'].items():
859
+ native_cli_closed(response,{'requestEvents','bodyProjectionDigest','actionId','resourceType','resourceId'},'response evidence')
860
+ native_cli_hash(response['bodyProjectionDigest']);events=response['requestEvents']
861
+ if not isinstance(events,list) or not 1<=len(events)<=8:
862
+ raise PackError('Actual bounded native-cli response request IDs required')
863
+ services=set()
864
+ for event in events:
865
+ native_cli_closed(event,{'requestId','method','service','path','status'},'request event')
866
+ identity=event['requestId'];path=event['path']
867
+ if (not isinstance(identity,str) or identity=='unavailable' or not re.fullmatch('[A-Za-z0-9][A-Za-z0-9._:-]{0,127}',identity) or
868
+ identity in seen or event['method'] not in {'GET','POST'} or event['service'] not in {'auth','agent'} or
869
+ type(event['status']) is not int or not 200<=event['status']<300 or not isinstance(path,str) or
870
+ not path.startswith('/') or len(path)>1024 or any(c in path for c in ['?','#','\\','\n','\r'])):
871
+ raise PackError('Actual canonical successful native-cli response evidence required')
872
+ seen.add(identity);services.add(event['service'])
873
+ wanted=(None,None,None) if kind=='identity' else ('app_topolo_agent.agent_packs.context','workspace',expected['context']['agentWorkspaceId'])
874
+ if (response['actionId'],response['resourceType'],response['resourceId'])!=wanted or ('auth' if kind=='identity' else 'agent') not in services:
875
+ raise PackError('Native-cli response action/resource attribution differs')
876
+ return copy.deepcopy(receipt)
877
+
878
+
879
+ def native_cli_context_ack(record,input,receipt,proof):
880
+ receipt=native_cli_receipt(input,receipt)
881
+ if (record.get('schemaVersion')!=2 or record['plan'].get('continuityTransport')!='native-cli' or
882
+ record['nativeId']!=input['nativeId'] or record['nativeConnectionGeneration']!=input['nativeConnectionGeneration'] or
883
+ record['state']!='loaded-context-ACK' or record['planDigest']!=input['source']['planDigest']):
884
+ raise PackError('Exact configured native-cli lane required before context ACK')
885
+ native_cli_closed(proof,{'harness','nativeId','generation','turnId','itemId','commandDigest','exitCode','receiptDigest'},'native tool attribution')
886
+ if (proof['harness']!=input['harness'] or proof['nativeId']!=input['nativeId'] or proof['generation']!=input['nativeConnectionGeneration'] or
887
+ proof['exitCode']!=0 or type(proof['exitCode'])is not int or proof['receiptDigest']!=digest(receipt)):
888
+ raise PackError('Actual same-query successful native tool attribution differs')
889
+ for key in ['turnId','itemId']:
890
+ _identity(proof[key],key)
891
+ native_cli_hash(proof['commandDigest'])
892
+ return {**record,'nativeCliContext':{'state':'native-cli-context-ACK','operationId':input['operationId'],
893
+ 'nonce':input['nonce'],'receiptDigest':digest(receipt),'proof':copy.deepcopy(proof)}}
894
+
895
+
896
+ def native_cli_record_version(record):
897
+ planned=record.get('plan',{})
898
+ if record.get('schemaVersion')==1:
899
+ return planned.get('schemaVersion')==1 and 'continuityTransport' not in planned
900
+ return (planned.get('schemaVersion')==2 and planned.get('continuityTransport')=='native-cli' and
901
+ planned.get('nativeParentId') is None and isinstance(planned.get('continuity'),dict) and
902
+ set(planned['continuity'])=={'cli','admissionReferences'})
@@ -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,56 @@ 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 compile_native_cli_command(value,store,pack,input_path,environment):
1994
+ """Compile a fixed native tool command; no shell/model text is accepted."""
1995
+ import shlex
1996
+ from . import lanes
1997
+ value=lanes.native_cli_input(value)
1998
+ allowed={'HOME','USER','LOGNAME','PATH','LANG','LC_ALL','TMPDIR','TOPOLO_CONFIG_PATH','TOPOLO_ENV','TOPOLO_CONTEXT'}
1999
+ if (not isinstance(environment,dict) or not set(environment)<=allowed or
2000
+ any(not isinstance(v,str) or len(v)>4096 or any(ord(c)<32 for c in v) for v in environment.values()) or
2001
+ not environment.get('TOPOLO_CONFIG_PATH') or not pathlib.Path(environment['TOPOLO_CONFIG_PATH']).is_absolute() or
2002
+ environment.get('TOPOLO_ENV')!=value['context']['environment'] or environment.get('TOPOLO_CONTEXT')!=value['context']['contextKey']):
2003
+ raise PackError('Exact child-only native-cli environment required; inherited credentials are forbidden')
2004
+ if any(not pathlib.Path(p).is_absolute() for p in [store,input_path]) or pack!=value['sourceRoleKey'].split('/')[0]:
2005
+ raise PackError('Exact owned store, pack and input references required')
2006
+ argv=['/usr/bin/env','-i',*[k+'='+environment[k] for k in sorted(environment)],value['cli']['nodeExecutable'],value['cli']['entry'],
2007
+ 'agent-packs','--store',str(store),'remote-context',pack,'--environment',value['context']['environment'],
2008
+ '--context-key',value['context']['contextKey'],'--role',value['context']['logicalId'],'--continuity-input',str(input_path)]
2009
+ return shlex.join(argv)
2010
+
2011
+
2012
+ def native_cli_codex_tool_proof(value,command,result,turn_id,cwd,guide_command,guide_digest):
2013
+ """Verify actual owned Codex tool DTOs; printed/foreign receipts do not ACK."""
2014
+ import shlex
2015
+ from . import lanes
2016
+ value=lanes.native_cli_input(value);lanes.native_cli_hash(guide_digest)
2017
+ if guide_digest!=value['source']['charterDigest']:
2018
+ raise PackError('Native-cli actual charter read differs from the pinned receipt source')
2019
+ thread=result.get('thread',{});turns=[t for t in thread.get('turns',[]) if t.get('id')==turn_id]
2020
+ if (thread.get('id')!=value['nativeId'] or thread.get('cwd')!=str(cwd) or len(turns)!=1 or
2021
+ turns[0].get('status')!='completed' or turns[0].get('error')):
2022
+ raise PackError('Exact same-child native-cli turn/cwd completion required')
2023
+ receipts=[];reads=[]
2024
+ for item in turns[0].get('items',[]):
2025
+ 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'):
2026
+ continue
2027
+ actual=item.get('command');args=shlex.split(actual) if isinstance(actual,str) else []
2028
+ unwrapped=args[2] if len(args)==3 and args[0] in {'/bin/sh','/bin/bash','/bin/zsh'} and args[1]=='-c' else actual
2029
+ if unwrapped==guide_command and hashlib.sha256(item.get('aggregatedOutput','').encode()).hexdigest()==guide_digest:
2030
+ reads.append(item['id'])
2031
+ if unwrapped!=command:
2032
+ continue
2033
+ try:
2034
+ receipt=json.loads(item.get('aggregatedOutput',''))
2035
+ except (ValueError,TypeError):
2036
+ continue
2037
+ receipt=lanes.native_cli_receipt(value,receipt);receipts.append((item['id'],receipt))
2038
+ if len(receipts)!=1 or len(reads)!=1 or receipts[0][0]==reads[0]:
2039
+ raise PackError('Actual native-cli command/receipt and own charter read require unique successful tool results')
2040
+ identity,receipt=receipts[0]
2041
+ proof={'harness':'codex','nativeId':value['nativeId'],'generation':value['nativeConnectionGeneration'],'turnId':turn_id,
2042
+ 'itemId':identity,'commandDigest':lanes.digest(command),'exitCode':0,'receiptDigest':lanes.digest(receipt)}
2043
+ return {'receipt':receipt,'proof':proof,'nativeCharterReadItemId':reads[0]}
@@ -6,21 +6,23 @@ class ConnectionSourceRefused(PackError):
6
6
  pass
7
7
 
8
8
  class Client:
9
- def __init__(self,environment,context,binary=None):
9
+ def __init__(self,environment,context,binary=None,env=None,node=None):
10
10
  if environment not in {'development','staging','production'}:
11
11
  raise PackError('Explicit Topolo environment required')
12
12
  if not context or any(c in context for c in '\n\r'):
13
13
  raise PackError('Explicit Topolo context required')
14
- if any(os.environ.get(k) for k in ['TOPOLO_API_KEY','TOPOLO_ACCESS_TOKEN','TOPOLO_'+environment.upper()+'_API_KEY','TOPOLO_'+environment.upper()+'_ACCESS_TOKEN']):
14
+ selected=os.environ if env is None else env
15
+ if any(selected.get(k) for k in ['TOPOLO_API_KEY','TOPOLO_ACCESS_TOKEN','TOPOLO_'+environment.upper()+'_API_KEY','TOPOLO_'+environment.upper()+'_ACCESS_TOKEN']):
15
16
  raise PackError('Pinned connections require the CLI protected account context, not injected credentials')
16
17
  self.environment=environment;self.context=context
17
18
  self.binary=binary or shutil.which('topolo')
18
19
  if not self.binary:
19
20
  raise PackError('Install the public @topolo/cli and authenticate before connecting')
20
- self.env={**os.environ,'TOPOLO_CONTEXT':context}
21
- self.env['PATH']=str(pathlib.Path(self.binary).parent)+os.pathsep+self.env.get('PATH','')
21
+ self.env={**selected,'TOPOLO_CONTEXT':context}
22
+ self.command=[node,self.binary] if node else [self.binary]
23
+ self.env['PATH']=(str(pathlib.Path(node).parent)+os.pathsep+'/usr/bin:/bin') if node else str(pathlib.Path(self.binary).parent)+os.pathsep+self.env.get('PATH','')
22
24
  def run(self,args):
23
- p=subprocess.run([self.binary,'--env',self.environment,*args,'--json'],env=self.env,capture_output=True,text=True,timeout=90)
25
+ p=subprocess.run([*self.command,'--env',self.environment,*args,'--json'],env=self.env,capture_output=True,text=True,timeout=90)
24
26
  if p.returncode:
25
27
  raise PackError('Topolo command failed (exit '+str(p.returncode)+'); inspect the published action, account permissions and environment. No fallback was used.')
26
28
  try:value=json.loads(p.stdout)
@@ -599,6 +601,8 @@ def connection_lock(lib,args):
599
601
  yield
600
602
 
601
603
  def dispatch(lib,args):
604
+ if args.command=='remote-context' and getattr(args,'continuity_input',None):
605
+ return native_cli_continuity_read(lib,args)
602
606
  client=Client(args.environment,args.context_key)
603
607
  if args.command=='connect':return connect(lib,args,client)
604
608
  if args.command=='remote-context':return remote_context(lib,args,client)[1]
@@ -610,3 +614,124 @@ def dispatch(lib,args):
610
614
  reference=read_json(args.reference)
611
615
  return read_artifact(client,binding,reference['key'],reference,args.destination)
612
616
  raise PackError('Unsupported Topolo command')
617
+
618
+
619
+
620
+ def native_cli_response(stdout,stderr,service):
621
+ """Preserve only canonical public response metadata; never echo raw stderr."""
622
+ from . import lanes
623
+ if len(stdout.encode())>262144 or len(stderr.encode())>65536:
624
+ raise PackError('native_cli_request_metadata_unavailable: bounded public response required')
625
+ try:
626
+ body=json.loads(stdout)
627
+ except ValueError as error:
628
+ raise PackError('native_cli_request_metadata_unavailable: invalid public JSON') from error
629
+ if not isinstance(body,dict) or body.get('error'):
630
+ raise PackError('native_cli_request_metadata_unavailable: unsuccessful public response')
631
+ events=[];seen=set()
632
+ pattern=r'^\[topolo\] ← (\d{3}) (GET|POST) (auth|agent) (/[^\s?#\\]+) \([0-9.]+ms, req=([A-Za-z0-9][A-Za-z0-9._:-]{0,127})\)$'
633
+ for line in stderr.splitlines():
634
+ match=re.fullmatch(pattern,line)
635
+ if not match:
636
+ continue
637
+ status,method,target,path,request_id=match.groups()
638
+ if request_id=='unavailable' or request_id in seen or not 200<=int(status)<300:
639
+ raise PackError('native_cli_request_metadata_unavailable: ambiguous or unsuccessful response request IDs')
640
+ seen.add(request_id);events.append({'requestId':request_id,'method':method,'service':target,'path':path,'status':int(status)})
641
+ if not 1<=len(events)<=8 or service not in {event['service'] for event in events}:
642
+ raise PackError('native_cli_request_metadata_unavailable: actual canonical server response IDs required')
643
+ return body.get('data',body),events
644
+
645
+
646
+ def native_cli_command_response(client,args,service):
647
+ env={**client.env,'TOPOLO_DEBUG':'1'}
648
+ result=subprocess.run([*client.command,'--env',client.environment,*args,'--json'],env=env,
649
+ capture_output=True,text=True,timeout=90)
650
+ if result.returncode:
651
+ raise PackError('native_cli_request_metadata_unavailable: public command refused; no fallback or retry')
652
+ return native_cli_response(result.stdout,result.stderr,service)
653
+
654
+
655
+ def native_cli_selected_environment(value,binding,host_env):
656
+ from . import lanes
657
+ from types import SimpleNamespace
658
+ context=value['context'];profile=binding.get('privateCredentialProfile')
659
+ lanes.native_cli_closed(profile,{'format','schemaVersion','configPath','environment','contextKey','identity'},'private profile reference')
660
+ if (profile['format']!='topolo-private-credential-profile' or profile['schemaVersion']!=1 or
661
+ profile['environment']!=context['environment'] or profile['contextKey']!=context['contextKey'] or
662
+ profile['identity']!={'userId':context['principalId'],'organizationId':context['organizationId']}):
663
+ raise PackError('Exact native-cli approved profile/principal/context reference required')
664
+ selected=SimpleNamespace(env={'TOPOLO_CONFIG_PATH':profile['configPath']},environment=context['environment'],context=context['contextKey'])
665
+ args=SimpleNamespace(private_profile=True,environment=context['environment'],context_key=context['contextKey'])
666
+ private_profile_reference(selected,args)
667
+ for path,sha in [(value['cli']['nodeExecutable'],value['cli']['nodeDigest']),(value['cli']['entry'],value['cli']['entryDigest'])]:
668
+ p=pathlib.Path(path);info=p.lstat()
669
+ if (p.resolve()!=p or not stat.S_ISREG(info.st_mode) or info.st_uid not in {0,os.getuid()} or info.st_mode&0o022 or
670
+ not info.st_mode&0o111 or hashlib.sha256(p.read_bytes()).hexdigest()!=sha):
671
+ raise PackError('Native-cli executable source/provenance changed')
672
+ allowed={'HOME','USER','LOGNAME','PATH','LANG','LC_ALL','TMPDIR'}
673
+ env={k:v for k,v in host_env.items() if k in allowed and isinstance(v,str)}
674
+ env['PATH']=str(pathlib.Path(value['cli']['nodeExecutable']).parent)+os.pathsep+'/usr/bin:/bin'
675
+ env.update(TOPOLO_CONFIG_PATH=profile['configPath'],TOPOLO_ENV=context['environment'],TOPOLO_CONTEXT=context['contextKey'])
676
+ return env
677
+
678
+
679
+ def native_cli_continuity_read(lib,args,*,_admit=None,_client_factory=None,_response=None):
680
+ """Native command producer. No current runtime authority producer exists.
681
+
682
+ Injected callbacks are internal hermetic seams, never CLI/JSON options.
683
+ Receipt output is public response evidence, not native-query attribution.
684
+ """
685
+ from . import lanes
686
+ path=pathlib.Path(args.continuity_input);info=path.lstat()
687
+ if (path.is_symlink() or not stat.S_ISREG(info.st_mode) or info.st_uid!=os.getuid() or info.st_nlink!=1 or
688
+ info.st_mode&0o077 or info.st_size>65536):
689
+ raise PackError('Exact owned bounded native-cli input required')
690
+ value=lanes.native_cli_input(read_json(path))
691
+ (lanes.native_cli_admission if _admit is None else _admit)(value)
692
+ context=value['context']
693
+ if (args.environment!=context['environment'] or args.context_key!=context['contextKey'] or args.role!=context['logicalId'] or
694
+ value['sourceRoleKey']!=args.name+'/'+args.role):
695
+ raise PackError('Native-cli command differs from the exact source/operation')
696
+ file=binding_path(lib,args.name,args.environment,args.context_key);info=file.lstat()
697
+ if (file.is_symlink() or not stat.S_ISREG(info.st_mode) or info.st_uid!=os.getuid() or info.st_nlink!=1 or
698
+ hashlib.sha256(file.read_bytes()).hexdigest()!=value['source']['bindingDigest']):
699
+ raise PackError('Native-cli current owned binding source differs')
700
+ binding=read_json(file);roles=[r for r in binding['input']['roles'] if r['logicalId']==args.role]
701
+ if (len(roles)!=1 or binding['identity']!={'userId':context['principalId'],'organizationId':context['organizationId']} or
702
+ binding['packRevision']!=value['source']['packRevision'] or binding['input']['instanceId']!=context['installationId'] or
703
+ binding['agentWorkspaceId']!=context['agentWorkspaceId'] or binding['input']['bytesWorkspaceId']!=context['bytesWorkspaceId'] or
704
+ binding['input']['bytesScope']!=context['bytesScope'] or roles[0]['agentId']!=context['agentId'] or roles[0]['agentVersionId']!=context['agentVersionId']):
705
+ raise PackError('Native-cli source principal/Agent/Bytes binding differs')
706
+ env=native_cli_selected_environment(value,binding,os.environ)
707
+ client=(Client if _client_factory is None else _client_factory)(args.environment,args.context_key,
708
+ binary=value['cli']['entry'],env=env,node=value['cli']['nodeExecutable'])
709
+ call=native_cli_command_response if _response is None else _response
710
+ identity,identity_events=call(client,['whoami'],'auth')
711
+ if (identity.get('user',{}).get('id')!=context['principalId'] or (identity.get('organization')or{}).get('id')!=context['organizationId']):
712
+ raise PackError('Actual native-cli public identity differs')
713
+ with tempfile.TemporaryDirectory(prefix='topolo-native-cli-context-') as directory:
714
+ input_file=pathlib.Path(directory)/'input.json';atomic_json(input_file,{'instanceId':context['installationId'],'logicalId':context['logicalId']})
715
+ envelope,context_events=call(client,['actions','call','app_topolo_agent.agent_packs.context','--detail','--data','@'+str(input_file),
716
+ '--resource-type','workspace','--resource-id',context['agentWorkspaceId']],'agent')
717
+ result=envelope.get('data',envelope)
718
+ actual=result.get('context',{})
719
+ expected={'organizationId':context['organizationId'],'workspaceId':context['agentWorkspaceId'],'instanceId':context['installationId'],
720
+ 'logicalId':context['logicalId'],'agentId':context['agentId'],'agentVersionId':context['agentVersionId'],
721
+ 'artifacts':{'appId':'app_topolo_bytes','resourceType':'workspace','resourceId':context['bytesWorkspaceId'],'scope':context['bytesScope']}}
722
+ if envelope.get('success') is False or any(actual.get(k)!=v for k,v in expected.items()):
723
+ raise PackError('Actual native-cli public Agent/Bytes context differs')
724
+ # Recheck mutable public binding, binary and stat-only profile after awaited reads.
725
+ if hashlib.sha256(file.read_bytes()).hexdigest()!=value['source']['bindingDigest']:
726
+ raise PackError('Native-cli binding changed during response; no receipt ACK')
727
+ native_cli_selected_environment(value,binding,os.environ)
728
+ (lanes.native_cli_admission if _admit is None else _admit)(value)
729
+ responses={}
730
+ for kind,body,events in [('identity',{'userId':context['principalId'],'organizationId':context['organizationId']},identity_events),
731
+ ('context',expected,context_events)]:
732
+ responses[kind]={'requestEvents':events,'bodyProjectionDigest':lanes.digest(body),'actionId':None if kind=='identity' else 'app_topolo_agent.agent_packs.context',
733
+ 'resourceType':None if kind=='identity' else 'workspace','resourceId':None if kind=='identity' else context['agentWorkspaceId']}
734
+ receipt={k:value[k] for k in lanes.NATIVE_CLI_RECEIPT_FIELDS-{'format','schemaVersion','responses','claim'}}
735
+ receipt.update(format='topolo-native-cli-continuity-receipt',schemaVersion=1,responses=responses,
736
+ claim='PUBLIC_IDENTITY_CONTEXT_RESPONSE_ONLY_NATIVE_ATTRIBUTION_REQUIRED')
737
+ return lanes.native_cli_receipt(value,receipt)
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/lib/claude-pack-backend.ts
4
- import { createHash, randomBytes, randomUUID } from "crypto";
4
+ import { createHash as createHash2, randomBytes, randomUUID } from "crypto";
5
5
  import { closeSync, existsSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, realpathSync, renameSync, unlinkSync, writeFileSync } from "fs";
6
6
  import * as filesystem from "fs";
7
7
  import * as cryptography from "crypto";
@@ -13,6 +13,7 @@ import { createConnection } from "net";
13
13
  import { WebSocket } from "undici";
14
14
 
15
15
  // src/lib/claude-native-pack.ts
16
+ import { createHash } from "crypto";
16
17
  async function nativeClaudePackOperation(manager, sidebar, plan, apply, checkpoint, verifyContext = () => {
17
18
  }) {
18
19
  const fenceKey = /* @__PURE__ */ Symbol.for("topolo.agent-pack.native-operation.v1");
@@ -849,7 +850,7 @@ async function nativeClaudeApprovalOperation(manager, routes, operation, fingerp
849
850
  const session = await manager.getSession(route.sessionId);
850
851
  if (!session || session.sessionId !== route.sessionId || session.cwd !== route.cwd || session.title !== route.title || session.isArchived)
851
852
  throw new Error("Native approval session identity differs: " + route.key);
852
- if (!descriptor || descriptor.sessionId !== route.sessionId || descriptor.cwd !== route.cwd || descriptor.title !== route.title || descriptor.isArchived || descriptor.backend?.kind !== "local" || descriptor.backend.remoteTarget || descriptor.worktreePath || descriptor.worktreeLazy || (route.dynamic ? descriptor.spawnedFrom?.sessionId !== route.dynamic.nativeParentId || descriptor.spawnedFrom?.sideSession !== true || route.dynamic.nativeParentId !== route.intakeSessionId || !/^[a-f0-9]{64}$/.test(route.dynamic.planDigest) : descriptor.spawnedFrom))
853
+ if (!descriptor || descriptor.sessionId !== route.sessionId || descriptor.cwd !== route.cwd || descriptor.title !== route.title || descriptor.isArchived || descriptor.backend?.kind !== "local" || descriptor.backend.remoteTarget || descriptor.worktreePath || descriptor.worktreeLazy || (route.dynamic ? (route.dynamic.continuityTransport === "native-cli" ? Boolean(descriptor.spawnedFrom) || route.dynamic.nativeParentId !== null : descriptor.spawnedFrom?.sessionId !== route.dynamic.nativeParentId || descriptor.spawnedFrom?.sideSession !== true || route.dynamic.nativeParentId !== route.intakeSessionId) || !/^[a-f0-9]{64}$/.test(route.dynamic.planDigest) : descriptor.spawnedFrom))
853
854
  throw new Error("Native approval session descriptor identity differs: " + route.key);
854
855
  return session;
855
856
  };
@@ -993,11 +994,30 @@ async function nativeClaudeApprovalOperation(manager, routes, operation, fingerp
993
994
  if (operation.kind === "list") return { requests: [...state.entries.values()].map((e) => ({ ...e.record })) };
994
995
  const entry = state.entries.get(operation.key);
995
996
  if (!entry || entry.record.state !== "pending" || entry.record.intakeSessionId !== operation.intake || entry.record.fingerprint !== operation.fingerprint) throw new Error("Exact pending request and owning intake required.");
996
- if (operation.kind === "respond" && operation.decision === "once" && byId.get(entry.record.sourceSessionId)?.dynamic)
997
- throw Object.assign(
997
+ let cliFinalGuard;
998
+ if (operation.kind === "respond" && operation.decision === "once" && byId.get(entry.record.sourceSessionId)?.dynamic) {
999
+ const route = byId.get(entry.record.sourceSessionId);
1000
+ if (route.dynamic?.continuityTransport === "native-cli") {
1001
+ const producer = state.dynamicLifecycle?.get(entry.record.sourceSessionId)?.authorizeNativeCLI;
1002
+ if (typeof producer !== "function") throw Object.assign(new Error("native_cli_startup_unqualified: actual native CLI admission producer unavailable."), { code: "native_cli_startup_unqualified" });
1003
+ const admission = await producer(entry.record, entry.request, operation);
1004
+ if (!admission || !["bootstrap", "work"].includes(admission.phase) || admission.nativeId !== entry.record.sourceSessionId || admission.intakeNativeId !== entry.record.intakeSessionId || admission.fingerprint !== entry.record.fingerprint || admission.generation !== route.dynamic.generation || typeof route.dynamic.generation !== "string" || !route.dynamic.generation || !admission.query || typeof admission.verifyCurrent !== "function" || typeof admission.model !== "string" || !admission.model || typeof admission.effort !== "string" || !admission.effort)
1005
+ throw new Error("Exact current native CLI query/intake admission differs.");
1006
+ const raw = await manager.getSessionWhenLoaded(entry.record.sourceSessionId, "topolo-native-cli-permission", { warnOnMiss: false });
1007
+ if (raw?.query !== admission.query || typeof manager.readRunningModel !== "function" || typeof manager.getEffort !== "function" || await manager.readRunningModel(raw, raw.query) !== admission.model || await manager.getEffort(raw.sessionId) !== admission.effort)
1008
+ throw new Error("Actual same-query native model/effort acknowledgement differs.");
1009
+ cliFinalGuard = () => {
1010
+ if (state.dynamicLifecycle?.get(entry.record.sourceSessionId)?.authorizeNativeCLI !== producer || raw.query !== admission.query || admission.verifyCurrent() !== void 0) throw new Error("Current native CLI admission changed before dispatch.");
1011
+ };
1012
+ if (admission.phase === "bootstrap") {
1013
+ if (entry.request.toolName !== "Bash" || entry.request.input?.command !== admission.command || admission.commandDigest !== fingerprint(admission.command))
1014
+ throw new Error("Only the exact native CLI bootstrap command may precede context acknowledgement.");
1015
+ } else if (!/^[a-f0-9]{64}$/.test(admission.contextReceiptDigest ?? "")) throw new Error("Native CLI work requires verified same-query context receipt.");
1016
+ } else throw Object.assign(
998
1017
  new Error("native_private_query_prestart_contract_unqualified: dynamic private query authorization remains closed."),
999
1018
  { code: "native_private_query_prestart_contract_unqualified" }
1000
1019
  );
1020
+ }
1001
1021
  const pending = await current(entry);
1002
1022
  if (manager[symbol] !== state || state.entries.get(operation.key) !== entry || entry.record.state !== "pending" || state.auditFailed)
1003
1023
  throw new Error("Exact pending request changed during native identity validation; reconcile without replay.");
@@ -1005,6 +1025,7 @@ async function nativeClaudeApprovalOperation(manager, routes, operation, fingerp
1005
1025
  if (operation.kind !== "respond" || operation.humanDecisionRecorded !== true || !["once", "deny"].includes(operation.decision ?? ""))
1006
1026
  throw new Error("Recorded human one-request decision required; never auto-approve.");
1007
1027
  if (operation.decision !== "deny" && entry.record.cardOnly) throw new Error("Claude requires the original card; intake cannot impersonate its origin.");
1028
+ cliFinalGuard?.();
1008
1029
  entry.record.state = "response-may-have-effects";
1009
1030
  checkpoint({ ...entry.record });
1010
1031
  await manager.respondToToolPermission(pending.requestId, operation.decision, void 0, "elsewhere");
@@ -1019,17 +1040,26 @@ async function nativeClaudeApprovalOperation(manager, routes, operation, fingerp
1019
1040
  }
1020
1041
  async function nativeClaudeAppendDynamicRoute(manager, route, verify, checkpoint, lifecycle) {
1021
1042
  const state = manager[/* @__PURE__ */ Symbol.for("topolo.agent-pack.approvals.v1")];
1022
- if (!state || state.auditFailed || !state.byId || !state.byKey || !route.dynamic || !/^[a-f0-9]{64}$/.test(route.dynamic.planDigest) || route.dynamic.nativeParentId !== route.intakeSessionId)
1043
+ if (!state || state.auditFailed || !state.byId || !state.byKey || !route.dynamic || !/^[a-f0-9]{64}$/.test(route.dynamic.planDigest) || (route.dynamic.continuityTransport === "native-cli" ? route.dynamic.nativeParentId !== null : route.dynamic.nativeParentId !== route.intakeSessionId))
1023
1044
  throw new Error("Current qualified original approval listener required.");
1045
+ let cliAdmission;
1046
+ if (route.dynamic.continuityTransport === "native-cli") {
1047
+ if (typeof lifecycle?.authorizeNativeCLI !== "function") throw Object.assign(new Error("native_cli_startup_unqualified: actual native CLI admission producer unavailable."), { code: "native_cli_startup_unqualified" });
1048
+ cliAdmission = await lifecycle.authorizeNativeCLI(null, null, { kind: "route-append" });
1049
+ }
1024
1050
  const intake = state.byId.get(route.intakeSessionId);
1025
1051
  if (!intake || intake.key !== route.intakeKey || intake.contextKey !== route.contextKey || !intake.key.includes("/intakes/"))
1026
1052
  throw new Error("Dynamic approval route has no exact same-context original intake.");
1027
1053
  await verify();
1028
1054
  const raw = await manager.getSessionWhenLoaded(route.sessionId, "topolo-dynamic-route", { warnOnMiss: false });
1029
1055
  const dto = await manager.getSession(route.sessionId);
1030
- if (!raw || !dto || raw.sessionId !== route.sessionId || raw.cwd !== route.cwd || raw.title !== route.title || dto.sessionId !== route.sessionId || dto.cwd !== route.cwd || dto.title !== route.title || raw.isArchived || dto.isArchived || raw.backend?.kind !== "local" || raw.backend.remoteTarget || raw.worktreePath || raw.worktreeLazy || raw.spawnedFrom?.sessionId !== route.intakeSessionId || raw.spawnedFrom?.sideSession !== true)
1056
+ if (!raw || !dto || raw.sessionId !== route.sessionId || raw.cwd !== route.cwd || raw.title !== route.title || dto.sessionId !== route.sessionId || dto.cwd !== route.cwd || dto.title !== route.title || raw.isArchived || dto.isArchived || raw.backend?.kind !== "local" || raw.backend.remoteTarget || raw.worktreePath || raw.worktreeLazy || (route.dynamic.continuityTransport === "native-cli" ? Boolean(raw.spawnedFrom) : raw.spawnedFrom?.sessionId !== route.intakeSessionId || raw.spawnedFrom?.sideSession !== true))
1031
1057
  throw new Error("Actual dynamic child descriptor differs before route append.");
1058
+ if (route.dynamic.continuityTransport === "native-cli" && (!cliAdmission?.query || raw.query !== cliAdmission.query || cliAdmission.nativeId !== route.sessionId || cliAdmission.intakeNativeId !== route.intakeSessionId || cliAdmission.generation !== route.dynamic.generation || typeof route.dynamic.generation !== "string" || !route.dynamic.generation || typeof cliAdmission.verifyCurrent !== "function" || typeof cliAdmission.model !== "string" || !cliAdmission.model || typeof cliAdmission.effort !== "string" || !cliAdmission.effort || typeof manager.readRunningModel !== "function" || typeof manager.getEffort !== "function" || await manager.readRunningModel(raw, raw.query) !== cliAdmission.model || await manager.getEffort(raw.sessionId) !== cliAdmission.effort))
1059
+ throw new Error("Actual native CLI route query/model/effort admission differs.");
1032
1060
  await verify();
1061
+ if (route.dynamic.continuityTransport === "native-cli" && (raw.query !== cliAdmission.query || cliAdmission.verifyCurrent() !== void 0))
1062
+ throw new Error("Current native CLI route admission changed before append.");
1033
1063
  if (manager[/* @__PURE__ */ Symbol.for("topolo.agent-pack.approvals.v1")] !== state || state.auditFailed)
1034
1064
  throw new Error("Original native permission connection changed.");
1035
1065
  const old = state.byId.get(route.sessionId);
@@ -1188,8 +1218,8 @@ function prepareClaudeNativePlan(store, pack, policyPath, mode = "configure") {
1188
1218
  captureMarkdown(cwd);
1189
1219
  const charter = resolve(cwd, "CLAUDE.md");
1190
1220
  const transcript = realpathSync(record.transcript);
1191
- context[charter] = createHash("sha256").update(readFileSync(charter)).digest("hex");
1192
- context[transcript] = createHash("sha256").update(readFileSync(transcript)).digest("hex");
1221
+ context[charter] = createHash2("sha256").update(readFileSync(charter)).digest("hex");
1222
+ context[transcript] = createHash2("sha256").update(readFileSync(transcript)).digest("hex");
1193
1223
  roleContextFiles[record.desktopSessionId] = [charter, transcript];
1194
1224
  members.push({
1195
1225
  key: record.key,
@@ -1207,7 +1237,7 @@ function prepareClaudeNativePlan(store, pack, policyPath, mode = "configure") {
1207
1237
  });
1208
1238
  }
1209
1239
  for (const file of [resolve(root, "claude/state.json"), realpathSync(policyPath)])
1210
- context[file] = createHash("sha256").update(readFileSync(file)).digest("hex");
1240
+ context[file] = createHash2("sha256").update(readFileSync(file)).digest("hex");
1211
1241
  const activePath = resolve(root, "claude/active.json");
1212
1242
  const active = existsSync(activePath) ? JSON.parse(readFileSync(activePath, "utf8")) : null;
1213
1243
  const target = new Set(members.map((m) => m.key));
@@ -1224,7 +1254,7 @@ function prepareClaudeNativePlan(store, pack, policyPath, mode = "configure") {
1224
1254
  if (!cwd.startsWith(resolve(root, "claude/workspaces", record.pack) + sep)) throw new Error("Outgoing workspace is outside the owned pack.");
1225
1255
  captureMarkdown(cwd);
1226
1256
  for (const file of [resolve(cwd, "CLAUDE.md"), realpathSync(record.transcript)])
1227
- context[file] = createHash("sha256").update(readFileSync(file)).digest("hex");
1257
+ context[file] = createHash2("sha256").update(readFileSync(file)).digest("hex");
1228
1258
  outgoing.push({ key, sessionId: record.desktopSessionId, cwd, title: record.title });
1229
1259
  }
1230
1260
  } else if (mode === "switch") throw new Error("Configure and verify an initial active roster before switching.");
@@ -1528,7 +1558,7 @@ async function configureClaudePack(options, evaluate = evaluateClaudeMain, quali
1528
1558
  if (!journal.startsWith(realpathSync(directory) + sep)) throw new Error("Recovery journal is outside the owned library.");
1529
1559
  const lines = readFileSync(journal, "utf8").split("\n").filter(Boolean);
1530
1560
  const line = lines[0];
1531
- if (createHash("sha256").update(line).digest("hex") !== marker.planHash) throw new Error("Recovery plan identity changed.");
1561
+ if (createHash2("sha256").update(line).digest("hex") !== marker.planHash) throw new Error("Recovery plan identity changed.");
1532
1562
  const record = JSON.parse(line);
1533
1563
  if (record.pack !== options.pack || record.operationId !== marker.operationId || !record.apply) throw new Error("Recovery pack or operation identity differs.");
1534
1564
  plan = { ...record, approvalRoutes: record.approvalRoutes ?? [], recovery: true, noEffects: marker.format === 2 && marker.dispatchState === "not-dispatched" && !lines.map((line2) => JSON.parse(line2)).some((r) => r.kind === "attempt") };
@@ -1542,7 +1572,7 @@ async function configureClaudePack(options, evaluate = evaluateClaudeMain, quali
1542
1572
  throw new Error("Unowned native approval connection.");
1543
1573
  const connection = JSON.parse(readFileSync(connectionPath, "utf8"));
1544
1574
  if (connection.pid === options.pid && connection.pack === options.pack && JSON.stringify(connection.routes) === JSON.stringify(plan.approvalRoutes) && JSON.stringify(connection.installationConsent) === JSON.stringify(plan.installationConsent)) {
1545
- if (typeof connection.secretPath !== "string" || typeof connection.socket !== "string" || !resolve(connection.secretPath).startsWith(realpathSync(resolve(root, "claude/private")) + sep) || realpathSync(connection.secretPath) !== resolve(connection.secretPath) || dirname(resolve(connection.socket)) !== resolve("/tmp", "topolo-claude-pack-" + createHash("sha256").update(root).digest("hex").slice(0, 16)))
1575
+ if (typeof connection.secretPath !== "string" || typeof connection.socket !== "string" || !resolve(connection.secretPath).startsWith(realpathSync(resolve(root, "claude/private")) + sep) || realpathSync(connection.secretPath) !== resolve(connection.secretPath) || dirname(resolve(connection.socket)) !== resolve("/tmp", "topolo-claude-pack-" + createHash2("sha256").update(root).digest("hex").slice(0, 16)))
1546
1576
  throw new Error("Native approval connection is outside owned control paths.");
1547
1577
  const secret = lstatSync(connection.secretPath);
1548
1578
  if (!secret.isFile() || secret.isSymbolicLink() || typeof process.getuid !== "function" || secret.uid !== process.getuid() || secret.mode & 63)
@@ -1553,7 +1583,7 @@ async function configureClaudePack(options, evaluate = evaluateClaudeMain, quali
1553
1583
  if (!plan.control) {
1554
1584
  const directory2 = resolve(root, "claude/private");
1555
1585
  mkdirSync(directory2, { recursive: true, mode: 448 });
1556
- const socketDirectory = resolve("/tmp", "topolo-claude-pack-" + createHash("sha256").update(root).digest("hex").slice(0, 16));
1586
+ const socketDirectory = resolve("/tmp", "topolo-claude-pack-" + createHash2("sha256").update(root).digest("hex").slice(0, 16));
1557
1587
  mkdirSync(socketDirectory, { recursive: true, mode: 448 });
1558
1588
  for (const path of [directory2, socketDirectory]) {
1559
1589
  const stat = lstatSync(path);
@@ -1568,7 +1598,7 @@ async function configureClaudePack(options, evaluate = evaluateClaudeMain, quali
1568
1598
  journal = resolve(directory, operationId + ".jsonl");
1569
1599
  const record = { kind: "plan", pack: options.pack, ...plan, apply: options.apply };
1570
1600
  durableRecord(journal, record, "wx");
1571
- marker = { format: 2, dispatchState: "not-dispatched", operationId, journal, planHash: createHash("sha256").update(JSON.stringify(record)).digest("hex") };
1601
+ marker = { format: 2, dispatchState: "not-dispatched", operationId, journal, planHash: createHash2("sha256").update(JSON.stringify(record)).digest("hex") };
1572
1602
  if (options.apply) durableRecord(markerPath, marker, "wx");
1573
1603
  syncDirectory(directory);
1574
1604
  syncDirectory(resolve(root, "claude"));
@@ -1837,8 +1867,9 @@ function prepareClaudeDynamicLane(store, pack, policyPath, laneId) {
1837
1867
  if (lstatSync(file).size > 131072) throw new Error("Dynamic lane record exceeds its schema bound.");
1838
1868
  const record = JSON.parse(readFileSync(file, "utf8"));
1839
1869
  const canonical = (value) => JSON.stringify(value, (_key, item) => item && typeof item === "object" && !Array.isArray(item) ? Object.fromEntries(Object.keys(item).sort().map((key) => [key, item[key]])) : item);
1840
- const digest = (value) => createHash("sha256").update(canonical(value)).digest("hex");
1841
- if (record.format !== "topolo-dynamic-lane" || record.schemaVersion !== 1 || record.harness !== "claude" || record.laneId !== laneId || record.planDigest !== digest(record.plan) || record.creationReceiptDigest !== digest(record.creationReceipt) || typeof record.nativeTitle !== "string" || !record.nativeTitle || record.nativeTitle.length > 256 || record.plan.role.packId !== pack || record.plan.nativeParentId !== record.plan.intake.nativeId)
1870
+ const digest = (value) => createHash2("sha256").update(canonical(value)).digest("hex");
1871
+ const nativeCLI = record.schemaVersion === 2 && record.plan?.schemaVersion === 2 && record.plan.continuityTransport === "native-cli";
1872
+ if (record.format !== "topolo-dynamic-lane" || !(record.schemaVersion === 1 || nativeCLI) || record.harness !== "claude" || record.laneId !== laneId || record.planDigest !== digest(record.plan) || record.creationReceiptDigest !== digest(record.creationReceipt) || typeof record.nativeTitle !== "string" || !record.nativeTitle || record.nativeTitle.length > 256 || record.plan.role.packId !== pack || (nativeCLI ? record.plan.nativeParentId !== null : record.plan.nativeParentId !== record.plan.intake.nativeId))
1842
1873
  throw new Error("Durable dynamic child identity or plan digest differs.");
1843
1874
  const stateFile = owned(resolve(root, "claude/state.json"));
1844
1875
  const state = JSON.parse(readFileSync(stateFile, "utf8"));
@@ -1847,8 +1878,8 @@ function prepareClaudeDynamicLane(store, pack, policyPath, laneId) {
1847
1878
  if (state.harness !== "claude" || policy.format !== "topolo-agent-execution-policy" || policy.pack !== pack || policy.sourceRevision !== record.plan.role.packRevision || !policy.installationConsent)
1848
1879
  throw new Error("Dynamic child source policy or organization-bound installation is stale.");
1849
1880
  const context = {
1850
- [stateFile]: createHash("sha256").update(readFileSync(stateFile)).digest("hex"),
1851
- [policyFile]: createHash("sha256").update(readFileSync(policyFile)).digest("hex")
1881
+ [stateFile]: createHash2("sha256").update(readFileSync(stateFile)).digest("hex"),
1882
+ [policyFile]: createHash2("sha256").update(readFileSync(policyFile)).digest("hex")
1852
1883
  };
1853
1884
  const inventories = {};
1854
1885
  const fixed = (descriptor) => {
@@ -1861,7 +1892,7 @@ function prepareClaudeDynamicLane(store, pack, policyPath, laneId) {
1861
1892
  throw new Error("Dynamic source workspace or current fixed settings differ.");
1862
1893
  const files = [resolve(cwd, "CLAUDE.md"), resolve(cwd, "topolo-connection.json"), resolve(cwd, ".mcp.json")];
1863
1894
  const hashes = {};
1864
- for (const f of files) hashes[owned(f)] = createHash("sha256").update(readFileSync(f)).digest("hex");
1895
+ for (const f of files) hashes[owned(f)] = createHash2("sha256").update(readFileSync(f)).digest("hex");
1865
1896
  const connection = JSON.parse(readFileSync(files[1], "utf8"));
1866
1897
  const expected = {
1867
1898
  environment: descriptor.environment,
@@ -1898,6 +1929,7 @@ function prepareClaudeDynamicLane(store, pack, policyPath, laneId) {
1898
1929
  planDigest: record.planDigest,
1899
1930
  nativeId: record.nativeId,
1900
1931
  cwd: record.creationReceipt.cwd,
1932
+ ...nativeCLI ? { continuityTransport: "native-cli", continuity: record.plan.continuity } : {},
1901
1933
  title: record.nativeTitle,
1902
1934
  source,
1903
1935
  intake,
@@ -1981,7 +2013,7 @@ function prepareClaudePrivateHelperConnection(store, pack, policyPath, sourceRol
1981
2013
  const policyBytes = readFileSync(policyFile), policy = JSON.parse(policyBytes.toString("utf8")), consent = policy.installationConsent;
1982
2014
  if (!consent || !["development", "staging", "production"].includes(consent.environment) || typeof consent.contextKey !== "string")
1983
2015
  throw new Error("Existing approved per-environment private connection required.");
1984
- const bindingFile = resolve(root, "connections", pack, consent.environment, createHash("sha256").update(consent.contextKey).digest("hex") + ".json");
2016
+ const bindingFile = resolve(root, "connections", pack, consent.environment, createHash2("sha256").update(consent.contextKey).digest("hex") + ".json");
1985
2017
  const bindingBytes = readFileSync(bindingFile), binding = JSON.parse(bindingBytes.toString("utf8"));
1986
2018
  const connection = {
1987
2019
  format: "topolo-private-helper-connection",
@@ -1991,9 +2023,9 @@ function prepareClaudePrivateHelperConnection(store, pack, policyPath, sourceRol
1991
2023
  sourceRoleKey,
1992
2024
  policyFile,
1993
2025
  bindingFile,
1994
- policyDigest: createHash("sha256").update(policyBytes).digest("hex"),
1995
- bindingDigest: createHash("sha256").update(bindingBytes).digest("hex"),
1996
- stateDigest: createHash("sha256").update(readFileSync(resolve(root, "claude/state.json"))).digest("hex"),
2026
+ policyDigest: createHash2("sha256").update(policyBytes).digest("hex"),
2027
+ bindingDigest: createHash2("sha256").update(bindingBytes).digest("hex"),
2028
+ stateDigest: createHash2("sha256").update(readFileSync(resolve(root, "claude/state.json"))).digest("hex"),
1997
2029
  profile: binding.privateCredentialProfile
1998
2030
  };
1999
2031
  privateClaudeHelperEnvironment(connection, filesystem, cryptography, paths, process.env);
@@ -2025,6 +2057,11 @@ function runClaudeLaneHelper(root, pack, policyPath, action, payload, source, co
2025
2057
  return Promise.reject(new Error("Closed typed native lifecycle helper required."));
2026
2058
  const encoded = JSON.stringify(payload);
2027
2059
  if (Buffer.byteLength(encoded) > 2097152) return Promise.reject(new Error("Own-child native transcript exceeds bounded transport; no broad history read."));
2060
+ try {
2061
+ requireClaudePrivateQueryEffectQualified("helper", payload);
2062
+ } catch (error) {
2063
+ return Promise.reject(error);
2064
+ }
2028
2065
  const selectedRole = payload.sourceRoleKey ?? payload.request?.sourceRoleKey ?? payload.plan?.role?.key ?? payload.operation?.record?.plan?.role?.key;
2029
2066
  if (!connection || root !== connection.root || pack !== connection.pack || policyPath !== connection.policyFile || selectedRole !== void 0 && selectedRole !== connection.sourceRoleKey)
2030
2067
  return Promise.reject(new Error("Private helper arguments differ from the exact bound source connection."));
@@ -2072,7 +2109,14 @@ function runClaudeLaneHelper(root, pack, policyPath, action, payload, source, co
2072
2109
  child.stdin.end(encoded);
2073
2110
  });
2074
2111
  }
2075
- function requireClaudePrivateQueryEffectQualified(operation) {
2112
+ function requireClaudePrivateQueryEffectQualified(operation, input) {
2113
+ const candidates = [input, input?.request, input?.plan, input?.record?.plan, input?.operation?.record?.plan];
2114
+ const transports = candidates.map((value) => value?.continuityTransport).filter((value) => value !== void 0);
2115
+ if (transports.some((value) => !["native-cli", "private-mcp"].includes(value)) || new Set(transports).size > 1)
2116
+ throw new Error("Closed explicit continuity transport required; no fallback.");
2117
+ const transport = transports[0];
2118
+ if (transport === "native-cli" || candidates.some((value) => value?.schemaVersion === 2))
2119
+ throw Object.assign(new Error("native_cli_startup_unqualified: current startup, profile refresh authority and native result producers are unavailable."), { code: "native_cli_startup_unqualified" });
2076
2120
  if (["create", "configure", "context-start", "fixture-start", "retire", "approval-once"].includes(operation))
2077
2121
  throw Object.assign(
2078
2122
  new Error("native_private_query_prestart_contract_unqualified: private native query startup, exclusion and owned lifecycle are not qualified."),
@@ -2082,12 +2126,12 @@ function requireClaudePrivateQueryEffectQualified(operation) {
2082
2126
  function claudeLaneExpression(options, source, journal, prepared) {
2083
2127
  if (!Number.isSafeInteger(options.pid) || options.pid < 1 || !["plan", "create", "create-read", "adopt", "read", "configure", "context-start", "context-read", "fixture-start", "fixture-read", "retire"].includes(options.operation))
2084
2128
  throw new Error("Closed typed Claude lane operation required.");
2085
- requireClaudePrivateQueryEffectQualified(options.operation);
2129
+ requireClaudePrivateQueryEffectQualified(options.operation, options.input);
2086
2130
  if (!options.privateConnection) throw new Error("Explicit approved private helper connection required before native access.");
2087
2131
  return `(async()=>{
2088
2132
  const expected=${JSON.stringify({ ...options, qualifier: CLAUDE_NATIVE_QUALIFIER, source, journal })};
2089
2133
  const requireClaudePrivateQueryEffectQualified=(${requireClaudePrivateQueryEffectQualified.toString()});
2090
- requireClaudePrivateQueryEffectQualified(expected.operation);
2134
+ requireClaudePrivateQueryEffectQualified(expected.operation,expected.input);
2091
2135
  if(process.pid!==expected.pid || typeof process.getBuiltinModule!=='function')throw new Error('Native process identity changed.');
2092
2136
  const load=process.getBuiltinModule('module').createRequire(process.resourcesPath+'/app.asar/package.json');
2093
2137
  const fs=load('node:fs'),crypto=load('node:crypto'),path=load('node:path');
@@ -2203,7 +2247,7 @@ function claudeLaneExpression(options, source, journal, prepared) {
2203
2247
  if(!secretInfo.isFile()||secretInfo.isSymbolicLink()||secretInfo.uid!==process.getuid()||secretInfo.mode&63)throw new Error('Unowned private native control secret.');
2204
2248
  const secret=JSON.parse(fs.readFileSync(control.secretPath,'utf8'));
2205
2249
  const ownedOperation=async op=>{
2206
- requireClaudePrivateQueryEffectQualified(op.kind==='approval-respond'&&op.decision==='once'?'approval-once':op.kind);
2250
+ requireClaudePrivateQueryEffectQualified(op.kind==='approval-respond'&&op.decision==='once'?'approval-once':op.kind,{continuityTransport:nativePlan.continuityTransport});
2207
2251
  qualify();const current=await helper('read',{laneId:nativePlan.laneId});
2208
2252
  const scopedPlan={...nativePlan,lifecycleState:current.state,deadline:Date.now()+200000};
2209
2253
  if(op.kind.startsWith('approval-')){
@@ -2270,7 +2314,7 @@ function claudeLaneExpression(options, source, journal, prepared) {
2270
2314
  })()`;
2271
2315
  }
2272
2316
  async function operateClaudeLane(options, evaluate = evaluateClaudeMain) {
2273
- requireClaudePrivateQueryEffectQualified(options.operation);
2317
+ requireClaudePrivateQueryEffectQualified(options.operation, options.input);
2274
2318
  validateInspectorEndpoint(options.endpoint);
2275
2319
  const root = realpathSync(options.store);
2276
2320
  const policy = realpathSync(options.policy);
@@ -2285,6 +2329,7 @@ async function operateClaudeLane(options, evaluate = evaluateClaudeMain) {
2285
2329
  const journal = resolve(directory, randomUUID() + ".jsonl");
2286
2330
  durableRecord(journal, { operation: options.operation, pid: options.pid, at: Date.now() }, "wx");
2287
2331
  const prepared = ["plan", "create", "create-read", "adopt"].includes(options.operation) ? void 0 : prepareClaudeDynamicLane(root, options.pack, policy, options.input.laneId);
2332
+ if (prepared?.record.plan.continuityTransport === "native-cli") requireClaudePrivateQueryEffectQualified(options.operation, prepared.record.plan);
2288
2333
  let sourceRoleKey = prepared?.record.plan.role.key ?? (options.operation === "plan" ? options.input.request?.sourceRoleKey : options.input.plan?.role?.key);
2289
2334
  if (["create", "create-read"].includes(options.operation)) {
2290
2335
  if (typeof options.input.laneId !== "string" || !/^[a-z0-9][a-z0-9-]*$/.test(options.input.laneId)) throw new Error("Exact existing creation lane required.");
@@ -2307,8 +2352,8 @@ async function operateClaudeLane(options, evaluate = evaluateClaudeMain) {
2307
2352
  mkdirSync(directory2, { mode: 448 });
2308
2353
  const secretPath = resolve(directory2, "secret.json");
2309
2354
  durableRecord(secretPath, { token: randomBytes(32).toString("hex") }, "wx");
2310
- const identity = createHash("sha256").update(JSON.stringify({ pid: options.pid, laneId, planDigest: prepared.record.planDigest, qualifier: CLAUDE_NATIVE_QUALIFIER })).digest("hex");
2311
- const socketDirectory = resolve("/tmp", "topolo-claude-" + process.getuid?.() + "-" + createHash("sha256").update(root + "\0" + laneId).digest("hex").slice(0, 24));
2355
+ const identity = createHash2("sha256").update(JSON.stringify({ pid: options.pid, laneId, planDigest: prepared.record.planDigest, qualifier: CLAUDE_NATIVE_QUALIFIER })).digest("hex");
2356
+ const socketDirectory = resolve("/tmp", "topolo-claude-" + process.getuid?.() + "-" + createHash2("sha256").update(root + "\0" + laneId).digest("hex").slice(0, 24));
2312
2357
  mkdirSync(socketDirectory, { mode: 448 });
2313
2358
  const socket = resolve(socketDirectory, "control.sock");
2314
2359
  control = { socket, secretPath, identity };
@@ -2335,7 +2380,7 @@ async function operateClaudeLane(options, evaluate = evaluateClaudeMain) {
2335
2380
  }
2336
2381
  }
2337
2382
  function operateClaudeLaneControl(options, request = requestClaudeApprovalControl) {
2338
- requireClaudePrivateQueryEffectQualified(options.operation.kind === "approval-respond" && options.operation.decision === "once" ? "approval-once" : options.operation.kind);
2383
+ requireClaudePrivateQueryEffectQualified(options.operation.kind === "approval-respond" && options.operation.decision === "once" ? "approval-once" : options.operation.kind, options.operation);
2339
2384
  if (!/^[a-z0-9][a-z0-9-]*$/.test(options.connection)) throw new Error("Exact owned native lane connection ID required.");
2340
2385
  const root = realpathSync(options.store);
2341
2386
  const directory = resolve(root, "claude/dynamic-controls", options.connection);
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ function registerAgentPacksCommand(program2) {
14
14
  throw new Error("Exact closed smoke operation, role/case and UUID required.");
15
15
  const store = packs.opts().store ?? resolve(homedir(), "AgentPacks");
16
16
  if (options.harness === "claude") {
17
- const { operateClaudeSmoke } = await import("./claude-pack-backend-RMSOPHVQ.js");
17
+ const { operateClaudeSmoke } = await import("./claude-pack-backend-YEVPAOID.js");
18
18
  console.log(JSON.stringify(await operateClaudeSmoke({
19
19
  store,
20
20
  kind: "smoke-" + operation,
@@ -44,7 +44,7 @@ function registerAgentPacksCommand(program2) {
44
44
  });
45
45
  packs.command("configure").description("Qualify and configure an adopted Claude roster through its live native backend").argument("<pack>").option("--harness <name>", "Host adapter", "claude").requiredOption("--policy <path>", "Reviewed execution policy JSON").requiredOption("--endpoint <url>", "Explicit loopback main-process inspector endpoint, enabled separately with authorization").requiredOption("--pid <pid>", "Expected Claude main-process PID").option("--apply", "Apply native groups, pins, models, effort and permission modes; otherwise preflight only").action(async (pack, options) => {
46
46
  if (options.harness !== "claude") throw new Error("Native configure is currently qualified only for Claude Desktop.");
47
- const { configureClaudePack } = await import("./claude-pack-backend-RMSOPHVQ.js");
47
+ const { configureClaudePack } = await import("./claude-pack-backend-YEVPAOID.js");
48
48
  const result = await configureClaudePack({
49
49
  store: packs.opts().store ?? resolve(homedir(), "AgentPacks"),
50
50
  pack,
@@ -92,7 +92,7 @@ function registerAgentPacksCommand(program2) {
92
92
  throw new Error("Closed existing Claude lane control operation required.");
93
93
  const input3 = options.input ? JSON.parse(readFileSync(options.input, "utf8")) : {};
94
94
  if (!input3 || typeof input3 !== "object" || Array.isArray(input3) || Object.keys(input3).some((k) => k !== "operationId")) throw new Error("Only an exact bootstrap/fixture operation UUID may be supplied.");
95
- const { operateClaudeLaneControl } = await import("./claude-pack-backend-RMSOPHVQ.js");
95
+ const { operateClaudeLaneControl } = await import("./claude-pack-backend-YEVPAOID.js");
96
96
  console.log(JSON.stringify(await operateClaudeLaneControl({
97
97
  store: packs.opts().store ?? resolve(homedir(), "AgentPacks"),
98
98
  connection: options.connection,
@@ -104,7 +104,7 @@ function registerAgentPacksCommand(program2) {
104
104
  throw new Error("Closed Claude lane operation requires --input, --pack, --policy, --endpoint and --pid.");
105
105
  const input2 = JSON.parse(readFileSync(options.input, "utf8"));
106
106
  if (!input2 || typeof input2 !== "object" || Array.isArray(input2)) throw new Error("Closed native lane input object required.");
107
- const { operateClaudeLane } = await import("./claude-pack-backend-RMSOPHVQ.js");
107
+ const { operateClaudeLane } = await import("./claude-pack-backend-YEVPAOID.js");
108
108
  console.log(JSON.stringify(await operateClaudeLane({
109
109
  store: packs.opts().store ?? resolve(homedir(), "AgentPacks"),
110
110
  pack: options.pack,
@@ -130,7 +130,7 @@ function registerAgentPacksCommand(program2) {
130
130
  decision = response.decision;
131
131
  }
132
132
  if (options.connection) {
133
- const { operateClaudeLaneControl } = await import("./claude-pack-backend-RMSOPHVQ.js");
133
+ const { operateClaudeLaneControl } = await import("./claude-pack-backend-YEVPAOID.js");
134
134
  console.log(JSON.stringify(await operateClaudeLaneControl({
135
135
  store: packs.opts().store ?? resolve(homedir(), "AgentPacks"),
136
136
  connection: options.connection,
@@ -142,7 +142,7 @@ function registerAgentPacksCommand(program2) {
142
142
  }), null, 2));
143
143
  return;
144
144
  }
145
- const { operateClaudeApprovals } = await import("./claude-pack-backend-RMSOPHVQ.js");
145
+ const { operateClaudeApprovals } = await import("./claude-pack-backend-YEVPAOID.js");
146
146
  console.log(JSON.stringify(await operateClaudeApprovals({
147
147
  store: packs.opts().store ?? resolve(homedir(), "AgentPacks"),
148
148
  kind: args[0],
@@ -157,7 +157,7 @@ function registerAgentPacksCommand(program2) {
157
157
  if (name !== "lane" && (options.policy || options.endpoint || options.pid || options.apply)) {
158
158
  if (options.harness !== "claude" || !options.policy || !options.endpoint || !options.pid || args.length !== 1)
159
159
  throw new Error("Native switch/recovery requires one Claude pack, --policy, --endpoint and --pid.");
160
- const { configureClaudePack } = await import("./claude-pack-backend-RMSOPHVQ.js");
160
+ const { configureClaudePack } = await import("./claude-pack-backend-YEVPAOID.js");
161
161
  const result2 = await configureClaudePack({
162
162
  store: packs.opts().store ?? resolve(homedir(), "AgentPacks"),
163
163
  pack: args[0],
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@topolo/cli",
3
3
  "packageManager": "pnpm@10.17.1",
4
- "version": "0.13.10",
5
- "topoloPublishedVersion": "0.13.10",
4
+ "version": "0.13.11",
5
+ "topoloPublishedVersion": "0.13.11",
6
6
  "description": "Public command-line interface for Topolo customer organizations, developers, and agents.",
7
7
  "license": "UNLICENSED",
8
8
  "author": "Topolo",