Dashboard › publish › Distillation
3da71c7e-7612-418a-aab5-da51f0e97423["lore_tm_v1_pegwbg7VCpt9sWWFdmQIIhd5wf7bfn5cEgXzN33ISEM","lore_tm_v1_eT0a5et_wxsl7TS9ZsXpvr_OLAWgxhRTJy_qL1umJtE","lore_tm_v1_X-Yslth5ldwl-14GCgpGgUdgi5Ay62LhpwRsGNdWvYQ","lore_tm_v1_AjPfYA76crxskBLmb7QsDIteOM2i21H44OHHJHD04ns","lore_tm_v1_QZrsMqH-ChWh2UZupCYAjYzFBpzfka6E8KkZAF6d7jE","lore_tm_v1_hw9rfrjqhLoHvrNH3pEYjxOXEOFZJDgU350EeOK28EY","lore_tm_v1_AiVrzAHRjecZBDiWAahQ1LlDdUuMj1Qn50qWJsA48Jk","lore_tm_v1_51beqx2gdwaWZdg3xBi3smaVPz-Jw0Noay9N795iTgk","lore_tm_v1_33E24iluAw5RyUtpPAng9329E1RFEFzEPo2S0W2d7dg","lore_tm_v1_rnQ0_4PgA9IPKS5ClS1FOEDNi8gOnCWK3P6jXVqRqsk","lore_tm_v1_KzJTdq9gZFeL8bAN2QorSl2o59xNLMinsY3U9dFy3LA","lore_tm_v1_n76F81oF0fRdHrjUKcVqmhWwHdYrP_nZJq5iW7jo2kA","lore_tm_v1_9f1qmB4_sMZ-Qtc9h_a-N34v2ErKGoABIfDzaiVz2Fw","lore_tm_v1_3_IO0oKmcSBzBMfVV5XbpY0NvQrSBFW9JTGW3kOTVI8","lore_tm_v1_w_aiYjAZ05woeDB9_9O5giq7h3TpAcXYM8wue-jZlxE","lore_tm_v1_oe6s9sNIJaOjLQGyna2R8B2Tq_D6gGoAzDEUuWwXoWI"]
Date: Sep 10, 2026
.github/workflows/test.yml defines workflow Test, triggered by pushes to main and all pull requests, with contents: read, default bash shell, and one unit-test job named unit tests on ubuntu-latest..github/workflows/test.yml runs 5 ordered unit-test actions/steps: 1. actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803, 2. actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 with node-version: 24, 3. actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 caching node_modules under key ${{ runner.os }}-node_modules-${{ hashFiles('package.json', 'yarn.lock') }}, 4. yarn install --frozen-lockfile only when steps.cache.outputs.cache-hit != 'true', 5. yarn test.ci-pending before the separate ci-ready add, which can fail. Assistant began checking attestation, parser, and Craft state/location code for later guards.src/modules/approval-attestation.js defines three base64url JSON HTML-comment attestations: approval prefix <!-- publish-approval , auto-approval prefix <!-- publish-auto-approval , CI-ready prefix <!-- publish-ci-ready , all ending with -->.requestDigest({ body, labels, title }) in src/modules/approval-attestation.js requires a string body, array labels, and string title, then computes a SHA-256 hex digest over JSON containing body, dryRun determined by a dry-run label, and title; invalid inputs throw Invalid publish request.actor, stringified eventId, requestDigest, and title; auto-approval attestations contain autoApprover, requestDigest, and title; CI-ready attestations contain acceptedActor, stringified acceptedEventId, ciReadyActor, requestDigest, and title.parseAttestation() in src/modules/approval-attestation.js extracts the first matching prefixed HTML comment, base64url-decodes and JSON-parses it, and returns null for missing prefix/suffix or decoding/parsing failure. parseApprovalAttestation(), parseAutoApprovalAttestation(), and parseCiReadyAttestation() additionally require every expected field to be a string.compareEventIds() in src/modules/approval-attestation.js compares arbitrarily long numeric issue-event IDs by stripping leading zeroes, validating digits, comparing normalized length, then using localeCompare; invalid IDs throw Invalid issue event ID.currentLabeledEvent(events, labelName) filters for labeled events with the requested label, string/number IDs, and an actor login, then chooses the greatest event ID and returns { actor, eventId }; it returns null for no valid events or comparison errors. currentAcceptedEvent() and currentCiReadyEvent() specialize it for accepted and ci-ready.src/modules/approval-attestation.js only accepts comments authored by the configured attestationAuthor: hasApprovalAttestation() binds actor, event ID, current request digest, and title; hasAutoApprovalAttestation() binds auto-approver, current request digest, and title; hasCiReadyAttestation() binds accepted actor/event ID, CI-ready actor, current request digest, and title.src/modules/approval-authorizer.js permits manual approval roles write, maintain, and admin. It defines AUTO_APPROVAL_LABELER = "sentry-internal-app[bot]" and auto-approvers getsantry[bot] and sentry-release-bot[bot].isAutoApprovedRepository() in src/modules/approval-authorizer.js requires the actor to be an allowed auto-approver, the publish title to parse and validate, and the exact getsentry/${title.repo}${title.path || ""} entry to exist in autoApprovedRepositories.authorizeApproval() in src/modules/approval-authorizer.js rejects invalid titles, all auto-approvers, and sentry-internal-app[bot]; otherwise it queries the actor’s permission for getsentry/${title.repo} and authorizes only write, maintain, or admin.getCiPollerInput() in src/modules/ci-poller-input.js parses and validates the publish issue title’s repository, version, and path; errors are Invalid publish issue title: '${title}', Invalid publish issue repository: '${repo}', Invalid publish issue version: '${version}', or Invalid publish issue path: '${path}'.getCiPollerInput() reads currentRevision from the original issue body. If a revision argument is provided, it creates resolvedIssueBody with updateReleaseRevision() and returns that body plus a request digest based on it, but the returned revision field remains currentRevision; it also returns repo: getsentry/${repo} and version.parsePublishTitle() in src/modules/details-from-context.js returns parsed title details only when repository, version, and "." + titleDetails.path all validate; parse or validation failures return null.detailsFromContext() in src/modules/details-from-context.js requires an issue context, validates title/repository/version/path, maps the dry-run label to dry_run: "1" and absence to dry_run: "", maps merge target "(default)" to merge_target: "", and returns checked target IDs from the body’s target-list section.src/modules/details-from-context.js uses TARGETS_SECTION_PARSER_REGEX to match an entire contiguous checkbox-list section, TARGETS_PARSER_REGEX to capture all target IDs, and CHECKED_TARGETS_PARSER_REGEX to capture only - [x] targets case-insensitively.resolvePublishLocation() in src/modules/publish-location.js validates both the publish path and every Craft workspace name. Path "." remains { path: "." }; a path "./${workspace}" matching a discovered workspace becomes { path: ".", workspace }; otherwise the original { path } is retained.isPublishPath() accepts only "." or "./" paths whose slash-separated segments pass isSafeWorkspaceSegment(). Safe segments match [A-Za-z0-9_.-]+, cannot equal ., .., or __proto__, and cannot start with -; isWorkspaceName() applies the same segment validation.src/modules/release-revision.js accepts revisions only when they match exactly 40 lowercase hexadecimal characters via /^[0-9a-f]{40}$/; invalid replacement revisions throw Release revision must be a lowercase 40-character SHA..getReleaseRevisionDetails() in src/modules/release-revision.js requires exactly one View check runs link in Quick links for getsentry/${repo}, parses its release revision details, and requires its repository to match. updateReleaseRevision() replaces only the parsed revision substring using its start and end offsets.processEndState() in src/modules/process-end-state.js validates terminal status before state mutation; on success it closes the issue before fetching workflow-run information, posting the terminal issue comment, and reporting the Sentry session. failure and cancelled do not close the issue.src/modules/process-end-state.js are: failure → Failed to publish. with run logs targeting ?check_suite_focus=true#step:8 and a URL-encoded branch-deletion link; cancelled → Publish workflow cancelled. with the same links; success → Published successfully: [run#${run_id}](${workflowInfo.html_url}). Unknown statuses throw Unknown status: '${status}'.src/modules/process-end-state.js initializes with SENTRY_DSN and release ${repo}@${version}, tags repository, stores issue_number and inputs under release context, and maps statuses as follows: failure → message Release failed: ${repo}, severity error, session crashed; cancelled → Release cancelled: ${repo}, severity warn, session crashed; success → Release succeeded: ${repo}, severity info, session ok.postWorkflowDetails() in src/modules/post-workflow-details.js fetches the current workflow run using context.runId and comments on the issue with Publishing: [run#${run_id}](${workflowInfo.html_url}).getGitHubToken() in src/libs/github.js returns process.env.GITHUB_TOKEN; when absent it throws No "GITHUB_TOKEN" environment variable found. Please ensure the workflow is configured correctly.src/publish/authorize-approval.js loads exact nonempty lines from auto-approve-repos.txt into a Set. Its GitHub API requests use APPROVAL_TOKEN, media type application/vnd.github+json, API version 2026-03-10, and paginated issue events/comments with per_page=100 until a page contains fewer than 100 records.authorize-approval.js requires GITHUB_OUTPUT, APPROVAL_TOKEN, APPROVAL_ISSUE_NUMBER, APPROVAL_ISSUE_REPOSITORY, and EXPECTED_REQUEST_DIGEST. For actor sentry-internal-app[bot], authorization requires a live open issue with unchanged title and request digest, a string requester who is one of getsantry[bot] or sentry-release-bot[bot], an allowlisted repository/path, the current accepted event actor equal to sentry-internal-app[bot], and a matching auto-approval attestation authored by github-actions[bot].src/publish/authorize-approval.js, the script first checks repository permission, then re-fetches the live issue and events. Final authorization requires the issue to remain open with unchanged title/digest, a string requester different from the approving actor case-insensitively, a current accepted event, and that event’s actor to equal the approver.authorize-approval.js output is authorized=true plus approval_attestation=${attestation}, binding the current accepted event actor/event ID to the live issue; failed authorization writes authorized=false.getCurrentAcceptedEvent() in src/publish/current-accepted-event.js loads all issue-event pages, selects the latest valid accepted label event, and throws The issue has no current accepted label event if none exists. Its CLI requires APPROVAL_TOKEN, APPROVAL_ISSUE_NUMBER, and APPROVAL_ISSUE_REPOSITORY, then writes the event as JSON to stdout.recordAutoApprovalAttestation() in src/publish/record-auto-approval-attestation.js re-fetches the issue and requires it to remain open, retain the expected title and request digest, and have a requester login equal to autoApprover case-insensitively; otherwise it throws The automated approval request changed before approval. Success writes auto_approval_attestation=${attestation} to GITHUB_OUTPUT.recordCiReadyAttestation() in src/publish/record-ci-ready-attestation.js concurrently fetches the live issue, all issue events, and the authenticated login. It requires the issue to remain open with unchanged title/digest, still contain the accepted label, have a current accepted event, and—when provided—have that event exactly match EXPECTED_ACCEPTED_ACTOR and EXPECTED_ACCEPTED_EVENT_ID; otherwise it throws The approval changed before CI could be marked ready.recordCiReadyAttestation() creates a CI-ready attestation binding the current accepted event and authenticated ciReadyActor, then writes ci_ready_attestation=${attestation} to GITHUB_OUTPUT. getAuthenticatedLogin() calls GitHub’s user endpoint and requires a string login.requestDigestFromEvent() in src/publish/request-digest-from-event.js requires the GitHub event to contain a non-null object issue snapshot and computes its request digest; the CLI reads JSON from GITHUB_EVENT_PATH, requires GITHUB_OUTPUT, and appends request_digest=${digest}.