From bd2cf8da36c0295a9ad4d1f39158055f9f9ea9bb Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 19 Oct 2026 17:15:16 +0700 Subject: [PATCH] claw: merge_judge.t27 -- the 24/7 merger decided in a spec; the claw-merge job replaces claw-refresh (Closes #10973) Owner order 2026-10-19: do autonomously what the PR-merge sweep did by hand all night. specs/claw/merge_judge.t27 (ClawMergeJudge) decides, from facts only, which open pull requests merge and why it leaves the rest alone; it moves nothing. It composes check_latest, check_state, required_red, red_origin, refresh and text. Rules, first match wins: no-auto-merge label (H), draft (H), base not master (H), queen- bee branch (N: auto-merge-ready-prs.yml's), conflict or unknown mergeability (W), no closing reference (W), rollup unread or cut short (W), check pending (W), required check red or missing (W), any other red must be inherited per red_origin (claw-sarif is, by definition; own or unknown is H with the reason; a red master also fails asks for its failing log with an L line), head commit younger than 10 minutes (W); then G: squash, pinned to the judged head sha, five per run at most, lowest number first. 67 tests on both backends, 0 vacuous, the negative controls of 2026-10-18/19 among them; a mutation sweep of the decision functions (104 mutants) found one survivor, now killed by a test. The claw-merge job of gate-topology.yml (push to master, the 10-minute schedule, dispatch) lists once, feeds gen/c/claw/merge_judge.c and applies the G lines; it is plan-only into the step summary unless the repository variable CLAW_MERGE_LIVE is 1. It folds claw-refresh in (update-branch after a push, same decisions, a pull request merged in the run is skipped). Foreign-line budget: gate-topology.yml +27/-27, spec-guards.yml +2/-2, net 0, exit 0. Census moved: tools/census/quiet.txt, "steps in a quiet shape" 33 -> 35 (failure branch passes 14 -> 16, a variable in the path 7 -> 8, no path anywhere 7 -> 8). The two new shapes are claw-merge's reads of master's last rollups and of a job log; both fail soft on purpose, because the spec treats a missing B or O line as an unread fact and never merges on it. Co-Authored-By: Claude Sonnet 5.5 --- .github/workflows/gate-topology.yml | 54 +- .github/workflows/spec-guards.yml | 4 +- .trinity/seals/claw_ClawMergeJudge.json | 61 + docs/claw/README.md | 21 + gen/c/claw/merge_judge.c | 3261 +++++++++++++++++++++++ specs/claw/merge_judge.t27 | 2091 +++++++++++++++ tools/census/quiet.txt | 10 +- 7 files changed, 5468 insertions(+), 34 deletions(-) create mode 100644 .trinity/seals/claw_ClawMergeJudge.json create mode 100644 gen/c/claw/merge_judge.c create mode 100644 specs/claw/merge_judge.t27 diff --git a/.github/workflows/gate-topology.yml b/.github/workflows/gate-topology.yml index 6bf880ffb3..1e1f123f63 100644 --- a/.github/workflows/gate-topology.yml +++ b/.github/workflows/gate-topology.yml @@ -20,7 +20,7 @@ on: # 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). + # Only claw-t27f (#10268) and claw-merge (#10973) run on this schedule. schedule: [{cron: "*/10 * * * *"}] # A run for a commit that has since been superseded measures nothing anyone @@ -171,45 +171,45 @@ jobs: cat /tmp/stats >> "$GITHUB_STEP_SUMMARY" if [ -s /tmp/review.json ]; then gh api -X POST "repos/$R/pulls/$N/reviews" --input /tmp/review.json -q .html_url >> "$GITHUB_STEP_SUMMARY"; fi - # #10397: after a push to master, bring every conflict-free open PR that fell behind up to date. - # The decision (which PRs, which order, the cap of 5, the queue limit of 150, every skip) is specs/claw/refresh.t27 lowered - # to gen/c/claw/refresh.c; this job lists, feeds the C, and calls the endpoint for each `R` line. - # The token is the update-branch row of specs/claw/github_surface.t27: pull_requests write and - # nothing else. If GitHub answers 403 here, the App also needs contents:write for update-branch - # (REST docs); the failure is a warning with GitHub's own text, never a red job. - claw-refresh: - if: github.event_name == 'push' + # The lane's tick (#10973): merge_judge.t27 (merge) and refresh.t27 (update-branch) decide, this job lists, feeds gen/c/claw/merge_judge.c and applies; plan-only unless CLAW_MERGE_LIVE is 1. + claw-merge: + if: github.event_name != 'pull_request' runs-on: ubuntu-24.04 - permissions: {contents: read, pull-requests: read, actions: read} - concurrency: {group: claw-refresh, cancel-in-progress: true} + permissions: {contents: write, pull-requests: write, actions: read} + concurrency: {group: claw-merge, cancel-in-progress: false} steps: - id: have env: {K: "${{ secrets.CLAW_APP_PRIVATE_KEY }}", I: "${{ vars.CLAW_APP_ID || secrets.CLAW_APP_ID }}"} run: if [ -n "$K" ] && [ -n "$I" ]; then echo ok=1 >> "$GITHUB_OUTPUT"; else echo "claw/refresh updated nothing - secret CLAW_APP_PRIVATE_KEY or CLAW_APP_ID (variable or secret) is not set (docs/claw/README.md)." >> "$GITHUB_STEP_SUMMARY"; fi - - {id: tok, if: steps.have.outputs.ok, continue-on-error: true, 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-pull-requests: write}} - - {if: steps.tok.outcome == 'failure', run: echo "claw/refresh updated nothing - the TRI CLAW App could not mint a token with pull_requests write; grant it and accept it on the installation - docs/claw/README.md steps 1-2." >> "$GITHUB_STEP_SUMMARY"} - - {if: steps.tok.outputs.token, uses: actions/checkout@v6, with: {persist-credentials: false}} - - if: steps.tok.outputs.token - env: {GH_TOKEN: "${{ github.token }}", CLAW_TOKEN: "${{ steps.tok.outputs.token }}", R: "${{ github.repository }}"} + - {id: tok, if: steps.have.outputs.ok, continue-on-error: true, 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-pull-requests: write, permission-contents: write}} + - {if: steps.tok.outcome == 'failure', run: echo "claw/refresh updated nothing - the TRI CLAW App could not mint a token with pull_requests and contents write; grant it and accept it on the installation - docs/claw/README.md steps 1-2." >> "$GITHUB_STEP_SUMMARY"} + - {uses: actions/checkout@v6, with: {persist-credentials: false}} + - env: {GH_TOKEN: "${{ github.token }}", CLAW_TOKEN: "${{ steps.tok.outputs.token }}", R: "${{ github.repository }}", E: "${{ github.event_name }}", LIVE: "${{ vars.CLAW_MERGE_LIVE }}"} run: | - printf '#include "gen/c/claw/refresh.c"\n#include \n#include \nint main(int c,char**v){size_t k=1<<22,n;uint8_t*b=malloc(k),*o=malloc(k);n=fread(b,1,k,stdin);if(n==k)return 3;size_t m=refresh_plan(b,n,o,k,atoi(v[1]));if(m>k)return 4;fwrite(o,1,m,stdout);return 0;}\n' | cc -O1 -w -I . -x c -o /tmp/feed - - skip() { echo "::warning::claw/refresh $1"; echo "claw/refresh updated nothing: $1" >> "$GITHUB_STEP_SUMMARY"; exit 0; } - # Pages stay small (a page of 100 PRs with 50 labels each came back as HTML). A failed page fails the jq step: try three times, keep nothing of a failed try. - list() { for t in 1 2 3; do gh api graphql --paginate -F owner="${R%/*}" -F name="${R#*/}" -f query='query($owner:String!,$name:String!,$endCursor:String){repository(owner:$owner,name:$name){pullRequests(states:OPEN,first:30,after:$endCursor,orderBy:{field:CREATED_AT,direction:ASC}){pageInfo{hasNextPage endCursor} nodes{number headRefOid createdAt isDraft isCrossRepository maintainerCanModify mergeable mergeStateStatus baseRefName headRepository{nameWithOwner} autoMergeRequest{enabledAt} labels(first:10){nodes{name}}}}}}' --jq '.data.repository.pullRequests.nodes[]|[.number,.headRefOid,.createdAt,.isDraft,([.labels.nodes[].name]|join(",")),.isCrossRepository,(.headRepository.nameWithOwner//""),.maintainerCanModify,.mergeable,.mergeStateStatus,.baseRefName,(.autoMergeRequest!=null)]|@tsv' > /tmp/page 2>/dev/null \n#include \nint main(int c,char**v){size_t k=1<<24,n;uint8_t*b=malloc(k),*o=malloc(k);n=fread(b,1,k,stdin);if(n==k)return 3;size_t m=lane_plan(atoi(v[1]),b,n,o,k,atoi(v[2]));if(m>k)return 4;fwrite(o,1,m,stdout);return 0;}\n' | cc -O1 -w -I . -x c -o /tmp/feed - + skip() { echo "::warning::claw-merge $1"; echo "claw-merge stopped: $1" >> "$GITHUB_STEP_SUMMARY"; exit 0; } + row='def row: if .__typename=="CheckRun" then [((.checkSuite.workflowRun.workflow.name // "")|if .=="" then "" else .+"/" end)+.name,.databaseId,.status,(.conclusion // "-")] else [.context,"-","CONTEXT",.state] end;' + list() { for t in 1 2 3; do gh api graphql --paginate -F owner="${R%/*}" -F name="${R#*/}" -f query='query($owner:String!,$name:String!,$endCursor:String){repository(owner:$owner,name:$name){pullRequests(states:OPEN,first:15,after:$endCursor,orderBy:{field:CREATED_AT,direction:ASC}){pageInfo{hasNextPage endCursor} nodes{number headRefOid headRefName createdAt isDraft isCrossRepository maintainerCanModify mergeable mergeStateStatus baseRefName changedFiles headRepository{nameWithOwner} autoMergeRequest{enabledAt} labels(first:10){nodes{name}} closingIssuesReferences(first:1){totalCount} commits(last:1){nodes{commit{oid committedDate statusCheckRollup{contexts(first:100){totalCount nodes{__typename ... on CheckRun{name status conclusion databaseId checkSuite{workflowRun{workflow{name}}}} ... on StatusContext{context state}}}}}}}}}}}' --jq "$row"'.data.repository.pullRequests.nodes[]|. as $p|$p.commits.nodes[0].commit as $c|($c.statusCheckRollup.contexts // {totalCount:0,nodes:[]}) as $x|([$p.labels.nodes[].name]|join(",")) as $l|(["R",$p.number,$p.headRefOid,$p.createdAt,$p.isDraft,$l,$p.isCrossRepository,($p.headRepository.nameWithOwner//""),$p.maintainerCanModify,$p.mergeable,$p.mergeStateStatus,$p.baseRefName,($p.autoMergeRequest!=null)]|@tsv),(["P",$p.number,$p.headRefOid,$p.isDraft,$l,$p.headRefName,$p.baseRefName,$p.mergeable,$p.mergeStateStatus,$p.closingIssuesReferences.totalCount,$c.oid,((now-($c.committedDate|fromdate))|floor|if .<0 then "-" else . end),$x.totalCount,$p.changedFiles]|@tsv),($x.nodes[]|row|["K"]+.|@tsv)' > /tmp/page 2>/dev/null /dev/null || :; sleep 30 - list > /tmp/prs.tsv || skip "the pull request listing failed three times, so no plan was made." - # Every update-branch starts about 26 workflows: refresh.t27 decides from the queued-run count, and an unread count is -1. + list > /tmp/in || skip "the pull request listing failed three times, so no plan was made." + gh api graphql -F owner="${R%/*}" -F name="${R#*/}" -f query='query($owner:String!,$name:String!){repository(owner:$owner,name:$name){ref(qualifiedName:"refs/heads/master"){target{... on Commit{history(first:20){nodes{statusCheckRollup{contexts(first:100){nodes{__typename ... on CheckRun{name status conclusion databaseId checkSuite{workflowRun{workflow{name}}}} ... on StatusContext{context state}}}}}}}}}}}' --jq "$row"'.data.repository.ref.target.history.nodes[]|(.statusCheckRollup.contexts.nodes // [])[]|row|["B"]+.|@tsv' > /tmp/b 2>/dev/null /tmp/mplan || skip "could not build a merge plan."; }; plan + while IFS=$'\t' read -r k a b c; do [ "$k" = L ] || continue + [ "$a" = "$p" ] || { p=$a; rm -f /tmp/f; gh api --paginate "repos/$R/pulls/$a/files" -q '.[].filename' > /tmp/f /tmp/log 2>/dev/null /tmp/x; plan /tmp/x + while IFS=$'\t' read -r k a b; do case "$k" in G) if [ "$LIVE" != 1 ]; then echo "- would merge #$a at $b (plan only: CLAW_MERGE_LIVE is not 1)" >> "$GITHUB_STEP_SUMMARY"; elif err=$(GH_TOKEN="${CLAW_TOKEN:-$GH_TOKEN}" gh api -X PUT "repos/$R/pulls/$a/merge" -f merge_method=squash -f sha="$b" 2>&1 > "$GITHUB_STEP_SUMMARY"; echo "$a" >> /tmp/merged; else echo "::warning::claw-merge could not merge #$a: $err"; fi;; + M) printf '%s\n' "$a" >> "$GITHUB_STEP_SUMMARY";; W|H|N) printf -- '- %s #%s: %s\n' "$k" "$a" "$b" >> "$GITHUB_STEP_SUMMARY";; esac; done < /tmp/mplan + [ "$E" = push ] && [ -n "$CLAW_TOKEN" ] || exit 0 q=$(gh api "repos/$R/actions/runs?status=queued&per_page=1" -q .total_count 2>/dev/null /dev/null /tmp/plan || skip "could not build a plan." + done | /tmp/feed 1 "$q" > /tmp/plan || skip "could not build a plan." while IFS=$'\t' read -r k a b; do case "$k" in - R) if err=$(GH_TOKEN="$CLAW_TOKEN" gh api -X PUT "repos/$R/pulls/$a/update-branch" -f expected_head_sha="$b" 2>&1 &1 > "$GITHUB_STEP_SUMMARY";; esac done < /tmp/plan diff --git a/.github/workflows/spec-guards.yml b/.github/workflows/spec-guards.yml index 5cddd3e4e6..a6b9c0df38 100644 --- a/.github/workflows/spec-guards.yml +++ b/.github/workflows/spec-guards.yml @@ -129,11 +129,11 @@ 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-c specs/claw/ledger_union.t27 > gen/c/claw/ledger_union.c; ./target/release/t27c gen-c specs/claw/merge_judge.t27 > gen/c/claw/merge_judge.c ./target/release/t27c gen-c specs/policy/secret_gate.t27 > gen/c/policy/secret_gate.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 gen/c/claw/ledger_union.c gen/c/policy/secret_gate.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 gen/c/claw/merge_judge.c gen/c/policy/secret_gate.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 diff --git a/.trinity/seals/claw_ClawMergeJudge.json b/.trinity/seals/claw_ClawMergeJudge.json new file mode 100644 index 0000000000..2c5ba8d1eb --- /dev/null +++ b/.trinity/seals/claw_ClawMergeJudge.json @@ -0,0 +1,61 @@ +{ + "built_by": "t27c-bootstrap@0.5.2+b4bf811ac", + "closure": [ + { + "spec": "specs/claw/check_latest.t27", + "spec_hash": "sha256:2de14a373379d1a5acc19fffeb4123c951dc6f36caffb883fb5a8a406be65d58" + }, + { + "spec": "specs/claw/check_state.t27", + "spec_hash": "sha256:bd3aa36047755e7b95144ca4bbdb760aa971ceeb94b7e0728dd4db49b842ead6" + }, + { + "spec": "specs/claw/queue_cost.t27", + "spec_hash": "sha256:9e5cd5e46f28ac23cf36a555a4f416bcabb8858d48fdd79afaa4743aa33e4106" + }, + { + "spec": "specs/claw/red_origin.t27", + "spec_hash": "sha256:ca93d2b89d539b2f811f8d42465df012dc2c664791fe2121f0809214b576bcbe" + }, + { + "spec": "specs/claw/refresh.t27", + "spec_hash": "sha256:6813191699e5c1bb152bb28126a34e87cf317fcfaf3c60c33c387ecd63af1df7" + }, + { + "spec": "specs/claw/required_red.t27", + "spec_hash": "sha256:585ddbdea9f98d3b4a45700e47cf6706528339874d1adfce796828b6804b5866" + }, + { + "spec": "specs/claw/sarif.t27", + "spec_hash": "sha256:f9c08d09fafe8c0e5665c3b5b803949b8c7f63432a66c3f1846f6731a15ffbdc" + }, + { + "spec": "specs/claw/text.t27", + "spec_hash": "sha256:968755ec1917f766eb2cdd0ab4c0c48aef028a0e7d69e191911695e925b286ad" + } + ], + "config": "gen=c,rust,verilog,zig;tests=zig", + "gen_hash_c": "sha256:960c99e70c02fd96bf64da3b77bba42f062efaa7e0310144aef81874ac8f9946", + "gen_hash_rust": "sha256:35f509afeb83e8548fc4839ffe8594f018f8cbb1d8ac81e53f114f63883685d6", + "gen_hash_verilog": "sha256:167cfe9d28bb76ac70218c180038e5a99a6caddbdb909b0d341155cbe4ad822e", + "gen_hash_zig": "sha256:cc5b29b0767ea02d0b469c849a98a8fd35162e78c32a49ec9e23b563fb2ebeab", + "module": "ClawMergeJudge", + "ring": 12, + "seal_schema": 2, + "sealed_at": "2026-10-19T10:23:45Z", + "sealed_by": "t27c-bootstrap@0.5.2", + "spec_hash": "sha256:614a28f7501e4eafa175b3250bf4c28ce3476dfeaf14a8c51a5a94087f3d39c7", + "spec_path": "specs/claw/merge_judge.t27", + "tests": { + "failed": 0, + "failing": [], + "forced": false, + "passed": 67, + "total": 67, + "vacuous": 0 + }, + "toolchain": { + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", + "zig": "0.16.0" + } +} \ No newline at end of file diff --git a/docs/claw/README.md b/docs/claw/README.md index d977a2a48b..3a0a483f31 100644 --- a/docs/claw/README.md +++ b/docs/claw/README.md @@ -27,6 +27,7 @@ behind one front door, with the gate logic in `.t27` specs. Epic | Lab receipt: whether a hand-run lab check of a patch really ran on the patch. A `git apply` that changed fewer files than the diff, a blank or BLOCKED test report, zero tests, a failure or a vacuous pass all hold | `specs/claw/lab_receipt.t27` (expected and applied file counts and the t27c `test-report` text in; one `L pass/hold` line with the reason out; not wired to a job yet) | #10810 | | Lab disk: whether a lab volume has room for a check, read from `df -Pk` before it runs. Under 1 GiB free holds; under 5 GiB or under 10% free warns; a mount df does not list, or a line with no readable size, holds | `specs/claw/lab_disk.t27` (`df -Pk` text and a mount in; one `D go/warn/hold` line with the reason out; not wired to a job yet) | #10813 | | Red origin: whose red a failing check is. A file the log names that the PR changes, or a green master, makes it the PR's; a red master, or a named file master changed since the PR's base, makes it inherited, and the PR gets no push. Ask required_red first | `specs/claw/red_origin.t27` (the PR's paths, the log's named paths, master's moved paths and master's state in; one `O own/inherited/unknown` line with the reason out; not wired to a job yet) | #10821 | +| Merge judge: which open pull requests TRI CLAW merges, and why it leaves the rest alone. First match wins: the no-auto-merge label, a draft, a base that is not master, a `queen-` bee branch (the reviewer-bee merger's), a conflict, no closing reference, an unread rollup, a pending check, a required check red or missing, any other red that is not inherited from master (claw-sarif is, by definition), a head commit under ten minutes old; then a squash merge pinned to the judged head sha, five per run at most, lowest number first | `specs/claw/merge_judge.t27` (`P`/`K`/`F`/`O`/`B` lines in; `G` merge, `W`/`H`/`N` skip with the reason, `L` failing log wanted and `M` summary out; lowered to `gen/c/claw/merge_judge.c`; run by the `claw-merge` job of `gate-topology.yml`, which also carries `refresh.t27`'s update-branch step; plan-only into the step summary until the repository variable `CLAW_MERGE_LIVE` is `1`) | #10973 | | Known survivors: whether a mutation sweep holds anything new. A survivor is keyed by its spec, operator and line text, not its line number, so an argued equivalent stays known when lines above it move; a new survivor, a NOAPPLY line or an unreadable line holds; a known entry no line matches asks to be pruned | `specs/claw/mut_known.t27` (the sweep text and the known list in; one `E pass/hold` line with the reason, and one `E new/harness/unreadable` line per holding sweep line, out; applied by hand, not wired to the sweep script yet) | #10835 | | Model routing: Opus 5.5 orchestrates, Sonnet 5.5 runs sub-tasks | `specs/claw/model_route.t27`, skill `model-routing`, `.claude/settings.json` + `.claude/agents/*` | owner directive 2026-10-18 | | A T27F visual in every answer that needs one | rule `specs/t27mcp/visual_output.t27`, particle-still layer `specs/claw/visual_output.t27`, procedure in skill `t27f`, "Visual in every output" | owner directive 2026-10-18 | @@ -74,6 +75,26 @@ token of the `pr-suggestions` row of `specs/claw/github_surface.t27`: `pull_requ An already formatted spec posts nothing. It needs the secret and variable of steps 3 and 4 above and the App permission `pull_requests: write`; without them the job posts nothing and says so in its step summary. +## The merge lane (#10973) + +The `claw-merge` job of `gate-topology.yml` runs on every push to master, on the 10-minute schedule and on +dispatch. It lists the open pull requests once, feeds `gen/c/claw/merge_judge.c` (`specs/claw/merge_judge.t27`), +and applies what the spec prints: `G` lines merge (squash, pinned to the judged head sha, so GitHub refuses it if +the head moved), every other line is a reason in the step summary. After a push it also runs `refresh.t27` as the +former `claw-refresh` job did, skipping a pull request it merged in the same run. Strictness is required + inherited: +the four required checks (validate, check-linked-issue, parse-ratchet, own-language) green, and every other red +check inherited from master by `red_origin.t27` (claw-sarif is, by definition, until #10501 is done). + +- The job is plan-only until the repository variable `CLAW_MERGE_LIVE` is `1`: it writes what it would merge to the + step summary and merges nothing. Set it after reading a dry run; unset it to stop the lane. +- The merge uses a second TRI CLAW token with `contents` and `pull_requests` write (the `merge-pr` row of + `github_surface.t27`), so master's push workflows start after a merge; without the App secrets it uses + `github.token`, whose merges start no workflow. The update-branch token is unchanged (`pull_requests` write). +- Bee pull requests (`queen-` branches) are never merged here: `auto-merge-ready-prs.yml` merges them, after the + reviewer bee. Drafts and pull requests labelled `no-auto-merge` are held; a conflict is listed, never resolved. +- Honest limits are in the header of the spec: the lane reads whether a failing log names a file the PR changes, not + the failure set; reviews are not read; two pull requests merged in one run were each green alone. + ## The inventory spec and how to regenerate it `specs/claw/gates.t27` holds three parallel `[67]str`-shaped arrays — `NAMES` diff --git a/gen/c/claw/merge_judge.c b/gen/c/claw/merge_judge.c new file mode 100644 index 0000000000..7fc88f264f --- /dev/null +++ b/gen/c/claw/merge_judge.c @@ -0,0 +1,3261 @@ +/* ============================================================================ + Generated from t27 spec: ClawMergeJudge + DO NOT EDIT - generated by t27c gen-c + phi^2 + 1/phi^2 = 3 | TRINITY + ============================================================================ */ + +#include +#include +#include +#include +#define t27_assert(c, m) do { if (!(c)) { __builtin_trap(); } } while (0) +#define assert_eq(a, b) do { if ((a) != (b)) { __builtin_trap(); } } while (0) + +#ifndef CLAWMERGEJUDGE_H +#define CLAWMERGEJUDGE_H + +/* ------------------------------------------------------- + Constants + ------------------------------------------------------- */ + +#define MJ_MAX_PRS 512 +#define MERGE_CAP 5 +#define QUIET_MIN 10 +#define QUIET_SECS 600 +#define MJ_MAX_ROWS 128 +#define MJ_TABLE_CAP 16384 +#define MJ_LIST_CAP 16384 +#define MJ_PF 14 +#define J_MERGE 0 +#define J_CAPPED 1 +#define J_HELD 2 +#define J_DRAFT 3 +#define J_BASE 4 +#define J_BEE 5 +#define J_CONFLICT 6 +#define J_UNKNOWN 7 +#define J_NO_CLOSE 8 +#define J_UNREAD 9 +#define J_PENDING 10 +#define J_REQ_RED 11 +#define J_REQ_MISSING 12 +#define J_LOG 13 +#define J_RED 14 +#define J_QUIET 15 +#define J_BAD 16 +#define J_COUNT 17 +#define J_CLASS "GWHHHNWWWWWWWWHWW" +static const const char* J_TEXTS[17] = { "every rule passed\n", "mergeable, but the per-run cap on merges is reached and the oldest go first: it goes next run\n", "the label no-auto-merge is on it: a person or a gate has claimed it\n", "draft: drafts are never touched\n", "its base branch is not master: the lane merges into master only\n", "bee pull request (queen- branch): only auto-merge-ready-prs.yml merges it, after the reviewer bee\n", "conflicts with its base branch: refresh.t27 lists it, the lane never resolves conflicts\n", "GitHub has not computed its mergeability yet: next run\n", "no closing reference (L1 TRACEABILITY): add Closes #N to the body\n", "the check rollup is not the head commit's, was cut short, or has a row that cannot be read\n", "checks are still waiting, or none has posted yet\n", "a required check is red: ", "a required check has not posted yet\n", "the failing log is not read yet, so whose red it is is not known: ", "red check ", "the head commit is younger than 10 minutes, or its age is unread: the author may still be pushing\n", "the record cannot be read: fail closed\n" }; +static const size_t J_LENS[17] = { 18, 94, 68, 32, 64, 98, 88, 55, 66, 91, 49, 25, 36, 66, 10, 98, 39 }; +#define MJ_SUM_HEAD "M\tclaw-merge: " +#define MJ_SUM_HEAD_LEN 14 +static const const char* MJ_SUM_TEXTS[6] = { " to merge, ", " over the cap, ", " waiting, ", " held, ", " not mine, ", " unreadable of " }; +static const size_t MJ_SUM_LENS[6] = { 11, 15, 10, 7, 11, 15 }; +#define MJ_SUM_TAIL " open\n" +#define MJ_SUM_TAIL_LEN 6 +#define KIND_NONE 0 +#define KIND_REQ 1 +#define KIND_SARIF 2 +#define KIND_OTHER 3 +#define RS_INHERITED 0 +#define RS_OWN 1 +#define RS_LOG 2 +static const const char* T_NAMES[9] = { "Schema Validation/validate", "Issue Gate/check-linked-issue", "Spec Parse Ratchet/parse-ratchet", "Own Language/own-language", "Corpus Ratchet/Corpus ratchet (expected-failure ledger)", "Gate Topology/claw-sarif", "t27b native/t27b-native-ratchet", "Spec Guards/spec-guards", "Seal Coverage/coverage" }; +static const size_t T_NLENS[9] = { 26, 29, 32, 25, 55, 24, 31, 23, 22 }; +static const const char* T_PATHS[4] = { "specs/claw/merge_judge.t27", "docs/claw/README.md", "specs/analog/new_circuit.t27", "tools/unrelated.py" }; +static const size_t T_PLENS[4] = { 26, 19, 28, 18 }; +#define K_SUPERSEDED 4 +#define MAX_RUN_DIGITS 20 +#define CHECK_MAX_ROWS 1024 +#define K_BAD 3 +#define K_FAIL 1 +#define K_PASS 0 +#define K_WAIT 2 +#define NO_CONCLUSION "-" +#define ST_COMPLETED "COMPLETED" +#define ST_CONTEXT "CONTEXT" +#define M_GREEN 0 +#define M_RED 1 +#define M_UNSEEN 2 +#define PR "specs/claw/move_gate.t27\nspecs/claw/reel_still.t27\n.trinity/seals/claw_ClawMoveGate.json\ndocs/claw/README.md\ndocs/reports/t27b_expectations.json\n" +#define R_INHERITED_MOVED 3 +#define R_INHERITED_RED 2 +#define R_OWN_MERGE 1 +#define R_OWN_PATH 0 +#define R_UNSEEN 4 +#define V_INHERITED 1 +#define V_OWN 0 +#define V_UNKNOWN 2 +static const size_t WHY_LENS[5] = { 59, 78, 76, 121, 100 }; +static const const char* WHY_TEXTS[5] = { "the log names a file this PR changes: fix it here and push\n", "master passes this check, so the PR's merge with master fails it: fix it here\n", "master fails this check too: do not push here; find or propose master's fix\n", "a file the log names changed on master since the PR's base, and the PR changes none: confirm on master, do not push here\n", "master has no finished run of this check and no named file settles it: read the log before you push\n" }; +#define BLANK_LINE "M\t\n" +#define BLANK_LINE_LEN 3 +#define CONFLICT_HEAD "M\t- #" +#define CONFLICT_HEAD_LEN 5 +#define CONFLICT_TAIL " conflicts with its base branch and was left alone\n" +#define CONFLICT_TAIL_LEN 51 +#define D_BAD 9 +#define D_CAPPED 8 +#define D_CONFLICT 2 +#define D_COUNT 11 +#define D_CURRENT 6 +#define D_DRAFT 5 +#define D_FORK 4 +#define D_HELD 3 +#define D_LATER 10 +#define D_REFRESH 1 +#define D_UNKNOWN 7 +#define HELD_LABEL "no-auto-merge" +#define HELD_LABEL_LEN 13 +#define MAX_PRS 512 +#define M_CONFLICT 1 +#define M_OK 0 +#define M_UNKNOWN 2 +#define NEXT_LABEL "bee-reviewed" +#define NEXT_LABEL_LEN 12 +#define NFIELDS 12 +#define QUEUE_LIMIT 150 +#define REFRESH_CAP 5 +#define SKIP_BUSY " Actions runs are queued, above the limit of 150\n" +#define SKIP_BUSY_LEN 49 +#define SKIP_HEAD "M\tclaw-refresh: skipped, " +#define SKIP_HEAD_LEN 25 +#define SKIP_UNREAD "the Actions queue size could not be read\n" +#define SKIP_UNREAD_LEN 41 +#define SUM_HEAD "M\tclaw-refresh: " +#define SUM_HEAD_LEN 16 +static const size_t SUM_LENS[9] = { 12, 14, 16, 32, 21, 20, 15, 19, 12 }; +static const const char* SUM_TEXTS[9] = { " to update, ", " in conflict, ", " held or draft, ", " fork without maintainer edits, ", " already up to date, ", " not decidable yet, ", " over the cap, ", " not next to land, ", " unreadable\n" }; +#define T_BAD 2 +#define T_FALSE 0 +#define T_TRUE 1 +#define RR_MISSING 4 +#define RR_REQUIRED 4 + +/* ------------------------------------------------------- + Function prototypes + ------------------------------------------------------- */ + +uint8_t mj_tag(uint8_t* raw, size_t ls, size_t le); +bool mj_starts(uint8_t* raw, size_t s, size_t e, const char* p, size_t p_len); +bool mj_same(uint8_t* a, size_t a0, size_t a1, uint8_t* b, size_t b0, size_t b1); +size_t mj_name_end(uint8_t* raw, size_t p); +bool mj_p_ok(uint8_t* raw, size_t* f); +bool mj_block_clean(uint8_t* raw, size_t bs, size_t be); +uint32_t mj_rows(uint8_t* raw, size_t bs, size_t be, uint8_t* rs, size_t cap, size_t* pos, size_t* len); +uint8_t mj_row_kind(uint8_t* rs, size_t rn, size_t ls, size_t le); +int32_t mj_first_kind(uint8_t* rs, size_t rn, uint8_t want); +uint8_t mj_master(uint8_t* raw, size_t n, uint8_t* nm, size_t ns, size_t ne); +uint32_t mj_files(uint8_t* raw, size_t n, uint32_t num, uint8_t* buf, size_t cap, size_t* len); +uint32_t mj_named(uint8_t* raw, size_t n, uint32_t num, uint8_t* nm, size_t ns, size_t ne, uint8_t* buf, size_t cap, size_t* len); +uint8_t mj_red_state(uint8_t* raw, size_t n, uint32_t num, uint8_t* nm, size_t ns, size_t ne, int32_t files_total, uint8_t* r); +size_t mj_reds(uint8_t* raw, size_t n, uint8_t* rs, size_t rn, size_t* pos, uint32_t num, int32_t files_total, bool emit, uint8_t* out, size_t cap, size_t w0, size_t* res); +uint8_t mj_judge(uint8_t* raw, size_t n, size_t bs, size_t be, size_t* f, size_t* det); +size_t mj_put_name(uint8_t* out, size_t cap, size_t w, uint8_t* raw, size_t p); +size_t mj_put_reason(uint8_t* out, size_t cap, size_t w, uint8_t d, uint8_t* raw, size_t p, uint8_t why); +size_t merge_plan(uint8_t* raw, size_t n, uint8_t* out, size_t cap); +size_t lane_plan(uint32_t kind, uint8_t* raw, size_t n, uint8_t* out, size_t cap, int32_t queued); +size_t t_sha(uint8_t* dst, size_t cap, size_t w, uint32_t d); +size_t t_p(uint8_t* dst, size_t cap, size_t w, uint32_t num, bool draft, uint32_t labels, uint32_t head, uint32_t base, uint32_t mg, uint32_t sc, uint32_t closing, uint32_t rd, uint32_t age, uint32_t total, uint32_t files); +size_t t_status(uint8_t* dst, size_t cap, size_t w, uint32_t sc); +size_t t_row(uint8_t* dst, size_t cap, size_t w, uint8_t tag, uint32_t name, uint32_t run, uint32_t sc); +size_t t_good_rows(uint8_t* dst, size_t cap, size_t w, uint32_t run0); +size_t t_file(uint8_t* dst, size_t cap, size_t w, uint32_t num, uint32_t path); +size_t t_named(uint8_t* dst, size_t cap, size_t w, uint32_t num, uint32_t name, uint32_t path); +size_t t_green(uint8_t* dst, size_t cap, size_t w, uint32_t num); +size_t t_v(uint8_t* dst, size_t cap, bool draft, uint32_t labels, uint32_t head, uint32_t base, uint32_t mg, uint32_t sc, uint32_t closing, uint32_t age); +uint8_t t_one(uint8_t* raw, size_t n); +bool t_g(uint8_t* out, size_t n, uint32_t num); +bool t_has(uint8_t* out, size_t n, uint8_t c, uint32_t num, uint8_t d); +bool t_has_named(uint8_t* out, size_t n, uint8_t c, uint32_t num, uint8_t d, uint32_t name); +bool t_summary(uint8_t* out, size_t n, uint32_t g, uint32_t c, uint32_t wt, uint32_t h, uint32_t nm, uint32_t bad, uint32_t total); +size_t t_red_pr(uint8_t* dst, size_t cap, size_t w, uint32_t name); +bool t_has_red(uint8_t* out, size_t n, uint32_t num, uint32_t name, uint8_t r); +bool t_has_log(uint8_t* out, size_t n, uint32_t num, uint32_t name, uint32_t id); +uint32_t t_gnums(uint8_t* out, size_t n, uint32_t* nums); +size_t t_tab(uint8_t* raw, size_t n, uint32_t k); +uint8_t t_class(uint32_t d); +uint8_t t_kind(uint8_t* rs, size_t n); +bool is_dash(uint8_t* raw, size_t s, size_t e); +bool is_superseded(uint8_t* raw, size_t n, size_t self_s, size_t* f); +uint32_t latest_counts(uint8_t* raw, size_t n, uint32_t* st); +bool row_ok(uint8_t* raw, size_t* f); +bool run_gt(uint8_t* raw, size_t a, size_t ae, size_t b, size_t be); +bool run_ok(uint8_t* raw, size_t s, size_t e); +uint8_t check_class(uint8_t* raw, size_t* f); +bool is_fail_conclusion(uint8_t* raw, size_t s, size_t e); +bool is_pass_conclusion(uint8_t* raw, size_t s, size_t e); +bool is_wait_status(uint8_t* raw, size_t s, size_t e); +size_t qc_field_end(uint8_t* raw, size_t e, size_t s); +size_t qc_line_end(uint8_t* raw, size_t n, size_t s); +size_t qc_put_byte(uint8_t* dst, size_t cap, size_t w, uint8_t b); +size_t qc_put_range(uint8_t* dst, size_t cap, size_t w, uint8_t* raw, size_t s, size_t e); +bool qc_range_eq(uint8_t* raw, size_t a, size_t ae, size_t b, size_t be); +bool qc_range_is(uint8_t* raw, size_t s, size_t e, const char* lit, size_t lit_len); +bool qc_split(uint8_t* raw, size_t ls, size_t le, size_t* f, size_t nf); +uint32_t hits(const char* b, size_t b_len, const char* list, size_t list_len); +bool line_in(const char* list, size_t list_len, const char* b, size_t s, size_t e); +uint8_t red_reason(const char* pr, size_t pr_len, const char* named, size_t named_len, const char* moved, size_t moved_len, uint8_t master); +uint8_t verdict_of(uint8_t r); +uint8_t decide(bool held, bool draft, bool fork_blocked, uint8_t mc, int32_t bh, bool next); +size_t refresh_plan(uint8_t* raw, size_t n, uint8_t* out, size_t cap, int32_t queued); +int32_t rf_behind(uint8_t* raw, size_t bs, size_t be, size_t ss, size_t se); +uint8_t rf_classify(uint8_t* raw, size_t* f); +bool rf_created_ok(uint8_t* raw, size_t s, size_t e); +bool rf_digit(uint8_t b); +uint8_t rf_flag(uint8_t* raw, size_t s, size_t e); +bool rf_has_label(uint8_t* raw, size_t s, size_t e, const char* label, size_t ll); +bool rf_hex(uint8_t b); +bool rf_label_held(uint8_t* raw, size_t s, size_t e); +uint8_t rf_merge_class(uint8_t* raw, size_t ms, size_t me, size_t ss, size_t se); +bool rf_older(uint8_t* raw, size_t ca, uint32_t na, size_t cb, uint32_t nb); +size_t rf_put_sha(uint8_t* dst, size_t cap, size_t w, uint8_t* raw, size_t s); +bool rf_sha_ok(uint8_t* raw, size_t s, size_t e); +bool rf_split(uint8_t* raw, size_t ls, size_t le, size_t* f); +uint32_t t_lines(uint8_t* out, size_t n, uint8_t b); +size_t job_start(uint8_t* raw, size_t s, size_t e); +uint32_t required_counts(uint8_t* raw, size_t n, uint32_t* st); +uint8_t required_index(uint8_t* raw, size_t s, size_t e); +int32_t digit_val(uint8_t b); +size_t field_end(uint8_t* text, size_t e, size_t s); +bool field_eq(uint8_t* text, size_t s, size_t e, const char* name, size_t n); +size_t line_end(uint8_t* text, size_t len, size_t s); +int32_t parse_num(uint8_t* text, size_t s, size_t e); +size_t put_byte(uint8_t* dst, size_t cap, size_t w, uint8_t b); +bool buf_has(uint8_t* buf, size_t buf_len, const char* needle, size_t needle_len); +uint8_t digit_byte(uint32_t d); +size_t put_count(uint8_t* dst, size_t cap, size_t w, uint32_t v); +size_t put_str(uint8_t* dst, size_t cap, size_t w, const char* s, size_t s_len); + +/* ------------------------------------------------------- + Function implementations + ------------------------------------------------------- */ + +uint8_t mj_tag(uint8_t* raw, size_t ls, size_t le) { + if ((le < (ls + 2))) { + return 0; + } + if ((raw[(ls + 1)] != '\t')) { + return 0; + } + return raw[ls]; +} + +bool mj_starts(uint8_t* raw, size_t s, size_t e, const char* p, size_t p_len) { + if ((e < (s + p_len))) { + return false; + } + size_t i = 0; + while ((i < p_len)) { + if ((raw[(s + i)] != p[i])) { + return false; + } + i += 1; + } + return true; +} + +bool mj_same(uint8_t* a, size_t a0, size_t a1, uint8_t* b, size_t b0, size_t b1) { + if (((a1 - a0) != (b1 - b0))) { + return false; + } + size_t i = 0; + while (((a0 + i) < a1)) { + if ((a[(a0 + i)] != b[(b0 + i)])) { + return false; + } + i += 1; + } + return true; +} + +size_t mj_name_end(uint8_t* raw, size_t p) { + size_t e = (p + 2); + while (((raw[e] != '\t') && (raw[e] != '\n'))) { + e += 1; + } + return e; +} + +bool mj_p_ok(uint8_t* raw, size_t* f) { + if ((parse_num(raw, f[2], f[3]) < 1)) { + return false; + } + if ((rf_sha_ok(raw, f[4], f[5]) == false)) { + return false; + } + if ((rf_flag(raw, f[6], f[7]) == T_BAD)) { + return false; + } + if (((f[11] == f[10]) || (f[13] == f[12]))) { + return false; + } + if ((parse_num(raw, f[18], f[19]) < 0)) { + return false; + } + if ((rf_sha_ok(raw, f[20], f[21]) == false)) { + return false; + } + if ((parse_num(raw, f[24], f[25]) < 0)) { + return false; + } + if ((parse_num(raw, f[26], f[27]) < 0)) { + return false; + } + return true; +} + +bool mj_block_clean(uint8_t* raw, size_t bs, size_t be) { + size_t ls = (qc_line_end(raw, be, bs) + 1); + while ((ls < be)) { + size_t le = qc_line_end(raw, be, ls); + if ((le > ls)) { + uint8_t t = mj_tag(raw, ls, le); + if ((((((t != 'K') && (t != 'F')) && (t != 'O')) && (t != 'B')) && (t != 'R'))) { + return false; + } + } + ls = (le + 1); + } + return true; +} + +uint32_t mj_rows(uint8_t* raw, size_t bs, size_t be, uint8_t* rs, size_t cap, size_t* pos, size_t* len) { + size_t w = 0; + uint32_t rows = 0; + size_t ls = bs; + while ((ls < be)) { + size_t le = qc_line_end(raw, be, ls); + if ((le > ls)) { + if ((mj_tag(raw, ls, le) == 'K')) { + if ((rows < MJ_MAX_ROWS)) { + pos[((size_t)(rows))] = ls; + } + w = qc_put_range(rs, cap, w, raw, (ls + 2), le); + w = put_str(rs, cap, w, "\n", 1); + rows += 1; + } + } + ls = (le + 1); + } + len[0] = w; + return rows; +} + +uint8_t mj_row_kind(uint8_t* rs, size_t rn, size_t ls, size_t le) { + size_t f[8] = {0}; + size_t c[4] = {0}; + if ((qc_split(rs, ls, le, &f, 4) == false)) { + return KIND_NONE; + } + if ((row_ok(rs, &f) == false)) { + return KIND_NONE; + } + if (is_superseded(rs, rn, ls, &f)) { + return KIND_NONE; + } + c[0] = f[4]; + c[1] = f[5]; + c[2] = f[6]; + c[3] = f[7]; + if ((check_class(rs, &c) != K_FAIL)) { + return KIND_NONE; + } + if ((required_index(rs, f[0], f[1]) < RR_REQUIRED)) { + return KIND_REQ; + } + if (qc_range_is(rs, job_start(rs, f[0], f[1]), f[1], "claw-sarif", 10)) { + return KIND_SARIF; + } + return KIND_OTHER; +} + +int32_t mj_first_kind(uint8_t* rs, size_t rn, uint8_t want) { + int32_t row = 0; + size_t ls = 0; + while ((ls < rn)) { + size_t le = qc_line_end(rs, rn, ls); + if ((le > ls)) { + if ((mj_row_kind(rs, rn, ls, le) == want)) { + return row; + } + row += 1; + } + ls = (le + 1); + } + return -1; +} + +uint8_t mj_master(uint8_t* raw, size_t n, uint8_t* nm, size_t ns, size_t ne) { + uint8_t best = M_UNSEEN; + size_t b0 = 0; + size_t b1 = 0; + size_t f[10] = {0}; + size_t ls = 0; + while ((ls < n)) { + size_t le = qc_line_end(raw, n, ls); + if ((le > ls)) { + if (((mj_tag(raw, ls, le) == 'B') && qc_split(raw, ls, le, &f, 5))) { + if (((mj_same(raw, f[2], f[3], nm, ns, ne) && run_ok(raw, f[4], f[5])) && qc_range_is(raw, f[6], f[7], "COMPLETED", 9))) { + uint8_t v = M_UNSEEN; + if (qc_range_is(raw, f[8], f[9], "SUCCESS", 7)) { + v = M_GREEN; + } + if (qc_range_is(raw, f[8], f[9], "FAILURE", 7)) { + v = M_RED; + } + if ((v != M_UNSEEN)) { + if (((best == M_UNSEEN) || run_gt(raw, f[4], f[5], b0, b1))) { + best = v; + b0 = f[4]; + b1 = f[5]; + } else if (((v == M_GREEN) && qc_range_eq(raw, f[4], f[5], b0, b1))) { + best = M_GREEN; + } + } + } + } + } + ls = (le + 1); + } + return best; +} + +uint32_t mj_files(uint8_t* raw, size_t n, uint32_t num, uint8_t* buf, size_t cap, size_t* len) { + size_t f[6] = {0}; + size_t w = 0; + uint32_t cnt = 0; + size_t ls = 0; + while ((ls < n)) { + size_t le = qc_line_end(raw, n, ls); + if ((le > ls)) { + if (((mj_tag(raw, ls, le) == 'F') && qc_split(raw, ls, le, &f, 3))) { + if (((parse_num(raw, f[2], f[3]) == ((int32_t)(num))) && (f[5] > f[4]))) { + w = qc_put_range(buf, cap, w, raw, f[4], f[5]); + w = put_str(buf, cap, w, "\n", 1); + cnt += 1; + } + } + } + ls = (le + 1); + } + len[0] = w; + return cnt; +} + +uint32_t mj_named(uint8_t* raw, size_t n, uint32_t num, uint8_t* nm, size_t ns, size_t ne, uint8_t* buf, size_t cap, size_t* len) { + size_t f[8] = {0}; + size_t w = 0; + uint32_t cnt = 0; + size_t ls = 0; + while ((ls < n)) { + size_t le = qc_line_end(raw, n, ls); + if ((le > ls)) { + if (((mj_tag(raw, ls, le) == 'O') && qc_split(raw, ls, le, &f, 4))) { + if (((parse_num(raw, f[2], f[3]) == ((int32_t)(num))) && mj_same(raw, f[4], f[5], nm, ns, ne))) { + cnt += 1; + if (((qc_range_is(raw, f[6], f[7], "-", 1) == false) && (f[7] > f[6]))) { + w = qc_put_range(buf, cap, w, raw, f[6], f[7]); + w = put_str(buf, cap, w, "\n", 1); + } + } + } + } + ls = (le + 1); + } + len[0] = w; + return cnt; +} + +uint8_t mj_red_state(uint8_t* raw, size_t n, uint32_t num, uint8_t* nm, size_t ns, size_t ne, int32_t files_total, uint8_t* r) { + uint8_t master = mj_master(raw, n, nm, ns, ne); + if ((master != M_RED)) { + r[0] = red_reason("", 0, "", 0, "", 0, master); + if ((verdict_of(r[0]) == V_INHERITED)) { + return RS_INHERITED; + } + return RS_OWN; + } + uint8_t fl[16384] = {0}; + uint8_t nl[16384] = {0}; + size_t flen[1] = {0}; + size_t nlen[1] = {0}; + uint32_t fc = mj_files(raw, n, num, &fl, MJ_LIST_CAP, &flen); + uint32_t nc = mj_named(raw, n, num, nm, ns, ne, &nl, MJ_LIST_CAP, &nlen); + if (((((nc == 0) || (((int32_t)(fc)) != files_total)) || (flen[0] > MJ_LIST_CAP)) || (nlen[0] > MJ_LIST_CAP))) { + return RS_LOG; + } + r[0] = red_reason(&fl, flen[0], &nl, nlen[0], "", 0, master); + if ((verdict_of(r[0]) == V_INHERITED)) { + return RS_INHERITED; + } + return RS_OWN; +} + +size_t mj_reds(uint8_t* raw, size_t n, uint8_t* rs, size_t rn, size_t* pos, uint32_t num, int32_t files_total, bool emit, uint8_t* out, size_t cap, size_t w0, size_t* res) { + size_t f[8] = {0}; + uint8_t r[1] = {0}; + size_t w = w0; + res[0] = ((size_t)(J_MERGE)); + res[1] = 0; + res[2] = 0; + bool own = false; + bool logged = false; + size_t row = 0; + size_t ls = 0; + while ((ls < rn)) { + size_t le = qc_line_end(rs, rn, ls); + if ((le > ls)) { + if (((mj_row_kind(rs, rn, ls, le) == KIND_OTHER) && qc_split(rs, ls, le, &f, 4))) { + uint8_t st = mj_red_state(raw, n, num, rs, f[0], f[1], files_total, &r); + if (((st == RS_OWN) && (own == false))) { + own = true; + res[0] = ((size_t)(J_RED)); + res[1] = pos[row]; + res[2] = ((size_t)(r[0])); + } + if ((st == RS_LOG)) { + if (((own == false) && (logged == false))) { + logged = true; + res[0] = ((size_t)(J_LOG)); + res[1] = pos[row]; + } + if (emit) { + w = put_str(out, cap, w, "L\t", 2); + w = put_count(out, cap, w, num); + w = put_byte(out, cap, w, '\t'); + w = qc_put_range(out, cap, w, rs, f[0], f[1]); + w = put_byte(out, cap, w, '\t'); + w = qc_put_range(out, cap, w, rs, f[2], f[3]); + w = put_byte(out, cap, w, '\n'); + } + } + } + row += 1; + } + ls = (le + 1); + } + return w; +} + +uint8_t mj_judge(uint8_t* raw, size_t n, size_t bs, size_t be, size_t* f, size_t* det) { + det[0] = 0; + det[1] = 0; + if ((mj_p_ok(raw, f) == false)) { + return J_BAD; + } + if ((mj_block_clean(raw, bs, be) == false)) { + return J_BAD; + } + if (rf_label_held(raw, f[8], f[9])) { + return J_HELD; + } + if ((rf_flag(raw, f[6], f[7]) == T_TRUE)) { + return J_DRAFT; + } + if ((qc_range_is(raw, f[12], f[13], "master", 6) == false)) { + return J_BASE; + } + if (mj_starts(raw, f[10], f[11], "queen-", 6)) { + return J_BEE; + } + uint8_t mc = rf_merge_class(raw, f[14], f[15], f[16], f[17]); + if ((mc == M_CONFLICT)) { + return J_CONFLICT; + } + if ((mc == M_UNKNOWN)) { + return J_UNKNOWN; + } + if ((parse_num(raw, f[18], f[19]) == 0)) { + return J_NO_CLOSE; + } + uint8_t rs[16384] = {0}; + size_t pos[128] = {0}; + size_t ln[1] = {0}; + uint32_t rows = mj_rows(raw, bs, be, &rs, MJ_TABLE_CAP, &pos, &ln); + if (((rows > MJ_MAX_ROWS) || (ln[0] > MJ_TABLE_CAP))) { + return J_UNREAD; + } + if ((((int32_t)(rows)) != parse_num(raw, f[24], f[25]))) { + return J_UNREAD; + } + if ((qc_range_eq(raw, f[4], f[5], f[20], f[21]) == false)) { + return J_UNREAD; + } + uint32_t st[5] = {0}; + latest_counts(&rs, ln[0], &st); + if ((st[K_BAD] > 0)) { + return J_UNREAD; + } + if (((st[K_WAIT] > 0) || ((st[K_PASS] + st[K_FAIL]) == 0))) { + return J_PENDING; + } + uint32_t rq[5] = {0}; + required_counts(&rs, ln[0], &rq); + if ((rq[K_FAIL] > 0)) { + int32_t k = mj_first_kind(&rs, ln[0], KIND_REQ); + if ((k >= 0)) { + det[0] = pos[((size_t)(k))]; + } + return J_REQ_RED; + } + if ((rq[RR_MISSING] > 0)) { + return J_REQ_MISSING; + } + size_t res[3] = {0}; + uint8_t sink[1] = {0}; + mj_reds(raw, n, &rs, ln[0], &pos, ((uint32_t)(parse_num(raw, f[2], f[3]))), parse_num(raw, f[26], f[27]), false, &sink, 0, 0, &res); + if ((res[0] != ((size_t)(J_MERGE)))) { + det[0] = res[1]; + det[1] = res[2]; + return ((uint8_t)(res[0])); + } + int32_t age = parse_num(raw, f[22], f[23]); + if ((age < QUIET_SECS)) { + return J_QUIET; + } + return J_MERGE; +} + +size_t mj_put_name(uint8_t* out, size_t cap, size_t w, uint8_t* raw, size_t p) { + return qc_put_range(out, cap, w, raw, (p + 2), mj_name_end(raw, p)); +} + +size_t mj_put_reason(uint8_t* out, size_t cap, size_t w, uint8_t d, uint8_t* raw, size_t p, uint8_t why) { + size_t x = put_str(out, cap, w, J_TEXTS[((size_t)(d))], J_LENS[((size_t)(d))]); + if (((d == J_REQ_RED) || (d == J_LOG))) { + x = mj_put_name(out, cap, x, raw, p); + x = put_byte(out, cap, x, '\n'); + } + if ((d == J_RED)) { + x = mj_put_name(out, cap, x, raw, p); + x = put_str(out, cap, x, ": ", 2); + if ((verdict_of(why) == V_OWN)) { + x = put_str(out, cap, x, "own: ", 5); + } else { + x = put_str(out, cap, x, "unknown: ", 9); + } + x = put_str(out, cap, x, WHY_TEXTS[((size_t)(why))], WHY_LENS[((size_t)(why))]); + } + return x; +} + +size_t merge_plan(uint8_t* raw, size_t n, uint8_t* out, size_t cap) { + size_t bs[512] = {0}; + size_t be[512] = {0}; + uint8_t dec[512] = {0}; + uint32_t num[512] = {0}; + uint32_t rk[512] = {0}; + size_t dp[512] = {0}; + uint8_t dy[512] = {0}; + size_t sp[512] = {0}; + int32_t ft[512] = {0}; + size_t f[28] = {0}; + size_t det[2] = {0}; + size_t cnt = 0; + uint32_t extra = 0; + size_t ls = 0; + while ((ls < n)) { + size_t le = qc_line_end(raw, n, ls); + if ((le > ls)) { + uint8_t t = mj_tag(raw, ls, le); + if ((t == 'P')) { + if ((cnt < MJ_MAX_PRS)) { + bs[cnt] = ls; + cnt += 1; + } else { + extra += 1; + } + } else if ((((((t != 'B') && (t != 'R')) && (t != 'F')) && (t != 'O')) && (cnt == 0))) { + extra += 1; + } + } + ls = (le + 1); + } + size_t i = 0; + while ((i < cnt)) { + if (((i + 1) < cnt)) { + be[i] = bs[(i + 1)]; + } else { + be[i] = n; + } + dec[i] = J_BAD; + num[i] = 0; + dp[i] = 0; + dy[i] = 0; + sp[i] = 0; + ft[i] = -1; + size_t le = qc_line_end(raw, n, bs[i]); + size_t fe = (bs[i] + 2); + while (((fe < le) && (raw[fe] != '\t'))) { + fe += 1; + } + int32_t pn = parse_num(raw, (bs[i] + 2), fe); + if ((pn > 0)) { + num[i] = ((uint32_t)(pn)); + } + if (((pn > 0) && qc_split(raw, bs[i], le, &f, MJ_PF))) { + sp[i] = f[4]; + ft[i] = parse_num(raw, f[26], f[27]); + dec[i] = mj_judge(raw, n, bs[i], be[i], &f, &det); + dp[i] = det[0]; + dy[i] = ((uint8_t)(det[1])); + } + size_t j = 0; + while ((j < i)) { + if (((num[i] != 0) && (num[j] == num[i]))) { + dec[i] = J_BAD; + } + j += 1; + } + i += 1; + } + i = 0; + while ((i < cnt)) { + rk[i] = 0; + if ((dec[i] == J_MERGE)) { + size_t j = 0; + while ((j < cnt)) { + if (((dec[j] == J_MERGE) && (num[j] < num[i]))) { + rk[i] += 1; + } + j += 1; + } + } + i += 1; + } + i = 0; + while ((i < cnt)) { + if (((dec[i] == J_MERGE) && (rk[i] >= MERGE_CAP))) { + dec[i] = J_CAPPED; + } + i += 1; + } + size_t w = 0; + uint32_t r = 0; + while ((r < MERGE_CAP)) { + i = 0; + while ((i < cnt)) { + if (((dec[i] == J_MERGE) && (rk[i] == r))) { + w = put_str(out, cap, w, "G\t", 2); + w = put_count(out, cap, w, num[i]); + w = put_byte(out, cap, w, '\t'); + w = qc_put_range(out, cap, w, raw, sp[i], (sp[i] + 40)); + w = put_byte(out, cap, w, '\n'); + } + i += 1; + } + r += 1; + } + uint32_t st[17] = {0}; + size_t c = 0; + while ((c < J_COUNT)) { + st[c] = 0; + c += 1; + } + i = 0; + while ((i < cnt)) { + st[((size_t)(dec[i]))] += 1; + if ((dec[i] != J_MERGE)) { + w = put_byte(out, cap, w, J_CLASS[((size_t)(dec[i]))]); + w = put_byte(out, cap, w, '\t'); + if ((num[i] == 0)) { + w = put_byte(out, cap, w, '-'); + } else { + w = put_count(out, cap, w, num[i]); + } + w = put_byte(out, cap, w, '\t'); + w = mj_put_reason(out, cap, w, dec[i], raw, dp[i], dy[i]); + } + i += 1; + } + i = 0; + while ((i < cnt)) { + if ((dec[i] == J_LOG)) { + uint8_t rs[16384] = {0}; + size_t pos[128] = {0}; + size_t ln[1] = {0}; + size_t res[3] = {0}; + mj_rows(raw, bs[i], be[i], &rs, MJ_TABLE_CAP, &pos, &ln); + w = mj_reds(raw, n, &rs, ln[0], &pos, num[i], ft[i], true, out, cap, w, &res); + } + i += 1; + } + uint32_t bad = (st[((size_t)(J_BAD))] + extra); + uint32_t waiting = ((((((((st[((size_t)(J_UNKNOWN))] + st[((size_t)(J_CONFLICT))]) + st[((size_t)(J_NO_CLOSE))]) + st[((size_t)(J_UNREAD))]) + st[((size_t)(J_PENDING))]) + st[((size_t)(J_REQ_RED))]) + st[((size_t)(J_REQ_MISSING))]) + st[((size_t)(J_LOG))]) + st[((size_t)(J_QUIET))]); + w = put_str(out, cap, w, MJ_SUM_HEAD, MJ_SUM_HEAD_LEN); + w = put_count(out, cap, w, st[((size_t)(J_MERGE))]); + w = put_str(out, cap, w, MJ_SUM_TEXTS[0], MJ_SUM_LENS[0]); + w = put_count(out, cap, w, st[((size_t)(J_CAPPED))]); + w = put_str(out, cap, w, MJ_SUM_TEXTS[1], MJ_SUM_LENS[1]); + w = put_count(out, cap, w, waiting); + w = put_str(out, cap, w, MJ_SUM_TEXTS[2], MJ_SUM_LENS[2]); + w = put_count(out, cap, w, (((st[((size_t)(J_HELD))] + st[((size_t)(J_DRAFT))]) + st[((size_t)(J_BASE))]) + st[((size_t)(J_RED))])); + w = put_str(out, cap, w, MJ_SUM_TEXTS[3], MJ_SUM_LENS[3]); + w = put_count(out, cap, w, st[((size_t)(J_BEE))]); + w = put_str(out, cap, w, MJ_SUM_TEXTS[4], MJ_SUM_LENS[4]); + w = put_count(out, cap, w, bad); + w = put_str(out, cap, w, MJ_SUM_TEXTS[5], MJ_SUM_LENS[5]); + w = put_count(out, cap, w, (((uint32_t)(cnt)) + extra)); + return put_str(out, cap, w, MJ_SUM_TAIL, MJ_SUM_TAIL_LEN); +} + +size_t lane_plan(uint32_t kind, uint8_t* raw, size_t n, uint8_t* out, size_t cap, int32_t queued) { + if ((kind == 0)) { + return merge_plan(raw, n, out, cap); + } + if ((kind == 1)) { + return refresh_plan(raw, n, out, cap, queued); + } + return 0; +} + +size_t t_sha(uint8_t* dst, size_t cap, size_t w, uint32_t d) { + size_t x = put_byte(dst, cap, w, digit_byte((d % 10))); + size_t k = 1; + while ((k < 40)) { + x = put_byte(dst, cap, x, 'a'); + k += 1; + } + return x; +} + +size_t t_p(uint8_t* dst, size_t cap, size_t w, uint32_t num, bool draft, uint32_t labels, uint32_t head, uint32_t base, uint32_t mg, uint32_t sc, uint32_t closing, uint32_t rd, uint32_t age, uint32_t total, uint32_t files) { + size_t x = put_str(dst, cap, w, "P\t", 2); + x = put_count(dst, cap, x, num); + x = put_byte(dst, cap, x, '\t'); + x = t_sha(dst, cap, x, num); + x = put_byte(dst, cap, x, '\t'); + if (draft) { + x = put_str(dst, cap, x, "true", 4); + } else { + x = put_str(dst, cap, x, "false", 5); + } + x = put_byte(dst, cap, x, '\t'); + if ((labels == 1)) { + x = put_str(dst, cap, x, "no-auto-merge", 13); + } + if ((labels == 2)) { + x = put_str(dst, cap, x, "bug,no-auto-merge,x", 19); + } + if ((labels == 3)) { + x = put_str(dst, cap, x, "no-auto-merge-2", 15); + } + if ((labels == 4)) { + x = put_str(dst, cap, x, "x-no-auto-merge", 15); + } + if ((labels == 5)) { + x = put_str(dst, cap, x, "bug,x", 5); + } + x = put_byte(dst, cap, x, '\t'); + if ((head == 0)) { + x = put_str(dst, cap, x, "feature-x", 9); + } + if ((head == 1)) { + x = put_str(dst, cap, x, "queen-77", 8); + } + if ((head == 2)) { + x = put_str(dst, cap, x, "queenly-x", 9); + } + if ((head == 3)) { + x = put_str(dst, cap, x, "xqueen-77", 9); + } + x = put_byte(dst, cap, x, '\t'); + if ((base == 0)) { + x = put_str(dst, cap, x, "master", 6); + } + if ((base == 1)) { + x = put_str(dst, cap, x, "stack-base", 10); + } + if ((base == 2)) { + x = put_str(dst, cap, x, "master2", 7); + } + x = put_byte(dst, cap, x, '\t'); + if ((mg == 0)) { + x = put_str(dst, cap, x, "MERGEABLE", 9); + } + if ((mg == 1)) { + x = put_str(dst, cap, x, "CONFLICTING", 11); + } + if ((mg == 2)) { + x = put_str(dst, cap, x, "UNKNOWN", 7); + } + x = put_byte(dst, cap, x, '\t'); + if ((sc == 0)) { + x = put_str(dst, cap, x, "CLEAN", 5); + } + if ((sc == 1)) { + x = put_str(dst, cap, x, "DIRTY", 5); + } + if ((sc == 2)) { + x = put_str(dst, cap, x, "BLOCKED", 7); + } + x = put_byte(dst, cap, x, '\t'); + x = put_count(dst, cap, x, closing); + x = put_byte(dst, cap, x, '\t'); + x = t_sha(dst, cap, x, rd); + x = put_byte(dst, cap, x, '\t'); + if ((age == 9999999)) { + x = put_byte(dst, cap, x, '-'); + } else { + x = put_count(dst, cap, x, age); + } + x = put_byte(dst, cap, x, '\t'); + x = put_count(dst, cap, x, total); + x = put_byte(dst, cap, x, '\t'); + x = put_count(dst, cap, x, files); + return put_byte(dst, cap, x, '\n'); +} + +size_t t_status(uint8_t* dst, size_t cap, size_t w, uint32_t sc) { + if ((sc == 0)) { + return put_str(dst, cap, w, "COMPLETED\tSUCCESS", 17); + } + if ((sc == 1)) { + return put_str(dst, cap, w, "COMPLETED\tFAILURE", 17); + } + if ((sc == 2)) { + return put_str(dst, cap, w, "QUEUED\t-", 8); + } + if ((sc == 3)) { + return put_str(dst, cap, w, "IN_PROGRESS\t-", 13); + } + if ((sc == 4)) { + return put_str(dst, cap, w, "COMPLETED\tSKIPPED", 17); + } + return put_str(dst, cap, w, "COMPLETED\tCANCELLED", 19); +} + +size_t t_row(uint8_t* dst, size_t cap, size_t w, uint8_t tag, uint32_t name, uint32_t run, uint32_t sc) { + size_t x = put_byte(dst, cap, w, tag); + x = put_byte(dst, cap, x, '\t'); + x = put_str(dst, cap, x, T_NAMES[((size_t)(name))], T_NLENS[((size_t)(name))]); + x = put_byte(dst, cap, x, '\t'); + x = put_count(dst, cap, x, run); + x = put_byte(dst, cap, x, '\t'); + x = t_status(dst, cap, x, sc); + return put_byte(dst, cap, x, '\n'); +} + +size_t t_good_rows(uint8_t* dst, size_t cap, size_t w, uint32_t run0) { + size_t x = t_row(dst, cap, w, 'K', 0, run0, 0); + x = t_row(dst, cap, x, 'K', 1, (run0 + 1), 0); + x = t_row(dst, cap, x, 'K', 2, (run0 + 2), 0); + x = t_row(dst, cap, x, 'K', 3, (run0 + 3), 0); + return t_row(dst, cap, x, 'K', 7, (run0 + 4), 0); +} + +size_t t_file(uint8_t* dst, size_t cap, size_t w, uint32_t num, uint32_t path) { + size_t x = put_str(dst, cap, w, "F\t", 2); + x = put_count(dst, cap, x, num); + x = put_byte(dst, cap, x, '\t'); + x = put_str(dst, cap, x, T_PATHS[((size_t)(path))], T_PLENS[((size_t)(path))]); + return put_byte(dst, cap, x, '\n'); +} + +size_t t_named(uint8_t* dst, size_t cap, size_t w, uint32_t num, uint32_t name, uint32_t path) { + size_t x = put_str(dst, cap, w, "O\t", 2); + x = put_count(dst, cap, x, num); + x = put_byte(dst, cap, x, '\t'); + x = put_str(dst, cap, x, T_NAMES[((size_t)(name))], T_NLENS[((size_t)(name))]); + x = put_byte(dst, cap, x, '\t'); + if ((path == 9)) { + x = put_byte(dst, cap, x, '-'); + } else { + x = put_str(dst, cap, x, T_PATHS[((size_t)(path))], T_PLENS[((size_t)(path))]); + } + return put_byte(dst, cap, x, '\n'); +} + +size_t t_green(uint8_t* dst, size_t cap, size_t w, uint32_t num) { + size_t x = t_p(dst, cap, w, num, false, 0, 0, 0, 0, 0, 1, num, 3600, 5, 2); + return t_good_rows(dst, cap, x, 100); +} + +size_t t_v(uint8_t* dst, size_t cap, bool draft, uint32_t labels, uint32_t head, uint32_t base, uint32_t mg, uint32_t sc, uint32_t closing, uint32_t age) { + size_t x = t_p(dst, cap, 0, 5, draft, labels, head, base, mg, sc, closing, 5, age, 5, 2); + return t_good_rows(dst, cap, x, 100); +} + +uint8_t t_one(uint8_t* raw, size_t n) { + size_t f[28] = {0}; + size_t det[2] = {0}; + size_t ls = 0; + while (((ls < n) && (mj_tag(raw, ls, qc_line_end(raw, n, ls)) != 'P'))) { + ls = (qc_line_end(raw, n, ls) + 1); + } + if ((qc_split(raw, ls, qc_line_end(raw, n, ls), &f, MJ_PF) == false)) { + return J_BAD; + } + return mj_judge(raw, n, ls, n, &f, &det); +} + +bool t_g(uint8_t* out, size_t n, uint32_t num) { + uint8_t e[64] = {0}; + size_t w = put_str(&e, 64, 0, "G\t", 2); + w = put_count(&e, 64, w, num); + w = put_byte(&e, 64, w, '\t'); + w = t_sha(&e, 64, w, num); + w = put_byte(&e, 64, w, '\n'); + return buf_has(out, n, &e, w); +} + +bool t_has(uint8_t* out, size_t n, uint8_t c, uint32_t num, uint8_t d) { + uint8_t e[160] = {0}; + size_t w = put_byte(&e, 160, 0, c); + w = put_byte(&e, 160, w, '\t'); + w = put_count(&e, 160, w, num); + w = put_byte(&e, 160, w, '\t'); + w = put_str(&e, 160, w, J_TEXTS[((size_t)(d))], J_LENS[((size_t)(d))]); + return buf_has(out, n, &e, w); +} + +bool t_has_named(uint8_t* out, size_t n, uint8_t c, uint32_t num, uint8_t d, uint32_t name) { + uint8_t e[240] = {0}; + size_t w = put_byte(&e, 240, 0, c); + w = put_byte(&e, 240, w, '\t'); + w = put_count(&e, 240, w, num); + w = put_byte(&e, 240, w, '\t'); + w = put_str(&e, 240, w, J_TEXTS[((size_t)(d))], J_LENS[((size_t)(d))]); + w = put_str(&e, 240, w, T_NAMES[((size_t)(name))], T_NLENS[((size_t)(name))]); + return buf_has(out, n, &e, w); +} + +bool t_summary(uint8_t* out, size_t n, uint32_t g, uint32_t c, uint32_t wt, uint32_t h, uint32_t nm, uint32_t bad, uint32_t total) { + uint8_t e[200] = {0}; + size_t w = put_str(&e, 200, 0, "M\tclaw-merge: ", 14); + w = put_count(&e, 200, w, g); + w = put_str(&e, 200, w, " to merge, ", 11); + w = put_count(&e, 200, w, c); + w = put_str(&e, 200, w, " over the cap, ", 15); + w = put_count(&e, 200, w, wt); + w = put_str(&e, 200, w, " waiting, ", 10); + w = put_count(&e, 200, w, h); + w = put_str(&e, 200, w, " held, ", 7); + w = put_count(&e, 200, w, nm); + w = put_str(&e, 200, w, " not mine, ", 11); + w = put_count(&e, 200, w, bad); + w = put_str(&e, 200, w, " unreadable of ", 15); + w = put_count(&e, 200, w, total); + w = put_str(&e, 200, w, " open\n", 6); + return buf_has(out, n, &e, w); +} + +size_t t_red_pr(uint8_t* dst, size_t cap, size_t w, uint32_t name) { + size_t x = t_p(dst, cap, w, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 6, 2); + x = t_good_rows(dst, cap, x, 100); + return t_row(dst, cap, x, 'K', name, 300, 1); +} + +bool t_has_red(uint8_t* out, size_t n, uint32_t num, uint32_t name, uint8_t r) { + uint8_t e[400] = {0}; + size_t w = put_str(&e, 400, 0, "H\t", 2); + w = put_count(&e, 400, w, num); + w = put_byte(&e, 400, w, '\t'); + w = put_str(&e, 400, w, "red check ", 10); + w = put_str(&e, 400, w, T_NAMES[((size_t)(name))], T_NLENS[((size_t)(name))]); + w = put_str(&e, 400, w, ": ", 2); + if ((verdict_of(r) == V_OWN)) { + w = put_str(&e, 400, w, "own: ", 5); + } else { + w = put_str(&e, 400, w, "unknown: ", 9); + } + w = put_str(&e, 400, w, WHY_TEXTS[((size_t)(r))], WHY_LENS[((size_t)(r))]); + return buf_has(out, n, &e, w); +} + +bool t_has_log(uint8_t* out, size_t n, uint32_t num, uint32_t name, uint32_t id) { + uint8_t e[160] = {0}; + size_t w = put_str(&e, 160, 0, "L\t", 2); + w = put_count(&e, 160, w, num); + w = put_byte(&e, 160, w, '\t'); + w = put_str(&e, 160, w, T_NAMES[((size_t)(name))], T_NLENS[((size_t)(name))]); + w = put_byte(&e, 160, w, '\t'); + w = put_count(&e, 160, w, id); + w = put_byte(&e, 160, w, '\n'); + return buf_has(out, n, &e, w); +} + +uint32_t t_gnums(uint8_t* out, size_t n, uint32_t* nums) { + uint32_t c = 0; + size_t i = 0; + while (((i + 2) < n)) { + if ((((out[i] == 'G') && (out[(i + 1)] == '\t')) && ((i == 0) || (out[(i - 1)] == '\n')))) { + uint32_t v = 0; + size_t j = (i + 2); + while ((out[j] != '\t')) { + v = ((v * 10) + ((uint32_t)((out[j] - '0')))); + j += 1; + } + nums[((size_t)(c))] = v; + c += 1; + } + i += 1; + } + return c; +} + +size_t t_tab(uint8_t* raw, size_t n, uint32_t k) { + uint32_t seen = 0; + size_t i = 0; + while ((i < n)) { + if ((raw[i] == '\t')) { + if ((seen == k)) { + return i; + } + seen += 1; + } + i += 1; + } + return n; +} + +uint8_t t_class(uint32_t d) { + if ((d == 0)) { + return 'G'; + } + if ((d == 5)) { + return 'N'; + } + if (((((d == 2) || (d == 3)) || (d == 4)) || (d == 14))) { + return 'H'; + } + return 'W'; +} + +uint8_t t_kind(uint8_t* rs, size_t n) { + if ((mj_first_kind(rs, n, KIND_REQ) == 0)) { + return KIND_REQ; + } + if ((mj_first_kind(rs, n, KIND_SARIF) == 0)) { + return KIND_SARIF; + } + if ((mj_first_kind(rs, n, KIND_OTHER) == 0)) { + return KIND_OTHER; + } + return KIND_NONE; +} + +bool is_dash(uint8_t* raw, size_t s, size_t e) { + return qc_range_is(raw, s, e, NO_CONCLUSION, 1); +} + +bool is_superseded(uint8_t* raw, size_t n, size_t self_s, size_t* f) { + if (is_dash(raw, f[2], f[3])) { + return false; + } + size_t g[8] = {0}; + uint32_t rows = 0; + size_t ls = 0; + while ((ls < n)) { + size_t le = qc_line_end(raw, n, ls); + if ((le > ls)) { + if ((((ls != self_s) && (rows < CHECK_MAX_ROWS)) && qc_split(raw, ls, le, &g, 4))) { + if ((row_ok(raw, &g) && (is_dash(raw, g[2], g[3]) == false))) { + if ((qc_range_eq(raw, g[0], g[1], f[0], f[1]) && run_gt(raw, g[2], g[3], f[2], f[3]))) { + return true; + } + } + } + rows += 1; + } + ls = (le + 1); + } + return false; +} + +uint32_t latest_counts(uint8_t* raw, size_t n, uint32_t* st) { + size_t f[8] = {0}; + size_t c[4] = {0}; + size_t k = 0; + while ((k < 5)) { + st[k] = 0; + k += 1; + } + uint32_t rows = 0; + size_t ls = 0; + while ((ls < n)) { + size_t le = qc_line_end(raw, n, ls); + if ((le > ls)) { + uint8_t d = K_BAD; + bool live = true; + if (((rows < CHECK_MAX_ROWS) && qc_split(raw, ls, le, &f, 4))) { + if (row_ok(raw, &f)) { + if (is_superseded(raw, n, ls, &f)) { + live = false; + } else { + c[0] = f[4]; + c[1] = f[5]; + c[2] = f[6]; + c[3] = f[7]; + d = check_class(raw, &c); + } + } + } + if (live) { + st[d] += 1; + } else { + st[K_SUPERSEDED] += 1; + } + rows += 1; + } + ls = (le + 1); + } + return rows; +} + +bool row_ok(uint8_t* raw, size_t* f) { + if ((f[1] == f[0])) { + return false; + } + bool ctx = qc_range_is(raw, f[4], f[5], ST_CONTEXT, 7); + if (is_dash(raw, f[2], f[3])) { + return ctx; + } + if (ctx) { + return false; + } + return run_ok(raw, f[2], f[3]); +} + +bool run_gt(uint8_t* raw, size_t a, size_t ae, size_t b, size_t be) { + if (((ae - a) != (be - b))) { + return ((ae - a) > (be - b)); + } + size_t i = 0; + while (((a + i) < ae)) { + if ((raw[(a + i)] != raw[(b + i)])) { + return (raw[(a + i)] > raw[(b + i)]); + } + i += 1; + } + return false; +} + +bool run_ok(uint8_t* raw, size_t s, size_t e) { + if (((e <= s) || ((e - s) > MAX_RUN_DIGITS))) { + return false; + } + if ((raw[s] == '0')) { + return false; + } + size_t i = s; + while ((i < e)) { + if (((raw[i] < '0') || (raw[i] > '9'))) { + return false; + } + i += 1; + } + return true; +} + +uint8_t check_class(uint8_t* raw, size_t* f) { + size_t s0 = f[0]; + size_t s1 = f[1]; + size_t c0 = f[2]; + size_t c1 = f[3]; + if (qc_range_is(raw, s0, s1, ST_CONTEXT, 7)) { + if (qc_range_is(raw, c0, c1, "SUCCESS", 7)) { + return K_PASS; + } + if ((qc_range_is(raw, c0, c1, "FAILURE", 7) || qc_range_is(raw, c0, c1, "ERROR", 5))) { + return K_FAIL; + } + if ((qc_range_is(raw, c0, c1, "PENDING", 7) || qc_range_is(raw, c0, c1, "EXPECTED", 8))) { + return K_WAIT; + } + return K_BAD; + } + if (qc_range_is(raw, s0, s1, ST_COMPLETED, 9)) { + if (is_pass_conclusion(raw, c0, c1)) { + return K_PASS; + } + if (is_fail_conclusion(raw, c0, c1)) { + return K_FAIL; + } + return K_BAD; + } + if (is_wait_status(raw, s0, s1)) { + if (qc_range_is(raw, c0, c1, NO_CONCLUSION, 1)) { + return K_WAIT; + } + return K_BAD; + } + return K_BAD; +} + +bool is_fail_conclusion(uint8_t* raw, size_t s, size_t e) { + return (((((qc_range_is(raw, s, e, "FAILURE", 7) || qc_range_is(raw, s, e, "CANCELLED", 9)) || qc_range_is(raw, s, e, "TIMED_OUT", 9)) || qc_range_is(raw, s, e, "ACTION_REQUIRED", 15)) || qc_range_is(raw, s, e, "STARTUP_FAILURE", 15)) || qc_range_is(raw, s, e, "STALE", 5)); +} + +bool is_pass_conclusion(uint8_t* raw, size_t s, size_t e) { + return ((qc_range_is(raw, s, e, "SUCCESS", 7) || qc_range_is(raw, s, e, "SKIPPED", 7)) || qc_range_is(raw, s, e, "NEUTRAL", 7)); +} + +bool is_wait_status(uint8_t* raw, size_t s, size_t e) { + return ((((qc_range_is(raw, s, e, "QUEUED", 6) || qc_range_is(raw, s, e, "IN_PROGRESS", 11)) || qc_range_is(raw, s, e, "WAITING", 7)) || qc_range_is(raw, s, e, "PENDING", 7)) || qc_range_is(raw, s, e, "REQUESTED", 9)); +} + +size_t qc_field_end(uint8_t* raw, size_t e, size_t s) { + size_t i = s; + while ((i < e)) { + if ((raw[i] == '\t')) { + return i; + } + i += 1; + } + return e; +} + +size_t qc_line_end(uint8_t* raw, size_t n, size_t s) { + size_t i = s; + while ((i < n)) { + if ((raw[i] == '\n')) { + return i; + } + i += 1; + } + return n; +} + +size_t qc_put_byte(uint8_t* dst, size_t cap, size_t w, uint8_t b) { + if ((w < cap)) { + dst[w] = b; + } + return (w + 1); +} + +size_t qc_put_range(uint8_t* dst, size_t cap, size_t w, uint8_t* raw, size_t s, size_t e) { + size_t x = w; + size_t i = s; + while ((i < e)) { + x = qc_put_byte(dst, cap, x, raw[i]); + i += 1; + } + return x; +} + +bool qc_range_eq(uint8_t* raw, size_t a, size_t ae, size_t b, size_t be) { + if (((ae - a) != (be - b))) { + return false; + } + size_t i = 0; + while (((a + i) < ae)) { + if ((raw[(a + i)] != raw[(b + i)])) { + return false; + } + i += 1; + } + return true; +} + +bool qc_range_is(uint8_t* raw, size_t s, size_t e, const char* lit, size_t lit_len) { + if (((e - s) != lit_len)) { + return false; + } + size_t i = 0; + while ((i < lit_len)) { + if ((raw[(s + i)] != lit[i])) { + return false; + } + i += 1; + } + return true; +} + +bool qc_split(uint8_t* raw, size_t ls, size_t le, size_t* f, size_t nf) { + size_t k = 0; + size_t s = ls; + while ((k < nf)) { + size_t e = qc_field_end(raw, le, s); + f[(2 * k)] = s; + f[((2 * k) + 1)] = e; + k += 1; + if ((k < nf)) { + if ((e >= le)) { + return false; + } + s = (e + 1); + } else { + return (e == le); + } + } + return false; +} + +uint32_t hits(const char* b, size_t b_len, const char* list, size_t list_len) { + uint32_t n = 0; + size_t s = 0; + while ((s < b_len)) { + size_t e = s; + while (((e < b_len) && (b[e] != '\n'))) { + e += 1; + } + if (line_in(list, list_len, b, s, e)) { + n += 1; + } + s = (e + 1); + } + return n; +} + +bool line_in(const char* list, size_t list_len, const char* b, size_t s, size_t e) { + if ((e <= s)) { + return false; + } + size_t n = (e - s); + size_t i = 0; + while ((i < list_len)) { + size_t j = i; + while (((j < list_len) && (list[j] != '\n'))) { + j += 1; + } + if (((j - i) == n)) { + size_t k = 0; + bool same = true; + while ((k < n)) { + if ((list[(i + k)] != b[(s + k)])) { + same = false; + } + k += 1; + } + if (same) { + return true; + } + } + i = (j + 1); + } + return false; +} + +uint8_t red_reason(const char* pr, size_t pr_len, const char* named, size_t named_len, const char* moved, size_t moved_len, uint8_t master) { + if ((hits(named, named_len, pr, pr_len) > 0)) { + return R_OWN_PATH; + } + if ((master == M_RED)) { + return R_INHERITED_RED; + } + if ((master == M_GREEN)) { + return R_OWN_MERGE; + } + if ((hits(named, named_len, moved, moved_len) > 0)) { + return R_INHERITED_MOVED; + } + return R_UNSEEN; +} + +uint8_t verdict_of(uint8_t r) { + if (((r == R_OWN_PATH) || (r == R_OWN_MERGE))) { + return V_OWN; + } + if (((r == R_INHERITED_RED) || (r == R_INHERITED_MOVED))) { + return V_INHERITED; + } + return V_UNKNOWN; +} + +uint8_t decide(bool held, bool draft, bool fork_blocked, uint8_t mc, int32_t bh, bool next) { + if (held) { + return D_HELD; + } + if (draft) { + return D_DRAFT; + } + if (fork_blocked) { + return D_FORK; + } + if ((mc == M_CONFLICT)) { + return D_CONFLICT; + } + if ((mc == M_UNKNOWN)) { + return D_UNKNOWN; + } + if ((bh < 0)) { + return D_UNKNOWN; + } + if ((bh == 0)) { + return D_CURRENT; + } + if ((next == false)) { + return D_LATER; + } + return D_REFRESH; +} + +size_t refresh_plan(uint8_t* raw, size_t n, uint8_t* out, size_t cap, int32_t queued) { + if ((queued < 0)) { + size_t u = put_str(out, cap, 0, SKIP_HEAD, SKIP_HEAD_LEN); + u = put_str(out, cap, u, SKIP_UNREAD, SKIP_UNREAD_LEN); + return u; + } + if ((queued > QUEUE_LIMIT)) { + size_t y = put_str(out, cap, 0, SKIP_HEAD, SKIP_HEAD_LEN); + y = put_count(out, cap, y, ((uint32_t)(queued))); + y = put_str(out, cap, y, SKIP_BUSY, SKIP_BUSY_LEN); + return y; + } + uint8_t dec[512] = {0}; + uint32_t num[512] = {0}; + size_t cs[512] = {0}; + size_t ss[512] = {0}; + uint32_t rk[512] = {0}; + size_t f[24] = {0}; + uint32_t st[11] = {0}; + size_t cnt = 0; + uint32_t extra = 0; + size_t ls = 0; + while ((ls < n)) { + size_t le = line_end(raw, n, ls); + if ((le > ls)) { + if ((cnt >= MAX_PRS)) { + extra += 1; + } else { + uint8_t d = D_BAD; + uint32_t v = 0; + cs[cnt] = 0; + ss[cnt] = 0; + if (rf_split(raw, ls, le, &f)) { + int32_t pn = parse_num(raw, f[0], f[1]); + if ((pn > 0)) { + v = ((uint32_t)(pn)); + d = rf_classify(raw, &f); + cs[cnt] = f[4]; + ss[cnt] = f[2]; + } + } + size_t j = 0; + while ((j < cnt)) { + if (((v != 0) && (num[j] == v))) { + d = D_BAD; + } + j += 1; + } + num[cnt] = v; + dec[cnt] = d; + cnt += 1; + } + } + size_t nx = le; + while (((nx < n) && (raw[nx] != '\n'))) { + nx += 1; + } + ls = (nx + 1); + } + size_t i = 0; + while ((i < cnt)) { + rk[i] = 0; + if ((dec[i] == D_REFRESH)) { + size_t j = 0; + while ((j < cnt)) { + if (((j != i) && (dec[j] == D_REFRESH))) { + if (rf_older(raw, cs[j], num[j], cs[i], num[i])) { + rk[i] += 1; + } + } + j += 1; + } + } + i += 1; + } + i = 0; + while ((i < cnt)) { + if (((dec[i] == D_REFRESH) && (rk[i] >= REFRESH_CAP))) { + dec[i] = D_CAPPED; + } + i += 1; + } + size_t c = 0; + while ((c < D_COUNT)) { + st[c] = 0; + c += 1; + } + i = 0; + while ((i < cnt)) { + st[((size_t)(dec[i]))] += 1; + i += 1; + } + st[((size_t)(D_BAD))] += extra; + size_t w = 0; + uint32_t r = 0; + while ((r < REFRESH_CAP)) { + i = 0; + while ((i < cnt)) { + if (((dec[i] == D_REFRESH) && (rk[i] == r))) { + w = put_str(out, cap, w, "R\t", 2); + w = put_count(out, cap, w, num[i]); + w = put_byte(out, cap, w, '\t'); + w = rf_put_sha(out, cap, w, raw, ss[i]); + w = put_byte(out, cap, w, '\n'); + } + i += 1; + } + r += 1; + } + w = put_str(out, cap, w, SUM_HEAD, SUM_HEAD_LEN); + w = put_count(out, cap, w, st[((size_t)(D_REFRESH))]); + w = put_str(out, cap, w, SUM_TEXTS[0], SUM_LENS[0]); + w = put_count(out, cap, w, st[((size_t)(D_CONFLICT))]); + w = put_str(out, cap, w, SUM_TEXTS[1], SUM_LENS[1]); + w = put_count(out, cap, w, (st[((size_t)(D_HELD))] + st[((size_t)(D_DRAFT))])); + w = put_str(out, cap, w, SUM_TEXTS[2], SUM_LENS[2]); + w = put_count(out, cap, w, st[((size_t)(D_FORK))]); + w = put_str(out, cap, w, SUM_TEXTS[3], SUM_LENS[3]); + w = put_count(out, cap, w, st[((size_t)(D_CURRENT))]); + w = put_str(out, cap, w, SUM_TEXTS[4], SUM_LENS[4]); + w = put_count(out, cap, w, st[((size_t)(D_UNKNOWN))]); + w = put_str(out, cap, w, SUM_TEXTS[5], SUM_LENS[5]); + w = put_count(out, cap, w, st[((size_t)(D_CAPPED))]); + w = put_str(out, cap, w, SUM_TEXTS[6], SUM_LENS[6]); + w = put_count(out, cap, w, st[((size_t)(D_LATER))]); + w = put_str(out, cap, w, SUM_TEXTS[7], SUM_LENS[7]); + w = put_count(out, cap, w, st[((size_t)(D_BAD))]); + w = put_str(out, cap, w, SUM_TEXTS[8], SUM_LENS[8]); + if ((st[((size_t)(D_CONFLICT))] > 0)) { + w = put_str(out, cap, w, BLANK_LINE, BLANK_LINE_LEN); + i = 0; + while ((i < cnt)) { + if ((dec[i] == D_CONFLICT)) { + w = put_str(out, cap, w, CONFLICT_HEAD, CONFLICT_HEAD_LEN); + w = put_count(out, cap, w, num[i]); + w = put_str(out, cap, w, CONFLICT_TAIL, CONFLICT_TAIL_LEN); + } + i += 1; + } + } + return w; +} + +int32_t rf_behind(uint8_t* raw, size_t bs, size_t be, size_t ss, size_t se) { + if (field_eq(raw, ss, se, "BEHIND", 6)) { + return 1; + } + int32_t n = parse_num(raw, bs, be); + if ((n > 0)) { + return 1; + } + if ((n == 0)) { + return 0; + } + return -1; +} + +uint8_t rf_classify(uint8_t* raw, size_t* f) { + if ((rf_sha_ok(raw, f[2], f[3]) == false)) { + return D_BAD; + } + if ((rf_created_ok(raw, f[4], f[5]) == false)) { + return D_BAD; + } + uint8_t draft = rf_flag(raw, f[6], f[7]); + uint8_t cross = rf_flag(raw, f[10], f[11]); + uint8_t modify = rf_flag(raw, f[14], f[15]); + uint8_t armed = rf_flag(raw, f[22], f[23]); + if (((((draft == T_BAD) || (cross == T_BAD)) || (modify == T_BAD)) || (armed == T_BAD))) { + return D_BAD; + } + bool held = rf_label_held(raw, f[8], f[9]); + bool gone = (f[13] == f[12]); + bool fork_blocked = (((cross == T_TRUE) || gone) && (modify != T_TRUE)); + uint8_t mc = rf_merge_class(raw, f[16], f[17], f[18], f[19]); + int32_t bh = rf_behind(raw, f[20], f[21], f[18], f[19]); + bool next = ((armed == T_TRUE) || rf_has_label(raw, f[8], f[9], NEXT_LABEL, NEXT_LABEL_LEN)); + return decide(held, (draft == T_TRUE), fork_blocked, mc, bh, next); +} + +bool rf_created_ok(uint8_t* raw, size_t s, size_t e) { + if (((e - s) != 20)) { + return false; + } + size_t i = 0; + while ((i < 20)) { + uint8_t b = raw[(s + i)]; + if (((i == 4) || (i == 7))) { + if ((b != '-')) { + return false; + } + } else if ((i == 10)) { + if ((b != 'T')) { + return false; + } + } else if (((i == 13) || (i == 16))) { + if ((b != ':')) { + return false; + } + } else if ((i == 19)) { + if ((b != 'Z')) { + return false; + } + } else if ((rf_digit(b) == false)) { + return false; + } + i += 1; + } + return true; +} + +bool rf_digit(uint8_t b) { + return ((b >= '0') && (b <= '9')); +} + +uint8_t rf_flag(uint8_t* raw, size_t s, size_t e) { + if (field_eq(raw, s, e, "true", 4)) { + return T_TRUE; + } + if (field_eq(raw, s, e, "false", 5)) { + return T_FALSE; + } + return T_BAD; +} + +bool rf_has_label(uint8_t* raw, size_t s, size_t e, const char* label, size_t ll) { + size_t p = s; + while ((p <= e)) { + size_t q = p; + while (((q < e) && (raw[q] != ','))) { + q += 1; + } + if (field_eq(raw, p, q, label, ll)) { + return true; + } + p = (q + 1); + } + return false; +} + +bool rf_hex(uint8_t b) { + if (((b >= '0') && (b <= '9'))) { + return true; + } + return ((b >= 'a') && (b <= 'f')); +} + +bool rf_label_held(uint8_t* raw, size_t s, size_t e) { + return rf_has_label(raw, s, e, HELD_LABEL, HELD_LABEL_LEN); +} + +uint8_t rf_merge_class(uint8_t* raw, size_t ms, size_t me, size_t ss, size_t se) { + if (field_eq(raw, ms, me, "CONFLICTING", 11)) { + return M_CONFLICT; + } + if (field_eq(raw, ss, se, "DIRTY", 5)) { + return M_CONFLICT; + } + if (field_eq(raw, ms, me, "MERGEABLE", 9)) { + return M_OK; + } + return M_UNKNOWN; +} + +bool rf_older(uint8_t* raw, size_t ca, uint32_t na, size_t cb, uint32_t nb) { + size_t k = 0; + while ((k < 20)) { + uint8_t x = raw[(ca + k)]; + uint8_t y = raw[(cb + k)]; + if ((x < y)) { + return true; + } + if ((x > y)) { + return false; + } + k += 1; + } + return (na < nb); +} + +size_t rf_put_sha(uint8_t* dst, size_t cap, size_t w, uint8_t* raw, size_t s) { + size_t x = w; + size_t i = 0; + while ((i < 40)) { + x = put_byte(dst, cap, x, raw[(s + i)]); + i += 1; + } + return x; +} + +bool rf_sha_ok(uint8_t* raw, size_t s, size_t e) { + if (((e - s) != 40)) { + return false; + } + size_t bad = 0; + size_t i = s; + while ((i < e)) { + if ((rf_hex(raw[i]) == false)) { + bad += 1; + } + i += 1; + } + return (bad == 0); +} + +bool rf_split(uint8_t* raw, size_t ls, size_t le, size_t* f) { + size_t k = 0; + size_t s = ls; + while ((k < NFIELDS)) { + size_t e = field_end(raw, le, s); + f[(2 * k)] = s; + f[((2 * k) + 1)] = e; + k += 1; + if ((k < NFIELDS)) { + if ((e >= le)) { + return false; + } + s = (e + 1); + } else { + return (e == le); + } + } + return false; +} + +uint32_t t_lines(uint8_t* out, size_t n, uint8_t b) { + uint32_t c = 0; + size_t i = 0; + while ((i < n)) { + if (((out[i] == b) && ((i == 0) || (out[(i - 1)] == '\n')))) { + c += 1; + } + i += 1; + } + return c; +} + +size_t job_start(uint8_t* raw, size_t s, size_t e) { + size_t j = s; + size_t i = s; + while ((i < e)) { + if ((raw[i] == '/')) { + j = (i + 1); + } + i += 1; + } + return j; +} + +uint32_t required_counts(uint8_t* raw, size_t n, uint32_t* st) { + size_t f[8] = {0}; + size_t c[4] = {0}; + uint32_t seen[4] = {0}; + size_t k = 0; + while ((k < 5)) { + st[k] = 0; + k += 1; + } + k = 0; + while ((k < 4)) { + seen[k] = 0; + k += 1; + } + uint32_t rows = 0; + size_t ls = 0; + while ((ls < n)) { + size_t le = qc_line_end(raw, n, ls); + if ((le > ls)) { + bool readable = false; + if (((rows < CHECK_MAX_ROWS) && qc_split(raw, ls, le, &f, 4))) { + if (row_ok(raw, &f)) { + readable = true; + uint8_t r = required_index(raw, f[0], f[1]); + if ((r < RR_REQUIRED)) { + seen[r] += 1; + if ((is_superseded(raw, n, ls, &f) == false)) { + c[0] = f[4]; + c[1] = f[5]; + c[2] = f[6]; + c[3] = f[7]; + st[check_class(raw, &c)] += 1; + } + } + } + } + if ((readable == false)) { + st[K_BAD] += 1; + } + rows += 1; + } + ls = (le + 1); + } + k = 0; + while ((k < 4)) { + if ((seen[k] == 0)) { + st[RR_MISSING] += 1; + } + k += 1; + } + return rows; +} + +uint8_t required_index(uint8_t* raw, size_t s, size_t e) { + size_t j = job_start(raw, s, e); + if (qc_range_is(raw, j, e, "validate", 8)) { + return 0; + } + if (qc_range_is(raw, j, e, "check-linked-issue", 18)) { + return 1; + } + if (qc_range_is(raw, j, e, "parse-ratchet", 13)) { + return 2; + } + if (qc_range_is(raw, j, e, "own-language", 12)) { + return 3; + } + return RR_REQUIRED; +} + +int32_t digit_val(uint8_t b) { + if ((b == '0')) { + return 0; + } + if ((b == '1')) { + return 1; + } + if ((b == '2')) { + return 2; + } + if ((b == '3')) { + return 3; + } + if ((b == '4')) { + return 4; + } + if ((b == '5')) { + return 5; + } + if ((b == '6')) { + return 6; + } + if ((b == '7')) { + return 7; + } + if ((b == '8')) { + return 8; + } + if ((b == '9')) { + return 9; + } + return -1; +} + +size_t field_end(uint8_t* text, size_t e, size_t s) { + size_t i = s; + while ((i < e)) { + if ((text[i] == '\t')) { + return i; + } + i += 1; + } + return e; +} + +bool field_eq(uint8_t* text, size_t s, size_t e, const char* name, size_t n) { + if ((e < s)) { + return false; + } + if (((e - s) != n)) { + return false; + } + size_t i = 0; + while ((i < n)) { + if ((text[(s + i)] != name[i])) { + return false; + } + i += 1; + } + return true; +} + +size_t line_end(uint8_t* text, size_t len, size_t s) { + size_t i = s; + while ((i < len)) { + if ((text[i] == '\n')) { +break; + } + i += 1; + } + if ((i > s)) { + if ((text[(i - 1)] == 13)) { + return (i - 1); + } + } + return i; +} + +int32_t parse_num(uint8_t* text, size_t s, size_t e) { + if ((e <= s)) { + return -1; + } + if (((e - s) > 9)) { + return -1; + } + int32_t v = 0; + size_t i = s; + while ((i < e)) { + int32_t d = digit_val(text[i]); + if ((d < 0)) { + return -1; + } + v = ((v * 10) + d); + i += 1; + } + return v; +} + +size_t put_byte(uint8_t* dst, size_t cap, size_t w, uint8_t b) { + if ((w < cap)) { + dst[w] = b; + } + return (w + 1); +} + +bool buf_has(uint8_t* buf, size_t buf_len, const char* needle, size_t needle_len) { + if ((needle_len > buf_len)) { + return false; + } + size_t start = 0; + while (((start + needle_len) <= buf_len)) { + size_t k = 0; + size_t diff = 0; + while ((k < needle_len)) { + if ((buf[(start + k)] != needle[k])) { + diff += 1; + } + k += 1; + } + if ((diff == 0)) { + return true; + } + start += 1; + } + return false; +} + +uint8_t digit_byte(uint32_t d) { + uint8_t b = '0'; + if ((d == 1)) { + b = '1'; + } + if ((d == 2)) { + b = '2'; + } + if ((d == 3)) { + b = '3'; + } + if ((d == 4)) { + b = '4'; + } + if ((d == 5)) { + b = '5'; + } + if ((d == 6)) { + b = '6'; + } + if ((d == 7)) { + b = '7'; + } + if ((d == 8)) { + b = '8'; + } + if ((d == 9)) { + b = '9'; + } + return b; +} + +size_t put_count(uint8_t* dst, size_t cap, size_t w, uint32_t v) { + uint8_t tmp[16] = {0}; + size_t n = 0; + uint32_t x = v; + if ((x == 0)) { + tmp[0] = digit_byte(0); + n = 1; + } + while ((x > 0)) { + tmp[n] = digit_byte((x % 10)); + n += 1; + x = (x / 10); + } + size_t k = 0; + while ((k < n)) { + if (((w + k) < cap)) { + dst[(w + k)] = tmp[((n - 1) - k)]; + } + k += 1; + } + return (w + n); +} + +size_t put_str(uint8_t* dst, size_t cap, size_t w, const char* s, size_t s_len) { + size_t i = 0; + while ((i < s_len)) { + if (((w + i) < cap)) { + dst[(w + i)] = s[i]; + } + i += 1; + } + return (w + s_len); +} + +/* ------------------------------------------------------- + Tests + ------------------------------------------------------- */ + +void test_a_pull_request_that_passes_every_rule_is_merged_pinned_to_its_head_sha(void) { + uint8_t raw[4096] = {0}; + uint8_t out[4096] = {0}; + size_t n = t_green(&raw, 4096, 0, 7); + assert((t_one(&raw, n) == J_MERGE)); + size_t m = merge_plan(&raw, n, &out, 4096); + assert(t_g(&out, m, 7)); + assert((t_lines(&out, m, 'G') == 1)); + assert(((((t_lines(&out, m, 'W') + t_lines(&out, m, 'H')) + t_lines(&out, m, 'N')) + t_lines(&out, m, 'L')) == 0)); + assert(t_summary(&out, m, 1, 0, 0, 0, 0, 0, 1)); +} + +void test_rule_1_the_held_label_holds_whatever_else_is_true(void) { + uint8_t raw[4096] = {0}; + uint8_t out[4096] = {0}; + size_t n = t_v(&raw, 4096, false, 1, 0, 0, 0, 0, 1, 3600); + assert((t_one(&raw, n) == J_HELD)); + n = t_v(&raw, 4096, false, 2, 0, 0, 0, 0, 1, 3600); + assert((t_one(&raw, n) == J_HELD)); + n = t_v(&raw, 4096, true, 1, 1, 1, 1, 1, 0, 0); + assert((t_one(&raw, n) == J_HELD)); + size_t m = merge_plan(&raw, n, &out, 4096); + assert(t_has(&out, m, 'H', 5, J_HELD)); + assert((t_lines(&out, m, 'G') == 0)); +} + +void test_rule_1_only_the_whole_label_holds(void) { + uint8_t raw[4096] = {0}; + size_t n = t_v(&raw, 4096, false, 3, 0, 0, 0, 0, 1, 3600); + assert((t_one(&raw, n) == J_MERGE)); + n = t_v(&raw, 4096, false, 4, 0, 0, 0, 0, 1, 3600); + assert((t_one(&raw, n) == J_MERGE)); + n = t_v(&raw, 4096, false, 5, 0, 0, 0, 0, 1, 3600); + assert((t_one(&raw, n) == J_MERGE)); +} + +void test_rule_2_a_draft_is_never_touched(void) { + uint8_t raw[4096] = {0}; + uint8_t out[4096] = {0}; + size_t n = t_v(&raw, 4096, true, 0, 0, 0, 0, 0, 1, 3600); + assert((t_one(&raw, n) == J_DRAFT)); + size_t m = merge_plan(&raw, n, &out, 4096); + assert(t_has(&out, m, 'H', 5, J_DRAFT)); + assert(t_summary(&out, m, 0, 0, 0, 1, 0, 0, 1)); +} + +void test_rule_3_only_master_is_merged_into(void) { + uint8_t raw[4096] = {0}; + size_t n = t_v(&raw, 4096, false, 0, 0, 1, 0, 0, 1, 3600); + assert((t_one(&raw, n) == J_BASE)); + n = t_v(&raw, 4096, false, 0, 0, 2, 0, 0, 1, 3600); + assert((t_one(&raw, n) == J_BASE)); + n = t_v(&raw, 4096, false, 0, 0, 0, 0, 0, 1, 3600); + assert((t_one(&raw, n) == J_MERGE)); +} + +void test_rule_4_a_bee_branch_is_not_mine(void) { + uint8_t raw[4096] = {0}; + uint8_t out[4096] = {0}; + size_t n = t_v(&raw, 4096, false, 0, 1, 0, 0, 0, 1, 3600); + assert((t_one(&raw, n) == J_BEE)); + size_t m = merge_plan(&raw, n, &out, 4096); + assert(t_has(&out, m, 'N', 5, J_BEE)); + assert(t_summary(&out, m, 0, 0, 0, 0, 1, 0, 1)); + n = t_v(&raw, 4096, false, 0, 2, 0, 0, 0, 1, 3600); + assert((t_one(&raw, n) == J_MERGE)); + n = t_v(&raw, 4096, false, 0, 3, 0, 0, 0, 1, 3600); + assert((t_one(&raw, n) == J_MERGE)); +} + +void test_rule_5_a_conflict_waits_and_the_lane_never_resolves_it(void) { + uint8_t raw[4096] = {0}; + uint8_t out[4096] = {0}; + size_t n = t_v(&raw, 4096, false, 0, 0, 0, 1, 2, 1, 3600); + assert((t_one(&raw, n) == J_CONFLICT)); + n = t_v(&raw, 4096, false, 0, 0, 0, 0, 1, 1, 3600); + assert((t_one(&raw, n) == J_CONFLICT)); + size_t m = merge_plan(&raw, n, &out, 4096); + assert(t_has(&out, m, 'W', 5, J_CONFLICT)); + assert(t_summary(&out, m, 0, 0, 1, 0, 0, 0, 1)); +} + +void test_rule_5_a_mergeability_github_has_not_computed_waits(void) { + uint8_t raw[4096] = {0}; + size_t n = t_v(&raw, 4096, false, 0, 0, 0, 2, 2, 1, 3600); + assert((t_one(&raw, n) == J_UNKNOWN)); +} + +void test_rule_6_no_closing_reference_waits(void) { + uint8_t raw[4096] = {0}; + uint8_t out[4096] = {0}; + size_t n = t_v(&raw, 4096, false, 0, 0, 0, 0, 0, 0, 3600); + assert((t_one(&raw, n) == J_NO_CLOSE)); + size_t m = merge_plan(&raw, n, &out, 4096); + assert(t_has(&out, m, 'W', 5, J_NO_CLOSE)); + n = t_v(&raw, 4096, false, 0, 0, 0, 0, 0, 1, 3600); + assert((t_one(&raw, n) == J_MERGE)); + n = t_v(&raw, 4096, false, 0, 0, 0, 0, 0, 3, 3600); + assert((t_one(&raw, n) == J_MERGE)); +} + +void test_the_first_matching_rule_names_the_reason(void) { + uint8_t raw[4096] = {0}; + size_t n = t_v(&raw, 4096, true, 1, 1, 1, 1, 1, 0, 10); + assert((t_one(&raw, n) == J_HELD)); + n = t_v(&raw, 4096, true, 0, 1, 1, 1, 1, 0, 10); + assert((t_one(&raw, n) == J_DRAFT)); + n = t_v(&raw, 4096, false, 0, 1, 1, 1, 1, 0, 10); + assert((t_one(&raw, n) == J_BASE)); + n = t_v(&raw, 4096, false, 0, 1, 0, 1, 1, 0, 10); + assert((t_one(&raw, n) == J_BEE)); + n = t_v(&raw, 4096, false, 0, 0, 0, 1, 1, 0, 10); + assert((t_one(&raw, n) == J_CONFLICT)); + n = t_v(&raw, 4096, false, 0, 0, 0, 0, 0, 0, 10); + assert((t_one(&raw, n) == J_NO_CLOSE)); + n = t_v(&raw, 4096, false, 0, 0, 0, 0, 0, 1, 10); + assert((t_one(&raw, n) == J_QUIET)); + n = t_v(&raw, 4096, false, 0, 0, 0, 0, 0, 1, 3600); + assert((t_one(&raw, n) == J_MERGE)); +} + +void test_rule_7_a_rollup_that_is_not_the_head_commits_is_not_read(void) { + uint8_t raw[4096] = {0}; + uint8_t out[4096] = {0}; + size_t w = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 6, 3600, 5, 2); + w = t_good_rows(&raw, 4096, w, 100); + assert((t_one(&raw, w) == J_UNREAD)); + size_t m = merge_plan(&raw, w, &out, 4096); + assert(t_has(&out, m, 'W', 5, J_UNREAD)); + assert((t_lines(&out, m, 'G') == 0)); +} + +void test_rule_7_a_rollup_cut_short_is_not_read(void) { + uint8_t raw[4096] = {0}; + size_t w = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 6, 2); + w = t_good_rows(&raw, 4096, w, 100); + assert((t_one(&raw, w) == J_UNREAD)); + w = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 4, 2); + w = t_good_rows(&raw, 4096, w, 100); + assert((t_one(&raw, w) == J_UNREAD)); +} + +void test_rule_7_a_row_that_cannot_be_read_is_not_read(void) { + uint8_t raw[4096] = {0}; + size_t w = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 6, 2); + w = t_good_rows(&raw, 4096, w, 100); + w = put_str(&raw, 4096, w, "K\tonly two fields\tCOMPLETED\n", 28); + assert((t_one(&raw, w) == J_UNREAD)); + w = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 6, 2); + w = t_good_rows(&raw, 4096, w, 100); + w = put_str(&raw, 4096, w, "K\tIssue Gate/check-linked-issue\tx\tCOMPLETED\tSUCCESS\n", 52); + assert((t_one(&raw, w) == J_UNREAD)); +} + +void test_rule_7_more_rows_than_the_lane_reads_are_not_read(void) { + uint8_t raw[16384] = {0}; + size_t w = t_p(&raw, 16384, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 129, 2); + uint32_t k = 0; + while ((k < 129)) { + w = t_row(&raw, 16384, w, 'K', 7, (1000 + k), 0); + k += 1; + } + assert((t_one(&raw, w) == J_UNREAD)); + w = t_p(&raw, 16384, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 128, 2); + k = 0; + while ((k < 124)) { + w = t_row(&raw, 16384, w, 'K', 7, (1000 + k), 0); + k += 1; + } + w = t_row(&raw, 16384, w, 'K', 0, 5000, 0); + w = t_row(&raw, 16384, w, 'K', 1, 5001, 0); + w = t_row(&raw, 16384, w, 'K', 2, 5002, 0); + w = t_row(&raw, 16384, w, 'K', 3, 5003, 0); + assert((t_one(&raw, w) == J_MERGE)); +} + +void test_rule_8_a_check_still_running_waits(void) { + uint8_t raw[4096] = {0}; + uint8_t out[4096] = {0}; + size_t w = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 6, 2); + w = t_good_rows(&raw, 4096, w, 100); + w = t_row(&raw, 4096, w, 'K', 8, 200, 2); + assert((t_one(&raw, w) == J_PENDING)); + size_t m = merge_plan(&raw, w, &out, 4096); + assert(t_has(&out, m, 'W', 5, J_PENDING)); + w = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 6, 2); + w = t_good_rows(&raw, 4096, w, 100); + w = t_row(&raw, 4096, w, 'K', 8, 200, 3); + assert((t_one(&raw, w) == J_PENDING)); +} + +void test_rule_8_no_check_posted_is_not_green(void) { + uint8_t raw[4096] = {0}; + size_t w = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 0, 2); + assert((t_one(&raw, w) == J_PENDING)); +} + +void test_rule_8_skipped_is_a_pass_but_the_required_checks_must_still_post(void) { + uint8_t raw[4096] = {0}; + size_t w = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 1, 2); + w = t_row(&raw, 4096, w, 'K', 8, 200, 4); + assert((t_one(&raw, w) == J_REQ_MISSING)); + w = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 6, 2); + w = t_good_rows(&raw, 4096, w, 100); + w = t_row(&raw, 4096, w, 'K', 8, 200, 4); + assert((t_one(&raw, w) == J_MERGE)); +} + +void test_rule_8_a_rerun_in_the_queue_supersedes_the_failure_it_replaces(void) { + uint8_t raw[4096] = {0}; + size_t w = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 6, 2); + w = t_good_rows(&raw, 4096, w, 100); + w = t_row(&raw, 4096, w, 'K', 1, 50, 1); + assert((t_one(&raw, w) == J_MERGE)); + w = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 6, 2); + w = t_good_rows(&raw, 4096, w, 100); + w = t_row(&raw, 4096, w, 'K', 1, 500, 1); + assert((t_one(&raw, w) == J_REQ_RED)); + w = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 6, 2); + w = t_good_rows(&raw, 4096, w, 100); + w = t_row(&raw, 4096, w, 'K', 1, 500, 2); + assert((t_one(&raw, w) == J_PENDING)); +} + +void test_rule_9_a_required_check_that_is_red_blocks_and_is_named(void) { + uint8_t raw[4096] = {0}; + uint8_t out[4096] = {0}; + size_t w = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 5, 2); + w = t_row(&raw, 4096, w, 'K', 0, 100, 0); + w = t_row(&raw, 4096, w, 'K', 1, 101, 1); + w = t_row(&raw, 4096, w, 'K', 2, 102, 0); + w = t_row(&raw, 4096, w, 'K', 3, 103, 0); + w = t_row(&raw, 4096, w, 'K', 7, 104, 0); + assert((t_one(&raw, w) == J_REQ_RED)); + size_t m = merge_plan(&raw, w, &out, 4096); + assert(t_has_named(&out, m, 'W', 5, J_REQ_RED, 1)); + assert((t_lines(&out, m, 'G') == 0)); +} + +void test_rule_9_names_the_first_red_required_check_even_when_it_is_the_first_row(void) { + uint8_t raw[4096] = {0}; + uint8_t out[4096] = {0}; + size_t w = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 5, 2); + w = t_row(&raw, 4096, w, 'K', 0, 100, 1); + w = t_row(&raw, 4096, w, 'K', 1, 101, 0); + w = t_row(&raw, 4096, w, 'K', 2, 102, 0); + w = t_row(&raw, 4096, w, 'K', 3, 103, 0); + w = t_row(&raw, 4096, w, 'K', 7, 104, 0); + assert((t_one(&raw, w) == J_REQ_RED)); + size_t m = merge_plan(&raw, w, &out, 4096); + assert(t_has_named(&out, m, 'W', 5, J_REQ_RED, 0)); + w = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 5, 2); + w = t_row(&raw, 4096, w, 'K', 0, 100, 0); + w = t_row(&raw, 4096, w, 'K', 1, 101, 1); + w = t_row(&raw, 4096, w, 'K', 2, 102, 0); + w = t_row(&raw, 4096, w, 'K', 3, 103, 1); + w = t_row(&raw, 4096, w, 'K', 7, 104, 0); + size_t k = merge_plan(&raw, w, &out, 4096); + assert(t_has_named(&out, k, 'W', 5, J_REQ_RED, 1)); +} + +void test_rule_9_each_of_the_four_required_checks_blocks(void) { + uint8_t raw[4096] = {0}; + uint32_t name = 0; + while ((name < 4)) { + size_t w = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 5, 2); + uint32_t k = 0; + while ((k < 4)) { + if ((k == name)) { + w = t_row(&raw, 4096, w, 'K', k, (100 + k), 1); + } else { + w = t_row(&raw, 4096, w, 'K', k, (100 + k), 0); + } + k += 1; + } + w = t_row(&raw, 4096, w, 'K', 7, 104, 0); + assert((t_one(&raw, w) == J_REQ_RED)); + name += 1; + } +} + +void test_rule_10_a_required_check_that_has_not_posted_is_missing(void) { + uint8_t raw[4096] = {0}; + uint8_t out[4096] = {0}; + size_t w = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 4, 2); + w = t_row(&raw, 4096, w, 'K', 0, 100, 0); + w = t_row(&raw, 4096, w, 'K', 1, 101, 0); + w = t_row(&raw, 4096, w, 'K', 2, 102, 0); + w = t_row(&raw, 4096, w, 'K', 7, 104, 0); + assert((t_one(&raw, w) == J_REQ_MISSING)); + size_t m = merge_plan(&raw, w, &out, 4096); + assert(t_has(&out, m, 'W', 5, J_REQ_MISSING)); +} + +void test_pending_outranks_a_required_red(void) { + uint8_t raw[4096] = {0}; + size_t w = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 7, 2); + w = t_good_rows(&raw, 4096, w, 100); + w = t_row(&raw, 4096, w, 'K', 0, 900, 1); + w = t_row(&raw, 4096, w, 'K', 8, 901, 2); + assert((t_one(&raw, w) == J_PENDING)); +} + +void test_a_commit_status_is_read_by_its_state(void) { + uint8_t raw[4096] = {0}; + size_t w = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 6, 2); + w = t_good_rows(&raw, 4096, w, 100); + w = put_str(&raw, 4096, w, "K\tcodecov/patch\t-\tCONTEXT\tSUCCESS\n", 34); + assert((t_one(&raw, w) == J_MERGE)); + w = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 6, 2); + w = t_good_rows(&raw, 4096, w, 100); + w = put_str(&raw, 4096, w, "K\tcodecov/patch\t-\tCONTEXT\tPENDING\n", 34); + assert((t_one(&raw, w) == J_PENDING)); +} + +void test_rule_12_a_head_commit_younger_than_ten_minutes_waits(void) { + uint8_t raw[4096] = {0}; + uint8_t out[4096] = {0}; + size_t n = t_v(&raw, 4096, false, 0, 0, 0, 0, 0, 1, 180); + assert((t_one(&raw, n) == J_QUIET)); + size_t m = merge_plan(&raw, n, &out, 4096); + assert(t_has(&out, m, 'W', 5, J_QUIET)); + n = t_v(&raw, 4096, false, 0, 0, 0, 0, 0, 1, 599); + assert((t_one(&raw, n) == J_QUIET)); + n = t_v(&raw, 4096, false, 0, 0, 0, 0, 0, 1, 600); + assert((t_one(&raw, n) == J_MERGE)); + n = t_v(&raw, 4096, false, 0, 0, 0, 0, 0, 1, 601); + assert((t_one(&raw, n) == J_MERGE)); + n = t_v(&raw, 4096, false, 0, 0, 0, 0, 0, 1, 0); + assert((t_one(&raw, n) == J_QUIET)); + assert((QUIET_SECS == (QUIET_MIN * 60))); +} + +void test_rule_12_an_age_nobody_could_read_waits(void) { + uint8_t raw[4096] = {0}; + size_t n = t_v(&raw, 4096, false, 0, 0, 0, 0, 0, 1, 9999999); + assert((t_one(&raw, n) == J_QUIET)); +} + +void test_claw_sarif_red_alone_is_inherited_by_definition_and_merges(void) { + uint8_t raw[4096] = {0}; + uint8_t out[4096] = {0}; + size_t n = t_red_pr(&raw, 4096, 0, 5); + assert((t_one(&raw, n) == J_MERGE)); + size_t m = merge_plan(&raw, n, &out, 4096); + assert(t_g(&out, m, 5)); + assert(t_summary(&out, m, 1, 0, 0, 0, 0, 0, 1)); +} + +void test_negative_control_a_corpus_ratchet_red_on_a_green_master_is_the_prs_own(void) { + uint8_t raw[4096] = {0}; + uint8_t out[4096] = {0}; + size_t n = t_red_pr(&raw, 4096, 0, 4); + n = t_row(&raw, 4096, n, 'B', 4, 250, 0); + assert((t_one(&raw, n) == J_RED)); + size_t m = merge_plan(&raw, n, &out, 4096); + assert(t_has_red(&out, m, 5, 4, R_OWN_MERGE)); + assert((t_lines(&out, m, 'G') == 0)); + assert(t_summary(&out, m, 0, 0, 0, 1, 0, 0, 1)); +} + +void test_a_red_that_master_has_no_finished_run_of_is_unknown_and_holds(void) { + uint8_t raw[4096] = {0}; + uint8_t out[4096] = {0}; + size_t n = t_red_pr(&raw, 4096, 0, 4); + assert((t_one(&raw, n) == J_RED)); + size_t m = merge_plan(&raw, n, &out, 4096); + assert(t_has_red(&out, m, 5, 4, R_UNSEEN)); + n = t_row(&raw, 4096, n, 'B', 3, 250, 1); + n = t_row(&raw, 4096, n, 'B', 4, 251, 2); + n = t_row(&raw, 4096, n, 'B', 4, 252, 5); + n = t_row(&raw, 4096, n, 'B', 4, 253, 4); + assert((t_one(&raw, n) == J_RED)); +} + +void test_a_red_on_a_red_master_asks_for_the_failing_log_before_anything_else(void) { + uint8_t raw[4096] = {0}; + uint8_t out[4096] = {0}; + size_t n = t_red_pr(&raw, 4096, 0, 4); + n = t_row(&raw, 4096, n, 'B', 4, 250, 1); + assert((t_one(&raw, n) == J_LOG)); + size_t m = merge_plan(&raw, n, &out, 4096); + assert(t_has_named(&out, m, 'W', 5, J_LOG, 4)); + assert(t_has_log(&out, m, 5, 4, 300)); + assert((t_lines(&out, m, 'L') == 1)); + assert((t_lines(&out, m, 'G') == 0)); + assert(t_summary(&out, m, 0, 0, 1, 0, 0, 0, 1)); +} + +void test_a_red_master_has_too_and_a_log_that_names_nothing_the_pr_changes_is_inherited(void) { + uint8_t raw[4096] = {0}; + uint8_t out[4096] = {0}; + size_t n = t_red_pr(&raw, 4096, 0, 4); + n = t_row(&raw, 4096, n, 'B', 4, 250, 1); + n = t_file(&raw, 4096, n, 5, 0); + n = t_file(&raw, 4096, n, 5, 1); + n = t_named(&raw, 4096, n, 5, 4, 9); + assert((t_one(&raw, n) == J_MERGE)); + size_t m = merge_plan(&raw, n, &out, 4096); + assert(t_g(&out, m, 5)); + assert((t_lines(&out, m, 'L') == 0)); + n = t_red_pr(&raw, 4096, 0, 4); + n = t_row(&raw, 4096, n, 'B', 4, 250, 1); + n = t_file(&raw, 4096, n, 5, 0); + n = t_file(&raw, 4096, n, 5, 1); + n = t_named(&raw, 4096, n, 5, 4, 3); + assert((t_one(&raw, n) == J_MERGE)); +} + +void test_negative_control_the_t27b_ratchet_red_on_a_spec_the_pr_adds_is_the_prs_own_though_master_is_red(void) { + uint8_t raw[4096] = {0}; + uint8_t out[4096] = {0}; + size_t n = t_red_pr(&raw, 4096, 0, 6); + n = t_row(&raw, 4096, n, 'B', 6, 250, 1); + n = t_file(&raw, 4096, n, 5, 2); + n = t_file(&raw, 4096, n, 5, 1); + n = t_named(&raw, 4096, n, 5, 6, 2); + assert((t_one(&raw, n) == J_RED)); + size_t m = merge_plan(&raw, n, &out, 4096); + assert(t_has_red(&out, m, 5, 6, R_OWN_PATH)); + assert((t_lines(&out, m, 'G') == 0)); +} + +void test_an_unread_file_list_is_not_a_clean_log(void) { + uint8_t raw[4096] = {0}; + size_t n = t_red_pr(&raw, 4096, 0, 4); + n = t_row(&raw, 4096, n, 'B', 4, 250, 1); + n = t_file(&raw, 4096, n, 5, 0); + n = t_named(&raw, 4096, n, 5, 4, 9); + assert((t_one(&raw, n) == J_LOG)); + n = t_red_pr(&raw, 4096, 0, 4); + n = t_row(&raw, 4096, n, 'B', 4, 250, 1); + n = t_file(&raw, 4096, n, 5, 0); + n = t_file(&raw, 4096, n, 5, 1); + n = t_named(&raw, 4096, n, 5, 6, 9); + assert((t_one(&raw, n) == J_LOG)); +} + +void test_master_is_the_newest_decisive_run_and_looks_past_the_ones_that_say_nothing(void) { + uint8_t raw[4096] = {0}; + size_t n = t_red_pr(&raw, 4096, 0, 4); + assert((mj_master(&raw, n, T_NAMES[4], 0, 55) == M_UNSEEN)); + uint8_t b[2048] = {0}; + size_t w = t_row(&b, 2048, 0, 'B', 4, 100, 0); + w = t_row(&b, 2048, w, 'B', 4, 200, 1); + w = t_row(&b, 2048, w, 'B', 4, 300, 5); + assert((mj_master(&b, w, T_NAMES[4], 0, 55) == M_RED)); + w = t_row(&b, 2048, w, 'B', 4, 400, 0); + assert((mj_master(&b, w, T_NAMES[4], 0, 55) == M_GREEN)); + w = t_row(&b, 2048, w, 'B', 4, 900, 4); + w = t_row(&b, 2048, w, 'B', 4, 950, 2); + assert((mj_master(&b, w, T_NAMES[4], 0, 55) == M_GREEN)); + w = t_row(&b, 2048, w, 'B', 4, 500, 1); + assert((mj_master(&b, w, T_NAMES[4], 0, 55) == M_RED)); +} + +void test_the_order_of_master_rows_does_not_matter_and_a_tie_reads_green(void) { + uint8_t b[2048] = {0}; + size_t w = t_row(&b, 2048, 0, 'B', 4, 999, 1); + w = t_row(&b, 2048, w, 'B', 4, 1000, 0); + assert((mj_master(&b, w, T_NAMES[4], 0, 55) == M_GREEN)); + w = t_row(&b, 2048, 0, 'B', 4, 1000, 0); + w = t_row(&b, 2048, w, 'B', 4, 999, 1); + assert((mj_master(&b, w, T_NAMES[4], 0, 55) == M_GREEN)); + w = t_row(&b, 2048, 0, 'B', 4, 1000, 1); + w = t_row(&b, 2048, w, 'B', 4, 1000, 0); + assert((mj_master(&b, w, T_NAMES[4], 0, 55) == M_GREEN)); + w = t_row(&b, 2048, 0, 'B', 4, 1000, 0); + w = t_row(&b, 2048, w, 'B', 4, 1000, 1); + assert((mj_master(&b, w, T_NAMES[4], 0, 55) == M_GREEN)); + w = t_row(&b, 2048, 0, 'B', 4, 99, 0); + w = t_row(&b, 2048, w, 'B', 4, 1000, 1); + assert((mj_master(&b, w, T_NAMES[4], 0, 55) == M_RED)); +} + +void test_two_reds_are_judged_one_by_one_and_an_own_red_outranks_a_wanted_log(void) { + uint8_t raw[4096] = {0}; + uint8_t out[4096] = {0}; + size_t n = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 7, 2); + n = t_good_rows(&raw, 4096, n, 100); + n = t_row(&raw, 4096, n, 'K', 4, 300, 1); + n = t_row(&raw, 4096, n, 'K', 8, 301, 1); + n = t_row(&raw, 4096, n, 'B', 4, 250, 1); + n = t_file(&raw, 4096, n, 5, 0); + n = t_file(&raw, 4096, n, 5, 1); + n = t_named(&raw, 4096, n, 5, 4, 9); + assert((t_one(&raw, n) == J_RED)); + n = t_row(&raw, 4096, n, 'B', 8, 251, 1); + n = t_named(&raw, 4096, n, 5, 8, 9); + assert((t_one(&raw, n) == J_MERGE)); + n = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 7, 2); + n = t_good_rows(&raw, 4096, n, 100); + n = t_row(&raw, 4096, n, 'K', 4, 300, 1); + n = t_row(&raw, 4096, n, 'K', 8, 301, 1); + n = t_row(&raw, 4096, n, 'B', 4, 250, 1); + n = t_row(&raw, 4096, n, 'B', 8, 251, 1); + assert((t_one(&raw, n) == J_LOG)); + size_t m = merge_plan(&raw, n, &out, 4096); + assert(t_has_log(&out, m, 5, 4, 300)); + assert(t_has_log(&out, m, 5, 8, 301)); + assert((t_lines(&out, m, 'L') == 2)); + n = t_row(&raw, 4096, n, 'B', 4, 260, 0); + assert((t_one(&raw, n) == J_RED)); + size_t k = merge_plan(&raw, n, &out, 4096); + assert((t_lines(&out, k, 'L') == 0)); + assert(t_has_red(&out, k, 5, 4, R_OWN_MERGE)); +} + +void test_a_cancelled_check_that_is_not_required_is_a_red_like_any_other(void) { + uint8_t raw[4096] = {0}; + size_t n = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 6, 2); + n = t_good_rows(&raw, 4096, n, 100); + n = t_row(&raw, 4096, n, 'K', 7, 300, 5); + assert((t_one(&raw, n) == J_RED)); +} + +void test_a_red_that_the_pr_re_ran_green_is_not_a_red(void) { + uint8_t raw[4096] = {0}; + size_t n = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 7, 2); + n = t_good_rows(&raw, 4096, n, 100); + n = t_row(&raw, 4096, n, 'K', 4, 300, 1); + n = t_row(&raw, 4096, n, 'K', 4, 400, 0); + assert((t_one(&raw, n) == J_MERGE)); +} + +void test_the_reasons_rank_required_before_other_reds_and_reds_before_the_quiet_time(void) { + uint8_t raw[4096] = {0}; + size_t n = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 7, 2); + n = t_good_rows(&raw, 4096, n, 100); + n = t_row(&raw, 4096, n, 'K', 0, 900, 1); + n = t_row(&raw, 4096, n, 'K', 4, 901, 1); + n = t_row(&raw, 4096, n, 'B', 4, 250, 0); + assert((t_one(&raw, n) == J_REQ_RED)); + n = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 30, 6, 2); + n = t_good_rows(&raw, 4096, n, 100); + n = t_row(&raw, 4096, n, 'K', 4, 300, 1); + n = t_row(&raw, 4096, n, 'B', 4, 250, 0); + assert((t_one(&raw, n) == J_RED)); + n = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 30, 6, 2); + n = t_good_rows(&raw, 4096, n, 100); + n = t_row(&raw, 4096, n, 'K', 4, 300, 1); + n = t_row(&raw, 4096, n, 'B', 4, 250, 1); + assert((t_one(&raw, n) == J_LOG)); +} + +void test_master_rows_may_stand_before_the_pull_request_or_inside_another_block(void) { + uint8_t raw[8192] = {0}; + uint8_t out[8192] = {0}; + size_t n = t_row(&raw, 8192, 0, 'B', 4, 250, 1); + n = t_red_pr(&raw, 8192, n, 4); + n = t_file(&raw, 8192, n, 5, 0); + n = t_file(&raw, 8192, n, 5, 1); + n = t_named(&raw, 8192, n, 5, 4, 9); + size_t m = merge_plan(&raw, n, &out, 8192); + assert(t_g(&out, m, 5)); + assert(t_summary(&out, m, 1, 0, 0, 0, 0, 0, 1)); + n = t_green(&raw, 8192, 0, 4); + n = t_row(&raw, 8192, n, 'B', 4, 250, 1); + n = t_red_pr(&raw, 8192, n, 4); + n = t_file(&raw, 8192, n, 5, 0); + n = t_file(&raw, 8192, n, 5, 1); + n = t_named(&raw, 8192, n, 5, 4, 9); + m = merge_plan(&raw, n, &out, 8192); + assert(t_g(&out, m, 4)); + assert(t_g(&out, m, 5)); + assert(t_summary(&out, m, 2, 0, 0, 0, 0, 0, 2)); +} + +void test_at_most_five_are_merged_per_run_lowest_number_first(void) { + uint8_t raw[8192] = {0}; + uint8_t out[8192] = {0}; + uint32_t nums[8] = {0}; + size_t n = t_green(&raw, 8192, 0, 30); + n = t_green(&raw, 8192, n, 12); + n = t_green(&raw, 8192, n, 45); + n = t_green(&raw, 8192, n, 3); + n = t_green(&raw, 8192, n, 9); + n = t_green(&raw, 8192, n, 21); + n = t_green(&raw, 8192, n, 18); + size_t m = merge_plan(&raw, n, &out, 8192); + assert((t_gnums(&out, m, &nums) == 5)); + assert((nums[0] == 3)); + assert((nums[1] == 9)); + assert((nums[2] == 12)); + assert((nums[3] == 18)); + assert((nums[4] == 21)); + assert(t_has(&out, m, 'W', 30, J_CAPPED)); + assert(t_has(&out, m, 'W', 45, J_CAPPED)); + assert(t_summary(&out, m, 5, 2, 0, 0, 0, 0, 7)); + assert((MERGE_CAP == 5)); +} + +void test_a_pull_request_that_waits_or_is_held_does_not_use_up_the_cap(void) { + uint8_t raw[8192] = {0}; + uint8_t out[8192] = {0}; + uint32_t nums[8] = {0}; + size_t n = t_p(&raw, 8192, 0, 1, false, 1, 0, 0, 0, 0, 1, 1, 3600, 5, 2); + n = t_good_rows(&raw, 8192, n, 100); + n = t_p(&raw, 8192, n, 2, true, 0, 0, 0, 0, 0, 1, 2, 3600, 5, 2); + n = t_good_rows(&raw, 8192, n, 100); + n = t_green(&raw, 8192, n, 13); + n = t_green(&raw, 8192, n, 14); + n = t_green(&raw, 8192, n, 15); + n = t_green(&raw, 8192, n, 16); + n = t_green(&raw, 8192, n, 17); + n = t_green(&raw, 8192, n, 18); + size_t m = merge_plan(&raw, n, &out, 8192); + assert((t_gnums(&out, m, &nums) == 5)); + assert((nums[0] == 13)); + assert((nums[4] == 17)); + assert(t_has(&out, m, 'W', 18, J_CAPPED)); + assert(t_has(&out, m, 'H', 1, J_HELD)); + assert(t_has(&out, m, 'H', 2, J_DRAFT)); + assert(t_summary(&out, m, 5, 1, 0, 2, 0, 0, 8)); +} + +void test_the_summary_counts_every_class(void) { + uint8_t raw[16384] = {0}; + uint8_t out[16384] = {0}; + size_t n = t_green(&raw, 16384, 0, 20); + n = t_p(&raw, 16384, n, 5, false, 1, 0, 0, 0, 0, 1, 5, 3600, 5, 2); + n = t_good_rows(&raw, 16384, n, 100); + n = t_p(&raw, 16384, n, 21, false, 0, 1, 0, 0, 0, 1, 1, 3600, 5, 2); + n = t_good_rows(&raw, 16384, n, 100); + n = t_p(&raw, 16384, n, 22, false, 0, 0, 0, 1, 1, 1, 2, 3600, 5, 2); + n = t_good_rows(&raw, 16384, n, 100); + n = t_p(&raw, 16384, n, 23, false, 0, 0, 0, 0, 0, 1, 3, 3600, 0, 2); + n = t_green(&raw, 16384, n, 24); + n = put_str(&raw, 16384, n, "P\t99\n", 5); + size_t m = merge_plan(&raw, n, &out, 16384); + assert(t_has(&out, m, 'H', 5, J_HELD)); + assert(t_has(&out, m, 'N', 21, J_BEE)); + assert(t_has(&out, m, 'W', 22, J_CONFLICT)); + assert(t_has(&out, m, 'W', 23, J_PENDING)); + assert(t_has(&out, m, 'W', 99, J_BAD)); + assert(t_g(&out, m, 20)); + assert(t_g(&out, m, 24)); + assert(t_summary(&out, m, 2, 0, 2, 1, 1, 1, 7)); +} + +void test_the_output_lists_merges_first_then_the_rest_in_input_order(void) { + uint8_t raw[8192] = {0}; + uint8_t out[8192] = {0}; + size_t n = t_p(&raw, 8192, 0, 8, true, 0, 0, 0, 0, 0, 1, 8, 3600, 5, 2); + n = t_good_rows(&raw, 8192, n, 100); + n = t_green(&raw, 8192, n, 4); + n = t_p(&raw, 8192, n, 2, false, 1, 0, 0, 0, 0, 1, 2, 3600, 5, 2); + n = t_good_rows(&raw, 8192, n, 100); + size_t m = merge_plan(&raw, n, &out, 8192); + assert((out[0] == 'G')); + assert((out[1] == '\t')); + assert((out[2] == '4')); + size_t i = 0; + while ((out[i] != '\n')) { + i += 1; + } + assert((out[(i + 1)] == 'H')); + assert((out[(i + 3)] == '8')); + size_t j = (i + 1); + while ((out[j] != '\n')) { + j += 1; + } + assert((out[(j + 1)] == 'H')); + assert((out[(j + 3)] == '2')); + assert((out[(m - 1)] == '\n')); +} + +void test_the_lane_plan_is_the_merge_plan_or_the_refresh_plan_and_nothing_else(void) { + uint8_t raw[4096] = {0}; + uint8_t a[4096] = {0}; + uint8_t b[4096] = {0}; + uint8_t c[64] = {0}; + size_t n = t_green(&raw, 4096, 0, 7); + size_t m = merge_plan(&raw, n, &a, 4096); + assert((lane_plan(0, &raw, n, &b, 4096, 0) == m)); + assert(t_g(&b, m, 7)); + size_t r = refresh_plan("", 0, &a, 4096, 0); + assert((lane_plan(1, "", 0, &b, 4096, 0) == r)); + assert(buf_has(&b, r, "M\tclaw-refresh: 0 to update", 27)); + assert((lane_plan(1, "", 0, &b, 4096, -1) == refresh_plan("", 0, &a, 4096, -1))); + assert(buf_has(&b, 66, "the Actions queue size could not be read", 40)); + c[0] = 'z'; + assert((lane_plan(2, &raw, n, &c, 64, 0) == 0)); + assert((c[0] == 'z')); +} + +void test_refresh_records_in_the_same_stream_are_skipped_not_counted(void) { + uint8_t raw[8192] = {0}; + uint8_t out[8192] = {0}; + size_t r = put_str(&raw, 8192, 0, "R\t5\t5aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n", 45); + size_t n = t_green(&raw, 8192, r, 5); + n = put_str(&raw, 8192, n, "R\t6\tx\n", 6); + n = t_green(&raw, 8192, n, 6); + size_t m = merge_plan(&raw, n, &out, 8192); + assert(t_summary(&out, m, 2, 0, 0, 0, 0, 0, 2)); + assert(t_g(&out, m, 5)); + assert(t_g(&out, m, 6)); + n = t_green(&raw, 8192, 0, 5); + n = put_str(&raw, 8192, n, "R\t5\tx\n", 6); + assert((t_one(&raw, n) == J_MERGE)); +} + +void test_an_unreadable_p_line_is_never_merged_and_is_counted(void) { + uint8_t raw[4096] = {0}; + uint8_t out[4096] = {0}; + size_t n = t_green(&raw, 4096, 0, 7); + assert((t_one(&raw, n) == J_MERGE)); + raw[(t_tab(&raw, n, 1) + 1)] = 'A'; + assert((t_one(&raw, n) == J_BAD)); + n = t_green(&raw, 4096, 0, 7); + raw[(t_tab(&raw, n, 2) + 1)] = 'x'; + assert((t_one(&raw, n) == J_BAD)); + n = t_green(&raw, 4096, 0, 7); + raw[t_tab(&raw, n, 12)] = ' '; + assert((t_one(&raw, n) == J_BAD)); + n = t_green(&raw, 4096, 0, 7); + raw[(t_tab(&raw, n, 8) + 1)] = 'x'; + assert((t_one(&raw, n) == J_BAD)); + n = t_green(&raw, 4096, 0, 7); + raw[(t_tab(&raw, n, 11) + 1)] = 'x'; + assert((t_one(&raw, n) == J_BAD)); + n = t_green(&raw, 4096, 0, 7); + raw[(t_tab(&raw, n, 12) + 1)] = 'x'; + assert((t_one(&raw, n) == J_BAD)); + n = t_green(&raw, 4096, 0, 7); + raw[(t_tab(&raw, n, 9) + 1)] = 'G'; + assert((t_one(&raw, n) == J_BAD)); + n = t_green(&raw, 4096, 0, 7); + raw[2] = '0'; + assert((t_one(&raw, n) == J_BAD)); + raw[2] = 'x'; + size_t m = merge_plan(&raw, n, &out, 4096); + assert((t_lines(&out, m, 'G') == 0)); + assert(t_summary(&out, m, 0, 0, 0, 0, 0, 1, 1)); + n = t_green(&raw, 4096, 0, 7); + raw[(t_tab(&raw, n, 4) + 1)] = '\t'; + assert((t_one(&raw, n) == J_BAD)); +} + +void test_a_pull_request_number_seen_twice_is_merged_at_most_once(void) { + uint8_t raw[8192] = {0}; + uint8_t out[8192] = {0}; + size_t n = t_green(&raw, 8192, 0, 7); + n = t_green(&raw, 8192, n, 7); + n = t_green(&raw, 8192, n, 8); + size_t m = merge_plan(&raw, n, &out, 8192); + assert(t_summary(&out, m, 2, 0, 0, 0, 0, 1, 3)); + assert((t_lines(&out, m, 'G') == 2)); + assert(t_has(&out, m, 'W', 7, J_BAD)); + assert(t_g(&out, m, 8)); +} + +void test_a_block_with_a_line_of_another_tag_is_never_merged(void) { + uint8_t raw[4096] = {0}; + size_t n = t_green(&raw, 4096, 0, 7); + n = put_str(&raw, 4096, n, "X\tsurprise\n", 10); + assert((t_one(&raw, n) == J_BAD)); + n = t_green(&raw, 4096, 0, 7); + n = put_str(&raw, 4096, n, "no tab here\n", 12); + assert((t_one(&raw, n) == J_BAD)); + n = t_green(&raw, 4096, 0, 7); + n = t_file(&raw, 4096, n, 7, 0); + n = t_named(&raw, 4096, n, 7, 4, 9); + n = t_row(&raw, 4096, n, 'B', 4, 250, 1); + assert((t_one(&raw, n) == J_MERGE)); +} + +void test_lines_before_the_first_pull_request_are_unreadable_unless_they_are_master_rows(void) { + uint8_t raw[4096] = {0}; + uint8_t out[4096] = {0}; + size_t n = put_str(&raw, 4096, 0, "K\tstray\t1\tCOMPLETED\tSUCCESS\n", 28); + n = t_green(&raw, 4096, n, 7); + size_t m = merge_plan(&raw, n, &out, 4096); + assert(t_summary(&out, m, 1, 0, 0, 0, 0, 1, 2)); + n = t_row(&raw, 4096, 0, 'B', 4, 250, 1); + n = t_green(&raw, 4096, n, 7); + m = merge_plan(&raw, n, &out, 4096); + assert(t_summary(&out, m, 1, 0, 0, 0, 0, 0, 1)); +} + +void test_more_pull_requests_than_the_lane_reads_are_counted_unreadable_not_dropped(void) { + uint8_t raw[8192] = {0}; + uint8_t out[40000] = {0}; + size_t n = 0; + uint32_t k = 0; + while ((k < 513)) { + n = put_str(&raw, 8192, n, "P\t", 2); + n = put_count(&raw, 8192, n, (k + 1)); + n = put_byte(&raw, 8192, n, '\n'); + k += 1; + } + size_t m = merge_plan(&raw, n, &out, 40000); + assert(t_summary(&out, m, 0, 0, 0, 0, 0, 513, 513)); + assert((t_lines(&out, m, 'G') == 0)); + assert((MJ_MAX_PRS == 512)); +} + +void test_an_empty_stream_is_an_empty_plan(void) { + uint8_t out[256] = {0}; + size_t m = merge_plan("", 0, &out, 256); + assert(t_summary(&out, m, 0, 0, 0, 0, 0, 0, 0)); + assert((t_lines(&out, m, 'G') == 0)); +} + +void test_a_clipped_answer_reports_what_it_needs_and_writes_nothing_past_the_cap(void) { + uint8_t raw[4096] = {0}; + uint8_t big[4096] = {0}; + uint8_t small[32] = {0}; + size_t n = t_green(&raw, 4096, 0, 7); + size_t need = merge_plan(&raw, n, &big, 4096); + size_t i = 0; + while ((i < 32)) { + small[i] = 'z'; + i += 1; + } + assert((merge_plan(&raw, n, &small, 10) == need)); + assert((small[0] == 'G')); + assert((small[9] == big[9])); + assert((small[10] == 'z')); + assert((small[31] == 'z')); + assert((need > 10)); +} + +void test_every_reason_is_one_line_and_its_length_is_the_text_length(void) { + size_t d = 0; + while ((d < J_COUNT)) { + size_t k = 0; + while ((k < J_LENS[d])) { + assert((J_TEXTS[d][k] != '\t')); + if (((k + 1) < J_LENS[d])) { + assert((J_TEXTS[d][k] != '\n')); + } + k += 1; + } + if (((d == ((size_t)(J_REQ_RED))) || (d == ((size_t)(J_LOG))))) { + assert((J_TEXTS[d][(J_LENS[d] - 1)] == ' ')); + assert((J_TEXTS[d][(J_LENS[d] - 2)] == ':')); + } else if ((d == ((size_t)(J_RED)))) { + assert((J_TEXTS[d][(J_LENS[d] - 1)] == ' ')); + assert((J_TEXTS[d][(J_LENS[d] - 2)] == 'k')); + } else { + assert((J_TEXTS[d][(J_LENS[d] - 1)] == '\n')); + assert((J_TEXTS[d][(J_LENS[d] - 2)] != '.')); + } + d += 1; + } + assert((J_COUNT == 17)); +} + +void test_the_class_of_each_decision_is_g_w_h_or_n(void) { + uint32_t d = 0; + while ((d < 17)) { + assert((J_CLASS[((size_t)(d))] == t_class(d))); + d += 1; + } + assert((J_CLASS[((size_t)(J_HELD))] == 'H')); + assert((J_CLASS[((size_t)(J_BEE))] == 'N')); + assert((J_CLASS[((size_t)(J_CAPPED))] == 'W')); + assert((J_CLASS[((size_t)(J_RED))] == 'H')); + assert((J_CLASS[((size_t)(J_BAD))] == 'W')); +} + +void test_the_summary_pieces_have_the_lengths_they_claim(void) { + size_t k = 0; + while ((k < 6)) { + assert((MJ_SUM_TEXTS[k][0] == ' ')); + assert((MJ_SUM_TEXTS[k][(MJ_SUM_LENS[k] - 1)] == ' ')); + assert((MJ_SUM_TEXTS[k][(MJ_SUM_LENS[k] - 2)] != ' ')); + k += 1; + } + assert((MJ_SUM_HEAD[(MJ_SUM_HEAD_LEN - 1)] == ' ')); + assert((MJ_SUM_HEAD[(MJ_SUM_HEAD_LEN - 2)] == ':')); + assert((MJ_SUM_TAIL[(MJ_SUM_TAIL_LEN - 1)] == '\n')); + assert((MJ_SUM_TAIL[(MJ_SUM_TAIL_LEN - 2)] == 'n')); + size_t j = 0; + while ((j < 9)) { + assert((T_NAMES[j][(T_NLENS[j] - 1)] != '\t')); + j += 1; + } + assert((T_NAMES[4][54] == ')')); + assert((T_PATHS[2][27] == '7')); +} + +void test_the_tag_of_a_line_is_its_first_byte_when_a_tab_follows(void) { + assert((mj_tag("P\tx", 0, 3) == 'P')); + assert((mj_tag("P\t", 0, 2) == 'P')); + assert((mj_tag("P", 0, 1) == 0)); + assert((mj_tag("Px\t", 0, 3) == 0)); + assert((mj_tag("", 0, 0) == 0)); + assert((mj_tag("aK\tx", 1, 4) == 'K')); + assert((mj_tag("aK\tx", 0, 4) == 0)); +} + +void test_a_prefix_must_be_whole_and_at_the_start(void) { + assert(mj_starts("queen-77", 0, 8, "queen-", 6)); + assert(mj_starts("queen-", 0, 6, "queen-", 6)); + assert((mj_starts("queen", 0, 5, "queen-", 6) == false)); + assert((mj_starts("queenX77", 0, 8, "queen-", 6) == false)); + assert((mj_starts("xqueen-77", 0, 9, "queen-", 6) == false)); + assert(mj_starts("xqueen-77", 1, 9, "queen-", 6)); + assert(mj_starts("ab", 0, 2, "", 0)); + assert((mj_starts("queen-77", 0, 5, "queen-", 6) == false)); +} + +void test_two_ranges_are_the_same_only_when_every_byte_is(void) { + assert(mj_same("abc", 0, 3, "abc", 0, 3)); + assert(mj_same("xabc", 1, 4, "abcy", 0, 3)); + assert((mj_same("abc", 0, 3, "abd", 0, 3) == false)); + assert((mj_same("abc", 0, 3, "xbc", 0, 3) == false)); + assert((mj_same("abc", 0, 3, "ab", 0, 2) == false)); + assert((mj_same("abc", 0, 2, "abc", 0, 3) == false)); + assert(mj_same("abc", 1, 1, "xyz", 2, 2)); +} + +void test_a_check_name_ends_at_the_next_tab_or_the_end_of_the_line(void) { + assert((mj_name_end("K\tname\trest", 0) == 6)); + assert((mj_name_end("K\tname\n", 0) == 6)); + assert((mj_name_end("xK\tab\tc", 1) == 5)); + assert((mj_name_end("K\t\tc", 0) == 2)); +} + +void test_an_empty_head_or_base_branch_is_unreadable(void) { + uint8_t raw[4096] = {0}; + size_t n = t_p(&raw, 4096, 0, 5, false, 0, 9, 0, 0, 0, 1, 5, 3600, 0, 0); + assert((t_one(&raw, n) == J_BAD)); + n = t_p(&raw, 4096, 0, 5, false, 0, 0, 9, 0, 0, 1, 5, 3600, 0, 0); + assert((t_one(&raw, n) == J_BAD)); + n = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 0, 0); + assert((t_one(&raw, n) == J_PENDING)); +} + +void test_the_changed_files_are_the_f_lines_of_that_pull_request_and_a_bad_line_is_not_counted(void) { + uint8_t raw[4096] = {0}; + uint8_t buf[256] = {0}; + size_t len[1] = {0}; + size_t n = t_file(&raw, 4096, 0, 5, 0); + n = t_file(&raw, 4096, n, 5, 1); + n = t_file(&raw, 4096, n, 6, 3); + assert((mj_files(&raw, n, 5, &buf, 256, &len) == 2)); + assert((len[0] == 47)); + assert((buf[0] == 's')); + assert((buf[26] == '\n')); + assert((buf[27] == 'd')); + assert((buf[46] == '\n')); + assert((mj_files(&raw, n, 6, &buf, 256, &len) == 1)); + assert((len[0] == 19)); + assert((mj_files(&raw, n, 56, &buf, 256, &len) == 0)); + assert((mj_files(&raw, n, 7, &buf, 256, &len) == 0)); + n = put_str(&raw, 4096, n, "F\t5\t\n", 5); + n = put_str(&raw, 4096, n, "F\t5\ta\tb\n", 8); + n = put_str(&raw, 4096, n, "K\t5\tx\n", 6); + n = put_str(&raw, 4096, n, "F\tx\ta\n", 6); + assert((mj_files(&raw, n, 5, &buf, 256, &len) == 2)); + assert((len[0] == 47)); + assert((mj_files(&raw, n, 5, &buf, 30, &len) == 2)); + assert((len[0] == 47)); + assert((mj_files(&raw, 0, 5, &buf, 256, &len) == 0)); + assert((len[0] == 0)); +} + +void test_the_named_paths_are_the_o_lines_of_that_pull_request_and_check(void) { + uint8_t raw[4096] = {0}; + uint8_t buf[256] = {0}; + size_t len[1] = {0}; + size_t n = t_named(&raw, 4096, 0, 5, 4, 0); + n = t_named(&raw, 4096, n, 5, 4, 9); + n = t_named(&raw, 4096, n, 5, 6, 2); + n = t_named(&raw, 4096, n, 6, 4, 3); + assert((mj_named(&raw, n, 5, T_NAMES[4], 0, 55, &buf, 256, &len) == 2)); + assert((len[0] == 27)); + assert((buf[0] == 's')); + assert((buf[26] == '\n')); + assert((mj_named(&raw, n, 5, T_NAMES[6], 0, 31, &buf, 256, &len) == 1)); + assert((len[0] == 29)); + assert((mj_named(&raw, n, 5, T_NAMES[7], 0, 23, &buf, 256, &len) == 0)); + assert((len[0] == 0)); + assert((mj_named(&raw, n, 6, T_NAMES[4], 0, 55, &buf, 256, &len) == 1)); + assert((len[0] == 19)); + assert((mj_named(&raw, n, 7, T_NAMES[4], 0, 55, &buf, 256, &len) == 0)); + assert((mj_named(&raw, n, 5, T_NAMES[4], 0, 54, &buf, 256, &len) == 0)); + n = put_str(&raw, 4096, n, "O\t5\tx\n", 6); + assert((mj_named(&raw, n, 5, T_NAMES[7], 0, 23, &buf, 256, &len) == 0)); +} + +void test_f_and_o_lines_find_their_pull_request_wherever_they_stand(void) { + uint8_t raw[8192] = {0}; + uint8_t out[8192] = {0}; + size_t n = t_red_pr(&raw, 8192, 0, 4); + n = t_row(&raw, 8192, n, 'B', 4, 250, 1); + n = t_green(&raw, 8192, n, 6); + size_t m = merge_plan(&raw, n, &out, 8192); + assert(t_has_named(&out, m, 'W', 5, J_LOG, 4)); + assert(t_has_log(&out, m, 5, 4, 300)); + assert(t_g(&out, m, 6)); + n = t_file(&raw, 8192, n, 5, 0); + n = t_file(&raw, 8192, n, 5, 1); + n = t_named(&raw, 8192, n, 5, 4, 9); + m = merge_plan(&raw, n, &out, 8192); + assert(t_g(&out, m, 5)); + assert(t_g(&out, m, 6)); + assert((t_lines(&out, m, 'L') == 0)); + assert(t_summary(&out, m, 2, 0, 0, 0, 0, 0, 2)); + n = t_red_pr(&raw, 8192, 0, 4); + n = t_row(&raw, 8192, n, 'B', 4, 250, 1); + n = t_file(&raw, 8192, n, 6, 0); + n = t_file(&raw, 8192, n, 6, 1); + n = t_named(&raw, 8192, n, 6, 4, 9); + assert((t_one(&raw, n) == J_LOG)); + m = merge_plan(&raw, n, &out, 8192); + assert(t_has_log(&out, m, 5, 4, 300)); +} + +void test_the_kind_of_a_red_row_is_read_from_its_job_name(void) { + uint8_t raw[4096] = {0}; + uint8_t rs[4096] = {0}; + size_t pos[128] = {0}; + size_t len[1] = {0}; + size_t n = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 9, 2); + n = t_row(&raw, 4096, n, 'K', 7, 100, 0); + n = t_row(&raw, 4096, n, 'K', 5, 101, 1); + n = t_row(&raw, 4096, n, 'K', 4, 102, 1); + n = t_row(&raw, 4096, n, 'K', 0, 103, 1); + n = put_str(&raw, 4096, n, "K\tOther/claw-sarif\t500\tCOMPLETED\tFAILURE\n", 41); + n = put_str(&raw, 4096, n, "K\tGate Topology/claw-sarif-2\t501\tCOMPLETED\tFAILURE\n", 51); + n = put_str(&raw, 4096, n, "K\tclaw-sarif\t502\tCOMPLETED\tFAILURE\n", 35); + n = put_str(&raw, 4096, n, "K\tx/validate\t503\tCOMPLETED\tFAILURE\n", 35); + n = put_str(&raw, 4096, n, "K\tx/validate2\t504\tCOMPLETED\tFAILURE\n", 36); + assert((mj_rows(&raw, 0, n, &rs, 4096, &pos, &len) == 9)); + assert((mj_first_kind(&rs, len[0], KIND_NONE) == 0)); + assert((mj_first_kind(&rs, len[0], KIND_SARIF) == 1)); + assert((mj_first_kind(&rs, len[0], KIND_OTHER) == 2)); + assert((mj_first_kind(&rs, len[0], KIND_REQ) == 3)); + assert((mj_first_kind(&rs, 0, KIND_SARIF) == -1)); + assert((rs[0] == 'S')); + assert((pos[1] == (pos[0] + 48))); + assert((raw[pos[2]] == 'K')); + assert((((mj_name_end(&raw, pos[2]) - pos[2]) - 2) == 55)); +} + +void test_a_superseded_or_unreadable_or_passed_row_is_no_red(void) { + uint8_t rs[512] = {0}; + size_t n = put_str(&rs, 512, 0, "a/job\t5\tCOMPLETED\tFAILURE\n", 26); + n = put_str(&rs, 512, n, "a/job\t9\tCOMPLETED\tSUCCESS\n", 26); + assert((mj_first_kind(&rs, n, KIND_OTHER) == -1)); + n = put_str(&rs, 512, 0, "a/job\t5\tCOMPLETED\tFAILURE\n", 26); + assert((mj_first_kind(&rs, n, KIND_OTHER) == 0)); + n = put_str(&rs, 512, 0, "a/job\tx\tCOMPLETED\tFAILURE\n", 26); + assert((mj_first_kind(&rs, n, KIND_OTHER) == -1)); + n = put_str(&rs, 512, 0, "a/job\t5\tQUEUED\t-\n", 17); + assert((mj_first_kind(&rs, n, KIND_OTHER) == -1)); + n = put_str(&rs, 512, 0, "ctx\t-\tCONTEXT\tFAILURE\n", 22); + assert((mj_first_kind(&rs, n, KIND_OTHER) == 0)); +} + +void test_only_the_job_named_claw_sarif_is_the_known_infra_red_and_only_four_jobs_are_required(void) { + uint8_t rs[256] = {0}; + size_t n = put_str(&rs, 256, 0, "Other/claw-sarif\t500\tCOMPLETED\tFAILURE\n", 39); + assert((t_kind(&rs, n) == KIND_SARIF)); + n = put_str(&rs, 256, 0, "claw-sarif\t502\tCOMPLETED\tFAILURE\n", 33); + assert((t_kind(&rs, n) == KIND_SARIF)); + n = put_str(&rs, 256, 0, "Gate Topology/claw-sarif-2\t501\tCOMPLETED\tFAILURE\n", 49); + assert((t_kind(&rs, n) == KIND_OTHER)); + n = put_str(&rs, 256, 0, "Gate Topology/xclaw-sarif\t501\tCOMPLETED\tFAILURE\n", 48); + assert((t_kind(&rs, n) == KIND_OTHER)); + n = put_str(&rs, 256, 0, "x/validate\t503\tCOMPLETED\tFAILURE\n", 33); + assert((t_kind(&rs, n) == KIND_REQ)); + n = put_str(&rs, 256, 0, "validate\t503\tCOMPLETED\tFAILURE\n", 31); + assert((t_kind(&rs, n) == KIND_REQ)); + n = put_str(&rs, 256, 0, "x/validate2\t504\tCOMPLETED\tFAILURE\n", 34); + assert((t_kind(&rs, n) == KIND_OTHER)); + n = put_str(&rs, 256, 0, "x/own-language\t504\tCOMPLETED\tFAILURE\n", 37); + assert((t_kind(&rs, n) == KIND_REQ)); + n = put_str(&rs, 256, 0, "x/parse-ratchet\t504\tCOMPLETED\tFAILURE\n", 38); + assert((t_kind(&rs, n) == KIND_REQ)); + n = put_str(&rs, 256, 0, "x/check-linked-issue\t504\tCOMPLETED\tFAILURE\n", 43); + assert((t_kind(&rs, n) == KIND_REQ)); + n = put_str(&rs, 256, 0, "x/validate\t503\tCOMPLETED\tSUCCESS\n", 33); + assert((t_kind(&rs, n) == KIND_NONE)); +} + + +/* ------------------------------------------------------- + Test runner (compile with -DT27_TEST_MAIN to execute) + ------------------------------------------------------- */ + +#ifdef T27_TEST_MAIN +#include +int main(void) { + test_a_pull_request_that_passes_every_rule_is_merged_pinned_to_its_head_sha(); + test_rule_1_the_held_label_holds_whatever_else_is_true(); + test_rule_1_only_the_whole_label_holds(); + test_rule_2_a_draft_is_never_touched(); + test_rule_3_only_master_is_merged_into(); + test_rule_4_a_bee_branch_is_not_mine(); + test_rule_5_a_conflict_waits_and_the_lane_never_resolves_it(); + test_rule_5_a_mergeability_github_has_not_computed_waits(); + test_rule_6_no_closing_reference_waits(); + test_the_first_matching_rule_names_the_reason(); + test_rule_7_a_rollup_that_is_not_the_head_commits_is_not_read(); + test_rule_7_a_rollup_cut_short_is_not_read(); + test_rule_7_a_row_that_cannot_be_read_is_not_read(); + test_rule_7_more_rows_than_the_lane_reads_are_not_read(); + test_rule_8_a_check_still_running_waits(); + test_rule_8_no_check_posted_is_not_green(); + test_rule_8_skipped_is_a_pass_but_the_required_checks_must_still_post(); + test_rule_8_a_rerun_in_the_queue_supersedes_the_failure_it_replaces(); + test_rule_9_a_required_check_that_is_red_blocks_and_is_named(); + test_rule_9_names_the_first_red_required_check_even_when_it_is_the_first_row(); + test_rule_9_each_of_the_four_required_checks_blocks(); + test_rule_10_a_required_check_that_has_not_posted_is_missing(); + test_pending_outranks_a_required_red(); + test_a_commit_status_is_read_by_its_state(); + test_rule_12_a_head_commit_younger_than_ten_minutes_waits(); + test_rule_12_an_age_nobody_could_read_waits(); + test_claw_sarif_red_alone_is_inherited_by_definition_and_merges(); + test_negative_control_a_corpus_ratchet_red_on_a_green_master_is_the_prs_own(); + test_a_red_that_master_has_no_finished_run_of_is_unknown_and_holds(); + test_a_red_on_a_red_master_asks_for_the_failing_log_before_anything_else(); + test_a_red_master_has_too_and_a_log_that_names_nothing_the_pr_changes_is_inherited(); + test_negative_control_the_t27b_ratchet_red_on_a_spec_the_pr_adds_is_the_prs_own_though_master_is_red(); + test_an_unread_file_list_is_not_a_clean_log(); + test_master_is_the_newest_decisive_run_and_looks_past_the_ones_that_say_nothing(); + test_the_order_of_master_rows_does_not_matter_and_a_tie_reads_green(); + test_two_reds_are_judged_one_by_one_and_an_own_red_outranks_a_wanted_log(); + test_a_cancelled_check_that_is_not_required_is_a_red_like_any_other(); + test_a_red_that_the_pr_re_ran_green_is_not_a_red(); + test_the_reasons_rank_required_before_other_reds_and_reds_before_the_quiet_time(); + test_master_rows_may_stand_before_the_pull_request_or_inside_another_block(); + test_at_most_five_are_merged_per_run_lowest_number_first(); + test_a_pull_request_that_waits_or_is_held_does_not_use_up_the_cap(); + test_the_summary_counts_every_class(); + test_the_output_lists_merges_first_then_the_rest_in_input_order(); + test_the_lane_plan_is_the_merge_plan_or_the_refresh_plan_and_nothing_else(); + test_refresh_records_in_the_same_stream_are_skipped_not_counted(); + test_an_unreadable_p_line_is_never_merged_and_is_counted(); + test_a_pull_request_number_seen_twice_is_merged_at_most_once(); + test_a_block_with_a_line_of_another_tag_is_never_merged(); + test_lines_before_the_first_pull_request_are_unreadable_unless_they_are_master_rows(); + test_more_pull_requests_than_the_lane_reads_are_counted_unreadable_not_dropped(); + test_an_empty_stream_is_an_empty_plan(); + test_a_clipped_answer_reports_what_it_needs_and_writes_nothing_past_the_cap(); + test_every_reason_is_one_line_and_its_length_is_the_text_length(); + test_the_class_of_each_decision_is_g_w_h_or_n(); + test_the_summary_pieces_have_the_lengths_they_claim(); + test_the_tag_of_a_line_is_its_first_byte_when_a_tab_follows(); + test_a_prefix_must_be_whole_and_at_the_start(); + test_two_ranges_are_the_same_only_when_every_byte_is(); + test_a_check_name_ends_at_the_next_tab_or_the_end_of_the_line(); + test_an_empty_head_or_base_branch_is_unreadable(); + test_the_changed_files_are_the_f_lines_of_that_pull_request_and_a_bad_line_is_not_counted(); + test_the_named_paths_are_the_o_lines_of_that_pull_request_and_check(); + test_f_and_o_lines_find_their_pull_request_wherever_they_stand(); + test_the_kind_of_a_red_row_is_read_from_its_job_name(); + test_a_superseded_or_unreadable_or_passed_row_is_no_red(); + test_only_the_job_named_claw_sarif_is_the_known_infra_red_and_only_four_jobs_are_required(); + printf("All %d tests passed.\n", 67); + return 0; +} +#endif /* T27_TEST_MAIN */ + +#endif /* CLAWMERGEJUDGE_H */ diff --git a/specs/claw/merge_judge.t27 b/specs/claw/merge_judge.t27 new file mode 100644 index 0000000000..8bccd0c4fd --- /dev/null +++ b/specs/claw/merge_judge.t27 @@ -0,0 +1,2091 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/claw/merge_judge.t27 -- which open pull requests TRI CLAW merges, and why it leaves the rest alone. +; Owner, 2026-10-19 (translated): set up TRI CLAW to do this autonomously, morning to night, 24/7. "This" +; is what the sweep session did by hand all night: merge every pull request that passes the rules, +; refresh the ones that fall behind, leave the rest alone and say why. Strictness: required + inherited. +; -- +; This module decides; it moves nothing. The claw-merge job in .github/workflows/gate-topology.yml lists the +; open pull requests, feeds this module the facts, and applies the `G` lines with one pinned merge call. The +; job holds no decision. Every skip below is decided here, in a unit test, from facts only. +; -- +; The rules, for one open pull request, first match wins. The letter is the output tag of the line (H hold: +; a person decides; W wait: look again next run; N not mine: another lane merges it): +; 1 held the label no-auto-merge is on it H a person or a gate has claimed it +; 2 draft isDraft H drafts are the hold convention +; 3 base the base branch is not master H the lane merges into master only +; 4 bee the head branch starts `queen-` N only auto-merge-ready-prs.yml +; merges a bee pull request, after the reviewer bee (owner rule 2026-10-02) +; 5 conflict CONFLICTING or DIRTY W refresh.t27 lists it; the lane +; never resolves a conflict. A mergeability GitHub has not computed is W too: next run +; 6 no_close GitHub's closingIssuesReferences is empty W L1 TRACEABILITY +; 7 unread the rollup is not the head commit's, is cut short, or has a row that cannot be read W +; 8 pending a check is still waiting, or none has posted W check_state / check_latest +; 9 req_red a REQUIRED check is red (validate, check-linked-issue, parse-ratchet, own-language) W +; 10 req_missing a required check has not posted W +; 11 red every other red check must be INHERITED (red_origin.t27). A red that is the PR's own, or +; unknown, is H with red_origin's reason line. claw-sarif is inherited by definition: the +; TRI CLAW App lacks security_events:write (#10501), so it fails before it reads any code. +; A red whose master run is red needs the failing log: the module asks for it (L line) and +; the pull request waits (W, log) until it has the answer +; 12 quiet the head commit is younger than QUIET_MIN minutes W the author may still be pushing +; 13 merge otherwise: squash, pinned to the head sha that was judged G +; Only G merges. At most MERGE_CAP of the mergeable ones are kept, lowest number first (the oldest); the rest +; are W (capped) and go on the next run. +; -- +; The input, one stream on stdin, one line per fact, TAB fields, the first field a one-letter tag. A pull +; request is its P line and the K lines after it up to the next P line: +; P number, head sha, isDraft, labels joined by commas, head branch, base branch, mergeable, +; mergeStateStatus, closing references (count), sha of the commit whose rollup follows, seconds since +; the head commit was made (`-` when unread), rollup items (count), changed files (count) +; K name, run, status, conclusion one rollup item, check_latest.t27's row. name is workflow/job; run is +; the check run's id (any id that grows with time; for an Actions job it is also the id of its log); +; a commit status carries `-`, CONTEXT, state +; F number, path a file that pull request changes +; O number, check name, path a path the failing log of that pull request's check names (`-` when +; it names none) +; B name, run, status, conclusion one rollup item of a recent master commit, same shape as K +; B, F and O lines may stand anywhere, F and O naming their pull request by number. R lines are refresh.t27's +; records in the same listing: this module skips them. A fact the glue could not read is left out, never +; invented: that is what makes an unread fact a W and never a G. +; The output, one line per fact: +; G TAB number TAB head sha merge this one (at most MERGE_CAP, lowest number first) +; W TAB number TAB reason wait H TAB number TAB reason hold N TAB number TAB reason not mine +; L TAB number TAB check TAB id the failing log of this check (id: the K line's run) decides a red +; M TAB text one line for the step summary +; -- +; The glue, so the workflow file carries no prose. The claw-merge job lists open pull requests in pages of 15 (a page of +; 100 with 50 labels each came back as HTML) and tries a failed page three times, keeping nothing of a failed try; +; GitHub computes mergeability lazily after a push, so it lists once, waits 30 s and lists again. B lines come from the +; rollups of master's last twenty commits (a failed read leaves none, and then no red is inherited; a check of a workflow +; master did not run in those commits, a path-filtered one, has no word, and its red is unknown). L lines are answered +; by one files call per pull request (F lines) and one job-log call per wanted check (O lines: the F paths the log +; contains; a log that cannot be read leaves no O line, so the red keeps waiting). Refresh (refresh.t27) runs only after +; a push and with the App token: every update-branch starts about 26 workflows, the queue limit is its, and behind_by is +; the one fact the listing lacks (the ruleset is not strict, so no pull request is ever reported BEHIND); a pull request +; merged in this run is not refreshed. The merge is TRI CLAW's token (contents + pull_requests write, so master's push +; workflows start and its checks stay fresh for the B lines), else github.token, whose merges start no workflow. A push +; run cancels the older push run (the workflow's own group): the lane then re-plans, one merge per run at worst. +; -- +; Whose red is it: red_origin.t27's red_reason runs on the lists this module builds from F and O lines: the +; PR's files, the paths the log names, no moved list. master is the newest DECISIVE row of the same check +; among the B lines (COMPLETED with SUCCESS or FAILURE; a cancelled, skipped or timed-out run is looked past, +; the way specs/queen/merger_gate.t27 look_back does), the largest run id first; the same run with both a +; SUCCESS and a FAILURE reads green, the safe side. A named path the PR changes makes the red the PR's even +; when master is red too; master green makes any red the PR's; master with no decisive row is unknown, and an +; unknown red holds. The paths are what the glue read: F is checked against the changed-files count of the +; P line, and a count that does not match is an unread list. +; -- +; Fail closed: a P line that is not fourteen fields, a number that is not a positive integer or is seen twice, +; a sha that is not 40 lowercase hex digits, a flag that is not `true` or `false`, a count that is not a +; number, a block that holds a line with another tag, more than MJ_MAX_ROWS rollup items, or a record past +; MJ_MAX_PRS is never merged; it is counted as unreadable. +; The honest limits. The module does not read the failure set of a red job, only whether its log names a file +; the PR changes: a PR that adds a new failure in a job master also fails, in a file it does not change, reads +; inherited. Reviews are not read (the label is the hold convention). A bot pull request without a closing +; reference waits for one. The age is the head commit's own date: a commit made long ago and pushed now looks +; old, which is why the merge is pinned to the judged sha and checks that have not finished wait. Two pull +; requests merged in one run were each green alone; the repository ruleset is not strict, as for every merger. +; ASCII only (L3). phi^2 + 1/phi^2 = 3 | TRINITY + +module ClawMergeJudge; + +use claw::sarif; +use claw::text; +use claw::refresh; +use claw::queue_cost; +use claw::check_state; +use claw::check_latest; +use claw::required_red; +use claw::red_origin; + +/// Pull requests one run reads; more are counted as unreadable, never silently dropped. +pub const MJ_MAX_PRS : usize = 512; +/// The most pull requests one run merges, lowest number first. +pub const MERGE_CAP : u32 = 5; +/// The head commit must be at least this many minutes old (QUIET_SECS seconds). +pub const QUIET_MIN : i32 = 10; +pub const QUIET_SECS : i32 = 600; +/// Rollup items one pull request may carry, and the bytes of their table. +pub const MJ_MAX_ROWS : u32 = 128; +pub const MJ_TABLE_CAP : usize = 16384; +/// Bytes of the file list and of the named list a red check is judged with. +pub const MJ_LIST_CAP : usize = 16384; +/// Fields of a P line, the tag included. +pub const MJ_PF : usize = 14; + +/// Decisions. J_MERGE is "every rule passed"; the cap turns the extra ones into J_CAPPED. +pub const J_MERGE : u8 = 0; +pub const J_CAPPED : u8 = 1; +pub const J_HELD : u8 = 2; +pub const J_DRAFT : u8 = 3; +pub const J_BASE : u8 = 4; +pub const J_BEE : u8 = 5; +pub const J_CONFLICT : u8 = 6; +pub const J_UNKNOWN : u8 = 7; +pub const J_NO_CLOSE : u8 = 8; +pub const J_UNREAD : u8 = 9; +pub const J_PENDING : u8 = 10; +pub const J_REQ_RED : u8 = 11; +pub const J_REQ_MISSING : u8 = 12; +pub const J_LOG : u8 = 13; +pub const J_RED : u8 = 14; +pub const J_QUIET : u8 = 15; +pub const J_BAD : u8 = 16; +pub const J_COUNT : usize = 17; + +/// The output tag of each decision, in decision order: G merge, W wait, H hold, N not mine. +pub const J_CLASS : str = "GWHHHNWWWWWWWWHWW"; + +/// The reason of each decision, with its byte length (a test pins every length). A text that ends in +/// `: ` takes the check name and a newline after it; the others end in a newline. +pub const J_TEXTS : [17]str = [ + "every rule passed\n", + "mergeable, but the per-run cap on merges is reached and the oldest go first: it goes next run\n", + "the label no-auto-merge is on it: a person or a gate has claimed it\n", + "draft: drafts are never touched\n", + "its base branch is not master: the lane merges into master only\n", + "bee pull request (queen- branch): only auto-merge-ready-prs.yml merges it, after the reviewer bee\n", + "conflicts with its base branch: refresh.t27 lists it, the lane never resolves conflicts\n", + "GitHub has not computed its mergeability yet: next run\n", + "no closing reference (L1 TRACEABILITY): add Closes #N to the body\n", + "the check rollup is not the head commit's, was cut short, or has a row that cannot be read\n", + "checks are still waiting, or none has posted yet\n", + "a required check is red: ", + "a required check has not posted yet\n", + "the failing log is not read yet, so whose red it is is not known: ", + "red check ", + "the head commit is younger than 10 minutes, or its age is unread: the author may still be pushing\n", + "the record cannot be read: fail closed\n" +]; +pub const J_LENS : [17]usize = [ + 18, 94, 68, 32, 64, 98, 88, 55, 66, 91, 49, 25, 36, 66, 10, 98, 39 +]; + +/// Summary pieces, each with its byte length (the tests pin the lengths). +pub const MJ_SUM_HEAD : str = "M\tclaw-merge: "; +pub const MJ_SUM_HEAD_LEN : usize = 14; +pub const MJ_SUM_TEXTS : [6]str = [" to merge, ", " over the cap, ", " waiting, ", " held, ", " not mine, ", " unreadable of "]; +pub const MJ_SUM_LENS : [6]usize = [11, 15, 10, 7, 11, 15]; +pub const MJ_SUM_TAIL : str = " open\n"; +pub const MJ_SUM_TAIL_LEN : usize = 6; + +/// What a red check's row is, for the rules that read reds. +pub const KIND_NONE : u8 = 0; +pub const KIND_REQ : u8 = 1; +pub const KIND_SARIF : u8 = 2; +pub const KIND_OTHER : u8 = 3; + +// ---- the small readers ---- + +/// The tag of the line raw[ls..le): its first byte when the second is a TAB, else 0. +pub fn mj_tag(raw : []const u8, ls : usize, le : usize) -> u8 { + if (le < ls + 2) { return 0; } + if (raw[ls + 1] != '\t') { return 0; } + return raw[ls]; +} + +/// raw[s..e) begins with the p_len bytes of p. +pub fn mj_starts(raw : []const u8, s : usize, e : usize, p : str, p_len : usize) -> bool { + if (e < s + p_len) { return false; } + var i : usize = 0; + while (i < p_len) { + if (raw[s + i] != p[i]) { return false; } + i += 1; + } + return true; +} + +/// a[a0..a1) and b[b0..b1) hold the same bytes. +pub fn mj_same(a : []const u8, a0 : usize, a1 : usize, b : []const u8, b0 : usize, b1 : usize) -> bool { + if (a1 - a0 != b1 - b0) { return false; } + var i : usize = 0; + while (a0 + i < a1) { + if (a[a0 + i] != b[b0 + i]) { return false; } + i += 1; + } + return true; +} + +/// The end of the name that starts at raw[p + 2], the byte after a K line's tag and TAB. +pub fn mj_name_end(raw : []const u8, p : usize) -> usize { + var e : usize = p + 2; + while (raw[e] != '\t' and raw[e] != '\n') { e += 1; } + return e; +} + +/// Whether the 14 fields of a P line (f holds their bounds) are all readable. +pub fn mj_p_ok(raw : []const u8, f : []const usize) -> bool { + if (parse_num(raw, f[2], f[3]) < 1) { return false; } + if (rf_sha_ok(raw, f[4], f[5]) == false) { return false; } + if (rf_flag(raw, f[6], f[7]) == T_BAD) { return false; } + if (f[11] == f[10] or f[13] == f[12]) { return false; } + if (parse_num(raw, f[18], f[19]) < 0) { return false; } + if (rf_sha_ok(raw, f[20], f[21]) == false) { return false; } + if (parse_num(raw, f[24], f[25]) < 0) { return false; } + if (parse_num(raw, f[26], f[27]) < 0) { return false; } + return true; +} + +/// Every line of the block raw[bs..be) after its P line is a K, F, O, B or R line (R: a refresh.t27 record, not ours). +pub fn mj_block_clean(raw : []const u8, bs : usize, be : usize) -> bool { + var ls : usize = qc_line_end(raw, be, bs) + 1; + while (ls < be) { + const le : usize = qc_line_end(raw, be, ls); + if (le > ls) { + const t : u8 = mj_tag(raw, ls, le); + if (t != 'K' and t != 'F' and t != 'O' and t != 'B' and t != 'R') { return false; } + } + ls = le + 1; + } + return true; +} + +// ---- the rollup of one pull request ---- + +/// The K lines of the block raw[bs..be) as check_latest rows (the tag cut off) in rs, one per line; pos[k] +/// holds the start in raw of the k-th K line. Returns the number of K lines, which may exceed MJ_MAX_ROWS. +/// len[0] is the bytes the table needs; the table is clipped at cap when that is larger. +pub fn mj_rows(raw : []const u8, bs : usize, be : usize, rs : []u8, cap : usize, pos : []usize, len : []usize) -> u32 { + var w : usize = 0; + var rows : u32 = 0; + var ls : usize = bs; + while (ls < be) { + const le : usize = qc_line_end(raw, be, ls); + if (le > ls) { + if (mj_tag(raw, ls, le) == 'K') { + if (rows < MJ_MAX_ROWS) { pos[rows as usize] = ls; } + w = qc_put_range(rs, cap, w, raw, ls + 2, le); + w = put_str(rs, cap, w, "\n", 1); + rows += 1; + } + } + ls = le + 1; + } + len[0] = w; + return rows; +} + +/// The kind of the row rs[ls..le): KIND_REQ for a live red required check, KIND_SARIF for a live red +/// claw-sarif, KIND_OTHER for any other live red, KIND_NONE for every other row. +pub fn mj_row_kind(rs : []const u8, rn : usize, ls : usize, le : usize) -> u8 { + var f : [8]usize = undefined; + var c : [4]usize = undefined; + if (qc_split(rs, ls, le, &f, 4) == false) { return KIND_NONE; } + if (row_ok(rs, &f) == false) { return KIND_NONE; } + if (is_superseded(rs, rn, ls, &f)) { return KIND_NONE; } + c[0] = f[4]; + c[1] = f[5]; + c[2] = f[6]; + c[3] = f[7]; + if (check_class(rs, &c) != K_FAIL) { return KIND_NONE; } + if (required_index(rs, f[0], f[1]) < RR_REQUIRED) { return KIND_REQ; } + if (qc_range_is(rs, job_start(rs, f[0], f[1]), f[1], "claw-sarif", 10)) { return KIND_SARIF; } + return KIND_OTHER; +} + +/// The row ordinal (0-based) of the first row of kind `want`, or -1. +pub fn mj_first_kind(rs : []const u8, rn : usize, want : u8) -> i32 { + var row : i32 = 0; + var ls : usize = 0; + while (ls < rn) { + const le : usize = qc_line_end(rs, rn, ls); + if (le > ls) { + if (mj_row_kind(rs, rn, ls, le) == want) { return row; } + row += 1; + } + ls = le + 1; + } + return -1; +} + +// ---- whose red is it ---- + +/// Master's word on the check named by nm[ns..ne): the newest decisive B line of that name, by run id. +pub fn mj_master(raw : []const u8, n : usize, nm : []const u8, ns : usize, ne : usize) -> u8 { + var best : u8 = M_UNSEEN; + var b0 : usize = 0; + var b1 : usize = 0; + var f : [10]usize = undefined; + var ls : usize = 0; + while (ls < n) { + const le : usize = qc_line_end(raw, n, ls); + if (le > ls) { + if (mj_tag(raw, ls, le) == 'B' and qc_split(raw, ls, le, &f, 5)) { + if (mj_same(raw, f[2], f[3], nm, ns, ne) and run_ok(raw, f[4], f[5]) and qc_range_is(raw, f[6], f[7], "COMPLETED", 9)) { + var v : u8 = M_UNSEEN; + if (qc_range_is(raw, f[8], f[9], "SUCCESS", 7)) { v = M_GREEN; } + if (qc_range_is(raw, f[8], f[9], "FAILURE", 7)) { v = M_RED; } + if (v != M_UNSEEN) { + if (best == M_UNSEEN or run_gt(raw, f[4], f[5], b0, b1)) { + best = v; + b0 = f[4]; + b1 = f[5]; + } else if (v == M_GREEN and qc_range_eq(raw, f[4], f[5], b0, b1)) { + best = M_GREEN; + } + } + } + } + } + ls = le + 1; + } + return best; +} + +/// The F lines of pull request num as a newline-separated path list in buf. Returns how many F lines it has; +/// len[0] is the bytes the list needs (clipped at cap when larger). +pub fn mj_files(raw : []const u8, n : usize, num : u32, buf : []u8, cap : usize, len : []usize) -> u32 { + var f : [6]usize = undefined; + var w : usize = 0; + var cnt : u32 = 0; + var ls : usize = 0; + while (ls < n) { + const le : usize = qc_line_end(raw, n, ls); + if (le > ls) { + if (mj_tag(raw, ls, le) == 'F' and qc_split(raw, ls, le, &f, 3)) { + if (parse_num(raw, f[2], f[3]) == (num as i32) and f[5] > f[4]) { + w = qc_put_range(buf, cap, w, raw, f[4], f[5]); + w = put_str(buf, cap, w, "\n", 1); + cnt += 1; + } + } + } + ls = le + 1; + } + len[0] = w; + return cnt; +} + +/// The O lines of pull request num that name the check nm[ns..ne), their paths as a newline-separated list in +/// buf (a `-` path adds nothing). Returns how many O lines name the check; len[0] is the bytes needed. +pub fn mj_named(raw : []const u8, n : usize, num : u32, nm : []const u8, ns : usize, ne : usize, buf : []u8, cap : usize, len : []usize) -> u32 { + var f : [8]usize = undefined; + var w : usize = 0; + var cnt : u32 = 0; + var ls : usize = 0; + while (ls < n) { + const le : usize = qc_line_end(raw, n, ls); + if (le > ls) { + if (mj_tag(raw, ls, le) == 'O' and qc_split(raw, ls, le, &f, 4)) { + if (parse_num(raw, f[2], f[3]) == (num as i32) and mj_same(raw, f[4], f[5], nm, ns, ne)) { + cnt += 1; + if (qc_range_is(raw, f[6], f[7], "-", 1) == false and f[7] > f[6]) { + w = qc_put_range(buf, cap, w, raw, f[6], f[7]); + w = put_str(buf, cap, w, "\n", 1); + } + } + } + } + ls = le + 1; + } + len[0] = w; + return cnt; +} + +/// RS_* : what one non-required red check comes to. +pub const RS_INHERITED : u8 = 0; +pub const RS_OWN : u8 = 1; +pub const RS_LOG : u8 = 2; + +/// The state of one live red check, named nm[ns..ne), of pull request num, which changes files_total files. +/// r[0] gets red_origin's reason code when the state is RS_INHERITED or RS_OWN. +pub fn mj_red_state(raw : []const u8, n : usize, num : u32, nm : []const u8, ns : usize, ne : usize, files_total : i32, r : []u8) -> u8 { + const master : u8 = mj_master(raw, n, nm, ns, ne); + if (master != M_RED) { + r[0] = red_reason("", 0, "", 0, "", 0, master); + if (verdict_of(r[0]) == V_INHERITED) { return RS_INHERITED; } + return RS_OWN; + } + var fl : [16384]u8 = undefined; + var nl : [16384]u8 = undefined; + var flen : [1]usize = undefined; + var nlen : [1]usize = undefined; + const fc : u32 = mj_files(raw, n, num, &fl, MJ_LIST_CAP, &flen); + const nc : u32 = mj_named(raw, n, num, nm, ns, ne, &nl, MJ_LIST_CAP, &nlen); + if (nc == 0 or (fc as i32) != files_total or flen[0] > MJ_LIST_CAP or nlen[0] > MJ_LIST_CAP) { return RS_LOG; } + r[0] = red_reason(&fl, flen[0], &nl, nlen[0], "", 0, master); + if (verdict_of(r[0]) == V_INHERITED) { return RS_INHERITED; } + return RS_OWN; +} + +/// Rule 11 over the table rs[0..rn) of pull request num. res[0] is J_MERGE when every live red is +/// inherited, J_RED when one is the PR's own or unknown (res[1] = start in raw of its K line, res[2] = the +/// reason code), else J_LOG (res[1] = its K line). With emit, writes into out at w one L line for every red +/// whose log is wanted; returns the cursor. +pub fn mj_reds(raw : []const u8, n : usize, rs : []const u8, rn : usize, pos : []const usize, num : u32, files_total : i32, emit : bool, out : []u8, cap : usize, w0 : usize, res : []usize) -> usize { + var f : [8]usize = undefined; + var r : [1]u8 = undefined; + var w : usize = w0; + res[0] = J_MERGE as usize; + res[1] = 0; + res[2] = 0; + var own : bool = false; + var logged : bool = false; + var row : usize = 0; + var ls : usize = 0; + while (ls < rn) { + const le : usize = qc_line_end(rs, rn, ls); + if (le > ls) { + if (mj_row_kind(rs, rn, ls, le) == KIND_OTHER and qc_split(rs, ls, le, &f, 4)) { + const st : u8 = mj_red_state(raw, n, num, rs, f[0], f[1], files_total, &r); + if (st == RS_OWN and own == false) { + own = true; + res[0] = J_RED as usize; + res[1] = pos[row]; + res[2] = r[0] as usize; + } + if (st == RS_LOG) { + if (own == false and logged == false) { + logged = true; + res[0] = J_LOG as usize; + res[1] = pos[row]; + } + if (emit) { + w = put_str(out, cap, w, "L\t", 2); + w = put_count(out, cap, w, num); + w = put_byte(out, cap, w, '\t'); + w = qc_put_range(out, cap, w, rs, f[0], f[1]); + w = put_byte(out, cap, w, '\t'); + w = qc_put_range(out, cap, w, rs, f[2], f[3]); + w = put_byte(out, cap, w, '\n'); + } + } + } + row += 1; + } + ls = le + 1; + } + return w; +} + +// ---- the decision for one pull request ---- + +/// The decision for the block raw[bs..be) whose P line has the 14 split fields f. det[0] is the start in raw +/// of the K line a reason names (J_REQ_RED, J_LOG, J_RED), det[1] red_origin's reason for J_RED. +pub fn mj_judge(raw : []const u8, n : usize, bs : usize, be : usize, f : []const usize, det : []usize) -> u8 { + det[0] = 0; + det[1] = 0; + if (mj_p_ok(raw, f) == false) { return J_BAD; } + if (mj_block_clean(raw, bs, be) == false) { return J_BAD; } + if (rf_label_held(raw, f[8], f[9])) { return J_HELD; } + if (rf_flag(raw, f[6], f[7]) == T_TRUE) { return J_DRAFT; } + if (qc_range_is(raw, f[12], f[13], "master", 6) == false) { return J_BASE; } + if (mj_starts(raw, f[10], f[11], "queen-", 6)) { return J_BEE; } + const mc : u8 = rf_merge_class(raw, f[14], f[15], f[16], f[17]); + if (mc == M_CONFLICT) { return J_CONFLICT; } + if (mc == M_UNKNOWN) { return J_UNKNOWN; } + if (parse_num(raw, f[18], f[19]) == 0) { return J_NO_CLOSE; } + var rs : [16384]u8 = undefined; + var pos : [128]usize = undefined; + var ln : [1]usize = undefined; + const rows : u32 = mj_rows(raw, bs, be, &rs, MJ_TABLE_CAP, &pos, &ln); + if (rows > MJ_MAX_ROWS or ln[0] > MJ_TABLE_CAP) { return J_UNREAD; } + if ((rows as i32) != parse_num(raw, f[24], f[25])) { return J_UNREAD; } + if (qc_range_eq(raw, f[4], f[5], f[20], f[21]) == false) { return J_UNREAD; } + var st : [5]u32 = undefined; + latest_counts(&rs, ln[0], &st); + if (st[K_BAD] > 0) { return J_UNREAD; } + if (st[K_WAIT] > 0 or st[K_PASS] + st[K_FAIL] == 0) { return J_PENDING; } + var rq : [5]u32 = undefined; + required_counts(&rs, ln[0], &rq); + if (rq[K_FAIL] > 0) { + const k : i32 = mj_first_kind(&rs, ln[0], KIND_REQ); + if (k >= 0) { det[0] = pos[k as usize]; } + return J_REQ_RED; + } + if (rq[RR_MISSING] > 0) { return J_REQ_MISSING; } + var res : [3]usize = undefined; + var sink : [1]u8 = undefined; + mj_reds(raw, n, &rs, ln[0], &pos, parse_num(raw, f[2], f[3]) as u32, parse_num(raw, f[26], f[27]), false, &sink, 0, 0, &res); + if (res[0] != (J_MERGE as usize)) { + det[0] = res[1]; + det[1] = res[2]; + return res[0] as u8; + } + const age : i32 = parse_num(raw, f[22], f[23]); + if (age < QUIET_SECS) { return J_QUIET; } + return J_MERGE; +} + +// ---- the plan ---- + +fn mj_put_name(out : []u8, cap : usize, w : usize, raw : []const u8, p : usize) -> usize { + return qc_put_range(out, cap, w, raw, p + 2, mj_name_end(raw, p)); +} + +/// The reason text of decision d into out at w, with the check name and red_origin's line where the +/// decision names a check. +fn mj_put_reason(out : []u8, cap : usize, w : usize, d : u8, raw : []const u8, p : usize, why : u8) -> usize { + var x : usize = put_str(out, cap, w, J_TEXTS[d as usize], J_LENS[d as usize]); + if (d == J_REQ_RED or d == J_LOG) { + x = mj_put_name(out, cap, x, raw, p); + x = put_byte(out, cap, x, '\n'); + } + if (d == J_RED) { + x = mj_put_name(out, cap, x, raw, p); + x = put_str(out, cap, x, ": ", 2); + if (verdict_of(why) == V_OWN) { x = put_str(out, cap, x, "own: ", 5); } else { x = put_str(out, cap, x, "unknown: ", 9); } + x = put_str(out, cap, x, WHY_TEXTS[why as usize], WHY_LENS[why as usize]); + } + return x; +} + +/// The answer for the facts in raw[0..n), into out. Returns the bytes it needs; when that exceeds cap the +/// buffer holds a clipped answer that must not be acted on. +pub fn merge_plan(raw : []const u8, n : usize, out : []u8, cap : usize) -> usize { + var bs : [512]usize = undefined; + var be : [512]usize = undefined; + var dec : [512]u8 = undefined; + var num : [512]u32 = undefined; + var rk : [512]u32 = undefined; + var dp : [512]usize = undefined; + var dy : [512]u8 = undefined; + var sp : [512]usize = undefined; + var ft : [512]i32 = undefined; + var f : [28]usize = undefined; + var det : [2]usize = undefined; + var cnt : usize = 0; + var extra : u32 = 0; + var ls : usize = 0; + while (ls < n) { + const le : usize = qc_line_end(raw, n, ls); + if (le > ls) { + const t : u8 = mj_tag(raw, ls, le); + if (t == 'P') { + if (cnt < MJ_MAX_PRS) { + bs[cnt] = ls; + cnt += 1; + } else { + extra += 1; + } + } else if (t != 'B' and t != 'R' and t != 'F' and t != 'O' and cnt == 0) { + extra += 1; + } + } + ls = le + 1; + } + var i : usize = 0; + while (i < cnt) { + if (i + 1 < cnt) { be[i] = bs[i + 1]; } else { be[i] = n; } + dec[i] = J_BAD; + num[i] = 0; + dp[i] = 0; + dy[i] = 0; + sp[i] = 0; + ft[i] = -1; + const le : usize = qc_line_end(raw, n, bs[i]); + var fe : usize = bs[i] + 2; + while (fe < le and raw[fe] != '\t') { fe += 1; } + const pn : i32 = parse_num(raw, bs[i] + 2, fe); + if (pn > 0) { num[i] = pn as u32; } + if (pn > 0 and qc_split(raw, bs[i], le, &f, MJ_PF)) { + sp[i] = f[4]; + ft[i] = parse_num(raw, f[26], f[27]); + dec[i] = mj_judge(raw, n, bs[i], be[i], &f, &det); + dp[i] = det[0]; + dy[i] = det[1] as u8; + } + var j : usize = 0; + while (j < i) { + if (num[i] != 0 and num[j] == num[i]) { dec[i] = J_BAD; } + j += 1; + } + i += 1; + } + i = 0; + while (i < cnt) { + rk[i] = 0; + if (dec[i] == J_MERGE) { + var j : usize = 0; + while (j < cnt) { + if (dec[j] == J_MERGE and num[j] < num[i]) { rk[i] += 1; } + j += 1; + } + } + i += 1; + } + i = 0; + while (i < cnt) { + if (dec[i] == J_MERGE and rk[i] >= MERGE_CAP) { dec[i] = J_CAPPED; } + i += 1; + } + var w : usize = 0; + var r : u32 = 0; + while (r < MERGE_CAP) { + i = 0; + while (i < cnt) { + if (dec[i] == J_MERGE and rk[i] == r) { + w = put_str(out, cap, w, "G\t", 2); + w = put_count(out, cap, w, num[i]); + w = put_byte(out, cap, w, '\t'); + w = qc_put_range(out, cap, w, raw, sp[i], sp[i] + 40); + w = put_byte(out, cap, w, '\n'); + } + i += 1; + } + r += 1; + } + var st : [17]u32 = undefined; + var c : usize = 0; + while (c < J_COUNT) { + st[c] = 0; + c += 1; + } + i = 0; + while (i < cnt) { + st[dec[i] as usize] += 1; + if (dec[i] != J_MERGE) { + w = put_byte(out, cap, w, J_CLASS[dec[i] as usize]); + w = put_byte(out, cap, w, '\t'); + if (num[i] == 0) { w = put_byte(out, cap, w, '-'); } else { w = put_count(out, cap, w, num[i]); } + w = put_byte(out, cap, w, '\t'); + w = mj_put_reason(out, cap, w, dec[i], raw, dp[i], dy[i]); + } + i += 1; + } + i = 0; + while (i < cnt) { + if (dec[i] == J_LOG) { + var rs : [16384]u8 = undefined; + var pos : [128]usize = undefined; + var ln : [1]usize = undefined; + var res : [3]usize = undefined; + mj_rows(raw, bs[i], be[i], &rs, MJ_TABLE_CAP, &pos, &ln); + w = mj_reds(raw, n, &rs, ln[0], &pos, num[i], ft[i], true, out, cap, w, &res); + } + i += 1; + } + const bad : u32 = st[J_BAD as usize] + extra; + const waiting : u32 = st[J_UNKNOWN as usize] + st[J_CONFLICT as usize] + st[J_NO_CLOSE as usize] + st[J_UNREAD as usize] + st[J_PENDING as usize] + st[J_REQ_RED as usize] + st[J_REQ_MISSING as usize] + st[J_LOG as usize] + st[J_QUIET as usize]; + w = put_str(out, cap, w, MJ_SUM_HEAD, MJ_SUM_HEAD_LEN); + w = put_count(out, cap, w, st[J_MERGE as usize]); + w = put_str(out, cap, w, MJ_SUM_TEXTS[0], MJ_SUM_LENS[0]); + w = put_count(out, cap, w, st[J_CAPPED as usize]); + w = put_str(out, cap, w, MJ_SUM_TEXTS[1], MJ_SUM_LENS[1]); + w = put_count(out, cap, w, waiting); + w = put_str(out, cap, w, MJ_SUM_TEXTS[2], MJ_SUM_LENS[2]); + w = put_count(out, cap, w, st[J_HELD as usize] + st[J_DRAFT as usize] + st[J_BASE as usize] + st[J_RED as usize]); + w = put_str(out, cap, w, MJ_SUM_TEXTS[3], MJ_SUM_LENS[3]); + w = put_count(out, cap, w, st[J_BEE as usize]); + w = put_str(out, cap, w, MJ_SUM_TEXTS[4], MJ_SUM_LENS[4]); + w = put_count(out, cap, w, bad); + w = put_str(out, cap, w, MJ_SUM_TEXTS[5], MJ_SUM_LENS[5]); + w = put_count(out, cap, w, (cnt as u32) + extra); + return put_str(out, cap, w, MJ_SUM_TAIL, MJ_SUM_TAIL_LEN); +} + +/// The lane's two plans behind one entry, so the job lowers one file: kind 0 is merge_plan, kind 1 is +/// refresh_plan with `queued` workflow runs queued (negative: unread). Any other kind answers nothing. +pub fn lane_plan(kind : u32, raw : []const u8, n : usize, out : []u8, cap : usize, queued : i32) -> usize { + if (kind == 0) { return merge_plan(raw, n, out, cap); } + if (kind == 1) { return refresh_plan(raw, n, out, cap, queued); } + return 0; +} + +// ---- test helpers: build the stream from small codes, so a test states the case and not the bytes ---- + +pub const T_NAMES : [9]str = [ + "Schema Validation/validate", "Issue Gate/check-linked-issue", "Spec Parse Ratchet/parse-ratchet", + "Own Language/own-language", "Corpus Ratchet/Corpus ratchet (expected-failure ledger)", + "Gate Topology/claw-sarif", "t27b native/t27b-native-ratchet", "Spec Guards/spec-guards", "Seal Coverage/coverage" +]; +pub const T_NLENS : [9]usize = [26, 29, 32, 25, 55, 24, 31, 23, 22]; +pub const T_PATHS : [4]str = ["specs/claw/merge_judge.t27", "docs/claw/README.md", "specs/analog/new_circuit.t27", "tools/unrelated.py"]; +pub const T_PLENS : [4]usize = [26, 19, 28, 18]; + +/// A 40-digit sha: the digit d, then 39 `a`. +fn t_sha(dst : []u8, cap : usize, w : usize, d : u32) -> usize { + var x : usize = put_byte(dst, cap, w, digit_byte(d % 10)); + var k : usize = 1; + while (k < 40) { + x = put_byte(dst, cap, x, 'a'); + k += 1; + } + return x; +} + +/// One P line. labels: 0 none, 1 no-auto-merge, 2 bug,no-auto-merge,x, 3 no-auto-merge-2, 4 x-no-auto-merge, +/// 5 bug,x. head: 0 feature-x, 1 queen-77, 2 queenly-x, 3 xqueen-77, 9 empty. base: 0 master, 1 stack-base, +/// 2 master2, 9 empty. +/// mg: 0 MERGEABLE, 1 CONFLICTING, 2 UNKNOWN. sc: 0 CLEAN, 1 DIRTY, 2 BLOCKED. rd: the digit of the +/// rollup sha (the sha of PR num starts with num % 10). age: seconds, 9999999 for `-`. +fn t_p(dst : []u8, cap : usize, w : usize, num : u32, draft : bool, labels : u32, head : u32, base : u32, mg : u32, sc : u32, closing : u32, rd : u32, age : u32, total : u32, files : u32) -> usize { + var x : usize = put_str(dst, cap, w, "P\t", 2); + x = put_count(dst, cap, x, num); + x = put_byte(dst, cap, x, '\t'); + x = t_sha(dst, cap, x, num); + x = put_byte(dst, cap, x, '\t'); + if (draft) { x = put_str(dst, cap, x, "true", 4); } else { x = put_str(dst, cap, x, "false", 5); } + x = put_byte(dst, cap, x, '\t'); + if (labels == 1) { x = put_str(dst, cap, x, "no-auto-merge", 13); } + if (labels == 2) { x = put_str(dst, cap, x, "bug,no-auto-merge,x", 19); } + if (labels == 3) { x = put_str(dst, cap, x, "no-auto-merge-2", 15); } + if (labels == 4) { x = put_str(dst, cap, x, "x-no-auto-merge", 15); } + if (labels == 5) { x = put_str(dst, cap, x, "bug,x", 5); } + x = put_byte(dst, cap, x, '\t'); + if (head == 0) { x = put_str(dst, cap, x, "feature-x", 9); } + if (head == 1) { x = put_str(dst, cap, x, "queen-77", 8); } + if (head == 2) { x = put_str(dst, cap, x, "queenly-x", 9); } + if (head == 3) { x = put_str(dst, cap, x, "xqueen-77", 9); } + x = put_byte(dst, cap, x, '\t'); + if (base == 0) { x = put_str(dst, cap, x, "master", 6); } + if (base == 1) { x = put_str(dst, cap, x, "stack-base", 10); } + if (base == 2) { x = put_str(dst, cap, x, "master2", 7); } + x = put_byte(dst, cap, x, '\t'); + if (mg == 0) { x = put_str(dst, cap, x, "MERGEABLE", 9); } + if (mg == 1) { x = put_str(dst, cap, x, "CONFLICTING", 11); } + if (mg == 2) { x = put_str(dst, cap, x, "UNKNOWN", 7); } + x = put_byte(dst, cap, x, '\t'); + if (sc == 0) { x = put_str(dst, cap, x, "CLEAN", 5); } + if (sc == 1) { x = put_str(dst, cap, x, "DIRTY", 5); } + if (sc == 2) { x = put_str(dst, cap, x, "BLOCKED", 7); } + x = put_byte(dst, cap, x, '\t'); + x = put_count(dst, cap, x, closing); + x = put_byte(dst, cap, x, '\t'); + x = t_sha(dst, cap, x, rd); + x = put_byte(dst, cap, x, '\t'); + if (age == 9999999) { x = put_byte(dst, cap, x, '-'); } else { x = put_count(dst, cap, x, age); } + x = put_byte(dst, cap, x, '\t'); + x = put_count(dst, cap, x, total); + x = put_byte(dst, cap, x, '\t'); + x = put_count(dst, cap, x, files); + return put_byte(dst, cap, x, '\n'); +} + +/// status and conclusion. sc: 0 COMPLETED SUCCESS, 1 COMPLETED FAILURE, 2 QUEUED, 3 IN_PROGRESS, +/// 4 COMPLETED SKIPPED, 5 COMPLETED CANCELLED. +fn t_status(dst : []u8, cap : usize, w : usize, sc : u32) -> usize { + if (sc == 0) { return put_str(dst, cap, w, "COMPLETED\tSUCCESS", 17); } + if (sc == 1) { return put_str(dst, cap, w, "COMPLETED\tFAILURE", 17); } + if (sc == 2) { return put_str(dst, cap, w, "QUEUED\t-", 8); } + if (sc == 3) { return put_str(dst, cap, w, "IN_PROGRESS\t-", 13); } + if (sc == 4) { return put_str(dst, cap, w, "COMPLETED\tSKIPPED", 17); } + return put_str(dst, cap, w, "COMPLETED\tCANCELLED", 19); +} + +/// One K or B line: tag, check name by code, run id, status by code. +fn t_row(dst : []u8, cap : usize, w : usize, tag : u8, name : u32, run : u32, sc : u32) -> usize { + var x : usize = put_byte(dst, cap, w, tag); + x = put_byte(dst, cap, x, '\t'); + x = put_str(dst, cap, x, T_NAMES[name as usize], T_NLENS[name as usize]); + x = put_byte(dst, cap, x, '\t'); + x = put_count(dst, cap, x, run); + x = put_byte(dst, cap, x, '\t'); + x = t_status(dst, cap, x, sc); + return put_byte(dst, cap, x, '\n'); +} + +/// The four required checks and spec-guards, all passed: run ids run0 .. run0 + 4. +fn t_good_rows(dst : []u8, cap : usize, w : usize, run0 : u32) -> usize { + var x : usize = t_row(dst, cap, w, 'K', 0, run0, 0); + x = t_row(dst, cap, x, 'K', 1, run0 + 1, 0); + x = t_row(dst, cap, x, 'K', 2, run0 + 2, 0); + x = t_row(dst, cap, x, 'K', 3, run0 + 3, 0); + return t_row(dst, cap, x, 'K', 7, run0 + 4, 0); +} + +/// An F line (pull request, path by code) or an O line (pull request, check by name code, path by code; 9 is `-`). +fn t_file(dst : []u8, cap : usize, w : usize, num : u32, path : u32) -> usize { + var x : usize = put_str(dst, cap, w, "F\t", 2); + x = put_count(dst, cap, x, num); + x = put_byte(dst, cap, x, '\t'); + x = put_str(dst, cap, x, T_PATHS[path as usize], T_PLENS[path as usize]); + return put_byte(dst, cap, x, '\n'); +} + +fn t_named(dst : []u8, cap : usize, w : usize, num : u32, name : u32, path : u32) -> usize { + var x : usize = put_str(dst, cap, w, "O\t", 2); + x = put_count(dst, cap, x, num); + x = put_byte(dst, cap, x, '\t'); + x = put_str(dst, cap, x, T_NAMES[name as usize], T_NLENS[name as usize]); + x = put_byte(dst, cap, x, '\t'); + if (path == 9) { x = put_byte(dst, cap, x, '-'); } else { x = put_str(dst, cap, x, T_PATHS[path as usize], T_PLENS[path as usize]); } + return put_byte(dst, cap, x, '\n'); +} + +/// PR num with nothing wrong: not a draft, no label, a feature branch into master, mergeable, one closing +/// reference, an hour old, five passed checks, two changed files. +fn t_green(dst : []u8, cap : usize, w : usize, num : u32) -> usize { + const x : usize = t_p(dst, cap, w, num, false, 0, 0, 0, 0, 0, 1, num, 3600, 5, 2); + return t_good_rows(dst, cap, x, 100); +} + +/// PR 5 with the given facts and five passed checks. +fn t_v(dst : []u8, cap : usize, draft : bool, labels : u32, head : u32, base : u32, mg : u32, sc : u32, closing : u32, age : u32) -> usize { + const x : usize = t_p(dst, cap, 0, 5, draft, labels, head, base, mg, sc, closing, 5, age, 5, 2); + return t_good_rows(dst, cap, x, 100); +} + +/// The decision for the first P line of raw[0..n) as one block. +fn t_one(raw : []const u8, n : usize) -> u8 { + var f : [28]usize = undefined; + var det : [2]usize = undefined; + var ls : usize = 0; + while (ls < n and mj_tag(raw, ls, qc_line_end(raw, n, ls)) != 'P') { ls = qc_line_end(raw, n, ls) + 1; } + if (qc_split(raw, ls, qc_line_end(raw, n, ls), &f, MJ_PF) == false) { return J_BAD; } + return mj_judge(raw, n, ls, n, &f, &det); +} + +/// Whether out holds the line `G TAB num TAB sha` (the sha of PR num). +fn t_g(out : []const u8, n : usize, num : u32) -> bool { + var e : [64]u8 = undefined; + var w : usize = put_str(&e, 64, 0, "G\t", 2); + w = put_count(&e, 64, w, num); + w = put_byte(&e, 64, w, '\t'); + w = t_sha(&e, 64, w, num); + w = put_byte(&e, 64, w, '\n'); + return buf_has(out, n, &e, w); +} + +/// Whether out holds a line that starts ` TAB num TAB` and the reason text of decision d. +fn t_has(out : []const u8, n : usize, c : u8, num : u32, d : u8) -> bool { + var e : [160]u8 = undefined; + var w : usize = put_byte(&e, 160, 0, c); + w = put_byte(&e, 160, w, '\t'); + w = put_count(&e, 160, w, num); + w = put_byte(&e, 160, w, '\t'); + w = put_str(&e, 160, w, J_TEXTS[d as usize], J_LENS[d as usize]); + return buf_has(out, n, &e, w); +} + +/// Whether out holds the line that starts ` TAB num TAB`, the text of decision d and the check by code. +fn t_has_named(out : []const u8, n : usize, c : u8, num : u32, d : u8, name : u32) -> bool { + var e : [240]u8 = undefined; + var w : usize = put_byte(&e, 240, 0, c); + w = put_byte(&e, 240, w, '\t'); + w = put_count(&e, 240, w, num); + w = put_byte(&e, 240, w, '\t'); + w = put_str(&e, 240, w, J_TEXTS[d as usize], J_LENS[d as usize]); + w = put_str(&e, 240, w, T_NAMES[name as usize], T_NLENS[name as usize]); + return buf_has(out, n, &e, w); +} + +/// Whether out holds the summary line with these counts. +fn t_summary(out : []const u8, n : usize, g : u32, c : u32, wt : u32, h : u32, nm : u32, bad : u32, total : u32) -> bool { + var e : [200]u8 = undefined; + var w : usize = put_str(&e, 200, 0, "M\tclaw-merge: ", 14); + w = put_count(&e, 200, w, g); + w = put_str(&e, 200, w, " to merge, ", 11); + w = put_count(&e, 200, w, c); + w = put_str(&e, 200, w, " over the cap, ", 15); + w = put_count(&e, 200, w, wt); + w = put_str(&e, 200, w, " waiting, ", 10); + w = put_count(&e, 200, w, h); + w = put_str(&e, 200, w, " held, ", 7); + w = put_count(&e, 200, w, nm); + w = put_str(&e, 200, w, " not mine, ", 11); + w = put_count(&e, 200, w, bad); + w = put_str(&e, 200, w, " unreadable of ", 15); + w = put_count(&e, 200, w, total); + w = put_str(&e, 200, w, " open\n", 6); + return buf_has(out, n, &e, w); +} + +// ---- tests: one per rule, first match wins ---- + +test a_pull_request_that_passes_every_rule_is_merged_pinned_to_its_head_sha { + var raw : [4096]u8 = undefined; + var out : [4096]u8 = undefined; + const n : usize = t_green(&raw, 4096, 0, 7); + assert t_one(&raw, n) == J_MERGE; + const m : usize = merge_plan(&raw, n, &out, 4096); + assert t_g(&out, m, 7); + assert t_lines(&out, m, 'G') == 1; + assert t_lines(&out, m, 'W') + t_lines(&out, m, 'H') + t_lines(&out, m, 'N') + t_lines(&out, m, 'L') == 0; + assert t_summary(&out, m, 1, 0, 0, 0, 0, 0, 1); +} + +test rule_1_the_held_label_holds_whatever_else_is_true { + var raw : [4096]u8 = undefined; + var out : [4096]u8 = undefined; + var n : usize = t_v(&raw, 4096, false, 1, 0, 0, 0, 0, 1, 3600); + assert t_one(&raw, n) == J_HELD; + n = t_v(&raw, 4096, false, 2, 0, 0, 0, 0, 1, 3600); + assert t_one(&raw, n) == J_HELD; + n = t_v(&raw, 4096, true, 1, 1, 1, 1, 1, 0, 0); + assert t_one(&raw, n) == J_HELD; + const m : usize = merge_plan(&raw, n, &out, 4096); + assert t_has(&out, m, 'H', 5, J_HELD); + assert t_lines(&out, m, 'G') == 0; +} + +test rule_1_only_the_whole_label_holds { + var raw : [4096]u8 = undefined; + var n : usize = t_v(&raw, 4096, false, 3, 0, 0, 0, 0, 1, 3600); + assert t_one(&raw, n) == J_MERGE; + n = t_v(&raw, 4096, false, 4, 0, 0, 0, 0, 1, 3600); + assert t_one(&raw, n) == J_MERGE; + n = t_v(&raw, 4096, false, 5, 0, 0, 0, 0, 1, 3600); + assert t_one(&raw, n) == J_MERGE; +} + +test rule_2_a_draft_is_never_touched { + var raw : [4096]u8 = undefined; + var out : [4096]u8 = undefined; + const n : usize = t_v(&raw, 4096, true, 0, 0, 0, 0, 0, 1, 3600); + assert t_one(&raw, n) == J_DRAFT; + const m : usize = merge_plan(&raw, n, &out, 4096); + assert t_has(&out, m, 'H', 5, J_DRAFT); + assert t_summary(&out, m, 0, 0, 0, 1, 0, 0, 1); +} + +test rule_3_only_master_is_merged_into { + var raw : [4096]u8 = undefined; + var n : usize = t_v(&raw, 4096, false, 0, 0, 1, 0, 0, 1, 3600); + assert t_one(&raw, n) == J_BASE; + n = t_v(&raw, 4096, false, 0, 0, 2, 0, 0, 1, 3600); + assert t_one(&raw, n) == J_BASE; + n = t_v(&raw, 4096, false, 0, 0, 0, 0, 0, 1, 3600); + assert t_one(&raw, n) == J_MERGE; +} + +test rule_4_a_bee_branch_is_not_mine { + var raw : [4096]u8 = undefined; + var out : [4096]u8 = undefined; + var n : usize = t_v(&raw, 4096, false, 0, 1, 0, 0, 0, 1, 3600); + assert t_one(&raw, n) == J_BEE; + const m : usize = merge_plan(&raw, n, &out, 4096); + assert t_has(&out, m, 'N', 5, J_BEE); + assert t_summary(&out, m, 0, 0, 0, 0, 1, 0, 1); + n = t_v(&raw, 4096, false, 0, 2, 0, 0, 0, 1, 3600); + assert t_one(&raw, n) == J_MERGE; + n = t_v(&raw, 4096, false, 0, 3, 0, 0, 0, 1, 3600); + assert t_one(&raw, n) == J_MERGE; +} + +test rule_5_a_conflict_waits_and_the_lane_never_resolves_it { + var raw : [4096]u8 = undefined; + var out : [4096]u8 = undefined; + var n : usize = t_v(&raw, 4096, false, 0, 0, 0, 1, 2, 1, 3600); + assert t_one(&raw, n) == J_CONFLICT; + n = t_v(&raw, 4096, false, 0, 0, 0, 0, 1, 1, 3600); + assert t_one(&raw, n) == J_CONFLICT; + const m : usize = merge_plan(&raw, n, &out, 4096); + assert t_has(&out, m, 'W', 5, J_CONFLICT); + assert t_summary(&out, m, 0, 0, 1, 0, 0, 0, 1); +} + +test rule_5_a_mergeability_github_has_not_computed_waits { + var raw : [4096]u8 = undefined; + const n : usize = t_v(&raw, 4096, false, 0, 0, 0, 2, 2, 1, 3600); + assert t_one(&raw, n) == J_UNKNOWN; +} + +test rule_6_no_closing_reference_waits { + var raw : [4096]u8 = undefined; + var out : [4096]u8 = undefined; + var n : usize = t_v(&raw, 4096, false, 0, 0, 0, 0, 0, 0, 3600); + assert t_one(&raw, n) == J_NO_CLOSE; + const m : usize = merge_plan(&raw, n, &out, 4096); + assert t_has(&out, m, 'W', 5, J_NO_CLOSE); + n = t_v(&raw, 4096, false, 0, 0, 0, 0, 0, 1, 3600); + assert t_one(&raw, n) == J_MERGE; + n = t_v(&raw, 4096, false, 0, 0, 0, 0, 0, 3, 3600); + assert t_one(&raw, n) == J_MERGE; +} + +test the_first_matching_rule_names_the_reason { + // Break every rule at once, then repair them in order: each repair exposes the next rule. + var raw : [4096]u8 = undefined; + var n : usize = t_v(&raw, 4096, true, 1, 1, 1, 1, 1, 0, 10); + assert t_one(&raw, n) == J_HELD; + n = t_v(&raw, 4096, true, 0, 1, 1, 1, 1, 0, 10); + assert t_one(&raw, n) == J_DRAFT; + n = t_v(&raw, 4096, false, 0, 1, 1, 1, 1, 0, 10); + assert t_one(&raw, n) == J_BASE; + n = t_v(&raw, 4096, false, 0, 1, 0, 1, 1, 0, 10); + assert t_one(&raw, n) == J_BEE; + n = t_v(&raw, 4096, false, 0, 0, 0, 1, 1, 0, 10); + assert t_one(&raw, n) == J_CONFLICT; + n = t_v(&raw, 4096, false, 0, 0, 0, 0, 0, 0, 10); + assert t_one(&raw, n) == J_NO_CLOSE; + n = t_v(&raw, 4096, false, 0, 0, 0, 0, 0, 1, 10); + assert t_one(&raw, n) == J_QUIET; + n = t_v(&raw, 4096, false, 0, 0, 0, 0, 0, 1, 3600); + assert t_one(&raw, n) == J_MERGE; +} + +// ---- tests: the rollup (rules 7 to 10) ---- + +test rule_7_a_rollup_that_is_not_the_head_commits_is_not_read { + var raw : [4096]u8 = undefined; + var out : [4096]u8 = undefined; + var w : usize = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 6, 3600, 5, 2); + w = t_good_rows(&raw, 4096, w, 100); + assert t_one(&raw, w) == J_UNREAD; + const m : usize = merge_plan(&raw, w, &out, 4096); + assert t_has(&out, m, 'W', 5, J_UNREAD); + assert t_lines(&out, m, 'G') == 0; +} + +test rule_7_a_rollup_cut_short_is_not_read { + var raw : [4096]u8 = undefined; + var w : usize = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 6, 2); + w = t_good_rows(&raw, 4096, w, 100); + assert t_one(&raw, w) == J_UNREAD; + w = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 4, 2); + w = t_good_rows(&raw, 4096, w, 100); + assert t_one(&raw, w) == J_UNREAD; +} + +test rule_7_a_row_that_cannot_be_read_is_not_read { + var raw : [4096]u8 = undefined; + var w : usize = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 6, 2); + w = t_good_rows(&raw, 4096, w, 100); + w = put_str(&raw, 4096, w, "K\tonly two fields\tCOMPLETED\n", 28); + assert t_one(&raw, w) == J_UNREAD; + // a K line with a run id that is not a number + w = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 6, 2); + w = t_good_rows(&raw, 4096, w, 100); + w = put_str(&raw, 4096, w, "K\tIssue Gate/check-linked-issue\tx\tCOMPLETED\tSUCCESS\n", 52); + assert t_one(&raw, w) == J_UNREAD; +} + +test rule_7_more_rows_than_the_lane_reads_are_not_read { + var raw : [16384]u8 = undefined; + var w : usize = t_p(&raw, 16384, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 129, 2); + var k : u32 = 0; + while (k < 129) { + w = t_row(&raw, 16384, w, 'K', 7, 1000 + k, 0); + k += 1; + } + assert t_one(&raw, w) == J_UNREAD; + w = t_p(&raw, 16384, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 128, 2); + k = 0; + while (k < 124) { + w = t_row(&raw, 16384, w, 'K', 7, 1000 + k, 0); + k += 1; + } + w = t_row(&raw, 16384, w, 'K', 0, 5000, 0); + w = t_row(&raw, 16384, w, 'K', 1, 5001, 0); + w = t_row(&raw, 16384, w, 'K', 2, 5002, 0); + w = t_row(&raw, 16384, w, 'K', 3, 5003, 0); + assert t_one(&raw, w) == J_MERGE; +} + +test rule_8_a_check_still_running_waits { + var raw : [4096]u8 = undefined; + var out : [4096]u8 = undefined; + var w : usize = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 6, 2); + w = t_good_rows(&raw, 4096, w, 100); + w = t_row(&raw, 4096, w, 'K', 8, 200, 2); + assert t_one(&raw, w) == J_PENDING; + const m : usize = merge_plan(&raw, w, &out, 4096); + assert t_has(&out, m, 'W', 5, J_PENDING); + w = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 6, 2); + w = t_good_rows(&raw, 4096, w, 100); + w = t_row(&raw, 4096, w, 'K', 8, 200, 3); + assert t_one(&raw, w) == J_PENDING; +} + +test rule_8_no_check_posted_is_not_green { + var raw : [4096]u8 = undefined; + const w : usize = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 0, 2); + assert t_one(&raw, w) == J_PENDING; +} + +test rule_8_skipped_is_a_pass_but_the_required_checks_must_still_post { + var raw : [4096]u8 = undefined; + var w : usize = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 1, 2); + w = t_row(&raw, 4096, w, 'K', 8, 200, 4); + assert t_one(&raw, w) == J_REQ_MISSING; + w = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 6, 2); + w = t_good_rows(&raw, 4096, w, 100); + w = t_row(&raw, 4096, w, 'K', 8, 200, 4); + assert t_one(&raw, w) == J_MERGE; +} + +test rule_8_a_rerun_in_the_queue_supersedes_the_failure_it_replaces { + // #10563 on 2026-10-19: check-linked-issue FAILURE (before its body was fixed), then the QUEUED re-run. + var raw : [4096]u8 = undefined; + var w : usize = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 6, 2); + w = t_good_rows(&raw, 4096, w, 100); + w = t_row(&raw, 4096, w, 'K', 1, 50, 1); + assert t_one(&raw, w) == J_MERGE; + w = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 6, 2); + w = t_good_rows(&raw, 4096, w, 100); + w = t_row(&raw, 4096, w, 'K', 1, 500, 1); + assert t_one(&raw, w) == J_REQ_RED; + w = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 6, 2); + w = t_good_rows(&raw, 4096, w, 100); + w = t_row(&raw, 4096, w, 'K', 1, 500, 2); + assert t_one(&raw, w) == J_PENDING; +} + +test rule_9_a_required_check_that_is_red_blocks_and_is_named { + var raw : [4096]u8 = undefined; + var out : [4096]u8 = undefined; + var w : usize = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 5, 2); + w = t_row(&raw, 4096, w, 'K', 0, 100, 0); + w = t_row(&raw, 4096, w, 'K', 1, 101, 1); + w = t_row(&raw, 4096, w, 'K', 2, 102, 0); + w = t_row(&raw, 4096, w, 'K', 3, 103, 0); + w = t_row(&raw, 4096, w, 'K', 7, 104, 0); + assert t_one(&raw, w) == J_REQ_RED; + const m : usize = merge_plan(&raw, w, &out, 4096); + assert t_has_named(&out, m, 'W', 5, J_REQ_RED, 1); + assert t_lines(&out, m, 'G') == 0; +} + +test rule_9_names_the_first_red_required_check_even_when_it_is_the_first_row { + var raw : [4096]u8 = undefined; + var out : [4096]u8 = undefined; + var w : usize = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 5, 2); + w = t_row(&raw, 4096, w, 'K', 0, 100, 1); + w = t_row(&raw, 4096, w, 'K', 1, 101, 0); + w = t_row(&raw, 4096, w, 'K', 2, 102, 0); + w = t_row(&raw, 4096, w, 'K', 3, 103, 0); + w = t_row(&raw, 4096, w, 'K', 7, 104, 0); + assert t_one(&raw, w) == J_REQ_RED; + const m : usize = merge_plan(&raw, w, &out, 4096); + assert t_has_named(&out, m, 'W', 5, J_REQ_RED, 0); + // two red required checks: the first in the table is named + w = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 5, 2); + w = t_row(&raw, 4096, w, 'K', 0, 100, 0); + w = t_row(&raw, 4096, w, 'K', 1, 101, 1); + w = t_row(&raw, 4096, w, 'K', 2, 102, 0); + w = t_row(&raw, 4096, w, 'K', 3, 103, 1); + w = t_row(&raw, 4096, w, 'K', 7, 104, 0); + const k : usize = merge_plan(&raw, w, &out, 4096); + assert t_has_named(&out, k, 'W', 5, J_REQ_RED, 1); +} + +test rule_9_each_of_the_four_required_checks_blocks { + var raw : [4096]u8 = undefined; + var name : u32 = 0; + while (name < 4) { + var w : usize = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 5, 2); + var k : u32 = 0; + while (k < 4) { + if (k == name) { w = t_row(&raw, 4096, w, 'K', k, 100 + k, 1); } else { w = t_row(&raw, 4096, w, 'K', k, 100 + k, 0); } + k += 1; + } + w = t_row(&raw, 4096, w, 'K', 7, 104, 0); + assert t_one(&raw, w) == J_REQ_RED; + name += 1; + } +} + +test rule_10_a_required_check_that_has_not_posted_is_missing { + var raw : [4096]u8 = undefined; + var out : [4096]u8 = undefined; + var w : usize = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 4, 2); + w = t_row(&raw, 4096, w, 'K', 0, 100, 0); + w = t_row(&raw, 4096, w, 'K', 1, 101, 0); + w = t_row(&raw, 4096, w, 'K', 2, 102, 0); + w = t_row(&raw, 4096, w, 'K', 7, 104, 0); + assert t_one(&raw, w) == J_REQ_MISSING; + const m : usize = merge_plan(&raw, w, &out, 4096); + assert t_has(&out, m, 'W', 5, J_REQ_MISSING); +} + +test pending_outranks_a_required_red { + var raw : [4096]u8 = undefined; + var w : usize = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 7, 2); + w = t_good_rows(&raw, 4096, w, 100); + w = t_row(&raw, 4096, w, 'K', 0, 900, 1); + w = t_row(&raw, 4096, w, 'K', 8, 901, 2); + assert t_one(&raw, w) == J_PENDING; +} + +test a_commit_status_is_read_by_its_state { + var raw : [4096]u8 = undefined; + var w : usize = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 6, 2); + w = t_good_rows(&raw, 4096, w, 100); + w = put_str(&raw, 4096, w, "K\tcodecov/patch\t-\tCONTEXT\tSUCCESS\n", 34); + assert t_one(&raw, w) == J_MERGE; + w = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 6, 2); + w = t_good_rows(&raw, 4096, w, 100); + w = put_str(&raw, 4096, w, "K\tcodecov/patch\t-\tCONTEXT\tPENDING\n", 34); + assert t_one(&raw, w) == J_PENDING; +} + +test rule_12_a_head_commit_younger_than_ten_minutes_waits { + var raw : [4096]u8 = undefined; + var out : [4096]u8 = undefined; + var n : usize = t_v(&raw, 4096, false, 0, 0, 0, 0, 0, 1, 180); + assert t_one(&raw, n) == J_QUIET; + const m : usize = merge_plan(&raw, n, &out, 4096); + assert t_has(&out, m, 'W', 5, J_QUIET); + n = t_v(&raw, 4096, false, 0, 0, 0, 0, 0, 1, 599); + assert t_one(&raw, n) == J_QUIET; + n = t_v(&raw, 4096, false, 0, 0, 0, 0, 0, 1, 600); + assert t_one(&raw, n) == J_MERGE; + n = t_v(&raw, 4096, false, 0, 0, 0, 0, 0, 1, 601); + assert t_one(&raw, n) == J_MERGE; + n = t_v(&raw, 4096, false, 0, 0, 0, 0, 0, 1, 0); + assert t_one(&raw, n) == J_QUIET; + assert QUIET_SECS == QUIET_MIN * 60; +} + +test rule_12_an_age_nobody_could_read_waits { + var raw : [4096]u8 = undefined; + const n : usize = t_v(&raw, 4096, false, 0, 0, 0, 0, 0, 1, 9999999); + assert t_one(&raw, n) == J_QUIET; +} + +// ---- tests: rule 11, whose red is it ---- + +/// PR 5 with five passed checks and the check `name` red (run 300), two changed files declared. +fn t_red_pr(dst : []u8, cap : usize, w : usize, name : u32) -> usize { + var x : usize = t_p(dst, cap, w, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 6, 2); + x = t_good_rows(dst, cap, x, 100); + return t_row(dst, cap, x, 'K', name, 300, 1); +} + +/// Whether out holds the H line of PR num for a red check, with red_origin's reason r. +fn t_has_red(out : []const u8, n : usize, num : u32, name : u32, r : u8) -> bool { + var e : [400]u8 = undefined; + var w : usize = put_str(&e, 400, 0, "H\t", 2); + w = put_count(&e, 400, w, num); + w = put_byte(&e, 400, w, '\t'); + w = put_str(&e, 400, w, "red check ", 10); + w = put_str(&e, 400, w, T_NAMES[name as usize], T_NLENS[name as usize]); + w = put_str(&e, 400, w, ": ", 2); + if (verdict_of(r) == V_OWN) { w = put_str(&e, 400, w, "own: ", 5); } else { w = put_str(&e, 400, w, "unknown: ", 9); } + w = put_str(&e, 400, w, WHY_TEXTS[r as usize], WHY_LENS[r as usize]); + return buf_has(out, n, &e, w); +} + +/// Whether out holds the line `L TAB num TAB check TAB id`. +fn t_has_log(out : []const u8, n : usize, num : u32, name : u32, id : u32) -> bool { + var e : [160]u8 = undefined; + var w : usize = put_str(&e, 160, 0, "L\t", 2); + w = put_count(&e, 160, w, num); + w = put_byte(&e, 160, w, '\t'); + w = put_str(&e, 160, w, T_NAMES[name as usize], T_NLENS[name as usize]); + w = put_byte(&e, 160, w, '\t'); + w = put_count(&e, 160, w, id); + w = put_byte(&e, 160, w, '\n'); + return buf_has(out, n, &e, w); +} + +test claw_sarif_red_alone_is_inherited_by_definition_and_merges { + // The TRI CLAW App lacks security_events:write (#10501): claw-sarif fails before it reads any code. + var raw : [4096]u8 = undefined; + var out : [4096]u8 = undefined; + const n : usize = t_red_pr(&raw, 4096, 0, 5); + assert t_one(&raw, n) == J_MERGE; + const m : usize = merge_plan(&raw, n, &out, 4096); + assert t_g(&out, m, 5); + assert t_summary(&out, m, 1, 0, 0, 0, 0, 0, 1); +} + +test negative_control_a_corpus_ratchet_red_on_a_green_master_is_the_prs_own { + // 2026-10-19: Corpus ratchet red while master's last finished run of it is green: the PR's merge with + // master fails it, and only a rebase in the PR fixes it. + var raw : [4096]u8 = undefined; + var out : [4096]u8 = undefined; + var n : usize = t_red_pr(&raw, 4096, 0, 4); + n = t_row(&raw, 4096, n, 'B', 4, 250, 0); + assert t_one(&raw, n) == J_RED; + const m : usize = merge_plan(&raw, n, &out, 4096); + assert t_has_red(&out, m, 5, 4, R_OWN_MERGE); + assert t_lines(&out, m, 'G') == 0; + assert t_summary(&out, m, 0, 0, 0, 1, 0, 0, 1); +} + +test a_red_that_master_has_no_finished_run_of_is_unknown_and_holds { + var raw : [4096]u8 = undefined; + var out : [4096]u8 = undefined; + var n : usize = t_red_pr(&raw, 4096, 0, 4); + assert t_one(&raw, n) == J_RED; + var m : usize = merge_plan(&raw, n, &out, 4096); + assert t_has_red(&out, m, 5, 4, R_UNSEEN); + // master's runs of other checks, and master runs that did not finish, say nothing about this one + n = t_row(&raw, 4096, n, 'B', 3, 250, 1); + n = t_row(&raw, 4096, n, 'B', 4, 251, 2); + n = t_row(&raw, 4096, n, 'B', 4, 252, 5); + n = t_row(&raw, 4096, n, 'B', 4, 253, 4); + assert t_one(&raw, n) == J_RED; +} + +test a_red_on_a_red_master_asks_for_the_failing_log_before_anything_else { + var raw : [4096]u8 = undefined; + var out : [4096]u8 = undefined; + var n : usize = t_red_pr(&raw, 4096, 0, 4); + n = t_row(&raw, 4096, n, 'B', 4, 250, 1); + assert t_one(&raw, n) == J_LOG; + const m : usize = merge_plan(&raw, n, &out, 4096); + assert t_has_named(&out, m, 'W', 5, J_LOG, 4); + assert t_has_log(&out, m, 5, 4, 300); + assert t_lines(&out, m, 'L') == 1; + assert t_lines(&out, m, 'G') == 0; + assert t_summary(&out, m, 0, 0, 1, 0, 0, 0, 1); +} + +test a_red_master_has_too_and_a_log_that_names_nothing_the_pr_changes_is_inherited { + var raw : [4096]u8 = undefined; + var out : [4096]u8 = undefined; + var n : usize = t_red_pr(&raw, 4096, 0, 4); + n = t_row(&raw, 4096, n, 'B', 4, 250, 1); + n = t_file(&raw, 4096, n, 5, 0); + n = t_file(&raw, 4096, n, 5, 1); + n = t_named(&raw, 4096, n, 5, 4, 9); + assert t_one(&raw, n) == J_MERGE; + const m : usize = merge_plan(&raw, n, &out, 4096); + assert t_g(&out, m, 5); + assert t_lines(&out, m, 'L') == 0; + // the log names a file, but not one this PR changes + n = t_red_pr(&raw, 4096, 0, 4); + n = t_row(&raw, 4096, n, 'B', 4, 250, 1); + n = t_file(&raw, 4096, n, 5, 0); + n = t_file(&raw, 4096, n, 5, 1); + n = t_named(&raw, 4096, n, 5, 4, 3); + assert t_one(&raw, n) == J_MERGE; +} + +test negative_control_the_t27b_ratchet_red_on_a_spec_the_pr_adds_is_the_prs_own_though_master_is_red { + // 2026-10-18: t27b-native-ratchet red with an UNLISTED row for a spec the PR itself adds; master red too. + var raw : [4096]u8 = undefined; + var out : [4096]u8 = undefined; + var n : usize = t_red_pr(&raw, 4096, 0, 6); + n = t_row(&raw, 4096, n, 'B', 6, 250, 1); + n = t_file(&raw, 4096, n, 5, 2); + n = t_file(&raw, 4096, n, 5, 1); + n = t_named(&raw, 4096, n, 5, 6, 2); + assert t_one(&raw, n) == J_RED; + const m : usize = merge_plan(&raw, n, &out, 4096); + assert t_has_red(&out, m, 5, 6, R_OWN_PATH); + assert t_lines(&out, m, 'G') == 0; +} + +test an_unread_file_list_is_not_a_clean_log { + var raw : [4096]u8 = undefined; + // one of the two declared changed files is missing from the list + var n : usize = t_red_pr(&raw, 4096, 0, 4); + n = t_row(&raw, 4096, n, 'B', 4, 250, 1); + n = t_file(&raw, 4096, n, 5, 0); + n = t_named(&raw, 4096, n, 5, 4, 9); + assert t_one(&raw, n) == J_LOG; + // a log that was read for another check says nothing about this one + n = t_red_pr(&raw, 4096, 0, 4); + n = t_row(&raw, 4096, n, 'B', 4, 250, 1); + n = t_file(&raw, 4096, n, 5, 0); + n = t_file(&raw, 4096, n, 5, 1); + n = t_named(&raw, 4096, n, 5, 6, 9); + assert t_one(&raw, n) == J_LOG; +} + +test master_is_the_newest_decisive_run_and_looks_past_the_ones_that_say_nothing { + var raw : [4096]u8 = undefined; + // the lookup reads B lines only: the red PR's own K row of that name is not master's word + const n : usize = t_red_pr(&raw, 4096, 0, 4); + assert mj_master(&raw, n, T_NAMES[4], 0, 55) == M_UNSEEN; + var b : [2048]u8 = undefined; + var w : usize = t_row(&b, 2048, 0, 'B', 4, 100, 0); + w = t_row(&b, 2048, w, 'B', 4, 200, 1); + w = t_row(&b, 2048, w, 'B', 4, 300, 5); + assert mj_master(&b, w, T_NAMES[4], 0, 55) == M_RED; + w = t_row(&b, 2048, w, 'B', 4, 400, 0); + assert mj_master(&b, w, T_NAMES[4], 0, 55) == M_GREEN; + w = t_row(&b, 2048, w, 'B', 4, 900, 4); + w = t_row(&b, 2048, w, 'B', 4, 950, 2); + assert mj_master(&b, w, T_NAMES[4], 0, 55) == M_GREEN; + w = t_row(&b, 2048, w, 'B', 4, 500, 1); + assert mj_master(&b, w, T_NAMES[4], 0, 55) == M_RED; +} + +test the_order_of_master_rows_does_not_matter_and_a_tie_reads_green { + var b : [2048]u8 = undefined; + var w : usize = t_row(&b, 2048, 0, 'B', 4, 999, 1); + w = t_row(&b, 2048, w, 'B', 4, 1000, 0); + assert mj_master(&b, w, T_NAMES[4], 0, 55) == M_GREEN; + w = t_row(&b, 2048, 0, 'B', 4, 1000, 0); + w = t_row(&b, 2048, w, 'B', 4, 999, 1); + assert mj_master(&b, w, T_NAMES[4], 0, 55) == M_GREEN; + // one run, both a failure and a success (a job re-run inside the run): the safe side is green + w = t_row(&b, 2048, 0, 'B', 4, 1000, 1); + w = t_row(&b, 2048, w, 'B', 4, 1000, 0); + assert mj_master(&b, w, T_NAMES[4], 0, 55) == M_GREEN; + w = t_row(&b, 2048, 0, 'B', 4, 1000, 0); + w = t_row(&b, 2048, w, 'B', 4, 1000, 1); + assert mj_master(&b, w, T_NAMES[4], 0, 55) == M_GREEN; + // ids are numbers, not strings + w = t_row(&b, 2048, 0, 'B', 4, 99, 0); + w = t_row(&b, 2048, w, 'B', 4, 1000, 1); + assert mj_master(&b, w, T_NAMES[4], 0, 55) == M_RED; +} + +test two_reds_are_judged_one_by_one_and_an_own_red_outranks_a_wanted_log { + var raw : [4096]u8 = undefined; + var out : [4096]u8 = undefined; + // Corpus ratchet: master red, log empty -> inherited. coverage: no master run -> unknown. + var n : usize = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 7, 2); + n = t_good_rows(&raw, 4096, n, 100); + n = t_row(&raw, 4096, n, 'K', 4, 300, 1); + n = t_row(&raw, 4096, n, 'K', 8, 301, 1); + n = t_row(&raw, 4096, n, 'B', 4, 250, 1); + n = t_file(&raw, 4096, n, 5, 0); + n = t_file(&raw, 4096, n, 5, 1); + n = t_named(&raw, 4096, n, 5, 4, 9); + assert t_one(&raw, n) == J_RED; + // the same PR with coverage inherited too + n = t_row(&raw, 4096, n, 'B', 8, 251, 1); + n = t_named(&raw, 4096, n, 5, 8, 9); + assert t_one(&raw, n) == J_MERGE; + // two reds whose logs are wanted: one L line each, and no G + n = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 7, 2); + n = t_good_rows(&raw, 4096, n, 100); + n = t_row(&raw, 4096, n, 'K', 4, 300, 1); + n = t_row(&raw, 4096, n, 'K', 8, 301, 1); + n = t_row(&raw, 4096, n, 'B', 4, 250, 1); + n = t_row(&raw, 4096, n, 'B', 8, 251, 1); + assert t_one(&raw, n) == J_LOG; + const m : usize = merge_plan(&raw, n, &out, 4096); + assert t_has_log(&out, m, 5, 4, 300); + assert t_has_log(&out, m, 5, 8, 301); + assert t_lines(&out, m, 'L') == 2; + // one own and one wanted log: the own red decides, nothing is fetched for it + n = t_row(&raw, 4096, n, 'B', 4, 260, 0); + assert t_one(&raw, n) == J_RED; + const k : usize = merge_plan(&raw, n, &out, 4096); + assert t_lines(&out, k, 'L') == 0; + assert t_has_red(&out, k, 5, 4, R_OWN_MERGE); +} + +test a_cancelled_check_that_is_not_required_is_a_red_like_any_other { + var raw : [4096]u8 = undefined; + var n : usize = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 6, 2); + n = t_good_rows(&raw, 4096, n, 100); + n = t_row(&raw, 4096, n, 'K', 7, 300, 5); + assert t_one(&raw, n) == J_RED; +} + +test a_red_that_the_pr_re_ran_green_is_not_a_red { + var raw : [4096]u8 = undefined; + var n : usize = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 7, 2); + n = t_good_rows(&raw, 4096, n, 100); + n = t_row(&raw, 4096, n, 'K', 4, 300, 1); + n = t_row(&raw, 4096, n, 'K', 4, 400, 0); + assert t_one(&raw, n) == J_MERGE; +} + +test the_reasons_rank_required_before_other_reds_and_reds_before_the_quiet_time { + var raw : [4096]u8 = undefined; + // validate red and Corpus ratchet red on a green master: the required one is named + var n : usize = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 7, 2); + n = t_good_rows(&raw, 4096, n, 100); + n = t_row(&raw, 4096, n, 'K', 0, 900, 1); + n = t_row(&raw, 4096, n, 'K', 4, 901, 1); + n = t_row(&raw, 4096, n, 'B', 4, 250, 0); + assert t_one(&raw, n) == J_REQ_RED; + // an own red outranks a young head, and so does a wanted log + n = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 30, 6, 2); + n = t_good_rows(&raw, 4096, n, 100); + n = t_row(&raw, 4096, n, 'K', 4, 300, 1); + n = t_row(&raw, 4096, n, 'B', 4, 250, 0); + assert t_one(&raw, n) == J_RED; + n = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 30, 6, 2); + n = t_good_rows(&raw, 4096, n, 100); + n = t_row(&raw, 4096, n, 'K', 4, 300, 1); + n = t_row(&raw, 4096, n, 'B', 4, 250, 1); + assert t_one(&raw, n) == J_LOG; +} + +test master_rows_may_stand_before_the_pull_request_or_inside_another_block { + var raw : [8192]u8 = undefined; + var out : [8192]u8 = undefined; + var n : usize = t_row(&raw, 8192, 0, 'B', 4, 250, 1); + n = t_red_pr(&raw, 8192, n, 4); + n = t_file(&raw, 8192, n, 5, 0); + n = t_file(&raw, 8192, n, 5, 1); + n = t_named(&raw, 8192, n, 5, 4, 9); + var m : usize = merge_plan(&raw, n, &out, 8192); + assert t_g(&out, m, 5); + assert t_summary(&out, m, 1, 0, 0, 0, 0, 0, 1); + // master's row inside PR 4's block serves PR 5's red too + n = t_green(&raw, 8192, 0, 4); + n = t_row(&raw, 8192, n, 'B', 4, 250, 1); + n = t_red_pr(&raw, 8192, n, 4); + n = t_file(&raw, 8192, n, 5, 0); + n = t_file(&raw, 8192, n, 5, 1); + n = t_named(&raw, 8192, n, 5, 4, 9); + m = merge_plan(&raw, n, &out, 8192); + assert t_g(&out, m, 4); + assert t_g(&out, m, 5); + assert t_summary(&out, m, 2, 0, 0, 0, 0, 0, 2); +} + +// ---- tests: the cap, the order, the output ---- + +/// The numbers of the G lines of out[0..n), in output order, into nums; returns how many. +fn t_gnums(out : []const u8, n : usize, nums : []u32) -> u32 { + var c : u32 = 0; + var i : usize = 0; + while (i + 2 < n) { + if (out[i] == 'G' and out[i + 1] == '\t' and (i == 0 or out[i - 1] == '\n')) { + var v : u32 = 0; + var j : usize = i + 2; + while (out[j] != '\t') { + v = v * 10 + ((out[j] - '0') as u32); + j += 1; + } + nums[c as usize] = v; + c += 1; + } + i += 1; + } + return c; +} + +test at_most_five_are_merged_per_run_lowest_number_first { + var raw : [8192]u8 = undefined; + var out : [8192]u8 = undefined; + var nums : [8]u32 = undefined; + var n : usize = t_green(&raw, 8192, 0, 30); + n = t_green(&raw, 8192, n, 12); + n = t_green(&raw, 8192, n, 45); + n = t_green(&raw, 8192, n, 3); + n = t_green(&raw, 8192, n, 9); + n = t_green(&raw, 8192, n, 21); + n = t_green(&raw, 8192, n, 18); + const m : usize = merge_plan(&raw, n, &out, 8192); + assert t_gnums(&out, m, &nums) == 5; + assert nums[0] == 3; + assert nums[1] == 9; + assert nums[2] == 12; + assert nums[3] == 18; + assert nums[4] == 21; + assert t_has(&out, m, 'W', 30, J_CAPPED); + assert t_has(&out, m, 'W', 45, J_CAPPED); + assert t_summary(&out, m, 5, 2, 0, 0, 0, 0, 7); + assert MERGE_CAP == 5; +} + +test a_pull_request_that_waits_or_is_held_does_not_use_up_the_cap { + var raw : [8192]u8 = undefined; + var out : [8192]u8 = undefined; + var nums : [8]u32 = undefined; + var n : usize = t_p(&raw, 8192, 0, 1, false, 1, 0, 0, 0, 0, 1, 1, 3600, 5, 2); + n = t_good_rows(&raw, 8192, n, 100); + n = t_p(&raw, 8192, n, 2, true, 0, 0, 0, 0, 0, 1, 2, 3600, 5, 2); + n = t_good_rows(&raw, 8192, n, 100); + n = t_green(&raw, 8192, n, 13); + n = t_green(&raw, 8192, n, 14); + n = t_green(&raw, 8192, n, 15); + n = t_green(&raw, 8192, n, 16); + n = t_green(&raw, 8192, n, 17); + n = t_green(&raw, 8192, n, 18); + const m : usize = merge_plan(&raw, n, &out, 8192); + assert t_gnums(&out, m, &nums) == 5; + assert nums[0] == 13; + assert nums[4] == 17; + assert t_has(&out, m, 'W', 18, J_CAPPED); + assert t_has(&out, m, 'H', 1, J_HELD); + assert t_has(&out, m, 'H', 2, J_DRAFT); + assert t_summary(&out, m, 5, 1, 0, 2, 0, 0, 8); +} + +test the_summary_counts_every_class { + var raw : [16384]u8 = undefined; + var out : [16384]u8 = undefined; + var n : usize = t_green(&raw, 16384, 0, 20); + n = t_p(&raw, 16384, n, 5, false, 1, 0, 0, 0, 0, 1, 5, 3600, 5, 2); + n = t_good_rows(&raw, 16384, n, 100); + n = t_p(&raw, 16384, n, 21, false, 0, 1, 0, 0, 0, 1, 1, 3600, 5, 2); + n = t_good_rows(&raw, 16384, n, 100); + n = t_p(&raw, 16384, n, 22, false, 0, 0, 0, 1, 1, 1, 2, 3600, 5, 2); + n = t_good_rows(&raw, 16384, n, 100); + n = t_p(&raw, 16384, n, 23, false, 0, 0, 0, 0, 0, 1, 3, 3600, 0, 2); + n = t_green(&raw, 16384, n, 24); + n = put_str(&raw, 16384, n, "P\t99\n", 5); + const m : usize = merge_plan(&raw, n, &out, 16384); + assert t_has(&out, m, 'H', 5, J_HELD); + assert t_has(&out, m, 'N', 21, J_BEE); + assert t_has(&out, m, 'W', 22, J_CONFLICT); + assert t_has(&out, m, 'W', 23, J_PENDING); + assert t_has(&out, m, 'W', 99, J_BAD); + assert t_g(&out, m, 20); + assert t_g(&out, m, 24); + assert t_summary(&out, m, 2, 0, 2, 1, 1, 1, 7); +} + +test the_output_lists_merges_first_then_the_rest_in_input_order { + var raw : [8192]u8 = undefined; + var out : [8192]u8 = undefined; + var n : usize = t_p(&raw, 8192, 0, 8, true, 0, 0, 0, 0, 0, 1, 8, 3600, 5, 2); + n = t_good_rows(&raw, 8192, n, 100); + n = t_green(&raw, 8192, n, 4); + n = t_p(&raw, 8192, n, 2, false, 1, 0, 0, 0, 0, 1, 2, 3600, 5, 2); + n = t_good_rows(&raw, 8192, n, 100); + const m : usize = merge_plan(&raw, n, &out, 8192); + assert out[0] == 'G'; + assert out[1] == '\t'; + assert out[2] == '4'; + // after the G line: the draft (8), then the held one (2), then the summary + var i : usize = 0; + while (out[i] != '\n') { i += 1; } + assert out[i + 1] == 'H'; + assert out[i + 3] == '8'; + var j : usize = i + 1; + while (out[j] != '\n') { j += 1; } + assert out[j + 1] == 'H'; + assert out[j + 3] == '2'; + assert out[m - 1] == '\n'; +} + +test the_lane_plan_is_the_merge_plan_or_the_refresh_plan_and_nothing_else { + var raw : [4096]u8 = undefined; + var a : [4096]u8 = undefined; + var b : [4096]u8 = undefined; + var c : [64]u8 = undefined; + const n : usize = t_green(&raw, 4096, 0, 7); + const m : usize = merge_plan(&raw, n, &a, 4096); + assert lane_plan(0, &raw, n, &b, 4096, 0) == m; + assert t_g(&b, m, 7); + // kind 1 answers refresh.t27's summary for an empty listing; an unread queue skips + const r : usize = refresh_plan("", 0, &a, 4096, 0); + assert lane_plan(1, "", 0, &b, 4096, 0) == r; + assert buf_has(&b, r, "M\tclaw-refresh: 0 to update", 27); + assert lane_plan(1, "", 0, &b, 4096, -1) == refresh_plan("", 0, &a, 4096, -1); + assert buf_has(&b, 66, "the Actions queue size could not be read", 40); + // any other kind answers nothing and writes nothing + c[0] = 'z'; + assert lane_plan(2, &raw, n, &c, 64, 0) == 0; + assert c[0] == 'z'; +} + +test refresh_records_in_the_same_stream_are_skipped_not_counted { + var raw : [8192]u8 = undefined; + var out : [8192]u8 = undefined; + const r : usize = put_str(&raw, 8192, 0, "R\t5\t5aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n", 45); + var n : usize = t_green(&raw, 8192, r, 5); + n = put_str(&raw, 8192, n, "R\t6\tx\n", 6); + n = t_green(&raw, 8192, n, 6); + var m : usize = merge_plan(&raw, n, &out, 8192); + assert t_summary(&out, m, 2, 0, 0, 0, 0, 0, 2); + assert t_g(&out, m, 5); + assert t_g(&out, m, 6); + // an R line inside a block does not spoil it + n = t_green(&raw, 8192, 0, 5); + n = put_str(&raw, 8192, n, "R\t5\tx\n", 6); + assert t_one(&raw, n) == J_MERGE; +} + +// ---- tests: fail closed ---- + +/// The offset of the k-th TAB of the first line of raw[0..n), the first TAB being number 0. +fn t_tab(raw : []const u8, n : usize, k : u32) -> usize { + var seen : u32 = 0; + var i : usize = 0; + while (i < n) { + if (raw[i] == '\t') { + if (seen == k) { return i; } + seen += 1; + } + i += 1; + } + return n; +} + +test an_unreadable_p_line_is_never_merged_and_is_counted { + var raw : [4096]u8 = undefined; + var out : [4096]u8 = undefined; + var n : usize = t_green(&raw, 4096, 0, 7); + assert t_one(&raw, n) == J_MERGE; + // a sha with an upper-case digit + raw[t_tab(&raw, n, 1) + 1] = 'A'; + assert t_one(&raw, n) == J_BAD; + // a draft flag that is neither true nor false + n = t_green(&raw, 4096, 0, 7); + raw[t_tab(&raw, n, 2) + 1] = 'x'; + assert t_one(&raw, n) == J_BAD; + // thirteen fields instead of fourteen + n = t_green(&raw, 4096, 0, 7); + raw[t_tab(&raw, n, 12)] = ' '; + assert t_one(&raw, n) == J_BAD; + // a closing count that is not a number, a rollup count that is not a number, a files count that is not + n = t_green(&raw, 4096, 0, 7); + raw[t_tab(&raw, n, 8) + 1] = 'x'; + assert t_one(&raw, n) == J_BAD; + n = t_green(&raw, 4096, 0, 7); + raw[t_tab(&raw, n, 11) + 1] = 'x'; + assert t_one(&raw, n) == J_BAD; + n = t_green(&raw, 4096, 0, 7); + raw[t_tab(&raw, n, 12) + 1] = 'x'; + assert t_one(&raw, n) == J_BAD; + // the rollup sha is not a sha + n = t_green(&raw, 4096, 0, 7); + raw[t_tab(&raw, n, 9) + 1] = 'G'; + assert t_one(&raw, n) == J_BAD; + // a pull request number that is not a positive integer + n = t_green(&raw, 4096, 0, 7); + raw[2] = '0'; + assert t_one(&raw, n) == J_BAD; + raw[2] = 'x'; + const m : usize = merge_plan(&raw, n, &out, 4096); + assert t_lines(&out, m, 'G') == 0; + assert t_summary(&out, m, 0, 0, 0, 0, 0, 1, 1); + // an empty head branch and an empty base branch + n = t_green(&raw, 4096, 0, 7); + raw[t_tab(&raw, n, 4) + 1] = '\t'; + assert t_one(&raw, n) == J_BAD; +} + +test a_pull_request_number_seen_twice_is_merged_at_most_once { + var raw : [8192]u8 = undefined; + var out : [8192]u8 = undefined; + var n : usize = t_green(&raw, 8192, 0, 7); + n = t_green(&raw, 8192, n, 7); + n = t_green(&raw, 8192, n, 8); + const m : usize = merge_plan(&raw, n, &out, 8192); + assert t_summary(&out, m, 2, 0, 0, 0, 0, 1, 3); + assert t_lines(&out, m, 'G') == 2; + assert t_has(&out, m, 'W', 7, J_BAD); + assert t_g(&out, m, 8); +} + +test a_block_with_a_line_of_another_tag_is_never_merged { + var raw : [4096]u8 = undefined; + var n : usize = t_green(&raw, 4096, 0, 7); + n = put_str(&raw, 4096, n, "X\tsurprise\n", 10); + assert t_one(&raw, n) == J_BAD; + n = t_green(&raw, 4096, 0, 7); + n = put_str(&raw, 4096, n, "no tab here\n", 12); + assert t_one(&raw, n) == J_BAD; + // F, O and B lines are part of a block + n = t_green(&raw, 4096, 0, 7); + n = t_file(&raw, 4096, n, 7, 0); + n = t_named(&raw, 4096, n, 7, 4, 9); + n = t_row(&raw, 4096, n, 'B', 4, 250, 1); + assert t_one(&raw, n) == J_MERGE; +} + +test lines_before_the_first_pull_request_are_unreadable_unless_they_are_master_rows { + var raw : [4096]u8 = undefined; + var out : [4096]u8 = undefined; + var n : usize = put_str(&raw, 4096, 0, "K\tstray\t1\tCOMPLETED\tSUCCESS\n", 28); + n = t_green(&raw, 4096, n, 7); + var m : usize = merge_plan(&raw, n, &out, 4096); + assert t_summary(&out, m, 1, 0, 0, 0, 0, 1, 2); + n = t_row(&raw, 4096, 0, 'B', 4, 250, 1); + n = t_green(&raw, 4096, n, 7); + m = merge_plan(&raw, n, &out, 4096); + assert t_summary(&out, m, 1, 0, 0, 0, 0, 0, 1); +} + +test more_pull_requests_than_the_lane_reads_are_counted_unreadable_not_dropped { + var raw : [8192]u8 = undefined; + var out : [40000]u8 = undefined; + var n : usize = 0; + var k : u32 = 0; + while (k < 513) { + n = put_str(&raw, 8192, n, "P\t", 2); + n = put_count(&raw, 8192, n, k + 1); + n = put_byte(&raw, 8192, n, '\n'); + k += 1; + } + const m : usize = merge_plan(&raw, n, &out, 40000); + assert t_summary(&out, m, 0, 0, 0, 0, 0, 513, 513); + assert t_lines(&out, m, 'G') == 0; + assert MJ_MAX_PRS == 512; +} + +test an_empty_stream_is_an_empty_plan { + var out : [256]u8 = undefined; + const m : usize = merge_plan("", 0, &out, 256); + assert t_summary(&out, m, 0, 0, 0, 0, 0, 0, 0); + assert t_lines(&out, m, 'G') == 0; +} + +test a_clipped_answer_reports_what_it_needs_and_writes_nothing_past_the_cap { + var raw : [4096]u8 = undefined; + var big : [4096]u8 = undefined; + var small : [32]u8 = undefined; + const n : usize = t_green(&raw, 4096, 0, 7); + const need : usize = merge_plan(&raw, n, &big, 4096); + var i : usize = 0; + while (i < 32) { + small[i] = 'z'; + i += 1; + } + assert merge_plan(&raw, n, &small, 10) == need; + assert small[0] == 'G'; + assert small[9] == big[9]; + assert small[10] == 'z'; + assert small[31] == 'z'; + assert need > 10; +} + +// ---- tests: the texts ---- + +test every_reason_is_one_line_and_its_length_is_the_text_length { + var d : usize = 0; + while (d < J_COUNT) { + var k : usize = 0; + while (k < J_LENS[d]) { + assert J_TEXTS[d][k] != '\t'; + if (k + 1 < J_LENS[d]) { assert J_TEXTS[d][k] != '\n'; } + k += 1; + } + if (d == J_REQ_RED as usize or d == J_LOG as usize) { + assert J_TEXTS[d][J_LENS[d] - 1] == ' '; + assert J_TEXTS[d][J_LENS[d] - 2] == ':'; + } else if (d == J_RED as usize) { + assert J_TEXTS[d][J_LENS[d] - 1] == ' '; + assert J_TEXTS[d][J_LENS[d] - 2] == 'k'; + } else { + assert J_TEXTS[d][J_LENS[d] - 1] == '\n'; + assert J_TEXTS[d][J_LENS[d] - 2] != '.'; + } + d += 1; + } + assert J_COUNT == 17; +} + +/// The class the table must hold for decision d, written once more as a rule. +fn t_class(d : u32) -> u8 { + if (d == 0) { return 'G'; } + if (d == 5) { return 'N'; } + if (d == 2 or d == 3 or d == 4 or d == 14) { return 'H'; } + return 'W'; +} + +test the_class_of_each_decision_is_g_w_h_or_n { + var d : u32 = 0; + while (d < 17) { + assert J_CLASS[d as usize] == t_class(d); + d += 1; + } + assert J_CLASS[J_HELD as usize] == 'H'; + assert J_CLASS[J_BEE as usize] == 'N'; + assert J_CLASS[J_CAPPED as usize] == 'W'; + assert J_CLASS[J_RED as usize] == 'H'; + assert J_CLASS[J_BAD as usize] == 'W'; +} + +test the_summary_pieces_have_the_lengths_they_claim { + var k : usize = 0; + while (k < 6) { + assert MJ_SUM_TEXTS[k][0] == ' '; + assert MJ_SUM_TEXTS[k][MJ_SUM_LENS[k] - 1] == ' '; + assert MJ_SUM_TEXTS[k][MJ_SUM_LENS[k] - 2] != ' '; + k += 1; + } + assert MJ_SUM_HEAD[MJ_SUM_HEAD_LEN - 1] == ' '; + assert MJ_SUM_HEAD[MJ_SUM_HEAD_LEN - 2] == ':'; + assert MJ_SUM_TAIL[MJ_SUM_TAIL_LEN - 1] == '\n'; + assert MJ_SUM_TAIL[MJ_SUM_TAIL_LEN - 2] == 'n'; + var j : usize = 0; + while (j < 9) { + assert T_NAMES[j][T_NLENS[j] - 1] != '\t'; + j += 1; + } + assert T_NAMES[4][54] == ')'; + assert T_PATHS[2][27] == '7'; +} + +// ---- tests: the readers ---- + +test the_tag_of_a_line_is_its_first_byte_when_a_tab_follows { + assert mj_tag("P\tx", 0, 3) == 'P'; + assert mj_tag("P\t", 0, 2) == 'P'; + assert mj_tag("P", 0, 1) == 0; + assert mj_tag("Px\t", 0, 3) == 0; + assert mj_tag("", 0, 0) == 0; + assert mj_tag("aK\tx", 1, 4) == 'K'; + assert mj_tag("aK\tx", 0, 4) == 0; +} + +test a_prefix_must_be_whole_and_at_the_start { + assert mj_starts("queen-77", 0, 8, "queen-", 6); + assert mj_starts("queen-", 0, 6, "queen-", 6); + assert mj_starts("queen", 0, 5, "queen-", 6) == false; + assert mj_starts("queenX77", 0, 8, "queen-", 6) == false; + assert mj_starts("xqueen-77", 0, 9, "queen-", 6) == false; + assert mj_starts("xqueen-77", 1, 9, "queen-", 6); + assert mj_starts("ab", 0, 2, "", 0); + assert mj_starts("queen-77", 0, 5, "queen-", 6) == false; +} + +test two_ranges_are_the_same_only_when_every_byte_is { + assert mj_same("abc", 0, 3, "abc", 0, 3); + assert mj_same("xabc", 1, 4, "abcy", 0, 3); + assert mj_same("abc", 0, 3, "abd", 0, 3) == false; + assert mj_same("abc", 0, 3, "xbc", 0, 3) == false; + assert mj_same("abc", 0, 3, "ab", 0, 2) == false; + assert mj_same("abc", 0, 2, "abc", 0, 3) == false; + assert mj_same("abc", 1, 1, "xyz", 2, 2); +} + +test a_check_name_ends_at_the_next_tab_or_the_end_of_the_line { + assert mj_name_end("K\tname\trest", 0) == 6; + assert mj_name_end("K\tname\n", 0) == 6; + assert mj_name_end("xK\tab\tc", 1) == 5; + assert mj_name_end("K\t\tc", 0) == 2; +} + +test an_empty_head_or_base_branch_is_unreadable { + var raw : [4096]u8 = undefined; + var n : usize = t_p(&raw, 4096, 0, 5, false, 0, 9, 0, 0, 0, 1, 5, 3600, 0, 0); + assert t_one(&raw, n) == J_BAD; + n = t_p(&raw, 4096, 0, 5, false, 0, 0, 9, 0, 0, 1, 5, 3600, 0, 0); + assert t_one(&raw, n) == J_BAD; + n = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 0, 0); + assert t_one(&raw, n) == J_PENDING; +} + +test the_changed_files_are_the_f_lines_of_that_pull_request_and_a_bad_line_is_not_counted { + var raw : [4096]u8 = undefined; + var buf : [256]u8 = undefined; + var len : [1]usize = undefined; + var n : usize = t_file(&raw, 4096, 0, 5, 0); + n = t_file(&raw, 4096, n, 5, 1); + n = t_file(&raw, 4096, n, 6, 3); + assert mj_files(&raw, n, 5, &buf, 256, &len) == 2; + assert len[0] == 47; + assert buf[0] == 's'; + assert buf[26] == '\n'; + assert buf[27] == 'd'; + assert buf[46] == '\n'; + assert mj_files(&raw, n, 6, &buf, 256, &len) == 1; + assert len[0] == 19; + assert mj_files(&raw, n, 56, &buf, 256, &len) == 0; + assert mj_files(&raw, n, 7, &buf, 256, &len) == 0; + // an empty path, a line of four fields, a line of another tag and a number that is not 5 are not counted + n = put_str(&raw, 4096, n, "F\t5\t\n", 5); + n = put_str(&raw, 4096, n, "F\t5\ta\tb\n", 8); + n = put_str(&raw, 4096, n, "K\t5\tx\n", 6); + n = put_str(&raw, 4096, n, "F\tx\ta\n", 6); + assert mj_files(&raw, n, 5, &buf, 256, &len) == 2; + assert len[0] == 47; + // a list that does not fit says so by its length + assert mj_files(&raw, n, 5, &buf, 30, &len) == 2; + assert len[0] == 47; + assert mj_files(&raw, 0, 5, &buf, 256, &len) == 0; + assert len[0] == 0; +} + +test the_named_paths_are_the_o_lines_of_that_pull_request_and_check { + var raw : [4096]u8 = undefined; + var buf : [256]u8 = undefined; + var len : [1]usize = undefined; + var n : usize = t_named(&raw, 4096, 0, 5, 4, 0); + n = t_named(&raw, 4096, n, 5, 4, 9); + n = t_named(&raw, 4096, n, 5, 6, 2); + n = t_named(&raw, 4096, n, 6, 4, 3); + assert mj_named(&raw, n, 5, T_NAMES[4], 0, 55, &buf, 256, &len) == 2; + assert len[0] == 27; + assert buf[0] == 's'; + assert buf[26] == '\n'; + assert mj_named(&raw, n, 5, T_NAMES[6], 0, 31, &buf, 256, &len) == 1; + assert len[0] == 29; + assert mj_named(&raw, n, 5, T_NAMES[7], 0, 23, &buf, 256, &len) == 0; + assert len[0] == 0; + assert mj_named(&raw, n, 6, T_NAMES[4], 0, 55, &buf, 256, &len) == 1; + assert len[0] == 19; + assert mj_named(&raw, n, 7, T_NAMES[4], 0, 55, &buf, 256, &len) == 0; + // a name that is only a prefix of the check's name is another check + assert mj_named(&raw, n, 5, T_NAMES[4], 0, 54, &buf, 256, &len) == 0; + n = put_str(&raw, 4096, n, "O\t5\tx\n", 6); + assert mj_named(&raw, n, 5, T_NAMES[7], 0, 23, &buf, 256, &len) == 0; +} + +test f_and_o_lines_find_their_pull_request_wherever_they_stand { + // The glue appends the logs it read after the whole listing: the lines still reach PR 5. + var raw : [8192]u8 = undefined; + var out : [8192]u8 = undefined; + var n : usize = t_red_pr(&raw, 8192, 0, 4); + n = t_row(&raw, 8192, n, 'B', 4, 250, 1); + n = t_green(&raw, 8192, n, 6); + var m : usize = merge_plan(&raw, n, &out, 8192); + assert t_has_named(&out, m, 'W', 5, J_LOG, 4); + assert t_has_log(&out, m, 5, 4, 300); + assert t_g(&out, m, 6); + n = t_file(&raw, 8192, n, 5, 0); + n = t_file(&raw, 8192, n, 5, 1); + n = t_named(&raw, 8192, n, 5, 4, 9); + m = merge_plan(&raw, n, &out, 8192); + assert t_g(&out, m, 5); + assert t_g(&out, m, 6); + assert t_lines(&out, m, 'L') == 0; + assert t_summary(&out, m, 2, 0, 0, 0, 0, 0, 2); + // the same lines under another number are no answer for PR 5 + n = t_red_pr(&raw, 8192, 0, 4); + n = t_row(&raw, 8192, n, 'B', 4, 250, 1); + n = t_file(&raw, 8192, n, 6, 0); + n = t_file(&raw, 8192, n, 6, 1); + n = t_named(&raw, 8192, n, 6, 4, 9); + assert t_one(&raw, n) == J_LOG; + m = merge_plan(&raw, n, &out, 8192); + assert t_has_log(&out, m, 5, 4, 300); +} + +test the_kind_of_a_red_row_is_read_from_its_job_name { + var raw : [4096]u8 = undefined; + var rs : [4096]u8 = undefined; + var pos : [128]usize = undefined; + var len : [1]usize = undefined; + var n : usize = t_p(&raw, 4096, 0, 5, false, 0, 0, 0, 0, 0, 1, 5, 3600, 9, 2); + n = t_row(&raw, 4096, n, 'K', 7, 100, 0); + n = t_row(&raw, 4096, n, 'K', 5, 101, 1); + n = t_row(&raw, 4096, n, 'K', 4, 102, 1); + n = t_row(&raw, 4096, n, 'K', 0, 103, 1); + n = put_str(&raw, 4096, n, "K\tOther/claw-sarif\t500\tCOMPLETED\tFAILURE\n", 41); + n = put_str(&raw, 4096, n, "K\tGate Topology/claw-sarif-2\t501\tCOMPLETED\tFAILURE\n", 51); + n = put_str(&raw, 4096, n, "K\tclaw-sarif\t502\tCOMPLETED\tFAILURE\n", 35); + n = put_str(&raw, 4096, n, "K\tx/validate\t503\tCOMPLETED\tFAILURE\n", 35); + n = put_str(&raw, 4096, n, "K\tx/validate2\t504\tCOMPLETED\tFAILURE\n", 36); + assert mj_rows(&raw, 0, n, &rs, 4096, &pos, &len) == 9; + assert mj_first_kind(&rs, len[0], KIND_NONE) == 0; + assert mj_first_kind(&rs, len[0], KIND_SARIF) == 1; + assert mj_first_kind(&rs, len[0], KIND_OTHER) == 2; + assert mj_first_kind(&rs, len[0], KIND_REQ) == 3; + // the job part of the name decides, whatever the workflow part says + assert mj_first_kind(&rs, 0, KIND_SARIF) == -1; + // the table is the K lines without their tags, one per line, and pos names the K lines in raw + assert rs[0] == 'S'; + assert pos[1] == pos[0] + 48; + assert raw[pos[2]] == 'K'; + assert mj_name_end(&raw, pos[2]) - pos[2] - 2 == 55; +} + +test a_superseded_or_unreadable_or_passed_row_is_no_red { + var rs : [512]u8 = undefined; + var n : usize = put_str(&rs, 512, 0, "a/job\t5\tCOMPLETED\tFAILURE\n", 26); + n = put_str(&rs, 512, n, "a/job\t9\tCOMPLETED\tSUCCESS\n", 26); + assert mj_first_kind(&rs, n, KIND_OTHER) == -1; + n = put_str(&rs, 512, 0, "a/job\t5\tCOMPLETED\tFAILURE\n", 26); + assert mj_first_kind(&rs, n, KIND_OTHER) == 0; + n = put_str(&rs, 512, 0, "a/job\tx\tCOMPLETED\tFAILURE\n", 26); + assert mj_first_kind(&rs, n, KIND_OTHER) == -1; + n = put_str(&rs, 512, 0, "a/job\t5\tQUEUED\t-\n", 17); + assert mj_first_kind(&rs, n, KIND_OTHER) == -1; + n = put_str(&rs, 512, 0, "ctx\t-\tCONTEXT\tFAILURE\n", 22); + assert mj_first_kind(&rs, n, KIND_OTHER) == 0; +} + +/// The kind of the first row of the table rs[0..n). +fn t_kind(rs : []const u8, n : usize) -> u8 { + if (mj_first_kind(rs, n, KIND_REQ) == 0) { return KIND_REQ; } + if (mj_first_kind(rs, n, KIND_SARIF) == 0) { return KIND_SARIF; } + if (mj_first_kind(rs, n, KIND_OTHER) == 0) { return KIND_OTHER; } + return KIND_NONE; +} + +test only_the_job_named_claw_sarif_is_the_known_infra_red_and_only_four_jobs_are_required { + var rs : [256]u8 = undefined; + var n : usize = put_str(&rs, 256, 0, "Other/claw-sarif\t500\tCOMPLETED\tFAILURE\n", 39); + assert t_kind(&rs, n) == KIND_SARIF; + n = put_str(&rs, 256, 0, "claw-sarif\t502\tCOMPLETED\tFAILURE\n", 33); + assert t_kind(&rs, n) == KIND_SARIF; + n = put_str(&rs, 256, 0, "Gate Topology/claw-sarif-2\t501\tCOMPLETED\tFAILURE\n", 49); + assert t_kind(&rs, n) == KIND_OTHER; + n = put_str(&rs, 256, 0, "Gate Topology/xclaw-sarif\t501\tCOMPLETED\tFAILURE\n", 48); + assert t_kind(&rs, n) == KIND_OTHER; + n = put_str(&rs, 256, 0, "x/validate\t503\tCOMPLETED\tFAILURE\n", 33); + assert t_kind(&rs, n) == KIND_REQ; + n = put_str(&rs, 256, 0, "validate\t503\tCOMPLETED\tFAILURE\n", 31); + assert t_kind(&rs, n) == KIND_REQ; + n = put_str(&rs, 256, 0, "x/validate2\t504\tCOMPLETED\tFAILURE\n", 34); + assert t_kind(&rs, n) == KIND_OTHER; + n = put_str(&rs, 256, 0, "x/own-language\t504\tCOMPLETED\tFAILURE\n", 37); + assert t_kind(&rs, n) == KIND_REQ; + n = put_str(&rs, 256, 0, "x/parse-ratchet\t504\tCOMPLETED\tFAILURE\n", 38); + assert t_kind(&rs, n) == KIND_REQ; + n = put_str(&rs, 256, 0, "x/check-linked-issue\t504\tCOMPLETED\tFAILURE\n", 43); + assert t_kind(&rs, n) == KIND_REQ; + n = put_str(&rs, 256, 0, "x/validate\t503\tCOMPLETED\tSUCCESS\n", 33); + assert t_kind(&rs, n) == KIND_NONE; +} diff --git a/tools/census/quiet.txt b/tools/census/quiet.txt index d4d9b07958..d07ac34ed8 100644 --- a/tools/census/quiet.txt +++ b/tools/census/quiet.txt @@ -1,11 +1,11 @@ GATE STEPS WHOSE PASS SURVIVES THE SUBJECT GOING MISSING workflow files read 67 - steps in a quiet shape 33 - named a path but not quiet 239 (--excluded prints them) + steps in a quiet shape 35 + named a path but not quiet 243 (--excluded prints them) by shape: - failure branch passes 14 `… 2>/dev/null … || echo PASSED` + failure branch passes 16 `… 2>/dev/null … || echo PASSED` a count that reads zero 9 `$(… 2>/dev/null | wc -l)` gated on the file existing 10 often legitimate, reported apart an if that falls through 0 an if whose then-branch exits and whose fi is followed by a pass @@ -14,8 +14,8 @@ GATE STEPS WHOSE PASS SURVIVES THE SUBJECT GOING MISSING a tracked path, ABSENT 0 <- guarding nothing right now a tracked path, present 12 the run builds it 7 absence from a checkout says nothing - a variable in the path 7 depends on the run - no path ANYWHERE in the step 7 cannot be checked -- NOT the same as absent + a variable in the path 8 depends on the run + no path ANYWHERE in the step 8 cannot be checked -- NOT the same as absent No quiet gate names a tracked path that is missing today. That is a result, not an absence of one: the shapes are there and the subjects