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.
- package/.opencode/agents/med-chat-triager.md +204 -0
- package/.opencode/agents/med-flashcard-maker.md +63 -0
- package/.opencode/agents/med-knowledge-architect.md +230 -0
- package/.opencode/agents/med-link-graph-curator.md +177 -0
- package/.opencode/agents/med-publish-guard.md +62 -0
- package/.opencode/commands/flashcards.md +25 -0
- package/.opencode/commands/mednotes/create.md +25 -0
- package/.opencode/commands/mednotes/enrich.md +27 -0
- package/.opencode/commands/mednotes/fix-wiki.md +27 -0
- package/.opencode/commands/mednotes/history.md +22 -0
- package/.opencode/commands/mednotes/link-body.md +25 -0
- package/.opencode/commands/mednotes/link-related.md +27 -0
- package/.opencode/commands/mednotes/link.md +27 -0
- package/.opencode/commands/mednotes/pdf-library.md +27 -0
- package/.opencode/commands/mednotes/process-chats.md +23 -0
- package/.opencode/commands/mednotes/setup.md +21 -0
- package/.opencode/commands/mednotes/status.md +27 -0
- package/.opencode/commands/mednotes/telemetry.md +27 -0
- package/.opencode/commands/report.md +26 -0
- package/.opencode/mednotes/AGENTS.md +57 -0
- package/.opencode/mednotes/agents/med-chat-triager.md +197 -0
- package/.opencode/mednotes/agents/med-flashcard-maker.md +56 -0
- package/.opencode/mednotes/agents/med-knowledge-architect.md +224 -0
- package/.opencode/mednotes/agents/med-link-graph-curator.md +171 -0
- package/.opencode/mednotes/agents/med-publish-guard.md +55 -0
- package/.opencode/mednotes/contracts/.gitkeep +1 -0
- package/.opencode/mednotes/contracts/agents.json +116 -0
- package/.opencode/mednotes/contracts/opencode-plugin.json +70 -0
- package/.opencode/mednotes/docs/agent-prompt-hardening.md +567 -0
- package/.opencode/mednotes/docs/agent-role-contracts.md +94 -0
- package/.opencode/mednotes/docs/anki-mcp-twenty-rules.md +214 -0
- package/.opencode/mednotes/docs/anki-templates/README.md +39 -0
- package/.opencode/mednotes/docs/anki-templates/cloze.back.html +23 -0
- package/.opencode/mednotes/docs/anki-templates/cloze.front.html +14 -0
- package/.opencode/mednotes/docs/anki-templates/qa.back.html +24 -0
- package/.opencode/mednotes/docs/anki-templates/qa.front.html +14 -0
- package/.opencode/mednotes/docs/anki-templates/style.css +182 -0
- package/.opencode/mednotes/docs/atomicity-splitting-policy.md +113 -0
- package/.opencode/mednotes/docs/extension-docs.md +40 -0
- package/.opencode/mednotes/docs/flashcard-ingestion.md +278 -0
- package/.opencode/mednotes/docs/knowledge-architect.md +208 -0
- package/.opencode/mednotes/docs/merge-policy.md +110 -0
- package/.opencode/mednotes/docs/public-vocabulary.md +104 -0
- package/.opencode/mednotes/docs/semantic-linker.md +141 -0
- package/.opencode/mednotes/docs/taxonomy-policy.md +90 -0
- package/.opencode/mednotes/docs/triage-policy.md +187 -0
- package/.opencode/mednotes/docs/vault-version-control.md +758 -0
- package/.opencode/mednotes/docs/vocabulary-db-recovery.md +58 -0
- package/.opencode/mednotes/docs/workflow-output-contract.md +779 -0
- package/.opencode/mednotes/hooks/hooks.json +79 -0
- package/.opencode/mednotes/package-lock.json +6361 -0
- package/.opencode/mednotes/package.json +15 -0
- package/.opencode/mednotes/pyproject.toml +48 -0
- package/.opencode/mednotes/scripts/bootstrap_windows_python_uv.cmd +13 -0
- package/.opencode/mednotes/scripts/bootstrap_windows_python_uv.ps1 +172 -0
- package/.opencode/mednotes/scripts/enrich_notes.py +23 -0
- package/.opencode/mednotes/scripts/full_reset_windows_python_uv.cmd +13 -0
- package/.opencode/mednotes/scripts/hooks/antigravity_hook_status.mjs +212 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/adapters/antigravity.mjs +169 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/adapters/harness_payload.mjs +103 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/adapters/opencode_plugin.mjs +341 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/adapters/opencode_user_config_sync.mjs +177 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/anki_preflight.mjs +214 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/cli.mjs +143 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/diagnostics.mjs +11 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/domain/agent_directive_core.mjs +160 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/fsm_directive.mjs +1470 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/hook_errors.mjs +120 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/retention.mjs +114 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/runtime.mjs +174 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/telemetry_capture.mjs +511 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook/vault_guard.mjs +624 -0
- package/.opencode/mednotes/scripts/hooks/mednotes_hook.mjs +5 -0
- package/.opencode/mednotes/scripts/mednotes/_runtime_paths.py +24 -0
- package/.opencode/mednotes/scripts/mednotes/anki_model_validator.py +18 -0
- package/.opencode/mednotes/scripts/mednotes/capture_extension_diff.py +1562 -0
- package/.opencode/mednotes/scripts/mednotes/feedback_report.py +16 -0
- package/.opencode/mednotes/scripts/mednotes/flashcard_index.py +18 -0
- package/.opencode/mednotes/scripts/mednotes/flashcard_pipeline.py +18 -0
- package/.opencode/mednotes/scripts/mednotes/flashcard_report.py +18 -0
- package/.opencode/mednotes/scripts/mednotes/flashcard_sources.py +18 -0
- package/.opencode/mednotes/scripts/mednotes/obsidian/README.md +6 -0
- package/.opencode/mednotes/scripts/mednotes/obsidian_note_utils.py +20 -0
- package/.opencode/mednotes/scripts/mednotes/pdf_library/cli.py +16 -0
- package/.opencode/mednotes/scripts/mednotes/project_fsm.py +229 -0
- package/.opencode/mednotes/scripts/mednotes/setup_telemetry_email.py +404 -0
- package/.opencode/mednotes/scripts/mednotes/sync_anki_twenty_rules.py +18 -0
- package/.opencode/mednotes/scripts/mednotes/sync_opencode_user_config.py +36 -0
- package/.opencode/mednotes/scripts/mednotes/wiki/cli.py +20 -0
- package/.opencode/mednotes/scripts/mednotes/wiki_graph.py +18 -0
- package/.opencode/mednotes/scripts/mednotes/wiki_tree.py +134 -0
- package/.opencode/mednotes/scripts/reset_windows_python_uv.ps1 +625 -0
- package/.opencode/mednotes/scripts/run_python.mjs +109 -0
- package/.opencode/mednotes/scripts/vault/vault_commit.ps1 +19 -0
- package/.opencode/mednotes/scripts/vault/vault_commit.sh +18 -0
- package/.opencode/mednotes/scripts/vault/vault_git.ps1 +19 -0
- package/.opencode/mednotes/scripts/vault/vault_git.py +3107 -0
- package/.opencode/mednotes/scripts/vault/vault_git.sh +18 -0
- package/.opencode/mednotes/scripts/vault/vault_precommit.ps1 +19 -0
- package/.opencode/mednotes/scripts/vault/vault_precommit.sh +18 -0
- package/.opencode/mednotes/skills/THIRD_PARTY_NOTICES.md +45 -0
- package/.opencode/mednotes/skills/create-medical-flashcards/SKILL.md +113 -0
- package/.opencode/mednotes/skills/create-medical-note/SKILL.md +90 -0
- package/.opencode/mednotes/skills/enrich-medical-note/SKILL.md +120 -0
- package/.opencode/mednotes/skills/fix-medical-wiki/SKILL.md +559 -0
- package/.opencode/mednotes/skills/link-medical-wiki/SKILL.md +224 -0
- package/.opencode/mednotes/skills/obsidian-cli/SKILL.md +118 -0
- package/.opencode/mednotes/skills/obsidian-markdown/SKILL.md +207 -0
- package/.opencode/mednotes/skills/obsidian-markdown/references/CALLOUTS.md +58 -0
- package/.opencode/mednotes/skills/obsidian-markdown/references/EMBEDS.md +63 -0
- package/.opencode/mednotes/skills/obsidian-markdown/references/PROPERTIES.md +61 -0
- package/.opencode/mednotes/skills/obsidian-ops/SKILL.md +136 -0
- package/.opencode/mednotes/skills/pdf-library/SKILL.md +45 -0
- package/.opencode/mednotes/skills/process-medical-chats/SKILL.md +246 -0
- package/.opencode/mednotes/skills/workflow-report/SKILL.md +100 -0
- package/.opencode/mednotes/src/mednotes/__init__.py +5 -0
- package/.opencode/mednotes/src/mednotes/domains/__init__.py +5 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/README.md +26 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/__init__.py +2 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/build_demo_apkg.py +177 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/contracts.py +385 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/flashcards_machine.py +522 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/fsm.py +817 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/index.py +630 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/install_models.py +445 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/model.py +359 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/obsidian_links.py +135 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/obsidian_note_utils.py +546 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/pipeline.py +580 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/report.py +510 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/sources.py +682 -0
- package/.opencode/mednotes/src/mednotes/domains/flashcards/sync_rules.py +184 -0
- package/.opencode/mednotes/src/mednotes/domains/history/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/history/history_fsm.py +852 -0
- package/.opencode/mednotes/src/mednotes/domains/history/history_machine.py +453 -0
- package/.opencode/mednotes/src/mednotes/domains/setup/__init__.py +7 -0
- package/.opencode/mednotes/src/mednotes/domains/setup/setup_fsm.py +808 -0
- package/.opencode/mednotes/src/mednotes/domains/setup/setup_machine.py +973 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/README.md +64 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/api.py +668 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/batch_state.py +102 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/atomicity/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/atomicity/atomicity.py +877 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/body_link/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/body_link/body_linker.py +1562 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/effects/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/effects/effect_adapters.py +949 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/effects/fix_wiki_runtime_adapters.py +433 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/graph/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/graph/coverage.py +413 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/graph/graph.py +396 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/graph/graph_fixes.py +161 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/hygiene/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/hygiene/hygiene.py +483 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/__init__.py +2 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/anchors.py +185 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/core/__init__.py +0 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/core/cache.py +223 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/core/config.py +131 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/core/download.py +224 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/core/frontmatter.py +59 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/core/insert.py +227 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/core/local_import.py +54 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/sources/__init__.py +42 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/sources/web_profiles.py +99 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/sources/web_search.py +203 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/illustrate/sources/wikimedia.py +102 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/markdown/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/markdown/markdown_db_adapter.mjs +434 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/markdown/markdown_node_runtime.py +274 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/markdown/markdown_query.py +227 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/artifacts.py +605 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/canonical_merge.py +277 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/markdown_zones.py +85 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/meaning_planner.py +307 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_iter.py +67 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_merge.py +278 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_plan.py +409 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_policy.py +22 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_style/__init__.py +79 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_style/fixes.py +264 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_style/frontmatter.py +435 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_style/models.py +208 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_style/prompts.py +37 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_style/tables.py +236 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/note_style/validate.py +404 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/provenance.py +478 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/raw_chats.py +273 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/notes/sources_backfill.py +235 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/__init__.py +10 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/anchors.py +16 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/captions.py +47 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/cli.py +179 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/cloud.py +52 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/config.py +196 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/context_packets.py +76 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/db.py +81 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/doctor.py +102 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/figure_ids.py +42 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/ingest.py +326 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/insert.py +316 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/mentions.py +57 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/ocr.py +71 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/paths.py +35 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/pdf_engine.py +77 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/schema.py +155 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/search.py +188 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/tui/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/tui/app.py +89 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/tui/image_backend.py +29 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/pdf/tui/state.py +65 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/publish/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/publish/publish.py +1139 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/publish/publish_receipts.py +365 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/publish/publish_recovery.py +240 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/agent_behavior_corpus.py +2069 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/agent_report_validation.py +4448 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/agent_run_audit.py +852 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/architect_prompt_eval.py +341 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/body_linker_eval.py +240 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/curator_output_validation.py +175 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/curator_prompt_eval.py +865 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/quality/triager_prompt_eval.py +1295 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/related_notes/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/related_notes/related_notes.py +1920 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/related_notes/related_notes_headless.py +1186 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/specialist/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/specialist/plan_attestation.py +148 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/specialist/specialist_receipts.py +360 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/specialist/specialist_runtime.py +52 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/specialist/specialist_task_runner.py +2470 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/style/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/style/style.py +1952 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/subagents/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/subagents/agents.py +1767 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/alias_projection.py +331 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/link_terms.py +151 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/llm_disambiguation.py +182 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/taxonomy/__init__.py +116 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/taxonomy/audit.py +201 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/taxonomy/migration.py +314 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/taxonomy/normalize.py +72 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/taxonomy/policy.py +135 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/taxonomy/resolve.py +413 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/taxonomy/schema.py +157 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/taxonomy/status.py +137 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/vocabulary_bootstrap.py +509 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/vocabulary_curator_batch.py +1115 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/vocabulary_ingestion.py +632 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/vocabulary_map.py +930 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/capabilities/vocabulary/vocabulary_recovery.py +1388 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/cli.py +6665 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/common.py +69 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/config.py +210 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/__init__.py +74 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/agent_report.py +242 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/agent_run_audit.py +196 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/agents.py +601 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/curator.py +256 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/effect_payloads.py +519 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/happy_path.py +190 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/link_git.py +110 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/link_runtime_artifact.py +52 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/note_plan.py +75 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/paths.py +114 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/public_report.py +53 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/publish.py +111 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/raw_coverage.py +217 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/related_notes.py +136 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/related_notes_headless.py +153 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/related_notes_runtime.py +395 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/schema_registry.py +637 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/specialist.py +432 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/status.py +62 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/style_rewrite.py +568 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/vocabulary_ingestion.py +223 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/workflow_blockers.py +510 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/workflow_guardrails.py +637 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/workflow_outcomes.py +121 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/contracts/workflow_receipts.py +100 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/__main__.py +4 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/cli.py +275 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/__init__.py +2 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/candidates.py +193 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/cli.py +189 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/gemini.py +220 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/inputs.py +120 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/models.py +34 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/parsing.py +48 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/prompts.py +216 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/quality.py +54 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/reporting.py +24 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/runner.py +433 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/utils.py +39 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/enrich/workflow/vault_guard_bridge.py +17 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_context_packets.py +454 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_decision_projection.py +133 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_effects.py +1260 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_fsm.py +2768 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_machine.py +1588 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_plan.py +306 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_primary_objective.py +316 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_problem.py +153 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_receipt_evidence.py +306 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_states.py +290 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/fix_wiki_user_report.py +342 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/fix_wiki/health.py +6332 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/link_fsm.py +1119 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/link_git.py +638 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/link_machine.py +1106 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/link_retry_governance.py +374 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/link_runtime_result.py +485 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/link_triggers.py +183 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/linking.py +2758 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/reference_repair.py +718 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link/related_notes_fsm.py +1855 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link_related/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/link_related/link_related_machine.py +834 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/process_chats/__init__.py +1 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/process_chats/process_chats_fsm.py +1592 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/process_chats/process_chats_machine.py +3097 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/process_chats/process_chats_primary_objective.py +28 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/flows/process_chats/process_chats_runtime_result.py +185 -0
- package/.opencode/mednotes/src/mednotes/domains/wiki/performance.py +97 -0
- package/.opencode/mednotes/src/mednotes/kernel/__init__.py +6 -0
- package/.opencode/mednotes/src/mednotes/kernel/agent_directive.py +336 -0
- package/.opencode/mednotes/src/mednotes/kernel/base.py +51 -0
- package/.opencode/mednotes/src/mednotes/kernel/blockers.py +39 -0
- package/.opencode/mednotes/src/mednotes/kernel/effect_executor.py +55 -0
- package/.opencode/mednotes/src/mednotes/kernel/effect_intent.py +69 -0
- package/.opencode/mednotes/src/mednotes/kernel/effects.py +160 -0
- package/.opencode/mednotes/src/mednotes/kernel/errors.py +38 -0
- package/.opencode/mednotes/src/mednotes/kernel/fsm_event.py +35 -0
- package/.opencode/mednotes/src/mednotes/kernel/fsm_model.py +55 -0
- package/.opencode/mednotes/src/mednotes/kernel/fsm_transition_result.py +75 -0
- package/.opencode/mednotes/src/mednotes/kernel/guardrails.py +188 -0
- package/.opencode/mednotes/src/mednotes/kernel/progress.py +319 -0
- package/.opencode/mednotes/src/mednotes/kernel/public_report.py +346 -0
- package/.opencode/mednotes/src/mednotes/kernel/state_machine.py +164 -0
- package/.opencode/mednotes/src/mednotes/kernel/workflow.py +619 -0
- package/.opencode/mednotes/src/mednotes/platform/__init__.py +5 -0
- package/.opencode/mednotes/src/mednotes/platform/backup_policy.py +382 -0
- package/.opencode/mednotes/src/mednotes/platform/feedback/__init__.py +62 -0
- package/.opencode/mednotes/src/mednotes/platform/feedback/cli.py +275 -0
- package/.opencode/mednotes/src/mednotes/platform/feedback/contracts.py +83 -0
- package/.opencode/mednotes/src/mednotes/platform/feedback/core.py +4168 -0
- package/.opencode/mednotes/src/mednotes/platform/feedback/integrity.py +989 -0
- package/.opencode/mednotes/src/mednotes/platform/feedback/operational_contract.py +2293 -0
- package/.opencode/mednotes/src/mednotes/platform/feedback/telemetry.py +875 -0
- package/.opencode/mednotes/src/mednotes/platform/feedback/telemetry_config.py +65 -0
- package/.opencode/mednotes/src/mednotes/platform/opencode_runtime_config.py +182 -0
- package/.opencode/mednotes/src/mednotes/platform/paths/__init__.py +1560 -0
- package/.opencode/mednotes/src/mednotes/platform/secrets.py +89 -0
- package/.opencode/mednotes/src/mednotes/platform/user_config.py +103 -0
- package/.opencode/mednotes/src/mednotes/platform/vault_guard.py +214 -0
- package/.opencode/mednotes/uv.lock +932 -0
- package/.opencode/mednotes.generated.json +395 -0
- package/.opencode/opencode.json +31 -0
- package/.opencode/plugins/mednotes-fsm.mjs +7 -0
- package/.opencode/plugins/mednotes_hook/adapters/antigravity.mjs +169 -0
- package/.opencode/plugins/mednotes_hook/adapters/harness_payload.mjs +103 -0
- package/.opencode/plugins/mednotes_hook/adapters/opencode_plugin.mjs +341 -0
- package/.opencode/plugins/mednotes_hook/adapters/opencode_user_config_sync.mjs +177 -0
- package/.opencode/plugins/mednotes_hook/anki_preflight.mjs +214 -0
- package/.opencode/plugins/mednotes_hook/cli.mjs +143 -0
- package/.opencode/plugins/mednotes_hook/diagnostics.mjs +11 -0
- package/.opencode/plugins/mednotes_hook/domain/agent_directive_core.mjs +160 -0
- package/.opencode/plugins/mednotes_hook/fsm_directive.mjs +1470 -0
- package/.opencode/plugins/mednotes_hook/hook_errors.mjs +120 -0
- package/.opencode/plugins/mednotes_hook/retention.mjs +114 -0
- package/.opencode/plugins/mednotes_hook/runtime.mjs +174 -0
- package/.opencode/plugins/mednotes_hook/telemetry_capture.mjs +511 -0
- package/.opencode/plugins/mednotes_hook/vault_guard.mjs +624 -0
- package/AGENTS.md +57 -0
- package/README.md +194 -0
- package/adapters/antigravity/agents.json +80 -0
- package/adapters/antigravity/templates/med-chat-triager.md +214 -0
- package/adapters/antigravity/templates/med-flashcard-maker.md +72 -0
- package/adapters/antigravity/templates/med-knowledge-architect.md +241 -0
- package/adapters/antigravity/templates/med-link-graph-curator.md +187 -0
- package/adapters/antigravity/templates/med-publish-guard.md +71 -0
- package/adapters/gemini-cli/gemini-extension.json +14 -0
- package/adapters/gemini-cli/package.json +15 -0
- package/adapters/gemini-cli/pyproject.toml +48 -0
- package/bin/mednotes-opencode.mjs +155 -0
- package/contracts/agents.json +116 -0
- package/core/agents/med-chat-triager.md +197 -0
- package/core/agents/med-flashcard-maker.md +56 -0
- package/core/agents/med-knowledge-architect.md +224 -0
- package/core/agents/med-link-graph-curator.md +171 -0
- package/core/agents/med-publish-guard.md +55 -0
- package/core/commands/flashcards.toml +22 -0
- package/core/commands/mednotes/create.toml +22 -0
- package/core/commands/mednotes/enrich.toml +24 -0
- package/core/commands/mednotes/fix-wiki.toml +24 -0
- package/core/commands/mednotes/history.toml +19 -0
- package/core/commands/mednotes/link-body.toml +22 -0
- package/core/commands/mednotes/link-related.toml +24 -0
- package/core/commands/mednotes/link.toml +24 -0
- package/core/commands/mednotes/pdf-library.toml +24 -0
- package/core/commands/mednotes/process-chats.toml +20 -0
- package/core/commands/mednotes/setup.toml +18 -0
- package/core/commands/mednotes/status.toml +24 -0
- package/core/commands/mednotes/telemetry.toml +24 -0
- package/core/commands/report.toml +23 -0
- package/core/skills/THIRD_PARTY_NOTICES.md +45 -0
- package/core/skills/create-medical-flashcards/SKILL.md +113 -0
- package/core/skills/create-medical-note/SKILL.md +90 -0
- package/core/skills/enrich-medical-note/SKILL.md +120 -0
- package/core/skills/fix-medical-wiki/SKILL.md +559 -0
- package/core/skills/link-medical-wiki/SKILL.md +224 -0
- package/core/skills/obsidian-cli/SKILL.md +118 -0
- package/core/skills/obsidian-markdown/SKILL.md +207 -0
- package/core/skills/obsidian-markdown/references/CALLOUTS.md +58 -0
- package/core/skills/obsidian-markdown/references/EMBEDS.md +63 -0
- package/core/skills/obsidian-markdown/references/PROPERTIES.md +61 -0
- package/core/skills/obsidian-ops/SKILL.md +136 -0
- package/core/skills/pdf-library/SKILL.md +45 -0
- package/core/skills/process-medical-chats/SKILL.md +246 -0
- package/core/skills/workflow-report/SKILL.md +100 -0
- package/package.json +45 -0
|
@@ -0,0 +1,1260 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Annotated, Literal, NamedTuple, cast
|
|
4
|
+
|
|
5
|
+
from pydantic import ConfigDict, Field, TypeAdapter, field_validator, model_validator
|
|
6
|
+
|
|
7
|
+
from mednotes.domains.wiki.contracts.effect_payloads import (
|
|
8
|
+
RelatedNotesRecoveryEffectPayload,
|
|
9
|
+
RelatedNotesRecoveryStateEffectPayload,
|
|
10
|
+
WaitExternalEffectPayload,
|
|
11
|
+
)
|
|
12
|
+
from mednotes.domains.wiki.contracts.workflow_outcomes import WorkflowDecision, decision_from_payload
|
|
13
|
+
from mednotes.domains.wiki.flows.fix_wiki.fix_wiki_fsm import (
|
|
14
|
+
FIX_WIKI_WORKFLOW,
|
|
15
|
+
FixWikiDiagnosisLane,
|
|
16
|
+
FixWikiFsmFacts,
|
|
17
|
+
FixWikiState,
|
|
18
|
+
)
|
|
19
|
+
from mednotes.domains.wiki.flows.fix_wiki.fix_wiki_machine import (
|
|
20
|
+
AtomicitySplitAppliedEvent,
|
|
21
|
+
AtomicitySplitBlockedEvent,
|
|
22
|
+
DeterministicRepairsAppliedEvent,
|
|
23
|
+
DeterministicRepairsBlockedEvent,
|
|
24
|
+
FinalValidationFailedEvent,
|
|
25
|
+
FinalValidationFoundMoreWorkEvent,
|
|
26
|
+
FinalValidationPassedEvent,
|
|
27
|
+
FinalValidationWarningsEvent,
|
|
28
|
+
FixWikiBoundaryEventAdapter,
|
|
29
|
+
FixWikiEvent,
|
|
30
|
+
FixWikiMachine,
|
|
31
|
+
LinkCompletedEvent,
|
|
32
|
+
LinkerBlockedEvent,
|
|
33
|
+
LinkGraphBlockedEvent,
|
|
34
|
+
MergeAppliedEvent,
|
|
35
|
+
MergeBlockedEvent,
|
|
36
|
+
RelatedNotesBlockedEvent,
|
|
37
|
+
RelatedNotesExportCompletedEvent,
|
|
38
|
+
RelatedNotesObsidianNotReadyEvent,
|
|
39
|
+
RelatedNotesQuotaWaitEvent,
|
|
40
|
+
RollbackCompletedEvent,
|
|
41
|
+
RollbackFailedEvent,
|
|
42
|
+
SetupBootstrapBlockedEvent,
|
|
43
|
+
SetupBootstrapReadyEvent,
|
|
44
|
+
StyleRewriteAppliedEvent,
|
|
45
|
+
StyleRewriteBlockedEvent,
|
|
46
|
+
StyleRewriteCapacityWaitEvent,
|
|
47
|
+
StyleRewriteReviewRequiredEvent,
|
|
48
|
+
StyleRewriteSpecialistCompletedEvent,
|
|
49
|
+
TaxonomyAppliedEvent,
|
|
50
|
+
TaxonomyBlockedEvent,
|
|
51
|
+
TaxonomyDecisionRequiredEvent,
|
|
52
|
+
VaultGuardBlockedEvent,
|
|
53
|
+
VaultGuardReadyEvent,
|
|
54
|
+
VocabularyAppliedEvent,
|
|
55
|
+
VocabularyCuratorCompletedEvent,
|
|
56
|
+
VocabularyEvalNeedsReviewEvent,
|
|
57
|
+
VocabularyIntegrityFailedEvent,
|
|
58
|
+
)
|
|
59
|
+
from mednotes.domains.wiki.flows.fix_wiki.fix_wiki_states import FIX_WIKI_DIAGNOSIS_PRIORITY
|
|
60
|
+
from mednotes.kernel.base import ContractModel, JsonObject, JsonObjectAdapter
|
|
61
|
+
from mednotes.kernel.effects import WorkflowEffect, WorkflowEffectKind, WorkflowEffectResult, WorkflowEffectStatus
|
|
62
|
+
from mednotes.kernel.fsm_model import WorkflowModel
|
|
63
|
+
from mednotes.kernel.state_machine import send_workflow_event
|
|
64
|
+
from mednotes.kernel.workflow import HumanDecisionPacket
|
|
65
|
+
|
|
66
|
+
RELATED_NOTES_EXTERNAL_RETRY_REASONS = frozenset(
|
|
67
|
+
{
|
|
68
|
+
"related_notes_headless_quota_exhausted",
|
|
69
|
+
"related_notes_headless_time_budget_exhausted",
|
|
70
|
+
}
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class _FixWikiEffectErrorContextFields(ContractModel):
|
|
75
|
+
"""Typed lens for effect error fields that influence recovery state."""
|
|
76
|
+
|
|
77
|
+
model_config = ConfigDict(extra="ignore")
|
|
78
|
+
|
|
79
|
+
blocked_reason: str = ""
|
|
80
|
+
root_cause: str = ""
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class _FixWikiLinkChildDiagnosticFields(ContractModel):
|
|
84
|
+
"""Typed diagnostic fields exposed by the child `/mednotes:link` FSM result."""
|
|
85
|
+
|
|
86
|
+
model_config = ConfigDict(extra="ignore")
|
|
87
|
+
|
|
88
|
+
related_notes_recovery_state: RelatedNotesRecoveryStateEffectPayload | None = None
|
|
89
|
+
|
|
90
|
+
@field_validator("related_notes_recovery_state", mode="before")
|
|
91
|
+
@classmethod
|
|
92
|
+
def _coerce_recovery_state(cls, value: object) -> RelatedNotesRecoveryStateEffectPayload | None:
|
|
93
|
+
if value in (None, {}):
|
|
94
|
+
return None
|
|
95
|
+
if isinstance(value, RelatedNotesRecoveryStateEffectPayload):
|
|
96
|
+
return value
|
|
97
|
+
return RelatedNotesRecoveryStateEffectPayload.from_payload(value)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class _FixWikiLinkChildPayloadFields(ContractModel):
|
|
101
|
+
"""Typed boundary for the child link FSM payload consumed by fix-wiki."""
|
|
102
|
+
|
|
103
|
+
model_config = ConfigDict(extra="ignore")
|
|
104
|
+
|
|
105
|
+
schema_id: str = Field(default="", alias="schema")
|
|
106
|
+
diagnostic_context: _FixWikiLinkChildDiagnosticFields = Field(default_factory=_FixWikiLinkChildDiagnosticFields)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class FixWikiEffectRuntimeUpdate(ContractModel):
|
|
110
|
+
"""Typed effect-result update before rebuilding canonical fix-wiki facts."""
|
|
111
|
+
|
|
112
|
+
model_config = ConfigDict(extra="forbid")
|
|
113
|
+
|
|
114
|
+
next_action: str = ""
|
|
115
|
+
diagnostic_context: JsonObject = Field(default_factory=dict)
|
|
116
|
+
error_context: JsonObject = Field(default_factory=dict)
|
|
117
|
+
external_wait_reason_code: str = ""
|
|
118
|
+
external_wait_resume_action: str = ""
|
|
119
|
+
external_wait_payload: JsonObject = Field(default_factory=dict)
|
|
120
|
+
related_notes_blocked: bool = Field(default=False, strict=True)
|
|
121
|
+
related_notes_recovery_state: JsonObject = Field(default_factory=dict)
|
|
122
|
+
human_decision_required: bool = Field(default=False, strict=True)
|
|
123
|
+
decision: WorkflowDecision | None = None
|
|
124
|
+
human_decision_packet: HumanDecisionPacket | None = None
|
|
125
|
+
pending_effects: list[WorkflowEffect] | None = None
|
|
126
|
+
linker_blocked: bool = Field(default=False, strict=True)
|
|
127
|
+
failed: bool = Field(default=False, strict=True)
|
|
128
|
+
failed_reason_code: str = ""
|
|
129
|
+
|
|
130
|
+
def to_runtime_update(self) -> dict[str, object]:
|
|
131
|
+
return cast(
|
|
132
|
+
dict[str, object],
|
|
133
|
+
self.model_dump(mode="python", exclude_defaults=True, exclude_none=True),
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _style_rewrite_payload_text(payload: JsonObject, key: str) -> str:
|
|
138
|
+
"""Read required work-item identity without letting raw JSON drive policy."""
|
|
139
|
+
|
|
140
|
+
if key not in payload:
|
|
141
|
+
return ""
|
|
142
|
+
value = payload[key]
|
|
143
|
+
if value is None:
|
|
144
|
+
return ""
|
|
145
|
+
return str(value).strip()
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class FixWikiStyleRewriteSpecialistEffectRequest(ContractModel):
|
|
149
|
+
"""Typed request for the executable specialist effect emitted by fix-wiki.
|
|
150
|
+
|
|
151
|
+
The effect authorizes parallel specialist authoring, but it never authorizes
|
|
152
|
+
parallel vault mutation. Each item is an independent temp-output proposal;
|
|
153
|
+
the CLI remains the only serial apply boundary.
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
model_config = ConfigDict(extra="forbid")
|
|
157
|
+
|
|
158
|
+
run_id: str
|
|
159
|
+
work_id: str = Field(min_length=1)
|
|
160
|
+
target_path: str = Field(min_length=1)
|
|
161
|
+
agent_name: str = Field(default="med-knowledge-architect", min_length=1)
|
|
162
|
+
title: str = ""
|
|
163
|
+
plan_path: str = ""
|
|
164
|
+
manifest_path: str = ""
|
|
165
|
+
current_batch_items: list[JsonObject] = Field(min_length=1)
|
|
166
|
+
authoring_max_concurrency: int = Field(default=1, ge=1)
|
|
167
|
+
|
|
168
|
+
@model_validator(mode="after")
|
|
169
|
+
def batch_items_have_unique_operational_owners(self) -> FixWikiStyleRewriteSpecialistEffectRequest:
|
|
170
|
+
work_ids = [_style_rewrite_payload_text(item, "work_id") for item in self.current_batch_items]
|
|
171
|
+
target_paths = [_style_rewrite_payload_text(item, "target_path") for item in self.current_batch_items]
|
|
172
|
+
temp_outputs = [_style_rewrite_payload_text(item, "temp_output") for item in self.current_batch_items]
|
|
173
|
+
if any(not value for value in work_ids):
|
|
174
|
+
raise ValueError("style rewrite batch items require work_id")
|
|
175
|
+
if any(not value for value in target_paths):
|
|
176
|
+
raise ValueError("style rewrite batch items require target_path")
|
|
177
|
+
if any(not value for value in temp_outputs):
|
|
178
|
+
raise ValueError("style rewrite batch items require temp_output")
|
|
179
|
+
if len(work_ids) != len(set(work_ids)):
|
|
180
|
+
raise ValueError("style rewrite batch work_id values must be unique")
|
|
181
|
+
if len(target_paths) != len(set(target_paths)):
|
|
182
|
+
raise ValueError("style rewrite batch target_path values must be unique")
|
|
183
|
+
if len(temp_outputs) != len(set(temp_outputs)):
|
|
184
|
+
raise ValueError("style rewrite batch temp_output values must be unique")
|
|
185
|
+
if work_ids[0] != self.work_id:
|
|
186
|
+
raise ValueError("style rewrite effect work_id must match the first batch item")
|
|
187
|
+
if target_paths[0] != self.target_path:
|
|
188
|
+
raise ValueError("style rewrite effect target_path must match the first batch item")
|
|
189
|
+
return self
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class _StyleRewriteWorkItemForEffect(ContractModel):
|
|
193
|
+
"""Minimal style-rewrite work item shape needed to launch a specialist."""
|
|
194
|
+
|
|
195
|
+
model_config = ConfigDict(extra="ignore")
|
|
196
|
+
|
|
197
|
+
work_id: str = ""
|
|
198
|
+
target_path: str = ""
|
|
199
|
+
agent: str = ""
|
|
200
|
+
title: str = ""
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class _StyleRewritePlanForEffect(ContractModel):
|
|
204
|
+
"""Typed plan slice for deriving specialist effects inside the effects domain."""
|
|
205
|
+
|
|
206
|
+
model_config = ConfigDict(extra="ignore")
|
|
207
|
+
|
|
208
|
+
status: str = ""
|
|
209
|
+
agent: str = ""
|
|
210
|
+
max_concurrency: int = Field(default=1, ge=0)
|
|
211
|
+
work_items: list[_StyleRewriteWorkItemForEffect] = Field(default_factory=list)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
class _StyleRewritePlanPayloadForEffect(ContractModel):
|
|
215
|
+
"""Raw JSON work-item payloads preserved after the typed plan slice passes."""
|
|
216
|
+
|
|
217
|
+
model_config = ConfigDict(extra="ignore")
|
|
218
|
+
|
|
219
|
+
work_items: list[JsonObject] = Field(default_factory=list)
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
class _StyleRewriteBlockerGroupForEffect(ContractModel):
|
|
223
|
+
"""Authorization group proving the style rewrite lane can run automatically."""
|
|
224
|
+
|
|
225
|
+
model_config = ConfigDict(extra="ignore")
|
|
226
|
+
|
|
227
|
+
route: str = ""
|
|
228
|
+
automatic: bool = Field(default=False, strict=True)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
class _StyleRewriteBlockerResolutionForEffect(ContractModel):
|
|
232
|
+
"""Typed blocker-resolution slice used before emitting a specialist effect."""
|
|
233
|
+
|
|
234
|
+
model_config = ConfigDict(extra="ignore")
|
|
235
|
+
|
|
236
|
+
groups: list[_StyleRewriteBlockerGroupForEffect] = Field(default_factory=list)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
class _StyleRewritePendingEffectSource(ContractModel):
|
|
240
|
+
"""Runtime facts accepted by the effect module before it emits executable work."""
|
|
241
|
+
|
|
242
|
+
model_config = ConfigDict(extra="ignore")
|
|
243
|
+
|
|
244
|
+
run_id: str = Field(min_length=1)
|
|
245
|
+
requested_apply: bool = Field(default=False, strict=True)
|
|
246
|
+
effective_apply: bool = Field(default=False, strict=True)
|
|
247
|
+
requires_llm_rewrite_count: int = Field(default=0, ge=0, strict=True)
|
|
248
|
+
style_rewrite_plan: JsonObject | None = None
|
|
249
|
+
blocker_resolution: _StyleRewriteBlockerResolutionForEffect | None = None
|
|
250
|
+
style_rewrite_plan_path: str = ""
|
|
251
|
+
style_rewrite_manifest_path: str = ""
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
class SetupBootstrapEffectPayload(ContractModel):
|
|
255
|
+
kind: Literal["setup_bootstrap"] = "setup_bootstrap"
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
class VaultGuardEffectPayload(ContractModel):
|
|
259
|
+
kind: Literal["vault_guard"] = "vault_guard"
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
class DeterministicRepairsEffectPayload(ContractModel):
|
|
263
|
+
kind: Literal["deterministic_repairs"] = "deterministic_repairs"
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
class StyleRewriteEffectPayload(ContractModel):
|
|
267
|
+
kind: Literal["style_rewrite"] = "style_rewrite"
|
|
268
|
+
work_id: str = ""
|
|
269
|
+
target_path: str = ""
|
|
270
|
+
operation_payload: JsonObject = Field(default_factory=dict)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
class StyleRewriteApplyEffectPayload(ContractModel):
|
|
274
|
+
kind: Literal["style_rewrite_apply"] = "style_rewrite_apply"
|
|
275
|
+
work_id: str = ""
|
|
276
|
+
target_path: str = ""
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
class TaxonomyEffectPayload(ContractModel):
|
|
280
|
+
kind: Literal["taxonomy"] = "taxonomy"
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
class VocabularyCuratorEffectPayload(ContractModel):
|
|
284
|
+
kind: Literal["vocabulary_curator"] = "vocabulary_curator"
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
class VocabularyEvalEffectPayload(ContractModel):
|
|
288
|
+
kind: Literal["vocabulary_eval"] = "vocabulary_eval"
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
class VocabularyApplyEffectPayload(ContractModel):
|
|
292
|
+
kind: Literal["vocabulary_apply"] = "vocabulary_apply"
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
class AtomicitySplitEffectPayload(ContractModel):
|
|
296
|
+
kind: Literal["atomicity_split"] = "atomicity_split"
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
class MergeEffectPayload(ContractModel):
|
|
300
|
+
kind: Literal["merge"] = "merge"
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
class RelatedNotesEffectPayload(ContractModel):
|
|
304
|
+
kind: Literal["related_notes"] = "related_notes"
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
class LinkEffectPayload(ContractModel):
|
|
308
|
+
kind: Literal["link"] = "link"
|
|
309
|
+
diagnose: bool = False
|
|
310
|
+
apply: bool = False
|
|
311
|
+
no_related_notes: bool = False
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
class FinalValidationEffectPayload(ContractModel):
|
|
315
|
+
kind: Literal["final_validation"] = "final_validation"
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
class RollbackEffectPayload(ContractModel):
|
|
319
|
+
kind: Literal["rollback"] = "rollback"
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
class WaitExternalIntentPayload(ContractModel):
|
|
323
|
+
"""Internal effect intent keyed by `kind`; the canonical WAIT_EXTERNAL contract lives in WorkflowEffect.payload."""
|
|
324
|
+
|
|
325
|
+
kind: Literal["wait_external"] = "wait_external"
|
|
326
|
+
reason_code: str = ""
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
class HumanDecisionEffectPayload(ContractModel):
|
|
330
|
+
kind: Literal["human_decision"] = "human_decision"
|
|
331
|
+
reason_code: str = ""
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
FixWikiEffectPayload = Annotated[
|
|
335
|
+
SetupBootstrapEffectPayload
|
|
336
|
+
| VaultGuardEffectPayload
|
|
337
|
+
| DeterministicRepairsEffectPayload
|
|
338
|
+
| StyleRewriteEffectPayload
|
|
339
|
+
| StyleRewriteApplyEffectPayload
|
|
340
|
+
| TaxonomyEffectPayload
|
|
341
|
+
| VocabularyCuratorEffectPayload
|
|
342
|
+
| VocabularyEvalEffectPayload
|
|
343
|
+
| VocabularyApplyEffectPayload
|
|
344
|
+
| AtomicitySplitEffectPayload
|
|
345
|
+
| MergeEffectPayload
|
|
346
|
+
| RelatedNotesEffectPayload
|
|
347
|
+
| LinkEffectPayload
|
|
348
|
+
| FinalValidationEffectPayload
|
|
349
|
+
| RollbackEffectPayload
|
|
350
|
+
| WaitExternalIntentPayload
|
|
351
|
+
| HumanDecisionEffectPayload,
|
|
352
|
+
Field(discriminator="kind"),
|
|
353
|
+
]
|
|
354
|
+
FixWikiEffectPayloadAdapter = TypeAdapter(FixWikiEffectPayload)
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
class FixWikiSetupBootstrapReadyOutcome(ContractModel):
|
|
358
|
+
code: Literal["setup.bootstrap.ready"] = "setup.bootstrap.ready"
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
class FixWikiSetupBootstrapBlockedOutcome(ContractModel):
|
|
362
|
+
code: Literal["setup.bootstrap.blocked"] = "setup.bootstrap.blocked"
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
class FixWikiVaultGuardReadyOutcome(ContractModel):
|
|
366
|
+
code: Literal["vault_guard.ready"] = "vault_guard.ready"
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
class FixWikiVaultGuardBlockedOutcome(ContractModel):
|
|
370
|
+
code: Literal["vault_guard.blocked"] = "vault_guard.blocked"
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
class FixWikiDeterministicAppliedOutcome(ContractModel):
|
|
374
|
+
code: Literal["deterministic.applied"] = "deterministic.applied"
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
class FixWikiDeterministicBlockedOutcome(ContractModel):
|
|
378
|
+
code: Literal["deterministic.blocked"] = "deterministic.blocked"
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
class FixWikiStyleSpecialistCompletedOutcome(ContractModel):
|
|
382
|
+
code: Literal["style.specialist_completed"] = "style.specialist_completed"
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
class FixWikiStyleCapacityWaitOutcome(ContractModel):
|
|
386
|
+
code: Literal["style.capacity_wait"] = "style.capacity_wait"
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
class FixWikiStyleReviewRequiredOutcome(ContractModel):
|
|
390
|
+
code: Literal["style.review_required"] = "style.review_required"
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
class FixWikiStyleApplyCompletedOutcome(ContractModel):
|
|
394
|
+
code: Literal["style.apply_completed"] = "style.apply_completed"
|
|
395
|
+
|
|
396
|
+
|
|
397
|
+
class FixWikiStyleBlockedOutcome(ContractModel):
|
|
398
|
+
code: Literal["style.blocked"] = "style.blocked"
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
class FixWikiTaxonomyDecisionRequiredOutcome(ContractModel):
|
|
402
|
+
code: Literal["taxonomy.decision_required"] = "taxonomy.decision_required"
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
class FixWikiTaxonomyAppliedOutcome(ContractModel):
|
|
406
|
+
code: Literal["taxonomy.applied"] = "taxonomy.applied"
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
class FixWikiTaxonomyBlockedOutcome(ContractModel):
|
|
410
|
+
code: Literal["taxonomy.blocked"] = "taxonomy.blocked"
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
class FixWikiVocabularyCuratorCompletedOutcome(ContractModel):
|
|
414
|
+
code: Literal["vocabulary.curator_completed"] = "vocabulary.curator_completed"
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
class FixWikiVocabularyEvalNeedsReviewOutcome(ContractModel):
|
|
418
|
+
code: Literal["vocabulary.eval_needs_review"] = "vocabulary.eval_needs_review"
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
class FixWikiVocabularyAppliedOutcome(ContractModel):
|
|
422
|
+
code: Literal["vocabulary.applied"] = "vocabulary.applied"
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
class FixWikiVocabularyIntegrityFailedOutcome(ContractModel):
|
|
426
|
+
code: Literal["vocabulary.integrity_failed"] = "vocabulary.integrity_failed"
|
|
427
|
+
|
|
428
|
+
|
|
429
|
+
class FixWikiAtomicitySplitAppliedOutcome(ContractModel):
|
|
430
|
+
code: Literal["atomicity.split_applied"] = "atomicity.split_applied"
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
class FixWikiAtomicityBlockedOutcome(ContractModel):
|
|
434
|
+
code: Literal["atomicity.blocked"] = "atomicity.blocked"
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
class FixWikiMergeAppliedOutcome(ContractModel):
|
|
438
|
+
code: Literal["merge.applied"] = "merge.applied"
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
class FixWikiMergeBlockedOutcome(ContractModel):
|
|
442
|
+
code: Literal["merge.blocked"] = "merge.blocked"
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
class FixWikiRelatedNotesExportCompletedOutcome(ContractModel):
|
|
446
|
+
code: Literal["related_notes.export_completed"] = "related_notes.export_completed"
|
|
447
|
+
|
|
448
|
+
|
|
449
|
+
class FixWikiRelatedNotesQuotaWaitOutcome(ContractModel):
|
|
450
|
+
code: Literal["related_notes.quota_wait"] = "related_notes.quota_wait"
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
class FixWikiRelatedNotesObsidianNotReadyOutcome(ContractModel):
|
|
454
|
+
code: Literal["related_notes.obsidian_not_ready"] = "related_notes.obsidian_not_ready"
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
class FixWikiRelatedNotesBlockedOutcome(ContractModel):
|
|
458
|
+
code: Literal["related_notes.blocked"] = "related_notes.blocked"
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
class FixWikiLinkCompletedOutcome(ContractModel):
|
|
462
|
+
code: Literal["link.completed"] = "link.completed"
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
class FixWikiLinkBlockedOutcome(ContractModel):
|
|
466
|
+
code: Literal["link.blocked"] = "link.blocked"
|
|
467
|
+
|
|
468
|
+
|
|
469
|
+
class FixWikiLinkGraphBlockedOutcome(ContractModel):
|
|
470
|
+
code: Literal["graph_blocked"] = "graph_blocked"
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
class FixWikiLinkerBlockedOutcome(ContractModel):
|
|
474
|
+
code: Literal["linker_blocked"] = "linker_blocked"
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
class FixWikiFinalValidationPassedOutcome(ContractModel):
|
|
478
|
+
code: Literal["final_validation.passed"] = "final_validation.passed"
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
class FixWikiFinalValidationWarningsOutcome(ContractModel):
|
|
482
|
+
code: Literal["final_validation.warnings"] = "final_validation.warnings"
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
class FixWikiFinalValidationFoundMoreWorkOutcome(ContractModel):
|
|
486
|
+
code: Literal["final_validation.found_more_work"] = "final_validation.found_more_work"
|
|
487
|
+
pending_lanes: list[FixWikiDiagnosisLane] = Field(min_length=1)
|
|
488
|
+
selected_lane: FixWikiDiagnosisLane
|
|
489
|
+
|
|
490
|
+
@field_validator("pending_lanes")
|
|
491
|
+
@classmethod
|
|
492
|
+
def _reject_duplicate_lanes(cls, value: list[FixWikiDiagnosisLane]) -> list[FixWikiDiagnosisLane]:
|
|
493
|
+
if len(set(value)) != len(value):
|
|
494
|
+
raise ValueError("duplicate diagnosis lanes are not allowed")
|
|
495
|
+
expected_order = sorted(value, key=FIX_WIKI_DIAGNOSIS_PRIORITY.index)
|
|
496
|
+
if list(value) != expected_order:
|
|
497
|
+
raise ValueError("diagnosis lanes must follow FIX_WIKI_DIAGNOSIS_PRIORITY")
|
|
498
|
+
return value
|
|
499
|
+
|
|
500
|
+
@model_validator(mode="after")
|
|
501
|
+
def _selected_lane_must_be_priority_head(self) -> FixWikiFinalValidationFoundMoreWorkOutcome:
|
|
502
|
+
if self.selected_lane != self.pending_lanes[0]:
|
|
503
|
+
raise ValueError("selected_lane must match the highest-priority pending lane")
|
|
504
|
+
return self
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
class FixWikiFinalValidationFailedOutcome(ContractModel):
|
|
508
|
+
code: Literal["final_validation.failed"] = "final_validation.failed"
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
class FixWikiRollbackCompletedOutcome(ContractModel):
|
|
512
|
+
code: Literal["rollback.completed"] = "rollback.completed"
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
class FixWikiRollbackFailedOutcome(ContractModel):
|
|
516
|
+
code: Literal["rollback.failed"] = "rollback.failed"
|
|
517
|
+
|
|
518
|
+
|
|
519
|
+
FixWikiEffectOutcome = Annotated[
|
|
520
|
+
FixWikiSetupBootstrapReadyOutcome
|
|
521
|
+
| FixWikiSetupBootstrapBlockedOutcome
|
|
522
|
+
| FixWikiVaultGuardReadyOutcome
|
|
523
|
+
| FixWikiVaultGuardBlockedOutcome
|
|
524
|
+
| FixWikiDeterministicAppliedOutcome
|
|
525
|
+
| FixWikiDeterministicBlockedOutcome
|
|
526
|
+
| FixWikiStyleSpecialistCompletedOutcome
|
|
527
|
+
| FixWikiStyleCapacityWaitOutcome
|
|
528
|
+
| FixWikiStyleReviewRequiredOutcome
|
|
529
|
+
| FixWikiStyleApplyCompletedOutcome
|
|
530
|
+
| FixWikiStyleBlockedOutcome
|
|
531
|
+
| FixWikiTaxonomyDecisionRequiredOutcome
|
|
532
|
+
| FixWikiTaxonomyAppliedOutcome
|
|
533
|
+
| FixWikiTaxonomyBlockedOutcome
|
|
534
|
+
| FixWikiVocabularyCuratorCompletedOutcome
|
|
535
|
+
| FixWikiVocabularyEvalNeedsReviewOutcome
|
|
536
|
+
| FixWikiVocabularyAppliedOutcome
|
|
537
|
+
| FixWikiVocabularyIntegrityFailedOutcome
|
|
538
|
+
| FixWikiAtomicitySplitAppliedOutcome
|
|
539
|
+
| FixWikiAtomicityBlockedOutcome
|
|
540
|
+
| FixWikiMergeAppliedOutcome
|
|
541
|
+
| FixWikiMergeBlockedOutcome
|
|
542
|
+
| FixWikiRelatedNotesExportCompletedOutcome
|
|
543
|
+
| FixWikiRelatedNotesQuotaWaitOutcome
|
|
544
|
+
| FixWikiRelatedNotesObsidianNotReadyOutcome
|
|
545
|
+
| FixWikiRelatedNotesBlockedOutcome
|
|
546
|
+
| FixWikiLinkCompletedOutcome
|
|
547
|
+
| FixWikiLinkBlockedOutcome
|
|
548
|
+
| FixWikiLinkGraphBlockedOutcome
|
|
549
|
+
| FixWikiLinkerBlockedOutcome
|
|
550
|
+
| FixWikiFinalValidationPassedOutcome
|
|
551
|
+
| FixWikiFinalValidationWarningsOutcome
|
|
552
|
+
| FixWikiFinalValidationFoundMoreWorkOutcome
|
|
553
|
+
| FixWikiFinalValidationFailedOutcome
|
|
554
|
+
| FixWikiRollbackCompletedOutcome
|
|
555
|
+
| FixWikiRollbackFailedOutcome,
|
|
556
|
+
Field(discriminator="code"),
|
|
557
|
+
]
|
|
558
|
+
FixWikiEffectOutcomeAdapter = TypeAdapter(FixWikiEffectOutcome)
|
|
559
|
+
|
|
560
|
+
|
|
561
|
+
class EffectReturnEventKey(NamedTuple):
|
|
562
|
+
kind: WorkflowEffectKind
|
|
563
|
+
target: str
|
|
564
|
+
origin_state: str
|
|
565
|
+
effect_code: str
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
# Exact effect-result matrix: no status/payload fallback is allowed to decide the
|
|
569
|
+
# next event. Adapter payloads may remain as opaque audit evidence only; this
|
|
570
|
+
# table is the domain-owned routing contract.
|
|
571
|
+
EFFECT_RETURN_EVENT_MATRIX: dict[EffectReturnEventKey, type[FixWikiEvent]] = {
|
|
572
|
+
EffectReturnEventKey(
|
|
573
|
+
WorkflowEffectKind.RUN_SUBWORKFLOW,
|
|
574
|
+
"/mednotes:setup",
|
|
575
|
+
FixWikiState.ENVIRONMENT_PATHS_MISSING.value,
|
|
576
|
+
"setup.bootstrap.ready",
|
|
577
|
+
): SetupBootstrapReadyEvent,
|
|
578
|
+
EffectReturnEventKey(
|
|
579
|
+
WorkflowEffectKind.RUN_SUBWORKFLOW,
|
|
580
|
+
"/mednotes:setup",
|
|
581
|
+
FixWikiState.ENVIRONMENT_PATHS_MISSING.value,
|
|
582
|
+
"setup.bootstrap.blocked",
|
|
583
|
+
): SetupBootstrapBlockedEvent,
|
|
584
|
+
EffectReturnEventKey(
|
|
585
|
+
WorkflowEffectKind.RUN_SUBWORKFLOW,
|
|
586
|
+
"/mednotes:setup",
|
|
587
|
+
FixWikiState.ENVIRONMENT_WIKI_DIR_MISSING.value,
|
|
588
|
+
"setup.bootstrap.ready",
|
|
589
|
+
): SetupBootstrapReadyEvent,
|
|
590
|
+
EffectReturnEventKey(
|
|
591
|
+
WorkflowEffectKind.RUN_SUBWORKFLOW,
|
|
592
|
+
"/mednotes:setup",
|
|
593
|
+
FixWikiState.ENVIRONMENT_WIKI_DIR_MISSING.value,
|
|
594
|
+
"setup.bootstrap.blocked",
|
|
595
|
+
): SetupBootstrapBlockedEvent,
|
|
596
|
+
EffectReturnEventKey(
|
|
597
|
+
WorkflowEffectKind.RUN_SUBWORKFLOW,
|
|
598
|
+
"/mednotes:setup",
|
|
599
|
+
FixWikiState.ENVIRONMENT_WINDOWS_PATH_OR_VENV_BLOCKED.value,
|
|
600
|
+
"setup.bootstrap.ready",
|
|
601
|
+
): SetupBootstrapReadyEvent,
|
|
602
|
+
EffectReturnEventKey(
|
|
603
|
+
WorkflowEffectKind.RUN_SUBWORKFLOW,
|
|
604
|
+
"/mednotes:setup",
|
|
605
|
+
FixWikiState.ENVIRONMENT_WINDOWS_PATH_OR_VENV_BLOCKED.value,
|
|
606
|
+
"setup.bootstrap.blocked",
|
|
607
|
+
): SetupBootstrapBlockedEvent,
|
|
608
|
+
EffectReturnEventKey(
|
|
609
|
+
WorkflowEffectKind.RUN_SUBWORKFLOW,
|
|
610
|
+
"fix_wiki.vault_guard",
|
|
611
|
+
FixWikiState.VAULT_GUARD_RUNNING.value,
|
|
612
|
+
"vault_guard.ready",
|
|
613
|
+
): VaultGuardReadyEvent,
|
|
614
|
+
EffectReturnEventKey(
|
|
615
|
+
WorkflowEffectKind.RUN_SUBWORKFLOW,
|
|
616
|
+
"fix_wiki.vault_guard",
|
|
617
|
+
FixWikiState.VAULT_GUARD_RUNNING.value,
|
|
618
|
+
"vault_guard.blocked",
|
|
619
|
+
): VaultGuardBlockedEvent,
|
|
620
|
+
EffectReturnEventKey(
|
|
621
|
+
WorkflowEffectKind.RUN_SUBWORKFLOW,
|
|
622
|
+
"fix_wiki.deterministic_repairs",
|
|
623
|
+
FixWikiState.DETERMINISTIC_REPAIRS_RUNNING.value,
|
|
624
|
+
"deterministic.applied",
|
|
625
|
+
): DeterministicRepairsAppliedEvent,
|
|
626
|
+
EffectReturnEventKey(
|
|
627
|
+
WorkflowEffectKind.RUN_SUBWORKFLOW,
|
|
628
|
+
"fix_wiki.deterministic_repairs",
|
|
629
|
+
FixWikiState.DETERMINISTIC_REPAIRS_RUNNING.value,
|
|
630
|
+
"deterministic.blocked",
|
|
631
|
+
): DeterministicRepairsBlockedEvent,
|
|
632
|
+
EffectReturnEventKey(
|
|
633
|
+
WorkflowEffectKind.CALL_SPECIALIST_MODEL,
|
|
634
|
+
"med-knowledge-architect",
|
|
635
|
+
FixWikiState.STYLE_REWRITE_SPECIALIST_REQUESTED.value,
|
|
636
|
+
"style.specialist_completed",
|
|
637
|
+
): StyleRewriteSpecialistCompletedEvent,
|
|
638
|
+
EffectReturnEventKey(
|
|
639
|
+
WorkflowEffectKind.CALL_SPECIALIST_MODEL,
|
|
640
|
+
"med-knowledge-architect",
|
|
641
|
+
FixWikiState.STYLE_REWRITE_SPECIALIST_REQUESTED.value,
|
|
642
|
+
"style.capacity_wait",
|
|
643
|
+
): StyleRewriteCapacityWaitEvent,
|
|
644
|
+
EffectReturnEventKey(
|
|
645
|
+
WorkflowEffectKind.CALL_SPECIALIST_MODEL,
|
|
646
|
+
"med-knowledge-architect",
|
|
647
|
+
FixWikiState.STYLE_REWRITE_SPECIALIST_REQUESTED.value,
|
|
648
|
+
"style.review_required",
|
|
649
|
+
): StyleRewriteReviewRequiredEvent,
|
|
650
|
+
EffectReturnEventKey(
|
|
651
|
+
WorkflowEffectKind.RUN_SUBWORKFLOW,
|
|
652
|
+
"fix_wiki.style_rewrite_apply",
|
|
653
|
+
FixWikiState.STYLE_REWRITE_APPLY_RUNNING.value,
|
|
654
|
+
"style.apply_completed",
|
|
655
|
+
): StyleRewriteAppliedEvent,
|
|
656
|
+
EffectReturnEventKey(
|
|
657
|
+
WorkflowEffectKind.RUN_SUBWORKFLOW,
|
|
658
|
+
"fix_wiki.style_rewrite_apply",
|
|
659
|
+
FixWikiState.STYLE_REWRITE_APPLY_RUNNING.value,
|
|
660
|
+
"style.blocked",
|
|
661
|
+
): StyleRewriteBlockedEvent,
|
|
662
|
+
EffectReturnEventKey(
|
|
663
|
+
WorkflowEffectKind.RUN_SUBWORKFLOW,
|
|
664
|
+
"fix_wiki.taxonomy",
|
|
665
|
+
FixWikiState.TAXONOMY_APPLY_RUNNING.value,
|
|
666
|
+
"taxonomy.decision_required",
|
|
667
|
+
): TaxonomyDecisionRequiredEvent,
|
|
668
|
+
EffectReturnEventKey(
|
|
669
|
+
WorkflowEffectKind.RUN_SUBWORKFLOW,
|
|
670
|
+
"fix_wiki.taxonomy",
|
|
671
|
+
FixWikiState.TAXONOMY_APPLY_RUNNING.value,
|
|
672
|
+
"taxonomy.applied",
|
|
673
|
+
): TaxonomyAppliedEvent,
|
|
674
|
+
EffectReturnEventKey(
|
|
675
|
+
WorkflowEffectKind.RUN_SUBWORKFLOW,
|
|
676
|
+
"fix_wiki.taxonomy",
|
|
677
|
+
FixWikiState.TAXONOMY_APPLY_RUNNING.value,
|
|
678
|
+
"taxonomy.blocked",
|
|
679
|
+
): TaxonomyBlockedEvent,
|
|
680
|
+
EffectReturnEventKey(
|
|
681
|
+
WorkflowEffectKind.RUN_SUBWORKFLOW,
|
|
682
|
+
"fix_wiki.vocabulary_curator",
|
|
683
|
+
FixWikiState.VOCABULARY_CURATOR_RUNNING.value,
|
|
684
|
+
"vocabulary.curator_completed",
|
|
685
|
+
): VocabularyCuratorCompletedEvent,
|
|
686
|
+
EffectReturnEventKey(
|
|
687
|
+
WorkflowEffectKind.RUN_SUBWORKFLOW,
|
|
688
|
+
"fix_wiki.vocabulary_curator",
|
|
689
|
+
FixWikiState.VOCABULARY_SEMANTIC_INGESTION_PENDING.value,
|
|
690
|
+
"vocabulary.curator_completed",
|
|
691
|
+
): VocabularyCuratorCompletedEvent,
|
|
692
|
+
EffectReturnEventKey(
|
|
693
|
+
WorkflowEffectKind.RUN_SUBWORKFLOW,
|
|
694
|
+
"fix_wiki.vocabulary_eval",
|
|
695
|
+
FixWikiState.VOCABULARY_EVAL_RUNNING.value,
|
|
696
|
+
"vocabulary.eval_needs_review",
|
|
697
|
+
): VocabularyEvalNeedsReviewEvent,
|
|
698
|
+
EffectReturnEventKey(
|
|
699
|
+
WorkflowEffectKind.RUN_SUBWORKFLOW,
|
|
700
|
+
"fix_wiki.vocabulary_apply",
|
|
701
|
+
FixWikiState.VOCABULARY_APPLY_RUNNING.value,
|
|
702
|
+
"vocabulary.applied",
|
|
703
|
+
): VocabularyAppliedEvent,
|
|
704
|
+
EffectReturnEventKey(
|
|
705
|
+
WorkflowEffectKind.RUN_SUBWORKFLOW,
|
|
706
|
+
"fix_wiki.vocabulary_apply",
|
|
707
|
+
FixWikiState.VOCABULARY_APPLY_RUNNING.value,
|
|
708
|
+
"vocabulary.integrity_failed",
|
|
709
|
+
): VocabularyIntegrityFailedEvent,
|
|
710
|
+
EffectReturnEventKey(
|
|
711
|
+
WorkflowEffectKind.RUN_SUBWORKFLOW,
|
|
712
|
+
"fix_wiki.atomicity_split",
|
|
713
|
+
FixWikiState.ATOMICITY_SPLIT_RUNNING.value,
|
|
714
|
+
"atomicity.split_applied",
|
|
715
|
+
): AtomicitySplitAppliedEvent,
|
|
716
|
+
EffectReturnEventKey(
|
|
717
|
+
WorkflowEffectKind.RUN_SUBWORKFLOW,
|
|
718
|
+
"fix_wiki.atomicity_split",
|
|
719
|
+
FixWikiState.ATOMICITY_SPLIT_RUNNING.value,
|
|
720
|
+
"atomicity.blocked",
|
|
721
|
+
): AtomicitySplitBlockedEvent,
|
|
722
|
+
EffectReturnEventKey(
|
|
723
|
+
WorkflowEffectKind.RUN_SUBWORKFLOW,
|
|
724
|
+
"fix_wiki.note_merge",
|
|
725
|
+
FixWikiState.MERGE_RUNNING.value,
|
|
726
|
+
"merge.applied",
|
|
727
|
+
): MergeAppliedEvent,
|
|
728
|
+
EffectReturnEventKey(
|
|
729
|
+
WorkflowEffectKind.RUN_SUBWORKFLOW,
|
|
730
|
+
"fix_wiki.note_merge",
|
|
731
|
+
FixWikiState.MERGE_RUNNING.value,
|
|
732
|
+
"merge.blocked",
|
|
733
|
+
): MergeBlockedEvent,
|
|
734
|
+
EffectReturnEventKey(
|
|
735
|
+
WorkflowEffectKind.RUN_SUBWORKFLOW,
|
|
736
|
+
"related_notes.export",
|
|
737
|
+
FixWikiState.RELATED_NOTES_EXPORT_RUNNING.value,
|
|
738
|
+
"related_notes.export_completed",
|
|
739
|
+
): RelatedNotesExportCompletedEvent,
|
|
740
|
+
EffectReturnEventKey(
|
|
741
|
+
WorkflowEffectKind.RUN_SUBWORKFLOW,
|
|
742
|
+
"related_notes.export",
|
|
743
|
+
FixWikiState.RELATED_NOTES_EXPORT_RUNNING.value,
|
|
744
|
+
"related_notes.quota_wait",
|
|
745
|
+
): RelatedNotesQuotaWaitEvent,
|
|
746
|
+
EffectReturnEventKey(
|
|
747
|
+
WorkflowEffectKind.RUN_SUBWORKFLOW,
|
|
748
|
+
"related_notes.export",
|
|
749
|
+
FixWikiState.RELATED_NOTES_EXPORT_RUNNING.value,
|
|
750
|
+
"related_notes.obsidian_not_ready",
|
|
751
|
+
): RelatedNotesObsidianNotReadyEvent,
|
|
752
|
+
EffectReturnEventKey(
|
|
753
|
+
WorkflowEffectKind.RUN_SUBWORKFLOW,
|
|
754
|
+
"related_notes.export",
|
|
755
|
+
FixWikiState.RELATED_NOTES_EXPORT_RUNNING.value,
|
|
756
|
+
"related_notes.blocked",
|
|
757
|
+
): RelatedNotesBlockedEvent,
|
|
758
|
+
EffectReturnEventKey(
|
|
759
|
+
WorkflowEffectKind.RUN_SUBWORKFLOW,
|
|
760
|
+
"/mednotes:link",
|
|
761
|
+
FixWikiState.LINK_RUN_REQUESTED.value,
|
|
762
|
+
"link.completed",
|
|
763
|
+
): LinkCompletedEvent,
|
|
764
|
+
EffectReturnEventKey(
|
|
765
|
+
WorkflowEffectKind.RUN_SUBWORKFLOW,
|
|
766
|
+
"/mednotes:link",
|
|
767
|
+
FixWikiState.LINK_RUN_REQUESTED.value,
|
|
768
|
+
"link.blocked",
|
|
769
|
+
): LinkerBlockedEvent,
|
|
770
|
+
EffectReturnEventKey(
|
|
771
|
+
WorkflowEffectKind.RUN_SUBWORKFLOW,
|
|
772
|
+
"/mednotes:link",
|
|
773
|
+
FixWikiState.LINK_RUN_REQUESTED.value,
|
|
774
|
+
"graph_blocked",
|
|
775
|
+
): LinkGraphBlockedEvent,
|
|
776
|
+
EffectReturnEventKey(
|
|
777
|
+
WorkflowEffectKind.RUN_SUBWORKFLOW,
|
|
778
|
+
"/mednotes:link",
|
|
779
|
+
FixWikiState.LINK_RUN_REQUESTED.value,
|
|
780
|
+
"linker_blocked",
|
|
781
|
+
): LinkerBlockedEvent,
|
|
782
|
+
EffectReturnEventKey(
|
|
783
|
+
WorkflowEffectKind.RUN_SUBWORKFLOW,
|
|
784
|
+
"/mednotes:link",
|
|
785
|
+
FixWikiState.LINK_RUN_REQUESTED.value,
|
|
786
|
+
"related_notes.quota_wait",
|
|
787
|
+
): RelatedNotesQuotaWaitEvent,
|
|
788
|
+
EffectReturnEventKey(
|
|
789
|
+
WorkflowEffectKind.RUN_SUBWORKFLOW,
|
|
790
|
+
"fix_wiki.final_validation",
|
|
791
|
+
FixWikiState.FINAL_VALIDATION_RUNNING.value,
|
|
792
|
+
"final_validation.passed",
|
|
793
|
+
): FinalValidationPassedEvent,
|
|
794
|
+
EffectReturnEventKey(
|
|
795
|
+
WorkflowEffectKind.RUN_SUBWORKFLOW,
|
|
796
|
+
"fix_wiki.final_validation",
|
|
797
|
+
FixWikiState.FINAL_VALIDATION_RUNNING.value,
|
|
798
|
+
"final_validation.warnings",
|
|
799
|
+
): FinalValidationWarningsEvent,
|
|
800
|
+
EffectReturnEventKey(
|
|
801
|
+
WorkflowEffectKind.RUN_SUBWORKFLOW,
|
|
802
|
+
"fix_wiki.final_validation",
|
|
803
|
+
FixWikiState.FINAL_VALIDATION_RUNNING.value,
|
|
804
|
+
"final_validation.found_more_work",
|
|
805
|
+
): FinalValidationFoundMoreWorkEvent,
|
|
806
|
+
EffectReturnEventKey(
|
|
807
|
+
WorkflowEffectKind.RUN_SUBWORKFLOW,
|
|
808
|
+
"fix_wiki.final_validation",
|
|
809
|
+
FixWikiState.FINAL_VALIDATION_RUNNING.value,
|
|
810
|
+
"final_validation.failed",
|
|
811
|
+
): FinalValidationFailedEvent,
|
|
812
|
+
EffectReturnEventKey(
|
|
813
|
+
WorkflowEffectKind.RUN_SUBWORKFLOW,
|
|
814
|
+
"fix_wiki.rollback",
|
|
815
|
+
FixWikiState.ROLLBACK_RUNNING.value,
|
|
816
|
+
"rollback.completed",
|
|
817
|
+
): RollbackCompletedEvent,
|
|
818
|
+
EffectReturnEventKey(
|
|
819
|
+
WorkflowEffectKind.RUN_SUBWORKFLOW,
|
|
820
|
+
"fix_wiki.rollback",
|
|
821
|
+
FixWikiState.ROLLBACK_RUNNING.value,
|
|
822
|
+
"rollback.failed",
|
|
823
|
+
): RollbackFailedEvent,
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
|
|
827
|
+
def fix_wiki_event_from_effect_result(model: object, result: WorkflowEffectResult) -> FixWikiEvent:
|
|
828
|
+
"""Convert one typed effect result into the next StateChart event.
|
|
829
|
+
|
|
830
|
+
`model` is accepted for the future persisted-run integration; the current
|
|
831
|
+
routing contract is intentionally derived from the effect identity and the
|
|
832
|
+
domain outcome code only.
|
|
833
|
+
"""
|
|
834
|
+
|
|
835
|
+
del model
|
|
836
|
+
outcome = FixWikiEffectOutcomeAdapter.validate_python(result.outcome.to_payload())
|
|
837
|
+
key = EffectReturnEventKey(
|
|
838
|
+
result.effect.kind,
|
|
839
|
+
result.effect.target,
|
|
840
|
+
result.effect.origin_state,
|
|
841
|
+
outcome.code,
|
|
842
|
+
)
|
|
843
|
+
event_cls = EFFECT_RETURN_EVENT_MATRIX.get(key)
|
|
844
|
+
if event_cls is None:
|
|
845
|
+
raise ValueError(f"missing fix-wiki effect return matrix row: {key}")
|
|
846
|
+
payload: JsonObject = {
|
|
847
|
+
"workflow": result.effect.workflow or FIX_WIKI_WORKFLOW,
|
|
848
|
+
"run_id": result.effect.run_id,
|
|
849
|
+
"current_state": result.effect.origin_state,
|
|
850
|
+
}
|
|
851
|
+
if isinstance(outcome, FixWikiFinalValidationFoundMoreWorkOutcome):
|
|
852
|
+
payload["pending_lanes"] = [lane.value for lane in outcome.pending_lanes]
|
|
853
|
+
payload["selected_lane"] = outcome.selected_lane.value
|
|
854
|
+
if event_cls is RelatedNotesQuotaWaitEvent:
|
|
855
|
+
recovery = _effect_related_notes_recovery_state(result)
|
|
856
|
+
if recovery is not None:
|
|
857
|
+
payload["related_notes_recovery_state"] = recovery
|
|
858
|
+
return event_cls.model_validate(payload)
|
|
859
|
+
|
|
860
|
+
|
|
861
|
+
def facts_after_effect_results(
|
|
862
|
+
facts: FixWikiFsmFacts,
|
|
863
|
+
effect_results: list[WorkflowEffectResult],
|
|
864
|
+
) -> FixWikiFsmFacts:
|
|
865
|
+
"""Fold typed effect results back into canonical fix-wiki facts.
|
|
866
|
+
|
|
867
|
+
The public workflow facade may execute the effects, but effect status
|
|
868
|
+
interpretation lives beside the effect contract so `health.py` does not
|
|
869
|
+
become a second recovery-state machine.
|
|
870
|
+
"""
|
|
871
|
+
|
|
872
|
+
if not effect_results:
|
|
873
|
+
return facts
|
|
874
|
+
statechart_facts = _facts_after_statechart_effect_results(facts, effect_results)
|
|
875
|
+
if statechart_facts is not None:
|
|
876
|
+
return statechart_facts
|
|
877
|
+
last = effect_results[-1]
|
|
878
|
+
diagnostics = _scrub_fsm_diagnostic_context(facts.diagnostic_context)
|
|
879
|
+
diagnostics["effect_results"] = [result.to_payload() for result in effect_results]
|
|
880
|
+
match last.status:
|
|
881
|
+
case WorkflowEffectStatus.WAITING_EXTERNAL:
|
|
882
|
+
update = FixWikiEffectRuntimeUpdate(
|
|
883
|
+
external_wait_reason_code=_effect_external_wait_reason(last),
|
|
884
|
+
external_wait_resume_action=last.resume_action or last.next_action,
|
|
885
|
+
external_wait_payload=last.to_payload(),
|
|
886
|
+
next_action=last.next_action or last.resume_action,
|
|
887
|
+
diagnostic_context=diagnostics,
|
|
888
|
+
)
|
|
889
|
+
recovery_state = _effect_related_notes_recovery_state(last)
|
|
890
|
+
if _is_related_notes_effect(last.effect) or recovery_state is not None:
|
|
891
|
+
update_payload = {
|
|
892
|
+
**update.model_dump(mode="python"),
|
|
893
|
+
"related_notes_blocked": True,
|
|
894
|
+
}
|
|
895
|
+
if recovery_state is not None:
|
|
896
|
+
update_payload["related_notes_recovery_state"] = recovery_state.to_payload()
|
|
897
|
+
update = FixWikiEffectRuntimeUpdate.model_validate(update_payload)
|
|
898
|
+
return facts.with_runtime_updates(update.to_runtime_update())
|
|
899
|
+
case WorkflowEffectStatus.WAITING_HUMAN:
|
|
900
|
+
decision = _workflow_decision_from_waiting_human_effect(last)
|
|
901
|
+
human_decision_required = decision.kind == "ask_human"
|
|
902
|
+
update = FixWikiEffectRuntimeUpdate(
|
|
903
|
+
human_decision_required=human_decision_required,
|
|
904
|
+
decision=decision,
|
|
905
|
+
human_decision_packet=last.human_decision_packet,
|
|
906
|
+
pending_effects=[],
|
|
907
|
+
next_action=last.next_action or last.resume_action or decision.next_action,
|
|
908
|
+
diagnostic_context=diagnostics,
|
|
909
|
+
error_context=last.error_context,
|
|
910
|
+
)
|
|
911
|
+
return facts.with_runtime_updates(update.to_runtime_update())
|
|
912
|
+
case WorkflowEffectStatus.BLOCKED:
|
|
913
|
+
update = _blocked_effect_runtime_update(last)
|
|
914
|
+
update = FixWikiEffectRuntimeUpdate.model_validate(
|
|
915
|
+
{**update.model_dump(mode="python"), "diagnostic_context": diagnostics}
|
|
916
|
+
)
|
|
917
|
+
return facts.with_runtime_updates(update.to_runtime_update())
|
|
918
|
+
case WorkflowEffectStatus.FAILED:
|
|
919
|
+
update = FixWikiEffectRuntimeUpdate(
|
|
920
|
+
failed=True,
|
|
921
|
+
failed_reason_code=_effect_result_reason(last, fallback="workflow_effect_failed"),
|
|
922
|
+
next_action=last.next_action,
|
|
923
|
+
diagnostic_context=diagnostics,
|
|
924
|
+
error_context=last.error_context,
|
|
925
|
+
)
|
|
926
|
+
return facts.with_runtime_updates(update.to_runtime_update())
|
|
927
|
+
case _:
|
|
928
|
+
update = FixWikiEffectRuntimeUpdate(diagnostic_context=diagnostics)
|
|
929
|
+
return facts.with_runtime_updates(update.to_runtime_update())
|
|
930
|
+
|
|
931
|
+
|
|
932
|
+
def _facts_after_statechart_effect_results(
|
|
933
|
+
facts: FixWikiFsmFacts,
|
|
934
|
+
effect_results: list[WorkflowEffectResult],
|
|
935
|
+
) -> FixWikiFsmFacts | None:
|
|
936
|
+
"""Apply effect outcomes as first-class FixWikiMachine events.
|
|
937
|
+
|
|
938
|
+
Adapters may execute effects, but they do not decide the post-effect state.
|
|
939
|
+
A typed domain outcome must map through `EFFECT_RETURN_EVENT_MATRIX` and then
|
|
940
|
+
through python-statemachine before it can become public workflow state.
|
|
941
|
+
"""
|
|
942
|
+
|
|
943
|
+
driving_results = [result for result in effect_results if _effect_result_drives_statechart(result)]
|
|
944
|
+
if not driving_results:
|
|
945
|
+
return None
|
|
946
|
+
|
|
947
|
+
diagnostics = _scrub_fsm_diagnostic_context(facts.diagnostic_context)
|
|
948
|
+
diagnostics["effect_results"] = [result.to_payload() for result in effect_results]
|
|
949
|
+
model = WorkflowModel.start(
|
|
950
|
+
workflow=FIX_WIKI_WORKFLOW,
|
|
951
|
+
run_id=facts.run_id,
|
|
952
|
+
initial_state=facts.initial_state.value,
|
|
953
|
+
)
|
|
954
|
+
machine = FixWikiMachine(model=model, state_field=WorkflowModel.STATECHART_STATE_FIELD)
|
|
955
|
+
send_workflow_event(machine, facts.event)
|
|
956
|
+
last_event: FixWikiEvent = facts.event
|
|
957
|
+
last_result: WorkflowEffectResult | None = None
|
|
958
|
+
for result in driving_results:
|
|
959
|
+
last_event = fix_wiki_event_from_effect_result(model, result)
|
|
960
|
+
send_workflow_event(machine, FixWikiBoundaryEventAdapter.validate_python(last_event.to_payload()))
|
|
961
|
+
last_result = result
|
|
962
|
+
|
|
963
|
+
runtime_update: dict[str, object] = {
|
|
964
|
+
"diagnostic_context": diagnostics,
|
|
965
|
+
"pending_effects": list(model.pending_effects),
|
|
966
|
+
}
|
|
967
|
+
if isinstance(last_event, RelatedNotesQuotaWaitEvent) and last_event.related_notes_recovery_state.status:
|
|
968
|
+
runtime_update.update(
|
|
969
|
+
{
|
|
970
|
+
"related_notes_blocked": True,
|
|
971
|
+
"related_notes_recovery_state": last_event.related_notes_recovery_state.to_payload(),
|
|
972
|
+
"external_wait_reason_code": last_event.related_notes_recovery_state.blocked_reason,
|
|
973
|
+
"external_wait_resume_action": last_event.related_notes_recovery_state.next_action,
|
|
974
|
+
}
|
|
975
|
+
)
|
|
976
|
+
elif isinstance(last_event, StyleRewriteCapacityWaitEvent) and last_result is not None:
|
|
977
|
+
resume_action = last_result.resume_action or last_result.next_action
|
|
978
|
+
runtime_update.update(
|
|
979
|
+
{
|
|
980
|
+
"external_wait_reason_code": _effect_result_reason(
|
|
981
|
+
last_result,
|
|
982
|
+
fallback="specialist_model_capacity_unavailable",
|
|
983
|
+
),
|
|
984
|
+
"external_wait_resume_action": resume_action,
|
|
985
|
+
"external_wait_payload": last_result.to_payload(),
|
|
986
|
+
"next_action": last_result.next_action or resume_action,
|
|
987
|
+
"error_context": last_result.error_context,
|
|
988
|
+
}
|
|
989
|
+
)
|
|
990
|
+
runtime = facts.runtime.__class__.model_validate(
|
|
991
|
+
{**facts.runtime.model_dump(mode="python"), **runtime_update}
|
|
992
|
+
)
|
|
993
|
+
event = FixWikiBoundaryEventAdapter.validate_python(last_event.to_payload())
|
|
994
|
+
return FixWikiFsmFacts(
|
|
995
|
+
run_id=facts.run_id,
|
|
996
|
+
initial_state=FixWikiState(event.current_state),
|
|
997
|
+
event=event,
|
|
998
|
+
runtime=runtime,
|
|
999
|
+
machine_effects=list(model.pending_effects),
|
|
1000
|
+
)
|
|
1001
|
+
|
|
1002
|
+
|
|
1003
|
+
def _effect_result_drives_statechart(result: WorkflowEffectResult) -> bool:
|
|
1004
|
+
"""Return true for effect kinds whose outcomes represent a new FSM event."""
|
|
1005
|
+
|
|
1006
|
+
if result.effect.kind in {WorkflowEffectKind.WAIT_EXTERNAL, WorkflowEffectKind.ASK_HUMAN}:
|
|
1007
|
+
return False
|
|
1008
|
+
return result.status in {
|
|
1009
|
+
WorkflowEffectStatus.COMPLETED,
|
|
1010
|
+
WorkflowEffectStatus.COMPLETED_WITH_WARNINGS,
|
|
1011
|
+
WorkflowEffectStatus.WAITING_AGENT,
|
|
1012
|
+
WorkflowEffectStatus.WAITING_EXTERNAL,
|
|
1013
|
+
WorkflowEffectStatus.WAITING_HUMAN,
|
|
1014
|
+
WorkflowEffectStatus.BLOCKED,
|
|
1015
|
+
WorkflowEffectStatus.FAILED,
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
|
|
1019
|
+
def _scrub_fsm_diagnostic_context(context: JsonObject) -> JsonObject:
|
|
1020
|
+
"""Keep diagnostics explanatory; executable routes live in FSM root contracts."""
|
|
1021
|
+
|
|
1022
|
+
diagnostics = dict(context)
|
|
1023
|
+
for key in (
|
|
1024
|
+
"orchestration_plan",
|
|
1025
|
+
"continuation_plan",
|
|
1026
|
+
"pending_effects",
|
|
1027
|
+
"action_directives",
|
|
1028
|
+
):
|
|
1029
|
+
diagnostics.pop(key, None)
|
|
1030
|
+
return diagnostics
|
|
1031
|
+
|
|
1032
|
+
|
|
1033
|
+
def _effect_result_reason(result: WorkflowEffectResult, *, fallback: str) -> str:
|
|
1034
|
+
"""Return the typed reason from an effect result without reading diagnostics."""
|
|
1035
|
+
|
|
1036
|
+
error_context = _FixWikiEffectErrorContextFields.model_validate(result.error_context)
|
|
1037
|
+
for value in (
|
|
1038
|
+
error_context.blocked_reason,
|
|
1039
|
+
error_context.root_cause,
|
|
1040
|
+
getattr(result.outcome, "reason_code", ""),
|
|
1041
|
+
):
|
|
1042
|
+
text = value.strip() if isinstance(value, str) else ""
|
|
1043
|
+
if text:
|
|
1044
|
+
return text
|
|
1045
|
+
return fallback
|
|
1046
|
+
|
|
1047
|
+
|
|
1048
|
+
def _blocked_effect_runtime_update(result: WorkflowEffectResult) -> FixWikiEffectRuntimeUpdate:
|
|
1049
|
+
"""Classify blocked effect results by the effect that actually blocked."""
|
|
1050
|
+
|
|
1051
|
+
reason = _effect_result_reason(result, fallback="workflow_effect_blocked")
|
|
1052
|
+
if result.effect.kind == WorkflowEffectKind.RUN_SUBWORKFLOW and result.effect.target == "/mednotes:link":
|
|
1053
|
+
return FixWikiEffectRuntimeUpdate(
|
|
1054
|
+
next_action=result.next_action,
|
|
1055
|
+
error_context=result.error_context,
|
|
1056
|
+
linker_blocked=True,
|
|
1057
|
+
)
|
|
1058
|
+
if _is_related_notes_effect(result.effect):
|
|
1059
|
+
recovery_state = _effect_related_notes_recovery_state(result)
|
|
1060
|
+
return FixWikiEffectRuntimeUpdate(
|
|
1061
|
+
next_action=result.next_action,
|
|
1062
|
+
error_context=result.error_context,
|
|
1063
|
+
related_notes_blocked=True,
|
|
1064
|
+
related_notes_recovery_state=recovery_state.to_payload() if recovery_state is not None else {},
|
|
1065
|
+
)
|
|
1066
|
+
return FixWikiEffectRuntimeUpdate(
|
|
1067
|
+
next_action=result.next_action,
|
|
1068
|
+
error_context=result.error_context,
|
|
1069
|
+
failed=True,
|
|
1070
|
+
failed_reason_code=reason,
|
|
1071
|
+
)
|
|
1072
|
+
|
|
1073
|
+
|
|
1074
|
+
def _workflow_decision_from_waiting_human_effect(result: WorkflowEffectResult) -> WorkflowDecision:
|
|
1075
|
+
"""Convert a typed ask-human effect result into the fix-wiki decision root."""
|
|
1076
|
+
|
|
1077
|
+
packet = result.human_decision_packet
|
|
1078
|
+
if packet is None:
|
|
1079
|
+
raise ValueError("waiting_human effect result requires human_decision_packet")
|
|
1080
|
+
next_action = result.next_action or result.resume_action or packet.resume_action
|
|
1081
|
+
return decision_from_payload(
|
|
1082
|
+
{
|
|
1083
|
+
"human_decision_packet": packet.to_payload(),
|
|
1084
|
+
"next_action": next_action,
|
|
1085
|
+
}
|
|
1086
|
+
)
|
|
1087
|
+
|
|
1088
|
+
|
|
1089
|
+
def _effect_related_notes_recovery_state(result: WorkflowEffectResult) -> RelatedNotesRecoveryStateEffectPayload | None:
|
|
1090
|
+
"""Return typed Related Notes recovery state from official effect payloads only."""
|
|
1091
|
+
|
|
1092
|
+
try:
|
|
1093
|
+
wait_payload = WaitExternalEffectPayload.from_effect_payload(result.payload)
|
|
1094
|
+
except ValueError:
|
|
1095
|
+
wait_payload = None
|
|
1096
|
+
if wait_payload is not None and wait_payload.related_notes_recovery_state is not None:
|
|
1097
|
+
return wait_payload.related_notes_recovery_state
|
|
1098
|
+
try:
|
|
1099
|
+
recovery_payload = RelatedNotesRecoveryEffectPayload.from_operation_payload(result.payload)
|
|
1100
|
+
except ValueError:
|
|
1101
|
+
return _link_child_related_notes_recovery_state(result)
|
|
1102
|
+
return recovery_payload.related_notes_recovery_state
|
|
1103
|
+
|
|
1104
|
+
|
|
1105
|
+
def _link_child_related_notes_recovery_state(
|
|
1106
|
+
result: WorkflowEffectResult,
|
|
1107
|
+
) -> RelatedNotesRecoveryStateEffectPayload | None:
|
|
1108
|
+
"""Read Related Notes progress from a typed child `/mednotes:link` FSM result."""
|
|
1109
|
+
|
|
1110
|
+
if result.effect.kind != WorkflowEffectKind.RUN_SUBWORKFLOW or result.effect.target != "/mednotes:link":
|
|
1111
|
+
return None
|
|
1112
|
+
try:
|
|
1113
|
+
child_payload = _FixWikiLinkChildPayloadFields.model_validate(result.payload)
|
|
1114
|
+
except ValueError:
|
|
1115
|
+
return None
|
|
1116
|
+
return child_payload.diagnostic_context.related_notes_recovery_state
|
|
1117
|
+
|
|
1118
|
+
|
|
1119
|
+
def _effect_external_wait_reason(result: WorkflowEffectResult) -> str:
|
|
1120
|
+
error_context = _FixWikiEffectErrorContextFields.model_validate(result.error_context)
|
|
1121
|
+
if error_context.blocked_reason:
|
|
1122
|
+
return error_context.blocked_reason
|
|
1123
|
+
recovery_state = _effect_related_notes_recovery_state(result)
|
|
1124
|
+
if recovery_state is not None and recovery_state.blocked_reason:
|
|
1125
|
+
return recovery_state.blocked_reason
|
|
1126
|
+
if result.effect.kind == WorkflowEffectKind.CALL_SPECIALIST_MODEL:
|
|
1127
|
+
return "specialist_model_capacity_unavailable"
|
|
1128
|
+
if _is_related_notes_effect(result.effect):
|
|
1129
|
+
return "related_notes_headless_quota_exhausted"
|
|
1130
|
+
return "workflow_external_wait"
|
|
1131
|
+
|
|
1132
|
+
|
|
1133
|
+
def _is_related_notes_effect(effect: WorkflowEffect) -> bool:
|
|
1134
|
+
"""Recognize Wiki-domain Related Notes work through target, not kernel kind."""
|
|
1135
|
+
|
|
1136
|
+
return effect.kind == WorkflowEffectKind.RUN_SUBWORKFLOW and effect.target in {
|
|
1137
|
+
"related_notes.export",
|
|
1138
|
+
"related_notes_export",
|
|
1139
|
+
"related_notes.section",
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
|
|
1143
|
+
def style_rewrite_specialist_effect_from_request(
|
|
1144
|
+
request: FixWikiStyleRewriteSpecialistEffectRequest,
|
|
1145
|
+
) -> WorkflowEffect:
|
|
1146
|
+
"""Build the rich specialist-call effect from a typed fix-wiki request."""
|
|
1147
|
+
|
|
1148
|
+
batch_items = request.current_batch_items
|
|
1149
|
+
authoring_max_concurrency = min(request.authoring_max_concurrency, len(batch_items))
|
|
1150
|
+
return WorkflowEffect(
|
|
1151
|
+
workflow=FIX_WIKI_WORKFLOW,
|
|
1152
|
+
run_id=request.run_id,
|
|
1153
|
+
effect_id=f"style-rewrite-{request.work_id}",
|
|
1154
|
+
origin_state=FixWikiState.STYLE_REWRITE_SPECIALIST_REQUESTED.value,
|
|
1155
|
+
kind=WorkflowEffectKind.CALL_SPECIALIST_MODEL,
|
|
1156
|
+
target=request.agent_name,
|
|
1157
|
+
payload={
|
|
1158
|
+
"kind": "style_rewrite",
|
|
1159
|
+
"work_id": request.work_id,
|
|
1160
|
+
"agent": request.agent_name,
|
|
1161
|
+
"title": request.title,
|
|
1162
|
+
"execution_mode": "parallel_authoring_serial_apply",
|
|
1163
|
+
"authoring_mode": "parallel",
|
|
1164
|
+
"authoring_max_concurrency": authoring_max_concurrency,
|
|
1165
|
+
"apply_mode": "serial",
|
|
1166
|
+
"serial_apply_required": True,
|
|
1167
|
+
"wait_for_all_authoring_outputs_before_apply": True,
|
|
1168
|
+
"current_batch_items": batch_items,
|
|
1169
|
+
"current_batch_item_count": len(batch_items),
|
|
1170
|
+
"plan_path": request.plan_path,
|
|
1171
|
+
"manifest_path": request.manifest_path,
|
|
1172
|
+
"style_rewrite_plan_path": request.plan_path,
|
|
1173
|
+
"style_rewrite_manifest_path": request.manifest_path,
|
|
1174
|
+
},
|
|
1175
|
+
requires_receipt=True,
|
|
1176
|
+
requires_attestation=True,
|
|
1177
|
+
model_policy={
|
|
1178
|
+
"policy": "medical_specialist_authoring.v1",
|
|
1179
|
+
"required_model_tier": "specialist",
|
|
1180
|
+
"preferred_model_tier": "pro",
|
|
1181
|
+
"forbid_flash_fallback": True,
|
|
1182
|
+
},
|
|
1183
|
+
)
|
|
1184
|
+
|
|
1185
|
+
|
|
1186
|
+
def pending_effect_payloads_from_fix_wiki_runtime_source(source: object) -> list[JsonObject]:
|
|
1187
|
+
"""Derive executable pending effects from typed runtime facts in the effects layer."""
|
|
1188
|
+
|
|
1189
|
+
fields = _StyleRewritePendingEffectSource.model_validate(source)
|
|
1190
|
+
if not fields.requested_apply or not fields.effective_apply:
|
|
1191
|
+
return []
|
|
1192
|
+
rewrite_plan = fields.style_rewrite_plan
|
|
1193
|
+
if rewrite_plan is None:
|
|
1194
|
+
return []
|
|
1195
|
+
typed_plan = _StyleRewritePlanForEffect.model_validate(rewrite_plan)
|
|
1196
|
+
plan_payload = _StyleRewritePlanPayloadForEffect.model_validate(rewrite_plan)
|
|
1197
|
+
if typed_plan.status != "ready":
|
|
1198
|
+
return []
|
|
1199
|
+
if fields.requires_llm_rewrite_count <= 0:
|
|
1200
|
+
return []
|
|
1201
|
+
blocker_resolution = fields.blocker_resolution
|
|
1202
|
+
if blocker_resolution is None or not _has_automatic_style_rewrite_group_for_effect(blocker_resolution):
|
|
1203
|
+
return []
|
|
1204
|
+
work_items = list(typed_plan.work_items)
|
|
1205
|
+
if not work_items:
|
|
1206
|
+
return []
|
|
1207
|
+
first_item = work_items[0]
|
|
1208
|
+
work_id = first_item.work_id.strip()
|
|
1209
|
+
target_path = first_item.target_path.strip()
|
|
1210
|
+
if not work_id or not target_path:
|
|
1211
|
+
return []
|
|
1212
|
+
agent_name = first_item.agent or typed_plan.agent or "med-knowledge-architect"
|
|
1213
|
+
current_batch_items: list[JsonObject] = []
|
|
1214
|
+
for index, work_item in enumerate(work_items):
|
|
1215
|
+
item_payload = (
|
|
1216
|
+
plan_payload.work_items[index]
|
|
1217
|
+
if index < len(plan_payload.work_items)
|
|
1218
|
+
else JsonObjectAdapter.validate_python(work_item.model_dump(exclude_defaults=True, exclude_none=True))
|
|
1219
|
+
)
|
|
1220
|
+
current_batch_items.append(item_payload)
|
|
1221
|
+
effect = style_rewrite_specialist_effect_from_request(
|
|
1222
|
+
FixWikiStyleRewriteSpecialistEffectRequest(
|
|
1223
|
+
run_id=fields.run_id,
|
|
1224
|
+
work_id=work_id,
|
|
1225
|
+
target_path=target_path,
|
|
1226
|
+
agent_name=agent_name,
|
|
1227
|
+
title=first_item.title,
|
|
1228
|
+
plan_path=fields.style_rewrite_plan_path,
|
|
1229
|
+
manifest_path=fields.style_rewrite_manifest_path,
|
|
1230
|
+
current_batch_items=current_batch_items,
|
|
1231
|
+
authoring_max_concurrency=typed_plan.max_concurrency or len(current_batch_items),
|
|
1232
|
+
)
|
|
1233
|
+
)
|
|
1234
|
+
return [effect.to_payload()]
|
|
1235
|
+
|
|
1236
|
+
|
|
1237
|
+
def _has_automatic_style_rewrite_group_for_effect(
|
|
1238
|
+
blocker_resolution: _StyleRewriteBlockerResolutionForEffect,
|
|
1239
|
+
) -> bool:
|
|
1240
|
+
return any(group.route == "style_rewrite" and group.automatic for group in blocker_resolution.groups)
|
|
1241
|
+
|
|
1242
|
+
|
|
1243
|
+
def missing_fix_wiki_effect_adapter_is_optional(effect: WorkflowEffect) -> bool:
|
|
1244
|
+
"""Return true only for agent-mediated effects that may wait for the agent."""
|
|
1245
|
+
|
|
1246
|
+
return effect.kind in {
|
|
1247
|
+
WorkflowEffectKind.ASK_HUMAN,
|
|
1248
|
+
WorkflowEffectKind.CALL_SPECIALIST_MODEL,
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
|
|
1252
|
+
def effect_result_stops_fix_wiki_execution(result: WorkflowEffectResult) -> bool:
|
|
1253
|
+
"""Return whether an effect result should pause the current fix-wiki pass."""
|
|
1254
|
+
|
|
1255
|
+
return result.status in {
|
|
1256
|
+
WorkflowEffectStatus.WAITING_EXTERNAL,
|
|
1257
|
+
WorkflowEffectStatus.WAITING_HUMAN,
|
|
1258
|
+
WorkflowEffectStatus.BLOCKED,
|
|
1259
|
+
WorkflowEffectStatus.FAILED,
|
|
1260
|
+
}
|