Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 157 additions & 5 deletions .github/workflows/publish.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,6 +47,7 @@ jobs:
if: >-
vars.RELEASE_PLEASE_ENABLED == 'true' &&
github.event_name == 'workflow_run' &&
github.run_attempt == 1 &&
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'push' &&
github.event.workflow_run.head_branch == 'main'
Expand DownExpand Up@@ -243,7 +244,17 @@ jobs:
after_runs="$RUNNER_TEMP/tag-dispatch-runs-after.json"
gh api --paginate --slurp \
"repos/${GITHUB_REPOSITORY}/actions/workflows/publish.yml/runs?event=workflow_dispatch&per_page=100" \
| jq '[.[].workflow_runs[].id]' > "$before_runs"
| jq '[.[].workflow_runs[]]' > "$before_runs"
prior_count="$(jq \
--arg commit "$RELEASE_COMMIT" --arg tag "$RELEASE_TAG" \
'[.[] | select(.actor.login == "github-actions[bot]" and
.triggering_actor.login == "github-actions[bot]" and
.event == "workflow_dispatch" and .head_branch == $tag and
.head_sha == $commit)] | length' "$before_runs")"
if [[ "$prior_count" != "0" ]]; then
echo "An exact Publish run already exists for this immutable tag and commit." >&2
exit 1
fi
gh api --method POST \
"repos/${GITHUB_REPOSITORY}/actions/workflows/publish.yml/dispatches" \
-f ref="$RELEASE_TAG" \
Expand All@@ -262,7 +273,7 @@ jobs:
candidate_count="$(jq \
--arg commit "$RELEASE_COMMIT" --arg tag "$RELEASE_TAG" \
--slurpfile before "$before_runs" \
'[.[] | select(.id as $id | ($before[0] | index($id) | not)) |
'[.[] | select(.id as $id | ($before[0] | map(.id) | index($id) | not)) |
select(.actor.login == "github-actions[bot]" and
.triggering_actor.login == "github-actions[bot]" and
.event == "workflow_dispatch" and .head_branch == $tag and
Expand All@@ -275,7 +286,7 @@ jobs:
publish_run_id="$(jq -r \
--arg commit "$RELEASE_COMMIT" --arg tag "$RELEASE_TAG" \
--slurpfile before "$before_runs" \
'[.[] | select(.id as $id | ($before[0] | index($id) | not)) |
'[.[] | select(.id as $id | ($before[0] | map(.id) | index($id) | not)) |
select(.actor.login == "github-actions[bot]" and
.triggering_actor.login == "github-actions[bot]" and
.event == "workflow_dispatch" and .head_branch == $tag and
Expand All@@ -296,6 +307,7 @@ jobs:
if: >-
vars.RELEASE_PLEASE_ENABLED == 'true' &&
github.event_name == 'workflow_dispatch' &&
github.run_attempt == 1 &&
inputs.publish_operation == 'release' &&
startsWith(github.ref, 'refs/tags/v0.1.') &&
github.ref == format('refs/tags/{0}', inputs.release_tag) &&
Expand All@@ -312,11 +324,21 @@ jobs:
artifact-name: ${{ steps.artifact-name.outputs.name }}
control-commit: ${{ inputs.control_commit }}
dist-tag: ${{ steps.version.outputs.dist-tag }}
expected-next-version: ${{ steps.registry-baseline.outputs.next-version }}
release-commit: ${{ steps.trust.outputs.release-commit }}
release-tag: ${{ steps.trust.outputs.release-tag }}
release-please-snapshot: ${{ steps.release-please-snapshot.outputs.digest }}
version: ${{ steps.version.outputs.version }}
steps:
- name: Reject a repeated verification attempt
env:
WORKFLOW_RUN_ATTEMPT: ${{ github.run_attempt }}
run: |
set -euo pipefail
if [[ "$WORKFLOW_RUN_ATTEMPT" != "1" ]]; then
echo "Exact-artifact verification is restricted to the initial run attempt." >&2
exit 1
fi
- name: Check out the workflow control commit
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
Expand DownExpand Up@@ -576,6 +598,18 @@ jobs:
EOF
- name: Use a Trusted Publishing-capable npm CLI
run: npm install --global npm@11.12.1
- name: Freeze the prerelease dist-tag
id: registry-baseline
shell: bash
run: |
set -euo pipefail
next_version="$(npm view cometapi@next version)"
NEXT_VERSION="$next_version" node --input-type=module <<'EOF'
import { validatePrereleaseDistTagBaseline } from "./scripts/release-workflow-validation.mjs";

validatePrereleaseDistTagBaseline(process.env.NEXT_VERSION);
EOF
echo "next-version=${next_version}" >> "$GITHUB_OUTPUT"
- name: Install locked dependencies
run: npm ci
- name: Run release checks
Expand DownExpand Up@@ -630,6 +664,9 @@ jobs:

live-smoke:
name: Verify the release tag against CometAPI
if: >-
github.run_attempt == 1 &&
needs.verify.result == 'success'
needs:
- verify
concurrency:
Expand All@@ -641,6 +678,15 @@ jobs:
# without required reviewers and add COMETAPI_KEY before publishing a release.
environment: live-smoke
steps:
- name: Reject a repeated live-smoke attempt
env:
WORKFLOW_RUN_ATTEMPT: ${{ github.run_attempt }}
run: |
set -euo pipefail
if [[ "$WORKFLOW_RUN_ATTEMPT" != "1" ]]; then
echo "The bounded live smoke is restricted to the initial run attempt." >&2
exit 1
fi
- name: Check out the verified release tag
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
Expand DownExpand Up@@ -668,6 +714,11 @@ jobs:

publish:
name: Publish with npm Trusted Publishing
if: >-
always() &&
(github.run_attempt == 1 || github.run_attempt == 2) &&
needs.live-smoke.result == 'success' &&
needs.verify.result == 'success'
needs:
- live-smoke
- verify
Expand All@@ -684,6 +735,16 @@ jobs:
deployments: read
id-token: write
steps:
- name: Reject an out-of-bounds publication attempt
env:
WORKFLOW_RUN_ATTEMPT: ${{ github.run_attempt }}
run: |
set -euo pipefail
if [[ "$WORKFLOW_RUN_ATTEMPT" != "1" &&
"$WORKFLOW_RUN_ATTEMPT" != "2" ]]; then
echo "Publication permits only the initial attempt and one failed-job replay." >&2
exit 1
fi
- name: Check out the verified release commit
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
Expand All@@ -704,12 +765,14 @@ jobs:
name: ${{ needs.verify.outputs.artifact-name }}
path: release-artifacts
- name: Reconfirm protected state immediately before publication
id: pre-publish
env:
CONTROL_COMMIT: ${{ needs.verify.outputs.control-commit }}
CONTROL_VALIDATOR: ${{ runner.temp }}/release-workflow-validation.mjs
ENVIRONMENT_FILE: ${{ runner.temp }}/npm-environment.json
EVENT_REF: ${{ github.ref }}
EVENT_SHA: ${{ github.sha }}
EXPECTED_NEXT_VERSION: ${{ needs.verify.outputs.expected-next-version }}
GH_TOKEN: ${{ github.token }}
POLICIES_FILE: ${{ runner.temp }}/npm-deployment-policies.json
RELEASE_COMMIT: ${{ needs.verify.outputs.release-commit }}
Expand All@@ -722,6 +785,7 @@ jobs:
RELEASE_PLEASE_RUNS: ${{ runner.temp }}/release-please-runs-before-publish.json
RELEASE_PLEASE_SNAPSHOT: ${{ needs.verify.outputs.release-please-snapshot }}
VERSION: ${{ needs.verify.outputs.version }}
WORKFLOW_RUN_ATTEMPT: ${{ github.run_attempt }}
WORKFLOW_SHA: ${{ github.workflow_sha }}
shell: bash
run: |
Expand DownExpand Up@@ -793,6 +857,10 @@ jobs:
exit 1
fi
fi
if [[ "$WORKFLOW_RUN_ATTEMPT" == "2" && -z "$exact_version" ]]; then
echo "The failed-job replay requires the exact registry version to exist." >&2
exit 1
fi
latest_version="$(npm view cometapi@latest version)"
next_version="$(npm view cometapi@next version)"
gh api --paginate --slurp \
Expand DownExpand Up@@ -885,21 +953,29 @@ jobs:
});
validateRegistryStateBeforePublish({
exactVersion: process.env.EXACT_VERSION || null,
expectedNextVersion: process.env.EXPECTED_NEXT_VERSION,
latestVersion: process.env.LATEST_VERSION,
nextVersion: process.env.NEXT_VERSION,
version,
});
EOF
if [[ -n "$exact_version" ]]; then
echo "expect-existing=true" >> "$GITHUB_OUTPUT"
else
echo "expect-existing=false" >> "$GITHUB_OUTPUT"
fi
- name: Publish the exact artifact with provenance
env:
DIST_TAG: ${{ needs.verify.outputs.dist-tag }}
EXPECT_EXISTING: ${{ steps.pre-publish.outputs.expect-existing }}
VERSION: ${{ needs.verify.outputs.version }}
run: bash scripts/publish-artifact.sh
- name: Verify the public registry artifact
env:
CONTROL_COMMIT: ${{ needs.verify.outputs.control-commit }}
CONTROL_VALIDATOR: ${{ runner.temp }}/release-workflow-validation.mjs
DIST_TAG: ${{ needs.verify.outputs.dist-tag }}
EXPECTED_NEXT_VERSION: ${{ needs.verify.outputs.expected-next-version }}
GH_TOKEN: ${{ github.token }}
WORKFLOW_REF: ${{ github.ref }}
VERSION: ${{ needs.verify.outputs.version }}
Expand DownExpand Up@@ -942,14 +1018,57 @@ jobs:
echo "Registry state did not converge for cometapi@${VERSION}, ${DIST_TAG}, integrity, and provenance." >&2
exit 1
fi
if [[ "$(npm view cometapi@next version)" != "0.1.0-alpha.3" ]]; then
if [[ "$(npm view cometapi@next version)" != "$EXPECTED_NEXT_VERSION" ]]; then
echo "The next dist-tag changed during publication." >&2
exit 1
fi

attestations_url="$(REGISTRY_DIST="$registry_dist" node -e 'process.stdout.write(JSON.parse(process.env.REGISTRY_DIST).attestations.url)')"
attestations_file="$RUNNER_TEMP/npm-attestations.json"
curl --fail --silent --show-error "$attestations_url" > "$attestations_file"
ATTESTATIONS_URL="$attestations_url" \
node --input-type=module <<'EOF'
import { pathToFileURL } from "node:url";

const { validateRegistryAttestationUrl } = await import(
pathToFileURL(process.env.CONTROL_VALIDATOR)
);
validateRegistryAttestationUrl({
url: process.env.ATTESTATIONS_URL,
version: process.env.VERSION,
});
EOF
ATTESTATIONS_FILE="$attestations_file" \
ATTESTATIONS_URL="$attestations_url" \
bash scripts/fetch-attestations.sh

post_exact_version="$(npm view "cometapi@${VERSION}" version)"
post_tagged_version="$(npm view "cometapi@${DIST_TAG}" version)"
post_next_version="$(npm view cometapi@next version)"
post_registry_dist="$(npm view "cometapi@${VERSION}" dist --json)"
ATTESTATIONS_URL="$attestations_url" \
EXACT_VERSION="$post_exact_version" \
EXPECTED_INTEGRITY="$local_integrity" \
NEXT_VERSION="$post_next_version" \
REGISTRY_DIST="$post_registry_dist" \
TAGGED_VERSION="$post_tagged_version" \
node --input-type=module <<'EOF'
import { pathToFileURL } from "node:url";

const { validatePublishedRegistryState } = await import(
pathToFileURL(process.env.CONTROL_VALIDATOR)
);
validatePublishedRegistryState({
attestationUrl: process.env.ATTESTATIONS_URL,
dist: JSON.parse(process.env.REGISTRY_DIST),
exactVersion: process.env.EXACT_VERSION,
expectedNextVersion: process.env.EXPECTED_NEXT_VERSION,
expectedIntegrity: process.env.EXPECTED_INTEGRITY,
nextVersion: process.env.NEXT_VERSION,
taggedVersion: process.env.TAGGED_VERSION,
version: process.env.VERSION,
});
EOF
registry_dist="$post_registry_dist"
local_sha512="$(node -e 'const {createHash}=require("node:crypto");const {readFileSync}=require("node:fs");process.stdout.write(createHash("sha512").update(readFileSync(process.argv[1])).digest("hex"))' "${tarballs[0]}")"
provenance_identity="$(ATTESTATIONS_FILE="$attestations_file" \
LOCAL_SHA512="$local_sha512" node --input-type=module <<'EOF'
Expand DownExpand Up@@ -1176,3 +1295,36 @@ jobs:
process.exitCode = 1;
});
EOF

