@mnstry/atelier 0.2.0-alpha.6 → 0.2.0-alpha.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +176 -0
- package/README.md +59 -15
- 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-review.v1.schema.json +1601 -0
- package/contracts/atelier-skill-steward.v1.schema.json +692 -0
- package/contracts/atelier-upgrade-plan.v2.schema.json +398 -0
- package/contracts/atelier-upgrade-receipt.v1.schema.json +99 -0
- package/contracts/atelier-vault-publication.v1.schema.json +58 -0
- package/docs/assurance-controls.md +9 -1
- package/docs/atelier-sync.md +5 -4
- package/docs/blocks/claims.md +15 -10
- package/docs/blocks/will-not-do.md +4 -1
- package/docs/coauthor-session.md +57 -0
- package/docs/connected-composition.md +75 -0
- package/docs/decisions.md +167 -0
- package/docs/exact-upgrades.md +159 -0
- package/docs/extension-pack-lifecycle.md +33 -0
- package/docs/guided-upgrades.md +99 -0
- package/docs/install.md +65 -9
- package/docs/intake-and-guides.md +65 -0
- package/docs/integration-and-review-plan.md +409 -0
- package/docs/integration-contract-decisions.md +22 -0
- package/docs/local-review.md +84 -0
- package/docs/local-services.md +227 -0
- package/docs/local-state.md +107 -0
- package/docs/obsidian-contract.md +687 -0
- package/docs/obsidian.md +387 -0
- package/docs/portable-presentation.md +328 -0
- package/docs/presentation-consumer-boundaries.md +75 -0
- package/docs/presentation-review-disposition.md +47 -0
- package/docs/project-command-inventory.md +38 -0
- package/docs/project-options.md +53 -0
- package/docs/release-engineering.md +88 -0
- package/docs/review-portability.md +55 -0
- package/docs/skill-steward.md +156 -0
- package/docs/upgrade.md +55 -2
- package/docs/vault-service.md +292 -0
- package/fixtures/atelier-coauthor/invalid/authority.v1.json +5 -0
- package/fixtures/atelier-coauthor/valid/config.v1.json +12 -0
- package/fixtures/atelier-guide/invalid/authority.v1.json +11 -0
- package/fixtures/atelier-guide/valid/capability.v1.json +10 -0
- package/fixtures/atelier-guide/valid/consent.v1.json +11 -0
- package/fixtures/atelier-guide/valid/engagement.v1.json +7 -0
- package/fixtures/atelier-guide/valid/offer.v1.json +10 -0
- package/fixtures/atelier-intake/invalid/authority.v1.json +9 -0
- package/fixtures/atelier-intake/valid/attempt.v1.json +8 -0
- package/fixtures/atelier-intake/valid/completion.v1.json +9 -0
- package/fixtures/atelier-intake/valid/source.v1.json +7 -0
- package/fixtures/atelier-review/invalid/authenticated-identity.v1.json +22 -0
- package/fixtures/atelier-review/valid/bound.v1.json +229 -0
- package/fixtures/atelier-review/valid/bundle.v1.json +267 -0
- package/fixtures/atelier-review/valid/contribution.v1.json +22 -0
- package/fixtures/atelier-review/valid/lifecycle.v1.json +14 -0
- package/fixtures/atelier-skill-steward/audit/invalid/source-mutation.v1.json +26 -0
- package/fixtures/atelier-skill-steward/audit/valid/clean.v1.json +26 -0
- package/fixtures/atelier-skill-steward/candidates/invalid/workflow-key.v1.json +37 -0
- package/fixtures/atelier-skill-steward/candidates/valid/create.v1.json +37 -0
- package/fixtures/atelier-skill-steward/lock/invalid/bad-digest.v1.json +18 -0
- package/fixtures/atelier-skill-steward/lock/valid/installed.v1.json +23 -0
- package/fixtures/atelier-skill-steward/plan/invalid/delete-action.v1.json +33 -0
- package/fixtures/atelier-skill-steward/plan/valid/add.v1.json +33 -0
- package/fixtures/atelier-upgrade-transaction/adoption-policy/invalid/example.json +12 -0
- package/fixtures/atelier-upgrade-transaction/adoption-policy/valid/example.json +11 -0
- package/fixtures/atelier-upgrade-transaction/migration/invalid/example.json +20 -0
- package/fixtures/atelier-upgrade-transaction/migration/valid/example.json +19 -0
- package/fixtures/atelier-upgrade-transaction/upgrade-plan/invalid/example.json +58 -0
- package/fixtures/atelier-upgrade-transaction/upgrade-plan/valid/example.json +57 -0
- package/fixtures/atelier-upgrade-transaction/upgrade-receipt/invalid/example.json +15 -0
- package/fixtures/atelier-upgrade-transaction/upgrade-receipt/valid/example.json +14 -0
- package/fixtures/atelier-vault-publication/invalid/publication.json +5 -0
- package/fixtures/atelier-vault-publication/valid/publication.json +11 -0
- package/fixtures/decisions/request/invalid/unknown-authority.v1.json +61 -0
- package/fixtures/decisions/request/valid/triage.v1.json +60 -0
- package/fixtures/decisions/result/invalid/execution-authority.v1.json +50 -0
- package/fixtures/decisions/result/valid/abstained.v1.json +23 -0
- package/fixtures/decisions/result/valid/assessed.v1.json +50 -0
- package/fixtures/obsidian/acceptance/ap05-extra-notes.json +14 -0
- package/fixtures/obsidian/acceptance/receipts/G07.valid.v1.json +63 -0
- package/fixtures/obsidian/acceptance/receipts/G13.valid.v1.json +69 -0
- package/fixtures/obsidian/acceptance/receipts/G14.valid.v1.json +62 -0
- package/fixtures/obsidian/acceptance/receipts/G15.valid.v1.json +56 -0
- package/fixtures/obsidian/acceptance/receipts/G16.valid.v1.json +73 -0
- package/fixtures/obsidian/acceptance/receipts/G17.valid.v1.json +75 -0
- package/fixtures/obsidian/acceptance/receipts/G18.valid.v1.json +64 -0
- package/fixtures/obsidian/acceptance/service-sentinels.json +10 -0
- package/fixtures/obsidian/contracts/acceptance-receipt/invalid/duplicate-evidence-name.v1.json +39 -0
- package/fixtures/obsidian/contracts/acceptance-receipt/invalid/evidence-by-absolute-path.v1.json +34 -0
- package/fixtures/obsidian/contracts/acceptance-receipt/invalid/missing-candidate-tree.v1.json +33 -0
- package/fixtures/obsidian/contracts/acceptance-receipt/invalid/no-evidence.v1.json +28 -0
- package/fixtures/obsidian/contracts/acceptance-receipt/invalid/unknown-outcome.v1.json +34 -0
- package/fixtures/obsidian/contracts/acceptance-receipt/invalid/unknown-top-level-field.v1.json +35 -0
- package/fixtures/obsidian/contracts/acceptance-receipt/valid/node-gate.v1.json +34 -0
- package/fixtures/obsidian/contracts/apply-policy/invalid/automatic-without-edit-class.v1.json +27 -0
- package/fixtures/obsidian/contracts/apply-policy/invalid/conflict-overwrite.v1.json +29 -0
- package/fixtures/obsidian/contracts/apply-policy/invalid/missing-policy-digest.v1.json +28 -0
- package/fixtures/obsidian/contracts/apply-policy/invalid/unbounded-batch.v1.json +29 -0
- package/fixtures/obsidian/contracts/apply-policy/invalid/unimplemented-edit-class.v1.json +30 -0
- package/fixtures/obsidian/contracts/apply-policy/invalid/unknown-mode.v1.json +29 -0
- package/fixtures/obsidian/contracts/apply-policy/invalid/unknown-selector-operator.v1.json +22 -0
- package/fixtures/obsidian/contracts/apply-policy/invalid/unknown-top-level-field.v1.json +30 -0
- package/fixtures/obsidian/contracts/apply-policy/valid/automatic-scoped.v1.json +29 -0
- package/fixtures/obsidian/contracts/apply-policy/valid/manual-default.v1.json +20 -0
- package/fixtures/obsidian/contracts/corpus-profile/invalid/absolute-path-in-ext.v1.json +30 -0
- package/fixtures/obsidian/contracts/corpus-profile/invalid/absolute-repository-root.v1.json +27 -0
- package/fixtures/obsidian/contracts/corpus-profile/invalid/duplicate-repository-id.v1.json +27 -0
- package/fixtures/obsidian/contracts/corpus-profile/invalid/overlapping-managed-roots.v1.json +27 -0
- package/fixtures/obsidian/contracts/corpus-profile/invalid/unknown-enrollment.v1.json +27 -0
- package/fixtures/obsidian/contracts/corpus-profile/invalid/unknown-top-level-field.v1.json +28 -0
- package/fixtures/obsidian/contracts/corpus-profile/valid/three-repositories.v1.json +27 -0
- package/fixtures/obsidian/contracts/edit-operation/invalid/absolute-recovery-ref.v1.json +21 -0
- package/fixtures/obsidian/contracts/edit-operation/invalid/inline-observed-bytes.v1.json +22 -0
- package/fixtures/obsidian/contracts/edit-operation/invalid/missing-base-digest.v1.json +20 -0
- package/fixtures/obsidian/contracts/edit-operation/invalid/short-idempotency-key.v1.json +21 -0
- package/fixtures/obsidian/contracts/edit-operation/invalid/unknown-kind.v1.json +21 -0
- package/fixtures/obsidian/contracts/edit-operation/valid/pending-body-replacement.v1.json +21 -0
- package/fixtures/obsidian/contracts/ext-settings/invalid/absolute-path-prefix.v1.json +28 -0
- package/fixtures/obsidian/contracts/ext-settings/invalid/apply-policy-in-portable-settings.v1.json +30 -0
- package/fixtures/obsidian/contracts/ext-settings/invalid/duplicate-scope-id.v1.json +27 -0
- package/fixtures/obsidian/contracts/ext-settings/invalid/machine-local-vault-path.v1.json +28 -0
- package/fixtures/obsidian/contracts/ext-settings/invalid/service-port-in-portable-settings.v1.json +28 -0
- package/fixtures/obsidian/contracts/ext-settings/invalid/unknown-default-scope.v1.json +27 -0
- package/fixtures/obsidian/contracts/ext-settings/valid/disabled.v1.json +5 -0
- package/fixtures/obsidian/contracts/ext-settings/valid/enabled-with-scopes.v1.json +27 -0
- package/fixtures/obsidian/contracts/generation-manifest/invalid/absolute-note-path.v1.json +114 -0
- package/fixtures/obsidian/contracts/generation-manifest/invalid/complete-with-unwritten-notes.v1.json +114 -0
- package/fixtures/obsidian/contracts/generation-manifest/invalid/derived-link-claimed-declared.v1.json +114 -0
- package/fixtures/obsidian/contracts/generation-manifest/invalid/duplicate-note-identity.v1.json +114 -0
- package/fixtures/obsidian/contracts/generation-manifest/invalid/in-scope-endpoint-missing.v1.json +114 -0
- package/fixtures/obsidian/contracts/generation-manifest/invalid/title-only-note-path.v1.json +114 -0
- package/fixtures/obsidian/contracts/generation-manifest/invalid/unknown-note-field.v1.json +115 -0
- package/fixtures/obsidian/contracts/generation-manifest/invalid/unknown-relation-type.v1.json +114 -0
- package/fixtures/obsidian/contracts/generation-manifest/invalid/withheld-endpoint.v1.json +114 -0
- package/fixtures/obsidian/contracts/generation-manifest/valid/two-notes.v1.json +114 -0
- package/fixtures/obsidian/contracts/oracles/scope-cases.json +412 -0
- package/fixtures/obsidian/contracts/proposal-receipt/invalid/accepted-without-proposal-id.v1.json +12 -0
- package/fixtures/obsidian/contracts/proposal-receipt/invalid/missing-adapter-operation-id.v1.json +11 -0
- package/fixtures/obsidian/contracts/proposal-receipt/invalid/unknown-backpressure-outcome.v1.json +12 -0
- package/fixtures/obsidian/contracts/proposal-receipt/invalid/unknown-top-level-field.v1.json +13 -0
- package/fixtures/obsidian/contracts/proposal-receipt/valid/accepted-new.v1.json +12 -0
- package/fixtures/obsidian/contracts/proposal-receipt/valid/deferred.v1.json +12 -0
- package/fixtures/obsidian/contracts/publication-journal/invalid/absolute-recovery-ref.v1.json +47 -0
- package/fixtures/obsidian/contracts/publication-journal/invalid/duplicate-sequence.v1.json +47 -0
- package/fixtures/obsidian/contracts/publication-journal/invalid/empty-protocol-id.v1.json +47 -0
- package/fixtures/obsidian/contracts/publication-journal/invalid/missing-protocol-id.v1.json +46 -0
- package/fixtures/obsidian/contracts/publication-journal/invalid/unknown-entry-field.v1.json +48 -0
- package/fixtures/obsidian/contracts/publication-journal/invalid/unknown-state.v1.json +47 -0
- package/fixtures/obsidian/contracts/publication-journal/valid/committed.v1.json +47 -0
- package/fixtures/obsidian/contracts/publication-journal/valid/updating-after-restart.v1.json +22 -0
- package/fixtures/obsidian/contracts/scope/invalid/absolute-path-prefix.v1.json +15 -0
- package/fixtures/obsidian/contracts/scope/invalid/duplicate-ids.v1.json +18 -0
- package/fixtures/obsidian/contracts/scope/invalid/expansion-without-node-budget.v1.json +40 -0
- package/fixtures/obsidian/contracts/scope/invalid/full-mode-with-subset.v1.json +8 -0
- package/fixtures/obsidian/contracts/scope/invalid/unknown-mode.v1.json +41 -0
- package/fixtures/obsidian/contracts/scope/invalid/unknown-selector-key.v1.json +15 -0
- package/fixtures/obsidian/contracts/scope/invalid/unknown-selector-operator.v1.json +14 -0
- package/fixtures/obsidian/contracts/scope/invalid/unknown-top-level-field.v1.json +42 -0
- package/fixtures/obsidian/contracts/scope/valid/explicit-empty.v1.json +8 -0
- package/fixtures/obsidian/contracts/scope/valid/focus.v1.json +11 -0
- package/fixtures/obsidian/contracts/scope/valid/full.v1.json +8 -0
- package/fixtures/obsidian/contracts/scope/valid/scoped-set-expression.v1.json +41 -0
- package/fixtures/obsidian/contracts/service-state/invalid/hostname-instead-of-literal-loopback.v1.json +23 -0
- package/fixtures/obsidian/contracts/service-state/invalid/missing-consent.v1.json +18 -0
- package/fixtures/obsidian/contracts/service-state/invalid/privileged-port.v1.json +23 -0
- package/fixtures/obsidian/contracts/service-state/invalid/unknown-top-level-field.v1.json +24 -0
- package/fixtures/obsidian/contracts/service-state/invalid/wildcard-bind.v1.json +23 -0
- package/fixtures/obsidian/contracts/service-state/valid/healthy.v1.json +23 -0
- package/fixtures/obsidian/contracts/source-snapshot/invalid/absolute-file-path.v1.json +48 -0
- package/fixtures/obsidian/contracts/source-snapshot/invalid/duplicate-repository-id.v1.json +48 -0
- package/fixtures/obsidian/contracts/source-snapshot/invalid/mixed-read.v1.json +48 -0
- package/fixtures/obsidian/contracts/source-snapshot/invalid/parent-traversal-path.v1.json +48 -0
- package/fixtures/obsidian/contracts/source-snapshot/invalid/unknown-file-field.v1.json +49 -0
- package/fixtures/obsidian/contracts/source-snapshot/valid/two-repositories.v1.json +48 -0
- package/fixtures/obsidian/edits/workspace.json +65 -0
- package/fixtures/obsidian/materialization/expected-full.json +99 -0
- package/fixtures/obsidian/materialization/expected-scoped.json +39 -0
- package/fixtures/obsidian/materialization/workspace.json +54 -0
- package/fixtures/obsidian/proposals/expected-shapes.json +11 -0
- package/fixtures/ui/presentation/pane.v1.json +7 -0
- package/fixtures/ui/presentation/reference.v1.json +293 -0
- package/package.json +57 -6
- package/skills/claude/atelier-guided-coauthor/SKILL.md +49 -0
- package/skills/claude/atelier-guided-upgrade/SKILL.md +97 -0
- package/skills/claude/atelier-open/SKILL.md +5 -0
- package/skills/claude/atelier-skill-steward/SKILL.md +62 -0
- package/skills/claude/mnstry-readiness/SKILL.md +5 -0
- package/skills/codex/atelier-guided-coauthor/SKILL.md +49 -0
- package/skills/codex/atelier-guided-upgrade/SKILL.md +97 -0
- package/skills/codex/atelier-open/SKILL.md +5 -0
- package/skills/codex/atelier-skill-steward/SKILL.md +62 -0
- package/skills/codex/mnstry-readiness/SKILL.md +5 -0
- package/src/access/preview-enforcer.mjs +84 -0
- package/src/boundary/policy.mjs +57 -25
- package/src/cli/project-options.mjs +58 -0
- package/src/cli/run.mjs +43 -3
- package/src/coauthor/session.mjs +161 -0
- package/src/coauthor/store.mjs +173 -0
- package/src/collaboration/event-ledger.mjs +16 -33
- package/src/collaboration/inspection-bundle.mjs +223 -0
- package/src/collaboration/review-contracts.mjs +25 -0
- package/src/collaboration/review-store.mjs +305 -0
- package/src/commands/coauthor.mjs +26 -0
- package/src/commands/config.mjs +6 -1
- package/src/commands/extension-pack.mjs +15 -8
- package/src/commands/init.mjs +44 -5
- package/src/commands/obsidian.mjs +368 -0
- package/src/commands/review.mjs +147 -0
- package/src/commands/setup.mjs +51 -14
- package/src/commands/skills.mjs +199 -0
- package/src/commands/sync.mjs +8 -1
- package/src/composition/registry.mjs +27 -0
- package/src/composition/wire.mjs +122 -0
- package/src/contracts/corpus.mjs +29 -0
- package/src/decisions/contracts.d.mts +96 -0
- package/src/decisions/contracts.mjs +321 -0
- package/src/disclosure/content-scan.mjs +13 -0
- package/src/export/atelier-export-contract.mjs +16 -1
- package/src/extension-packs/lifecycle.mjs +154 -0
- package/src/graph/graph.mjs +64 -2
- package/src/graph/knowledge-graph.mjs +548 -24
- package/src/guides/contracts.mjs +54 -0
- package/src/intake/store.mjs +119 -0
- package/src/preview/channel.mjs +89 -0
- package/src/project/config.mjs +26 -27
- package/src/project/durable-state.mjs +161 -0
- package/src/project/file-class.mjs +166 -0
- package/src/project/private-state.mjs +11 -1
- package/src/projection/obsidian/contracts.mjs +571 -0
- package/src/projection/obsidian/edits/align.mjs +169 -0
- package/src/projection/obsidian/edits/apply.mjs +889 -0
- package/src/projection/obsidian/edits/arbitrate.mjs +414 -0
- package/src/projection/obsidian/edits/contribution.mjs +108 -0
- package/src/projection/obsidian/edits/index.mjs +19 -0
- package/src/projection/obsidian/edits/object-identity.mjs +61 -0
- package/src/projection/obsidian/edits/object-store.mjs +517 -0
- package/src/projection/obsidian/edits/observe.mjs +123 -0
- package/src/projection/obsidian/edits/policy.mjs +123 -0
- package/src/projection/obsidian/edits/regions.mjs +648 -0
- package/src/projection/obsidian/materialize/byte-lens.mjs +98 -0
- package/src/projection/obsidian/materialize/index.mjs +7 -0
- package/src/projection/obsidian/materialize/path-registry.mjs +105 -0
- package/src/projection/obsidian/materialize/prepare-view.mjs +746 -0
- package/src/projection/obsidian/materialize/settings.mjs +57 -0
- package/src/projection/obsidian/materialize/stage.mjs +27 -0
- package/src/projection/obsidian/proposals/adapter.mjs +572 -0
- package/src/projection/obsidian/proposals/backpressure.mjs +113 -0
- package/src/projection/obsidian/proposals/contribution.mjs +55 -0
- package/src/projection/obsidian/proposals/index.mjs +23 -0
- package/src/projection/obsidian/proposals/observation.mjs +216 -0
- package/src/projection/obsidian/proposals/queue.mjs +277 -0
- package/src/projection/obsidian/proposals/router.mjs +168 -0
- package/src/projection/obsidian/publication/bridge-script.mjs +266 -0
- package/src/projection/obsidian/publication/exchange.mjs +117 -0
- package/src/projection/obsidian/publication/index.mjs +5 -0
- package/src/projection/obsidian/publication/publisher.mjs +588 -0
- package/src/projection/obsidian/publication/test-seam.mjs +16 -0
- package/src/projection/obsidian/publication/transport.mjs +139 -0
- package/src/projection/obsidian/recovery/index.mjs +6 -0
- package/src/projection/obsidian/recovery/journal.mjs +134 -0
- package/src/projection/obsidian/recovery/late-writer.mjs +46 -0
- package/src/projection/obsidian/recovery/restart.mjs +316 -0
- package/src/projection/obsidian/recovery/store.mjs +234 -0
- package/src/projection/obsidian/selection-ui/conflict-view.mjs +91 -0
- package/src/projection/obsidian/selection-ui/contribution.mjs +141 -0
- package/src/projection/obsidian/selection-ui/focus.mjs +82 -0
- package/src/projection/obsidian/selection-ui/index.mjs +12 -0
- package/src/projection/obsidian/selection-ui/policy-setup.mjs +107 -0
- package/src/projection/obsidian/selection-ui/receipt.mjs +173 -0
- package/src/projection/obsidian/selection-ui/selection-state.mjs +122 -0
- package/src/projection/obsidian/selection-ui/selection.mjs +109 -0
- package/src/projection/project.mjs +28 -19
- package/src/readiness-protocols/evidence.mjs +282 -0
- package/src/readiness-protocols/runtime.mjs +4 -3
- package/src/readiness-protocols/source-read.mjs +39 -0
- package/src/runtime/obsidian/app-capability.mjs +110 -0
- package/src/runtime/obsidian/app-production-seams.mjs +96 -0
- package/src/runtime/obsidian/contributions/proposal-adapter.mjs +4 -0
- package/src/runtime/obsidian/contributions/selection-ui.mjs +4 -0
- package/src/runtime/obsidian/contributions/source-apply.mjs +4 -0
- package/src/runtime/obsidian/contributions.mjs +27 -0
- package/src/runtime/obsidian/documents.mjs +37 -0
- package/src/runtime/obsidian/enablement.mjs +53 -0
- package/src/runtime/obsidian/engine.mjs +532 -0
- package/src/runtime/obsidian/errors.mjs +14 -0
- package/src/runtime/obsidian/extension-points.mjs +103 -0
- package/src/runtime/obsidian/index.mjs +34 -0
- package/src/runtime/obsidian/lifecycle.mjs +258 -0
- package/src/runtime/obsidian/machine-settings.mjs +260 -0
- package/src/runtime/obsidian/observation.mjs +158 -0
- package/src/runtime/obsidian/opening.mjs +243 -0
- package/src/runtime/obsidian/pending-edits.mjs +141 -0
- package/src/runtime/obsidian/pipeline.mjs +122 -0
- package/src/runtime/obsidian/private-lock.mjs +156 -0
- package/src/runtime/obsidian/process-identity.mjs +58 -0
- package/src/runtime/obsidian/service-client.mjs +66 -0
- package/src/runtime/obsidian/service-main.mjs +89 -0
- package/src/runtime/obsidian/service-record.mjs +169 -0
- package/src/runtime/obsidian/service-server.mjs +121 -0
- package/src/runtime/obsidian/service.mjs +216 -0
- package/src/runtime/obsidian/startup-adapters.mjs +87 -0
- package/src/runtime/obsidian/state-store.mjs +180 -0
- package/src/runtime/obsidian/sync-notice.mjs +33 -0
- package/src/runtime/obsidian/tick-loop.mjs +84 -0
- package/src/runtime/obsidian/watchers.mjs +36 -0
- package/src/runtime/supervisor.mjs +16 -1
- package/src/server/local-sidecar.mjs +44 -0
- package/src/server/server.mjs +1 -0
- package/src/skills/steward.mjs +780 -0
- package/src/ui/presentation/browser.mjs +278 -0
- package/src/ui/presentation/contract.mjs +116 -0
- package/src/ui/presentation/index.mjs +7 -0
- package/src/ui/presentation/native.mjs +153 -0
- package/src/ui/presentation/proof.mjs +57 -0
- package/src/ui/presentation/schema-check.mjs +30 -0
- package/src/ui/presentation/schema.generated.mjs +1371 -0
- package/src/ui/presentation/state.mjs +43 -0
- package/src/ui/presentation/styles.mjs +67 -0
- package/src/ui/presentation/tokens.mjs +74 -0
- package/src/ui/presentation/web.mjs +85 -0
- package/src/ui/review-page.mjs +32 -0
- package/src/upgrade/explanation.mjs +33 -0
- package/src/upgrade/provenance.mjs +338 -0
- package/src/upgrade/transaction-files.mjs +103 -0
- package/src/upgrade/transaction.mjs +475 -0
- package/src/upgrade/upgrade.mjs +63 -28
- package/src/vault/hosts.mjs +21 -0
- package/src/vault/identity.mjs +19 -0
- package/src/vault/index.mjs +7 -0
- package/src/vault/interface.mjs +11 -0
- package/src/vault/metadata.mjs +31 -0
- package/src/vault/privacy.mjs +148 -0
- package/src/vault/probe.mjs +89 -0
- package/src/vault/service.mjs +131 -0
- package/src/vault/source.mjs +45 -0
- package/src/vault/storage.mjs +23 -0
- package/templates/distribution-workspace/.claude/launch.json +6 -2
- package/templates/distribution-workspace/gitignore +1 -0
- package/templates/distribution-workspace/project/README.md +2 -2
- package/templates/external-project-workspace/README.md +45 -0
- package/templates/external-project-workspace/answers.example.json +4 -0
- package/templates/external-project-workspace/atelier.project.json +40 -0
- package/templates/external-project-workspace/boundary-policy.v1.json +60 -0
- package/templates/external-project-workspace/gitignore +8 -0
- package/templates/external-project-workspace/pack-lifecycle.json +14 -0
- package/templates/external-project-workspace/packs/protocols/contract-gate.v1.json +109 -0
- package/templates/external-project-workspace/packs/sample-pack.v1.json +48 -0
- package/templates/external-project-workspace/repo-access.v1.json +9 -0
- package/templates/external-project-workspace/source/README.md +11 -0
- package/templates/launch.json +6 -2
- package/templates/private-domain-workspace/.claude/launch.json +6 -2
- package/templates/private-domain-workspace/domain/README.md +2 -2
- package/templates/private-domain-workspace/gitignore +1 -0
- package/templates/shared-project-workspace/.claude/launch.json +6 -2
- package/templates/shared-project-workspace/gitignore +1 -0
- package/templates/shared-project-workspace/project/README.md +2 -2
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto'
|
|
2
|
+
|
|
3
|
+
const ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/
|
|
4
|
+
const RESERVED = new Set(['__proto__', 'constructor', 'prototype'])
|
|
5
|
+
const VERSION = /^1\.[0-9]+\.[0-9]+$/
|
|
6
|
+
const REASONS = new Set(['insufficient-evidence', 'ambiguous', 'no-match', 'budget-exhausted', 'provider-unavailable', 'timeout', 'invalid-response', 'unauthorized'])
|
|
7
|
+
const TOLERANCE = 1e-6
|
|
8
|
+
|
|
9
|
+
// Inspect descriptors before reading values. Invalid in-process inputs such as
|
|
10
|
+
// accessors, symbols, sparse arrays, cycles and custom prototypes are refused
|
|
11
|
+
// without calling their conversion methods or returning their content.
|
|
12
|
+
function jsonSnapshot(input) {
|
|
13
|
+
const ancestors = new Set()
|
|
14
|
+
let nodes = 0
|
|
15
|
+
let bytes = 0
|
|
16
|
+
function count(value) {
|
|
17
|
+
bytes += value
|
|
18
|
+
if (bytes > 16777216) throw new TypeError()
|
|
19
|
+
}
|
|
20
|
+
function copy(value, depth) {
|
|
21
|
+
if (++nodes > 100000 || depth > 32) throw new TypeError()
|
|
22
|
+
if (value === null || typeof value === 'string' || typeof value === 'boolean' || typeof value === 'number' && Number.isFinite(value)) {
|
|
23
|
+
if (typeof value === 'string' && Buffer.byteLength(value) > 16777216) throw new TypeError()
|
|
24
|
+
count(Buffer.byteLength(JSON.stringify(value)))
|
|
25
|
+
return value
|
|
26
|
+
}
|
|
27
|
+
if (!value || typeof value !== 'object' || ancestors.has(value)) throw new TypeError()
|
|
28
|
+
const array = Array.isArray(value)
|
|
29
|
+
const prototype = Object.getPrototypeOf(value)
|
|
30
|
+
if (array ? prototype !== Array.prototype : prototype !== Object.prototype && prototype !== null) throw new TypeError()
|
|
31
|
+
const descriptors = Object.getOwnPropertyDescriptors(value)
|
|
32
|
+
const keys = Reflect.ownKeys(descriptors)
|
|
33
|
+
if (keys.some((key) => typeof key !== 'string' || RESERVED.has(key))) throw new TypeError()
|
|
34
|
+
count(2)
|
|
35
|
+
ancestors.add(value)
|
|
36
|
+
let result
|
|
37
|
+
if (array) {
|
|
38
|
+
const length = descriptors.length?.value
|
|
39
|
+
if (!Number.isSafeInteger(length) || length < 0 || length > 100000 || keys.length !== length + 1) throw new TypeError()
|
|
40
|
+
result = []
|
|
41
|
+
for (let index = 0; index < length; index++) {
|
|
42
|
+
const descriptor = descriptors[String(index)]
|
|
43
|
+
if (!descriptor || !Object.hasOwn(descriptor, 'value') || !descriptor.enumerable) throw new TypeError()
|
|
44
|
+
if (index) count(1)
|
|
45
|
+
result.push(copy(descriptor.value, depth + 1))
|
|
46
|
+
}
|
|
47
|
+
} else {
|
|
48
|
+
result = Object.create(null)
|
|
49
|
+
for (const [index, key] of keys.entries()) {
|
|
50
|
+
const descriptor = descriptors[key]
|
|
51
|
+
if (!Object.hasOwn(descriptor, 'value') || !descriptor.enumerable) throw new TypeError()
|
|
52
|
+
if (Buffer.byteLength(key) > 16777216) throw new TypeError()
|
|
53
|
+
count(Buffer.byteLength(JSON.stringify(key)) + 1 + (index ? 1 : 0))
|
|
54
|
+
result[key] = copy(descriptor.value, depth + 1)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
ancestors.delete(value)
|
|
58
|
+
return result
|
|
59
|
+
}
|
|
60
|
+
return copy(input, 0)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function object(value) {
|
|
64
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function text(value, maximum, allowEmpty = false) {
|
|
68
|
+
if (typeof value !== 'string' || (!allowEmpty && !value.trim())) return false
|
|
69
|
+
let length = 0
|
|
70
|
+
for (const character of value) {
|
|
71
|
+
void character
|
|
72
|
+
if (++length > maximum) return false
|
|
73
|
+
}
|
|
74
|
+
return true
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function identifier(value) {
|
|
78
|
+
return typeof value === 'string' && ID.test(value) && !RESERVED.has(value)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function report() {
|
|
82
|
+
const errors = []
|
|
83
|
+
return {
|
|
84
|
+
errors,
|
|
85
|
+
check(condition, label) {
|
|
86
|
+
if (!condition && errors.length < 64) errors.push(label)
|
|
87
|
+
return condition
|
|
88
|
+
},
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function closed(value, required, optional, label, checks) {
|
|
93
|
+
if (!checks.check(object(value), `${label}: expected object`)) return false
|
|
94
|
+
const allowed = new Set([...required, ...optional, 'ext'])
|
|
95
|
+
checks.check(required.every((key) => Object.hasOwn(value, key)), `${label}: required field missing`)
|
|
96
|
+
checks.check(Object.keys(value).every((key) => allowed.has(key)), `${label}: unknown field`)
|
|
97
|
+
if (Object.hasOwn(value, 'ext')) checks.check(object(value.ext), `${label}.ext: expected object`)
|
|
98
|
+
return true
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function record(value, minimum, maximum, label, checks) {
|
|
102
|
+
if (!checks.check(object(value), `${label}: expected record`)) return false
|
|
103
|
+
const keys = Object.keys(value)
|
|
104
|
+
checks.check(keys.length >= minimum && keys.length <= maximum, `${label}: entry count outside limits`)
|
|
105
|
+
checks.check(keys.every(identifier), `${label}: invalid identifier`)
|
|
106
|
+
return keys.length <= maximum
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function scope(value, label, checks) {
|
|
110
|
+
if (closed(value, ['workspaceId', 'authorizationRef'], [], label, checks)) {
|
|
111
|
+
checks.check(identifier(value.workspaceId), `${label}.workspaceId: invalid identifier`)
|
|
112
|
+
checks.check(identifier(value.authorizationRef), `${label}.authorizationRef: invalid identifier`)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function version(value, label, checks) {
|
|
117
|
+
if (Object.hasOwn(value, 'contractVersion')) {
|
|
118
|
+
checks.check(typeof value.contractVersion === 'string' && VERSION.test(value.contractVersion), `${label}.contractVersion: invalid version`)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function inspectRequest(request, checks) {
|
|
123
|
+
if (!closed(request, ['schema', 'id', 'task', 'rubricVersion', 'scope', 'state', 'evidence', 'questions'], ['contractVersion'], 'request', checks)) return
|
|
124
|
+
checks.check(request.schema === 'atelier-decision-request@v1', 'request.schema: unsupported schema')
|
|
125
|
+
version(request, 'request', checks)
|
|
126
|
+
for (const key of ['id', 'task', 'rubricVersion']) checks.check(identifier(request[key]), `request.${key}: invalid identifier`)
|
|
127
|
+
scope(request.scope, 'request.scope', checks)
|
|
128
|
+
checks.check(text(request.state, 32000, true), 'request.state: expected string within limit')
|
|
129
|
+
const evidenceIds = new Set()
|
|
130
|
+
if (checks.check(Array.isArray(request.evidence) && request.evidence.length >= 1 && request.evidence.length <= 256, 'request.evidence: expected 1..256 entries')) {
|
|
131
|
+
for (const evidence of request.evidence) {
|
|
132
|
+
if (!closed(evidence, ['id', 'sourceRef'], [], 'request.evidence[]', checks)) continue
|
|
133
|
+
checks.check(identifier(evidence.id), 'request.evidence[].id: invalid identifier')
|
|
134
|
+
checks.check(!evidenceIds.has(evidence.id), 'request.evidence: duplicate identifier')
|
|
135
|
+
evidenceIds.add(evidence.id)
|
|
136
|
+
checks.check(text(evidence.sourceRef, 2048), 'request.evidence[].sourceRef: expected nonempty reference within limit')
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
inspectQuestions(request.questions, checks, 'request.questions', evidenceIds)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function inspectQuestions(questions, checks, label, evidenceIds = null) {
|
|
143
|
+
if (!record(questions, 1, 64, label, checks)) return
|
|
144
|
+
for (const question of Object.values(questions)) {
|
|
145
|
+
if (!closed(question, ['type', 'instructions', 'evidenceIds', 'criteria'], [], `${label}[]`, checks)) continue
|
|
146
|
+
checks.check(text(question.instructions, 4000), `${label}[].instructions: expected nonempty string within limit`)
|
|
147
|
+
if (checks.check(Array.isArray(question.evidenceIds) && question.evidenceIds.length >= 1 && question.evidenceIds.length <= 256, `${label}[].evidenceIds: expected 1..256 references`)) {
|
|
148
|
+
checks.check(new Set(question.evidenceIds).size === question.evidenceIds.length, `${label}[].evidenceIds: duplicate reference`)
|
|
149
|
+
checks.check(question.evidenceIds.every(identifier), `${label}[].evidenceIds: invalid reference`)
|
|
150
|
+
if (evidenceIds !== null) checks.check(question.evidenceIds.every((id) => evidenceIds.has(id)), `${label}[].evidenceIds: unknown reference`)
|
|
151
|
+
}
|
|
152
|
+
if (question.type === 'choice') {
|
|
153
|
+
if (record(question.criteria, 2, 64, `${label}[].criteria`, checks)) {
|
|
154
|
+
checks.check(Object.values(question.criteria).every((value) => text(value, 2000)), `${label}[].criteria: expected nonempty descriptions within limit`)
|
|
155
|
+
}
|
|
156
|
+
} else if (question.type === 'score') {
|
|
157
|
+
checks.check(Array.isArray(question.criteria) && question.criteria.length >= 2 && question.criteria.length <= 10 && question.criteria.every((value) => text(value, 2000)), `${label}[].criteria: expected 2..10 nonempty ordered descriptions within limit`)
|
|
158
|
+
} else if (question.type === 'boolean') {
|
|
159
|
+
if (closed(question.criteria, ['true', 'false'], [], `${label}[].criteria`, checks)) {
|
|
160
|
+
checks.check(text(question.criteria.true, 2000) && text(question.criteria.false, 2000), `${label}[].criteria: expected nonempty descriptions within limit`)
|
|
161
|
+
}
|
|
162
|
+
} else {
|
|
163
|
+
checks.check(false, `${label}[].type: unsupported type`)
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function canonical(value) {
|
|
169
|
+
if (Array.isArray(value)) return `[${value.map(canonical).join(',')}]`
|
|
170
|
+
if (object(value)) return `{${Object.keys(value).sort().map((key) => `${JSON.stringify(key)}:${canonical(value[key])}`).join(',')}}`
|
|
171
|
+
return JSON.stringify(value)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function digest(request) {
|
|
175
|
+
return createHash('sha256').update(canonical(request), 'utf8').digest('hex')
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function inspected(input) {
|
|
179
|
+
const checks = report()
|
|
180
|
+
let request
|
|
181
|
+
try {
|
|
182
|
+
request = jsonSnapshot(input)
|
|
183
|
+
inspectRequest(request, checks)
|
|
184
|
+
} catch {
|
|
185
|
+
checks.check(false, 'request: expected bounded plain JSON')
|
|
186
|
+
}
|
|
187
|
+
return { request, checks }
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** Validate structure and evidence references; this does not authorize egress. */
|
|
191
|
+
export function validateDecisionRequest(input) {
|
|
192
|
+
const { checks } = inspected(input)
|
|
193
|
+
return { ok: checks.errors.length === 0, errors: checks.errors }
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** Hash a valid request, including its scope, evidence, rubric and extensions. */
|
|
197
|
+
export function decisionRequestDigest(input) {
|
|
198
|
+
const { request, checks } = inspected(input)
|
|
199
|
+
if (checks.errors.length) throw new TypeError('Invalid decision request')
|
|
200
|
+
return digest(request)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function probability(value) {
|
|
204
|
+
return typeof value === 'number' && Number.isFinite(value) && value >= 0 && value <= 1
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function distribution(values, label, checks) {
|
|
208
|
+
if (!checks.check(values.every(probability), `${label}: values must be probabilities`)) return false
|
|
209
|
+
return checks.check(Math.abs(values.reduce((sum, value) => sum + value, 0) - 1) <= TOLERANCE, `${label}: probabilities must sum to one`)
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function sameKeys(left, right) {
|
|
213
|
+
const leftKeys = Object.keys(left).sort()
|
|
214
|
+
const rightKeys = Object.keys(right).sort()
|
|
215
|
+
return leftKeys.length === rightKeys.length && leftKeys.every((key, index) => key === rightKeys[index])
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function answer(question, value, checks, label) {
|
|
219
|
+
if (question.type === 'choice') {
|
|
220
|
+
if (!closed(value, ['type', 'choice', 'probabilities', 'confidence'], [], label, checks)) return
|
|
221
|
+
checks.check(value.type === 'choice', `${label}.type: question type mismatch`)
|
|
222
|
+
checks.check(probability(value.confidence), `${label}.confidence: expected probability`)
|
|
223
|
+
checks.check(identifier(value.choice) && Object.hasOwn(question.criteria, value.choice), `${label}.choice: unknown choice`)
|
|
224
|
+
if (record(value.probabilities, 2, 64, `${label}.probabilities`, checks)) {
|
|
225
|
+
checks.check(sameKeys(value.probabilities, question.criteria), `${label}.probabilities: criteria keys mismatch`)
|
|
226
|
+
const values = Object.values(value.probabilities)
|
|
227
|
+
if (distribution(values, `${label}.probabilities`, checks) && Object.hasOwn(value.probabilities, value.choice)) {
|
|
228
|
+
checks.check(value.probabilities[value.choice] === Math.max(...values), `${label}.choice: choice must have maximum probability`)
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
} else if (question.type === 'score') {
|
|
232
|
+
if (!closed(value, ['type', 'score', 'probabilities', 'confidence'], [], label, checks)) return
|
|
233
|
+
checks.check(value.type === 'score', `${label}.type: question type mismatch`)
|
|
234
|
+
checks.check(probability(value.confidence), `${label}.confidence: expected probability`)
|
|
235
|
+
checks.check(typeof value.score === 'number' && Number.isFinite(value.score) && value.score >= 0 && value.score <= question.criteria.length - 1, `${label}.score: outside score range`)
|
|
236
|
+
if (checks.check(Array.isArray(value.probabilities) && value.probabilities.length === question.criteria.length, `${label}.probabilities: criteria count mismatch`)) {
|
|
237
|
+
if (distribution(value.probabilities, `${label}.probabilities`, checks)) {
|
|
238
|
+
const expectation = value.probabilities.reduce((sum, value, index) => sum + value * index, 0)
|
|
239
|
+
checks.check(Math.abs(value.score - expectation) <= TOLERANCE, `${label}.score: weighted expectation mismatch`)
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
} else {
|
|
243
|
+
if (!closed(value, ['type', 'probability'], [], label, checks)) return
|
|
244
|
+
checks.check(value.type === 'boolean', `${label}.type: question type mismatch`)
|
|
245
|
+
checks.check(probability(value.probability), `${label}.probability: expected probability`)
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function inspectAnswers(questions, answers, checks, label) {
|
|
250
|
+
if (!record(answers, 0, 64, label, checks)) return
|
|
251
|
+
checks.check(sameKeys(answers, questions), `${label}: question keys mismatch`)
|
|
252
|
+
for (const [id, question] of Object.entries(questions)) {
|
|
253
|
+
if (Object.hasOwn(answers, id)) answer(question, answers[id], checks, `${label}[]`)
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Validate question/answer shape and distributions without state or hashing.
|
|
259
|
+
* Evidence reference syntax is checked; membership and authority belong to
|
|
260
|
+
* the host that holds the source snapshot and transient request binding.
|
|
261
|
+
*/
|
|
262
|
+
export function validateDecisionAnswers(inputQuestions, inputAnswers) {
|
|
263
|
+
const checks = report()
|
|
264
|
+
let questions
|
|
265
|
+
try {
|
|
266
|
+
questions = jsonSnapshot(inputQuestions)
|
|
267
|
+
inspectQuestions(questions, checks, 'questions')
|
|
268
|
+
} catch {
|
|
269
|
+
checks.check(false, 'questions: expected bounded plain JSON')
|
|
270
|
+
}
|
|
271
|
+
if (checks.errors.length) return { ok: false, errors: checks.errors }
|
|
272
|
+
try {
|
|
273
|
+
inspectAnswers(questions, jsonSnapshot(inputAnswers), checks, 'answers')
|
|
274
|
+
} catch {
|
|
275
|
+
checks.check(false, 'answers: expected bounded plain JSON')
|
|
276
|
+
}
|
|
277
|
+
return { ok: checks.errors.length === 0, errors: checks.errors }
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function inspectResult(request, result, checks) {
|
|
281
|
+
if (!closed(result, ['schema', 'requestId', 'requestDigest', 'task', 'rubricVersion', 'scope', 'provider', 'authority', 'mode', 'status', 'answers', 'usage', 'elapsedMs'], ['reason', 'contractVersion'], 'result', checks)) return
|
|
282
|
+
checks.check(result.schema === 'atelier-decision-result@v1', 'result.schema: unsupported schema')
|
|
283
|
+
version(result, 'result', checks)
|
|
284
|
+
checks.check(result.requestId === request.id, 'result.requestId: request binding mismatch')
|
|
285
|
+
checks.check(result.requestDigest === digest(request), 'result.requestDigest: request binding mismatch')
|
|
286
|
+
checks.check(result.task === request.task && result.rubricVersion === request.rubricVersion, 'result: task or rubric binding mismatch')
|
|
287
|
+
scope(result.scope, 'result.scope', checks)
|
|
288
|
+
checks.check(canonical(result.scope) === canonical(request.scope), 'result.scope: request binding mismatch')
|
|
289
|
+
if (closed(result.provider, ['id', 'model'], [], 'result.provider', checks)) {
|
|
290
|
+
checks.check(identifier(result.provider.id), 'result.provider.id: invalid identifier')
|
|
291
|
+
checks.check(text(result.provider.model, 128), 'result.provider.model: expected nonempty model within limit')
|
|
292
|
+
}
|
|
293
|
+
checks.check(result.authority === 'proposal-only', 'result.authority: proposal-only required')
|
|
294
|
+
checks.check(result.mode === 'shadow' || result.mode === 'advisory', 'result.mode: unsupported mode')
|
|
295
|
+
checks.check(typeof result.elapsedMs === 'number' && Number.isFinite(result.elapsedMs) && result.elapsedMs >= 0, 'result.elapsedMs: expected finite nonnegative number')
|
|
296
|
+
if (result.usage !== null && closed(result.usage, ['inputTokens', 'outputTokens'], [], 'result.usage', checks)) {
|
|
297
|
+
checks.check([result.usage.inputTokens, result.usage.outputTokens].every((value) => Number.isSafeInteger(value) && value >= 0), 'result.usage: expected nonnegative safe integers')
|
|
298
|
+
}
|
|
299
|
+
if (!record(result.answers, 0, 64, 'result.answers', checks)) return
|
|
300
|
+
if (result.status === 'assessed') {
|
|
301
|
+
checks.check(!Object.hasOwn(result, 'reason'), 'result.reason: not allowed for assessed result')
|
|
302
|
+
inspectAnswers(request.questions, result.answers, checks, 'result.answers')
|
|
303
|
+
} else if (result.status === 'abstained') {
|
|
304
|
+
checks.check(Object.keys(result.answers).length === 0, 'result.answers: abstention must have no answers')
|
|
305
|
+
checks.check(REASONS.has(result.reason), 'result.reason: unsupported abstention reason')
|
|
306
|
+
} else {
|
|
307
|
+
checks.check(false, 'result.status: unsupported status')
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/** Validate integrity and distributions; validity never establishes correctness. */
|
|
312
|
+
export function validateDecisionResult(input, output) {
|
|
313
|
+
const { request, checks } = inspected(input)
|
|
314
|
+
if (checks.errors.length) return { ok: false, errors: ['request: invalid decision request'] }
|
|
315
|
+
try {
|
|
316
|
+
inspectResult(request, jsonSnapshot(output), checks)
|
|
317
|
+
} catch {
|
|
318
|
+
checks.check(false, 'result: expected bounded plain JSON')
|
|
319
|
+
}
|
|
320
|
+
return { ok: checks.errors.length === 0, errors: checks.errors }
|
|
321
|
+
}
|
|
@@ -35,6 +35,19 @@ export function compileDisclosurePatterns(patternDocs = []) {
|
|
|
35
35
|
})
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
export function scanDisclosureText(text, { denylistPatterns = [] } = {}) {
|
|
39
|
+
if (typeof text !== 'string' || Buffer.byteLength(text) > 4 * 1024 * 1024) throw new Error('disclosure text unavailable or exceeds bounds')
|
|
40
|
+
const findings = []
|
|
41
|
+
const patterns = [...STRUCTURAL_DISCLOSURE_PATTERNS, ...denylistPatterns]
|
|
42
|
+
for (const [index,line] of text.split('\n').entries()) {
|
|
43
|
+
for (const {pattern,label} of patterns) {
|
|
44
|
+
pattern.lastIndex = 0
|
|
45
|
+
if (pattern.test(line)) findings.push({label,line:index+1})
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return {ok:findings.length===0,findings}
|
|
49
|
+
}
|
|
50
|
+
|
|
38
51
|
export function scanDisclosureContent({
|
|
39
52
|
root = process.cwd(),
|
|
40
53
|
staged = false,
|
|
@@ -191,8 +191,23 @@ function formatAjvError(error) {
|
|
|
191
191
|
return `${location} ${error.message ?? 'failed schema validation'}`
|
|
192
192
|
}
|
|
193
193
|
|
|
194
|
+
// One compiled validator per schema object, for as long as the schema lives:
|
|
195
|
+
// compilation is a pure function of the schema, and a registered contract is
|
|
196
|
+
// validated many times per run (a large manifest, every journal entry).
|
|
197
|
+
const compiledValidators = new WeakMap()
|
|
198
|
+
|
|
199
|
+
function validatorFor(schema) {
|
|
200
|
+
if (schema === null || typeof schema !== 'object') return ajvForSchema().compile(schema)
|
|
201
|
+
let validate = compiledValidators.get(schema)
|
|
202
|
+
if (!validate) {
|
|
203
|
+
validate = ajvForSchema().compile(schema)
|
|
204
|
+
compiledValidators.set(schema, validate)
|
|
205
|
+
}
|
|
206
|
+
return validate
|
|
207
|
+
}
|
|
208
|
+
|
|
194
209
|
export function validateJsonSchema(schema, doc) {
|
|
195
|
-
const validate =
|
|
210
|
+
const validate = validatorFor(schema)
|
|
196
211
|
if (validate(doc)) return []
|
|
197
212
|
return asArray(validate.errors).map(formatAjvError)
|
|
198
213
|
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import fs from 'node:fs'
|
|
2
|
+
import { BASE_MIGRATIONS } from '../upgrade/upgrade.mjs'
|
|
3
|
+
import { validReviewArtifact } from '../collaboration/review-contracts.mjs'
|
|
4
|
+
import { fileURLToPath } from 'node:url'
|
|
5
|
+
import { readBoundedSource } from '../readiness-protocols/source-read.mjs'
|
|
6
|
+
|
|
7
|
+
const rootVersion = JSON.parse(
|
|
8
|
+
fs.readFileSync(
|
|
9
|
+
fileURLToPath(new URL('../../package.json', import.meta.url)),
|
|
10
|
+
'utf8',
|
|
11
|
+
),
|
|
12
|
+
).version
|
|
13
|
+
|
|
14
|
+
export function inspectPackLifecycle(project, packs) {
|
|
15
|
+
const declared =
|
|
16
|
+
project.config.ext?.['mnstry.atelier']?.extensionPackLifecycle
|
|
17
|
+
if (!declared)
|
|
18
|
+
return {
|
|
19
|
+
ok: packs.length === 0,
|
|
20
|
+
schema: 'atelier-pack-lifecycle-report@v1',
|
|
21
|
+
rootVersion,
|
|
22
|
+
entries: packs.map((pack) => ({
|
|
23
|
+
id: pack.id,
|
|
24
|
+
status: 'legacy-unqualified',
|
|
25
|
+
admitted: false,
|
|
26
|
+
})),
|
|
27
|
+
errors: packs.length
|
|
28
|
+
? [
|
|
29
|
+
'extension-pack lifecycle declaration required for evidence-bound review',
|
|
30
|
+
]
|
|
31
|
+
: [],
|
|
32
|
+
}
|
|
33
|
+
let policy
|
|
34
|
+
try {
|
|
35
|
+
policy = JSON.parse(readBoundedSource(project.configDir, declared).text)
|
|
36
|
+
} catch {
|
|
37
|
+
return {
|
|
38
|
+
ok: false,
|
|
39
|
+
errors: ['lifecycle declaration unavailable or unsafe'],
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (
|
|
43
|
+
!validReviewArtifact('lifecycle', policy) ||
|
|
44
|
+
policy.schema !== 'atelier-pack-lifecycle@v1' ||
|
|
45
|
+
!Array.isArray(policy.packs) ||
|
|
46
|
+
policy.packs.length > 100 ||
|
|
47
|
+
Object.keys(policy).some(
|
|
48
|
+
(key) => !['schema', 'packs', 'ext', 'contractVersion'].includes(key),
|
|
49
|
+
)
|
|
50
|
+
)
|
|
51
|
+
return { ok: false, errors: ['invalid lifecycle declaration'] }
|
|
52
|
+
const ids = new Set(),
|
|
53
|
+
errors = []
|
|
54
|
+
for (const entry of policy.packs) {
|
|
55
|
+
if (
|
|
56
|
+
!entry ||
|
|
57
|
+
typeof entry.id !== 'string' ||
|
|
58
|
+
ids.has(entry.id) ||
|
|
59
|
+
!Array.isArray(entry.compatibleRootVersions) ||
|
|
60
|
+
!entry.compatibleRootVersions.length ||
|
|
61
|
+
entry.compatibleRootVersions.some(
|
|
62
|
+
(version) => typeof version !== 'string',
|
|
63
|
+
) ||
|
|
64
|
+
!['active', 'deprecated', 'retired'].includes(entry.status) ||
|
|
65
|
+
typeof entry.version !== 'string' ||
|
|
66
|
+
!/^sha256:[a-f0-9]{64}$/.test(entry.digest ?? '') ||
|
|
67
|
+
Object.keys(entry).some(
|
|
68
|
+
(key) =>
|
|
69
|
+
![
|
|
70
|
+
'id',
|
|
71
|
+
'version',
|
|
72
|
+
'digest',
|
|
73
|
+
'compatibleRootVersions',
|
|
74
|
+
'status',
|
|
75
|
+
'replacement',
|
|
76
|
+
'migrationId',
|
|
77
|
+
'ext',
|
|
78
|
+
].includes(key),
|
|
79
|
+
)
|
|
80
|
+
)
|
|
81
|
+
errors.push('invalid or duplicate lifecycle pack entry')
|
|
82
|
+
if (
|
|
83
|
+
entry?.migrationId &&
|
|
84
|
+
!BASE_MIGRATIONS.some(
|
|
85
|
+
(migration) =>
|
|
86
|
+
migration.id === entry.migrationId &&
|
|
87
|
+
migration.class === 'extension_pack' &&
|
|
88
|
+
migration.active !== false,
|
|
89
|
+
)
|
|
90
|
+
)
|
|
91
|
+
errors.push('unknown pack migration; owner migration unavailable')
|
|
92
|
+
ids.add(entry?.id)
|
|
93
|
+
}
|
|
94
|
+
const entries = packs.map((pack) => {
|
|
95
|
+
const entry = policy.packs.find((item) => item?.id === pack.id)
|
|
96
|
+
const admitted = Boolean(
|
|
97
|
+
entry &&
|
|
98
|
+
entry.version === pack.version &&
|
|
99
|
+
entry.digest === pack.digest &&
|
|
100
|
+
entry.compatibleRootVersions?.includes(rootVersion) &&
|
|
101
|
+
entry.status !== 'retired',
|
|
102
|
+
)
|
|
103
|
+
if (!admitted)
|
|
104
|
+
errors.push(`pack ${pack.id} is not qualified for new execution`)
|
|
105
|
+
return {
|
|
106
|
+
id: pack.id,
|
|
107
|
+
status: entry?.status ?? 'legacy-unqualified',
|
|
108
|
+
admitted,
|
|
109
|
+
replacement: entry?.replacement ?? null,
|
|
110
|
+
migrationId: entry?.migrationId ?? null,
|
|
111
|
+
}
|
|
112
|
+
})
|
|
113
|
+
return {
|
|
114
|
+
ok: errors.length === 0,
|
|
115
|
+
schema: 'atelier-pack-lifecycle-report@v1',
|
|
116
|
+
rootVersion,
|
|
117
|
+
entries,
|
|
118
|
+
errors,
|
|
119
|
+
policy,
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// This report names the existing data-only upgrade operation. It neither
|
|
124
|
+
// changes declarations nor admits a replacement; the source owner applies it.
|
|
125
|
+
export function planPackLifecycleMigration(project, packs) {
|
|
126
|
+
const report = inspectPackLifecycle(project, packs)
|
|
127
|
+
const migration = BASE_MIGRATIONS.find(
|
|
128
|
+
(entry) => entry.class === 'extension_pack' && entry.active !== false,
|
|
129
|
+
)
|
|
130
|
+
return {
|
|
131
|
+
ok: report.ok,
|
|
132
|
+
schema: 'atelier-pack-migration-plan@v1',
|
|
133
|
+
dryRun: true,
|
|
134
|
+
writes: false,
|
|
135
|
+
current: report,
|
|
136
|
+
migration: migration
|
|
137
|
+
? {
|
|
138
|
+
id: migration.id,
|
|
139
|
+
files: migration.files,
|
|
140
|
+
requiredPostChecks: migration.requiredPostChecks,
|
|
141
|
+
authority: migration.authority,
|
|
142
|
+
}
|
|
143
|
+
: null,
|
|
144
|
+
steps: [
|
|
145
|
+
'Retain the prior package, pack declarations, lock and historical records.',
|
|
146
|
+
'Review replacement term and protocol meanings in the source owner workflow.',
|
|
147
|
+
'Update the declared version, digest and exact compatible root versions together.',
|
|
148
|
+
'Use the existing upgrade migration to verify and refresh the lock after owner review.',
|
|
149
|
+
'Create a new evidence-bound run; old runs retain their original meaning.',
|
|
150
|
+
],
|
|
151
|
+
rollback:
|
|
152
|
+
'Restore the prior declarations, package and lock together. Retain contribution and evidence history.',
|
|
153
|
+
}
|
|
154
|
+
}
|
package/src/graph/graph.mjs
CHANGED
|
@@ -6,9 +6,12 @@ import {
|
|
|
6
6
|
REPO_ACCESS_SCHEMA,
|
|
7
7
|
VALID_RELATION_TYPES,
|
|
8
8
|
buildKnowledgeGraph,
|
|
9
|
+
createGraphFileCache,
|
|
9
10
|
parseYamlSubset,
|
|
10
11
|
} from './knowledge-graph.mjs'
|
|
11
12
|
|
|
13
|
+
export { createGraphFileCache }
|
|
14
|
+
|
|
12
15
|
// Compatibility exports. Parsing, census, validation, and diagnostics all live
|
|
13
16
|
// in knowledge-graph.mjs; this module only projects its canonical result into
|
|
14
17
|
// the long-standing `atelier graph` artifact shape.
|
|
@@ -100,12 +103,71 @@ function externalRepos(project) {
|
|
|
100
103
|
.sort((a, b) => String(a.name).localeCompare(String(b.name)))
|
|
101
104
|
}
|
|
102
105
|
|
|
103
|
-
function canonicalBuild(project) {
|
|
106
|
+
function canonicalBuild(project, overrides = {}) {
|
|
104
107
|
const input = canonicalInput(project)
|
|
105
|
-
const result = buildKnowledgeGraph(input.options)
|
|
108
|
+
const result = buildKnowledgeGraph({ ...input.options, ...overrides })
|
|
106
109
|
return { input, result }
|
|
107
110
|
}
|
|
108
111
|
|
|
112
|
+
// The supported project-to-options seam: the exact buildKnowledgeGraph options
|
|
113
|
+
// every graph consumer derives from a loaded project, plus the configured
|
|
114
|
+
// repositories that could not be read.
|
|
115
|
+
export function canonicalGraphOptions(project) {
|
|
116
|
+
return canonicalInput(project)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const canonicalEdgeId = (edge) => JSON.stringify([edge.source, edge.type, edge.target])
|
|
120
|
+
|
|
121
|
+
// The canonical graph for a loaded project, for consumers that need derived
|
|
122
|
+
// edges and the source offsets behind them. Nothing is resolved here: nodes,
|
|
123
|
+
// edges, link occurrences and link findings all come from
|
|
124
|
+
// buildKnowledgeGraph. Each edge gains a stable id and an origin; a derived
|
|
125
|
+
// edge carries every source occurrence that produced it. Embedded assets are
|
|
126
|
+
// reported beside the graph, like link occurrences: `embeds` holds one record
|
|
127
|
+
// per embed occurrence and `assets` each embedded file once. Neither adds a
|
|
128
|
+
// node or an edge.
|
|
129
|
+
// `fileCache` (createGraphFileCache) lets repeated builds of one workspace
|
|
130
|
+
// reuse the per-file census and link scan of every Markdown source whose
|
|
131
|
+
// bytes did not change; the result is the same as without it. `observedDigest`
|
|
132
|
+
// (see createGraphFileCache) lets a caller that observes sources by digest
|
|
133
|
+
// spare the read of a source it knows unchanged.
|
|
134
|
+
export function buildCanonicalGraph(project, { isLinkTargetEligible, isAssetEligible, fileCache = null, observedDigest = null } = {}) {
|
|
135
|
+
const { input, result } = canonicalBuild(project, {
|
|
136
|
+
...(isLinkTargetEligible ? { isLinkTargetEligible } : {}),
|
|
137
|
+
...(isAssetEligible ? { isAssetEligible } : {}),
|
|
138
|
+
...(fileCache ? { fileCache } : {}),
|
|
139
|
+
...(observedDigest ? { observedDigest } : {}),
|
|
140
|
+
})
|
|
141
|
+
const canonical = result.workspaceGraph ?? { nodes: [], edges: [], diagnostics: [] }
|
|
142
|
+
const occurrences = new Map()
|
|
143
|
+
for (const link of result.resolvedLinks ?? []) {
|
|
144
|
+
const id = canonicalEdgeId(link)
|
|
145
|
+
if (!occurrences.has(id)) occurrences.set(id, [])
|
|
146
|
+
occurrences.get(id).push(link)
|
|
147
|
+
}
|
|
148
|
+
const edges = canonical.edges.map((edge) => {
|
|
149
|
+
const id = canonicalEdgeId(edge)
|
|
150
|
+
return edge.declared === true
|
|
151
|
+
? { id, ...edge, origin: 'declared' }
|
|
152
|
+
: { id, ...edge, origin: 'ordinary-link', occurrences: occurrences.get(id) ?? [] }
|
|
153
|
+
})
|
|
154
|
+
const embeds = result.resolvedEmbeds ?? []
|
|
155
|
+
const assets = [...new Map(embeds.map((embed) => [embed.asset.id, embed.asset])).values()].sort((a, b) => (a.id < b.id ? -1 : a.id > b.id ? 1 : 0))
|
|
156
|
+
return {
|
|
157
|
+
ok: result.ok && input.missing.length === 0,
|
|
158
|
+
errors: [...input.missing, ...result.errors],
|
|
159
|
+
options: input.options,
|
|
160
|
+
nodes: canonical.nodes,
|
|
161
|
+
edges,
|
|
162
|
+
diagnostics: canonical.diagnostics,
|
|
163
|
+
links: result.resolvedLinks ?? [],
|
|
164
|
+
embeds,
|
|
165
|
+
assets,
|
|
166
|
+
linkDiagnostics: result.linkDiagnostics ?? [],
|
|
167
|
+
...(result.fileCensus ? { fileCensus: result.fileCensus } : {}),
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
109
171
|
export function buildGraph(project) {
|
|
110
172
|
const { input, result } = canonicalBuild(project)
|
|
111
173
|
const canonical = result.workspaceGraph ?? { nodes: [], edges: [], diagnostics: [] }
|