Dashboard › publish › Distillation
bda90a83-4a81-47c6-aa06-eea238cc0a8d["lore_tm_v1_x_1JJjuAF23D-CJ7IAiguTtg68mhJuBGbkc8EW5kx-g","lore_tm_v1_5Bjjw54VvJB7HNJ_nzQn5Hv8T4Xm_l7q8FFezENuMxU","lore_tm_v1_acGxzKb4UELtLRJJlREF4HkJv-5kWfmW52qk5xwIDy4","lore_tm_v1_dqxUWYmjl9XXMOgqAhO2lHDweyAQsFAjGYT-JgzC2Dw","lore_tm_v1_MJ971K5BrmI75B9vnJJnL2MO0lnJanPHc23wvg246gE","lore_tm_v1_r0ir6Dj6PyqEfAyMFvfVMmR_DWrSPmqgkGt9yfbMXSA","lore_tm_v1_jsuCDMp59gyQ8cHQu5m_-xSnDjyCyAyN7xRT7ab_jcQ","lore_tm_v1_RfivDXRWHg-4XdFRq1ZZ56gMNdWJ6LnmcjXvX52AsJM","lore_tm_v1_KkX7FWqE3vOcCKcVrW2EMxOo165vcN2xiM7zN8QrO6U","lore_tm_v1_YBhBTFN9BLl8x4RMAqRKBxgA3iROVQ-tmEo6JcXjYdU","lore_tm_v1_aLJMFOtxSTnAz2Krona7tYZcIa6UWPemJNX4wHT5utA","lore_tm_v1_1qmZr7ao721pdHkCguLCoKMMvbzVbGH1vgDmvOhzG-E"]
Date: Aug 28, 2026
src/modules/approval-authorizer.js defines ALLOWED_ROLE_NAMES = new Set(["write", "maintain", "admin"]) and AUTO_APPROVER = "getsantry[bot]".authorizeApproval({ actor, issueTitle, getPermission, autoApprovedRepositories = new Set() }) parses issueTitle with parsePublishTitle(); malformed titles return { authorized: false, repository: null } without a GitHub lookup.authorizeApproval() derives repository = \getsentry/${title.repo}`andreleasePath = `${repository}${title.path || ""}`. getsantry[bot]is authorized only whenautoApprovedRepositoriescontains that exactreleasePath; otherwise it is denied without calling getPermission`.authorizeApproval() calls getPermission({ owner: "getsentry", repository: title.repo, username: actor }) and authorizes only GitHub roles write, maintain, or admin; permission-lookup failures propagate.src/publish/authorize-approval.js getAutoApprovedRepositories() reads auto-approve-repos.txt as UTF-8, splits on /\r?\n/, filters blank lines, and returns a Set of exact release paths.src/publish/authorize-approval.js sends GitHub REST requests through getGitHubResponse(path) to https://api.github.com/${path} with headers Accept: "application/vnd.github+json", Authorization: Bearer ${process.env.APPROVAL_TOKEN}, and "X-GitHub-Api-Version": "2026-03-10".getPermission() requests repos/{owner}/{repository}/collaborators/{username}/permission, URL-encoding all three components; a non-OK response throws Could not retrieve ${username}'s permission for ${owner}/${repository}: GitHub returned ${response.status}.getIssue({ repository, issueNumber }) requests repos/${repository}/issues/${issueNumber} and throws Could not retrieve issue #${issueNumber}: GitHub returned ${response.status} on non-OK responses.getIssueEvents({ repository, issueNumber }) paginates repos/${repository}/issues/${issueNumber}/events?per_page=100&page=${page}, accumulating event records until a page contains fewer than 100; non-OK responses throw Could not retrieve events for issue #${issueNumber}: GitHub returned ${response.status}.authorize-approval.js main() requires GITHUB_OUTPUT, APPROVAL_TOKEN, APPROVAL_ISSUE_NUMBER, and APPROVAL_ISSUE_REPOSITORY; missing variables throw No "{NAME}" environment variable found.authorizeApproval() using APPROVAL_ACTOR, APPROVAL_ISSUE_TITLE, getPermission, and the auto-approve-repos.txt allowlist. If unauthorized, it appends authorized=false\n to GITHUB_OUTPUT.authorize-approval.js concurrently fetches the current approval issue and all its events; it fails closed by appending authorized=false\n unless fetched issue title exactly equals APPROVAL_ISSUE_TITLE, a current accepted event exists, and that event actor exactly equals APPROVAL_ACTOR.authorize-approval.js creates an approval attestation from the current accepted event actor/event ID and fetched title, then appends authorized=true\napproval_attestation=${attestation}\n to GITHUB_OUTPUT. When executed directly, errors are written with console.error() and set process.exitCode = 1.src/publish/validate-approval-attestation.js uses the same GitHub API base URL, APPROVAL_TOKEN Bearer authorization, Accept: "application/vnd.github+json", and API version "2026-03-10".validate-approval-attestation.js getAllPages({ repository, issueNumber, resource }) paginates repos/${repository}/issues/${issueNumber}/${resource}?per_page=100&page=${page} until a page has fewer than 100 records; failed pages throw Could not retrieve ${resource} for issue #${issueNumber}: GitHub returned ${response.status}.validateApprovalAttestation({ attestationAuthor, issueNumber, issueTitle, repository }) concurrently fetches issue metadata, all events, and all comments; it returns true only if the fetched title exactly equals issueTitle, current labels include accepted, currentAcceptedEvent(events) is non-null, and hasApprovalAttestation() finds a matching trusted attestation.APPROVAL_TOKEN, APPROVAL_ISSUE_NUMBER, APPROVAL_ISSUE_REPOSITORY, APPROVAL_ISSUE_TITLE, and APPROVAL_ATTESTATION_AUTHOR; invalid validation throws The current accepted label has no matching approval attestation. Direct execution logs errors and sets process.exitCode = 1.src/modules/__tests__/approval-attestation.js covers: trusted github-actions[bot] attestations matching the latest accepted event ID "200"; numeric GitHub event ID 29503999078 converting to string "29503999078"; rejection when author is untrusted, attested event/title are stale ("200" versus "201" and py@1.2.3 versus py@1.2.4), or encoded content is malformed (<!-- publish-approval not-base64 -->).src/modules/__tests__/approval-authorizer.js tests exact allowlisted auto-approval for getsantry[bot] and new Set(["getsentry/sentry-javascript"]), including that no target-repository permission query occurs.getsantry[bot] for non-allowlisted getsentry/sentry-javascript when allowlist contains getsentry/sentry-python, also without querying permissions.write, maintain, and admin; reject none, read, triage, "Elevated Bot", and undefined; support unqualified title publish: sentry-javascript/packages/core@10.0.0 by looking up repository sentry-javascript; and reject malformed titles publish: @1.0.0 and publish: getsentry/sentry-javascript without GitHub queries.src/publish/__tests__/authorize-approval.js verifies getAutoApprovedRepositories() preserves exact allowlist entries getsentry/sentry-javascript and getsentry/objectstore/clients.write lookup for actor contractor and title publish: getsentry/sentry-javascript@10.0.0 requests https://api.github.com/repos/getsentry/sentry-javascript/collaborators/contractor/permission and outputs an attestation matching /^authorized=true\napproval_attestation=<!-- publish-approval .+ -->\n$/.Could not retrieve contractor's permission for getsentry/sentry-javascript: GitHub returned 404, with neither output written nor console.error() called.src/publish/__tests__/validate-approval-attestation.js verifies acceptance for a trusted github-actions[bot] attestation matching current accepted event "100" and title publish: getsentry/sentry-javascript@10.0.0.publish: getsentry/sentry-javascript@10.0.0 to publish: getsentry/sentry-python@10.0.0; the current issue labels no longer include accepted; or accepted is re-added under newer event ID "200" after the attested event ID "100"..github/workflows/publish.yml, .github/workflows/ci-poller.yml, .github/workflows/auto-approve.yml, .github/workflows/test.yml, and .github/workflows/cocoapods-keepalive.yml..github/workflows/auto-approve.yml is named auto-approve non-sdks, triggers on newly opened issues, grants contents: read, and runs auto-approve on ubuntu-latest only when github.actor is sentry-release-bot[bot] or getsantry[bot] and issue title starts with publish: .auto-approve-repos.txt via actions/checkout@v4 with sparse-checkout-cone-mode: false; it creates an app token using actions/create-github-app-token@v3, vars.SENTRY_INTERNAL_APP_ID, and secret SENTRY_INTERNAL_APP_PRIVATE_KEY.REPO using sed -n 's/^publish: \(.*\)@.*/\1/p'; if nonempty and an exact grep -qxF match in auto-approve-repos.txt, it runs gh issue edit "$ISSUE_URL" --add-label accepted using GH_TOKEN from the generated app token..github/workflows/test.yml runs on pushes to main and all pull requests, with contents: read; its unit-test job uses ubuntu-latest, Bash, actions/checkout@v6, actions/setup-node@v6 with Node 24, and actions/cache@v4 for node_modules keyed by ${{ runner.os }}-node_modules-${{ hashFiles('package.json', 'yarn.lock') }}.yarn install --frozen-lockfile only on a cache miss, then runs yarn test.src/modules/details-from-context.js defines PUBLISH_TITLE_REGEX = /^publish: (?:getsentry\/)?(?<repo>[^/@]+)(?<path>\/[\w./-]+)?@(?<version>[\w.+-]+)$/; parsePublishTitle(title) returns regex named groups or null.detailsFromContext({ context }) throws Issue context is not defined without context.payload.issue, and throws Invalid publish issue title: ${context.payload.issue.title} if parsePublishTitle() fails.detailsFromContext() returns parsed title fields plus dry_run ("1" when issue labels include dry-run, otherwise ""), path ("." + (titleMatch.path || "")), merge_target, and targets.detailsFromContext() parses merge_target from issue-body line Merge target: (?<merge_target>[\w.\-/]+); unmatched input yields "".TARGETS_SECTION_PARSER_REGEX = /^(?!### Targets$\s)(?: *- \[[ xX]\] \S+\s*$(?:\r?\n)?)+/m, TARGETS_PARSER_REGEX = /^\s*- \[[ x]\] (\S+)/gim, and CHECKED_TARGETS_PARSER_REGEX = /^\s*- \[x\] (\S+)/gim; matched target sections produce targets from checked ([x]) entries only.