Skip to content
Closed
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
6 changes: 4 additions & 2 deletions .castiron.stats.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
schema_version: 1
generation_id: 04cb977a-ba1d-438c-a56e-94f269e79218
generation_id: 5df0d6f6-d156-43b6-ba92-de149763e673
openapi_spec_hash: dd725fb7d43ceec7fb2de6f8713d14b6
openapi_transformed_spec_hash: 10930179c5f116288e24e0c6fda46559
config_hash: 85382dd94c503b5d225adc7636a77c9f
codegen_sha: 6e990f52e3cbdeaae602710a1b0f2a2c944a5c35
codegen_sha: 175308fd49c80670236b9cca66953da62b910eb5
codegen_hash: 229f3e1c25b55fb04b07864af9ccdbc81edbdf6c341240a40fe336a3869ac9cb
public_codegen_sha: 193d1d672dc7aec341535802c14fa453372bf3db
138 changes: 138 additions & 0 deletions .github/workflows/castiron-custom-code.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
# File generated from our OpenAPI spec by Castiron. See CONTRIBUTING.md for details.
name: Castiron custom code

on:
pull_request:
types: [opened, synchronize, reopened, ready_for_review]

permissions:
contents: read

# These experimental refs are restricted to trusted SDK maintainers.
# The workflow definition is trusted because its branch writers are trusted.
concurrency:
group: castiron-custom-code-${{ github.event.pull_request.number }}
cancel-in-progress: false

env:
REPORTER_SHA256: 2855964a3b73aa57fd5a6b668c98b4aeb5dc54fc73a227091f832ca13a1f1f04

jobs:
report:
name: Castiron / baseline consistency
if: github.event.pull_request.head.repo.full_name == github.repository && contains(fromJSON(vars.CASTIRON_CUSTOM_CODE_BRANCHES || '[]'), github.event.pull_request.head.ref)
runs-on: ubuntu-latest
timeout-minutes: 10
outputs:
trusted: ${{ steps.trust.outputs.trusted }}
steps:
- name: Check the exact protected branch name
id: trust
env:
ALLOWED_BRANCHES: ${{ vars.CASTIRON_CUSTOM_CODE_BRANCHES || '[]' }}
PR_BRANCH: ${{ github.event.pull_request.head.ref }}
run: |
jq -e --arg branch "$PR_BRANCH" 'type == "array" and index($branch) != null' <<< "$ALLOWED_BRANCHES" > /dev/null
printf 'trusted=true\n' >> "$GITHUB_OUTPUT"

- name: Check out the protected test branch
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 0

- name: Verify the reviewed reporter
run: printf '%s %s\n' "$REPORTER_SHA256" scripts/castiron/custom_code_report.py | sha256sum --check --strict

- name: Test hash mismatch and snapshot isolation
run: python3 scripts/castiron/test_custom_code_report.py

- name: Validate the codegen hash and report custom code
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
PUBLIC_REPOSITORY: ${{ !github.event.repository.private }}
run: |
git fetch --quiet --no-tags origin "$BASE_SHA" "$HEAD_SHA"
mode=()
if [[ "$PUBLIC_REPOSITORY" == true ]]; then mode=(--public); fi
python3 -I scripts/castiron/custom_code_report.py report \
--base "$BASE_SHA" --head "$HEAD_SHA" \
--fetch --require-head-hash "${mode[@]}" \
--out "$RUNNER_TEMP/castiron-custom-code"

- name: Add the report to the run summary
if: always()
run: |
if test -f "$RUNNER_TEMP/castiron-custom-code/summary.md"; then
cat "$RUNNER_TEMP/castiron-custom-code/summary.md" >> "$GITHUB_STEP_SUMMARY"
fi

- name: Upload report and current custom-code patch
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: castiron-custom-code-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ runner.temp }}/castiron-custom-code/
if-no-files-found: error
retention-days: 7

