@mnstry/atelier 0.2.0-alpha.12 → 0.2.0-alpha.14
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 +894 -0
- package/README.md +159 -6
- 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-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/docs/architecture.md +89 -0
- package/docs/assurance-controls.md +2 -2
- 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 +267 -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 +210 -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 +237 -0
- package/docs/learning.md +215 -0
- package/docs/local-services.md +223 -19
- package/docs/obsidian-contract.md +246 -15
- package/docs/obsidian-plugin.md +252 -7
- package/docs/obsidian.md +874 -52
- package/docs/optional-ingestion.md +83 -0
- package/docs/personal-workspace.md +266 -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 +50 -0
- package/docs/repo-boundary-guard.md +120 -3
- 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 +318 -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 +219 -2
- 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/personal-workspace/overlay.json +24 -0
- package/fixtures/templates/local-library.v1.json +76 -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 +60 -5
- package/plugins/obsidian/main.js +57 -8
- package/plugins/obsidian/manifest.json +1 -1
- 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 +536 -69
- package/src/boundary/delegation-command.mjs +158 -0
- package/src/boundary/delegation.mjs +396 -0
- package/src/boundary/policy.mjs +185 -64
- 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 +66 -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 +772 -65
- 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 +30 -0
- package/src/coordination/index.mjs +110 -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 +30 -6
- package/src/graph/knowledge-graph.mjs +77 -9
- 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 +938 -0
- package/src/ingestion/optional/graphify/json_bridge.py +163 -0
- package/src/ingestion/optional/graphify/native-closure-pins.json +275 -0
- package/src/ingestion/optional/graphify/native-pins.json +306 -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 +49 -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 +401 -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/path-match.mjs +2 -1
- package/src/projection/obsidian/edits/apply.mjs +55 -16
- package/src/projection/obsidian/materialize/prepare-view.mjs +87 -5
- package/src/projection/obsidian/personal-workspace.mjs +519 -0
- package/src/projection/obsidian/proposals/adapter.mjs +79 -17
- package/src/projection/obsidian/proposals/observation.mjs +50 -4
- package/src/projection/obsidian/publication/publisher.mjs +214 -18
- package/src/projection/obsidian/publication/vault-list.mjs +4 -3
- package/src/projection/obsidian/recovery/index.mjs +4 -1
- package/src/projection/obsidian/recovery/store.mjs +160 -7
- package/src/projection/obsidian/selection-ui/contribution.mjs +6 -5
- package/src/projection/project.mjs +3 -2
- package/src/readiness/readiness.mjs +10 -3
- package/src/reflection/index.mjs +56 -0
- package/src/runtime/obsidian/app-production-seams.mjs +48 -16
- package/src/runtime/obsidian/app-registration.mjs +175 -28
- package/src/runtime/obsidian/app-restart.mjs +154 -0
- package/src/runtime/obsidian/engine.mjs +437 -56
- package/src/runtime/obsidian/extension-points.mjs +1 -1
- package/src/runtime/obsidian/index.mjs +10 -7
- package/src/runtime/obsidian/launch-plan.mjs +7 -4
- package/src/runtime/obsidian/lifecycle.mjs +108 -23
- package/src/runtime/obsidian/login-item.mjs +335 -0
- package/src/runtime/obsidian/machine-settings.mjs +119 -12
- package/src/runtime/obsidian/observation.mjs +36 -4
- package/src/runtime/obsidian/opening.mjs +173 -38
- package/src/runtime/obsidian/personal-selection.mjs +125 -0
- package/src/runtime/obsidian/pipeline.mjs +115 -6
- package/src/runtime/obsidian/plugin-channel.mjs +11 -3
- package/src/runtime/obsidian/plugin-choice.mjs +16 -4
- package/src/runtime/obsidian/plugin-drift.mjs +59 -11
- package/src/runtime/obsidian/plugin-presence.mjs +14 -1
- package/src/runtime/obsidian/private-lock.mjs +39 -4
- 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 +148 -19
- 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 +87 -23
- package/src/runtime/obsidian/service.mjs +102 -9
- package/src/runtime/obsidian/startup-adapters.mjs +59 -21
- 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/runtime/obsidian/view-permission.mjs +334 -0
- package/src/server/local-sidecar.mjs +169 -2
- package/src/server/server.mjs +28 -2
- 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/control-system.mjs +201 -0
- package/src/ui/html-primitives.mjs +3 -0
- package/src/ui/knowledge-page.mjs +1388 -0
- package/src/upgrade/closure-diagnostics.mjs +135 -0
- package/src/upgrade/closure.mjs +649 -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 +20 -7
- package/templates/external-project-workspace/pack-lifecycle.json +7 -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
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createCooperativeBudget, isSchedulingStop } from '../materialize/prepare-view.mjs'
|
|
1
2
|
import fs from 'node:fs'
|
|
2
3
|
import fsp from 'node:fs/promises'
|
|
3
4
|
import path from 'node:path'
|
|
@@ -9,7 +10,7 @@ import { PLUGIN_DATA_MODE, PLUGIN_DATA_PATH, PLUGIN_SOURCE_MODE } from '../plugi
|
|
|
9
10
|
import { createJournal, newJournalId } from '../recovery/journal.mjs'
|
|
10
11
|
import { recheckDisplacedFiles } from '../recovery/late-writer.mjs'
|
|
11
12
|
import { publishedSinceCommit, reconcileUnit, recordDisplaced, recoverPublicationsLocked, retireStagedFile } from '../recovery/restart.mjs'
|
|
12
|
-
import { PublicationRefusal, acquireVaultLock, readFileBytes, readFileDigest, refuse, sha256Digest } from '../recovery/store.mjs'
|
|
13
|
+
import { PublicationRefusal, acquireVaultLock, allocatedFolderState, readFileBytes, readFileDigest, refuse, sha256Digest } from '../recovery/store.mjs'
|
|
13
14
|
import { PROTOCOL_ID, isAddressableVaultPath } from './bridge-script.mjs'
|
|
14
15
|
import { probeExchange } from './exchange.mjs'
|
|
15
16
|
import { CRASH_INJECTION_TEST_SEAM } from './test-seam.mjs'
|
|
@@ -44,11 +45,34 @@ import { createDirectAdapter } from './transport.mjs'
|
|
|
44
45
|
// displaced to recovery and never lost, and a plugin path that a person has to
|
|
45
46
|
// repair never holds the view back. The plugin's data file names a bearer and
|
|
46
47
|
// is written only into a vault root that is private to this user.
|
|
48
|
+
//
|
|
49
|
+
// First publication into a vault no Obsidian lists (`direct-unheld`). A running
|
|
50
|
+
// or unknowable app that cannot be coordinated with still refuses the whole
|
|
51
|
+
// publication, as ever, except in one case: the view has never been committed,
|
|
52
|
+
// its vault is the folder Atelier allocated for it (the record's device and
|
|
53
|
+
// inode), and the evidence (`unheldEvidence`, read by the caller from the app's
|
|
54
|
+
// own settings file) says no list this account's Obsidian keeps names that
|
|
55
|
+
// folder, a folder above it or one inside it, on macOS or Linux. The vault is
|
|
56
|
+
// then filled in this process, with no editor, by creating files only: every
|
|
57
|
+
// file appears through the exclusive link, where nothing is, or not at all. A
|
|
58
|
+
// unit that would replace or remove a file, or write a settings file over one
|
|
59
|
+
// that is there, refuses as `editor-uncoordinated` (blocking) and writes
|
|
60
|
+
// nothing. No editor can hold an unsaved buffer of a file that does not exist,
|
|
61
|
+
// so nothing a person wrote can be lost, whoever holds the vault. The evidence
|
|
62
|
+
// is read again immediately before the first unit and whenever two seconds
|
|
63
|
+
// have passed since the last reading; once a reading gives no evidence, every
|
|
64
|
+
// remaining unit refuses and nothing is committed. A run that completes is
|
|
65
|
+
// committed before `open` tells the app about the vault; a run that stops
|
|
66
|
+
// partway reports why, and what it left is published through the app.
|
|
47
67
|
|
|
48
68
|
const hex = (digest) => digest.slice('sha256:'.length)
|
|
49
69
|
const iso = (clock) => { const value = clock(); return (value instanceof Date ? value : new Date(value)).toISOString() }
|
|
50
70
|
const REFUSED_BY_EDIT = new Set(['editor-edit', 'disk-changed'])
|
|
51
71
|
const unreleased = new Map()
|
|
72
|
+
// A release kept earlier whose lock folder is gone when it is tried again (the vault deleted, replaced, or made again
|
|
73
|
+
// empty at its path) holds nothing there, and is dropped rather than holding every later publication of that vault back.
|
|
74
|
+
// A vault put back holds its ticket again, and the release kept for it then succeeds.
|
|
75
|
+
const lockFolderGone = (error) => error?.code === 'ENOENT' || error?.code === 'ENOTDIR'
|
|
52
76
|
|
|
53
77
|
// A note another program replaces by rename can change between the check of its leaf and the open
|
|
54
78
|
// (ELEAFCHANGED). Every rename leaves a complete file, so it is read again; one that keeps changing is left to
|
|
@@ -212,17 +236,47 @@ function currentDigestOf(vaultRoot, relativePath) {
|
|
|
212
236
|
try { return readFileDigest(path.join(vaultRoot, relativePath)) } catch { return null }
|
|
213
237
|
}
|
|
214
238
|
|
|
239
|
+
// Whether publishing would only leave this plugin path for the person, as it
|
|
240
|
+
// stands: a parent that is a link or not a directory, a leaf that is not a
|
|
241
|
+
// regular file (path-unsafe), or the data file where the vault root cannot hold
|
|
242
|
+
// the bearer and is not one Atelier makes private itself (vault-not-private;
|
|
243
|
+
// see ensurePrivateVaultRoot). Read-only. Conservative: a leaf nobody may
|
|
244
|
+
// read, a parent that cannot be looked into, and a vault root Atelier keeps
|
|
245
|
+
// whose mode cannot be changed are left for the person too, but count here as
|
|
246
|
+
// a drift, as before.
|
|
247
|
+
function leftForThePerson(store, relativePath) {
|
|
248
|
+
let current = store.vaultRoot
|
|
249
|
+
for (const part of relativePath.split('/').slice(0, -1)) {
|
|
250
|
+
current = path.join(current, part)
|
|
251
|
+
const stat = fs.lstatSync(current, { throwIfNoEntry: false })
|
|
252
|
+
if (!stat) break
|
|
253
|
+
if (stat.isSymbolicLink() || !stat.isDirectory()) return true
|
|
254
|
+
}
|
|
255
|
+
const leaf = fs.lstatSync(path.join(store.vaultRoot, relativePath), { throwIfNoEntry: false })
|
|
256
|
+
if (leaf && !leaf.isFile()) return true
|
|
257
|
+
if (relativePath !== PLUGIN_DATA_PATH) return false
|
|
258
|
+
if (store.linkedVaultRoot === true) return true
|
|
259
|
+
const uid = typeof process.getuid === 'function' ? process.getuid() : null
|
|
260
|
+
const root = fs.lstatSync(store.vaultRoot, { throwIfNoEntry: false })
|
|
261
|
+
if (!root?.isDirectory() || uid === null || root.uid !== uid) return true
|
|
262
|
+
return (root.mode & 0o077) !== 0 && store.managedVaultRoot !== true
|
|
263
|
+
}
|
|
264
|
+
|
|
215
265
|
// A generation that is already committed is published again, as it is, when a
|
|
216
266
|
// plugin file it carries is not on disk as it pins it: the person repaired a
|
|
217
267
|
// path Atelier had to leave, or removed or changed a file. The bytes are
|
|
218
268
|
// Atelier's and nothing else of the view changed, so the generation stays the
|
|
219
269
|
// same; its notes are kept, and its plugin units are planned from the disk as
|
|
220
|
-
// ever. A file prepared only where present that is gone is not a drift
|
|
270
|
+
// ever. A file prepared only where present that is gone is not a drift, and
|
|
271
|
+
// neither is one that publishing would only leave for the person again: it
|
|
272
|
+
// would change nothing, and it would need the app for nothing (a view that
|
|
273
|
+
// waits for an app for it would wait for a write that never comes).
|
|
221
274
|
function pluginFilesDrifted(preparedView, store) {
|
|
222
275
|
return preparedView.files.some((file) => {
|
|
223
276
|
if (file.kind !== 'plugin') return false
|
|
224
277
|
const current = currentDigestOf(store.vaultRoot, file.path)
|
|
225
|
-
|
|
278
|
+
if (current === file.digest || (current === null && file.onlyIfPresent === true)) return false
|
|
279
|
+
try { return !leftForThePerson(store, file.path) } catch { return true }
|
|
226
280
|
})
|
|
227
281
|
}
|
|
228
282
|
|
|
@@ -264,14 +318,68 @@ export function planUnits({ files, priorManifest, pointer, ledger, pluginDisk =
|
|
|
264
318
|
return units
|
|
265
319
|
}
|
|
266
320
|
|
|
267
|
-
|
|
321
|
+
// The code of a failure met while candidates were being cleaned up after a staging that did not complete, for the detail
|
|
322
|
+
// of what ended it. Nothing else of the failure is kept: its message may name a path.
|
|
323
|
+
const cleanupCauseOf = (failure) => (failure === null ? {} : { cleanupCause: typeof failure?.code === 'string' && failure.code !== '' ? failure.code.slice(0, 64) : 'unknown' })
|
|
324
|
+
// The stop or refusal itself is handed on, so the caller can still tell it by identity: its `detail` is replaced by a
|
|
325
|
+
// copy that adds `cleanupCause`, and an object shared as its detail is left as it was. A caller that throws the same
|
|
326
|
+
// error object again keeps that field on it.
|
|
327
|
+
function withCleanupCause(error, failure) {
|
|
328
|
+
if (failure !== null) try { error.detail = { ...(error.detail ?? {}), ...cleanupCauseOf(failure) } } catch { /* an error that cannot be extended is handed on as it is */ }
|
|
329
|
+
return error
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// The rules the oracle tests are sensitive to. Production always uses these; the tests substitute a deliberately
|
|
333
|
+
// broken one through publishViewForOracleTests to prove the oracle can fail.
|
|
334
|
+
export const PUBLICATION_PRIMITIVES = Object.freeze({
|
|
335
|
+
// The one operation the `direct-unheld` path writes into the vault: a file created where nothing is.
|
|
336
|
+
unheldWrites: (op) => op === 'create',
|
|
337
|
+
// Whether an error that stopped staging was the caller's stop (its settings check, or a cancellation), handed on
|
|
338
|
+
// under its own code once the candidates are cleaned up, rather than reported as a staging failure.
|
|
339
|
+
callerStoppedStaging: (error) => isSchedulingStop(error),
|
|
340
|
+
// Whether a refusal raised while candidates are being staged (the vault's folder replaced or gone under the run) is
|
|
341
|
+
// handed on only once those candidates are cleaned up, as a stop is. A candidate staged before the journal is
|
|
342
|
+
// written is named by no journal, so restart recovery would never remove one left there.
|
|
343
|
+
refusedStagingCleansUp: () => true,
|
|
344
|
+
// The code the journal's last entry gives an error that ended the publication once the journal was open: a
|
|
345
|
+
// caller's stop under its own code, which is what the caller is told, and anything else as a fault of the publisher.
|
|
346
|
+
// Decided by the tag a stop carries, never by what an error looks like; a stop that carries no code has none to give.
|
|
347
|
+
journalFailureCode: (error) => (isSchedulingStop(error) && typeof error.code === 'string' && error.code !== '' ? error.code.slice(0, 200) : 'publisher-error'),
|
|
348
|
+
})
|
|
349
|
+
|
|
350
|
+
// Whether the `direct-unheld` path applies, after the adapter found an app it cannot coordinate with (see the head of
|
|
351
|
+
// this file). Every condition must hold; an evidence reader that is absent, throws or answers anything but
|
|
352
|
+
// `unlisted: true` gives none.
|
|
353
|
+
async function unheldPathApplies({ probe, pointer, store, unheldEvidence, platform }) {
|
|
354
|
+
if (probe.code !== undefined || pointer !== null) return false
|
|
355
|
+
if (platform !== 'darwin' && platform !== 'linux') return false
|
|
356
|
+
const { allocation } = store
|
|
357
|
+
if (store.vaultOrigin !== 'allocated' || store.managedVaultRoot !== true || !allocation || store.vaultRoot !== allocation.path || allocatedFolderState(allocation) !== 'same') return false
|
|
358
|
+
return readsUnlisted(unheldEvidence, store)
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
async function readsUnlisted(unheldEvidence, store) {
|
|
362
|
+
if (typeof unheldEvidence !== 'function') return false
|
|
363
|
+
try { return (await unheldEvidence({ vaultRoot: store.vaultRoot }))?.unlisted === true } catch { return false }
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
export function publishView(options = {}) {
|
|
367
|
+
return publishViewForOracleTests(options, PUBLICATION_PRIMITIVES)
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
export async function publishViewForOracleTests(options = {}, primitives = PUBLICATION_PRIMITIVES) {
|
|
268
371
|
const { preparedView, protocolId, expectedGeneration, recoveryStore: store, adapter, clock = () => new Date(), quietPeriodMs = 1500, exchangeOptions = {} } = options
|
|
372
|
+
// `unheldEvidence({ vaultRoot })` answers whether the app's own list names this vault (see the head of this file).
|
|
373
|
+
const { unheldEvidence = null, platform = process.platform } = options
|
|
374
|
+
const rules = { ...PUBLICATION_PRIMITIVES, ...primitives }
|
|
269
375
|
const seam = options[CRASH_INJECTION_TEST_SEAM] ?? null
|
|
270
376
|
const crash = (point) => { if (seam && seam.at === point) seam.halt(point) }
|
|
271
377
|
if (!store || typeof store.commitManifest !== 'function') throw new TypeError('publishView needs a recoveryStore')
|
|
272
378
|
if (!adapter || typeof adapter.probe !== 'function') throw new TypeError('publishView needs an editor coordination adapter')
|
|
273
379
|
if (expectedGeneration !== null && typeof expectedGeneration !== 'string') throw new TypeError('expectedGeneration must be a generation identity or null')
|
|
274
380
|
|
|
381
|
+
const budget = createCooperativeBudget(options.scheduling)
|
|
382
|
+
budget.check()
|
|
275
383
|
const releases = []
|
|
276
384
|
let journal = null
|
|
277
385
|
try {
|
|
@@ -280,22 +388,37 @@ export async function publishView(options = {}) {
|
|
|
280
388
|
// A release that could not be written (a full disk) is finished first.
|
|
281
389
|
for (const lockPath of [store.vaultLockPath, store.lockPath]) {
|
|
282
390
|
if (!unreleased.has(lockPath)) continue
|
|
283
|
-
try { unreleased.get(lockPath)() } catch (error) { refuse('state-unwritable', 'private publication state cannot be written; nothing in the vault was touched', { cause: error.code ?? String(error.message) }) }
|
|
391
|
+
try { unreleased.get(lockPath)() } catch (error) { if (!lockFolderGone(error)) refuse('state-unwritable', 'private publication state cannot be written; nothing in the vault was touched', { cause: error.code ?? String(error.message) }) }
|
|
284
392
|
unreleased.delete(lockPath)
|
|
285
393
|
}
|
|
286
394
|
// Two locks, the view's and then the vault's: a second view or a second
|
|
287
395
|
// workspace state pointed at the same vault refuses instead of racing.
|
|
288
396
|
const acquire = (lockPath, take, held) => {
|
|
289
397
|
try { releases.push([lockPath, take()]) } catch (error) {
|
|
398
|
+
if (error instanceof PublicationRefusal) throw error
|
|
290
399
|
if (error.code === 'EEXIST') refuse('publication-in-progress', `another publication ${held} holds the lock`)
|
|
291
400
|
refuse('state-unwritable', 'private publication state cannot be written; nothing in the vault was touched', { cause: error.code ?? String(error.message) })
|
|
292
401
|
}
|
|
293
402
|
}
|
|
294
403
|
acquire(store.lockPath, () => acquirePrivateLock(store.lockPath), 'of this view')
|
|
404
|
+
// A vault allocated for the view is still the folder it made, before anything is written in it (the vault lock
|
|
405
|
+
// included) and again once the lock is held: a store kept by a long-running service may outlive its folder. It is
|
|
406
|
+
// checked again after every wait on the app, before each unit and before the commit, so a folder replaced during a
|
|
407
|
+
// publication refuses it and is never reported current. (Only this account can replace it; the check and the
|
|
408
|
+
// write that follows it are still two steps.)
|
|
409
|
+
store.checkAllocatedVault?.()
|
|
295
410
|
acquire(store.vaultLockPath, () => acquireVaultLock(store), 'into this vault')
|
|
411
|
+
store.checkAllocatedVault?.()
|
|
412
|
+
await budget.checkpoint('recover', true)
|
|
413
|
+
store.checkAllocatedVault?.()
|
|
296
414
|
const recovered = recoverPublicationsLocked({ store, clock })
|
|
415
|
+
await budget.checkpoint('recovery-complete', true)
|
|
416
|
+
store.checkAllocatedVault?.()
|
|
297
417
|
const pointer = store.readCurrent()
|
|
298
418
|
if (pointer?.generationId === manifest.generationId && !pluginFilesDrifted(preparedView, store)) {
|
|
419
|
+
await options.beforeCommit?.()
|
|
420
|
+
budget.check()
|
|
421
|
+
store.checkAllocatedVault?.()
|
|
299
422
|
return { state: 'committed', alreadyCommitted: true, generationId: manifest.generationId, journalId: pointer.journalId, notes: [], retainedEdits: pointer.retained ?? [], lateWriters: [], recovered }
|
|
300
423
|
}
|
|
301
424
|
if ((pointer?.generationId ?? null) !== expectedGeneration) refuse('generation-mismatch', 'the committed generation is not the one this publication expects', { committed: pointer?.generationId ?? null })
|
|
@@ -303,8 +426,12 @@ export async function publishView(options = {}) {
|
|
|
303
426
|
// Path selection. An app with this vault open in several windows is refused as such: publishing through one of
|
|
304
427
|
// them would leave the others uncoordinated.
|
|
305
428
|
const probe = await adapter.probe({ vaultRoot: store.vaultRoot })
|
|
306
|
-
|
|
307
|
-
const
|
|
429
|
+
store.checkAllocatedVault?.()
|
|
430
|
+
const unheld = probe.state !== 'coordinated' && probe.state !== 'absent' && await unheldPathApplies({ probe, pointer, store, unheldEvidence, platform })
|
|
431
|
+
// The evidence took time to read: the folder is checked again.
|
|
432
|
+
if (unheld) store.checkAllocatedVault?.()
|
|
433
|
+
if (probe.state !== 'coordinated' && probe.state !== 'absent' && !unheld) refuse(probe.code === 'vault-open-in-several-windows' ? probe.code : 'editor-uncoordinated', `an Obsidian process may have this vault open and cannot be coordinated with: ${probe.reason}`)
|
|
434
|
+
const mode = probe.state === 'coordinated' ? 'in-app' : unheld ? 'direct-unheld' : 'direct'
|
|
308
435
|
const channel = mode === 'in-app' ? adapter : createDirectAdapter({ crashSeam: seam })
|
|
309
436
|
|
|
310
437
|
// Same volume, and an exchange that works on it.
|
|
@@ -329,13 +456,18 @@ export async function publishView(options = {}) {
|
|
|
329
456
|
let stagingDir = null
|
|
330
457
|
try {
|
|
331
458
|
stagingDir = store.stagingDir(journalId)
|
|
332
|
-
|
|
459
|
+
// On the `direct-unheld` path a replacement is never written, so no candidate is staged for one.
|
|
460
|
+
for (const unit of units.filter((item) => item.op === 'create' || (item.op === 'replace' && (mode !== 'direct-unheld' || rules.unheldWrites(item.op))))) {
|
|
461
|
+
await budget.checkpoint('stage-candidate')
|
|
462
|
+
store.checkAllocatedVault?.()
|
|
333
463
|
const existing = fs.lstatSync(path.join(store.vaultRoot, unit.path), { throwIfNoEntry: false })
|
|
334
464
|
unit.preparedPath = store.preparedPath(journalId, unit.unit)
|
|
335
465
|
stageCandidate(unit.preparedPath, unit.bytes, unit.mode ?? (existing?.isFile() ? existing.mode & 0o777 : 0o644), created)
|
|
336
466
|
// A replacement is exchanged, and the exchange leaves the displaced bytes at this path: it is in recovery.
|
|
337
467
|
unit.stagedPath = unit.op === 'replace' ? store.exchangeCandidatePath(journalId, unit.unit) : unit.preparedPath
|
|
338
468
|
}
|
|
469
|
+
await budget.checkpoint('staging-complete', true)
|
|
470
|
+
store.checkAllocatedVault?.()
|
|
339
471
|
syncPrivateDirectory(stagingDir)
|
|
340
472
|
const manifestFile = path.join(store.journalDir(journalId), 'manifest.json')
|
|
341
473
|
publishPrivateFile(manifestFile, manifestBytes)
|
|
@@ -350,25 +482,42 @@ export async function publishView(options = {}) {
|
|
|
350
482
|
// The journal now names every exchange candidate by path and digest. Only complete, fsynced files are moved
|
|
351
483
|
// in, so a file at an exchange path is never a partial candidate.
|
|
352
484
|
for (const unit of units.filter((item) => item.stagedPath && item.stagedPath !== item.preparedPath)) {
|
|
485
|
+
await budget.checkpoint('move-candidate')
|
|
486
|
+
store.checkAllocatedVault?.()
|
|
353
487
|
moveToExchangePath(store, journalId, unit, moved)
|
|
354
488
|
}
|
|
355
489
|
} catch (error) {
|
|
356
|
-
|
|
490
|
+
const stopped = rules.callerStoppedStaging(error)
|
|
491
|
+
// A refusal raised between two candidates (the publisher's own check found the vault's folder replaced or gone)
|
|
492
|
+
// is handed on under its own code too, and like a stop only after the cleanup below.
|
|
493
|
+
const refused = error instanceof PublicationRefusal && !stopped
|
|
494
|
+
if (refused && !rules.refusedStagingCleansUp(error)) throw error
|
|
357
495
|
// No payload has named any of these paths, so nothing was exchanged and they can only hold our own
|
|
358
496
|
// candidates. In staging they are removed by name. In recovery a file is removed only when its bytes are
|
|
359
497
|
// the candidate's; anything else stays for restart recovery, which the journal header points at it. Only
|
|
360
498
|
// empty directories are removed, so a failed staging leaves nothing that looks like an unfinished unit.
|
|
499
|
+
// Each file is cleaned up on its own, so one that cannot be removed (EBUSY, EPERM, a volume gone) leaves the
|
|
500
|
+
// others removed; the first such failure is kept.
|
|
501
|
+
let cleanupFailure = null
|
|
502
|
+
const cleanUp = (operation) => { try { operation() } catch (failure) { cleanupFailure ??= failure } }
|
|
361
503
|
for (const unit of moved) {
|
|
362
|
-
if (readFileDigest(unit.stagedPath) === unit.candidateDigest) fs.rmSync(unit.stagedPath, { force: true })
|
|
504
|
+
cleanUp(() => { if (readFileDigest(unit.stagedPath) === unit.candidateDigest) fs.rmSync(unit.stagedPath, { force: true }) })
|
|
363
505
|
try { fs.rmdirSync(path.dirname(unit.stagedPath)) } catch { /* not empty: left for restart recovery */ }
|
|
364
506
|
}
|
|
365
|
-
for (const file of created) fs.rmSync(file, { force: true })
|
|
507
|
+
for (const file of created) cleanUp(() => fs.rmSync(file, { force: true }))
|
|
366
508
|
for (const directory of [stagingDir, store.unitsRoot(journalId)]) if (directory) try { fs.rmdirSync(directory) } catch { /* absent, or not empty */ }
|
|
367
|
-
|
|
509
|
+
// The caller stopped the publication between two candidates: nothing failed to stage, and its reason (a
|
|
510
|
+
// setting that changed during the tick, `mixed-read`, or a cancellation) is the one reported. So is a refusal's.
|
|
511
|
+
// A cleanup that failed does not replace either: it is named in their detail as `cleanupCause`, and what it could
|
|
512
|
+
// not remove stays where it is.
|
|
513
|
+
if (stopped || refused) throw withCleanupCause(error, cleanupFailure)
|
|
514
|
+
refuse('staging-failed', 'the candidates could not be staged; nothing in the vault was touched', { cause: error.code ?? String(error.message), ...cleanupCauseOf(cleanupFailure) })
|
|
368
515
|
}
|
|
516
|
+
await budget.checkpoint('journal-and-moves-complete', true)
|
|
517
|
+
store.checkAllocatedVault?.()
|
|
369
518
|
crash('after-staging')
|
|
370
519
|
|
|
371
|
-
const context = { store, journal, journalId, channel, clock, crash, mode }
|
|
520
|
+
const context = { store, journal, journalId, channel, clock, crash, mode, rules }
|
|
372
521
|
const results = []
|
|
373
522
|
// Kept notes are compared first, several at a time; one that holds exactly its candidate bytes is unchanged.
|
|
374
523
|
const unchanged = await unchangedKeepUnits(units, store)
|
|
@@ -379,15 +528,24 @@ export async function publishView(options = {}) {
|
|
|
379
528
|
// passed since the last reading. An app that starts after a reading and
|
|
380
529
|
// before the next is not seen; that window is at most two seconds plus
|
|
381
530
|
// one note's publication.
|
|
531
|
+
// The `direct-unheld` path reads its evidence on the same schedule, and once a reading gives none, every remaining
|
|
532
|
+
// unit refuses, a kept one included, so nothing is committed.
|
|
382
533
|
let lastProbe = null
|
|
383
534
|
for (const unit of units) {
|
|
535
|
+
await budget.checkpoint('publish-unit')
|
|
536
|
+
store.checkAllocatedVault?.()
|
|
384
537
|
if (mode === 'direct' && (lastProbe === null || Date.now() - lastProbe > 2000)) {
|
|
385
538
|
lastProbe = Date.now()
|
|
386
539
|
const again = await adapter.probe({ vaultRoot: store.vaultRoot })
|
|
387
540
|
if (again.state !== 'absent') context.uncoordinated = again.reason
|
|
388
541
|
}
|
|
542
|
+
if (mode === 'direct-unheld' && !context.uncoordinated && (lastProbe === null || Date.now() - lastProbe > 2000)) {
|
|
543
|
+
lastProbe = Date.now()
|
|
544
|
+
if (!(await readsUnlisted(unheldEvidence, store))) context.uncoordinated = 'the vault list of the app no longer shows, positively, that no Obsidian lists this vault'
|
|
545
|
+
}
|
|
546
|
+
store.checkAllocatedVault?.()
|
|
389
547
|
if (unit.op === 'keep' && unchanged.has(unit.path)) {
|
|
390
|
-
results.push({ path: unit.path, kind: unit.kind, op: unit.op, outcome: context.uncoordinated ? 'editor-uncoordinated' : 'unchanged', blocking:
|
|
548
|
+
results.push({ path: unit.path, kind: unit.kind, op: unit.op, outcome: context.uncoordinated ? 'editor-uncoordinated' : 'unchanged', blocking: mode === 'direct-unheld' && Boolean(context.uncoordinated) })
|
|
391
549
|
continue
|
|
392
550
|
}
|
|
393
551
|
try { results.push(await publishUnit(unit, context)) } catch (error) {
|
|
@@ -398,11 +556,13 @@ export async function publishView(options = {}) {
|
|
|
398
556
|
}
|
|
399
557
|
}
|
|
400
558
|
|
|
559
|
+
await budget.checkpoint('publication-complete', true)
|
|
560
|
+
store.checkAllocatedVault?.()
|
|
401
561
|
const blocking = results.filter((result) => result.blocking)
|
|
402
562
|
const retainedEdits = results.filter((result) => result.retained).map((result) => result.retained)
|
|
403
563
|
// Notes kept earlier stay surfaced until they leave the vault or come back into a view.
|
|
404
564
|
const base = { journalId, generationId: manifest.generationId, mode, notes: results.map(({ retained, ...rest }) => rest), retainedEdits, recovered }
|
|
405
|
-
if (blocking.length > 0) {
|
|
565
|
+
if (blocking.length > 0 || (mode === 'direct-unheld' && context.uncoordinated)) {
|
|
406
566
|
const lateWriters = recheckDisplacedFiles({ store, journalIds: [journalId], clock })
|
|
407
567
|
return { ...base, state: 'updating', lateWriters }
|
|
408
568
|
}
|
|
@@ -414,8 +574,19 @@ export async function publishView(options = {}) {
|
|
|
414
574
|
journal.append({ step: 'verify', outcome: 'conflict', state: 'verifying', notePath: finding.notePath, beforeDigest: finding.digestAtMove, afterDigest: finding.observedDigest, recoveryRef: finding.displacedRef,
|
|
415
575
|
detail: { unit: finding.unit, code: 'late-writer-captured', objectRef: finding.objectRef } })
|
|
416
576
|
}
|
|
577
|
+
await budget.checkpoint('verify-before-commit', true)
|
|
578
|
+
// The resident engine revalidates the selected source pins here, while
|
|
579
|
+
// publication still owns its locks. Its check may yield; no durability
|
|
580
|
+
// unit or committed pointer is half-written across that wait. It comes
|
|
581
|
+
// before the entry that says the publication settled: restart recovery
|
|
582
|
+
// commits a journal that holds that entry, so a refusal after it would be
|
|
583
|
+
// committed by the next publication of this view.
|
|
584
|
+
await options.beforeCommit?.()
|
|
585
|
+
budget.check()
|
|
586
|
+
store.checkAllocatedVault?.()
|
|
417
587
|
journal.append({ step: 'verify', outcome: 'ok', state: 'verifying', detail: { settled: true, retained: retainedEdits } })
|
|
418
588
|
crash('before-manifest-commit')
|
|
589
|
+
store.checkAllocatedVault?.()
|
|
419
590
|
store.commitManifest({ manifestBytes, generationId: manifest.generationId, journalId, retained: retainedEdits, committedAt: iso(clock) })
|
|
420
591
|
crash('after-manifest-pointer')
|
|
421
592
|
journal.append({ step: 'manifest-commit', outcome: 'ok', state: 'committed', detail: { manifestDigest: sha256Digest(manifestBytes) } })
|
|
@@ -424,11 +595,13 @@ export async function publishView(options = {}) {
|
|
|
424
595
|
return { ...base, state: 'committed', lateWriters }
|
|
425
596
|
} catch (error) {
|
|
426
597
|
if (!(error instanceof PublicationRefusal)) {
|
|
427
|
-
if (journal) try { journal.append({ step: 'verify', outcome: 'failed', state: 'failed', detail: { code:
|
|
598
|
+
if (journal) try { journal.append({ step: 'verify', outcome: 'failed', state: 'failed', detail: { code: rules.journalFailureCode(error), message: String(error.code ?? error.message).slice(0, 200) } }) } catch { /* the journal itself cannot be written */ }
|
|
428
599
|
throw error
|
|
429
600
|
}
|
|
430
601
|
return { state: 'refused', refusal: { code: error.code, message: error.message, detail: error.detail }, notes: [], retainedEdits: [], lateWriters: [] }
|
|
431
602
|
} finally {
|
|
603
|
+
// A release that fails is kept and tried again before the next publication of that lock, even when the lock's folder
|
|
604
|
+
// is not there now: a vault moved away keeps its ticket and may be put back.
|
|
432
605
|
for (const [lockPath, release] of releases.reverse()) try { release() } catch { unreleased.set(lockPath, release) }
|
|
433
606
|
}
|
|
434
607
|
}
|
|
@@ -487,9 +660,15 @@ async function publishOneUnit(unit, context) {
|
|
|
487
660
|
const { store, journal, journalId, clock } = context
|
|
488
661
|
const note = path.join(store.vaultRoot, unit.path)
|
|
489
662
|
const outcome = (code, extra = {}) => ({ path: unit.path, kind: unit.kind, op: unit.op, outcome: code, blocking: false, ...extra })
|
|
490
|
-
|
|
663
|
+
// On the `direct-unheld` path a unit after a failed reading refuses, whatever it would have done, so the run says why
|
|
664
|
+
// it stopped.
|
|
665
|
+
if (unit.op === 'leave-absent') return context.mode === 'direct-unheld' && context.uncoordinated ? outcome('editor-uncoordinated', { blocking: true }) : outcome('left-absent')
|
|
491
666
|
const observe = (bytes) => store.retainObject(bytes)
|
|
492
|
-
if (context.uncoordinated) return outcome('editor-uncoordinated', { blocking: unit.op !== 'keep' })
|
|
667
|
+
if (context.uncoordinated) return outcome('editor-uncoordinated', { blocking: unit.op !== 'keep' || context.mode === 'direct-unheld' })
|
|
668
|
+
// The `direct-unheld` path creates files and nothing else. A unit planned as a replacement or a removal has no
|
|
669
|
+
// candidate staged, and is refused before its file is even read. (A kept unit may still become a create; a settings
|
|
670
|
+
// unit is decided in planSettings.)
|
|
671
|
+
if (context.mode === 'direct-unheld' && unit.op !== 'keep' && unit.op !== 'settings' && !context.rules.unheldWrites(unit.op)) return outcome('editor-uncoordinated', { blocking: true })
|
|
493
672
|
// A settings path that cannot be written safely (a symlinked `.obsidian`,
|
|
494
673
|
// say) is reported and left alone; it must not keep every note from
|
|
495
674
|
// converging while the person repairs it. Notes and attachments block.
|
|
@@ -532,6 +711,11 @@ async function publishOneUnit(unit, context) {
|
|
|
532
711
|
if (plan.stagedPath) retire(plan, context)
|
|
533
712
|
return outcome('already-current')
|
|
534
713
|
}
|
|
714
|
+
// Whatever a unit became on the way here, the `direct-unheld` path writes nothing below this point but a create.
|
|
715
|
+
if (context.mode === 'direct-unheld' && !context.rules.unheldWrites(plan.op)) {
|
|
716
|
+
if (plan.stagedPath) retire(plan, context)
|
|
717
|
+
return outcome('editor-uncoordinated', { blocking: true })
|
|
718
|
+
}
|
|
535
719
|
|
|
536
720
|
if (unit.path === PLUGIN_DATA_PATH && plan.op !== 'remove') {
|
|
537
721
|
const privacy = ensurePrivateVaultRoot(store)
|
|
@@ -706,6 +890,11 @@ function planSettings(unit, context) {
|
|
|
706
890
|
if (Object.hasOwn(unit, 'expectedDigest') && (existing === null ? null : sha256Digest(existing)) !== unit.expectedDigest) {
|
|
707
891
|
return { path: unit.path, kind: 'settings', op: 'settings', outcome: 'settings-changed', blocking: true }
|
|
708
892
|
}
|
|
893
|
+
// On the `direct-unheld` path a settings file that is there is never written over, and no candidate is staged for it.
|
|
894
|
+
// Only one that already holds exactly what the policy asks (Atelier's own, from an interrupted run) is left as it is.
|
|
895
|
+
if (context.mode === 'direct-unheld' && existing !== null && !context.rules.unheldWrites('replace') && !holdsPolicy(unit.path, existing)) {
|
|
896
|
+
return { path: unit.path, kind: 'settings', op: 'settings', outcome: 'editor-uncoordinated', blocking: true }
|
|
897
|
+
}
|
|
709
898
|
let file
|
|
710
899
|
try { file = preparePolicySettingsFile(unit.path, existing) } catch (error) {
|
|
711
900
|
if (error instanceof ObsidianContractRefusal) return { path: unit.path, kind: 'settings', op: 'settings', outcome: 'settings-invalid', blocking: false }
|
|
@@ -720,6 +909,13 @@ function planSettings(unit, context) {
|
|
|
720
909
|
return plan
|
|
721
910
|
}
|
|
722
911
|
|
|
912
|
+
function holdsPolicy(settingsPath, bytes) {
|
|
913
|
+
try { return preparePolicySettingsFile(settingsPath, bytes).bytes.equals(bytes) } catch (error) {
|
|
914
|
+
if (error instanceof ObsidianContractRefusal) return false
|
|
915
|
+
throw error
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
|
|
723
919
|
function verify(unit, plan, { store, journal }, result) {
|
|
724
920
|
const bytes = readNote(path.join(store.vaultRoot, unit.path))
|
|
725
921
|
const digest = bytes === null ? null : sha256Digest(bytes)
|
|
@@ -109,13 +109,14 @@ const currentUid = () => (typeof process.getuid === 'function' ? process.getuid(
|
|
|
109
109
|
// { ok: false, code, message }. The user-data directory and the file must be
|
|
110
110
|
// this user's own, a real directory and a regular file; the file must be a
|
|
111
111
|
// JSON object whose `vaults`, when present, is an object. A missing directory
|
|
112
|
-
// or file means Obsidian has not run on this account
|
|
112
|
+
// or file means Obsidian has not run on this account, or has shown only its
|
|
113
|
+
// starter window, which writes no list: that refusal names `userDataDir`.
|
|
113
114
|
export function readObsidianSettings({ userDataDir, uid = currentUid() } = {}) {
|
|
114
115
|
if (typeof userDataDir !== 'string' || !path.isAbsolute(userDataDir)) return refused('obsidian-settings-location-unknown', 'where Obsidian keeps its settings on this system is not known')
|
|
115
116
|
const owned = (stat) => uid === null || stat.uid === uid
|
|
116
117
|
let directory
|
|
117
118
|
try { directory = fs.lstatSync(userDataDir) } catch (error) {
|
|
118
|
-
if (error.code === 'ENOENT') return refused('obsidian-settings-missing', 'Obsidian has no settings directory on this account; it has not run here')
|
|
119
|
+
if (error.code === 'ENOENT') return { ...refused('obsidian-settings-missing', 'Obsidian has no settings directory on this account; it has not run here'), userDataDir }
|
|
119
120
|
return refused('obsidian-settings-unreadable', 'the Obsidian settings directory cannot be read')
|
|
120
121
|
}
|
|
121
122
|
if (directory.isSymbolicLink() || !directory.isDirectory()) return refused('obsidian-settings-unsafe', 'the Obsidian settings directory is a link or not a directory')
|
|
@@ -123,7 +124,7 @@ export function readObsidianSettings({ userDataDir, uid = currentUid() } = {}) {
|
|
|
123
124
|
const file = path.join(userDataDir, OBSIDIAN_SETTINGS_FILE)
|
|
124
125
|
let leaf
|
|
125
126
|
try { leaf = fs.lstatSync(file) } catch (error) {
|
|
126
|
-
if (error.code === 'ENOENT') return refused('obsidian-settings-missing', 'Obsidian has no settings file on this account; it has not run here')
|
|
127
|
+
if (error.code === 'ENOENT') return { ...refused('obsidian-settings-missing', 'Obsidian has no settings file on this account; it has not run here'), userDataDir }
|
|
127
128
|
return refused('obsidian-settings-unreadable', 'the Obsidian settings file cannot be read')
|
|
128
129
|
}
|
|
129
130
|
if (leaf.isSymbolicLink() || !leaf.isFile()) return refused('obsidian-settings-unsafe', 'the Obsidian settings file is a link or not a regular file')
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
// Recovery state for Obsidian publication: the immutable store, the durable
|
|
2
2
|
// journal, restart recovery and the late-writer re-check.
|
|
3
|
-
export {
|
|
3
|
+
export {
|
|
4
|
+
EXCHANGE_CANDIDATE_NAME, LATE_EXCHANGE_CANDIDATE_NAME, PublicationRefusal, VAULT_ALLOCATION_SCHEMA, VAULT_LOCK_DIRECTORY, VAULT_ORIGINS, acquireVaultLock, allocatedFolderState, assertAllocatedFolder, allocationFile, folderIdentity,
|
|
5
|
+
createRecoveryStore, hasCommittedGeneration, legacyVaultRoot, readFileDigest, readVaultAllocation, sha256Digest, validateVaultAllocation, vaultRootFor, writeVaultAllocation,
|
|
6
|
+
} from './store.mjs'
|
|
4
7
|
export { JOURNAL_SCHEMA, createJournal, journalDetail, listJournals, newJournalId } from './journal.mjs'
|
|
5
8
|
export { classifyCandidateFile, namedCandidates, publishedSinceCommit, reconcileUnit, recoverPublications, retireStagedFile } from './restart.mjs'
|
|
6
9
|
export { recheckDisplacedFiles } from './late-writer.mjs'
|