@topolo/cli 0.13.17 → 0.13.21

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.
Files changed (148) hide show
  1. package/README.md +18 -4
  2. package/agent-packs/README.md +469 -1
  3. package/agent-packs/coverage_changed.py +1 -1
  4. package/agent-packs/docs/selected-artifacts.md +47 -0
  5. package/agent-packs/documentation/cloud-task-state.md +56 -0
  6. package/agent-packs/domain-contexts/README.md +25 -0
  7. package/agent-packs/domain-contexts/cicd.json +8 -0
  8. package/agent-packs/gui/feed-adapter.js +4 -0
  9. package/agent-packs/gui/index.html +9 -0
  10. package/agent-packs/gui/studio.css +14 -0
  11. package/agent-packs/gui/studio.js +121 -0
  12. package/agent-packs/gui/studio_server.py +355 -0
  13. package/agent-packs/roles/cicd/CLAUDE.md +24 -0
  14. package/agent-packs/specializations/cicd--cicd.json +5 -0
  15. package/agent-packs/specializations/cicd--pipeline-engineering.json +5 -0
  16. package/agent-packs/specializations/cicd--promotion-deployment.json +5 -0
  17. package/agent-packs/specializations/cicd--queue-recovery.json +5 -0
  18. package/agent-packs/specializations/cicd--release-verification.json +5 -0
  19. package/agent-packs/specializations/cicd--runner-capacity.json +5 -0
  20. package/agent-packs/specializations/intakes--engineering-02.json +5 -0
  21. package/agent-packs/specializations/intakes--engineering-03.json +5 -0
  22. package/agent-packs/specializations/intakes--engineering.json +5 -0
  23. package/agent-packs/specializations/intakes--operator-01.json +5 -0
  24. package/agent-packs/specializations/intakes--operator-02.json +5 -0
  25. package/agent-packs/specializations/operator-skills--audio-and-narration-specialist.json +5 -0
  26. package/agent-packs/specializations/operator-skills--brand-and-copywriting-specialist.json +5 -0
  27. package/agent-packs/specializations/operator-skills--capability-research-specialist.json +5 -0
  28. package/agent-packs/specializations/operator-skills--data-analysis-and-visualization.json +5 -0
  29. package/agent-packs/specializations/operator-skills--financial-evidence-specialist.json +5 -0
  30. package/agent-packs/specializations/operator-skills--operating-template-specialist.json +5 -0
  31. package/agent-packs/specializations/operator-skills--operator-skill-library.json +5 -0
  32. package/agent-packs/specializations/operator-skills--operator-skills.json +5 -0
  33. package/agent-packs/specializations/operator-skills--operator-workflow-evaluation.json +5 -0
  34. package/agent-packs/specializations/operator-skills--privacy-and-agreement-review.json +5 -0
  35. package/agent-packs/specializations/operator-skills--storytelling-specialist.json +5 -0
  36. package/agent-packs/specializations/operator-skills--ux-and-accessibility-specialist.json +5 -0
  37. package/agent-packs/specializations/operator-skills--video-production-specialist.json +5 -0
  38. package/agent-packs/specializations/operator-skills--visual-design-specialist.json +5 -0
  39. package/agent-packs/specializations/operator-skills--workflow-integration-specialist.json +5 -0
  40. package/agent-packs/specializations/operators--access-and-privacy.json +5 -0
  41. package/agent-packs/specializations/operators--accounting-evidence-review.json +5 -0
  42. package/agent-packs/specializations/operators--agreement-operations.json +5 -0
  43. package/agent-packs/specializations/operators--books-operator.json +5 -0
  44. package/agent-packs/specializations/operators--brand-operator.json +5 -0
  45. package/agent-packs/specializations/operators--commerce-operations.json +5 -0
  46. package/agent-packs/specializations/operators--communications-operator.json +5 -0
  47. package/agent-packs/specializations/operators--content-and-assets-operator.json +5 -0
  48. package/agent-packs/specializations/operators--customer-operations.json +5 -0
  49. package/agent-packs/specializations/operators--design-operator.json +5 -0
  50. package/agent-packs/specializations/operators--design-production-02.json +5 -0
  51. package/agent-packs/specializations/operators--design-production-03.json +5 -0
  52. package/agent-packs/specializations/operators--design-production-04.json +5 -0
  53. package/agent-packs/specializations/operators--device-and-feed-operator.json +5 -0
  54. package/agent-packs/specializations/operators--director-operator.json +5 -0
  55. package/agent-packs/specializations/operators--experience-review.json +5 -0
  56. package/agent-packs/specializations/operators--growth-operations.json +5 -0
  57. package/agent-packs/specializations/operators--learn-operator.json +5 -0
  58. package/agent-packs/specializations/operators--localization-operator.json +5 -0
  59. package/agent-packs/specializations/operators--operators.json +5 -0
  60. package/agent-packs/specializations/operators--pay-operator.json +5 -0
  61. package/agent-packs/specializations/operators--research-and-intelligence.json +5 -0
  62. package/agent-packs/specializations/operators--roadmap-operator.json +5 -0
  63. package/agent-packs/specializations/operators--social-content-operator.json +5 -0
  64. package/agent-packs/specializations/operators--talent-operations.json +5 -0
  65. package/agent-packs/specializations/operators--web-operator.json +5 -0
  66. package/agent-packs/specializations/operators--workflow-operator.json +5 -0
  67. package/agent-packs/specializations/specialists--adversarial-qa.json +5 -0
  68. package/agent-packs/specializations/specialists--backlog-dispatch.json +5 -0
  69. package/agent-packs/specializations/specialists--build-01.json +5 -0
  70. package/agent-packs/specializations/specialists--build-02.json +5 -0
  71. package/agent-packs/specializations/specialists--build-03.json +5 -0
  72. package/agent-packs/specializations/specialists--developer-environment-steward.json +5 -0
  73. package/agent-packs/specializations/specialists--documentation-knowledge-steward.json +5 -0
  74. package/agent-packs/specializations/specialists--execution-readiness.json +5 -0
  75. package/agent-packs/specializations/specialists--incident-triage.json +5 -0
  76. package/agent-packs/specializations/specialists--platform-package-steward.json +5 -0
  77. package/agent-packs/specializations/specialists--product-direction-01.json +5 -0
  78. package/agent-packs/specializations/specialists--product-direction-02.json +5 -0
  79. package/agent-packs/specializations/specialists--release-recovery.json +5 -0
  80. package/agent-packs/specializations/specialists--release-watch.json +5 -0
  81. package/agent-packs/specializations/specialists--roadmap-planning-01.json +5 -0
  82. package/agent-packs/specializations/specialists--roadmap-planning-02.json +5 -0
  83. package/agent-packs/specializations/specialists--security-permissions-review.json +5 -0
  84. package/agent-packs/specializations/specialists--system-architecture-01.json +5 -0
  85. package/agent-packs/specializations/specialists--system-architecture-02.json +5 -0
  86. package/agent-packs/specializations/specialists--translation.json +5 -0
  87. package/agent-packs/specializations/specialists--ux-consistency-review.json +5 -0
  88. package/agent-packs/specializations/specialists--workflow-improvement.json +5 -0
  89. package/agent-packs/specializations/specialists--worktree-retirement.json +5 -0
  90. package/agent-packs/src/topolo_agent_pack/cicd.py +101 -0
  91. package/agent-packs/src/topolo_agent_pack/claude.py +22 -7
  92. package/agent-packs/src/topolo_agent_pack/claude_activation.py +157 -0
  93. package/agent-packs/src/topolo_agent_pack/claude_completion_recovery.py +216 -0
  94. package/agent-packs/src/topolo_agent_pack/claude_desktop.py +181 -0
  95. package/agent-packs/src/topolo_agent_pack/claude_dispatch.py +240 -0
  96. package/agent-packs/src/topolo_agent_pack/claude_prompt.py +74 -0
  97. package/agent-packs/src/topolo_agent_pack/claude_runner.py +207 -0
  98. package/agent-packs/src/topolo_agent_pack/claude_service.py +397 -0
  99. package/agent-packs/src/topolo_agent_pack/cli.py +188 -18
  100. package/agent-packs/src/topolo_agent_pack/closed_ledger_fence.py +118 -0
  101. package/agent-packs/src/topolo_agent_pack/cloud_attach.py +128 -0
  102. package/agent-packs/src/topolo_agent_pack/cloud_cutover.py +134 -0
  103. package/agent-packs/src/topolo_agent_pack/cloud_delta.py +162 -0
  104. package/agent-packs/src/topolo_agent_pack/cloud_migrate.py +142 -0
  105. package/agent-packs/src/topolo_agent_pack/cloud_recovery.py +42 -0
  106. package/agent-packs/src/topolo_agent_pack/cloud_work.py +126 -0
  107. package/agent-packs/src/topolo_agent_pack/coordinator_activity.py +27 -0
  108. package/agent-packs/src/topolo_agent_pack/coordinator_context.py +91 -0
  109. package/agent-packs/src/topolo_agent_pack/coordinator_refresh.py +116 -0
  110. package/agent-packs/src/topolo_agent_pack/desktop_acceptance.py +45 -0
  111. package/agent-packs/src/topolo_agent_pack/desktop_launcher.py +114 -0
  112. package/agent-packs/src/topolo_agent_pack/domain_context.py +38 -0
  113. package/agent-packs/src/topolo_agent_pack/execution.py +104 -7
  114. package/agent-packs/src/topolo_agent_pack/fresh.py +1 -1
  115. package/agent-packs/src/topolo_agent_pack/frontdoor.py +23 -0
  116. package/agent-packs/src/topolo_agent_pack/linked_task_context.py +80 -0
  117. package/agent-packs/src/topolo_agent_pack/live.py +285 -18
  118. package/agent-packs/src/topolo_agent_pack/native.py +160 -4
  119. package/agent-packs/src/topolo_agent_pack/policy.py +8 -0
  120. package/agent-packs/src/topolo_agent_pack/review_context.py +33 -0
  121. package/agent-packs/src/topolo_agent_pack/role_catalog.py +137 -0
  122. package/agent-packs/src/topolo_agent_pack/role_context.py +63 -0
  123. package/agent-packs/src/topolo_agent_pack/role_refresh.py +50 -0
  124. package/agent-packs/src/topolo_agent_pack/task_models.py +11 -7
  125. package/agent-packs/src/topolo_agent_pack/throughput.py +149 -0
  126. package/agent-packs/src/topolo_agent_pack/topolo.py +7 -1
  127. package/agent-packs/src/topolo_agent_pack/work.py +322 -0
  128. package/agent-packs/src/topolo_agent_pack/work_adoption.py +122 -0
  129. package/agent-packs/src/topolo_agent_pack/work_artifacts.py +84 -0
  130. package/agent-packs/src/topolo_agent_pack/work_context.py +77 -0
  131. package/agent-packs/src/topolo_agent_pack/work_gui.py +51 -0
  132. package/agent-packs/src/topolo_agent_pack/work_native.py +414 -0
  133. package/agent-packs/src/topolo_agent_pack/work_wait.py +151 -0
  134. package/agent-packs/templates/topolo-coordinators/AGENTS.md +7 -0
  135. package/agent-packs/templates/topolo-coordinators/agents/intakes/cicd/AGENTS.md +6 -0
  136. package/agent-packs/templates/topolo-coordinators/agents/intakes/cicd/config.json +4 -0
  137. package/agent-packs/templates/topolo-coordinators/agents/intakes/engineering/AGENTS.md +3 -0
  138. package/agent-packs/templates/topolo-coordinators/agents/intakes/engineering/config.json +4 -0
  139. package/agent-packs/templates/topolo-coordinators/agents/intakes/operator-01/AGENTS.md +3 -0
  140. package/agent-packs/templates/topolo-coordinators/agents/intakes/operator-01/config.json +4 -0
  141. package/agent-packs/templates/topolo-coordinators/pack.json +56 -0
  142. package/dist/chunk-NYDIAYVY.js +144 -0
  143. package/dist/{claude-pack-backend-YEVPAOID.js → chunk-UY5LEU5C.js} +1659 -21
  144. package/dist/claude-desktop-profile-JCQKSY3K.js +13 -0
  145. package/dist/claude-pack-activation-Q25MCEPU.js +506 -0
  146. package/dist/claude-pack-backend-HUTKGIDS.js +90 -0
  147. package/dist/index.js +408 -164
  148. package/package.json +8 -6
