DashboardpublishSession 1KMTeZxofBtJ

Session 1KMTeZxofBtJ

Full ID: 1KMTeZxofBtJ5ipOK
Messages: 35
Distillations: 6
Time range: 2026-08-29 13:07:19 — 2026-08-29 13:33:46

Conversation (35 messages)

Perform a read-only, substantive correctness review of the current working tree's release approval gate. Focus on .github/workflows/publish.yml, .github/workflows/ci-poller.yml, src/modules/approval-attestation.js, src/publish/{authorize-approval,current-accepted-event,record-ci-ready-attestation,validate-approval-attestation}.js and their tests. Verify these invariants: (1) only target-repo write/maintain/admin or exact allowlisted getsantry bot approvals proceed, (2) title and original accepted-label event remain bound from poll start through ci-ready, even across a quick re-approval, (3) ci-ready triggers cannot be manually forged, (4) workflow_dispatch remains allowed, (5) failures clean up accepted and do not publish. Do not edit. Return a substantive report with file:line evidence. Classify each finding PASS, CONCERN, or MUST-FIX and end exactly MERGE or DO-NOT-MERGE. Do not return an empty response.

gateway · 2026-08-29 13:07:19
result
<path>/home/byk/Code/getsentry/publish</path>
<type>directory</type>
<entries>
.eslintrc.js
.git/
.github/
.gitignore
.lore.md
AGENTS.md
auto-approve-repos.txt
docs/
LICENSE
node_modules/
package.json
README.md
src/
vitest.config.js
yarn.lock

