@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,94 @@
|
|
|
1
|
+
import {
|
|
2
|
+
githubRecoveryCapabilityProfile
|
|
3
|
+
} from "./github.js"
|
|
4
|
+
import {
|
|
5
|
+
npmRecoveryCapabilityProfile
|
|
6
|
+
} from "./npm.js"
|
|
7
|
+
import { pypiRecoveryCapabilityProfile } from "./pypi.js"
|
|
8
|
+
import { catalogRecoveryCapabilityProfile } from "./catalog-git.js"
|
|
9
|
+
import { validatePublicationProfiles } from "./recovery.js"
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Canonical provider registrations shared by executable capability modules,
|
|
13
|
+
* runtime dispatch, and generated recovery documentation. An authored
|
|
14
|
+
* correction proposal is not an installed conditional correction adapter.
|
|
15
|
+
*/
|
|
16
|
+
export const installedPublicationProfiles = validatePublicationProfiles(Object.freeze({
|
|
17
|
+
npm: Object.freeze({
|
|
18
|
+
id: "publish.npm",
|
|
19
|
+
provider: "npm",
|
|
20
|
+
preparedTag: "PreparedNpmPublication",
|
|
21
|
+
recovery: npmRecoveryCapabilityProfile,
|
|
22
|
+
correctionAdapters: [] as const,
|
|
23
|
+
evidence: Object.freeze({
|
|
24
|
+
reviewedAt: "2026-08-12",
|
|
25
|
+
observationSources: Object.freeze([
|
|
26
|
+
"https://github.com/npm/registry/blob/main/docs/responses/package-metadata.md",
|
|
27
|
+
"https://docs.npmjs.com/cli/v11/commands/npm-publish/"
|
|
28
|
+
]),
|
|
29
|
+
correctionSources: Object.freeze([
|
|
30
|
+
"https://docs.npmjs.com/cli/v11/commands/npm-deprecate/",
|
|
31
|
+
"https://docs.npmjs.com/policies/unpublish/"
|
|
32
|
+
]),
|
|
33
|
+
correctionFinding: "Official npm documentation exposes deprecation but no conditional update bound to an observed package generation."
|
|
34
|
+
})
|
|
35
|
+
}),
|
|
36
|
+
pypi: Object.freeze({
|
|
37
|
+
id: "publish.pypi",
|
|
38
|
+
provider: "pypi",
|
|
39
|
+
preparedTag: "PreparedPyPiPublication",
|
|
40
|
+
recovery: pypiRecoveryCapabilityProfile,
|
|
41
|
+
correctionAdapters: [] as const,
|
|
42
|
+
evidence: Object.freeze({
|
|
43
|
+
reviewedAt: "2026-08-13",
|
|
44
|
+
observationSources: Object.freeze([
|
|
45
|
+
"https://packaging.python.org/en/latest/specifications/simple-repository-api/",
|
|
46
|
+
"https://packaging.python.org/en/latest/specifications/file-yanking/"
|
|
47
|
+
]),
|
|
48
|
+
correctionSources: Object.freeze([
|
|
49
|
+
"https://docs.pypi.org/project-management/yanking/"
|
|
50
|
+
]),
|
|
51
|
+
correctionFinding: "PyPI documents yanking behavior but no stable exact conditional per-file update API; no correction adapter is installed."
|
|
52
|
+
})
|
|
53
|
+
}),
|
|
54
|
+
catalogGit: Object.freeze({
|
|
55
|
+
id: "publish.catalog-git",
|
|
56
|
+
provider: "catalog-git",
|
|
57
|
+
preparedTag: "PreparedCatalogPublication",
|
|
58
|
+
recovery: catalogRecoveryCapabilityProfile,
|
|
59
|
+
correctionAdapters: ["forward-catalog-state"] as const,
|
|
60
|
+
evidence: Object.freeze({
|
|
61
|
+
reviewedAt: "2026-08-13",
|
|
62
|
+
observationSources: Object.freeze([
|
|
63
|
+
"https://docs.github.com/en/rest/git/refs?apiVersion=2022-11-28",
|
|
64
|
+
"https://docs.github.com/en/rest/git/trees?apiVersion=2022-11-28"
|
|
65
|
+
]),
|
|
66
|
+
correctionSources: Object.freeze([
|
|
67
|
+
"https://docs.github.com/en/rest/git/trees?apiVersion=2022-11-28#create-a-tree",
|
|
68
|
+
"https://docs.github.com/en/rest/git/refs?apiVersion=2022-11-28#update-a-reference"
|
|
69
|
+
]),
|
|
70
|
+
correctionFinding: "Catalog correction is an exact forward SemVer replacement of the managed target/state pair on one observed Git commit; the ref update is non-forced."
|
|
71
|
+
})
|
|
72
|
+
}),
|
|
73
|
+
github: Object.freeze({
|
|
74
|
+
id: "publish.github",
|
|
75
|
+
provider: "github",
|
|
76
|
+
preparedTag: "PreparedGitHubPublication",
|
|
77
|
+
recovery: githubRecoveryCapabilityProfile,
|
|
78
|
+
correctionAdapters: [] as const,
|
|
79
|
+
evidence: Object.freeze({
|
|
80
|
+
reviewedAt: "2026-08-12",
|
|
81
|
+
observationSources: Object.freeze([
|
|
82
|
+
"https://docs.github.com/en/rest/git/refs?apiVersion=2022-11-28",
|
|
83
|
+
"https://docs.github.com/en/rest/git/tags?apiVersion=2022-11-28",
|
|
84
|
+
"https://docs.github.com/en/rest/releases/releases?apiVersion=2022-11-28",
|
|
85
|
+
"https://docs.github.com/en/rest/releases/assets?apiVersion=2022-11-28"
|
|
86
|
+
]),
|
|
87
|
+
correctionSources: Object.freeze([
|
|
88
|
+
"https://docs.github.com/en/rest/releases/releases?apiVersion=2022-11-28#update-a-release",
|
|
89
|
+
"https://docs.github.com/en/rest/using-the-rest-api/best-practices-for-using-the-rest-api?apiVersion=2022-11-28#use-conditional-requests"
|
|
90
|
+
]),
|
|
91
|
+
correctionFinding: "GitHub documents conditional reads, but unsafe-method conditions are unsupported unless an endpoint says otherwise; the release update endpoint documents none."
|
|
92
|
+
})
|
|
93
|
+
})
|
|
94
|
+
}))
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import * as Context from "effect/Context"
|
|
2
|
+
import * as Effect from "effect/Effect"
|
|
3
|
+
import * as Schema from "effect/Schema"
|
|
4
|
+
import type * as Scope from "effect/Scope"
|
|
5
|
+
import type { NonEmptyName, Version } from "../model/primitives.js"
|
|
6
|
+
import type { PreparedNpmPublication } from "../release/prepared.js"
|
|
7
|
+
import type { CredentialAuthorityError, MutationCredentialGrant, ScopedSecret, WorkloadIdentity } from "./authority.js"
|
|
8
|
+
import type { HttpAuthorizationError } from "./http.js"
|
|
9
|
+
import type { PublisherOperation } from "./authority.js"
|
|
10
|
+
|
|
11
|
+
const NpmUserConfigTypeId: unique symbol = Symbol("ts-release/NpmUserConfig")
|
|
12
|
+
|
|
13
|
+
/** Opaque handle: only CertifiedPublisherSpawn can eliminate it to a path. */
|
|
14
|
+
export interface NpmUserConfig {
|
|
15
|
+
readonly _tag: "NpmUserConfig"
|
|
16
|
+
readonly [NpmUserConfigTypeId]: typeof NpmUserConfigTypeId
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const makeNpmUserConfigHandle = (): NpmUserConfig => Object.freeze({
|
|
20
|
+
_tag: "NpmUserConfig" as const,
|
|
21
|
+
[NpmUserConfigTypeId]: NpmUserConfigTypeId
|
|
22
|
+
} as NpmUserConfig)
|
|
23
|
+
|
|
24
|
+
export type NpmPublishOperation = Extract<PublisherOperation, { readonly _tag: "PublishOperation" }>
|
|
25
|
+
|
|
26
|
+
export interface NpmUserConfigInput {
|
|
27
|
+
readonly operation: NpmPublishOperation
|
|
28
|
+
readonly registryUrl: string
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface NpmUserConfigResourceShape {
|
|
32
|
+
readonly acquire: (
|
|
33
|
+
input: NpmUserConfigInput,
|
|
34
|
+
grant: ScopedSecret
|
|
35
|
+
) => Effect.Effect<NpmUserConfig, CredentialAuthorityError | HttpAuthorizationError, Scope.Scope>
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export class NpmUserConfigResource
|
|
39
|
+
extends Context.Service<NpmUserConfigResource, NpmUserConfigResourceShape>()(
|
|
40
|
+
"ts-release/NpmUserConfigResource"
|
|
41
|
+
) {}
|
|
42
|
+
|
|
43
|
+
interface CertifiedPublisherSpecBase {
|
|
44
|
+
readonly operation: NpmPublishOperation
|
|
45
|
+
readonly cwd: string
|
|
46
|
+
readonly tarballPath: string
|
|
47
|
+
readonly packageName: NonEmptyName
|
|
48
|
+
readonly version: Version
|
|
49
|
+
readonly registryUrl: PreparedNpmPublication["registryUrl"]
|
|
50
|
+
readonly distTag: PreparedNpmPublication["distTag"]
|
|
51
|
+
readonly access: PreparedNpmPublication["access"]
|
|
52
|
+
readonly provenance: PreparedNpmPublication["provenance"]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface NpmPublisherSpec extends CertifiedPublisherSpecBase {
|
|
56
|
+
readonly _tag: "NpmPublisherSpec"
|
|
57
|
+
readonly operation: NpmPublishOperation
|
|
58
|
+
readonly userConfig: NpmUserConfig
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export interface WorkloadPublisherSpec extends CertifiedPublisherSpecBase {
|
|
62
|
+
readonly _tag: "WorkloadPublisherSpec"
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export type CertifiedPublisherSpec = NpmPublisherSpec | WorkloadPublisherSpec
|
|
66
|
+
|
|
67
|
+
/** The credential-bearing host owns the only admitted npm mutation argv. */
|
|
68
|
+
export const npmPublishArgv = (
|
|
69
|
+
spec: CertifiedPublisherSpec
|
|
70
|
+
): readonly [string, ...Array<string>] => [
|
|
71
|
+
"npm",
|
|
72
|
+
"publish",
|
|
73
|
+
spec.tarballPath,
|
|
74
|
+
"--ignore-scripts",
|
|
75
|
+
"--registry",
|
|
76
|
+
spec.registryUrl,
|
|
77
|
+
"--tag",
|
|
78
|
+
spec.distTag,
|
|
79
|
+
"--access",
|
|
80
|
+
spec.access,
|
|
81
|
+
...(spec.provenance === "required"
|
|
82
|
+
? ["--provenance"]
|
|
83
|
+
: spec.provenance === "disabled"
|
|
84
|
+
? ["--provenance=false"]
|
|
85
|
+
: []),
|
|
86
|
+
"--json"
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
export class RejectedBeforeStart
|
|
90
|
+
extends Schema.TaggedClass<RejectedBeforeStart>()("RejectedBeforeStart", {
|
|
91
|
+
commitment: Schema.Literal("before-dispatch"),
|
|
92
|
+
reason: Schema.NonEmptyString
|
|
93
|
+
}) {}
|
|
94
|
+
|
|
95
|
+
export class PublisherExited
|
|
96
|
+
extends Schema.TaggedClass<PublisherExited>()("PublisherExited", {
|
|
97
|
+
commitment: Schema.Literal("started"),
|
|
98
|
+
exitCode: Schema.Int,
|
|
99
|
+
stdout: Schema.String,
|
|
100
|
+
stderr: Schema.String
|
|
101
|
+
}) {}
|
|
102
|
+
|
|
103
|
+
export class PublisherOutcomeUnknown
|
|
104
|
+
extends Schema.TaggedClass<PublisherOutcomeUnknown>()("PublisherOutcomeUnknown", {
|
|
105
|
+
commitment: Schema.Literal("unknown"),
|
|
106
|
+
reason: Schema.NonEmptyString
|
|
107
|
+
}) {}
|
|
108
|
+
|
|
109
|
+
export const CertifiedPublisherResult = Schema.Union([
|
|
110
|
+
RejectedBeforeStart,
|
|
111
|
+
PublisherExited,
|
|
112
|
+
PublisherOutcomeUnknown
|
|
113
|
+
])
|
|
114
|
+
export type CertifiedPublisherResult = typeof CertifiedPublisherResult.Type
|
|
115
|
+
|
|
116
|
+
export interface CertifiedPublisherSpawnShape {
|
|
117
|
+
readonly preflightTrustedNpm: (
|
|
118
|
+
operation: NpmPublishOperation,
|
|
119
|
+
grant: WorkloadIdentity
|
|
120
|
+
) => Effect.Effect<void, CredentialAuthorityError | HttpAuthorizationError>
|
|
121
|
+
readonly spawn: (
|
|
122
|
+
spec: CertifiedPublisherSpec,
|
|
123
|
+
grant: MutationCredentialGrant
|
|
124
|
+
) => Effect.Effect<CertifiedPublisherResult, CredentialAuthorityError | HttpAuthorizationError>
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export class CertifiedPublisherSpawn
|
|
128
|
+
extends Context.Service<CertifiedPublisherSpawn, CertifiedPublisherSpawnShape>()(
|
|
129
|
+
"ts-release/CertifiedPublisherSpawn"
|
|
130
|
+
) {}
|
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
import * as Effect from "effect/Effect"
|
|
2
|
+
import * as Schema from "effect/Schema"
|
|
3
|
+
import type { CredentialRequest, ResolvedAuthStrategy } from "../model/authority.js"
|
|
4
|
+
import { CanonicalAudience, CredentialRequest as CredentialRequestSchema } from "../model/authority.js"
|
|
5
|
+
import { digestEquals, formatSha256Hex, parseSha256Hex, sha256Digest } from "../model/digest.js"
|
|
6
|
+
import { NonEmptyName } from "../model/primitives.js"
|
|
7
|
+
import { encodePreparedRelease, type PreparedPyPiFile, type PreparedPyPiPublication } from "../release/prepared.js"
|
|
8
|
+
import type { PreparedBundle } from "../release/prepared-store.js"
|
|
9
|
+
import type { CredentialGrant, MutationCredentialGrant } from "./authority.js"
|
|
10
|
+
import {
|
|
11
|
+
PublicationClaimRequest,
|
|
12
|
+
unavailablePublicationClaimStore,
|
|
13
|
+
type PublicationClaimStoreShape
|
|
14
|
+
} from "./claim.js"
|
|
15
|
+
import type { AuthorizedMutationHttpShape, HttpAuthorizerShape, HttpResponse } from "./http.js"
|
|
16
|
+
import {
|
|
17
|
+
ReleaseSubjectError,
|
|
18
|
+
type ReleaseObservationContext,
|
|
19
|
+
type ReleaseSubject
|
|
20
|
+
} from "./coordinator.js"
|
|
21
|
+
import {
|
|
22
|
+
AbsenceBasis,
|
|
23
|
+
AuthoritativelyAbsent,
|
|
24
|
+
Conflict,
|
|
25
|
+
Difference,
|
|
26
|
+
InconclusiveObservation,
|
|
27
|
+
MutationPrecondition,
|
|
28
|
+
NeedsMutation,
|
|
29
|
+
OutcomeUnknown,
|
|
30
|
+
PresentDifferent,
|
|
31
|
+
PresentEquivalent,
|
|
32
|
+
ProviderAlreadyEquivalent,
|
|
33
|
+
ProviderBlocked,
|
|
34
|
+
ProviderRejectionFact,
|
|
35
|
+
RejectedByProvider,
|
|
36
|
+
SafeReason,
|
|
37
|
+
Started,
|
|
38
|
+
VisibilityBasis,
|
|
39
|
+
VisibilityPending,
|
|
40
|
+
type MutationDecision,
|
|
41
|
+
type Observation,
|
|
42
|
+
type ProviderDecision
|
|
43
|
+
} from "./report.js"
|
|
44
|
+
import { makeRecoveryCapabilityProfile } from "./recovery.js"
|
|
45
|
+
|
|
46
|
+
export const pypiRecoveryCapabilityProfile = makeRecoveryCapabilityProfile({
|
|
47
|
+
observation: "exact",
|
|
48
|
+
authoritativeAbsence: "provider-specific",
|
|
49
|
+
createAuthorization: "none",
|
|
50
|
+
replay: "unsafe",
|
|
51
|
+
identifierReuse: "consumed-after-delete",
|
|
52
|
+
correction: [],
|
|
53
|
+
exposure: "persistent-to-consumers",
|
|
54
|
+
historyRequirement: "durable-cas-required",
|
|
55
|
+
readConvergence: {
|
|
56
|
+
contract: {
|
|
57
|
+
_tag: "assumed",
|
|
58
|
+
basis: "ASSUMED/UNVERIFIED: no authorized live mutation evidence establishes PyPI Simple API read-convergence timing."
|
|
59
|
+
},
|
|
60
|
+
observationRetry: {
|
|
61
|
+
maxAttempts: 6,
|
|
62
|
+
backoff: { baseMs: 2_000, factor: 2, capMs: 30_000 },
|
|
63
|
+
totalBudgetMs: 120_000
|
|
64
|
+
},
|
|
65
|
+
retryEligible: "VisibilityPending | Inconclusive",
|
|
66
|
+
exhaustion: "UncertainSubject with full trace"
|
|
67
|
+
}
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
export const pypiProviderProtocolDocumentation = Object.freeze({
|
|
71
|
+
reviewedAt: "2026-08-13",
|
|
72
|
+
simpleApi: "https://packaging.python.org/en/latest/specifications/simple-repository-api/",
|
|
73
|
+
yanking: "https://packaging.python.org/en/latest/specifications/file-yanking/",
|
|
74
|
+
upload: "https://docs.pypi.org/api/upload/",
|
|
75
|
+
trustedPublishing: "https://docs.pypi.org/trusted-publishers/using-a-publisher/"
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
/** No stable exact conditional per-file yank mutation is installed. */
|
|
79
|
+
export class PyPiYankUnsupported
|
|
80
|
+
extends Schema.TaggedErrorClass<PyPiYankUnsupported>()("PyPiYankUnsupported", {
|
|
81
|
+
filename: Schema.NonEmptyString,
|
|
82
|
+
reason: SafeReason
|
|
83
|
+
}) {}
|
|
84
|
+
|
|
85
|
+
const asObject = (value: unknown): Readonly<Record<string, unknown>> | undefined =>
|
|
86
|
+
typeof value === "object" && value !== null && !Array.isArray(value)
|
|
87
|
+
? value as Readonly<Record<string, unknown>>
|
|
88
|
+
: undefined
|
|
89
|
+
|
|
90
|
+
const parseJson = (body: Uint8Array | string): unknown | undefined => {
|
|
91
|
+
try {
|
|
92
|
+
const text = typeof body === "string" ? body : new TextDecoder("utf-8", { fatal: true }).decode(body)
|
|
93
|
+
return JSON.parse(text) as unknown
|
|
94
|
+
} catch {
|
|
95
|
+
return undefined
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const header = (response: HttpResponse, name: string): string | undefined => {
|
|
100
|
+
const found = Object.entries(response.headers).find(([candidate]) => candidate.toLowerCase() === name.toLowerCase())
|
|
101
|
+
return found?.[1]
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const contentType = (value: string | undefined): string | undefined => value?.split(";", 1)[0]?.trim().toLowerCase()
|
|
105
|
+
|
|
106
|
+
type SimpleFile = {
|
|
107
|
+
readonly filename: string
|
|
108
|
+
readonly size: number
|
|
109
|
+
readonly sha256: string
|
|
110
|
+
readonly yanked: false | string
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
type SimpleProject = {
|
|
114
|
+
readonly apiVersion: string
|
|
115
|
+
readonly name: string
|
|
116
|
+
readonly files: ReadonlyArray<SimpleFile>
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const simpleProject = (value: unknown): SimpleProject | undefined => {
|
|
120
|
+
const root = asObject(value)
|
|
121
|
+
const meta = root === undefined ? undefined : asObject(root.meta)
|
|
122
|
+
if (root === undefined || meta === undefined || typeof meta["api-version"] !== "string" ||
|
|
123
|
+
typeof root.name !== "string" || !Array.isArray(root.files)) return undefined
|
|
124
|
+
const files: Array<SimpleFile> = []
|
|
125
|
+
const filenames = new Set<string>()
|
|
126
|
+
for (const raw of root.files) {
|
|
127
|
+
const file = asObject(raw)
|
|
128
|
+
const hashes = file === undefined ? undefined : asObject(file.hashes)
|
|
129
|
+
if (file === undefined || hashes === undefined || typeof file.filename !== "string" ||
|
|
130
|
+
!Number.isSafeInteger(file.size) || (file.size as number) < 0 ||
|
|
131
|
+
typeof hashes.sha256 !== "string" || !/^[a-f0-9]{64}$/u.test(hashes.sha256)) return undefined
|
|
132
|
+
if (filenames.has(file.filename)) return undefined
|
|
133
|
+
filenames.add(file.filename)
|
|
134
|
+
const rawYanked = file.yanked
|
|
135
|
+
if (rawYanked !== undefined && typeof rawYanked !== "boolean" && typeof rawYanked !== "string") return undefined
|
|
136
|
+
files.push({
|
|
137
|
+
filename: file.filename,
|
|
138
|
+
size: file.size as number,
|
|
139
|
+
sha256: hashes.sha256,
|
|
140
|
+
yanked: rawYanked === true ? "yanked without a reason" : typeof rawYanked === "string" && rawYanked.length > 0
|
|
141
|
+
? rawYanked : false
|
|
142
|
+
})
|
|
143
|
+
}
|
|
144
|
+
return { apiVersion: meta["api-version"], name: root.name, files }
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const fingerprint = (value: string): SafeReason => SafeReason.make(
|
|
148
|
+
`provider value sha256-${formatSha256Hex(sha256Digest(new TextEncoder().encode(value)))}`
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
const difference = (field: string, expected: string, observed: string): Difference => Difference.make({
|
|
152
|
+
field: NonEmptyName.make(field),
|
|
153
|
+
expected: SafeReason.make(expected),
|
|
154
|
+
observed: fingerprint(observed)
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
const inconclusive = (file: PreparedPyPiFile, reason: string): InconclusiveObservation =>
|
|
158
|
+
InconclusiveObservation.make({ subject: file.authority.subject, reason: SafeReason.make(reason) })
|
|
159
|
+
|
|
160
|
+
const observationRequests = (
|
|
161
|
+
publication: PreparedPyPiPublication,
|
|
162
|
+
file: PreparedPyPiFile
|
|
163
|
+
): readonly [CredentialRequest, ...Array<CredentialRequest>] => {
|
|
164
|
+
const strategy: ResolvedAuthStrategy = file.authority.observationStrategies[0]!
|
|
165
|
+
return [CredentialRequestSchema.make({
|
|
166
|
+
subject: file.authority.subject,
|
|
167
|
+
provider: file.authority.provider,
|
|
168
|
+
audience: CanonicalAudience.make(publication.projectUrl),
|
|
169
|
+
purpose: "observe",
|
|
170
|
+
strategy
|
|
171
|
+
})]
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const mutationRequest = (file: PreparedPyPiFile): CredentialRequest => CredentialRequestSchema.make({
|
|
175
|
+
subject: file.authority.subject,
|
|
176
|
+
provider: file.authority.provider,
|
|
177
|
+
audience: file.authority.audience,
|
|
178
|
+
purpose: "publish",
|
|
179
|
+
strategy: file.authority.publishStrategy
|
|
180
|
+
})
|
|
181
|
+
|
|
182
|
+
const visibilityPending = (file: PreparedPyPiFile): VisibilityPending => VisibilityPending.make({
|
|
183
|
+
subject: file.authority.subject,
|
|
184
|
+
expectation: SafeReason.make("The exact uploaded PyPI filename, size, and SHA-256 become visible."),
|
|
185
|
+
basis: VisibilityBasis.make({
|
|
186
|
+
kind: NonEmptyName.make("pypi-post-upload-simple-read"),
|
|
187
|
+
detail: SafeReason.make("The same invocation dispatched one exact upload and is rereading the JSON Simple API only.")
|
|
188
|
+
})
|
|
189
|
+
})
|
|
190
|
+
|
|
191
|
+
const visibleFileAbsent = (file: PreparedPyPiFile): AuthoritativelyAbsent => AuthoritativelyAbsent.make({
|
|
192
|
+
subject: file.authority.subject,
|
|
193
|
+
basis: AbsenceBasis.make({
|
|
194
|
+
kind: NonEmptyName.make("pypi-visible-project-file-absent"),
|
|
195
|
+
detail: SafeReason.make("A standards-compliant visible project page omitted the exact prepared filename.")
|
|
196
|
+
})
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
const decide = (file: PreparedPyPiFile, observation: Observation): ProviderDecision => {
|
|
200
|
+
switch (observation._tag) {
|
|
201
|
+
case "PresentEquivalent": return ProviderAlreadyEquivalent.make({ subject: file.authority.subject })
|
|
202
|
+
case "PresentDifferent": return Conflict.make({ subject: file.authority.subject, differences: observation.differences })
|
|
203
|
+
case "AuthoritativelyAbsent": return observation.basis.kind === "pypi-visible-project-file-absent"
|
|
204
|
+
? NeedsMutation.make({
|
|
205
|
+
subject: file.authority.subject,
|
|
206
|
+
precondition: MutationPrecondition.make({ kind: NonEmptyName.make("pypi-visible-project-file-absent") })
|
|
207
|
+
})
|
|
208
|
+
: ProviderBlocked.make({
|
|
209
|
+
subject: file.authority.subject,
|
|
210
|
+
reason: SafeReason.make("PyPI absence lacks a visible-project exact-filename proof.")
|
|
211
|
+
})
|
|
212
|
+
case "VisibilityPending":
|
|
213
|
+
case "Inconclusive": return ProviderBlocked.make({
|
|
214
|
+
subject: file.authority.subject,
|
|
215
|
+
reason: SafeReason.make("PyPI observation did not prove exact equivalence or visible-project file absence.")
|
|
216
|
+
})
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const bytesConcat = (parts: ReadonlyArray<Uint8Array>): Uint8Array => {
|
|
221
|
+
const length = parts.reduce((sum, part) => sum + part.length, 0)
|
|
222
|
+
const result = new Uint8Array(length)
|
|
223
|
+
let offset = 0
|
|
224
|
+
for (const part of parts) { result.set(part, offset); offset += part.length }
|
|
225
|
+
return result
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const containsBytes = (haystack: Uint8Array, needle: Uint8Array): boolean => {
|
|
229
|
+
if (needle.length === 0 || needle.length > haystack.length) return false
|
|
230
|
+
outer: for (let offset = 0; offset <= haystack.length - needle.length; offset += 1) {
|
|
231
|
+
for (let index = 0; index < needle.length; index += 1) {
|
|
232
|
+
if (haystack[offset + index] !== needle[index]) continue outer
|
|
233
|
+
}
|
|
234
|
+
return true
|
|
235
|
+
}
|
|
236
|
+
return false
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const multipart = (
|
|
240
|
+
publication: PreparedPyPiPublication,
|
|
241
|
+
file: PreparedPyPiFile,
|
|
242
|
+
bytes: Uint8Array
|
|
243
|
+
): { readonly contentType: string, readonly body: Uint8Array } => {
|
|
244
|
+
const encode = (value: string): Uint8Array => new TextEncoder().encode(value)
|
|
245
|
+
const boundaryBase = `ts-release-${file.sha256.hex}`
|
|
246
|
+
let boundary = boundaryBase
|
|
247
|
+
for (let suffix = 0; containsBytes(bytes, encode(boundary)); suffix += 1) {
|
|
248
|
+
boundary = `${boundaryBase}-${suffix + 1}`
|
|
249
|
+
}
|
|
250
|
+
const fields: ReadonlyArray<readonly [string, string]> = [
|
|
251
|
+
[":action", "file_upload"],
|
|
252
|
+
["protocol_version", "1"],
|
|
253
|
+
["sha256_digest", file.sha256.hex],
|
|
254
|
+
["filetype", file.distribution._tag === "wheel" ? "bdist_wheel" : "sdist"],
|
|
255
|
+
["pyversion", file.distribution.pythonTag],
|
|
256
|
+
["metadata_version", file.distribution.metadataVersion],
|
|
257
|
+
["name", publication.project],
|
|
258
|
+
["version", publication.version]
|
|
259
|
+
]
|
|
260
|
+
const parts: Array<Uint8Array> = []
|
|
261
|
+
for (const [name, value] of fields) parts.push(encode(
|
|
262
|
+
`--${boundary}\r\nContent-Disposition: form-data; name="${name}"\r\n\r\n${value}\r\n`
|
|
263
|
+
))
|
|
264
|
+
parts.push(encode(
|
|
265
|
+
`--${boundary}\r\nContent-Disposition: form-data; name="content"; filename="${file.filename}"\r\n` +
|
|
266
|
+
`Content-Type: ${file.mediaType}\r\n\r\n`
|
|
267
|
+
))
|
|
268
|
+
parts.push(bytes)
|
|
269
|
+
parts.push(encode(`\r\n--${boundary}--\r\n`))
|
|
270
|
+
return { contentType: `multipart/form-data; boundary=${boundary}`, body: bytesConcat(parts) }
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const validDecision = (decision: MutationDecision): boolean =>
|
|
274
|
+
decision._tag === "NeedsMutation" && decision.precondition.kind === "pypi-visible-project-file-absent"
|
|
275
|
+
|
|
276
|
+
const mutationFailure = (file: PreparedPyPiFile, cause: unknown): ReleaseSubjectError =>
|
|
277
|
+
new ReleaseSubjectError({
|
|
278
|
+
subject: file.authority.subject,
|
|
279
|
+
phase: "mutate",
|
|
280
|
+
commitment: typeof cause === "object" && cause !== null && "commitment" in cause && cause.commitment === "unknown"
|
|
281
|
+
? "unknown" : "before-dispatch",
|
|
282
|
+
reason: SafeReason.make("The typed PyPI upload boundary rejected or lost the exact prepared file operation.")
|
|
283
|
+
})
|
|
284
|
+
|
|
285
|
+
/** One exact independently recoverable Simple-API/upload subject. */
|
|
286
|
+
export const makePyPiSubject = (
|
|
287
|
+
bundle: PreparedBundle,
|
|
288
|
+
publication: PreparedPyPiPublication,
|
|
289
|
+
file: PreparedPyPiFile,
|
|
290
|
+
http: HttpAuthorizerShape,
|
|
291
|
+
mutationHttp: AuthorizedMutationHttpShape,
|
|
292
|
+
prerequisites: ReadonlyArray<PreparedPyPiFile> = [],
|
|
293
|
+
claims: PublicationClaimStoreShape = unavailablePublicationClaimStore
|
|
294
|
+
): ReleaseSubject => {
|
|
295
|
+
const bytes = bundle.blobs.get(file.artifactId.toString())
|
|
296
|
+
const preparedDigest = sha256Digest(encodePreparedRelease(bundle.manifest))
|
|
297
|
+
const observe = Effect.fn("PyPiFileSubject.observe")(function*(
|
|
298
|
+
grant: CredentialGrant,
|
|
299
|
+
context: ReleaseObservationContext
|
|
300
|
+
) {
|
|
301
|
+
if (grant._tag !== "AnonymousAccess") {
|
|
302
|
+
return inconclusive(file, "PyPI Simple API observation requires the prepared anonymous strategy.")
|
|
303
|
+
}
|
|
304
|
+
if (bytes === undefined || bytes.length !== file.size || !digestEquals(sha256Digest(bytes), file.sha256)) {
|
|
305
|
+
return inconclusive(file, "The exact prepared PyPI artifact bytes are unavailable.")
|
|
306
|
+
}
|
|
307
|
+
const response = yield* http.execute({
|
|
308
|
+
subject: file.authority.subject,
|
|
309
|
+
method: "GET",
|
|
310
|
+
url: publication.projectUrl,
|
|
311
|
+
headers: { accept: "application/vnd.pypi.simple.v1+json" }
|
|
312
|
+
}, grant).pipe(Effect.mapError((cause) => new ReleaseSubjectError({
|
|
313
|
+
subject: file.authority.subject,
|
|
314
|
+
phase: "observe",
|
|
315
|
+
commitment: cause._tag === "CredentialPlatformError" ? cause.commitment : "before-dispatch",
|
|
316
|
+
reason: SafeReason.make("PyPI Simple API observation could not be completed by the host HTTP boundary.")
|
|
317
|
+
})))
|
|
318
|
+
if (response.status === 404) {
|
|
319
|
+
return context.phase === "post-mutation" && context.attempt._tag !== "RejectedBeforeDispatch"
|
|
320
|
+
? visibilityPending(file)
|
|
321
|
+
: inconclusive(file, "A PyPI project 404 does not prove public absence, private absence, or first-create authority.")
|
|
322
|
+
}
|
|
323
|
+
if (response.status < 200 || response.status >= 300) {
|
|
324
|
+
return inconclusive(file, `PyPI Simple API observation returned HTTP ${response.status}.`)
|
|
325
|
+
}
|
|
326
|
+
if (contentType(header(response, "content-type")) !== "application/vnd.pypi.simple.v1+json") {
|
|
327
|
+
return inconclusive(file, "PyPI Simple API response did not negotiate the required v1 JSON media type.")
|
|
328
|
+
}
|
|
329
|
+
const project = simpleProject(parseJson(response.body))
|
|
330
|
+
if (project === undefined) return inconclusive(file, "PyPI Simple API response was malformed or omitted exact file equality fields.")
|
|
331
|
+
const version = /^(\d+)\.(\d+)$/u.exec(project.apiVersion)
|
|
332
|
+
if (version === null || Number(version[1]) !== 1 || Number(version[2]) < 1) {
|
|
333
|
+
return inconclusive(file, "PyPI Simple API version is older than 1.1 or has an unsupported major version.")
|
|
334
|
+
}
|
|
335
|
+
if (project.name !== publication.project) {
|
|
336
|
+
return PresentDifferent.make({
|
|
337
|
+
subject: file.authority.subject,
|
|
338
|
+
differences: [difference("project", publication.project, project.name)]
|
|
339
|
+
})
|
|
340
|
+
}
|
|
341
|
+
const observed = project.files.find((candidate) => candidate.filename === file.filename)
|
|
342
|
+
if (observed === undefined) {
|
|
343
|
+
return context.phase === "post-mutation" && context.attempt._tag !== "RejectedBeforeDispatch"
|
|
344
|
+
? visibilityPending(file)
|
|
345
|
+
: visibleFileAbsent(file)
|
|
346
|
+
}
|
|
347
|
+
const differences: Array<Difference> = []
|
|
348
|
+
if (observed.size !== file.size) differences.push(difference("size", String(file.size), String(observed.size)))
|
|
349
|
+
try {
|
|
350
|
+
if (!digestEquals(parseSha256Hex(observed.sha256), file.sha256)) {
|
|
351
|
+
differences.push(difference("sha256", file.sha256.hex, observed.sha256))
|
|
352
|
+
}
|
|
353
|
+
} catch {
|
|
354
|
+
return inconclusive(file, "PyPI Simple API returned a malformed SHA-256 digest.")
|
|
355
|
+
}
|
|
356
|
+
if (observed.yanked !== false) differences.push(difference("yanked", "false", observed.yanked))
|
|
357
|
+
return differences.length === 0
|
|
358
|
+
? PresentEquivalent.make({ subject: file.authority.subject })
|
|
359
|
+
: PresentDifferent.make({
|
|
360
|
+
subject: file.authority.subject,
|
|
361
|
+
differences: differences as [Difference, ...Array<Difference>]
|
|
362
|
+
})
|
|
363
|
+
})
|
|
364
|
+
|
|
365
|
+
const mutate = (decision: MutationDecision, grant: MutationCredentialGrant) => {
|
|
366
|
+
if (!validDecision(decision) || bytes === undefined) return Effect.fail(new ReleaseSubjectError({
|
|
367
|
+
subject: file.authority.subject,
|
|
368
|
+
phase: "mutate",
|
|
369
|
+
commitment: "before-dispatch",
|
|
370
|
+
reason: SafeReason.make("PyPI mutation lacks its exact visible-project file-absence proof or prepared bytes.")
|
|
371
|
+
}))
|
|
372
|
+
if (grant._tag === "WorkloadIdentity" || publication.authentication.strategy !== "token") {
|
|
373
|
+
return Effect.fail(new ReleaseSubjectError({
|
|
374
|
+
subject: file.authority.subject,
|
|
375
|
+
phase: "mutate",
|
|
376
|
+
commitment: "before-dispatch",
|
|
377
|
+
reason: SafeReason.make("PyPI trusted publishing is owned externally by pypa/gh-action-pypi-publish@release/v1.")
|
|
378
|
+
}))
|
|
379
|
+
}
|
|
380
|
+
const operation = {
|
|
381
|
+
_tag: "PublishOperation" as const,
|
|
382
|
+
subject: file.authority.subject,
|
|
383
|
+
provider: file.authority.provider,
|
|
384
|
+
audience: file.authority.audience,
|
|
385
|
+
purpose: "publish" as const,
|
|
386
|
+
decision
|
|
387
|
+
}
|
|
388
|
+
const form = multipart(publication, file, bytes)
|
|
389
|
+
return mutationHttp.execute(operation, {
|
|
390
|
+
method: "POST",
|
|
391
|
+
url: publication.uploadUrl,
|
|
392
|
+
credentialScheme: "pypi-token-basic",
|
|
393
|
+
headers: {
|
|
394
|
+
accept: "text/plain, application/json;q=0.1",
|
|
395
|
+
"content-type": form.contentType,
|
|
396
|
+
"content-length": String(form.body.length)
|
|
397
|
+
},
|
|
398
|
+
body: form.body
|
|
399
|
+
}, grant).pipe(
|
|
400
|
+
Effect.map((response) => response.status >= 200 && response.status < 300
|
|
401
|
+
? Started.make({ subject: file.authority.subject })
|
|
402
|
+
: response.status >= 300 && response.status < 400
|
|
403
|
+
? OutcomeUnknown.make({
|
|
404
|
+
subject: file.authority.subject,
|
|
405
|
+
reason: SafeReason.make("PyPI upload returned a redirect that the typed boundary did not follow.")
|
|
406
|
+
})
|
|
407
|
+
: RejectedByProvider.make({
|
|
408
|
+
subject: file.authority.subject,
|
|
409
|
+
fact: ProviderRejectionFact.make({
|
|
410
|
+
subject: file.authority.subject,
|
|
411
|
+
code: NonEmptyName.make(`pypi-http-${response.status}`),
|
|
412
|
+
detail: SafeReason.make(`PyPI upload returned HTTP ${response.status}; exact reobservation determines remote state.`)
|
|
413
|
+
})
|
|
414
|
+
})),
|
|
415
|
+
Effect.mapError((cause) => mutationFailure(file, cause))
|
|
416
|
+
)
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const claimMutation = (decision: MutationDecision) => publication.authentication.strategy !== "token"
|
|
420
|
+
? Effect.fail(new ReleaseSubjectError({
|
|
421
|
+
subject: file.authority.subject,
|
|
422
|
+
phase: "mutate",
|
|
423
|
+
commitment: "before-dispatch",
|
|
424
|
+
reason: SafeReason.make("PyPI trusted publishing is external-host-owned and cannot enter the stock terminal-claim or upload path.")
|
|
425
|
+
}))
|
|
426
|
+
: !validDecision(decision)
|
|
427
|
+
? Effect.fail(new ReleaseSubjectError({
|
|
428
|
+
subject: file.authority.subject,
|
|
429
|
+
phase: "mutate",
|
|
430
|
+
commitment: "before-dispatch",
|
|
431
|
+
reason: SafeReason.make("PyPI terminal claim requires the exact visible-project file-absence decision.")
|
|
432
|
+
}))
|
|
433
|
+
: claims.claim(PublicationClaimRequest.make({
|
|
434
|
+
subject: file.authority.subject,
|
|
435
|
+
preparedDigest
|
|
436
|
+
})).pipe(Effect.mapError(() => new ReleaseSubjectError({
|
|
437
|
+
subject: file.authority.subject,
|
|
438
|
+
phase: "mutate",
|
|
439
|
+
commitment: "before-dispatch",
|
|
440
|
+
reason: SafeReason.make("The shared terminal PyPI publication claim was unavailable or occupied.")
|
|
441
|
+
})))
|
|
442
|
+
|
|
443
|
+
return {
|
|
444
|
+
id: file.authority.subject,
|
|
445
|
+
recovery: pypiRecoveryCapabilityProfile,
|
|
446
|
+
...(prerequisites.length === 0 ? {} : { prerequisites: prerequisites.map((prior) => prior.authority.subject) }),
|
|
447
|
+
observationRequests: observationRequests(publication, file),
|
|
448
|
+
mutationRequest: mutationRequest(file),
|
|
449
|
+
claimMutation,
|
|
450
|
+
observe,
|
|
451
|
+
decide: (observation) => decide(file, observation),
|
|
452
|
+
mutate
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
export const makePyPiSubjects = (
|
|
457
|
+
bundle: PreparedBundle,
|
|
458
|
+
publication: PreparedPyPiPublication,
|
|
459
|
+
http: HttpAuthorizerShape,
|
|
460
|
+
mutationHttp: AuthorizedMutationHttpShape,
|
|
461
|
+
claims: PublicationClaimStoreShape = unavailablePublicationClaimStore
|
|
462
|
+
): ReadonlyArray<ReleaseSubject> => publication.files.map((file, index) =>
|
|
463
|
+
makePyPiSubject(bundle, publication, file, http, mutationHttp, publication.files.slice(0, index), claims))
|