DashboardcraftSession 0qdfyNMXGWQ5

Session 0qdfyNMXGWQ5

Full ID: 0qdfyNMXGWQ5OTKCQ
Messages: 46
Distillations: 6
Time range: 2026-09-08 02:11:29 — 2026-09-08 02:21:47

Conversation (46 messages)

Perform a strict read-only adversarial final audit of the current working tree in /home/byk/Code/getsentry/publish-workspace-acceptance. Do not edit files, format, generate artifacts, run git-mutating commands, or change state. Review the complete diff against origin/main and relevant surrounding source/tests/workflows. Verify: (1) exact release-revision contract: body-start canonical header, Merge target requirement, exactly one global - [View check runs]( occurrence including embedded/outside-header strings, URL binds getsentry requested repo + 40 lowercase hex revision + required /checks and optional terminal slash; error/update behavior remains correct; (2) title parser/doc generated single source and generator fails when documentation markers absent; (3) blank/malformed Craft workspace discovery fails with contextual invalid-list error and root missing config remains checkout-path fallback; (4) workflow refactor preserves exact CI revision checkout before discovery, getsentry/craft:latest, fail-closed discovery and state handling; (5) JSON output serialization/fallback behavior and checkout@v7 request; (6) tests cover fixes, generated artifacts current; (7) diff/repo hygiene with no unrelated churn. Produce evidence-based severity ordered findings with exact file:line references, explicit PASS/FAIL for each contract, and finish exactly MERGE or DO-NOT-MERGE. If blocked, state exact blocker. Return only final audit report.

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

(15 entries)
</entries>
gateway · 2026-09-08 02:11:39
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/AGENTS.md</path>
<type>file</type>
<content>
1: # AGENTS.md - Coding Agent Guidelines
2: 
3: This document provides guidelines for AI coding agents working in this repository.
4: 
5: ## Project Overview
6: 
7: This is the **Sentry Publish Repository** - a GitHub Actions-based approval system for publishing releases across multiple Sentry projects using [Craft](https://github.com/getsentry/craft). It's a pure JavaScript (Node.js 24.0.0) project using CommonJS modules.
8: 
9: ## Build/Lint/Test Commands
10: 
11: ### Package Manager
12: 
13: - **Yarn 1.22.22** (classic) - Do NOT use npm
14: - Version management via Volta (pinned in package.json)
15: 
16: ### Commands
17: 
18: ```bash
19: # Install dependencies
20: yarn install
21: 
22: # Run all tests
23: yarn test
24: 
25: # Run tests in watch mode
26: yarn test:watch
27: 
28: # Run a single test file
29: yarn test src/modules/__tests__/update-issue.js
30: 
31: # Run tests ...
gateway · 2026-09-08 02:11:42
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/AGENTS.md</path>
<type>file</type>
<content>
201: 
202: describe("myFunction", () => {
203:   test("does something", () => {
204:     expect(myFunction()).toBe(expected);
205:   });
206: });
207: ```
208: 
209: ### Mocking Patterns
210: 
211: ```javascript
212: // Mock a module
213: vi.mock("@actions/github");
214: 
215: // Mock with implementation
216: vi.mock("fs", () => ({
217:   existsSync: vi.fn(),
218:   readFileSync: vi.fn(),
219: }));
220: 
221: // Inline snapshots for complex output
222: expect(result).toMatchInlineSnapshot(`"expected output"`);
223: ```
224: 
225: ## Key Dependencies
226: 
227: - `@actions/core` - GitHub Actions toolkit (inputs, outputs, logging)
228: - `@actions/github` - GitHub API client for Actions
229: - `@sentry/node` - Sentry error reporting
230: 
231: ## Common Patterns
232: 
233: ### Async/Await with Parallel Execution
234: 
235: ```javascript
236: await Promise.all([operation1(), ...
gateway · 2026-09-08 02:11:50
result
 M .github/workflows/ci-poller.yml
 M .github/workflows/publish.yml
 M docs/publish-issue-format.md
 M scripts/generate-publish-issue-title-parser.js
 M src/modules/__tests__/publish-workflow.js
 M src/modules/__tests__/release-revision.js
 M src/modules/publish-issue-title.js
 M src/modules/publish-issue-title.peggy
 M src/modules/release-revision.js
 M src/publish/inputs.js
 M src/publish/resolve-location.js
 M src/publish/resolve-release-revision.js
?? src/modules/__tests__/generate-publish-issue-title-parser.js
?? src/publish/__tests__/
?? ...
gateway · 2026-09-08 02:11:59
result
M	.github/workflows/ci-poller.yml
M	.github/workflows/cocoapods-keepalive.yml
M	.github/workflows/publish.yml
M	README.md
A	docs/publish-issue-format.md
M	package.json
A	scripts/generate-publish-issue-title-parser.js
A	src/modules/__tests__/ci-poller-input.js
M	src/modules/__tests__/details-from-context.js
A	src/modules/__tests__/publish-location.js
A	src/modules/__tests__/publish-workflow.js
A	src/modules/__tests__/release-revision.js
M	src/modules/__tests__/update-issue.js
A	src/modules/ci-poller-input.js
M	src/modules/details-from-context.js
A	src/modules/publish-issue-title.js
A	src/modules/publish-issue-title.peggy
A	src/modules/publish-issue-validation.js
A	src/modules/publish-location.js
A	src/modules/release-revision.js
M	src/modules/update-issue.js
M	src/publish/inputs.js
A	src/publish/resolve-ci-poller-input.js
A	src/publish/resolve-location.js
A	src/publish/resolve-release-revision.js
M	yarn.lock
 .github/workflows/ci-poller.yml                |   32 +-
 ...
gateway · 2026-09-08 02:12:04
result
/home/byk/Code/getsentry/publish-workspace-acceptance/src/publish/__tests__/discover-location.js
/home/byk/Code/getsentry/publish-workspace-acceptance/src/publish/__tests__/resolve-release-revision.js
/home/byk/Code/getsentry/publish-workspace-acceptance/src/publish/__tests__/resolve-location.js
gateway · 2026-09-08 02:12:08
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/release-revision.js</path>
<type>file</type>
<content>
1: const { parse } = require("./publish-issue-title");
2: 
3: function getReleaseRevisionDetails({ issueBody, repo }) {
4:   if (
5:     parse(issueBody, { startRule: "CheckRunsLinkCount" }) !== 1
6:   ) {
7:     throw new Error(
8:       `Expected exactly one View check runs link in Quick links for getsentry/${repo}.`
9:     );
10:   }
11: 
12:   let details;
13:   try {
14:     details = parse(issueBody, { startRule: "ReleaseRevision" });
15:   } catch {
16:     throw new Error(
17:       `Expected a View check runs link for getsentry/${repo} in the publish issue body.`
18:     );
19:   }
20: 
21:   if (details.repo !== repo) {
22:     throw new Error(
23:       `Expected a View check runs link for getsentry/${repo} in the publish issue body.`
24:     );
25:   }
26: 
27:   return details;
28: }
29: 
30: function getReleaseRevision({ issueBody, repo }) {
31:  ...
gateway · 2026-09-08 02:12:13
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/publish-issue-title.peggy</path>
<type>file</type>
<content>
1: {
2:   function join(characters) {
3:     return characters.join("");
4:   }
5: }
6: 
7: // BEGIN TITLE GRAMMAR
8: // Canonical grammar for publish issue titles. A path suffix is syntactic only:
9: // the controller resolves the complete suffix as a workspace after checking
10: // out the CI-approved revision.
11: PublishIssueTitle
12:   = "publish: " "getsentry/"? repo:Repository path:Path? "@" version:Version !. {
13:       return {
14:         repo,
15:         path: path || "",
16:         version,
17:       };
18:     }
19: 
20: Repository
21:   = characters:RepositoryCharacter+ { return join(characters); }
22: 
23: RepositoryCharacter
24:   = [A-Za-z0-9_.-]
25: 
26: Path
27:   = segments:("/" segment:PathSegment { return `/${segment}`; })+ { return join(segments); }
28: 
29: PathSegment
30:   = characters:RepositoryCharacter+ { return ...
gateway · 2026-09-08 02:12:22
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/scripts/generate-publish-issue-title-parser.js</path>
<type>file</type>
<content>
1: const fs = require("fs");
2: const path = require("path");
3: const peggy = require("peggy");
4: const prettier = require("prettier");
5: 
6: const grammarPath = path.join(
7:   __dirname,
8:   "..",
9:   "src",
10:   "modules",
11:   "publish-issue-title.peggy"
12: );
13: const outputPath = path.join(
14:   __dirname,
15:   "..",
16:   "src",
17:   "modules",
18:   "publish-issue-title.js"
19: );
20: const documentationPath = path.join(
21:   __dirname,
22:   "..",
23:   "docs",
24:   "publish-issue-format.md"
25: );
26: function generateDocumentation({ documentation, titleGrammar }) {
27:   const marker = /<!-- BEGIN GENERATED TITLE GRAMMAR -->\n[\s\S]*?<!-- END GENERATED TITLE GRAMMAR -->/.exec(
28:     documentation
29:   );
30: 
31:   if (!marker) {
32:     throw new Error("Could not find the generated title grammar in the ...
gateway · 2026-09-08 02:12:27
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/docs/publish-issue-format.md</path>
<type>file</type>
<content>
1: # Publish Issue Format
2: 
3: The Publish workflow treats a publish issue as a release request. The title identifies
4: the release; the body supplies the merge target and selected targets. Do not edit the
5: title by hand unless it remains valid under this format.
6: 
7: ## Title
8: 
9: Every title starts with `publish: `. The following canonical Peggy grammar is
10: generated from `src/modules/publish-issue-title.peggy`:
11: 
12: <!-- BEGIN GENERATED TITLE GRAMMAR -->
13: ```peggy
14: // Canonical grammar for publish issue titles. A path suffix is syntactic only:
15: // the controller resolves the complete suffix as a workspace after checking
16: // out the CI-approved revision.
17: PublishIssueTitle
18:   = "publish: " "getsentry/"? repo:Repository path:Path? "@" version:Version !. ...
gateway · 2026-09-08 02:12:32
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/publish-issue-validation.js</path>
<type>file</type>
<content>
1: function isPublishRepository(name) {
2:   return (
3:     /^[A-Za-z0-9_.-]+$/.test(name) &&
4:     name !== "." &&
5:     name !== ".." &&
6:     name !== "__proto__" &&
7:     !name.startsWith("-")
8:   );
9: }
10: 
11: function isReleaseVersion(version) {
12:   return /^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(?:-?([0-9a-z-]+(?:\.[0-9a-z-]+)*))?(?:\+([0-9a-z-]+(?:\.[0-9a-z-]+)*))?$/i.test(
13:     version
14:   );
15: }
16: 
17: module.exports = { isPublishRepository, isReleaseVersion };

(End of file - total 17 lines)
</content>
gateway · 2026-09-08 02:12:35
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/details-from-context.js</path>
<type>file</type>
<content>
1: const { parse: parsePublishIssueTitle } = require("./publish-issue-title");
2: const { isPublishPath } = require("./publish-location");
3: const {
4:   isPublishRepository,
5:   isReleaseVersion,
6: } = require("./publish-issue-validation");
7: 
8: /**
9:  * Matches the entire "Targets" section of a github publish issue body.
10:  */
11: const TARGETS_SECTION_PARSER_REGEX =
12:   /^(?!### Targets$\s)(?: *- \[[ xX]\] \S+\s*$(?:\r?\n)?)+/m;
13: 
14: /**
15:  * Matches all targets of a github publish issue body in a section that was already matched and extracted with `TARGETS_PARSER_REGEX`.
16:  * The "id" of the targets is captured within a capture group. ...
gateway · 2026-09-08 02:12:41
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/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', JSON.stringify(result));
10: }
11: 
12: 
13: inputs();

(End of file - total 13 lines)
</content>
gateway · 2026-09-08 02:12:45
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/src/publish/resolve-release-revision.js</path>
<type>file</type>
<content>
1: const core = require("@actions/core");
2: const { getReleaseRevision } = require("../modules/release-revision");
3: 
4: function resolveReleaseRevision() {
5:   const { repo } = JSON.parse(process.env.PUBLISH_ARGS || "{}");
6:   if (!repo) {
7:     throw new Error("Publish input must define a repository.");
8:   }
9: 
10:   core.setOutput(
11:     "revision",
12:     getReleaseRevision({
13:       issueBody: process.env.PUBLISH_ISSUE_BODY || "",
14:       repo,
15:     })
16:   );
17: }
18: 
19: resolveReleaseRevision();

(End of file - total 19 lines)
</content>
gateway · 2026-09-08 02:12:50
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/.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 ...
gateway · 2026-09-08 02:13:00
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/src/publish/discover-location.js</path>
<type>file</type>
<content>
1: const { execFileSync } = require("child_process");
2: const { existsSync } = require("fs");
3: const path = require("path");
4: 
5: const core = require("@actions/core");
6: 
7: const { resolvePublishLocation } = require("../modules/publish-location");
8: 
9: function getWorkspaceNames({
10:   repositoryDirectory,
11:   exists = existsSync,
12:   execFile = execFileSync,
13: }) {
14:   if (!exists(path.join(repositoryDirectory, ".craft.yml"))) {
15:     return [];
16:   }
17: 
18:   const output = execFile(
19:     "docker",
20:     [
21:       "run",
22:       "--rm",
23:       "--volume",
24:       `${path.resolve(repositoryDirectory)}:/github/workspace/__repo__`,
25:       "--workdir",
26:       "/github/workspace/__repo__",
27:       "getsentry/craft:latest",
28:       "workspace",
29:       "list",
30:     ],
31:     { encoding: "utf8" }
32:   );
33:  ...
gateway · 2026-09-08 02:13:05
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/publish-location.js</path>
<type>file</type>
<content>
1: function resolvePublishLocation({ path, workspaceNames }) {
2:   if (!isPublishPath(path)) {
3:     throw new Error("Invalid publish path.");
4:   }
5: 
6:   if (!workspaceNames.every(isWorkspaceName)) {
7:     throw new Error(
8:       "Craft workspace discovery returned an invalid workspace list."
9:     );
10:   }
11: 
12:   if (path === ".") {
13:     return { path };
14:   }
15: 
16:   const workspace = path.slice(2);
17:   if (workspaceNames.includes(workspace)) {
18:     return { path: ".", workspace };
19:   }
20: 
21:   return { path };
22: }
23: 
24: function isPublishPath(path) {
25:   return (
26:     typeof path === "string" &&
27:     (path === "." ||
28:       (path.startsWith("./") &&
29:         path
30:           .slice(2)
31:           .split("/")
32:           .every(isSafeWorkspaceSegment)))
33:   );
34: }
35: 
36: function ...
gateway · 2026-09-08 02:13:13
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/src/publish/resolve-location.js</path>
<type>file</type>
<content>
1: const core = require("@actions/core");
2: const { resolvePublishLocation } = require("../modules/publish-location");
3: 
4: function resolveLocation() {
5:   const input = JSON.parse(process.env.PUBLISH_ARGS || "{}");
6:   const workspaceNames = JSON.parse(process.env.CRAFT_WORKSPACE_NAMES || "[]");
7: 
8:   if (!Array.isArray(workspaceNames)) {
9:     throw new Error(
10:       "Craft workspace discovery returned an invalid workspace list."
11:     );
12:   }
13: 
14:   core.setOutput(
15:     "result",
16:     JSON.stringify(
17:       resolvePublishLocation({
18:         path: input.path,
19:         workspaceNames,
20:       })
21:     )
22:   );
23: }
24: 
25: resolveLocation();

(End of file - total 25 lines)
</content>
gateway · 2026-09-08 02:13:17
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/src/publish/resolve-ci-poller-input.js</path>
<type>file</type>
<content>
1: const { getCiPollerInput } = require("../modules/ci-poller-input");
2: 
3: process.stdout.write(
4:   JSON.stringify(
5:     getCiPollerInput({
6:       issueBody: process.env.PUBLISH_ISSUE_BODY || "",
7:       title: process.env.PUBLISH_TITLE || "",
8:       revision: process.env.PUBLISH_REVISION || "",
9:     })
10:   )
11: );

(End of file - total 11 lines)
</content>
gateway · 2026-09-08 02:13:21
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/ci-poller-input.js</path>
<type>file</type>
<content>
1: const { parse: parsePublishIssueTitle } = require("./publish-issue-title");
2: const {
3:   getReleaseRevision,
4:   updateReleaseRevision,
5: } = require("./release-revision");
6: const {
7:   isPublishRepository,
8:   isReleaseVersion,
9: } = require("./publish-issue-validation");
10: const { isPublishPath } = require("./publish-location");
11: 
12: function getCiPollerInput({ title, issueBody, revision }) {
13:   let parsedTitle;
14:   try {
15:     parsedTitle = parsePublishIssueTitle(title);
16:   } catch {
17:     throw new Error(`Invalid publish issue title: '${title}'`);
18:   }
19:   const { repo, version } = parsedTitle;
20:   if (!isPublishRepository(repo)) {
21:     throw new Error(`Invalid publish issue repository: '${repo}'`);
22:   }
23:   if (!isReleaseVersion(version)) {
24:     throw new Error(`Invalid publish issue version: ...
gateway · 2026-09-08 02:13:25
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/.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: read
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:       # sentry-internal-app token ...
gateway · 2026-09-08 02:13:39
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/__tests__/release-revision.js</path>
<type>file</type>
<content>
1: import { expect, test } from "vitest";
2: 
3: const {
4:   getReleaseRevision,
5:   updateReleaseRevision,
6: } = require("../release-revision.js");
7: 
8: const REVISION = "7e5ca7ed5581552de066e2a8bc295b8306be38ac";
9: 
10: function requestBody(quickLinks) {
11:   return `Requested by: @byk
12: 
13: Merge target: (default)
14: 
15: Quick links:
16: ${quickLinks}`;
17: }
18: 
19: function canonicalQuickLinks(revision = REVISION) {
20:   return `- [View changes](https://github.com/getsentry/toolkit/compare/1.2.2...release/1.2.3)
21: - [View check runs](https://github.com/getsentry/toolkit/commit/${revision}/checks/)`;
22: }
23: 
24: test("gets the CI-approved revision from the request header", () => {
25:   expect(
26:     getReleaseRevision({
27:       repo: "toolkit",
28:       issueBody: requestBody(canonicalQuickLinks()),
29:     })
30:   ...
gateway · 2026-09-08 02:13:44
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/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({
8:   octokit,
9:   stateFilePath,
10:   publishRepo,
11:   issue_number,
12: }) {
13:   if (!stateFilePath || !fs.existsSync(stateFilePath)) {
14:     return;
15:   }
16: 
17:   const issueRequest = octokit.rest.issues.get({
18:     ...publishRepo,
19:     issue_number,
20:   });
21: 
22:   const craftStateRequest = fs.promises
23:     .readFile(stateFilePath, { encoding: "utf-8" })
24:     .then((data) => JSON.parse(data));
25: 
26:   const [{ data: issue }, craftState] = await Promise.all([
27:     issueRequest,
28:     craftStateRequest,
29:   ]);
30: 
31:   const newIssueBody = transformIssueBody(craftState, issue.body);
32: 
33:   await octokit.rest.issues.update({
34:     ...
gateway · 2026-09-08 02:13:48
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/src/publish/__tests__/discover-location.js</path>
<type>file</type>
<content>
1: import { afterEach, expect, test, vi } from "vitest";
2: 
3: const { discoverLocation, getWorkspaceNames } = require("../discover-location.js");
4: 
5: afterEach(() => {
6:   vi.restoreAllMocks();
7: });
8: 
9: test("retains checkout-path behavior when the root config is absent", () => {
10:   expect(
11:     discoverLocation({
12:       input: { path: "./packages/cli" },
13:       repositoryDirectory: "__repo__",
14:       exists: () => false,
15:     })
16:   ).toEqual({ path: "./packages/cli" });
17: });
18: 
19: test("discovers exact workspace paths using the released Craft image", () => {
20:   const execFile = vi.fn(() => '["packages/CLI"]');
21: 
22:   expect(
23:     discoverLocation({
24:       input: { path: "./packages/CLI" },
25:       repositoryDirectory: "__repo__",
26:       exists: () => true,
27:       execFile,
28:     })
29:   ...
gateway · 2026-09-08 02:13:53
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/__tests__/generate-publish-issue-title-parser.js</path>
<type>file</type>
<content>
1: import { expect, test } from "vitest";
2: 
3: const {
4:   generateDocumentation,
5: } = require("../../../scripts/generate-publish-issue-title-parser.js");
6: 
7: test("requires generated title grammar markers in the documentation", () => {
8:   expect(() =>
9:     generateDocumentation({
10:       documentation: "# Publish Issue Format\n",
11:       titleGrammar: 'PublishIssueTitle = "publish: "',
12:     })
13:   ).toThrow("Could not find the generated title grammar in the documentation.");
14: });

(End of file - total 14 lines)
</content>
gateway · 2026-09-08 02:14:01
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/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:     "generate": "node scripts/generate-publish-issue-title-parser.js",
19:     "check:generated": "node scripts/generate-publish-issue-title-parser.js --check",
20:     "test": "yarn check:generated && vitest run",
21:     "test:watch": "vitest",
22:     "lint": "eslint src .github --ignore-pattern '!.github'",
23:     "prettier": "prettier --write src"
24:   },
25:   "devDependencies": {
26:     "eslint": ...
gateway · 2026-09-08 02:14:09
result
(no output)
gateway · 2026-09-08 02:14:14
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/src/publish/__tests__/resolve-release-revision.js</path>
<type>file</type>
<content>
1: import { spawnSync } from "child_process";
2: import { expect, test } from "vitest";
3: 
4: const script = new URL("../resolve-release-revision.js", import.meta.url).pathname;
5: 
6: test("rejects missing workflow input through release revision validation", () => {
7:   const result = spawnSync(process.execPath, [script], {
8:     encoding: "utf8",
9:     env: {
10:       ...process.env,
11:       PUBLISH_ARGS: "",
12:       PUBLISH_ISSUE_BODY: "",
13:     },
14:   });
15: 
16:   expect(result.status).not.toBe(0);
17:   expect(result.stderr).toContain("Publish input must define a repository.");
18:   expect(result.stderr).not.toContain("Unexpected end of JSON input");
19: });

(End of file - total 19 lines)
</content>
gateway · 2026-09-08 02:14:18
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/src/publish/__tests__/resolve-location.js</path>
<type>file</type>
<content>
1: import { spawnSync } from "child_process";
2: import { expect, test } from "vitest";
3: 
4: const script = new URL("../resolve-location.js", import.meta.url).pathname;
5: 
6: test("rejects missing workflow input through location validation", () => {
7:   const result = spawnSync(process.execPath, [script], {
8:     encoding: "utf8",
9:     env: {
10:       ...process.env,
11:       PUBLISH_ARGS: "",
12:       CRAFT_WORKSPACE_NAMES: "[]",
13:     },
14:   });
15: 
16:   expect(result.status).not.toBe(0);
17:   expect(result.stderr).toContain("Invalid publish path.");
18:   expect(result.stderr).not.toContain("Unexpected end of JSON input");
19: });

(End of file - total 19 lines)
</content>
gateway · 2026-09-08 02:14:22
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/__tests__/publish-workflow.js</path>
<type>file</type>
<content>
1: import { spawnSync } from "child_process";
2: import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "fs";
3: import { tmpdir } from "os";
4: import { join } from "path";
5: 
6: import { afterEach, expect, test } from "vitest";
7: 
8: const { resolvePublishLocation } = require("../publish-location.js");
9: 
10: const temporaryDirectories = [];
11: 
12: function getWorkflow() {
13:   return readFileSync(
14:     join(__dirname, "../../../.github/workflows/publish.yml"),
15:     "utf8"
16:   );
17: }
18: 
19: function getSetTargetsScript() {
20:   const workflow = getWorkflow();
21:   const section = workflow.match(
22:     / {6}- name: Set targets[\s\S]*? {8}run: \|\n(?<script>[\s\S]*?)\n {6}- uses: docker:\/\/getsentry\/craft:latest/
23:   );
24:   if (!section?.groups?.script) {
25:     throw new Error("Missing Set targets workflow ...
gateway · 2026-09-08 02:14:29
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/__tests__/ci-poller-input.js</path>
<type>file</type>
<content>
1: import { readFileSync } from "fs";
2: import { join } from "path";
3: 
4: import { expect, test } from "vitest";
5: 
6: const { getCiPollerInput } = require("../ci-poller-input.js");
7: 
8: const REVISION = "7e5ca7ed5581552de066e2a8bc295b8306be38ac";
9: const issueBody = `Requested by: @byk
10: 
11: Merge target: (default)
12: 
13: Quick links:
14: - [View changes](https://github.com/getsentry/toolkit/compare/1.2.2...release/1.2.3)
15: - [View check runs](https://github.com/getsentry/toolkit/commit/${REVISION}/checks/)`;
16: 
17: test("parses root and full-path publish titles", () => {
18:   expect(
19:     getCiPollerInput({
20:       issueBody,
21:       title: "publish: getsentry/toolkit/cli@1.2.3",
22:     })
23:   ).toEqual({
24:     repo: "getsentry/toolkit",
25:     revision: REVISION,
26:     version: "1.2.3",
27:   });
28: 
29:   ...
gateway · 2026-09-08 02:14:34
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/__tests__/details-from-context.js</path>
<type>file</type>
<content>
1: import { test, expect } from "vitest";
2: 
3: const { detailsFromContext } = require("../details-from-context.js");
4: 
5: const inputsArgs = {
6:   context: {
7:     repo: { owner: "getsentry", repo: "publish" },
8:     payload: {
9:       issue: {
10:         number: "223",
11:         title: "publish: getsentry/sentry@21.3.1",
12:         body: `
13: Requested by: @BYK
14: 
15: Merge target: custom-branch
16: 
17: Quick links:
18: - [View changes](https://github.com/getsentry/sentry/compare/21.3.0...refs/heads/releases/21.3.1)
19: - [View check runs](https://github.com/getsentry/sentry/commit/7e5ca7ed5581552de066e2a8bc295b8306be38ac/checks/)
20: 
21: Assign the **accepted** label to this issue to approve the release. ...
gateway · 2026-09-08 02:14:42
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/.github/workflows/cocoapods-keepalive.yml</path>
<type>file</type>
<content>
1: name: CocoaPods Token Keep-Alive
2: 
3: on:
4:   schedule:
5:     # CocoaPods sessions currently expire after 3 days of inactivity (VALIDITY_LENGTH)
6:     # This runs every day at midnight UTC to keep the current token alive
7:     # See: https://github.com/CocoaPods/trunk.cocoapods.org/blob/a1869790e9ae2229b6985b6af4532da814202558/app/models/session.rb#L9
8:     - cron: "0 0 * * *" # Runs every day at midnight UTC
9:   workflow_dispatch:
10: 
11: permissions:
12:   contents: read
13: 
14: jobs:
15:   keepalive:
16:     name: Refresh CocoaPods Session
17:     runs-on: macos-15
18:     environment: production
19:     env:
20:       COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }}
21: 
22:     steps:
23:       - name: Install CocoaPods
24:         run: gem install cocoapods
25:       - name: Print CocoaPods Version
26:         run: pod ...
gateway · 2026-09-08 02:14:49
result
commit 33de3f1df5bdb9a6a480e9217d566df7b5a48b8e
Author:     Burak Yigit Kaya <byk@sentry.io>
AuthorDate: Wed Aug 26 14:15:22 2026 +0000
Commit:     Burak Yigit Kaya <byk@sentry.io>
CommitDate: Wed Aug 26 14:15:22 2026 +0000

    feat: accept workspace publish requests

 .github/workflows/publish.yml                 |  27 +++-
 src/modules/__tests__/details-from-context.js | 212 +++++++++++++++++++++++++-
 src/modules/__tests__/publish-workflow.js     |  82 ++++++++++
 src/modules/__tests__/update-issue.js         |  15 +-
 src/modules/details-from-context.js           |  27 +++-
 src/modules/update-issue.js                   |  54 ++++---
 6 files changed, 377 insertions(+), 40 deletions(-)
 create mode 100644 src/modules/__tests__/publish-workflow.js
commit b658ffa0299fedf78c9dd9a2121cd740691ca157
Author:     Burak Yigit Kaya <byk@sentry.io>
AuthorDate: Thu Aug 27 09:14:38 2026 +0000
Commit:     Burak Yigit Kaya <byk@sentry.io>
CommitDate: Thu Aug 27 09:14:38 2026 +0000

    fix: ...
gateway · 2026-09-08 02:14:59
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/README.md</path>
<type>file</type>
<content>
1: # Sentry Publish 🏠
2: 
3: This is a meta/control repository that implements the [Central Publish Repository](docs/rfc.md) RFC
4: 
5: ## Quick Start
6: 
7: [craft quick start](https://craft.sentry.dev/github-actions/)
8: 
9: ## Release Flow
10: 
11: ```mermaid
12: flowchart TD
13:     A[Developer triggers release workflow] --> B["SDK Repo: craft prepare"]
14:     B --> C[Build artifacts & create release branch]
15:     C --> D[Upload artifacts to GitHub]
16:     D --> E["Create issue in getsentry/publish"]
17:     E --> F{Release Manager Review}
18:     F -->|"Add 'accepted' label"| G[Publish workflow triggers]
19:     G --> I[Download artifacts from GitHub]
20:     I --> J["craft publish to registries"]
21:     J --> K{Publish successful?}
22:     K -->|Yes| L[Issue closed - success]
23:     K -->|No| M[Issue updated with failure]
24: ```
25: 
26: ## Goals
27: 
28: 1. ...
gateway · 2026-09-08 02:15:07
result
...output truncated...

Full output saved to: /home/byk/.local/share/opencode/tool-output/tool_07ecc38f2001KdVYmd46eoS4zG

+            body: "",
+            labels: [],
+          },
+        },
+      },
+    });
+
+  await expect(fn).rejects.toThrow("Invalid publish issue path");
+});
+
+test.each(["-toolkit", ".", "..", "__proto__"])(
+  "rejects an unsafe checkout repository identity: %s",
+  async (repo) => {
+    await expect(
+      detailsFromContext({
+        context: {
+          payload: {
+            issue: {
+              title: `publish: getsentry/${repo}@1.2.3`,
+              body: "",
+              labels: [],
+            },
+          },
+        },
+      })
+    ).rejects.toThrow("Invalid publish issue repository");
+  }
+);
+
+test.each(["--config", "1.2"])(
+  "rejects an invalid release version: %s",
+  async (version) => {
+    await expect(
+      detailsFromContext({
+        context: {
+          payload: {
+            issue: {
+              title: ...
gateway · 2026-09-08 02:17:49
result
Found 13 matches
/home/byk/Code/getsentry/publish-workspace-acceptance/src/publish/resolve-ci-poller-input.js:
  Line 8:       revision: process.env.PUBLISH_REVISION || "",


/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/ci-poller-input.js:
  Line 4:   updateReleaseRevision,

  Line 34:       ? { issueBody: updateReleaseRevision({ issueBody, repo, revision }) }


/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/release-revision.js:
  Line 34: function updateReleaseRevision({ issueBody, repo, revision }) {

  Line 39: module.exports = { getReleaseRevision, updateReleaseRevision };


/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/__tests__/release-revision.js:
  Line 5:   updateReleaseRevision,

  Line 132:     updateReleaseRevision({ issueBody, repo: "toolkit", revision: replacement })


/home/byk/Code/getsentry/publish-workspace-acceptance/.github/workflows/test.yml:
  Line 19:       - uses: ...
gateway · 2026-09-08 02:19:05
result
Found 4 matches
/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/publish-issue-title.js:
  Line 199:     ReleaseRevision: peg$parseReleaseRevision,

  Line 200:     CheckRunsLinkCount: peg$parseCheckRunsLinkCount,

  Line 748:   function peg$parseReleaseRevision() {

  Line 1093:   function peg$parseCheckRunsLinkCount() {
gateway · 2026-09-08 02:19:11
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/publish-issue-title.js</path>
<type>file</type>
<content>
185:       describeFound(found) +
186:       " found."
187:     );
188:   }
189: }
190: 
191: function peg$parse(input, options) {
192:   options = options !== undefined ? options : {};
193: 
194:   const peg$FAILED = {};
195:   const peg$source = options.grammarSource;
196: 
197:   const peg$startRuleFunctions = {
198:     PublishIssueTitle: peg$parsePublishIssueTitle,
199:     ReleaseRevision: peg$parseReleaseRevision,
200:     CheckRunsLinkCount: peg$parseCheckRunsLinkCount,
201:   };
202:   let peg$startRuleFunction = peg$parsePublishIssueTitle;
203: 
204:   const peg$c0 = "publish: ";
205:   const peg$c1 = "getsentry/";
206:   const peg$c2 = "@";
207:   const peg$c3 = "/";
208:   const peg$c4 = "Requested by: @";
209:   const peg$c5 = "Merge target: ";
210:   const peg$c6 = "Quick links:";
211:   const peg$c7 = "- [View changes](";
212:   const peg$c8 ...
gateway · 2026-09-08 02:19:15
result
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/publish-issue-title.js</path>
<type>file</type>
<content>
740:       peg$savedPos = s0;
741:       s1 = peg$f5(s1);
742:     }
743:     s0 = s1;
744: 
745:     return s0;
746:   }
747: 
748:   function peg$parseReleaseRevision() {
749:     let s0, s1, s2, s3;
750: 
751:     s0 = peg$currPos;
752:     s1 = peg$parseRequestHeader();
753:     if (s1 !== peg$FAILED) {
754:       s2 = [];
755:       if (input.length > peg$currPos) {
756:         s3 = input.charAt(peg$currPos);
757:         peg$currPos++;
758:       } else {
759:         s3 = peg$FAILED;
760:         if (peg$silentFails === 0) {
761:           peg$fail(peg$e3);
762:         }
763:       }
764:       while (s3 !== peg$FAILED) {
765:         s2.push(s3);
766:         if (input.length > peg$currPos) {
767:           s3 = input.charAt(peg$currPos);
768:           peg$currPos++;
769:         } else {
770:           s3 = peg$FAILED;
771:           if ...
gateway · 2026-09-08 02:19:57
result
attacker-branch
gateway · 2026-09-08 02:20:08
result
{"merge_target":"attacker-branch","revision":"7e5ca7ed5581552de066e2a8bc295b8306be38ac"}
gateway · 2026-09-08 02:20:15
result
true
gateway · 2026-09-08 02:20:51
result
 M .github/workflows/ci-poller.yml
 M .github/workflows/publish.yml
 M docs/publish-issue-format.md
 M scripts/generate-publish-issue-title-parser.js
 M src/modules/__tests__/publish-workflow.js
 M src/modules/__tests__/release-revision.js
 M src/modules/publish-issue-title.js
 M src/modules/publish-issue-title.peggy
 M src/modules/release-revision.js
 M src/publish/inputs.js
 M src/publish/resolve-location.js
 M src/publish/resolve-release-revision.js
?? src/modules/__tests__/generate-publish-issue-title-parser.js
?? src/publish/__tests__/
?? src/publish/discover-location.js
26
gateway · 2026-09-08 02:21:47

Final audit

Base: origin/main 83d210b; current HEAD a81ab03 plus uncommitted changes. Reviewed 26 tracked diff paths and all untracked paths. git diff --check origin/main passed. node scripts/generate-publish-issue-title-parser.js --check passed.

Findings

  1. HIGH — canonical Merge target is not bound to the body-start header

    • src/modules/publish-issue-title.peggy:42-47 requires a start-of-body header but discards its merge target.
    • src/modules/details-from-context.js:53-59 instead reads the first syntactically valid Merge target: anywhere in the body.
    • .github/workflows/publish.yml:247 passes that independently parsed value to Craft.
    • Reproduction: a valid header with Merge target: (default) followed by Merge target: attacker-branch yields {"merge_target":"attacker-branch","revision":"7e5ca7…"}. The revision remains canonical while the appended decoy controls the merge target.
    • Extract and validate the merge target from the canonical parsed header only.
  2. HIGH — revision updates accept malformed revisions

    • src/modules/release-revision.js:34-36 validates the existing link but inserts revision without validating it.
    • src/modules/ci-poller-input.js:33-35 forwards PUBLISH_REVISION into that update.
    • Reproduction: updateReleaseRevision(..., revision: "not-a-sha") returns a body containing /commit/not-a-sha/checks/.
    • Require exactly 40 lowercase hexadecimal characters before updating.
  3. MEDIUM — CI-poller revision updates modify more than the canonical revision

    • .github/workflows/ci-poller.yml:98 and 145-147 capture issue bodies through command substitution and jq -r.
    • Bash removes all trailing newlines from command-substitution output. A branch-move update therefore rewrites trailing body whitespace in addition to the matched revision, violating the narrow-update contract. ...
gpt-5.6-terra · 2026-09-08 02:21:47 · ~904 tokens

Distillations (6)

Gen 0 2026-09-08 02:23:32 · 1744 tokens

Date: Sep 8, 2026 * 🟡 (02:19) Tool output located 13 matches related to release-revision handling, including `src/publish/resolve-ci-poller-input.js:8` using `PUBLISH_REVISION`, `src/modules/ci-poller-input.js:4,34` importing/calling `updateReleaseRevision`, `src/modules/release-revision.js:34,39` defining/exporting it, and its test at `src/modules/__tests__/release-revision.js:5,132`; checkout …

Gen 0 2026-09-08 02:22:08 · 307 tokens

Date: Sep 8, 2026 * 🟡 (02:17) Tool output showed tests requiring rejection of unsafe checkout repository identities `-toolkit`, `.`, `..`, and `__proto__` in publish issue titles such as `publish: getsentry/${repo}@1.2.3`. * 🟡 (02:17) Tool output showed a test expecting `detailsFromContext(...)` to reject with `Invalid publish issue path`. * 🟡 (02:17) Tool output showed `resolvePublishLocation…

Gen 0 2026-09-08 02:19:34 · 2220 tokens

Date: Sep 8, 2026 * 🟡 (02:14) Tool output showed commits by Burak Yigit Kaya <byk@sentry.io>: `33de3f1df5bdb9a6a480e9217d566df7b5a48b8e` (`feat: accept workspace publish requests`, 377 insertions/40 deletions across 6 files, Aug 26, 2026); `b658ffa0299fedf78c9dd9a2121cd740691ca157` (`fix: validate publish workspace JSON`, 112 insertions/2 deletions across 5 files, Aug 27, 2026); `52ef600d6759911…

Gen 0 2026-09-08 02:18:41 · 2793 tokens

Date: Sep 8, 2026 * 🟡 (02:13) `src/modules/__tests__/release-revision.js` defines `REVISION = "7e5ca7ed5581552de066e2a8bc295b8306be38ac"` and tests `getReleaseRevision`/`updateReleaseRevision` from `src/modules/release-revision.js`. Canonical request bodies begin `Requested by: @byk`, contain `Merge target: (default)`, and a `Quick links:` block with `View changes` and `View check runs` URLs for…

Gen 0 2026-09-08 02:17:04 · 875 tokens

Date: Sep 8, 2026 * 🟡 (02:13) `.github/workflows/ci-poller.yml` defines `CI Status Poller`, triggered by cron `*/5 * * * *` and `workflow_dispatch` with optional `attempt` input defaulting to `"0"`; `check-ci` runs on `ubuntu-latest` in `production` only when `vars.CI_POLLER_HAS_PENDING == 'true' || github.event_name == 'workflow_dispatch'`, with concurrency group `ci-status-poller` and `cancel-…

Gen 0 2026-09-08 02:14:50 · 2798 tokens

Date: Sep 8, 2026 * 🔴 [requested-audit] (02:11) User requested a strict read-only adversarial final audit of `/home/byk/Code/getsentry/publish-workspace-acceptance`, with no file edits, formatting, artifact generation, git-mutating commands, or state changes. Requested review of the complete diff against `origin/main` and relevant surrounding source/tests/workflows; evidence-based severity-order…