@mnstry/atelier 0.2.0-alpha.11 → 0.2.0-alpha.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +1005 -0
- package/README.md +149 -9
- package/contracts/atelier-adoption-policy.v2.schema.json +55 -0
- package/contracts/atelier-architecture.v1.schema.json +258 -0
- package/contracts/atelier-boundary-delegations.v1.schema.json +161 -0
- package/contracts/atelier-boundary-owner-keys.v1.schema.json +98 -0
- package/contracts/atelier-build.v1.schema.json +1076 -0
- package/contracts/atelier-capability.v1.schema.json +1252 -0
- package/contracts/atelier-coordination.v1.schema.json +1050 -0
- package/contracts/atelier-evidence-navigation.v1.schema.json +603 -0
- package/contracts/atelier-harness.v1.schema.json +234 -0
- package/contracts/atelier-ingestion.v1.schema.json +806 -0
- package/contracts/atelier-inquiry.v1.schema.json +1772 -0
- package/contracts/atelier-instruction-adoption.v1.schema.json +490 -0
- package/contracts/atelier-interaction.v1.schema.json +553 -0
- package/contracts/atelier-judgment.v1.schema.json +690 -0
- package/contracts/atelier-knowledge.v1.schema.json +1512 -0
- package/contracts/atelier-learning.v1.schema.json +1095 -0
- package/contracts/atelier-migration.v3.schema.json +64 -0
- package/contracts/atelier-obsidian-generation-manifest.v2.schema.json +372 -0
- package/contracts/atelier-personal-workspace-manifest.v1.schema.json +87 -0
- package/contracts/atelier-personal-workspace-overlay.v1.schema.json +299 -0
- package/contracts/atelier-template-adoption.v1.schema.json +223 -0
- package/contracts/atelier-template-profile.v1.schema.json +1168 -0
- package/contracts/atelier-trackable.v1.schema.json +781 -0
- package/contracts/atelier-upgrade-plan.v3.schema.json +532 -0
- package/contracts/compat-baseline.json +1 -1
- package/docs/architecture.md +89 -0
- package/docs/assurance-controls.md +4 -3
- package/docs/blocks/capability-stewardship.md +17 -0
- package/docs/blocks/claims.md +16 -3
- package/docs/blocks/inquiry-harnesses.md +19 -0
- package/docs/blocks/learning-harnesses.md +17 -0
- package/docs/capability-stewardship.md +269 -0
- package/docs/coordination.md +51 -0
- package/docs/decision-practices.md +111 -0
- package/docs/decisions.md +29 -0
- package/docs/evidence-navigation.md +237 -0
- package/docs/exact-upgrades.md +3 -2
- package/docs/ingestion.md +181 -0
- package/docs/inquiry-harnesses.md +211 -0
- package/docs/install.md +80 -3
- package/docs/intake-and-guides.md +5 -0
- package/docs/integration-contract-decisions.md +24 -2
- package/docs/interaction-and-reflection.md +72 -0
- package/docs/knowledge-graph.md +20 -0
- package/docs/knowledge-health/api.md +30 -0
- package/docs/knowledge-health/assessment/api.md +96 -0
- package/docs/knowledge-health/assessment/capabilities.md +40 -0
- package/docs/knowledge-health/assessment/evaluation.md +27 -0
- package/docs/knowledge-health/assessment/facilitator.md +30 -0
- package/docs/knowledge-health/assessment/quickstart.md +61 -0
- package/docs/knowledge-health/assessment/troubleshooting.md +23 -0
- package/docs/knowledge-health/devday/facilitator.md +16 -0
- package/docs/knowledge-health/devday/participant-response.md +22 -0
- package/docs/knowledge-health/devday/public-default.md +107 -0
- package/docs/knowledge-health/devday/quickstart.md +18 -0
- package/docs/knowledge-health/devday/support.md +22 -0
- package/docs/knowledge-health/exercise.md +17 -0
- package/docs/knowledge-health/facilitator.md +20 -0
- package/docs/knowledge-health/quickstart.md +94 -0
- package/docs/knowledge-health/readme.md +15 -0
- package/docs/knowledge-health/support.md +34 -0
- package/docs/knowledge-setup.md +248 -0
- package/docs/knowledge-workspace.md +264 -0
- package/docs/learning-harnesses.md +238 -0
- package/docs/learning.md +215 -0
- package/docs/local-services.md +242 -26
- package/docs/obsidian-contract.md +878 -36
- package/docs/obsidian-plugin.md +619 -0
- package/docs/obsidian.md +809 -64
- package/docs/optional-ingestion.md +83 -0
- package/docs/personal-workspace.md +257 -0
- package/docs/practical-judgment.md +92 -0
- package/docs/progressive-ingestion-acceptance.md +89 -0
- package/docs/progressive-ingestion.md +235 -0
- package/docs/project-command-inventory.md +1 -0
- package/docs/release-engineering.md +56 -1
- package/docs/repo-boundary-guard.md +94 -0
- package/docs/research-starter-quickstart.md +66 -0
- package/docs/research-starter.md +114 -0
- package/docs/semantic-ingestion.md +198 -0
- package/docs/semantic-operation.md +317 -0
- package/docs/skill-steward.md +6 -0
- package/docs/template-profile.md +164 -0
- package/docs/template-upgrades.md +114 -0
- package/docs/trackables.md +111 -0
- package/docs/upgrade.md +68 -3
- package/fixtures/atelier-architecture/binding/invalid/document.json +19 -0
- package/fixtures/atelier-architecture/binding/valid/document.json +19 -0
- package/fixtures/atelier-architecture/catalog/invalid/document.json +290 -0
- package/fixtures/atelier-architecture/catalog/valid/document.json +343 -0
- package/fixtures/atelier-build-contract/attempt/invalid/document.json +20 -0
- package/fixtures/atelier-build-contract/attempt/valid/document.json +19 -0
- package/fixtures/atelier-build-contract/candidate/invalid/document.json +24 -0
- package/fixtures/atelier-build-contract/candidate/valid/document.json +23 -0
- package/fixtures/atelier-build-contract/decision/invalid/document.json +31 -0
- package/fixtures/atelier-build-contract/decision/valid/document.json +30 -0
- package/fixtures/atelier-build-contract/delivery/invalid/document.json +18 -0
- package/fixtures/atelier-build-contract/delivery/valid/document.json +17 -0
- package/fixtures/atelier-build-contract/gate/invalid/document.json +22 -0
- package/fixtures/atelier-build-contract/gate/valid/document.json +21 -0
- package/fixtures/atelier-build-contract/ledger/invalid/document.json +270 -0
- package/fixtures/atelier-build-contract/ledger/valid/document.json +269 -0
- package/fixtures/atelier-build-contract/objective/invalid/document.json +73 -0
- package/fixtures/atelier-build-contract/objective/valid/document.json +72 -0
- package/fixtures/atelier-build-contract/progress/invalid/document.json +18 -0
- package/fixtures/atelier-build-contract/progress/valid/document.json +17 -0
- package/fixtures/atelier-capability-contract/adoption/invalid/document.json +71 -0
- package/fixtures/atelier-capability-contract/adoption/valid/document.json +70 -0
- package/fixtures/atelier-capability-contract/event/invalid/document.json +18 -0
- package/fixtures/atelier-capability-contract/event/valid/document.json +17 -0
- package/fixtures/atelier-capability-contract/journal/invalid/document.json +382 -0
- package/fixtures/atelier-capability-contract/journal/valid/document.json +381 -0
- package/fixtures/atelier-capability-contract/notice/invalid/document.json +8 -0
- package/fixtures/atelier-capability-contract/notice/valid/document.json +7 -0
- package/fixtures/atelier-capability-contract/package/invalid/document.json +59 -0
- package/fixtures/atelier-capability-contract/package/valid/document.json +58 -0
- package/fixtures/atelier-capability-contract/plan/invalid/document.json +107 -0
- package/fixtures/atelier-capability-contract/plan/valid/document.json +106 -0
- package/fixtures/atelier-capability-contract/release/invalid/document.json +85 -0
- package/fixtures/atelier-capability-contract/release/valid/document.json +84 -0
- package/fixtures/atelier-capability-contract/state/invalid/document.json +316 -0
- package/fixtures/atelier-capability-contract/state/valid/document.json +315 -0
- package/fixtures/atelier-coordination/dependency/invalid/document.json +37 -0
- package/fixtures/atelier-coordination/dependency/valid/document.json +36 -0
- package/fixtures/atelier-coordination/directive/invalid/document.json +25 -0
- package/fixtures/atelier-coordination/directive/valid/document.json +24 -0
- package/fixtures/atelier-coordination/disposition/invalid/document.json +34 -0
- package/fixtures/atelier-coordination/disposition/valid/document.json +33 -0
- package/fixtures/atelier-coordination/outcome/invalid/document.json +39 -0
- package/fixtures/atelier-coordination/outcome/valid/document.json +38 -0
- package/fixtures/atelier-coordination/recovery/invalid/document.json +34 -0
- package/fixtures/atelier-coordination/recovery/valid/document.json +33 -0
- package/fixtures/atelier-harness-contract/handoff/invalid/document.json +25 -0
- package/fixtures/atelier-harness-contract/handoff/valid/document.json +24 -0
- package/fixtures/atelier-ingestion/invalid/authority.json +19 -0
- package/fixtures/atelier-ingestion/semantic-baseline.json +26 -0
- package/fixtures/atelier-ingestion/semantic-operation.json +75 -0
- package/fixtures/atelier-ingestion/valid/plan-input.json +18 -0
- package/fixtures/atelier-inquiry-contract/assessment/invalid/document.json +41 -0
- package/fixtures/atelier-inquiry-contract/assessment/valid/document.json +40 -0
- package/fixtures/atelier-inquiry-contract/bundle/invalid/document.json +51 -0
- package/fixtures/atelier-inquiry-contract/bundle/valid/document.json +50 -0
- package/fixtures/atelier-inquiry-contract/campaign/invalid/document.json +17 -0
- package/fixtures/atelier-inquiry-contract/campaign/valid/document.json +16 -0
- package/fixtures/atelier-inquiry-contract/decision/invalid/document.json +22 -0
- package/fixtures/atelier-inquiry-contract/decision/valid/document.json +21 -0
- package/fixtures/atelier-inquiry-contract/feedback/invalid/document.json +19 -0
- package/fixtures/atelier-inquiry-contract/feedback/valid/document.json +18 -0
- package/fixtures/atelier-inquiry-contract/handoff/invalid/document.json +92 -0
- package/fixtures/atelier-inquiry-contract/handoff/valid/document.json +91 -0
- package/fixtures/atelier-inquiry-contract/hypothesis/invalid/document.json +20 -0
- package/fixtures/atelier-inquiry-contract/hypothesis/valid/document.json +19 -0
- package/fixtures/atelier-inquiry-contract/ledger/invalid/document.json +680 -0
- package/fixtures/atelier-inquiry-contract/ledger/valid/document.json +679 -0
- package/fixtures/atelier-inquiry-contract/legacy-assessment/invalid/document.json +19 -0
- package/fixtures/atelier-inquiry-contract/legacy-assessment/valid/document.json +18 -0
- package/fixtures/atelier-inquiry-contract/request/invalid/document.json +50 -0
- package/fixtures/atelier-inquiry-contract/request/valid/document.json +49 -0
- package/fixtures/atelier-inquiry-contract/source/invalid/document.json +18 -0
- package/fixtures/atelier-inquiry-contract/source/valid/document.json +17 -0
- package/fixtures/atelier-inquiry-contract/withdrawal/invalid/document.json +16 -0
- package/fixtures/atelier-inquiry-contract/withdrawal/valid/document.json +15 -0
- package/fixtures/atelier-instruction-adoption/context-receipt/invalid/document.json +18 -0
- package/fixtures/atelier-instruction-adoption/context-receipt/valid/document.json +17 -0
- package/fixtures/atelier-instruction-adoption/plan/invalid/document.json +63 -0
- package/fixtures/atelier-instruction-adoption/plan/valid/document.json +62 -0
- package/fixtures/atelier-instruction-adoption/receipt/invalid/document.json +22 -0
- package/fixtures/atelier-instruction-adoption/receipt/valid/document.json +21 -0
- package/fixtures/atelier-instruction-adoption/state/invalid/document.json +30 -0
- package/fixtures/atelier-instruction-adoption/state/valid/document.json +29 -0
- package/fixtures/atelier-interaction/act/invalid/document.json +25 -0
- package/fixtures/atelier-interaction/act/valid/document.json +24 -0
- package/fixtures/atelier-interaction/assessment/invalid/document.json +32 -0
- package/fixtures/atelier-interaction/assessment/valid/document.json +31 -0
- package/fixtures/atelier-interaction/observation/invalid/document.json +22 -0
- package/fixtures/atelier-interaction/observation/valid/document.json +21 -0
- package/fixtures/atelier-interaction/policy/invalid/document.json +20 -0
- package/fixtures/atelier-interaction/policy/valid/document.json +19 -0
- package/fixtures/atelier-judgment/case/invalid/document.json +76 -0
- package/fixtures/atelier-judgment/case/valid/document.json +75 -0
- package/fixtures/atelier-judgment/decision/invalid/document.json +38 -0
- package/fixtures/atelier-judgment/decision/valid/document.json +37 -0
- package/fixtures/atelier-judgment/native-decision.md +3 -0
- package/fixtures/atelier-judgment/practice-reconsideration/definition.json +92 -0
- package/fixtures/atelier-judgment/practice-reconsideration/scenario.json +435 -0
- package/fixtures/atelier-knowledge-contract/activation/invalid/document.json +26 -0
- package/fixtures/atelier-knowledge-contract/activation/valid/document.json +25 -0
- package/fixtures/atelier-knowledge-contract/contribution/invalid/document.json +34 -0
- package/fixtures/atelier-knowledge-contract/contribution/valid/document.json +33 -0
- package/fixtures/atelier-knowledge-contract/domain/invalid/document.json +41 -0
- package/fixtures/atelier-knowledge-contract/domain/valid/document.json +40 -0
- package/fixtures/atelier-knowledge-contract/domain-revision/invalid/document.json +46 -0
- package/fixtures/atelier-knowledge-contract/domain-revision/valid/document.json +45 -0
- package/fixtures/atelier-knowledge-contract/evaluation/invalid/document.json +21 -0
- package/fixtures/atelier-knowledge-contract/evaluation/valid/document.json +20 -0
- package/fixtures/atelier-knowledge-contract/ledger/invalid/document.json +318 -0
- package/fixtures/atelier-knowledge-contract/ledger/valid/document.json +317 -0
- package/fixtures/atelier-knowledge-contract/relation/invalid/document.json +25 -0
- package/fixtures/atelier-knowledge-contract/relation/valid/document.json +24 -0
- package/fixtures/atelier-knowledge-contract/review/invalid/document.json +18 -0
- package/fixtures/atelier-knowledge-contract/review/valid/document.json +17 -0
- package/fixtures/atelier-knowledge-contract/withdrawal/invalid/document.json +16 -0
- package/fixtures/atelier-knowledge-contract/withdrawal/valid/document.json +15 -0
- package/fixtures/atelier-learning/invalid/authority.json +14 -0
- package/fixtures/atelier-learning/valid/capture.json +13 -0
- package/fixtures/atelier-personal-workspace-contract/manifest/invalid/authority-key.json +17 -0
- package/fixtures/atelier-personal-workspace-contract/manifest/invalid/nonempty-ext.json +25 -0
- package/fixtures/atelier-personal-workspace-contract/manifest/invalid/wrong-schema.json +22 -0
- package/fixtures/atelier-personal-workspace-contract/manifest/valid/manifest.json +22 -0
- package/fixtures/atelier-personal-workspace-contract/overlay/invalid/authority-key.json +25 -0
- package/fixtures/atelier-personal-workspace-contract/overlay/invalid/unknown-top-level-field.json +27 -0
- package/fixtures/atelier-personal-workspace-contract/overlay/invalid/wrong-schema.json +24 -0
- package/fixtures/atelier-trackable/definition/invalid/document.json +15 -0
- package/fixtures/atelier-trackable/definition/valid/document.json +14 -0
- package/fixtures/atelier-trackable/request/invalid/document.json +23 -0
- package/fixtures/atelier-trackable/request/valid/document.json +22 -0
- package/fixtures/boundary-delegations/invalid/authority-key.v1.json +30 -0
- package/fixtures/boundary-delegations/invalid/push-operation.v1.json +28 -0
- package/fixtures/boundary-delegations/invalid/unsigned.v1.json +25 -0
- package/fixtures/boundary-delegations/valid/empty.v1.json +4 -0
- package/fixtures/boundary-delegations/valid/signed-operator.v1.json +30 -0
- package/fixtures/boundary-owner-keys/invalid/wrong-algorithm.v1.json +15 -0
- package/fixtures/boundary-owner-keys/invalid/wrong-curve.v1.json +15 -0
- package/fixtures/boundary-owner-keys/valid/one-owner.v1.json +17 -0
- package/fixtures/capability-packages/adoption.example.json +70 -0
- package/fixtures/capability-packages/evidence-review/capability-package.json +58 -0
- package/fixtures/capability-packages/evidence-review/capability-release.json +84 -0
- package/fixtures/capability-packages/evidence-review/evaluation.md +15 -0
- package/fixtures/capability-packages/evidence-review/skills/evidence-review/SKILL.md +22 -0
- package/fixtures/capability-packages/evidence-review/skills/evidence-review/references/ledger.md +7 -0
- package/fixtures/capability-packages/research/capability-package.json +88 -0
- package/fixtures/capability-packages/research/capability-release.json +134 -0
- package/fixtures/capability-packages/research/evaluation.md +15 -0
- package/fixtures/capability-packages/research/skills/research-integrate/SKILL.md +29 -0
- package/fixtures/capability-packages/research/skills/research-integrate/references/integration.md +10 -0
- package/fixtures/capability-packages/research/skills/research-plan/SKILL.md +27 -0
- package/fixtures/capability-packages/research/skills/research-plan/references/inquiries.md +26 -0
- package/fixtures/capability-packages/research/skills/research-synthesize/SKILL.md +23 -0
- package/fixtures/capability-packages/research/skills/research-synthesize/references/synthesis.md +12 -0
- package/fixtures/evidence-navigation/invalid/duplicate-operation.json +16 -0
- package/fixtures/evidence-navigation/invalid/instruction-kind.json +36 -0
- package/fixtures/evidence-navigation/invalid/invalid-digest.json +36 -0
- package/fixtures/evidence-navigation/invalid/invalid-time.json +36 -0
- package/fixtures/evidence-navigation/invalid/missing-attribution.json +35 -0
- package/fixtures/evidence-navigation/invalid/reserved-extension.json +19 -0
- package/fixtures/evidence-navigation/invalid/unattributed-acceptance.json +36 -0
- package/fixtures/evidence-navigation/invalid/uncoerced-version.json +16 -0
- package/fixtures/evidence-navigation/invalid/unknown-currency.json +17 -0
- package/fixtures/evidence-navigation/invalid/unknown-selector-field.json +37 -0
- package/fixtures/evidence-navigation/invalid/unsupported-operation.json +15 -0
- package/fixtures/evidence-navigation/valid/claim.json +36 -0
- package/fixtures/evidence-navigation/valid/host.json +16 -0
- package/fixtures/evidence-navigation/valid/profile.json +16 -0
- package/fixtures/evidence-navigation/valid/snapshot.json +17 -0
- package/fixtures/harness-packages/build-harness/capability-package.json +61 -0
- package/fixtures/harness-packages/build-harness/capability-release.json +82 -0
- package/fixtures/harness-packages/build-harness/evaluation.md +3 -0
- package/fixtures/harness-packages/build-harness/skills/atelier-build-harness/SKILL.md +59 -0
- package/fixtures/harness-packages/knowledge-harness/capability-package.json +61 -0
- package/fixtures/harness-packages/knowledge-harness/capability-release.json +82 -0
- package/fixtures/harness-packages/knowledge-harness/evaluation.md +3 -0
- package/fixtures/harness-packages/knowledge-harness/skills/atelier-knowledge-harness/SKILL.md +60 -0
- package/fixtures/harnesses/learning-cycle.json +780 -0
- package/fixtures/inquiry/workshop.json +675 -0
- package/fixtures/inquiry-packages/discovery-harness/capability-package.json +61 -0
- package/fixtures/inquiry-packages/discovery-harness/capability-release.json +82 -0
- package/fixtures/inquiry-packages/discovery-harness/evaluation.md +3 -0
- package/fixtures/inquiry-packages/discovery-harness/skills/atelier-discovery-harness/SKILL.md +48 -0
- package/fixtures/inquiry-packages/research-harness/capability-package.json +61 -0
- package/fixtures/inquiry-packages/research-harness/capability-release.json +82 -0
- package/fixtures/inquiry-packages/research-harness/evaluation.md +3 -0
- package/fixtures/inquiry-packages/research-harness/skills/atelier-research-harness/SKILL.md +43 -0
- package/fixtures/knowledge-health/calibration-suite.json +26 -0
- package/fixtures/knowledge-health/calibration-trial.json +37 -0
- package/fixtures/knowledge-health/corrected/checklist.md +18 -0
- package/fixtures/knowledge-health/devday/participant-record.json +19 -0
- package/fixtures/knowledge-health/expected-summary.json +27 -0
- package/fixtures/knowledge-health/workshop/README.md +20 -0
- package/fixtures/knowledge-health/workshop/atelier.project.json +7 -0
- package/fixtures/knowledge-health/workshop/knowledge-plan.json +65 -0
- package/fixtures/knowledge-health/workshop/records/checklist.md +18 -0
- package/fixtures/knowledge-health/workshop/records/workshop.md +18 -0
- package/fixtures/knowledge-health/workshop/repo-access.v1.json +5 -0
- package/fixtures/knowledge-health/workspace/atelier.project.json +18 -0
- package/fixtures/knowledge-health/workspace/knowledge-plan.json +74 -0
- package/fixtures/knowledge-health/workspace/records/checklist.md +18 -0
- package/fixtures/knowledge-health/workspace/records/pilot.md +16 -0
- package/fixtures/knowledge-health/workspace/repo-access.v1.json +9 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/case-only-collision.v2.json +160 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/flat-note-path.v2.json +160 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/hidden-folder.v2.json +160 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/identity-in-the-body.v2.json +160 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/link-breaking-name.v2.json +160 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/missing-identity-region.v2.json +156 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/missing-layout-version.v2.json +159 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/note-named-like-a-folder.v2.json +160 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/unknown-region-kind.v2.json +160 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/v1-contract-version.v2.json +161 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/valid/three-notes.v2.json +160 -0
- package/fixtures/obsidian/edits/workspace.json +1 -1
- package/fixtures/obsidian/materialization/expected-full.json +21 -21
- package/fixtures/obsidian/materialization/expected-full.layout-1.json +99 -0
- package/fixtures/obsidian/materialization/expected-scoped.json +7 -7
- package/fixtures/obsidian/materialization/expected-scoped.layout-1.json +39 -0
- package/fixtures/personal-workspace/overlay.json +24 -0
- package/fixtures/templates/local-library.v1.json +75 -0
- package/fixtures/templates/upgrade-contracts/adoption-policy.v2/invalid.json +16 -0
- package/fixtures/templates/upgrade-contracts/adoption-policy.v2/valid.json +13 -0
- package/fixtures/templates/upgrade-contracts/migration.v3/invalid.json +24 -0
- package/fixtures/templates/upgrade-contracts/migration.v3/valid.json +21 -0
- package/fixtures/templates/upgrade-contracts/template-adoption.v1/invalid.json +60 -0
- package/fixtures/templates/upgrade-contracts/template-adoption.v1/valid.json +57 -0
- package/fixtures/templates/upgrade-contracts/upgrade-plan.v3/invalid.json +85 -0
- package/fixtures/templates/upgrade-contracts/upgrade-plan.v3/valid.json +82 -0
- package/package.json +64 -6
- package/plugins/obsidian/main.js +444 -0
- package/plugins/obsidian/manifest.json +9 -0
- package/plugins/obsidian/styles.css +22 -0
- package/skills/claude/atelier-build-harness/SKILL.md +59 -0
- package/skills/claude/atelier-coordination/SKILL.md +32 -0
- package/skills/claude/atelier-discovery-harness/SKILL.md +48 -0
- package/skills/claude/atelier-guided-upgrade/SKILL.md +29 -0
- package/skills/claude/atelier-interaction/SKILL.md +34 -0
- package/skills/claude/atelier-knowledge-harness/SKILL.md +60 -0
- package/skills/claude/atelier-knowledge-setup/SKILL.md +72 -0
- package/skills/claude/atelier-practical-judgment/SKILL.md +64 -0
- package/skills/claude/atelier-research-harness/SKILL.md +43 -0
- package/skills/claude/atelier-skill-steward/SKILL.md +53 -0
- package/skills/claude/atelier-trackables/SKILL.md +40 -0
- package/skills/codex/atelier-build-harness/SKILL.md +59 -0
- package/skills/codex/atelier-coordination/SKILL.md +32 -0
- package/skills/codex/atelier-discovery-harness/SKILL.md +48 -0
- package/skills/codex/atelier-guided-upgrade/SKILL.md +29 -0
- package/skills/codex/atelier-interaction/SKILL.md +34 -0
- package/skills/codex/atelier-knowledge-harness/SKILL.md +60 -0
- package/skills/codex/atelier-knowledge-setup/SKILL.md +72 -0
- package/skills/codex/atelier-practical-judgment/SKILL.md +64 -0
- package/skills/codex/atelier-research-harness/SKILL.md +43 -0
- package/skills/codex/atelier-skill-steward/SKILL.md +53 -0
- package/skills/codex/atelier-trackables/SKILL.md +40 -0
- package/src/architecture/catalog.mjs +355 -0
- package/src/architecture/index.mjs +93 -0
- package/src/boundary/content-rules.mjs +4 -2
- package/src/boundary/delegation-command.mjs +158 -0
- package/src/boundary/delegation.mjs +396 -0
- package/src/boundary/policy.mjs +52 -10
- package/src/build/git.mjs +18 -0
- package/src/build/index.mjs +3 -0
- package/src/build/ledger.mjs +88 -0
- package/src/capabilities/adoption.mjs +381 -0
- package/src/capabilities/evidence.mjs +140 -0
- package/src/capabilities/files.mjs +124 -0
- package/src/capabilities/index.mjs +4 -0
- package/src/capabilities/instructions.mjs +250 -0
- package/src/capabilities/package.mjs +136 -0
- package/src/cli/command-failure.mjs +36 -0
- package/src/cli/execute-command.mjs +16 -2
- package/src/cli/run.mjs +65 -9
- package/src/coauthor/store.mjs +43 -2
- package/src/collaboration/proposals.mjs +189 -0
- package/src/commands/architecture.mjs +27 -0
- package/src/commands/boundary.mjs +3 -0
- package/src/commands/capability.mjs +73 -0
- package/src/commands/enroll.mjs +3 -0
- package/src/commands/harness.mjs +79 -0
- package/src/commands/ingest.mjs +39 -0
- package/src/commands/init.mjs +4 -1
- package/src/commands/inquiry.mjs +66 -0
- package/src/commands/knowledge-semantic.mjs +128 -0
- package/src/commands/knowledge.mjs +119 -0
- package/src/commands/learn.mjs +61 -0
- package/src/commands/obsidian.mjs +832 -62
- package/src/commands/practice.mjs +24 -0
- package/src/commands/setup.mjs +22 -2
- package/src/commands/trackable.mjs +19 -0
- package/src/contracts/corpus.mjs +36 -0
- package/src/coordination/index.mjs +110 -0
- package/src/egress/forbidden-egress.mjs +2 -0
- package/src/evidence-navigation/contracts.mjs +60 -0
- package/src/evidence-navigation/evaluate.mjs +69 -0
- package/src/evidence-navigation/generation.mjs +22 -0
- package/src/evidence-navigation/index.mjs +2 -0
- package/src/evidence-navigation/local.mjs +167 -0
- package/src/evidence-navigation/schema.generated.mjs +604 -0
- package/src/evidence-navigation/types.mjs +12 -0
- package/src/export/atelier-export-contract.mjs +35 -8
- package/src/graph/enroll.mjs +248 -0
- package/src/graph/graph.mjs +17 -1
- package/src/graph/knowledge-graph.mjs +58 -8
- package/src/harnesses/contracts.mjs +41 -0
- package/src/harnesses/exchange.mjs +154 -0
- package/src/harnesses/feedback.mjs +16 -0
- package/src/harnesses/history.mjs +37 -0
- package/src/harnesses/index.mjs +4 -0
- package/src/harnesses/store.mjs +29 -0
- package/src/ingestion/contracts.mjs +41 -0
- package/src/ingestion/evaluation.mjs +119 -0
- package/src/ingestion/optional/composition/index.mjs +171 -0
- package/src/ingestion/optional/evaluation/index.mjs +219 -0
- package/src/ingestion/optional/graphify/composition-binding.mjs +60 -0
- package/src/ingestion/optional/graphify/composition_receiving.py +185 -0
- package/src/ingestion/optional/graphify/graphify_supplier.py +933 -0
- package/src/ingestion/optional/graphify/json_bridge.py +163 -0
- package/src/ingestion/optional/graphify/query_receiving.py +209 -0
- package/src/ingestion/optional/index.mjs +9 -0
- package/src/ingestion/optional/jev/bridge.mjs +166 -0
- package/src/ingestion/optional/jev/composition-binding.mjs +116 -0
- package/src/ingestion/optional/jev/judgments.mjs +174 -0
- package/src/ingestion/optional/jev/native-translation.mjs +32 -0
- package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/__init__.py +7 -0
- package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/adapter.py +966 -0
- package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/artifacts.py +101 -0
- package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/projection.py +146 -0
- package/src/ingestion/optional/lightrag/composition-binding.mjs +67 -0
- package/src/ingestion/optional/vanilla/composition-binding.mjs +102 -0
- package/src/ingestion/optional/vanilla/index.mjs +218 -0
- package/src/ingestion/optional/vanilla/native-normalization.mjs +187 -0
- package/src/ingestion/processors.mjs +218 -0
- package/src/ingestion/semantic.mjs +203 -0
- package/src/ingestion/store.mjs +372 -0
- package/src/inquiry/beliefs.mjs +32 -0
- package/src/inquiry/contracts.mjs +23 -0
- package/src/inquiry/index.mjs +5 -0
- package/src/inquiry/ledger.mjs +135 -0
- package/src/inquiry/lenses.mjs +15 -0
- package/src/inquiry/projection.mjs +77 -0
- package/src/inquiry/store.mjs +35 -0
- package/src/intake/read-scope.mjs +12 -0
- package/src/intake/store.mjs +69 -6
- package/src/interaction/index.mjs +91 -0
- package/src/judgment/index.mjs +82 -0
- package/src/judgment/practice-evaluation.mjs +70 -0
- package/src/judgment/practice.mjs +144 -0
- package/src/knowledge/context.mjs +217 -0
- package/src/knowledge/index.mjs +5 -0
- package/src/knowledge/ingestion.mjs +79 -0
- package/src/knowledge/intake.mjs +33 -0
- package/src/knowledge/ledger.mjs +110 -0
- package/src/knowledge/participatory/guidance.mjs +36 -0
- package/src/knowledge/participatory/guided-contribution.mjs +44 -0
- package/src/knowledge/participatory/index.mjs +2 -0
- package/src/knowledge/participatory/installed-workshop.mjs +108 -0
- package/src/knowledge/plan.mjs +67 -0
- package/src/knowledge/projection.mjs +43 -0
- package/src/knowledge/semantic-operation.mjs +567 -0
- package/src/knowledge/sessions.mjs +467 -0
- package/src/knowledge/workspace.mjs +295 -0
- package/src/knowledge-health/assessment.mjs +165 -0
- package/src/knowledge-health/capabilities.mjs +350 -0
- package/src/knowledge-health/context.mjs +62 -0
- package/src/knowledge-health/example.mjs +47 -0
- package/src/knowledge-health/index.mjs +10 -0
- package/src/knowledge-health/owner-join.mjs +88 -0
- package/src/knowledge-health/proof/common.mjs +24 -0
- package/src/knowledge-health/proof/installed-check.mjs +100 -0
- package/src/knowledge-health/proof/native-readback.mjs +37 -0
- package/src/knowledge-health/proof/rehearse.mjs +68 -0
- package/src/knowledge-health/proof/validate-participant.mjs +17 -0
- package/src/knowledge-health/proof/verify-native-capture.mjs +14 -0
- package/src/knowledge-health/proof/workshop-check.mjs +36 -0
- package/src/knowledge-health/rule-identity.mjs +44 -0
- package/src/knowledge-health/workshop-composition.mjs +19 -0
- package/src/knowledge-health/workshop-profile.mjs +176 -0
- package/src/knowledge-health/workshop.mjs +99 -0
- package/src/learning/adapters.mjs +68 -0
- package/src/learning/assistance.mjs +127 -0
- package/src/learning/contracts.mjs +65 -0
- package/src/learning/store.mjs +272 -0
- package/src/personal-workspace/index.mjs +391 -0
- package/src/personal-workspace/selection.mjs +385 -0
- package/src/project/config.mjs +8 -0
- package/src/project/durable-state.mjs +3 -1
- package/src/project/file-class.mjs +1 -1
- package/src/project/private-state.mjs +10 -1
- package/src/projection/obsidian/contracts.mjs +224 -18
- package/src/projection/obsidian/edits/apply.mjs +63 -19
- package/src/projection/obsidian/edits/regions.mjs +137 -42
- package/src/projection/obsidian/materialize/index.mjs +6 -3
- package/src/projection/obsidian/materialize/path-registry.mjs +443 -57
- package/src/projection/obsidian/materialize/prepare-view.mjs +382 -131
- package/src/projection/obsidian/materialize/redaction.mjs +327 -0
- package/src/projection/obsidian/materialize/settings.mjs +113 -34
- package/src/projection/obsidian/personal-workspace.mjs +436 -0
- package/src/projection/obsidian/plugin-bridge/bundle.mjs +50 -0
- package/src/projection/obsidian/plugin-bridge/channel.mjs +174 -0
- package/src/projection/obsidian/plugin-bridge/index.mjs +10 -0
- package/src/projection/obsidian/proposals/adapter.mjs +47 -8
- package/src/projection/obsidian/proposals/observation.mjs +20 -7
- package/src/projection/obsidian/publication/bridge-script.mjs +11 -5
- package/src/projection/obsidian/publication/index.mjs +2 -1
- package/src/projection/obsidian/publication/publisher.mjs +312 -36
- package/src/projection/obsidian/publication/transport.mjs +63 -8
- package/src/projection/obsidian/publication/vault-list.mjs +200 -0
- package/src/projection/obsidian/recovery/index.mjs +4 -1
- package/src/projection/obsidian/recovery/restart.mjs +3 -1
- package/src/projection/obsidian/recovery/store.mjs +168 -8
- package/src/projection/obsidian/selection-ui/contribution.mjs +6 -5
- package/src/projection/obsidian/selection-ui/focus.mjs +14 -7
- package/src/projection/obsidian/selection-ui/selection-state.mjs +2 -2
- package/src/projection/obsidian/selection-ui/selection.mjs +8 -3
- package/src/readiness/readiness.mjs +10 -3
- package/src/reflection/index.mjs +56 -0
- package/src/runtime/obsidian/app-capability.mjs +104 -12
- package/src/runtime/obsidian/app-production-seams.mjs +161 -42
- package/src/runtime/obsidian/app-registration.mjs +313 -0
- package/src/runtime/obsidian/app-restart.mjs +154 -0
- package/src/runtime/obsidian/engine.mjs +288 -29
- package/src/runtime/obsidian/extension-points.mjs +1 -1
- package/src/runtime/obsidian/index.mjs +15 -8
- package/src/runtime/obsidian/launch-plan.mjs +91 -0
- package/src/runtime/obsidian/lifecycle.mjs +193 -22
- package/src/runtime/obsidian/login-item.mjs +332 -0
- package/src/runtime/obsidian/machine-settings.mjs +119 -12
- package/src/runtime/obsidian/opening.mjs +369 -63
- package/src/runtime/obsidian/pending-edits.mjs +10 -0
- package/src/runtime/obsidian/pipeline.mjs +71 -3
- package/src/runtime/obsidian/plugin-channel.mjs +289 -0
- package/src/runtime/obsidian/plugin-choice.mjs +182 -0
- package/src/runtime/obsidian/plugin-drift.mjs +124 -0
- package/src/runtime/obsidian/plugin-presence.mjs +61 -0
- package/src/runtime/obsidian/process-identity.mjs +20 -10
- package/src/runtime/obsidian/project-views.mjs +282 -0
- package/src/runtime/obsidian/questions.mjs +48 -0
- package/src/runtime/obsidian/release-watch.mjs +56 -0
- package/src/runtime/obsidian/service-main.mjs +113 -17
- package/src/runtime/obsidian/service-manager-production.mjs +61 -0
- package/src/runtime/obsidian/service-managers.mjs +275 -0
- package/src/runtime/obsidian/service-record.mjs +105 -5
- package/src/runtime/obsidian/service-server.mjs +50 -12
- package/src/runtime/obsidian/service.mjs +134 -7
- package/src/runtime/obsidian/startup-adapters.mjs +59 -21
- package/src/runtime/obsidian/state-store.mjs +13 -1
- package/src/runtime/obsidian/unheld-evidence.mjs +84 -0
- package/src/runtime/obsidian/vault-location.mjs +300 -0
- package/src/runtime/obsidian/view-counts.mjs +34 -0
- package/src/server/local-sidecar.mjs +193 -7
- package/src/server/server.mjs +45 -6
- package/src/template-bindings/project.mjs +92 -0
- package/src/templates/profile.mjs +225 -0
- package/src/trackables/domain.mjs +175 -0
- package/src/trackables/store.mjs +114 -0
- package/src/ui/knowledge-page.mjs +1388 -0
- package/src/upgrade/explanation.mjs +1 -1
- package/src/upgrade/template-participant.mjs +171 -0
- package/src/upgrade/transaction.mjs +72 -25
- package/src/upgrade/upgrade.mjs +2 -1
- package/templates/external-project-workspace/pack-lifecycle.json +6 -1
- package/templates/knowledge-workspace/.gitattributes +2 -0
- package/templates/knowledge-workspace/AGENTS.md +27 -0
- package/templates/knowledge-workspace/README.md +44 -0
- package/templates/knowledge-workspace/atelier.project.json +22 -0
- package/templates/knowledge-workspace/gitignore +6 -0
- package/templates/knowledge-workspace/knowledge-plan.json +111 -0
- package/templates/knowledge-workspace/knowledge-plan.schema.json +281 -0
- package/templates/knowledge-workspace/records/forecast.md +22 -0
- package/templates/knowledge-workspace/records/inspection.md +25 -0
- package/templates/knowledge-workspace/records/telescope.md +24 -0
- package/templates/knowledge-workspace/repo-access.v1.json +9 -0
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto'
|
|
2
|
+
import fs from 'node:fs'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import { Worker, isMainThread, parentPort, workerData } from 'node:worker_threads'
|
|
5
|
+
import { PersonalWorkspaceRefusal, composePersonalWorkspace } from '@mnstry/atelier/personal-workspace'
|
|
6
|
+
import { ensureLocalState, resolveProjectConfig, validateProjectConfigDoc } from '@mnstry/atelier/project'
|
|
7
|
+
import { buildCanonicalGraph } from '../../graph/graph.mjs'
|
|
8
|
+
import { refuse } from '../../runtime/obsidian/errors.mjs'
|
|
9
|
+
import { OBSIDIAN_EXT_KEY } from './contracts.mjs'
|
|
10
|
+
|
|
11
|
+
// The one personal-workspace route of the Obsidian projection.
|
|
12
|
+
//
|
|
13
|
+
// A person's private home holds the authored manifest and overlay, and the
|
|
14
|
+
// generations the personal-workspace module materialized from them. A binding
|
|
15
|
+
// names one home and one generation, both explicitly: nothing here discovers
|
|
16
|
+
// either from the environment or the working directory. The module's
|
|
17
|
+
// `composePersonalWorkspace` is the only authority on whether that generation
|
|
18
|
+
// may be used.
|
|
19
|
+
//
|
|
20
|
+
// One graph. Every caller (the engine, view counts, source apply, the proposal
|
|
21
|
+
// adapter and the selection operation) builds through the pipeline's
|
|
22
|
+
// `buildGraph`, which builds a bound project here: the canonical graph of the
|
|
23
|
+
// generation's configuration, with the composition's own rule that no private
|
|
24
|
+
// note is a link target, and the caller's file cache. A plain build would
|
|
25
|
+
// differ wherever a shared note's link names a private note.
|
|
26
|
+
//
|
|
27
|
+
// Validity. A graph is returned only under a validity key the composition has
|
|
28
|
+
// confirmed: the key names the inputs a stat or a bounded read can see (see
|
|
29
|
+
// inputsDigest) and a digest of the built graph's facts. The inputs are read
|
|
30
|
+
// before composing, so a key is confirmed only when the composition accepted
|
|
31
|
+
// those inputs and composed a graph with exactly those facts, and a returned
|
|
32
|
+
// graph is always one the composition built too (a change and its exact undo
|
|
33
|
+
// during one composition is caught at the next full reconciliation). A key not yet confirmed is
|
|
34
|
+
// confirmed by composing: synchronously for a caller that builds once, and off
|
|
35
|
+
// the event loop, in a worker with a deadline, for the engine and the proposal
|
|
36
|
+
// adapter it runs (`validatePersonalWorkspace`). What no key names is asked of
|
|
37
|
+
// the composition at every full reconciliation.
|
|
38
|
+
//
|
|
39
|
+
// What a binding returns:
|
|
40
|
+
// project the generation's atelier.project.json, resolved, with the
|
|
41
|
+
// Obsidian settings member built in memory from the overlay's
|
|
42
|
+
// saved views (each a scope) plus an `everything` scope, and the
|
|
43
|
+
// frozen binding under one symbol key. No generation file is
|
|
44
|
+
// ever written.
|
|
45
|
+
// binding { personalHome, generationId, overlayRepoId }, frozen.
|
|
46
|
+
// preferences the overlay's preferences, read only. A theme is reported,
|
|
47
|
+
// never written: the vault's appearance is the person's.
|
|
48
|
+
// coverage passed through as the module returned it. Nothing here reads
|
|
49
|
+
// it: it describes what is not enforced, and is never permission.
|
|
50
|
+
//
|
|
51
|
+
// The project's own folder is the private home rather than the generation:
|
|
52
|
+
// the projection keeps its workspace pointer in `<folder>/.atelier-local/`,
|
|
53
|
+
// and a generation is a closed, verified inventory that any extra file
|
|
54
|
+
// invalidates. A home is outside every Git worktree, so its local state is
|
|
55
|
+
// ignored by construction.
|
|
56
|
+
|
|
57
|
+
const BINDING = Symbol('atelier.obsidian.personal-workspace-binding')
|
|
58
|
+
// What each binding was made with: the composition that admitted it, the build that serves its graph, and whether the
|
|
59
|
+
// composition can run in a worker (only the module's own can).
|
|
60
|
+
const ROUTES = new WeakMap()
|
|
61
|
+
// The last validity key the composition confirmed for each binding: { inputs, facts }.
|
|
62
|
+
const CONFIRMED = new WeakMap()
|
|
63
|
+
export const EVERYTHING_SCOPE_ID = 'everything'
|
|
64
|
+
const EXT_SCHEMA = 'atelier-obsidian-ext-settings/v1'
|
|
65
|
+
// The settings member of a bound project names its generation here. It survives any copy of the project (a structured
|
|
66
|
+
// clone, a JSON round trip) that drops the symbol-keyed binding, so such a copy is refused instead of being built as an
|
|
67
|
+
// ordinary project; and a rebound generation changes the member, which invalidates every view. An ordinary project's
|
|
68
|
+
// settings may not use this key.
|
|
69
|
+
export const PERSONAL_MEMBER_KEY = 'mnstry.atelier.personal-workspace'
|
|
70
|
+
// The Obsidian settings contract's limits: at most 256 scopes, one of them `everything`, and at most 256 members in a
|
|
71
|
+
// selector's union.
|
|
72
|
+
const MAX_SAVED_VIEWS = 255
|
|
73
|
+
const MAX_VIEW_REPOSITORIES = 256
|
|
74
|
+
// A build that found its validity key unconfirmed, for a caller that confirms it off the event loop and builds again.
|
|
75
|
+
export const PERSONAL_VALIDATION_PENDING = 'personal-validation-pending'
|
|
76
|
+
const WORKER_KIND = 'atelier-obsidian-personal-composition'
|
|
77
|
+
|
|
78
|
+
const sha256 = (bytes) => createHash('sha256').update(bytes).digest('hex')
|
|
79
|
+
const deepFreeze = (value) => { if (value && typeof value === 'object') { Object.values(value).forEach(deepFreeze); Object.freeze(value) } return value }
|
|
80
|
+
const sortKeys = (value) => (Array.isArray(value) ? value.map(sortKeys)
|
|
81
|
+
: value && typeof value === 'object' ? Object.fromEntries(Object.keys(value).sort().map((key) => [key, sortKeys(value[key])])) : value)
|
|
82
|
+
const digestOf = (value) => sha256(JSON.stringify(sortKeys(value)))
|
|
83
|
+
const generationRoot = ({ personalHome, generationId }) => path.join(personalHome, 'generations', generationId)
|
|
84
|
+
const linkTargetRule = (overlayRepoId) => (node) => node.repo !== overlayRepoId
|
|
85
|
+
|
|
86
|
+
// The facts of a graph every caller reads: nodes, edges, embeds, assets, resolved links and every finding.
|
|
87
|
+
const graphFactsDigest = (graph) => digestOf([graph.nodes, graph.edges, graph.embeds ?? [], graph.assets ?? [], graph.links ?? [], graph.linkDiagnostics ?? [], graph.diagnostics ?? []])
|
|
88
|
+
|
|
89
|
+
// The personal-workspace module's read limits: an authored input, and a generation file.
|
|
90
|
+
const AUTHORED_LIMIT = 1024 * 1024
|
|
91
|
+
const GENERATION_LIMIT = 8 * 1024 * 1024
|
|
92
|
+
const NONBLOCK = fs.constants.O_NONBLOCK ?? 0
|
|
93
|
+
|
|
94
|
+
// One regular file, opened without following a link and without waiting on it (a FIFO or a device never blocks the
|
|
95
|
+
// caller), and read up to `limit`. Anything else refuses `code`, as the composition would.
|
|
96
|
+
function regularFileBytes(file, limit, code, { follow = false } = {}) {
|
|
97
|
+
let fd
|
|
98
|
+
try { fd = fs.openSync(file, fs.constants.O_RDONLY | (follow ? 0 : fs.constants.O_NOFOLLOW) | NONBLOCK) } catch (error) {
|
|
99
|
+
if (error.code === 'ENOENT' || error.code === 'ENOTDIR') return null
|
|
100
|
+
throw new PersonalWorkspaceRefusal(code)
|
|
101
|
+
}
|
|
102
|
+
try {
|
|
103
|
+
const stat = fs.fstatSync(fd)
|
|
104
|
+
if (!stat.isFile() || stat.size > limit) throw new PersonalWorkspaceRefusal(code)
|
|
105
|
+
return fs.readFileSync(fd)
|
|
106
|
+
} finally { fs.closeSync(fd) }
|
|
107
|
+
}
|
|
108
|
+
const regularFileDigest = (file, limit, code) => { const bytes = regularFileBytes(file, limit, code); return bytes === null ? 'missing' : sha256(bytes) }
|
|
109
|
+
// A Git configuration file, which Git itself reads through a link (a managed dotfile): followed, but still only a
|
|
110
|
+
// regular file, bounded, and never waited on.
|
|
111
|
+
const configDigest = (file) => { try { const bytes = regularFileBytes(file, AUTHORED_LIMIT, 'malformed-input', { follow: true }); return bytes === null ? 'missing' : sha256(bytes) } catch { return 'not-regular' } }
|
|
112
|
+
// The configuration files of an enrolled repository: `.git/config` (through `.git` when it is a link to a directory),
|
|
113
|
+
// or, where `.git` is a file (a separate Git directory, a linked worktree, a submodule), the configuration of the
|
|
114
|
+
// directory it names and of its common directory. A root that cannot be looked at (a file, a link loop, no permission)
|
|
115
|
+
// is recorded as such, so the composition answers with its own code.
|
|
116
|
+
function repositoryConfigDigests(repoRoot) {
|
|
117
|
+
const dotGit = path.join(repoRoot, '.git')
|
|
118
|
+
let kind
|
|
119
|
+
try { kind = fs.lstatSync(dotGit, { throwIfNoEntry: false }) } catch (error) { return [`unreadable:${error.code ?? 'error'}`] }
|
|
120
|
+
if (kind === undefined) return ['missing']
|
|
121
|
+
if (kind.isDirectory()) return [configDigest(path.join(dotGit, 'config'))]
|
|
122
|
+
if (kind.isSymbolicLink()) {
|
|
123
|
+
try { if (fs.statSync(dotGit).isDirectory()) return ['linked', fs.realpathSync(dotGit), configDigest(path.join(dotGit, 'config'))] } catch (error) { return [`unreadable:${error.code ?? 'error'}`] }
|
|
124
|
+
}
|
|
125
|
+
let pointer
|
|
126
|
+
try { pointer = regularFileBytes(dotGit, AUTHORED_LIMIT, 'malformed-input', { follow: true }) } catch { return ['not-regular'] }
|
|
127
|
+
const named = /^gitdir:\s*(.+?)\s*$/m.exec(pointer?.toString('utf8') ?? '')
|
|
128
|
+
if (named === null) return ['unnamed']
|
|
129
|
+
const gitDir = path.resolve(repoRoot, named[1])
|
|
130
|
+
let commonDir = null
|
|
131
|
+
try { const common = regularFileBytes(path.join(gitDir, 'commondir'), AUTHORED_LIMIT, 'malformed-input', { follow: true }); if (common !== null) commonDir = path.resolve(gitDir, common.toString('utf8').trim()) } catch { commonDir = 'not-regular' }
|
|
132
|
+
return [gitDir, configDigest(path.join(gitDir, 'config')), commonDir, commonDir === null || commonDir === 'not-regular' ? null : configDigest(path.join(commonDir, 'config'))]
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Everything outside the graph that a composition's answer depends on and a stat or a bounded read can see: the authored
|
|
136
|
+
// inputs (regular files only), every file of the generation (an extra, missing or irregular one too), the private home's
|
|
137
|
+
// own state and any `.git` above it, each enrolled root's link status and Git configuration (its remote and its helpers),
|
|
138
|
+
// the user's Git configuration, and any ambient Git variable. A file that is not regular refuses here with the code the
|
|
139
|
+
// composition gives it, before anything waits on it.
|
|
140
|
+
function inputsDigest({ personalHome, generationId }) {
|
|
141
|
+
const stat = (file) => { try { const s = fs.lstatSync(file); return [s.mode, s.uid, s.ino, s.isSymbolicLink()] } catch (error) { return `missing:${error.code ?? 'error'}` } }
|
|
142
|
+
const authoredBytes = ['atelier.personal.json', 'atelier.overlay.json'].map((name) => regularFileBytes(path.join(personalHome, name), AUTHORED_LIMIT, 'malformed-input'))
|
|
143
|
+
const authored = ['atelier.personal.json', 'atelier.overlay.json'].map((name, index) => [stat(path.join(personalHome, name)), authoredBytes[index] === null ? 'missing' : sha256(authoredBytes[index])])
|
|
144
|
+
const generation = []
|
|
145
|
+
const walk = (directory, prefix) => {
|
|
146
|
+
let entries
|
|
147
|
+
try { entries = fs.readdirSync(directory, { withFileTypes: true }) } catch (error) { generation.push([prefix, `unreadable:${error.code ?? 'error'}`]); return }
|
|
148
|
+
for (const entry of entries.sort((left, right) => (left.name < right.name ? -1 : left.name > right.name ? 1 : 0))) {
|
|
149
|
+
const relative = prefix === '' ? entry.name : `${prefix}/${entry.name}`
|
|
150
|
+
const absolute = path.join(directory, entry.name)
|
|
151
|
+
if (entry.isDirectory()) { generation.push([relative, stat(absolute)]); walk(absolute, relative) } else if (entry.isFile()) generation.push([relative, regularFileDigest(absolute, GENERATION_LIMIT, 'generation-corrupt')])
|
|
152
|
+
else throw new PersonalWorkspaceRefusal('generation-corrupt')
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const root = generationRoot({ personalHome, generationId })
|
|
156
|
+
walk(root, '')
|
|
157
|
+
let realHome
|
|
158
|
+
try { realHome = fs.realpathSync(personalHome) === personalHome } catch { realHome = false }
|
|
159
|
+
const ancestorsWithGit = []
|
|
160
|
+
for (let ancestor = personalHome; ; ancestor = path.dirname(ancestor)) {
|
|
161
|
+
if (fs.lstatSync(path.join(ancestor, '.git'), { throwIfNoEntry: false }) !== undefined) ancestorsWithGit.push(ancestor)
|
|
162
|
+
if (ancestor === path.dirname(ancestor)) break
|
|
163
|
+
}
|
|
164
|
+
let repos = []
|
|
165
|
+
// The bytes read above, never the file again.
|
|
166
|
+
try { repos = JSON.parse(authoredBytes[0]?.toString('utf8') ?? '{}').repos.filter((repo) => repo.enrolled).map((repo) => repo.root) } catch { repos = [] }
|
|
167
|
+
const enrolled = repos.map((repoRoot) => {
|
|
168
|
+
let real
|
|
169
|
+
try { real = fs.realpathSync(repoRoot) === repoRoot } catch { real = false }
|
|
170
|
+
return [repoRoot, stat(repoRoot), real, repositoryConfigDigests(repoRoot)]
|
|
171
|
+
})
|
|
172
|
+
const home = process.env.HOME
|
|
173
|
+
const userGit = typeof home === 'string' && path.isAbsolute(home)
|
|
174
|
+
? [path.join(home, '.gitconfig'), path.join(process.env.XDG_CONFIG_HOME || path.join(home, '.config'), 'git', 'config')].map(configDigest)
|
|
175
|
+
: ['no-home']
|
|
176
|
+
return digestOf({
|
|
177
|
+
authored,
|
|
178
|
+
generation,
|
|
179
|
+
roots: [stat(personalHome), realHome, stat(path.join(personalHome, 'generations')), stat(path.join(personalHome, 'generations', '.git')), stat(root), ancestorsWithGit],
|
|
180
|
+
enrolled,
|
|
181
|
+
userGit,
|
|
182
|
+
ambientGit: Object.keys(process.env).filter((name) => /^GIT_/i.test(name)).sort(),
|
|
183
|
+
})
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// One generation file, read without following a link, and checked against the
|
|
187
|
+
// digest the generation records for it (and compose verified a moment ago).
|
|
188
|
+
function generationFile(generation, final, relative) {
|
|
189
|
+
const listed = generation.files.find((file) => file.path === relative)
|
|
190
|
+
if (!listed) throw new PersonalWorkspaceRefusal('generation-corrupt')
|
|
191
|
+
let bytes
|
|
192
|
+
try {
|
|
193
|
+
const fd = fs.openSync(path.join(final, ...relative.split('/')), fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW)
|
|
194
|
+
try { bytes = fs.readFileSync(fd) } finally { fs.closeSync(fd) }
|
|
195
|
+
} catch { throw new PersonalWorkspaceRefusal('generation-corrupt') }
|
|
196
|
+
if (sha256(bytes) !== listed.sha256) throw new PersonalWorkspaceRefusal('generation-corrupt')
|
|
197
|
+
return bytes
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function selectorFor(repoIds) {
|
|
201
|
+
const repos = [...new Set(repoIds)]
|
|
202
|
+
if (repos.length === 0) return { ids: [] }
|
|
203
|
+
if (repos.length === 1) return { repo: repos[0] }
|
|
204
|
+
return { union: repos.map((repo) => ({ repo })) }
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// The Obsidian settings member of a personal binding: one scoped view per saved
|
|
208
|
+
// selection of repositories, in the overlay's order, and one full view of the
|
|
209
|
+
// whole composed graph. A preferred view that names a saved one is the default.
|
|
210
|
+
// Refused, typed, where the overlay allows more than a projection can declare.
|
|
211
|
+
export function personalWorkspaceScopes(overlay, generationId) {
|
|
212
|
+
if (overlay.views.some((view) => view.id === EVERYTHING_SCOPE_ID)) {
|
|
213
|
+
refuse('personal-view-id-reserved', `a saved view may not be named "${EVERYTHING_SCOPE_ID}"; that view is the whole personal workspace`)
|
|
214
|
+
}
|
|
215
|
+
if (overlay.views.length > MAX_SAVED_VIEWS) refuse('personal-views-exceed-settings-limit', `a personal workspace can show at most ${MAX_SAVED_VIEWS} saved views; nothing is bound`, { limit: MAX_SAVED_VIEWS, kind: 'views' })
|
|
216
|
+
if (overlay.views.some((view) => new Set(view.repoIds).size > MAX_VIEW_REPOSITORIES)) {
|
|
217
|
+
refuse('personal-views-exceed-settings-limit', `a saved view can name at most ${MAX_VIEW_REPOSITORIES} repositories; nothing is bound`, { limit: MAX_VIEW_REPOSITORIES, kind: 'repositories' })
|
|
218
|
+
}
|
|
219
|
+
const scopes = [
|
|
220
|
+
...overlay.views.map((view) => ({ scopeId: view.id, mode: 'scoped', selector: selectorFor(view.repoIds) })),
|
|
221
|
+
{ scopeId: EVERYTHING_SCOPE_ID, mode: 'full', selector: { all: true } },
|
|
222
|
+
]
|
|
223
|
+
const preferred = overlay.preferences?.defaultView
|
|
224
|
+
const defaultScopeId = typeof preferred === 'string' && overlay.views.some((view) => view.id === preferred) ? preferred : EVERYTHING_SCOPE_ID
|
|
225
|
+
return { schema: EXT_SCHEMA, enabled: true, defaultScopeId, scopes, ext: { [PERSONAL_MEMBER_KEY]: { generationId } } }
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// The bound project, from a composition the caller just made: `composed` carries the generation's record, the
|
|
229
|
+
// preferences and the coverage, and the validity key that composition confirmed.
|
|
230
|
+
function assemble({ personalHome, generationId }, composed, route) {
|
|
231
|
+
const final = generationRoot({ personalHome, generationId })
|
|
232
|
+
const projectBytes = generationFile(composed.generation, final, 'atelier.project.json')
|
|
233
|
+
const inputs = JSON.parse(generationFile(composed.generation, final, 'inputs.json').toString('utf8'))
|
|
234
|
+
const configPath = path.join(final, 'atelier.project.json')
|
|
235
|
+
const resolved = resolveProjectConfig({ argv: [`--project-config=${configPath}`], cwd: final, env: { PATH: process.env.PATH }, writeLocalState: false })
|
|
236
|
+
// The same pinned resolution compose made; anything else means the generation changed between the two reads.
|
|
237
|
+
if (JSON.stringify(resolved.config) !== JSON.stringify(JSON.parse(projectBytes.toString('utf8')))) throw new PersonalWorkspaceRefusal('generation-corrupt')
|
|
238
|
+
if (validateProjectConfigDoc(resolved.config).length > 0) throw new PersonalWorkspaceRefusal('generation-corrupt')
|
|
239
|
+
if (resolved.localOverlay.paths.length > 0 || resolved.repos.some((repo) => repo.pathSource !== 'tracked-config')) throw new PersonalWorkspaceRefusal('ambient-overlay-present')
|
|
240
|
+
const overlayRepoId = `personal-${inputs.manifest.workspaceId}`
|
|
241
|
+
const overlayRepo = resolved.repos.find((repo) => repo.name === overlayRepoId)
|
|
242
|
+
if (!overlayRepo || overlayRepo.path !== path.join(final, 'overlay')) throw new PersonalWorkspaceRefusal('generation-corrupt')
|
|
243
|
+
|
|
244
|
+
const binding = Object.freeze({ personalHome, generationId, overlayRepoId })
|
|
245
|
+
ROUTES.set(binding, route)
|
|
246
|
+
CONFIRMED.set(binding, composed.key)
|
|
247
|
+
const member = deepFreeze(personalWorkspaceScopes(inputs.overlay, generationId))
|
|
248
|
+
const project = {
|
|
249
|
+
...resolved,
|
|
250
|
+
config: { ...resolved.config, ext: { ...(resolved.config.ext ?? {}), [OBSIDIAN_EXT_KEY]: member } },
|
|
251
|
+
configDir: personalHome,
|
|
252
|
+
[BINDING]: binding,
|
|
253
|
+
}
|
|
254
|
+
project.localState = ensureLocalState(project, { write: false, env: { PATH: process.env.PATH } })
|
|
255
|
+
return Object.freeze({ project, binding, preferences: deepFreeze(structuredClone(composed.preferences ?? {})), coverage: composed.coverage })
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// A composition in this thread, as the summary a worker sends back.
|
|
259
|
+
// The inputs are digested BEFORE composing: a single change that lands while the composition runs then either is seen by
|
|
260
|
+
// it, or leaves the confirmed key behind, so the next build composes again. A change and its exact undo within one
|
|
261
|
+
// composition (A, then B, then A again) is not seen this way: it is caught at the next full reconciliation.
|
|
262
|
+
function composeHere(compose, binding) {
|
|
263
|
+
const inputs = inputsDigest(binding)
|
|
264
|
+
const composed = compose({ personalHome: binding.personalHome, generationId: binding.generationId })
|
|
265
|
+
return { generation: composed.generation, preferences: composed.preferences, coverage: composed.coverage, key: { inputs, facts: graphFactsDigest(composed.graph) } }
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// A composition in a worker thread, so the event loop of the caller stays free while it runs. Resolves to the summary, or
|
|
269
|
+
// to `{ code }` for a refusal; a worker that cannot run answers `personal-composition-unavailable`.
|
|
270
|
+
// A worker that does not answer within the deadline is terminated and let go of, and the composition refuses
|
|
271
|
+
// `personal-composition-unavailable`: the tick does not wait on it. A worker blocked in a system call (a FIFO opened
|
|
272
|
+
// for reading, say) cannot be stopped until that call returns: until then it keeps the process from exiting, and each
|
|
273
|
+
// such expiry leaves one thread behind. A service stopped in that state needs a kill.
|
|
274
|
+
export const WORKER_DEADLINE_MS = 120 * 1000
|
|
275
|
+
function composeInWorker({ personalHome, generationId }, { deadlineMs = WORKER_DEADLINE_MS, stallMs = 0 } = {}) {
|
|
276
|
+
return new Promise((resolve) => {
|
|
277
|
+
let settled = false
|
|
278
|
+
let worker = null
|
|
279
|
+
let timer = null
|
|
280
|
+
const settle = (value) => {
|
|
281
|
+
if (settled) return
|
|
282
|
+
settled = true
|
|
283
|
+
clearTimeout(timer)
|
|
284
|
+
if (value.code === 'personal-composition-unavailable' && worker !== null) { worker.unref(); worker.terminate().catch(() => {}) }
|
|
285
|
+
resolve(value)
|
|
286
|
+
}
|
|
287
|
+
try { worker = new Worker(new URL(import.meta.url), { workerData: { kind: WORKER_KIND, personalHome, generationId, stallMs } }) } catch { settle({ code: 'personal-composition-unavailable' }); return }
|
|
288
|
+
timer = setTimeout(() => settle({ code: 'personal-composition-unavailable' }), deadlineMs)
|
|
289
|
+
timer.unref?.()
|
|
290
|
+
worker.once('message', settle)
|
|
291
|
+
worker.once('error', () => settle({ code: 'personal-composition-unavailable' }))
|
|
292
|
+
worker.once('exit', () => settle({ code: 'personal-composition-unavailable' }))
|
|
293
|
+
})
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Test seam only: `compose` replaces the module's composePersonalWorkspace for the binding and every validation of
|
|
297
|
+
// it, `build` the canonical build that serves its graph, and `worker` the worker's deadline (and a stall, so a worker that
|
|
298
|
+
// never answers can be shown). Production code uses bindPersonalWorkspace.
|
|
299
|
+
export function createPersonalWorkspaceBinderForOracleTests({ compose = composePersonalWorkspace, build = null, worker = {} } = {}) {
|
|
300
|
+
const route = { compose, offThread: compose === composePersonalWorkspace, build, worker }
|
|
301
|
+
const bind = ({ personalHome, generationId } = {}) => {
|
|
302
|
+
// The validity authority first: a refusal propagates as the module's own typed refusal, and nothing is read or
|
|
303
|
+
// written after it.
|
|
304
|
+
const composed = composeHere(compose, { personalHome, generationId })
|
|
305
|
+
return assemble({ personalHome, generationId }, composed, route)
|
|
306
|
+
}
|
|
307
|
+
// The same binding, with the composition off the event loop.
|
|
308
|
+
bind.offThread = async ({ personalHome, generationId } = {}) => {
|
|
309
|
+
const composed = route.offThread ? await composeInWorker({ personalHome, generationId }, route.worker) : composeHere(compose, { personalHome, generationId })
|
|
310
|
+
if (typeof composed.code === 'string') {
|
|
311
|
+
if (composed.code === 'personal-composition-unavailable') refuse(composed.code, 'the personal workspace could not be composed off the event loop; nothing is bound')
|
|
312
|
+
throw new PersonalWorkspaceRefusal(composed.code)
|
|
313
|
+
}
|
|
314
|
+
return assemble({ personalHome, generationId }, composed, route)
|
|
315
|
+
}
|
|
316
|
+
return bind
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
export const bindPersonalWorkspace = createPersonalWorkspaceBinderForOracleTests()
|
|
320
|
+
|
|
321
|
+
// A refusal of the personal-workspace module becomes the maintenance refusal with the same code, as at every build, so
|
|
322
|
+
// the engine and every command report it typed and no view stays current.
|
|
323
|
+
const asMaintenanceRefusal = (error, message) => {
|
|
324
|
+
if (error instanceof PersonalWorkspaceRefusal) refuse(error.code, message, { source: 'personal-workspace' })
|
|
325
|
+
throw error
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// The project of a binding, for a loader that may block (a command run once).
|
|
329
|
+
export function loadBoundProject({ personalHome, generationId } = {}, { bind = bindPersonalWorkspace } = {}) {
|
|
330
|
+
try { return bind({ personalHome, generationId }).project } catch (error) { return asMaintenanceRefusal(error, 'the personal workspace refused this generation; nothing is loaded from it') }
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// The project of a binding, for a loader on a service's event loop: the composition runs in a worker. The engine
|
|
334
|
+
// awaits its loader, so this is the loader a service uses.
|
|
335
|
+
export async function loadBoundProjectOffThread({ personalHome, generationId } = {}, { bind = bindPersonalWorkspace } = {}) {
|
|
336
|
+
try { return (await bind.offThread({ personalHome, generationId })).project } catch (error) { return asMaintenanceRefusal(error, 'the personal workspace refused this generation; nothing is loaded from it') }
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// The binding a project carries, or null for every other project.
|
|
340
|
+
export function personalWorkspaceBindingOf(project) {
|
|
341
|
+
return project !== null && typeof project === 'object' && Object.hasOwn(project, BINDING) ? project[BINDING] : null
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// The graph of a bound project, or null when the project carries no binding. `fileCache` and `observedDigest` are the
|
|
345
|
+
// caller's, as for any build. With `defer`, a key the composition has not confirmed is not confirmed here: the build
|
|
346
|
+
// refuses `personal-validation-pending`, and the caller confirms it off the event loop (validatePersonalWorkspace) and
|
|
347
|
+
// builds again.
|
|
348
|
+
export function personalWorkspaceGraph(project, { fileCache = null, observedDigest = null, defer = false } = {}) {
|
|
349
|
+
const binding = personalWorkspaceBindingOf(project)
|
|
350
|
+
if (binding === null) {
|
|
351
|
+
if (project?.config?.ext?.[OBSIDIAN_EXT_KEY]?.ext?.[PERSONAL_MEMBER_KEY] !== undefined) {
|
|
352
|
+
refuse('personal-binding-lost', `the project's Obsidian settings use the reserved key ext["${PERSONAL_MEMBER_KEY}"], which only a personal-workspace binding sets, and the project carries no binding; nothing is prepared from it`)
|
|
353
|
+
}
|
|
354
|
+
return null
|
|
355
|
+
}
|
|
356
|
+
const route = ROUTES.get(binding)
|
|
357
|
+
if (route === undefined) refuse('personal-binding-unrecognized', 'the project carries a personal-workspace binding this release did not make; nothing is prepared from it')
|
|
358
|
+
if (project.configPath !== path.join(generationRoot(binding), 'atelier.project.json')) refuse('personal-binding-mismatch', 'the project is not the configuration of the generation it is bound to; nothing is prepared from it')
|
|
359
|
+
const pending = () => refuse(PERSONAL_VALIDATION_PENDING, 'the personal workspace has changed since it was last composed; it is composed again before a graph is used', { source: 'personal-workspace-pending' })
|
|
360
|
+
const confirmed = CONFIRMED.get(binding)
|
|
361
|
+
// Inputs that changed are composed before anything is built from them.
|
|
362
|
+
let inputs
|
|
363
|
+
try { inputs = inputsDigest(binding) } catch (error) { CONFIRMED.delete(binding); asMaintenanceRefusal(error, 'the personal workspace refused this generation; nothing is prepared from it') }
|
|
364
|
+
if (confirmed?.inputs !== inputs) {
|
|
365
|
+
if (defer) pending()
|
|
366
|
+
confirmHere(route, binding)
|
|
367
|
+
}
|
|
368
|
+
const build = route.build ?? ((target, options) => buildCanonicalGraph(target, { ...options, isLinkTargetEligible: linkTargetRule(binding.overlayRepoId) }))
|
|
369
|
+
const graph = build(project, { fileCache, observedDigest })
|
|
370
|
+
if (CONFIRMED.get(binding)?.facts === graphFactsDigest(graph)) return graph
|
|
371
|
+
if (defer) pending()
|
|
372
|
+
confirmHere(route, binding)
|
|
373
|
+
if (CONFIRMED.get(binding)?.facts === graphFactsDigest(graph)) return graph
|
|
374
|
+
// Composed just now, and still another graph: the sources moved between the two reads, or the build is not the
|
|
375
|
+
// composition's. Nothing is prepared from it.
|
|
376
|
+
return refuse('personal-graph-unconfirmed', 'the graph built for the personal workspace is not the one its composition built; nothing is prepared from it', { source: 'personal-workspace' })
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function confirmHere(route, binding) {
|
|
380
|
+
try { CONFIRMED.set(binding, composeHere(route.compose, binding).key) } catch (error) { CONFIRMED.delete(binding); asMaintenanceRefusal(error, 'the personal workspace refused this generation; nothing is prepared from it') }
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Asks the composition again, off the event loop when it can: `{ ok: true, changed }` when it accepts the generation
|
|
384
|
+
// (`changed` when its key differs from the one last confirmed), `{ ok: false, code }` when it refuses. Null for a project
|
|
385
|
+
// without a binding. The engine asks this before a build that found its key unconfirmed, and at every full
|
|
386
|
+
// reconciliation of a bound project.
|
|
387
|
+
export async function validatePersonalWorkspace(project) {
|
|
388
|
+
const binding = personalWorkspaceBindingOf(project)
|
|
389
|
+
if (binding === null) return null
|
|
390
|
+
const route = ROUTES.get(binding)
|
|
391
|
+
if (route === undefined) return { ok: false, code: 'personal-binding-unrecognized' }
|
|
392
|
+
let composed
|
|
393
|
+
if (route.offThread) composed = await composeInWorker(binding, route.worker)
|
|
394
|
+
else {
|
|
395
|
+
try { composed = composeHere(route.compose, binding) } catch (error) { if (!(error instanceof PersonalWorkspaceRefusal)) throw error; composed = { code: error.code } }
|
|
396
|
+
}
|
|
397
|
+
if (typeof composed.code === 'string') { CONFIRMED.delete(binding); return { ok: false, code: composed.code } }
|
|
398
|
+
const previous = CONFIRMED.get(binding)
|
|
399
|
+
CONFIRMED.set(binding, composed.key)
|
|
400
|
+
return { ok: true, changed: previous?.inputs !== composed.key.inputs || previous?.facts !== composed.key.facts }
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// The authored and generation files whose change means the binding must be asked again: the manifest, the overlay and
|
|
404
|
+
// the bound generation's record. Observed as configuration by the engine. Empty for any other project.
|
|
405
|
+
export function personalWorkspaceInputs(project) {
|
|
406
|
+
const binding = personalWorkspaceBindingOf(project)
|
|
407
|
+
if (binding === null) return []
|
|
408
|
+
return [path.join(binding.personalHome, 'atelier.personal.json'), path.join(binding.personalHome, 'atelier.overlay.json'), path.join(generationRoot(binding), 'generation.json')]
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// The repository of a bound project's private notes, or null. A vault edit to one of them is never applied into a
|
|
412
|
+
// module-owned generation, and is not proposed yet: it stays held in the vault.
|
|
413
|
+
export function personalOverlayRepoOf(project) {
|
|
414
|
+
return personalWorkspaceBindingOf(project)?.overlayRepoId ?? null
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// Settings that a personal binding takes from the overlay or does not have yet. A saved view is authored in the
|
|
418
|
+
// overlay, so `view add` would write a module-owned generation file; who may see and where vaults live wait for a
|
|
419
|
+
// stable per-person projection configuration. Refused before anything is resolved or written.
|
|
420
|
+
export function refuseOnPersonalBinding(project, operation) {
|
|
421
|
+
if (personalWorkspaceBindingOf(project) === null) return
|
|
422
|
+
if (operation === 'view add') refuse('views-from-personal-overlay', 'the views of a personal workspace come from its overlay; nothing was written', { operation })
|
|
423
|
+
refuse('personal-binding-decision-unavailable', `${operation} is not available for a personal workspace yet; nothing was written`, { operation })
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// The worker side of composeInWorker: one composition, its summary, and exit.
|
|
427
|
+
if (!isMainThread && workerData?.kind === WORKER_KIND) {
|
|
428
|
+
const binding = { personalHome: workerData.personalHome, generationId: workerData.generationId }
|
|
429
|
+
// A worker that never answers, for the deadline test only: blocked as a system call would block it.
|
|
430
|
+
if (workerData.stallMs > 0) Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, workerData.stallMs)
|
|
431
|
+
try {
|
|
432
|
+
parentPort.postMessage(composeHere(composePersonalWorkspace, binding))
|
|
433
|
+
} catch (error) {
|
|
434
|
+
parentPort.postMessage({ code: error instanceof PersonalWorkspaceRefusal ? error.code : 'personal-composition-unavailable' })
|
|
435
|
+
}
|
|
436
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
4
|
+
import { refuse, sha256Digest } from '../materialize/byte-lens.mjs'
|
|
5
|
+
import { PLUGIN_DATA_FILE, PLUGIN_DATA_MODE, PLUGIN_DIRECTORY, PLUGIN_ID, PLUGIN_MINIMUM_APP_VERSION, PLUGIN_SOURCE_FILES, PLUGIN_SOURCE_MODE, pluginDataBytes } from './channel.mjs'
|
|
6
|
+
|
|
7
|
+
// The files of Atelier's plugin as they go into one vault: the three shipped
|
|
8
|
+
// source files, byte for byte, and that vault's data file. Pure apart from
|
|
9
|
+
// reading the shipped source once; nothing here writes anywhere.
|
|
10
|
+
|
|
11
|
+
export const PLUGIN_SOURCE_ROOT = fileURLToPath(new URL('../../../../plugins/obsidian/', import.meta.url))
|
|
12
|
+
|
|
13
|
+
export { PLUGIN_DATA_MODE, PLUGIN_SOURCE_MODE }
|
|
14
|
+
|
|
15
|
+
let cached = null
|
|
16
|
+
|
|
17
|
+
// { version, files: [{ name, bytes }] } of the shipped plugin. The manifest
|
|
18
|
+
// must name this plugin, the protocol's app floor and a desktop-only plugin.
|
|
19
|
+
export function readPluginSource({ root = PLUGIN_SOURCE_ROOT } = {}) {
|
|
20
|
+
if (root === PLUGIN_SOURCE_ROOT && cached) return cached
|
|
21
|
+
const files = PLUGIN_SOURCE_FILES.map((name) => ({ name, bytes: fs.readFileSync(path.join(root, name)) }))
|
|
22
|
+
let manifest
|
|
23
|
+
try { manifest = JSON.parse(files.find((file) => file.name === 'manifest.json').bytes.toString('utf8')) } catch { refuse('plugin-source-invalid', 'the shipped plugin manifest is not JSON') }
|
|
24
|
+
if (manifest?.id !== PLUGIN_ID || manifest.minAppVersion !== PLUGIN_MINIMUM_APP_VERSION || manifest.isDesktopOnly !== true || typeof manifest.version !== 'string' || manifest.version === '') {
|
|
25
|
+
refuse('plugin-source-invalid', 'the shipped plugin manifest does not name this plugin, its app floor and a desktop-only plugin')
|
|
26
|
+
}
|
|
27
|
+
const source = Object.freeze({ version: manifest.version, files: Object.freeze(files) })
|
|
28
|
+
if (root === PLUGIN_SOURCE_ROOT) cached = source
|
|
29
|
+
return source
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Prepared files of kind `plugin` for one vault, and the record that pins
|
|
33
|
+
// their digests in the generation manifest. `channel` is where the service of
|
|
34
|
+
// the workspace listens; `bearer` is this vault's. With `onlyIfPresent` every
|
|
35
|
+
// file is kept current where it exists and never created: the person turned
|
|
36
|
+
// the plugin off, and a folder they removed stays removed.
|
|
37
|
+
export function preparePluginFiles({ channel, scopeId, bearer, source = readPluginSource(), onlyIfPresent = false }) {
|
|
38
|
+
const presence = onlyIfPresent ? { onlyIfPresent: true } : {}
|
|
39
|
+
const files = [
|
|
40
|
+
...source.files.map(({ name, bytes }) => ({ path: `${PLUGIN_DIRECTORY}/${name}`, kind: 'plugin', bytes, digest: sha256Digest(bytes), mode: PLUGIN_SOURCE_MODE, ...presence })),
|
|
41
|
+
(() => {
|
|
42
|
+
const bytes = pluginDataBytes({ host: channel?.host, port: channel?.port, scopeId, bearer })
|
|
43
|
+
return { path: `${PLUGIN_DIRECTORY}/${PLUGIN_DATA_FILE}`, kind: 'plugin', bytes, digest: sha256Digest(bytes), mode: PLUGIN_DATA_MODE, ...presence }
|
|
44
|
+
})(),
|
|
45
|
+
]
|
|
46
|
+
return {
|
|
47
|
+
files,
|
|
48
|
+
ownership: { id: PLUGIN_ID, version: source.version, directory: PLUGIN_DIRECTORY, files: files.map(({ path, digest, bytes }) => ({ path, digest, byteLength: bytes.length })) },
|
|
49
|
+
}
|
|
50
|
+
}
|