@mnstry/atelier 0.2.0-alpha.11 → 0.2.0-alpha.13
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/CHANGELOG.md +1005 -0
- package/README.md +149 -9
- package/contracts/atelier-adoption-policy.v2.schema.json +55 -0
- package/contracts/atelier-architecture.v1.schema.json +258 -0
- package/contracts/atelier-boundary-delegations.v1.schema.json +161 -0
- package/contracts/atelier-boundary-owner-keys.v1.schema.json +98 -0
- package/contracts/atelier-build.v1.schema.json +1076 -0
- package/contracts/atelier-capability.v1.schema.json +1252 -0
- package/contracts/atelier-coordination.v1.schema.json +1050 -0
- package/contracts/atelier-evidence-navigation.v1.schema.json +603 -0
- package/contracts/atelier-harness.v1.schema.json +234 -0
- package/contracts/atelier-ingestion.v1.schema.json +806 -0
- package/contracts/atelier-inquiry.v1.schema.json +1772 -0
- package/contracts/atelier-instruction-adoption.v1.schema.json +490 -0
- package/contracts/atelier-interaction.v1.schema.json +553 -0
- package/contracts/atelier-judgment.v1.schema.json +690 -0
- package/contracts/atelier-knowledge.v1.schema.json +1512 -0
- package/contracts/atelier-learning.v1.schema.json +1095 -0
- package/contracts/atelier-migration.v3.schema.json +64 -0
- package/contracts/atelier-obsidian-generation-manifest.v2.schema.json +372 -0
- package/contracts/atelier-personal-workspace-manifest.v1.schema.json +87 -0
- package/contracts/atelier-personal-workspace-overlay.v1.schema.json +299 -0
- package/contracts/atelier-template-adoption.v1.schema.json +223 -0
- package/contracts/atelier-template-profile.v1.schema.json +1168 -0
- package/contracts/atelier-trackable.v1.schema.json +781 -0
- package/contracts/atelier-upgrade-plan.v3.schema.json +532 -0
- package/contracts/compat-baseline.json +1 -1
- package/docs/architecture.md +89 -0
- package/docs/assurance-controls.md +4 -3
- package/docs/blocks/capability-stewardship.md +17 -0
- package/docs/blocks/claims.md +16 -3
- package/docs/blocks/inquiry-harnesses.md +19 -0
- package/docs/blocks/learning-harnesses.md +17 -0
- package/docs/capability-stewardship.md +269 -0
- package/docs/coordination.md +51 -0
- package/docs/decision-practices.md +111 -0
- package/docs/decisions.md +29 -0
- package/docs/evidence-navigation.md +237 -0
- package/docs/exact-upgrades.md +3 -2
- package/docs/ingestion.md +181 -0
- package/docs/inquiry-harnesses.md +211 -0
- package/docs/install.md +80 -3
- package/docs/intake-and-guides.md +5 -0
- package/docs/integration-contract-decisions.md +24 -2
- package/docs/interaction-and-reflection.md +72 -0
- package/docs/knowledge-graph.md +20 -0
- package/docs/knowledge-health/api.md +30 -0
- package/docs/knowledge-health/assessment/api.md +96 -0
- package/docs/knowledge-health/assessment/capabilities.md +40 -0
- package/docs/knowledge-health/assessment/evaluation.md +27 -0
- package/docs/knowledge-health/assessment/facilitator.md +30 -0
- package/docs/knowledge-health/assessment/quickstart.md +61 -0
- package/docs/knowledge-health/assessment/troubleshooting.md +23 -0
- package/docs/knowledge-health/devday/facilitator.md +16 -0
- package/docs/knowledge-health/devday/participant-response.md +22 -0
- package/docs/knowledge-health/devday/public-default.md +107 -0
- package/docs/knowledge-health/devday/quickstart.md +18 -0
- package/docs/knowledge-health/devday/support.md +22 -0
- package/docs/knowledge-health/exercise.md +17 -0
- package/docs/knowledge-health/facilitator.md +20 -0
- package/docs/knowledge-health/quickstart.md +94 -0
- package/docs/knowledge-health/readme.md +15 -0
- package/docs/knowledge-health/support.md +34 -0
- package/docs/knowledge-setup.md +248 -0
- package/docs/knowledge-workspace.md +264 -0
- package/docs/learning-harnesses.md +238 -0
- package/docs/learning.md +215 -0
- package/docs/local-services.md +242 -26
- package/docs/obsidian-contract.md +878 -36
- package/docs/obsidian-plugin.md +619 -0
- package/docs/obsidian.md +809 -64
- package/docs/optional-ingestion.md +83 -0
- package/docs/personal-workspace.md +257 -0
- package/docs/practical-judgment.md +92 -0
- package/docs/progressive-ingestion-acceptance.md +89 -0
- package/docs/progressive-ingestion.md +235 -0
- package/docs/project-command-inventory.md +1 -0
- package/docs/release-engineering.md +56 -1
- package/docs/repo-boundary-guard.md +94 -0
- package/docs/research-starter-quickstart.md +66 -0
- package/docs/research-starter.md +114 -0
- package/docs/semantic-ingestion.md +198 -0
- package/docs/semantic-operation.md +317 -0
- package/docs/skill-steward.md +6 -0
- package/docs/template-profile.md +164 -0
- package/docs/template-upgrades.md +114 -0
- package/docs/trackables.md +111 -0
- package/docs/upgrade.md +68 -3
- package/fixtures/atelier-architecture/binding/invalid/document.json +19 -0
- package/fixtures/atelier-architecture/binding/valid/document.json +19 -0
- package/fixtures/atelier-architecture/catalog/invalid/document.json +290 -0
- package/fixtures/atelier-architecture/catalog/valid/document.json +343 -0
- package/fixtures/atelier-build-contract/attempt/invalid/document.json +20 -0
- package/fixtures/atelier-build-contract/attempt/valid/document.json +19 -0
- package/fixtures/atelier-build-contract/candidate/invalid/document.json +24 -0
- package/fixtures/atelier-build-contract/candidate/valid/document.json +23 -0
- package/fixtures/atelier-build-contract/decision/invalid/document.json +31 -0
- package/fixtures/atelier-build-contract/decision/valid/document.json +30 -0
- package/fixtures/atelier-build-contract/delivery/invalid/document.json +18 -0
- package/fixtures/atelier-build-contract/delivery/valid/document.json +17 -0
- package/fixtures/atelier-build-contract/gate/invalid/document.json +22 -0
- package/fixtures/atelier-build-contract/gate/valid/document.json +21 -0
- package/fixtures/atelier-build-contract/ledger/invalid/document.json +270 -0
- package/fixtures/atelier-build-contract/ledger/valid/document.json +269 -0
- package/fixtures/atelier-build-contract/objective/invalid/document.json +73 -0
- package/fixtures/atelier-build-contract/objective/valid/document.json +72 -0
- package/fixtures/atelier-build-contract/progress/invalid/document.json +18 -0
- package/fixtures/atelier-build-contract/progress/valid/document.json +17 -0
- package/fixtures/atelier-capability-contract/adoption/invalid/document.json +71 -0
- package/fixtures/atelier-capability-contract/adoption/valid/document.json +70 -0
- package/fixtures/atelier-capability-contract/event/invalid/document.json +18 -0
- package/fixtures/atelier-capability-contract/event/valid/document.json +17 -0
- package/fixtures/atelier-capability-contract/journal/invalid/document.json +382 -0
- package/fixtures/atelier-capability-contract/journal/valid/document.json +381 -0
- package/fixtures/atelier-capability-contract/notice/invalid/document.json +8 -0
- package/fixtures/atelier-capability-contract/notice/valid/document.json +7 -0
- package/fixtures/atelier-capability-contract/package/invalid/document.json +59 -0
- package/fixtures/atelier-capability-contract/package/valid/document.json +58 -0
- package/fixtures/atelier-capability-contract/plan/invalid/document.json +107 -0
- package/fixtures/atelier-capability-contract/plan/valid/document.json +106 -0
- package/fixtures/atelier-capability-contract/release/invalid/document.json +85 -0
- package/fixtures/atelier-capability-contract/release/valid/document.json +84 -0
- package/fixtures/atelier-capability-contract/state/invalid/document.json +316 -0
- package/fixtures/atelier-capability-contract/state/valid/document.json +315 -0
- package/fixtures/atelier-coordination/dependency/invalid/document.json +37 -0
- package/fixtures/atelier-coordination/dependency/valid/document.json +36 -0
- package/fixtures/atelier-coordination/directive/invalid/document.json +25 -0
- package/fixtures/atelier-coordination/directive/valid/document.json +24 -0
- package/fixtures/atelier-coordination/disposition/invalid/document.json +34 -0
- package/fixtures/atelier-coordination/disposition/valid/document.json +33 -0
- package/fixtures/atelier-coordination/outcome/invalid/document.json +39 -0
- package/fixtures/atelier-coordination/outcome/valid/document.json +38 -0
- package/fixtures/atelier-coordination/recovery/invalid/document.json +34 -0
- package/fixtures/atelier-coordination/recovery/valid/document.json +33 -0
- package/fixtures/atelier-harness-contract/handoff/invalid/document.json +25 -0
- package/fixtures/atelier-harness-contract/handoff/valid/document.json +24 -0
- package/fixtures/atelier-ingestion/invalid/authority.json +19 -0
- package/fixtures/atelier-ingestion/semantic-baseline.json +26 -0
- package/fixtures/atelier-ingestion/semantic-operation.json +75 -0
- package/fixtures/atelier-ingestion/valid/plan-input.json +18 -0
- package/fixtures/atelier-inquiry-contract/assessment/invalid/document.json +41 -0
- package/fixtures/atelier-inquiry-contract/assessment/valid/document.json +40 -0
- package/fixtures/atelier-inquiry-contract/bundle/invalid/document.json +51 -0
- package/fixtures/atelier-inquiry-contract/bundle/valid/document.json +50 -0
- package/fixtures/atelier-inquiry-contract/campaign/invalid/document.json +17 -0
- package/fixtures/atelier-inquiry-contract/campaign/valid/document.json +16 -0
- package/fixtures/atelier-inquiry-contract/decision/invalid/document.json +22 -0
- package/fixtures/atelier-inquiry-contract/decision/valid/document.json +21 -0
- package/fixtures/atelier-inquiry-contract/feedback/invalid/document.json +19 -0
- package/fixtures/atelier-inquiry-contract/feedback/valid/document.json +18 -0
- package/fixtures/atelier-inquiry-contract/handoff/invalid/document.json +92 -0
- package/fixtures/atelier-inquiry-contract/handoff/valid/document.json +91 -0
- package/fixtures/atelier-inquiry-contract/hypothesis/invalid/document.json +20 -0
- package/fixtures/atelier-inquiry-contract/hypothesis/valid/document.json +19 -0
- package/fixtures/atelier-inquiry-contract/ledger/invalid/document.json +680 -0
- package/fixtures/atelier-inquiry-contract/ledger/valid/document.json +679 -0
- package/fixtures/atelier-inquiry-contract/legacy-assessment/invalid/document.json +19 -0
- package/fixtures/atelier-inquiry-contract/legacy-assessment/valid/document.json +18 -0
- package/fixtures/atelier-inquiry-contract/request/invalid/document.json +50 -0
- package/fixtures/atelier-inquiry-contract/request/valid/document.json +49 -0
- package/fixtures/atelier-inquiry-contract/source/invalid/document.json +18 -0
- package/fixtures/atelier-inquiry-contract/source/valid/document.json +17 -0
- package/fixtures/atelier-inquiry-contract/withdrawal/invalid/document.json +16 -0
- package/fixtures/atelier-inquiry-contract/withdrawal/valid/document.json +15 -0
- package/fixtures/atelier-instruction-adoption/context-receipt/invalid/document.json +18 -0
- package/fixtures/atelier-instruction-adoption/context-receipt/valid/document.json +17 -0
- package/fixtures/atelier-instruction-adoption/plan/invalid/document.json +63 -0
- package/fixtures/atelier-instruction-adoption/plan/valid/document.json +62 -0
- package/fixtures/atelier-instruction-adoption/receipt/invalid/document.json +22 -0
- package/fixtures/atelier-instruction-adoption/receipt/valid/document.json +21 -0
- package/fixtures/atelier-instruction-adoption/state/invalid/document.json +30 -0
- package/fixtures/atelier-instruction-adoption/state/valid/document.json +29 -0
- package/fixtures/atelier-interaction/act/invalid/document.json +25 -0
- package/fixtures/atelier-interaction/act/valid/document.json +24 -0
- package/fixtures/atelier-interaction/assessment/invalid/document.json +32 -0
- package/fixtures/atelier-interaction/assessment/valid/document.json +31 -0
- package/fixtures/atelier-interaction/observation/invalid/document.json +22 -0
- package/fixtures/atelier-interaction/observation/valid/document.json +21 -0
- package/fixtures/atelier-interaction/policy/invalid/document.json +20 -0
- package/fixtures/atelier-interaction/policy/valid/document.json +19 -0
- package/fixtures/atelier-judgment/case/invalid/document.json +76 -0
- package/fixtures/atelier-judgment/case/valid/document.json +75 -0
- package/fixtures/atelier-judgment/decision/invalid/document.json +38 -0
- package/fixtures/atelier-judgment/decision/valid/document.json +37 -0
- package/fixtures/atelier-judgment/native-decision.md +3 -0
- package/fixtures/atelier-judgment/practice-reconsideration/definition.json +92 -0
- package/fixtures/atelier-judgment/practice-reconsideration/scenario.json +435 -0
- package/fixtures/atelier-knowledge-contract/activation/invalid/document.json +26 -0
- package/fixtures/atelier-knowledge-contract/activation/valid/document.json +25 -0
- package/fixtures/atelier-knowledge-contract/contribution/invalid/document.json +34 -0
- package/fixtures/atelier-knowledge-contract/contribution/valid/document.json +33 -0
- package/fixtures/atelier-knowledge-contract/domain/invalid/document.json +41 -0
- package/fixtures/atelier-knowledge-contract/domain/valid/document.json +40 -0
- package/fixtures/atelier-knowledge-contract/domain-revision/invalid/document.json +46 -0
- package/fixtures/atelier-knowledge-contract/domain-revision/valid/document.json +45 -0
- package/fixtures/atelier-knowledge-contract/evaluation/invalid/document.json +21 -0
- package/fixtures/atelier-knowledge-contract/evaluation/valid/document.json +20 -0
- package/fixtures/atelier-knowledge-contract/ledger/invalid/document.json +318 -0
- package/fixtures/atelier-knowledge-contract/ledger/valid/document.json +317 -0
- package/fixtures/atelier-knowledge-contract/relation/invalid/document.json +25 -0
- package/fixtures/atelier-knowledge-contract/relation/valid/document.json +24 -0
- package/fixtures/atelier-knowledge-contract/review/invalid/document.json +18 -0
- package/fixtures/atelier-knowledge-contract/review/valid/document.json +17 -0
- package/fixtures/atelier-knowledge-contract/withdrawal/invalid/document.json +16 -0
- package/fixtures/atelier-knowledge-contract/withdrawal/valid/document.json +15 -0
- package/fixtures/atelier-learning/invalid/authority.json +14 -0
- package/fixtures/atelier-learning/valid/capture.json +13 -0
- package/fixtures/atelier-personal-workspace-contract/manifest/invalid/authority-key.json +17 -0
- package/fixtures/atelier-personal-workspace-contract/manifest/invalid/nonempty-ext.json +25 -0
- package/fixtures/atelier-personal-workspace-contract/manifest/invalid/wrong-schema.json +22 -0
- package/fixtures/atelier-personal-workspace-contract/manifest/valid/manifest.json +22 -0
- package/fixtures/atelier-personal-workspace-contract/overlay/invalid/authority-key.json +25 -0
- package/fixtures/atelier-personal-workspace-contract/overlay/invalid/unknown-top-level-field.json +27 -0
- package/fixtures/atelier-personal-workspace-contract/overlay/invalid/wrong-schema.json +24 -0
- package/fixtures/atelier-trackable/definition/invalid/document.json +15 -0
- package/fixtures/atelier-trackable/definition/valid/document.json +14 -0
- package/fixtures/atelier-trackable/request/invalid/document.json +23 -0
- package/fixtures/atelier-trackable/request/valid/document.json +22 -0
- package/fixtures/boundary-delegations/invalid/authority-key.v1.json +30 -0
- package/fixtures/boundary-delegations/invalid/push-operation.v1.json +28 -0
- package/fixtures/boundary-delegations/invalid/unsigned.v1.json +25 -0
- package/fixtures/boundary-delegations/valid/empty.v1.json +4 -0
- package/fixtures/boundary-delegations/valid/signed-operator.v1.json +30 -0
- package/fixtures/boundary-owner-keys/invalid/wrong-algorithm.v1.json +15 -0
- package/fixtures/boundary-owner-keys/invalid/wrong-curve.v1.json +15 -0
- package/fixtures/boundary-owner-keys/valid/one-owner.v1.json +17 -0
- package/fixtures/capability-packages/adoption.example.json +70 -0
- package/fixtures/capability-packages/evidence-review/capability-package.json +58 -0
- package/fixtures/capability-packages/evidence-review/capability-release.json +84 -0
- package/fixtures/capability-packages/evidence-review/evaluation.md +15 -0
- package/fixtures/capability-packages/evidence-review/skills/evidence-review/SKILL.md +22 -0
- package/fixtures/capability-packages/evidence-review/skills/evidence-review/references/ledger.md +7 -0
- package/fixtures/capability-packages/research/capability-package.json +88 -0
- package/fixtures/capability-packages/research/capability-release.json +134 -0
- package/fixtures/capability-packages/research/evaluation.md +15 -0
- package/fixtures/capability-packages/research/skills/research-integrate/SKILL.md +29 -0
- package/fixtures/capability-packages/research/skills/research-integrate/references/integration.md +10 -0
- package/fixtures/capability-packages/research/skills/research-plan/SKILL.md +27 -0
- package/fixtures/capability-packages/research/skills/research-plan/references/inquiries.md +26 -0
- package/fixtures/capability-packages/research/skills/research-synthesize/SKILL.md +23 -0
- package/fixtures/capability-packages/research/skills/research-synthesize/references/synthesis.md +12 -0
- package/fixtures/evidence-navigation/invalid/duplicate-operation.json +16 -0
- package/fixtures/evidence-navigation/invalid/instruction-kind.json +36 -0
- package/fixtures/evidence-navigation/invalid/invalid-digest.json +36 -0
- package/fixtures/evidence-navigation/invalid/invalid-time.json +36 -0
- package/fixtures/evidence-navigation/invalid/missing-attribution.json +35 -0
- package/fixtures/evidence-navigation/invalid/reserved-extension.json +19 -0
- package/fixtures/evidence-navigation/invalid/unattributed-acceptance.json +36 -0
- package/fixtures/evidence-navigation/invalid/uncoerced-version.json +16 -0
- package/fixtures/evidence-navigation/invalid/unknown-currency.json +17 -0
- package/fixtures/evidence-navigation/invalid/unknown-selector-field.json +37 -0
- package/fixtures/evidence-navigation/invalid/unsupported-operation.json +15 -0
- package/fixtures/evidence-navigation/valid/claim.json +36 -0
- package/fixtures/evidence-navigation/valid/host.json +16 -0
- package/fixtures/evidence-navigation/valid/profile.json +16 -0
- package/fixtures/evidence-navigation/valid/snapshot.json +17 -0
- package/fixtures/harness-packages/build-harness/capability-package.json +61 -0
- package/fixtures/harness-packages/build-harness/capability-release.json +82 -0
- package/fixtures/harness-packages/build-harness/evaluation.md +3 -0
- package/fixtures/harness-packages/build-harness/skills/atelier-build-harness/SKILL.md +59 -0
- package/fixtures/harness-packages/knowledge-harness/capability-package.json +61 -0
- package/fixtures/harness-packages/knowledge-harness/capability-release.json +82 -0
- package/fixtures/harness-packages/knowledge-harness/evaluation.md +3 -0
- package/fixtures/harness-packages/knowledge-harness/skills/atelier-knowledge-harness/SKILL.md +60 -0
- package/fixtures/harnesses/learning-cycle.json +780 -0
- package/fixtures/inquiry/workshop.json +675 -0
- package/fixtures/inquiry-packages/discovery-harness/capability-package.json +61 -0
- package/fixtures/inquiry-packages/discovery-harness/capability-release.json +82 -0
- package/fixtures/inquiry-packages/discovery-harness/evaluation.md +3 -0
- package/fixtures/inquiry-packages/discovery-harness/skills/atelier-discovery-harness/SKILL.md +48 -0
- package/fixtures/inquiry-packages/research-harness/capability-package.json +61 -0
- package/fixtures/inquiry-packages/research-harness/capability-release.json +82 -0
- package/fixtures/inquiry-packages/research-harness/evaluation.md +3 -0
- package/fixtures/inquiry-packages/research-harness/skills/atelier-research-harness/SKILL.md +43 -0
- package/fixtures/knowledge-health/calibration-suite.json +26 -0
- package/fixtures/knowledge-health/calibration-trial.json +37 -0
- package/fixtures/knowledge-health/corrected/checklist.md +18 -0
- package/fixtures/knowledge-health/devday/participant-record.json +19 -0
- package/fixtures/knowledge-health/expected-summary.json +27 -0
- package/fixtures/knowledge-health/workshop/README.md +20 -0
- package/fixtures/knowledge-health/workshop/atelier.project.json +7 -0
- package/fixtures/knowledge-health/workshop/knowledge-plan.json +65 -0
- package/fixtures/knowledge-health/workshop/records/checklist.md +18 -0
- package/fixtures/knowledge-health/workshop/records/workshop.md +18 -0
- package/fixtures/knowledge-health/workshop/repo-access.v1.json +5 -0
- package/fixtures/knowledge-health/workspace/atelier.project.json +18 -0
- package/fixtures/knowledge-health/workspace/knowledge-plan.json +74 -0
- package/fixtures/knowledge-health/workspace/records/checklist.md +18 -0
- package/fixtures/knowledge-health/workspace/records/pilot.md +16 -0
- package/fixtures/knowledge-health/workspace/repo-access.v1.json +9 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/case-only-collision.v2.json +160 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/flat-note-path.v2.json +160 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/hidden-folder.v2.json +160 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/identity-in-the-body.v2.json +160 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/link-breaking-name.v2.json +160 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/missing-identity-region.v2.json +156 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/missing-layout-version.v2.json +159 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/note-named-like-a-folder.v2.json +160 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/unknown-region-kind.v2.json +160 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/v1-contract-version.v2.json +161 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/valid/three-notes.v2.json +160 -0
- package/fixtures/obsidian/edits/workspace.json +1 -1
- package/fixtures/obsidian/materialization/expected-full.json +21 -21
- package/fixtures/obsidian/materialization/expected-full.layout-1.json +99 -0
- package/fixtures/obsidian/materialization/expected-scoped.json +7 -7
- package/fixtures/obsidian/materialization/expected-scoped.layout-1.json +39 -0
- package/fixtures/personal-workspace/overlay.json +24 -0
- package/fixtures/templates/local-library.v1.json +75 -0
- package/fixtures/templates/upgrade-contracts/adoption-policy.v2/invalid.json +16 -0
- package/fixtures/templates/upgrade-contracts/adoption-policy.v2/valid.json +13 -0
- package/fixtures/templates/upgrade-contracts/migration.v3/invalid.json +24 -0
- package/fixtures/templates/upgrade-contracts/migration.v3/valid.json +21 -0
- package/fixtures/templates/upgrade-contracts/template-adoption.v1/invalid.json +60 -0
- package/fixtures/templates/upgrade-contracts/template-adoption.v1/valid.json +57 -0
- package/fixtures/templates/upgrade-contracts/upgrade-plan.v3/invalid.json +85 -0
- package/fixtures/templates/upgrade-contracts/upgrade-plan.v3/valid.json +82 -0
- package/package.json +64 -6
- package/plugins/obsidian/main.js +444 -0
- package/plugins/obsidian/manifest.json +9 -0
- package/plugins/obsidian/styles.css +22 -0
- package/skills/claude/atelier-build-harness/SKILL.md +59 -0
- package/skills/claude/atelier-coordination/SKILL.md +32 -0
- package/skills/claude/atelier-discovery-harness/SKILL.md +48 -0
- package/skills/claude/atelier-guided-upgrade/SKILL.md +29 -0
- package/skills/claude/atelier-interaction/SKILL.md +34 -0
- package/skills/claude/atelier-knowledge-harness/SKILL.md +60 -0
- package/skills/claude/atelier-knowledge-setup/SKILL.md +72 -0
- package/skills/claude/atelier-practical-judgment/SKILL.md +64 -0
- package/skills/claude/atelier-research-harness/SKILL.md +43 -0
- package/skills/claude/atelier-skill-steward/SKILL.md +53 -0
- package/skills/claude/atelier-trackables/SKILL.md +40 -0
- package/skills/codex/atelier-build-harness/SKILL.md +59 -0
- package/skills/codex/atelier-coordination/SKILL.md +32 -0
- package/skills/codex/atelier-discovery-harness/SKILL.md +48 -0
- package/skills/codex/atelier-guided-upgrade/SKILL.md +29 -0
- package/skills/codex/atelier-interaction/SKILL.md +34 -0
- package/skills/codex/atelier-knowledge-harness/SKILL.md +60 -0
- package/skills/codex/atelier-knowledge-setup/SKILL.md +72 -0
- package/skills/codex/atelier-practical-judgment/SKILL.md +64 -0
- package/skills/codex/atelier-research-harness/SKILL.md +43 -0
- package/skills/codex/atelier-skill-steward/SKILL.md +53 -0
- package/skills/codex/atelier-trackables/SKILL.md +40 -0
- package/src/architecture/catalog.mjs +355 -0
- package/src/architecture/index.mjs +93 -0
- package/src/boundary/content-rules.mjs +4 -2
- package/src/boundary/delegation-command.mjs +158 -0
- package/src/boundary/delegation.mjs +396 -0
- package/src/boundary/policy.mjs +52 -10
- package/src/build/git.mjs +18 -0
- package/src/build/index.mjs +3 -0
- package/src/build/ledger.mjs +88 -0
- package/src/capabilities/adoption.mjs +381 -0
- package/src/capabilities/evidence.mjs +140 -0
- package/src/capabilities/files.mjs +124 -0
- package/src/capabilities/index.mjs +4 -0
- package/src/capabilities/instructions.mjs +250 -0
- package/src/capabilities/package.mjs +136 -0
- package/src/cli/command-failure.mjs +36 -0
- package/src/cli/execute-command.mjs +16 -2
- package/src/cli/run.mjs +65 -9
- package/src/coauthor/store.mjs +43 -2
- package/src/collaboration/proposals.mjs +189 -0
- package/src/commands/architecture.mjs +27 -0
- package/src/commands/boundary.mjs +3 -0
- package/src/commands/capability.mjs +73 -0
- package/src/commands/enroll.mjs +3 -0
- package/src/commands/harness.mjs +79 -0
- package/src/commands/ingest.mjs +39 -0
- package/src/commands/init.mjs +4 -1
- package/src/commands/inquiry.mjs +66 -0
- package/src/commands/knowledge-semantic.mjs +128 -0
- package/src/commands/knowledge.mjs +119 -0
- package/src/commands/learn.mjs +61 -0
- package/src/commands/obsidian.mjs +832 -62
- package/src/commands/practice.mjs +24 -0
- package/src/commands/setup.mjs +22 -2
- package/src/commands/trackable.mjs +19 -0
- package/src/contracts/corpus.mjs +36 -0
- package/src/coordination/index.mjs +110 -0
- package/src/egress/forbidden-egress.mjs +2 -0
- package/src/evidence-navigation/contracts.mjs +60 -0
- package/src/evidence-navigation/evaluate.mjs +69 -0
- package/src/evidence-navigation/generation.mjs +22 -0
- package/src/evidence-navigation/index.mjs +2 -0
- package/src/evidence-navigation/local.mjs +167 -0
- package/src/evidence-navigation/schema.generated.mjs +604 -0
- package/src/evidence-navigation/types.mjs +12 -0
- package/src/export/atelier-export-contract.mjs +35 -8
- package/src/graph/enroll.mjs +248 -0
- package/src/graph/graph.mjs +17 -1
- package/src/graph/knowledge-graph.mjs +58 -8
- package/src/harnesses/contracts.mjs +41 -0
- package/src/harnesses/exchange.mjs +154 -0
- package/src/harnesses/feedback.mjs +16 -0
- package/src/harnesses/history.mjs +37 -0
- package/src/harnesses/index.mjs +4 -0
- package/src/harnesses/store.mjs +29 -0
- package/src/ingestion/contracts.mjs +41 -0
- package/src/ingestion/evaluation.mjs +119 -0
- package/src/ingestion/optional/composition/index.mjs +171 -0
- package/src/ingestion/optional/evaluation/index.mjs +219 -0
- package/src/ingestion/optional/graphify/composition-binding.mjs +60 -0
- package/src/ingestion/optional/graphify/composition_receiving.py +185 -0
- package/src/ingestion/optional/graphify/graphify_supplier.py +933 -0
- package/src/ingestion/optional/graphify/json_bridge.py +163 -0
- package/src/ingestion/optional/graphify/query_receiving.py +209 -0
- package/src/ingestion/optional/index.mjs +9 -0
- package/src/ingestion/optional/jev/bridge.mjs +166 -0
- package/src/ingestion/optional/jev/composition-binding.mjs +116 -0
- package/src/ingestion/optional/jev/judgments.mjs +174 -0
- package/src/ingestion/optional/jev/native-translation.mjs +32 -0
- package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/__init__.py +7 -0
- package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/adapter.py +966 -0
- package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/artifacts.py +101 -0
- package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/projection.py +146 -0
- package/src/ingestion/optional/lightrag/composition-binding.mjs +67 -0
- package/src/ingestion/optional/vanilla/composition-binding.mjs +102 -0
- package/src/ingestion/optional/vanilla/index.mjs +218 -0
- package/src/ingestion/optional/vanilla/native-normalization.mjs +187 -0
- package/src/ingestion/processors.mjs +218 -0
- package/src/ingestion/semantic.mjs +203 -0
- package/src/ingestion/store.mjs +372 -0
- package/src/inquiry/beliefs.mjs +32 -0
- package/src/inquiry/contracts.mjs +23 -0
- package/src/inquiry/index.mjs +5 -0
- package/src/inquiry/ledger.mjs +135 -0
- package/src/inquiry/lenses.mjs +15 -0
- package/src/inquiry/projection.mjs +77 -0
- package/src/inquiry/store.mjs +35 -0
- package/src/intake/read-scope.mjs +12 -0
- package/src/intake/store.mjs +69 -6
- package/src/interaction/index.mjs +91 -0
- package/src/judgment/index.mjs +82 -0
- package/src/judgment/practice-evaluation.mjs +70 -0
- package/src/judgment/practice.mjs +144 -0
- package/src/knowledge/context.mjs +217 -0
- package/src/knowledge/index.mjs +5 -0
- package/src/knowledge/ingestion.mjs +79 -0
- package/src/knowledge/intake.mjs +33 -0
- package/src/knowledge/ledger.mjs +110 -0
- package/src/knowledge/participatory/guidance.mjs +36 -0
- package/src/knowledge/participatory/guided-contribution.mjs +44 -0
- package/src/knowledge/participatory/index.mjs +2 -0
- package/src/knowledge/participatory/installed-workshop.mjs +108 -0
- package/src/knowledge/plan.mjs +67 -0
- package/src/knowledge/projection.mjs +43 -0
- package/src/knowledge/semantic-operation.mjs +567 -0
- package/src/knowledge/sessions.mjs +467 -0
- package/src/knowledge/workspace.mjs +295 -0
- package/src/knowledge-health/assessment.mjs +165 -0
- package/src/knowledge-health/capabilities.mjs +350 -0
- package/src/knowledge-health/context.mjs +62 -0
- package/src/knowledge-health/example.mjs +47 -0
- package/src/knowledge-health/index.mjs +10 -0
- package/src/knowledge-health/owner-join.mjs +88 -0
- package/src/knowledge-health/proof/common.mjs +24 -0
- package/src/knowledge-health/proof/installed-check.mjs +100 -0
- package/src/knowledge-health/proof/native-readback.mjs +37 -0
- package/src/knowledge-health/proof/rehearse.mjs +68 -0
- package/src/knowledge-health/proof/validate-participant.mjs +17 -0
- package/src/knowledge-health/proof/verify-native-capture.mjs +14 -0
- package/src/knowledge-health/proof/workshop-check.mjs +36 -0
- package/src/knowledge-health/rule-identity.mjs +44 -0
- package/src/knowledge-health/workshop-composition.mjs +19 -0
- package/src/knowledge-health/workshop-profile.mjs +176 -0
- package/src/knowledge-health/workshop.mjs +99 -0
- package/src/learning/adapters.mjs +68 -0
- package/src/learning/assistance.mjs +127 -0
- package/src/learning/contracts.mjs +65 -0
- package/src/learning/store.mjs +272 -0
- package/src/personal-workspace/index.mjs +391 -0
- package/src/personal-workspace/selection.mjs +385 -0
- package/src/project/config.mjs +8 -0
- package/src/project/durable-state.mjs +3 -1
- package/src/project/file-class.mjs +1 -1
- package/src/project/private-state.mjs +10 -1
- package/src/projection/obsidian/contracts.mjs +224 -18
- package/src/projection/obsidian/edits/apply.mjs +63 -19
- package/src/projection/obsidian/edits/regions.mjs +137 -42
- package/src/projection/obsidian/materialize/index.mjs +6 -3
- package/src/projection/obsidian/materialize/path-registry.mjs +443 -57
- package/src/projection/obsidian/materialize/prepare-view.mjs +382 -131
- package/src/projection/obsidian/materialize/redaction.mjs +327 -0
- package/src/projection/obsidian/materialize/settings.mjs +113 -34
- package/src/projection/obsidian/personal-workspace.mjs +436 -0
- package/src/projection/obsidian/plugin-bridge/bundle.mjs +50 -0
- package/src/projection/obsidian/plugin-bridge/channel.mjs +174 -0
- package/src/projection/obsidian/plugin-bridge/index.mjs +10 -0
- package/src/projection/obsidian/proposals/adapter.mjs +47 -8
- package/src/projection/obsidian/proposals/observation.mjs +20 -7
- package/src/projection/obsidian/publication/bridge-script.mjs +11 -5
- package/src/projection/obsidian/publication/index.mjs +2 -1
- package/src/projection/obsidian/publication/publisher.mjs +312 -36
- package/src/projection/obsidian/publication/transport.mjs +63 -8
- package/src/projection/obsidian/publication/vault-list.mjs +200 -0
- package/src/projection/obsidian/recovery/index.mjs +4 -1
- package/src/projection/obsidian/recovery/restart.mjs +3 -1
- package/src/projection/obsidian/recovery/store.mjs +168 -8
- package/src/projection/obsidian/selection-ui/contribution.mjs +6 -5
- package/src/projection/obsidian/selection-ui/focus.mjs +14 -7
- package/src/projection/obsidian/selection-ui/selection-state.mjs +2 -2
- package/src/projection/obsidian/selection-ui/selection.mjs +8 -3
- package/src/readiness/readiness.mjs +10 -3
- package/src/reflection/index.mjs +56 -0
- package/src/runtime/obsidian/app-capability.mjs +104 -12
- package/src/runtime/obsidian/app-production-seams.mjs +161 -42
- package/src/runtime/obsidian/app-registration.mjs +313 -0
- package/src/runtime/obsidian/app-restart.mjs +154 -0
- package/src/runtime/obsidian/engine.mjs +288 -29
- package/src/runtime/obsidian/extension-points.mjs +1 -1
- package/src/runtime/obsidian/index.mjs +15 -8
- package/src/runtime/obsidian/launch-plan.mjs +91 -0
- package/src/runtime/obsidian/lifecycle.mjs +193 -22
- package/src/runtime/obsidian/login-item.mjs +332 -0
- package/src/runtime/obsidian/machine-settings.mjs +119 -12
- package/src/runtime/obsidian/opening.mjs +369 -63
- package/src/runtime/obsidian/pending-edits.mjs +10 -0
- package/src/runtime/obsidian/pipeline.mjs +71 -3
- package/src/runtime/obsidian/plugin-channel.mjs +289 -0
- package/src/runtime/obsidian/plugin-choice.mjs +182 -0
- package/src/runtime/obsidian/plugin-drift.mjs +124 -0
- package/src/runtime/obsidian/plugin-presence.mjs +61 -0
- package/src/runtime/obsidian/process-identity.mjs +20 -10
- package/src/runtime/obsidian/project-views.mjs +282 -0
- package/src/runtime/obsidian/questions.mjs +48 -0
- package/src/runtime/obsidian/release-watch.mjs +56 -0
- package/src/runtime/obsidian/service-main.mjs +113 -17
- package/src/runtime/obsidian/service-manager-production.mjs +61 -0
- package/src/runtime/obsidian/service-managers.mjs +275 -0
- package/src/runtime/obsidian/service-record.mjs +105 -5
- package/src/runtime/obsidian/service-server.mjs +50 -12
- package/src/runtime/obsidian/service.mjs +134 -7
- package/src/runtime/obsidian/startup-adapters.mjs +59 -21
- package/src/runtime/obsidian/state-store.mjs +13 -1
- package/src/runtime/obsidian/unheld-evidence.mjs +84 -0
- package/src/runtime/obsidian/vault-location.mjs +300 -0
- package/src/runtime/obsidian/view-counts.mjs +34 -0
- package/src/server/local-sidecar.mjs +193 -7
- package/src/server/server.mjs +45 -6
- package/src/template-bindings/project.mjs +92 -0
- package/src/templates/profile.mjs +225 -0
- package/src/trackables/domain.mjs +175 -0
- package/src/trackables/store.mjs +114 -0
- package/src/ui/knowledge-page.mjs +1388 -0
- package/src/upgrade/explanation.mjs +1 -1
- package/src/upgrade/template-participant.mjs +171 -0
- package/src/upgrade/transaction.mjs +72 -25
- package/src/upgrade/upgrade.mjs +2 -1
- package/templates/external-project-workspace/pack-lifecycle.json +6 -1
- package/templates/knowledge-workspace/.gitattributes +2 -0
- package/templates/knowledge-workspace/AGENTS.md +27 -0
- package/templates/knowledge-workspace/README.md +44 -0
- package/templates/knowledge-workspace/atelier.project.json +22 -0
- package/templates/knowledge-workspace/gitignore +6 -0
- package/templates/knowledge-workspace/knowledge-plan.json +111 -0
- package/templates/knowledge-workspace/knowledge-plan.schema.json +281 -0
- package/templates/knowledge-workspace/records/forecast.md +22 -0
- package/templates/knowledge-workspace/records/inspection.md +25 -0
- package/templates/knowledge-workspace/records/telescope.md +24 -0
- package/templates/knowledge-workspace/repo-access.v1.json +9 -0
|
@@ -0,0 +1,619 @@
|
|
|
1
|
+
# Atelier's Obsidian plugin
|
|
2
|
+
|
|
3
|
+
Atelier ships its own Obsidian plugin inside every vault it manages. Nobody
|
|
4
|
+
finds or installs it: the maintenance service puts it into the vault with the
|
|
5
|
+
notes, and Obsidian asks once per vault whether to trust the vault's plugins.
|
|
6
|
+
Until the person says yes, and whenever the plugin is absent, turned off or
|
|
7
|
+
running in an app that is too old, everything works as it did before through
|
|
8
|
+
the command-line path described in [obsidian.md](obsidian.md).
|
|
9
|
+
|
|
10
|
+
This is phase 1: the plugin is present, reports status and holds a channel to
|
|
11
|
+
the service. It decides nothing and writes nothing. Phase 2, planned at the
|
|
12
|
+
end of this document, moves the in-app part of publication into it.
|
|
13
|
+
|
|
14
|
+
The source is `plugins/obsidian/` in the package: `manifest.json`, `main.js`
|
|
15
|
+
and `styles.css`, plain JavaScript with no build step. The channel it speaks
|
|
16
|
+
is `src/projection/obsidian/plugin-bridge/channel.mjs`; the service's half is
|
|
17
|
+
`src/runtime/obsidian/plugin-channel.mjs`.
|
|
18
|
+
|
|
19
|
+
## What it does
|
|
20
|
+
|
|
21
|
+
- A status bar item says how current the view is: `Atelier: current`,
|
|
22
|
+
`Atelier: updating`, `Atelier: held (N)` with the number of notes held for
|
|
23
|
+
an edit you made, `Atelier: stale` or `Atelier: service unreachable`. Before
|
|
24
|
+
the first answer it says `Atelier: connecting`; without channel data, or in
|
|
25
|
+
a copy of the vault rather than the vault Atelier maintains, it says
|
|
26
|
+
`Atelier: not set up`; in an app older than 1.13.7 it says
|
|
27
|
+
`Atelier: needs a newer Obsidian`. Between `not set up` and
|
|
28
|
+
`service unreachable` it moves only when two rounds in a row agree, so a
|
|
29
|
+
listener that answers one way and then the other does not make it flip;
|
|
30
|
+
every other change shows at once.
|
|
31
|
+
- The command "Atelier: show status", or a click on the status bar item, opens
|
|
32
|
+
a window with the view, its state and the reason in words, the committed and
|
|
33
|
+
the prepared generation, when freshness was last checked, the held, retained
|
|
34
|
+
and pending edits, the service address and state, and the plugin and app
|
|
35
|
+
versions.
|
|
36
|
+
- A notice appears when the view moves into a state that needs a person (held,
|
|
37
|
+
stale, service unreachable, app too old) and when it is current again.
|
|
38
|
+
`updating` is not announced: it is what every change looks like.
|
|
39
|
+
- While the vault is open the plugin tells the service so: it shakes hands
|
|
40
|
+
once (see "The channel"), then renews a lease every two seconds, and
|
|
41
|
+
releases it when the plugin unloads (the vault closes, the app quits, the
|
|
42
|
+
plugin is turned off). A lease that is not renewed lapses after six seconds.
|
|
43
|
+
The hello carries the plugin version, the app version (`apiVersion`, the
|
|
44
|
+
version of the app the plugin runs in), an id of this launch of the plugin,
|
|
45
|
+
and a proof of the real path of the vault the app has open. Both sides
|
|
46
|
+
take that path as the file system stores it (on a volume that folds
|
|
47
|
+
letter case, in the case it is stored in; on Windows, Node's own
|
|
48
|
+
resolution), so a data root given in another letter case, or an app that
|
|
49
|
+
has the vault in another, still proves the view's vault. Plugin 1.1.1
|
|
50
|
+
does so; 1.1.0 kept the app's spelling.
|
|
51
|
+
|
|
52
|
+
## What it never does
|
|
53
|
+
|
|
54
|
+
- It writes no file. No note, no setting, and not its own `data.json`: it
|
|
55
|
+
never calls `saveData` and never uses the vault's write operations.
|
|
56
|
+
- It runs no code it receives. The service answers with JSON documents of
|
|
57
|
+
fixed shape and the plugin reads fields out of them.
|
|
58
|
+
- It reaches nothing but the maintenance service named in its `data.json`, on
|
|
59
|
+
the literal address `127.0.0.1` or `::1`, through Node's `http` module, with
|
|
60
|
+
no name to resolve. It sends no telemetry.
|
|
61
|
+
- It never sends its vault's key, and it sends nothing that names the vault or
|
|
62
|
+
the view until whatever listens at that address has proven that it holds
|
|
63
|
+
the key.
|
|
64
|
+
- It holds no state that matters: no manifest, no edit, no policy, no recovery
|
|
65
|
+
bytes. Removing it loses nothing.
|
|
66
|
+
|
|
67
|
+
The test suite holds the plugin to this: it loads `main.js` the way the app
|
|
68
|
+
does into a stand-in app whose every write throws, records every module the
|
|
69
|
+
plugin requires (`obsidian`, `http`, `fs`, `crypto`) and every request it
|
|
70
|
+
makes with its exact body, and fails a copy of the plugin that points a
|
|
71
|
+
request at a host from its data file (see "Evidence" below).
|
|
72
|
+
|
|
73
|
+
## How it gets into a vault
|
|
74
|
+
|
|
75
|
+
The publisher's settings unit owns these paths of a vault, and nothing else
|
|
76
|
+
under `.obsidian/`:
|
|
77
|
+
|
|
78
|
+
| Path | Owned | How it is written |
|
|
79
|
+
| --- | --- | --- |
|
|
80
|
+
| `.obsidian/core-plugins.json` | the `publish` and `sync` keys, kept `false` | merged with the file on disk; every other key stays |
|
|
81
|
+
| `.obsidian/community-plugins.json` | the `atelier-projection` entry | appended to the list on disk when missing; every other entry stays, in its order |
|
|
82
|
+
| `.obsidian/plugins/atelier-projection/manifest.json`, `main.js`, `styles.css` | the whole file | the shipped bytes |
|
|
83
|
+
| `.obsidian/plugins/atelier-projection/data.json` | the whole file | this vault's channel: the service address, the view and the view's bearer |
|
|
84
|
+
|
|
85
|
+
The plugin files' digests are pinned in the generation manifest, under
|
|
86
|
+
`ext["mnstry.atelier.obsidian"].settings.pluginOwned`, beside the record of
|
|
87
|
+
the owned settings keys and entries. A prepared view that carries a plugin
|
|
88
|
+
file its manifest does not pin, or pins one it does not carry, is refused
|
|
89
|
+
before anything is written.
|
|
90
|
+
|
|
91
|
+
Plugin files go through the same conditional publication as notes, with two
|
|
92
|
+
differences. Each is compared with what the disk holds when the run begins
|
|
93
|
+
rather than with an earlier generation, so whatever occupies a plugin path (a
|
|
94
|
+
hand edit, an earlier release, something else entirely) is exchanged out into
|
|
95
|
+
recovery with a receipt and is never lost. And a plugin path that a person has
|
|
96
|
+
to repair never holds a view back: a symlinked folder, a directory where a
|
|
97
|
+
file should be, or a file or folder nobody may read (`path-unsafe`), a vault
|
|
98
|
+
root that is not private enough for the bearer (`vault-not-private`), or a
|
|
99
|
+
file that cannot be made or replaced there, in a plugin folder the person can
|
|
100
|
+
read but not write, say (`create-failed`, `exchange-failed`), is reported and
|
|
101
|
+
left alone while every note converges. Once it is repaired,
|
|
102
|
+
or when a person changed or removed a plugin file, the file is written again
|
|
103
|
+
at the maintenance service's next tick, with nothing changed at the view's
|
|
104
|
+
sources: the service compares the plugin files each committed generation pins
|
|
105
|
+
with the disk, has a view whose files differ prepared again, and a generation
|
|
106
|
+
that is already committed is published again, as it is, with its notes kept.
|
|
107
|
+
A drift left for the person is asked about once, and again once it changes.
|
|
108
|
+
What a publication could not change is not asked about again: a drift that
|
|
109
|
+
publishing cannot repair (the data file of a vault root that is a link, say)
|
|
110
|
+
is asked about at most once more after a publication that left it, which
|
|
111
|
+
writes no vault file (the view is prepared again and takes the vault's lock,
|
|
112
|
+
and for that tick shows as `stale`, then `updating`, before it is `current`
|
|
113
|
+
again), and not again after that. A plugin file
|
|
114
|
+
another writer removes or changes after the publication, even while the same
|
|
115
|
+
tick goes on, unless it holds again what it held before the tick under the
|
|
116
|
+
same pin and vault root, and a path
|
|
117
|
+
the person repairs before the tick ends (the vault root made private,
|
|
118
|
+
say), is asked about at the next look and written;
|
|
119
|
+
where the plugin is turned off in the vault, a pinned file that is gone is no
|
|
120
|
+
drift. Publishing a committed generation again needs the app when one runs;
|
|
121
|
+
if that app does not qualify, nothing is written, the view stays `current`
|
|
122
|
+
(a committed generation needs no app, so an app that cannot be qualified
|
|
123
|
+
never makes a current view stale), and the file is written at a later
|
|
124
|
+
attempt, on the retry schedule of an unsettled view, once the app
|
|
125
|
+
qualifies. A drift that publishing would only leave for the person again
|
|
126
|
+
(the data file of a vault root that is a link or not private, a folder where
|
|
127
|
+
a file goes) is no reason to publish again and needs no app: the path is
|
|
128
|
+
left as it is, and the view keeps its plain reason. Nothing in `status` or
|
|
129
|
+
`open` names such a path yet; without its data file the plugin cannot reach
|
|
130
|
+
the service, so it shows as not present (`no-live-lease`). A few paths the
|
|
131
|
+
publisher leaves for the person still count as a drift here, and wait for the
|
|
132
|
+
app as one: a file nobody may read, a parent folder that cannot be looked
|
|
133
|
+
into, and a vault root Atelier keeps whose mode cannot be changed. Until the app qualifies, a
|
|
134
|
+
view waiting for it has the reason
|
|
135
|
+
`verified-by-read-back-plugin-waits-for-app` rather than
|
|
136
|
+
`verified-by-read-back`, and `status` and `open` add `plugin files wait for
|
|
137
|
+
the app` to the plugin line (`files: "waits-for-app"` in `--json`), with a
|
|
138
|
+
next step for the plugin. (The settings files are held to the same rule for a file nobody
|
|
139
|
+
may read.) A plugin file that another program changes while a publication runs
|
|
140
|
+
is a race and is handled like one on a note: that publication does not
|
|
141
|
+
commit, the view is tried again, and the file is planned from the disk anew
|
|
142
|
+
(`plugin-file-changed`); nothing reads a plugin file as an edit of a note.
|
|
143
|
+
The view is then `publisher-conflict`, and its next step in `status` and
|
|
144
|
+
`open` names that file as what stopped the publication (changed by another
|
|
145
|
+
program or by the person; one change is enough), rather than asking the
|
|
146
|
+
person to close another publisher; the same holds for a settings file
|
|
147
|
+
(`settings-changed`). The reason is the first unit that stopped the
|
|
148
|
+
publication, so other causes are not ruled out.
|
|
149
|
+
Upgrades replace the plugin files the same way. The plugin's version in
|
|
150
|
+
`manifest.json` changes whenever its code does, and the test suite holds the
|
|
151
|
+
two together: the app keeps running the `main.js` it loaded until it reloads
|
|
152
|
+
the plugin, so one version must name one code.
|
|
153
|
+
|
|
154
|
+
`community-plugins.json` is a list the person owns, with Atelier's entry in it
|
|
155
|
+
only while the person wants the plugin in that vault (next section). A file
|
|
156
|
+
that is not a list is the person's to repair; it is reported as
|
|
157
|
+
`settings-invalid` and left alone. Atelier never removes plugin files from a
|
|
158
|
+
vault.
|
|
159
|
+
|
|
160
|
+
### Turning it off, and on again
|
|
161
|
+
|
|
162
|
+
The person decides whether Atelier's plugin runs in a vault, and Atelier
|
|
163
|
+
follows. The decision is the one Obsidian itself records: the
|
|
164
|
+
`atelier-projection` entry of `community-plugins.json`.
|
|
165
|
+
|
|
166
|
+
- The first publication of a vault offers the entry. Once the app has it, a
|
|
167
|
+
list without it is the person's decision: turning the plugin off in
|
|
168
|
+
Settings, Community plugins, removes the entry, and uninstalling it there
|
|
169
|
+
removes the entry and deletes the plugin's folder. The app has the entry
|
|
170
|
+
when it was in place before an app next opened the vault (published while
|
|
171
|
+
no app held the vault), or once the plugin has run there. An entry
|
|
172
|
+
published while an app holds the vault is only `offered`: that app keeps
|
|
173
|
+
the list it read when it opened the vault and may write it back without the
|
|
174
|
+
entry, which is no decision of the person's, so the next publication offers
|
|
175
|
+
the entry again.
|
|
176
|
+
- From then on the vault's plugin is `off`. Atelier leaves
|
|
177
|
+
`community-plugins.json` exactly as the person left it and never adds the
|
|
178
|
+
entry back. It creates no plugin file that is not there, so a folder the
|
|
179
|
+
person deleted stays deleted, and it keeps the files that are still there
|
|
180
|
+
current, so turning the plugin back on gets the current code. The vault
|
|
181
|
+
works through the command-line path, and `status`, `open` and
|
|
182
|
+
`atelier obsidian plugin show` report the plugin as not present with reason
|
|
183
|
+
`turned-off-in-this-vault` and the way back. They read the vault's list, so
|
|
184
|
+
they say so as soon as the person has turned the plugin off; the decision
|
|
185
|
+
is recorded when the view is next prepared (`plugin show` marks a choice
|
|
186
|
+
not recorded yet as `pending`).
|
|
187
|
+
- There are two ways back. Turning "Atelier" on again in Obsidian's settings
|
|
188
|
+
(possible while its folder is there) lists the entry again; the view's next
|
|
189
|
+
publication records the plugin as on and owns the entry again.
|
|
190
|
+
`atelier obsidian plugin on --scope ID` records a request instead and asks
|
|
191
|
+
the running maintenance service for a tick that names the view: that tick
|
|
192
|
+
prepares and publishes the view again, which adds the entry back and makes
|
|
193
|
+
the plugin's files again (the way back after an uninstall), and the command
|
|
194
|
+
says so when it returns (`takesEffect: "published"`). With
|
|
195
|
+
`--adapter=obsidian-cli`, a service of an earlier release still running
|
|
196
|
+
after an upgrade is replaced by the installed one first, under the consent
|
|
197
|
+
already recorded, as `open` does; without it, such a service is only
|
|
198
|
+
reported (`service-outdated`) with that next step. A service of a later
|
|
199
|
+
release is never replaced; it is reported (`service-other-release`) with
|
|
200
|
+
`open`'s next step for it. Either next step is also the document's
|
|
201
|
+
`next`. With no service
|
|
202
|
+
running, the view's next publication does it: the one the next change at
|
|
203
|
+
its sources causes, or the one when the service next starts.
|
|
204
|
+
Obsidian reads `community-plugins.json` when it opens a vault, so a plugin
|
|
205
|
+
brought back this way runs from the next time the vault is opened; one
|
|
206
|
+
turned on in Obsidian's settings runs at once.
|
|
207
|
+
- The list is read before the view is prepared, and the entry is written only
|
|
208
|
+
over exactly the bytes it was read as. A change the person makes in
|
|
209
|
+
between, turning the plugin off while a publication runs for example, is
|
|
210
|
+
never written over: that settings unit stops (`settings-changed`), the
|
|
211
|
+
publication does not commit, and the view is tried again, when its
|
|
212
|
+
preparation reads the list as the person left it.
|
|
213
|
+
- A list that is missing, or that is not a list, decides nothing. A missing
|
|
214
|
+
list is written again with the entry, unless the vault's plugin is `off`.
|
|
215
|
+
Restricted mode leaves the list alone, and needs nothing from Atelier: no
|
|
216
|
+
community plugin runs.
|
|
217
|
+
|
|
218
|
+
The decision is kept owner-only in the workspace's private state, under
|
|
219
|
+
`state/plugin/choices/<view>.json`, as `requested`, `offered`, `on` or `off`,
|
|
220
|
+
with the reason (`entry-confirmed` or `entry-seen-by-the-app` for `on`,
|
|
221
|
+
`entry-removed-by-person` for `off`), so an entry the app never had is never
|
|
222
|
+
taken for the person's decision. A record that cannot be read counts as
|
|
223
|
+
`off`: nothing is added back on a guess.
|
|
224
|
+
|
|
225
|
+
### The data file and the privacy of the vault
|
|
226
|
+
|
|
227
|
+
`data.json` names the address the service listens on and the key of this one
|
|
228
|
+
vault, its bearer:
|
|
229
|
+
|
|
230
|
+
```json
|
|
231
|
+
{
|
|
232
|
+
"schema": "atelier-obsidian-plugin-data/v1",
|
|
233
|
+
"channel": { "host": "127.0.0.1", "port": 43123 },
|
|
234
|
+
"scopeId": "scope-example",
|
|
235
|
+
"bearer": "<43 characters>"
|
|
236
|
+
}
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
The bearer is 32 random bytes, minted by the service the first time the view
|
|
240
|
+
is prepared and kept owner-only in `state/plugin/<view>.json` under the
|
|
241
|
+
workspace's private state. It is the key of the handshake below and never
|
|
242
|
+
crosses the wire. The data file is written with mode `0600`, and only into a
|
|
243
|
+
vault root that is private to this user (owned by this user, no group or other
|
|
244
|
+
permission bits). Atelier creates the vaults it places under its own data root
|
|
245
|
+
with mode `0700` and makes an existing one private before the first bearer
|
|
246
|
+
goes in. Any other vault root is only checked: when it is not private the data
|
|
247
|
+
file is not published (`vault-not-private`), the rest of the vault is, and the
|
|
248
|
+
plugin says `Atelier: not set up`. A vault path under the data root that is a
|
|
249
|
+
link to a folder somewhere else never receives the data file, private or not
|
|
250
|
+
(`vault-not-private`, reason `vault-root-is-a-link`), and its mode is never
|
|
251
|
+
changed; the view is published into it as ever.
|
|
252
|
+
|
|
253
|
+
Deleting `state/plugin/<view>.json` rotates the bearer. A session made with
|
|
254
|
+
the old one ends at its next request, and a handshake with it is refused; the
|
|
255
|
+
next publication of the view (its next change, or the next start of the
|
|
256
|
+
service) mints a new one and replaces the data file, which the plugin reads
|
|
257
|
+
again without a restart.
|
|
258
|
+
|
|
259
|
+
## The trust prompt
|
|
260
|
+
|
|
261
|
+
A vault that carries community plugins asks, the first time an app profile
|
|
262
|
+
opens it:
|
|
263
|
+
|
|
264
|
+
> Do you trust the author of this vault?
|
|
265
|
+
>
|
|
266
|
+
> You're opening this vault for the first time, and it comes with some plugins.
|
|
267
|
+
|
|
268
|
+
with two buttons, "Browse vault in Restricted Mode" and "Trust author and
|
|
269
|
+
enable plugins". Obsidian keeps the answer for each vault in the app's own
|
|
270
|
+
storage for that app profile (not in the vault), so it is asked once per vault
|
|
271
|
+
and per profile.
|
|
272
|
+
|
|
273
|
+
- Trust: Obsidian enables the plugins the vault lists, Atelier's among them.
|
|
274
|
+
The status bar item appears and the service sees the vault open.
|
|
275
|
+
- Restricted mode: no community plugin runs. Obsidian asks again the next time
|
|
276
|
+
the vault is opened, until restricted mode is switched on for good in
|
|
277
|
+
Settings, Community plugins. Nothing of Atelier depends on the answer:
|
|
278
|
+
maintenance, `open` and `status` work through the command-line path, and
|
|
279
|
+
`status` reports the plugin as not present.
|
|
280
|
+
|
|
281
|
+
Only the person answers this prompt. Atelier does not answer it, write the
|
|
282
|
+
app's storage, or change the app profile. The isolated real-app test answers
|
|
283
|
+
it in a disposable app profile only; see "Evidence".
|
|
284
|
+
|
|
285
|
+
## The channel
|
|
286
|
+
|
|
287
|
+
The service's loopback listener (see [local-services.md](local-services.md))
|
|
288
|
+
answers five plugin commands besides its own four operations. Every one is a
|
|
289
|
+
`POST` of a JSON object of at most 1 KiB with exactly the fields of its
|
|
290
|
+
command, and none carries a credential in a header: the plugin proves it holds
|
|
291
|
+
the vault's key without sending it, and the service proves it first
|
|
292
|
+
(protocol `atelier-obsidian-plugin-channel/v2`).
|
|
293
|
+
|
|
294
|
+
1. The plugin sends a fresh 32-byte nonce, the time it made it, and a hint at
|
|
295
|
+
its key: an HMAC of both under the key. The hint names no vault, and two
|
|
296
|
+
hints of one vault do not look alike. The service computes the hint for
|
|
297
|
+
each vault's key it holds, in constant time for every one of them, and
|
|
298
|
+
answers only for a key it holds, only within thirty seconds of the time the
|
|
299
|
+
challenge names, and each nonce only once: its own nonce, a handshake id,
|
|
300
|
+
and a proof, an HMAC under the key over the view, its own exact address,
|
|
301
|
+
both nonces and the handshake id. At most four handshakes wait for their
|
|
302
|
+
hello per view, so a view's challenges never hold up another view's. A
|
|
303
|
+
challenge recorded by a program that took the port while the service was
|
|
304
|
+
down is therefore worth one answer, and only for thirty seconds, and the
|
|
305
|
+
answer leads nowhere without the key.
|
|
306
|
+
2. The plugin checks that proof, in constant time, against the address in
|
|
307
|
+
its data file. A listener that cannot make it (a program that took the
|
|
308
|
+
port while the service was down, or one relaying a service that listens
|
|
309
|
+
elsewhere) is told nothing more: the plugin shows the service as
|
|
310
|
+
unreachable (`listener-not-proven`) and tries again at its next round.
|
|
311
|
+
3. Only then does the plugin say hello: its own proof under the key, over the
|
|
312
|
+
same values and everything the hello carries (plugin version, app version,
|
|
313
|
+
the id of this launch, and a proof of the vault path under the session key,
|
|
314
|
+
so the path itself is never sent). A handshake is used once, whatever the
|
|
315
|
+
hello proves, and lapses after five seconds.
|
|
316
|
+
4. Both derive a key for this session from the same values. Every later
|
|
317
|
+
command carries the session, a counter that only goes up, and a MAC under
|
|
318
|
+
the session key over the command, the session and the counter; every
|
|
319
|
+
answer the service gives is the exact text of its document with a MAC over
|
|
320
|
+
it, the command and the request's counter. The plugin believes no answer
|
|
321
|
+
that does not verify. Any answer to a command that is not sealed (an
|
|
322
|
+
error, which nobody vouches for, a timeout, or an answer that does not
|
|
323
|
+
verify) ends the session in the plugin, which shakes hands again: at once
|
|
324
|
+
when a renewal is refused as an unknown session or as not authenticated,
|
|
325
|
+
at its next round otherwise. A listener that cannot seal therefore
|
|
326
|
+
receives one command of a session at most, besides its release. A session
|
|
327
|
+
ends when its lease lapses, when it is released, after fifteen minutes (the
|
|
328
|
+
plugin shakes hands again), or at its next request after its vault's key
|
|
329
|
+
was rotated.
|
|
330
|
+
|
|
331
|
+
| Command | Fields | Answer |
|
|
332
|
+
| --- | --- | --- |
|
|
333
|
+
| `POST /plugin/challenge` | `protocol`, `keyHint`, `clientNonce`, `issuedAt` | `protocol`, `handshakeId`, `serverNonce`, `serverProof` |
|
|
334
|
+
| `POST /plugin/hello` | `handshakeId`, `pluginVersion`, `appVersion`, `instanceId`, `vaultProof`, `clientProof` | sealed: a session identity, the lease time and the renewal interval |
|
|
335
|
+
| `POST /plugin/lease` | `sessionId`, `counter`, `mac` | sealed: the renewed lease |
|
|
336
|
+
| `POST /plugin/release` | `sessionId`, `counter`, `mac` | sealed: whether the session was released |
|
|
337
|
+
| `POST /plugin/status` | `sessionId`, `counter`, `mac` | sealed: the view's state, reason, verification, committed and prepared generation, held notes (counted, not named), retained edits, open pending edits, and the service's own state |
|
|
338
|
+
|
|
339
|
+
Refusals, before anything else is looked at: `Host` other than the listener's
|
|
340
|
+
literal loopback authority, a cross-site `Origin` or `Sec-Fetch-Site`, a path
|
|
341
|
+
other than one of the nine exactly (a query included), or another method (the
|
|
342
|
+
listener's rules for every request). Then:
|
|
343
|
+
|
|
344
|
+
| Case | Status | Code |
|
|
345
|
+
| --- | --- | --- |
|
|
346
|
+
| a credential in the `Authorization` header, the service's runtime bearer included | 400 | `plugin-command-takes-no-bearer` |
|
|
347
|
+
| a body over 1 KiB | 413 | `payload-too-large` |
|
|
348
|
+
| a body that is not a JSON object | 400 | `payload-not-json-object` |
|
|
349
|
+
| a field too many or missing, or of the wrong shape (a challenge that names the view, say) | 400 | `request-malformed` |
|
|
350
|
+
| a challenge in another protocol | 409 | `protocol-unsupported` |
|
|
351
|
+
| a challenge whose hint matches no key the service holds | 401 | `plugin-key-unknown` |
|
|
352
|
+
| a challenge whose time is more than thirty seconds from the service's clock | 401 | `challenge-stale` |
|
|
353
|
+
| a challenge whose nonce the service has already answered for that view | 401 | `challenge-replayed` |
|
|
354
|
+
| four handshakes of that view already waiting for their hello | 429 | `too-many-handshakes` |
|
|
355
|
+
| a hello for a handshake that is unknown, used, lapsed, or was made at another address | 401 | `handshake-unknown` |
|
|
356
|
+
| a hello whose proof does not verify under the key | 401 | `plugin-not-authenticated` |
|
|
357
|
+
| a hello that proves a vault path other than this view's | 409 | `wrong-vault` |
|
|
358
|
+
| a ninth live session for one view | 429 | `too-many-sessions` |
|
|
359
|
+
| a command for a session the service does not know, or whose key was rotated | 409 | `session-unknown` |
|
|
360
|
+
| a command whose MAC does not verify | 401 | `request-not-authenticated` |
|
|
361
|
+
| a command with a counter already seen | 401 | `request-replayed` |
|
|
362
|
+
|
|
363
|
+
The service keeps handshakes and sessions in memory. A service that started
|
|
364
|
+
again knows neither; the plugin's next lease is refused with
|
|
365
|
+
`session-unknown`, and it shakes hands again.
|
|
366
|
+
|
|
367
|
+
What the person sees of a refused challenge: a 401 is a key the service does
|
|
368
|
+
not know (`Atelier: not set up`, reason "key not known to the service"; the
|
|
369
|
+
data file is older than a rotated key, say), unless the answer says the
|
|
370
|
+
challenge was `challenge-stale` or `challenge-replayed`. The key is not in
|
|
371
|
+
question then: the plugin shows the service as unreachable with that reason,
|
|
372
|
+
and its next round tries again with a fresh challenge. None of these answers
|
|
373
|
+
is authenticated, so they only ever decide what is shown.
|
|
374
|
+
|
|
375
|
+
## Security argument
|
|
376
|
+
|
|
377
|
+
- Reach. The listener binds a literal loopback address and refuses any `Host`
|
|
378
|
+
but its own authority, so nothing off the machine and no name that resolves
|
|
379
|
+
to loopback reaches it. A web page, including a note rendered in the app,
|
|
380
|
+
sends an `Origin` or `Sec-Fetch-Site` that is refused, and it cannot compute
|
|
381
|
+
a proof. The plugin's own requests come from Node's `http` module and carry
|
|
382
|
+
neither header.
|
|
383
|
+
- Authenticity, both ways. The service answers a challenge only for a key it
|
|
384
|
+
holds and proves it over its own exact address; the plugin proves the same
|
|
385
|
+
key over the same values; every later request and answer is sealed with the
|
|
386
|
+
session's key. Comparisons are constant time. A proof or a MAC cannot stand
|
|
387
|
+
in for another (each has its own label), a challenge is answered once and
|
|
388
|
+
only while fresh, a handshake is used once, and a counter is accepted once,
|
|
389
|
+
so nothing recorded can be replayed for any use.
|
|
390
|
+
- Secrecy. The key lives in two owner-only places, the private state of the
|
|
391
|
+
workspace and the vault's `data.json` (`0600`, in a vault root that is
|
|
392
|
+
`0700`), and never crosses the wire. Another user of the machine cannot read
|
|
393
|
+
it, and a program of any user that takes the service's port while it is
|
|
394
|
+
down learns a nonce, a hint it cannot link to a vault, and of a session
|
|
395
|
+
that ended with the service one sealed renewal or status request at most
|
|
396
|
+
(and its release, when the plugin unloads meanwhile): no key, no view, no
|
|
397
|
+
path, no session and no app version it can use (the squatter tests in
|
|
398
|
+
"Evidence"). Any process that runs as this user can read the data file,
|
|
399
|
+
including other community plugins in the same app: the key separates vaults
|
|
400
|
+
and processes of one person, it does not authenticate a person. That is the
|
|
401
|
+
same boundary the service's runtime bearer has. A vault copied or
|
|
402
|
+
synchronized to another machine carries its key there, where it reaches
|
|
403
|
+
nothing: the service answers on this machine's loopback address only.
|
|
404
|
+
- Blast radius. Whoever holds a vault's key can make that view look open, or
|
|
405
|
+
closed, take its eight sessions for as long as it keeps renewing them, and
|
|
406
|
+
claim an app version for it. A claimed version only admits the command-line
|
|
407
|
+
path for that view: publication still needs the app to answer for the vault
|
|
408
|
+
through its own channel, and the in-app step still refuses on an app that
|
|
409
|
+
lacks the saved-content field it relies on (`unsupported-app`). Nothing a
|
|
410
|
+
key holder sends writes a file, starts a publication or changes an edit, and
|
|
411
|
+
the key reaches nothing of the service itself: not its status, tick or stop.
|
|
412
|
+
- Cost. A request without a key costs the service one look at the bearer
|
|
413
|
+
directory (the bearers are kept in memory and read again only when that
|
|
414
|
+
directory changed) and one HMAC per vault; at most four handshakes wait per
|
|
415
|
+
view, and recorded challenges can keep them filled for at most about
|
|
416
|
+
thirty seconds after a restart, since a challenge is answered only within
|
|
417
|
+
thirty seconds of the time it names.
|
|
418
|
+
- Egress. `plugins/` is in the egress scan, which fails a request whose target
|
|
419
|
+
is not a literal loopback address, and the release audit requires the three
|
|
420
|
+
plugin files in the package and scans them in the tarball.
|
|
421
|
+
|
|
422
|
+
## When the plugin is not there
|
|
423
|
+
|
|
424
|
+
If the plugin is not loaded (the person chose restricted mode or has not
|
|
425
|
+
answered the prompt yet, the app is older than 1.13.7, or the person turned it
|
|
426
|
+
off in the vault), nothing changes: the service coordinates through the
|
|
427
|
+
command-line path exactly as before, `open` asks the command-line tool for the
|
|
428
|
+
app version, and `status` reports the plugin as not present with the reason
|
|
429
|
+
(`no-live-lease`, `turned-off-in-this-vault` or `service-not-running`).
|
|
430
|
+
|
|
431
|
+
## The app version a plugin reports
|
|
432
|
+
|
|
433
|
+
The plugin runs inside the app whose version it reports, so its version is
|
|
434
|
+
the app's. The command-line tool, though, reaches the app a publication
|
|
435
|
+
coordinates with, which may be another app holding the same vault, so its own
|
|
436
|
+
answer decides wherever it gives one:
|
|
437
|
+
|
|
438
|
+
- The service's adapter factory asks the probe as it would without a plugin,
|
|
439
|
+
through the same remembered answer. Where the tool answers with a version,
|
|
440
|
+
that version qualifies or refuses the app, whatever the plugin reports.
|
|
441
|
+
Where it gives none (no vault open yet, as it says while a vault is still
|
|
442
|
+
loading, or no answer in time), the version the plugin reports stands in,
|
|
443
|
+
reason `plugin-reported`, while one launch of the plugin alone holds the
|
|
444
|
+
view. Whether the app is installed where Atelier looks and has its
|
|
445
|
+
command-line tool is the probe's answer either way. The adapter still
|
|
446
|
+
coordinates through the command-line tool, which must answer for exactly
|
|
447
|
+
this vault before anything is published.
|
|
448
|
+
- `open` does the same: the plugin's version stands in only where the tool
|
|
449
|
+
gives none. It asks the service about the plugin at most once a second
|
|
450
|
+
while it waits for the app. A version only the plugin reported is not the
|
|
451
|
+
command line answering, so `open` then asks the command line nothing to
|
|
452
|
+
find the vault: it reads the app's settings file, as with no vault open,
|
|
453
|
+
and opens by path the vault the app lists. Where the settings file does
|
|
454
|
+
not show it (a sandboxed build, say), or where it lists it and, once the
|
|
455
|
+
vault is opened, the command line still does not answer for it while only
|
|
456
|
+
the plugin reports the version, `open` answers
|
|
457
|
+
`app-cli-unavailable` / `vault-open-cli-silent`: the plugin shows the vault
|
|
458
|
+
open, so the command line is what did not answer, and waiting for the app
|
|
459
|
+
would change nothing. After a launch that answer is given at the second
|
|
460
|
+
such round in a row, not the first: one tool call that timed out while the
|
|
461
|
+
app was busy opening its window looks the same once, and `open` then goes
|
|
462
|
+
on waiting for the vault. A wait that ends right after one such round gives
|
|
463
|
+
the same answer.
|
|
464
|
+
- Whether an app runs is the process table's answer, which says no only when
|
|
465
|
+
it finds no Obsidian process at all. A lease outlives its app by up to the
|
|
466
|
+
lease time (six seconds after a crash), and any process that holds the
|
|
467
|
+
vault's key can hold one, so while the process table shows no app, a
|
|
468
|
+
plugin's report counts for nothing, in the adapter factory and in `open`.
|
|
469
|
+
The probe's own answer stands (`app-not-running-version-not-needed`): the
|
|
470
|
+
publication goes to the files, and an adapter qualified on that answer
|
|
471
|
+
never coordinates with an app found running later (`app-version-unchecked`),
|
|
472
|
+
so an app started since, perhaps updated as it restarted, is asked for its
|
|
473
|
+
own version at the next qualification.
|
|
474
|
+
- With two launches holding one view (two app profiles with the same vault
|
|
475
|
+
open, both running the plugin), neither plugin's version decides.
|
|
476
|
+
- An app below the floor refuses as before (`app-version-unsupported`,
|
|
477
|
+
`below-minimum-version`), whoever reported the version.
|
|
478
|
+
|
|
479
|
+
What remains: where the tool gives no version but still coordinates (its
|
|
480
|
+
`version` call timed out, say), and a second app holds the same vault without
|
|
481
|
+
the plugin, the plugin's version may belong to the other app. The in-app step
|
|
482
|
+
still refuses an app without the saved-content field it relies on, for a
|
|
483
|
+
note open in the editor.
|
|
484
|
+
|
|
485
|
+
`atelier obsidian status` and `open` report the plugin for each view:
|
|
486
|
+
`{ present, reason, appVersion, pluginVersion, sessions }`, and a line such as
|
|
487
|
+
`plugin present (Obsidian 1.13.7)`.
|
|
488
|
+
|
|
489
|
+
## Evidence
|
|
490
|
+
|
|
491
|
+
- `test/obsidian-plugin.test.mjs` runs the shipped `main.js` in a stand-in app
|
|
492
|
+
against a real listener on an ephemeral loopback port: parity of the
|
|
493
|
+
plugin's constants and of every proof and MAC with the channel contract (a
|
|
494
|
+
plugin that computes any one of them otherwise gets nowhere); the status
|
|
495
|
+
bar, the status window and the notices through every state; lease renewal,
|
|
496
|
+
release and lapse; a service restart; a republished data file; a rotated
|
|
497
|
+
key; missing or unusable channel data; the app floor; a program squatting
|
|
498
|
+
the service's address, which learns no key, no view and no path and, with
|
|
499
|
+
everything it received replayed against the restarted service, opens no
|
|
500
|
+
session and sets no app version; an answer relayed from a service at
|
|
501
|
+
another address; answers that are not sealed, and a listener that answers
|
|
502
|
+
every command with an error, which gets one renewal of a session and
|
|
503
|
+
nothing more of it; answers that alternate between the two failures, which
|
|
504
|
+
do not make the status bar flip; an unload while a hello is
|
|
505
|
+
under way; the plugin's own timer (a renewal every two seconds, rounds that
|
|
506
|
+
never overlap, nothing after an unload); the refusal table above with
|
|
507
|
+
mutation controls that must fail
|
|
508
|
+
it; bearer minting, rotation and the bearer cache; publication of the
|
|
509
|
+
plugin files, entries and data file, with a person's settings kept, the
|
|
510
|
+
person's choice followed (an entry the app had and the person removed is
|
|
511
|
+
not added back; one published while an app held the vault is only offered
|
|
512
|
+
until the plugin runs there; `plugin on` publishing the view at once while
|
|
513
|
+
the service runs),
|
|
514
|
+
displaced bytes kept in recovery, the privacy rule of the vault root, a
|
|
515
|
+
linked vault root, unreadable files, a drifted plugin file written again
|
|
516
|
+
(at the service's next tick, with nothing changed at the sources, and a
|
|
517
|
+
drift left for the person asked about once),
|
|
518
|
+
and an upgrade; the service publishing the plugin and the plugin it
|
|
519
|
+
published holding the view; qualification from a plugin report, with one
|
|
520
|
+
launch or two; and `status` and `open` reporting it. A spawn guard refuses
|
|
521
|
+
any child of the suite that could reach an Obsidian with the developer's
|
|
522
|
+
own `HOME`.
|
|
523
|
+
- The same file has three real-app tests, skipped unless
|
|
524
|
+
`ATELIER_OBSIDIAN_PLUGIN=1` is set on a desktop host with Obsidian
|
|
525
|
+
installed. Each publishes a synthetic vault through the maintenance service
|
|
526
|
+
and opens it in a disposable Obsidian instance (private `HOME`, private
|
|
527
|
+
profile, mock keychain, a copy of the pinned app archive when
|
|
528
|
+
`ATELIER_OBSIDIAN_ASAR` names one). The first checks that the trust prompt
|
|
529
|
+
is shown, answers it the way a person does, by pressing "Trust author and
|
|
530
|
+
enable plugins" in that window through the command-line tool's `eval`, and
|
|
531
|
+
asserts that the service sees the lease with the app's version, that the
|
|
532
|
+
status bar item says `Atelier: current`, that a publication made while the
|
|
533
|
+
plugin holds the view qualifies the app from the command-line tool's own
|
|
534
|
+
version (reason `meets-minimum-version`), that
|
|
535
|
+
a rotated key reaches the running plugin, and that quitting the app ends the
|
|
536
|
+
presence. The second declines the prompt and publishes through the
|
|
537
|
+
command-line path as before. The third uninstalls the plugin in the app and
|
|
538
|
+
follows it until `atelier obsidian plugin on` brings it back at once (the
|
|
539
|
+
running service publishes the view on a tick that names it), offered while
|
|
540
|
+
that app holds the vault and confirmed when the plugin runs after a
|
|
541
|
+
restart. They never touch another app profile, and they end each disposable
|
|
542
|
+
instance by its profile path.
|
|
543
|
+
|
|
544
|
+
The disposable window takes focus when it opens, so input meant for another
|
|
545
|
+
window can answer its prompt before the test looks. The first test then
|
|
546
|
+
fails and says so; the third, whose subject comes after trust, accepts a
|
|
547
|
+
vault trusted that way. The desktop procedures
|
|
548
|
+
(`scripts/obsidian/desktop-receipts.mjs`) decline the prompt as the second
|
|
549
|
+
test does. When the command-line tool loses the reply to that press, a
|
|
550
|
+
prompt that is gone proves nothing by itself: the app's own restricted mode
|
|
551
|
+
says how it was answered, and a procedure refuses to run with community
|
|
552
|
+
plugins on (`community-plugins-on`), ending the instance it started.
|
|
553
|
+
|
|
554
|
+
Pressing the button in the prompt was chosen over the alternatives because
|
|
555
|
+
it exercises the prompt a person sees and needs no knowledge of the app's
|
|
556
|
+
storage. Writing the profile's local storage would mean editing a LevelDB
|
|
557
|
+
database behind the app's back, and calling `app.plugins.setEnable(true)`
|
|
558
|
+
would skip the prompt altogether.
|
|
559
|
+
|
|
560
|
+
## Phase 2 plan
|
|
561
|
+
|
|
562
|
+
Phase 2 moves the in-app critical section of publication into the plugin, and
|
|
563
|
+
closes gates G20 and G21 of the initiative. Nothing below is implemented.
|
|
564
|
+
|
|
565
|
+
1. Commands. The channel gains the publication protocol's three operations,
|
|
566
|
+
`inspect`, `publish` (`replace` and `remove`) and `collect`, with the
|
|
567
|
+
payloads the command-line path validates today (`validatePayload`), and a
|
|
568
|
+
protocol variant, `obsidian-plugin-critical-section/v1`, recorded in every
|
|
569
|
+
journal it writes. The service stays the only client that starts work; the
|
|
570
|
+
plugin fetches work with a bounded long poll (`POST /plugin/work`) and
|
|
571
|
+
answers with `POST /plugin/result`, both sealed under the session's key
|
|
572
|
+
like every command of phase 1, so the plugin never listens on a port and
|
|
573
|
+
takes work only from a service that proved it holds the vault's key.
|
|
574
|
+
At most one operation per vault is in flight, every operation has an
|
|
575
|
+
identity, a lost answer is recovered with `collect`, and a publish is never
|
|
576
|
+
sent twice.
|
|
577
|
+
2. The critical section. The plugin runs the same synchronous body the
|
|
578
|
+
command-line path sends through `eval` (`criticalSection` in
|
|
579
|
+
`bridge-script.mjs`), shipped in `main.js` rather than sent: refuse on a
|
|
580
|
+
dirty or differing editor in any window, refuse on changed bytes on disk,
|
|
581
|
+
exchange atomically, update open editors in one transaction of minimal
|
|
582
|
+
hunks and record `lastSavedData`, record the outcome, reply at once. The
|
|
583
|
+
late-writer re-check and the manifest commit stay in the publisher.
|
|
584
|
+
3. Path selection. With a live lease from a plugin whose protocol matches, the
|
|
585
|
+
publisher uses the plugin channel; otherwise the command-line path, and
|
|
586
|
+
with no app running, the app-free path, exactly as today. A refusal is
|
|
587
|
+
never relaxed by switching paths.
|
|
588
|
+
4. Live edit preservation. The plugin forwards the vault's `modify` events and
|
|
589
|
+
the editor's dirty state for Atelier's notes to the service as they happen,
|
|
590
|
+
so an edit is preserved in recovery before any maintenance tick looks for
|
|
591
|
+
it. The service still decides what an edit is.
|
|
592
|
+
5. G20. The complete G00 interleaving suite (sixteen interleavings, racing
|
|
593
|
+
cases repeated) and the G04 production-publisher suite run in plugin mode
|
|
594
|
+
on a real isolated app, unchanged, with zero lost bytes. A case that passes
|
|
595
|
+
on the command-line path and fails in plugin mode fails the gate; the suite
|
|
596
|
+
is not narrowed.
|
|
597
|
+
6. G21. Apply and Discard in the status window for held edits, forwarded to
|
|
598
|
+
the service's existing apply operation, which decides; then the procedure
|
|
599
|
+
AP-08: enable, disable, uninstall and an app below the floor on one vault
|
|
600
|
+
with pending edits and recovery bytes, comparing manifests, path
|
|
601
|
+
allocations, pending edits and recovery digests with the command-line path
|
|
602
|
+
after each step.
|
|
603
|
+
|
|
604
|
+
Open questions for phase 2: when, if ever, plugin files are removed from a
|
|
605
|
+
vault; and how an upgraded service answers an older `main.js` that the app
|
|
606
|
+
keeps running until it reloads the plugin (the hello carries the plugin
|
|
607
|
+
version and the protocol for that).
|
|
608
|
+
|
|
609
|
+
### Deviations from the track plan
|
|
610
|
+
|
|
611
|
+
The obsidian-plugin track planned an optional enhanced mode. The owner's
|
|
612
|
+
decision of 2026-09-24 made the plugin the primary in-app half, shipped in
|
|
613
|
+
every managed vault, with the command-line path as the fallback. Phase 1 here
|
|
614
|
+
therefore builds the plugin, its delivery and a presence and status channel
|
|
615
|
+
before the publication commands the plan's first phase describes; the parity
|
|
616
|
+
held today is between the plugin's constants and the channel contract, and the
|
|
617
|
+
refusal parity with the command-line path belongs to phase 2, when the plugin
|
|
618
|
+
carries publication commands. Fixtures are generated in temporary directories
|
|
619
|
+
rather than kept under `fixtures/obsidian/plugin/`.
|