@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,8 +1,12 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto'
|
|
2
2
|
import { unclosedFenceAtEnd } from '../../../graph/knowledge-graph.mjs'
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
DERIVED_RELATION_TYPE, IDENTITY_KEYS, RELATION_TYPES, SCOPE_PRIMITIVES, assertObsidianContract, identityLineTexts, identitySuffix, manifestLayoutVersion, readableTitle,
|
|
5
|
+
selectScope,
|
|
6
|
+
} from '../contracts.mjs'
|
|
4
7
|
import { assertStrictUtf8, readMarkdownLens, refuse, sha256Digest } from './byte-lens.mjs'
|
|
5
|
-
import {
|
|
8
|
+
import { allocateLegacyPaths, allocateViewPaths, collisionKey, titleWithinBudget, viewsOfRegistry, withViewSection } from './path-registry.mjs'
|
|
9
|
+
import { REDACTION_RULES, assertOnlyVaultIdentities, assertViewRedaction, createDenyMatcher } from './redaction.mjs'
|
|
6
10
|
import { isUserOwnedSettingsPath, prepareSettings } from './settings.mjs'
|
|
7
11
|
|
|
8
12
|
// prepareView: a pure preparation of one Obsidian view. It reads sources
|
|
@@ -13,25 +17,38 @@ import { isUserOwnedSettingsPath, prepareSettings } from './settings.mjs'
|
|
|
13
17
|
// An embedded asset is copied only when the canonical graph resolved it; the
|
|
14
18
|
// emitter never infers one from authored bytes.
|
|
15
19
|
//
|
|
20
|
+
// A view is laid out in vault layout 2: folders mirror the repository, the
|
|
21
|
+
// file name is the note's title and each note names its identity in generated
|
|
22
|
+
// front-matter properties (see "Vault layout" in docs/obsidian-contract.md).
|
|
23
|
+
// Layout 1, the earlier release's, is kept for a view whose prior generation
|
|
24
|
+
// is in layout 1 and holds a note with an open edit, and for recovering a
|
|
25
|
+
// layout 1 generation as it was published.
|
|
26
|
+
//
|
|
16
27
|
// Redaction happens where bytes are made. Every title, path and identity that
|
|
17
28
|
// reaches an output is looked up through `vault`, the set selectScope returned
|
|
18
|
-
// for this view, so a node outside it cannot be serialized by any branch
|
|
29
|
+
// for this view, so a node outside it cannot be serialized by any branch; the
|
|
30
|
+
// redaction guard checks the whole result before anything is returned.
|
|
19
31
|
//
|
|
20
32
|
// Preparation is incremental when the caller passes a preparation cache (see
|
|
21
33
|
// createPreparationCache). Every note's bytes and manifest entry are a pure
|
|
22
|
-
// function of a small set of inputs: the pinned source digest, the
|
|
23
|
-
// record, the allocated
|
|
24
|
-
// and the rewritten occurrences with their emitted targets. Those inputs
|
|
25
|
-
// serialized into a dependency key per note; a note whose key matches the
|
|
34
|
+
// function of a small set of inputs: the layout, the pinned source digest, the
|
|
35
|
+
// node record, the allocated paths, the generated rows, the outside-selection
|
|
36
|
+
// count and the rewritten occurrences with their emitted targets. Those inputs
|
|
37
|
+
// are serialized into a dependency key per note; a note whose key matches the
|
|
26
38
|
// cached one reuses the cached bytes and manifest entry instead of being
|
|
27
39
|
// emitted again, so the result is byte-identical to a full preparation by
|
|
28
40
|
// construction. The cache holds derived state only and can be dropped at any
|
|
29
41
|
// time; the redaction guard still runs over the whole result.
|
|
30
42
|
|
|
31
|
-
export const EMITTER_VERSION = '
|
|
43
|
+
export const EMITTER_VERSION = '2.0.0'
|
|
44
|
+
export const VAULT_LAYOUTS = Object.freeze({
|
|
45
|
+
1: Object.freeze({ version: 1, emitterVersion: '1.0.0', manifestSchema: 'atelier-obsidian-generation-manifest/v1', contractVersion: '1.0.0' }),
|
|
46
|
+
2: Object.freeze({ version: 2, emitterVersion: EMITTER_VERSION, manifestSchema: 'atelier-obsidian-generation-manifest/v2', contractVersion: '2.0.0' }),
|
|
47
|
+
})
|
|
48
|
+
export const CURRENT_VAULT_LAYOUT = 2
|
|
32
49
|
const EXT_KEY = 'mnstry.atelier.obsidian'
|
|
33
50
|
const EMBEDDABLE = new Set(['pdf', 'png', 'jpg', 'jpeg', 'gif', 'svg', 'webp'])
|
|
34
|
-
// Bytes left for an asset's readable stem beside the longest suffix and extension.
|
|
51
|
+
// Bytes left for an asset's readable stem beside the longest suffix and extension (layout 1).
|
|
35
52
|
const ASSET_STEM_BYTE_BUDGET = 255 - '--'.length - 64 - '.'.length - 16
|
|
36
53
|
const RELATIONS_HEADING = '## Relations (generated)\n\n%% Generated from the canonical graph. Edits to this section are not applied to any source. %%\n\n'
|
|
37
54
|
|
|
@@ -55,7 +72,22 @@ function encodeHref(fileName) {
|
|
|
55
72
|
return encodeURIComponent(fileName).replace(/[()']/g, (character) => `%${character.charCodeAt(0).toString(16).toUpperCase()}`)
|
|
56
73
|
}
|
|
57
74
|
|
|
58
|
-
|
|
75
|
+
// A vault path as a Markdown link target: every segment percent-encoded.
|
|
76
|
+
const encodeVaultPath = (vaultPath) => vaultPath.split('/').map(encodeHref).join('/')
|
|
77
|
+
|
|
78
|
+
const legacyBasename = (notePathValue) => notePathValue.slice('notes/'.length, -'.md'.length)
|
|
79
|
+
|
|
80
|
+
// How a link names a note: layout 1 by its unique basename, layout 2 by its
|
|
81
|
+
// full vault path, which the app looks up as an exact path before any other
|
|
82
|
+
// match. A wikilink keeps `.md` so a note named after a file never resolves
|
|
83
|
+
// to the file.
|
|
84
|
+
function noteLinkTarget(layout, notePathValue) {
|
|
85
|
+
if (layout.version === 1) {
|
|
86
|
+
const stem = legacyBasename(notePathValue)
|
|
87
|
+
return { markdown: encodeHref(`${stem}.md`), wikilink: stem }
|
|
88
|
+
}
|
|
89
|
+
return { markdown: encodeVaultPath(notePathValue), wikilink: notePathValue }
|
|
90
|
+
}
|
|
59
91
|
|
|
60
92
|
function edgeIdentifier(edge) {
|
|
61
93
|
return `e-${createHash('sha256').update(`${edge.source}\u0000${edge.type}\u0000${edge.target}`).digest('hex').slice(0, 32)}`
|
|
@@ -114,15 +146,19 @@ function isPreparationCache(cache) {
|
|
|
114
146
|
|
|
115
147
|
// Everything the emitted bytes and the manifest entry of one note depend on,
|
|
116
148
|
// in a canonical serialization. A cached note is reused only under an equal
|
|
117
|
-
// key. The emitter version
|
|
118
|
-
// produced it, should a cache ever outlive this module.
|
|
119
|
-
|
|
149
|
+
// key. The layout and emitter version are part of the key so an entry states
|
|
150
|
+
// which emitter produced it, should a cache ever outlive this module. The
|
|
151
|
+
// identity block and where it goes are functions of the node record and the
|
|
152
|
+
// source bytes, which the key holds through the pinned digest.
|
|
153
|
+
function dependencyKey({ layout, node, notePathValue, attachmentPathValue, pinned, rows, outsideCount, occurrences, emittedTarget }) {
|
|
120
154
|
// A wrapper note serializes the record's summary and tags; a Markdown note never does.
|
|
121
155
|
const wrapper = node.extension === 'md' ? null : JSON.stringify([String(node.summary ?? ''), Array.isArray(node.tags) ? node.tags : null])
|
|
122
156
|
const parts = [
|
|
123
|
-
|
|
157
|
+
layout.emitterVersion, notePathValue, node.repo, node.id, node.path, String(node.extension), String(node.title ?? ''), String(wrapper),
|
|
124
158
|
pinned.rawDigest, String(pinned.byteLength), String(outsideCount), rows.join(''),
|
|
125
159
|
]
|
|
160
|
+
// Layout 1 keys are exactly the earlier release's; layout 2 adds its own inputs after them.
|
|
161
|
+
if (layout.version !== 1) parts.push(`layout-${layout.version}`, String(attachmentPathValue ?? ''))
|
|
126
162
|
for (const occurrence of occurrences) {
|
|
127
163
|
const target = emittedTarget(occurrence)
|
|
128
164
|
parts.push(JSON.stringify([
|
|
@@ -211,6 +247,81 @@ function applyEdits({ source, from, to, edits, noteOffset }) {
|
|
|
211
247
|
return { bytes: Buffer.concat(parts), inversions }
|
|
212
248
|
}
|
|
213
249
|
|
|
250
|
+
// ---------------------------------------------------------------------------
|
|
251
|
+
// The identity block (layout 2)
|
|
252
|
+
// ---------------------------------------------------------------------------
|
|
253
|
+
|
|
254
|
+
const identityText = (node, eol) => identityLineTexts(node).map((line) => `${line}${eol}`).join('')
|
|
255
|
+
|
|
256
|
+
// The key of one line of a block mapping at column 0, or null when the line
|
|
257
|
+
// is not one: a quoted or plain key followed by `:` and a space, a tab or the
|
|
258
|
+
// end of the line.
|
|
259
|
+
function blockMappingKey(line) {
|
|
260
|
+
let key
|
|
261
|
+
let rest
|
|
262
|
+
if (line.startsWith('"')) {
|
|
263
|
+
const match = /^"((?:[^"\\]|\\.)*)"/.exec(line)
|
|
264
|
+
if (!match) return null
|
|
265
|
+
try { key = JSON.parse(`"${match[1]}"`) } catch { return null }
|
|
266
|
+
rest = line.slice(match[0].length)
|
|
267
|
+
} else if (line.startsWith("'")) {
|
|
268
|
+
const match = /^'((?:[^']|'')*)'/.exec(line)
|
|
269
|
+
if (!match) return null
|
|
270
|
+
key = match[1].replaceAll("''", "'")
|
|
271
|
+
rest = line.slice(match[0].length)
|
|
272
|
+
} else {
|
|
273
|
+
if (/^[-?:,[\]{}#&*!|>%@`]/.test(line)) return null
|
|
274
|
+
const colon = /:(?:[ \t]|$)/.exec(line)
|
|
275
|
+
if (!colon) return null
|
|
276
|
+
key = line.slice(0, colon.index).trimEnd()
|
|
277
|
+
rest = line.slice(colon.index)
|
|
278
|
+
if (key === '' || /[ \t]#/.test(key)) return null
|
|
279
|
+
}
|
|
280
|
+
return /^[ \t]*:(?:[ \t]|$)/.test(rest) ? key : null
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Whether identity lines can be appended to this front matter without changing
|
|
284
|
+
// what it means: a block mapping at column 0 (blank and comment lines aside,
|
|
285
|
+
// every line at column 0 is `key:`, everything else is indented under one) that
|
|
286
|
+
// does not already use one of the identity keys. Answers 'fits', 'author-keys'
|
|
287
|
+
// (the author wrote one of the three keys, which would then show in Properties
|
|
288
|
+
// instead of the generated one) or 'shape' (a flow or sequence root, an
|
|
289
|
+
// indented root, a directive or document marker, or a line that is no key).
|
|
290
|
+
function identityLinesFit(yaml) {
|
|
291
|
+
let first = true
|
|
292
|
+
let authorKeys = false
|
|
293
|
+
for (const raw of yaml.split('\n')) {
|
|
294
|
+
const line = raw.endsWith('\r') ? raw.slice(0, -1) : raw
|
|
295
|
+
if (line.trim() === '' || /^[ \t]*#/.test(line)) continue
|
|
296
|
+
if (/^[ \t]/.test(line)) {
|
|
297
|
+
if (first || line.startsWith('\t')) return 'shape'
|
|
298
|
+
continue
|
|
299
|
+
}
|
|
300
|
+
first = false
|
|
301
|
+
const key = blockMappingKey(line)
|
|
302
|
+
if (key === null) return 'shape'
|
|
303
|
+
if (IDENTITY_KEYS.includes(key)) authorKeys = true
|
|
304
|
+
}
|
|
305
|
+
return authorKeys ? 'author-keys' : 'fits'
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Where the identity lines of a Markdown note go, decided from the source
|
|
309
|
+
// bytes alone: appended to its front matter, in a generated front matter of
|
|
310
|
+
// their own, or, for a front matter that cannot take them, at the end.
|
|
311
|
+
function identityPlacement(source, lens) {
|
|
312
|
+
if (lens.frontmatter === null) {
|
|
313
|
+
const firstBreak = source.indexOf(0x0a, lens.body.start)
|
|
314
|
+
const eol = firstBreak > lens.body.start && source[firstBreak - 1] === 0x0d ? '\r\n' : '\n'
|
|
315
|
+
return { kind: 'generated-frontmatter', eol }
|
|
316
|
+
}
|
|
317
|
+
const { start, end } = lens.frontmatter
|
|
318
|
+
const openingEnd = source.indexOf(0x0a, start) + 1
|
|
319
|
+
const closingStart = source.lastIndexOf(0x0a, end - 2) + 1
|
|
320
|
+
const fit = identityLinesFit(source.subarray(openingEnd, closingStart).toString('utf8'))
|
|
321
|
+
if (fit !== 'fits') return { kind: 'tail', authorKeys: fit === 'author-keys' }
|
|
322
|
+
return { kind: 'frontmatter-lines', at: closingStart, eol: source[closingStart - 2] === 0x0d ? '\r\n' : '\n' }
|
|
323
|
+
}
|
|
324
|
+
|
|
214
325
|
// ---------------------------------------------------------------------------
|
|
215
326
|
// Generated sections
|
|
216
327
|
// ---------------------------------------------------------------------------
|
|
@@ -231,14 +342,14 @@ function titleRenderings() {
|
|
|
231
342
|
}
|
|
232
343
|
}
|
|
233
344
|
|
|
234
|
-
function relationRows({ node, outgoing, incoming, vaultNode, pathOf, titles }) {
|
|
345
|
+
function relationRows({ layout, node, outgoing, incoming, vaultNode, pathOf, titles }) {
|
|
235
346
|
const rows = []
|
|
236
347
|
for (const type of RELATION_TYPES) {
|
|
237
348
|
for (const edge of outgoing) {
|
|
238
349
|
if (edge.type !== type) continue
|
|
239
350
|
const target = vaultNode(edge.target)
|
|
240
351
|
if (!target) continue
|
|
241
|
-
rows.push(`- ${type} → [[${
|
|
352
|
+
rows.push(`- ${type} → [[${noteLinkTarget(layout, pathOf(target)).wikilink}|${titles.readableOf(target)}]]\n`)
|
|
242
353
|
}
|
|
243
354
|
// An incoming row is plain text: a link here would give this note an
|
|
244
355
|
// outgoing native link that the canonical graph does not hold.
|
|
@@ -269,7 +380,9 @@ function fenceClosureFor(source, finalNewline) {
|
|
|
269
380
|
return { fence: line, text: `${finalNewline === 'none' ? eol : ''}${line}${eol}` }
|
|
270
381
|
}
|
|
271
382
|
|
|
272
|
-
|
|
383
|
+
// `identity`, when given, is the text of identity lines that go at the very
|
|
384
|
+
// end of the note, in a comment block, as the last generated region.
|
|
385
|
+
function generatedSections({ precedingBytes, offset, rows, outsideCount, closure = null, identity = null }) {
|
|
273
386
|
const regions = []
|
|
274
387
|
const parts = []
|
|
275
388
|
let cursor = offset
|
|
@@ -290,14 +403,25 @@ function generatedSections({ precedingBytes, offset, rows, outsideCount, closure
|
|
|
290
403
|
const lead = rows.length > 0 ? '\n' : `${separatorAfter(precedingBytes)}${RELATIONS_HEADING}`
|
|
291
404
|
push('outside-selection', `${lead}Relationships leading outside this view: ${outsideCount}\n`)
|
|
292
405
|
}
|
|
406
|
+
if (identity !== null) push('identity', `${regions.length > 0 ? '\n' : separatorAfter(precedingBytes)}%%\n${identity}%%\n`)
|
|
293
407
|
return { bytes: Buffer.concat(parts), regions }
|
|
294
408
|
}
|
|
295
409
|
|
|
410
|
+
// The wrapper's own lines. `format` and `source` are what the redaction guard
|
|
411
|
+
// recognises as the node's own extension and path.
|
|
412
|
+
function wrapperLines(node) {
|
|
413
|
+
return {
|
|
414
|
+
format: `- Format: ${plainText(node.extension)}`,
|
|
415
|
+
source: `- Source: ${plainText(node.repo)} · ${node.path.split('/').map(plainText).join(' / ')}`,
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
296
419
|
function wrapperRepresentation({ node, attachmentPath }) {
|
|
420
|
+
const own = wrapperLines(node)
|
|
297
421
|
const lines = [`# ${plainText(node.title)}\n`]
|
|
298
422
|
if (node.summary) lines.push(`\n${plainText(node.summary)}\n`)
|
|
299
|
-
lines.push(`\n
|
|
300
|
-
lines.push(
|
|
423
|
+
lines.push(`\n${own.format}\n`)
|
|
424
|
+
lines.push(`${own.source}\n`)
|
|
301
425
|
if (Array.isArray(node.tags) && node.tags.length > 0) lines.push(`- Tags: ${node.tags.map(plainText).join(', ')}\n`)
|
|
302
426
|
lines.push(`- Original: [[${attachmentPath}|Open the original file]]\n`)
|
|
303
427
|
if (EMBEDDABLE.has(String(node.extension).toLowerCase())) lines.push(`\n![[${attachmentPath}]]\n`)
|
|
@@ -320,7 +444,7 @@ function assertContained(relativePath) {
|
|
|
320
444
|
// inversions it contributes per rewritten edge, and whether it closed a fence.
|
|
321
445
|
// Nothing outside the arguments is read, which is what lets the result be
|
|
322
446
|
// cached under the dependency key.
|
|
323
|
-
function emitNote({ node, notePathValue, source: { bytes: source, rawDigest }, rows, outsideCount, occurrences, emittedTarget, key }) {
|
|
447
|
+
function emitNote({ layout, node, notePathValue, attachmentPathValue, source: { bytes: source, rawDigest }, rows, outsideCount, occurrences, emittedTarget, key }) {
|
|
324
448
|
const sourceRecord = { path: node.path, rawDigest, byteLength: source.length }
|
|
325
449
|
const assetInversions = new Map()
|
|
326
450
|
const edgeInversions = new Map()
|
|
@@ -329,11 +453,31 @@ function emitNote({ node, notePathValue, source: { bytes: source, rawDigest }, r
|
|
|
329
453
|
let authored
|
|
330
454
|
let regions
|
|
331
455
|
let closure = null
|
|
456
|
+
let identityAtEnd = null
|
|
457
|
+
let placementOf = null
|
|
332
458
|
|
|
333
459
|
if (node.extension === 'md') {
|
|
334
460
|
const lens = readMarkdownLens(source, { repoId: node.repo, nodeId: node.id })
|
|
335
461
|
const edits = linkEdits({ source, lens, occurrences, emittedTarget })
|
|
336
|
-
const
|
|
462
|
+
const placement = layout.version === 1 ? null : identityPlacement(source, lens)
|
|
463
|
+
placementOf = placement
|
|
464
|
+
let prefix = source.subarray(0, lens.body.start)
|
|
465
|
+
let frontmatter = lens.frontmatter
|
|
466
|
+
let identity = null
|
|
467
|
+
if (placement?.kind === 'frontmatter-lines') {
|
|
468
|
+
const block = utf8(identityText(node, placement.eol))
|
|
469
|
+
prefix = Buffer.concat([source.subarray(0, placement.at), block, source.subarray(placement.at, lens.body.start)])
|
|
470
|
+
identity = { start: placement.at, end: placement.at + block.length }
|
|
471
|
+
frontmatter = { start: lens.frontmatter.start, end: lens.frontmatter.end + block.length }
|
|
472
|
+
} else if (placement?.kind === 'generated-frontmatter') {
|
|
473
|
+
// After a byte order prefix, which the app strips before it reads front matter.
|
|
474
|
+
const block = utf8(`---${placement.eol}${identityText(node, placement.eol)}---${placement.eol}`)
|
|
475
|
+
prefix = Buffer.concat([source.subarray(0, lens.body.start), block])
|
|
476
|
+
identity = { start: lens.body.start, end: lens.body.start + block.length }
|
|
477
|
+
frontmatter = identity
|
|
478
|
+
} else if (placement?.kind === 'tail') {
|
|
479
|
+
identityAtEnd = identityText(node, '\n')
|
|
480
|
+
}
|
|
337
481
|
const body = applyEdits({ source, from: lens.body.start, to: lens.body.end, edits, noteOffset: prefix.length })
|
|
338
482
|
authored = Buffer.concat([prefix, body.bytes])
|
|
339
483
|
for (const { edgeKey, assetKey, ...inversion } of body.inversions) {
|
|
@@ -346,27 +490,34 @@ function emitNote({ node, notePathValue, source: { bytes: source, rawDigest }, r
|
|
|
346
490
|
edgeInversions.get(edgeKey).push(inversion)
|
|
347
491
|
}
|
|
348
492
|
regions = {
|
|
349
|
-
...(
|
|
350
|
-
|
|
493
|
+
...(frontmatter ? { frontmatter } : {}),
|
|
494
|
+
...(identity ? { identity } : {}),
|
|
495
|
+
body: { start: prefix.length, end: authored.length },
|
|
351
496
|
}
|
|
352
497
|
closure = fenceClosureFor(source, lens.finalNewline)
|
|
353
498
|
Object.assign(sourceRecord, { kind: 'markdown', finalNewline: lens.finalNewline, ...(lens.bom ? { bom: lens.bom } : {}) })
|
|
354
499
|
} else {
|
|
355
500
|
const extension = /^[a-z0-9]{1,16}$/.test(String(node.extension).toLowerCase()) ? String(node.extension).toLowerCase() : 'bin'
|
|
356
|
-
const attachmentPath = `attachments/${
|
|
501
|
+
const attachmentPath = layout.version === 1 ? `attachments/${legacyBasename(notePathValue)}.${extension}` : attachmentPathValue
|
|
357
502
|
assertContained(attachmentPath)
|
|
358
503
|
attachment = { path: attachmentPath, digest: rawDigest, byteLength: source.length }
|
|
359
504
|
files.push({ path: attachmentPath, kind: 'attachment', bytes: source, digest: rawDigest })
|
|
360
|
-
|
|
361
|
-
|
|
505
|
+
const identityBlock = layout.version === 1 ? Buffer.alloc(0) : utf8(`---\n${identityText(node, '\n')}---\n`)
|
|
506
|
+
authored = Buffer.concat([identityBlock, wrapperRepresentation({ node, attachmentPath })])
|
|
507
|
+
regions = {
|
|
508
|
+
...(identityBlock.length > 0 ? { frontmatter: { start: 0, end: identityBlock.length }, identity: { start: 0, end: identityBlock.length } } : {}),
|
|
509
|
+
body: { start: authored.length, end: authored.length },
|
|
510
|
+
representation: { start: identityBlock.length, end: authored.length },
|
|
511
|
+
}
|
|
362
512
|
Object.assign(sourceRecord, { kind: 'wrapper', attachment: attachmentPath })
|
|
363
513
|
}
|
|
364
514
|
|
|
365
|
-
const generated = generatedSections({ precedingBytes: authored, offset: authored.length, rows, outsideCount, closure })
|
|
515
|
+
const generated = generatedSections({ precedingBytes: authored, offset: authored.length, rows, outsideCount, closure, identity: identityAtEnd })
|
|
366
516
|
const bytes = Buffer.concat([authored, generated.bytes])
|
|
367
517
|
assertStrictUtf8(bytes)
|
|
368
518
|
assertContained(notePathValue)
|
|
369
519
|
const { representation, ...authoredRegions } = regions
|
|
520
|
+
if (identityAtEnd !== null) authoredRegions.identity = { ...generated.regions.at(-1).range }
|
|
370
521
|
files.push({ path: notePathValue, kind: 'note', bytes, digest: sha256Digest(bytes) })
|
|
371
522
|
const note = {
|
|
372
523
|
repoId: node.repo,
|
|
@@ -382,12 +533,17 @@ function emitNote({ node, notePathValue, source: { bytes: source, rawDigest }, r
|
|
|
382
533
|
[EXT_KEY]: {
|
|
383
534
|
source: sourceRecord,
|
|
384
535
|
...(assetInversions.size > 0
|
|
385
|
-
? { assetEmbeds: [...assetInversions].map(([
|
|
536
|
+
? { assetEmbeds: [...assetInversions].map(([attachmentKey, inversions]) => ({ attachment: attachmentKey, inversions })).sort((left, right) => compare(left.attachment, right.attachment)) }
|
|
386
537
|
: {}),
|
|
387
538
|
},
|
|
388
539
|
},
|
|
389
540
|
}
|
|
390
|
-
|
|
541
|
+
// Only the closure that precedes a generated section is emitted; one before the end-of-note identity block counts.
|
|
542
|
+
return {
|
|
543
|
+
key, files, attachment, note, edgeInversions: [...edgeInversions], fenceClosed: Boolean(closure && generated.regions.length > 0),
|
|
544
|
+
// The author wrote an identity key of their own: it, not the generated block, is what Properties shows.
|
|
545
|
+
authorIdentityKeys: placementOf?.authorKeys === true,
|
|
546
|
+
}
|
|
391
547
|
}
|
|
392
548
|
|
|
393
549
|
// ---------------------------------------------------------------------------
|
|
@@ -413,10 +569,11 @@ function visibleAssets(graph, profile) {
|
|
|
413
569
|
return visible
|
|
414
570
|
}
|
|
415
571
|
|
|
416
|
-
// attachments/<readable stem>--<identity suffix>.<ext>, from the
|
|
417
|
-
// asset identity alone. The suffix lengthens only when two
|
|
418
|
-
// would share a name on a case- or normalization-
|
|
419
|
-
|
|
572
|
+
// Layout 1: attachments/<readable stem>--<identity suffix>.<ext>, from the
|
|
573
|
+
// repository and asset identity alone. The suffix lengthens only when two
|
|
574
|
+
// assets of this view would share a name on a case- or normalization-
|
|
575
|
+
// insensitive filesystem.
|
|
576
|
+
function allocateLegacyAssetPaths(assets) {
|
|
420
577
|
const taken = new Set()
|
|
421
578
|
const allocated = new Map()
|
|
422
579
|
for (const asset of [...assets].sort((left, right) => compare(left.repo, right.repo) || compare(left.id, right.id))) {
|
|
@@ -450,18 +607,53 @@ function canonicalSnapshotOf(graph) {
|
|
|
450
607
|
return { nodes: graph.nodes, edges, externalEdgeCount: graph.edges.length - edges.length }
|
|
451
608
|
}
|
|
452
609
|
|
|
453
|
-
//
|
|
454
|
-
//
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
610
|
+
// What a layout 2 prior generation published, which the view's allocation
|
|
611
|
+
// keeps (see allocateViewPaths). A layout 1 prior generation offers nothing:
|
|
612
|
+
// its paths are the earlier layout's.
|
|
613
|
+
function publishedOf(priorManifest) {
|
|
614
|
+
if (!priorManifest || manifestLayoutVersion(priorManifest) !== CURRENT_VAULT_LAYOUT) return null
|
|
615
|
+
return {
|
|
616
|
+
notes: priorManifest.notes.map(({ repoId, nodeId, path: notePathValue, ext }) => {
|
|
617
|
+
const attachment = ext?.[EXT_KEY]?.source?.attachment
|
|
618
|
+
return { repoId, nodeId, path: notePathValue, ...(typeof attachment === 'string' ? { attachment } : {}) }
|
|
619
|
+
}),
|
|
620
|
+
assets: priorManifest.attachments
|
|
621
|
+
.filter((item) => item.ext?.[EXT_KEY]?.kind === 'embedded-asset')
|
|
622
|
+
.map((item) => ({ repoId: item.ext[EXT_KEY].repoId, assetPath: item.ext[EXT_KEY].assetPath, path: item.path })),
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// `layoutHeld` are the notes that hold a layout 1 view in layout 1: those of
|
|
627
|
+
// open edits and of edits closed on this tick (see layoutHeldPaths in
|
|
628
|
+
// src/runtime/obsidian/pending-edits.mjs).
|
|
629
|
+
function layoutOf({ layout, priorManifest, layoutHeld }) {
|
|
630
|
+
if (layout !== undefined && layout !== null) {
|
|
631
|
+
if (!Object.hasOwn(VAULT_LAYOUTS, layout)) refuse('invalid-layout', 'the vault layout must be 1 or 2')
|
|
632
|
+
return VAULT_LAYOUTS[layout]
|
|
633
|
+
}
|
|
634
|
+
// A layout 1 view that holds a note stays in layout 1 until no note of it is held.
|
|
635
|
+
const held = new Set(layoutHeld)
|
|
636
|
+
if (priorManifest && manifestLayoutVersion(priorManifest) === 1 && priorManifest.notes.some((note) => held.has(note.path))) return VAULT_LAYOUTS[1]
|
|
637
|
+
return VAULT_LAYOUTS[CURRENT_VAULT_LAYOUT]
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
export function prepareView(options = {}) {
|
|
641
|
+
return prepareWithRules(REDACTION_RULES, options)
|
|
462
642
|
}
|
|
463
643
|
|
|
464
|
-
|
|
644
|
+
// Test seam only: the mutation controls of the redaction guard substitute a
|
|
645
|
+
// rule through this. Runtime code uses prepareView.
|
|
646
|
+
export function createViewPreparationForOracleTests(rules = REDACTION_RULES) {
|
|
647
|
+
const guard = { ...REDACTION_RULES, ...rules }
|
|
648
|
+
return (options = {}) => prepareWithRules(guard, options)
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// `heldNotePaths` are the vault files held for an open edit: they stay in the
|
|
652
|
+
// vault, so their names stay taken. `layoutHeldNotePaths`, when given, are the
|
|
653
|
+
// notes that hold a layout 1 view in layout 1 (open edits and edits closed on
|
|
654
|
+
// this tick); without it, `heldNotePaths` do. `viewScopeIds`, when given, are
|
|
655
|
+
// the views still maintained: the registry drops the sections of any other.
|
|
656
|
+
function prepareWithRules(guard, { snapshot, profile, scope, persistentPathRegistry = null, priorManifest = null, existingSettings = null, plugin = null, clock, generationId, vaultRootBytes, maxFullPathBytes, cache = null, heldNotePaths = null, layoutHeldNotePaths = null, viewScopeIds = null, layout: requestedLayout } = {}) {
|
|
465
657
|
if (cache !== null && !isPreparationCache(cache)) refuse('invalid-preparation-cache', 'the preparation cache must come from createPreparationCache')
|
|
466
658
|
assertObsidianContract('corpus-profile', profile)
|
|
467
659
|
assertObsidianContract('scope', scope)
|
|
@@ -472,6 +664,11 @@ export function prepareView({ snapshot, profile, scope, persistentPathRegistry =
|
|
|
472
664
|
assertObsidianContract('generation-manifest', priorManifest)
|
|
473
665
|
if (priorManifest.scopeId !== scope.scopeId) refuse('prior-manifest-mismatch', 'the prior manifest belongs to another scope')
|
|
474
666
|
}
|
|
667
|
+
for (const [name, value] of [['heldNotePaths', heldNotePaths], ['layoutHeldNotePaths', layoutHeldNotePaths]]) {
|
|
668
|
+
if (value !== null && !Array.isArray(value)) refuse('invalid-held-notes', `${name} must be an array of note paths`)
|
|
669
|
+
}
|
|
670
|
+
if (viewScopeIds !== null && !(Array.isArray(viewScopeIds) && viewScopeIds.every((id) => typeof id === 'string'))) refuse('invalid-view-scopes', 'viewScopeIds must be an array of scope identities')
|
|
671
|
+
const layout = layoutOf({ layout: requestedLayout, priorManifest, layoutHeld: layoutHeldNotePaths ?? heldNotePaths ?? [] })
|
|
475
672
|
const checkedAt = timestampFrom(clock)
|
|
476
673
|
const canonical = canonicalSnapshotOf(snapshot.graph)
|
|
477
674
|
|
|
@@ -481,19 +678,13 @@ export function prepareView({ snapshot, profile, scope, persistentPathRegistry =
|
|
|
481
678
|
const nodeById = new Map(canonical.nodes.map((node) => [node.id, node]))
|
|
482
679
|
const vault = new Set(selection.vaultNodes)
|
|
483
680
|
const vaultNode = (id) => (vault.has(id) ? nodeById.get(id) : null)
|
|
484
|
-
|
|
485
|
-
const
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
})
|
|
492
|
-
const pathOf = (node) => allocatedPath(node.repo, node.id)
|
|
493
|
-
for (const note of priorManifest?.notes ?? []) {
|
|
494
|
-
const current = allocatedPath(note.repoId, note.nodeId)
|
|
495
|
-
if (current !== null && current !== note.path) refuse('path-registry-divergence', 'the path registry and the prior manifest disagree about an allocated path')
|
|
496
|
-
}
|
|
681
|
+
const universeNodes = universe.nodes.map((id) => nodeById.get(id))
|
|
682
|
+
const assets = visibleAssets(snapshot.graph, profile)
|
|
683
|
+
const censusAssets = Array.isArray(snapshot.graph.assets) ? snapshot.graph.assets : []
|
|
684
|
+
const usable = (item) => item && typeof item.repo === 'string' && item.repo !== '' && typeof item.id === 'string' && item.id !== ''
|
|
685
|
+
const limits = { ...(vaultRootBytes === undefined ? {} : { vaultRootBytes }), ...(maxFullPathBytes === undefined ? {} : { maxFullPathBytes }) }
|
|
686
|
+
// Every view's allocation of the registry, this one's included; a registry of another shape or workspace refuses.
|
|
687
|
+
const views = viewsOfRegistry(persistentPathRegistry, profile.workspaceId)
|
|
497
688
|
|
|
498
689
|
const edgeById = new Map(canonical.edges.map((edge) => [edge.id, edge]))
|
|
499
690
|
const vaultEdges = selection.vaultEdges.map((id) => edgeById.get(id)).filter((edge) => vault.has(edge.source) && vault.has(edge.target))
|
|
@@ -509,7 +700,6 @@ export function prepareView({ snapshot, profile, scope, persistentPathRegistry =
|
|
|
509
700
|
// Asset embeds: canonical occurrences whose source is a note of this view and
|
|
510
701
|
// whose asset this view may copy. Anything else is left exactly as authored
|
|
511
702
|
// and appears in no output.
|
|
512
|
-
const assets = visibleAssets(snapshot.graph, profile)
|
|
513
703
|
const embeddedAssets = new Map()
|
|
514
704
|
for (const embed of Array.isArray(snapshot.graph.embeds) ? snapshot.graph.embeds : []) {
|
|
515
705
|
const asset = assets.get(embed?.asset?.id)
|
|
@@ -518,14 +708,43 @@ export function prepareView({ snapshot, profile, scope, persistentPathRegistry =
|
|
|
518
708
|
if (!occurrencesBySource.has(embed.source)) occurrencesBySource.set(embed.source, [])
|
|
519
709
|
occurrencesBySource.get(embed.source).push(embed)
|
|
520
710
|
}
|
|
521
|
-
|
|
711
|
+
|
|
712
|
+
// Layout 2 paths are allocated for this view alone, seeded from what its
|
|
713
|
+
// prior generation published; a file held for an open edit keeps its name
|
|
714
|
+
// taken. A view prepared in layout 1 holds layout 1 files, none of which is a
|
|
715
|
+
// layout 2 path. The registry keeps the result as this view's section,
|
|
716
|
+
// whichever layout the view is prepared in.
|
|
717
|
+
const readable = allocateViewPaths({
|
|
718
|
+
published: publishedOf(priorManifest),
|
|
719
|
+
nodes: [...vault].map((id) => nodeById.get(id)),
|
|
720
|
+
assets: [...embeddedAssets.values()],
|
|
721
|
+
occupied: layout.version === 1 ? [] : heldNotePaths ?? [],
|
|
722
|
+
...limits,
|
|
723
|
+
})
|
|
724
|
+
const legacy = layout.version === 1
|
|
725
|
+
? allocateLegacyPaths({ nodes: universeNodes, priorManifest: priorManifest && manifestLayoutVersion(priorManifest) === 1 ? priorManifest : null, ...limits })
|
|
726
|
+
: null
|
|
727
|
+
const allocatedPath = legacy ? legacy.pathOf : readable.pathOf
|
|
728
|
+
const pathOf = (node) => allocatedPath(node.repo, node.id)
|
|
729
|
+
const attachmentOf = (node) => (legacy ? null : readable.attachmentOf(node.repo, node.id))
|
|
730
|
+
// Layout 1 paths are derived from identities; a disagreement there is state that cannot be trusted.
|
|
731
|
+
if (legacy && priorManifest && manifestLayoutVersion(priorManifest) === 1) {
|
|
732
|
+
for (const note of priorManifest.notes) {
|
|
733
|
+
const current = allocatedPath(note.repoId, note.nodeId)
|
|
734
|
+
if (current !== null && current !== note.path) refuse('path-registry-divergence', 'the path registry and the prior manifest disagree about an allocated path')
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
const assetPaths = legacy
|
|
738
|
+
? allocateLegacyAssetPaths(embeddedAssets.values())
|
|
739
|
+
: new Map([...embeddedAssets.values()].map((asset) => [asset.id, readable.assetPathOf(asset.repo, asset.path)]))
|
|
740
|
+
for (const assetPath of assetPaths.values()) assertContained(assetPath)
|
|
522
741
|
const emittedTarget = (occurrence) => {
|
|
523
742
|
if (occurrence.type === 'embeds_asset') {
|
|
524
743
|
const attachment = assetPaths.get(occurrence.asset.id)
|
|
525
|
-
return { key: { assetKey: attachment }, markdown: attachment
|
|
744
|
+
return { key: { assetKey: attachment }, markdown: encodeVaultPath(attachment), wikilink: attachment, alias: false }
|
|
526
745
|
}
|
|
527
|
-
const
|
|
528
|
-
return { key: { edgeKey: occurrence.target }, markdown:
|
|
746
|
+
const target = noteLinkTarget(layout, pathOf(vaultNode(occurrence.target)))
|
|
747
|
+
return { key: { edgeKey: occurrence.target }, markdown: target.markdown, wikilink: target.wikilink, alias: true }
|
|
529
748
|
}
|
|
530
749
|
|
|
531
750
|
const read = sourceReader(snapshot)
|
|
@@ -554,26 +773,31 @@ export function prepareView({ snapshot, profile, scope, persistentPathRegistry =
|
|
|
554
773
|
const reusable = isPreparationCache(cache) ? cache.notes : null
|
|
555
774
|
const nextCache = reusable ? new Map() : null
|
|
556
775
|
const preparation = { emitted: 0, reused: 0 }
|
|
776
|
+
const reusedPaths = new Set()
|
|
777
|
+
const authorKeyNotes = []
|
|
557
778
|
const titles = titleRenderings()
|
|
558
779
|
|
|
559
780
|
for (const node of orderedNodes) {
|
|
560
781
|
const notePathValue = pathOf(node)
|
|
782
|
+
const attachmentPathValue = node.extension === 'md' ? null : attachmentOf(node)
|
|
561
783
|
const pinned = read.pinned(node.repo, node.path)
|
|
562
784
|
const outgoing = (outgoingBy.get(node.id) ?? []).slice().sort((left, right) => compare(left.target, right.target))
|
|
563
785
|
const incoming = (incomingBy.get(node.id) ?? []).slice().sort((left, right) => compare(left.source, right.source))
|
|
564
|
-
const rows = relationRows({ node, outgoing, incoming, vaultNode, pathOf, titles })
|
|
786
|
+
const rows = relationRows({ layout, node, outgoing, incoming, vaultNode, pathOf, titles })
|
|
565
787
|
const outsideCount = outsideCountBy.get(node.id) ?? 0
|
|
566
788
|
const occurrences = occurrencesBySource.get(node.id) ?? []
|
|
567
|
-
const key = reusable ? dependencyKey({ node, notePathValue, pinned, rows, outsideCount, occurrences, emittedTarget }) : null
|
|
789
|
+
const key = reusable ? dependencyKey({ layout, node, notePathValue, attachmentPathValue, pinned, rows, outsideCount, occurrences, emittedTarget }) : null
|
|
568
790
|
const cached = reusable?.get(notePathValue)
|
|
569
791
|
let entry
|
|
570
792
|
if (cached && cached.key === key) {
|
|
571
793
|
entry = cached
|
|
572
794
|
preparation.reused += 1
|
|
795
|
+
reusedPaths.add(notePathValue)
|
|
573
796
|
} else {
|
|
574
|
-
entry = emitNote({ node, notePathValue, source: read(node.repo, node.path), rows, outsideCount, occurrences, emittedTarget, key })
|
|
797
|
+
entry = emitNote({ layout, node, notePathValue, attachmentPathValue, source: read(node.repo, node.path), rows, outsideCount, occurrences, emittedTarget, key })
|
|
575
798
|
preparation.emitted += 1
|
|
576
799
|
}
|
|
800
|
+
if (entry.authorIdentityKeys) authorKeyNotes.push({ code: 'author-identity-properties', repoId: node.repo, nodeId: node.id, notePath: notePathValue })
|
|
577
801
|
if (nextCache) nextCache.set(notePathValue, entry)
|
|
578
802
|
// The result never aliases the cache: a caller may change what it was handed, bytes included.
|
|
579
803
|
for (const file of entry.files) files.push({ ...file, bytes: Buffer.from(file.bytes) })
|
|
@@ -611,7 +835,7 @@ export function prepareView({ snapshot, profile, scope, persistentPathRegistry =
|
|
|
611
835
|
})
|
|
612
836
|
.sort((left, right) => compare(left.edgeId, right.edgeId))
|
|
613
837
|
|
|
614
|
-
const settings = prepareSettings({ existing: existingSettings })
|
|
838
|
+
const settings = prepareSettings({ existing: existingSettings, plugin })
|
|
615
839
|
files.push(...settings.files)
|
|
616
840
|
const occupied = new Set()
|
|
617
841
|
for (const file of files) {
|
|
@@ -626,8 +850,9 @@ export function prepareView({ snapshot, profile, scope, persistentPathRegistry =
|
|
|
626
850
|
const contentDigest = createHash('sha256')
|
|
627
851
|
for (const file of files) contentDigest.update(`${file.path}\u0000${file.digest}\n`)
|
|
628
852
|
const manifest = {
|
|
629
|
-
schema:
|
|
630
|
-
contractVersion:
|
|
853
|
+
schema: layout.manifestSchema,
|
|
854
|
+
contractVersion: layout.contractVersion,
|
|
855
|
+
...(layout.version === 1 ? {} : { layoutVersion: layout.version }),
|
|
631
856
|
generationId: generationId ?? `gen-${contentDigest.digest('hex').slice(0, 32)}`,
|
|
632
857
|
scopeId: scope.scopeId,
|
|
633
858
|
snapshotId: snapshot.document.snapshotId,
|
|
@@ -636,24 +861,98 @@ export function prepareView({ snapshot, profile, scope, persistentPathRegistry =
|
|
|
636
861
|
attachments,
|
|
637
862
|
completeness: { status: 'complete', expectedNotes: orderedNodes.length, writtenNotes: notes.length },
|
|
638
863
|
freshness: { status: 'current', checkedAt },
|
|
639
|
-
ext: { [EXT_KEY]: { emitterVersion:
|
|
864
|
+
ext: { [EXT_KEY]: { emitterVersion: layout.emitterVersion, mode: selection.mode, settings: settings.ownership } },
|
|
640
865
|
}
|
|
641
866
|
assertObsidianContract('generation-manifest', manifest)
|
|
642
|
-
|
|
643
|
-
|
|
867
|
+
// What was laid out anyway and should be known, naming the note: a layout 2 generation records it.
|
|
868
|
+
const noteDiagnostics = legacy ? [] : [...readable.diagnostics, ...authorKeyNotes]
|
|
869
|
+
|
|
870
|
+
// The redaction guard, over the whole result. Its deny-list holds the
|
|
871
|
+
// identifiers of every census node and asset outside this view, and follows
|
|
872
|
+
// the audience. Of one the audience may not see (withheld), an unambiguous
|
|
873
|
+
// identifier refuses the view (an identity qualified by any repository of
|
|
874
|
+
// the census, a repository-qualified path, a repository-relative path with a
|
|
875
|
+
// folder, a vault path) and an ambiguous one is reported (a bare-word
|
|
876
|
+
// identity, a file name at a repository's root, such as README.md). Of one
|
|
877
|
+
// the audience may see but this view does not select, every identifier is
|
|
878
|
+
// reported. An identifier this view's own notes share names nothing outside
|
|
879
|
+
// it.
|
|
880
|
+
const viewAttachments = new Set(attachments.map((item) => item.path))
|
|
881
|
+
const visible = new Set(universe.nodes)
|
|
882
|
+
const censusRepositories = new Set([...canonical.nodes, ...censusAssets].filter(usable).map((item) => item.repo))
|
|
883
|
+
const qualified = (id) => id.indexOf(':') > 0 && censusRepositories.has(id.slice(0, id.indexOf(':')))
|
|
884
|
+
const refused = []
|
|
885
|
+
const reported = []
|
|
886
|
+
const unselected = []
|
|
887
|
+
const identifierOf = (item, withheld) => {
|
|
888
|
+
const id = String(item.id)
|
|
889
|
+
for (const [value, unambiguous] of [[id, qualified(id)], [`${item.repo}/${item.path}`, true], [item.path, item.path.includes('/')]]) {
|
|
890
|
+
;(!withheld ? unselected : unambiguous ? refused : reported).push(value)
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
for (const item of canonical.nodes) {
|
|
894
|
+
if (!usable(item) || vault.has(item.id) || typeof item.path !== 'string') continue
|
|
895
|
+
const withheld = !visible.has(item.id)
|
|
896
|
+
identifierOf(item, withheld)
|
|
897
|
+
const allocated = legacy ? legacy.pathOf(item.repo, item.id) : null
|
|
898
|
+
if (allocated !== null) (withheld ? refused : unselected).push(allocated)
|
|
899
|
+
}
|
|
900
|
+
for (const item of censusAssets) if (usable(item) && typeof item.path === 'string' && !embeddedAssets.has(item.id)) identifierOf(item, !assets.has(item.id))
|
|
901
|
+
// Vault paths of notes and files outside this view, what other views hold for them and what this view held
|
|
902
|
+
// before: refused for what the audience may not see, reported otherwise (a note gone from the census included).
|
|
903
|
+
const inView = (repoId, nodeId) => vault.has(nodeId) && nodeById.get(nodeId)?.repo === repoId
|
|
904
|
+
const withheldNode = (repoId, nodeId) => nodeById.get(nodeId)?.repo === repoId && !visible.has(nodeId)
|
|
905
|
+
const copied = new Set([...embeddedAssets.values()].map((asset) => `${asset.repo}\u0000${asset.path}`))
|
|
906
|
+
const withheldAssets = new Set(censusAssets.filter((item) => usable(item) && typeof item.path === 'string' && !assets.has(item.id)).map((item) => `${item.repo}\u0000${item.path}`))
|
|
907
|
+
for (const section of Object.values(views)) {
|
|
908
|
+
for (const entry of section.entries) {
|
|
909
|
+
if (!inView(entry?.repoId, entry?.nodeId)) (withheldNode(entry?.repoId, entry?.nodeId) ? refused : unselected).push(entry?.path, ...(entry?.attachment ? [entry.attachment] : []))
|
|
910
|
+
}
|
|
911
|
+
for (const entry of section.assets) {
|
|
912
|
+
const key = `${entry?.repoId}\u0000${entry?.assetPath}`
|
|
913
|
+
if (!copied.has(key)) (withheldAssets.has(key) ? refused : unselected).push(entry?.path)
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
for (const note of priorManifest?.notes ?? []) if (!inView(note.repoId, note.nodeId)) (withheldNode(note.repoId, note.nodeId) ? refused : unselected).push(note.path)
|
|
917
|
+
const own = new Set()
|
|
918
|
+
for (const node of orderedNodes) for (const value of [node.id, `${node.repo}/${node.path}`, node.path, pathOf(node), attachmentOf(node)]) if (typeof value === 'string') own.add(value.normalize('NFC'))
|
|
919
|
+
for (const asset of embeddedAssets.values()) for (const value of [asset.id, `${asset.repo}/${asset.path}`, asset.path, assetPaths.get(asset.id)]) if (typeof value === 'string') own.add(value.normalize('NFC'))
|
|
920
|
+
const outside = (values) => values.filter((value) => typeof value === 'string' && value !== '' && !own.has(value.normalize('NFC')))
|
|
921
|
+
const linkTargets = new Set()
|
|
922
|
+
for (const node of orderedNodes) {
|
|
923
|
+
const target = noteLinkTarget(layout, pathOf(node))
|
|
924
|
+
linkTargets.add(target.markdown).add(target.wikilink)
|
|
925
|
+
}
|
|
926
|
+
for (const attachmentPath of viewAttachments) linkTargets.add(attachmentPath).add(encodeVaultPath(attachmentPath))
|
|
927
|
+
const allocatedInView = new Map(orderedNodes.map((node) => [node.id, pathOf(node)]))
|
|
928
|
+
const wrapperAttachments = new Set(orderedNodes.filter((node) => node.extension !== 'md').map((node) => (legacy ? `attachments/${legacyBasename(pathOf(node))}.${/^[a-z0-9]{1,16}$/.test(String(node.extension).toLowerCase()) ? String(node.extension).toLowerCase() : 'bin'}` : attachmentOf(node))))
|
|
929
|
+
const allowedAttachments = new Set([...wrapperAttachments, ...assetPaths.values()])
|
|
930
|
+
const guardReported = assertViewRedaction({
|
|
644
931
|
manifest,
|
|
645
932
|
files,
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
].
|
|
656
|
-
|
|
933
|
+
reused: reusedPaths,
|
|
934
|
+
nodeOf: (nodeId) => nodeById.get(nodeId),
|
|
935
|
+
ownOf: (nodeId) => wrapperLines(nodeById.get(nodeId)),
|
|
936
|
+
view: { allocatedPathOf: (nodeId) => allocatedInView.get(nodeId) ?? null, attachments: allowedAttachments, linkTargets },
|
|
937
|
+
deny: createDenyMatcher({ refuse: outside(refused), diagnose: outside(reported), notice: outside(unselected) }),
|
|
938
|
+
layoutVersion: layout.version,
|
|
939
|
+
}, guard)
|
|
940
|
+
if (!legacy) noteDiagnostics.push(...guardReported)
|
|
941
|
+
if (legacy) {
|
|
942
|
+
const vaultSuffixes = new Set([...orderedNodes, ...embeddedAssets.values()].map((item) => identitySuffix(item.repo, item.id, 64)))
|
|
943
|
+
assertOnlyVaultIdentities({
|
|
944
|
+
manifest,
|
|
945
|
+
files,
|
|
946
|
+
vaultSuffixes,
|
|
947
|
+
// Every census identity that is not part of this view: withheld, out of
|
|
948
|
+
// the selection, or an asset this view does not copy.
|
|
949
|
+
forbiddenSuffixes: new Set([
|
|
950
|
+
// A record without a usable identity is not in any view and has no
|
|
951
|
+
// suffix to forbid; it must not abort the view either.
|
|
952
|
+
...[...canonical.nodes, ...censusAssets].filter(usable).map((item) => identitySuffix(item.repo, item.id, 64)),
|
|
953
|
+
].filter((suffix) => !vaultSuffixes.has(suffix))),
|
|
954
|
+
})
|
|
955
|
+
}
|
|
657
956
|
|
|
658
957
|
const prior = new Map((priorManifest?.notes ?? []).map((note) => [note.path, note.noteDigest]))
|
|
659
958
|
const changes = { added: [], changed: [], unchanged: [], removed: [] }
|
|
@@ -666,12 +965,13 @@ export function prepareView({ snapshot, profile, scope, persistentPathRegistry =
|
|
|
666
965
|
// The cache is replaced only by a preparation that passed every check, and
|
|
667
966
|
// holds exactly the notes of this view.
|
|
668
967
|
if (nextCache) cache.notes = nextCache
|
|
968
|
+
if (noteDiagnostics.length > 0) manifest.ext[EXT_KEY].diagnostics = noteDiagnostics
|
|
669
969
|
|
|
670
970
|
return {
|
|
671
971
|
manifest,
|
|
672
972
|
manifestBytes: utf8(`${JSON.stringify(manifest, null, 2)}\n`),
|
|
673
973
|
files,
|
|
674
|
-
persistentPathRegistry:
|
|
974
|
+
persistentPathRegistry: withViewSection(persistentPathRegistry, { workspaceId: profile.workspaceId, scopeId: scope.scopeId, section: readable.section, keep: viewScopeIds }),
|
|
675
975
|
changes,
|
|
676
976
|
// How many notes were emitted on this call and how many were reused from the cache; without a cache every note is emitted.
|
|
677
977
|
preparation,
|
|
@@ -683,55 +983,6 @@ export function prepareView({ snapshot, profile, scope, persistentPathRegistry =
|
|
|
683
983
|
}
|
|
684
984
|
}
|
|
685
985
|
|
|
686
|
-
// Last check before anything is returned. Two rules, because the two kinds of
|
|
687
|
-
// bytes differ: a substring the emitter itself produced from an allocated path
|
|
688
|
-
// (note and attachment paths, rewritten link targets) may name only a note or
|
|
689
|
-
// asset of this view; free text in a generated region (titles, tags, source
|
|
690
|
-
// paths, which are the author's words) is refused only when it names an
|
|
691
|
-
// identity of the census that is outside this view. Author text that merely
|
|
692
|
-
// looks like an identity suffix, such as a content-hashed asset name, is not
|
|
693
|
-
// a redaction failure.
|
|
694
|
-
function assertOnlyVaultIdentities({ manifest, files, vaultSuffixes, forbiddenSuffixes }) {
|
|
695
|
-
const index = (suffixes) => {
|
|
696
|
-
const byPrefix = new Map()
|
|
697
|
-
for (const full of suffixes) byPrefix.set(full.slice(0, 12), [...(byPrefix.get(full.slice(0, 12)) ?? []), full])
|
|
698
|
-
return (suffix) => (byPrefix.get(suffix.slice(0, 12)) ?? []).some((full) => full.startsWith(suffix))
|
|
699
|
-
}
|
|
700
|
-
const allowed = index(vaultSuffixes)
|
|
701
|
-
const forbidden = index(forbiddenSuffixes)
|
|
702
|
-
const suffixesIn = (text) => [...text.matchAll(/--([0-9a-f]{12,64})(?![0-9a-f])/g)].map((match) => match[1])
|
|
703
|
-
// An emitted path ends in the allocated suffix (before the extension); the
|
|
704
|
-
// readable part before it is the author's title and may look like anything.
|
|
705
|
-
// A wikilink rewrite may append `|<the author's own words>`; only the part
|
|
706
|
-
// before the first `|` is an emitted path.
|
|
707
|
-
const checkEmitted = (text) => {
|
|
708
|
-
for (const segment of text.split('|')[0].split('#')[0].split('/')) {
|
|
709
|
-
const trailing = /--([0-9a-f]{12,64})(?:\.[^./]+)?$/.exec(segment)
|
|
710
|
-
if (trailing && !allowed(trailing[1])) refuse('redaction-failure', 'an emitted path names a note that is not part of this view')
|
|
711
|
-
}
|
|
712
|
-
for (const suffix of suffixesIn(text)) if (forbidden(suffix)) refuse('redaction-failure', 'an emitted path names a note that is not part of this view')
|
|
713
|
-
}
|
|
714
|
-
const checkText = (text) => {
|
|
715
|
-
for (const suffix of suffixesIn(text)) if (forbidden(suffix)) refuse('redaction-failure', 'generated output names a note that is not part of this view')
|
|
716
|
-
}
|
|
717
|
-
const byPath = new Map(files.map((file) => [file.path, file.bytes]))
|
|
718
|
-
for (const note of manifest.notes) {
|
|
719
|
-
const bytes = byPath.get(note.path)
|
|
720
|
-
for (const region of note.regions.generated) checkText(bytes.subarray(region.range.start, region.range.end).toString('utf8'))
|
|
721
|
-
}
|
|
722
|
-
for (const link of manifest.links) {
|
|
723
|
-
for (const inversion of link.inversions ?? []) checkEmitted(Buffer.from(inversion.ext[EXT_KEY].emitted, 'base64url').toString('utf8'))
|
|
724
|
-
}
|
|
725
|
-
for (const note of manifest.notes) {
|
|
726
|
-
for (const embed of note.ext[EXT_KEY].assetEmbeds ?? []) {
|
|
727
|
-
checkEmitted(embed.attachment)
|
|
728
|
-
for (const inversion of embed.inversions) checkEmitted(Buffer.from(inversion.ext[EXT_KEY].emitted, 'base64url').toString('utf8'))
|
|
729
|
-
}
|
|
730
|
-
}
|
|
731
|
-
for (const note of manifest.notes) checkEmitted(note.path)
|
|
732
|
-
for (const attachment of manifest.attachments) checkEmitted(attachment.path)
|
|
733
|
-
}
|
|
734
|
-
|
|
735
986
|
// Adds the fail-closed eligibility flag selectScope reads. A node is eligible
|
|
736
987
|
// only when `isEligible` returns exactly true for it. Embedded assets follow
|
|
737
988
|
// the same rule through `isAssetEligible`; without it every asset is withheld.
|