Dashboard › publish › Distillation
53be46fd-db7e-420f-9ce0-b144a38f828e["lore_tm_v1_3TMkWHVqPo14JFZy0Lwmd0Ft3XjDDfDDE8Sy1Aovkj8","lore_tm_v1_uDwNs_C1PNyxILgXf07Gmor3kc1m-FVKcJ7dAb8Jzvs"]
🔴 (12:55) User’s manual-recovery directive is exact: “Always allow workflow_dispatch for manual recovery.” The current design preserves manual recovery through a protected workflow_dispatch relay whose successful default-branch workflow_run can start .github/workflows/ci-poller.yml.
🔴 (12:55) User’s trusted-execution directive is exact: “Always run trusted code.” .github/workflows/ci-poller.yml checks out ref: ${{ github.event.repository.default_branch }} with persist-credentials: false before running repository scripts.
🔴 (12:55) User’s release-integrity directive is exact: “Never move a release to ci-ready after it changes.”
🔴 (12:55) User requires that a renamed or re-approved issue “never reaches ci-ready”; .github/workflows/ci-poller.yml binds processing to the current accepted event and revalidates after CI, after recording the CI-ready proof, and immediately before applying ci-ready.
đź”´ (12:55) GitHub Actions were pinned to immutable revisions across the modified workflows: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803, actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1, and actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38.
đź”´ (12:55) README.md no longer links a docs/publish-issue-format.md contract. Its security section now states that secret-bearing jobs use protected environment production, which permits only main deployments and disallows administrator bypass.
đź”´ (12:55) README.md states that SENTRY_INTERNAL_APP_PRIVATE_KEY and SENTRY_RELEASE_BOT_PRIVATE_KEY are organization-level secrets; their availability to other repositories is an organization-wide boundary that this repository cannot narrow and Security must manage separately.
đź”´ (12:55) package.json removed scripts generate and check:generated, changed test from yarn check:generated && vitest run to vitest run, and removed dev dependency peggy version 5.1.0.
đź”´ (12:55) src/modules/details-from-context.js replaced generated parsePublishIssueTitle plus isPublishRepository, isReleaseVersion, isPublishPath, and getReleaseRevisionDetails validation with PUBLISH_TITLE_REGEX = /^publish: (?:getsentry\/)?(?<repo>[^/@]+)(?<path>\/[\w./-]+)?@(?<version>[\w.+-]+)$/ and exported parsePublishTitle() plus PUBLISH_TITLE_REGEX.
đź”´ (12:55) detailsFromContext() now throws Invalid publish issue title: ${context.payload.issue.title} when parsePublishTitle() fails, derives path as "." + (titleMatch.path || ""), and no longer independently validates repository, version, or path.
đź”´ (12:55) detailsFromContext() now parses merge targets directly from the issue body using /^Merge target: (?<merge_target>[\w.\-/]+)$/m; absent matches produce merge_target: "".
đź”´ (12:55) src/modules/update-issue.js changed updateTargets() to derive CRAFT_STATE_FILE_PATH as ${process.env.GITHUB_WORKSPACE}/__repo__/.craft-publish-${version}.json rather than accepting stateFilePath. updateIssue({ context, octokit, inputs }) only calls updateTargets() when inputs?.version exists.
đź”´ (12:55) src/publish/update-issue.js now defines async main(), parses PUBLISH_ARGS only when present, warns Could not parse publish inputs; skipping target update on invalid JSON, awaits updateIssue({ context, octokit, inputs }), sets process.exitCode = 1 on failure, and exports { main }.
đź”´ (12:55) Documentation-only formatting changes normalized nested Markdown indentation in docs/key_rotation_vault_changes.md and docs/rfc.md; docs/rfc.md also gained a final newline.
🔴 (12:56) /home/byk/Code/getsentry/publish/src/modules/approval-attestation.js is 259 lines and defines three hidden-comment formats: <!-- publish-approval … -->, <!-- publish-auto-approval … -->, and <!-- publish-ci-ready … -->, with JSON payloads encoded using base64url.
đź”´ (12:56) requestDigest({ body, labels, title }) in src/modules/approval-attestation.js rejects non-string bodies or non-array labels with Invalid publish request, then computes a SHA-256 hex digest over JSON containing exact body, title, and dryRun, where dryRun is true when any label name is dry-run.
đź”´ (12:56) 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.
đź”´ (12:56) parseAttestation(body, prefix) locates the first matching prefix and suffix, decodes the intervening base64url JSON, and returns null for absent, unterminated, or invalid payloads. parseApprovalAttestation(), parseAutoApprovalAttestation(), and parseCiReadyAttestation() additionally require all expected fields to be strings.
đź”´ (12:56) compareEventIds(left, right) strips leading zeroes, treats an all-zero value as "0", rejects non-decimal IDs with Invalid issue event ID, and compares arbitrary-length IDs first by normalized string length and then with localeCompare.
đź”´ (12:56) currentLabeledEvent(events, labelName) considers only labeled events matching the requested label with string/number id and actor.login, selects the greatest ID via compareEventIds(), and returns { actor, eventId }; malformed comparisons or no matching events return null. currentAcceptedEvent() targets accepted, and currentCiReadyEvent() targets ci-ready.
🔴 (12:56) hasApprovalAttestation() accepts only a comment authored by attestationAuthor whose decoded approval attestation matches the current event’s actor and event ID plus the issue’s current digest and title.
🔴 (12:56) hasAutoApprovalAttestation() accepts only a comment authored by attestationAuthor whose decoded auto-approval attestation matches autoApprover plus the issue’s current digest and title.
🔴 (12:56) hasCiReadyAttestation() accepts only a comment authored by attestationAuthor whose decoded CI-ready attestation matches the accepted event actor/ID, the current ci-ready event actor, and the issue’s current digest and title.