Dashboard › publish › Distillation
268a4871-c56e-4f5e-8740-223a06ee3838["lore_tm_v1_EYpBLDzGjPDRmabl-puawRptku99J_wqMmI_9s1AAJg","lore_tm_v1__23IOUcMcmQCilw2fOgBFRKbfoGkX_tLfeaRYMc64CY","lore_tm_v1_PvPwsibF7lGnySMSCrG6sT-Pn0dcAgI7dQsDh5_Cv_M","lore_tm_v1_RkRLleiQ-XWGtNJYQBFQ8rxryzUotF3FSixs_qk57a0","lore_tm_v1_uXCcipiXojtBnRrE0Q_prDgSDqMv_7G-AjmX_eo7Cm4","lore_tm_v1_kXZpJbZGPnWzKBhvoBgmNuY13GH3BVHNVW3viyYwpis","lore_tm_v1_YuzWAnZLQJm6yDqP6nJ4MaliZyHk-uFD3Go2agb08os","lore_tm_v1_2dvjWRq65qR9PFLFIfV2JKqgvsyqiN5NG4RIoNAHn5Y"]
src/publish/__tests__/publish-workflow.js contains 3 workflow security tests: (1) approval/publication is bound to the triggering issue snapshot via node src/publish/request-digest-from-event.js and exactly 4 occurrences of EXPECTED_REQUEST_DIGEST: ${{ steps.request-digest.outputs.request_digest }}; (2) Revalidate approval attestation occurs after Set targets and before Publish using Craft, with REQUIRE_CI_READY_ATTESTATION: "true"; (3) Craft is pinned to an immutable docker://getsentry/craft@sha256:[a-f0-9]{64} digest.before:one, after:one, outer:one, then twice reported bash: line 1: continue: only meaningful in a \for', `while', or `until' loop, before printing before:two, after:two, outer:two; this demonstrates that continue` invoked outside the active loop context did not skip the surrounding iterations.ci-ready: βNever move a release to ci-ready after it changes.β.github/workflows/ci-poller.yml changed its direct trigger from workflow_dispatch with optional attempt input defaulting to "0" to repository_dispatch type ci-poller plus completed workflow_run events from Run CI Status Poller; issues permission changed from read to write..github/workflows/ci-poller.yml now treats repository_dispatch and a successful default-branch run of the protected workflow_dispatch relay as trusted manual-recovery paths. The check-ci job runs when vars.CI_POLLER_HAS_PENDING == 'true', on repository_dispatch, or when github.event_name == 'workflow_run', conclusion is success, and head_branch == github.event.repository.default_branch.APPROVAL_ISSUE_NUMBER, and require the resulting ci-ready event to have the expected actor; the stated invariant is that title/request or approval changes cannot result in ci-ready..github/workflows/auto-approve.yml runs on newly opened issues with contents: read and issues: write; job auto-approve uses ubuntu-latest, environment production, and only runs when the actor is sentry-release-bot[bot] or getsantry[bot] and the issue title starts with publish: ..github/workflows/auto-approve.yml checks out with actions/checkout@11d5960a326750d5838078e36cf38b85af677262 and persist-credentials: false, then creates a GitHub App token using actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1, vars.SENTRY_INTERNAL_APP_ID, and the private-key secret.node src/publish/request-digest-from-event.js, then runs node src/publish/record-auto-approval-attestation.js with APPROVAL_TOKEN: ${{ github.token }}, issue number/repository/title, AUTO_APPROVER: ${{ github.actor }}, and EXPECTED_REQUEST_DIGEST: ${{ steps.request-digest.outputs.request_digest }}..github/workflows/auto-approve.yml posts ${{ steps.attestation.outputs.auto_approval_attestation }} to the issue using gh issue comment; it extracts the repository with sed -n 's/^publish: \(.*\)@.*/\1/p' and adds accepted only if the nonempty repository exactly matches a line in auto-approve-repos.txt via grep -qxF.src/modules/approval-attestation.js defines three hidden-comment formats: <!-- publish-approval β¦ -->, <!-- publish-auto-approval β¦ -->, and <!-- publish-ci-ready β¦ -->; payloads are JSON encoded with base64url.requestDigest({ body, labels, title }) in src/modules/approval-attestation.js rejects non-string body/title or non-array labels with Invalid publish request, then computes a hexadecimal SHA-256 over JSON.stringify({ body, dryRun, title }), where dryRun is true when a label is named dry-run.createApprovalAttestation({ actor, eventId, issue }) records actor, stringified eventId, the current requestDigest, and title; createAutoApprovalAttestation({ autoApprover, issue }) records autoApprover, digest, and title; createCiReadyAttestation({ acceptedEvent, ciReadyActor, issue }) records acceptedActor, stringified acceptedEventId, ciReadyActor, digest, and title.src/modules/approval-attestation.js locates the first matching prefix and --> suffix, base64url-decodes and JSON-parses the payload, and returns null for missing/malformed markers or payloads. Typed parsers require every expected field to be a string.compareEventIds(left, right) strips leading zeroes, normalizes an empty result to "0", rejects nondecimal IDs with Invalid issue event ID, compares decimal magnitude by string length, then uses localeCompare; this avoids unsafe numeric conversion for large GitHub event IDs.currentLabeledEvent(events, labelName) filters for valid labeled events with the requested label, string/number ID, and actor.login, then selects the greatest event ID using compareEventIds; it returns { actor, eventId } or null if none exist or comparison fails. currentAcceptedEvent() and currentCiReadyEvent() specialize it for accepted and ci-ready.hasApprovalAttestation() only accepts a comment authored by attestationAuthor whose parsed approval attestation matches the current event actor/event ID and the issueβs current digest/title. hasAutoApprovalAttestation() additionally requires the specified autoApprover. hasCiReadyAttestation() requires matching accepted actor/event ID, current ci-ready actor, digest, and title.src/publish/validate-approval-attestation.js calls GitHub at https://api.github.com/${path} with bearer APPROVAL_TOKEN, media type application/vnd.github+json, and API version 2026-03-10.getIssue() throws Could not retrieve issue #${issueNumber}: GitHub returned ${response.status} for a failed issue request. getAllPages() retrieves issue events or comments with per_page=100&page=${page}, continuing until a page contains fewer than 100 records and throwing a resource-specific status error on failure.validateApprovalAttestation() concurrently fetches the current issue, all events, and all comments. Approval is valid only if the issue is open, its title and request digest match expected values, it currently has accepted, a current accepted-label event exists, any supplied expectedAcceptedEvent matches that eventβs actor and ID, and a matching approval attestation by attestationAuthor exists.requireCiReadyAttestation is true, validateApprovalAttestation() additionally requires a current ci-ready label/event and a matching CI-ready attestation tying the current accepted event, current CI-ready actor, request digest, and title together.validate-approval-attestation.js requires environment variables APPROVAL_TOKEN, APPROVAL_ISSUE_NUMBER, APPROVAL_ISSUE_REPOSITORY, APPROVAL_ISSUE_TITLE, APPROVAL_ATTESTATION_AUTHOR, and EXPECTED_REQUEST_DIGEST; optional EXPECTED_ACCEPTED_ACTOR plus EXPECTED_ACCEPTED_EVENT_ID bind an expected acceptance event, and REQUIRE_CI_READY_ATTESTATION === "true" enables the CI-ready requirement.src/publish/validate-approval-attestation.js throws The current accepted label has no matching approval attestation; CLI failures are printed and set process.exitCode = 1.src/publish/record-ci-ready-attestation.js gets the authenticated login from GitHubβs user endpoint, throwing on a non-OK response or if GitHub returns no string login.recordCiReadyAttestation() concurrently retrieves the issue, issue events, and authenticated login, then refuses to attest unless the issue remains open, title and request digest remain unchanged, accepted remains present, a current accepted event exists, and any supplied expected accepted actor/event ID still matches. Failure throws The approval changed before CI could be marked ready.recordCiReadyAttestation() calls createCiReadyAttestation({ acceptedEvent, ciReadyActor, issue }); its CLI appends ci_ready_attestation=${attestation}\n to GITHUB_OUTPUT.record-ci-ready-attestation.js requires GITHUB_OUTPUT, APPROVAL_TOKEN, APPROVAL_ISSUE_NUMBER, APPROVAL_ISSUE_REPOSITORY, APPROVAL_ISSUE_TITLE, and EXPECTED_REQUEST_DIGEST; it optionally binds EXPECTED_ACCEPTED_ACTOR and EXPECTED_ACCEPTED_EVENT_ID, and CLI failures are printed with process.exitCode = 1.src/modules/details-from-context.js defines TARGETS_SECTION_PARSER_REGEX to capture an entire contiguous GitHub issue target checklist section, TARGETS_PARSER_REGEX to capture all target IDs, and CHECKED_TARGETS_PARSER_REGEX to capture only checked target IDs; matching is case-insensitive for checkbox x.parsePublishTitle(title) parses with publish-issue-title, prefixes the parsed path with ., and returns null if parsing fails or isPublishRepository, isReleaseVersion, or isPublishPath rejects the result.detailsFromContext({ context }) throws Issue context is not defined if issue context is absent; it emits specific errors for an invalid publish issue title, repository, version, or path.detailsFromContext() sets dry_run to "1" when the issue has label dry-run, otherwise ""; derives merge_target from getReleaseRevisionDetails() and converts "(default)" to ""; extracts checked targets from the matched checklist; and returns parsed title details plus dry_run, merge_target, dot-prefixed path, and optional targets.