zob-harness 0.1.0

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 (356) hide show
  1. package/.pi/adapters/registry.json +103 -0
  2. package/.pi/agents/architecture-cartographer.md +53 -0
  3. package/.pi/agents/chief-vision.md +39 -0
  4. package/.pi/agents/clarifier.md +58 -0
  5. package/.pi/agents/context-steward.md +52 -0
  6. package/.pi/agents/doc-steward.md +34 -0
  7. package/.pi/agents/explore.md +49 -0
  8. package/.pi/agents/factory.md +41 -0
  9. package/.pi/agents/implementer.md +44 -0
  10. package/.pi/agents/librarian.md +32 -0
  11. package/.pi/agents/oracle-merge.md +50 -0
  12. package/.pi/agents/oracle.md +55 -0
  13. package/.pi/agents/pattern-miner.md +53 -0
  14. package/.pi/agents/planner.md +39 -0
  15. package/.pi/agents/project-dna-golden-evaluator.md +32 -0
  16. package/.pi/agents/project-dna-ontology-steward.md +30 -0
  17. package/.pi/agents/project-dna-oracle.md +56 -0
  18. package/.pi/agents/project-dna-orchestrator.md +60 -0
  19. package/.pi/agents/project-dna-query-steward.md +38 -0
  20. package/.pi/agents/project-dna-safety-preflight.md +54 -0
  21. package/.pi/agents/project-dna-test-linker.md +27 -0
  22. package/.pi/agents/qa.md +38 -0
  23. package/.pi/agents/refactor-cartographer.md +28 -0
  24. package/.pi/agents/refactor-mover.md +31 -0
  25. package/.pi/agents/refactor-oracle.md +49 -0
  26. package/.pi/agents/repo-scout.md +60 -0
  27. package/.pi/agents/sample-architect.md +48 -0
  28. package/.pi/agents/specifier.md +57 -0
  29. package/.pi/agents/symbol-range-curator.md +41 -0
  30. package/.pi/agents/synthesis.md +52 -0
  31. package/.pi/agents/temp-agent-creator.md +35 -0
  32. package/.pi/autonomy-policy.json +67 -0
  33. package/.pi/budget-policy.json +54 -0
  34. package/.pi/capabilities/zob-public-runtime-capabilities.json +1700 -0
  35. package/.pi/chains/explore-plan-oracle.json +78 -0
  36. package/.pi/chains/explore-spec-clarify-plan-oracle.json +64 -0
  37. package/.pi/chains/explore-spec-plan-oracle.json +53 -0
  38. package/.pi/chains/spec-clarify-plan-oracle.json +53 -0
  39. package/.pi/chains/spec-factory-oracle.json +42 -0
  40. package/.pi/chains/spec-plan-oracle.json +42 -0
  41. package/.pi/compute-profiles/defaults.json +19 -0
  42. package/.pi/compute-profiles/overrides.json +13 -0
  43. package/.pi/compute-profiles/risk-rules.json +16 -0
  44. package/.pi/daemon-policy.json +80 -0
  45. package/.pi/damage-control-rules.json +45 -0
  46. package/.pi/extensions/zob-child-safety/index.ts +212 -0
  47. package/.pi/extensions/zob-harness/AGENTS.md +28 -0
  48. package/.pi/extensions/zob-harness/index.ts +391 -0
  49. package/.pi/extensions/zob-harness/src/AGENTS.md +25 -0
  50. package/.pi/extensions/zob-harness/src/agents.ts +82 -0
  51. package/.pi/extensions/zob-harness/src/autonomous-runtime.ts +2912 -0
  52. package/.pi/extensions/zob-harness/src/autonomy-readiness.ts +778 -0
  53. package/.pi/extensions/zob-harness/src/budget-policy.ts +308 -0
  54. package/.pi/extensions/zob-harness/src/capabilities.ts +249 -0
  55. package/.pi/extensions/zob-harness/src/child-runner.ts +249 -0
  56. package/.pi/extensions/zob-harness/src/chronicle.ts +262 -0
  57. package/.pi/extensions/zob-harness/src/compute-profile.ts +602 -0
  58. package/.pi/extensions/zob-harness/src/compute-workflow-shape.ts +168 -0
  59. package/.pi/extensions/zob-harness/src/coms-v2/AGENTS.md +16 -0
  60. package/.pi/extensions/zob-harness/src/coms-v2/envelope.ts +121 -0
  61. package/.pi/extensions/zob-harness/src/coms-v2/identity.ts +53 -0
  62. package/.pi/extensions/zob-harness/src/coms-v2/ledger-bridge.ts +67 -0
  63. package/.pi/extensions/zob-harness/src/coms-v2/local-transport.ts +147 -0
  64. package/.pi/extensions/zob-harness/src/coms-v2/pending-replies.ts +80 -0
  65. package/.pi/extensions/zob-harness/src/coms-v2/policy.ts +125 -0
  66. package/.pi/extensions/zob-harness/src/coms-v2/presence.ts +55 -0
  67. package/.pi/extensions/zob-harness/src/coms-v2/registry.ts +113 -0
  68. package/.pi/extensions/zob-harness/src/coms-v2/response-capture.ts +50 -0
  69. package/.pi/extensions/zob-harness/src/coms-v2/transcript-capture.ts +164 -0
  70. package/.pi/extensions/zob-harness/src/coms-v2/types.ts +149 -0
  71. package/.pi/extensions/zob-harness/src/coms-v2/zpeer-profile.ts +140 -0
  72. package/.pi/extensions/zob-harness/src/coms-v2/zpeer.ts +452 -0
  73. package/.pi/extensions/zob-harness/src/constants.ts +108 -0
  74. package/.pi/extensions/zob-harness/src/context-gbrain.ts +465 -0
  75. package/.pi/extensions/zob-harness/src/daemon-policy.ts +223 -0
  76. package/.pi/extensions/zob-harness/src/daemon-readiness.ts +134 -0
  77. package/.pi/extensions/zob-harness/src/daemon-runtime.ts +393 -0
  78. package/.pi/extensions/zob-harness/src/factory/AGENTS.md +24 -0
  79. package/.pi/extensions/zob-harness/src/factory/agentic-plan.ts +65 -0
  80. package/.pi/extensions/zob-harness/src/factory/quarantine.ts +319 -0
  81. package/.pi/extensions/zob-harness/src/factory/run.ts +520 -0
  82. package/.pi/extensions/zob-harness/src/factory/validation.ts +454 -0
  83. package/.pi/extensions/zob-harness/src/factory-selector.ts +318 -0
  84. package/.pi/extensions/zob-harness/src/full-autonomy-test.ts +226 -0
  85. package/.pi/extensions/zob-harness/src/git-ops.ts +868 -0
  86. package/.pi/extensions/zob-harness/src/goal-room.ts +178 -0
  87. package/.pi/extensions/zob-harness/src/goal-runtime.ts +1569 -0
  88. package/.pi/extensions/zob-harness/src/goal-todo-imports.ts +111 -0
  89. package/.pi/extensions/zob-harness/src/goal-todo-types.ts +231 -0
  90. package/.pi/extensions/zob-harness/src/goal-todos.ts +1410 -0
  91. package/.pi/extensions/zob-harness/src/goal.ts +152 -0
  92. package/.pi/extensions/zob-harness/src/governed-requests.ts +436 -0
  93. package/.pi/extensions/zob-harness/src/interactive-autonomy.ts +595 -0
  94. package/.pi/extensions/zob-harness/src/launch-apply.ts +313 -0
  95. package/.pi/extensions/zob-harness/src/merge-queue.ts +290 -0
  96. package/.pi/extensions/zob-harness/src/mission-control.ts +573 -0
  97. package/.pi/extensions/zob-harness/src/model-availability.ts +52 -0
  98. package/.pi/extensions/zob-harness/src/model-routing.ts +429 -0
  99. package/.pi/extensions/zob-harness/src/orchestration/AGENTS.md +23 -0
  100. package/.pi/extensions/zob-harness/src/orchestration/adaptive-delegation.ts +547 -0
  101. package/.pi/extensions/zob-harness/src/orchestration/adaptive-workflow.ts +585 -0
  102. package/.pi/extensions/zob-harness/src/orchestration/lead-plan.ts +192 -0
  103. package/.pi/extensions/zob-harness/src/orchestration/plan.ts +168 -0
  104. package/.pi/extensions/zob-harness/src/orchestration/room.ts +346 -0
  105. package/.pi/extensions/zob-harness/src/orchestration/run.ts +134 -0
  106. package/.pi/extensions/zob-harness/src/orchestration/supervised-readonly.ts +1147 -0
  107. package/.pi/extensions/zob-harness/src/orchestration/widget-readers.ts +132 -0
  108. package/.pi/extensions/zob-harness/src/output-contracts.ts +656 -0
  109. package/.pi/extensions/zob-harness/src/project-dna.ts +533 -0
  110. package/.pi/extensions/zob-harness/src/promotion/AGENTS.md +24 -0
  111. package/.pi/extensions/zob-harness/src/promotion/candidate.ts +336 -0
  112. package/.pi/extensions/zob-harness/src/promotion/coms.ts +127 -0
  113. package/.pi/extensions/zob-harness/src/promotion/documentation.ts +142 -0
  114. package/.pi/extensions/zob-harness/src/promotion/factory.ts +107 -0
  115. package/.pi/extensions/zob-harness/src/promotion/ledger.ts +2 -0
  116. package/.pi/extensions/zob-harness/src/promotion/temp-agent.ts +151 -0
  117. package/.pi/extensions/zob-harness/src/promotion/types.ts +149 -0
  118. package/.pi/extensions/zob-harness/src/promotion/validate.ts +6 -0
  119. package/.pi/extensions/zob-harness/src/promotion/write-lane.ts +162 -0
  120. package/.pi/extensions/zob-harness/src/prompt-packs.ts +239 -0
  121. package/.pi/extensions/zob-harness/src/queue.ts +386 -0
  122. package/.pi/extensions/zob-harness/src/rules.ts +225 -0
  123. package/.pi/extensions/zob-harness/src/runtime/AGENTS.md +26 -0
  124. package/.pi/extensions/zob-harness/src/runtime/adaptive-zmode.ts +116 -0
  125. package/.pi/extensions/zob-harness/src/runtime/auto-compaction.ts +715 -0
  126. package/.pi/extensions/zob-harness/src/runtime/commands.ts +1315 -0
  127. package/.pi/extensions/zob-harness/src/runtime/compaction-policy.ts +516 -0
  128. package/.pi/extensions/zob-harness/src/runtime/delegation-click-markers.ts +141 -0
  129. package/.pi/extensions/zob-harness/src/runtime/delegation-feed.ts +415 -0
  130. package/.pi/extensions/zob-harness/src/runtime/delegation-markdown.ts +97 -0
  131. package/.pi/extensions/zob-harness/src/runtime/delegation-monitor.ts +553 -0
  132. package/.pi/extensions/zob-harness/src/runtime/delegation-mouse.ts +205 -0
  133. package/.pi/extensions/zob-harness/src/runtime/delegation-overlay.ts +434 -0
  134. package/.pi/extensions/zob-harness/src/runtime/events.ts +736 -0
  135. package/.pi/extensions/zob-harness/src/runtime/goal-todo-overlay.ts +214 -0
  136. package/.pi/extensions/zob-harness/src/runtime/mode-intent.ts +144 -0
  137. package/.pi/extensions/zob-harness/src/runtime/plan-capture.ts +270 -0
  138. package/.pi/extensions/zob-harness/src/runtime/state.ts +403 -0
  139. package/.pi/extensions/zob-harness/src/runtime/tools-autonomous.ts +117 -0
  140. package/.pi/extensions/zob-harness/src/runtime/tools-compute.ts +136 -0
  141. package/.pi/extensions/zob-harness/src/runtime/tools-coms.ts +365 -0
  142. package/.pi/extensions/zob-harness/src/runtime/tools-context.ts +70 -0
  143. package/.pi/extensions/zob-harness/src/runtime/tools-delegation.ts +1854 -0
  144. package/.pi/extensions/zob-harness/src/runtime/tools-factory.ts +810 -0
  145. package/.pi/extensions/zob-harness/src/runtime/tools-goal-room.ts +46 -0
  146. package/.pi/extensions/zob-harness/src/runtime/tools-governed-requests.ts +38 -0
  147. package/.pi/extensions/zob-harness/src/runtime/tools-merge-queue.ts +61 -0
  148. package/.pi/extensions/zob-harness/src/runtime/tools-mission-control.ts +77 -0
  149. package/.pi/extensions/zob-harness/src/runtime/tools-orchestration.ts +106 -0
  150. package/.pi/extensions/zob-harness/src/runtime/tools-project-dna.ts +123 -0
  151. package/.pi/extensions/zob-harness/src/runtime/tools-worker-pool.ts +93 -0
  152. package/.pi/extensions/zob-harness/src/runtime/tools-workspace-claims.ts +62 -0
  153. package/.pi/extensions/zob-harness/src/runtime/tools-zcommit.ts +147 -0
  154. package/.pi/extensions/zob-harness/src/runtime/widget.ts +353 -0
  155. package/.pi/extensions/zob-harness/src/runtime/zobHarness.ts +60 -0
  156. package/.pi/extensions/zob-harness/src/safety.ts +338 -0
  157. package/.pi/extensions/zob-harness/src/sandbox.ts +1508 -0
  158. package/.pi/extensions/zob-harness/src/schemas-project-dna.ts +47 -0
  159. package/.pi/extensions/zob-harness/src/schemas.ts +695 -0
  160. package/.pi/extensions/zob-harness/src/telemetry.ts +373 -0
  161. package/.pi/extensions/zob-harness/src/topology/AGENTS.md +22 -0
  162. package/.pi/extensions/zob-harness/src/topology/chains.ts +236 -0
  163. package/.pi/extensions/zob-harness/src/topology/coms.ts +211 -0
  164. package/.pi/extensions/zob-harness/src/topology/orchestration-profiles.ts +204 -0
  165. package/.pi/extensions/zob-harness/src/topology/teams.ts +113 -0
  166. package/.pi/extensions/zob-harness/src/types/core.ts +47 -0
  167. package/.pi/extensions/zob-harness/src/types.ts +939 -0
  168. package/.pi/extensions/zob-harness/src/utils/AGENTS.md +22 -0
  169. package/.pi/extensions/zob-harness/src/utils/formatting.ts +34 -0
  170. package/.pi/extensions/zob-harness/src/utils/hashing.ts +11 -0
  171. package/.pi/extensions/zob-harness/src/utils/json.ts +28 -0
  172. package/.pi/extensions/zob-harness/src/utils/paths.ts +54 -0
  173. package/.pi/extensions/zob-harness/src/utils/records.ts +25 -0
  174. package/.pi/extensions/zob-harness/src/utils/resources.ts +38 -0
  175. package/.pi/extensions/zob-harness/src/worker-pool.ts +672 -0
  176. package/.pi/extensions/zob-harness/src/workspace-claims.ts +297 -0
  177. package/.pi/extensions/zob-switch/index.ts +180 -0
  178. package/.pi/factories/budget-preflight-dry-run/batch-manifest.json +59 -0
  179. package/.pi/factories/budget-preflight-dry-run/factory.json +94 -0
  180. package/.pi/factories/budget-preflight-dry-run/pilot-manifest.json +50 -0
  181. package/.pi/factories/budget-preflight-dry-run/smoke-manifest.json +43 -0
  182. package/.pi/factories/code-review-matrix/batch-manifest.json +61 -0
  183. package/.pi/factories/code-review-matrix/factory.json +163 -0
  184. package/.pi/factories/code-review-matrix/pilot-manifest.json +41 -0
  185. package/.pi/factories/code-review-matrix/smoke-manifest.json +35 -0
  186. package/.pi/factories/factory-forge/batch-manifest.json +56 -0
  187. package/.pi/factories/factory-forge/factory.json +84 -0
  188. package/.pi/factories/factory-forge/pilot-manifest.json +32 -0
  189. package/.pi/factories/factory-forge/smoke-manifest.json +19 -0
  190. package/.pi/factories/opencode-pattern-canonizer/batch-manifest.json +54 -0
  191. package/.pi/factories/opencode-pattern-canonizer/factory.json +86 -0
  192. package/.pi/factories/opencode-pattern-canonizer/pilot-manifest.json +39 -0
  193. package/.pi/factories/opencode-pattern-canonizer/smoke-manifest.json +26 -0
  194. package/.pi/factories/project-dna/README.md +182 -0
  195. package/.pi/factories/project-dna/batch-manifest.json +37 -0
  196. package/.pi/factories/project-dna/example-project-dna-manifest-v2.json +80 -0
  197. package/.pi/factories/project-dna/example-project-dna-manifest.json +58 -0
  198. package/.pi/factories/project-dna/factory.json +131 -0
  199. package/.pi/factories/project-dna/golden-cases-smoke.json +62 -0
  200. package/.pi/factories/project-dna/pi-agentic-ontology.json +88 -0
  201. package/.pi/factories/project-dna/pilot-manifest.json +32 -0
  202. package/.pi/factories/project-dna/schemas/benchmark-suite.schema.json +27 -0
  203. package/.pi/factories/project-dna/schemas/code-knowledge-graph.schema.json +97 -0
  204. package/.pi/factories/project-dna/schemas/context-pack.schema.json +43 -0
  205. package/.pi/factories/project-dna/schemas/golden-case.schema.json +36 -0
  206. package/.pi/factories/project-dna/schemas/manifest-v2.schema.json +128 -0
  207. package/.pi/factories/project-dna/schemas/manifest.schema.json +77 -0
  208. package/.pi/factories/project-dna/schemas/ontology.schema.json +45 -0
  209. package/.pi/factories/project-dna/schemas/project-fingerprint.schema.json +28 -0
  210. package/.pi/factories/project-dna/schemas/query-steward-report.schema.json +52 -0
  211. package/.pi/factories/project-dna/smoke-manifest.json +27 -0
  212. package/.pi/factories/roadmap-smoke-lots/batch-manifest.json +49 -0
  213. package/.pi/factories/roadmap-smoke-lots/factory.json +89 -0
  214. package/.pi/factories/roadmap-smoke-lots/pilot-manifest.json +50 -0
  215. package/.pi/factories/roadmap-smoke-lots/smoke-manifest.json +35 -0
  216. package/.pi/git-policy.json +120 -0
  217. package/.pi/mission-control/zob_coms_transport.json +64 -0
  218. package/.pi/model-catalog.example.json +345 -0
  219. package/.pi/model-economy.example.json +196 -0
  220. package/.pi/model-routing.json +86 -0
  221. package/.pi/orchestrations/adaptive-chief-vision.json +193 -0
  222. package/.pi/orchestrations/ceo-feature-build.json +182 -0
  223. package/.pi/orchestrations/readonly-dynamic-smoke.json +75 -0
  224. package/.pi/output-contracts/agent-event.v1.json +19 -0
  225. package/.pi/output-contracts/base.v1.json +24 -0
  226. package/.pi/output-contracts/brain-lookup.v1.json +21 -0
  227. package/.pi/output-contracts/clarification.v1.json +21 -0
  228. package/.pi/output-contracts/context-pack.v1.json +20 -0
  229. package/.pi/output-contracts/context-request.v1.json +21 -0
  230. package/.pi/output-contracts/context-steward.v1.json +19 -0
  231. package/.pi/output-contracts/context-writeback-proposal.v1.json +18 -0
  232. package/.pi/output-contracts/delegation-request.v1.json +21 -0
  233. package/.pi/output-contracts/explore.v1.json +52 -0
  234. package/.pi/output-contracts/factory.v1.json +48 -0
  235. package/.pi/output-contracts/guidance-steward.v1.json +18 -0
  236. package/.pi/output-contracts/implement.v1.json +40 -0
  237. package/.pi/output-contracts/launch-authorization.v1.json +21 -0
  238. package/.pi/output-contracts/lead-plan.v1.json +22 -0
  239. package/.pi/output-contracts/mission-readiness.v1.json +20 -0
  240. package/.pi/output-contracts/oracle-merge.v1.json +44 -0
  241. package/.pi/output-contracts/oracle-request.v1.json +20 -0
  242. package/.pi/output-contracts/oracle.v1.json +44 -0
  243. package/.pi/output-contracts/orchestration-profile.v1.json +22 -0
  244. package/.pi/output-contracts/plan.v1.json +48 -0
  245. package/.pi/output-contracts/prompt-pack.v1.json +20 -0
  246. package/.pi/output-contracts/qa.v1.json +40 -0
  247. package/.pi/output-contracts/research.v1.json +36 -0
  248. package/.pi/output-contracts/spec.v1.json +22 -0
  249. package/.pi/output-contracts/synthesis.v1.json +44 -0
  250. package/.pi/output-contracts/temp-agent-card.v1.json +23 -0
  251. package/.pi/output-contracts/todo-child-result.v1.json +20 -0
  252. package/.pi/output-contracts/todo-child-result.v2.json +22 -0
  253. package/.pi/output-contracts/todo-claim-validation.v1.json +22 -0
  254. package/.pi/output-contracts/todo-split-request.v1.json +20 -0
  255. package/.pi/prompts/adaptive-workflow.md +63 -0
  256. package/.pi/prompts/autonomous-runtime.md +15 -0
  257. package/.pi/prompts/benchmark-contender.md +15 -0
  258. package/.pi/prompts/benchmark-judge.md +19 -0
  259. package/.pi/prompts/clarify-spec.md +20 -0
  260. package/.pi/prompts/compute-plan.md +36 -0
  261. package/.pi/prompts/compute-preview.md +42 -0
  262. package/.pi/prompts/contract.md +29 -0
  263. package/.pi/prompts/explore.md +13 -0
  264. package/.pi/prompts/factory-run.md +36 -0
  265. package/.pi/prompts/factory.md +20 -0
  266. package/.pi/prompts/implement.md +27 -0
  267. package/.pi/prompts/model-catalog.md +68 -0
  268. package/.pi/prompts/model-economy.md +64 -0
  269. package/.pi/prompts/oracle-merge.md +18 -0
  270. package/.pi/prompts/oracle.md +13 -0
  271. package/.pi/prompts/orchestrator.md +48 -0
  272. package/.pi/prompts/parallel-review.md +21 -0
  273. package/.pi/prompts/plan.md +21 -0
  274. package/.pi/prompts/project-dna.md +90 -0
  275. package/.pi/prompts/refactor-oracle.md +23 -0
  276. package/.pi/prompts/refactor-slice.md +24 -0
  277. package/.pi/prompts/research.md +20 -0
  278. package/.pi/prompts/spec.md +19 -0
  279. package/.pi/prompts/synthesis.md +18 -0
  280. package/.pi/rules/always.md +38 -0
  281. package/.pi/rules/docs.md +32 -0
  282. package/.pi/rules/factory.md +44 -0
  283. package/.pi/rules/oracle.md +34 -0
  284. package/.pi/rules/orchestration.md +44 -0
  285. package/.pi/rules/project.md +34 -0
  286. package/.pi/rules/prompts.md +43 -0
  287. package/.pi/rules/runtime.md +43 -0
  288. package/.pi/rules/sandbox.md +43 -0
  289. package/.pi/settings.json +28 -0
  290. package/.pi/skills/zob-agentic-access/SKILL.md +20 -0
  291. package/.pi/skills/zob-autonomous-runtime/SKILL.md +41 -0
  292. package/.pi/skills/zob-commit/SKILL.md +79 -0
  293. package/.pi/skills/zob-compaction-policy/SKILL.md +92 -0
  294. package/.pi/skills/zob-compute-profile/SKILL.md +108 -0
  295. package/.pi/skills/zob-coms-safety/SKILL.md +54 -0
  296. package/.pi/skills/zob-coms-v2-live/SKILL.md +47 -0
  297. package/.pi/skills/zob-delegation-routing/SKILL.md +82 -0
  298. package/.pi/skills/zob-factory/SKILL.md +28 -0
  299. package/.pi/skills/zob-goal-todo-tree/SKILL.md +279 -0
  300. package/.pi/skills/zob-harness/SKILL.md +68 -0
  301. package/.pi/skills/zob-mission-control-coms/SKILL.md +39 -0
  302. package/.pi/skills/zob-oracle/SKILL.md +21 -0
  303. package/.pi/skills/zob-owner-pool-drill-writer/SKILL.md +244 -0
  304. package/.pi/skills/zob-owner-pool-launcher/SKILL.md +261 -0
  305. package/.pi/skills/zob-project-dna/SKILL.md +275 -0
  306. package/.pi/skills/zob-sandbox/SKILL.md +29 -0
  307. package/.pi/skills/zob-spec/SKILL.md +25 -0
  308. package/.pi/skills/zob-split-refactor/SKILL.md +39 -0
  309. package/.pi/skills/zob-tool-router/SKILL.md +104 -0
  310. package/.pi/teams/zob-core.json +122 -0
  311. package/AGENTS.md +89 -0
  312. package/CONTRIBUTING.md +56 -0
  313. package/LICENSE +21 -0
  314. package/README.md +360 -0
  315. package/SECURITY.md +35 -0
  316. package/SOURCE_INDEX.md +46 -0
  317. package/package.json +135 -0
  318. package/scripts/README.md +57 -0
  319. package/scripts/autonomy/mission-readiness-secret-smoke.mjs +90 -0
  320. package/scripts/compute-profile/plan-workflow.mjs +85 -0
  321. package/scripts/compute-profile/preview.mjs +242 -0
  322. package/scripts/compute-profile/regression-smoke.mjs +38 -0
  323. package/scripts/compute-profile/summarize.mjs +72 -0
  324. package/scripts/compute-profile/validate-policy.mjs +50 -0
  325. package/scripts/compute-profile/validate-preview.mjs +95 -0
  326. package/scripts/compute-profile/validate-workflow.mjs +58 -0
  327. package/scripts/git-ops/commit-policy-smoke.mjs +221 -0
  328. package/scripts/goal-todo/child-goal-ref-smoke.mjs +252 -0
  329. package/scripts/harness-switch/static-smoke.mjs +43 -0
  330. package/scripts/model-catalog/validate-economy.mjs +223 -0
  331. package/scripts/model-catalog/validate.mjs +199 -0
  332. package/scripts/package-surface/validate-script-refs.mjs +190 -0
  333. package/scripts/path-policy/validate-smoke.mjs +103 -0
  334. package/scripts/project-dna/bench-smoke.mjs +217 -0
  335. package/scripts/project-dna/build-capsules.mjs +207 -0
  336. package/scripts/project-dna/build-sample-spec.mjs +140 -0
  337. package/scripts/project-dna/emit-golden-cases.mjs +75 -0
  338. package/scripts/project-dna/emit-ontology.mjs +75 -0
  339. package/scripts/project-dna/generate-sample.mjs +302 -0
  340. package/scripts/project-dna/oracle-review-smoke.mjs +157 -0
  341. package/scripts/project-dna/plan-workflow.mjs +289 -0
  342. package/scripts/project-dna/query-context.mjs +276 -0
  343. package/scripts/project-dna/query-steward.mjs +149 -0
  344. package/scripts/project-dna/scan.mjs +553 -0
  345. package/scripts/project-dna/validate-5of5.mjs +159 -0
  346. package/scripts/project-dna/validate-golden-cases.mjs +78 -0
  347. package/scripts/project-dna/validate-ontology.mjs +97 -0
  348. package/scripts/project-dna/validate-sample-project.mjs +105 -0
  349. package/scripts/project-dna/validate-scaffold.mjs +383 -0
  350. package/scripts/project-dna/validate-scan-artifacts.mjs +187 -0
  351. package/scripts/project-dna/validate-workflow.mjs +166 -0
  352. package/scripts/start-pi.sh +4 -0
  353. package/scripts/worker-pool/static-smoke.mjs +54 -0
  354. package/scripts/zpeer-local-e2e-smoke.mjs +395 -0
  355. package/scripts/zpeer-static-smoke.mjs +129 -0
  356. package/tsconfig.json +12 -0
