@mnstry/atelier 0.2.0-alpha.12 → 0.2.0-alpha.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +894 -0
- package/README.md +159 -6
- package/contracts/atelier-adoption-policy.v2.schema.json +55 -0
- package/contracts/atelier-architecture.v1.schema.json +258 -0
- package/contracts/atelier-boundary-delegations.v1.schema.json +161 -0
- package/contracts/atelier-boundary-owner-keys.v1.schema.json +98 -0
- package/contracts/atelier-build.v1.schema.json +1076 -0
- package/contracts/atelier-capability.v1.schema.json +1252 -0
- package/contracts/atelier-coordination.v1.schema.json +1050 -0
- package/contracts/atelier-evidence-navigation.v1.schema.json +603 -0
- package/contracts/atelier-harness.v1.schema.json +234 -0
- package/contracts/atelier-ingestion.v1.schema.json +806 -0
- package/contracts/atelier-inquiry.v1.schema.json +1772 -0
- package/contracts/atelier-instruction-adoption.v1.schema.json +490 -0
- package/contracts/atelier-interaction.v1.schema.json +553 -0
- package/contracts/atelier-judgment.v1.schema.json +690 -0
- package/contracts/atelier-knowledge.v1.schema.json +1512 -0
- package/contracts/atelier-learning.v1.schema.json +1095 -0
- package/contracts/atelier-migration.v3.schema.json +64 -0
- package/contracts/atelier-personal-workspace-manifest.v1.schema.json +87 -0
- package/contracts/atelier-personal-workspace-overlay.v1.schema.json +299 -0
- package/contracts/atelier-template-adoption.v1.schema.json +223 -0
- package/contracts/atelier-template-profile.v1.schema.json +1168 -0
- package/contracts/atelier-trackable.v1.schema.json +781 -0
- package/contracts/atelier-upgrade-plan.v3.schema.json +532 -0
- package/docs/architecture.md +89 -0
- package/docs/assurance-controls.md +2 -2
- package/docs/blocks/capability-stewardship.md +17 -0
- package/docs/blocks/claims.md +16 -3
- package/docs/blocks/inquiry-harnesses.md +19 -0
- package/docs/blocks/learning-harnesses.md +17 -0
- package/docs/capability-stewardship.md +267 -0
- package/docs/coordination.md +51 -0
- package/docs/decision-practices.md +111 -0
- package/docs/decisions.md +29 -0
- package/docs/evidence-navigation.md +237 -0
- package/docs/exact-upgrades.md +3 -2
- package/docs/ingestion.md +181 -0
- package/docs/inquiry-harnesses.md +210 -0
- package/docs/install.md +80 -3
- package/docs/intake-and-guides.md +5 -0
- package/docs/integration-contract-decisions.md +24 -2
- package/docs/interaction-and-reflection.md +72 -0
- package/docs/knowledge-graph.md +20 -0
- package/docs/knowledge-health/api.md +30 -0
- package/docs/knowledge-health/assessment/api.md +96 -0
- package/docs/knowledge-health/assessment/capabilities.md +40 -0
- package/docs/knowledge-health/assessment/evaluation.md +27 -0
- package/docs/knowledge-health/assessment/facilitator.md +30 -0
- package/docs/knowledge-health/assessment/quickstart.md +61 -0
- package/docs/knowledge-health/assessment/troubleshooting.md +23 -0
- package/docs/knowledge-health/devday/facilitator.md +16 -0
- package/docs/knowledge-health/devday/participant-response.md +22 -0
- package/docs/knowledge-health/devday/public-default.md +107 -0
- package/docs/knowledge-health/devday/quickstart.md +18 -0
- package/docs/knowledge-health/devday/support.md +22 -0
- package/docs/knowledge-health/exercise.md +17 -0
- package/docs/knowledge-health/facilitator.md +20 -0
- package/docs/knowledge-health/quickstart.md +94 -0
- package/docs/knowledge-health/readme.md +15 -0
- package/docs/knowledge-health/support.md +34 -0
- package/docs/knowledge-setup.md +248 -0
- package/docs/knowledge-workspace.md +264 -0
- package/docs/learning-harnesses.md +237 -0
- package/docs/learning.md +215 -0
- package/docs/local-services.md +223 -19
- package/docs/obsidian-contract.md +246 -15
- package/docs/obsidian-plugin.md +252 -7
- package/docs/obsidian.md +874 -52
- package/docs/optional-ingestion.md +83 -0
- package/docs/personal-workspace.md +266 -0
- package/docs/practical-judgment.md +92 -0
- package/docs/progressive-ingestion-acceptance.md +89 -0
- package/docs/progressive-ingestion.md +235 -0
- package/docs/project-command-inventory.md +1 -0
- package/docs/release-engineering.md +50 -0
- package/docs/repo-boundary-guard.md +120 -3
- package/docs/research-starter-quickstart.md +66 -0
- package/docs/research-starter.md +114 -0
- package/docs/semantic-ingestion.md +198 -0
- package/docs/semantic-operation.md +318 -0
- package/docs/skill-steward.md +6 -0
- package/docs/template-profile.md +164 -0
- package/docs/template-upgrades.md +114 -0
- package/docs/trackables.md +111 -0
- package/docs/upgrade.md +219 -2
- package/fixtures/atelier-architecture/binding/invalid/document.json +19 -0
- package/fixtures/atelier-architecture/binding/valid/document.json +19 -0
- package/fixtures/atelier-architecture/catalog/invalid/document.json +290 -0
- package/fixtures/atelier-architecture/catalog/valid/document.json +343 -0
- package/fixtures/atelier-build-contract/attempt/invalid/document.json +20 -0
- package/fixtures/atelier-build-contract/attempt/valid/document.json +19 -0
- package/fixtures/atelier-build-contract/candidate/invalid/document.json +24 -0
- package/fixtures/atelier-build-contract/candidate/valid/document.json +23 -0
- package/fixtures/atelier-build-contract/decision/invalid/document.json +31 -0
- package/fixtures/atelier-build-contract/decision/valid/document.json +30 -0
- package/fixtures/atelier-build-contract/delivery/invalid/document.json +18 -0
- package/fixtures/atelier-build-contract/delivery/valid/document.json +17 -0
- package/fixtures/atelier-build-contract/gate/invalid/document.json +22 -0
- package/fixtures/atelier-build-contract/gate/valid/document.json +21 -0
- package/fixtures/atelier-build-contract/ledger/invalid/document.json +270 -0
- package/fixtures/atelier-build-contract/ledger/valid/document.json +269 -0
- package/fixtures/atelier-build-contract/objective/invalid/document.json +73 -0
- package/fixtures/atelier-build-contract/objective/valid/document.json +72 -0
- package/fixtures/atelier-build-contract/progress/invalid/document.json +18 -0
- package/fixtures/atelier-build-contract/progress/valid/document.json +17 -0
- package/fixtures/atelier-capability-contract/adoption/invalid/document.json +71 -0
- package/fixtures/atelier-capability-contract/adoption/valid/document.json +70 -0
- package/fixtures/atelier-capability-contract/event/invalid/document.json +18 -0
- package/fixtures/atelier-capability-contract/event/valid/document.json +17 -0
- package/fixtures/atelier-capability-contract/journal/invalid/document.json +382 -0
- package/fixtures/atelier-capability-contract/journal/valid/document.json +381 -0
- package/fixtures/atelier-capability-contract/notice/invalid/document.json +8 -0
- package/fixtures/atelier-capability-contract/notice/valid/document.json +7 -0
- package/fixtures/atelier-capability-contract/package/invalid/document.json +59 -0
- package/fixtures/atelier-capability-contract/package/valid/document.json +58 -0
- package/fixtures/atelier-capability-contract/plan/invalid/document.json +107 -0
- package/fixtures/atelier-capability-contract/plan/valid/document.json +106 -0
- package/fixtures/atelier-capability-contract/release/invalid/document.json +85 -0
- package/fixtures/atelier-capability-contract/release/valid/document.json +84 -0
- package/fixtures/atelier-capability-contract/state/invalid/document.json +316 -0
- package/fixtures/atelier-capability-contract/state/valid/document.json +315 -0
- package/fixtures/atelier-coordination/dependency/invalid/document.json +37 -0
- package/fixtures/atelier-coordination/dependency/valid/document.json +36 -0
- package/fixtures/atelier-coordination/directive/invalid/document.json +25 -0
- package/fixtures/atelier-coordination/directive/valid/document.json +24 -0
- package/fixtures/atelier-coordination/disposition/invalid/document.json +34 -0
- package/fixtures/atelier-coordination/disposition/valid/document.json +33 -0
- package/fixtures/atelier-coordination/outcome/invalid/document.json +39 -0
- package/fixtures/atelier-coordination/outcome/valid/document.json +38 -0
- package/fixtures/atelier-coordination/recovery/invalid/document.json +34 -0
- package/fixtures/atelier-coordination/recovery/valid/document.json +33 -0
- package/fixtures/atelier-harness-contract/handoff/invalid/document.json +25 -0
- package/fixtures/atelier-harness-contract/handoff/valid/document.json +24 -0
- package/fixtures/atelier-ingestion/invalid/authority.json +19 -0
- package/fixtures/atelier-ingestion/semantic-baseline.json +26 -0
- package/fixtures/atelier-ingestion/semantic-operation.json +75 -0
- package/fixtures/atelier-ingestion/valid/plan-input.json +18 -0
- package/fixtures/atelier-inquiry-contract/assessment/invalid/document.json +41 -0
- package/fixtures/atelier-inquiry-contract/assessment/valid/document.json +40 -0
- package/fixtures/atelier-inquiry-contract/bundle/invalid/document.json +51 -0
- package/fixtures/atelier-inquiry-contract/bundle/valid/document.json +50 -0
- package/fixtures/atelier-inquiry-contract/campaign/invalid/document.json +17 -0
- package/fixtures/atelier-inquiry-contract/campaign/valid/document.json +16 -0
- package/fixtures/atelier-inquiry-contract/decision/invalid/document.json +22 -0
- package/fixtures/atelier-inquiry-contract/decision/valid/document.json +21 -0
- package/fixtures/atelier-inquiry-contract/feedback/invalid/document.json +19 -0
- package/fixtures/atelier-inquiry-contract/feedback/valid/document.json +18 -0
- package/fixtures/atelier-inquiry-contract/handoff/invalid/document.json +92 -0
- package/fixtures/atelier-inquiry-contract/handoff/valid/document.json +91 -0
- package/fixtures/atelier-inquiry-contract/hypothesis/invalid/document.json +20 -0
- package/fixtures/atelier-inquiry-contract/hypothesis/valid/document.json +19 -0
- package/fixtures/atelier-inquiry-contract/ledger/invalid/document.json +680 -0
- package/fixtures/atelier-inquiry-contract/ledger/valid/document.json +679 -0
- package/fixtures/atelier-inquiry-contract/legacy-assessment/invalid/document.json +19 -0
- package/fixtures/atelier-inquiry-contract/legacy-assessment/valid/document.json +18 -0
- package/fixtures/atelier-inquiry-contract/request/invalid/document.json +50 -0
- package/fixtures/atelier-inquiry-contract/request/valid/document.json +49 -0
- package/fixtures/atelier-inquiry-contract/source/invalid/document.json +18 -0
- package/fixtures/atelier-inquiry-contract/source/valid/document.json +17 -0
- package/fixtures/atelier-inquiry-contract/withdrawal/invalid/document.json +16 -0
- package/fixtures/atelier-inquiry-contract/withdrawal/valid/document.json +15 -0
- package/fixtures/atelier-instruction-adoption/context-receipt/invalid/document.json +18 -0
- package/fixtures/atelier-instruction-adoption/context-receipt/valid/document.json +17 -0
- package/fixtures/atelier-instruction-adoption/plan/invalid/document.json +63 -0
- package/fixtures/atelier-instruction-adoption/plan/valid/document.json +62 -0
- package/fixtures/atelier-instruction-adoption/receipt/invalid/document.json +22 -0
- package/fixtures/atelier-instruction-adoption/receipt/valid/document.json +21 -0
- package/fixtures/atelier-instruction-adoption/state/invalid/document.json +30 -0
- package/fixtures/atelier-instruction-adoption/state/valid/document.json +29 -0
- package/fixtures/atelier-interaction/act/invalid/document.json +25 -0
- package/fixtures/atelier-interaction/act/valid/document.json +24 -0
- package/fixtures/atelier-interaction/assessment/invalid/document.json +32 -0
- package/fixtures/atelier-interaction/assessment/valid/document.json +31 -0
- package/fixtures/atelier-interaction/observation/invalid/document.json +22 -0
- package/fixtures/atelier-interaction/observation/valid/document.json +21 -0
- package/fixtures/atelier-interaction/policy/invalid/document.json +20 -0
- package/fixtures/atelier-interaction/policy/valid/document.json +19 -0
- package/fixtures/atelier-judgment/case/invalid/document.json +76 -0
- package/fixtures/atelier-judgment/case/valid/document.json +75 -0
- package/fixtures/atelier-judgment/decision/invalid/document.json +38 -0
- package/fixtures/atelier-judgment/decision/valid/document.json +37 -0
- package/fixtures/atelier-judgment/native-decision.md +3 -0
- package/fixtures/atelier-judgment/practice-reconsideration/definition.json +92 -0
- package/fixtures/atelier-judgment/practice-reconsideration/scenario.json +435 -0
- package/fixtures/atelier-knowledge-contract/activation/invalid/document.json +26 -0
- package/fixtures/atelier-knowledge-contract/activation/valid/document.json +25 -0
- package/fixtures/atelier-knowledge-contract/contribution/invalid/document.json +34 -0
- package/fixtures/atelier-knowledge-contract/contribution/valid/document.json +33 -0
- package/fixtures/atelier-knowledge-contract/domain/invalid/document.json +41 -0
- package/fixtures/atelier-knowledge-contract/domain/valid/document.json +40 -0
- package/fixtures/atelier-knowledge-contract/domain-revision/invalid/document.json +46 -0
- package/fixtures/atelier-knowledge-contract/domain-revision/valid/document.json +45 -0
- package/fixtures/atelier-knowledge-contract/evaluation/invalid/document.json +21 -0
- package/fixtures/atelier-knowledge-contract/evaluation/valid/document.json +20 -0
- package/fixtures/atelier-knowledge-contract/ledger/invalid/document.json +318 -0
- package/fixtures/atelier-knowledge-contract/ledger/valid/document.json +317 -0
- package/fixtures/atelier-knowledge-contract/relation/invalid/document.json +25 -0
- package/fixtures/atelier-knowledge-contract/relation/valid/document.json +24 -0
- package/fixtures/atelier-knowledge-contract/review/invalid/document.json +18 -0
- package/fixtures/atelier-knowledge-contract/review/valid/document.json +17 -0
- package/fixtures/atelier-knowledge-contract/withdrawal/invalid/document.json +16 -0
- package/fixtures/atelier-knowledge-contract/withdrawal/valid/document.json +15 -0
- package/fixtures/atelier-learning/invalid/authority.json +14 -0
- package/fixtures/atelier-learning/valid/capture.json +13 -0
- package/fixtures/atelier-personal-workspace-contract/manifest/invalid/authority-key.json +17 -0
- package/fixtures/atelier-personal-workspace-contract/manifest/invalid/nonempty-ext.json +25 -0
- package/fixtures/atelier-personal-workspace-contract/manifest/invalid/wrong-schema.json +22 -0
- package/fixtures/atelier-personal-workspace-contract/manifest/valid/manifest.json +22 -0
- package/fixtures/atelier-personal-workspace-contract/overlay/invalid/authority-key.json +25 -0
- package/fixtures/atelier-personal-workspace-contract/overlay/invalid/unknown-top-level-field.json +27 -0
- package/fixtures/atelier-personal-workspace-contract/overlay/invalid/wrong-schema.json +24 -0
- package/fixtures/atelier-trackable/definition/invalid/document.json +15 -0
- package/fixtures/atelier-trackable/definition/valid/document.json +14 -0
- package/fixtures/atelier-trackable/request/invalid/document.json +23 -0
- package/fixtures/atelier-trackable/request/valid/document.json +22 -0
- package/fixtures/boundary-delegations/invalid/authority-key.v1.json +30 -0
- package/fixtures/boundary-delegations/invalid/push-operation.v1.json +28 -0
- package/fixtures/boundary-delegations/invalid/unsigned.v1.json +25 -0
- package/fixtures/boundary-delegations/valid/empty.v1.json +4 -0
- package/fixtures/boundary-delegations/valid/signed-operator.v1.json +30 -0
- package/fixtures/boundary-owner-keys/invalid/wrong-algorithm.v1.json +15 -0
- package/fixtures/boundary-owner-keys/invalid/wrong-curve.v1.json +15 -0
- package/fixtures/boundary-owner-keys/valid/one-owner.v1.json +17 -0
- package/fixtures/capability-packages/adoption.example.json +70 -0
- package/fixtures/capability-packages/evidence-review/capability-package.json +58 -0
- package/fixtures/capability-packages/evidence-review/capability-release.json +84 -0
- package/fixtures/capability-packages/evidence-review/evaluation.md +15 -0
- package/fixtures/capability-packages/evidence-review/skills/evidence-review/SKILL.md +22 -0
- package/fixtures/capability-packages/evidence-review/skills/evidence-review/references/ledger.md +7 -0
- package/fixtures/capability-packages/research/capability-package.json +88 -0
- package/fixtures/capability-packages/research/capability-release.json +134 -0
- package/fixtures/capability-packages/research/evaluation.md +15 -0
- package/fixtures/capability-packages/research/skills/research-integrate/SKILL.md +29 -0
- package/fixtures/capability-packages/research/skills/research-integrate/references/integration.md +10 -0
- package/fixtures/capability-packages/research/skills/research-plan/SKILL.md +27 -0
- package/fixtures/capability-packages/research/skills/research-plan/references/inquiries.md +26 -0
- package/fixtures/capability-packages/research/skills/research-synthesize/SKILL.md +23 -0
- package/fixtures/capability-packages/research/skills/research-synthesize/references/synthesis.md +12 -0
- package/fixtures/evidence-navigation/invalid/duplicate-operation.json +16 -0
- package/fixtures/evidence-navigation/invalid/instruction-kind.json +36 -0
- package/fixtures/evidence-navigation/invalid/invalid-digest.json +36 -0
- package/fixtures/evidence-navigation/invalid/invalid-time.json +36 -0
- package/fixtures/evidence-navigation/invalid/missing-attribution.json +35 -0
- package/fixtures/evidence-navigation/invalid/reserved-extension.json +19 -0
- package/fixtures/evidence-navigation/invalid/unattributed-acceptance.json +36 -0
- package/fixtures/evidence-navigation/invalid/uncoerced-version.json +16 -0
- package/fixtures/evidence-navigation/invalid/unknown-currency.json +17 -0
- package/fixtures/evidence-navigation/invalid/unknown-selector-field.json +37 -0
- package/fixtures/evidence-navigation/invalid/unsupported-operation.json +15 -0
- package/fixtures/evidence-navigation/valid/claim.json +36 -0
- package/fixtures/evidence-navigation/valid/host.json +16 -0
- package/fixtures/evidence-navigation/valid/profile.json +16 -0
- package/fixtures/evidence-navigation/valid/snapshot.json +17 -0
- package/fixtures/harness-packages/build-harness/capability-package.json +61 -0
- package/fixtures/harness-packages/build-harness/capability-release.json +82 -0
- package/fixtures/harness-packages/build-harness/evaluation.md +3 -0
- package/fixtures/harness-packages/build-harness/skills/atelier-build-harness/SKILL.md +59 -0
- package/fixtures/harness-packages/knowledge-harness/capability-package.json +61 -0
- package/fixtures/harness-packages/knowledge-harness/capability-release.json +82 -0
- package/fixtures/harness-packages/knowledge-harness/evaluation.md +3 -0
- package/fixtures/harness-packages/knowledge-harness/skills/atelier-knowledge-harness/SKILL.md +60 -0
- package/fixtures/harnesses/learning-cycle.json +780 -0
- package/fixtures/inquiry/workshop.json +675 -0
- package/fixtures/inquiry-packages/discovery-harness/capability-package.json +61 -0
- package/fixtures/inquiry-packages/discovery-harness/capability-release.json +82 -0
- package/fixtures/inquiry-packages/discovery-harness/evaluation.md +3 -0
- package/fixtures/inquiry-packages/discovery-harness/skills/atelier-discovery-harness/SKILL.md +48 -0
- package/fixtures/inquiry-packages/research-harness/capability-package.json +61 -0
- package/fixtures/inquiry-packages/research-harness/capability-release.json +82 -0
- package/fixtures/inquiry-packages/research-harness/evaluation.md +3 -0
- package/fixtures/inquiry-packages/research-harness/skills/atelier-research-harness/SKILL.md +43 -0
- package/fixtures/knowledge-health/calibration-suite.json +26 -0
- package/fixtures/knowledge-health/calibration-trial.json +37 -0
- package/fixtures/knowledge-health/corrected/checklist.md +18 -0
- package/fixtures/knowledge-health/devday/participant-record.json +19 -0
- package/fixtures/knowledge-health/expected-summary.json +27 -0
- package/fixtures/knowledge-health/workshop/README.md +20 -0
- package/fixtures/knowledge-health/workshop/atelier.project.json +7 -0
- package/fixtures/knowledge-health/workshop/knowledge-plan.json +65 -0
- package/fixtures/knowledge-health/workshop/records/checklist.md +18 -0
- package/fixtures/knowledge-health/workshop/records/workshop.md +18 -0
- package/fixtures/knowledge-health/workshop/repo-access.v1.json +5 -0
- package/fixtures/knowledge-health/workspace/atelier.project.json +18 -0
- package/fixtures/knowledge-health/workspace/knowledge-plan.json +74 -0
- package/fixtures/knowledge-health/workspace/records/checklist.md +18 -0
- package/fixtures/knowledge-health/workspace/records/pilot.md +16 -0
- package/fixtures/knowledge-health/workspace/repo-access.v1.json +9 -0
- package/fixtures/personal-workspace/overlay.json +24 -0
- package/fixtures/templates/local-library.v1.json +76 -0
- package/fixtures/templates/upgrade-contracts/adoption-policy.v2/invalid.json +16 -0
- package/fixtures/templates/upgrade-contracts/adoption-policy.v2/valid.json +13 -0
- package/fixtures/templates/upgrade-contracts/migration.v3/invalid.json +24 -0
- package/fixtures/templates/upgrade-contracts/migration.v3/valid.json +21 -0
- package/fixtures/templates/upgrade-contracts/template-adoption.v1/invalid.json +60 -0
- package/fixtures/templates/upgrade-contracts/template-adoption.v1/valid.json +57 -0
- package/fixtures/templates/upgrade-contracts/upgrade-plan.v3/invalid.json +85 -0
- package/fixtures/templates/upgrade-contracts/upgrade-plan.v3/valid.json +82 -0
- package/package.json +60 -5
- package/plugins/obsidian/main.js +57 -8
- package/plugins/obsidian/manifest.json +1 -1
- package/skills/claude/atelier-build-harness/SKILL.md +59 -0
- package/skills/claude/atelier-coordination/SKILL.md +32 -0
- package/skills/claude/atelier-discovery-harness/SKILL.md +48 -0
- package/skills/claude/atelier-guided-upgrade/SKILL.md +29 -0
- package/skills/claude/atelier-interaction/SKILL.md +34 -0
- package/skills/claude/atelier-knowledge-harness/SKILL.md +60 -0
- package/skills/claude/atelier-knowledge-setup/SKILL.md +72 -0
- package/skills/claude/atelier-practical-judgment/SKILL.md +64 -0
- package/skills/claude/atelier-research-harness/SKILL.md +43 -0
- package/skills/claude/atelier-skill-steward/SKILL.md +53 -0
- package/skills/claude/atelier-trackables/SKILL.md +40 -0
- package/skills/codex/atelier-build-harness/SKILL.md +59 -0
- package/skills/codex/atelier-coordination/SKILL.md +32 -0
- package/skills/codex/atelier-discovery-harness/SKILL.md +48 -0
- package/skills/codex/atelier-guided-upgrade/SKILL.md +29 -0
- package/skills/codex/atelier-interaction/SKILL.md +34 -0
- package/skills/codex/atelier-knowledge-harness/SKILL.md +60 -0
- package/skills/codex/atelier-knowledge-setup/SKILL.md +72 -0
- package/skills/codex/atelier-practical-judgment/SKILL.md +64 -0
- package/skills/codex/atelier-research-harness/SKILL.md +43 -0
- package/skills/codex/atelier-skill-steward/SKILL.md +53 -0
- package/skills/codex/atelier-trackables/SKILL.md +40 -0
- package/src/architecture/catalog.mjs +355 -0
- package/src/architecture/index.mjs +93 -0
- package/src/boundary/content-rules.mjs +536 -69
- package/src/boundary/delegation-command.mjs +158 -0
- package/src/boundary/delegation.mjs +396 -0
- package/src/boundary/policy.mjs +185 -64
- package/src/build/git.mjs +18 -0
- package/src/build/index.mjs +3 -0
- package/src/build/ledger.mjs +88 -0
- package/src/capabilities/adoption.mjs +381 -0
- package/src/capabilities/evidence.mjs +140 -0
- package/src/capabilities/files.mjs +124 -0
- package/src/capabilities/index.mjs +4 -0
- package/src/capabilities/instructions.mjs +250 -0
- package/src/capabilities/package.mjs +136 -0
- package/src/cli/command-failure.mjs +36 -0
- package/src/cli/execute-command.mjs +16 -2
- package/src/cli/run.mjs +66 -9
- package/src/coauthor/store.mjs +43 -2
- package/src/collaboration/proposals.mjs +189 -0
- package/src/commands/architecture.mjs +27 -0
- package/src/commands/boundary.mjs +3 -0
- package/src/commands/capability.mjs +73 -0
- package/src/commands/enroll.mjs +3 -0
- package/src/commands/harness.mjs +79 -0
- package/src/commands/ingest.mjs +39 -0
- package/src/commands/init.mjs +4 -1
- package/src/commands/inquiry.mjs +66 -0
- package/src/commands/knowledge-semantic.mjs +128 -0
- package/src/commands/knowledge.mjs +119 -0
- package/src/commands/learn.mjs +61 -0
- package/src/commands/obsidian.mjs +772 -65
- package/src/commands/practice.mjs +24 -0
- package/src/commands/setup.mjs +22 -2
- package/src/commands/trackable.mjs +19 -0
- package/src/contracts/corpus.mjs +30 -0
- package/src/coordination/index.mjs +110 -0
- package/src/evidence-navigation/contracts.mjs +60 -0
- package/src/evidence-navigation/evaluate.mjs +69 -0
- package/src/evidence-navigation/generation.mjs +22 -0
- package/src/evidence-navigation/index.mjs +2 -0
- package/src/evidence-navigation/local.mjs +167 -0
- package/src/evidence-navigation/schema.generated.mjs +604 -0
- package/src/evidence-navigation/types.mjs +12 -0
- package/src/export/atelier-export-contract.mjs +35 -8
- package/src/graph/enroll.mjs +248 -0
- package/src/graph/graph.mjs +30 -6
- package/src/graph/knowledge-graph.mjs +77 -9
- package/src/harnesses/contracts.mjs +41 -0
- package/src/harnesses/exchange.mjs +154 -0
- package/src/harnesses/feedback.mjs +16 -0
- package/src/harnesses/history.mjs +37 -0
- package/src/harnesses/index.mjs +4 -0
- package/src/harnesses/store.mjs +29 -0
- package/src/ingestion/contracts.mjs +41 -0
- package/src/ingestion/evaluation.mjs +119 -0
- package/src/ingestion/optional/composition/index.mjs +171 -0
- package/src/ingestion/optional/evaluation/index.mjs +219 -0
- package/src/ingestion/optional/graphify/composition-binding.mjs +60 -0
- package/src/ingestion/optional/graphify/composition_receiving.py +185 -0
- package/src/ingestion/optional/graphify/graphify_supplier.py +938 -0
- package/src/ingestion/optional/graphify/json_bridge.py +163 -0
- package/src/ingestion/optional/graphify/native-closure-pins.json +275 -0
- package/src/ingestion/optional/graphify/native-pins.json +306 -0
- package/src/ingestion/optional/graphify/query_receiving.py +209 -0
- package/src/ingestion/optional/index.mjs +9 -0
- package/src/ingestion/optional/jev/bridge.mjs +166 -0
- package/src/ingestion/optional/jev/composition-binding.mjs +116 -0
- package/src/ingestion/optional/jev/judgments.mjs +174 -0
- package/src/ingestion/optional/jev/native-translation.mjs +32 -0
- package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/__init__.py +7 -0
- package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/adapter.py +966 -0
- package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/artifacts.py +101 -0
- package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/projection.py +146 -0
- package/src/ingestion/optional/lightrag/composition-binding.mjs +67 -0
- package/src/ingestion/optional/vanilla/composition-binding.mjs +102 -0
- package/src/ingestion/optional/vanilla/index.mjs +218 -0
- package/src/ingestion/optional/vanilla/native-normalization.mjs +187 -0
- package/src/ingestion/processors.mjs +218 -0
- package/src/ingestion/semantic.mjs +203 -0
- package/src/ingestion/store.mjs +372 -0
- package/src/inquiry/beliefs.mjs +32 -0
- package/src/inquiry/contracts.mjs +23 -0
- package/src/inquiry/index.mjs +5 -0
- package/src/inquiry/ledger.mjs +135 -0
- package/src/inquiry/lenses.mjs +15 -0
- package/src/inquiry/projection.mjs +77 -0
- package/src/inquiry/store.mjs +35 -0
- package/src/intake/read-scope.mjs +12 -0
- package/src/intake/store.mjs +69 -6
- package/src/interaction/index.mjs +91 -0
- package/src/judgment/index.mjs +82 -0
- package/src/judgment/practice-evaluation.mjs +70 -0
- package/src/judgment/practice.mjs +144 -0
- package/src/knowledge/context.mjs +217 -0
- package/src/knowledge/index.mjs +5 -0
- package/src/knowledge/ingestion.mjs +79 -0
- package/src/knowledge/intake.mjs +33 -0
- package/src/knowledge/ledger.mjs +110 -0
- package/src/knowledge/participatory/guidance.mjs +36 -0
- package/src/knowledge/participatory/guided-contribution.mjs +44 -0
- package/src/knowledge/participatory/index.mjs +2 -0
- package/src/knowledge/participatory/installed-workshop.mjs +108 -0
- package/src/knowledge/plan.mjs +67 -0
- package/src/knowledge/projection.mjs +43 -0
- package/src/knowledge/semantic-operation.mjs +567 -0
- package/src/knowledge/sessions.mjs +467 -0
- package/src/knowledge/workspace.mjs +295 -0
- package/src/knowledge-health/assessment.mjs +165 -0
- package/src/knowledge-health/capabilities.mjs +350 -0
- package/src/knowledge-health/context.mjs +62 -0
- package/src/knowledge-health/example.mjs +47 -0
- package/src/knowledge-health/index.mjs +10 -0
- package/src/knowledge-health/owner-join.mjs +88 -0
- package/src/knowledge-health/proof/common.mjs +24 -0
- package/src/knowledge-health/proof/installed-check.mjs +100 -0
- package/src/knowledge-health/proof/native-readback.mjs +37 -0
- package/src/knowledge-health/proof/rehearse.mjs +68 -0
- package/src/knowledge-health/proof/validate-participant.mjs +17 -0
- package/src/knowledge-health/proof/verify-native-capture.mjs +14 -0
- package/src/knowledge-health/proof/workshop-check.mjs +36 -0
- package/src/knowledge-health/rule-identity.mjs +49 -0
- package/src/knowledge-health/workshop-composition.mjs +19 -0
- package/src/knowledge-health/workshop-profile.mjs +176 -0
- package/src/knowledge-health/workshop.mjs +99 -0
- package/src/learning/adapters.mjs +68 -0
- package/src/learning/assistance.mjs +127 -0
- package/src/learning/contracts.mjs +65 -0
- package/src/learning/store.mjs +272 -0
- package/src/personal-workspace/index.mjs +391 -0
- package/src/personal-workspace/selection.mjs +401 -0
- package/src/project/config.mjs +8 -0
- package/src/project/durable-state.mjs +3 -1
- package/src/project/file-class.mjs +1 -1
- package/src/project/path-match.mjs +2 -1
- package/src/projection/obsidian/edits/apply.mjs +55 -16
- package/src/projection/obsidian/materialize/prepare-view.mjs +87 -5
- package/src/projection/obsidian/personal-workspace.mjs +519 -0
- package/src/projection/obsidian/proposals/adapter.mjs +79 -17
- package/src/projection/obsidian/proposals/observation.mjs +50 -4
- package/src/projection/obsidian/publication/publisher.mjs +214 -18
- package/src/projection/obsidian/publication/vault-list.mjs +4 -3
- package/src/projection/obsidian/recovery/index.mjs +4 -1
- package/src/projection/obsidian/recovery/store.mjs +160 -7
- package/src/projection/obsidian/selection-ui/contribution.mjs +6 -5
- package/src/projection/project.mjs +3 -2
- package/src/readiness/readiness.mjs +10 -3
- package/src/reflection/index.mjs +56 -0
- package/src/runtime/obsidian/app-production-seams.mjs +48 -16
- package/src/runtime/obsidian/app-registration.mjs +175 -28
- package/src/runtime/obsidian/app-restart.mjs +154 -0
- package/src/runtime/obsidian/engine.mjs +437 -56
- package/src/runtime/obsidian/extension-points.mjs +1 -1
- package/src/runtime/obsidian/index.mjs +10 -7
- package/src/runtime/obsidian/launch-plan.mjs +7 -4
- package/src/runtime/obsidian/lifecycle.mjs +108 -23
- package/src/runtime/obsidian/login-item.mjs +335 -0
- package/src/runtime/obsidian/machine-settings.mjs +119 -12
- package/src/runtime/obsidian/observation.mjs +36 -4
- package/src/runtime/obsidian/opening.mjs +173 -38
- package/src/runtime/obsidian/personal-selection.mjs +125 -0
- package/src/runtime/obsidian/pipeline.mjs +115 -6
- package/src/runtime/obsidian/plugin-channel.mjs +11 -3
- package/src/runtime/obsidian/plugin-choice.mjs +16 -4
- package/src/runtime/obsidian/plugin-drift.mjs +59 -11
- package/src/runtime/obsidian/plugin-presence.mjs +14 -1
- package/src/runtime/obsidian/private-lock.mjs +39 -4
- package/src/runtime/obsidian/process-identity.mjs +20 -10
- package/src/runtime/obsidian/project-views.mjs +282 -0
- package/src/runtime/obsidian/questions.mjs +48 -0
- package/src/runtime/obsidian/release-watch.mjs +56 -0
- package/src/runtime/obsidian/service-main.mjs +148 -19
- package/src/runtime/obsidian/service-manager-production.mjs +61 -0
- package/src/runtime/obsidian/service-managers.mjs +275 -0
- package/src/runtime/obsidian/service-record.mjs +87 -23
- package/src/runtime/obsidian/service.mjs +102 -9
- package/src/runtime/obsidian/startup-adapters.mjs +59 -21
- package/src/runtime/obsidian/unheld-evidence.mjs +84 -0
- package/src/runtime/obsidian/vault-location.mjs +300 -0
- package/src/runtime/obsidian/view-counts.mjs +34 -0
- package/src/runtime/obsidian/view-permission.mjs +334 -0
- package/src/server/local-sidecar.mjs +169 -2
- package/src/server/server.mjs +28 -2
- package/src/template-bindings/project.mjs +92 -0
- package/src/templates/profile.mjs +225 -0
- package/src/trackables/domain.mjs +175 -0
- package/src/trackables/store.mjs +114 -0
- package/src/ui/control-system.mjs +201 -0
- package/src/ui/html-primitives.mjs +3 -0
- package/src/ui/knowledge-page.mjs +1388 -0
- package/src/upgrade/closure-diagnostics.mjs +135 -0
- package/src/upgrade/closure.mjs +649 -0
- package/src/upgrade/explanation.mjs +1 -1
- package/src/upgrade/template-participant.mjs +171 -0
- package/src/upgrade/transaction.mjs +72 -25
- package/src/upgrade/upgrade.mjs +20 -7
- package/templates/external-project-workspace/pack-lifecycle.json +7 -1
- package/templates/knowledge-workspace/.gitattributes +2 -0
- package/templates/knowledge-workspace/AGENTS.md +27 -0
- package/templates/knowledge-workspace/README.md +44 -0
- package/templates/knowledge-workspace/atelier.project.json +22 -0
- package/templates/knowledge-workspace/gitignore +6 -0
- package/templates/knowledge-workspace/knowledge-plan.json +111 -0
- package/templates/knowledge-workspace/knowledge-plan.schema.json +281 -0
- package/templates/knowledge-workspace/records/forecast.md +22 -0
- package/templates/knowledge-workspace/records/inspection.md +25 -0
- package/templates/knowledge-workspace/records/telescope.md +24 -0
- package/templates/knowledge-workspace/repo-access.v1.json +9 -0
package/src/boundary/policy.mjs
CHANGED
|
@@ -7,6 +7,9 @@ import { matchesPathPattern } from '../project/path-match.mjs'
|
|
|
7
7
|
import { sanitizedGitEnvironment } from '../runtime/git-adapter.mjs'
|
|
8
8
|
import {
|
|
9
9
|
CHECK_AGGREGATE_MAX_BYTES,
|
|
10
|
+
canonicalDiffArgs,
|
|
11
|
+
gitOutputResult,
|
|
12
|
+
parseDiffSections,
|
|
10
13
|
parsePushRefInput,
|
|
11
14
|
resolveContentRules,
|
|
12
15
|
scanPushUpdate,
|
|
@@ -15,6 +18,7 @@ import {
|
|
|
15
18
|
validateContentRuleExceptions,
|
|
16
19
|
validateContentRules,
|
|
17
20
|
} from './content-rules.mjs'
|
|
21
|
+
import { delegationsPathFor, loadDelegationsFile, ownerKeysForCommand, resolveDelegation, validateDelegationsDocument } from './delegation.mjs'
|
|
18
22
|
|
|
19
23
|
export const BOUNDARY_POLICY_SCHEMA = 'mnstry.atelier-boundary-policy@v1'
|
|
20
24
|
export const PROMOTE_EVENT_SCHEMA = 'git-promote-event@v1'
|
|
@@ -115,6 +119,12 @@ function unknownKeys(value, label, allowed) {
|
|
|
115
119
|
.map((key) => `${label} must not include additional property ${key}`)
|
|
116
120
|
}
|
|
117
121
|
|
|
122
|
+
// The schema declares `ext` as a reserved extension container that consumers
|
|
123
|
+
// must ignore. It is accepted, never read, and never carries authority.
|
|
124
|
+
function extErrors(value, label) {
|
|
125
|
+
return value?.ext != null && !isObject(value.ext) ? [`${label}.ext must be an object`] : []
|
|
126
|
+
}
|
|
127
|
+
|
|
118
128
|
function validAudienceList(value, label, required = false) {
|
|
119
129
|
if (value == null && !required) return []
|
|
120
130
|
if (!Array.isArray(value)) return [`${label} must be a list`]
|
|
@@ -128,8 +138,12 @@ function validAudienceList(value, label, required = false) {
|
|
|
128
138
|
export function validateBoundaryPolicy(policy, project = null) {
|
|
129
139
|
const errors = []
|
|
130
140
|
if (!isObject(policy)) return ['boundary policy must be a JSON object']
|
|
131
|
-
errors.push(...unknownKeys(policy, '/', new Set(['schema', 'mode', 'actors', 'repos', 'promotion', 'forbiddenPaths', 'contentRules', 'contentRuleExceptions', 'governanceLedgerPath'])))
|
|
141
|
+
errors.push(...unknownKeys(policy, '/', new Set(['schema', 'contractVersion', 'mode', 'actors', 'repos', 'promotion', 'forbiddenPaths', 'contentRules', 'contentRuleExceptions', 'governanceLedgerPath', 'ext'])))
|
|
132
142
|
if (policy.schema !== BOUNDARY_POLICY_SCHEMA) errors.push(`schema must be ${BOUNDARY_POLICY_SCHEMA}`)
|
|
143
|
+
if (policy.contractVersion != null && !(typeof policy.contractVersion === 'string' && /^1\.[0-9]+\.[0-9]+$/.test(policy.contractVersion))) {
|
|
144
|
+
errors.push('contractVersion must be a 1.x.y version')
|
|
145
|
+
}
|
|
146
|
+
errors.push(...extErrors(policy, '/'))
|
|
133
147
|
if (!VALID_BOUNDARY_MODES.has(policy.mode)) errors.push('mode must be strict or legacy-warning')
|
|
134
148
|
|
|
135
149
|
const actors = policy.actors
|
|
@@ -138,7 +152,8 @@ export function validateBoundaryPolicy(policy, project = null) {
|
|
|
138
152
|
if (!isObject(actors)) errors.push('actors must be an object')
|
|
139
153
|
if (!isObject(repos)) errors.push('repos must be an object')
|
|
140
154
|
if (policy.promotion != null && !isObject(policy.promotion)) errors.push('promotion must be an object')
|
|
141
|
-
errors.push(...unknownKeys(promotion, 'promotion', new Set(['requiresGitPromote', 'recordsPath'])))
|
|
155
|
+
errors.push(...unknownKeys(promotion, 'promotion', new Set(['requiresGitPromote', 'recordsPath', 'ext'])))
|
|
156
|
+
errors.push(...extErrors(promotion, 'promotion'))
|
|
142
157
|
if (policy.forbiddenPaths != null && !Array.isArray(policy.forbiddenPaths)) errors.push('forbiddenPaths must be a list')
|
|
143
158
|
if (policy.contentRules != null) errors.push(...validateContentRules(policy.contentRules))
|
|
144
159
|
if (policy.contentRuleExceptions != null) {
|
|
@@ -153,11 +168,15 @@ export function validateBoundaryPolicy(policy, project = null) {
|
|
|
153
168
|
}
|
|
154
169
|
|
|
155
170
|
for (const [actorId, actor] of Object.entries(isObject(actors) ? actors : {})) {
|
|
156
|
-
errors.push(...unknownKeys(actor, `actors.${actorId}`, new Set(['githubLogin', 'gitEmails', 'privateDomainRepo'])))
|
|
171
|
+
errors.push(...unknownKeys(actor, `actors.${actorId}`, new Set(['githubLogin', 'gitEmails', 'privateDomainRepo', 'ext'])))
|
|
172
|
+
errors.push(...extErrors(actor, `actors.${actorId}`))
|
|
157
173
|
if (!isObject(actor)) {
|
|
158
174
|
errors.push(`actors.${actorId} must be an object`)
|
|
159
175
|
continue
|
|
160
176
|
}
|
|
177
|
+
// The schema types these fields; a non-string login would still match through String().
|
|
178
|
+
if (actor.githubLogin != null && typeof actor.githubLogin !== 'string') errors.push(`actors.${actorId}.githubLogin must be a string`)
|
|
179
|
+
if (actor.gitEmails != null && !Array.isArray(actor.gitEmails)) errors.push(`actors.${actorId}.gitEmails must be a list`)
|
|
161
180
|
if (!firstString(actor.githubLogin) && !asArray(actor.gitEmails).length) {
|
|
162
181
|
errors.push(`actors.${actorId} must declare githubLogin or gitEmails`)
|
|
163
182
|
}
|
|
@@ -176,7 +195,8 @@ export function validateBoundaryPolicy(policy, project = null) {
|
|
|
176
195
|
}
|
|
177
196
|
|
|
178
197
|
for (const [repoName, repo] of Object.entries(isObject(repos) ? repos : {})) {
|
|
179
|
-
errors.push(...unknownKeys(repo, `repos.${repoName}`, new Set(['kind', 'ownerActor', 'readBoundary', 'allowedAudiences', 'forbiddenAudiences', 'autoCommit'])))
|
|
198
|
+
errors.push(...unknownKeys(repo, `repos.${repoName}`, new Set(['kind', 'ownerActor', 'readBoundary', 'allowedAudiences', 'forbiddenAudiences', 'autoCommit', 'ext'])))
|
|
199
|
+
errors.push(...extErrors(repo, `repos.${repoName}`))
|
|
180
200
|
if (!isObject(repo)) {
|
|
181
201
|
errors.push(`repos.${repoName} must be an object`)
|
|
182
202
|
continue
|
|
@@ -306,7 +326,7 @@ function nodePlacementFindings({ node, policy }) {
|
|
|
306
326
|
return findings
|
|
307
327
|
}
|
|
308
328
|
|
|
309
|
-
function actorFindings({ policy, project, actor, gitExecutable, allowNetworkActorResolution, allowHistoryActorResolution, forceActorErrors }) {
|
|
329
|
+
function actorFindings({ policy, project, actor, gitExecutable, allowNetworkActorResolution, allowHistoryActorResolution, forceActorErrors, operation, delegations, ownerKeys, now }) {
|
|
310
330
|
const findings = []
|
|
311
331
|
const operatedRepos = new Set(managedRepos(project).map((repo) => repo.name))
|
|
312
332
|
const ownedRepos = Object.entries(policy.repos ?? {}).filter(([name, repo]) =>
|
|
@@ -322,23 +342,102 @@ function actorFindings({ policy, project, actor, gitExecutable, allowNetworkActo
|
|
|
322
342
|
findings.push(finding({ severity, code: 'private-domain-actor-unverified', repo: repoName,
|
|
323
343
|
message: `${repoName}: could not verify local actor for private domain repo owned by ${repo.ownerActor}${current.reason ? ` (${current.reason})` : ''}` }))
|
|
324
344
|
} else if (current.actorId !== repo.ownerActor) {
|
|
325
|
-
|
|
345
|
+
// Only an owner-signed delegation, verified against a host-supplied key,
|
|
346
|
+
// changes this outcome, and only for its operator, repos and operation.
|
|
347
|
+
const delegated = resolveDelegation({ policy, project, delegations, repoName, repo, actorId: current.actorId, operation, ownerKeys, now })
|
|
348
|
+
if (delegated.applies) {
|
|
349
|
+
const { id, owner, expiresAt } = delegated.delegation
|
|
350
|
+
findings.push(finding({ severity: 'info', code: 'private-domain-delegated-operator', repo: repoName,
|
|
351
|
+
message: `${repoName}: ${current.actorId} operates under delegation ${id} from owner ${owner} until ${expiresAt}`,
|
|
352
|
+
details: { actorId: current.actorId, owner, delegationId: id, expiresAt, operation } }))
|
|
353
|
+
} else {
|
|
354
|
+
const note = delegated.reason === 'delegation-missing' ? '' : ` (${delegated.reason})`
|
|
355
|
+
findings.push(finding({ severity, code: 'private-domain-actor-mismatch', repo: repoName,
|
|
356
|
+
message: `${repoName}: private domain repo is owned by ${repo.ownerActor}, but current actor is ${current.actorId}${note}`,
|
|
357
|
+
details: { delegationReason: delegated.reason } }))
|
|
358
|
+
}
|
|
326
359
|
}
|
|
327
360
|
}
|
|
328
361
|
return findings
|
|
329
362
|
}
|
|
330
363
|
|
|
331
|
-
|
|
364
|
+
/**
|
|
365
|
+
* The index a pre-commit hook is judging. Git names it in GIT_INDEX_FILE (a
|
|
366
|
+
* temporary index under `git commit -a` or `git commit <paths>`, a linked
|
|
367
|
+
* worktree's own index, or `.git/index` relative to the work tree's top) and
|
|
368
|
+
* runs the hook at that top, though a chained hook may have moved. The work
|
|
369
|
+
* tree is found by Git's own discovery from where the hook runs; the hook's
|
|
370
|
+
* GIT_DIR is not used, because in a linked worktree Git exports it without a
|
|
371
|
+
* work tree, which would make any subdirectory look like the top. A work tree
|
|
372
|
+
* discovery cannot find is unresolved, and blocks. Returns null when no index
|
|
373
|
+
* is named.
|
|
374
|
+
*/
|
|
375
|
+
export function hookIndexFromEnvironment({ env = process.env, cwd = process.cwd(), gitExecutable = 'git' } = {}) {
|
|
376
|
+
const named = typeof env.GIT_INDEX_FILE === 'string' ? env.GIT_INDEX_FILE : ''
|
|
377
|
+
if (!named) return null
|
|
378
|
+
const workTree = gitPathOutput(cwd, ['rev-parse', '--show-toplevel'], sanitizedGitEnvironment(), gitExecutable)
|
|
379
|
+
const commonDir = gitPathOutput(cwd, ['rev-parse', '--git-common-dir'], sanitizedGitEnvironment(), gitExecutable)
|
|
380
|
+
return { workTree, indexFile: path.resolve(workTree ?? cwd, named), commonDir }
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function gitPathOutput(cwd, args, env, gitExecutable = 'git') {
|
|
384
|
+
const result = spawnSync(gitExecutable, ['-C', cwd, ...args], { encoding: 'utf8', env })
|
|
385
|
+
const output = result.status === 0 ? String(result.stdout ?? '').replace(/\r?\n$/, '') : ''
|
|
386
|
+
return output ? nativeRealPath(path.resolve(cwd, output)) : null
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// The file system's own spelling of a path, so a configured path that differs
|
|
390
|
+
// only in case (macOS, Windows) still names the same repo.
|
|
391
|
+
function nativeRealPath(value) {
|
|
392
|
+
try {
|
|
393
|
+
return fs.realpathSync.native(path.resolve(value))
|
|
394
|
+
} catch {
|
|
395
|
+
return path.resolve(value)
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/**
|
|
400
|
+
* Where each managed repo's staged state is read. The repo the hook is
|
|
401
|
+
* committing is read from the hook's work tree and index: the managed repo
|
|
402
|
+
* configured at that work tree, or the one managed repo sharing its Git
|
|
403
|
+
* directory (a linked worktree of it). Every other repo is read as configured.
|
|
404
|
+
* When an index is named but no single managed repo is being committed, the
|
|
405
|
+
* guard cannot tell what it is judging, and says so.
|
|
406
|
+
*/
|
|
407
|
+
function stagedTargets(project, hookIndex, gitExecutable = 'git') {
|
|
408
|
+
const repos = managedRepos(project)
|
|
409
|
+
const targets = new Map(repos.map((repo) => [repo, { root: repo.path, indexFile: null }]))
|
|
410
|
+
if (!hookIndex) return { targets, unresolved: null }
|
|
411
|
+
let committing = hookIndex.workTree ? repos.filter((repo) => repo.path && nativeRealPath(repo.path) === hookIndex.workTree) : []
|
|
412
|
+
if (committing.length === 0 && hookIndex.commonDir) {
|
|
413
|
+
committing = repos.filter((repo) => repo.path && fs.existsSync(repo.path)
|
|
414
|
+
&& gitPathOutput(repo.path, ['rev-parse', '--git-common-dir'], sanitizedGitEnvironment(), gitExecutable) === hookIndex.commonDir)
|
|
415
|
+
}
|
|
416
|
+
if (committing.length !== 1) return { targets, unresolved: hookIndex.workTree ?? '(an unresolved work tree)' }
|
|
417
|
+
targets.set(committing[0], { root: hookIndex.workTree, indexFile: hookIndex.indexFile })
|
|
418
|
+
return { targets, unresolved: null }
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function stagedPathsResult(targets, { gitExecutable = 'git' } = {}) {
|
|
332
422
|
const paths = []
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
423
|
+
const failures = []
|
|
424
|
+
for (const [repo, target] of targets) {
|
|
425
|
+
if (!target.root || !fs.existsSync(path.join(target.root, '.git'))) continue
|
|
426
|
+
// NUL-separated names are exact: a quoted name could never match a forbidden-path glob.
|
|
427
|
+
const result = gitOutputResult(target.root, ['diff', '--cached', '--name-only', '-z', '--no-relative', '--diff-filter=ACMR'], { gitExecutable, indexFile: target.indexFile })
|
|
428
|
+
if (!result.ok) {
|
|
429
|
+
failures.push({ repo, reason: result.code })
|
|
430
|
+
continue
|
|
431
|
+
}
|
|
432
|
+
for (const rel of result.stdout.split('\0').filter(Boolean)) {
|
|
433
|
+
paths.push({ repo: repo.name, repoRoot: target.root, path: normalize(rel) })
|
|
339
434
|
}
|
|
340
435
|
}
|
|
341
|
-
return paths
|
|
436
|
+
return { paths, failures }
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
export function stagedPathsForProject(project, { gitExecutable = 'git', hookIndex = null } = {}) {
|
|
440
|
+
return stagedPathsResult(stagedTargets(project, hookIndex, gitExecutable).targets, { gitExecutable }).paths
|
|
342
441
|
}
|
|
343
442
|
|
|
344
443
|
function forbiddenPathFindings({ policy, stagedPaths }) {
|
|
@@ -380,30 +479,14 @@ function isInitializationDefault(field, value) {
|
|
|
380
479
|
return Array.isArray(defaults) && defaults.includes(value)
|
|
381
480
|
}
|
|
382
481
|
|
|
482
|
+
/** Diff sections with their hunks, read by the same parser as the content scan. */
|
|
383
483
|
export function diffFileSections(diff) {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
current = { path: header ? normalize(header[2]) : null, hunks: [], lines: [] }
|
|
391
|
-
files.push(current)
|
|
392
|
-
hunk = null
|
|
393
|
-
continue
|
|
394
|
-
}
|
|
395
|
-
if (!current) continue
|
|
396
|
-
current.lines.push(line)
|
|
397
|
-
if (line.startsWith('@@')) {
|
|
398
|
-
hunk = { added: [], removed: [] }
|
|
399
|
-
current.hunks.push(hunk)
|
|
400
|
-
continue
|
|
401
|
-
}
|
|
402
|
-
if (!hunk) continue
|
|
403
|
-
if (/^\+(?!\+)/.test(line)) hunk.added.push(line.slice(1))
|
|
404
|
-
else if (/^-(?!-)/.test(line)) hunk.removed.push(line.slice(1))
|
|
405
|
-
}
|
|
406
|
-
return files
|
|
484
|
+
return parseDiffSections(diff, { detail: true }).map((section) => ({
|
|
485
|
+
path: section.path,
|
|
486
|
+
hunks: section.hunks,
|
|
487
|
+
lines: section.lines,
|
|
488
|
+
incomplete: section.incomplete,
|
|
489
|
+
}))
|
|
407
490
|
}
|
|
408
491
|
|
|
409
492
|
export function semanticChangesInFile(file) {
|
|
@@ -426,26 +509,38 @@ export function semanticChangesInFile(file) {
|
|
|
426
509
|
return changes
|
|
427
510
|
}
|
|
428
511
|
|
|
429
|
-
function semanticDiffFindings({
|
|
512
|
+
function semanticDiffFindings({ targets, gitExecutable = 'git' }) {
|
|
430
513
|
const findings = []
|
|
431
514
|
const severity = 'error'
|
|
432
|
-
for (const repo of
|
|
433
|
-
if (!
|
|
515
|
+
for (const [repo, target] of targets) {
|
|
516
|
+
if (!target.root || !fs.existsSync(path.join(target.root, '.git'))) {
|
|
434
517
|
findings.push(unscannableRepoFinding(repo, 'configured path is absent or is not a Git checkout'))
|
|
435
518
|
continue
|
|
436
519
|
}
|
|
437
|
-
|
|
438
|
-
|
|
520
|
+
// --text: a document declared binary, or holding a NUL byte, still has its fields read.
|
|
521
|
+
const result = gitOutputResult(target.root, [...canonicalDiffArgs(['--cached'], ['--text']), '--', '*.md', '*.kg.json'], { gitExecutable, indexFile: target.indexFile })
|
|
522
|
+
if (!result.ok) {
|
|
439
523
|
findings.push(unscannableRepoFinding(repo, 'git diff --cached failed'))
|
|
440
524
|
continue
|
|
441
525
|
}
|
|
442
526
|
if (!result.stdout.trim()) continue
|
|
443
527
|
for (const file of diffFileSections(result.stdout)) {
|
|
528
|
+
if (file.incomplete) {
|
|
529
|
+
findings.push(finding({
|
|
530
|
+
severity,
|
|
531
|
+
code: 'semantic-field-scan-incomplete',
|
|
532
|
+
repo: repo.name,
|
|
533
|
+
path: file.path,
|
|
534
|
+
message: `${repo.name}/${file.path ?? '(unknown path)'}: the staged diff could not be read, so its semantic fields were not checked`,
|
|
535
|
+
details: { reason: file.incomplete },
|
|
536
|
+
}))
|
|
537
|
+
continue
|
|
538
|
+
}
|
|
444
539
|
const changes = semanticChangesInFile(file)
|
|
445
540
|
if (!changes.length) continue
|
|
446
|
-
// The marker has to
|
|
447
|
-
// a marker elsewhere in the commit approves nothing.
|
|
448
|
-
if (
|
|
541
|
+
// The marker has to be added in the same file's diff as the change it approves:
|
|
542
|
+
// a marker elsewhere in the commit, a removed one, or one in a file name approves nothing.
|
|
543
|
+
if (file.hunks.some((hunk) => hunk.added.some((line) => REVIEW_MARKER_RE.test(line)))) continue
|
|
449
544
|
const label = `${repo.name}/${file.path ?? '(unknown path)'}`
|
|
450
545
|
findings.push(
|
|
451
546
|
finding({
|
|
@@ -462,17 +557,17 @@ function semanticDiffFindings({ policy, project, gitExecutable = 'git' }) {
|
|
|
462
557
|
return findings
|
|
463
558
|
}
|
|
464
559
|
|
|
465
|
-
function stagedContentFindings({ policy,
|
|
560
|
+
function stagedContentFindings({ policy, targets, gitExecutable = 'git' }) {
|
|
466
561
|
const rules = resolveContentRules(policy)
|
|
467
562
|
const exceptions = asArray(policy?.contentRuleExceptions)
|
|
468
563
|
const findings = []
|
|
469
564
|
let totalBytes = 0
|
|
470
|
-
for (const repo of
|
|
471
|
-
if (!
|
|
565
|
+
for (const [repo, target] of targets) {
|
|
566
|
+
if (!target.root || !fs.existsSync(path.join(target.root, '.git'))) {
|
|
472
567
|
findings.push(unscannableRepoFinding(repo, 'configured path is absent or is not a Git checkout'))
|
|
473
568
|
continue
|
|
474
569
|
}
|
|
475
|
-
const result = scanStagedRepository({ repoRoot:
|
|
570
|
+
const result = scanStagedRepository({ repoRoot: target.root, rules, exceptions, repo: repo.name, gitExecutable, indexFile: target.indexFile })
|
|
476
571
|
findings.push(...result.findings, ...result.diagnostics)
|
|
477
572
|
totalBytes += result.bytes
|
|
478
573
|
if (totalBytes > CHECK_AGGREGATE_MAX_BYTES) {
|
|
@@ -515,7 +610,7 @@ function repoForCwd(project, cwd) {
|
|
|
515
610
|
* which reports without blocking, so an accepted usage elsewhere in the repo can
|
|
516
611
|
* never strand unrelated work on the machine.
|
|
517
612
|
*/
|
|
518
|
-
export function checkPushContent({ project, policy, repo, updates, cwd = process.cwd(), gitRunner = spawnSync } = {}) {
|
|
613
|
+
export function checkPushContent({ project, policy, repo, updates, remote = null, cwd = process.cwd(), gitRunner = spawnSync } = {}) {
|
|
519
614
|
const target = repo ?? repoForCwd(project, cwd)
|
|
520
615
|
if (!target) {
|
|
521
616
|
// Fail closed. This hook is only installed into managed repos, so failing to
|
|
@@ -533,15 +628,17 @@ export function checkPushContent({ project, policy, repo, updates, cwd = process
|
|
|
533
628
|
const findings = []
|
|
534
629
|
let totalBytes = 0
|
|
535
630
|
for (const update of updates) {
|
|
536
|
-
const result = scanPushUpdate({ repoRoot: target.path, update, rules, exceptions, repo: target.name, gitRunner })
|
|
631
|
+
const result = scanPushUpdate({ repoRoot: target.path, update, rules, exceptions, repo: target.name, remote, gitRunner, maxBytes: CHECK_AGGREGATE_MAX_BYTES - totalBytes })
|
|
537
632
|
findings.push(...result.findings, ...result.diagnostics)
|
|
538
633
|
totalBytes += result.bytes
|
|
634
|
+
// The per-commit scan reports its own budget refusal; say it once.
|
|
635
|
+
if (result.diagnostics.some((item) => item.details?.reason === 'push-evidence-too-large')) break
|
|
539
636
|
if (totalBytes > CHECK_AGGREGATE_MAX_BYTES) {
|
|
540
637
|
findings.push(finding({
|
|
541
638
|
severity: 'error',
|
|
542
639
|
code: 'content-scan-incomplete',
|
|
543
640
|
repo: target.name,
|
|
544
|
-
message: `push evidence exceeds the ${CHECK_AGGREGATE_MAX_BYTES}-byte aggregate limit`,
|
|
641
|
+
message: `push evidence exceeds the ${CHECK_AGGREGATE_MAX_BYTES}-byte aggregate limit; push it in smaller parts`,
|
|
545
642
|
}))
|
|
546
643
|
break
|
|
547
644
|
}
|
|
@@ -635,7 +732,7 @@ function promotionFindings({ policy, project, graph }) {
|
|
|
635
732
|
return findings
|
|
636
733
|
}
|
|
637
734
|
|
|
638
|
-
export function checkBoundaryPolicy({ project, policy, staged = false, stagedOnly = false, actor = null, gitExecutable = 'git', allowNetworkActorResolution = true, allowHistoryActorResolution = false, forceActorErrors = false } = {}) {
|
|
735
|
+
export function checkBoundaryPolicy({ project, policy, staged = false, stagedOnly = false, actor = null, gitExecutable = 'git', allowNetworkActorResolution = true, allowHistoryActorResolution = false, forceActorErrors = false, delegations = null, ownerKeys = null, now = Date.now(), hookIndex = null } = {}) {
|
|
639
736
|
const validationErrors = validateBoundaryPolicy(policy, project)
|
|
640
737
|
let graph = null
|
|
641
738
|
const findings = validationErrors.map((message) => finding({ severity: 'error', code: 'boundary-policy-invalid', message }))
|
|
@@ -645,12 +742,27 @@ export function checkBoundaryPolicy({ project, policy, staged = false, stagedOnl
|
|
|
645
742
|
for (const node of graph.nodes ?? []) findings.push(...nodePlacementFindings({ node, policy }))
|
|
646
743
|
findings.push(...promotionFindings({ policy, project, graph }))
|
|
647
744
|
}
|
|
648
|
-
|
|
745
|
+
// Delegations come from their own document, never from the policy. An invalid
|
|
746
|
+
// document is an error and grants nothing.
|
|
747
|
+
const delegationErrors = delegations == null ? [] : validateDelegationsDocument(delegations, policy)
|
|
748
|
+
findings.push(...delegationErrors.map((message) => finding({ severity: 'error', code: 'boundary-delegations-invalid', message })))
|
|
749
|
+
const delegationList = delegations != null && !delegationErrors.length ? delegations.delegations : []
|
|
750
|
+
// A staged check is the commit path (the installed pre-commit hook runs it).
|
|
751
|
+
const operation = staged ? 'pre-commit' : 'boundary-check'
|
|
752
|
+
findings.push(...actorFindings({ policy, project, actor, gitExecutable, allowNetworkActorResolution, allowHistoryActorResolution, forceActorErrors, operation, delegations: delegationList, ownerKeys, now }))
|
|
649
753
|
if (staged) {
|
|
650
|
-
const
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
754
|
+
const { targets, unresolved } = stagedTargets(project, hookIndex, gitExecutable)
|
|
755
|
+
if (unresolved) {
|
|
756
|
+
findings.push(finding({
|
|
757
|
+
code: 'pre-commit-repo-unresolved',
|
|
758
|
+
message: `${unresolved} is not a repo declared in ${project?.configPath ?? 'the project config'}, or several declared repos share its Git directory; the pre-commit guard cannot tell what is being committed`,
|
|
759
|
+
}))
|
|
760
|
+
}
|
|
761
|
+
const listed = stagedPathsResult(targets, { gitExecutable })
|
|
762
|
+
findings.push(...listed.failures.map(({ repo, reason }) => unscannableRepoFinding(repo, `staged paths could not be listed (${reason})`)))
|
|
763
|
+
findings.push(...forbiddenPathFindings({ policy, stagedPaths: listed.paths }))
|
|
764
|
+
findings.push(...semanticDiffFindings({ targets, gitExecutable }))
|
|
765
|
+
findings.push(...stagedContentFindings({ policy, targets, gitExecutable }))
|
|
654
766
|
}
|
|
655
767
|
const errors = findings.filter((item) => item.severity === 'error')
|
|
656
768
|
const warnings = findings.filter((item) => item.severity !== 'error')
|
|
@@ -673,9 +785,14 @@ export function runBoundaryCheckCommand(argv = process.argv.slice(2)) {
|
|
|
673
785
|
const project = commandProject({ argv })
|
|
674
786
|
const loaded = loadBoundaryPolicy(project)
|
|
675
787
|
const missingFindings = loaded.errors?.map((message) => finding({ code: 'boundary-policy-missing', message })) ?? []
|
|
676
|
-
const
|
|
677
|
-
|
|
788
|
+
const sidecar = loaded.ok ? loadDelegationsFile(delegationsPathFor(loaded.policyPath)) : { present: false, document: null, errors: [] }
|
|
789
|
+
const sidecarFindings = sidecar.errors.map((message) => finding({ code: 'boundary-delegations-invalid', message }))
|
|
790
|
+
// Owner keys are read only when a delegations document exists.
|
|
791
|
+
const ownerKeys = sidecar.present && sidecar.document ? ownerKeysForCommand() : null
|
|
792
|
+
let report = loaded.ok
|
|
793
|
+
? checkBoundaryPolicy({ project, policy: loaded.policy, staged, stagedOnly, actor: firstString(args.actor), delegations: sidecar.document, ownerKeys, hookIndex: staged ? hookIndexFromEnvironment() : null })
|
|
678
794
|
: { ok: false, mode: null, schema: BOUNDARY_POLICY_SCHEMA, graphCounts: null, findings: missingFindings, errors: missingFindings, warnings: [] }
|
|
795
|
+
if (sidecarFindings.length) report = { ...report, ok: false, findings: [...sidecarFindings, ...report.findings], errors: [...sidecarFindings, ...report.errors] }
|
|
679
796
|
|
|
680
797
|
if (json) {
|
|
681
798
|
console.log(JSON.stringify(report, null, 2))
|
|
@@ -742,7 +859,7 @@ ATELIER_HOOK_PROJECT_CONFIG="$ATELIER_HOOK_REPO_ROOT"/${shellSingleQuote(portabl
|
|
|
742
859
|
// pre-push reads the ref updates git writes to stdin and judges only that range;
|
|
743
860
|
// pre-commit judges the staged diff. Neither scans the whole tree — that view is
|
|
744
861
|
// `atelier boundary audit`, which reports without blocking.
|
|
745
|
-
const invocation = hookName === 'pre-push' ? `boundary push-check ${config}` : `boundary check --staged ${config}`
|
|
862
|
+
const invocation = hookName === 'pre-push' ? `boundary push-check ${config} --remote="\${1:-}"` : `boundary check --staged ${config}`
|
|
746
863
|
return `#!/usr/bin/env bash
|
|
747
864
|
# MNSTRY_ATELIER_BOUNDARY_GUARD ${hookName}
|
|
748
865
|
set -euo pipefail
|
|
@@ -788,13 +905,17 @@ export function runBoundaryPushCheckCommand(argv = process.argv.slice(2), { stdi
|
|
|
788
905
|
console.log('[boundary:push-check] no ref updates on stdin · nothing to judge')
|
|
789
906
|
process.exit(0)
|
|
790
907
|
}
|
|
791
|
-
|
|
908
|
+
// The installed hook passes the remote Git names as its first argument.
|
|
909
|
+
const report = checkPushContent({ project, policy: loaded.policy, updates: parsed.updates, remote: firstString(args.remote), cwd: process.cwd() })
|
|
792
910
|
if (args.json) {
|
|
793
911
|
console.log(JSON.stringify(report, null, 2))
|
|
794
912
|
} else {
|
|
795
913
|
console.log(`[boundary:push-check] ${report.ok ? 'passed' : 'blocked'} · ${report.repo ?? 'unknown repo'} · ${report.errors.length} errors · ${report.warnings.length} warnings`)
|
|
796
|
-
for (const item of report.findings.slice(0, 50))
|
|
797
|
-
|
|
914
|
+
for (const item of report.findings.slice(0, 50)) {
|
|
915
|
+
const also = item.commits?.length ? ` (also in commits ${item.commits.map((commit) => commit.slice(0, 12)).join(', ')})` : ''
|
|
916
|
+
console.log(`${item.severity} ${item.rule ?? item.code}: ${item.message}${also}`)
|
|
917
|
+
}
|
|
918
|
+
if (!report.ok && report.repo && report.errors.some((item) => item.code === 'content-rule-violation')) {
|
|
798
919
|
console.log('')
|
|
799
920
|
console.log('If this usage is intentional, declare it in the boundary policy:')
|
|
800
921
|
console.log(' contentRuleExceptions: [{ rule, repo, paths: [...], reason: "why this is accepted" }]')
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process'
|
|
2
|
+
import { bytesAt, workspaceRoot, digest } from '../capabilities/files.mjs'
|
|
3
|
+
import { harnessRef } from '../harnesses/contracts.mjs'
|
|
4
|
+
import { inspectBuild } from './ledger.mjs'
|
|
5
|
+
|
|
6
|
+
// Read-only local adapter. A host still owns commands, writer custody and CI.
|
|
7
|
+
export function prepareGitCandidate({ workspaceRoot: input, records, artifact, writer }) {
|
|
8
|
+
const root = workspaceRoot(input), state = inspectBuild(records)
|
|
9
|
+
if (!state.establishment) throw new Error('build objective missing')
|
|
10
|
+
const env = Object.fromEntries(Object.entries(process.env).filter(([key]) => !key.toUpperCase().startsWith('GIT_')))
|
|
11
|
+
const git = args => execFileSync('git', ['-C', root, ...args], { env, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] }).trim()
|
|
12
|
+
if (git(['status', '--porcelain', '--untracked-files=all'])) throw new Error('candidate requires clean repository sources')
|
|
13
|
+
const commit = git(['rev-parse', 'HEAD']), tree = git(['rev-parse', 'HEAD^{tree}'])
|
|
14
|
+
const artifactDigest = digest(bytesAt(root, artifact))
|
|
15
|
+
if (git(['rev-parse', 'HEAD']) !== commit || git(['status', '--porcelain', '--untracked-files=all']) || digest(bytesAt(root, artifact)) !== artifactDigest) throw new Error('candidate changed during capture')
|
|
16
|
+
return { data: { objective: harnessRef(state.establishment), repository: state.establishment.data.repository, commit, tree, artifactDigest, writer,
|
|
17
|
+
...(state.candidate ? { supersedes: harnessRef(state.candidate) } : {}) }, artifact, assurance: 'local-bytes-and-git-observation; writer-is-caller-reported', authority: 'none' }
|
|
18
|
+
}
|
|
@@ -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
|
+
}
|