result:
name: Enforce the bounded publication result
if: >-
always() &&
github.event_name == 'workflow_dispatch' &&
inputs.publish_operation == 'release'
needs:
- live-smoke
- publish
- verify
runs-on: ubuntu-latest
permissions: {}
steps:
- name: Reject skipped or out-of-bounds publication
env:
LIVE_SMOKE_RESULT: ${{ needs.live-smoke.result }}
PUBLISH_RESULT: ${{ needs.publish.result }}
VERIFY_RESULT: ${{ needs.verify.result }}
WORKFLOW_RUN_ATTEMPT: ${{ github.run_attempt }}
run: |
set -euo pipefail
if [[ "$WORKFLOW_RUN_ATTEMPT" != "1" &&
"$WORKFLOW_RUN_ATTEMPT" != "2" ]]; then
echo "Publication permits only the initial attempt and one failed-job replay." >&2
exit 1
fi
if [[ "$VERIFY_RESULT" != "success" ||
"$LIVE_SMOKE_RESULT" != "success" ||
"$PUBLISH_RESULT" != "success" ]]; then
echo "The bounded publication job set did not complete successfully." >&2
exit 1
fi
27 changes: 25 additions & 2 deletions AGENTS.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -283,9 +283,32 @@ repository root.
- Prepare or refresh a release PR with a new first-attempt manual Release Please
dispatch. Do not rerun a preparation dispatch. Release Please same-run Release
reconciliation is allowed only under the exact conditions in `RELEASING.md`.
- The successful Release Please `workflow_run` handoff is attempt-1-only and
must refuse to dispatch when any exact Publish child already exists for the
immutable tag and commit. Never rerun a handoff to create a second child run.
- Never bypass a failed stable release with a manual or auxiliary tag, a
branch-context publish, a temporary `main` npm Environment policy, reused
artifact or live evidence, an arbitrary rerun, or a different patch version.
branch-context publish, a temporary `main` npm Environment policy, cross-run
artifact or live-evidence reuse, an arbitrary rerun, or a different patch
version.
- If `npm publish` succeeds but post-publication verification fails, first read
the exact public version, `latest`, `next`, integrity, and every readable
attestation, signature, and provenance field. When the exact version is
absent, `latest` must equal the previous patch. When the exact version equals
the candidate, `latest` may equal the previous patch or candidate. Final state
requires both exact and `latest` to equal the candidate, while `next` must
equal the prerelease value frozen by the initial verification job. A transient
early attestation-endpoint `404` is the known registry convergence condition;
all transport failures receive one wall-clock-bounded wait, and exhaustion is
terminal. Do not rerun to extend it. Only when attestations are readable and
independent signature and provenance checks have passed may exactly one
`rerun failed jobs` on the same immutable-tag run resume a different failed
post-publication gate. Confirm that GitHub preserves the successful
exact-artifact and live-smoke jobs; rerun-all must fail before live API access.
The attempt-2 publish job requires the exact version to exist, matching
integrity, and a fresh Environment approval; it must skip `npm publish`.
Attempt 3 or later, an exact-version metadata `E404`, a second replay request,
non-convergence, or any identity, integrity, provenance, dist-tag, or
configuration mismatch is a hard stop.
- After registry verification, immediately restore
`RELEASE_PLEASE_ENABLED=false`, keep `LIVE_SMOKE_ENABLED=true`, and require the
npm Environment deployment-policy set to contain only `tag:v*`.
Expand Down
21 changes: 20 additions & 1 deletion ARCHITECTURE.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -139,7 +139,9 @@ the exact Release-producing attempt, SHA, tag, version, URL, repository,
workflow identity, run ID, and attempt in a schema-v2 exact-run artifact. An
unprivileged `workflow_run` handoff validates only that attempt's artifact,
exact tag, immutable Release, and current `main`, then dispatches `publish.yml`
with `ref=v<version>`. Only that tag-bound `workflow_dispatch` can reach fresh
with `ref=v<version>`. The handoff is attempt-1-only and refuses to dispatch if
an exact child run already exists for the tag and commit. Only that tag-bound
`workflow_dispatch` can reach fresh
artifact verification, bounded live smoke, the npm Environment, or OIDC. A
first-attempt manual run is explicitly release-inert and must succeed only after
independently validating one canonical action-created patch PR; its event cannot
Expand All@@ -153,6 +155,23 @@ Release Please and publication remain separate trust domains. Release Please
does not receive npm OIDC permission; `id-token: write` remains limited to the
protected publish job. Repository variables gate both flows, and reruns remain
fail-closed on exact tag, artifact, dist-tag, integrity, and provenance state.
Registry package metadata and its attestation endpoint can converge at
different times. Post-publication verification therefore gives attestation HTTP
failures one strict wall-clock-bounded retry window before failing; an early
`404` is the known convergence case, while persistent URL, authentication,
authorization, server, and transport failures remain terminal. The initial
verification job freezes the prerelease dist-tag value, and every later registry
gate requires it to remain unchanged rather than encoding a current package
version in durable workflow guidance. If publication succeeded before a later
gate failed, only one attempt-2 failed-job replay of that same immutable-tag run
may continue, and only after attestations, signature, and provenance are already
valid. Verification and bounded live smoke are attempt-1-only, so rerun-all
fails before live API access; attempt 3 or later also fails. The protected-state
step requires the exact version to exist on replay; `publish-artifact.sh` then
skips registry mutation only after its integrity matches the verified tarball
and refuses a later `E404` instead of republishing. The failed-job replay
preserves the same run's successful artifact and bounded live-smoke jobs rather
than borrowing evidence from another run.
Manual preparation rejects attempt 2 or later; restart uses a new dispatch with
Release creation disabled. A `push` rerun is bounded to the same run ID, SHA,
candidate, and final-head review. It may retry while the tag and Release remain
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all \u003cpre\u003e\u003ccode\u003e blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks"); } } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); } })(); (function(){ try { var __m = "github.com"; var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 157 additions & 5 deletions .github/workflows/publish.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,6 +47,7 @@ jobs:
if: >-
vars.RELEASE_PLEASE_ENABLED == 'true' &&
github.event_name == 'workflow_run' &&
github.run_attempt == 1 &&
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'push' &&
github.event.workflow_run.head_branch == 'main'
Expand DownExpand Up@@ -243,7 +244,17 @@ jobs:
after_runs="$RUNNER_TEMP/tag-dispatch-runs-after.json"
gh api --paginate --slurp \
"repos/${GITHUB_REPOSITORY}/actions/workflows/publish.yml/runs?event=workflow_dispatch&per_page=100" \
| jq '[.[].workflow_runs[].id]' > "$before_runs"
| jq '[.[].workflow_runs[]]' > "$before_runs"
prior_count="$(jq \
--arg commit "$RELEASE_COMMIT" --arg tag "$RELEASE_TAG" \
'[.[] | select(.actor.login == "github-actions[bot]" and
.triggering_actor.login == "github-actions[bot]" and
.event == "workflow_dispatch" and .head_branch == $tag and
.head_sha == $commit)] | length' "$before_runs")"
if [[ "$prior_count" != "0" ]]; then
echo "An exact Publish run already exists for this immutable tag and commit." >&2
exit 1
fi
gh api --method POST \
"repos/${GITHUB_REPOSITORY}/actions/workflows/publish.yml/dispatches" \
-f ref="$RELEASE_TAG" \
Expand All@@ -262,7 +273,7 @@ jobs:
candidate_count="$(jq \
--arg commit "$RELEASE_COMMIT" --arg tag "$RELEASE_TAG" \
--slurpfile before "$before_runs" \
'[.[] | select(.id as $id | ($before[0] | index($id) | not)) |
'[.[] | select(.id as $id | ($before[0] | map(.id) | index($id) | not)) |
select(.actor.login == "github-actions[bot]" and
.triggering_actor.login == "github-actions[bot]" and
.event == "workflow_dispatch" and .head_branch == $tag and
Expand All@@ -275,7 +286,7 @@ jobs:
publish_run_id="$(jq -r \
--arg commit "$RELEASE_COMMIT" --arg tag "$RELEASE_TAG" \
--slurpfile before "$before_runs" \
'[.[] | select(.id as $id | ($before[0] | index($id) | not)) |
'[.[] | select(.id as $id | ($before[0] | map(.id) | index($id) | not)) |
select(.actor.login == "github-actions[bot]" and
.triggering_actor.login == "github-actions[bot]" and
.event == "workflow_dispatch" and .head_branch == $tag and
Expand All@@ -296,6 +307,7 @@ jobs:
if: >-
vars.RELEASE_PLEASE_ENABLED == 'true' &&
github.event_name == 'workflow_dispatch' &&
github.run_attempt == 1 &&
inputs.publish_operation == 'release' &&
startsWith(github.ref, 'refs/tags/v0.1.') &&
github.ref == format('refs/tags/{0}', inputs.release_tag) &&
Expand All@@ -312,11 +324,21 @@ jobs:
artifact-name: ${{ steps.artifact-name.outputs.name }}
control-commit: ${{ inputs.control_commit }}
dist-tag: ${{ steps.version.outputs.dist-tag }}
expected-next-version: ${{ steps.registry-baseline.outputs.next-version }}
release-commit: ${{ steps.trust.outputs.release-commit }}
release-tag: ${{ steps.trust.outputs.release-tag }}
release-please-snapshot: ${{ steps.release-please-snapshot.outputs.digest }}
version: ${{ steps.version.outputs.version }}
steps:
- name: Reject a repeated verification attempt
env:
WORKFLOW_RUN_ATTEMPT: ${{ github.run_attempt }}
run: |
set -euo pipefail
if [[ "$WORKFLOW_RUN_ATTEMPT" != "1" ]]; then
echo "Exact-artifact verification is restricted to the initial run attempt." >&2
exit 1
fi
- name: Check out the workflow control commit
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
Expand DownExpand Up@@ -576,6 +598,18 @@ jobs:
EOF
- name: Use a Trusted Publishing-capable npm CLI
run: npm install --global npm@11.12.1
- name: Freeze the prerelease dist-tag
id: registry-baseline
shell: bash
run: |
set -euo pipefail
next_version="$(npm view cometapi@next version)"
NEXT_VERSION="$next_version" node --input-type=module <<'EOF'
import { validatePrereleaseDistTagBaseline } from "./scripts/release-workflow-validation.mjs";

validatePrereleaseDistTagBaseline(process.env.NEXT_VERSION);
EOF
echo "next-version=${next_version}" >> "$GITHUB_OUTPUT"
- name: Install locked dependencies
run: npm ci
- name: Run release checks
Expand DownExpand Up@@ -630,6 +664,9 @@ jobs:

live-smoke:
name: Verify the release tag against CometAPI
if: >-
github.run_attempt == 1 &&
needs.verify.result == 'success'
needs:
- verify
concurrency:
Expand All@@ -641,6 +678,15 @@ jobs:
# without required reviewers and add COMETAPI_KEY before publishing a release.
environment: live-smoke
steps:
- name: Reject a repeated live-smoke attempt
env:
WORKFLOW_RUN_ATTEMPT: ${{ github.run_attempt }}
run: |
set -euo pipefail
if [[ "$WORKFLOW_RUN_ATTEMPT" != "1" ]]; then
echo "The bounded live smoke is restricted to the initial run attempt." >&2
exit 1
fi
- name: Check out the verified release tag
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
Expand DownExpand Up@@ -668,6 +714,11 @@ jobs:

publish:
name: Publish with npm Trusted Publishing
if: >-
always() &&
(github.run_attempt == 1 || github.run_attempt == 2) &&
needs.live-smoke.result == 'success' &&
needs.verify.result == 'success'
needs:
- live-smoke
- verify
Expand All@@ -684,6 +735,16 @@ jobs:
deployments: read
id-token: write
steps:
- name: Reject an out-of-bounds publication attempt
env:
WORKFLOW_RUN_ATTEMPT: ${{ github.run_attempt }}
run: |
set -euo pipefail
if [[ "$WORKFLOW_RUN_ATTEMPT" != "1" &&
"$WORKFLOW_RUN_ATTEMPT" != "2" ]]; then
echo "Publication permits only the initial attempt and one failed-job replay." >&2
exit 1
fi
- name: Check out the verified release commit
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
Expand All@@ -704,12 +765,14 @@ jobs:
name: ${{ needs.verify.outputs.artifact-name }}
path: release-artifacts
- name: Reconfirm protected state immediately before publication
id: pre-publish
env:
CONTROL_COMMIT: ${{ needs.verify.outputs.control-commit }}
CONTROL_VALIDATOR: ${{ runner.temp }}/release-workflow-validation.mjs
ENVIRONMENT_FILE: ${{ runner.temp }}/npm-environment.json
EVENT_REF: ${{ github.ref }}
EVENT_SHA: ${{ github.sha }}
EXPECTED_NEXT_VERSION: ${{ needs.verify.outputs.expected-next-version }}
GH_TOKEN: ${{ github.token }}
POLICIES_FILE: ${{ runner.temp }}/npm-deployment-policies.json
RELEASE_COMMIT: ${{ needs.verify.outputs.release-commit }}
Expand All@@ -722,6 +785,7 @@ jobs:
RELEASE_PLEASE_RUNS: ${{ runner.temp }}/release-please-runs-before-publish.json
RELEASE_PLEASE_SNAPSHOT: ${{ needs.verify.outputs.release-please-snapshot }}
VERSION: ${{ needs.verify.outputs.version }}
WORKFLOW_RUN_ATTEMPT: ${{ github.run_attempt }}
WORKFLOW_SHA: ${{ github.workflow_sha }}
shell: bash
run: |
Expand DownExpand Up@@ -793,6 +857,10 @@ jobs:
exit 1
fi
fi
if [[ "$WORKFLOW_RUN_ATTEMPT" == "2" && -z "$exact_version" ]]; then
echo "The failed-job replay requires the exact registry version to exist." >&2
exit 1
fi
latest_version="$(npm view cometapi@latest version)"
next_version="$(npm view cometapi@next version)"
gh api --paginate --slurp \
Expand DownExpand Up@@ -885,21 +953,29 @@ jobs:
});
validateRegistryStateBeforePublish({
exactVersion: process.env.EXACT_VERSION || null,
expectedNextVersion: process.env.EXPECTED_NEXT_VERSION,
latestVersion: process.env.LATEST_VERSION,
nextVersion: process.env.NEXT_VERSION,
version,
});
EOF
if [[ -n "$exact_version" ]]; then
echo "expect-existing=true" >> "$GITHUB_OUTPUT"
else
echo "expect-existing=false" >> "$GITHUB_OUTPUT"
fi
- name: Publish the exact artifact with provenance
env:
DIST_TAG: ${{ needs.verify.outputs.dist-tag }}
EXPECT_EXISTING: ${{ steps.pre-publish.outputs.expect-existing }}
VERSION: ${{ needs.verify.outputs.version }}
run: bash scripts/publish-artifact.sh
- name: Verify the public registry artifact
env:
CONTROL_COMMIT: ${{ needs.verify.outputs.control-commit }}
CONTROL_VALIDATOR: ${{ runner.temp }}/release-workflow-validation.mjs
DIST_TAG: ${{ needs.verify.outputs.dist-tag }}
EXPECTED_NEXT_VERSION: ${{ needs.verify.outputs.expected-next-version }}
GH_TOKEN: ${{ github.token }}
WORKFLOW_REF: ${{ github.ref }}
VERSION: ${{ needs.verify.outputs.version }}
Expand DownExpand Up@@ -942,14 +1018,57 @@ jobs:
echo "Registry state did not converge for cometapi@${VERSION}, ${DIST_TAG}, integrity, and provenance." >&2
exit 1
fi
if [[ "$(npm view cometapi@next version)" != "0.1.0-alpha.3" ]]; then
if [[ "$(npm view cometapi@next version)" != "$EXPECTED_NEXT_VERSION" ]]; then
echo "The next dist-tag changed during publication." >&2
exit 1
fi

attestations_url="$(REGISTRY_DIST="$registry_dist" node -e 'process.stdout.write(JSON.parse(process.env.REGISTRY_DIST).attestations.url)')"
attestations_file="$RUNNER_TEMP/npm-attestations.json"
curl --fail --silent --show-error "$attestations_url" > "$attestations_file"
ATTESTATIONS_URL="$attestations_url" \
node --input-type=module <<'EOF'
import { pathToFileURL } from "node:url";

const { validateRegistryAttestationUrl } = await import(
pathToFileURL(process.env.CONTROL_VALIDATOR)
);
validateRegistryAttestationUrl({
url: process.env.ATTESTATIONS_URL,
version: process.env.VERSION,
});
EOF
ATTESTATIONS_FILE="$attestations_file" \
ATTESTATIONS_URL="$attestations_url" \
bash scripts/fetch-attestations.sh

post_exact_version="$(npm view "cometapi@${VERSION}" version)"
post_tagged_version="$(npm view "cometapi@${DIST_TAG}" version)"
post_next_version="$(npm view cometapi@next version)"
post_registry_dist="$(npm view "cometapi@${VERSION}" dist --json)"
ATTESTATIONS_URL="$attestations_url" \
EXACT_VERSION="$post_exact_version" \
EXPECTED_INTEGRITY="$local_integrity" \
NEXT_VERSION="$post_next_version" \
REGISTRY_DIST="$post_registry_dist" \
TAGGED_VERSION="$post_tagged_version" \
node --input-type=module <<'EOF'
import { pathToFileURL } from "node:url";

const { validatePublishedRegistryState } = await import(
pathToFileURL(process.env.CONTROL_VALIDATOR)
);
validatePublishedRegistryState({
attestationUrl: process.env.ATTESTATIONS_URL,
dist: JSON.parse(process.env.REGISTRY_DIST),
exactVersion: process.env.EXACT_VERSION,
expectedNextVersion: process.env.EXPECTED_NEXT_VERSION,
expectedIntegrity: process.env.EXPECTED_INTEGRITY,
nextVersion: process.env.NEXT_VERSION,
taggedVersion: process.env.TAGGED_VERSION,
version: process.env.VERSION,
});
EOF
registry_dist="$post_registry_dist"
local_sha512="$(node -e 'const {createHash}=require("node:crypto");const {readFileSync}=require("node:fs");process.stdout.write(createHash("sha512").update(readFileSync(process.argv[1])).digest("hex"))' "${tarballs[0]}")"
provenance_identity="$(ATTESTATIONS_FILE="$attestations_file" \
LOCAL_SHA512="$local_sha512" node --input-type=module <<'EOF'
Expand DownExpand Up@@ -1176,3 +1295,36 @@ jobs:
process.exitCode = 1;
});
EOF

