Skip to content
Merged
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
38 changes: 38 additions & 0 deletions .github/workflows/gate-topology.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ on:
# passing, and there was no way to fire it at the default branch and find
# out. `tri gates unmeasured` reports the gap; this is what closes it.
workflow_dispatch:
inputs: {prs: {description: "claw-batch only: N:headsha pairs of specs/port pull requests to fold into one batch", required: false, default: "", type: string}}
# Only the claw-t27f job below runs on this schedule (#10268).
schedule: [{cron: "*/10 * * * *"}]

Expand Down Expand Up @@ -204,3 +205,40 @@ jobs:
M) printf '%s\n' "$a" >> "$GITHUB_STEP_SUMMARY";;
esac
done < /tmp/plan

# claw-batch (#10398, lane 5 of #10393): fold the port pull requests named in prs ("N:headsha ...") into ONE batch pull
# request. The merge rule is specs/claw/ledger_union.t27 (gen/c/claw/ledger_union.c; the printf'd harness is machinery:
# u union, s pr_scope, m marker_lines). A moved head, a file outside specs/port + its seal + the ledger, or refused ledger
# lines leave a pull request out, named; a conflict in any other file stops the batch. Token: contents + pull_requests
# write. Opened as a draft with no-auto-merge (issue-gate.yml would squash it), then a MERGE-commit auto-merge: never
# squash, so every folded head stays a parent and GitHub marks it merged.
claw-batch:
if: github.event_name == 'workflow_dispatch' && inputs.prs != ''
runs-on: ubuntu-24.04
permissions: {contents: read}
steps:
- {id: tok, uses: actions/create-github-app-token@v2, with: {app-id: "${{ vars.CLAW_APP_ID || secrets.CLAW_APP_ID }}", private-key: "${{ secrets.CLAW_APP_PRIVATE_KEY }}", owner: gHashTag, repositories: t27, permission-contents: write, permission-pull-requests: write}}
- {uses: actions/checkout@v6, with: {ref: master, fetch-depth: 0, token: "${{ steps.tok.outputs.token }}"}}
- env: {GH_TOKEN: "${{ steps.tok.outputs.token }}", PRS: "${{ inputs.prs }}", SLUG: "${{ steps.tok.outputs.app-slug }}", RUN: "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}", B: "claw/batch-${{ github.run_id }}"}
shell: bash {0}
run: |
set -uo pipefail; L=docs/reports/t27b_expectations.json; d=$(mktemp -d); ok=(); out=(); stop=""
printf '#include "gen/c/claw/ledger_union.c"\n#include <stdio.h>\n#include <stdlib.h>\nstatic size_t rd(FILE*f,uint8_t*b,size_t k){size_t n=f?fread(b,1,k,f):k;if(n==k)exit(3);if(f!=stdin)fclose(f);return n;}\nint main(int c,char**v){size_t k=1<<24,m,n;uint8_t*b=malloc(k),*t=malloc(k),*o=malloc(k),*w=malloc(k);uint32_t s[4];\nif(c==5&&*v[1]==117){n=rd(fopen(v[2],"rb"),b,k);size_t tn=rd(fopen(v[3],"rb"),t,k),on=rd(fopen(v[4],"rb"),o,k);m=ledger_union(b,n,t,tn,o,on,w,k,s);if(m>k)return 3;fwrite(w,1,m,s[0]?stderr:stdout);return s[0]?1:0;}\nn=rd(stdin,b,k);if(c==2&&*v[1]==115){m=pr_scope(b,n,w,k,s);if(m>k)return 3;fwrite(w,1,m,stdout);return m?1:0;}\nif(c==2&&*v[1]==109){uint32_t x=marker_lines(b,n);printf("%%u\\n",x);return x?1:0;}\nreturn 2;}\n' | cc -O1 -w -I . -x c -o "$d/lu" - || exit 1
U=$(gh api "users/$SLUG[bot]" -q .id) || exit 1; git config user.name "$SLUG[bot]"; git config user.email "$U+$SLUG[bot]@users.noreply.github.com"; git checkout -q -B "$B" origin/master
for e in $PRS; do n=${e%%:*}; h=${e#*:}
if [ -n "$stop" ]; then out+=("#$n: not tried, the batch stopped at $stop"); continue; fi; [[ "$e" =~ ^[0-9]+:[0-9a-f]{40}$ ]] || { out+=("$e: not N:headsha"); continue; }
read -r st cur base br <<< "$(gh pr view "$n" --json state,headRefOid,baseRefName,headRefName -q '"\(.state) \(.headRefOid) \(.baseRefName) \(.headRefName)"' </dev/null)"
[ "$st $base" = "OPEN master" ] || { out+=("#$n: ${st:-unreadable} into ${base:-?}, not an open pull request into master"); continue; }; [ "$cur" = "$h" ] || { out+=("#$n: head moved to $cur since $h was named"); continue; }
git fetch -q origin "pull/$n/head" </dev/null && [ "$(git rev-parse FETCH_HEAD)" = "$h" ] || { out+=("#$n: the fetched head is not $h"); continue; }
r=$( { git diff --name-only --no-renames "origin/master...$h" | sed 's/^/F\t/'; git diff --name-only --no-renames --diff-filter=d "origin/master...$h" -- '.trinity/seals/*.json' | while read -r f; do printf 'S\t%s\t%s\n' "$f" "$(git show "$h:$f" 2>/dev/null | jq -r '.spec_path // empty' 2>/dev/null)"; done; } | "$d/lu" s) || { out+=("#$n: ${r:-the file list is unreadable}"); continue; }
m="Merge PR #$n ($br) into the port batch"; git merge -q --no-ff -m "$m" "$h" </dev/null > "$d/log" 2>&1 && { ok+=("$n:$h"); continue; }
u=$(git diff --name-only --diff-filter=U); [ "$u" = "$L" ] || { git merge --abort; stop="#$n"; out+=("#$n: conflicts outside the ledger, the batch stops here: $(echo $u)"); continue; }
if git show ":1:$L" > "$d/b" && git show ":3:$L" > "$d/t" && git show ":2:$L" > "$d/o" && "$d/lu" u "$d/b" "$d/t" "$d/o" > "$L" 2> "$d/why" && git add "$L" && git commit -q -m "$m"; then ok+=("$n:$h"); else git merge --abort; out+=("#$n: ledger $(cat "$d/why")"); fi
done
{ for x in "${ok[@]}"; do echo "- folded #${x%%:*} at ${x#*:}"; done; for x in "${out[@]}"; do echo "- left out $x"; done; } > "$d/list"; cat "$d/list" >> "$GITHUB_STEP_SUMMARY"; [ ${#ok[@]} -gt 0 ] || { echo "::error::claw-batch folded no pull request"; exit 1; }
bad=0; while read -r f; do "$d/lu" m < "$f" > /dev/null || { echo "::error file=$f::conflict-marker lines in the batch, or unreadable"; bad=1; }; done < <(git diff --name-only --diff-filter=d origin/master HEAD)
jq empty "$L" && "$d/lu" u "$L" "$L" "$L" > /dev/null || { echo "::error file=$L::the batch ledger is not JSON in the ledger shape"; bad=1; }; [ $bad = 0 ] && git push -q origin "HEAD:refs/heads/$B" || exit 1
{ echo "Port batch of ${#ok[@]} pull requests, folded by the claw-batch job of gate-topology.yml: $RUN"; echo; echo "Merge with a merge commit, never squash: every folded head is a parent here, so GitHub marks it merged when this lands."; echo; cat "$d/list"; echo
for x in "${ok[@]}"; do gh pr view "${x%%:*}" --json closingIssuesReferences -q '.closingIssuesReferences[].number' </dev/null; done | sort -un | sed 's/^/Refs #/'; } > "$d/body"
p=$(gh pr create --draft --label no-auto-merge --base master --head "$B" --title "claw: port batch of ${#ok[@]} pull requests" --body-file "$d/body") || exit 1; echo "batch pull request: $p" >> "$GITHUB_STEP_SUMMARY"
gh pr ready "$p" && gh pr merge "$p" --merge --auto || echo "::warning::$p is open but its merge-commit auto-merge is not set"
3 changes: 2 additions & 1 deletion .github/workflows/spec-guards.yml
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,10 @@ jobs:
./target/release/t27c gen-c specs/claw/sarif_feed.t27 > gen/c/claw/sarif_feed.c
./target/release/t27c gen-c specs/claw/suggest_feed.t27 > gen/c/claw/suggest_feed.c
./target/release/t27c gen-c specs/claw/refresh.t27 > gen/c/claw/refresh.c
./target/release/t27c gen-c specs/claw/ledger_union.t27 > gen/c/claw/ledger_union.c
./target/release/t27c gen-rust specs/tri/fmt_source.t27 > bootstrap/gen/rust/tri/fmt_source.rs
./target/release/t27c gen-json specs/igla/own_model_board.t27 > gen/json/igla/own_model_board.json
git diff --exit-code -- gen/c/claw/gates.c gen/c/claw/t27f_gate.c gen/c/claw/sarif_feed.c gen/c/claw/suggest_feed.c gen/c/claw/refresh.c bootstrap/gen/rust/tri/fmt_source.rs gen/json/igla/own_model_board.json
git diff --exit-code -- gen/c/claw/gates.c gen/c/claw/t27f_gate.c gen/c/claw/sarif_feed.c gen/c/claw/suggest_feed.c gen/c/claw/refresh.c gen/c/claw/ledger_union.c bootstrap/gen/rust/tri/fmt_source.rs gen/json/igla/own_model_board.json

# #7565: test only the specs this change can affect. specs/ci/affected.t27 picks them (the
# changed specs and every spec that imports one through `use`, transitively); the graph is the
Expand Down
33 changes: 33 additions & 0 deletions .trinity/seals/claw_ClawLedgerUnion.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"built_by": "t27c-bootstrap@0.5.2+9448ab4be",
"closure": [
{
"spec": "specs/claw/text.t27",
"spec_hash": "sha256:b65da69dff231c61a6d3a48558af9e17af42e1acbe3f4763348f1f0c2f118f21"
}
],
"config": "gen=c,rust,verilog,zig;tests=zig",
"gen_hash_c": "sha256:f5f2bdf2bfdd9ef25d0ba280851b3504149efd01b36191943b9ec731eddfec99",
"gen_hash_rust": "sha256:0c4d2ff8d06ea85b4c205e68dedc8081b1c3768ce0fdcfad6d948cd72b952254",
"gen_hash_verilog": "sha256:7beda094f46814f7db05772cd52e22de60e4ba6f845b94b01a8d761b3596ffab",
"gen_hash_zig": "sha256:86d1598c1805d89624d659204e8a37c5409e3b1e2745ad9c32947b97a5bcda11",
"module": "ClawLedgerUnion",
"ring": 12,
"seal_schema": 2,
"sealed_at": "2026-10-18T14:15:59Z",
"sealed_by": "t27c-bootstrap@0.5.2",
"spec_hash": "sha256:38a31c67e68321f02ef60d23f2e563f51e528921112a22dc6cfe9c5d1ccead69",
"spec_path": "specs/claw/ledger_union.t27",
"tests": {
"failed": 0,
"failing": [],
"forced": false,
"passed": 25,
"total": 25,
"vacuous": 0
},
"toolchain": {
"test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d",
"zig": "0.16.0"
}
}
Loading
Loading