@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
|
@@ -10,16 +10,19 @@ here enables a feature. The publisher under
|
|
|
10
10
|
## Registered shapes
|
|
11
11
|
|
|
12
12
|
Eleven closed v1 schemas, `contracts/atelier-obsidian-<shape>.v1.schema.json`,
|
|
13
|
+
and one v2 schema, `contracts/atelier-obsidian-generation-manifest.v2.schema.json`,
|
|
13
14
|
are registered in `src/contracts/corpus.mjs` with valid and invalid fixtures
|
|
14
|
-
under `fixtures/obsidian/contracts/<shape
|
|
15
|
-
|
|
15
|
+
under `fixtures/obsidian/contracts/<shape>/` (the v2 manifest under
|
|
16
|
+
`generation-manifest-v2/`). `src/projection/obsidian/contracts.mjs` validates
|
|
17
|
+
them and adds the refusals a schema cannot express; a generation manifest is
|
|
18
|
+
validated against the version its `schema` names.
|
|
16
19
|
|
|
17
20
|
| Shape | Portable | Purpose |
|
|
18
21
|
| --- | --- | --- |
|
|
19
22
|
| `corpus-profile` | yes | Workspace and repository identities, enrollment, audience |
|
|
20
23
|
| `scope` | yes | Mode and selector set-AST; an empty selection is valid and empty |
|
|
21
24
|
| `source-snapshot` | yes | Raw byte digests, graph pin, dirty state, single consistent read |
|
|
22
|
-
| `generation-manifest` | yes | Path map, byte regions, link inversion map, completeness |
|
|
25
|
+
| `generation-manifest` | yes | Path map, byte regions, link inversion map, completeness. v1 is vault layout 1; v2 is layout 2 (see "Vault layout") |
|
|
23
26
|
| `publication-journal` | no | Conditional operations, partial transition, restart; protocol ID required |
|
|
24
27
|
| `service-state` | no | Literal loopback, port, runtime ID, PID, executable, consent |
|
|
25
28
|
| `edit-operation` | yes | Object identity, origin generation, base digest, idempotency key |
|
|
@@ -32,6 +35,11 @@ Portable shapes refuse absolute paths. `atelier-project-config.v1` is
|
|
|
32
35
|
unchanged: extension settings are validated by their own schema, and an
|
|
33
36
|
unknown extension key refuses in the adapter, not in the project validator.
|
|
34
37
|
|
|
38
|
+
The `ext-settings` key `ext["mnstry.atelier.personal-workspace"]` is reserved.
|
|
39
|
+
Only a personal-workspace binding sets it, in memory, to name its generation.
|
|
40
|
+
A project whose settings declare it without a binding is refused at every
|
|
41
|
+
build with `personal-binding-lost`.
|
|
42
|
+
|
|
35
43
|
## Selection
|
|
36
44
|
|
|
37
45
|
`selectScope({ canonicalSnapshot, profile, selector, expansion })` is the only
|
|
@@ -44,17 +52,65 @@ proceeds in canonical-identity order and reports truncation. Relation types are
|
|
|
44
52
|
`contradicts`, `belongs_to` and the derived `links_to`. Literal oracles and
|
|
45
53
|
refusals live in `fixtures/obsidian/contracts/oracles/scope-cases.json`.
|
|
46
54
|
|
|
55
|
+
### Which notes are eligible
|
|
56
|
+
|
|
57
|
+
The eligibility a view is built with is decided by the machine settings of
|
|
58
|
+
the workspace (`eligibilityFor` in `src/runtime/obsidian/pipeline.mjs`), and
|
|
59
|
+
the engine, source apply, the proposal adapter and the selection operation
|
|
60
|
+
all ask it, so they agree on what a view holds. By default
|
|
61
|
+
(`DEFAULT_ELIGIBILITY`, revision
|
|
62
|
+
`classified-documents/v2+assets-embedded-by-eligible-documents/v1`) a node is
|
|
63
|
+
eligible only when the census classified it. When the audience decision is
|
|
64
|
+
`only-you` with `unclassified: shown`, and only then, an unclassified note is
|
|
65
|
+
eligible too, provided its bytes pass the byte lens the emitter reads notes
|
|
66
|
+
with, and it has no front matter or front matter whose every top-level line
|
|
67
|
+
starts at column 0 with a plain `key:` (an ASCII letter or `_`, then ASCII
|
|
68
|
+
letters, digits, `_` and `-`), none of them `kg`, with indented lines only as
|
|
69
|
+
the values of such keys, and in which the graph's own reading finds no `kg`
|
|
70
|
+
key either. A quoted key, a flow or JSON mapping, a complex key, a top-level
|
|
71
|
+
list, an indented root mapping or front matter the graph could not read keeps
|
|
72
|
+
it withheld, since it may carry a `kg.audience` such as `sensitive`
|
|
73
|
+
(`onlyYouEligibility`, revision
|
|
74
|
+
`classified-documents/v2+unclassified-notes-read-as-notes-for-only-you/v4+assets-embedded-by-eligible-documents/v1`).
|
|
75
|
+
An unclassified note carries the `private` audience, which "only you" admits.
|
|
76
|
+
An admitted note is an ordinary member of the view for the redaction guard:
|
|
77
|
+
its identity may appear in generated bytes of that view, and stays forbidden
|
|
78
|
+
in every view it is not part of. Assets follow the documents that embed them.
|
|
79
|
+
|
|
47
80
|
### What redaction covers
|
|
48
81
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
82
|
+
The guarantee: Atelier never generates a reference to a note outside the
|
|
83
|
+
view. No link, embed or relation row the emitter writes targets one, no
|
|
84
|
+
identity block names one, and no folder name or collision qualifier is
|
|
85
|
+
derived from one; manifest entries and link inversions carry nothing about
|
|
86
|
+
one either.
|
|
87
|
+
|
|
88
|
+
Text that comes from an author is carried as authored. Emission is
|
|
89
|
+
byte-faithful: a visible author's bytes are emitted unchanged, so a visible
|
|
90
|
+
author's own references to withheld or out-of-selection documents (canonical
|
|
91
|
+
identities in front matter, repository-relative paths, link text) appear in
|
|
92
|
+
the vault exactly as written. Generated regions repeat author text as well:
|
|
93
|
+
the titles of in-view notes in relation rows, and a wrapped file's title,
|
|
94
|
+
summary and tags in its note. That text is carried as its authors wrote it
|
|
95
|
+
too, escaped for Markdown or, in file names and link aliases, with the
|
|
96
|
+
characters those cannot hold turned into spaces. A scoped vault is not a
|
|
97
|
+
confidentiality boundary against what visible authors wrote (owner decision,
|
|
98
|
+
2026-09-22). A redaction boundary over authored bytes cannot coexist with
|
|
99
|
+
byte-faithful emission; it would be a separate mode with its own evidence.
|
|
100
|
+
The deny-list below is defence in depth over the author text that generated
|
|
101
|
+
regions repeat, not the guarantee, and it follows the audience: a note outside
|
|
102
|
+
the selection that the view's audience may see is not withheld from the
|
|
103
|
+
vault's readers, so text naming it is reported, not refused (see "The
|
|
104
|
+
redaction guard").
|
|
105
|
+
|
|
106
|
+
A layout 2 vault shows, in its folder names, the repository identity and the
|
|
107
|
+
source directory chain of the notes in the view, and every note names its
|
|
108
|
+
repository and source path in its `atelier-repo` and `atelier-source`
|
|
109
|
+
properties; layout 1 showed neither. A folder exists only because a note of
|
|
110
|
+
the view, a file the view copies for an embed, or a file held for an open edit
|
|
111
|
+
is in it, though it may hold only some of that source directory's files. This
|
|
112
|
+
surface is acceptable for every vault, scoped ones included (owner decision,
|
|
113
|
+
2026-09-24).
|
|
58
114
|
|
|
59
115
|
Each note may carry the generated line `Relationships leading outside this
|
|
60
116
|
view: N`. N counts that note's relationships to notes that are visible but not
|
|
@@ -62,15 +118,416 @@ selected, so it reveals that count, and two views of one workspace can be
|
|
|
62
118
|
compared. It never counts a withheld node: edges to a withheld endpoint are
|
|
63
119
|
dropped before the count is taken.
|
|
64
120
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
121
|
+
A collision qualifier is the qualified note's own source file stem, or a
|
|
122
|
+
short id of its own identity (see "Collisions"); a qualified folder carries a
|
|
123
|
+
short id of its own source folder or repository. A qualifier appears only
|
|
124
|
+
because something this vault shows took the name first: another note of the
|
|
125
|
+
view, an asset the view copies, or a file held for an open edit, which stays
|
|
126
|
+
in the vault while its edit is open, also after its note has left the view.
|
|
127
|
+
The one exception is fixed and names no note: a repository whose folder would
|
|
128
|
+
be `notes` or `attachments` spelled another way (see "Folders"). It stays while the
|
|
129
|
+
qualified note is in the view, also after the other has left, because a path
|
|
130
|
+
never moves while its note is in the view. A note of another view, a withheld
|
|
131
|
+
note, or one that was never in this view never causes a qualifier by itself:
|
|
132
|
+
each view allocates among its own notes and the files its vault holds.
|
|
133
|
+
|
|
134
|
+
### The redaction guard
|
|
135
|
+
|
|
136
|
+
Before anything is returned, `prepareView` checks the whole result, every
|
|
137
|
+
note included, whether it was emitted on this call or reused from the
|
|
138
|
+
preparation cache. Any failure refuses the view with `redaction-failure`,
|
|
139
|
+
whose detail names the rule (`allow-list`, `identity-block` or `deny-list`)
|
|
140
|
+
and, where there is one, the note or file of this view it concerns: a path
|
|
141
|
+
allocated to this view (`notePath` or `filePath`), never the value that was
|
|
142
|
+
found and never a path in question. A deny-list refusal names the note whose
|
|
143
|
+
generated region holds the text, which for an incoming relation row is the
|
|
144
|
+
note the row is in, not the note whose title it repeats. An allow-list
|
|
145
|
+
refusal names the note by its allocated path, or the rule alone when the path
|
|
146
|
+
in question is the only one at hand (an attachment that is not the view's, a
|
|
147
|
+
manifest entry of no note of the view). The engine records the same in the
|
|
148
|
+
view's freshness entry, and `atelier obsidian status` shows it. Three rules,
|
|
149
|
+
in the vault layout the view is prepared in:
|
|
150
|
+
|
|
151
|
+
1. Allow-list. Every path the emitter wrote must be a path allocated to this
|
|
152
|
+
view: every note and attachment path of the manifest, every rewritten link
|
|
153
|
+
or embed target (the bytes an inversion emitted must be such a path, as it
|
|
154
|
+
is or percent-encoded the way the emitter encodes it; the words an
|
|
155
|
+
author's wikilink keeps after `|` are the author's), every attachment an
|
|
156
|
+
embed names, and every `[[…]]` or `![[…]]` target in a generated region,
|
|
157
|
+
without its `#` fragment or `|` alias (relation rows, a wrapper's link and
|
|
158
|
+
embed of its file). The identity block of each note must name exactly that
|
|
159
|
+
note's own repository, identity and source path, and nothing else may
|
|
160
|
+
appear in it.
|
|
161
|
+
2. Deny-list, defence in depth, following the audience. The free text of the
|
|
162
|
+
generated regions (the titles in relation rows, a wrapper's title, summary
|
|
163
|
+
and tags), the link targets excluded, is read as a reader sees it, with the
|
|
164
|
+
backslashes the emitter adds to escape Markdown removed, and compared in
|
|
165
|
+
NFC, against the identifiers of every census node and embedded asset
|
|
166
|
+
outside this view:
|
|
167
|
+
- Of a node or asset the view's audience may not see (withheld), an
|
|
168
|
+
unambiguous identifier refuses the view: an identity qualified by any
|
|
169
|
+
repository of the census (`<repository>:…`, whichever repository holds
|
|
170
|
+
the note), a repository-qualified source path (`<repository>/<path>`), a
|
|
171
|
+
repository-relative path that holds a folder (a `/`), or a vault path
|
|
172
|
+
recorded for it (see below). An identity that is a bare
|
|
173
|
+
word, and a file name at a repository's root (`README.md`, `index.md`),
|
|
174
|
+
are ambiguous and only reported: in-view text names such words and files
|
|
175
|
+
all the time.
|
|
176
|
+
- Of a node or asset the audience may see but this view does not hold (not
|
|
177
|
+
selected, or an asset the view does not copy), every identifier, a vault
|
|
178
|
+
path of it in another view included, is only reported: every view of a
|
|
179
|
+
workspace has one audience today, so such a note is not withheld from
|
|
180
|
+
anyone who reads this vault (decided 2026-09-24 under the owner decision
|
|
181
|
+
of 2026-09-22). So is a vault path of a note no longer in the census.
|
|
182
|
+
The rule follows the audience: a view with an audience of its own would
|
|
183
|
+
refuse for whatever that audience may not see, with no change to this
|
|
184
|
+
rule.
|
|
185
|
+
- A value that also identifies a note of this view names nothing outside it
|
|
186
|
+
and is neither.
|
|
187
|
+
|
|
188
|
+
What is reported is recorded as a diagnostic (see "Notes that were laid out
|
|
189
|
+
anyway"). A match is a whole token when no letter or digit is beside it,
|
|
190
|
+
and no one of `. _ : / -` joined to a letter or digit (a full stop that
|
|
191
|
+
ends a sentence is beside a whole token; `x.north-desk:a` is one longer
|
|
192
|
+
token); it is compared exactly, case included. The identity block, the one
|
|
193
|
+
place a note names its own repository-relative path, is held to rule 1.
|
|
194
|
+
3. Coverage. Rules 1 and 2 run over every note of the view on every
|
|
195
|
+
preparation; a reused note is judged exactly as an emitted one.
|
|
196
|
+
|
|
197
|
+
What an author wrote inside the authored body or front matter of an in-view
|
|
198
|
+
note (a withheld node's canonical identity in a relation list, a link to its
|
|
199
|
+
path) is authored bytes: it is emitted as written and never triggers the
|
|
200
|
+
guard. When the same words reach generated prose unchanged, as an incoming
|
|
201
|
+
relation row repeats an in-view title or a wrapper repeats its summary, rule 2
|
|
202
|
+
refuses the view. Where they reach generated text only in a form made safe for
|
|
203
|
+
a name, the outgoing relation row's alias and every file name, `:` and `/`
|
|
204
|
+
have become spaces and runs of whitespace one space; that form is the
|
|
205
|
+
author's text carried as authored, and rule 2 does not match it: there is no
|
|
206
|
+
fuzzy matching (the residual of the 2026-09-22 decision above).
|
|
207
|
+
|
|
208
|
+
A refusing deny-list is itself a signal. Someone who can edit the text of an
|
|
209
|
+
in-view note and watch the view go stale learns that the words they wrote name
|
|
210
|
+
something the audience may not see; the refusal never says which note or
|
|
211
|
+
value. That existence oracle is open only to a person who can already edit
|
|
212
|
+
in-view text, and is accepted with the rule.
|
|
213
|
+
|
|
214
|
+
The vault paths the deny-list knows come from two places only: the sections of
|
|
215
|
+
the persistent registry, which hold each view's last allocation, this view's
|
|
216
|
+
included, and this view's prior generation. So after the registry is lost, a
|
|
217
|
+
vault path another view holds is not denied until that view is prepared again
|
|
218
|
+
and writes its section; within one tick, what a view denies of another view
|
|
219
|
+
depends on the order the views are prepared in; and a view still held in
|
|
220
|
+
layout 1 records its layout 2 allocation in its section, not the layout 1
|
|
221
|
+
paths its vault holds, which other views therefore do not deny. The
|
|
222
|
+
identities and source paths of the same notes are always denied.
|
|
223
|
+
|
|
224
|
+
Identities are what the graph records. A sidecar's identity is checked for its
|
|
225
|
+
form, but a Markdown note's `kg.id` may be any non-empty string, a bare word
|
|
226
|
+
included; Atelier's own documents use repository-qualified identities
|
|
227
|
+
(`<repository>:<name>`). An identity qualified by any repository of the census
|
|
228
|
+
is unambiguous, whichever repository holds its note. A bare word could be any
|
|
229
|
+
word of a title, and a root file name such as `README.md` any repository's, so
|
|
230
|
+
both are reported, never refused. The matcher is one Aho–Corasick automaton
|
|
231
|
+
over every value, with the token boundary tested at each hit: building it
|
|
232
|
+
takes time and memory linear in the total length of the values, on every
|
|
233
|
+
preparation (some tenths of a second and about 100 MB at 120,000 values), and
|
|
234
|
+
it then reads a text once, however many values there are.
|
|
235
|
+
|
|
236
|
+
In layout 1 the guard additionally refuses any `--<hex>` identity suffix of a
|
|
237
|
+
census identity outside the view, anywhere in generated text or emitted paths,
|
|
238
|
+
as the earlier release did. `test/obsidian-materialization.test.mjs` carries a
|
|
239
|
+
mutation control per rule: with that rule removed, a view that breaks it is
|
|
240
|
+
accepted.
|
|
241
|
+
|
|
242
|
+
## Vault layout
|
|
243
|
+
|
|
244
|
+
A prepared view lays its files out in layout 2: the file name is the title a
|
|
245
|
+
person reads, the folders are the repository's own folders, and the identity a
|
|
246
|
+
machine needs is written into the note, not into its name. Obsidian shows a
|
|
247
|
+
file name as the note's title in the file explorer, tabs, the graph, search and
|
|
248
|
+
link completion, so the file name is the human title. The generation manifest
|
|
249
|
+
records the layout (`layoutVersion: 2`, in
|
|
250
|
+
`atelier-obsidian-generation-manifest/v2`) and keeps the path of every note and
|
|
251
|
+
attachment beside its identity, as before. Layout 1
|
|
252
|
+
(`notes/<readable title>--<identity suffix>.md`, flat, with attachments under
|
|
253
|
+
`attachments/`) is what releases up to 0.2.0-alpha.11 wrote; see "Upgrade from
|
|
254
|
+
layout 1".
|
|
255
|
+
|
|
256
|
+
### Folders
|
|
257
|
+
|
|
258
|
+
A note lives at `<repository folder>/<source directory>/<file name>`.
|
|
259
|
+
`<source directory>` is the directory of its source relative to its repository
|
|
260
|
+
root, segment by segment, and `<repository folder>` is the repository's
|
|
261
|
+
identity (`repoId`); each segment is made safe as a name (below). Nothing is
|
|
262
|
+
stripped or shared across repositories: a vault of several repositories has
|
|
263
|
+
one top-level folder per repository. No part of a path depends on a property
|
|
264
|
+
of the whole census, such as a prefix every path shares, so adding or removing
|
|
265
|
+
a file never moves another.
|
|
266
|
+
|
|
267
|
+
Two folders whose names differ only in case or Unicode normalization are one
|
|
268
|
+
folder on a case-insensitive file system (APFS, NTFS), with one spelling. The
|
|
269
|
+
first allocation of a folder in a view fixes its spelling, and a later note
|
|
270
|
+
whose folder differs from it only that way takes the existing spelling: every
|
|
271
|
+
path of a vault spells each folder one way, so the app reports each note under
|
|
272
|
+
exactly the path the manifest records, which is how the publisher finds the
|
|
273
|
+
note's open editors. Two repositories of a view whose folder names would
|
|
274
|
+
collide that way are told apart: the one allocated later gets ` (<id>)`, the
|
|
275
|
+
first 6 hexadecimal characters of the SHA-256 of its identity. A source folder
|
|
276
|
+
whose mirrored folder would take the name of a file already allocated (a
|
|
277
|
+
source directory `docs/Guide.md/` after a note `docs/Guide.md`, or `a/Data/`
|
|
278
|
+
after a wrapped file `a/data`) is qualified the same way, with the id of the
|
|
279
|
+
source folder: `docs/Guide.md (1a2b3c)/`. So is a repository folder whose name
|
|
280
|
+
a file held for an edit already spells another way in its folder (a layout 1
|
|
281
|
+
file under `notes/` and a repository named `Notes`).
|
|
282
|
+
|
|
283
|
+
The folders `notes` and `attachments`, where layout 1 kept every note and file,
|
|
284
|
+
count as taken in every vault: an upgrade retires their files but leaves the
|
|
285
|
+
folders, and on a case-insensitive file system a repository folder named
|
|
286
|
+
`Notes` would otherwise be merged into `notes`, spelled as the disk has it and
|
|
287
|
+
not as the manifest does. A repository whose folder differs from either only
|
|
288
|
+
in case or normalization gets ` (<id>)`; a repository named exactly `notes` or
|
|
289
|
+
`attachments` uses the folder as it is.
|
|
290
|
+
|
|
291
|
+
### File names
|
|
292
|
+
|
|
293
|
+
The name of a note is:
|
|
294
|
+
|
|
295
|
+
- for a Markdown source, its canonical title, the one the graph read: the
|
|
296
|
+
front-matter `title`, else the first H1, with the author's casing and
|
|
297
|
+
spacing. A title that ends in the source's own extension
|
|
298
|
+
(`07-tide-survey-brief.md`) loses that extension. When the graph's title
|
|
299
|
+
is exactly the title-cased file name, the graph's fallback, the title came
|
|
300
|
+
from the file name or equals it, and the file stem is used as written
|
|
301
|
+
(`getting-started`, not `Getting Started`). The rule reads the node record
|
|
302
|
+
only, so every allocation, and a selection resolved without the sources,
|
|
303
|
+
gives the same name. The record does not say where its title came from, so
|
|
304
|
+
an author heading that is exactly the title-cased file name (`# Getting
|
|
305
|
+
Started` in `getting-started.md`) names the note by its stem as well.
|
|
306
|
+
- for any other source, a wrapped file, the file name itself: the note is
|
|
307
|
+
`<file name>.md` (`depth-chart.pdf.md`) and the file keeps its own name
|
|
308
|
+
(`depth-chart.pdf`) in the same folder. The pairing is visible, a search
|
|
309
|
+
for the file finds both, and the file's own title (from its sidecar or
|
|
310
|
+
`<title>`) is the first heading of the note.
|
|
311
|
+
|
|
312
|
+
Every name and folder segment is made safe for macOS, Linux, Windows and
|
|
313
|
+
Obsidian links:
|
|
314
|
+
|
|
315
|
+
1. It is NFC-normalized.
|
|
316
|
+
2. Control characters (C0, C1, U+2028 and U+2029) and
|
|
317
|
+
`/ \ : * ? " < > | # ^ [ ]` become a space; bidirectional embedding,
|
|
318
|
+
override and isolate controls and U+FEFF are removed.
|
|
319
|
+
3. Runs of spaces become one space, and leading and trailing spaces and dots
|
|
320
|
+
are removed: a leading dot hides a file from the app, and Windows allows no
|
|
321
|
+
trailing dot or space.
|
|
322
|
+
4. It is cut to 150 bytes of UTF-8 (a folder segment to 120, a wrapped file's
|
|
323
|
+
stem so that its extension still fits), on a character boundary; a
|
|
324
|
+
combining character, zero-width joiner or variation selector left at the end by
|
|
325
|
+
the cut is removed.
|
|
326
|
+
5. A Windows device name (`CON`, `PRN`, `AUX`, `NUL`, `COM0` to `COM9`,
|
|
327
|
+
`LPT0` to `LPT9`, `COM¹` to `COM³`, `LPT¹` to `LPT³`, `CONIN$`, `CONOUT$`),
|
|
328
|
+
compared case-insensitively on the part before the first dot, gets a `_`
|
|
329
|
+
in front.
|
|
330
|
+
6. A name left empty is the file stem, made safe the same way, else
|
|
331
|
+
`Untitled`; a folder segment left empty is `_`.
|
|
332
|
+
|
|
333
|
+
A path longer than the file system allows (255 bytes a name, and
|
|
334
|
+
`maxFullPathBytes`, 1024 by default, with the view's own vault root) is
|
|
335
|
+
shortened by cutting the title part further, never below 16 bytes. A folder is
|
|
336
|
+
kept only where a name of 16 bytes and its extensions still fits in it,
|
|
337
|
+
whatever the title of the note at hand, so a folder's spelling depends on its
|
|
338
|
+
source directory alone. A folder chain that leaves less room than that keeps
|
|
339
|
+
as many leading folders as fit, and the last folder kept, cut, carries
|
|
340
|
+
` (<id>)`, the first 6 hexadecimal characters of the SHA-256 of the
|
|
341
|
+
repository and the whole source directory: two deep directories under one
|
|
342
|
+
readable prefix stay two folders. When not even `<repository folder>/(<id>)`
|
|
343
|
+
leaves that room, the note sits directly in its repository's folder. Only a
|
|
344
|
+
note whose name does not fit even there, qualified if it collides, refuses
|
|
345
|
+
the view, with `path-too-long`: its repository's folder under the view's vault
|
|
346
|
+
root leaves no room for it.
|
|
347
|
+
|
|
348
|
+
### Collisions
|
|
349
|
+
|
|
350
|
+
Names are compared the way a case-insensitive, normalization-insensitive file
|
|
351
|
+
system compares them (`collisionKey`: NFKC, case-folded), across every file of
|
|
352
|
+
the vault: notes, wrapped files and embedded assets. When two would share a
|
|
353
|
+
name in one folder, the one allocated later is qualified:
|
|
354
|
+
|
|
355
|
+
1. `<name> (<source file stem>).md`, the stem made safe and cut to 60 bytes;
|
|
356
|
+
2. when that is taken too, `<name> (<stem>) (<id>).md`, where `<id>` is the
|
|
357
|
+
first 6 hexadecimal characters of the SHA-256 of the repository identity
|
|
358
|
+
and the node identity, lengthened two at a time, up to 16, until it is
|
|
359
|
+
free.
|
|
360
|
+
|
|
361
|
+
A wrapped file's name is already its file name, so it is qualified by the
|
|
362
|
+
identity alone, before its extension: `depth-chart (1a2b3c).pdf` and
|
|
363
|
+
`depth-chart (1a2b3c).pdf.md`. An embedded asset is qualified the same way.
|
|
364
|
+
A name that collides with nothing carries no qualifier and no hash.
|
|
365
|
+
|
|
366
|
+
Each view allocates among its own notes only, in canonical identity order
|
|
367
|
+
(repository, then node), then its embedded assets, and an allocation that its
|
|
368
|
+
prior generation published is never changed while the note is in the view: a
|
|
369
|
+
newcomer that collides takes the qualifier. The same notes and prior
|
|
370
|
+
generation give the same paths in whatever order the census lists them.
|
|
371
|
+
|
|
372
|
+
### Stability
|
|
373
|
+
|
|
374
|
+
A view's paths are seeded from what its prior generation published: a path,
|
|
375
|
+
once published, stays while its note is in the view. A retitled source keeps
|
|
376
|
+
its path: the note shows the new title in its heading and every relation row
|
|
377
|
+
that names it shows it too. Renaming on retitle arrives with the in-vault
|
|
378
|
+
plugin, which can use Obsidian's own rename so that the app's bookmarks, open
|
|
379
|
+
tabs, graph layout and links follow; a rename made by a program outside the
|
|
380
|
+
app would break all of them.
|
|
381
|
+
|
|
382
|
+
A note that leaves the view (deleted, withheld, no longer selected, or renamed
|
|
383
|
+
into a new identity, since a source without an identity of its own is
|
|
384
|
+
identified by its path) releases its path there, unless its file is held for
|
|
385
|
+
an edit: then the file stays in the vault and its name stays taken. A renamed
|
|
386
|
+
source therefore takes its name back instead of keeping a qualifier its old
|
|
387
|
+
name forced. A note that comes back is allocated again like a newcomer: it
|
|
388
|
+
gets its earlier path only when its title is unchanged and the names around it
|
|
389
|
+
are as they were, nothing having taken its name meanwhile and no note it
|
|
390
|
+
collided with having left. A published path that no longer fits the view's
|
|
391
|
+
vault root (a root that grew) is allocated again as well. One identity may
|
|
392
|
+
have different paths in different views: each view allocates among its own
|
|
393
|
+
notes, with its own vault root.
|
|
394
|
+
|
|
395
|
+
The persistent path registry keeps each view's last allocation in a section of
|
|
396
|
+
its own (`views.<scopeId>`); a selection resolved without the view's
|
|
397
|
+
generation reads it. The engine passes the views it maintains
|
|
398
|
+
(`viewScopeIds`), and the registry drops the section of any other view.
|
|
399
|
+
Preparation seeds from the prior generation itself, so a lost registry costs
|
|
400
|
+
no view its paths and is written again on the next preparation.
|
|
401
|
+
|
|
402
|
+
### Notes that were laid out anyway
|
|
403
|
+
|
|
404
|
+
What is worth a person's look, but no reason to refuse a view, is recorded as a
|
|
405
|
+
diagnostic that names the note and never a value: in the generation manifest
|
|
406
|
+
(`ext["mnstry.atelier.obsidian"].diagnostics`, layout 2), in the view's
|
|
407
|
+
freshness entry (the first 100), and in `atelier obsidian status`. The manifest
|
|
408
|
+
holds those of the preparation that first committed its generation: a
|
|
409
|
+
preparation that yields the same generation (the same files) does not commit
|
|
410
|
+
it again. The freshness entry, and so `status`, holds those of the latest
|
|
411
|
+
preparation. Each is
|
|
412
|
+
`{ code, rule?, repoId, nodeId, notePath }` (an embedded asset has `assetPath`
|
|
413
|
+
and `filePath` instead of `nodeId` and `notePath`):
|
|
414
|
+
|
|
415
|
+
- `folder-shortened`: the folder chain was cut to fit (see "File names");
|
|
416
|
+
- `folder-qualified`: a folder met a file of the same name (see "Folders");
|
|
417
|
+
- `author-identity-properties`: the author's own front matter holds one of the
|
|
418
|
+
identity keys, which Properties shows instead of the generated block (see
|
|
419
|
+
"Identity in the note");
|
|
420
|
+
- `bare-identity-in-generated-text` (rule `deny-list`): generated text of the
|
|
421
|
+
note holds, as a whole token, the bare-word identity or the root file name
|
|
422
|
+
(`README.md`) of a note the audience may not see (see "The redaction
|
|
423
|
+
guard");
|
|
424
|
+
- `unselected-identity-in-generated-text` (rule `deny-list`): generated text of
|
|
425
|
+
the note holds an identifier of a note or asset the audience may see but the
|
|
426
|
+
view does not hold.
|
|
427
|
+
|
|
428
|
+
A view refused by the redaction guard records `{ code: "redaction-failure",
|
|
429
|
+
rule, notePath | filePath }` in its freshness entry the same way.
|
|
430
|
+
|
|
431
|
+
### Identity in the note
|
|
432
|
+
|
|
433
|
+
Every note names its identity in three generated properties that Obsidian
|
|
434
|
+
shows (Properties, Bases, search) and any YAML reader or agent can read
|
|
435
|
+
without the manifest:
|
|
436
|
+
|
|
437
|
+
```yaml
|
|
438
|
+
atelier-id: "north-desk:harbor-plan"
|
|
439
|
+
atelier-repo: "north-desk"
|
|
440
|
+
atelier-source: "plans/harbor-plan.md"
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
`atelier-id` is the canonical node identity, `atelier-repo` the repository
|
|
444
|
+
identity and `atelier-source` the repository-relative source path, each a
|
|
445
|
+
JSON string. They are the last lines of the note's front matter, directly
|
|
446
|
+
before its closing delimiter, when the source's front matter is a plain block
|
|
447
|
+
mapping at column 0 that does not already use one of the three keys. A source
|
|
448
|
+
without front matter gets a generated front matter holding only them (after a
|
|
449
|
+
byte order prefix, which the app does not read). Any other front matter (a flow
|
|
450
|
+
mapping, an indented or sequence root, a document marker, a line that is not a
|
|
451
|
+
key) could change meaning with a line added, and one that already uses one of
|
|
452
|
+
the three keys would hold it twice, so for those sources the lines go into a
|
|
453
|
+
generated `%%` comment block at the very end of the note instead, the last
|
|
454
|
+
generated region. Keys are not otherwise compared: a front matter that repeats
|
|
455
|
+
a key of its own still takes the lines. The line ending is the source's. When
|
|
456
|
+
the author's own front matter holds one of the three keys, it is what
|
|
457
|
+
Properties shows, not the generated block; the note is reported as
|
|
458
|
+
`author-identity-properties` (see "Notes that were laid out anyway").
|
|
459
|
+
|
|
460
|
+
The source digest is not written into the note. Once a person's edit is
|
|
461
|
+
applied, the source has a new digest, and the hold on the edited note lifts
|
|
462
|
+
only when the note prepared from the new source is byte for byte the note the
|
|
463
|
+
person edited; a digest in the note would keep it held forever. The digest of
|
|
464
|
+
every generation is in the manifest
|
|
465
|
+
(`notes[].ext["mnstry.atelier.obsidian"].source.rawDigest`).
|
|
466
|
+
|
|
467
|
+
The block is generated. The manifest records its bytes as `regions.identity`,
|
|
468
|
+
inverting a note removes them, and the edit lens never attributes them to the
|
|
469
|
+
author. An edit to them is a front-matter edit, which is never applied to a
|
|
470
|
+
source: it becomes a semantic proposal (see "Proposal adapter for structural
|
|
471
|
+
edits"). In the end-of-note placement it is a generated-region edit and
|
|
472
|
+
refuses.
|
|
473
|
+
|
|
474
|
+
### Links
|
|
475
|
+
|
|
476
|
+
A rewritten link or embed, a relation row and a wrapper's link to its file
|
|
477
|
+
name the target by its full vault path:
|
|
478
|
+
`[[north-desk/plans/Harbor plan.md|the plan]]`,
|
|
479
|
+
`[tide table](south-desk/tables/Tide%20table.md#spring)`,
|
|
480
|
+
`![[east-desk/pages/img/gauge.png|200]]`. A wikilink carries `.md`, so a note
|
|
481
|
+
named after a file never resolves to the file. Obsidian's resolver
|
|
482
|
+
(`MetadataCache.getLinkpathDest`, read in the 1.13.7 application bundle) looks
|
|
483
|
+
a path that contains a folder up as an exact vault path before it tries any
|
|
484
|
+
suffix or file-name match, so the target is exactly the allocated file,
|
|
485
|
+
whatever folder the linking note is in. A wikilink rewrite keeps the words the
|
|
486
|
+
author chose by appending `|<what the author wrote>`, as before.
|
|
487
|
+
|
|
488
|
+
### Upgrade from layout 1
|
|
489
|
+
|
|
490
|
+
A view whose prior generation is in layout 1 is laid out again once, in one
|
|
491
|
+
generation:
|
|
492
|
+
|
|
493
|
+
1. `prepareView` allocates layout 2 paths for every note and asset. The
|
|
494
|
+
layout 1 entries of the persistent registry are dropped, and the registry
|
|
495
|
+
records `layout: 2`. The generation is an
|
|
496
|
+
`atelier-obsidian-generation-manifest/v2`, and `changes.removed` names
|
|
497
|
+
every layout 1 path.
|
|
498
|
+
2. The publisher retires each layout 1 path with its ordinary remove unit. A
|
|
499
|
+
note or file that still holds its published bytes is moved to the recovery
|
|
500
|
+
area with a receipt; a note somebody edited is never removed: it stays
|
|
501
|
+
where it is and is surfaced as a retained edit. Nothing is deleted.
|
|
502
|
+
3. While a note of the view has an open edit, the view is not laid out again.
|
|
503
|
+
The engine passes as `heldNotePaths` the files of open edits, which stay in
|
|
504
|
+
the vault and keep their names taken in layout 2, and as
|
|
505
|
+
`layoutHeldNotePaths` those and the files whose edit closed on this tick.
|
|
506
|
+
`prepareView` prepares a view whose prior generation is in layout 1 and
|
|
507
|
+
holds one of the latter in layout 1, exactly as the earlier release did,
|
|
508
|
+
a new note included
|
|
509
|
+
(`notes/<title>--<suffix>.md`). A held note therefore stays under its
|
|
510
|
+
layout 1 path and is held, applied and withdrawn by the rules it was held
|
|
511
|
+
under. The engine lays a settled layout 1 view out again at the first tick
|
|
512
|
+
where no note holds it.
|
|
513
|
+
4. An edit made on a layout 1 note can be applied after the upgrade: apply
|
|
514
|
+
and the proposal adapter prepare the generation the edit was made on in
|
|
515
|
+
that generation's own layout (`layout`) to recover the note as it was
|
|
516
|
+
published.
|
|
517
|
+
|
|
518
|
+
The folders `notes/` and `attachments/` are left behind empty: the publisher
|
|
519
|
+
moves files, never folders, and a person may delete them. While a view waits
|
|
520
|
+
for a held note, a selection resolved from the registry names layout 2 paths
|
|
521
|
+
that the vault does not hold yet.
|
|
522
|
+
|
|
523
|
+
An edit that closes on a tick still holds the layout on that tick. In
|
|
524
|
+
automatic mode a policy applies an edit on the very tick the edit is observed:
|
|
525
|
+
the view is prepared in layout 1 once more, from the new source, so the note
|
|
526
|
+
the person edited becomes the published note, and the next tick lays the view
|
|
527
|
+
out again and retires that file to recovery like any other. It is never left
|
|
528
|
+
in the vault unobserved. In manual mode the hold lifts when the observed note
|
|
529
|
+
equals the published one, one tick in layout 1 follows the same way, and the
|
|
530
|
+
view is then laid out again.
|
|
74
531
|
|
|
75
532
|
## Graph behaviour changes since the shared link resolver
|
|
76
533
|
|
|
@@ -124,9 +581,11 @@ In a view, an asset is copied only when the embedding note is in the vault
|
|
|
124
581
|
set, the asset record carries `eligible: true` (`withEligibility` takes a
|
|
125
582
|
second predicate; without it every asset is withheld) and the profile enrols
|
|
126
583
|
its repository under the same audience rule as a node without an audience of
|
|
127
|
-
its own. It is copied once, byte for byte, to
|
|
128
|
-
|
|
129
|
-
|
|
584
|
+
its own. It is copied once, byte for byte, to its own path mirrored in the
|
|
585
|
+
vault (`<repository folder>/<source directory>/<file name>`, allocated like a
|
|
586
|
+
note; see "Vault layout"; in layout 1,
|
|
587
|
+
`attachments/<readable stem>--<identity suffix>.<ext>`), read through the
|
|
588
|
+
pinned snapshot (`source-not-in-snapshot`, `mixed-read`). The embed target is
|
|
130
589
|
rewritten through the link inversion machinery: a Markdown embed to the
|
|
131
590
|
percent-encoded vault-root path, a wikilink embed to the plain path with its
|
|
132
591
|
size or fragment left as authored and no alias added. The manifest lists the
|
|
@@ -185,13 +644,18 @@ seen by the graph, the snapshot or the view until observation hashes the file
|
|
|
185
644
|
again. The result reports `fileCensus: { reused, derived, read }`.
|
|
186
645
|
|
|
187
646
|
Preparation stage: `prepareView` takes `cache` (`createPreparationCache`).
|
|
188
|
-
Each note's bytes and manifest entry are a function of
|
|
189
|
-
|
|
190
|
-
and
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
647
|
+
Each note's bytes and manifest entry are a function of the vault layout and
|
|
648
|
+
emitter version, its pinned source digest, node record (repository, identity
|
|
649
|
+
and source path, which also make its identity block), allocated path (which
|
|
650
|
+
also names a wrapped file), generated rows (which carry the paths and titles
|
|
651
|
+
of their targets), outside-selection count and rewritten occurrences with
|
|
652
|
+
their emitted targets; those inputs form a dependency key, and a note whose
|
|
653
|
+
key equals the cached one reuses the cached bytes, manifest entry, attachment
|
|
654
|
+
record and inversions. Where the identity block goes and with which line
|
|
655
|
+
ending is decided from the source bytes, which the pinned digest covers.
|
|
656
|
+
Everything else (asset copies, links, settings, collision checks, the
|
|
657
|
+
manifest and the redaction guard over the whole result) runs as before. The
|
|
658
|
+
result reports
|
|
195
659
|
`preparation: { emitted, reused }`, and a preparation that emits exactly the
|
|
196
660
|
notes whose output changed is what the tests pin.
|
|
197
661
|
|
|
@@ -243,7 +707,7 @@ This is a layout inside the private store; the protocol is unchanged.
|
|
|
243
707
|
| --- | --- |
|
|
244
708
|
| Any candidate while it is written and fsynced (generated bytes only) | `staging/<journalId>/NNNNNN.candidate`, or `NNNNNN.late.candidate` for one that could not be known when the run began |
|
|
245
709
|
| Candidate of a file that is created (exclusive link, no exchange) | stays at that staging path until it is linked |
|
|
246
|
-
| Candidate of a file that is replaced (note, attachment, policy settings) | moved, complete, to `recovery/<journalId>/<unit>/exchange.candidate`, or `exchange.late.candidate` |
|
|
710
|
+
| Candidate of a file that is replaced (note, attachment, policy settings, Atelier's plugin files) | moved, complete, to `recovery/<journalId>/<unit>/exchange.candidate`, or `exchange.late.candidate` |
|
|
247
711
|
| Displaced bytes | the vault path, then that same candidate path, then `recovery/<journalId>/<unit>/displaced.bin` |
|
|
248
712
|
|
|
249
713
|
The journal names every exchange candidate, by path and SHA-256, before the
|
|
@@ -264,8 +728,10 @@ share nothing but the vault, so the vault lock lives under the vault's real
|
|
|
264
728
|
path in `.atelier-publication/`, a dot-directory that no note path can name
|
|
265
729
|
and that the app does not show. The second publisher refuses with
|
|
266
730
|
`publication-in-progress` and writes nothing. It is the only thing the
|
|
267
|
-
publisher writes in a vault besides notes, attachments
|
|
268
|
-
settings
|
|
731
|
+
publisher writes in a vault besides notes, attachments, the two policy
|
|
732
|
+
settings files (`core-plugins.json`, `community-plugins.json`) and Atelier's
|
|
733
|
+
own plugin files under `.obsidian/plugins/atelier-projection/` (see
|
|
734
|
+
[obsidian-plugin.md](obsidian-plugin.md)).
|
|
269
735
|
|
|
270
736
|
Release and recovery follow the view lock exactly. A finished publication
|
|
271
737
|
writes a release marker. A publisher that dies leaves a ticket naming its
|
|
@@ -304,6 +770,77 @@ Before 0.2.0-alpha.10 no production reading was ever absent, because the probe
|
|
|
304
770
|
found Atelier's own service; from that release this path, and this window, are
|
|
305
771
|
live.
|
|
306
772
|
|
|
773
|
+
### First publication into a vault no Obsidian lists
|
|
774
|
+
|
|
775
|
+
A running or unknowable app that cannot be coordinated with still refuses the
|
|
776
|
+
whole publication, with one exception, the `direct-unheld` path. It is chosen
|
|
777
|
+
at path selection only when all of these hold:
|
|
778
|
+
|
|
779
|
+
1. The view has no committed generation.
|
|
780
|
+
2. Its vault is the folder allocated for it (`vaultOrigin: 'allocated'`), and
|
|
781
|
+
that folder's device and inode are the ones its record names.
|
|
782
|
+
3. The app's own settings file (`obsidian.json`) was read, and none of its
|
|
783
|
+
entries is the vault folder, a folder above it or a folder inside it. Both
|
|
784
|
+
sides are compared as written and by their real paths, in any letter case.
|
|
785
|
+
A file that cannot be read is read again, up to three times 50 ms apart,
|
|
786
|
+
because the app rewrites it with a plain write. A missing or unreadable
|
|
787
|
+
file, an entry whose folder is not an absolute path, an entry with a `..`
|
|
788
|
+
segment (the folder it names through a link cannot be placed by its text),
|
|
789
|
+
or a Flatpak or snap build of the app anywhere on the account (its list is
|
|
790
|
+
elsewhere) gives no evidence.
|
|
791
|
+
4. The platform is macOS or Linux.
|
|
792
|
+
|
|
793
|
+
Any other case gives the refusal it gave before. The maintenance service reads
|
|
794
|
+
the evidence from the file and never asks the app.
|
|
795
|
+
|
|
796
|
+
On this path the publisher writes only creates: a file appears through the
|
|
797
|
+
exclusive link where nothing is, or not at all. A unit that would replace or
|
|
798
|
+
remove a file, or write a settings file over one that is there, refuses as
|
|
799
|
+
`editor-uncoordinated` (blocking) and writes nothing. A settings file that
|
|
800
|
+
already holds exactly what the policy asks is left as it is, which is what an
|
|
801
|
+
interrupted run of this path leaves. A file already at a note path is kept as
|
|
802
|
+
`create-conflict`. The evidence is read again immediately before the first
|
|
803
|
+
unit and whenever two seconds have passed. Once a reading gives none, every
|
|
804
|
+
remaining unit refuses, a kept one included, and nothing is committed. The
|
|
805
|
+
view lock and the vault lock are taken as on every path, and the journal
|
|
806
|
+
records `mode: 'direct-unheld'`. Restart recovery and the late-writer check
|
|
807
|
+
treat it as a `direct` journal.
|
|
808
|
+
|
|
809
|
+
Why this loses nothing a person wrote, whoever holds the vault: no editor can
|
|
810
|
+
hold an unsaved buffer of a file that does not exist; the link either makes
|
|
811
|
+
the whole file appear or fails because something is there, which is kept; and
|
|
812
|
+
an editor that later saves over a created file wins, and is read as an edit.
|
|
813
|
+
The list evidence is for the experience, and it holds only for a run that
|
|
814
|
+
completes: that run is committed before `open` first tells the app about the
|
|
815
|
+
vault. A run that stops partway commits nothing. That happens when the list
|
|
816
|
+
names the folder meanwhile, a settings file with other bytes is already there,
|
|
817
|
+
or an interrupted run left a replacement or a removal. The view then reports
|
|
818
|
+
`editor-uncoordinated` and falls back to the coordinated path: `open` may add
|
|
819
|
+
the vault to the app and open it before it is complete, and what is left,
|
|
820
|
+
every replacement and removal included, is published through the app with its
|
|
821
|
+
usual base and buffer checks. The person may see an incomplete vault then;
|
|
822
|
+
nothing they write is at risk. Lists this account's app does not keep are
|
|
823
|
+
covered by the argument above alone: another user-data folder, another
|
|
824
|
+
machine through a synced folder.
|
|
825
|
+
|
|
826
|
+
A maintenance service whose app does not qualify (its command line off, no
|
|
827
|
+
vault open, a version below the floor) no longer keeps such a view from its
|
|
828
|
+
first publication: the publisher is reached with an adapter that coordinates
|
|
829
|
+
with nothing, and takes this path when every condition holds. When the path
|
|
830
|
+
does not apply, or the run stops partway at a unit refused as
|
|
831
|
+
`editor-uncoordinated`, the view reports the app's refusal, as before. `open`
|
|
832
|
+
then answers that refusal without adding the vault to the app, unless the
|
|
833
|
+
person asks it to restart Obsidian (`--restart-obsidian`): the vault is then
|
|
834
|
+
added and opened, and may be incomplete until the app has published the rest.
|
|
835
|
+
One case opens a vault without that flag: with the app running and no vault
|
|
836
|
+
open, a vault the app already lists is opened by its path, as before this
|
|
837
|
+
path existed, and may be incomplete. A run that stops only on a file in its
|
|
838
|
+
way reports that (`create-conflict`). When it stops for more than one reason,
|
|
839
|
+
one is reported: while the app does not qualify, the app's refusal wins over a
|
|
840
|
+
file in the way, and while it qualifies, the first blocking unit's reason is
|
|
841
|
+
reported. A reason not reported comes back at a later run, and nothing is
|
|
842
|
+
written meanwhile.
|
|
843
|
+
|
|
307
844
|
### Proven boundary
|
|
308
845
|
|
|
309
846
|
macOS (Darwin 25, arm64) with Obsidian 1.13.7 (installer 1.12.7), CLI enabled,
|
|
@@ -322,6 +859,13 @@ A fifth run aborted because the harness could not open a note after the
|
|
|
322
859
|
second-window case; no bytes were involved. The receipts are maintainer-held
|
|
323
860
|
and are not part of this repository.
|
|
324
861
|
|
|
862
|
+
Every vault Atelier publishes now carries Atelier's own plugin
|
|
863
|
+
([obsidian-plugin.md](obsidian-plugin.md)), so a trusted vault runs one
|
|
864
|
+
community plugin. In phase 1 it registers no editor extension, no vault event
|
|
865
|
+
handler and nothing that writes; it only talks to the maintenance service over
|
|
866
|
+
loopback. The protocol cases above have not been re-run with it loaded; that
|
|
867
|
+
run is gate G20, before the plugin carries any part of publication.
|
|
868
|
+
|
|
325
869
|
### Receiving obligations for the production publisher
|
|
326
870
|
|
|
327
871
|
The publisher may not claim the protocol outside the proven boundary until
|
|
@@ -345,6 +889,297 @@ each open item is discharged with its own evidence.
|
|
|
345
889
|
| Link-then-rename | Rejected. It leaves a window in which a concurrent replacement is destroyed. |
|
|
346
890
|
| App-driven save after replacement | Rejected. Observed losing an outside writer's bytes; kept as a negative control. |
|
|
347
891
|
|
|
892
|
+
## Obsidian's vault list
|
|
893
|
+
|
|
894
|
+
Obsidian opens a vault from a link only when the folder is in its own vault
|
|
895
|
+
list. `atelier obsidian open` puts the view's vault there.
|
|
896
|
+
Outside its own storage (the data root, which holds the vaults and their
|
|
897
|
+
policy-owned `.obsidian/core-plugins.json`), the settings file that holds the
|
|
898
|
+
list is the only file of another application that Atelier writes, and in it
|
|
899
|
+
only two things: the view's vault entry and the command-line switch `cli`. To
|
|
900
|
+
write it, Atelier writes nothing else outside the app's user-data directory,
|
|
901
|
+
and no vault note. Every write is shown in `open`'s answer
|
|
902
|
+
(`obsidianSettings`: the file, whether it was created, whether the vault was
|
|
903
|
+
added, whether the switch was turned on, and the backup).
|
|
904
|
+
|
|
905
|
+
**The file.** `obsidian.json` in the app's user-data directory:
|
|
906
|
+
`$HOME/Library/Application Support/obsidian/` on macOS and
|
|
907
|
+
`$XDG_CONFIG_HOME/obsidian/` (else `$HOME/.config/obsidian/`) on Linux; no
|
|
908
|
+
location is known on other platforms. Its shape is
|
|
909
|
+
`{ "vaults": { "<16 hex>": { "path", "ts", "open"? } }, …other settings }`.
|
|
910
|
+
The app reads it when it starts and rewrites all of it, with
|
|
911
|
+
`JSON.stringify`, whenever its list changes; `open` is true while a window of
|
|
912
|
+
that vault is open and stays true for the vaults open when the app quit.
|
|
913
|
+
Modules: `src/projection/obsidian/publication/vault-list.mjs` (read only) and
|
|
914
|
+
`src/runtime/obsidian/app-registration.mjs` (the write).
|
|
915
|
+
|
|
916
|
+
**While Obsidian runs and answers its command line**, the file is the running
|
|
917
|
+
app's and is only read. `open` asks the app instead, through `obsidian-cli
|
|
918
|
+
eval` with constant scripts: `vault-list` (the app's own map) and `vault-open`
|
|
919
|
+
with the folder and `false`, which adds an existing folder to the list (the app
|
|
920
|
+
writes its own file) and opens it in a window; `true`, which would create a
|
|
921
|
+
folder, is never sent. The folder travels as a base64-encoded JSON payload, as
|
|
922
|
+
in the publication bridge, so a path never becomes code. The addition is
|
|
923
|
+
verified by reading the app's list again and finding the vault root's entry
|
|
924
|
+
(asked again, a bounded number of times, while the window the app just opened
|
|
925
|
+
is still loading and answers that a command does not exist yet); an addition
|
|
926
|
+
the app did not answer (a call that timed out) is looked up the same way, and
|
|
927
|
+
is `addition-not-answered` when the list does not show it. `open` then waits,
|
|
928
|
+
bounded, until the app answers for exactly that vault. Like Obsidian's own
|
|
929
|
+
"open folder as vault", `vault-open` also adds the folder to the operating
|
|
930
|
+
system's recently used documents (Recent Items on macOS).
|
|
931
|
+
|
|
932
|
+
An entry is the vault root's when its folder, as written, is the root as
|
|
933
|
+
given or its real path; a listed folder's own real path is read only when its
|
|
934
|
+
last component is the root's (the root reached through a linked parent), so
|
|
935
|
+
a vault on a mount that does not answer is never waited on, and a link to the
|
|
936
|
+
root under another name is not recognised.
|
|
937
|
+
|
|
938
|
+
**While Obsidian runs with no vault open**, its command line answers nothing
|
|
939
|
+
and the file is still the running app's: it is read, never written. A vault
|
|
940
|
+
it already lists is opened by path; one it does not is refused as
|
|
941
|
+
`no-vault-open`, unless the person asked for a restart (below).
|
|
942
|
+
|
|
943
|
+
**While Obsidian runs with its command line off**, it answers every command
|
|
944
|
+
but a link with "Command line interface is not enabled", read as
|
|
945
|
+
`app-cli-unavailable` / `cli-turned-off`. The file is the running app's, which
|
|
946
|
+
holds the switch in memory and writes it back with its list, so it is not
|
|
947
|
+
written; the switch is turned on only while no Obsidian runs, or through the
|
|
948
|
+
restart below.
|
|
949
|
+
|
|
950
|
+
**Never inside another vault.** In every state, a vault inside a folder the
|
|
951
|
+
list has as a vault already (compared as written, against the vault root and
|
|
952
|
+
its real path) is not added, through the
|
|
953
|
+
app or in the file: that vault would show its notes too, and a call run in
|
|
954
|
+
its folder would reach that vault. `open` answers `launch-failed` with reason
|
|
955
|
+
`vault-inside-another-vault`.
|
|
956
|
+
|
|
957
|
+
**One folder open in several windows.** The list can name one folder more
|
|
958
|
+
than once (under another letter case, or through a link), and the app opens
|
|
959
|
+
one window per entry. When more than one of the entries that name the view's
|
|
960
|
+
folder is open, each window holds the vault and a call reaches one of them
|
|
961
|
+
only (`vaultRoute` answers `duplicated`): no publication call is made (the
|
|
962
|
+
publisher refuses `vault-open-in-several-windows`, a `publisher-conflict`),
|
|
963
|
+
and `open` answers `publisher-conflict` with that reason, names the entries
|
|
964
|
+
(`duplicates`) and launches nothing. The entries are read from the list's
|
|
965
|
+
`open` marks, and the app keeps the last window it closed marked open, so this
|
|
966
|
+
can hold with a single window showing; the next step is to remove the extra
|
|
967
|
+
entries from the list. While one entry has a window, a call
|
|
968
|
+
reaches only that entry, and `open` finds and launches it, so a closed entry of
|
|
969
|
+
the same folder is never opened beside it.
|
|
970
|
+
|
|
971
|
+
**A Flatpak or snap build** keeps its list inside its sandbox
|
|
972
|
+
(`~/.var/app/md.obsidian.Obsidian/config/obsidian/`,
|
|
973
|
+
`~/snap/obsidian/current/.config/obsidian/`) and never reads the file above.
|
|
974
|
+
On Linux the build in use is the one whose `obsidian.json` (native, Flatpak or
|
|
975
|
+
snap) was written last, since every build rewrites its own when a vault window
|
|
976
|
+
opens or closes; only when none was ever written, a Flatpak or snap
|
|
977
|
+
installation or leftover decides (`~/.var/app/md.obsidian.Obsidian`,
|
|
978
|
+
`~/.local/share/flatpak/app/md.obsidian.Obsidian`,
|
|
979
|
+
`/var/lib/flatpak/app/md.obsidian.Obsidian`, `~/snap/obsidian`,
|
|
980
|
+
`/snap/obsidian`, `/var/lib/snapd/snap/obsidian`). For a Flatpak or snap
|
|
981
|
+
build the file is neither read nor written, `open` answers
|
|
982
|
+
`obsidian-sandboxed`, and the vault is added through the running app only.
|
|
983
|
+
|
|
984
|
+
**While no Obsidian runs**, the vault is added to the file itself, and only
|
|
985
|
+
then:
|
|
986
|
+
|
|
987
|
+
1. The process table is read and must say, positively, `absent`. `running` or
|
|
988
|
+
`unknown` refuses (`app-may-be-running`).
|
|
989
|
+
2. The user-data directory and the file must exist, be this user's own, and be
|
|
990
|
+
a real directory and a regular file (neither a link), and the file must be
|
|
991
|
+
a JSON object whose `vaults`, when present, is an object, of at most 4 MiB.
|
|
992
|
+
A file with a second name (a hard link) is refused as
|
|
993
|
+
`obsidian-settings-unsafe`: the replacement would leave that name with the
|
|
994
|
+
old list. A missing file means Obsidian never started on this account; it
|
|
995
|
+
is created as described under "Obsidian never started" below. The other
|
|
996
|
+
refusals are `obsidian-settings-unsafe`, `obsidian-settings-not-owned`,
|
|
997
|
+
`obsidian-settings-unreadable` and `obsidian-settings-not-object`.
|
|
998
|
+
3. A vault whose real path an entry already has is left as it is. When `cli`
|
|
999
|
+
is already `true` too, nothing is written. A vault inside a folder an
|
|
1000
|
+
entry has is refused (`vault-inside-another-vault`).
|
|
1001
|
+
4. The new document is the file's object with one entry added under a fresh
|
|
1002
|
+
random 16-hex id that no entry has: `{ path: <the vault root's real path>,
|
|
1003
|
+
ts: <now, ms>, open: true }`, unless the vault is listed already, and with
|
|
1004
|
+
`cli` set to `true` when it is not (the command-line switch: in its place
|
|
1005
|
+
when the file has the key, otherwise last, as the app adds it). Every
|
|
1006
|
+
other key and every other entry is kept in its place, as values. A
|
|
1007
|
+
document that would be larger than 4 MiB is refused
|
|
1008
|
+
(`obsidian-settings-too-large`).
|
|
1009
|
+
5. A temporary file in the same directory is written with the file's mode and
|
|
1010
|
+
fsynced; the bytes as they were are written, fsynced, to
|
|
1011
|
+
`obsidian.json.atelier-backup-<UTC time>` beside it.
|
|
1012
|
+
6. Immediately before the rename, the process table must still say `absent`
|
|
1013
|
+
and the file must still hold the bytes that were read; otherwise both new
|
|
1014
|
+
files are removed and the write is refused (`app-may-be-running`,
|
|
1015
|
+
`obsidian-settings-changed`).
|
|
1016
|
+
7. The temporary file is renamed over `obsidian.json` and the directory is
|
|
1017
|
+
fsynced. Of the backups beside it, the first (the list as it was before
|
|
1018
|
+
Atelier wrote it) and this one are kept, and any between them removed. The
|
|
1019
|
+
process table is read once more: an app that appeared just then may have
|
|
1020
|
+
read the list before the rename, so the addition is reported unconfirmed
|
|
1021
|
+
and `open` answers `launch-failed` with reason
|
|
1022
|
+
`app-started-during-registration` instead of launching; the next `open`
|
|
1023
|
+
adds the vault through the app if it missed it. A file that cannot be read
|
|
1024
|
+
back to find the new entry is reported unconfirmed as well, with reason
|
|
1025
|
+
`registration-not-read-back`.
|
|
1026
|
+
|
|
1027
|
+
Every refusal writes nothing and leaves nothing behind: a temporary file or
|
|
1028
|
+
backup created on the way is removed again whichever later step fails.
|
|
1029
|
+
|
|
1030
|
+
**Obsidian never started.** When the file does not exist and the process
|
|
1031
|
+
table says, positively, `absent` (read before, and again immediately before
|
|
1032
|
+
the file appears), the file is created, and only then:
|
|
1033
|
+
|
|
1034
|
+
1. The user-data directory must be this user's own real directory. When it
|
|
1035
|
+
does not exist, it is created with mode 0700, only inside an existing
|
|
1036
|
+
parent directory that is this user's own and no link; a missing parent is
|
|
1037
|
+
`obsidian-settings-missing`, and nothing is created above the directory.
|
|
1038
|
+
2. The content is exactly `{ "vaults": { "<16 hex>": { "path": <the vault
|
|
1039
|
+
root's real path>, "ts": <now, ms>, "open": true } }, "cli": true }`, mode
|
|
1040
|
+
0600. The app reads the file at start-up (a missing or unreadable one reads
|
|
1041
|
+
as no settings) and opens every vault flagged open, so a plain start opens
|
|
1042
|
+
this one, with its command line on.
|
|
1043
|
+
3. The bytes are written and fsynced to a temporary file in the directory,
|
|
1044
|
+
which is then hard-linked to `obsidian.json` and removed: the file appears
|
|
1045
|
+
complete or not at all, and the link fails on any file there, one the app
|
|
1046
|
+
wrote meanwhile included, which is kept (`obsidian-settings-changed`). The
|
|
1047
|
+
directory is fsynced. There is nothing to back up.
|
|
1048
|
+
4. An app that appears before the link refuses (`app-may-be-running`); one
|
|
1049
|
+
that appears right after it leaves the addition unconfirmed, as in step 7
|
|
1050
|
+
above. A refusal removes the temporary file and a directory created on the
|
|
1051
|
+
way.
|
|
1052
|
+
|
|
1053
|
+
`open` answers `registration.how: 'created-settings'` for it.
|
|
1054
|
+
|
|
1055
|
+
**Restart, only when the person asks** (`open --restart-obsidian`; never
|
|
1056
|
+
implied, and refused by every other operation). Only in the two states above
|
|
1057
|
+
where the app runs and cannot be reached: its command line off, or no vault
|
|
1058
|
+
open and the view's vault not in its list. Module:
|
|
1059
|
+
`src/runtime/obsidian/app-restart.mjs`.
|
|
1060
|
+
|
|
1061
|
+
0. Read-only, before anything is sent (`settingsWriteOutlook`): the view's
|
|
1062
|
+
vault folder exists, the settings file reads as in step 2 above (no
|
|
1063
|
+
Flatpak or snap build), has one name, would stay within 4 MiB with the
|
|
1064
|
+
entry, and lists no vault at a folder above the vault's. A file that does
|
|
1065
|
+
not exist (the app has shown only its starter window, which writes none)
|
|
1066
|
+
is the create case, checked as "Obsidian never started" checks before it
|
|
1067
|
+
writes (step 1 there); it is created after the quit. Otherwise `open`
|
|
1068
|
+
answers that refusal and the app keeps running. Once the main process is
|
|
1069
|
+
proven (step 2 below), right before the first signal, a copy of an existing
|
|
1070
|
+
file is kept beside it under the backup name (`backupObsidianSettings`,
|
|
1071
|
+
fsynced, the file's mode; `restart.settingsCopy`); a copy that cannot be
|
|
1072
|
+
kept sends nothing. The write in the same run does not prune that copy
|
|
1073
|
+
(`keepBackups`); a later write prunes it as step 7 says.
|
|
1074
|
+
1. The app's main process is proven from the process table
|
|
1075
|
+
(`ps -ww -A -o pid=,ppid=,uid=,comm=`; a uid is signed, since macOS shows
|
|
1076
|
+
`nobody` as -2): every Obsidian process (the names the process probe
|
|
1077
|
+
counts) runs as this user, exactly one names the app bundle's main
|
|
1078
|
+
executable by an absolute path (`…/Contents/MacOS/Obsidian`), and every
|
|
1079
|
+
other one is its child. `comm` is the process's argv[0], not a verified
|
|
1080
|
+
image: a process of the same user could take that name, which proves
|
|
1081
|
+
nothing while the real app runs too (two main processes). Otherwise nothing
|
|
1082
|
+
is sent (`app-main-process-unproven`, with `restart.detail`).
|
|
1083
|
+
2. That process is read again (`ps -o ppid=,uid=,lstart=,comm=`): it must be
|
|
1084
|
+
this user's, under the parent the table showed, with the same name, and
|
|
1085
|
+
read the same twice in a row; then it, and only it, gets SIGTERM, which
|
|
1086
|
+
Electron handles as a quit. The app closes every window, keeping the
|
|
1087
|
+
vaults that were open flagged open, and its helpers end (1.13.7, checked on
|
|
1088
|
+
an isolated instance).
|
|
1089
|
+
3. The process probe must say `absent` within 30 seconds. On macOS the main
|
|
1090
|
+
process can stay after the first SIGTERM, with no window (seen on
|
|
1091
|
+
an isolated 1.13.7, with vaults open and with only the starter window).
|
|
1092
|
+
Electron handles only the first SIGTERM as a quit; a second one ends the
|
|
1093
|
+
process at once, without its quit handlers. It is sent once, only when the
|
|
1094
|
+
table shows that same process alone (no helper) and it reads the same, at
|
|
1095
|
+
two readings at least 5 seconds apart. There is never a third signal, and
|
|
1096
|
+
never another kind; `restart.signals` counts the signals delivered.
|
|
1097
|
+
Otherwise `open` answers `launch-failed` / `app-did-not-quit`, writes
|
|
1098
|
+
nothing and starts nothing.
|
|
1099
|
+
4. The file is written as above, with the vault and the switch.
|
|
1100
|
+
5. The app is started plainly (`open -b md.obsidian`) by the launch steps
|
|
1101
|
+
below, and reopens its vaults and this one. An answer given after the quit
|
|
1102
|
+
and before the launch (the file could not take the vault, say) starts the
|
|
1103
|
+
app plainly first, as it was; a start that fails is said, with the next
|
|
1104
|
+
step to start it by hand.
|
|
1105
|
+
|
|
1106
|
+
The answer carries `restart: { asked, state, quit, signalled, pid, signals,
|
|
1107
|
+
settingsCopy, startedAgain }`, and `reason` and `detail` when it did not quit.
|
|
1108
|
+
An `open` interrupted between the quit and the start leaves the app quit (a
|
|
1109
|
+
known limit). At a terminal the restart still needs the flag; the question the
|
|
1110
|
+
design asks in that state comes with the first-run command. Qualified on macOS only; elsewhere the answer is
|
|
1111
|
+
`restart-platform-unqualified`, since a quit app there is started with a
|
|
1112
|
+
link, which would drop the other vaults' reopen flags.
|
|
1113
|
+
|
|
1114
|
+
`open` then shows the vault by the steps of `launchPlan`
|
|
1115
|
+
(`src/runtime/obsidian/launch-plan.mjs`, carried out by `runLaunchPlan`),
|
|
1116
|
+
naming it `obsidian://open?vault=<id>` when the id reaches it first (a path is
|
|
1117
|
+
matched against the list by string prefix, with no separator check), and by
|
|
1118
|
+
its own listed path otherwise (another vault's folder named like the id, an
|
|
1119
|
+
unusual id): a running app is handed the link through its command-line tool,
|
|
1120
|
+
which takes a link even with its command line turned off, or through the
|
|
1121
|
+
operating system when the tool did not take it; on macOS a quit app is started
|
|
1122
|
+
plainly (`open -b md.obsidian`), which reopens every vault the list flags open
|
|
1123
|
+
(the entry written above is flagged open), and handed the link through its
|
|
1124
|
+
tool once the app itself answers the tool (`appAnswered`: a version or one of
|
|
1125
|
+
the app's own lines; the tool's "unable to find Obsidian" while the app starts
|
|
1126
|
+
does not count). A link is never handed to the operating system for an app
|
|
1127
|
+
this launch started, since that would reach it while it starts. A link the
|
|
1128
|
+
started app did not take is left to it when the list flags the vault open,
|
|
1129
|
+
since the plain start reopens it; for a listed vault that is closed, which a
|
|
1130
|
+
plain start does not reopen, the link is handed once more, through the tool
|
|
1131
|
+
only (this launch started the app), one wait round later, before `open`
|
|
1132
|
+
answers `launch-failed` /
|
|
1133
|
+
`app-did-not-answer-for-this-vault`. An app started with a link would delete the
|
|
1134
|
+
reopen flag of every other vault without a window (1.13.7, checked on an
|
|
1135
|
+
isolated instance). On Linux a quit app is started with the link, a known
|
|
1136
|
+
limit. With its command line turned off the app answers every other command
|
|
1137
|
+
with "Command line interface is not enabled"; that is read as
|
|
1138
|
+
`app-cli-unavailable` / `cli-turned-off`, never as a version.
|
|
1139
|
+
|
|
1140
|
+
**Which window answers.** The command line (1.13.7) answers a call whose first
|
|
1141
|
+
argument is `vault=<value>` in the window of the first listed vault whose id is
|
|
1142
|
+
the value, or whose folder's name is the value in any letter case; any other
|
|
1143
|
+
call in the window of the first listed vault whose folder is the tool's working
|
|
1144
|
+
directory or contains it, the folders compared as written; and otherwise in the
|
|
1145
|
+
vault window that had focus last. It opens a vault that takes a call when it is
|
|
1146
|
+
closed. The first match in list order wins, not the deepest folder, so a vault
|
|
1147
|
+
listed before the view's vault at a folder above it would take every call run
|
|
1148
|
+
in the view's folder. Calls about no vault (the version, the vault list, the
|
|
1149
|
+
addition) run in a directory that is no vault and name none. A call about the
|
|
1150
|
+
view's vault goes where the list says only that vault takes it
|
|
1151
|
+
(`vaultRoute` in `vault-list.mjs`): from a directory that is no vault with
|
|
1152
|
+
`vault=<id>` first when that id names this vault first, which does not depend
|
|
1153
|
+
on how a working directory is spelled; otherwise in its folder when the first
|
|
1154
|
+
listed vault that is or contains that folder is this vault; when neither
|
|
1155
|
+
holds, no call is made. The vault root is taken in the spelling the file
|
|
1156
|
+
system stores (`realpathSync.native` on macOS and Linux), which is how the
|
|
1157
|
+
operating system reports the tool's working directory: on a volume that folds
|
|
1158
|
+
letter case, a data root given in another case neither hides a vault listed
|
|
1159
|
+
above it nor routes a call there. `open`'s check
|
|
1160
|
+
that the app answers for the vault is routed from the list it verified the
|
|
1161
|
+
vault in. Publication calls are routed from the file, and only while it lists
|
|
1162
|
+
the vault open; otherwise they run in a directory that is no vault and name
|
|
1163
|
+
none, so maintenance never reopens a vault window that was closed and never
|
|
1164
|
+
reaches another vault. The bridge still checks that the app answered for
|
|
1165
|
+
exactly this vault, and `open` that the app answers for it, both folders taken
|
|
1166
|
+
as the file system stores them: an app that holds the vault under another
|
|
1167
|
+
letter case or through a link holds this vault, and a store written under
|
|
1168
|
+
another spelling keeps its vault.
|
|
1169
|
+
|
|
1170
|
+
**Retries.** The maintenance service is told what the app looks like from
|
|
1171
|
+
the process table and the file alone (whether an Obsidian process runs, and
|
|
1172
|
+
which vaults the file shows open; the file is read, never written, by the
|
|
1173
|
+
service): nothing runs in the app to find out. The app is asked for its
|
|
1174
|
+
version only when a view is about to be published, and without blocking the
|
|
1175
|
+
service. A tick requested over the service's listener for one view (`open`
|
|
1176
|
+
names its own) prepares and publishes that view once more, with the app's
|
|
1177
|
+
qualification asked again; the engine takes such requests only for views the
|
|
1178
|
+
project declared at its last tick, and at most 64 before its first. A view
|
|
1179
|
+
whose last publication did not settle is also tried again as soon as that
|
|
1180
|
+
picture changes, and otherwise after a delay that starts at 30 seconds and
|
|
1181
|
+
doubles per attempt, up to the full reconciliation interval.
|
|
1182
|
+
|
|
348
1183
|
Source application back into canonical files carries the same conditional-write
|
|
349
1184
|
obligation against other source writers; its protocol follows.
|
|
350
1185
|
|
|
@@ -465,6 +1300,7 @@ today) it refuses `exchange-unavailable`. Both write nothing.
|
|
|
465
1300
|
| `interrupted-before-exchange`, `apply-interrupted-needs-person` | what restart recovery decided for an interrupted apply |
|
|
466
1301
|
| `recovery-state-unreadable` | a candidate, backup or source path of an interrupted apply may not be looked at or read; nothing is settled from a read this process was denied, that record is reported with its intent still open, and every other one is still settled |
|
|
467
1302
|
| `apply-outcome-unknown` | the source was exchanged and the settlement from digests could not be carried out; the source may have been changed, the intent stays open and `apply recover` decides |
|
|
1303
|
+
| `personal-overlay-proposals-only` | the note is a private note of a personal workspace, whose file belongs to a module-owned generation; nothing is written there. The proposal adapter's observation answers the same edit with `personal-overlay-not-proposed`, and records nothing |
|
|
468
1304
|
|
|
469
1305
|
A path that another program removes or replaces between two steps, before the
|
|
470
1306
|
intent is recorded, answers one of these refusals (`source-missing`,
|
|
@@ -556,7 +1392,11 @@ list`, `show`, `run` and `recover` answer such a record as a typed refusal.
|
|
|
556
1392
|
fragment of one left in the body, and text typed at the very end of the body
|
|
557
1393
|
together with a removed region all refuse `generated-region-edited`. Some of
|
|
558
1394
|
those refusals a person could have been spared; a generated byte in a source
|
|
559
|
-
is never accepted in exchange.
|
|
1395
|
+
is never accepted in exchange. The identity block in the front matter is
|
|
1396
|
+
generated as well: it is removed before the source bytes are made, and a
|
|
1397
|
+
note whose front matter changed, the identity lines included, is a
|
|
1398
|
+
front-matter edit that becomes a proposal. A byte order prefix the editor
|
|
1399
|
+
dropped is taken from the source, as before.
|
|
560
1400
|
- After an apply the next tick prepares the view again. When the prepared note
|
|
561
1401
|
is byte for byte the note the person edited (read from the vault at the
|
|
562
1402
|
moment of that decision, never taken from the engine's observation index; a
|
|
@@ -683,7 +1523,9 @@ line, 16 MiB, 10,000 events, and an unreadable line refuses every append.
|
|
|
683
1523
|
| `ledger-corrupt` | refused, for that repository only; other repositories progress in the same tick |
|
|
684
1524
|
| `store-unavailable` | the store cannot be read or is locked now; waits like a full one |
|
|
685
1525
|
|
|
686
|
-
|
|
1526
|
+
For a personal workspace whose graph the engine has not confirmed yet, a due
|
|
1527
|
+
operation is left as it is (`personal-validation-pending`), and that tick is not
|
|
1528
|
+
counted as an attempt. A waiting operation is tried at most 8 times, 60 seconds after the first attempt
|
|
687
1529
|
and twice as long after each, up to an hour. At most 8 operations per repository
|
|
688
1530
|
and 64 unexamined edits are looked at per tick. A repository holds at most 4096
|
|
689
1531
|
operations, 256 of them open, 64 records of 16 KiB each; a full queue refuses
|