Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .castiron.stats.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
schema_version: 1
generation_id: 37722dc7-eca2-41b0-bfce-0f6baee2f2aa
generation_id: d52034e7-c4ef-4643-b0a5-177be0c8df24
openapi_spec_hash: 641f7e0f4f9849ca42dc9d9ba4f938f6
openapi_transformed_spec_hash: e9bfbf2ff383ae7da0b32dec03b168e9
config_hash: e51f06a3c5b16edb58e2aebe45a67903
codegen_sha: f09aa003172bb9fe7c58d8d394b439b0f5892b67
codegen_hash: d5980a6ee9429c1ab2cc493e826c10245a3608e19a1540cd38a23e857f0d484a
public_codegen_sha: 813494a2e0ea4ff52130a75c3e959b4db72534de
codegen_sha: 0bc5285b80b377a352358b04f36fa219e2c96a7c
codegen_hash: d97aec850c8938ce86ee2a2703797a71015582003e7e323b81005c0b4bf1d3b2
public_codegen_sha: af5a23f281b751563e117b974a795e26fa8943d5
106 changes: 50 additions & 56 deletions .github/workflows/castiron-custom-code-comment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ permissions: {}
concurrency:
group: castiron-custom-code-comment-${{ github.event.workflow_run.head_repository.id }}-${{ github.event.workflow_run.head_branch }}
cancel-in-progress: false
queue: max # Preserve newer pending evaluations if older runs arrive out of order.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Remove the unsupported concurrency queue key

GitHub's workflow syntax only supports group and cancel-in-progress in the concurrency mapping; queue is not valid there. This makes the trusted workflow_run workflow invalid, preventing all custom-code status and comment jobs from running. Remove this key or implement ordering with supported workflow logic, and update the authoritative generator template rather than only its generated output.

AGENTS.md reference: AGENTS.md:L5-L8

Useful? React with 👍 / 👎.


jobs:
compute:
Expand All @@ -31,13 +32,15 @@ jobs:
isolation: ${{ steps.budget.outputs.isolation }}
budget: ${{ steps.budget.outputs.budget }}
steps:
# Selecting main here pins both the executable checker and PR policy base.
# Subsequent steps use this checkout's SHA even if main advances.
- name: Check out the trusted reporter
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: main
persist-credentials: false

