create-egregore 0.21.6 → 0.22.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 (719) hide show
  1. package/assets/egregore-launcher.js +58 -6
  2. package/assets/egregore-launcher.sh +15 -2
  3. package/lib/claude-runtime.js +2 -0
  4. package/lib/codex-runtime.js +2 -0
  5. package/lib/setup.js +20 -3
  6. package/package.json +2 -2
  7. package/runtime/claude/.claude/context/commit-format.md +7 -4
  8. package/runtime/claude/.claude/context/handoff-claiming.md +32 -0
  9. package/runtime/claude/.claude/context/notification-consent.md +12 -9
  10. package/runtime/claude/.claude/hooks/boundary-check.sh +14 -331
  11. package/runtime/claude/.claude/hooks/memory-read-guard.sh +9 -1
  12. package/runtime/claude/.claude/rules/voice-bedrock.md +6 -2
  13. package/runtime/claude/.claude/settings.json +1 -1
  14. package/runtime/claude/.claude/skills/commit/SKILL.md +5 -3
  15. package/runtime/claude/.claude/skills/dark-mode/SKILL.md +48 -369
  16. package/runtime/claude/.claude/skills/records/SKILL.md +158 -0
  17. package/runtime/claude/.claude/skills/reflect/SKILL.md +50 -21
  18. package/runtime/claude/.claude/skills/review-pr/SKILL.md +4 -2
  19. package/runtime/claude/.claude/skills/save/SKILL.md +9 -3
  20. package/runtime/claude/.claude/skills/summon/SKILL.md +138 -207
  21. package/runtime/claude/.claude/skills/tui-design/SKILL.md +1 -1
  22. package/runtime/claude/.claude/skills/update/SKILL.md +1 -1
  23. package/runtime/claude/.claude/skills/view/SKILL.md +52 -16
  24. package/runtime/claude/.claude/skills/view/compose-scaffold.html +19 -13
  25. package/runtime/claude/CLAUDE.md +59 -95
  26. package/runtime/claude/bin/agent.sh +231 -7
  27. package/runtime/claude/bin/api-call.sh +44 -23
  28. package/runtime/claude/bin/artifact-register.sh +7 -1
  29. package/runtime/claude/bin/artifact-writeback.sh +1 -1
  30. package/runtime/claude/bin/artifacts.sh +66 -265
  31. package/runtime/claude/bin/attendant.sh +4 -1
  32. package/runtime/claude/bin/boundary-guard.cjs +1848 -0
  33. package/runtime/claude/bin/boundary-hook.sh +41 -0
  34. package/runtime/claude/bin/boundary.sh +15 -155
  35. package/runtime/claude/bin/capture-run.sh +32 -2
  36. package/runtime/claude/bin/codex-render-spec.mjs +24 -46
  37. package/runtime/claude/bin/codex-session-start.sh +19 -0
  38. package/runtime/claude/bin/codex-skill-render.mjs +18 -0
  39. package/runtime/claude/bin/config-get.sh +10 -1
  40. package/runtime/claude/bin/connect-refresh.sh +16 -0
  41. package/runtime/claude/bin/corpus_map.py +5 -0
  42. package/runtime/claude/bin/data/capabilities/claude.json +1480 -0
  43. package/runtime/claude/bin/genui.sh +19 -0
  44. package/runtime/claude/bin/graph-batch.sh +1 -1
  45. package/runtime/claude/bin/graph-op.sh +14 -0
  46. package/runtime/claude/bin/graph.sh +1 -1
  47. package/runtime/claude/bin/handoff-pr-backfill.sh +1 -1
  48. package/runtime/claude/bin/handoff-run.sh +11 -6
  49. package/runtime/claude/bin/ingest-item.sh +1 -1
  50. package/runtime/claude/bin/ingest-surface.html +22 -4
  51. package/runtime/claude/bin/issue.sh +1 -1
  52. package/runtime/claude/bin/knowledge.sh +1 -1
  53. package/runtime/claude/bin/lib/card-delivery.sh +329 -0
  54. package/runtime/claude/bin/lib/config.sh +148 -3
  55. package/runtime/claude/bin/lib/context.sh +38 -67
  56. package/runtime/claude/bin/lib/genui-entry.py +36 -0
  57. package/runtime/claude/bin/lib/git-message.sh +25 -6
  58. package/runtime/claude/bin/lib/git-sync.sh +12 -4
  59. package/runtime/claude/bin/lib/greeting.sh +255 -28
  60. package/runtime/claude/bin/lib/handoff_meta.py +294 -0
  61. package/runtime/claude/bin/lib/health-footer.sh +83 -2
  62. package/runtime/claude/bin/lib/identity.sh +31 -10
  63. package/runtime/claude/bin/lib/instance-registry.sh +71 -0
  64. package/runtime/claude/bin/lib/local_ci_receipt.py +249 -0
  65. package/runtime/claude/bin/lib/metrics.sh +121 -8
  66. package/runtime/claude/bin/lib/notices.sh +36 -0
  67. package/runtime/claude/bin/lib/record_outcomes.py +729 -0
  68. package/runtime/claude/bin/lib/reflexes.py +267 -0
  69. package/runtime/claude/bin/lib/retrieval_scorecard.py +624 -0
  70. package/runtime/claude/bin/lib/session-record-prompt.txt +18 -0
  71. package/runtime/claude/bin/lib/session_record.py +3747 -0
  72. package/runtime/claude/bin/lib/session_trail.py +1983 -0
  73. package/runtime/claude/bin/lib/settings-drift.sh +8 -5
  74. package/runtime/claude/bin/lib/startup-restart.sh +41 -0
  75. package/runtime/claude/bin/lib/worktree-links.sh +92 -0
  76. package/runtime/claude/bin/memory-push.sh +6 -0
  77. package/runtime/claude/bin/merge-gate.sh +498 -0
  78. package/runtime/claude/bin/notify.sh +5 -2
  79. package/runtime/claude/bin/page-access.sh +103 -0
  80. package/runtime/claude/bin/person.sh +13 -2
  81. package/runtime/claude/bin/pi-render-spec.mjs +0 -1
  82. package/runtime/claude/bin/pi-spirit-discovery.mjs +34 -0
  83. package/runtime/claude/bin/post-update.sh +181 -0
  84. package/runtime/claude/bin/prime-render-spec.mjs +0 -1
  85. package/runtime/claude/bin/publish-artifact.sh +89 -42
  86. package/runtime/claude/bin/qa-inventory.sh +2 -2
  87. package/runtime/claude/bin/quest.sh +1 -1
  88. package/runtime/claude/bin/records.sh +11 -0
  89. package/runtime/claude/bin/reflexes/05-vitals.sh +9 -0
  90. package/runtime/claude/bin/reflexes/10-open-items.sh +12 -0
  91. package/runtime/claude/bin/reflexes/15-retrieval.sh +8 -0
  92. package/runtime/claude/bin/reflexes/20-nightly.sh +100 -0
  93. package/runtime/claude/bin/reflexes/20-sandbox.sh +85 -0
  94. package/runtime/claude/bin/reflexes/30-cost.py +116 -0
  95. package/runtime/claude/bin/reflexes/40-shells.py +51 -0
  96. package/runtime/claude/bin/reflexes/README.md +61 -0
  97. package/runtime/claude/bin/reflexes.sh +7 -0
  98. package/runtime/claude/bin/runtime-identity.sh +140 -42
  99. package/runtime/claude/bin/screenshot.sh +77 -0
  100. package/runtime/claude/bin/scroll.sh +1 -1
  101. package/runtime/claude/bin/search.sh +31 -5
  102. package/runtime/claude/bin/session-autosave.sh +6 -3
  103. package/runtime/claude/bin/session-id.sh +147 -0
  104. package/runtime/claude/bin/session-log.sh +19 -0
  105. package/runtime/claude/bin/session-record.sh +26 -0
  106. package/runtime/claude/bin/session-start.sh +121 -151
  107. package/runtime/claude/bin/settings.sh +13 -8
  108. package/runtime/claude/bin/spirit-discovery-claude.sh +4 -0
  109. package/runtime/claude/bin/spirit-discovery-codex.sh +4 -0
  110. package/runtime/claude/bin/spirit-discovery-pi.sh +4 -0
  111. package/runtime/claude/bin/spirit-discovery-prime.sh +4 -0
  112. package/runtime/claude/bin/spirit-discovery.sh +4 -0
  113. package/runtime/claude/bin/startup-check.sh +1 -1
  114. package/runtime/claude/bin/telemetry.sh +11 -4
  115. package/runtime/claude/bin/test-baseline.sh +33 -5
  116. package/runtime/claude/bin/todo.sh +1 -1
  117. package/runtime/claude/bin/worktree-create.sh +15 -45
  118. package/runtime/claude/bin/worktree.sh +6 -0
  119. package/runtime/claude/docs/spirit-discovery.md +96 -0
  120. package/runtime/claude/egregore_runtime/__init__.py +18 -12
  121. package/runtime/claude/egregore_runtime/access.py +608 -0
  122. package/runtime/claude/egregore_runtime/access_cli.py +190 -0
  123. package/runtime/claude/egregore_runtime/adapters/invitation.py +5 -3
  124. package/runtime/claude/egregore_runtime/adapters/qmd.py +521 -47
  125. package/runtime/claude/egregore_runtime/admin_entry.py +56 -0
  126. package/runtime/claude/egregore_runtime/artifacts.py +222 -16
  127. package/runtime/claude/egregore_runtime/contracts.py +27 -0
  128. package/runtime/claude/egregore_runtime/eligibility.py +5 -12
  129. package/runtime/claude/egregore_runtime/episode_binding.py +16 -5
  130. package/runtime/claude/egregore_runtime/github_issues.py +2 -2
  131. package/runtime/claude/egregore_runtime/handoff_lookup.py +9 -20
  132. package/runtime/claude/egregore_runtime/harness_cli.py +674 -149
  133. package/runtime/claude/egregore_runtime/held.py +2436 -0
  134. package/runtime/claude/egregore_runtime/hosted_api.py +137 -0
  135. package/runtime/claude/egregore_runtime/identity.py +196 -65
  136. package/runtime/claude/egregore_runtime/identity_transfer.py +6 -2
  137. package/runtime/claude/egregore_runtime/ingest_cli.py +2 -2
  138. package/runtime/claude/egregore_runtime/ingest_index_cli.py +4 -16
  139. package/runtime/claude/egregore_runtime/installation.py +119 -0
  140. package/runtime/claude/egregore_runtime/investigation.py +64 -41
  141. package/runtime/claude/egregore_runtime/investigation_guidance.py +16 -11
  142. package/runtime/claude/egregore_runtime/issue_cli.py +51 -32
  143. package/runtime/claude/egregore_runtime/issue_reporting.py +4 -3
  144. package/runtime/claude/egregore_runtime/issue_triage_service.py +2 -2
  145. package/runtime/claude/egregore_runtime/issues.py +12 -6
  146. package/runtime/claude/egregore_runtime/knowledge.py +331 -5
  147. package/runtime/claude/egregore_runtime/knowledge_cli.py +57 -17
  148. package/runtime/claude/egregore_runtime/lifecycle.py +88 -22
  149. package/runtime/claude/egregore_runtime/lifecycle_cli.py +21 -13
  150. package/runtime/claude/egregore_runtime/line_ids.py +10 -0
  151. package/runtime/claude/egregore_runtime/memory_push.py +349 -0
  152. package/runtime/claude/egregore_runtime/observe.py +64 -25
  153. package/runtime/claude/egregore_runtime/observers.py +19 -0
  154. package/runtime/claude/egregore_runtime/optional_modules.py +13 -0
  155. package/runtime/claude/egregore_runtime/outputs_cli.py +321 -0
  156. package/runtime/claude/egregore_runtime/policy.py +651 -24
  157. package/runtime/claude/egregore_runtime/private_result.py +2 -1
  158. package/runtime/claude/egregore_runtime/projection.py +946 -0
  159. package/runtime/claude/egregore_runtime/quest_cli.py +20 -23
  160. package/runtime/claude/egregore_runtime/question_cli.py +15 -11
  161. package/runtime/claude/egregore_runtime/questions.py +10 -10
  162. package/runtime/claude/egregore_runtime/quests.py +10 -10
  163. package/runtime/claude/egregore_runtime/record_publish.py +548 -0
  164. package/runtime/claude/egregore_runtime/record_subject.py +564 -0
  165. package/runtime/claude/egregore_runtime/research_ingest.py +2 -4
  166. package/runtime/claude/egregore_runtime/runtime.py +169 -60
  167. package/runtime/claude/egregore_runtime/sandbox.py +194 -0
  168. package/runtime/claude/egregore_runtime/scroll_cli.py +22 -32
  169. package/runtime/claude/egregore_runtime/scrolls.py +23 -15
  170. package/runtime/claude/egregore_runtime/shadow.py +262 -0
  171. package/runtime/claude/egregore_runtime/spirit_discovery.py +487 -0
  172. package/runtime/claude/egregore_runtime/spirit_discovery_cli.py +25 -0
  173. package/runtime/claude/egregore_runtime/spirit_discovery_transport.py +18 -0
  174. package/runtime/claude/egregore_runtime/status.py +101 -44
  175. package/runtime/claude/egregore_runtime/status_cli.py +13 -5
  176. package/runtime/claude/egregore_runtime/sync.py +39 -21
  177. package/runtime/claude/egregore_runtime/taste.py +934 -0
  178. package/runtime/claude/egregore_runtime/taste_cli.py +242 -0
  179. package/runtime/claude/egregore_runtime/taste_rules.py +91 -0
  180. package/runtime/claude/egregore_runtime/telemetry.py +26 -3
  181. package/runtime/claude/egregore_runtime/todo_cli.py +15 -12
  182. package/runtime/claude/egregore_runtime/todos.py +3 -2
  183. package/runtime/claude/egregore_runtime/upgrade.py +85 -58
  184. package/runtime/claude/egregore_runtime/writeback.py +330 -24
  185. package/runtime/claude/manifest.json +65 -5
  186. package/runtime/codex/.claude/context/handoff-claiming.md +32 -0
  187. package/runtime/codex/.claude/context/notification-consent.md +12 -9
  188. package/runtime/codex/.claude/skills/view/SKILL.md +438 -0
  189. package/runtime/codex/.codex/hooks.json +11 -0
  190. package/runtime/codex/.codex/skills/dark-mode/SKILL.md +1 -1
  191. package/runtime/codex/.codex/skills/records/SKILL.md +29 -0
  192. package/runtime/codex/.codex/skills/summon/SKILL.md +1 -1
  193. package/runtime/codex/.codex/skills/view/assets/compose-scaffold.html +19 -13
  194. package/runtime/codex/.codex/spec-manifest.json +13 -19
  195. package/runtime/codex/AGENTS.md +54 -105
  196. package/runtime/codex/bin/agent.sh +231 -7
  197. package/runtime/codex/bin/api-call.sh +44 -23
  198. package/runtime/codex/bin/artifact-register.sh +7 -1
  199. package/runtime/codex/bin/artifact-writeback.sh +1 -1
  200. package/runtime/codex/bin/artifacts.sh +66 -265
  201. package/runtime/codex/bin/attendant.sh +4 -1
  202. package/runtime/codex/bin/boundary-guard.cjs +1848 -0
  203. package/runtime/codex/bin/boundary-hook.sh +41 -0
  204. package/runtime/codex/bin/capture-run.sh +32 -2
  205. package/runtime/codex/bin/codex-render-spec.mjs +24 -46
  206. package/runtime/codex/bin/codex-session-start.sh +19 -0
  207. package/runtime/codex/bin/codex-skill-render.mjs +18 -0
  208. package/runtime/codex/bin/config-get.sh +10 -1
  209. package/runtime/codex/bin/connect-refresh.sh +16 -0
  210. package/runtime/codex/bin/corpus_map.py +5 -0
  211. package/runtime/codex/bin/data/capabilities/codex.json +1458 -0
  212. package/runtime/codex/bin/genui.sh +19 -0
  213. package/runtime/codex/bin/graph-batch.sh +1 -1
  214. package/runtime/codex/bin/graph-op.sh +14 -0
  215. package/runtime/codex/bin/graph.sh +1 -1
  216. package/runtime/codex/bin/handoff-pr-backfill.sh +1 -1
  217. package/runtime/codex/bin/handoff-run.sh +11 -6
  218. package/runtime/codex/bin/ingest-item.sh +1 -1
  219. package/runtime/codex/bin/ingest-surface.html +22 -4
  220. package/runtime/codex/bin/issue.sh +1 -1
  221. package/runtime/codex/bin/knowledge.sh +1 -1
  222. package/runtime/codex/bin/lib/card-delivery.sh +329 -0
  223. package/runtime/codex/bin/lib/config.sh +148 -3
  224. package/runtime/codex/bin/lib/context.sh +38 -67
  225. package/runtime/codex/bin/lib/genui-entry.py +36 -0
  226. package/runtime/codex/bin/lib/git-message.sh +25 -6
  227. package/runtime/codex/bin/lib/git-sync.sh +12 -4
  228. package/runtime/codex/bin/lib/greeting.sh +255 -28
  229. package/runtime/codex/bin/lib/handoff_meta.py +294 -0
  230. package/runtime/codex/bin/lib/health-footer.sh +83 -2
  231. package/runtime/codex/bin/lib/identity.sh +31 -10
  232. package/runtime/codex/bin/lib/instance-registry.sh +71 -0
  233. package/runtime/codex/bin/lib/local_ci_receipt.py +249 -0
  234. package/runtime/codex/bin/lib/metrics.sh +121 -8
  235. package/runtime/codex/bin/lib/notices.sh +36 -0
  236. package/runtime/codex/bin/lib/record_outcomes.py +729 -0
  237. package/runtime/codex/bin/lib/reflexes.py +267 -0
  238. package/runtime/codex/bin/lib/retrieval_scorecard.py +624 -0
  239. package/runtime/codex/bin/lib/session-record-prompt.txt +18 -0
  240. package/runtime/codex/bin/lib/session_record.py +3747 -0
  241. package/runtime/codex/bin/lib/session_trail.py +1983 -0
  242. package/runtime/codex/bin/lib/settings-drift.sh +8 -5
  243. package/runtime/codex/bin/lib/startup-restart.sh +41 -0
  244. package/runtime/codex/bin/lib/worktree-links.sh +92 -0
  245. package/runtime/codex/bin/memory-push.sh +6 -0
  246. package/runtime/codex/bin/merge-gate.sh +498 -0
  247. package/runtime/codex/bin/notify.sh +5 -2
  248. package/runtime/codex/bin/page-access.sh +103 -0
  249. package/runtime/codex/bin/person.sh +13 -2
  250. package/runtime/codex/bin/pi-render-spec.mjs +0 -1
  251. package/runtime/codex/bin/pi-spirit-discovery.mjs +34 -0
  252. package/runtime/codex/bin/post-update.sh +181 -0
  253. package/runtime/codex/bin/prime-render-spec.mjs +0 -1
  254. package/runtime/codex/bin/publish-artifact.sh +89 -42
  255. package/runtime/codex/bin/qa-inventory.sh +2 -2
  256. package/runtime/codex/bin/quest.sh +1 -1
  257. package/runtime/codex/bin/records.sh +11 -0
  258. package/runtime/codex/bin/reflexes/05-vitals.sh +9 -0
  259. package/runtime/codex/bin/reflexes/10-open-items.sh +12 -0
  260. package/runtime/codex/bin/reflexes/15-retrieval.sh +8 -0
  261. package/runtime/codex/bin/reflexes/20-nightly.sh +100 -0
  262. package/runtime/codex/bin/reflexes/20-sandbox.sh +85 -0
  263. package/runtime/codex/bin/reflexes/30-cost.py +116 -0
  264. package/runtime/codex/bin/reflexes/40-shells.py +51 -0
  265. package/runtime/codex/bin/reflexes/README.md +61 -0
  266. package/runtime/codex/bin/reflexes.sh +7 -0
  267. package/runtime/codex/bin/runtime-identity.sh +140 -42
  268. package/runtime/codex/bin/screenshot.sh +77 -0
  269. package/runtime/codex/bin/scroll.sh +1 -1
  270. package/runtime/codex/bin/search.sh +31 -5
  271. package/runtime/codex/bin/session-autosave.sh +6 -3
  272. package/runtime/codex/bin/session-id.sh +147 -0
  273. package/runtime/codex/bin/session-log.sh +19 -0
  274. package/runtime/codex/bin/session-record.sh +26 -0
  275. package/runtime/codex/bin/settings.sh +13 -8
  276. package/runtime/codex/bin/spirit-discovery-claude.sh +4 -0
  277. package/runtime/codex/bin/spirit-discovery-codex.sh +4 -0
  278. package/runtime/codex/bin/spirit-discovery-pi.sh +4 -0
  279. package/runtime/codex/bin/spirit-discovery-prime.sh +4 -0
  280. package/runtime/codex/bin/spirit-discovery.sh +4 -0
  281. package/runtime/codex/bin/startup-check.sh +1 -1
  282. package/runtime/codex/bin/telemetry.sh +11 -4
  283. package/runtime/codex/bin/test-baseline.sh +33 -5
  284. package/runtime/codex/bin/todo.sh +1 -1
  285. package/runtime/codex/bin/worktree-create.sh +15 -45
  286. package/runtime/codex/bin/worktree.sh +6 -0
  287. package/runtime/codex/docs/spirit-discovery.md +96 -0
  288. package/runtime/codex/egregore_runtime/__init__.py +18 -12
  289. package/runtime/codex/egregore_runtime/access.py +608 -0
  290. package/runtime/codex/egregore_runtime/access_cli.py +190 -0
  291. package/runtime/codex/egregore_runtime/adapters/invitation.py +5 -3
  292. package/runtime/codex/egregore_runtime/adapters/qmd.py +521 -47
  293. package/runtime/codex/egregore_runtime/admin_entry.py +56 -0
  294. package/runtime/codex/egregore_runtime/artifacts.py +222 -16
  295. package/runtime/codex/egregore_runtime/contracts.py +27 -0
  296. package/runtime/codex/egregore_runtime/eligibility.py +5 -12
  297. package/runtime/codex/egregore_runtime/episode_binding.py +16 -5
  298. package/runtime/codex/egregore_runtime/github_issues.py +2 -2
  299. package/runtime/codex/egregore_runtime/handoff_lookup.py +9 -20
  300. package/runtime/codex/egregore_runtime/harness_cli.py +674 -149
  301. package/runtime/codex/egregore_runtime/held.py +2436 -0
  302. package/runtime/codex/egregore_runtime/hosted_api.py +137 -0
  303. package/runtime/codex/egregore_runtime/identity.py +196 -65
  304. package/runtime/codex/egregore_runtime/identity_transfer.py +6 -2
  305. package/runtime/codex/egregore_runtime/ingest_cli.py +2 -2
  306. package/runtime/codex/egregore_runtime/ingest_index_cli.py +4 -16
  307. package/runtime/codex/egregore_runtime/installation.py +119 -0
  308. package/runtime/codex/egregore_runtime/investigation.py +64 -41
  309. package/runtime/codex/egregore_runtime/investigation_guidance.py +16 -11
  310. package/runtime/codex/egregore_runtime/issue_cli.py +51 -32
  311. package/runtime/codex/egregore_runtime/issue_reporting.py +4 -3
  312. package/runtime/codex/egregore_runtime/issue_triage_service.py +2 -2
  313. package/runtime/codex/egregore_runtime/issues.py +12 -6
  314. package/runtime/codex/egregore_runtime/knowledge.py +331 -5
  315. package/runtime/codex/egregore_runtime/knowledge_cli.py +57 -17
  316. package/runtime/codex/egregore_runtime/lifecycle.py +88 -22
  317. package/runtime/codex/egregore_runtime/lifecycle_cli.py +21 -13
  318. package/runtime/codex/egregore_runtime/line_ids.py +10 -0
  319. package/runtime/codex/egregore_runtime/memory_push.py +349 -0
  320. package/runtime/codex/egregore_runtime/observe.py +64 -25
  321. package/runtime/codex/egregore_runtime/observers.py +19 -0
  322. package/runtime/codex/egregore_runtime/optional_modules.py +13 -0
  323. package/runtime/codex/egregore_runtime/outputs_cli.py +321 -0
  324. package/runtime/codex/egregore_runtime/policy.py +651 -24
  325. package/runtime/codex/egregore_runtime/private_result.py +2 -1
  326. package/runtime/codex/egregore_runtime/projection.py +946 -0
  327. package/runtime/codex/egregore_runtime/quest_cli.py +20 -23
  328. package/runtime/codex/egregore_runtime/question_cli.py +15 -11
  329. package/runtime/codex/egregore_runtime/questions.py +10 -10
  330. package/runtime/codex/egregore_runtime/quests.py +10 -10
  331. package/runtime/codex/egregore_runtime/record_publish.py +548 -0
  332. package/runtime/codex/egregore_runtime/record_subject.py +564 -0
  333. package/runtime/codex/egregore_runtime/research_ingest.py +2 -4
  334. package/runtime/codex/egregore_runtime/runtime.py +169 -60
  335. package/runtime/codex/egregore_runtime/sandbox.py +194 -0
  336. package/runtime/codex/egregore_runtime/scroll_cli.py +22 -32
  337. package/runtime/codex/egregore_runtime/scrolls.py +23 -15
  338. package/runtime/codex/egregore_runtime/shadow.py +262 -0
  339. package/runtime/codex/egregore_runtime/spirit_discovery.py +487 -0
  340. package/runtime/codex/egregore_runtime/spirit_discovery_cli.py +25 -0
  341. package/runtime/codex/egregore_runtime/spirit_discovery_transport.py +18 -0
  342. package/runtime/codex/egregore_runtime/status.py +101 -44
  343. package/runtime/codex/egregore_runtime/status_cli.py +13 -5
  344. package/runtime/codex/egregore_runtime/sync.py +39 -21
  345. package/runtime/codex/egregore_runtime/taste.py +934 -0
  346. package/runtime/codex/egregore_runtime/taste_cli.py +242 -0
  347. package/runtime/codex/egregore_runtime/taste_rules.py +91 -0
  348. package/runtime/codex/egregore_runtime/telemetry.py +26 -3
  349. package/runtime/codex/egregore_runtime/todo_cli.py +15 -12
  350. package/runtime/codex/egregore_runtime/todos.py +3 -2
  351. package/runtime/codex/egregore_runtime/upgrade.py +85 -58
  352. package/runtime/codex/egregore_runtime/writeback.py +330 -24
  353. package/runtime/codex/manifest.json +66 -5
  354. package/runtime/pi/.claude/context/handoff-claiming.md +32 -0
  355. package/runtime/pi/.claude/context/notification-consent.md +12 -9
  356. package/runtime/pi/.claude/skills/view/SKILL.md +438 -0
  357. package/runtime/pi/.codex/hooks.json +11 -0
  358. package/runtime/pi/.codex/skills/dark-mode/SKILL.md +1 -1
  359. package/runtime/pi/.codex/skills/records/SKILL.md +29 -0
  360. package/runtime/pi/.codex/skills/summon/SKILL.md +1 -1
  361. package/runtime/pi/.codex/skills/view/assets/compose-scaffold.html +19 -13
  362. package/runtime/pi/.codex/spec-manifest.json +13 -19
  363. package/runtime/pi/.pi/APPEND_SYSTEM.md +53 -99
  364. package/runtime/pi/.pi/extensions/egregore.ts +13 -1
  365. package/runtime/pi/.pi/spec-manifest.json +2 -2
  366. package/runtime/pi/AGENTS.md +54 -105
  367. package/runtime/pi/bin/agent.sh +231 -7
  368. package/runtime/pi/bin/api-call.sh +44 -23
  369. package/runtime/pi/bin/artifact-register.sh +7 -1
  370. package/runtime/pi/bin/artifact-writeback.sh +1 -1
  371. package/runtime/pi/bin/artifacts.sh +66 -265
  372. package/runtime/pi/bin/attendant.sh +4 -1
  373. package/runtime/pi/bin/boundary-guard.cjs +1848 -0
  374. package/runtime/pi/bin/boundary-hook.sh +41 -0
  375. package/runtime/pi/bin/capture-run.sh +32 -2
  376. package/runtime/pi/bin/codex-render-spec.mjs +24 -46
  377. package/runtime/pi/bin/codex-session-start.sh +19 -0
  378. package/runtime/pi/bin/codex-skill-render.mjs +18 -0
  379. package/runtime/pi/bin/config-get.sh +10 -1
  380. package/runtime/pi/bin/connect-refresh.sh +16 -0
  381. package/runtime/pi/bin/corpus_map.py +5 -0
  382. package/runtime/pi/bin/data/capabilities/pi.json +1458 -0
  383. package/runtime/pi/bin/genui.sh +19 -0
  384. package/runtime/pi/bin/graph-batch.sh +1 -1
  385. package/runtime/pi/bin/graph-op.sh +14 -0
  386. package/runtime/pi/bin/graph.sh +1 -1
  387. package/runtime/pi/bin/handoff-pr-backfill.sh +1 -1
  388. package/runtime/pi/bin/handoff-run.sh +11 -6
  389. package/runtime/pi/bin/ingest-item.sh +1 -1
  390. package/runtime/pi/bin/ingest-surface.html +22 -4
  391. package/runtime/pi/bin/issue.sh +1 -1
  392. package/runtime/pi/bin/knowledge.sh +1 -1
  393. package/runtime/pi/bin/lib/card-delivery.sh +329 -0
  394. package/runtime/pi/bin/lib/config.sh +148 -3
  395. package/runtime/pi/bin/lib/context.sh +38 -67
  396. package/runtime/pi/bin/lib/genui-entry.py +36 -0
  397. package/runtime/pi/bin/lib/git-message.sh +25 -6
  398. package/runtime/pi/bin/lib/git-sync.sh +12 -4
  399. package/runtime/pi/bin/lib/greeting.sh +255 -28
  400. package/runtime/pi/bin/lib/handoff_meta.py +294 -0
  401. package/runtime/pi/bin/lib/health-footer.sh +83 -2
  402. package/runtime/pi/bin/lib/identity.sh +31 -10
  403. package/runtime/pi/bin/lib/instance-registry.sh +71 -0
  404. package/runtime/pi/bin/lib/local_ci_receipt.py +249 -0
  405. package/runtime/pi/bin/lib/metrics.sh +121 -8
  406. package/runtime/pi/bin/lib/notices.sh +36 -0
  407. package/runtime/pi/bin/lib/record_outcomes.py +729 -0
  408. package/runtime/pi/bin/lib/reflexes.py +267 -0
  409. package/runtime/pi/bin/lib/retrieval_scorecard.py +624 -0
  410. package/runtime/pi/bin/lib/session-record-prompt.txt +18 -0
  411. package/runtime/pi/bin/lib/session_record.py +3747 -0
  412. package/runtime/pi/bin/lib/session_trail.py +1983 -0
  413. package/runtime/pi/bin/lib/settings-drift.sh +8 -5
  414. package/runtime/pi/bin/lib/startup-restart.sh +41 -0
  415. package/runtime/pi/bin/lib/worktree-links.sh +92 -0
  416. package/runtime/pi/bin/memory-push.sh +6 -0
  417. package/runtime/pi/bin/merge-gate.sh +498 -0
  418. package/runtime/pi/bin/notify.sh +5 -2
  419. package/runtime/pi/bin/page-access.sh +103 -0
  420. package/runtime/pi/bin/person.sh +13 -2
  421. package/runtime/pi/bin/pi-branch-policy.mjs +37 -0
  422. package/runtime/pi/bin/pi-observe-context.mjs +97 -4
  423. package/runtime/pi/bin/pi-render-spec.mjs +0 -1
  424. package/runtime/pi/bin/pi-session-start.sh +4 -1
  425. package/runtime/pi/bin/pi-spirit-discovery.mjs +34 -0
  426. package/runtime/pi/bin/post-update.sh +181 -0
  427. package/runtime/pi/bin/prime-render-spec.mjs +0 -1
  428. package/runtime/pi/bin/publish-artifact.sh +89 -42
  429. package/runtime/pi/bin/qa-inventory.sh +2 -2
  430. package/runtime/pi/bin/quest.sh +1 -1
  431. package/runtime/pi/bin/records.sh +11 -0
  432. package/runtime/pi/bin/reflexes/05-vitals.sh +9 -0
  433. package/runtime/pi/bin/reflexes/10-open-items.sh +12 -0
  434. package/runtime/pi/bin/reflexes/15-retrieval.sh +8 -0
  435. package/runtime/pi/bin/reflexes/20-nightly.sh +100 -0
  436. package/runtime/pi/bin/reflexes/20-sandbox.sh +85 -0
  437. package/runtime/pi/bin/reflexes/30-cost.py +116 -0
  438. package/runtime/pi/bin/reflexes/40-shells.py +51 -0
  439. package/runtime/pi/bin/reflexes/README.md +61 -0
  440. package/runtime/pi/bin/reflexes.sh +7 -0
  441. package/runtime/pi/bin/runtime-identity.sh +140 -42
  442. package/runtime/pi/bin/screenshot.sh +77 -0
  443. package/runtime/pi/bin/scroll.sh +1 -1
  444. package/runtime/pi/bin/search.sh +31 -5
  445. package/runtime/pi/bin/session-autosave.sh +6 -3
  446. package/runtime/pi/bin/session-id.sh +147 -0
  447. package/runtime/pi/bin/session-log.sh +19 -0
  448. package/runtime/pi/bin/session-record.sh +26 -0
  449. package/runtime/pi/bin/settings.sh +13 -8
  450. package/runtime/pi/bin/spirit-discovery-claude.sh +4 -0
  451. package/runtime/pi/bin/spirit-discovery-codex.sh +4 -0
  452. package/runtime/pi/bin/spirit-discovery-pi.sh +4 -0
  453. package/runtime/pi/bin/spirit-discovery-prime.sh +4 -0
  454. package/runtime/pi/bin/spirit-discovery.sh +4 -0
  455. package/runtime/pi/bin/startup-check.sh +1 -1
  456. package/runtime/pi/bin/telemetry.sh +11 -4
  457. package/runtime/pi/bin/test-baseline.sh +33 -5
  458. package/runtime/pi/bin/todo.sh +1 -1
  459. package/runtime/pi/bin/worktree-create.sh +15 -45
  460. package/runtime/pi/bin/worktree.sh +6 -0
  461. package/runtime/pi/docs/spirit-discovery.md +96 -0
  462. package/runtime/pi/egregore_runtime/__init__.py +18 -12
  463. package/runtime/pi/egregore_runtime/access.py +608 -0
  464. package/runtime/pi/egregore_runtime/access_cli.py +190 -0
  465. package/runtime/pi/egregore_runtime/adapters/invitation.py +5 -3
  466. package/runtime/pi/egregore_runtime/adapters/qmd.py +521 -47
  467. package/runtime/pi/egregore_runtime/admin_entry.py +56 -0
  468. package/runtime/pi/egregore_runtime/artifacts.py +222 -16
  469. package/runtime/pi/egregore_runtime/contracts.py +27 -0
  470. package/runtime/pi/egregore_runtime/eligibility.py +5 -12
  471. package/runtime/pi/egregore_runtime/episode_binding.py +16 -5
  472. package/runtime/pi/egregore_runtime/github_issues.py +2 -2
  473. package/runtime/pi/egregore_runtime/handoff_lookup.py +9 -20
  474. package/runtime/pi/egregore_runtime/harness_cli.py +674 -149
  475. package/runtime/pi/egregore_runtime/held.py +2436 -0
  476. package/runtime/pi/egregore_runtime/hosted_api.py +137 -0
  477. package/runtime/pi/egregore_runtime/identity.py +196 -65
  478. package/runtime/pi/egregore_runtime/identity_transfer.py +6 -2
  479. package/runtime/pi/egregore_runtime/ingest_cli.py +2 -2
  480. package/runtime/pi/egregore_runtime/ingest_index_cli.py +4 -16
  481. package/runtime/pi/egregore_runtime/installation.py +119 -0
  482. package/runtime/pi/egregore_runtime/investigation.py +64 -41
  483. package/runtime/pi/egregore_runtime/investigation_guidance.py +16 -11
  484. package/runtime/pi/egregore_runtime/issue_cli.py +51 -32
  485. package/runtime/pi/egregore_runtime/issue_reporting.py +4 -3
  486. package/runtime/pi/egregore_runtime/issue_triage_service.py +2 -2
  487. package/runtime/pi/egregore_runtime/issues.py +12 -6
  488. package/runtime/pi/egregore_runtime/knowledge.py +331 -5
  489. package/runtime/pi/egregore_runtime/knowledge_cli.py +57 -17
  490. package/runtime/pi/egregore_runtime/lifecycle.py +88 -22
  491. package/runtime/pi/egregore_runtime/lifecycle_cli.py +21 -13
  492. package/runtime/pi/egregore_runtime/line_ids.py +10 -0
  493. package/runtime/pi/egregore_runtime/memory_push.py +349 -0
  494. package/runtime/pi/egregore_runtime/observe.py +64 -25
  495. package/runtime/pi/egregore_runtime/observers.py +19 -0
  496. package/runtime/pi/egregore_runtime/optional_modules.py +13 -0
  497. package/runtime/pi/egregore_runtime/outputs_cli.py +321 -0
  498. package/runtime/pi/egregore_runtime/policy.py +651 -24
  499. package/runtime/pi/egregore_runtime/private_result.py +2 -1
  500. package/runtime/pi/egregore_runtime/projection.py +946 -0
  501. package/runtime/pi/egregore_runtime/quest_cli.py +20 -23
  502. package/runtime/pi/egregore_runtime/question_cli.py +15 -11
  503. package/runtime/pi/egregore_runtime/questions.py +10 -10
  504. package/runtime/pi/egregore_runtime/quests.py +10 -10
  505. package/runtime/pi/egregore_runtime/record_publish.py +548 -0
  506. package/runtime/pi/egregore_runtime/record_subject.py +564 -0
  507. package/runtime/pi/egregore_runtime/research_ingest.py +2 -4
  508. package/runtime/pi/egregore_runtime/runtime.py +169 -60
  509. package/runtime/pi/egregore_runtime/sandbox.py +194 -0
  510. package/runtime/pi/egregore_runtime/scroll_cli.py +22 -32
  511. package/runtime/pi/egregore_runtime/scrolls.py +23 -15
  512. package/runtime/pi/egregore_runtime/shadow.py +262 -0
  513. package/runtime/pi/egregore_runtime/spirit_discovery.py +487 -0
  514. package/runtime/pi/egregore_runtime/spirit_discovery_cli.py +25 -0
  515. package/runtime/pi/egregore_runtime/spirit_discovery_transport.py +18 -0
  516. package/runtime/pi/egregore_runtime/status.py +101 -44
  517. package/runtime/pi/egregore_runtime/status_cli.py +13 -5
  518. package/runtime/pi/egregore_runtime/sync.py +39 -21
  519. package/runtime/pi/egregore_runtime/taste.py +934 -0
  520. package/runtime/pi/egregore_runtime/taste_cli.py +242 -0
  521. package/runtime/pi/egregore_runtime/taste_rules.py +91 -0
  522. package/runtime/pi/egregore_runtime/telemetry.py +26 -3
  523. package/runtime/pi/egregore_runtime/todo_cli.py +15 -12
  524. package/runtime/pi/egregore_runtime/todos.py +3 -2
  525. package/runtime/pi/egregore_runtime/upgrade.py +85 -58
  526. package/runtime/pi/egregore_runtime/writeback.py +330 -24
  527. package/runtime/pi/manifest.json +66 -5
  528. package/runtime/prime/.claude/context/handoff-claiming.md +32 -0
  529. package/runtime/prime/.claude/context/notification-consent.md +12 -9
  530. package/runtime/prime/.claude/skills/view/SKILL.md +438 -0
  531. package/runtime/prime/.codex/hooks.json +11 -0
  532. package/runtime/prime/.codex/skills/dark-mode/SKILL.md +1 -1
  533. package/runtime/prime/.codex/skills/records/SKILL.md +29 -0
  534. package/runtime/prime/.codex/skills/summon/SKILL.md +1 -1
  535. package/runtime/prime/.codex/skills/view/assets/compose-scaffold.html +19 -13
  536. package/runtime/prime/.codex/spec-manifest.json +13 -19
  537. package/runtime/prime/.prime/agent/APPEND_SYSTEM.md +53 -99
  538. package/runtime/prime/.prime/agent/extensions/egregore.ts +14 -1
  539. package/runtime/prime/.prime/agent/skills/egregore/SKILL.md +14 -6
  540. package/runtime/prime/.prime/agent/skills/egregore/src/egregore/__init__.py +66 -1
  541. package/runtime/prime/.prime/agent/spec-manifest.json +2 -2
  542. package/runtime/prime/AGENTS.md +54 -105
  543. package/runtime/prime/bin/agent.sh +231 -7
  544. package/runtime/prime/bin/api-call.sh +44 -23
  545. package/runtime/prime/bin/artifact-register.sh +7 -1
  546. package/runtime/prime/bin/artifact-writeback.sh +1 -1
  547. package/runtime/prime/bin/artifacts.sh +66 -265
  548. package/runtime/prime/bin/attendant.sh +4 -1
  549. package/runtime/prime/bin/boundary-guard.cjs +1848 -0
  550. package/runtime/prime/bin/boundary-hook.sh +41 -0
  551. package/runtime/prime/bin/capture-run.sh +32 -2
  552. package/runtime/prime/bin/codex-render-spec.mjs +24 -46
  553. package/runtime/prime/bin/codex-session-start.sh +19 -0
  554. package/runtime/prime/bin/codex-skill-render.mjs +18 -0
  555. package/runtime/prime/bin/config-get.sh +10 -1
  556. package/runtime/prime/bin/connect-refresh.sh +16 -0
  557. package/runtime/prime/bin/corpus_map.py +5 -0
  558. package/runtime/prime/bin/data/capabilities/prime.json +1458 -0
  559. package/runtime/prime/bin/genui.sh +19 -0
  560. package/runtime/prime/bin/graph-batch.sh +1 -1
  561. package/runtime/prime/bin/graph-op.sh +14 -0
  562. package/runtime/prime/bin/graph.sh +1 -1
  563. package/runtime/prime/bin/handoff-pr-backfill.sh +1 -1
  564. package/runtime/prime/bin/handoff-run.sh +11 -6
  565. package/runtime/prime/bin/ingest-item.sh +1 -1
  566. package/runtime/prime/bin/ingest-surface.html +22 -4
  567. package/runtime/prime/bin/issue.sh +1 -1
  568. package/runtime/prime/bin/knowledge.sh +1 -1
  569. package/runtime/prime/bin/lib/card-delivery.sh +329 -0
  570. package/runtime/prime/bin/lib/config.sh +148 -3
  571. package/runtime/prime/bin/lib/context.sh +38 -67
  572. package/runtime/prime/bin/lib/genui-entry.py +36 -0
  573. package/runtime/prime/bin/lib/git-message.sh +25 -6
  574. package/runtime/prime/bin/lib/git-sync.sh +12 -4
  575. package/runtime/prime/bin/lib/greeting.sh +255 -28
  576. package/runtime/prime/bin/lib/handoff_meta.py +294 -0
  577. package/runtime/prime/bin/lib/health-footer.sh +83 -2
  578. package/runtime/prime/bin/lib/identity.sh +31 -10
  579. package/runtime/prime/bin/lib/instance-registry.sh +71 -0
  580. package/runtime/prime/bin/lib/local_ci_receipt.py +249 -0
  581. package/runtime/prime/bin/lib/metrics.sh +121 -8
  582. package/runtime/prime/bin/lib/notices.sh +36 -0
  583. package/runtime/prime/bin/lib/record_outcomes.py +729 -0
  584. package/runtime/prime/bin/lib/reflexes.py +267 -0
  585. package/runtime/prime/bin/lib/retrieval_scorecard.py +624 -0
  586. package/runtime/prime/bin/lib/session-record-prompt.txt +18 -0
  587. package/runtime/prime/bin/lib/session_record.py +3747 -0
  588. package/runtime/prime/bin/lib/session_trail.py +1983 -0
  589. package/runtime/prime/bin/lib/settings-drift.sh +8 -5
  590. package/runtime/prime/bin/lib/startup-restart.sh +41 -0
  591. package/runtime/prime/bin/lib/worktree-links.sh +92 -0
  592. package/runtime/prime/bin/memory-push.sh +6 -0
  593. package/runtime/prime/bin/merge-gate.sh +498 -0
  594. package/runtime/prime/bin/notify.sh +5 -2
  595. package/runtime/prime/bin/page-access.sh +103 -0
  596. package/runtime/prime/bin/person.sh +13 -2
  597. package/runtime/prime/bin/pi-branch-policy.mjs +37 -0
  598. package/runtime/prime/bin/pi-observe-context.mjs +97 -4
  599. package/runtime/prime/bin/pi-render-spec.mjs +0 -1
  600. package/runtime/prime/bin/pi-spirit-discovery.mjs +34 -0
  601. package/runtime/prime/bin/post-update.sh +181 -0
  602. package/runtime/prime/bin/prime-render-spec.mjs +0 -1
  603. package/runtime/prime/bin/prime-session-start.sh +3 -1
  604. package/runtime/prime/bin/publish-artifact.sh +89 -42
  605. package/runtime/prime/bin/qa-inventory.sh +2 -2
  606. package/runtime/prime/bin/quest.sh +1 -1
  607. package/runtime/prime/bin/records.sh +11 -0
  608. package/runtime/prime/bin/reflexes/05-vitals.sh +9 -0
  609. package/runtime/prime/bin/reflexes/10-open-items.sh +12 -0
  610. package/runtime/prime/bin/reflexes/15-retrieval.sh +8 -0
  611. package/runtime/prime/bin/reflexes/20-nightly.sh +100 -0
  612. package/runtime/prime/bin/reflexes/20-sandbox.sh +85 -0
  613. package/runtime/prime/bin/reflexes/30-cost.py +116 -0
  614. package/runtime/prime/bin/reflexes/40-shells.py +51 -0
  615. package/runtime/prime/bin/reflexes/README.md +61 -0
  616. package/runtime/prime/bin/reflexes.sh +7 -0
  617. package/runtime/prime/bin/runtime-identity.sh +140 -42
  618. package/runtime/prime/bin/screenshot.sh +77 -0
  619. package/runtime/prime/bin/scroll.sh +1 -1
  620. package/runtime/prime/bin/search.sh +31 -5
  621. package/runtime/prime/bin/session-autosave.sh +6 -3
  622. package/runtime/prime/bin/session-id.sh +147 -0
  623. package/runtime/prime/bin/session-log.sh +19 -0
  624. package/runtime/prime/bin/session-record.sh +26 -0
  625. package/runtime/prime/bin/settings.sh +13 -8
  626. package/runtime/prime/bin/spirit-discovery-claude.sh +4 -0
  627. package/runtime/prime/bin/spirit-discovery-codex.sh +4 -0
  628. package/runtime/prime/bin/spirit-discovery-pi.sh +4 -0
  629. package/runtime/prime/bin/spirit-discovery-prime.sh +4 -0
  630. package/runtime/prime/bin/spirit-discovery.sh +4 -0
  631. package/runtime/prime/bin/startup-check.sh +1 -1
  632. package/runtime/prime/bin/telemetry.sh +11 -4
  633. package/runtime/prime/bin/test-baseline.sh +33 -5
  634. package/runtime/prime/bin/todo.sh +1 -1
  635. package/runtime/prime/bin/worktree-create.sh +15 -45
  636. package/runtime/prime/bin/worktree.sh +6 -0
  637. package/runtime/prime/docs/spirit-discovery.md +96 -0
  638. package/runtime/prime/egregore_runtime/__init__.py +18 -12
  639. package/runtime/prime/egregore_runtime/access.py +608 -0
  640. package/runtime/prime/egregore_runtime/access_cli.py +190 -0
  641. package/runtime/prime/egregore_runtime/adapters/invitation.py +5 -3
  642. package/runtime/prime/egregore_runtime/adapters/qmd.py +521 -47
  643. package/runtime/prime/egregore_runtime/admin_entry.py +56 -0
  644. package/runtime/prime/egregore_runtime/artifacts.py +222 -16
  645. package/runtime/prime/egregore_runtime/contracts.py +27 -0
  646. package/runtime/prime/egregore_runtime/eligibility.py +5 -12
  647. package/runtime/prime/egregore_runtime/episode_binding.py +16 -5
  648. package/runtime/prime/egregore_runtime/github_issues.py +2 -2
  649. package/runtime/prime/egregore_runtime/handoff_lookup.py +9 -20
  650. package/runtime/prime/egregore_runtime/harness_cli.py +674 -149
  651. package/runtime/prime/egregore_runtime/held.py +2436 -0
  652. package/runtime/prime/egregore_runtime/hosted_api.py +137 -0
  653. package/runtime/prime/egregore_runtime/identity.py +196 -65
  654. package/runtime/prime/egregore_runtime/identity_transfer.py +6 -2
  655. package/runtime/prime/egregore_runtime/ingest_cli.py +2 -2
  656. package/runtime/prime/egregore_runtime/ingest_index_cli.py +4 -16
  657. package/runtime/prime/egregore_runtime/installation.py +119 -0
  658. package/runtime/prime/egregore_runtime/investigation.py +64 -41
  659. package/runtime/prime/egregore_runtime/investigation_guidance.py +16 -11
  660. package/runtime/prime/egregore_runtime/issue_cli.py +51 -32
  661. package/runtime/prime/egregore_runtime/issue_reporting.py +4 -3
  662. package/runtime/prime/egregore_runtime/issue_triage_service.py +2 -2
  663. package/runtime/prime/egregore_runtime/issues.py +12 -6
  664. package/runtime/prime/egregore_runtime/knowledge.py +331 -5
  665. package/runtime/prime/egregore_runtime/knowledge_cli.py +57 -17
  666. package/runtime/prime/egregore_runtime/lifecycle.py +88 -22
  667. package/runtime/prime/egregore_runtime/lifecycle_cli.py +21 -13
  668. package/runtime/prime/egregore_runtime/line_ids.py +10 -0
  669. package/runtime/prime/egregore_runtime/memory_push.py +349 -0
  670. package/runtime/prime/egregore_runtime/observe.py +64 -25
  671. package/runtime/prime/egregore_runtime/observers.py +19 -0
  672. package/runtime/prime/egregore_runtime/optional_modules.py +13 -0
  673. package/runtime/prime/egregore_runtime/outputs_cli.py +321 -0
  674. package/runtime/prime/egregore_runtime/policy.py +651 -24
  675. package/runtime/prime/egregore_runtime/private_result.py +2 -1
  676. package/runtime/prime/egregore_runtime/projection.py +946 -0
  677. package/runtime/prime/egregore_runtime/quest_cli.py +20 -23
  678. package/runtime/prime/egregore_runtime/question_cli.py +15 -11
  679. package/runtime/prime/egregore_runtime/questions.py +10 -10
  680. package/runtime/prime/egregore_runtime/quests.py +10 -10
  681. package/runtime/prime/egregore_runtime/record_publish.py +548 -0
  682. package/runtime/prime/egregore_runtime/record_subject.py +564 -0
  683. package/runtime/prime/egregore_runtime/research_ingest.py +2 -4
  684. package/runtime/prime/egregore_runtime/runtime.py +169 -60
  685. package/runtime/prime/egregore_runtime/sandbox.py +194 -0
  686. package/runtime/prime/egregore_runtime/scroll_cli.py +22 -32
  687. package/runtime/prime/egregore_runtime/scrolls.py +23 -15
  688. package/runtime/prime/egregore_runtime/shadow.py +262 -0
  689. package/runtime/prime/egregore_runtime/spirit_discovery.py +487 -0
  690. package/runtime/prime/egregore_runtime/spirit_discovery_cli.py +25 -0
  691. package/runtime/prime/egregore_runtime/spirit_discovery_transport.py +18 -0
  692. package/runtime/prime/egregore_runtime/status.py +101 -44
  693. package/runtime/prime/egregore_runtime/status_cli.py +13 -5
  694. package/runtime/prime/egregore_runtime/sync.py +39 -21
  695. package/runtime/prime/egregore_runtime/taste.py +934 -0
  696. package/runtime/prime/egregore_runtime/taste_cli.py +242 -0
  697. package/runtime/prime/egregore_runtime/taste_rules.py +91 -0
  698. package/runtime/prime/egregore_runtime/telemetry.py +26 -3
  699. package/runtime/prime/egregore_runtime/todo_cli.py +15 -12
  700. package/runtime/prime/egregore_runtime/todos.py +3 -2
  701. package/runtime/prime/egregore_runtime/upgrade.py +85 -58
  702. package/runtime/prime/egregore_runtime/writeback.py +330 -24
  703. package/runtime/prime/manifest.json +66 -5
  704. package/runtime/claude/bin/thread.sh +0 -9
  705. package/runtime/claude/egregore_runtime/admin_gate.py +0 -102
  706. package/runtime/claude/egregore_runtime/thread_cli.py +0 -173
  707. package/runtime/claude/egregore_runtime/threads.py +0 -521
  708. package/runtime/codex/bin/thread.sh +0 -9
  709. package/runtime/codex/egregore_runtime/admin_gate.py +0 -102
  710. package/runtime/codex/egregore_runtime/thread_cli.py +0 -173
  711. package/runtime/codex/egregore_runtime/threads.py +0 -521
  712. package/runtime/pi/bin/thread.sh +0 -9
  713. package/runtime/pi/egregore_runtime/admin_gate.py +0 -102
  714. package/runtime/pi/egregore_runtime/thread_cli.py +0 -173
  715. package/runtime/pi/egregore_runtime/threads.py +0 -521
  716. package/runtime/prime/bin/thread.sh +0 -9
  717. package/runtime/prime/egregore_runtime/admin_gate.py +0 -102
  718. package/runtime/prime/egregore_runtime/thread_cli.py +0 -173
  719. package/runtime/prime/egregore_runtime/threads.py +0 -521