result:
name: Enforce the bounded publication result
if: >-
always() &&
github.event_name == 'workflow_dispatch' &&
inputs.publish_operation == 'release'
needs:
- live-smoke
- publish
- verify
runs-on: ubuntu-latest
permissions: {}
steps:
- name: Reject skipped or out-of-bounds publication
env:
LIVE_SMOKE_RESULT: ${{ needs.live-smoke.result }}
PUBLISH_RESULT: ${{ needs.publish.result }}
VERIFY_RESULT: ${{ needs.verify.result }}
WORKFLOW_RUN_ATTEMPT: ${{ github.run_attempt }}
run: |
set -euo pipefail
if [[ "$WORKFLOW_RUN_ATTEMPT" != "1" &&
"$WORKFLOW_RUN_ATTEMPT" != "2" ]]; then
echo "Publication permits only the initial attempt and one failed-job replay." >&2
exit 1
fi
if [[ "$VERIFY_RESULT" != "success" ||
"$LIVE_SMOKE_RESULT" != "success" ||
"$PUBLISH_RESULT" != "success" ]]; then
echo "The bounded publication job set did not complete successfully." >&2
exit 1
fi
27 changes: 25 additions & 2 deletions AGENTS.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -283,9 +283,32 @@ repository root.
- Prepare or refresh a release PR with a new first-attempt manual Release Please
dispatch. Do not rerun a preparation dispatch. Release Please same-run Release
reconciliation is allowed only under the exact conditions in `RELEASING.md`.
- The successful Release Please `workflow_run` handoff is attempt-1-only and
must refuse to dispatch when any exact Publish child already exists for the
immutable tag and commit. Never rerun a handoff to create a second child run.
- Never bypass a failed stable release with a manual or auxiliary tag, a
branch-context publish, a temporary `main` npm Environment policy, reused
artifact or live evidence, an arbitrary rerun, or a different patch version.
branch-context publish, a temporary `main` npm Environment policy, cross-run
artifact or live-evidence reuse, an arbitrary rerun, or a different patch
version.
- If `npm publish` succeeds but post-publication verification fails, first read
the exact public version, `latest`, `next`, integrity, and every readable
attestation, signature, and provenance field. When the exact version is
absent, `latest` must equal the previous patch. When the exact version equals
the candidate, `latest` may equal the previous patch or candidate. Final state
requires both exact and `latest` to equal the candidate, while `next` must
equal the prerelease value frozen by the initial verification job. A transient
early attestation-endpoint `404` is the known registry convergence condition;
all transport failures receive one wall-clock-bounded wait, and exhaustion is
terminal. Do not rerun to extend it. Only when attestations are readable and
independent signature and provenance checks have passed may exactly one
`rerun failed jobs` on the same immutable-tag run resume a different failed
post-publication gate. Confirm that GitHub preserves the successful
exact-artifact and live-smoke jobs; rerun-all must fail before live API access.
The attempt-2 publish job requires the exact version to exist, matching
integrity, and a fresh Environment approval; it must skip `npm publish`.
Attempt 3 or later, an exact-version metadata `E404`, a second replay request,
non-convergence, or any identity, integrity, provenance, dist-tag, or
configuration mismatch is a hard stop.
- After registry verification, immediately restore
`RELEASE_PLEASE_ENABLED=false`, keep `LIVE_SMOKE_ENABLED=true`, and require the
npm Environment deployment-policy set to contain only `tag:v*`.
Expand Down
21 changes: 20 additions & 1 deletion ARCHITECTURE.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -139,7 +139,9 @@ the exact Release-producing attempt, SHA, tag, version, URL, repository,
workflow identity, run ID, and attempt in a schema-v2 exact-run artifact. An
unprivileged `workflow_run` handoff validates only that attempt's artifact,
exact tag, immutable Release, and current `main`, then dispatches `publish.yml`
with `ref=v<version>`. Only that tag-bound `workflow_dispatch` can reach fresh
with `ref=v<version>`. The handoff is attempt-1-only and refuses to dispatch if
an exact child run already exists for the tag and commit. Only that tag-bound
`workflow_dispatch` can reach fresh
artifact verification, bounded live smoke, the npm Environment, or OIDC. A
first-attempt manual run is explicitly release-inert and must succeed only after
independently validating one canonical action-created patch PR; its event cannot
Expand All@@ -153,6 +155,23 @@ Release Please and publication remain separate trust domains. Release Please
does not receive npm OIDC permission; `id-token: write` remains limited to the
protected publish job. Repository variables gate both flows, and reruns remain
fail-closed on exact tag, artifact, dist-tag, integrity, and provenance state.
Registry package metadata and its attestation endpoint can converge at
different times. Post-publication verification therefore gives attestation HTTP
failures one strict wall-clock-bounded retry window before failing; an early
`404` is the known convergence case, while persistent URL, authentication,
authorization, server, and transport failures remain terminal. The initial
verification job freezes the prerelease dist-tag value, and every later registry
gate requires it to remain unchanged rather than encoding a current package
version in durable workflow guidance. If publication succeeded before a later
gate failed, only one attempt-2 failed-job replay of that same immutable-tag run
may continue, and only after attestations, signature, and provenance are already
valid. Verification and bounded live smoke are attempt-1-only, so rerun-all
fails before live API access; attempt 3 or later also fails. The protected-state
step requires the exact version to exist on replay; `publish-artifact.sh` then
skips registry mutation only after its integrity matches the verified tarball
and refuses a later `E404` instead of republishing. The failed-job replay
preserves the same run's successful artifact and bounded live-smoke jobs rather
than borrowing evidence from another run.
Manual preparation rejects attempt 2 or later; restart uses a new dispatch with
Release creation disabled. A `push` rerun is bounded to the same run ID, SHA,
candidate, and final-head review. It may retry while the tag and Release remain
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 157 additions & 5 deletions .github/workflows/publish.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,6 +47,7 @@ jobs:
if: >-
vars.RELEASE_PLEASE_ENABLED == 'true' &&
github.event_name == 'workflow_run' &&
github.run_attempt == 1 &&
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'push' &&
github.event.workflow_run.head_branch == 'main'
Expand DownExpand Up@@ -243,7 +244,17 @@ jobs:
after_runs="$RUNNER_TEMP/tag-dispatch-runs-after.json"
gh api --paginate --slurp \
"repos/${GITHUB_REPOSITORY}/actions/workflows/publish.yml/runs?event=workflow_dispatch&per_page=100" \
| jq '[.[].workflow_runs[].id]' > "$before_runs"
| jq '[.[].workflow_runs[]]' > "$before_runs"
prior_count="$(jq \
--arg commit "$RELEASE_COMMIT" --arg tag "$RELEASE_TAG" \
'[.[] | select(.actor.login == "github-actions[bot]" and
.triggering_actor.login == "github-actions[bot]" and
.event == "workflow_dispatch" and .head_branch == $tag and
.head_sha == $commit)] | length' "$before_runs")"
if [[ "$prior_count" != "0" ]]; then
echo "An exact Publish run already exists for this immutable tag and commit." >&2
exit 1
fi
gh api --method POST \
"repos/${GITHUB_REPOSITORY}/actions/workflows/publish.yml/dispatches" \
-f ref="$RELEASE_TAG" \
Expand All@@ -262,7 +273,7 @@ jobs:
candidate_count="$(jq \
--arg commit "$RELEASE_COMMIT" --arg tag "$RELEASE_TAG" \
--slurpfile before "$before_runs" \
'[.[] | select(.id as $id | ($before[0] | index($id) | not)) |
'[.[] | select(.id as $id | ($before[0] | map(.id) | index($id) | not)) |
select(.actor.login == "github-actions[bot]" and
.triggering_actor.login == "github-actions[bot]" and
.event == "workflow_dispatch" and .head_branch == $tag and
Expand All@@ -275,7 +286,7 @@ jobs:
publish_run_id="$(jq -r \
--arg commit "$RELEASE_COMMIT" --arg tag "$RELEASE_TAG" \
--slurpfile before "$before_runs" \
'[.[] | select(.id as $id | ($before[0] | index($id) | not)) |
'[.[] | select(.id as $id | ($before[0] | map(.id) | index($id) | not)) |
select(.actor.login == "github-actions[bot]" and
.triggering_actor.login == "github-actions[bot]" and
.event == "workflow_dispatch" and .head_branch == $tag and
Expand All@@ -296,6 +307,7 @@ jobs:
if: >-
vars.RELEASE_PLEASE_ENABLED == 'true' &&
github.event_name == 'workflow_dispatch' &&
github.run_attempt == 1 &&
inputs.publish_operation == 'release' &&
startsWith(github.ref, 'refs/tags/v0.1.') &&
github.ref == format('refs/tags/{0}', inputs.release_tag) &&
Expand All@@ -312,11 +324,21 @@ jobs:
artifact-name: ${{ steps.artifact-name.outputs.name }}
control-commit: ${{ inputs.control_commit }}
dist-tag: ${{ steps.version.outputs.dist-tag }}
expected-next-version: ${{ steps.registry-baseline.outputs.next-version }}
release-commit: ${{ steps.trust.outputs.release-commit }}
release-tag: ${{ steps.trust.outputs.release-tag }}
release-please-snapshot: ${{ steps.release-please-snapshot.outputs.digest }}
version: ${{ steps.version.outputs.version }}
steps:
- name: Reject a repeated verification attempt
env:
WORKFLOW_RUN_ATTEMPT: ${{ github.run_attempt }}
run: |
set -euo pipefail
if [[ "$WORKFLOW_RUN_ATTEMPT" != "1" ]]; then
echo "Exact-artifact verification is restricted to the initial run attempt." >&2
exit 1
fi
- name: Check out the workflow control commit
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
Expand DownExpand Up@@ -576,6 +598,18 @@ jobs:
EOF
- name: Use a Trusted Publishing-capable npm CLI
run: npm install --global npm@11.12.1
- name: Freeze the prerelease dist-tag
id: registry-baseline
shell: bash
run: |
set -euo pipefail
next_version="$(npm view cometapi@next version)"
NEXT_VERSION="$next_version" node --input-type=module <<'EOF'
import { validatePrereleaseDistTagBaseline } from "./scripts/release-workflow-validation.mjs";

validatePrereleaseDistTagBaseline(process.env.NEXT_VERSION);
EOF
echo "next-version=${next_version}" >> "$GITHUB_OUTPUT"
- name: Install locked dependencies
run: npm ci
- name: Run release checks
Expand DownExpand Up@@ -630,6 +664,9 @@ jobs:

live-smoke:
name: Verify the release tag against CometAPI
if: >-
github.run_attempt == 1 &&
needs.verify.result == 'success'
needs:
- verify
concurrency:
Expand All@@ -641,6 +678,15 @@ jobs:
# without required reviewers and add COMETAPI_KEY before publishing a release.
environment: live-smoke
steps:
- name: Reject a repeated live-smoke attempt
env:
WORKFLOW_RUN_ATTEMPT: ${{ github.run_attempt }}
run: |
set -euo pipefail
if [[ "$WORKFLOW_RUN_ATTEMPT" != "1" ]]; then
echo "The bounded live smoke is restricted to the initial run attempt." >&2
exit 1
fi
- name: Check out the verified release tag
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
Expand DownExpand Up@@ -668,6 +714,11 @@ jobs:

publish:
name: Publish with npm Trusted Publishing
if: >-
always() &&
(github.run_attempt == 1 || github.run_attempt == 2) &&
needs.live-smoke.result == 'success' &&
needs.verify.result == 'success'
needs:
- live-smoke
- verify
Expand All@@ -684,6 +735,16 @@ jobs:
deployments: read
id-token: write
steps:
- name: Reject an out-of-bounds publication attempt
env:
WORKFLOW_RUN_ATTEMPT: ${{ github.run_attempt }}
run: |
set -euo pipefail
if [[ "$WORKFLOW_RUN_ATTEMPT" != "1" &&
"$WORKFLOW_RUN_ATTEMPT" != "2" ]]; then
echo "Publication permits only the initial attempt and one failed-job replay." >&2
exit 1
fi
- name: Check out the verified release commit
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
Expand All@@ -704,12 +765,14 @@ jobs:
name: ${{ needs.verify.outputs.artifact-name }}
path: release-artifacts
- name: Reconfirm protected state immediately before publication
id: pre-publish
env:
CONTROL_COMMIT: ${{ needs.verify.outputs.control-commit }}
CONTROL_VALIDATOR: ${{ runner.temp }}/release-workflow-validation.mjs
ENVIRONMENT_FILE: ${{ runner.temp }}/npm-environment.json
EVENT_REF: ${{ github.ref }}
EVENT_SHA: ${{ github.sha }}
EXPECTED_NEXT_VERSION: ${{ needs.verify.outputs.expected-next-version }}
GH_TOKEN: ${{ github.token }}
POLICIES_FILE: ${{ runner.temp }}/npm-deployment-policies.json
RELEASE_COMMIT: ${{ needs.verify.outputs.release-commit }}
Expand All@@ -722,6 +785,7 @@ jobs:
RELEASE_PLEASE_RUNS: ${{ runner.temp }}/release-please-runs-before-publish.json
RELEASE_PLEASE_SNAPSHOT: ${{ needs.verify.outputs.release-please-snapshot }}
VERSION: ${{ needs.verify.outputs.version }}
WORKFLOW_RUN_ATTEMPT: ${{ github.run_attempt }}
WORKFLOW_SHA: ${{ github.workflow_sha }}
shell: bash
run: |
Expand DownExpand Up@@ -793,6 +857,10 @@ jobs:
exit 1
fi
fi
if [[ "$WORKFLOW_RUN_ATTEMPT" == "2" && -z "$exact_version" ]]; then
echo "The failed-job replay requires the exact registry version to exist." >&2
exit 1
fi
latest_version="$(npm view cometapi@latest version)"
next_version="$(npm view cometapi@next version)"
gh api --paginate --slurp \
Expand DownExpand Up@@ -885,21 +953,29 @@ jobs:
});
validateRegistryStateBeforePublish({
exactVersion: process.env.EXACT_VERSION || null,
expectedNextVersion: process.env.EXPECTED_NEXT_VERSION,
latestVersion: process.env.LATEST_VERSION,
nextVersion: process.env.NEXT_VERSION,
version,
});
EOF
if [[ -n "$exact_version" ]]; then
echo "expect-existing=true" >> "$GITHUB_OUTPUT"
else
echo "expect-existing=false" >> "$GITHUB_OUTPUT"
fi
- name: Publish the exact artifact with provenance
env:
DIST_TAG: ${{ needs.verify.outputs.dist-tag }}
EXPECT_EXISTING: ${{ steps.pre-publish.outputs.expect-existing }}
VERSION: ${{ needs.verify.outputs.version }}
run: bash scripts/publish-artifact.sh
- name: Verify the public registry artifact
env:
CONTROL_COMMIT: ${{ needs.verify.outputs.control-commit }}
CONTROL_VALIDATOR: ${{ runner.temp }}/release-workflow-validation.mjs
DIST_TAG: ${{ needs.verify.outputs.dist-tag }}
EXPECTED_NEXT_VERSION: ${{ needs.verify.outputs.expected-next-version }}
GH_TOKEN: ${{ github.token }}
WORKFLOW_REF: ${{ github.ref }}
VERSION: ${{ needs.verify.outputs.version }}
Expand DownExpand Up@@ -942,14 +1018,57 @@ jobs:
echo "Registry state did not converge for cometapi@${VERSION}, ${DIST_TAG}, integrity, and provenance." >&2
exit 1
fi
if [[ "$(npm view cometapi@next version)" != "0.1.0-alpha.3" ]]; then
if [[ "$(npm view cometapi@next version)" != "$EXPECTED_NEXT_VERSION" ]]; then
echo "The next dist-tag changed during publication." >&2
exit 1
fi

attestations_url="$(REGISTRY_DIST="$registry_dist" node -e 'process.stdout.write(JSON.parse(process.env.REGISTRY_DIST).attestations.url)')"
attestations_file="$RUNNER_TEMP/npm-attestations.json"
curl --fail --silent --show-error "$attestations_url" > "$attestations_file"
ATTESTATIONS_URL="$attestations_url" \
node --input-type=module <<'EOF'
import { pathToFileURL } from "node:url";

const { validateRegistryAttestationUrl } = await import(
pathToFileURL(process.env.CONTROL_VALIDATOR)
);
validateRegistryAttestationUrl({
url: process.env.ATTESTATIONS_URL,
version: process.env.VERSION,
});
EOF
ATTESTATIONS_FILE="$attestations_file" \
ATTESTATIONS_URL="$attestations_url" \
bash scripts/fetch-attestations.sh

post_exact_version="$(npm view "cometapi@${VERSION}" version)"
post_tagged_version="$(npm view "cometapi@${DIST_TAG}" version)"
post_next_version="$(npm view cometapi@next version)"
post_registry_dist="$(npm view "cometapi@${VERSION}" dist --json)"
ATTESTATIONS_URL="$attestations_url" \
EXACT_VERSION="$post_exact_version" \
EXPECTED_INTEGRITY="$local_integrity" \
NEXT_VERSION="$post_next_version" \
REGISTRY_DIST="$post_registry_dist" \
TAGGED_VERSION="$post_tagged_version" \
node --input-type=module <<'EOF'
import { pathToFileURL } from "node:url";

const { validatePublishedRegistryState } = await import(
pathToFileURL(process.env.CONTROL_VALIDATOR)
);
validatePublishedRegistryState({
attestationUrl: process.env.ATTESTATIONS_URL,
dist: JSON.parse(process.env.REGISTRY_DIST),
exactVersion: process.env.EXACT_VERSION,
expectedNextVersion: process.env.EXPECTED_NEXT_VERSION,
expectedIntegrity: process.env.EXPECTED_INTEGRITY,
nextVersion: process.env.NEXT_VERSION,
taggedVersion: process.env.TAGGED_VERSION,
version: process.env.VERSION,
});
EOF
registry_dist="$post_registry_dist"
local_sha512="$(node -e 'const {createHash}=require("node:crypto");const {readFileSync}=require("node:fs");process.stdout.write(createHash("sha512").update(readFileSync(process.argv[1])).digest("hex"))' "${tarballs[0]}")"
provenance_identity="$(ATTESTATIONS_FILE="$attestations_file" \
LOCAL_SHA512="$local_sha512" node --input-type=module <<'EOF'
Expand DownExpand Up@@ -1176,3 +1295,36 @@ jobs:
process.exitCode = 1;
});
EOF

