@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
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto'
|
|
2
|
+
import fs from 'node:fs'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
import { PRIVATE_AUDIENCES, loadBoundaryPolicy } from '../boundary/policy.mjs'
|
|
5
|
+
import { parseProjectOptions } from '../cli/project-options.mjs'
|
|
6
|
+
import { AtelierDiagnosticError, commandProject } from '../project/config.mjs'
|
|
7
|
+
import { VALID_AUDIENCES } from '../projection/policy.mjs'
|
|
8
|
+
import { canonicalGraphOptions } from './graph.mjs'
|
|
9
|
+
import { SOURCE_SIDECAR_SCHEMA, buildKnowledgeGraph, validateSourceSidecar } from './knowledge-graph.mjs'
|
|
10
|
+
|
|
11
|
+
// Document enrollment writes the sidecar the census demands of every
|
|
12
|
+
// non-Markdown document that has none: the smallest classification that lets
|
|
13
|
+
// `atelier graph` pass, never a judgement about the document. It reads the same
|
|
14
|
+
// census as the graph (scope, Git ignore rules and all), writes only where no
|
|
15
|
+
// sidecar exists, and never changes a source.
|
|
16
|
+
|
|
17
|
+
export const DEFAULT_ENROLLMENT_AUDIENCE = 'private'
|
|
18
|
+
|
|
19
|
+
const AUDIENCE_ORDER = ['private', 'sensitive', 'staff', 'operator', 'team', 'public']
|
|
20
|
+
const orderedAudiences = (audiences) => AUDIENCE_ORDER.filter((audience) => audiences.includes(audience))
|
|
21
|
+
|
|
22
|
+
// The audiences a repository's boundary policy lets a node carry, by the same
|
|
23
|
+
// three rules the boundary check applies to every node. Null when the policy
|
|
24
|
+
// does not cover the repository, which the boundary check reports on its own.
|
|
25
|
+
export function enrollableAudiences(repoPolicy) {
|
|
26
|
+
if (!repoPolicy || typeof repoPolicy !== 'object') return null
|
|
27
|
+
const allowed = Array.isArray(repoPolicy.allowedAudiences) ? repoPolicy.allowedAudiences : []
|
|
28
|
+
const forbidden = Array.isArray(repoPolicy.forbiddenAudiences) ? repoPolicy.forbiddenAudiences : []
|
|
29
|
+
return orderedAudiences(
|
|
30
|
+
allowed.filter((audience) => !forbidden.includes(audience) && !(PRIVATE_AUDIENCES.has(audience) && repoPolicy.kind !== 'private_domain')),
|
|
31
|
+
)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function assertEnrollmentAudience(audience) {
|
|
35
|
+
if (typeof audience === 'string' && VALID_AUDIENCES.has(audience)) return audience
|
|
36
|
+
throw new AtelierDiagnosticError('enroll-audience-invalid', `--audience must be one of ${orderedAudiences([...VALID_AUDIENCES]).join(', ')}`, {
|
|
37
|
+
hint: `Omit --audience to enroll documents as ${DEFAULT_ENROLLMENT_AUDIENCE}.`,
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Refuses, before anything is written, an audience the boundary policy would
|
|
42
|
+
// reject in any repository that has a document to enroll. A policy in
|
|
43
|
+
// legacy-warning mode only warns about placement, and so does not refuse here.
|
|
44
|
+
export function assertAudienceAllowed({ policy, repoNames, audience }) {
|
|
45
|
+
if (policy?.mode === 'legacy-warning') return
|
|
46
|
+
for (const repoName of repoNames) {
|
|
47
|
+
const repoPolicy = policy?.repos && Object.hasOwn(policy.repos, repoName) ? policy.repos[repoName] : null
|
|
48
|
+
const allowed = enrollableAudiences(repoPolicy)
|
|
49
|
+
if (!allowed || allowed.includes(audience)) continue
|
|
50
|
+
// The message names the allowed audiences itself: adopt prints only the
|
|
51
|
+
// message of a refusal, not its hint.
|
|
52
|
+
const kind = typeof repoPolicy.kind === 'string' ? `${repoPolicy.kind.replaceAll('_', ' ')} ` : ''
|
|
53
|
+
const allows = allowed.length ? `it allows ${allowed.join(', ')}` : 'it allows no audience there'
|
|
54
|
+
throw new AtelierDiagnosticError(
|
|
55
|
+
'enroll-audience-not-allowed',
|
|
56
|
+
`the boundary policy does not allow audience ${audience} in ${kind}repository ${repoName}; ${allows}`,
|
|
57
|
+
{
|
|
58
|
+
hint: allowed.length
|
|
59
|
+
? 'Pass --audience with one of the audiences it allows.'
|
|
60
|
+
: 'Review boundary-policy.v1.json before enrolling documents in that repository.',
|
|
61
|
+
},
|
|
62
|
+
)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const idSegment = (value) =>
|
|
67
|
+
String(value ?? '')
|
|
68
|
+
.toLowerCase()
|
|
69
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
70
|
+
.replace(/^-+|-+$/g, '')
|
|
71
|
+
|
|
72
|
+
const pathSegment = (rel) =>
|
|
73
|
+
String(rel)
|
|
74
|
+
.toLowerCase()
|
|
75
|
+
.replace(/[^a-z0-9._/-]+/g, '-')
|
|
76
|
+
.replace(/-+([./])/g, '$1')
|
|
77
|
+
.replace(/([./])-+/g, '$1')
|
|
78
|
+
.replace(/-{2,}/g, '-')
|
|
79
|
+
|
|
80
|
+
const shortDigest = (value) => createHash('sha256').update(value).digest('hex').slice(0, 8)
|
|
81
|
+
|
|
82
|
+
// `<repo>:asset:<path>` keeps the source's folders and file name readable in
|
|
83
|
+
// the id. Two documents whose paths fold to one id (case, spaces) each get a
|
|
84
|
+
// digest of their exact path, as does one whose id is already taken, so the
|
|
85
|
+
// result does not depend on the order documents are found in.
|
|
86
|
+
function assignIds(wanted, taken) {
|
|
87
|
+
const bases = wanted.map((item) => `${idSegment(item.repo) || 'repo'}:asset:${pathSegment(item.path)}`)
|
|
88
|
+
const counts = new Map()
|
|
89
|
+
for (const base of bases) counts.set(base, (counts.get(base) ?? 0) + 1)
|
|
90
|
+
const used = new Set(taken)
|
|
91
|
+
return wanted.map((item, index) => {
|
|
92
|
+
const base = bases[index]
|
|
93
|
+
const id = counts.get(base) > 1 || used.has(base) ? `${base}-${shortDigest(`${item.repo}/${item.path}`)}` : base
|
|
94
|
+
if (used.has(id)) throw new Error('document enrollment could not derive a unique kg.id')
|
|
95
|
+
used.add(id)
|
|
96
|
+
return id
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function sidecarDocument(node, id, audience) {
|
|
101
|
+
return {
|
|
102
|
+
schema: SOURCE_SIDECAR_SCHEMA,
|
|
103
|
+
asset: path.posix.basename(node.path),
|
|
104
|
+
title: node.title || path.posix.basename(node.path),
|
|
105
|
+
summary: '',
|
|
106
|
+
tags: [],
|
|
107
|
+
kg: {
|
|
108
|
+
id,
|
|
109
|
+
type: node.extension,
|
|
110
|
+
domain: node.domain,
|
|
111
|
+
lifecycle: node.lifecycle,
|
|
112
|
+
status: node.status,
|
|
113
|
+
audience,
|
|
114
|
+
relations: {},
|
|
115
|
+
},
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// What enrollment would write, and what it leaves alone. `policy` defaults to
|
|
120
|
+
// the project's boundary policy; a missing or unreadable one is not consulted,
|
|
121
|
+
// since the graph itself does not need it.
|
|
122
|
+
export function planDocumentEnrollment(project, { audience = DEFAULT_ENROLLMENT_AUDIENCE, policy = undefined } = {}) {
|
|
123
|
+
assertEnrollmentAudience(audience)
|
|
124
|
+
const { options } = canonicalGraphOptions(project)
|
|
125
|
+
const result = buildKnowledgeGraph(options)
|
|
126
|
+
if (!result.workspaceGraph) {
|
|
127
|
+
throw new AtelierDiagnosticError('enroll-census-unavailable', 'the knowledge graph census could not run, so no document was enrolled', {
|
|
128
|
+
hint: 'Run atelier graph with the same --project path to see why, fix that, then retry.',
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
const roots = new Map(options.repoEntries.map((entry) => [entry.name, entry.path]))
|
|
132
|
+
const nodes = result.workspaceGraph.nodes
|
|
133
|
+
// Code-point order, so the report reads the same under every locale.
|
|
134
|
+
const byPath = (a, b) => (`${a.repo}/${a.path}` < `${b.repo}/${b.path}` ? -1 : 1)
|
|
135
|
+
const demanded = nodes.filter((node) => node.extension !== 'md' && !node.hasSidecar).sort(byPath)
|
|
136
|
+
|
|
137
|
+
const wanted = []
|
|
138
|
+
const skipped = []
|
|
139
|
+
for (const node of demanded) {
|
|
140
|
+
const sidecar = `${node.path}.kg.json`
|
|
141
|
+
const target = path.join(roots.get(node.repo), ...sidecar.split('/'))
|
|
142
|
+
const existing = fs.lstatSync(target, { throwIfNoEntry: false })
|
|
143
|
+
// A sidecar that exists but is not visible to the census is Git-ignored,
|
|
144
|
+
// or is a link or folder. Either way it is someone's, and stays as it is.
|
|
145
|
+
if (existing) {
|
|
146
|
+
skipped.push({ repo: node.repo, path: node.path, sidecar, reason: existing.isFile() ? 'sidecar-ignored' : 'sidecar-not-a-file' })
|
|
147
|
+
continue
|
|
148
|
+
}
|
|
149
|
+
wanted.push({ repo: node.repo, path: node.path, sidecar, target, node })
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (wanted.length) {
|
|
153
|
+
const loaded = policy === undefined ? loadBoundaryPolicy(project) : { ok: Boolean(policy), policy }
|
|
154
|
+
if (loaded.ok) assertAudienceAllowed({ policy: loaded.policy, repoNames: [...new Set(wanted.map((item) => item.repo))], audience })
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const enrolling = new Set(wanted.map((item) => item.node))
|
|
158
|
+
const ids = assignIds(wanted, nodes.filter((node) => !enrolling.has(node)).map((node) => node.id))
|
|
159
|
+
const enroll = wanted.map((item, index) => {
|
|
160
|
+
const document = sidecarDocument(item.node, ids[index], audience)
|
|
161
|
+
const errors = validateSourceSidecar(document, document.asset)
|
|
162
|
+
if (errors.length) throw new Error(`document enrollment built an invalid sidecar: ${errors.join('; ')}`)
|
|
163
|
+
return { repo: item.repo, path: item.path, sidecar: item.sidecar, target: item.target, id: ids[index], document }
|
|
164
|
+
})
|
|
165
|
+
return { audience, enroll, skipped }
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Writes each planned sidecar with an exclusive create, so a file, link or
|
|
169
|
+
// folder that appeared since the plan is never replaced or followed; it is
|
|
170
|
+
// reported as skipped instead. Sources are never opened.
|
|
171
|
+
export function writeDocumentEnrollment(plan) {
|
|
172
|
+
const written = []
|
|
173
|
+
const skipped = [...plan.skipped]
|
|
174
|
+
for (const item of plan.enroll) {
|
|
175
|
+
try {
|
|
176
|
+
fs.writeFileSync(item.target, `${JSON.stringify(item.document, null, 2)}\n`, { flag: 'wx' })
|
|
177
|
+
written.push(item)
|
|
178
|
+
} catch (error) {
|
|
179
|
+
if (error?.code !== 'EEXIST') throw error
|
|
180
|
+
skipped.push({ repo: item.repo, path: item.path, sidecar: item.sidecar, reason: 'sidecar-exists' })
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return { audience: plan.audience, written, skipped }
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const SKIP_REASONS = {
|
|
187
|
+
'sidecar-ignored': 'a sidecar exists but Git ignores it',
|
|
188
|
+
'sidecar-not-a-file': 'a link or folder has the sidecar name',
|
|
189
|
+
'sidecar-exists': 'a sidecar appeared while enrolling',
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export function enrollmentReport({ written = [], skipped = [], audience, dryRun = false }) {
|
|
193
|
+
return {
|
|
194
|
+
ok: true,
|
|
195
|
+
command: 'enroll documents',
|
|
196
|
+
dryRun,
|
|
197
|
+
audience,
|
|
198
|
+
enrolled: written.map((item) => ({ repo: item.repo, path: item.path, sidecar: item.sidecar, id: item.id })),
|
|
199
|
+
skipped: skipped.map((item) => ({ repo: item.repo, path: item.path, sidecar: item.sidecar, reason: item.reason })),
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function printReport(report) {
|
|
204
|
+
const count = report.enrolled.length
|
|
205
|
+
const noun = count === 1 ? 'document' : 'documents'
|
|
206
|
+
if (count === 0) console.log('No document needs a sidecar.')
|
|
207
|
+
else console.log(`${report.dryRun ? 'Would enroll' : 'Enrolled'} ${count} ${noun} with ${report.audience} sidecars:`)
|
|
208
|
+
for (const item of report.enrolled) console.log(` ${item.repo}/${item.sidecar}`)
|
|
209
|
+
if (report.skipped.length) {
|
|
210
|
+
console.log(`Left ${report.skipped.length} unchanged; the graph still needs a sidecar for each:`)
|
|
211
|
+
for (const item of report.skipped) console.log(` ${item.repo}/${item.path}: ${SKIP_REASONS[item.reason] ?? item.reason}`)
|
|
212
|
+
}
|
|
213
|
+
if (count && !report.dryRun) {
|
|
214
|
+
console.log('Next: review the sidecars (title, audience), then run atelier graph with the same --project path.')
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const usage = () =>
|
|
219
|
+
new AtelierDiagnosticError('usage', 'enroll has one subcommand, documents', {
|
|
220
|
+
hint: 'Run atelier enroll documents [--audience private] [--dry-run] [--json] [--project ./atelier.project.json].',
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
export function parseEnrollArgs(argv = []) {
|
|
224
|
+
const { remaining } = parseProjectOptions(argv)
|
|
225
|
+
const args = { subcommand: null, audience: DEFAULT_ENROLLMENT_AUDIENCE, dryRun: false, json: false }
|
|
226
|
+
for (let index = 0; index < remaining.length; index += 1) {
|
|
227
|
+
const arg = remaining[index]
|
|
228
|
+
if (arg === '--dry-run') args.dryRun = true
|
|
229
|
+
else if (arg === '--json') args.json = true
|
|
230
|
+
else if (arg === '--audience') args.audience = remaining[++index]
|
|
231
|
+
else if (arg.startsWith('--audience=')) args.audience = arg.slice('--audience='.length)
|
|
232
|
+
else if (!arg.startsWith('-') && args.subcommand === null) args.subcommand = arg
|
|
233
|
+
else throw usage()
|
|
234
|
+
}
|
|
235
|
+
if (args.subcommand !== 'documents') throw usage()
|
|
236
|
+
assertEnrollmentAudience(args.audience)
|
|
237
|
+
return args
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export function runEnrollCommand(argv = process.argv.slice(2)) {
|
|
241
|
+
const args = parseEnrollArgs(argv)
|
|
242
|
+
const project = commandProject({ argv })
|
|
243
|
+
const plan = planDocumentEnrollment(project, { audience: args.audience })
|
|
244
|
+
const outcome = args.dryRun ? { audience: plan.audience, written: plan.enroll, skipped: plan.skipped } : writeDocumentEnrollment(plan)
|
|
245
|
+
const report = enrollmentReport({ ...outcome, dryRun: args.dryRun })
|
|
246
|
+
if (args.json) console.log(JSON.stringify(report, null, 2))
|
|
247
|
+
else printReport(report)
|
|
248
|
+
}
|
package/src/graph/graph.mjs
CHANGED
|
@@ -5,9 +5,10 @@ import { VALID_AUDIENCES as PROJECTION_AUDIENCES } from '../projection/policy.mj
|
|
|
5
5
|
import {
|
|
6
6
|
REPO_ACCESS_SCHEMA,
|
|
7
7
|
VALID_RELATION_TYPES,
|
|
8
|
-
|
|
8
|
+
buildKnowledgeGraphSteps,
|
|
9
9
|
createGraphFileCache,
|
|
10
10
|
parseYamlSubset,
|
|
11
|
+
runGraphSteps,
|
|
11
12
|
} from './knowledge-graph.mjs'
|
|
12
13
|
|
|
13
14
|
export { createGraphFileCache }
|
|
@@ -19,6 +20,18 @@ export const VALID_AUDIENCES = PROJECTION_AUDIENCES
|
|
|
19
20
|
export const VALID_RELATIONS = VALID_RELATION_TYPES
|
|
20
21
|
export const parseFrontmatterYaml = parseYamlSubset
|
|
21
22
|
|
|
23
|
+
// setup.include and setup.exclude scope the census. Both are path patterns
|
|
24
|
+
// relative to the project config's folder, so each repository learns where its
|
|
25
|
+
// root sits relative to that folder.
|
|
26
|
+
function censusScope(project, repoPath) {
|
|
27
|
+
const setup = project.config?.setup
|
|
28
|
+
const include = typeof setup?.include === 'string' && setup.include.trim() ? setup.include : null
|
|
29
|
+
const exclude = typeof setup?.exclude === 'string' && setup.exclude.trim() ? setup.exclude : null
|
|
30
|
+
if (!include && !exclude) return null
|
|
31
|
+
const base = path.relative(project.configDir, repoPath).split(path.sep).join('/')
|
|
32
|
+
return { base, include, exclude }
|
|
33
|
+
}
|
|
34
|
+
|
|
22
35
|
function canonicalInput(project) {
|
|
23
36
|
const loadedAccess = loadRepoAccess(project)
|
|
24
37
|
const managed = []
|
|
@@ -31,7 +44,8 @@ function canonicalInput(project) {
|
|
|
31
44
|
missing.push(`configured repo is missing or unreadable: ${repo.name ?? '(unnamed)'}`)
|
|
32
45
|
continue
|
|
33
46
|
}
|
|
34
|
-
|
|
47
|
+
const scope = censusScope(project, repo.path)
|
|
48
|
+
managed.push({ name: repo.name, path: repo.path, ...(scope ? { censusScope: scope } : {}) })
|
|
35
49
|
accessRepos[repo.name] = {
|
|
36
50
|
readBoundary:
|
|
37
51
|
loadedAccess.repos?.[repo.name]?.readBoundary ?? repo.readBoundary ?? loadedAccess.defaultReadBoundary ?? 'team',
|
|
@@ -103,11 +117,12 @@ function externalRepos(project) {
|
|
|
103
117
|
.sort((a, b) => String(a.name).localeCompare(String(b.name)))
|
|
104
118
|
}
|
|
105
119
|
|
|
106
|
-
function
|
|
120
|
+
function* canonicalBuildSteps(project, overrides = {}) {
|
|
107
121
|
const input = canonicalInput(project)
|
|
108
|
-
const result =
|
|
122
|
+
const result = yield* buildKnowledgeGraphSteps({ ...input.options, ...overrides })
|
|
109
123
|
return { input, result }
|
|
110
124
|
}
|
|
125
|
+
const canonicalBuild = (project, overrides = {}) => runGraphSteps(canonicalBuildSteps(project, overrides))
|
|
111
126
|
|
|
112
127
|
// The supported project-to-options seam: the exact buildKnowledgeGraph options
|
|
113
128
|
// every graph consumer derives from a loaded project, plus the configured
|
|
@@ -131,8 +146,14 @@ const canonicalEdgeId = (edge) => JSON.stringify([edge.source, edge.type, edge.t
|
|
|
131
146
|
// bytes did not change; the result is the same as without it. `observedDigest`
|
|
132
147
|
// (see createGraphFileCache) lets a caller that observes sources by digest
|
|
133
148
|
// spare the read of a source it knows unchanged.
|
|
134
|
-
|
|
135
|
-
|
|
149
|
+
// buildCanonicalGraphSteps is the same build as steps (see buildKnowledgeGraphSteps), for a caller that waits between
|
|
150
|
+
// them; buildCanonicalGraph runs them through.
|
|
151
|
+
export function buildCanonicalGraph(project, options = {}) {
|
|
152
|
+
return runGraphSteps(buildCanonicalGraphSteps(project, options))
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function* buildCanonicalGraphSteps(project, { isLinkTargetEligible, isAssetEligible, fileCache = null, observedDigest = null } = {}) {
|
|
156
|
+
const { input, result } = yield* canonicalBuildSteps(project, {
|
|
136
157
|
...(isLinkTargetEligible ? { isLinkTargetEligible } : {}),
|
|
137
158
|
...(isAssetEligible ? { isAssetEligible } : {}),
|
|
138
159
|
...(fileCache ? { fileCache } : {}),
|
|
@@ -211,6 +232,9 @@ export function runGraphCommand(argv = process.argv.slice(2)) {
|
|
|
211
232
|
for (const warning of ignoredSidecarWarnings(project)) console.warn(`warning: ${warning.message}`)
|
|
212
233
|
if (graph.errors.length) {
|
|
213
234
|
console.error(graph.errors.join('\n'))
|
|
235
|
+
if (graph.errors.some((error) => error.includes(': non-Markdown source requires sidecar '))) {
|
|
236
|
+
console.error('Next: run atelier enroll documents with the same --project path to write private sidecars for documents that have none, review them, then rerun atelier graph.')
|
|
237
|
+
}
|
|
214
238
|
process.exit(1)
|
|
215
239
|
}
|
|
216
240
|
if (check) {
|
|
@@ -4,6 +4,7 @@ import path from 'node:path'
|
|
|
4
4
|
import { VALID_AUDIENCES } from '../projection/policy.mjs'
|
|
5
5
|
import { generatedProjectionBasenames, generatedProjectionDirectoryBasenames } from '../project/file-class.mjs'
|
|
6
6
|
import { gitIgnoreFilter } from '../project/git-ignore.mjs'
|
|
7
|
+
import { matchesPathPattern, normalizeRelPath } from '../project/path-match.mjs'
|
|
7
8
|
|
|
8
9
|
export const KNOWLEDGE_GRAPH_SCHEMA = 'mnstry.knowledge-graph@v1'
|
|
9
10
|
export const REPO_ACCESS_SCHEMA = 'mnstry.repo-access@v1'
|
|
@@ -339,14 +340,58 @@ export function listRepos(workspaceRoot) {
|
|
|
339
340
|
.sort()
|
|
340
341
|
}
|
|
341
342
|
|
|
343
|
+
// The project's census scope: `include` and `exclude` are path patterns in the
|
|
344
|
+
// kit's one dialect (src/project/path-match.mjs), relative to the project
|
|
345
|
+
// config's folder, read from the tracked config so every machine walks the
|
|
346
|
+
// same paths. `base` is the repository root relative to that folder. The
|
|
347
|
+
// returned predicate takes a repository-relative path and says whether it is
|
|
348
|
+
// outside the census; `directory` lets a folder that may still hold included
|
|
349
|
+
// files be walked. A path is excluded when it or a folder above it matches
|
|
350
|
+
// `exclude`; with `include`, a file is in the census only when it or a folder
|
|
351
|
+
// above it matches. Exclude wins. A `<file>.kg.json` sidecar belongs to its
|
|
352
|
+
// source and shares the source's scope. Null when the scope restricts nothing.
|
|
353
|
+
export function censusScopeFilter({ base = '', include = null, exclude = null } = {}) {
|
|
354
|
+
// A trailing slash names a folder, which a pattern without one matches too.
|
|
355
|
+
const pattern = (value) => (typeof value === 'string' && value.trim() ? normalizeRelPath(value.trim()).replace(/\/+$/, '') || null : null)
|
|
356
|
+
const includePattern = pattern(include)
|
|
357
|
+
const excludePattern = pattern(exclude)
|
|
358
|
+
if (!includePattern && !excludePattern) return null
|
|
359
|
+
const prefix = normalizeRelPath(base).replace(/\/+$/, '')
|
|
360
|
+
const lineage = (rel) => {
|
|
361
|
+
const parts = path.posix.normalize(prefix ? `${prefix}/${rel}` : rel).split('/')
|
|
362
|
+
return parts.map((_, index) => parts.slice(0, index + 1).join('/'))
|
|
363
|
+
}
|
|
364
|
+
// A folder may lead to an included file when it lies on the pattern's
|
|
365
|
+
// literal leading path, or below it. A slashless pattern matches at any
|
|
366
|
+
// depth, so every folder may.
|
|
367
|
+
const segments = includePattern?.includes('/') ? includePattern.split('/') : null
|
|
368
|
+
const globAt = segments ? segments.findIndex((part) => part.includes('*')) : -1
|
|
369
|
+
const literalLead = segments ? (globAt === -1 ? segments : segments.slice(0, globAt)) : null
|
|
370
|
+
const mayLeadToInclude = (paths) => {
|
|
371
|
+
if (!literalLead) return true
|
|
372
|
+
const parts = paths.at(-1).split('/')
|
|
373
|
+
const shared = Math.min(parts.length, literalLead.length)
|
|
374
|
+
return parts.slice(0, shared).every((part, index) => part.toLowerCase() === literalLead[index].toLowerCase())
|
|
375
|
+
}
|
|
376
|
+
return (rel, { directory = false } = {}) => {
|
|
377
|
+
const paths = lineage(!directory && rel.endsWith('.kg.json') ? rel.slice(0, -'.kg.json'.length) : rel)
|
|
378
|
+
if (excludePattern && paths.some((candidate) => matchesPathPattern(excludePattern, candidate))) return true
|
|
379
|
+
if (!includePattern || paths.some((candidate) => matchesPathPattern(includePattern, candidate))) return false
|
|
380
|
+
return !(directory && mayLeadToInclude(paths))
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
342
384
|
// Git-ignored paths are machine-local. Counting them makes committed graph
|
|
343
385
|
// artifacts differ per machine, which turns every sync tick into a conflict.
|
|
386
|
+
// `isIgnored(rel, { directory })` also carries the project's census scope when
|
|
387
|
+
// the builder composes one in (censusScopeFilter); a plain Git filter ignores
|
|
388
|
+
// the second argument.
|
|
344
389
|
export function walkDocuments(dir, root, acc = [], isIgnored = gitIgnoreFilter(root)) {
|
|
345
390
|
for (const ent of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
346
391
|
if (SKIP_DIRS.has(ent.name) || ent.name.startsWith('.')) continue
|
|
347
392
|
const abs = path.join(dir, ent.name)
|
|
348
393
|
const rel = relPath(root, abs)
|
|
349
|
-
if (isIgnored(rel)) continue
|
|
394
|
+
if (isIgnored(rel, { directory: ent.isDirectory() })) continue
|
|
350
395
|
if (ent.isDirectory()) {
|
|
351
396
|
walkDocuments(abs, root, acc, isIgnored)
|
|
352
397
|
} else if (ent.isFile()) {
|
|
@@ -384,7 +429,7 @@ export function ignoredSourceSidecars(repoName, repoRoot, isIgnored = gitIgnoreF
|
|
|
384
429
|
const abs = path.join(dir, ent.name)
|
|
385
430
|
const rel = relPath(repoRoot, abs)
|
|
386
431
|
if (ent.isDirectory()) {
|
|
387
|
-
if (!isIgnored(rel)) visit(abs)
|
|
432
|
+
if (!isIgnored(rel, { directory: true })) visit(abs)
|
|
388
433
|
continue
|
|
389
434
|
}
|
|
390
435
|
if (!ent.isFile() || !ent.name.endsWith('.kg.json') || !isIgnored(rel)) continue
|
|
@@ -402,7 +447,7 @@ export function walkSidecars(dir, root, acc = [], isIgnored = gitIgnoreFilter(ro
|
|
|
402
447
|
for (const ent of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
403
448
|
if (SKIP_DIRS.has(ent.name) || ent.name.startsWith('.')) continue
|
|
404
449
|
const abs = path.join(dir, ent.name)
|
|
405
|
-
if (isIgnored(relPath(root, abs))) continue
|
|
450
|
+
if (isIgnored(relPath(root, abs), { directory: ent.isDirectory() })) continue
|
|
406
451
|
if (ent.isDirectory()) {
|
|
407
452
|
walkSidecars(abs, root, acc, isIgnored)
|
|
408
453
|
} else if (ent.isFile() && ent.name.endsWith('.kg.json')) {
|
|
@@ -898,7 +943,7 @@ const isMarkdownPath = (rel) => rel.toLowerCase().endsWith('.md')
|
|
|
898
943
|
// a path is tested, not the path alone.
|
|
899
944
|
function ignoredAtAnyDepth(isIgnored, rel) {
|
|
900
945
|
const parts = rel.split('/')
|
|
901
|
-
return parts.some((_, index) => isIgnored(parts.slice(0, index + 1).join('/')))
|
|
946
|
+
return parts.some((_, index) => isIgnored(parts.slice(0, index + 1).join('/'), { directory: index < parts.length - 1 }))
|
|
902
947
|
}
|
|
903
948
|
|
|
904
949
|
// True only for a regular file whose real location is exactly `rel` under the
|
|
@@ -979,7 +1024,7 @@ export function resolveWorkspaceLinks({ repos = [], isLinkTargetEligible = () =>
|
|
|
979
1024
|
if (ent.name === '.git') continue
|
|
980
1025
|
const abs = path.join(dir, ent.name)
|
|
981
1026
|
const rel = relPath(owner.root, abs)
|
|
982
|
-
if (isIgnored(rel)) continue
|
|
1027
|
+
if (isIgnored(rel, { directory: ent.isDirectory() })) continue
|
|
983
1028
|
// A nested enrolled repository owns its own files.
|
|
984
1029
|
if (ent.isDirectory()) {
|
|
985
1030
|
if (!roots.has(abs)) visit(abs)
|
|
@@ -1342,7 +1387,24 @@ function markdownCensus({ repoName, repoRoot, coverage, file, accessConfig, cach
|
|
|
1342
1387
|
return { node: structuredClone(entry.node), scan: structuredClone(entry.scan), reused: entry === cached, read }
|
|
1343
1388
|
}
|
|
1344
1389
|
|
|
1345
|
-
|
|
1390
|
+
// Runs the steps of a graph build through, with no wait, and answers what the build answers.
|
|
1391
|
+
export function runGraphSteps(steps) {
|
|
1392
|
+
for (;;) {
|
|
1393
|
+
const step = steps.next()
|
|
1394
|
+
if (step.done) return step.value
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
// The knowledge graph of a workspace, in one stretch. Every caller but the maintenance engine's tick builds it this way.
|
|
1399
|
+
export function buildKnowledgeGraph(options) {
|
|
1400
|
+
return runGraphSteps(buildKnowledgeGraphSteps(options))
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
// The same build as steps: one after each Markdown source of the census (its read, hash and parse, or its reuse from
|
|
1404
|
+
// the file cache), named 'graph-census'. A caller that gives the event loop a turn between steps (the maintenance
|
|
1405
|
+
// engine) keeps a large census from holding it; walking the directories, resolving links and assembling the graph
|
|
1406
|
+
// are not split. The answer is the one buildKnowledgeGraph gives.
|
|
1407
|
+
export function* buildKnowledgeGraphSteps({
|
|
1346
1408
|
workspaceRoot,
|
|
1347
1409
|
repoAccessConfig,
|
|
1348
1410
|
repoRoots = null,
|
|
@@ -1362,7 +1424,9 @@ export function buildKnowledgeGraph({
|
|
|
1362
1424
|
const resolvedWorkspaceRoot = path.resolve(workspaceRoot)
|
|
1363
1425
|
const external = new Set(externalRepos)
|
|
1364
1426
|
const discoveredEntries = repoEntries
|
|
1365
|
-
? repoEntries
|
|
1427
|
+
? repoEntries
|
|
1428
|
+
.map((entry) => ({ name: entry.name, path: path.resolve(entry.path), ...(entry.censusScope ? { censusScope: entry.censusScope } : {}) }))
|
|
1429
|
+
.sort((a, b) => String(a.name).localeCompare(String(b.name)))
|
|
1366
1430
|
: (repoRoots ? repoRoots.map((repoRoot) => path.resolve(repoRoot)) : listRepos(resolvedWorkspaceRoot))
|
|
1367
1431
|
.sort()
|
|
1368
1432
|
.map((repoRoot) => ({ name: path.basename(repoRoot), path: repoRoot }))
|
|
@@ -1398,8 +1462,11 @@ export function buildKnowledgeGraph({
|
|
|
1398
1462
|
for (const entry of roots) {
|
|
1399
1463
|
const repoRoot = entry.path
|
|
1400
1464
|
const repoName = entry.name
|
|
1401
|
-
// One batched ignore lookup per repo, shared by every walk below.
|
|
1402
|
-
|
|
1465
|
+
// One batched ignore lookup per repo, shared by every walk below. A path
|
|
1466
|
+
// outside the project's census scope is treated exactly as an ignored one.
|
|
1467
|
+
const gitIgnored = gitIgnoreFilter(repoRoot)
|
|
1468
|
+
const outOfScope = censusScopeFilter(entry.censusScope ?? {})
|
|
1469
|
+
const isIgnored = outOfScope ? (rel, info) => gitIgnored(rel) || outOfScope(rel, info) : gitIgnored
|
|
1403
1470
|
const files = walkDocuments(repoRoot, repoRoot, [], isIgnored)
|
|
1404
1471
|
workspaceOrphanSidecars.push(...activeOrphanSidecars(repoName, repoRoot, files, isIgnored))
|
|
1405
1472
|
workspaceIgnoredSidecars.push(...ignoredSourceSidecars(repoName, repoRoot, isIgnored))
|
|
@@ -1416,6 +1483,7 @@ export function buildKnowledgeGraph({
|
|
|
1416
1483
|
scanned.set(node, entry.scan)
|
|
1417
1484
|
reuse[entry.reused ? 'reused' : 'derived'] += 1
|
|
1418
1485
|
if (entry.read) reuse.read += 1
|
|
1486
|
+
yield 'graph-census'
|
|
1419
1487
|
} else {
|
|
1420
1488
|
node = nodeForFile(repoName, repoRoot, coverage, file, accessConfig)
|
|
1421
1489
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import Ajv from 'ajv/dist/2020.js'
|
|
3
|
+
import addFormats from 'ajv-formats'
|
|
4
|
+
import { objectDigest, digest } from '../capabilities/files.mjs'
|
|
5
|
+
export const HARNESS_LIMITS = Object.freeze({ records: 256, bytes: 8 * 1024 * 1024, dependencyVerifications: 256, dependencyDepth: 12 })
|
|
6
|
+
export const harnessDigest = objectDigest
|
|
7
|
+
export const contentDigest = digest
|
|
8
|
+
export const harnessRef = record => ({ id: record.id, digest: objectDigest(record) })
|
|
9
|
+
export const EMPTY_HARNESS_HEAD = objectDigest([])
|
|
10
|
+
const ajv = new Ajv({ allErrors: true, strict: true }); addFormats(ajv)
|
|
11
|
+
const schemas = new Map(['harness', 'knowledge', 'build'].map(name => [name, JSON.parse(fs.readFileSync(new URL(`../../contracts/atelier-${name}.v1.schema.json`, import.meta.url)))]))
|
|
12
|
+
const validators = new Map()
|
|
13
|
+
export function validateHarnessDocument(value, profile, shape = 'record') {
|
|
14
|
+
const schema = schemas.get(profile), key = `${profile}/${shape}`
|
|
15
|
+
if (!schema || !Object.hasOwn(schema.$defs, shape)) return ['unknown harness profile or shape']
|
|
16
|
+
if (!validators.has(key)) validators.set(key, ajv.compile({ $schema: schema.$schema, $defs: schema.$defs, $ref: `#/$defs/${shape}` }))
|
|
17
|
+
const validate = validators.get(key)
|
|
18
|
+
return validate(value) ? [] : validate.errors.map(e => `${e.instancePath || '/'}: ${e.message}`)
|
|
19
|
+
}
|
|
20
|
+
export function assertHarness(value, profile, shape = 'record') {
|
|
21
|
+
const errors = validateHarnessDocument(value, profile, shape)
|
|
22
|
+
if (errors.length) throw new Error(`invalid ${profile} ${shape}: ${errors.slice(0, 5).join('; ')}`)
|
|
23
|
+
return value
|
|
24
|
+
}
|
|
25
|
+
// A private container may receive a public source. An export that includes that
|
|
26
|
+
// container's metadata must retain the more restrictive combined audience.
|
|
27
|
+
export function restrictiveAudience(...values) {
|
|
28
|
+
if (!values.length || values.some(v => !['public', 'team', 'operator', 'staff', 'private', 'sensitive'].includes(v))) throw new Error('unknown audience')
|
|
29
|
+
const restricted = [...new Set(values.filter(v => v !== 'public'))]
|
|
30
|
+
if (restricted.includes('sensitive')) return 'sensitive'
|
|
31
|
+
if (restricted.includes('private') || restricted.length > 1) return 'private'
|
|
32
|
+
return restricted[0] ?? 'public'
|
|
33
|
+
}
|
|
34
|
+
export function assertAudience(receiver, source) {
|
|
35
|
+
if (restrictiveAudience(receiver, source) !== receiver) throw new Error('audience exceeds receiving scope')
|
|
36
|
+
}
|
|
37
|
+
export function assertHandoff(value) {
|
|
38
|
+
assertHarness(value, 'harness', 'handoff')
|
|
39
|
+
if (contentDigest(value.payload) !== value.payloadDigest) throw new Error('handoff payload digest mismatch')
|
|
40
|
+
return value
|
|
41
|
+
}
|