@@ -0,0 +1,564 @@
1
+ """Pure, public presentation of record subjects. Classification grants no access.
2
+
3
+ Unknown or damaged producer metadata never falls back to a human session. Only
4
+ the fixed, closed metadata facts shape is projected; Markdown prose is not data.
5
+ Callers must authorize the source and mask held records before using this module.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from collections import Counter
10
+ from datetime import datetime
11
+ import hashlib
12
+ import json
13
+ import math
14
+ import re
15
+
16
+ PRODUCER = "pi-durable.metadata/v1"
17
+ CONTROL = re.compile(r"^spirit-[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$")
18
+ ACTOR = re.compile(r"^[A-Za-z0-9._:-]{1,160}$")
19
+ PRINCIPAL = re.compile(r"^principal:[a-z0-9][a-z0-9_.-]{0,63}$")
20
+ HASH = re.compile(r"^[a-f0-9]{64}$")
21
+ MARKERS = {"producer", "producer_principal_id", "record_owner_actor_id", "producer_identity", "subject_kind",
22
+ "source_projection_version", "source_projection_revision", "source_evidence_revision"}
23
+ STATE = {
24
+ "execution": {"queued", "running", "stopped"},
25
+ "outcome": {"pending", "unverified", "fulfilled", "partial", "needs_input", "blocked", "failed", "cancelled"},
26
+ "verification": {"not_run", "passed", "failed", "inconclusive"},
27
+ "delivery": {"not_requested", "pending", "unknown", "sent", "failed"},
28
+ }
29
+ GAPS = {"attachment_unavailable", "submission_unavailable", "reconciliation_failed", "entry_limit",
30
+ "incomplete_submission", "observer_stopped", "identity_conflict"}
31
+
32
+ OBSERVATION_ENUMS = {
33
+ **STATE,
34
+ "transition": {"admitted", "claimed", "settled", "cancelled", "retried", "answered", "recovered", "delivery"},
35
+ "status": {"queued", "placed", "done", "unanswered"},
36
+ "entry_kind": {"user", "assistant", "tool_result", "other"},
37
+ "tool_status": {"success", "error", "unknown"},
38
+ }
39
+ OBSERVATION_FIELDS = {
40
+ "lifecycle": {"transition", "execution", "outcome", "verification", "delivery", "attempt"},
41
+ "submission": {"status"}, "entry": {"entry_kind", "tool_status"}, "gap": {"reason"},
42
+ }
43
+ OBSERVATION_REQUIRED = {"lifecycle": "transition", "submission": "status", "entry": "entry_kind", "gap": "reason"}
44
+ V2_GAPS = GAPS | {"restart_gap", "queue_overflow", "stream_snapshot", "write_failed", "flush_failed",
45
+ "source_unavailable", "unattributed", "lineage_limit", "usage_unavailable", "usage_reset", "shutdown_incomplete"}
46
+ V2_FIELDS = {
47
+ "task": {"task_kind", "state", "outcome", "parent_ref", "background", "abort_requested"},
48
+ "run": {"phase", "submission_ref"},
49
+ "tool": {"phase", "status", "capability_ref", "call_ref", "task_ref", "parent_call_ref", "parent_task_ref"},
50
+ "retry": {"phase", "attempt"}, "compaction": {"phase", "reason", "task_ref", "blocking", "status"},
51
+ }
52
+ V2_ENUMS = {
53
+ "task_kind": {"run", "generation", "tool", "compaction", "other"},
54
+ "state": {"pending", "running", "waiting", "completing", "terminal"},
55
+ "outcome": {"completed", "failed", "aborted", "orphaned", "faulted", "unknown"},
56
+ "phase": {"start", "end", "snapshot"}, "status": {"success", "error", "unknown"},
57
+ "reason": {"manual", "threshold", "overflow", "unknown"},
58
+ }
59
+ USAGE_COUNTERS = {"model_tokens", "tool_tokens", "model_reported_cost", "tool_reported_cost"}
60
+
61
+ def _int(value, maximum=2**53 - 1):
62
+ return type(value) is int and 0 <= value <= maximum
63
+
64
+
65
+ def _stamp(value):
66
+ try:
67
+ return isinstance(value, str) and len(value) <= 40 and datetime.fromisoformat(value.replace("Z", "+00:00")).tzinfo is not None
68
+ except ValueError:
69
+ return False
70
+
71
+
72
+ def _observation_fields(value, allowed, required=None):
73
+ if not isinstance(value, dict) or not set(value) <= set(allowed) or not set(required if required is not None else allowed) <= set(value):
74
+ raise ValueError("invalid metadata shape")
75
+
76
+
77
+ def _observation_stamp(value):
78
+ if not isinstance(value, str) or len(value) > 40 or not value.endswith(("Z", "+00:00")):
79
+ raise ValueError("invalid metadata time")
80
+ parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
81
+ if parsed.tzinfo is None:
82
+ raise ValueError("invalid metadata time")
83
+ return parsed.timestamp()
84
+
85
+
86
+ def _observation_reference(value, *, nullable=False):
87
+ if nullable and value is None:
88
+ return
89
+ if not isinstance(value, str) or not HASH.fullmatch(value):
90
+ raise ValueError("invalid opaque reference")
91
+
92
+
93
+ def _observation_metadata_v2(kind, metadata):
94
+ if kind in OBSERVATION_FIELDS:
95
+ _observation_fields(metadata, OBSERVATION_FIELDS[kind], {OBSERVATION_REQUIRED[kind]})
96
+ for key, item in metadata.items():
97
+ valid = _int(item) if key == "attempt" else isinstance(item, str) and item in (V2_GAPS if kind == "gap" else OBSERVATION_ENUMS[key])
98
+ if not valid:
99
+ raise ValueError("invalid metadata value")
100
+ return
101
+ if kind not in V2_FIELDS:
102
+ raise ValueError("invalid metadata kind")
103
+ _observation_fields(metadata, V2_FIELDS[kind])
104
+ for key, item in metadata.items():
105
+ if key.endswith("_ref"):
106
+ _observation_reference(item, nullable=True)
107
+ elif key in {"background", "abort_requested", "blocking"}:
108
+ if type(item) is not bool and not (key == "blocking" and item is None):
109
+ raise ValueError("invalid metadata boolean")
110
+ elif key == "attempt":
111
+ if not _int(item):
112
+ raise ValueError("invalid metadata attempt")
113
+ elif not isinstance(item, str) or item not in (V2_ENUMS[key] | {"update"} if kind == "tool" and key == "phase" else V2_ENUMS[key]):
114
+ raise ValueError("invalid metadata enum")
115
+
116
+
117
+ def _observation_usage_values(value, *, bound=False):
118
+ _observation_fields(value, USAGE_COUNTERS | ({"ref"} if bound else set()))
119
+ if bound:
120
+ _observation_reference(value["ref"])
121
+ for key in USAGE_COUNTERS:
122
+ item = value[key]
123
+ if item is not None and (not _int(item) if key.endswith("_tokens") else
124
+ type(item) not in {int, float} or not math.isfinite(item) or item < 0):
125
+ raise ValueError("invalid usage counter")
126
+
127
+
128
+ def validate_spirit_snapshot_v2(snapshot, *, revision):
129
+ """Pure closed V2 projection validation, with no live freshness or authority.
130
+
131
+ Private capture separately enforces current collection time and permissions;
132
+ public readers validate historical canonical facts without renewing either.
133
+ """
134
+ _observation_fields(snapshot, {"state", "events", "coverage", "captured_at"})
135
+ state = snapshot["state"]
136
+ _observation_fields(state, {*STATE, "revision", "attempt"})
137
+ if (not _int(revision) or not _int(state["revision"]) or state["revision"] != revision
138
+ or not _int(state["attempt"]) or any(not isinstance(state[key], str) or state[key] not in allowed for key, allowed in STATE.items())):
139
+ raise ValueError("invalid intent state")
140
+ captured = _observation_stamp(snapshot["captured_at"])
141
+ events, coverage = snapshot["events"], snapshot["coverage"]
142
+ if (not isinstance(events, list) or len(events) > 256
143
+ or len(json.dumps(events, separators=(",", ":"), ensure_ascii=False).encode()) > 128 * 1024
144
+ or len(json.dumps(snapshot, separators=(",", ":"), ensure_ascii=False).encode()) > 256 * 1024):
145
+ raise ValueError("invalid metadata count")
146
+ identities = set()
147
+ for event in events:
148
+ _observation_fields(event, {"id", "revision", "source", "source_ref", "source_revision_ref", "epoch_ref", "kind", "metadata", "observed_at", "native_event_time"})
149
+ for key in ("id", "source_ref", "source_revision_ref", "epoch_ref"):
150
+ _observation_reference(event[key], nullable=key == "epoch_ref")
151
+ if (event["id"] in identities or type(event["revision"]) is not int or event["revision"] != revision
152
+ or event["source"] not in {"application", "commit", "semantic", "reconcile"}
153
+ or _observation_stamp(event["observed_at"]) > captured + 2
154
+ or event["native_event_time"] is not None and _observation_stamp(event["native_event_time"]) > captured + 2):
155
+ raise ValueError("invalid metadata event")
156
+ identities.add(event["id"])
157
+ _observation_metadata_v2(event["kind"], event["metadata"])
158
+ _observation_fields(coverage, {"schema", "projection_revision", "historical", "native_journal", "window", "capture", "gaps", "epochs", "usage"})
159
+ if (coverage["schema"] != "egregore.spirit-observation-coverage/v2" or not _int(coverage["projection_revision"])
160
+ or coverage["historical"] != "unknown" or coverage["native_journal"] != "unknown"):
161
+ raise ValueError("invalid coverage")
162
+ window = coverage["window"]
163
+ _observation_fields(window, {"selection", "revision", "limit", "event_count", "earlier", "last_event_ref"})
164
+ if (window["selection"] != "revision_tail" or type(window["revision"]) is not int or window["revision"] != revision
165
+ or type(window["limit"]) is not int or window["limit"] != 256 or not _int(window["event_count"], 256)
166
+ or window["event_count"] != len(events) or window["earlier"] not in {"none", "omitted"}
167
+ or window["last_event_ref"] != (events[-1]["id"] if events else None)):
168
+ raise ValueError("invalid coverage window")
169
+ capture = coverage["capture"]
170
+ _observation_fields(capture, {"durable", "semantic", "application", "unattributed"})
171
+ if (any(capture[key] not in {"captured", "partial", "unknown"} for key in ("durable", "semantic", "application"))
172
+ or capture["unattributed"] != "unknown"):
173
+ raise ValueError("invalid capture coverage")
174
+ gaps = coverage["gaps"]
175
+ _observation_fields(gaps, {"reasons", "dropped_lower_bound", "unmeasured_loss"})
176
+ if (not isinstance(gaps["reasons"], list) or any(not isinstance(item, str) or item not in V2_GAPS for item in gaps["reasons"])
177
+ or gaps["reasons"] != sorted(set(gaps["reasons"])) or not _int(gaps["dropped_lower_bound"])
178
+ or type(gaps["unmeasured_loss"]) is not bool):
179
+ raise ValueError("invalid gap coverage")
180
+ epochs = coverage["epochs"]
181
+ _observation_fields(epochs, {"items", "earlier"})
182
+ if not isinstance(epochs["items"], list) or len(epochs["items"]) > 32 or epochs["earlier"] not in {"none", "omitted"}:
183
+ raise ValueError("invalid epoch coverage")
184
+ seen = set()
185
+ for epoch in epochs["items"]:
186
+ _observation_fields(epoch, {"ref", "conversation_ref", "scope", "state", "opened_at", "closed_at", "first_commit_ref", "last_commit_ref", "last_semantic_ref"})
187
+ for key in ("ref", "conversation_ref", "first_commit_ref", "last_commit_ref", "last_semantic_ref"):
188
+ _observation_reference(epoch[key], nullable=key not in {"ref", "conversation_ref"})
189
+ opened = _observation_stamp(epoch["opened_at"])
190
+ closed = _observation_stamp(epoch["closed_at"]) if epoch["closed_at"] is not None else None
191
+ if (epoch["ref"] in seen or epoch["scope"] not in {"root", "owned_child"} or epoch["state"] not in {"open", "closed", "uncertain"}
192
+ or opened > captured + 2 or closed is not None and (closed < opened or closed > captured + 2)
193
+ or epoch["state"] == "closed" and closed is None or epoch["state"] == "open" and closed is not None):
194
+ raise ValueError("invalid epoch")
195
+ seen.add(epoch["ref"])
196
+ usage = coverage["usage"]
197
+ _observation_fields(usage, {"source", "billing", "items", "earlier"})
198
+ if (usage["source"] != "pi.usage" or usage["billing"] != "unverified" or usage["earlier"] not in {"none", "omitted"}
199
+ or not isinstance(usage["items"], list) or len(usage["items"]) > 16):
200
+ raise ValueError("invalid usage coverage")
201
+ seen = set()
202
+ for row in usage["items"]:
203
+ _observation_fields(row, {"conversation_ref", "scope", "attribution", "start", "end", "delta"})
204
+ _observation_reference(row["conversation_ref"])
205
+ if (row["conversation_ref"] in seen or row["scope"] not in {"root", "owned_child"}
206
+ or row["attribution"] not in {"owned_revision", "conversation_only", "unknown"}):
207
+ raise ValueError("invalid usage ownership")
208
+ seen.add(row["conversation_ref"])
209
+ for key in ("start", "end"):
210
+ if row[key] is not None:
211
+ _observation_usage_values(row[key], bound=True)
212
+ if row["delta"] is not None:
213
+ _observation_usage_values(row["delta"])
214
+ if row["attribution"] != "owned_revision" or row["start"] is None or row["end"] is None:
215
+ raise ValueError("invalid usage attribution")
216
+ for key in USAGE_COUNTERS:
217
+ start, end = row["start"][key], row["end"][key]
218
+ expected = None if start is None or end is None else end - start
219
+ if expected is not None and expected < 0 or row["delta"][key] != expected:
220
+ raise ValueError("invalid usage delta")
221
+
222
+
223
+ def spirit_snapshot_facts_v2(control_id, revision, snapshot):
224
+ validate_spirit_snapshot_v2(snapshot, revision=revision)
225
+ if not isinstance(control_id, str) or not CONTROL.fullmatch(control_id):
226
+ raise ValueError("invalid intent control")
227
+ events = snapshot["events"]
228
+ return {
229
+ "schema": "egregore.spirit-session-facts/v2", "harness": "pi-durable", "subject_kind": "durable-intent",
230
+ "control_id": control_id, "intents": 1, "current_revision": revision,
231
+ "projection_revision": snapshot["coverage"]["projection_revision"], "state": snapshot["state"],
232
+ "observed_event_count": len(events), "observed_event_counts": {
233
+ "source": dict(sorted(Counter(item["source"] for item in events).items())),
234
+ "kind": dict(sorted(Counter(item["kind"] for item in events).items()))},
235
+ "events": events, "coverage": snapshot["coverage"],
236
+ "observed_through": max((item["observed_at"] for item in events), key=_observation_stamp, default=None),
237
+ "captured_at": snapshot["captured_at"], "cost": None, "duration": None,
238
+ "canonical_transcript": False, "complete_history": False,
239
+ }
240
+
241
+
242
+ # Public integrity checks only. These closed historical facts neither authenticate
243
+ # their producer nor confer permission to obtain the underlying private sources.
244
+ EVIDENCE_REASONS = {"not_recorded", "source_unavailable", "source_limit", "identity_conflict",
245
+ "stale_revision", "stale_attempt", "pending_capture", "capture_failed", "historical_retention", "unknown_schema"}
246
+ EVIDENCE_DOMAINS = {"generation", "recipe", "capability_lock", "authority", "artifacts", "outcome", "repair", "capture"}
247
+ EVIDENCE_EXTRA = ("evidence", "evidence_history", "observation_fingerprint", "pair_digest")
248
+
249
+
250
+ def _ev_enum(*values):
251
+ return lambda value: isinstance(value, str) and value in values
252
+
253
+
254
+ def _ev_nullable(check):
255
+ return lambda value: value is None or check(value)
256
+
257
+
258
+ def _ev_hash(value):
259
+ return isinstance(value, str) and HASH.fullmatch(value) is not None
260
+
261
+
262
+ def _ev_object(shape):
263
+ return lambda value: (isinstance(value, dict) and set(value) == set(shape)
264
+ and all(check(value[key]) for key, check in shape.items()))
265
+
266
+
267
+ def _ev_array(check, limit, key=lambda value: value, *, ordered=True):
268
+ def valid(value):
269
+ if not isinstance(value, list) or len(value) > limit or not all(check(item) for item in value):
270
+ return False
271
+ identities = [key(item) for item in value]
272
+ return len(set(identities)) == len(identities) and (not ordered or identities == sorted(identities))
273
+ return valid
274
+
275
+
276
+ _EV_REF = _ev_nullable(_ev_hash)
277
+ _EV_DOMAIN = {"status": _ev_enum("retained", "missing", "conflicting", "truncated", "not_applicable"),
278
+ "reason": _ev_nullable(_ev_enum(*EVIDENCE_REASONS))}
279
+ _EV_ERROR = _ev_nullable(_ev_enum("cancelled", "deadline", "lease_lost", "verification_failed", "output_limit", "execution_failed", "unknown"))
280
+ _EV_GENERATION_REFS = ("generation_digest", "descriptor_digest", "source_binding_digest", "source_digest", "registry_digest",
281
+ "policy_digest", "persona_digest", "model_ref", "dependency_digest")
282
+ _EV_RECIPE_REFS = ("binding_digest", "recipe_ref", "recipe_digest", "candidate_subject", "candidate_envelope", "catalog_snapshot",
283
+ "generation_digest", "registry_digest", "policy_digest", "profile_ref", "scope_digest")
284
+ _EV_AUTHORITY_REFS = ("actor_ref", "caller_ref", "grant_ref", "audience_ref", "profile_ref", "origin_ref", "parent_request_ref", "admission_digest", "validator_ref")
285
+ _EV_CHECK = {"check_ref": _ev_hash, "evaluator_ref": _ev_hash, "evaluator_code_digest": _EV_REF,
286
+ "status": _ev_enum("passed", "failed", "inconclusive"), "scope": _ev_enum("artifact_ownership", "artifact_retention", "repair_preparation", "unknown"),
287
+ "basis": _ev_enum("retained_host_check"), "evidence_refs": _ev_array(_ev_hash, 30)}
288
+ _EV_CAPABILITY = {**{key: _ev_hash for key in ("capability_ref", "definition_digest", "package_digest", "recipe_digest", "generation_digest")},
289
+ "adapter_refs": _ev_array(_ev_hash, 16), "tool_refs": _ev_array(_ev_hash, 32), "binding": _ev_enum("admitted_definition")}
290
+ _EV_REPAIR_CHECK = {"check_ref": _ev_hash, **{key: _EV_REF for key in ("command_ref", "manifest_digest", "verifier_digest")},
291
+ "status": _ev_enum("passed", "failed", "inconclusive"), "identity_scope": _ev_enum("declared_files", "configuration_only", "unknown")}
292
+ _EV_REPAIR = {**_EV_DOMAIN, **{key: _EV_REF for key in ("receipt_digest", "task_ref", "accepted_event_ref", "accepted_result_ref",
293
+ "accepted_source_ref", "source_revision_ref", "base_revision_ref", "configuration_digest", "patch_digest")},
294
+ "repair_attempt": _ev_nullable(_int), "lease_epoch": _ev_nullable(_int),
295
+ "accepted_state": _ev_nullable(_ev_enum("review_ready", "failed", "needs_attention")),
296
+ "result_state": _ev_nullable(_ev_enum("ready_for_review", "cancelled", "timed_out", "failed", "needs_attention", "unknown")),
297
+ "verification": _ev_array(_ev_object(_EV_REPAIR_CHECK), 20, lambda row: row["check_ref"], ordered=False),
298
+ "error_class": _EV_ERROR, "evidence_scope": _ev_enum("none", "accepted_repair_preparation"),
299
+ "verification_identity": _ev_enum("retained_declared_files", "configuration_only", "unknown")}
300
+ _EV_ARTIFACT = {"artifact_ref": _ev_hash, "kind": _ev_enum("finding", "proposal", "issue_registration"), "request_ref": _ev_hash,
301
+ **{key: _EV_REF for key in ("source_fingerprint", "task_ref", "capability_ref", "result_ref")},
302
+ "retention": _ev_enum("retained", "missing", "conflicting"), "external_result": _ev_enum("unknown", "accepted_preparation"),
303
+ "check_refs": _ev_array(_ev_hash, 32)}
304
+ _EV_OUTCOME = {**_EV_DOMAIN, "execution": _ev_enum(*STATE["execution"]), "current_state": _ev_enum(*STATE["outcome"]),
305
+ **{key: _EV_REF for key in ("report_ref", "acceptance_contract_digest", "validator_ref")},
306
+ "reported_state": _ev_nullable(_ev_enum("fulfilled", "partial", "needs_input", "blocked", "failed")),
307
+ "reported_evidence_refs": _ev_array(_ev_hash, 30), "resolved_state": _ev_nullable(_ev_enum(*STATE["outcome"])),
308
+ "verification": _ev_enum(*STATE["verification"]),
309
+ "basis": _ev_nullable(_ev_enum("host_registered_checks", "reported_with_bounded_host_checks", "host_execution_or_missing_report")),
310
+ "checks": _ev_array(_ev_object(_EV_CHECK), 32, lambda row: row["check_ref"]),
311
+ "limitation_refs": _ev_array(_ev_hash, 20), "unmet_requirement_refs": _ev_array(_ev_hash, 20), "error_class": _EV_ERROR}
312
+ _EV_COVERAGE = {"state": _ev_enum("complete", "partial"),
313
+ **{key: _ev_array(_ev_enum(*EVIDENCE_DOMAINS), 8) for key in ("missing", "truncated", "conflicting")},
314
+ "reasons": _ev_array(_ev_enum(*EVIDENCE_REASONS), 10)}
315
+ _EV_BODY = {"app_attempt": _int,
316
+ "generation": _ev_object({**_EV_DOMAIN, **{key: _EV_REF for key in _EV_GENERATION_REFS},
317
+ "provider_evidence": _ev_enum("host_built", "simulated_host", "unknown"),
318
+ "dependency_evidence": _ev_enum("lock_and_installed_metadata", "unknown"), "external_adapter_evidence": _ev_enum("unavailable")}),
319
+ "recipe": _ev_object({**_EV_DOMAIN, **{key: _EV_REF for key in _EV_RECIPE_REFS}, "selection_evidence": _ev_enum("retained_selection", "unknown"),
320
+ "capability_lock": _ev_object({**_EV_DOMAIN, "lock_digest": _EV_REF,
321
+ "entries": _ev_array(_ev_object(_EV_CAPABILITY), 32, lambda row: row["capability_ref"])})}),
322
+ "authority": _ev_object({**_EV_DOMAIN, "owner_ref": _ev_hash, **{key: _EV_REF for key in _EV_AUTHORITY_REFS},
323
+ "basis": _ev_enum("retained_admission", "retained_mapping", "partial_legacy", "unknown")}),
324
+ "artifacts": _ev_array(_ev_object(_EV_ARTIFACT), 64, lambda row: row["artifact_ref"]),
325
+ "outcome": _ev_object(_EV_OUTCOME), "repair": _ev_object(_EV_REPAIR), "coverage": _ev_object(_EV_COVERAGE)}
326
+ _EV_ROOT = {"schema": _ev_enum("egregore.spirit-evidence/v1"),
327
+ **{key: _ev_hash for key in ("owner_ref", "job_ref", "request_ref", "evidence_digest")},
328
+ "revision": _int, "evidence_revision": lambda value: _int(value) and value > 0, "evidence": _ev_object(_EV_BODY)}
329
+
330
+
331
+ def _evidence_hash(value):
332
+ return hashlib.sha256(json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=True).encode()).hexdigest()
333
+
334
+
335
+ def validate_spirit_evidence(value):
336
+ if not _ev_object(_EV_ROOT)(value) or len(json.dumps(value, separators=(",", ":")).encode()) > 64 * 1024:
337
+ raise ValueError("invalid spirit evidence")
338
+ body = value["evidence"]
339
+ covered = {key: body[key] for key in ("generation", "recipe", "authority", "outcome", "repair")}
340
+ covered["capability_lock"] = body["recipe"]["capability_lock"]
341
+ coverage = body["coverage"]
342
+ for name, domain in covered.items():
343
+ if (domain["reason"] is None) != (domain["status"] in {"retained", "not_applicable"}):
344
+ raise ValueError("invalid evidence coverage")
345
+ if domain["status"] in {"missing", "truncated", "conflicting"} and (name not in coverage[domain["status"]] or domain["reason"] not in coverage["reasons"]):
346
+ raise ValueError("invalid evidence coverage")
347
+ if coverage["state"] != ("partial" if any(coverage[key] for key in ("missing", "truncated", "conflicting", "reasons")) else "complete"):
348
+ raise ValueError("invalid evidence coverage")
349
+ for name, required in (("generation", tuple(key for key in _EV_GENERATION_REFS if key != "source_binding_digest")),
350
+ ("recipe", _EV_RECIPE_REFS), ("authority", tuple(key for key in _EV_AUTHORITY_REFS if key != "parent_request_ref"))):
351
+ if body[name]["status"] == "retained" and any(body[name][key] is None for key in required):
352
+ raise ValueError("invalid retained provenance")
353
+ if body["authority"]["owner_ref"] != value["owner_ref"]:
354
+ raise ValueError("invalid evidence owner")
355
+ recipe, repair = body["recipe"], body["repair"]
356
+ lock = recipe["capability_lock"]
357
+ if (lock["status"] != "retained" and lock["entries"]
358
+ or lock["status"] == "retained" and (lock["lock_digest"] is None or recipe["status"] != "retained")
359
+ or any(row["recipe_digest"] != recipe["recipe_digest"] or row["generation_digest"] != recipe["generation_digest"] for row in lock["entries"])):
360
+ raise ValueError("invalid capability evidence")
361
+ artifacts = {row["artifact_ref"] for row in body["artifacts"]}
362
+ checks = {row["check_ref"] for row in body["outcome"]["checks"]}
363
+ capabilities = {row["capability_ref"] for row in lock["entries"]}
364
+ for row in body["artifacts"]:
365
+ if (row["request_ref"] != value["request_ref"] or not set(row["check_refs"]) <= checks
366
+ or row["capability_ref"] is not None and (row["capability_ref"] not in capabilities or row["task_ref"] is None)
367
+ or row["retention"] in {"missing", "conflicting"} and "artifacts" not in coverage[row["retention"]]
368
+ or row["external_result"] == "unknown" and row["result_ref"] is not None
369
+ or row["external_result"] == "accepted_preparation" and (row["result_ref"] is None or repair["evidence_scope"] != "accepted_repair_preparation")):
370
+ raise ValueError("invalid artifact attribution")
371
+ if (not set(body["outcome"]["reported_evidence_refs"]) <= artifacts
372
+ or any(not set(row["evidence_refs"]) <= artifacts for row in body["outcome"]["checks"])):
373
+ raise ValueError("invalid outcome attribution")
374
+ if repair["status"] == "retained" and any(repair[key] is None for key in ("task_ref", "repair_attempt", "lease_epoch", "accepted_event_ref", "accepted_result_ref", "accepted_state", "result_state", "accepted_source_ref")):
375
+ raise ValueError("invalid repair acceptance")
376
+ if (repair["evidence_scope"] == "accepted_repair_preparation" and (repair["status"] != "retained" or repair["accepted_state"] != "review_ready"
377
+ or repair["result_state"] != "ready_for_review" or repair["patch_digest"] is None or not repair["verification"]
378
+ or any(row["status"] != "passed" for row in repair["verification"]))
379
+ or any(row["identity_scope"] == "declared_files" and any(row[key] is None for key in ("command_ref", "manifest_digest", "verifier_digest")) for row in repair["verification"])):
380
+ raise ValueError("invalid repair verification")
381
+ if value["evidence_digest"] != _evidence_hash({key: value[key] for key in ("schema", "owner_ref", "job_ref", "revision", "request_ref", "evidence")}):
382
+ raise ValueError("invalid evidence digest")
383
+ return value
384
+
385
+
386
+ def spirit_evidence_pair_digest(projection_revision, observation_fingerprint, evidence, history):
387
+ validate_spirit_evidence(evidence)
388
+ if (not _int(projection_revision) or not _ev_hash(observation_fingerprint)
389
+ or not _ev_object({"retained": lambda value: _int(value, 32) and value > 0, "dropped": _int})(history)
390
+ or history["retained"] + history["dropped"] != evidence["evidence_revision"]):
391
+ raise ValueError("invalid evidence pair")
392
+ return _evidence_hash([evidence["owner_ref"], evidence["job_ref"], evidence["revision"], projection_revision,
393
+ observation_fingerprint, evidence["evidence_revision"], evidence["evidence_digest"], history])
394
+
395
+
396
+ def validate_spirit_snapshot_v3(snapshot, *, revision):
397
+ _observation_fields(snapshot, {"state", "events", "coverage", "captured_at", *EVIDENCE_EXTRA})
398
+ if len(json.dumps(snapshot, separators=(",", ":")).encode()) > 256 * 1024:
399
+ raise ValueError("evidence snapshot exceeds byte limit")
400
+ validate_spirit_snapshot_v2({key: snapshot[key] for key in ("state", "events", "coverage", "captured_at")}, revision=revision)
401
+ evidence, state = snapshot["evidence"], snapshot["state"]
402
+ paired = spirit_evidence_pair_digest(snapshot["coverage"]["projection_revision"], snapshot["observation_fingerprint"], evidence, snapshot["evidence_history"])
403
+ recorded = evidence["evidence"]
404
+ if (paired != snapshot["pair_digest"] or evidence["revision"] != revision or recorded["app_attempt"] != state["attempt"]
405
+ or any(recorded["outcome"][key] != state[target] for key, target in (("execution", "execution"), ("current_state", "outcome"), ("verification", "verification")))):
406
+ raise ValueError("evidence pair differs from observation")
407
+
408
+
409
+ def spirit_snapshot_facts_v3(control_id, revision, snapshot):
410
+ validate_spirit_snapshot_v3(snapshot, revision=revision)
411
+ core = {key: snapshot[key] for key in ("state", "events", "coverage", "captured_at")}
412
+ return {**spirit_snapshot_facts_v2(control_id, revision, core), "schema": "egregore.spirit-session-facts/v3",
413
+ **{key: snapshot[key] for key in EVIDENCE_EXTRA}}
414
+
415
+
416
+
417
+ def _marker_key(key):
418
+ return key.strip().strip("\"'").casefold() if isinstance(key, str) else ""
419
+
420
+
421
+ def native_marked(meta):
422
+ return isinstance(meta, dict) and any(_marker_key(key) in MARKERS
423
+ and (key != "subject_kind" or value != "human-session") for key, value in meta.items())
424
+
425
+
426
+ def classify_record(meta, text=""):
427
+ if not isinstance(meta, dict):
428
+ return "unsupported-native"
429
+ # Some generic Markdown readers discard indented header lines. Preserve
430
+ # malformed marker detection without treating that spelling as an identity.
431
+ if isinstance(text, str) and text.startswith("---\n"):
432
+ for line in text[4:].partition("\n---")[0].splitlines():
433
+ key, sep, _ = line.partition(":")
434
+ if sep and _marker_key(key) in MARKERS and key != _marker_key(key):
435
+ return "unsupported-native"
436
+ if not native_marked(meta):
437
+ return "human-session"
438
+ if any(_marker_key(key) in MARKERS and key != _marker_key(key) for key in meta):
439
+ return "unsupported-native"
440
+ if (meta.get("kind") != "session-record" or meta.get("producer") != PRODUCER
441
+ or meta.get("subject_kind") != "durable-intent" or not CONTROL.fullmatch(str(meta.get("session", "")))
442
+ or not PRINCIPAL.fullmatch(str(meta.get("producer_principal_id", "")))
443
+ or meta.get("from") != meta.get("producer_principal_id")
444
+ or not ACTOR.fullmatch(str(meta.get("record_owner_actor_id", "")))
445
+ or meta.get("requester_actor_id") != meta.get("record_owner_actor_id")
446
+ or not isinstance(meta.get("producer_identity"), str) or not 1 <= len(meta["producer_identity"]) <= 160
447
+ or not _int(meta.get("record_revision")) or meta["record_revision"] < 1
448
+ or not isinstance(meta.get("detail"), str) or meta["detail"] not in {"metadata", "stub"}):
449
+ return "unsupported-native"
450
+ if meta["detail"] != "stub" and (not _int(meta.get("source_revision"))
451
+ or not HASH.fullmatch(str(meta.get("source_snapshot_digest", "")))
452
+ or not _int(meta.get("observed_event_count"), 512)
453
+ or meta.get("through") is not None and not _stamp(meta["through"])):
454
+ return "unsupported-native"
455
+ if meta["detail"] != "stub" and any(key in meta for key in ("source_projection_version", "source_projection_revision", "source_evidence_revision")):
456
+ if (type(meta.get("source_projection_version")) is not int or meta["source_projection_version"] not in {2, 3}
457
+ or not _int(meta.get("source_projection_revision")) or not _int(meta["observed_event_count"], 256)):
458
+ return "unsupported-native"
459
+ if meta["source_projection_version"] == 3:
460
+ if not _int(meta.get("source_evidence_revision")) or meta["source_evidence_revision"] < 1:
461
+ return "unsupported-native"
462
+ elif "source_evidence_revision" in meta:
463
+ return "unsupported-native"
464
+ return "durable-intent"
465
+
466
+
467
+ def _object(pairs):
468
+ value = {}
469
+ for key, item in pairs:
470
+ if key in value:
471
+ raise ValueError("duplicate metadata field")
472
+ value[key] = item
473
+ return value
474
+
475
+
476
+ def decode_spirit_json(raw):
477
+ """Reject ambiguous keys and non-JSON numbers at private/public boundaries."""
478
+ def invalid_constant(_value):
479
+ raise ValueError("invalid metadata number")
480
+ return json.loads(raw, object_pairs_hook=_object, parse_constant=invalid_constant)
481
+
482
+
483
+ def _facts(meta, text):
484
+ if not isinstance(text, str) or len(text.encode()) > 256 * 1024:
485
+ return None
486
+ sections = re.findall(r"^## Observed metadata\n\n```json\n(.*?)\n```\s*(?=\Z)", text, re.M | re.S)
487
+ if len(sections) != 1:
488
+ return None
489
+ try:
490
+ value = decode_spirit_json(sections[0])
491
+ if isinstance(value, dict) and value.get("schema") in {"egregore.spirit-session-facts/v2", "egregore.spirit-session-facts/v3"}:
492
+ version = 3 if value["schema"].endswith("/v3") else 2
493
+ if (meta.get("source_projection_version") != version or type(meta.get("source_projection_version")) is not int
494
+ or not _int(meta.get("source_projection_revision"))):
495
+ return None
496
+ snapshot = {key: value[key] for key in ("state", "events", "coverage", "captured_at", *(EVIDENCE_EXTRA if version == 3 else ()))}
497
+ builder = spirit_snapshot_facts_v3 if version == 3 else spirit_snapshot_facts_v2
498
+ expected = builder(meta["session"], meta["source_revision"], snapshot)
499
+ if (version == 3 and (not _int(meta.get("source_evidence_revision"))
500
+ or meta["source_evidence_revision"] != expected["evidence"]["evidence_revision"])
501
+ or version == 2 and "source_evidence_revision" in meta):
502
+ return None
503
+ if (expected["projection_revision"] != meta["source_projection_revision"]
504
+ or expected["observed_event_count"] != meta["observed_event_count"]
505
+ or expected["observed_through"] != meta.get("through")
506
+ or json.dumps(expected, sort_keys=True, separators=(",", ":")) != json.dumps(value, sort_keys=True, separators=(",", ":"))):
507
+ return None
508
+ return expected
509
+ if any(key in meta for key in ("source_projection_version", "source_projection_revision", "source_evidence_revision")):
510
+ return None
511
+ keys = {"schema", "harness", "subject_kind", "control_id", "intents", "current_revision",
512
+ "observed_revision_count", "state", "observed_event_count", "terminal_tool_results", "gap_counts",
513
+ "coverage", "observed_through", "captured_at", "native_event_through", "cost", "duration",
514
+ "generation_count", "compactions", "canonical_transcript", "complete_history"}
515
+ if (not isinstance(value, dict) or set(value) != keys or value["schema"] != "egregore.spirit-session-facts/v1"
516
+ or value["harness"] != "pi-durable" or value["subject_kind"] != "durable-intent"
517
+ or value["control_id"] != meta["session"] or type(value["intents"]) is not int or value["intents"] != 1
518
+ or not _int(value["current_revision"]) or value["current_revision"] != meta["source_revision"]
519
+ or not _int(value["observed_revision_count"], 512) or not _int(value["observed_event_count"], 512)
520
+ or value["observed_event_count"] != meta["observed_event_count"]
521
+ or value["observed_through"] != meta.get("through") or not _stamp(value["captured_at"])
522
+ or any(value[key] is not None for key in ("native_event_through", "cost", "duration", "generation_count", "compactions"))
523
+ or value["canonical_transcript"] is not False or value["complete_history"] is not False):
524
+ return None
525
+ state, coverage, tools, gaps = (value[key] for key in ("state", "coverage", "terminal_tool_results", "gap_counts"))
526
+ if (not isinstance(state, dict) or set(state) != {*STATE, "revision", "attempt"}
527
+ or not _int(state["revision"]) or state["revision"] != meta["source_revision"] or not _int(state["attempt"])
528
+ or any(not isinstance(state[key], str) or state[key] not in allowed for key, allowed in STATE.items())
529
+ or not isinstance(coverage, dict) or set(coverage) != {"historical", "pi_transient", "bounded", "write_failures"}
530
+ or coverage["historical"] != "unknown" or coverage["pi_transient"] != "unknown"
531
+ or type(coverage["bounded"]) is not bool or not _int(coverage["write_failures"])
532
+ or not isinstance(tools, dict) or set(tools) != {"observed", "success", "error", "unknown"}
533
+ or any(not _int(number, 512) for number in tools.values())
534
+ or tools["observed"] != sum(tools[key] for key in ("success", "error", "unknown"))
535
+ or tools["observed"] > value["observed_event_count"]
536
+ or not isinstance(gaps, dict) or not set(gaps) <= GAPS or any(not _int(number, 512) for number in gaps.values())):
537
+ return None
538
+ return value
539
+ except (ValueError, KeyError, TypeError, OverflowError, RecursionError):
540
+ return None
541
+
542
+
543
+ def record_subject(meta, text=""):
544
+ kind = classify_record(meta, text)
545
+ if kind != "durable-intent":
546
+ return {"subject_kind": kind}
547
+ if meta["detail"] == "stub":
548
+ return {"subject_kind": kind, "held": True}
549
+ facts = _facts(meta, text)
550
+ result = {"subject_kind": kind, "held": False, "control_id": meta["session"], "producer": PRODUCER,
551
+ "producer_principal_id": meta["producer_principal_id"], "record_owner_actor_id": meta["record_owner_actor_id"],
552
+ "record_revision": meta["record_revision"], "source_revision": meta["source_revision"], "through": meta.get("through"),
553
+ "state": facts["state"] if facts else None, "coverage": facts["coverage"] if facts else None,
554
+ "observed_event_count": facts["observed_event_count"] if facts else None,
555
+ "metadata_status": "validated" if facts else "unavailable", "cost": None, "duration": None,
556
+ "complete_history": False}
557
+ if facts and facts["schema"] in {"egregore.spirit-session-facts/v2", "egregore.spirit-session-facts/v3"}:
558
+ result.update(projection_version=3 if facts["schema"].endswith("/v3") else 2, projection_revision=facts["projection_revision"],
559
+ observed_event_counts=facts["observed_event_counts"])
560
+ if result["projection_version"] == 3:
561
+ evidence = facts["evidence"]
562
+ result.update(evidence_revision=evidence["evidence_revision"], evidence_coverage=evidence["evidence"]["coverage"],
563
+ evidence_history=facts["evidence_history"])
564
+ return result
@@ -17,7 +17,7 @@ from .contracts import (
17
17
  WritebackStatus,
18
18
  )
19
19
  from .runtime import EgregoreRuntime
20
- from .policy import scope_allows_path
20
+ from .policy import allows, action_for
21
21
 
22
22
 
23
23
  RESEARCH_INGEST_SCHEMA_VERSION = "egregore-research-ingest/v1"
@@ -164,9 +164,7 @@ class CanonicalResearchIngestService:
164
164
  raise PermissionError("; ".join(decision.reasons) or "research ingest denied")
165
165
 
166
166
  for _, _, _, _, canonical_path, _ in metadata:
167
- if decision.scopes and not scope_allows_path(
168
- f"memory/{canonical_path}", decision.scopes
169
- ):
167
+ if not allows(decision, f"memory/{canonical_path}", "write", root=self.runtime.canonical_root):
170
168
  raise PermissionError(
171
169
  "research artifact path is outside the actor's authorized scope"
172
170
  )