@heretek-ai/epistemic-swarm 0.7.25 → 1.0.2

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 (350) hide show
  1. package/index.ts +1 -0
  2. package/package.json +63 -139
  3. package/rpc.ts +1 -0
  4. package/server.ts +1 -0
  5. package/src/agents.ts +69 -0
  6. package/src/continue.ts +40 -0
  7. package/src/hooks.ts +283 -0
  8. package/src/panels.tsx +220 -0
  9. package/src/policy.ts +221 -0
  10. package/src/pr.ts +37 -0
  11. package/src/research.ts +63 -0
  12. package/src/rpc-def.ts +136 -0
  13. package/src/rpc.ts +331 -0
  14. package/src/runtime.ts +112 -0
  15. package/src/server.ts +311 -0
  16. package/src/session.ts +61 -0
  17. package/src/tools.ts +61 -0
  18. package/src/tui.tsx +206 -0
  19. package/tui.tsx +1 -0
  20. package/.agents/skills/brainstorming/SKILL.md +0 -19
  21. package/.agents/skills/code_audit/SKILL.md +0 -19
  22. package/.agents/skills/darkharvest/SKILL.md +0 -19
  23. package/.agents/skills/epistemic_search/SKILL.md +0 -19
  24. package/.agents/skills/factory/SKILL.md +0 -19
  25. package/.agents/skills/grilling/SKILL.md +0 -19
  26. package/.agents/skills/oss_scout/SKILL.md +0 -19
  27. package/.agents/skills/research_cache/SKILL.md +0 -19
  28. package/.agents/skills/swarm_config/SKILL.md +0 -19
  29. package/.claude-plugin/marketplace.json +0 -61
  30. package/.claude-plugin/plugin.json +0 -76
  31. package/.omp/README.md +0 -39
  32. package/.omp/SYSTEM.md +0 -17
  33. package/.omp/commands/audit.md +0 -10
  34. package/.omp/commands/brainstorming.md +0 -12
  35. package/.omp/commands/darkharvest.md +0 -11
  36. package/.omp/commands/domainexpansion.md +0 -9
  37. package/.omp/commands/factory.md +0 -9
  38. package/.omp/commands/grill.md +0 -9
  39. package/.omp/commands/scout.md +0 -10
  40. package/.omp/commands/swarm-config.md +0 -10
  41. package/.omp/commands/swarm.md +0 -9
  42. package/.omp/hooks/post/epistemic-audit.ts +0 -16
  43. package/.omp/hooks/pre/epistemic-redirect.ts +0 -21
  44. package/.omp/prompts/brainstorming.md +0 -8
  45. package/.omp/prompts/swarm.md +0 -8
  46. package/LICENSE +0 -126
  47. package/MARKETPLACE.md +0 -130
  48. package/README.md +0 -403
  49. package/bin/cli.js +0 -297
  50. package/config/claude-settings-patch.json +0 -10
  51. package/config/docker-compose.infra.yml +0 -23
  52. package/config/domain_packs/biopharma.json +0 -23
  53. package/config/domain_packs/legal.json +0 -19
  54. package/config/domain_packs/quant.json +0 -19
  55. package/config/mcp-research-servers.json +0 -33
  56. package/config/mcp_launcher.py +0 -153
  57. package/config/opencode-snippet.json +0 -156
  58. package/config/searxng_mcp.py +0 -92
  59. package/extensions/pi/index.js +0 -357
  60. package/hooks/hooks.json +0 -45
  61. package/install.sh +0 -473
  62. package/plugins/antigravity/README.md +0 -72
  63. package/plugins/antigravity/agents/alpha-thesis.md +0 -36
  64. package/plugins/antigravity/agents/beta-antithesis.md +0 -35
  65. package/plugins/antigravity/agents/brainstormer.md +0 -27
  66. package/plugins/antigravity/agents/code-auditor.md +0 -28
  67. package/plugins/antigravity/agents/darkharvester.md +0 -30
  68. package/plugins/antigravity/agents/epistemic-auditor.md +0 -28
  69. package/plugins/antigravity/agents/manager.md +0 -26
  70. package/plugins/antigravity/agents/oss-scout.md +0 -27
  71. package/plugins/antigravity/agents/programmer.md +0 -25
  72. package/plugins/antigravity/agents/qa-a.md +0 -22
  73. package/plugins/antigravity/agents/qa-b.md +0 -22
  74. package/plugins/antigravity/antigravity_hooks.py +0 -265
  75. package/plugins/antigravity/commands/audit.md +0 -11
  76. package/plugins/antigravity/commands/brainstorm.md +0 -9
  77. package/plugins/antigravity/commands/brainstorming.md +0 -10
  78. package/plugins/antigravity/commands/darkharvest.md +0 -12
  79. package/plugins/antigravity/commands/domainexpansion.md +0 -12
  80. package/plugins/antigravity/commands/factory.md +0 -14
  81. package/plugins/antigravity/commands/grill.md +0 -10
  82. package/plugins/antigravity/commands/scout.md +0 -11
  83. package/plugins/antigravity/commands/swarm-config.md +0 -9
  84. package/plugins/antigravity/commands/swarm.md +0 -12
  85. package/plugins/antigravity/hook_bus_bridge.js +0 -352
  86. package/plugins/antigravity/hooks.json +0 -44
  87. package/plugins/antigravity/mcp_config.json +0 -33
  88. package/plugins/antigravity/mcp_launcher.py +0 -58
  89. package/plugins/antigravity/plugin.json +0 -13
  90. package/plugins/antigravity/rules/AGENTS.md +0 -127
  91. package/plugins/antigravity/rules/epistemic-integrity.md +0 -16
  92. package/plugins/antigravity/skills/brainstorming/SKILL.md +0 -19
  93. package/plugins/antigravity/skills/code_audit/SKILL.md +0 -19
  94. package/plugins/antigravity/skills/darkharvest/SKILL.md +0 -19
  95. package/plugins/antigravity/skills/epistemic_search/SKILL.md +0 -19
  96. package/plugins/antigravity/skills/factory/SKILL.md +0 -19
  97. package/plugins/antigravity/skills/grilling/SKILL.md +0 -19
  98. package/plugins/antigravity/skills/oss_scout/SKILL.md +0 -19
  99. package/plugins/antigravity/skills/research_cache/SKILL.md +0 -19
  100. package/plugins/antigravity/skills/swarm_config/SKILL.md +0 -19
  101. package/plugins/codex/AGENTS.md.snippet +0 -10
  102. package/plugins/codex/README.md +0 -37
  103. package/plugins/codex/config.toml.snippet +0 -28
  104. package/plugins/codex/openai.yaml +0 -24
  105. package/plugins/codex/skills/brainstorming/SKILL.md +0 -19
  106. package/plugins/codex/skills/code_audit/SKILL.md +0 -19
  107. package/plugins/codex/skills/darkharvest/SKILL.md +0 -19
  108. package/plugins/codex/skills/epistemic_search/SKILL.md +0 -19
  109. package/plugins/codex/skills/factory/SKILL.md +0 -19
  110. package/plugins/codex/skills/grilling/SKILL.md +0 -19
  111. package/plugins/codex/skills/oss_scout/SKILL.md +0 -19
  112. package/plugins/codex/skills/research_cache/SKILL.md +0 -19
  113. package/plugins/codex/skills/swarm_config/SKILL.md +0 -19
  114. package/plugins/darkharvest/.claude-plugin/plugin.json +0 -15
  115. package/plugins/darkharvest/agents/harvest-proponent.md +0 -22
  116. package/plugins/darkharvest/agents/harvest-redteam.md +0 -20
  117. package/plugins/darkharvest/evals/teardown-verdict/graders/license-line.md +0 -6
  118. package/plugins/darkharvest/evals/teardown-verdict/graders/skill-fired.md +0 -5
  119. package/plugins/darkharvest/evals/teardown-verdict/prompt.md +0 -6
  120. package/plugins/darkharvest/skills/darkharvest/SKILL.md +0 -70
  121. package/plugins/darkharvest/skills/darkharvest/scripts/harvest.py +0 -289
  122. package/plugins/factory/.claude-plugin/plugin.json +0 -15
  123. package/plugins/factory/agents/factory-manager.md +0 -22
  124. package/plugins/factory/agents/programmer.md +0 -16
  125. package/plugins/factory/agents/qa-adversarial.md +0 -17
  126. package/plugins/factory/agents/qa-functional.md +0 -17
  127. package/plugins/factory/evals/gate-halt/graders/gates-first.md +0 -6
  128. package/plugins/factory/evals/gate-halt/graders/skill-fired.md +0 -5
  129. package/plugins/factory/evals/gate-halt/prompt.md +0 -6
  130. package/plugins/factory/skills/factory/SKILL.md +0 -61
  131. package/plugins/factory/skills/factory/scripts/factory.py +0 -300
  132. package/plugins/gemini/GEMINI.md +0 -16
  133. package/plugins/gemini/README.md +0 -20
  134. package/plugins/gemini/commands/audit.toml +0 -6
  135. package/plugins/gemini/commands/brainstorming.toml +0 -10
  136. package/plugins/gemini/commands/darkharvest.toml +0 -8
  137. package/plugins/gemini/commands/domainexpansion.toml +0 -7
  138. package/plugins/gemini/commands/factory.toml +0 -8
  139. package/plugins/gemini/commands/grill.toml +0 -6
  140. package/plugins/gemini/commands/scout.toml +0 -7
  141. package/plugins/gemini/commands/swarm-config.toml +0 -7
  142. package/plugins/gemini/commands/swarm.toml +0 -8
  143. package/plugins/gemini/gemini-extension.json +0 -38
  144. package/plugins/gemini/hooks/hooks.json +0 -11
  145. package/plugins/gemini/skills/brainstorming/SKILL.md +0 -19
  146. package/plugins/gemini/skills/code_audit/SKILL.md +0 -19
  147. package/plugins/gemini/skills/darkharvest/SKILL.md +0 -19
  148. package/plugins/gemini/skills/epistemic_search/SKILL.md +0 -19
  149. package/plugins/gemini/skills/factory/SKILL.md +0 -19
  150. package/plugins/gemini/skills/grilling/SKILL.md +0 -19
  151. package/plugins/gemini/skills/oss_scout/SKILL.md +0 -19
  152. package/plugins/gemini/skills/research_cache/SKILL.md +0 -19
  153. package/plugins/gemini/skills/swarm_config/SKILL.md +0 -19
  154. package/plugins/opencode/analysis-bridge.js +0 -620
  155. package/plugins/opencode/config-io.js +0 -1651
  156. package/plugins/opencode/dep-health.js +0 -1039
  157. package/plugins/opencode/hook-bus.js +0 -496
  158. package/plugins/opencode/index.js +0 -2992
  159. package/plugins/opencode/tui.js +0 -1551
  160. package/plugins/opencode/weight-baseline.json +0 -16
  161. package/plugins/opencode/weight-signals.js +0 -769
  162. package/plugins/research-cache/.claude-plugin/plugin.json +0 -15
  163. package/plugins/research-cache/skills/research_cache/SKILL.md +0 -36
  164. package/plugins/research-cache/skills/research_cache/__init__.py +0 -0
  165. package/plugins/research-cache/skills/research_cache/hasher.py +0 -302
  166. package/plugins/socratic-grilling/.claude-plugin/plugin.json +0 -15
  167. package/plugins/socratic-grilling/skills/grilling/SKILL.md +0 -61
  168. package/plugins/socratic-grilling/skills/grilling/__init__.py +0 -0
  169. package/plugins/socratic-grilling/skills/grilling/socratic_tree.py +0 -363
  170. package/prompts/agent_alpha_thesis.md +0 -70
  171. package/prompts/agent_beta_antithesis.md +0 -78
  172. package/prompts/agent_brainstormer.md +0 -143
  173. package/prompts/agent_code_auditor.md +0 -88
  174. package/prompts/agent_darkharvest.md +0 -140
  175. package/prompts/agent_oss_scout.md +0 -78
  176. package/prompts/antigravity_repo_factcheck.md +0 -143
  177. package/prompts/base_epistemic_system.md +0 -50
  178. package/prompts/epistemic_auditor.md +0 -74
  179. package/prompts/orchestrator.md +0 -70
  180. package/runner/__init__.py +0 -0
  181. package/runner/__pycache__/__init__.cpython-311.pyc +0 -0
  182. package/runner/__pycache__/auctioneer.cpython-311.pyc +0 -0
  183. package/runner/__pycache__/auditor_engine.cpython-311.pyc +0 -0
  184. package/runner/__pycache__/claim_store.cpython-311.pyc +0 -0
  185. package/runner/__pycache__/claim_witness.cpython-311.pyc +0 -0
  186. package/runner/__pycache__/darkharvest_claims.cpython-311.pyc +0 -0
  187. package/runner/__pycache__/errors.cpython-311.pyc +0 -0
  188. package/runner/__pycache__/living_dossiers.cpython-311.pyc +0 -0
  189. package/runner/__pycache__/mcp_protocol.cpython-311.pyc +0 -0
  190. package/runner/__pycache__/mcp_server.cpython-311.pyc +0 -0
  191. package/runner/__pycache__/path_safety.cpython-311.pyc +0 -0
  192. package/runner/__pycache__/pcrb.cpython-311.pyc +0 -0
  193. package/runner/__pycache__/pcrb_verify.cpython-311.pyc +0 -0
  194. package/runner/__pycache__/preflight.cpython-311.pyc +0 -0
  195. package/runner/__pycache__/refinement.cpython-311.pyc +0 -0
  196. package/runner/__pycache__/research_swarm.cpython-311.pyc +0 -0
  197. package/runner/__pycache__/retrieval_log.cpython-311.pyc +0 -0
  198. package/runner/__pycache__/schema_validate.cpython-311.pyc +0 -0
  199. package/runner/__pycache__/schemas.cpython-311.pyc +0 -0
  200. package/runner/__pycache__/state_machine.cpython-311.pyc +0 -0
  201. package/runner/antigravity_hooks.py +0 -265
  202. package/runner/auctioneer.py +0 -169
  203. package/runner/auditor_engine.py +0 -711
  204. package/runner/claim_store.py +0 -417
  205. package/runner/claim_witness.py +0 -412
  206. package/runner/darkharvest_claims.py +0 -358
  207. package/runner/errors.py +0 -51
  208. package/runner/living_dossiers.py +0 -395
  209. package/runner/mcp_protocol.py +0 -188
  210. package/runner/mcp_server.py +0 -1447
  211. package/runner/path_safety.py +0 -62
  212. package/runner/pcrb.py +0 -290
  213. package/runner/pcrb_verify.py +0 -267
  214. package/runner/preflight.py +0 -1011
  215. package/runner/refinement.py +0 -580
  216. package/runner/research_swarm.py +0 -1848
  217. package/runner/retrieval_log.py +0 -90
  218. package/runner/schema_validate.py +0 -193
  219. package/runner/schemas.py +0 -630
  220. package/runner/state_machine.py +0 -463
  221. package/runner/tests/__pycache__/test_analysis_bridge.cpython-311.pyc +0 -0
  222. package/runner/tests/__pycache__/test_antigravity_plugin.cpython-311.pyc +0 -0
  223. package/runner/tests/__pycache__/test_auction_order.cpython-311.pyc +0 -0
  224. package/runner/tests/__pycache__/test_backends.cpython-311.pyc +0 -0
  225. package/runner/tests/__pycache__/test_bet1_spike.cpython-311.pyc +0 -0
  226. package/runner/tests/__pycache__/test_claim_store.cpython-311.pyc +0 -0
  227. package/runner/tests/__pycache__/test_claim_witness.cpython-311.pyc +0 -0
  228. package/runner/tests/__pycache__/test_claude_plugin.cpython-311.pyc +0 -0
  229. package/runner/tests/__pycache__/test_config_schema.cpython-311.pyc +0 -0
  230. package/runner/tests/__pycache__/test_contract_tooling.cpython-311.pyc +0 -0
  231. package/runner/tests/__pycache__/test_darkharvest_audit.cpython-311.pyc +0 -0
  232. package/runner/tests/__pycache__/test_dep_health.cpython-311.pyc +0 -0
  233. package/runner/tests/__pycache__/test_domain_packs.cpython-311.pyc +0 -0
  234. package/runner/tests/__pycache__/test_dossier_contract.cpython-311.pyc +0 -0
  235. package/runner/tests/__pycache__/test_factory.cpython-311.pyc +0 -0
  236. package/runner/tests/__pycache__/test_fleet_seam.cpython-311.pyc +0 -0
  237. package/runner/tests/__pycache__/test_frontier_concurrency.cpython-311.pyc +0 -0
  238. package/runner/tests/__pycache__/test_frontier_mirror_standalone.cpython-311.pyc +0 -0
  239. package/runner/tests/__pycache__/test_grounding.cpython-311.pyc +0 -0
  240. package/runner/tests/__pycache__/test_hardening_contract.cpython-311.pyc +0 -0
  241. package/runner/tests/__pycache__/test_hook_bus.cpython-311.pyc +0 -0
  242. package/runner/tests/__pycache__/test_living_dossiers.cpython-311.pyc +0 -0
  243. package/runner/tests/__pycache__/test_manifest_concurrency.cpython-311.pyc +0 -0
  244. package/runner/tests/__pycache__/test_mcp_server.cpython-311.pyc +0 -0
  245. package/runner/tests/__pycache__/test_nk_hardening.cpython-311.pyc +0 -0
  246. package/runner/tests/__pycache__/test_opencode_ux.cpython-311.pyc +0 -0
  247. package/runner/tests/__pycache__/test_opencode_v2_registrars.cpython-311.pyc +0 -0
  248. package/runner/tests/__pycache__/test_pcrb.cpython-311.pyc +0 -0
  249. package/runner/tests/__pycache__/test_phase08_waiver_reopens.cpython-311.pyc +0 -0
  250. package/runner/tests/__pycache__/test_refinement.cpython-311.pyc +0 -0
  251. package/runner/tests/__pycache__/test_search_anomaly.cpython-311.pyc +0 -0
  252. package/runner/tests/__pycache__/test_search_provider.cpython-311.pyc +0 -0
  253. package/runner/tests/__pycache__/test_search_provider_plugin.cpython-311.pyc +0 -0
  254. package/runner/tests/__pycache__/test_swarm.cpython-311.pyc +0 -0
  255. package/runner/tests/__pycache__/test_swarm_config.cpython-311.pyc +0 -0
  256. package/runner/tests/__pycache__/test_sweep_regressions.cpython-311.pyc +0 -0
  257. package/runner/tests/__pycache__/test_webcache.cpython-311.pyc +0 -0
  258. package/runner/tests/__pycache__/test_weight_signals.cpython-311.pyc +0 -0
  259. package/runner/tests/fixtures/auction_objective.json +0 -35
  260. package/runner/tests/fixtures/borderline_claims.json +0 -27
  261. package/runner/tests/fixtures/config_validation_cases.json +0 -276
  262. package/runner/tests/fixtures/ddg_anomaly_page.html +0 -176
  263. package/runner/tests/fixtures/ddg_results_page.html +0 -28
  264. package/runner/tests/fixtures/divergence_objectives.json +0 -31
  265. package/runner/tests/test_analysis_bridge.py +0 -450
  266. package/runner/tests/test_antigravity_plugin.py +0 -361
  267. package/runner/tests/test_auction_order.py +0 -173
  268. package/runner/tests/test_backends.py +0 -457
  269. package/runner/tests/test_bet1_spike.py +0 -95
  270. package/runner/tests/test_claim_store.py +0 -162
  271. package/runner/tests/test_claim_witness.py +0 -186
  272. package/runner/tests/test_claude_plugin.py +0 -170
  273. package/runner/tests/test_config_schema.py +0 -879
  274. package/runner/tests/test_contract_tooling.py +0 -312
  275. package/runner/tests/test_darkharvest_audit.py +0 -395
  276. package/runner/tests/test_dep_health.py +0 -595
  277. package/runner/tests/test_domain_packs.py +0 -217
  278. package/runner/tests/test_dossier_contract.py +0 -196
  279. package/runner/tests/test_factory.py +0 -258
  280. package/runner/tests/test_fleet_seam.py +0 -113
  281. package/runner/tests/test_frontier_concurrency.py +0 -288
  282. package/runner/tests/test_frontier_mirror_standalone.py +0 -156
  283. package/runner/tests/test_grounding.py +0 -183
  284. package/runner/tests/test_hardening_contract.py +0 -391
  285. package/runner/tests/test_hook_bus.py +0 -956
  286. package/runner/tests/test_living_dossiers.py +0 -204
  287. package/runner/tests/test_manifest_concurrency.py +0 -187
  288. package/runner/tests/test_mcp_server.py +0 -257
  289. package/runner/tests/test_nk_hardening.py +0 -1046
  290. package/runner/tests/test_opencode_ux.py +0 -2438
  291. package/runner/tests/test_opencode_v2_registrars.py +0 -2157
  292. package/runner/tests/test_pcrb.py +0 -240
  293. package/runner/tests/test_phase08_waiver_reopens.py +0 -539
  294. package/runner/tests/test_refinement.py +0 -255
  295. package/runner/tests/test_search_anomaly.py +0 -342
  296. package/runner/tests/test_search_provider.py +0 -1534
  297. package/runner/tests/test_search_provider_plugin.py +0 -401
  298. package/runner/tests/test_swarm.py +0 -444
  299. package/runner/tests/test_swarm_config.py +0 -520
  300. package/runner/tests/test_sweep_regressions.py +0 -453
  301. package/runner/tests/test_webcache.py +0 -271
  302. package/runner/tests/test_weight_signals.py +0 -401
  303. package/schemas/alpha_dossier.schema.json +0 -204
  304. package/schemas/audit_report.schema.json +0 -79
  305. package/schemas/beta_dossier.schema.json +0 -214
  306. package/schemas/config.schema.json +0 -311
  307. package/schemas/frontier.schema.json +0 -61
  308. package/schemas/manifest.schema.json +0 -49
  309. package/schemas/scope_manifest.schema.json +0 -47
  310. package/scripts/__pycache__/bet1_advisory_spike.cpython-311.pyc +0 -0
  311. package/scripts/__pycache__/build_adapters.cpython-311.pyc +0 -0
  312. package/scripts/__pycache__/check_docs.cpython-311.pyc +0 -0
  313. package/scripts/__pycache__/divergence_experiment.cpython-311.pyc +0 -0
  314. package/scripts/__pycache__/gen_schemas.cpython-311.pyc +0 -0
  315. package/scripts/auction_experiment.py +0 -188
  316. package/scripts/bet1_advisory_spike.py +0 -442
  317. package/scripts/build_adapters.py +0 -417
  318. package/scripts/check_docs.py +0 -66
  319. package/scripts/divergence_experiment.py +0 -214
  320. package/scripts/gen_schemas.py +0 -69
  321. package/skills/brainstorming/SKILL.md +0 -106
  322. package/skills/brainstorming/__init__.py +0 -1
  323. package/skills/brainstorming/scripts/brainstorm.py +0 -201
  324. package/skills/code_audit/SKILL.md +0 -45
  325. package/skills/darkharvest/SKILL.md +0 -70
  326. package/skills/darkharvest/scripts/harvest.py +0 -289
  327. package/skills/epistemic_search/SKILL.md +0 -67
  328. package/skills/epistemic_search/__init__.py +0 -1
  329. package/skills/epistemic_search/scripts/__pycache__/search.cpython-311.pyc +0 -0
  330. package/skills/epistemic_search/scripts/fetch.py +0 -156
  331. package/skills/epistemic_search/scripts/search.py +0 -551
  332. package/skills/epistemic_search/scripts/webcache.py +0 -339
  333. package/skills/factory/SKILL.md +0 -61
  334. package/skills/factory/scripts/factory.py +0 -300
  335. package/skills/grilling/SKILL.md +0 -61
  336. package/skills/grilling/__init__.py +0 -0
  337. package/skills/grilling/__pycache__/__init__.cpython-311.pyc +0 -0
  338. package/skills/grilling/__pycache__/socratic_tree.cpython-311.pyc +0 -0
  339. package/skills/grilling/socratic_tree.py +0 -363
  340. package/skills/oss_scout/SKILL.md +0 -49
  341. package/skills/research_cache/SKILL.md +0 -36
  342. package/skills/research_cache/__init__.py +0 -0
  343. package/skills/research_cache/__pycache__/__init__.cpython-311.pyc +0 -0
  344. package/skills/research_cache/__pycache__/hasher.cpython-311.pyc +0 -0
  345. package/skills/research_cache/hasher.py +0 -302
  346. package/skills/swarm_config/SKILL.md +0 -178
  347. package/skills/swarm_config/__init__.py +0 -35
  348. package/skills/swarm_config/__pycache__/__init__.cpython-311.pyc +0 -0
  349. package/skills/swarm_config/__pycache__/configure.cpython-311.pyc +0 -0
  350. package/skills/swarm_config/configure.py +0 -1002
