From a769a7ed47d31747fd54376dd5fd549153f3b5ca Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Sun, 18 Oct 2026 14:25:13 +0700 Subject: [PATCH 1/3] feat(claw): claw/t27f -- the seal-gated T27F check on every .t27 pull request through the TRI CLAW App (Closes #10268) - specs/claw/t27f_gate.t27 (+ zig-only pins): scope = changed .t27 plus direct importers, judged by `t27c seal --verify`, decision reuses hot.t27; unsealed neutral, stale/vacuous/unreadable failure, malformed input fails closed. MODE_T27F in specs/claw/service.t27. - gen/c/claw/t27f_gate.c: its gen-c lowering; spec-guards.yml now regenerates both claw C files and diffs them. - gate-topology.yml: a claw-t27f job on a new 10-minute schedule and on workflow_dispatch; the existing job skips the schedule; concurrency keyed by event. The job mints the App token (secret CLAW_APP_PRIVATE_KEY, variable CLAW_APP_ID) and exits 0 with a notice when either is absent. It only fetches, runs the cosign-verified t27o-v0.1.0 t27c and posts what the law prints. - specs/claw/gates.t27: gate-topology events gain schedule (67 workflows, 62/3/2 unchanged); gen/c/claw/gates.c regenerated. - docs/claw/README.md: the owner checklist. AGENTS.md section 8: pointers to what landed. - Own-language offset: scripts/gen_w389.py deleted (spent generator, no runtime refs). MAX_DEAD_TRI unchanged at 140. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/gate-topology.yml | 39 +- .github/workflows/spec-guards.yml | 5 +- AGENTS.md | 18 +- docs/claw/README.md | 16 + gen/c/claw/gates.c | 2 +- gen/c/claw/t27f_gate.c | 1149 +++++++++++++++++++++++++++ scripts/gen_w389.py | 54 -- specs/claw/gates.t27 | 4 +- specs/claw/service.t27 | 22 +- specs/claw/t27f_gate.t27 | 819 +++++++++++++++++++ specs/claw/t27f_gate_pins.t27 | 57 ++ 11 files changed, 2110 insertions(+), 75 deletions(-) create mode 100644 gen/c/claw/t27f_gate.c delete mode 100644 scripts/gen_w389.py create mode 100644 specs/claw/t27f_gate.t27 create mode 100644 specs/claw/t27f_gate_pins.t27 diff --git a/.github/workflows/gate-topology.yml b/.github/workflows/gate-topology.yml index 95913b7d2a..7589b0b0a5 100644 --- a/.github/workflows/gate-topology.yml +++ b/.github/workflows/gate-topology.yml @@ -19,17 +19,20 @@ on: # passing, and there was no way to fire it at the default branch and find # out. `tri gates unmeasured` reports the gap; this is what closes it. workflow_dispatch: + # Only the claw-t27f job below runs on this schedule (#10268). + schedule: [{cron: "*/10 * * * *"}] # A run for a commit that has since been superseded measures nothing anyone # will read. Without a group, every merge to master queued one more run of # this gate behind the last: 62 were waiting on 2026-09-17 while the 15 # workflows that do carry a group had exactly one each. concurrency: - group: gate-topology-${{ github.ref }} + group: gate-topology-${{ github.event_name }}-${{ github.ref }} cancel-in-progress: true jobs: gate-topology: + if: github.event_name != 'schedule' runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 @@ -72,3 +75,37 @@ jobs: printf '#include \n#include \n#include "gen/c/claw/gates.c"\nint main(void){static unsigned char dir[65536],out[65536];size_t n=fread(dir,1,sizeof dir,stdin);int p=claw_status(dir,n,out,sizeof out);fwrite(out,1,strlen((const char*)out),stdout);return p>0?1:0;}\n' \ | cc -w -I . -x c -o "$d/claw-audit" - (cd .github/workflows && ls *.yml | sed 's/\.yml$//') | "$d/claw-audit" + + # claw/t27f (#10268): one seal-gated T27F check-run per open .t27 pull request in every repository + # the TRI CLAW App is installed on. The law is specs/claw/t27f_gate.t27 (gen/c/claw/t27f_gate.c); this + # job only mints the App token, fetches, runs the signed t27o release t27c and posts what the law prints. + claw-t27f: + if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' + 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/t27f posted 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}} + - {if: steps.have.outputs.ok, uses: actions/checkout@v6} + - {if: steps.have.outputs.ok, uses: sigstore/cosign-installer@v3} + - if: steps.have.outputs.ok + env: {GH_TOKEN: "${{ steps.tok.outputs.token }}", V: t27o-v0.1.0} + shell: bash {0} + run: | + set -e; 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; T=/tmp/t27c + printf '#include "%s/gen/c/claw/t27f_gate.c"\n#include \n#include \nint main(int c,char**v){size_t k=1<<24,m;uint8_t*b=malloc(k),*o=malloc(k);size_t n=fread(b,1,k,stdin);if(n==k)return 2;\nif(c>1){m=t27f_scope(b,n,o,k);if(m>k)return 3;}else m=t27f_report(b,n,o,65000);fwrite(o,1,m,stdout);return 0;}\n' "$GITHUB_WORKSPACE" > h.c; cc -O1 -w -o law h.c; L=/tmp/law; set +e + gh api --paginate /installation/repositories -q '.repositories[].full_name' | while read -r r; do + gh pr list -R $r -L 100 --json number,headRefOid,baseRefName,files -q '.[]|select(any(.files[];.path|endswith(".t27")))|"\(.number) \(.headRefOid) \(.baseRefName)"' ../scope || echo --unreadable > ../scope + { printf 'repo\t%s\n' $r; while read -r f; do if [ "$f" = --unreadable ]; then echo $f; else printf '@\t%s\t%s\thttps://raw.githubusercontent.com/%s/%s/%s\n' "$f" "$(sha256sum < "$f" | cut -c1-64)" $r $s "$f"; $T seal "$f" --verify 2>&1 ../out + jq -Rs --arg s $s 'split("\n") as $l | {name: $l[0], head_sha: $s, status: "completed", conclusion: $l[1], output: {title: $l[2], summary: ($l[3:] | join("\n"))}}' ../out | gh api -X POST repos/$r/check-runs --input - > /dev/null ); [ $? = 0 ] || echo "::warning::claw/t27f did not check $r#$n" + done + done diff --git a/.github/workflows/spec-guards.yml b/.github/workflows/spec-guards.yml index 3ba36e1ce5..905b15ca7a 100644 --- a/.github/workflows/spec-guards.yml +++ b/.github/workflows/spec-guards.yml @@ -123,10 +123,11 @@ jobs: # committed C would keep answering for a spec it no longer lowers. # Same shape as t27o-golden's check mode (#8996): regenerate, then # diff --exit-code. - - name: committed gen/c/claw/gates.c is what the spec lowers to today + - name: committed gen/c/claw/gates.c and t27f_gate.c are what their specs lower to today run: | ./target/release/t27c gen-c specs/claw/gates.t27 > gen/c/claw/gates.c - git diff --exit-code -- gen/c/claw/gates.c + ./target/release/t27c gen-c specs/claw/t27f_gate.t27 > gen/c/claw/t27f_gate.c + git diff --exit-code -- gen/c/claw/gates.c gen/c/claw/t27f_gate.c # #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 01be76da90..441713f82e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -140,13 +140,17 @@ reusable workflow. Epic #9271. | Service contract: modes, exit codes, release tag `claw-v*`, check prefix `claw/` | [`specs/claw/service.t27`](specs/claw/service.t27) | | Auto-fix recipes and the fix engine: which red check gets which deterministic fix | [`specs/claw/recipes.t27`](specs/claw/recipes.t27), [`specs/claw/engine.t27`](specs/claw/engine.t27) | | 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. Today 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 | - -In flight (2026-10-18): a `claw/t27f` check that the App posts on pull requests -in every gHashTag repository that holds `.t27` specs. It is seal-gated, so only -changed specs and their importers are checked again, and the summary links to -each spec's T27F graph. No file is added to consumer repositories. Until it -lands, read `docs/claw/README.md` for the status, not this paragraph. +| 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` | + +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 +on. No consumer repository carries a workflow file. An unsealed spec is neutral +and a stale seal is failure; each summary row links to the spec's T27F graph. +It posts nothing until the owner finishes the four steps in +[`docs/claw/README.md`](docs/claw/README.md#the-t27f-check-in-every-t27-repository-10268): +App permission checks: write, accepting that permission, secret +`CLAW_APP_PRIVATE_KEY`, variable `CLAW_APP_ID`. That section has the status. Adding a workflow file means adding its row to `gates.t27` in the same commit. The drift gate refuses anything else. diff --git a/docs/claw/README.md b/docs/claw/README.md index 4113655063..56f280ffd8 100644 --- a/docs/claw/README.md +++ b/docs/claw/README.md @@ -12,9 +12,25 @@ behind one front door, with the gate logic in `.t27` specs. Epic | Drift audit law | `claw_status` in `specs/claw/gates.t27` | T2, this branch | | 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 | | Service skeleton | Railway, thin I/O glue only | T5, merged (spec contract) | | Positioning / pricing | `docs/claw/competitors.md` | T6, started | +## The T27F check in every `.t27` repository (#10268) + +Every 10 minutes the `claw-t27f` job of `gate-topology.yml` in gHashTag/t27 lists the repositories the TRI CLAW App +is installed on, and for each open pull request that touches `.t27` posts one `claw/t27f` +check-run on its head commit: the changed specs and their direct importers, each judged by +`t27c seal --verify` from the signed t27o-v0.1.0 release. The law is `specs/claw/t27f_gate.t27`: +unsealed is neutral; a stale, vacuous or unreadable seal is failure; gHashTag/ghashtag.github.io +only reports. No consumer repository carries a workflow file. Until the owner does these four +steps, it posts nothing: + +1. Give the App the permission checks: write at https://github.com/settings/apps/tri-claw. +2. Accept the new permission on the App's installation for gHashTag. +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`. + ## 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/gates.c b/gen/c/claw/gates.c index e1c125e8d0..1e9bc51427 100644 --- a/gen/c/claw/gates.c +++ b/gen/c/claw/gates.c @@ -26,7 +26,7 @@ static const const char* NAMES[67] = { "agent-runner-docker", "auto-merge-ready-prs", "bootstrap-tests", "build-paper", "build-vivado-image", "catalog-count-gate", "cli-tri", "conflict-markers", "conformance-integrity-gate", "coq-kernel", "coq-proofs", "corpus-ratchet", "damage-negatives", "deploy-api", "documented-commands", "dupe-ratchet", "emit-bitexact-gate", "exhaustive-nightly", "fix-carries-source", "fpga-build", "gate-topology", "gf-wide-conformance", "gf16-conformance", "harness-scratch", "issue-gate", "l1-traceability", "lean-proofs", "loop-tools-gate", "needs-boundary", "oracle-nightly", "orphan-modules", "own-language", "pack-index-consistency-gate", "phi-loop-ci", "pr-budget", "pr-dashboard", "pusher", "queen-doctor", "queen-export-push", "queen-feed-empty-bodies", "queen-feed-roadmap", "queen-feed-untested", "queen-publish", "queen-refile", "queen-watchdog", "release", "rings-rust", "sandbox-docker", "sbom", "schema-validation", "scorecard", "seal-coverage", "seal-staleness-warn", "secret-scan", "sign-release", "spec-guards", "spec-parse-ratchet", "t27b-native", "t27o-golden", "t27o-release", "untrusted-input-gate", "verilog-widths", "vivado-synth", "wasm-explorer", "withdrawn-live-gate", "xilinx7-l3-bench", "zenodo-publish" }; static const size_t NAME_LENS[67] = { 19, 20, 15, 11, 18, 18, 7, 16, 26, 10, 10, 14, 16, 10, 19, 12, 18, 18, 18, 10, 13, 19, 16, 15, 10, 15, 11, 15, 14, 14, 14, 12, 27, 11, 9, 12, 6, 12, 17, 23, 18, 19, 13, 12, 14, 7, 10, 14, 4, 17, 9, 13, 19, 11, 12, 11, 18, 11, 11, 12, 20, 14, 12, 13, 19, 16, 14 }; static const uint8_t CLASSES[67] = { 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 2, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; -static const const char* EVENTS[67] = { "push,workflow_dispatch", "schedule,pull_request_target,workflow_dispatch", "push,pull_request,workflow_dispatch", "push,pull_request,workflow_dispatch", "workflow_dispatch", "pull_request,workflow_dispatch", "pull_request,push", "pull_request,push,workflow_dispatch", "push,pull_request,workflow_dispatch", "push,pull_request,workflow_dispatch", "push,pull_request,workflow_dispatch", "pull_request,push,workflow_dispatch", "pull_request,push,workflow_dispatch", "push,workflow_dispatch", "pull_request,push,workflow_dispatch", "pull_request,push,workflow_dispatch", "pull_request,workflow_dispatch", "schedule,workflow_dispatch", "pull_request,workflow_dispatch", "pull_request,push,workflow_dispatch", "pull_request,push,workflow_dispatch", "push,pull_request,workflow_dispatch", "push,pull_request,workflow_dispatch", "pull_request,push,workflow_dispatch", "pull_request_target,workflow_dispatch", "pull_request,workflow_dispatch", "pull_request,workflow_dispatch", "pull_request,push,workflow_dispatch", "schedule,workflow_dispatch", "schedule,workflow_dispatch", "pull_request,push,workflow_dispatch", "pull_request", "push,pull_request,workflow_dispatch", "pull_request,workflow_dispatch", "pull_request_target", "workflow_dispatch,pull_request", "schedule,workflow_dispatch", "schedule,workflow_dispatch", "schedule,workflow_dispatch", "schedule,workflow_dispatch", "schedule,workflow_dispatch", "schedule,workflow_dispatch", "push,schedule,workflow_dispatch", "schedule,workflow_dispatch", "schedule,workflow_dispatch", "release", "push,pull_request,workflow_dispatch", "push,workflow_dispatch", "push,release,workflow_dispatch", "pull_request,push,workflow_dispatch", "branch_protection_rule,schedule,push,workflow_dispatch", "pull_request,push,workflow_dispatch", "pull_request,workflow_dispatch", "push,pull_request,workflow_dispatch", "release,workflow_dispatch", "pull_request,push,workflow_dispatch", "pull_request,push,workflow_dispatch", "pull_request,push,workflow_dispatch", "workflow_call", "push", "pull_request,push,workflow_dispatch", "pull_request,push,workflow_dispatch", "workflow_dispatch", "push,pull_request", "pull_request,push,workflow_dispatch", "workflow_dispatch,pull_request", "release,workflow_dispatch" }; +static const const char* EVENTS[67] = { "push,workflow_dispatch", "schedule,pull_request_target,workflow_dispatch", "push,pull_request,workflow_dispatch", "push,pull_request,workflow_dispatch", "workflow_dispatch", "pull_request,workflow_dispatch", "pull_request,push", "pull_request,push,workflow_dispatch", "push,pull_request,workflow_dispatch", "push,pull_request,workflow_dispatch", "push,pull_request,workflow_dispatch", "pull_request,push,workflow_dispatch", "pull_request,push,workflow_dispatch", "push,workflow_dispatch", "pull_request,push,workflow_dispatch", "pull_request,push,workflow_dispatch", "pull_request,workflow_dispatch", "schedule,workflow_dispatch", "pull_request,workflow_dispatch", "pull_request,push,workflow_dispatch", "pull_request,push,workflow_dispatch,schedule", "push,pull_request,workflow_dispatch", "push,pull_request,workflow_dispatch", "pull_request,push,workflow_dispatch", "pull_request_target,workflow_dispatch", "pull_request,workflow_dispatch", "pull_request,workflow_dispatch", "pull_request,push,workflow_dispatch", "schedule,workflow_dispatch", "schedule,workflow_dispatch", "pull_request,push,workflow_dispatch", "pull_request", "push,pull_request,workflow_dispatch", "pull_request,workflow_dispatch", "pull_request_target", "workflow_dispatch,pull_request", "schedule,workflow_dispatch", "schedule,workflow_dispatch", "schedule,workflow_dispatch", "schedule,workflow_dispatch", "schedule,workflow_dispatch", "schedule,workflow_dispatch", "push,schedule,workflow_dispatch", "schedule,workflow_dispatch", "schedule,workflow_dispatch", "release", "push,pull_request,workflow_dispatch", "push,workflow_dispatch", "push,release,workflow_dispatch", "pull_request,push,workflow_dispatch", "branch_protection_rule,schedule,push,workflow_dispatch", "pull_request,push,workflow_dispatch", "pull_request,workflow_dispatch", "push,pull_request,workflow_dispatch", "release,workflow_dispatch", "pull_request,push,workflow_dispatch", "pull_request,push,workflow_dispatch", "pull_request,push,workflow_dispatch", "workflow_call", "push", "pull_request,push,workflow_dispatch", "pull_request,push,workflow_dispatch", "workflow_dispatch", "push,pull_request", "pull_request,push,workflow_dispatch", "workflow_dispatch,pull_request", "release,workflow_dispatch" }; #define P_OK 0 #define P_SPEC_ONLY 1 #define P_DIR_ONLY 2 diff --git a/gen/c/claw/t27f_gate.c b/gen/c/claw/t27f_gate.c new file mode 100644 index 0000000000..683eb2c86b --- /dev/null +++ b/gen/c/claw/t27f_gate.c @@ -0,0 +1,1149 @@ +/* ============================================================================ + Generated from t27 spec: ClawT27fGate + 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 CLAWT27FGATE_H +#define CLAWT27FGATE_H + +/* ------------------------------------------------------- + Constants + ------------------------------------------------------- */ + +#define CHECK_NAME "claw/t27f$" +#define CONCL_SUCCESS 0 +#define CONCL_NEUTRAL 1 +#define CONCL_FAILURE 2 +static const const char* CONCL_NAMES[3] = { "success$", "neutral$", "failure$" }; +#define V_SEALED 0 +#define V_TOOLCHAIN 1 +#define V_UNSEALED 2 +#define V_STALE 3 +#define V_VACUOUS 4 +#define V_UNREADABLE 5 +static const const char* VERDICT_NAMES[6] = { "sealed$", "toolchain-moved$", "unsealed$", "stale-seal$", "vacuous-seal$", "unreadable$" }; +static const const char* DECISION_LABELS[4] = { "reuse$", "rebuild_spec$", "rebuild_exporter$", "missing$" }; +#define F_READ 1 +#define F_RECORDED 2 +#define F_SPEC_SAME 4 +#define F_EXP_RECORDED 8 +#define F_EXP_SAME 16 +#define F_VACUOUS 32 +#define GEN_HASHES 4 +#define NO_SEAL "No saved seal found$" +#define SPEC_LINE "spec_hash: $" +#define SPEC_SAME_LINE "spec_hash: MATCH$" +#define GEN_LINE "gen_hash_$" +#define SAME_WORD ": MATCH$" +#define SAVED_MISSING "saved=missing$" +#define VACUOUS_LINE "saved seal is vacuous$" +#define REPO_LINE "repo\t$" +#define RECORD_LINE "@\t$" +#define MARK_UNREADABLE "--unreadable$" +#define MARK_GRAPH "--graph$" +#define SPEC_EXT ".t27$" +#define DIGEST_SHOWN 12 +#define GRAPH_BASE "https://t27.ai/t27f/graph/?src=$" +#define URL_SAFE "-._~/:$" +#define HEX "0123456789ABCDEF$" +#define DIGITS "0123456789$" +#define TITLE_HEAD "T27F seals: $" +#define TITLE_SPECS " spec(s), $" +#define TITLE_SEALED " sealed, $" +#define TITLE_NEUTRAL " neutral, $" +#define TITLE_FAILING " failing$" +#define TITLE_EMPTY "T27F seals: no spec in scope$" +#define TITLE_UNREADABLE "T27F seals: the input could not be read$" +#define TITLE_OVERFLOW "T27F seals: the report did not fit$" +#define SUMMARY_HEAD "Seal-gated T27F check of the changed .t27 files and their direct importers, decided by specs/claw/t27f_gate.t27 in gHashTag/t27.$" +#define REPORTING_NOTE "This repository is a published copy (reporting role): a failure is posted as neutral.$" +#define TABLE_HEAD "| path | digest | nodes | edges | verdict | decision | graph |$" +#define TABLE_RULE "|---|---|---|---|---|---|---|$" +#define SUMMARY_FOOT "nodes and edges are not measured here: the T27F exporter is t27f.wasm in the viewer, not t27c. The graph link passes the spec's raw URL as src=; the viewer does not read src= yet and opens its own spec list.$" +#define ROLE_CHECK 0 +#define ROLE_REPORT 1 +#define CONSUMERS 16 +static const const char* CONSUMER_NAMES[16] = { "gHashTag/t27$", "gHashTag/trinity$", "gHashTag/ghashtag.github.io$", "gHashTag/tri-net$", "gHashTag/trinity-fpga$", "gHashTag/trios$", "gHashTag/999-multibots-telegraf$", "gHashTag/tt-trinity-gamma$", "gHashTag/tt-trinity-euler$", "gHashTag/tt-trinity-phi$", "gHashTag/turbobaby-user-bot$", "gHashTag/tri-claw$", "gHashTag/igla-coder-gpu$", "gHashTag/tt-trinity-corona$", "gHashTag/tern-tc-9m-verify$", "gHashTag/tern-tc-9m-t27-verify$" }; +static const uint32_t CONSUMER_SPECS[16] = { 2604, 3458, 3254, 113, 72, 71, 51, 49, 49, 49, 46, 13, 12, 6, 1, 1 }; +static const uint32_t CONSUMER_SEALS[16] = { 2514, 2, 0, 1, 0, 0, 0, 29, 29, 24, 0, 0, 0, 1, 0, 0 }; +static const uint8_t CONSUMER_ROLE[16] = { 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; +#define LIST_END 36 +#define LIST_SEP 44 +#define ALLOW 0 +#define DENY 1 +#define DENY_FROM 16 +#define DIGEST_HEX_LEN 64 +#define HOT_MISSING 3 +#define HOT_REBUILD_EXPORTER 2 +#define HOT_REBUILD_SPEC 1 +#define HOT_REUSE 0 +#define PART_DIFF 2 +#define PART_MISSING 0 +#define PART_SAME 1 +#define REBUILD_MISSING 6 +#define REBUILD_SPEC 1 +#define REBUILD_TOOLCHAIN 3 +#define REUSE 0 + +/* ------------------------------------------------------- + Function prototypes + ------------------------------------------------------- */ + +size_t lit_len(uint8_t* lit); +bool holds(uint8_t* buf, size_t s, size_t t, uint8_t* lit); +bool flag_on(uint8_t fl, uint8_t f); +uint8_t seal_flags(uint8_t* buf, size_t f, size_t t); +uint8_t flags_decision(uint8_t fl); +uint8_t flags_verdict(uint8_t fl); +uint8_t verdict_conclusion(uint8_t v); +uint8_t role_conclusion(uint8_t role, uint8_t c); +size_t consumer_index(uint8_t* buf, size_t f, size_t t); +uint8_t consumer_role(uint8_t* buf, size_t f, size_t t); +bool digest_valid(uint8_t* buf, size_t f, size_t t); +size_t next_record(uint8_t* buf, size_t s, size_t t); +uint8_t record_verdict(uint8_t* buf, size_t r, size_t b); +size_t repo_at(uint8_t* buf, size_t n); +uint8_t input_role(uint8_t* buf, size_t n); +uint32_t record_count(uint8_t* buf, size_t n); +bool input_readable(uint8_t* buf, size_t n); +uint8_t t27f_conclusion(uint8_t* buf, size_t n); +size_t put_decimal(uint8_t* out, size_t w, size_t cap, uint32_t v); +size_t put_query(uint8_t* out, size_t w, size_t cap, uint8_t* buf, size_t f, size_t t); +size_t put_row(uint8_t* out, size_t w, size_t cap, uint8_t* buf, size_t r, size_t b); +size_t put_overflow(uint8_t* out, size_t cap); +size_t t27f_report(uint8_t* buf, size_t n, uint8_t* out, size_t cap); +bool scope_spec(uint8_t* buf, size_t f, size_t t); +size_t t27f_scope(uint8_t* buf, size_t n, uint8_t* set, size_t scap); +uint32_t census_specs(void); +uint32_t census_seals(void); +size_t first_tab(uint8_t* buf, size_t s, size_t t); +bool in_set(uint8_t* set, size_t sn, uint8_t* s, size_t f, size_t t); +bool line_is(uint8_t* buf, size_t s, size_t t, uint8_t* lit); +uint8_t lower(uint8_t c); +size_t marker_at(uint8_t* buf, size_t n, size_t from, uint8_t* lit); +size_t put(uint8_t* out, size_t w, size_t cap, uint8_t c); +bool same(uint8_t* a, size_t af, size_t at, uint8_t* b, size_t bf, size_t bt); +size_t set_add(uint8_t* set, size_t sn, size_t scap, uint8_t* s, size_t f, size_t t); +bool has_prefix(uint8_t* list, uint8_t* s, size_t from, size_t to); +bool is_letter_of(uint8_t* list, uint8_t c); +size_t put_msg(uint8_t* out, size_t w, size_t cap, uint8_t* msg); +size_t put_range(uint8_t* out, size_t w, size_t cap, uint8_t* s, size_t from, size_t to); +uint8_t verdict(uint8_t r); +size_t line_end(uint8_t* buf, size_t s, size_t n); +size_t text_end(uint8_t* buf, size_t s, size_t e); +bool digest_ok(uint32_t len); +uint8_t hot_decision(bool spec_recorded, bool spec_equal, bool exporter_recorded, bool exporter_equal); +uint8_t hot_from_reuse(uint8_t r); +uint8_t part_reason(uint8_t p, uint8_t diff_reason); +uint8_t recorded_part(bool recorded, bool equal); + +/* ------------------------------------------------------- + Function implementations + ------------------------------------------------------- */ + +size_t lit_len(uint8_t* lit) { + size_t k = 0; + while ((lit[k] != LIST_END)) { + k += 1; + } + return k; +} + +bool holds(uint8_t* buf, size_t s, size_t t, uint8_t* lit) { + size_t i = s; + while ((i < t)) { + size_t k = 0; + while ((((lit[k] != LIST_END) && ((i + k) < t)) && (buf[(i + k)] == lit[k]))) { + k += 1; + } + if ((lit[k] == LIST_END)) { + return true; + } + i += 1; + } + return false; +} + +bool flag_on(uint8_t fl, uint8_t f) { + return (((fl / f) % 2) == 1); +} + +uint8_t seal_flags(uint8_t* buf, size_t f, size_t t) { + bool read = false; + bool none = false; + bool recorded = false; + bool spec_same = false; + bool vacuous = false; + uint32_t gens = 0; + uint32_t gen_same = 0; + uint32_t gen_missing = 0; + size_t s = f; + while ((s < t)) { + size_t e = line_end(buf, s, t); + size_t x = text_end(buf, s, e); + if (holds(buf, s, x, NO_SEAL)) { + read = true; + none = true; + } + if (has_prefix(SPEC_LINE, buf, s, x)) { + read = true; + if ((holds(buf, s, x, SAVED_MISSING) == false)) { + recorded = true; + } + if (has_prefix(SPEC_SAME_LINE, buf, s, x)) { + spec_same = true; + } + } + if (has_prefix(GEN_LINE, buf, s, x)) { + gens += 1; + if (holds(buf, s, x, SAME_WORD)) { + gen_same += 1; + } + if (holds(buf, s, x, SAVED_MISSING)) { + gen_missing += 1; + } + } + if (holds(buf, s, x, VACUOUS_LINE)) { + vacuous = true; + } + s = (e + 1); + } + uint8_t fl = 0; + if (read) { + fl += F_READ; + } + if ((recorded && (none == false))) { + fl += F_RECORDED; + } + if ((spec_same && (none == false))) { + fl += F_SPEC_SAME; + } + if ((((gens == GEN_HASHES) && (gen_missing == 0)) && (none == false))) { + fl += F_EXP_RECORDED; + } + if ((((gens == GEN_HASHES) && (gen_same == GEN_HASHES)) && (none == false))) { + fl += F_EXP_SAME; + } + if (vacuous) { + fl += F_VACUOUS; + } + return fl; +} + +uint8_t flags_decision(uint8_t fl) { + return hot_decision(flag_on(fl, F_RECORDED), flag_on(fl, F_SPEC_SAME), flag_on(fl, F_EXP_RECORDED), flag_on(fl, F_EXP_SAME)); +} + +uint8_t flags_verdict(uint8_t fl) { + if ((flag_on(fl, F_READ) == false)) { + return V_UNREADABLE; + } + if (flag_on(fl, F_VACUOUS)) { + return V_VACUOUS; + } + uint8_t d = flags_decision(fl); + if ((d == HOT_REUSE)) { + return V_SEALED; + } + if ((d == HOT_REBUILD_SPEC)) { + return V_STALE; + } + if ((d == HOT_REBUILD_EXPORTER)) { + return V_TOOLCHAIN; + } + return V_UNSEALED; +} + +uint8_t verdict_conclusion(uint8_t v) { + if ((v == V_SEALED)) { + return CONCL_SUCCESS; + } + if (((v == V_TOOLCHAIN) || (v == V_UNSEALED))) { + return CONCL_NEUTRAL; + } + return CONCL_FAILURE; +} + +uint8_t role_conclusion(uint8_t role, uint8_t c) { + if (((role == ROLE_REPORT) && (c == CONCL_FAILURE))) { + return CONCL_NEUTRAL; + } + return c; +} + +size_t consumer_index(uint8_t* buf, size_t f, size_t t) { + size_t i = 0; + while ((i < CONSUMERS)) { + size_t k = 0; + while ((((CONSUMER_NAMES[i][k] != LIST_END) && ((f + k) < t)) && (lower(buf[(f + k)]) == lower(CONSUMER_NAMES[i][k])))) { + k += 1; + } + if (((CONSUMER_NAMES[i][k] == LIST_END) && ((f + k) == t))) { + return i; + } + i += 1; + } + return CONSUMERS; +} + +uint8_t consumer_role(uint8_t* buf, size_t f, size_t t) { + size_t i = consumer_index(buf, f, t); + if ((i >= CONSUMERS)) { + return ROLE_CHECK; + } + return CONSUMER_ROLE[i]; +} + +bool digest_valid(uint8_t* buf, size_t f, size_t t) { + uint32_t len = 0; + size_t i = f; + while ((i < t)) { + uint8_t c = lower(buf[i]); + if (((((c >= '0') && (c <= '9')) == false) && (((c >= 'a') && (c <= 'f')) == false))) { + return false; + } + len += 1; + i += 1; + } + return digest_ok(len); +} + +size_t next_record(uint8_t* buf, size_t s, size_t t) { + size_t p = s; + while ((p < t)) { + size_t e = line_end(buf, p, t); + if (has_prefix(RECORD_LINE, buf, p, text_end(buf, p, e))) { + return p; + } + p = (e + 1); + } + return t; +} + +uint8_t record_verdict(uint8_t* buf, size_t r, size_t b) { + size_t e = line_end(buf, r, b); + size_t x = text_end(buf, r, e); + size_t p1 = first_tab(buf, r, x); + size_t p2 = first_tab(buf, (p1 + 1), x); + if ((p2 >= x)) { + return V_UNREADABLE; + } + size_t p3 = first_tab(buf, (p2 + 1), x); + if (((p3 >= x) || (p2 == (p1 + 1)))) { + return V_UNREADABLE; + } + if ((digest_valid(buf, (p2 + 1), p3) == false)) { + return V_UNREADABLE; + } + return flags_verdict(seal_flags(buf, (e + 1), b)); +} + +size_t repo_at(uint8_t* buf, size_t n) { + size_t s = 0; + while ((s < n)) { + size_t e = line_end(buf, s, n); + if (has_prefix(REPO_LINE, buf, s, text_end(buf, s, e))) { + return s; + } + s = (e + 1); + } + return n; +} + +uint8_t input_role(uint8_t* buf, size_t n) { + size_t s = repo_at(buf, n); + if ((s >= n)) { + return ROLE_CHECK; + } + size_t e = line_end(buf, s, n); + return consumer_role(buf, (s + 5), text_end(buf, s, e)); +} + +uint32_t record_count(uint8_t* buf, size_t n) { + uint32_t c = 0; + size_t r = next_record(buf, 0, n); + while ((r < n)) { + c += 1; + r = next_record(buf, (line_end(buf, r, n) + 1), n); + } + return c; +} + +bool input_readable(uint8_t* buf, size_t n) { + if ((repo_at(buf, n) >= n)) { + return false; + } + if ((marker_at(buf, n, 0, MARK_UNREADABLE) < n)) { + return false; + } + return true; +} + +uint8_t t27f_conclusion(uint8_t* buf, size_t n) { + if ((input_readable(buf, n) == false)) { + return CONCL_FAILURE; + } + uint8_t worst = CONCL_NEUTRAL; + size_t r = next_record(buf, 0, n); + if ((r < n)) { + worst = CONCL_SUCCESS; + } + while ((r < n)) { + size_t b = next_record(buf, (line_end(buf, r, n) + 1), n); + uint8_t c = verdict_conclusion(record_verdict(buf, r, b)); + if ((c > worst)) { + worst = c; + } + r = b; + } + return role_conclusion(input_role(buf, n), worst); +} + +size_t put_decimal(uint8_t* out, size_t w, size_t cap, uint32_t v) { + uint32_t d = 1; + while (((v / d) >= 10)) { + d = (d * 10); + } + size_t p = w; + while ((d > 0)) { + p = put(out, p, cap, DIGITS[((v / d) % 10)]); + d = (d / 10); + } + return p; +} + +size_t put_query(uint8_t* out, size_t w, size_t cap, uint8_t* buf, size_t f, size_t t) { + size_t p = w; + size_t i = f; + while ((i < t)) { + uint8_t c = buf[i]; + bool plain = (((((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z'))) || ((c >= '0') && (c <= '9'))) || is_letter_of(URL_SAFE, c)); + if (plain) { + p = put(out, p, cap, c); + } else { + p = put(out, p, cap, '%'); + p = put(out, p, cap, HEX[(c / 16)]); + p = put(out, p, cap, HEX[(c % 16)]); + } + i += 1; + } + return p; +} + +size_t put_row(uint8_t* out, size_t w, size_t cap, uint8_t* buf, size_t r, size_t b) { + size_t e = line_end(buf, r, b); + size_t x = text_end(buf, r, e); + size_t p1 = first_tab(buf, r, x); + size_t p2 = first_tab(buf, (p1 + 1), x); + size_t p3 = first_tab(buf, (p2 + 1), x); + uint8_t v = record_verdict(buf, r, b); + size_t p = put_msg(out, w, cap, "| `$"); + p = put_range(out, p, cap, buf, (p1 + 1), p2); + p = put_msg(out, p, cap, "` | `$"); + size_t shown = p3; + if (((p3 < x) && (p3 > ((p2 + 1) + DIGEST_SHOWN)))) { + shown = ((p2 + 1) + DIGEST_SHOWN); + } + if ((p2 < x)) { + p = put_range(out, p, cap, buf, (p2 + 1), shown); + } + p = put_msg(out, p, cap, "` | - | - | $"); + p = put_msg(out, p, cap, VERDICT_NAMES[v]); + p = put_msg(out, p, cap, " | $"); + if ((v == V_UNREADABLE)) { + p = put(out, p, cap, '-'); + } else { + p = put_msg(out, p, cap, DECISION_LABELS[flags_decision(seal_flags(buf, (e + 1), b))]); + } + p = put_msg(out, p, cap, " | $"); + if ((p3 < x)) { + p = put_msg(out, p, cap, "[view]($"); + p = put_msg(out, p, cap, GRAPH_BASE); + p = put_query(out, p, cap, buf, (p3 + 1), x); + p = put(out, p, cap, ')'); + } else { + p = put(out, p, cap, '-'); + } + p = put_msg(out, p, cap, " |$"); + return put(out, p, cap, '\n'); +} + +size_t put_overflow(uint8_t* out, size_t cap) { + size_t p = put_msg(out, 0, cap, CHECK_NAME); + p = put(out, p, cap, '\n'); + p = put_msg(out, p, cap, CONCL_NAMES[CONCL_FAILURE]); + p = put(out, p, cap, '\n'); + p = put_msg(out, p, cap, TITLE_OVERFLOW); + p = put(out, p, cap, '\n'); + if ((p > cap)) { + return cap; + } + return p; +} + +size_t t27f_report(uint8_t* buf, size_t n, uint8_t* out, size_t cap) { + uint8_t concl = t27f_conclusion(buf, n); + size_t p = put_msg(out, 0, cap, CHECK_NAME); + p = put(out, p, cap, '\n'); + p = put_msg(out, p, cap, CONCL_NAMES[concl]); + p = put(out, p, cap, '\n'); + if ((input_readable(buf, n) == false)) { + p = put_msg(out, p, cap, TITLE_UNREADABLE); + p = put(out, p, cap, '\n'); + if ((p > cap)) { + return put_overflow(out, cap); + } + return p; + } + uint32_t total = record_count(buf, n); + if ((total == 0)) { + p = put_msg(out, p, cap, TITLE_EMPTY); + p = put(out, p, cap, '\n'); + if ((p > cap)) { + return put_overflow(out, cap); + } + return p; + } + uint32_t sealed = 0; + uint32_t neutral = 0; + uint32_t failing = 0; + size_t r = next_record(buf, 0, n); + while ((r < n)) { + size_t b = next_record(buf, (line_end(buf, r, n) + 1), n); + uint8_t c = verdict_conclusion(record_verdict(buf, r, b)); + if ((c == CONCL_SUCCESS)) { + sealed += 1; + } + if ((c == CONCL_NEUTRAL)) { + neutral += 1; + } + if ((c == CONCL_FAILURE)) { + failing += 1; + } + r = b; + } + p = put_msg(out, p, cap, TITLE_HEAD); + p = put_decimal(out, p, cap, total); + p = put_msg(out, p, cap, TITLE_SPECS); + p = put_decimal(out, p, cap, sealed); + p = put_msg(out, p, cap, TITLE_SEALED); + p = put_decimal(out, p, cap, neutral); + p = put_msg(out, p, cap, TITLE_NEUTRAL); + p = put_decimal(out, p, cap, failing); + p = put_msg(out, p, cap, TITLE_FAILING); + p = put(out, p, cap, '\n'); + p = put_msg(out, p, cap, SUMMARY_HEAD); + p = put(out, p, cap, '\n'); + p = put(out, p, cap, '\n'); + if ((input_role(buf, n) == ROLE_REPORT)) { + p = put_msg(out, p, cap, REPORTING_NOTE); + p = put(out, p, cap, '\n'); + p = put(out, p, cap, '\n'); + } + p = put_msg(out, p, cap, TABLE_HEAD); + p = put(out, p, cap, '\n'); + p = put_msg(out, p, cap, TABLE_RULE); + p = put(out, p, cap, '\n'); + r = next_record(buf, 0, n); + while ((r < n)) { + size_t b = next_record(buf, (line_end(buf, r, n) + 1), n); + p = put_row(out, p, cap, buf, r, b); + r = b; + } + p = put(out, p, cap, '\n'); + p = put_msg(out, p, cap, SUMMARY_FOOT); + p = put(out, p, cap, '\n'); + if ((p > cap)) { + return put_overflow(out, cap); + } + return p; +} + +bool scope_spec(uint8_t* buf, size_t f, size_t t) { + size_t k = lit_len(SPEC_EXT); + if ((t <= (f + k))) { + return false; + } + if ((buf[f] == '"')) { + return false; + } + size_t i = 0; + while ((i < k)) { + if ((lower(buf[((t - k) + i)]) != SPEC_EXT[i])) { + return false; + } + i += 1; + } + return (buf[((t - k) - 1)] != '/'); +} + +size_t t27f_scope(uint8_t* buf, size_t n, uint8_t* set, size_t scap) { + size_t g = marker_at(buf, n, 0, MARK_GRAPH); + if ((g >= n)) { + return (scap + 1); + } + size_t sn = 0; + size_t s = 0; + while (((s < g) && (sn <= scap))) { + size_t e = line_end(buf, s, g); + size_t t = text_end(buf, s, e); + if (scope_spec(buf, s, t)) { + sn = set_add(set, sn, scap, buf, s, t); + } + s = (e + 1); + } + size_t seeds = sn; + s = (line_end(buf, g, n) + 1); + while (((s < n) && (sn <= scap))) { + size_t e = line_end(buf, s, n); + size_t t = text_end(buf, s, e); + size_t p = first_tab(buf, s, t); + if ((((p < t) && (p > s)) && (seeds <= scap))) { + if ((in_set(set, seeds, buf, (p + 1), t) && scope_spec(buf, s, p))) { + sn = set_add(set, sn, scap, buf, s, p); + } + } + s = (e + 1); + } + return sn; +} + +uint32_t census_specs(void) { + uint32_t c = 0; + size_t i = 0; + while ((i < CONSUMERS)) { + c += CONSUMER_SPECS[i]; + i += 1; + } + return c; +} + +uint32_t census_seals(void) { + uint32_t c = 0; + size_t i = 0; + while ((i < CONSUMERS)) { + c += CONSUMER_SEALS[i]; + i += 1; + } + return c; +} + +size_t first_tab(uint8_t* buf, size_t s, size_t t) { + size_t i = s; + while ((i < t)) { + if ((buf[i] == '\t')) { + return i; + } + i += 1; + } + return t; +} + +bool in_set(uint8_t* set, size_t sn, uint8_t* s, size_t f, size_t t) { + size_t i = 0; + while ((i < sn)) { + size_t e = line_end(set, i, sn); + if (same(set, i, e, s, f, t)) { + return true; + } + i = (e + 1); + } + return false; +} + +bool line_is(uint8_t* buf, size_t s, size_t t, uint8_t* lit) { + size_t k = 0; + while ((lit[k] != LIST_END)) { + if (((s + k) >= t)) { + return false; + } + if ((buf[(s + k)] != lit[k])) { + return false; + } + k += 1; + } + return ((s + k) == t); +} + +uint8_t lower(uint8_t c) { + if (((c >= 'A') && (c <= 'Z'))) { + return (c + 32); + } + return c; +} + +size_t marker_at(uint8_t* buf, size_t n, size_t from, uint8_t* lit) { + size_t s = from; + while ((s < n)) { + size_t e = line_end(buf, s, n); + if (line_is(buf, s, text_end(buf, s, e), lit)) { + return s; + } + s = (e + 1); + } + return n; +} + +size_t put(uint8_t* out, size_t w, size_t cap, uint8_t c) { + if ((w < cap)) { + out[w] = c; + } + return (w + 1); +} + +bool same(uint8_t* a, size_t af, size_t at, uint8_t* b, size_t bf, size_t bt) { + if (((at - af) != (bt - bf))) { + return false; + } + size_t k = 0; + while (((af + k) < at)) { + if ((a[(af + k)] != b[(bf + k)])) { + return false; + } + k += 1; + } + return true; +} + +size_t set_add(uint8_t* set, size_t sn, size_t scap, uint8_t* s, size_t f, size_t t) { + if ((sn > scap)) { + return sn; + } + if (in_set(set, sn, s, f, t)) { + return sn; + } + if ((((sn + (t - f)) + 1) > scap)) { + return (scap + 1); + } + size_t w = sn; + size_t i = f; + while ((i < t)) { + set[w] = s[i]; + w += 1; + i += 1; + } + set[w] = '\n'; + return (w + 1); +} + +bool has_prefix(uint8_t* list, uint8_t* s, size_t from, size_t to) { + size_t i = 0; + while ((list[i] != LIST_END)) { + size_t k = from; + bool ok = true; + while (((list[i] != LIST_SEP) && (list[i] != LIST_END))) { + if ((k >= to)) { + ok = false; + } else if ((s[k] != list[i])) { + ok = false; + } + k += 1; + i += 1; + } + if (ok) { + return true; + } + if ((list[i] == LIST_SEP)) { + i += 1; + } + } + return false; +} + +bool is_letter_of(uint8_t* list, uint8_t c) { + size_t i = 0; + while ((list[i] != LIST_END)) { + if ((list[i] == c)) { + return true; + } + i += 1; + } + return false; +} + +size_t put_msg(uint8_t* out, size_t w, size_t cap, uint8_t* msg) { + size_t p = w; + size_t i = 0; + while ((msg[i] != LIST_END)) { + p = put(out, p, cap, msg[i]); + i += 1; + } + return p; +} + +size_t put_range(uint8_t* out, size_t w, size_t cap, uint8_t* s, size_t from, size_t to) { + size_t p = w; + size_t i = from; + while ((i < to)) { + p = put(out, p, cap, s[i]); + i += 1; + } + return p; +} + +uint8_t verdict(uint8_t r) { + if ((r >= DENY_FROM)) { + return DENY; + } + return ALLOW; +} + +size_t line_end(uint8_t* buf, size_t s, size_t n) { + size_t e = s; + while (((e < n) && (buf[e] != '\n'))) { + e += 1; + } + return e; +} + +size_t text_end(uint8_t* buf, size_t s, size_t e) { + if (((e > s) && (buf[(e - 1)] == '\r'))) { + return (e - 1); + } + return e; +} + +bool digest_ok(uint32_t len) { + return (len == DIGEST_HEX_LEN); +} + +uint8_t hot_decision(bool spec_recorded, bool spec_equal, bool exporter_recorded, bool exporter_equal) { + uint8_t r = part_reason(recorded_part(spec_recorded, spec_equal), REBUILD_SPEC); + if ((r != REUSE)) { + return hot_from_reuse(r); + } + r = part_reason(recorded_part(exporter_recorded, exporter_equal), REBUILD_TOOLCHAIN); + return hot_from_reuse(r); +} + +uint8_t hot_from_reuse(uint8_t r) { + if ((r == REUSE)) { + return HOT_REUSE; + } + if ((r == REBUILD_SPEC)) { + return HOT_REBUILD_SPEC; + } + if ((r == REBUILD_TOOLCHAIN)) { + return HOT_REBUILD_EXPORTER; + } + return HOT_MISSING; +} + +uint8_t part_reason(uint8_t p, uint8_t diff_reason) { + if ((p == PART_SAME)) { + return REUSE; + } + if ((p == PART_DIFF)) { + return diff_reason; + } + return REBUILD_MISSING; +} + +uint8_t recorded_part(bool recorded, bool equal) { + if ((recorded == false)) { + return PART_MISSING; + } + if (equal) { + return PART_SAME; + } + return PART_DIFF; +} + +/* ------------------------------------------------------- + Invariants (compile-time assertions) + ------------------------------------------------------- */ + +/* invariant: the_conclusions_order_success_neutral_failure */ +_Static_assert((CONCL_SUCCESS < CONCL_NEUTRAL), "invariant: the_conclusions_order_success_neutral_failure"); +_Static_assert((CONCL_NEUTRAL < CONCL_FAILURE), "invariant: the_conclusions_order_success_neutral_failure"); +_Static_assert((V_SEALED == HOT_REUSE), "invariant: the_conclusions_order_success_neutral_failure"); +_Static_assert((ROLE_CHECK != ROLE_REPORT), "invariant: the_conclusions_order_success_neutral_failure"); + + +/* ------------------------------------------------------- + Tests + ------------------------------------------------------- */ + +void test_the_census_is_the_measured_one(void) { + assert((census_specs() == 9849)); + assert((census_seals() == 2600)); + assert((CONSUMER_SPECS[0] == 2604)); + assert((CONSUMER_SPECS[1] == 3458)); + assert((CONSUMER_SEALS[7] == 29)); +} + +void test_only_the_published_copy_reports(void) { + uint32_t reporting = 0; + size_t i = 0; + while ((i < CONSUMERS)) { + if ((CONSUMER_ROLE[i] == ROLE_REPORT)) { + reporting += 1; + } + i += 1; + } + assert((reporting == 1)); + const char* pub_copy = "gHashTag/ghashtag.github.io$"; + assert((consumer_role(pub_copy, 0, lit_len(pub_copy)) == ROLE_REPORT)); + const char* site = "gHashTag/trinity$"; + assert((consumer_role(site, 0, lit_len(site)) == ROLE_CHECK)); +} + +void test_every_consumer_is_found_by_its_own_name(void) { + size_t i = 0; + while ((i < CONSUMERS)) { + assert((consumer_index(CONSUMER_NAMES[i], 0, lit_len(CONSUMER_NAMES[i])) == i)); + i += 1; + } +} + +void test_negative_control_an_unlisted_or_truncated_name_is_checked_not_reporting(void) { + const char* other = "someone/else$"; + assert((consumer_index(other, 0, lit_len(other)) == CONSUMERS)); + assert((consumer_role(other, 0, lit_len(other)) == ROLE_CHECK)); + const char* cut = "gHashTag/ghashtag.github$"; + assert((consumer_index(cut, 0, lit_len(cut)) == CONSUMERS)); + const char* longer = "gHashTag/ghashtag.github.io2$"; + assert((consumer_index(longer, 0, lit_len(longer)) == CONSUMERS)); + const char* upper = "GHASHTAG/GHASHTAG.GITHUB.IO$"; + assert((consumer_role(upper, 0, lit_len(upper)) == ROLE_REPORT)); +} + +void test_a_matching_seal_is_sealed_and_succeeds(void) { + const char* out = "spec_hash: MATCH\ngen_hash_zig: MATCH\ngen_hash_verilog: MATCH\ngen_hash_c: MATCH\ngen_hash_rust: MATCH\n\nall hashes MATCH\n$"; + uint8_t fl = seal_flags(out, 0, lit_len(out)); + assert((flag_on(fl, F_READ) == true)); + assert((flag_on(fl, F_RECORDED) == true)); + assert((flag_on(fl, F_SPEC_SAME) == true)); + assert((flag_on(fl, F_EXP_RECORDED) == true)); + assert((flag_on(fl, F_EXP_SAME) == true)); + assert((flags_decision(fl) == HOT_REUSE)); + assert((flags_verdict(fl) == V_SEALED)); + assert((verdict_conclusion(V_SEALED) == CONCL_SUCCESS)); +} + +void test_negative_control_a_missing_seal_is_unsealed_never_success(void) { + const char* out = "Error: No saved seal found at .trinity/seals/A.json. Run with --save first.\n$"; + uint8_t fl = seal_flags(out, 0, lit_len(out)); + assert((flag_on(fl, F_READ) == true)); + assert((flag_on(fl, F_RECORDED) == false)); + assert((flags_decision(fl) == HOT_MISSING)); + assert((flags_verdict(fl) == V_UNSEALED)); + assert((verdict_conclusion(flags_verdict(fl)) == CONCL_NEUTRAL)); +} + +void test_a_seal_saved_for_other_bytes_is_stale_and_fails(void) { + const char* out = "spec_hash: MISMATCH (saved=sha256:aa, current=sha256:bb)\ngen_hash_zig: MATCH\ngen_hash_verilog: MATCH\ngen_hash_c: MATCH\ngen_hash_rust: MATCH\n\nVERIFICATION FAILED\n$"; + uint8_t fl = seal_flags(out, 0, lit_len(out)); + assert((flag_on(fl, F_RECORDED) == true)); + assert((flag_on(fl, F_SPEC_SAME) == false)); + assert((flags_decision(fl) == HOT_REBUILD_SPEC)); + assert((flags_verdict(fl) == V_STALE)); + assert((verdict_conclusion(V_STALE) == CONCL_FAILURE)); +} + +void test_a_moved_toolchain_is_neutral(void) { + const char* out = "spec_hash: MATCH\ngen_hash_zig: MISMATCH (saved=sha256:aa, current=sha256:bb)\ngen_hash_verilog: MATCH\ngen_hash_c: MATCH\ngen_hash_rust: MATCH\n$"; + uint8_t fl = seal_flags(out, 0, lit_len(out)); + assert((flag_on(fl, F_EXP_RECORDED) == true)); + assert((flag_on(fl, F_EXP_SAME) == false)); + assert((flags_decision(fl) == HOT_REBUILD_EXPORTER)); + assert((flags_verdict(fl) == V_TOOLCHAIN)); + assert((verdict_conclusion(V_TOOLCHAIN) == CONCL_NEUTRAL)); +} + +void test_a_seal_without_gen_hashes_is_missing_not_sealed(void) { + const char* out = "spec_hash: MATCH\ngen_hash_zig: MISMATCH (saved=missing, current=sha256:bb)\ngen_hash_verilog: MATCH\ngen_hash_c: MATCH\ngen_hash_rust: MATCH\n$"; + uint8_t fl = seal_flags(out, 0, lit_len(out)); + assert((flag_on(fl, F_EXP_RECORDED) == false)); + assert((flags_decision(fl) == HOT_MISSING)); + assert((flags_verdict(fl) == V_UNSEALED)); + const char* three = "spec_hash: MATCH\ngen_hash_zig: MATCH\ngen_hash_c: MATCH\ngen_hash_rust: MATCH\n$"; + assert((flags_verdict(seal_flags(three, 0, lit_len(three))) == V_UNSEALED)); +} + +void test_negative_control_a_vacuous_seal_fails_even_when_it_matches(void) { + const char* out = "spec_hash: MATCH\ngen_hash_zig: MATCH\ngen_hash_verilog: MATCH\ngen_hash_c: MATCH\ngen_hash_rust: MATCH\n\nVERIFICATION FAILED -- the saved seal is vacuous: every gen_hash is none\n$"; + uint8_t fl = seal_flags(out, 0, lit_len(out)); + assert((flags_decision(fl) == HOT_REUSE)); + assert((flags_verdict(fl) == V_VACUOUS)); + assert((verdict_conclusion(V_VACUOUS) == CONCL_FAILURE)); +} + +void test_negative_control_output_with_no_known_line_is_unreadable(void) { + const char* out = "Error: parse error at line 3\n$"; + uint8_t fl = seal_flags(out, 0, lit_len(out)); + assert((flag_on(fl, F_READ) == false)); + assert((flags_verdict(fl) == V_UNREADABLE)); + assert((verdict_conclusion(V_UNREADABLE) == CONCL_FAILURE)); + assert((flags_verdict(seal_flags(out, 0, 0)) == V_UNREADABLE)); + assert((verdict_conclusion(200) == CONCL_FAILURE)); +} + +void test_the_reporting_role_softens_only_failure(void) { + assert((role_conclusion(ROLE_REPORT, CONCL_FAILURE) == CONCL_NEUTRAL)); + assert((role_conclusion(ROLE_REPORT, CONCL_SUCCESS) == CONCL_SUCCESS)); + assert((role_conclusion(ROLE_CHECK, CONCL_FAILURE) == CONCL_FAILURE)); + assert((role_conclusion(ROLE_CHECK, CONCL_NEUTRAL) == CONCL_NEUTRAL)); +} + +void test_a_digest_is_sixty_four_hex_characters(void) { + const char* good = "0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789abcdef$"; + assert((digest_valid(good, 0, lit_len(good)) == true)); + const char* brief = "0123456789abcdef$"; + assert((digest_valid(brief, 0, lit_len(brief)) == false)); + const char* bad = "0123456789abcdeg0123456789abcdef0123456789abcdef0123456789abcdef$"; + assert((digest_valid(bad, 0, lit_len(bad)) == false)); +} + +void test_the_worst_row_decides_the_check(void) { + const char* inp = "repo\tgHashTag/tt-trinity-phi\n@\tspecs/a.t27\t0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\thttps://raw.githubusercontent.com/gHashTag/tt-trinity-phi/abc/specs/a.t27\nspec_hash: MATCH\ngen_hash_zig: MATCH\ngen_hash_verilog: MATCH\ngen_hash_c: MATCH\ngen_hash_rust: MATCH\n@\tspecs/b.t27\t0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\thttps://raw.githubusercontent.com/gHashTag/tt-trinity-phi/abc/specs/b.t27\nError: No saved seal found at .trinity/seals/B.json. Run with --save first.\n$"; + size_t n = lit_len(inp); + assert((record_count(inp, n) == 2)); + assert((t27f_conclusion(inp, n) == CONCL_NEUTRAL)); + const char* ok = "repo\tgHashTag/tt-trinity-phi\n@\tspecs/a.t27\t0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\thttps://raw.githubusercontent.com/gHashTag/tt-trinity-phi/abc/specs/a.t27\nspec_hash: MATCH\ngen_hash_zig: MATCH\ngen_hash_verilog: MATCH\ngen_hash_c: MATCH\ngen_hash_rust: MATCH\n$"; + assert((t27f_conclusion(ok, lit_len(ok)) == CONCL_SUCCESS)); + const char* stale = "repo\tgHashTag/tt-trinity-phi\n@\tspecs/a.t27\t0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\thttps://raw.githubusercontent.com/gHashTag/tt-trinity-phi/abc/specs/a.t27\nspec_hash: MISMATCH (saved=sha256:aa, current=sha256:bb)\n$"; + assert((t27f_conclusion(stale, lit_len(stale)) == CONCL_FAILURE)); +} + +void test_negative_control_unreadable_input_fails_the_check(void) { + const char* no_repo = "@\tspecs/a.t27\t0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\turl\nspec_hash: MATCH\n$"; + assert((t27f_conclusion(no_repo, lit_len(no_repo)) == CONCL_FAILURE)); + const char* marked = "repo\tgHashTag/trios\n--unreadable\n$"; + assert((t27f_conclusion(marked, lit_len(marked)) == CONCL_FAILURE)); + const char* bad_digest = "repo\tgHashTag/trios\n@\tspecs/a.t27\tnot-a-digest\turl\nspec_hash: MATCH\ngen_hash_zig: MATCH\ngen_hash_verilog: MATCH\ngen_hash_c: MATCH\ngen_hash_rust: MATCH\n$"; + assert((t27f_conclusion(bad_digest, lit_len(bad_digest)) == CONCL_FAILURE)); + const char* few_tabs = "repo\tgHashTag/trios\n@\tspecs/a.t27\nspec_hash: MATCH\n$"; + assert((t27f_conclusion(few_tabs, lit_len(few_tabs)) == CONCL_FAILURE)); + const char* empty = "repo\tgHashTag/trios\n$"; + assert((t27f_conclusion(empty, lit_len(empty)) == CONCL_NEUTRAL)); +} + +void test_the_published_copy_posts_a_failure_as_neutral(void) { + const char* stale = "repo\tgHashTag/ghashtag.github.io\n@\tt27/a.t27\t0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\turl\nspec_hash: MISMATCH (saved=sha256:aa, current=sha256:bb)\n$"; + assert((t27f_conclusion(stale, lit_len(stale)) == CONCL_NEUTRAL)); + const char* marked = "--unreadable\nrepo\tgHashTag/ghashtag.github.io\n$"; + assert((t27f_conclusion(marked, lit_len(marked)) == CONCL_FAILURE)); +} + +void test_the_report_names_the_check_and_carries_the_row_vocabulary(void) { + uint8_t out[4096] = {0}; + const char* inp = "repo\tgHashTag/tt-trinity-phi\n@\tspecs/a b.t27\t0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\thttps://raw.githubusercontent.com/gHashTag/tt-trinity-phi/abc/specs/a b.t27\nspec_hash: MATCH\ngen_hash_zig: MATCH\ngen_hash_verilog: MATCH\ngen_hash_c: MATCH\ngen_hash_rust: MATCH\n$"; + size_t w = t27f_report(inp, lit_len(inp), &out, 4096); + assert((w < 4096)); + assert((holds(&out, 0, w, "claw/t27f\nsuccess\nT27F seals: 1 spec(s), 1 sealed, 0 neutral, 0 failing\n$") == true)); + assert((holds(&out, 0, w, "| path | digest | nodes | edges | verdict | decision | graph |$") == true)); + assert((holds(&out, 0, w, "| `specs/a b.t27` | `0123456789ab` | - | - | sealed | reuse | [view](https://t27.ai/t27f/graph/?src=https://raw.githubusercontent.com/gHashTag/tt-trinity-phi/abc/specs/a%20b.t27) |$") == true)); + assert((holds(&out, 0, w, "reporting role$") == false)); +} + +void test_the_report_of_an_unreadable_row_shows_no_decision(void) { + uint8_t out[4096] = {0}; + const char* inp = "repo\tgHashTag/trios\n@\tspecs/a.t27\t0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\turl\nError: parse error\n$"; + size_t w = t27f_report(inp, lit_len(inp), &out, 4096); + assert((holds(&out, 0, w, "claw/t27f\nfailure\nT27F seals: 1 spec(s), 0 sealed, 0 neutral, 1 failing\n$") == true)); + assert((holds(&out, 0, w, "| unreadable | - |$") == true)); +} + +void test_negative_control_a_report_that_does_not_fit_is_failure(void) { + uint8_t out[4096] = {0}; + const char* inp = "repo\tgHashTag/trios\n@\tspecs/a.t27\t0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\turl\nspec_hash: MATCH\ngen_hash_zig: MATCH\ngen_hash_verilog: MATCH\ngen_hash_c: MATCH\ngen_hash_rust: MATCH\n$"; + size_t w = t27f_report(inp, lit_len(inp), &out, 200); + assert((w <= 200)); + assert((holds(&out, 0, w, "claw/t27f\nfailure\nT27F seals: the report did not fit\n$") == true)); + size_t u = t27f_report("@\tx\n$", 4, &out, 4096); + assert((holds(&out, 0, u, "claw/t27f\nfailure\nT27F seals: the input could not be read\n$") == true)); +} + +void test_the_scope_is_the_changed_specs_and_their_direct_importers(void) { + uint8_t set[512] = {0}; + const char* inp = "specs/c.t27\nREADME.md\n--graph\nspecs/b.t27\tspecs/c.t27\nspecs/a.t27\tspecs/b.t27\nspecs/x.t27\tspecs/y.t27\n$"; + size_t sn = t27f_scope(inp, lit_len(inp), &set, 512); + assert((sn <= 512)); + assert((in_set(&set, sn, "specs/c.t27", 0, 11) == true)); + assert((in_set(&set, sn, "specs/b.t27", 0, 11) == true)); + assert((in_set(&set, sn, "specs/a.t27", 0, 11) == false)); + assert((in_set(&set, sn, "specs/x.t27", 0, 11) == false)); + assert((in_set(&set, sn, "README.md", 0, 9) == false)); + assert((sn == 24)); +} + +void test_negative_control_an_import_does_not_put_what_it_imports_in_scope(void) { + uint8_t set[512] = {0}; + const char* inp = "specs/a.t27\n--graph\nspecs/a.t27\tspecs/b.t27\n$"; + size_t sn = t27f_scope(inp, lit_len(inp), &set, 512); + assert((in_set(&set, sn, "specs/b.t27", 0, 11) == false)); + assert((sn == 12)); +} + +void test_negative_control_a_scope_without_its_marker_or_room_fails_closed(void) { + uint8_t set[512] = {0}; + const char* no_mark = "specs/a.t27\n$"; + assert((t27f_scope(no_mark, lit_len(no_mark), &set, 512) == 513)); + const char* inp = "specs/c.t27\n--graph\nspecs/b.t27\tspecs/c.t27\n$"; + assert((t27f_scope(inp, lit_len(inp), &set, 20) == 21)); + assert((t27f_scope(inp, lit_len(inp), &set, 24) == 24)); + const char* quoted = "\"specs/a b.t27\"\nspecs/OK.T27\n--graph\n$"; + size_t sn = t27f_scope(quoted, lit_len(quoted), &set, 512); + assert((sn == 13)); +} + +void test_numbers_and_query_values_are_written_exactly(void) { + uint8_t out[64] = {0}; + size_t w = put_decimal(&out, 0, 64, 0); + assert((w == 1)); + assert((out[0] == '0')); + w = put_decimal(&out, 0, 64, 9849); + assert((w == 4)); + assert((out[0] == '9')); + assert((out[3] == '9')); + const char* q = "a b&c/d:e#$"; + w = put_query(&out, 0, 64, q, 0, lit_len(q)); + assert((holds(&out, 0, w, "a%20b%26c/d:e%23$") == true)); +} + + +/* ------------------------------------------------------- + Test runner (compile with -DT27_TEST_MAIN to execute) + ------------------------------------------------------- */ + +#ifdef T27_TEST_MAIN +#include +int main(void) { + test_the_census_is_the_measured_one(); + test_only_the_published_copy_reports(); + test_every_consumer_is_found_by_its_own_name(); + test_negative_control_an_unlisted_or_truncated_name_is_checked_not_reporting(); + test_a_matching_seal_is_sealed_and_succeeds(); + test_negative_control_a_missing_seal_is_unsealed_never_success(); + test_a_seal_saved_for_other_bytes_is_stale_and_fails(); + test_a_moved_toolchain_is_neutral(); + test_a_seal_without_gen_hashes_is_missing_not_sealed(); + test_negative_control_a_vacuous_seal_fails_even_when_it_matches(); + test_negative_control_output_with_no_known_line_is_unreadable(); + test_the_reporting_role_softens_only_failure(); + test_a_digest_is_sixty_four_hex_characters(); + test_the_worst_row_decides_the_check(); + test_negative_control_unreadable_input_fails_the_check(); + test_the_published_copy_posts_a_failure_as_neutral(); + test_the_report_names_the_check_and_carries_the_row_vocabulary(); + test_the_report_of_an_unreadable_row_shows_no_decision(); + test_negative_control_a_report_that_does_not_fit_is_failure(); + test_the_scope_is_the_changed_specs_and_their_direct_importers(); + test_negative_control_an_import_does_not_put_what_it_imports_in_scope(); + test_negative_control_a_scope_without_its_marker_or_room_fails_closed(); + test_numbers_and_query_values_are_written_exactly(); + printf("All %d tests passed.\n", 23); + return 0; +} +#endif /* T27_TEST_MAIN */ + +#endif /* CLAWT27FGATE_H */ diff --git a/scripts/gen_w389.py b/scripts/gen_w389.py deleted file mode 100644 index d2ed64164f..0000000000 --- a/scripts/gen_w389.py +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/env python3 -"""Batch append W389 test/invariant blocks to all 27 IGLA specs.""" - -import re -from pathlib import Path - -SPECS = ( - list(Path("specs/igla/race").glob("*.t27")) + - list(Path("specs/igla/coder").glob("*.t27")) -) - -WAVE_HEADER_RE = re.compile(r"// Wave Loop (\d+)") - - -def wave_segments(text: str) -> list[tuple[int, int, int]]: - """Return list of (wave_number, start, end) for each Wave Loop block.""" - matches = list(WAVE_HEADER_RE.finditer(text)) - segments = [] - for i, m in enumerate(matches): - wave = int(m.group(1)) - start = m.start() - end = matches[i + 1].start() if i + 1 < len(matches) else len(text) - segments.append((wave, start, end)) - return segments - - -def append_w389(path: Path) -> None: - text = path.read_text() - segments = wave_segments(text) - if not segments: - print(f"SKIP {path}: no Wave Loop block found") - return - - last_wave, last_start, last_end = segments[-1] - if last_wave != 388: - print(f"SKIP {path}: last wave is {last_wave}, expected 388") - return - - block = text[last_start:last_end] - new_block = block - new_block = new_block.replace("Wave Loop 388", "Wave Loop 389") - new_block = new_block.replace("w388_", "w389_") - new_block = new_block.replace("after W387", "after W388") - new_block = new_block.replace("W387 seal regeneration", "W388 seal regeneration") - new_block = new_block.replace("WAVE_LOOP_387_COOPERATION.md", "WAVE_LOOP_388_COOPERATION.md") - - new_text = text[:last_end] + new_block - path.write_text(new_text) - print(f"APPENDED W389 to {path}") - - -if __name__ == "__main__": - for spec in sorted(SPECS): - append_w389(spec) diff --git a/specs/claw/gates.t27 b/specs/claw/gates.t27 index 9b04e3790f..f90a82dae0 100644 --- a/specs/claw/gates.t27 +++ b/specs/claw/gates.t27 @@ -23,7 +23,7 @@ ; it does not know is not a gate list (fail-closed applies to the inventory reader in T2). ; EVENTS are the `on:` trigger keys of each workflow, comma-joined, as measured 2026-10-12; they ; are recorded data for the report, not a class rule -- the class rule above is the law. -; Snapshot: 67 workflows on master, 2026-10-18 (pr-budget added, #10074). Census: 62 blocking + 3 soft + 2 reporting. +; Snapshot: 67 workflows on master, 2026-10-18 (gate-topology gained schedule for its claw-t27f job, #10268). Census: 62 blocking + 3 soft + 2 reporting. ; phi^2 + 1/phi^2 = 3 | TRINITY module ClawGates; @@ -102,7 +102,7 @@ pub const EVENTS : [67]str = [ "pull_request,workflow_dispatch", "pull_request,push", "pull_request,push,workflow_dispatch", "push,pull_request,workflow_dispatch", "push,pull_request,workflow_dispatch", "push,pull_request,workflow_dispatch", "pull_request,push,workflow_dispatch", "pull_request,push,workflow_dispatch", "push,workflow_dispatch", "pull_request,push,workflow_dispatch", "pull_request,push,workflow_dispatch", "pull_request,workflow_dispatch", "schedule,workflow_dispatch", "pull_request,workflow_dispatch", - "pull_request,push,workflow_dispatch", "pull_request,push,workflow_dispatch", "push,pull_request,workflow_dispatch", "push,pull_request,workflow_dispatch", "pull_request,push,workflow_dispatch", + "pull_request,push,workflow_dispatch", "pull_request,push,workflow_dispatch,schedule", "push,pull_request,workflow_dispatch", "push,pull_request,workflow_dispatch", "pull_request,push,workflow_dispatch", "pull_request_target,workflow_dispatch", "pull_request,workflow_dispatch", "pull_request,workflow_dispatch", "pull_request,push,workflow_dispatch", "schedule,workflow_dispatch", "schedule,workflow_dispatch", "pull_request,push,workflow_dispatch", "pull_request", "push,pull_request,workflow_dispatch", "pull_request,workflow_dispatch", "pull_request_target", "workflow_dispatch,pull_request", "schedule,workflow_dispatch", "schedule,workflow_dispatch", "schedule,workflow_dispatch", "schedule,workflow_dispatch", diff --git a/specs/claw/service.t27 b/specs/claw/service.t27 index 384acf40e8..85701115d7 100644 --- a/specs/claw/service.t27 +++ b/specs/claw/service.t27 @@ -20,12 +20,14 @@ ; phi^2 + 1/phi^2 = 3 | TRINITY module ClawService; -/// The three modes of the reusable workflow a consumer calls. audit: diff the consumer's -/// workflows directory against the gate inventory, exit code below. report: the markdown table -/// of the inventory, for a PR body or a step summary. matrix: the raw class/name/events rows. +/// The four modes of the service. audit: diff the consumer's workflows directory against the gate +/// inventory, exit code below. report: the markdown table of the inventory, for a PR body or a step +/// summary. matrix: the raw class/name/events rows. t27f: the seal-gated T27F check-run the App posts +/// on a pull request that touches .t27 files (specs/claw/t27f_gate.t27, check "claw/t27f"). pub const MODE_AUDIT : str = "audit"; pub const MODE_REPORT : str = "report"; pub const MODE_MATRIX : str = "matrix"; +pub const MODE_T27F : str = "t27f"; pub const DEFAULT_MODE : str = "audit"; /// The tag family a release is cut from (docs/claw/RELEASE.md is the checklist that gates a tag). @@ -50,7 +52,7 @@ pub const LABEL_REPORTING : str = "reporting"; pub const LABEL_UNKNOWN : str = "unknown"; pub fn mode_valid(m : str) -> bool { - return m == MODE_AUDIT or m == MODE_REPORT or m == MODE_MATRIX; + return m == MODE_AUDIT or m == MODE_REPORT or m == MODE_MATRIX or m == MODE_T27F; } pub fn has_prefix(s : str, p : str) -> bool { @@ -85,17 +87,20 @@ pub fn class_label(c : u8) -> str { return LABEL_UNKNOWN; } -test mode_valid_accepts_the_three_modes { - const modes : [4]str = [ MODE_AUDIT, MODE_REPORT, MODE_MATRIX, DEFAULT_MODE ]; +test mode_valid_accepts_the_four_modes { + const modes : [5]str = [ MODE_AUDIT, MODE_REPORT, MODE_MATRIX, MODE_T27F, DEFAULT_MODE ]; var i : usize = 0; var valid : u32 = 0; - while (i < 4) { + while (i < 5) { if (mode_valid(modes[i])) { valid += 1; } i += 1; } - assert(valid == 4); + assert(valid == 5); assert(mode_valid("audit") == true); + assert(mode_valid("t27f") == true); assert(mode_valid("nope") == false); + assert(mode_valid("t27") == false); + assert(mode_valid("t27f2") == false); } test default_mode_is_audit_and_audit_is_valid { @@ -160,4 +165,5 @@ test check_prefix_namespaced { const check_run : str = "claw/matrix-drift"; assert(has_prefix(check_run, CHECK_PREFIX) == true); assert(has_prefix("matrix-drift", CHECK_PREFIX) == false); + assert(has_prefix("claw/t27f", CHECK_PREFIX) == true); } diff --git a/specs/claw/t27f_gate.t27 b/specs/claw/t27f_gate.t27 new file mode 100644 index 0000000000..acc0beea76 --- /dev/null +++ b/specs/claw/t27f_gate.t27 @@ -0,0 +1,819 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/claw/t27f_gate.t27 -- the claw/t27f check: a seal-gated T27F check, posted by the TRI CLAW App +; on every pull request that touches a .t27 file in any repository the App is installed on. +; Epic #9271. Owner order 2026-10-18: roll the T27F hot-reload and seal work out to every gHashTag +; repository that holds .t27 specs, through the TRI CLAW App (App id 5166765), with no +; workflow file added to the consumer repositories. +; -- +; Where things live: +; - Which specs a change touches: the changed .t27 files plus their DIRECT importers, read from the +; path-keyed edge list `t27c use-edges` prints. That is the idea of specs/ci/affected.t27, which +; follows importers transitively for this repository's tests; this check stops at one hop, because +; it re-verifies seals, and a seal records its own spec's bytes, not its importers' bytes. The +; set helpers are affected.t27's, imported, not copied. +; - What one spec's seal says: specs/t27f/hot.t27 hot_decision, which is built on +; specs/verified/seal_identity.t27 recorded_part. The spec part is spec_hash of the saved seal; +; the exporter part is its four gen hashes. A spec with no seal is MISSING and reads "unsealed": +; neutral, never success. A seal saved for other bytes is "stale-seal": failure. +; - What the check-run says: t27f_report below writes the whole text the App posts -- the check +; name, the conclusion, the title and the summary table. The job that posts it (claw-t27f in +; .github/workflows/gate-topology.yml) is I/O only: it lists the App's installations, fetches each +; pull request head, runs t27c from the signed t27o release and hands the bytes to this law. +; - A head commit that already carries a claw/t27f check-run is not checked again; the workflow does +; that lookup, the rule is stated here. +; -- +; Fail closed: an output this law cannot read, a digest that is not 64 hex characters, a scope the +; workflow could not compute and a report that does not fit are failure. Nothing unread is success. +; -- +; The honest limits: +; - nodes and edges are printed as "-": the T27F exporter is t27f.wasm in the viewer, not t27c, so +; the runner measures neither. The graph link names the spec by its raw URL in src=; the viewer at +; t27.ai/t27f/graph/ does not read src= yet, and the summary says so. +; - Importers are found under specs/ only (the `t27c use-edges --specs-dir specs` walk). A repository +; that keeps its specs elsewhere gets its changed files checked and no importers. +; - ghashtag.github.io is the published copy of trinity's site; its row is reporting, so a failure +; there is posted as neutral. Every other repository, listed or not, is checked. +; -- +; The consumer census, measured 2026-10-18 with the git trees API (no tree truncated): the .t27 files +; and the seal files (.trinity/seals/*.json) of each repository, in CONSUMER_NAMES order. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module ClawT27fGate; + +use policy::own_language; +use policy::text_lines; +use ci::affected; +use t27f::hot; + +/// The check-run name: CHECK_PREFIX of specs/claw/service.t27 and the gate (pinned in t27f_gate_pins.t27). +pub const CHECK_NAME : str = "claw/t27f$"; + +/// Conclusions, in the order they win: the worst row decides. +pub const CONCL_SUCCESS : u8 = 0; +pub const CONCL_NEUTRAL : u8 = 1; +pub const CONCL_FAILURE : u8 = 2; +/// How GitHub spells them, indexed by CONCL_*. +pub const CONCL_NAMES : [3]str = [ "success$", "neutral$", "failure$" ]; + +/// What one spec's row says. +pub const V_SEALED : u8 = 0; +pub const V_TOOLCHAIN : u8 = 1; +pub const V_UNSEALED : u8 = 2; +pub const V_STALE : u8 = 3; +pub const V_VACUOUS : u8 = 4; +pub const V_UNREADABLE : u8 = 5; +/// The row-state column, indexed by V_*. +pub const VERDICT_NAMES : [6]str = [ "sealed$", "toolchain-moved$", "unsealed$", "stale-seal$", "vacuous-seal$", "unreadable$" ]; + +/// The decision column: specs/t27f/hot.t27 HOT_DECISION_NAMES, '$'-terminated so gen-c can measure +/// them (pinned to the originals in t27f_gate_pins.t27). +pub const DECISION_LABELS : [4]str = [ "reuse$", "rebuild_spec$", "rebuild_exporter$", "missing$" ]; + +/// What the reader found in one spec's `t27c seal --verify` output. Each flag is a power of two; +/// flag_on() reads one back. +pub const F_READ : u8 = 1; +pub const F_RECORDED : u8 = 2; +pub const F_SPEC_SAME : u8 = 4; +pub const F_EXP_RECORDED : u8 = 8; +pub const F_EXP_SAME : u8 = 16; +pub const F_VACUOUS : u8 = 32; + +/// The gen hashes a seal records: zig, verilog, c, rust. +pub const GEN_HASHES : u32 = 4; + +/// The lines of `t27c seal --verify` this law reads (bootstrap/src/main.rs run_seal). +pub const NO_SEAL : str = "No saved seal found$"; +pub const SPEC_LINE : str = "spec_hash: $"; +pub const SPEC_SAME_LINE : str = "spec_hash: MATCH$"; +pub const GEN_LINE : str = "gen_hash_$"; +pub const SAME_WORD : str = ": MATCH$"; +pub const SAVED_MISSING : str = "saved=missing$"; +pub const VACUOUS_LINE : str = "saved seal is vacuous$"; + +/// The report input: a "repo" line, then per spec a record line "@pathdigestraw url" +/// followed by that spec's verify output. A MARK_UNREADABLE line anywhere fails the whole check. +pub const REPO_LINE : str = "repo\t$"; +pub const RECORD_LINE : str = "@\t$"; +pub const MARK_UNREADABLE : str = "--unreadable$"; +/// The scope input: changed paths, one per line, then MARK_GRAPH and the `t27c use-edges` lines. +pub const MARK_GRAPH : str = "--graph$"; +pub const SPEC_EXT : str = ".t27$"; + +/// The digest column shows this many hex characters of the sha256. +pub const DIGEST_SHOWN : usize = 12; + +/// The viewer a row links to, and the bytes a query value may carry unescaped. +pub const GRAPH_BASE : str = "https://t27.ai/t27f/graph/?src=$"; +pub const URL_SAFE : str = "-._~/:$"; +pub const HEX : str = "0123456789ABCDEF$"; +pub const DIGITS : str = "0123456789$"; + +/// The text pieces of the check-run. +pub const TITLE_HEAD : str = "T27F seals: $"; +pub const TITLE_SPECS : str = " spec(s), $"; +pub const TITLE_SEALED : str = " sealed, $"; +pub const TITLE_NEUTRAL : str = " neutral, $"; +pub const TITLE_FAILING : str = " failing$"; +pub const TITLE_EMPTY : str = "T27F seals: no spec in scope$"; +pub const TITLE_UNREADABLE : str = "T27F seals: the input could not be read$"; +pub const TITLE_OVERFLOW : str = "T27F seals: the report did not fit$"; +pub const SUMMARY_HEAD : str = "Seal-gated T27F check of the changed .t27 files and their direct importers, decided by specs/claw/t27f_gate.t27 in gHashTag/t27.$"; +pub const REPORTING_NOTE : str = "This repository is a published copy (reporting role): a failure is posted as neutral.$"; +pub const TABLE_HEAD : str = "| path | digest | nodes | edges | verdict | decision | graph |$"; +pub const TABLE_RULE : str = "|---|---|---|---|---|---|---|$"; +pub const SUMMARY_FOOT : str = "nodes and edges are not measured here: the T27F exporter is t27f.wasm in the viewer, not t27c. The graph link passes the spec's raw URL as src=; the viewer does not read src= yet and opens its own spec list.$"; + +/// A consumer repository's role. +pub const ROLE_CHECK : u8 = 0; +pub const ROLE_REPORT : u8 = 1; + +/// The consumers, measured 2026-10-18 (see the header). Names compare case-insensitively. +pub const CONSUMERS : usize = 16; +pub const CONSUMER_NAMES : [16]str = [ + "gHashTag/t27$", + "gHashTag/trinity$", + "gHashTag/ghashtag.github.io$", + "gHashTag/tri-net$", + "gHashTag/trinity-fpga$", + "gHashTag/trios$", + "gHashTag/999-multibots-telegraf$", + "gHashTag/tt-trinity-gamma$", + "gHashTag/tt-trinity-euler$", + "gHashTag/tt-trinity-phi$", + "gHashTag/turbobaby-user-bot$", + "gHashTag/tri-claw$", + "gHashTag/igla-coder-gpu$", + "gHashTag/tt-trinity-corona$", + "gHashTag/tern-tc-9m-verify$", + "gHashTag/tern-tc-9m-t27-verify$" +]; +/// .t27 files per consumer. +pub const CONSUMER_SPECS : [16]u32 = [ 2604, 3458, 3254, 113, 72, 71, 51, 49, 49, 49, 46, 13, 12, 6, 1, 1 ]; +/// Seal files per consumer. +pub const CONSUMER_SEALS : [16]u32 = [ 2514, 2, 0, 1, 0, 0, 0, 29, 29, 24, 0, 0, 0, 1, 0, 0 ]; +/// Role per consumer. +pub const CONSUMER_ROLE : [16]u8 = [ 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]; + +/// Length of a '$'-terminated literal. +pub fn lit_len(lit: []const u8) -> usize { + var k : usize = 0; + while (lit[k] != LIST_END) { k += 1; } + return k; +} + +/// Does buf[s..t] contain the '$'-terminated literal `lit`? +pub fn holds(buf: []const u8, s: usize, t: usize, lit: []const u8) -> bool { + var i : usize = s; + while (i < t) { + var k : usize = 0; + while (lit[k] != LIST_END and i + k < t and buf[i + k] == lit[k]) { k += 1; } + if (lit[k] == LIST_END) { return true; } + i += 1; + } + return false; +} + +/// Is flag f set in fl? +pub fn flag_on(fl: u8, f: u8) -> bool { + return (fl / f) % 2 == 1; +} + +/// Read one spec's `t27c seal --verify` output buf[f..t] into flags. The seal is recorded when a +/// spec_hash line names a saved hash; it is not when the output says no seal exists. The exporter +/// part is recorded when all four gen hash lines name a saved hash, and the same when all four match. +pub fn seal_flags(buf: []const u8, f: usize, t: usize) -> u8 { + var read : bool = false; + var none : bool = false; + var recorded : bool = false; + var spec_same : bool = false; + var vacuous : bool = false; + var gens : u32 = 0; + var gen_same : u32 = 0; + var gen_missing : u32 = 0; + var s : usize = f; + while (s < t) { + const e : usize = line_end(buf, s, t); + const x : usize = text_end(buf, s, e); + if (holds(buf, s, x, NO_SEAL)) { + read = true; + none = true; + } + if (has_prefix(SPEC_LINE, buf, s, x)) { + read = true; + if (holds(buf, s, x, SAVED_MISSING) == false) { recorded = true; } + if (has_prefix(SPEC_SAME_LINE, buf, s, x)) { spec_same = true; } + } + if (has_prefix(GEN_LINE, buf, s, x)) { + gens += 1; + if (holds(buf, s, x, SAME_WORD)) { gen_same += 1; } + if (holds(buf, s, x, SAVED_MISSING)) { gen_missing += 1; } + } + if (holds(buf, s, x, VACUOUS_LINE)) { vacuous = true; } + s = e + 1; + } + var fl : u8 = 0; + if (read) { fl += F_READ; } + if (recorded and none == false) { fl += F_RECORDED; } + if (spec_same and none == false) { fl += F_SPEC_SAME; } + if (gens == GEN_HASHES and gen_missing == 0 and none == false) { fl += F_EXP_RECORDED; } + if (gens == GEN_HASHES and gen_same == GEN_HASHES and none == false) { fl += F_EXP_SAME; } + if (vacuous) { fl += F_VACUOUS; } + return fl; +} + +/// The hot reuse decision for the flags: specs/t27f/hot.t27, spec part first, then the exporter. +pub fn flags_decision(fl: u8) -> u8 { + return hot_decision(flag_on(fl, F_RECORDED), flag_on(fl, F_SPEC_SAME), flag_on(fl, F_EXP_RECORDED), flag_on(fl, F_EXP_SAME)); +} + +/// The row's state. Unread output is unreadable; a vacuous seal is never sealed; otherwise the +/// decision names it. +pub fn flags_verdict(fl: u8) -> u8 { + if (flag_on(fl, F_READ) == false) { return V_UNREADABLE; } + if (flag_on(fl, F_VACUOUS)) { return V_VACUOUS; } + const d : u8 = flags_decision(fl); + if (d == HOT_REUSE) { return V_SEALED; } + if (d == HOT_REBUILD_SPEC) { return V_STALE; } + if (d == HOT_REBUILD_EXPORTER) { return V_TOOLCHAIN; } + return V_UNSEALED; +} + +/// The conclusion one row state asks for. A moved toolchain and a missing seal are neutral: the spec +/// is not shown wrong, and it is not shown sealed. Anything else, known or not, is failure. +pub fn verdict_conclusion(v: u8) -> u8 { + if (v == V_SEALED) { return CONCL_SUCCESS; } + if (v == V_TOOLCHAIN or v == V_UNSEALED) { return CONCL_NEUTRAL; } + return CONCL_FAILURE; +} + +/// The conclusion a role posts: a reporting repository's failure is neutral. +pub fn role_conclusion(role: u8, c: u8) -> u8 { + if (role == ROLE_REPORT and c == CONCL_FAILURE) { return CONCL_NEUTRAL; } + return c; +} + +/// Index of the consumer named buf[f..t] (case-insensitive), or CONSUMERS when it is not listed. +pub fn consumer_index(buf: []const u8, f: usize, t: usize) -> usize { + var i : usize = 0; + while (i < CONSUMERS) { + var k : usize = 0; + while (CONSUMER_NAMES[i][k] != LIST_END and f + k < t and lower(buf[f + k]) == lower(CONSUMER_NAMES[i][k])) { k += 1; } + if (CONSUMER_NAMES[i][k] == LIST_END and f + k == t) { return i; } + i += 1; + } + return CONSUMERS; +} + +/// The role of the repository named buf[f..t]; an unlisted repository is checked. +pub fn consumer_role(buf: []const u8, f: usize, t: usize) -> u8 { + const i : usize = consumer_index(buf, f, t); + if (i >= CONSUMERS) { return ROLE_CHECK; } + return CONSUMER_ROLE[i]; +} + +/// Is buf[f..t] a 64-character hex digest (hot.t27 digest_ok)? +pub fn digest_valid(buf: []const u8, f: usize, t: usize) -> bool { + var len : u32 = 0; + var i : usize = f; + while (i < t) { + const c : u8 = lower(buf[i]); + if ((c >= '0' and c <= '9') == false and (c >= 'a' and c <= 'f') == false) { return false; } + len += 1; + i += 1; + } + return digest_ok(len); +} + +/// Start of the next record line at or after s, or t. +pub fn next_record(buf: []const u8, s: usize, t: usize) -> usize { + var p : usize = s; + while (p < t) { + const e : usize = line_end(buf, p, t); + if (has_prefix(RECORD_LINE, buf, p, text_end(buf, p, e))) { return p; } + p = e + 1; + } + return t; +} + +/// The row state of the record that starts at r, whose block ends at b: a record line with fewer than +/// three tabs or a bad digest is unreadable, whatever its output says. +pub fn record_verdict(buf: []const u8, r: usize, b: usize) -> u8 { + const e : usize = line_end(buf, r, b); + const x : usize = text_end(buf, r, e); + const p1 : usize = first_tab(buf, r, x); + const p2 : usize = first_tab(buf, p1 + 1, x); + if (p2 >= x) { return V_UNREADABLE; } + const p3 : usize = first_tab(buf, p2 + 1, x); + if (p3 >= x or p2 == p1 + 1) { return V_UNREADABLE; } + if (digest_valid(buf, p2 + 1, p3) == false) { return V_UNREADABLE; } + return flags_verdict(seal_flags(buf, e + 1, b)); +} + +/// Start of the repo line, or n when there is none. +pub fn repo_at(buf: []const u8, n: usize) -> usize { + var s : usize = 0; + while (s < n) { + const e : usize = line_end(buf, s, n); + if (has_prefix(REPO_LINE, buf, s, text_end(buf, s, e))) { return s; } + s = e + 1; + } + return n; +} + +/// The role named by the input's repo line. +pub fn input_role(buf: []const u8, n: usize) -> u8 { + const s : usize = repo_at(buf, n); + if (s >= n) { return ROLE_CHECK; } + const e : usize = line_end(buf, s, n); + return consumer_role(buf, s + 5, text_end(buf, s, e)); +} + +/// How many records the input carries. +pub fn record_count(buf: []const u8, n: usize) -> u32 { + var c : u32 = 0; + var r : usize = next_record(buf, 0, n); + while (r < n) { + c += 1; + r = next_record(buf, line_end(buf, r, n) + 1, n); + } + return c; +} + +/// Is the input readable at all: a repo line, and no MARK_UNREADABLE line? +pub fn input_readable(buf: []const u8, n: usize) -> bool { + if (repo_at(buf, n) >= n) { return false; } + if (marker_at(buf, n, 0, MARK_UNREADABLE) < n) { return false; } + return true; +} + +/// The conclusion of the whole input: failure when it cannot be read, neutral when it has no record, +/// otherwise the worst row, then the repository's role. +pub fn t27f_conclusion(buf: []const u8, n: usize) -> u8 { + if (input_readable(buf, n) == false) { return CONCL_FAILURE; } + var worst : u8 = CONCL_NEUTRAL; + var r : usize = next_record(buf, 0, n); + if (r < n) { worst = CONCL_SUCCESS; } + while (r < n) { + const b : usize = next_record(buf, line_end(buf, r, n) + 1, n); + const c : u8 = verdict_conclusion(record_verdict(buf, r, b)); + if (c > worst) { worst = c; } + r = b; + } + return role_conclusion(input_role(buf, n), worst); +} + +/// Write v in decimal. +pub fn put_decimal(out: []u8, w: usize, cap: usize, v: u32) -> usize { + var d : u32 = 1; + while (v / d >= 10) { d = d * 10; } + var p : usize = w; + while (d > 0) { + p = put(out, p, cap, DIGITS[(v / d) % 10]); + d = d / 10; + } + return p; +} + +/// Write buf[f..t] as a query value: URL_SAFE bytes, letters and digits as they are, the rest %XX. +pub fn put_query(out: []u8, w: usize, cap: usize, buf: []const u8, f: usize, t: usize) -> usize { + var p : usize = w; + var i : usize = f; + while (i < t) { + const c : u8 = buf[i]; + const plain : bool = (c >= 'a' and c <= 'z') or (c >= 'A' and c <= 'Z') or (c >= '0' and c <= '9') or is_letter_of(URL_SAFE, c); + if (plain) { + p = put(out, p, cap, c); + } else { + p = put(out, p, cap, '%'); + p = put(out, p, cap, HEX[c / 16]); + p = put(out, p, cap, HEX[c % 16]); + } + i += 1; + } + return p; +} + +/// Write one table row for the record at r whose block ends at b. +pub fn put_row(out: []u8, w: usize, cap: usize, buf: []const u8, r: usize, b: usize) -> usize { + const e : usize = line_end(buf, r, b); + const x : usize = text_end(buf, r, e); + const p1 : usize = first_tab(buf, r, x); + const p2 : usize = first_tab(buf, p1 + 1, x); + const p3 : usize = first_tab(buf, p2 + 1, x); + const v : u8 = record_verdict(buf, r, b); + var p : usize = put_msg(out, w, cap, "| `$"); + p = put_range(out, p, cap, buf, p1 + 1, p2); + p = put_msg(out, p, cap, "` | `$"); + var shown : usize = p3; + if (p3 < x and p3 > p2 + 1 + DIGEST_SHOWN) { shown = p2 + 1 + DIGEST_SHOWN; } + if (p2 < x) { p = put_range(out, p, cap, buf, p2 + 1, shown); } + p = put_msg(out, p, cap, "` | - | - | $"); + p = put_msg(out, p, cap, VERDICT_NAMES[v]); + p = put_msg(out, p, cap, " | $"); + if (v == V_UNREADABLE) { + p = put(out, p, cap, '-'); + } else { + p = put_msg(out, p, cap, DECISION_LABELS[flags_decision(seal_flags(buf, e + 1, b))]); + } + p = put_msg(out, p, cap, " | $"); + if (p3 < x) { + p = put_msg(out, p, cap, "[view]($"); + p = put_msg(out, p, cap, GRAPH_BASE); + p = put_query(out, p, cap, buf, p3 + 1, x); + p = put(out, p, cap, ')'); + } else { + p = put(out, p, cap, '-'); + } + p = put_msg(out, p, cap, " |$"); + return put(out, p, cap, '\n'); +} + +/// The short text posted when the full report does not fit in out[0..cap]. +pub fn put_overflow(out: []u8, cap: usize) -> usize { + var p : usize = put_msg(out, 0, cap, CHECK_NAME); + p = put(out, p, cap, '\n'); + p = put_msg(out, p, cap, CONCL_NAMES[CONCL_FAILURE]); + p = put(out, p, cap, '\n'); + p = put_msg(out, p, cap, TITLE_OVERFLOW); + p = put(out, p, cap, '\n'); + if (p > cap) { return cap; } + return p; +} + +/// The check-run text for the input buf[0..n], written to out[0..cap]: the check name, the +/// conclusion, the title, then the summary in Markdown, one per line. Returns the bytes written; +/// a report that does not fit is replaced by the overflow text, which is failure. +pub fn t27f_report(buf: []const u8, n: usize, out: []u8, cap: usize) -> usize { + const concl : u8 = t27f_conclusion(buf, n); + var p : usize = put_msg(out, 0, cap, CHECK_NAME); + p = put(out, p, cap, '\n'); + p = put_msg(out, p, cap, CONCL_NAMES[concl]); + p = put(out, p, cap, '\n'); + if (input_readable(buf, n) == false) { + p = put_msg(out, p, cap, TITLE_UNREADABLE); + p = put(out, p, cap, '\n'); + if (p > cap) { return put_overflow(out, cap); } + return p; + } + const total : u32 = record_count(buf, n); + if (total == 0) { + p = put_msg(out, p, cap, TITLE_EMPTY); + p = put(out, p, cap, '\n'); + if (p > cap) { return put_overflow(out, cap); } + return p; + } + var sealed : u32 = 0; + var neutral : u32 = 0; + var failing : u32 = 0; + var r : usize = next_record(buf, 0, n); + while (r < n) { + const b : usize = next_record(buf, line_end(buf, r, n) + 1, n); + const c : u8 = verdict_conclusion(record_verdict(buf, r, b)); + if (c == CONCL_SUCCESS) { sealed += 1; } + if (c == CONCL_NEUTRAL) { neutral += 1; } + if (c == CONCL_FAILURE) { failing += 1; } + r = b; + } + p = put_msg(out, p, cap, TITLE_HEAD); + p = put_decimal(out, p, cap, total); + p = put_msg(out, p, cap, TITLE_SPECS); + p = put_decimal(out, p, cap, sealed); + p = put_msg(out, p, cap, TITLE_SEALED); + p = put_decimal(out, p, cap, neutral); + p = put_msg(out, p, cap, TITLE_NEUTRAL); + p = put_decimal(out, p, cap, failing); + p = put_msg(out, p, cap, TITLE_FAILING); + p = put(out, p, cap, '\n'); + p = put_msg(out, p, cap, SUMMARY_HEAD); + p = put(out, p, cap, '\n'); + p = put(out, p, cap, '\n'); + if (input_role(buf, n) == ROLE_REPORT) { + p = put_msg(out, p, cap, REPORTING_NOTE); + p = put(out, p, cap, '\n'); + p = put(out, p, cap, '\n'); + } + p = put_msg(out, p, cap, TABLE_HEAD); + p = put(out, p, cap, '\n'); + p = put_msg(out, p, cap, TABLE_RULE); + p = put(out, p, cap, '\n'); + r = next_record(buf, 0, n); + while (r < n) { + const b : usize = next_record(buf, line_end(buf, r, n) + 1, n); + p = put_row(out, p, cap, buf, r, b); + r = b; + } + p = put(out, p, cap, '\n'); + p = put_msg(out, p, cap, SUMMARY_FOOT); + p = put(out, p, cap, '\n'); + if (p > cap) { return put_overflow(out, cap); } + return p; +} + +/// Is buf[f..t] a .t27 path this check can read: not quoted, a name before the extension, and the +/// extension SPEC_EXT in any case? +pub fn scope_spec(buf: []const u8, f: usize, t: usize) -> bool { + const k : usize = lit_len(SPEC_EXT); + if (t <= f + k) { return false; } + if (buf[f] == '"') { return false; } + var i : usize = 0; + while (i < k) { + if (lower(buf[t - k + i]) != SPEC_EXT[i]) { return false; } + i += 1; + } + return buf[t - k - 1] != '/'; +} + +/// The scope, built in set[0..scap]: the changed .t27 paths of buf[0..n] (the lines before +/// MARK_GRAPH), then every importer of one of them (the edge lines after it, "importerimported"). +/// One hop only: an importer's importer is not added. Returns the set's length, or scap + 1 when the +/// marker is missing or the set does not fit -- the workflow then fails the check (fail closed). +pub fn t27f_scope(buf: []const u8, n: usize, set: []u8, scap: usize) -> usize { + const g : usize = marker_at(buf, n, 0, MARK_GRAPH); + if (g >= n) { return scap + 1; } + var sn : usize = 0; + var s : usize = 0; + while (s < g and sn <= scap) { + const e : usize = line_end(buf, s, g); + const t : usize = text_end(buf, s, e); + if (scope_spec(buf, s, t)) { sn = set_add(set, sn, scap, buf, s, t); } + s = e + 1; + } + const seeds : usize = sn; + s = line_end(buf, g, n) + 1; + while (s < n and sn <= scap) { + const e : usize = line_end(buf, s, n); + const t : usize = text_end(buf, s, e); + const p : usize = first_tab(buf, s, t); + if (p < t and p > s and seeds <= scap) { + if (in_set(set, seeds, buf, p + 1, t) and scope_spec(buf, s, p)) { sn = set_add(set, sn, scap, buf, s, p); } + } + s = e + 1; + } + return sn; +} + +/// The census totals over CONSUMER_SPECS and CONSUMER_SEALS. +pub fn census_specs() -> u32 { + var c : u32 = 0; + var i : usize = 0; + while (i < CONSUMERS) { + c += CONSUMER_SPECS[i]; + i += 1; + } + return c; +} + +pub fn census_seals() -> u32 { + var c : u32 = 0; + var i : usize = 0; + while (i < CONSUMERS) { + c += CONSUMER_SEALS[i]; + i += 1; + } + return c; +} + +// Test inputs end with '$' and are measured with lit_len, so no length below is counted by hand. + +test the_census_is_the_measured_one { + assert census_specs() == 9849; + assert census_seals() == 2600; + assert CONSUMER_SPECS[0] == 2604; + assert CONSUMER_SPECS[1] == 3458; + assert CONSUMER_SEALS[7] == 29; +} + +test only_the_published_copy_reports { + var reporting : u32 = 0; + var i : usize = 0; + while (i < CONSUMERS) { + if (CONSUMER_ROLE[i] == ROLE_REPORT) { reporting += 1; } + i += 1; + } + assert reporting == 1; + const pub_copy : str = "gHashTag/ghashtag.github.io$"; + assert consumer_role(pub_copy, 0, lit_len(pub_copy)) == ROLE_REPORT; + const site : str = "gHashTag/trinity$"; + assert consumer_role(site, 0, lit_len(site)) == ROLE_CHECK; +} + +test every_consumer_is_found_by_its_own_name { + var i : usize = 0; + while (i < CONSUMERS) { + assert consumer_index(CONSUMER_NAMES[i], 0, lit_len(CONSUMER_NAMES[i])) == i; + i += 1; + } +} + +test negative_control_an_unlisted_or_truncated_name_is_checked_not_reporting { + const other : str = "someone/else$"; + assert consumer_index(other, 0, lit_len(other)) == CONSUMERS; + assert consumer_role(other, 0, lit_len(other)) == ROLE_CHECK; + const cut : str = "gHashTag/ghashtag.github$"; + assert consumer_index(cut, 0, lit_len(cut)) == CONSUMERS; + const longer : str = "gHashTag/ghashtag.github.io2$"; + assert consumer_index(longer, 0, lit_len(longer)) == CONSUMERS; + const upper : str = "GHASHTAG/GHASHTAG.GITHUB.IO$"; + assert consumer_role(upper, 0, lit_len(upper)) == ROLE_REPORT; +} + +test a_matching_seal_is_sealed_and_succeeds { + const out : str = "spec_hash: MATCH\ngen_hash_zig: MATCH\ngen_hash_verilog: MATCH\ngen_hash_c: MATCH\ngen_hash_rust: MATCH\n\nall hashes MATCH\n$"; + const fl : u8 = seal_flags(out, 0, lit_len(out)); + assert flag_on(fl, F_READ) == true; + assert flag_on(fl, F_RECORDED) == true; + assert flag_on(fl, F_SPEC_SAME) == true; + assert flag_on(fl, F_EXP_RECORDED) == true; + assert flag_on(fl, F_EXP_SAME) == true; + assert flags_decision(fl) == HOT_REUSE; + assert flags_verdict(fl) == V_SEALED; + assert verdict_conclusion(V_SEALED) == CONCL_SUCCESS; +} + +test negative_control_a_missing_seal_is_unsealed_never_success { + const out : str = "Error: No saved seal found at .trinity/seals/A.json. Run with --save first.\n$"; + const fl : u8 = seal_flags(out, 0, lit_len(out)); + assert flag_on(fl, F_READ) == true; + assert flag_on(fl, F_RECORDED) == false; + assert flags_decision(fl) == HOT_MISSING; + assert flags_verdict(fl) == V_UNSEALED; + assert verdict_conclusion(flags_verdict(fl)) == CONCL_NEUTRAL; +} + +test a_seal_saved_for_other_bytes_is_stale_and_fails { + const out : str = "spec_hash: MISMATCH (saved=sha256:aa, current=sha256:bb)\ngen_hash_zig: MATCH\ngen_hash_verilog: MATCH\ngen_hash_c: MATCH\ngen_hash_rust: MATCH\n\nVERIFICATION FAILED\n$"; + const fl : u8 = seal_flags(out, 0, lit_len(out)); + assert flag_on(fl, F_RECORDED) == true; + assert flag_on(fl, F_SPEC_SAME) == false; + assert flags_decision(fl) == HOT_REBUILD_SPEC; + assert flags_verdict(fl) == V_STALE; + assert verdict_conclusion(V_STALE) == CONCL_FAILURE; +} + +test a_moved_toolchain_is_neutral { + const out : str = "spec_hash: MATCH\ngen_hash_zig: MISMATCH (saved=sha256:aa, current=sha256:bb)\ngen_hash_verilog: MATCH\ngen_hash_c: MATCH\ngen_hash_rust: MATCH\n$"; + const fl : u8 = seal_flags(out, 0, lit_len(out)); + assert flag_on(fl, F_EXP_RECORDED) == true; + assert flag_on(fl, F_EXP_SAME) == false; + assert flags_decision(fl) == HOT_REBUILD_EXPORTER; + assert flags_verdict(fl) == V_TOOLCHAIN; + assert verdict_conclusion(V_TOOLCHAIN) == CONCL_NEUTRAL; +} + +test a_seal_without_gen_hashes_is_missing_not_sealed { + const out : str = "spec_hash: MATCH\ngen_hash_zig: MISMATCH (saved=missing, current=sha256:bb)\ngen_hash_verilog: MATCH\ngen_hash_c: MATCH\ngen_hash_rust: MATCH\n$"; + const fl : u8 = seal_flags(out, 0, lit_len(out)); + assert flag_on(fl, F_EXP_RECORDED) == false; + assert flags_decision(fl) == HOT_MISSING; + assert flags_verdict(fl) == V_UNSEALED; + const three : str = "spec_hash: MATCH\ngen_hash_zig: MATCH\ngen_hash_c: MATCH\ngen_hash_rust: MATCH\n$"; + assert flags_verdict(seal_flags(three, 0, lit_len(three))) == V_UNSEALED; +} + +test negative_control_a_vacuous_seal_fails_even_when_it_matches { + const out : str = "spec_hash: MATCH\ngen_hash_zig: MATCH\ngen_hash_verilog: MATCH\ngen_hash_c: MATCH\ngen_hash_rust: MATCH\n\nVERIFICATION FAILED -- the saved seal is vacuous: every gen_hash is none\n$"; + const fl : u8 = seal_flags(out, 0, lit_len(out)); + assert flags_decision(fl) == HOT_REUSE; + assert flags_verdict(fl) == V_VACUOUS; + assert verdict_conclusion(V_VACUOUS) == CONCL_FAILURE; +} + +test negative_control_output_with_no_known_line_is_unreadable { + const out : str = "Error: parse error at line 3\n$"; + const fl : u8 = seal_flags(out, 0, lit_len(out)); + assert flag_on(fl, F_READ) == false; + assert flags_verdict(fl) == V_UNREADABLE; + assert verdict_conclusion(V_UNREADABLE) == CONCL_FAILURE; + assert flags_verdict(seal_flags(out, 0, 0)) == V_UNREADABLE; + assert verdict_conclusion(200) == CONCL_FAILURE; +} + +test the_reporting_role_softens_only_failure { + assert role_conclusion(ROLE_REPORT, CONCL_FAILURE) == CONCL_NEUTRAL; + assert role_conclusion(ROLE_REPORT, CONCL_SUCCESS) == CONCL_SUCCESS; + assert role_conclusion(ROLE_CHECK, CONCL_FAILURE) == CONCL_FAILURE; + assert role_conclusion(ROLE_CHECK, CONCL_NEUTRAL) == CONCL_NEUTRAL; +} + +test a_digest_is_sixty_four_hex_characters { + const good : str = "0123456789abcdef0123456789ABCDEF0123456789abcdef0123456789abcdef$"; + assert digest_valid(good, 0, lit_len(good)) == true; + const brief : str = "0123456789abcdef$"; + assert digest_valid(brief, 0, lit_len(brief)) == false; + const bad : str = "0123456789abcdeg0123456789abcdef0123456789abcdef0123456789abcdef$"; + assert digest_valid(bad, 0, lit_len(bad)) == false; +} + +test the_worst_row_decides_the_check { + const inp : str = "repo\tgHashTag/tt-trinity-phi\n@\tspecs/a.t27\t0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\thttps://raw.githubusercontent.com/gHashTag/tt-trinity-phi/abc/specs/a.t27\nspec_hash: MATCH\ngen_hash_zig: MATCH\ngen_hash_verilog: MATCH\ngen_hash_c: MATCH\ngen_hash_rust: MATCH\n@\tspecs/b.t27\t0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\thttps://raw.githubusercontent.com/gHashTag/tt-trinity-phi/abc/specs/b.t27\nError: No saved seal found at .trinity/seals/B.json. Run with --save first.\n$"; + const n : usize = lit_len(inp); + assert record_count(inp, n) == 2; + assert t27f_conclusion(inp, n) == CONCL_NEUTRAL; + const ok : str = "repo\tgHashTag/tt-trinity-phi\n@\tspecs/a.t27\t0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\thttps://raw.githubusercontent.com/gHashTag/tt-trinity-phi/abc/specs/a.t27\nspec_hash: MATCH\ngen_hash_zig: MATCH\ngen_hash_verilog: MATCH\ngen_hash_c: MATCH\ngen_hash_rust: MATCH\n$"; + assert t27f_conclusion(ok, lit_len(ok)) == CONCL_SUCCESS; + const stale : str = "repo\tgHashTag/tt-trinity-phi\n@\tspecs/a.t27\t0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\thttps://raw.githubusercontent.com/gHashTag/tt-trinity-phi/abc/specs/a.t27\nspec_hash: MISMATCH (saved=sha256:aa, current=sha256:bb)\n$"; + assert t27f_conclusion(stale, lit_len(stale)) == CONCL_FAILURE; +} + +test negative_control_unreadable_input_fails_the_check { + const no_repo : str = "@\tspecs/a.t27\t0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\turl\nspec_hash: MATCH\n$"; + assert t27f_conclusion(no_repo, lit_len(no_repo)) == CONCL_FAILURE; + const marked : str = "repo\tgHashTag/trios\n--unreadable\n$"; + assert t27f_conclusion(marked, lit_len(marked)) == CONCL_FAILURE; + const bad_digest : str = "repo\tgHashTag/trios\n@\tspecs/a.t27\tnot-a-digest\turl\nspec_hash: MATCH\ngen_hash_zig: MATCH\ngen_hash_verilog: MATCH\ngen_hash_c: MATCH\ngen_hash_rust: MATCH\n$"; + assert t27f_conclusion(bad_digest, lit_len(bad_digest)) == CONCL_FAILURE; + const few_tabs : str = "repo\tgHashTag/trios\n@\tspecs/a.t27\nspec_hash: MATCH\n$"; + assert t27f_conclusion(few_tabs, lit_len(few_tabs)) == CONCL_FAILURE; + const empty : str = "repo\tgHashTag/trios\n$"; + assert t27f_conclusion(empty, lit_len(empty)) == CONCL_NEUTRAL; +} + +test the_published_copy_posts_a_failure_as_neutral { + const stale : str = "repo\tgHashTag/ghashtag.github.io\n@\tt27/a.t27\t0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\turl\nspec_hash: MISMATCH (saved=sha256:aa, current=sha256:bb)\n$"; + assert t27f_conclusion(stale, lit_len(stale)) == CONCL_NEUTRAL; + const marked : str = "--unreadable\nrepo\tgHashTag/ghashtag.github.io\n$"; + assert t27f_conclusion(marked, lit_len(marked)) == CONCL_FAILURE; +} + +test the_report_names_the_check_and_carries_the_row_vocabulary { + var out : [4096]u8 = undefined; + const inp : str = "repo\tgHashTag/tt-trinity-phi\n@\tspecs/a b.t27\t0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\thttps://raw.githubusercontent.com/gHashTag/tt-trinity-phi/abc/specs/a b.t27\nspec_hash: MATCH\ngen_hash_zig: MATCH\ngen_hash_verilog: MATCH\ngen_hash_c: MATCH\ngen_hash_rust: MATCH\n$"; + const w : usize = t27f_report(inp, lit_len(inp), &out, 4096); + assert w < 4096; + assert holds(&out, 0, w, "claw/t27f\nsuccess\nT27F seals: 1 spec(s), 1 sealed, 0 neutral, 0 failing\n$") == true; + assert holds(&out, 0, w, "| path | digest | nodes | edges | verdict | decision | graph |$") == true; + assert holds(&out, 0, w, "| `specs/a b.t27` | `0123456789ab` | - | - | sealed | reuse | [view](https://t27.ai/t27f/graph/?src=https://raw.githubusercontent.com/gHashTag/tt-trinity-phi/abc/specs/a%20b.t27) |$") == true; + assert holds(&out, 0, w, "reporting role$") == false; +} + +test the_report_of_an_unreadable_row_shows_no_decision { + var out : [4096]u8 = undefined; + const inp : str = "repo\tgHashTag/trios\n@\tspecs/a.t27\t0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\turl\nError: parse error\n$"; + const w : usize = t27f_report(inp, lit_len(inp), &out, 4096); + assert holds(&out, 0, w, "claw/t27f\nfailure\nT27F seals: 1 spec(s), 0 sealed, 0 neutral, 1 failing\n$") == true; + assert holds(&out, 0, w, "| unreadable | - |$") == true; +} + +test negative_control_a_report_that_does_not_fit_is_failure { + var out : [4096]u8 = undefined; + const inp : str = "repo\tgHashTag/trios\n@\tspecs/a.t27\t0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef\turl\nspec_hash: MATCH\ngen_hash_zig: MATCH\ngen_hash_verilog: MATCH\ngen_hash_c: MATCH\ngen_hash_rust: MATCH\n$"; + const w : usize = t27f_report(inp, lit_len(inp), &out, 200); + assert w <= 200; + assert holds(&out, 0, w, "claw/t27f\nfailure\nT27F seals: the report did not fit\n$") == true; + const u : usize = t27f_report("@\tx\n$", 4, &out, 4096); + assert holds(&out, 0, u, "claw/t27f\nfailure\nT27F seals: the input could not be read\n$") == true; +} + +test the_scope_is_the_changed_specs_and_their_direct_importers { + var set : [512]u8 = undefined; + const inp : str = "specs/c.t27\nREADME.md\n--graph\nspecs/b.t27\tspecs/c.t27\nspecs/a.t27\tspecs/b.t27\nspecs/x.t27\tspecs/y.t27\n$"; + const sn : usize = t27f_scope(inp, lit_len(inp), &set, 512); + assert sn <= 512; + assert in_set(&set, sn, "specs/c.t27", 0, 11) == true; + assert in_set(&set, sn, "specs/b.t27", 0, 11) == true; + assert in_set(&set, sn, "specs/a.t27", 0, 11) == false; + assert in_set(&set, sn, "specs/x.t27", 0, 11) == false; + assert in_set(&set, sn, "README.md", 0, 9) == false; + assert sn == 24; +} + +test negative_control_an_import_does_not_put_what_it_imports_in_scope { + var set : [512]u8 = undefined; + const inp : str = "specs/a.t27\n--graph\nspecs/a.t27\tspecs/b.t27\n$"; + const sn : usize = t27f_scope(inp, lit_len(inp), &set, 512); + assert in_set(&set, sn, "specs/b.t27", 0, 11) == false; + assert sn == 12; +} + +test negative_control_a_scope_without_its_marker_or_room_fails_closed { + var set : [512]u8 = undefined; + const no_mark : str = "specs/a.t27\n$"; + assert t27f_scope(no_mark, lit_len(no_mark), &set, 512) == 513; + const inp : str = "specs/c.t27\n--graph\nspecs/b.t27\tspecs/c.t27\n$"; + assert t27f_scope(inp, lit_len(inp), &set, 20) == 21; + assert t27f_scope(inp, lit_len(inp), &set, 24) == 24; + const quoted : str = "\"specs/a b.t27\"\nspecs/OK.T27\n--graph\n$"; + const sn : usize = t27f_scope(quoted, lit_len(quoted), &set, 512); + assert sn == 13; +} + +test numbers_and_query_values_are_written_exactly { + var out : [64]u8 = undefined; + var w : usize = put_decimal(&out, 0, 64, 0); + assert w == 1; + assert out[0] == '0'; + w = put_decimal(&out, 0, 64, 9849); + assert w == 4; + assert out[0] == '9'; + assert out[3] == '9'; + const q : str = "a b&c/d:e#$"; + w = put_query(&out, 0, 64, q, 0, lit_len(q)); + assert holds(&out, 0, w, "a%20b%26c/d:e%23$") == true; +} + +invariant the_conclusions_order_success_neutral_failure { + assert CONCL_SUCCESS < CONCL_NEUTRAL; + assert CONCL_NEUTRAL < CONCL_FAILURE; + assert V_SEALED == HOT_REUSE; + assert ROLE_CHECK != ROLE_REPORT; +} diff --git a/specs/claw/t27f_gate_pins.t27 b/specs/claw/t27f_gate_pins.t27 new file mode 100644 index 0000000000..710fd83a1f --- /dev/null +++ b/specs/claw/t27f_gate_pins.t27 @@ -0,0 +1,57 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/claw/t27f_gate_pins.t27 -- the pins for specs/claw/t27f_gate.t27 that need .len. +; t27f_gate.t27 is lowered by gen-c (gen/c/claw/t27f_gate.c), and gen-c lowers str to const char*: +; .len reaches C verbatim and == between two strings is a pointer compare there. So the gate measures +; its '$'-terminated literals with its own lit_len, and the facts that need the real string length or +; another module's strings are checked HERE, in a test-only module that is never passed to gen-c +; (the claw/gates_pins.t27 precedent). +; specs/claw/service.t27 is not imported: its has_prefix(s, p) and own_language's has_prefix(list, s, +; from, to) share a name, and the flat `use` splice refuses an ambiguous name. The service side of the +; link is service.t27's own test check_prefix_namespaced, which asserts "claw/t27f" carries CHECK_PREFIX. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module ClawT27fGatePins; + +use claw::t27f_gate; +use t27f::hot; + +/// Does a equal the '$'-terminated b without its '$'? +fn same_as_lit(a: []const u8, b: []const u8) -> bool { + if (b.len != a.len + 1) { return false; } + if (b[a.len] != '$') { return false; } + for i in 0..a.len { + if (a[i] != b[i]) { return false; } + } + return true; +} + +test the_decision_labels_are_hot_decision_names { + for i in 0..4 { + assert same_as_lit(hot::HOT_DECISION_NAMES[i], t27f_gate::DECISION_LABELS[i]) == true; + } +} + +test the_check_name_is_claw_t27f { + assert same_as_lit("claw/t27f", t27f_gate::CHECK_NAME) == true; + assert same_as_lit("claw/t27", t27f_gate::CHECK_NAME) == false; +} + +test lit_len_agrees_with_len_on_every_table { + for i in 0..t27f_gate::CONSUMERS { + assert t27f_gate::lit_len(t27f_gate::CONSUMER_NAMES[i]) + 1 == t27f_gate::CONSUMER_NAMES[i].len; + } + for i in 0..6 { + assert t27f_gate::lit_len(t27f_gate::VERDICT_NAMES[i]) + 1 == t27f_gate::VERDICT_NAMES[i].len; + } + for i in 0..3 { + assert t27f_gate::lit_len(t27f_gate::CONCL_NAMES[i]) + 1 == t27f_gate::CONCL_NAMES[i].len; + } + assert t27f_gate::lit_len(t27f_gate::CHECK_NAME) == 9; +} + +test negative_control_a_wrong_label_is_caught { + assert same_as_lit("reuse", "reuse$") == true; + assert same_as_lit("reuse", "reuses$") == false; + assert same_as_lit("reuse", "reuse") == false; + assert same_as_lit("reuse", "reusE$") == false; +} From ea0ea6e8c74681a9e0f167c528020f06632c93ee Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Sun, 18 Oct 2026 14:29:15 +0700 Subject: [PATCH 2/3] seals(claw): reseal gates/gates_pins/service and seal t27f_gate/t27f_gate_pins after #10250 (Refs #10268) Co-Authored-By: Claude Opus 5.5 --- .trinity/seals/claw_ClawGates.json | 14 +++---- .trinity/seals/claw_ClawGatesPins.json | 6 +-- .trinity/seals/claw_ClawService.json | 14 +++---- .trinity/seals/claw_ClawT27fGate.json | 45 +++++++++++++++++++++++ .trinity/seals/claw_ClawT27fGatePins.json | 37 +++++++++++++++++++ 5 files changed, 99 insertions(+), 17 deletions(-) create mode 100644 .trinity/seals/claw_ClawT27fGate.json create mode 100644 .trinity/seals/claw_ClawT27fGatePins.json diff --git a/.trinity/seals/claw_ClawGates.json b/.trinity/seals/claw_ClawGates.json index 6fda2eada1..0cb0fee79b 100644 --- a/.trinity/seals/claw_ClawGates.json +++ b/.trinity/seals/claw_ClawGates.json @@ -1,17 +1,17 @@ { - "built_by": "t27c-bootstrap@0.5.2+d2c8e7d41", + "built_by": "t27c-bootstrap@0.5.2+043cdea36", "closure": [], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:fab414aec1539c4212435e1ae3bdd41a070b4018a9a93f33b03e0ec4155b8379", - "gen_hash_rust": "sha256:e110e3736d4857882d3879cb7fbfad65a58d147e3b5264837edd8932fa184981", - "gen_hash_verilog": "sha256:c6716a19fcbbc555dc888aae7d5c7cb4b5af3ac90d7221f16eee449f2ba19017", - "gen_hash_zig": "sha256:7ca573a135276493df60266248f8fd1e4a890685064830c0e679e7dc02f58367", + "gen_hash_c": "sha256:97188898c4d8cda3d8bbb551de2a41cc67db142ee15f724bbfee30bb4cedd0c4", + "gen_hash_rust": "sha256:6192bfeb11dcb93a415eb8ef5f30a07d2588e58b0ea832f8ac99f4428a6829ee", + "gen_hash_verilog": "sha256:4ec80fa7604787bbb30bfee71cc6e838f4ac24e33348cbfbe46682182ff43b5e", + "gen_hash_zig": "sha256:3f6afb1b574d5a4346abf72b7a313ccae1316f56c04ef486c999fa2e160e4ee9", "module": "ClawGates", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-18T05:03:44Z", + "sealed_at": "2026-10-18T07:27:06Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:9b6229226a0de2129fc9ca6fb890bd0aa923fce0fd880ce6ca85b299ab9e57d1", + "spec_hash": "sha256:2b49b0ada4cf5323472026378a33a07831bcc750ccc3a5a7b649e115dbcb6ab6", "spec_path": "specs/claw/gates.t27", "tests": { "failed": 0, diff --git a/.trinity/seals/claw_ClawGatesPins.json b/.trinity/seals/claw_ClawGatesPins.json index ad4dd1b868..1e219126e4 100644 --- a/.trinity/seals/claw_ClawGatesPins.json +++ b/.trinity/seals/claw_ClawGatesPins.json @@ -1,9 +1,9 @@ { - "built_by": "t27c-bootstrap@0.5.2+d2c8e7d41", + "built_by": "t27c-bootstrap@0.5.2+043cdea36", "closure": [ { "spec": "specs/claw/gates.t27", - "spec_hash": "sha256:9b6229226a0de2129fc9ca6fb890bd0aa923fce0fd880ce6ca85b299ab9e57d1" + "spec_hash": "sha256:2b49b0ada4cf5323472026378a33a07831bcc750ccc3a5a7b649e115dbcb6ab6" } ], "config": "gen=c,rust,verilog,zig;tests=zig", @@ -14,7 +14,7 @@ "module": "ClawGatesPins", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-18T05:03:44Z", + "sealed_at": "2026-10-18T07:27:08Z", "sealed_by": "t27c-bootstrap@0.5.2", "spec_hash": "sha256:e18a7800770e75607f68b089aaa38880e490276fbe6706372b0e302bb990238c", "spec_path": "specs/claw/gates_pins.t27", diff --git a/.trinity/seals/claw_ClawService.json b/.trinity/seals/claw_ClawService.json index 6aeecaac4c..53889e1b18 100644 --- a/.trinity/seals/claw_ClawService.json +++ b/.trinity/seals/claw_ClawService.json @@ -1,17 +1,17 @@ { - "built_by": "t27c-bootstrap@0.5.2+d2c8e7d41", + "built_by": "t27c-bootstrap@0.5.2+043cdea36", "closure": [], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:a46b772287b86e54f09b8b161a7a413325bede4b00b811709d7fc99a90e40f98", - "gen_hash_rust": "sha256:39154b909f8cc996ddf4a955407e5c9598c74e8ecbf245d114687b347a7e6624", - "gen_hash_verilog": "sha256:d5ff17eed0fd5470487a76f45ad79f6c943182b234d54abade1cfaf505ce506c", - "gen_hash_zig": "sha256:ab22e01d9a92e7bce4b694c4f0c52c3d28a8b763f1b133da262a331705fd5abd", + "gen_hash_c": "sha256:de9f70b57e80b4aa791a51d8ea356605a0c1c99611c7d25017654742fb5a6a39", + "gen_hash_rust": "sha256:5c38af81d488436c4cb4db98ade0c82a4a63a4fff1b9a4cfa6a24e68f89e391e", + "gen_hash_verilog": "sha256:4c83cf81d17f778a8a07ee15277e7a4685b479851f036f8048f9e8f35a46dbdf", + "gen_hash_zig": "sha256:23cdedf78a2b4e9a324ad4653ea746379a3beacfea7ded12fd1d4cd4cbf91f5a", "module": "ClawService", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-18T05:03:45Z", + "sealed_at": "2026-10-18T07:27:09Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:ed78b892c6b5936d45ffad0fc4fa184b0616c757c057a611f7ca44f10b8257d0", + "spec_hash": "sha256:e704897a637b01492a2608aad832cb63461cbdf6f45222cd0c46e8728764381e", "spec_path": "specs/claw/service.t27", "tests": { "failed": 0, diff --git a/.trinity/seals/claw_ClawT27fGate.json b/.trinity/seals/claw_ClawT27fGate.json new file mode 100644 index 0000000000..2c27180ca4 --- /dev/null +++ b/.trinity/seals/claw_ClawT27fGate.json @@ -0,0 +1,45 @@ +{ + "built_by": "t27c-bootstrap@0.5.2+043cdea36", + "closure": [ + { + "spec": "specs/ci/affected.t27", + "spec_hash": "sha256:38a7bdc245f1974f0d5f1a55a45966decd70098afdb89e882f568da3650ee9f9" + }, + { + "spec": "specs/policy/own_language.t27", + "spec_hash": "sha256:bba616b5794a7c390b13de07a81f0b2a066f22d6620ae349e5d164dddc17429a" + }, + { + "spec": "specs/policy/text_lines.t27", + "spec_hash": "sha256:13c90895a4a44c760cce6b3b65f72f937298633062f040f8489486108ab0c2d1" + }, + { + "spec": "specs/t27f/hot.t27", + "spec_hash": "sha256:a2ea398b37bd4d85e279b69a7e3d12d7944a2f849a2d231db24973f5731d557b" + } + ], + "config": "gen=c,rust,verilog,zig;tests=zig", + "gen_hash_c": "sha256:75cfffcfd6a4ba47ac96aa1c0ebbc232f9cbb9c85003eb458014abffc2ee8350", + "gen_hash_rust": "sha256:79e0722227eb57bccb498fb165c69f59e0f38b0d93e0bfb5b803263289c56e33", + "gen_hash_verilog": "sha256:1d75c13c148bb32ac17685f94d1dba57ec103911c3c0074bdfd169bc10882a91", + "gen_hash_zig": "sha256:a5fcd394b3d9538a3fc58fc2dad169db8ca0a43d722ee12f65a8a79dc8e2d55d", + "module": "ClawT27fGate", + "ring": 12, + "seal_schema": 2, + "sealed_at": "2026-10-18T07:27:09Z", + "sealed_by": "t27c-bootstrap@0.5.2", + "spec_hash": "sha256:d971531b23c57834d5ffbfd200748db34f51a291f44b87716dcb02910f8e1993", + "spec_path": "specs/claw/t27f_gate.t27", + "tests": { + "failed": 0, + "failing": [], + "forced": false, + "passed": 23, + "total": 23, + "vacuous": 0 + }, + "toolchain": { + "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", + "zig": "0.16.0" + } +} \ No newline at end of file diff --git a/.trinity/seals/claw_ClawT27fGatePins.json b/.trinity/seals/claw_ClawT27fGatePins.json new file mode 100644 index 0000000000..3a40171719 --- /dev/null +++ b/.trinity/seals/claw_ClawT27fGatePins.json @@ -0,0 +1,37 @@ +{ + "built_by": "t27c-bootstrap@0.5.2+043cdea36", + "closure": [ + { + "spec": "specs/claw/t27f_gate.t27", + "spec_hash": "sha256:d971531b23c57834d5ffbfd200748db34f51a291f44b87716dcb02910f8e1993" + }, + { + "spec": "specs/t27f/hot.t27", + "spec_hash": "sha256:a2ea398b37bd4d85e279b69a7e3d12d7944a2f849a2d231db24973f5731d557b" + } + ], + "config": "gen=c,rust,verilog,zig;tests=zig", + "gen_hash_c": "sha256:e4d2bd19107fff5f42908395ebd42a855bc4821f85014db435196d0a360aff79", + "gen_hash_rust": "sha256:4dc02033cea4ae56d81cca417e99faf450bfd97ffcbd7fed702b56b30119ce25", + "gen_hash_verilog": "sha256:a5a99dff99da4b4ebd3c15c210a92a3cb487039215158b2bb3c89a83eab168df", + "gen_hash_zig": "sha256:50ca11596eb89c8ffac6476a97349bd9efd9ec5e64ef6ea5ed1bf42793aeb8c9", + "module": "ClawT27fGatePins", + "ring": 12, + "seal_schema": 2, + "sealed_at": "2026-10-18T07:27:10Z", + "sealed_by": "t27c-bootstrap@0.5.2", + "spec_hash": "sha256:8cc4c19346755daeec7f6ad0e004bbd5b496fdf105eef7c36b4db2bb976c6f34", + "spec_path": "specs/claw/t27f_gate_pins.t27", + "tests": { + "failed": 0, + "failing": [], + "forced": false, + "passed": 4, + "total": 4, + "vacuous": 0 + }, + "toolchain": { + "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", + "zig": "0.16.0" + } +} \ No newline at end of file From 3c0d82780ad5f3ccd4e3046068bffbf9bda127eb Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Sun, 18 Oct 2026 14:31:51 +0700 Subject: [PATCH 3/3] fix(claw): mint the claw/t27f App token scoped to checks:write, pull_requests:read, contents:read (Refs #10268) The App holds write on about 40 permissions across every gHashTag installation; an unscoped token minted from a t27 branch would carry secrets, workflows and administration write. The three scopes are the check-run, pr-files and spec-read rows of specs/claw/github_surface.t27 (#10280). Co-Authored-By: Claude Opus 5.5 --- .github/workflows/gate-topology.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/gate-topology.yml b/.github/workflows/gate-topology.yml index 7589b0b0a5..fadfebda09 100644 --- a/.github/workflows/gate-topology.yml +++ b/.github/workflows/gate-topology.yml @@ -87,7 +87,7 @@ jobs: - 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/t27f posted 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}} + - {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, permission-checks: write, permission-pull-requests: read, permission-contents: read}} - {if: steps.have.outputs.ok, uses: actions/checkout@v6} - {if: steps.have.outputs.ok, uses: sigstore/cosign-installer@v3} - if: steps.have.outputs.ok