Dashboard › craft › Session 0qdfyNMXGWQ5
0qdfyNMXGWQ5OTKCQPerform 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.
<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>
<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 ...
<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(), ...
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__/ ?? ...
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 +- ...
/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
<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: ...
<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 ...
<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 ...
<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 !. ...
<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>
<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. ...
<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>
<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>
<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 ...
<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: ...
<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 ...
<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>
<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>
<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: ...
<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 ...
<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: ...
<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: ...
<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: ...
<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>
<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": ...
(no output)
<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>
<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>
<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 ...
<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: ...
<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. ...
<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 ...
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: ...
<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. ...
...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: ...
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: ...
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() {
<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 ...
<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 ...
attacker-branch
{"merge_target":"attacker-branch","revision":"7e5ca7ed5581552de066e2a8bc295b8306be38ac"}
true
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
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.
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.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.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.updateReleaseRevision(..., revision: "not-a-sha") returns a body containing /commit/not-a-sha/checks/.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.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 …
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…
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…
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…
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-…
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…