@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,88 @@
|
|
|
1
|
+
import { replay, unique } from '../harnesses/history.mjs'
|
|
2
|
+
import { assertAudience, assertHandoff } from '../harnesses/contracts.mjs'
|
|
3
|
+
const terminal = new Set(['completed', 'failed', 'cancelled'])
|
|
4
|
+
export function inspectBuild(records) {
|
|
5
|
+
const attempts = new Map(), gates = new Map(), decisions = []
|
|
6
|
+
let candidate
|
|
7
|
+
const state = replay(records, 'build', 'objective', (record, ctx) => {
|
|
8
|
+
const d = record.data, refs = []
|
|
9
|
+
const resolve = (pin, kinds) => { const r = ctx.resolve(pin, kinds); refs.push(r.id); return r }
|
|
10
|
+
const objective = ctx.first.data
|
|
11
|
+
switch (record.kind) {
|
|
12
|
+
case 'objective':
|
|
13
|
+
unique(d.gates.map(g => g.id), 'build gate'); unique(d.dependencies.map(d => d.id), 'build dependency'); unique(d.allowedEffects, 'allowed effect')
|
|
14
|
+
if (!d.gates.some(g => g.required)) throw new Error('build needs a required gate')
|
|
15
|
+
for (const dependency of d.dependencies) {
|
|
16
|
+
const h = assertHandoff(dependency.handoff)
|
|
17
|
+
if (h.source.profile === 'build' || h.target.profile !== 'build' || h.target.repository !== d.repository) throw new Error('build dependency requires knowledge or inquiry handoff to this repository')
|
|
18
|
+
assertAudience(d.audience, h.audience)
|
|
19
|
+
}
|
|
20
|
+
break
|
|
21
|
+
case 'candidate':
|
|
22
|
+
resolve(d.objective, ['objective'])
|
|
23
|
+
if (d.repository !== objective.repository) throw new Error('candidate repository mismatch')
|
|
24
|
+
if (candidate) {
|
|
25
|
+
if (!d.supersedes || ctx.resolve(d.supersedes, ['candidate']).id !== candidate.id) throw new Error('candidate must supersede current candidate')
|
|
26
|
+
if ([...attempts.values()].some(a => a.candidate === candidate.id && !terminal.has(a.state))) throw new Error('unsettled attempt prevents candidate replacement')
|
|
27
|
+
ctx.invalidate(candidate.id, `candidate-replaced:${record.id}`)
|
|
28
|
+
} else if (d.supersedes) throw new Error('initial candidate cannot supersede missing work')
|
|
29
|
+
candidate = record
|
|
30
|
+
break
|
|
31
|
+
case 'attempt':
|
|
32
|
+
resolve(d.candidate, ['candidate']); unique(d.effects, 'attempt effect')
|
|
33
|
+
if (d.effects.some(effect => !objective.allowedEffects.includes(effect))) throw new Error('attempt effect is outside declared scope')
|
|
34
|
+
if (attempts.size >= objective.maxAttempts) throw new Error('build attempt budget exhausted')
|
|
35
|
+
if ([...attempts.values()].some(a => a.candidate === d.candidate.id && a.operation === d.operation && !['failed', 'cancelled'].includes(a.state))) throw new Error('operation already completed or unsettled; reconcile before retry')
|
|
36
|
+
attempts.set(record.id, { candidate: d.candidate.id, operation: d.operation, state: 'intent', last: record.id })
|
|
37
|
+
break
|
|
38
|
+
case 'progress': {
|
|
39
|
+
const original = resolve(d.attempt, ['attempt']), previous = attempts.get(original.id)
|
|
40
|
+
const allowed = { intent: ['running', 'uncertain', 'completed', 'failed', 'cancelled'], running: ['uncertain', 'completed', 'failed', 'cancelled'], uncertain: ['completed', 'failed', 'cancelled'] }
|
|
41
|
+
if (!allowed[previous.state]?.includes(d.state)) throw new Error('invalid or terminal attempt transition')
|
|
42
|
+
refs.push(previous.last)
|
|
43
|
+
attempts.set(original.id, { ...previous, state: d.state, last: record.id })
|
|
44
|
+
break
|
|
45
|
+
}
|
|
46
|
+
case 'gate': {
|
|
47
|
+
resolve(d.candidate, ['candidate'])
|
|
48
|
+
if (!objective.gates.some(g => g.id === d.gate)) throw new Error('undeclared build gate')
|
|
49
|
+
if (d.attempt) {
|
|
50
|
+
const a = resolve(d.attempt, ['attempt']), status = attempts.get(a.id)
|
|
51
|
+
if (a.data.candidate.id !== d.candidate.id || (d.status === 'passed' && status.state !== 'completed')) throw new Error('gate requires completed attempt on exact candidate')
|
|
52
|
+
refs.push(status.last)
|
|
53
|
+
}
|
|
54
|
+
const key = `${d.candidate.id}/${d.gate}`, old = gates.get(key)
|
|
55
|
+
if (old) ctx.invalidate(old.id, `gate-replaced:${record.id}`)
|
|
56
|
+
gates.set(key, record)
|
|
57
|
+
break
|
|
58
|
+
}
|
|
59
|
+
case 'decision': {
|
|
60
|
+
resolve(d.candidate, ['candidate']); unique(d.gates.map(g => g.id), 'decision gate')
|
|
61
|
+
const named = d.gates.map(pin => resolve(pin, ['gate']))
|
|
62
|
+
if (named.some(g => g.data.candidate.id !== d.candidate.id)) throw new Error('gate names another candidate')
|
|
63
|
+
if (d.disposition === 'accepted') {
|
|
64
|
+
for (const required of objective.gates.filter(g => g.required)) {
|
|
65
|
+
const gate = gates.get(`${d.candidate.id}/${required.id}`)
|
|
66
|
+
if (!gate || gate.data.status !== 'passed' || !named.some(r => r.id === gate.id)) throw new Error('accepted build requires all current required gates')
|
|
67
|
+
}
|
|
68
|
+
if ([...attempts.values()].some(a => a.candidate === d.candidate.id && !terminal.has(a.state))) throw new Error('unsettled attempt prevents acceptance')
|
|
69
|
+
}
|
|
70
|
+
if (decisions.length) ctx.invalidate(decisions.at(-1).id, `decision-replaced:${record.id}`)
|
|
71
|
+
decisions.push(record)
|
|
72
|
+
break
|
|
73
|
+
}
|
|
74
|
+
case 'delivery': {
|
|
75
|
+
const decision = resolve(d.decision, ['decision'])
|
|
76
|
+
if (decision.data.disposition !== 'accepted' || decisions.at(-1)?.id !== decision.id) throw new Error('delivery requires latest accepted decision')
|
|
77
|
+
break
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return refs
|
|
81
|
+
})
|
|
82
|
+
const stale = new Set(state.reconsider.map(r => r.id))
|
|
83
|
+
const decision = decisions.filter(r => !stale.has(r.id)).at(-1)
|
|
84
|
+
return { ...state, candidate: candidate ?? null, attempts: Object.fromEntries(attempts),
|
|
85
|
+
gates: [...gates.values()].filter(r => !stale.has(r.id)), decision: decision ?? null,
|
|
86
|
+
reportedAccepted: decision?.data.disposition === 'accepted',
|
|
87
|
+
dependencyFreshness: 'unverified-until-snapshots-supplied', executionAuthorized: false, authenticatedAcceptance: false }
|
|
88
|
+
}
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import crypto from 'node:crypto'
|
|
4
|
+
import { syncPrivateDirectory } from '../project/private-state.mjs'
|
|
5
|
+
import { acquirePrivateLock } from '../project/durable-state.mjs'
|
|
6
|
+
import { assertDocument, HOST_PROFILES, projectSkill, unique, verifyCapabilityRelease } from './package.mjs'
|
|
7
|
+
import { bytesAt, canonical, currentTree, digest, jsonAt, jsonText, objectDigest, replaceJson, stat, tree, within, workspaceRoot, writeNew } from './files.mjs'
|
|
8
|
+
|
|
9
|
+
export const CAPABILITY_STATE = '.atelier-local/capabilities/state.json'
|
|
10
|
+
const ACTIVE = '.atelier-local/capabilities/active.json'
|
|
11
|
+
const OPERATION_LOCK = '.atelier-local/skill-steward/.operation.lock'
|
|
12
|
+
const TX = '.atelier-local/capabilities/transactions'
|
|
13
|
+
|
|
14
|
+
export function readCapabilityState(root) {
|
|
15
|
+
const file = within(root, CAPABILITY_STATE)
|
|
16
|
+
if (!stat(file)) return null
|
|
17
|
+
const state = assertDocument(jsonAt(root, CAPABILITY_STATE), 'state')
|
|
18
|
+
if (state.workspace !== digest(root)) throw new Error('capability state belongs to a different workspace')
|
|
19
|
+
unique(state.packages.map(item => item.id), 'installed package')
|
|
20
|
+
unique(state.packages.flatMap(item => item.bindings.map(binding => binding.target.toLowerCase())), 'installed target')
|
|
21
|
+
for (const item of state.packages) {
|
|
22
|
+
if (item.id !== item.release.package.id) throw new Error('installed package identity mismatch')
|
|
23
|
+
for (const binding of item.bindings) validateTarget(binding)
|
|
24
|
+
}
|
|
25
|
+
return state
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function validateTarget(binding) {
|
|
29
|
+
if (`${HOST_PROFILES[binding.host]?.target}/${binding.alias}` !== binding.target) throw new Error('binding target differs from the host profile')
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function activeJournal(root) {
|
|
33
|
+
if (!stat(within(root, ACTIVE))) return null
|
|
34
|
+
const pointer = jsonAt(root, ACTIVE)
|
|
35
|
+
if (Object.keys(pointer).length !== 1 || !/^[a-f0-9-]{36}$/.test(pointer.id)) throw new Error('invalid active capability transaction')
|
|
36
|
+
const journal = assertDocument(jsonAt(root, `${TX}/${pointer.id}/journal.json`), 'journal')
|
|
37
|
+
if (journal.id !== pointer.id || journal.workspace !== digest(root)) throw new Error('transaction belongs to a different workspace')
|
|
38
|
+
for (const action of journal.actions) {
|
|
39
|
+
if (!/^\.(agents|claude)\/skills\/[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/.test(action.target)) throw new Error('transaction target is outside host profiles')
|
|
40
|
+
}
|
|
41
|
+
return journal
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function prepare(options) {
|
|
45
|
+
const { adoption, sources = [], availableTools = [], notices = [] } = options
|
|
46
|
+
const root = workspaceRoot(options.workspaceRoot ?? process.cwd())
|
|
47
|
+
assertDocument(adoption, 'adoption')
|
|
48
|
+
unique(adoption.packages.map(item => item.id), 'adoption package')
|
|
49
|
+
unique(availableTools, 'tool observation')
|
|
50
|
+
if (availableTools.length > 64 || availableTools.some(tool => !/^[a-z][a-z0-9.-]{0,63}$/.test(tool))) throw new Error('invalid available tools')
|
|
51
|
+
if (!Array.isArray(sources) || sources.length > 32) throw new Error('too many release sources')
|
|
52
|
+
if (!Array.isArray(notices) || notices.length > 256) throw new Error('too many release notices')
|
|
53
|
+
for (const notice of notices) assertDocument(notice, 'notice')
|
|
54
|
+
const before = readCapabilityState(root)
|
|
55
|
+
const blockers = [], actions = [], changes = [], projected = new Map(), releases = new Map()
|
|
56
|
+
if (activeJournal(root)) blockers.push('unfinished-transaction: inspect recovery before applying')
|
|
57
|
+
if (before && before.enrollment !== adoption.id) blockers.push('enrollment-identity-changed')
|
|
58
|
+
for (const packageRoot of sources) {
|
|
59
|
+
const realSource = fs.realpathSync(path.resolve(packageRoot))
|
|
60
|
+
for (const profile of Object.values(HOST_PROFILES)) {
|
|
61
|
+
const target = path.join(root, profile.target)
|
|
62
|
+
if (realSource === target || realSource.startsWith(`${target}${path.sep}`) || target.startsWith(`${realSource}${path.sep}`)) throw new Error('release source overlaps a managed host surface')
|
|
63
|
+
}
|
|
64
|
+
const release = verifyCapabilityRelease({ packageRoot: realSource })
|
|
65
|
+
if (releases.has(release.package.id)) throw new Error('multiple sources for the same package')
|
|
66
|
+
releases.set(release.package.id, { release, root: realSource })
|
|
67
|
+
}
|
|
68
|
+
const versions = [...(before?.versions ?? [])]
|
|
69
|
+
const nextPackages = []
|
|
70
|
+
const claimed = new Set()
|
|
71
|
+
for (const desired of adoption.packages) {
|
|
72
|
+
const old = before?.packages.find(item => item.id === desired.id)
|
|
73
|
+
const supplied = releases.get(desired.id)
|
|
74
|
+
const release = supplied?.release ?? (['retired', 'customized'].includes(desired.mode) ? old?.release : null)
|
|
75
|
+
if (!release || release.digest !== desired.digest) { blockers.push(`${desired.id}: missing-or-unpinned-release`); continue }
|
|
76
|
+
const descriptor = release.package
|
|
77
|
+
if (['managed', 'customized'].includes(desired.mode) && notices.some(notice => notice.id === desired.id && notice.digest === release.digest && notice.status === 'withdrawn')) blockers.push(`${desired.id}: release-withdrawn-by-supplied-notice`)
|
|
78
|
+
const version = versions.find(item => item.id === desired.id && item.version === descriptor.version)
|
|
79
|
+
if (version && version.digest !== release.digest) blockers.push(`${desired.id}: immutable-version-reused`)
|
|
80
|
+
if (!version) versions.push({ id: desired.id, version: descriptor.version, digest: release.digest })
|
|
81
|
+
if (['customized', 'retired'].includes(desired.mode) && (!old || old.release.digest !== desired.digest)) blockers.push(`${desired.id}: ${desired.mode}-requires-current-adopted-release`)
|
|
82
|
+
if (['reference', 'retired'].includes(desired.mode) && desired.bindings.length) blockers.push(`${desired.id}: inactive-mode-has-bindings`)
|
|
83
|
+
if (desired.mode === 'managed' && !desired.bindings.length) blockers.push(`${desired.id}: managed-package-has-no-bindings`)
|
|
84
|
+
const toolsAdded = descriptor.requirements.tools.filter(tool => !old?.release.package.requirements.tools.includes(tool))
|
|
85
|
+
const effectsAdded = descriptor.requirements.effects.filter(effect => !old?.release.package.requirements.effects.includes(effect))
|
|
86
|
+
changes.push({ package: desired.id, fromDigest: old?.release.digest ?? null, toDigest: release.digest, fromMode: old?.mode ?? null, toMode: desired.mode, toolsAdded, effectsAdded })
|
|
87
|
+
if (['managed', 'customized'].includes(desired.mode)) {
|
|
88
|
+
for (const tool of descriptor.requirements.tools) {
|
|
89
|
+
if (!desired.allowedTools.includes(tool)) blockers.push(`${desired.id}: tool-not-admitted:${tool}`)
|
|
90
|
+
if (!availableTools.includes(tool)) blockers.push(`${desired.id}: tool-not-observed:${tool}`)
|
|
91
|
+
}
|
|
92
|
+
for (const effect of descriptor.requirements.effects) if (!desired.allowedEffects.includes(effect)) blockers.push(`${desired.id}: effect-not-admitted:${effect}`)
|
|
93
|
+
}
|
|
94
|
+
const bindings = []
|
|
95
|
+
for (const binding of desired.bindings) {
|
|
96
|
+
const target = `${HOST_PROFILES[binding.host].target}/${binding.alias}`
|
|
97
|
+
if (claimed.has(target.toLowerCase())) blockers.push(`${desired.id}: alias-collision:${target}`)
|
|
98
|
+
claimed.add(target.toLowerCase())
|
|
99
|
+
const currentDigest = currentTree(root, target)
|
|
100
|
+
const prior = old?.bindings.find(item => item.target === target)
|
|
101
|
+
// A different managed publisher cannot acquire an old binding by renaming.
|
|
102
|
+
const otherOwner = before?.packages.find(item => item.id !== desired.id && item.bindings.some(entry => entry.target === target))
|
|
103
|
+
if (otherOwner) blockers.push(`${desired.id}: target-owned-by-another-package:${target}`)
|
|
104
|
+
if (!prior && currentDigest) blockers.push(`${desired.id}: unmanaged-collision:${target}`)
|
|
105
|
+
const legacyLock = `${HOST_PROFILES[binding.host].target}/.atelier-skill-lock.json`
|
|
106
|
+
if (stat(within(root, legacyLock)) && jsonAt(root, legacyLock)?.skills?.some(item => item.name === binding.alias)) blockers.push(`${desired.id}: target-owned-by-legacy-steward:${target}`)
|
|
107
|
+
if (prior && !currentDigest) blockers.push(`${desired.id}: managed-target-missing:${target}`)
|
|
108
|
+
if (desired.mode === 'customized') {
|
|
109
|
+
if (!prior || prior.skill !== binding.skill || old.release.digest !== release.digest) {
|
|
110
|
+
blockers.push(`${desired.id}: customization-requires-existing-binding:${target}`)
|
|
111
|
+
continue
|
|
112
|
+
}
|
|
113
|
+
bindings.push({ ...prior, digest: currentDigest ?? prior.digest })
|
|
114
|
+
actions.push({ type: 'customize', package: desired.id, target, currentDigest, nextDigest: currentDigest })
|
|
115
|
+
continue
|
|
116
|
+
}
|
|
117
|
+
const restored = old?.mode === 'customized' && currentDigest === prior?.parentDigest
|
|
118
|
+
if (old?.mode === 'customized' && !restored) blockers.push(`${desired.id}: customization-requires-explicit-fork-or-restoration:${target}`)
|
|
119
|
+
if (prior && currentDigest !== prior.digest && !restored) blockers.push(`${desired.id}: local-drift:${target}`)
|
|
120
|
+
if (!supplied) { blockers.push(`${desired.id}: managed-source-required`); continue }
|
|
121
|
+
const projection = projectSkill({ packageRoot: supplied.root, release, skillId: binding.skill, alias: binding.alias, host: binding.host })
|
|
122
|
+
projected.set(target, projection.files)
|
|
123
|
+
bindings.push({ ...binding, target, digest: projection.digest, parentDigest: projection.digest })
|
|
124
|
+
actions.push({ type: !currentDigest ? 'add' : currentDigest === projection.digest ? 'keep' : 'update', package: desired.id, target, currentDigest, nextDigest: projection.digest })
|
|
125
|
+
}
|
|
126
|
+
for (const prior of old?.bindings ?? []) {
|
|
127
|
+
if (bindings.some(binding => binding.target === prior.target)) continue
|
|
128
|
+
const currentDigest = currentTree(root, prior.target)
|
|
129
|
+
const restored = old.mode === 'customized' && currentDigest === prior.parentDigest
|
|
130
|
+
if (currentDigest !== prior.digest && !restored) blockers.push(`${desired.id}: local-drift:${prior.target}`)
|
|
131
|
+
if (old.mode === 'customized' && !restored) blockers.push(`${desired.id}: customized-binding-must-be-preserved:${prior.target}`)
|
|
132
|
+
actions.push({ type: 'retire', package: desired.id, target: prior.target, currentDigest, nextDigest: null })
|
|
133
|
+
}
|
|
134
|
+
nextPackages.push({ id: desired.id, release, mode: desired.mode, bindings })
|
|
135
|
+
}
|
|
136
|
+
for (const old of before?.packages ?? []) if (!adoption.packages.some(item => item.id === old.id)) blockers.push(`${old.id}: explicit-retirement-required-before-removal`)
|
|
137
|
+
for (const item of nextPackages.filter(item => ['managed', 'customized'].includes(item.mode))) {
|
|
138
|
+
for (const dependency of item.release.package.dependencies) {
|
|
139
|
+
const target = nextPackages.find(entry => entry.id === dependency.id && ['managed', 'customized'].includes(entry.mode))
|
|
140
|
+
if (!target && dependency.optional) continue
|
|
141
|
+
if (!target || target.release.digest !== dependency.digest || target.release.package.version !== dependency.version) blockers.push(`${item.id}: unsatisfied-dependency:${dependency.id}`)
|
|
142
|
+
if (target) {
|
|
143
|
+
for (const host of new Set(item.bindings.map(binding => binding.host))) {
|
|
144
|
+
const provided = new Set(target.bindings.filter(binding => binding.host === host)
|
|
145
|
+
.flatMap(binding => target.release.package.skills.find(skill => skill.id === binding.skill)?.capabilities ?? []))
|
|
146
|
+
for (const required of dependency.capabilities) if (!provided.has(required)) blockers.push(`${item.id}: dependency-capability-not-bound:${dependency.id}:${required}:${host}`)
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
const visiting = new Set(), visited = new Set()
|
|
152
|
+
function visit(id) {
|
|
153
|
+
if (visiting.has(id)) { blockers.push(`${id}: dependency-cycle`); return }
|
|
154
|
+
if (visited.has(id)) return
|
|
155
|
+
visiting.add(id)
|
|
156
|
+
const entry = nextPackages.find(item => item.id === id && ['managed', 'customized'].includes(item.mode))
|
|
157
|
+
for (const dependency of entry?.release.package.dependencies ?? []) visit(dependency.id)
|
|
158
|
+
visiting.delete(id); visited.add(id)
|
|
159
|
+
}
|
|
160
|
+
for (const item of nextPackages) visit(item.id)
|
|
161
|
+
const payload = {
|
|
162
|
+
schema: 'mnstry.atelier-capability-plan@v1', workspace: digest(root), enrollment: adoption.id,
|
|
163
|
+
policyDigest: objectDigest(adoption), stateDigest: before ? objectDigest(before) : null,
|
|
164
|
+
sources: [...releases.values()].map(item => ({ id: item.release.package.id, digest: item.release.digest })).sort((a, b) => a.id.localeCompare(b.id)),
|
|
165
|
+
tools: [...availableTools].sort(), notices, actions, changes, blockers: [...new Set(blockers)], applyAllowed: blockers.length === 0,
|
|
166
|
+
}
|
|
167
|
+
const plan = { ...payload, planDigest: objectDigest(payload) }
|
|
168
|
+
assertDocument(plan, 'plan')
|
|
169
|
+
const after = { schema: 'mnstry.atelier-capability-state@v1', workspace: digest(root), enrollment: adoption.id, generation: plan.planDigest, policyDigest: objectDigest(adoption), packages: nextPackages, versions }
|
|
170
|
+
if (plan.applyAllowed) assertDocument(after, 'state')
|
|
171
|
+
return { root, plan, before, after, projected }
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export function planCapabilityAdoption(options) { return prepare(options).plan }
|
|
175
|
+
|
|
176
|
+
function operationLocked(reason) {
|
|
177
|
+
return Object.assign(new Error(`EEXIST: operation lock ${reason}`), { code: 'EEXIST' })
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function sameLockFile(left, right) {
|
|
181
|
+
return left && right && left.identity.dev === right.identity.dev && left.identity.ino === right.identity.ino && left.bytes.equals(right.bytes)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function readOperationLock(root) {
|
|
185
|
+
const identity = stat(within(root, OPERATION_LOCK))
|
|
186
|
+
if (!identity) return null
|
|
187
|
+
// A competing recoverer may remove or replace the lock between the stat and
|
|
188
|
+
// the read; that is a change during inspection, not an unrelated I/O failure.
|
|
189
|
+
let bytes
|
|
190
|
+
try { bytes = bytesAt(root, OPERATION_LOCK) } catch (error) {
|
|
191
|
+
if (error.code === 'ENOENT' || error.code === 'ELEAFCHANGED') throw operationLocked('changed during inspection')
|
|
192
|
+
throw error
|
|
193
|
+
}
|
|
194
|
+
const current = stat(within(root, OPERATION_LOCK))
|
|
195
|
+
if (!current || identity.dev !== current.dev || identity.ino !== current.ino || identity.mtimeMs !== current.mtimeMs || identity.ctimeMs !== current.ctimeMs) throw operationLocked('changed during inspection')
|
|
196
|
+
return { identity, bytes }
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function requireDeadOperationOwner(observed) {
|
|
200
|
+
if (!observed) return
|
|
201
|
+
let owner
|
|
202
|
+
try { owner = JSON.parse(observed.bytes.toString('utf8')) } catch { throw operationLocked('requires its owner') }
|
|
203
|
+
if (owner?.owner !== 'capability-steward' || !Number.isSafeInteger(owner.pid) || owner.pid < 1 || !/^[a-f0-9-]{36}$/.test(owner.operationId)) throw operationLocked('requires its owner')
|
|
204
|
+
try { process.kill(owner.pid, 0) } catch (error) {
|
|
205
|
+
if (error.code === 'ESRCH') return
|
|
206
|
+
throw operationLocked('owner identity is unavailable')
|
|
207
|
+
}
|
|
208
|
+
// An existing PID may be a reused PID. Neither case permits reclamation.
|
|
209
|
+
throw operationLocked('owner is still alive')
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function removeOperationLock(root, observed) {
|
|
213
|
+
if (!sameLockFile(readOperationLock(root), observed)) return false
|
|
214
|
+
fs.unlinkSync(within(root, OPERATION_LOCK))
|
|
215
|
+
syncPrivateDirectory(path.dirname(path.join(root, OPERATION_LOCK)))
|
|
216
|
+
return true
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// After the callback has run, a lock that changed or vanished while cleanup
|
|
220
|
+
// inspected it is not ours: preserve it. EEXIST means "not entered", so it must
|
|
221
|
+
// never be reported for an operation that already committed.
|
|
222
|
+
function releaseOwnedOperationLock(root, owned) {
|
|
223
|
+
try { return removeOperationLock(root, owned) } catch (error) {
|
|
224
|
+
if (error.code === 'EEXIST' || error.code === 'ENOENT') return false
|
|
225
|
+
throw error
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function withOperationLock(root, callback) {
|
|
230
|
+
within(root, path.posix.dirname(OPERATION_LOCK), { directory: true, create: true })
|
|
231
|
+
const prior = readOperationLock(root)
|
|
232
|
+
requireDeadOperationOwner(prior)
|
|
233
|
+
// Durable owner generations serialize competing reclaimers. Keep the shared
|
|
234
|
+
// exclusive file as well, so the existing skill-sync writer still excludes us.
|
|
235
|
+
const release = acquirePrivateLock(within(root, OPERATION_LOCK))
|
|
236
|
+
let owned, failed = false
|
|
237
|
+
try {
|
|
238
|
+
if (prior) {
|
|
239
|
+
requireDeadOperationOwner(prior)
|
|
240
|
+
if (!removeOperationLock(root, prior)) throw operationLocked('changed before reclamation')
|
|
241
|
+
}
|
|
242
|
+
const bytes = Buffer.from(jsonText({ owner: 'capability-steward', operationId: crypto.randomUUID(), pid: process.pid }))
|
|
243
|
+
writeNew(root, OPERATION_LOCK, bytes)
|
|
244
|
+
const installed = readOperationLock(root)
|
|
245
|
+
if (!installed?.bytes.equals(bytes)) throw operationLocked('changed before the operation')
|
|
246
|
+
owned = installed
|
|
247
|
+
return callback()
|
|
248
|
+
} catch (error) { failed = true; throw error } finally {
|
|
249
|
+
let cleanupError
|
|
250
|
+
try { if (owned) releaseOwnedOperationLock(root, owned) } catch (error) { cleanupError = error }
|
|
251
|
+
try { release() } catch (error) { cleanupError ??= error }
|
|
252
|
+
// A missing or changed file never authorizes deleting a replacement; a
|
|
253
|
+
// cleanup failure must also preserve the operation's original exception.
|
|
254
|
+
if (cleanupError && !failed) throw cleanupError
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function move(root, from, to) {
|
|
259
|
+
const source = within(root, from, { directory: true })
|
|
260
|
+
const parent = path.posix.dirname(to)
|
|
261
|
+
within(root, parent, { directory: true, create: true })
|
|
262
|
+
const target = within(root, to, { directory: true })
|
|
263
|
+
if (stat(target)) throw new Error('move destination already exists')
|
|
264
|
+
fs.renameSync(source, target)
|
|
265
|
+
syncPrivateDirectory(path.dirname(source))
|
|
266
|
+
syncPrivateDirectory(path.dirname(target))
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
export function applyCapabilityAdoption(options) {
|
|
270
|
+
const initial = prepare(options)
|
|
271
|
+
if (initial.plan.planDigest !== options.confirm) throw new Error('confirmation must exactly match the current adoption plan')
|
|
272
|
+
if (!initial.plan.applyAllowed) throw new Error(initial.plan.blockers.join('; '))
|
|
273
|
+
const root = workspaceRoot(initial.root, { write: true })
|
|
274
|
+
return withOperationLock(root, () => {
|
|
275
|
+
const prepared = prepare(options)
|
|
276
|
+
if (prepared.plan.planDigest !== options.confirm) throw new Error('adoption changed before the operation lock')
|
|
277
|
+
const { plan, before, after, projected } = prepared
|
|
278
|
+
if (before && canonical({ ...before, generation: null }) === canonical({ ...after, generation: null })) return { changed: false, plan, state: before }
|
|
279
|
+
const id = crypto.randomUUID(), base = `${TX}/${id}`
|
|
280
|
+
const journal = { schema: 'mnstry.atelier-capability-journal@v1', id, workspace: digest(root), planDigest: plan.planDigest, phase: 'prepared', before, after, actions: plan.actions }
|
|
281
|
+
assertDocument(journal, 'journal')
|
|
282
|
+
for (const action of plan.actions.filter(item => ['add', 'update'].includes(item.type))) {
|
|
283
|
+
for (const file of projected.get(action.target)) writeNew(root, `${base}/staged/${action.target}/${file.path}`, file.bytes)
|
|
284
|
+
if (currentTree(root, `${base}/staged/${action.target}`) !== action.nextDigest) throw new Error('staged binding verification failed')
|
|
285
|
+
}
|
|
286
|
+
// Re-read both supplied release bytes and repository state after staging.
|
|
287
|
+
if (prepare(options).plan.planDigest !== plan.planDigest) throw new Error('adoption changed during staging')
|
|
288
|
+
replaceJson(root, `${base}/journal.json`, journal)
|
|
289
|
+
writeNew(root, ACTIVE, jsonText({ id }))
|
|
290
|
+
try {
|
|
291
|
+
journal.phase = 'applying'; replaceJson(root, `${base}/journal.json`, journal)
|
|
292
|
+
for (const action of plan.actions) {
|
|
293
|
+
if (currentTree(root, action.target) !== action.currentDigest) throw new Error('binding changed during adoption')
|
|
294
|
+
if (['update', 'retire'].includes(action.type)) move(root, action.target, `${base}/before/${action.target}`)
|
|
295
|
+
if (['add', 'update'].includes(action.type)) move(root, `${base}/staged/${action.target}`, action.target)
|
|
296
|
+
}
|
|
297
|
+
for (const action of plan.actions) if (currentTree(root, action.target) !== action.nextDigest) throw new Error('installed binding readback failed')
|
|
298
|
+
if (objectDigest(readCapabilityState(root)) !== objectDigest(before)) throw new Error('adoption state changed during publication')
|
|
299
|
+
replaceJson(root, CAPABILITY_STATE, after)
|
|
300
|
+
if (objectDigest(readCapabilityState(root)) !== objectDigest(after)) throw new Error('adoption state readback failed')
|
|
301
|
+
journal.phase = 'committed'; replaceJson(root, `${base}/journal.json`, journal)
|
|
302
|
+
fs.unlinkSync(within(root, ACTIVE)); syncPrivateDirectory(path.dirname(path.join(root, ACTIVE)))
|
|
303
|
+
return { changed: true, plan, state: after, transaction: id, quarantine: `${base}/before` }
|
|
304
|
+
} catch (error) {
|
|
305
|
+
journal.phase = 'recovery-required'
|
|
306
|
+
replaceJson(root, `${base}/journal.json`, journal)
|
|
307
|
+
// The cause stays attached for local debugging; the message names no host path.
|
|
308
|
+
throw new Error('adoption interrupted; inspect capability recovery', { cause: error })
|
|
309
|
+
}
|
|
310
|
+
})
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export function inspectCapabilityRecovery({ workspaceRoot: input }) {
|
|
314
|
+
const root = workspaceRoot(input ?? process.cwd())
|
|
315
|
+
const journal = activeJournal(root)
|
|
316
|
+
if (!journal) return { pending: false, planDigest: null, blockers: [], actions: [] }
|
|
317
|
+
const blockers = []
|
|
318
|
+
const actions = journal.actions.filter(item => ['add', 'update', 'retire'].includes(item.type)).map(action => {
|
|
319
|
+
const current = currentTree(root, action.target)
|
|
320
|
+
const backup = currentTree(root, `${TX}/${journal.id}/before/${action.target}`)
|
|
321
|
+
const held = currentTree(root, `${TX}/${journal.id}/rolled-back/${action.target}`)
|
|
322
|
+
// Accept each crash boundary, including an interrupted recovery itself.
|
|
323
|
+
const untouched = current === action.currentDigest && backup === null
|
|
324
|
+
const exchanged = backup === action.currentDigest && (current === action.nextDigest || current === null) && (held === null || held === action.nextDigest)
|
|
325
|
+
const added = action.type === 'add' && backup === null && (current === action.nextDigest || current === null) && (held === null || held === action.nextDigest)
|
|
326
|
+
if (!untouched && !exchanged && !added) blockers.push(`ambiguous-binding:${action.target}`)
|
|
327
|
+
return { ...action, observedDigest: current, backupDigest: backup, heldDigest: held }
|
|
328
|
+
})
|
|
329
|
+
const currentState = readCapabilityState(root)
|
|
330
|
+
if (![objectDigest(journal.before), objectDigest(journal.after)].includes(objectDigest(currentState))) blockers.push('adoption-state-changed')
|
|
331
|
+
const payload = { pending: true, transaction: journal.id, phase: journal.phase, actions, blockers, stateDigest: objectDigest(currentState), journalDigest: objectDigest(journal) }
|
|
332
|
+
return { ...payload, planDigest: objectDigest(payload) }
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export function recoverCapabilityAdoption({ workspaceRoot: input, confirm }) {
|
|
336
|
+
const root = workspaceRoot(input ?? process.cwd(), { write: true })
|
|
337
|
+
const initial = inspectCapabilityRecovery({ workspaceRoot: root })
|
|
338
|
+
if (!initial.pending || initial.planDigest !== confirm || initial.blockers.length) throw new Error('recovery requires the exact unblocked recovery plan')
|
|
339
|
+
return withOperationLock(root, () => {
|
|
340
|
+
const plan = inspectCapabilityRecovery({ workspaceRoot: root })
|
|
341
|
+
if (plan.planDigest !== confirm || plan.blockers.length) throw new Error('recovery plan changed')
|
|
342
|
+
const journal = activeJournal(root), base = `${TX}/${journal.id}`
|
|
343
|
+
for (const action of [...plan.actions].reverse()) {
|
|
344
|
+
if (currentTree(root, action.target) !== action.observedDigest) throw new Error('binding changed during recovery')
|
|
345
|
+
if (action.observedDigest === action.currentDigest && action.backupDigest === null) continue
|
|
346
|
+
if (action.observedDigest !== null) move(root, action.target, `${base}/rolled-back/${action.target}`)
|
|
347
|
+
if (action.backupDigest !== null) move(root, `${base}/before/${action.target}`, action.target)
|
|
348
|
+
}
|
|
349
|
+
for (const action of plan.actions) if (currentTree(root, action.target) !== action.currentDigest) throw new Error('recovery binding readback failed')
|
|
350
|
+
if (journal.before) replaceJson(root, CAPABILITY_STATE, journal.before)
|
|
351
|
+
else if (stat(within(root, CAPABILITY_STATE))) { fs.unlinkSync(within(root, CAPABILITY_STATE)); syncPrivateDirectory(path.dirname(path.join(root, CAPABILITY_STATE))) }
|
|
352
|
+
journal.phase = 'rolled-back'; replaceJson(root, `${base}/journal.json`, journal)
|
|
353
|
+
fs.unlinkSync(within(root, ACTIVE)); syncPrivateDirectory(path.dirname(path.join(root, ACTIVE)))
|
|
354
|
+
return { recovered: true, transaction: journal.id, state: readCapabilityState(root), preserved: `${base}/rolled-back` }
|
|
355
|
+
})
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
export function inspectCapabilityAdoption({ workspaceRoot: input }) {
|
|
359
|
+
const root = workspaceRoot(input ?? process.cwd())
|
|
360
|
+
const state = readCapabilityState(root)
|
|
361
|
+
const packages = (state?.packages ?? []).map(item => ({
|
|
362
|
+
id: item.id, version: item.release.package.version, digest: item.release.digest, mode: item.mode,
|
|
363
|
+
bindings: item.bindings.map(binding => {
|
|
364
|
+
const observedDigest = currentTree(root, binding.target)
|
|
365
|
+
return { ...binding, observedDigest, installed: observedDigest === binding.digest ? 'current' : observedDigest === null ? 'missing' : 'drifted', hostObserved: 'unknown', exercised: 'unknown' }
|
|
366
|
+
}),
|
|
367
|
+
}))
|
|
368
|
+
return { schema: 'mnstry.atelier-capability-status@v1', workspace: digest(root), enrollment: state?.enrollment ?? null, generation: state?.generation ?? null, policyDigest: state?.policyDigest ?? null, recovery: inspectCapabilityRecovery({ workspaceRoot: root }), packages }
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
export function inspectCapabilityFleet({ workspaces }) {
|
|
372
|
+
if (!Array.isArray(workspaces) || workspaces.length > 64) throw new Error('fleet requires at most 64 explicitly enrolled repositories')
|
|
373
|
+
const repositories = workspaces.map(input => {
|
|
374
|
+
try { return { ok: true, status: inspectCapabilityAdoption({ workspaceRoot: input }) } }
|
|
375
|
+
catch { return { ok: false, workspace: digest(path.resolve(input)), error: 'repository-inspection-failed' } }
|
|
376
|
+
})
|
|
377
|
+
const ids = repositories.filter(item => item.ok && item.status.enrollment).map(item => item.status.enrollment)
|
|
378
|
+
return { schema: 'mnstry.atelier-capability-fleet@v1', repositories, duplicateEnrollments: [...new Set(ids.filter((id, index) => ids.indexOf(id) !== index))], atomicAcrossRepositories: false }
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
export { withOperationLock }
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import { inspectCapabilityAdoption, inspectCapabilityRecovery, readCapabilityState, withOperationLock } from './adoption.mjs'
|
|
3
|
+
import { assertDocument, unique } from './package.mjs'
|
|
4
|
+
import { currentTree, digest, jsonAt, jsonText, objectDigest, stat, within, workspaceRoot, writeNew } from './files.mjs'
|
|
5
|
+
|
|
6
|
+
const EVENTS = '.atelier-local/capabilities/events'
|
|
7
|
+
const IDENTIFIER_PATTERN = /^[a-z][a-z0-9.-]{0,63}$/
|
|
8
|
+
|
|
9
|
+
export function readCapabilityEvents({ workspaceRoot: input }) {
|
|
10
|
+
const root = workspaceRoot(input ?? process.cwd())
|
|
11
|
+
const directory = within(root, EVENTS, { directory: true })
|
|
12
|
+
if (!stat(directory)) return []
|
|
13
|
+
const names = fs.readdirSync(directory).sort()
|
|
14
|
+
if (names.length > 4096) throw new Error('capability event ceiling reached; archive reviewed events before adding more')
|
|
15
|
+
return names.map(name => {
|
|
16
|
+
if (!/^[a-z][a-z0-9.-]{0,63}\.json$/.test(name)) throw new Error('unrecognized event file')
|
|
17
|
+
const event = assertDocument(jsonAt(root, `${EVENTS}/${name}`), 'event')
|
|
18
|
+
if (`${event.id}.json` !== name) throw new Error('event identity differs from its storage path')
|
|
19
|
+
return event
|
|
20
|
+
})
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function recordCapabilityEvent({ workspaceRoot: input, event }) {
|
|
24
|
+
const root = workspaceRoot(input ?? process.cwd(), { write: true })
|
|
25
|
+
assertDocument(event, 'event')
|
|
26
|
+
// Metadata is supplied by the caller, never harvested from a host session.
|
|
27
|
+
// Its schema cannot carry prompts, source excerpts, URLs or free-form prose.
|
|
28
|
+
if (event.ext && Object.keys(event.ext).length) throw new Error('event extensions are not admitted for local observation storage')
|
|
29
|
+
return withOperationLock(root, () => {
|
|
30
|
+
if (inspectCapabilityRecovery({ workspaceRoot: root }).pending) throw new Error('cannot record an event during unfinished adoption')
|
|
31
|
+
const state = readCapabilityState(root)
|
|
32
|
+
const item = state?.packages.find(entry => entry.id === event.package)
|
|
33
|
+
const binding = item?.bindings.find(entry => entry.target === event.binding)
|
|
34
|
+
if (!binding || item.release.digest !== event.releaseDigest || state.generation !== event.generation || binding.host !== event.host || binding.digest !== event.bindingDigest) throw new Error('event does not identify the current installed binding')
|
|
35
|
+
if (currentTree(root, binding.target) !== binding.digest) throw new Error('cannot record an event for a drifted binding')
|
|
36
|
+
const events = readCapabilityEvents({ workspaceRoot: root })
|
|
37
|
+
if (events.length >= 4096) throw new Error('capability event ceiling reached')
|
|
38
|
+
if (events.some(item => item.id === event.id)) throw new Error('event identity already recorded')
|
|
39
|
+
writeNew(root, `${EVENTS}/${event.id}.json`, jsonText(event))
|
|
40
|
+
return { recorded: true, event, assurance: 'caller-reported-evidence-reference' }
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function capabilityEvidenceStatus({ workspaceRoot: input, session = null }) {
|
|
45
|
+
if (session !== null && !IDENTIFIER_PATTERN.test(session)) throw new Error('invalid host session identifier')
|
|
46
|
+
const status = inspectCapabilityAdoption({ workspaceRoot: input })
|
|
47
|
+
const events = readCapabilityEvents({ workspaceRoot: input })
|
|
48
|
+
for (const item of status.packages) {
|
|
49
|
+
for (const binding of item.bindings) {
|
|
50
|
+
const related = events.filter(event => event.package === item.id && event.binding === binding.target)
|
|
51
|
+
const current = related.filter(event => event.generation === status.generation && event.releaseDigest === item.digest && event.bindingDigest === binding.observedDigest && session !== null && event.session === session)
|
|
52
|
+
for (const [kind, field] of [['host-observed', 'hostObserved'], ['exercise', 'exercised']]) {
|
|
53
|
+
const recent = current.filter(event => event.kind === kind).sort((a, b) => a.at.localeCompare(b.at) || a.id.localeCompare(b.id)).at(-1)
|
|
54
|
+
binding[field] = recent ? `reported-${recent.outcome}` : related.some(event => event.kind === kind) ? 'historical-or-other-session' : 'unknown'
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return { ...status, evidenceAssurance: 'caller-reported; no host probe or model execution', session, events }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function capabilityImprovementCandidates({ workspaceRoot: input }) {
|
|
62
|
+
const status = capabilityEvidenceStatus({ workspaceRoot: input })
|
|
63
|
+
const grouped = new Map()
|
|
64
|
+
for (const event of status.events) {
|
|
65
|
+
const item = status.packages.find(item => item.id === event.package && item.digest === event.releaseDigest)
|
|
66
|
+
if (!item || !item.bindings.some(binding => binding.target === event.binding && binding.digest === event.bindingDigest && binding.installed === 'current') || event.generation !== status.generation) continue
|
|
67
|
+
const key = `${event.package}|${event.releaseDigest}|${event.binding}|${event.cause}`
|
|
68
|
+
if (!grouped.has(key)) grouped.set(key, [])
|
|
69
|
+
grouped.get(key).push(event)
|
|
70
|
+
}
|
|
71
|
+
const candidates = []
|
|
72
|
+
for (const events of grouped.values()) {
|
|
73
|
+
const failed = events.filter(event => event.outcome === 'failed' && ['exercise', 'feedback', 'evaluation'].includes(event.kind))
|
|
74
|
+
// Duplicate reports for one session/evidence do not count as repetition.
|
|
75
|
+
const independent = new Map(failed.map(event => [`${event.session}|${event.evidenceDigest}`, event]))
|
|
76
|
+
if (independent.size < 2) continue
|
|
77
|
+
const first = failed[0]
|
|
78
|
+
candidates.push({ package: first.package, releaseDigest: first.releaseDigest, binding: first.binding, cause: first.cause,
|
|
79
|
+
proposal: first.cause === 'skill' ? 'review-skill' : first.cause === 'unknown' ? 'investigate-cause' : `review-${first.cause}`,
|
|
80
|
+
evidence: [...independent.values()].map(event => event.id), automaticEdit: false, automaticRetirement: false })
|
|
81
|
+
}
|
|
82
|
+
return { schema: 'mnstry.atelier-capability-candidates@v1', candidates, inference: 'reported causes and repetition are review signals, not causal proof' }
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Return reviewable graph source documents, never write to canonical content.
|
|
86
|
+
// The caller chooses a repository, audience and destination through its normal
|
|
87
|
+
// authoring/graph workflow. Generated evidence remains an unreviewed claim.
|
|
88
|
+
export function capabilityGraphSources({ workspaceRoot: input, namespace, audience = 'private' }) {
|
|
89
|
+
if (!/^[a-z][a-z0-9-]{0,63}$/.test(namespace) || !['private', 'team', 'operator', 'staff', 'public', 'sensitive'].includes(audience)) throw new Error('invalid graph namespace or audience')
|
|
90
|
+
const root = workspaceRoot(input ?? process.cwd())
|
|
91
|
+
const state = readCapabilityState(root), events = readCapabilityEvents({ workspaceRoot: root })
|
|
92
|
+
if (!state) return { schema: 'mnstry.atelier-capability-graph-sources@v1', files: [], canonicalMutation: false }
|
|
93
|
+
const documents = new Map()
|
|
94
|
+
const nodeId = (kind, key) => `${namespace}:capability-${kind}-${objectDigest(key).slice(7)}`
|
|
95
|
+
function add(kind, key, title, body, relations = {}) {
|
|
96
|
+
const id = nodeId(kind, key)
|
|
97
|
+
const metadata = [
|
|
98
|
+
'---', `title: ${JSON.stringify(title)}`, 'kg:', ` id: ${JSON.stringify(id)}`, ' type: "document"',
|
|
99
|
+
' lifecycle: "capability-stewardship"', ' status: "draft"', ` audience: ${JSON.stringify(audience)}`,
|
|
100
|
+
]
|
|
101
|
+
if (Object.keys(relations).length) {
|
|
102
|
+
metadata.push(' relations:')
|
|
103
|
+
for (const [relation, targets] of Object.entries(relations)) {
|
|
104
|
+
metadata.push(` ${relation}:`)
|
|
105
|
+
for (const target of targets) metadata.push(` - ${JSON.stringify(target)}`)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
metadata.push('---', '', `# ${title}`, '', body, '', 'This record describes evidence or adoption. It grants no execution or publication authority.', '')
|
|
109
|
+
documents.set(id, { path: `${id.split(':')[1]}.md`, content: metadata.join('\n') })
|
|
110
|
+
return id
|
|
111
|
+
}
|
|
112
|
+
const enrollmentId = add('adoption', [state.enrollment, state.generation], `Capability adoption ${state.enrollment}`, `Generation: ${state.generation}\n\nDesired policy digest: ${state.policyDigest}`)
|
|
113
|
+
for (const version of state.versions) {
|
|
114
|
+
const packageId = add('package', version.id, `Capability package ${version.id}`, 'Publisher identity is declared. A digest establishes integrity, not publisher authentication.')
|
|
115
|
+
add('release', [version.id, version.digest], `Release ${version.id} ${version.version}`, `Previously observed release digest: ${version.digest}`, { belongs_to: [packageId] })
|
|
116
|
+
}
|
|
117
|
+
for (const item of state.packages) {
|
|
118
|
+
const packageId = add('package', item.id, `Capability package ${item.id}`, 'Publisher identity is declared. A digest establishes integrity, not publisher authentication.')
|
|
119
|
+
const releaseId = add('release', [item.id, item.release.digest], `Release ${item.id} ${item.release.package.version}`, `Release digest: ${item.release.digest}\n\nMode: ${item.mode}`, { belongs_to: [packageId] })
|
|
120
|
+
for (const capability of item.release.package.capabilities) add('outcome', [item.id, capability.id, item.release.digest], `Capability ${capability.id}`, `Provided by release ${item.release.digest}.`, { belongs_to: [releaseId] })
|
|
121
|
+
for (const evaluation of item.release.package.evaluations) add('evaluation', [item.id, item.release.digest, evaluation.id], `Publisher evaluation ${evaluation.id}`, `Publisher-reported outcome: ${evaluation.outcome}\n\nEvidence is bound by the release payload, not independently verified by this graph.`, { evidences: [releaseId] })
|
|
122
|
+
for (const binding of item.bindings) {
|
|
123
|
+
add('binding', [state.enrollment, state.generation, binding.target], `Host binding ${binding.alias}`, `Profile: ${binding.host}\n\nInstalled digest: ${binding.digest}\n\nHost loading and successful exercise require separate evidence.`, { implements: [releaseId], belongs_to: [enrollmentId] })
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
for (const event of events) {
|
|
127
|
+
const releaseId = nodeId('release', [event.package, event.releaseDigest])
|
|
128
|
+
if (!documents.has(releaseId)) throw new Error('event release is absent from adoption history')
|
|
129
|
+
const adoptionId = nodeId('adoption', [state.enrollment, event.generation])
|
|
130
|
+
if (!documents.has(adoptionId)) add('adoption', [state.enrollment, event.generation], `Historical adoption ${state.enrollment}`, `Historical generation: ${event.generation}`)
|
|
131
|
+
const bindingId = nodeId('binding', [state.enrollment, event.generation, event.binding])
|
|
132
|
+
if (!documents.has(bindingId)) add('binding', [state.enrollment, event.generation, event.binding], `Historical host binding ${event.host}`, `Previously reported binding digest: ${event.bindingDigest}`, { implements: [releaseId], belongs_to: [adoptionId] })
|
|
133
|
+
add('event', [state.enrollment, event.id], `Reported ${event.kind} ${event.id}`, `Outcome: ${event.outcome}\n\nCause reported: ${event.cause}\n\nEvidence digest: ${event.evidenceDigest}\n\nReview status: unreviewed caller report.`, { evidences: [bindingId] })
|
|
134
|
+
}
|
|
135
|
+
for (const proposal of capabilityImprovementCandidates({ workspaceRoot: root }).candidates) {
|
|
136
|
+
add('proposal', [state.enrollment, proposal.package, proposal.releaseDigest, proposal.binding, proposal.cause], `Proposed ${proposal.proposal}`, 'A review proposal from reported evidence; no edit or retirement is authorized.', { related: proposal.evidence.map(id => nodeId('event', [state.enrollment, id])) })
|
|
137
|
+
}
|
|
138
|
+
unique([...documents.values()].map(item => item.path), 'graph source path')
|
|
139
|
+
return { schema: 'mnstry.atelier-capability-graph-sources@v1', files: [...documents.values()], canonicalMutation: false }
|
|
140
|
+
}
|