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,852 @@
|
|
|
1
|
+
"""Public FSM projection for `/mednotes:history`.
|
|
2
|
+
|
|
3
|
+
`HistoryMachine` owns the operational state. This module only projects that
|
|
4
|
+
state into the public workflow contract used by hooks, agents, and human-facing
|
|
5
|
+
reports; it does not execute restore IO or infer policy from adapter text.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from enum import StrEnum
|
|
11
|
+
from typing import Literal
|
|
12
|
+
|
|
13
|
+
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
|
14
|
+
from pydantic.json_schema import SkipJsonSchema
|
|
15
|
+
|
|
16
|
+
from mednotes.domains.history.history_machine import (
|
|
17
|
+
HistoryBlockedEvent,
|
|
18
|
+
HistoryFailedEvent,
|
|
19
|
+
HistoryMachine,
|
|
20
|
+
HistoryState,
|
|
21
|
+
PreviewRequiresConfirmationEvent,
|
|
22
|
+
RestoreAppliedEvent,
|
|
23
|
+
RestorePointsListedEvent,
|
|
24
|
+
StaleRestorePointDetectedEvent,
|
|
25
|
+
category_for_history_state,
|
|
26
|
+
)
|
|
27
|
+
from mednotes.kernel.agent_directive import (
|
|
28
|
+
AgentDirective,
|
|
29
|
+
agent_directive_from_progress_view_model,
|
|
30
|
+
assert_agent_directive_matches_progress,
|
|
31
|
+
)
|
|
32
|
+
from mednotes.kernel.base import ContractModel, JsonObject, JsonObjectAdapter
|
|
33
|
+
from mednotes.kernel.effects import WorkflowEffectKind
|
|
34
|
+
from mednotes.kernel.fsm_event import WorkflowEventLike
|
|
35
|
+
from mednotes.kernel.fsm_model import WorkflowModel
|
|
36
|
+
from mednotes.kernel.fsm_transition_result import WorkflowTransitionResult
|
|
37
|
+
from mednotes.kernel.progress import (
|
|
38
|
+
WorkflowProgressCounts,
|
|
39
|
+
WorkflowProgressEventType,
|
|
40
|
+
WorkflowProgressState,
|
|
41
|
+
WorkflowProgressStatus,
|
|
42
|
+
WorkflowProgressViewModel,
|
|
43
|
+
build_progress_view_model,
|
|
44
|
+
progress_state_from_view_model,
|
|
45
|
+
)
|
|
46
|
+
from mednotes.kernel.public_report import (
|
|
47
|
+
WorkflowPrimaryObjectiveSummary,
|
|
48
|
+
WorkflowPublicReport,
|
|
49
|
+
WorkflowReports,
|
|
50
|
+
assert_public_report_matches_progress,
|
|
51
|
+
public_progress_followup_line,
|
|
52
|
+
)
|
|
53
|
+
from mednotes.kernel.state_machine import (
|
|
54
|
+
WorkflowStateCategory,
|
|
55
|
+
WorkflowStateMachineSnapshot,
|
|
56
|
+
WorkflowTransition,
|
|
57
|
+
send_workflow_event,
|
|
58
|
+
)
|
|
59
|
+
from mednotes.kernel.workflow import (
|
|
60
|
+
HumanDecisionPacket,
|
|
61
|
+
VersionControlSafety,
|
|
62
|
+
WorkflowDecision,
|
|
63
|
+
assert_diagnostic_context_evidence_only,
|
|
64
|
+
diagnostic_context_evidence_only,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
HISTORY_WORKFLOW = "/mednotes:history"
|
|
68
|
+
HISTORY_FSM_SCHEMA = "medical-notes-workbench.history-fsm-result.v1"
|
|
69
|
+
HISTORY_AGENT_DIRECTIVE_FIELD = "agent_directive"
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class _HistoryMachineEventEvidence(ContractModel):
|
|
73
|
+
"""Typed lens over persisted history machine event evidence."""
|
|
74
|
+
|
|
75
|
+
model_config = ConfigDict(extra="ignore")
|
|
76
|
+
|
|
77
|
+
audit_evidence: JsonObject = Field(default_factory=dict)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
HISTORY_ALLOWED_ROOT_KEYS = frozenset(
|
|
81
|
+
{
|
|
82
|
+
"schema",
|
|
83
|
+
"workflow",
|
|
84
|
+
"run_id",
|
|
85
|
+
"state_machine_snapshot",
|
|
86
|
+
"progress_view_model",
|
|
87
|
+
"decision",
|
|
88
|
+
"human_decision_packet",
|
|
89
|
+
"receipt",
|
|
90
|
+
"reports",
|
|
91
|
+
"agent_directive",
|
|
92
|
+
"artifacts",
|
|
93
|
+
"version_control_safety",
|
|
94
|
+
"diagnostic_context",
|
|
95
|
+
"error_context",
|
|
96
|
+
}
|
|
97
|
+
)
|
|
98
|
+
HISTORY_FORBIDDEN_ROOT_KEYS = frozenset(
|
|
99
|
+
{
|
|
100
|
+
"status",
|
|
101
|
+
"phase",
|
|
102
|
+
"blocked_reason",
|
|
103
|
+
"next_action",
|
|
104
|
+
"required_inputs",
|
|
105
|
+
"human_decision_required",
|
|
106
|
+
"workflow_exit_code",
|
|
107
|
+
}
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class HistoryVaultOutcome(StrEnum):
|
|
112
|
+
"""Canonical outcome for the vault history/restore adapter boundary."""
|
|
113
|
+
|
|
114
|
+
RESTORE_POINTS_LISTED = "restore_points_listed"
|
|
115
|
+
PREVIEW_READY = "preview_ready"
|
|
116
|
+
RESTORE_APPLIED = "restore_applied"
|
|
117
|
+
STALE_RESTORE_POINT = "stale_restore_point"
|
|
118
|
+
BLOCKED = "blocked"
|
|
119
|
+
FAILED = "failed"
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class HistoryReceipt(ContractModel):
|
|
123
|
+
"""Receipt projection for history states, including non-terminal running states."""
|
|
124
|
+
|
|
125
|
+
schema_id: Literal["medical-notes-workbench.history-receipt.v1"] = Field(
|
|
126
|
+
default="medical-notes-workbench.history-receipt.v1",
|
|
127
|
+
alias="schema",
|
|
128
|
+
)
|
|
129
|
+
workflow: Literal["/mednotes:history"] = HISTORY_WORKFLOW
|
|
130
|
+
run_id: str = Field(min_length=1)
|
|
131
|
+
status: str = Field(min_length=1)
|
|
132
|
+
mutated: bool = False
|
|
133
|
+
next_action: str = ""
|
|
134
|
+
human_decision_required: bool = False
|
|
135
|
+
restored_file_count: int = Field(default=0, ge=0)
|
|
136
|
+
version_control_safety: VersionControlSafety
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class HistoryFsmResult(ContractModel):
|
|
140
|
+
schema_id: Literal["medical-notes-workbench.history-fsm-result.v1"] = Field(
|
|
141
|
+
default=HISTORY_FSM_SCHEMA,
|
|
142
|
+
alias="schema",
|
|
143
|
+
)
|
|
144
|
+
workflow: Literal["/mednotes:history"] = HISTORY_WORKFLOW
|
|
145
|
+
run_id: str = Field(min_length=1)
|
|
146
|
+
state_machine_snapshot: WorkflowStateMachineSnapshot
|
|
147
|
+
progress_state: SkipJsonSchema[WorkflowProgressState]
|
|
148
|
+
progress_view_model: WorkflowProgressViewModel
|
|
149
|
+
decision: WorkflowDecision | None = None
|
|
150
|
+
human_decision_packet: HumanDecisionPacket | None = None
|
|
151
|
+
receipt: HistoryReceipt
|
|
152
|
+
reports: WorkflowReports
|
|
153
|
+
agent_directive: JsonObject
|
|
154
|
+
artifacts: JsonObject = Field(default_factory=dict)
|
|
155
|
+
version_control_safety: VersionControlSafety
|
|
156
|
+
diagnostic_context: JsonObject = Field(default_factory=dict)
|
|
157
|
+
error_context: JsonObject = Field(default_factory=dict)
|
|
158
|
+
|
|
159
|
+
@model_validator(mode="before")
|
|
160
|
+
@classmethod
|
|
161
|
+
def _hydrate_progress_state_from_public_payload(cls, value: object) -> object:
|
|
162
|
+
"""Accept public payloads where progress_state is intentionally hidden."""
|
|
163
|
+
|
|
164
|
+
if not isinstance(value, dict) or "progress_state" in value or "progress_view_model" not in value:
|
|
165
|
+
return value
|
|
166
|
+
hydrated = dict(value)
|
|
167
|
+
progress_view = WorkflowProgressViewModel.model_validate(value["progress_view_model"])
|
|
168
|
+
hydrated["progress_state"] = progress_state_from_view_model(progress_view).to_payload()
|
|
169
|
+
return hydrated
|
|
170
|
+
|
|
171
|
+
@model_validator(mode="after")
|
|
172
|
+
def _progress_view_model_matches_state(self) -> HistoryFsmResult:
|
|
173
|
+
expected = build_progress_view_model(self.progress_state).to_payload()
|
|
174
|
+
if self.progress_view_model.to_payload() != expected:
|
|
175
|
+
raise ValueError("progress_view_model must match progress_state")
|
|
176
|
+
return self
|
|
177
|
+
|
|
178
|
+
def to_payload(self) -> JsonObject:
|
|
179
|
+
payload: JsonObject = {
|
|
180
|
+
"schema": self.schema_id,
|
|
181
|
+
"workflow": self.workflow,
|
|
182
|
+
"run_id": self.run_id,
|
|
183
|
+
"state_machine_snapshot": self.state_machine_snapshot.to_payload(),
|
|
184
|
+
"progress_view_model": self.progress_view_model.to_payload(),
|
|
185
|
+
"decision": self.decision.to_payload() if self.decision is not None else None,
|
|
186
|
+
"human_decision_packet": self.human_decision_packet.to_payload()
|
|
187
|
+
if self.human_decision_packet is not None
|
|
188
|
+
else None,
|
|
189
|
+
"receipt": self.receipt.to_payload(),
|
|
190
|
+
"reports": self.reports.to_payload(),
|
|
191
|
+
"agent_directive": dict(self.agent_directive),
|
|
192
|
+
"artifacts": dict(self.artifacts),
|
|
193
|
+
"version_control_safety": self.version_control_safety.to_payload(),
|
|
194
|
+
"error_context": dict(self.error_context),
|
|
195
|
+
}
|
|
196
|
+
if self.diagnostic_context:
|
|
197
|
+
payload["diagnostic_context"] = dict(self.diagnostic_context)
|
|
198
|
+
payload = JsonObjectAdapter.validate_python(payload)
|
|
199
|
+
assert_history_fsm_payload(payload)
|
|
200
|
+
return payload
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def assert_history_fsm_payload(payload: JsonObject) -> None:
|
|
204
|
+
"""Gate the public history FSM payload against legacy root truth."""
|
|
205
|
+
|
|
206
|
+
payload = JsonObjectAdapter.validate_python(payload)
|
|
207
|
+
legacy_keys = set(payload) & HISTORY_FORBIDDEN_ROOT_KEYS
|
|
208
|
+
if legacy_keys:
|
|
209
|
+
raise ValueError(f"history FSM payload contains legacy root keys: {sorted(legacy_keys)}")
|
|
210
|
+
required_keys = HISTORY_ALLOWED_ROOT_KEYS - {"diagnostic_context"}
|
|
211
|
+
missing_keys = required_keys - set(payload)
|
|
212
|
+
if missing_keys:
|
|
213
|
+
raise ValueError(f"history FSM payload missing canonical root keys: {sorted(missing_keys)}")
|
|
214
|
+
unexpected_keys = set(payload) - HISTORY_ALLOWED_ROOT_KEYS
|
|
215
|
+
if unexpected_keys:
|
|
216
|
+
raise ValueError(f"history FSM payload contains unexpected root keys: {sorted(unexpected_keys)}")
|
|
217
|
+
diagnostic_context = payload["diagnostic_context"] if "diagnostic_context" in payload else {}
|
|
218
|
+
assert_diagnostic_context_evidence_only(diagnostic_context)
|
|
219
|
+
if isinstance(diagnostic_context, dict) and "agent_directive" in diagnostic_context:
|
|
220
|
+
raise ValueError("history FSM diagnostic_context must not contain agent_directive")
|
|
221
|
+
reports_payload = JsonObjectAdapter.validate_python(payload["reports"])
|
|
222
|
+
if "human" in reports_payload:
|
|
223
|
+
raise ValueError("history FSM reports must not expose legacy human report text")
|
|
224
|
+
reports = WorkflowReports.model_validate(reports_payload)
|
|
225
|
+
public_report = reports.public_report
|
|
226
|
+
snapshot = WorkflowStateMachineSnapshot.model_validate(payload["state_machine_snapshot"])
|
|
227
|
+
progress_view_model = WorkflowProgressViewModel.model_validate(payload["progress_view_model"])
|
|
228
|
+
receipt = HistoryReceipt.model_validate(payload["receipt"])
|
|
229
|
+
if progress_view_model.status != snapshot.current_category.value:
|
|
230
|
+
raise ValueError("history FSM status must match state_machine_snapshot category")
|
|
231
|
+
if receipt.status != progress_view_model.status:
|
|
232
|
+
raise ValueError("history FSM receipt status must match progress view status")
|
|
233
|
+
assert_public_report_matches_progress(
|
|
234
|
+
public_report,
|
|
235
|
+
workflow=HISTORY_WORKFLOW,
|
|
236
|
+
run_id=str(payload["run_id"]),
|
|
237
|
+
progress_view_model=progress_view_model,
|
|
238
|
+
label="history FSM",
|
|
239
|
+
)
|
|
240
|
+
assert_agent_directive_matches_progress(
|
|
241
|
+
AgentDirective.model_validate(payload[HISTORY_AGENT_DIRECTIVE_FIELD]),
|
|
242
|
+
workflow=HISTORY_WORKFLOW,
|
|
243
|
+
run_id=str(payload["run_id"]),
|
|
244
|
+
progress_view_model=progress_view_model,
|
|
245
|
+
snapshot=snapshot,
|
|
246
|
+
allowed_effect_kinds=_allowed_agent_effect_kinds_for_category(snapshot.current_category),
|
|
247
|
+
label="history FSM",
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _allowed_agent_effect_kinds_for_category(category: WorkflowStateCategory) -> set[WorkflowEffectKind]:
|
|
252
|
+
"""History has no hidden executable adapter effect in its public directive."""
|
|
253
|
+
|
|
254
|
+
match category:
|
|
255
|
+
case WorkflowStateCategory.RUNNING:
|
|
256
|
+
return {WorkflowEffectKind.RUN_SUBWORKFLOW}
|
|
257
|
+
case WorkflowStateCategory.WAITING_EXTERNAL:
|
|
258
|
+
return {WorkflowEffectKind.WAIT_EXTERNAL}
|
|
259
|
+
case WorkflowStateCategory.WAITING_HUMAN:
|
|
260
|
+
return {WorkflowEffectKind.ASK_HUMAN}
|
|
261
|
+
case _:
|
|
262
|
+
return set()
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def history_fsm_payload_from_model(
|
|
266
|
+
model: WorkflowModel,
|
|
267
|
+
*,
|
|
268
|
+
version_control_safety: VersionControlSafety | dict[str, object],
|
|
269
|
+
) -> JsonObject:
|
|
270
|
+
"""JSON boundary for the machine-driven history FSM projection."""
|
|
271
|
+
|
|
272
|
+
return build_history_fsm_result_from_model(
|
|
273
|
+
model,
|
|
274
|
+
version_control_safety=version_control_safety,
|
|
275
|
+
).to_payload()
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
class _VaultHistoryPayload(BaseModel):
|
|
279
|
+
"""Typed adapter lens for vault history/restore payloads."""
|
|
280
|
+
|
|
281
|
+
model_config = ConfigDict(extra="ignore", strict=True)
|
|
282
|
+
|
|
283
|
+
schema_id: str = Field(default="", alias="schema")
|
|
284
|
+
adapter_status: str = Field(default="", alias="status")
|
|
285
|
+
adapter_outcome: HistoryVaultOutcome = HistoryVaultOutcome.FAILED
|
|
286
|
+
reason_code: str = ""
|
|
287
|
+
restore_mutated: bool = False
|
|
288
|
+
count: int = Field(default=0, ge=0)
|
|
289
|
+
plan_path: str = ""
|
|
290
|
+
affected_files: list[str] = Field(default_factory=list)
|
|
291
|
+
restored_file_count: int = Field(default=0, ge=0)
|
|
292
|
+
next_action: str = ""
|
|
293
|
+
human_message: str = ""
|
|
294
|
+
|
|
295
|
+
@model_validator(mode="after")
|
|
296
|
+
def _derive_adapter_outcome(self) -> _VaultHistoryPayload:
|
|
297
|
+
status = self.adapter_status
|
|
298
|
+
object.__setattr__(self, "reason_code", status or "history_failed")
|
|
299
|
+
object.__setattr__(
|
|
300
|
+
self,
|
|
301
|
+
"restore_mutated",
|
|
302
|
+
self.schema_id == "medical-notes-workbench.vault-restore-apply.v1" and status == "restored",
|
|
303
|
+
)
|
|
304
|
+
if self.schema_id == "medical-notes-workbench.vault-timeline.v1" and status == "completed":
|
|
305
|
+
object.__setattr__(self, "adapter_outcome", HistoryVaultOutcome.RESTORE_POINTS_LISTED)
|
|
306
|
+
elif self.schema_id == "medical-notes-workbench.vault-restore-plan.v1" and status == "preview_ready":
|
|
307
|
+
object.__setattr__(self, "adapter_outcome", HistoryVaultOutcome.PREVIEW_READY)
|
|
308
|
+
elif self.schema_id == "medical-notes-workbench.vault-restore-apply.v1" and status in {
|
|
309
|
+
"restored",
|
|
310
|
+
"no_changes",
|
|
311
|
+
}:
|
|
312
|
+
object.__setattr__(self, "adapter_outcome", HistoryVaultOutcome.RESTORE_APPLIED)
|
|
313
|
+
elif status == "blocked_stale_preview":
|
|
314
|
+
object.__setattr__(self, "adapter_outcome", HistoryVaultOutcome.STALE_RESTORE_POINT)
|
|
315
|
+
object.__setattr__(self, "reason_code", "stale_restore_point")
|
|
316
|
+
elif status.startswith("blocked"):
|
|
317
|
+
object.__setattr__(self, "adapter_outcome", HistoryVaultOutcome.BLOCKED)
|
|
318
|
+
else:
|
|
319
|
+
object.__setattr__(self, "adapter_outcome", HistoryVaultOutcome.FAILED)
|
|
320
|
+
return self
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
def history_fsm_payload_from_vault_payload(
|
|
324
|
+
payload: object,
|
|
325
|
+
*,
|
|
326
|
+
run_id: str = "history-vault",
|
|
327
|
+
version_control_safety: VersionControlSafety | dict[str, object] | None = None,
|
|
328
|
+
) -> JsonObject:
|
|
329
|
+
"""Convert a vault history adapter result into the public history FSM payload."""
|
|
330
|
+
|
|
331
|
+
raw = _VaultHistoryPayload.model_validate(payload)
|
|
332
|
+
initial_state, event = _history_event_from_vault_payload(raw, run_id=run_id)
|
|
333
|
+
model = WorkflowModel.start(workflow=HISTORY_WORKFLOW, run_id=run_id, initial_state=initial_state.value)
|
|
334
|
+
send_workflow_event(HistoryMachine(model=model, state_field=WorkflowModel.STATECHART_STATE_FIELD), event)
|
|
335
|
+
return build_history_fsm_result_from_model(
|
|
336
|
+
model,
|
|
337
|
+
version_control_safety=version_control_safety or {"no_resource_mutation": not raw.restore_mutated},
|
|
338
|
+
).to_payload()
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def _history_event_from_vault_payload(
|
|
342
|
+
payload: _VaultHistoryPayload,
|
|
343
|
+
*,
|
|
344
|
+
run_id: str,
|
|
345
|
+
) -> tuple[HistoryState, WorkflowEventLike]:
|
|
346
|
+
if payload.adapter_outcome == HistoryVaultOutcome.RESTORE_POINTS_LISTED:
|
|
347
|
+
state = HistoryState.LISTING_RESTORE_POINTS
|
|
348
|
+
return (
|
|
349
|
+
state,
|
|
350
|
+
RestorePointsListedEvent(
|
|
351
|
+
workflow=HISTORY_WORKFLOW,
|
|
352
|
+
run_id=run_id,
|
|
353
|
+
current_state=state.value,
|
|
354
|
+
restore_point_count=payload.count,
|
|
355
|
+
),
|
|
356
|
+
)
|
|
357
|
+
if payload.adapter_outcome == HistoryVaultOutcome.PREVIEW_READY:
|
|
358
|
+
state = HistoryState.PREVIEW_READY
|
|
359
|
+
return (
|
|
360
|
+
state,
|
|
361
|
+
PreviewRequiresConfirmationEvent(
|
|
362
|
+
workflow=HISTORY_WORKFLOW,
|
|
363
|
+
run_id=run_id,
|
|
364
|
+
current_state=state.value,
|
|
365
|
+
restore_preview_path=payload.plan_path or "vault-restore-plan",
|
|
366
|
+
affected_file_count=max(1, len(payload.affected_files)),
|
|
367
|
+
),
|
|
368
|
+
)
|
|
369
|
+
if payload.adapter_outcome == HistoryVaultOutcome.RESTORE_APPLIED:
|
|
370
|
+
state = HistoryState.APPLYING_RESTORE
|
|
371
|
+
return (
|
|
372
|
+
state,
|
|
373
|
+
RestoreAppliedEvent(
|
|
374
|
+
workflow=HISTORY_WORKFLOW,
|
|
375
|
+
run_id=run_id,
|
|
376
|
+
current_state=state.value,
|
|
377
|
+
restored_file_count=payload.restored_file_count or len(payload.affected_files),
|
|
378
|
+
),
|
|
379
|
+
)
|
|
380
|
+
if payload.adapter_outcome == HistoryVaultOutcome.STALE_RESTORE_POINT:
|
|
381
|
+
state = HistoryState.APPLYING_RESTORE
|
|
382
|
+
return (
|
|
383
|
+
state,
|
|
384
|
+
StaleRestorePointDetectedEvent(
|
|
385
|
+
workflow=HISTORY_WORKFLOW,
|
|
386
|
+
run_id=run_id,
|
|
387
|
+
current_state=state.value,
|
|
388
|
+
next_action="history:refresh-restore-point",
|
|
389
|
+
),
|
|
390
|
+
)
|
|
391
|
+
state = HistoryState.LISTING_RESTORE_POINTS
|
|
392
|
+
if payload.adapter_outcome == HistoryVaultOutcome.BLOCKED:
|
|
393
|
+
return (
|
|
394
|
+
state,
|
|
395
|
+
HistoryBlockedEvent(
|
|
396
|
+
workflow=HISTORY_WORKFLOW,
|
|
397
|
+
run_id=run_id,
|
|
398
|
+
current_state=state.value,
|
|
399
|
+
reason_code=payload.reason_code or "history_blocked",
|
|
400
|
+
next_action=payload.next_action or payload.human_message or "Gerar nova prévia pela rota oficial.",
|
|
401
|
+
),
|
|
402
|
+
)
|
|
403
|
+
return (
|
|
404
|
+
state,
|
|
405
|
+
HistoryFailedEvent(
|
|
406
|
+
workflow=HISTORY_WORKFLOW,
|
|
407
|
+
run_id=run_id,
|
|
408
|
+
current_state=state.value,
|
|
409
|
+
reason_code=payload.reason_code or "history_failed",
|
|
410
|
+
next_action=payload.next_action or payload.human_message or "Repetir /mednotes:history pela rota oficial.",
|
|
411
|
+
),
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
def build_history_fsm_result_from_model(
|
|
416
|
+
model: WorkflowModel,
|
|
417
|
+
*,
|
|
418
|
+
version_control_safety: VersionControlSafety | dict[str, object],
|
|
419
|
+
) -> HistoryFsmResult:
|
|
420
|
+
"""Project a real HistoryMachine model into the public workflow contract."""
|
|
421
|
+
|
|
422
|
+
_validate_history_machine_model(model)
|
|
423
|
+
state = HistoryState(model.state)
|
|
424
|
+
category = category_for_history_state(state)
|
|
425
|
+
progress_state = _progress_state_from_model(model, state, category)
|
|
426
|
+
progress_view_model = build_progress_view_model(progress_state)
|
|
427
|
+
snapshot = _snapshot_from_model(model, state, category)
|
|
428
|
+
safety = _version_control_safety(version_control_safety)
|
|
429
|
+
reports = _reports_from_model(state, progress_state)
|
|
430
|
+
public_report = reports.public_report
|
|
431
|
+
agent_directive = agent_directive_from_progress_view_model(
|
|
432
|
+
progress_view_model,
|
|
433
|
+
schema="medical-notes-workbench.agent-directive.v1",
|
|
434
|
+
reason=_machine_reason_code(model, state),
|
|
435
|
+
effects=model.pending_effects,
|
|
436
|
+
blockers=_machine_blockers(category, model, state),
|
|
437
|
+
resume=progress_state.resume_action,
|
|
438
|
+
report_requires=["primary_objective", "restore_preview", "restore_apply"],
|
|
439
|
+
summary=public_report.summary_text(),
|
|
440
|
+
instructions=_machine_agent_instructions(category),
|
|
441
|
+
).to_payload()
|
|
442
|
+
return HistoryFsmResult(
|
|
443
|
+
run_id=model.run_id,
|
|
444
|
+
state_machine_snapshot=snapshot,
|
|
445
|
+
progress_state=progress_state,
|
|
446
|
+
progress_view_model=progress_view_model,
|
|
447
|
+
decision=model.last_transition.decision if model.last_transition is not None else None,
|
|
448
|
+
human_decision_packet=model.last_transition.human_decision_packet if model.last_transition is not None else None,
|
|
449
|
+
receipt=_receipt_from_model(
|
|
450
|
+
model,
|
|
451
|
+
progress_state=progress_state,
|
|
452
|
+
version_control_safety=safety,
|
|
453
|
+
),
|
|
454
|
+
reports=reports,
|
|
455
|
+
agent_directive=JsonObjectAdapter.validate_python(agent_directive),
|
|
456
|
+
version_control_safety=safety,
|
|
457
|
+
diagnostic_context=_diagnostic_context_from_model(model, state, category),
|
|
458
|
+
error_context=_error_context_from_model(model, state, category),
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
|
|
462
|
+
def _validate_history_machine_model(model: WorkflowModel) -> None:
|
|
463
|
+
if model.workflow != HISTORY_WORKFLOW:
|
|
464
|
+
raise ValueError(f"history FSM projector requires workflow={HISTORY_WORKFLOW}")
|
|
465
|
+
HistoryState(model.state)
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
def _progress_state_from_model(
|
|
469
|
+
model: WorkflowModel,
|
|
470
|
+
state: HistoryState,
|
|
471
|
+
category: WorkflowStateCategory,
|
|
472
|
+
) -> WorkflowProgressState:
|
|
473
|
+
status = _machine_progress_status(category)
|
|
474
|
+
affected = _last_event_int(model, "affected_file_count")
|
|
475
|
+
restored = _last_event_int(model, "restored_file_count")
|
|
476
|
+
total = max(affected, restored)
|
|
477
|
+
current = restored if state == HistoryState.COMPLETED else 0
|
|
478
|
+
return WorkflowProgressState(
|
|
479
|
+
workflow=HISTORY_WORKFLOW,
|
|
480
|
+
run_id=model.run_id,
|
|
481
|
+
state=state.value,
|
|
482
|
+
phase=_machine_phase_for_state(state),
|
|
483
|
+
event_type=_machine_event_type(status),
|
|
484
|
+
message=_machine_message_for_state(state),
|
|
485
|
+
status=status,
|
|
486
|
+
current=current,
|
|
487
|
+
total=total,
|
|
488
|
+
counts=WorkflowProgressCounts(
|
|
489
|
+
planned_items=total,
|
|
490
|
+
processed_items=current,
|
|
491
|
+
mutated_files=restored,
|
|
492
|
+
written_files=restored,
|
|
493
|
+
remaining_items=max(total - current, 0),
|
|
494
|
+
blocked_items=total if status in {WorkflowProgressStatus.BLOCKED, WorkflowProgressStatus.FAILED} else 0,
|
|
495
|
+
),
|
|
496
|
+
resume_action=_machine_resume_action(model, state),
|
|
497
|
+
resume_supported=status
|
|
498
|
+
in {
|
|
499
|
+
WorkflowProgressStatus.RUNNING,
|
|
500
|
+
WorkflowProgressStatus.WAITING_HUMAN,
|
|
501
|
+
WorkflowProgressStatus.BLOCKED,
|
|
502
|
+
},
|
|
503
|
+
can_continue_now=status == WorkflowProgressStatus.RUNNING,
|
|
504
|
+
decision=model.last_transition.decision.decision_summary()
|
|
505
|
+
if model.last_transition is not None and model.last_transition.decision is not None
|
|
506
|
+
else None,
|
|
507
|
+
technical_context={
|
|
508
|
+
"reason": _machine_reason_code(model, state),
|
|
509
|
+
"category": category.value,
|
|
510
|
+
"source": "HistoryMachine",
|
|
511
|
+
"affected_file_count": affected,
|
|
512
|
+
"restored_file_count": restored,
|
|
513
|
+
},
|
|
514
|
+
)
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
def _snapshot_from_model(
|
|
518
|
+
model: WorkflowModel,
|
|
519
|
+
state: HistoryState,
|
|
520
|
+
category: WorkflowStateCategory,
|
|
521
|
+
) -> WorkflowStateMachineSnapshot:
|
|
522
|
+
return WorkflowStateMachineSnapshot(
|
|
523
|
+
workflow=HISTORY_WORKFLOW,
|
|
524
|
+
run_id=model.run_id,
|
|
525
|
+
current_state=state.value,
|
|
526
|
+
current_category=category,
|
|
527
|
+
transitions=[_machine_snapshot_transition(transition) for transition in model.transition_log],
|
|
528
|
+
metadata={"reason": _machine_reason_code(model, state), "source": "HistoryMachine"},
|
|
529
|
+
)
|
|
530
|
+
|
|
531
|
+
|
|
532
|
+
def _machine_snapshot_transition(transition: WorkflowTransitionResult) -> WorkflowTransition:
|
|
533
|
+
return WorkflowTransition(
|
|
534
|
+
workflow=transition.workflow,
|
|
535
|
+
from_state=transition.from_state,
|
|
536
|
+
to_state=transition.to_state,
|
|
537
|
+
to_category=category_for_history_state(HistoryState(transition.to_state)),
|
|
538
|
+
trigger=transition.trigger,
|
|
539
|
+
effects=list(transition.effects),
|
|
540
|
+
decision=transition.decision,
|
|
541
|
+
resume_action=transition.resume_action,
|
|
542
|
+
)
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
def _receipt_from_model(
|
|
546
|
+
model: WorkflowModel,
|
|
547
|
+
*,
|
|
548
|
+
progress_state: WorkflowProgressState,
|
|
549
|
+
version_control_safety: VersionControlSafety,
|
|
550
|
+
) -> HistoryReceipt:
|
|
551
|
+
return HistoryReceipt(
|
|
552
|
+
run_id=model.run_id,
|
|
553
|
+
status=progress_state.status.value,
|
|
554
|
+
mutated=version_control_safety.changed_file_count > 0,
|
|
555
|
+
next_action="" if progress_state.status == WorkflowProgressStatus.COMPLETED else progress_state.resume_action,
|
|
556
|
+
human_decision_required=progress_state.status == WorkflowProgressStatus.WAITING_HUMAN,
|
|
557
|
+
restored_file_count=_last_event_int(model, "restored_file_count"),
|
|
558
|
+
version_control_safety=version_control_safety,
|
|
559
|
+
)
|
|
560
|
+
|
|
561
|
+
|
|
562
|
+
def _reports_from_model(state: HistoryState, progress_state: WorkflowProgressState) -> WorkflowReports:
|
|
563
|
+
summary = _machine_message_for_state(state)
|
|
564
|
+
public_lines = [summary]
|
|
565
|
+
followup_line = public_progress_followup_line(progress_state)
|
|
566
|
+
if followup_line:
|
|
567
|
+
public_lines.append(followup_line)
|
|
568
|
+
public_report = WorkflowPublicReport(
|
|
569
|
+
workflow=HISTORY_WORKFLOW,
|
|
570
|
+
run_id=progress_state.run_id,
|
|
571
|
+
headline=summary,
|
|
572
|
+
lines=public_lines,
|
|
573
|
+
)
|
|
574
|
+
return WorkflowReports(
|
|
575
|
+
summary=summary,
|
|
576
|
+
public_report=public_report,
|
|
577
|
+
details={
|
|
578
|
+
"primary_objective_summary": _history_primary_objective_summary(
|
|
579
|
+
state=state,
|
|
580
|
+
progress_state=progress_state,
|
|
581
|
+
).to_payload()
|
|
582
|
+
},
|
|
583
|
+
)
|
|
584
|
+
|
|
585
|
+
|
|
586
|
+
def _history_primary_objective_summary(
|
|
587
|
+
*,
|
|
588
|
+
state: HistoryState,
|
|
589
|
+
progress_state: WorkflowProgressState,
|
|
590
|
+
) -> WorkflowPrimaryObjectiveSummary:
|
|
591
|
+
"""State-owned answer to whether history listed/restored as requested."""
|
|
592
|
+
|
|
593
|
+
completed = state in {HistoryState.RESTORE_POINTS_LISTED, HistoryState.COMPLETED}
|
|
594
|
+
restored_count = progress_state.counts.written_files
|
|
595
|
+
return WorkflowPrimaryObjectiveSummary(
|
|
596
|
+
workflow=HISTORY_WORKFLOW,
|
|
597
|
+
run_id=progress_state.run_id,
|
|
598
|
+
objective="Listar pontos de restauração e aplicar restauração somente após prévia/decisão.",
|
|
599
|
+
completed=completed,
|
|
600
|
+
status=state.value,
|
|
601
|
+
mutation_state="changed" if restored_count > 0 else "unchanged",
|
|
602
|
+
mutation_summary=_history_mutation_summary(restored_count),
|
|
603
|
+
remaining_work_summary=_history_remaining_work_summary(state, completed),
|
|
604
|
+
next_step_summary=_history_next_step_summary(progress_state, completed),
|
|
605
|
+
blocked_reason="" if completed else state.value,
|
|
606
|
+
)
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
def _history_mutation_summary(restored_count: int) -> str:
|
|
610
|
+
if restored_count > 0:
|
|
611
|
+
return f"{restored_count} arquivo(s) foram restaurados."
|
|
612
|
+
return "Nenhuma restauração foi aplicada nesta etapa."
|
|
613
|
+
|
|
614
|
+
|
|
615
|
+
def _history_remaining_work_summary(state: HistoryState, completed: bool) -> str:
|
|
616
|
+
if completed and state == HistoryState.RESTORE_POINTS_LISTED:
|
|
617
|
+
return "Pontos de restauração listados; nenhuma restauração foi solicitada."
|
|
618
|
+
if completed:
|
|
619
|
+
return "Restauração aplicada e conferida."
|
|
620
|
+
return _machine_message_for_state(state)
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
def _history_next_step_summary(progress_state: WorkflowProgressState, completed: bool) -> str:
|
|
624
|
+
if completed:
|
|
625
|
+
return "Nenhuma ação pendente para o histórico nesta rota."
|
|
626
|
+
return progress_state.resume_action or "Retomar /mednotes:history pela rota oficial."
|
|
627
|
+
|
|
628
|
+
|
|
629
|
+
def _diagnostic_context_from_model(
|
|
630
|
+
model: WorkflowModel,
|
|
631
|
+
state: HistoryState,
|
|
632
|
+
category: WorkflowStateCategory,
|
|
633
|
+
) -> JsonObject:
|
|
634
|
+
if category == WorkflowStateCategory.COMPLETED:
|
|
635
|
+
return {}
|
|
636
|
+
context: JsonObject = {
|
|
637
|
+
"schema": "medical-notes-workbench.history-fsm-diagnostic-context.v1",
|
|
638
|
+
"state": state.value,
|
|
639
|
+
"category": category.value,
|
|
640
|
+
"reason": _machine_reason_code(model, state),
|
|
641
|
+
"source": "HistoryMachine",
|
|
642
|
+
}
|
|
643
|
+
evidence = _machine_audit_evidence(model)
|
|
644
|
+
for key, value in evidence.items():
|
|
645
|
+
if key not in context:
|
|
646
|
+
context[key] = value
|
|
647
|
+
return diagnostic_context_evidence_only(context)
|
|
648
|
+
|
|
649
|
+
|
|
650
|
+
def _error_context_from_model(
|
|
651
|
+
model: WorkflowModel,
|
|
652
|
+
state: HistoryState,
|
|
653
|
+
category: WorkflowStateCategory,
|
|
654
|
+
) -> JsonObject:
|
|
655
|
+
"""Expose a typed recovery route for blocked/failed history states."""
|
|
656
|
+
|
|
657
|
+
if category not in {WorkflowStateCategory.BLOCKED, WorkflowStateCategory.FAILED}:
|
|
658
|
+
return {}
|
|
659
|
+
reason = _machine_reason_code(model, state)
|
|
660
|
+
next_action = _machine_resume_action(model, state) or "history:timeline"
|
|
661
|
+
return JsonObjectAdapter.validate_python(
|
|
662
|
+
{
|
|
663
|
+
"schema": "medical-notes-workbench.error-context.v1",
|
|
664
|
+
"phase": _machine_phase_for_state(state),
|
|
665
|
+
"blocked_reason": reason,
|
|
666
|
+
"root_cause": reason,
|
|
667
|
+
"affected_artifact": "vault_restore",
|
|
668
|
+
"error_summary": _machine_message_for_state(state),
|
|
669
|
+
"suggested_fix": next_action,
|
|
670
|
+
"next_action": next_action,
|
|
671
|
+
"retry_scope": "history_restore_workflow",
|
|
672
|
+
"missing_inputs": [],
|
|
673
|
+
"human_decision_required": category == WorkflowStateCategory.WAITING_HUMAN,
|
|
674
|
+
"version_control_safety": "preserve_vault_restore_point_before_retry",
|
|
675
|
+
}
|
|
676
|
+
)
|
|
677
|
+
|
|
678
|
+
|
|
679
|
+
def _machine_audit_evidence(model: WorkflowModel) -> JsonObject:
|
|
680
|
+
if not model.event_log:
|
|
681
|
+
return {}
|
|
682
|
+
event = _HistoryMachineEventEvidence.model_validate(model.event_log[-1])
|
|
683
|
+
return JsonObjectAdapter.validate_python(event.audit_evidence)
|
|
684
|
+
|
|
685
|
+
|
|
686
|
+
def _machine_progress_status(category: WorkflowStateCategory) -> WorkflowProgressStatus:
|
|
687
|
+
match category:
|
|
688
|
+
case WorkflowStateCategory.PREPARING | WorkflowStateCategory.RUNNING:
|
|
689
|
+
return WorkflowProgressStatus.RUNNING
|
|
690
|
+
case WorkflowStateCategory.WAITING_AGENT:
|
|
691
|
+
return WorkflowProgressStatus.WAITING_AGENT
|
|
692
|
+
case WorkflowStateCategory.WAITING_EXTERNAL:
|
|
693
|
+
return WorkflowProgressStatus.WAITING_EXTERNAL
|
|
694
|
+
case WorkflowStateCategory.WAITING_HUMAN:
|
|
695
|
+
return WorkflowProgressStatus.WAITING_HUMAN
|
|
696
|
+
case WorkflowStateCategory.BLOCKED:
|
|
697
|
+
return WorkflowProgressStatus.BLOCKED
|
|
698
|
+
case WorkflowStateCategory.FAILED:
|
|
699
|
+
return WorkflowProgressStatus.FAILED
|
|
700
|
+
case WorkflowStateCategory.COMPLETED:
|
|
701
|
+
return WorkflowProgressStatus.COMPLETED
|
|
702
|
+
case WorkflowStateCategory.COMPLETED_WITH_WARNINGS:
|
|
703
|
+
return WorkflowProgressStatus.COMPLETED_WITH_WARNINGS
|
|
704
|
+
|
|
705
|
+
|
|
706
|
+
def _machine_event_type(status: WorkflowProgressStatus) -> WorkflowProgressEventType:
|
|
707
|
+
match status:
|
|
708
|
+
case WorkflowProgressStatus.COMPLETED | WorkflowProgressStatus.COMPLETED_WITH_WARNINGS:
|
|
709
|
+
return WorkflowProgressEventType.WORKFLOW_COMPLETED
|
|
710
|
+
case WorkflowProgressStatus.FAILED:
|
|
711
|
+
return WorkflowProgressEventType.WORKFLOW_FAILED
|
|
712
|
+
case WorkflowProgressStatus.WAITING_HUMAN | WorkflowProgressStatus.BLOCKED:
|
|
713
|
+
return WorkflowProgressEventType.DECISION_EMITTED
|
|
714
|
+
case _:
|
|
715
|
+
return WorkflowProgressEventType.STATE_ENTERED
|
|
716
|
+
|
|
717
|
+
|
|
718
|
+
def _machine_phase_for_state(state: HistoryState) -> str:
|
|
719
|
+
match state:
|
|
720
|
+
case HistoryState.LISTING_RESTORE_POINTS:
|
|
721
|
+
return "history_list"
|
|
722
|
+
case HistoryState.RESTORE_POINTS_LISTED:
|
|
723
|
+
return "history_list"
|
|
724
|
+
case HistoryState.PREVIEW_READY | HistoryState.WAITING_HUMAN_CONFIRMATION:
|
|
725
|
+
return "history_preview"
|
|
726
|
+
case HistoryState.APPLYING_RESTORE | HistoryState.COMPLETED:
|
|
727
|
+
return "history_restore"
|
|
728
|
+
case HistoryState.STALE_RESTORE_POINT:
|
|
729
|
+
return "history_stale_restore_point"
|
|
730
|
+
case HistoryState.RESTORE_CONFLICT:
|
|
731
|
+
return "history_restore_conflict"
|
|
732
|
+
case HistoryState.RESTORE_CANCELLED:
|
|
733
|
+
return "history_restore_cancelled"
|
|
734
|
+
case HistoryState.RESTORE_POINT_LIST_BLOCKED:
|
|
735
|
+
return "restore_point_list_blocked"
|
|
736
|
+
case HistoryState.RESTORE_PREVIEW_BLOCKED:
|
|
737
|
+
return "restore_preview_blocked"
|
|
738
|
+
case HistoryState.RESTORE_CONFIRMATION_BLOCKED:
|
|
739
|
+
return "restore_confirmation_blocked"
|
|
740
|
+
case HistoryState.RESTORE_APPLY_BLOCKED:
|
|
741
|
+
return "restore_apply_blocked"
|
|
742
|
+
case HistoryState.FAILED:
|
|
743
|
+
return "history_failed"
|
|
744
|
+
|
|
745
|
+
|
|
746
|
+
def _machine_message_for_state(state: HistoryState) -> str:
|
|
747
|
+
match state:
|
|
748
|
+
case HistoryState.PREVIEW_READY:
|
|
749
|
+
return "Restauração aguardando geração de prévia."
|
|
750
|
+
case HistoryState.WAITING_HUMAN_CONFIRMATION:
|
|
751
|
+
return "Prévia de restauração aguardando confirmação humana."
|
|
752
|
+
case HistoryState.APPLYING_RESTORE:
|
|
753
|
+
return "Restauração aguardando aplicação pelo adapter oficial."
|
|
754
|
+
case HistoryState.STALE_RESTORE_POINT:
|
|
755
|
+
return "Ponto de restauração ficou desatualizado."
|
|
756
|
+
case HistoryState.RESTORE_CONFLICT:
|
|
757
|
+
return "Restauração encontrou conflito antes de aplicar."
|
|
758
|
+
case HistoryState.COMPLETED:
|
|
759
|
+
return "Restauração aplicada e conferida."
|
|
760
|
+
case HistoryState.RESTORE_POINTS_LISTED:
|
|
761
|
+
return "Histórico de pontos de restauração listado."
|
|
762
|
+
case HistoryState.RESTORE_CANCELLED:
|
|
763
|
+
return "Restauração cancelada antes de alterar o vault."
|
|
764
|
+
case HistoryState.RESTORE_POINT_LIST_BLOCKED:
|
|
765
|
+
return "Listagem de pontos de restauração bloqueada."
|
|
766
|
+
case HistoryState.RESTORE_PREVIEW_BLOCKED:
|
|
767
|
+
return "Prévia de restauração bloqueada."
|
|
768
|
+
case HistoryState.RESTORE_CONFIRMATION_BLOCKED:
|
|
769
|
+
return "Confirmação da restauração bloqueada."
|
|
770
|
+
case HistoryState.RESTORE_APPLY_BLOCKED:
|
|
771
|
+
return "Aplicação da restauração bloqueada."
|
|
772
|
+
case HistoryState.FAILED:
|
|
773
|
+
return "Histórico falhou antes de concluir."
|
|
774
|
+
case _:
|
|
775
|
+
return "Histórico em andamento."
|
|
776
|
+
|
|
777
|
+
|
|
778
|
+
def _machine_resume_action(model: WorkflowModel, state: HistoryState) -> str:
|
|
779
|
+
if state in {HistoryState.COMPLETED, HistoryState.RESTORE_POINTS_LISTED}:
|
|
780
|
+
return ""
|
|
781
|
+
if model.last_transition is not None and model.last_transition.resume_action:
|
|
782
|
+
return model.last_transition.resume_action
|
|
783
|
+
match state:
|
|
784
|
+
case HistoryState.PREVIEW_READY:
|
|
785
|
+
return "history:preview"
|
|
786
|
+
case HistoryState.WAITING_HUMAN_CONFIRMATION:
|
|
787
|
+
return "history:confirm-restore"
|
|
788
|
+
case HistoryState.APPLYING_RESTORE:
|
|
789
|
+
return "history:apply"
|
|
790
|
+
case HistoryState.STALE_RESTORE_POINT:
|
|
791
|
+
return "history:refresh-restore-point"
|
|
792
|
+
case HistoryState.RESTORE_CONFLICT:
|
|
793
|
+
return "history:resolve-conflict"
|
|
794
|
+
case (
|
|
795
|
+
HistoryState.RESTORE_CANCELLED
|
|
796
|
+
| HistoryState.RESTORE_POINT_LIST_BLOCKED
|
|
797
|
+
| HistoryState.RESTORE_PREVIEW_BLOCKED
|
|
798
|
+
| HistoryState.RESTORE_CONFIRMATION_BLOCKED
|
|
799
|
+
| HistoryState.RESTORE_APPLY_BLOCKED
|
|
800
|
+
| HistoryState.FAILED
|
|
801
|
+
):
|
|
802
|
+
return "history:timeline"
|
|
803
|
+
case _:
|
|
804
|
+
return ""
|
|
805
|
+
|
|
806
|
+
|
|
807
|
+
def _machine_reason_code(model: WorkflowModel, state: HistoryState) -> str:
|
|
808
|
+
if model.last_transition is not None:
|
|
809
|
+
return model.last_transition.reason_code
|
|
810
|
+
return state.value
|
|
811
|
+
|
|
812
|
+
|
|
813
|
+
def _machine_blockers(
|
|
814
|
+
category: WorkflowStateCategory,
|
|
815
|
+
model: WorkflowModel,
|
|
816
|
+
state: HistoryState,
|
|
817
|
+
) -> list[str]:
|
|
818
|
+
if category in {
|
|
819
|
+
WorkflowStateCategory.WAITING_AGENT,
|
|
820
|
+
WorkflowStateCategory.WAITING_EXTERNAL,
|
|
821
|
+
WorkflowStateCategory.WAITING_HUMAN,
|
|
822
|
+
WorkflowStateCategory.BLOCKED,
|
|
823
|
+
WorkflowStateCategory.FAILED,
|
|
824
|
+
}:
|
|
825
|
+
return [_machine_reason_code(model, state)]
|
|
826
|
+
return []
|
|
827
|
+
|
|
828
|
+
|
|
829
|
+
def _machine_agent_instructions(category: WorkflowStateCategory) -> list[str]:
|
|
830
|
+
if category == WorkflowStateCategory.WAITING_HUMAN:
|
|
831
|
+
return ["Peça confirmação humana fechada antes de aplicar restauração."]
|
|
832
|
+
if category in {WorkflowStateCategory.BLOCKED, WorkflowStateCategory.FAILED}:
|
|
833
|
+
return ["Use a decisão e o resume_action da FSM para recuperar /mednotes:history."]
|
|
834
|
+
return ["Execute somente os efeitos em agent_directive.control.effects e retome /mednotes:history pelo resultado tipado."]
|
|
835
|
+
|
|
836
|
+
|
|
837
|
+
def _last_event_int(model: WorkflowModel, field_name: str) -> int:
|
|
838
|
+
if not model.event_log:
|
|
839
|
+
return 0
|
|
840
|
+
event = model.event_log[-1]
|
|
841
|
+
value = event[field_name] if field_name in event else 0
|
|
842
|
+
if isinstance(value, bool):
|
|
843
|
+
return 0
|
|
844
|
+
if isinstance(value, int) and value >= 0:
|
|
845
|
+
return value
|
|
846
|
+
return 0
|
|
847
|
+
|
|
848
|
+
|
|
849
|
+
def _version_control_safety(value: VersionControlSafety | dict[str, object]) -> VersionControlSafety:
|
|
850
|
+
if isinstance(value, VersionControlSafety):
|
|
851
|
+
return value
|
|
852
|
+
return VersionControlSafety.model_validate(value)
|