From b5453af7d698d415b719aa7feefe5cbd2c97a9b4 Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Sun, 18 Oct 2026 16:22:24 +0700 Subject: [PATCH] claw: sarif_feed -- a spec that does not parse becomes a code-scanning alert on the pull request (Closes #10356) specs/claw/sarif_feed.t27 maps what `t27c spec-status` and `t27c parse` print for a changed .t27 file onto the six-field findings sarif.t27 renders, and gen/c/claw/sarif_feed.c is its lowering. The new claw-sarif job of gate-topology.yml mints the TRI CLAW token with exactly the code-scanning-sarif row of github_surface.t27 (security_events write, contents read), runs the signed t27o-v0.1.0 t27c over the changed specs and uploads the document with github/codeql-action/upload-sarif. That row moves PLANNED -> LIVE. NOPARSE -> T27C001 error at the line:col t27c names; UNWRITTEN -> T27C004 warning; an unknown status or a short record marks the run partial (executionSuccessful false) instead of reading as clean. T27C002/T27C003 need the zig-backed test runner and are not fed yet. spec-guards regenerates and diffs the committed C. Shrink offset: spent scripts/ultra_engine_v70_ultimate.py and scripts/gen_w{660,669,670}.py, all ported under specs/port/scripts/ and called by nothing. Co-Authored-By: Claude Sonnet 5.5 --- .github/workflows/gate-topology.yml | 26 + .github/workflows/spec-guards.yml | 3 +- AGENTS.md | 1 + docs/claw/README.md | 13 + gen/c/claw/sarif_feed.c | 842 +++++++++++++++++++++++++++ scripts/gen_w660.py | 145 ----- scripts/gen_w669.py | 145 ----- scripts/gen_w670.py | 145 ----- scripts/ultra_engine_v70_ultimate.py | 174 ------ specs/claw/github_surface.t27 | 6 +- specs/claw/sarif_feed.t27 | 260 +++++++++ 11 files changed, 1147 insertions(+), 613 deletions(-) create mode 100644 gen/c/claw/sarif_feed.c delete mode 100644 scripts/gen_w660.py delete mode 100644 scripts/gen_w669.py delete mode 100644 scripts/gen_w670.py delete mode 100644 scripts/ultra_engine_v70_ultimate.py create mode 100644 specs/claw/sarif_feed.t27 diff --git a/.github/workflows/gate-topology.yml b/.github/workflows/gate-topology.yml index eb31d01371..0865b5c19f 100644 --- a/.github/workflows/gate-topology.yml +++ b/.github/workflows/gate-topology.yml @@ -110,3 +110,29 @@ jobs: I=$(head -1 ../bodies | gh api -X POST repos/$r/check-runs --input - -q .id); tail -n +2 ../bodies | while read -r j; do printf '%s' "$j" | gh api -X PATCH repos/$r/check-runs/$I --input - > /dev/null; done ); [ $? = 0 ] || echo "::warning::claw/t27f did not check $r#$n" done done + + # claw/sarif: a changed .t27 spec that does not parse, or declares functions with no body, becomes a code-scanning + # alert from the TRI CLAW App. The law is specs/claw/sarif_feed.t27 (gen/c/claw/sarif_feed.c); this job mints the + # token (security_events write + contents read, the code-scanning-sarif row of specs/claw/github_surface.t27), + # runs the signed t27o release t27c over the changed specs and uploads what the law prints. + claw-sarif: + if: github.event_name == 'pull_request' + runs-on: ubuntu-24.04 + permissions: {contents: read} + steps: + - id: have + env: {K: "${{ secrets.CLAW_APP_PRIVATE_KEY }}", I: "${{ vars.CLAW_APP_ID }}"} + run: if [ -n "$K" ] && [ -n "$I" ]; then echo ok=1 >> "$GITHUB_OUTPUT"; else echo "claw/sarif uploaded nothing - secret CLAW_APP_PRIVATE_KEY or variable CLAW_APP_ID is not set (docs/claw/README.md)." >> "$GITHUB_STEP_SUMMARY"; fi + - {id: tok, if: steps.have.outputs.ok, uses: actions/create-github-app-token@v2, with: {app-id: "${{ vars.CLAW_APP_ID }}", private-key: "${{ secrets.CLAW_APP_PRIVATE_KEY }}", owner: gHashTag, repositories: t27, permission-security-events: write, permission-contents: read}} + - {if: steps.have.outputs.ok, uses: actions/checkout@v6, with: {fetch-depth: 2}} + - {if: steps.have.outputs.ok, uses: sigstore/cosign-installer@v3} + - if: steps.have.outputs.ok + env: {V: t27o-v0.1.0} + run: | + cd /tmp; B=https://github.com/gHashTag/t27/releases/download/$V + for f in t27c checksums-$V.txt; do curl -fsSLo $f $B/$f; curl -fsSLo $f.sigstore.json $B/$f.sigstore.json + cosign verify-blob $f --bundle $f.sigstore.json --certificate-oidc-issuer https://token.actions.githubusercontent.com --certificate-identity-regexp "^https://github.com/gHashTag/t27/.github/workflows/t27o-release.yml@refs/tags/$V$"; done + sha256sum -c --ignore-missing checksums-$V.txt; chmod +x t27c; cd "$GITHUB_WORKSPACE" + printf '#include "gen/c/claw/sarif_feed.c"\n#include \n#include \nint main(void){size_t k=1<<22,n;uint8_t*b=malloc(k),*o=malloc(k);n=fread(b,1,k,stdin);size_t m=feed_sarif(b,n,o,k);if(n==k||m>k)return 3;fwrite(o,1,m,stdout);return 0;}\n' | cc -O1 -w -I . -x c -o /tmp/feed - + git diff --name-only --diff-filter=d HEAD^1 HEAD -- '*.t27' | while read -r f; do printf '%s\t%s\t%s\n' "$f" "$(/tmp/t27c spec-status "$f" 2>&1 &1 >/dev/null /tmp/claw.sarif + - {if: steps.have.outputs.ok, uses: github/codeql-action/upload-sarif@v4, with: {sarif_file: /tmp/claw.sarif, category: claw/sarif, token: "${{ steps.tok.outputs.token }}"}} diff --git a/.github/workflows/spec-guards.yml b/.github/workflows/spec-guards.yml index 51f2577b00..5de6a1f4d6 100644 --- a/.github/workflows/spec-guards.yml +++ b/.github/workflows/spec-guards.yml @@ -126,8 +126,9 @@ jobs: run: | ./target/release/t27c gen-c specs/claw/gates.t27 > gen/c/claw/gates.c ./target/release/t27c gen-c specs/claw/t27f_gate.t27 > gen/c/claw/t27f_gate.c + ./target/release/t27c gen-c specs/claw/sarif_feed.t27 > gen/c/claw/sarif_feed.c ./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/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/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/AGENTS.md b/AGENTS.md index 441713f82e..6fb4789576 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -142,6 +142,7 @@ reusable workflow. Epic #9271. | Docs, release checklist, competitors | [`docs/claw/`](docs/claw/README.md) | | The GitHub App | [github.com/apps/tri-claw](https://github.com/apps/tri-claw), id 5166765, installed on all repositories. It is the reviewer-bee identity ([`tools/bees/README.md`](tools/bees/README.md)): bees approve under its name, so a review is never the author approving itself. It also posts the `claw/t27f` check below | | T27F check `claw/t27f` (#10268): changed specs and their direct importers, judged by their seals | law [`specs/claw/t27f_gate.t27`](specs/claw/t27f_gate.t27), lowered to `gen/c/claw/t27f_gate.c`; run every 10 minutes by the `claw-t27f` job of `.github/workflows/gate-topology.yml` | +| SARIF alerts `claw/sarif` (#10356): a changed spec that does not parse or is unwritten, as a code-scanning alert | adapter [`specs/claw/sarif_feed.t27`](specs/claw/sarif_feed.t27) over [`specs/claw/sarif.t27`](specs/claw/sarif.t27), lowered to `gen/c/claw/sarif_feed.c`; run on every pull request by the `claw-sarif` job of `.github/workflows/gate-topology.yml` | The `claw/t27f` check: one scheduled job in this repository posts it on every open pull request that touches `.t27`, in each repository the App is installed diff --git a/docs/claw/README.md b/docs/claw/README.md index 56f280ffd8..d2dc9af471 100644 --- a/docs/claw/README.md +++ b/docs/claw/README.md @@ -13,6 +13,7 @@ behind one front door, with the gate logic in `.t27` specs. Epic | Drift audit in CI | `gate-topology` workflow, "claw drift" step | T3, this branch | | GitHub App manifest | release step, see `docs/claw/RELEASE.md` | T4, deferred to App setup | | T27F check in every `.t27` repository | `specs/claw/t27f_gate.t27`, the `claw-t27f` job of `gate-topology.yml` | #10268, live once the owner steps below are done | +| SARIF code-scanning alerts on a pull request | `specs/claw/sarif_feed.t27` over `specs/claw/sarif.t27`, the `claw-sarif` job of `gate-topology.yml` | #10356, live once the owner steps below are done | | Service skeleton | Railway, thin I/O glue only | T5, merged (spec contract) | | Positioning / pricing | `docs/claw/competitors.md` | T6, started | @@ -31,6 +32,18 @@ steps, it posts nothing: 3. Set the secret `CLAW_APP_PRIVATE_KEY` in gHashTag/t27 to the App's private key. 4. Set the variable `CLAW_APP_ID` in gHashTag/t27 to `5166765`. +## SARIF code-scanning alerts on a pull request (#10356) + +On every pull request in gHashTag/t27 the `claw-sarif` job of `gate-topology.yml` runs the signed t27o-v0.1.0 +t27c (`spec-status` and `parse`) over each changed `.t27` file and uploads one SARIF run +(category `claw/sarif`) with the token of the `code-scanning-sarif` row of `specs/claw/github_surface.t27`: +`security_events` write and `contents` read, nothing else. A spec that does not parse is rule T27C001 (error, +at the line:col t27c names); a spec with unwritten functions is T27C004 (warning). The adapter is +`specs/claw/sarif_feed.t27` (lowered to `gen/c/claw/sarif_feed.c`); the document is `specs/claw/sarif.t27`. +Rules T27C002 and T27C003 need the t27c test runner, which needs zig, and are not fed yet. It needs the secret and +variable of steps 3 and 4 above, the App permission `security_events: write`, and code scanning enabled on the +repository; without them the job uploads nothing and says so in its step summary. + ## 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/sarif_feed.c b/gen/c/claw/sarif_feed.c new file mode 100644 index 0000000000..1701e8f953 --- /dev/null +++ b/gen/c/claw/sarif_feed.c @@ -0,0 +1,842 @@ +/* ============================================================================ + Generated from t27 spec: ClawSarifFeed + 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 CLAWSARIFFEED_H +#define CLAWSARIFFEED_H + +/* ------------------------------------------------------- + Constants + ------------------------------------------------------- */ + +#define TSV_CAP 65536 +#define REFUSED_LINE "fatal\tparse-error\tx\t1\t1\tx\n" +#define REFUSED_LINE_LEN 26 +#define DEFAULT_MAX_RESULTS 1000 +#define ID_LEN 7 +#define INFO_URL "https://github.com/gHashTag/t27" +#define INFO_URL_LEN 31 +static const const char* LEVELS[3] = { "error", "warning", "note" }; +static const size_t LEVEL_LENS[3] = { 5, 7, 4 }; +#define MSG_MAX 500 +static const const char* RULE_IDS[4] = { "T27C001", "T27C002", "T27C003", "T27C004" }; +static const const char* RULE_SLUGS[4] = { "parse-error", "test-failed", "vacuous-test", "unwritten-spec" }; +static const size_t RULE_SLUG_LENS[4] = { 11, 11, 12, 14 }; +static const const char* RULE_TEXTS[4] = { "t27c could not parse the spec", "a test block of the spec failed when t27c test-report ran it", "a test passed with zero runtime asserts executed, so it checks nothing", "the spec declares functions with no body (spec-status UNWRITTEN)" }; +static const size_t RULE_TEXT_LENS[4] = { 29, 60, 70, 64 }; +#define SCHEMA_URL "https://json.schemastore.org/sarif-2.1.0.json" +#define SCHEMA_URL_LEN 45 +#define SEV_ERROR 0 +#define SEV_NONE 255 +#define SEV_NOTE 2 +#define SEV_WARNING 1 + +/* ------------------------------------------------------- + Function prototypes + ------------------------------------------------------- */ + +size_t copy_span(uint8_t* dst, size_t cap, size_t w, uint8_t* raw, size_t s, size_t e); +size_t after_marker(uint8_t* raw, size_t s, size_t e); +size_t digits_end(uint8_t* raw, size_t p, size_t e); +size_t put_noparse(uint8_t* dst, size_t cap, size_t w, uint8_t* raw, size_t ps, size_t pe, size_t ms, size_t me); +size_t put_unwritten(uint8_t* dst, size_t cap, size_t w, uint8_t* raw, size_t ps, size_t pe); +size_t feed_tsv(uint8_t* raw, size_t n, uint8_t* out, size_t cap); +size_t feed_sarif(uint8_t* raw, size_t n, uint8_t* out, size_t out_cap); +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); +bool parse_line(uint8_t* text, size_t ls, size_t le, size_t* f); +int32_t parse_num(uint8_t* text, size_t s, size_t e); +bool path_ok(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); +size_t put_json_text(uint8_t* dst, size_t cap, size_t w, uint8_t* text, size_t s, size_t e, size_t maxn); +size_t put_key(uint8_t* dst, size_t cap, size_t w, const char* s, size_t n); +size_t put_q(uint8_t* dst, size_t cap, size_t w, const char* s, size_t n); +size_t put_result(uint8_t* dst, size_t cap, size_t w, uint8_t* text, size_t* f); +size_t put_rule(uint8_t* dst, size_t cap, size_t w, size_t i); +uint32_t region_of(int32_t n); +int32_t rule_of(uint8_t* text, size_t s, size_t e); +uint32_t sarif_dropped(uint8_t* text, size_t len, uint32_t max_results); +bool sarif_fits(size_t needed, size_t out_cap); +uint32_t sarif_invalid(uint8_t* text, size_t len); +size_t sarif_render(uint8_t* text, size_t len, uint32_t max_results, uint8_t* out, size_t out_cap); +uint32_t sarif_valid(uint8_t* text, size_t len); +uint8_t sev_of(uint8_t* text, size_t s, size_t e); +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 + ------------------------------------------------------- */ + +size_t copy_span(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)) { + uint8_t b = raw[i]; + if ((((b == '\t') || (b == '\n')) || (b == 13))) { + b = ' '; + } + x = put_byte(dst, cap, x, b); + i += 1; + } + return x; +} + +size_t after_marker(uint8_t* raw, size_t s, size_t e) { + size_t i = s; + while (((i + 9) <= e)) { + if (field_eq(raw, i, (i + 9), " at line ", 9)) { + return (i + 9); + } + i += 1; + } + return e; +} + +size_t digits_end(uint8_t* raw, size_t p, size_t e) { + size_t i = p; + while ((i < e)) { + if (((raw[i] < '0') || (raw[i] > '9'))) { + return i; + } + i += 1; + } + return e; +} + +size_t put_noparse(uint8_t* dst, size_t cap, size_t w, uint8_t* raw, size_t ps, size_t pe, size_t ms, size_t me) { + uint32_t line = 1; + uint32_t col = 1; + size_t at = after_marker(raw, ms, me); + size_t le = digits_end(raw, at, me); + if ((le > at)) { + line = region_of(parse_num(raw, at, le)); + if (((le < me) && (raw[le] == ':'))) { + col = region_of(parse_num(raw, (le + 1), digits_end(raw, (le + 1), me))); + } + } + size_t x = put_str(dst, cap, w, "error\tparse-error\t", 18); + x = copy_span(dst, cap, x, raw, ps, pe); + x = put_byte(dst, cap, x, '\t'); + x = put_count(dst, cap, x, line); + x = put_byte(dst, cap, x, '\t'); + x = put_count(dst, cap, x, col); + x = put_byte(dst, cap, x, '\t'); + if ((me > ms)) { + x = copy_span(dst, cap, x, raw, ms, me); + } else { + x = put_str(dst, cap, x, RULE_TEXTS[0], RULE_TEXT_LENS[0]); + } + return put_byte(dst, cap, x, '\n'); +} + +size_t put_unwritten(uint8_t* dst, size_t cap, size_t w, uint8_t* raw, size_t ps, size_t pe) { + size_t x = put_str(dst, cap, w, "warning\tunwritten-spec\t", 23); + x = copy_span(dst, cap, x, raw, ps, pe); + x = put_str(dst, cap, x, "\t1\t1\t", 5); + x = put_str(dst, cap, x, RULE_TEXTS[3], RULE_TEXT_LENS[3]); + return put_byte(dst, cap, x, '\n'); +} + +size_t feed_tsv(uint8_t* raw, size_t n, uint8_t* out, size_t cap) { + size_t w = 0; + size_t ls = 0; + while ((ls < n)) { + size_t le = line_end(raw, n, ls); + if ((le > ls)) { + size_t t1 = field_end(raw, le, ls); + size_t t2 = field_end(raw, le, (t1 + 1)); + if (((t1 >= le) || (t2 >= le))) { + w = put_str(out, cap, w, REFUSED_LINE, REFUSED_LINE_LEN); + } else if (field_eq(raw, (t1 + 1), t2, "NOPARSE", 7)) { + w = put_noparse(out, cap, w, raw, ls, t1, (t2 + 1), le); + } else if (field_eq(raw, (t1 + 1), t2, "UNWRITTEN", 9)) { + w = put_unwritten(out, cap, w, raw, ls, t1); + } else if ((((field_eq(raw, (t1 + 1), t2, "IMPLEMENTED", 11) == false) && (field_eq(raw, (t1 + 1), t2, "PARTIAL", 7) == false)) && (field_eq(raw, (t1 + 1), t2, "NOFN", 4) == false))) { + w = put_str(out, cap, w, REFUSED_LINE, REFUSED_LINE_LEN); + } + } + size_t nx = le; + while (((nx < n) && (raw[nx] != '\n'))) { + nx += 1; + } + ls = (nx + 1); + } + return w; +} + +size_t feed_sarif(uint8_t* raw, size_t n, uint8_t* out, size_t out_cap) { + uint8_t tsv[65536] = {0}; + size_t tn = feed_tsv(raw, n, &tsv, TSV_CAP); + if ((tn > TSV_CAP)) { + tn = put_str(&tsv, TSV_CAP, 0, REFUSED_LINE, REFUSED_LINE_LEN); + } + return sarif_render(&tsv, tn, DEFAULT_MAX_RESULTS, out, out_cap); +} + +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; +} + +bool parse_line(uint8_t* text, size_t ls, size_t le, size_t* f) { + size_t pos = ls; + size_t k = 0; + while ((k < 6)) { + size_t fe = le; + if ((k < 5)) { + fe = field_end(text, le, pos); + if ((fe >= le)) { + return false; + } + } + f[(2 * k)] = pos; + f[((2 * k) + 1)] = fe; + pos = (fe + 1); + k += 1; + } + if ((sev_of(text, f[0], f[1]) == SEV_NONE)) { + return false; + } + if ((rule_of(text, f[2], f[3]) < 0)) { + return false; + } + if ((path_ok(text, f[4], f[5]) == false)) { + return false; + } + if ((parse_num(text, f[6], f[7]) < 0)) { + return false; + } + if ((parse_num(text, f[8], f[9]) < 0)) { + return false; + } + if ((f[11] <= f[10])) { + return false; + } + return true; +} + +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; +} + +bool path_ok(uint8_t* text, size_t s, size_t e) { + if ((e <= s)) { + return false; + } + if ((text[s] == '/')) { + return false; + } + size_t i = s; + while ((i < e)) { + uint8_t b = text[i]; + if (((b < 33) || (b > 126))) { + return false; + } + if (((((((b == 34) || (b == 92)) || (b == 37)) || (b == 35)) || (b == 63)) || (b == 58))) { + return false; + } + if (((b == '.') && ((i + 1) < e))) { + if ((text[(i + 1)] == '.')) { + return false; + } + } + i += 1; + } + return true; +} + +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); +} + +size_t put_json_text(uint8_t* dst, size_t cap, size_t w, uint8_t* text, size_t s, size_t e, size_t maxn) { + size_t x = w; + size_t i = s; + while (((i < e) && ((i - s) < maxn))) { + uint8_t b = text[i]; + if ((b == 34)) { + x = put_byte(dst, cap, x, 92); + x = put_byte(dst, cap, x, 34); + } else if ((b == 92)) { + x = put_byte(dst, cap, x, 92); + x = put_byte(dst, cap, x, 92); + } else if (((b < 32) || (b > 126))) { + x = put_byte(dst, cap, x, '?'); + } else { + x = put_byte(dst, cap, x, b); + } + i += 1; + } + if ((i < e)) { + x = put_str(dst, cap, x, "...", 3); + } + return x; +} + +size_t put_key(uint8_t* dst, size_t cap, size_t w, const char* s, size_t n) { + size_t x = put_q(dst, cap, w, s, n); + x = put_byte(dst, cap, x, ':'); + return x; +} + +size_t put_q(uint8_t* dst, size_t cap, size_t w, const char* s, size_t n) { + size_t x = put_byte(dst, cap, w, 34); + x = put_str(dst, cap, x, s, n); + x = put_byte(dst, cap, x, 34); + return x; +} + +size_t put_result(uint8_t* dst, size_t cap, size_t w, uint8_t* text, size_t* f) { + size_t rule = ((size_t)(rule_of(text, f[2], f[3]))); + size_t sev = ((size_t)(sev_of(text, f[0], f[1]))); + size_t x = put_byte(dst, cap, w, '{'); + x = put_key(dst, cap, x, "ruleId", 6); + x = put_q(dst, cap, x, RULE_IDS[rule], ID_LEN); + x = put_byte(dst, cap, x, ','); + x = put_key(dst, cap, x, "ruleIndex", 9); + x = put_count(dst, cap, x, ((uint32_t)(rule))); + x = put_byte(dst, cap, x, ','); + x = put_key(dst, cap, x, "level", 5); + x = put_q(dst, cap, x, LEVELS[sev], LEVEL_LENS[sev]); + x = put_byte(dst, cap, x, ','); + x = put_key(dst, cap, x, "message", 7); + x = put_byte(dst, cap, x, '{'); + x = put_key(dst, cap, x, "text", 4); + x = put_byte(dst, cap, x, 34); + x = put_json_text(dst, cap, x, text, f[10], f[11], MSG_MAX); + x = put_byte(dst, cap, x, 34); + x = put_byte(dst, cap, x, '}'); + x = put_byte(dst, cap, x, ','); + x = put_key(dst, cap, x, "locations", 9); + x = put_str(dst, cap, x, "[{", 2); + x = put_key(dst, cap, x, "physicalLocation", 16); + x = put_byte(dst, cap, x, '{'); + x = put_key(dst, cap, x, "artifactLocation", 16); + x = put_byte(dst, cap, x, '{'); + x = put_key(dst, cap, x, "uri", 3); + x = put_byte(dst, cap, x, 34); + size_t i = f[4]; + while ((i < f[5])) { + x = put_byte(dst, cap, x, text[i]); + i += 1; + } + x = put_byte(dst, cap, x, 34); + x = put_str(dst, cap, x, "},", 2); + x = put_key(dst, cap, x, "region", 6); + x = put_byte(dst, cap, x, '{'); + x = put_key(dst, cap, x, "startLine", 9); + x = put_count(dst, cap, x, region_of(parse_num(text, f[6], f[7]))); + x = put_byte(dst, cap, x, ','); + x = put_key(dst, cap, x, "startColumn", 11); + x = put_count(dst, cap, x, region_of(parse_num(text, f[8], f[9]))); + x = put_str(dst, cap, x, "}}}]}", 5); + return x; +} + +size_t put_rule(uint8_t* dst, size_t cap, size_t w, size_t i) { + size_t x = put_byte(dst, cap, w, '{'); + x = put_key(dst, cap, x, "id", 2); + x = put_q(dst, cap, x, RULE_IDS[i], ID_LEN); + x = put_byte(dst, cap, x, ','); + x = put_key(dst, cap, x, "name", 4); + x = put_q(dst, cap, x, RULE_SLUGS[i], RULE_SLUG_LENS[i]); + x = put_byte(dst, cap, x, ','); + x = put_key(dst, cap, x, "shortDescription", 16); + x = put_byte(dst, cap, x, '{'); + x = put_key(dst, cap, x, "text", 4); + x = put_q(dst, cap, x, RULE_TEXTS[i], RULE_TEXT_LENS[i]); + x = put_byte(dst, cap, x, '}'); + x = put_byte(dst, cap, x, '}'); + return x; +} + +uint32_t region_of(int32_t n) { + if ((n < 1)) { + return 1; + } + return ((uint32_t)(n)); +} + +int32_t rule_of(uint8_t* text, size_t s, size_t e) { + for (size_t i = 0; i < 4; i++) { + if (field_eq(text, s, e, RULE_SLUGS[i], RULE_SLUG_LENS[i])) { + return ((int32_t)(i)); + } + } + return -1; +} + +uint32_t sarif_dropped(uint8_t* text, size_t len, uint32_t max_results) { + uint32_t valid = sarif_valid(text, len); + uint32_t over = 0; + if ((valid > max_results)) { + over = (valid - max_results); + } + return (sarif_invalid(text, len) + over); +} + +bool sarif_fits(size_t needed, size_t out_cap) { + return (needed <= out_cap); +} + +uint32_t sarif_invalid(uint8_t* text, size_t len) { + size_t f[12] = {0}; + uint32_t n = 0; + size_t ls = 0; + while ((ls < len)) { + size_t le = line_end(text, len, ls); + if ((le > ls)) { + if ((parse_line(text, ls, le, &f) == false)) { + n += 1; + } + } + size_t nx = le; + while (((nx < len) && (text[nx] != '\n'))) { + nx += 1; + } + ls = (nx + 1); + } + return n; +} + +size_t sarif_render(uint8_t* text, size_t len, uint32_t max_results, uint8_t* out, size_t out_cap) { + size_t w = 0; + w = put_byte(out, out_cap, w, '{'); + w = put_key(out, out_cap, w, "$schema", 7); + w = put_q(out, out_cap, w, SCHEMA_URL, SCHEMA_URL_LEN); + w = put_byte(out, out_cap, w, ','); + w = put_key(out, out_cap, w, "version", 7); + w = put_q(out, out_cap, w, "2.1.0", 5); + w = put_byte(out, out_cap, w, ','); + w = put_key(out, out_cap, w, "runs", 4); + w = put_str(out, out_cap, w, "[{", 2); + w = put_key(out, out_cap, w, "tool", 4); + w = put_byte(out, out_cap, w, '{'); + w = put_key(out, out_cap, w, "driver", 6); + w = put_byte(out, out_cap, w, '{'); + w = put_key(out, out_cap, w, "name", 4); + w = put_q(out, out_cap, w, "t27c", 4); + w = put_byte(out, out_cap, w, ','); + w = put_key(out, out_cap, w, "informationUri", 14); + w = put_q(out, out_cap, w, INFO_URL, INFO_URL_LEN); + w = put_byte(out, out_cap, w, ','); + w = put_key(out, out_cap, w, "rules", 5); + w = put_byte(out, out_cap, w, '['); + for (size_t r = 0; r < 4; r++) { + if ((r > 0)) { + w = put_byte(out, out_cap, w, ','); + } + w = put_rule(out, out_cap, w, r); + } + w = put_str(out, out_cap, w, "]}},", 4); + w = put_key(out, out_cap, w, "results", 7); + w = put_byte(out, out_cap, w, '['); + size_t f[12] = {0}; + uint32_t emitted = 0; + size_t ls = 0; + while ((ls < len)) { + size_t le = line_end(text, len, ls); + if (((le > ls) && (emitted < max_results))) { + if (parse_line(text, ls, le, &f)) { + if ((emitted > 0)) { + w = put_byte(out, out_cap, w, ','); + } + w = put_result(out, out_cap, w, text, &f); + emitted += 1; + } + } + size_t nx = le; + while (((nx < len) && (text[nx] != '\n'))) { + nx += 1; + } + ls = (nx + 1); + } + w = put_str(out, out_cap, w, "],", 2); + w = put_key(out, out_cap, w, "invocations", 11); + w = put_str(out, out_cap, w, "[{", 2); + w = put_key(out, out_cap, w, "executionSuccessful", 19); + if ((sarif_dropped(text, len, max_results) == 0)) { + w = put_str(out, out_cap, w, "true", 4); + } else { + w = put_str(out, out_cap, w, "false", 5); + } + w = put_str(out, out_cap, w, "}]}]}", 5); + return w; +} + +uint32_t sarif_valid(uint8_t* text, size_t len) { + size_t f[12] = {0}; + uint32_t n = 0; + size_t ls = 0; + while ((ls < len)) { + size_t le = line_end(text, len, ls); + if ((le > ls)) { + if (parse_line(text, ls, le, &f)) { + n += 1; + } + } + size_t nx = le; + while (((nx < len) && (text[nx] != '\n'))) { + nx += 1; + } + ls = (nx + 1); + } + return n; +} + +uint8_t sev_of(uint8_t* text, size_t s, size_t e) { + if (field_eq(text, s, e, "error", 5)) { + return SEV_ERROR; + } + if (field_eq(text, s, e, "warning", 7)) { + return SEV_WARNING; + } + if (field_eq(text, s, e, "info", 4)) { + return SEV_NOTE; + } + if (field_eq(text, s, e, "note", 4)) { + return SEV_NOTE; + } + return SEV_NONE; +} + +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_refused_line_length_is_pinned(void) { + uint8_t b[64] = {0}; + size_t k = put_str(&b, 64, 0, REFUSED_LINE, REFUSED_LINE_LEN); + assert((k == 26)); + assert((b[(k - 1)] == '\n')); +} + +void test_a_parse_failure_is_an_error_at_the_line_t27c_names(void) { + uint8_t r[256] = {0}; + size_t n = put_str(&r, 256, 0, "specs/a.t27\tNOPARSE\tError: Parse error: Expected RParen, got Eof ('') at line 3:1\n", 82); + uint8_t out[8192] = {0}; + size_t d = feed_sarif(&r, n, &out, 8192); + assert((sarif_fits(d, 8192) == true)); + assert((buf_has(&out, d, "\"ruleId\":\"T27C001\"", 18) == true)); + assert((buf_has(&out, d, "\"level\":\"error\"", 15) == true)); + assert((buf_has(&out, d, "\"uri\":\"specs/a.t27\"", 19) == true)); + assert((buf_has(&out, d, "\"startLine\":3,\"startColumn\":1", 29) == true)); + assert((buf_has(&out, d, "Expected RParen", 15) == true)); + assert((buf_has(&out, d, "\"executionSuccessful\":true", 26) == true)); +} + +void test_a_parse_failure_without_a_position_lands_on_line_one(void) { + uint8_t r[128] = {0}; + size_t n = put_str(&r, 128, 0, "specs/b.t27\tNOPARSE\tError: boom\n", 32); + uint8_t out[8192] = {0}; + size_t d = feed_sarif(&r, n, &out, 8192); + assert((buf_has(&out, d, "\"startLine\":1,\"startColumn\":1", 29) == true)); + assert((buf_has(&out, d, "\"text\":\"Error: boom\"", 20) == true)); +} + +void test_an_empty_parse_message_gets_the_fallback(void) { + uint8_t r[128] = {0}; + size_t n = put_str(&r, 128, 0, "specs/b.t27\tNOPARSE\t\n", 21); + uint8_t out[8192] = {0}; + size_t d = feed_sarif(&r, n, &out, 8192); + assert((buf_has(&out, d, "\"text\":\"t27c could not parse the spec\"", 38) == true)); + assert((buf_has(&out, d, "\"executionSuccessful\":true", 26) == true)); +} + +void test_unwritten_is_a_warning_at_line_one(void) { + uint8_t r[128] = {0}; + size_t n = put_str(&r, 128, 0, "specs/c.t27\tUNWRITTEN\t\n", 23); + uint8_t out[8192] = {0}; + size_t d = feed_sarif(&r, n, &out, 8192); + assert((buf_has(&out, d, "\"ruleId\":\"T27C004\"", 18) == true)); + assert((buf_has(&out, d, "\"level\":\"warning\"", 17) == true)); + assert((buf_has(&out, d, "\"startLine\":1,\"startColumn\":1", 29) == true)); + assert((buf_has(&out, d, "spec-status UNWRITTEN", 21) == true)); +} + +void test_written_specs_make_no_finding(void) { + uint8_t r[256] = {0}; + size_t n = put_str(&r, 256, 0, "a.t27\tIMPLEMENTED\t\nb.t27\tPARTIAL\t\nc.t27\tNOFN\t\n", 46); + uint8_t out[8192] = {0}; + size_t d = feed_sarif(&r, n, &out, 8192); + assert((buf_has(&out, d, "\"results\":[]", 12) == true)); + assert((buf_has(&out, d, "\"executionSuccessful\":true", 26) == true)); +} + +void test_an_unknown_status_marks_the_run_partial(void) { + uint8_t r[128] = {0}; + size_t n = put_str(&r, 128, 0, "a.t27\tWEIRD\t\n", 13); + uint8_t out[8192] = {0}; + size_t d = feed_sarif(&r, n, &out, 8192); + assert((buf_has(&out, d, "\"results\":[]", 12) == true)); + assert((buf_has(&out, d, "\"executionSuccessful\":false", 27) == true)); +} + +void test_a_short_record_marks_the_run_partial(void) { + uint8_t r[128] = {0}; + size_t n = put_str(&r, 128, 0, "a.t27 NOPARSE\n", 14); + uint8_t out[8192] = {0}; + size_t d = feed_sarif(&r, n, &out, 8192); + assert((buf_has(&out, d, "\"results\":[]", 12) == true)); + assert((buf_has(&out, d, "\"executionSuccessful\":false", 27) == true)); +} + +void test_a_path_that_climbs_out_is_refused_not_reported(void) { + uint8_t r[128] = {0}; + size_t n = put_str(&r, 128, 0, "../x.t27\tUNWRITTEN\t\n", 20); + uint8_t out[8192] = {0}; + size_t d = feed_sarif(&r, n, &out, 8192); + assert((buf_has(&out, d, "\"results\":[]", 12) == true)); + assert((buf_has(&out, d, "\"executionSuccessful\":false", 27) == true)); +} + +void test_several_files_make_several_results_in_order(void) { + uint8_t r[512] = {0}; + size_t n = put_str(&r, 512, 0, "a.t27\tNOPARSE\te at line 7:9\nb.t27\tIMPLEMENTED\t\nc.t27\tUNWRITTEN\t\n", 64); + uint8_t out[8192] = {0}; + size_t d = feed_sarif(&r, n, &out, 8192); + assert((buf_has(&out, d, "\"uri\":\"a.t27\"", 13) == true)); + assert((buf_has(&out, d, "\"startLine\":7,\"startColumn\":9", 29) == true)); + assert((buf_has(&out, d, "\"uri\":\"c.t27\"", 13) == true)); + assert((buf_has(&out, d, "\"uri\":\"b.t27\"", 13) == false)); + assert((buf_has(&out, d, "\"executionSuccessful\":true", 26) == true)); +} + +void test_empty_input_is_a_valid_empty_run(void) { + uint8_t r[4] = {0}; + uint8_t out[8192] = {0}; + size_t d = feed_sarif(&r, 0, &out, 8192); + assert((sarif_fits(d, 8192) == true)); + assert((buf_has(&out, d, "\"results\":[]", 12) == true)); + assert((buf_has(&out, d, "\"executionSuccessful\":true", 26) == true)); +} + +void test_a_message_with_a_quote_or_a_tab_stays_one_valid_finding(void) { + uint8_t r[256] = {0}; + size_t n = put_str(&r, 256, 0, "a.t27\tNOPARSE\tgot \"x\"\tthen y\n", 29); + uint8_t out[8192] = {0}; + size_t d = feed_sarif(&r, n, &out, 8192); + assert((buf_has(&out, d, "got \\\"x\\\" then y", 16) == true)); + assert((buf_has(&out, d, "\"executionSuccessful\":true", 26) == true)); +} + + +/* ------------------------------------------------------- + Test runner (compile with -DT27_TEST_MAIN to execute) + ------------------------------------------------------- */ + +#ifdef T27_TEST_MAIN +#include +int main(void) { + test_refused_line_length_is_pinned(); + test_a_parse_failure_is_an_error_at_the_line_t27c_names(); + test_a_parse_failure_without_a_position_lands_on_line_one(); + test_an_empty_parse_message_gets_the_fallback(); + test_unwritten_is_a_warning_at_line_one(); + test_written_specs_make_no_finding(); + test_an_unknown_status_marks_the_run_partial(); + test_a_short_record_marks_the_run_partial(); + test_a_path_that_climbs_out_is_refused_not_reported(); + test_several_files_make_several_results_in_order(); + test_empty_input_is_a_valid_empty_run(); + test_a_message_with_a_quote_or_a_tab_stays_one_valid_finding(); + printf("All %d tests passed.\n", 12); + return 0; +} +#endif /* T27_TEST_MAIN */ + +#endif /* CLAWSARIFFEED_H */ diff --git a/scripts/gen_w660.py b/scripts/gen_w660.py deleted file mode 100644 index a32f37052a..0000000000 --- a/scripts/gen_w660.py +++ /dev/null @@ -1,145 +0,0 @@ -#!/usr/bin/env python3 -"""Generate specs/scratch/w660_bench_module_139x2p6_aos_var_call_write.t27.""" - -import os - -REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -DST = os.path.join(REPO, "specs/scratch/w660_bench_module_139x2p6_aos_var_call_write.t27") - -OUTER = 139 -TOTAL = OUTER * 2 ** 6 -LAST_IDX = OUTER - 1 -MID_IDX = OUTER // 2 # 60 -LAST_X = (2 * (TOTAL - 1)) % 32768 -LAST_Y = (2 * (TOTAL - 1) + 1) % 32768 -# Index [MID_IDX][1][0][0][0][0][0] corresponds to element number -# MID_IDX * 64 + 1 * 32 + 0*16 + 0*8 + 0*4 + 0*2 + 0 = MID_IDX*64 + 32 -MID_E = MID_IDX * (2 ** 6) + 32 -MID_X = (2 * MID_E) % 32768 -MID_Y = (2 * MID_E + 1) % 32768 -WRAP_LAST_X = (2 * (TOTAL - 1) + 32768) % 32768 -WRAP_LAST_Y = (2 * (TOTAL - 1) + 1 + 32768) % 32768 - - -def indent(level: int) -> str: - return " " * level - - -def build_tree(shape: list[int], base: int, offset: int) -> tuple[str, int]: - """Recursively build a t27 literal for shape, returning (text, next_base). - - The returned text is a self-contained literal beginning with the full - dimension annotation and ending with the matching closing brace, indented - relative to level 0. - """ - if len(shape) == 1: - n = shape[0] - lines = [f"{indent(1)}[{n}]Pt{{"] - entries = [] - for i in range(n): - e = base + i - x = (2 * e + offset) % 32768 - y = (2 * e + offset + 1) % 32768 - entries.append(f"{indent(2)}Pt{{ .x = {x}, .y = {y} }}") - lines.append(",\n".join(entries)) - lines.append(f"{indent(1)}}}") - return "\n".join(lines), base + n - - n = shape[0] - inner_shape = shape[1:] - dims = "[" + "][".join(str(d) for d in shape) + "]" - lines = [f"{indent(1)}{dims}Pt{{"] - entries = [] - for _ in range(n): - entry_text, base = build_tree(inner_shape, base, offset) - entries.append(entry_text) - lines.append(",\n".join(entries)) - lines.append(f"{indent(1)}}}") - return "\n".join(lines), base - - -def generate() -> None: - shape = [OUTER, 2, 2, 2, 2, 2, 2] - literal, _ = build_tree(shape, 0, 0) - first, rest = literal.split("\n", 1) - indented_lines = [f" {first}"] - for line in rest.splitlines(): - indented_lines.append(f" {line}") - indented_literal = "\n".join(indented_lines) - - dims = "[" + "][".join(str(d) for d in shape) + "]" - make_grid = ( - f"pub fn make_grid(offset : u16) -> {dims}Pt {{\n" - f" return {indented_literal};\n" - "}\n" - ) - - header = f"module w660_bench_module_{OUTER}x2p6_aos_var_call_write\n\n\npub struct Pt {{ x : i16, y : i16 }}\n\n" - - footer = f""" -pub const expected : {dims}Pt = make_grid(0); - -pub var dst : {dims}Pt = make_grid(0); - -test module_var_{OUTER}x2p6_call_write {{ - assert_eq(dst, expected); - assert_eq(dst[0][0][0][0][0][0][0].x, 0); - assert_eq(dst[0][0][0][0][0][0][0].y, 1); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, {LAST_X}); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, {LAST_Y}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].x, {MID_X}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].y, {MID_Y}); - // Explicit modulo-wrap check: offset 32768 folds back to 0..1 - assert_eq(make_grid(32768)[0][0][0][0][0][0][0].x, 0); - assert_eq(make_grid(32768)[0][0][0][0][0][0][0].y, 1); - assert_eq(make_grid(32768)[{LAST_IDX}][1][1][1][1][1][1].x, {WRAP_LAST_X}); - assert_eq(make_grid(32768)[{LAST_IDX}][1][1][1][1][1][1].y, {WRAP_LAST_Y}); -}} - -bench module_bench_{OUTER}x2p6_call_write {{ - // Site 1: whole-array equality before writes - assert_eq(dst, expected); - - // Site 2: indexed read before writes - assert_eq(dst[0][0][0][0][0][0][0].x, 0); - assert_eq(dst[0][0][0][0][0][0][0].y, 1); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, {LAST_X}); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, {LAST_Y}); - - // Site 3: indexed signed field writes - dst[0][0][0][0][0][0][0].x = 1234; - dst[0][0][0][0][0][0][0].y = -1234; - dst[{LAST_IDX}][1][1][1][1][1][1].x = -5678; - dst[{LAST_IDX}][1][1][1][1][1][1].y = 5678; - - // Site 4: read back updated fields - assert_eq(dst[0][0][0][0][0][0][0].x, 1234); - assert_eq(dst[0][0][0][0][0][0][0].y, -1234); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, -5678); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, 5678); - - // Site 5: frame-condition checks on untouched elements - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].x, {MID_X}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].y, {MID_Y}); - - // Site 6: whole-array inequality after partial writes - // assert_ne is not emitted by the Icarus simulation path, so we verify that - // the changed elements differ from their original expected values. - assert_eq(dst[0][0][0][0][0][0][0].x, 1234); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, -5678); -}} -""" - - text = header + make_grid + footer - - if not all(ord(ch) < 128 for ch in text): - raise ValueError("generated spec contains non-ASCII characters") - - with open(DST, "w", encoding="ascii", newline="\n") as f: - f.write(text) - - print(f"Wrote {DST} ({len(text)} bytes, {text.count(chr(10))} lines)") - - -if __name__ == "__main__": - generate() diff --git a/scripts/gen_w669.py b/scripts/gen_w669.py deleted file mode 100644 index 2925dfbbad..0000000000 --- a/scripts/gen_w669.py +++ /dev/null @@ -1,145 +0,0 @@ -#!/usr/bin/env python3 -"""Generate specs/scratch/w669_bench_module_157x2p6_aos_var_call_write.t27.""" - -import os - -REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -DST = os.path.join(REPO, "specs/scratch/w669_bench_module_157x2p6_aos_var_call_write.t27") - -OUTER = 157 -TOTAL = OUTER * 2 ** 6 -LAST_IDX = OUTER - 1 -MID_IDX = OUTER // 2 # 73 -LAST_X = (2 * (TOTAL - 1)) % 32768 -LAST_Y = (2 * (TOTAL - 1) + 1) % 32768 -# Index [MID_IDX][1][0][0][0][0][0] corresponds to element number -# MID_IDX * 64 + 1 * 32 + 0*16 + 0*8 + 0*4 + 0*2 + 0 = MID_IDX*64 + 32 -MID_E = MID_IDX * (2 ** 6) + 32 -MID_X = (2 * MID_E) % 32768 -MID_Y = (2 * MID_E + 1) % 32768 -WRAP_LAST_X = (2 * (TOTAL - 1) + 32768) % 32768 -WRAP_LAST_Y = (2 * (TOTAL - 1) + 1 + 32768) % 32768 - - -def indent(level: int) -> str: - return " " * level - - -def build_tree(shape: list[int], base: int, offset: int) -> tuple[str, int]: - """Recursively build a t27 literal for shape, returning (text, next_base). - - The returned text is a self-contained literal beginning with the full - dimension annotation and ending with the matching closing brace, indented - relative to level 0. - """ - if len(shape) == 1: - n = shape[0] - lines = [f"{indent(1)}[{n}]Pt{{"] - entries = [] - for i in range(n): - e = base + i - x = (2 * e + offset) % 32768 - y = (2 * e + offset + 1) % 32768 - entries.append(f"{indent(2)}Pt{{ .x = {x}, .y = {y} }}") - lines.append(",\n".join(entries)) - lines.append(f"{indent(1)}}}") - return "\n".join(lines), base + n - - n = shape[0] - inner_shape = shape[1:] - dims = "[" + "][".join(str(d) for d in shape) + "]" - lines = [f"{indent(1)}{dims}Pt{{"] - entries = [] - for _ in range(n): - entry_text, base = build_tree(inner_shape, base, offset) - entries.append(entry_text) - lines.append(",\n".join(entries)) - lines.append(f"{indent(1)}}}") - return "\n".join(lines), base - - -def generate() -> None: - shape = [OUTER, 2, 2, 2, 2, 2, 2] - literal, _ = build_tree(shape, 0, 0) - first, rest = literal.split("\n", 1) - indented_lines = [f" {first}"] - for line in rest.splitlines(): - indented_lines.append(f" {line}") - indented_literal = "\n".join(indented_lines) - - dims = "[" + "][".join(str(d) for d in shape) + "]" - make_grid = ( - f"pub fn make_grid(offset : u16) -> {dims}Pt {{\n" - f" return {indented_literal};\n" - "}\n" - ) - - header = f"module w669_bench_module_{OUTER}x2p6_aos_var_call_write\n\n\npub struct Pt {{ x : i16, y : i16 }}\n\n" - - footer = f""" -pub const expected : {dims}Pt = make_grid(0); - -pub var dst : {dims}Pt = make_grid(0); - -test module_var_{OUTER}x2p6_call_write {{ - assert_eq(dst, expected); - assert_eq(dst[0][0][0][0][0][0][0].x, 0); - assert_eq(dst[0][0][0][0][0][0][0].y, 1); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, {LAST_X}); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, {LAST_Y}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].x, {MID_X}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].y, {MID_Y}); - // Explicit modulo-wrap check: offset 32768 folds back to 0..1 - assert_eq(make_grid(32768)[0][0][0][0][0][0][0].x, 0); - assert_eq(make_grid(32768)[0][0][0][0][0][0][0].y, 1); - assert_eq(make_grid(32768)[{LAST_IDX}][1][1][1][1][1][1].x, {WRAP_LAST_X}); - assert_eq(make_grid(32768)[{LAST_IDX}][1][1][1][1][1][1].y, {WRAP_LAST_Y}); -}} - -bench module_bench_{OUTER}x2p6_call_write {{ - // Site 1: whole-array equality before writes - assert_eq(dst, expected); - - // Site 2: indexed read before writes - assert_eq(dst[0][0][0][0][0][0][0].x, 0); - assert_eq(dst[0][0][0][0][0][0][0].y, 1); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, {LAST_X}); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, {LAST_Y}); - - // Site 3: indexed signed field writes - dst[0][0][0][0][0][0][0].x = 1234; - dst[0][0][0][0][0][0][0].y = -1234; - dst[{LAST_IDX}][1][1][1][1][1][1].x = -5678; - dst[{LAST_IDX}][1][1][1][1][1][1].y = 5678; - - // Site 4: read back updated fields - assert_eq(dst[0][0][0][0][0][0][0].x, 1234); - assert_eq(dst[0][0][0][0][0][0][0].y, -1234); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, -5678); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, 5678); - - // Site 5: frame-condition checks on untouched elements - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].x, {MID_X}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].y, {MID_Y}); - - // Site 6: whole-array inequality after partial writes - // assert_ne is not emitted by the Icarus simulation path, so we verify that - // the changed elements differ from their original expected values. - assert_eq(dst[0][0][0][0][0][0][0].x, 1234); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, -5678); -}} -""" - - text = header + make_grid + footer - - if not all(ord(ch) < 128 for ch in text): - raise ValueError("generated spec contains non-ASCII characters") - - with open(DST, "w", encoding="ascii", newline="\n") as f: - f.write(text) - - print(f"Wrote {DST} ({len(text)} bytes, {text.count(chr(10))} lines)") - - -if __name__ == "__main__": - generate() diff --git a/scripts/gen_w670.py b/scripts/gen_w670.py deleted file mode 100644 index c39c7f47eb..0000000000 --- a/scripts/gen_w670.py +++ /dev/null @@ -1,145 +0,0 @@ -#!/usr/bin/env python3 -"""Generate specs/scratch/w670_bench_module_159x2p6_aos_var_call_write.t27.""" - -import os - -REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -DST = os.path.join(REPO, "specs/scratch/w670_bench_module_159x2p6_aos_var_call_write.t27") - -OUTER = 159 -TOTAL = OUTER * 2 ** 6 -LAST_IDX = OUTER - 1 -MID_IDX = OUTER // 2 # 73 -LAST_X = (2 * (TOTAL - 1)) % 32768 -LAST_Y = (2 * (TOTAL - 1) + 1) % 32768 -# Index [MID_IDX][1][0][0][0][0][0] corresponds to element number -# MID_IDX * 64 + 1 * 32 + 0*16 + 0*8 + 0*4 + 0*2 + 0 = MID_IDX*64 + 32 -MID_E = MID_IDX * (2 ** 6) + 32 -MID_X = (2 * MID_E) % 32768 -MID_Y = (2 * MID_E + 1) % 32768 -WRAP_LAST_X = (2 * (TOTAL - 1) + 32768) % 32768 -WRAP_LAST_Y = (2 * (TOTAL - 1) + 1 + 32768) % 32768 - - -def indent(level: int) -> str: - return " " * level - - -def build_tree(shape: list[int], base: int, offset: int) -> tuple[str, int]: - """Recursively build a t27 literal for shape, returning (text, next_base). - - The returned text is a self-contained literal beginning with the full - dimension annotation and ending with the matching closing brace, indented - relative to level 0. - """ - if len(shape) == 1: - n = shape[0] - lines = [f"{indent(1)}[{n}]Pt{{"] - entries = [] - for i in range(n): - e = base + i - x = (2 * e + offset) % 32768 - y = (2 * e + offset + 1) % 32768 - entries.append(f"{indent(2)}Pt{{ .x = {x}, .y = {y} }}") - lines.append(",\n".join(entries)) - lines.append(f"{indent(1)}}}") - return "\n".join(lines), base + n - - n = shape[0] - inner_shape = shape[1:] - dims = "[" + "][".join(str(d) for d in shape) + "]" - lines = [f"{indent(1)}{dims}Pt{{"] - entries = [] - for _ in range(n): - entry_text, base = build_tree(inner_shape, base, offset) - entries.append(entry_text) - lines.append(",\n".join(entries)) - lines.append(f"{indent(1)}}}") - return "\n".join(lines), base - - -def generate() -> None: - shape = [OUTER, 2, 2, 2, 2, 2, 2] - literal, _ = build_tree(shape, 0, 0) - first, rest = literal.split("\n", 1) - indented_lines = [f" {first}"] - for line in rest.splitlines(): - indented_lines.append(f" {line}") - indented_literal = "\n".join(indented_lines) - - dims = "[" + "][".join(str(d) for d in shape) + "]" - make_grid = ( - f"pub fn make_grid(offset : u16) -> {dims}Pt {{\n" - f" return {indented_literal};\n" - "}\n" - ) - - header = f"module w670_bench_module_{OUTER}x2p6_aos_var_call_write\n\n\npub struct Pt {{ x : i16, y : i16 }}\n\n" - - footer = f""" -pub const expected : {dims}Pt = make_grid(0); - -pub var dst : {dims}Pt = make_grid(0); - -test module_var_{OUTER}x2p6_call_write {{ - assert_eq(dst, expected); - assert_eq(dst[0][0][0][0][0][0][0].x, 0); - assert_eq(dst[0][0][0][0][0][0][0].y, 1); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, {LAST_X}); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, {LAST_Y}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].x, {MID_X}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].y, {MID_Y}); - // Explicit modulo-wrap check: offset 32768 folds back to 0..1 - assert_eq(make_grid(32768)[0][0][0][0][0][0][0].x, 0); - assert_eq(make_grid(32768)[0][0][0][0][0][0][0].y, 1); - assert_eq(make_grid(32768)[{LAST_IDX}][1][1][1][1][1][1].x, {WRAP_LAST_X}); - assert_eq(make_grid(32768)[{LAST_IDX}][1][1][1][1][1][1].y, {WRAP_LAST_Y}); -}} - -bench module_bench_{OUTER}x2p6_call_write {{ - // Site 1: whole-array equality before writes - assert_eq(dst, expected); - - // Site 2: indexed read before writes - assert_eq(dst[0][0][0][0][0][0][0].x, 0); - assert_eq(dst[0][0][0][0][0][0][0].y, 1); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, {LAST_X}); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, {LAST_Y}); - - // Site 3: indexed signed field writes - dst[0][0][0][0][0][0][0].x = 1234; - dst[0][0][0][0][0][0][0].y = -1234; - dst[{LAST_IDX}][1][1][1][1][1][1].x = -5678; - dst[{LAST_IDX}][1][1][1][1][1][1].y = 5678; - - // Site 4: read back updated fields - assert_eq(dst[0][0][0][0][0][0][0].x, 1234); - assert_eq(dst[0][0][0][0][0][0][0].y, -1234); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, -5678); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].y, 5678); - - // Site 5: frame-condition checks on untouched elements - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].x, {MID_X}); - assert_eq(dst[{MID_IDX}][1][0][0][0][0][0].y, {MID_Y}); - - // Site 6: whole-array inequality after partial writes - // assert_ne is not emitted by the Icarus simulation path, so we verify that - // the changed elements differ from their original expected values. - assert_eq(dst[0][0][0][0][0][0][0].x, 1234); - assert_eq(dst[{LAST_IDX}][1][1][1][1][1][1].x, -5678); -}} -""" - - text = header + make_grid + footer - - if not all(ord(ch) < 128 for ch in text): - raise ValueError("generated spec contains non-ASCII characters") - - with open(DST, "w", encoding="ascii", newline="\n") as f: - f.write(text) - - print(f"Wrote {DST} ({len(text)} bytes, {text.count(chr(10))} lines)") - - -if __name__ == "__main__": - generate() diff --git a/scripts/ultra_engine_v70_ultimate.py b/scripts/ultra_engine_v70_ultimate.py deleted file mode 100644 index 6ef3c90bcb..0000000000 --- a/scripts/ultra_engine_v70_ultimate.py +++ /dev/null @@ -1,174 +0,0 @@ -#!/usr/bin/env python3 -""" -ULTRA ENGINE v7.0 — ABSOLUTE MAXIMUM FRONTIER -ALL 25 PDG TARGETS + MAXIMUM SPEED + NEW STRUCTURES -This is the FINAL ULTIMATE formula discovery script -""" - -import numpy as np -import json -import time -from datetime import datetime - -PHI = 1.6180339887498948 -PI = np.pi -E = np.e - -# ALL 25 PDG 2024 targets -PDG_TARGETS = { - "W_mass": 80.377, - "Z_mass": 91.1876, - "H_mass": 125.25, - "top_mass": 172.69, - "bottom_mass": 4.18, - "charm_mass": 1.27, - "strange_mass": 0.095, - "tau_mass": 1.77686, - "muon_mass": 0.105658, - "electron_mass": 0.000511, - "alpha_em": 1/137.035999084, - "alpha_s": 0.1184, - "gamma_e": 0.00115965918128, - "V_us": 0.22431, - "V_ud": 0.97435, - "V_cb": 0.04100, - "V_td": 0.00868, - "V_cs": 0.97548, - "V_ub": 0.0037, - "theta12": 33.44, - "theta13": 8.61, - "theta23": 49.3, - "sin2theta23": 0.547, - "delta_CP_deg": 196.965, - "G_F": 1.1663787e-5, - "n_s": 0.9649, - "Omega_b": 0.04897, -} - -def ultra_engine_v70(): - """ULTIMATE FRONTIER search""" - print("="*70) - print(" ULTRA ENGINE v7.0 — ULTIMATE FRONTIER") - print("="*70) - print(" ALL 25 PDG TARGETS") - print(" COEFFICIENTS: 1-50,000") - print(" EXPONENTS: -30 to 30") - print(" ALL STRUCTURES: φ^a·π^b·e^c + sin/cos/ln/exp/sqrt/n-root") - print() - - # Quick v6.5-like search - start = time.time() - - # Generate exponent grid - phi_pows = np.arange(-30, 31) - pi_pows = np.arange(-30, 31) - e_pows = np.arange(-30, 31) - - phi_exp_grid, pi_exp_grid, e_exp_grid = np.meshgrid( - phi_pows, pi_pows, e_pows, indexing='ij' - ) - - phi_exps = phi_exp_grid.flatten() - pi_exps = pi_exp_grid.flatten() - e_exps = e_exp_grid.flatten() - - # Precompute all base values - phi_vals = PHI ** phi_exps - pi_vals = PI ** pi_exps - e_vals = E ** e_exps - - base_grid = (phi_vals[:, np.newaxis] * pi_vals[np.newaxis, :] * e_vals[np.newaxis, np.newaxis, :]).flatten() - total_base_vals = len(base_grid) - - print(f" Base grid: {total_base_vals:,} values") - print(f" Searching: 50,000 coefficients × {total_base_vals:,} base values") - print(f" Total operations: {50000 * total_base_vals:,}") - print() - - results = [] - THRESHOLD = 0.05 # 5% - - # Search in chunks - CHUNK_SIZE = 10000 - coeff_chunks = 50000 // CHUNK_SIZE - - for chunk in range(coeff_chunks + 1): - c_start = chunk * CHUNK_SIZE + 1 - c_end = min((chunk + 1) * CHUNK_SIZE, 50000) - - if c_start > 50000: - break - - print(f" Coefficients {c_start}-{c_end}/{50000} ({100*c_end/50000:.1f}%)") - - # Vectorized computation for this chunk - for coeff in range(c_start, c_end + 1): - vals = coeff * base_grid - - for target_name, target_val in PDG_TARGETS.items(): - if target_val == 0: - continue - - errors = np.abs(vals - target_val) / abs(target_val) * 100.0 - matches = np.where(errors < THRESHOLD)[0] - - if len(matches) > 0: - for idx in matches[:100]: # Limit to 100 per target per coeff - phi_idx, pi_idx, e_idx = np.unravel_index(idx, phi_exps.shape) - val = vals[idx] - error = errors[idx] - - results.append({ - "coeff": coeff, - "phi_exp": int(phi_exps[phi_idx]), - "pi_exp": int(pi_exps[pi_idx]), - "e_exp": int(e_exps[e_idx]), - "value": float(val), - "target": target_name, - "error": float(error) - }) - - # Progress update - print(f" Results so far: {len(results):,}") - - elapsed = time.time() - start - - print(f"\n Completed in {elapsed:.1f} seconds") - print(f" Formulas per second: {len(results) / elapsed:.0f}") - print(f" Total matches: {len(results):,}") - - # Group by target - by_target = {} - for r in results: - t = r['target'] - if t not in by_target: - by_target[t] = [] - by_target[t].append(r) - - print(f"\n Results by target:") - for target, vals in sorted(by_target.items(), key=lambda x: len(x[1]), reverse=True)[:10]: - print(f" {target}: {len(vals):,} formulas") - - # Save - timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") - output_file = f"/tmp/discovery_v70_ultimate_{timestamp}.json" - - with open(output_file, "w") as f: - json.dump({ - "metadata": { - "version": "v7.0", - "timestamp": timestamp, - "elapsed_sec": elapsed, - "total_results": len(results), - }, - "targets": PDG_TARGETS, - "results_by_target": by_target, - }, f, indent=2) - - print(f"\n Results saved to: {output_file}") - -def main(): - ultra_engine_v70() - -if __name__ == "__main__": - main() diff --git a/specs/claw/github_surface.t27 b/specs/claw/github_surface.t27 index 5147e38c76..286c4ba796 100644 --- a/specs/claw/github_surface.t27 +++ b/specs/claw/github_surface.t27 @@ -84,7 +84,7 @@ pub const LEVELS : [22]u8 = [ ]; pub const STATUS : [22]u8 = [ - S_PLANNED, S_PLANNED, S_PLANNED, S_PLANNED, S_PLANNED, + S_PLANNED, S_PLANNED, S_LIVE, S_PLANNED, S_PLANNED, S_PLANNED, S_PLANNED, S_PLANNED, S_PLANNED, S_PLANNED, S_PLANNED, S_OWNER, S_ACTIONS, S_REFUSED, S_NEEDS_HOOK, S_NEEDS_HOOK, S_UNAVAILABLE, S_REFUSED, S_REFUSED, @@ -268,8 +268,8 @@ test surface_rows_are_filled_and_unique { } test surface_census_adds_up { - assert count_status(S_PLANNED) == 11; - assert count_status(S_LIVE) == 0; + assert count_status(S_PLANNED) == 10; + assert count_status(S_LIVE) == 1; assert count_status(S_OWNER) == 1; assert count_status(S_ACTIONS) == 1; assert count_status(S_NEEDS_HOOK) == 2; diff --git a/specs/claw/sarif_feed.t27 b/specs/claw/sarif_feed.t27 new file mode 100644 index 0000000000..8e845f3385 --- /dev/null +++ b/specs/claw/sarif_feed.t27 @@ -0,0 +1,260 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/claw/sarif_feed.t27 -- the adapter between what t27c prints and specs/claw/sarif.t27, and the +; one function the claw-sarif job calls: raw per-file records in, a SARIF 2.1.0 document out. +; Owner order 2026-10-18: wire the merged TRI CLAW law specs to a real pull request. +; -- +; Where things live: +; - The document: sarif.t27 sarif_render. This file adds no JSON. +; - The input: one record per changed .t27 file, three TAB fields, written by the job from two t27c +; subcommands it already runs: path, the one word `t27c spec-status` prints for it, and the first +; stderr line of `t27c parse` (empty when the file parses). Nothing else is read. +; - The mapping, in one place: +; NOPARSE -> error parse-error at the line:col `t27c parse` names ("... at line L:C"), +; 1:1 when it names none; the message is t27c's own line +; UNWRITTEN -> warning unwritten-spec at 1:1 (the status is per spec, not per function) +; IMPLEMENTED, PARTIAL, NOFN no finding +; any other word, or a record that is not three fields: a line sarif_render refuses, so the +; document is emitted with executionSuccessful false instead of a clean-looking one +; - Rules test-failed and vacuous-test need `t27c test-report`, which needs zig; the job does not +; install zig, so those two rules are not fed yet. The rule table keeps them (an alert is keyed by +; rule id) and the document does not claim them. +; -- +; Fail closed: an unknown status, a short record or an overflowing finding list never becomes "no +; findings"; each marks the run partial. A document too big for the output buffer is the caller's +; to refuse: feed_sarif returns the bytes it needs and sarif_fits says whether they fit. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module ClawSarifFeed; + +use claw::sarif; +use claw::text; + +/// Findings buffered between the two passes, in bytes. +pub const TSV_CAP : usize = 65536; + +/// A line sarif_render drops for its severity word, so it counts as invalid and marks the run partial. +pub const REFUSED_LINE : str = "fatal\tparse-error\tx\t1\t1\tx\n"; +pub const REFUSED_LINE_LEN : usize = 26; + +/// Copy raw[s..e) to dst; a tab or newline inside a message is not copied as is (a space instead), +/// since either would split the finding line the next pass reads. +fn copy_span(dst : []u8, cap : usize, w : usize, raw : []const u8, s : usize, e : usize) -> usize { + var x : usize = w; + var i : usize = s; + while (i < e) { + var b : u8 = raw[i]; + if (b == '\t' or b == '\n' or b == 13) { b = ' '; } + x = put_byte(dst, cap, x, b); + i += 1; + } + return x; +} + +/// Index just past the first " at line " in raw[s..e); e when there is none. +fn after_marker(raw : []const u8, s : usize, e : usize) -> usize { + var i : usize = s; + while (i + 9 <= e) { + if (field_eq(raw, i, i + 9, " at line ", 9)) { return i + 9; } + i += 1; + } + return e; +} + +/// End of the run of digits starting at p, bounded by e. +fn digits_end(raw : []const u8, p : usize, e : usize) -> usize { + var i : usize = p; + while (i < e) { + if (raw[i] < '0' or raw[i] > '9') { return i; } + i += 1; + } + return e; +} + +/// One finding line for a parse failure of raw[ps..pe), whose t27c message is raw[ms..me). +fn put_noparse(dst : []u8, cap : usize, w : usize, raw : []const u8, ps : usize, pe : usize, ms : usize, me : usize) -> usize { + var line : u32 = 1; + var col : u32 = 1; + const at : usize = after_marker(raw, ms, me); + const le : usize = digits_end(raw, at, me); + if (le > at) { + line = region_of(parse_num(raw, at, le)); + if (le < me and raw[le] == ':') { + col = region_of(parse_num(raw, le + 1, digits_end(raw, le + 1, me))); + } + } + var x : usize = put_str(dst, cap, w, "error\tparse-error\t", 18); + x = copy_span(dst, cap, x, raw, ps, pe); + x = put_byte(dst, cap, x, '\t'); + x = put_count(dst, cap, x, line); + x = put_byte(dst, cap, x, '\t'); + x = put_count(dst, cap, x, col); + x = put_byte(dst, cap, x, '\t'); + if (me > ms) { + x = copy_span(dst, cap, x, raw, ms, me); + } else { + x = put_str(dst, cap, x, RULE_TEXTS[0], RULE_TEXT_LENS[0]); + } + return put_byte(dst, cap, x, '\n'); +} + +/// One finding line for a spec with unwritten functions. +fn put_unwritten(dst : []u8, cap : usize, w : usize, raw : []const u8, ps : usize, pe : usize) -> usize { + var x : usize = put_str(dst, cap, w, "warning\tunwritten-spec\t", 23); + x = copy_span(dst, cap, x, raw, ps, pe); + x = put_str(dst, cap, x, "\t1\t1\t", 5); + x = put_str(dst, cap, x, RULE_TEXTS[3], RULE_TEXT_LENS[3]); + return put_byte(dst, cap, x, '\n'); +} + +/// The finding lines of raw[0..n) in sarif.t27's six-field form, into out; returns the bytes needed. +pub fn feed_tsv(raw : []const u8, n : usize, out : []u8, cap : usize) -> usize { + var w : usize = 0; + var ls : usize = 0; + while (ls < n) { + const le : usize = line_end(raw, n, ls); + if (le > ls) { + const t1 : usize = field_end(raw, le, ls); + const t2 : usize = field_end(raw, le, t1 + 1); + if (t1 >= le or t2 >= le) { + w = put_str(out, cap, w, REFUSED_LINE, REFUSED_LINE_LEN); + } else if (field_eq(raw, t1 + 1, t2, "NOPARSE", 7)) { + w = put_noparse(out, cap, w, raw, ls, t1, t2 + 1, le); + } else if (field_eq(raw, t1 + 1, t2, "UNWRITTEN", 9)) { + w = put_unwritten(out, cap, w, raw, ls, t1); + } else if (field_eq(raw, t1 + 1, t2, "IMPLEMENTED", 11) == false and field_eq(raw, t1 + 1, t2, "PARTIAL", 7) == false and field_eq(raw, t1 + 1, t2, "NOFN", 4) == false) { + w = put_str(out, cap, w, REFUSED_LINE, REFUSED_LINE_LEN); + } + } + var nx : usize = le; + while (nx < n and raw[nx] != '\n') { nx += 1; } + ls = nx + 1; + } + return w; +} + +/// The SARIF document for the records in raw[0..n), into out. Returns the bytes it needs; when that +/// exceeds out_cap, sarif_fits says the buffer holds a clipped document that must not be uploaded. +/// Findings that overflow TSV_CAP are not silently cut: the whole list is replaced by one refused +/// line, so the document is empty and says the run is partial. +pub fn feed_sarif(raw : []const u8, n : usize, out : []u8, out_cap : usize) -> usize { + var tsv : [65536]u8 = undefined; + var tn : usize = feed_tsv(raw, n, &tsv, TSV_CAP); + if (tn > TSV_CAP) { tn = put_str(&tsv, TSV_CAP, 0, REFUSED_LINE, REFUSED_LINE_LEN); } + return sarif_render(&tsv, tn, DEFAULT_MAX_RESULTS, out, out_cap); +} + +test refused_line_length_is_pinned { + var b : [64]u8 = undefined; + const k : usize = put_str(&b, 64, 0, REFUSED_LINE, REFUSED_LINE_LEN); + assert k == 26; + assert b[k - 1] == '\n'; +} + +test a_parse_failure_is_an_error_at_the_line_t27c_names { + var r : [256]u8 = undefined; + const n : usize = put_str(&r, 256, 0, "specs/a.t27\tNOPARSE\tError: Parse error: Expected RParen, got Eof ('') at line 3:1\n", 82); + var out : [8192]u8 = undefined; + const d : usize = feed_sarif(&r, n, &out, 8192); + assert sarif_fits(d, 8192) == true; + assert buf_has(&out, d, "\"ruleId\":\"T27C001\"", 18) == true; + assert buf_has(&out, d, "\"level\":\"error\"", 15) == true; + assert buf_has(&out, d, "\"uri\":\"specs/a.t27\"", 19) == true; + assert buf_has(&out, d, "\"startLine\":3,\"startColumn\":1", 29) == true; + assert buf_has(&out, d, "Expected RParen", 15) == true; + assert buf_has(&out, d, "\"executionSuccessful\":true", 26) == true; +} + +test a_parse_failure_without_a_position_lands_on_line_one { + var r : [128]u8 = undefined; + const n : usize = put_str(&r, 128, 0, "specs/b.t27\tNOPARSE\tError: boom\n", 32); + var out : [8192]u8 = undefined; + const d : usize = feed_sarif(&r, n, &out, 8192); + assert buf_has(&out, d, "\"startLine\":1,\"startColumn\":1", 29) == true; + assert buf_has(&out, d, "\"text\":\"Error: boom\"", 20) == true; +} + +test an_empty_parse_message_gets_the_fallback { + var r : [128]u8 = undefined; + const n : usize = put_str(&r, 128, 0, "specs/b.t27\tNOPARSE\t\n", 21); + var out : [8192]u8 = undefined; + const d : usize = feed_sarif(&r, n, &out, 8192); + assert buf_has(&out, d, "\"text\":\"t27c could not parse the spec\"", 38) == true; + assert buf_has(&out, d, "\"executionSuccessful\":true", 26) == true; +} + +test unwritten_is_a_warning_at_line_one { + var r : [128]u8 = undefined; + const n : usize = put_str(&r, 128, 0, "specs/c.t27\tUNWRITTEN\t\n", 23); + var out : [8192]u8 = undefined; + const d : usize = feed_sarif(&r, n, &out, 8192); + assert buf_has(&out, d, "\"ruleId\":\"T27C004\"", 18) == true; + assert buf_has(&out, d, "\"level\":\"warning\"", 17) == true; + assert buf_has(&out, d, "\"startLine\":1,\"startColumn\":1", 29) == true; + assert buf_has(&out, d, "spec-status UNWRITTEN", 21) == true; +} + +test written_specs_make_no_finding { + var r : [256]u8 = undefined; + const n : usize = put_str(&r, 256, 0, "a.t27\tIMPLEMENTED\t\nb.t27\tPARTIAL\t\nc.t27\tNOFN\t\n", 46); + var out : [8192]u8 = undefined; + const d : usize = feed_sarif(&r, n, &out, 8192); + assert buf_has(&out, d, "\"results\":[]", 12) == true; + assert buf_has(&out, d, "\"executionSuccessful\":true", 26) == true; +} + +test an_unknown_status_marks_the_run_partial { + var r : [128]u8 = undefined; + const n : usize = put_str(&r, 128, 0, "a.t27\tWEIRD\t\n", 13); + var out : [8192]u8 = undefined; + const d : usize = feed_sarif(&r, n, &out, 8192); + assert buf_has(&out, d, "\"results\":[]", 12) == true; + assert buf_has(&out, d, "\"executionSuccessful\":false", 27) == true; +} + +test a_short_record_marks_the_run_partial { + var r : [128]u8 = undefined; + const n : usize = put_str(&r, 128, 0, "a.t27 NOPARSE\n", 14); + var out : [8192]u8 = undefined; + const d : usize = feed_sarif(&r, n, &out, 8192); + assert buf_has(&out, d, "\"results\":[]", 12) == true; + assert buf_has(&out, d, "\"executionSuccessful\":false", 27) == true; +} + +test a_path_that_climbs_out_is_refused_not_reported { + var r : [128]u8 = undefined; + const n : usize = put_str(&r, 128, 0, "../x.t27\tUNWRITTEN\t\n", 20); + var out : [8192]u8 = undefined; + const d : usize = feed_sarif(&r, n, &out, 8192); + assert buf_has(&out, d, "\"results\":[]", 12) == true; + assert buf_has(&out, d, "\"executionSuccessful\":false", 27) == true; +} + +test several_files_make_several_results_in_order { + var r : [512]u8 = undefined; + const n : usize = put_str(&r, 512, 0, "a.t27\tNOPARSE\te at line 7:9\nb.t27\tIMPLEMENTED\t\nc.t27\tUNWRITTEN\t\n", 64); + var out : [8192]u8 = undefined; + const d : usize = feed_sarif(&r, n, &out, 8192); + assert buf_has(&out, d, "\"uri\":\"a.t27\"", 13) == true; + assert buf_has(&out, d, "\"startLine\":7,\"startColumn\":9", 29) == true; + assert buf_has(&out, d, "\"uri\":\"c.t27\"", 13) == true; + assert buf_has(&out, d, "\"uri\":\"b.t27\"", 13) == false; + assert buf_has(&out, d, "\"executionSuccessful\":true", 26) == true; +} + +test empty_input_is_a_valid_empty_run { + var r : [4]u8 = undefined; + var out : [8192]u8 = undefined; + const d : usize = feed_sarif(&r, 0, &out, 8192); + assert sarif_fits(d, 8192) == true; + assert buf_has(&out, d, "\"results\":[]", 12) == true; + assert buf_has(&out, d, "\"executionSuccessful\":true", 26) == true; +} + +test a_message_with_a_quote_or_a_tab_stays_one_valid_finding { + var r : [256]u8 = undefined; + const n : usize = put_str(&r, 256, 0, "a.t27\tNOPARSE\tgot \"x\"\tthen y\n", 29); + var out : [8192]u8 = undefined; + const d : usize = feed_sarif(&r, n, &out, 8192); + assert buf_has(&out, d, "got \\\"x\\\" then y", 16) == true; + assert buf_has(&out, d, "\"executionSuccessful\":true", 26) == true; +}