@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,99 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
4
|
+
import { spawnSync } from 'node:child_process'
|
|
5
|
+
import { resolveProjectConfig } from '../project/config.mjs'
|
|
6
|
+
import { openRegularFileNoFollow } from '../project/private-state.mjs'
|
|
7
|
+
import { assessmentDigest } from './assessment.mjs'
|
|
8
|
+
import { assessKnowledgeHealth } from './owner-join.mjs'
|
|
9
|
+
import { createGuidedContribution } from '../knowledge/participatory/index.mjs'
|
|
10
|
+
|
|
11
|
+
// A projection of the existing public workshop identities, not a new model.
|
|
12
|
+
const relationId = 'checklist-workshop'
|
|
13
|
+
const questionId = 'workshop-readiness'
|
|
14
|
+
const sourceId = 'devday:checklist'
|
|
15
|
+
const fieldId = 'kg.relations.supports'
|
|
16
|
+
const exampleBinding = Object.freeze({ storeId: 'devday-workshop-example', sourceId,
|
|
17
|
+
artifactId: 'workshop-checklist', fieldId, actorBindingId: 'invented-workshop-owner' })
|
|
18
|
+
|
|
19
|
+
function read(file) {
|
|
20
|
+
const fd = openRegularFileNoFollow(file)
|
|
21
|
+
try {
|
|
22
|
+
const size = fs.fstatSync(fd).size
|
|
23
|
+
if (size > 65536) throw Error('Workshop input exceeds its 65536-byte bound')
|
|
24
|
+
const bytes = Buffer.alloc(size + 1)
|
|
25
|
+
const count = fs.readSync(fd, bytes, 0, bytes.length, 0)
|
|
26
|
+
if (count !== size) throw Error('Workshop input changed during read')
|
|
27
|
+
return bytes.subarray(0, count)
|
|
28
|
+
} finally { fs.closeSync(fd) }
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Copy the actual shipped public fixture into a NEW disposable workspace. */
|
|
32
|
+
export function initializeKnowledgeHealthWorkshop(destination) {
|
|
33
|
+
const root = path.resolve(destination)
|
|
34
|
+
fs.mkdirSync(root, { recursive: false })
|
|
35
|
+
fs.cpSync(fileURLToPath(new URL('../../fixtures/knowledge-health/workshop/', import.meta.url)), root,
|
|
36
|
+
{ recursive: true, force: false, errorOnExist: true })
|
|
37
|
+
fs.writeFileSync(path.join(root, '.gitignore'), '.atelier-local/\noutput/\n', { flag: 'wx' })
|
|
38
|
+
const git = spawnSync('git', ['init', '--quiet'], { cwd: root, encoding: 'utf8', timeout: 15000,
|
|
39
|
+
env: { PATH: process.env.PATH, GIT_CONFIG_NOSYSTEM: '1',
|
|
40
|
+
GIT_CONFIG_GLOBAL: process.platform === 'win32' ? 'NUL' : '/dev/null' } })
|
|
41
|
+
if (git.status !== 0) throw Error('Git initialization failed for the new disposable workshop')
|
|
42
|
+
return root
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Use the generic owner assessment against the participatory fixture itself.
|
|
46
|
+
* Expected evidence is read unchanged. This function writes no source or plan.
|
|
47
|
+
* A digest revision names source content; it is not an authoring-store head. */
|
|
48
|
+
export function assessKnowledgeHealthWorkshop({ workspaceRoot, authoringBinding = exampleBinding,
|
|
49
|
+
sourceRevisions } = {}) {
|
|
50
|
+
const root = path.resolve(workspaceRoot)
|
|
51
|
+
if (authoringBinding.sourceId !== sourceId || authoringBinding.fieldId !== fieldId)
|
|
52
|
+
throw Error('The existing workshop source and field binding are required')
|
|
53
|
+
const project = resolveProjectConfig({ cwd: root, argv: ['--project', path.join(root, 'atelier.project.json')],
|
|
54
|
+
env: {}, writeLocalState: false })
|
|
55
|
+
const sourceBytes = read(path.join(root, 'records/checklist.md'))
|
|
56
|
+
const planBytes = read(path.join(root, 'knowledge-plan.json'))
|
|
57
|
+
const plan = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(planBytes))
|
|
58
|
+
if (!plan.relations?.some(r => r.id === relationId && r.from === 'checklist' && r.to === 'workshop' && r.predicate === 'supports')
|
|
59
|
+
|| !plan.questions?.some(q => q.id === questionId && q.relations.includes(relationId)))
|
|
60
|
+
throw Error('The participatory workshop plan is required; the pilot fixture is separate')
|
|
61
|
+
const text = new TextDecoder('utf-8', { fatal: true }).decode(sourceBytes)
|
|
62
|
+
const matches = [...text.matchAll(/^ supports: (\[[^\r\n]*\])$/gm)]
|
|
63
|
+
if (matches.length !== 1) throw Error('One exact inline workshop supports declaration is required')
|
|
64
|
+
const match = matches[0], prefix = text.slice(0, match.index) + ' supports: '
|
|
65
|
+
const start = Buffer.byteLength(prefix), quote = match[1], digest = assessmentDigest(sourceBytes)
|
|
66
|
+
const revisions = sourceRevisions ?? ['checklist.md', 'workshop.md'].map(p => ({ repo: 'records', path: p,
|
|
67
|
+
revision: `content-sha256:${assessmentDigest(read(path.join(root, 'records', p)))}` }))
|
|
68
|
+
const revision = revisions.find(r => r.repo === 'records' && r.path === 'checklist.md')?.revision
|
|
69
|
+
if (!revision) throw Error('The workshop checklist source revision is required')
|
|
70
|
+
const binding = { source: { id: sourceId, repo: 'records', path: 'checklist.md', sha256: digest, revision },
|
|
71
|
+
field: { id: fieldId, pointer: '/kg/relations/supports', format: 'markdown-inline-array',
|
|
72
|
+
start, end: start + Buffer.byteLength(quote), quote }, authoring: structuredClone(authoringBinding) }
|
|
73
|
+
const observed = assessKnowledgeHealth({ project, relationId, questionId, sourceRevisions: revisions,
|
|
74
|
+
binding, currentSourceBytes: sourceBytes })
|
|
75
|
+
const graphRun = observed.retrievalEvaluation?.cases.find(c => c.id === questionId)?.runs.graph
|
|
76
|
+
return { ...observed, workshop: { sourceId, relationId, questionId, binding,
|
|
77
|
+
sourceSha256: digest, planSha256: assessmentDigest(planBytes),
|
|
78
|
+
expectedEvidence: structuredClone(plan.questions.find(q => q.id === questionId).expectedEvidence),
|
|
79
|
+
evaluationStaleSources: graphRun ? [...graphRun.stale] : null,
|
|
80
|
+
evaluationStatus: graphRun?.status ?? 'unavailable',
|
|
81
|
+
sourceRevisionKind: sourceRevisions ? 'owner-supplied' : 'content-digest',
|
|
82
|
+
sourceWritten: false, expectedEvidenceRefreshed: false, humanAcceptance: false } }
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Compose the existing guided adapter and native caller with this observation.
|
|
86
|
+
* The host supplies actual native APIs and a current join. All writes and
|
|
87
|
+
* recovery remain in the existing native caller/store; none happen here. */
|
|
88
|
+
export function prepareKnowledgeHealthWorkshopChoice({ observation, adapter, caller, same, ...input } = {}) {
|
|
89
|
+
const assessment = observation?.assessment, finding = assessment?.findings[0]
|
|
90
|
+
const joined = input.joined, reference = joined?.finding?.reference, evidence = assessment?.evidence
|
|
91
|
+
if (!finding || finding.source.id !== sourceId || evidence.relationId !== relationId
|
|
92
|
+
|| evidence.questionId !== questionId || !reference
|
|
93
|
+
|| reference.checkId !== finding.check.id || reference.checkVersion !== finding.check.version
|
|
94
|
+
|| reference.planSha256 !== evidence.planSha256 || reference.readSetSha256 !== evidence.readSetSha256
|
|
95
|
+
|| reference.comparisonSha256 !== evidence.comparisonSha256
|
|
96
|
+
|| joined.document?.binding.sourceId !== sourceId || joined.document?.binding.fieldId !== fieldId)
|
|
97
|
+
throw Error('A current native join for this exact workshop observation is required')
|
|
98
|
+
return createGuidedContribution({ adapter, caller, same }).prepareChoice(input)
|
|
99
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { learningDigest, validateLearningInput } from './store.mjs'
|
|
2
|
+
import { boundedLearningValue } from './contracts.mjs'
|
|
3
|
+
import { assertDocument } from '../capabilities/package.mjs'
|
|
4
|
+
|
|
5
|
+
// Preserve exact version, binding and reported cause. Metadata never becomes a
|
|
6
|
+
// fabricated user quote, a verified causal claim or an automatic lesson.
|
|
7
|
+
export function capabilityEventToLearningInput({ event, scope, feedback = null }) {
|
|
8
|
+
boundedLearningValue({ event, scope, feedback })
|
|
9
|
+
assertDocument(event, 'event')
|
|
10
|
+
if (event.ext && Object.keys(event.ext).length) throw new Error('capability event extensions are not admitted')
|
|
11
|
+
let cause = event.cause
|
|
12
|
+
if (feedback !== null) {
|
|
13
|
+
const fields = ['event', 'subject', 'historyDigest', 'reportedCause', 'causeMapping', 'recorded', 'authority']
|
|
14
|
+
if (!feedback || Object.keys(feedback).sort().join() !== fields.sort().join() ||
|
|
15
|
+
learningDigest(feedback.event) !== learningDigest(event) || feedback.authority !== 'none' || feedback.recorded !== false ||
|
|
16
|
+
!['skill', 'host', 'tool', 'configuration', 'context', 'provider', 'unknown'].includes(feedback.reportedCause) ||
|
|
17
|
+
!/^sha256:[a-f0-9]{64}$/.test(feedback.historyDigest) ||
|
|
18
|
+
!feedback.subject || Object.keys(feedback.subject).sort().join() !== 'digest,id' ||
|
|
19
|
+
typeof feedback.subject.id !== 'string' || !/^sha256:[a-f0-9]{64}$/.test(feedback.subject.digest)) throw new Error('invalid harness feedback provenance')
|
|
20
|
+
cause = feedback.reportedCause
|
|
21
|
+
const mapped = ['context', 'provider'].includes(cause) ? 'unknown' : cause
|
|
22
|
+
if (mapped !== event.cause || feedback.causeMapping !== (mapped === cause ? 'exact' : 'unsupported-steward-cause-preserved-in-wrapper')) throw new Error('feedback cause mapping mismatch')
|
|
23
|
+
}
|
|
24
|
+
const input = {
|
|
25
|
+
id: `capability:${event.id}`,
|
|
26
|
+
signal: event.kind === 'exercise' && event.outcome === 'passed' ? 'successful-run' : event.outcome === 'failed' && cause === 'tool' ? 'tool-failure' : 'exception',
|
|
27
|
+
text: `Caller-reported capability metadata: ${event.kind}; outcome: ${event.outcome}; reported cause: ${cause}; package: ${event.package}; release: ${event.releaseDigest}; binding: ${event.binding}; binding digest: ${event.bindingDigest}; session: ${event.session}. Original feedback text and causal verification are unavailable.`,
|
|
28
|
+
interpretation: 'tool', scope,
|
|
29
|
+
source: { ref: `capability-event:${event.id}`, digest: learningDigest(feedback ?? event), locator: event.evidenceDigest },
|
|
30
|
+
}
|
|
31
|
+
if (validateLearningInput('capture', input).length) throw new Error('capability event cannot fit learning contract')
|
|
32
|
+
return input
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Existing skill observations contain metadata, not the owner's actual words.
|
|
36
|
+
// Preserve that limitation rather than manufacturing a transcript or lesson.
|
|
37
|
+
export function skillObservationToLearningInput({ receipt, scope }) {
|
|
38
|
+
const observation = receipt?.observation
|
|
39
|
+
if (receipt?.schema !== 'mnstry.atelier-skill-observation-receipt@v1' || receipt.ok !== true
|
|
40
|
+
|| typeof observation?.id !== 'string' || typeof observation.workflowKey !== 'string'
|
|
41
|
+
|| typeof observation.signal !== 'string' || typeof observation.outcome !== 'string') {
|
|
42
|
+
throw new Error('valid skill observation receipt required')
|
|
43
|
+
}
|
|
44
|
+
const supported = { 'user-correction': 'user-correction', 'successful-run': 'successful-run', 'tool-failure': 'tool-failure' }
|
|
45
|
+
const input = {
|
|
46
|
+
id: `skill:${observation.id}`, signal: supported[observation.signal] ?? 'exception',
|
|
47
|
+
text: `Skill metadata: ${observation.signal}; outcome: ${observation.outcome}; workflow: ${observation.workflowKey}. Original feedback text is unavailable.`,
|
|
48
|
+
interpretation: 'tool', scope,
|
|
49
|
+
source: { ref: `skill-receipt:${observation.id}`, digest: learningDigest(receipt) },
|
|
50
|
+
}
|
|
51
|
+
if (validateLearningInput('capture', input).length) throw new Error('skill observation cannot fit learning contract')
|
|
52
|
+
return input
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// A legacy proposal is evidence for a new proposal. Its old status/receipt can
|
|
56
|
+
// never activate anything in this workspace. The caller selects exact local
|
|
57
|
+
// evidence and scope; review remains a separate operation.
|
|
58
|
+
export function legacyLessonToLearningInput({ legacy, id, scope, evidenceIds, artifact }) {
|
|
59
|
+
if (!legacy || typeof legacy.id !== 'string' || typeof legacy.claim !== 'string'
|
|
60
|
+
|| !legacy.claim.trim()) throw new Error('legacy lesson claim required')
|
|
61
|
+
const input = { id, title: artifact?.name, principle: legacy.claim,
|
|
62
|
+
rationale: 'Imported proposal; its prior acceptance and activation are not authority in this workspace.',
|
|
63
|
+
exceptions: [], evidenceIds, scope, artifact,
|
|
64
|
+
}
|
|
65
|
+
if (validateLearningInput('propose', input).length) throw new Error('legacy proposal cannot fit learning contract')
|
|
66
|
+
return { input, provenance: { sourceId: legacy.id, sourceDigest: learningDigest(legacy),
|
|
67
|
+
authorityTransferred: false } }
|
|
68
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { learningDigest, validateLearningInput } from './store.mjs'
|
|
2
|
+
|
|
3
|
+
function requireSnapshot(snapshot) {
|
|
4
|
+
if (!snapshot || typeof snapshot.workspaceId !== 'string'
|
|
5
|
+
|| !Array.isArray(snapshot.observations) || !Array.isArray(snapshot.lessons)
|
|
6
|
+
|| !Number.isSafeInteger(snapshot.revision)) throw new Error('invalid learning snapshot')
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
// Group metadata first. The plan never interprets a correction as accepted
|
|
10
|
+
// guidance, and repeated observations are not independent corroboration.
|
|
11
|
+
export function planLearningWork(snapshot) {
|
|
12
|
+
requireSnapshot(snapshot)
|
|
13
|
+
const groups = new Map()
|
|
14
|
+
const addressed = new Set(snapshot.lessons.flatMap(lesson => lesson.evidenceIds))
|
|
15
|
+
for (const observation of snapshot.observations) {
|
|
16
|
+
if (addressed.has(observation.id)) continue
|
|
17
|
+
const key = learningDigest(observation.scope)
|
|
18
|
+
if (!groups.has(key)) groups.set(key, { scope: observation.scope, observationIds: [], signals: new Set() })
|
|
19
|
+
const group = groups.get(key)
|
|
20
|
+
group.observationIds.push(observation.id)
|
|
21
|
+
group.signals.add(observation.signal)
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
schema: 'atelier-learning-plan@v1', workspaceId: snapshot.workspaceId, revision: snapshot.revision,
|
|
25
|
+
groups: [...groups.values()].map(group => ({ scope: group.scope,
|
|
26
|
+
observationIds: group.observationIds,
|
|
27
|
+
route: group.signals.has('exception') ? 'focused-reasoning-or-human' : 'bounded-proposal',
|
|
28
|
+
reason: group.signals.has('exception') ? 'exception-needs-context' : 'unaddressed-observations',
|
|
29
|
+
})),
|
|
30
|
+
authority: { execution: false, acceptance: false, activation: false },
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function prepareLearningBatch({ snapshot, observationIds, maxInputBytes = 32 * 1024 }) {
|
|
35
|
+
requireSnapshot(snapshot)
|
|
36
|
+
if (!Array.isArray(observationIds) || !observationIds.length || observationIds.length > 32
|
|
37
|
+
|| new Set(observationIds).size !== observationIds.length
|
|
38
|
+
|| !Number.isSafeInteger(maxInputBytes) || maxInputBytes < 1 || maxInputBytes > 128 * 1024) {
|
|
39
|
+
throw new Error('invalid learning batch bounds')
|
|
40
|
+
}
|
|
41
|
+
const observations = observationIds.map(id => snapshot.observations.find(item => item.id === id))
|
|
42
|
+
if (observations.some(item => !item)) throw new Error('learning batch has unavailable evidence')
|
|
43
|
+
if (new Set(observations.map(item => learningDigest(item.scope))).size !== 1) throw new Error('learning batch must have one exact scope')
|
|
44
|
+
const payload = { schema: 'atelier-learning-batch@v1', workspaceId: snapshot.workspaceId,
|
|
45
|
+
revision: snapshot.revision, scope: observations[0].scope, observations: structuredClone(observations),
|
|
46
|
+
authority: { inputIsEvidence: true, acceptance: false, activation: false },
|
|
47
|
+
}
|
|
48
|
+
const bytes = Buffer.byteLength(JSON.stringify(payload))
|
|
49
|
+
if (bytes > maxInputBytes) throw new Error('learning batch exceeds input byte limit')
|
|
50
|
+
return { payload, digest: learningDigest(payload), bytes }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// A host supplies qualified execution, exact-payload authorization and a durable
|
|
54
|
+
// reservation implementation. No provider, network transport or credential is
|
|
55
|
+
// embedded here. Callers receive inert proposals, never implicit store writes.
|
|
56
|
+
export async function draftLearningProposals({ batch, adapter, authorization, budget,
|
|
57
|
+
maxOutputBytes = 32 * 1024, maxProposals = 8 }) {
|
|
58
|
+
// Retain the exact authorized bytes across asynchronous host calls.
|
|
59
|
+
try { batch = structuredClone(batch) }
|
|
60
|
+
catch { throw new Error('invalid learning batch') }
|
|
61
|
+
if (!batch?.payload || batch.digest !== learningDigest(batch.payload)
|
|
62
|
+
|| batch.bytes !== Buffer.byteLength(JSON.stringify(batch.payload))
|
|
63
|
+
|| batch.bytes > 128 * 1024 || batch.payload.schema !== 'atelier-learning-batch@v1'
|
|
64
|
+
|| !Array.isArray(batch.payload.observations) || !batch.payload.observations.length
|
|
65
|
+
|| batch.payload.observations.length > 32) throw new Error('invalid learning batch')
|
|
66
|
+
if (!adapter || typeof adapter.id !== 'string' || !adapter.id || typeof adapter.version !== 'string'
|
|
67
|
+
|| !adapter.version || typeof adapter.draft !== 'function'
|
|
68
|
+
|| adapter.qualification?.task !== 'lesson-proposal' || !adapter.qualification?.evidenceRef) {
|
|
69
|
+
throw new Error('qualified lesson proposal adapter required')
|
|
70
|
+
}
|
|
71
|
+
if (authorization?.workspaceId !== batch.payload.workspaceId || authorization?.payloadDigest !== batch.digest
|
|
72
|
+
|| authorization?.adapterId !== adapter.id || authorization?.adapterVersion !== adapter.version
|
|
73
|
+
|| authorization?.permitted !== true) throw new Error('exact host authorization required')
|
|
74
|
+
if (typeof budget?.reserve !== 'function' || typeof budget?.settle !== 'function') throw new Error('durable host budget required')
|
|
75
|
+
if (!Number.isSafeInteger(maxOutputBytes) || maxOutputBytes < 1 || maxOutputBytes > 128 * 1024
|
|
76
|
+
|| !Number.isSafeInteger(maxProposals) || maxProposals < 1 || maxProposals > 16) throw new Error('invalid learning output bounds')
|
|
77
|
+
const key = learningDigest({ batch: batch.digest, adapter: adapter.id, version: adapter.version,
|
|
78
|
+
qualification: adapter.qualification, maxOutputBytes, maxProposals })
|
|
79
|
+
const adapterId = adapter.id, adapterVersion = adapter.version
|
|
80
|
+
const qualificationRef = adapter.qualification.evidenceRef
|
|
81
|
+
const draft = adapter.draft.bind(adapter)
|
|
82
|
+
const reservation = await budget.reserve({ key, calls: 1, inputBytes: batch.bytes, maxOutputBytes })
|
|
83
|
+
if (reservation?.status !== 'reserved' || typeof reservation.id !== 'string' || !reservation.id) {
|
|
84
|
+
throw new Error('learning budget unavailable or attempt already exists')
|
|
85
|
+
}
|
|
86
|
+
let response
|
|
87
|
+
try {
|
|
88
|
+
response = await draft(structuredClone(batch.payload), { maxOutputBytes, maxProposals })
|
|
89
|
+
} catch {
|
|
90
|
+
await budget.settle({ reservationId: reservation.id, key, outcome: 'unknown', usage: null })
|
|
91
|
+
throw new Error('learning adapter outcome unknown; reconcile before retry')
|
|
92
|
+
}
|
|
93
|
+
// Reconcile a returned attempt even if its proposed content fails validation.
|
|
94
|
+
const usage = measuredUsage(response?.usage)
|
|
95
|
+
await budget.settle({ reservationId: reservation.id, key, outcome: 'returned', usage })
|
|
96
|
+
let proposals
|
|
97
|
+
try {
|
|
98
|
+
const bytes = Buffer.byteLength(JSON.stringify(response?.proposals))
|
|
99
|
+
if (bytes > maxOutputBytes || !Array.isArray(response?.proposals)
|
|
100
|
+
|| response.proposals.length > maxProposals) throw new Error()
|
|
101
|
+
const known = new Set(batch.payload.observations.map(item => item.id))
|
|
102
|
+
const ids = new Set()
|
|
103
|
+
proposals = response.proposals.map(proposal => {
|
|
104
|
+
if (validateLearningInput('propose', proposal).length || ids.has(proposal.id)
|
|
105
|
+
|| learningDigest(proposal.scope) !== learningDigest(batch.payload.scope)
|
|
106
|
+
|| proposal.evidenceIds.some(id => !known.has(id))) throw new Error()
|
|
107
|
+
ids.add(proposal.id)
|
|
108
|
+
return structuredClone(proposal)
|
|
109
|
+
})
|
|
110
|
+
} catch { throw new Error('learning adapter returned invalid proposals') }
|
|
111
|
+
return { schema: 'atelier-learning-drafts@v1', batchDigest: batch.digest, adapterId,
|
|
112
|
+
adapterVersion, qualificationRef,
|
|
113
|
+
reservationId: reservation.id, proposals, usage,
|
|
114
|
+
authority: { stored: false, acceptance: false, activation: false },
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function measuredUsage(value) {
|
|
119
|
+
if (value == null) return null
|
|
120
|
+
if (typeof value !== 'object' || Array.isArray(value)) return null
|
|
121
|
+
const result = {}
|
|
122
|
+
for (const key of ['inputTokens', 'outputTokens', 'durationMs']) {
|
|
123
|
+
result[key] = Number.isSafeInteger(value[key]) && value[key] >= 0 ? value[key] : null
|
|
124
|
+
}
|
|
125
|
+
// No price estimate or quota-percentage conversion is invented here.
|
|
126
|
+
return result
|
|
127
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import Ajv2020 from 'ajv/dist/2020.js';
|
|
4
|
+
import addFormats from 'ajv-formats';
|
|
5
|
+
import { canonicalize } from '../attestation/jcs.mjs';
|
|
6
|
+
|
|
7
|
+
export const LEARNING_MAX_BYTES = 256 * 1024;
|
|
8
|
+
export const LEARNING_MAX_EVENTS = 10000;
|
|
9
|
+
export const LEARNING_MAX_JOURNAL_BYTES = 32 * 1024 * 1024;
|
|
10
|
+
// Minimum ordinary-write headroom; the store also reserves a complete event
|
|
11
|
+
// and one event slot for every active binding after each prospective write.
|
|
12
|
+
export const LEARNING_WITHDRAWAL_RESERVE_BYTES = 1024 * 1024;
|
|
13
|
+
export const LEARNING_WITHDRAWAL_RESERVE_EVENTS = 128;
|
|
14
|
+
export const learningSchema = JSON.parse(fs.readFileSync(new URL('../../contracts/atelier-learning.v1.schema.json', import.meta.url), 'utf8'));
|
|
15
|
+
const ajv = new Ajv2020({ allErrors: true, strict: false });
|
|
16
|
+
addFormats(ajv); ajv.addSchema(learningSchema);
|
|
17
|
+
const validators = new Map();
|
|
18
|
+
const operations = new Set(['capture', 'propose', 'decide', 'activate', 'withdraw']);
|
|
19
|
+
|
|
20
|
+
// Canonical JSON, not a signature or evidence of the identity of the caller.
|
|
21
|
+
export function learningDigest(value) {
|
|
22
|
+
return `sha256:${createHash('sha256').update(canonicalize(value)).digest('hex')}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function boundedLearningValue(value) {
|
|
26
|
+
const visited = new Set();
|
|
27
|
+
function walk(item, depth) {
|
|
28
|
+
if (depth > 24) throw new Error('learning value exceeds depth ceiling');
|
|
29
|
+
if (item === null || typeof item === 'boolean') return;
|
|
30
|
+
if (typeof item === 'string') {
|
|
31
|
+
if (item.includes('\u0000') || item.length > LEARNING_MAX_BYTES) throw new Error('invalid learning text');
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (typeof item === 'number' && Number.isFinite(item)) return;
|
|
35
|
+
if (typeof item !== 'object' || visited.has(item)) throw new Error('learning value must be JSON');
|
|
36
|
+
if (!Array.isArray(item) && ![null, Object.prototype].includes(Object.getPrototypeOf(item))) throw new Error('learning value must be plain JSON');
|
|
37
|
+
visited.add(item);
|
|
38
|
+
const keys = Reflect.ownKeys(item);
|
|
39
|
+
if (keys.length > 1024) throw new Error('learning value exceeds member ceiling');
|
|
40
|
+
for (const key of keys) {
|
|
41
|
+
if (typeof key !== 'string') throw new Error('learning value must be plain JSON');
|
|
42
|
+
if (Array.isArray(item) && key === 'length') continue;
|
|
43
|
+
const descriptor = Object.getOwnPropertyDescriptor(item, key);
|
|
44
|
+
if (!descriptor.enumerable || !Object.hasOwn(descriptor, 'value')) throw new Error('learning value must be plain JSON');
|
|
45
|
+
walk(descriptor.value, depth + 1);
|
|
46
|
+
}
|
|
47
|
+
if (Array.isArray(item) && Object.keys(item).length !== item.length) throw new Error('learning array must be dense');
|
|
48
|
+
visited.delete(item);
|
|
49
|
+
}
|
|
50
|
+
walk(value, 0);
|
|
51
|
+
if (Buffer.byteLength(canonicalize(value)) > LEARNING_MAX_BYTES) throw new Error('learning value exceeds byte ceiling');
|
|
52
|
+
return structuredClone(value);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function validateLearningValue(definition, value) {
|
|
56
|
+
try { boundedLearningValue(value); } catch (error) { return [error.message]; }
|
|
57
|
+
if (!Object.hasOwn(learningSchema.$defs, definition)) return ['unknown learning definition'];
|
|
58
|
+
if (!validators.has(definition)) validators.set(definition, ajv.getSchema(`${learningSchema.$id}#/$defs/${definition}`));
|
|
59
|
+
const validate = validators.get(definition);
|
|
60
|
+
return validate(value) ? [] : ['learning document does not match the contract'];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function validateLearningInput(operation, input) {
|
|
64
|
+
return operations.has(operation) ? validateLearningValue(`${operation}Input`, input) : ['unknown learning operation'];
|
|
65
|
+
}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { execFileSync } from 'node:child_process';
|
|
4
|
+
import { canonicalize } from '../attestation/jcs.mjs';
|
|
5
|
+
import { withPrivateLock, publishPrivateFile, createVerifiedFileSequence, isPendingPrivateWrite } from '../project/durable-state.mjs';
|
|
6
|
+
import { ensureContainedPrivateDirectory, openRegularFileNoFollow } from '../project/private-state.mjs';
|
|
7
|
+
import { learningDigest, boundedLearningValue, validateLearningValue, LEARNING_MAX_BYTES, LEARNING_MAX_EVENTS, LEARNING_MAX_JOURNAL_BYTES, LEARNING_WITHDRAWAL_RESERVE_BYTES, LEARNING_WITHDRAWAL_RESERVE_EVENTS } from './contracts.mjs';
|
|
8
|
+
export { learningDigest, validateLearningInput, validateLearningValue } from './contracts.mjs';
|
|
9
|
+
|
|
10
|
+
const prefix = 'mnstry.atelier-learning-';
|
|
11
|
+
const schema = kind => `${prefix}${kind}@v1`;
|
|
12
|
+
function refuse(code, message) { throw Object.assign(new Error(message), { code }); }
|
|
13
|
+
function valid(definition, value) {
|
|
14
|
+
const errors = validateLearningValue(definition, value);
|
|
15
|
+
if (errors.length) refuse('LEARNING_INVALID', errors[0]);
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
function seal(value) { return { ...value, digest: learningDigest(value) }; }
|
|
19
|
+
function unsealed(value) { const { digest, ...body } = value; return body; }
|
|
20
|
+
function same(left, right) { return canonicalize(left) === canonicalize(right); }
|
|
21
|
+
function latestDecision(state, lessonId) { return state.decisions.findLast(record => record.lessonId === lessonId); }
|
|
22
|
+
function lessonFor(state, lessonId, digest) {
|
|
23
|
+
const lesson = state.lessons.find(record => record.id === lessonId);
|
|
24
|
+
if (!lesson) refuse('LEARNING_MISSING', 'lesson does not exist');
|
|
25
|
+
if (digest !== undefined && lesson.digest !== digest) refuse('LEARNING_STALE', 'lesson digest changed or does not match');
|
|
26
|
+
return lesson;
|
|
27
|
+
}
|
|
28
|
+
function activationState(state, id) { return state.activationStates.find(record => record.activationId === id); }
|
|
29
|
+
function activeFor(state, lessonId, harnessId) {
|
|
30
|
+
return state.activations.filter(record => record.lessonId === lessonId &&
|
|
31
|
+
(harnessId === undefined || record.harnessId === harnessId) && activationState(state, record.id)?.status === 'active');
|
|
32
|
+
}
|
|
33
|
+
function initial(workspaceId) {
|
|
34
|
+
return { schema: schema('snapshot'), workspaceId, revision: 0, observations: [], lessons: [], decisions: [], activations: [], withdrawals: [], activationStates: [], events: [], headDigest: null, journalBytes: 0 };
|
|
35
|
+
}
|
|
36
|
+
function common(state, actor, createdAt, kind, id) {
|
|
37
|
+
return { schema: schema(kind), id, workspaceId: state.workspaceId, revision: state.revision + 1, actor, createdAt };
|
|
38
|
+
}
|
|
39
|
+
function generatedId(kind, requestId) { return `${kind}:${learningDigest(requestId).slice(7, 39)}`; }
|
|
40
|
+
function makeRecord(state, request, actor, createdAt) {
|
|
41
|
+
const input = request.input, operation = request.operation;
|
|
42
|
+
if (request.expectedRevision !== state.revision) refuse('LEARNING_STALE', 'learning revision changed; reload before deciding');
|
|
43
|
+
if (['decide', 'activate', 'withdraw'].includes(operation) && actor.kind !== 'human') refuse('LEARNING_AUTHORITY', 'this operation requires an explicitly asserted local human actor');
|
|
44
|
+
if (operation === 'capture') {
|
|
45
|
+
if (state.observations.some(record => record.id === input.id)) refuse('LEARNING_DUPLICATE', 'observation identifier already exists');
|
|
46
|
+
if (input.lessonId && lessonFor(state, input.lessonId).scope.project !== input.scope.project) refuse('LEARNING_SCOPE', 'observation and lesson projects differ');
|
|
47
|
+
return seal({ ...common(state, actor, createdAt, 'observation', input.id), ...input });
|
|
48
|
+
}
|
|
49
|
+
if (operation === 'propose') {
|
|
50
|
+
if (state.lessons.some(record => record.id === input.id)) refuse('LEARNING_DUPLICATE', 'lesson identifier already exists');
|
|
51
|
+
const evidence = input.evidenceIds.map(id => {
|
|
52
|
+
const record = state.observations.find(item => item.id === id);
|
|
53
|
+
if (!record) refuse('LEARNING_MISSING', 'proposal evidence does not exist');
|
|
54
|
+
if (record.scope.project !== input.scope.project) refuse('LEARNING_SCOPE', 'proposal evidence cannot cross projects');
|
|
55
|
+
return { id: record.id, digest: record.digest };
|
|
56
|
+
});
|
|
57
|
+
if (input.supersedes) {
|
|
58
|
+
const prior = lessonFor(state, input.supersedes);
|
|
59
|
+
if (!same(prior.scope, input.scope) || prior.artifact.kind !== input.artifact.kind || prior.artifact.name !== input.artifact.name) refuse('LEARNING_SCOPE', 'replacement must retain the exact scope and artifact slot');
|
|
60
|
+
}
|
|
61
|
+
return seal({ ...common(state, actor, createdAt, 'lesson', input.id), ...input, artifact: seal(input.artifact), evidence });
|
|
62
|
+
}
|
|
63
|
+
if (operation === 'decide') {
|
|
64
|
+
lessonFor(state, input.lessonId, input.lessonDigest);
|
|
65
|
+
if (activeFor(state, input.lessonId).length) refuse('LEARNING_ACTIVE', 'withdraw active lesson before changing its decision');
|
|
66
|
+
return seal({ ...common(state, actor, createdAt, 'decision', generatedId('decision', request.requestId)), ...input });
|
|
67
|
+
}
|
|
68
|
+
if (operation === 'activate') {
|
|
69
|
+
const lesson = lessonFor(state, input.lessonId, input.lessonDigest);
|
|
70
|
+
const decision = latestDecision(state, lesson.id);
|
|
71
|
+
if (!decision || decision.id !== input.decisionId || decision.verdict !== 'accepted' || decision.lessonDigest !== lesson.digest) refuse('LEARNING_DECISION', 'activation requires the latest exact accepted decision');
|
|
72
|
+
if (state.activations.some(record => record.id === input.id)) refuse('LEARNING_DUPLICATE', 'activation identifier already exists');
|
|
73
|
+
if (activeFor(state, lesson.id, input.harnessId).length) refuse('LEARNING_ACTIVE', 'lesson is already active for this harness');
|
|
74
|
+
if (state.activations.some(record => record.lessonId === lesson.id && record.harnessId === input.harnessId && activationState(state, record.id)?.status === 'superseded')) refuse('LEARNING_SUPERSEDED', 'superseded lesson requires a new reviewed proposal');
|
|
75
|
+
if (lesson.supersedes) {
|
|
76
|
+
const prior = lessonFor(state, lesson.supersedes);
|
|
77
|
+
if (!same(prior.scope, lesson.scope)) refuse('LEARNING_SCOPE', 'replacement scope changed');
|
|
78
|
+
}
|
|
79
|
+
return seal({ ...common(state, actor, createdAt, 'activation', input.id), ...input, artifactDigest: lesson.artifact.digest });
|
|
80
|
+
}
|
|
81
|
+
const activation = state.activations.find(record => record.id === input.activationId);
|
|
82
|
+
if (!activation) refuse('LEARNING_MISSING', 'activation does not exist');
|
|
83
|
+
if (activationState(state, activation.id)?.status !== 'active') refuse('LEARNING_INACTIVE', 'activation is no longer active');
|
|
84
|
+
return seal({ ...common(state, actor, createdAt, 'withdrawal', generatedId('withdrawal', request.requestId)), ...input });
|
|
85
|
+
}
|
|
86
|
+
function applyEvent(event, state) {
|
|
87
|
+
const operation = event.request.operation, record = event.record;
|
|
88
|
+
const target = { capture: 'observations', propose: 'lessons', decide: 'decisions', activate: 'activations', withdraw: 'withdrawals' }[operation];
|
|
89
|
+
state[target].push(record);
|
|
90
|
+
if (operation === 'activate') {
|
|
91
|
+
const lesson = lessonFor(state, record.lessonId);
|
|
92
|
+
if (lesson.supersedes) {
|
|
93
|
+
for (const prior of activeFor(state, lesson.supersedes, record.harnessId)) {
|
|
94
|
+
Object.assign(activationState(state, prior.id), { status: 'superseded', eventId: event.id });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
state.activationStates.push({ activationId: record.id, status: 'active', eventId: event.id });
|
|
98
|
+
}
|
|
99
|
+
if (operation === 'withdraw') Object.assign(activationState(state, record.activationId), { status: 'withdrawn', eventId: event.id });
|
|
100
|
+
state.events.push(event); state.revision = event.revision; state.headDigest = event.digest;
|
|
101
|
+
return state;
|
|
102
|
+
}
|
|
103
|
+
function publicSnapshot(state) {
|
|
104
|
+
const { events, headDigest, journalBytes, ...snapshot } = state;
|
|
105
|
+
return structuredClone({ ...snapshot, headDigest });
|
|
106
|
+
}
|
|
107
|
+
function boundedRead(file) {
|
|
108
|
+
const fd = openRegularFileNoFollow(file);
|
|
109
|
+
try {
|
|
110
|
+
if (fs.fstatSync(fd).size > LEARNING_MAX_BYTES * 3) refuse('LEARNING_LIMIT', 'learning journal byte ceiling exceeded');
|
|
111
|
+
const bytes = fs.readFileSync(fd);
|
|
112
|
+
if (bytes.length > LEARNING_MAX_BYTES * 3) refuse('LEARNING_LIMIT', 'learning journal byte ceiling exceeded');
|
|
113
|
+
return new TextDecoder('utf-8', { fatal: true }).decode(bytes);
|
|
114
|
+
} finally { fs.closeSync(fd); }
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function readWorkspaceIdentity(file) {
|
|
118
|
+
const text = boundedRead(file);
|
|
119
|
+
let document;
|
|
120
|
+
try { document = JSON.parse(text); } catch { refuse('LEARNING_HISTORY', 'learning workspace identity contains invalid JSON'); }
|
|
121
|
+
return valid('workspace', document);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Local integrity and lifecycle, not remote authentication, semantic truth,
|
|
125
|
+
// executable installation, or an authority to read provenance locations.
|
|
126
|
+
export function createLearningStore({ workspaceRoot = process.cwd(), workspaceId } = {}) {
|
|
127
|
+
valid('id', workspaceId);
|
|
128
|
+
const root = fs.realpathSync(workspaceRoot);
|
|
129
|
+
function placement() {
|
|
130
|
+
const env = Object.fromEntries(Object.entries(process.env).filter(([key]) => !key.toUpperCase().startsWith('GIT_')));
|
|
131
|
+
try {
|
|
132
|
+
if (execFileSync('git', ['-C', root, 'ls-files', '-z', '--', '.atelier-local'], { env, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] })) throw new Error('tracked');
|
|
133
|
+
execFileSync('git', ['-C', root, 'check-ignore', '--quiet', '.atelier-local/'], { env, stdio: 'ignore' });
|
|
134
|
+
} catch { refuse('LEARNING_PRIVATE', 'learning requires ignored, untracked .atelier-local/ in a Git workspace'); }
|
|
135
|
+
}
|
|
136
|
+
placement();
|
|
137
|
+
function directory(...parts) {
|
|
138
|
+
return ensureContainedPrivateDirectory({ workspaceRoot: root, directory: path.join(root, '.atelier-local', 'learning', ...parts), label: 'learning state' });
|
|
139
|
+
}
|
|
140
|
+
const stateDir = directory(), eventDir = directory('events');
|
|
141
|
+
const identityPath = path.join(stateDir, 'workspace.json');
|
|
142
|
+
const identity = { schema: schema('workspace'), workspaceId };
|
|
143
|
+
withPrivateLock(path.join(stateDir, 'operation.lock'), () => {
|
|
144
|
+
if (fs.existsSync(identityPath)) {
|
|
145
|
+
const stored = readWorkspaceIdentity(identityPath);
|
|
146
|
+
if (!same(stored, identity)) refuse('LEARNING_WORKSPACE', 'learning workspace identity differs');
|
|
147
|
+
} else {
|
|
148
|
+
if (fs.readdirSync(eventDir).filter(name => !isPendingPrivateWrite(name)).length) refuse('LEARNING_HISTORY', 'learning workspace identity is missing');
|
|
149
|
+
publishPrivateFile(identityPath, canonicalize(identity) + '\n');
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
const readSequence = createVerifiedFileSequence({
|
|
153
|
+
directory: eventDir,
|
|
154
|
+
initial: () => initial(workspaceId),
|
|
155
|
+
apply(text, state, index, name) {
|
|
156
|
+
if (name !== `${String(index).padStart(10, '0')}.json`) refuse('LEARNING_HISTORY', 'learning history is not contiguous');
|
|
157
|
+
if (Buffer.byteLength(text) > LEARNING_MAX_BYTES * 3) refuse('LEARNING_LIMIT', 'learning journal byte ceiling exceeded');
|
|
158
|
+
let event;
|
|
159
|
+
try { event = JSON.parse(text); } catch { refuse('LEARNING_HISTORY', 'learning history contains invalid JSON'); }
|
|
160
|
+
valid('event', event);
|
|
161
|
+
if (event.workspaceId !== workspaceId || event.revision !== index || event.previousDigest !== state.headDigest || event.id !== `event:${index}` || event.digest !== learningDigest(unsealed(event))) refuse('LEARNING_HISTORY', 'learning history integrity mismatch');
|
|
162
|
+
if (event.requestDigest !== learningDigest({ request: event.request, actor: event.actor })) refuse('LEARNING_HISTORY', 'learning request integrity mismatch');
|
|
163
|
+
if (state.events.some(item => item.request.requestId === event.request.requestId)) refuse('LEARNING_HISTORY', 'learning history repeats a request');
|
|
164
|
+
const expected = makeRecord(state, event.request, event.actor, event.createdAt);
|
|
165
|
+
if (!same(expected, event.record)) refuse('LEARNING_HISTORY', 'learning record integrity mismatch');
|
|
166
|
+
state.journalBytes += Buffer.byteLength(text);
|
|
167
|
+
return applyEvent(event, state);
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
function readState() {
|
|
171
|
+
placement(); directory(); directory('events');
|
|
172
|
+
if (!same(readWorkspaceIdentity(identityPath), identity)) refuse('LEARNING_WORKSPACE', 'learning workspace identity differs');
|
|
173
|
+
const entries = fs.readdirSync(eventDir).filter(name => !isPendingPrivateWrite(name));
|
|
174
|
+
if (entries.length > LEARNING_MAX_EVENTS) refuse('LEARNING_LIMIT', 'learning journal event ceiling exceeded');
|
|
175
|
+
let totalBytes = 0;
|
|
176
|
+
for (const name of entries) {
|
|
177
|
+
const stat = fs.lstatSync(path.join(eventDir, name));
|
|
178
|
+
if (stat.size > LEARNING_MAX_BYTES * 3) refuse('LEARNING_LIMIT', 'learning journal byte ceiling exceeded');
|
|
179
|
+
totalBytes += stat.size;
|
|
180
|
+
if (totalBytes > LEARNING_MAX_JOURNAL_BYTES) refuse('LEARNING_LIMIT', 'learning journal aggregate byte ceiling exceeded');
|
|
181
|
+
}
|
|
182
|
+
return readSequence();
|
|
183
|
+
}
|
|
184
|
+
readState();
|
|
185
|
+
function snapshot() { return publicSnapshot(readState()); }
|
|
186
|
+
function render({ lessonId, lessonDigest } = {}) {
|
|
187
|
+
valid('id', lessonId); valid('digest', lessonDigest);
|
|
188
|
+
const state = readState(), lesson = lessonFor(state, lessonId, lessonDigest);
|
|
189
|
+
return { schema: schema('render'), lessonId, lessonDigest, artifact: structuredClone(lesson.artifact), status: latestDecision(state, lessonId)?.verdict ?? 'proposed', executable: false };
|
|
190
|
+
}
|
|
191
|
+
function context({ scope, harnessId } = {}) {
|
|
192
|
+
valid('scope', scope); valid('id', harnessId);
|
|
193
|
+
if (scope.activity === '*') refuse('LEARNING_SCOPE', 'context requires a concrete activity');
|
|
194
|
+
const state = readState();
|
|
195
|
+
const candidates = state.activations.filter(activation => activation.harnessId === harnessId && activationState(state, activation.id)?.status === 'active').flatMap(activation => {
|
|
196
|
+
const lesson = lessonFor(state, activation.lessonId, activation.lessonDigest);
|
|
197
|
+
const decision = latestDecision(state, lesson.id);
|
|
198
|
+
if (!decision || decision.verdict !== 'accepted' || decision.id !== activation.decisionId || lesson.artifact.digest !== activation.artifactDigest) return [];
|
|
199
|
+
if (lesson.scope.project !== scope.project || (lesson.scope.activity !== '*' && lesson.scope.activity !== scope.activity)) return [];
|
|
200
|
+
return [{ id: lesson.id, lessonDigest: lesson.digest, scope: lesson.scope, artifact: lesson.artifact, activationId: activation.id, decisionId: decision.id, principle: lesson.principle, exceptions: lesson.exceptions, evidence: lesson.evidence.map(item => ({ ...item, source: state.observations.find(record => record.id === item.id).source })) }];
|
|
201
|
+
});
|
|
202
|
+
const groups = new Map();
|
|
203
|
+
for (const item of candidates) {
|
|
204
|
+
const slot = canonicalize([item.artifact.kind, item.artifact.name]);
|
|
205
|
+
if (!groups.has(slot)) groups.set(slot, []);
|
|
206
|
+
groups.get(slot).push(item);
|
|
207
|
+
}
|
|
208
|
+
const conflicts = [], withheld = new Set();
|
|
209
|
+
for (const group of groups.values()) {
|
|
210
|
+
if (new Set(group.map(item => item.artifact.digest)).size < 2) continue;
|
|
211
|
+
const lessonIds = group.map(item => item.id).sort();
|
|
212
|
+
for (const id of lessonIds) withheld.add(id);
|
|
213
|
+
conflicts.push({ kind: group[0].artifact.kind, name: group[0].artifact.name, lessonIds, reason: 'overlapping-artifact-slot' });
|
|
214
|
+
}
|
|
215
|
+
return { schema: schema('context'), workspaceId, revision: state.revision, scope: structuredClone(scope), harnessId, lessons: structuredClone(candidates.filter(item => !withheld.has(item.id))), conflicts, authority: { grantsToolPermissions: false, authenticated: false } };
|
|
216
|
+
}
|
|
217
|
+
function graph() {
|
|
218
|
+
const state = readState(), nodes = [], edges = [];
|
|
219
|
+
for (const [collection, type] of [['observations', 'observation'], ['lessons', 'lesson-proposal'], ['decisions', 'decision'], ['activations', 'activation'], ['withdrawals', 'withdrawal']]) {
|
|
220
|
+
for (const record of state[collection]) nodes.push({ id: `${type}:${record.id}`, type, record: structuredClone(record) });
|
|
221
|
+
}
|
|
222
|
+
const edge = (from, to, type) => edges.push({ from, to, type });
|
|
223
|
+
for (const record of state.observations) if (record.lessonId) edge(`observation:${record.id}`, `lesson-proposal:${record.lessonId}`, 'application-feedback');
|
|
224
|
+
for (const record of state.lessons) {
|
|
225
|
+
for (const evidence of record.evidence) edge(`lesson-proposal:${record.id}`, `observation:${evidence.id}`, 'supported-by');
|
|
226
|
+
if (record.supersedes) edge(`lesson-proposal:${record.id}`, `lesson-proposal:${record.supersedes}`, 'proposes-replacement');
|
|
227
|
+
}
|
|
228
|
+
for (const record of state.decisions) edge(`decision:${record.id}`, `lesson-proposal:${record.lessonId}`, record.verdict);
|
|
229
|
+
for (const record of state.activations) { edge(`activation:${record.id}`, `lesson-proposal:${record.lessonId}`, 'activates'); edge(`activation:${record.id}`, `decision:${record.decisionId}`, 'authorized-by-local-assertion'); }
|
|
230
|
+
for (const record of state.withdrawals) edge(`withdrawal:${record.id}`, `activation:${record.activationId}`, 'withdraws');
|
|
231
|
+
return { schema: schema('graph'), workspaceId, revision: state.revision, nodes, edges, activationStates: structuredClone(state.activationStates), authority: { visibility: 'private', canonicalGraphMutation: false, authenticated: false } };
|
|
232
|
+
}
|
|
233
|
+
return Object.freeze({
|
|
234
|
+
execute(request, { actor } = {}) {
|
|
235
|
+
request = boundedLearningValue(request); actor = boundedLearningValue(actor);
|
|
236
|
+
valid('request', request); valid('actor', actor);
|
|
237
|
+
return withPrivateLock(path.join(directory(), 'operation.lock'), () => {
|
|
238
|
+
const state = readState(), requestDigest = learningDigest({ request, actor });
|
|
239
|
+
const prior = state.events.find(event => event.request.requestId === request.requestId);
|
|
240
|
+
if (prior) {
|
|
241
|
+
if (prior.requestDigest !== requestDigest) refuse('LEARNING_REQUEST_REUSE', 'request identifier was reused with different input or actor');
|
|
242
|
+
return { schema: schema('result'), ok: true, duplicate: true, revision: prior.revision, eventId: prior.id, record: structuredClone(prior.record) };
|
|
243
|
+
}
|
|
244
|
+
const withdrawing = request.operation === 'withdraw';
|
|
245
|
+
const createdAt = new Date().toISOString(), record = makeRecord(state, request, actor, createdAt);
|
|
246
|
+
const event = seal({ schema: schema('event'), workspaceId, id: `event:${state.revision + 1}`, revision: state.revision + 1, previousDigest: state.headDigest, requestDigest, request, actor, createdAt, record });
|
|
247
|
+
valid('event', event);
|
|
248
|
+
const eventBytes = canonicalize(event) + '\n';
|
|
249
|
+
// readState returns a private clone, so prospective lifecycle changes
|
|
250
|
+
// cannot affect the verified committed projection when admission fails.
|
|
251
|
+
const prospective = applyEvent(event, state);
|
|
252
|
+
const remainingActive = prospective.activationStates.filter(binding => binding.status === 'active').length;
|
|
253
|
+
// Every committed event passes the canonical LEARNING_MAX_BYTES bound.
|
|
254
|
+
// Account for its final newline as well, so each remaining binding has
|
|
255
|
+
// room for any valid withdrawal, even after the last ordinary write.
|
|
256
|
+
const reserveBytes = Math.max(remainingActive * (LEARNING_MAX_BYTES + 1), withdrawing ? 0 : LEARNING_WITHDRAWAL_RESERVE_BYTES);
|
|
257
|
+
const reserveEvents = Math.max(remainingActive, withdrawing ? 0 : LEARNING_WITHDRAWAL_RESERVE_EVENTS);
|
|
258
|
+
if (prospective.revision + reserveEvents > LEARNING_MAX_EVENTS) refuse('LEARNING_LIMIT', 'learning journal event ceiling exceeded; withdrawal reservations retained');
|
|
259
|
+
if (prospective.journalBytes + Buffer.byteLength(eventBytes) + reserveBytes > LEARNING_MAX_JOURNAL_BYTES) refuse('LEARNING_LIMIT', 'learning journal aggregate byte ceiling exceeded; withdrawal reservations retained');
|
|
260
|
+
publishPrivateFile(path.join(eventDir, `${String(event.revision).padStart(10, '0')}.json`), eventBytes);
|
|
261
|
+
const committed = readState().events.at(-1);
|
|
262
|
+
if (committed.digest !== event.digest) refuse('LEARNING_HISTORY', 'learning journal readback mismatch');
|
|
263
|
+
return { schema: schema('result'), ok: true, duplicate: false, revision: event.revision, eventId: event.id, record: structuredClone(record) };
|
|
264
|
+
});
|
|
265
|
+
},
|
|
266
|
+
snapshot, render, context, graph,
|
|
267
|
+
export() {
|
|
268
|
+
const state = readState();
|
|
269
|
+
return seal({ schema: schema('export'), workspaceId, revision: state.revision, visibility: 'private', authority: { authenticated: false, importedActivation: false, automaticPublication: false }, history: state.events, snapshot: publicSnapshot(state) });
|
|
270
|
+
},
|
|
271
|
+
});
|
|
272
|
+
}
|