@mnstry/atelier 0.2.0-alpha.1 → 0.2.0-alpha.11
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 +413 -0
- package/README.md +402 -205
- 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/contracts/public-api-baseline.json +57 -0
- package/docs/assurance-controls.md +49 -0
- package/docs/atelier-runtime.md +28 -2
- package/docs/atelier-sync.md +172 -0
- package/docs/blocks/audience-visibility.md +9 -0
- package/docs/blocks/claims.md +61 -0
- package/docs/blocks/conformance-admission.md +15 -0
- package/docs/blocks/will-not-do.md +18 -0
- package/docs/coauthor-session.md +57 -0
- package/docs/connected-composition.md +75 -0
- package/docs/continuity.md +3 -4
- package/docs/decisions.md +167 -0
- package/docs/design.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 +99 -16
- 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/knowledge-graph.md +8 -4
- package/docs/local-review.md +84 -0
- package/docs/local-services.md +328 -0
- package/docs/local-state.md +107 -0
- package/docs/obsidian-contract.md +698 -0
- package/docs/obsidian.md +484 -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 +174 -12
- package/docs/repo-boundary-guard.md +12 -2
- package/docs/review-portability.md +55 -0
- package/docs/skill-steward.md +156 -0
- package/docs/upgrade.md +155 -5
- 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/projects/sample-workspace/content/source.html.kg.json +4 -1
- package/fixtures/projects/source-formats-workspace/content/data.json.kg.json +4 -1
- package/fixtures/projects/source-formats-workspace/content/logo.png.kg.json +4 -1
- package/fixtures/projects/source-formats-workspace/content/metrics.csv.kg.json +4 -1
- package/fixtures/projects/source-formats-workspace/content/pipeline.yaml.kg.json +4 -1
- package/fixtures/ui/presentation/pane.v1.json +7 -0
- package/fixtures/ui/presentation/reference.v1.json +293 -0
- package/package.json +86 -10
- package/skills/claude/atelier-guided-coauthor/SKILL.md +49 -0
- package/skills/claude/atelier-guided-upgrade/SKILL.md +97 -0
- package/skills/claude/atelier-local-service/SKILL.md +47 -0
- package/skills/claude/atelier-open/SKILL.md +11 -5
- package/skills/claude/atelier-public-boundary/SKILL.md +31 -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-local-service/SKILL.md +47 -0
- package/skills/codex/atelier-open/SKILL.md +11 -5
- package/skills/codex/atelier-public-boundary/SKILL.md +31 -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 +283 -20
- package/src/boundary/policy.mjs +213 -91
- package/src/cli/execute-command.mjs +36 -0
- package/src/cli/project-options.mjs +58 -0
- package/src/cli/run.mjs +78 -10
- package/src/coauthor/session.mjs +161 -0
- package/src/coauthor/store.mjs +173 -0
- package/src/collaboration/event-ledger.mjs +348 -0
- package/src/collaboration/index.mjs +17 -0
- package/src/collaboration/inspection-bundle.mjs +223 -0
- package/src/collaboration/proposals.mjs +265 -65
- package/src/collaboration/review-contracts.mjs +25 -0
- package/src/collaboration/review-store.mjs +305 -0
- package/src/commands/attestation.mjs +20 -6
- package/src/commands/coauthor.mjs +26 -0
- package/src/commands/config.mjs +6 -1
- package/src/commands/disclosure.mjs +133 -0
- package/src/commands/distribution.mjs +2 -1
- package/src/commands/extension-pack.mjs +17 -9
- package/src/commands/init.mjs +46 -6
- package/src/commands/obsidian.mjs +371 -0
- package/src/commands/review.mjs +147 -0
- package/src/commands/server.mjs +1 -4
- 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 +206 -0
- package/src/egress/check.mjs +7 -38
- package/src/egress/forbidden-egress.mjs +32 -18
- package/src/export/atelier-export-contract.mjs +16 -1
- package/src/extension-packs/lifecycle.mjs +154 -0
- package/src/graph/graph.mjs +176 -316
- package/src/graph/knowledge-graph.mjs +643 -43
- package/src/guides/contracts.mjs +54 -0
- package/src/harness/context-client.mjs +9 -1
- package/src/index.mjs +41 -0
- package/src/intake/store.mjs +119 -0
- package/src/preview/channel.mjs +89 -0
- package/src/project/config.mjs +112 -52
- package/src/project/durable-state.mjs +161 -0
- package/src/project/file-class.mjs +180 -0
- package/src/project/package-root.mjs +10 -0
- package/src/project/path-match.mjs +38 -15
- package/src/project/private-state.mjs +120 -0
- 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 +212 -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 +138 -0
- package/src/runtime/obsidian/app-production-seams.mjs +107 -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 +280 -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-entry-path.mjs +9 -0
- package/src/runtime/obsidian/service-main.mjs +93 -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 +125 -59
- package/src/server/security.mjs +89 -4
- package/src/server/server.mjs +4 -2
- package/src/skills/steward.mjs +780 -0
- package/src/support/feedback-report.mjs +4 -3
- 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 +65 -29
- 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
package/src/cli/run.mjs
CHANGED
|
@@ -51,11 +51,18 @@ export const commandMap = new Map([
|
|
|
51
51
|
['extension-pack:list', ['src/commands/extension-pack.mjs', 'list']],
|
|
52
52
|
['distribution', ['src/commands/distribution.mjs']],
|
|
53
53
|
['distribution:check', ['src/commands/distribution.mjs', 'check']],
|
|
54
|
+
['disclosure', ['src/commands/disclosure.mjs']],
|
|
55
|
+
['disclosure:check', ['src/commands/disclosure.mjs', 'check']],
|
|
54
56
|
['attestation', ['src/commands/attestation.mjs']],
|
|
55
57
|
['feedback', ['src/commands/feedback.mjs']],
|
|
56
58
|
['feedback:check', ['src/commands/feedback.mjs', 'check']],
|
|
57
59
|
['announcements', ['src/commands/announcements.mjs']],
|
|
58
60
|
['announcements:list', ['src/commands/announcements.mjs', 'list']],
|
|
61
|
+
['sync', ['src/commands/sync.mjs']],
|
|
62
|
+
['review', ['src/commands/review.mjs']],
|
|
63
|
+
['coauthor', ['src/commands/coauthor.mjs']],
|
|
64
|
+
['skills', ['src/commands/skills.mjs']],
|
|
65
|
+
['obsidian', ['src/commands/obsidian.mjs']],
|
|
59
66
|
])
|
|
60
67
|
|
|
61
68
|
function isDefaultBrand(brand) {
|
|
@@ -113,18 +120,35 @@ Core commands:
|
|
|
113
120
|
support bundle --dry-run Preview a no-send support bundle.
|
|
114
121
|
feedback --message TEXT Write a local, never-sent feedback report.
|
|
115
122
|
announcements list List and verify MNSTRY announcements.
|
|
123
|
+
sync enroll Explicitly enroll one repository for supervision.
|
|
124
|
+
sync status Fully observe the enrolled repository.
|
|
125
|
+
sync reconcile Fetch and perform only proven fast-forwards.
|
|
126
|
+
sync plan Prepare one bounded, reviewable commit plan.
|
|
127
|
+
sync commit Execute an exactly confirmed commit plan.
|
|
128
|
+
sync run --once Run one full-state supervisor cycle.
|
|
129
|
+
obsidian status|open|apply Maintain and open Obsidian views of the corpus; apply edits made in them.
|
|
116
130
|
egress check Check extracted Atelier paths for forbidden egress.
|
|
117
131
|
boundary check Enforce private/shared repo placement rules.
|
|
118
132
|
boundary audit Report content-rule matches tree-wide without blocking.
|
|
119
133
|
boundary install-hooks Install staged/private-domain Git guards.
|
|
120
134
|
promote Record a git.promote disclosure event.
|
|
121
|
-
upgrade --
|
|
135
|
+
upgrade plan --save Save an exact local candidate plan.
|
|
136
|
+
upgrade explain --plan Explain saved changes and consent without writes.
|
|
137
|
+
upgrade apply --plan --confirm Apply the selected plan through commit hooks.
|
|
138
|
+
upgrade status|recover Inspect receipts or preview recovery.
|
|
139
|
+
upgrade --dry-run Plan a legacy package/template upgrade.
|
|
122
140
|
upgrade --apply Apply a branch-based reviewable upgrade.
|
|
123
141
|
lock check|write Verify or create atelier.lock.json.
|
|
142
|
+
lock provenance Inspect actual installed package identity.
|
|
143
|
+
review run|history|handoff Evidence-bound local human review.
|
|
144
|
+
review export|inspect|packs Preview inspection bundles and pack lifecycle.
|
|
145
|
+
coauthor start|read|event|recover Save and resume private authoring drafts.
|
|
146
|
+
skills audit|observe|candidates|sync Audit and manage local skill projections.
|
|
124
147
|
config check Validate project config.
|
|
125
148
|
extension-pack validate Validate declared extension packs.
|
|
126
149
|
extension-pack list List declared extension packs.
|
|
127
150
|
distribution check Check a distribution for MNSTRY attribution.
|
|
151
|
+
disclosure check Scan tracked or staged content for disclosure risks.
|
|
128
152
|
|
|
129
153
|
Attestation commands:
|
|
130
154
|
attestation hash FILE Print the canonical payload hash of a payload.
|
|
@@ -132,15 +156,27 @@ Attestation commands:
|
|
|
132
156
|
attestation verify FILE Verify an attestation against a public key file.
|
|
133
157
|
attestation keygen --key-id ID Generate a signing key pair.
|
|
134
158
|
|
|
135
|
-
|
|
136
|
-
|
|
159
|
+
Commands whose usage names --project accept --project=PATH or --project PATH.
|
|
160
|
+
The project resolver also accepts --project-config=PATH and
|
|
161
|
+
MNSTRY_ATELIER_PROJECT_CONFIG=PATH; each command's own help is authoritative.
|
|
162
|
+
Project-aware commands accept repeated --repo-path NAME=PATH or
|
|
163
|
+
--repo-path=NAME=PATH. Overrides change location, never declared read authority.
|
|
164
|
+
Run config check --explain for path-free resolution sources.
|
|
165
|
+
Machine-local repo paths belong in
|
|
137
166
|
.atelier-local/, atelier.local.json, or atelier.workspace.local.json.`
|
|
138
167
|
}
|
|
139
168
|
|
|
140
169
|
export function buildCommandHelpText(command, brand = DEFAULT_BRAND) {
|
|
141
170
|
const c = brand.command
|
|
142
171
|
const help = {
|
|
143
|
-
|
|
172
|
+
skills: `Usage: ${c} skills audit|observe|candidates|sync
|
|
173
|
+
Audit bundled skills or --root DIR [--peer DIR].
|
|
174
|
+
Sync previews by default; applying requires --apply --confirm PLAN_DIGEST.
|
|
175
|
+
Observations are local metadata, never automatic telemetry.`,
|
|
176
|
+
coauthor: `Usage: ${c} coauthor start|read|event|recover
|
|
177
|
+
|
|
178
|
+
Read one JSON request from stdin (maximum 1 MiB). Start takes {"config":{"id":"SESSION","fields":[{"id":"FIELD","source":{"ref":"packet.md","digest":"SHA256"}}]}}. Read/recover take {"sessionId":"SESSION"}. Event takes {"sessionId":"SESSION","event":{"id":"UNIQUE","expectedRevision":0,"type":"answer","text":"ANSWER"}}. Run from the intended Git workspace with ignored .atelier-local/. Saves are private drafts, never canonical source edits.`,
|
|
179
|
+
init: `Usage: ${c} init [--template private-domain|shared-project|sample-workspace|distribution|external-project] [--target DIR] [--actor ID]
|
|
144
180
|
|
|
145
181
|
Creates tracked starter files and an Atelier lockfile. It does not install hooks unless asked separately. An unrecognized --template exits 1 and writes nothing; omit --template for the blank scaffold.`,
|
|
146
182
|
adopt: `Usage: ${c} adopt [--profile single-repo|private-domain|shared-project|multi-repo|monorepo|control-workspace] [--target DIR] [--yes]
|
|
@@ -152,9 +188,9 @@ Ensures ignored local Atelier state exists, verifies ignore coverage, and record
|
|
|
152
188
|
doctor: `Usage: ${c} doctor [--project ./atelier.project.json] [--fix] [--dry-run]
|
|
153
189
|
|
|
154
190
|
Reports project config, local overlay, and repo boundary readiness. --fix only repairs ignored local state.`,
|
|
155
|
-
boundary: `Usage: ${c} boundary check|push-check|audit|install-hooks [--project ./atelier.project.json] [--staged]
|
|
191
|
+
boundary: `Usage: ${c} boundary check|push-check|audit|install-hooks [--project ./atelier.project.json] [--staged] [--source=working-tree|head]
|
|
156
192
|
|
|
157
|
-
check --staged judges the staged diff. push-check reads pre-push ref updates on stdin and judges only the pushed range. audit scans the
|
|
193
|
+
check --staged judges the staged diff. push-check reads pre-push ref updates on stdin and judges only the pushed range. audit scans the working tree by default and reports without blocking; --source=head selects the committed snapshot.`,
|
|
158
194
|
graph: `Usage: ${c} graph [--check] [--project ./atelier.project.json]
|
|
159
195
|
|
|
160
196
|
Builds or checks the project knowledge graph from tracked sources plus ignored local path bindings.`,
|
|
@@ -171,25 +207,55 @@ Runs the bundled MNSTRY readiness pack claim-first. Protocol state and packet dr
|
|
|
171
207
|
${c} extension-pack validate [--json] [--project ./atelier.project.json]
|
|
172
208
|
${c} extension-pack list [--json] [--project ./atelier.project.json]
|
|
173
209
|
|
|
174
|
-
Loads every extension pack declared under ext["mnstry.atelier"].extensionPacks in the tracked project config and reports one line per pack. Packs load additively alongside the bundled MNSTRY protocols and can never replace them. list is the default subcommand.`,
|
|
210
|
+
Loads every extension pack declared under ext["mnstry.atelier"].extensionPacks in the tracked project config and reports one line per pack. Packs load additively alongside the bundled MNSTRY protocols and can never replace them. list is the default subcommand. Shared --project-config and repeated --repo-path NAME=PATH options are supported in both space and equals forms.`,
|
|
175
211
|
distribution: `Usage: ${c} distribution check [--target DIR] [--pack DIR]
|
|
176
212
|
|
|
177
213
|
Checks a distribution package for the required MNSTRY attribution markers. Blocking: the distribution README.md byte check, and a CLI probe that EXECUTES the target's declared bin with --version (spawned with the current Node, cwd set to the target — only run this against distributions you trust) and requires the attribution in its output; a target that looks like a distribution but declares no probe-able bin, or ships a malformed package.json, is also blocking. The extension-pack manifest attribution key is advisory and reported only. The normative wording lives in TRADEMARKS.md under "Required attribution"; see also docs/attestation.md and docs/distributions.md.`,
|
|
214
|
+
disclosure: `Usage: ${c} disclosure check [--root DIR] [--staged] [--denylist FILE | --structural-only] [--fail-on-binary] [--untrusted]
|
|
215
|
+
|
|
216
|
+
Scans Git-tracked files, or staged index blobs with --staged, without following symlinks. A private denylist is required by default and must be supplied through ATELIER_DENYLIST_JSON, --denylist, or ignored .atelier-local/disclosure-denylist.json. --structural-only is the explicit no-denylist lane. --untrusted suppresses finding details.`,
|
|
178
217
|
attestation: `Usage:
|
|
179
218
|
${c} attestation hash <payload.json>
|
|
180
219
|
${c} attestation sign <attestation.json> [--key FILE] [--out FILE]
|
|
181
220
|
${c} attestation verify <attestation.json> --public-key FILE [--payload FILE] [--json]
|
|
182
221
|
${c} attestation keygen --key-id ID [--algorithm ed25519|es256] [--out FILE]
|
|
183
222
|
|
|
184
|
-
Records and checks admission decisions. hash prints the canonical payload hash (RFC 8785 JCS, SHA-256). sign reads the local signing key file. verify reads a public key file and exits 1 when it judges the attestation invalid. keygen writes the signing key file mode 0600, refuses to overwrite, and prints only the public key document.`,
|
|
223
|
+
Records and checks admission decisions. hash prints the canonical payload hash (RFC 8785 JCS, SHA-256). sign reads the local signing key file. verify reads a public key file and exits 1 when it judges the attestation invalid. keygen writes the signing key file mode 0600 on POSIX, refuses to overwrite, and prints only the public key document.`,
|
|
185
224
|
feedback: `Usage:
|
|
186
225
|
${c} feedback create --message TEXT | --message-file PATH [--context FILE] [--include-gates]
|
|
187
226
|
${c} feedback check FILE
|
|
188
227
|
|
|
189
|
-
Assembles a local feedback report under ignored .atelier-local/feedback/ (mode 0600), scanned with the support-bundle banned key and value patterns before writing; any match refuses the write naming pattern label and location only. Files given to --message-file and --context must be valid UTF-8 text of at most 262144 bytes. The scan is a backstop, not clearance: read the whole report before sharing it. The kit has no send path — sharing the file is always the user's own explicit act.`,
|
|
228
|
+
Assembles a local feedback report under ignored .atelier-local/feedback/ (mode 0600 on POSIX), scanned with the support-bundle banned key and value patterns before writing; any match refuses the write naming pattern label and location only. Files given to --message-file and --context must be valid UTF-8 text of at most 262144 bytes. The scan is a backstop, not clearance: read the whole report before sharing it. The kit has no send path — sharing the file is always the user's own explicit act.`,
|
|
190
229
|
announcements: `Usage: ${c} announcements list [--dir DIR] [--public-key FILE] | verify <file> [--public-key FILE] [--json] | show <file> [--public-key FILE]
|
|
191
230
|
|
|
192
231
|
MNSTRY announcements are a pull-only channel: signed JSON documents under announcements/ in the repository. The trust anchor is always the committed MNSTRY key, or one you pass explicitly with --public-key; --dir changes only where documents are read from and never which key verifies them. Every run names the key and keyId it used. The kit never fetches anything — receiving announcements is the git pull you chose to run, and show refuses to print a body whose signature does not verify.`,
|
|
232
|
+
obsidian: `Usage: ${c} obsidian status|scope|audience|mode|policy|service|open|apply|conflicts|apply-policy|selection|proposals [--project atelier.project.json] [--data-root DIR] [--json]
|
|
233
|
+
|
|
234
|
+
${c} obsidian status
|
|
235
|
+
${c} obsidian scope list | scope show ID
|
|
236
|
+
${c} obsidian audience show | set A,B | clear
|
|
237
|
+
${c} obsidian mode show | set manual|automatic
|
|
238
|
+
${c} obsidian policy show | install FILE | revoke
|
|
239
|
+
${c} obsidian service start [--consent-actor ID] --adapter=obsidian-cli | status | stop | unit --print --adapter=obsidian-cli
|
|
240
|
+
${c} obsidian open [--scope ID] [--consent-actor ID] [--allow-stale] --adapter=obsidian-cli
|
|
241
|
+
${c} obsidian apply list | show EDIT | run EDIT [--actor ID] | recover
|
|
242
|
+
${c} obsidian conflicts [ID]
|
|
243
|
+
${c} obsidian apply-policy create FILE | show | revoke
|
|
244
|
+
${c} obsidian selection resolve ID | persist ID [allow-empty] | show ID | list
|
|
245
|
+
${c} obsidian proposals list | show OPERATION
|
|
246
|
+
|
|
247
|
+
Noninteractive. With --json exactly one JSON document is printed, for a refusal too. status, scope, audience show, mode show, policy show and service status are read-only. open reports current only when the owned maintenance service ticked after the request, the view's trusted generation is the prepared one and reads back byte for byte, and the installed Obsidian meets the minimum version and answers for that vault; every other outcome is typed and exits 3. Reaching the installed app is never a default: open, service start and service unit need --adapter=obsidian-cli. Audiences, mode and the apply policy are private machine settings, never project configuration and never a note. apply, conflicts, apply-policy, selection and proposals are contributed operations registered from modules shipped under src/runtime/obsidian/contributions/; the built-in names cannot be replaced. While no apply operation is registered every apply reports apply-unavailable. Exit codes: 0 done, 1 internal error, 2 refusal or usage, 3 ran and the answer is not success.`,
|
|
248
|
+
sync: `Usage:
|
|
249
|
+
${c} sync enroll --repo DIR [--project atelier.project.json] [--git ABSOLUTE_PATH]
|
|
250
|
+
${c} sync status --repo DIR
|
|
251
|
+
${c} sync reconcile --repo DIR [--retries 3]
|
|
252
|
+
${c} sync run --repo DIR [--once] [--interval 30]
|
|
253
|
+
${c} sync plan --repo DIR --path FILE [--path FILE] --message TEXT [--publish]
|
|
254
|
+
${c} sync commit --repo DIR --operation ID --confirm ID
|
|
255
|
+
${c} sync pause|freeze|resume --repo DIR
|
|
256
|
+
${c} sync trace --repo DIR
|
|
257
|
+
|
|
258
|
+
Enrolls exactly one repository and keeps Git plus readable files authoritative. Reconciliation observes the complete repository every cycle and performs only fast-forward updates. Commit creation is a two-phase user-confirmed operation: plan shows one bounded change set, and commit refuses unless the repository is unchanged and --confirm exactly repeats the operation id. No semantic conflict resolution, force operation, browser apply endpoint, telemetry, or desktop shell is present.`,
|
|
193
259
|
}
|
|
194
260
|
return help[command] || `Usage: ${c} ${command} [args]\n\nRun ${c} --help for the command list.`
|
|
195
261
|
}
|
|
@@ -219,6 +285,7 @@ function normalizeArgs(argv) {
|
|
|
219
285
|
if (args[0] === 'extension-pack' && args[1] === 'validate') args.splice(0, 2, 'extension-pack:validate')
|
|
220
286
|
if (args[0] === 'extension-pack' && args[1] === 'list') args.splice(0, 2, 'extension-pack:list')
|
|
221
287
|
if (args[0] === 'distribution' && args[1] === 'check') args.splice(0, 2, 'distribution:check')
|
|
288
|
+
if (args[0] === 'disclosure' && args[1] === 'check') args.splice(0, 2, 'disclosure:check')
|
|
222
289
|
if (args[0] === 'support' && args[1] === 'bundle') args.splice(0, 2, 'support:bundle')
|
|
223
290
|
if (args[0] === 'support:bundle' && args[1] === '--dry-run') args.splice(1, 1)
|
|
224
291
|
return { help: false, args }
|
|
@@ -261,12 +328,13 @@ export async function runCli({
|
|
|
261
328
|
|
|
262
329
|
const [script, ...prefixArgs] = target
|
|
263
330
|
const scriptPath = path.join(packageRoot, script)
|
|
331
|
+
const executorPath = path.join(packageRoot, 'src', 'cli', 'execute-command.mjs')
|
|
264
332
|
if (!fs.existsSync(scriptPath)) {
|
|
265
333
|
stderr(`${brand.displayName} command is not available in this package install: ${command}`)
|
|
266
334
|
return 1
|
|
267
335
|
}
|
|
268
336
|
|
|
269
|
-
const result = spawnSync(process.execPath, [scriptPath, ...prefixArgs, ...rest], {
|
|
337
|
+
const result = spawnSync(process.execPath, [executorPath, scriptPath, ...prefixArgs, ...rest], {
|
|
270
338
|
cwd,
|
|
271
339
|
stdio: 'inherit',
|
|
272
340
|
env: {
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { canonicalize } from '../attestation/jcs.mjs';
|
|
3
|
+
|
|
4
|
+
// Experimental adapter seam. This reducer performs no storage or authorization.
|
|
5
|
+
export const COAUTHOR_SESSION_VERSION = 'atelier-coauthor-session/experimental-v1';
|
|
6
|
+
export function contentDigest(text) {
|
|
7
|
+
if (typeof text !== 'string') throw new TypeError('text required');
|
|
8
|
+
if (Buffer.from(text, 'utf8').toString('utf8') !== text) throw new TypeError('text must be lossless UTF-8');
|
|
9
|
+
return createHash('sha256').update(text, 'utf8').digest('hex');
|
|
10
|
+
}
|
|
11
|
+
const digestPattern = /^[a-f0-9]{64}$/;
|
|
12
|
+
function requireText(value, label) {
|
|
13
|
+
if (typeof value !== 'string' || !value.trim() || value.length > 100000) {
|
|
14
|
+
throw new TypeError(`${label} required and bounded`);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
function sourceCheck(source) {
|
|
18
|
+
if (!source || typeof source !== 'object') throw new TypeError('source required');
|
|
19
|
+
requireText(source.ref, 'source ref');
|
|
20
|
+
if (!digestPattern.test(source.digest)) throw new TypeError('source digest required');
|
|
21
|
+
}
|
|
22
|
+
export function createSession({ id, fields }) {
|
|
23
|
+
requireText(id, 'session id');
|
|
24
|
+
if (!Array.isArray(fields) || fields.length === 0 || fields.length > 1000) throw new TypeError('fields required');
|
|
25
|
+
const ids = new Set();
|
|
26
|
+
const targets = fields.map(({ id: fieldId, source }) => {
|
|
27
|
+
requireText(fieldId, 'field id');
|
|
28
|
+
if (ids.has(fieldId)) throw new Error('duplicate field');
|
|
29
|
+
ids.add(fieldId);
|
|
30
|
+
sourceCheck(source);
|
|
31
|
+
return { id: fieldId, source: { ref: source.ref, digest: source.digest } };
|
|
32
|
+
});
|
|
33
|
+
return { version: COAUTHOR_SESSION_VERSION, id, revision: 0, phase: 'input', index: 0,
|
|
34
|
+
fields: targets, answers: [], proposal: null, saved: [], pending: null, events: [] };
|
|
35
|
+
}
|
|
36
|
+
function checkEvent(event) {
|
|
37
|
+
if (!event || typeof event !== 'object' || Array.isArray(event)) throw new TypeError('event required');
|
|
38
|
+
requireText(event.id, 'event id');
|
|
39
|
+
if (!Number.isSafeInteger(event.expectedRevision) || event.expectedRevision < 0) throw new TypeError('revision required');
|
|
40
|
+
const keys = {
|
|
41
|
+
answer: ['text', 'continueAfterSave'], propose: ['text'], confirm: [], reject: [],
|
|
42
|
+
save: [], receipt: ['receipt'], failed: [], retry: [], advance: [], pause: [], resume: [],
|
|
43
|
+
}[event.type];
|
|
44
|
+
if (!keys || Object.keys(event).some(key => !['id', 'type', 'expectedRevision', ...keys].includes(key))) {
|
|
45
|
+
throw new TypeError('unknown event or field');
|
|
46
|
+
}
|
|
47
|
+
if (['answer', 'propose'].includes(event.type)) requireText(event.text, 'answer text');
|
|
48
|
+
if (event.continueAfterSave !== undefined && typeof event.continueAfterSave !== 'boolean') throw new TypeError('continue must be boolean');
|
|
49
|
+
if (event.type === 'receipt') {
|
|
50
|
+
const r = event.receipt;
|
|
51
|
+
if (!r || Object.keys(r).sort().join(',') !== 'fieldId,requestId,sessionId,sourceDigest,sourceRef,valueDigest') throw new TypeError('invalid receipt shape');
|
|
52
|
+
for (const key of ['requestId', 'sessionId', 'fieldId', 'sourceRef']) requireText(r[key], key);
|
|
53
|
+
for (const key of ['sourceDigest', 'valueDigest']) if (!digestPattern.test(r[key])) throw new TypeError('invalid receipt digest');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function need(state, phases) {
|
|
57
|
+
if (!phases.includes(state.phase)) throw new Error(`event refused in ${state.phase}`);
|
|
58
|
+
}
|
|
59
|
+
function writeEffect(state) {
|
|
60
|
+
const field = state.fields[state.index];
|
|
61
|
+
return { type: 'write-field', requestId: state.pending.requestId, sessionId: state.id,
|
|
62
|
+
fieldId: field.id, sourceRef: field.source.ref, sourceDigest: field.source.digest,
|
|
63
|
+
text: state.proposal.text, valueDigest: contentDigest(state.proposal.text) };
|
|
64
|
+
}
|
|
65
|
+
function advance(state) {
|
|
66
|
+
state.index += 1;
|
|
67
|
+
state.phase = state.index === state.fields.length ? 'complete' : 'input';
|
|
68
|
+
state.proposal = null;
|
|
69
|
+
state.pending = null;
|
|
70
|
+
}
|
|
71
|
+
export function transition(session, event) {
|
|
72
|
+
// State is trusted adapter-owned memory. Rehydrate by replaying validated events,
|
|
73
|
+
// not by accepting an arbitrary caller-supplied session snapshot.
|
|
74
|
+
if (session?.version !== COAUTHOR_SESSION_VERSION) throw new Error('unsupported session');
|
|
75
|
+
checkEvent(event);
|
|
76
|
+
const fingerprint = contentDigest(canonicalize(event));
|
|
77
|
+
const prior = session.events.find(item => item.id === event.id);
|
|
78
|
+
if (prior) {
|
|
79
|
+
if (prior.fingerprint !== fingerprint) throw new Error('event id conflict');
|
|
80
|
+
return { session: structuredClone(session), effects: [], duplicate: true };
|
|
81
|
+
}
|
|
82
|
+
if (event.expectedRevision !== session.revision) throw new Error('stale revision');
|
|
83
|
+
const state = structuredClone(session);
|
|
84
|
+
const effects = [];
|
|
85
|
+
switch (event.type) {
|
|
86
|
+
case 'answer':
|
|
87
|
+
need(state, ['input', 'draft']);
|
|
88
|
+
state.answers.push({ eventId: event.id, fieldId: state.fields[state.index].id, text: event.text });
|
|
89
|
+
state.proposal = { text: event.text, originalEventId: event.id, confirmed: true,
|
|
90
|
+
continueAfterSave: event.continueAfterSave ?? false };
|
|
91
|
+
state.phase = 'draft';
|
|
92
|
+
break;
|
|
93
|
+
case 'propose':
|
|
94
|
+
need(state, ['draft']);
|
|
95
|
+
// Every machine revision requires confirmation; no heuristic decides meaning.
|
|
96
|
+
state.proposal.text = event.text;
|
|
97
|
+
state.proposal.confirmed = false;
|
|
98
|
+
state.phase = 'confirmation';
|
|
99
|
+
break;
|
|
100
|
+
case 'confirm':
|
|
101
|
+
need(state, ['confirmation']);
|
|
102
|
+
state.proposal.confirmed = true;
|
|
103
|
+
state.phase = 'draft';
|
|
104
|
+
break;
|
|
105
|
+
case 'reject':
|
|
106
|
+
need(state, ['confirmation']);
|
|
107
|
+
state.proposal.text = state.answers.find(answer => answer.eventId === state.proposal.originalEventId).text;
|
|
108
|
+
state.proposal.confirmed = true;
|
|
109
|
+
state.phase = 'draft';
|
|
110
|
+
break;
|
|
111
|
+
case 'save':
|
|
112
|
+
need(state, ['draft']);
|
|
113
|
+
state.pending = { requestId: event.id, retries: 0 };
|
|
114
|
+
state.phase = 'saving';
|
|
115
|
+
effects.push(writeEffect(state));
|
|
116
|
+
break;
|
|
117
|
+
case 'failed':
|
|
118
|
+
need(state, ['saving']);
|
|
119
|
+
state.phase = 'recovery';
|
|
120
|
+
break;
|
|
121
|
+
case 'retry':
|
|
122
|
+
need(state, ['recovery']);
|
|
123
|
+
if (state.pending.retries >= 1) throw new Error('retry budget exhausted; adapter reconciliation required');
|
|
124
|
+
state.pending.retries += 1;
|
|
125
|
+
state.phase = 'saving';
|
|
126
|
+
effects.push(writeEffect(state));
|
|
127
|
+
break;
|
|
128
|
+
case 'receipt': {
|
|
129
|
+
need(state, ['saving', 'recovery']);
|
|
130
|
+
const expected = writeEffect(state);
|
|
131
|
+
for (const key of ['requestId', 'sessionId', 'fieldId', 'sourceRef', 'sourceDigest', 'valueDigest']) {
|
|
132
|
+
if (event.receipt[key] !== expected[key]) throw new Error(`receipt mismatch: ${key}`);
|
|
133
|
+
}
|
|
134
|
+
state.saved.push({ fieldId: expected.fieldId, text: state.proposal.text, receipt: structuredClone(event.receipt) });
|
|
135
|
+
state.phase = 'saved';
|
|
136
|
+
state.pending = null;
|
|
137
|
+
if (state.proposal.continueAfterSave) advance(state);
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
case 'advance':
|
|
141
|
+
need(state, ['saved']);
|
|
142
|
+
advance(state);
|
|
143
|
+
break;
|
|
144
|
+
case 'pause':
|
|
145
|
+
need(state, ['input', 'draft', 'confirmation', 'saved', 'recovery']);
|
|
146
|
+
state.resumePhase = state.phase;
|
|
147
|
+
state.phase = 'paused';
|
|
148
|
+
break;
|
|
149
|
+
case 'resume':
|
|
150
|
+
need(state, ['paused']);
|
|
151
|
+
state.phase = state.resumePhase;
|
|
152
|
+
delete state.resumePhase;
|
|
153
|
+
break;
|
|
154
|
+
}
|
|
155
|
+
state.revision += 1;
|
|
156
|
+
state.events.push({ id: event.id, fingerprint, event: structuredClone(event) });
|
|
157
|
+
return { session: state, effects, duplicate: false };
|
|
158
|
+
}
|
|
159
|
+
export function replaySession(config, events) {
|
|
160
|
+
return events.reduce((session, event) => transition(session, event).session, createSession(config));
|
|
161
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { withPrivateLock, publishPrivateFile } from '../project/durable-state.mjs';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { execFileSync } from 'node:child_process';
|
|
5
|
+
import { canonicalize } from '../attestation/jcs.mjs';
|
|
6
|
+
import { createCollaborationEventLedger } from '../collaboration/event-ledger.mjs';
|
|
7
|
+
import { ensureContainedPrivateDirectory, openRegularFileNoFollow } from '../project/private-state.mjs';
|
|
8
|
+
import { createSession, transition, contentDigest } from './session.mjs';
|
|
9
|
+
import { validateJsonSchema } from '../export/atelier-export-contract.mjs';
|
|
10
|
+
|
|
11
|
+
const schema = JSON.parse(fs.readFileSync(new URL('../../contracts/atelier-coauthor.v1.schema.json', import.meta.url), 'utf8'));
|
|
12
|
+
function validate(value, kind) {
|
|
13
|
+
const errors = validateJsonSchema({ $schema: schema.$schema, $defs: schema.$defs, $ref: `#/$defs/${kind}` }, value);
|
|
14
|
+
if (errors.length) throw new Error(`invalid coauthor ${kind}`);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const MAX_SOURCE_BYTES = 1024 * 1024;
|
|
18
|
+
function readText(file) {
|
|
19
|
+
const fd = openRegularFileNoFollow(file);
|
|
20
|
+
try {
|
|
21
|
+
if (fs.fstatSync(fd).size > MAX_SOURCE_BYTES) throw new Error('coauthor file exceeds byte limit');
|
|
22
|
+
return new TextDecoder('utf-8', { fatal: true }).decode(fs.readFileSync(fd));
|
|
23
|
+
} finally { fs.closeSync(fd); }
|
|
24
|
+
}
|
|
25
|
+
function sourcePath(root, ref) {
|
|
26
|
+
if (typeof ref !== 'string' || !ref || ref.includes('\\') || path.isAbsolute(ref) || ref.split('/').some(s => !s || s === '.' || s === '..' || s.startsWith('.'))) {
|
|
27
|
+
throw new Error('source must be a visible workspace-relative file');
|
|
28
|
+
}
|
|
29
|
+
let current = root;
|
|
30
|
+
for (const part of ref.split('/')) {
|
|
31
|
+
current = path.join(current, part);
|
|
32
|
+
if (fs.lstatSync(current).isSymbolicLink()) throw new Error('redirected source refused');
|
|
33
|
+
}
|
|
34
|
+
return current;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Local CLI boundary, not an authenticated multi-user service. Responses remain
|
|
38
|
+
// private drafts; this store never modifies a canonical source file.
|
|
39
|
+
export function createCoauthorStore({ workspaceRoot = process.cwd() } = {}) {
|
|
40
|
+
const root = fs.realpathSync(workspaceRoot);
|
|
41
|
+
function privatePlacement() {
|
|
42
|
+
const gitEnv = Object.fromEntries(Object.entries(process.env).filter(([key]) => !key.toUpperCase().startsWith('GIT_')));
|
|
43
|
+
try {
|
|
44
|
+
const tracked = execFileSync('git', ['-C', root, 'ls-files', '-z', '--', '.atelier-local'], { env: gitEnv, encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
|
|
45
|
+
if (tracked) throw new Error('tracked private state');
|
|
46
|
+
execFileSync('git', ['-C', root, 'check-ignore', '--quiet', '.atelier-local/'], { env: gitEnv, stdio: 'ignore' });
|
|
47
|
+
} catch { throw new Error('coauthor requires a Git workspace with untracked, ignored .atelier-local/ state'); }
|
|
48
|
+
}
|
|
49
|
+
privatePlacement();
|
|
50
|
+
const directory = () => ensureContainedPrivateDirectory({ workspaceRoot: root,
|
|
51
|
+
directory: path.join(root, '.atelier-local', 'coauthor'), label: 'coauthor state' });
|
|
52
|
+
const ledger = () => createCollaborationEventLedger({ workspaceRoot: root,
|
|
53
|
+
ledgerPath: path.join(directory(), 'events.ndjson') });
|
|
54
|
+
const aggregate = id => `coauthor-${contentDigest(id)}`;
|
|
55
|
+
function locked(operation) {
|
|
56
|
+
privatePlacement();
|
|
57
|
+
const lockPath = path.join(directory(), 'operation.lock');
|
|
58
|
+
return withPrivateLock(lockPath, operation);
|
|
59
|
+
}
|
|
60
|
+
function load(id) {
|
|
61
|
+
const result = ledger().eventsFor(aggregate(id));
|
|
62
|
+
if (!result.ok) throw new Error(result.error);
|
|
63
|
+
if (!result.events.length) throw new Error('coauthor session not found');
|
|
64
|
+
let state;
|
|
65
|
+
let previous = null;
|
|
66
|
+
let version = 0;
|
|
67
|
+
let config;
|
|
68
|
+
for (const record of result.events) {
|
|
69
|
+
if (record.version !== ++version || record.payload.previousEventId !== previous) throw new Error('coauthor event chain is incomplete');
|
|
70
|
+
if (version === 1) {
|
|
71
|
+
if (record.type !== 'coauthor.started') throw new Error('coauthor start missing');
|
|
72
|
+
config = record.payload.config;
|
|
73
|
+
validate(config, 'config');
|
|
74
|
+
if (config.id !== id) throw new Error('coauthor identity mismatch');
|
|
75
|
+
state = createSession(config);
|
|
76
|
+
} else {
|
|
77
|
+
if (record.type !== 'coauthor.transition') throw new Error('unknown coauthor record');
|
|
78
|
+
const next = transition(state, record.payload.event);
|
|
79
|
+
if (next.duplicate) throw new Error('duplicate persisted transition');
|
|
80
|
+
state = next.session;
|
|
81
|
+
}
|
|
82
|
+
previous = record.id;
|
|
83
|
+
}
|
|
84
|
+
return { state, config, version, previous };
|
|
85
|
+
}
|
|
86
|
+
function verifySources(config) {
|
|
87
|
+
for (const field of config.fields) {
|
|
88
|
+
if (contentDigest(readText(sourcePath(root, field.source.ref))) !== field.source.digest) throw new Error('source changed; preserve history and start a newly bound session');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
function append(id, current, event) {
|
|
92
|
+
const next = transition(current.state, event);
|
|
93
|
+
if (next.duplicate) return current;
|
|
94
|
+
const result = ledger().append({ aggregateId: aggregate(id), expectedVersion: current.version,
|
|
95
|
+
type: 'coauthor.transition', actor: 'local-harness', payload: { previousEventId: current.previous, event } });
|
|
96
|
+
if (!result.ok) throw new Error(result.error);
|
|
97
|
+
return { ...current, state: next.session, version: result.event.version, previous: result.event.id };
|
|
98
|
+
}
|
|
99
|
+
function reconcile(id, current) {
|
|
100
|
+
if (!['saving', 'recovery'].includes(current.state.phase)) return current;
|
|
101
|
+
verifySources(current.config);
|
|
102
|
+
const state = current.state;
|
|
103
|
+
const field = state.fields[state.index];
|
|
104
|
+
const requestId = state.pending.requestId;
|
|
105
|
+
const values = ensureContainedPrivateDirectory({ workspaceRoot: root,
|
|
106
|
+
directory: path.join(directory(), 'values', contentDigest(id)), label: 'coauthor values' });
|
|
107
|
+
const target = path.join(values, `${contentDigest(requestId)}.json`);
|
|
108
|
+
const receipt = { sessionId: id, requestId, fieldId: field.id, sourceRef: field.source.ref,
|
|
109
|
+
sourceDigest: field.source.digest, valueDigest: contentDigest(state.proposal.text) };
|
|
110
|
+
const saved = { schema: 'atelier-coauthor-draft@v1', status: 'draft', text: state.proposal.text, receipt };
|
|
111
|
+
validate(saved, 'draft');
|
|
112
|
+
const bytes = canonicalize(saved);
|
|
113
|
+
try {
|
|
114
|
+
// Existing values are immutable. A retry only verifies identical bytes.
|
|
115
|
+
try {
|
|
116
|
+
fs.lstatSync(target);
|
|
117
|
+
if (readText(target) !== bytes) throw new Error('saved draft differs from pending value');
|
|
118
|
+
} catch (error) {
|
|
119
|
+
if (error.code !== 'ENOENT') throw error;
|
|
120
|
+
publishPrivateFile(target, bytes);
|
|
121
|
+
}
|
|
122
|
+
if (readText(target) !== bytes) throw new Error('saved draft readback mismatch');
|
|
123
|
+
verifySources(current.config);
|
|
124
|
+
} catch (error) {
|
|
125
|
+
if (state.phase === 'saving') append(id, current, {
|
|
126
|
+
id: `failure-${contentDigest(requestId)}-${state.revision}`, expectedRevision: state.revision, type: 'failed',
|
|
127
|
+
});
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
130
|
+
return append(id, current, { id: `receipt-${contentDigest(requestId)}`,
|
|
131
|
+
expectedRevision: state.revision, type: 'receipt', receipt });
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
start(config) {
|
|
135
|
+
return locked(() => {
|
|
136
|
+
validate(config, 'config');
|
|
137
|
+
const clean = createSession(config);
|
|
138
|
+
const canonicalConfig = { id: clean.id, fields: clean.fields };
|
|
139
|
+
verifySources(canonicalConfig);
|
|
140
|
+
const current = ledger().eventsFor(aggregate(clean.id));
|
|
141
|
+
if (!current.ok) throw new Error(current.error);
|
|
142
|
+
if (current.events.length) {
|
|
143
|
+
const existing = load(clean.id);
|
|
144
|
+
if (canonicalize(existing.config) !== canonicalize(canonicalConfig)) throw new Error('session id already bound to another configuration');
|
|
145
|
+
return existing.state;
|
|
146
|
+
}
|
|
147
|
+
const result = ledger().append({ aggregateId: aggregate(clean.id), expectedVersion: 0,
|
|
148
|
+
type: 'coauthor.started', actor: 'local-harness', payload: { previousEventId: null, config: canonicalConfig } });
|
|
149
|
+
if (!result.ok) throw new Error(result.error);
|
|
150
|
+
return load(clean.id).state;
|
|
151
|
+
});
|
|
152
|
+
},
|
|
153
|
+
read(id) { return load(id).state; },
|
|
154
|
+
dispatch(id, event) {
|
|
155
|
+
return locked(() => {
|
|
156
|
+
if (['receipt', 'failed'].includes(event?.type)) throw new Error('receipt and failure events are store-owned');
|
|
157
|
+
const current = load(id);
|
|
158
|
+
verifySources(current.config);
|
|
159
|
+
const next = append(id, current, event);
|
|
160
|
+
// Duplicate historical events never cause repeated effects.
|
|
161
|
+
if (next === current) return next.state;
|
|
162
|
+
return ['save', 'retry'].includes(event.type) ? reconcile(id, next).state : next.state;
|
|
163
|
+
});
|
|
164
|
+
},
|
|
165
|
+
recover(id) { return locked(() => {
|
|
166
|
+
const current = load(id);
|
|
167
|
+
// Paused and exhausted sessions require an explicit resume or operator
|
|
168
|
+
// reconciliation. Never turn a read into another automatic write attempt.
|
|
169
|
+
if (current.state.phase !== 'saving') return current.state;
|
|
170
|
+
return reconcile(id, current).state;
|
|
171
|
+
}); },
|
|
172
|
+
};
|
|
173
|
+
}
|