comment:
name: Update custom-code comment
needs: report
if: always() && !cancelled() && needs.report.outputs.trusted == 'true'
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
actions: read
pull-requests: write
steps:
- name: Check out the protected test branch
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
ref: ${{ github.event.pull_request.head.sha }}
persist-credentials: false

- name: Verify the reviewed publisher before execution
run: printf '%s %s\n' "$REPORTER_SHA256" scripts/castiron/custom_code_report.py | sha256sum --check --strict

- name: Download this run's report
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: castiron-custom-code-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ runner.temp }}/castiron-custom-code

- name: Create or update the single report comment
id: publish
env:
GH_TOKEN: ${{ github.token }}
REPOSITORY: ${{ github.repository }}
PR_NUMBER: ${{ github.event.pull_request.number }}
RUN_ID: ${{ github.run_id }}
RUN_ATTEMPT: ${{ github.run_attempt }}
run: |
python3 -I scripts/castiron/custom_code_report.py comment \
--report "$RUNNER_TEMP/castiron-custom-code/report.json" \
--repository "$REPOSITORY" --pr "$PR_NUMBER" --run-id "$RUN_ID" \
--run-attempt "$RUN_ATTEMPT"

- name: Publish a trusted failure status
if: always() && !cancelled() && steps.publish.outcome != 'success'
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7
with:
script: |
const marker = '<!-- castiron:custom-code-report:v1 -->';
const event = context.payload.pull_request;
const {data: current} = await github.rest.pulls.get({...context.repo, pull_number: event.number});
if (current.state !== 'open' || current.head.sha !== event.head.sha || current.base.sha !== event.base.sha) return;
const comments = await github.paginate(github.rest.issues.listComments, {...context.repo, issue_number: event.number});
const previous = comments.find(c => c.user?.type === 'Bot' && c.user?.login === 'github-actions[bot]' && c.body?.includes(marker));
const run = Number(context.runId);
const attempt = Number(process.env.GITHUB_RUN_ATTEMPT);
const prior = previous?.body?.match(/<!-- castiron:run:v1:(\d+):(\d+) -->/);
if (prior && (Number(prior[1]) > run || (Number(prior[1]) === run && Number(prior[2]) > attempt))) return;
const url = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${run}`;
const body = `${marker}\n\n## Castiron custom code\n\n⚠️ Report unavailable for \`${event.head.sha.slice(0, 12)}\`.\n\nThe report setup or validation failed. [Inspect the workflow run](${url}).\n\n<!-- castiron:run:v1:${run}:${attempt} -->`;
if (previous) await github.rest.issues.updateComment({...context.repo, comment_id: previous.id, body});
else await github.rest.issues.createComment({...context.repo, issue_number: event.number, body});
23 changes: 23 additions & 0 deletions scripts/castiron/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<!-- File generated from our OpenAPI spec by Castiron. See CONTRIBUTING.md for details. -->
# Castiron custom-code reporting

Castiron maintains shared templates for these files. Prefer changing those templates
for cross-SDK improvements; repository-specific customizations use the normal
three-way merge and are allowed.
The reporter uses Python 3.10+, Git, and `gh`; it does not import SDK code.

Run `python3 scripts/castiron/test_custom_code_report.py` for focused tests.
The report comment includes commands to inspect the exact custom-code patch.
Public reporting uses only public snapshots and needs no private repository access.

The workflow validates the recorded `codegen_hash`.
Its hash format is documented in the reporter. Only `.github/actions/` and
`.github/workflows/` are excluded from the content hash.

During the draft rollout, `CASTIRON_CUSTOM_CODE_BRANCHES` is a repository-local
JSON array of maintainer-protected branch names. Only those branches can publish
comments. A general contributor/fork rollout requires a trusted default-branch
publisher. Never execute PR-controlled code with write credentials.
Changing the workflow may require one-time AM permission. Its reporter checksum
is a credential-safety check, not a requirement that every generated file remain
identical to its template.
Loading
Loading