@mnstry/atelier 0.2.0-alpha.1 → 0.2.0-alpha.10
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 +372 -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 +690 -0
- package/docs/obsidian.md +423 -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 +146 -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 +160 -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 +129 -0
- package/src/runtime/obsidian/app-production-seams.mjs +105 -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 +263 -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 +91 -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,96 @@
|
|
|
1
|
+
export type JsonValue = null | boolean | number | string | JsonValue[] | JsonObject
|
|
2
|
+
export interface JsonObject { [key: string]: JsonValue }
|
|
3
|
+
export interface DecisionExtension { ext?: JsonObject }
|
|
4
|
+
export interface DecisionScope extends DecisionExtension {
|
|
5
|
+
workspaceId: string
|
|
6
|
+
/** Host-reported provenance; this reference does not grant authorization. */
|
|
7
|
+
authorizationRef: string
|
|
8
|
+
}
|
|
9
|
+
export interface DecisionEvidence extends DecisionExtension {
|
|
10
|
+
id: string
|
|
11
|
+
sourceRef: string
|
|
12
|
+
}
|
|
13
|
+
export interface DecisionQuestionBase extends DecisionExtension {
|
|
14
|
+
instructions: string
|
|
15
|
+
evidenceIds: string[]
|
|
16
|
+
}
|
|
17
|
+
export interface ChoiceQuestion extends DecisionQuestionBase {
|
|
18
|
+
type: 'choice'
|
|
19
|
+
criteria: Record<string, string>
|
|
20
|
+
}
|
|
21
|
+
export interface ScoreQuestion extends DecisionQuestionBase {
|
|
22
|
+
type: 'score'
|
|
23
|
+
criteria: string[]
|
|
24
|
+
}
|
|
25
|
+
export interface BooleanQuestion extends DecisionQuestionBase {
|
|
26
|
+
type: 'boolean'
|
|
27
|
+
criteria: DecisionExtension & { true: string; false: string }
|
|
28
|
+
}
|
|
29
|
+
export type DecisionQuestion = ChoiceQuestion | ScoreQuestion | BooleanQuestion
|
|
30
|
+
export interface DecisionRequest extends DecisionExtension {
|
|
31
|
+
schema: 'atelier-decision-request@v1'
|
|
32
|
+
contractVersion?: `1.${number}.${number}`
|
|
33
|
+
id: string
|
|
34
|
+
task: string
|
|
35
|
+
rubricVersion: string
|
|
36
|
+
scope: DecisionScope
|
|
37
|
+
state: string
|
|
38
|
+
evidence: DecisionEvidence[]
|
|
39
|
+
questions: Record<string, DecisionQuestion>
|
|
40
|
+
}
|
|
41
|
+
export interface ChoiceAnswer extends DecisionExtension {
|
|
42
|
+
type: 'choice'
|
|
43
|
+
choice: string
|
|
44
|
+
probabilities: Record<string, number>
|
|
45
|
+
/** A provider statistic, not a measured probability of semantic correctness. */
|
|
46
|
+
confidence: number
|
|
47
|
+
}
|
|
48
|
+
export interface ScoreAnswer extends DecisionExtension {
|
|
49
|
+
type: 'score'
|
|
50
|
+
/** The zero-index weighted expectation of the ordered criteria. */
|
|
51
|
+
score: number
|
|
52
|
+
probabilities: number[]
|
|
53
|
+
confidence: number
|
|
54
|
+
}
|
|
55
|
+
export interface BooleanAnswer extends DecisionExtension {
|
|
56
|
+
type: 'boolean'
|
|
57
|
+
probability: number
|
|
58
|
+
}
|
|
59
|
+
export type DecisionAnswer = ChoiceAnswer | ScoreAnswer | BooleanAnswer
|
|
60
|
+
export interface DecisionUsage extends DecisionExtension {
|
|
61
|
+
inputTokens: number
|
|
62
|
+
outputTokens: number
|
|
63
|
+
}
|
|
64
|
+
export interface DecisionResultBase extends DecisionExtension {
|
|
65
|
+
schema: 'atelier-decision-result@v1'
|
|
66
|
+
contractVersion?: `1.${number}.${number}`
|
|
67
|
+
requestId: string
|
|
68
|
+
requestDigest: string
|
|
69
|
+
task: string
|
|
70
|
+
rubricVersion: string
|
|
71
|
+
scope: DecisionScope
|
|
72
|
+
provider: DecisionExtension & { id: string; model: string }
|
|
73
|
+
authority: 'proposal-only'
|
|
74
|
+
mode: 'shadow' | 'advisory'
|
|
75
|
+
usage: DecisionUsage | null
|
|
76
|
+
elapsedMs: number
|
|
77
|
+
}
|
|
78
|
+
export interface AssessedDecisionResult extends DecisionResultBase {
|
|
79
|
+
status: 'assessed'
|
|
80
|
+
answers: Record<string, DecisionAnswer>
|
|
81
|
+
reason?: never
|
|
82
|
+
}
|
|
83
|
+
export type DecisionAbstentionReason = 'insufficient-evidence' | 'ambiguous' | 'no-match' | 'budget-exhausted' | 'provider-unavailable' | 'timeout' | 'invalid-response' | 'unauthorized'
|
|
84
|
+
export interface AbstainedDecisionResult extends DecisionResultBase {
|
|
85
|
+
status: 'abstained'
|
|
86
|
+
answers: Record<string, never>
|
|
87
|
+
reason: DecisionAbstentionReason
|
|
88
|
+
}
|
|
89
|
+
export type DecisionResult = AssessedDecisionResult | AbstainedDecisionResult
|
|
90
|
+
export interface DecisionValidation { ok: boolean; errors: string[] }
|
|
91
|
+
export function validateDecisionRequest(input: unknown): DecisionValidation
|
|
92
|
+
export function validateDecisionResult(request: unknown, result: unknown): DecisionValidation
|
|
93
|
+
/** Checks questions and answers without state, hashing, or evidence membership. */
|
|
94
|
+
export function validateDecisionAnswers(questions: unknown, answers: unknown): DecisionValidation
|
|
95
|
+
/** Throws a generic TypeError for invalid input. The digest is lowercase SHA-256. */
|
|
96
|
+
export function decisionRequestDigest(request: unknown): string
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process'
|
|
2
|
+
import fs from 'node:fs'
|
|
3
|
+
import path from 'node:path'
|
|
4
|
+
|
|
5
|
+
export const STRUCTURAL_DISCLOSURE_PATTERNS = Object.freeze([
|
|
6
|
+
{ pattern: new RegExp('\\/Users\\/'), label: 'absolute user path' },
|
|
7
|
+
{ pattern: new RegExp('\\/var\\/folders\\/'), label: 'machine-local temp path' },
|
|
8
|
+
{ pattern: new RegExp('\\.' + 'codex'), label: 'agent-local state path' },
|
|
9
|
+
{
|
|
10
|
+
pattern: new RegExp([
|
|
11
|
+
'BEGIN ',
|
|
12
|
+
'(?:[A-Z0-9]+ ){0,4}',
|
|
13
|
+
'PRIVATE KEY(?: BLOCK)?',
|
|
14
|
+
'|BEGIN (?:RSA|OPENSSH) KEY',
|
|
15
|
+
].join('')),
|
|
16
|
+
label: 'private key material',
|
|
17
|
+
},
|
|
18
|
+
{ pattern: /"d"\s*:\s*"[A-Za-z0-9_-]{20,}"/, label: 'JWK private key material' },
|
|
19
|
+
{
|
|
20
|
+
pattern: /\b(api[_-]?key|secret|password|(?<!id-)token)\b\s*[:=]/i,
|
|
21
|
+
label: 'secret-like assignment',
|
|
22
|
+
},
|
|
23
|
+
])
|
|
24
|
+
|
|
25
|
+
export function compileDisclosurePatterns(patternDocs = []) {
|
|
26
|
+
if (!Array.isArray(patternDocs)) throw new Error('denylist document must contain a patterns array')
|
|
27
|
+
return patternDocs.map(({ pattern, flags = '', label }) => {
|
|
28
|
+
if (typeof label !== 'string' || label.length === 0) throw new Error('denylist pattern label must be non-empty')
|
|
29
|
+
if (typeof pattern !== 'string' || pattern.length === 0) throw new Error(`denylist pattern failed to compile: ${label}`)
|
|
30
|
+
try {
|
|
31
|
+
return { pattern: new RegExp(pattern, String(flags).replace(/[gy]/g, '')), label }
|
|
32
|
+
} catch {
|
|
33
|
+
throw new Error(`denylist pattern failed to compile: ${label}`)
|
|
34
|
+
}
|
|
35
|
+
})
|
|
36
|
+
}
|
|
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
|
+
|
|
51
|
+
export function scanDisclosureContent({
|
|
52
|
+
root = process.cwd(),
|
|
53
|
+
staged = false,
|
|
54
|
+
denylistPatterns = [],
|
|
55
|
+
failOnBinary = false,
|
|
56
|
+
} = {}) {
|
|
57
|
+
const resolvedRoot = path.resolve(root)
|
|
58
|
+
assertGitCheckout(resolvedRoot)
|
|
59
|
+
const patterns = [...STRUCTURAL_DISCLOSURE_PATTERNS, ...denylistPatterns]
|
|
60
|
+
const findings = []
|
|
61
|
+
const skippedBinary = []
|
|
62
|
+
let scannedFiles = 0
|
|
63
|
+
|
|
64
|
+
for (const entry of stagedEntries(resolvedRoot, staged)) {
|
|
65
|
+
const buffer = entry.buffer ?? readTrackedFile(resolvedRoot, entry.path)
|
|
66
|
+
if (buffer === null) continue
|
|
67
|
+
const text = decodeText(buffer)
|
|
68
|
+
if (text === null) {
|
|
69
|
+
skippedBinary.push(entry.path)
|
|
70
|
+
if (failOnBinary) findings.push({ label: 'binary content cannot be disclosure-scanned', path: entry.path, line: null })
|
|
71
|
+
continue
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
scannedFiles += 1
|
|
75
|
+
const lines = text.split('\n')
|
|
76
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
77
|
+
for (const { pattern, label } of patterns) {
|
|
78
|
+
if (pattern.test(lines[index])) findings.push({ label, path: entry.path, line: index + 1 })
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
ok: findings.length === 0,
|
|
85
|
+
staged,
|
|
86
|
+
scannedFiles,
|
|
87
|
+
skippedBinary,
|
|
88
|
+
findings,
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function decodeText(buffer) {
|
|
93
|
+
if (buffer.includes(0)) return null
|
|
94
|
+
try {
|
|
95
|
+
return new TextDecoder('utf-8', { fatal: true }).decode(buffer)
|
|
96
|
+
} catch {
|
|
97
|
+
return null
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function isPathTracked(root, candidatePath) {
|
|
102
|
+
const resolvedRoot = path.resolve(root)
|
|
103
|
+
const resolvedCandidate = path.resolve(candidatePath)
|
|
104
|
+
const relative = path.relative(resolvedRoot, resolvedCandidate)
|
|
105
|
+
if (relative.startsWith('..') || path.isAbsolute(relative)) return false
|
|
106
|
+
const gitRelative = relative.split(path.sep).join('/')
|
|
107
|
+
const result = execFileSync('git', ['-C', resolvedRoot, 'ls-files', '-z', '--', gitRelative], {
|
|
108
|
+
encoding: 'utf8',
|
|
109
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
110
|
+
})
|
|
111
|
+
if (result.split('\0').filter(Boolean).includes(gitRelative)) return true
|
|
112
|
+
|
|
113
|
+
// Git pathspec matching is case-sensitive even when the checkout filesystem
|
|
114
|
+
// is not. Compare filesystem identities so alternate-cased callers cannot
|
|
115
|
+
// make a tracked file appear local and untracked.
|
|
116
|
+
return execFileSync('git', [
|
|
117
|
+
'-C',
|
|
118
|
+
resolvedRoot,
|
|
119
|
+
'ls-files',
|
|
120
|
+
'-z',
|
|
121
|
+
'--',
|
|
122
|
+
`:(icase,literal)${gitRelative}`,
|
|
123
|
+
], {
|
|
124
|
+
encoding: 'utf8',
|
|
125
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
126
|
+
})
|
|
127
|
+
.split('\0')
|
|
128
|
+
.filter(Boolean)
|
|
129
|
+
.some((trackedPath) => sameExistingFile(resolvedCandidate, path.join(resolvedRoot, trackedPath)))
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function sameExistingFile(left, right) {
|
|
133
|
+
try {
|
|
134
|
+
if (fs.realpathSync.native(left) === fs.realpathSync.native(right)) return true
|
|
135
|
+
const leftStat = fs.statSync(left)
|
|
136
|
+
const rightStat = fs.statSync(right)
|
|
137
|
+
return leftStat.ino !== 0 && leftStat.dev === rightStat.dev && leftStat.ino === rightStat.ino
|
|
138
|
+
} catch {
|
|
139
|
+
return false
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export function isPathIgnored(root, candidatePath) {
|
|
144
|
+
const resolvedRoot = path.resolve(root)
|
|
145
|
+
const relative = path.relative(resolvedRoot, path.resolve(candidatePath))
|
|
146
|
+
if (relative.startsWith('..') || path.isAbsolute(relative)) return false
|
|
147
|
+
const gitRelative = relative.split(path.sep).join('/')
|
|
148
|
+
try {
|
|
149
|
+
execFileSync('git', ['-C', resolvedRoot, 'check-ignore', '--quiet', '--', gitRelative], {
|
|
150
|
+
stdio: ['ignore', 'ignore', 'ignore'],
|
|
151
|
+
})
|
|
152
|
+
return true
|
|
153
|
+
} catch {
|
|
154
|
+
return false
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function assertGitCheckout(root) {
|
|
159
|
+
try {
|
|
160
|
+
execFileSync('git', ['-C', root, 'rev-parse', '--git-dir'], {
|
|
161
|
+
stdio: ['ignore', 'ignore', 'ignore'],
|
|
162
|
+
})
|
|
163
|
+
} catch {
|
|
164
|
+
throw new Error(`disclosure root is not a Git checkout`)
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function stagedEntries(root, staged) {
|
|
169
|
+
if (!staged) {
|
|
170
|
+
return git(root, ['ls-files', '-z'])
|
|
171
|
+
.split('\0')
|
|
172
|
+
.filter(Boolean)
|
|
173
|
+
.map((filePath) => ({ path: filePath }))
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return git(root, ['diff', '--cached', '--name-only', '--diff-filter=ACMR', '-z'])
|
|
177
|
+
.split('\0')
|
|
178
|
+
.filter(Boolean)
|
|
179
|
+
.map((filePath) => ({
|
|
180
|
+
path: filePath,
|
|
181
|
+
buffer: execFileSync('git', ['-C', root, 'show', `:${filePath}`], {
|
|
182
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
183
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
184
|
+
}),
|
|
185
|
+
}))
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function readTrackedFile(root, relativePath) {
|
|
189
|
+
const absolutePath = path.join(root, relativePath)
|
|
190
|
+
let stat
|
|
191
|
+
try {
|
|
192
|
+
stat = fs.lstatSync(absolutePath)
|
|
193
|
+
} catch {
|
|
194
|
+
return null
|
|
195
|
+
}
|
|
196
|
+
if (!stat.isFile()) return null
|
|
197
|
+
return fs.readFileSync(absolutePath)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function git(root, args) {
|
|
201
|
+
return execFileSync('git', ['-C', root, ...args], {
|
|
202
|
+
encoding: 'utf8',
|
|
203
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
204
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
205
|
+
})
|
|
206
|
+
}
|
package/src/egress/check.mjs
CHANGED
|
@@ -1,44 +1,13 @@
|
|
|
1
|
-
import
|
|
2
|
-
import path from 'node:path'
|
|
1
|
+
import { checkForbiddenEgress } from './forbidden-egress.mjs'
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
const SCAN_DIRS = ['bin', 'src']
|
|
6
|
-
const FORBIDDEN = [
|
|
7
|
-
{ pattern: /fetch\(\s*['"]https?:\/\/(?!127\.0\.0\.1|localhost|\[::1\])/, label: 'non-localhost fetch' },
|
|
8
|
-
{ pattern: /https?\.request\s*\(/, label: 'http request primitive' },
|
|
9
|
-
{ pattern: /new\s+WebSocket\s*\(/, label: 'websocket egress primitive' },
|
|
10
|
-
{ pattern: /net\.connect\s*\(/, label: 'net connect primitive' },
|
|
11
|
-
{ pattern: /dns\./, label: 'dns primitive' },
|
|
12
|
-
{ pattern: /\bcurl\s+https?:\/\//, label: 'shell network call' },
|
|
13
|
-
]
|
|
14
|
-
|
|
15
|
-
function walk(dir, files = []) {
|
|
16
|
-
if (!fs.existsSync(dir)) return files
|
|
17
|
-
for (const ent of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
18
|
-
const abs = path.join(dir, ent.name)
|
|
19
|
-
if (ent.isDirectory()) walk(abs, files)
|
|
20
|
-
else if (/\.(mjs|js|sh)$/.test(ent.name)) files.push(abs)
|
|
21
|
-
}
|
|
22
|
-
return files
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function checkForbiddenEgress(root = ROOT) {
|
|
26
|
-
const failures = []
|
|
27
|
-
for (const rel of SCAN_DIRS) {
|
|
28
|
-
for (const file of walk(path.join(root, rel))) {
|
|
29
|
-
const text = fs.readFileSync(file, 'utf8')
|
|
30
|
-
for (const rule of FORBIDDEN) {
|
|
31
|
-
if (rule.pattern.test(text)) failures.push(`${path.relative(root, file)}:${rule.label}`)
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
return failures
|
|
36
|
-
}
|
|
3
|
+
export { checkForbiddenEgress } from './forbidden-egress.mjs'
|
|
37
4
|
|
|
38
5
|
export function runEgressCommand() {
|
|
39
|
-
const
|
|
40
|
-
if (
|
|
41
|
-
|
|
6
|
+
const findings = checkForbiddenEgress()
|
|
7
|
+
if (findings.length) {
|
|
8
|
+
for (const finding of findings) {
|
|
9
|
+
console.error(`[atelier-egress] ${finding.file}:${finding.line} ${finding.type}: ${finding.detail}`)
|
|
10
|
+
}
|
|
42
11
|
process.exit(1)
|
|
43
12
|
}
|
|
44
13
|
console.log('[egress:check] no forbidden non-localhost egress found in package runtime paths')
|