result:
name: Enforce the bounded publication result
if: >-
always() &&
github.event_name == 'workflow_dispatch' &&
inputs.publish_operation == 'release'
needs:
- live-smoke
- publish
- verify
runs-on: ubuntu-latest
permissions: {}
steps:
- name: Reject skipped or out-of-bounds publication
env:
LIVE_SMOKE_RESULT: ${{ needs.live-smoke.result }}
PUBLISH_RESULT: ${{ needs.publish.result }}
VERIFY_RESULT: ${{ needs.verify.result }}
WORKFLOW_RUN_ATTEMPT: ${{ github.run_attempt }}
run: |
set -euo pipefail
if [[ "$WORKFLOW_RUN_ATTEMPT" != "1" &&
"$WORKFLOW_RUN_ATTEMPT" != "2" ]]; then
echo "Publication permits only the initial attempt and one failed-job replay." >&2
exit 1
fi
if [[ "$VERIFY_RESULT" != "success" ||
"$LIVE_SMOKE_RESULT" != "success" ||
"$PUBLISH_RESULT" != "success" ]]; then
echo "The bounded publication job set did not complete successfully." >&2
exit 1
fi
27 changes: 25 additions & 2 deletions AGENTS.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -283,9 +283,32 @@ repository root.
- Prepare or refresh a release PR with a new first-attempt manual Release Please
dispatch. Do not rerun a preparation dispatch. Release Please same-run Release
reconciliation is allowed only under the exact conditions in `RELEASING.md`.
- The successful Release Please `workflow_run` handoff is attempt-1-only and
must refuse to dispatch when any exact Publish child already exists for the
immutable tag and commit. Never rerun a handoff to create a second child run.
- Never bypass a failed stable release with a manual or auxiliary tag, a
branch-context publish, a temporary `main` npm Environment policy, reused
artifact or live evidence, an arbitrary rerun, or a different patch version.
branch-context publish, a temporary `main` npm Environment policy, cross-run
artifact or live-evidence reuse, an arbitrary rerun, or a different patch
version.
- If `npm publish` succeeds but post-publication verification fails, first read
the exact public version, `latest`, `next`, integrity, and every readable
attestation, signature, and provenance field. When the exact version is
absent, `latest` must equal the previous patch. When the exact version equals
the candidate, `latest` may equal the previous patch or candidate. Final state
requires both exact and `latest` to equal the candidate, while `next` must
equal the prerelease value frozen by the initial verification job. A transient
early attestation-endpoint `404` is the known registry convergence condition;
all transport failures receive one wall-clock-bounded wait, and exhaustion is
terminal. Do not rerun to extend it. Only when attestations are readable and
independent signature and provenance checks have passed may exactly one
`rerun failed jobs` on the same immutable-tag run resume a different failed
post-publication gate. Confirm that GitHub preserves the successful
exact-artifact and live-smoke jobs; rerun-all must fail before live API access.
The attempt-2 publish job requires the exact version to exist, matching
integrity, and a fresh Environment approval; it must skip `npm publish`.
Attempt 3 or later, an exact-version metadata `E404`, a second replay request,
non-convergence, or any identity, integrity, provenance, dist-tag, or
configuration mismatch is a hard stop.
- After registry verification, immediately restore
`RELEASE_PLEASE_ENABLED=false`, keep `LIVE_SMOKE_ENABLED=true`, and require the
npm Environment deployment-policy set to contain only `tag:v*`.
Expand Down
21 changes: 20 additions & 1 deletion ARCHITECTURE.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -139,7 +139,9 @@ the exact Release-producing attempt, SHA, tag, version, URL, repository,
workflow identity, run ID, and attempt in a schema-v2 exact-run artifact. An
unprivileged `workflow_run` handoff validates only that attempt's artifact,
exact tag, immutable Release, and current `main`, then dispatches `publish.yml`
with `ref=v<version>`. Only that tag-bound `workflow_dispatch` can reach fresh
with `ref=v<version>`. The handoff is attempt-1-only and refuses to dispatch if
an exact child run already exists for the tag and commit. Only that tag-bound
`workflow_dispatch` can reach fresh
artifact verification, bounded live smoke, the npm Environment, or OIDC. A
first-attempt manual run is explicitly release-inert and must succeed only after
independently validating one canonical action-created patch PR; its event cannot
Expand All@@ -153,6 +155,23 @@ Release Please and publication remain separate trust domains. Release Please
does not receive npm OIDC permission; `id-token: write` remains limited to the
protected publish job. Repository variables gate both flows, and reruns remain
fail-closed on exact tag, artifact, dist-tag, integrity, and provenance state.
Registry package metadata and its attestation endpoint can converge at
different times. Post-publication verification therefore gives attestation HTTP
failures one strict wall-clock-bounded retry window before failing; an early
`404` is the known convergence case, while persistent URL, authentication,
authorization, server, and transport failures remain terminal. The initial
verification job freezes the prerelease dist-tag value, and every later registry
gate requires it to remain unchanged rather than encoding a current package
version in durable workflow guidance. If publication succeeded before a later
gate failed, only one attempt-2 failed-job replay of that same immutable-tag run
may continue, and only after attestations, signature, and provenance are already
valid. Verification and bounded live smoke are attempt-1-only, so rerun-all
fails before live API access; attempt 3 or later also fails. The protected-state
step requires the exact version to exist on replay; `publish-artifact.sh` then
skips registry mutation only after its integrity matches the verified tarball
and refuses a later `E404` instead of republishing. The failed-job replay
preserves the same run's successful artifact and bounded live-smoke jobs rather
than borrowing evidence from another run.
Manual preparation rejects attempt 2 or later; restart uses a new dispatch with
Release creation disabled. A `push` rerun is bounded to the same run ID, SHA,
candidate, and final-head review. It may retry while the tag and Release remain
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length \u003e 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 157 additions & 5 deletions .github/workflows/publish.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,6 +47,7 @@ jobs:
if: >-
vars.RELEASE_PLEASE_ENABLED == 'true' &&
github.event_name == 'workflow_run' &&
github.run_attempt == 1 &&
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'push' &&
github.event.workflow_run.head_branch == 'main'
Expand DownExpand Up@@ -243,7 +244,17 @@ jobs:
after_runs="$RUNNER_TEMP/tag-dispatch-runs-after.json"
gh api --paginate --slurp \
"repos/${GITHUB_REPOSITORY}/actions/workflows/publish.yml/runs?event=workflow_dispatch&per_page=100" \
| jq '[.[].workflow_runs[].id]' > "$before_runs"
| jq '[.[].workflow_runs[]]' > "$before_runs"
prior_count="$(jq \
--arg commit "$RELEASE_COMMIT" --arg tag "$RELEASE_TAG" \
'[.[] | select(.actor.login == "github-actions[bot]" and
.triggering_actor.login == "github-actions[bot]" and
.event == "workflow_dispatch" and .head_branch == $tag and
.head_sha == $commit)] | length' "$before_runs")"
if [[ "$prior_count" != "0" ]]; then
echo "An exact Publish run already exists for this immutable tag and commit." >&2
exit 1
fi
gh api --method POST \
"repos/${GITHUB_REPOSITORY}/actions/workflows/publish.yml/dispatches" \
-f ref="$RELEASE_TAG" \
Expand All@@ -262,7 +273,7 @@ jobs:
candidate_count="$(jq \
--arg commit "$RELEASE_COMMIT" --arg tag "$RELEASE_TAG" \
--slurpfile before "$before_runs" \
'[.[] | select(.id as $id | ($before[0] | index($id) | not)) |
'[.[] | select(.id as $id | ($before[0] | map(.id) | index($id) | not)) |
select(.actor.login == "github-actions[bot]" and
.triggering_actor.login == "github-actions[bot]" and
.event == "workflow_dispatch" and .head_branch == $tag and
Expand All@@ -275,7 +286,7 @@ jobs:
publish_run_id="$(jq -r \
--arg commit "$RELEASE_COMMIT" --arg tag "$RELEASE_TAG" \
--slurpfile before "$before_runs" \
'[.[] | select(.id as $id | ($before[0] | index($id) | not)) |
'[.[] | select(.id as $id | ($before[0] | map(.id) | index($id) | not)) |
select(.actor.login == "github-actions[bot]" and
.triggering_actor.login == "github-actions[bot]" and
.event == "workflow_dispatch" and .head_branch == $tag and
Expand All@@ -296,6 +307,7 @@ jobs:
if: >-
vars.RELEASE_PLEASE_ENABLED == 'true' &&
github.event_name == 'workflow_dispatch' &&
github.run_attempt == 1 &&
inputs.publish_operation == 'release' &&
startsWith(github.ref, 'refs/tags/v0.1.') &&
github.ref == format('refs/tags/{0}', inputs.release_tag) &&
Expand All@@ -312,11 +324,21 @@ jobs:
artifact-name: ${{ steps.artifact-name.outputs.name }}
control-commit: ${{ inputs.control_commit }}
dist-tag: ${{ steps.version.outputs.dist-tag }}
expected-next-version: ${{ steps.registry-baseline.outputs.next-version }}
release-commit: ${{ steps.trust.outputs.release-commit }}
release-tag: ${{ steps.trust.outputs.release-tag }}
release-please-snapshot: ${{ steps.release-please-snapshot.outputs.digest }}
version: ${{ steps.version.outputs.version }}
steps:
- name: Reject a repeated verification attempt
env:
WORKFLOW_RUN_ATTEMPT: ${{ github.run_attempt }}
run: |
set -euo pipefail
if [[ "$WORKFLOW_RUN_ATTEMPT" != "1" ]]; then
echo "Exact-artifact verification is restricted to the initial run attempt." >&2
exit 1
fi
- name: Check out the workflow control commit
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
Expand DownExpand Up@@ -576,6 +598,18 @@ jobs:
EOF
- name: Use a Trusted Publishing-capable npm CLI
run: npm install --global npm@11.12.1
- name: Freeze the prerelease dist-tag
id: registry-baseline
shell: bash
run: |
set -euo pipefail
next_version="$(npm view cometapi@next version)"
NEXT_VERSION="$next_version" node --input-type=module <<'EOF'
import { validatePrereleaseDistTagBaseline } from "./scripts/release-workflow-validation.mjs";

validatePrereleaseDistTagBaseline(process.env.NEXT_VERSION);
EOF
echo "next-version=${next_version}" >> "$GITHUB_OUTPUT"
- name: Install locked dependencies
run: npm ci
- name: Run release checks
Expand DownExpand Up@@ -630,6 +664,9 @@ jobs:

live-smoke:
name: Verify the release tag against CometAPI
if: >-
github.run_attempt == 1 &&
needs.verify.result == 'success'
needs:
- verify
concurrency:
Expand All@@ -641,6 +678,15 @@ jobs:
# without required reviewers and add COMETAPI_KEY before publishing a release.
environment: live-smoke
steps:
- name: Reject a repeated live-smoke attempt
env:
WORKFLOW_RUN_ATTEMPT: ${{ github.run_attempt }}
run: |
set -euo pipefail
if [[ "$WORKFLOW_RUN_ATTEMPT" != "1" ]]; then
echo "The bounded live smoke is restricted to the initial run attempt." >&2
exit 1
fi
- name: Check out the verified release tag
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
Expand DownExpand Up@@ -668,6 +714,11 @@ jobs:

publish:
name: Publish with npm Trusted Publishing
if: >-
always() &&
(github.run_attempt == 1 || github.run_attempt == 2) &&
needs.live-smoke.result == 'success' &&
needs.verify.result == 'success'
needs:
- live-smoke
- verify
Expand All@@ -684,6 +735,16 @@ jobs:
deployments: read
id-token: write
steps:
- name: Reject an out-of-bounds publication attempt
env:
WORKFLOW_RUN_ATTEMPT: ${{ github.run_attempt }}
run: |
set -euo pipefail
if [[ "$WORKFLOW_RUN_ATTEMPT" != "1" &&
"$WORKFLOW_RUN_ATTEMPT" != "2" ]]; then
echo "Publication permits only the initial attempt and one failed-job replay." >&2
exit 1
fi
- name: Check out the verified release commit
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
Expand All@@ -704,12 +765,14 @@ jobs:
name: ${{ needs.verify.outputs.artifact-name }}
path: release-artifacts
- name: Reconfirm protected state immediately before publication
id: pre-publish
env:
CONTROL_COMMIT: ${{ needs.verify.outputs.control-commit }}
CONTROL_VALIDATOR: ${{ runner.temp }}/release-workflow-validation.mjs
ENVIRONMENT_FILE: ${{ runner.temp }}/npm-environment.json
EVENT_REF: ${{ github.ref }}
EVENT_SHA: ${{ github.sha }}
EXPECTED_NEXT_VERSION: ${{ needs.verify.outputs.expected-next-version }}
GH_TOKEN: ${{ github.token }}
POLICIES_FILE: ${{ runner.temp }}/npm-deployment-policies.json
RELEASE_COMMIT: ${{ needs.verify.outputs.release-commit }}
Expand All@@ -722,6 +785,7 @@ jobs:
RELEASE_PLEASE_RUNS: ${{ runner.temp }}/release-please-runs-before-publish.json
RELEASE_PLEASE_SNAPSHOT: ${{ needs.verify.outputs.release-please-snapshot }}
VERSION: ${{ needs.verify.outputs.version }}
WORKFLOW_RUN_ATTEMPT: ${{ github.run_attempt }}
WORKFLOW_SHA: ${{ github.workflow_sha }}
shell: bash
run: |
Expand DownExpand Up@@ -793,6 +857,10 @@ jobs:
exit 1
fi
fi
if [[ "$WORKFLOW_RUN_ATTEMPT" == "2" && -z "$exact_version" ]]; then
echo "The failed-job replay requires the exact registry version to exist." >&2
exit 1
fi
latest_version="$(npm view cometapi@latest version)"
next_version="$(npm view cometapi@next version)"
gh api --paginate --slurp \
Expand DownExpand Up@@ -885,21 +953,29 @@ jobs:
});
validateRegistryStateBeforePublish({
exactVersion: process.env.EXACT_VERSION || null,
expectedNextVersion: process.env.EXPECTED_NEXT_VERSION,
latestVersion: process.env.LATEST_VERSION,
nextVersion: process.env.NEXT_VERSION,
version,
});
EOF
if [[ -n "$exact_version" ]]; then
echo "expect-existing=true" >> "$GITHUB_OUTPUT"
else
echo "expect-existing=false" >> "$GITHUB_OUTPUT"
fi
- name: Publish the exact artifact with provenance
env:
DIST_TAG: ${{ needs.verify.outputs.dist-tag }}
EXPECT_EXISTING: ${{ steps.pre-publish.outputs.expect-existing }}
VERSION: ${{ needs.verify.outputs.version }}
run: bash scripts/publish-artifact.sh
- name: Verify the public registry artifact
env:
CONTROL_COMMIT: ${{ needs.verify.outputs.control-commit }}
CONTROL_VALIDATOR: ${{ runner.temp }}/release-workflow-validation.mjs
DIST_TAG: ${{ needs.verify.outputs.dist-tag }}
EXPECTED_NEXT_VERSION: ${{ needs.verify.outputs.expected-next-version }}
GH_TOKEN: ${{ github.token }}
WORKFLOW_REF: ${{ github.ref }}
VERSION: ${{ needs.verify.outputs.version }}
Expand DownExpand Up@@ -942,14 +1018,57 @@ jobs:
echo "Registry state did not converge for cometapi@${VERSION}, ${DIST_TAG}, integrity, and provenance." >&2
exit 1
fi
if [[ "$(npm view cometapi@next version)" != "0.1.0-alpha.3" ]]; then
if [[ "$(npm view cometapi@next version)" != "$EXPECTED_NEXT_VERSION" ]]; then
echo "The next dist-tag changed during publication." >&2
exit 1
fi

attestations_url="$(REGISTRY_DIST="$registry_dist" node -e 'process.stdout.write(JSON.parse(process.env.REGISTRY_DIST).attestations.url)')"
attestations_file="$RUNNER_TEMP/npm-attestations.json"
curl --fail --silent --show-error "$attestations_url" > "$attestations_file"
ATTESTATIONS_URL="$attestations_url" \
node --input-type=module <<'EOF'
import { pathToFileURL } from "node:url";

const { validateRegistryAttestationUrl } = await import(
pathToFileURL(process.env.CONTROL_VALIDATOR)
);
validateRegistryAttestationUrl({
url: process.env.ATTESTATIONS_URL,
version: process.env.VERSION,
});
EOF
ATTESTATIONS_FILE="$attestations_file" \
ATTESTATIONS_URL="$attestations_url" \
bash scripts/fetch-attestations.sh

post_exact_version="$(npm view "cometapi@${VERSION}" version)"
post_tagged_version="$(npm view "cometapi@${DIST_TAG}" version)"
post_next_version="$(npm view cometapi@next version)"
post_registry_dist="$(npm view "cometapi@${VERSION}" dist --json)"
ATTESTATIONS_URL="$attestations_url" \
EXACT_VERSION="$post_exact_version" \
EXPECTED_INTEGRITY="$local_integrity" \
NEXT_VERSION="$post_next_version" \
REGISTRY_DIST="$post_registry_dist" \
TAGGED_VERSION="$post_tagged_version" \
node --input-type=module <<'EOF'
import { pathToFileURL } from "node:url";

const { validatePublishedRegistryState } = await import(
pathToFileURL(process.env.CONTROL_VALIDATOR)
);
validatePublishedRegistryState({
attestationUrl: process.env.ATTESTATIONS_URL,
dist: JSON.parse(process.env.REGISTRY_DIST),
exactVersion: process.env.EXACT_VERSION,
expectedNextVersion: process.env.EXPECTED_NEXT_VERSION,
expectedIntegrity: process.env.EXPECTED_INTEGRITY,
nextVersion: process.env.NEXT_VERSION,
taggedVersion: process.env.TAGGED_VERSION,
version: process.env.VERSION,
});
EOF
registry_dist="$post_registry_dist"
local_sha512="$(node -e 'const {createHash}=require("node:crypto");const {readFileSync}=require("node:fs");process.stdout.write(createHash("sha512").update(readFileSync(process.argv[1])).digest("hex"))' "${tarballs[0]}")"
provenance_identity="$(ATTESTATIONS_FILE="$attestations_file" \
LOCAL_SHA512="$local_sha512" node --input-type=module <<'EOF'
Expand DownExpand Up@@ -1176,3 +1295,36 @@ jobs:
process.exitCode = 1;
});
EOF

