Dashboard › publish › Session 11FQPwdNCjDr
11FQPwdNCjDr0vdipIndependently audit the exact current worktree at /home/byk/Code/getsentry/publish in READ-ONLY mode. Do not edit, format, generate, stage, or mutate files. Inspect the full diff against origin/main and every changed or untracked behavioral file. You MUST return a non-empty report. If any tool blocks you, begin BLOCKED and quote the exact tool/error. Provide current file:line evidence and classify each required area PASS, CONCERN, or MUST-FIX: (1) latest accepted/ci-ready labeled-versus-unlabeled event semantics, including malformed and very large event IDs; (2) request digest and immutable trigger snapshot binding through human approval, auto-approval, polling, CI-ready proof, and publication; (3) requester/approver separation and all special bot identities; (4) arbitrary-ref workflow_dispatch, trusted default-branch controller code, token/secret timing and environment boundaries; (5) check-suite/branch-head failures, release revision movement, and no fail-open CI path; (6) stale ci-ready removal, immediate final revalidation, mandatory fresh add, failed-add recovery; (7) pre-Craft approval fence, exact approved checkout, workspace/path containment, immutable action and Craft references; (8) shell/JSON/environment injection and attacker-controlled GitHub fields; (9) success/failure/cancellation deauthorization and cleanup independence. Run only read-only tests/probes if useful. End with exactly MERGE or DO-NOT-MERGE. Empty output is forbidden.
No files found
## main...origin/main MM .github/workflows/auto-approve.yml MM .github/workflows/ci-poller.yml M .github/workflows/cocoapods-keepalive.yml MM .github/workflows/publish.yml M .github/workflows/test.yml M AGENTS.md M README.md M docs/key_rotation_vault_changes.md M docs/rfc.md M src/libs/__tests__/github.js M src/libs/github.js M src/modules/__tests__/ci-poller-input.js M src/modules/__tests__/ci-poller-workflow.js MM src/modules/__tests__/details-from-context.js M src/modules/__tests__/process-end-state.js M src/modules/__tests__/publish-location.js M src/modules/__tests__/publish-workflow.js M src/modules/__tests__/release-revision.js MM src/modules/__tests__/update-issue.js M src/modules/ci-poller-input.js M src/modules/details-from-context.js M src/modules/post-workflow-details.js MM src/modules/process-end-state.js M src/modules/publish-location.js M src/modules/release-revision.js M src/modules/update-issue.js M src/publish/__tests__/discover-location.js M ...
/home/byk/Code/getsentry/publish/package.json
<path>/home/byk/Code/getsentry/publish/package.json</path>
<type>file</type>
<content>
1: {
2: "name": "publish",
3: "private": true,
4: "version": "0.0.1",
5: "description": "Approval-based publishing system for Sentry",
6: "main": "index.js",
7: "repository": "git@github.com:getsentry/publish.git",
8: "author": "Sentry Open Source <oss@sentry.io>",
9: "license": "Apache-2.0",
10: "volta": {
11: "node": "24.0.0",
12: "yarn": "1.22.22"
13: },
14: "resolutions": {
15: "undici": "^6.23.0"
16: },
17: "scripts": {
18: "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": "^8.9.0",
27: ...
No files found
The server restarted while your read-only audit was active. Resume from your existing context and complete the exact-tree security review now. Do not edit or mutate files. Return the required non-empty PASS/CONCERN/MUST-FIX report across all nine specified areas with current file:line evidence, then end exactly MERGE or DO-NOT-MERGE. If blocked, begin BLOCKED and quote the exact blocker.
<path>/home/byk/Code/getsentry/publish/.github/workflows/auto-approve.yml</path>
<type>file</type>
<content>
1: name: auto-approve non-sdks
2: on:
3: issues:
4: types: [opened]
5:
6: permissions:
7: contents: read
8: issues: write
9:
10: jobs:
11: auto-approve:
12: runs-on: ubuntu-latest
13: environment: production
14: if: |
15: (github.actor == 'sentry-release-bot[bot]' || github.actor == 'getsantry[bot]') &&
16: startsWith(github.event.issue.title, 'publish: ')
17: steps:
18: - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262
19: with:
20: persist-credentials: false
21: - name: Get auth token
22: id: token
23: uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1
24: with:
25: client-id: ${{ vars.SENTRY_INTERNAL_APP_ID }}
26: private-key: ${{ secrets.SENTRY_INTERNAL_APP_PRIVATE_KEY }}
27: - name: Bind request snapshot
28: ...
<path>/home/byk/Code/getsentry/publish/.github/workflows/ci-poller.yml</path> <type>file</type> <content> 1: name: CI Status Poller 2: 3: on: 4: schedule: 5: - cron: "*/5 * * * *" 6: repository_dispatch: 7: types: [ci-poller] 8: workflow_run: 9: workflows: [Run CI Status Poller] 10: types: [completed] 11: 12: permissions: 13: contents: read 14: issues: write 15: 16: jobs: 17: check-ci: 18: runs-on: ubuntu-latest 19: environment: production 20: # Skip entirely (no runner provisioned) when there's nothing to check. 21: # Set to "true" by ci-pending.yml, reset to "false" here when done. 22: # repository_dispatch and a successful main-branch run of the protected 23: # workflow_dispatch relay are the trusted manual-recovery paths. 24: if: >- 25: vars.CI_POLLER_HAS_PENDING == 'true' 26: || github.event_name == 'repository_dispatch' 27: || ( 28: github.event_name == 'workflow_run' 29: && ...
<path>/home/byk/Code/getsentry/publish/.github/workflows/publish.yml</path>
<type>file</type>
<content>
1: name: Publish
2: on:
3: issues:
4: types: [labeled]
5:
6: concurrency:
7: # Use the issue title (e.g. "publish: getsentry/foo@1.2.3") so duplicate
8: # issues for the same repo@version share a concurrency group.
9: group: ${{ github.event.issue.title }}
10: cancel-in-progress: false
11:
12: permissions:
13: contents: read
14: issues: write
15: packages: write
16:
17: jobs:
18: # When accepted is added to a publish issue:
19: # - Add ci-pending (and remove ci-failed if retrying)
20: # - Enable the poller via CI_POLLER_HAS_PENDING=true
21: # - Comment on the issue
22: # - Trigger the poller immediately so we don't wait for the next cron tick
23: # The publish job below requires ci-ready, so it will not fire until the
24: # poller flips ci-pending → ci-ready (which also prevents publishing without
25: # CI verification in the ...
<path>/home/byk/Code/getsentry/publish/.github/workflows/ci-poller-dispatch.yml</path>
<type>file</type>
<content>
1: name: Run CI Status Poller
2:
3: on:
4: workflow_dispatch:
5:
6: permissions: {}
7:
8: jobs:
9: dispatch:
10: # production only permits deployments from main. Repository secrets must
11: # never be used by this workflow; protected environment secrets enforce
12: # that an arbitrary dispatched ref cannot add them.
13: environment: production
14: runs-on: ubuntu-latest
15: steps:
16: - run: ":"
(End of file - total 16 lines)
</content>
<path>/home/byk/Code/getsentry/publish/.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@d23441a48e516b6c34aea4fa41551a30e30af803
20: - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38
21: with:
22: node-version: 24
23: - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830
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: ...
<path>/home/byk/Code/getsentry/publish/.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:
10: permissions:
11: contents: read
12:
13: jobs:
14: keepalive:
15: name: Refresh CocoaPods Session
16: runs-on: macos-15
17: environment: production
18: env:
19: COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }}
20:
21: steps:
22: - name: Install CocoaPods
23: run: gem install cocoapods
24: - name: Print CocoaPods Version
25: run: pod --version
26: - name: Refresh CocoaPods ...
<path>/home/byk/Code/getsentry/publish/src/modules/approval-attestation.js</path>
<type>file</type>
<content>
1: const crypto = require("crypto");
2:
3: const APPROVAL_ATTESTATION_PREFIX = "<!-- publish-approval ";
4: const AUTO_APPROVAL_ATTESTATION_PREFIX = "<!-- publish-auto-approval ";
5: const CI_READY_ATTESTATION_PREFIX = "<!-- publish-ci-ready ";
6: const ATTESTATION_SUFFIX = " -->";
7:
8: function createAttestation(prefix, value) {
9: return `${prefix}${Buffer.from(JSON.stringify(value)).toString(
10: "base64url"
11: )}${ATTESTATION_SUFFIX}`;
12: }
13:
14: function requestDigest({ body, labels, title }) {
15: if (
16: typeof body !== "string" ||
17: !Array.isArray(labels) ||
18: typeof title !== "string"
19: ) {
20: throw new Error("Invalid publish request");
21: }
22:
23: return crypto
24: .createHash("sha256")
25: .update(
26: JSON.stringify({
27: body,
28: dryRun: labels.some((label) => label.name === ...
<path>/home/byk/Code/getsentry/publish/src/modules/approval-authorizer.js</path>
<type>file</type>
<content>
1: const ALLOWED_ROLE_NAMES = new Set(["write", "maintain", "admin"]);
2: const { parsePublishTitle } = require("./details-from-context.js");
3:
4: const AUTO_APPROVAL_LABELER = "sentry-internal-app[bot]";
5: const AUTO_APPROVERS = new Set(["getsantry[bot]", "sentry-release-bot[bot]"]);
6:
7: function isAutoApprovedRepository({
8: actor,
9: autoApprovedRepositories,
10: issueTitle,
11: }) {
12: if (!AUTO_APPROVERS.has(actor)) {
13: return false;
14: }
15:
16: const title = parsePublishTitle(issueTitle);
17:
18: if (!title) {
19: return false;
20: }
21:
22: return autoApprovedRepositories.has(
23: `getsentry/${title.repo}${title.path || ""}`
24: );
25: }
26:
27: async function authorizeApproval({ actor, issueTitle, getPermission }) {
28: const title = parsePublishTitle(issueTitle);
29:
30: if (!title) {
31: return { authorized: ...
<path>/home/byk/Code/getsentry/publish/src/publish/authorize-approval.js</path>
<type>file</type>
<content>
1: const fs = require("fs");
2: const {
3: createApprovalAttestation,
4: currentAcceptedEvent,
5: hasAutoApprovalAttestation,
6: requestDigest,
7: } = require("../modules/approval-attestation.js");
8: const {
9: AUTO_APPROVAL_LABELER,
10: AUTO_APPROVERS,
11: authorizeApproval,
12: isAutoApprovedRepository,
13: } = require("../modules/approval-authorizer.js");
14:
15: function getAutoApprovedRepositories() {
16: return new Set(
17: fs
18: .readFileSync("auto-approve-repos.txt", "utf8")
19: .split(/\r?\n/)
20: .filter(Boolean)
21: );
22: }
23:
24: async function getPermission({ owner, repository, username }) {
25: const response = await getGitHubResponse(
26: `repos/${encodeURIComponent(owner)}/${encodeURIComponent(
27: repository
28: )}/collaborators/${encodeURIComponent(username)}/permission`
29: );
30:
31: if ...
<path>/home/byk/Code/getsentry/publish/src/publish/current-accepted-event.js</path>
<type>file</type>
<content>
1: const { currentAcceptedEvent } = require("../modules/approval-attestation.js");
2: const { getAllPages } = require("./validate-approval-attestation.js");
3:
4: async function getCurrentAcceptedEvent({
5: getIssueEvents,
6: issueNumber,
7: repository,
8: }) {
9: const event = currentAcceptedEvent(
10: await getIssueEvents({ repository, issueNumber })
11: );
12:
13: if (!event) {
14: throw new Error("The issue has no current accepted label event");
15: }
16:
17: return event;
18: }
19:
20: async function main() {
21: for (const name of [
22: "APPROVAL_TOKEN",
23: "APPROVAL_ISSUE_NUMBER",
24: "APPROVAL_ISSUE_REPOSITORY",
25: ]) {
26: if (!process.env[name]) {
27: throw new Error(`No "${name}" environment variable found`);
28: }
29: }
30:
31: const event = await getCurrentAcceptedEvent({
32: getIssueEvents: ({ ...
<path>/home/byk/Code/getsentry/publish/src/publish/request-digest-from-event.js</path>
<type>file</type>
<content>
1: const fs = require("fs");
2: const { requestDigest } = require("../modules/approval-attestation.js");
3:
4: function requestDigestFromEvent(event) {
5: if (!event || typeof event.issue !== "object" || event.issue === null) {
6: throw new Error("The GitHub event has no issue snapshot");
7: }
8:
9: return requestDigest(event.issue);
10: }
11:
12: function main() {
13: if (!process.env.GITHUB_EVENT_PATH) {
14: throw new Error('No "GITHUB_EVENT_PATH" environment variable found');
15: }
16:
17: if (!process.env.GITHUB_OUTPUT) {
18: throw new Error('No "GITHUB_OUTPUT" environment variable found');
19: }
20:
21: const event = JSON.parse(
22: fs.readFileSync(process.env.GITHUB_EVENT_PATH, "utf8")
23: );
24: fs.appendFileSync(
25: process.env.GITHUB_OUTPUT,
26: `request_digest=${requestDigestFromEvent(event)}\n`
27: );
28: ...
<path>/home/byk/Code/getsentry/publish/src/publish/record-auto-approval-attestation.js</path>
<type>file</type>
<content>
1: const fs = require("fs");
2: const {
3: createAutoApprovalAttestation,
4: requestDigest,
5: } = require("../modules/approval-attestation.js");
6: const { getIssue } = require("./validate-approval-attestation.js");
7:
8: async function recordAutoApprovalAttestation({
9: autoApprover,
10: expectedRequestDigest,
11: getIssue,
12: issueNumber,
13: issueTitle,
14: repository,
15: }) {
16: const issue = await getIssue({ repository, issueNumber });
17:
18: if (
19: issue.state !== "open" ||
20: issue.title !== issueTitle ||
21: requestDigest(issue) !== expectedRequestDigest ||
22: issue.user?.login?.toLowerCase() !== autoApprover.toLowerCase()
23: ) {
24: throw new Error("The automated approval request changed before approval");
25: }
26:
27: return createAutoApprovalAttestation({ autoApprover, issue });
28: }
29:
30: ...
<path>/home/byk/Code/getsentry/publish/src/publish/record-ci-ready-attestation.js</path>
<type>file</type>
<content>
1: const fs = require("fs");
2: const {
3: createCiReadyAttestation,
4: currentAcceptedEvent,
5: requestDigest,
6: } = require("../modules/approval-attestation.js");
7: const {
8: getAllPages,
9: getGitHubResponse,
10: getIssue,
11: } = require("./validate-approval-attestation.js");
12:
13: async function getAuthenticatedLogin() {
14: const response = await getGitHubResponse("user");
15:
16: if (!response.ok) {
17: throw new Error(
18: `Could not retrieve the authenticated user: GitHub returned ${response.status}`
19: );
20: }
21:
22: const { login } = await response.json();
23:
24: if (typeof login !== "string") {
25: throw new Error("GitHub returned no authenticated user login");
26: }
27:
28: return login;
29: }
30:
31: async function recordCiReadyAttestation({
32: expectedAcceptedEvent,
33: ...
<path>/home/byk/Code/getsentry/publish/src/publish/validate-approval-attestation.js</path>
<type>file</type>
<content>
1: const {
2: currentAcceptedEvent,
3: currentCiReadyEvent,
4: hasApprovalAttestation,
5: hasCiReadyAttestation,
6: requestDigest,
7: } = require("../modules/approval-attestation.js");
8:
9: async function getGitHubResponse(path) {
10: return fetch(`https://api.github.com/${path}`, {
11: headers: {
12: Accept: "application/vnd.github+json",
13: Authorization: `Bearer ${process.env.APPROVAL_TOKEN}`,
14: "X-GitHub-Api-Version": "2026-03-10",
15: },
16: });
17: }
18:
19: async function getIssue({ repository, issueNumber }) {
20: const response = await getGitHubResponse(
21: `repos/${repository}/issues/${issueNumber}`
22: );
23:
24: if (!response.ok) {
25: throw new Error(
26: `Could not retrieve issue #${issueNumber}: GitHub returned ${response.status}`
27: );
28: }
29:
30: return ...
<path>/home/byk/Code/getsentry/publish/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/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: const { requestDigest } = require("./approval-attestation");
12:
13: function getCiPollerInput({ title, issueBody, labels, revision }) {
14: let parsedTitle;
15: try {
16: parsedTitle = parsePublishIssueTitle(title);
17: } catch {
18: throw new Error(`Invalid publish issue title: '${title}'`);
19: }
20: const { repo, version } = parsedTitle;
21: if (!isPublishRepository(repo)) {
22: throw new Error(`Invalid publish issue repository: '${repo}'`);
23: }
24: if (!isReleaseVersion(version)) {
25: throw new ...
<path>/home/byk/Code/getsentry/publish/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: labels: JSON.parse(process.env.PUBLISH_ISSUE_LABELS || ""),
13: title: process.env.PUBLISH_TITLE || "",
14: revision: process.env.PUBLISH_REVISION || "",
15: })
16: )
17: );
(End of file - total 17 lines)
</content>
<path>/home/byk/Code/getsentry/publish/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 (parse(issueBody, { startRule: "CheckRunsLinkCount" }) !== 1) {
9: throw new Error(
10: `Expected exactly one View check runs link in Quick links for getsentry/${repo}.`
11: );
12: }
13:
14: let details;
15: try {
16: details = parse(issueBody, { startRule: "ReleaseRevision" });
17: } catch {
18: throw new Error(
19: `Expected a View check runs link for getsentry/${repo} in the publish issue body.`
20: );
21: }
22:
23: if (details.repo !== repo) {
24: throw new Error(
25: `Expected a View check runs link for getsentry/${repo} in the publish issue body.`
26: );
27: }
28:
29: return details;
30: }
31:
32: ...
<path>/home/byk/Code/getsentry/publish/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/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: inputs();
(End of file - total 11 lines)
</content>
<path>/home/byk/Code/getsentry/publish/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.slice(2).split("/").every(isSafeWorkspaceSegment)))
30: );
31: }
32:
33: function isWorkspaceName(name) {
34: return (
35: typeof name === "string" && ...
<path>/home/byk/Code/getsentry/publish/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: const CRAFT_IMAGE =
10: "getsentry/craft@sha256:9a4a5d5efa44a00c2215078ead39800d4aaa5a97908b94f45a64d7d506d6e14b";
11:
12: function getWorkspaceNames({
13: repositoryDirectory,
14: exists = existsSync,
15: execFile = execFileSync,
16: }) {
17: if (!exists(path.join(repositoryDirectory, ".craft.yml"))) {
18: return [];
19: }
20:
21: const output = execFile(
22: "docker",
23: [
24: "run",
25: "--rm",
26: "--volume",
27: `${path.resolve(repositoryDirectory)}:/github/workspace/__repo__`,
28: "--workdir",
29: "/github/workspace/__repo__",
30: ...
Your response was empty and did not satisfy the review gate. This is your one retry. Complete the READ-ONLY exact-tree audit and return substantive evidence now. At minimum, provide one current file:line PASS/CONCERN/MUST-FIX finding for each of the nine areas in the prior prompt. If repository or tools are unavailable, begin BLOCKED and quote the exact failing command/tool and error. Do not edit or mutate anything. End exactly MERGE or DO-NOT-MERGE. Never return empty output.
<path>/home/byk/Code/getsentry/publish/src/libs/github.js</path>
<type>file</type>
<content>
1: /**
2: * This is a helper function that throws a useful error message if the
3: * workflow environment is not configured correctly.
4: *
5: * @returns string
6: */
7: function getGitHubToken() {
8: if (!process.env.GITHUB_TOKEN) {
9: throw new Error(
10: 'No "GITHUB_TOKEN" environment variable found. ' +
11: "Please ensure the workflow is configured correctly"
12: );
13: }
14: return process.env.GITHUB_TOKEN;
15: }
16:
17: module.exports = {
18: getGitHubToken,
19: };
(End of file - total 19 lines)
</content>
<path>/home/byk/Code/getsentry/publish/src/modules/process-end-state.js</path>
<type>file</type>
<content>
1: const Sentry = require("@sentry/node");
2:
3: async function processEndState({ context, octokit, inputs = {}, status }) {
4: const { repo, version } = inputs;
5: const { repo: publishRepo, runId: run_id } = context;
6: const { number: issue_number } = context.payload.issue;
7:
8: // Validate the status before applying any terminal state transition.
9: sentryInfoFromDetails({ status, repo });
10:
11: if (status === "success") {
12: await octokit.rest.issues.update({
13: ...publishRepo,
14: issue_number,
15: state: "closed",
16: });
17: }
18:
19: const workflowInfo = (
20: await octokit.rest.actions.getWorkflowRun({
21: ...publishRepo,
22: run_id,
23: })
24: ).data;
25:
26: const details = {
27: repo,
28: version,
29: publishRepo,
30: run_id,
31: issue_number,
32: workflowInfo,
33: ...
<path>/home/byk/Code/getsentry/publish/src/modules/update-issue.js</path>
<type>file</type>
<content>
1: const fs = require("fs");
2: const {
3: TARGETS_SECTION_PARSER_REGEX,
4: TARGETS_PARSER_REGEX,
5: } = require("./details-from-context");
6:
7: async function updateTargets({
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: ...publishRepo,
35: ...
<path>/home/byk/Code/getsentry/publish/src/modules/post-workflow-details.js</path>
<type>file</type>
<content>
1: async function postWorkflowDetails({ context, octokit }) {
2: const { repo: publishRepo, runId: run_id } = context;
3: const { number: issue_number } = context.payload.issue;
4:
5: const workflowInfo = (
6: await octokit.rest.actions.getWorkflowRun({
7: ...publishRepo,
8: run_id,
9: })
10: ).data;
11:
12: return octokit.rest.issues.createComment({
13: ...publishRepo,
14: issue_number,
15: body: `Publishing: [run#${run_id}](${workflowInfo.html_url})`,
16: });
17: }
18:
19: module.exports = postWorkflowDetails;
(End of file - total 19 lines)
</content>
<path>/home/byk/Code/getsentry/publish/src/publish/post-result.js</path>
<type>file</type>
<content>
1: const processEndState = require("../modules/process-end-state.js");
2: const { getGitHubToken } = require("../libs/github.js");
3: const github = require("@actions/github");
4:
5: async function main({
6: context = github.context,
7: octokit = github.getOctokit(getGitHubToken()),
8: report = processEndState,
9: status = process.argv.slice(2)[0],
10: } = {}) {
11: let inputs = {};
12:
13: if (process.env.PUBLISH_ARGS) {
14: try {
15: inputs = JSON.parse(process.env.PUBLISH_ARGS);
16: } catch {
17: console.warn("Could not parse publish inputs; reporting without them");
18: }
19: }
20:
21: await report({ context, octokit, inputs, status });
22: }
23:
24: if (require.main === module) {
25: main().catch((error) => {
26: console.error(error);
27: process.exitCode = 1;
28: });
29: }
30:
31: module.exports = { main };
(End of file - ...
<path>/home/byk/Code/getsentry/publish/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>
<path>/home/byk/Code/getsentry/publish/src/publish/update-issue.js</path>
<type>file</type>
<content>
1: const { updateIssue } = require("../modules/update-issue.js");
2: const { getGitHubToken } = require("../libs/github");
3: const github = require("@actions/github");
4:
5: async function main() {
6: const context = github.context;
7: const octokit = github.getOctokit(getGitHubToken());
8: let inputs;
9:
10: if (process.env.PUBLISH_ARGS) {
11: try {
12: inputs = JSON.parse(process.env.PUBLISH_ARGS);
13: } catch {
14: console.warn("Could not parse publish inputs; skipping target update");
15: }
16: }
17:
18: await updateIssue({ context, octokit, inputs });
19: }
20:
21: if (require.main === module) {
22: main().catch((error) => {
23: console.error(error);
24: process.exitCode = 1;
25: });
26: }
27:
28: module.exports = { main };
(End of file - total 28 lines)
</content>
<path>/home/byk/Code/getsentry/publish/src/modules/__tests__/approval-attestation.js</path>
<type>file</type>
<content>
1: import { describe, expect, test } from "vitest";
2:
3: const {
4: createApprovalAttestation,
5: createCiReadyAttestation,
6: currentAcceptedEvent,
7: hasApprovalAttestation,
8: hasCiReadyAttestation,
9: parseApprovalAttestation,
10: requestDigest,
11: } = require("../approval-attestation.js");
12:
13: function issue(title, { body = "", dryRun = false } = {}) {
14: return {
15: body,
16: labels: dryRun ? [{ name: "dry-run" }] : [],
17: title,
18: };
19: }
20:
21: describe("approval attestations", () => {
22: test("matches the latest accepted event to a trusted attestation", () => {
23: const title = "publish: getsentry/relay/py@1.2.3";
24: const publishIssue = issue(title);
25: const attestation = createApprovalAttestation({
26: actor: "contractor",
27: eventId: "200",
28: issue: publishIssue,
29: ...
Found 57 matches
/home/byk/Code/getsentry/publish/src/publish/record-ci-ready-attestation.js:
Line 56: acceptedEvent.eventId !== expectedAcceptedEvent.eventId))
Line 84: eventId: process.env.EXPECTED_ACCEPTED_EVENT_ID,
/home/byk/Code/getsentry/publish/src/publish/validate-approval-attestation.js:
Line 81: event.eventId === expectedAcceptedEvent.eventId)) &&
Line 95: issue.labels.some((label) => label.name === "ci-ready") &&
Line 127: eventId: process.env.EXPECTED_ACCEPTED_EVENT_ID,
/home/byk/Code/getsentry/publish/src/publish/__tests__/record-ci-ready-attestation.js:
Line 9: } = require("../record-ci-ready-attestation.js");
Line 12: test("binds the current approval to the app that will add ci-ready", async () => {
Line 54: test("rejects an approval that changes before ci-ready is recorded", async () => {
Line 93: expectedAcceptedEvent: { actor: "contractor", eventId: "100" ...
<path>/home/byk/Code/getsentry/publish/src/publish/__tests__/current-accepted-event.js</path>
<type>file</type>
<content>
1: import { describe, expect, test, vi } from "vitest";
2:
3: const { getCurrentAcceptedEvent } = require("../current-accepted-event.js");
4:
5: describe("getCurrentAcceptedEvent", () => {
6: test("returns the latest accepted-label event", async () => {
7: await expect(
8: getCurrentAcceptedEvent({
9: getIssueEvents: vi.fn().mockResolvedValue([
10: {
11: actor: { login: "contractor" },
12: event: "labeled",
13: id: "100",
14: label: { name: "accepted" },
15: },
16: {
17: actor: { login: "contractor" },
18: event: "labeled",
19: id: "200",
20: label: { name: "accepted" },
21: },
22: ]),
23: issueNumber: "123",
24: repository: "getsentry/publish",
25: })
26: ).resolves.toEqual({ ...
<path>/home/byk/Code/getsentry/publish/src/modules/__tests__/approval-authorizer.js</path>
<type>file</type>
<content>
1: import { describe, expect, test, vi } from "vitest";
2:
3: const { authorizeApproval } = require("../approval-authorizer.js");
4:
5: const issueTitle = "publish: getsentry/sentry-javascript@10.0.0";
6:
7: describe("authorizeApproval", () => {
8: test.each(["getsantry[bot]", "sentry-release-bot[bot]"])(
9: "rejects direct approval by %s without a target repository lookup",
10: async (actor) => {
11: const getPermission = vi.fn();
12:
13: await expect(
14: authorizeApproval({
15: actor,
16: issueTitle,
17: getPermission,
18: autoApprovedRepositories: new Set(["getsentry/sentry-javascript"]),
19: })
20: ).resolves.toEqual({
21: authorized: false,
22: repository: "getsentry/sentry-javascript",
23: });
24:
25: ...
<path>/home/byk/Code/getsentry/publish/src/publish/__tests__/authorize-approval.js</path>
<type>file</type>
<content>
1: import { afterEach, describe, expect, test, vi } from "vitest";
2: import fs from "fs";
3: const {
4: createAutoApprovalAttestation,
5: requestDigest,
6: } = require("../../modules/approval-attestation.js");
7: const {
8: getAutoApprovedRepositories,
9: main,
10: } = require("../authorize-approval.js");
11:
12: const originalEnvironment = { ...process.env };
13:
14: afterEach(() => {
15: process.env = { ...originalEnvironment };
16: vi.restoreAllMocks();
17: vi.resetModules();
18: });
19:
20: function jsonResponse(json) {
21: return { ok: true, json: vi.fn().mockResolvedValue(json) };
22: }
23:
24: async function runAuthorization({ actor, issueTitle, responses }) {
25: process.env.GITHUB_OUTPUT = "/tmp/github-output";
26: process.env.APPROVAL_TOKEN = "release-bot-token";
27: process.env.APPROVAL_ACTOR = actor;
28: ...
Found 16 matches
/home/byk/Code/getsentry/publish/src/publish/__tests__/workflow-action-pinning.js:
Line 36: for (const [, revision] of actionReferences) {
Line 37: expect(revision).toMatch(/^[a-f0-9]{40}$/);
/home/byk/Code/getsentry/publish/src/publish/__tests__/ci-poller-workflow.js:
Line 13: /- name: Get publish code\n\s+uses: actions\/checkout@[a-f0-9]{40}\n\s+with:\n(?:\s+#.*\n)*\s+ref: \$\{\{ github\.event\.repository\.default_branch \}\}/
Line 30: "github.event.workflow_run.head_branch == github.event.repository.default_branch"
/home/byk/Code/getsentry/publish/src/modules/__tests__/publish-workflow.js:
Line 63: test("uses the CI-approved revision for checkout, location resolution, and publishing", () => {
Line 65: const revision = workflow.indexOf(
Line 66: "name: Resolve CI-approved release revision"
Line 75: expect(informStart).toBeGreaterThan(revision);
Line 81: "ref: ${{ steps.release-revision.outputs.revision ...
<path>/home/byk/Code/getsentry/publish/src/modules/__tests__/ci-poller-workflow.js</path>
<type>file</type>
<content>
1: import {
2: mkdtempSync,
3: mkdirSync,
4: readFileSync,
5: readdirSync,
6: rmSync,
7: writeFileSync,
8: } from "fs";
9: import { tmpdir } from "os";
10: import { join } from "path";
11: import { spawnSync } from "child_process";
12:
13: import { afterEach, expect, test } from "vitest";
14:
15: const temporaryDirectories = [];
16: const INITIAL_REVISION = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
17: const UPDATED_REVISION = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb";
18:
19: function getPollerScript() {
20: const workflow = readFileSync(
21: join(__dirname, "../../../.github/workflows/ci-poller.yml"),
22: "utf8"
23: );
24: const section = workflow.match(
25: / {6}- name: Check CI status for ci-pending issues[\s\S]*? {8}run: \|\n(?<script>[\s\S]*?)\n {6}- name: Check for remaining pending issues/
26: );
27:
28: if ...
/home/byk/Code/getsentry/publish/src/modules/publish-issue-title.peggy /home/byk/Code/getsentry/publish/src/modules/publish-issue-title.js
<path>/home/byk/Code/getsentry/publish/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 join(characters); }
31:
32: ...
(no output)
diff --git a/src/libs/github.js b/src/libs/github.js
index e4d506f..f1188c1 100644
--- a/src/libs/github.js
+++ b/src/libs/github.js
@@ -1,17 +1,19 @@
/**
* This is a helper function that throws a useful error message if the
* workflow environment is not configured correctly.
*
* @returns string
*/
function getGitHubToken() {
if (!process.env.GITHUB_TOKEN) {
- throw new Error('No "GITHUB_TOKEN" environment variable found. ' +
- 'Please ensure the workflow is configured correctly');
+ throw new Error(
+ 'No "GITHUB_TOKEN" environment variable found. ' +
+ "Please ensure the workflow is configured correctly"
+ );
}
return process.env.GITHUB_TOKEN;
}
module.exports = {
getGitHubToken,
};
diff --git a/src/modules/ci-poller-input.js b/src/modules/ci-poller-input.js
index ca08a72..bf2d23d 100644
--- a/src/modules/ci-poller-input.js
+++ b/src/modules/ci-poller-input.js
@@ -1,42 +1,45 @@
const { parse: parsePublishIssueTitle } = ...
(no output)
...output truncated...
Full output saved to: /home/byk/.local/share/opencode/tool-output/tool_08c4f39660015xwf5bUbMT275W
+ id: dispatch-attempt
+ if: github.event_name == 'repository_dispatch'
+ env:
+ ATTEMPT: ${{ github.event.client_payload.attempt || '0' }}
+ run: |
+ if ! [[ "$ATTEMPT" =~ ^(0|[1-9]|[1-5][0-9])$ ]]; then
+ echo "::error::repository_dispatch attempt must be an integer from 0 through 59"
+ exit 1
+ fi
+ echo "value=$ATTEMPT" >> "$GITHUB_OUTPUT"
+
+ - name: Get publish code
+ uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803
+ with:
+ # workflow_dispatch can target any ref. Always run trusted code.
+ ref: ${{ github.event.repository.default_branch }}
+ path: .__publish__
+ persist-credentials: false
+
# sentry-internal-app token for label changes on this repo.
# This token is what triggers ...
<path>/home/byk/Code/getsentry/publish/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. ...
🟡 (17:03) [requested-review] User requested resumption and completion of the interrupted exact-worktree security audit for `/home/byk/Code/getsentry/publish`, preserving the prior nine-area scope and requiring current `file:line` evidence plus `PASS`, `CONCERN`, or `MUST-FIX` for every area. 🔴 (17:03) [enforced-read-only] User required the resumed audit to remain strictly read-only and prohibit…
🟡 (16:59) [requested-review] User requested an independent audit of the exact current worktree at `/home/byk/Code/getsentry/publish` against `origin/main`, strictly in READ-ONLY mode; instructed not to edit, format, generate, stage, or mutate files, and to inspect the full diff plus every changed or untracked behavioral file. 🟡 (16:59) User required a non-empty audit report; if any tool blocks …