mednotes-opencode 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (430) hide show
  1. package/.opencode/agents/med-chat-triager.md +204 -0
  2. package/.opencode/agents/med-flashcard-maker.md +63 -0
  3. package/.opencode/agents/med-knowledge-architect.md +230 -0
  4. package/.opencode/agents/med-link-graph-curator.md +177 -0
  5. package/.opencode/agents/med-publish-guard.md +62 -0
  6. package/.opencode/commands/flashcards.md +25 -0
  7. package/.opencode/commands/mednotes/create.md +25 -0
  8. package/.opencode/commands/mednotes/enrich.md +27 -0
  9. package/.opencode/commands/mednotes/fix-wiki.md +27 -0
  10. package/.opencode/commands/mednotes/history.md +22 -0
  11. package/.opencode/commands/mednotes/link-body.md +25 -0
  12. package/.opencode/commands/mednotes/link-related.md +27 -0
  13. package/.opencode/commands/mednotes/link.md +27 -0
  14. package/.opencode/commands/mednotes/pdf-library.md +27 -0
  15. package/.opencode/commands/mednotes/process-chats.md +23 -0
  16. package/.opencode/commands/mednotes/setup.md +21 -0
  17. package/.opencode/commands/mednotes/status.md +27 -0
  18. package/.opencode/commands/mednotes/telemetry.md +27 -0
  19. package/.opencode/commands/report.md +26 -0
  20. package/.opencode/mednotes/AGENTS.md +57 -0
  21. package/.opencode/mednotes/agents/med-chat-triager.md +197 -0
  22. package/.opencode/mednotes/agents/med-flashcard-maker.md +56 -0
  23. package/.opencode/mednotes/agents/med-knowledge-architect.md +224 -0
  24. package/.opencode/mednotes/agents/med-link-graph-curator.md +171 -0
  25. package/.opencode/mednotes/agents/med-publish-guard.md +55 -0
  26. package/.opencode/mednotes/contracts/.gitkeep +1 -0
  27. package/.opencode/mednotes/contracts/agents.json +116 -0
  28. package/.opencode/mednotes/contracts/opencode-plugin.json +70 -0
  29. package/.opencode/mednotes/docs/agent-prompt-hardening.md +567 -0
  30. package/.opencode/mednotes/docs/agent-role-contracts.md +94 -0
  31. package/.opencode/mednotes/docs/anki-mcp-twenty-rules.md +214 -0
  32. package/.opencode/mednotes/docs/anki-templates/README.md +39 -0
  33. package/.opencode/mednotes/docs/anki-templates/cloze.back.html +23 -0
  34. package/.opencode/mednotes/docs/anki-templates/cloze.front.html +14 -0
  35. package/.opencode/mednotes/docs/anki-templates/qa.back.html +24 -0
  36. package/.opencode/mednotes/docs/anki-templates/qa.front.html +14 -0
  37. package/.opencode/mednotes/docs/anki-templates/style.css +182 -0
  38. package/.opencode/mednotes/docs/atomicity-splitting-policy.md +113 -0
  39. package/.opencode/mednotes/docs/extension-docs.md +40 -0
  40. package/.opencode/mednotes/docs/flashcard-ingestion.md +278 -0
  41. package/.opencode/mednotes/docs/knowledge-architect.md +208 -0
  42. package/.opencode/mednotes/docs/merge-policy.md +110 -0
  43. package/.opencode/mednotes/docs/public-vocabulary.md +104 -0
  44. package/.opencode/mednotes/docs/semantic-linker.md +141 -0
  45. package/.opencode/mednotes/docs/taxonomy-policy.md +90 -0
  46. package/.opencode/mednotes/docs/triage-policy.md +187 -0
  47. package/.opencode/mednotes/docs/vault-version-control.md +758 -0
  48. package/.opencode/mednotes/docs/vocabulary-db-recovery.md +58 -0
  49. package/.opencode/mednotes/docs/workflow-output-contract.md +779 -0
  50. package/.opencode/mednotes/hooks/hooks.json +79 -0
  51. package/.opencode/mednotes/package-lock.json +6361 -0
  52. package/.opencode/mednotes/package.json +15 -0
  53. package/.opencode/mednotes/pyproject.toml +48 -0
  54. package/.opencode/mednotes/scripts/bootstrap_windows_python_uv.cmd +13 -0
  55. package/.opencode/mednotes/scripts/bootstrap_windows_python_uv.ps1 +172 -0
  56. package/.opencode/mednotes/scripts/enrich_notes.py +23 -0
  57. package/.opencode/mednotes/scripts/full_reset_windows_python_uv.cmd +13 -0
  58. package/.opencode/mednotes/scripts/hooks/antigravity_hook_status.mjs +212 -0
  59. package/.opencode/mednotes/scripts/hooks/mednotes_hook/adapters/antigravity.mjs +169 -0
  60. package/.opencode/mednotes/scripts/hooks/mednotes_hook/adapters/harness_payload.mjs +103 -0
  61. package/.opencode/mednotes/scripts/hooks/mednotes_hook/adapters/opencode_plugin.mjs +341 -0
  62. package/.opencode/mednotes/scripts/hooks/mednotes_hook/adapters/opencode_user_config_sync.mjs +177 -0
  63. package/.opencode/mednotes/scripts/hooks/mednotes_hook/anki_preflight.mjs +214 -0
  64. package/.opencode/mednotes/scripts/hooks/mednotes_hook/cli.mjs +143 -0
  65. package/.opencode/mednotes/scripts/hooks/mednotes_hook/diagnostics.mjs +11 -0
  66. package/.opencode/mednotes/scripts/hooks/mednotes_hook/domain/agent_directive_core.mjs +160 -0
  67. package/.opencode/mednotes/scripts/hooks/mednotes_hook/fsm_directive.mjs +1470 -0
  68. package/.opencode/mednotes/scripts/hooks/mednotes_hook/hook_errors.mjs +120 -0
  69. package/.opencode/mednotes/scripts/hooks/mednotes_hook/retention.mjs +114 -0
  70. package/.opencode/mednotes/scripts/hooks/mednotes_hook/runtime.mjs +174 -0
  71. package/.opencode/mednotes/scripts/hooks/mednotes_hook/telemetry_capture.mjs +511 -0
  72. package/.opencode/mednotes/scripts/hooks/mednotes_hook/vault_guard.mjs +624 -0
  73. package/.opencode/mednotes/scripts/hooks/mednotes_hook.mjs +5 -0
  74. package/.opencode/mednotes/scripts/mednotes/_runtime_paths.py +24 -0
  75. package/.opencode/mednotes/scripts/mednotes/anki_model_validator.py +18 -0
  76. package/.opencode/mednotes/scripts/mednotes/capture_extension_diff.py +1562 -0
  77. package/.opencode/mednotes/scripts/mednotes/feedback_report.py +16 -0
  78. package/.opencode/mednotes/scripts/mednotes/flashcard_index.py +18 -0
  79. package/.opencode/mednotes/scripts/mednotes/flashcard_pipeline.py +18 -0
  80. package/.opencode/mednotes/scripts/mednotes/flashcard_report.py +18 -0
  81. package/.opencode/mednotes/scripts/mednotes/flashcard_sources.py +18 -0
  82. package/.opencode/mednotes/scripts/mednotes/obsidian/README.md +6 -0
  83. package/.opencode/mednotes/scripts/mednotes/obsidian_note_utils.py +20 -0
  84. package/.opencode/mednotes/scripts/mednotes/pdf_library/cli.py +16 -0
  85. package/.opencode/mednotes/scripts/mednotes/project_fsm.py +229 -0
  86. package/.opencode/mednotes/scripts/mednotes/setup_telemetry_email.py +404 -0
  87. package/.opencode/mednotes/scripts/mednotes/sync_anki_twenty_rules.py +18 -0
  88. package/.opencode/mednotes/scripts/mednotes/sync_opencode_user_config.py +36 -0
  89. package/.opencode/mednotes/scripts/mednotes/wiki/cli.py +20 -0
  90. package/.opencode/mednotes/scripts/mednotes/wiki_graph.py +18 -0
  91. package/.opencode/mednotes/scripts/mednotes/wiki_tree.py +134 -0
  92. package/.opencode/mednotes/scripts/reset_windows_python_uv.ps1 +625 -0
  93. package/.opencode/mednotes/scripts/run_python.mjs +109 -0
  94. package/.opencode/mednotes/scripts/vault/vault_commit.ps1 +19 -0
  95. package/.opencode/mednotes/scripts/vault/vault_commit.sh +18 -0
  96. package/.opencode/mednotes/scripts/vault/vault_git.ps1 +19 -0
  97. package/.opencode/mednotes/scripts/vault/vault_git.py +3107 -0
  98. package/.opencode/mednotes/scripts/vault/vault_git.sh +18 -0
  99. package/.opencode/mednotes/scripts/vault/vault_precommit.ps1 +19 -0
  100. package/.opencode/mednotes/scripts/vault/vault_precommit.sh +18 -0
  101. package/.opencode/mednotes/skills/THIRD_PARTY_NOTICES.md +45 -0
  102. package/.opencode/mednotes/skills/create-medical-flashcards/SKILL.md +113 -0
  103. package/.opencode/mednotes/skills/create-medical-note/SKILL.md +90 -0
  104. package/.opencode/mednotes/skills/enrich-medical-note/SKILL.md +120 -0
  105. package/.opencode/mednotes/skills/fix-medical-wiki/SKILL.md +559 -0
  106. package/.opencode/mednotes/skills/link-medical-wiki/SKILL.md +224 -0
  107. package/.opencode/mednotes/skills/obsidian-cli/SKILL.md +118 -0
  108. package/.opencode/mednotes/skills/obsidian-markdown/SKILL.md +207 -0
  109. package/.opencode/mednotes/skills/obsidian-markdown/references/CALLOUTS.md +58 -0
  110. package/.opencode/mednotes/skills/obsidian-markdown/references/EMBEDS.md +63 -0
  111. package/.opencode/mednotes/skills/obsidian-markdown/references/PROPERTIES.md +61 -0
  112. package/.opencode/mednotes/skills/obsidian-ops/SKILL.md +136 -0
  113. package/.opencode/mednotes/skills/pdf-library/SKILL.md +45 -0
  114. package/.opencode/mednotes/skills/process-medical-chats/SKILL.md +246 -0
  115. package/.opencode/mednotes/skills/workflow-report/SKILL.md +100 -0
  116. package/.opencode/mednotes/src/mednotes/__init__.py +5 -0
  117. package/.opencode/mednotes/src/mednotes/domains/__init__.py +5 -0
  118. package/.opencode/mednotes/src/mednotes/domains/flashcards/README.md +26 -0
  119. package/.opencode/mednotes/src/mednotes/domains/flashcards/__init__.py +2 -0
  120. package/.opencode/mednotes/src/mednotes/domains/flashcards/build_demo_apkg.py +177 -0
  121. package/.opencode/mednotes/src/mednotes/domains/flashcards/contracts.py +385 -0
  122. package/.opencode/mednotes/src/mednotes/domains/flashcards/flashcards_machine.py +522 -0
  123. package/.opencode/mednotes/src/mednotes/domains/flashcards/fsm.py +817 -0
  124. package/.opencode/mednotes/src/mednotes/domains/flashcards/index.py +630 -0
  125. package/.opencode/mednotes/src/mednotes/domains/flashcards/install_models.py +445 -0
  126. package/.opencode/mednotes/src/mednotes/domains/flashcards/model.py +359 -0
  127. package/.opencode/mednotes/src/mednotes/domains/flashcards/obsidian_links.py +135 -0
  128. package/.opencode/mednotes/src/mednotes/domains/flashcards/obsidian_note_utils.py +546 -0
  129. package/.opencode/mednotes/src/mednotes/domains/flashcards/pipeline.py +580 -0
  130. package/.opencode/mednotes/src/mednotes/domains/flashcards/report.py +510 -0
  131. package/.opencode/mednotes/src/mednotes/domains/flashcards/sources.py +682 -0
  132. package/.opencode/mednotes/src/mednotes/domains/flashcards/sync_rules.py +184 -0
  133. package/.opencode/mednotes/src/mednotes/domains/history/__init__.py +1 -0
  134. package/.opencode/mednotes/src/mednotes/domains/history/history_fsm.py +852 -0
  135. package/.opencode/mednotes/src/mednotes/domains/history/history_machine.py +453 -0
  136. package/.opencode/mednotes/src/mednotes/domains/setup/__init__.py +7 -0
  137. package/.opencode/mednotes/src/mednotes/domains/setup/setup_fsm.py +808 -0
  138. package/.opencode/mednotes/src/mednotes/domains/setup/setup_machine.py +973 -0
  139. package/.opencode/mednotes/src/mednotes/domains/wiki/README.md +64 -0
  140. package/.opencode/mednotes/src/mednotes/domains/wiki/__init__.py +1 -0
  141. package/.opencode/mednotes/src/mednotes/domains/wiki/api.py +668 -0
  142. package/.opencode/mednotes/src/mednotes/domains/wiki/batch_state.py +102 -0
  143. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/__init__.py +1 -0
  144. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/atomicity/__init__.py +1 -0
  145. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/atomicity/atomicity.py +877 -0
  146. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/body_link/__init__.py +1 -0
  147. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/body_link/body_linker.py +1562 -0
  148. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/effects/__init__.py +1 -0
  149. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/effects/effect_adapters.py +949 -0
  150. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/effects/fix_wiki_runtime_adapters.py +433 -0
  151. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/graph/__init__.py +1 -0
  152. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/graph/coverage.py +413 -0
  153. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/graph/graph.py +396 -0
  154. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/graph/graph_fixes.py +161 -0
  155. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/hygiene/__init__.py +1 -0
  156. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/hygiene/hygiene.py +483 -0
  157. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/__init__.py +2 -0
  158. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/anchors.py +185 -0
  159. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/core/__init__.py +0 -0
  160. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/core/cache.py +223 -0
  161. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/core/config.py +131 -0
  162. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/core/download.py +224 -0
  163. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/core/frontmatter.py +59 -0
  164. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/core/insert.py +227 -0
  165. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/core/local_import.py +54 -0
  166. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/sources/__init__.py +42 -0
  167. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/sources/web_profiles.py +99 -0
  168. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/sources/web_search.py +203 -0
  169. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/sources/wikimedia.py +102 -0
  170. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/markdown/__init__.py +1 -0
  171. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/markdown/markdown_db_adapter.mjs +434 -0
  172. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/markdown/markdown_node_runtime.py +274 -0
  173. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/markdown/markdown_query.py +227 -0
  174. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/__init__.py +1 -0
  175. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/artifacts.py +605 -0
  176. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/canonical_merge.py +277 -0
  177. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/markdown_zones.py +85 -0
  178. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/meaning_planner.py +307 -0
  179. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_iter.py +67 -0
  180. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_merge.py +278 -0
  181. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_plan.py +409 -0
  182. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_policy.py +22 -0
  183. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_style/__init__.py +79 -0
  184. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_style/fixes.py +264 -0
  185. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_style/frontmatter.py +435 -0
  186. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_style/models.py +208 -0
  187. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_style/prompts.py +37 -0
  188. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_style/tables.py +236 -0
  189. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_style/validate.py +404 -0
  190. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/provenance.py +478 -0
  191. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/raw_chats.py +273 -0
  192. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/sources_backfill.py +235 -0
  193. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/__init__.py +10 -0
  194. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/anchors.py +16 -0
  195. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/captions.py +47 -0
  196. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/cli.py +179 -0
  197. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/cloud.py +52 -0
  198. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/config.py +196 -0
  199. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/context_packets.py +76 -0
  200. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/db.py +81 -0
  201. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/doctor.py +102 -0
  202. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/figure_ids.py +42 -0
  203. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/ingest.py +326 -0
  204. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/insert.py +316 -0
  205. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/mentions.py +57 -0
  206. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/ocr.py +71 -0
  207. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/paths.py +35 -0
  208. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/pdf_engine.py +77 -0
  209. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/schema.py +155 -0
  210. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/search.py +188 -0
  211. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/tui/__init__.py +1 -0
  212. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/tui/app.py +89 -0
  213. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/tui/image_backend.py +29 -0
  214. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/tui/state.py +65 -0
  215. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/publish/__init__.py +1 -0
  216. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/publish/publish.py +1139 -0
  217. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/publish/publish_receipts.py +365 -0
  218. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/publish/publish_recovery.py +240 -0
  219. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/__init__.py +1 -0
  220. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/agent_behavior_corpus.py +2069 -0
  221. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/agent_report_validation.py +4448 -0
  222. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/agent_run_audit.py +852 -0
  223. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/architect_prompt_eval.py +341 -0
  224. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/body_linker_eval.py +240 -0
  225. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/curator_output_validation.py +175 -0
  226. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/curator_prompt_eval.py +865 -0
  227. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/triager_prompt_eval.py +1295 -0
  228. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/related_notes/__init__.py +1 -0
  229. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/related_notes/related_notes.py +1920 -0
  230. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/related_notes/related_notes_headless.py +1186 -0
  231. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/specialist/__init__.py +1 -0
  232. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/specialist/plan_attestation.py +148 -0
  233. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/specialist/specialist_receipts.py +360 -0
  234. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/specialist/specialist_runtime.py +52 -0
  235. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/specialist/specialist_task_runner.py +2470 -0
  236. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/style/__init__.py +1 -0
  237. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/style/style.py +1952 -0
  238. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/subagents/__init__.py +1 -0
  239. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/subagents/agents.py +1767 -0
  240. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/__init__.py +1 -0
  241. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/alias_projection.py +331 -0
  242. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/link_terms.py +151 -0
  243. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/llm_disambiguation.py +182 -0
  244. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/taxonomy/__init__.py +116 -0
  245. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/taxonomy/audit.py +201 -0
  246. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/taxonomy/migration.py +314 -0
  247. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/taxonomy/normalize.py +72 -0
  248. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/taxonomy/policy.py +135 -0
  249. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/taxonomy/resolve.py +413 -0
  250. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/taxonomy/schema.py +157 -0
  251. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/taxonomy/status.py +137 -0
  252. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/vocabulary_bootstrap.py +509 -0
  253. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/vocabulary_curator_batch.py +1115 -0
  254. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/vocabulary_ingestion.py +632 -0
  255. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/vocabulary_map.py +930 -0
  256. package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/vocabulary_recovery.py +1388 -0
  257. package/.opencode/mednotes/src/mednotes/domains/wiki/cli.py +6665 -0
  258. package/.opencode/mednotes/src/mednotes/domains/wiki/common.py +69 -0
  259. package/.opencode/mednotes/src/mednotes/domains/wiki/config.py +210 -0
  260. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/__init__.py +74 -0
  261. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/agent_report.py +242 -0
  262. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/agent_run_audit.py +196 -0
  263. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/agents.py +601 -0
  264. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/curator.py +256 -0
  265. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/effect_payloads.py +519 -0
  266. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/happy_path.py +190 -0
  267. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/link_git.py +110 -0
  268. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/link_runtime_artifact.py +52 -0
  269. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/note_plan.py +75 -0
  270. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/paths.py +114 -0
  271. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/public_report.py +53 -0
  272. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/publish.py +111 -0
  273. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/raw_coverage.py +217 -0
  274. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/related_notes.py +136 -0
  275. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/related_notes_headless.py +153 -0
  276. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/related_notes_runtime.py +395 -0
  277. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/schema_registry.py +637 -0
  278. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/specialist.py +432 -0
  279. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/status.py +62 -0
  280. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/style_rewrite.py +568 -0
  281. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/vocabulary_ingestion.py +223 -0
  282. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/workflow_blockers.py +510 -0
  283. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/workflow_guardrails.py +637 -0
  284. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/workflow_outcomes.py +121 -0
  285. package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/workflow_receipts.py +100 -0
  286. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/__init__.py +1 -0
  287. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/__init__.py +1 -0
  288. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/__main__.py +4 -0
  289. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/cli.py +275 -0
  290. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/__init__.py +2 -0
  291. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/candidates.py +193 -0
  292. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/cli.py +189 -0
  293. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/gemini.py +220 -0
  294. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/inputs.py +120 -0
  295. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/models.py +34 -0
  296. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/parsing.py +48 -0
  297. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/prompts.py +216 -0
  298. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/quality.py +54 -0
  299. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/reporting.py +24 -0
  300. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/runner.py +433 -0
  301. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/utils.py +39 -0
  302. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/vault_guard_bridge.py +17 -0
  303. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/__init__.py +1 -0
  304. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_context_packets.py +454 -0
  305. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_decision_projection.py +133 -0
  306. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_effects.py +1260 -0
  307. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_fsm.py +2768 -0
  308. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_machine.py +1588 -0
  309. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_plan.py +306 -0
  310. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_primary_objective.py +316 -0
  311. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_problem.py +153 -0
  312. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_receipt_evidence.py +306 -0
  313. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_states.py +290 -0
  314. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_user_report.py +342 -0
  315. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/health.py +6332 -0
  316. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/__init__.py +1 -0
  317. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/link_fsm.py +1119 -0
  318. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/link_git.py +638 -0
  319. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/link_machine.py +1106 -0
  320. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/link_retry_governance.py +374 -0
  321. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/link_runtime_result.py +485 -0
  322. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/link_triggers.py +183 -0
  323. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/linking.py +2758 -0
  324. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/reference_repair.py +718 -0
  325. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/related_notes_fsm.py +1855 -0
  326. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link_related/__init__.py +1 -0
  327. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link_related/link_related_machine.py +834 -0
  328. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/process_chats/__init__.py +1 -0
  329. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/process_chats/process_chats_fsm.py +1592 -0
  330. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/process_chats/process_chats_machine.py +3097 -0
  331. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/process_chats/process_chats_primary_objective.py +28 -0
  332. package/.opencode/mednotes/src/mednotes/domains/wiki/flows/process_chats/process_chats_runtime_result.py +185 -0
  333. package/.opencode/mednotes/src/mednotes/domains/wiki/performance.py +97 -0
  334. package/.opencode/mednotes/src/mednotes/kernel/__init__.py +6 -0
  335. package/.opencode/mednotes/src/mednotes/kernel/agent_directive.py +336 -0
  336. package/.opencode/mednotes/src/mednotes/kernel/base.py +51 -0
  337. package/.opencode/mednotes/src/mednotes/kernel/blockers.py +39 -0
  338. package/.opencode/mednotes/src/mednotes/kernel/effect_executor.py +55 -0
  339. package/.opencode/mednotes/src/mednotes/kernel/effect_intent.py +69 -0
  340. package/.opencode/mednotes/src/mednotes/kernel/effects.py +160 -0
  341. package/.opencode/mednotes/src/mednotes/kernel/errors.py +38 -0
  342. package/.opencode/mednotes/src/mednotes/kernel/fsm_event.py +35 -0
  343. package/.opencode/mednotes/src/mednotes/kernel/fsm_model.py +55 -0
  344. package/.opencode/mednotes/src/mednotes/kernel/fsm_transition_result.py +75 -0
  345. package/.opencode/mednotes/src/mednotes/kernel/guardrails.py +188 -0
  346. package/.opencode/mednotes/src/mednotes/kernel/progress.py +319 -0
  347. package/.opencode/mednotes/src/mednotes/kernel/public_report.py +346 -0
  348. package/.opencode/mednotes/src/mednotes/kernel/state_machine.py +164 -0
  349. package/.opencode/mednotes/src/mednotes/kernel/workflow.py +619 -0
  350. package/.opencode/mednotes/src/mednotes/platform/__init__.py +5 -0
  351. package/.opencode/mednotes/src/mednotes/platform/backup_policy.py +382 -0
  352. package/.opencode/mednotes/src/mednotes/platform/feedback/__init__.py +62 -0
  353. package/.opencode/mednotes/src/mednotes/platform/feedback/cli.py +275 -0
  354. package/.opencode/mednotes/src/mednotes/platform/feedback/contracts.py +83 -0
  355. package/.opencode/mednotes/src/mednotes/platform/feedback/core.py +4168 -0
  356. package/.opencode/mednotes/src/mednotes/platform/feedback/integrity.py +989 -0
  357. package/.opencode/mednotes/src/mednotes/platform/feedback/operational_contract.py +2293 -0
  358. package/.opencode/mednotes/src/mednotes/platform/feedback/telemetry.py +875 -0
  359. package/.opencode/mednotes/src/mednotes/platform/feedback/telemetry_config.py +65 -0
  360. package/.opencode/mednotes/src/mednotes/platform/opencode_runtime_config.py +182 -0
  361. package/.opencode/mednotes/src/mednotes/platform/paths/__init__.py +1560 -0
  362. package/.opencode/mednotes/src/mednotes/platform/secrets.py +89 -0
  363. package/.opencode/mednotes/src/mednotes/platform/user_config.py +103 -0
  364. package/.opencode/mednotes/src/mednotes/platform/vault_guard.py +214 -0
  365. package/.opencode/mednotes/uv.lock +932 -0
  366. package/.opencode/mednotes.generated.json +395 -0
  367. package/.opencode/opencode.json +31 -0
  368. package/.opencode/plugins/mednotes-fsm.mjs +7 -0
  369. package/.opencode/plugins/mednotes_hook/adapters/antigravity.mjs +169 -0
  370. package/.opencode/plugins/mednotes_hook/adapters/harness_payload.mjs +103 -0
  371. package/.opencode/plugins/mednotes_hook/adapters/opencode_plugin.mjs +341 -0
  372. package/.opencode/plugins/mednotes_hook/adapters/opencode_user_config_sync.mjs +177 -0
  373. package/.opencode/plugins/mednotes_hook/anki_preflight.mjs +214 -0
  374. package/.opencode/plugins/mednotes_hook/cli.mjs +143 -0
  375. package/.opencode/plugins/mednotes_hook/diagnostics.mjs +11 -0
  376. package/.opencode/plugins/mednotes_hook/domain/agent_directive_core.mjs +160 -0
  377. package/.opencode/plugins/mednotes_hook/fsm_directive.mjs +1470 -0
  378. package/.opencode/plugins/mednotes_hook/hook_errors.mjs +120 -0
  379. package/.opencode/plugins/mednotes_hook/retention.mjs +114 -0
  380. package/.opencode/plugins/mednotes_hook/runtime.mjs +174 -0
  381. package/.opencode/plugins/mednotes_hook/telemetry_capture.mjs +511 -0
  382. package/.opencode/plugins/mednotes_hook/vault_guard.mjs +624 -0
  383. package/AGENTS.md +57 -0
  384. package/README.md +194 -0
  385. package/adapters/antigravity/agents.json +80 -0
  386. package/adapters/antigravity/templates/med-chat-triager.md +214 -0
  387. package/adapters/antigravity/templates/med-flashcard-maker.md +72 -0
  388. package/adapters/antigravity/templates/med-knowledge-architect.md +241 -0
  389. package/adapters/antigravity/templates/med-link-graph-curator.md +187 -0
  390. package/adapters/antigravity/templates/med-publish-guard.md +71 -0
  391. package/adapters/gemini-cli/gemini-extension.json +14 -0
  392. package/adapters/gemini-cli/package.json +15 -0
  393. package/adapters/gemini-cli/pyproject.toml +48 -0
  394. package/bin/mednotes-opencode.mjs +155 -0
  395. package/contracts/agents.json +116 -0
  396. package/core/agents/med-chat-triager.md +197 -0
  397. package/core/agents/med-flashcard-maker.md +56 -0
  398. package/core/agents/med-knowledge-architect.md +224 -0
  399. package/core/agents/med-link-graph-curator.md +171 -0
  400. package/core/agents/med-publish-guard.md +55 -0
  401. package/core/commands/flashcards.toml +22 -0
  402. package/core/commands/mednotes/create.toml +22 -0
  403. package/core/commands/mednotes/enrich.toml +24 -0
  404. package/core/commands/mednotes/fix-wiki.toml +24 -0
  405. package/core/commands/mednotes/history.toml +19 -0
  406. package/core/commands/mednotes/link-body.toml +22 -0
  407. package/core/commands/mednotes/link-related.toml +24 -0
  408. package/core/commands/mednotes/link.toml +24 -0
  409. package/core/commands/mednotes/pdf-library.toml +24 -0
  410. package/core/commands/mednotes/process-chats.toml +20 -0
  411. package/core/commands/mednotes/setup.toml +18 -0
  412. package/core/commands/mednotes/status.toml +24 -0
  413. package/core/commands/mednotes/telemetry.toml +24 -0
  414. package/core/commands/report.toml +23 -0
  415. package/core/skills/THIRD_PARTY_NOTICES.md +45 -0
  416. package/core/skills/create-medical-flashcards/SKILL.md +113 -0
  417. package/core/skills/create-medical-note/SKILL.md +90 -0
  418. package/core/skills/enrich-medical-note/SKILL.md +120 -0
  419. package/core/skills/fix-medical-wiki/SKILL.md +559 -0
  420. package/core/skills/link-medical-wiki/SKILL.md +224 -0
  421. package/core/skills/obsidian-cli/SKILL.md +118 -0
  422. package/core/skills/obsidian-markdown/SKILL.md +207 -0
  423. package/core/skills/obsidian-markdown/references/CALLOUTS.md +58 -0
  424. package/core/skills/obsidian-markdown/references/EMBEDS.md +63 -0
  425. package/core/skills/obsidian-markdown/references/PROPERTIES.md +61 -0
  426. package/core/skills/obsidian-ops/SKILL.md +136 -0
  427. package/core/skills/pdf-library/SKILL.md +45 -0
  428. package/core/skills/process-medical-chats/SKILL.md +246 -0
  429. package/core/skills/workflow-report/SKILL.md +100 -0
  430. package/package.json +45 -0
