@mnstry/atelier 0.2.0-alpha.12 → 0.2.0-alpha.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +894 -0
- package/README.md +159 -6
- package/contracts/atelier-adoption-policy.v2.schema.json +55 -0
- package/contracts/atelier-architecture.v1.schema.json +258 -0
- package/contracts/atelier-boundary-delegations.v1.schema.json +161 -0
- package/contracts/atelier-boundary-owner-keys.v1.schema.json +98 -0
- package/contracts/atelier-build.v1.schema.json +1076 -0
- package/contracts/atelier-capability.v1.schema.json +1252 -0
- package/contracts/atelier-coordination.v1.schema.json +1050 -0
- package/contracts/atelier-evidence-navigation.v1.schema.json +603 -0
- package/contracts/atelier-harness.v1.schema.json +234 -0
- package/contracts/atelier-ingestion.v1.schema.json +806 -0
- package/contracts/atelier-inquiry.v1.schema.json +1772 -0
- package/contracts/atelier-instruction-adoption.v1.schema.json +490 -0
- package/contracts/atelier-interaction.v1.schema.json +553 -0
- package/contracts/atelier-judgment.v1.schema.json +690 -0
- package/contracts/atelier-knowledge.v1.schema.json +1512 -0
- package/contracts/atelier-learning.v1.schema.json +1095 -0
- package/contracts/atelier-migration.v3.schema.json +64 -0
- package/contracts/atelier-personal-workspace-manifest.v1.schema.json +87 -0
- package/contracts/atelier-personal-workspace-overlay.v1.schema.json +299 -0
- package/contracts/atelier-template-adoption.v1.schema.json +223 -0
- package/contracts/atelier-template-profile.v1.schema.json +1168 -0
- package/contracts/atelier-trackable.v1.schema.json +781 -0
- package/contracts/atelier-upgrade-plan.v3.schema.json +532 -0
- package/docs/architecture.md +89 -0
- package/docs/assurance-controls.md +2 -2
- package/docs/blocks/capability-stewardship.md +17 -0
- package/docs/blocks/claims.md +16 -3
- package/docs/blocks/inquiry-harnesses.md +19 -0
- package/docs/blocks/learning-harnesses.md +17 -0
- package/docs/capability-stewardship.md +267 -0
- package/docs/coordination.md +51 -0
- package/docs/decision-practices.md +111 -0
- package/docs/decisions.md +29 -0
- package/docs/evidence-navigation.md +237 -0
- package/docs/exact-upgrades.md +3 -2
- package/docs/ingestion.md +181 -0
- package/docs/inquiry-harnesses.md +210 -0
- package/docs/install.md +80 -3
- package/docs/intake-and-guides.md +5 -0
- package/docs/integration-contract-decisions.md +24 -2
- package/docs/interaction-and-reflection.md +72 -0
- package/docs/knowledge-graph.md +20 -0
- package/docs/knowledge-health/api.md +30 -0
- package/docs/knowledge-health/assessment/api.md +96 -0
- package/docs/knowledge-health/assessment/capabilities.md +40 -0
- package/docs/knowledge-health/assessment/evaluation.md +27 -0
- package/docs/knowledge-health/assessment/facilitator.md +30 -0
- package/docs/knowledge-health/assessment/quickstart.md +61 -0
- package/docs/knowledge-health/assessment/troubleshooting.md +23 -0
- package/docs/knowledge-health/devday/facilitator.md +16 -0
- package/docs/knowledge-health/devday/participant-response.md +22 -0
- package/docs/knowledge-health/devday/public-default.md +107 -0
- package/docs/knowledge-health/devday/quickstart.md +18 -0
- package/docs/knowledge-health/devday/support.md +22 -0
- package/docs/knowledge-health/exercise.md +17 -0
- package/docs/knowledge-health/facilitator.md +20 -0
- package/docs/knowledge-health/quickstart.md +94 -0
- package/docs/knowledge-health/readme.md +15 -0
- package/docs/knowledge-health/support.md +34 -0
- package/docs/knowledge-setup.md +248 -0
- package/docs/knowledge-workspace.md +264 -0
- package/docs/learning-harnesses.md +237 -0
- package/docs/learning.md +215 -0
- package/docs/local-services.md +223 -19
- package/docs/obsidian-contract.md +246 -15
- package/docs/obsidian-plugin.md +252 -7
- package/docs/obsidian.md +874 -52
- package/docs/optional-ingestion.md +83 -0
- package/docs/personal-workspace.md +266 -0
- package/docs/practical-judgment.md +92 -0
- package/docs/progressive-ingestion-acceptance.md +89 -0
- package/docs/progressive-ingestion.md +235 -0
- package/docs/project-command-inventory.md +1 -0
- package/docs/release-engineering.md +50 -0
- package/docs/repo-boundary-guard.md +120 -3
- package/docs/research-starter-quickstart.md +66 -0
- package/docs/research-starter.md +114 -0
- package/docs/semantic-ingestion.md +198 -0
- package/docs/semantic-operation.md +318 -0
- package/docs/skill-steward.md +6 -0
- package/docs/template-profile.md +164 -0
- package/docs/template-upgrades.md +114 -0
- package/docs/trackables.md +111 -0
- package/docs/upgrade.md +219 -2
- package/fixtures/atelier-architecture/binding/invalid/document.json +19 -0
- package/fixtures/atelier-architecture/binding/valid/document.json +19 -0
- package/fixtures/atelier-architecture/catalog/invalid/document.json +290 -0
- package/fixtures/atelier-architecture/catalog/valid/document.json +343 -0
- package/fixtures/atelier-build-contract/attempt/invalid/document.json +20 -0
- package/fixtures/atelier-build-contract/attempt/valid/document.json +19 -0
- package/fixtures/atelier-build-contract/candidate/invalid/document.json +24 -0
- package/fixtures/atelier-build-contract/candidate/valid/document.json +23 -0
- package/fixtures/atelier-build-contract/decision/invalid/document.json +31 -0
- package/fixtures/atelier-build-contract/decision/valid/document.json +30 -0
- package/fixtures/atelier-build-contract/delivery/invalid/document.json +18 -0
- package/fixtures/atelier-build-contract/delivery/valid/document.json +17 -0
- package/fixtures/atelier-build-contract/gate/invalid/document.json +22 -0
- package/fixtures/atelier-build-contract/gate/valid/document.json +21 -0
- package/fixtures/atelier-build-contract/ledger/invalid/document.json +270 -0
- package/fixtures/atelier-build-contract/ledger/valid/document.json +269 -0
- package/fixtures/atelier-build-contract/objective/invalid/document.json +73 -0
- package/fixtures/atelier-build-contract/objective/valid/document.json +72 -0
- package/fixtures/atelier-build-contract/progress/invalid/document.json +18 -0
- package/fixtures/atelier-build-contract/progress/valid/document.json +17 -0
- package/fixtures/atelier-capability-contract/adoption/invalid/document.json +71 -0
- package/fixtures/atelier-capability-contract/adoption/valid/document.json +70 -0
- package/fixtures/atelier-capability-contract/event/invalid/document.json +18 -0
- package/fixtures/atelier-capability-contract/event/valid/document.json +17 -0
- package/fixtures/atelier-capability-contract/journal/invalid/document.json +382 -0
- package/fixtures/atelier-capability-contract/journal/valid/document.json +381 -0
- package/fixtures/atelier-capability-contract/notice/invalid/document.json +8 -0
- package/fixtures/atelier-capability-contract/notice/valid/document.json +7 -0
- package/fixtures/atelier-capability-contract/package/invalid/document.json +59 -0
- package/fixtures/atelier-capability-contract/package/valid/document.json +58 -0
- package/fixtures/atelier-capability-contract/plan/invalid/document.json +107 -0
- package/fixtures/atelier-capability-contract/plan/valid/document.json +106 -0
- package/fixtures/atelier-capability-contract/release/invalid/document.json +85 -0
- package/fixtures/atelier-capability-contract/release/valid/document.json +84 -0
- package/fixtures/atelier-capability-contract/state/invalid/document.json +316 -0
- package/fixtures/atelier-capability-contract/state/valid/document.json +315 -0
- package/fixtures/atelier-coordination/dependency/invalid/document.json +37 -0
- package/fixtures/atelier-coordination/dependency/valid/document.json +36 -0
- package/fixtures/atelier-coordination/directive/invalid/document.json +25 -0
- package/fixtures/atelier-coordination/directive/valid/document.json +24 -0
- package/fixtures/atelier-coordination/disposition/invalid/document.json +34 -0
- package/fixtures/atelier-coordination/disposition/valid/document.json +33 -0
- package/fixtures/atelier-coordination/outcome/invalid/document.json +39 -0
- package/fixtures/atelier-coordination/outcome/valid/document.json +38 -0
- package/fixtures/atelier-coordination/recovery/invalid/document.json +34 -0
- package/fixtures/atelier-coordination/recovery/valid/document.json +33 -0
- package/fixtures/atelier-harness-contract/handoff/invalid/document.json +25 -0
- package/fixtures/atelier-harness-contract/handoff/valid/document.json +24 -0
- package/fixtures/atelier-ingestion/invalid/authority.json +19 -0
- package/fixtures/atelier-ingestion/semantic-baseline.json +26 -0
- package/fixtures/atelier-ingestion/semantic-operation.json +75 -0
- package/fixtures/atelier-ingestion/valid/plan-input.json +18 -0
- package/fixtures/atelier-inquiry-contract/assessment/invalid/document.json +41 -0
- package/fixtures/atelier-inquiry-contract/assessment/valid/document.json +40 -0
- package/fixtures/atelier-inquiry-contract/bundle/invalid/document.json +51 -0
- package/fixtures/atelier-inquiry-contract/bundle/valid/document.json +50 -0
- package/fixtures/atelier-inquiry-contract/campaign/invalid/document.json +17 -0
- package/fixtures/atelier-inquiry-contract/campaign/valid/document.json +16 -0
- package/fixtures/atelier-inquiry-contract/decision/invalid/document.json +22 -0
- package/fixtures/atelier-inquiry-contract/decision/valid/document.json +21 -0
- package/fixtures/atelier-inquiry-contract/feedback/invalid/document.json +19 -0
- package/fixtures/atelier-inquiry-contract/feedback/valid/document.json +18 -0
- package/fixtures/atelier-inquiry-contract/handoff/invalid/document.json +92 -0
- package/fixtures/atelier-inquiry-contract/handoff/valid/document.json +91 -0
- package/fixtures/atelier-inquiry-contract/hypothesis/invalid/document.json +20 -0
- package/fixtures/atelier-inquiry-contract/hypothesis/valid/document.json +19 -0
- package/fixtures/atelier-inquiry-contract/ledger/invalid/document.json +680 -0
- package/fixtures/atelier-inquiry-contract/ledger/valid/document.json +679 -0
- package/fixtures/atelier-inquiry-contract/legacy-assessment/invalid/document.json +19 -0
- package/fixtures/atelier-inquiry-contract/legacy-assessment/valid/document.json +18 -0
- package/fixtures/atelier-inquiry-contract/request/invalid/document.json +50 -0
- package/fixtures/atelier-inquiry-contract/request/valid/document.json +49 -0
- package/fixtures/atelier-inquiry-contract/source/invalid/document.json +18 -0
- package/fixtures/atelier-inquiry-contract/source/valid/document.json +17 -0
- package/fixtures/atelier-inquiry-contract/withdrawal/invalid/document.json +16 -0
- package/fixtures/atelier-inquiry-contract/withdrawal/valid/document.json +15 -0
- package/fixtures/atelier-instruction-adoption/context-receipt/invalid/document.json +18 -0
- package/fixtures/atelier-instruction-adoption/context-receipt/valid/document.json +17 -0
- package/fixtures/atelier-instruction-adoption/plan/invalid/document.json +63 -0
- package/fixtures/atelier-instruction-adoption/plan/valid/document.json +62 -0
- package/fixtures/atelier-instruction-adoption/receipt/invalid/document.json +22 -0
- package/fixtures/atelier-instruction-adoption/receipt/valid/document.json +21 -0
- package/fixtures/atelier-instruction-adoption/state/invalid/document.json +30 -0
- package/fixtures/atelier-instruction-adoption/state/valid/document.json +29 -0
- package/fixtures/atelier-interaction/act/invalid/document.json +25 -0
- package/fixtures/atelier-interaction/act/valid/document.json +24 -0
- package/fixtures/atelier-interaction/assessment/invalid/document.json +32 -0
- package/fixtures/atelier-interaction/assessment/valid/document.json +31 -0
- package/fixtures/atelier-interaction/observation/invalid/document.json +22 -0
- package/fixtures/atelier-interaction/observation/valid/document.json +21 -0
- package/fixtures/atelier-interaction/policy/invalid/document.json +20 -0
- package/fixtures/atelier-interaction/policy/valid/document.json +19 -0
- package/fixtures/atelier-judgment/case/invalid/document.json +76 -0
- package/fixtures/atelier-judgment/case/valid/document.json +75 -0
- package/fixtures/atelier-judgment/decision/invalid/document.json +38 -0
- package/fixtures/atelier-judgment/decision/valid/document.json +37 -0
- package/fixtures/atelier-judgment/native-decision.md +3 -0
- package/fixtures/atelier-judgment/practice-reconsideration/definition.json +92 -0
- package/fixtures/atelier-judgment/practice-reconsideration/scenario.json +435 -0
- package/fixtures/atelier-knowledge-contract/activation/invalid/document.json +26 -0
- package/fixtures/atelier-knowledge-contract/activation/valid/document.json +25 -0
- package/fixtures/atelier-knowledge-contract/contribution/invalid/document.json +34 -0
- package/fixtures/atelier-knowledge-contract/contribution/valid/document.json +33 -0
- package/fixtures/atelier-knowledge-contract/domain/invalid/document.json +41 -0
- package/fixtures/atelier-knowledge-contract/domain/valid/document.json +40 -0
- package/fixtures/atelier-knowledge-contract/domain-revision/invalid/document.json +46 -0
- package/fixtures/atelier-knowledge-contract/domain-revision/valid/document.json +45 -0
- package/fixtures/atelier-knowledge-contract/evaluation/invalid/document.json +21 -0
- package/fixtures/atelier-knowledge-contract/evaluation/valid/document.json +20 -0
- package/fixtures/atelier-knowledge-contract/ledger/invalid/document.json +318 -0
- package/fixtures/atelier-knowledge-contract/ledger/valid/document.json +317 -0
- package/fixtures/atelier-knowledge-contract/relation/invalid/document.json +25 -0
- package/fixtures/atelier-knowledge-contract/relation/valid/document.json +24 -0
- package/fixtures/atelier-knowledge-contract/review/invalid/document.json +18 -0
- package/fixtures/atelier-knowledge-contract/review/valid/document.json +17 -0
- package/fixtures/atelier-knowledge-contract/withdrawal/invalid/document.json +16 -0
- package/fixtures/atelier-knowledge-contract/withdrawal/valid/document.json +15 -0
- package/fixtures/atelier-learning/invalid/authority.json +14 -0
- package/fixtures/atelier-learning/valid/capture.json +13 -0
- package/fixtures/atelier-personal-workspace-contract/manifest/invalid/authority-key.json +17 -0
- package/fixtures/atelier-personal-workspace-contract/manifest/invalid/nonempty-ext.json +25 -0
- package/fixtures/atelier-personal-workspace-contract/manifest/invalid/wrong-schema.json +22 -0
- package/fixtures/atelier-personal-workspace-contract/manifest/valid/manifest.json +22 -0
- package/fixtures/atelier-personal-workspace-contract/overlay/invalid/authority-key.json +25 -0
- package/fixtures/atelier-personal-workspace-contract/overlay/invalid/unknown-top-level-field.json +27 -0
- package/fixtures/atelier-personal-workspace-contract/overlay/invalid/wrong-schema.json +24 -0
- package/fixtures/atelier-trackable/definition/invalid/document.json +15 -0
- package/fixtures/atelier-trackable/definition/valid/document.json +14 -0
- package/fixtures/atelier-trackable/request/invalid/document.json +23 -0
- package/fixtures/atelier-trackable/request/valid/document.json +22 -0
- package/fixtures/boundary-delegations/invalid/authority-key.v1.json +30 -0
- package/fixtures/boundary-delegations/invalid/push-operation.v1.json +28 -0
- package/fixtures/boundary-delegations/invalid/unsigned.v1.json +25 -0
- package/fixtures/boundary-delegations/valid/empty.v1.json +4 -0
- package/fixtures/boundary-delegations/valid/signed-operator.v1.json +30 -0
- package/fixtures/boundary-owner-keys/invalid/wrong-algorithm.v1.json +15 -0
- package/fixtures/boundary-owner-keys/invalid/wrong-curve.v1.json +15 -0
- package/fixtures/boundary-owner-keys/valid/one-owner.v1.json +17 -0
- package/fixtures/capability-packages/adoption.example.json +70 -0
- package/fixtures/capability-packages/evidence-review/capability-package.json +58 -0
- package/fixtures/capability-packages/evidence-review/capability-release.json +84 -0
- package/fixtures/capability-packages/evidence-review/evaluation.md +15 -0
- package/fixtures/capability-packages/evidence-review/skills/evidence-review/SKILL.md +22 -0
- package/fixtures/capability-packages/evidence-review/skills/evidence-review/references/ledger.md +7 -0
- package/fixtures/capability-packages/research/capability-package.json +88 -0
- package/fixtures/capability-packages/research/capability-release.json +134 -0
- package/fixtures/capability-packages/research/evaluation.md +15 -0
- package/fixtures/capability-packages/research/skills/research-integrate/SKILL.md +29 -0
- package/fixtures/capability-packages/research/skills/research-integrate/references/integration.md +10 -0
- package/fixtures/capability-packages/research/skills/research-plan/SKILL.md +27 -0
- package/fixtures/capability-packages/research/skills/research-plan/references/inquiries.md +26 -0
- package/fixtures/capability-packages/research/skills/research-synthesize/SKILL.md +23 -0
- package/fixtures/capability-packages/research/skills/research-synthesize/references/synthesis.md +12 -0
- package/fixtures/evidence-navigation/invalid/duplicate-operation.json +16 -0
- package/fixtures/evidence-navigation/invalid/instruction-kind.json +36 -0
- package/fixtures/evidence-navigation/invalid/invalid-digest.json +36 -0
- package/fixtures/evidence-navigation/invalid/invalid-time.json +36 -0
- package/fixtures/evidence-navigation/invalid/missing-attribution.json +35 -0
- package/fixtures/evidence-navigation/invalid/reserved-extension.json +19 -0
- package/fixtures/evidence-navigation/invalid/unattributed-acceptance.json +36 -0
- package/fixtures/evidence-navigation/invalid/uncoerced-version.json +16 -0
- package/fixtures/evidence-navigation/invalid/unknown-currency.json +17 -0
- package/fixtures/evidence-navigation/invalid/unknown-selector-field.json +37 -0
- package/fixtures/evidence-navigation/invalid/unsupported-operation.json +15 -0
- package/fixtures/evidence-navigation/valid/claim.json +36 -0
- package/fixtures/evidence-navigation/valid/host.json +16 -0
- package/fixtures/evidence-navigation/valid/profile.json +16 -0
- package/fixtures/evidence-navigation/valid/snapshot.json +17 -0
- package/fixtures/harness-packages/build-harness/capability-package.json +61 -0
- package/fixtures/harness-packages/build-harness/capability-release.json +82 -0
- package/fixtures/harness-packages/build-harness/evaluation.md +3 -0
- package/fixtures/harness-packages/build-harness/skills/atelier-build-harness/SKILL.md +59 -0
- package/fixtures/harness-packages/knowledge-harness/capability-package.json +61 -0
- package/fixtures/harness-packages/knowledge-harness/capability-release.json +82 -0
- package/fixtures/harness-packages/knowledge-harness/evaluation.md +3 -0
- package/fixtures/harness-packages/knowledge-harness/skills/atelier-knowledge-harness/SKILL.md +60 -0
- package/fixtures/harnesses/learning-cycle.json +780 -0
- package/fixtures/inquiry/workshop.json +675 -0
- package/fixtures/inquiry-packages/discovery-harness/capability-package.json +61 -0
- package/fixtures/inquiry-packages/discovery-harness/capability-release.json +82 -0
- package/fixtures/inquiry-packages/discovery-harness/evaluation.md +3 -0
- package/fixtures/inquiry-packages/discovery-harness/skills/atelier-discovery-harness/SKILL.md +48 -0
- package/fixtures/inquiry-packages/research-harness/capability-package.json +61 -0
- package/fixtures/inquiry-packages/research-harness/capability-release.json +82 -0
- package/fixtures/inquiry-packages/research-harness/evaluation.md +3 -0
- package/fixtures/inquiry-packages/research-harness/skills/atelier-research-harness/SKILL.md +43 -0
- package/fixtures/knowledge-health/calibration-suite.json +26 -0
- package/fixtures/knowledge-health/calibration-trial.json +37 -0
- package/fixtures/knowledge-health/corrected/checklist.md +18 -0
- package/fixtures/knowledge-health/devday/participant-record.json +19 -0
- package/fixtures/knowledge-health/expected-summary.json +27 -0
- package/fixtures/knowledge-health/workshop/README.md +20 -0
- package/fixtures/knowledge-health/workshop/atelier.project.json +7 -0
- package/fixtures/knowledge-health/workshop/knowledge-plan.json +65 -0
- package/fixtures/knowledge-health/workshop/records/checklist.md +18 -0
- package/fixtures/knowledge-health/workshop/records/workshop.md +18 -0
- package/fixtures/knowledge-health/workshop/repo-access.v1.json +5 -0
- package/fixtures/knowledge-health/workspace/atelier.project.json +18 -0
- package/fixtures/knowledge-health/workspace/knowledge-plan.json +74 -0
- package/fixtures/knowledge-health/workspace/records/checklist.md +18 -0
- package/fixtures/knowledge-health/workspace/records/pilot.md +16 -0
- package/fixtures/knowledge-health/workspace/repo-access.v1.json +9 -0
- package/fixtures/personal-workspace/overlay.json +24 -0
- package/fixtures/templates/local-library.v1.json +76 -0
- package/fixtures/templates/upgrade-contracts/adoption-policy.v2/invalid.json +16 -0
- package/fixtures/templates/upgrade-contracts/adoption-policy.v2/valid.json +13 -0
- package/fixtures/templates/upgrade-contracts/migration.v3/invalid.json +24 -0
- package/fixtures/templates/upgrade-contracts/migration.v3/valid.json +21 -0
- package/fixtures/templates/upgrade-contracts/template-adoption.v1/invalid.json +60 -0
- package/fixtures/templates/upgrade-contracts/template-adoption.v1/valid.json +57 -0
- package/fixtures/templates/upgrade-contracts/upgrade-plan.v3/invalid.json +85 -0
- package/fixtures/templates/upgrade-contracts/upgrade-plan.v3/valid.json +82 -0
- package/package.json +60 -5
- package/plugins/obsidian/main.js +57 -8
- package/plugins/obsidian/manifest.json +1 -1
- package/skills/claude/atelier-build-harness/SKILL.md +59 -0
- package/skills/claude/atelier-coordination/SKILL.md +32 -0
- package/skills/claude/atelier-discovery-harness/SKILL.md +48 -0
- package/skills/claude/atelier-guided-upgrade/SKILL.md +29 -0
- package/skills/claude/atelier-interaction/SKILL.md +34 -0
- package/skills/claude/atelier-knowledge-harness/SKILL.md +60 -0
- package/skills/claude/atelier-knowledge-setup/SKILL.md +72 -0
- package/skills/claude/atelier-practical-judgment/SKILL.md +64 -0
- package/skills/claude/atelier-research-harness/SKILL.md +43 -0
- package/skills/claude/atelier-skill-steward/SKILL.md +53 -0
- package/skills/claude/atelier-trackables/SKILL.md +40 -0
- package/skills/codex/atelier-build-harness/SKILL.md +59 -0
- package/skills/codex/atelier-coordination/SKILL.md +32 -0
- package/skills/codex/atelier-discovery-harness/SKILL.md +48 -0
- package/skills/codex/atelier-guided-upgrade/SKILL.md +29 -0
- package/skills/codex/atelier-interaction/SKILL.md +34 -0
- package/skills/codex/atelier-knowledge-harness/SKILL.md +60 -0
- package/skills/codex/atelier-knowledge-setup/SKILL.md +72 -0
- package/skills/codex/atelier-practical-judgment/SKILL.md +64 -0
- package/skills/codex/atelier-research-harness/SKILL.md +43 -0
- package/skills/codex/atelier-skill-steward/SKILL.md +53 -0
- package/skills/codex/atelier-trackables/SKILL.md +40 -0
- package/src/architecture/catalog.mjs +355 -0
- package/src/architecture/index.mjs +93 -0
- package/src/boundary/content-rules.mjs +536 -69
- package/src/boundary/delegation-command.mjs +158 -0
- package/src/boundary/delegation.mjs +396 -0
- package/src/boundary/policy.mjs +185 -64
- package/src/build/git.mjs +18 -0
- package/src/build/index.mjs +3 -0
- package/src/build/ledger.mjs +88 -0
- package/src/capabilities/adoption.mjs +381 -0
- package/src/capabilities/evidence.mjs +140 -0
- package/src/capabilities/files.mjs +124 -0
- package/src/capabilities/index.mjs +4 -0
- package/src/capabilities/instructions.mjs +250 -0
- package/src/capabilities/package.mjs +136 -0
- package/src/cli/command-failure.mjs +36 -0
- package/src/cli/execute-command.mjs +16 -2
- package/src/cli/run.mjs +66 -9
- package/src/coauthor/store.mjs +43 -2
- package/src/collaboration/proposals.mjs +189 -0
- package/src/commands/architecture.mjs +27 -0
- package/src/commands/boundary.mjs +3 -0
- package/src/commands/capability.mjs +73 -0
- package/src/commands/enroll.mjs +3 -0
- package/src/commands/harness.mjs +79 -0
- package/src/commands/ingest.mjs +39 -0
- package/src/commands/init.mjs +4 -1
- package/src/commands/inquiry.mjs +66 -0
- package/src/commands/knowledge-semantic.mjs +128 -0
- package/src/commands/knowledge.mjs +119 -0
- package/src/commands/learn.mjs +61 -0
- package/src/commands/obsidian.mjs +772 -65
- package/src/commands/practice.mjs +24 -0
- package/src/commands/setup.mjs +22 -2
- package/src/commands/trackable.mjs +19 -0
- package/src/contracts/corpus.mjs +30 -0
- package/src/coordination/index.mjs +110 -0
- package/src/evidence-navigation/contracts.mjs +60 -0
- package/src/evidence-navigation/evaluate.mjs +69 -0
- package/src/evidence-navigation/generation.mjs +22 -0
- package/src/evidence-navigation/index.mjs +2 -0
- package/src/evidence-navigation/local.mjs +167 -0
- package/src/evidence-navigation/schema.generated.mjs +604 -0
- package/src/evidence-navigation/types.mjs +12 -0
- package/src/export/atelier-export-contract.mjs +35 -8
- package/src/graph/enroll.mjs +248 -0
- package/src/graph/graph.mjs +30 -6
- package/src/graph/knowledge-graph.mjs +77 -9
- package/src/harnesses/contracts.mjs +41 -0
- package/src/harnesses/exchange.mjs +154 -0
- package/src/harnesses/feedback.mjs +16 -0
- package/src/harnesses/history.mjs +37 -0
- package/src/harnesses/index.mjs +4 -0
- package/src/harnesses/store.mjs +29 -0
- package/src/ingestion/contracts.mjs +41 -0
- package/src/ingestion/evaluation.mjs +119 -0
- package/src/ingestion/optional/composition/index.mjs +171 -0
- package/src/ingestion/optional/evaluation/index.mjs +219 -0
- package/src/ingestion/optional/graphify/composition-binding.mjs +60 -0
- package/src/ingestion/optional/graphify/composition_receiving.py +185 -0
- package/src/ingestion/optional/graphify/graphify_supplier.py +938 -0
- package/src/ingestion/optional/graphify/json_bridge.py +163 -0
- package/src/ingestion/optional/graphify/native-closure-pins.json +275 -0
- package/src/ingestion/optional/graphify/native-pins.json +306 -0
- package/src/ingestion/optional/graphify/query_receiving.py +209 -0
- package/src/ingestion/optional/index.mjs +9 -0
- package/src/ingestion/optional/jev/bridge.mjs +166 -0
- package/src/ingestion/optional/jev/composition-binding.mjs +116 -0
- package/src/ingestion/optional/jev/judgments.mjs +174 -0
- package/src/ingestion/optional/jev/native-translation.mjs +32 -0
- package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/__init__.py +7 -0
- package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/adapter.py +966 -0
- package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/artifacts.py +101 -0
- package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/projection.py +146 -0
- package/src/ingestion/optional/lightrag/composition-binding.mjs +67 -0
- package/src/ingestion/optional/vanilla/composition-binding.mjs +102 -0
- package/src/ingestion/optional/vanilla/index.mjs +218 -0
- package/src/ingestion/optional/vanilla/native-normalization.mjs +187 -0
- package/src/ingestion/processors.mjs +218 -0
- package/src/ingestion/semantic.mjs +203 -0
- package/src/ingestion/store.mjs +372 -0
- package/src/inquiry/beliefs.mjs +32 -0
- package/src/inquiry/contracts.mjs +23 -0
- package/src/inquiry/index.mjs +5 -0
- package/src/inquiry/ledger.mjs +135 -0
- package/src/inquiry/lenses.mjs +15 -0
- package/src/inquiry/projection.mjs +77 -0
- package/src/inquiry/store.mjs +35 -0
- package/src/intake/read-scope.mjs +12 -0
- package/src/intake/store.mjs +69 -6
- package/src/interaction/index.mjs +91 -0
- package/src/judgment/index.mjs +82 -0
- package/src/judgment/practice-evaluation.mjs +70 -0
- package/src/judgment/practice.mjs +144 -0
- package/src/knowledge/context.mjs +217 -0
- package/src/knowledge/index.mjs +5 -0
- package/src/knowledge/ingestion.mjs +79 -0
- package/src/knowledge/intake.mjs +33 -0
- package/src/knowledge/ledger.mjs +110 -0
- package/src/knowledge/participatory/guidance.mjs +36 -0
- package/src/knowledge/participatory/guided-contribution.mjs +44 -0
- package/src/knowledge/participatory/index.mjs +2 -0
- package/src/knowledge/participatory/installed-workshop.mjs +108 -0
- package/src/knowledge/plan.mjs +67 -0
- package/src/knowledge/projection.mjs +43 -0
- package/src/knowledge/semantic-operation.mjs +567 -0
- package/src/knowledge/sessions.mjs +467 -0
- package/src/knowledge/workspace.mjs +295 -0
- package/src/knowledge-health/assessment.mjs +165 -0
- package/src/knowledge-health/capabilities.mjs +350 -0
- package/src/knowledge-health/context.mjs +62 -0
- package/src/knowledge-health/example.mjs +47 -0
- package/src/knowledge-health/index.mjs +10 -0
- package/src/knowledge-health/owner-join.mjs +88 -0
- package/src/knowledge-health/proof/common.mjs +24 -0
- package/src/knowledge-health/proof/installed-check.mjs +100 -0
- package/src/knowledge-health/proof/native-readback.mjs +37 -0
- package/src/knowledge-health/proof/rehearse.mjs +68 -0
- package/src/knowledge-health/proof/validate-participant.mjs +17 -0
- package/src/knowledge-health/proof/verify-native-capture.mjs +14 -0
- package/src/knowledge-health/proof/workshop-check.mjs +36 -0
- package/src/knowledge-health/rule-identity.mjs +49 -0
- package/src/knowledge-health/workshop-composition.mjs +19 -0
- package/src/knowledge-health/workshop-profile.mjs +176 -0
- package/src/knowledge-health/workshop.mjs +99 -0
- package/src/learning/adapters.mjs +68 -0
- package/src/learning/assistance.mjs +127 -0
- package/src/learning/contracts.mjs +65 -0
- package/src/learning/store.mjs +272 -0
- package/src/personal-workspace/index.mjs +391 -0
- package/src/personal-workspace/selection.mjs +401 -0
- package/src/project/config.mjs +8 -0
- package/src/project/durable-state.mjs +3 -1
- package/src/project/file-class.mjs +1 -1
- package/src/project/path-match.mjs +2 -1
- package/src/projection/obsidian/edits/apply.mjs +55 -16
- package/src/projection/obsidian/materialize/prepare-view.mjs +87 -5
- package/src/projection/obsidian/personal-workspace.mjs +519 -0
- package/src/projection/obsidian/proposals/adapter.mjs +79 -17
- package/src/projection/obsidian/proposals/observation.mjs +50 -4
- package/src/projection/obsidian/publication/publisher.mjs +214 -18
- package/src/projection/obsidian/publication/vault-list.mjs +4 -3
- package/src/projection/obsidian/recovery/index.mjs +4 -1
- package/src/projection/obsidian/recovery/store.mjs +160 -7
- package/src/projection/obsidian/selection-ui/contribution.mjs +6 -5
- package/src/projection/project.mjs +3 -2
- package/src/readiness/readiness.mjs +10 -3
- package/src/reflection/index.mjs +56 -0
- package/src/runtime/obsidian/app-production-seams.mjs +48 -16
- package/src/runtime/obsidian/app-registration.mjs +175 -28
- package/src/runtime/obsidian/app-restart.mjs +154 -0
- package/src/runtime/obsidian/engine.mjs +437 -56
- package/src/runtime/obsidian/extension-points.mjs +1 -1
- package/src/runtime/obsidian/index.mjs +10 -7
- package/src/runtime/obsidian/launch-plan.mjs +7 -4
- package/src/runtime/obsidian/lifecycle.mjs +108 -23
- package/src/runtime/obsidian/login-item.mjs +335 -0
- package/src/runtime/obsidian/machine-settings.mjs +119 -12
- package/src/runtime/obsidian/observation.mjs +36 -4
- package/src/runtime/obsidian/opening.mjs +173 -38
- package/src/runtime/obsidian/personal-selection.mjs +125 -0
- package/src/runtime/obsidian/pipeline.mjs +115 -6
- package/src/runtime/obsidian/plugin-channel.mjs +11 -3
- package/src/runtime/obsidian/plugin-choice.mjs +16 -4
- package/src/runtime/obsidian/plugin-drift.mjs +59 -11
- package/src/runtime/obsidian/plugin-presence.mjs +14 -1
- package/src/runtime/obsidian/private-lock.mjs +39 -4
- package/src/runtime/obsidian/process-identity.mjs +20 -10
- package/src/runtime/obsidian/project-views.mjs +282 -0
- package/src/runtime/obsidian/questions.mjs +48 -0
- package/src/runtime/obsidian/release-watch.mjs +56 -0
- package/src/runtime/obsidian/service-main.mjs +148 -19
- package/src/runtime/obsidian/service-manager-production.mjs +61 -0
- package/src/runtime/obsidian/service-managers.mjs +275 -0
- package/src/runtime/obsidian/service-record.mjs +87 -23
- package/src/runtime/obsidian/service.mjs +102 -9
- package/src/runtime/obsidian/startup-adapters.mjs +59 -21
- package/src/runtime/obsidian/unheld-evidence.mjs +84 -0
- package/src/runtime/obsidian/vault-location.mjs +300 -0
- package/src/runtime/obsidian/view-counts.mjs +34 -0
- package/src/runtime/obsidian/view-permission.mjs +334 -0
- package/src/server/local-sidecar.mjs +169 -2
- package/src/server/server.mjs +28 -2
- package/src/template-bindings/project.mjs +92 -0
- package/src/templates/profile.mjs +225 -0
- package/src/trackables/domain.mjs +175 -0
- package/src/trackables/store.mjs +114 -0
- package/src/ui/control-system.mjs +201 -0
- package/src/ui/html-primitives.mjs +3 -0
- package/src/ui/knowledge-page.mjs +1388 -0
- package/src/upgrade/closure-diagnostics.mjs +135 -0
- package/src/upgrade/closure.mjs +649 -0
- package/src/upgrade/explanation.mjs +1 -1
- package/src/upgrade/template-participant.mjs +171 -0
- package/src/upgrade/transaction.mjs +72 -25
- package/src/upgrade/upgrade.mjs +20 -7
- package/templates/external-project-workspace/pack-lifecycle.json +7 -1
- package/templates/knowledge-workspace/.gitattributes +2 -0
- package/templates/knowledge-workspace/AGENTS.md +27 -0
- package/templates/knowledge-workspace/README.md +44 -0
- package/templates/knowledge-workspace/atelier.project.json +22 -0
- package/templates/knowledge-workspace/gitignore +6 -0
- package/templates/knowledge-workspace/knowledge-plan.json +111 -0
- package/templates/knowledge-workspace/knowledge-plan.schema.json +281 -0
- package/templates/knowledge-workspace/records/forecast.md +22 -0
- package/templates/knowledge-workspace/records/inspection.md +25 -0
- package/templates/knowledge-workspace/records/telescope.md +24 -0
- package/templates/knowledge-workspace/repo-access.v1.json +9 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto'
|
|
2
|
+
import fs from 'node:fs'
|
|
3
|
+
import { readObsidianEnablement } from './enablement.mjs'
|
|
4
|
+
import { DEFAULT_FULL_RECONCILIATION_INTERVAL_MS, configFilesOf } from './engine.mjs'
|
|
5
|
+
import { readFileFacts } from './observation.mjs'
|
|
6
|
+
|
|
7
|
+
// Whether the project allows a view of this workspace right now.
|
|
8
|
+
//
|
|
9
|
+
// What the service stored about a view (its freshness, its pending edits) says
|
|
10
|
+
// what was true when it was written. A view can be turned off, or taken out of
|
|
11
|
+
// the project, or the project can point at another workspace, well before the
|
|
12
|
+
// next tick writes that down; and what a tick then writes still carries the
|
|
13
|
+
// view's generations and counts. So before a stored report is told to whoever
|
|
14
|
+
// holds the view open, `permits(scopeId)` asks the project as it is at that
|
|
15
|
+
// moment:
|
|
16
|
+
//
|
|
17
|
+
// the view the Obsidian settings are enabled and declare it
|
|
18
|
+
// the workspace the project's pointer names this workspace, and its
|
|
19
|
+
// private state is where this service keeps it
|
|
20
|
+
//
|
|
21
|
+
// Whatever cannot be established is "no": a project that does not load,
|
|
22
|
+
// settings that refuse, a pointer that cannot be read, a load that has not
|
|
23
|
+
// answered yet. Nothing is written. This is not about who may see a note; the
|
|
24
|
+
// audiences of machine-settings.mjs decide that when a view is prepared.
|
|
25
|
+
//
|
|
26
|
+
// Cost. Loading a project runs Git in child processes, and a plugin asks every
|
|
27
|
+
// two seconds for every vault it holds open. A project is therefore kept, and
|
|
28
|
+
// loaded again only when a file that decides what it is changed: the files the
|
|
29
|
+
// engine observes as configuration (configFilesOf), compared at every request
|
|
30
|
+
// by a digest of what they hold, never by their times. The pointer and the
|
|
31
|
+
// place of the workspace's private state are read at every request.
|
|
32
|
+
//
|
|
33
|
+
// what a file holds where it really is and the bytes there: its path
|
|
34
|
+
// resolved as the system resolves it when the loader
|
|
35
|
+
// opens it (realpath(3), through a link anywhere on the
|
|
36
|
+
// way), so a link pointed elsewhere, its own or a
|
|
37
|
+
// folder's above it, is a change. Nothing there is a
|
|
38
|
+
// state like any other. A folder in a file's place, a
|
|
39
|
+
// link that leads nowhere or to anything but a regular
|
|
40
|
+
// file, and a file that does not read vouch for nothing.
|
|
41
|
+
// Where the system resolves no path for a regular file
|
|
42
|
+
// that is there and is not a link (some volumes on
|
|
43
|
+
// Windows; a C library that needs /proc and has none),
|
|
44
|
+
// the file holds its own path as given and its bytes: a
|
|
45
|
+
// link above it pointed elsewhere is not seen there
|
|
46
|
+
// a project is kept when two loads in a row, each started after the
|
|
47
|
+
// files were read and found to hold the same, returned
|
|
48
|
+
// the same project as far as this permission reads one:
|
|
49
|
+
// the views it enables, and what the service's
|
|
50
|
+
// workspace resolution reads of it, handed in as
|
|
51
|
+
// `workspaceInputsOf` (where its pointer
|
|
52
|
+
// is, the data root its local overlay prefers, the
|
|
53
|
+
// roots it protects). One load is not enough: a file
|
|
54
|
+
// changed and changed back while it ran leaves the same
|
|
55
|
+
// bytes around a project read from other ones, and
|
|
56
|
+
// reading the files once more when it answers shows
|
|
57
|
+
// nothing. The second load reads the project again.
|
|
58
|
+
// The first load of all only tells which files decide
|
|
59
|
+
// the project, so three loads keep the first project
|
|
60
|
+
// and two keep one after a change
|
|
61
|
+
// and used only while the files hold what they held before
|
|
62
|
+
// those loads, read again at every request
|
|
63
|
+
// and not for long a kept project is loaded again once it is half as old
|
|
64
|
+
// as the engine's full reconciliations are apart, and
|
|
65
|
+
// is never used past that interval: whatever no file
|
|
66
|
+
// here names is seen by then
|
|
67
|
+
//
|
|
68
|
+
// While a file vouches for nothing no project is kept: a loader that answers
|
|
69
|
+
// at once is asked at every request, and its answer decides that request, as
|
|
70
|
+
// it does for the two requests that come before a project is kept.
|
|
71
|
+
//
|
|
72
|
+
// A loader may answer a promise (one that composes a personal workspace off
|
|
73
|
+
// the event loop). The answer to a request never waits for it: a plugin gives
|
|
74
|
+
// a request a second and a half, and takes an answer that does not come for a
|
|
75
|
+
// service that is gone. The load is started, one at a time, and a request is
|
|
76
|
+
// only ever decided from a kept project: until two loads agreed the view is
|
|
77
|
+
// not permitted, and while a kept project is loaded again it goes on deciding,
|
|
78
|
+
// within its time. A load that has not answered by its deadline is given up,
|
|
79
|
+
// whether a request or its own late answer is the first to see the deadline
|
|
80
|
+
// passed, and what it answers then is ignored.
|
|
81
|
+
//
|
|
82
|
+
// A load that fails (it throws, it is refused, it is given up, or it answers a
|
|
83
|
+
// promise while a file vouches for nothing) ends whatever was kept, and is not
|
|
84
|
+
// asked for again at once: after two seconds, then four, up to thirty, for as
|
|
85
|
+
// long as the files hold the same. A change to them is tried at once, once a
|
|
86
|
+
// load has named them: until a load has answered, a corrected configuration
|
|
87
|
+
// waits for the next try. And while one of them vouches for nothing there is
|
|
88
|
+
// no digest to change: after a load that failed then (one that answers a
|
|
89
|
+
// promise, or one that answers at once and threw), a change to another file
|
|
90
|
+
// waits for the next try as well.
|
|
91
|
+
//
|
|
92
|
+
// `onLoad({ durationMs, outcome, promised, kept, sameFiles, vouched, at })` is
|
|
93
|
+
// told how long each load took and how it ended (`answered`, `failed` or
|
|
94
|
+
// `given-up`), whether a project is kept after it, whether it named the files
|
|
95
|
+
// that decide the project as they were when it started (not the first load,
|
|
96
|
+
// which started over none, nor one that named other files), whether those
|
|
97
|
+
// files vouched then, and when it ended (`now()`): times, codes and flags
|
|
98
|
+
// only. createLoadReport, below, counts them and decides which are worth a
|
|
99
|
+
// log line.
|
|
100
|
+
|
|
101
|
+
// A kept project is never used for longer than the engine goes without a full reconciliation.
|
|
102
|
+
export const DEFAULT_KEEP_FOR_MS = DEFAULT_FULL_RECONCILIATION_INTERVAL_MS
|
|
103
|
+
// Longer than the two minutes a composition in a worker is given, so one that runs out of its own time answers first.
|
|
104
|
+
export const DEFAULT_LOAD_DEADLINE_MS = 150 * 1000
|
|
105
|
+
export const DEFAULT_RETRY_AFTER_MS = 2 * 1000
|
|
106
|
+
export const DEFAULT_RETRY_AT_MOST_MS = 30 * 1000
|
|
107
|
+
|
|
108
|
+
// What one file holds, as [path, its real path, digest of the bytes there], or [path, null, null] when it is absent;
|
|
109
|
+
// undefined when it vouches for nothing. The real path is the system's own (realpath(3)): JavaScript's realpathSync
|
|
110
|
+
// takes a `..` after a link away before following the link, and names a file the loader never opens.
|
|
111
|
+
function heldBy(file, realpath) {
|
|
112
|
+
let real
|
|
113
|
+
try { real = realpath(file) } catch {
|
|
114
|
+
// Unresolved. Nothing there is absent. A regular file that is there, and is not a link, is read where it is given
|
|
115
|
+
// (readFileFacts follows no link at its end). A link, resolved or not, never is: it leads nowhere, or nobody can say where.
|
|
116
|
+
let stat
|
|
117
|
+
try { stat = fs.lstatSync(file, { throwIfNoEntry: false }) } catch (inner) { return inner.code === 'ENOTDIR' ? [file, null, null] : undefined }
|
|
118
|
+
if (stat === undefined) return [file, null, null]
|
|
119
|
+
if (!stat.isFile()) return undefined
|
|
120
|
+
real = file
|
|
121
|
+
}
|
|
122
|
+
let facts
|
|
123
|
+
try { facts = readFileFacts(real) } catch { return undefined }
|
|
124
|
+
return facts === null ? undefined : [file, real, facts.digest]
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// One digest over what every file holds, in order. Null when one of them vouches for nothing.
|
|
128
|
+
function digestOver(files, realpath) {
|
|
129
|
+
const hash = createHash('sha256')
|
|
130
|
+
for (const file of files) {
|
|
131
|
+
const held = heldBy(file, realpath)
|
|
132
|
+
if (held === undefined) return null
|
|
133
|
+
hash.update(`${JSON.stringify(held)}\n`)
|
|
134
|
+
}
|
|
135
|
+
return hash.digest('hex')
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// The views the project enables. Settings that refuse enable none.
|
|
139
|
+
function viewsOf(project) {
|
|
140
|
+
try {
|
|
141
|
+
const enablement = readObsidianEnablement(project)
|
|
142
|
+
return new Set(enablement.state === 'enabled' ? enablement.scopes.map((scope) => scope.scopeId) : [])
|
|
143
|
+
} catch { return new Set() }
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export const STATUS_LOAD_EVENT = 'status-project-loaded'
|
|
147
|
+
// A second: most of the second and a half a plugin gives a request, spent on the listener's own event loop.
|
|
148
|
+
export const DEFAULT_SLOW_LOAD_MS = 1000
|
|
149
|
+
// How long a row of failed or slow loads goes on through loads that answered in time between them.
|
|
150
|
+
export const DEFAULT_ROW_ENDS_AFTER_MS = 60 * 1000
|
|
151
|
+
|
|
152
|
+
// Counts every load a permission is told about (onLoad), and writes a line for the ones somebody has to see:
|
|
153
|
+
//
|
|
154
|
+
// failed, given up the first in a row, then when the row is 2, 4, 8, ... long, so a loader that keeps failing
|
|
155
|
+
// writes a line less and less often, never one per try
|
|
156
|
+
// answered the one that ends a row of failures; one that answered at once and held the event loop for
|
|
157
|
+
// `slowMs` or more, on the same doubling row while slow loads follow each other; and one after
|
|
158
|
+
// which nothing is kept although it named the files it started over, from the second in a row
|
|
159
|
+
// on the same doubling row: a load at every request
|
|
160
|
+
//
|
|
161
|
+
// A row of failures ends only at a load that answers `rowEndsAfterMs` or more after its last failure, and a row of
|
|
162
|
+
// slow loads at a load that answers in time that long after its last slow one. While loads come closer together than
|
|
163
|
+
// that (a load at every request, or the retries after a failure), a loader that fails, or is slow, every other load or
|
|
164
|
+
// every third keeps one row, written less and less often, not a line per load. A kept project is loaded again only
|
|
165
|
+
// every half of its keep time, so there a row of failures ends at the next reload that answers: a loader that answers
|
|
166
|
+
// at least one reload between two failures writes each failure and the load that ends its row, as before, and one that
|
|
167
|
+
// fails at every reload and answers only at its retry keeps one row. A row of loads after which nothing is kept ends
|
|
168
|
+
// at the next that keeps a project. A load that named other files than it started over (the first, or one after a
|
|
169
|
+
// change that names other files) neither counts nor ends that row, so a start and a change, each with one load that
|
|
170
|
+
// has nothing to agree with yet, write none of these, unless the change lands while a load reads the files. Any other
|
|
171
|
+
// load that answered in time and kept the project writes nothing unless it ends a row of failures: that is every load
|
|
172
|
+
// of a healthy service. A line holds the event, milliseconds, the outcome, flags and counts; never a path, a view or a
|
|
173
|
+
// message.
|
|
174
|
+
export function createLoadReport({ write, slowMs = DEFAULT_SLOW_LOAD_MS, rowEndsAfterMs = DEFAULT_ROW_ENDS_AFTER_MS }) {
|
|
175
|
+
if (typeof write !== 'function') throw new TypeError('a load report needs somewhere to write')
|
|
176
|
+
const counts = { answered: 0, failed: 0, givenUp: 0, slow: 0, notKept: 0, longestMs: 0 }
|
|
177
|
+
const doubled = (row) => (row & (row - 1)) === 0
|
|
178
|
+
let failedInARow = 0
|
|
179
|
+
let lastFailedAt = 0
|
|
180
|
+
let slowInARow = 0
|
|
181
|
+
let lastSlowAt = 0
|
|
182
|
+
let notKeptInARow = 0
|
|
183
|
+
return {
|
|
184
|
+
// `kept` and `sameFiles` default to a load that kept its project, `vouched` to files that vouched, and `at` to now.
|
|
185
|
+
onLoad({ durationMs, outcome, promised, kept = true, sameFiles = true, vouched = true, at = performance.now() }) {
|
|
186
|
+
const ms = Math.round(durationMs)
|
|
187
|
+
counts.longestMs = Math.max(counts.longestMs, ms)
|
|
188
|
+
if (outcome !== 'answered') {
|
|
189
|
+
counts[outcome === 'given-up' ? 'givenUp' : 'failed'] += 1
|
|
190
|
+
failedInARow += 1
|
|
191
|
+
lastFailedAt = at
|
|
192
|
+
if (doubled(failedInARow)) write({ event: STATUS_LOAD_EVENT, durationMs: ms, outcome, promised, inARow: failedInARow })
|
|
193
|
+
return
|
|
194
|
+
}
|
|
195
|
+
counts.answered += 1
|
|
196
|
+
const afterFailures = failedInARow > 0 && at - lastFailedAt >= rowEndsAfterMs ? failedInARow : 0
|
|
197
|
+
if (afterFailures > 0) failedInARow = 0
|
|
198
|
+
const slow = !promised && ms >= slowMs
|
|
199
|
+
if (slow) {
|
|
200
|
+
counts.slow += 1
|
|
201
|
+
slowInARow += 1
|
|
202
|
+
lastSlowAt = at
|
|
203
|
+
} else if (slowInARow > 0 && at - lastSlowAt >= rowEndsAfterMs) slowInARow = 0
|
|
204
|
+
const notKept = sameFiles && !kept
|
|
205
|
+
if (notKept) {
|
|
206
|
+
counts.notKept += 1
|
|
207
|
+
notKeptInARow += 1
|
|
208
|
+
} else if (sameFiles) notKeptInARow = 0
|
|
209
|
+
const writeSlow = slow && doubled(slowInARow)
|
|
210
|
+
const writeNotKept = notKept && notKeptInARow >= 2 && doubled(notKeptInARow)
|
|
211
|
+
if (afterFailures > 0 || writeSlow || writeNotKept) {
|
|
212
|
+
write({
|
|
213
|
+
event: STATUS_LOAD_EVENT, durationMs: ms, outcome, promised,
|
|
214
|
+
...(afterFailures > 0 ? { afterFailures } : {}),
|
|
215
|
+
...(writeSlow ? { slow: true, inARow: slowInARow } : {}),
|
|
216
|
+
...(writeNotKept ? { notKept: true, notKeptInARow, vouched } : {}),
|
|
217
|
+
})
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
// answered, failed, givenUp, slow and notKept loads so far, and the longest in milliseconds.
|
|
221
|
+
counts: () => ({ ...counts }),
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// `resolveWorkspace(project)` answers { workspaceId, workspaceRoot } as the project names them now, or null, and
|
|
226
|
+
// `workspaceInputsOf(project)` what it reads of a project to do so, as JSON: the service hands in its own of both
|
|
227
|
+
// (resolveServiceWorkspace, serviceWorkspaceInputs), so what two loads are compared by is never a copy kept here.
|
|
228
|
+
// `now()` answers milliseconds that only go up; the times are this module's defaults unless a test gives its own.
|
|
229
|
+
// `realpath(file)` resolves a path as the system does; a test hands in one that fails.
|
|
230
|
+
export function createViewPermission({
|
|
231
|
+
loadProject, resolveWorkspace, workspaceInputsOf, workspaceId, workspaceRoot, now = () => performance.now(),
|
|
232
|
+
keepForMs = DEFAULT_KEEP_FOR_MS, loadDeadlineMs = DEFAULT_LOAD_DEADLINE_MS, retryAfterMs = DEFAULT_RETRY_AFTER_MS, retryAtMostMs = DEFAULT_RETRY_AT_MOST_MS,
|
|
233
|
+
onLoad = () => {}, realpath = fs.realpathSync.native,
|
|
234
|
+
}) {
|
|
235
|
+
if (typeof loadProject !== 'function') throw new TypeError('a view permission needs loadProject')
|
|
236
|
+
if (typeof resolveWorkspace !== 'function') throw new TypeError('a view permission needs resolveWorkspace')
|
|
237
|
+
if (typeof workspaceInputsOf !== 'function') throw new TypeError('a view permission needs workspaceInputsOf')
|
|
238
|
+
const digestOf = (files) => digestOver(files, realpath)
|
|
239
|
+
// What two loads have to agree on: the views, and what the workspace is resolved from. Nothing else of a project is
|
|
240
|
+
// compared, so a field that differs from load to load (a time, a binding under a symbol key) does not keep a project
|
|
241
|
+
// from being kept, and nothing a decision reads is left out.
|
|
242
|
+
const decidedBy = (project, views) => JSON.stringify([[...views].sort(), workspaceInputsOf(project)])
|
|
243
|
+
// The files that decided the project last loaded.
|
|
244
|
+
let files = null
|
|
245
|
+
// What the last load returned that was started over files that vouched: { digest, shape }.
|
|
246
|
+
let seen = null
|
|
247
|
+
// The project two such loads agreed on: { project, views, digest, since }, `since` the start of the second.
|
|
248
|
+
let kept = null
|
|
249
|
+
// A load that answered a promise and has not answered yet: { id, digest, startedAt }.
|
|
250
|
+
let underWay = null
|
|
251
|
+
// Loads that failed in a row while the files held the same: { digest, times, at }.
|
|
252
|
+
let failed = null
|
|
253
|
+
let started = 0
|
|
254
|
+
|
|
255
|
+
function fail(digest, at) {
|
|
256
|
+
kept = null
|
|
257
|
+
failed = { digest, times: failed !== null && failed.digest === digest ? failed.times + 1 : 1, at }
|
|
258
|
+
}
|
|
259
|
+
const tooSoon = (digest, at) => failed !== null && failed.digest === digest && at - failed.at < Math.min(retryAtMostMs, retryAfterMs * 2 ** (failed.times - 1))
|
|
260
|
+
// `before` and `vouched`: the files that decided the project when the load started, and whether they vouched then.
|
|
261
|
+
const sameList = (left, right) => left !== null && right !== null && left.length === right.length && left.every((file, index) => file === right[index])
|
|
262
|
+
const told = (startedAt, outcome, promised, { before, vouched }) => {
|
|
263
|
+
try { const at = now(); onLoad({ durationMs: Math.max(0, at - startedAt), outcome, promised, kept: kept !== null, sameFiles: sameList(before, files), vouched, at }) } catch { /* only told */ }
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// What a load returned; `digest` was read before it started. A project that names other files than the ones read
|
|
267
|
+
// is kept under a digest the next request cannot read again, so it is dropped there, before it decides anything.
|
|
268
|
+
function answered(project, digest, since) {
|
|
269
|
+
files = configFilesOf(project).map((file) => file.absolute)
|
|
270
|
+
const views = viewsOf(project)
|
|
271
|
+
const loaded = { project, views, digest, since }
|
|
272
|
+
const shape = digest === null ? null : decidedBy(project, views)
|
|
273
|
+
kept = digest !== null && seen !== null && seen.digest === digest && seen.shape === shape ? loaded : null
|
|
274
|
+
seen = digest === null ? null : { digest, shape }
|
|
275
|
+
return loaded
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Asks the loader. What it returned, when it answers at once; null when it answers a promise.
|
|
279
|
+
function load(digest, at) {
|
|
280
|
+
const as = { before: files, vouched: digest !== null }
|
|
281
|
+
let answer
|
|
282
|
+
try {
|
|
283
|
+
answer = loadProject()
|
|
284
|
+
if (typeof answer?.then !== 'function') {
|
|
285
|
+
const loaded = answered(answer, digest, at)
|
|
286
|
+
failed = null
|
|
287
|
+
told(at, 'answered', false, as)
|
|
288
|
+
return loaded
|
|
289
|
+
}
|
|
290
|
+
} catch (error) { fail(digest, at); told(at, 'failed', false, as); throw error }
|
|
291
|
+
const id = (started += 1)
|
|
292
|
+
underWay = { id, digest, startedAt: at, as }
|
|
293
|
+
// Only the load under way is listened to: one given up answers nobody. An answer that comes once the deadline has
|
|
294
|
+
// passed gives the load up itself, when no request was there to see the deadline pass first.
|
|
295
|
+
const mine = () => {
|
|
296
|
+
if (underWay?.id !== id) return false
|
|
297
|
+
underWay = null
|
|
298
|
+
if (now() - at < loadDeadlineMs) return true
|
|
299
|
+
fail(digest, now())
|
|
300
|
+
told(at, 'given-up', true, as)
|
|
301
|
+
return false
|
|
302
|
+
}
|
|
303
|
+
Promise.resolve(answer).then((project) => {
|
|
304
|
+
if (!mine()) return
|
|
305
|
+
answered(project, digest, at)
|
|
306
|
+
// While a file vouches for nothing, nothing can be kept from this answer, and no request is here to be decided by it.
|
|
307
|
+
if (digestOf(files) === null) { fail(null, now()); told(at, 'failed', true, as) } else { failed = null; told(at, 'answered', true, as) }
|
|
308
|
+
}, () => { if (mine()) { fail(digest, now()); told(at, 'failed', true, as) } }).catch(() => { fail(digest, now()); told(at, 'failed', true, as) })
|
|
309
|
+
return null
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
// The project as it is now, or null while that is not known.
|
|
313
|
+
function current() {
|
|
314
|
+
const at = now()
|
|
315
|
+
const digest = files === null ? null : digestOf(files)
|
|
316
|
+
if (kept !== null && (digest !== kept.digest || at - kept.since >= keepForMs)) kept = null
|
|
317
|
+
if (underWay !== null && at - underWay.startedAt >= loadDeadlineMs) { const given = underWay; underWay = null; fail(given.digest, at); told(given.startedAt, 'given-up', true, given.as) }
|
|
318
|
+
if (kept !== null && at - kept.since < keepForMs / 2) return kept
|
|
319
|
+
if (underWay === null && !tooSoon(digest, at)) {
|
|
320
|
+
const loaded = load(digest, at)
|
|
321
|
+
if (loaded !== null) return loaded
|
|
322
|
+
}
|
|
323
|
+
return kept
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
return function permits(scopeId) {
|
|
327
|
+
try {
|
|
328
|
+
const known = current()
|
|
329
|
+
if (known === null || !known.views.has(scopeId)) return false
|
|
330
|
+
const workspace = resolveWorkspace(known.project)
|
|
331
|
+
return workspace?.workspaceId === workspaceId && workspace.workspaceRoot === workspaceRoot
|
|
332
|
+
} catch { return false }
|
|
333
|
+
}
|
|
334
|
+
}
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
trustedMutationRequest,
|
|
26
26
|
trustedReadRequest,
|
|
27
27
|
} from './security.mjs'
|
|
28
|
+
import { canonicalize } from '../attestation/jcs.mjs'
|
|
28
29
|
import { createProposalStore } from '../collaboration/proposals.mjs'
|
|
29
30
|
import {
|
|
30
31
|
renderProposalDetailPageHtml,
|
|
@@ -34,6 +35,9 @@ import {
|
|
|
34
35
|
import { createReviewStore } from '../collaboration/review-store.mjs'
|
|
35
36
|
import { loadBoundRun, currentRunEligibility } from '../readiness-protocols/evidence.mjs'
|
|
36
37
|
import { renderReviewPage } from '../ui/review-page.mjs'
|
|
38
|
+
import { renderKnowledgePage } from '../ui/knowledge-page.mjs'
|
|
39
|
+
import { knowledgeDashboard, knowledgeQuestionContext, loadKnowledgeWorkspace } from '../knowledge/workspace.mjs'
|
|
40
|
+
import { createKnowledgeSessions } from '../knowledge/sessions.mjs'
|
|
37
41
|
|
|
38
42
|
export const ATELIER_CONTEXT_SCHEMA = 'atelier-context@v1'
|
|
39
43
|
export const ATELIER_PRESENCE_SCHEMA = 'atelier-presence@v1'
|
|
@@ -216,6 +220,28 @@ function listenFailure(error, port) {
|
|
|
216
220
|
return error
|
|
217
221
|
}
|
|
218
222
|
|
|
223
|
+
function knowledgeError(error) {
|
|
224
|
+
const message = error instanceof Error ? error.message : ''
|
|
225
|
+
if (message === 'coauthor requires a Git workspace with untracked, ignored .atelier-local/ state')
|
|
226
|
+
return 'coauthor requires a Git workspace with untracked, ignored local state. Initialize Git and ignore the .atelier-local directory.'
|
|
227
|
+
if (/^collaboration (ledger|event).*ceiling/.test(message))
|
|
228
|
+
return 'knowledge history capacity reached. Preserve the ledger and saved values for operator inspection; do not compact active session chains.'
|
|
229
|
+
if (message.startsWith('EEXIST: private state is locked'))
|
|
230
|
+
return 'knowledge writer is busy. Preserve the request and retry after the active writer finishes.'
|
|
231
|
+
const safe = [
|
|
232
|
+
'workspace changed', 'stale revision', 'event id conflict', 'source changed',
|
|
233
|
+
'coauthor session not found', 'coauthor requires', 'unknown knowledge',
|
|
234
|
+
'invalid knowledge', 'request UUID', 'retry budget exhausted',
|
|
235
|
+
'receipt and failure events are store-owned', 'event refused in',
|
|
236
|
+
'coauthor plan must', 'knowledge session configuration mismatch',
|
|
237
|
+
'knowledge session identity mismatch', 'knowledge session exceeds',
|
|
238
|
+
'knowledge session list exceeds',
|
|
239
|
+
'knowledge history unavailable', 'knowledge start marker mismatch',
|
|
240
|
+
]
|
|
241
|
+
return safe.some(prefix => message.startsWith(prefix)) && !message.includes('/')
|
|
242
|
+
? message : 'Knowledge operation unavailable. Preserve your draft and inspect the local workspace or active writer.'
|
|
243
|
+
}
|
|
244
|
+
|
|
219
245
|
function doctorEnvelope() {
|
|
220
246
|
return {
|
|
221
247
|
ok: true,
|
|
@@ -231,6 +257,7 @@ export function createAtelierSidecarServer({
|
|
|
231
257
|
workspaceRoot = process.cwd(),
|
|
232
258
|
stateDir = workspaceRoot,
|
|
233
259
|
reviewProject = null,
|
|
260
|
+
knowledgeProject = null,
|
|
234
261
|
port = 0,
|
|
235
262
|
presenceTtlMs = Number(process.env.ATELIER_PRESENCE_TTL_MS || 5 * 60 * 1000),
|
|
236
263
|
} = {}) {
|
|
@@ -243,6 +270,7 @@ export function createAtelierSidecarServer({
|
|
|
243
270
|
})
|
|
244
271
|
const publication = loadPublishedWorkspaceManifest(root)
|
|
245
272
|
const review = reviewProject ? createReviewStore(reviewProject) : null
|
|
273
|
+
const knowledge = knowledgeProject ? createKnowledgeSessions(knowledgeProject) : null
|
|
246
274
|
const workspaceId = workspaceIdForRoot(root)
|
|
247
275
|
const noncePath = path.join(stateDirReal, '.atelier-nonce')
|
|
248
276
|
const presencePath = path.join(stateDirReal, '.atelier-presence.json')
|
|
@@ -253,6 +281,95 @@ export function createAtelierSidecarServer({
|
|
|
253
281
|
workspaceId,
|
|
254
282
|
})
|
|
255
283
|
|
|
284
|
+
// One copy-only declaration is retained from the workshop: this path, this
|
|
285
|
+
// action, from this view. The request stays `{ sessionId }`; everything
|
|
286
|
+
// retained is derived here from fresh owning readbacks, and the generic
|
|
287
|
+
// /api/proposals route keeps its published-HTML fence.
|
|
288
|
+
const WORKSHOP_HANDOFF_TARGET = Object.freeze({ path: 'records/checklist.md', action: 'copy.repoPath', viewId: 'knowledge-workshop' })
|
|
289
|
+
const sameJson = (left, right) => canonicalize(left) === canonicalize(right)
|
|
290
|
+
const refusedRetention = (error, status = 'refused') => ({ status: 409, body: { ok: false, error, retention: { status } } })
|
|
291
|
+
const retentionSummary = (record, outcome, sourceCurrent) => ({
|
|
292
|
+
status: 'retained',
|
|
293
|
+
outcome,
|
|
294
|
+
sourceCurrent,
|
|
295
|
+
proposalId: record.proposal.id,
|
|
296
|
+
readback: `/api/proposals/${record.proposal.id}`,
|
|
297
|
+
receipt: {
|
|
298
|
+
sessionId: record.retention.sessionId,
|
|
299
|
+
requestId: record.retention.requestId,
|
|
300
|
+
valueDigest: record.retention.valueDigest,
|
|
301
|
+
},
|
|
302
|
+
payloadDigest: record.retention.payloadDigest,
|
|
303
|
+
proposalStatus: record.proposal.status,
|
|
304
|
+
eventVersion: record.proposal.eventVersion,
|
|
305
|
+
})
|
|
306
|
+
|
|
307
|
+
function retainWorkshopHandoff(body) {
|
|
308
|
+
if (!body || Array.isArray(body) || Object.keys(body).join(',') !== 'sessionId' || typeof body.sessionId !== 'string') {
|
|
309
|
+
return refusedRetention('workshop handoff accepts a session id only')
|
|
310
|
+
}
|
|
311
|
+
// A fresh owning readback: this throws when the session, its ledger or a
|
|
312
|
+
// saved value is missing, redirected or altered.
|
|
313
|
+
const session = knowledge.read(body.sessionId)
|
|
314
|
+
const selected = session.record.flow === 'workshop-review'
|
|
315
|
+
? session.state.saved.find((value) => value.fieldId === 'authored-revision')
|
|
316
|
+
: null
|
|
317
|
+
if (!selected) {
|
|
318
|
+
// No separately selected draft receipt: prepared only, as before. An
|
|
319
|
+
// incomplete Review or a response that selects nothing is refused by
|
|
320
|
+
// the existing preparation.
|
|
321
|
+
return { status: 200, body: { ...knowledge.workshopHandoff(body), retention: { status: 'not-retained', reason: 'no separately selected draft receipt' } } }
|
|
322
|
+
}
|
|
323
|
+
const own = session.owningReadbacks?.verified === true
|
|
324
|
+
? session.owningReadbacks.immutableValues.find((value) => sameJson(value.receipt, selected.receipt))
|
|
325
|
+
: null
|
|
326
|
+
if (!own || own.text !== selected.text || selected.receipt.sessionId !== session.record.id) {
|
|
327
|
+
return refusedRetention('the selected draft receipt is not confirmed by its owning readback')
|
|
328
|
+
}
|
|
329
|
+
if (!session.current) {
|
|
330
|
+
// Reopen after a source change: read back what was retained, never append.
|
|
331
|
+
const known = proposals.lookupRetainedProposal(selected.receipt)
|
|
332
|
+
if (known.outcome === 'retained') {
|
|
333
|
+
return {
|
|
334
|
+
status: 200,
|
|
335
|
+
body: {
|
|
336
|
+
ok: true,
|
|
337
|
+
handoff: proposals.retainedHandoff(known.record),
|
|
338
|
+
sourceEditsApplied: false,
|
|
339
|
+
directWrite: false,
|
|
340
|
+
retention: retentionSummary(known.record, 'existing', false),
|
|
341
|
+
},
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return known.outcome === 'not-found'
|
|
345
|
+
? refusedRetention('the source changed before this draft was retained; nothing is retained for this receipt', 'not-retained')
|
|
346
|
+
: refusedRetention(known.error, known.outcome === 'conflict' ? 'refused' : 'unknown')
|
|
347
|
+
}
|
|
348
|
+
const prepared = knowledge.workshopHandoff(body)
|
|
349
|
+
const handoff = prepared.handoff
|
|
350
|
+
if (
|
|
351
|
+
!sameJson(handoff.proposal.selectedDraftReceipt, selected.receipt) || handoff.sessionId !== session.record.id ||
|
|
352
|
+
handoff.path !== WORKSHOP_HANDOFF_TARGET.path || handoff.action !== WORKSHOP_HANDOFF_TARGET.action ||
|
|
353
|
+
handoff.viewId !== WORKSHOP_HANDOFF_TARGET.viewId || handoff.directWrite !== false ||
|
|
354
|
+
handoff.applyEndpoint !== null || handoff.diff?.replacement !== selected.text
|
|
355
|
+
) {
|
|
356
|
+
return refusedRetention('the prepared handoff does not match the selected draft readback')
|
|
357
|
+
}
|
|
358
|
+
// The original contribution is read back from its own session as well.
|
|
359
|
+
const original = knowledge.read(handoff.proposal.contributionSessionId)
|
|
360
|
+
const values = original.owningReadbacks?.verified === true ? original.owningReadbacks.immutableValues : null
|
|
361
|
+
if (
|
|
362
|
+
!values ||
|
|
363
|
+
!sameJson([...values.map((value) => value.receipt), selected.receipt], handoff.proposal.savedDraftReceipts) ||
|
|
364
|
+
values.find((value) => value.receipt.fieldId === 'original-words')?.text !== handoff.proposal.originalWords
|
|
365
|
+
) {
|
|
366
|
+
return refusedRetention('the original contribution is not confirmed by its owning readback')
|
|
367
|
+
}
|
|
368
|
+
const result = proposals.retainProposal(handoff)
|
|
369
|
+
if (!result.ok) return refusedRetention(result.error, result.outcome === 'unknown' ? 'unknown' : 'refused')
|
|
370
|
+
return { status: 200, body: { ...prepared, retention: retentionSummary(result.record, result.outcome, true) } }
|
|
371
|
+
}
|
|
372
|
+
|
|
256
373
|
function resolvePublishedPath({ rel, requireFile = false, requireHtml = false } = {}) {
|
|
257
374
|
const resolved = resolveWorkspacePath({
|
|
258
375
|
workspaceRoot: root,
|
|
@@ -395,8 +512,8 @@ export function createAtelierSidecarServer({
|
|
|
395
512
|
// Browsers send Origin on POST; same-origin GET normally omits it. Keep
|
|
396
513
|
// the strict origin gate for nonce disclosure instead of relaxing the
|
|
397
514
|
// existing session-auth GET contract or accepting unclassified reads.
|
|
398
|
-
if (review && url.pathname === '/api/review/session') {
|
|
399
|
-
if (!trustedMutationRequest(req.headers) || !body || Array.isArray(body) || Object.keys(body).length) {
|
|
515
|
+
if ((review && url.pathname === '/api/review/session') || (knowledge && url.pathname === '/api/knowledge/session')) {
|
|
516
|
+
if (!trustedMutationRequest(req.headers) || !body || Array.isArray(body) || Object.keys(body).length || (url.pathname === '/api/knowledge/session' && url.search)) {
|
|
400
517
|
json(res, 403, { ok: false, error: 'review session requires a same-origin empty request' })
|
|
401
518
|
return
|
|
402
519
|
}
|
|
@@ -411,6 +528,22 @@ export function createAtelierSidecarServer({
|
|
|
411
528
|
return
|
|
412
529
|
}
|
|
413
530
|
|
|
531
|
+
if (knowledge && url.pathname.startsWith('/api/knowledge/')) {
|
|
532
|
+
const operation = url.pathname.slice('/api/knowledge/'.length)
|
|
533
|
+
if (!['start', 'event', 'recover', 'lookup', 'workshop-handoff', 'workshop-review'].includes(operation) || url.search) {
|
|
534
|
+
json(res, 404, { ok: false, error: 'unknown knowledge action' }); return
|
|
535
|
+
}
|
|
536
|
+
try {
|
|
537
|
+
if (operation === 'workshop-handoff') {
|
|
538
|
+
const retained = retainWorkshopHandoff(body)
|
|
539
|
+
json(res, retained.status, retained.body)
|
|
540
|
+
} else {
|
|
541
|
+
json(res, 200, knowledge[operation === 'workshop-review' ? 'workshopReview' : operation](body))
|
|
542
|
+
}
|
|
543
|
+
} catch (error) { json(res, 409, { ok: false, error: knowledgeError(error) }) }
|
|
544
|
+
return
|
|
545
|
+
}
|
|
546
|
+
|
|
414
547
|
if (review && url.pathname === '/api/review/contributions') {
|
|
415
548
|
const result = review.contribute(body)
|
|
416
549
|
json(res, result.status, result)
|
|
@@ -528,6 +661,38 @@ export function createAtelierSidecarServer({
|
|
|
528
661
|
return
|
|
529
662
|
}
|
|
530
663
|
|
|
664
|
+
if (knowledge && url.pathname === '/knowledge' && !url.search) {
|
|
665
|
+
const body = renderKnowledgePage()
|
|
666
|
+
res.writeHead(200, htmlDocumentHeaders(Buffer.byteLength(body)))
|
|
667
|
+
res.end(body)
|
|
668
|
+
return
|
|
669
|
+
}
|
|
670
|
+
if (knowledge && url.pathname.startsWith('/api/knowledge/')) {
|
|
671
|
+
try {
|
|
672
|
+
let result
|
|
673
|
+
const operation = url.pathname.slice('/api/knowledge/'.length)
|
|
674
|
+
const parameters = operation === 'context' ? ['id', 'mode'] : operation === 'read' ? ['id'] : []
|
|
675
|
+
if ([...url.searchParams.keys()].some(k => !parameters.includes(k)) || [...url.searchParams.keys()].length !== new Set(url.searchParams.keys()).size) throw new Error('invalid knowledge query')
|
|
676
|
+
if (operation === 'dashboard') {
|
|
677
|
+
const dashboard = knowledgeDashboard(loadKnowledgeWorkspace(knowledgeProject))
|
|
678
|
+
try {
|
|
679
|
+
const profile = knowledge.workshopProfile()
|
|
680
|
+
dashboard.flows = [profile.flow, ...dashboard.flows]
|
|
681
|
+
dashboard.workshop = { available: true, context: profile.context }
|
|
682
|
+
} catch {
|
|
683
|
+
dashboard.workshop = { available: false, reason: 'No current actionable public workshop finding.' }
|
|
684
|
+
}
|
|
685
|
+
result = { dashboard }
|
|
686
|
+
}
|
|
687
|
+
else if (operation === 'context') result = { context: knowledgeQuestionContext(loadKnowledgeWorkspace(knowledgeProject), url.searchParams.get('id'), url.searchParams.get('mode') || 'graph') }
|
|
688
|
+
else if (operation === 'sessions') result = knowledge.list()
|
|
689
|
+
else if (operation === 'read') result = knowledge.read(url.searchParams.get('id'))
|
|
690
|
+
else { json(res, 404, { ok: false, error: 'unknown knowledge action' }); return }
|
|
691
|
+
json(res, 200, { ok: true, ...result })
|
|
692
|
+
} catch (error) { json(res, 409, { ok: false, error: knowledgeError(error) }) }
|
|
693
|
+
return
|
|
694
|
+
}
|
|
695
|
+
|
|
531
696
|
if (review && url.pathname === '/review') {
|
|
532
697
|
const body = renderReviewPage()
|
|
533
698
|
res.writeHead(200, htmlDocumentHeaders(Buffer.byteLength(body)))
|
|
@@ -626,6 +791,8 @@ export function createAtelierSidecarServer({
|
|
|
626
791
|
proposal: record.proposal,
|
|
627
792
|
diff: record.diff,
|
|
628
793
|
copyable: record.copyable,
|
|
794
|
+
// A retained workshop handoff is read back whole; other proposals are unchanged.
|
|
795
|
+
...(record.retention ? { payload: record.payload, retention: record.retention } : {}),
|
|
629
796
|
} : { ok: false, error: result.error, diagnostics: result.diagnostics })
|
|
630
797
|
return
|
|
631
798
|
}
|
package/src/server/server.mjs
CHANGED
|
@@ -1,6 +1,27 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
1
3
|
import { AtelierDiagnosticError, resolveProjectConfig } from '../project/config.mjs'
|
|
2
4
|
import { createAtelierSidecarServer } from './local-sidecar.mjs'
|
|
3
5
|
|
|
6
|
+
// dev serves what build wrote and builds nothing itself. Before build, the
|
|
7
|
+
// output folder (or, after graph alone, its manifest) is absent, and the
|
|
8
|
+
// sidecar failed with a raw ENOENT carrying an absolute path or a redacted
|
|
9
|
+
// internal error. Name the missing steps instead, and name the folder only
|
|
10
|
+
// relative to the project config. A non-directory is left to the sidecar.
|
|
11
|
+
function assertProjectionBuilt(project) {
|
|
12
|
+
const stat = fs.statSync(project.outputRoot, { throwIfNoEntry: false })
|
|
13
|
+
if (stat && !stat.isDirectory()) return
|
|
14
|
+
const manifestMissing = stat && !fs.existsSync(path.join(project.outputRoot, 'atelier.manifest.json'))
|
|
15
|
+
if (stat && !manifestMissing) return
|
|
16
|
+
const rel = path.relative(project.configDir, project.outputRoot)
|
|
17
|
+
const contained = rel !== '' && rel !== '..' && !rel.startsWith(`..${path.sep}`) && !path.isAbsolute(rel)
|
|
18
|
+
const folder = contained ? `projection output folder ${rel}` : 'the projection output folder'
|
|
19
|
+
const state = manifestMissing ? 'has no atelier.manifest.json' : 'does not exist'
|
|
20
|
+
throw new AtelierDiagnosticError('projection-output-missing', `${folder} ${state}; this project has not been built yet`, {
|
|
21
|
+
hint: 'Run atelier graph, then atelier build, with the same --project path, then retry.',
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
|
|
4
25
|
function parseServerArgs(argv = []) {
|
|
5
26
|
const args = { projectArgs: [] }
|
|
6
27
|
for (const arg of argv) {
|
|
@@ -30,11 +51,16 @@ function resolvePort(args, env = process.env) {
|
|
|
30
51
|
export async function runServerCommand(argv = process.argv.slice(2)) {
|
|
31
52
|
const args = parseServerArgs(argv)
|
|
32
53
|
const project = resolveProjectConfig({ argv: args.projectArgs })
|
|
54
|
+
// An unusable --port or PORT is refused before the project's build state,
|
|
55
|
+
// so a mistyped argument is named even in a project not built yet.
|
|
56
|
+
const port = args.smoke ? 0 : resolvePort(args)
|
|
57
|
+
assertProjectionBuilt(project)
|
|
33
58
|
const sidecar = createAtelierSidecarServer({
|
|
34
59
|
workspaceRoot: project.outputRoot,
|
|
35
60
|
stateDir: project.outputRoot,
|
|
36
61
|
reviewProject: argv.includes('--review') ? project : null,
|
|
37
|
-
|
|
62
|
+
knowledgeProject: argv.includes('--knowledge') ? project : null,
|
|
63
|
+
port,
|
|
38
64
|
})
|
|
39
65
|
|
|
40
66
|
if (args.smoke) {
|
|
@@ -53,5 +79,5 @@ export async function runServerCommand(argv = process.argv.slice(2)) {
|
|
|
53
79
|
}
|
|
54
80
|
|
|
55
81
|
const address = await sidecar.listen()
|
|
56
|
-
console.log(`MNSTRY Atelier listening on http://127.0.0.1:${address.port}
|
|
82
|
+
console.log(`MNSTRY Atelier listening on http://127.0.0.1:${address.port}/${argv.includes('--knowledge') ? 'knowledge' : ''}`)
|
|
57
83
|
}
|