@@ -0,0 +1,126 @@
1
+ """Docs-selected task state; local SQLite exists only inside a disposable operation.
2
+
3
+ Requires the published conditional-version and current-only-read contracts.
4
+ No network failure falls back to a local ledger, and startup never adopts workers.
5
+ """
6
+ import copy,hashlib,json,pathlib,sqlite3,tempfile,uuid
7
+ from .storage import PackError,read_json,canonical
8
+ from .work import Store,require
9
+
10
+ TABLES={'tasks':('id','input_hash','body'),'workers':('id','task_id','body'),'events':('sequence','task_id','body'),'responses':('id','input_hash','task_id')}
11
+ READS={'get','list','worker','workers','events','retained','retained_page'}
12
+ WRITES={'submit','update','reserve','worker_update','respond','re_review','progress','adopt_ready','adopt_retained'}
13
+
14
+ class CloudStore:
15
+ cloud=True
16
+ def __init__(self,root,client=None,inventory=None):
17
+ self.root=pathlib.Path(root)/'work'
18
+ self.binding=read_json(self.root/'cloud.json')
19
+ renewal=self.root/'coordinator-renewal.json'
20
+ if renewal.exists() or renewal.is_symlink():
21
+ from .coordinator_refresh import verified_local
22
+ from types import SimpleNamespace
23
+ verified_local(SimpleNamespace(root=self.root.parent))
24
+ if (self.root/'cloud-migration.json').exists():
25
+ from .cloud_migrate import verified
26
+ verified(self.root.parent)
27
+ attachment_path=self.root/'cloud-attach.json'
28
+ if attachment_path.exists() or attachment_path.is_symlink():
29
+ require(not attachment_path.is_symlink(),'Cloud attachment journal cannot be a symlink')
30
+ attachment=read_json(attachment_path)
31
+ require(isinstance(attachment,dict) and attachment.get('format')=='topolo-cloud-attach-v1' and attachment.get('state')=='verified' and attachment.get('binding')==self.binding,'Cloud attachment is incomplete; reconcile before execution')
32
+ require(set(self.binding)=={'format','organizationId','workspaceId','documentId','datasetId','installationId'},'Invalid cloud work binding')
33
+ require(self.binding['format']=='topolo-cloud-work-binding-v1' and self.binding['organizationId']=='org_topolo_platform','Cloud work organization differs')
34
+ if client is None:
35
+ from .topolo import Client
36
+ client=Client('production','org:org_topolo_platform')
37
+ require(all(isinstance(self.binding[k],str) and 1<=len(self.binding[k])<=240 for k in ['workspaceId','documentId','datasetId','installationId']),'Cloud work binding identity is missing')
38
+ self.client=client;self.inventory=inventory
39
+ def recover(self):
40
+ # Another machine's running or UNKNOWN worker retains its owner. A fresh
41
+ # process is not a lease, checkpoint adoption, or execution authorization.
42
+ return None
43
+ def load(self):
44
+ r=self.client.action('app_topolo_docs.documents.get',{'documentId':self.binding['documentId'],'includeHistory':False},self.binding['workspaceId'])
45
+ doc=r['document'];rows=[v for v in r['versions'] if v['id']==doc['currentVersionId']]
46
+ require(doc['id']==self.binding['documentId'] and doc['workspaceId']==self.binding['workspaceId'] and doc['organizationId']==self.binding['organizationId'] and len(rows)==1,'Cloud task-state readback differs')
47
+ value=json.loads(rows[0]['bodyMarkdown'])
48
+ require(value['format']=='topolo-cloud-work-state-v1' and value['datasetId']==self.binding['datasetId'] and type(value['generation']) is int and set(value['tables'])==set(TABLES),'Cloud work state is unqualified')
49
+ require(value.get('activated') is True,'Cloud task-state migration has not been activated; no execution authority')
50
+ return doc['currentVersionId'],value
51
+ def retained_rows(self,ref):
52
+ if self.inventory is not None:
53
+ require(hashlib.sha256(canonical(self.inventory)).hexdigest()==ref['sha256'],'Retained inventory cache differs from cloud')
54
+ return self.inventory
55
+ from .topolo import read_artifact
56
+ with tempfile.TemporaryDirectory(prefix='topolo-work-inventory-') as tmp:
57
+ path=pathlib.Path(tmp)/'inventory.json'
58
+ read_artifact(self.client,{'input':{'bytesScope':ref['scope'],'bytesWorkspaceId':ref['workspaceId']}},ref['key'],ref,path)
59
+ value=json.loads(path.read_text())
60
+ require(hashlib.sha256(canonical(value)).hexdigest()==ref['sha256'],'Cloud retained inventory digest differs')
61
+ self.inventory=value;return value
62
+ def snapshot(self):
63
+ _,value=self.load()
64
+ return {'tasks':[json.loads(r[2]) for r in reversed(value['tables']['tasks'])],
65
+ 'workers':[json.loads(r[2]) for r in value['tables']['workers']],
66
+ 'events':[{'sequence':r[0],**json.loads(r[2])} for r in value['tables']['events']],
67
+ 'retainedCount':value['inventory']['count'],'authority':'topolo-docs','generation':value['generation']}
68
+ def adopt_ready(self,tid,revision):
69
+ return self.invoke('adopt_ready',(tid,revision,self.binding['installationId']),{})
70
+ def __getattr__(self,name):
71
+ if name not in READS|WRITES:
72
+ raise AttributeError(name)
73
+ return lambda *args,**kwargs:self.invoke(name,args,kwargs)
74
+ def invoke(self,name,args,kwargs):
75
+ version,state=self.load()
76
+ with tempfile.TemporaryDirectory(prefix='topolo-work-operation-') as tmp:
77
+ store=Store(tmp)
78
+ with store.db() as db:
79
+ for table,columns in TABLES.items():
80
+ db.executemany('INSERT INTO '+table+' VALUES ('+','.join('?' for _ in columns)+')',state['tables'][table])
81
+ if name in {'retained','retained_page','adopt_retained'}:
82
+ db.executemany('INSERT INTO retained VALUES (?,?)',[(row[0],row[1]) for row in self.retained_rows(state['inventory'])])
83
+ if name in WRITES and name not in {'submit','adopt_ready','adopt_retained'}:
84
+ task_id=store.worker(args[0])['taskId'] if name in {'worker_update','progress'} else args[0]['taskId'] if name in {'respond','re_review'} else args[0]
85
+ if name!='respond':require(store.get(task_id).get('ownerInstallationId')==self.binding['installationId'],'Task belongs to another installation; explicit checkpoint adoption is required')
86
+ if name=='adopt_retained':
87
+ parent=store.get(args[0]['continuationOwnerTaskId'])
88
+ if parent.get('ownerInstallationId')!=self.binding['installationId']:
89
+ from .work_adoption import verify_parent
90
+ workers=[w for w in store.workers() if w['taskId']==parent['id']]
91
+ proof=verify_parent(parent,workers,args[0],self.root,self.client,self.binding['installationId'])
92
+ kwargs={**kwargs,'parent_proof':proof}
93
+ result=getattr(store,name)(*args,**kwargs)
94
+ if name in READS:
95
+ return result
96
+ if name in {'submit','adopt_retained'} and result[1]:
97
+ task=result[0];task['ownerInstallationId']=self.binding['installationId'];task['resumeAfterCheckpoint']=True
98
+ with store.db() as db:
99
+ db.execute('UPDATE tasks SET body=? WHERE id=?',(json.dumps(task),task['id']))
100
+ if name=='respond' and result[1] and result[0]['state']=='queued':
101
+ task=result[0];task['ownerInstallationId']=self.binding['installationId'];task['resumeAfterCheckpoint']=True
102
+ with store.db() as db:
103
+ db.execute('UPDATE tasks SET body=? WHERE id=?',(json.dumps(task),task['id']))
104
+ with store.db() as db:
105
+ tables={table:db.execute('SELECT * FROM '+table+' ORDER BY '+('sequence' if table=='events' else 'rowid')).fetchall() for table in TABLES}
106
+ if tables=={k:[tuple(row) for row in rows] for k,rows in state['tables'].items()}:
107
+ return result
108
+ operation=str(uuid.uuid4());next_state={**copy.deepcopy(state),'tables':tables,'generation':state['generation']+1,'operation':{'id':operation,'method':name,'installationId':self.binding['installationId']}}
109
+ body=canonical(next_state).decode();require(len(body)<=900000,'Active cloud ledger requires archival before accepting more state; no local fallback')
110
+ try:
111
+ self.client.action('app_topolo_docs.documents.versions.create',{'documentId':self.binding['documentId'],'bodyMarkdown':body,'expectedCurrentVersionId':version},self.binding['workspaceId'],write=True)
112
+ except Exception as exc:
113
+ # Reconcile a lost successful ACK once. A competing generation
114
+ # or unavailable read is never permission to repeat the write.
115
+ try:_,actual=self.load()
116
+ except Exception:
117
+ raise PackError('Cloud task write is uncertain; no local commit or external replay was authorized') from exc
118
+ if actual.get('operation',{}).get('id')!=operation:
119
+ raise PackError('Cloud state changed or write was refused; reread and reconcile without replay') from exc
120
+ return result
121
+
122
+ def open_store(root):
123
+ config=read_json(pathlib.Path(root)/'work/config.json',{})
124
+ cloud=(pathlib.Path(root)/'work/cloud.json').is_file()
125
+ require(((config.get('authority')=='topolo-docs' or config.get('requireCloudAuthority') is True)==cloud),'Cloud authority binding is incomplete; no local fallback')
126
+ return CloudStore(root) if cloud else Store(root)
@@ -0,0 +1,27 @@
1
+ """Durable native coordinator observations, separate from task completion."""
2
+ import time,fcntl
3
+ from pathlib import Path
4
+ from .storage import read_json,atomic_json
5
+
6
+ def record(root,status):
7
+ roster=read_json(Path(status['runtime'])/'agents.json',{}).get('agents',[])
8
+ active=set(status['activeTurns'])
9
+ work=Path(root)/'work';work.mkdir(exist_ok=True)
10
+ with (work/'coordinator-observations.lock').open('a') as lock:
11
+ fcntl.flock(lock,fcntl.LOCK_EX)
12
+ path=work/'coordinator-observations.json'
13
+ record=read_json(path,{'states':{},'events':[]})
14
+ if read_json(work/'config.json',{}).get('frontdoorDispatch') is True:
15
+ return record['events']
16
+ for role in roster:
17
+ if role.get('set')!='intakes':continue
18
+ tid=role['threadId'];state='active' if tid in active else 'idle'
19
+ previous=record['states'].get(tid)
20
+ # Do not invent past starts or completion from the first idle observation.
21
+ if previous!=state and (state=='active' or previous is not None):
22
+ record['events'].append({'id':'native-observation:'+tid+':'+str(len(record['events'])+1),
23
+ 'at':time.time(),'threadId':tid,'owner':role.get('title',role['id']),'state':state,
24
+ 'summary':('Native coordinator execution is active outside the task broker. Existing work has not been dispatched again; task progress and outcome remain unverified.' if state=='active' else 'Native coordinator is now idle. This observation does not verify task completion or deployment.')})
25
+ record['states'][tid]=state
26
+ atomic_json(path,record)
27
+ return record['events']
@@ -0,0 +1,91 @@
1
+ """Bounded conversational context and typed coordinator broker-tool boundary."""
2
+ import copy,json,pathlib,shlex,sys,uuid,re
3
+ from .storage import canonical,atomic_json,read_json,PackError
4
+ from .work import require
5
+
6
+ CONTRACT='''Stay in the human-facing conversation. Answer questions, clarify scope, inspect current task status and explain results directly using canonical cloud evidence. A status question, follow-up or clarification is not a new assignment and must not create a task. For an actual new assignment, first reconcile the selected existing tasks; continue the original task when appropriate instead of duplicating it. Submit a new bounded assignment only through the installed task broker, using the supplied native-message dispatch identity exactly once. Delegate implementation, infrastructure, operational effects and bounded diagnostic execution to task-based specialists; do not perform them in this coordinator. Preserve task IDs, owners, holds, acceptance and uncertain effects. Use the existing broker read/wait tools to follow progress and inspect the final result, then explain the actual outcome here. A queue receipt is not completion and a GUI link is not a substitute for answering the human. Native model, effort, permissions and specialist-selection gates remain in force. Never invent a completed result, resend uncertain work, resume archived workers, or use another agent mechanism outside the task broker.'''
7
+
8
+ OPERATIONS={'list','read','retained','submit','respond','re-review','wait'}
9
+
10
+ def dispatch_id(thread,message):
11
+ require(isinstance(message,str) and 1<=len(message)<=200,'Native human message identity is missing; no new assignment can be dispatched')
12
+ return str(uuid.uuid5(uuid.NAMESPACE_URL,'topolo-frontdoor:'+thread+':'+message))
13
+
14
+ def payload_valid(value,operation,identity,channel):
15
+ require(isinstance(value,dict),'Typed broker request object required')
16
+ if operation=='read':require(set(value)=={'taskId'} and isinstance(value['taskId'],str) and 1<=len(value['taskId'])<=200,'Exact existing task identity required')
17
+ elif operation=='wait':require(set(value)=={'taskId','afterRevision','afterSequence','timeoutSeconds'} and isinstance(value['taskId'],str) and type(value['afterRevision']) is int and value['afterRevision']>=0 and type(value['afterSequence']) is int and value['afterSequence']>=0 and type(value['timeoutSeconds']) is int and 1<=value['timeoutSeconds']<=30,'Bounded existing task wait required')
18
+ elif operation=='retained':require(set(value)<={'cursor','limit'} and type(value.get('cursor',0)) is int and value.get('cursor',0)>=0 and type(value.get('limit',50)) is int and 1<=value.get('limit',50)<=50,'Bounded retained query required')
19
+ elif operation in {'submit','respond','re-review'}:
20
+ expected={'submit':{'requestId','objective','channel','canonicalClaimId'},'respond':{'requestId','taskId','revision','intent','note'},'re-review':{'requestId','taskId','revision','reviewWorkerId','reviewCheckpoint','note'}}[operation]
21
+ require(set(value)==expected and value['requestId']==identity,'Use this exact native-message dispatch identity; no invented request')
22
+ if operation=='submit':require(value['channel']==channel and isinstance(value['objective'],str) and 1<=len(value['objective'])<=16000 and value['canonicalClaimId'] is None,'Bounded new assignment required; existing claims need their supported adoption authority')
23
+ else:require(isinstance(value['taskId'],str) and type(value['revision']) is int and value['revision']>0 and isinstance(value['note'],str) and 1<=len(value['note'])<=8000,'Existing task revision and human response required')
24
+ else:require(operation=='list' and value=={},'Unsupported coordinator broker operation')
25
+ return value
26
+
27
+ def context(root,channel,thread,message,snapshot,harness):
28
+ require(harness in {'codex','claude'} and channel in {'engineering','operations','cicd'},'Qualified coordinator channel/harness required')
29
+ require(snapshot.get('authority')=='topolo-docs' and isinstance(snapshot.get('tasks'),list) and isinstance(snapshot.get('workers'),list),'Canonical cloud work snapshot required')
30
+ rows=[t for t in snapshot['tasks'] if t.get('channel')==channel][:20]
31
+ selected=[]
32
+ for task in rows:
33
+ workers=[w for w in snapshot['workers'] if w['taskId']==task['id']]
34
+ selected.append({k:task.get(k) for k in ['id','revision','state','phase','attempt','ownerInstallationId']}|{'objective':str(task.get('objective',''))[:500],'summary':str(task.get('summary',''))[:800],'workers':[{'phase':w['phase'],'state':w['state'],'nativeArchived':w.get('nativeArchived') is True} for w in workers[-8:]]})
35
+ adapter=pathlib.Path(__file__).resolve().parents[2]/'topolo-agent-pack'
36
+ require(adapter.is_file() and not adapter.is_symlink(),'Qualified bundled broker command is unavailable')
37
+ command=[sys.executable,str(adapter),'--store',str(pathlib.Path(root).resolve()),'--harness',harness,'work']
38
+ return {'format':'topolo-coordinator-conversation-v1','contract':CONTRACT,'channel':channel,'dispatchRequestId':dispatch_id(thread,message),'commands':{'list':shlex.join(command+['list']),'requestPrefix':shlex.join(command),'requestPath':'context/coordinator-request.json','wait':'Write {taskId, afterRevision, afterSequence, timeoutSeconds: 30} for an existing task, then wait --input <absolute request path>. This event-driven read returns changed or terminal state and results; do not create another task or poll in a background loop.','read':"Write {taskId} to the request file, then run the requestPrefix plus read --input <absolute request path>.",'dispatch':"Only for an actual assignment: write {requestId: dispatchRequestId, objective: exact bounded assignment, channel, canonicalClaimId: null}, then submit --input <absolute request path>. For existing work use read/respond instead of creating a duplicate.",'allowedOperations':sorted(OPERATIONS),'respond':"For a human-authorized continuation/change/hold of existing work, first read that task and use its returned latest task.revision. Write exactly {requestId: dispatchRequestId, taskId, revision: latest task.revision, intent: 'continue' | 'request_changes' | 'hold', note: exact human instruction}, then respond --input <absolute request path>. This updates the same task; never submit a replacement or reuse a stale revision. A refusal or uncertain response is not permission to replay.",'re-review':"Only for an explicitly requested fresh review of the same failed review, first read the task. Write exactly {requestId: dispatchRequestId, taskId, revision: latest task.revision, reviewWorkerId: rejected reviewer worker ID, reviewCheckpoint: rejected review checkpoint sha256, note: exact review correction}, then re-review --input <absolute request path>. Preserve the original route/execution, attempt, rejection and worker history; do not rerun execution.",'requestTemplates':{'respond':{'requestId':dispatch_id(thread,message),'taskId':'<existing task ID from read>','revision':'<latest task.revision integer from read>','intent':'continue','note':'<exact human continuation/change/hold instruction>'},'re-review':{'requestId':dispatch_id(thread,message),'taskId':'<existing task ID from read>','revision':'<latest task.revision integer from read>','reviewWorkerId':'<rejected reviewer worker ID from read>','reviewCheckpoint':'<rejected review checkpoint sha256 from read>','note':'<exact human review correction>'}}},'tasks':selected,'matchingTasks':sum(t.get('channel')==channel for t in snapshot['tasks']),'retainedCount':snapshot.get('retainedCount'),'evidenceBoundary':'Canonical recorded state only; queued is not running, retainedCount counts historical retained claims rather than active workloads, a model turn ending is not delivery, and no work is submitted by this context read.'}
39
+
40
+ def augment(value,selected):
41
+ require(isinstance(value.get('params',{}).get('input'),list) and bool(value['params']['input']),'Native coordinator input is missing')
42
+ result=copy.deepcopy(value);result['params']['input'].append({'type':'text','text':'Topolo coordinator context (platform data; the original human message above is unchanged):\n'+canonical(selected).decode()});return result
43
+
44
+ def prepare(root,role,event,snapshot,harness='claude'):
45
+ selected=context(root,role['channel'],'claude:'+event['session_id'],event['prompt_id'],snapshot,harness)
46
+ cwd=pathlib.Path(role['cwd']);require(cwd.is_absolute() and cwd.is_dir() and cwd.resolve()==cwd,'Owned coordinator workspace required')
47
+ directory=cwd/'context';require(not directory.is_symlink(),'Coordinator context cannot be a symlink');directory.mkdir(mode=0o700,exist_ok=True)
48
+ atomic_json(directory/'coordinator-session.json',{'format':'topolo-coordinator-session-v1','sessionId':event['session_id'],'dispatchRequestId':selected['dispatchRequestId'],'channel':role['channel'],'commandPrefix':shlex.split(selected['commands']['requestPrefix'])})
49
+ return selected
50
+
51
+ def tool_allowed(root,role,event):
52
+ """Allow reads and exact broker transport, never direct engineering effects."""
53
+ cwd=pathlib.Path(role['cwd']);session=cwd/'context/coordinator-session.json'
54
+ require(cwd.is_absolute() and cwd.is_dir() and cwd.resolve()==cwd and not (cwd/'context').is_symlink() and not session.is_symlink(),'Owned coordinator context required')
55
+ record=read_json(session,{})
56
+ require(record.get('format')=='topolo-coordinator-session-v1' and record.get('sessionId')==event['session_id'] and record.get('channel')==role['channel'],'Current native prompt context is missing')
57
+ expected=[sys.executable,str(pathlib.Path(__file__).resolve().parents[2]/'topolo-agent-pack'),'--store',str(pathlib.Path(root).resolve()),'--harness','claude','work']
58
+ require(record.get('commandPrefix')==expected,'Current bundled broker command changed')
59
+ require(event.get('permission_mode')=='bypassPermissions','Coordinator native permissions differ')
60
+ request_path=cwd/'context/coordinator-request.json';tool=event.get('tool_name');value=event.get('tool_input') or {}
61
+ if tool=='Read':
62
+ path=pathlib.Path(value.get('file_path',''));return path.is_absolute() and path.resolve()==path and path.is_relative_to(cwd) and path.is_file()
63
+ if tool=='Write':
64
+ require(value.get('file_path')==str(request_path) and not request_path.is_symlink(),'Only the typed coordinator request file can be written')
65
+ body=value.get('content');require(isinstance(body,str) and len(body.encode())<=20000,'Bounded broker request required');payload=json.loads(body)
66
+ for operation in OPERATIONS:
67
+ try:payload_valid(payload,operation,record['dispatchRequestId'],role['channel']);return True
68
+ except PackError:pass
69
+ return False
70
+ if tool=='Bash':
71
+ require(set(value)<={'command','description','timeout','run_in_background'} and value.get('run_in_background') is not True,'Foreground typed broker command required')
72
+ args=shlex.split(value.get('command',''));prefix=record['commandPrefix']
73
+ require(args[:len(prefix)]==prefix,'Only the installed typed broker transport is allowed')
74
+ suffix=args[len(prefix):];require(bool(suffix) and suffix[0] in OPERATIONS,'Unsupported broker operation')
75
+ if suffix==['list']:return True
76
+ require(len(suffix)==3 and suffix[1]=='--input' and suffix[2]==str(request_path) and request_path.is_file() and request_path.resolve()==request_path,'Exact owned broker request path required')
77
+ require(request_path.stat().st_size<=20000,'Bounded broker request required')
78
+ payload_valid(read_json(request_path),suffix[0],record['dispatchRequestId'],role['channel']);return True
79
+ return False
80
+
81
+
82
+ def charter(logical,previous):
83
+ """Authored canonical Agent update; installation still requires governed CAS."""
84
+ summaries={'intakes/engineering':'Coordinate engineering conversation and existing tasks; delegate implementation and bounded diagnostics through the task broker.',
85
+ 'intakes/operator-01':'Coordinate operational conversation and existing tasks; delegate bounded business operations to the owning specialist through the task broker.',
86
+ 'intakes/cicd':'Coordinate CI/CD conversation, existing releases and decisions. Delegate bounded diagnostics, engineering and release effects through the task broker; never SSH, edit source or deploy directly.'}
87
+ require(logical in summaries and isinstance(previous,str) and previous.count('## Specialization')==1,'Existing canonical coordinator definition required')
88
+ body=re.sub(r'## Specialization\n.*?(?=\n## |\Z)','## Specialization\n'+summaries[logical]+'\n\n'+CONTRACT+'\n',previous,flags=re.S)
89
+ body=body.replace('Capture exact request, attachments and desired outcome in the existing task. Check its request identity before creating anything. Select one discipline and task complexity/risk; dispatch promptly, then return idle. A separate task worker owns execution and progress. Surface decisions in the GUI; do not occupy this coordinator with implementation.', 'Read the selected cloud task context first. Answer status, follow-ups and clarification in this conversation. For actual assignments use the supplied deterministic request identity and the existing broker; reuse the original task for continuation. Follow its real result through read/wait before reporting the outcome. Specialists own implementation and operational effects.')
90
+ body=body.replace('Execute only the bounded assignment, then return its checkpoint and stop.', 'Report verified task outcomes and next actions in this conversation; specialists checkpoint and stop after their bounded work.')
91
+ return body
@@ -0,0 +1,116 @@
1
+ """Reviewed instruction-only renewal; task state and native identities never change."""
2
+ import copy,fcntl,hashlib,json,os,pathlib
3
+ from contextlib import ExitStack
4
+ from .storage import read_json,atomic_json,PackError
5
+ from .work import require
6
+ from .cloud_work import CloudStore
7
+ from .cloud_cutover import digest
8
+
9
+ FORMAT='topolo-coordinator-renewal-v1'
10
+ LOGICAL={'intakes/engineering','intakes/operator-01','intakes/cicd'}
11
+
12
+ def local(lib):
13
+ names=['config.json','role-bindings.json','cloud.json','cutover.json','cloud-migration.json','role-catalog.json']
14
+ from .closed_ledger_fence import owned
15
+ paths=[lib.root/'work'/name for name in names if (lib.root/'work'/name).exists() or (lib.root/'work'/name).is_symlink()]
16
+ for path in paths:owned(path)
17
+ return {path.name:read_json(path) for path in paths}
18
+
19
+ def verified_local(lib):
20
+ path=lib.root/'work/coordinator-renewal.json'
21
+ from .closed_ledger_fence import owned
22
+ owned(path)
23
+ record=read_json(path)
24
+ require(record.get('format')==FORMAT and record.get('state')=='verified'
25
+ and record.get('root')==str(lib.root.resolve()),
26
+ 'Coordinator renewal is incomplete or belongs to another authority')
27
+ current=local(lib)
28
+ require(current==record['after'],'Renewed coordinator metadata changed')
29
+ return record
30
+
31
+ def effective(lib,migration):
32
+ path=lib.root/'work/coordinator-renewal.json'
33
+ if not path.exists():return migration['afterMetadataDigest'],migration['request']['expectedTargetCatalogDigest']
34
+ record=verified_local(lib)
35
+ require(record.get('originalMigrationDigest')==digest(migration)
36
+ and record.get('binding')==migration['binding'],'Coordinator renewal changed migration authority')
37
+ return record['afterMetadataDigest'],record['targetCatalogDigest']
38
+
39
+ def renew(lib,request,client=None):
40
+ require(isinstance(request,dict) and set(request)=={'expectedLocalDigest','reviewedBindings','expectedTargetCatalogDigest'},
41
+ 'Exact coordinator renewal selectors required')
42
+ with lib.lock(),ExitStack() as stack:
43
+ for name in ['claude-service-start.lock','claude-service-owner.lock']:
44
+ path=lib.root/'work'/name
45
+ require(not path.is_symlink(),'Coordinator owner lock cannot be a symlink')
46
+ stream=stack.enter_context(path.open('a'))
47
+ try:fcntl.flock(stream.fileno(),fcntl.LOCK_EX|fcntl.LOCK_NB)
48
+ except BlockingIOError:raise PackError('Coordinator dispatcher owner lock is held')
49
+ bridge=read_json(lib.root/'live-bridge.json',{})
50
+ if bridge:
51
+ pid=bridge.get('pid');require(type(pid) is int and pid>1,'Native dispatcher owner is unverified')
52
+ try:os.kill(pid,0)
53
+ except ProcessLookupError:pass
54
+ else:require(False,'Stop the idle owning dispatcher before coordinator renewal')
55
+ journal=lib.root/'work/coordinator-renewal.json'
56
+ require(not journal.exists() and not journal.is_symlink(),'Existing coordinator renewal requires reconciliation, not replay')
57
+ before=local(lib);require(digest(before)==request['expectedLocalDigest'],'Reviewed coordinator preimage changed')
58
+ binding=before['cloud.json'];registry=before['role-bindings.json'];refs=request['reviewedBindings']
59
+ old={k:v for k,v in registry['roles'].items() if v['logicalId'] in LOGICAL}
60
+ require(len(old)==3 and isinstance(refs,dict) and set(refs)==set(old),'Exactly the three existing coordinators are required')
61
+ if client is None:
62
+ from .topolo import Client
63
+ client=Client('production','org:org_topolo_platform')
64
+ require(client.environment=='production' and client.context=='org:org_topolo_platform','Production organization context required')
65
+ from .coordinator_context import CONTRACT
66
+ for key,ref in refs.items():
67
+ prior=old[key]
68
+ require(set(ref)==set(prior) and {k:v for k,v in ref.items() if k!='instructionsSHA256'}=={k:v for k,v in prior.items() if k!='instructionsSHA256'},
69
+ 'Coordinator identity or domain context cannot change')
70
+ agent=client.action('app_topolo_agent.agents.get',{'id':ref['agentId']},ref['workspaceId'])['agent']
71
+ config=json.loads(agent['config']) if isinstance(agent['config'],str) else agent['config'];spec=config['specialization']
72
+ require(agent['id']==ref['agentId'] and agent['workspaceId']==ref['workspaceId']
73
+ and agent['organizationId']==binding['organizationId']=='org_topolo_platform'
74
+ and spec['format']=='topolo-role-specialization-v1' and spec['logicalId']==ref['logicalId']
75
+ and config.get('domainContextRefs',[])==ref.get('domainContextRefs',[])
76
+ and config['purpose']==spec['instructions'] and CONTRACT in spec['instructions']
77
+ and hashlib.sha256(spec['instructions'].encode()).hexdigest()==ref['instructionsSHA256'],
78
+ 'Live coordinator conversation instructions differ')
79
+ from types import SimpleNamespace
80
+ version,state=CloudStore.load(SimpleNamespace(binding=binding,client=client))
81
+ for row in state['tables']['workers']:
82
+ w=json.loads(row[2])
83
+ require(w['state']=='archived' and w.get('nativeArchived') is True or
84
+ w['state']=='failed' and not w.get('turnId') and (not w.get('nativeId') or w.get('nativeArchived') is True),
85
+ 'Native worker closure is unresolved; preserve its instructions')
86
+ after=copy.deepcopy(before);after['role-bindings.json']['roles'].update(copy.deepcopy(refs))
87
+ migration=before.get('cloud-migration.json')
88
+ if 'role-catalog.json' in before:
89
+ catalog=before['role-catalog.json'];require(registry['roles']==catalog['bindings'] and before['config.json']['catalogDigest']==digest(catalog)==request['expectedTargetCatalogDigest'],
90
+ 'Canonical role catalog preimage differs')
91
+ after['role-catalog.json']['bindings'].update(copy.deepcopy(refs))
92
+ target_digest=digest(after['role-catalog.json']);after['config.json']['catalogDigest']=target_digest
93
+ else:
94
+ require(migration is not None,'Existing migrated installation required')
95
+ target=pathlib.Path(before['cutover.json']['request']['destinationRoot'])
96
+ catalog=read_json(target/'work/role-catalog.json');target_digest=digest(catalog)
97
+ require(target_digest==request['expectedTargetCatalogDigest'],'Renewed canonical catalog differs')
98
+ canonical={r['logicalId']:r for r in catalog['bindings'].values()}
99
+ require(all(canonical.get(ref['logicalId'])==ref for ref in refs.values()),'Coordinator references differ from canonical renewal')
100
+ require(local(lib)==before,'Coordinator metadata changed during validation')
101
+ record={'format':FORMAT,'state':'prepared','root':str(lib.root.resolve()),'before':before,'after':after,
102
+ 'binding':binding,'cloudVersionId':version,'targetCatalogDigest':target_digest,
103
+ 'originalMigrationDigest':digest(migration) if migration else None,'cloudTaskStateChanged':False}
104
+ atomic_json(journal,record)
105
+ for name in ['role-bindings.json','role-catalog.json','config.json']:
106
+ if name in after:atomic_json(lib.root/'work'/name,after[name])
107
+ require(local(lib)==after,'Coordinator renewal readback differs')
108
+ if migration:
109
+ from .cloud_migrate import metadata
110
+ record['afterMetadataDigest']=digest(metadata(lib))
111
+ atomic_json(journal,{**record,'state':'verified'})
112
+ if migration:
113
+ from .cloud_migrate import verified
114
+ verified(lib.root)
115
+ return {'state':'coordinator-instructions-renewed','coordinators':3,'catalogDigest':target_digest,
116
+ 'tasksCreated':0,'nativeSessionsCreated':0,'cloudTaskStateChanged':False}
@@ -0,0 +1,45 @@
1
+ """One fixed no-tool turn through the owning proxy; never replays delivery."""
2
+ import pathlib
3
+ import uuid
4
+ from .storage import PackError,atomic_json,read_json
5
+
6
+ async def run(owner,params):
7
+ if not isinstance(params,dict) or set(params)!={'operationId','key'}:
8
+ raise PackError('Exact desktop acceptance identity required')
9
+ try:valid=str(uuid.UUID(params['operationId']))==params['operationId']
10
+ except (ValueError,TypeError,AttributeError):valid=False
11
+ if not valid:
12
+ raise PackError('Acceptance UUID required')
13
+ path=pathlib.Path(owner.runtime)/'desktop-acceptance'/(params['operationId']+'.json')
14
+ rows=[r for r in owner.execution.roles.values() if r['key']==params['key']]
15
+ if len(rows)!=1:
16
+ raise PackError('Owned acceptance role missing')
17
+ role=rows[0];tid=role['threadId']
18
+ if path.exists():
19
+ record=read_json(path)
20
+ if record['key']!=params['key'] or record['threadId']!=tid:
21
+ raise PackError('Acceptance identity changed')
22
+ if not record.get('turnId'):return record
23
+ native=await owner.call_native('thread/read',{'threadId':tid,'includeTurns':True})
24
+ matches=[t for t in native['thread'].get('turns',[]) if t['id']==record['turnId']]
25
+ if len(matches)!=1:
26
+ raise PackError('Exact acceptance turn missing')
27
+ turn=matches[0];items=turn.get('items',[])
28
+ text='\n'.join(i.get('text','') for i in items if i.get('type')=='agentMessage' and i.get('phase')!='commentary')
29
+ record.update(nativeStatus=turn['status'],reply=text)
30
+ record['verified']=turn['status']=='completed' and text.strip().rstrip('.')=='TOPOLO_DESKTOP_ACCEPTED' and all(i.get('type') in {'userMessage','agentMessage','reasoning','tokenUsage'} for i in items)
31
+ atomic_json(path,record);return record
32
+ if owner.active_turns or owner.pending or owner.quiesced:
33
+ raise PackError('Acceptance requires an idle owner')
34
+ await owner.call_native('thread/resume',{'threadId':tid})
35
+ spec=role['settings'];record={**params,'threadId':tid,'state':'reserved','verified':False,'settings':spec}
36
+ atomic_json(path,record)
37
+ # Save uncertainty before sending. A lost ACK cannot create another turn.
38
+ record['state']='delivery-uncertain';atomic_json(path,record)
39
+ roots=[role['cwd']]
40
+ home=getattr(owner.execution,'desktop_home',None)
41
+ if home is not None:
42
+ matches=list((pathlib.Path(home)/'visualizations').glob('*/*/*/'+tid))
43
+ if len(matches)==1:roots.append(str(matches[0]))
44
+ result=await owner.call_native('turn/start',{'threadId':tid,'input':[{'type':'text','text':'Transport acceptance test only. Do not use tools, read files, perform work, contact anyone or resume any task. Reply exactly TOPOLO_DESKTOP_ACCEPTED.'}],'environments':[{'environmentId':'local','cwd':role['cwd'],'runtimeWorkspaceRoots':roots}], 'runtimeWorkspaceRoots':roots, 'collaborationMode':{'mode':'default','settings':{'model':spec['model'],'reasoning_effort':spec['model_reasoning_effort'],'developer_instructions':None}}})
45
+ record.update(state='native-accepted',turnId=result['turn']['id']);atomic_json(path,record);return record
@@ -0,0 +1,114 @@
1
+ """An owned Dock launcher for the selected Agent Pack, separate from personal apps."""
2
+ import hashlib
3
+ import json
4
+ import os
5
+ from pathlib import Path
6
+ import plistlib
7
+ import shlex
8
+ import re
9
+ import subprocess
10
+ import sys
11
+ import tempfile
12
+
13
+ from .storage import PackError, atomic_json, read_json
14
+ from .work import require
15
+
16
+
17
+ def _digest(path):
18
+ return hashlib.sha256(path.read_bytes()).hexdigest()
19
+
20
+
21
+ def _files(root):
22
+ result = {}
23
+ for path in sorted(root.rglob('*')):
24
+ metadata = path.lstat()
25
+ require(not path.is_symlink() and metadata.st_uid == os.getuid()
26
+ and not metadata.st_mode & 0o022, 'Desktop launcher contains an unowned or writable entry')
27
+ if path.is_file():
28
+ result[str(path.relative_to(root))] = _digest(path)
29
+ return result
30
+
31
+
32
+ def install(lib, app, codex_bin, run=None):
33
+ """Caller owns Library.lock. Compilation/configuration does not open any app."""
34
+ require(sys.platform == 'darwin', 'Managed Dock launchers require macOS')
35
+ run = run or subprocess.run
36
+ root = lib.root.resolve(); folder = root / 'launchers'
37
+ folder.mkdir(exist_ok=True, mode=0o700)
38
+ target = folder / 'Topolo Agent Pack.app'; shim = folder / 'topolo-agent-pack-open'
39
+ record = folder / 'desktop-launcher.json'
40
+ require(not folder.is_symlink() and folder.stat().st_uid == os.getuid()
41
+ and not folder.stat().st_mode & 0o022, 'Unowned desktop launcher directory')
42
+ app = Path(app).expanduser().resolve(); native = Path(codex_bin).expanduser().resolve()
43
+ require(app.is_dir() and native.is_file(), 'Existing native application and CLI are required')
44
+ adapter = Path(__file__).resolve().parents[2] / 'topolo-agent-pack'
45
+ require(adapter.is_file(), 'Installed public pack adapter is unavailable')
46
+ argv = [sys.executable, str(adapter), '--store', str(root), 'open', '--app', str(app), '--codex-bin', str(native)]
47
+ source = '#!' + sys.executable + '\nimport os\nos.execv(' + repr(sys.executable) + ',' + repr(argv) + ')\n'
48
+ script = ('on openPack()\ntry\ndo shell script ' + json.dumps(shlex.join([str(shim)])) +
49
+ '\non error messageText number errorNumber\ndisplay alert "Topolo Agent Pack" message messageText as warning\nend try\nend openPack\non run\nmy openPack()\nend run\non reopen\nmy openPack()\nend reopen\n')
50
+ script_digest = hashlib.sha256(script.encode()).hexdigest()
51
+ previous = read_json(record, {})
52
+ if previous:
53
+ require(set(previous) == {'format', 'store', 'app', 'scriptSHA256', 'appFiles', 'shimSHA256'}
54
+ and previous['format'] == 'topolo-managed-desktop-launcher-v1'
55
+ and previous['store'] == str(root) and previous['app'] == str(target)
56
+ and previous['scriptSHA256'] == script_digest
57
+ and not target.is_symlink() and target.is_dir() and _files(target) == previous['appFiles']
58
+ and not shim.is_symlink() and shim.is_file() and _digest(shim) == previous['shimSHA256'],
59
+ 'Existing managed Dock launcher changed; reconcile before replacement')
60
+ else:
61
+ require(not target.exists() and not target.is_symlink() and not shim.exists() and not shim.is_symlink(),
62
+ 'Desktop launcher destination is not an owned installation')
63
+ with tempfile.TemporaryDirectory(prefix='.desktop-launcher-', dir=folder) as temporary:
64
+ temporary = Path(temporary); apple = temporary / 'launcher.applescript'; apple.write_text(script)
65
+ built = temporary / target.name
66
+ run(['/usr/bin/osacompile', '-s', '-o', str(built), str(apple)], check=True, timeout=30)
67
+ info = built / 'Contents/Info.plist'
68
+ value = plistlib.loads(info.read_bytes())
69
+ value.update(CFBundleIdentifier='io.topolo.agent-pack.' + hashlib.sha256(str(root).encode()).hexdigest()[:16],
70
+ CFBundleName='Topolo Agent Pack', CFBundleDisplayName='Topolo Agent Pack')
71
+ info.write_bytes(plistlib.dumps(value))
72
+ run(['/usr/bin/codesign', '--force', '--sign', '-', str(built)], check=True, timeout=30)
73
+ run(['/usr/bin/codesign', '--verify', '--deep', '--strict', str(built)], check=True, timeout=15)
74
+ _files(built)
75
+ os.rename(built, target)
76
+ # Refuse unjournaled partial publication instead of silently replacing it.
77
+ temporary = shim.with_name(shim.name + '.new')
78
+ require(not temporary.exists() and not temporary.is_symlink(), 'Desktop launcher publication is unresolved')
79
+ with temporary.open('x') as stream:
80
+ os.chmod(temporary, 0o700); stream.write(source); stream.flush(); os.fsync(stream.fileno())
81
+ os.replace(temporary, shim)
82
+ value = {'format': 'topolo-managed-desktop-launcher-v1', 'store': str(root), 'app': str(target),
83
+ 'scriptSHA256': script_digest, 'appFiles': _files(target), 'shimSHA256': _digest(shim)}
84
+ atomic_json(record, value)
85
+ require(read_json(record) == value and _files(target) == value['appFiles'], 'Desktop launcher readback differs')
86
+ return {'state': 'managed-desktop-launcher-installed', 'app': str(target), 'store': str(root),
87
+ 'appOpened': False, 'dockPreferencesChanged': False}
88
+
89
+
90
+ def restore(lib, bridge, app, run=None):
91
+ """Activate only the current managed PID; never prepare or restart its backend."""
92
+ require(sys.platform == 'darwin', 'Managed window restoration requires macOS')
93
+ run = run or subprocess.run
94
+ pid = bridge.get('desktopPid'); expected_runtime = lib.active().get('runtime')
95
+ require(type(pid) is int and pid > 1 and bridge.get('runtime') == expected_runtime
96
+ and bridge.get('nativeTransport') == 'owned-stdio', 'Exact managed Desktop owner is required')
97
+ os.kill(pid, 0)
98
+ command = run(['/bin/ps', '-p', str(pid), '-o', 'command='], check=True, capture_output=True,
99
+ text=True, timeout=5).stdout.strip()
100
+ bundle = Path(app).expanduser().resolve()
101
+ executable_name = plistlib.loads((bundle / 'Contents/Info.plist').read_bytes())['CFBundleExecutable']
102
+ require(isinstance(executable_name, str) and Path(executable_name).name == executable_name,
103
+ 'Native application executable identity differs')
104
+ executable = str(bundle / 'Contents/MacOS' / executable_name)
105
+ flag = '--user-data-dir=' + str(lib.root / 'desktop-data')
106
+ require(command.startswith(executable + ' ') and re.search(r'(?:^|\s)' + re.escape(flag) + r'(?:\s|$)', command),
107
+ 'Managed Desktop process does not own this library profile')
108
+ # AppKit activation targets this PID. Electron's normal activate handler
109
+ # restores its primary window; a same-bundle personal process is untouched.
110
+ script = ('ObjC.import("AppKit"); const app=$.NSRunningApplication.runningApplicationWithProcessIdentifier(' +
111
+ str(pid) + '); if(!app || !app.activateWithOptions($.NSApplicationActivateAllWindows|$.NSApplicationActivateIgnoringOtherApps)) throw Error("Managed window activation refused");')
112
+ run(['/usr/bin/osascript', '-l', 'JavaScript', '-e', script], check=True, timeout=15)
113
+ return {'state': 'managed-window-restoration-requested', 'desktopPid': pid,
114
+ 'runtime': expected_runtime, 'backendRestarted': False, 'frontendVerified': False}
@@ -0,0 +1,38 @@
1
+ """Selected immutable operating references, separate from task history."""
2
+ import hashlib,pathlib
3
+ from .storage import atomic_json
4
+ from .work import require
5
+ from .cicd import DISCIPLINES
6
+
7
+
8
+ def references(config,logical):
9
+ selected=logical=='intakes/cicd' or logical in {'specialists/'+name for name in DISCIPLINES}
10
+ refs=config.get('domainContextRefs',[])
11
+ require(isinstance(refs,list) and len(refs)==(1 if selected else 0),'CI/CD requires one reviewed release-controller domain reference')
12
+ for ref in refs:
13
+ require(isinstance(ref,dict) and set(ref)=={'workspaceId','documentId','versionId','bodySHA256','heading','sectionSHA256'} and all(isinstance(v,str) and v for v in ref.values()),'Invalid domain context reference')
14
+ require(ref['heading']=='CI/CD operating bootstrap' and all(len(ref[k])==64 and all(c in '0123456789abcdef' for c in ref[k]) for k in ['bodySHA256','sectionSHA256']),'Unqualified release-controller bootstrap reference')
15
+ return refs
16
+
17
+
18
+ def materialize(refs,cwd,client):
19
+ receipts=[]
20
+ for ref in refs:
21
+ result=client.action('app_topolo_docs.documents.get',{'documentId':ref['documentId']},ref['workspaceId'])
22
+ doc=result['document'];require(isinstance(result['versions'],list) and len(result['versions'])<=128,'Operating reference history exceeds the bounded reader')
23
+ versions=[v for v in result['versions'] if v['id']==ref['versionId']]
24
+ require(doc['id']==ref['documentId'] and doc['workspaceId']==ref['workspaceId'] and doc['organizationId']=='org_topolo_platform' and len(versions)==1,'Pinned operating reference is unavailable')
25
+ body=versions[0]['bodyMarkdown']
26
+ require(isinstance(body,str) and len(body.encode())<=1000000,'Operating reference body exceeds the bounded reader')
27
+ require(hashlib.sha256(body.encode()).hexdigest()==ref['bodySHA256'],'Pinned operating reference changed')
28
+ marker='## '+ref['heading']+'\n'
29
+ require(body.count(marker)==1,'Operating bootstrap heading is missing or ambiguous')
30
+ section=body.split(marker,1)[1].split('\n## ',1)[0].strip()+'\n'
31
+ require(100<=len(section.encode())<=12000 and hashlib.sha256(section.encode()).hexdigest()==ref['sectionSHA256'],'Operating bootstrap section differs')
32
+ require(all(term in section for term in ['Topolo-io/TopoloReleaseController','topolo-box-submit','"op":"intent-status"','"op":"job-diagnostic"','hosts','read-only']),'Operating bootstrap lacks controller access and custody boundaries')
33
+ target=pathlib.Path(cwd)/'context';require(not target.is_symlink(),'Domain context directory cannot be a symlink');target.mkdir(exist_ok=True,mode=0o700)
34
+ path=target/'release-controller.md';require(not path.is_symlink(),'Domain context file cannot be a symlink')
35
+ path.write_text('# Verified CI/CD operating reference\n\n'+marker+section);path.chmod(0o600)
36
+ receipts.append(dict(ref))
37
+ if receipts:atomic_json(pathlib.Path(cwd)/'context/domain-source.json',{'format':'topolo-selected-domain-context-v1','references':receipts,'taskHistoryLoaded':False,'wholeDocumentLoadedIntoContext':False})
38
+ return receipts