@mnstry/atelier 0.2.0-alpha.5 → 0.2.0-alpha.8
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 +191 -0
- package/README.md +86 -25
- package/SECURITY.md +6 -1
- package/contracts/atelier-adoption-policy.v1.schema.json +49 -0
- package/contracts/atelier-coauthor.v1.schema.json +163 -0
- package/contracts/atelier-decision-request.v1.schema.json +476 -0
- package/contracts/atelier-decision-result.v1.schema.json +500 -0
- package/contracts/atelier-guide.v1.schema.json +227 -0
- package/contracts/atelier-intake.v1.schema.json +151 -0
- package/contracts/atelier-migration.v2.schema.json +61 -0
- package/contracts/atelier-obsidian-acceptance-receipt.v1.schema.json +211 -0
- package/contracts/atelier-obsidian-apply-policy.v1.schema.json +298 -0
- package/contracts/atelier-obsidian-corpus-profile.v1.schema.json +98 -0
- package/contracts/atelier-obsidian-edit-operation.v1.schema.json +138 -0
- package/contracts/atelier-obsidian-ext-settings.v1.schema.json +287 -0
- package/contracts/atelier-obsidian-generation-manifest.v1.schema.json +353 -0
- package/contracts/atelier-obsidian-proposal-receipt.v1.schema.json +110 -0
- package/contracts/atelier-obsidian-publication-journal.v1.schema.json +167 -0
- package/contracts/atelier-obsidian-scope.v1.schema.json +264 -0
- package/contracts/atelier-obsidian-service-state.v1.schema.json +149 -0
- package/contracts/atelier-obsidian-source-snapshot.v1.schema.json +179 -0
- package/contracts/atelier-pane-presentation.v1.schema.json +76 -0
- package/contracts/atelier-presentation.v1.schema.json +1293 -0
- package/contracts/atelier-repository-observation.v1.schema.json +163 -0
- package/contracts/atelier-review.v1.schema.json +1601 -0
- package/contracts/atelier-skill-steward.v1.schema.json +692 -0
- package/contracts/atelier-upgrade-plan.v2.schema.json +398 -0
- package/contracts/atelier-upgrade-receipt.v1.schema.json +99 -0
- package/contracts/atelier-vault-publication.v1.schema.json +58 -0
- package/docs/assurance-controls.md +12 -2
- package/docs/atelier-runtime.md +13 -2
- package/docs/atelier-sync.md +172 -0
- package/docs/blocks/claims.md +28 -18
- package/docs/blocks/will-not-do.md +12 -3
- package/docs/coauthor-session.md +57 -0
- package/docs/connected-composition.md +75 -0
- package/docs/decisions.md +167 -0
- package/docs/exact-upgrades.md +159 -0
- package/docs/extension-pack-lifecycle.md +33 -0
- package/docs/guided-upgrades.md +99 -0
- package/docs/install.md +65 -9
- package/docs/intake-and-guides.md +65 -0
- package/docs/integration-and-review-plan.md +409 -0
- package/docs/integration-contract-decisions.md +22 -0
- package/docs/local-review.md +84 -0
- package/docs/local-services.md +227 -0
- package/docs/local-state.md +107 -0
- package/docs/obsidian-contract.md +687 -0
- package/docs/obsidian.md +387 -0
- package/docs/portable-presentation.md +328 -0
- package/docs/presentation-consumer-boundaries.md +75 -0
- package/docs/presentation-review-disposition.md +47 -0
- package/docs/project-command-inventory.md +38 -0
- package/docs/project-options.md +53 -0
- package/docs/release-engineering.md +99 -2
- package/docs/review-portability.md +55 -0
- package/docs/skill-steward.md +156 -0
- package/docs/upgrade.md +70 -2
- package/docs/vault-service.md +292 -0
- package/fixtures/atelier-coauthor/invalid/authority.v1.json +5 -0
- package/fixtures/atelier-coauthor/valid/config.v1.json +12 -0
- package/fixtures/atelier-guide/invalid/authority.v1.json +11 -0
- package/fixtures/atelier-guide/valid/capability.v1.json +10 -0
- package/fixtures/atelier-guide/valid/consent.v1.json +11 -0
- package/fixtures/atelier-guide/valid/engagement.v1.json +7 -0
- package/fixtures/atelier-guide/valid/offer.v1.json +10 -0
- package/fixtures/atelier-intake/invalid/authority.v1.json +9 -0
- package/fixtures/atelier-intake/valid/attempt.v1.json +8 -0
- package/fixtures/atelier-intake/valid/completion.v1.json +9 -0
- package/fixtures/atelier-intake/valid/source.v1.json +7 -0
- package/fixtures/atelier-repository-observation/invalid/complete-with-blocker.v1.json +18 -0
- package/fixtures/atelier-repository-observation/valid/complete-local.v1.json +48 -0
- package/fixtures/atelier-review/invalid/authenticated-identity.v1.json +22 -0
- package/fixtures/atelier-review/valid/bound.v1.json +229 -0
- package/fixtures/atelier-review/valid/bundle.v1.json +267 -0
- package/fixtures/atelier-review/valid/contribution.v1.json +22 -0
- package/fixtures/atelier-review/valid/lifecycle.v1.json +14 -0
- package/fixtures/atelier-skill-steward/audit/invalid/source-mutation.v1.json +26 -0
- package/fixtures/atelier-skill-steward/audit/valid/clean.v1.json +26 -0
- package/fixtures/atelier-skill-steward/candidates/invalid/workflow-key.v1.json +37 -0
- package/fixtures/atelier-skill-steward/candidates/valid/create.v1.json +37 -0
- package/fixtures/atelier-skill-steward/lock/invalid/bad-digest.v1.json +18 -0
- package/fixtures/atelier-skill-steward/lock/valid/installed.v1.json +23 -0
- package/fixtures/atelier-skill-steward/plan/invalid/delete-action.v1.json +33 -0
- package/fixtures/atelier-skill-steward/plan/valid/add.v1.json +33 -0
- package/fixtures/atelier-upgrade-transaction/adoption-policy/invalid/example.json +12 -0
- package/fixtures/atelier-upgrade-transaction/adoption-policy/valid/example.json +11 -0
- package/fixtures/atelier-upgrade-transaction/migration/invalid/example.json +20 -0
- package/fixtures/atelier-upgrade-transaction/migration/valid/example.json +19 -0
- package/fixtures/atelier-upgrade-transaction/upgrade-plan/invalid/example.json +58 -0
- package/fixtures/atelier-upgrade-transaction/upgrade-plan/valid/example.json +57 -0
- package/fixtures/atelier-upgrade-transaction/upgrade-receipt/invalid/example.json +15 -0
- package/fixtures/atelier-upgrade-transaction/upgrade-receipt/valid/example.json +14 -0
- package/fixtures/atelier-vault-publication/invalid/publication.json +5 -0
- package/fixtures/atelier-vault-publication/valid/publication.json +11 -0
- package/fixtures/decisions/request/invalid/unknown-authority.v1.json +61 -0
- package/fixtures/decisions/request/valid/triage.v1.json +60 -0
- package/fixtures/decisions/result/invalid/execution-authority.v1.json +50 -0
- package/fixtures/decisions/result/valid/abstained.v1.json +23 -0
- package/fixtures/decisions/result/valid/assessed.v1.json +50 -0
- package/fixtures/obsidian/acceptance/ap05-extra-notes.json +14 -0
- package/fixtures/obsidian/acceptance/receipts/G07.valid.v1.json +63 -0
- package/fixtures/obsidian/acceptance/receipts/G13.valid.v1.json +69 -0
- package/fixtures/obsidian/acceptance/receipts/G14.valid.v1.json +62 -0
- package/fixtures/obsidian/acceptance/receipts/G15.valid.v1.json +56 -0
- package/fixtures/obsidian/acceptance/receipts/G16.valid.v1.json +73 -0
- package/fixtures/obsidian/acceptance/receipts/G17.valid.v1.json +75 -0
- package/fixtures/obsidian/acceptance/receipts/G18.valid.v1.json +64 -0
- package/fixtures/obsidian/acceptance/service-sentinels.json +10 -0
- package/fixtures/obsidian/contracts/acceptance-receipt/invalid/duplicate-evidence-name.v1.json +39 -0
- package/fixtures/obsidian/contracts/acceptance-receipt/invalid/evidence-by-absolute-path.v1.json +34 -0
- package/fixtures/obsidian/contracts/acceptance-receipt/invalid/missing-candidate-tree.v1.json +33 -0
- package/fixtures/obsidian/contracts/acceptance-receipt/invalid/no-evidence.v1.json +28 -0
- package/fixtures/obsidian/contracts/acceptance-receipt/invalid/unknown-outcome.v1.json +34 -0
- package/fixtures/obsidian/contracts/acceptance-receipt/invalid/unknown-top-level-field.v1.json +35 -0
- package/fixtures/obsidian/contracts/acceptance-receipt/valid/node-gate.v1.json +34 -0
- package/fixtures/obsidian/contracts/apply-policy/invalid/automatic-without-edit-class.v1.json +27 -0
- package/fixtures/obsidian/contracts/apply-policy/invalid/conflict-overwrite.v1.json +29 -0
- package/fixtures/obsidian/contracts/apply-policy/invalid/missing-policy-digest.v1.json +28 -0
- package/fixtures/obsidian/contracts/apply-policy/invalid/unbounded-batch.v1.json +29 -0
- package/fixtures/obsidian/contracts/apply-policy/invalid/unimplemented-edit-class.v1.json +30 -0
- package/fixtures/obsidian/contracts/apply-policy/invalid/unknown-mode.v1.json +29 -0
- package/fixtures/obsidian/contracts/apply-policy/invalid/unknown-selector-operator.v1.json +22 -0
- package/fixtures/obsidian/contracts/apply-policy/invalid/unknown-top-level-field.v1.json +30 -0
- package/fixtures/obsidian/contracts/apply-policy/valid/automatic-scoped.v1.json +29 -0
- package/fixtures/obsidian/contracts/apply-policy/valid/manual-default.v1.json +20 -0
- package/fixtures/obsidian/contracts/corpus-profile/invalid/absolute-path-in-ext.v1.json +30 -0
- package/fixtures/obsidian/contracts/corpus-profile/invalid/absolute-repository-root.v1.json +27 -0
- package/fixtures/obsidian/contracts/corpus-profile/invalid/duplicate-repository-id.v1.json +27 -0
- package/fixtures/obsidian/contracts/corpus-profile/invalid/overlapping-managed-roots.v1.json +27 -0
- package/fixtures/obsidian/contracts/corpus-profile/invalid/unknown-enrollment.v1.json +27 -0
- package/fixtures/obsidian/contracts/corpus-profile/invalid/unknown-top-level-field.v1.json +28 -0
- package/fixtures/obsidian/contracts/corpus-profile/valid/three-repositories.v1.json +27 -0
- package/fixtures/obsidian/contracts/edit-operation/invalid/absolute-recovery-ref.v1.json +21 -0
- package/fixtures/obsidian/contracts/edit-operation/invalid/inline-observed-bytes.v1.json +22 -0
- package/fixtures/obsidian/contracts/edit-operation/invalid/missing-base-digest.v1.json +20 -0
- package/fixtures/obsidian/contracts/edit-operation/invalid/short-idempotency-key.v1.json +21 -0
- package/fixtures/obsidian/contracts/edit-operation/invalid/unknown-kind.v1.json +21 -0
- package/fixtures/obsidian/contracts/edit-operation/valid/pending-body-replacement.v1.json +21 -0
- package/fixtures/obsidian/contracts/ext-settings/invalid/absolute-path-prefix.v1.json +28 -0
- package/fixtures/obsidian/contracts/ext-settings/invalid/apply-policy-in-portable-settings.v1.json +30 -0
- package/fixtures/obsidian/contracts/ext-settings/invalid/duplicate-scope-id.v1.json +27 -0
- package/fixtures/obsidian/contracts/ext-settings/invalid/machine-local-vault-path.v1.json +28 -0
- package/fixtures/obsidian/contracts/ext-settings/invalid/service-port-in-portable-settings.v1.json +28 -0
- package/fixtures/obsidian/contracts/ext-settings/invalid/unknown-default-scope.v1.json +27 -0
- package/fixtures/obsidian/contracts/ext-settings/valid/disabled.v1.json +5 -0
- package/fixtures/obsidian/contracts/ext-settings/valid/enabled-with-scopes.v1.json +27 -0
- package/fixtures/obsidian/contracts/generation-manifest/invalid/absolute-note-path.v1.json +114 -0
- package/fixtures/obsidian/contracts/generation-manifest/invalid/complete-with-unwritten-notes.v1.json +114 -0
- package/fixtures/obsidian/contracts/generation-manifest/invalid/derived-link-claimed-declared.v1.json +114 -0
- package/fixtures/obsidian/contracts/generation-manifest/invalid/duplicate-note-identity.v1.json +114 -0
- package/fixtures/obsidian/contracts/generation-manifest/invalid/in-scope-endpoint-missing.v1.json +114 -0
- package/fixtures/obsidian/contracts/generation-manifest/invalid/title-only-note-path.v1.json +114 -0
- package/fixtures/obsidian/contracts/generation-manifest/invalid/unknown-note-field.v1.json +115 -0
- package/fixtures/obsidian/contracts/generation-manifest/invalid/unknown-relation-type.v1.json +114 -0
- package/fixtures/obsidian/contracts/generation-manifest/invalid/withheld-endpoint.v1.json +114 -0
- package/fixtures/obsidian/contracts/generation-manifest/valid/two-notes.v1.json +114 -0
- package/fixtures/obsidian/contracts/oracles/scope-cases.json +412 -0
- package/fixtures/obsidian/contracts/proposal-receipt/invalid/accepted-without-proposal-id.v1.json +12 -0
- package/fixtures/obsidian/contracts/proposal-receipt/invalid/missing-adapter-operation-id.v1.json +11 -0
- package/fixtures/obsidian/contracts/proposal-receipt/invalid/unknown-backpressure-outcome.v1.json +12 -0
- package/fixtures/obsidian/contracts/proposal-receipt/invalid/unknown-top-level-field.v1.json +13 -0
- package/fixtures/obsidian/contracts/proposal-receipt/valid/accepted-new.v1.json +12 -0
- package/fixtures/obsidian/contracts/proposal-receipt/valid/deferred.v1.json +12 -0
- package/fixtures/obsidian/contracts/publication-journal/invalid/absolute-recovery-ref.v1.json +47 -0
- package/fixtures/obsidian/contracts/publication-journal/invalid/duplicate-sequence.v1.json +47 -0
- package/fixtures/obsidian/contracts/publication-journal/invalid/empty-protocol-id.v1.json +47 -0
- package/fixtures/obsidian/contracts/publication-journal/invalid/missing-protocol-id.v1.json +46 -0
- package/fixtures/obsidian/contracts/publication-journal/invalid/unknown-entry-field.v1.json +48 -0
- package/fixtures/obsidian/contracts/publication-journal/invalid/unknown-state.v1.json +47 -0
- package/fixtures/obsidian/contracts/publication-journal/valid/committed.v1.json +47 -0
- package/fixtures/obsidian/contracts/publication-journal/valid/updating-after-restart.v1.json +22 -0
- package/fixtures/obsidian/contracts/scope/invalid/absolute-path-prefix.v1.json +15 -0
- package/fixtures/obsidian/contracts/scope/invalid/duplicate-ids.v1.json +18 -0
- package/fixtures/obsidian/contracts/scope/invalid/expansion-without-node-budget.v1.json +40 -0
- package/fixtures/obsidian/contracts/scope/invalid/full-mode-with-subset.v1.json +8 -0
- package/fixtures/obsidian/contracts/scope/invalid/unknown-mode.v1.json +41 -0
- package/fixtures/obsidian/contracts/scope/invalid/unknown-selector-key.v1.json +15 -0
- package/fixtures/obsidian/contracts/scope/invalid/unknown-selector-operator.v1.json +14 -0
- package/fixtures/obsidian/contracts/scope/invalid/unknown-top-level-field.v1.json +42 -0
- package/fixtures/obsidian/contracts/scope/valid/explicit-empty.v1.json +8 -0
- package/fixtures/obsidian/contracts/scope/valid/focus.v1.json +11 -0
- package/fixtures/obsidian/contracts/scope/valid/full.v1.json +8 -0
- package/fixtures/obsidian/contracts/scope/valid/scoped-set-expression.v1.json +41 -0
- package/fixtures/obsidian/contracts/service-state/invalid/hostname-instead-of-literal-loopback.v1.json +23 -0
- package/fixtures/obsidian/contracts/service-state/invalid/missing-consent.v1.json +18 -0
- package/fixtures/obsidian/contracts/service-state/invalid/privileged-port.v1.json +23 -0
- package/fixtures/obsidian/contracts/service-state/invalid/unknown-top-level-field.v1.json +24 -0
- package/fixtures/obsidian/contracts/service-state/invalid/wildcard-bind.v1.json +23 -0
- package/fixtures/obsidian/contracts/service-state/valid/healthy.v1.json +23 -0
- package/fixtures/obsidian/contracts/source-snapshot/invalid/absolute-file-path.v1.json +48 -0
- package/fixtures/obsidian/contracts/source-snapshot/invalid/duplicate-repository-id.v1.json +48 -0
- package/fixtures/obsidian/contracts/source-snapshot/invalid/mixed-read.v1.json +48 -0
- package/fixtures/obsidian/contracts/source-snapshot/invalid/parent-traversal-path.v1.json +48 -0
- package/fixtures/obsidian/contracts/source-snapshot/invalid/unknown-file-field.v1.json +49 -0
- package/fixtures/obsidian/contracts/source-snapshot/valid/two-repositories.v1.json +48 -0
- package/fixtures/obsidian/edits/workspace.json +65 -0
- package/fixtures/obsidian/materialization/expected-full.json +99 -0
- package/fixtures/obsidian/materialization/expected-scoped.json +39 -0
- package/fixtures/obsidian/materialization/workspace.json +54 -0
- package/fixtures/obsidian/proposals/expected-shapes.json +11 -0
- package/fixtures/ui/presentation/pane.v1.json +7 -0
- package/fixtures/ui/presentation/reference.v1.json +293 -0
- package/package.json +61 -6
- package/skills/claude/atelier-guided-coauthor/SKILL.md +49 -0
- package/skills/claude/atelier-guided-upgrade/SKILL.md +97 -0
- package/skills/claude/atelier-open/SKILL.md +5 -0
- package/skills/claude/atelier-skill-steward/SKILL.md +62 -0
- package/skills/claude/mnstry-readiness/SKILL.md +5 -0
- package/skills/codex/atelier-guided-coauthor/SKILL.md +49 -0
- package/skills/codex/atelier-guided-upgrade/SKILL.md +97 -0
- package/skills/codex/atelier-open/SKILL.md +5 -0
- package/skills/codex/atelier-skill-steward/SKILL.md +62 -0
- package/skills/codex/mnstry-readiness/SKILL.md +5 -0
- package/src/access/preview-enforcer.mjs +84 -0
- package/src/boundary/content-rules.mjs +9 -4
- package/src/boundary/policy.mjs +74 -42
- package/src/cli/project-options.mjs +58 -0
- package/src/cli/run.mjs +61 -3
- package/src/coauthor/session.mjs +161 -0
- package/src/coauthor/store.mjs +173 -0
- package/src/collaboration/event-ledger.mjs +16 -33
- package/src/collaboration/inspection-bundle.mjs +223 -0
- package/src/collaboration/review-contracts.mjs +25 -0
- package/src/collaboration/review-store.mjs +305 -0
- package/src/commands/coauthor.mjs +26 -0
- package/src/commands/config.mjs +6 -1
- package/src/commands/extension-pack.mjs +15 -8
- package/src/commands/init.mjs +44 -5
- package/src/commands/obsidian.mjs +368 -0
- package/src/commands/review.mjs +147 -0
- package/src/commands/setup.mjs +51 -14
- package/src/commands/skills.mjs +199 -0
- package/src/commands/sync.mjs +107 -0
- package/src/composition/registry.mjs +27 -0
- package/src/composition/wire.mjs +122 -0
- package/src/contracts/corpus.mjs +35 -0
- package/src/decisions/contracts.d.mts +96 -0
- package/src/decisions/contracts.mjs +321 -0
- package/src/disclosure/content-scan.mjs +13 -0
- package/src/export/atelier-export-contract.mjs +16 -1
- package/src/extension-packs/lifecycle.mjs +154 -0
- package/src/graph/graph.mjs +64 -2
- package/src/graph/knowledge-graph.mjs +548 -24
- package/src/guides/contracts.mjs +54 -0
- package/src/index.mjs +29 -0
- package/src/intake/store.mjs +119 -0
- package/src/preview/channel.mjs +89 -0
- package/src/project/config.mjs +50 -49
- package/src/project/durable-state.mjs +161 -0
- package/src/project/file-class.mjs +166 -0
- package/src/project/private-state.mjs +11 -1
- package/src/projection/obsidian/contracts.mjs +571 -0
- package/src/projection/obsidian/edits/align.mjs +169 -0
- package/src/projection/obsidian/edits/apply.mjs +889 -0
- package/src/projection/obsidian/edits/arbitrate.mjs +414 -0
- package/src/projection/obsidian/edits/contribution.mjs +108 -0
- package/src/projection/obsidian/edits/index.mjs +19 -0
- package/src/projection/obsidian/edits/object-identity.mjs +61 -0
- package/src/projection/obsidian/edits/object-store.mjs +517 -0
- package/src/projection/obsidian/edits/observe.mjs +123 -0
- package/src/projection/obsidian/edits/policy.mjs +123 -0
- package/src/projection/obsidian/edits/regions.mjs +648 -0
- package/src/projection/obsidian/materialize/byte-lens.mjs +98 -0
- package/src/projection/obsidian/materialize/index.mjs +7 -0
- package/src/projection/obsidian/materialize/path-registry.mjs +105 -0
- package/src/projection/obsidian/materialize/prepare-view.mjs +746 -0
- package/src/projection/obsidian/materialize/settings.mjs +57 -0
- package/src/projection/obsidian/materialize/stage.mjs +27 -0
- package/src/projection/obsidian/proposals/adapter.mjs +572 -0
- package/src/projection/obsidian/proposals/backpressure.mjs +113 -0
- package/src/projection/obsidian/proposals/contribution.mjs +55 -0
- package/src/projection/obsidian/proposals/index.mjs +23 -0
- package/src/projection/obsidian/proposals/observation.mjs +216 -0
- package/src/projection/obsidian/proposals/queue.mjs +277 -0
- package/src/projection/obsidian/proposals/router.mjs +168 -0
- package/src/projection/obsidian/publication/bridge-script.mjs +266 -0
- package/src/projection/obsidian/publication/exchange.mjs +117 -0
- package/src/projection/obsidian/publication/index.mjs +5 -0
- package/src/projection/obsidian/publication/publisher.mjs +588 -0
- package/src/projection/obsidian/publication/test-seam.mjs +16 -0
- package/src/projection/obsidian/publication/transport.mjs +139 -0
- package/src/projection/obsidian/recovery/index.mjs +6 -0
- package/src/projection/obsidian/recovery/journal.mjs +134 -0
- package/src/projection/obsidian/recovery/late-writer.mjs +46 -0
- package/src/projection/obsidian/recovery/restart.mjs +316 -0
- package/src/projection/obsidian/recovery/store.mjs +234 -0
- package/src/projection/obsidian/selection-ui/conflict-view.mjs +91 -0
- package/src/projection/obsidian/selection-ui/contribution.mjs +141 -0
- package/src/projection/obsidian/selection-ui/focus.mjs +82 -0
- package/src/projection/obsidian/selection-ui/index.mjs +12 -0
- package/src/projection/obsidian/selection-ui/policy-setup.mjs +107 -0
- package/src/projection/obsidian/selection-ui/receipt.mjs +173 -0
- package/src/projection/obsidian/selection-ui/selection-state.mjs +122 -0
- package/src/projection/obsidian/selection-ui/selection.mjs +109 -0
- package/src/projection/project.mjs +28 -19
- package/src/readiness-protocols/evidence.mjs +282 -0
- package/src/readiness-protocols/runtime.mjs +4 -3
- package/src/readiness-protocols/source-read.mjs +39 -0
- package/src/runtime/git-adapter.mjs +189 -0
- package/src/runtime/local-state.mjs +439 -0
- package/src/runtime/obsidian/app-capability.mjs +110 -0
- package/src/runtime/obsidian/app-production-seams.mjs +96 -0
- package/src/runtime/obsidian/contributions/proposal-adapter.mjs +4 -0
- package/src/runtime/obsidian/contributions/selection-ui.mjs +4 -0
- package/src/runtime/obsidian/contributions/source-apply.mjs +4 -0
- package/src/runtime/obsidian/contributions.mjs +27 -0
- package/src/runtime/obsidian/documents.mjs +37 -0
- package/src/runtime/obsidian/enablement.mjs +53 -0
- package/src/runtime/obsidian/engine.mjs +532 -0
- package/src/runtime/obsidian/errors.mjs +14 -0
- package/src/runtime/obsidian/extension-points.mjs +103 -0
- package/src/runtime/obsidian/index.mjs +34 -0
- package/src/runtime/obsidian/lifecycle.mjs +258 -0
- package/src/runtime/obsidian/machine-settings.mjs +260 -0
- package/src/runtime/obsidian/observation.mjs +158 -0
- package/src/runtime/obsidian/opening.mjs +243 -0
- package/src/runtime/obsidian/pending-edits.mjs +141 -0
- package/src/runtime/obsidian/pipeline.mjs +122 -0
- package/src/runtime/obsidian/private-lock.mjs +156 -0
- package/src/runtime/obsidian/process-identity.mjs +58 -0
- package/src/runtime/obsidian/service-client.mjs +66 -0
- package/src/runtime/obsidian/service-main.mjs +89 -0
- package/src/runtime/obsidian/service-record.mjs +169 -0
- package/src/runtime/obsidian/service-server.mjs +121 -0
- package/src/runtime/obsidian/service.mjs +216 -0
- package/src/runtime/obsidian/startup-adapters.mjs +87 -0
- package/src/runtime/obsidian/state-store.mjs +180 -0
- package/src/runtime/obsidian/sync-notice.mjs +33 -0
- package/src/runtime/obsidian/tick-loop.mjs +84 -0
- package/src/runtime/obsidian/watchers.mjs +36 -0
- package/src/runtime/repository-observation.mjs +491 -0
- package/src/runtime/supervisor.mjs +803 -0
- package/src/server/local-sidecar.mjs +44 -0
- package/src/server/server.mjs +1 -0
- package/src/skills/steward.mjs +780 -0
- package/src/ui/presentation/browser.mjs +278 -0
- package/src/ui/presentation/contract.mjs +116 -0
- package/src/ui/presentation/index.mjs +7 -0
- package/src/ui/presentation/native.mjs +153 -0
- package/src/ui/presentation/proof.mjs +57 -0
- package/src/ui/presentation/schema-check.mjs +30 -0
- package/src/ui/presentation/schema.generated.mjs +1371 -0
- package/src/ui/presentation/state.mjs +43 -0
- package/src/ui/presentation/styles.mjs +67 -0
- package/src/ui/presentation/tokens.mjs +74 -0
- package/src/ui/presentation/web.mjs +85 -0
- package/src/ui/review-page.mjs +32 -0
- package/src/upgrade/explanation.mjs +33 -0
- package/src/upgrade/provenance.mjs +338 -0
- package/src/upgrade/transaction-files.mjs +103 -0
- package/src/upgrade/transaction.mjs +475 -0
- package/src/upgrade/upgrade.mjs +63 -28
- package/src/vault/hosts.mjs +21 -0
- package/src/vault/identity.mjs +19 -0
- package/src/vault/index.mjs +7 -0
- package/src/vault/interface.mjs +11 -0
- package/src/vault/metadata.mjs +31 -0
- package/src/vault/privacy.mjs +148 -0
- package/src/vault/probe.mjs +89 -0
- package/src/vault/service.mjs +131 -0
- package/src/vault/source.mjs +45 -0
- package/src/vault/storage.mjs +23 -0
- package/templates/distribution-workspace/.claude/launch.json +6 -2
- package/templates/distribution-workspace/gitignore +1 -0
- package/templates/distribution-workspace/project/README.md +2 -2
- package/templates/external-project-workspace/README.md +45 -0
- package/templates/external-project-workspace/answers.example.json +4 -0
- package/templates/external-project-workspace/atelier.project.json +40 -0
- package/templates/external-project-workspace/boundary-policy.v1.json +60 -0
- package/templates/external-project-workspace/gitignore +8 -0
- package/templates/external-project-workspace/pack-lifecycle.json +14 -0
- package/templates/external-project-workspace/packs/protocols/contract-gate.v1.json +109 -0
- package/templates/external-project-workspace/packs/sample-pack.v1.json +48 -0
- package/templates/external-project-workspace/repo-access.v1.json +9 -0
- package/templates/external-project-workspace/source/README.md +11 -0
- package/templates/launch.json +6 -2
- package/templates/private-domain-workspace/.claude/launch.json +6 -2
- package/templates/private-domain-workspace/domain/README.md +2 -2
- package/templates/private-domain-workspace/gitignore +1 -0
- package/templates/shared-project-workspace/.claude/launch.json +6 -2
- package/templates/shared-project-workspace/gitignore +1 -0
- package/templates/shared-project-workspace/project/README.md +2 -2
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { canonicalize } from '../attestation/jcs.mjs';
|
|
4
|
+
import { validateJsonSchema } from '../export/atelier-export-contract.mjs';
|
|
5
|
+
|
|
6
|
+
const schema = JSON.parse(fs.readFileSync(new URL('../../contracts/atelier-guide.v1.schema.json', import.meta.url), 'utf8'));
|
|
7
|
+
export function guideDocumentDigest(value) { return createHash('sha256').update(canonicalize(value)).digest('hex'); }
|
|
8
|
+
export function validateGuideDocument(value, kind) {
|
|
9
|
+
if (!['offer', 'capability', 'consent', 'engagement'].includes(kind)) return ['unknown guide document kind'];
|
|
10
|
+
const errors = validateJsonSchema({ $schema: schema.$schema, $defs: schema.$defs, $ref: `#/$defs/${kind}` }, value);
|
|
11
|
+
if (kind === 'capability' && !errors.length) {
|
|
12
|
+
const url = new URL(value.serviceRef);
|
|
13
|
+
if (url.username || url.password || url.search || url.hash) errors.push('service reference must not contain credentials, query or fragment');
|
|
14
|
+
}
|
|
15
|
+
return errors;
|
|
16
|
+
}
|
|
17
|
+
function requireValid(value, kind) {
|
|
18
|
+
if (validateGuideDocument(value, kind).length) throw new Error(`invalid guide ${kind}`);
|
|
19
|
+
}
|
|
20
|
+
export function createGuideEngagement(offer) {
|
|
21
|
+
requireValid(offer, 'offer');
|
|
22
|
+
return { schema: 'mnstry.atelier-guide-engagement@v1', offerDigest: guideDocumentDigest(offer),
|
|
23
|
+
revision: 0, status: 'proposed', authority: 'local-assertion-only' };
|
|
24
|
+
}
|
|
25
|
+
export function transitionGuideEngagement(state, { expectedRevision, action, offerDigest }) {
|
|
26
|
+
requireValid(state, 'engagement');
|
|
27
|
+
if (expectedRevision !== state.revision || offerDigest !== state.offerDigest) throw new Error('stale or mismatched guide engagement');
|
|
28
|
+
const routes = { proposed: { accept: 'accepted', revoke: 'revoked' }, accepted: { pause: 'paused', revoke: 'revoked' }, paused: { resume: 'accepted', revoke: 'revoked' }, revoked: {} };
|
|
29
|
+
const status = Object.hasOwn(routes[state.status], action) ? routes[state.status][action] : null;
|
|
30
|
+
if (!status) throw new Error('guide transition refused');
|
|
31
|
+
return { ...state, revision: state.revision + 1, status };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// No connection, credentials, invocation, payment, or permission grant occurs.
|
|
35
|
+
// A host must independently authenticate participants and revalidate revocation,
|
|
36
|
+
// consent, policy, schema and exact bytes immediately before any disclosure.
|
|
37
|
+
export function assessGuideInvocation({ offer, capability, engagement, consent, payload, now }) {
|
|
38
|
+
const blockers = [];
|
|
39
|
+
for (const [kind, value] of Object.entries({ offer, capability, engagement, consent })) {
|
|
40
|
+
if (validateGuideDocument(value, kind).length) blockers.push(`invalid ${kind}`);
|
|
41
|
+
}
|
|
42
|
+
if (blockers.length) return { status: 'blocked', blockers, executionAuthority: false };
|
|
43
|
+
let bytes;
|
|
44
|
+
try { bytes = canonicalize(payload); } catch { blockers.push('payload is not canonical JSON'); }
|
|
45
|
+
if (!Number.isFinite(now)) blockers.push('explicit current time required');
|
|
46
|
+
if (engagement.status !== 'accepted') blockers.push('engagement is not accepted');
|
|
47
|
+
if (engagement.offerDigest !== guideDocumentDigest(offer) || consent.offerDigest !== engagement.offerDigest) blockers.push('offer binding mismatch');
|
|
48
|
+
if (consent.engagementRevision !== engagement.revision) blockers.push('consent revision is stale');
|
|
49
|
+
if (!offer.capabilityIds.includes(capability.id) || consent.capabilityDigest !== guideDocumentDigest(capability)) blockers.push('capability binding mismatch');
|
|
50
|
+
if (consent.status !== 'approved') blockers.push('disclosure consent is not approved');
|
|
51
|
+
if (now < Date.parse(consent.validFrom) || now >= Date.parse(consent.expiresAt) || Date.parse(consent.validFrom) >= Date.parse(consent.expiresAt)) blockers.push('consent is not currently valid');
|
|
52
|
+
if (bytes !== undefined && (Buffer.byteLength(bytes) > capability.maxInputBytes || guideDocumentDigest(payload) !== consent.payloadDigest)) blockers.push('payload binding or byte limit mismatch');
|
|
53
|
+
return { status: blockers.length ? 'blocked' : 'eligible-for-host-validation', blockers, executionAuthority: false };
|
|
54
|
+
}
|
package/src/index.mjs
CHANGED
|
@@ -98,3 +98,32 @@ export {
|
|
|
98
98
|
export {
|
|
99
99
|
analysisAdapterDryRun,
|
|
100
100
|
} from './analysis/adapter.mjs'
|
|
101
|
+
|
|
102
|
+
export {
|
|
103
|
+
GitCommandError,
|
|
104
|
+
classifyRemoteAuthentication,
|
|
105
|
+
inspectGitEngine,
|
|
106
|
+
parseGitVersion,
|
|
107
|
+
resolveGitExecutable,
|
|
108
|
+
runGit,
|
|
109
|
+
sanitizeRemoteUrl,
|
|
110
|
+
} from './runtime/git-adapter.mjs'
|
|
111
|
+
|
|
112
|
+
export {
|
|
113
|
+
ATELIER_REPOSITORY_OBSERVATION_SCHEMA,
|
|
114
|
+
classifyFilesystemRoot,
|
|
115
|
+
observeRepository,
|
|
116
|
+
resolveRepositoryRoot,
|
|
117
|
+
validateRepositoryObservation,
|
|
118
|
+
} from './runtime/repository-observation.mjs'
|
|
119
|
+
|
|
120
|
+
export {
|
|
121
|
+
ATELIER_COMMIT_PLAN_SCHEMA,
|
|
122
|
+
enrollRepository,
|
|
123
|
+
executeUserConfirmedCommit,
|
|
124
|
+
operationTrace,
|
|
125
|
+
planUserConfirmedCommit,
|
|
126
|
+
reconcileRepository,
|
|
127
|
+
runtimeStatus,
|
|
128
|
+
setRepositoryPaused,
|
|
129
|
+
} from './runtime/supervisor.mjs'
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { withPrivateLock, publishPrivateFile } from '../project/durable-state.mjs';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { createHash } from 'node:crypto';
|
|
5
|
+
import { execFileSync } from 'node:child_process';
|
|
6
|
+
import { canonicalize } from '../attestation/jcs.mjs';
|
|
7
|
+
import { validateJsonSchema } from '../export/atelier-export-contract.mjs';
|
|
8
|
+
import { ensureContainedPrivateDirectory, openRegularFileNoFollow } from '../project/private-state.mjs';
|
|
9
|
+
|
|
10
|
+
export const INTAKE_MAX_BYTES = 16 * 1024 * 1024;
|
|
11
|
+
const schema = JSON.parse(fs.readFileSync(new URL('../../contracts/atelier-intake.v1.schema.json', import.meta.url), 'utf8'));
|
|
12
|
+
export function validateIntakeDocument(value) { return validateJsonSchema(schema, value); }
|
|
13
|
+
function valid(value) { if (validateIntakeDocument(value).length) throw new Error('invalid intake document'); return value; }
|
|
14
|
+
export function intakeDigest(bytes) { return createHash('sha256').update(bytes).digest('hex'); }
|
|
15
|
+
const idPattern = /^[a-z][a-z0-9-]{0,63}$/;
|
|
16
|
+
const digestPattern = /^[0-9a-f]{64}$/;
|
|
17
|
+
function digest(value) { if (!digestPattern.test(value ?? '')) throw new Error('invalid content digest'); return value; }
|
|
18
|
+
function id(value) { if (!idPattern.test(value ?? '')) throw new Error('invalid intake identifier'); return value; }
|
|
19
|
+
function read(file) {
|
|
20
|
+
const fd = openRegularFileNoFollow(file);
|
|
21
|
+
try {
|
|
22
|
+
if (fs.fstatSync(fd).size > INTAKE_MAX_BYTES) throw new Error('intake byte ceiling exceeded');
|
|
23
|
+
const bytes = fs.readFileSync(fd);
|
|
24
|
+
if (bytes.length > INTAKE_MAX_BYTES) throw new Error('intake byte ceiling exceeded');
|
|
25
|
+
return bytes;
|
|
26
|
+
} finally { fs.closeSync(fd); }
|
|
27
|
+
}
|
|
28
|
+
function immutable(file, bytes) {
|
|
29
|
+
publishPrivateFile(file, bytes);
|
|
30
|
+
if (!read(file).equals(Buffer.from(bytes))) throw new Error('immutable intake readback mismatch');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Local integrity storage, not a worker, semantic acceptance service, or import
|
|
34
|
+
// authority. Callers retain their source trees and choose their existing extractor.
|
|
35
|
+
export function createIntakeStore({ workspaceRoot = process.cwd() } = {}) {
|
|
36
|
+
const root = fs.realpathSync(workspaceRoot);
|
|
37
|
+
function placement() {
|
|
38
|
+
const env = Object.fromEntries(Object.entries(process.env).filter(([key]) => !key.toUpperCase().startsWith('GIT_')));
|
|
39
|
+
try {
|
|
40
|
+
if (execFileSync('git', ['-C', root, 'ls-files', '-z', '--', '.atelier-local'], { env, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] })) throw new Error('tracked state');
|
|
41
|
+
execFileSync('git', ['-C', root, 'check-ignore', '--quiet', '.atelier-local/'], { env, stdio: 'ignore' });
|
|
42
|
+
} catch { throw new Error('intake requires ignored, untracked .atelier-local/ in a Git workspace'); }
|
|
43
|
+
}
|
|
44
|
+
placement();
|
|
45
|
+
function directory(...parts) {
|
|
46
|
+
return ensureContainedPrivateDirectory({ workspaceRoot: root, directory: path.join(root, '.atelier-local', 'intake', ...parts), label: 'intake state' });
|
|
47
|
+
}
|
|
48
|
+
function locked(fn) {
|
|
49
|
+
placement();
|
|
50
|
+
const file = path.join(directory(), 'operation.lock');
|
|
51
|
+
return withPrivateLock(file, fn);
|
|
52
|
+
}
|
|
53
|
+
function sourceFile(ref) {
|
|
54
|
+
if (typeof ref !== 'string' || !ref || path.isAbsolute(ref) || ref.includes('\\') ||
|
|
55
|
+
ref.split('/').some(p => !p || p === '.' || p === '..' || p.startsWith('.'))) throw new Error('source must be a visible workspace-relative file');
|
|
56
|
+
let file = root;
|
|
57
|
+
for (const part of ref.split('/')) {
|
|
58
|
+
file = path.join(file, part);
|
|
59
|
+
if (fs.lstatSync(file).isSymbolicLink()) throw new Error('redirected intake source refused');
|
|
60
|
+
}
|
|
61
|
+
return file;
|
|
62
|
+
}
|
|
63
|
+
function blob(blobId) {
|
|
64
|
+
const bytes = read(path.join(directory('blobs'), digest(blobId)));
|
|
65
|
+
if (intakeDigest(bytes) !== blobId) throw new Error('stored blob digest mismatch');
|
|
66
|
+
return bytes;
|
|
67
|
+
}
|
|
68
|
+
function record(dir, name, value) { valid(value); immutable(path.join(dir, name), canonicalize(value) + '\n'); return value; }
|
|
69
|
+
return Object.freeze({
|
|
70
|
+
ingest({ ref, expectedDigest }) {
|
|
71
|
+
return locked(() => {
|
|
72
|
+
digest(expectedDigest);
|
|
73
|
+
const file = sourceFile(ref), bytes = read(file);
|
|
74
|
+
if (intakeDigest(bytes) !== expectedDigest) throw new Error('source digest mismatch');
|
|
75
|
+
immutable(path.join(directory('blobs'), expectedDigest), bytes);
|
|
76
|
+
if (intakeDigest(read(sourceFile(ref))) !== expectedDigest) throw new Error('source changed during intake');
|
|
77
|
+
const manifest = { schema: 'mnstry.atelier-intake-source@v1', sourceRef: ref, blobId: expectedDigest, bytes: bytes.length, originalMutation: false };
|
|
78
|
+
return record(directory('sources'), intakeDigest(canonicalize(manifest)) + '.json', manifest);
|
|
79
|
+
});
|
|
80
|
+
},
|
|
81
|
+
beginAttempt({ attemptId, blobId, extractorId, extractorVersion, configurationDigest }) {
|
|
82
|
+
return locked(() => {
|
|
83
|
+
id(attemptId); id(extractorId); digest(configurationDigest); blob(blobId);
|
|
84
|
+
if (typeof extractorVersion !== 'string' || !/^[a-zA-Z0-9._-]{1,64}$/.test(extractorVersion)) throw new Error('invalid extractor version');
|
|
85
|
+
const manifest = { schema: 'mnstry.atelier-intake-attempt@v1', attemptId, blobId, extractorId, extractorVersion, configurationDigest };
|
|
86
|
+
return record(directory('attempts', attemptId), 'attempt.json', manifest);
|
|
87
|
+
});
|
|
88
|
+
},
|
|
89
|
+
completeAttempt({ attemptId, output, expectedOutputDigest }) {
|
|
90
|
+
return locked(() => {
|
|
91
|
+
id(attemptId); digest(expectedOutputDigest);
|
|
92
|
+
if (typeof output !== 'string' || Buffer.byteLength(output) > INTAKE_MAX_BYTES) throw new Error('output must be bounded UTF-8 text');
|
|
93
|
+
if (intakeDigest(output) !== expectedOutputDigest) throw new Error('output digest mismatch');
|
|
94
|
+
const dir = directory('attempts', attemptId);
|
|
95
|
+
const attemptBytes = read(path.join(dir, 'attempt.json'));
|
|
96
|
+
const attempt = valid(JSON.parse(attemptBytes));
|
|
97
|
+
if (attempt.attemptId !== attemptId || attempt.schema !== 'mnstry.atelier-intake-attempt@v1') throw new Error('attempt identity mismatch');
|
|
98
|
+
blob(attempt.blobId);
|
|
99
|
+
immutable(path.join(dir, 'output.txt'), output);
|
|
100
|
+
// Completion is written last. A crash before it is incomplete, not success.
|
|
101
|
+
return record(dir, 'completion.json', { schema: 'mnstry.atelier-intake-completion@v1', attemptId,
|
|
102
|
+
attemptDigest: intakeDigest(attemptBytes), outputDigest: expectedOutputDigest, bytes: Buffer.byteLength(output),
|
|
103
|
+
integrity: 'verified', semanticAcceptance: 'pending' });
|
|
104
|
+
});
|
|
105
|
+
},
|
|
106
|
+
readCompletion(attemptId) {
|
|
107
|
+
placement(); id(attemptId);
|
|
108
|
+
const dir = directory('attempts', attemptId), receipt = valid(JSON.parse(read(path.join(dir, 'completion.json'))));
|
|
109
|
+
if (receipt.schema !== 'mnstry.atelier-intake-completion@v1' || receipt.attemptId !== attemptId ||
|
|
110
|
+
receipt.integrity !== 'verified' || receipt.semanticAcceptance !== 'pending') throw new Error('invalid completion receipt');
|
|
111
|
+
const attemptBytes = read(path.join(dir, 'attempt.json')), output = read(path.join(dir, 'output.txt'));
|
|
112
|
+
if (intakeDigest(attemptBytes) !== receipt.attemptDigest || intakeDigest(output) !== receipt.outputDigest || output.length !== receipt.bytes) throw new Error('completion integrity mismatch');
|
|
113
|
+
const attempt = valid(JSON.parse(attemptBytes));
|
|
114
|
+
if (attempt.attemptId !== attemptId || attempt.schema !== 'mnstry.atelier-intake-attempt@v1') throw new Error('attempt identity mismatch');
|
|
115
|
+
blob(attempt.blobId);
|
|
116
|
+
return receipt;
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// Browser-only channel. Host/session/service authority never enters this module.
|
|
2
|
+
import { parseRequest, parseBoundedJSON, closed, need, isRef, isDigest, MODES } from '../composition/wire.mjs';
|
|
3
|
+
const HELLO = 'atelier.preview.mount/experimental-v1';
|
|
4
|
+
function origin(value) { const u = new URL(value); need(u.origin !== 'null' && u.protocol === 'https:' && u.origin === value); return value; }
|
|
5
|
+
function resultCheck(bytes, request, generation) {
|
|
6
|
+
const result = parseBoundedJSON(bytes);
|
|
7
|
+
closed(result, ['state'], ['projectedData']);
|
|
8
|
+
const s = result.state;
|
|
9
|
+
closed(s, ['schema', 'requestId', 'placementId', 'sourceDigest', 'mode', 'state', 'sessionGeneration', 'publicMessage']);
|
|
10
|
+
need(s.schema === 'atelier-preview-state/proposal-v1' && s.requestId === request.requestId && s.placementId === request.placementId && s.sourceDigest === request.sourceDigest && s.mode === request.mode);
|
|
11
|
+
need(s.sessionGeneration === generation && ['loading', 'ready', 'unauthenticated', 'forbidden', 'expired', 'unavailable', 'stale'].includes(s.state));
|
|
12
|
+
need(typeof s.publicMessage === 'string' && s.publicMessage.length <= 160);
|
|
13
|
+
need(s.state === 'ready' ? Object.hasOwn(result, 'projectedData') : !Object.hasOwn(result, 'projectedData'));
|
|
14
|
+
return result;
|
|
15
|
+
}
|
|
16
|
+
function fail(request, generation) { return { state: { schema: 'atelier-preview-state/proposal-v1', requestId: request.requestId, placementId: request.placementId,
|
|
17
|
+
sourceDigest: request.sourceDigest, mode: request.mode, state: 'unavailable', sessionGeneration: generation, publicMessage: 'Preview unavailable.' } }; }
|
|
18
|
+
|
|
19
|
+
export function mountPreview({ frame, previewOrigin, requestBytes, generation, handle, onState = () => {} }) {
|
|
20
|
+
origin(previewOrigin); need(new URL(frame.src).origin === previewOrigin && previewOrigin !== window.location.origin);
|
|
21
|
+
const template = parseRequest(requestBytes); need(!template.intentRef && Number.isSafeInteger(generation) && generation >= 0 && typeof handle === 'function');
|
|
22
|
+
const nonce = crypto.randomUUID(), channel = new MessageChannel(), controller = new AbortController();
|
|
23
|
+
let active = true, ready = false, sequence = 0, latest = 0;
|
|
24
|
+
const invalidate = () => {
|
|
25
|
+
if (!active) return; active = false; controller.abort();
|
|
26
|
+
channel.port1.postMessage(JSON.stringify({type:'invalidate',nonce})); channel.port1.close(); frame.removeEventListener('load', invalidate);
|
|
27
|
+
onState('stale');
|
|
28
|
+
};
|
|
29
|
+
frame.addEventListener('load', invalidate); // Future navigation invalidates this mount.
|
|
30
|
+
channel.port1.onmessage = async event => {
|
|
31
|
+
if (!active) return;
|
|
32
|
+
try {
|
|
33
|
+
const v = parseBoundedJSON(event.data);
|
|
34
|
+
if (!ready) { closed(v, ['type', 'nonce']); need(v.type === 'ready' && v.nonce === nonce); ready = true; onState('connected'); return; }
|
|
35
|
+
closed(v, ['type', 'nonce', 'sequence', 'request']);
|
|
36
|
+
need(v.type === 'request' && v.nonce === nonce && Number.isSafeInteger(v.sequence) && v.sequence > sequence);
|
|
37
|
+
const request = parseRequest(v.request);
|
|
38
|
+
need(request.placementId === template.placementId && request.sourceDigest === template.sourceDigest && request.mode === template.mode);
|
|
39
|
+
sequence = v.sequence; latest = sequence; const thisSequence = sequence;
|
|
40
|
+
let result;
|
|
41
|
+
try { result = resultCheck(JSON.stringify(await handle(v.request, controller.signal)), request, generation); }
|
|
42
|
+
catch { result = fail(request, generation); }
|
|
43
|
+
if (!active || controller.signal.aborted || thisSequence !== latest) return;
|
|
44
|
+
channel.port1.postMessage(JSON.stringify({ type: 'result', nonce, sequence: thisSequence, result }));
|
|
45
|
+
} catch { invalidate(); }
|
|
46
|
+
};
|
|
47
|
+
channel.port1.start();
|
|
48
|
+
frame.contentWindow.postMessage(JSON.stringify({ type: HELLO, nonce, generation, request: requestBytes }), previewOrigin, [channel.port2]);
|
|
49
|
+
return Object.freeze({ dispose: invalidate });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function receivePreview({ shellOrigin, onMount, onResult, onInvalidate = () => {} }) {
|
|
53
|
+
origin(shellOrigin); need(shellOrigin !== window.location.origin);
|
|
54
|
+
let active = null;
|
|
55
|
+
function clear() { active?.port.close(); active = null; onInvalidate(); }
|
|
56
|
+
function receive(event) {
|
|
57
|
+
if (event.origin !== shellOrigin || event.source !== window.parent) return;
|
|
58
|
+
try {
|
|
59
|
+
const v = parseBoundedJSON(event.data);
|
|
60
|
+
closed(v, ['type', 'nonce', 'generation', 'request']);
|
|
61
|
+
need(v.type === HELLO && isRef(v.nonce) && Number.isSafeInteger(v.generation) && v.generation >= 0 && event.ports.length === 1);
|
|
62
|
+
const request = parseRequest(v.request); need(!request.intentRef && isDigest(request.sourceDigest) && MODES.includes(request.mode));
|
|
63
|
+
clear();
|
|
64
|
+
const mount = { port: event.ports[0], nonce: v.nonce, generation: v.generation, request, sequence: 0, settled: 0 };
|
|
65
|
+
active = mount;
|
|
66
|
+
mount.port.onmessage = e => {
|
|
67
|
+
if (active !== mount) return;
|
|
68
|
+
try {
|
|
69
|
+
const packet = parseBoundedJSON(e.data);
|
|
70
|
+
if (packet?.type === 'invalidate') { closed(packet, ['type','nonce']); need(packet.nonce === mount.nonce); clear(); return; }
|
|
71
|
+
closed(packet, ['type', 'nonce', 'sequence', 'result']);
|
|
72
|
+
need(packet.type === 'result' && packet.nonce === mount.nonce && packet.sequence === mount.sequence && packet.sequence > mount.settled);
|
|
73
|
+
const result = resultCheck(JSON.stringify(packet.result), mount.pending, mount.generation);
|
|
74
|
+
mount.settled = packet.sequence; onResult(result);
|
|
75
|
+
} catch { clear(); }
|
|
76
|
+
};
|
|
77
|
+
mount.port.start(); mount.port.postMessage(JSON.stringify({ type: 'ready', nonce: mount.nonce }));
|
|
78
|
+
onMount(request);
|
|
79
|
+
} catch { clear(); }
|
|
80
|
+
}
|
|
81
|
+
window.addEventListener('message', receive);
|
|
82
|
+
return Object.freeze({ request(intent) {
|
|
83
|
+
if (!active) return false;
|
|
84
|
+
const sequence = ++active.sequence;
|
|
85
|
+
const request = { ...active.request, requestId: crypto.randomUUID(), ...(intent ? { schema: 'atelier-component-intent/proposal-v1', intentRef: intent.ref, payload: intent.payload } : {}) };
|
|
86
|
+
parseRequest(JSON.stringify(request)); active.pending = request;
|
|
87
|
+
active.port.postMessage(JSON.stringify({ type: 'request', nonce: active.nonce, sequence, request: JSON.stringify(request) })); return true;
|
|
88
|
+
}, dispose() { clear(); window.removeEventListener('message', receive); } });
|
|
89
|
+
}
|
package/src/project/config.mjs
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import fs from 'node:fs'
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
import { spawnSync } from 'node:child_process'
|
|
4
|
+
import { sanitizedGitEnvironment } from '../runtime/git-adapter.mjs'
|
|
5
|
+
import { parseProjectOptions } from '../cli/project-options.mjs'
|
|
4
6
|
|
|
5
7
|
export const PROJECT_CONFIG_ARG_PREFIX = '--project-config='
|
|
6
8
|
export const PROJECT_CONFIG_ENV = 'MNSTRY_ATELIER_PROJECT_CONFIG'
|
|
@@ -94,14 +96,7 @@ export function writeJson(file, value) {
|
|
|
94
96
|
}
|
|
95
97
|
|
|
96
98
|
export function projectConfigArg(argv = process.argv.slice(2), prefix = PROJECT_CONFIG_ARG_PREFIX) {
|
|
97
|
-
|
|
98
|
-
const arg = argv[index]
|
|
99
|
-
if (typeof arg !== 'string') continue
|
|
100
|
-
if (arg.startsWith(prefix)) return arg.slice(prefix.length).trim() || null
|
|
101
|
-
if ((arg === '--project-config' || arg === '--project') && argv[index + 1]) return String(argv[index + 1]).trim() || null
|
|
102
|
-
if (arg.startsWith('--project=')) return arg.slice('--project='.length).trim() || null
|
|
103
|
-
}
|
|
104
|
-
return null
|
|
99
|
+
return parseProjectOptions(argv, prefix).project
|
|
105
100
|
}
|
|
106
101
|
|
|
107
102
|
export function stripProjectConfigArgs(argv = [], prefix = PROJECT_CONFIG_ARG_PREFIX) {
|
|
@@ -170,22 +165,7 @@ export function readLocalOverlay({ configDir, env = process.env, cwd = process.c
|
|
|
170
165
|
}
|
|
171
166
|
|
|
172
167
|
export function parseRepoPathOverrides(argv = process.argv.slice(2), cwd = process.cwd()) {
|
|
173
|
-
|
|
174
|
-
for (let index = 0; index < argv.length; index += 1) {
|
|
175
|
-
const arg = argv[index]
|
|
176
|
-
let value = null
|
|
177
|
-
if (arg === '--repo-path' && argv[index + 1]) {
|
|
178
|
-
value = argv[index + 1]
|
|
179
|
-
index += 1
|
|
180
|
-
} else if (typeof arg === 'string' && arg.startsWith('--repo-path=')) {
|
|
181
|
-
value = arg.slice('--repo-path='.length)
|
|
182
|
-
}
|
|
183
|
-
if (!value || !value.includes('=')) continue
|
|
184
|
-
const [name, ...rest] = value.split('=')
|
|
185
|
-
const repoPath = rest.join('=')
|
|
186
|
-
if (firstString(name) && firstString(repoPath)) out.set(name.trim(), resolvePathValue(repoPath, cwd))
|
|
187
|
-
}
|
|
188
|
-
return out
|
|
168
|
+
return new Map([...parseProjectOptions(argv).repoPaths].map(([name, value]) => [name, resolvePathValue(value, cwd)]))
|
|
189
169
|
}
|
|
190
170
|
|
|
191
171
|
function overlayRepoPath(overlay, repoName) {
|
|
@@ -195,25 +175,25 @@ function overlayRepoPath(overlay, repoName) {
|
|
|
195
175
|
return firstString(direct, repo.path, repo.localPath)
|
|
196
176
|
}
|
|
197
177
|
|
|
198
|
-
function resolveRepoPath({ repo, repoName, configDir, workspaceRoot, overlay, cliRepoPaths }) {
|
|
178
|
+
function resolveRepoPath({ repo, repoName, configDir, workspaceRoot, overlay, cliRepoPaths, gitExecutable, env }) {
|
|
199
179
|
if (repoName && cliRepoPaths.has(repoName)) return cliRepoPaths.get(repoName)
|
|
200
180
|
const overlayPath = overlayRepoPath(overlay, repoName)
|
|
201
181
|
const fromOverlay = resolvePathValue(overlayPath, configDir)
|
|
202
182
|
if (fromOverlay) return fromOverlay
|
|
203
183
|
const fromConfig = resolvePathValue(repo.path, configDir) || resolvePathValue(repo.path, workspaceRoot)
|
|
204
184
|
if (fromConfig) return fromConfig
|
|
205
|
-
return discoverSiblingRepoPath({ repo, repoName, configDir, workspaceRoot })
|
|
185
|
+
return discoverSiblingRepoPath({ repo, repoName, configDir, workspaceRoot, gitExecutable, env })
|
|
206
186
|
}
|
|
207
187
|
|
|
208
|
-
function repoPathSource({ repo, repoName, overlay, cliRepoPaths, workspaceRoot, configDir }) {
|
|
188
|
+
function repoPathSource({ repo, repoName, overlay, cliRepoPaths, workspaceRoot, configDir, gitExecutable, env }) {
|
|
209
189
|
if (repoName && cliRepoPaths.has(repoName)) return 'cli'
|
|
210
190
|
if (overlayRepoPath(overlay, repoName)) return 'local-overlay'
|
|
211
191
|
if (firstString(repo.path)) return 'tracked-config'
|
|
212
|
-
if (discoverSiblingRepoPath({ repo, repoName, configDir, workspaceRoot })) return 'sibling-discovery'
|
|
192
|
+
if (discoverSiblingRepoPath({ repo, repoName, configDir, workspaceRoot, gitExecutable, env })) return 'sibling-discovery'
|
|
213
193
|
return null
|
|
214
194
|
}
|
|
215
195
|
|
|
216
|
-
function discoverSiblingRepoPath({ repo, repoName, configDir, workspaceRoot }) {
|
|
196
|
+
function discoverSiblingRepoPath({ repo, repoName, configDir, workspaceRoot, gitExecutable = 'git', env = process.env }) {
|
|
217
197
|
if (!repoName) return null
|
|
218
198
|
const candidates = [
|
|
219
199
|
path.join(workspaceRoot, repoName),
|
|
@@ -222,14 +202,14 @@ function discoverSiblingRepoPath({ repo, repoName, configDir, workspaceRoot }) {
|
|
|
222
202
|
]
|
|
223
203
|
for (const candidate of candidates) {
|
|
224
204
|
if (!fs.existsSync(candidate)) continue
|
|
225
|
-
if (repo.remote && !gitRemoteMatches(candidate, repo.remote)) continue
|
|
205
|
+
if (repo.remote && !gitRemoteMatches(candidate, repo.remote, { gitExecutable, env })) continue
|
|
226
206
|
return path.resolve(candidate)
|
|
227
207
|
}
|
|
228
208
|
return null
|
|
229
209
|
}
|
|
230
210
|
|
|
231
|
-
function gitRemoteMatches(repoPath, expected) {
|
|
232
|
-
const result = spawnSync(
|
|
211
|
+
function gitRemoteMatches(repoPath, expected, { gitExecutable = 'git', env = process.env } = {}) {
|
|
212
|
+
const result = spawnSync(gitExecutable, ['-C', repoPath, 'remote', 'get-url', 'origin'], { encoding: 'utf8', env: sanitizedGitEnvironment(env) })
|
|
233
213
|
if (result.status !== 0) return false
|
|
234
214
|
const actual = normalizeRemote(result.stdout.trim())
|
|
235
215
|
return actual === normalizeRemote(expected)
|
|
@@ -243,9 +223,9 @@ function normalizeRemote(value) {
|
|
|
243
223
|
.toLowerCase()
|
|
244
224
|
}
|
|
245
225
|
|
|
246
|
-
export function gitRemoteUrl(repoPath) {
|
|
226
|
+
export function gitRemoteUrl(repoPath, { gitExecutable = 'git', env = process.env } = {}) {
|
|
247
227
|
if (!repoPath || !fs.existsSync(repoPath)) return null
|
|
248
|
-
const result = spawnSync(
|
|
228
|
+
const result = spawnSync(gitExecutable, ['-C', repoPath, 'remote', 'get-url', 'origin'], { encoding: 'utf8', env: sanitizedGitEnvironment(env) })
|
|
249
229
|
return result.status === 0 ? firstString(result.stdout) : null
|
|
250
230
|
}
|
|
251
231
|
|
|
@@ -268,7 +248,9 @@ export function resolveProjectConfig({
|
|
|
268
248
|
env = process.env,
|
|
269
249
|
cwd = process.cwd(),
|
|
270
250
|
configArgPrefix = PROJECT_CONFIG_ARG_PREFIX,
|
|
251
|
+
gitExecutable = 'git',
|
|
271
252
|
configEnv = PROJECT_CONFIG_ENV,
|
|
253
|
+
writeLocalState = true,
|
|
272
254
|
defaults = {},
|
|
273
255
|
} = {}) {
|
|
274
256
|
const argPath = projectConfigArg(argv, configArgPrefix)
|
|
@@ -373,7 +355,7 @@ export function resolveProjectConfig({
|
|
|
373
355
|
localOverlay,
|
|
374
356
|
repos: (Array.isArray(config.repos) ? config.repos : []).map((repo) => {
|
|
375
357
|
const repoName = firstString(repo.name) || (repo.path ? path.basename(repo.path) : null)
|
|
376
|
-
const repoPath = resolveRepoPath({ repo, repoName, configDir, workspaceRoot, overlay: localOverlay.overlay, cliRepoPaths })
|
|
358
|
+
const repoPath = resolveRepoPath({ repo, repoName, configDir, workspaceRoot, overlay: localOverlay.overlay, cliRepoPaths, gitExecutable, env })
|
|
377
359
|
const external = isExternalRepo(repo)
|
|
378
360
|
return {
|
|
379
361
|
...repo,
|
|
@@ -381,16 +363,16 @@ export function resolveProjectConfig({
|
|
|
381
363
|
path: repoPath,
|
|
382
364
|
external,
|
|
383
365
|
readBoundary: external ? null : firstString(repo.readBoundary) || 'team',
|
|
384
|
-
pathSource: repoPath ? repoPathSource({ repo, repoName, overlay: localOverlay.overlay, cliRepoPaths, workspaceRoot, configDir }) : null,
|
|
366
|
+
pathSource: repoPath ? repoPathSource({ repo, repoName, overlay: localOverlay.overlay, cliRepoPaths, workspaceRoot, configDir, gitExecutable, env }) : null,
|
|
385
367
|
}
|
|
386
368
|
}),
|
|
387
369
|
}
|
|
388
|
-
resolved.localState = ensureLocalState(resolved, { write:
|
|
370
|
+
resolved.localState = ensureLocalState(resolved, { write: writeLocalState, gitExecutable, env })
|
|
389
371
|
return resolved
|
|
390
372
|
}
|
|
391
373
|
|
|
392
|
-
export function commandProject({ argv = process.argv.slice(2), env = process.env, cwd = process.cwd() } = {}) {
|
|
393
|
-
const project = resolveProjectConfig({ argv, env, cwd })
|
|
374
|
+
export function commandProject({ argv = process.argv.slice(2), env = process.env, cwd = process.cwd(), gitExecutable = 'git', writeLocalState = true } = {}) {
|
|
375
|
+
const project = resolveProjectConfig({ argv, env, cwd, gitExecutable, writeLocalState })
|
|
394
376
|
// Fail closed at CLI entry, but only when a real config file was loaded; the
|
|
395
377
|
// defaults/no-file path resolves with an empty config that would spuriously
|
|
396
378
|
// fail document validation.
|
|
@@ -419,35 +401,35 @@ export function localStateRoot(projectOrDir) {
|
|
|
419
401
|
return path.join(configDir || process.cwd(), LOCAL_STATE_DIR)
|
|
420
402
|
}
|
|
421
403
|
|
|
422
|
-
function gitRootFor(dir) {
|
|
423
|
-
const result = spawnSync(
|
|
404
|
+
function gitRootFor(dir, { gitExecutable = 'git', env = process.env } = {}) {
|
|
405
|
+
const result = spawnSync(gitExecutable, ['-C', dir, 'rev-parse', '--show-toplevel'], { encoding: 'utf8', env: sanitizedGitEnvironment(env) })
|
|
424
406
|
return result.status === 0 ? result.stdout.trim() : null
|
|
425
407
|
}
|
|
426
408
|
|
|
427
|
-
function gitPrefixFor(dir) {
|
|
428
|
-
const result = spawnSync(
|
|
409
|
+
function gitPrefixFor(dir, { gitExecutable = 'git', env = process.env } = {}) {
|
|
410
|
+
const result = spawnSync(gitExecutable, ['-C', dir, 'rev-parse', '--show-prefix'], { encoding: 'utf8', env: sanitizedGitEnvironment(env) })
|
|
429
411
|
return result.status === 0 ? result.stdout.trim() : null
|
|
430
412
|
}
|
|
431
413
|
|
|
432
|
-
function isIgnoredByGit(gitRoot, rel) {
|
|
433
|
-
const result = spawnSync(
|
|
414
|
+
function isIgnoredByGit(gitRoot, rel, { gitExecutable = 'git', env = process.env } = {}) {
|
|
415
|
+
const result = spawnSync(gitExecutable, ['-C', gitRoot, 'check-ignore', '-q', rel], { encoding: 'utf8', env: sanitizedGitEnvironment(env) })
|
|
434
416
|
return result.status === 0
|
|
435
417
|
}
|
|
436
418
|
|
|
437
|
-
export function ensureLocalState(project, { write = false } = {}) {
|
|
419
|
+
export function ensureLocalState(project, { write = false, gitExecutable = 'git', env = process.env } = {}) {
|
|
438
420
|
const root = localStateRoot(project)
|
|
439
|
-
const gitRoot = gitRootFor(project.configDir)
|
|
421
|
+
const gitRoot = gitRootFor(project.configDir, { gitExecutable, env })
|
|
440
422
|
// Git owns the repository-relative spelling. Deriving it by comparing
|
|
441
423
|
// filesystem paths breaks when Windows exposes the cwd through an 8.3 short
|
|
442
424
|
// name (RUNNER~1) but Git reports the same root through its long name.
|
|
443
|
-
const gitPrefix = gitRoot ? gitPrefixFor(project.configDir) : null
|
|
425
|
+
const gitPrefix = gitRoot ? gitPrefixFor(project.configDir, { gitExecutable, env }) : null
|
|
444
426
|
const rel = gitRoot && gitPrefix !== null ? `${gitPrefix}${LOCAL_STATE_DIR}` : LOCAL_STATE_DIR
|
|
445
427
|
// A directory-only ignore rule such as `.atelier-local/` is not evaluated
|
|
446
428
|
// consistently by Git for an absent directory on every host. Probe a
|
|
447
429
|
// hypothetical child as well: it proves the directory rule before Atelier
|
|
448
430
|
// creates any local state, including on Git for Windows.
|
|
449
431
|
const ignored = gitRoot && gitPrefix !== null
|
|
450
|
-
? isIgnoredByGit(gitRoot, `${rel}/.atelier-ignore-probe
|
|
432
|
+
? isIgnoredByGit(gitRoot, `${rel}/.atelier-ignore-probe`, { gitExecutable, env }) || isIgnoredByGit(gitRoot, `${rel}/`, { gitExecutable, env }) || isIgnoredByGit(gitRoot, rel, { gitExecutable, env })
|
|
451
433
|
: gitRoot === null
|
|
452
434
|
const report = {
|
|
453
435
|
root,
|
|
@@ -502,6 +484,19 @@ function extErrors(value, label) {
|
|
|
502
484
|
.map(([key]) => `${label}.${key} must be an object; ext members are namespaced objects`)
|
|
503
485
|
}
|
|
504
486
|
|
|
487
|
+
// Hands one namespaced ext member to its structured consumer, uninterpreted.
|
|
488
|
+
// The project validator above deliberately does not look inside a member, so
|
|
489
|
+
// the consumer validates it against its own closed contract and fails closed.
|
|
490
|
+
// `present` separates "no such member" (the consumer's feature is simply not
|
|
491
|
+
// configured) from a member that exists with any value at all, including a
|
|
492
|
+
// malformed one, which the consumer must refuse rather than ignore.
|
|
493
|
+
export function projectExtMember(project, key) {
|
|
494
|
+
const ext = project?.config?.ext
|
|
495
|
+
if (ext == null) return { present: false, value: undefined }
|
|
496
|
+
if (typeof ext !== 'object' || Array.isArray(ext)) return { present: true, value: undefined }
|
|
497
|
+
return Object.hasOwn(ext, key) ? { present: true, value: ext[key] } : { present: false, value: undefined }
|
|
498
|
+
}
|
|
499
|
+
|
|
505
500
|
export function validateProjectConfigDoc(doc, { neutralTemplate = false } = {}) {
|
|
506
501
|
const errors = []
|
|
507
502
|
if (!doc || typeof doc !== 'object' || Array.isArray(doc)) return ['project config must be a JSON object']
|
|
@@ -563,6 +558,7 @@ export function validateProjectConfigDoc(doc, { neutralTemplate = false } = {})
|
|
|
563
558
|
}
|
|
564
559
|
if (alignment.appRepo != null && !firstString(alignment.appRepo)) errors.push('alignment.appRepo must be a non-empty string')
|
|
565
560
|
|
|
561
|
+
const repoNames = new Set()
|
|
566
562
|
for (const [index, repo] of (Array.isArray(doc.repos) ? doc.repos : []).entries()) {
|
|
567
563
|
if (!repo || typeof repo !== 'object' || Array.isArray(repo)) {
|
|
568
564
|
errors.push(`repos[${index}] must be an object`)
|
|
@@ -571,6 +567,11 @@ export function validateProjectConfigDoc(doc, { neutralTemplate = false } = {})
|
|
|
571
567
|
errors.push(...unknownKeyErrors(repo, `repos[${index}]`, new Set(['name', 'path', 'readBoundary', 'role', 'kind', 'remote', 'identity', 'aliases', 'required', 'ext'])))
|
|
572
568
|
errors.push(...extErrors(repo.ext, `repos[${index}].ext`))
|
|
573
569
|
if (!firstString(repo.name)) errors.push(`repos[${index}].name is required`)
|
|
570
|
+
else {
|
|
571
|
+
const key = repo.name.toLowerCase()
|
|
572
|
+
if (repoNames.has(key)) errors.push(`repos[${index}].name duplicates another repository name`)
|
|
573
|
+
repoNames.add(key)
|
|
574
|
+
}
|
|
574
575
|
if (repo.identity != null) {
|
|
575
576
|
const identity = asObject(repo.identity)
|
|
576
577
|
errors.push(...unknownKeyErrors(identity, `repos[${index}].identity`, new Set(['provider', 'id', 'ext'])))
|