- name: Compute from the current pull request Git objects
- name: Compute from the selected main and pull request Git objects
id: report
if: github.event.workflow_run.event == 'pull_request'
env:
Expand All @@ -53,6 +56,7 @@ jobs:
run: |
python3 -I scripts/castiron/custom_code_report.py trusted-report \
--repo "$RUNNER_TEMP/castiron-objects.git" \
--base "$(git rev-parse HEAD)" \
--repository "$REPOSITORY" --run-id "$RUN_ID" --run-attempt "$RUN_ATTEMPT" \
--out "$RUNNER_TEMP/castiron-custom-code"
if test -f "$RUNNER_TEMP/castiron-custom-code/context.json"; then
Expand Down Expand Up @@ -133,52 +137,61 @@ jobs:
HEAD_SHA: ${{ needs.compute.outputs.head-sha }}
ISOLATION_RESULT: ${{ needs.compute.outputs.isolation }}
BUDGET_RESULT: ${{ needs.compute.outputs.budget }}
PUBLISH_ATTEMPT: ${{ github.run_attempt }}
with:
script: |
const event = context.payload.workflow_run;
const {data: run} = await github.rest.actions.getWorkflowRun({...context.repo, run_id: event.id});
if (run.head_sha !== event.head_sha || run.run_attempt !== event.run_attempt ||
if (run.event !== event.event || run.head_sha !== event.head_sha || run.run_attempt !== event.run_attempt ||
run.status !== 'completed' || run.path.split('@', 1)[0] !== '.github/workflows/castiron-custom-code.yml' ||
run.repository.full_name !== `${context.repo.owner}/${context.repo.repo}`) return;
const head = run.head_sha;
if (!/^[0-9a-f]{40}$/.test(head)) throw new Error('Invalid candidate SHA');
const {data: repository} = await github.rest.repos.get(context.repo);
const branch = repository.default_branch;
const {data: main} = await github.rest.git.getRef({...context.repo, ref: `heads/${branch}`});
const base = main.object.sha;
const base = process.env.BASE_SHA;
let fresh = /^[0-9a-f]{40}$/.test(base) && head === process.env.HEAD_SHA;
if (run.event === 'pull_request') {
const headRepository = run.head_repository;
if (!headRepository || !Number.isInteger(headRepository.id) || headRepository.id <= 0 ||
!headRepository.full_name || !headRepository.owner?.login || !run.head_branch) return;
const pulls = run.pull_requests.length ? run.pull_requests : await github.paginate(
github.rest.pulls.list, {...context.repo, state: 'open',
head: `${headRepository.owner.login}:${run.head_branch}`, base: branch, per_page: 100});
let pulls = run.pull_requests.length ? run.pull_requests : await github.paginate(
github.rest.repos.listPullRequestsAssociatedWithCommit, {...context.repo, commit_sha: head});
if (!pulls.length) pulls = await github.paginate(github.rest.pulls.list, {
...context.repo, state: 'open', head: `${run.head_repository.owner.login}:${run.head_branch}`,
});
const current = [];
for (const number of [...new Set(pulls.map(pull => pull.number))].sort((a, b) => a - b)) {
if (!Number.isInteger(number) || number <= 0) return;
const {data: pr} = await github.rest.pulls.get({...context.repo, pull_number: number});
if (pr.state === 'open' && pr.head.sha === head && pr.base.sha === base &&
pr.head.ref === run.head_branch && pr.head.repo?.id === headRepository.id &&
pr.head.repo?.full_name === headRepository.full_name &&
pr.base.ref === branch &&
pr.base.repo.full_name === `${context.repo.owner}/${context.repo.repo}`) current.push(pr);
for (const pull of pulls) {
const {data: pr} = await github.rest.pulls.get({...context.repo, pull_number: pull.number});
if (pr.state === 'open' && pr.head.sha === head &&
pr.base.ref === 'main' && pr.base.repo.full_name === `${context.repo.owner}/${context.repo.repo}`) current.push(pr);
}
if (current.length !== 1) return;
} else if (run.event !== 'merge_group' || !run.head_branch.startsWith(`gh-readonly-queue/${branch}/`)) {
} else if (run.event !== 'merge_group' || !run.head_branch.startsWith('gh-readonly-queue/main/')) {
return;
} else {
const {data: main} = await github.rest.git.getRef({...context.repo, ref: 'heads/main'});
fresh = fresh && base === main.object.sha;
}
const fresh = base === process.env.BASE_SHA && head === process.env.HEAD_SHA;
const url = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
// Concurrency serializes writes, but does not order runs or retries.
// Keep an older evaluation from overwriting a newer result for this head.
const order = [run.id, run.run_attempt, Number(context.runId), Number(process.env.PUBLISH_ATTEMPT)];
const marker = `[evaluation ${order.join(':')}]`;
const statuses = await github.paginate(github.rest.repos.listCommitStatusesForRef, {...context.repo, ref: head});
const names = ['Castiron / budget-only change', 'Castiron / custom-code budget'];
for (const status of statuses) {
if (!names.includes(status.context) || status.creator?.login !== 'github-actions[bot]') continue;
const prior = status.description?.match(/\[evaluation (\d+):(\d+):(\d+):(\d+)\]$/);
if (!prior || status.target_url !== `${url.slice(0, url.lastIndexOf('/') + 1)}${prior[3]}`) continue;
const previous = prior.slice(1).map(Number);
const different = previous.findIndex((value, index) => value !== order[index]);
if (different !== -1 && previous[different] > order[different]) return;
}
for (const [name, result] of [
['Castiron / budget-only change', process.env.ISOLATION_RESULT],
['Castiron / custom-code budget', process.env.BUDGET_RESULT],
]) {
const state = fresh && result === 'success' ? 'success' : 'failure';
const description = !fresh ? 'Evaluation unavailable or base changed; rerun against current main.'
: state === 'success' ? 'Passed against main policy. See the trusted run summary.'
: 'Budget check failed. See the trusted run summary.';
const description = !fresh ? 'Evaluation unavailable or queue base changed; inspect the trusted run and rerun.'
: `${state === 'success' ? 'Passed' : 'Failed'} against main ${base.slice(0, 12)}. See the trusted run summary.`;
await github.rest.repos.createCommitStatus({...context.repo, sha: head, context: name,
state, description, target_url: url});
state, description: `${description} ${marker}`, target_url: url});
}