@@ -1,62 +0,0 @@
1
- #!/usr/bin/env python3
2
- """Canonical path safety and containment helpers.
3
-
4
- Trust boundary
5
- --------------
6
- These helpers exist for the real S8707 pattern: an **untrusted relative segment
7
- joined under a trusted base** (a cache key, a scope id, a filename from an LLM).
8
- That is where containment must be enforced.
9
-
10
- They are deliberately *not* applied to caller-chosen CLI/tool arguments such as
11
- ``pcrb --out``, ``socratic_tree --file`` or ``--key-file``. Those are the
12
- principal's own paths: the user (or the orchestrating agent) is the trust root,
13
- and restricting them would break legitimate use like writing a brief to
14
- ``~/reports/``. Those sites canonicalise with ``os.path.realpath`` only. Do not
15
- "tighten" them by routing through :func:`safe_resolve_path` — that is a behavior
16
- change, not a hardening.
17
- """
18
-
19
- import os
20
- from pathlib import Path
21
- from typing import Union
22
-
23
-
24
- def safe_resolve_path(untrusted_path: Union[str, Path], base_dir: Union[str, Path]) -> Path:
25
- """Canonicalise a path and require that it stays inside ``base_dir``.
26
-
27
- Accepts either an absolute path or one relative to ``base_dir``. An absolute
28
- path is allowed only when it already resolves inside the base; ``..``
29
- segments and symlinks are resolved *before* the containment test, so neither
30
- can be used to escape.
31
-
32
- Raises ValueError if the resolved path escapes ``base_dir``.
33
- """
34
- base = os.path.realpath(str(base_dir))
35
- raw = str(untrusted_path)
36
- # os.path.join would silently drop `base` for an absolute second argument,
37
- # so decide the candidate explicitly.
38
- candidate = raw if os.path.isabs(raw) else os.path.join(base, raw)
39
- target = os.path.realpath(candidate)
40
- base_prefix = base if base.endswith(os.sep) else base + os.sep
41
- if target != base and not target.startswith(base_prefix):
42
- raise ValueError(f"Path traversal detected: {untrusted_path!r} escapes {base_dir!r}")
43
- return Path(target)
44
-
45
-
46
- def safe_join(base_dir: Union[str, Path], *segments: str) -> Path:
47
- """Join untrusted *relative* segments under a trusted base, with containment.
48
-
49
- This is the S8707 shape: ``base_dir / f"{user_token}.md"``. Each segment must
50
- be a bare name — no separators, no ``..`` — and the joined result must still
51
- resolve inside ``base_dir``.
52
-
53
- Raises ValueError on any segment that could escape.
54
- """
55
- for segment in segments:
56
- if not segment or segment in (".", ".."):
57
- raise ValueError(f"Unsafe path segment: {segment!r}")
58
- if os.sep in segment or (os.altsep and os.altsep in segment):
59
- raise ValueError(f"Path separator in segment: {segment!r}")
60
- if Path(segment).name != segment:
61
- raise ValueError(f"Unsafe path segment: {segment!r}")
62
- return safe_resolve_path(os.path.join(*segments), base_dir)
package/runner/pcrb.py DELETED
@@ -1,290 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Proof-Carrying Research Brief (PCRB) — export side (Stream D).
4
-
5
- A PCRB is a self-contained signed bundle: the synthesis, every claim with its
6
- quote witness, the full text of every cited source, an integrity manifest, and
7
- an optional HMAC signature. A third party can re-verify everything WITHOUT
8
- trusting the LLM that produced it and WITHOUT access to the original
9
- .research/ workspace — verification runs against the bundled sources.
10
-
11
- Bundle shape (brief.pcrb.json):
12
- {
13
- "pcrb_version": "1.0",
14
- "objective": ..., "exported_at": ...,
15
- "synthesis_markdown": ...,
16
- "claims": [ {claim fields..., "witness": {quote, source_hash, confidence, verified_at}} ],
17
- "sources": { "<hash>": {"url","title","tier","content"} },
18
- "manifest": { "synthesis_markdown": sha, "claims": {id: sha}, "sources": {hash: sha} },
19
- "signature": {"alg": "hmac-sha256" | "none", "key_id": ..., "sig": ...}
20
- }
21
-
22
- Member hashes use SourceHasher.compute_sha256 semantics so a bundled source's
23
- member hash equals its cache hash.
24
-
25
- Signing: stdlib hmac when a key is provided (env IUMBTEMS_PCRB_KEY or key
26
- file). alg="none" provides integrity (member-vs-manifest) but NOT authenticity
27
- — a coordinated rewrite of content + manifest is undetectable without a key.
28
- Real PKI (ed25519) is a gated follow-on; HMAC is sufficient for the tamper
29
- probe, not for the regulatory story.
30
- """
31
-
32
- import hashlib
33
- import hmac
34
- import json
35
- import os
36
- import sys
37
- from datetime import datetime, timezone
38
- from pathlib import Path
39
- from typing import Any, Dict, List, Optional, Tuple
40
-
41
- SCRIPT_DIR = Path(__file__).resolve().parent
42
- PROJECT_ROOT = SCRIPT_DIR.parent
43
- if str(PROJECT_ROOT) not in sys.path:
44
- sys.path.insert(0, str(PROJECT_ROOT))
45
-
46
- from runner.claim_witness import claims_from_dossier, load_dossier # noqa: E402
47
-
48
- PCRB_VERSION = "1.0"
49
- ENV_KEY = "IUMBTEMS_PCRB_KEY"
50
-
51
-
52
- def canonical_json(obj: Any) -> str:
53
- """Deterministic serialization for hashing/signing."""
54
- return json.dumps(obj, sort_keys=True, separators=(",", ":"))
55
-
56
-
57
- def sha256_text(text: str) -> str:
58
- """SourceHasher.compute_sha256 semantics (strip + utf-8)."""
59
- return hashlib.sha256(text.strip().encode("utf-8")).hexdigest()
60
-
61
-
62
- def member_hash(obj: Any) -> str:
63
- """Hash a structured member via canonical JSON."""
64
- return sha256_text(canonical_json(obj))
65
-
66
-
67
- def _resolve_key(key: Optional[str], key_path: Optional[Path]) -> Optional[bytes]:
68
- if key:
69
- return key.encode("utf-8")
70
- env = os.environ.get(ENV_KEY)
71
- if env:
72
- return env.encode("utf-8")
73
- if key_path:
74
- # An explicitly supplied key file that cannot be read must fail loudly.
75
- # Returning None here would sign the brief with `alg: "none"` and report
76
- # success — a typo'd path silently produces an unauthenticated bundle.
77
- canonical_key_path = Path(os.path.realpath(str(key_path)))
78
- if not canonical_key_path.is_file():
79
- raise ValueError(
80
- f"key file is not a readable file: {key_path!r} (resolved {canonical_key_path})"
81
- )
82
- return canonical_key_path.read_bytes().strip()
83
- return None
84
-
85
-
86
- def sign_manifest(
87
- manifest: Dict[str, Any], key: Optional[bytes], key_id: str = "local"
88
- ) -> Dict[str, Any]:
89
- if not key:
90
- return {"alg": "none", "key_id": None, "sig": None}
91
- sig = hmac.new(
92
- key, canonical_json(manifest).encode("utf-8"), hashlib.sha256
93
- ).hexdigest()
94
- return {"alg": "hmac-sha256", "key_id": key_id, "sig": sig}
95
-
96
-
97
- def _resolve_synthesis(base_dir: Path) -> str:
98
- final_md = base_dir / "final_synthesis.md"
99
- if final_md.exists():
100
- return final_md.read_text(encoding="utf-8")
101
- parts = sorted((base_dir / "scratchpads").glob("*/scope_synthesis.md"))
102
- return "\n\n---\n\n".join(p.read_text(encoding="utf-8") for p in parts)
103
-
104
-
105
- def _witness_for(c, hasher: Any, needed_hashes: set) -> Dict[str, Any]:
106
- """Build one claim's witness record and track hashes worth bundling.
107
-
108
- R8 (01-nk-hardening tiebreak-2 F1): source_hash/verbatim_quote are
109
- untrusted-adjacent (hand-built witnesses may carry int/dict/list) —
110
- isinstance-guard both before hasher/set. Non-string sides degrade to
111
- reject (confidence None, no bundling), never TypeError via
112
- `re.match` (hasher) or `set.add` (unhashable dict).
113
- """
114
- _sh = getattr(c, "source_hash", None)
115
- _q = getattr(c, "verbatim_quote", None)
116
- witness: Dict[str, Any] = {
117
- "quote": _q,
118
- "source_hash": _sh,
119
- "confidence": None,
120
- "verified_at": None,
121
- }
122
- if not isinstance(_sh, str) or not _sh:
123
- return witness
124
- if not isinstance(_q, str) or not _q:
125
- try:
126
- needed_hashes.add(_sh)
127
- except TypeError:
128
- pass
129
- return witness
130
- try:
131
- passed, conf, _msg = hasher.verify_quote(_sh, _q)
132
- except (TypeError, AttributeError):
133
- return witness
134
- try:
135
- witness["confidence"] = float(conf)
136
- except (TypeError, ValueError):
137
- witness["confidence"] = None
138
- witness["verified_at"] = datetime.now(timezone.utc).isoformat()
139
- if passed:
140
- try:
141
- needed_hashes.add(_sh)
142
- except TypeError:
143
- pass
144
- # Legacy: a present-but-unverified quote bundles nothing (verifier
145
- # re-checks independently); a missing quote bundles the hash for audit.
146
- return witness
147
-
148
-
149
- def _iter_dossier_claims(scope_dirs: List[Path]):
150
- """Yield claim witnesses from every alpha/beta dossier under scopes."""
151
- for scope_dir in scope_dirs:
152
- for dossier_name in ("alpha_dossier.json", "beta_dossier.json"):
153
- dpath = scope_dir / dossier_name
154
- if not dpath.exists():
155
- continue
156
- for c in claims_from_dossier(load_dossier(dpath)):
157
- yield c
158
-
159
-
160
- def _collect_claims(
161
- scope_dirs: List[Path], hasher: Any
162
- ) -> Tuple[List[Dict[str, Any]], set]:
163
- claim_records: List[Dict[str, Any]] = []
164
- needed_hashes: set = set()
165
- for c in _iter_dossier_claims(scope_dirs):
166
- rec = c.to_dict()
167
- rec["witness"] = _witness_for(c, hasher, needed_hashes)
168
- claim_records.append(rec)
169
- return claim_records, needed_hashes
170
-
171
-
172
- def _resolve_objective(base_dir: Path, objective: Optional[str]) -> Optional[str]:
173
- if objective:
174
- return objective
175
- from runner.state_machine import find_any_manifest
176
-
177
- manifest_p = find_any_manifest(base_dir)
178
- if manifest_p is not None and manifest_p.exists():
179
- try:
180
- return json.loads(manifest_p.read_text(encoding="utf-8")).get("objective")
181
- except (OSError, json.JSONDecodeError):
182
- return None
183
- return None
184
-
185
-
186
- def export_brief(
187
- base_dir: Path,
188
- out_path: Optional[Path] = None,
189
- scope_ids: Optional[List[str]] = None,
190
- objective: Optional[str] = None,
191
- key: Optional[str] = None,
192
- key_path: Optional[Path] = None,
193
- key_id: str = "local",
194
- ) -> Path:
195
- """Assemble a PCRB from a .research workspace. Returns the bundle path."""
196
- from skills.research_cache.hasher import SourceHasher
197
-
198
- base_dir = Path(os.path.realpath(str(base_dir)))
199
- hasher = SourceHasher(base_dir)
200
- synthesis = _resolve_synthesis(base_dir)
201
-
202
- scratch = base_dir / "scratchpads"
203
- if scope_ids:
204
- scope_dirs = [scratch / s for s in scope_ids]
205
- elif scratch.exists():
206
- scope_dirs = sorted(p for p in scratch.iterdir() if p.is_dir())
207
- else:
208
- scope_dirs = []
209
-
210
- claim_records, needed_hashes = _collect_claims(scope_dirs, hasher)
211
-
212
- # --- bundled sources (self-contained: full text in the bundle) ---
213
- sources: Dict[str, Dict[str, Any]] = {}
214
- for src_hash in sorted(needed_hashes):
215
- content = hasher.get_source_content(src_hash)
216
- meta = hasher.get_source_metadata(src_hash) or {}
217
- if content is None:
218
- continue
219
- sources[src_hash] = {
220
- "url": meta.get("url"),
221
- "title": meta.get("title"),
222
- "tier": meta.get("tier"),
223
- "content": content,
224
- }
225
-
226
- # --- manifest (integrity) ---
227
- manifest: Dict[str, Any] = {
228
- "synthesis_markdown": sha256_text(synthesis),
229
- "claims": {rec["claim_id"]: member_hash(rec) for rec in claim_records},
230
- "sources": {h: sha256_text(s["content"]) for h, s in sources.items()},
231
- }
232
-
233
- resolved_key = _resolve_key(key, key_path)
234
- signature = sign_manifest(manifest, resolved_key, key_id=key_id)
235
-
236
- bundle = {
237
- "pcrb_version": PCRB_VERSION,
238
- "objective": _resolve_objective(base_dir, objective),
239
- "exported_at": datetime.now(timezone.utc).isoformat(),
240
- "synthesis_markdown": synthesis,
241
- "claims": claim_records,
242
- "sources": sources,
243
- "manifest": manifest,
244
- "signature": signature,
245
- }
246
-
247
- out = (
248
- Path(os.path.realpath(str(out_path)))
249
- if out_path
250
- else (base_dir / "brief.pcrb.json")
251
- )
252
- out.parent.mkdir(parents=True, exist_ok=True)
253
- with open(out, "w", encoding="utf-8") as f:
254
- json.dump(bundle, f, indent=2)
255
- return out
256
-
257
-
258
- def main(argv: Optional[List[str]] = None) -> int:
259
- import argparse
260
-
261
- parser = argparse.ArgumentParser(
262
- description="Export a proof-carrying research brief"
263
- )
264
- parser.add_argument("--dir", default=".research")
265
- parser.add_argument("--out", default=None)
266
- parser.add_argument("--scope", action="append", dest="scope_ids")
267
- parser.add_argument("--objective", default=None)
268
- parser.add_argument("--key-file", default=None)
269
- parser.add_argument("--key-id", default="local")
270
- args = parser.parse_args(argv)
271
-
272
- canonical_dir = Path(os.path.realpath(str(args.dir)))
273
- canonical_out = Path(os.path.realpath(str(args.out))) if args.out else None
274
- canonical_key = (
275
- Path(os.path.realpath(str(args.key_file))) if args.key_file else None
276
- )
277
- path = export_brief(
278
- canonical_dir,
279
- out_path=canonical_out,
280
- scope_ids=args.scope_ids,
281
- objective=args.objective,
282
- key_path=canonical_key,
283
- key_id=args.key_id,
284
- )
285
- print(json.dumps({"status": "exported", "path": str(path)}))
286
- return 0
287
-
288
-
289
- if __name__ == "__main__":
290
- sys.exit(main())
@@ -1,267 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Proof-Carrying Research Brief (PCRB) — standalone verifier (Stream D).
4
-
5
- Verifies a brief.pcrb.json WITHOUT trusting the LLM that produced it and
6
- WITHOUT the original .research/ workspace: every quote is re-checked against
7
- the sources BUNDLED INSIDE the brief.
8
-
9
- Checks (all must pass for exit 0):
10
- 1. source identity — sha256(source content) == its hash key in the bundle
11
- 2. manifest integrity — every member hash recomputes to the manifest value
12
- 3. signature — HMAC over the manifest (when alg != "none")
13
- 4. quotes — every claim's verbatim_quote re-verified against the bundled
14
- source text, using the SAME verification stack as the auditor
15
- (SourceHasher.verify_quote via a throwaway cache directory)
16
-
17
- Usage:
18
- python3 runner/pcrb_verify.py brief.pcrb.json [--json] [--key-file ...]
19
-
20
- Exit codes: 0 = all checks passed, 1 = tampering/failure detected,
21
- 2 = unreadable bundle.
22
- """
23
-
24
- import argparse
25
- import hashlib
26
- import hmac
27
- import json
28
- import os
29
- import sys
30
- import tempfile
31
- import time
32
- from pathlib import Path
33
- from typing import Any, Dict, List, Optional, Tuple
34
-
35
- SCRIPT_DIR = Path(__file__).resolve().parent
36
- PROJECT_ROOT = SCRIPT_DIR.parent
37
- if str(PROJECT_ROOT) not in sys.path:
38
- sys.path.insert(0, str(PROJECT_ROOT))
39
-
40
- # Reuse the export-side canonicalization so hashes always agree.
41
- from runner.pcrb import ( # noqa: E402
42
- ENV_KEY,
43
- canonical_json,
44
- member_hash,
45
- sha256_text,
46
- )
47
-
48
-
49
- def _check_source_identity(sources: Dict[str, Any]) -> List[Dict[str, str]]:
50
- failures = []
51
- for src_hash, meta in sources.items():
52
- content = meta.get("content") or ""
53
- actual = sha256_text(content)
54
- if actual != src_hash:
55
- failures.append({
56
- "check": "source_identity",
57
- "member": src_hash,
58
- "message": f"bundled source content hashes to {actual}, not its key {src_hash}",
59
- })
60
- return failures
61
-
62
-
63
- def _check_manifest_integrity(
64
- bundle: Dict[str, Any],
65
- manifest: Dict[str, Any],
66
- claims: List[Dict[str, Any]],
67
- sources: Dict[str, Any],
68
- ) -> List[Dict[str, str]]:
69
- failures = []
70
- synth_hash = sha256_text(bundle.get("synthesis_markdown") or "")
71
- if manifest.get("synthesis_markdown") != synth_hash:
72
- failures.append({
73
- "check": "manifest_integrity",
74
- "member": "synthesis_markdown",
75
- "message": "synthesis_markdown does not match its manifest hash",
76
- })
77
-
78
- manifest_claims = manifest.get("claims") or {}
79
- for rec in claims:
80
- cid = rec.get("claim_id", "UNKNOWN")
81
- expected = manifest_claims.get(cid)
82
- actual = member_hash(rec)
83
- if expected is None:
84
- failures.append({"check": "manifest_integrity", "member": cid, "message": "claim missing from manifest"})
85
- elif expected != actual:
86
- failures.append({
87
- "check": "manifest_integrity",
88
- "member": cid,
89
- "message": f"claim hash mismatch (manifest {expected[:16]}… vs actual {actual[:16]}…)",
90
- })
91
-
92
- manifest_sources = manifest.get("sources") or {}
93
- for src_hash, meta in sources.items():
94
- expected = manifest_sources.get(src_hash)
95
- actual = sha256_text(meta.get("content") or "")
96
- if expected is None:
97
- failures.append({"check": "manifest_integrity", "member": src_hash, "message": "source missing from manifest"})
98
- elif expected != actual:
99
- failures.append({
100
- "check": "manifest_integrity",
101
- "member": src_hash,
102
- "message": "source content does not match its manifest hash",
103
- })
104
- return failures
105
-
106
-
107
- def _check_signature(
108
- signature: Dict[str, Any],
109
- manifest: Dict[str, Any],
110
- key: Optional[bytes],
111
- ) -> Tuple[str, List[Dict[str, str]]]:
112
- alg = signature.get("alg") or "none"
113
- failures = []
114
- if alg != "hmac-sha256":
115
- return "unsigned", failures
116
- if not key:
117
- failures.append({
118
- "check": "signature",
119
- "member": "manifest",
120
- "message": "brief is HMAC-signed but no key was provided (env IUMBTEMS_PCRB_KEY or --key-file)",
121
- })
122
- return "unverifiable", failures
123
-
124
- expected_sig = hmac.new(key, canonical_json(manifest).encode("utf-8"), hashlib.sha256).hexdigest()
125
- provided = signature.get("sig") or ""
126
- if not hmac.compare_digest(expected_sig, provided):
127
- failures.append({
128
- "check": "signature",
129
- "member": "manifest",
130
- "message": "HMAC signature does not match manifest (manifest was rewritten or key differs)",
131
- })
132
- return "invalid", failures
133
- return "valid", failures
134
-
135
-
136
- def _check_quotes(
137
- claims: List[Dict[str, Any]],
138
- sources: Dict[str, Any],
139
- ) -> Tuple[int, int, List[Dict[str, str]]]:
140
- quotes_verified = 0
141
- quotes_failed = 0
142
- failures = []
143
- with tempfile.TemporaryDirectory(prefix="pcrb_verify_") as tmp:
144
- from skills.research_cache.hasher import SourceHasher
145
-
146
- hasher = SourceHasher(Path(tmp))
147
- for src_hash, meta in sources.items():
148
- content = meta.get("content") or ""
149
- md_path = Path(tmp) / "sources" / f"{src_hash}.md"
150
- md_path.parent.mkdir(parents=True, exist_ok=True)
151
- md_path.write_text(content, encoding="utf-8")
152
-
153
- for rec in claims:
154
- cid = rec.get("claim_id", "UNKNOWN")
155
- witness = rec.get("witness") or {}
156
- src_hash = witness.get("source_hash") or rec.get("source_hash")
157
- quote = witness.get("quote") or rec.get("verbatim_quote")
158
- if not src_hash or not quote:
159
- continue
160
- if src_hash not in sources:
161
- quotes_failed += 1
162
- failures.append({
163
- "check": "quote",
164
- "member": cid,
165
- "message": f"claim cites source {src_hash} which is not bundled",
166
- })
167
- continue
168
- passed, conf, msg = hasher.verify_quote(src_hash, quote)
169
- if passed:
170
- quotes_verified += 1
171
- else:
172
- quotes_failed += 1
173
- failures.append({
174
- "check": "quote",
175
- "member": cid,
176
- "message": f"quote not found in bundled source (conf={conf}): {msg}",
177
- })
178
- return quotes_verified, quotes_failed, failures
179
-
180
-
181
- def verify_brief(
182
- bundle: Dict[str, Any],
183
- key: Optional[bytes] = None,
184
- ) -> Dict[str, Any]:
185
- """Run all checks. Returns a structured report dict."""
186
- started = time.perf_counter()
187
- manifest = bundle.get("manifest") or {}
188
- signature = bundle.get("signature") or {}
189
- claims = bundle.get("claims") or []
190
- sources = bundle.get("sources") or {}
191
-
192
- failures: List[Dict[str, str]] = []
193
- failures.extend(_check_source_identity(sources))
194
- failures.extend(_check_manifest_integrity(bundle, manifest, claims, sources))
195
- sig_status, sig_failures = _check_signature(signature, manifest, key)
196
- failures.extend(sig_failures)
197
- quotes_verified, quotes_failed, quote_failures = _check_quotes(claims, sources)
198
- failures.extend(quote_failures)
199
-
200
- elapsed = time.perf_counter() - started
201
- return {
202
- "ok": not failures,
203
- "failures": failures,
204
- "checks": {
205
- "source_identity": "pass" if not any(f["check"] == "source_identity" for f in failures) else "fail",
206
- "manifest_integrity": "pass" if not any(f["check"] == "manifest_integrity" for f in failures) else "fail",
207
- "signature": sig_status,
208
- "quotes": "pass" if quotes_failed == 0 else "fail",
209
- },
210
- "stats": {
211
- "claims": len(claims),
212
- "sources": len(sources),
213
- "quotes_verified": quotes_verified,
214
- "quotes_failed": quotes_failed,
215
- "elapsed_s": round(elapsed, 4),
216
- },
217
- }
218
-
219
-
220
- def main(argv: Optional[List[str]] = None) -> int:
221
- parser = argparse.ArgumentParser(description="Verify a proof-carrying research brief")
222
- parser.add_argument("brief", help="Path to brief.pcrb.json")
223
- parser.add_argument("--json", action="store_true", help="Print JSON report")
224
- parser.add_argument("--key-file", default=None)
225
- args = parser.parse_args(argv)
226
-
227
- path = Path(os.path.realpath(str(args.brief)))
228
- if not path.is_file():
229
- print(f"error: no such brief: {path}", file=sys.stderr)
230
- return 2
231
- try:
232
- bundle = json.loads(path.read_text(encoding="utf-8"))
233
- except (OSError, json.JSONDecodeError) as exc:
234
- print(f"error: unreadable brief: {exc}", file=sys.stderr)
235
- return 2
236
-
237
- key = None
238
- if args.key_file:
239
- key_file_path = Path(os.path.realpath(str(args.key_file)))
240
- # Fail loudly rather than fall through to an unsigned verification:
241
- # a typo'd --key-file would otherwise silently verify with no key.
242
- if not key_file_path.is_file():
243
- print(f"error: key file is not a readable file: {args.key_file}", file=sys.stderr)
244
- return 2
245
- key = key_file_path.read_bytes().strip()
246
- elif os.environ.get(ENV_KEY):
247
- key = os.environ[ENV_KEY].encode("utf-8")
248
-
249
- report = verify_brief(bundle, key=key)
250
-
251
- if args.json:
252
- print(json.dumps(report, indent=2))
253
- else:
254
- status = "PASS" if report["ok"] else "FAIL"
255
- print(f"[{status}] {path}")
256
- for name, st in report["checks"].items():
257
- print(f" {name}: {st}")
258
- for f in report["failures"]:
259
- print(f" ✗ [{f['check']}] {f['member']}: {f['message']}")
260
- s = report["stats"]
261
- print(f" quotes verified: {s['quotes_verified']}/{s['claims']} in {s['elapsed_s']}s")
262
-
263
- return 0 if report["ok"] else 1
264
-
265
-
266
- if __name__ == "__main__":
267
- sys.exit(main())