@mnstry/atelier 0.2.0-alpha.12 → 0.2.0-alpha.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +894 -0
- package/README.md +159 -6
- package/contracts/atelier-adoption-policy.v2.schema.json +55 -0
- package/contracts/atelier-architecture.v1.schema.json +258 -0
- package/contracts/atelier-boundary-delegations.v1.schema.json +161 -0
- package/contracts/atelier-boundary-owner-keys.v1.schema.json +98 -0
- package/contracts/atelier-build.v1.schema.json +1076 -0
- package/contracts/atelier-capability.v1.schema.json +1252 -0
- package/contracts/atelier-coordination.v1.schema.json +1050 -0
- package/contracts/atelier-evidence-navigation.v1.schema.json +603 -0
- package/contracts/atelier-harness.v1.schema.json +234 -0
- package/contracts/atelier-ingestion.v1.schema.json +806 -0
- package/contracts/atelier-inquiry.v1.schema.json +1772 -0
- package/contracts/atelier-instruction-adoption.v1.schema.json +490 -0
- package/contracts/atelier-interaction.v1.schema.json +553 -0
- package/contracts/atelier-judgment.v1.schema.json +690 -0
- package/contracts/atelier-knowledge.v1.schema.json +1512 -0
- package/contracts/atelier-learning.v1.schema.json +1095 -0
- package/contracts/atelier-migration.v3.schema.json +64 -0
- package/contracts/atelier-personal-workspace-manifest.v1.schema.json +87 -0
- package/contracts/atelier-personal-workspace-overlay.v1.schema.json +299 -0
- package/contracts/atelier-template-adoption.v1.schema.json +223 -0
- package/contracts/atelier-template-profile.v1.schema.json +1168 -0
- package/contracts/atelier-trackable.v1.schema.json +781 -0
- package/contracts/atelier-upgrade-plan.v3.schema.json +532 -0
- package/docs/architecture.md +89 -0
- package/docs/assurance-controls.md +2 -2
- package/docs/blocks/capability-stewardship.md +17 -0
- package/docs/blocks/claims.md +16 -3
- package/docs/blocks/inquiry-harnesses.md +19 -0
- package/docs/blocks/learning-harnesses.md +17 -0
- package/docs/capability-stewardship.md +267 -0
- package/docs/coordination.md +51 -0
- package/docs/decision-practices.md +111 -0
- package/docs/decisions.md +29 -0
- package/docs/evidence-navigation.md +237 -0
- package/docs/exact-upgrades.md +3 -2
- package/docs/ingestion.md +181 -0
- package/docs/inquiry-harnesses.md +210 -0
- package/docs/install.md +80 -3
- package/docs/intake-and-guides.md +5 -0
- package/docs/integration-contract-decisions.md +24 -2
- package/docs/interaction-and-reflection.md +72 -0
- package/docs/knowledge-graph.md +20 -0
- package/docs/knowledge-health/api.md +30 -0
- package/docs/knowledge-health/assessment/api.md +96 -0
- package/docs/knowledge-health/assessment/capabilities.md +40 -0
- package/docs/knowledge-health/assessment/evaluation.md +27 -0
- package/docs/knowledge-health/assessment/facilitator.md +30 -0
- package/docs/knowledge-health/assessment/quickstart.md +61 -0
- package/docs/knowledge-health/assessment/troubleshooting.md +23 -0
- package/docs/knowledge-health/devday/facilitator.md +16 -0
- package/docs/knowledge-health/devday/participant-response.md +22 -0
- package/docs/knowledge-health/devday/public-default.md +107 -0
- package/docs/knowledge-health/devday/quickstart.md +18 -0
- package/docs/knowledge-health/devday/support.md +22 -0
- package/docs/knowledge-health/exercise.md +17 -0
- package/docs/knowledge-health/facilitator.md +20 -0
- package/docs/knowledge-health/quickstart.md +94 -0
- package/docs/knowledge-health/readme.md +15 -0
- package/docs/knowledge-health/support.md +34 -0
- package/docs/knowledge-setup.md +248 -0
- package/docs/knowledge-workspace.md +264 -0
- package/docs/learning-harnesses.md +237 -0
- package/docs/learning.md +215 -0
- package/docs/local-services.md +223 -19
- package/docs/obsidian-contract.md +246 -15
- package/docs/obsidian-plugin.md +252 -7
- package/docs/obsidian.md +874 -52
- package/docs/optional-ingestion.md +83 -0
- package/docs/personal-workspace.md +266 -0
- package/docs/practical-judgment.md +92 -0
- package/docs/progressive-ingestion-acceptance.md +89 -0
- package/docs/progressive-ingestion.md +235 -0
- package/docs/project-command-inventory.md +1 -0
- package/docs/release-engineering.md +50 -0
- package/docs/repo-boundary-guard.md +120 -3
- package/docs/research-starter-quickstart.md +66 -0
- package/docs/research-starter.md +114 -0
- package/docs/semantic-ingestion.md +198 -0
- package/docs/semantic-operation.md +318 -0
- package/docs/skill-steward.md +6 -0
- package/docs/template-profile.md +164 -0
- package/docs/template-upgrades.md +114 -0
- package/docs/trackables.md +111 -0
- package/docs/upgrade.md +219 -2
- package/fixtures/atelier-architecture/binding/invalid/document.json +19 -0
- package/fixtures/atelier-architecture/binding/valid/document.json +19 -0
- package/fixtures/atelier-architecture/catalog/invalid/document.json +290 -0
- package/fixtures/atelier-architecture/catalog/valid/document.json +343 -0
- package/fixtures/atelier-build-contract/attempt/invalid/document.json +20 -0
- package/fixtures/atelier-build-contract/attempt/valid/document.json +19 -0
- package/fixtures/atelier-build-contract/candidate/invalid/document.json +24 -0
- package/fixtures/atelier-build-contract/candidate/valid/document.json +23 -0
- package/fixtures/atelier-build-contract/decision/invalid/document.json +31 -0
- package/fixtures/atelier-build-contract/decision/valid/document.json +30 -0
- package/fixtures/atelier-build-contract/delivery/invalid/document.json +18 -0
- package/fixtures/atelier-build-contract/delivery/valid/document.json +17 -0
- package/fixtures/atelier-build-contract/gate/invalid/document.json +22 -0
- package/fixtures/atelier-build-contract/gate/valid/document.json +21 -0
- package/fixtures/atelier-build-contract/ledger/invalid/document.json +270 -0
- package/fixtures/atelier-build-contract/ledger/valid/document.json +269 -0
- package/fixtures/atelier-build-contract/objective/invalid/document.json +73 -0
- package/fixtures/atelier-build-contract/objective/valid/document.json +72 -0
- package/fixtures/atelier-build-contract/progress/invalid/document.json +18 -0
- package/fixtures/atelier-build-contract/progress/valid/document.json +17 -0
- package/fixtures/atelier-capability-contract/adoption/invalid/document.json +71 -0
- package/fixtures/atelier-capability-contract/adoption/valid/document.json +70 -0
- package/fixtures/atelier-capability-contract/event/invalid/document.json +18 -0
- package/fixtures/atelier-capability-contract/event/valid/document.json +17 -0
- package/fixtures/atelier-capability-contract/journal/invalid/document.json +382 -0
- package/fixtures/atelier-capability-contract/journal/valid/document.json +381 -0
- package/fixtures/atelier-capability-contract/notice/invalid/document.json +8 -0
- package/fixtures/atelier-capability-contract/notice/valid/document.json +7 -0
- package/fixtures/atelier-capability-contract/package/invalid/document.json +59 -0
- package/fixtures/atelier-capability-contract/package/valid/document.json +58 -0
- package/fixtures/atelier-capability-contract/plan/invalid/document.json +107 -0
- package/fixtures/atelier-capability-contract/plan/valid/document.json +106 -0
- package/fixtures/atelier-capability-contract/release/invalid/document.json +85 -0
- package/fixtures/atelier-capability-contract/release/valid/document.json +84 -0
- package/fixtures/atelier-capability-contract/state/invalid/document.json +316 -0
- package/fixtures/atelier-capability-contract/state/valid/document.json +315 -0
- package/fixtures/atelier-coordination/dependency/invalid/document.json +37 -0
- package/fixtures/atelier-coordination/dependency/valid/document.json +36 -0
- package/fixtures/atelier-coordination/directive/invalid/document.json +25 -0
- package/fixtures/atelier-coordination/directive/valid/document.json +24 -0
- package/fixtures/atelier-coordination/disposition/invalid/document.json +34 -0
- package/fixtures/atelier-coordination/disposition/valid/document.json +33 -0
- package/fixtures/atelier-coordination/outcome/invalid/document.json +39 -0
- package/fixtures/atelier-coordination/outcome/valid/document.json +38 -0
- package/fixtures/atelier-coordination/recovery/invalid/document.json +34 -0
- package/fixtures/atelier-coordination/recovery/valid/document.json +33 -0
- package/fixtures/atelier-harness-contract/handoff/invalid/document.json +25 -0
- package/fixtures/atelier-harness-contract/handoff/valid/document.json +24 -0
- package/fixtures/atelier-ingestion/invalid/authority.json +19 -0
- package/fixtures/atelier-ingestion/semantic-baseline.json +26 -0
- package/fixtures/atelier-ingestion/semantic-operation.json +75 -0
- package/fixtures/atelier-ingestion/valid/plan-input.json +18 -0
- package/fixtures/atelier-inquiry-contract/assessment/invalid/document.json +41 -0
- package/fixtures/atelier-inquiry-contract/assessment/valid/document.json +40 -0
- package/fixtures/atelier-inquiry-contract/bundle/invalid/document.json +51 -0
- package/fixtures/atelier-inquiry-contract/bundle/valid/document.json +50 -0
- package/fixtures/atelier-inquiry-contract/campaign/invalid/document.json +17 -0
- package/fixtures/atelier-inquiry-contract/campaign/valid/document.json +16 -0
- package/fixtures/atelier-inquiry-contract/decision/invalid/document.json +22 -0
- package/fixtures/atelier-inquiry-contract/decision/valid/document.json +21 -0
- package/fixtures/atelier-inquiry-contract/feedback/invalid/document.json +19 -0
- package/fixtures/atelier-inquiry-contract/feedback/valid/document.json +18 -0
- package/fixtures/atelier-inquiry-contract/handoff/invalid/document.json +92 -0
- package/fixtures/atelier-inquiry-contract/handoff/valid/document.json +91 -0
- package/fixtures/atelier-inquiry-contract/hypothesis/invalid/document.json +20 -0
- package/fixtures/atelier-inquiry-contract/hypothesis/valid/document.json +19 -0
- package/fixtures/atelier-inquiry-contract/ledger/invalid/document.json +680 -0
- package/fixtures/atelier-inquiry-contract/ledger/valid/document.json +679 -0
- package/fixtures/atelier-inquiry-contract/legacy-assessment/invalid/document.json +19 -0
- package/fixtures/atelier-inquiry-contract/legacy-assessment/valid/document.json +18 -0
- package/fixtures/atelier-inquiry-contract/request/invalid/document.json +50 -0
- package/fixtures/atelier-inquiry-contract/request/valid/document.json +49 -0
- package/fixtures/atelier-inquiry-contract/source/invalid/document.json +18 -0
- package/fixtures/atelier-inquiry-contract/source/valid/document.json +17 -0
- package/fixtures/atelier-inquiry-contract/withdrawal/invalid/document.json +16 -0
- package/fixtures/atelier-inquiry-contract/withdrawal/valid/document.json +15 -0
- package/fixtures/atelier-instruction-adoption/context-receipt/invalid/document.json +18 -0
- package/fixtures/atelier-instruction-adoption/context-receipt/valid/document.json +17 -0
- package/fixtures/atelier-instruction-adoption/plan/invalid/document.json +63 -0
- package/fixtures/atelier-instruction-adoption/plan/valid/document.json +62 -0
- package/fixtures/atelier-instruction-adoption/receipt/invalid/document.json +22 -0
- package/fixtures/atelier-instruction-adoption/receipt/valid/document.json +21 -0
- package/fixtures/atelier-instruction-adoption/state/invalid/document.json +30 -0
- package/fixtures/atelier-instruction-adoption/state/valid/document.json +29 -0
- package/fixtures/atelier-interaction/act/invalid/document.json +25 -0
- package/fixtures/atelier-interaction/act/valid/document.json +24 -0
- package/fixtures/atelier-interaction/assessment/invalid/document.json +32 -0
- package/fixtures/atelier-interaction/assessment/valid/document.json +31 -0
- package/fixtures/atelier-interaction/observation/invalid/document.json +22 -0
- package/fixtures/atelier-interaction/observation/valid/document.json +21 -0
- package/fixtures/atelier-interaction/policy/invalid/document.json +20 -0
- package/fixtures/atelier-interaction/policy/valid/document.json +19 -0
- package/fixtures/atelier-judgment/case/invalid/document.json +76 -0
- package/fixtures/atelier-judgment/case/valid/document.json +75 -0
- package/fixtures/atelier-judgment/decision/invalid/document.json +38 -0
- package/fixtures/atelier-judgment/decision/valid/document.json +37 -0
- package/fixtures/atelier-judgment/native-decision.md +3 -0
- package/fixtures/atelier-judgment/practice-reconsideration/definition.json +92 -0
- package/fixtures/atelier-judgment/practice-reconsideration/scenario.json +435 -0
- package/fixtures/atelier-knowledge-contract/activation/invalid/document.json +26 -0
- package/fixtures/atelier-knowledge-contract/activation/valid/document.json +25 -0
- package/fixtures/atelier-knowledge-contract/contribution/invalid/document.json +34 -0
- package/fixtures/atelier-knowledge-contract/contribution/valid/document.json +33 -0
- package/fixtures/atelier-knowledge-contract/domain/invalid/document.json +41 -0
- package/fixtures/atelier-knowledge-contract/domain/valid/document.json +40 -0
- package/fixtures/atelier-knowledge-contract/domain-revision/invalid/document.json +46 -0
- package/fixtures/atelier-knowledge-contract/domain-revision/valid/document.json +45 -0
- package/fixtures/atelier-knowledge-contract/evaluation/invalid/document.json +21 -0
- package/fixtures/atelier-knowledge-contract/evaluation/valid/document.json +20 -0
- package/fixtures/atelier-knowledge-contract/ledger/invalid/document.json +318 -0
- package/fixtures/atelier-knowledge-contract/ledger/valid/document.json +317 -0
- package/fixtures/atelier-knowledge-contract/relation/invalid/document.json +25 -0
- package/fixtures/atelier-knowledge-contract/relation/valid/document.json +24 -0
- package/fixtures/atelier-knowledge-contract/review/invalid/document.json +18 -0
- package/fixtures/atelier-knowledge-contract/review/valid/document.json +17 -0
- package/fixtures/atelier-knowledge-contract/withdrawal/invalid/document.json +16 -0
- package/fixtures/atelier-knowledge-contract/withdrawal/valid/document.json +15 -0
- package/fixtures/atelier-learning/invalid/authority.json +14 -0
- package/fixtures/atelier-learning/valid/capture.json +13 -0
- package/fixtures/atelier-personal-workspace-contract/manifest/invalid/authority-key.json +17 -0
- package/fixtures/atelier-personal-workspace-contract/manifest/invalid/nonempty-ext.json +25 -0
- package/fixtures/atelier-personal-workspace-contract/manifest/invalid/wrong-schema.json +22 -0
- package/fixtures/atelier-personal-workspace-contract/manifest/valid/manifest.json +22 -0
- package/fixtures/atelier-personal-workspace-contract/overlay/invalid/authority-key.json +25 -0
- package/fixtures/atelier-personal-workspace-contract/overlay/invalid/unknown-top-level-field.json +27 -0
- package/fixtures/atelier-personal-workspace-contract/overlay/invalid/wrong-schema.json +24 -0
- package/fixtures/atelier-trackable/definition/invalid/document.json +15 -0
- package/fixtures/atelier-trackable/definition/valid/document.json +14 -0
- package/fixtures/atelier-trackable/request/invalid/document.json +23 -0
- package/fixtures/atelier-trackable/request/valid/document.json +22 -0
- package/fixtures/boundary-delegations/invalid/authority-key.v1.json +30 -0
- package/fixtures/boundary-delegations/invalid/push-operation.v1.json +28 -0
- package/fixtures/boundary-delegations/invalid/unsigned.v1.json +25 -0
- package/fixtures/boundary-delegations/valid/empty.v1.json +4 -0
- package/fixtures/boundary-delegations/valid/signed-operator.v1.json +30 -0
- package/fixtures/boundary-owner-keys/invalid/wrong-algorithm.v1.json +15 -0
- package/fixtures/boundary-owner-keys/invalid/wrong-curve.v1.json +15 -0
- package/fixtures/boundary-owner-keys/valid/one-owner.v1.json +17 -0
- package/fixtures/capability-packages/adoption.example.json +70 -0
- package/fixtures/capability-packages/evidence-review/capability-package.json +58 -0
- package/fixtures/capability-packages/evidence-review/capability-release.json +84 -0
- package/fixtures/capability-packages/evidence-review/evaluation.md +15 -0
- package/fixtures/capability-packages/evidence-review/skills/evidence-review/SKILL.md +22 -0
- package/fixtures/capability-packages/evidence-review/skills/evidence-review/references/ledger.md +7 -0
- package/fixtures/capability-packages/research/capability-package.json +88 -0
- package/fixtures/capability-packages/research/capability-release.json +134 -0
- package/fixtures/capability-packages/research/evaluation.md +15 -0
- package/fixtures/capability-packages/research/skills/research-integrate/SKILL.md +29 -0
- package/fixtures/capability-packages/research/skills/research-integrate/references/integration.md +10 -0
- package/fixtures/capability-packages/research/skills/research-plan/SKILL.md +27 -0
- package/fixtures/capability-packages/research/skills/research-plan/references/inquiries.md +26 -0
- package/fixtures/capability-packages/research/skills/research-synthesize/SKILL.md +23 -0
- package/fixtures/capability-packages/research/skills/research-synthesize/references/synthesis.md +12 -0
- package/fixtures/evidence-navigation/invalid/duplicate-operation.json +16 -0
- package/fixtures/evidence-navigation/invalid/instruction-kind.json +36 -0
- package/fixtures/evidence-navigation/invalid/invalid-digest.json +36 -0
- package/fixtures/evidence-navigation/invalid/invalid-time.json +36 -0
- package/fixtures/evidence-navigation/invalid/missing-attribution.json +35 -0
- package/fixtures/evidence-navigation/invalid/reserved-extension.json +19 -0
- package/fixtures/evidence-navigation/invalid/unattributed-acceptance.json +36 -0
- package/fixtures/evidence-navigation/invalid/uncoerced-version.json +16 -0
- package/fixtures/evidence-navigation/invalid/unknown-currency.json +17 -0
- package/fixtures/evidence-navigation/invalid/unknown-selector-field.json +37 -0
- package/fixtures/evidence-navigation/invalid/unsupported-operation.json +15 -0
- package/fixtures/evidence-navigation/valid/claim.json +36 -0
- package/fixtures/evidence-navigation/valid/host.json +16 -0
- package/fixtures/evidence-navigation/valid/profile.json +16 -0
- package/fixtures/evidence-navigation/valid/snapshot.json +17 -0
- package/fixtures/harness-packages/build-harness/capability-package.json +61 -0
- package/fixtures/harness-packages/build-harness/capability-release.json +82 -0
- package/fixtures/harness-packages/build-harness/evaluation.md +3 -0
- package/fixtures/harness-packages/build-harness/skills/atelier-build-harness/SKILL.md +59 -0
- package/fixtures/harness-packages/knowledge-harness/capability-package.json +61 -0
- package/fixtures/harness-packages/knowledge-harness/capability-release.json +82 -0
- package/fixtures/harness-packages/knowledge-harness/evaluation.md +3 -0
- package/fixtures/harness-packages/knowledge-harness/skills/atelier-knowledge-harness/SKILL.md +60 -0
- package/fixtures/harnesses/learning-cycle.json +780 -0
- package/fixtures/inquiry/workshop.json +675 -0
- package/fixtures/inquiry-packages/discovery-harness/capability-package.json +61 -0
- package/fixtures/inquiry-packages/discovery-harness/capability-release.json +82 -0
- package/fixtures/inquiry-packages/discovery-harness/evaluation.md +3 -0
- package/fixtures/inquiry-packages/discovery-harness/skills/atelier-discovery-harness/SKILL.md +48 -0
- package/fixtures/inquiry-packages/research-harness/capability-package.json +61 -0
- package/fixtures/inquiry-packages/research-harness/capability-release.json +82 -0
- package/fixtures/inquiry-packages/research-harness/evaluation.md +3 -0
- package/fixtures/inquiry-packages/research-harness/skills/atelier-research-harness/SKILL.md +43 -0
- package/fixtures/knowledge-health/calibration-suite.json +26 -0
- package/fixtures/knowledge-health/calibration-trial.json +37 -0
- package/fixtures/knowledge-health/corrected/checklist.md +18 -0
- package/fixtures/knowledge-health/devday/participant-record.json +19 -0
- package/fixtures/knowledge-health/expected-summary.json +27 -0
- package/fixtures/knowledge-health/workshop/README.md +20 -0
- package/fixtures/knowledge-health/workshop/atelier.project.json +7 -0
- package/fixtures/knowledge-health/workshop/knowledge-plan.json +65 -0
- package/fixtures/knowledge-health/workshop/records/checklist.md +18 -0
- package/fixtures/knowledge-health/workshop/records/workshop.md +18 -0
- package/fixtures/knowledge-health/workshop/repo-access.v1.json +5 -0
- package/fixtures/knowledge-health/workspace/atelier.project.json +18 -0
- package/fixtures/knowledge-health/workspace/knowledge-plan.json +74 -0
- package/fixtures/knowledge-health/workspace/records/checklist.md +18 -0
- package/fixtures/knowledge-health/workspace/records/pilot.md +16 -0
- package/fixtures/knowledge-health/workspace/repo-access.v1.json +9 -0
- package/fixtures/personal-workspace/overlay.json +24 -0
- package/fixtures/templates/local-library.v1.json +76 -0
- package/fixtures/templates/upgrade-contracts/adoption-policy.v2/invalid.json +16 -0
- package/fixtures/templates/upgrade-contracts/adoption-policy.v2/valid.json +13 -0
- package/fixtures/templates/upgrade-contracts/migration.v3/invalid.json +24 -0
- package/fixtures/templates/upgrade-contracts/migration.v3/valid.json +21 -0
- package/fixtures/templates/upgrade-contracts/template-adoption.v1/invalid.json +60 -0
- package/fixtures/templates/upgrade-contracts/template-adoption.v1/valid.json +57 -0
- package/fixtures/templates/upgrade-contracts/upgrade-plan.v3/invalid.json +85 -0
- package/fixtures/templates/upgrade-contracts/upgrade-plan.v3/valid.json +82 -0
- package/package.json +60 -5
- package/plugins/obsidian/main.js +57 -8
- package/plugins/obsidian/manifest.json +1 -1
- package/skills/claude/atelier-build-harness/SKILL.md +59 -0
- package/skills/claude/atelier-coordination/SKILL.md +32 -0
- package/skills/claude/atelier-discovery-harness/SKILL.md +48 -0
- package/skills/claude/atelier-guided-upgrade/SKILL.md +29 -0
- package/skills/claude/atelier-interaction/SKILL.md +34 -0
- package/skills/claude/atelier-knowledge-harness/SKILL.md +60 -0
- package/skills/claude/atelier-knowledge-setup/SKILL.md +72 -0
- package/skills/claude/atelier-practical-judgment/SKILL.md +64 -0
- package/skills/claude/atelier-research-harness/SKILL.md +43 -0
- package/skills/claude/atelier-skill-steward/SKILL.md +53 -0
- package/skills/claude/atelier-trackables/SKILL.md +40 -0
- package/skills/codex/atelier-build-harness/SKILL.md +59 -0
- package/skills/codex/atelier-coordination/SKILL.md +32 -0
- package/skills/codex/atelier-discovery-harness/SKILL.md +48 -0
- package/skills/codex/atelier-guided-upgrade/SKILL.md +29 -0
- package/skills/codex/atelier-interaction/SKILL.md +34 -0
- package/skills/codex/atelier-knowledge-harness/SKILL.md +60 -0
- package/skills/codex/atelier-knowledge-setup/SKILL.md +72 -0
- package/skills/codex/atelier-practical-judgment/SKILL.md +64 -0
- package/skills/codex/atelier-research-harness/SKILL.md +43 -0
- package/skills/codex/atelier-skill-steward/SKILL.md +53 -0
- package/skills/codex/atelier-trackables/SKILL.md +40 -0
- package/src/architecture/catalog.mjs +355 -0
- package/src/architecture/index.mjs +93 -0
- package/src/boundary/content-rules.mjs +536 -69
- package/src/boundary/delegation-command.mjs +158 -0
- package/src/boundary/delegation.mjs +396 -0
- package/src/boundary/policy.mjs +185 -64
- package/src/build/git.mjs +18 -0
- package/src/build/index.mjs +3 -0
- package/src/build/ledger.mjs +88 -0
- package/src/capabilities/adoption.mjs +381 -0
- package/src/capabilities/evidence.mjs +140 -0
- package/src/capabilities/files.mjs +124 -0
- package/src/capabilities/index.mjs +4 -0
- package/src/capabilities/instructions.mjs +250 -0
- package/src/capabilities/package.mjs +136 -0
- package/src/cli/command-failure.mjs +36 -0
- package/src/cli/execute-command.mjs +16 -2
- package/src/cli/run.mjs +66 -9
- package/src/coauthor/store.mjs +43 -2
- package/src/collaboration/proposals.mjs +189 -0
- package/src/commands/architecture.mjs +27 -0
- package/src/commands/boundary.mjs +3 -0
- package/src/commands/capability.mjs +73 -0
- package/src/commands/enroll.mjs +3 -0
- package/src/commands/harness.mjs +79 -0
- package/src/commands/ingest.mjs +39 -0
- package/src/commands/init.mjs +4 -1
- package/src/commands/inquiry.mjs +66 -0
- package/src/commands/knowledge-semantic.mjs +128 -0
- package/src/commands/knowledge.mjs +119 -0
- package/src/commands/learn.mjs +61 -0
- package/src/commands/obsidian.mjs +772 -65
- package/src/commands/practice.mjs +24 -0
- package/src/commands/setup.mjs +22 -2
- package/src/commands/trackable.mjs +19 -0
- package/src/contracts/corpus.mjs +30 -0
- package/src/coordination/index.mjs +110 -0
- package/src/evidence-navigation/contracts.mjs +60 -0
- package/src/evidence-navigation/evaluate.mjs +69 -0
- package/src/evidence-navigation/generation.mjs +22 -0
- package/src/evidence-navigation/index.mjs +2 -0
- package/src/evidence-navigation/local.mjs +167 -0
- package/src/evidence-navigation/schema.generated.mjs +604 -0
- package/src/evidence-navigation/types.mjs +12 -0
- package/src/export/atelier-export-contract.mjs +35 -8
- package/src/graph/enroll.mjs +248 -0
- package/src/graph/graph.mjs +30 -6
- package/src/graph/knowledge-graph.mjs +77 -9
- package/src/harnesses/contracts.mjs +41 -0
- package/src/harnesses/exchange.mjs +154 -0
- package/src/harnesses/feedback.mjs +16 -0
- package/src/harnesses/history.mjs +37 -0
- package/src/harnesses/index.mjs +4 -0
- package/src/harnesses/store.mjs +29 -0
- package/src/ingestion/contracts.mjs +41 -0
- package/src/ingestion/evaluation.mjs +119 -0
- package/src/ingestion/optional/composition/index.mjs +171 -0
- package/src/ingestion/optional/evaluation/index.mjs +219 -0
- package/src/ingestion/optional/graphify/composition-binding.mjs +60 -0
- package/src/ingestion/optional/graphify/composition_receiving.py +185 -0
- package/src/ingestion/optional/graphify/graphify_supplier.py +938 -0
- package/src/ingestion/optional/graphify/json_bridge.py +163 -0
- package/src/ingestion/optional/graphify/native-closure-pins.json +275 -0
- package/src/ingestion/optional/graphify/native-pins.json +306 -0
- package/src/ingestion/optional/graphify/query_receiving.py +209 -0
- package/src/ingestion/optional/index.mjs +9 -0
- package/src/ingestion/optional/jev/bridge.mjs +166 -0
- package/src/ingestion/optional/jev/composition-binding.mjs +116 -0
- package/src/ingestion/optional/jev/judgments.mjs +174 -0
- package/src/ingestion/optional/jev/native-translation.mjs +32 -0
- package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/__init__.py +7 -0
- package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/adapter.py +966 -0
- package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/artifacts.py +101 -0
- package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/projection.py +146 -0
- package/src/ingestion/optional/lightrag/composition-binding.mjs +67 -0
- package/src/ingestion/optional/vanilla/composition-binding.mjs +102 -0
- package/src/ingestion/optional/vanilla/index.mjs +218 -0
- package/src/ingestion/optional/vanilla/native-normalization.mjs +187 -0
- package/src/ingestion/processors.mjs +218 -0
- package/src/ingestion/semantic.mjs +203 -0
- package/src/ingestion/store.mjs +372 -0
- package/src/inquiry/beliefs.mjs +32 -0
- package/src/inquiry/contracts.mjs +23 -0
- package/src/inquiry/index.mjs +5 -0
- package/src/inquiry/ledger.mjs +135 -0
- package/src/inquiry/lenses.mjs +15 -0
- package/src/inquiry/projection.mjs +77 -0
- package/src/inquiry/store.mjs +35 -0
- package/src/intake/read-scope.mjs +12 -0
- package/src/intake/store.mjs +69 -6
- package/src/interaction/index.mjs +91 -0
- package/src/judgment/index.mjs +82 -0
- package/src/judgment/practice-evaluation.mjs +70 -0
- package/src/judgment/practice.mjs +144 -0
- package/src/knowledge/context.mjs +217 -0
- package/src/knowledge/index.mjs +5 -0
- package/src/knowledge/ingestion.mjs +79 -0
- package/src/knowledge/intake.mjs +33 -0
- package/src/knowledge/ledger.mjs +110 -0
- package/src/knowledge/participatory/guidance.mjs +36 -0
- package/src/knowledge/participatory/guided-contribution.mjs +44 -0
- package/src/knowledge/participatory/index.mjs +2 -0
- package/src/knowledge/participatory/installed-workshop.mjs +108 -0
- package/src/knowledge/plan.mjs +67 -0
- package/src/knowledge/projection.mjs +43 -0
- package/src/knowledge/semantic-operation.mjs +567 -0
- package/src/knowledge/sessions.mjs +467 -0
- package/src/knowledge/workspace.mjs +295 -0
- package/src/knowledge-health/assessment.mjs +165 -0
- package/src/knowledge-health/capabilities.mjs +350 -0
- package/src/knowledge-health/context.mjs +62 -0
- package/src/knowledge-health/example.mjs +47 -0
- package/src/knowledge-health/index.mjs +10 -0
- package/src/knowledge-health/owner-join.mjs +88 -0
- package/src/knowledge-health/proof/common.mjs +24 -0
- package/src/knowledge-health/proof/installed-check.mjs +100 -0
- package/src/knowledge-health/proof/native-readback.mjs +37 -0
- package/src/knowledge-health/proof/rehearse.mjs +68 -0
- package/src/knowledge-health/proof/validate-participant.mjs +17 -0
- package/src/knowledge-health/proof/verify-native-capture.mjs +14 -0
- package/src/knowledge-health/proof/workshop-check.mjs +36 -0
- package/src/knowledge-health/rule-identity.mjs +49 -0
- package/src/knowledge-health/workshop-composition.mjs +19 -0
- package/src/knowledge-health/workshop-profile.mjs +176 -0
- package/src/knowledge-health/workshop.mjs +99 -0
- package/src/learning/adapters.mjs +68 -0
- package/src/learning/assistance.mjs +127 -0
- package/src/learning/contracts.mjs +65 -0
- package/src/learning/store.mjs +272 -0
- package/src/personal-workspace/index.mjs +391 -0
- package/src/personal-workspace/selection.mjs +401 -0
- package/src/project/config.mjs +8 -0
- package/src/project/durable-state.mjs +3 -1
- package/src/project/file-class.mjs +1 -1
- package/src/project/path-match.mjs +2 -1
- package/src/projection/obsidian/edits/apply.mjs +55 -16
- package/src/projection/obsidian/materialize/prepare-view.mjs +87 -5
- package/src/projection/obsidian/personal-workspace.mjs +519 -0
- package/src/projection/obsidian/proposals/adapter.mjs +79 -17
- package/src/projection/obsidian/proposals/observation.mjs +50 -4
- package/src/projection/obsidian/publication/publisher.mjs +214 -18
- package/src/projection/obsidian/publication/vault-list.mjs +4 -3
- package/src/projection/obsidian/recovery/index.mjs +4 -1
- package/src/projection/obsidian/recovery/store.mjs +160 -7
- package/src/projection/obsidian/selection-ui/contribution.mjs +6 -5
- package/src/projection/project.mjs +3 -2
- package/src/readiness/readiness.mjs +10 -3
- package/src/reflection/index.mjs +56 -0
- package/src/runtime/obsidian/app-production-seams.mjs +48 -16
- package/src/runtime/obsidian/app-registration.mjs +175 -28
- package/src/runtime/obsidian/app-restart.mjs +154 -0
- package/src/runtime/obsidian/engine.mjs +437 -56
- package/src/runtime/obsidian/extension-points.mjs +1 -1
- package/src/runtime/obsidian/index.mjs +10 -7
- package/src/runtime/obsidian/launch-plan.mjs +7 -4
- package/src/runtime/obsidian/lifecycle.mjs +108 -23
- package/src/runtime/obsidian/login-item.mjs +335 -0
- package/src/runtime/obsidian/machine-settings.mjs +119 -12
- package/src/runtime/obsidian/observation.mjs +36 -4
- package/src/runtime/obsidian/opening.mjs +173 -38
- package/src/runtime/obsidian/personal-selection.mjs +125 -0
- package/src/runtime/obsidian/pipeline.mjs +115 -6
- package/src/runtime/obsidian/plugin-channel.mjs +11 -3
- package/src/runtime/obsidian/plugin-choice.mjs +16 -4
- package/src/runtime/obsidian/plugin-drift.mjs +59 -11
- package/src/runtime/obsidian/plugin-presence.mjs +14 -1
- package/src/runtime/obsidian/private-lock.mjs +39 -4
- package/src/runtime/obsidian/process-identity.mjs +20 -10
- package/src/runtime/obsidian/project-views.mjs +282 -0
- package/src/runtime/obsidian/questions.mjs +48 -0
- package/src/runtime/obsidian/release-watch.mjs +56 -0
- package/src/runtime/obsidian/service-main.mjs +148 -19
- package/src/runtime/obsidian/service-manager-production.mjs +61 -0
- package/src/runtime/obsidian/service-managers.mjs +275 -0
- package/src/runtime/obsidian/service-record.mjs +87 -23
- package/src/runtime/obsidian/service.mjs +102 -9
- package/src/runtime/obsidian/startup-adapters.mjs +59 -21
- package/src/runtime/obsidian/unheld-evidence.mjs +84 -0
- package/src/runtime/obsidian/vault-location.mjs +300 -0
- package/src/runtime/obsidian/view-counts.mjs +34 -0
- package/src/runtime/obsidian/view-permission.mjs +334 -0
- package/src/server/local-sidecar.mjs +169 -2
- package/src/server/server.mjs +28 -2
- package/src/template-bindings/project.mjs +92 -0
- package/src/templates/profile.mjs +225 -0
- package/src/trackables/domain.mjs +175 -0
- package/src/trackables/store.mjs +114 -0
- package/src/ui/control-system.mjs +201 -0
- package/src/ui/html-primitives.mjs +3 -0
- package/src/ui/knowledge-page.mjs +1388 -0
- package/src/upgrade/closure-diagnostics.mjs +135 -0
- package/src/upgrade/closure.mjs +649 -0
- package/src/upgrade/explanation.mjs +1 -1
- package/src/upgrade/template-participant.mjs +171 -0
- package/src/upgrade/transaction.mjs +72 -25
- package/src/upgrade/upgrade.mjs +20 -7
- package/templates/external-project-workspace/pack-lifecycle.json +7 -1
- package/templates/knowledge-workspace/.gitattributes +2 -0
- package/templates/knowledge-workspace/AGENTS.md +27 -0
- package/templates/knowledge-workspace/README.md +44 -0
- package/templates/knowledge-workspace/atelier.project.json +22 -0
- package/templates/knowledge-workspace/gitignore +6 -0
- package/templates/knowledge-workspace/knowledge-plan.json +111 -0
- package/templates/knowledge-workspace/knowledge-plan.schema.json +281 -0
- package/templates/knowledge-workspace/records/forecast.md +22 -0
- package/templates/knowledge-workspace/records/inspection.md +25 -0
- package/templates/knowledge-workspace/records/telescope.md +24 -0
- package/templates/knowledge-workspace/repo-access.v1.json +9 -0
|
@@ -7,7 +7,8 @@ import { atomicReplacePrivateText, ensureContainedPrivateDirectory, openRegularF
|
|
|
7
7
|
|
|
8
8
|
// Private per-workspace state for one Obsidian view:
|
|
9
9
|
//
|
|
10
|
-
// <workspaceRoot>/vaults/<scopeId>/ the editable vault
|
|
10
|
+
// <workspaceRoot>/vaults/<scopeId>/ the editable vault, unless it was allocated elsewhere or a vaultRoot is named
|
|
11
|
+
// <workspaceRoot>/state/allocations/<scopeId>.json where the view's vault was allocated, when it was (see below)
|
|
11
12
|
// <workspaceRoot>/state/manifests/<scopeId>/ trusted manifests and the current pointer
|
|
12
13
|
// <workspaceRoot>/state/journals/<scopeId>/<id>/ publication journals
|
|
13
14
|
// <workspaceRoot>/recovery/objects/<sha256>.bin immutable content-addressed bytes
|
|
@@ -49,7 +50,15 @@ export const VAULT_LOCK_DIRECTORY = '.atelier-publication'
|
|
|
49
50
|
// marker before its ticket, so the directory does not grow inside a person's
|
|
50
51
|
// vault and an interrupted removal never leaves a marker without its ticket.
|
|
51
52
|
export function acquireVaultLock(store) {
|
|
53
|
+
// Only in the vault the store was made for, reached through no link: the lock's folder is made inside whatever the
|
|
54
|
+
// vault root leads to.
|
|
55
|
+
let real = null
|
|
56
|
+
try { real = realPathAsStored(store.vaultRoot) } catch { /* not reachable: refused below */ }
|
|
57
|
+
if (real !== store.vaultRoot) refuse(store.allocation ? 'vault-allocation-moved' : 'vault-root-moved', 'the vault is no longer the folder this publication was prepared for: its path now leads somewhere else; nothing was written', { path: store.vaultRoot, leadsTo: real })
|
|
52
58
|
const directory = ensureContainedPrivateDirectory({ workspaceRoot: store.vaultRoot, directory: path.dirname(store.vaultLockPath), label: 'vault publication lock' })
|
|
59
|
+
// The lock's folder resolved, again, to the one inside the vault: a link put there after the check above is refused
|
|
60
|
+
// before any ticket is written (only the empty folder may have been made through it).
|
|
61
|
+
if (directory !== path.dirname(store.vaultLockPath)) refuse(store.allocation ? 'vault-allocation-moved' : 'vault-root-moved', 'the vault is no longer the folder this publication was prepared for: its path now leads somewhere else; no lock was taken', { path: store.vaultRoot, leadsTo: path.dirname(directory) })
|
|
53
62
|
const release = acquirePrivateLock(path.join(directory, path.basename(store.vaultLockPath)))
|
|
54
63
|
try {
|
|
55
64
|
const owners = `${store.vaultLockPath}.owners`
|
|
@@ -111,11 +120,135 @@ function publishOnce(file, bytes) {
|
|
|
111
120
|
return true
|
|
112
121
|
}
|
|
113
122
|
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
// Where a view's vault is
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
|
|
127
|
+
// A view of a workspace that decided where its vaults live gets a folder
|
|
128
|
+
// there, allocated once at its first publication (by the maintenance engine)
|
|
129
|
+
// and recorded, owner only, in the workspace's private state:
|
|
130
|
+
//
|
|
131
|
+
// <workspaceRoot>/state/allocations/<scopeId>.json atelier-obsidian-vault-allocation/v1
|
|
132
|
+
//
|
|
133
|
+
// A view without a record has its vault where every earlier release put it,
|
|
134
|
+
// `<workspaceRoot>/vaults/<scopeId>`: a vault published there stays there
|
|
135
|
+
// until it is moved, and a workspace that decided no place publishes there as
|
|
136
|
+
// before. The record is the one thing that names another folder, and the
|
|
137
|
+
// store reads it, so every reader of a view's vault (the engine, source apply,
|
|
138
|
+
// the proposal adapter, `open`) finds the same one.
|
|
139
|
+
|
|
140
|
+
export const VAULT_ALLOCATION_SCHEMA = 'atelier-obsidian-vault-allocation/v1'
|
|
141
|
+
export const VAULT_ORIGINS = Object.freeze(['allocated', 'legacy-data-root'])
|
|
142
|
+
|
|
143
|
+
const TIMESTAMP = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?Z$/
|
|
144
|
+
const CONTROL = /[\u0000-\u001f\u007f]/
|
|
145
|
+
// `device` and `inode` name the folder that was made, as decimal strings (a 64-bit number does not fit a JSON number):
|
|
146
|
+
// a folder at the recorded path that is another one (restored, moved in, made again by someone else) is never
|
|
147
|
+
// published into.
|
|
148
|
+
const ALLOCATION_KEYS = Object.freeze(['schema', 'workspaceId', 'scopeId', 'path', 'name', 'parent', 'device', 'inode', 'allocatedAt'])
|
|
149
|
+
const DECIMAL = /^(0|[1-9][0-9]{0,19})$/
|
|
150
|
+
const isPlainPath = (value) => typeof value === 'string' && value.length <= 4096 && !CONTROL.test(value) && path.isAbsolute(value) && path.resolve(value) === value
|
|
151
|
+
|
|
152
|
+
export const allocationFile = (workspaceRoot, scopeId) => path.join(workspaceRoot, 'state', 'allocations', `${segment(scopeId)}.json`)
|
|
153
|
+
export const legacyVaultRoot = (workspaceRoot, scopeId) => path.join(workspaceRoot, 'vaults', segment(scopeId))
|
|
154
|
+
|
|
155
|
+
export function validateVaultAllocation(document, { workspaceId, scopeId }) {
|
|
156
|
+
const code = 'invalid-vault-allocation'
|
|
157
|
+
if (document === null || typeof document !== 'object' || Array.isArray(document)) refuse(code, 'a vault allocation is an object')
|
|
158
|
+
const unknown = Object.keys(document).filter((key) => !ALLOCATION_KEYS.includes(key))
|
|
159
|
+
if (unknown.length > 0 || ALLOCATION_KEYS.some((key) => !Object.hasOwn(document, key))) refuse(code, 'a vault allocation carries exactly its own members', { unknown: unknown.sort() })
|
|
160
|
+
if (document.schema !== VAULT_ALLOCATION_SCHEMA) refuse(code, 'a vault allocation names an unknown schema')
|
|
161
|
+
if (document.workspaceId !== workspaceId || document.scopeId !== scopeId || !IDENTIFIER.test(String(scopeId))) refuse(code, 'the vault allocation belongs to another workspace or view')
|
|
162
|
+
if (!isPlainPath(document.path) || !isPlainPath(document.parent)) refuse(code, 'the vault allocation names a folder that is not an absolute, plainly written path')
|
|
163
|
+
if (path.dirname(document.path) !== document.parent || path.basename(document.path) !== document.name) refuse(code, 'the vault allocation names its folder inconsistently')
|
|
164
|
+
if (typeof document.device !== 'string' || !DECIMAL.test(document.device) || typeof document.inode !== 'string' || !DECIMAL.test(document.inode)) refuse(code, 'the vault allocation names its folder\'s device and inode as decimal strings')
|
|
165
|
+
if (typeof document.allocatedAt !== 'string' || !TIMESTAMP.test(document.allocatedAt)) refuse(code, 'the vault allocation needs a UTC time')
|
|
166
|
+
return document
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// The record of a view, validated, or null when it has none. Reads only.
|
|
170
|
+
export function readVaultAllocation({ workspaceRoot, workspaceId, scopeId }) {
|
|
171
|
+
let text
|
|
172
|
+
try { text = readRegularTextNoFollow(allocationFile(workspaceRoot, scopeId)) } catch (error) {
|
|
173
|
+
if (error.code === 'ENOENT') return null
|
|
174
|
+
return refuse('invalid-vault-allocation', 'the vault allocation cannot be read', { cause: error.code ?? String(error.message) })
|
|
175
|
+
}
|
|
176
|
+
let document
|
|
177
|
+
try { document = JSON.parse(text) } catch { return refuse('invalid-vault-allocation', 'the vault allocation is not JSON') }
|
|
178
|
+
return validateVaultAllocation(document, { workspaceId, scopeId })
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// The device and inode of a folder, as an allocation records them; null when nothing is there.
|
|
182
|
+
export function folderIdentity(folder) {
|
|
183
|
+
const stat = fs.lstatSync(folder, { bigint: true, throwIfNoEntry: false })
|
|
184
|
+
return stat === undefined ? null : { device: String(stat.dev), inode: String(stat.ino), directory: stat.isDirectory(), link: stat.isSymbolicLink() }
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Whether the folder at an allocation's path is the one it recorded: 'same', 'missing', or 'replaced'.
|
|
188
|
+
export function allocatedFolderState(allocation) {
|
|
189
|
+
const found = folderIdentity(allocation.path)
|
|
190
|
+
if (found === null) return 'missing'
|
|
191
|
+
return found.directory && !found.link && found.device === allocation.device && found.inode === allocation.inode ? 'same' : 'replaced'
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Refuses, typed, unless the folder at an allocation's path is the one it recorded, at that real path: checked when a store is made, by
|
|
195
|
+
// the maintenance engine before it uses a store again, and by the publisher before and after it takes the vault lock,
|
|
196
|
+
// so a folder replaced while a service runs is never published into.
|
|
197
|
+
export function assertAllocatedFolder(allocation) {
|
|
198
|
+
const found = allocatedFolderState(allocation)
|
|
199
|
+
if (found === 'missing') refuse('vault-allocation-missing', 'the folder allocated to this view is gone; the maintenance service makes it again at its next tick', { path: allocation.path })
|
|
200
|
+
if (found === 'replaced') refuse('vault-allocation-replaced', 'another folder is where this view\'s vault was allocated; Atelier publishes only into the folder it made', { path: allocation.path })
|
|
201
|
+
// Reached through no link: the record holds the folder's real path, so the same folder moved elsewhere (into a
|
|
202
|
+
// vault the app lists, say) and linked back, which keeps its device and inode, is not published into either.
|
|
203
|
+
let real = null
|
|
204
|
+
try { real = realPathAsStored(allocation.path) } catch { /* not reachable: refused below */ }
|
|
205
|
+
if (real !== allocation.path) refuse('vault-allocation-moved', 'this view\'s vault is now reached through a link, somewhere else than where it was allocated; Atelier publishes only into the folder at the path it recorded', { path: allocation.path, leadsTo: real })
|
|
206
|
+
return allocation
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Written only by the maintenance engine, under its lock, and by what moves a vault under the same lock.
|
|
210
|
+
export function writeVaultAllocation({ workspaceRoot, allocation }) {
|
|
211
|
+
validateVaultAllocation(allocation, allocation)
|
|
212
|
+
const directory = ensureContainedPrivateDirectory({ workspaceRoot, directory: path.dirname(allocationFile(workspaceRoot, allocation.scopeId)), label: 'Obsidian vault allocations' })
|
|
213
|
+
atomicReplacePrivateText(path.join(directory, path.basename(allocationFile(workspaceRoot, allocation.scopeId))), `${JSON.stringify(allocation, null, 2)}\n`)
|
|
214
|
+
return allocation
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// A view without a record that was published, and has no vault under the data root, where a view published there
|
|
218
|
+
// always has its folder: its record was lost (removed, or a backup restored without it), or, for a view that was never
|
|
219
|
+
// allocated a folder, its folder under the data root was removed. Refused, typed, rather than published again into a
|
|
220
|
+
// new, empty vault under the data root while a vault it had elsewhere stays orphaned.
|
|
221
|
+
function refuseLostAllocation({ workspaceRoot, scopeId }) {
|
|
222
|
+
const legacy = legacyVaultRoot(workspaceRoot, scopeId)
|
|
223
|
+
if (!hasCommittedGeneration({ workspaceRoot, scopeId }) || fs.lstatSync(legacy, { throwIfNoEntry: false }) !== undefined) return
|
|
224
|
+
refuse('vault-allocation-lost', `this view was published, and its vault is not where Atelier can find it: there is no record of a folder allocated for it, and no folder under the data root. If its vault was allocated elsewhere, restore ${allocationFile(workspaceRoot, scopeId)} from a backup; otherwise, or to publish the view under the data root from now on, make the folder ${legacy}: the view is published there at the next change at its sources`, { record: allocationFile(workspaceRoot, scopeId), path: legacy })
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Where a view's vault is, without creating anything: { path, origin, allocation }.
|
|
228
|
+
export function vaultRootFor({ workspaceRoot, workspaceId, scopeId }) {
|
|
229
|
+
const allocation = readVaultAllocation({ workspaceRoot, workspaceId, scopeId })
|
|
230
|
+
if (allocation === null) refuseLostAllocation({ workspaceRoot, scopeId })
|
|
231
|
+
return allocation === null
|
|
232
|
+
? { path: legacyVaultRoot(workspaceRoot, scopeId), origin: 'legacy-data-root', allocation: null }
|
|
233
|
+
: { path: allocation.path, origin: 'allocated', allocation }
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Whether a view has a committed generation, whichever folder it was published into.
|
|
237
|
+
export const hasCommittedGeneration = ({ workspaceRoot, scopeId }) => fs.existsSync(path.join(workspaceRoot, 'state', 'manifests', segment(scopeId), 'current.json'))
|
|
238
|
+
|
|
114
239
|
// `repositoryRoots` is required: every enrolled repository root of the loaded
|
|
115
240
|
// project. Neither the workspace state nor the vault may overlap one of them,
|
|
116
241
|
// in either direction, lexically or by real path; the store refuses before it
|
|
117
242
|
// creates anything. A caller with no enrolled repository (a test in a
|
|
118
243
|
// temporary directory) says so with an explicit empty list.
|
|
244
|
+
//
|
|
245
|
+
// Without `vaultRoot` the vault is where the view's allocation says, else in
|
|
246
|
+
// the workspace's `vaults/` (vaultRootFor). An allocated folder must be the
|
|
247
|
+
// very folder the record names (device and inode): one that has gone is made
|
|
248
|
+
// again by the maintenance engine under its lock (ensureVaultAllocation), and
|
|
249
|
+
// until then, like one that another folder replaced, the store refuses. A
|
|
250
|
+
// published view whose record was lost is refused too, never published again
|
|
251
|
+
// into a new vault under the data root.
|
|
119
252
|
export function createRecoveryStore({ workspaceRoot, workspaceId, scopeId, vaultRoot, repositoryRoots } = {}) {
|
|
120
253
|
if (typeof workspaceRoot !== 'string' || !path.isAbsolute(workspaceRoot)) throw new TypeError('workspaceRoot must be an absolute path')
|
|
121
254
|
for (const [label, value] of [['workspaceId', workspaceId], ['scopeId', scopeId]]) {
|
|
@@ -125,20 +258,28 @@ export function createRecoveryStore({ workspaceRoot, workspaceId, scopeId, vault
|
|
|
125
258
|
throw new TypeError('repositoryRoots must list the absolute root of every enrolled repository; pass an explicit empty list when there is none')
|
|
126
259
|
}
|
|
127
260
|
if (vaultRoot !== undefined && (typeof vaultRoot !== 'string' || !path.isAbsolute(vaultRoot))) throw new TypeError('vaultRoot must be an absolute path')
|
|
128
|
-
const
|
|
261
|
+
const allocation = vaultRoot === undefined ? readVaultAllocation({ workspaceRoot, workspaceId, scopeId }) : null
|
|
262
|
+
const namedVault = vaultRoot ?? allocation?.path
|
|
263
|
+
// A folder that is there but is not the one allocated (a link put where it was, say) is refused as such first, with
|
|
264
|
+
// its own next step; the checks below only read. One that has gone is checked against the repositories first.
|
|
265
|
+
if (allocation !== null && allocatedFolderState(allocation) !== 'missing') assertAllocatedFolder(allocation)
|
|
266
|
+
const guard = checkManagedRoots({ managedRoots: [workspaceRoot, ...(namedVault === undefined ? [] : [namedVault])], repositoryRoots })
|
|
129
267
|
if (!guard.ok) refuse(guard.refusals[0].code, guard.refusals[0].message, { refusals: guard.refusals })
|
|
268
|
+
if (allocation !== null) assertAllocatedFolder(allocation)
|
|
269
|
+
else if (vaultRoot === undefined) refuseLostAllocation({ workspaceRoot, scopeId })
|
|
130
270
|
fs.mkdirSync(workspaceRoot, { recursive: true, mode: 0o700 })
|
|
131
271
|
// Both as the file system stores them: the vault root is what the app is told, and what it compares its own
|
|
132
272
|
// working-directory spelling with.
|
|
133
273
|
const root = realPathAsStored(workspaceRoot)
|
|
134
274
|
const privateDir = (...parts) => ensureContainedPrivateDirectory({ workspaceRoot: root, directory: path.join(root, ...parts), label: 'Obsidian publication state' })
|
|
135
|
-
const requestedVault =
|
|
136
|
-
// A vault this store places itself
|
|
275
|
+
const requestedVault = namedVault ?? legacyVaultRoot(root, scopeId)
|
|
276
|
+
// A vault this store places itself, under the data root or where the view's vault was allocated, is private to this
|
|
277
|
+
// user from the start: it will hold the plugin's bearer.
|
|
137
278
|
fs.mkdirSync(requestedVault, { recursive: true, ...(vaultRoot === undefined ? { mode: 0o700 } : {}) })
|
|
138
279
|
const vault = realPathAsStored(requestedVault)
|
|
139
280
|
const inside = (parent, child) => { const relative = path.relative(parent, child); return relative === '' || (!relative.startsWith('..') && !path.isAbsolute(relative)) }
|
|
140
281
|
for (const area of ['state', 'recovery', 'staging']) {
|
|
141
|
-
if (inside(path.join(root, area), vault) || inside(vault, path.join(root, area)))
|
|
282
|
+
if (inside(path.join(root, area), vault) || inside(vault, path.join(root, area))) refuse('vault-overlaps-private-state', 'the vault may not overlap private publication state')
|
|
142
283
|
}
|
|
143
284
|
const manifests = privateDir('state', 'manifests', segment(scopeId))
|
|
144
285
|
const journals = privateDir('state', 'journals', segment(scopeId))
|
|
@@ -152,10 +293,22 @@ export function createRecoveryStore({ workspaceRoot, workspaceId, scopeId, vault
|
|
|
152
293
|
workspaceId,
|
|
153
294
|
scopeId,
|
|
154
295
|
vaultRoot: vault,
|
|
155
|
-
// Placed by this store under the workspace state
|
|
156
|
-
// that is a link leads to a folder someone else chose, which is only looked
|
|
296
|
+
// Placed by this store under the workspace state or where the view's vault was allocated, rather than named by the
|
|
297
|
+
// caller, and really there: a vault path that is a link leads to a folder someone else chose, which is only looked
|
|
298
|
+
// at and never receives the bearer.
|
|
157
299
|
managedVaultRoot: vaultRoot === undefined && vault === requestedVault,
|
|
158
300
|
linkedVaultRoot: vaultRoot === undefined && vault !== requestedVault,
|
|
301
|
+
// Where the vault is and why: named by the caller, allocated for the view, or under the data root.
|
|
302
|
+
vaultOrigin: vaultRoot !== undefined ? 'explicit' : allocation === null ? 'legacy-data-root' : 'allocated',
|
|
303
|
+
allocation,
|
|
304
|
+
// Refuses, typed, once the folder of an allocated vault is no longer the one this store was made for (see
|
|
305
|
+
// assertAllocatedFolder), and once any other vault root is gone or leads elsewhere (vault-root-moved).
|
|
306
|
+
checkAllocatedVault() {
|
|
307
|
+
if (allocation !== null) { assertAllocatedFolder(allocation); return }
|
|
308
|
+
let real = null
|
|
309
|
+
try { real = realPathAsStored(vault) } catch { /* gone: refused below */ }
|
|
310
|
+
if (real !== vault) refuse('vault-root-moved', 'this view\'s vault is gone, or its path now leads somewhere else; nothing more is written into it', { path: vault, leadsTo: real })
|
|
311
|
+
},
|
|
159
312
|
journalsRoot: journals,
|
|
160
313
|
lockPath: path.join(locks, `${segment(scopeId)}.lock`),
|
|
161
314
|
// One publisher per vault, whichever view or workspace state it belongs to.
|
|
@@ -4,7 +4,7 @@ import { isoTime } from '../../../runtime/obsidian/documents.mjs'
|
|
|
4
4
|
import { refuse } from '../../../runtime/obsidian/errors.mjs'
|
|
5
5
|
import { protectedRoots, readMachineSettings } from '../../../runtime/obsidian/machine-settings.mjs'
|
|
6
6
|
import { resolveScope } from '../../../runtime/obsidian/opening.mjs'
|
|
7
|
-
import {
|
|
7
|
+
import { createProductionSeams, eligibilityFor } from '../../../runtime/obsidian/pipeline.mjs'
|
|
8
8
|
import { createMaintenanceStateStore } from '../../../runtime/obsidian/state-store.mjs'
|
|
9
9
|
import { readConflictView } from './conflict-view.mjs'
|
|
10
10
|
import { runPolicySetup } from './policy-setup.mjs'
|
|
@@ -28,8 +28,9 @@ const EXIT = Object.freeze({ ok: 0, notSuccess: 3 })
|
|
|
28
28
|
const MAX_REQUEST_BYTES = 64 * 1024
|
|
29
29
|
|
|
30
30
|
// The canonical snapshot selectScope reads, built exactly as the engine
|
|
31
|
-
// builds it: the canonical graph with
|
|
32
|
-
// declared relations whose both endpoints the
|
|
31
|
+
// builds it: the canonical graph with the eligibility the machine settings
|
|
32
|
+
// decide (fail closed), and the declared relations whose both endpoints the
|
|
33
|
+
// census knows.
|
|
33
34
|
function snapshotNow({ project, seams, eligibility }) {
|
|
34
35
|
const graph = seams.buildGraph({ project, eligibility })
|
|
35
36
|
const known = new Set(graph.nodes.map((node) => node.id))
|
|
@@ -49,7 +50,7 @@ function readRequest(value) {
|
|
|
49
50
|
}
|
|
50
51
|
}
|
|
51
52
|
|
|
52
|
-
export function createSelectionOperation({ seams = createProductionSeams(), eligibility =
|
|
53
|
+
export function createSelectionOperation({ seams = createProductionSeams(), eligibility: fixedEligibility = null } = {}) {
|
|
53
54
|
return {
|
|
54
55
|
name: 'selection',
|
|
55
56
|
summary: 'resolve ID | persist ID [allow-empty] | show ID | list The exact selected set of a declared view, its note paths and, for a focus, the graph query; persisted in Atelier state.',
|
|
@@ -74,7 +75,7 @@ export function createSelectionOperation({ seams = createProductionSeams(), elig
|
|
|
74
75
|
const usable = workspace !== null && workspace.workspaceRoot !== null
|
|
75
76
|
const machine = usable ? readMachineSettings(workspace) : null
|
|
76
77
|
const profile = seams.profileFor({ project, workspaceId: workspace?.workspaceId ?? 'ws-unprepared', audienceAllow: machine?.audienceAllow ?? [] })
|
|
77
|
-
const canonicalSnapshot = snapshotNow({ project, seams, eligibility })
|
|
78
|
+
const canonicalSnapshot = snapshotNow({ project, seams, eligibility: fixedEligibility ?? eligibilityFor({ machine, project }) })
|
|
78
79
|
const pathRegistry = usable ? createMaintenanceStateStore(workspace).readPathRegistry() : null
|
|
79
80
|
// Explicit empty is an honest answer of `resolve`; `persist` of an empty selection is refused and asks for it by name.
|
|
80
81
|
const selection = resolveSelection({ canonicalSnapshot, profile, scope, pathRegistry, allowEmpty: sub === 'resolve' || option === 'allow-empty' })
|
|
@@ -3,6 +3,7 @@ import { isDeepStrictEqual } from 'node:util'
|
|
|
3
3
|
import path from 'node:path'
|
|
4
4
|
import { commandProject, readJson, writeJson } from '../project/config.mjs'
|
|
5
5
|
import { summarizeReadinessJourney } from '../readiness-protocols/runtime.mjs'
|
|
6
|
+
import { atelierControlStyles } from '../ui/control-system.mjs'
|
|
6
7
|
|
|
7
8
|
const esc = (value) => String(value ?? '').replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"')
|
|
8
9
|
|
|
@@ -53,7 +54,7 @@ export function buildProjectProjection(project) {
|
|
|
53
54
|
<h3>${esc(item.title)}</h3>
|
|
54
55
|
<p><strong>${esc(item.status)}</strong> · ${esc(item.score)} / 100 answers complete</p>
|
|
55
56
|
<p>${esc(item.blockers[0] || 'review-ready')}</p>
|
|
56
|
-
<button type="button" data-copy="${esc(item.agentPrompt)}">Copy protocol prompt</button>
|
|
57
|
+
<button type="button" class="system-control" data-variant="secondary" data-size="compact" data-copy="${esc(item.agentPrompt)}">Copy protocol prompt</button>
|
|
57
58
|
</article>`).join('\n')
|
|
58
59
|
const cards = graph.nodes.map((node) => `<article class="card" data-node="${esc(node.id)}">
|
|
59
60
|
<p class="eyebrow">${esc(node.repo)} · ${esc(node.type)} · ${esc(node.audience)}</p>
|
|
@@ -80,9 +81,9 @@ h1{font-size:clamp(2rem,4vw,4rem);margin:.2em 0}
|
|
|
80
81
|
.readiness-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:12px;margin-top:16px}
|
|
81
82
|
.readiness-card{border:1px solid #342b26;border-radius:8px;padding:14px;background:#171312}
|
|
82
83
|
.readiness-card h3{font-size:1.05rem;margin:.2rem 0}
|
|
83
|
-
.readiness-card button{min-height:44px;border:1px solid #755f43;border-radius:6px;background:#241d18;color:var(--atelier-text);padding:8px 10px;cursor:pointer}
|
|
84
84
|
.eyebrow{color:var(--atelier-eyebrow);text-transform:uppercase;letter-spacing:.08em;font-size:.8rem}
|
|
85
85
|
a{color:var(--atelier-accent)}
|
|
86
|
+
${atelierControlStyles}
|
|
86
87
|
${branding.themeCss}</style>
|
|
87
88
|
</head>
|
|
88
89
|
<body><main>
|
|
@@ -123,6 +123,13 @@ function buildPackageReadiness({
|
|
|
123
123
|
}
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
// Paths in the written artifact are relative to the project config directory
|
|
127
|
+
// in POSIX form, so `readiness --check` does not depend on where the project
|
|
128
|
+
// is checked out and the artifact names no machine-specific directory.
|
|
129
|
+
function configRelative(project, file) {
|
|
130
|
+
return path.relative(project.configDir, file).split(path.sep).join('/') || '.'
|
|
131
|
+
}
|
|
132
|
+
|
|
126
133
|
function buildProjectReadiness({ project, graph = null } = {}) {
|
|
127
134
|
const loadedGraph = graph || (fs.existsSync(project.graphPath) ? readJson(project.graphPath) : null)
|
|
128
135
|
const projectionEntry = path.join(project.outputRoot, 'index.html')
|
|
@@ -142,14 +149,14 @@ function buildProjectReadiness({ project, graph = null } = {}) {
|
|
|
142
149
|
blockers,
|
|
143
150
|
warnings,
|
|
144
151
|
graph: {
|
|
145
|
-
path: project.graphPath,
|
|
152
|
+
path: configRelative(project, project.graphPath),
|
|
146
153
|
nodes: loadedGraph?.counts?.nodes ?? loadedGraph?.nodes?.length ?? 0,
|
|
147
154
|
edges: loadedGraph?.counts?.edges ?? loadedGraph?.edges?.length ?? 0,
|
|
148
155
|
diagnostics: loadedGraph?.counts?.diagnostics ?? loadedGraph?.diagnostics?.length ?? 0,
|
|
149
156
|
},
|
|
150
157
|
projection: {
|
|
151
|
-
outputRoot: project.outputRoot,
|
|
152
|
-
entry: projectionEntry,
|
|
158
|
+
outputRoot: configRelative(project, project.outputRoot),
|
|
159
|
+
entry: configRelative(project, projectionEntry),
|
|
153
160
|
},
|
|
154
161
|
support: {
|
|
155
162
|
telemetry: 'none',
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import Ajv from 'ajv/dist/2020.js'
|
|
3
|
+
import addFormats from 'ajv-formats'
|
|
4
|
+
import { boundedLearningValue, learningDigest as digest } from '../learning/contracts.mjs'
|
|
5
|
+
const schema = JSON.parse(fs.readFileSync(new URL('../../contracts/atelier-interaction.v1.schema.json', import.meta.url)))
|
|
6
|
+
const ajv = new Ajv({ strict: true, allErrors: true }); addFormats(ajv)
|
|
7
|
+
const validators = new Map()
|
|
8
|
+
export function validateInteraction(value, shape) {
|
|
9
|
+
try { boundedLearningValue(value) } catch (error) { return [error.message] }
|
|
10
|
+
if (!['observation', 'assessment', 'act', 'policy'].includes(shape)) return ['unsupported interaction shape']
|
|
11
|
+
if (!validators.has(shape)) validators.set(shape, ajv.compile({ $schema: schema.$schema, $defs: schema.$defs, $ref: `#/$defs/${shape}` }))
|
|
12
|
+
const validate = validators.get(shape)
|
|
13
|
+
return validate(value) ? [] : validate.errors.map(e => `${e.instancePath || '/'}: ${e.message}`)
|
|
14
|
+
}
|
|
15
|
+
export function assertInteraction(value, shape) {
|
|
16
|
+
const errors = validateInteraction(value, shape)
|
|
17
|
+
if (errors.length) throw new Error(`invalid ${shape}: ${errors.join('; ')}`)
|
|
18
|
+
return value
|
|
19
|
+
}
|
|
20
|
+
export function reflectionReference(record) {
|
|
21
|
+
const kind = record?.schema === 'atelier-reflection-observation@v1' ? 'observation' : 'assessment'
|
|
22
|
+
assertInteraction(record, kind)
|
|
23
|
+
return { kind, id: record.id, digest: digest(record) }
|
|
24
|
+
}
|
|
25
|
+
export function qualifyReflection({ assessment, observations, scope, purpose }) {
|
|
26
|
+
assertInteraction(assessment, 'assessment'); boundedLearningValue(observations)
|
|
27
|
+
if (!Array.isArray(observations) || observations.length > 128) throw new Error('bounded observations required')
|
|
28
|
+
for (const o of observations) assertInteraction(o, 'observation')
|
|
29
|
+
if (new Set(observations.map(o => o.id)).size !== observations.length) throw new Error('ambiguous observation identity')
|
|
30
|
+
const reasons = []
|
|
31
|
+
if (assessment.scope !== scope || assessment.purpose !== purpose || assessment.status !== 'active') reasons.push('assessment-outside-active-purpose')
|
|
32
|
+
if (!assessment.evidence.length) reasons.push('assessment-without-evidence')
|
|
33
|
+
if (new Set(assessment.evidence.map(e => `${e.kind}:${e.id}`)).size !== assessment.evidence.length) reasons.push('duplicate-evidence')
|
|
34
|
+
const admitted = []
|
|
35
|
+
for (const pin of assessment.evidence) {
|
|
36
|
+
const o = observations.find(o => o.id === pin.id)
|
|
37
|
+
if (pin.kind !== 'observation' || !o || digest(o) !== pin.digest || o.status !== 'active' || o.scope !== scope || o.purpose !== purpose) reasons.push(`evidence-stale-or-outside-purpose:${pin.id}`)
|
|
38
|
+
else admitted.push(o)
|
|
39
|
+
}
|
|
40
|
+
if (assessment.claimKind === 'independent-capacity' && (assessment.subject.kind !== 'person' || admitted.some(o => o.assistance !== 'independent' || o.subject.kind !== 'person' || o.subject.id !== assessment.subject.id))) reasons.push('independent-capacity-not-supported-by-admitted-evidence')
|
|
41
|
+
if (assessment.claimKind === 'system-friction' && assessment.subject.kind === 'person') reasons.push('system-friction-cannot-be-a-personal-capacity-claim')
|
|
42
|
+
return { reference: reflectionReference(assessment), current: reasons.length === 0, reasons,
|
|
43
|
+
uncertainty: assessment.uncertainty, limits: assessment.limits, semanticTruthVerified: false, observationAuthorityTransferred: false }
|
|
44
|
+
}
|
|
45
|
+
// The caller supplies an attributed judgment. This function qualifies its exact
|
|
46
|
+
// evidence, never invents a developmental score or silently persists a profile.
|
|
47
|
+
export function prepareReflectiveAct({ assessment, observations, act }) {
|
|
48
|
+
assertInteraction(act, 'act')
|
|
49
|
+
const result = qualifyReflection({ assessment, observations, scope: act.scope, purpose: act.purpose })
|
|
50
|
+
if (!result.current) throw new Error(`reflection unavailable: ${result.reasons.join('; ')}`)
|
|
51
|
+
if (act.origin !== 'witness' || !['reflective', 'coaching'].includes(act.mode)) throw new Error('reflective contribution requires Witness origin and declared mode')
|
|
52
|
+
const required = [reflectionReference(assessment), ...assessment.evidence]
|
|
53
|
+
for (const pin of required) if (!act.dependencies.some(p => digest(p) === digest(pin))) throw new Error('reflective act must pin its assessment and evidence')
|
|
54
|
+
if (act.mode === 'coaching' && !act.dependencies.some(p => p.kind === 'intention')) throw new Error('coaching requires a current chosen intention')
|
|
55
|
+
return { act: structuredClone(act), assessment: result, deliverable: false, executionAuthorized: false }
|
|
56
|
+
}
|
|
@@ -1,25 +1,28 @@
|
|
|
1
|
-
import { execFile, spawnSync } from 'node:child_process'
|
|
1
|
+
import { execFile, execFileSync, spawnSync } from 'node:child_process'
|
|
2
2
|
import fs from 'node:fs'
|
|
3
|
+
import { performance } from 'node:perf_hooks'
|
|
3
4
|
import path from 'node:path'
|
|
4
5
|
import { NEUTRAL_DIRECTORY, defaultCliPath, defaultObsidianProcessProbe, routedCall } from '../../projection/obsidian/publication/transport.mjs'
|
|
5
6
|
import { obsidianSandboxedBuild, obsidianUserDataDir, readObsidianSettings } from '../../projection/obsidian/publication/vault-list.mjs'
|
|
6
7
|
import { realPathAsStored } from '../../project/private-state.mjs'
|
|
7
8
|
import { appAnswered, readEvalAnswer, readVersionAnswer } from './app-capability.mjs'
|
|
8
|
-
import { registerVaultInObsidianSettings } from './app-registration.mjs'
|
|
9
|
+
import { backupObsidianSettings, registerVaultInObsidianSettings } from './app-registration.mjs'
|
|
10
|
+
import { createAppQuitter } from './app-restart.mjs'
|
|
9
11
|
import { launchPlan, runLaunchPlan, urlProcessed } from './launch-plan.mjs'
|
|
10
12
|
|
|
11
13
|
// The production seams of `obsidian open` and of the service's adapter
|
|
12
|
-
// factory: the only code that asks the installed Obsidian anything
|
|
13
|
-
// operating system to open it.
|
|
14
|
+
// factory: the only code that asks the installed Obsidian anything, asks the
|
|
15
|
+
// operating system to open it, or quits it (for a restart a person asked for).
|
|
14
16
|
//
|
|
15
17
|
// This module is imported by exactly two places, both real command-line
|
|
16
18
|
// entries and both behind an explicit `--adapter=obsidian-cli`: the service
|
|
17
19
|
// entry (service-main.mjs) and the command entry (src/commands/obsidian.mjs,
|
|
18
20
|
// `production: true`). No test of the default suite imports it in-process, and
|
|
19
|
-
// importing it leaves a trace below that the test suite asserts is absent.
|
|
20
|
-
//
|
|
21
|
-
// tool, to read the
|
|
22
|
-
//
|
|
21
|
+
// importing it leaves a trace below that the test suite asserts is absent.
|
|
22
|
+
// Tests import it in child processes only, with a private HOME: with a
|
|
23
|
+
// stand-in for the command-line tool, to read the answers it parses, and with
|
|
24
|
+
// a stand-in execFile, to check the launcher's executors; the opt-in real-app
|
|
25
|
+
// suite imports it to reach an isolated app through that app's private HOME.
|
|
23
26
|
//
|
|
24
27
|
// Status: exercised against an isolated Obsidian 1.13.7 (installer 1.12.7) on
|
|
25
28
|
// macOS by the opt-in real-app suite in test/obsidian-first-open-real-app.test.mjs;
|
|
@@ -127,7 +130,8 @@ function vaultRegisterCode(vaultRoot) {
|
|
|
127
130
|
// listThroughApp() -> { answered: true, vaults } | { answered: false, reason }
|
|
128
131
|
// registerThroughApp({ vaultRoot }) -> { answered: true, result } | { answered: false, reason }
|
|
129
132
|
// readSettings() -> readObsidianSettings answer (never writes)
|
|
130
|
-
// registerInSettings({ vaultRoot }) -> registerVaultInObsidianSettings answer
|
|
133
|
+
// registerInSettings({ vaultRoot, keepBackups }) -> registerVaultInObsidianSettings answer
|
|
134
|
+
// backupSettings() -> backupObsidianSettings answer (a copy beside the file; the file is not written)
|
|
131
135
|
//
|
|
132
136
|
// Through the app only while it runs and answers; in its settings file only
|
|
133
137
|
// while no Obsidian runs, which registerVaultInObsidianSettings checks itself.
|
|
@@ -155,9 +159,13 @@ export function createProductionAppRegistry({
|
|
|
155
159
|
return answer.answered ? { answered: true, result: answer.value?.result ?? null } : answer
|
|
156
160
|
},
|
|
157
161
|
readSettings: () => (sandbox === null ? readObsidianSettings({ userDataDir }) : sandboxed),
|
|
158
|
-
|
|
162
|
+
// A copy of the settings file as it is, before a restart sends the app any signal (backupObsidianSettings).
|
|
163
|
+
backupSettings: () => (sandbox !== null ? sandboxed : userDataDir === null
|
|
159
164
|
? { ok: false, code: 'obsidian-settings-location-unknown', message: 'where Obsidian keeps its settings on this system is not known' }
|
|
160
|
-
:
|
|
165
|
+
: backupObsidianSettings({ userDataDir })),
|
|
166
|
+
registerInSettings: ({ vaultRoot, keepBackups = [] }) => (sandbox !== null ? sandboxed : userDataDir === null
|
|
167
|
+
? { ok: false, code: 'obsidian-settings-location-unknown', message: 'where Obsidian keeps its settings on this system is not known' }
|
|
168
|
+
: registerVaultInObsidianSettings({ userDataDir, vaultRoot, processProbe, keepBackups })),
|
|
161
169
|
}
|
|
162
170
|
}
|
|
163
171
|
|
|
@@ -167,18 +175,25 @@ export function createProductionAppRegistry({
|
|
|
167
175
|
// the app itself answers the tool; a running app is handed the URL through its
|
|
168
176
|
// tool, which takes a URL even with its command line turned off, and through the
|
|
169
177
|
// operating system when the tool did not take it. A URL is never handed to the
|
|
170
|
-
// operating system for an app this launch started. No shell.
|
|
178
|
+
// operating system for an app this launch started. No shell. `execFile` is
|
|
179
|
+
// node's, and a stand-in in the test of these executors, which runs in a child
|
|
180
|
+
// process (the default suite never imports this module in-process).
|
|
171
181
|
export function createProductionLauncher({
|
|
172
|
-
platform = process.platform, env = process.env, cliPath = defaultCliPath(platform), workingDirectory = NEUTRAL_DIRECTORY, waitMs, pollMs, sleep,
|
|
182
|
+
platform = process.platform, env = process.env, cliPath = defaultCliPath(platform), workingDirectory = NEUTRAL_DIRECTORY, waitMs, pollMs, sleep, execFile: runFile = execFile,
|
|
173
183
|
} = {}) {
|
|
174
184
|
const osCommand = platform === 'darwin' ? '/usr/bin/open' : platform === 'linux' ? 'xdg-open' : null
|
|
175
185
|
const run = (file, args, timeout = 15_000) => new Promise((resolve) => {
|
|
176
|
-
|
|
186
|
+
runFile(file, args, { env, cwd: workingDirectory, timeout, killSignal: 'SIGKILL', encoding: 'utf8', maxBuffer: 1024 * 1024 }, (error, stdout, stderr) => resolve({ failed: Boolean(error), stdout: String(stdout ?? ''), stderr: String(stderr ?? '') }))
|
|
177
187
|
})
|
|
178
188
|
return {
|
|
179
|
-
|
|
189
|
+
// Starts a quit app with no URL, so it reopens the vaults its list flags open: for giving back an app `open`
|
|
190
|
+
// quit to restart it, when the vault could not be added after all. macOS only, as the restart is.
|
|
191
|
+
startPlain: async () => platform === 'darwin' && !(await run(osCommand, ['-b', 'md.obsidian'])).failed,
|
|
192
|
+
// `startedByThisOpen`: this `open` started the app with an earlier launch; the link goes through its tool only.
|
|
193
|
+
open({ vaultId = null, vaultPath = null, appRunning, startedByThisOpen = false }) {
|
|
180
194
|
const plan = launchPlan({ platform, appRunning, vaultId, vaultPath })
|
|
181
195
|
return runLaunchPlan(plan, {
|
|
196
|
+
startedEarlier: startedByThisOpen === true,
|
|
182
197
|
start: async () => !(await run(osCommand, ['-b', 'md.obsidian'])).failed,
|
|
183
198
|
answered: async () => { const reply = await run(cliPath, ['version'], CLI_TIMEOUT_MS); return appAnswered({ stdout: reply.stdout, stderr: reply.stderr, exited: !reply.failed }) },
|
|
184
199
|
handLink: async (uri) => urlProcessed((await run(cliPath, [uri], CLI_TIMEOUT_MS)).stdout),
|
|
@@ -189,6 +204,23 @@ export function createProductionLauncher({
|
|
|
189
204
|
}
|
|
190
205
|
}
|
|
191
206
|
|
|
207
|
+
// Quits the running app for a restart a person asked for (app-restart.mjs): SIGTERM to its main process only, proven
|
|
208
|
+
// from this machine's process table (`ps`, never a pattern search) as this account's, then waits until the process
|
|
209
|
+
// probe says no Obsidian runs. The table is `pid ppid uid executable`; one process is described again by its parent,
|
|
210
|
+
// user, start time and executable immediately before the signal.
|
|
211
|
+
export function createProductionAppQuitter({ platform = process.platform, processProbe = () => defaultObsidianProcessProbe({ platform }), waitMs } = {}) {
|
|
212
|
+
const ps = (args) => execFileSync('/bin/ps', args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'], maxBuffer: 16 * 1024 * 1024, timeout: 5000, killSignal: 'SIGKILL' })
|
|
213
|
+
return createAppQuitter({
|
|
214
|
+
platform, uid: typeof process.getuid === 'function' ? process.getuid() : null, processProbe,
|
|
215
|
+
readTable: () => ps(['-ww', '-A', '-o', 'pid=,ppid=,uid=,comm=']),
|
|
216
|
+
readProcess: (pid) => ps(['-ww', '-o', 'ppid=,uid=,lstart=,comm=', '-p', String(pid)]),
|
|
217
|
+
signal: (pid) => process.kill(pid, 'SIGTERM'),
|
|
218
|
+
// The wait and the linger are measured on a monotonic clock: a step of the wall clock changes neither.
|
|
219
|
+
now: () => performance.now(),
|
|
220
|
+
...(waitMs === undefined ? {} : { waitMs }),
|
|
221
|
+
})
|
|
222
|
+
}
|
|
223
|
+
|
|
192
224
|
export function createProductionAppSeams(options = {}) {
|
|
193
|
-
return { appProbe: createProductionAppProbe(options), launcher: createProductionLauncher(options), registry: createProductionAppRegistry(options) }
|
|
225
|
+
return { appProbe: createProductionAppProbe(options), launcher: createProductionLauncher(options), registry: createProductionAppRegistry(options), quitter: createProductionAppQuitter(options) }
|
|
194
226
|
}
|