Dashboard › publish › Distillation
ff7c32c5-ea34-41c7-a36c-6e1670eedd43["lore_tm_v1_e8LGL1Ohm46VmszFTM_wbgjKhwlb3aKfFTP-v_O-5vk","lore_tm_v1_xvzaT2qS6nmUZTi1rRsNrSE0DYSVxIfsnzggvsTjVgg","lore_tm_v1_e4lRUupqK74q9VnEVaa62Z58g_PBvI-0vDTkl6EtVtM","lore_tm_v1_IReLrAoWnQalwny5rblhgd8V_orbJowJdxQ5TUivdSI","lore_tm_v1_iYxeZTuDdSAdmdrQl3WnvJB8yHVFQ4fvSNxbJIc5Vmw","lore_tm_v1_QF47OGtR5DUFoPhh9kr6tf9UOtBm0I5ATvyHdmxZeNI","lore_tm_v1_kYKLM6xoF_al8s6Yg76bINMHmUAwlG1Q1gSR9geGd9I","lore_tm_v1_mxNZ_wOzq2DDyuzd32AzL1MDrMqKJdEnjN5Ybi2NPrE","lore_tm_v1_-wCvkTrJEe8ACsAKWzHsM1ZsfSsg5V92ASUD3cJXo88","lore_tm_v1_nP9sfLlJCbfH1V_WYstfBLvXOyHdCqGW-JqfHO6oOMc","lore_tm_v1_NYyt5eMu2HEZ9AhhclgGcXSxitXFOwwlILlsYe2qhbI","lore_tm_v1_8a6TJhX868pjQ88imZhLQGpWCLbkcpp6RpBkkSuM2nQ","lore_tm_v1_o_URY8KzORpEH-sG7gj4i6_0jr9zhD3Trgb7cAzKKts"]
Date: Sep 9, 2026
src/publish/__tests__/workflow-action-pinning.js enumerates every .github/workflows/*.yml file and verifies all actions/* references are pinned to lowercase 40-character commit SHAs.src/publish/__tests__/workflow-action-pinning.js verifies workflow_dispatch: is absent from every workflow except .github/workflows/ci-poller-dispatch.yml; the relay must contain workflow_dispatch:, permissions: {}, and environment: production, and must not contain secrets..src/publish/__tests__/workflow-action-pinning.js defines secret-bearing jobs that must use environment: production: .github/workflows/auto-approve.yml job auto-approve; .github/workflows/ci-poller.yml job check-ci; .github/workflows/cocoapods-keepalive.yml job keepalive; and .github/workflows/publish.yml jobs waiting-for-ci and publish.src/publish/authorize-approval.js:44 sends X-GitHub-Api-Version: "2026-03-10", matching src/publish/validate-approval-attestation.js:13.actions/checkout@11d5960a326750d5838078e36cf38b85af677262 in .github/workflows/auto-approve.yml; actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 in .github/workflows/publish.yml, .github/workflows/test.yml, and .github/workflows/ci-poller.yml; actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 in .github/workflows/auto-approve.yml, .github/workflows/publish.yml, and .github/workflows/ci-poller.yml; actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 in .github/workflows/publish.yml and .github/workflows/test.yml; and actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 in .github/workflows/test.yml..github/workflows/publish.yml uses Craft image docker://getsentry/craft@sha256:9a4a5d5efa44a00c2215078ead39800d4aaa5a97908b94f45a64d7d506d6e14b..github/workflows/cocoapods-keepalive.yml runs CocoaPods Token Keep-Alive daily at midnight UTC via cron "0 0 * * *" because CocoaPods sessions currently expire after 3 days of inactivity; job keepalive runs on macos-15 under environment: production, uses COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }}, installs CocoaPods with gem install cocoapods, prints pod --version, and refreshes the session using pod trunk me > /dev/null 2>&1.src/publish/update-issue.js must parse PUBLISH_ARGS only when present, catch malformed JSON and skip target restoration, and always call updateIssue() so the accepted label is removed even when release-input parsing fails.ci-ready label after CI passes, even if that label is already present; an idempotence check must not skip relabeling because re-adding emits a fresh label event that resolves the waiting-for-ci race and starts publishing, whereas skipping it can strand accepted releases.workflow_dispatch wherever operators need manual recovery. For privileged recovery, user prefers a no-secret relay protected by a main-only environment plus a successful default-branch workflow_run handoff over direct secret-bearing dispatch, because the dispatcher selects workflow YAML before jobs, guards, or checkout execute; workflows should be identified by checked-in filenames such as ci-poller.yml, not placeholders or numeric IDs.github-actions[bot] before sentry-internal-app[bot] adds accepted; generic app identity is insufficient.accepted event never to reach ci-ready; the captured event and request digest must be validated before CI, after CI, while recording CI-ready proof, immediately before adding ci-ready, and before Craft. User chose repeated live fencing over one initial validation because issue state can change during CI or setup.production environment; repository secrets must never be exposed to workflows dispatchable from arbitrary refs; manual dispatch must use an unprivileged relay followed by successful default-branch execution; approval state must be revalidated immediately before transitions such as ci-ready; regression tests, the full test suite, and lint checks must run after changes.accepted, moving an issue to ci-ready, or publishing, validate the live canonical issue, current label events, actor provenance, and matching attestations; reject renamed, closed, changed, re-approved, self-approved, or unverifiable requests. The CI poller is solely responsible for adding ci-ready. Recovery must retain workflow_dispatch without running arbitrary dispatched-ref code or exposing repository secrets; use trusted checkout refs, protected environments, least-privilege permissions, immutable action/image revisions, and final revalidation immediately before credential-bearing publication.write, maintain, or admin and cannot self-approve; automated bot requests must not use collaborator permissions or trust the Internal App label actor alone, and instead require the exact parsed repository/release path in auto-approve-repos.txt plus a trusted attestation bound to the live request. Missing, malformed, stale, mismatched, or changed state must be unauthorized.package.json identifies private package publish version 0.0.1, repository git@github.com:getsentry/publish.git, Apache-2.0 license, Volta Node 24.0.0 and Yarn 1.22.22, and resolution undici: ^6.23.0. Scripts are test: vitest run, test:watch: vitest, lint: eslint src .github --ignore-pattern '!.github', and prettier: prettier --write src; dependencies include @actions/core ^2.0.0, @actions/github ^7.0.0, and @sentry/node ^10.0.0, with vitest ^4.1.0.src/modules/details-from-context.js parses publish titles with PUBLISH_TITLE_REGEX = /^publish: (?:getsentry\/)?(?<repo>[^/@]+)(?<path>\/[\w./-]+)?@(?<version>[\w.+-]+)$/; detailsFromContext() rejects missing issue context or invalid titles, emits dry_run: "1" only for a dry-run label, prefixes parsed paths with ., parses Merge target: ..., and extracts checked targets from the issue body.src/modules/update-issue.js has updateIssue() concurrently restore target state only when inputs?.version exists and remove label accepted unconditionally. updateTargets() reads ${process.env.GITHUB_WORKSPACE}/__repo__/.craft-publish-${version}.json only if it exists, transforms the live issue body from craftState.published, preserves declared target ordering/state, appends unlisted targets, and updates the issue body.src/publish/update-issue.js conditionally parses process.env.PUBLISH_ARGS; malformed JSON logs Could not parse publish inputs; skipping target update, after which main() still calls updateIssue({ context, octokit, inputs }). Direct-execution errors are logged and set process.exitCode = 1.src/modules/__tests__/update-issue.js verifies the Craft state path ./__repo__/.craft-publish-21.3.1.json, restoration of target states including - [x] github, - [ ] pypi, appended - [x] lol, and - [ ] hey, no issue-body update when the state file is absent, and unconditional removal of accepted from issue "211".src/modules/__tests__/update-issue.js specifically verifies that updateIssue() with inputs: undefined performs no state-file lookup, issue retrieval, or issue-body update, but still calls removeLabel({ owner: "getsentry", repo: "publish", issue_number: "211", name: "accepted" }).src/modules/process-end-state.js posts a workflow-result issue comment, closes the issue only on status === "success", and reports a Sentry session. Status mappings are: failure → message Release failed: ${repo}, severity error, session crashed; cancelled → Release cancelled: ${repo}, severity warn, session crashed; success → Release succeeded: ${repo}, severity info, session ok; unknown statuses throw Unknown status: '${status}'.