@mnstry/atelier 0.2.0-alpha.12 → 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 +661 -0
- package/README.md +142 -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 +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 +181 -16
- package/docs/obsidian-contract.md +223 -15
- package/docs/obsidian-plugin.md +38 -4
- package/docs/obsidian.md +613 -51
- 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 +49 -0
- 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 +47 -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 +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 +59 -5
- 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 +754 -63
- 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 +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/projection/obsidian/edits/apply.mjs +55 -16
- package/src/projection/obsidian/personal-workspace.mjs +436 -0
- package/src/projection/obsidian/proposals/adapter.mjs +47 -8
- package/src/projection/obsidian/proposals/observation.mjs +10 -2
- package/src/projection/obsidian/publication/publisher.mjs +142 -13
- 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/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 +163 -20
- 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 +92 -12
- package/src/runtime/obsidian/login-item.mjs +332 -0
- package/src/runtime/obsidian/machine-settings.mjs +119 -12
- package/src/runtime/obsidian/opening.mjs +171 -37
- package/src/runtime/obsidian/pipeline.mjs +71 -3
- 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/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 +99 -16
- 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 +36 -3
- 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/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/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 +1 -0
- package/templates/external-project-workspace/pack-lifecycle.json +6 -1
- package/templates/knowledge-workspace/.gitattributes +2 -0
- package/templates/knowledge-workspace/AGENTS.md +27 -0
- package/templates/knowledge-workspace/README.md +44 -0
- package/templates/knowledge-workspace/atelier.project.json +22 -0
- package/templates/knowledge-workspace/gitignore +6 -0
- package/templates/knowledge-workspace/knowledge-plan.json +111 -0
- package/templates/knowledge-workspace/knowledge-plan.schema.json +281 -0
- package/templates/knowledge-workspace/records/forecast.md +22 -0
- package/templates/knowledge-workspace/records/inspection.md +25 -0
- package/templates/knowledge-workspace/records/telescope.md +24 -0
- package/templates/knowledge-workspace/repo-access.v1.json +9 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
import { performance } from 'node:perf_hooks'
|
|
3
|
+
|
|
4
|
+
// Quitting a running Obsidian so that `open` can add a vault and turn its
|
|
5
|
+
// command line on, only when a person asked for it (`--restart-obsidian`).
|
|
6
|
+
//
|
|
7
|
+
// `open` restarts the app in two states only, where it runs but cannot be
|
|
8
|
+
// reached: its command line is turned off, or no vault is open (and the app
|
|
9
|
+
// does not list the view's vault). The settings file then belongs to the
|
|
10
|
+
// running app, which would write its own list back over any change, and the
|
|
11
|
+
// app cannot be asked to add a vault. Before anything is sent, `open` checks,
|
|
12
|
+
// read-only, that the write the restart is for can succeed (opening.mjs); right
|
|
13
|
+
// before the first signal, once the main process is proven, `beforeSignal` keeps
|
|
14
|
+
// a copy of the settings file, and a copy that cannot be kept sends nothing.
|
|
15
|
+
//
|
|
16
|
+
// SIGTERM, which Electron handles as a quit, is sent to one process: the app's
|
|
17
|
+
// main process, proven from the process table, and never to anything else. The
|
|
18
|
+
// main process is proven when, in a table of this machine's processes as
|
|
19
|
+
// `pid ppid uid comm` (`comm` is the process's argv[0] as macOS reports it, not
|
|
20
|
+
// a verified image: a process of this user can name itself anything, which is
|
|
21
|
+
// why two candidates prove nothing):
|
|
22
|
+
//
|
|
23
|
+
// - every process of the app (the names the process probe counts: `Obsidian`
|
|
24
|
+
// and the bundle's `Obsidian Helper…`) runs as this user;
|
|
25
|
+
// - exactly one of them names the app bundle's main executable by an absolute
|
|
26
|
+
// path (`…/Contents/MacOS/Obsidian`); a process shown by its short name
|
|
27
|
+
// only is not proven;
|
|
28
|
+
// - every other one is a child of that process (its helpers).
|
|
29
|
+
//
|
|
30
|
+
// Anything else (two instances, another user's app, a helper of another
|
|
31
|
+
// parent, a table that cannot be read) is `app-main-process-unproven`, and no
|
|
32
|
+
// signal is sent. Immediately before the signal the one process is read again
|
|
33
|
+
// (`ppid uid start-time comm`) and must be of this user, under the parent the
|
|
34
|
+
// table showed, with the same name, and read the same twice.
|
|
35
|
+
//
|
|
36
|
+
// What SIGTERM does (1.13.7, checked on an isolated instance): the app handles
|
|
37
|
+
// the first one as a quit. It closes every window, keeping the vaults that were
|
|
38
|
+
// open flagged open in its list, and its helper processes end; on macOS the main
|
|
39
|
+
// process can then stay, with no window, as a macOS app does. Electron handles
|
|
40
|
+
// only the first SIGTERM as a quit: a second one ends the process at once,
|
|
41
|
+
// without its quit handlers. It is sent only after that same process has shown
|
|
42
|
+
// no window and no helper (it is alone in the table, and reads the same) at
|
|
43
|
+
// every reading for at least `lingerMs`, measured on a monotonic clock; a
|
|
44
|
+
// reading that cannot be parsed does not count as alone. That is a margin, not
|
|
45
|
+
// a proof that nothing is left to save. There is never a third signal, and
|
|
46
|
+
// never another kind; an app that is still not gone is reported
|
|
47
|
+
// (`app-did-not-quit`) and left to the person. `signals` counts the signals
|
|
48
|
+
// that were delivered.
|
|
49
|
+
//
|
|
50
|
+
// Qualified on macOS only: on Linux a quit app is started again with a link,
|
|
51
|
+
// which drops the other vaults' reopen flags (launch-plan.mjs), so the promise
|
|
52
|
+
// that they reopen would not hold there.
|
|
53
|
+
//
|
|
54
|
+
// Nothing here reads the process table or sends a signal itself: every seam is
|
|
55
|
+
// injected, with no default. Only the production seams module of the command
|
|
56
|
+
// entry and the opt-in real-app suite, for its isolated app, construct one.
|
|
57
|
+
|
|
58
|
+
export const RESTART_PLATFORMS = Object.freeze(['darwin'])
|
|
59
|
+
const APP_PROCESS = /^obsidian(?: helper\b.*)?$/i
|
|
60
|
+
const MAIN_EXECUTABLE = /^\/.+\.app\/Contents\/MacOS\/Obsidian$/i
|
|
61
|
+
// `pid ppid uid comm`. macOS prints the uid of `nobody` as -2: a uid is signed.
|
|
62
|
+
export const PROCESS_ROW = /^\s*(\d+)\s+(\d+)\s+(-?\d+)\s+(.+?)\s*$/
|
|
63
|
+
// `ppid uid start-time comm`, one process read again.
|
|
64
|
+
const DESCRIBED = /^\s*(\d+)\s+(-?\d+)\s+(.+?)\s*$/
|
|
65
|
+
|
|
66
|
+
const unproven = (detail) => ({ state: 'unproven', reason: 'app-main-process-unproven', detail })
|
|
67
|
+
const rowsOf = (table) => table.split('\n').filter((line) => line.trim() !== '').map((line) => PROCESS_ROW.exec(line))
|
|
68
|
+
|
|
69
|
+
// Pure. The app's main process in a process table (`ps -A -o pid=,ppid=,uid=,comm=`), for the account `uid`:
|
|
70
|
+
// { state: 'proven', pid, ppid, executable } | { state: 'absent' } | { state: 'unproven', reason, detail }.
|
|
71
|
+
export function provenMainProcess({ table, uid } = {}) {
|
|
72
|
+
if (typeof table !== 'string' || table.trim() === '') return unproven('the process table could not be read')
|
|
73
|
+
if (!Number.isSafeInteger(uid) || uid < 0) return unproven('the account is not known')
|
|
74
|
+
const matches = rowsOf(table)
|
|
75
|
+
if (matches.some((match) => match === null)) return unproven('the process table has a line that is not a process')
|
|
76
|
+
const rows = matches.map((match) => ({ pid: Number(match[1]), ppid: Number(match[2]), uid: Number(match[3]), executable: match[4] }))
|
|
77
|
+
const app = rows.filter((row) => APP_PROCESS.test(path.posix.basename(row.executable)))
|
|
78
|
+
if (app.length === 0) return { state: 'absent' }
|
|
79
|
+
if (app.some((row) => row.uid !== uid)) return unproven('an Obsidian process runs as another user')
|
|
80
|
+
const main = app.filter((row) => /^obsidian$/i.test(path.posix.basename(row.executable)))
|
|
81
|
+
if (main.length !== 1) return unproven(main.length === 0 ? 'no Obsidian main process is in the process table' : 'more than one Obsidian main process runs')
|
|
82
|
+
const [one] = main
|
|
83
|
+
if (!MAIN_EXECUTABLE.test(one.executable)) return unproven('the main process does not name the app bundle\'s executable')
|
|
84
|
+
if (app.some((row) => row !== one && row.ppid !== one.pid)) return unproven('an Obsidian helper process is not a child of the main process')
|
|
85
|
+
return { state: 'proven', pid: one.pid, ppid: one.ppid, executable: one.executable }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// `readTable()` -> the process table as provenMainProcess reads it (throws or answers '' when it cannot)
|
|
89
|
+
// `readProcess(pid)` -> that process read again as `ppid uid start-time comm`, or null
|
|
90
|
+
// `signal(pid)` -> sends SIGTERM to exactly that pid; throws when it was not delivered
|
|
91
|
+
// `processProbe()` -> 'running' | 'absent' | 'unknown', the probe the settings write uses
|
|
92
|
+
//
|
|
93
|
+
// quit({ beforeSignal }) answers { quit: true, pid, signalled, signals, settingsCopy? } once the probe says `absent`,
|
|
94
|
+
// or { quit: false, reason, signalled, signals, pid?, detail?, settingsCopy? }: `restart-platform-unqualified`,
|
|
95
|
+
// `app-main-process-unproven` and the refusal of `beforeSignal` before any signal, `app-did-not-quit` after one, when
|
|
96
|
+
// the probe did not say `absent` within `waitMs`. `beforeSignal()`, when given, runs once the main process is proven
|
|
97
|
+
// and read again, immediately before the first signal: { ok: true, backupPath? } lets it be sent, anything else
|
|
98
|
+
// ({ ok: false, code }) sends nothing. `now` is a monotonic clock in milliseconds.
|
|
99
|
+
export function createAppQuitter({ platform, uid, readTable, readProcess, signal, processProbe, sleep = (ms) => new Promise((resolve) => { setTimeout(resolve, ms) }), now = () => performance.now(), waitMs = 30_000, pollMs = 250, lingerMs = 5000 } = {}) {
|
|
100
|
+
for (const [name, seam] of Object.entries({ readTable, readProcess, signal, processProbe })) if (typeof seam !== 'function') throw new TypeError(`quitting Obsidian needs an injected ${name}`)
|
|
101
|
+
const probe = () => { try { return processProbe() } catch { return 'unknown' } }
|
|
102
|
+
const describe = (pid) => { try { const line = readProcess(pid); return typeof line === 'string' && line.trim() !== '' ? line.trim() : null } catch { return null } }
|
|
103
|
+
const table = () => { try { const text = readTable(); return typeof text === 'string' ? text : '' } catch { return '' } }
|
|
104
|
+
// The process read again is the one the table proved: this user's, under the parent the table showed, same name.
|
|
105
|
+
const isFound = (line, found) => {
|
|
106
|
+
const match = line === null ? null : DESCRIBED.exec(line)
|
|
107
|
+
return match !== null && Number(match[1]) === found.ppid && Number(match[2]) === uid && match[3].endsWith(found.executable)
|
|
108
|
+
}
|
|
109
|
+
// The proven main process alone in the table, still the process `seen` described: its windows closed, it did not quit.
|
|
110
|
+
const lingersAlone = (found, seen) => {
|
|
111
|
+
const matches = rowsOf(table())
|
|
112
|
+
if (matches.length === 0 || matches.some((match) => match === null)) return false
|
|
113
|
+
const rows = matches.filter((match) => APP_PROCESS.test(path.posix.basename(match[4])))
|
|
114
|
+
return rows.length === 1 && Number(rows[0][1]) === found.pid && describe(found.pid) === seen
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
async quit({ beforeSignal = null } = {}) {
|
|
118
|
+
if (!RESTART_PLATFORMS.includes(platform)) return { quit: false, reason: 'restart-platform-unqualified', signalled: false, signals: 0 }
|
|
119
|
+
const found = provenMainProcess({ table: table(), uid })
|
|
120
|
+
if (found.state === 'absent') return probe() === 'absent' ? { quit: true, pid: null, signalled: false, signals: 0 } : { quit: false, reason: 'app-main-process-unproven', detail: 'the process table shows no Obsidian, and the process probe does not say it is gone', signalled: false, signals: 0 }
|
|
121
|
+
if (found.state !== 'proven') return { quit: false, reason: found.reason, detail: found.detail, signalled: false, signals: 0 }
|
|
122
|
+
const seen = describe(found.pid)
|
|
123
|
+
if (!isFound(seen, found)) return { quit: false, reason: 'app-main-process-unproven', detail: 'the main process read again is not the one the table showed', signalled: false, signals: 0 }
|
|
124
|
+
// Immediately before the signal: still the same process.
|
|
125
|
+
if (describe(found.pid) !== seen) return { quit: false, reason: 'app-main-process-unproven', detail: 'the main process changed while it was being proven', signalled: false, signals: 0 }
|
|
126
|
+
let copy = {}
|
|
127
|
+
if (typeof beforeSignal === 'function') {
|
|
128
|
+
let kept
|
|
129
|
+
try { kept = await beforeSignal() } catch { kept = null }
|
|
130
|
+
if (kept?.ok !== true) return { quit: false, reason: typeof kept?.code === 'string' ? kept.code : 'obsidian-settings-unwritable', signalled: false, signals: 0 }
|
|
131
|
+
if (typeof kept.backupPath === 'string') copy = { settingsCopy: kept.backupPath }
|
|
132
|
+
}
|
|
133
|
+
try { signal(found.pid) } catch { return { quit: false, reason: 'app-main-process-unproven', detail: 'the main process could not be signalled', signalled: false, signals: 0, ...copy } }
|
|
134
|
+
let signals = 1
|
|
135
|
+
let second = false
|
|
136
|
+
let aloneSince = null
|
|
137
|
+
const deadline = now() + waitMs
|
|
138
|
+
for (;;) {
|
|
139
|
+
if (probe() === 'absent') return { quit: true, pid: found.pid, signalled: true, signals, ...copy }
|
|
140
|
+
if (now() >= deadline) return { quit: false, reason: 'app-did-not-quit', pid: found.pid, signalled: true, signals, ...copy }
|
|
141
|
+
if (!second) {
|
|
142
|
+
if (!lingersAlone(found, seen)) aloneSince = null
|
|
143
|
+
else if (aloneSince === null) aloneSince = now()
|
|
144
|
+
else if (now() - aloneSince >= lingerMs) {
|
|
145
|
+
// Once, whether or not it is delivered.
|
|
146
|
+
second = true
|
|
147
|
+
try { signal(found.pid); signals = 2 } catch { /* not delivered: not counted */ }
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
await sleep(pollMs)
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -2,11 +2,12 @@ import fs from 'node:fs'
|
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
import { AtelierDiagnosticError } from '../../project/config.mjs'
|
|
4
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,
|
|
@@ -14,10 +15,12 @@ import {
|
|
|
14
15
|
} from './machine-settings.mjs'
|
|
15
16
|
import { configKey, listConfigFiles, listSourceFiles, listVaultNotes, readFileFacts, reconcile, sha256Digest, sourceKey, vaultKey } from './observation.mjs'
|
|
16
17
|
import { dispatchAutomaticApply, heldPaths, layoutHeldPaths, observeVaultEdits, preserveInRecoveryStore, trustedNoteBases } from './pending-edits.mjs'
|
|
17
|
-
import {
|
|
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
|
|
@@ -126,7 +129,15 @@ export const ENGINE_PRIMITIVES = Object.freeze({
|
|
|
126
129
|
})
|
|
127
130
|
|
|
128
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)
|
|
129
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 }))]
|
|
130
141
|
|
|
131
142
|
// An editor adapter that `build` makes, synchronously or not, the first time the publisher calls it; a refusal of
|
|
132
143
|
// `build` is thrown from that call, and `refusal()` answers it afterwards.
|
|
@@ -142,6 +153,11 @@ function builtOnFirstUse(build) {
|
|
|
142
153
|
publish: async (payload) => (await built()).publish(payload),
|
|
143
154
|
}
|
|
144
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
|
+
}
|
|
145
161
|
// Views a tick may be asked to prepare again, at most, before it runs.
|
|
146
162
|
const MAX_PREPARATION_REQUESTS = 64
|
|
147
163
|
|
|
@@ -166,9 +182,19 @@ function journalsToRecheck(store, { nowMs, full, windowMs }) {
|
|
|
166
182
|
export function createMaintenanceEngineForOracleTests(options = {}, primitives = ENGINE_PRIMITIVES) {
|
|
167
183
|
const {
|
|
168
184
|
loadProject, dataRoot, adapterFactory, clock,
|
|
169
|
-
|
|
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,
|
|
170
187
|
fullReconciliationIntervalMs = DEFAULT_FULL_RECONCILIATION_INTERVAL_MS, retryIntervalMs = DEFAULT_RETRY_INTERVAL_MS, lateWriterWindowMs = DEFAULT_LATE_WRITER_WINDOW_MS,
|
|
171
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,
|
|
172
198
|
quietPeriodMs, lstat = fs.lstatSync, randomBytes, env = process.env, platform = process.platform,
|
|
173
199
|
// A service names where it answers health, so a lock it leaves behind can be proven abandoned.
|
|
174
200
|
lockOwner = null, lockProbe = probeHealth,
|
|
@@ -215,6 +241,14 @@ export function createMaintenanceEngineForOracleTests(options = {}, primitives =
|
|
|
215
241
|
const waitingForApp = new Map()
|
|
216
242
|
const proveAbandoned = createAbandonmentProof({ probe: lockProbe })
|
|
217
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
|
+
|
|
218
252
|
async function takeLock(workspaceRoot, workspaceId) {
|
|
219
253
|
if (heldLock) return { acquired: true }
|
|
220
254
|
const lock = await rules.acquireEngineLock({
|
|
@@ -249,9 +283,18 @@ export function createMaintenanceEngineForOracleTests(options = {}, primitives =
|
|
|
249
283
|
}
|
|
250
284
|
|
|
251
285
|
function storeFor(scope, workspaceRoot, workspaceId, repositoryRoots) {
|
|
252
|
-
|
|
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 })])
|
|
253
291
|
const cached = stores.get(scope.scopeId)
|
|
254
|
-
|
|
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)
|
|
255
298
|
const store = seams.createRecoveryStore({ workspaceRoot, workspaceId, scopeId: scope.scopeId, repositoryRoots })
|
|
256
299
|
stores.set(scope.scopeId, { signature, store })
|
|
257
300
|
return store
|
|
@@ -318,15 +361,19 @@ export function createMaintenanceEngineForOracleTests(options = {}, primitives =
|
|
|
318
361
|
|
|
319
362
|
// 1. Project and enablement. The digests are seeded before the project is
|
|
320
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.
|
|
321
368
|
if (project === null) {
|
|
322
|
-
project = loadProject()
|
|
323
|
-
reconcile({ index, files:
|
|
324
|
-
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()
|
|
325
372
|
} else {
|
|
326
|
-
const config = reconcile({ index, files:
|
|
373
|
+
const config = reconcile({ index, files: configFilesOf(project), prefix: CONFIG_PREFIX, full, hinted, lstat })
|
|
327
374
|
if (config.changes.length > 0) {
|
|
328
|
-
project = loadProject()
|
|
329
|
-
reconcile({ index, files:
|
|
375
|
+
project = await loadProject()
|
|
376
|
+
reconcile({ index, files: configFilesOf(project), prefix: CONFIG_PREFIX, full: true, lstat })
|
|
330
377
|
}
|
|
331
378
|
}
|
|
332
379
|
const enablement = readObsidianEnablement(project)
|
|
@@ -351,14 +398,63 @@ export function createMaintenanceEngineForOracleTests(options = {}, primitives =
|
|
|
351
398
|
if (!lock.acquired) return { state: 'busy', reason: 'engine-lock-held', lock: { reason: lock.reason, holder: lock.holder ?? null }, changes: [], scopes: [], pendingEdits: [], dispatched: [], lateWriters: [] }
|
|
352
399
|
const machine = readMachineSettings({ workspaceRoot, workspaceId })
|
|
353
400
|
?? writeMachineSettings({ workspaceRoot, workspaceId, repositoryRoots, settings: defaultMachineSettings({ workspaceId, updatedAt: now }) })
|
|
401
|
+
const eligibility = fixedEligibility ?? eligibilityFor({ machine, project })
|
|
354
402
|
const stateStore = createMaintenanceStateStore({ workspaceRoot, workspaceId })
|
|
355
403
|
known = { stateStore, maintenanceMode: machine.maintenanceMode }
|
|
356
|
-
|
|
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
|
+
}
|
|
357
453
|
|
|
358
454
|
watch([
|
|
359
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) })),
|
|
360
456
|
...scopes.map(({ scope, store }) => ({ id: `vault:${scope.scopeId}`, path: store.vaultRoot, recursive: true, keyPrefix: vaultKey(scope.scopeId, ''), keyOf: (relative) => vaultKey(scope.scopeId, relative) })),
|
|
361
|
-
...[...new Set(
|
|
457
|
+
...[...new Set(configFilesOf(project).map((file) => path.dirname(file.absolute)))].filter((directory) => fs.existsSync(directory))
|
|
362
458
|
.map((directory) => ({ id: `config:${directory}`, path: directory, recursive: false, keyPrefix: CONFIG_PREFIX, keyOf: (relative) => configKey(path.join(directory, relative)) })),
|
|
363
459
|
])
|
|
364
460
|
|
|
@@ -385,9 +481,12 @@ export function createMaintenanceEngineForOracleTests(options = {}, primitives =
|
|
|
385
481
|
// Observation. The registered proposal adapter is handed a copy of the pending edits and observes the open ones
|
|
386
482
|
// the object store does not know yet, a bounded number per tick: each is classified from its preserved bytes
|
|
387
483
|
// against the source as it is now and recorded as what it is, in manual and in automatic mode alike, and no
|
|
388
|
-
// 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.
|
|
389
487
|
const proposalAdapter = extensions.get('proposal-adapter')
|
|
390
|
-
|
|
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 })
|
|
391
490
|
let observed = null
|
|
392
491
|
if (proposalAdapter !== null && rules.observeEdits(proposalAdapter)) {
|
|
393
492
|
try { observed = await proposalAdapter.observe(adapterContext(), { retryRefused: full }) } catch { observed = { adapterId: proposalAdapter.id, failed: 'proposal-adapter-threw' } }
|
|
@@ -455,7 +554,7 @@ export function createMaintenanceEngineForOracleTests(options = {}, primitives =
|
|
|
455
554
|
if (full) { lastFullMs = nowMs; forceFull = false }
|
|
456
555
|
|
|
457
556
|
// 7. Which views are invalid.
|
|
458
|
-
declaredScopes = new Set(scopes.map((
|
|
557
|
+
declaredScopes = new Set(enablement.scopes.map((scope) => scope.scopeId))
|
|
459
558
|
const previous = readPreviousFreshness(stateStore)
|
|
460
559
|
const entries = new Map()
|
|
461
560
|
const unseen = new Set()
|
|
@@ -479,9 +578,21 @@ export function createMaintenanceEngineForOracleTests(options = {}, primitives =
|
|
|
479
578
|
if (earlierLayout.has(scope.scopeId) && entry.state === 'current' && layoutHeldOf(scope.scopeId).length === 0) invalidate(scope.scopeId, null)
|
|
480
579
|
}
|
|
481
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
|
+
}
|
|
482
590
|
|
|
483
591
|
// Invalidation is durable before any work: a view is not reported current while it is being rebuilt.
|
|
484
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
|
+
}
|
|
485
596
|
for (const entry of previous?.scopes ?? []) if (!entries.has(entry.scopeId)) entries.set(entry.scopeId, demote(entry, 'disabled', 'scope-not-configured', now))
|
|
486
597
|
const persist = () => stateStore.writeFreshness(freshnessDocument({ workspaceId, enablement: 'enabled', maintenanceMode: machine.maintenanceMode, now, entries }))
|
|
487
598
|
persist()
|
|
@@ -490,7 +601,19 @@ export function createMaintenanceEngineForOracleTests(options = {}, primitives =
|
|
|
490
601
|
if (attempt.size > 0) {
|
|
491
602
|
let built = null
|
|
492
603
|
try {
|
|
493
|
-
|
|
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
|
+
}
|
|
494
617
|
// The assets this graph lets a view copy are observed from now on, and hashed now so the snapshot pins
|
|
495
618
|
// what observation saw. A withheld asset is not observed: its bytes can change no view.
|
|
496
619
|
const formerAssetKeys = new Set(observedAssets.map((asset) => sourceKey(asset.repo, asset.path)))
|
|
@@ -508,6 +631,9 @@ export function createMaintenanceEngineForOracleTests(options = {}, primitives =
|
|
|
508
631
|
if (!isTypedRefusal(error)) throw error
|
|
509
632
|
for (const scopeId of attempt.keys()) entries.set(scopeId, demote(entries.get(scopeId), 'stale', error.code, now))
|
|
510
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
|
|
511
637
|
}
|
|
512
638
|
for (const { scope, store } of built ? scopes.filter((item) => attempt.has(item.scope.scopeId)) : []) {
|
|
513
639
|
const { scopeId } = scope
|
|
@@ -543,13 +669,29 @@ export function createMaintenanceEngineForOracleTests(options = {}, primitives =
|
|
|
543
669
|
// that cannot be qualified never makes a current view stale. Any other publication builds the adapter first,
|
|
544
670
|
// and an app that does not qualify keeps the publisher from being reached at all.
|
|
545
671
|
const lazy = trusted()?.generationId === preparedGenerationId ? builtOnFirstUse(() => adapterFactory({ store, scope })) : null
|
|
546
|
-
|
|
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
|
+
}
|
|
547
685
|
let result
|
|
548
686
|
try {
|
|
549
687
|
result = await seams.publishView({
|
|
550
688
|
preparedView: prepared, protocolId: PROTOCOL_ID, expectedGeneration: trusted()?.generationId ?? null, recoveryStore: store, adapter, clock,
|
|
551
689
|
...(quietPeriodMs === undefined ? {} : { quietPeriodMs }),
|
|
690
|
+
...(typeof readUnheldEvidence === 'function' ? { unheldEvidence: readUnheldEvidence, platform } : {}),
|
|
552
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
|
|
553
695
|
} catch (error) {
|
|
554
696
|
// The publisher asked for the app only to publish the committed generation again (a plugin file drifted),
|
|
555
697
|
// and the app did not qualify: nothing was written, and the committed generation is read back as it is.
|
|
@@ -579,7 +721,8 @@ export function createMaintenanceEngineForOracleTests(options = {}, primitives =
|
|
|
579
721
|
// Somebody wrote during the publication. The next tick preserves and queues it.
|
|
580
722
|
else if (differing.length > 0 || editedUnderPublisher) settle('updating', 'read-back-differs', common)
|
|
581
723
|
else if (common.generationId !== preparedGenerationId) settle('stale', 'committed-generation-differs', common)
|
|
582
|
-
|
|
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 })
|
|
583
726
|
}
|
|
584
727
|
} catch (error) {
|
|
585
728
|
if (!isTypedRefusal(error)) { settle('stale', 'publisher-error'); persist(); throw error }
|
|
@@ -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', 'plugin', '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,8 +6,9 @@
|
|
|
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'
|
|
@@ -20,19 +21,21 @@ export {
|
|
|
20
21
|
} from './state-store.mjs'
|
|
21
22
|
export { listConfigFiles, listSourceFiles, listVaultNotes, reconcile } from './observation.mjs'
|
|
22
23
|
export { createFsWatcherFactory, createNullWatcherFactory } from './watchers.mjs'
|
|
23
|
-
export { DEFAULT_ELIGIBILITY, assetEligibilityFor, createProductionSeams } from './pipeline.mjs'
|
|
24
|
+
export { DEFAULT_ELIGIBILITY, assetEligibilityFor, createProductionSeams, eligibilityFor, onlyYouEligibility } from './pipeline.mjs'
|
|
24
25
|
export { DEFAULT_FULL_RECONCILIATION_INTERVAL_MS, DEFAULT_LATE_WRITER_WINDOW_MS, DEFAULT_PUBLICATION_RETRY_MS, DEFAULT_RETRY_INTERVAL_MS, createMaintenanceEngine } from './engine.mjs'
|
|
25
26
|
export { ENGINE_LOCK_DIRECTORY, LOCK_TICKET_SCHEMA, acquirePrivateGenerationLock, createAbandonmentProof, inspectPrivateGenerationLock } from './private-lock.mjs'
|
|
26
27
|
export { DEFAULT_MAX_BACKOFF_MS, DEFAULT_TICK_INTERVAL_MS, createTickLoop } from './tick-loop.mjs'
|
|
27
28
|
export { HEALTH_SCHEMA, LOOPBACK_HOSTS, probeHealth, requestLoopback } from './service-client.mjs'
|
|
28
29
|
export {
|
|
29
|
-
CONSENT_COVERAGES, SERVICE_ERROR_SCHEMA, SERVICE_SETTINGS_SCHEMA, executableIdentity, publicRecord, readLastServiceError,
|
|
30
|
-
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,
|
|
31
32
|
} from './service-record.mjs'
|
|
32
33
|
export { MAX_REQUEST_BYTES, SERVICE_OPERATIONS, createServiceServer } from './service-server.mjs'
|
|
33
34
|
export { PLUGIN_BEARER_SCHEMA, PLUGIN_PRESENCE_SCHEMA, createPluginChannel, createPluginSessions, ensurePluginBearer, pluginBearerDirectory, pluginPresence, readPluginBearers } from './plugin-channel.mjs'
|
|
34
35
|
export { PLUGIN_PRESENCE_REASONS, pluginPresenceOf, turnPluginOnNext, withPluginReportedVersion } from './plugin-presence.mjs'
|
|
35
36
|
export { PLUGIN_CHOICE_SCHEMA, PLUGIN_CHOICE_STATES, confirmPluginEntry, confirmPluginSeen, currentPluginChoice, decidePluginChoice, pluginChoiceDirectory, readCommunityEntry, readPluginChoice, viewVaultRoot, writePluginChoice } from './plugin-choice.mjs'
|
|
36
|
-
export { DEFAULT_SHUTDOWN_GRACE_MS, SERVICE_STATUS_SCHEMA, resolveServiceWorkspace, runMaintenanceService } from './service.mjs'
|
|
37
|
+
export { DEFAULT_SHUTDOWN_GRACE_MS, RELEASE_CHANGED, SERVICE_STATUS_SCHEMA, resolveServiceWorkspace, runMaintenanceService } from './service.mjs'
|
|
37
38
|
export { DEFAULT_START_TIMEOUT_MS, DEFAULT_STOP_TIMEOUT_MS, SERVICE_STATES, readServiceStatusDocument, requestServiceTick, serviceStatus, startService, stopService } from './lifecycle.mjs'
|
|
38
|
-
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'
|
|
@@ -55,11 +55,14 @@ export const urlProcessed = (stdout) => typeof stdout === 'string' && /^processe
|
|
|
55
55
|
//
|
|
56
56
|
// A URL is never handed to the operating system for an app this launch
|
|
57
57
|
// started: that would start or reach it while it starts, which is what drops
|
|
58
|
-
// the other vaults' reopen flags.
|
|
59
|
-
// started app
|
|
60
|
-
|
|
58
|
+
// the other vaults' reopen flags. A vault added flagged open is opened anyway by
|
|
59
|
+
// a plainly started app, and `open` waits for it; one already listed but closed
|
|
60
|
+
// is handed the link once more instead (opening.mjs). `startedEarlier` says
|
|
61
|
+
// that an earlier launch of the same `open` started the app (a link handed to
|
|
62
|
+
// it once more): the link then goes through the app's tool only, as well.
|
|
63
|
+
export async function runLaunchPlan(plan, { start, answered, handLink, osOpen, sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms)), waitMs = 30_000, pollMs = 500, now = () => Date.now(), startedEarlier = false }) {
|
|
61
64
|
if (!plan?.ok) return { launched: false, reason: plan?.reason ?? 'launcher-refused' }
|
|
62
|
-
let started =
|
|
65
|
+
let started = startedEarlier === true
|
|
63
66
|
let reason = 'url-accepted'
|
|
64
67
|
for (const { step, uri } of plan.steps) {
|
|
65
68
|
if (step === 'plain-start') {
|