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