result:
name: Enforce the bounded publication result
if: >-
always() &&
github.event_name == 'workflow_dispatch' &&
inputs.publish_operation == 'release'
needs:
- live-smoke
- publish
- verify
runs-on: ubuntu-latest
permissions: {}
steps:
- name: Reject skipped or out-of-bounds publication
env:
LIVE_SMOKE_RESULT: ${{ needs.live-smoke.result }}
PUBLISH_RESULT: ${{ needs.publish.result }}
VERIFY_RESULT: ${{ needs.verify.result }}
WORKFLOW_RUN_ATTEMPT: ${{ github.run_attempt }}
run: |
set -euo pipefail
if [[ "$WORKFLOW_RUN_ATTEMPT" != "1" &&
"$WORKFLOW_RUN_ATTEMPT" != "2" ]]; then
echo "Publication permits only the initial attempt and one failed-job replay." >&2
exit 1
fi
if [[ "$VERIFY_RESULT" != "success" ||
"$LIVE_SMOKE_RESULT" != "success" ||
"$PUBLISH_RESULT" != "success" ]]; then
echo "The bounded publication job set did not complete successfully." >&2
exit 1
fi
27 changes: 25 additions & 2 deletions AGENTS.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -283,9 +283,32 @@ repository root.
- Prepare or refresh a release PR with a new first-attempt manual Release Please
dispatch. Do not rerun a preparation dispatch. Release Please same-run Release
reconciliation is allowed only under the exact conditions in `RELEASING.md`.
- The successful Release Please `workflow_run` handoff is attempt-1-only and
must refuse to dispatch when any exact Publish child already exists for the
immutable tag and commit. Never rerun a handoff to create a second child run.
- Never bypass a failed stable release with a manual or auxiliary tag, a
branch-context publish, a temporary `main` npm Environment policy, reused
artifact or live evidence, an arbitrary rerun, or a different patch version.
branch-context publish, a temporary `main` npm Environment policy, cross-run
artifact or live-evidence reuse, an arbitrary rerun, or a different patch
version.
- If `npm publish` succeeds but post-publication verification fails, first read
the exact public version, `latest`, `next`, integrity, and every readable
attestation, signature, and provenance field. When the exact version is
absent, `latest` must equal the previous patch. When the exact version equals
the candidate, `latest` may equal the previous patch or candidate. Final state
requires both exact and `latest` to equal the candidate, while `next` must
equal the prerelease value frozen by the initial verification job. A transient
early attestation-endpoint `404` is the known registry convergence condition;
all transport failures receive one wall-clock-bounded wait, and exhaustion is
terminal. Do not rerun to extend it. Only when attestations are readable and
independent signature and provenance checks have passed may exactly one
`rerun failed jobs` on the same immutable-tag run resume a different failed
post-publication gate. Confirm that GitHub preserves the successful
exact-artifact and live-smoke jobs; rerun-all must fail before live API access.
The attempt-2 publish job requires the exact version to exist, matching
integrity, and a fresh Environment approval; it must skip `npm publish`.
Attempt 3 or later, an exact-version metadata `E404`, a second replay request,
non-convergence, or any identity, integrity, provenance, dist-tag, or
configuration mismatch is a hard stop.
- After registry verification, immediately restore
`RELEASE_PLEASE_ENABLED=false`, keep `LIVE_SMOKE_ENABLED=true`, and require the
npm Environment deployment-policy set to contain only `tag:v*`.
Expand Down
21 changes: 20 additions & 1 deletion ARCHITECTURE.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -139,7 +139,9 @@ the exact Release-producing attempt, SHA, tag, version, URL, repository,
workflow identity, run ID, and attempt in a schema-v2 exact-run artifact. An
unprivileged `workflow_run` handoff validates only that attempt's artifact,
exact tag, immutable Release, and current `main`, then dispatches `publish.yml`
with `ref=v<version>`. Only that tag-bound `workflow_dispatch` can reach fresh
with `ref=v<version>`. The handoff is attempt-1-only and refuses to dispatch if
an exact child run already exists for the tag and commit. Only that tag-bound
`workflow_dispatch` can reach fresh
artifact verification, bounded live smoke, the npm Environment, or OIDC. A
first-attempt manual run is explicitly release-inert and must succeed only after
independently validating one canonical action-created patch PR; its event cannot
Expand All@@ -153,6 +155,23 @@ Release Please and publication remain separate trust domains. Release Please
does not receive npm OIDC permission; `id-token: write` remains limited to the
protected publish job. Repository variables gate both flows, and reruns remain
fail-closed on exact tag, artifact, dist-tag, integrity, and provenance state.
Registry package metadata and its attestation endpoint can converge at
different times. Post-publication verification therefore gives attestation HTTP
failures one strict wall-clock-bounded retry window before failing; an early
`404` is the known convergence case, while persistent URL, authentication,
authorization, server, and transport failures remain terminal. The initial
verification job freezes the prerelease dist-tag value, and every later registry
gate requires it to remain unchanged rather than encoding a current package
version in durable workflow guidance. If publication succeeded before a later
gate failed, only one attempt-2 failed-job replay of that same immutable-tag run
may continue, and only after attestations, signature, and provenance are already
valid. Verification and bounded live smoke are attempt-1-only, so rerun-all
fails before live API access; attempt 3 or later also fails. The protected-state
step requires the exact version to exist on replay; `publish-artifact.sh` then
skips registry mutation only after its integrity matches the verified tarball
and refuses a later `E404` instead of republishing. The failed-job replay
preserves the same run's successful artifact and bounded live-smoke jobs rather
than borrowing evidence from another run.
Manual preparation rejects attempt 2 or later; restart uses a new dispatch with
Release creation disabled. A `push` rerun is bounded to the same run ID, SHA,
candidate, and final-head review. It may retry while the tag and Release remain
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 157 additions & 5 deletions .github/workflows/publish.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,6 +47,7 @@ jobs:
if: >-
vars.RELEASE_PLEASE_ENABLED == 'true' &&
github.event_name == 'workflow_run' &&
github.run_attempt == 1 &&
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'push' &&
github.event.workflow_run.head_branch == 'main'
Expand DownExpand Up@@ -243,7 +244,17 @@ jobs:
after_runs="$RUNNER_TEMP/tag-dispatch-runs-after.json"
gh api --paginate --slurp \
"repos/${GITHUB_REPOSITORY}/actions/workflows/publish.yml/runs?event=workflow_dispatch&per_page=100" \
| jq '[.[].workflow_runs[].id]' > "$before_runs"
| jq '[.[].workflow_runs[]]' > "$before_runs"
prior_count="$(jq \
--arg commit "$RELEASE_COMMIT" --arg tag "$RELEASE_TAG" \
'[.[] | select(.actor.login == "github-actions[bot]" and
.triggering_actor.login == "github-actions[bot]" and
.event == "workflow_dispatch" and .head_branch == $tag and
.head_sha == $commit)] | length' "$before_runs")"
if [[ "$prior_count" != "0" ]]; then
echo "An exact Publish run already exists for this immutable tag and commit." >&2
exit 1
fi
gh api --method POST \
"repos/${GITHUB_REPOSITORY}/actions/workflows/publish.yml/dispatches" \
-f ref="$RELEASE_TAG" \
Expand All@@ -262,7 +273,7 @@ jobs:
candidate_count="$(jq \
--arg commit "$RELEASE_COMMIT" --arg tag "$RELEASE_TAG" \
--slurpfile before "$before_runs" \
'[.[] | select(.id as $id | ($before[0] | index($id) | not)) |
'[.[] | select(.id as $id | ($before[0] | map(.id) | index($id) | not)) |
select(.actor.login == "github-actions[bot]" and
.triggering_actor.login == "github-actions[bot]" and
.event == "workflow_dispatch" and .head_branch == $tag and
Expand All@@ -275,7 +286,7 @@ jobs:
publish_run_id="$(jq -r \
--arg commit "$RELEASE_COMMIT" --arg tag "$RELEASE_TAG" \
--slurpfile before "$before_runs" \
'[.[] | select(.id as $id | ($before[0] | index($id) | not)) |
'[.[] | select(.id as $id | ($before[0] | map(.id) | index($id) | not)) |
select(.actor.login == "github-actions[bot]" and
.triggering_actor.login == "github-actions[bot]" and
.event == "workflow_dispatch" and .head_branch == $tag and
Expand All@@ -296,6 +307,7 @@ jobs:
if: >-
vars.RELEASE_PLEASE_ENABLED == 'true' &&
github.event_name == 'workflow_dispatch' &&
github.run_attempt == 1 &&
inputs.publish_operation == 'release' &&
startsWith(github.ref, 'refs/tags/v0.1.') &&
github.ref == format('refs/tags/{0}', inputs.release_tag) &&
Expand All@@ -312,11 +324,21 @@ jobs:
artifact-name: ${{ steps.artifact-name.outputs.name }}
control-commit: ${{ inputs.control_commit }}
dist-tag: ${{ steps.version.outputs.dist-tag }}
expected-next-version: ${{ steps.registry-baseline.outputs.next-version }}
release-commit: ${{ steps.trust.outputs.release-commit }}
release-tag: ${{ steps.trust.outputs.release-tag }}
release-please-snapshot: ${{ steps.release-please-snapshot.outputs.digest }}
version: ${{ steps.version.outputs.version }}
steps:
- name: Reject a repeated verification attempt
env:
WORKFLOW_RUN_ATTEMPT: ${{ github.run_attempt }}
run: |
set -euo pipefail
if [[ "$WORKFLOW_RUN_ATTEMPT" != "1" ]]; then
echo "Exact-artifact verification is restricted to the initial run attempt." >&2
exit 1
fi
- name: Check out the workflow control commit
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
Expand DownExpand Up@@ -576,6 +598,18 @@ jobs:
EOF
- name: Use a Trusted Publishing-capable npm CLI
run: npm install --global npm@11.12.1
- name: Freeze the prerelease dist-tag
id: registry-baseline
shell: bash
run: |
set -euo pipefail
next_version="$(npm view cometapi@next version)"
NEXT_VERSION="$next_version" node --input-type=module <<'EOF'
import { validatePrereleaseDistTagBaseline } from "./scripts/release-workflow-validation.mjs";

validatePrereleaseDistTagBaseline(process.env.NEXT_VERSION);
EOF
echo "next-version=${next_version}" >> "$GITHUB_OUTPUT"
- name: Install locked dependencies
run: npm ci
- name: Run release checks
Expand DownExpand Up@@ -630,6 +664,9 @@ jobs:

live-smoke:
name: Verify the release tag against CometAPI
if: >-
github.run_attempt == 1 &&
needs.verify.result == 'success'
needs:
- verify
concurrency:
Expand All@@ -641,6 +678,15 @@ jobs:
# without required reviewers and add COMETAPI_KEY before publishing a release.
environment: live-smoke
steps:
- name: Reject a repeated live-smoke attempt
env:
WORKFLOW_RUN_ATTEMPT: ${{ github.run_attempt }}
run: |
set -euo pipefail
if [[ "$WORKFLOW_RUN_ATTEMPT" != "1" ]]; then
echo "The bounded live smoke is restricted to the initial run attempt." >&2
exit 1
fi
- name: Check out the verified release tag
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
Expand DownExpand Up@@ -668,6 +714,11 @@ jobs:

publish:
name: Publish with npm Trusted Publishing
if: >-
always() &&
(github.run_attempt == 1 || github.run_attempt == 2) &&
needs.live-smoke.result == 'success' &&
needs.verify.result == 'success'
needs:
- live-smoke
- verify
Expand All@@ -684,6 +735,16 @@ jobs:
deployments: read
id-token: write
steps:
- name: Reject an out-of-bounds publication attempt
env:
WORKFLOW_RUN_ATTEMPT: ${{ github.run_attempt }}
run: |
set -euo pipefail
if [[ "$WORKFLOW_RUN_ATTEMPT" != "1" &&
"$WORKFLOW_RUN_ATTEMPT" != "2" ]]; then
echo "Publication permits only the initial attempt and one failed-job replay." >&2
exit 1
fi
- name: Check out the verified release commit
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
Expand All@@ -704,12 +765,14 @@ jobs:
name: ${{ needs.verify.outputs.artifact-name }}
path: release-artifacts
- name: Reconfirm protected state immediately before publication
id: pre-publish
env:
CONTROL_COMMIT: ${{ needs.verify.outputs.control-commit }}
CONTROL_VALIDATOR: ${{ runner.temp }}/release-workflow-validation.mjs
ENVIRONMENT_FILE: ${{ runner.temp }}/npm-environment.json
EVENT_REF: ${{ github.ref }}
EVENT_SHA: ${{ github.sha }}
EXPECTED_NEXT_VERSION: ${{ needs.verify.outputs.expected-next-version }}
GH_TOKEN: ${{ github.token }}
POLICIES_FILE: ${{ runner.temp }}/npm-deployment-policies.json
RELEASE_COMMIT: ${{ needs.verify.outputs.release-commit }}
Expand All@@ -722,6 +785,7 @@ jobs:
RELEASE_PLEASE_RUNS: ${{ runner.temp }}/release-please-runs-before-publish.json
RELEASE_PLEASE_SNAPSHOT: ${{ needs.verify.outputs.release-please-snapshot }}
VERSION: ${{ needs.verify.outputs.version }}
WORKFLOW_RUN_ATTEMPT: ${{ github.run_attempt }}
WORKFLOW_SHA: ${{ github.workflow_sha }}
shell: bash
run: |
Expand DownExpand Up@@ -793,6 +857,10 @@ jobs:
exit 1
fi
fi
if [[ "$WORKFLOW_RUN_ATTEMPT" == "2" && -z "$exact_version" ]]; then
echo "The failed-job replay requires the exact registry version to exist." >&2
exit 1
fi
latest_version="$(npm view cometapi@latest version)"
next_version="$(npm view cometapi@next version)"
gh api --paginate --slurp \
Expand DownExpand Up@@ -885,21 +953,29 @@ jobs:
});
validateRegistryStateBeforePublish({
exactVersion: process.env.EXACT_VERSION || null,
expectedNextVersion: process.env.EXPECTED_NEXT_VERSION,
latestVersion: process.env.LATEST_VERSION,
nextVersion: process.env.NEXT_VERSION,
version,
});
EOF
if [[ -n "$exact_version" ]]; then
echo "expect-existing=true" >> "$GITHUB_OUTPUT"
else
echo "expect-existing=false" >> "$GITHUB_OUTPUT"
fi
- name: Publish the exact artifact with provenance
env:
DIST_TAG: ${{ needs.verify.outputs.dist-tag }}
EXPECT_EXISTING: ${{ steps.pre-publish.outputs.expect-existing }}
VERSION: ${{ needs.verify.outputs.version }}
run: bash scripts/publish-artifact.sh
- name: Verify the public registry artifact
env:
CONTROL_COMMIT: ${{ needs.verify.outputs.control-commit }}
CONTROL_VALIDATOR: ${{ runner.temp }}/release-workflow-validation.mjs
DIST_TAG: ${{ needs.verify.outputs.dist-tag }}
EXPECTED_NEXT_VERSION: ${{ needs.verify.outputs.expected-next-version }}
GH_TOKEN: ${{ github.token }}
WORKFLOW_REF: ${{ github.ref }}
VERSION: ${{ needs.verify.outputs.version }}
Expand DownExpand Up@@ -942,14 +1018,57 @@ jobs:
echo "Registry state did not converge for cometapi@${VERSION}, ${DIST_TAG}, integrity, and provenance." >&2
exit 1
fi
if [[ "$(npm view cometapi@next version)" != "0.1.0-alpha.3" ]]; then
if [[ "$(npm view cometapi@next version)" != "$EXPECTED_NEXT_VERSION" ]]; then
echo "The next dist-tag changed during publication." >&2
exit 1
fi

attestations_url="$(REGISTRY_DIST="$registry_dist" node -e 'process.stdout.write(JSON.parse(process.env.REGISTRY_DIST).attestations.url)')"
attestations_file="$RUNNER_TEMP/npm-attestations.json"
curl --fail --silent --show-error "$attestations_url" > "$attestations_file"
ATTESTATIONS_URL="$attestations_url" \
node --input-type=module <<'EOF'
import { pathToFileURL } from "node:url";

const { validateRegistryAttestationUrl } = await import(
pathToFileURL(process.env.CONTROL_VALIDATOR)
);
validateRegistryAttestationUrl({
url: process.env.ATTESTATIONS_URL,
version: process.env.VERSION,
});
EOF
ATTESTATIONS_FILE="$attestations_file" \
ATTESTATIONS_URL="$attestations_url" \
bash scripts/fetch-attestations.sh

post_exact_version="$(npm view "cometapi@${VERSION}" version)"
post_tagged_version="$(npm view "cometapi@${DIST_TAG}" version)"
post_next_version="$(npm view cometapi@next version)"
post_registry_dist="$(npm view "cometapi@${VERSION}" dist --json)"
ATTESTATIONS_URL="$attestations_url" \
EXACT_VERSION="$post_exact_version" \
EXPECTED_INTEGRITY="$local_integrity" \
NEXT_VERSION="$post_next_version" \
REGISTRY_DIST="$post_registry_dist" \
TAGGED_VERSION="$post_tagged_version" \
node --input-type=module <<'EOF'
import { pathToFileURL } from "node:url";

const { validatePublishedRegistryState } = await import(
pathToFileURL(process.env.CONTROL_VALIDATOR)
);
validatePublishedRegistryState({
attestationUrl: process.env.ATTESTATIONS_URL,
dist: JSON.parse(process.env.REGISTRY_DIST),
exactVersion: process.env.EXACT_VERSION,
expectedNextVersion: process.env.EXPECTED_NEXT_VERSION,
expectedIntegrity: process.env.EXPECTED_INTEGRITY,
nextVersion: process.env.NEXT_VERSION,
taggedVersion: process.env.TAGGED_VERSION,
version: process.env.VERSION,
});
EOF
registry_dist="$post_registry_dist"
local_sha512="$(node -e 'const {createHash}=require("node:crypto");const {readFileSync}=require("node:fs");process.stdout.write(createHash("sha512").update(readFileSync(process.argv[1])).digest("hex"))' "${tarballs[0]}")"
provenance_identity="$(ATTESTATIONS_FILE="$attestations_file" \
LOCAL_SHA512="$local_sha512" node --input-type=module <<'EOF'
Expand DownExpand Up@@ -1176,3 +1295,36 @@ jobs:
process.exitCode = 1;
});
EOF

