@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,23 +1,26 @@
|
|
|
1
1
|
import fs from 'node:fs'
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
import { AtelierDiagnosticError } from '../../project/config.mjs'
|
|
4
|
-
import { OBSIDIAN_EXT_KEY, ObsidianContractRefusal } from '../../projection/obsidian/contracts.mjs'
|
|
4
|
+
import { OBSIDIAN_EXT_KEY, ObsidianContractRefusal, manifestLayoutVersion } from '../../projection/obsidian/contracts.mjs'
|
|
5
|
+
import { PERSONAL_VALIDATION_PENDING, personalWorkspaceBindingOf, personalWorkspaceInputs, validatePersonalWorkspace } from '../../projection/obsidian/personal-workspace.mjs'
|
|
5
6
|
import { PROTOCOL_ID } from '../../projection/obsidian/publication/bridge-script.mjs'
|
|
6
|
-
import { PublicationRefusal } from '../../projection/obsidian/recovery/store.mjs'
|
|
7
|
+
import { PublicationRefusal, allocatedFolderState, hasCommittedGeneration, readVaultAllocation } from '../../projection/obsidian/recovery/store.mjs'
|
|
7
8
|
import { canonicalJson, compareText, isoTime } from './documents.mjs'
|
|
8
9
|
import { readObsidianEnablement } from './enablement.mjs'
|
|
9
|
-
import { ObsidianMaintenanceRefusal } from './errors.mjs'
|
|
10
|
+
import { ObsidianMaintenanceRefusal, refuse } from './errors.mjs'
|
|
10
11
|
import { createMaintenanceExtensions } from './extension-points.mjs'
|
|
11
12
|
import {
|
|
12
13
|
assertOutsideRepositories, authorizeAutomaticApply, defaultMachineSettings, ensureWorkspaceIdentity, protectedRoots, readLocalPointer,
|
|
13
14
|
readMachineSettings, resolveDataRoot, workspaceStateRoot, writeMachineSettings,
|
|
14
15
|
} from './machine-settings.mjs'
|
|
15
16
|
import { configKey, listConfigFiles, listSourceFiles, listVaultNotes, readFileFacts, reconcile, sha256Digest, sourceKey, vaultKey } from './observation.mjs'
|
|
16
|
-
import { dispatchAutomaticApply, heldPaths, observeVaultEdits, preserveInRecoveryStore, trustedNoteBases } from './pending-edits.mjs'
|
|
17
|
-
import {
|
|
17
|
+
import { dispatchAutomaticApply, heldPaths, layoutHeldPaths, observeVaultEdits, preserveInRecoveryStore, trustedNoteBases } from './pending-edits.mjs'
|
|
18
|
+
import { createProductionSeams, eligibilityFor } from './pipeline.mjs'
|
|
19
|
+
import { PLUGIN_FILES_WAIT_FOR_APP } from './plugin-presence.mjs'
|
|
18
20
|
import { ENGINE_LOCK_DIRECTORY, acquirePrivateGenerationLock, createAbandonmentProof } from './private-lock.mjs'
|
|
19
21
|
import { probeHealth } from './service-client.mjs'
|
|
20
22
|
import { FRESHNESS_SCHEMA, LATE_WRITERS_SCHEMA, createMaintenanceStateStore } from './state-store.mjs'
|
|
23
|
+
import { ensureVaultAllocation, projectDisplayName } from './vault-location.mjs'
|
|
21
24
|
import { createNullWatcherFactory } from './watchers.mjs'
|
|
22
25
|
|
|
23
26
|
// The maintenance engine. One explicit `tick()`; no timer, no process, no
|
|
@@ -38,6 +41,18 @@ import { createNullWatcherFactory } from './watchers.mjs'
|
|
|
38
41
|
// prepareView -> publishView, unless that would replace a held note
|
|
39
42
|
// 7. persist the freshness of every view
|
|
40
43
|
//
|
|
44
|
+
// A view is prepared and published once more, whatever its state, on the
|
|
45
|
+
// next tick after `requestPreparation(scopeId)`: a one-shot request per view
|
|
46
|
+
// the project declared at the last tick (at most 64 wait before the first),
|
|
47
|
+
// which the service makes for a tick requested for that view over its
|
|
48
|
+
// listener (`open` requests its own). A view whose last publication did not
|
|
49
|
+
// settle (stale, updating, or refused as a publisher conflict) is also tried
|
|
50
|
+
// again without a request: at the full reconciliation; as soon as the app
|
|
51
|
+
// looks different from when it was last tried (`observeApp`, when given: an
|
|
52
|
+
// app that quit, started, or opened or closed a vault); and otherwise after a
|
|
53
|
+
// delay that doubles per attempt from `publicationRetryMs` up to the full
|
|
54
|
+
// reconciliation interval.
|
|
55
|
+
//
|
|
41
56
|
// From the moment a workspace is resolved until the tick ends, the tick holds
|
|
42
57
|
// the workspace's private engine lock, so two engines (a service and a
|
|
43
58
|
// foreground command, or two services) never tick one workspace together. An
|
|
@@ -50,12 +65,13 @@ import { createNullWatcherFactory } from './watchers.mjs'
|
|
|
50
65
|
|
|
51
66
|
export const DEFAULT_FULL_RECONCILIATION_INTERVAL_MS = 5 * 60 * 1000
|
|
52
67
|
export const DEFAULT_RETRY_INTERVAL_MS = 60 * 1000
|
|
68
|
+
export const DEFAULT_PUBLICATION_RETRY_MS = 30 * 1000
|
|
53
69
|
export const DEFAULT_LATE_WRITER_WINDOW_MS = 7 * 24 * 60 * 60 * 1000
|
|
54
70
|
export const LATE_WRITER_EVERY_TICK_WINDOW_MS = 60 * 60 * 1000
|
|
55
71
|
|
|
56
72
|
// A refusal that means "someone else is publishing or editing here" rather
|
|
57
73
|
// than "this machine cannot publish".
|
|
58
|
-
const CONFLICT_REFUSALS = new Set(['publication-in-progress', 'generation-mismatch', 'editor-uncoordinated', 'state-mismatch'])
|
|
74
|
+
const CONFLICT_REFUSALS = new Set(['publication-in-progress', 'generation-mismatch', 'editor-uncoordinated', 'vault-open-in-several-windows', 'state-mismatch'])
|
|
59
75
|
const EDIT_OUTCOMES = new Set(['disk-changed', 'editor-edit'])
|
|
60
76
|
const RETRIED_STATES = new Set(['stale', 'updating', 'publisher-conflict'])
|
|
61
77
|
// A file changed or went away under a tick: everything is hashed again on the next one.
|
|
@@ -76,6 +92,15 @@ export const ENGINE_PRIMITIVES = Object.freeze({
|
|
|
76
92
|
dispatchAllowed: (maintenanceMode) => maintenanceMode === 'automatic',
|
|
77
93
|
// Every file is hashed, whatever its stat says, at least this often.
|
|
78
94
|
isFullReconciliationDue: ({ nowMs, lastFullMs, intervalMs }) => lastFullMs === null || nowMs - lastFullMs >= intervalMs,
|
|
95
|
+
// A view whose last attempt did not settle, between full reconciliations: again once a delay has passed that doubles
|
|
96
|
+
// per attempt in a row, from `retryMs` up to `maxMs`, and at once when the app looks different from when it was last
|
|
97
|
+
// tried. `appState()` answers what it looks like now, or null when that is not known.
|
|
98
|
+
isRetryDue({ nowMs, unsettled, appState, retryMs, maxMs }) {
|
|
99
|
+
if (unsettled === undefined) return false
|
|
100
|
+
if (nowMs - unsettled.lastAttemptMs >= Math.min(maxMs, retryMs * 2 ** Math.min(unsettled.attempts - 1, 20))) return true
|
|
101
|
+
const now = appState()
|
|
102
|
+
return now !== null && now !== unsettled.appState
|
|
103
|
+
},
|
|
79
104
|
// The embedded assets observed beside the walk: those the last built graph lets a view copy, and no withheld one.
|
|
80
105
|
observedAssetsOf: (graph) => (graph.assets ?? []).filter((asset) => asset.eligible === true).map(({ repo, path: assetPath }) => ({ repo, path: assetPath })),
|
|
81
106
|
// One engine per workspace at a time, across processes.
|
|
@@ -104,7 +129,37 @@ export const ENGINE_PRIMITIVES = Object.freeze({
|
|
|
104
129
|
})
|
|
105
130
|
|
|
106
131
|
const isTypedRefusal = (error) => error instanceof ObsidianMaintenanceRefusal || error instanceof AtelierDiagnosticError || error instanceof ObsidianContractRefusal || error instanceof PublicationRefusal
|
|
132
|
+
// The code a view is refused with when its own vault cannot be placed: a typed refusal's, or `vault-location-unusable`
|
|
133
|
+
// for a file-system error there (a file in the way, no permission, a read-only volume). Null for anything else, which
|
|
134
|
+
// is a fault of the engine and stops the tick.
|
|
135
|
+
const viewRefusalCode = (error) => (isTypedRefusal(error) ? error.code : typeof error?.code === 'string' && /^E[A-Z]+$/.test(error.code) ? 'vault-location-unusable' : null)
|
|
107
136
|
const digestOfJson = (value) => sha256Digest(Buffer.from(canonicalJson(value)))
|
|
137
|
+
// The configuration a tick observes. A project bound to a personal workspace adds the person's authored manifest and
|
|
138
|
+
// overlay and its generation's record: a change to any of them is a configuration change, so every view is prepared
|
|
139
|
+
// again, asks the composition, and is refused at once when the generation no longer holds.
|
|
140
|
+
const configFilesOf = (project) => [...listConfigFiles(project), ...personalWorkspaceInputs(project).map((absolute) => ({ key: configKey(absolute), changeClass: 'config', absolute }))]
|
|
141
|
+
|
|
142
|
+
// An editor adapter that `build` makes, synchronously or not, the first time the publisher calls it; a refusal of
|
|
143
|
+
// `build` is thrown from that call, and `refusal()` answers it afterwards.
|
|
144
|
+
function builtOnFirstUse(build) {
|
|
145
|
+
let adapter = null
|
|
146
|
+
let refused = null
|
|
147
|
+
const built = () => (adapter ??= Promise.resolve().then(build).catch((error) => { refused = error; throw error }))
|
|
148
|
+
return {
|
|
149
|
+
refusal: () => refused,
|
|
150
|
+
probe: async (input) => (await built()).probe(input),
|
|
151
|
+
inspect: async (payload) => (await built()).inspect(payload),
|
|
152
|
+
collect: async (payload) => (await built()).collect(payload),
|
|
153
|
+
publish: async (payload) => (await built()).publish(payload),
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// The adapter a publication gets when the factory refused the app: it coordinates with nothing and never calls it.
|
|
157
|
+
function uncoordinatedAdapter(refusal) {
|
|
158
|
+
const never = async () => { throw refusal }
|
|
159
|
+
return { probe: async () => ({ state: 'uncoordinated', reason: `${refusal.code}: the installed Obsidian does not qualify, and nothing is published through it` }), inspect: never, collect: never, publish: never }
|
|
160
|
+
}
|
|
161
|
+
// Views a tick may be asked to prepare again, at most, before it runs.
|
|
162
|
+
const MAX_PREPARATION_REQUESTS = 64
|
|
108
163
|
|
|
109
164
|
function journalTime(journalId) {
|
|
110
165
|
const match = /^journal-(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})(\d{3})-/.exec(journalId)
|
|
@@ -127,8 +182,19 @@ function journalsToRecheck(store, { nowMs, full, windowMs }) {
|
|
|
127
182
|
export function createMaintenanceEngineForOracleTests(options = {}, primitives = ENGINE_PRIMITIVES) {
|
|
128
183
|
const {
|
|
129
184
|
loadProject, dataRoot, adapterFactory, clock,
|
|
130
|
-
|
|
185
|
+
// Which notes may enter a view: what the machine settings decide at each tick (eligibilityFor), unless a test fixes it.
|
|
186
|
+
watcherFactory = createNullWatcherFactory(), extensions = createMaintenanceExtensions(), eligibility: fixedEligibility = null,
|
|
131
187
|
fullReconciliationIntervalMs = DEFAULT_FULL_RECONCILIATION_INTERVAL_MS, retryIntervalMs = DEFAULT_RETRY_INTERVAL_MS, lateWriterWindowMs = DEFAULT_LATE_WRITER_WINDOW_MS,
|
|
188
|
+
publicationRetryMs = DEFAULT_PUBLICATION_RETRY_MS, observeApp = null,
|
|
189
|
+
// Reads the app's own vault list without asking the app (readAppVaultListForAllocation): { ok: true, vaults } with
|
|
190
|
+
// the map id -> { path }, or { ok: false, code }. A folder for a new vault is never allocated inside a vault it
|
|
191
|
+
// lists, nor under a name a vault it lists already has, nor while the list cannot be read.
|
|
192
|
+
readAppVaultList = null,
|
|
193
|
+
// Reads the app's own vault list from its file, without asking the app (unheld-evidence.mjs): { unlisted: true } only
|
|
194
|
+
// when no list names a view's vault, a folder above it or one inside it. A view never published into its allocated
|
|
195
|
+
// folder is then published even while an app runs that cannot be coordinated with, creating files only (the
|
|
196
|
+
// publisher's `direct-unheld` path). An engine given no reader never takes that path.
|
|
197
|
+
readUnheldEvidence = null,
|
|
132
198
|
quietPeriodMs, lstat = fs.lstatSync, randomBytes, env = process.env, platform = process.platform,
|
|
133
199
|
// A service names where it answers health, so a lock it leaves behind can be proven abandoned.
|
|
134
200
|
lockOwner = null, lockProbe = probeHealth,
|
|
@@ -163,8 +229,26 @@ export function createMaintenanceEngineForOracleTests(options = {}, primitives =
|
|
|
163
229
|
let watching = { signature: null, handle: null }
|
|
164
230
|
let known = null // { stateStore, maintenanceMode } once a workspace has been resolved
|
|
165
231
|
let heldLock = null
|
|
232
|
+
// Views somebody asked to have prepared and published once more, at the next tick.
|
|
233
|
+
const preparationRequests = new Set()
|
|
234
|
+
// The views the project declared at the last tick; null before the first.
|
|
235
|
+
let declaredScopes = null
|
|
236
|
+
// Views whose last attempt did not settle, by scope: { attempts in a row, lastAttemptMs, appState then }. Kept in
|
|
237
|
+
// memory only: a new engine attempts every view on its first tick.
|
|
238
|
+
const unsettled = new Map()
|
|
239
|
+
// Views kept current although publishing their committed generation again (a drifted plugin file) needed an app
|
|
240
|
+
// that did not qualify: tried again on the same schedule as an unsettled view, and forgotten once one settles.
|
|
241
|
+
const waitingForApp = new Map()
|
|
166
242
|
const proveAbandoned = createAbandonmentProof({ probe: lockProbe })
|
|
167
243
|
|
|
244
|
+
// Whether a view kept from publication by an app that does not qualify may still be published on the publisher's
|
|
245
|
+
// `direct-unheld` path: never committed, its vault the folder allocated for it, and the app's list naming no folder
|
|
246
|
+
// on the way. The publisher checks every condition again, under its locks.
|
|
247
|
+
async function unheldMayApply(store, pointer) {
|
|
248
|
+
if (typeof readUnheldEvidence !== 'function' || pointer !== null || store.vaultOrigin !== 'allocated') return false
|
|
249
|
+
try { return (await readUnheldEvidence({ vaultRoot: store.vaultRoot }))?.unlisted === true } catch { return false }
|
|
250
|
+
}
|
|
251
|
+
|
|
168
252
|
async function takeLock(workspaceRoot, workspaceId) {
|
|
169
253
|
if (heldLock) return { acquired: true }
|
|
170
254
|
const lock = await rules.acquireEngineLock({
|
|
@@ -199,9 +283,18 @@ export function createMaintenanceEngineForOracleTests(options = {}, primitives =
|
|
|
199
283
|
}
|
|
200
284
|
|
|
201
285
|
function storeFor(scope, workspaceRoot, workspaceId, repositoryRoots) {
|
|
202
|
-
|
|
286
|
+
// A view's vault can be allocated, made again or moved while the engine runs: the store follows its record. A
|
|
287
|
+
// store is used again only while its allocated folder is still the one the record names; otherwise it is dropped,
|
|
288
|
+
// and the store made in its place refuses, typed (vault-allocation-replaced, say), so a folder replaced while the
|
|
289
|
+
// service runs, or a link put where it was, is never published into.
|
|
290
|
+
const signature = JSON.stringify([workspaceRoot, workspaceId, repositoryRoots, readVaultAllocation({ workspaceRoot, workspaceId, scopeId: scope.scopeId })])
|
|
203
291
|
const cached = stores.get(scope.scopeId)
|
|
204
|
-
|
|
292
|
+
// A vault root that is gone (a folder under the data root the person removed, say) is looked for again too, so the
|
|
293
|
+
// store made in its place says why rather than failing on the folder that is not there.
|
|
294
|
+
if (cached?.signature === signature && fs.lstatSync(cached.store.vaultRoot, { throwIfNoEntry: false }) !== undefined) {
|
|
295
|
+
try { cached.store.checkAllocatedVault?.(); return cached.store } catch (error) { if (!isTypedRefusal(error)) throw error }
|
|
296
|
+
}
|
|
297
|
+
stores.delete(scope.scopeId)
|
|
205
298
|
const store = seams.createRecoveryStore({ workspaceRoot, workspaceId, scopeId: scope.scopeId, repositoryRoots })
|
|
206
299
|
stores.set(scope.scopeId, { signature, store })
|
|
207
300
|
return store
|
|
@@ -246,6 +339,17 @@ export function createMaintenanceEngineForOracleTests(options = {}, primitives =
|
|
|
246
339
|
async function runTick() {
|
|
247
340
|
const now = isoTime(clock)
|
|
248
341
|
const nowMs = Date.parse(now)
|
|
342
|
+
// A request that arrives while this tick runs belongs to the next one.
|
|
343
|
+
const requested = new Set(preparationRequests)
|
|
344
|
+
preparationRequests.clear()
|
|
345
|
+
// What the app looked like, asked at most once per tick and only when a decision needs it.
|
|
346
|
+
let appState
|
|
347
|
+
const appNow = () => {
|
|
348
|
+
if (appState === undefined) { try { appState = typeof observeApp === 'function' ? observeApp() ?? null : null } catch { appState = null } }
|
|
349
|
+
return appState
|
|
350
|
+
}
|
|
351
|
+
// An adapter built on a tick somebody asked for asks the app again instead of reusing an answer from before it changed.
|
|
352
|
+
if (requested.size > 0) try { adapterFactory.forget?.() } catch { /* a factory that remembers nothing */ }
|
|
249
353
|
const full = firstTick || forceFull || rules.isFullReconciliationDue({ nowMs, lastFullMs, intervalMs: fullReconciliationIntervalMs })
|
|
250
354
|
const changes = []
|
|
251
355
|
// The hints collected so far belong to this tick; one that arrives while it runs belongs to the next.
|
|
@@ -257,20 +361,25 @@ export function createMaintenanceEngineForOracleTests(options = {}, primitives =
|
|
|
257
361
|
|
|
258
362
|
// 1. Project and enablement. The digests are seeded before the project is
|
|
259
363
|
// loaded for good, so the loaded project is never older than the index.
|
|
364
|
+
// A loader may answer a promise (one that composes a personal workspace off
|
|
365
|
+
// the event loop). A project bound to a personal workspace is loaded once:
|
|
366
|
+
// what a generation holds is fixed, and an input that changed after the load
|
|
367
|
+
// changes the validity key every build is checked against.
|
|
260
368
|
if (project === null) {
|
|
261
|
-
project = loadProject()
|
|
262
|
-
reconcile({ index, files:
|
|
263
|
-
project = loadProject()
|
|
369
|
+
project = await loadProject()
|
|
370
|
+
reconcile({ index, files: configFilesOf(project), prefix: CONFIG_PREFIX, full: true, lstat })
|
|
371
|
+
if (personalWorkspaceBindingOf(project) === null) project = await loadProject()
|
|
264
372
|
} else {
|
|
265
|
-
const config = reconcile({ index, files:
|
|
373
|
+
const config = reconcile({ index, files: configFilesOf(project), prefix: CONFIG_PREFIX, full, hinted, lstat })
|
|
266
374
|
if (config.changes.length > 0) {
|
|
267
|
-
project = loadProject()
|
|
268
|
-
reconcile({ index, files:
|
|
375
|
+
project = await loadProject()
|
|
376
|
+
reconcile({ index, files: configFilesOf(project), prefix: CONFIG_PREFIX, full: true, lstat })
|
|
269
377
|
}
|
|
270
378
|
}
|
|
271
379
|
const enablement = readObsidianEnablement(project)
|
|
272
380
|
if (enablement.state === 'disabled') {
|
|
273
381
|
stopWatching()
|
|
382
|
+
declaredScopes = new Set()
|
|
274
383
|
await persistOutcome({ enablement: 'disabled', state: 'disabled', reason: enablement.reason, now, scopeIds: enablement.scopes.map((scope) => scope.scopeId) })
|
|
275
384
|
semantic = null
|
|
276
385
|
firstTick = true
|
|
@@ -289,14 +398,63 @@ export function createMaintenanceEngineForOracleTests(options = {}, primitives =
|
|
|
289
398
|
if (!lock.acquired) return { state: 'busy', reason: 'engine-lock-held', lock: { reason: lock.reason, holder: lock.holder ?? null }, changes: [], scopes: [], pendingEdits: [], dispatched: [], lateWriters: [] }
|
|
290
399
|
const machine = readMachineSettings({ workspaceRoot, workspaceId })
|
|
291
400
|
?? writeMachineSettings({ workspaceRoot, workspaceId, repositoryRoots, settings: defaultMachineSettings({ workspaceId, updatedAt: now }) })
|
|
401
|
+
const eligibility = fixedEligibility ?? eligibilityFor({ machine, project })
|
|
292
402
|
const stateStore = createMaintenanceStateStore({ workspaceRoot, workspaceId })
|
|
293
403
|
known = { stateStore, maintenanceMode: machine.maintenanceMode }
|
|
294
|
-
|
|
404
|
+
// Each view's vault: allocated now where this workspace decided its vaults live, when it has none and was never
|
|
405
|
+
// published under the data root (vault-location.mjs). A view whose folder cannot be allocated is not prepared on
|
|
406
|
+
// this tick, and its freshness says why; the other views go on.
|
|
407
|
+
const allocationRefusals = new Map()
|
|
408
|
+
if (machine.decisions.location !== null) {
|
|
409
|
+
// The app's list, read once per tick and only when a view is about to be allocated. An engine given no reader
|
|
410
|
+
// coordinates with no app, and knows no list. A list that cannot be read allocates nothing: the view says why,
|
|
411
|
+
// and its folder is allocated at a later tick, once the list reads.
|
|
412
|
+
let list = null
|
|
413
|
+
const appList = async () => {
|
|
414
|
+
if (typeof readAppVaultList !== 'function') return { ok: true, vaults: null }
|
|
415
|
+
if (list === null) {
|
|
416
|
+
let answer
|
|
417
|
+
try { answer = await readAppVaultList() } catch { answer = null }
|
|
418
|
+
list = answer?.ok === true ? { ok: true, vaults: answer.vaults ?? {} } : { ok: false, code: typeof answer?.code === 'string' ? answer.code : 'obsidian-settings-unreadable' }
|
|
419
|
+
}
|
|
420
|
+
return list
|
|
421
|
+
}
|
|
422
|
+
// The folders allocated to the views; a record that cannot be read refuses only its own view, below.
|
|
423
|
+
const allocatedPaths = enablement.scopes.map((scope) => { try { return readVaultAllocation({ workspaceRoot, workspaceId, scopeId: scope.scopeId })?.path } catch { return undefined } }).filter((item) => typeof item === 'string')
|
|
424
|
+
for (const scope of enablement.scopes) {
|
|
425
|
+
try {
|
|
426
|
+
let vaults = null
|
|
427
|
+
// The list is read where a folder is about to be made: a first allocation, or an allocated folder that has gone.
|
|
428
|
+
const record = readVaultAllocation({ workspaceRoot, workspaceId, scopeId: scope.scopeId })
|
|
429
|
+
if (record === null ? !hasCommittedGeneration({ workspaceRoot, scopeId: scope.scopeId }) : allocatedFolderState(record) === 'missing') {
|
|
430
|
+
const known = await appList()
|
|
431
|
+
if (!known.ok) refuse('app-vault-list-unreadable', 'Obsidian\'s vault list could not be read, so no folder is allocated for this view yet; it is tried again at the next tick', { cause: known.code })
|
|
432
|
+
vaults = known.vaults
|
|
433
|
+
}
|
|
434
|
+
ensureVaultAllocation({ workspaceRoot, workspaceId, scopeId: scope.scopeId, location: machine.decisions.location, projectName: projectDisplayName(project), repositoryRoots, vaults, allocatedPaths, now })
|
|
435
|
+
} catch (error) {
|
|
436
|
+
const code = viewRefusalCode(error)
|
|
437
|
+
if (code === null) throw error
|
|
438
|
+
allocationRefusals.set(scope.scopeId, code)
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
// Each view's store, on its own: one whose vault cannot be placed (a record naming a folder inside a repository,
|
|
443
|
+
// say) is refused alone, and the other views go on.
|
|
444
|
+
const scopes = []
|
|
445
|
+
for (const scope of enablement.scopes) {
|
|
446
|
+
if (allocationRefusals.has(scope.scopeId)) continue
|
|
447
|
+
try { scopes.push({ scope, store: storeFor(scope, workspaceRoot, workspaceId, repositoryRoots) }) } catch (error) {
|
|
448
|
+
const code = viewRefusalCode(error)
|
|
449
|
+
if (code === null) throw error
|
|
450
|
+
allocationRefusals.set(scope.scopeId, code)
|
|
451
|
+
}
|
|
452
|
+
}
|
|
295
453
|
|
|
296
454
|
watch([
|
|
297
455
|
...(project.repos ?? []).filter((repo) => !repo.external && typeof repo.path === 'string').map((repo) => ({ id: `repo:${repo.name}`, path: repo.path, recursive: true, keyPrefix: sourceKey(repo.name, ''), keyOf: (relative) => sourceKey(repo.name, relative) })),
|
|
298
456
|
...scopes.map(({ scope, store }) => ({ id: `vault:${scope.scopeId}`, path: store.vaultRoot, recursive: true, keyPrefix: vaultKey(scope.scopeId, ''), keyOf: (relative) => vaultKey(scope.scopeId, relative) })),
|
|
299
|
-
...[...new Set(
|
|
457
|
+
...[...new Set(configFilesOf(project).map((file) => path.dirname(file.absolute)))].filter((directory) => fs.existsSync(directory))
|
|
300
458
|
.map((directory) => ({ id: `config:${directory}`, path: directory, recursive: false, keyPrefix: CONFIG_PREFIX, keyOf: (relative) => configKey(path.join(directory, relative)) })),
|
|
301
459
|
])
|
|
302
460
|
|
|
@@ -305,10 +463,12 @@ export function createMaintenanceEngineForOracleTests(options = {}, primitives =
|
|
|
305
463
|
const pending = stateStore.readPendingEdits()
|
|
306
464
|
let edits = pending.edits
|
|
307
465
|
const basesOf = new Map()
|
|
466
|
+
const earlierLayout = new Set()
|
|
308
467
|
for (const { scope, store } of scopes) {
|
|
309
468
|
const { manifest, bases } = trustedNoteBases(store)
|
|
310
469
|
basesOf.set(scope.scopeId, bases)
|
|
311
470
|
if (!manifest) continue
|
|
471
|
+
if (manifestLayoutVersion(manifest) === 1) earlierLayout.add(scope.scopeId)
|
|
312
472
|
const vault = reconcile({ index, files: listVaultNotes({ scopeId: scope.scopeId, vaultRoot: store.vaultRoot, manifest }), prefix: vaultKey(scope.scopeId, ''), full, hinted, lstat })
|
|
313
473
|
const digestOf = (notePath) => index.get(vaultKey(scope.scopeId, notePath))?.digest ?? null
|
|
314
474
|
const observed = observeVaultEdits({ store, workspaceId, scopeId: scope.scopeId, manifest, bases, digestOf, edits, now, preserve: rules.preserve })
|
|
@@ -321,9 +481,12 @@ export function createMaintenanceEngineForOracleTests(options = {}, primitives =
|
|
|
321
481
|
// Observation. The registered proposal adapter is handed a copy of the pending edits and observes the open ones
|
|
322
482
|
// the object store does not know yet, a bounded number per tick: each is classified from its preserved bytes
|
|
323
483
|
// against the source as it is now and recorded as what it is, in manual and in automatic mode alike, and no
|
|
324
|
-
// source is written. What refused before anything was recorded is offered again on a full reconciliation only
|
|
484
|
+
// source is written. What refused before anything was recorded is offered again on a full reconciliation only;
|
|
485
|
+
// for a personal workspace, also on the tick after one whose graph was pending, which offers every earlier
|
|
486
|
+
// refusal again.
|
|
325
487
|
const proposalAdapter = extensions.get('proposal-adapter')
|
|
326
|
-
|
|
488
|
+
// A personal workspace is never composed on this event loop by the adapter: its builds defer, as the engine's do.
|
|
489
|
+
const adapterContext = () => ({ project, workspaceRoot, workspaceId, repositoryRoots, edits: structuredClone(edits), clock, env, deferPersonalValidation: true })
|
|
327
490
|
let observed = null
|
|
328
491
|
if (proposalAdapter !== null && rules.observeEdits(proposalAdapter)) {
|
|
329
492
|
try { observed = await proposalAdapter.observe(adapterContext(), { retryRefused: full }) } catch { observed = { adapterId: proposalAdapter.id, failed: 'proposal-adapter-threw' } }
|
|
@@ -391,6 +554,7 @@ export function createMaintenanceEngineForOracleTests(options = {}, primitives =
|
|
|
391
554
|
if (full) { lastFullMs = nowMs; forceFull = false }
|
|
392
555
|
|
|
393
556
|
// 7. Which views are invalid.
|
|
557
|
+
declaredScopes = new Set(enablement.scopes.map((scope) => scope.scopeId))
|
|
394
558
|
const previous = readPreviousFreshness(stateStore)
|
|
395
559
|
const entries = new Map()
|
|
396
560
|
const unseen = new Set()
|
|
@@ -401,15 +565,34 @@ export function createMaintenanceEngineForOracleTests(options = {}, primitives =
|
|
|
401
565
|
}
|
|
402
566
|
const attempt = new Map()
|
|
403
567
|
const invalidate = (scopeId, changeClass) => attempt.set(scopeId, new Set([...(attempt.get(scopeId) ?? []), ...(changeClass ? [changeClass] : [])]))
|
|
568
|
+
// The notes that keep a view in the vault layout they were published in (see layoutHeldPaths).
|
|
569
|
+
const layoutHeldOf = (scopeId) => layoutHeldPaths(edits, scopeId, now)
|
|
404
570
|
for (const { scope } of scopes) {
|
|
405
571
|
const entry = entries.get(scope.scopeId)
|
|
406
|
-
// A view that did not settle is tried again
|
|
407
|
-
|
|
572
|
+
// A view that did not settle is tried again, not on every tick: see the head of this file.
|
|
573
|
+
const retried = (RETRIED_STATES.has(entry.state)
|
|
574
|
+
&& (full || rules.isRetryDue({ nowMs, unsettled: unsettled.get(scope.scopeId), appState: appNow, retryMs: publicationRetryMs, maxMs: fullReconciliationIntervalMs })))
|
|
575
|
+
|| (waitingForApp.has(scope.scopeId) && rules.isRetryDue({ nowMs, unsettled: waitingForApp.get(scope.scopeId), appState: appNow, retryMs: publicationRetryMs, maxMs: fullReconciliationIntervalMs }))
|
|
576
|
+
if (firstTick || unseen.has(scope.scopeId) || requested.has(scope.scopeId) || retried) invalidate(scope.scopeId, null)
|
|
577
|
+
// A settled view still in the earlier vault layout is laid out again at the first tick where no note holds it.
|
|
578
|
+
if (earlierLayout.has(scope.scopeId) && entry.state === 'current' && layoutHeldOf(scope.scopeId).length === 0) invalidate(scope.scopeId, null)
|
|
408
579
|
}
|
|
409
580
|
for (const change of changes) for (const { scope } of scopes) if (change.scopeId === undefined || change.scopeId === scope.scopeId) invalidate(scope.scopeId, change.changeClass)
|
|
581
|
+
// A bound personal workspace is composed again, off the event loop, at every full reconciliation, whatever else is
|
|
582
|
+
// being prepared: a cause outside its validity key can stop a generation holding without a file this engine
|
|
583
|
+
// observes. Its views are prepared again only when the composition refuses, or confirms another key.
|
|
584
|
+
let personalRefusal = null
|
|
585
|
+
if (full && personalWorkspaceBindingOf(project) !== null) {
|
|
586
|
+
const verdict = await validatePersonalWorkspace(project)
|
|
587
|
+
if (verdict.ok !== true) personalRefusal = verdict.code
|
|
588
|
+
if (verdict.ok !== true || verdict.changed) for (const { scope } of scopes) invalidate(scope.scopeId, null)
|
|
589
|
+
}
|
|
410
590
|
|
|
411
591
|
// Invalidation is durable before any work: a view is not reported current while it is being rebuilt.
|
|
412
592
|
for (const [scopeId, classes] of attempt) entries.set(scopeId, { ...demote(entries.get(scopeId), 'stale', 'invalidated', now), changeClasses: [...classes].sort() })
|
|
593
|
+
for (const [scopeId, code] of allocationRefusals) {
|
|
594
|
+
entries.set(scopeId, demote(previous?.scopes.find((entry) => entry.scopeId === scopeId && entry.state !== 'disabled') ?? blankEntry(scopeId, now), 'stale', code, now))
|
|
595
|
+
}
|
|
413
596
|
for (const entry of previous?.scopes ?? []) if (!entries.has(entry.scopeId)) entries.set(entry.scopeId, demote(entry, 'disabled', 'scope-not-configured', now))
|
|
414
597
|
const persist = () => stateStore.writeFreshness(freshnessDocument({ workspaceId, enablement: 'enabled', maintenanceMode: machine.maintenanceMode, now, entries }))
|
|
415
598
|
persist()
|
|
@@ -418,7 +601,19 @@ export function createMaintenanceEngineForOracleTests(options = {}, primitives =
|
|
|
418
601
|
if (attempt.size > 0) {
|
|
419
602
|
let built = null
|
|
420
603
|
try {
|
|
421
|
-
|
|
604
|
+
if (personalRefusal !== null) refuse(personalRefusal, 'the personal workspace refused this generation; nothing is prepared from it', { source: 'personal-workspace' })
|
|
605
|
+
// A personal workspace whose validity key is not confirmed yet is composed off the event loop, then built again.
|
|
606
|
+
const build = () => seams.buildGraph({ project, eligibility, cache: graphCache, index, deferPersonalValidation: true })
|
|
607
|
+
let graph
|
|
608
|
+
try { graph = build() } catch (error) {
|
|
609
|
+
if (error?.code !== PERSONAL_VALIDATION_PENDING) throw error
|
|
610
|
+
const verdict = await validatePersonalWorkspace(project)
|
|
611
|
+
if (verdict?.ok !== true) refuse(verdict?.code ?? 'personal-binding-unrecognized', 'the personal workspace refused this generation; nothing is prepared from it', { source: 'personal-workspace' })
|
|
612
|
+
try { graph = build() } catch (again) {
|
|
613
|
+
if (again?.code !== PERSONAL_VALIDATION_PENDING) throw again
|
|
614
|
+
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' })
|
|
615
|
+
}
|
|
616
|
+
}
|
|
422
617
|
// The assets this graph lets a view copy are observed from now on, and hashed now so the snapshot pins
|
|
423
618
|
// what observation saw. A withheld asset is not observed: its bytes can change no view.
|
|
424
619
|
const formerAssetKeys = new Set(observedAssets.map((asset) => sourceKey(asset.repo, asset.path)))
|
|
@@ -436,20 +631,30 @@ export function createMaintenanceEngineForOracleTests(options = {}, primitives =
|
|
|
436
631
|
if (!isTypedRefusal(error)) throw error
|
|
437
632
|
for (const scopeId of attempt.keys()) entries.set(scopeId, demote(entries.get(scopeId), 'stale', error.code, now))
|
|
438
633
|
if (REREAD_CODES.has(error.code)) forceFull = true
|
|
634
|
+
// The personal workspace refused the bound generation: the project is loaded again at the next tick, so a
|
|
635
|
+
// loader that now binds another generation is followed.
|
|
636
|
+
if (error.detail?.source === 'personal-workspace') project = null
|
|
439
637
|
}
|
|
440
638
|
for (const { scope, store } of built ? scopes.filter((item) => attempt.has(item.scope.scopeId)) : []) {
|
|
441
639
|
const { scopeId } = scope
|
|
442
640
|
const entry = entries.get(scopeId)
|
|
443
|
-
|
|
641
|
+
// What this attempt found worth naming: notes and rules (see "Notes that were laid out anyway" in docs/obsidian-contract.md).
|
|
642
|
+
let diagnostics = []
|
|
643
|
+
const settle = (state, reason, extra = {}) => {
|
|
644
|
+
const { diagnostics: _earlier, ...rest } = { ...entry, ...extra, state, reason, verified: extra.verified === true, checkedAt: now }
|
|
645
|
+
entries.set(scopeId, diagnostics.length > 0 ? { ...rest, diagnostics } : rest)
|
|
646
|
+
}
|
|
444
647
|
try {
|
|
648
|
+
const held = heldPaths(edits, scopeId)
|
|
445
649
|
const prepared = seams.prepareView({
|
|
446
650
|
snapshot: built.snapshot, profile: built.profile, scope, persistentPathRegistry: stateStore.readPathRegistry(), priorManifest: store.readCurrentManifest(),
|
|
447
651
|
existingSettings: null, clock, vaultRootBytes: Buffer.byteLength(store.vaultRoot, 'utf8'), cache: preparationCacheFor(scopeId),
|
|
652
|
+
heldNotePaths: held, layoutHeldNotePaths: layoutHeldOf(scopeId), viewScopeIds: scopes.map((item) => item.scope.scopeId),
|
|
448
653
|
})
|
|
449
654
|
stateStore.writePathRegistry(prepared.persistentPathRegistry)
|
|
655
|
+
diagnostics = (prepared.manifest.ext?.[OBSIDIAN_EXT_KEY]?.diagnostics ?? []).slice(0, 100)
|
|
450
656
|
const preparedGenerationId = prepared.manifest.generationId
|
|
451
657
|
const trusted = () => store.readCurrent()
|
|
452
|
-
const held = heldPaths(edits, scopeId)
|
|
453
658
|
const observed = new Map(held.map((notePath) => [notePath, rules.heldNoteDigest({ file: path.join(store.vaultRoot, notePath), indexed: index.get(vaultKey(scopeId, notePath))?.digest ?? null })]))
|
|
454
659
|
const conflicts = rules.publicationConflicts({ prepared, held, bases: basesOf.get(scopeId) ?? new Map(), observed })
|
|
455
660
|
if (conflicts.length > 0) {
|
|
@@ -459,10 +664,43 @@ export function createMaintenanceEngineForOracleTests(options = {}, primitives =
|
|
|
459
664
|
}
|
|
460
665
|
settle('updating', 'publishing', { generationId: trusted()?.generationId ?? null, preparedGenerationId, heldNotes: held })
|
|
461
666
|
persist()
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
667
|
+
// A generation that is already the committed one needs no app: the publisher settles it after its own restart
|
|
668
|
+
// recovery, before it would probe. Its adapter is then built only if the publisher asks for one, so an app
|
|
669
|
+
// that cannot be qualified never makes a current view stale. Any other publication builds the adapter first,
|
|
670
|
+
// and an app that does not qualify keeps the publisher from being reached at all.
|
|
671
|
+
const lazy = trusted()?.generationId === preparedGenerationId ? builtOnFirstUse(() => adapterFactory({ store, scope })) : null
|
|
672
|
+
// A view never published into its allocated folder is not kept from its first publication by an app that
|
|
673
|
+
// does not qualify, while that app's list shows no entry for the folder: the publisher is reached with an
|
|
674
|
+
// adapter that coordinates with nothing, and may take its `direct-unheld` path. Any other outcome is the
|
|
675
|
+
// factory's refusal, as before.
|
|
676
|
+
let heldBackBy = null
|
|
677
|
+
let adapter = lazy
|
|
678
|
+
if (adapter === null) {
|
|
679
|
+
try { adapter = await adapterFactory({ store, scope }) } catch (error) {
|
|
680
|
+
if (!isTypedRefusal(error) || !(await unheldMayApply(store, trusted()))) throw error
|
|
681
|
+
heldBackBy = error
|
|
682
|
+
adapter = uncoordinatedAdapter(error)
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
let result
|
|
686
|
+
try {
|
|
687
|
+
result = await seams.publishView({
|
|
688
|
+
preparedView: prepared, protocolId: PROTOCOL_ID, expectedGeneration: trusted()?.generationId ?? null, recoveryStore: store, adapter, clock,
|
|
689
|
+
...(quietPeriodMs === undefined ? {} : { quietPeriodMs }),
|
|
690
|
+
...(typeof readUnheldEvidence === 'function' ? { unheldEvidence: readUnheldEvidence, platform } : {}),
|
|
691
|
+
})
|
|
692
|
+
// Stopped before its commit for want of an app that coordinates: the app's refusal is what the person has to
|
|
693
|
+
// act on, and `open` answers it before it would add the vault to the app.
|
|
694
|
+
if (heldBackBy !== null && (result.state === 'refused' || (result.state === 'updating' && result.notes.some((note) => note.blocking && note.outcome === 'editor-uncoordinated')))) throw heldBackBy
|
|
695
|
+
} catch (error) {
|
|
696
|
+
// The publisher asked for the app only to publish the committed generation again (a plugin file drifted),
|
|
697
|
+
// and the app did not qualify: nothing was written, and the committed generation is read back as it is.
|
|
698
|
+
// The file is written at a later attempt, once the app qualifies.
|
|
699
|
+
if (lazy === null || lazy.refusal() !== error || !isTypedRefusal(error) || trusted()?.generationId !== preparedGenerationId) throw error
|
|
700
|
+
result = { state: 'committed', alreadyCommitted: true, notes: [], waitsForApp: true }
|
|
701
|
+
}
|
|
702
|
+
if (result.waitsForApp === true) waitingForApp.set(scopeId, { attempts: (waitingForApp.get(scopeId)?.attempts ?? 0) + 1, lastAttemptMs: nowMs, appState: appNow() })
|
|
703
|
+
else waitingForApp.delete(scopeId)
|
|
466
704
|
const pointerNow = trusted()
|
|
467
705
|
const common = { generationId: pointerNow?.generationId ?? null, preparedGenerationId, retainedEdits: pointerNow?.retained?.length ?? 0 }
|
|
468
706
|
if (result.state === 'refused') {
|
|
@@ -483,16 +721,27 @@ export function createMaintenanceEngineForOracleTests(options = {}, primitives =
|
|
|
483
721
|
// Somebody wrote during the publication. The next tick preserves and queues it.
|
|
484
722
|
else if (differing.length > 0 || editedUnderPublisher) settle('updating', 'read-back-differs', common)
|
|
485
723
|
else if (common.generationId !== preparedGenerationId) settle('stale', 'committed-generation-differs', common)
|
|
486
|
-
|
|
724
|
+
// Kept current while a plugin file waits for the app: the reason says so, and status shows it on the plugin line.
|
|
725
|
+
else settle('current', result.waitsForApp === true ? PLUGIN_FILES_WAIT_FOR_APP : result.alreadyCommitted ? 'verified-by-read-back' : 'published-and-verified', { ...common, heldNotes: [], verified: true })
|
|
487
726
|
}
|
|
488
727
|
} catch (error) {
|
|
489
728
|
if (!isTypedRefusal(error)) { settle('stale', 'publisher-error'); persist(); throw error }
|
|
729
|
+
// A redaction refusal names the note and the rule it concerns, never the value.
|
|
730
|
+
const where = error.code === 'redaction-failure' ? error.detail ?? {} : {}
|
|
731
|
+
diagnostics = typeof where.rule === 'string' ? [{ code: error.code, rule: where.rule, ...(typeof where.notePath === 'string' ? { notePath: where.notePath } : {}), ...(typeof where.filePath === 'string' ? { filePath: where.filePath } : {}) }] : []
|
|
490
732
|
settle('stale', error.code)
|
|
491
733
|
if (REREAD_CODES.has(error.code)) forceFull = true
|
|
492
734
|
}
|
|
493
735
|
}
|
|
494
736
|
}
|
|
495
737
|
persist()
|
|
738
|
+
for (const scopeId of attempt.keys()) {
|
|
739
|
+
const settled = !RETRIED_STATES.has(entries.get(scopeId).state)
|
|
740
|
+
if (settled) unsettled.delete(scopeId)
|
|
741
|
+
else unsettled.set(scopeId, { attempts: (unsettled.get(scopeId)?.attempts ?? 0) + 1, lastAttemptMs: nowMs, appState: appNow() })
|
|
742
|
+
}
|
|
743
|
+
for (const scopeId of [...unsettled.keys()]) if (!entries.has(scopeId) || entries.get(scopeId).state === 'disabled') unsettled.delete(scopeId)
|
|
744
|
+
for (const scopeId of [...waitingForApp.keys()]) if (entries.get(scopeId)?.state !== 'current') waitingForApp.delete(scopeId)
|
|
496
745
|
semantic = nextSemantic
|
|
497
746
|
firstTick = false
|
|
498
747
|
return {
|
|
@@ -505,6 +754,16 @@ export function createMaintenanceEngineForOracleTests(options = {}, primitives =
|
|
|
505
754
|
|
|
506
755
|
return {
|
|
507
756
|
extensions,
|
|
757
|
+
// The next tick that starts prepares and publishes this view once more, whatever its state. A view this
|
|
758
|
+
// project does not declare is ignored then.
|
|
759
|
+
// A view the project declared at the last tick (before the first, any view), and at most MAX_PREPARATION_REQUESTS
|
|
760
|
+
// of them; whether the request was taken.
|
|
761
|
+
requestPreparation(scopeId) {
|
|
762
|
+
if (typeof scopeId !== 'string' || scopeId === '' || (declaredScopes !== null && !declaredScopes.has(scopeId))) return false
|
|
763
|
+
if (!preparationRequests.has(scopeId) && preparationRequests.size >= MAX_PREPARATION_REQUESTS) return false
|
|
764
|
+
preparationRequests.add(scopeId)
|
|
765
|
+
return true
|
|
766
|
+
},
|
|
508
767
|
async tick() {
|
|
509
768
|
if (running) return { state: 'busy' }
|
|
510
769
|
running = true
|
|
@@ -65,7 +65,7 @@ export function normalizeApplyResult(value) {
|
|
|
65
65
|
// an apply operation replaces it.
|
|
66
66
|
const OPERATION_NAME = /^[a-z][a-z0-9-]{0,31}$/
|
|
67
67
|
// The operations the command ships. The service composes the same registry, so both refuse the same contributions.
|
|
68
|
-
export const BUILT_IN_OPERATIONS = Object.freeze(['status', 'scope', 'audience', 'mode', 'policy', 'service', 'open', 'apply', 'help'])
|
|
68
|
+
export const BUILT_IN_OPERATIONS = Object.freeze(['status', 'settings', 'location', 'scope', 'view', 'audience', 'mode', 'policy', 'service', 'open', 'uninstall', 'plugin', 'apply', 'help'])
|
|
69
69
|
export const REPLACEABLE_OPERATIONS = Object.freeze(['apply'])
|
|
70
70
|
|
|
71
71
|
export function createCommandOperations({ reserved = [] } = {}) {
|
|
@@ -6,29 +6,36 @@
|
|
|
6
6
|
export { ObsidianMaintenanceRefusal } from './errors.mjs'
|
|
7
7
|
export { DISABLED_REASONS, ENABLEMENT_STATES, readObsidianEnablement } from './enablement.mjs'
|
|
8
8
|
export {
|
|
9
|
-
LOCAL_POINTER_SCHEMA, MACHINE_SETTINGS_SCHEMA,
|
|
10
|
-
|
|
9
|
+
DECISIONS, DECISION_SOURCES, LOCAL_POINTER_SCHEMA, MACHINE_SETTINGS_SCHEMA, MACHINE_SETTINGS_SCHEMA_V1, MAINTENANCE_MODES, ONLY_YOU_AUDIENCES, authorizeAutomaticApply,
|
|
10
|
+
defaultDataRoot, defaultMachineSettings, ensureWorkspaceIdentity, installApplyPolicy, localPointerPath, protectedRoots, readInstalledApplyPolicy, readLocalPointer,
|
|
11
|
+
readMachineSettings, resolveDataRoot, revokeApplyPolicy, withDecision, workspaceStateRoot, writeLocalPointer, writeMachineSettings,
|
|
11
12
|
} from './machine-settings.mjs'
|
|
12
13
|
export { APPLY_RESULT_STATUSES, BUILT_IN_OPERATIONS, EXTENSION_KINDS, REPLACEABLE_OPERATIONS, UNAVAILABLE_APPLY_OPERATION, createCommandOperations, createMaintenanceExtensions, createObsidianRegistry } from './extension-points.mjs'
|
|
13
14
|
export { CONTRIBUTIONS_DIRECTORY, loadContributions } from './contributions.mjs'
|
|
14
15
|
export { APP_OUTCOMES, MINIMUM_APP_VERSION, compareAppVersions, createQualifiedAdapterFactory, meetsMinimumAppVersion, parseAppVersion, qualifyApp } from './app-capability.mjs'
|
|
15
16
|
export { OPENING_OUTCOMES, describeOutcome, openScope, scopeReport } from './opening.mjs'
|
|
17
|
+
export { appStateSignature, registerVaultInObsidianSettings } from './app-registration.mjs'
|
|
16
18
|
export {
|
|
17
19
|
CHANGE_CLASSES, CLOSED_EDIT_STATES, EDIT_STATES, FRESHNESS_SCHEMA, FRESHNESS_STATES, LATE_WRITERS_SCHEMA, OPEN_EDIT_STATES, PENDING_EDITS_SCHEMA,
|
|
18
20
|
createMaintenanceStateStore, validateFreshness, validateLateWriters, validatePendingEdits,
|
|
19
21
|
} from './state-store.mjs'
|
|
20
22
|
export { listConfigFiles, listSourceFiles, listVaultNotes, reconcile } from './observation.mjs'
|
|
21
23
|
export { createFsWatcherFactory, createNullWatcherFactory } from './watchers.mjs'
|
|
22
|
-
export { DEFAULT_ELIGIBILITY, assetEligibilityFor, createProductionSeams } from './pipeline.mjs'
|
|
23
|
-
export { DEFAULT_FULL_RECONCILIATION_INTERVAL_MS, DEFAULT_LATE_WRITER_WINDOW_MS, DEFAULT_RETRY_INTERVAL_MS, createMaintenanceEngine } from './engine.mjs'
|
|
24
|
+
export { DEFAULT_ELIGIBILITY, assetEligibilityFor, createProductionSeams, eligibilityFor, onlyYouEligibility } from './pipeline.mjs'
|
|
25
|
+
export { DEFAULT_FULL_RECONCILIATION_INTERVAL_MS, DEFAULT_LATE_WRITER_WINDOW_MS, DEFAULT_PUBLICATION_RETRY_MS, DEFAULT_RETRY_INTERVAL_MS, createMaintenanceEngine } from './engine.mjs'
|
|
24
26
|
export { ENGINE_LOCK_DIRECTORY, LOCK_TICKET_SCHEMA, acquirePrivateGenerationLock, createAbandonmentProof, inspectPrivateGenerationLock } from './private-lock.mjs'
|
|
25
27
|
export { DEFAULT_MAX_BACKOFF_MS, DEFAULT_TICK_INTERVAL_MS, createTickLoop } from './tick-loop.mjs'
|
|
26
28
|
export { HEALTH_SCHEMA, LOOPBACK_HOSTS, probeHealth, requestLoopback } from './service-client.mjs'
|
|
27
29
|
export {
|
|
28
|
-
CONSENT_COVERAGES, SERVICE_ERROR_SCHEMA, SERVICE_SETTINGS_SCHEMA, executableIdentity, publicRecord, readLastServiceError,
|
|
29
|
-
removeServiceRecord, serviceNameFor, servicePaths, validateServiceRecord, writeServiceRecord, writeServiceSettings,
|
|
30
|
+
CONSENT_COVERAGES, LAST_STARTUP_SCHEMA, SERVICE_ERROR_SCHEMA, SERVICE_SETTINGS_SCHEMA, STARTUP_OUTCOMES, executableIdentity, publicRecord, readLastServiceError, readLastStartup,
|
|
31
|
+
readServiceRecord, readServiceSettings, removeServiceRecord, serviceNameFor, servicePaths, validateServiceRecord, writeServiceRecord, writeServiceSettings,
|
|
30
32
|
} from './service-record.mjs'
|
|
31
33
|
export { MAX_REQUEST_BYTES, SERVICE_OPERATIONS, createServiceServer } from './service-server.mjs'
|
|
32
|
-
export {
|
|
34
|
+
export { PLUGIN_BEARER_SCHEMA, PLUGIN_PRESENCE_SCHEMA, createPluginChannel, createPluginSessions, ensurePluginBearer, pluginBearerDirectory, pluginPresence, readPluginBearers } from './plugin-channel.mjs'
|
|
35
|
+
export { PLUGIN_PRESENCE_REASONS, pluginPresenceOf, turnPluginOnNext, withPluginReportedVersion } from './plugin-presence.mjs'
|
|
36
|
+
export { PLUGIN_CHOICE_SCHEMA, PLUGIN_CHOICE_STATES, confirmPluginEntry, confirmPluginSeen, currentPluginChoice, decidePluginChoice, pluginChoiceDirectory, readCommunityEntry, readPluginChoice, viewVaultRoot, writePluginChoice } from './plugin-choice.mjs'
|
|
37
|
+
export { DEFAULT_SHUTDOWN_GRACE_MS, RELEASE_CHANGED, SERVICE_STATUS_SCHEMA, resolveServiceWorkspace, runMaintenanceService } from './service.mjs'
|
|
33
38
|
export { DEFAULT_START_TIMEOUT_MS, DEFAULT_STOP_TIMEOUT_MS, SERVICE_STATES, readServiceStatusDocument, requestServiceTick, serviceStatus, startService, stopService } from './lifecycle.mjs'
|
|
34
|
-
export { STARTUP_PLATFORMS, buildStartupAdapter } from './startup-adapters.mjs'
|
|
39
|
+
export { STARTUP_PLATFORMS, buildStartupAdapter, startupSearchPath } from './startup-adapters.mjs'
|
|
40
|
+
export { LOGIN_ITEM_SCHEMA, installLoginItem, loginItemStatus, removeLoginItem, resolveLoginItemEntry } from './login-item.mjs'
|
|
41
|
+
export { SERVICE_MANAGER_KINDS, createLaunchdManager, createSystemdManager } from './service-managers.mjs'
|