(15 entries)
</entries>
gateway · 2026-08-29 13:10:36
result
/home/byk/Code/getsentry/publish/src/publish/current-accepted-event.js
/home/byk/Code/getsentry/publish/src/publish/record-ci-ready-attestation.js
/home/byk/Code/getsentry/publish/src/publish/validate-approval-attestation.js
/home/byk/Code/getsentry/publish/src/publish/__tests__/current-accepted-event.js
/home/byk/Code/getsentry/publish/src/publish/__tests__/record-ci-ready-attestation.js
/home/byk/Code/getsentry/publish/src/publish/__tests__/validate-approval-attestation.js
/home/byk/Code/getsentry/publish/src/publish/__tests__/authorize-approval.js
/home/byk/Code/getsentry/publish/src/publish/authorize-approval.js
/home/byk/Code/getsentry/publish/src/modules/approval-attestation.js
/home/byk/Code/getsentry/publish/src/modules/__tests__/approval-attestation.js
/home/byk/Code/getsentry/publish/src/modules/__tests__/approval-authorizer.js
gateway · 2026-08-29 13:10:42
result
<path>/home/byk/Code/getsentry/publish/.github/workflows/publish.yml</path>
<type>file</type>
<content>
1: name: Publish
2: on:
3:   issues:
4:     types: [labeled]
5: 
6: concurrency:
7:   # Use the issue title (e.g. "publish: getsentry/foo@1.2.3") so duplicate
8:   # issues for the same repo@version share a concurrency group.
9:   group: ${{ github.event.issue.title }}
10:   cancel-in-progress: false
11: 
12: permissions:
13:   contents: read
14:   issues: write
15:   packages: write
16: 
17: jobs:
18:   # When accepted is added to a publish issue:
19:   #   - Add ci-pending (and remove ci-failed if retrying)
20:   #   - Enable the poller via CI_POLLER_HAS_PENDING=true
21:   #   - Comment on the issue
22:   #   - Trigger the poller immediately so we don't wait for the next cron tick
23:   # The publish job below requires ci-ready, so it will not fire until the
24:   # poller flips ci-pending → ci-ready (which also prevents publishing without
25:   # CI verification in the ...
gateway · 2026-08-29 13:10:45
result
<path>/home/byk/Code/getsentry/publish/.github/workflows/ci-poller.yml</path>
<type>file</type>
<content>
1: name: CI Status Poller
2: 
3: on:
4:   schedule:
5:     - cron: "*/5 * * * *"
6:   workflow_dispatch:
7:     inputs:
8:       attempt:
9:         description: Self-dispatch attempt counter (internal use)
10:         required: false
11:         default: "0"
12: 
13: permissions:
14:   contents: read
15:   issues: write
16: 
17: jobs:
18:   check-ci:
19:     runs-on: ubuntu-latest
20:     environment: production
21:     # Skip entirely (no runner provisioned) when there's nothing to check.
22:     # Set to "true" by ci-pending.yml, reset to "false" here when done.
23:     # Always allow workflow_dispatch for manual recovery.
24:     if: vars.CI_POLLER_HAS_PENDING == 'true' || github.event_name == 'workflow_dispatch'
25:     concurrency:
26:       group: ci-status-poller
27:       cancel-in-progress: false
28:     steps:
29:       - name: Get publish code
30:         uses: ...
gateway · 2026-08-29 13:10:47
result
<path>/home/byk/Code/getsentry/publish/src/modules/approval-attestation.js</path>
<type>file</type>
<content>
1: const APPROVAL_ATTESTATION_PREFIX = "<!-- publish-approval ";
2: const CI_READY_ATTESTATION_PREFIX = "<!-- publish-ci-ready ";
3: const ATTESTATION_SUFFIX = " -->";
4: 
5: function createAttestation(prefix, value) {
6:   return `${prefix}${Buffer.from(JSON.stringify(value)).toString(
7:     "base64url"
8:   )}${ATTESTATION_SUFFIX}`;
9: }
10: 
11: function createApprovalAttestation({ actor, eventId, title }) {
12:   return createAttestation(APPROVAL_ATTESTATION_PREFIX, {
13:     actor,
14:     eventId: String(eventId),
15:     title,
16:   });
17: }
18: 
19: function createCiReadyAttestation({ acceptedEvent, ciReadyActor, title }) {
20:   return createAttestation(CI_READY_ATTESTATION_PREFIX, {
21:     acceptedActor: acceptedEvent.actor,
22:     acceptedEventId: String(acceptedEvent.eventId),
23:     ciReadyActor,
24:     title,
25:   });
26: }
27: 
28: function ...
gateway · 2026-08-29 13:10:53
result
<path>/home/byk/Code/getsentry/publish/src/publish/authorize-approval.js</path>
<type>file</type>
<content>
1: const fs = require("fs");
2: const {
3:   createApprovalAttestation,
4:   currentAcceptedEvent,
5: } = require("../modules/approval-attestation.js");
6: const { authorizeApproval } = require("../modules/approval-authorizer.js");
7: 
8: function getAutoApprovedRepositories() {
9:   return new Set(
10:     fs
11:       .readFileSync("auto-approve-repos.txt", "utf8")
12:       .split(/\r?\n/)
13:       .filter(Boolean)
14:   );
15: }
16: 
17: async function getPermission({ owner, repository, username }) {
18:   const response = await getGitHubResponse(
19:     `repos/${encodeURIComponent(owner)}/${encodeURIComponent(
20:       repository
21:     )}/collaborators/${encodeURIComponent(username)}/permission`
22:   );
23: 
24:   if (!response.ok) {
25:     throw new Error(
26:       `Could not retrieve ${username}'s permission for ${owner}/${repository}: GitHub returned ...
gateway · 2026-08-29 13:12:11
result
<path>/home/byk/Code/getsentry/publish/src/publish/current-accepted-event.js</path>
<type>file</type>
<content>
1: const { currentAcceptedEvent } = require("../modules/approval-attestation.js");
2: const { getAllPages } = require("./validate-approval-attestation.js");
3: 
4: async function getCurrentAcceptedEvent({
5:   getIssueEvents,
6:   issueNumber,
7:   repository,
8: }) {
9:   const event = currentAcceptedEvent(
10:     await getIssueEvents({ repository, issueNumber })
11:   );
12: 
13:   if (!event) {
14:     throw new Error("The issue has no current accepted label event");
15:   }
16: 
17:   return event;
18: }
19: 
20: async function main() {
21:   for (const name of [
22:     "APPROVAL_TOKEN",
23:     "APPROVAL_ISSUE_NUMBER",
24:     "APPROVAL_ISSUE_REPOSITORY",
25:   ]) {
26:     if (!process.env[name]) {
27:       throw new Error(`No "${name}" environment variable found`);
28:     }
29:   }
30: 
31:   const event = await getCurrentAcceptedEvent({
32:     getIssueEvents: ({ ...
gateway · 2026-08-29 13:12:47
result
<path>/home/byk/Code/getsentry/publish/src/publish/record-ci-ready-attestation.js</path>
<type>file</type>
<content>
1: const fs = require("fs");
2: const {
3:   createCiReadyAttestation,
4:   currentAcceptedEvent,
5: } = require("../modules/approval-attestation.js");
6: const {
7:   getAllPages,
8:   getGitHubResponse,
9:   getIssue,
10: } = require("./validate-approval-attestation.js");
11: 
12: async function getAuthenticatedLogin() {
13:   const response = await getGitHubResponse("user");
14: 
15:   if (!response.ok) {
16:     throw new Error(
17:       `Could not retrieve the authenticated user: GitHub returned ${response.status}`
18:     );
19:   }
20: 
21:   const { login } = await response.json();
22: 
23:   if (typeof login !== "string") {
24:     throw new Error("GitHub returned no authenticated user login");
25:   }
26: 
27:   return login;
28: }
29: 
30: async function recordCiReadyAttestation({
31:   expectedAcceptedEvent,
32:   getAuthenticatedLogin,
33:   getIssue,
34:  ...
gateway · 2026-08-29 13:12:53
result
<path>/home/byk/Code/getsentry/publish/src/publish/validate-approval-attestation.js</path>
<type>file</type>
<content>
1: const {
2:   currentAcceptedEvent,
3:   currentCiReadyEvent,
4:   hasApprovalAttestation,
5:   hasCiReadyAttestation,
6: } = require("../modules/approval-attestation.js");
7: 
8: async function getGitHubResponse(path) {
9:   return fetch(`https://api.github.com/${path}`, {
10:     headers: {
11:       Accept: "application/vnd.github+json",
12:       Authorization: `Bearer ${process.env.APPROVAL_TOKEN}`,
13:       "X-GitHub-Api-Version": "2026-03-10",
14:     },
15:   });
16: }
17: 
18: async function getIssue({ repository, issueNumber }) {
19:   const response = await getGitHubResponse(
20:     `repos/${repository}/issues/${issueNumber}`
21:   );
22: 
23:   if (!response.ok) {
24:     throw new Error(
25:       `Could not retrieve issue #${issueNumber}: GitHub returned ${response.status}`
26:     );
27:   }
28: 
29:   return response.json();
30: }
31: 
32: async ...
gateway · 2026-08-29 13:12:59
result
<path>/home/byk/Code/getsentry/publish/src/modules/approval-authorizer.js</path>
<type>file</type>
<content>
1: const ALLOWED_ROLE_NAMES = new Set(["write", "maintain", "admin"]);
2: const AUTO_APPROVER = "getsantry[bot]";
3: const { parsePublishTitle } = require("./details-from-context.js");
4: 
5: async function authorizeApproval({
6:   actor,
7:   issueTitle,
8:   getPermission,
9:   autoApprovedRepositories = new Set(),
10: }) {
11:   const title = parsePublishTitle(issueTitle);
12: 
13:   if (!title) {
14:     return { authorized: false, repository: null };
15:   }
16: 
17:   const repository = `getsentry/${title.repo}`;
18:   const releasePath = `${repository}${title.path || ""}`;
19: 
20:   if (actor === AUTO_APPROVER && autoApprovedRepositories.has(releasePath)) {
21:     return { authorized: true, repository };
22:   }
23: 
24:   if (actor === AUTO_APPROVER) {
25:     return { authorized: false, repository };
26:   }
27: 
28:   const { role_name } = await getPermission({
29:  ...
gateway · 2026-08-29 13:13:02
result
<path>/home/byk/Code/getsentry/publish/src/modules/__tests__/approval-attestation.js</path>
<type>file</type>
<content>
1: import { describe, expect, test } from "vitest";
2: 
3: const {
4:   createApprovalAttestation,
5:   createCiReadyAttestation,
6:   currentAcceptedEvent,
7:   hasApprovalAttestation,
8:   hasCiReadyAttestation,
9:   parseApprovalAttestation,
10: } = require("../approval-attestation.js");
11: 
12: describe("approval attestations", () => {
13:   test("matches the latest accepted event to a trusted attestation", () => {
14:     const attestation = createApprovalAttestation({
15:       actor: "contractor",
16:       eventId: "200",
17:       title: "publish: getsentry/relay/py@1.2.3",
18:     });
19:     const event = currentAcceptedEvent([
20:       {
21:         actor: { login: "contractor" },
22:         event: "labeled",
23:         id: "100",
24:         label: { name: "accepted" },
25:       },
26:       {
27:         actor: { login: "contractor" },
28:         ...
gateway · 2026-08-29 13:13:06
result
<path>/home/byk/Code/getsentry/publish/src/modules/__tests__/approval-authorizer.js</path>
<type>file</type>
<content>
1: import { describe, expect, test, vi } from "vitest";
2: 
3: const { authorizeApproval } = require("../approval-authorizer.js");
4: 
5: const issueTitle = "publish: getsentry/sentry-javascript@10.0.0";
6: 
7: describe("authorizeApproval", () => {
8:   test("allows the auto-approval bot for an allowlisted release without a target repository lookup", async () => {
9:     const getPermission = vi.fn();
10: 
11:     await expect(
12:       authorizeApproval({
13:         actor: "getsantry[bot]",
14:         issueTitle,
15:         getPermission,
16:         autoApprovedRepositories: new Set(["getsentry/sentry-javascript"]),
17:       })
18:     ).resolves.toEqual({
19:       authorized: true,
20:       repository: "getsentry/sentry-javascript",
21:     });
22: 
23:     expect(getPermission).not.toHaveBeenCalled();
24:   });
25: 
26:   test("rejects the auto-approval bot ...
gateway · 2026-08-29 13:13:15
result
<path>/home/byk/Code/getsentry/publish/src/publish/__tests__/authorize-approval.js</path>
<type>file</type>
<content>
1: import { afterEach, describe, expect, test, vi } from "vitest";
2: import fs from "fs";
3: const {
4:   getAutoApprovedRepositories,
5:   main,
6: } = require("../authorize-approval.js");
7: 
8: const originalEnvironment = { ...process.env };
9: 
10: afterEach(() => {
11:   process.env = { ...originalEnvironment };
12:   vi.restoreAllMocks();
13:   vi.resetModules();
14: });
15: 
16: function jsonResponse(json) {
17:   return { ok: true, json: vi.fn().mockResolvedValue(json) };
18: }
19: 
20: async function runAuthorization({ actor, issueTitle, responses }) {
21:   process.env.GITHUB_OUTPUT = "/tmp/github-output";
22:   process.env.APPROVAL_TOKEN = "release-bot-token";
23:   process.env.APPROVAL_ACTOR = actor;
24:   process.env.APPROVAL_ISSUE_NUMBER = "123";
25:   process.env.APPROVAL_ISSUE_REPOSITORY = "getsentry/publish";
26:   process.env.APPROVAL_ISSUE_TITLE = ...
gateway · 2026-08-29 13:13:20
result
<path>/home/byk/Code/getsentry/publish/src/publish/__tests__/current-accepted-event.js</path>
<type>file</type>
<content>
1: import { describe, expect, test, vi } from "vitest";
2: 
3: const { getCurrentAcceptedEvent } = require("../current-accepted-event.js");
4: 
5: describe("getCurrentAcceptedEvent", () => {
6:   test("returns the latest accepted-label event", async () => {
7:     await expect(
8:       getCurrentAcceptedEvent({
9:         getIssueEvents: vi.fn().mockResolvedValue([
10:           {
11:             actor: { login: "contractor" },
12:             event: "labeled",
13:             id: "100",
14:             label: { name: "accepted" },
15:           },
16:           {
17:             actor: { login: "contractor" },
18:             event: "labeled",
19:             id: "200",
20:             label: { name: "accepted" },
21:           },
22:         ]),
23:         issueNumber: "123",
24:         repository: "getsentry/publish",
25:       })
26:     ).resolves.toEqual({ ...
gateway · 2026-08-29 13:13:24
result
<path>/home/byk/Code/getsentry/publish/src/publish/__tests__/record-ci-ready-attestation.js</path>
<type>file</type>
<content>
1: import { describe, expect, test, vi } from "vitest";
2: 
3: const {
4:   parseCiReadyAttestation,
5: } = require("../../modules/approval-attestation.js");
6: const {
7:   recordCiReadyAttestation,
8: } = require("../record-ci-ready-attestation.js");
9: 
10: describe("recordCiReadyAttestation", () => {
11:   test("binds the current approval to the app that will add ci-ready", async () => {
12:     const title = "publish: getsentry/sentry-javascript@10.0.0";
13: 
14:     await expect(
15:       recordCiReadyAttestation({
16:         getAuthenticatedLogin: vi
17:           .fn()
18:           .mockResolvedValue("sentry-internal-app[bot]"),
19:         getIssue: vi.fn().mockResolvedValue({
20:           labels: [{ name: "accepted" }],
21:           title,
22:         }),
23:         getIssueEvents: vi.fn().mockResolvedValue([
24:           {
25:             ...
gateway · 2026-08-29 13:13:28
result
<path>/home/byk/Code/getsentry/publish/src/publish/__tests__/validate-approval-attestation.js</path>
<type>file</type>
<content>
1: import { afterEach, describe, expect, test, vi } from "vitest";
2: 
3: const {
4:   validateApprovalAttestation,
5: } = require("../validate-approval-attestation.js");
6: const {
7:   createApprovalAttestation,
8:   createCiReadyAttestation,
9: } = require("../../modules/approval-attestation.js");
10: 
11: afterEach(() => {
12:   vi.restoreAllMocks();
13: });
14: 
15: function jsonResponse(json) {
16:   return { ok: true, json: vi.fn().mockResolvedValue(json) };
17: }
18: 
19: describe("validateApprovalAttestation", () => {
20:   test("accepts the current accepted event and its trusted attestation", async () => {
21:     const title = "publish: getsentry/sentry-javascript@10.0.0";
22:     const attestation = createApprovalAttestation({
23:       actor: "contractor",
24:       eventId: "100",
25:       title,
26:     });
27:     global.fetch = vi
28:       ...
gateway · 2026-08-29 13:13:41
result
 M .github/workflows/ci-poller.yml
 M .github/workflows/publish.yml
 M AGENTS.md
 M src/libs/__tests__/github.js
 M src/libs/github.js
 M src/modules/__tests__/details-from-context.js
 M src/modules/details-from-context.js
 M src/modules/post-workflow-details.js
 M src/modules/process-end-state.js
 M src/modules/update-issue.js
 M src/publish/inputs.js
 M src/publish/post-result.js
 M src/publish/post-workflow-details.js
 M src/publish/update-issue.js
?? .lore.md
?? src/modules/__tests__/approval-attestation.js
?? src/modules/__tests__/approval-authorizer.js
?? src/modules/approval-attestation.js
?? src/modules/approval-authorizer.js
?? src/publish/__tests__/
?? src/publish/authorize-approval.js
?? src/publish/current-accepted-event.js
?? src/publish/record-ci-ready-attestation.js
?? src/publish/validate-approval-attestation.js
diff --git a/.github/workflows/ci-poller.yml b/.github/workflows/ci-poller.yml
index a3db430..a557c2d 100644
--- a/.github/workflows/ci-poller.yml
+++ ...
gateway · 2026-08-29 13:14:33
result
<path>/home/byk/Code/getsentry/publish/src/publish/update-issue.js</path>
<type>file</type>
<content>
1: const { updateIssue } = require("../modules/update-issue.js");
2: const { getGitHubToken } = require("../libs/github");
3: const github = require("@actions/github");
4: 
5: const context = github.context;
6: const octokit = github.getOctokit(getGitHubToken());
7: const inputs = JSON.parse(process.env.PUBLISH_ARGS);
8: 
9: updateIssue({ context, octokit, inputs });

(End of file - total 9 lines)
</content>
gateway · 2026-08-29 13:16:16
result
<path>/home/byk/Code/getsentry/publish/.github/workflows/auto-approve.yml</path>
<type>file</type>
<content>
1: name: auto-approve non-sdks
2: on:
3:   issues:
4:     types: [opened]
5: 
6: permissions:
7:   contents: read
8: 
9: jobs:
10:   auto-approve:
11:     runs-on: ubuntu-latest
12:     if: |
13:       (github.actor == 'sentry-release-bot[bot]' || github.actor == 'getsantry[bot]') &&
14:       startsWith(github.event.issue.title, 'publish: ')
15:     steps:
16:       - uses: actions/checkout@v4
17:         with:
18:           sparse-checkout: auto-approve-repos.txt
19:           sparse-checkout-cone-mode: false
20:       - name: Get auth token
21:         id: token
22:         uses: actions/create-github-app-token@v3
23:         with:
24:           client-id: ${{ vars.SENTRY_INTERNAL_APP_ID }}
25:           private-key: ${{ secrets.SENTRY_INTERNAL_APP_PRIVATE_KEY }}
26:       - name: Auto-approve if repo is in the list
27:         env:
28:           GH_TOKEN: ${{ ...
gateway · 2026-08-29 13:16:26
result
<path>/home/byk/Code/getsentry/publish/src/modules/update-issue.js</path>
<type>file</type>
<content>
1: const fs = require("fs");
2: const {
3:   TARGETS_SECTION_PARSER_REGEX,
4:   TARGETS_PARSER_REGEX,
5: } = require("./details-from-context");
6: 
7: async function updateTargets({ octokit, version, publishRepo, issue_number }) {
8:   const CRAFT_STATE_FILE_PATH = `${process.env.GITHUB_WORKSPACE}/__repo__/.craft-publish-${version}.json`;
9: 
10:   if (!fs.existsSync(CRAFT_STATE_FILE_PATH)) {
11:     return;
12:   }
13: 
14:   const issueRequest = octokit.rest.issues.get({
15:     ...publishRepo,
16:     issue_number,
17:   });
18: 
19:   const craftStateRequest = fs.promises
20:     .readFile(CRAFT_STATE_FILE_PATH, { encoding: "utf-8" })
21:     .then((data) => JSON.parse(data));
22: 
23:   const [{ data: issue }, craftState] = await Promise.all([
24:     issueRequest,
25:     craftStateRequest,
26:   ]);
27: 
28:   const newIssueBody = transformIssueBody(craftState, issue.body);
29: ...
gateway · 2026-08-29 13:16:31
result
<path>/home/byk/Code/getsentry/publish/src/publish/post-result.js</path>
<type>file</type>
<content>
1: const processEndState = require("../modules/process-end-state");
2: const { getGitHubToken } = require("../libs/github");
3: const github = require("@actions/github");
4: 
5: const context = github.context;
6: const octokit = github.getOctokit(getGitHubToken());
7: const inputs = JSON.parse(process.env.PUBLISH_ARGS);
8: const args = process.argv.slice(2);
9: const status = args[0];
10: 
11: processEndState({ context, octokit, inputs, status });

(End of file - total 11 lines)
</content>
gateway · 2026-08-29 13:16:37
result
<path>/home/byk/Code/getsentry/publish/src/modules/process-end-state.js</path>
<type>file</type>
<content>
1: const Sentry = require("@sentry/node");
2: 
3: async function processEndState({ context, octokit, inputs, status }) {
4:   const { repo, version } = inputs;
5:   const { repo: publishRepo, runId: run_id } = context;
6:   const { number: issue_number } = context.payload.issue;
7:   const workflowInfo = (
8:     await octokit.rest.actions.getWorkflowRun({
9:       ...publishRepo,
10:       run_id,
11:     })
12:   ).data;
13: 
14:   const details = {
15:     repo,
16:     version,
17:     publishRepo,
18:     run_id,
19:     issue_number,
20:     workflowInfo,
21:     status,
22:   };
23: 
24:   await postIssueComment({
25:     octokit,
26:     details,
27:   });
28: 
29:   if (status === "success") {
30:     await octokit.rest.issues.update({
31:       ...publishRepo,
32:       issue_number,
33:       state: "closed",
34:     });
35:   }
36: 
37:   await reportSession({ ...
gateway · 2026-08-29 13:16:42
result
<path>/home/byk/Code/getsentry/publish/src/modules/details-from-context.js</path>
<type>file</type>
<content>
1: /**
2:  * Matches the entire "Targets" section of a github publish issue body.
3:  */
4: const TARGETS_SECTION_PARSER_REGEX =
5:   /^(?!### Targets$\s)(?: *- \[[ xX]\] \S+\s*$(?:\r?\n)?)+/m;
6: 
7: /**
8:  * Matches all targets of a github publish issue body in a section that was already matched and extracted with `TARGETS_PARSER_REGEX`.
9:  * The "id" of the targets is captured within a capture group.
10:  */
11: const TARGETS_PARSER_REGEX = /^\s*- \[[ x]\] (\S+)/gim;
12: 
13: /**
14:  * Matches checked targets of a github publish issue body in a section that was already matched and extracted with `TARGETS_PARSER_REGEX`.
15:  * The "id" of the targets is captured within a capture group.
16:  */
17: const CHECKED_TARGETS_PARSER_REGEX = /^\s*- \[x\] (\S+)/gim;
18: const PUBLISH_TITLE_REGEX =
19:   /^publish: ...
gateway · 2026-08-29 13:17:22
result
yarn run v1.22.22
$ vitest run src/modules/__tests__/approval-attestation.js src/modules/__tests__/approval-authorizer.js src/publish/__tests__/authorize-approval.js src/publish/__tests__/current-accepted-event.js src/publish/__tests__/record-ci-ready-attestation.js src/publish/__tests__/validate-approval-attestation.js

 RUN  v4.0.17 /home/byk/Code/getsentry/publish

 ✓ src/publish/__tests__/validate-approval-attestation.js (7 tests) 23ms
 ✓ src/modules/__tests__/approval-authorizer.js (14 tests) 19ms
 ✓ src/publish/__tests__/current-accepted-event.js (2 tests) 20ms
 ✓ src/modules/__tests__/approval-attestation.js (4 tests) 18ms
 ✓ src/publish/__tests__/record-ci-ready-attestation.js (3 ...
gateway · 2026-08-29 13:18:58
result
yarn run v1.22.22
$ vitest run

 RUN  v4.0.17 /home/byk/Code/getsentry/publish

 ✓ src/publish/__tests__/validate-approval-attestation.js (7 tests) 24ms
 ✓ src/publish/__tests__/current-accepted-event.js (2 tests) 36ms
 ✓ src/modules/__tests__/approval-attestation.js (4 tests) 18ms
 ✓ src/publish/__tests__/authorize-approval.js (3 tests) 35ms
 ✓ src/modules/__tests__/approval-authorizer.js (14 tests) 31ms
 ✓ src/modules/__tests__/process-end-state.js (4 tests) 57ms
 ✓ src/modules/__tests__/post-workflow-details.js (1 test) 12ms
 ✓ ...
gateway · 2026-08-29 13:19:08
result
<path>/home/byk/Code/getsentry/publish/package.json</path>
<type>file</type>
<content>
1: {
2:   "name": "publish",
3:   "private": true,
4:   "version": "0.0.1",
5:   "description": "Approval-based publishing system for Sentry",
6:   "main": "index.js",
7:   "repository": "git@github.com:getsentry/publish.git",
8:   "author": "Sentry Open Source <oss@sentry.io>",
9:   "license": "Apache-2.0",
10:   "volta": {
11:     "node": "24.0.0",
12:     "yarn": "1.22.22"
13:   },
14:   "resolutions": {
15:     "undici": "^6.23.0"
16:   },
17:   "scripts": {
18:     "test": "vitest run",
19:     "test:watch": "vitest",
20:     "lint": "eslint src .github --ignore-pattern '!.github'",
21:     "prettier": "prettier --write src"
22:   },
23:   "devDependencies": {
24:     "eslint": "^8.9.0",
25:     "eslint-config-prettier": "^8.3.0",
26:     "eslint-plugin-yml": "^0.13.0",
27:     "prettier": "^2.2.1",
28:     "vitest": "^4.1.0"
29:   },
30:   "dependencies": {
31:     "@actions/core": ...
gateway · 2026-08-29 13:19:14
result
yarn run v1.22.22
$ /home/byk/Code/getsentry/publish/node_modules/.bin/eslint .github/workflows/publish.yml .github/workflows/ci-poller.yml src/modules/approval-attestation.js src/modules/approval-authorizer.js src/publish/authorize-approval.js src/publish/current-accepted-event.js src/publish/record-ci-ready-attestation.js src/publish/validate-approval-attestation.js src/modules/__tests__/approval-attestation.js src/modules/__tests__/approval-authorizer.js src/publish/__tests__/authorize-approval.js src/publish/__tests__/current-accepted-event.js src/publish/__tests__/record-ci-ready-attestation.js src/publish/__tests__/validate-approval-attestation.js

/home/byk/Code/getsentry/publish/.github/workflows/ci-poller.yml
  0:0  warning  File ignored by default.  Use a negated ignore pattern (like "--ignore-pattern '!<relative/path/to/filename>'") to override

/home/byk/Code/getsentry/publish/.github/workflows/publish.yml
  0:0  warning  File ignored by default. ...
gateway · 2026-08-29 13:19:25
result
yarn run v1.22.22
$ /home/byk/Code/getsentry/publish/node_modules/.bin/eslint .github/workflows/publish.yml .github/workflows/ci-poller.yml --ignore-pattern !.github
Done in 0.73s.
gateway · 2026-08-29 13:20:03
result
<path>/home/byk/Code/getsentry/publish/auto-approve-repos.txt</path>
<type>file</type>
<content>
1: getsentry/arroyo
2: getsentry/auto-type-annotate
3: getsentry/devenv
4: getsentry/infra-event-notifier
5: getsentry/jest-sentry-environment
6: getsentry/json-schema-diff
7: getsentry/js-source-scopes
8: getsentry/objectstore/clients
9: getsentry/ophio
10: getsentry/pdb
11: getsentry/pyo3-python-tracing-subscriber
12: getsentry/pytest-sentry
13: getsentry/relay/py
14: getsentry/responses
15: getsentry/rust-proguard
16: getsentry/rust-sourcemap
17: getsentry/rust-usage-accountant
18: getsentry/script-runner
19: getsentry/sentry-api-schema
20: getsentry/sentry-forked-djangorestframework-stubs
21: getsentry/sentry-forked-django-stubs
22: getsentry/sentry-forked-jsonnet
23: getsentry/sentry-infra-tools
24: getsentry/sentry-kafka-management
25: getsentry/sentry-kafka-schemas
26: getsentry/sentry-protos
27: getsentry/sentry-redis-tools
28: getsentry/service-registry
29: getsentry/skrooge
30: ...
gateway · 2026-08-29 13:22:57
result
bash: line 1: $(printf injected >&2): syntax error: operand expected (error token is "$(printf injected >&2)")
gateway · 2026-08-29 13:24:11
result
<path>/home/byk/Code/getsentry/publish/src/publish/inputs.js</path>
<type>file</type>
<content>
1: const github = require("@actions/github");
2: const core = require("@actions/core");
3: const { detailsFromContext } = require("../modules/details-from-context");
4: 
5: async function inputs() {
6:   const result = await detailsFromContext({
7:     context: github.context,
8:   });
9:   core.setOutput("result", result);
10: }
11: 
12: inputs();

(End of file - total 12 lines)
</content>
gateway · 2026-08-29 13:28:44
result
Found 65 matches
/home/byk/Code/getsentry/publish/src/publish/__tests__/validate-approval-attestation.js:
  Line 44:           { body: attestation, user: { login: "github-actions[bot]" } },

  Line 50:         attestationAuthor: "github-actions[bot]",

  Line 83:           { body: attestation, user: { login: "github-actions[bot]" } },

  Line 89:         attestationAuthor: "github-actions[bot]",

  Line 119:           { body: attestation, user: { login: "github-actions[bot]" } },

  Line 125:         attestationAuthor: "github-actions[bot]",

  Line 163:           { body: attestation, user: { login: "github-actions[bot]" } },

  Line 169:         attestationAuthor: "github-actions[bot]",

  Line 207:           { body: attestation, user: { login: "github-actions[bot]" } },

  Line 213:         attestationAuthor: "github-actions[bot]",

  Line 263:             user: { login: "github-actions[bot]" },

  Line 267:             user: { login: "github-actions[bot]" },

  Line 274:         ...
gateway · 2026-08-29 13:29:52
result
<path>/home/byk/Code/getsentry/publish/src/modules/post-workflow-details.js</path>
<type>file</type>
<content>
1: async function postWorkflowDetails({ context, octokit }) {
2:   const { repo: publishRepo, runId: run_id } = context;
3:   const { number: issue_number } = context.payload.issue;
4: 
5:   const workflowInfo = (
6:     await octokit.rest.actions.getWorkflowRun({
7:       ...publishRepo,
8:       run_id,
9:     })
10:   ).data;
11: 
12:   return octokit.rest.issues.createComment({
13:     ...publishRepo,
14:     issue_number,
15:     body: `Publishing: [run#${run_id}](${workflowInfo.html_url})`,
16:   });
17: }
18: 
19: module.exports = postWorkflowDetails;

(End of file - total 19 lines)
</content>
gateway · 2026-08-29 13:33:44

Read-only release-gate review

MUST-FIX — early publish failures retain accepted

publish.yml runs setup and dependency installation before it produces steps.inputs.outputs.result (.github/workflows/publish.yml:221-233). If either step fails, the failure handler still runs (.github/workflows/publish.yml:383-388) but passes an empty PUBLISH_ARGS.

src/publish/update-issue.js:7 parses that value before calling updateIssue; it throws, so updateIssue never reaches its accepted removal at src/modules/update-issue.js:67-74. The issue retains both accepted and ci-ready. A rerun can then validate the old attestations and publish without a fresh acceptance.

This violates invariant 5. Add a regression for failures before Parse and set inputs, and make cleanup independent of parsed publish inputs.

PASS — authorization restricts approvers correctly

Only write, maintain, and admin roles pass (src/modules/approval-authorizer.js:1,28-34). The permission lookup targets the repository parsed from the release title (src/modules/approval-authorizer.js:17-18,28-32).

The bot exception is exact: only getsantry[bot] and only an exact allowlisted release path pass (src/modules/approval-authorizer.js:2,20-25; src/publish/authorize-approval.js:8-15). Authorization failures remove accepted and stop the job (.github/workflows/publish.yml:93-111).

PASS — title and accepted event remain bound through CI

The poller snapshots the current accepted event before CI work (.github/workflows/ci-poller.yml:100-116) and checks the live title plus exact event ID before CI, after CI, before proof creation, and immediately before ci-ready (.github/workflows/ci-poller.yml:117-131,253-267,274-289,316-333).

The attestation binds actor, event ID, and title (src/modules/approval-attestation.js:11-16,19-25). ...

gpt-5.6-terra · 2026-08-29 13:33:46 · ~765 tokens

Distillations (6)

Gen 0 2026-08-29 13:36:43 · 2060 tokens

Date: Aug 29, 2026 * 🟡 (13:19) `package.json` identifies the private `publish` project, version `0.0.1`, as “Approval-based publishing system for Sentry”; repository `git@github.com:getsentry/publish.git`; Apache-2.0 license; Volta Node `24.0.0` and Yarn `1.22.22`; resolution `undici: "^6.23.0"`; scripts `test: "vitest run"`, `test:watch: "vitest"`, `lint: "eslint src .github --ignore-pattern '!…

Gen 0 2026-08-29 13:25:25 · 1533 tokens

Date: Aug 29, 2026 * 🟡 (13:16) `src/publish/update-issue.js` loads `context`, an Octokit client using `getGitHubToken()`, and `inputs` from `JSON.parse(process.env.PUBLISH_ARGS)`, then invokes `updateIssue({ context, octokit, inputs })`. * 🟡 (13:16) `.github/workflows/auto-approve.yml` is named `auto-approve non-sdks`; triggers on newly opened issues; has `contents: read`; and runs `auto-approv…

Gen 0 2026-08-29 13:24:13 · 1345 tokens

Date: Aug 29, 2026 * 🟡 (13:13) `src/modules/approval-authorizer.js` defines `ALLOWED_ROLE_NAMES = new Set(["write", "maintain", "admin"])` and `AUTO_APPROVER = "getsantry[bot]"`. `authorizeApproval({ actor, issueTitle, getPermission, autoApprovedRepositories = new Set() })` parses `issueTitle` via `parsePublishTitle()`; malformed titles return `{ authorized: false, repository: null }`. * 🟡 (13:…

Gen 0 2026-08-29 13:14:53 · 1801 tokens

Date: Aug 29, 2026 * 🟡 (13:12) `src/publish/authorize-approval.js`: `getAutoApprovedRepositories()` reads `auto-approve-repos.txt` as UTF-8, splits on `/\r?\n/`, filters empty lines, and returns a `Set`. * 🟡 (13:12) `src/publish/authorize-approval.js`: `getGitHubResponse(path)` calls `fetch("https://api.github.com/${path}")` with headers `Accept: "application/vnd.github+json"`, `Authorization: …

Gen 0 2026-08-29 13:14:19 · 882 tokens

Date: Aug 29, 2026 * 🟢 (13:10) Repository directory `/home/byk/Code/getsentry/publish` contains `.eslintrc.js`, `.git/`, `.github/`, `.gitignore`, `.lore.md`, `AGENTS.md`, `auto-approve-repos.txt`, `docs/`, `LICENSE`, `node_modules/`, `package.json`, `README.md`, `src/`, `vitest.config.js`, and `yarn.lock` (15 entries). * 🟢 (13:10) Relevant release-approval files found: `src/publish/current-acc…

Gen 0 2026-08-29 13:13:09 · 327 tokens

Date: Aug 29, 2026 * 🔴 [requested-review] (13:07) User requested a read-only, substantive correctness review of the current working tree’s release approval gate; explicitly instructed not to edit. * 🟡 (13:07) User asked review to focus on `.github/workflows/publish.yml`, `.github/workflows/ci-poller.yml`, `src/modules/approval-attestation.js`, `src/publish/authorize-approval.js`, `src/publish/c…