@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,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
|
@@ -19,6 +19,18 @@ export const VALID_AUDIENCES = PROJECTION_AUDIENCES
|
|
|
19
19
|
export const VALID_RELATIONS = VALID_RELATION_TYPES
|
|
20
20
|
export const parseFrontmatterYaml = parseYamlSubset
|
|
21
21
|
|
|
22
|
+
// setup.include and setup.exclude scope the census. Both are path patterns
|
|
23
|
+
// relative to the project config's folder, so each repository learns where its
|
|
24
|
+
// root sits relative to that folder.
|
|
25
|
+
function censusScope(project, repoPath) {
|
|
26
|
+
const setup = project.config?.setup
|
|
27
|
+
const include = typeof setup?.include === 'string' && setup.include.trim() ? setup.include : null
|
|
28
|
+
const exclude = typeof setup?.exclude === 'string' && setup.exclude.trim() ? setup.exclude : null
|
|
29
|
+
if (!include && !exclude) return null
|
|
30
|
+
const base = path.relative(project.configDir, repoPath).split(path.sep).join('/')
|
|
31
|
+
return { base, include, exclude }
|
|
32
|
+
}
|
|
33
|
+
|
|
22
34
|
function canonicalInput(project) {
|
|
23
35
|
const loadedAccess = loadRepoAccess(project)
|
|
24
36
|
const managed = []
|
|
@@ -31,7 +43,8 @@ function canonicalInput(project) {
|
|
|
31
43
|
missing.push(`configured repo is missing or unreadable: ${repo.name ?? '(unnamed)'}`)
|
|
32
44
|
continue
|
|
33
45
|
}
|
|
34
|
-
|
|
46
|
+
const scope = censusScope(project, repo.path)
|
|
47
|
+
managed.push({ name: repo.name, path: repo.path, ...(scope ? { censusScope: scope } : {}) })
|
|
35
48
|
accessRepos[repo.name] = {
|
|
36
49
|
readBoundary:
|
|
37
50
|
loadedAccess.repos?.[repo.name]?.readBoundary ?? repo.readBoundary ?? loadedAccess.defaultReadBoundary ?? 'team',
|
|
@@ -211,6 +224,9 @@ export function runGraphCommand(argv = process.argv.slice(2)) {
|
|
|
211
224
|
for (const warning of ignoredSidecarWarnings(project)) console.warn(`warning: ${warning.message}`)
|
|
212
225
|
if (graph.errors.length) {
|
|
213
226
|
console.error(graph.errors.join('\n'))
|
|
227
|
+
if (graph.errors.some((error) => error.includes(': non-Markdown source requires sidecar '))) {
|
|
228
|
+
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.')
|
|
229
|
+
}
|
|
214
230
|
process.exit(1)
|
|
215
231
|
}
|
|
216
232
|
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)
|
|
@@ -1362,7 +1407,9 @@ export function buildKnowledgeGraph({
|
|
|
1362
1407
|
const resolvedWorkspaceRoot = path.resolve(workspaceRoot)
|
|
1363
1408
|
const external = new Set(externalRepos)
|
|
1364
1409
|
const discoveredEntries = repoEntries
|
|
1365
|
-
? repoEntries
|
|
1410
|
+
? repoEntries
|
|
1411
|
+
.map((entry) => ({ name: entry.name, path: path.resolve(entry.path), ...(entry.censusScope ? { censusScope: entry.censusScope } : {}) }))
|
|
1412
|
+
.sort((a, b) => String(a.name).localeCompare(String(b.name)))
|
|
1366
1413
|
: (repoRoots ? repoRoots.map((repoRoot) => path.resolve(repoRoot)) : listRepos(resolvedWorkspaceRoot))
|
|
1367
1414
|
.sort()
|
|
1368
1415
|
.map((repoRoot) => ({ name: path.basename(repoRoot), path: repoRoot }))
|
|
@@ -1398,8 +1445,11 @@ export function buildKnowledgeGraph({
|
|
|
1398
1445
|
for (const entry of roots) {
|
|
1399
1446
|
const repoRoot = entry.path
|
|
1400
1447
|
const repoName = entry.name
|
|
1401
|
-
// One batched ignore lookup per repo, shared by every walk below.
|
|
1402
|
-
|
|
1448
|
+
// One batched ignore lookup per repo, shared by every walk below. A path
|
|
1449
|
+
// outside the project's census scope is treated exactly as an ignored one.
|
|
1450
|
+
const gitIgnored = gitIgnoreFilter(repoRoot)
|
|
1451
|
+
const outOfScope = censusScopeFilter(entry.censusScope ?? {})
|
|
1452
|
+
const isIgnored = outOfScope ? (rel, info) => gitIgnored(rel) || outOfScope(rel, info) : gitIgnored
|
|
1403
1453
|
const files = walkDocuments(repoRoot, repoRoot, [], isIgnored)
|
|
1404
1454
|
workspaceOrphanSidecars.push(...activeOrphanSidecars(repoName, repoRoot, files, isIgnored))
|
|
1405
1455
|
workspaceIgnoredSidecars.push(...ignoredSourceSidecars(repoName, repoRoot, isIgnored))
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { inspectInquiry } from '../inquiry/index.mjs'
|
|
2
|
+
import { inspectKnowledge } from '../knowledge/ledger.mjs'
|
|
3
|
+
import { inspectBuild } from '../build/ledger.mjs'
|
|
4
|
+
import { assertHandoff, contentDigest, harnessDigest, harnessRef, restrictiveAudience, HARNESS_LIMITS } from './contracts.mjs'
|
|
5
|
+
import { canonical } from '../capabilities/files.mjs'
|
|
6
|
+
import { createHash } from 'node:crypto'
|
|
7
|
+
|
|
8
|
+
export function inspectHarness(profile, records) {
|
|
9
|
+
if (profile === 'inquiry') return inspectInquiry(records)
|
|
10
|
+
if (profile === 'knowledge') return inspectKnowledge(records)
|
|
11
|
+
if (profile === 'build') return inspectBuild(records)
|
|
12
|
+
throw new Error('unknown harness profile')
|
|
13
|
+
}
|
|
14
|
+
function context(dependencySnapshots = [], verificationLimit = HARNESS_LIMITS.dependencyVerifications) {
|
|
15
|
+
if (!Array.isArray(dependencySnapshots) || dependencySnapshots.length > 64) throw new Error('dependency snapshot ceiling exceeded')
|
|
16
|
+
if (!Number.isSafeInteger(verificationLimit) || verificationLimit < 1 || verificationLimit > HARNESS_LIMITS.dependencyVerifications) throw new Error('invalid dependency verification limit')
|
|
17
|
+
return { snapshots: dependencySnapshots, inspections: new Map(), verified: new Map(), verificationLimit, verificationCalls: 0, historyReplays: 0, exhausted: false }
|
|
18
|
+
}
|
|
19
|
+
function inspect(profile, records, ctx) {
|
|
20
|
+
if (!ctx.inspections.has(profile)) ctx.inspections.set(profile, new Map())
|
|
21
|
+
const cache = ctx.inspections.get(profile)
|
|
22
|
+
if (!cache.has(records)) { cache.set(records, inspectHarness(profile, records)); ctx.historyReplays++ }
|
|
23
|
+
return cache.get(records)
|
|
24
|
+
}
|
|
25
|
+
const work = ctx => ({ verificationCalls: ctx.verificationCalls, historyReplays: ctx.historyReplays, verificationLimit: ctx.verificationLimit, exhausted: ctx.exhausted })
|
|
26
|
+
const bounded = depth => { if (depth > HARNESS_LIMITS.dependencyDepth) throw new Error('harness dependency depth exceeded') }
|
|
27
|
+
function artifact(profile, records, subjectId, ctx, depth) {
|
|
28
|
+
bounded(depth)
|
|
29
|
+
const state = inspect(profile, records, ctx), subject = records.find(r => r.id === subjectId)
|
|
30
|
+
if (!subject || state.reconsider.some(r => r.id === subjectId)) throw new Error('handoff subject is missing or requires reconsideration')
|
|
31
|
+
let audience, context
|
|
32
|
+
if (profile === 'knowledge') {
|
|
33
|
+
if (subject.kind !== 'contribution' || !state.accepted.includes(subject.id)) throw new Error('knowledge handoff requires an accepted contribution')
|
|
34
|
+
if (knowledgeFreshness(records, ctx, depth, [subject.id]).reconsider.some(r => r.id === subject.id)) throw new Error('knowledge import requires current source snapshots')
|
|
35
|
+
const review = records.findLast(r => r.kind === 'review' && r.data.target.id === subject.id)
|
|
36
|
+
context = [state.domain, review, ...review.data.evaluations.map(pin => records.find(r => r.id === pin.id))]
|
|
37
|
+
audience = restrictiveAudience(subject.data.audience, state.domain.data.audience)
|
|
38
|
+
} else if (profile === 'inquiry') {
|
|
39
|
+
if (subject.kind !== 'decision' || subject.data.disposition !== 'accepted') throw new Error('inquiry handoff requires an accepted decision')
|
|
40
|
+
const assessment = records.find(r => r.id === subject.data.assessment.id)
|
|
41
|
+
context = [records.find(r => r.id === assessment.data.hypothesis.id), assessment]
|
|
42
|
+
audience = state.campaign.data.audience
|
|
43
|
+
} else {
|
|
44
|
+
if (subject.kind !== 'candidate' || state.candidate?.id !== subject.id || !readiness(records, ctx, depth).readyReported) throw new Error('build handoff requires current accepted candidate and fresh dependencies')
|
|
45
|
+
context = [state.establishment, state.decision, ...state.gates]
|
|
46
|
+
audience = state.establishment.data.audience
|
|
47
|
+
}
|
|
48
|
+
return { state, audience, subject, payload: JSON.stringify({ subject, context, assurance: 'caller-reported; acceptance-is-not-truth-or-authority' }) }
|
|
49
|
+
}
|
|
50
|
+
function create({ profile, repository, records, subjectId, target }, ctx, depth) {
|
|
51
|
+
const { state, audience, subject, payload } = artifact(profile, records, subjectId, ctx, depth)
|
|
52
|
+
if (profile !== 'inquiry' && state.establishment.data.repository !== repository) throw new Error('handoff repository differs from establishment')
|
|
53
|
+
const repositoryBinding = profile === 'inquiry' ? 'caller-declared' : 'establishment-record'
|
|
54
|
+
return assertHandoff({ schema: 'atelier-harness-handoff@v1', source: { repository, repositoryBinding, profile, run: records[0].id, historyDigest: state.head, subject: harnessRef(subject) }, target, audience, payload, payloadDigest: contentDigest(payload), assurance: 'caller-reported', authority: 'none' })
|
|
55
|
+
}
|
|
56
|
+
function verify({ handoff, records }, ctx, depth) {
|
|
57
|
+
bounded(depth)
|
|
58
|
+
if (ctx.verificationCalls >= ctx.verificationLimit) { ctx.exhausted = true; throw new Error('harness dependency verification budget exhausted') }
|
|
59
|
+
ctx.verificationCalls++
|
|
60
|
+
assertHandoff(handoff)
|
|
61
|
+
if (!ctx.verified.has(records)) ctx.verified.set(records, new Map())
|
|
62
|
+
const cache = ctx.verified.get(records), key = harnessDigest(handoff), prior = cache.get(key)
|
|
63
|
+
if (prior?.active) throw new Error('harness dependency cycle detected')
|
|
64
|
+
if (prior?.result) return prior.result
|
|
65
|
+
cache.set(key, { active: true })
|
|
66
|
+
try {
|
|
67
|
+
inspect(handoff.source.profile, records, ctx)
|
|
68
|
+
const hash = createHash('sha256').update('[')
|
|
69
|
+
let extendsIssuedHistory = false
|
|
70
|
+
for (let i = 0; i < records.length; i++) {
|
|
71
|
+
if (i) hash.update(',')
|
|
72
|
+
hash.update(canonical(records[i]))
|
|
73
|
+
if (`sha256:${hash.copy().update(']').digest('hex')}` === handoff.source.historyDigest) extendsIssuedHistory = true
|
|
74
|
+
}
|
|
75
|
+
if (!extendsIssuedHistory) throw new Error('handoff source history changed or diverged')
|
|
76
|
+
const current = create({ ...handoff.source, records, subjectId: handoff.source.subject.id, target: handoff.target }, ctx, depth)
|
|
77
|
+
const observedHead = current.source.historyDigest
|
|
78
|
+
current.source.historyDigest = handoff.source.historyDigest
|
|
79
|
+
if (harnessDigest(current) !== harnessDigest(handoff)) throw new Error('handoff is stale or differs from its source history')
|
|
80
|
+
const result = { current: true, historyDigest: observedHead, issuedHistoryDigest: handoff.source.historyDigest, sourceRepositoryBinding: current.source.repositoryBinding, sourceAuthenticity: 'unverified', authority: 'none' }
|
|
81
|
+
cache.set(key, { result })
|
|
82
|
+
return result
|
|
83
|
+
} finally {
|
|
84
|
+
if (cache.get(key)?.active) cache.delete(key)
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function matching(h, snapshots) {
|
|
88
|
+
return snapshots.filter(s => s.repository === h.source.repository && s.profile === h.source.profile && s.records?.[0]?.id === h.source.run)
|
|
89
|
+
}
|
|
90
|
+
function knowledgeFreshness(records, ctx, depth, subjects = null) {
|
|
91
|
+
bounded(depth)
|
|
92
|
+
const state = inspect('knowledge', records, ctx), stale = new Map(state.reconsider.map(r => [r.id, r.reasons]))
|
|
93
|
+
const needed = subjects ? new Set(subjects) : null
|
|
94
|
+
if (needed) for (const record of [...records].reverse()) if (needed.has(record.id) && record.kind === 'contribution') for (const item of record.data.basedOn) needed.add(item.contribution.id)
|
|
95
|
+
for (const record of records) {
|
|
96
|
+
if (record.kind !== 'contribution' || record.data.origin.method !== 'exchange' || (needed && !needed.has(record.id))) continue
|
|
97
|
+
const h = record.data.origin.handoff, snapshots = matching(h, ctx.snapshots)
|
|
98
|
+
let reason
|
|
99
|
+
if (snapshots.length !== 1) reason = 'import-source-missing-or-ambiguous'
|
|
100
|
+
else try { verify({ handoff: h, records: snapshots[0].records }, ctx, depth + 1) } catch { reason = ctx.exhausted ? 'import-source-verification-budget-exhausted' : 'import-source-requires-reconsideration' }
|
|
101
|
+
if (reason) stale.set(record.id, [...(stale.get(record.id) ?? []), reason])
|
|
102
|
+
}
|
|
103
|
+
// References always point backwards, so one history-order pass propagates
|
|
104
|
+
// changes discovered in external inputs through local meaning and activation.
|
|
105
|
+
for (const record of records) {
|
|
106
|
+
const d = record.data
|
|
107
|
+
const refs = record.kind === 'contribution' ? [d.domain, ...d.basedOn.map(e => e.contribution)]
|
|
108
|
+
: record.kind === 'evaluation' ? [d.contribution]
|
|
109
|
+
: record.kind === 'relation' ? [d.domain, d.subject, d.object]
|
|
110
|
+
: record.kind === 'review' ? [d.target, ...d.evaluations]
|
|
111
|
+
: record.kind === 'activation' ? d.reviews : []
|
|
112
|
+
const reasons = refs.flatMap(pin => stale.get(pin.id) ?? [])
|
|
113
|
+
if (reasons.length) stale.set(record.id, [...new Set([...(stale.get(record.id) ?? []), ...reasons])])
|
|
114
|
+
}
|
|
115
|
+
return { historyDigest: state.head, reconsider: [...stale].map(([id, reasons]) => ({ id, reasons })), freshness: 'relative-to-explicit-snapshots', canonicalMutation: false }
|
|
116
|
+
}
|
|
117
|
+
function readiness(records, ctx, depth) {
|
|
118
|
+
bounded(depth)
|
|
119
|
+
const state = inspect('build', records, ctx), blockers = []
|
|
120
|
+
if (!state.candidate) blockers.push('candidate-missing')
|
|
121
|
+
if (!state.reportedAccepted) blockers.push('current-accepted-decision-missing')
|
|
122
|
+
for (const [id, a] of Object.entries(state.attempts)) if (a.candidate === state.candidate?.id && !['completed', 'failed', 'cancelled'].includes(a.state)) blockers.push(`attempt-unsettled:${id}`)
|
|
123
|
+
for (const dependency of state.establishment?.data.dependencies ?? []) {
|
|
124
|
+
const h = dependency.handoff
|
|
125
|
+
const snapshots = matching(h, ctx.snapshots)
|
|
126
|
+
if (snapshots.length !== 1) { blockers.push(`dependency-missing-or-ambiguous:${dependency.id}`); continue }
|
|
127
|
+
try { verify({ handoff: h, records: snapshots[0].records }, ctx, depth + 1) } catch { blockers.push(`${ctx.exhausted ? 'dependency-verification-budget-exhausted' : 'dependency-requires-reconsideration'}:${dependency.id}`) }
|
|
128
|
+
}
|
|
129
|
+
return { candidate: state.candidate, readyReported: blockers.length === 0, blockers, historyDigest: state.head,
|
|
130
|
+
dependencyFreshness: 'relative-to-explicit-snapshots; no-live-observation', authenticatedAcceptance: false, executionAuthorized: false }
|
|
131
|
+
}
|
|
132
|
+
export function createHarnessHandoff(options) { return create(options, context(options.dependencySnapshots, options.verificationLimit), 0) }
|
|
133
|
+
export function verifyHarnessHandoff(options) {
|
|
134
|
+
const ctx = context(options.dependencySnapshots, options.verificationLimit)
|
|
135
|
+
return { ...verify(options, ctx, 0), dependencyWork: work(ctx) }
|
|
136
|
+
}
|
|
137
|
+
export function buildReadiness(records, { dependencySnapshots, verificationLimit } = {}) {
|
|
138
|
+
const ctx = context(dependencySnapshots, verificationLimit)
|
|
139
|
+
return { ...readiness(records, ctx, 0), dependencyWork: work(ctx) }
|
|
140
|
+
}
|
|
141
|
+
export function reconcileKnowledge(records, { dependencySnapshots, verificationLimit } = {}) {
|
|
142
|
+
const ctx = context(dependencySnapshots, verificationLimit)
|
|
143
|
+
return { ...knowledgeFreshness(records, ctx, 0), dependencyWork: work(ctx) }
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// A read-only projection that a Fabric adapter or another coordinator can read.
|
|
147
|
+
// It imports no private registry, creates no tasks and grants no dispatch power.
|
|
148
|
+
export function buildCoordinationProposal(records, options = {}) {
|
|
149
|
+
const state = inspectBuild(records), readiness = buildReadiness(records, options)
|
|
150
|
+
return { schema: 'atelier-build-coordination-proposal@v1', outcome: state.establishment?.data.purpose ?? null,
|
|
151
|
+
owner: state.establishment?.data.owner ?? null, candidate: state.candidate?.data ?? null,
|
|
152
|
+
dependencies: state.establishment?.data.dependencies ?? [], gates: state.gates.map(r => ({ id: r.data.gate, status: r.data.status, evidence: r.data.evidence, record: harnessRef(r) })),
|
|
153
|
+
...readiness, reconsider: state.reconsider, acceptedByCoordinator: false }
|
|
154
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { assertDocument } from '../capabilities/package.mjs'
|
|
2
|
+
import { harnessRef } from './contracts.mjs'
|
|
3
|
+
import { inspectHarness } from './exchange.mjs'
|
|
4
|
+
export function prepareHarnessFeedback({ profile, records, subjectId, id, outcome, cause, evidenceDigest, at, by }) {
|
|
5
|
+
const state = inspectHarness(profile, records), subject = records.find(r => r.id === subjectId)
|
|
6
|
+
if (!subject) throw new Error('feedback subject missing')
|
|
7
|
+
const atSubject = inspectHarness(profile, records.slice(0, records.indexOf(subject) + 1))
|
|
8
|
+
const binding = (atSubject.domain ?? atSubject.establishment ?? atSubject.campaign)?.data.binding
|
|
9
|
+
if (!binding) throw new Error('harness establishment has no skill binding')
|
|
10
|
+
if (!['skill', 'host', 'tool', 'configuration', 'context', 'provider', 'unknown'].includes(cause)) throw new Error('unknown feedback cause')
|
|
11
|
+
const event = { schema: 'mnstry.atelier-capability-event@v1', id, kind: 'feedback', ...binding, observer: by, outcome,
|
|
12
|
+
cause: ['context', 'provider'].includes(cause) ? 'unknown' : cause, evidenceDigest, at }
|
|
13
|
+
assertDocument(event, 'event')
|
|
14
|
+
return { event, subject: harnessRef(subject), historyDigest: state.head, reportedCause: cause,
|
|
15
|
+
causeMapping: event.cause === cause ? 'exact' : 'unsupported-steward-cause-preserved-in-wrapper', recorded: false, authority: 'none' }
|
|
16
|
+
}
|