result:
name: Enforce the bounded publication result
if: >-
always() &&
github.event_name == 'workflow_dispatch' &&
inputs.publish_operation == 'release'
needs:
- live-smoke
- publish
- verify
runs-on: ubuntu-latest
permissions: {}
steps:
- name: Reject skipped or out-of-bounds publication
env:
LIVE_SMOKE_RESULT: ${{ needs.live-smoke.result }}
PUBLISH_RESULT: ${{ needs.publish.result }}
VERIFY_RESULT: ${{ needs.verify.result }}
WORKFLOW_RUN_ATTEMPT: ${{ github.run_attempt }}
run: |
set -euo pipefail
if [[ "$WORKFLOW_RUN_ATTEMPT" != "1" &&
"$WORKFLOW_RUN_ATTEMPT" != "2" ]]; then
echo "Publication permits only the initial attempt and one failed-job replay." >&2
exit 1
fi
if [[ "$VERIFY_RESULT" != "success" ||
"$LIVE_SMOKE_RESULT" != "success" ||
"$PUBLISH_RESULT" != "success" ]]; then
echo "The bounded publication job set did not complete successfully." >&2
exit 1
fi
27 changes: 25 additions & 2 deletions AGENTS.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -283,9 +283,32 @@ repository root.
- Prepare or refresh a release PR with a new first-attempt manual Release Please
dispatch. Do not rerun a preparation dispatch. Release Please same-run Release
reconciliation is allowed only under the exact conditions in `RELEASING.md`.
- The successful Release Please `workflow_run` handoff is attempt-1-only and
must refuse to dispatch when any exact Publish child already exists for the
immutable tag and commit. Never rerun a handoff to create a second child run.
- Never bypass a failed stable release with a manual or auxiliary tag, a
branch-context publish, a temporary `main` npm Environment policy, reused
artifact or live evidence, an arbitrary rerun, or a different patch version.
branch-context publish, a temporary `main` npm Environment policy, cross-run
artifact or live-evidence reuse, an arbitrary rerun, or a different patch
version.
- If `npm publish` succeeds but post-publication verification fails, first read
the exact public version, `latest`, `next`, integrity, and every readable
attestation, signature, and provenance field. When the exact version is
absent, `latest` must equal the previous patch. When the exact version equals
the candidate, `latest` may equal the previous patch or candidate. Final state
requires both exact and `latest` to equal the candidate, while `next` must
equal the prerelease value frozen by the initial verification job. A transient
early attestation-endpoint `404` is the known registry convergence condition;
all transport failures receive one wall-clock-bounded wait, and exhaustion is
terminal. Do not rerun to extend it. Only when attestations are readable and
independent signature and provenance checks have passed may exactly one
`rerun failed jobs` on the same immutable-tag run resume a different failed
post-publication gate. Confirm that GitHub preserves the successful
exact-artifact and live-smoke jobs; rerun-all must fail before live API access.
The attempt-2 publish job requires the exact version to exist, matching
integrity, and a fresh Environment approval; it must skip `npm publish`.
Attempt 3 or later, an exact-version metadata `E404`, a second replay request,
non-convergence, or any identity, integrity, provenance, dist-tag, or
configuration mismatch is a hard stop.
- After registry verification, immediately restore
`RELEASE_PLEASE_ENABLED=false`, keep `LIVE_SMOKE_ENABLED=true`, and require the
npm Environment deployment-policy set to contain only `tag:v*`.
Expand Down
21 changes: 20 additions & 1 deletion ARCHITECTURE.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -139,7 +139,9 @@ the exact Release-producing attempt, SHA, tag, version, URL, repository,
workflow identity, run ID, and attempt in a schema-v2 exact-run artifact. An
unprivileged `workflow_run` handoff validates only that attempt's artifact,
exact tag, immutable Release, and current `main`, then dispatches `publish.yml`
with `ref=v<version>`. Only that tag-bound `workflow_dispatch` can reach fresh
with `ref=v<version>`. The handoff is attempt-1-only and refuses to dispatch if
an exact child run already exists for the tag and commit. Only that tag-bound
`workflow_dispatch` can reach fresh
artifact verification, bounded live smoke, the npm Environment, or OIDC. A
first-attempt manual run is explicitly release-inert and must succeed only after
independently validating one canonical action-created patch PR; its event cannot
Expand All@@ -153,6 +155,23 @@ Release Please and publication remain separate trust domains. Release Please
does not receive npm OIDC permission; `id-token: write` remains limited to the
protected publish job. Repository variables gate both flows, and reruns remain
fail-closed on exact tag, artifact, dist-tag, integrity, and provenance state.
Registry package metadata and its attestation endpoint can converge at
different times. Post-publication verification therefore gives attestation HTTP
failures one strict wall-clock-bounded retry window before failing; an early
`404` is the known convergence case, while persistent URL, authentication,
authorization, server, and transport failures remain terminal. The initial
verification job freezes the prerelease dist-tag value, and every later registry
gate requires it to remain unchanged rather than encoding a current package
version in durable workflow guidance. If publication succeeded before a later
gate failed, only one attempt-2 failed-job replay of that same immutable-tag run
may continue, and only after attestations, signature, and provenance are already
valid. Verification and bounded live smoke are attempt-1-only, so rerun-all
fails before live API access; attempt 3 or later also fails. The protected-state
step requires the exact version to exist on replay; `publish-artifact.sh` then
skips registry mutation only after its integrity matches the verified tarball
and refuses a later `E404` instead of republishing. The failed-job replay
preserves the same run's successful artifact and bounded live-smoke jobs rather
than borrowing evidence from another run.
Manual preparation rejects attempt 2 or later; restart uses a new dispatch with
Release creation disabled. A `push` rerun is bounded to the same run ID, SHA,
candidate, and final-head review. It may retry while the tag and Release remain
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 157 additions & 5 deletions .github/workflows/publish.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,6 +47,7 @@ jobs:
if: >-
vars.RELEASE_PLEASE_ENABLED == 'true' &&
github.event_name == 'workflow_run' &&
github.run_attempt == 1 &&
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'push' &&
github.event.workflow_run.head_branch == 'main'
Expand DownExpand Up@@ -243,7 +244,17 @@ jobs:
after_runs="$RUNNER_TEMP/tag-dispatch-runs-after.json"
gh api --paginate --slurp \
"repos/${GITHUB_REPOSITORY}/actions/workflows/publish.yml/runs?event=workflow_dispatch&per_page=100" \
| jq '[.[].workflow_runs[].id]' > "$before_runs"
| jq '[.[].workflow_runs[]]' > "$before_runs"
prior_count="$(jq \
--arg commit "$RELEASE_COMMIT" --arg tag "$RELEASE_TAG" \
'[.[] | select(.actor.login == "github-actions[bot]" and
.triggering_actor.login == "github-actions[bot]" and
.event == "workflow_dispatch" and .head_branch == $tag and
.head_sha == $commit)] | length' "$before_runs")"
if [[ "$prior_count" != "0" ]]; then
echo "An exact Publish run already exists for this immutable tag and commit." >&2
exit 1
fi
gh api --method POST \
"repos/${GITHUB_REPOSITORY}/actions/workflows/publish.yml/dispatches" \
-f ref="$RELEASE_TAG" \
Expand All@@ -262,7 +273,7 @@ jobs:
candidate_count="$(jq \
--arg commit "$RELEASE_COMMIT" --arg tag "$RELEASE_TAG" \
--slurpfile before "$before_runs" \
'[.[] | select(.id as $id | ($before[0] | index($id) | not)) |
'[.[] | select(.id as $id | ($before[0] | map(.id) | index($id) | not)) |
select(.actor.login == "github-actions[bot]" and
.triggering_actor.login == "github-actions[bot]" and
.event == "workflow_dispatch" and .head_branch == $tag and
Expand All@@ -275,7 +286,7 @@ jobs:
publish_run_id="$(jq -r \
--arg commit "$RELEASE_COMMIT" --arg tag "$RELEASE_TAG" \
--slurpfile before "$before_runs" \
'[.[] | select(.id as $id | ($before[0] | index($id) | not)) |
'[.[] | select(.id as $id | ($before[0] | map(.id) | index($id) | not)) |
select(.actor.login == "github-actions[bot]" and
.triggering_actor.login == "github-actions[bot]" and
.event == "workflow_dispatch" and .head_branch == $tag and
Expand All@@ -296,6 +307,7 @@ jobs:
if: >-
vars.RELEASE_PLEASE_ENABLED == 'true' &&
github.event_name == 'workflow_dispatch' &&
github.run_attempt == 1 &&
inputs.publish_operation == 'release' &&
startsWith(github.ref, 'refs/tags/v0.1.') &&
github.ref == format('refs/tags/{0}', inputs.release_tag) &&
Expand All@@ -312,11 +324,21 @@ jobs:
artifact-name: ${{ steps.artifact-name.outputs.name }}
control-commit: ${{ inputs.control_commit }}
dist-tag: ${{ steps.version.outputs.dist-tag }}
expected-next-version: ${{ steps.registry-baseline.outputs.next-version }}
release-commit: ${{ steps.trust.outputs.release-commit }}
release-tag: ${{ steps.trust.outputs.release-tag }}
release-please-snapshot: ${{ steps.release-please-snapshot.outputs.digest }}
version: ${{ steps.version.outputs.version }}
steps:
- name: Reject a repeated verification attempt
env:
WORKFLOW_RUN_ATTEMPT: ${{ github.run_attempt }}
run: |
set -euo pipefail
if [[ "$WORKFLOW_RUN_ATTEMPT" != "1" ]]; then
echo "Exact-artifact verification is restricted to the initial run attempt." >&2
exit 1
fi
- name: Check out the workflow control commit
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
Expand DownExpand Up@@ -576,6 +598,18 @@ jobs:
EOF
- name: Use a Trusted Publishing-capable npm CLI
run: npm install --global npm@11.12.1
- name: Freeze the prerelease dist-tag
id: registry-baseline
shell: bash
run: |
set -euo pipefail
next_version="$(npm view cometapi@next version)"
NEXT_VERSION="$next_version" node --input-type=module <<'EOF'
import { validatePrereleaseDistTagBaseline } from "./scripts/release-workflow-validation.mjs";

validatePrereleaseDistTagBaseline(process.env.NEXT_VERSION);
EOF
echo "next-version=${next_version}" >> "$GITHUB_OUTPUT"
- name: Install locked dependencies
run: npm ci
- name: Run release checks
Expand DownExpand Up@@ -630,6 +664,9 @@ jobs:

live-smoke:
name: Verify the release tag against CometAPI
if: >-
github.run_attempt == 1 &&
needs.verify.result == 'success'
needs:
- verify
concurrency:
Expand All@@ -641,6 +678,15 @@ jobs:
# without required reviewers and add COMETAPI_KEY before publishing a release.
environment: live-smoke
steps:
- name: Reject a repeated live-smoke attempt
env:
WORKFLOW_RUN_ATTEMPT: ${{ github.run_attempt }}
run: |
set -euo pipefail
if [[ "$WORKFLOW_RUN_ATTEMPT" != "1" ]]; then
echo "The bounded live smoke is restricted to the initial run attempt." >&2
exit 1
fi
- name: Check out the verified release tag
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
Expand DownExpand Up@@ -668,6 +714,11 @@ jobs:

publish:
name: Publish with npm Trusted Publishing
if: >-
always() &&
(github.run_attempt == 1 || github.run_attempt == 2) &&
needs.live-smoke.result == 'success' &&
needs.verify.result == 'success'
needs:
- live-smoke
- verify
Expand All@@ -684,6 +735,16 @@ jobs:
deployments: read
id-token: write
steps:
- name: Reject an out-of-bounds publication attempt
env:
WORKFLOW_RUN_ATTEMPT: ${{ github.run_attempt }}
run: |
set -euo pipefail
if [[ "$WORKFLOW_RUN_ATTEMPT" != "1" &&
"$WORKFLOW_RUN_ATTEMPT" != "2" ]]; then
echo "Publication permits only the initial attempt and one failed-job replay." >&2
exit 1
fi
- name: Check out the verified release commit
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
Expand All@@ -704,12 +765,14 @@ jobs:
name: ${{ needs.verify.outputs.artifact-name }}
path: release-artifacts
- name: Reconfirm protected state immediately before publication
id: pre-publish
env:
CONTROL_COMMIT: ${{ needs.verify.outputs.control-commit }}
CONTROL_VALIDATOR: ${{ runner.temp }}/release-workflow-validation.mjs
ENVIRONMENT_FILE: ${{ runner.temp }}/npm-environment.json
EVENT_REF: ${{ github.ref }}
EVENT_SHA: ${{ github.sha }}
EXPECTED_NEXT_VERSION: ${{ needs.verify.outputs.expected-next-version }}
GH_TOKEN: ${{ github.token }}
POLICIES_FILE: ${{ runner.temp }}/npm-deployment-policies.json
RELEASE_COMMIT: ${{ needs.verify.outputs.release-commit }}
Expand All@@ -722,6 +785,7 @@ jobs:
RELEASE_PLEASE_RUNS: ${{ runner.temp }}/release-please-runs-before-publish.json
RELEASE_PLEASE_SNAPSHOT: ${{ needs.verify.outputs.release-please-snapshot }}
VERSION: ${{ needs.verify.outputs.version }}
WORKFLOW_RUN_ATTEMPT: ${{ github.run_attempt }}
WORKFLOW_SHA: ${{ github.workflow_sha }}
shell: bash
run: |
Expand DownExpand Up@@ -793,6 +857,10 @@ jobs:
exit 1
fi
fi
if [[ "$WORKFLOW_RUN_ATTEMPT" == "2" && -z "$exact_version" ]]; then
echo "The failed-job replay requires the exact registry version to exist." >&2
exit 1
fi
latest_version="$(npm view cometapi@latest version)"
next_version="$(npm view cometapi@next version)"
gh api --paginate --slurp \
Expand DownExpand Up@@ -885,21 +953,29 @@ jobs:
});
validateRegistryStateBeforePublish({
exactVersion: process.env.EXACT_VERSION || null,
expectedNextVersion: process.env.EXPECTED_NEXT_VERSION,
latestVersion: process.env.LATEST_VERSION,
nextVersion: process.env.NEXT_VERSION,
version,
});
EOF
if [[ -n "$exact_version" ]]; then
echo "expect-existing=true" >> "$GITHUB_OUTPUT"
else
echo "expect-existing=false" >> "$GITHUB_OUTPUT"
fi
- name: Publish the exact artifact with provenance
env:
DIST_TAG: ${{ needs.verify.outputs.dist-tag }}
EXPECT_EXISTING: ${{ steps.pre-publish.outputs.expect-existing }}
VERSION: ${{ needs.verify.outputs.version }}
run: bash scripts/publish-artifact.sh
- name: Verify the public registry artifact
env:
CONTROL_COMMIT: ${{ needs.verify.outputs.control-commit }}
CONTROL_VALIDATOR: ${{ runner.temp }}/release-workflow-validation.mjs
DIST_TAG: ${{ needs.verify.outputs.dist-tag }}
EXPECTED_NEXT_VERSION: ${{ needs.verify.outputs.expected-next-version }}
GH_TOKEN: ${{ github.token }}
WORKFLOW_REF: ${{ github.ref }}
VERSION: ${{ needs.verify.outputs.version }}
Expand DownExpand Up@@ -942,14 +1018,57 @@ jobs:
echo "Registry state did not converge for cometapi@${VERSION}, ${DIST_TAG}, integrity, and provenance." >&2
exit 1
fi
if [[ "$(npm view cometapi@next version)" != "0.1.0-alpha.3" ]]; then
if [[ "$(npm view cometapi@next version)" != "$EXPECTED_NEXT_VERSION" ]]; then
echo "The next dist-tag changed during publication." >&2
exit 1
fi

attestations_url="$(REGISTRY_DIST="$registry_dist" node -e 'process.stdout.write(JSON.parse(process.env.REGISTRY_DIST).attestations.url)')"
attestations_file="$RUNNER_TEMP/npm-attestations.json"
curl --fail --silent --show-error "$attestations_url" > "$attestations_file"
ATTESTATIONS_URL="$attestations_url" \
node --input-type=module <<'EOF'
import { pathToFileURL } from "node:url";

const { validateRegistryAttestationUrl } = await import(
pathToFileURL(process.env.CONTROL_VALIDATOR)
);
validateRegistryAttestationUrl({
url: process.env.ATTESTATIONS_URL,
version: process.env.VERSION,
});
EOF
ATTESTATIONS_FILE="$attestations_file" \
ATTESTATIONS_URL="$attestations_url" \
bash scripts/fetch-attestations.sh

post_exact_version="$(npm view "cometapi@${VERSION}" version)"
post_tagged_version="$(npm view "cometapi@${DIST_TAG}" version)"
post_next_version="$(npm view cometapi@next version)"
post_registry_dist="$(npm view "cometapi@${VERSION}" dist --json)"
ATTESTATIONS_URL="$attestations_url" \
EXACT_VERSION="$post_exact_version" \
EXPECTED_INTEGRITY="$local_integrity" \
NEXT_VERSION="$post_next_version" \
REGISTRY_DIST="$post_registry_dist" \
TAGGED_VERSION="$post_tagged_version" \
node --input-type=module <<'EOF'
import { pathToFileURL } from "node:url";

const { validatePublishedRegistryState } = await import(
pathToFileURL(process.env.CONTROL_VALIDATOR)
);
validatePublishedRegistryState({
attestationUrl: process.env.ATTESTATIONS_URL,
dist: JSON.parse(process.env.REGISTRY_DIST),
exactVersion: process.env.EXACT_VERSION,
expectedNextVersion: process.env.EXPECTED_NEXT_VERSION,
expectedIntegrity: process.env.EXPECTED_INTEGRITY,
nextVersion: process.env.NEXT_VERSION,
taggedVersion: process.env.TAGGED_VERSION,
version: process.env.VERSION,
});
EOF
registry_dist="$post_registry_dist"
local_sha512="$(node -e 'const {createHash}=require("node:crypto");const {readFileSync}=require("node:fs");process.stdout.write(createHash("sha512").update(readFileSync(process.argv[1])).digest("hex"))' "${tarballs[0]}")"
provenance_identity="$(ATTESTATIONS_FILE="$attestations_file" \
LOCAL_SHA512="$local_sha512" node --input-type=module <<'EOF'
Expand DownExpand Up@@ -1176,3 +1295,36 @@ jobs:
process.exitCode = 1;
});
EOF

