@mannyc1/ts-release 0.0.6 → 0.2.2
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/ARCHITECTURE.md +124 -87
- package/CHANGELOG.md +115 -0
- package/LICENSE +21 -0
- package/README.md +272 -540
- package/SPEC.md +206 -305
- package/apps/release-ts/README.md +32 -0
- package/apps/release-ts/src/cli/command.ts +164 -0
- package/apps/release-ts/src/cli/commands.ts +351 -0
- package/apps/release-ts/src/cli/main.ts +43 -0
- package/apps/release-ts/src/cli/node-main.ts +49 -0
- package/apps/ts-release-agents/LICENSE +21 -0
- package/apps/ts-release-agents/README.md +21 -0
- package/apps/ts-release-agents/evals/cases.json +11 -0
- package/apps/ts-release-agents/manifests/claude.json +5 -0
- package/apps/ts-release-agents/manifests/codex.json +5 -0
- package/apps/ts-release-agents/package.json +11 -0
- package/apps/ts-release-agents/skills/release/SKILL.md +48 -0
- package/apps/ts-release-agents/skills/release/references/configuration.md +18 -0
- package/apps/ts-release-agents/skills/release/references/recovery.md +10 -0
- package/apps/ts-release-agents/skills/release/references/staged-workflow.md +12 -0
- package/apps/ts-release-agents/skills/release/references/target-selection.md +13 -0
- package/apps/ts-release-agents/skills/release/references/verification.md +14 -0
- package/apps/ts-release-agents/src/build.ts +105 -0
- package/apps/ts-release-agents/src/check.ts +117 -0
- package/apps/ts-release-agents/src/install.ts +121 -0
- package/apps/ts-release-agents/test/contract.test.ts +56 -0
- package/apps/ts-release-agents/tsconfig.json +5 -0
- package/dist/api/api.d.ts +15 -0
- package/dist/api/api.d.ts.map +1 -0
- package/dist/api/api.js +244 -0
- package/dist/api/api.js.map +1 -0
- package/dist/api/errors.d.ts +39 -0
- package/dist/api/errors.d.ts.map +1 -0
- package/dist/api/errors.js +32 -0
- package/dist/api/errors.js.map +1 -0
- package/dist/api/input.d.ts +10 -0
- package/dist/api/input.d.ts.map +1 -0
- package/dist/api/input.js +73 -0
- package/dist/api/input.js.map +1 -0
- package/dist/api/runtime.d.ts +18 -0
- package/dist/api/runtime.d.ts.map +1 -0
- package/dist/api/runtime.js +7 -0
- package/dist/api/runtime.js.map +1 -0
- package/dist/api/types.d.ts +76 -0
- package/dist/api/types.d.ts.map +1 -0
- package/dist/api/types.js +17 -0
- package/dist/api/types.js.map +1 -0
- package/dist/bin/ts-release.js +39239 -0
- package/dist/capabilities/bun-targets.d.ts +3 -0
- package/dist/capabilities/bun-targets.d.ts.map +1 -0
- package/dist/capabilities/bun-targets.js +3 -0
- package/dist/capabilities/bun-targets.js.map +1 -0
- package/dist/capabilities/field-ownership.d.ts +17 -0
- package/dist/capabilities/field-ownership.d.ts.map +1 -0
- package/dist/capabilities/field-ownership.js +40 -0
- package/dist/capabilities/field-ownership.js.map +1 -0
- package/dist/capabilities/module.d.ts +91 -0
- package/dist/capabilities/module.d.ts.map +1 -0
- package/dist/capabilities/module.js +2 -0
- package/dist/capabilities/module.js.map +1 -0
- package/dist/capabilities/registry.d.ts +829 -0
- package/dist/capabilities/registry.d.ts.map +1 -0
- package/dist/capabilities/registry.js +291 -0
- package/dist/capabilities/registry.js.map +1 -0
- package/dist/config/config.d.ts +6 -0
- package/dist/config/config.d.ts.map +1 -0
- package/dist/config/config.js +42 -0
- package/dist/config/config.js.map +1 -0
- package/dist/correction/coordinator.d.ts +248 -0
- package/dist/correction/coordinator.d.ts.map +1 -0
- package/dist/correction/coordinator.js +274 -0
- package/dist/correction/coordinator.js.map +1 -0
- package/dist/correction/index.d.ts +3 -0
- package/dist/correction/index.d.ts.map +1 -0
- package/dist/correction/index.js +3 -0
- package/dist/correction/index.js.map +1 -0
- package/dist/correction/intent.d.ts +111 -0
- package/dist/correction/intent.d.ts.map +1 -0
- package/dist/correction/intent.js +146 -0
- package/dist/correction/intent.js.map +1 -0
- package/dist/drivers/archive.d.ts +8 -0
- package/dist/drivers/archive.d.ts.map +1 -0
- package/dist/drivers/archive.js +101 -0
- package/dist/drivers/archive.js.map +1 -0
- package/dist/drivers/contain.d.ts +6 -0
- package/dist/drivers/contain.d.ts.map +1 -0
- package/dist/drivers/contain.js +17 -0
- package/dist/drivers/contain.js.map +1 -0
- package/dist/drivers/environment.d.ts +4 -0
- package/dist/drivers/environment.d.ts.map +1 -0
- package/dist/drivers/environment.js +18 -0
- package/dist/drivers/environment.js.map +1 -0
- package/dist/drivers/errors.d.ts +9 -0
- package/dist/drivers/errors.d.ts.map +1 -0
- package/dist/drivers/errors.js +6 -0
- package/dist/drivers/errors.js.map +1 -0
- package/dist/drivers/glob.d.ts +2 -0
- package/dist/drivers/glob.d.ts.map +1 -0
- package/dist/drivers/glob.js +162 -0
- package/dist/drivers/glob.js.map +1 -0
- package/dist/drivers/process.d.ts +48 -0
- package/dist/drivers/process.d.ts.map +1 -0
- package/dist/drivers/process.js +257 -0
- package/dist/drivers/process.js.map +1 -0
- package/dist/drivers/redact.d.ts +4 -0
- package/dist/drivers/redact.d.ts.map +1 -0
- package/dist/drivers/redact.js +24 -0
- package/dist/drivers/redact.js.map +1 -0
- package/dist/drivers/seccomp-helper-source.d.ts +7 -0
- package/dist/drivers/seccomp-helper-source.d.ts.map +1 -0
- package/dist/drivers/seccomp-helper-source.js +121 -0
- package/dist/drivers/seccomp-helper-source.js.map +1 -0
- package/dist/drivers/utils.d.ts +4 -0
- package/dist/drivers/utils.d.ts.map +1 -0
- package/dist/drivers/utils.js +5 -0
- package/dist/drivers/utils.js.map +1 -0
- package/dist/drivers/workspace.d.ts +6 -0
- package/dist/drivers/workspace.d.ts.map +1 -0
- package/dist/drivers/workspace.js +49 -0
- package/dist/drivers/workspace.js.map +1 -0
- package/dist/extensions/provider-adapter.d.ts +48 -0
- package/dist/extensions/provider-adapter.d.ts.map +1 -0
- package/dist/extensions/provider-adapter.js +64 -0
- package/dist/extensions/provider-adapter.js.map +1 -0
- package/dist/host.d.ts +33 -0
- package/dist/host.d.ts.map +1 -0
- package/dist/host.js +20 -0
- package/dist/host.js.map +1 -0
- package/dist/index.d.ts +19 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -1
- package/dist/index.js.map +1 -1
- package/dist/model/artifact-collection.d.ts +84 -0
- package/dist/model/artifact-collection.d.ts.map +1 -0
- package/dist/model/artifact-collection.js +185 -0
- package/dist/model/artifact-collection.js.map +1 -0
- package/dist/model/authority.d.ts +89 -0
- package/dist/model/authority.d.ts.map +1 -0
- package/dist/model/authority.js +80 -0
- package/dist/model/authority.js.map +1 -0
- package/dist/model/bun-targets.d.ts +34 -0
- package/dist/model/bun-targets.d.ts.map +1 -0
- package/dist/model/bun-targets.js +17 -0
- package/dist/model/bun-targets.js.map +1 -0
- package/dist/model/canonical.d.ts +8 -0
- package/dist/model/canonical.d.ts.map +1 -0
- package/dist/model/canonical.js +205 -0
- package/dist/model/canonical.js.map +1 -0
- package/dist/model/catalog.d.ts +76 -0
- package/dist/model/catalog.d.ts.map +1 -0
- package/dist/model/catalog.js +174 -0
- package/dist/model/catalog.js.map +1 -0
- package/dist/model/digest.d.ts +53 -0
- package/dist/model/digest.d.ts.map +1 -0
- package/dist/model/digest.js +127 -0
- package/dist/model/digest.js.map +1 -0
- package/dist/model/errors.d.ts +14 -0
- package/dist/model/errors.d.ts.map +1 -0
- package/dist/model/errors.js +8 -0
- package/dist/model/errors.js.map +1 -0
- package/dist/model/primitives.d.ts +19 -0
- package/dist/model/primitives.d.ts.map +1 -0
- package/dist/model/primitives.js +18 -0
- package/dist/model/primitives.js.map +1 -0
- package/dist/model/pypi.d.ts +12 -0
- package/dist/model/pypi.d.ts.map +1 -0
- package/dist/model/pypi.js +20 -0
- package/dist/model/pypi.js.map +1 -0
- package/dist/model/python-distribution.d.ts +33 -0
- package/dist/model/python-distribution.d.ts.map +1 -0
- package/dist/model/python-distribution.js +367 -0
- package/dist/model/python-distribution.js.map +1 -0
- package/dist/model/secret-patterns.d.ts +2 -0
- package/dist/model/secret-patterns.d.ts.map +1 -0
- package/dist/model/secret-patterns.js +12 -0
- package/dist/model/secret-patterns.js.map +1 -0
- package/dist/platform/bun.d.ts +7 -0
- package/dist/platform/bun.d.ts.map +1 -0
- package/dist/platform/bun.js +15 -0
- package/dist/platform/bun.js.map +1 -0
- package/dist/platform/credentials.d.ts +32 -0
- package/dist/platform/credentials.d.ts.map +1 -0
- package/dist/platform/credentials.js +772 -0
- package/dist/platform/credentials.js.map +1 -0
- package/dist/platform/host-support.d.ts +2 -0
- package/dist/platform/host-support.d.ts.map +1 -0
- package/dist/platform/host-support.js +4 -0
- package/dist/platform/host-support.js.map +1 -0
- package/dist/platform/node.d.ts +7 -0
- package/dist/platform/node.d.ts.map +1 -0
- package/dist/platform/node.js +15 -0
- package/dist/platform/node.js.map +1 -0
- package/dist/platform/release-runtime.d.ts +11 -0
- package/dist/platform/release-runtime.d.ts.map +1 -0
- package/dist/platform/release-runtime.js +34 -0
- package/dist/platform/release-runtime.js.map +1 -0
- package/dist/platform/services.d.ts +15 -0
- package/dist/platform/services.d.ts.map +1 -0
- package/dist/platform/services.js +30 -0
- package/dist/platform/services.js.map +1 -0
- package/dist/platform/source-observer.d.ts +5 -0
- package/dist/platform/source-observer.d.ts.map +1 -0
- package/dist/platform/source-observer.js +159 -0
- package/dist/platform/source-observer.js.map +1 -0
- package/dist/provider-sdk.d.ts +14 -0
- package/dist/provider-sdk.d.ts.map +1 -0
- package/dist/provider-sdk.js +10 -0
- package/dist/provider-sdk.js.map +1 -0
- package/dist/publication/adapter.d.ts +212 -0
- package/dist/publication/adapter.d.ts.map +1 -0
- package/dist/publication/adapter.js +83 -0
- package/dist/publication/adapter.js.map +1 -0
- package/dist/publication/authority.d.ts +137 -0
- package/dist/publication/authority.d.ts.map +1 -0
- package/dist/publication/authority.js +233 -0
- package/dist/publication/authority.js.map +1 -0
- package/dist/publication/catalog-git.d.ts +37 -0
- package/dist/publication/catalog-git.d.ts.map +1 -0
- package/dist/publication/catalog-git.js +451 -0
- package/dist/publication/catalog-git.js.map +1 -0
- package/dist/publication/claim.d.ts +39 -0
- package/dist/publication/claim.d.ts.map +1 -0
- package/dist/publication/claim.js +31 -0
- package/dist/publication/claim.js.map +1 -0
- package/dist/publication/coordinator.d.ts +246 -0
- package/dist/publication/coordinator.d.ts.map +1 -0
- package/dist/publication/coordinator.js +370 -0
- package/dist/publication/coordinator.js.map +1 -0
- package/dist/publication/github.d.ts +36 -0
- package/dist/publication/github.d.ts.map +1 -0
- package/dist/publication/github.js +644 -0
- package/dist/publication/github.js.map +1 -0
- package/dist/publication/http.d.ts +69 -0
- package/dist/publication/http.d.ts.map +1 -0
- package/dist/publication/http.js +29 -0
- package/dist/publication/http.js.map +1 -0
- package/dist/publication/index.d.ts +5 -0
- package/dist/publication/index.d.ts.map +1 -0
- package/dist/publication/index.js +5 -0
- package/dist/publication/index.js.map +1 -0
- package/dist/publication/npm.d.ts +40 -0
- package/dist/publication/npm.d.ts.map +1 -0
- package/dist/publication/npm.js +359 -0
- package/dist/publication/npm.js.map +1 -0
- package/dist/publication/profiles.d.ts +136 -0
- package/dist/publication/profiles.d.ts.map +1 -0
- package/dist/publication/profiles.js +90 -0
- package/dist/publication/profiles.js.map +1 -0
- package/dist/publication/publisher.d.ts +82 -0
- package/dist/publication/publisher.d.ts.map +1 -0
- package/dist/publication/publisher.js +53 -0
- package/dist/publication/publisher.js.map +1 -0
- package/dist/publication/pypi.d.ts +45 -0
- package/dist/publication/pypi.d.ts.map +1 -0
- package/dist/publication/pypi.js +369 -0
- package/dist/publication/pypi.js.map +1 -0
- package/dist/publication/recovery.d.ts +663 -0
- package/dist/publication/recovery.d.ts.map +1 -0
- package/dist/publication/recovery.js +252 -0
- package/dist/publication/recovery.js.map +1 -0
- package/dist/publication/report.d.ts +1061 -0
- package/dist/publication/report.d.ts.map +1 -0
- package/dist/publication/report.js +441 -0
- package/dist/publication/report.js.map +1 -0
- package/dist/recipes/config.d.ts +296 -0
- package/dist/recipes/config.d.ts.map +1 -0
- package/dist/recipes/config.js +325 -0
- package/dist/recipes/config.js.map +1 -0
- package/dist/release/capabilities.d.ts +12 -0
- package/dist/release/capabilities.d.ts.map +1 -0
- package/dist/release/capabilities.js +395 -0
- package/dist/release/capabilities.js.map +1 -0
- package/dist/release/compiler.d.ts +5 -0
- package/dist/release/compiler.d.ts.map +1 -0
- package/dist/release/compiler.js +19 -0
- package/dist/release/compiler.js.map +1 -0
- package/dist/release/config.d.ts +5 -0
- package/dist/release/config.d.ts.map +1 -0
- package/dist/release/config.js +10 -0
- package/dist/release/config.js.map +1 -0
- package/dist/release/context.d.ts +96 -0
- package/dist/release/context.d.ts.map +1 -0
- package/dist/release/context.js +168 -0
- package/dist/release/context.js.map +1 -0
- package/dist/release/graph.d.ts +271 -0
- package/dist/release/graph.d.ts.map +1 -0
- package/dist/release/graph.js +766 -0
- package/dist/release/graph.js.map +1 -0
- package/dist/release/inspect.d.ts +88 -0
- package/dist/release/inspect.d.ts.map +1 -0
- package/dist/release/inspect.js +75 -0
- package/dist/release/inspect.js.map +1 -0
- package/dist/release/prepare.d.ts +24 -0
- package/dist/release/prepare.d.ts.map +1 -0
- package/dist/release/prepare.js +1116 -0
- package/dist/release/prepare.js.map +1 -0
- package/dist/release/prepared-ref.d.ts +60 -0
- package/dist/release/prepared-ref.d.ts.map +1 -0
- package/dist/release/prepared-ref.js +131 -0
- package/dist/release/prepared-ref.js.map +1 -0
- package/dist/release/prepared-store.d.ts +90 -0
- package/dist/release/prepared-store.d.ts.map +1 -0
- package/dist/release/prepared-store.js +337 -0
- package/dist/release/prepared-store.js.map +1 -0
- package/dist/release/prepared.d.ts +172 -0
- package/dist/release/prepared.d.ts.map +1 -0
- package/dist/release/prepared.js +268 -0
- package/dist/release/prepared.js.map +1 -0
- package/dist/release/staging.d.ts +31 -0
- package/dist/release/staging.d.ts.map +1 -0
- package/dist/release/staging.js +218 -0
- package/dist/release/staging.js.map +1 -0
- package/dist/resolve/authored.d.ts +53 -0
- package/dist/resolve/authored.d.ts.map +1 -0
- package/dist/resolve/authored.js +55 -0
- package/dist/resolve/authored.js.map +1 -0
- package/dist/resolve/encode.d.ts +3 -0
- package/dist/resolve/encode.d.ts.map +1 -0
- package/dist/resolve/encode.js +21 -0
- package/dist/resolve/encode.js.map +1 -0
- package/dist/resolve/errors.d.ts +7 -0
- package/dist/resolve/errors.d.ts.map +1 -0
- package/dist/resolve/errors.js +17 -0
- package/dist/resolve/errors.js.map +1 -0
- package/dist/resolve/facts.d.ts +12 -0
- package/dist/resolve/facts.d.ts.map +1 -0
- package/dist/resolve/facts.js +18 -0
- package/dist/resolve/facts.js.map +1 -0
- package/dist/resolve/resolve.d.ts +10 -0
- package/dist/resolve/resolve.d.ts.map +1 -0
- package/dist/resolve/resolve.js +242 -0
- package/dist/resolve/resolve.js.map +1 -0
- package/dist/store.d.ts +11 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +9 -0
- package/dist/store.js.map +1 -0
- package/examples/README.md +20 -30
- package/examples/github-release/release.config.json +11 -18
- package/examples/homebrew-tap/README.md +11 -0
- package/examples/homebrew-tap/release.config.json +40 -28
- package/examples/multi-target/release.config.json +28 -53
- package/examples/npm-first-publish/release.config.json +23 -25
- package/examples/npm-only/release.config.json +22 -30
- package/examples/portable-cli/README.md +17 -0
- package/examples/portable-cli/artifacts/portable-example-0.1.0-darwin-arm64 +1 -0
- package/examples/portable-cli/artifacts/portable-example-0.1.0-darwin-x64 +1 -0
- package/examples/portable-cli/artifacts/portable-example-0.1.0-linux-arm64 +1 -0
- package/examples/portable-cli/artifacts/portable-example-0.1.0-linux-x64 +1 -0
- package/examples/portable-cli/artifacts/portable-example-0.1.0-windows-x64.exe +1 -0
- package/examples/portable-cli/artifacts/portable_example-0.1.0-py3-none-macosx_10_15_x86_64.whl +1 -0
- package/examples/portable-cli/artifacts/portable_example-0.1.0-py3-none-macosx_11_0_arm64.whl +1 -0
- package/examples/portable-cli/artifacts/portable_example-0.1.0-py3-none-manylinux2014_aarch64.whl +1 -0
- package/examples/portable-cli/artifacts/portable_example-0.1.0-py3-none-manylinux2014_x86_64.whl +1 -0
- package/examples/portable-cli/artifacts/portable_example-0.1.0-py3-none-win_amd64.whl +1 -0
- package/examples/portable-cli/package.json +9 -0
- package/examples/portable-cli/release.config.json +53 -0
- package/examples/portable-cli/src/cli.ts +3 -0
- package/examples/pypi-registry/README.md +8 -0
- package/examples/scoop-bucket/README.md +10 -0
- package/examples/scoop-bucket/release.config.json +40 -30
- package/package.json +61 -154
- package/src/api/api.ts +365 -0
- package/src/api/errors.ts +36 -0
- package/src/api/input.ts +92 -0
- package/src/api/runtime.ts +14 -0
- package/src/api/types.ts +103 -0
- package/src/capabilities/bun-targets.ts +2 -0
- package/src/capabilities/field-ownership.ts +60 -0
- package/src/capabilities/module.ts +158 -0
- package/src/capabilities/registry.ts +333 -0
- package/src/config/config.ts +48 -0
- package/src/correction/coordinator.ts +336 -0
- package/src/correction/index.ts +2 -0
- package/src/correction/intent.ts +172 -0
- package/src/drivers/archive.ts +102 -0
- package/src/drivers/contain.ts +20 -0
- package/src/drivers/environment.ts +28 -0
- package/src/drivers/errors.ts +6 -0
- package/src/drivers/glob.ts +137 -0
- package/src/drivers/process.ts +340 -0
- package/src/drivers/redact.ts +31 -0
- package/src/drivers/seccomp-helper-source.ts +121 -0
- package/src/drivers/utils.ts +10 -0
- package/src/drivers/workspace.ts +40 -0
- package/src/extensions/provider-adapter.ts +99 -0
- package/src/host.ts +140 -0
- package/src/index.ts +64 -0
- package/src/model/artifact-collection.ts +237 -0
- package/src/model/authority.ts +116 -0
- package/src/model/bun-targets.ts +34 -0
- package/src/model/canonical.ts +205 -0
- package/src/model/catalog.ts +220 -0
- package/src/model/digest.ts +172 -0
- package/src/model/errors.ts +6 -0
- package/src/model/primitives.ts +37 -0
- package/src/model/pypi.ts +30 -0
- package/src/model/python-distribution.ts +360 -0
- package/src/model/secret-patterns.ts +11 -0
- package/src/platform/bun.ts +38 -0
- package/src/platform/credentials.ts +1083 -0
- package/src/platform/host-support.ts +4 -0
- package/src/platform/node.ts +38 -0
- package/src/platform/release-runtime.ts +47 -0
- package/src/platform/services.ts +57 -0
- package/src/platform/source-observer.ts +193 -0
- package/src/provider-sdk.ts +48 -0
- package/src/publication/adapter.ts +132 -0
- package/src/publication/authority.ts +397 -0
- package/src/publication/catalog-git.ts +603 -0
- package/src/publication/claim.ts +51 -0
- package/src/publication/coordinator.ts +578 -0
- package/src/publication/github.ts +954 -0
- package/src/publication/http.ts +101 -0
- package/src/publication/index.ts +4 -0
- package/src/publication/npm.ts +525 -0
- package/src/publication/profiles.ts +94 -0
- package/src/publication/publisher.ts +130 -0
- package/src/publication/pypi.ts +463 -0
- package/src/publication/recovery.ts +349 -0
- package/src/publication/report.ts +628 -0
- package/src/recipes/config.ts +395 -0
- package/src/release/capabilities.ts +459 -0
- package/src/release/compiler.ts +28 -0
- package/src/release/config.ts +12 -0
- package/src/release/context.ts +223 -0
- package/src/release/graph.ts +866 -0
- package/src/release/inspect.ts +83 -0
- package/src/release/prepare.ts +1331 -0
- package/src/release/prepared-ref.ts +193 -0
- package/src/release/prepared-store.ts +377 -0
- package/src/release/prepared.ts +299 -0
- package/src/release/staging.ts +257 -0
- package/src/resolve/authored.ts +64 -0
- package/src/resolve/encode.ts +22 -0
- package/src/resolve/errors.ts +15 -0
- package/src/resolve/facts.ts +18 -0
- package/src/resolve/resolve.ts +328 -0
- package/src/store.ts +28 -0
- package/templates/README.md +55 -59
- package/templates/bun-cli-github/README.md +12 -0
- package/templates/bun-cli-github/release.config.json +51 -0
- package/templates/github-actions/release.yml +57 -0
- package/templates/github-actions/reviewed-release.yml +80 -0
- package/templates/multi-target-homebrew/README.md +10 -0
- package/templates/multi-target-homebrew/release.config.json +43 -60
- package/templates/multi-target-scoop/README.md +10 -0
- package/templates/multi-target-scoop/release.config.json +43 -62
- package/templates/npm-github/release.config.json +25 -38
- package/templates/npm-github/reviewed-release.config.json +37 -0
- package/templates/npm-only/README.md +10 -0
- package/templates/npm-only/release.config.json +15 -30
- package/templates/portable-cli/README.md +13 -0
- package/templates/portable-cli/release.config.json +53 -0
- package/dist/config/errors.d.ts +0 -24
- package/dist/config/errors.d.ts.map +0 -1
- package/dist/config/errors.js +0 -19
- package/dist/config/errors.js.map +0 -1
- package/dist/config/load.d.ts +0 -9
- package/dist/config/load.d.ts.map +0 -1
- package/dist/config/load.js +0 -29
- package/dist/config/load.js.map +0 -1
- package/dist/config/schema.d.ts +0 -12
- package/dist/config/schema.d.ts.map +0 -1
- package/dist/config/schema.js +0 -19
- package/dist/config/schema.js.map +0 -1
- package/dist/domain/artifact.d.ts +0 -36
- package/dist/domain/artifact.d.ts.map +0 -1
- package/dist/domain/artifact.js +0 -29
- package/dist/domain/artifact.js.map +0 -1
- package/dist/domain/evidence.d.ts +0 -108
- package/dist/domain/evidence.d.ts.map +0 -1
- package/dist/domain/evidence.js +0 -96
- package/dist/domain/evidence.js.map +0 -1
- package/dist/domain/operation.d.ts +0 -153
- package/dist/domain/operation.d.ts.map +0 -1
- package/dist/domain/operation.js +0 -320
- package/dist/domain/operation.js.map +0 -1
- package/dist/domain/release.d.ts +0 -141
- package/dist/domain/release.d.ts.map +0 -1
- package/dist/domain/release.js +0 -127
- package/dist/domain/release.js.map +0 -1
- package/dist/domain/remote-state.d.ts +0 -85
- package/dist/domain/remote-state.d.ts.map +0 -1
- package/dist/domain/remote-state.js +0 -77
- package/dist/domain/remote-state.js.map +0 -1
- package/dist/domain/status.d.ts +0 -43
- package/dist/domain/status.d.ts.map +0 -1
- package/dist/domain/status.js +0 -51
- package/dist/domain/status.js.map +0 -1
- package/dist/domain/target.d.ts +0 -135
- package/dist/domain/target.d.ts.map +0 -1
- package/dist/domain/target.js +0 -137
- package/dist/domain/target.js.map +0 -1
- package/dist/host/host.d.ts +0 -32
- package/dist/host/host.d.ts.map +0 -1
- package/dist/host/host.js +0 -24
- package/dist/host/host.js.map +0 -1
- package/dist/host/http-live.d.ts +0 -6
- package/dist/host/http-live.d.ts.map +0 -1
- package/dist/host/http-live.js +0 -86
- package/dist/host/http-live.js.map +0 -1
- package/dist/host/http.d.ts +0 -43
- package/dist/host/http.d.ts.map +0 -1
- package/dist/host/http.js +0 -59
- package/dist/host/http.js.map +0 -1
- package/dist/host/platform.d.ts +0 -10
- package/dist/host/platform.d.ts.map +0 -1
- package/dist/host/platform.js +0 -114
- package/dist/host/platform.js.map +0 -1
- package/dist/host/test.d.ts +0 -23
- package/dist/host/test.d.ts.map +0 -1
- package/dist/host/test.js +0 -222
- package/dist/host/test.js.map +0 -1
- package/dist/internal/workflow-phases.d.ts +0 -3
- package/dist/internal/workflow-phases.d.ts.map +0 -1
- package/dist/internal/workflow-phases.js +0 -2
- package/dist/internal/workflow-phases.js.map +0 -1
- package/dist/internal/workspace-path.d.ts +0 -15
- package/dist/internal/workspace-path.d.ts.map +0 -1
- package/dist/internal/workspace-path.js +0 -36
- package/dist/internal/workspace-path.js.map +0 -1
- package/dist/planner/artifact-inventory.d.ts +0 -9
- package/dist/planner/artifact-inventory.d.ts.map +0 -1
- package/dist/planner/artifact-inventory.js +0 -84
- package/dist/planner/artifact-inventory.js.map +0 -1
- package/dist/planner/create-release-plan.d.ts +0 -5
- package/dist/planner/create-release-plan.d.ts.map +0 -1
- package/dist/planner/create-release-plan.js +0 -25
- package/dist/planner/create-release-plan.js.map +0 -1
- package/dist/planner/errors.d.ts +0 -81
- package/dist/planner/errors.d.ts.map +0 -1
- package/dist/planner/errors.js +0 -70
- package/dist/planner/errors.js.map +0 -1
- package/dist/planner/evidence-recorder.d.ts +0 -26
- package/dist/planner/evidence-recorder.d.ts.map +0 -1
- package/dist/planner/evidence-recorder.js +0 -330
- package/dist/planner/evidence-recorder.js.map +0 -1
- package/dist/planner/executor.d.ts +0 -58
- package/dist/planner/executor.d.ts.map +0 -1
- package/dist/planner/executor.js +0 -159
- package/dist/planner/executor.js.map +0 -1
- package/dist/planner/normalize-release.d.ts +0 -16
- package/dist/planner/normalize-release.d.ts.map +0 -1
- package/dist/planner/normalize-release.js +0 -218
- package/dist/planner/normalize-release.js.map +0 -1
- package/dist/planner/reconcile.d.ts +0 -17
- package/dist/planner/reconcile.d.ts.map +0 -1
- package/dist/planner/reconcile.js +0 -261
- package/dist/planner/reconcile.js.map +0 -1
- package/dist/planner/release-eligibility.d.ts +0 -39
- package/dist/planner/release-eligibility.d.ts.map +0 -1
- package/dist/planner/release-eligibility.js +0 -578
- package/dist/planner/release-eligibility.js.map +0 -1
- package/dist/planner/render-plan.d.ts +0 -15
- package/dist/planner/render-plan.d.ts.map +0 -1
- package/dist/planner/render-plan.js +0 -238
- package/dist/planner/render-plan.js.map +0 -1
- package/dist/planner/status.d.ts +0 -40
- package/dist/planner/status.d.ts.map +0 -1
- package/dist/planner/status.js +0 -313
- package/dist/planner/status.js.map +0 -1
- package/dist/targets/adapter-helpers.d.ts +0 -64
- package/dist/targets/adapter-helpers.d.ts.map +0 -1
- package/dist/targets/adapter-helpers.js +0 -124
- package/dist/targets/adapter-helpers.js.map +0 -1
- package/dist/targets/adapter.d.ts +0 -17
- package/dist/targets/adapter.d.ts.map +0 -1
- package/dist/targets/adapter.js +0 -2
- package/dist/targets/adapter.js.map +0 -1
- package/dist/targets/github-release.d.ts +0 -25
- package/dist/targets/github-release.d.ts.map +0 -1
- package/dist/targets/github-release.js +0 -82
- package/dist/targets/github-release.js.map +0 -1
- package/dist/targets/github.d.ts +0 -11
- package/dist/targets/github.d.ts.map +0 -1
- package/dist/targets/github.js +0 -125
- package/dist/targets/github.js.map +0 -1
- package/dist/targets/homebrew.d.ts +0 -11
- package/dist/targets/homebrew.d.ts.map +0 -1
- package/dist/targets/homebrew.js +0 -97
- package/dist/targets/homebrew.js.map +0 -1
- package/dist/targets/live.d.ts +0 -5
- package/dist/targets/live.d.ts.map +0 -1
- package/dist/targets/live.js +0 -50
- package/dist/targets/live.js.map +0 -1
- package/dist/targets/npm.d.ts +0 -10
- package/dist/targets/npm.d.ts.map +0 -1
- package/dist/targets/npm.js +0 -118
- package/dist/targets/npm.js.map +0 -1
- package/dist/targets/pypi.d.ts +0 -11
- package/dist/targets/pypi.d.ts.map +0 -1
- package/dist/targets/pypi.js +0 -103
- package/dist/targets/pypi.js.map +0 -1
- package/dist/targets/registry.d.ts +0 -25
- package/dist/targets/registry.d.ts.map +0 -1
- package/dist/targets/registry.js +0 -34
- package/dist/targets/registry.js.map +0 -1
- package/dist/targets/scoop.d.ts +0 -11
- package/dist/targets/scoop.d.ts.map +0 -1
- package/dist/targets/scoop.js +0 -78
- package/dist/targets/scoop.js.map +0 -1
- package/dist/types/effect-internal.d.ts +0 -5
- package/dist/types/effect-internal.d.ts.map +0 -1
- package/dist/types/effect-internal.js +0 -2
- package/dist/types/effect-internal.js.map +0 -1
- package/dist/workflows/config.d.ts +0 -252
- package/dist/workflows/config.d.ts.map +0 -1
- package/dist/workflows/config.js +0 -465
- package/dist/workflows/config.js.map +0 -1
- package/dist/workflows/diagnostics.d.ts +0 -62
- package/dist/workflows/diagnostics.d.ts.map +0 -1
- package/dist/workflows/diagnostics.js +0 -527
- package/dist/workflows/diagnostics.js.map +0 -1
- package/dist/workflows/evidence.d.ts +0 -26
- package/dist/workflows/evidence.d.ts.map +0 -1
- package/dist/workflows/evidence.js +0 -44
- package/dist/workflows/evidence.js.map +0 -1
- package/dist/workflows/index.d.ts +0 -7
- package/dist/workflows/index.d.ts.map +0 -1
- package/dist/workflows/index.js +0 -6
- package/dist/workflows/index.js.map +0 -1
- package/dist/workflows/init.d.ts +0 -84
- package/dist/workflows/init.d.ts.map +0 -1
- package/dist/workflows/init.js +0 -441
- package/dist/workflows/init.js.map +0 -1
- package/dist/workflows/live.d.ts +0 -9
- package/dist/workflows/live.d.ts.map +0 -1
- package/dist/workflows/live.js +0 -7
- package/dist/workflows/live.js.map +0 -1
- package/dist/workflows/options.d.ts +0 -30
- package/dist/workflows/options.d.ts.map +0 -1
- package/dist/workflows/options.js +0 -16
- package/dist/workflows/options.js.map +0 -1
- package/examples/non-strict-skips/artifacts/release-example-skips-0.1.0.tgz +0 -1
- package/examples/non-strict-skips/release.config.json +0 -31
- package/examples/pypi-registry/release.config.json +0 -31
- package/templates/github-actions/plan-and-approved-execute.yml +0 -58
- package/templates/github-actions/plan-only.yml +0 -33
- package/templates/github-actions/trusted-publishing.yml +0 -61
|
@@ -0,0 +1,603 @@
|
|
|
1
|
+
import { createHash } from "node:crypto"
|
|
2
|
+
import * as Effect from "effect/Effect"
|
|
3
|
+
import type { CredentialRequest } from "../model/authority.js"
|
|
4
|
+
import { CredentialRequest as CredentialRequestSchema, SubjectId } from "../model/authority.js"
|
|
5
|
+
import {
|
|
6
|
+
CatalogManagedState,
|
|
7
|
+
compareCatalogVersions,
|
|
8
|
+
decodeCatalogManagedState
|
|
9
|
+
} from "../model/catalog.js"
|
|
10
|
+
import { digestEquals, sha256Digest } from "../model/digest.js"
|
|
11
|
+
import { NonEmptyName } from "../model/primitives.js"
|
|
12
|
+
import type { PreparedCatalogPublication } from "../release/prepared.js"
|
|
13
|
+
import type { PreparedBundle } from "../release/prepared-store.js"
|
|
14
|
+
import type { CredentialGrant, MutationCredentialGrant, PublisherOperation, ScopedSecret } from "./authority.js"
|
|
15
|
+
import { ReleaseSubjectError, type ReleaseObservationContext, type ReleaseSubject } from "./coordinator.js"
|
|
16
|
+
import type { AuthorizedMutationHttpShape, HttpAuthorizerShape, HttpResponse, MutationHttpRequest } from "./http.js"
|
|
17
|
+
import {
|
|
18
|
+
AbsenceBasis,
|
|
19
|
+
Applied,
|
|
20
|
+
AuthoritativelyAbsent,
|
|
21
|
+
Conflict,
|
|
22
|
+
Difference,
|
|
23
|
+
InconclusiveObservation,
|
|
24
|
+
MutationPrecondition,
|
|
25
|
+
NeedsMutation,
|
|
26
|
+
OutcomeUnknown,
|
|
27
|
+
PresentDifferent,
|
|
28
|
+
PresentEquivalent,
|
|
29
|
+
ProviderAlreadyEquivalent,
|
|
30
|
+
ProviderBlocked,
|
|
31
|
+
ProviderMutationFact,
|
|
32
|
+
ProviderRejectionFact,
|
|
33
|
+
RejectedByProvider,
|
|
34
|
+
SafeReason,
|
|
35
|
+
Started,
|
|
36
|
+
VisibilityBasis,
|
|
37
|
+
VisibilityPending,
|
|
38
|
+
type MutationAttempt,
|
|
39
|
+
type MutationDecision,
|
|
40
|
+
type Observation,
|
|
41
|
+
type ProviderDecision
|
|
42
|
+
} from "./report.js"
|
|
43
|
+
import { makeRecoveryCapabilityProfile } from "./recovery.js"
|
|
44
|
+
|
|
45
|
+
const apiVersion = "2022-11-28"
|
|
46
|
+
const jsonHeaders = {
|
|
47
|
+
accept: "application/vnd.github+json",
|
|
48
|
+
"x-github-api-version": apiVersion
|
|
49
|
+
} as const
|
|
50
|
+
const objectSha = /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/u
|
|
51
|
+
|
|
52
|
+
export const catalogRecoveryCapabilityProfile = makeRecoveryCapabilityProfile({
|
|
53
|
+
observation: "exact",
|
|
54
|
+
authoritativeAbsence: "provider-specific",
|
|
55
|
+
createAuthorization: "none",
|
|
56
|
+
replay: "conditional",
|
|
57
|
+
identifierReuse: "reusable",
|
|
58
|
+
correction: ["forward-catalog-state"],
|
|
59
|
+
exposure: "persistent-to-consumers",
|
|
60
|
+
historyRequirement: "optional-evidence",
|
|
61
|
+
readConvergence: {
|
|
62
|
+
contract: {
|
|
63
|
+
_tag: "assumed",
|
|
64
|
+
basis: "ASSUMED/UNVERIFIED: no authorized live mutation evidence establishes GitHub Git-data read-convergence timing."
|
|
65
|
+
},
|
|
66
|
+
observationRetry: {
|
|
67
|
+
maxAttempts: 5,
|
|
68
|
+
backoff: { baseMs: 1_000, factor: 2, capMs: 15_000 },
|
|
69
|
+
totalBudgetMs: 60_000
|
|
70
|
+
},
|
|
71
|
+
retryEligible: "VisibilityPending | Inconclusive",
|
|
72
|
+
exhaustion: "UncertainSubject with full trace"
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
type JsonObject = Readonly<Record<string, unknown>>
|
|
77
|
+
const object = (value: unknown): JsonObject | undefined =>
|
|
78
|
+
typeof value === "object" && value !== null && !Array.isArray(value) ? value as JsonObject : undefined
|
|
79
|
+
const string = (value: unknown): string | undefined => typeof value === "string" ? value : undefined
|
|
80
|
+
const parseJson = (body: string | Uint8Array): unknown | undefined => {
|
|
81
|
+
try {
|
|
82
|
+
return JSON.parse(typeof body === "string" ? body : new TextDecoder("utf-8", { fatal: true }).decode(body)) as unknown
|
|
83
|
+
} catch {
|
|
84
|
+
return undefined
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const bytesEqual = (left: Uint8Array | undefined, right: Uint8Array): boolean =>
|
|
88
|
+
left !== undefined && left.length === right.length && left.every((byte, index) => byte === right[index])
|
|
89
|
+
|
|
90
|
+
interface TreeEntry {
|
|
91
|
+
readonly path: string
|
|
92
|
+
readonly mode: string
|
|
93
|
+
readonly type: "blob" | "tree" | "commit"
|
|
94
|
+
readonly sha: string
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
interface RepositorySnapshot {
|
|
98
|
+
readonly commit: string
|
|
99
|
+
readonly rootTree: string
|
|
100
|
+
readonly tree: ReadonlyMap<string, TreeEntry>
|
|
101
|
+
readonly target?: Uint8Array
|
|
102
|
+
readonly state?: Uint8Array
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
interface MutationPlan {
|
|
106
|
+
readonly snapshot: RepositorySnapshot
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface CatalogSubjectPair {
|
|
110
|
+
readonly id?: SubjectId
|
|
111
|
+
readonly purpose?: "publish" | "correct"
|
|
112
|
+
readonly target: Uint8Array
|
|
113
|
+
readonly state: Uint8Array
|
|
114
|
+
/** Exact old pair that authorizes one forward correction. */
|
|
115
|
+
readonly baselineTarget?: Uint8Array
|
|
116
|
+
readonly baselineState?: Uint8Array
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const observationRequests = (
|
|
120
|
+
publication: PreparedCatalogPublication,
|
|
121
|
+
subject: SubjectId
|
|
122
|
+
): readonly [CredentialRequest, ...Array<CredentialRequest>] => publication.authority.observationStrategies.map((strategy) =>
|
|
123
|
+
CredentialRequestSchema.make({
|
|
124
|
+
subject,
|
|
125
|
+
provider: publication.authority.provider,
|
|
126
|
+
audience: publication.authority.audience,
|
|
127
|
+
purpose: "observe",
|
|
128
|
+
strategy
|
|
129
|
+
})) as [CredentialRequest, ...Array<CredentialRequest>]
|
|
130
|
+
|
|
131
|
+
const mutationRequest = (
|
|
132
|
+
publication: PreparedCatalogPublication,
|
|
133
|
+
subject: SubjectId,
|
|
134
|
+
purpose: "publish" | "correct"
|
|
135
|
+
): CredentialRequest => CredentialRequestSchema.make({
|
|
136
|
+
subject,
|
|
137
|
+
provider: publication.authority.provider,
|
|
138
|
+
audience: publication.authority.audience,
|
|
139
|
+
purpose,
|
|
140
|
+
strategy: publication.authority.publishStrategy
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
const failure = (
|
|
144
|
+
subject: SubjectId,
|
|
145
|
+
phase: "observe" | "mutate",
|
|
146
|
+
commitment: "before-dispatch" | "unknown",
|
|
147
|
+
reason: string
|
|
148
|
+
): ReleaseSubjectError => new ReleaseSubjectError({
|
|
149
|
+
subject,
|
|
150
|
+
phase,
|
|
151
|
+
commitment,
|
|
152
|
+
reason: SafeReason.make(reason)
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
const inconclusive = (subject: SubjectId, reason: string): InconclusiveObservation =>
|
|
156
|
+
InconclusiveObservation.make({ subject, reason: SafeReason.make(reason) })
|
|
157
|
+
const difference = (field: string, expected: string, observed: string): Difference => Difference.make({
|
|
158
|
+
field: NonEmptyName.make(field),
|
|
159
|
+
expected: SafeReason.make(expected),
|
|
160
|
+
observed: SafeReason.make(observed)
|
|
161
|
+
})
|
|
162
|
+
const fingerprint = (bytes: Uint8Array): string => `sha256-${sha256Digest(bytes).hex}`
|
|
163
|
+
|
|
164
|
+
const get = (
|
|
165
|
+
subject: SubjectId,
|
|
166
|
+
http: HttpAuthorizerShape,
|
|
167
|
+
grant: Exclude<CredentialGrant, { readonly _tag: "WorkloadIdentity" }>,
|
|
168
|
+
url: string
|
|
169
|
+
): Effect.Effect<HttpResponse, ReleaseSubjectError> => http.execute({
|
|
170
|
+
subject,
|
|
171
|
+
method: "GET",
|
|
172
|
+
url,
|
|
173
|
+
headers: jsonHeaders
|
|
174
|
+
}, grant).pipe(Effect.mapError((cause) => failure(
|
|
175
|
+
subject,
|
|
176
|
+
"observe",
|
|
177
|
+
cause._tag === "CredentialPlatformError" ? cause.commitment : "before-dispatch",
|
|
178
|
+
"GitHub catalog observation failed at the authorized HTTP boundary."
|
|
179
|
+
)))
|
|
180
|
+
|
|
181
|
+
const treeEntries = (value: unknown, expectedSha: string): ReadonlyArray<TreeEntry> | undefined => {
|
|
182
|
+
const root = object(value)
|
|
183
|
+
if (root?.sha !== expectedSha || root.truncated !== false || !Array.isArray(root.tree)) return undefined
|
|
184
|
+
const result: Array<TreeEntry> = []
|
|
185
|
+
const paths = new Set<string>()
|
|
186
|
+
for (const raw of root.tree) {
|
|
187
|
+
const entry = object(raw)
|
|
188
|
+
const path = string(entry?.path)
|
|
189
|
+
const mode = string(entry?.mode)
|
|
190
|
+
const type = entry?.type
|
|
191
|
+
const sha = string(entry?.sha)
|
|
192
|
+
if (path === undefined || path.length === 0 || path.startsWith("/") || path.includes("\\") ||
|
|
193
|
+
path.split("/").some((part) => part === "" || part === "..") || paths.has(path) ||
|
|
194
|
+
mode === undefined || !/^[0-7]{6}$/u.test(mode) ||
|
|
195
|
+
(type !== "blob" && type !== "tree" && type !== "commit") || sha === undefined || !objectSha.test(sha)) {
|
|
196
|
+
return undefined
|
|
197
|
+
}
|
|
198
|
+
paths.add(path)
|
|
199
|
+
result.push({ path, mode, type, sha })
|
|
200
|
+
}
|
|
201
|
+
return result
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const gitObjectDigest = (bytes: Uint8Array, length: number): string => {
|
|
205
|
+
const header = new TextEncoder().encode(`blob ${bytes.length}\0`)
|
|
206
|
+
const content = new Uint8Array(header.length + bytes.length)
|
|
207
|
+
content.set(header)
|
|
208
|
+
content.set(bytes, header.length)
|
|
209
|
+
return createHash(length === 40 ? "sha1" : "sha256").update(content).digest("hex")
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const blobBytes = (value: unknown, expectedSha: string): Uint8Array | undefined => {
|
|
213
|
+
const blob = object(value)
|
|
214
|
+
const content = string(blob?.content)?.replaceAll(/\s/gu, "")
|
|
215
|
+
if (blob?.sha !== expectedSha || blob.encoding !== "base64" || content === undefined ||
|
|
216
|
+
!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/u.test(content)) return undefined
|
|
217
|
+
const bytes = new Uint8Array(Buffer.from(content, "base64"))
|
|
218
|
+
return gitObjectDigest(bytes, expectedSha.length) === expectedSha ? bytes : undefined
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const readSnapshot = Effect.fn("CatalogGitSubject.readSnapshot")(function*(
|
|
222
|
+
publication: PreparedCatalogPublication,
|
|
223
|
+
subject: SubjectId,
|
|
224
|
+
http: HttpAuthorizerShape,
|
|
225
|
+
grant: Exclude<CredentialGrant, { readonly _tag: "WorkloadIdentity" }>
|
|
226
|
+
) {
|
|
227
|
+
const base = publication.authority.audience.toString()
|
|
228
|
+
const repository = yield* get(subject, http, grant, base)
|
|
229
|
+
if (repository.status === 404) return { _tag: "Inconclusive", reason: "Catalog repository is missing or hidden." } as const
|
|
230
|
+
if (repository.status !== 200 || object(parseJson(repository.body))?.full_name !== publication.repository) {
|
|
231
|
+
return { _tag: "Inconclusive", reason: "Catalog repository visibility or identity was not proved." } as const
|
|
232
|
+
}
|
|
233
|
+
const fullRef = `refs/heads/${publication.branch}`
|
|
234
|
+
const ref = yield* get(subject, http, grant, `${base}/git/ref/heads/${encodeURIComponent(publication.branch)}`)
|
|
235
|
+
const refObject = object(parseJson(ref.body))
|
|
236
|
+
const commit = string(object(refObject?.object)?.sha)
|
|
237
|
+
if (ref.status !== 200 || refObject?.ref !== fullRef || commit === undefined || !objectSha.test(commit)) {
|
|
238
|
+
return { _tag: "Inconclusive", reason: "Catalog branch ref is missing, hidden, or malformed." } as const
|
|
239
|
+
}
|
|
240
|
+
const commitResponse = yield* get(subject, http, grant, `${base}/git/commits/${commit}`)
|
|
241
|
+
const commitObject = object(parseJson(commitResponse.body))
|
|
242
|
+
const rootTree = string(object(commitObject?.tree)?.sha)
|
|
243
|
+
if (commitResponse.status !== 200 || commitObject?.sha !== commit || rootTree === undefined || !objectSha.test(rootTree)) {
|
|
244
|
+
return { _tag: "Inconclusive", reason: "Catalog branch commit/root tree was malformed." } as const
|
|
245
|
+
}
|
|
246
|
+
const treeResponse = yield* get(subject, http, grant, `${base}/git/trees/${rootTree}?recursive=1`)
|
|
247
|
+
const entries = treeResponse.status === 200 ? treeEntries(parseJson(treeResponse.body), rootTree) : undefined
|
|
248
|
+
if (entries === undefined) return { _tag: "Inconclusive", reason: "Catalog full tree was truncated, malformed, or unavailable." } as const
|
|
249
|
+
const tree = new Map(entries.map((entry) => [entry.path, entry]))
|
|
250
|
+
const targetEntry = tree.get(publication.targetPath.toString())
|
|
251
|
+
const stateEntry = tree.get(publication.statePath.toString())
|
|
252
|
+
if ((targetEntry !== undefined && (targetEntry.type !== "blob" || !["100644", "100755"].includes(targetEntry.mode))) ||
|
|
253
|
+
(stateEntry !== undefined && (stateEntry.type !== "blob" || stateEntry.mode !== "100644"))) {
|
|
254
|
+
return { _tag: "Inconclusive", reason: "Catalog managed paths have unsupported Git object types or modes." } as const
|
|
255
|
+
}
|
|
256
|
+
const readBlob = (entry: TreeEntry | undefined) => entry === undefined
|
|
257
|
+
? Effect.succeed(undefined)
|
|
258
|
+
: get(subject, http, grant, `${base}/git/blobs/${entry.sha}`).pipe(Effect.map((response) =>
|
|
259
|
+
response.status === 200 ? blobBytes(parseJson(response.body), entry.sha) : undefined))
|
|
260
|
+
const target = yield* readBlob(targetEntry)
|
|
261
|
+
const state = yield* readBlob(stateEntry)
|
|
262
|
+
if ((targetEntry !== undefined && target === undefined) || (stateEntry !== undefined && state === undefined)) {
|
|
263
|
+
return { _tag: "Inconclusive", reason: "Catalog target or managed-state blob was malformed or unavailable." } as const
|
|
264
|
+
}
|
|
265
|
+
return {
|
|
266
|
+
_tag: "Snapshot",
|
|
267
|
+
snapshot: { commit, rootTree, tree, ...(target === undefined ? {} : { target }), ...(state === undefined ? {} : { state }) }
|
|
268
|
+
} as const
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
const pairObservation = (
|
|
272
|
+
publication: PreparedCatalogPublication,
|
|
273
|
+
subject: SubjectId,
|
|
274
|
+
pair: CatalogSubjectPair,
|
|
275
|
+
snapshot: RepositorySnapshot,
|
|
276
|
+
context: ReleaseObservationContext,
|
|
277
|
+
setPlan: (plan: MutationPlan | undefined) => void
|
|
278
|
+
): Observation => {
|
|
279
|
+
const absentPair = snapshot.target === undefined && snapshot.state === undefined
|
|
280
|
+
if (absentPair) {
|
|
281
|
+
if (context.phase === "post-mutation" && context.attempt._tag !== "RejectedBeforeDispatch" &&
|
|
282
|
+
context.attempt._tag !== "RejectedByProvider") return VisibilityPending.make({
|
|
283
|
+
subject,
|
|
284
|
+
expectation: SafeReason.make("The exact catalog target/state pair becomes visible at the updated branch ref."),
|
|
285
|
+
basis: VisibilityBasis.make({
|
|
286
|
+
kind: NonEmptyName.make("catalog-git-ref-convergence"),
|
|
287
|
+
detail: SafeReason.make("The same invocation attempted one conditional GitHub ref update.")
|
|
288
|
+
})
|
|
289
|
+
})
|
|
290
|
+
setPlan({ snapshot })
|
|
291
|
+
return AuthoritativelyAbsent.make({
|
|
292
|
+
subject,
|
|
293
|
+
basis: AbsenceBasis.make({
|
|
294
|
+
kind: NonEmptyName.make("catalog-visible-repository-pair-absent"),
|
|
295
|
+
detail: SafeReason.make("The visible branch full tree omits both exact managed paths.")
|
|
296
|
+
})
|
|
297
|
+
})
|
|
298
|
+
}
|
|
299
|
+
if (snapshot.target === undefined || snapshot.state === undefined) return PresentDifferent.make({
|
|
300
|
+
subject,
|
|
301
|
+
differences: [difference("catalog.pair", "both files present or absent", "half-present")]
|
|
302
|
+
})
|
|
303
|
+
if (bytesEqual(snapshot.target, pair.target) && bytesEqual(snapshot.state, pair.state)) {
|
|
304
|
+
return PresentEquivalent.make({ subject })
|
|
305
|
+
}
|
|
306
|
+
if (pair.baselineTarget !== undefined && pair.baselineState !== undefined &&
|
|
307
|
+
bytesEqual(snapshot.target, pair.baselineTarget) && bytesEqual(snapshot.state, pair.baselineState)) {
|
|
308
|
+
if (context.phase === "post-mutation" && context.attempt._tag !== "RejectedBeforeDispatch" &&
|
|
309
|
+
context.attempt._tag !== "RejectedByProvider") return VisibilityPending.make({
|
|
310
|
+
subject,
|
|
311
|
+
expectation: SafeReason.make("The forward catalog correction becomes visible at the branch ref."),
|
|
312
|
+
basis: VisibilityBasis.make({
|
|
313
|
+
kind: NonEmptyName.make("catalog-git-ref-convergence"),
|
|
314
|
+
detail: SafeReason.make("The old exact pair remains visible immediately after one conditional update attempt.")
|
|
315
|
+
})
|
|
316
|
+
})
|
|
317
|
+
setPlan({ snapshot })
|
|
318
|
+
return PresentDifferent.make({
|
|
319
|
+
subject,
|
|
320
|
+
differences: [difference("catalog.correction", "forward replacement", "exact prepared baseline")]
|
|
321
|
+
})
|
|
322
|
+
}
|
|
323
|
+
const observedState = decodeCatalogManagedState(snapshot.state)
|
|
324
|
+
const expectedState = decodeCatalogManagedState(pair.state)
|
|
325
|
+
if (observedState === undefined || expectedState === undefined) return inconclusive(
|
|
326
|
+
subject,
|
|
327
|
+
"Catalog managed state is malformed or noncanonical."
|
|
328
|
+
)
|
|
329
|
+
if (!digestEquals(observedState.targetDigest, sha256Digest(snapshot.target)) ||
|
|
330
|
+
!digestEquals(expectedState.targetDigest, sha256Digest(pair.target))) return inconclusive(
|
|
331
|
+
subject,
|
|
332
|
+
"Catalog managed state does not bind the exact adjacent target bytes."
|
|
333
|
+
)
|
|
334
|
+
if (pair.baselineTarget === undefined && observedState.status === "active" && expectedState.status === "active") {
|
|
335
|
+
const ordering = compareCatalogVersions(observedState.generation.toString(), expectedState.generation.toString())
|
|
336
|
+
if (ordering < 0 && observedState.catalogId === expectedState.catalogId &&
|
|
337
|
+
observedState.renderer === expectedState.renderer &&
|
|
338
|
+
observedState.sourceRepository === expectedState.sourceRepository) {
|
|
339
|
+
setPlan({ snapshot })
|
|
340
|
+
return PresentDifferent.make({
|
|
341
|
+
subject,
|
|
342
|
+
differences: [difference("catalog.generation", expectedState.generation.toString(), observedState.generation.toString())]
|
|
343
|
+
})
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
return PresentDifferent.make({
|
|
347
|
+
subject,
|
|
348
|
+
differences: [
|
|
349
|
+
difference("catalog.target", fingerprint(pair.target), fingerprint(snapshot.target)),
|
|
350
|
+
difference("catalog.state", fingerprint(pair.state), fingerprint(snapshot.state))
|
|
351
|
+
]
|
|
352
|
+
})
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const managedAncestors = (paths: ReadonlyArray<string>): ReadonlySet<string> => {
|
|
356
|
+
const result = new Set<string>()
|
|
357
|
+
for (const path of paths) {
|
|
358
|
+
const parts = path.split("/")
|
|
359
|
+
for (let index = 1; index < parts.length; index += 1) result.add(parts.slice(0, index).join("/"))
|
|
360
|
+
}
|
|
361
|
+
return result
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const treePreserved = (
|
|
365
|
+
before: ReadonlyMap<string, TreeEntry>,
|
|
366
|
+
after: ReadonlyArray<TreeEntry>,
|
|
367
|
+
targetPath: string,
|
|
368
|
+
targetSha: string,
|
|
369
|
+
statePath: string,
|
|
370
|
+
stateSha: string
|
|
371
|
+
): boolean => {
|
|
372
|
+
const afterMap = new Map(after.map((entry) => [entry.path, entry]))
|
|
373
|
+
const managed = new Set([targetPath, statePath])
|
|
374
|
+
const ancestors = managedAncestors([targetPath, statePath])
|
|
375
|
+
const target = afterMap.get(targetPath)
|
|
376
|
+
const state = afterMap.get(statePath)
|
|
377
|
+
if (target?.type !== "blob" || target.mode !== "100644" || target.sha !== targetSha ||
|
|
378
|
+
state?.type !== "blob" || state.mode !== "100644" || state.sha !== stateSha) return false
|
|
379
|
+
for (const [path, entry] of before) {
|
|
380
|
+
if (managed.has(path) || ancestors.has(path)) continue
|
|
381
|
+
const candidate = afterMap.get(path)
|
|
382
|
+
if (candidate === undefined || candidate.mode !== entry.mode || candidate.type !== entry.type || candidate.sha !== entry.sha) return false
|
|
383
|
+
}
|
|
384
|
+
for (const path of afterMap.keys()) {
|
|
385
|
+
if (!before.has(path) && !managed.has(path) && !ancestors.has(path)) return false
|
|
386
|
+
}
|
|
387
|
+
return true
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
const mutationAttempt = (
|
|
391
|
+
subject: SubjectId,
|
|
392
|
+
code: string,
|
|
393
|
+
detail: string
|
|
394
|
+
): RejectedByProvider => RejectedByProvider.make({
|
|
395
|
+
subject,
|
|
396
|
+
fact: ProviderRejectionFact.make({
|
|
397
|
+
subject,
|
|
398
|
+
code: NonEmptyName.make(code),
|
|
399
|
+
detail: SafeReason.make(detail)
|
|
400
|
+
})
|
|
401
|
+
})
|
|
402
|
+
|
|
403
|
+
export const makeCatalogSubject = (
|
|
404
|
+
publication: PreparedCatalogPublication,
|
|
405
|
+
http: HttpAuthorizerShape,
|
|
406
|
+
mutationHttp: AuthorizedMutationHttpShape,
|
|
407
|
+
pair: CatalogSubjectPair
|
|
408
|
+
): ReleaseSubject => {
|
|
409
|
+
const subject = pair.id ?? publication.authority.subject
|
|
410
|
+
const purpose = pair.purpose ?? "publish"
|
|
411
|
+
let plan: MutationPlan | undefined
|
|
412
|
+
const observe = Effect.fn("CatalogGitSubject.observe")(function*(grant: CredentialGrant, context: ReleaseObservationContext) {
|
|
413
|
+
plan = undefined
|
|
414
|
+
if (grant._tag === "WorkloadIdentity") return inconclusive(subject, "Catalog Git observation does not accept workload identity.")
|
|
415
|
+
const result = yield* readSnapshot(publication, subject, http, grant)
|
|
416
|
+
if (result._tag === "Inconclusive") return inconclusive(subject, result.reason)
|
|
417
|
+
return pairObservation(publication, subject, pair, result.snapshot, context, (value) => { plan = value })
|
|
418
|
+
})
|
|
419
|
+
const decide = (observation: Observation): ProviderDecision => {
|
|
420
|
+
if (observation._tag === "PresentEquivalent") return ProviderAlreadyEquivalent.make({ subject })
|
|
421
|
+
if (plan !== undefined && (observation._tag === "AuthoritativelyAbsent" || observation._tag === "PresentDifferent")) {
|
|
422
|
+
return NeedsMutation.make({
|
|
423
|
+
subject,
|
|
424
|
+
precondition: MutationPrecondition.make({ kind: NonEmptyName.make("catalog-observed-ref-and-root-tree") })
|
|
425
|
+
})
|
|
426
|
+
}
|
|
427
|
+
if (observation._tag === "PresentDifferent") return Conflict.make({ subject, differences: observation.differences })
|
|
428
|
+
return ProviderBlocked.make({
|
|
429
|
+
subject,
|
|
430
|
+
reason: SafeReason.make("Catalog observation did not prove an exact conditional pair update.")
|
|
431
|
+
})
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
const mutate = Effect.fn("CatalogGitSubject.mutate")(function*(
|
|
435
|
+
decision: MutationDecision,
|
|
436
|
+
grant: MutationCredentialGrant
|
|
437
|
+
): Effect.fn.Return<MutationAttempt, ReleaseSubjectError> {
|
|
438
|
+
const selected = plan
|
|
439
|
+
plan = undefined
|
|
440
|
+
if (decision._tag !== "NeedsMutation" || decision.precondition.kind !== "catalog-observed-ref-and-root-tree" ||
|
|
441
|
+
selected === undefined || grant._tag !== "ScopedSecret") {
|
|
442
|
+
return yield* failure(subject, "mutate", "before-dispatch", "Catalog mutation lacks its exact observed ref/tree plan or token grant.")
|
|
443
|
+
}
|
|
444
|
+
const operation: PublisherOperation = purpose === "publish"
|
|
445
|
+
? {
|
|
446
|
+
_tag: "PublishOperation",
|
|
447
|
+
subject,
|
|
448
|
+
provider: publication.authority.provider,
|
|
449
|
+
audience: publication.authority.audience,
|
|
450
|
+
purpose,
|
|
451
|
+
decision
|
|
452
|
+
}
|
|
453
|
+
: {
|
|
454
|
+
_tag: "CorrectionOperation",
|
|
455
|
+
subject,
|
|
456
|
+
provider: publication.authority.provider,
|
|
457
|
+
audience: publication.authority.audience,
|
|
458
|
+
purpose,
|
|
459
|
+
decision
|
|
460
|
+
}
|
|
461
|
+
const dispatch = (request: MutationHttpRequest) => mutationHttp.execute(operation, request, grant).pipe(
|
|
462
|
+
Effect.mapError((cause) => failure(
|
|
463
|
+
subject,
|
|
464
|
+
"mutate",
|
|
465
|
+
cause._tag === "CredentialPlatformError" ? cause.commitment : "before-dispatch",
|
|
466
|
+
"GitHub catalog mutation failed at the authorized HTTP boundary."
|
|
467
|
+
))
|
|
468
|
+
)
|
|
469
|
+
const json = (value: unknown): { readonly body: string, readonly headers: Readonly<Record<string, string>> } => {
|
|
470
|
+
const body = JSON.stringify(value)
|
|
471
|
+
return {
|
|
472
|
+
body,
|
|
473
|
+
headers: { ...jsonHeaders, "content-type": "application/json", "content-length": String(new TextEncoder().encode(body).length) }
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
const base = publication.authority.audience.toString()
|
|
477
|
+
const createBlob = Effect.fn("CatalogGitSubject.createBlob")(function*(bytes: Uint8Array) {
|
|
478
|
+
const response = yield* dispatch({
|
|
479
|
+
method: "POST",
|
|
480
|
+
url: `${base}/git/blobs`,
|
|
481
|
+
credentialScheme: "bearer",
|
|
482
|
+
...json({ content: Buffer.from(bytes).toString("base64"), encoding: "base64" })
|
|
483
|
+
})
|
|
484
|
+
const sha = string(object(parseJson(response.body))?.sha)
|
|
485
|
+
return response.status === 201 && sha !== undefined && objectSha.test(sha) && gitObjectDigest(bytes, sha.length) === sha
|
|
486
|
+
? { _tag: "Created", sha } as const
|
|
487
|
+
: { _tag: "Rejected", attempt: mutationAttempt(subject, `catalog-blob-http-${response.status}`, "GitHub did not prove exact catalog blob creation.") } as const
|
|
488
|
+
})
|
|
489
|
+
const targetBlob = yield* createBlob(pair.target)
|
|
490
|
+
if (targetBlob._tag === "Rejected") return targetBlob.attempt
|
|
491
|
+
const stateBlob = yield* createBlob(pair.state)
|
|
492
|
+
if (stateBlob._tag === "Rejected") return stateBlob.attempt
|
|
493
|
+
const treeResponse = yield* dispatch({
|
|
494
|
+
method: "POST",
|
|
495
|
+
url: `${base}/git/trees`,
|
|
496
|
+
credentialScheme: "bearer",
|
|
497
|
+
...json({
|
|
498
|
+
base_tree: selected.snapshot.rootTree,
|
|
499
|
+
tree: [
|
|
500
|
+
{ path: publication.targetPath, mode: "100644", type: "blob", sha: targetBlob.sha },
|
|
501
|
+
{ path: publication.statePath, mode: "100644", type: "blob", sha: stateBlob.sha }
|
|
502
|
+
]
|
|
503
|
+
})
|
|
504
|
+
})
|
|
505
|
+
const proposedTree = string(object(parseJson(treeResponse.body))?.sha)
|
|
506
|
+
if (treeResponse.status !== 201 || proposedTree === undefined || !objectSha.test(proposedTree)) {
|
|
507
|
+
return mutationAttempt(subject, `catalog-tree-http-${treeResponse.status}`, "GitHub did not prove base-tree catalog tree creation.")
|
|
508
|
+
}
|
|
509
|
+
const proposedResponse = yield* get(subject, http, grant, `${base}/git/trees/${proposedTree}?recursive=1`)
|
|
510
|
+
const proposedEntries = proposedResponse.status === 200 ? treeEntries(parseJson(proposedResponse.body), proposedTree) : undefined
|
|
511
|
+
if (proposedEntries === undefined || !treePreserved(
|
|
512
|
+
selected.snapshot.tree,
|
|
513
|
+
proposedEntries,
|
|
514
|
+
publication.targetPath.toString(),
|
|
515
|
+
targetBlob.sha,
|
|
516
|
+
publication.statePath.toString(),
|
|
517
|
+
stateBlob.sha
|
|
518
|
+
)) return mutationAttempt(subject, "catalog-tree-preservation-refused", "Proposed GitHub tree did not preserve every unrelated path, object, and mode.")
|
|
519
|
+
const commitResponse = yield* dispatch({
|
|
520
|
+
method: "POST",
|
|
521
|
+
url: `${base}/git/commits`,
|
|
522
|
+
credentialScheme: "bearer",
|
|
523
|
+
...json({
|
|
524
|
+
message: purpose === "correct"
|
|
525
|
+
? `Correct ${publication.catalogId} catalog state`
|
|
526
|
+
: `Publish ${publication.catalogId} ${publication.version}`,
|
|
527
|
+
tree: proposedTree,
|
|
528
|
+
parents: [selected.snapshot.commit]
|
|
529
|
+
})
|
|
530
|
+
})
|
|
531
|
+
const commit = string(object(parseJson(commitResponse.body))?.sha)
|
|
532
|
+
if (commitResponse.status !== 201 || commit === undefined || !objectSha.test(commit)) {
|
|
533
|
+
return mutationAttempt(subject, `catalog-commit-http-${commitResponse.status}`, "GitHub did not prove exact-parent catalog commit creation.")
|
|
534
|
+
}
|
|
535
|
+
const refResponse = yield* dispatch({
|
|
536
|
+
method: "PATCH",
|
|
537
|
+
url: `${base}/git/refs/heads/${encodeURIComponent(publication.branch)}`,
|
|
538
|
+
credentialScheme: "bearer",
|
|
539
|
+
...json({ sha: commit, force: false })
|
|
540
|
+
})
|
|
541
|
+
if (refResponse.status === 409 || refResponse.status === 422) {
|
|
542
|
+
return mutationAttempt(subject, `catalog-ref-http-${refResponse.status}`, "The catalog branch moved or refused the non-force update; reobservation is required.")
|
|
543
|
+
}
|
|
544
|
+
if (refResponse.status !== 200) {
|
|
545
|
+
return OutcomeUnknown.make({
|
|
546
|
+
subject,
|
|
547
|
+
reason: SafeReason.make(`GitHub catalog ref update returned HTTP ${refResponse.status}; exact reobservation decides the outcome.`)
|
|
548
|
+
})
|
|
549
|
+
}
|
|
550
|
+
const ref = object(parseJson(refResponse.body))
|
|
551
|
+
if (ref?.ref !== `refs/heads/${publication.branch}` || object(ref.object)?.sha !== commit) {
|
|
552
|
+
return OutcomeUnknown.make({
|
|
553
|
+
subject,
|
|
554
|
+
reason: SafeReason.make("GitHub accepted the catalog ref request but returned no exact updated-ref proof.")
|
|
555
|
+
})
|
|
556
|
+
}
|
|
557
|
+
return Started.make({ subject })
|
|
558
|
+
})
|
|
559
|
+
|
|
560
|
+
return {
|
|
561
|
+
id: subject,
|
|
562
|
+
recovery: catalogRecoveryCapabilityProfile,
|
|
563
|
+
observationRequests: observationRequests(publication, subject),
|
|
564
|
+
mutationRequest: mutationRequest(publication, subject, purpose),
|
|
565
|
+
observe,
|
|
566
|
+
decide,
|
|
567
|
+
mutate
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
export const makeCatalogPublicationSubject = (
|
|
572
|
+
bundle: PreparedBundle,
|
|
573
|
+
publication: PreparedCatalogPublication,
|
|
574
|
+
http: HttpAuthorizerShape,
|
|
575
|
+
mutationHttp: AuthorizedMutationHttpShape
|
|
576
|
+
): ReleaseSubject => {
|
|
577
|
+
const target = bundle.blobs.get(publication.targetArtifactId.toString())
|
|
578
|
+
const state = bundle.blobs.get(publication.stateArtifactId.toString())
|
|
579
|
+
if (target === undefined || state === undefined || !digestEquals(sha256Digest(target), publication.targetDigest) ||
|
|
580
|
+
!digestEquals(sha256Digest(state), publication.stateDigest)) {
|
|
581
|
+
const invalid = makeCatalogSubject(publication, http, mutationHttp, {
|
|
582
|
+
target: new Uint8Array(), state: new Uint8Array()
|
|
583
|
+
})
|
|
584
|
+
return {
|
|
585
|
+
...invalid,
|
|
586
|
+
observe: () => Effect.succeed(inconclusive(
|
|
587
|
+
invalid.id,
|
|
588
|
+
"Prepared catalog target/state bytes are missing or disagree with their durable digests."
|
|
589
|
+
)),
|
|
590
|
+
decide: () => ProviderBlocked.make({
|
|
591
|
+
subject: invalid.id,
|
|
592
|
+
reason: SafeReason.make("Prepared catalog target/state evidence is unavailable.")
|
|
593
|
+
}),
|
|
594
|
+
mutate: () => Effect.fail(failure(
|
|
595
|
+
invalid.id,
|
|
596
|
+
"mutate",
|
|
597
|
+
"before-dispatch",
|
|
598
|
+
"Prepared catalog target/state evidence is unavailable."
|
|
599
|
+
))
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
return makeCatalogSubject(publication, http, mutationHttp, { target, state })
|
|
603
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import * as Context from "effect/Context"
|
|
2
|
+
import * as Effect from "effect/Effect"
|
|
3
|
+
import * as Schema from "effect/Schema"
|
|
4
|
+
import { SubjectId } from "../model/authority.js"
|
|
5
|
+
import { Sha256Digest } from "../model/digest.js"
|
|
6
|
+
import { SafeReason } from "./report.js"
|
|
7
|
+
|
|
8
|
+
/** Exact immutable CAS key for one prepared mutation subject. */
|
|
9
|
+
export class PublicationClaimRequest
|
|
10
|
+
extends Schema.Class<PublicationClaimRequest>("PublicationClaimRequest")({
|
|
11
|
+
subject: SubjectId,
|
|
12
|
+
preparedDigest: Sha256Digest
|
|
13
|
+
}) {}
|
|
14
|
+
|
|
15
|
+
export class PublicationClaimUnavailable
|
|
16
|
+
extends Schema.TaggedErrorClass<PublicationClaimUnavailable>()("PublicationClaimUnavailable", {
|
|
17
|
+
subject: SubjectId,
|
|
18
|
+
reason: SafeReason
|
|
19
|
+
}) {}
|
|
20
|
+
|
|
21
|
+
export class PublicationClaimOccupied
|
|
22
|
+
extends Schema.TaggedErrorClass<PublicationClaimOccupied>()("PublicationClaimOccupied", {
|
|
23
|
+
subject: SubjectId,
|
|
24
|
+
reason: SafeReason
|
|
25
|
+
}) {}
|
|
26
|
+
|
|
27
|
+
export type PublicationClaimError = PublicationClaimUnavailable | PublicationClaimOccupied
|
|
28
|
+
|
|
29
|
+
export interface PublicationClaimStoreShape {
|
|
30
|
+
/**
|
|
31
|
+
* Atomically create one terminal claim. A successful call can never become
|
|
32
|
+
* claimable again through lease expiry, process death, or local cleanup.
|
|
33
|
+
* The implementation must be shared by every runner that can dispatch the
|
|
34
|
+
* subject; process memory and runner-local files do not satisfy this seam.
|
|
35
|
+
*/
|
|
36
|
+
readonly claim: (
|
|
37
|
+
request: PublicationClaimRequest
|
|
38
|
+
) => Effect.Effect<void, PublicationClaimError>
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class PublicationClaimStore
|
|
42
|
+
extends Context.Service<PublicationClaimStore, PublicationClaimStoreShape>()(
|
|
43
|
+
"ts-release/PublicationClaimStore"
|
|
44
|
+
) {}
|
|
45
|
+
|
|
46
|
+
export const unavailablePublicationClaimStore: PublicationClaimStoreShape = {
|
|
47
|
+
claim: (request) => Effect.fail(PublicationClaimUnavailable.make({
|
|
48
|
+
subject: request.subject,
|
|
49
|
+
reason: SafeReason.make("A shared durable terminal publication-claim store is not installed by this host.")
|
|
50
|
+
}))
|
|
51
|
+
}
|