Dashboard › publish › Distillation
ba9eaaf3-3e79-4579-a017-c038f4afeb70["lore_tm_v1_JW1fZ7tii1kJPoVJ81GwjyBiCtfJwcW9yF9Ya1i0Ge4","lore_tm_v1_EGMXXkT4ficVWUG663_oA9WAQNDcmqBnooDCmoCc1k4","lore_tm_v1_8MsOv85YQg1EaLYTzOcPYIxvq4phz5Hu9guEpxmV1SU","lore_tm_v1_GEjar3lhjrQMjdEoV6QGb_9lWRCStiq4fqmIEomXvLk","lore_tm_v1_xjLhaoe6a-PHHvtXnk79adt56_Eev7ZaE-SClyt09O8","lore_tm_v1_PDY8B9uCaIFj4CJACV9X495k6uXRaNrJ-IBtQ3c1PDY","lore_tm_v1_nT7RmuRmIGoUBA2BCDyDjLL2i1TqfEFoKYMSbYmymns","lore_tm_v1_YS4zBKz0SzexqA-eAbql8dZHQKFZ4MlAPFxm2YTNGsQ","lore_tm_v1_RUuq4ym8yatIl2P-MJmvnP0x8-_w6tPmNUyrisMQO3I","lore_tm_v1_Z-Jmu_bbmT2Y78rDhYfgLJhv0EzuxahfJVYjNGk-TFc"]
Date: Sep 8, 2026
/home/byk/Code/getsentry/publish; instructed not to edit files, to inspect git diff and relevant tests, report only substantive findings with current file:line evidence classified PASS, CONCERN, or MUST-FIX, and end exactly MERGE or DO-NOT-MERGE.src/publish/authorize-approval.js, GitHub response data shapes, race behavior, auto-approval interactions, the remediated workflow_dispatch trusted checkout, and attestation handoffs./home/byk/Code/getsentry/publish contains .eslintrc.js, .git/, .github/, .gitignore, .lore.md, AGENTS.md, auto-approve-repos.txt, docs/, LICENSE, node_modules/, package.json, README.md, src/, vitest.config.js, and yarn.lock (15 entries)..github/workflows/ci-poller.yml, .github/workflows/publish.yml, AGENTS.md, src/libs/__tests__/github.js, src/libs/github.js, src/modules/__tests__/update-issue.js, src/modules/details-from-context.js, src/publish/post-workflow-details.js, and src/publish/update-issue.js; the diff report was truncated and indicated additional modified files.ci-poller.yml diff: “Always run trusted code.” workflow_dispatch can target any ref, so actions/checkout@v6 checks out ${{ github.event.repository.default_branch }} with persist-credentials: false.ci-poller.yml diff: “Never move a release to ci-ready after it changes.” The poller binds a cycle to the current accepted-label event before inspecting CI.publish.yml comments say waiting-for-ci removes a pre-existing ci-ready first so a fresh labeled event fires.src/publish/authorize-approval.js: getAutoApprovedRepositories() reads auto-approve-repos.txt as UTF-8, splits on /\r?\n/, filters empty lines, and returns a Set.src/publish/authorize-approval.js:17-31: getPermission({ owner, repository, username }) requests repos/${encodeURIComponent(owner)}/${encodeURIComponent(repository)}/collaborators/${encodeURIComponent(username)}/permission, throws on non-OK responses with the returned status, and returns response.json().src/publish/authorize-approval.js:33-40: getGitHubResponse(path) uses fetch("https://api.github.com/${path}") with Accept: "application/vnd.github+json", Authorization: Bearer ${process.env.APPROVAL_TOKEN}, and X-GitHub-Api-Version: "2026-03-10".src/publish/authorize-approval.js:43-55 retrieves repos/${repository}/issues/${issueNumber}, throws on non-OK responses, and returns the issue JSON.src/publish/authorize-approval.js:57-78 retrieves all issue-event pages from repos/${repository}/issues/${issueNumber}/events?per_page=100&page=${page}, accumulating until a page has fewer than 100 records.src/publish/authorize-approval.js:80-97 requires GITHUB_OUTPUT, APPROVAL_TOKEN, APPROVAL_ISSUE_NUMBER, and APPROVAL_ISSUE_REPOSITORY; missing variables throw explicit errors.src/publish/authorize-approval.js:99-109 calls authorizeApproval() with APPROVAL_ACTOR, APPROVAL_ISSUE_TITLE, getPermission, and the auto-approved-repository set; if unauthorized, it appends authorized=false to $GITHUB_OUTPUT and returns.src/publish/authorize-approval.js:111-143 concurrently retrieves the issue and all events, finds currentAcceptedEvent(events), obtains requester login from issue.user?.login, and writes authorized=false unless: fetched issue title equals APPROVAL_ISSUE_TITLE; requester is a string; requester and APPROVAL_ACTOR differ under lowercase comparison; a current accepted event exists; and event.actor === APPROVAL_ACTOR. On success it creates an approval attestation using the accepted-event actor/ID and fetched issue, then writes authorized=true and approval_attestation=${attestation}.src/modules/approval-authorizer.js: authorized human roles are exactly write, maintain, and admin; the auto-approver identity is exactly getsantry[bot].src/modules/approval-authorizer.js:11-35: approval authorization parses the publish title with parsePublishTitle(issueTitle); invalid titles return { authorized: false, repository: null }. Valid titles form repository getsentry/${title.repo} and release path ${repository}${title.path || ""}. getsantry[bot] is authorized only when autoApprovedRepositories contains that release path; other instances of that bot are rejected. Other actors’ current target-repository permission is obtained through getPermission({ owner: "getsentry", repository: title.repo, username: actor }), and authorization is based on role_name.src/modules/approval-attestation.js: approval attestations use prefix <!-- publish-approval and CI-ready attestations use prefix <!-- publish-ci-ready ; payload JSON is base64url encoded and closed with -->.src/modules/approval-attestation.js:13-27: request digest is SHA-256 of JSON containing issue body, title, and dryRun, where dryRun is true if any label has name dry-run; it throws Invalid publish request unless body is a string and labels is an array.src/modules/approval-attestation.js:30-46: approval attestation binds actor, stringified eventId, request digest, and title; CI-ready attestation binds accepted actor, stringified accepted event ID, CI-ready actor, request digest, and title.src/modules/approval-attestation.js:106-142: event IDs are normalized by stripping leading zeroes and compared numerically by length then localeCompare; nonnumeric IDs throw Invalid issue event ID. currentLabeledEvent() filters labeled events for the requested label with a string/number ID and event.actor?.login, then selects the highest ID; errors produce null.src/modules/approval-attestation.js:153-195: approval proof requires a comment by the specified attestation author whose parsed approval attestation exactly matches the accepted event’s actor/ID, current issue request digest, and title. CI-ready proof similarly requires a comment by that author matching the current accepted event, current CI-ready event actor, digest, and title.src/publish/validate-approval-attestation.js:55-100 concurrently fetches the issue, all events, and all comments; approval validity requires title equality, current accepted label, a current accepted event, optional expected accepted actor/event-ID equality, and a matching approval attestation. When requireCiReadyAttestation is true, it additionally requires a current ci-ready event, current ci-ready label, and a matching CI-ready attestation.src/publish/validate-approval-attestation.js:102-143 requires environment variables APPROVAL_TOKEN, APPROVAL_ISSUE_NUMBER, APPROVAL_ISSUE_REPOSITORY, APPROVAL_ISSUE_TITLE, and APPROVAL_ATTESTATION_AUTHOR; optional expected accepted-event binding is supplied only when both EXPECTED_ACCEPTED_ACTOR and EXPECTED_ACCEPTED_EVENT_ID are present. Invalid validation throws The current accepted label has no matching approval attestation.src/publish/record-ci-ready-attestation.js:12-28 obtains the authenticated GitHub user through GET /user, requires a successful response and string login, and uses that login as the CI-ready actor.src/publish/record-ci-ready-attestation.js:30-58 concurrently retrieves issue, all events, and authenticated login; it throws The approval changed before CI could be marked ready unless issue title matches, accepted remains labeled, a current accepted event exists, and it matches the optional expected accepted event. It then creates a CI-ready attestation from the current accepted event, authenticated CI-ready actor, and issue..github/workflows/publish.yml:17-167 waiting-for-ci runs on issues:labeled events only when the added label is accepted, the issue is open, and title starts publish: . It checks out with actions/checkout@v6 and persist-credentials: false; obtains an internal-app token and a best-effort getsentry-scoped release-bot token; runs node src/publish/authorize-approval.js with continue-on-error: true; and records the emitted approval attestation as a comment using GH_TOKEN: ${{ github.token }}..github/workflows/publish.yml:81-111 validates the newly recorded approval attestation with APPROVAL_TOKEN: ${{ github.token }} and APPROVAL_ATTESTATION_AUTHOR: github-actions[bot]. If authorization, attestation recording, or validation does not succeed, it removes accepted, comments Approval is invalid or could not be verified. Re-add the accepted label to retry after resolving the issue., and exits 1..github/workflows/publish.yml:113-167 after successful authorization removes ci-failed and ci-ready, adds ci-pending via the internal-app token, comments either Retrying — CI was previously failed. Checking CI status now. or Approved. Checking CI status on the release branch. Publishing will start automatically when CI passes., best-effort sets CI_POLLER_HAS_PENDING to true with a dedicated poller-app token, and triggers ci-poller.yml using the internal-app token..github/workflows/publish.yml:169-219 publish job runs only for an open issue’s ci-ready label-addition event when labels include accepted and ci-ready but exclude ci-pending and ci-failed. Before publishing, it checks out source into .__publish__ with persist-credentials: false, validates both approval and CI-ready attestation via node .__publish__/src/publish/validate-approval-attestation.js with REQUIRE_CI_READY_ATTESTATION: "true", and on validation failure removes accepted, posts the invalid-approval comment, and exits 1..github/workflows/publish.yml:221-320 publish job uses actions/setup-node@v6 with Node 24, installs dependencies with yarn install --cwd ".__publish__", runs node .__publish__/src/publish/inputs.js, and posts workflow details with node .__publish__/src/publish/post-workflow-details.js..github/workflows/publish.yml:265-281 creates a getsentry-scoped release-bot token with actions/create-github-app-token@v3 and checks out the target repository as getsentry/${{ fromJSON(steps.inputs.outputs.result).repo }} into __repo__, with fetch-depth: 0 and optional protected target branch ref..github/workflows/publish.yml:283-320 Set targets constructs Craft’s already-published state outside __repo__/ at $GITHUB_WORKSPACE/.craft-state; it derives the container working directory, strips trailing slash, hashes it with sha1sum | cut -c1-12, and sanitizes owner, repository, and version to lowercase [a-z0-9._-]-compatible values.