result:
name: Enforce the bounded publication result
if: >-
always() &&
github.event_name == 'workflow_dispatch' &&
inputs.publish_operation == 'release'
needs:
- live-smoke
- publish
- verify
runs-on: ubuntu-latest
permissions: {}
steps:
- name: Reject skipped or out-of-bounds publication
env:
LIVE_SMOKE_RESULT: ${{ needs.live-smoke.result }}
PUBLISH_RESULT: ${{ needs.publish.result }}
VERIFY_RESULT: ${{ needs.verify.result }}
WORKFLOW_RUN_ATTEMPT: ${{ github.run_attempt }}
run: |
set -euo pipefail
if [[ "$WORKFLOW_RUN_ATTEMPT" != "1" &&
"$WORKFLOW_RUN_ATTEMPT" != "2" ]]; then
echo "Publication permits only the initial attempt and one failed-job replay." >&2
exit 1
fi
if [[ "$VERIFY_RESULT" != "success" ||
"$LIVE_SMOKE_RESULT" != "success" ||
"$PUBLISH_RESULT" != "success" ]]; then
echo "The bounded publication job set did not complete successfully." >&2
exit 1
fi
27 changes: 25 additions & 2 deletions AGENTS.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -283,9 +283,32 @@ repository root.
- Prepare or refresh a release PR with a new first-attempt manual Release Please
dispatch. Do not rerun a preparation dispatch. Release Please same-run Release
reconciliation is allowed only under the exact conditions in `RELEASING.md`.
- The successful Release Please `workflow_run` handoff is attempt-1-only and
must refuse to dispatch when any exact Publish child already exists for the
immutable tag and commit. Never rerun a handoff to create a second child run.
- Never bypass a failed stable release with a manual or auxiliary tag, a
branch-context publish, a temporary `main` npm Environment policy, reused
artifact or live evidence, an arbitrary rerun, or a different patch version.
branch-context publish, a temporary `main` npm Environment policy, cross-run
artifact or live-evidence reuse, an arbitrary rerun, or a different patch
version.
- If `npm publish` succeeds but post-publication verification fails, first read
the exact public version, `latest`, `next`, integrity, and every readable
attestation, signature, and provenance field. When the exact version is
absent, `latest` must equal the previous patch. When the exact version equals
the candidate, `latest` may equal the previous patch or candidate. Final state
requires both exact and `latest` to equal the candidate, while `next` must
equal the prerelease value frozen by the initial verification job. A transient
early attestation-endpoint `404` is the known registry convergence condition;
all transport failures receive one wall-clock-bounded wait, and exhaustion is
terminal. Do not rerun to extend it. Only when attestations are readable and
independent signature and provenance checks have passed may exactly one
`rerun failed jobs` on the same immutable-tag run resume a different failed
post-publication gate. Confirm that GitHub preserves the successful
exact-artifact and live-smoke jobs; rerun-all must fail before live API access.
The attempt-2 publish job requires the exact version to exist, matching
integrity, and a fresh Environment approval; it must skip `npm publish`.
Attempt 3 or later, an exact-version metadata `E404`, a second replay request,
non-convergence, or any identity, integrity, provenance, dist-tag, or
configuration mismatch is a hard stop.
- After registry verification, immediately restore
`RELEASE_PLEASE_ENABLED=false`, keep `LIVE_SMOKE_ENABLED=true`, and require the
npm Environment deployment-policy set to contain only `tag:v*`.
Expand Down
21 changes: 20 additions & 1 deletion ARCHITECTURE.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -139,7 +139,9 @@ the exact Release-producing attempt, SHA, tag, version, URL, repository,
workflow identity, run ID, and attempt in a schema-v2 exact-run artifact. An
unprivileged `workflow_run` handoff validates only that attempt's artifact,
exact tag, immutable Release, and current `main`, then dispatches `publish.yml`
with `ref=v<version>`. Only that tag-bound `workflow_dispatch` can reach fresh
with `ref=v<version>`. The handoff is attempt-1-only and refuses to dispatch if
an exact child run already exists for the tag and commit. Only that tag-bound
`workflow_dispatch` can reach fresh
artifact verification, bounded live smoke, the npm Environment, or OIDC. A
first-attempt manual run is explicitly release-inert and must succeed only after
independently validating one canonical action-created patch PR; its event cannot
Expand All@@ -153,6 +155,23 @@ Release Please and publication remain separate trust domains. Release Please
does not receive npm OIDC permission; `id-token: write` remains limited to the
protected publish job. Repository variables gate both flows, and reruns remain
fail-closed on exact tag, artifact, dist-tag, integrity, and provenance state.
Registry package metadata and its attestation endpoint can converge at
different times. Post-publication verification therefore gives attestation HTTP
failures one strict wall-clock-bounded retry window before failing; an early
`404` is the known convergence case, while persistent URL, authentication,
authorization, server, and transport failures remain terminal. The initial
verification job freezes the prerelease dist-tag value, and every later registry
gate requires it to remain unchanged rather than encoding a current package
version in durable workflow guidance. If publication succeeded before a later
gate failed, only one attempt-2 failed-job replay of that same immutable-tag run
may continue, and only after attestations, signature, and provenance are already
valid. Verification and bounded live smoke are attempt-1-only, so rerun-all
fails before live API access; attempt 3 or later also fails. The protected-state
step requires the exact version to exist on replay; `publish-artifact.sh` then
skips registry mutation only after its integrity matches the verified tarball
and refuses a later `E404` instead of republishing. The failed-job replay
preserves the same run's successful artifact and bounded live-smoke jobs rather
than borrowing evidence from another run.
Manual preparation rejects attempt 2 or later; restart uses a new dispatch with
Release creation disabled. A `push` rerun is bounded to the same run ID, SHA,
candidate, and final-head review. It may retry while the tag and Release remain
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 157 additions & 5 deletions .github/workflows/publish.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,6 +47,7 @@ jobs:
if: >-
vars.RELEASE_PLEASE_ENABLED == 'true' &&
github.event_name == 'workflow_run' &&
github.run_attempt == 1 &&
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'push' &&
github.event.workflow_run.head_branch == 'main'
Expand DownExpand Up@@ -243,7 +244,17 @@ jobs:
after_runs="$RUNNER_TEMP/tag-dispatch-runs-after.json"
gh api --paginate --slurp \
"repos/${GITHUB_REPOSITORY}/actions/workflows/publish.yml/runs?event=workflow_dispatch&per_page=100" \
| jq '[.[].workflow_runs[].id]' > "$before_runs"
| jq '[.[].workflow_runs[]]' > "$before_runs"
prior_count="$(jq \
--arg commit "$RELEASE_COMMIT" --arg tag "$RELEASE_TAG" \
'[.[] | select(.actor.login == "github-actions[bot]" and
.triggering_actor.login == "github-actions[bot]" and
.event == "workflow_dispatch" and .head_branch == $tag and
.head_sha == $commit)] | length' "$before_runs")"
if [[ "$prior_count" != "0" ]]; then
echo "An exact Publish run already exists for this immutable tag and commit." >&2
exit 1
fi
gh api --method POST \
"repos/${GITHUB_REPOSITORY}/actions/workflows/publish.yml/dispatches" \
-f ref="$RELEASE_TAG" \
Expand All@@ -262,7 +273,7 @@ jobs:
candidate_count="$(jq \
--arg commit "$RELEASE_COMMIT" --arg tag "$RELEASE_TAG" \
--slurpfile before "$before_runs" \
'[.[] | select(.id as $id | ($before[0] | index($id) | not)) |
'[.[] | select(.id as $id | ($before[0] | map(.id) | index($id) | not)) |
select(.actor.login == "github-actions[bot]" and
.triggering_actor.login == "github-actions[bot]" and
.event == "workflow_dispatch" and .head_branch == $tag and
Expand All@@ -275,7 +286,7 @@ jobs:
publish_run_id="$(jq -r \
--arg commit "$RELEASE_COMMIT" --arg tag "$RELEASE_TAG" \
--slurpfile before "$before_runs" \
'[.[] | select(.id as $id | ($before[0] | index($id) | not)) |
'[.[] | select(.id as $id | ($before[0] | map(.id) | index($id) | not)) |
select(.actor.login == "github-actions[bot]" and
.triggering_actor.login == "github-actions[bot]" and
.event == "workflow_dispatch" and .head_branch == $tag and
Expand All@@ -296,6 +307,7 @@ jobs:
if: >-
vars.RELEASE_PLEASE_ENABLED == 'true' &&
github.event_name == 'workflow_dispatch' &&
github.run_attempt == 1 &&
inputs.publish_operation == 'release' &&
startsWith(github.ref, 'refs/tags/v0.1.') &&
github.ref == format('refs/tags/{0}', inputs.release_tag) &&
Expand All@@ -312,11 +324,21 @@ jobs:
artifact-name: ${{ steps.artifact-name.outputs.name }}
control-commit: ${{ inputs.control_commit }}
dist-tag: ${{ steps.version.outputs.dist-tag }}
expected-next-version: ${{ steps.registry-baseline.outputs.next-version }}
release-commit: ${{ steps.trust.outputs.release-commit }}
release-tag: ${{ steps.trust.outputs.release-tag }}
release-please-snapshot: ${{ steps.release-please-snapshot.outputs.digest }}
version: ${{ steps.version.outputs.version }}
steps:
- name: Reject a repeated verification attempt
env:
WORKFLOW_RUN_ATTEMPT: ${{ github.run_attempt }}
run: |
set -euo pipefail
if [[ "$WORKFLOW_RUN_ATTEMPT" != "1" ]]; then
echo "Exact-artifact verification is restricted to the initial run attempt." >&2
exit 1
fi
- name: Check out the workflow control commit
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
Expand DownExpand Up@@ -576,6 +598,18 @@ jobs:
EOF
- name: Use a Trusted Publishing-capable npm CLI
run: npm install --global npm@11.12.1
- name: Freeze the prerelease dist-tag
id: registry-baseline
shell: bash
run: |
set -euo pipefail
next_version="$(npm view cometapi@next version)"
NEXT_VERSION="$next_version" node --input-type=module <<'EOF'
import { validatePrereleaseDistTagBaseline } from "./scripts/release-workflow-validation.mjs";

validatePrereleaseDistTagBaseline(process.env.NEXT_VERSION);
EOF
echo "next-version=${next_version}" >> "$GITHUB_OUTPUT"
- name: Install locked dependencies
run: npm ci
- name: Run release checks
Expand DownExpand Up@@ -630,6 +664,9 @@ jobs:

live-smoke:
name: Verify the release tag against CometAPI
if: >-
github.run_attempt == 1 &&
needs.verify.result == 'success'
needs:
- verify
concurrency:
Expand All@@ -641,6 +678,15 @@ jobs:
# without required reviewers and add COMETAPI_KEY before publishing a release.
environment: live-smoke
steps:
- name: Reject a repeated live-smoke attempt
env:
WORKFLOW_RUN_ATTEMPT: ${{ github.run_attempt }}
run: |
set -euo pipefail
if [[ "$WORKFLOW_RUN_ATTEMPT" != "1" ]]; then
echo "The bounded live smoke is restricted to the initial run attempt." >&2
exit 1
fi
- name: Check out the verified release tag
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
Expand DownExpand Up@@ -668,6 +714,11 @@ jobs:

publish:
name: Publish with npm Trusted Publishing
if: >-
always() &&
(github.run_attempt == 1 || github.run_attempt == 2) &&
needs.live-smoke.result == 'success' &&
needs.verify.result == 'success'
needs:
- live-smoke
- verify
Expand All@@ -684,6 +735,16 @@ jobs:
deployments: read
id-token: write
steps:
- name: Reject an out-of-bounds publication attempt
env:
WORKFLOW_RUN_ATTEMPT: ${{ github.run_attempt }}
run: |
set -euo pipefail
if [[ "$WORKFLOW_RUN_ATTEMPT" != "1" &&
"$WORKFLOW_RUN_ATTEMPT" != "2" ]]; then
echo "Publication permits only the initial attempt and one failed-job replay." >&2
exit 1
fi
- name: Check out the verified release commit
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
Expand All@@ -704,12 +765,14 @@ jobs:
name: ${{ needs.verify.outputs.artifact-name }}
path: release-artifacts
- name: Reconfirm protected state immediately before publication
id: pre-publish
env:
CONTROL_COMMIT: ${{ needs.verify.outputs.control-commit }}
CONTROL_VALIDATOR: ${{ runner.temp }}/release-workflow-validation.mjs
ENVIRONMENT_FILE: ${{ runner.temp }}/npm-environment.json
EVENT_REF: ${{ github.ref }}
EVENT_SHA: ${{ github.sha }}
EXPECTED_NEXT_VERSION: ${{ needs.verify.outputs.expected-next-version }}
GH_TOKEN: ${{ github.token }}
POLICIES_FILE: ${{ runner.temp }}/npm-deployment-policies.json
RELEASE_COMMIT: ${{ needs.verify.outputs.release-commit }}
Expand All@@ -722,6 +785,7 @@ jobs:
RELEASE_PLEASE_RUNS: ${{ runner.temp }}/release-please-runs-before-publish.json
RELEASE_PLEASE_SNAPSHOT: ${{ needs.verify.outputs.release-please-snapshot }}
VERSION: ${{ needs.verify.outputs.version }}
WORKFLOW_RUN_ATTEMPT: ${{ github.run_attempt }}
WORKFLOW_SHA: ${{ github.workflow_sha }}
shell: bash
run: |
Expand DownExpand Up@@ -793,6 +857,10 @@ jobs:
exit 1
fi
fi
if [[ "$WORKFLOW_RUN_ATTEMPT" == "2" && -z "$exact_version" ]]; then
echo "The failed-job replay requires the exact registry version to exist." >&2
exit 1
fi
latest_version="$(npm view cometapi@latest version)"
next_version="$(npm view cometapi@next version)"
gh api --paginate --slurp \
Expand DownExpand Up@@ -885,21 +953,29 @@ jobs:
});
validateRegistryStateBeforePublish({
exactVersion: process.env.EXACT_VERSION || null,
expectedNextVersion: process.env.EXPECTED_NEXT_VERSION,
latestVersion: process.env.LATEST_VERSION,
nextVersion: process.env.NEXT_VERSION,
version,
});
EOF
if [[ -n "$exact_version" ]]; then
echo "expect-existing=true" >> "$GITHUB_OUTPUT"
else
echo "expect-existing=false" >> "$GITHUB_OUTPUT"
fi
- name: Publish the exact artifact with provenance
env:
DIST_TAG: ${{ needs.verify.outputs.dist-tag }}
EXPECT_EXISTING: ${{ steps.pre-publish.outputs.expect-existing }}
VERSION: ${{ needs.verify.outputs.version }}
run: bash scripts/publish-artifact.sh
- name: Verify the public registry artifact
env:
CONTROL_COMMIT: ${{ needs.verify.outputs.control-commit }}
CONTROL_VALIDATOR: ${{ runner.temp }}/release-workflow-validation.mjs
DIST_TAG: ${{ needs.verify.outputs.dist-tag }}
EXPECTED_NEXT_VERSION: ${{ needs.verify.outputs.expected-next-version }}
GH_TOKEN: ${{ github.token }}
WORKFLOW_REF: ${{ github.ref }}
VERSION: ${{ needs.verify.outputs.version }}
Expand DownExpand Up@@ -942,14 +1018,57 @@ jobs:
echo "Registry state did not converge for cometapi@${VERSION}, ${DIST_TAG}, integrity, and provenance." >&2
exit 1
fi
if [[ "$(npm view cometapi@next version)" != "0.1.0-alpha.3" ]]; then
if [[ "$(npm view cometapi@next version)" != "$EXPECTED_NEXT_VERSION" ]]; then
echo "The next dist-tag changed during publication." >&2
exit 1
fi

attestations_url="$(REGISTRY_DIST="$registry_dist" node -e 'process.stdout.write(JSON.parse(process.env.REGISTRY_DIST).attestations.url)')"
attestations_file="$RUNNER_TEMP/npm-attestations.json"
curl --fail --silent --show-error "$attestations_url" > "$attestations_file"
ATTESTATIONS_URL="$attestations_url" \
node --input-type=module <<'EOF'
import { pathToFileURL } from "node:url";

const { validateRegistryAttestationUrl } = await import(
pathToFileURL(process.env.CONTROL_VALIDATOR)
);
validateRegistryAttestationUrl({
url: process.env.ATTESTATIONS_URL,
version: process.env.VERSION,
});
EOF
ATTESTATIONS_FILE="$attestations_file" \
ATTESTATIONS_URL="$attestations_url" \
bash scripts/fetch-attestations.sh

post_exact_version="$(npm view "cometapi@${VERSION}" version)"
post_tagged_version="$(npm view "cometapi@${DIST_TAG}" version)"
post_next_version="$(npm view cometapi@next version)"
post_registry_dist="$(npm view "cometapi@${VERSION}" dist --json)"
ATTESTATIONS_URL="$attestations_url" \
EXACT_VERSION="$post_exact_version" \
EXPECTED_INTEGRITY="$local_integrity" \
NEXT_VERSION="$post_next_version" \
REGISTRY_DIST="$post_registry_dist" \
TAGGED_VERSION="$post_tagged_version" \
node --input-type=module <<'EOF'
import { pathToFileURL } from "node:url";

const { validatePublishedRegistryState } = await import(
pathToFileURL(process.env.CONTROL_VALIDATOR)
);
validatePublishedRegistryState({
attestationUrl: process.env.ATTESTATIONS_URL,
dist: JSON.parse(process.env.REGISTRY_DIST),
exactVersion: process.env.EXACT_VERSION,
expectedNextVersion: process.env.EXPECTED_NEXT_VERSION,
expectedIntegrity: process.env.EXPECTED_INTEGRITY,
nextVersion: process.env.NEXT_VERSION,
taggedVersion: process.env.TAGGED_VERSION,
version: process.env.VERSION,
});
EOF
registry_dist="$post_registry_dist"
local_sha512="$(node -e 'const {createHash}=require("node:crypto");const {readFileSync}=require("node:fs");process.stdout.write(createHash("sha512").update(readFileSync(process.argv[1])).digest("hex"))' "${tarballs[0]}")"
provenance_identity="$(ATTESTATIONS_FILE="$attestations_file" \
LOCAL_SHA512="$local_sha512" node --input-type=module <<'EOF'
Expand DownExpand Up@@ -1176,3 +1295,36 @@ jobs:
process.exitCode = 1;
});
EOF