@@ -0,0 +1,1588 @@
1
+ """Flat StateChart for `/mednotes:fix-wiki`.
2
+
3
+ The first fix-wiki StateChart is intentionally flat: every operational lane is a
4
+ leaf state, and `category_for_state()` derives the public category. That keeps
5
+ diagnosis, blockers, agent handoff, link routing and final validation observable
6
+ without introducing generic container states as a second source of truth.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from typing import Annotated, Literal
12
+
13
+ from pydantic import Field, TypeAdapter, field_validator, model_validator
14
+ from statemachine import StateChart
15
+ from statemachine.states import States
16
+
17
+ from mednotes.domains.wiki.contracts.effect_payloads import (
18
+ LinkWorkflowRunEffectPayload,
19
+ RelatedNotesExportEffectPayload,
20
+ RelatedNotesRecoveryStateEffectPayload,
21
+ WaitExternalEffectPayload,
22
+ )
23
+ from mednotes.domains.wiki.contracts.workflow_outcomes import (
24
+ DecisionEvidence,
25
+ HumanDecisionOption,
26
+ RejectedAutomation,
27
+ WorkflowDecision,
28
+ )
29
+ from mednotes.domains.wiki.flows.fix_wiki.fix_wiki_states import (
30
+ FIX_WIKI_DIAGNOSIS_PRIORITY,
31
+ FIX_WIKI_WORKFLOW,
32
+ FixWikiDiagnosisLane,
33
+ FixWikiState,
34
+ reason_for_state,
35
+ )
36
+ from mednotes.domains.wiki.flows.fix_wiki.fix_wiki_states import (
37
+ category_for_state as fix_wiki_category_for_state,
38
+ )
39
+ from mednotes.kernel.base import ContractModel, JsonObject
40
+ from mednotes.kernel.effects import WorkflowEffect, WorkflowEffectKind
41
+ from mednotes.kernel.fsm_model import WorkflowModel
42
+ from mednotes.kernel.fsm_transition_result import WorkflowTransitionResult
43
+ from mednotes.kernel.state_machine import WorkflowStateCategory
44
+ from mednotes.kernel.workflow import HumanDecisionPacket
45
+
46
+
47
+ class FixWikiEvent(ContractModel):
48
+ """Base event for fix-wiki facts accepted by the StateChart."""
49
+
50
+ workflow: str = FIX_WIKI_WORKFLOW
51
+ run_id: str = Field(min_length=1)
52
+ current_state: str = Field(min_length=1)
53
+ audit_evidence: JsonObject = Field(default_factory=dict)
54
+
55
+ @field_validator("workflow")
56
+ @classmethod
57
+ def _workflow_must_be_fix_wiki(cls, value: str) -> str:
58
+ if value != FIX_WIKI_WORKFLOW:
59
+ raise ValueError(f"fix-wiki event workflow must be {FIX_WIKI_WORKFLOW}")
60
+ return value
61
+
62
+
63
+ def _event_name(event: FixWikiEvent) -> str:
64
+ """Return the concrete Literal discriminator declared by each event class."""
65
+
66
+ name = getattr(event, "name", "")
67
+ if not isinstance(name, str) or not name.strip():
68
+ raise ValueError("fix-wiki events must declare a name discriminator")
69
+ return name
70
+
71
+
72
+ class DiagnosisQueueEvent(FixWikiEvent):
73
+ """Canonical diagnosis queue; concrete events supply the trigger name."""
74
+
75
+ pending_lanes: list[FixWikiDiagnosisLane] = Field(min_length=1)
76
+ selected_lane: FixWikiDiagnosisLane
77
+ style_rewrite_effect: WorkflowEffect | None = None
78
+
79
+ @field_validator("pending_lanes")
80
+ @classmethod
81
+ def _reject_duplicate_lanes(cls, value: list[FixWikiDiagnosisLane]) -> list[FixWikiDiagnosisLane]:
82
+ if len(set(value)) != len(value):
83
+ raise ValueError("duplicate diagnosis lanes are not allowed")
84
+ expected_order = sorted(value, key=FIX_WIKI_DIAGNOSIS_PRIORITY.index)
85
+ if list(value) != expected_order:
86
+ raise ValueError("diagnosis lanes must follow FIX_WIKI_DIAGNOSIS_PRIORITY")
87
+ return value
88
+
89
+ @model_validator(mode="after")
90
+ def _selected_lane_must_be_priority_head(self) -> DiagnosisQueueEvent:
91
+ if self.selected_lane not in self.pending_lanes:
92
+ raise ValueError("selected_lane must be present in pending_lanes")
93
+ expected = self.pending_lanes[0]
94
+ if self.selected_lane != expected:
95
+ raise ValueError("selected_lane must match the highest-priority pending lane")
96
+ if self.style_rewrite_effect is not None:
97
+ if self.selected_lane != FixWikiDiagnosisLane.STYLE_REWRITE:
98
+ raise ValueError("style_rewrite_effect is only valid for the style rewrite lane")
99
+ if self.style_rewrite_effect.kind != WorkflowEffectKind.CALL_SPECIALIST_MODEL:
100
+ raise ValueError("style_rewrite_effect must be a specialist model effect")
101
+ if self.style_rewrite_effect.origin_state != FixWikiState.STYLE_REWRITE_SPECIALIST_REQUESTED.value:
102
+ raise ValueError("style_rewrite_effect origin_state must match style_rewrite.specialist_requested")
103
+ return self
104
+
105
+
106
+ class DiagnosisProducedEvent(DiagnosisQueueEvent):
107
+ """Typed diagnosis fan-out; guards must inspect only `selected_lane`."""
108
+
109
+ name: Literal["diagnosis_produced"] = "diagnosis_produced"
110
+
111
+
112
+ class FixWikiRuntimeObservation(ContractModel):
113
+ """Runtime facts normalized at the boundary; StateChart guards choose state."""
114
+
115
+ failed: bool = False
116
+ failed_reason_code: str = ""
117
+ vault_guard_required: bool = False
118
+ environment_windows_path_or_venv_blocked: bool = False
119
+ next_action: str = ""
120
+ human_decision_required: bool = False
121
+ external_wait_reason_code: str = ""
122
+ related_notes_waiting_external: bool = False
123
+ vocabulary_semantic_ingestion_pending: bool = False
124
+ vocabulary_eval_needs_review: bool = False
125
+ atomicity_split_required: bool = False
126
+ merge_review_required: bool = False
127
+ graph_review_required: bool = False
128
+ graph_blocker_count: int = Field(default=0, ge=0)
129
+ graph_error_count: int = Field(default=0, ge=0)
130
+ related_notes_blocked: bool = False
131
+ linker_blocked: bool = False
132
+ taxonomy_action_required: bool = False
133
+ specialist_model_waiting_agent: bool = False
134
+ requires_llm_rewrite_count: int = Field(default=0, ge=0)
135
+ effective_apply: bool = False
136
+ warning_count: int = Field(default=0, ge=0)
137
+ style_rewrite_effect: WorkflowEffect | None = None
138
+ link_subworkflow_required: bool = False
139
+ link_effect: WorkflowEffect | None = None
140
+ related_notes_recovery_state: RelatedNotesRecoveryStateEffectPayload = Field(
141
+ default_factory=RelatedNotesRecoveryStateEffectPayload
142
+ )
143
+
144
+ @model_validator(mode="after")
145
+ def _style_effect_matches_observed_lane(self) -> FixWikiRuntimeObservation:
146
+ if self.style_rewrite_effect is not None:
147
+ if not self.specialist_model_waiting_agent:
148
+ raise ValueError("style_rewrite_effect requires specialist_model_waiting_agent")
149
+ if self.style_rewrite_effect.kind != WorkflowEffectKind.CALL_SPECIALIST_MODEL:
150
+ raise ValueError("style_rewrite_effect must be a specialist model effect")
151
+ if self.style_rewrite_effect.origin_state != FixWikiState.STYLE_REWRITE_SPECIALIST_REQUESTED.value:
152
+ raise ValueError("style_rewrite_effect origin_state must match style_rewrite.specialist_requested")
153
+ if self.link_effect is not None and not self.link_subworkflow_required:
154
+ raise ValueError("link_effect requires link_subworkflow_required")
155
+ if self.link_subworkflow_required and self.link_effect is None:
156
+ raise ValueError("link_subworkflow_required requires link_effect")
157
+ if self.link_effect is not None:
158
+ if self.link_effect.kind != WorkflowEffectKind.RUN_SUBWORKFLOW:
159
+ raise ValueError("link_effect must be a run_subworkflow effect")
160
+ if self.link_effect.target != "/mednotes:link":
161
+ raise ValueError("link_effect target must be /mednotes:link")
162
+ if self.link_effect.origin_state != FixWikiState.LINK_RUN_REQUESTED.value:
163
+ raise ValueError("link_effect origin_state must match link.run_requested")
164
+ return self
165
+
166
+
167
+ class RuntimeObservedEvent(FixWikiEvent):
168
+ """Single adapter event; StateChart guards own priority and leaf selection."""
169
+
170
+ name: Literal["runtime_observed"] = "runtime_observed"
171
+ observation: FixWikiRuntimeObservation
172
+
173
+
174
+ class DiagnosisCleanEvent(FixWikiEvent):
175
+ """Clean diagnosis path; no pending lane is fabricated for final validation."""
176
+ name: Literal["diagnosis_clean"] = "diagnosis_clean"
177
+
178
+
179
+ class BlockerRetryRequestedEvent(FixWikiEvent):
180
+ """Recoverable blocker retry; the next step is a fresh diagnosis."""
181
+ name: Literal["blocker_retry_requested"] = "blocker_retry_requested"
182
+ reason_code: str = "retry_requested"
183
+
184
+
185
+ class SetupBootstrapReadyEvent(FixWikiEvent):
186
+ name: Literal["setup_bootstrap_ready"] = "setup_bootstrap_ready"
187
+
188
+
189
+ class SetupBootstrapBlockedEvent(FixWikiEvent):
190
+ name: Literal["setup_bootstrap_blocked"] = "setup_bootstrap_blocked"
191
+
192
+
193
+ class VaultGuardReadyEvent(FixWikiEvent):
194
+ name: Literal["vault_guard_ready"] = "vault_guard_ready"
195
+
196
+
197
+ class VaultGuardDecisionApprovedEvent(FixWikiEvent):
198
+ name: Literal["vault_guard_decision_approved"] = "vault_guard_decision_approved"
199
+
200
+
201
+ class VaultGuardBlockedEvent(FixWikiEvent):
202
+ name: Literal["vault_guard_blocked"] = "vault_guard_blocked"
203
+
204
+
205
+ class DeterministicRepairsAppliedEvent(FixWikiEvent):
206
+ name: Literal["deterministic_repairs_applied"] = "deterministic_repairs_applied"
207
+
208
+
209
+ class DeterministicRepairsBlockedEvent(FixWikiEvent):
210
+ name: Literal["deterministic_repairs_blocked"] = "deterministic_repairs_blocked"
211
+
212
+
213
+ class StyleRewriteSpecialistCompletedEvent(FixWikiEvent):
214
+ name: Literal["style_rewrite_specialist_completed"] = "style_rewrite_specialist_completed"
215
+
216
+
217
+ class StyleRewriteCapacityWaitEvent(FixWikiEvent):
218
+ name: Literal["style_rewrite_capacity_wait"] = "style_rewrite_capacity_wait"
219
+
220
+
221
+ class StyleRewriteReviewRequiredEvent(FixWikiEvent):
222
+ name: Literal["style_rewrite_review_required"] = "style_rewrite_review_required"
223
+
224
+
225
+ class StyleRewriteReviewApprovedEvent(FixWikiEvent):
226
+ name: Literal["style_rewrite_review_approved"] = "style_rewrite_review_approved"
227
+
228
+
229
+ class StyleRewriteAppliedEvent(FixWikiEvent):
230
+ name: Literal["style_rewrite_applied"] = "style_rewrite_applied"
231
+
232
+
233
+ class StyleRewriteBlockedEvent(FixWikiEvent):
234
+ name: Literal["style_rewrite_blocked"] = "style_rewrite_blocked"
235
+
236
+
237
+ class TaxonomyDecisionRequiredEvent(FixWikiEvent):
238
+ name: Literal["taxonomy_decision_required"] = "taxonomy_decision_required"
239
+
240
+
241
+ class TaxonomyDecisionApprovedEvent(FixWikiEvent):
242
+ name: Literal["taxonomy_decision_approved"] = "taxonomy_decision_approved"
243
+
244
+
245
+ class TaxonomyAppliedEvent(FixWikiEvent):
246
+ name: Literal["taxonomy_applied"] = "taxonomy_applied"
247
+
248
+
249
+ class TaxonomyBlockedEvent(FixWikiEvent):
250
+ name: Literal["taxonomy_blocked"] = "taxonomy_blocked"
251
+
252
+
253
+ class VocabularyCuratorCompletedEvent(FixWikiEvent):
254
+ name: Literal["vocabulary_curator_completed"] = "vocabulary_curator_completed"
255
+
256
+
257
+ class VocabularyEvalNeedsReviewEvent(FixWikiEvent):
258
+ name: Literal["vocabulary_eval_needs_review"] = "vocabulary_eval_needs_review"
259
+
260
+
261
+ class VocabularyEvalPassedEvent(FixWikiEvent):
262
+ name: Literal["vocabulary_eval_passed"] = "vocabulary_eval_passed"
263
+
264
+
265
+ class VocabularyAppliedEvent(FixWikiEvent):
266
+ name: Literal["vocabulary_applied"] = "vocabulary_applied"
267
+
268
+
269
+ class VocabularyIntegrityFailedEvent(FixWikiEvent):
270
+ name: Literal["vocabulary_integrity_failed"] = "vocabulary_integrity_failed"
271
+
272
+
273
+ class AtomicitySplitAppliedEvent(FixWikiEvent):
274
+ name: Literal["atomicity_split_applied"] = "atomicity_split_applied"
275
+
276
+
277
+ class AtomicitySplitBlockedEvent(FixWikiEvent):
278
+ name: Literal["atomicity_split_blocked"] = "atomicity_split_blocked"
279
+
280
+
281
+ class MergeAppliedEvent(FixWikiEvent):
282
+ name: Literal["merge_applied"] = "merge_applied"
283
+
284
+
285
+ class MergeBlockedEvent(FixWikiEvent):
286
+ name: Literal["merge_blocked"] = "merge_blocked"
287
+
288
+
289
+ class RelatedNotesExportCompletedEvent(FixWikiEvent):
290
+ name: Literal["related_notes_export_completed"] = "related_notes_export_completed"
291
+
292
+
293
+ class RelatedNotesQuotaWaitEvent(FixWikiEvent):
294
+ name: Literal["related_notes_quota_wait"] = "related_notes_quota_wait"
295
+ related_notes_recovery_state: RelatedNotesRecoveryStateEffectPayload = Field(
296
+ default_factory=RelatedNotesRecoveryStateEffectPayload
297
+ )
298
+
299
+ @field_validator("related_notes_recovery_state", mode="before")
300
+ @classmethod
301
+ def _coerce_related_notes_recovery_state(cls, value: object) -> RelatedNotesRecoveryStateEffectPayload:
302
+ return RelatedNotesRecoveryStateEffectPayload.from_payload(value)
303
+
304
+
305
+ class RelatedNotesObsidianNotReadyEvent(FixWikiEvent):
306
+ name: Literal["related_notes_obsidian_not_ready"] = "related_notes_obsidian_not_ready"
307
+
308
+
309
+ class RelatedNotesBlockedEvent(FixWikiEvent):
310
+ name: Literal["related_notes_blocked"] = "related_notes_blocked"
311
+
312
+
313
+ class LinkCompletedEvent(FixWikiEvent):
314
+ name: Literal["link_completed"] = "link_completed"
315
+
316
+
317
+ class LinkGraphBlockedEvent(FixWikiEvent):
318
+ name: Literal["link_graph_blocked"] = "link_graph_blocked"
319
+
320
+
321
+ class LinkerBlockedEvent(FixWikiEvent):
322
+ name: Literal["linker_blocked"] = "linker_blocked"
323
+
324
+
325
+ class FinalValidationPassedEvent(FixWikiEvent):
326
+ name: Literal["final_validation_passed"] = "final_validation_passed"
327
+
328
+
329
+ class PreviewReadyEvent(FixWikiEvent):
330
+ name: Literal["preview_ready"] = "preview_ready"
331
+
332
+
333
+ class FinalValidationWarningsEvent(FixWikiEvent):
334
+ name: Literal["final_validation_warnings"] = "final_validation_warnings"
335
+
336
+
337
+ class FinalValidationFoundMoreWorkEvent(DiagnosisQueueEvent):
338
+ name: Literal["final_validation_found_more_work"] = "final_validation_found_more_work"
339
+
340
+
341
+ class FinalValidationFailedEvent(FixWikiEvent):
342
+ name: Literal["final_validation_failed"] = "final_validation_failed"
343
+
344
+
345
+ class RollbackCompletedEvent(FixWikiEvent):
346
+ name: Literal["rollback_completed"] = "rollback_completed"
347
+
348
+
349
+ class RollbackFailedEvent(FixWikiEvent):
350
+ name: Literal["rollback_failed"] = "rollback_failed"
351
+
352
+
353
+ FixWikiBoundaryEvent = Annotated[
354
+ DiagnosisProducedEvent
355
+ | RuntimeObservedEvent
356
+ | DiagnosisCleanEvent
357
+ | BlockerRetryRequestedEvent
358
+ | SetupBootstrapReadyEvent
359
+ | SetupBootstrapBlockedEvent
360
+ | VaultGuardReadyEvent
361
+ | VaultGuardDecisionApprovedEvent
362
+ | VaultGuardBlockedEvent
363
+ | DeterministicRepairsAppliedEvent
364
+ | DeterministicRepairsBlockedEvent
365
+ | StyleRewriteSpecialistCompletedEvent
366
+ | StyleRewriteCapacityWaitEvent
367
+ | StyleRewriteReviewRequiredEvent
368
+ | StyleRewriteReviewApprovedEvent
369
+ | StyleRewriteAppliedEvent
370
+ | StyleRewriteBlockedEvent
371
+ | TaxonomyDecisionRequiredEvent
372
+ | TaxonomyDecisionApprovedEvent
373
+ | TaxonomyAppliedEvent
374
+ | TaxonomyBlockedEvent
375
+ | VocabularyCuratorCompletedEvent
376
+ | VocabularyEvalNeedsReviewEvent
377
+ | VocabularyEvalPassedEvent
378
+ | VocabularyAppliedEvent
379
+ | VocabularyIntegrityFailedEvent
380
+ | AtomicitySplitAppliedEvent
381
+ | AtomicitySplitBlockedEvent
382
+ | MergeAppliedEvent
383
+ | MergeBlockedEvent
384
+ | RelatedNotesExportCompletedEvent
385
+ | RelatedNotesQuotaWaitEvent
386
+ | RelatedNotesObsidianNotReadyEvent
387
+ | RelatedNotesBlockedEvent
388
+ | LinkCompletedEvent
389
+ | LinkGraphBlockedEvent
390
+ | LinkerBlockedEvent
391
+ | FinalValidationPassedEvent
392
+ | PreviewReadyEvent
393
+ | FinalValidationWarningsEvent
394
+ | FinalValidationFoundMoreWorkEvent
395
+ | FinalValidationFailedEvent
396
+ | RollbackCompletedEvent
397
+ | RollbackFailedEvent,
398
+ Field(discriminator="name"),
399
+ ]
400
+ FixWikiBoundaryEventAdapter = TypeAdapter(FixWikiBoundaryEvent)
401
+
402
+ FIX_WIKI_BOUNDARY_EVENT_NAMES = frozenset(
403
+ FixWikiBoundaryEventAdapter.json_schema()["discriminator"]["mapping"]
404
+ )
405
+
406
+
407
+ class FixWikiMachine(StateChart[WorkflowModel]):
408
+ """Pure domain StateChart; callbacks only return typed transition results."""
409
+
410
+ allow_event_without_transition = False
411
+ catch_errors_as_events = False
412
+ states = States.from_enum(
413
+ FixWikiState,
414
+ initial=FixWikiState.DIAGNOSIS_RUNNING,
415
+ final={
416
+ FixWikiState.AGENT_TOOL_CONTRACT_VIOLATION,
417
+ FixWikiState.CONTRACT_GAP_MISSING_NEXT_ACTION,
418
+ FixWikiState.CONTRACT_GAP_MISSING_ERROR_CONTEXT,
419
+ FixWikiState.DETERMINISTIC_REPAIRS_FAILED,
420
+ FixWikiState.VOCABULARY_SQLITE_INTEGRITY_FAILED,
421
+ FixWikiState.ROLLBACK_PERFORMED,
422
+ FixWikiState.FINAL_VALIDATION_FAILED,
423
+ FixWikiState.FAILED,
424
+ FixWikiState.PREVIEW_READY,
425
+ FixWikiState.COMPLETED,
426
+ FixWikiState.COMPLETED_WITH_WARNINGS,
427
+ },
428
+ use_enum_instance=False,
429
+ )
430
+
431
+ diagnosis_clean = states.DIAGNOSIS_RUNNING.to(
432
+ states.FINAL_VALIDATION_RUNNING,
433
+ on="_on_enter_final_validation",
434
+ )
435
+
436
+ diagnosis_produced = (
437
+ states.DIAGNOSIS_RUNNING.to(
438
+ states.ENVIRONMENT_PATHS_MISSING,
439
+ cond="_selected_environment_paths_missing",
440
+ on="_on_diagnosis_produced",
441
+ )
442
+ | states.DIAGNOSIS_RUNNING.to(
443
+ states.ENVIRONMENT_WIKI_DIR_MISSING,
444
+ cond="_selected_environment_wiki_dir_missing",
445
+ on="_on_diagnosis_produced",
446
+ )
447
+ | states.DIAGNOSIS_RUNNING.to(
448
+ states.ENVIRONMENT_WINDOWS_PATH_OR_VENV_BLOCKED,
449
+ cond="_selected_environment_windows_path_or_venv_blocked",
450
+ on="_on_diagnosis_produced",
451
+ )
452
+ | states.DIAGNOSIS_RUNNING.to(
453
+ states.VAULT_GUARD_DECISION_REQUIRED,
454
+ cond="_selected_vault_guard_decision_required",
455
+ on="_on_diagnosis_produced",
456
+ )
457
+ | states.DIAGNOSIS_RUNNING.to(
458
+ states.SUBAGENT_PLAN_ATTESTATION_REQUIRED,
459
+ cond="_selected_subagent_plan_attestation_required",
460
+ on="_on_diagnosis_produced",
461
+ )
462
+ | states.DIAGNOSIS_RUNNING.to(
463
+ states.SUBAGENT_PLAN_ATTESTATION_INVALID,
464
+ cond="_selected_subagent_plan_attestation_invalid",
465
+ on="_on_diagnosis_produced",
466
+ )
467
+ | states.DIAGNOSIS_RUNNING.to(
468
+ states.AGENT_TOOL_CONTRACT_VIOLATION,
469
+ cond="_selected_agent_tool_contract_violation",
470
+ on="_on_diagnosis_produced",
471
+ )
472
+ | states.DIAGNOSIS_RUNNING.to(
473
+ states.DETERMINISTIC_REPAIRS_RUNNING,
474
+ cond="_selected_deterministic_repairs",
475
+ on="_on_diagnosis_produced",
476
+ )
477
+ | states.DIAGNOSIS_RUNNING.to(
478
+ states.STYLE_REWRITE_SPECIALIST_REQUESTED,
479
+ cond="_selected_style_rewrite",
480
+ on="_on_diagnosis_produced",
481
+ )
482
+ | states.DIAGNOSIS_RUNNING.to(
483
+ states.TAXONOMY_DECISION_REQUIRED,
484
+ cond="_selected_taxonomy",
485
+ on="_on_diagnosis_produced",
486
+ )
487
+ | states.DIAGNOSIS_RUNNING.to(
488
+ states.VOCABULARY_SEMANTIC_INGESTION_PENDING,
489
+ cond="_selected_vocabulary_semantic_ingestion_pending",
490
+ on="_on_diagnosis_produced",
491
+ )
492
+ | states.DIAGNOSIS_RUNNING.to(
493
+ states.VOCABULARY_CURATOR_RUNNING,
494
+ cond="_selected_vocabulary",
495
+ on="_on_diagnosis_produced",
496
+ )
497
+ | states.DIAGNOSIS_RUNNING.to(
498
+ states.ATOMICITY_SPLIT_RUNNING,
499
+ cond="_selected_atomicity_split",
500
+ on="_on_diagnosis_produced",
501
+ )
502
+ | states.DIAGNOSIS_RUNNING.to(
503
+ states.MERGE_RUNNING,
504
+ cond="_selected_merge",
505
+ on="_on_diagnosis_produced",
506
+ )
507
+ | states.DIAGNOSIS_RUNNING.to(
508
+ states.RELATED_NOTES_EXPORT_RUNNING,
509
+ cond="_selected_related_notes",
510
+ on="_on_diagnosis_produced",
511
+ )
512
+ | states.DIAGNOSIS_RUNNING.to(
513
+ states.LINK_RUN_REQUESTED,
514
+ cond="_selected_link",
515
+ on="_on_diagnosis_produced",
516
+ )
517
+ | states.DIAGNOSIS_RUNNING.to(
518
+ states.CONTRACT_GAP_MISSING_NEXT_ACTION,
519
+ cond="_selected_contract_gap_missing_next_action",
520
+ on="_on_diagnosis_produced",
521
+ )
522
+ | states.DIAGNOSIS_RUNNING.to(
523
+ states.CONTRACT_GAP_MISSING_ERROR_CONTEXT,
524
+ cond="_selected_contract_gap_missing_error_context",
525
+ on="_on_diagnosis_produced",
526
+ )
527
+ | states.DIAGNOSIS_RUNNING.to(
528
+ states.ROLLBACK_RUNNING,
529
+ cond="_selected_rollback",
530
+ on="_on_diagnosis_produced",
531
+ )
532
+ | states.DIAGNOSIS_RUNNING.to(
533
+ states.FINAL_VALIDATION_RUNNING,
534
+ cond="_selected_final_validation",
535
+ on="_on_diagnosis_produced",
536
+ )
537
+ )
538
+
539
+ runtime_observed = (
540
+ states.DIAGNOSIS_RUNNING.to(
541
+ states.VAULT_GUARD_DECISION_REQUIRED,
542
+ cond="_observed_vault_guard_required",
543
+ on="_on_blocked",
544
+ )
545
+ | states.DIAGNOSIS_RUNNING.to(
546
+ states.ENVIRONMENT_WINDOWS_PATH_OR_VENV_BLOCKED,
547
+ cond="_observed_environment_blocked",
548
+ on="_on_blocked",
549
+ )
550
+ | states.DIAGNOSIS_RUNNING.to(
551
+ states.FAILED,
552
+ cond="_observed_failed",
553
+ on="_on_failed",
554
+ )
555
+ | states.DIAGNOSIS_RUNNING.to(
556
+ states.RELATED_NOTES_QUOTA_WAIT,
557
+ cond="_observed_related_notes_quota_wait",
558
+ on="_on_wait_external",
559
+ )
560
+ | states.DIAGNOSIS_RUNNING.to(
561
+ states.STYLE_REWRITE_CAPACITY_WAIT,
562
+ cond="_observed_style_rewrite_capacity_wait",
563
+ on="_on_wait_external",
564
+ )
565
+ | states.DIAGNOSIS_RUNNING.to(
566
+ states.VOCABULARY_SEMANTIC_INGESTION_PENDING,
567
+ cond="_observed_vocabulary_semantic_ingestion_pending",
568
+ on="_on_runtime_observed",
569
+ )
570
+ | states.DIAGNOSIS_RUNNING.to(
571
+ states.VOCABULARY_EVAL_NEEDS_REVIEW,
572
+ cond="_observed_vocabulary_eval_needs_review",
573
+ on="_on_human_review",
574
+ )
575
+ | states.DIAGNOSIS_RUNNING.to(
576
+ states.ATOMICITY_SPLIT_REVIEW_REQUIRED,
577
+ cond="_observed_atomicity_split_required",
578
+ on="_on_human_review",
579
+ )
580
+ | states.DIAGNOSIS_RUNNING.to(
581
+ states.MERGE_REVIEW_REQUIRED,
582
+ cond="_observed_merge_review_required",
583
+ on="_on_human_review",
584
+ )
585
+ | states.DIAGNOSIS_RUNNING.to(
586
+ states.LINK_GRAPH_REVIEW_REQUIRED,
587
+ cond="_observed_graph_review_required",
588
+ on="_on_human_review",
589
+ )
590
+ | states.DIAGNOSIS_RUNNING.to(
591
+ states.LINK_GRAPH_BLOCKED,
592
+ cond="_observed_graph_blocked",
593
+ on="_on_blocked",
594
+ )
595
+ | states.DIAGNOSIS_RUNNING.to(
596
+ states.TAXONOMY_DECISION_REQUIRED,
597
+ cond="_observed_taxonomy_action_required",
598
+ on="_on_human_review",
599
+ )
600
+ | states.DIAGNOSIS_RUNNING.to(
601
+ states.RELATED_NOTES_BLOCKED,
602
+ cond="_observed_related_notes_blocked",
603
+ on="_on_blocked",
604
+ )
605
+ | states.DIAGNOSIS_RUNNING.to(
606
+ states.LINKER_BLOCKED,
607
+ cond="_observed_linker_blocked",
608
+ on="_on_blocked",
609
+ )
610
+ | states.DIAGNOSIS_RUNNING.to(
611
+ states.STYLE_REWRITE_SPECIALIST_REQUESTED,
612
+ cond="_observed_specialist_model_waiting_agent",
613
+ on="_on_runtime_observed",
614
+ )
615
+ | states.DIAGNOSIS_RUNNING.to(
616
+ states.STYLE_REWRITE_REVIEW_REQUIRED,
617
+ cond="_observed_style_rewrite_review_required",
618
+ on="_on_human_review",
619
+ )
620
+ | states.DIAGNOSIS_RUNNING.to(
621
+ states.CONTRACT_GAP_MISSING_ERROR_CONTEXT,
622
+ cond="_observed_human_decision_contract_gap",
623
+ on="_on_failed",
624
+ )
625
+ | states.DIAGNOSIS_RUNNING.to(
626
+ states.LINK_RUN_REQUESTED,
627
+ cond="_observed_link_subworkflow_required",
628
+ on="_on_runtime_observed",
629
+ )
630
+ | states.DIAGNOSIS_RUNNING.to(
631
+ states.PREVIEW_READY,
632
+ cond="_observed_preview_ready",
633
+ on="_on_completed",
634
+ )
635
+ | states.DIAGNOSIS_RUNNING.to(
636
+ states.COMPLETED_WITH_WARNINGS,
637
+ cond="_observed_completed_with_warnings",
638
+ on="_on_completed",
639
+ )
640
+ | states.DIAGNOSIS_RUNNING.to(
641
+ states.COMPLETED,
642
+ cond="_observed_completed",
643
+ on="_on_completed",
644
+ )
645
+ )
646
+
647
+ blocker_retry_requested = (
648
+ states.ENVIRONMENT_PATHS_MISSING.to(states.DIAGNOSIS_RUNNING, on="_on_resume_diagnosis")
649
+ | states.ENVIRONMENT_WIKI_DIR_MISSING.to(states.DIAGNOSIS_RUNNING, on="_on_resume_diagnosis")
650
+ | states.ENVIRONMENT_WINDOWS_PATH_OR_VENV_BLOCKED.to(
651
+ states.DIAGNOSIS_RUNNING,
652
+ on="_on_resume_diagnosis",
653
+ )
654
+ | states.VAULT_GUARD_DECISION_REQUIRED.to(states.DIAGNOSIS_RUNNING, on="_on_resume_diagnosis")
655
+ | states.SUBAGENT_PLAN_ATTESTATION_REQUIRED.to(states.DIAGNOSIS_RUNNING, on="_on_resume_diagnosis")
656
+ | states.SUBAGENT_PLAN_ATTESTATION_INVALID.to(states.DIAGNOSIS_RUNNING, on="_on_resume_diagnosis")
657
+ | states.STYLE_REWRITE_CAPACITY_WAIT.to(states.DIAGNOSIS_RUNNING, on="_on_resume_diagnosis")
658
+ | states.STYLE_REWRITE_REVIEW_REQUIRED.to(states.DIAGNOSIS_RUNNING, on="_on_resume_diagnosis")
659
+ | states.TAXONOMY_DECISION_REQUIRED.to(states.DIAGNOSIS_RUNNING, on="_on_resume_diagnosis")
660
+ | states.VOCABULARY_SEMANTIC_INGESTION_PENDING.to(states.DIAGNOSIS_RUNNING, on="_on_resume_diagnosis")
661
+ | states.VOCABULARY_EVAL_NEEDS_REVIEW.to(states.DIAGNOSIS_RUNNING, on="_on_resume_diagnosis")
662
+ | states.ATOMICITY_SPLIT_REVIEW_REQUIRED.to(states.DIAGNOSIS_RUNNING, on="_on_resume_diagnosis")
663
+ | states.RELATED_NOTES_QUOTA_WAIT.to(states.DIAGNOSIS_RUNNING, on="_on_resume_diagnosis")
664
+ | states.RELATED_NOTES_OBSIDIAN_NOT_READY.to(states.DIAGNOSIS_RUNNING, on="_on_resume_diagnosis")
665
+ | states.RELATED_NOTES_BLOCKED.to(states.DIAGNOSIS_RUNNING, on="_on_resume_diagnosis")
666
+ | states.LINK_GRAPH_REVIEW_REQUIRED.to(states.DIAGNOSIS_RUNNING, on="_on_resume_diagnosis")
667
+ | states.LINK_GRAPH_BLOCKED.to(states.DIAGNOSIS_RUNNING, on="_on_resume_diagnosis")
668
+ | states.LINKER_BLOCKED.to(states.DIAGNOSIS_RUNNING, on="_on_resume_diagnosis")
669
+ | states.MERGE_REVIEW_REQUIRED.to(states.DIAGNOSIS_RUNNING, on="_on_resume_diagnosis")
670
+ | states.ROLLBACK_FAILED.to(states.DIAGNOSIS_RUNNING, on="_on_resume_diagnosis")
671
+ )
672
+
673
+ setup_bootstrap_ready = (
674
+ states.ENVIRONMENT_PATHS_MISSING.to(states.DIAGNOSIS_RUNNING, on="_on_resume_diagnosis")
675
+ | states.ENVIRONMENT_WIKI_DIR_MISSING.to(states.DIAGNOSIS_RUNNING, on="_on_resume_diagnosis")
676
+ | states.ENVIRONMENT_WINDOWS_PATH_OR_VENV_BLOCKED.to(
677
+ states.DIAGNOSIS_RUNNING,
678
+ on="_on_resume_diagnosis",
679
+ )
680
+ )
681
+ setup_bootstrap_blocked = (
682
+ states.ENVIRONMENT_PATHS_MISSING.to(states.FAILED, on="_on_failed")
683
+ | states.ENVIRONMENT_WIKI_DIR_MISSING.to(states.FAILED, on="_on_failed")
684
+ | states.ENVIRONMENT_WINDOWS_PATH_OR_VENV_BLOCKED.to(states.FAILED, on="_on_failed")
685
+ )
686
+ vault_guard_decision_approved = states.VAULT_GUARD_DECISION_REQUIRED.to(
687
+ states.VAULT_GUARD_RUNNING,
688
+ on="_on_vault_guard_run",
689
+ )
690
+ vault_guard_ready = states.VAULT_GUARD_RUNNING.to(states.DIAGNOSIS_RUNNING, on="_on_resume_diagnosis")
691
+ vault_guard_blocked = states.VAULT_GUARD_RUNNING.to(
692
+ states.VAULT_GUARD_DECISION_REQUIRED,
693
+ on="_on_blocked",
694
+ )
695
+ deterministic_repairs_applied = states.DETERMINISTIC_REPAIRS_RUNNING.to(
696
+ states.LINK_RUN_REQUESTED,
697
+ on="_on_route_link",
698
+ )
699
+ deterministic_repairs_blocked = states.DETERMINISTIC_REPAIRS_RUNNING.to(
700
+ states.DETERMINISTIC_REPAIRS_FAILED,
701
+ on="_on_failed",
702
+ )
703
+ style_rewrite_specialist_completed = states.STYLE_REWRITE_SPECIALIST_REQUESTED.to(
704
+ states.STYLE_REWRITE_REVIEW_REQUIRED,
705
+ on="_on_human_review",
706
+ )
707
+ style_rewrite_capacity_wait = states.STYLE_REWRITE_SPECIALIST_REQUESTED.to(
708
+ states.STYLE_REWRITE_CAPACITY_WAIT,
709
+ on="_on_wait_external",
710
+ )
711
+ style_rewrite_review_required = states.STYLE_REWRITE_SPECIALIST_REQUESTED.to(
712
+ states.STYLE_REWRITE_REVIEW_REQUIRED,
713
+ on="_on_human_review",
714
+ )
715
+ style_rewrite_review_approved = states.STYLE_REWRITE_REVIEW_REQUIRED.to(
716
+ states.STYLE_REWRITE_APPLY_RUNNING,
717
+ on="_on_style_rewrite_apply",
718
+ )
719
+ style_rewrite_applied = states.STYLE_REWRITE_APPLY_RUNNING.to(states.LINK_RUN_REQUESTED, on="_on_route_link")
720
+ style_rewrite_blocked = states.STYLE_REWRITE_APPLY_RUNNING.to(states.FAILED, on="_on_failed")
721
+ taxonomy_decision_required = states.TAXONOMY_APPLY_RUNNING.to(
722
+ states.TAXONOMY_DECISION_REQUIRED,
723
+ on="_on_human_review",
724
+ )
725
+ taxonomy_decision_approved = states.TAXONOMY_DECISION_REQUIRED.to(
726
+ states.TAXONOMY_APPLY_RUNNING,
727
+ on="_on_taxonomy_apply",
728
+ )
729
+ taxonomy_applied = states.TAXONOMY_APPLY_RUNNING.to(states.LINK_RUN_REQUESTED, on="_on_route_link")
730
+ taxonomy_blocked = states.TAXONOMY_APPLY_RUNNING.to(
731
+ states.TAXONOMY_DECISION_REQUIRED,
732
+ on="_on_human_review",
733
+ )
734
+ vocabulary_curator_completed = (
735
+ states.VOCABULARY_CURATOR_RUNNING.to(
736
+ states.VOCABULARY_EVAL_RUNNING,
737
+ on="_on_vocabulary_eval",
738
+ )
739
+ | states.VOCABULARY_SEMANTIC_INGESTION_PENDING.to(
740
+ states.VOCABULARY_EVAL_RUNNING,
741
+ on="_on_vocabulary_eval",
742
+ )
743
+ )
744
+ vocabulary_eval_needs_review = states.VOCABULARY_EVAL_RUNNING.to(
745
+ states.VOCABULARY_EVAL_NEEDS_REVIEW,
746
+ on="_on_human_review",
747
+ )
748
+ vocabulary_eval_passed = states.VOCABULARY_EVAL_RUNNING.to(
749
+ states.VOCABULARY_APPLY_RUNNING,
750
+ on="_on_vocabulary_apply",
751
+ )
752
+ vocabulary_applied = states.VOCABULARY_APPLY_RUNNING.to(states.LINK_RUN_REQUESTED, on="_on_route_link")
753
+ vocabulary_integrity_failed = states.VOCABULARY_APPLY_RUNNING.to(
754
+ states.VOCABULARY_SQLITE_INTEGRITY_FAILED,
755
+ on="_on_failed",
756
+ )
757
+ atomicity_split_applied = states.ATOMICITY_SPLIT_RUNNING.to(states.LINK_RUN_REQUESTED, on="_on_route_link")
758
+ atomicity_split_blocked = states.ATOMICITY_SPLIT_RUNNING.to(
759
+ states.ATOMICITY_SPLIT_REVIEW_REQUIRED,
760
+ on="_on_human_review",
761
+ )
762
+ merge_applied = states.MERGE_RUNNING.to(states.LINK_RUN_REQUESTED, on="_on_route_link")
763
+ merge_blocked = states.MERGE_RUNNING.to(states.MERGE_REVIEW_REQUIRED, on="_on_human_review")
764
+ related_notes_export_completed = states.RELATED_NOTES_EXPORT_RUNNING.to(
765
+ states.LINK_RUN_REQUESTED,
766
+ on="_on_route_link",
767
+ )
768
+ related_notes_quota_wait = (
769
+ states.RELATED_NOTES_EXPORT_RUNNING.to(
770
+ states.RELATED_NOTES_QUOTA_WAIT,
771
+ on="_on_wait_external",
772
+ )
773
+ | states.LINK_RUN_REQUESTED.to(
774
+ states.RELATED_NOTES_QUOTA_WAIT,
775
+ on="_on_wait_external",
776
+ )
777
+ )
778
+ related_notes_obsidian_not_ready = states.RELATED_NOTES_EXPORT_RUNNING.to(
779
+ states.RELATED_NOTES_OBSIDIAN_NOT_READY,
780
+ on="_on_blocked",
781
+ )
782
+ related_notes_blocked = states.RELATED_NOTES_EXPORT_RUNNING.to(
783
+ states.RELATED_NOTES_BLOCKED,
784
+ on="_on_blocked",
785
+ )
786
+ link_completed = states.LINK_RUN_REQUESTED.to(states.FINAL_VALIDATION_RUNNING, on="_on_enter_final_validation")
787
+ link_graph_blocked = states.LINK_RUN_REQUESTED.to(states.LINK_GRAPH_BLOCKED, on="_on_blocked")
788
+ linker_blocked = states.LINK_RUN_REQUESTED.to(states.LINKER_BLOCKED, on="_on_blocked")
789
+ preview_ready = states.FINAL_VALIDATION_RUNNING.to(states.PREVIEW_READY, on="_on_completed")
790
+ final_validation_passed = states.FINAL_VALIDATION_RUNNING.to(states.COMPLETED, on="_on_completed")
791
+ final_validation_warnings = states.FINAL_VALIDATION_RUNNING.to(
792
+ states.COMPLETED_WITH_WARNINGS,
793
+ on="_on_completed",
794
+ )
795
+ final_validation_found_more_work = states.FINAL_VALIDATION_RUNNING.to(
796
+ states.DIAGNOSIS_RUNNING,
797
+ on="_on_resume_diagnosis",
798
+ )
799
+ final_validation_failed = states.FINAL_VALIDATION_RUNNING.to(states.FINAL_VALIDATION_FAILED, on="_on_failed")
800
+ rollback_completed = states.ROLLBACK_RUNNING.to(states.ROLLBACK_PERFORMED, on="_on_failed")
801
+ rollback_failed = states.ROLLBACK_RUNNING.to(states.ROLLBACK_FAILED, on="_on_blocked")
802
+
803
+ def category_for_state(self, state: str) -> WorkflowStateCategory:
804
+ return fix_wiki_category_for_state(state)
805
+
806
+ def _selected_environment_paths_missing(self, workflow_event: DiagnosisProducedEvent) -> bool:
807
+ return workflow_event.selected_lane == FixWikiDiagnosisLane.ENVIRONMENT_PATHS_MISSING
808
+
809
+ def _selected_environment_wiki_dir_missing(self, workflow_event: DiagnosisProducedEvent) -> bool:
810
+ return workflow_event.selected_lane == FixWikiDiagnosisLane.ENVIRONMENT_WIKI_DIR_MISSING
811
+
812
+ def _selected_environment_windows_path_or_venv_blocked(self, workflow_event: DiagnosisProducedEvent) -> bool:
813
+ return workflow_event.selected_lane == FixWikiDiagnosisLane.ENVIRONMENT_WINDOWS_PATH_OR_VENV_BLOCKED
814
+
815
+ def _selected_vault_guard_decision_required(self, workflow_event: DiagnosisProducedEvent) -> bool:
816
+ return workflow_event.selected_lane == FixWikiDiagnosisLane.VAULT_GUARD_DECISION_REQUIRED
817
+
818
+ def _selected_subagent_plan_attestation_required(self, workflow_event: DiagnosisProducedEvent) -> bool:
819
+ return workflow_event.selected_lane == FixWikiDiagnosisLane.SUBAGENT_PLAN_ATTESTATION_REQUIRED
820
+
821
+ def _selected_subagent_plan_attestation_invalid(self, workflow_event: DiagnosisProducedEvent) -> bool:
822
+ return workflow_event.selected_lane == FixWikiDiagnosisLane.SUBAGENT_PLAN_ATTESTATION_INVALID
823
+
824
+ def _selected_agent_tool_contract_violation(self, workflow_event: DiagnosisProducedEvent) -> bool:
825
+ return workflow_event.selected_lane == FixWikiDiagnosisLane.AGENT_TOOL_CONTRACT_VIOLATION
826
+
827
+ def _selected_deterministic_repairs(self, workflow_event: DiagnosisProducedEvent) -> bool:
828
+ return workflow_event.selected_lane == FixWikiDiagnosisLane.DETERMINISTIC_REPAIRS
829
+
830
+ def _selected_style_rewrite(self, workflow_event: DiagnosisProducedEvent) -> bool:
831
+ return workflow_event.selected_lane == FixWikiDiagnosisLane.STYLE_REWRITE
832
+
833
+ def _selected_taxonomy(self, workflow_event: DiagnosisProducedEvent) -> bool:
834
+ return workflow_event.selected_lane == FixWikiDiagnosisLane.TAXONOMY
835
+
836
+ def _selected_vocabulary_semantic_ingestion_pending(self, workflow_event: DiagnosisProducedEvent) -> bool:
837
+ return workflow_event.selected_lane == FixWikiDiagnosisLane.VOCABULARY_SEMANTIC_INGESTION_PENDING
838
+
839
+ def _selected_vocabulary(self, workflow_event: DiagnosisProducedEvent) -> bool:
840
+ return workflow_event.selected_lane == FixWikiDiagnosisLane.VOCABULARY
841
+
842
+ def _selected_atomicity_split(self, workflow_event: DiagnosisProducedEvent) -> bool:
843
+ return workflow_event.selected_lane == FixWikiDiagnosisLane.ATOMICITY_SPLIT
844
+
845
+ def _selected_merge(self, workflow_event: DiagnosisProducedEvent) -> bool:
846
+ return workflow_event.selected_lane == FixWikiDiagnosisLane.MERGE
847
+
848
+ def _selected_related_notes(self, workflow_event: DiagnosisProducedEvent) -> bool:
849
+ return workflow_event.selected_lane == FixWikiDiagnosisLane.RELATED_NOTES
850
+
851
+ def _selected_link(self, workflow_event: DiagnosisProducedEvent) -> bool:
852
+ return workflow_event.selected_lane == FixWikiDiagnosisLane.LINK
853
+
854
+ def _selected_contract_gap_missing_next_action(self, workflow_event: DiagnosisProducedEvent) -> bool:
855
+ return workflow_event.selected_lane == FixWikiDiagnosisLane.CONTRACT_GAP_MISSING_NEXT_ACTION
856
+
857
+ def _selected_contract_gap_missing_error_context(self, workflow_event: DiagnosisProducedEvent) -> bool:
858
+ return workflow_event.selected_lane == FixWikiDiagnosisLane.CONTRACT_GAP_MISSING_ERROR_CONTEXT
859
+
860
+ def _selected_rollback(self, workflow_event: DiagnosisProducedEvent) -> bool:
861
+ return workflow_event.selected_lane == FixWikiDiagnosisLane.ROLLBACK
862
+
863
+ def _selected_final_validation(self, workflow_event: DiagnosisProducedEvent) -> bool:
864
+ return workflow_event.selected_lane == FixWikiDiagnosisLane.FINAL_VALIDATION
865
+
866
+ def _observed_vault_guard_required(self, workflow_event: RuntimeObservedEvent) -> bool:
867
+ return workflow_event.observation.vault_guard_required
868
+
869
+ def _observed_environment_blocked(self, workflow_event: RuntimeObservedEvent) -> bool:
870
+ return workflow_event.observation.environment_windows_path_or_venv_blocked
871
+
872
+ def _observed_related_notes_quota_wait(self, workflow_event: RuntimeObservedEvent) -> bool:
873
+ return bool(
874
+ workflow_event.observation.external_wait_reason_code
875
+ and workflow_event.observation.related_notes_recovery_state.status
876
+ ) or workflow_event.observation.related_notes_waiting_external
877
+
878
+ def _observed_style_rewrite_capacity_wait(self, workflow_event: RuntimeObservedEvent) -> bool:
879
+ observation = workflow_event.observation
880
+ return bool(observation.external_wait_reason_code) and not self._observed_related_notes_quota_wait(
881
+ workflow_event
882
+ )
883
+
884
+ def _observed_vocabulary_semantic_ingestion_pending(self, workflow_event: RuntimeObservedEvent) -> bool:
885
+ return workflow_event.observation.vocabulary_semantic_ingestion_pending
886
+
887
+ def _observed_vocabulary_eval_needs_review(self, workflow_event: RuntimeObservedEvent) -> bool:
888
+ return workflow_event.observation.vocabulary_eval_needs_review
889
+
890
+ def _observed_atomicity_split_required(self, workflow_event: RuntimeObservedEvent) -> bool:
891
+ return workflow_event.observation.atomicity_split_required
892
+
893
+ def _observed_merge_review_required(self, workflow_event: RuntimeObservedEvent) -> bool:
894
+ return workflow_event.observation.merge_review_required
895
+
896
+ def _observed_graph_review_required(self, workflow_event: RuntimeObservedEvent) -> bool:
897
+ observation = workflow_event.observation
898
+ return observation.graph_review_required and (observation.graph_blocker_count > 0 or observation.graph_error_count > 0)
899
+
900
+ def _observed_graph_blocked(self, workflow_event: RuntimeObservedEvent) -> bool:
901
+ observation = workflow_event.observation
902
+ return observation.graph_blocker_count > 0 or observation.graph_error_count > 0
903
+
904
+ def _observed_related_notes_blocked(self, workflow_event: RuntimeObservedEvent) -> bool:
905
+ return workflow_event.observation.related_notes_blocked
906
+
907
+ def _observed_linker_blocked(self, workflow_event: RuntimeObservedEvent) -> bool:
908
+ return workflow_event.observation.linker_blocked
909
+
910
+ def _observed_taxonomy_action_required(self, workflow_event: RuntimeObservedEvent) -> bool:
911
+ return workflow_event.observation.taxonomy_action_required
912
+
913
+ def _observed_specialist_model_waiting_agent(self, workflow_event: RuntimeObservedEvent) -> bool:
914
+ return workflow_event.observation.specialist_model_waiting_agent
915
+
916
+ def _observed_style_rewrite_review_required(self, workflow_event: RuntimeObservedEvent) -> bool:
917
+ return workflow_event.observation.requires_llm_rewrite_count > 0
918
+
919
+ def _observed_human_decision_contract_gap(self, workflow_event: RuntimeObservedEvent) -> bool:
920
+ return workflow_event.observation.human_decision_required
921
+
922
+ def _observed_link_subworkflow_required(self, workflow_event: RuntimeObservedEvent) -> bool:
923
+ return workflow_event.observation.link_subworkflow_required
924
+
925
+ def _observed_failed(self, workflow_event: RuntimeObservedEvent) -> bool:
926
+ return workflow_event.observation.failed
927
+
928
+ def _observed_preview_ready(self, workflow_event: RuntimeObservedEvent) -> bool:
929
+ return not workflow_event.observation.effective_apply
930
+
931
+ def _observed_completed_with_warnings(self, workflow_event: RuntimeObservedEvent) -> bool:
932
+ return workflow_event.observation.warning_count > 0
933
+
934
+ def _observed_completed(self, workflow_event: RuntimeObservedEvent) -> bool:
935
+ return workflow_event.observation.effective_apply
936
+
937
+ def _on_diagnosis_produced(
938
+ self,
939
+ workflow_event: DiagnosisProducedEvent,
940
+ target: object,
941
+ ) -> WorkflowTransitionResult:
942
+ to_state = _target_state(target)
943
+ match to_state:
944
+ case (
945
+ FixWikiState.ENVIRONMENT_PATHS_MISSING
946
+ | FixWikiState.ENVIRONMENT_WIKI_DIR_MISSING
947
+ | FixWikiState.ENVIRONMENT_WINDOWS_PATH_OR_VENV_BLOCKED
948
+ ):
949
+ return _setup_recovery_transition(workflow_event, to_state)
950
+ case FixWikiState.VAULT_GUARD_DECISION_REQUIRED:
951
+ return _blocked_transition(workflow_event, to_state)
952
+ case (
953
+ FixWikiState.SUBAGENT_PLAN_ATTESTATION_REQUIRED
954
+ | FixWikiState.SUBAGENT_PLAN_ATTESTATION_INVALID
955
+ ):
956
+ return _human_transition(workflow_event, to_state)
957
+ case FixWikiState.AGENT_TOOL_CONTRACT_VIOLATION:
958
+ return _failed_transition(workflow_event, to_state)
959
+ case FixWikiState.DETERMINISTIC_REPAIRS_RUNNING:
960
+ return _transition(
961
+ workflow_event,
962
+ to_state,
963
+ effects=[_domain_effect(workflow_event, to_state, target="fix_wiki.deterministic_repairs")],
964
+ )
965
+ case FixWikiState.STYLE_REWRITE_SPECIALIST_REQUESTED:
966
+ return _transition(workflow_event, to_state, effects=[_style_rewrite_effect(workflow_event, to_state)])
967
+ case FixWikiState.TAXONOMY_DECISION_REQUIRED:
968
+ return _human_transition(workflow_event, to_state)
969
+ case (
970
+ FixWikiState.VOCABULARY_CURATOR_RUNNING
971
+ | FixWikiState.VOCABULARY_SEMANTIC_INGESTION_PENDING
972
+ ):
973
+ return _transition(
974
+ workflow_event,
975
+ to_state,
976
+ effects=[_domain_effect(workflow_event, to_state, target="fix_wiki.vocabulary_curator")],
977
+ )
978
+ case FixWikiState.ATOMICITY_SPLIT_RUNNING:
979
+ return _transition(
980
+ workflow_event,
981
+ to_state,
982
+ effects=[_domain_effect(workflow_event, to_state, target="fix_wiki.atomicity_split")],
983
+ )
984
+ case FixWikiState.MERGE_RUNNING:
985
+ return _transition(
986
+ workflow_event,
987
+ to_state,
988
+ effects=[_domain_effect(workflow_event, to_state, target="fix_wiki.note_merge")],
989
+ )
990
+ case FixWikiState.RELATED_NOTES_EXPORT_RUNNING:
991
+ return _transition(workflow_event, to_state, effects=[_related_notes_effect(workflow_event, to_state)])
992
+ case FixWikiState.LINK_RUN_REQUESTED:
993
+ return _transition(workflow_event, to_state, effects=[_link_effect(workflow_event, to_state)])
994
+ case (
995
+ FixWikiState.CONTRACT_GAP_MISSING_NEXT_ACTION
996
+ | FixWikiState.CONTRACT_GAP_MISSING_ERROR_CONTEXT
997
+ ):
998
+ return _failed_transition(workflow_event, to_state)
999
+ case FixWikiState.ROLLBACK_RUNNING:
1000
+ return _transition(
1001
+ workflow_event,
1002
+ to_state,
1003
+ effects=[_domain_effect(workflow_event, to_state, target="fix_wiki.rollback")],
1004
+ )
1005
+ case FixWikiState.FINAL_VALIDATION_RUNNING:
1006
+ return _transition(
1007
+ workflow_event,
1008
+ to_state,
1009
+ effects=[_final_validation_effect(workflow_event, to_state)],
1010
+ )
1011
+ case _:
1012
+ raise AssertionError(f"unexpected diagnosis target: {to_state}")
1013
+
1014
+ def _on_runtime_observed(self, workflow_event: RuntimeObservedEvent, target: object) -> WorkflowTransitionResult:
1015
+ to_state = _target_state(target)
1016
+ match to_state:
1017
+ case FixWikiState.STYLE_REWRITE_SPECIALIST_REQUESTED:
1018
+ return _transition(workflow_event, to_state, effects=[_style_rewrite_effect(workflow_event, to_state)])
1019
+ case FixWikiState.VOCABULARY_SEMANTIC_INGESTION_PENDING:
1020
+ return _transition(
1021
+ workflow_event,
1022
+ to_state,
1023
+ effects=[_domain_effect(workflow_event, to_state, target="fix_wiki.vocabulary_curator")],
1024
+ )
1025
+ case FixWikiState.LINK_RUN_REQUESTED:
1026
+ return _transition(workflow_event, to_state, effects=[_link_effect(workflow_event, to_state)])
1027
+ case _:
1028
+ return _transition(workflow_event, to_state)
1029
+
1030
+ def _on_resume_diagnosis(self, workflow_event: FixWikiEvent) -> WorkflowTransitionResult:
1031
+ return _transition(workflow_event, FixWikiState.DIAGNOSIS_RUNNING)
1032
+
1033
+ def _on_route_link(self, workflow_event: FixWikiEvent) -> WorkflowTransitionResult:
1034
+ return _transition(
1035
+ workflow_event,
1036
+ FixWikiState.LINK_RUN_REQUESTED,
1037
+ effects=[_link_effect(workflow_event, FixWikiState.LINK_RUN_REQUESTED)],
1038
+ )
1039
+
1040
+ def _on_vault_guard_run(self, workflow_event: FixWikiEvent) -> WorkflowTransitionResult:
1041
+ return _transition(
1042
+ workflow_event,
1043
+ FixWikiState.VAULT_GUARD_RUNNING,
1044
+ effects=[_vault_guard_effect(workflow_event, FixWikiState.VAULT_GUARD_RUNNING)],
1045
+ )
1046
+
1047
+ def _on_enter_final_validation(self, workflow_event: FixWikiEvent) -> WorkflowTransitionResult:
1048
+ return _transition(
1049
+ workflow_event,
1050
+ FixWikiState.FINAL_VALIDATION_RUNNING,
1051
+ effects=[_final_validation_effect(workflow_event, FixWikiState.FINAL_VALIDATION_RUNNING)],
1052
+ )
1053
+
1054
+ def _on_vocabulary_eval(self, workflow_event: FixWikiEvent) -> WorkflowTransitionResult:
1055
+ return _transition(
1056
+ workflow_event,
1057
+ FixWikiState.VOCABULARY_EVAL_RUNNING,
1058
+ effects=[
1059
+ _domain_effect(
1060
+ workflow_event,
1061
+ FixWikiState.VOCABULARY_EVAL_RUNNING,
1062
+ target="fix_wiki.vocabulary_eval",
1063
+ )
1064
+ ],
1065
+ )
1066
+
1067
+ def _on_style_rewrite_apply(self, workflow_event: FixWikiEvent) -> WorkflowTransitionResult:
1068
+ return _transition(
1069
+ workflow_event,
1070
+ FixWikiState.STYLE_REWRITE_APPLY_RUNNING,
1071
+ effects=[
1072
+ _domain_effect(
1073
+ workflow_event,
1074
+ FixWikiState.STYLE_REWRITE_APPLY_RUNNING,
1075
+ target="fix_wiki.style_rewrite_apply",
1076
+ )
1077
+ ],
1078
+ )
1079
+
1080
+ def _on_taxonomy_apply(self, workflow_event: FixWikiEvent) -> WorkflowTransitionResult:
1081
+ return _transition(
1082
+ workflow_event,
1083
+ FixWikiState.TAXONOMY_APPLY_RUNNING,
1084
+ effects=[_domain_effect(workflow_event, FixWikiState.TAXONOMY_APPLY_RUNNING, target="fix_wiki.taxonomy")],
1085
+ )
1086
+
1087
+ def _on_vocabulary_apply(self, workflow_event: FixWikiEvent) -> WorkflowTransitionResult:
1088
+ return _transition(
1089
+ workflow_event,
1090
+ FixWikiState.VOCABULARY_APPLY_RUNNING,
1091
+ effects=[
1092
+ _domain_effect(
1093
+ workflow_event,
1094
+ FixWikiState.VOCABULARY_APPLY_RUNNING,
1095
+ target="fix_wiki.vocabulary_apply",
1096
+ )
1097
+ ],
1098
+ )
1099
+
1100
+ def _on_wait_external(self, workflow_event: FixWikiEvent, target: object) -> WorkflowTransitionResult:
1101
+ to_state = _target_state(target)
1102
+ return _transition(
1103
+ workflow_event,
1104
+ to_state,
1105
+ effects=[_wait_external_effect(workflow_event, to_state)],
1106
+ resume_action="Retomar pela rota oficial depois que a condição externa estiver resolvida.",
1107
+ )
1108
+
1109
+ def _on_human_review(self, workflow_event: FixWikiEvent, target: object) -> WorkflowTransitionResult:
1110
+ to_state = _target_state(target)
1111
+ return _human_transition(workflow_event, to_state)
1112
+
1113
+ def _on_blocked(self, workflow_event: FixWikiEvent, target: object) -> WorkflowTransitionResult:
1114
+ to_state = _target_state(target)
1115
+ return _blocked_transition(workflow_event, to_state)
1116
+
1117
+ def _on_failed(self, workflow_event: FixWikiEvent, target: object) -> WorkflowTransitionResult:
1118
+ to_state = _target_state(target)
1119
+ return _failed_transition(workflow_event, to_state)
1120
+
1121
+ def _on_completed(self, workflow_event: FixWikiEvent, target: object) -> WorkflowTransitionResult:
1122
+ to_state = _target_state(target)
1123
+ return _transition(workflow_event, to_state)
1124
+
1125
+
1126
+ def _transition(
1127
+ workflow_event: FixWikiEvent,
1128
+ to_state: FixWikiState,
1129
+ *,
1130
+ reason_code: str | None = None,
1131
+ effects: list[WorkflowEffect] | None = None,
1132
+ decision: WorkflowDecision | None = None,
1133
+ human_decision_packet: HumanDecisionPacket | None = None,
1134
+ resume_action: str = "",
1135
+ ) -> WorkflowTransitionResult:
1136
+ trigger = _event_name(workflow_event)
1137
+ state_reason = _transition_reason_for_state(to_state)
1138
+ return WorkflowTransitionResult(
1139
+ workflow=workflow_event.workflow,
1140
+ run_id=workflow_event.run_id,
1141
+ from_state=workflow_event.current_state,
1142
+ to_state=to_state.value,
1143
+ trigger=trigger,
1144
+ reason_code=reason_code or state_reason,
1145
+ effects=list(effects or []),
1146
+ decision=decision,
1147
+ human_decision_packet=human_decision_packet,
1148
+ resume_action=resume_action,
1149
+ )
1150
+
1151
+
1152
+ def _transition_reason_for_state(state: FixWikiState) -> str:
1153
+ """Record the reached leaf for running states and public state reason otherwise."""
1154
+
1155
+ if fix_wiki_category_for_state(state) == WorkflowStateCategory.RUNNING:
1156
+ return state.value
1157
+ return reason_for_state(state).value
1158
+
1159
+
1160
+ def _target_state(target: object) -> FixWikiState:
1161
+ """Read the python-statemachine transition target without touching IO."""
1162
+
1163
+ value = getattr(target, "value", target)
1164
+ return FixWikiState(str(value))
1165
+
1166
+
1167
+ def _blocked_transition(
1168
+ workflow_event: FixWikiEvent,
1169
+ to_state: FixWikiState,
1170
+ ) -> WorkflowTransitionResult:
1171
+ reason_code = reason_for_state(to_state).value
1172
+ return _transition(
1173
+ workflow_event,
1174
+ to_state,
1175
+ reason_code=reason_code,
1176
+ decision=_decision(kind="hard_block", phase=to_state.value, reason_code=reason_code),
1177
+ )
1178
+
1179
+
1180
+ def _setup_recovery_transition(
1181
+ workflow_event: FixWikiEvent,
1182
+ to_state: FixWikiState,
1183
+ ) -> WorkflowTransitionResult:
1184
+ reason_code = reason_for_state(to_state).value
1185
+ resume_action = _setup_resume_action(to_state)
1186
+ return _transition(
1187
+ workflow_event,
1188
+ to_state,
1189
+ reason_code=reason_code,
1190
+ effects=[_setup_recovery_effect(workflow_event, to_state, reason_code=reason_code)],
1191
+ decision=_decision(
1192
+ kind="hard_block",
1193
+ phase=to_state.value,
1194
+ reason_code=reason_code,
1195
+ next_action=resume_action,
1196
+ ),
1197
+ resume_action=resume_action,
1198
+ )
1199
+
1200
+
1201
+ def _failed_transition(
1202
+ workflow_event: FixWikiEvent,
1203
+ to_state: FixWikiState,
1204
+ ) -> WorkflowTransitionResult:
1205
+ reason_code = reason_for_state(to_state).value
1206
+ return _transition(
1207
+ workflow_event,
1208
+ to_state,
1209
+ reason_code=reason_code,
1210
+ decision=_decision(kind="failed", phase=to_state.value, reason_code=reason_code),
1211
+ )
1212
+
1213
+
1214
+ def _human_transition(
1215
+ workflow_event: FixWikiEvent,
1216
+ to_state: FixWikiState,
1217
+ ) -> WorkflowTransitionResult:
1218
+ reason_code = reason_for_state(to_state).value
1219
+ decision = _decision(
1220
+ kind="ask_human",
1221
+ phase=to_state.value,
1222
+ reason_code=reason_code,
1223
+ next_action=_human_next_action(to_state),
1224
+ )
1225
+ packet = HumanDecisionPacket.model_validate(decision.to_human_decision_packet())
1226
+ effect = WorkflowEffect(
1227
+ workflow=workflow_event.workflow,
1228
+ run_id=workflow_event.run_id,
1229
+ effect_id=f"fix-wiki-{to_state.value.replace('.', '-')}-human-decision",
1230
+ origin_state=to_state.value,
1231
+ kind=WorkflowEffectKind.ASK_HUMAN,
1232
+ payload={"kind": "human_decision", "reason_code": reason_code},
1233
+ requires_receipt=False,
1234
+ no_resource_mutation=True,
1235
+ )
1236
+ return _transition(
1237
+ workflow_event,
1238
+ to_state,
1239
+ reason_code=reason_code,
1240
+ effects=[effect],
1241
+ decision=decision,
1242
+ human_decision_packet=packet,
1243
+ resume_action=decision.resume_action,
1244
+ )
1245
+
1246
+
1247
+ def _human_next_action(to_state: FixWikiState) -> str:
1248
+ """Keep human-decision actions tied to the reached operational leaf."""
1249
+
1250
+ match to_state:
1251
+ case FixWikiState.STYLE_REWRITE_REVIEW_REQUIRED:
1252
+ return "Executar a reescrita semantica oficial antes de concluir."
1253
+ case FixWikiState.TAXONOMY_DECISION_REQUIRED:
1254
+ return "Resolver a acao de taxonomia pela rota oficial antes de concluir."
1255
+ case FixWikiState.ATOMICITY_SPLIT_REVIEW_REQUIRED:
1256
+ return "Resolver o split de atomicidade pela rota oficial."
1257
+ case FixWikiState.VOCABULARY_EVAL_NEEDS_REVIEW:
1258
+ return "Revisar a avaliacao do vocabulario e retomar pela rota oficial."
1259
+ case FixWikiState.LINK_GRAPH_REVIEW_REQUIRED:
1260
+ return "Revisar os bloqueios de grafo e retomar pela rota oficial."
1261
+ case FixWikiState.MERGE_REVIEW_REQUIRED:
1262
+ return "Revisar o merge de notas e retomar pela rota oficial."
1263
+ case (
1264
+ FixWikiState.SUBAGENT_PLAN_ATTESTATION_REQUIRED
1265
+ | FixWikiState.SUBAGENT_PLAN_ATTESTATION_INVALID
1266
+ ):
1267
+ return "Reemitir o plano de subagente pela rota oficial com atestacao valida."
1268
+ case _:
1269
+ return "Responder a decisao solicitada para continuar."
1270
+
1271
+
1272
+ def _decision(
1273
+ *,
1274
+ kind: Literal["hard_block", "failed", "ask_human"],
1275
+ phase: str,
1276
+ reason_code: str,
1277
+ next_action: str = "Retomar pela rota oficial indicada pelo workflow.",
1278
+ ) -> WorkflowDecision:
1279
+ evidence = [
1280
+ DecisionEvidence(
1281
+ summary=f"fix-wiki StateChart reached {phase}.",
1282
+ technical_code=reason_code,
1283
+ source="fix_wiki_machine",
1284
+ )
1285
+ ]
1286
+ base: JsonObject = {
1287
+ "kind": kind,
1288
+ "phase": phase,
1289
+ "reason_code": reason_code,
1290
+ "public_summary": "O fix-wiki precisa parar nesta etapa.",
1291
+ "developer_summary": f"StateChart transition stopped at {phase}:{reason_code}.",
1292
+ "evidence": evidence,
1293
+ "next_action": next_action,
1294
+ "resume_action": next_action,
1295
+ }
1296
+ if kind == "ask_human":
1297
+ base.update(
1298
+ {
1299
+ "public_summary": "Preciso de uma decisão sua antes de continuar.",
1300
+ "human_decision_kind": reason_code,
1301
+ "recommended_option_id": "continue",
1302
+ "options": [
1303
+ HumanDecisionOption(
1304
+ id="continue",
1305
+ label="Continuar",
1306
+ description="Retoma pela rota oficial depois da confirmação.",
1307
+ )
1308
+ ],
1309
+ "rejected_automations": _rejected_automations(reason_code),
1310
+ }
1311
+ )
1312
+ return WorkflowDecision(**base)
1313
+
1314
+
1315
+ def _rejected_automations(reason_code: str) -> list[RejectedAutomation]:
1316
+ return [
1317
+ RejectedAutomation(kind="auto_fix", reason_code=reason_code, reason="Requer confirmação humana."),
1318
+ RejectedAutomation(
1319
+ kind="auto_defer",
1320
+ reason_code=reason_code,
1321
+ reason="Adiar sem decisão deixaria o fluxo ambíguo.",
1322
+ ),
1323
+ RejectedAutomation(
1324
+ kind="auto_plan",
1325
+ reason_code=reason_code,
1326
+ reason="Planejar sem escolha humana não resolve o bloqueio.",
1327
+ ),
1328
+ ]
1329
+
1330
+
1331
+ def _payload(kind: str) -> JsonObject:
1332
+ return {"kind": kind}
1333
+
1334
+
1335
+ def _domain_effect(workflow_event: FixWikiEvent, origin_state: FixWikiState, *, target: str) -> WorkflowEffect:
1336
+ payload_kind = target.removeprefix("fix_wiki.")
1337
+ return WorkflowEffect(
1338
+ workflow=workflow_event.workflow,
1339
+ run_id=workflow_event.run_id,
1340
+ effect_id=f"fix-wiki-{origin_state.value.replace('.', '-')}",
1341
+ origin_state=origin_state.value,
1342
+ kind=WorkflowEffectKind.RUN_SUBWORKFLOW,
1343
+ target=target,
1344
+ payload=_payload(payload_kind),
1345
+ mutates_resources=True,
1346
+ rollback_declared=True,
1347
+ requires_receipt=False,
1348
+ )
1349
+
1350
+
1351
+ def _setup_recovery_effect(
1352
+ workflow_event: FixWikiEvent,
1353
+ origin_state: FixWikiState,
1354
+ *,
1355
+ reason_code: str,
1356
+ ) -> WorkflowEffect:
1357
+ resume_action = _setup_resume_action(origin_state)
1358
+ return WorkflowEffect(
1359
+ workflow=workflow_event.workflow,
1360
+ run_id=workflow_event.run_id,
1361
+ effect_id=f"fix-wiki-{origin_state.value.replace('.', '-')}-setup",
1362
+ origin_state=origin_state.value,
1363
+ kind=WorkflowEffectKind.RUN_SUBWORKFLOW,
1364
+ target="/mednotes:setup",
1365
+ payload={
1366
+ "kind": "setup_recovery",
1367
+ "reason_code": reason_code,
1368
+ "setup_state": _setup_state_for_fix_wiki_environment(origin_state),
1369
+ "resume_action": resume_action,
1370
+ },
1371
+ requires_receipt=False,
1372
+ no_resource_mutation=True,
1373
+ resume_action=resume_action,
1374
+ )
1375
+
1376
+
1377
+ def _setup_resume_action(state: FixWikiState) -> str:
1378
+ match state:
1379
+ case FixWikiState.ENVIRONMENT_PATHS_MISSING | FixWikiState.ENVIRONMENT_WIKI_DIR_MISSING:
1380
+ return "setup:set-paths"
1381
+ case FixWikiState.ENVIRONMENT_WINDOWS_PATH_OR_VENV_BLOCKED:
1382
+ return "setup:bootstrap-python"
1383
+ case _:
1384
+ return "/mednotes:setup"
1385
+
1386
+
1387
+ def _setup_state_for_fix_wiki_environment(state: FixWikiState) -> str:
1388
+ match state:
1389
+ case FixWikiState.ENVIRONMENT_PATHS_MISSING | FixWikiState.ENVIRONMENT_WIKI_DIR_MISSING:
1390
+ return "paths_required"
1391
+ case FixWikiState.ENVIRONMENT_WINDOWS_PATH_OR_VENV_BLOCKED:
1392
+ return "python_env_required"
1393
+ case _:
1394
+ return "checking_environment"
1395
+
1396
+
1397
+ def _vault_guard_effect(workflow_event: FixWikiEvent, origin_state: FixWikiState) -> WorkflowEffect:
1398
+ return WorkflowEffect(
1399
+ workflow=workflow_event.workflow,
1400
+ run_id=workflow_event.run_id,
1401
+ effect_id="fix-wiki-vault-guard",
1402
+ origin_state=origin_state.value,
1403
+ kind=WorkflowEffectKind.RUN_SUBWORKFLOW,
1404
+ target="fix_wiki.vault_guard",
1405
+ payload=_payload("vault_guard"),
1406
+ requires_receipt=False,
1407
+ no_resource_mutation=True,
1408
+ )
1409
+
1410
+
1411
+ def _style_rewrite_effect(workflow_event: FixWikiEvent, origin_state: FixWikiState) -> WorkflowEffect:
1412
+ """Emit the executable specialist effect from the StateChart transition.
1413
+
1414
+ Runtime evidence may carry the batch payload, but the transition owns the
1415
+ public effect identity, target, origin state and model policy.
1416
+ """
1417
+
1418
+ runtime_effect: WorkflowEffect | None = None
1419
+ if isinstance(workflow_event, DiagnosisQueueEvent):
1420
+ runtime_effect = workflow_event.style_rewrite_effect
1421
+ elif isinstance(workflow_event, RuntimeObservedEvent):
1422
+ runtime_effect = workflow_event.observation.style_rewrite_effect
1423
+
1424
+ if runtime_effect is not None:
1425
+ payload = dict(runtime_effect.payload)
1426
+ if "kind" not in payload:
1427
+ payload["kind"] = "style_rewrite"
1428
+ return WorkflowEffect(
1429
+ workflow=workflow_event.workflow,
1430
+ run_id=workflow_event.run_id,
1431
+ effect_id="fix-wiki-style-rewrite-specialist",
1432
+ origin_state=origin_state.value,
1433
+ kind=WorkflowEffectKind.CALL_SPECIALIST_MODEL,
1434
+ target="med-knowledge-architect",
1435
+ payload=payload,
1436
+ requires_receipt=True,
1437
+ requires_attestation=True,
1438
+ model_policy=_style_rewrite_model_policy(runtime_effect.model_policy),
1439
+ )
1440
+ return WorkflowEffect(
1441
+ workflow=workflow_event.workflow,
1442
+ run_id=workflow_event.run_id,
1443
+ effect_id="fix-wiki-style-rewrite-specialist",
1444
+ origin_state=origin_state.value,
1445
+ kind=WorkflowEffectKind.CALL_SPECIALIST_MODEL,
1446
+ target="med-knowledge-architect",
1447
+ payload=_payload("style_rewrite"),
1448
+ requires_receipt=True,
1449
+ requires_attestation=True,
1450
+ model_policy=_style_rewrite_model_policy(),
1451
+ )
1452
+
1453
+
1454
+ def _style_rewrite_model_policy(override: JsonObject | None = None) -> JsonObject:
1455
+ """Keep specialist policy canonical even when runtime supplied batch data."""
1456
+
1457
+ policy = {
1458
+ "policy": "medical_specialist_authoring.v1",
1459
+ "required_model_tier": "specialist",
1460
+ "preferred_model_tier": "pro",
1461
+ "forbid_flash_fallback": True,
1462
+ }
1463
+ if override:
1464
+ policy.update(override)
1465
+ policy["forbid_flash_fallback"] = True
1466
+ policy["required_model_tier"] = "specialist"
1467
+ return policy
1468
+
1469
+
1470
+ def _related_notes_effect(workflow_event: FixWikiEvent, origin_state: FixWikiState) -> WorkflowEffect:
1471
+ return WorkflowEffect(
1472
+ workflow=workflow_event.workflow,
1473
+ run_id=workflow_event.run_id,
1474
+ effect_id="fix-wiki-related-notes-export",
1475
+ origin_state=origin_state.value,
1476
+ kind=WorkflowEffectKind.RUN_SUBWORKFLOW,
1477
+ target="related_notes.export",
1478
+ payload=RelatedNotesExportEffectPayload(reason_code="related_notes").to_payload(),
1479
+ requires_receipt=False,
1480
+ no_resource_mutation=True,
1481
+ )
1482
+
1483
+
1484
+ def _link_effect(workflow_event: FixWikiEvent, origin_state: FixWikiState) -> WorkflowEffect:
1485
+ runtime_effect: WorkflowEffect | None = None
1486
+ if isinstance(workflow_event, RuntimeObservedEvent):
1487
+ runtime_effect = workflow_event.observation.link_effect
1488
+ if runtime_effect is not None:
1489
+ payload = dict(runtime_effect.payload)
1490
+ return WorkflowEffect(
1491
+ workflow=workflow_event.workflow,
1492
+ run_id=workflow_event.run_id,
1493
+ effect_id="fix-wiki-link-run",
1494
+ origin_state=origin_state.value,
1495
+ kind=WorkflowEffectKind.RUN_SUBWORKFLOW,
1496
+ target="/mednotes:link",
1497
+ payload=payload,
1498
+ mutates_resources=True,
1499
+ rollback_declared=True,
1500
+ requires_receipt=False,
1501
+ )
1502
+ payload = LinkWorkflowRunEffectPayload(
1503
+ kind="link_run",
1504
+ diagnose=False,
1505
+ apply=True,
1506
+ no_related_notes=False,
1507
+ ).to_payload()
1508
+ return WorkflowEffect(
1509
+ workflow=workflow_event.workflow,
1510
+ run_id=workflow_event.run_id,
1511
+ effect_id="fix-wiki-link-run",
1512
+ origin_state=origin_state.value,
1513
+ kind=WorkflowEffectKind.RUN_SUBWORKFLOW,
1514
+ target="/mednotes:link",
1515
+ payload=payload,
1516
+ mutates_resources=True,
1517
+ rollback_declared=True,
1518
+ requires_receipt=False,
1519
+ )
1520
+
1521
+
1522
+ def _wait_external_effect(workflow_event: FixWikiEvent, origin_state: FixWikiState) -> WorkflowEffect:
1523
+ target = (
1524
+ "related_notes.quota"
1525
+ if origin_state == FixWikiState.RELATED_NOTES_QUOTA_WAIT
1526
+ else "specialist_model.capacity"
1527
+ )
1528
+ resume_action = "Retomar pela rota oficial depois que a condição externa estiver resolvida."
1529
+ payload = WaitExternalEffectPayload(
1530
+ wait_target=target,
1531
+ blocked_reason=reason_for_state(origin_state).value,
1532
+ next_action=resume_action,
1533
+ resume_supported=True,
1534
+ ).to_payload()
1535
+ if isinstance(workflow_event, RelatedNotesQuotaWaitEvent):
1536
+ recovery_state = workflow_event.related_notes_recovery_state
1537
+ elif isinstance(workflow_event, RuntimeObservedEvent) and origin_state == FixWikiState.RELATED_NOTES_QUOTA_WAIT:
1538
+ recovery_state = workflow_event.observation.related_notes_recovery_state
1539
+ else:
1540
+ recovery_state = None
1541
+ if recovery_state is not None:
1542
+ if not recovery_state.status:
1543
+ recovery_state = RelatedNotesRecoveryStateEffectPayload.from_payload(
1544
+ {
1545
+ "schema": "medical-notes-workbench.related-notes-recovery-state.v1",
1546
+ "status": "waiting_for_retry",
1547
+ "blocked_reason": "related_notes_headless_quota_exhausted",
1548
+ "next_action": resume_action,
1549
+ "resume_supported": True,
1550
+ }
1551
+ )
1552
+ if isinstance(workflow_event, RuntimeObservedEvent):
1553
+ resume_action = recovery_state.next_action or workflow_event.observation.next_action or resume_action
1554
+ else:
1555
+ resume_action = recovery_state.next_action or resume_action
1556
+ payload = WaitExternalEffectPayload.model_validate(
1557
+ {
1558
+ "wait_target": target,
1559
+ "related_notes_recovery_state": recovery_state,
1560
+ "next_action": resume_action,
1561
+ }
1562
+ ).to_payload()
1563
+ return WorkflowEffect(
1564
+ workflow=workflow_event.workflow,
1565
+ run_id=workflow_event.run_id,
1566
+ effect_id=f"fix-wiki-{origin_state.value.replace('.', '-')}-wait",
1567
+ origin_state=origin_state.value,
1568
+ kind=WorkflowEffectKind.WAIT_EXTERNAL,
1569
+ target=target,
1570
+ payload=payload,
1571
+ requires_receipt=False,
1572
+ no_resource_mutation=True,
1573
+ resume_action=resume_action,
1574
+ )
1575
+
1576
+
1577
+ def _final_validation_effect(workflow_event: FixWikiEvent, origin_state: FixWikiState) -> WorkflowEffect:
1578
+ return WorkflowEffect(
1579
+ workflow=workflow_event.workflow,
1580
+ run_id=workflow_event.run_id,
1581
+ effect_id="fix-wiki-final-validation",
1582
+ origin_state=origin_state.value,
1583
+ kind=WorkflowEffectKind.RUN_SUBWORKFLOW,
1584
+ target="fix_wiki.final_validation",
1585
+ payload=_payload("final_validation"),
1586
+ requires_receipt=False,
1587
+ no_resource_mutation=True,
1588
+ )