@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
package/README.md
CHANGED
|
@@ -436,3 +436,25 @@ 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.
|
|
451
|
+
|
|
452
|
+
### Preserving private pack runtime configuration
|
|
453
|
+
|
|
454
|
+
`topolo agent-packs --store /owned/AgentPacks runtime-adoption plan --manifest /review/adoption.json` produces a nonwriting configuration plan. Review its physical targets and digest, then use `runtime-adoption apply --manifest /review/adoption.json --plan-digest <sha256> --confirm` for one configuration-only attempt.
|
|
455
|
+
|
|
456
|
+
The closed owner manifest binds the actual consumer set and effective local Topolo transport origins, physical file preimages, shared aliases, private profile metadata, immutable CLI 0.13.11/MCP 0.14.26/SDK 0.23.21 targets and the separately pinned successor Python adapter. It must include every server in each physical config as either a targeted transport or an explicitly untouched server. Empty or incomplete manifests refuse. The command never installs skills, rebinds a home, changes existing instructions/permissions, starts a consumer or probes Auth.
|
|
457
|
+
|
|
458
|
+
A successful apply means configuration is prepared. It does not mean existing processes reloaded it, the private account is healthy, permissions were granted or either native harness completed a task. Starting or refreshing consumers is a separate owner-authorized step and can contact private Auth and renew the selected private profile. Public credential-store repair is not a blanket prerequisite for independent private profiles. See [the manifest and effect boundaries](agent-packs/README.md#private-runtime-adoption).
|
|
459
|
+
|
|
460
|
+
The protocol SDK pin addresses [GHSA-6qxp-vccf-f47h](https://github.com/modelcontextprotocol/typescript-sdk/security/advisories/GHSA-6qxp-vccf-f47h). The CLI doctor's protocol transport is stdio. Separate HTTP OAuth consumers still need appropriate issuer binding for legacy credentials and bundled providers; this source update neither inspects nor migrates credentials.
|
package/agent-packs/README.md
CHANGED
|
@@ -669,3 +669,56 @@ 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.
|
|
702
|
+
|
|
703
|
+
## Private runtime adoption
|
|
704
|
+
|
|
705
|
+
The public CLI forwards `agent-packs runtime-adoption plan|apply` to the bundled successor adapter. `plan` performs no writes. `apply` requires the exact reviewed `--plan-digest` and `--confirm`; it never calls `prepare`, `bind`, `reconnect`, skill installation or a native/Auth client. The selected immutable public CLI/MCP cohort is separate from the successor Python adapter source. Do not edit an installed immutable runtime to add the successor hooks.
|
|
706
|
+
|
|
707
|
+
The manifest is a local owner-reviewed configuration scope, not native provenance, a permission grant or a health certificate. Its exact top-level fields are:
|
|
708
|
+
|
|
709
|
+
- `schemaVersion: 1`, a fresh UUID `operationId`, and the absolute physical `library`.
|
|
710
|
+
- `ownerGeneration: {path, sha256}`: the owner's current bounded nonsecret effective-transport/generation record, inside that library. The owner must establish its native currency separately.
|
|
711
|
+
- `consumers`: the complete unique existing native IDs in scope. Target rows must account for every ID; no new native IDs are created.
|
|
712
|
+
- `profile: {path, publicPath, metadata, environment, contextKey, principalId}`: absolute private and public credential-file references, explicit organization context, and metadata only. Files must be physically distinct; the private profile must be owned, single-link, regular and mode 0600. Neither profile's contents are read or hashed. A renewal that changes private file metadata invalidates the descriptor and requires owner reconciliation.
|
|
713
|
+
- `runtime: {node, cli, mcp, cohort, pythonSource, adapterFiles, osEnvironment}`. Each executable/entry and each `cohort` value is `{path, sha256}`. Cohort keys `cli`, `mcp`, `sdk` pin their package JSON; required target versions are 0.13.11, 0.14.26 and 0.23.21. CLI and MCP entries must be their pinned package's `dist/index.js`. `pythonSource` is the physical successor source directory; `adapterFiles` pins its `topolo_agent_pack/{cli,live,topolo}.py`. Verify the complete installed cohort independently before admitting its pins. `osEnvironment` permits only HOME/PATH and optional USER/LOGNAME/LANG/LC_ALL/TMPDIR, with no credential, service-origin, Node or installation overrides.
|
|
714
|
+
- `targets`: one row per unique physical configuration file, with `{path, before, aliases, format, rootKey, consumers, transports, untouchedServers}`. Supported formats are JSON `mcpServers` and simple TOML `mcp_servers` tables. Each transport is `{name, origin, consumers}` and must already exist as a local command; URL, inherited `env_vars`, installation-mode, ambiguous names or unsupported TOML layouts refuse. `origin` is the exact physical file. The complete physical server map must equal the targeted names plus `untouchedServers`. The owner must independently bind all effective native layers; a zero-table query or a guessed server name is insufficient.
|
|
715
|
+
- `launcher: {path, before}`: exactly the owned generated `launchers/topolo-agent-pack-backend`; its interpreter, owner and mode are preserved.
|
|
716
|
+
- `preserve`: nonsecret `{path, sha256}` references to existing roster, model/effort, permission, charter/context, routing and lifecycle records. Include every protected fixed and dynamic record in the approved scope. These may not overlap destinations. No transcript or credential contents belong in a manifest.
|
|
717
|
+
|
|
718
|
+
A physical `before` snapshot contains `device`, `inode`, `uid`, numeric permission `mode`, `bytes`, `mtimeNs`, `ctimeNs`, `parents` (absolute path/device/inode triples), and `sha256`. Profile `metadata` uses the same shape without `sha256`. Each alias is `{path, linkTarget, metadata}`, where `metadata` is the symlink's device/inode/uid/full mode/size/mtimeNs/ctimeNs tuple. The plan emits actual beforeimages for review; effect admission must independently establish the owner, complete source set, physical sharing and current generation. It cannot silently authorize another consumer sharing a file.
|
|
719
|
+
|
|
720
|
+
Only existing owned physical destinations inside the library can change. Shared desktop/backend config symlinks remain intact and their common file changes once. The generated private descriptor and isolated Python MCP launcher are operation-specific. The isolated MCP launcher validates successor bytes before import, then current profile/generation/preservation pins before replacing itself with the explicit Node/MCP pair and closed private environment. This retains the native host's PID, stdio and cancellation custody. Existing host agent names are retained as a closed wrapper argument. Managed default helper clients use the exact Node/CLI pair regardless of PATH. Unmanaged clients retain their ordinary behavior. No parent credential/service-origin/installation/Node environment is copied into those Topolo children. An intentional installation credential mode needs its own separately explicit contract.
|
|
721
|
+
|
|
722
|
+
Apply reserves `launchers/adoption-<UUID>.json` exclusively. The journal starts in `may-have-config-effects`, records each verified replacement, and becomes `configuration-prepared-native-adoption-unverified` only after all readbacks. Every write rechecks generation, source, profile metadata, preservation pins and destination custody. An interrupted or conflicting apply retains the journal and any uncertain temporary file; it never automatically replays or rolls back. Reconcile exact effects with the original owner.
|
|
723
|
+
|
|
724
|
+
No apply result proves native reload, identity, current RBAC, model/effort, loaded context, original-intake permission routing or a native tool result. Hosts may react to configuration changes; admit the actual affected owner/generation and normal request settlement before applying in a live host. Later ordinary startup can renew the private credential and load identity. A previous 429/403 or scoped recovery hold remains separately applicable and elapsed time grants no retry. Actual Codex and Claude same-query context, permission, nonce result, cancellation/settlement and own retirement require independent native evidence.
|
|
@@ -290,6 +290,9 @@ def parser():
|
|
|
290
290
|
p.add_argument('--version',action='version',version=__version__)
|
|
291
291
|
p.add_argument('--store',default=os.environ.get('CODEX_PACK_HOME',str(pathlib.Path.home()/'AgentPacks')))
|
|
292
292
|
sub=p.add_subparsers(dest='command',required=True)
|
|
293
|
+
q=sub.add_parser('runtime-adoption',help='Configuration-only private runtime plan/apply; no consumer startup or authentication')
|
|
294
|
+
q.add_argument('operation',choices=['plan','apply']);q.add_argument('--manifest',required=True)
|
|
295
|
+
q.add_argument('--plan-digest');q.add_argument('--confirm',action='store_true')
|
|
293
296
|
def selection(q):
|
|
294
297
|
q.add_argument('--set',dest='sets',action='append');q.add_argument('--agent',dest='agents',action='append')
|
|
295
298
|
def activation(q):
|
|
@@ -344,6 +347,7 @@ def parser():
|
|
|
344
347
|
q.add_argument('--ownership',choices=['user','organization'],required=True);q.add_argument('--bytes-scope',choices=['private','org'],required=True);q.add_argument('--instance')
|
|
345
348
|
q.add_argument('--reconcile-local-context',action='store_true',help='Explicitly reconcile a recorded partial context attachment; never replays cloud writes')
|
|
346
349
|
q.add_argument('--private-profile',action='store_true',help='Bind the explicitly selected protected profile metadata for native child helpers')
|
|
350
|
+
if command=='remote-context':q.add_argument('--continuity-input',help='Closed native-cli continuity input; runtime authority must independently qualify before any client')
|
|
347
351
|
if command=='remember':
|
|
348
352
|
q.add_argument('--content',required=True);q.add_argument('--kind',choices=['instruction','knowledge','durable'],required=True)
|
|
349
353
|
q.add_argument('--provenance',required=True);q.add_argument('--visibility',choices=['private','shared'],default='private')
|
|
@@ -373,7 +377,7 @@ def main(argv=None):
|
|
|
373
377
|
elif command_args and command_args[0].startswith('--store='):command_args=command_args[1:]
|
|
374
378
|
lane_command=bool(command_args and command_args[0]=='lane')
|
|
375
379
|
cloud_command=bool(command_args and command_args[0] in {'connect','remote-context','remember','artifact','artifact-get','remote-recover'})
|
|
376
|
-
if harness == 'claude' and not policy_command and not cloud_command and not lane_command:
|
|
380
|
+
if harness == 'claude' and not policy_command and not cloud_command and not lane_command and not (command_args and command_args[0]=='runtime-adoption'):
|
|
377
381
|
from . import claude
|
|
378
382
|
return claude.main(argv)
|
|
379
383
|
if harness not in {'codex', 'claude'}:
|
|
@@ -382,10 +386,18 @@ def main(argv=None):
|
|
|
382
386
|
args=parser().parse_args(argv)
|
|
383
387
|
args.harness=harness
|
|
384
388
|
try:
|
|
389
|
+
if args.command=='runtime-adoption':
|
|
390
|
+
if harness!='codex':
|
|
391
|
+
raise PackError('Runtime adoption is one explicit cross-host owner manifest, not a harness override')
|
|
392
|
+
emit(live.private_runtime_adoption(args.store,args.manifest,args.operation,args.plan_digest,args.confirm))
|
|
393
|
+
return 0
|
|
385
394
|
lib=Library(args.store)
|
|
386
395
|
if args.command in {'connect','remote-context','remember','artifact','artifact-get','remote-recover'}:
|
|
387
396
|
from . import topolo
|
|
388
|
-
|
|
397
|
+
if args.command=='remote-context' and args.continuity_input:
|
|
398
|
+
result=topolo.native_cli_continuity_read(lib,args)
|
|
399
|
+
else:
|
|
400
|
+
with topolo.connection_lock(lib,args):result=topolo.dispatch(lib,args)
|
|
389
401
|
emit(result)
|
|
390
402
|
return 0
|
|
391
403
|
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
|
-
|
|
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':
|
|
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')
|
|
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')
|
|
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'})
|