comment:
Expand All @@ -193,7 +206,7 @@ jobs:
pull-requests: write
steps:
- name: Check out the trusted publisher
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.workflow_sha }}
persist-credentials: false
Expand Down Expand Up @@ -230,34 +243,15 @@ jobs:
with:
script: |
const marker = '<!-- castiron:custom-code-report:v1 -->';
const event = context.payload.workflow_run;
const {data: run} = await github.rest.actions.getWorkflowRun({...context.repo, run_id: event.id});
if (run.event !== 'pull_request' ||
run.head_sha !== event.head_sha || run.run_attempt !== event.run_attempt ||
run.status !== 'completed' ||
run.path.split('@', 1)[0] !== '.github/workflows/castiron-custom-code.yml' ||
run.repository.full_name !== `${context.repo.owner}/${context.repo.repo}`) return;
const {data: repository} = await github.rest.repos.get(context.repo);
const branch = repository.default_branch;
const {data: main} = await github.rest.git.getRef({...context.repo, ref: `heads/${branch}`});
const headRepository = run.head_repository;
if (!headRepository || !Number.isInteger(headRepository.id) || headRepository.id <= 0 ||
!headRepository.full_name || !headRepository.owner?.login || !run.head_branch) return;
const pulls = run.pull_requests?.length ? run.pull_requests : await github.paginate(
github.rest.pulls.list, {...context.repo, state: 'open',
head: `${headRepository.owner.login}:${run.head_branch}`, base: branch, per_page: 100});
const current = [];
for (const number of [...new Set(pulls.map(pull => pull.number))].sort((a, b) => a - b)) {
if (!Number.isInteger(number) || number <= 0) return;
const {data: pr} = await github.rest.pulls.get({...context.repo, pull_number: number});
if (pr.state === 'open' && pr.head.sha === run.head_sha &&
pr.head.ref === run.head_branch && pr.head.repo?.id === headRepository.id &&
pr.head.repo?.full_name === headRepository.full_name &&
pr.base.sha === main.object.sha && pr.base.ref === branch &&
pr.base.repo.full_name === `${context.repo.owner}/${context.repo.repo}`) current.push(pr);
}
if (current.length !== 1) return;
for (const pull of current) {
const run = context.payload.workflow_run;
if (run.event !== 'pull_request' || run.path !== '.github/workflows/castiron-custom-code.yml') return;
let pulls = run.pull_requests?.length ? run.pull_requests : await github.paginate(github.rest.repos.listPullRequestsAssociatedWithCommit, {...context.repo, commit_sha: run.head_sha});
if (!pulls.length) pulls = await github.paginate(github.rest.pulls.list, {
...context.repo, state: 'open', head: `${run.head_repository.owner.login}:${run.head_branch}`,
});
for (const pull of pulls) {
const {data: current} = await github.rest.pulls.get({...context.repo, pull_number: pull.number});
if (current.state !== 'open' || current.head.sha !== run.head_sha) continue;
Comment on lines +252 to +254

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restrict failure comments to the source PR

When workflow_run.pull_requests is empty—common for fork runs—and a commit is associated with multiple PRs, this fallback loops over every associated PR and verifies only that it is open at the same SHA. A failed computation can therefore create or overwrite the Castiron comment on another PR sharing that commit, including one targeting a non-main branch because the producer has an unrestricted pull_request trigger. Restore the main-target/repository checks and require exactly one matching PR before writing, with a regression case covering multiple associations.

AGENTS.md reference: AGENTS.md:L41-L45

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P2] Keep failure reports behind the same live-run/PR validation

This replaces the previous live-run and unique-target checks with the event payload and an open/same-SHA test. A delayed attempt-1 publisher can reach trusted_report while attempt 2 is in progress: the live-status check raises, so compute fails and this fallback runs. With no newer comment marker yet, it can overwrite the report with an obsolete failure. The loop can also write to multiple same-SHA PRs, including a different base/source branch that the previous resolver rejected.

Please fix this in the shared template and regenerate: resolve the current source run/attempt and exactly one matching source-repository/branch PR targeting main once, and reuse that decision for success and failure publication. Recheck freshness before writing. Cover a newer retry still in progress and an unrelated same-SHA PR; the current failure fixtures omit these identity fields.

const comments = await github.paginate(github.rest.issues.listComments, {...context.repo, issue_number: pull.number});
const previous = comments.find(c => c.user?.type === 'Bot' && c.user?.login === 'github-actions[bot]' && c.body?.startsWith(marker));
const prior = previous?.body?.match(/<!-- castiron:run:v1:(\d+):(\d+) -->/);
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/castiron-custom-code.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ concurrency:
cancel-in-progress: false

env:
REPORTER_SHA256: 66b128590c674d4dec7c59b99dc0de54d7d1672eee4ab6d193bfc6b369a664d0
REPORTER_SHA256: d92cc331cea526bb3230ea4b0c2430ad974338a8373134e0b07b3a1ad3535e2a

jobs:
queue-signal:
Expand Down Expand Up @@ -47,7 +47,7 @@ jobs:
> "$RUNNER_TEMP/castiron-custom-code/context.json"

- name: Check out the pull request
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -306,8 +306,8 @@ private constructor(
* on an older version than the API, then the API may respond with new variants that the SDK
* is unaware of.
*
* Recognized events also reach this method when their visit method is not overridden. This
* allows existing visitors to handle event variants added by newer SDK versions.
* Recognized variants also reach this method when their visit method is not overridden.
* This allows existing visitors to handle variants added by newer SDK versions.
*
* @throws OpenAIInvalidDataException in the default implementation.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1165,8 +1165,8 @@ private constructor(
* version than the API, then the API may respond with new variants that the SDK is unaware
* of.
*
* Recognized events also reach this method when their visit method is not overridden. This
* allows existing visitors to handle event variants added by newer SDK versions.
* Recognized variants also reach this method when their visit method is not overridden.
* This allows existing visitors to handle variants added by newer SDK versions.
*
* @throws OpenAIInvalidDataException in the default implementation.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1221,8 +1221,8 @@ private constructor(
* on an older version than the API, then the API may respond with new variants that the SDK
* is unaware of.
*
* Recognized events also reach this method when their visit method is not overridden. This
* allows existing visitors to handle event variants added by newer SDK versions.
* Recognized variants also reach this method when their visit method is not overridden.
* This allows existing visitors to handle variants added by newer SDK versions.
*
* @throws OpenAIInvalidDataException in the default implementation.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2526,8 +2526,8 @@ private constructor(
* on an older version than the API, then the API may respond with new variants that the SDK
* is unaware of.
*
* Recognized events also reach this method when their visit method is not overridden. This
* allows existing visitors to handle event variants added by newer SDK versions.
* Recognized variants also reach this method when their visit method is not overridden.
* This allows existing visitors to handle variants added by newer SDK versions.
*
* @throws OpenAIInvalidDataException in the default implementation.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3125,8 +3125,8 @@ private constructor(
* on an older version than the API, then the API may respond with new variants that the SDK
* is unaware of.
*
* Recognized events also reach this method when their visit method is not overridden. This
* allows existing visitors to handle event variants added by newer SDK versions.
* Recognized variants also reach this method when their visit method is not overridden.
* This allows existing visitors to handle variants added by newer SDK versions.
*
* @throws OpenAIInvalidDataException in the default implementation.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,8 @@ private constructor(
* on an older version than the API, then the API may respond with new variants that the SDK
* is unaware of.
*
* Recognized events also reach this method when their visit method is not overridden. This
* allows existing visitors to handle event variants added by newer SDK versions.
* Recognized variants also reach this method when their visit method is not overridden.
* This allows existing visitors to handle variants added by newer SDK versions.
*
* @throws OpenAIInvalidDataException in the default implementation.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,8 +203,8 @@ private constructor(
* on an older version than the API, then the API may respond with new variants that the SDK
* is unaware of.
*
* Recognized events also reach this method when their visit method is not overridden. This
* allows existing visitors to handle event variants added by newer SDK versions.
* Recognized variants also reach this method when their visit method is not overridden.
* This allows existing visitors to handle variants added by newer SDK versions.
*
* @throws OpenAIInvalidDataException in the default implementation.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1153,8 +1153,8 @@ private constructor(
* version than the API, then the API may respond with new variants that the SDK is unaware
* of.
*
* Recognized events also reach this method when their visit method is not overridden. This
* allows existing visitors to handle event variants added by newer SDK versions.
* Recognized variants also reach this method when their visit method is not overridden.
* This allows existing visitors to handle variants added by newer SDK versions.
*
* @throws OpenAIInvalidDataException in the default implementation.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2536,8 +2536,8 @@ private constructor(
* on an older version than the API, then the API may respond with new variants that the SDK
* is unaware of.
*
* Recognized events also reach this method when their visit method is not overridden. This
* allows existing visitors to handle event variants added by newer SDK versions.
* Recognized variants also reach this method when their visit method is not overridden.
* This allows existing visitors to handle variants added by newer SDK versions.
*
* @throws OpenAIInvalidDataException in the default implementation.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2240,8 +2240,8 @@ private constructor(
* on an older version than the API, then the API may respond with new variants that the SDK
* is unaware of.
*
* Recognized events also reach this method when their visit method is not overridden. This
* allows existing visitors to handle event variants added by newer SDK versions.
* Recognized variants also reach this method when their visit method is not overridden.
* This allows existing visitors to handle variants added by newer SDK versions.
*
* @throws OpenAIInvalidDataException in the default implementation.
*/
Expand Down
Loading
Loading