result:
name: Enforce the bounded publication result
if: >-
always() &&
github.event_name == 'workflow_dispatch' &&
inputs.publish_operation == 'release'
needs:
- live-smoke
- publish
- verify
runs-on: ubuntu-latest
permissions: {}
steps:
- name: Reject skipped or out-of-bounds publication
env:
LIVE_SMOKE_RESULT: ${{ needs.live-smoke.result }}
PUBLISH_RESULT: ${{ needs.publish.result }}
VERIFY_RESULT: ${{ needs.verify.result }}
WORKFLOW_RUN_ATTEMPT: ${{ github.run_attempt }}
run: |
set -euo pipefail
if [[ "$WORKFLOW_RUN_ATTEMPT" != "1" &&
"$WORKFLOW_RUN_ATTEMPT" != "2" ]]; then
echo "Publication permits only the initial attempt and one failed-job replay." >&2
exit 1
fi
if [[ "$VERIFY_RESULT" != "success" ||
"$LIVE_SMOKE_RESULT" != "success" ||
"$PUBLISH_RESULT" != "success" ]]; then
echo "The bounded publication job set did not complete successfully." >&2
exit 1
fi
27 changes: 25 additions & 2 deletions AGENTS.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -283,9 +283,32 @@ repository root.
- Prepare or refresh a release PR with a new first-attempt manual Release Please
dispatch. Do not rerun a preparation dispatch. Release Please same-run Release
reconciliation is allowed only under the exact conditions in `RELEASING.md`.
- The successful Release Please `workflow_run` handoff is attempt-1-only and
must refuse to dispatch when any exact Publish child already exists for the
immutable tag and commit. Never rerun a handoff to create a second child run.
- Never bypass a failed stable release with a manual or auxiliary tag, a
branch-context publish, a temporary `main` npm Environment policy, reused
artifact or live evidence, an arbitrary rerun, or a different patch version.
branch-context publish, a temporary `main` npm Environment policy, cross-run
artifact or live-evidence reuse, an arbitrary rerun, or a different patch
version.
- If `npm publish` succeeds but post-publication verification fails, first read
the exact public version, `latest`, `next`, integrity, and every readable
attestation, signature, and provenance field. When the exact version is
absent, `latest` must equal the previous patch. When the exact version equals
the candidate, `latest` may equal the previous patch or candidate. Final state
requires both exact and `latest` to equal the candidate, while `next` must
equal the prerelease value frozen by the initial verification job. A transient
early attestation-endpoint `404` is the known registry convergence condition;
all transport failures receive one wall-clock-bounded wait, and exhaustion is
terminal. Do not rerun to extend it. Only when attestations are readable and
independent signature and provenance checks have passed may exactly one
`rerun failed jobs` on the same immutable-tag run resume a different failed
post-publication gate. Confirm that GitHub preserves the successful
exact-artifact and live-smoke jobs; rerun-all must fail before live API access.
The attempt-2 publish job requires the exact version to exist, matching
integrity, and a fresh Environment approval; it must skip `npm publish`.
Attempt 3 or later, an exact-version metadata `E404`, a second replay request,
non-convergence, or any identity, integrity, provenance, dist-tag, or
configuration mismatch is a hard stop.
- After registry verification, immediately restore
`RELEASE_PLEASE_ENABLED=false`, keep `LIVE_SMOKE_ENABLED=true`, and require the
npm Environment deployment-policy set to contain only `tag:v*`.
Expand Down
21 changes: 20 additions & 1 deletion ARCHITECTURE.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -139,7 +139,9 @@ the exact Release-producing attempt, SHA, tag, version, URL, repository,
workflow identity, run ID, and attempt in a schema-v2 exact-run artifact. An
unprivileged `workflow_run` handoff validates only that attempt's artifact,
exact tag, immutable Release, and current `main`, then dispatches `publish.yml`
with `ref=v<version>`. Only that tag-bound `workflow_dispatch` can reach fresh
with `ref=v<version>`. The handoff is attempt-1-only and refuses to dispatch if
an exact child run already exists for the tag and commit. Only that tag-bound
`workflow_dispatch` can reach fresh
artifact verification, bounded live smoke, the npm Environment, or OIDC. A
first-attempt manual run is explicitly release-inert and must succeed only after
independently validating one canonical action-created patch PR; its event cannot
Expand All@@ -153,6 +155,23 @@ Release Please and publication remain separate trust domains. Release Please
does not receive npm OIDC permission; `id-token: write` remains limited to the
protected publish job. Repository variables gate both flows, and reruns remain
fail-closed on exact tag, artifact, dist-tag, integrity, and provenance state.
Registry package metadata and its attestation endpoint can converge at
different times. Post-publication verification therefore gives attestation HTTP
failures one strict wall-clock-bounded retry window before failing; an early
`404` is the known convergence case, while persistent URL, authentication,
authorization, server, and transport failures remain terminal. The initial
verification job freezes the prerelease dist-tag value, and every later registry
gate requires it to remain unchanged rather than encoding a current package
version in durable workflow guidance. If publication succeeded before a later
gate failed, only one attempt-2 failed-job replay of that same immutable-tag run
may continue, and only after attestations, signature, and provenance are already
valid. Verification and bounded live smoke are attempt-1-only, so rerun-all
fails before live API access; attempt 3 or later also fails. The protected-state
step requires the exact version to exist on replay; `publish-artifact.sh` then
skips registry mutation only after its integrity matches the verified tarball
and refuses a later `E404` instead of republishing. The failed-job replay
preserves the same run's successful artifact and bounded live-smoke jobs rather
than borrowing evidence from another run.
Manual preparation rejects attempt 2 or later; restart uses a new dispatch with
Release creation disabled. A `push` rerun is bounded to the same run ID, SHA,
candidate, and final-head review. It may retry while the tag and Release remain
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
162 changes: 157 additions & 5 deletions .github/workflows/publish.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -47,6 +47,7 @@ jobs:
if: >-
vars.RELEASE_PLEASE_ENABLED == 'true' &&
github.event_name == 'workflow_run' &&
github.run_attempt == 1 &&
github.event.workflow_run.conclusion == 'success' &&
github.event.workflow_run.event == 'push' &&
github.event.workflow_run.head_branch == 'main'
Expand DownExpand Up@@ -243,7 +244,17 @@ jobs:
after_runs="$RUNNER_TEMP/tag-dispatch-runs-after.json"
gh api --paginate --slurp \
"repos/${GITHUB_REPOSITORY}/actions/workflows/publish.yml/runs?event=workflow_dispatch&per_page=100" \
| jq '[.[].workflow_runs[].id]' > "$before_runs"
| jq '[.[].workflow_runs[]]' > "$before_runs"
prior_count="$(jq \
--arg commit "$RELEASE_COMMIT" --arg tag "$RELEASE_TAG" \
'[.[] | select(.actor.login == "github-actions[bot]" and
.triggering_actor.login == "github-actions[bot]" and
.event == "workflow_dispatch" and .head_branch == $tag and
.head_sha == $commit)] | length' "$before_runs")"
if [[ "$prior_count" != "0" ]]; then
echo "An exact Publish run already exists for this immutable tag and commit." >&2
exit 1
fi
gh api --method POST \
"repos/${GITHUB_REPOSITORY}/actions/workflows/publish.yml/dispatches" \
-f ref="$RELEASE_TAG" \
Expand All@@ -262,7 +273,7 @@ jobs:
candidate_count="$(jq \
--arg commit "$RELEASE_COMMIT" --arg tag "$RELEASE_TAG" \
--slurpfile before "$before_runs" \
'[.[] | select(.id as $id | ($before[0] | index($id) | not)) |
'[.[] | select(.id as $id | ($before[0] | map(.id) | index($id) | not)) |
select(.actor.login == "github-actions[bot]" and
.triggering_actor.login == "github-actions[bot]" and
.event == "workflow_dispatch" and .head_branch == $tag and
Expand All@@ -275,7 +286,7 @@ jobs:
publish_run_id="$(jq -r \
--arg commit "$RELEASE_COMMIT" --arg tag "$RELEASE_TAG" \
--slurpfile before "$before_runs" \
'[.[] | select(.id as $id | ($before[0] | index($id) | not)) |
'[.[] | select(.id as $id | ($before[0] | map(.id) | index($id) | not)) |
select(.actor.login == "github-actions[bot]" and
.triggering_actor.login == "github-actions[bot]" and
.event == "workflow_dispatch" and .head_branch == $tag and
Expand All@@ -296,6 +307,7 @@ jobs:
if: >-
vars.RELEASE_PLEASE_ENABLED == 'true' &&
github.event_name == 'workflow_dispatch' &&
github.run_attempt == 1 &&
inputs.publish_operation == 'release' &&
startsWith(github.ref, 'refs/tags/v0.1.') &&
github.ref == format('refs/tags/{0}', inputs.release_tag) &&
Expand All@@ -312,11 +324,21 @@ jobs:
artifact-name: ${{ steps.artifact-name.outputs.name }}
control-commit: ${{ inputs.control_commit }}
dist-tag: ${{ steps.version.outputs.dist-tag }}
expected-next-version: ${{ steps.registry-baseline.outputs.next-version }}
release-commit: ${{ steps.trust.outputs.release-commit }}
release-tag: ${{ steps.trust.outputs.release-tag }}
release-please-snapshot: ${{ steps.release-please-snapshot.outputs.digest }}
version: ${{ steps.version.outputs.version }}
steps:
- name: Reject a repeated verification attempt
env:
WORKFLOW_RUN_ATTEMPT: ${{ github.run_attempt }}
run: |
set -euo pipefail
if [[ "$WORKFLOW_RUN_ATTEMPT" != "1" ]]; then
echo "Exact-artifact verification is restricted to the initial run attempt." >&2
exit 1
fi
- name: Check out the workflow control commit
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
Expand DownExpand Up@@ -576,6 +598,18 @@ jobs:
EOF
- name: Use a Trusted Publishing-capable npm CLI
run: npm install --global npm@11.12.1
- name: Freeze the prerelease dist-tag
id: registry-baseline
shell: bash
run: |
set -euo pipefail
next_version="$(npm view cometapi@next version)"
NEXT_VERSION="$next_version" node --input-type=module <<'EOF'
import { validatePrereleaseDistTagBaseline } from "./scripts/release-workflow-validation.mjs";

validatePrereleaseDistTagBaseline(process.env.NEXT_VERSION);
EOF
echo "next-version=${next_version}" >> "$GITHUB_OUTPUT"
- name: Install locked dependencies
run: npm ci
- name: Run release checks
Expand DownExpand Up@@ -630,6 +664,9 @@ jobs:

live-smoke:
name: Verify the release tag against CometAPI
if: >-
github.run_attempt == 1 &&
needs.verify.result == 'success'
needs:
- verify
concurrency:
Expand All@@ -641,6 +678,15 @@ jobs:
# without required reviewers and add COMETAPI_KEY before publishing a release.
environment: live-smoke
steps:
- name: Reject a repeated live-smoke attempt
env:
WORKFLOW_RUN_ATTEMPT: ${{ github.run_attempt }}
run: |
set -euo pipefail
if [[ "$WORKFLOW_RUN_ATTEMPT" != "1" ]]; then
echo "The bounded live smoke is restricted to the initial run attempt." >&2
exit 1
fi
- name: Check out the verified release tag
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
Expand DownExpand Up@@ -668,6 +714,11 @@ jobs:

publish:
name: Publish with npm Trusted Publishing
if: >-
always() &&
(github.run_attempt == 1 || github.run_attempt == 2) &&
needs.live-smoke.result == 'success' &&
needs.verify.result == 'success'
needs:
- live-smoke
- verify
Expand All@@ -684,6 +735,16 @@ jobs:
deployments: read
id-token: write
steps:
- name: Reject an out-of-bounds publication attempt
env:
WORKFLOW_RUN_ATTEMPT: ${{ github.run_attempt }}
run: |
set -euo pipefail
if [[ "$WORKFLOW_RUN_ATTEMPT" != "1" &&
"$WORKFLOW_RUN_ATTEMPT" != "2" ]]; then
echo "Publication permits only the initial attempt and one failed-job replay." >&2
exit 1
fi
- name: Check out the verified release commit
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
Expand All@@ -704,12 +765,14 @@ jobs:
name: ${{ needs.verify.outputs.artifact-name }}
path: release-artifacts
- name: Reconfirm protected state immediately before publication
id: pre-publish
env:
CONTROL_COMMIT: ${{ needs.verify.outputs.control-commit }}
CONTROL_VALIDATOR: ${{ runner.temp }}/release-workflow-validation.mjs
ENVIRONMENT_FILE: ${{ runner.temp }}/npm-environment.json
EVENT_REF: ${{ github.ref }}
EVENT_SHA: ${{ github.sha }}
EXPECTED_NEXT_VERSION: ${{ needs.verify.outputs.expected-next-version }}
GH_TOKEN: ${{ github.token }}
POLICIES_FILE: ${{ runner.temp }}/npm-deployment-policies.json
RELEASE_COMMIT: ${{ needs.verify.outputs.release-commit }}
Expand All@@ -722,6 +785,7 @@ jobs:
RELEASE_PLEASE_RUNS: ${{ runner.temp }}/release-please-runs-before-publish.json
RELEASE_PLEASE_SNAPSHOT: ${{ needs.verify.outputs.release-please-snapshot }}
VERSION: ${{ needs.verify.outputs.version }}
WORKFLOW_RUN_ATTEMPT: ${{ github.run_attempt }}
WORKFLOW_SHA: ${{ github.workflow_sha }}
shell: bash
run: |
Expand DownExpand Up@@ -793,6 +857,10 @@ jobs:
exit 1
fi
fi
if [[ "$WORKFLOW_RUN_ATTEMPT" == "2" && -z "$exact_version" ]]; then
echo "The failed-job replay requires the exact registry version to exist." >&2
exit 1
fi
latest_version="$(npm view cometapi@latest version)"
next_version="$(npm view cometapi@next version)"
gh api --paginate --slurp \
Expand DownExpand Up@@ -885,21 +953,29 @@ jobs:
});
validateRegistryStateBeforePublish({
exactVersion: process.env.EXACT_VERSION || null,
expectedNextVersion: process.env.EXPECTED_NEXT_VERSION,
latestVersion: process.env.LATEST_VERSION,
nextVersion: process.env.NEXT_VERSION,
version,
});
EOF
if [[ -n "$exact_version" ]]; then
echo "expect-existing=true" >> "$GITHUB_OUTPUT"
else
echo "expect-existing=false" >> "$GITHUB_OUTPUT"
fi
- name: Publish the exact artifact with provenance
env:
DIST_TAG: ${{ needs.verify.outputs.dist-tag }}
EXPECT_EXISTING: ${{ steps.pre-publish.outputs.expect-existing }}
VERSION: ${{ needs.verify.outputs.version }}
run: bash scripts/publish-artifact.sh
- name: Verify the public registry artifact
env:
CONTROL_COMMIT: ${{ needs.verify.outputs.control-commit }}
CONTROL_VALIDATOR: ${{ runner.temp }}/release-workflow-validation.mjs
DIST_TAG: ${{ needs.verify.outputs.dist-tag }}
EXPECTED_NEXT_VERSION: ${{ needs.verify.outputs.expected-next-version }}
GH_TOKEN: ${{ github.token }}
WORKFLOW_REF: ${{ github.ref }}
VERSION: ${{ needs.verify.outputs.version }}
Expand DownExpand Up@@ -942,14 +1018,57 @@ jobs:
echo "Registry state did not converge for cometapi@${VERSION}, ${DIST_TAG}, integrity, and provenance." >&2
exit 1
fi
if [[ "$(npm view cometapi@next version)" != "0.1.0-alpha.3" ]]; then
if [[ "$(npm view cometapi@next version)" != "$EXPECTED_NEXT_VERSION" ]]; then
echo "The next dist-tag changed during publication." >&2
exit 1
fi

attestations_url="$(REGISTRY_DIST="$registry_dist" node -e 'process.stdout.write(JSON.parse(process.env.REGISTRY_DIST).attestations.url)')"
attestations_file="$RUNNER_TEMP/npm-attestations.json"
curl --fail --silent --show-error "$attestations_url" > "$attestations_file"
ATTESTATIONS_URL="$attestations_url" \
node --input-type=module <<'EOF'
import { pathToFileURL } from "node:url";

const { validateRegistryAttestationUrl } = await import(
pathToFileURL(process.env.CONTROL_VALIDATOR)
);
validateRegistryAttestationUrl({
url: process.env.ATTESTATIONS_URL,
version: process.env.VERSION,
});
EOF
ATTESTATIONS_FILE="$attestations_file" \
ATTESTATIONS_URL="$attestations_url" \
bash scripts/fetch-attestations.sh

post_exact_version="$(npm view "cometapi@${VERSION}" version)"
post_tagged_version="$(npm view "cometapi@${DIST_TAG}" version)"
post_next_version="$(npm view cometapi@next version)"
post_registry_dist="$(npm view "cometapi@${VERSION}" dist --json)"
ATTESTATIONS_URL="$attestations_url" \
EXACT_VERSION="$post_exact_version" \
EXPECTED_INTEGRITY="$local_integrity" \
NEXT_VERSION="$post_next_version" \
REGISTRY_DIST="$post_registry_dist" \
TAGGED_VERSION="$post_tagged_version" \
node --input-type=module <<'EOF'
import { pathToFileURL } from "node:url";

const { validatePublishedRegistryState } = await import(
pathToFileURL(process.env.CONTROL_VALIDATOR)
);
validatePublishedRegistryState({
attestationUrl: process.env.ATTESTATIONS_URL,
dist: JSON.parse(process.env.REGISTRY_DIST),
exactVersion: process.env.EXACT_VERSION,
expectedNextVersion: process.env.EXPECTED_NEXT_VERSION,
expectedIntegrity: process.env.EXPECTED_INTEGRITY,
nextVersion: process.env.NEXT_VERSION,
taggedVersion: process.env.TAGGED_VERSION,
version: process.env.VERSION,
});
EOF
registry_dist="$post_registry_dist"
local_sha512="$(node -e 'const {createHash}=require("node:crypto");const {readFileSync}=require("node:fs");process.stdout.write(createHash("sha512").update(readFileSync(process.argv[1])).digest("hex"))' "${tarballs[0]}")"
provenance_identity="$(ATTESTATIONS_FILE="$attestations_file" \
LOCAL_SHA512="$local_sha512" node --input-type=module <<'EOF'
Expand DownExpand Up@@ -1176,3 +1295,36 @@ jobs:
process.exitCode = 1;
});
EOF

result:
name: Enforce the bounded publication result
if: >-
always() &&
github.event_name == 'workflow_dispatch' &&
inputs.publish_operation == 'release'
needs:
- live-smoke
- publish
- verify
runs-on: ubuntu-latest
permissions: {}
steps:
- name: Reject skipped or out-of-bounds publication
env:
LIVE_SMOKE_RESULT: ${{ needs.live-smoke.result }}
PUBLISH_RESULT: ${{ needs.publish.result }}
VERIFY_RESULT: ${{ needs.verify.result }}
WORKFLOW_RUN_ATTEMPT: ${{ github.run_attempt }}
run: |
set -euo pipefail
if [[ "$WORKFLOW_RUN_ATTEMPT" != "1" &&
"$WORKFLOW_RUN_ATTEMPT" != "2" ]]; then
echo "Publication permits only the initial attempt and one failed-job replay." >&2
exit 1
fi
if [[ "$VERIFY_RESULT" != "success" ||
"$LIVE_SMOKE_RESULT" != "success" ||
"$PUBLISH_RESULT" != "success" ]]; then
echo "The bounded publication job set did not complete successfully." >&2
exit 1
fi
27 changes: 25 additions & 2 deletions AGENTS.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -283,9 +283,32 @@ repository root.
- Prepare or refresh a release PR with a new first-attempt manual Release Please
dispatch. Do not rerun a preparation dispatch. Release Please same-run Release
reconciliation is allowed only under the exact conditions in `RELEASING.md`.
- The successful Release Please `workflow_run` handoff is attempt-1-only and
must refuse to dispatch when any exact Publish child already exists for the
immutable tag and commit. Never rerun a handoff to create a second child run.
- Never bypass a failed stable release with a manual or auxiliary tag, a
branch-context publish, a temporary `main` npm Environment policy, reused
artifact or live evidence, an arbitrary rerun, or a different patch version.
branch-context publish, a temporary `main` npm Environment policy, cross-run
artifact or live-evidence reuse, an arbitrary rerun, or a different patch
version.
- If `npm publish` succeeds but post-publication verification fails, first read
the exact public version, `latest`, `next`, integrity, and every readable
attestation, signature, and provenance field. When the exact version is
absent, `latest` must equal the previous patch. When the exact version equals
the candidate, `latest` may equal the previous patch or candidate. Final state
requires both exact and `latest` to equal the candidate, while `next` must
equal the prerelease value frozen by the initial verification job. A transient
early attestation-endpoint `404` is the known registry convergence condition;
all transport failures receive one wall-clock-bounded wait, and exhaustion is
terminal. Do not rerun to extend it. Only when attestations are readable and
independent signature and provenance checks have passed may exactly one
`rerun failed jobs` on the same immutable-tag run resume a different failed
post-publication gate. Confirm that GitHub preserves the successful
exact-artifact and live-smoke jobs; rerun-all must fail before live API access.
The attempt-2 publish job requires the exact version to exist, matching
integrity, and a fresh Environment approval; it must skip `npm publish`.
Attempt 3 or later, an exact-version metadata `E404`, a second replay request,
non-convergence, or any identity, integrity, provenance, dist-tag, or
configuration mismatch is a hard stop.
- After registry verification, immediately restore
`RELEASE_PLEASE_ENABLED=false`, keep `LIVE_SMOKE_ENABLED=true`, and require the
npm Environment deployment-policy set to contain only `tag:v*`.
Expand Down
21 changes: 20 additions & 1 deletion ARCHITECTURE.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -139,7 +139,9 @@ the exact Release-producing attempt, SHA, tag, version, URL, repository,
workflow identity, run ID, and attempt in a schema-v2 exact-run artifact. An
unprivileged `workflow_run` handoff validates only that attempt's artifact,
exact tag, immutable Release, and current `main`, then dispatches `publish.yml`
with `ref=v<version>`. Only that tag-bound `workflow_dispatch` can reach fresh
with `ref=v<version>`. The handoff is attempt-1-only and refuses to dispatch if
an exact child run already exists for the tag and commit. Only that tag-bound
`workflow_dispatch` can reach fresh
artifact verification, bounded live smoke, the npm Environment, or OIDC. A
first-attempt manual run is explicitly release-inert and must succeed only after
independently validating one canonical action-created patch PR; its event cannot
Expand All@@ -153,6 +155,23 @@ Release Please and publication remain separate trust domains. Release Please
does not receive npm OIDC permission; `id-token: write` remains limited to the
protected publish job. Repository variables gate both flows, and reruns remain
fail-closed on exact tag, artifact, dist-tag, integrity, and provenance state.
Registry package metadata and its attestation endpoint can converge at
different times. Post-publication verification therefore gives attestation HTTP
failures one strict wall-clock-bounded retry window before failing; an early
`404` is the known convergence case, while persistent URL, authentication,
authorization, server, and transport failures remain terminal. The initial
verification job freezes the prerelease dist-tag value, and every later registry
gate requires it to remain unchanged rather than encoding a current package
version in durable workflow guidance. If publication succeeded before a later
gate failed, only one attempt-2 failed-job replay of that same immutable-tag run
may continue, and only after attestations, signature, and provenance are already
valid. Verification and bounded live smoke are attempt-1-only, so rerun-all
fails before live API access; attempt 3 or later also fails. The protected-state
step requires the exact version to exist on replay; `publish-artifact.sh` then
skips registry mutation only after its integrity matches the verified tarball
and refuses a later `E404` instead of republishing. The failed-job replay
preserves the same run's successful artifact and bounded live-smoke jobs rather
than borrowing evidence from another run.
Manual preparation rejects attempt 2 or later; restart uses a new dispatch with
Release creation disabled. A `push` rerun is bounded to the same run ID, SHA,
candidate, and final-head review. It may retry while the tag and Release remain
Expand Down
Loading