Dashboard › publish › Distillation
0464fe66-7d69-4da4-b4e6-e259cbe6ffcf["lore_tm_v1_v6Exg6P_Y2FLHwPI_ArOyyFnNF-2VcpVwNij8l--MCU","lore_tm_v1_4wT6W-R-Zln0aeWgXtYweh1csatNmwrZZBW5_jgRGwg","lore_tm_v1_BWFs1PRlz-5aZwrLeM6X_e7WFGlLjWQ2DkG_iBr7A40","lore_tm_v1_KmLHobshEExK1H1ma8uRe_g38BVgkYz9I2TE-DMwOUc"]
π΄ (16:02) /home/byk/Code/getsentry/publish/src/modules/approval-attestation.js is a 273-line CommonJS module using Nodeβs crypto.
π΄ (16:02) src/modules/approval-attestation.js:3-6 defines attestation framing constants: APPROVAL_ATTESTATION_PREFIX = "<!-- publish-approval ", AUTO_APPROVAL_ATTESTATION_PREFIX = "<!-- publish-auto-approval ", CI_READY_ATTESTATION_PREFIX = "<!-- publish-ci-ready ", and ATTESTATION_SUFFIX = " -->".
π΄ (16:02) src/modules/approval-attestation.js:8-12 defines createAttestation(prefix, value), which JSON-serializes value, encodes it with Buffer as "base64url", and wraps it with the supplied prefix and ATTESTATION_SUFFIX.
π΄ (16:02) src/modules/approval-attestation.js:14-33 defines requestDigest({ body, labels, title }); it throws Error("Invalid publish request") unless body and title are strings and labels is an array, then computes a SHA-256 hex digest over JSON.stringify({ body, dryRun: labels.some((label) => label.name === "dry-run"), title }).
π΄ (16:02) src/modules/approval-attestation.js:35-60 defines three creators: createApprovalAttestation({ actor, eventId, issue }) stores actor, eventId: String(eventId), requestDigest, and title; createAutoApprovalAttestation({ autoApprover, issue }) stores autoApprover, requestDigest, and title; createCiReadyAttestation({ acceptedEvent, ciReadyActor, issue }) stores acceptedActor, acceptedEventId: String(acceptedEvent.eventId), ciReadyActor, requestDigest, and title.
π΄ (16:02) src/modules/approval-attestation.js:62-86 defines parseAttestation(body, prefix): it finds the first prefix and subsequent " -->", base64url-decodes and JSON-parses the enclosed payload, and returns null for missing framing or decoding/parsing failures.
π΄ (16:02) src/modules/approval-attestation.js:88-131 defines typed parsers. parseApprovalAttestation() requires string fields actor, eventId, requestDigest, and title; parseAutoApprovalAttestation() requires autoApprover, requestDigest, and title; parseCiReadyAttestation() requires acceptedActor, acceptedEventId, ciReadyActor, requestDigest, and title; each returns null for an invalid payload.
π΄ (16:02) src/modules/approval-attestation.js:133-146 defines compareEventIds(left, right), which stringifies IDs, strips leading zeroes while preserving zero as "0", throws Error("Invalid issue event ID") for non-decimal values, compares digit length first, and then uses localeCompare(); this avoids numeric precision loss for large issue-event IDs.
π΄ (16:02) src/modules/approval-attestation.js:148-180 defines currentLabeledEvent(events, labelName): it filters matching "labeled"/"unlabeled" events, validates all IDs through compareEventIds(), selects the greatest ID, and returns { actor: event.actor.login, eventId: String(event.id) } only when the latest event is "labeled" with a nonempty string actor login; otherwise, including caught ID errors, it returns null.
π΄ (16:02) src/modules/approval-attestation.js:182-188 defines currentAcceptedEvent(events) as currentLabeledEvent(events, "accepted") and currentCiReadyEvent(events) as currentLabeledEvent(events, "ci-ready").
π΄ (16:02) src/modules/approval-attestation.js:190-207 defines hasApprovalAttestation({ comments, event, issue, attestationAuthor }); it requires a comment authored by attestationAuthor whose parsed approval attestation exactly matches event.actor, event.eventId, the live requestDigest(issue), and issue.title.
π΄ (16:02) src/modules/approval-attestation.js:209-230 defines hasAutoApprovalAttestation({ autoApprover, attestationAuthor, comments, issue }); it requires a comment authored by attestationAuthor whose parsed auto-approval attestation exactly matches autoApprover, the live requestDigest(issue), and issue.title.
π΄ (16:02) src/modules/approval-attestation.js:232-256 defines hasCiReadyAttestation({ comments, acceptedEvent, ciReadyEvent, issue, attestationAuthor }); it requires a comment authored by attestationAuthor whose parsed CI-ready attestation matches acceptedEvent.actor, acceptedEvent.eventId, ciReadyEvent.actor, the live requestDigest(issue), and issue.title.
π΄ (16:02) src/modules/approval-attestation.js:258-273 exports compareEventIds, createApprovalAttestation, createAutoApprovalAttestation, createCiReadyAttestation, currentAcceptedEvent, currentCiReadyEvent, currentLabeledEvent, hasApprovalAttestation, hasAutoApprovalAttestation, hasCiReadyAttestation, parseApprovalAttestation, parseAutoApprovalAttestation, parseCiReadyAttestation, and requestDigest.
π΄ (16:03) /home/byk/Code/getsentry/publish/src/modules/approval-authorizer.js is a 57-line CommonJS module that imports parsePublishTitle from ./details-from-context.js.
π΄ (16:03) src/modules/approval-authorizer.js:1-5 defines ALLOWED_ROLE_NAMES = new Set(["write", "maintain", "admin"]), AUTO_APPROVAL_LABELER = "sentry-internal-app[bot]", and AUTO_APPROVERS = new Set(["getsantry[bot]", "sentry-release-bot[bot]"]); the first bot name is spelled exactly getsantry[bot].
π΄ (16:03) src/modules/approval-authorizer.js:7-25 defines isAutoApprovedRepository({ actor, autoApprovedRepositories, issueTitle }): it rejects actors outside AUTO_APPROVERS and unparseable publish titles, then tests membership of getsentry/${title.repo}${title.path || ""} in autoApprovedRepositories.
π΄ (16:03) src/modules/approval-authorizer.js:27-50 defines authorizeApproval({ actor, issueTitle, getPermission }): an invalid title returns { authorized: false, repository: null }; valid titles produce repository = \getsentry/${title.repo}`; actors in AUTO_APPROVERSand actorAUTO_APPROVAL_LABELERare rejected without permission lookup; other actors are checked viagetPermission({ owner: "getsentry", repository: title.repo, username: actor })and authorized only whenrole_nameiswrite, maintain, or admin. π΄ (16:03) src/modules/approval-authorizer.js:52-57exportsAUTO_APPROVAL_LABELER, AUTO_APPROVERS, authorizeApproval, and isAutoApprovedRepository. π΄ (16:03) /home/byk/Code/getsentry/publish/src/publish/authorize-approval.jsis a 249-line CommonJS executable module usingfs, approval-attestation helpers, and approval-authorizer helpers. π΄ (16:03) src/publish/authorize-approval.js:15-22definesgetAutoApprovedRepositories(), which synchronously reads auto-approve-repos.txtas UTF-8, splits on/\r?\n/, removes empty lines, and returns a Set. π΄ (16:03) src/publish/authorize-approval.js:24-38definesgetPermission({ owner, repository, username }); it requests repos/${encodeURIComponent(owner)}/${encodeURIComponent(repository)}/collaborators/${encodeURIComponent(username)}/permission, throws Could not retrieve ${username}'s permission for ${owner}/${repository}: GitHub returned ${response.status}for a non-OK response, and otherwise returnsresponse.json(). π΄ (16:03) src/publish/authorize-approval.js:40-48definesgetGitHubResponse(path)usingfetch(`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". π΄ (16:03) src/publish/authorize-approval.js:50-62definesgetIssue({ repository, issueNumber }), requesting repos/${repository}/issues/${issueNumber}and throwingCould not retrieve issue #${issueNumber}: GitHub returned ${response.status}on a non-OK response. π΄ (16:03)src/publish/authorize-approval.js:64-85definesgetIssueEvents({ repository, issueNumber }); it paginates repos/${repository}/issues/${issueNumber}/events?per_page=100&page=${page}, accumulates all events, stops when a page contains fewer than 100events, and throwsCould not retrieve events for issue #${issueNumber}: GitHub returned ${response.status}on failure. π΄ (16:03)src/publish/authorize-approval.js:87-108definesgetIssueComments({ repository, issueNumber }); it paginates repos/${repository}/issues/${issueNumber}/comments?per_page=100&page=${page}, accumulates all comments, stops when a page contains fewer than 100comments, and throwsCould not retrieve comments for issue #${issueNumber}: GitHub returned ${response.status}on failure. π΄ (16:03)src/publish/authorize-approval.js:110-136requires environment variablesGITHUB_OUTPUT, APPROVAL_TOKEN, APPROVAL_ISSUE_NUMBER, APPROVAL_ISSUE_REPOSITORY, and EXPECTED_REQUEST_DIGEST, throwing a specific No "<NAME>" environment variable founderror when any is absent; it also readsAPPROVAL_ACTORandAPPROVAL_ISSUE_TITLE. π΄ (16:03) src/publish/authorize-approval.js:141-178handlesactor === AUTO_APPROVAL_LABELERby concurrently fetching the live issue, all events, and all comments; authorization requires the issue to remain"open", the live title to equal APPROVAL_ISSUE_TITLE, the live requestDigestto equalEXPECTED_REQUEST_DIGEST, a string requester in AUTO_APPROVERS, an auto-approved repository/path, the current accepted event actor to equal sentry-internal-app[bot], and a matching auto-approval attestation authored by "github-actions[bot]". π΄ (16:03) src/publish/authorize-approval.js:179-186handles non-auto-labeler actors throughauthorizeApproval({ actor, issueTitle, getPermission, autoApprovedRepositories }); although autoApprovedRepositoriesis passed, the shownauthorizeApproval()signature does not consume it. π΄ (16:03)src/publish/authorize-approval.js:188-191writesauthorized=false\ntoGITHUB_OUTPUTand returns when preliminary authorization fails. π΄ (16:03)src/publish/authorize-approval.js:193-206refetches the live issue and issue events concurrently when they were not already loaded, then derivesevent = currentAcceptedEvent(events). π΄ (16:03) src/publish/authorize-approval.js:207-221performs final live-state validation: issue state must be"open"; title and request digest must still match expected values; requester must be a string; for non-AUTO_APPROVAL_LABELERactors, requester and approver must differ case-insensitively; a current accepted event must exist; andevent.actormust equal the approval actor. Any failure writesauthorized=false\n. π΄ (16:03) src/publish/authorize-approval.js:223-231creates an approval attestation using the current accepted eventβsactorandeventIdplus the live issue, then appendsauthorized=true\napproval_attestation=${attestation}\ntoGITHUB_OUTPUT. π΄ (16:03) src/publish/authorize-approval.js:234-249executesmain()only when run directly, logs caught errors withconsole.error(error), sets process.exitCode = 1, and exports getAutoApprovedRepositories, getGitHubResponse, getIssue, getIssueComments, getIssueEvents, getPermission, and main. π΄ (16:03) /home/byk/Code/getsentry/publish/src/publish/request-digest-from-event.jsis a 34-line CommonJS executable module usingfsandrequestDigestfrom../modules/approval-attestation.js. π΄ (16:03) src/publish/request-digest-from-event.js:4-10definesrequestDigestFromEvent(event); it throws Error("The GitHub event has no issue snapshot")unlessevent.issueis a non-null object, then returnsrequestDigest(event.issue). π΄ (16:03) src/publish/request-digest-from-event.js:12-28definesmain(), which requires GITHUB_EVENT_PATHandGITHUB_OUTPUT, parses the UTF-8 JSON event file at GITHUB_EVENT_PATH, and appends request_digest=${requestDigestFromEvent(event)}\ntoGITHUB_OUTPUT. π΄ (16:03) src/publish/request-digest-from-event.js:30-34runsmain()when invoked directly and exports{ main, requestDigestFromEvent }`.