Dashboard › publish › Session 1KMTeZxofBtJ
1KMTeZxofBtJ5ipOKPerform 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.
<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>
/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
<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 ...
<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: ...
<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 ...
<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 ...
<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: ({ ...
<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: ...
<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 ...
<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: ...
<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: ...
<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 ...
<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 = ...
<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({ ...
<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: ...
<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: ...
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 +++ ...
<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>
<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: ${{ ...
<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: ...
<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>
<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({ ...
<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: ...
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 [1m[46m RUN [49m[22m [36mv4.0.17 [39m[90m/home/byk/Code/getsentry/publish[39m [32m✓[39m src/publish/__tests__/validate-approval-attestation.js [2m([22m[2m7 tests[22m[2m)[22m[32m 23[2mms[22m[39m [32m✓[39m src/modules/__tests__/approval-authorizer.js [2m([22m[2m14 tests[22m[2m)[22m[32m 19[2mms[22m[39m [32m✓[39m src/publish/__tests__/current-accepted-event.js [2m([22m[2m2 tests[22m[2m)[22m[32m 20[2mms[22m[39m [32m✓[39m src/modules/__tests__/approval-attestation.js [2m([22m[2m4 tests[22m[2m)[22m[32m 18[2mms[22m[39m [32m✓[39m src/publish/__tests__/record-ci-ready-attestation.js [2m([22m[2m3 ...
yarn run v1.22.22 $ vitest run [1m[46m RUN [49m[22m [36mv4.0.17 [39m[90m/home/byk/Code/getsentry/publish[39m [32m✓[39m src/publish/__tests__/validate-approval-attestation.js [2m([22m[2m7 tests[22m[2m)[22m[32m 24[2mms[22m[39m [32m✓[39m src/publish/__tests__/current-accepted-event.js [2m([22m[2m2 tests[22m[2m)[22m[32m 36[2mms[22m[39m [32m✓[39m src/modules/__tests__/approval-attestation.js [2m([22m[2m4 tests[22m[2m)[22m[32m 18[2mms[22m[39m [32m✓[39m src/publish/__tests__/authorize-approval.js [2m([22m[2m3 tests[22m[2m)[22m[32m 35[2mms[22m[39m [32m✓[39m src/modules/__tests__/approval-authorizer.js [2m([22m[2m14 tests[22m[2m)[22m[32m 31[2mms[22m[39m [32m✓[39m src/modules/__tests__/process-end-state.js [2m([22m[2m4 tests[22m[2m)[22m[32m 57[2mms[22m[39m [32m✓[39m src/modules/__tests__/post-workflow-details.js [2m([22m[2m1 test[22m[2m)[22m[32m 12[2mms[22m[39m [32m✓[39m ...
<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": ...
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. ...
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.
<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: ...
bash: line 1: $(printf injected >&2): syntax error: operand expected (error token is "$(printf injected >&2)")
<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>
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: ...
<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>
acceptedpublish.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.
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).
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). ...
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 '!…
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…
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:…
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: …
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…
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…