Dashboard › publish › Distillation
39008685-810b-47ed-9be5-681a26be6dab["lore_tm_v1_tMJqZWOBYUw2Ld5AmjOM8n8g2lDJE_8_tlxQqxSMzJ8","lore_tm_v1_Ohjf5b9MX9Zrp7qjQEGmLmVfUGnbgvgPoAaFUn11Usg","lore_tm_v1_fT6BRPshD1y-SAm5Jn15Nm-QqNLDiZ7Hlvk13cRn_h8","lore_tm_v1_HyvSW1tqk6YtpBU8CpP8t95Y1Uf4tIBS2hVVJvCXDiA","lore_tm_v1_PgAnzVacjU4Dy-_J1CnFlQuYsAJVZH27SPFw0bFZCYQ","lore_tm_v1_97MD92bf_GjtomBJ4dhm6kigACSjA5NvvZfpwZp0BNk","lore_tm_v1_vlRbVNe98mHw5HY9VCYeJNGtoKeCvp8FwDvCVMbj8PU","lore_tm_v1_TjaRSAZnC6C_CV-832S-UVJFZKii6eVtBoVYpF-tcKY","lore_tm_v1_nv5Hf5KBwZqJwY4C79zYG8LgbkTNEYXQhoXgYw5dpbE","lore_tm_v1_p8JIlUHw22IJbnFG1SyStLkrKL6cY31u_y_TT6_yqtQ","lore_tm_v1_9OTmxPkZUYyMEPmEBHM7qdXpLda4bsVbZ4ol8FKiY4Q","lore_tm_v1_VUyYyIGeacPWPI85zEAia67otw2MczCzMF6ThK9mfvA","lore_tm_v1_Lg7E36QIdO16OZhEJapk0X61ifHynDrY766kCpcde9k","lore_tm_v1_rpBFzBIOejK3Hs-hJGlp-izLfBInLYyyxqp15TfD1cM","lore_tm_v1_W7oYbTRzMBFHPIH4UZoEQeWYThD0YDnfDe5R2U4_7dA"]
π΄ (11:51) /home/byk/Code/getsentry/publish/.github/workflows/test.yml defines workflow Test, triggered by pushes to main and all pull_request events, with contents: read, default bash shell, and one unit-test job named unit tests on ubuntu-latest.
π΄ (11:51) .github/workflows/test.yml runs ordered steps: 1. actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803; 2. actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 with node-version: 24; 3. actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 with id: cache, path node_modules, and key ${{ runner.os }}-node_modules-${{ hashFiles('package.json', 'yarn.lock') }}; 4. Install Dependencies, conditionally running yarn install --frozen-lockfile when steps.cache.outputs.cache-hit != 'true'; 5. Test, running yarn test.
π΄ (11:52) src/publish/ contains implementation files: request-digest-from-event.js, update-issue.js, post-workflow-details.js, post-result.js, inputs.js, resolve-release-revision.js, resolve-location.js, resolve-ci-poller-input.js, discover-location.js, record-auto-approval-attestation.js, current-accepted-event.js, record-ci-ready-attestation.js, validate-approval-attestation.js, and authorize-approval.js.
π΄ (11:52) src/publish/__tests__/ contains: post-result.js, request-digest-from-event.js, resolve-release-revision.js, resolve-location.js, discover-location.js, workflow-action-pinning.js, record-auto-approval-attestation.js, publish-workflow.js, auto-approval-workflow.js, ci-poller-workflow.js, current-accepted-event.js, record-ci-ready-attestation.js, validate-approval-attestation.js, and authorize-approval.js.
π΄ (11:52) src/modules/ contains implementation files: update-issue.js, process-end-state.js, post-workflow-details.js, details-from-context.js, release-revision.js, publish-location.js, publish-issue-validation.js, publish-issue-title.js, ci-poller-input.js, approval-attestation.js, and approval-authorizer.js.
π΄ (11:52) src/modules/__tests__/ contains: update-issue.js, details-from-context.js, release-revision.js, publish-workflow.js, publish-location.js, generate-publish-issue-title-parser.js, ci-poller-workflow.js, ci-poller-input.js, approval-attestation.js, approval-authorizer.js, process-end-state.js, and post-workflow-details.js.
π΄ (11:52) src/libs/ contains github.js; src/libs/__tests__/ contains github.js.
π΄ (11:52) src/modules/approval-attestation.js defines three HTML-comment attestation prefixesβ"<!-- publish-approval ", "<!-- publish-auto-approval ", and "<!-- publish-ci-ready "βplus suffix " -->"; createAttestation(prefix, value) JSON-serializes and Base64URL-encodes the value between the prefix and suffix.
π΄ (11:52) requestDigest({ body, labels, title }) in src/modules/approval-attestation.js rejects non-string body, non-array labels, or non-string title with Invalid publish request; it returns a SHA-256 hex digest of JSON containing body, dryRun derived from a dry-run label, and title.
π΄ (11:52) createApprovalAttestation({ actor, eventId, issue }) records actor, stringified eventId, requestDigest, and title; createAutoApprovalAttestation({ autoApprover, issue }) records autoApprover, requestDigest, and title; createCiReadyAttestation({ acceptedEvent, ciReadyActor, issue }) records acceptedActor, stringified acceptedEventId, ciReadyActor, requestDigest, and title.
π΄ (11:52) parseAttestation(body, prefix) finds the first matching prefix and suffix, Base64URL-decodes and JSON-parses the payload, and returns null for missing delimiters or parse failures. parseApprovalAttestation, parseAutoApprovalAttestation, and parseCiReadyAttestation additionally require all corresponding fields to be strings.
π΄ (11:52) compareEventIds(left, right) strips leading zeroes, normalizes an empty result to "0", rejects non-decimal IDs with Invalid issue event ID, compares differing digit lengths numerically, and otherwise uses localeCompare.
π΄ (11:52) currentLabeledEvent(events, labelName) filters for labeled events matching label.name, IDs of type string or number, and a truthy actor.login; it chooses the greatest event ID via compareEventIds and returns { actor, eventId }, or null if no event exists or comparison fails. currentAcceptedEvent uses label accepted; currentCiReadyEvent uses ci-ready.
π΄ (11:52) hasApprovalAttestation, hasAutoApprovalAttestation, and hasCiReadyAttestation only accept comments authored by the specified attestationAuthor and require their parsed fields to match the current issue digest/title and relevant actor/event data. CI-ready validation binds acceptedActor, acceptedEventId, and ciReadyActor.
π΄ (11:52) src/modules/approval-attestation.js exports compareEventIds, all three attestation creators, currentAcceptedEvent, currentCiReadyEvent, currentLabeledEvent, all three has*Attestation functions, all three parsers, and requestDigest.
π΄ (11:52) src/modules/approval-authorizer.js permits repository roles write, maintain, and admin; defines AUTO_APPROVAL_LABELER = "sentry-internal-app[bot]"; and defines auto-approvers "getsantry[bot]" and "sentry-release-bot[bot]".
π΄ (11:52) isAutoApprovedRepository({ actor, autoApprovedRepositories, issueTitle }) requires the actor to be in AUTO_APPROVERS, requires parsePublishTitle(issueTitle) to succeed, and tests membership of getsentry/${title.repo}${title.path || ""} in autoApprovedRepositories.
π΄ (11:52) authorizeApproval({ actor, issueTitle, getPermission }) returns { authorized: false, repository: null } for an invalid title; derives repository = getsentry/${title.repo} for a valid title; rejects auto-approvers and AUTO_APPROVAL_LABELER; otherwise queries permission with owner getsentry, repository title.repo, and username actor, authorizing only roles in ALLOWED_ROLE_NAMES.
π΄ (11:52) src/modules/details-from-context.js defines 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.
π΄ (11:52) parsePublishTitle(title) in src/modules/details-from-context.js parses with publish-issue-title, derives path = "." + titleDetails.path, and returns null if parsing throws or if isPublishRepository, isReleaseVersion, or isPublishPath fails.
π΄ (11:52) detailsFromContext({ context }) throws Issue context is not defined without context.payload.issue; reports specific invalid title, repository, version, and path errors; maps the dry-run label to dry_run: "1" and its absence to dry_run: ""; obtains mergeTarget from getReleaseRevisionDetails; maps "(default)" to merge_target: ""; and extracts only checked targets from the matched targets section.
π΄ (11:52) src/modules/ci-poller-input.js defines getCiPollerInput({ title, issueBody, labels, revision }), which validates parsed repository, version, and .${parsedTitle.path} with specific Invalid publish issue ... errors; reads currentRevision from the original body; optionally replaces the body revision; returns repo: getsentry/${repo}, a digest over the resolved body, revision: currentRevision, and version; and includes issueBody: resolvedIssueBody only when a replacement revision was supplied.
π΄ (11:52) src/modules/release-revision.js defines isRevision(revision) as exactly /^[0-9a-f]{40}$/.
π΄ (11:52) getReleaseRevisionDetails({ issueBody, repo }) requires exactly one CheckRunsLinkCount, otherwise throwing Expected exactly one View check runs link in Quick links for getsentry/${repo}.; it parses ReleaseRevision, requires details.repo === repo, and otherwise throws Expected a View check runs link for getsentry/${repo} in the publish issue body.
π΄ (11:52) getReleaseRevision({ issueBody, repo }) returns .revision.value; updateReleaseRevision({ issueBody, repo, revision }) rejects anything other than a lowercase 40-character SHA with Release revision must be a lowercase 40-character SHA. and replaces only the parsed revision span via currentRevision.start and currentRevision.end.
π΄ (11:53) src/modules/publish-location.js defines resolvePublishLocation({ path, workspaceNames }): it rejects an invalid path with Invalid publish path., rejects any invalid workspace name with Craft workspace discovery returned an invalid workspace list., returns { path } for ".", maps an exact workspace match for path.slice(2) to { path: ".", workspace }, and otherwise returns { path }.
π΄ (11:53) isPublishPath(path) accepts only "." or "./" followed by slash-separated safe workspace segments. A safe segment must match /^[A-Za-z0-9_.-]+$/, cannot be ".", "..", or "__proto__", and cannot start with "-". isWorkspaceName(name) applies the same segment rules to slash-separated names.
π΄ (11:53) src/modules/process-end-state.js defines processEndState({ context, octokit, inputs = {}, status }); it validates status through sentryInfoFromDetails before terminal state changes, closes the issue only for status === "success", fetches the current workflow run, posts an issue comment, and reports a Sentry session.
π΄ (11:53) githubIssueComment in src/modules/process-end-state.js returns: for failure, Failed to publish. with run logs anchored to ?check_suite_focus=true#step:8 and a URL-encoded branch-deletion link; for cancelled, Publish workflow cancelled. with the same links; for success, Published successfully: [run#${run_id}](${workflowInfo.html_url}); otherwise it throws Unknown status: '${status}'.
π΄ (11:53) reportSession initializes Sentry with dsn: process.env.SENTRY_DSN and release = ${repo}@${version}, tags repository, sets release context containing issue_number and inputs, captures the status-specific message/severity, starts and ends a status-specific session, and awaits Sentry.close().
π΄ (11:53) sentryInfoFromDetails maps failure to message Release failed: ${repo}, severity error, session status crashed; cancelled to Release cancelled: ${repo}, severity warn, status crashed; and success to Release succeeded: ${repo}, severity info, status ok; unknown statuses throw.
π΄ (11:53) src/modules/post-workflow-details.js fetches the current workflow run and creates an issue comment with body Publishing: [run#${run_id}](${workflowInfo.html_url}).
π΄ (11:53) src/publish/authorize-approval.js reads auto-approve-repos.txt as UTF-8, splits on /\r?\n/, removes empty lines, and returns a Set.
π΄ (11:53) getGitHubResponse(path) in src/publish/authorize-approval.js fetches https://api.github.com/${path} with Accept: application/vnd.github+json, bearer APPROVAL_TOKEN, and X-GitHub-Api-Version: 2026-03-10.
π΄ (11:53) getPermission calls repos/{owner}/{repository}/collaborators/{username}/permission with each path component URL-encoded and throws Could not retrieve ${username}'s permission for ${owner}/${repository}: GitHub returned ${response.status} on failure. getIssue similarly fetches repos/${repository}/issues/${issueNumber} and reports the issue number and response status on failure.
π΄ (11:53) getIssueEvents and getIssueComments in src/publish/authorize-approval.js paginate GitHub issue resources with per_page=100&page=${page}, aggregate all pages, and stop when a page contains fewer than 100 records.
π΄ (11:53) main() in src/publish/authorize-approval.js requires GITHUB_OUTPUT, APPROVAL_TOKEN, APPROVAL_ISSUE_NUMBER, APPROVAL_ISSUE_REPOSITORY, and EXPECTED_REQUEST_DIGEST; it reads actor/title/digest from APPROVAL_ACTOR, APPROVAL_ISSUE_TITLE, and EXPECTED_REQUEST_DIGEST.
π΄ (11:53) For actor sentry-internal-app[bot], authorize-approval.js concurrently fetches the live issue, all events, and all comments; authorizes only if the issue remains open with the expected title and digest, the requester is one of the auto-approvers and targets an allow-listed repository/path, the current accepted event was performed by the labeler, and a matching auto-approval attestation authored by github-actions[bot] exists.
π΄ (11:53) For other actors, authorize-approval.js uses authorizeApproval; if authorization fails it appends authorized=false to GITHUB_OUTPUT. After authorization, it re-fetches the live issue and events if needed and rejects if the issue changed, requester is invalid, a non-labeler actor is also the requester case-insensitively, no accepted event exists, or the accepted-event actor differs from the approving actor.
π΄ (11:53) Successful authorization creates an approval attestation bound to the accepted eventβs actor and event ID plus the live issue, then appends authorized=true and approval_attestation=${attestation} to GITHUB_OUTPUT; top-level failures are logged and set process.exitCode = 1.
π΄ (11:54) src/publish/validate-approval-attestation.js uses GitHub API version 2026-03-10, bearer APPROVAL_TOKEN, and media type application/vnd.github+json; getAllPages paginates arbitrary issue resource values with 100 records per page and reports resource, issue number, and HTTP status on errors.
π΄ (11:54) validateApprovalAttestation({ attestationAuthor, expectedAcceptedEvent, expectedRequestDigest, issueNumber, issueTitle, repository, requireCiReadyAttestation = false }) concurrently loads the issue, all events, and all comments; approval requires an open issue, exact title and digest, a current accepted label, a current accepted event, an optional exact accepted actor/event-ID match, and a matching approval attestation by attestationAuthor.
π΄ (11:54) If basic approval is false or requireCiReadyAttestation is false, validateApprovalAttestation returns the basic approval result. If CI-ready proof is required, it additionally requires a current ci-ready event, a current ci-ready label, and a matching CI-ready attestation bound to the accepted event, CI-ready event actor, issue digest, and title.
π΄ (11:54) main() in src/publish/validate-approval-attestation.js requires APPROVAL_TOKEN, APPROVAL_ISSUE_NUMBER, APPROVAL_ISSUE_REPOSITORY, APPROVAL_ISSUE_TITLE, APPROVAL_ATTESTATION_AUTHOR, and EXPECTED_REQUEST_DIGEST; optional expected accepted-event data is used only when both EXPECTED_ACCEPTED_ACTOR and EXPECTED_ACCEPTED_EVENT_ID exist; REQUIRE_CI_READY_ATTESTATION enables the extra check only when exactly "true".
π΄ (11:54) Failed validation in src/publish/validate-approval-attestation.js throws The current accepted label has no matching approval attestation; top-level errors are logged and set process.exitCode = 1.
π΄ (11:54) src/publish/current-accepted-event.js defines getCurrentAcceptedEvent({ getIssueEvents, issueNumber, repository }), obtains the current accepted label event, and throws The issue has no current accepted label event when absent.
π΄ (11:54) main() in src/publish/current-accepted-event.js requires APPROVAL_TOKEN, APPROVAL_ISSUE_NUMBER, and APPROVAL_ISSUE_REPOSITORY; it gets all event pages via getAllPages(... resource: "events") and writes the JSON-serialized { actor, eventId } directly to stdout.
π΄ (11:54) src/publish/record-auto-approval-attestation.js defines recordAutoApprovalAttestation, which fetches the live issue and requires it to remain open with the exact expected title and digest and a requester login matching autoApprover case-insensitively; otherwise it throws The automated approval request changed before approval.
π΄ (11:54) Successful recordAutoApprovalAttestation returns createAutoApprovalAttestation({ autoApprover, issue }). Its main() requires GITHUB_OUTPUT, APPROVAL_TOKEN, APPROVAL_ISSUE_NUMBER, APPROVAL_ISSUE_REPOSITORY, APPROVAL_ISSUE_TITLE, AUTO_APPROVER, and EXPECTED_REQUEST_DIGEST, then appends auto_approval_attestation=${attestation} to GITHUB_OUTPUT.
π΄ (11:54) src/publish/record-ci-ready-attestation.js defines getAuthenticatedLogin(), which fetches GitHub API path user, throws with the HTTP status if retrieval fails, throws GitHub returned no authenticated user login if login is not a string, and otherwise returns the login.
π΄ (11:54) recordCiReadyAttestation concurrently fetches the issue, issue events, and authenticated login as ciReadyActor; it requires the issue to remain open with the exact title and digest, retain the accepted label, have a current accepted event, and optionally match an expected accepted actor/event ID exactly; otherwise it throws The approval changed before CI could be marked ready.
π΄ (11:54) Successful recordCiReadyAttestation returns an attestation binding the current accepted event, authenticated ciReadyActor, and issue. Its main() requires GITHUB_OUTPUT, APPROVAL_TOKEN, APPROVAL_ISSUE_NUMBER, APPROVAL_ISSUE_REPOSITORY, APPROVAL_ISSUE_TITLE, and EXPECTED_REQUEST_DIGEST; optional expected accepted-event data requires both EXPECTED_ACCEPTED_ACTOR and EXPECTED_ACCEPTED_EVENT_ID; all event pages are fetched with resource "events"; and ci_ready_attestation=${attestation} is appended to GITHUB_OUTPUT.