Dashboard › craft › Session 0r59Q9fvc7NA
0r59Q9fvc7NAAA5xvPerform a strict, read-only adversarial audit of the current uncommitted review-fix worktree at /home/byk/Code/getsentry/publish-workspace-acceptance. Do not edit, format, regenerate, or otherwise mutate files. Inspect git status/diff vs branch baseline a81ab03 and relevant unchanged sources/workflows/tests. Recheck the previous blockers specifically: (1) all CI poller per-issue error branches must safely skip after cleanup without invalid Bash control flow or any body edit; resolver JSON failure cannot produce empty/truncated issue updates; trailing body bytes/newlines preserved; temp files cleaned. (2) documentation generator rejects missing, malformed, and duplicate begin/end markers. Also audit canonical strict body-start request header and merge-target authority/token policy; exact repo binding; lowercase SHA and exact /checks path; duplicate check-runs link detection; validated indexed revision replacements and byte preservation; output JSON serialization and valid JSON fallbacks; all requested checkouts use v7; workflow gating + workflow_dispatch; exact SHA checkout before config discovery and fail-closed discovery; intentional craft:latest preserved; generated docs/parser parity; tests and hygiene including no unrelated CocoaPods diff. You may run only non-mutating commands/tests. Report severity-ordered findings with precise current file:line citations, explicit PASS/FAIL for each required concern, testing gaps, and end exactly MERGE or DO-NOT-MERGE. If blocked, name exact blocker and end DO-NOT-MERGE.
I’ll inspect the exact worktree and baseline read-only, then trace each stated contract through implementation, workflows, generated files, and tests.
<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 ...
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__/details-from-context.js M src/modules/__tests__/publish-workflow.js M src/modules/__tests__/release-revision.js M src/modules/details-from-context.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-ci-poller-input.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__/ ?? ...
/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/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": ...
<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/.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/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/src/modules/publish-issue-title.js</path>
<type>file</type>
<content>
1: /* eslint-disable */
2: // @generated by Peggy 5.1.0.
3: //
4: // https://peggyjs.org/
5:
6: "use strict";
7:
8: class peg$SyntaxError extends SyntaxError {
9: constructor(message, expected, found, location) {
10: super(message);
11: this.expected = expected;
12: this.found = found;
13: this.location = location;
14: this.name = "SyntaxError";
15: }
16:
17: format(sources) {
18: let str = "Error: " + this.message;
19: if (this.location) {
20: let src = null;
21: const st = sources.find((s) => s.source === this.location.source);
22: if (st) {
23: src = st.text.split(/\r\n|\n|\r/g);
24: }
25: const s = this.location.start;
26: const offset_s =
27: this.location.source &&
28: typeof this.location.source.offset === "function"
29: ? ...
<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 isRevision(revision) {
4: return /^[0-9a-f]{40}$/.test(revision);
5: }
6:
7: function getReleaseRevisionDetails({ issueBody, repo }) {
8: if (
9: parse(issueBody, { startRule: "CheckRunsLinkCount" }) !== 1
10: ) {
11: throw new Error(
12: `Expected exactly one View check runs link in Quick links for getsentry/${repo}.`
13: );
14: }
15:
16: let details;
17: try {
18: details = parse(issueBody, { startRule: "ReleaseRevision" });
19: } catch {
20: throw new Error(
21: `Expected a View check runs link for getsentry/${repo} in the publish issue body.`
22: );
23: }
24:
25: if (details.repo !== repo) {
26: throw new Error(
27: `Expected a View check runs link for getsentry/${repo} in the publish issue body.`
28: );
29: }
30: ...
<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 { getReleaseRevisionDetails } = require("./release-revision");
3: const { isPublishPath } = require("./publish-location");
4: const {
5: isPublishRepository,
6: isReleaseVersion,
7: } = require("./publish-issue-validation");
8:
9: /**
10: * Matches the entire "Targets" section of a github publish issue body.
11: */
12: const TARGETS_SECTION_PARSER_REGEX =
13: /^(?!### Targets$\s)(?: *- \[[ xX]\] \S+\s*$(?:\r?\n)?)+/m;
14:
15: /**
16: * Matches all targets of a github publish issue body in a section that was already matched and extracted with `TARGETS_PARSER_REGEX`.
17: * 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-ci-poller-input.js</path>
<type>file</type>
<content>
1: const { getCiPollerInput } = require("../modules/ci-poller-input");
2: const { readFileSync } = require("fs");
3:
4: const issueBody = process.env.PUBLISH_ISSUE_BODY_FILE
5: ? readFileSync(process.env.PUBLISH_ISSUE_BODY_FILE, "utf8")
6: : process.env.PUBLISH_ISSUE_BODY || "";
7:
8: process.stdout.write(
9: JSON.stringify(
10: getCiPollerInput({
11: issueBody,
12: title: process.env.PUBLISH_TITLE || "",
13: revision: process.env.PUBLISH_REVISION || "",
14: })
15: )
16: );
(End of file - total 16 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/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/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/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 ...
diff --git a/.github/workflows/ci-poller.yml b/.github/workflows/ci-poller.yml
index 324b63f..a04d7f8 100644
--- a/.github/workflows/ci-poller.yml
+++ b/.github/workflows/ci-poller.yml
@@ -1,308 +1,324 @@
name: CI Status Poller
on:
schedule:
- cron: "*/5 * * * *"
workflow_dispatch:
inputs:
attempt:
description: Self-dispatch attempt counter (internal use)
required: false
default: "0"
permissions:
contents: read
issues: read
jobs:
check-ci:
runs-on: ubuntu-latest
environment: production
# Skip entirely (no runner provisioned) when there's nothing to check.
# Set to "true" by ci-pending.yml, reset to "false" here when done.
# Always allow workflow_dispatch for manual recovery.
if: vars.CI_POLLER_HAS_PENDING == 'true' || github.event_name == 'workflow_dispatch'
concurrency:
group: ci-status-poller
cancel-in-progress: false
steps:
# sentry-internal-app token ...
diff --git a/docs/publish-issue-format.md b/docs/publish-issue-format.md
index f5dfa40..9f9624e 100644
--- a/docs/publish-issue-format.md
+++ b/docs/publish-issue-format.md
@@ -1,86 +1,107 @@
# Publish Issue Format
The Publish workflow treats a publish issue as a release request. The title identifies
the release; the body supplies the merge target and selected targets. Do not edit the
title by hand unless it remains valid under this format.
## Title
-Every title starts with `publish: `. This EBNF is canonical:
-
-```text
-title = "publish: ", [ "getsentry/" ], repository, [ path ],
- "@", version ;
-repository = token, { token } ;
-path = "/", path-segment, { "/", path-segment } ;
-path-segment = token, { token } ;
-version = version-character, { version-character } ;
-token = ? ASCII letter, digit, ".", "_", or "-" ? ;
-version-character = token | "+" ;
+Every title starts with `publish: `. ...
<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: });
15:
16: test("rejects duplicate generated title grammar markers", () => {
17: expect(() =>
18: generateDocumentation({
19: documentation: `<!-- BEGIN GENERATED TITLE GRAMMAR -->
20: old
21: <!-- END GENERATED TITLE GRAMMAR -->
22: <!-- BEGIN GENERATED TITLE GRAMMAR -->
23: old
24: <!-- END GENERATED TITLE GRAMMAR ...
<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/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: ...
Found 82 matches
/home/byk/Code/getsentry/publish-workspace-acceptance/src/publish/__tests__/resolve-release-revision.js:
Line 4: const script = new URL("../resolve-release-revision.js", import.meta.url).pathname;
/home/byk/Code/getsentry/publish-workspace-acceptance/src/publish/resolve-release-revision.js:
Line 2: const { getReleaseRevision } = require("../modules/release-revision");
/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/ci-poller-input.js:
Line 5: } = require("./release-revision");
/home/byk/Code/getsentry/publish-workspace-acceptance/src/modules/release-revision.js:
Line 12: `Expected exactly one View check runs link in Quick links for getsentry/${repo}.`
Line 21: `Expected a View check runs link for getsentry/${repo} in the publish issue body.`
Line 27: `Expected a View check runs link for getsentry/${repo} in the publish issue ...
File not found: /home/byk/Code/getsentry/publish-workspace-acceptance/.github/workflows/ci-pending.yml
No files found
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/.github</path> <type>directory</type> <entries> CODEOWNERS workflows/ (2 entries) </entries>
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/.github/workflows</path> <type>directory</type> <entries> auto-approve.yml ci-poller.yml cocoapods-keepalive.yml publish.yml test.yml (5 entries) </entries>
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/.github/workflows/test.yml</path>
<type>file</type>
<content>
1: name: Test
2: on:
3: push:
4: branches:
5: - main
6: pull_request:
7:
8: permissions:
9: contents: read
10:
11: defaults:
12: run:
13: shell: bash
14: jobs:
15: unit-test:
16: runs-on: ubuntu-latest
17: name: unit tests
18: steps:
19: - uses: actions/checkout@v6
20: - uses: actions/setup-node@v6
21: with:
22: node-version: 24
23: - uses: actions/cache@v4
24: id: cache
25: with:
26: path: node_modules
27: key: ${{ runner.os }}-node_modules-${{ hashFiles('package.json', 'yarn.lock') }}
28:
29: - name: Install Dependencies
30: if: steps.cache.outputs.cache-hit != 'true'
31: run: yarn install --frozen-lockfile
32:
33: - name: Test
34: run: yarn test
(End of file - total 34 lines)
</content>
<path>/home/byk/Code/getsentry/publish-workspace-acceptance/.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: ...
yarn run v1.22.22 $ yarn check:generated && vitest run --printConsoleTrace $ node scripts/generate-publish-issue-title-parser.js --check [1m[46m RUN [49m[22m [36mv4.1.0 [39m[90m/home/byk/Code/getsentry/publish-workspace-acceptance[39m [2m Test Files [22m [1m[32m13 passed[39m[22m[90m (13)[39m [2m Tests [22m [1m[32m87 passed[39m[22m[90m (87)[39m [2m Start at [22m 03:32:18 [2m Duration [22m 2.19s[2m (transform 272ms, setup 0ms, import 1.43s, tests 923ms, environment 2ms)[22m Done in 3.57s.
yarn run v1.22.22 $ eslint src .github --ignore-pattern '!.github' Done in 0.96s.
<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/publish/post-workflow-details.js</path>
<type>file</type>
<content>
1: const postWorkflowDetails = require('../modules/post-workflow-details.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:
8: postWorkflowDetails({context, octokit});
(End of file - total 8 lines)
</content>
accepted malformed extra marker
CI poller per-issue shell block passes bash -n
<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/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 markers = documentation.match(
28: /<!-- (?:BEGIN|END) GENERATED TITLE GRAMMAR -->/g
29: );
30:
31: if (
32: markers?.filter(marker => marker.includes("BEGIN")).length !== 1 ||
33: markers?.filter(marker => ...
Found 2 matches /home/byk/Code/getsentry/publish-workspace-acceptance/src/publish/resolve-ci-poller-input.js: Line 4: const issueBody = process.env.PUBLISH_ISSUE_BODY_FILE Line 5: ? readFileSync(process.env.PUBLISH_ISSUE_BODY_FILE, "utf8")
indexed revision update preserves all non-SHA bytes
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__/details-from-context.js M src/modules/__tests__/publish-workflow.js M src/modules/__tests__/release-revision.js M src/modules/details-from-context.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-ci-poller-input.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__/ ?? ...
Malformed documentation markers are accepted.
generateDocumentation counts only exact markers, then replaces the valid pair without rejecting marker-like malformed extras (scripts/generate-publish-issue-title-parser.js:27-49).
Read-only probe: a valid pair plus <!-- BEGIN GENERATED TITLE GRAMMAR -- > returned successfully.
Existing tests cover missing and duplicate exact markers only (src/modules/__tests__/generate-publish-issue-title-parser.js:7-28), not malformed markers alongside a valid pair.
Not all checkouts use v7.
The requested policy remains unmet:
.github/workflows/publish.yml:120.github/workflows/publish.yml:159.github/workflows/test.yml:19.github/workflows/auto-approve.yml:16.github/workflows/ci-poller.yml:52).CI-poller regression coverage does not prove the repaired failure paths.
The workflow now correctly isolates each issue in a subshell and cleans temporary files (.github/workflows/ci-poller.yml:95-163), but no test exercises resolver failure, invalid/missing .issueBody, no-edit behavior, cleanup, or exact trailing-byte preservation. The existing CI-poller test only checks for a resolver reference (src/modules/__tests__/ci-poller-input.js:41-53). This leaves the prior blocker unguarded against regression.
| Concern | Result | Evidence |
|---|---|---|
| Per-issue CI-poller error branches safely skip | PASS | The per-issue subshell makes exit 0 valid and local (ci-poller.yml:95-108, :255). bash -n on the extracted block passed. |
| Resolver failure cannot edit the body | PASS | Both resolver and JSON-decoding failures exit before gh issue edit (ci-poller.yml:152-163). |
| Resolver JSON cannot produce empty/truncated updates | PASS | jq -ejr requires a string .issueBody; only successful decoding reaches --body-file (ci-poller.yml:157-163). ...
Date: Sep 8, 2026 * 🟡 (03:33) `src/publish/post-workflow-details.js` invokes `postWorkflowDetails` from `../modules/post-workflow-details.js` with `{context, octokit}`; `context` is `github.context`, and `octokit` is `github.getOctokit(getGitHubToken())`. * 🟡 (03:33) Investigation result: `generateDocumentation()` accepted a malformed extra generated-title-grammar marker. * 🟡 (03:33) Verificat…
Date: Sep 8, 2026 * 🟡 (03:33) `src/modules/__tests__/ci-poller-input.js` imports `getCiPollerInput` from `../ci-poller-input.js`; fixtures use `REVISION = "7e5ca7ed5581552de066e2a8bc295b8306be38ac"` and an issue body for `@byk`, merge target `(default)`, a toolkit changes link, and `View check runs` URL `https://github.com/getsentry/toolkit/commit/${REVISION}/checks/`. * 🟡 (03:33) `getCiPollerI…
Date: Sep 8, 2026 * 🟡 (03:31) `src/publish/__tests__/discover-location.js` tests `discoverLocation()` retains checkout-path behavior when root config is absent: input `{ path: "./packages/cli" }`, `repositoryDirectory: "__repo__"`, and `exists: () => false` return `{ path: "./packages/cli" }`. * 🟡 (03:31) `src/publish/__tests__/discover-location.js` tests exact workspace discovery through the r…
Date: Sep 8, 2026 * 🔴 (03:30) User directive: New Craft requests always include the checkout repository identity. * 🔴 (03:30) User directive: A missing root `.craft.yml` always means checkout-path behavior. * 🔴 (03:30) User directive: The controller never normalizes names. * 🟡 (03:30) `docs/publish-issue-format.md` now presents the canonical publish-title syntax as a generated Peggy grammar f…
Date: Sep 8, 2026 * 🔴 (03:30) User directive: Always allow `workflow_dispatch` for manual recovery; `.github/workflows/ci-poller.yml` `check-ci` gate is `vars.CI_POLLER_HAS_PENDING == 'true' || github.event_name == 'workflow_dispatch'`. * 🔴 (03:30) User stated the CI poller always adds `ci-ready` after checking CI; `publish.yml` relies on the fresh `ci-ready` labeled event to trigger publishing…
Date: Sep 8, 2026 * 🔴 (03:29) User supplied a large, line-numbered excerpt of generated JavaScript parser code (lines at least 25–1275 visible), including PEG-style parsing internals such as `peg$currPos`, `peg$FAILED`, `peg$silentFails`, `peg$fail`, `peg$buildStructuredError`, `location()`, and grammar-action functions including `peg$f11(entries)`.
Date: Sep 8, 2026 * 🟡 [requested-audit] (03:28) User requested a strict, read-only adversarial audit of the current uncommitted review-fix worktree at `/home/byk/Code/getsentry/publish-workspace-acceptance`, against branch baseline `a81ab03` (`a81ab033098f151fbc291b7b0c934901405bb162`). User prohibited editing, formatting, regenerating, or otherwise mutating files; permitted only non-mutating co…