@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
|
@@ -1,37 +1,414 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createHash } from 'node:crypto'
|
|
2
|
+
import {
|
|
3
|
+
FOLDER_NAME_BYTES, MIN_NAME_BYTES, QUALIFIER_STEM_BYTES, collisionKey, cutName, extensionOf, fileNameParts, fileStemOf, folderNameOf, identitySuffix,
|
|
4
|
+
isReadableVaultPath, joinFileName, notePath, noteNameOf, qualifierIdOf, vaultName,
|
|
5
|
+
} from '../contracts.mjs'
|
|
2
6
|
import { refuse } from './byte-lens.mjs'
|
|
3
7
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
//
|
|
8
|
+
export { collisionKey }
|
|
9
|
+
|
|
10
|
+
// Vault paths of layout 2 (see "Vault layout" in docs/obsidian-contract.md):
|
|
11
|
+
//
|
|
12
|
+
// <repository folder>/<source directory>/<title>.md a Markdown note
|
|
13
|
+
// <repository folder>/<source directory>/<file name> a wrapped file
|
|
14
|
+
// <repository folder>/<source directory>/<file name>.md its note
|
|
15
|
+
// <repository folder>/<source directory>/<file name> an embedded asset
|
|
16
|
+
//
|
|
17
|
+
// Each view allocates among its own notes and embedded assets only, seeded
|
|
18
|
+
// from what its own prior generation published. A path stays while its note
|
|
19
|
+
// is in the view; a note that leaves the view releases its path there; a
|
|
20
|
+
// note of another view, or one that was never in this one, never causes a
|
|
21
|
+
// qualifier. One identity may have different paths in different views.
|
|
22
|
+
// Paths the caller names as occupied (files held for an edit, which stay in
|
|
23
|
+
// the vault) are never taken.
|
|
24
|
+
//
|
|
25
|
+
// A source that does not fit is laid out anyway, and reported: a folder chain
|
|
26
|
+
// too long for the path budget keeps a readable prefix and a short stable id,
|
|
27
|
+
// and a source folder that meets a file of the same name is qualified with
|
|
28
|
+
// one. Each is a diagnostic naming the note.
|
|
7
29
|
//
|
|
8
|
-
// The registry
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
30
|
+
// The persistent path registry holds each view's last allocation, for a
|
|
31
|
+
// selection resolved without the view's generation: { schema, workspaceId,
|
|
32
|
+
// layout: 2, entries: [], assets: [], views: { <scopeId>: { entries, assets }
|
|
33
|
+
// } }, entries { repoId, nodeId, path[, attachment] }, assets { repoId,
|
|
34
|
+
// assetPath, path }. The top-level `entries` and `assets` stay empty; they
|
|
35
|
+
// held the workspace-wide allocation of an earlier revision. A registry
|
|
36
|
+
// without `layout: 2` is a layout 1 registry and holds no view.
|
|
14
37
|
|
|
15
38
|
export const PATH_REGISTRY_SCHEMA = 'atelier-obsidian-path-registry/v1'
|
|
16
|
-
|
|
17
|
-
const NOTE_PATH = /^notes\/([^/\\\u0000]+)--([0-9a-f]{12,64})\.md$/
|
|
18
|
-
const MAX_COMPONENT_BYTES = 255
|
|
19
|
-
const MAX_SUFFIX = 64
|
|
20
|
-
// Bytes left for the readable title once the longest suffix, the separator and
|
|
21
|
-
// the extension are reserved, so lengthening a suffix never overflows a name.
|
|
22
|
-
const TITLE_BYTE_BUDGET = MAX_COMPONENT_BYTES - '--'.length - MAX_SUFFIX - '.md'.length
|
|
39
|
+
export const PATH_REGISTRY_LAYOUT = 2
|
|
23
40
|
export const DEFAULT_MAX_FULL_PATH_BYTES = 1024
|
|
41
|
+
const MAX_COMPONENT_BYTES = 255
|
|
42
|
+
const MAX_QUALIFIER_ID = 16
|
|
43
|
+
// What a folder chain leaves for the name after it, beyond the separator,
|
|
44
|
+
// the shortest title and the extensions: nothing at first, and room for both
|
|
45
|
+
// qualifiers at their longest when the name had to be qualified.
|
|
46
|
+
const QUALIFIED_NAME_BYTES = (QUALIFIER_STEM_BYTES + 3) + (MAX_QUALIFIER_ID + 3)
|
|
47
|
+
// The folders layout 1 kept every note and file in. An upgrade retires their
|
|
48
|
+
// files but leaves the folders, so a repository folder that differs from one
|
|
49
|
+
// only in case or normalization, which a case-insensitive file system would
|
|
50
|
+
// merge with it, is told apart; a repository named exactly `notes` uses it.
|
|
51
|
+
const LAYOUT_1_FOLDERS = ['notes', 'attachments']
|
|
24
52
|
|
|
25
53
|
const compare = (left, right) => (left < right ? -1 : left > right ? 1 : 0)
|
|
26
54
|
export const identityKey = (repoId, nodeId) => `${repoId}\u0000${nodeId}`
|
|
55
|
+
const assetKey = (repoId, assetPath) => `${repoId}\u0000${assetPath}`
|
|
56
|
+
const bytesOf = (value) => Buffer.byteLength(value, 'utf8')
|
|
57
|
+
const baseOf = (filePath) => filePath.slice(filePath.lastIndexOf('/') + 1)
|
|
58
|
+
const byIdentity = (left, right) => compare(left.repoId, right.repoId) || compare(left.nodeId, right.nodeId)
|
|
59
|
+
const byAsset = (left, right) => compare(left.repoId, right.repoId) || compare(left.assetPath, right.assetPath)
|
|
60
|
+
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
// The registry document
|
|
63
|
+
// ---------------------------------------------------------------------------
|
|
27
64
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
export function collisionKey(value) {
|
|
31
|
-
return value.normalize('NFKC').toUpperCase().toLowerCase().normalize('NFKC')
|
|
65
|
+
export function emptyPathRegistry(workspaceId) {
|
|
66
|
+
return { schema: PATH_REGISTRY_SCHEMA, workspaceId, layout: PATH_REGISTRY_LAYOUT, entries: [], assets: [], views: {} }
|
|
32
67
|
}
|
|
33
68
|
|
|
34
|
-
|
|
69
|
+
const isPlainObject = (value) => value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
70
|
+
|
|
71
|
+
// The views of a registry document, or {} for none (no registry, a layout 1
|
|
72
|
+
// registry, or one written before views). A document of another shape or
|
|
73
|
+
// another workspace refuses.
|
|
74
|
+
export function viewsOfRegistry(registry, workspaceId) {
|
|
75
|
+
if (registry === undefined || registry === null) return {}
|
|
76
|
+
if (registry.schema !== PATH_REGISTRY_SCHEMA || !Array.isArray(registry.entries)) refuse('invalid-path-registry', 'path registry has an unknown shape')
|
|
77
|
+
if (registry.workspaceId !== workspaceId) refuse('invalid-path-registry', 'path registry belongs to another workspace')
|
|
78
|
+
if (registry.layout !== PATH_REGISTRY_LAYOUT || registry.views === undefined) return {}
|
|
79
|
+
if (!isPlainObject(registry.views)) refuse('invalid-path-registry', 'path registry has an unknown shape')
|
|
80
|
+
for (const section of Object.values(registry.views)) {
|
|
81
|
+
if (!isPlainObject(section) || !Array.isArray(section.entries) || !Array.isArray(section.assets)) refuse('invalid-path-registry', 'path registry has an unknown shape')
|
|
82
|
+
}
|
|
83
|
+
return registry.views
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// The registry document with one view's allocation replaced. With `keep`, the
|
|
87
|
+
// sections of views it does not name (views no longer maintained) are dropped;
|
|
88
|
+
// this view's own section is always kept.
|
|
89
|
+
export function withViewSection(registry, { workspaceId, scopeId, section, keep = null }) {
|
|
90
|
+
const kept = Object.entries(viewsOfRegistry(registry, workspaceId)).filter(([id]) => keep === null || keep.includes(id))
|
|
91
|
+
return { ...emptyPathRegistry(workspaceId), views: { ...Object.fromEntries(kept), [scopeId]: section } }
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
// Allocation of one view
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
// Everything allocated so far: files and folders by collision key, so a
|
|
99
|
+
// newcomer is compared the way a case- and normalization-insensitive file
|
|
100
|
+
// system compares, and the one spelling every folder has.
|
|
101
|
+
function newState() {
|
|
102
|
+
return { byIdentity: new Map(), byAsset: new Map(), files: new Map(), folders: new Map(), repoFolders: new Map(), repoFolderOwners: new Map() }
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function claimFolders(state, filePath) {
|
|
106
|
+
const parts = filePath.split('/').slice(0, -1)
|
|
107
|
+
for (let index = 1; index <= parts.length; index += 1) {
|
|
108
|
+
const folder = parts.slice(0, index).join('/')
|
|
109
|
+
const key = collisionKey(folder)
|
|
110
|
+
const spelled = state.folders.get(key)
|
|
111
|
+
if (spelled === undefined) {
|
|
112
|
+
if (state.files.has(key)) refuse('path-collision', 'a folder would take the name of an allocated file')
|
|
113
|
+
state.folders.set(key, folder)
|
|
114
|
+
} else if (spelled !== folder) {
|
|
115
|
+
refuse('invalid-path-registry', 'the published paths spell one folder two ways')
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function claimFile(state, filePath, owner) {
|
|
121
|
+
const key = collisionKey(filePath)
|
|
122
|
+
if (state.files.has(key) || state.folders.has(key)) refuse('path-collision', 'the published paths give one path to more than one file')
|
|
123
|
+
claimFolders(state, filePath)
|
|
124
|
+
state.files.set(key, owner)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function claimRepositoryFolder(state, repoId, folder) {
|
|
128
|
+
const known = state.repoFolders.get(repoId)
|
|
129
|
+
if (known !== undefined) {
|
|
130
|
+
if (known !== folder) refuse('invalid-path-registry', 'the published paths give one repository two folders')
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
if (state.repoFolderOwners.has(collisionKey(folder))) refuse('invalid-path-registry', 'the published paths give two repositories one folder')
|
|
134
|
+
state.repoFolders.set(repoId, folder)
|
|
135
|
+
state.repoFolderOwners.set(collisionKey(folder), repoId)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Whether `filePath` could be claimed as spelled, without claiming it.
|
|
139
|
+
function claimable(state, filePath) {
|
|
140
|
+
const key = collisionKey(filePath)
|
|
141
|
+
if (state.files.has(key) || state.folders.has(key)) return false
|
|
142
|
+
const parts = filePath.split('/').slice(0, -1)
|
|
143
|
+
for (let index = 1; index <= parts.length; index += 1) {
|
|
144
|
+
const folder = parts.slice(0, index).join('/')
|
|
145
|
+
const spelled = state.folders.get(collisionKey(folder))
|
|
146
|
+
if (spelled === undefined ? state.files.has(collisionKey(folder)) : spelled !== folder) return false
|
|
147
|
+
}
|
|
148
|
+
return true
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// How many bytes the longest of `paths` is over a limit: 255 bytes a name, and
|
|
152
|
+
// `maxFullPathBytes` with the vault root and its separator.
|
|
153
|
+
function overflowOf(paths, { vaultRootBytes, maxFullPathBytes }) {
|
|
154
|
+
return Math.max(0, ...paths.map((filePath) => Math.max(bytesOf(baseOf(filePath)) - MAX_COMPONENT_BYTES, vaultRootBytes + 1 + bytesOf(filePath) - maxFullPathBytes)))
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// What a view's prior generation published, taken as it is for every note and
|
|
158
|
+
// asset still in the view. A path that no longer fits the limits (a vault root
|
|
159
|
+
// that grew) is allocated again. `published.notes` are { repoId, nodeId, path[,
|
|
160
|
+
// attachment] }, `published.assets` { repoId, assetPath, path }.
|
|
161
|
+
function seed(state, published, { nodeKeys, assetKeys, limits }) {
|
|
162
|
+
for (const note of [...published.notes].sort(byIdentity)) {
|
|
163
|
+
const wellFormed = typeof note?.repoId === 'string' && note.repoId !== '' && typeof note.nodeId === 'string' && note.nodeId !== ''
|
|
164
|
+
&& isReadableVaultPath(note.path) && note.path.endsWith('.md') && note.path.includes('/')
|
|
165
|
+
&& (note.attachment === undefined || (isReadableVaultPath(note.attachment) && note.path === `${note.attachment}.md`))
|
|
166
|
+
if (!wellFormed) refuse('invalid-path-registry', 'a published path is malformed')
|
|
167
|
+
const key = identityKey(note.repoId, note.nodeId)
|
|
168
|
+
if (state.byIdentity.has(key)) refuse('duplicate-identity', 'the published paths repeat a canonical identity')
|
|
169
|
+
const paths = [note.path, ...(note.attachment === undefined ? [] : [note.attachment])]
|
|
170
|
+
if (!nodeKeys.has(key) || overflowOf(paths, limits) > 0) continue
|
|
171
|
+
claimRepositoryFolder(state, note.repoId, note.path.split('/')[0])
|
|
172
|
+
for (const filePath of paths) claimFile(state, filePath, key)
|
|
173
|
+
state.byIdentity.set(key, { path: note.path, ...(note.attachment === undefined ? {} : { attachment: note.attachment }) })
|
|
174
|
+
}
|
|
175
|
+
for (const asset of [...published.assets].sort(byAsset)) {
|
|
176
|
+
const wellFormed = typeof asset?.repoId === 'string' && asset.repoId !== '' && typeof asset.assetPath === 'string' && asset.assetPath !== ''
|
|
177
|
+
&& isReadableVaultPath(asset.path) && asset.path.includes('/')
|
|
178
|
+
if (!wellFormed) refuse('invalid-path-registry', 'a published path is malformed')
|
|
179
|
+
const key = assetKey(asset.repoId, asset.assetPath)
|
|
180
|
+
if (state.byAsset.has(key)) refuse('duplicate-identity', 'the published paths repeat an asset')
|
|
181
|
+
if (!assetKeys.has(key) || overflowOf([asset.path], limits) > 0) continue
|
|
182
|
+
claimRepositoryFolder(state, asset.repoId, asset.path.split('/')[0])
|
|
183
|
+
claimFile(state, asset.path, `asset\u0000${key}`)
|
|
184
|
+
state.byAsset.set(key, asset.path)
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// The folder of a repository: its identity made safe, or, when that name is
|
|
189
|
+
// taken (by another repository of the view, a file, or a folder spelled
|
|
190
|
+
// another way, such as the `notes` folder of a held layout 1 file), the same
|
|
191
|
+
// with a short stable id.
|
|
192
|
+
function repositoryFolder(state, repoId) {
|
|
193
|
+
const known = state.repoFolders.get(repoId)
|
|
194
|
+
if (known !== undefined) return known
|
|
195
|
+
const plain = folderNameOf(repoId)
|
|
196
|
+
const hex = createHash('sha256').update(repoId).digest('hex')
|
|
197
|
+
for (let length = 0; length <= MAX_QUALIFIER_ID; length += length === 0 ? 6 : 2) {
|
|
198
|
+
const candidate = length === 0 ? plain : `${plain} (${hex.slice(0, length)})`
|
|
199
|
+
const key = collisionKey(candidate)
|
|
200
|
+
if (!state.repoFolderOwners.has(key) && !state.files.has(key) && (state.folders.get(key) ?? candidate) === candidate) return candidate
|
|
201
|
+
}
|
|
202
|
+
return refuse('path-collision', 'unable to allocate a distinct repository folder')
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// The short stable id of a source folder: hexadecimal characters of the
|
|
206
|
+
// SHA-256 of the repository identity and the folder's source path.
|
|
207
|
+
const folderIdOf = (repoId, sourceFolder, length = 6) => createHash('sha256').update(`${repoId}\u0000${sourceFolder}`).digest('hex').slice(0, length)
|
|
208
|
+
|
|
209
|
+
// The folder a source directory mirrors to. Each segment is the source
|
|
210
|
+
// folder's name made safe, spelled as a folder already allocated under the
|
|
211
|
+
// same parent is spelled when the two differ only in case or normalization; a
|
|
212
|
+
// segment that meets an allocated file carries the id of its source folder. A
|
|
213
|
+
// chain that leaves less than `reserve` bytes for the name keeps as many
|
|
214
|
+
// leading folders as fit, and its last kept folder, cut, carries the id of the
|
|
215
|
+
// whole source directory; when not even that fits, the note sits directly in
|
|
216
|
+
// its repository's folder.
|
|
217
|
+
function mirroredFolder(state, { repoFolder, repoId, sourcePath, limits, reserve }) {
|
|
218
|
+
const sourceSegments = sourcePath.split('/').slice(0, -1)
|
|
219
|
+
const sourceOf = (count) => sourceSegments.slice(0, count).join('/')
|
|
220
|
+
// One segment under `folder`: its spelling, qualified when it meets a file.
|
|
221
|
+
const under = (folder, segment, source) => {
|
|
222
|
+
let candidate = `${folder}/${segment}`
|
|
223
|
+
let qualified = false
|
|
224
|
+
for (let length = 6; state.files.has(collisionKey(candidate)); length += 2) {
|
|
225
|
+
if (length > MAX_QUALIFIER_ID) refuse('path-collision', 'unable to allocate a distinct folder')
|
|
226
|
+
qualified = true
|
|
227
|
+
candidate = `${folder}/${cutName(segment, FOLDER_NAME_BYTES - (length + 3))} (${folderIdOf(repoId, source, length)})`
|
|
228
|
+
}
|
|
229
|
+
return { folder: state.folders.get(collisionKey(candidate)) ?? candidate, qualified }
|
|
230
|
+
}
|
|
231
|
+
const budget = limits.maxFullPathBytes - limits.vaultRootBytes - 1 - reserve
|
|
232
|
+
let folder = repoFolder
|
|
233
|
+
let qualified = false
|
|
234
|
+
const walked = [repoFolder]
|
|
235
|
+
sourceSegments.forEach((segment, index) => {
|
|
236
|
+
const next = under(folder, folderNameOf(segment), sourceOf(index + 1))
|
|
237
|
+
folder = next.folder
|
|
238
|
+
qualified ||= next.qualified
|
|
239
|
+
walked.push(folder)
|
|
240
|
+
})
|
|
241
|
+
if (bytesOf(folder) <= budget) return { folder, shortened: false, qualified }
|
|
242
|
+
// Too long: the longest walked prefix that leaves room for a cut folder carrying the id of the whole directory.
|
|
243
|
+
const suffix = ` (${folderIdOf(repoId, sourceOf(sourceSegments.length))})`
|
|
244
|
+
for (let count = sourceSegments.length - 1; count >= 0; count -= 1) {
|
|
245
|
+
const room = Math.min(budget - bytesOf(walked[count]) - 1, FOLDER_NAME_BYTES) - bytesOf(suffix)
|
|
246
|
+
const head = room > 0 ? cutName(folderNameOf(sourceSegments[count]), room) : ''
|
|
247
|
+
if (head === '' && count > 0) continue
|
|
248
|
+
const tail = under(walked[count], `${head}${suffix}`.trim(), sourceOf(sourceSegments.length))
|
|
249
|
+
if (bytesOf(tail.folder) <= budget) return { folder: tail.folder, shortened: true, qualified: qualified || tail.qualified }
|
|
250
|
+
}
|
|
251
|
+
if (bytesOf(repoFolder) <= budget) return { folder: repoFolder, shortened: true, qualified: false }
|
|
252
|
+
return refuse('path-too-long', 'the vault root leaves no room for a name in this repository folder')
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Fits a file name into the limits by cutting the title part `base` further,
|
|
256
|
+
// never below MIN_NAME_BYTES. `build(base)` returns the path(s) the name makes.
|
|
257
|
+
function fitted(base, build, limits) {
|
|
258
|
+
const paths = build(base)
|
|
259
|
+
const overflow = overflowOf(paths, limits)
|
|
260
|
+
if (overflow === 0) return paths
|
|
261
|
+
const budget = bytesOf(base) - overflow
|
|
262
|
+
if (budget < MIN_NAME_BYTES) refuse('path-too-long', 'an allocated path exceeds the supported path length')
|
|
263
|
+
const shorter = build(cutName(base, budget))
|
|
264
|
+
if (overflowOf(shorter, limits) > 0) refuse('path-too-long', 'an allocated path exceeds the supported path length')
|
|
265
|
+
return shorter
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const isFree = (state, paths) => paths.every((filePath) => {
|
|
269
|
+
const key = collisionKey(filePath)
|
|
270
|
+
return !state.files.has(key) && !state.folders.has(key)
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
// The names a note tries in order: its title, then qualified by its source
|
|
274
|
+
// file stem, then by a short stable id lengthened until it is free.
|
|
275
|
+
function noteCandidates(node) {
|
|
276
|
+
const base = noteNameOf(node)
|
|
277
|
+
const stem = vaultName(fileStemOf(node.path), QUALIFIER_STEM_BYTES)
|
|
278
|
+
const names = [(title) => title]
|
|
279
|
+
if (stem !== '') names.push((title) => `${title} (${stem})`)
|
|
280
|
+
for (let length = 6; length <= MAX_QUALIFIER_ID; length += 2) {
|
|
281
|
+
const id = qualifierIdOf(node.repo, node.id, length)
|
|
282
|
+
names.push(stem === '' ? (title) => `${title} (${id})` : (title) => `${title} (${stem}) (${id})`)
|
|
283
|
+
}
|
|
284
|
+
return { base, names }
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// A file keeps its own name; a collision qualifies its stem with a short id.
|
|
288
|
+
function fileCandidates(sourcePath, repoId, identity) {
|
|
289
|
+
const { stem, extension } = fileNameParts(sourcePath)
|
|
290
|
+
const names = [(base) => joinFileName({ stem: base, extension })]
|
|
291
|
+
for (let length = 6; length <= MAX_QUALIFIER_ID; length += 2) {
|
|
292
|
+
const id = qualifierIdOf(repoId, identity, length)
|
|
293
|
+
names.push((base) => joinFileName({ stem: `${base} (${id})`, extension }))
|
|
294
|
+
}
|
|
295
|
+
return { base: stem, names }
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function allocate(state, { folder, candidates, build, limits }) {
|
|
299
|
+
for (const name of candidates.names) {
|
|
300
|
+
const paths = fitted(candidates.base, (base) => build(`${folder}/${name(base)}`), limits)
|
|
301
|
+
if (isFree(state, paths)) return paths
|
|
302
|
+
}
|
|
303
|
+
return refuse('path-collision', 'unable to allocate a distinct vault path')
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// The paths of one file (a note, a wrapped file and its note, or an asset),
|
|
307
|
+
// allocated fresh and claimed, and what was degraded to lay it out. The folder
|
|
308
|
+
// leaves room for the shortest name first; a name that then has to be
|
|
309
|
+
// qualified and no longer fits is laid out again under a shorter folder.
|
|
310
|
+
function place(state, { repoId, sourcePath, owner, candidates, build, nameBytes, limits }) {
|
|
311
|
+
const repoFolder = repositoryFolder(state, repoId)
|
|
312
|
+
let placed = null
|
|
313
|
+
for (const reserve of [nameBytes, nameBytes + QUALIFIED_NAME_BYTES]) {
|
|
314
|
+
const { folder, shortened, qualified } = mirroredFolder(state, { repoFolder, repoId, sourcePath, limits, reserve })
|
|
315
|
+
try {
|
|
316
|
+
placed = { paths: allocate(state, { folder, candidates, build, limits }), shortened, qualified }
|
|
317
|
+
break
|
|
318
|
+
} catch (error) {
|
|
319
|
+
if (error?.code !== 'path-too-long' || reserve !== nameBytes) throw error
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
claimRepositoryFolder(state, repoId, repoFolder)
|
|
323
|
+
for (const filePath of placed.paths) claimFile(state, filePath, owner)
|
|
324
|
+
return { paths: placed.paths, degraded: [...(placed.shortened ? ['folder-shortened'] : []), ...(placed.qualified ? ['folder-qualified'] : [])] }
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Allocates the paths of one view: `nodes` are the view's canonical nodes ({
|
|
328
|
+
// repo, id, path, title, extension }), `assets` the assets its notes embed ({
|
|
329
|
+
// repo, id, path }), `published` what the view's prior generation published (or
|
|
330
|
+
// null), `occupied` vault paths that must stay free (files held for an edit).
|
|
331
|
+
// Published paths come first, then new nodes and new assets, each in canonical
|
|
332
|
+
// identity order, so the result depends on the inputs alone and never on the
|
|
333
|
+
// order they are listed in. Returns the view's registry section, the lookups,
|
|
334
|
+
// and a diagnostic for every note or asset whose folder was degraded.
|
|
335
|
+
export function allocateViewPaths({ published = null, nodes, assets = [], occupied = [], vaultRootBytes = 0, maxFullPathBytes = DEFAULT_MAX_FULL_PATH_BYTES }) {
|
|
336
|
+
const state = newState()
|
|
337
|
+
const limits = { vaultRootBytes, maxFullPathBytes }
|
|
338
|
+
const sortedNodes = [...nodes].sort((left, right) => compare(left.repo, right.repo) || compare(left.id, right.id))
|
|
339
|
+
const nodeKeys = new Set()
|
|
340
|
+
for (const node of sortedNodes) {
|
|
341
|
+
const key = identityKey(node.repo, node.id)
|
|
342
|
+
if (nodeKeys.has(key)) refuse('duplicate-identity', 'more than one node carries the same canonical identity')
|
|
343
|
+
nodeKeys.add(key)
|
|
344
|
+
}
|
|
345
|
+
const sortedAssets = [...new Map(assets.map((asset) => [assetKey(asset.repo, asset.path), asset])).values()].sort((left, right) => compare(left.repo, right.repo) || compare(left.path, right.path))
|
|
346
|
+
const assetKeys = new Set(sortedAssets.map((asset) => assetKey(asset.repo, asset.path)))
|
|
347
|
+
seed(state, { notes: published?.notes ?? published?.entries ?? [], assets: published?.assets ?? [] }, { nodeKeys, assetKeys, limits })
|
|
348
|
+
for (const filePath of [...new Set(occupied)].sort()) {
|
|
349
|
+
if (typeof filePath !== 'string' || !isReadableVaultPath(filePath) || state.files.has(collisionKey(filePath)) || !claimable(state, filePath)) continue
|
|
350
|
+
claimFile(state, filePath, 'occupied')
|
|
351
|
+
}
|
|
352
|
+
for (const folder of LAYOUT_1_FOLDERS) {
|
|
353
|
+
const key = collisionKey(folder)
|
|
354
|
+
if (!state.folders.has(key) && !state.files.has(key)) state.folders.set(key, folder)
|
|
355
|
+
}
|
|
356
|
+
const diagnostics = []
|
|
357
|
+
for (const node of sortedNodes) {
|
|
358
|
+
const key = identityKey(node.repo, node.id)
|
|
359
|
+
if (state.byIdentity.has(key)) continue
|
|
360
|
+
const wrapped = extensionOf(node) !== 'md'
|
|
361
|
+
const { paths, degraded } = place(state, {
|
|
362
|
+
repoId: node.repo, sourcePath: node.path, owner: key, limits,
|
|
363
|
+
candidates: wrapped ? fileCandidates(node.path, node.repo, node.id) : noteCandidates(node),
|
|
364
|
+
build: wrapped ? (base) => [base, `${base}.md`] : (base) => [`${base}.md`],
|
|
365
|
+
nameBytes: 1 + MIN_NAME_BYTES + (wrapped ? bytesOf(joinFileName({ stem: '', extension: fileNameParts(node.path).extension })) : 0) + '.md'.length,
|
|
366
|
+
})
|
|
367
|
+
// A wrapped file's paths are [file, note]; a Markdown note's [note].
|
|
368
|
+
const allocated = wrapped ? { path: paths[1], attachment: paths[0] } : { path: paths[0] }
|
|
369
|
+
state.byIdentity.set(key, allocated)
|
|
370
|
+
for (const code of degraded) diagnostics.push({ code, repoId: node.repo, nodeId: node.id, notePath: allocated.path })
|
|
371
|
+
}
|
|
372
|
+
for (const asset of sortedAssets) {
|
|
373
|
+
const key = assetKey(asset.repo, asset.path)
|
|
374
|
+
if (state.byAsset.has(key)) continue
|
|
375
|
+
const { paths, degraded } = place(state, {
|
|
376
|
+
repoId: asset.repo, sourcePath: asset.path, owner: `asset\u0000${key}`, limits,
|
|
377
|
+
candidates: fileCandidates(asset.path, asset.repo, asset.id ?? `${asset.repo}:asset:${asset.path}`), build: (base) => [base],
|
|
378
|
+
nameBytes: 1 + MIN_NAME_BYTES + bytesOf(joinFileName({ stem: '', extension: fileNameParts(asset.path).extension })),
|
|
379
|
+
})
|
|
380
|
+
state.byAsset.set(key, paths[0])
|
|
381
|
+
for (const code of degraded) diagnostics.push({ code, repoId: asset.repo, assetPath: asset.path, filePath: paths[0] })
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const entries = [...state.byIdentity].map(([key, allocated]) => {
|
|
385
|
+
const [repoId, nodeId] = key.split('\u0000')
|
|
386
|
+
return { repoId, nodeId, path: allocated.path, ...(allocated.attachment === undefined ? {} : { attachment: allocated.attachment }) }
|
|
387
|
+
}).sort(byIdentity)
|
|
388
|
+
const assetEntries = [...state.byAsset].map(([key, allocatedPath]) => {
|
|
389
|
+
const [repoId, assetPath] = key.split('\u0000')
|
|
390
|
+
return { repoId, assetPath, path: allocatedPath }
|
|
391
|
+
}).sort(byAsset)
|
|
392
|
+
return {
|
|
393
|
+
section: { entries, assets: assetEntries },
|
|
394
|
+
pathOf: (repoId, nodeId) => state.byIdentity.get(identityKey(repoId, nodeId))?.path ?? null,
|
|
395
|
+
attachmentOf: (repoId, nodeId) => state.byIdentity.get(identityKey(repoId, nodeId))?.attachment ?? null,
|
|
396
|
+
assetPathOf: (repoId, assetPathValue) => state.byAsset.get(assetKey(repoId, assetPathValue)) ?? null,
|
|
397
|
+
diagnostics,
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// ---------------------------------------------------------------------------
|
|
402
|
+
// Layout 1
|
|
403
|
+
// ---------------------------------------------------------------------------
|
|
404
|
+
|
|
405
|
+
const LEGACY_NOTE_PATH = /^notes\/([^/\\\u0000]+)--([0-9a-f]{12,64})\.md$/
|
|
406
|
+
const LEGACY_SUFFIX = 64
|
|
407
|
+
// Bytes left for the readable title once the longest suffix, the separator and
|
|
408
|
+
// the extension are reserved, so lengthening a suffix never overflows a name.
|
|
409
|
+
const LEGACY_TITLE_BYTE_BUDGET = MAX_COMPONENT_BYTES - '--'.length - LEGACY_SUFFIX - '.md'.length
|
|
410
|
+
|
|
411
|
+
export function titleWithinBudget(title, budget = LEGACY_TITLE_BYTE_BUDGET) {
|
|
35
412
|
let kept = ''
|
|
36
413
|
let bytes = 0
|
|
37
414
|
for (const character of String(title ?? '').normalize('NFC')) {
|
|
@@ -42,39 +419,20 @@ export function titleWithinBudget(title, budget = TITLE_BYTE_BUDGET) {
|
|
|
42
419
|
return kept
|
|
43
420
|
}
|
|
44
421
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
function indexRegistry(registry, workspaceId) {
|
|
50
|
-
if (registry === undefined || registry === null) return { byIdentity: new Map(), taken: new Map() }
|
|
51
|
-
if (registry.schema !== PATH_REGISTRY_SCHEMA || !Array.isArray(registry.entries)) refuse('invalid-path-registry', 'path registry has an unknown shape')
|
|
52
|
-
if (registry.workspaceId !== workspaceId) refuse('invalid-path-registry', 'path registry belongs to another workspace')
|
|
422
|
+
// Layout 1 paths, as the earlier release allocated them: the paths a layout 1
|
|
423
|
+
// prior generation recorded, then `notes/<title>--<suffix>.md` for every other
|
|
424
|
+
// node in canonical identity order, the suffix lengthened on collision.
|
|
425
|
+
export function allocateLegacyPaths({ nodes, priorManifest = null, vaultRootBytes = 0, maxFullPathBytes = DEFAULT_MAX_FULL_PATH_BYTES }) {
|
|
53
426
|
const byIdentity = new Map()
|
|
54
|
-
const taken = new
|
|
55
|
-
for (const
|
|
56
|
-
const match =
|
|
57
|
-
if (!match ||
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
const key = identityKey(entry.repoId, entry.nodeId)
|
|
62
|
-
if (byIdentity.has(key)) refuse('duplicate-identity', 'path registry repeats a canonical identity')
|
|
63
|
-
if (taken.has(collisionKey(entry.path))) refuse('path-collision', 'path registry allocates one path to more than one identity')
|
|
64
|
-
byIdentity.set(key, entry.path)
|
|
65
|
-
taken.set(collisionKey(entry.path), key)
|
|
427
|
+
const taken = new Set()
|
|
428
|
+
for (const note of priorManifest?.notes ?? []) {
|
|
429
|
+
const match = LEGACY_NOTE_PATH.exec(note.path)
|
|
430
|
+
if (!match || identitySuffix(note.repoId, note.nodeId, LEGACY_SUFFIX).slice(0, match[2].length) !== match[2]) refuse('invalid-path-registry', 'a layout 1 manifest entry carries a suffix that is not derived from its identity')
|
|
431
|
+
byIdentity.set(identityKey(note.repoId, note.nodeId), note.path)
|
|
432
|
+
taken.add(collisionKey(note.path))
|
|
66
433
|
}
|
|
67
|
-
return { byIdentity, taken }
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Allocates a path for every node that lacks one and returns the grown
|
|
71
|
-
// registry. `nodes` are canonical nodes ({ repo, id, title }); allocation order
|
|
72
|
-
// is canonical-identity order, so the result depends on the inputs alone.
|
|
73
|
-
export function allocateWorkspacePaths({ registry, workspaceId, nodes, vaultRootBytes = 0, maxFullPathBytes = DEFAULT_MAX_FULL_PATH_BYTES }) {
|
|
74
|
-
const { byIdentity, taken } = indexRegistry(registry, workspaceId)
|
|
75
434
|
const seen = new Set()
|
|
76
|
-
const
|
|
77
|
-
for (const node of ordered) {
|
|
435
|
+
for (const node of [...nodes].sort((left, right) => compare(left.repo, right.repo) || compare(left.id, right.id))) {
|
|
78
436
|
const key = identityKey(node.repo, node.id)
|
|
79
437
|
if (seen.has(key)) refuse('duplicate-identity', 'more than one node carries the same canonical identity')
|
|
80
438
|
seen.add(key)
|
|
@@ -84,11 +442,11 @@ export function allocateWorkspacePaths({ registry, workspaceId, nodes, vaultRoot
|
|
|
84
442
|
let candidate = notePath(titled, length)
|
|
85
443
|
while (taken.has(collisionKey(candidate))) {
|
|
86
444
|
length += 4
|
|
87
|
-
if (length >
|
|
445
|
+
if (length > LEGACY_SUFFIX) refuse('path-collision', 'unable to allocate a distinct note path')
|
|
88
446
|
candidate = notePath(titled, length)
|
|
89
447
|
}
|
|
90
448
|
byIdentity.set(key, candidate)
|
|
91
|
-
taken.
|
|
449
|
+
taken.add(collisionKey(candidate))
|
|
92
450
|
}
|
|
93
451
|
for (const candidate of byIdentity.values()) {
|
|
94
452
|
const name = candidate.slice('notes/'.length)
|
|
@@ -99,7 +457,35 @@ export function allocateWorkspacePaths({ registry, workspaceId, nodes, vaultRoot
|
|
|
99
457
|
const entries = [...byIdentity].map(([key, allocated]) => {
|
|
100
458
|
const [repoId, nodeId] = key.split('\u0000')
|
|
101
459
|
return { repoId, nodeId, path: allocated }
|
|
102
|
-
})
|
|
103
|
-
entries
|
|
104
|
-
|
|
460
|
+
}).sort((left, right) => compare(left.repoId, right.repoId) || compare(left.nodeId, right.nodeId))
|
|
461
|
+
return { entries, pathOf: (repoId, nodeId) => byIdentity.get(identityKey(repoId, nodeId)) ?? null }
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Deprecated. The earlier release's allocation, kept for callers of the
|
|
465
|
+
// published subpath with the same inputs, checks and result: layout 1 note
|
|
466
|
+
// paths (`notes/<title>--<suffix>.md`) for every node of a workspace, reusing
|
|
467
|
+
// and returning a registry of `{ repoId, nodeId, path }` entries. Atelier no
|
|
468
|
+
// longer calls it: views are laid out in layout 2, each by allocateViewPaths.
|
|
469
|
+
// A layout 2 registry holds no layout 1 entries and is read as empty.
|
|
470
|
+
export function allocateWorkspacePaths({ registry, workspaceId, nodes, vaultRootBytes = 0, maxFullPathBytes = DEFAULT_MAX_FULL_PATH_BYTES }) {
|
|
471
|
+
const notes = []
|
|
472
|
+
if (registry !== undefined && registry !== null) {
|
|
473
|
+
if (registry.schema !== PATH_REGISTRY_SCHEMA || !Array.isArray(registry.entries)) refuse('invalid-path-registry', 'path registry has an unknown shape')
|
|
474
|
+
if (registry.workspaceId !== workspaceId) refuse('invalid-path-registry', 'path registry belongs to another workspace')
|
|
475
|
+
const seen = new Set()
|
|
476
|
+
const taken = new Set()
|
|
477
|
+
for (const entry of registry.entries) {
|
|
478
|
+
const match = typeof entry?.path === 'string' ? LEGACY_NOTE_PATH.exec(entry.path) : null
|
|
479
|
+
if (!match || typeof entry.repoId !== 'string' || typeof entry.nodeId !== 'string') refuse('invalid-path-registry', 'path registry entry is malformed')
|
|
480
|
+
if (identitySuffix(entry.repoId, entry.nodeId, LEGACY_SUFFIX).slice(0, match[2].length) !== match[2]) refuse('invalid-path-registry', 'path registry entry carries a suffix that is not derived from its identity')
|
|
481
|
+
const key = identityKey(entry.repoId, entry.nodeId)
|
|
482
|
+
if (seen.has(key)) refuse('duplicate-identity', 'path registry repeats a canonical identity')
|
|
483
|
+
if (taken.has(collisionKey(entry.path))) refuse('path-collision', 'path registry allocates one path to more than one identity')
|
|
484
|
+
seen.add(key)
|
|
485
|
+
taken.add(collisionKey(entry.path))
|
|
486
|
+
notes.push({ repoId: entry.repoId, nodeId: entry.nodeId, path: entry.path })
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
const { entries, pathOf } = allocateLegacyPaths({ nodes, priorManifest: { notes }, vaultRootBytes, maxFullPathBytes })
|
|
490
|
+
return { registry: { schema: PATH_REGISTRY_SCHEMA, workspaceId, entries }, pathOf }
|
|
105
491
|
}
|