@@ -0,0 +1,166 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { isAbsolute, relative, resolve, sep } from "node:path";
4
+
5
+ const repoRoot = process.cwd();
6
+ const profiles = new Set(["low", "medium", "high", "xhigh", "max"]);
7
+ const semanticCaptureModes = new Set(["full_capture", "architecture_only", "targeted_capture", "sample_first", "context_only"]);
8
+ const rawKeyDenylist = new Set(["body", "prompt", "output", "content", "diff", "patch"]);
9
+
10
+ function usage() {
11
+ console.error("Usage: node scripts/project-dna/validate-workflow.mjs --plan reports/project-dna-scans/project-dna-factory-smoke/agentic-plan.json [--manifest .pi/factories/project-dna/example-project-dna-manifest-v2.json]");
12
+ }
13
+
14
+ function parseArgs(argv) {
15
+ const args = {};
16
+ for (let index = 0; index < argv.length; index += 1) {
17
+ const arg = argv[index];
18
+ if (arg === "--plan" || arg === "--workflow") args.plan = argv[++index];
19
+ else if (arg === "--manifest") args.manifest = argv[++index];
20
+ else if (arg === "--help" || arg === "-h") { usage(); process.exit(0); }
21
+ else throw new Error(`unknown argument: ${arg}`);
22
+ }
23
+ if (!args.plan) throw new Error("--plan is required");
24
+ return args;
25
+ }
26
+
27
+ function repoPath(path, label) {
28
+ if (!path || isAbsolute(path)) throw new Error(`${label} must be repo-relative`);
29
+ const resolved = resolve(repoRoot, path);
30
+ const root = resolve(repoRoot);
31
+ const rel = relative(root, resolved);
32
+ if (rel === "" || rel.startsWith("..") || isAbsolute(rel)) throw new Error(`${label} must stay inside repo and not be repo root: ${path}`);
33
+ return resolved;
34
+ }
35
+
36
+ function assertNoRawKeys(value, errors, trail = []) {
37
+ if (Array.isArray(value)) {
38
+ for (let index = 0; index < value.length; index += 1) assertNoRawKeys(value[index], errors, [...trail, String(index)]);
39
+ return;
40
+ }
41
+ if (!value || typeof value !== "object") return;
42
+ for (const [key, child] of Object.entries(value)) {
43
+ if (rawKeyDenylist.has(key.toLowerCase())) errors.push(`raw key forbidden: ${[...trail, key].join(".")}`);
44
+ assertNoRawKeys(child, errors, [...trail, key]);
45
+ }
46
+ }
47
+
48
+ function mustBe(value, expected, label, errors) {
49
+ if (value !== expected) errors.push(`${label} must be ${expected}`);
50
+ }
51
+
52
+ function validatePlan(plan) {
53
+ const errors = [];
54
+ assertNoRawKeys(plan, errors);
55
+ if (plan.schema !== "zob.project-dna-agentic-plan.v1") errors.push("plan schema must be zob.project-dna-agentic-plan.v1");
56
+ if (typeof plan.runId !== "string" || !/^[A-Za-z0-9._-]+$/.test(plan.runId)) errors.push("runId must be a safe id");
57
+ if (typeof plan.sourceId !== "string" || !/^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/.test(plan.sourceId)) errors.push("sourceId must be safe kebab id");
58
+ if (!profiles.has(plan.effectiveComputeProfile)) errors.push("effectiveComputeProfile invalid");
59
+ if (!semanticCaptureModes.has(plan.requestedCaptureMode)) errors.push("requestedCaptureMode invalid");
60
+ if (!semanticCaptureModes.has(plan.effectiveCaptureMode)) errors.push("effectiveCaptureMode invalid");
61
+ if (!Array.isArray(plan.targetedDomains)) errors.push("targetedDomains must be an array");
62
+ if (!plan.repoStats || typeof plan.repoStats !== "object" || Array.isArray(plan.repoStats)) errors.push("repoStats metadata object required");
63
+ mustBe(plan.metadataOnly, true, "metadataOnly", errors);
64
+ mustBe(plan.parentOwnedDispatch, true, "parentOwnedDispatch", errors);
65
+ mustBe(plan.childDirectDispatch, false, "childDirectDispatch", errors);
66
+ mustBe(plan.liveDispatchEnabled, false, "liveDispatchEnabled", errors);
67
+ mustBe(plan.noExecution, true, "noExecution", errors);
68
+ mustBe(plan.networkAccessed, false, "networkAccessed", errors);
69
+ mustBe(plan.sourceProjectModified, false, "sourceProjectModified", errors);
70
+ mustBe(plan.sourceMutationAllowed, false, "sourceMutationAllowed", errors);
71
+ mustBe(plan.knowledgeBackendWriteEnabled, false, "knowledgeBackendWriteEnabled", errors);
72
+ mustBe(plan.durablePromotionAllowed, false, "durablePromotionAllowed", errors);
73
+ mustBe(plan.rawBodiesStored, false, "rawBodiesStored", errors);
74
+ mustBe(plan.promptBodiesStored, false, "promptBodiesStored", errors);
75
+ mustBe(plan.resultBodiesStored, false, "resultBodiesStored", errors);
76
+ mustBe(plan.bodyFreePlan, true, "bodyFreePlan", errors);
77
+ if (plan.promotionPolicy !== "proposal_only") errors.push("promotionPolicy must be proposal_only");
78
+ if (!Array.isArray(plan.artifactTargets) || plan.artifactTargets.length < 1) errors.push("artifactTargets must be non-empty");
79
+ for (const artifact of plan.artifactTargets ?? []) {
80
+ if (typeof artifact !== "string" || artifact.includes("/") || artifact.includes("\\") || artifact.includes("..")) errors.push(`artifactTargets entry must be safe basename: ${artifact}`);
81
+ }
82
+ if (!Array.isArray(plan.lanes) || plan.lanes.length < 1) errors.push("lanes must be non-empty");
83
+ const laneIds = new Set();
84
+ for (const [index, lane] of (plan.lanes ?? []).entries()) {
85
+ if (typeof lane.id !== "string" || !/^[a-z0-9_]+$/.test(lane.id)) errors.push(`lane ${index} id invalid`);
86
+ if (laneIds.has(lane.id)) errors.push(`duplicate lane id: ${lane.id}`);
87
+ laneIds.add(lane.id);
88
+ if (!["p0", "p1"].includes(lane.phase)) errors.push(`lane ${index} phase must be p0 or p1`);
89
+ if (!Number.isInteger(lane.order) || lane.order < 1) errors.push(`lane ${index} order invalid`);
90
+ if (typeof lane.agent !== "string" || !/^[a-z0-9._-]+$/.test(lane.agent)) errors.push(`lane ${index} agent invalid`);
91
+ if (!Number.isInteger(lane.maxWorkers) || lane.maxWorkers < 1 || lane.maxWorkers > 8) errors.push(`lane ${index} maxWorkers must be 1..8`);
92
+ if (!Array.isArray(lane.toolHints) || lane.toolHints.length < 1) errors.push(`lane ${index} toolHints required`);
93
+ for (const tool of lane.toolHints ?? []) {
94
+ if (!["read", "grep", "find", "ls", "bash"].includes(tool)) errors.push(`lane ${index} tool not allowed: ${tool}`);
95
+ }
96
+ mustBe(lane.parentOwnedDispatch, true, `lane ${index} parentOwnedDispatch`, errors);
97
+ mustBe(lane.childDirectDispatch, false, `lane ${index} childDirectDispatch`, errors);
98
+ mustBe(lane.liveDispatchEnabled, false, `lane ${index} liveDispatchEnabled`, errors);
99
+ mustBe(lane.sourceMutationAllowed, false, `lane ${index} sourceMutationAllowed`, errors);
100
+ mustBe(lane.backendWritesEnabled, false, `lane ${index} backendWritesEnabled`, errors);
101
+ mustBe(lane.durablePromotionAllowed, false, `lane ${index} durablePromotionAllowed`, errors);
102
+ mustBe(lane.storesRawBodies, false, `lane ${index} storesRawBodies`, errors);
103
+ }
104
+ if (!laneIds.has("manifest_preflight")) errors.push("lanes must include manifest_preflight");
105
+ if (!laneIds.has("read_only_scan")) errors.push("lanes must include read_only_scan");
106
+ if (!laneIds.has("scan_validation")) errors.push("lanes must include scan_validation");
107
+ mustBe(plan.safetyAssertions?.sourceReadOnly, true, "safetyAssertions.sourceReadOnly", errors);
108
+ mustBe(plan.safetyAssertions?.forbiddenPathsExcluded, true, "safetyAssertions.forbiddenPathsExcluded", errors);
109
+ mustBe(plan.safetyAssertions?.secretLikePathsForbidden, true, "safetyAssertions.secretLikePathsForbidden", errors);
110
+ mustBe(plan.safetyAssertions?.externalKnowledgeBackendDisabled, true, "safetyAssertions.externalKnowledgeBackendDisabled", errors);
111
+ mustBe(plan.safetyAssertions?.durablePromotionRequiresHumanApproval, true, "safetyAssertions.durablePromotionRequiresHumanApproval", errors);
112
+ mustBe(plan.safetyAssertions?.citationsRequired, true, "safetyAssertions.citationsRequired", errors);
113
+ mustBe(plan.safetyAssertions?.boundedContextOnly, true, "safetyAssertions.boundedContextOnly", errors);
114
+ return errors;
115
+ }
116
+
117
+ function validateManifest(manifest, plan) {
118
+ const errors = [];
119
+ assertNoRawKeys(manifest, errors, ["manifest"]);
120
+ if (manifest.schema !== "zob.project-dna-manifest.v2") errors.push("manifest schema must be zob.project-dna-manifest.v2");
121
+ if (manifest.run_id !== plan.runId) errors.push("manifest run_id must match plan runId");
122
+ if (manifest.source_project?.source_id !== plan.sourceId) errors.push("manifest source_project.source_id must match plan sourceId");
123
+ if (!semanticCaptureModes.has(manifest.capture_mode_policy?.semantic_mode)) errors.push("manifest semantic capture mode invalid");
124
+ if (manifest.capture_mode_policy?.semantic_mode !== plan.requestedCaptureMode) errors.push("manifest semantic_mode must match plan requestedCaptureMode");
125
+ if (manifest.capture_mode_policy?.metadata_only !== true) errors.push("manifest capture_mode_policy.metadata_only must be true");
126
+ if (manifest.capture_mode_policy?.child_direct_dispatch_allowed !== false) errors.push("manifest child direct dispatch must be false");
127
+ if (manifest.capture_mode_policy?.source_mutation_allowed !== false) errors.push("manifest source mutation must be false");
128
+ if (manifest.capture_mode_policy?.knowledge_backend_write_allowed !== false) errors.push("manifest backend writes must be false");
129
+ if (manifest.promotion?.writeback_policy !== "proposal_only") errors.push("manifest promotion must be proposal_only");
130
+ if (manifest.promotion?.durable_promotion_allowed !== false) errors.push("manifest durable promotion must be false");
131
+ return errors;
132
+ }
133
+
134
+ try {
135
+ const args = parseArgs(process.argv.slice(2));
136
+ const planPath = repoPath(args.plan, "--plan");
137
+ if (!existsSync(planPath)) throw new Error(`missing plan: ${args.plan}`);
138
+ const normalizedPlan = args.plan.split(sep).join("/");
139
+ if (!normalizedPlan.startsWith("reports/project-dna-scans/")) throw new Error("--plan must be under reports/project-dna-scans");
140
+ const plan = JSON.parse(readFileSync(planPath, "utf8"));
141
+ let errors = validatePlan(plan);
142
+ if (args.manifest) {
143
+ const manifestPath = repoPath(args.manifest, "--manifest");
144
+ if (!existsSync(manifestPath)) throw new Error(`missing manifest: ${args.manifest}`);
145
+ errors = errors.concat(validateManifest(JSON.parse(readFileSync(manifestPath, "utf8")), plan));
146
+ }
147
+ const result = {
148
+ schema: "zob.project-dna-agentic-plan-validation.v1",
149
+ valid: errors.length === 0,
150
+ errors,
151
+ planPath: normalizedPlan,
152
+ metadataOnly: true,
153
+ childDispatchAllowed: false,
154
+ networkAccessed: false,
155
+ sourceProjectModified: false,
156
+ knowledgeBackendWriteEnabled: false,
157
+ durablePromotionAllowed: false,
158
+ rawBodiesStored: false
159
+ };
160
+ console.log(JSON.stringify(result, null, 2));
161
+ if (errors.length > 0) process.exit(1);
162
+ } catch (error) {
163
+ console.error(error instanceof Error ? error.message : String(error));
164
+ usage();
165
+ process.exit(1);
166
+ }
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ cd "$(dirname "$0")/.."
4
+ exec pi "$@"
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync } from "node:fs";
3
+
4
+ const files = {
5
+ workerPool: ".pi/extensions/zob-harness/src/worker-pool.ts",
6
+ runtime: ".pi/extensions/zob-harness/src/runtime/tools-worker-pool.ts",
7
+ goalRoom: ".pi/extensions/zob-harness/src/goal-room.ts",
8
+ constants: ".pi/extensions/zob-harness/src/constants.ts",
9
+ schemas: ".pi/extensions/zob-harness/src/schemas.ts",
10
+ governedRequests: ".pi/extensions/zob-harness/src/governed-requests.ts",
11
+ governedRuntime: ".pi/extensions/zob-harness/src/runtime/tools-governed-requests.ts",
12
+ registry: ".pi/capabilities/zob-public-runtime-capabilities.json",
13
+ };
14
+
15
+ const read = (path) => readFileSync(path, "utf8");
16
+ const checks = [
17
+ [files.workerPool, "schema: \"zob.worker-pool-plan.v1\""],
18
+ [files.workerPool, "productionWritesPerformed: false"],
19
+ [files.workerPool, "autoApply: false"],
20
+ [files.workerPool, "write_paths must be within owned_paths"],
21
+ [files.workerPool, "workspaceClaimsCoverWriteIntent"],
22
+ [files.workerPool, "zob.worker-pool-safety-gates.v1"],
23
+ [files.workerPool, "owner request requested_paths must be covered"],
24
+ [files.workerPool, "kind: \"OWNER_CHANGE_REQUEST\""],
25
+ [files.workerPool, "kind: \"OWNER_CHANGE_DECISION\""],
26
+ [files.runtime, "zob_worker_pool_plan"],
27
+ [files.runtime, "zob_worker_pool_owner_request"],
28
+ [files.runtime, "zob_worker_pool_owner_decision"],
29
+ [files.goalRoom, "OWNER_CHANGE_REQUEST"],
30
+ [files.goalRoom, "OWNER_CHANGE_DECISION"],
31
+ [files.constants, "ZOB_WORKER_POOL_TOOLS"],
32
+ [files.schemas, "WorkerPoolPlanParams"],
33
+ [files.schemas, "read_across_write_overlap_justification_hash"],
34
+ [files.schemas, "each path must be within owned_paths"],
35
+ [files.registry, "zob_worker_pool_plan"],
36
+ [files.registry, "zob_worker_pool_status"],
37
+ [files.registry, "zob_worker_pool_owner_request"],
38
+ [files.registry, "zob_worker_pool_owner_decision"],
39
+ [files.registry, "Actual child dispatch remains parent-owned through delegate_task/delegate_agent"],
40
+ [files.governedRequests, "OWNER_CHANGE_REQUEST"],
41
+ [files.governedRequests, "owner-change-request.v1"],
42
+ [files.governedRequests, "owner change request change_hash must be sha256 hex"],
43
+ [files.governedRequests, "requested_path_hashes"],
44
+ [files.governedRuntime, "OWNER_CHANGE_REQUEST.v1"],
45
+ [files.schemas, "OWNER_CHANGE_REQUEST.v1"],
46
+ ];
47
+
48
+ const failures = checks.filter(([path, needle]) => !read(path).includes(needle));
49
+ if (failures.length > 0) {
50
+ console.error("worker-pool static smoke failed:");
51
+ for (const [path, needle] of failures) console.error(`- ${path} missing ${needle}`);
52
+ process.exit(1);
53
+ }
54
+ console.log("worker-pool static smoke passed");
@@ -0,0 +1,395 @@
1
+ #!/usr/bin/env node
2
+ import { mkdtempSync, rmSync, mkdirSync, readFileSync, existsSync, symlinkSync } from 'node:fs';
3
+ import { tmpdir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { spawnSync } from 'node:child_process';
6
+
7
+ const failures = [];
8
+ const forbiddenLedgerKeys = new Set(['body', 'task', 'prompt', 'output', 'content', 'message', 'text', 'rationale', 'diff', 'patch', 'transientPrompt', 'transientResponse']);
9
+ const rawPrompt = 'hash-only local zpeer smoke prompt';
10
+ const rawResponse = 'hash-only local zpeer smoke response';
11
+ const root = mkdtempSync(join(tmpdir(), 'zpeer-local-e2e-'));
12
+ const outDir = join(root, 'compiled');
13
+ const repoRoot = join(root, 'repo');
14
+ const registryRoot = join(root, 'registry');
15
+ const previousRegistryRoot = process.env.ZOB_COMS_REGISTRY_ROOT;
16
+ const previousZpeerProfileId = process.env.ZOB_ZPEER_PROFILE_ID;
17
+ const previousZpeerProfile = process.env.ZPEER_PROFILE;
18
+ const previousComsSessionId = process.env.ZOB_COMS_SESSION_ID;
19
+ const previousTmuxPane = process.env.TMUX_PANE;
20
+ const servers = [];
21
+
22
+ function fail(message) {
23
+ failures.push(message);
24
+ }
25
+
26
+ function assert(condition, message) {
27
+ if (!condition) fail(message);
28
+ }
29
+
30
+ function readJsonl(file) {
31
+ if (!existsSync(file)) return [];
32
+ return readFileSync(file, 'utf8').trim().split('\n').filter(Boolean).map((line) => JSON.parse(line));
33
+ }
34
+
35
+ function hasForbiddenKey(value) {
36
+ if (!value || typeof value !== 'object') return false;
37
+ if (Array.isArray(value)) return value.some(hasForbiddenKey);
38
+ return Object.entries(value).some(([key, child]) => forbiddenLedgerKeys.has(key) || hasForbiddenKey(child));
39
+ }
40
+
41
+ function containsRawBody(value) {
42
+ return JSON.stringify(value).includes(rawPrompt) || JSON.stringify(value).includes(rawResponse);
43
+ }
44
+
45
+ function makePeer({ alias, roomId, endpoint, endpointHash, sha256, roleId = 'zob-orchestrator', roleType = 'orchestrator', heartbeatAt }) {
46
+ const now = new Date().toISOString();
47
+ return {
48
+ schema: 'zob.live-peer-card.v1',
49
+ projectId: 'temporary-project-id-overwritten-by-ensureZpeerFields',
50
+ team: 'zob-core',
51
+ roleId,
52
+ roleType,
53
+ agent: 'zpeer-local-e2e-smoke',
54
+ sessionId: `session-${alias}`,
55
+ sessionHash: sha256(`session-${alias}`),
56
+ transport: 'local_socket',
57
+ endpoint,
58
+ endpointHash,
59
+ cwdHash: sha256(repoRoot),
60
+ pid: process.pid,
61
+ startedAt: now,
62
+ heartbeatAt: heartbeatAt ?? now,
63
+ contextUsedPct: 0,
64
+ queueDepth: 0,
65
+ status: 'online',
66
+ zpeerRoomId: roomId,
67
+ zpeerAlias: alias,
68
+ zpeerLocalOnly: true,
69
+ staleAfterMs: 60_000,
70
+ offlineAfterMs: 120_000,
71
+ bodyStored: false,
72
+ };
73
+ }
74
+
75
+ async function main() {
76
+ mkdirSync(repoRoot, { recursive: true });
77
+ mkdirSync(registryRoot, { recursive: true });
78
+ process.env.ZOB_COMS_REGISTRY_ROOT = registryRoot;
79
+
80
+ const localTsc = join(process.cwd(), 'node_modules', 'typescript', 'bin', 'tsc');
81
+ if (!existsSync(localTsc)) {
82
+ throw new Error(`local TypeScript compiler not found at ${localTsc}; run from a repo with installed local dependencies`);
83
+ }
84
+ const tsc = spawnSync(process.execPath, [localTsc, '--project', 'tsconfig.json', '--noEmit', 'false', '--outDir', outDir, '--rootDir', '.'], {
85
+ cwd: process.cwd(),
86
+ encoding: 'utf8',
87
+ stdio: ['ignore', 'pipe', 'pipe'],
88
+ });
89
+ if (tsc.status !== 0) {
90
+ throw new Error(`temporary TypeScript compile failed\nstdout:\n${tsc.stdout}\nstderr:\n${tsc.stderr}`);
91
+ }
92
+ const repoNodeModules = join(process.cwd(), 'node_modules');
93
+ if (existsSync(repoNodeModules)) symlinkSync(repoNodeModules, join(outDir, 'node_modules'), 'dir');
94
+
95
+ const compiledSrc = join(outDir, '.pi/extensions/zob-harness/src');
96
+ const zpeer = await import(`${compiledSrc}/coms-v2/zpeer.js`);
97
+ const zpeerProfile = await import(`${compiledSrc}/coms-v2/zpeer-profile.js`);
98
+ const toolsComs = await import(`${compiledSrc}/runtime/tools-coms.js`);
99
+ const localTransport = await import(`${compiledSrc}/coms-v2/local-transport.js`);
100
+ const envelope = await import(`${compiledSrc}/coms-v2/envelope.js`);
101
+ const hashing = await import(`${compiledSrc}/utils/hashing.js`);
102
+
103
+ process.env.ZOB_ZPEER_PROFILE_ID = 'profile-alpha';
104
+ delete process.env.ZPEER_PROFILE;
105
+ delete process.env.ZOB_COMS_SESSION_ID;
106
+ const alphaProfile = zpeerProfile.writeZpeerLocalProfile(repoRoot, { alias: 'persistedalpha', roomId: 'persisted-room' });
107
+ const alphaProfilePath = zpeerProfile.zpeerProfilePath(repoRoot);
108
+ const restoredAlphaProfile = zpeerProfile.readZpeerLocalProfile(repoRoot);
109
+ assert(restoredAlphaProfile?.alias === 'persistedalpha', 'zpeer profile must restore persisted alias for same explicit profile id');
110
+ assert(restoredAlphaProfile?.roomId === 'persisted-room', 'zpeer profile must restore persisted room for same explicit profile id');
111
+ assert(alphaProfile.localOnly === true && alphaProfile.networkEnabled === false && alphaProfile.bodyStored === false, 'zpeer profile must persist metadata-only safety flags');
112
+
113
+ process.env.ZOB_ZPEER_PROFILE_ID = 'profile-beta';
114
+ const betaProfile = zpeerProfile.writeZpeerLocalProfile(repoRoot, { alias: 'persistedbeta', roomId: 'other-room' });
115
+ const betaProfilePath = zpeerProfile.zpeerProfilePath(repoRoot);
116
+ assert(betaProfilePath !== alphaProfilePath, 'different zpeer profile ids must not overwrite the same profile file');
117
+ assert(zpeerProfile.readZpeerLocalProfile(repoRoot)?.alias === 'persistedbeta', 'second zpeer profile must read its own alias');
118
+ process.env.ZOB_ZPEER_PROFILE_ID = 'profile-alpha';
119
+ assert(zpeerProfile.readZpeerLocalProfile(repoRoot)?.alias === 'persistedalpha', 'first zpeer profile must remain intact after writing second profile');
120
+ const profileJson = JSON.parse(readFileSync(alphaProfilePath, 'utf8'));
121
+ assert(profileJson.schema === 'zob.zpeer-local-profile.v1', 'zpeer profile schema must be explicit');
122
+ assert(profileJson.projectId === alphaProfile.projectId && profileJson.profileId === alphaProfile.profileId, 'zpeer profile must include project/profile ids');
123
+ assert(!hasForbiddenKey(profileJson), 'zpeer profile must not contain forbidden raw body-like keys');
124
+ assert(!alphaProfilePath.includes(join(repoRoot, '.pi', 'coms')), 'zpeer profile path must not be under .pi/coms');
125
+
126
+ const reloadPeer = zpeer.ensureZpeerFields(repoRoot, makePeer({ alias: 'reloadbase', roomId: 'default', endpoint: join(root, 'reload.sock'), endpointHash: hashing.sha256(join(root, 'reload.sock')), sha256: hashing.sha256 }), restoredAlphaProfile.roomId, restoredAlphaProfile.alias);
127
+ assert(reloadPeer.zpeerAlias === 'persistedalpha', 'simulated reload must apply restored profile alias before registration');
128
+ assert(reloadPeer.zpeerRoomId === 'persisted-room', 'simulated reload must apply restored profile room before registration');
129
+ const restoredOnlyPeer = zpeer.ensureZpeerFields(repoRoot, makePeer({ alias: 'reloadbase', roomId: 'default', endpoint: join(root, 'reload-restored.sock'), endpointHash: hashing.sha256(join(root, 'reload-restored.sock')), sha256: hashing.sha256 }), undefined, undefined, [{ roomId: 'persisted-room', alias: 'persistedalpha', role: 'member', joinedAt: new Date().toISOString(), localOnly: true, networkEnabled: false, bodyStored: false }]);
130
+ const restoredOnlyMemberships = zpeer.zpeerMembershipsForPeer(restoredOnlyPeer);
131
+ assert(restoredOnlyPeer.zpeerRoomId === 'persisted-room' && restoredOnlyPeer.zpeerAlias === 'persistedalpha', 'restored memberships must set active room/alias without legacy default input');
132
+ assert(restoredOnlyMemberships.length === 1 && restoredOnlyMemberships[0].roomId === 'persisted-room', 'restored memberships must not inject an unrequested default membership');
133
+ const staleBasePeer = makePeer({ alias: 'stalealias', roomId: 'default', endpoint: join(root, 'reload-stale.sock'), endpointHash: hashing.sha256(join(root, 'reload-stale.sock')), sha256: hashing.sha256 });
134
+ staleBasePeer.zpeerActiveRoomId = 'stale-room';
135
+ const restoredMultiPeer = zpeer.ensureZpeerFields(repoRoot, staleBasePeer, undefined, undefined, [
136
+ { roomId: 'room-b', alias: 'bravo', role: 'member', joinedAt: new Date().toISOString(), localOnly: true, networkEnabled: false, bodyStored: false },
137
+ { roomId: 'room-a', alias: 'alpha', role: 'member', joinedAt: new Date().toISOString(), localOnly: true, networkEnabled: false, bodyStored: false },
138
+ ]);
139
+ const restoredMultiMemberships = zpeer.zpeerMembershipsForPeer(restoredMultiPeer);
140
+ const restoredMultiRoomIds = restoredMultiMemberships.map((membership) => membership.roomId);
141
+ assert(restoredMultiRoomIds.length === 2 && restoredMultiRoomIds.includes('room-a') && restoredMultiRoomIds.includes('room-b'), 'restored memberships with stale/default legacy room ids must keep only restored rooms');
142
+ assert(!restoredMultiRoomIds.includes('default') && !restoredMultiRoomIds.includes('stale-room'), 'stale/default legacy active room ids must not create restored membership parasites');
143
+ assert(restoredMultiPeer.zpeerRoomId === 'room-a' && restoredMultiPeer.zpeerActiveRoomId === 'room-a' && restoredMultiPeer.zpeerAlias === 'alpha', 'restored memberships must fall back to first restored room/alias when legacy active room is stale');
144
+
145
+ delete process.env.ZOB_ZPEER_PROFILE_ID;
146
+ delete process.env.ZPEER_PROFILE;
147
+ delete process.env.ZOB_COMS_SESSION_ID;
148
+ process.env.TMUX_PANE = '%zpeer-smoke-pane';
149
+ const terminalProfileIdOne = zpeerProfile.resolveZpeerProfileId(repoRoot);
150
+ zpeerProfile.writeZpeerLocalProfile(repoRoot, { alias: 'terminalalpha', roomId: 'terminal-room' });
151
+ const terminalProfileIdTwo = zpeerProfile.resolveZpeerProfileId(repoRoot);
152
+ assert(terminalProfileIdOne === terminalProfileIdTwo, 'terminal-derived zpeer profile id must be stable without explicit env ids');
153
+ assert(zpeerProfile.readZpeerLocalProfile(repoRoot)?.roomId === 'terminal-room', 'terminal-derived zpeer profile must restore room without explicit env ids');
154
+ process.env.ZOB_ZPEER_PROFILE_ID = 'profile-alpha';
155
+
156
+ const alphaEndpoint = join(root, 'alpha.sock');
157
+ const betaEndpoint = join(root, 'beta.sock');
158
+ const gammaEndpoint = join(root, 'gamma.sock');
159
+ const workerOneEndpoint = join(root, 'worker-one.sock');
160
+ const workerTwoEndpoint = join(root, 'worker-two.sock');
161
+ const pendingReplies = new Map();
162
+ const receivedPrompts = [];
163
+ const receivedResponses = [];
164
+
165
+ servers.push(await localTransport.bindZobLocalEndpoint(alphaEndpoint, async (incoming) => {
166
+ if (incoming.type === 'response') {
167
+ receivedResponses.push(incoming);
168
+ const pending = pendingReplies.get(incoming.msgId);
169
+ if (pending) pending.resolve({ status: 'completed', envelope: incoming });
170
+ return envelope.buildZobLiveAckEnvelope(incoming);
171
+ }
172
+ return envelope.buildZobLiveAckEnvelope(incoming);
173
+ }));
174
+
175
+ servers.push(await localTransport.bindZobLocalEndpoint(betaEndpoint, async (incoming) => {
176
+ receivedPrompts.push(incoming);
177
+ if (incoming.type === 'prompt' && incoming.replyEndpoint) {
178
+ setTimeout(() => {
179
+ const response = envelope.buildZobLiveEnvelope({
180
+ type: 'response',
181
+ msgId: incoming.msgId,
182
+ runId: incoming.runId,
183
+ sender: incoming.receiver,
184
+ receiver: incoming.sender,
185
+ team: incoming.team,
186
+ taskHash: incoming.taskHash,
187
+ outputHash: hashing.sha256(rawResponse),
188
+ transientResponse: rawResponse,
189
+ });
190
+ localTransport.sendZobLocalEnvelope(incoming.replyEndpoint, response, { timeoutMs: 5_000 }).catch((error) => {
191
+ const pending = pendingReplies.get(incoming.msgId);
192
+ if (pending) pending.resolve({ status: 'error', envelope: envelope.buildZobLiveErrorEnvelope(incoming, String(error), 'reply_send_failed') });
193
+ });
194
+ }, 10);
195
+ }
196
+ return envelope.buildZobLiveAckEnvelope(incoming);
197
+ }));
198
+
199
+ servers.push(await localTransport.bindZobLocalEndpoint(gammaEndpoint, async (incoming) => envelope.buildZobLiveAckEnvelope(incoming)));
200
+ servers.push(await localTransport.bindZobLocalEndpoint(workerOneEndpoint, async (incoming) => envelope.buildZobLiveAckEnvelope(incoming)));
201
+ servers.push(await localTransport.bindZobLocalEndpoint(workerTwoEndpoint, async (incoming) => envelope.buildZobLiveAckEnvelope(incoming)));
202
+
203
+ const oldHeartbeatAt = new Date(Date.now() - 180_000).toISOString();
204
+ let alpha = zpeer.ensureZpeerFields(repoRoot, makePeer({ alias: 'alpha', roomId: 'room-one', endpoint: alphaEndpoint, endpointHash: hashing.sha256(alphaEndpoint), sha256: hashing.sha256, heartbeatAt: oldHeartbeatAt }), 'room-one', 'alpha');
205
+ let beta = zpeer.ensureZpeerFields(repoRoot, makePeer({ alias: 'beta', roomId: 'room-one', endpoint: betaEndpoint, endpointHash: hashing.sha256(betaEndpoint), sha256: hashing.sha256, heartbeatAt: oldHeartbeatAt }), 'room-one', 'beta');
206
+ zpeer.ensureZpeerFields(repoRoot, makePeer({ alias: 'gamma', roomId: 'room-two', endpoint: gammaEndpoint, endpointHash: hashing.sha256(gammaEndpoint), sha256: hashing.sha256 }), 'room-two', 'gamma');
207
+ const workerOne = zpeer.ensureZpeerFields(repoRoot, makePeer({ alias: 'workerone', roomId: 'worker-room', endpoint: workerOneEndpoint, endpointHash: hashing.sha256(workerOneEndpoint), sha256: hashing.sha256, roleId: 'explore-worker', roleType: 'worker' }), 'worker-room', 'workerone');
208
+ zpeer.ensureZpeerFields(repoRoot, makePeer({ alias: 'workertwo', roomId: 'worker-room', endpoint: workerTwoEndpoint, endpointHash: hashing.sha256(workerTwoEndpoint), sha256: hashing.sha256, roleId: 'research-worker', roleType: 'worker' }), 'worker-room', 'workertwo');
209
+
210
+ assert(alpha.bodyStored === false && beta.bodyStored === false, 'registered peers must be bodyStored=false');
211
+ const staleSummary = zpeer.buildZpeerRoomSummary(repoRoot, alpha);
212
+ assert(staleSummary.roomId === 'room-one', 'summary must be scoped to room-one');
213
+ assert(staleSummary.peerCount === 2, `stale room-one summary expected 2 peers, got ${staleSummary.peerCount}`);
214
+ assert(staleSummary.online === 0, `stale room-one summary expected 0 online peers before refresh, got ${staleSummary.online}`);
215
+ assert(staleSummary.aliases.includes('alpha') && staleSummary.aliases.includes('beta') && !staleSummary.aliases.includes('gamma'), 'stale room-one aliases must include alpha/beta only');
216
+
217
+ alpha = zpeer.refreshZpeerSelf(repoRoot, alpha);
218
+ beta = zpeer.refreshZpeerSelf(repoRoot, beta);
219
+ const initialSummary = zpeer.buildZpeerRoomSummary(repoRoot, alpha);
220
+ assert(initialSummary.peerCount === 2, `room-one summary expected 2 peers, got ${initialSummary.peerCount}`);
221
+ assert(initialSummary.online === 2, `room-one summary expected 2 online peers after refresh, got ${initialSummary.online}`);
222
+ assert(initialSummary.aliases.includes('alpha') && initialSummary.aliases.includes('beta') && !initialSummary.aliases.includes('gamma'), 'room-one aliases must include alpha/beta only');
223
+
224
+ const waitForReply = (msgId) => new Promise((resolve) => {
225
+ const timer = setTimeout(() => resolve({ status: 'timeout' }), 5_000);
226
+ pendingReplies.set(msgId, {
227
+ resolve: (value) => {
228
+ clearTimeout(timer);
229
+ pendingReplies.delete(msgId);
230
+ resolve(value);
231
+ },
232
+ });
233
+ });
234
+
235
+ const joinedAlpha = zpeer.joinZpeerRoom(repoRoot, alpha, 'shared-room', 'sharedalpha', 'bridge');
236
+ assert(joinedAlpha.ok === true, `alpha multi-room join expected ok, got ${joinedAlpha.reason ?? 'not ok'}`);
237
+ alpha = joinedAlpha.peer;
238
+ const joinedBeta = zpeer.joinZpeerRoom(repoRoot, beta, 'shared-room', 'sharedbeta');
239
+ assert(joinedBeta.ok === true, `beta multi-room join expected ok, got ${joinedBeta.reason ?? 'not ok'}`);
240
+ beta = joinedBeta.peer;
241
+ assert(zpeer.zpeerMembershipsForPeer(alpha).length === 2, 'alpha must be in two zpeer rooms after join');
242
+ const sharedSummary = zpeer.buildZpeerRoomSummary(repoRoot, alpha, 'shared-room');
243
+ assert(sharedSummary.peerCount === 2 && sharedSummary.aliases.includes('sharedalpha') && sharedSummary.aliases.includes('sharedbeta'), 'shared-room summary must include alpha/beta aliases');
244
+ const stillActiveSummary = zpeer.buildZpeerRoomSummary(repoRoot, alpha);
245
+ assert(stillActiveSummary.roomId === 'room-one' && stillActiveSummary.aliases.includes('alpha') && !stillActiveSummary.aliases.includes('sharedalpha'), 'join must preserve active room-one compatibility');
246
+ const explicitRoomResult = await zpeer.sendZpeerPrompt(repoRoot, alpha, 'sharedbeta', rawPrompt, waitForReply, { roomId: 'shared-room' });
247
+ assert(explicitRoomResult.status === 'reply' && explicitRoomResult.roomId === 'shared-room', `explicit room send expected shared-room reply, got ${explicitRoomResult.status}`);
248
+ const explicitRoomEnvelope = receivedPrompts.at(-1);
249
+ assert(explicitRoomEnvelope.sender === 'sharedalpha' && explicitRoomEnvelope.receiver === 'sharedbeta' && explicitRoomEnvelope.runId === 'zpeer:shared-room', 'explicit room envelope must use room-scoped sender/receiver aliases and runId');
250
+ const implicitBlocked = await zpeer.sendZpeerPrompt(repoRoot, alpha, 'sharedbeta', rawPrompt, waitForReply);
251
+ assert(implicitBlocked.status === 'blocked' && String(implicitBlocked.reason).includes("not found in room 'room-one'"), 'implicit active-room send must not cross into shared-room');
252
+ const duplicateJoin = zpeer.joinZpeerRoom(repoRoot, beta, 'shared-room', 'sharedalpha');
253
+ assert(duplicateJoin.ok === false && String(duplicateJoin.reason).includes('already exists'), 'duplicate alias in the same room must be blocked');
254
+ const crossRoomAlias = zpeer.joinZpeerRoom(repoRoot, alpha, 'alias-room', 'beta');
255
+ assert(crossRoomAlias.ok === true, 'same alias in a different room must be allowed');
256
+ alpha = crossRoomAlias.peer;
257
+ const peerRoomSummaries = zpeer.buildZpeerPeerRoomSummaries(repoRoot, alpha);
258
+ assert(peerRoomSummaries.length === 3, `multi-room helper expected 3 alpha rooms, got ${peerRoomSummaries.length}`);
259
+ assert(peerRoomSummaries.filter((summary) => summary.active).length === 1 && peerRoomSummaries[0].active === true && peerRoomSummaries[0].roomId === 'room-one', 'multi-room helper must mark exactly one active room first');
260
+ const roomOneSummary = peerRoomSummaries.find((summary) => summary.roomId === 'room-one');
261
+ const sharedRoomSummary = peerRoomSummaries.find((summary) => summary.roomId === 'shared-room');
262
+ const aliasRoomSummary = peerRoomSummaries.find((summary) => summary.roomId === 'alias-room');
263
+ assert(roomOneSummary?.selfAlias === 'alpha' && sharedRoomSummary?.selfAlias === 'sharedalpha' && aliasRoomSummary?.selfAlias === 'beta', 'multi-room helper must expose room-scoped self aliases');
264
+ assert(roomOneSummary?.peerCount === 2 && sharedRoomSummary?.peerCount === 2 && aliasRoomSummary?.peerCount === 1, 'multi-room helper peer counts must remain scoped per room');
265
+ assert(roomOneSummary?.aliases.includes('beta') && !roomOneSummary.aliases.includes('sharedbeta'), 'room-one summary must not leak shared-room aliases');
266
+ assert(sharedRoomSummary?.aliases.includes('sharedbeta') && !sharedRoomSummary.aliases.includes('alpha'), 'shared-room summary must not leak room-one aliases');
267
+ const useShared = zpeer.useZpeerRoom(repoRoot, alpha, 'shared-room');
268
+ assert(useShared.ok === true && useShared.peer.zpeerRoomId === 'shared-room' && useShared.peer.zpeerAlias === 'sharedalpha', 'useZpeerRoom must switch active room and alias');
269
+ alpha = zpeer.useZpeerRoom(repoRoot, useShared.peer, 'room-one').peer;
270
+
271
+ const directPromptCountBefore = receivedPrompts.length;
272
+ const directResponseCountBefore = receivedResponses.length;
273
+ const result = await zpeer.sendZpeerPrompt(repoRoot, alpha, 'beta', rawPrompt, waitForReply);
274
+ assert(result.status === 'reply', `sendZpeerPrompt expected reply, got ${result.status}${result.reason ? `: ${result.reason}` : ''}`);
275
+ assert(typeof result.transientResponse === 'string' && result.transientResponse === rawResponse, 'reply result must include transientResponse from peer');
276
+ assert(result.taskHash === hashing.sha256(rawPrompt), 'reply result must include prompt taskHash');
277
+ assert(result.outputHash === hashing.sha256(rawResponse), 'reply result must include response outputHash');
278
+ assert(result.bodyStored === false, 'reply result must be bodyStored=false');
279
+ assert(receivedPrompts.length === directPromptCountBefore + 1 && receivedPrompts.at(-1).replyEndpoint === alphaEndpoint, 'beta must receive one prompt with alpha replyEndpoint');
280
+ assert(receivedResponses.length === directResponseCountBefore + 1 && receivedResponses.at(-1).msgId === result.msgId, 'alpha must receive async response on replyEndpoint');
281
+
282
+ const feedback = [];
283
+ const responseCountBeforeAsync = receivedResponses.length;
284
+ const asyncResult = await zpeer.sendZpeerPrompt(repoRoot, alpha, 'beta', rawPrompt, waitForReply, { mode: 'async', onFeedback: (item) => feedback.push(item.kind) });
285
+ assert(asyncResult.status === 'waiting', `async send expected waiting after ACK, got ${asyncResult.status}`);
286
+ assert(feedback.filter((kind) => kind === 'waiting').length === 1 && !feedback.includes('delivered'), 'async send must emit one compact delivered/waiting feedback after ACK');
287
+ await new Promise((resolve) => setTimeout(resolve, 250));
288
+ assert(receivedResponses.length > responseCountBeforeAsync, 'async late reply must still arrive on replyEndpoint after command returns waiting');
289
+
290
+ const registeredTools = new Map();
291
+ const appendEntries = [];
292
+ const feedMessages = [];
293
+ const mockPi = {
294
+ registerTool: (tool) => registeredTools.set(tool.name, tool),
295
+ appendEntry: (customType, data) => appendEntries.push({ customType, data }),
296
+ sendMessage: (message) => { feedMessages.push(message); return Promise.resolve(); },
297
+ };
298
+ const toolState = { zobLive: { peerCard: alpha, pendingReplies: { wait: waitForReply } } };
299
+ toolsComs.registerComsTools(mockPi, toolState);
300
+ const zpeerAsk = registeredTools.get('zpeer_ask');
301
+ assert(zpeerAsk, 'zpeer_ask tool must be registered by registerComsTools');
302
+ assert(zpeerAsk.parameters, 'zpeer_ask tool must expose a schema');
303
+ const toolResponseCountBefore = receivedResponses.length;
304
+ const toolResult = await zpeerAsk.execute('tool-call-zpeer-ask', { targetAlias: 'beta', message: rawPrompt, reason: 'smoke coordination reason' }, undefined, undefined, { cwd: repoRoot });
305
+ assert(toolResult?.details?.status === 'waiting', `zpeer_ask default async expected waiting, got ${toolResult?.details?.status}`);
306
+ assert(toolResult?.details?.mode === 'async', 'zpeer_ask must default to async mode');
307
+ const asyncWaitingFeed = feedMessages.filter((item) => item.customType === 'zob-zpeer-event' && item.details?.source === 'agent-request' && item.details?.status === 'waiting' && item.details?.msgId === toolResult?.details?.msgId);
308
+ assert(asyncWaitingFeed.length === 1, `zpeer_ask async must emit exactly one compact waiting feed event, got ${asyncWaitingFeed.length}`);
309
+ assert(!feedMessages.some((item) => item.customType === 'zob-zpeer-event' && item.details?.source === 'agent-request' && item.details?.kind === 'attempt'), 'zpeer_ask async must not emit a pre-ACK attempt feed event');
310
+ assert(!containsRawBody(toolResult), 'zpeer_ask async tool result must not echo the full prompt/response body');
311
+ assert(appendEntries.some((item) => item.customType === 'zob-zpeer' && item.data?.schema === 'zob.zpeer-ask.v1' && item.data?.action === 'agent_request' && item.data?.mode === 'async' && item.data?.bodyStored === false), 'zpeer_ask must append hash-only visible command metadata');
312
+ await new Promise((resolve) => setTimeout(resolve, 250));
313
+ assert(receivedResponses.length > toolResponseCountBefore, 'zpeer_ask async late reply must still arrive on replyEndpoint after tool returns waiting');
314
+ for (const [name, value] of [['appendEntries', appendEntries], ['feed metadata', feedMessages.map((item) => item.details)]]) {
315
+ assert(!containsRawBody(value), `${name} must not persist raw zpeer_ask prompt/response`);
316
+ assert(!hasForbiddenKey(value), `${name} must not contain forbidden raw body-like keys`);
317
+ }
318
+ const duplicateGuard = await zpeerAsk.execute('tool-call-zpeer-ask-dup', { targetAlias: 'beta', message: rawPrompt }, undefined, undefined, { cwd: repoRoot });
319
+ assert(duplicateGuard?.details?.status === 'blocked' && String(duplicateGuard?.details?.reason).includes('duplicate'), 'zpeer_ask must block duplicate target/message loop attempts');
320
+ const selfGuard = await zpeerAsk.execute('tool-call-zpeer-ask-self', { targetAlias: 'alpha', message: 'different smoke prompt' }, undefined, undefined, { cwd: repoRoot });
321
+ assert(selfGuard?.details?.status === 'blocked' && String(selfGuard?.details?.reason).includes('self'), 'zpeer_ask must block self-target attempts');
322
+ const explicitRoomToolResult = await zpeerAsk.execute('tool-call-zpeer-ask-room', { roomId: 'shared-room', targetAlias: 'sharedbeta', message: rawPrompt }, undefined, undefined, { cwd: repoRoot });
323
+ assert(explicitRoomToolResult?.details?.status === 'waiting' && explicitRoomToolResult?.details?.roomId === 'shared-room', 'zpeer_ask roomId must route to explicit membership room');
324
+ const explicitRoomFeed = feedMessages.find((item) => item.customType === 'zob-zpeer-event' && item.details?.source === 'agent-request' && item.details?.msgId === explicitRoomToolResult?.details?.msgId);
325
+ assert(explicitRoomFeed?.details?.roomId === 'shared-room' && explicitRoomFeed?.details?.fromAlias === 'sharedalpha' && explicitRoomFeed?.details?.toAlias === 'sharedbeta', 'zpeer_ask explicit room feed must use room-scoped fromAlias/toAlias/roomId');
326
+ assert(toolState.zobLive.lastEvent?.roomId === 'shared-room' && toolState.zobLive.lastEvent?.fromAlias === 'sharedalpha' && toolState.zobLive.lastEvent?.toAlias === 'sharedbeta', 'zpeer_ask explicit room lastEvent must use room-scoped aliases');
327
+ assert(!containsRawBody(explicitRoomToolResult), 'zpeer_ask explicit room tool result must not echo the full prompt/response body');
328
+ const promptCountBeforeWorkerTool = receivedPrompts.length;
329
+ const workerToolState = { zobLive: { peerCard: workerOne, pendingReplies: { wait: waitForReply } } };
330
+ const workerRegisteredTools = new Map();
331
+ toolsComs.registerComsTools({ ...mockPi, registerTool: (tool) => workerRegisteredTools.set(tool.name, tool) }, workerToolState);
332
+ const workerToolBlocked = await workerRegisteredTools.get('zpeer_ask').execute('tool-call-zpeer-ask-worker', { targetAlias: 'workertwo', message: 'worker topology smoke prompt' }, undefined, undefined, { cwd: repoRoot });
333
+ assert(workerToolBlocked?.details?.status === 'blocked' && String(workerToolBlocked?.details?.reason).includes('topology'), 'zpeer_ask must reuse topology guard for worker-to-worker blocks');
334
+ assert(receivedPrompts.length === promptCountBeforeWorkerTool, 'zpeer_ask topology block must happen before transport prompt delivery');
335
+
336
+ const isolated = await zpeer.sendZpeerPrompt(repoRoot, alpha, 'gamma', rawPrompt, waitForReply);
337
+ assert(isolated.status === 'blocked', `cross-room send expected blocked, got ${isolated.status}`);
338
+ assert(typeof isolated.reason === 'string' && isolated.reason.includes("not found in room 'room-one'"), 'cross-room send must report target not found in sender room');
339
+
340
+ const promptCountBeforeWorkerDirect = receivedPrompts.length;
341
+ const workerBlocked = await zpeer.sendZpeerPrompt(repoRoot, workerOne, 'workertwo', rawPrompt, waitForReply);
342
+ assert(workerBlocked.status === 'blocked', `worker-to-worker send expected blocked, got ${workerBlocked.status}`);
343
+ assert(typeof workerBlocked.reason === 'string' && workerBlocked.reason.includes('Worker-to-worker coms are blocked by topology guard'), 'worker-to-worker send must be blocked by topology guard');
344
+ assert(receivedPrompts.length === promptCountBeforeWorkerDirect, 'worker-to-worker topology block must happen before transport prompt delivery');
345
+
346
+ const messagesPath = join(repoRoot, '.pi', 'coms', 'peer-messages.jsonl');
347
+ const statusesPath = join(repoRoot, '.pi', 'coms', 'peer-status.jsonl');
348
+ const messages = readJsonl(messagesPath);
349
+ const statuses = readJsonl(statusesPath);
350
+ assert(messages.length >= 4, `expected peer-messages records for attempt/ack/terminal/isolation, got ${messages.length}`);
351
+ assert(statuses.length === messages.length, 'peer-status must mirror peer-messages count');
352
+ for (const [file, records] of [[messagesPath, messages], [statusesPath, statuses]]) {
353
+ assert(file.startsWith(repoRoot), `${file} must be inside temp repo`);
354
+ records.forEach((record, index) => {
355
+ assert(record.bodyStored === false, `${file} record ${index} must be bodyStored=false`);
356
+ assert(record.localOnly === true, `${file} record ${index} must be localOnly=true`);
357
+ assert(record.networkEnabled === false, `${file} record ${index} must keep network disabled`);
358
+ assert(!hasForbiddenKey(record), `${file} record ${index} contains forbidden raw body-like key`);
359
+ assert(!containsRawBody(record), `${file} record ${index} contains raw prompt/response body`);
360
+ });
361
+ }
362
+ assert(messages.some((record) => record.event === 'ack' && record.status === 'delivered' && record.taskHash === hashing.sha256(rawPrompt)), 'peer ledger must include delivered ack hash record');
363
+ assert(messages.some((record) => record.event === 'terminal' && record.status === 'reply' && record.outputHash === hashing.sha256(rawResponse)), 'peer ledger must include reply outputHash record');
364
+ assert(messages.some((record) => record.event === 'terminal' && record.status === 'waiting' && record.taskHash === hashing.sha256(rawPrompt)), 'peer ledger must include async waiting hash record');
365
+ assert(messages.some((record) => record.event === 'attempt' && record.status === 'blocked' && record.reasonHash), 'peer ledger must include hash-only blocked room-isolation record');
366
+ assert(messages.some((record) => record.event === 'attempt' && record.status === 'blocked' && record.targetAliasHash === hashing.sha256('workertwo') && record.reasonHash && record.taskHash === hashing.sha256(rawPrompt)), 'peer ledger must include hash-only worker-to-worker topology block record');
367
+
368
+ const realRepoComs = join(process.cwd(), '.pi', 'coms');
369
+ assert(messagesPath !== join(realRepoComs, 'peer-messages.jsonl'), 'smoke must not target real .pi/coms peer-messages ledger');
370
+ assert(statusesPath !== join(realRepoComs, 'peer-status.jsonl'), 'smoke must not target real .pi/coms peer-status ledger');
371
+
372
+ if (failures.length > 0) {
373
+ console.error(`zpeer local e2e smoke FAIL\n- ${failures.join('\n- ')}`);
374
+ process.exitCode = 1;
375
+ return;
376
+ }
377
+ console.log('zpeer local e2e smoke PASS');
378
+ }
379
+
380
+ try {
381
+ await main();
382
+ } finally {
383
+ await Promise.allSettled(servers.map((server) => server.close()));
384
+ if (previousRegistryRoot === undefined) delete process.env.ZOB_COMS_REGISTRY_ROOT;
385
+ else process.env.ZOB_COMS_REGISTRY_ROOT = previousRegistryRoot;
386
+ if (previousZpeerProfileId === undefined) delete process.env.ZOB_ZPEER_PROFILE_ID;
387
+ else process.env.ZOB_ZPEER_PROFILE_ID = previousZpeerProfileId;
388
+ if (previousZpeerProfile === undefined) delete process.env.ZPEER_PROFILE;
389
+ else process.env.ZPEER_PROFILE = previousZpeerProfile;
390
+ if (previousComsSessionId === undefined) delete process.env.ZOB_COMS_SESSION_ID;
391
+ else process.env.ZOB_COMS_SESSION_ID = previousComsSessionId;
392
+ if (previousTmuxPane === undefined) delete process.env.TMUX_PANE;
393
+ else process.env.TMUX_PANE = previousTmuxPane;
394
+ rmSync(root, { recursive: true, force: true });
395
+ }