Dashboard › publish › Distillation
29e6dc68-5a01-460f-bf92-1ad18238482b["lore_tm_v1_WHhdJVTyfq-Hb6bD-2NzhgNAApyGaFmSgqPlifEpRWc","lore_tm_v1_i8vrpJubWBWF6q0rhNuGN5Be9ioPo34MzlxmN2i3t-s","lore_tm_v1_gF9PkXKz92svhRWueos3cWdVdI-FPbynQMDZLuNbkgg","lore_tm_v1_vnw6vVEZBqrea_rdYgtLUnaRC7pMCqogDxwZPUrI804","lore_tm_v1_eGFt2tclumfhGwNUy5qwT_4Vpt9WuljoxidTF8nyTdk","lore_tm_v1_tp62VLUpz5MWLUCGynsesClZogI83pwbIEilzFRdS48"]
π΄ (10:11) Repository /home/byk/Code/getsentry/publish was on main...origin/main [behind 1] with 21 tracked files changed: 499 insertions and 117 deletions.
π΄ (10:11) Modified tracked files were .github/workflows/auto-approve.yml, .github/workflows/ci-poller.yml, .github/workflows/cocoapods-keepalive.yml, .github/workflows/publish.yml, .github/workflows/test.yml, AGENTS.md, README.md, docs/key_rotation_vault_changes.md, docs/rfc.md, src/libs/__tests__/github.js, src/libs/github.js, src/modules/__tests__/details-from-context.js, src/modules/__tests__/update-issue.js, src/modules/details-from-context.js, src/modules/post-workflow-details.js, src/modules/process-end-state.js, src/modules/update-issue.js, src/publish/inputs.js, src/publish/post-result.js, src/publish/post-workflow-details.js, and src/publish/update-issue.js.
π΄ (10:11) Untracked files/directories were .github/workflows/ci-poller-dispatch.yml, .lore.md, src/modules/__tests__/approval-attestation.js, src/modules/__tests__/approval-authorizer.js, src/modules/approval-attestation.js, src/modules/approval-authorizer.js, src/publish/__tests__/, src/publish/authorize-approval.js, src/publish/current-accepted-event.js, src/publish/record-auto-approval-attestation.js, src/publish/record-ci-ready-attestation.js, and src/publish/validate-approval-attestation.js.
π΄ (10:12) .github/workflows/ contains exactly 6 workflows: auto-approve.yml, ci-poller-dispatch.yml, ci-poller.yml, cocoapods-keepalive.yml, publish.yml, and test.yml.
π΄ (10:12) In .github/workflows/publish.yml, publish concurrency groups are shared by issues for the same repo@version.
π΄ (10:12) The publish workflow has a waiting-for-ci job and a later publish path gated on the presence of the ci-ready label via contains(github.event.issue.labels.*.name, 'ci-ready').
π΄ (10:12) The CI poller always adds the ci-ready label after CI passes; the workflow relies on this event to prevent the publish job from racing with waiting-for-ci on the same event.
π΄ (10:12) The workflowβs label operation includes --remove-label "ci-ready"; label additions/removals are documented as idempotent.
π΄ (10:12) The workflow retrieves publish code, obtains GitHub App tokens using SENTRY_INTERNAL_APP_PRIVATE_KEY and SENTRY_RELEASE_BOT_CLIENT_ID, passes APPROVAL_ACTOR: ${{ github.actor }}, executes node src/publish/validate-approval-attestation.js, and uses approval authorization/validation outcomes as guards.
π΄ (10:12) The publish job installs dependencies with yarn install --cwd ".__publish__" and executes node .__publish__/src/publish/post-workflow-details.js.
π΄ (10:12) The publish workflow includes a special condition for repo == 'sentry-migr8' && merge_target == 'tmp-merge-target'.
π΄ (10:12) The publish workflow runs Craft from docker://getsentry/craft@sha256:9a4a5d5efa44a00c2215078ead39800d4aaa5a97908b94f45a64d7d506d6e14b, computes a container-visible working directory based on CRAFT_PUBLISH_PATH, writes a state file, and uses GIT_COMMITTER_NAME: sentry-release-bot[bot].
π΄ (10:12) src/publish/record-auto-approval-attestation.js imports fs, createAutoApprovalAttestation from ../modules/approval-attestation.js, and getIssue from ./validate-approval-attestation.js.
π΄ (10:12) recordAutoApprovalAttestation({ autoApprover, getIssue, issueNumber, issueTitle, repository }) fetches the live issue and rejects it with The automated approval request changed before approval unless the issue remains open, its title exactly matches issueTitle, and issue.user?.login case-insensitively matches autoApprover.
π΄ (10:12) On successful validation, recordAutoApprovalAttestation() returns createAutoApprovalAttestation({ autoApprover, issue }).
π΄ (10:12) src/publish/record-auto-approval-attestation.js requires all 6 environment variables GITHUB_OUTPUT, APPROVAL_TOKEN, APPROVAL_ISSUE_NUMBER, APPROVAL_ISSUE_REPOSITORY, APPROVAL_ISSUE_TITLE, and AUTO_APPROVER; a missing value throws No "${name}" environment variable found.
π΄ (10:12) record-auto-approval-attestation.js appends auto_approval_attestation=${attestation}\n to process.env.GITHUB_OUTPUT; direct execution catches errors, prints them with console.error(error), and sets process.exitCode = 1. It exports { main, recordAutoApprovalAttestation }.
π΄ (10:12) src/publish/authorize-approval.js imports createApprovalAttestation, currentAcceptedEvent, and hasAutoApprovalAttestation from ../modules/approval-attestation.js, plus AUTO_APPROVAL_LABELER, AUTO_APPROVERS, authorizeApproval, and isAutoApprovedRepository from ../modules/approval-authorizer.js.
π΄ (10:12) getAutoApprovedRepositories() reads auto-approve-repos.txt as UTF-8, splits on /\r?\n/, removes blank entries, and returns the repository names as a Set.
π΄ (10:12) getGitHubResponse(path) calls fetch(\https://api.github.com/${path}`)with headersAccept: "application/vnd.github+json", Authorization: `Bearer ${process.env.APPROVAL_TOKEN}`, and "X-GitHub-Api-Version": "2026-03-10"`.
π΄ (10:12) getPermission({ owner, repository, username }) requests repos/${encodeURIComponent(owner)}/${encodeURIComponent(repository)}/collaborators/${encodeURIComponent(username)}/permission; a non-OK response throws Could not retrieve ${username}'s permission for ${owner}/${repository}: GitHub returned ${response.status}.
π΄ (10:12) getIssue({ repository, issueNumber }) requests repos/${repository}/issues/${issueNumber}; a non-OK response throws Could not retrieve issue #${issueNumber}: GitHub returned ${response.status}.
π΄ (10:12) getIssueEvents({ repository, issueNumber }) paginates repos/${repository}/issues/${issueNumber}/events?per_page=100&page=${page}, collecting pages until one contains fewer than 100 events; a non-OK response throws Could not retrieve events for issue #${issueNumber}: GitHub returned ${response.status}.
π΄ (10:12) getIssueComments({ repository, issueNumber }) paginates repos/${repository}/issues/${issueNumber}/comments?per_page=100&page=${page}, collecting pages until one contains fewer than 100 comments; a non-OK response throws Could not retrieve comments for issue #${issueNumber}: GitHub returned ${response.status}.
π΄ (10:12) authorize-approval.js requires GITHUB_OUTPUT, APPROVAL_TOKEN, APPROVAL_ISSUE_NUMBER, and APPROVAL_ISSUE_REPOSITORY; each missing variable causes a corresponding No "β¦" environment variable found error.
π΄ (10:12) authorize-approval.js obtains actor from APPROVAL_ACTOR, issueTitle from APPROVAL_ISSUE_TITLE, and the auto-approved repository set from getAutoApprovedRepositories().
π΄ (10:12) When actor === AUTO_APPROVAL_LABELER, authorize-approval.js concurrently fetches the live issue, all issue events, and all issue comments, then computes requester = liveIssue.user?.login and acceptedEvent = currentAcceptedEvent(events).
π΄ (10:12) The AUTO_APPROVAL_LABELER path authorizes only if all conditions hold: the live issue is open; its title equals issueTitle; the requester is a string; AUTO_APPROVERS.has(requester); isAutoApprovedRepository({ actor: requester, autoApprovedRepositories, issueTitle }) succeeds; acceptedEvent?.actor === actor; and hasAutoApprovalAttestation({ autoApprover: requester, attestationAuthor: "github-actions[bot]", comments, issue: liveIssue }) succeeds.
π΄ (10:12) For actors other than AUTO_APPROVAL_LABELER, authorize-approval.js calls authorizeApproval({ actor, issueTitle, getPermission, autoApprovedRepositories }) and extracts its authorized result.
π΄ (10:12) If initial authorization fails, authorize-approval.js appends authorized=false\n to GITHUB_OUTPUT and returns.
π΄ (10:12) After initial authorization, if the live issue and accepted event were not already fetched, authorize-approval.js concurrently calls getIssue() and getIssueEvents(), then derives the event with currentAcceptedEvent(events).
π΄ (10:12) Final approval validation rejects by appending authorized=false\n if the issue is not open, the live title differs from APPROVAL_ISSUE_TITLE, the requester is not a string, a non-AUTO_APPROVAL_LABELER actor is the requester under a case-insensitive comparison, no accepted event exists, or event.actor !== actor.
π΄ (10:12) On successful final validation, authorize-approval.js creates an attestation with createApprovalAttestation({ actor: event.actor, eventId: event.eventId, issue }) and appends authorized=true\napproval_attestation=${attestation}\n to GITHUB_OUTPUT.
π΄ (10:12) Direct execution of authorize-approval.js catches errors, prints them with console.error(error), and sets process.exitCode = 1; the module exports getAutoApprovedRepositories, getGitHubResponse, getIssue, getIssueComments, getIssueEvents, getPermission, and main.