Dashboard › publish › Distillation
1bce18a4-285b-40ff-ad8d-26dbb73fadcc["lore_tm_v1_ZFbdSiXctVO86gBk9kmiL4aqJx55ejXOKfBoMAwjY7s","lore_tm_v1_9I9gDth_Y1iaev6ga5DHlsdqgVBCi4QDh05oJ9e0nJY","lore_tm_v1_pZoldvPbElZyAPjU3NEAbKFvdq1eb7mmXkbRsw0b0Gs","lore_tm_v1_m3QiA6O-iHsTDoHOboIRBRdJSqOH3ab9VP8kWepoGF4","lore_tm_v1_gOJ4axWVrssLOZMj0hnI53gNSwNsBMtgNC9VTvIS91A","lore_tm_v1_Uqejt2cbnz4vzTlJeNv3Q-MaRNaEOTogtJKAP4U9pjI","lore_tm_v1_aohg-mhmzaruXTX4X5OCg-PBgFAKpwtZgVFEp-ctH9s","lore_tm_v1_NqsKkGRfYxqUQObHemlMKQwyRdMCLxsREjkrtTUmU1Q","lore_tm_v1_0U3F_VgBHKcP2xUEHj1U2zbK8J_Wa0t24UIMVnjs4rY","lore_tm_v1_HCRKMfBvh1pV5ZTNwS_Nt4TQd5WHGCJg4vdxNK1YtCg"]
Date: Sep 10, 2026
src/publish/validate-approval-attestation.js uses GitHub API version 2026-03-10 with APPROVAL_TOKEN. getIssue({ repository, issueNumber }) fetches repos/${repository}/issues/${issueNumber}; getAllPages({ repository, issueNumber, resource }) fetches issue events or comments with per_page=100, incrementing page until fewer than 100 records are returned. Failed requests throw errors containing the issue number, resource when applicable, and HTTP status (src/publish/validate-approval-attestation.js:8-53).validateApprovalAttestation() concurrently fetches the issue, all events, and all comments, then derives currentAcceptedEvent(events) and currentCiReadyEvent(events). Base approval requires: issue state open; exact issueTitle; an accepted label; a current accepted event; an exact actor/event-ID match when expectedAcceptedEvent is supplied; and hasApprovalAttestation() matching the supplied attestationAuthor. If requireCiReadyAttestation is true, it additionally requires a current ci-ready event, a live ci-ready label, and matching hasCiReadyAttestation() bound to the accepted event, CI-ready event, issue, comments, and author (src/publish/validate-approval-attestation.js:55-101).src/publish/validate-approval-attestation.js CLI requires APPROVAL_TOKEN, APPROVAL_ISSUE_NUMBER, APPROVAL_ISSUE_REPOSITORY, APPROVAL_ISSUE_TITLE, and APPROVAL_ATTESTATION_AUTHOR. It creates expectedAcceptedEvent only when both EXPECTED_ACCEPTED_ACTOR and EXPECTED_ACCEPTED_EVENT_ID exist, enables CI-ready validation only when REQUIRE_CI_READY_ATTESTATION === "true", and throws The current accepted label has no matching approval attestation on invalid proof. Exports are getAllPages, getGitHubResponse, getIssue, main, and validateApprovalAttestation (src/publish/validate-approval-attestation.js:103-153).docs/key_rotation_vault_changes.md documents three existing secret vaults: SDKs/OSS (available to a range of employees, including some who departed), Shared (available to all engineers, including some who departed), and Ops (available to operations, none of whom departed). Its goal is to prevent employee-account publishing and employee access to global credentials; Craft release preparation no longer needs credentials, but actual Craft publishing still does, so publishing should run away from employee machines while approved engineers can trigger it. Approval may vary by project: SDK publishing requires a manager, while repositories such as getsentry/sentry may allow automatic publishing.docs/key_rotation_vault_changes.md considers two credential-management setups: (1) Dedicated Serviceβa configured machine is notified by a GitHub Action about a prepared project/version, holds credentials for every publication service, and publishes via an authorized personβs button press or automatically where permitted; (2) GitHub Actions in Separate Repoβcentralize release actions in one restricted repository, grant admin/write access only to bare-minimum personnel, expose credentials only there, restrict release tokens to a few people, let others request approved releases through structured issues and issue-based workflow triggers with read access, and optionally auto-create release-request issues when a release/* branch is pushed.docs/key_rotation_vault_changes.md says credentials should ideally be accessible to almost nobody because departures force rotation. The OSS vault has redacted users and these key assignments: PyPI Bot β OSS, Crates.io β OSS, DockerHub β OSS, Github (getsentry-bot) β OSS, NPM Bot β SDKs, Cocoapods β SDKs, Nexus β SDKs, Packagist β SDKs, RubyGems β SDKs + Ops, Code Signing Certificate β SDKs, Bintray β SDKs, Bot Apple ITC β Shared, and clojars.org β SDKs. The SDK Low Security vault is assigned to SDK Team users and contains the remaining items from the SDKs vault.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, CHECKED_TARGETS_PARSER_REGEX = /^\s*- \[x\] (\S+)/gim, and PUBLISH_TITLE_REGEX = /^publish: (?:getsentry\/)?(?<repo>[^/@]+)(?<path>\/[\w./-]+)?@(?<version>[\w.+-]+)$/. parsePublishTitle(title) returns the named match groups or null (src/modules/details-from-context.js:1-23).detailsFromContext({ context }) throws Issue context is not defined if issue context is absent and Invalid publish issue title: ${context.payload.issue.title} if title parsing fails. It sets dry_run to "1" when the issue has a dry-run label and "" otherwise; prefixes the parsed path with "."; parses Merge target: β¦ using /^Merge target: (?<merge_target>[\w.\-/]+)$/m, defaulting merge_target to ""; and, when a targets section exists, returns checked target IDs as targets. The result contains the title groups plus dry_run, merge_target, path, and targets (src/modules/details-from-context.js:25-66).src/modules/update-issue.js function updateTargets({ octokit, version, publishRepo, issue_number }) reads ${process.env.GITHUB_WORKSPACE}/__repo__/.craft-publish-${version}.json; it returns without action if the file does not exist. Otherwise it concurrently fetches the issue and parses the UTF-8 Craft state, transforms the issue body, and updates the issue body (src/modules/update-issue.js:1-35).transformIssueBody(craftState, issueBody) in src/modules/update-issue.js replaces the matched targets section: each declared target becomes - [x] ${targetId} when craftState.published[targetId] is truthy or - [ ] ${targetId} otherwise. It tracks declared IDs in a Set, appends every unlisted key from craftState.published with the corresponding checked/unchecked state, and preserves a trailing newline (src/modules/update-issue.js:37-60).updateIssue({ context, octokit, inputs }) concurrently runs updateTargets() when inputs?.version is present and removes the accepted label from the current issue. src/modules/update-issue.js exports updateIssue and transformIssueBody (src/modules/update-issue.js:62-82).postWorkflowDetails({ context, octokit }) in src/modules/post-workflow-details.js retrieves the current workflow run using context.runId, then comments on the issue with Publishing: [run#${run_id}](${workflowInfo.html_url}). The function is the moduleβs default export (src/modules/post-workflow-details.js:1-19).processEndState({ context, octokit, inputs, status }) in src/modules/process-end-state.js gathers repo, version, publishing repository, workflow run_id, issue_number, workflow metadata, and status; posts the corresponding issue comment; closes the issue only when status === "success"; and then calls reportSession({ details, inputs }) (src/modules/process-end-state.js:1-38).githubIssueComment() in src/modules/process-end-state.js maps statuses as follows: failure β Failed to publish. with run logs linked to ?check_suite_focus=true#step:8 and a βBad branch?β deletion link to https://github.com/getsentry/${repo}/branches/all?query=${encodeURIComponent(version)}; cancelled β Publish workflow cancelled. with the same logs/deletion-link structure; success β Published successfully: [run#${run_id}](${workflowInfo.html_url}); any other status throws Unknown status: '${status}' (src/modules/process-end-state.js:40-68).reportSession() in src/modules/process-end-state.js initializes @sentry/node with dsn: process.env.SENTRY_DSN and release ${details.repo}@${details.version}. Within a Sentry scope it tags repository, sets release context containing issue_number and inputs, captures the mapped message/severity, starts and ends a session with the mapped status, then awaits Sentry.close() (src/modules/process-end-state.js:70-92).sentryInfoFromDetails() maps release outcomes: failure β message Release failed: ${repo}, severity error, session status crashed; cancelled β Release cancelled: ${repo}, severity warn, status crashed; success β Release succeeded: ${repo}, severity info, status ok; unknown statuses throw Unknown status: '${status}' (src/modules/process-end-state.js:94-119).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. The module exports getGitHubToken (src/libs/github.js:1-19).src/publish/inputs.js calls detailsFromContext({ context: github.context }) and publishes the result through core.setOutput("result", result). It invokes inputs() immediately (src/publish/inputs.js:1-12).src/publish/post-result.js obtains github.context, creates Octokit with github.getOctokit(getGitHubToken()), parses process.env.PUBLISH_ARGS as JSON, takes the status from the first positional CLI argument, and calls processEndState({ context, octokit, inputs, status }) (src/publish/post-result.js:1-11).src/publish/post-workflow-details.js obtains github.context, creates Octokit using getGitHubToken(), and calls postWorkflowDetails({ context, octokit }) (src/publish/post-workflow-details.js:1-8).