Dashboard › publish › Distillation
4dfcd539-ebef-4eb0-9525-b112377075f8["lore_tm_v1_MRixErEU3OzmjwHkdNAZAPEfPBv8GhPpErVZPcGnvVc","lore_tm_v1_IRY1XEYgOsX_LzquBj8KhBoIywMAiSEPKw_Yn0-8bg0","lore_tm_v1_0wJCXsPXjF9LMWaqExrBQKKveGVw37h63VDfnarUy8U","lore_tm_v1_RdAsAediPp4al47qeXc0QsAMCrhyk-UhqUtxZOve3n8","lore_tm_v1_BXlc5Ilk5NHLQZkFT_yDva0cx3lSb4vaODbWbRGZqQA","lore_tm_v1_pC370WHDJsfOjRtdB3fB11kXMEBFwCWQe-3Czh4quQQ","lore_tm_v1_6kKfsRZADNyNwEXlYmecFWIZP76wLJHN7wg92TDkb5s"]
π΄ (10:07) /home/byk/Code/getsentry/publish/package.json defines package publish version 0.0.1, private, with Node 24.0.0 and Yarn 1.22.22 via Volta. Scripts are test: vitest run, test:watch: vitest, lint: eslint src .github --ignore-pattern '!.github', and prettier: prettier --write src. Dependencies are @actions/core ^2.0.0, @actions/github ^7.0.0, and @sentry/node ^10.0.0; dev dependencies include Vitest ^4.1.0, ESLint ^8.9.0, eslint-plugin-yml ^0.13.0, and Prettier ^2.2.1; undici is resolved to ^6.23.0.
π΄ (10:07) The live production GitHub environment for getsentry/publish has environment ID 9871907870, can_admins_bypass: false, one branch_policy protection rule with ID 42720759, and custom branch policies enabled while protected_branches is false. Repository defaults are default_workflow_permissions: write and can_approve_pull_request_reviews: true.
π΄ (10:08) The live production environment has exactly 1 deployment branch policy: branch main, policy ID 38207111.
π΄ (10:08) .github/workflows/auto-approve.yml currently runs only for opened issues when github.actor is sentry-release-bot[bot] or getsantry[bot] and the title starts with publish: . Its auto-approve job uses environment: production, checks out only auto-approve-repos.txt, creates a sentry-internal-app token, runs src/publish/record-auto-approval-attestation.js, posts the resulting attestation using ${{ github.token }}, and adds accepted with the app token only when the title-derived REPO exactly matches a line in auto-approve-repos.txt.
π΄ (10:08) .github/workflows/auto-approve.yml pins actions/checkout to 11d5960a326750d5838078e36cf38b85af677262 and actions/create-github-app-token to bcd2ba49218906704ab6c1aa796996da409d3eb1.
π΄ (10:08) src/modules/approval-attestation.js defines three base64url comment formats: <!-- publish-approval β¦ -->, <!-- publish-auto-approval β¦ -->, and <!-- publish-ci-ready β¦ -->. requestDigest() computes SHA-256 over JSON containing exact body, derived dryRun, and title; approval proof binds actor, stringified eventId, digest, and title; auto-approval proof binds autoApprover, digest, and title; CI-ready proof binds accepted actor/event ID, ciReadyActor, digest, and title.
π΄ (10:08) src/modules/approval-attestation.js implements arbitrary-length decimal issue-event ordering in compareEventIds(left, right) by string-normalizing leading zeros, rejecting non-decimal values, comparing length, then lexicographically comparing equal-length values. currentLabeledEvent(events, labelName) accepts numeric or string event IDs, selects the greatest valid labeled-event ID, and returns { actor, eventId: String(event.id) }; malformed IDs cause a fail-closed null.
π΄ (10:08) src/modules/approval-attestation.js validates attestations only from the required attestationAuthor. hasApprovalAttestation() requires exact actor, event ID, request digest, and title; hasAutoApprovalAttestation() requires exact auto-approver, digest, and title; hasCiReadyAttestation() requires exact accepted actor/event ID, CI-ready actor, digest, and title.
π΄ (10:08) src/publish/validate-approval-attestation.js fetches the live issue, all issue events, and all comments from GitHub using paginated requests of per_page=100 and API version 2026-03-10. validateApprovalAttestation() requires the issue to remain open, retain the exact expected title and accepted label, have a current accepted event matching optional expectedAcceptedEvent, and have a matching approval attestation. With requireCiReadyAttestation: true, it additionally requires the live ci-ready label, current CI-ready event, and matching CI-ready attestation.
π΄ (10:08) .github/workflows/ci-poller.yml runs on cron */5 * * * *, repository_dispatch type ci-poller, and successful completion of workflow Run CI Status Poller. The protected check-ci job uses environment: production, concurrency group ci-status-poller with cancel-in-progress: false, and runs only for pending state, repository dispatch, or a successful default-branch workflow_run.
π΄ (10:08) Always run trusted code. .github/workflows/ci-poller.yml enforces this by checking out ${{ github.event.repository.default_branch }} with persist-credentials: false before obtaining app credentials; actions/checkout is pinned to d23441a48e516b6c34aea4fa41551a30e30af803.
π΄ (10:08) Never move a release to ci-ready after it changes; a renamed, mutated, or re-approved release never reaches ci-ready. The CI poller captures the current accepted actor/event before checking CI, validates that exact binding initially, revalidates it after CI completes, validates again while recording CI-ready proof, and validates once more immediately before applying ci-ready.
π΄ (10:08) .github/workflows/ci-poller.yml polls only open issues carrying both ci-pending and accepted, with a maximum query size of 200. Failure to capture or validate approval removes both labels and comments that the user must re-add accepted after resolving the issue.
π΄ (10:08) The CI poller parses titles with parsePublishTitle() from src/modules/details-from-context.js, extracts a 40-character lowercase hexadecimal commit SHA from the issue bodyβs /commit/{SHA}/checks link, resolves the release branch from the original commitβs first check suite, and then resolves branch HEAD. If branch or HEAD resolution fails, it falls back to the issue SHA; if branch HEAD changed, it rewrites the issue-body SHA before continuing.
π΄ (10:08) CI readiness in .github/workflows/ci-poller.yml requires at least 1 check run or commit status, no pending check runs, no unsuccessful check conclusions, and either combined commit status success or zero reported commit statuses. Check conclusions success, neutral, and skipped pass; other completed conclusions are unsuccessful. Check runs are fetched with pagination.
π΄ (10:08) The CI poller writes CI-ready proof to a mktemp output file, reads the file as literal text via ci_ready_attestation="$(<"$attestation_output")", requires the exact prefix ci_ready_attestation=, strips only that prefix, and posts the proof as github-actions[bot]; it does not source the output file.
π΄ (10:08) On successful CI, .github/workflows/ci-poller.yml removes ci-pending and adds ci-ready, then comments CI checks passed for ${repo}@${version}. Publishing is starting now. On failed completed check runs, it removes ci-pending and accepted, adds ci-failed, lists failed check names, and requests renewed approval after repair. A failed combined commit status with otherwise passing check runs follows the same label transition and lists failed status contexts with links when available.
π΄ (10:08) The CI poller synchronizes repository variable CI_POLLER_HAS_PENDING after every run, including failures: it sets the variable to false when no open issue retains both ci-pending and accepted, otherwise to true. It uses a separately generated poller app token from CI_POLLER_APP_CLIENT_ID and CI_POLLER_APP_PRIVATE_KEY.
π΄ (10:08) .github/workflows/ci-poller.yml self-dispatches through repository_dispatch while work remains, validates incoming attempt as an integer from 0 through 59, increments it, and stops self-dispatch at 60 attempts. The intended fast-loop interval is approximately 30β60 seconds through GitHub Actions startup latency; cron remains the fallback because */5 scheduling can drift to 30β40 minutes under load.