@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
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
import { validReviewArtifact } from './review-contracts.mjs'
|
|
3
|
+
import { createCollaborationEventLedger } from './event-ledger.mjs'
|
|
4
|
+
import {
|
|
5
|
+
loadBoundRun,
|
|
6
|
+
currentRunEligibility,
|
|
7
|
+
hashEvidence,
|
|
8
|
+
readBoundedSource,
|
|
9
|
+
} from '../readiness-protocols/evidence.mjs'
|
|
10
|
+
import { buildGraph } from '../graph/graph.mjs'
|
|
11
|
+
import { readRegularTextNoFollow } from '../project/private-state.mjs'
|
|
12
|
+
|
|
13
|
+
const idPattern = /^[a-zA-Z0-9][a-zA-Z0-9:._-]{0,159}$/
|
|
14
|
+
const text = (value, max) =>
|
|
15
|
+
typeof value === 'string' && value.trim().length > 0 && value.length <= max
|
|
16
|
+
const invalid = (error, status = 400) => ({ ok: false, status, error })
|
|
17
|
+
|
|
18
|
+
export const contributionKey = (input) =>
|
|
19
|
+
hashEvidence({
|
|
20
|
+
kind: input.kind,
|
|
21
|
+
targetId: input.targetId,
|
|
22
|
+
runId: input.runId ?? null,
|
|
23
|
+
repo: input.repo ?? null,
|
|
24
|
+
path: input.path ?? null,
|
|
25
|
+
reader: input.kind === 'position' ? input.reviewer : null,
|
|
26
|
+
})
|
|
27
|
+
export function validContribution(record) {
|
|
28
|
+
return (
|
|
29
|
+
validReviewArtifact('record', record) &&
|
|
30
|
+
record.inputDigest === hashEvidence(record.input) &&
|
|
31
|
+
record.key === contributionKey(record.input) &&
|
|
32
|
+
record.version === record.input.expectedVersion + 1 &&
|
|
33
|
+
record.identity.name === record.input.reviewer
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function createReviewStore(project) {
|
|
38
|
+
const ledger = createCollaborationEventLedger({
|
|
39
|
+
workspaceRoot: project.configDir,
|
|
40
|
+
ledgerPath: path.join(
|
|
41
|
+
project.configDir,
|
|
42
|
+
'.atelier-local/review/contributions.ndjson',
|
|
43
|
+
),
|
|
44
|
+
})
|
|
45
|
+
function records() {
|
|
46
|
+
const all = ledger.readAll()
|
|
47
|
+
if (!all.ok) return all
|
|
48
|
+
const result = all.events.map((event) => event.payload)
|
|
49
|
+
const versions = new Map(),
|
|
50
|
+
requests = new Set()
|
|
51
|
+
for (let index = 0; index < result.length; index++) {
|
|
52
|
+
const record = result[index],
|
|
53
|
+
event = all.events[index]
|
|
54
|
+
if (
|
|
55
|
+
!validContribution(record) ||
|
|
56
|
+
requests.has(record.input.requestId) ||
|
|
57
|
+
event.aggregateId !== `contribution:${record.input.requestId}` ||
|
|
58
|
+
event.type !== 'contribution-recorded' ||
|
|
59
|
+
event.actor !== record.input.reviewer ||
|
|
60
|
+
record.version !== (versions.get(record.key) ?? 0) + 1
|
|
61
|
+
)
|
|
62
|
+
return invalid('contribution history is invalid', 422)
|
|
63
|
+
requests.add(record.input.requestId)
|
|
64
|
+
versions.set(record.key, record.version)
|
|
65
|
+
}
|
|
66
|
+
return { ok: true, status: 200, records: result }
|
|
67
|
+
}
|
|
68
|
+
function contribution(input) {
|
|
69
|
+
if (!input || typeof input !== 'object' || Array.isArray(input))
|
|
70
|
+
return invalid('contribution must be an object')
|
|
71
|
+
const keys = [
|
|
72
|
+
'requestId',
|
|
73
|
+
'kind',
|
|
74
|
+
'targetId',
|
|
75
|
+
'expectedVersion',
|
|
76
|
+
'reviewer',
|
|
77
|
+
'rationale',
|
|
78
|
+
'runId',
|
|
79
|
+
'evidenceDigest',
|
|
80
|
+
'decision',
|
|
81
|
+
'revision',
|
|
82
|
+
'repo',
|
|
83
|
+
'path',
|
|
84
|
+
'documentDigest',
|
|
85
|
+
'anchor',
|
|
86
|
+
'responseType',
|
|
87
|
+
'wording',
|
|
88
|
+
'position',
|
|
89
|
+
'ext',
|
|
90
|
+
]
|
|
91
|
+
if (Object.keys(input).some((key) => !keys.includes(key)))
|
|
92
|
+
return invalid('unknown contribution field')
|
|
93
|
+
if (
|
|
94
|
+
!idPattern.test(input.requestId ?? '') ||
|
|
95
|
+
!idPattern.test(input.targetId ?? '') ||
|
|
96
|
+
!Number.isSafeInteger(input.expectedVersion) ||
|
|
97
|
+
input.expectedVersion < 0 ||
|
|
98
|
+
!text(input.reviewer, 160) ||
|
|
99
|
+
input.reviewer !== input.reviewer.trim()
|
|
100
|
+
)
|
|
101
|
+
return invalid(
|
|
102
|
+
'request identity, expected version and reviewer are required',
|
|
103
|
+
)
|
|
104
|
+
if (!['decision', 'response', 'position'].includes(input.kind))
|
|
105
|
+
return invalid('unsupported contribution kind')
|
|
106
|
+
if (
|
|
107
|
+
input.kind === 'decision' &&
|
|
108
|
+
(!['accepted', 'rejected', 'revised'].includes(input.decision) ||
|
|
109
|
+
!text(input.rationale, 8000) ||
|
|
110
|
+
!text(input.runId, 160) ||
|
|
111
|
+
!/^sha256:[a-f0-9]{64}$/.test(input.evidenceDigest ?? '') ||
|
|
112
|
+
(input.decision === 'revised' && !text(input.revision, 8000)))
|
|
113
|
+
)
|
|
114
|
+
return invalid('decision, rationale and evidence identity are required')
|
|
115
|
+
if (
|
|
116
|
+
input.kind !== 'decision' &&
|
|
117
|
+
(!text(input.repo, 160) ||
|
|
118
|
+
!text(input.path, 2048) ||
|
|
119
|
+
!/^sha256:[a-f0-9]{64}$/.test(input.documentDigest ?? '') ||
|
|
120
|
+
!text(input.anchor, 8000))
|
|
121
|
+
)
|
|
122
|
+
return invalid('document identity and passage anchor are required')
|
|
123
|
+
if (
|
|
124
|
+
input.kind === 'response' &&
|
|
125
|
+
(!['question', 'correction', 'discussion'].includes(input.responseType) ||
|
|
126
|
+
!text(input.wording, 16000))
|
|
127
|
+
)
|
|
128
|
+
return invalid('response type and original wording are required')
|
|
129
|
+
if (
|
|
130
|
+
input.kind === 'position' &&
|
|
131
|
+
(!Number.isSafeInteger(input.position) || input.position < 0)
|
|
132
|
+
)
|
|
133
|
+
return invalid('reading position is required')
|
|
134
|
+
if (!validReviewArtifact('input', input))
|
|
135
|
+
return invalid(
|
|
136
|
+
'contribution contract invalid or contains fields for a different kind',
|
|
137
|
+
)
|
|
138
|
+
const inputDigest = hashEvidence(input)
|
|
139
|
+
const history = records()
|
|
140
|
+
if (!history.ok) return history
|
|
141
|
+
const previous = history.records.find(
|
|
142
|
+
(record) => record.input.requestId === input.requestId,
|
|
143
|
+
)
|
|
144
|
+
if (previous)
|
|
145
|
+
return previous.inputDigest === inputDigest
|
|
146
|
+
? { ok: true, status: 200, record: previous, replayed: true }
|
|
147
|
+
: invalid('request ID reused with different content', 409)
|
|
148
|
+
const key = contributionKey(input)
|
|
149
|
+
const payload = {
|
|
150
|
+
schema: 'atelier-contribution@v1',
|
|
151
|
+
input,
|
|
152
|
+
inputDigest,
|
|
153
|
+
key,
|
|
154
|
+
version: input.expectedVersion + 1,
|
|
155
|
+
identity: { kind: 'local-asserted', name: input.reviewer },
|
|
156
|
+
recordedAt: new Date().toISOString(),
|
|
157
|
+
}
|
|
158
|
+
const appended = ledger.append({
|
|
159
|
+
aggregateId: `contribution:${input.requestId}`,
|
|
160
|
+
expectedVersion: 0,
|
|
161
|
+
type: 'contribution-recorded',
|
|
162
|
+
actor: input.reviewer,
|
|
163
|
+
payload,
|
|
164
|
+
precondition: () => {
|
|
165
|
+
const current = records()
|
|
166
|
+
if (!current.ok) return current
|
|
167
|
+
const count = current.records.filter(
|
|
168
|
+
(record) => record.key === key,
|
|
169
|
+
).length
|
|
170
|
+
if (count !== input.expectedVersion)
|
|
171
|
+
return invalid('stale contribution version', 409)
|
|
172
|
+
if (input.kind === 'decision') {
|
|
173
|
+
const bound = loadBoundRun(project, input.runId)
|
|
174
|
+
if (!bound.ok) return bound
|
|
175
|
+
if (
|
|
176
|
+
input.decision === 'accepted' &&
|
|
177
|
+
(bound.run.blockers.length ||
|
|
178
|
+
bound.snapshot.claimEvidence[
|
|
179
|
+
bound.run.claims.findIndex(
|
|
180
|
+
(claim) => claim.claimId === input.targetId,
|
|
181
|
+
)
|
|
182
|
+
]?.status !== 'linked-source')
|
|
183
|
+
)
|
|
184
|
+
return invalid(
|
|
185
|
+
'required input or mapped source evidence is incomplete; acceptance refused',
|
|
186
|
+
409,
|
|
187
|
+
)
|
|
188
|
+
if (
|
|
189
|
+
bound.snapshot.digest !== input.evidenceDigest ||
|
|
190
|
+
!bound.run.claims.some(
|
|
191
|
+
(claim) => claim.claimId === input.targetId,
|
|
192
|
+
) ||
|
|
193
|
+
!currentRunEligibility(project, bound)
|
|
194
|
+
)
|
|
195
|
+
return invalid('claim evidence changed or is unavailable', 409)
|
|
196
|
+
} else {
|
|
197
|
+
const source = document(input.repo, input.path)
|
|
198
|
+
if (!source.ok) return source
|
|
199
|
+
if (
|
|
200
|
+
source.digest !== input.documentDigest ||
|
|
201
|
+
!source.text.includes(input.anchor)
|
|
202
|
+
)
|
|
203
|
+
return invalid(
|
|
204
|
+
'document changed or anchor is missing; reassociation required',
|
|
205
|
+
409,
|
|
206
|
+
)
|
|
207
|
+
if (
|
|
208
|
+
input.kind === 'position' &&
|
|
209
|
+
(input.position > source.text.length ||
|
|
210
|
+
!source.text.slice(input.position).startsWith(input.anchor))
|
|
211
|
+
)
|
|
212
|
+
return invalid('position does not match the anchored passage')
|
|
213
|
+
}
|
|
214
|
+
return { ok: true }
|
|
215
|
+
},
|
|
216
|
+
})
|
|
217
|
+
if (!appended.ok) {
|
|
218
|
+
if (appended.status === 409) {
|
|
219
|
+
const latest = records()
|
|
220
|
+
const duplicate =
|
|
221
|
+
latest.ok &&
|
|
222
|
+
latest.records.find(
|
|
223
|
+
(record) => record.input.requestId === input.requestId,
|
|
224
|
+
)
|
|
225
|
+
if (duplicate?.inputDigest === inputDigest)
|
|
226
|
+
return { ok: true, status: 200, record: duplicate, replayed: true }
|
|
227
|
+
}
|
|
228
|
+
return appended
|
|
229
|
+
}
|
|
230
|
+
return { ok: true, status: 200, record: payload, replayed: false }
|
|
231
|
+
}
|
|
232
|
+
function document(repoName, relative) {
|
|
233
|
+
const repo = project.repos.find(
|
|
234
|
+
(entry) => entry.name === repoName && !entry.external,
|
|
235
|
+
)
|
|
236
|
+
if (
|
|
237
|
+
!repo?.path ||
|
|
238
|
+
!/\.(md|txt)$/i.test(relative ?? '') ||
|
|
239
|
+
relative.split(/[\\/]/).some((segment) => segment.startsWith('.'))
|
|
240
|
+
)
|
|
241
|
+
return invalid('document is outside the review surface', 403)
|
|
242
|
+
try {
|
|
243
|
+
if (
|
|
244
|
+
project.configPath &&
|
|
245
|
+
hashEvidence(
|
|
246
|
+
JSON.parse(readRegularTextNoFollow(project.configPath)),
|
|
247
|
+
) !== hashEvidence(project.config)
|
|
248
|
+
)
|
|
249
|
+
return invalid('project changed; restart review', 409)
|
|
250
|
+
const graph = buildGraph(project)
|
|
251
|
+
if (
|
|
252
|
+
graph.errors.length ||
|
|
253
|
+
!graph.nodes.some(
|
|
254
|
+
(node) => node.repo === repoName && node.path === relative,
|
|
255
|
+
)
|
|
256
|
+
)
|
|
257
|
+
return invalid('document is not in the valid source graph', 403)
|
|
258
|
+
return {
|
|
259
|
+
ok: true,
|
|
260
|
+
status: 200,
|
|
261
|
+
...readBoundedSource(repo.path, relative),
|
|
262
|
+
}
|
|
263
|
+
} catch {
|
|
264
|
+
return invalid('document unavailable or unsafe', 409)
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
function handoff(requestId) {
|
|
268
|
+
const all = records()
|
|
269
|
+
if (!all.ok) return all
|
|
270
|
+
const record = all.records.find(
|
|
271
|
+
(entry) =>
|
|
272
|
+
entry.input.requestId === requestId &&
|
|
273
|
+
entry.input.kind === 'decision' &&
|
|
274
|
+
entry.input.decision === 'accepted',
|
|
275
|
+
)
|
|
276
|
+
if (!record) return invalid('accepted decision unavailable', 404)
|
|
277
|
+
const bound = loadBoundRun(project, record.input.runId)
|
|
278
|
+
if (!bound.ok) return bound
|
|
279
|
+
const latest = all.records
|
|
280
|
+
.filter((entry) => entry.key === record.key)
|
|
281
|
+
.at(-1)
|
|
282
|
+
return {
|
|
283
|
+
ok: true,
|
|
284
|
+
status: 200,
|
|
285
|
+
schema: 'atelier-owner-handoff@v1',
|
|
286
|
+
decision: record,
|
|
287
|
+
claim: bound.run.claims.find(
|
|
288
|
+
(claim) => claim.claimId === record.input.targetId,
|
|
289
|
+
),
|
|
290
|
+
requiredEvidenceDigest: bound.snapshot.digest,
|
|
291
|
+
current:
|
|
292
|
+
latest.input.requestId === requestId &&
|
|
293
|
+
currentRunEligibility(project, bound),
|
|
294
|
+
supersededBy:
|
|
295
|
+
latest.input.requestId === requestId ? null : latest.input.requestId,
|
|
296
|
+
sourceChangesApplied: false,
|
|
297
|
+
promotion: 'not-established',
|
|
298
|
+
instructions:
|
|
299
|
+
'Source owner must review the intended edit against current source and record application separately.',
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
// One immutable event per aggregate means generic compaction retains every
|
|
303
|
+
// contribution. Per-target versions are checked inside the ledger write lock.
|
|
304
|
+
return { records, contribute: contribution, document, handoff }
|
|
305
|
+
}
|
|
@@ -37,7 +37,7 @@ Subcommands:
|
|
|
37
37
|
|
|
38
38
|
keygen --key-id ID [--algorithm ed25519|es256] [--out FILE]
|
|
39
39
|
Generate a signing key pair. Writes the private key file (default
|
|
40
|
-
${LOCAL_KEY_FILE}, mode 0600, refuses to overwrite)
|
|
40
|
+
${LOCAL_KEY_FILE}, mode 0600 on POSIX, refuses to overwrite)
|
|
41
41
|
and prints only the public key document.
|
|
42
42
|
|
|
43
43
|
Exit codes: 0 success or valid, 1 verify judged the attestation invalid,
|
|
@@ -162,11 +162,24 @@ function runKeygen(argv) {
|
|
|
162
162
|
fail(`--algorithm must be ed25519 or es256, got ${algorithm}`)
|
|
163
163
|
}
|
|
164
164
|
const outPath = path.resolve(options.out ?? LOCAL_KEY_FILE)
|
|
165
|
+
// On Windows, O_EXCL alone can still follow a planted dangling symlink.
|
|
166
|
+
// lstat examines the directory entry itself, so refuse every existing entry
|
|
167
|
+
// (including a dangling link) before the exclusive create. The subsequent
|
|
168
|
+
// 'wx' retains the atomic no-overwrite guarantee for ordinary files created
|
|
169
|
+
// after this check.
|
|
170
|
+
try {
|
|
171
|
+
fs.lstatSync(outPath)
|
|
172
|
+
fail(`refusing to write signing key file: path already exists or is a symlink: ${outPath}`)
|
|
173
|
+
} catch (error) {
|
|
174
|
+
if (error?.code !== 'ENOENT') {
|
|
175
|
+
if (error?.code === undefined) throw error
|
|
176
|
+
fail(`cannot inspect signing key file path: ${outPath}`)
|
|
177
|
+
}
|
|
178
|
+
}
|
|
165
179
|
const { privateKeyDoc, publicKeyDoc } = generateKeyPair({ algorithm, keyId })
|
|
166
|
-
// Atomic create-exclusive ('wx' = O_CREAT|O_EXCL, mode 0600):
|
|
167
|
-
//
|
|
168
|
-
//
|
|
169
|
-
// there is no check-then-write race window. Error output stays path-only.
|
|
180
|
+
// Atomic create-exclusive ('wx' = O_CREAT|O_EXCL, mode 0600 on POSIX): an
|
|
181
|
+
// ordinary competing create cannot overwrite the destination. Error output
|
|
182
|
+
// stays path-only.
|
|
170
183
|
let fd = null
|
|
171
184
|
try {
|
|
172
185
|
fd = fs.openSync(outPath, 'wx', 0o600)
|
|
@@ -181,7 +194,8 @@ function runKeygen(argv) {
|
|
|
181
194
|
} finally {
|
|
182
195
|
fs.closeSync(fd)
|
|
183
196
|
}
|
|
184
|
-
|
|
197
|
+
const permissions = process.platform === 'win32' ? 'containing-directory Windows ACLs inherited' : 'mode 0600'
|
|
198
|
+
console.error(`signing key file written to ${outPath} (${permissions}). Keep it out of version control; ${LOCAL_KEY_FILE} is gitignored by default.`)
|
|
185
199
|
console.log(JSON.stringify(publicKeyDoc, null, 2))
|
|
186
200
|
}
|
|
187
201
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { createCoauthorStore } from '../coauthor/store.mjs';
|
|
2
|
+
|
|
3
|
+
const usage = 'Usage: atelier coauthor start|read|event|recover\nRead one JSON request from stdin (maximum 1 MiB). Start: {"config":...}; read/recover: {"sessionId":...}; event: {"sessionId":...,"event":...}. Workspace is the current directory. Saves are private drafts, not source edits.';
|
|
4
|
+
const command = process.argv[2];
|
|
5
|
+
if (command === '--help' || command === 'help') { console.log(usage); process.exit(0); }
|
|
6
|
+
try {
|
|
7
|
+
if (!['start', 'read', 'event', 'recover'].includes(command) || process.argv.length !== 3) throw new Error(usage);
|
|
8
|
+
const chunks = [];
|
|
9
|
+
let count = 0;
|
|
10
|
+
for await (const chunk of process.stdin) {
|
|
11
|
+
count += chunk.length;
|
|
12
|
+
if (count > 1024 * 1024) throw new Error('request exceeds byte ceiling');
|
|
13
|
+
chunks.push(chunk);
|
|
14
|
+
}
|
|
15
|
+
const input = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(Buffer.concat(chunks)));
|
|
16
|
+
if (!input || typeof input !== 'object' || Array.isArray(input)) throw new Error('object required');
|
|
17
|
+
const allowed = command === 'start' ? ['config'] : command === 'event' ? ['sessionId', 'event'] : ['sessionId'];
|
|
18
|
+
if (Object.keys(input).some(key => !allowed.includes(key)) || allowed.some(key => !Object.hasOwn(input, key))) throw new Error('invalid request fields');
|
|
19
|
+
const store = createCoauthorStore();
|
|
20
|
+
const state = command === 'start' ? store.start(input.config) : command === 'read' ? store.read(input.sessionId)
|
|
21
|
+
: command === 'recover' ? store.recover(input.sessionId) : store.dispatch(input.sessionId, input.event);
|
|
22
|
+
console.log(JSON.stringify({ ok: true, state, savedMeaning: 'private-draft-only' }));
|
|
23
|
+
} catch (error) {
|
|
24
|
+
console.error(JSON.stringify({ ok: false, error: error.message }));
|
|
25
|
+
process.exitCode = 1;
|
|
26
|
+
}
|
package/src/commands/config.mjs
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { commandProject } from '../project/config.mjs'
|
|
3
3
|
const project = commandProject()
|
|
4
|
-
|
|
4
|
+
const report = process.argv.includes('--explain')
|
|
5
|
+
? { ok: true, schema: project.schema, configSource: project.source, repos: project.repos.map((repo) => ({
|
|
6
|
+
name: repo.name, pathSource: repo.pathSource, resolved: repo.path !== null, readBoundary: repo.readBoundary, external: repo.external,
|
|
7
|
+
})) }
|
|
8
|
+
: { ok: true, schema: project.schema, configPath: project.configPath, repos: project.repos.map((repo) => repo.name) }
|
|
9
|
+
console.log(JSON.stringify(report, null, 2))
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
import { pathToFileURL } from 'node:url'
|
|
4
|
+
import {
|
|
5
|
+
compileDisclosurePatterns,
|
|
6
|
+
isPathIgnored,
|
|
7
|
+
isPathTracked,
|
|
8
|
+
scanDisclosureContent,
|
|
9
|
+
} from '../disclosure/content-scan.mjs'
|
|
10
|
+
|
|
11
|
+
export function runDisclosureCommand(argv = process.argv.slice(2), { env = process.env, stdout = console.log, stderr = console.error } = {}) {
|
|
12
|
+
let root = process.cwd()
|
|
13
|
+
let staged = false
|
|
14
|
+
let structuralOnly = false
|
|
15
|
+
let untrusted = false
|
|
16
|
+
let failOnBinary = false
|
|
17
|
+
let denylistPath = null
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
21
|
+
const arg = argv[index]
|
|
22
|
+
if (arg === 'check') continue
|
|
23
|
+
if (arg === '--root') {
|
|
24
|
+
root = requiredValue(argv, ++index, '--root')
|
|
25
|
+
} else if (arg === '--denylist') {
|
|
26
|
+
denylistPath = requiredValue(argv, ++index, '--denylist')
|
|
27
|
+
} else if (arg === '--staged') {
|
|
28
|
+
staged = true
|
|
29
|
+
} else if (arg === '--structural-only') {
|
|
30
|
+
structuralOnly = true
|
|
31
|
+
} else if (arg === '--untrusted') {
|
|
32
|
+
untrusted = true
|
|
33
|
+
} else if (arg === '--fail-on-binary') {
|
|
34
|
+
failOnBinary = true
|
|
35
|
+
} else {
|
|
36
|
+
return usageError(stderr, `unknown argument: ${arg}`)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
} catch (error) {
|
|
40
|
+
return usageError(stderr, error.message)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const resolvedRoot = path.resolve(root)
|
|
44
|
+
let denylistPatterns = []
|
|
45
|
+
if (!structuralOnly) {
|
|
46
|
+
const denylist = loadDenylist({ root: resolvedRoot, denylistPath, env, stderr })
|
|
47
|
+
if (!denylist.ok) return 2
|
|
48
|
+
try {
|
|
49
|
+
denylistPatterns = compileDisclosurePatterns(denylist.document.patterns)
|
|
50
|
+
} catch (error) {
|
|
51
|
+
stderr(`[disclosure:check] ${error.message}`)
|
|
52
|
+
return 2
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let report
|
|
57
|
+
try {
|
|
58
|
+
report = scanDisclosureContent({
|
|
59
|
+
root: resolvedRoot,
|
|
60
|
+
staged,
|
|
61
|
+
denylistPatterns,
|
|
62
|
+
failOnBinary,
|
|
63
|
+
})
|
|
64
|
+
} catch (error) {
|
|
65
|
+
stderr(`[disclosure:check] ${error.message}`)
|
|
66
|
+
return 2
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (!report.ok) {
|
|
70
|
+
if (untrusted) {
|
|
71
|
+
stderr('[disclosure:check] findings present (details suppressed: untrusted tree)')
|
|
72
|
+
} else {
|
|
73
|
+
for (const finding of report.findings) {
|
|
74
|
+
const location = finding.line === null ? finding.path : `${finding.path}:${finding.line}`
|
|
75
|
+
stderr(`[disclosure:check] ${finding.label}: ${location}`)
|
|
76
|
+
}
|
|
77
|
+
stderr(`[disclosure:check] ${report.findings.length} finding(s)`)
|
|
78
|
+
}
|
|
79
|
+
return 1
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const binaryNote = report.skippedBinary.length > 0
|
|
83
|
+
? `; ${report.skippedBinary.length} binary file(s) skipped${failOnBinary ? '' : ' (use --fail-on-binary for public text-only surfaces)'}`
|
|
84
|
+
: ''
|
|
85
|
+
stdout(`[disclosure:check] clean: ${report.scannedFiles} ${staged ? 'staged' : 'tracked'} text file(s)${binaryNote}`)
|
|
86
|
+
return 0
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function loadDenylist({ root, denylistPath, env, stderr }) {
|
|
90
|
+
if (env.ATELIER_DENYLIST_JSON) {
|
|
91
|
+
try {
|
|
92
|
+
return { ok: true, document: JSON.parse(env.ATELIER_DENYLIST_JSON) }
|
|
93
|
+
} catch {
|
|
94
|
+
stderr('[disclosure:check] ATELIER_DENYLIST_JSON is not valid JSON')
|
|
95
|
+
return { ok: false }
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const candidate = path.resolve(root, denylistPath ?? '.atelier-local/disclosure-denylist.json')
|
|
100
|
+
if (!fs.existsSync(candidate)) {
|
|
101
|
+
stderr('[disclosure:check] private denylist unavailable; provide ATELIER_DENYLIST_JSON, --denylist FILE, or pass --structural-only explicitly')
|
|
102
|
+
return { ok: false }
|
|
103
|
+
}
|
|
104
|
+
if (isPathTracked(root, candidate)) {
|
|
105
|
+
stderr('[disclosure:check] refusing a Git-tracked private denylist')
|
|
106
|
+
return { ok: false }
|
|
107
|
+
}
|
|
108
|
+
if (path.resolve(candidate).startsWith(`${path.resolve(root)}${path.sep}`) && !isPathIgnored(root, candidate)) {
|
|
109
|
+
stderr('[disclosure:check] a repository-local private denylist must be covered by .gitignore')
|
|
110
|
+
return { ok: false }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
return { ok: true, document: JSON.parse(fs.readFileSync(candidate, 'utf8')) }
|
|
115
|
+
} catch {
|
|
116
|
+
stderr('[disclosure:check] private denylist is not valid JSON')
|
|
117
|
+
return { ok: false }
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function requiredValue(argv, index, flag) {
|
|
122
|
+
if (!argv[index]) throw new Error(`${flag} requires a value`)
|
|
123
|
+
return argv[index]
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function usageError(stderr, message) {
|
|
127
|
+
stderr(`[disclosure:check] ${message}`)
|
|
128
|
+
return 2
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
132
|
+
process.exitCode = runDisclosureCommand()
|
|
133
|
+
}
|
|
@@ -22,7 +22,8 @@ const logError = (message) => console.error(`${PREFIX} ${message}`)
|
|
|
22
22
|
|
|
23
23
|
// Paths in output stay relative to the checked distribution so logs never
|
|
24
24
|
// leak machine-local absolute paths.
|
|
25
|
-
const
|
|
25
|
+
const portablePath = (file) => file.split(path.sep).join('/')
|
|
26
|
+
const displayPath = (target, file) => portablePath(path.relative(target, file) || path.basename(file))
|
|
26
27
|
|
|
27
28
|
function checkReadmeAttribution(target) {
|
|
28
29
|
const readmePath = path.join(target, 'README.md')
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// atelier extension-pack validate|list
|
|
3
3
|
//
|
|
4
|
-
//
|
|
5
|
-
// be invoked directly: node src/commands/extension-pack.mjs <subcommand> ...
|
|
4
|
+
// Shared project options also work when this module is invoked directly.
|
|
6
5
|
//
|
|
7
6
|
// Exit codes: 0 every enabled pack loads clean (warnings do not fail),
|
|
8
7
|
// 1 an enabled pack failed to load, 2 usage or project resolution error.
|
|
@@ -13,6 +12,7 @@
|
|
|
13
12
|
import path from 'node:path'
|
|
14
13
|
import { ATELIER_EXT_NAMESPACE, loadExtensionPacks } from '../extension-packs/loader.mjs'
|
|
15
14
|
import { asObject, commandProject } from '../project/config.mjs'
|
|
15
|
+
import { PROJECT_OPTION_SPEC, projectOptionAt } from '../cli/project-options.mjs'
|
|
16
16
|
|
|
17
17
|
const REPORT_SCHEMA = 'mnstry.atelier-extension-pack-report@v1'
|
|
18
18
|
const LIST_SCHEMA = 'mnstry.atelier-extension-pack-list@v1'
|
|
@@ -35,6 +35,10 @@ ext["${ATELIER_EXT_NAMESPACE}"].extensionPacks. Pack protocols are resolvable
|
|
|
35
35
|
by full namespaced id only; the machine-local overlay can disable a declared
|
|
36
36
|
pack but can never enable an undeclared one.
|
|
37
37
|
|
|
38
|
+
Project options: --project PATH (alias --project-config PATH) and repeated
|
|
39
|
+
--repo-path NAME=PATH also accept --flag=value. Repo overrides resolve from
|
|
40
|
+
the current directory and do not declare new repos or widen read boundaries.
|
|
41
|
+
|
|
38
42
|
Exit codes: 0 every enabled pack loads clean (warnings do not fail),
|
|
39
43
|
1 an enabled pack failed to load, 2 usage or project resolution error.`
|
|
40
44
|
|
|
@@ -43,10 +47,10 @@ function fail(message) {
|
|
|
43
47
|
process.exit(2)
|
|
44
48
|
}
|
|
45
49
|
|
|
46
|
-
//
|
|
50
|
+
// Shared project options (both --flag VALUE and --flag=VALUE forms) are
|
|
47
51
|
// recognized here for the unknown-option check only; commandProject reads the
|
|
48
52
|
// same process.argv itself, so the values need no forwarding.
|
|
49
|
-
const OPTION_SPEC = { json: 'flag',
|
|
53
|
+
const OPTION_SPEC = { json: 'flag', ...PROJECT_OPTION_SPEC }
|
|
50
54
|
|
|
51
55
|
function parseOptions(argv) {
|
|
52
56
|
const options = { json: false }
|
|
@@ -60,12 +64,15 @@ function parseOptions(argv) {
|
|
|
60
64
|
fail(`unexpected argument: ${arg}\n\n${USAGE}`)
|
|
61
65
|
}
|
|
62
66
|
const name = arg.slice(2).split('=')[0]
|
|
63
|
-
if (!(name
|
|
67
|
+
if (!Object.hasOwn(OPTION_SPEC, name)) fail(`unknown option --${name}\n\n${USAGE}`)
|
|
64
68
|
if (OPTION_SPEC[name] === 'flag') {
|
|
65
69
|
options[name] = true
|
|
66
|
-
} else
|
|
67
|
-
|
|
68
|
-
|
|
70
|
+
} else {
|
|
71
|
+
try {
|
|
72
|
+
i = projectOptionAt(argv, i).nextIndex - 1
|
|
73
|
+
} catch (error) {
|
|
74
|
+
fail(error.message)
|
|
75
|
+
}
|
|
69
76
|
}
|
|
70
77
|
}
|
|
71
78
|
return options
|
|
@@ -86,6 +93,7 @@ function declaredEntries(project) {
|
|
|
86
93
|
}
|
|
87
94
|
|
|
88
95
|
const LOCK_MISMATCH_PATTERN = /^lock (version|digest) mismatch/
|
|
96
|
+
const portablePath = (file) => file.split(path.sep).join('/')
|
|
89
97
|
|
|
90
98
|
// One row per declared entry, in declaration order, joining the loader report
|
|
91
99
|
// back onto the declaration. Loader messages without a usable packId (project-
|
|
@@ -107,7 +115,7 @@ function buildRows(project, result) {
|
|
|
107
115
|
// (same rationale as distribution.mjs) so neither text nor --json output
|
|
108
116
|
// ever leaks machine-local absolute paths.
|
|
109
117
|
const resolvedPath = record?.path ?? (declaredPath ? path.resolve(project.configDir, declaredPath) : null)
|
|
110
|
-
const displayPath = resolvedPath ? path.relative(project.configDir, resolvedPath) || '.' : null
|
|
118
|
+
const displayPath = resolvedPath ? portablePath(path.relative(project.configDir, resolvedPath) || '.') : null
|
|
111
119
|
return {
|
|
112
120
|
id: label,
|
|
113
121
|
status,
|