diff --git a/.claude/skills/own-git-landing/SKILL.md b/.claude/skills/own-git-landing/SKILL.md new file mode 100644 index 0000000000..2add04db30 --- /dev/null +++ b/.claude/skills/own-git-landing/SKILL.md @@ -0,0 +1,68 @@ +--- +id: own-git-landing +name: Own git landing +description: How every agent lands work -- verify locally, land on the local golden head, push to GitHub once per batch. Use before any git push, PR, update-branch, or re-run of checks; when CI is queued; when the owner says "GitHub is our bottleneck", "наш git", "golden chain", "батч". +--- + +# Own git landing (owner, 2026-10-20, #11275) + + +> **Only t27: everything is written in `.t27`.** (Owner hard rule 2026-10-05; budget #7371; restated in every skill per #7588.) +> - New logic starts as a `.t27` spec with `test` / `invariant` blocks. It reaches Rust, C, Zig, JS or Python only through `t27c gen-*`, and generated files are never hand-edited (L2). +> - Hand-written code in any other language is denied by the hooks and by CI: `.rs .py .ts .js .sh .zig .c .go .v .yml .toml`, Dockerfile, Makefile; the full list is in `specs/policy/own_language.t27`. +> - Existing foreign code is debt that only shrinks. Delete it, or replace it with a spec and its generated output. +> - Thin hand-written glue is capped at 40 added lines per file and 80 per PR. No label lifts the cap. Never split a PR to slip under it. +> - What gen cannot express yet is a compiler defect. File it on #5980 with a minimal `.t27` repro instead of writing the code by hand. +> - This rule overrides any step below that says to write a script or code in another language. The full text is in `AGENTS.md`, "Only t27". + +## The law + +The rule is `specs/queen/golden_chain.t27`, section 6 (lane_ready, batch_from, when_to_push, lane_may, +gc_rerun_local, gc_bisect), over sections 1-5 (links, golden head by CAS, CARRY, gc_land). Do not +restate it; read it. Related: `specs/claw/push_gate.t27` (is a push worth its place in the queue), +`specs/claw/queue_cost.t27`, `specs/verified/reuse.t27` (verdict reuse), `specs/tri/land/land.t27`. +Why: a push starts every required check again at the back of an Actions queue of 800-900 runs (#10644, +#10588, #6595). Our git judges a change by its verdicts, locally. + +## Practices + +1. Verify locally first, in this order: `t27b test ` on each changed spec and on the specs that + import it; `t27c test-report ` (fresh `ZIG_GLOBAL_CACHE_DIR`, delete after; 0 failed, 0 + vacuous); `t27c seal --save`; regenerate with `tri gen`, never hand-edit `gen/`. +2. Record the link: SPEC, GEN, SEAL, TEST, RECEIPT. A stage whose input key was judged golden before is + REUSED, not re-run. +3. Land on the local golden head (`refs/golden/head`, GC_REF), only through gc_land (compare-and-swap on + the parent). Never write the ref by hand. +4. Push to GitHub once per batch: up to 8 ready links, every 15 minutes (30 while the Actions queue + holds 200 or more runs). One PR per batch, `Closes #N` for each issue in the commit messages (L1). +5. Merge master before pushing a shared file; derived paths (gen/, ledgers, figures) are regenerated on + the new head, never merge-resolved. +6. A red batch is bisected here by replaying prefixes (gc_bisect); do not re-run GitHub to find it. + +## Never + +- Push per commit; push a link that is not golden; run `update-branch` loops. +- Re-run checks after a derived-only rebase (CARRIED or REGEN re-run nothing). +- Force-push, or move the head except by gc_land. +- Count a red as inherited unless the PR's failure set is a subset of master's. + +## Traps + +- `--auto` merge races a second push: run every gate before the first push, or the auto-merge lands + commit 1 alone. +- Absolute `core.hooksPath` runs the main checkout's hooks in a worktree; use + `git -c core.hooksPath=.githooks commit`. +- "fix #N" in a PR table closes the issue; cite as "see #N". +- Backticks inside `--body "..."` run in zsh; use `--body-file - <<'EOF'`. + +## Commands, and what is missing + +- `tri land [branch] [--plan|--check]` exists (specs/tri/land/land.t27, #11068): the GitHub leg for ONE + branch (merge master, tests, seals, push, PR, auto-merge). Use it for the batch branch, once. +- `tri golden land [--plan]`, `tri golden push [--plan] [--queued N] --issue N`, `tri golden status` run the local leg + (specs/tri/land/golden_land.t27, a thin host over golden_chain.t27; hosted by `cmd_golden` in cli/tri/src/main.rs, #11275). + `land` reads the queue (local claude/evolve-* branches and `.trinity/golden/queue`), judges each link by `t27c seal --verify` + and `t27b test` in a scratch worktree of its tip, and lands the ready ones on `refs/golden/head` by `git update-ref` with the + old value (compare-and-swap); `--plan` writes no ref and prints the update-ref lines. `push` applies when_to_push to the links + after `refs/golden/pushed` and, when due, cuts `claude/golden-` at the batch's last link and runs `tri land` on it once. + A lane built on a head that has since moved is UNKNOWN_PARENT: `git merge refs/golden/head` into it first. diff --git a/.claude/skills/swarm-economy/SKILL.md b/.claude/skills/swarm-economy/SKILL.md new file mode 100644 index 0000000000..b3a419c6ac --- /dev/null +++ b/.claude/skills/swarm-economy/SKILL.md @@ -0,0 +1,56 @@ +--- +name: swarm-economy +description: Run T27 OS work cheaply when the weekly Claude limit is low -- free-model actors and bees do the work, Claude only lands, watches and unblocks; hourly pulse of bee load and token stock on the dashboard so nothing idles. Use when the owner says "economically", "few tokens left", "through our actors", or when a loop must watch that everyone works at full capacity. +--- + +# Swarm economy (owner, 2026-10-20) + +Owner: "fix everything through our actors, economically -- few tokens left on the weekly limit"; +"switch on a cron that watches that everyone works at maximum efficiency"; "show bee load and token +stock online on the dashboard so there is no idle time". Deadline set by the owner: Saturday 2026-10-24. + +## Who does what + +| Work | Who | Model / cost | +|---|---|---| +| Specs, ports, fixes, tests | bees, Queen lanes, T27 OS panes (OpenCode, Crush, Codex) | free tiers via the key master: glm-4.5-flash (7 keys), OpenRouter `:free` | +| Landing, reseal, merge, screenshots, answers | the Claude orchestrator | Claude, short turns | +| New Claude subagents | nobody, unless 2 free-tier attempts failed on the same task | -- | + +1. Remaining work becomes **4-heading issues** (Boundary / User Scenarios / Requirements / Success + Criteria) under the epics in AGENTS.md "Swarm focus". One issue = one lane = one letter. +2. The orchestrator never re-reads large files to answer a status question: the pulse answers it. +3. A Claude turn that only waits is a wasted turn: use notifications, never polling loops. + +## Pulse (no LLM) + +The collector (session scratchpad, plumbing only; every decision is specs/t27os/swarm_pulse.t27) collects, without any model call: + +- bees running / queued (GitHub Actions runs on gHashTag/t27), PRs opened and merged in 24 h; +- swarm issues waiting; +- token stock: count of Keychain items per provider under service `ai.t27.os` -- **presence only, the + script never reads a key value** (`security find-generic-password` without `-w`); +- agent processes on this Mac, free disk. + +It writes `measures.json`; the orchestrator uploads it to the Mission Control dashboard dataset +`measures` (artifact LvTojtwf5fatVpxwkSzunZ). The first rows are live, labelled `live HH:MM`. + +## Hourly watch (cron, minute 17) + +1. Run `pulse.sh` in a terminal tab; upload; update the dataset. +2. **Idle rule:** queued == 0 and swarm issues waiting > 0 -> capacity is idle: start one free-tier lane + on the oldest waiting issue (T27 OS pane through the key master), never a Claude subagent. +3. **Keys rule:** z.ai keys < 3 or OpenRouter == 0 -> request the owner for keys (exact value to + provide, once), meanwhile route all lanes to the remaining tier. +4. **Disk rule:** free disk < 8 GB -> start no build (cargo, t27b app, nextpnr); remove own merged + worktrees first (skill self-cleanup). +5. End the tick with one line: state, numbers, action taken, `Requests out:`. + +## Traps + +- A cron fire is itself a Claude turn: keep the tick to the collector + at most one action. +- `gh` search with `#` inside quotes can undercount; count swarm issues by epic number in the body. +- The sandbox blocks the Keychain and `gh` writes: run the collector in a terminal tab. + +Port target: this collector is plumbing; its rules (idle, keys, disk thresholds) belong in +`specs/t27os/evolve.t27` as tested fns -- see the swarm issues under gHashTag/t27#11275. diff --git a/.claude/skills/t27os/SKILL.md b/.claude/skills/t27os/SKILL.md new file mode 100644 index 0000000000..be69fbbbd6 --- /dev/null +++ b/.claude/skills/t27os/SKILL.md @@ -0,0 +1,215 @@ +--- +id: t27os +name: T27 OS +description: T27 OS is the spaceship instrument panel for working on t27 -- a native macOS window written in .t27 and built by t27b: the 27-petal home, the chat (petal 3), the home tabs t27a/t27b/t27c/t27f/t27o/t27t/t27u + Queen, one console per agent letter with its widgets as instruments, end-to-end analytics per agent domain, and the BENCH of the three FPGA boards. Use when the user says "T27 OS", "t27os", "приборная панель", "дома агентов", "лепесток", "консоль агента", "запусти локально", or asks to change, build, run or screenshot the T27 OS window. +--- + +# T27 OS + + +> **Only t27: everything is written in `.t27`.** (Owner hard rule 2026-10-05; budget #7371; restated in every skill per #7588.) +> - New logic starts as a `.t27` spec with `test` / `invariant` blocks. It reaches Rust, C, Zig, JS or Python only through `t27c gen-*`, and generated files are never hand-edited (L2). +> - Hand-written code in any other language is denied by the hooks and by CI: `.rs .py .ts .js .sh .zig .c .go .v .yml .toml`, Dockerfile, Makefile; the full list is in `specs/policy/own_language.t27`. +> - Existing foreign code is debt that only shrinks. Delete it, or replace it with a spec and its generated output. +> - Thin hand-written glue is capped at 40 added lines per file and 80 per PR. No label lifts the cap. Never split a PR to slip under it. +> - What gen cannot express yet is a compiler defect. File it on #5980 with a minimal `.t27` repro instead of writing the code by hand. +> - This rule overrides any step below that says to write a script or code in another language. The full text is in `AGENTS.md`, "Only t27". + +## The product (owner, 2026-10-20) + +An ergonomic project-management system made 100% of widgets -- our signature. Each letter is a +tool t27 with its own page (t27k kanban, t27c chat + commander, t27i issues, t27p PRs and +CI, t27q Queen, t27s specs, t27w widgets, ...; table in agent_parts.t27 TOOL_NAMES). The work loop: +i (issues) -> k (kanban) -> c (task an agent) -> p (PR, CI) -> v (verdict) -> n (now) -> e +(experience), and q (the Queen in GOD MODE) turns it on her own. A petal opens straight into that +tool's page, the agent's dashboard open; nothing on a page is drawn outside a widget. + +## Glass everywhere (owner rule, repeated 2026-10-20 -- do not get it wrong again) + +In a WINDOW every surface is matte transparent glass: sidebar, content, tiles, panels, hero, and +the embedded live widget page (WKWebView drawsBackground off + an injected +`html,body{background:transparent}` user script). In FULL SCREEN the background is opaque black +#000000. Use glass_window's surface_alpha (#11266) for every new surface; never hardcode an opaque +fill. Before claiming a surface done, screenshot it in both modes. + +## The image (owner, 2026-10-20) + +We sit in a spaceship; T27 OS is its **instrument panel** for working on the project. + +- **Everything is a widget, and widgets contain widgets.** Screen > home > hero gauges + agent consoles > instruments. One recursive layout packs children into the parent's rect. +- **Many widgets on screen**: 20-40 instruments in a 960 x 540 window, more in full screen. Never a text list, never a nine-cell honeycomb (both were rejected). +- **One console per agent letter** (27 agents, `specs/agents/*.t27`), titled by letter + domain (e.g. `C COMPILER CORE`); its instruments are the widgets, app tabs and pages assigned to that letter by domain. +- **Look**: the chat screen's sidebar and glass; headings like the website course (giant 800-weight title, segment bar, outlined big numbers, course map); cards like the widgets gallery (card.png, click = the widget live inside the window). +- **End-to-end analytics per domain**: what is happening in each agent's domain (commits 24 h / 7 d, last activity, 7-day pulse, lamp ACTIVE / WARM / QUIET / BLOCKED, default action), every fact labelled CONFIRMED or GUESS, an OPEN count of unowned files (idea from the REA + Opus 5.5 reverse-engineering posts). +- **For hardware engineers**: the BENCH (three AX7203 dice live), provenance of every number, "not measured" instead of 0, keyboard first, idle at no CPU. + +## Every widget carries its agents (owner, 2026-10-20, mandatory) + +No widget, tab or instrument without its pinned agents: the owner letter plus the letters that feed +it, each with a one-line reason, in `specs/t27os/agent_parts.t27`. A test refuses a widget with no +agent, and the widget factory (`specs/t27f/widget_factory.t27`) refuses a new widget without them. +On screen every instrument shows its agent badges (owner first, bold); the live widget view shows +an `AGENTS:` line. Purpose: make the system's hard-to-follow work transparent to a human. + +## Where the law lives (one home each, never restated) + +| What | Spec | +|---|---| +| Petals, names, hit test; CHAT is petal index 2 (Cmd+3) | `specs/t27f/petal_menu.t27` (`WORLD_NAMES` is the only copy) | +| Homes t27a..t27u + Queen, letters per home, widget runs, 7 courses x 27 lessons, home petals 19..25 | `specs/t27os/homes.t27` | +| Every gallery widget (195) and app surface (65: 21 HUD views, 10 TRI screens, 34 pages) -> one agent letter, console labels, letters with no instrument | `specs/t27os/agent_parts.t27` | +| What is happening per domain (git pulse, lamp, CONFIRMED/GUESS, OPEN) | `specs/t27os/domain_pulse.t27` (paths -> letters through `use agents::::KEY_FILES`) | +| Boards, provenance of every number, project summary strip | `specs/t27os/bench.t27` (+ `specs/verified/bench_boards.t27`) | +| The window: screens, sidebar helpers shared by chat and home tabs, scrolling, live widget pane | `specs/t27os/glass_window.t27` | +| T27F surfaces T27 OS can host (spec graph, outline, check, particles, dashboard) | `specs/t27f/frontend.t27` | + +## Build and run locally (verified 2026-10-20) + +1. `t27b` needs the extern boundary of #8946, not on master: a worktree from master with + `git cherry-pick --no-commit 865b4ee3f 6113f868b`, then `cargo build --release -p t27b`. +2. Build: `t27b build specs/t27os/pty_server.t27 -o pty_server.o --macho`, the same for + `glass_window.t27`; `nm -u pty_server.o` must list symbols (an empty list = trapping stubs). +3. Link the window with `-lobjc` and the frameworks Foundation, AppKit, CoreGraphics, CoreText, + CoreFoundation, QuartzCore, **Security** (keys) and **WebKit** (live widget pane); ad-hoc + `codesign`; a hand-made Info.plist (`ai.t27.os`). Details: `docs/t27os/run-local.md`. +4. Run from a normal Terminal tab (the Bash sandbox blocks `open`, `kill` and the socket): + `PATH=$HOME/.local/bin:$PATH /t27os-pty &`, then `open /T27OS.app`. +5. Look before you claim: screenshot the window (computer-use `app_screenshot` on `ai.t27.os`). + The home logo is drawn at about 0.705 pt per box unit: petal p centre = + `(480 + (cx - 298) * 0.705, 76 + cy * 0.705)` with cx, cy the vertex mean of `QX/QY` in units; + take two screenshots after a tap, the first one is often mid cross-fade. + +## Traps measured + +- Two sessions edit `glass_window.t27` (the window and the keys lanes): `git fetch` and merge master + before every push; reseal `homes.t27` after master moves (its gen hashes follow its imports). +- `--auto` merge can land a PR before a redesign: do not enable it while the owner is still judging the look. +- `t27b` rejects `;` comment lines and apostrophes inside an array literal. +- `KEY_FILES` of the agent specs drive the analytics; stale paths make every domain look idle. +- The sandbox cannot create worktrees or write `.git/config`: create them from a Terminal tab. +- Use Sonnet subagents for the work, at most three at once on this 8 GB Mac; the orchestrator + merges, builds, launches and screenshots. + +## The 27 petals are the 27 agents (owner, 2026-10-20) + +Petal index p = agent ordinal p + 1. The realms match the alphabet layers: RAZUM petals 0-8 = +Archetypal A-I, MATERIYA 9-17 = Spiritual J-R, DUKH 18-26 = Physical S-Ti. A petal opens that +letter's console. **Petal 3 = C: the chat and the commander live here** (driving the agents is +compiling intent into work), together with the compiler core. Homes (t27a..t27u, Queen) are +groups in the console sidebar, not petals. + +What each console needs. "Source" = where its live data comes from today; "gap" = what is missing. + +| # | Letter, domain | Instruments (widgets inside the console) | Actions | Source / gap | +|---|---|---|---|---| +| 1 | A Architecture / ADR / SOUL | law map L1-L7 with violations, ADR list + status, constitution diff | open ADR, check laws | SOUL.md, architecture/ADR-*; gap: no law-violation counter | +| 2 | B Build / Pipeline | CI lamp per workflow, queue length, build time, red set vs master | rerun, open red log | specs/claw/gates.t27, gh checks; gap: host must poll gh | +| 3 | C Compiler Core + CHAT + COMMANDER | chat panes of all agents (honeycomb), commander line (Cmd+K: tri/t27c verbs), compiler stages t27c-stages, gen backends, seal status | send to agent, run t27c verb | pty server, glass_window chat; widgets t27c-* | +| 4 | D De-Zigfication | foreign lines left (Rust/Zig/TS), port progress, t27o sweep | open port task | specs/tools/t27o, tools/policy; gap: one counter spec | +| 5 | E Experience / XAI | experience log stream, lessons learned, why-cards | save experience | .trinity/experience, why_card.t27 | +| 6 | F Formal Conformance | conformance pass/fail, vacuous passes, test-report | run tri test | conformance/, t27b test; 22 widgets | +| 7 | G Graph / ArchBench | SPEC MAP (T27F spec graph), dependency fan-in/out, archbench | open spec graph | spec_graph*.t27, t27f_graph | +| 8 | H HSLM / NN | model architectures, training runs (t27t), loss curves | open run | specs/t27t, specs/hslm; see t27t study | +| 9 | I ISA / Registers | register file, encodings, t27a assemble/disassemble | assemble a line | specs/isa/t27a.t27; gap: no widget yet | +| 10 | J Jobs / Task Routing | issue queue, claims, lanes | claim, route | specs/queen, gh issues | +| 11 | K Kernel / FPGA MAC | BENCH (3 dice), bitstreams, timing, pins, LUTs (64 widgets) | flash, detect | bench.t27, bench_boards.t27, camera | +| 12 | L Language / Syntax | grammar coverage, unparsed constructs, fmt suggestions | format spec | tri unparsed, fmt_source.t27 | +| 13 | M Metrics / Telemetry | domain_pulse for all letters, CPU/RAM of the Mac, swarm stats | open pulse | domain_pulse.t27 | +| 14 | N Numeric / GoldenFloat | gf16 calc, format table, paper claims 34/8/3 (43 widgets) | recompute claim | gf16.t27, paper_claims.t27 | +| 15 | O Orchestration / Phases | PHI LOOP phase per lane, ring state | advance phase | specs/queen phases; gap | +| 16 | P Physics / SacredPhysics | identity checks phi^2 + phi^-2 = 3, formula catalog | verify identity | specs/physics, specs/sacred | +| 17 | Q Queue / Scheduling | crons (specs/crons), scheduler queue, next runs | pause/resume | specs/crons | +| 18 | R Runtime / Bootstrap | stage0 FROZEN_HASH, bootstrap build, toolchain versions | rebuild t27c | bootstrap/, toolchain.t27 | +| 19 | S Specs / Standards | spec count, seals fresh/stale, duplicates ledger | reseal, dupe scan | .trinity/seals, dupe_scan | +| 20 | T Queen / Lotus | Queen panel, GOD MODE, suggestions, swarm board | confirm/dismiss | queen.t27 | +| 21 | U Universe / Domains | the 27-letter map itself, homes, ownership gaps (OPEN) | reassign path | agent_parts.t27, homes.t27 | +| 22 | V Verdict / Bench | verdicts, bench results, mutation score | run verdict | t27c verdict, tri mutate | +| 23 | W Workflow / tri cell | tri command palette, recent tri runs | run tri verb | specs/tools/tri | +| 24 | X External / Interop | MCP servers, bindings, providers, keys (KEYS tab) | test provider | t27mcp, keys.t27, router.t27 | +| 25 | Y Yield / DePIN | lab nodes, XP earned, hosting | join lab | specs/lab, depin; flags off | +| 26 | Z Zero-Touch UX / Docs | t27u university: 7 course maps, lessons, blog | open lesson | courses.t27 (trinity) | +| 27 | Ti Security / Reserve | secrets scan, key rotation, identity | rotate key | gitleaks, keys.t27 | + +## One registry per letter (owner, 2026-10-20) + +Each of the 27 letters gathers, with no copy of any list: its TOOL t27 (t27a assembler, +t27b native compiler, t27c compiler, t27f FRONTEND, t27o converter, t27t trainer, t27u university; +other letters: inventory pending), its agent (specs/agents), its specs (domain_pulse letter_of_path), +its widgets and app surfaces with owner + feeders (agent_parts), its tri commands and MCP tools +(the agent's TOOLS field; cards in specs/tools/tri (53), specs/tools/mcp (11), specs/t27mcp +tool_schema (20 t27_/t27f_ tools)), and its skills (the agent's SKILLS field; specs/skills, +.claude/skills). The registry is an extension of agent_parts.t27, not a new spec. +On screen: a console's command strip runs its tri commands and MCP tools in a PTY pane inside the +window (output kept in the console log, numbers flow into instruments labelled measured + command); +skills are playbook buttons that start an agent in the C chat with that skill; the commander on C +(Cmd+K) is a palette of every command and tool with its agent badge; empty letters show their gap. +Open question for the owner: where a tool letter and an alphabet agent disagree (A assembler vs +Architecture, B native compiler vs Build, F frontend vs Formal Conformance, T trainer vs Queen, +U university vs Universe), the default is that the tool decides the letter. + +## The t27 tools that exist (inventory 2026-10-20, origin/master + all local repos) + +| Tool | What | Law / path | Front door | Status | +|---|---|---|---|---| +| t27a | assembler / disassembler | specs/isa/t27a.t27 | t27c asm, t27c disasm | live | +| t27b | native compiler (AArch64 JIT, Mach-O, Verilog) | cli/t27b | t27b test/build/verilog | live | +| t27c | bootstrap compiler + sweep | bootstrap/src/compiler.rs | t27c, tri test | live | +| t27f | FRONTEND (spec graph, outline, map stills, petal home) | specs/t27f/frontend.t27 | MCP t27f_*, claw/t27f, t27.ai/t27f/graph | live | +| t27o | Ouroboros: foreign code into .t27 | specs/tools/t27o/rust_convert.t27 | tri t27o | live (Rust first) | +| t27t | trainer (law; runners igla-coder-gpu train.py, specs/ml/train_hslm.t27) | specs/t27t/t27t.t27 | tri t27t | live spec; F_MEASURED=false, RUNNER_USES_INT_MM=false | +| t27u | university, 7 courses x 27 lessons | trinity apps/website/specs/course/courses.t27 | t27.ai/learn | live (law in trinity) | +| t27os / t27mcp | the OS window / MCP service | specs/t27os, specs/t27mcp | T27 OS app, t27_* tools | live | + +No tool exists for d, e, g, h, i, j, k, l, m, n, p, q, r, s, v, w, x, y, z (hits for t27x/t27w/t27s/t27z +are false: a refused extension, logs, temp names). specs/compiler/theory/toolchain.t27:643 refuses +inventing a t27d..t27z series without an artifact: do not name a tool before it exists. +t27t gaps (live console for H/N): no live run feed (runner private), no model registry (own_model.t27 +is a map), HTOK data and checkpoints host-local in /tmp/hslm; real today: GEMM bench, saving with its +estimate label (bind to F_MEASURED), paper_claims, dataset/igla-coder MANIFEST. + +## Every page has its own agent chat (owner, 2026-10-20) + +At the bottom of every letter page sits a docked chat with THAT letter's agent: Claude Code through +the key master (free tier, rotation, traffic light), started with a brief built from the agent's own +spec (tool t27, DOMAIN, ARCHETYPE, KEY_FILES, SKILLS, TOOLS), allowed only the tri commands and +MCP tools of its TOOLS field, in that letter's worktree. Tapping an instrument adds it to the chat as +context; 'do it' turns the conversation into a kanban card and a fan-out child whose result returns +to the parent. Decisions: specs/t27os/agent_chat.t27; spawn through the pane verbs of #11316. + +## Harness visibility (what people love on X, studied 2026-10-20) + +Hook-event timeline per agent (every tool call; our pty server already receives Claude Code hooks), +a 'waiting for you' column with batch approve/reject, a parent -> child tree with live pane preview, +session replay with a time scrubber, token/cost per agent/key/model/tier, a playful glanceable hive +(H page), a read-only watch mode for shared links. + +## One UI, three platforms; the game moves in before the site is replaced (owner, 2026-10-20) + +One UI logic and one design for the desktop, the website and the FPGA: a frame function in .t27 +(extend specs/t27f/ui_plan.t27) turns state + normalized input into a draw list (canvas_out.t27 +primitives) and hit regions; three thin hosts only replay it -- CoreGraphics (glass_window becomes a +player), wasm + canvas on the site (the widgets host.js pattern), a PL rasterizer to HDMI (glyph57 +fonts). One input script, byte-equal draw lists on all three. +Before app.t27.ai/game is replaced by T27 OS on the web, EVERY piece of the game's logic is ported +into the 27 petals -- including the SPACE MAP: QueenUniverse (the universe atlas of worlds = repos, +queenUniverseAtlas, the hex hive with foundation/castle/code layers, territory held/neutral/fog, +castle rings) goes to M (MAP) and H (HIVE). A parity table (game feature -> petal -> native / live +page / missing) gates the switch: nothing missing. + +## Plan (open items, 2026-10-20) + +1. Cockpit dashboard in the home tabs: nested widget tree, agent consoles, card.png instruments, + live widget pane (WKWebView), t27u course maps (issue #11275). +2. Wire `agent_parts.t27` and `domain_pulse.t27` into the consoles; fix `KEY_FILES` to real paths. +3. T27F instruments in the consoles: SPEC MAP (spec_graph_view drawn natively), CHECK lamp, particles + from the pulse; register T27 OS as a T27F host in `frontend.t27`. +4. BENCH tab drawn from `bench.t27`; project summary strip on T27B/T27C. +5. Idle CPU to zero (#11025): redraw on events only. +6. Promotion, only after the owner's Telegram yes: an X Article "T27 OS + Opus 5.5, full setup + included", a 30-45 s video petals -> console -> live widget -> board blinking; no claim + without its command. + +## Landing (GOD MODE lanes) + +GOD MODE lanes land through `own-git-landing` (law: `specs/queen/golden_chain.t27` section 6): verify +locally, land on the local golden head, one batched GitHub push per window; never a push per commit. diff --git a/.trinity/seals/agents_agent_a.json b/.trinity/seals/agents_agent_a.json index 3f0b5c9b17..10eccf68c8 100644 --- a/.trinity/seals/agents_agent_a.json +++ b/.trinity/seals/agents_agent_a.json @@ -1,17 +1,17 @@ { - "built_by": "t27c-bootstrap@0.5.2+d2c8e7d41", + "built_by": "t27c-bootstrap@0.5.2+0fba674ef", "closure": [], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:ad1b6f986564827541c0758cfc46b64ac0033c81201327a8510a0bf4e4cc1a0f", - "gen_hash_rust": "sha256:0eb41b942eb513532c4c4439beb2a2dcb1e55d3137382989c9f2541a65527d72", - "gen_hash_verilog": "sha256:2d13cbe301407ac4c72c4cfce9d8e71e1d130e0cae238635d95bd9b1f417ce6c", - "gen_hash_zig": "sha256:f946cc34f0164c74c8e96be63a160687ae313a5bbac97bdd3b32a66ff7d3fe30", + "gen_hash_c": "sha256:0ed823c52079f697e185cb47ba3d044717196197e27bf4cc864b82f8be4672e6", + "gen_hash_rust": "sha256:4bb7207a7fa8468b4bb6dbc8c2a7234bb60c74ef5fd0f514239d0bbc80f56d97", + "gen_hash_verilog": "sha256:01f70626a9d69a5582df73418860759d6152ff793d33b75fa746ff2fd0bb925f", + "gen_hash_zig": "sha256:0b5ef4c09af0dd37932d8564a8701b072592a768f60259dcd28e0fd3a34d0ced", "module": "agent_a", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-18T05:03:21Z", + "sealed_at": "2026-10-20T08:02:56Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:9c50b42a17b64c3b61012440d134d8776cdb6bdffc9817ab4873907a209de2ae", + "spec_hash": "sha256:71906ca0bc81a48947f323c86cd7942a89b2c66ac2300c6008b5eb2038593a02", "spec_path": "specs/agents/a.t27", "tests": { "failed": 0, @@ -22,7 +22,7 @@ "vacuous": 0 }, "toolchain": { - "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", "zig": "0.16.0" } } \ No newline at end of file diff --git a/.trinity/seals/agents_agent_b.json b/.trinity/seals/agents_agent_b.json index 9c1eae47a2..ae796233da 100644 --- a/.trinity/seals/agents_agent_b.json +++ b/.trinity/seals/agents_agent_b.json @@ -1,17 +1,17 @@ { - "built_by": "t27c-bootstrap@0.5.2+d2c8e7d41", + "built_by": "t27c-bootstrap@0.5.2+0fba674ef", "closure": [], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:2af0938300a5010313ffc3541b99d979742741467999baff83bd7c959f38908f", - "gen_hash_rust": "sha256:70b3940fc729e406d06b7ed39183d4c3eb3fcceb8b09370875e1e9f8e65716cd", - "gen_hash_verilog": "sha256:e7fbe55b62b120fcf26561c7607b3318733f48968b526978b2c437cd72126d30", - "gen_hash_zig": "sha256:e20835f1079a9a7fa4759b9e7d0a97ca12377dfbb883d2f4297d9a25ff79f61c", + "gen_hash_c": "sha256:f12e543118f24989cb998d5eb00502bad182debb129bd81b49c4413803c5b786", + "gen_hash_rust": "sha256:b7d5abce671c5c23cdf7ad23b03300cd1882408774752f6321dee0236e292ad7", + "gen_hash_verilog": "sha256:1e3647a9bcdc39b7d7956289a76cfd0ae370120fa3f7f43e3b34b506b40ea05f", + "gen_hash_zig": "sha256:f5505482424e7e4608efc77b146ff09b366a6babaadf2949a30658acdda593d0", "module": "agent_b", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-18T05:03:21Z", + "sealed_at": "2026-10-20T08:03:02Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:e5bf310d2b0720a132af9a3efedf564ca87a89a9a395638f5f36bdcf7074c489", + "spec_hash": "sha256:cc5f65d684a810470f4806a05c90e78ee22cdcce96236e791aedce3b94ddbab9", "spec_path": "specs/agents/b.t27", "tests": { "failed": 0, @@ -22,7 +22,7 @@ "vacuous": 0 }, "toolchain": { - "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", "zig": "0.16.0" } } \ No newline at end of file diff --git a/.trinity/seals/agents_agent_c.json b/.trinity/seals/agents_agent_c.json index 75dad0d8aa..d8bcaede73 100644 --- a/.trinity/seals/agents_agent_c.json +++ b/.trinity/seals/agents_agent_c.json @@ -1,17 +1,17 @@ { - "built_by": "t27c-bootstrap@0.5.2+3c1da6226", + "built_by": "t27c-bootstrap@0.5.2+0fba674ef", "closure": [], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:400008d7b1f3591782d6aad152f534f2623e7124584439dae8dcfe6ba50ec6f7", - "gen_hash_rust": "sha256:75e7832af9371dbb8e306da516c38dc079aea95a7db5ffebdea8df4a8f6cb681", - "gen_hash_verilog": "sha256:5ec2c295816a35082da8fdf21c60c9bb1a13a954dec7f1c15d418ffd573e1d2c", - "gen_hash_zig": "sha256:d2de49d2c25c6928de9cb10c92293a430a609628b78849aeeb7e7105ab3cfb35", + "gen_hash_c": "sha256:a18d9ecb413aca87e10d7552b78f672f1bdaf8e02d253185c0aa92b475ad1196", + "gen_hash_rust": "sha256:0506a29442b47b92453f5138616ba32cfdb013e0959a81fc6e21bdeb732dafb1", + "gen_hash_verilog": "sha256:c74fd359db4e554d74ffc96278893841d96d9d7549585a479b894137c7a79f36", + "gen_hash_zig": "sha256:ca11870fd12307733cf3f65688dc2a090198f63917a0e6a4f275a012dbbeb82d", "module": "agent_c", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-11T19:09:31Z", + "sealed_at": "2026-10-20T08:03:07Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:279a32045fbb5b40ba98d1b8d05858327669d0534a8ee2a09b041003c6974a2b", + "spec_hash": "sha256:edc4a17e399d5f35d98626411f2df555474932fe44fdd490526e23d76767de55", "spec_path": "specs/agents/c.t27", "tests": { "failed": 0, @@ -22,7 +22,7 @@ "vacuous": 0 }, "toolchain": { - "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", "zig": "0.16.0" } } \ No newline at end of file diff --git a/.trinity/seals/agents_agent_d.json b/.trinity/seals/agents_agent_d.json index 4347f0de07..40d2e74d79 100644 --- a/.trinity/seals/agents_agent_d.json +++ b/.trinity/seals/agents_agent_d.json @@ -1,17 +1,17 @@ { - "built_by": "t27c-bootstrap@0.5.2+d2c8e7d41", + "built_by": "t27c-bootstrap@0.5.2+0fba674ef", "closure": [], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:81ce5caf27f65b8f6a9dfc0601e5e99427d19cf555418dddb6c0370678bf18b4", - "gen_hash_rust": "sha256:ee862f97ad74b33ee6197d26739bf0d0f20a6032a7d9fd20a8a2f0361c3ce875", - "gen_hash_verilog": "sha256:da7637849162f4d23c09a5242d594c7e3e2d5cc42cd2c7d57781500dfb4aabf7", - "gen_hash_zig": "sha256:329800cf6f47c02be01141d7c04588dabf05bb6310baa2ca2d65866662fea0b7", + "gen_hash_c": "sha256:9872f57fb4b3b9e7cd579ea87552747894ce9823988043e631bbfe7e2611c0c7", + "gen_hash_rust": "sha256:7db47dcf3cc1b6bd84ee95df1b08911e03be2fde676261c0703ee9d693b61dca", + "gen_hash_verilog": "sha256:e09bec3abe239f2c251cdc4fc01a0c6ef75f1800ea450e42f17cf27b3b702cc8", + "gen_hash_zig": "sha256:40e19a2a457ddb654b06a79a73d2ad780602b876a1e01d1c361279e39e05395c", "module": "agent_d", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-18T05:03:21Z", + "sealed_at": "2026-10-20T08:03:11Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:dba3eec722e9bdfbd64f446947336cad4fdc84f42c4cada17f327af8f2cd38b6", + "spec_hash": "sha256:1ec256e780c1923ca5786484b5abd30de463fae370e9502b9a97cc6586331206", "spec_path": "specs/agents/d.t27", "tests": { "failed": 0, @@ -22,7 +22,7 @@ "vacuous": 0 }, "toolchain": { - "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", "zig": "0.16.0" } } \ No newline at end of file diff --git a/.trinity/seals/agents_agent_e.json b/.trinity/seals/agents_agent_e.json index 77a958c9d7..d422fc1fb1 100644 --- a/.trinity/seals/agents_agent_e.json +++ b/.trinity/seals/agents_agent_e.json @@ -1,17 +1,17 @@ { - "built_by": "t27c-bootstrap@0.5.2+d2c8e7d41", + "built_by": "t27c-bootstrap@0.5.2+0fba674ef", "closure": [], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:1c5cf53eeb59571aea56f46bd7f96bb571fac8a8a2c2ed38a7abca52bbab707c", - "gen_hash_rust": "sha256:4eb7773494f018ff6196d8b2cd0a0f05f59909a0354b72739e2b6b7865e52249", - "gen_hash_verilog": "sha256:1e286888c84a8741398e4c4d5b04ee736411ce61ff20ff0007bfd2661c3fc7cf", - "gen_hash_zig": "sha256:a104863396e4c419994d7a8ee9d875536ada7dfcfd0cfd971558911e120447d7", + "gen_hash_c": "sha256:827df4ef3f255c4cc48440736b44b463f7d48381c78f3771be7a66ea1fb0848d", + "gen_hash_rust": "sha256:a4917e9a6c6ce30e4ef809f45e5955fb50797d5f41f99b848aa71ed0e20f4bf6", + "gen_hash_verilog": "sha256:93312fa872e7780d94d5abb30d8aed5c9797df211ecc9a7a149dfbbf27d2b125", + "gen_hash_zig": "sha256:bcd8373e6e72a2f1a6a70f1e4ce8a56f58511fb88a57db4f70eb982df7ccd399", "module": "agent_e", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-18T05:03:22Z", + "sealed_at": "2026-10-20T08:03:15Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:e4a98e73ed8ebb98f931f1e88fdb4d7221b61f59f55477fffca736ad5b86fa60", + "spec_hash": "sha256:1fe9e8ac254611a3acf8dafe9d348e722fc7780d284899d9b2790da588b337d9", "spec_path": "specs/agents/e.t27", "tests": { "failed": 0, @@ -22,7 +22,7 @@ "vacuous": 0 }, "toolchain": { - "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", "zig": "0.16.0" } } \ No newline at end of file diff --git a/.trinity/seals/agents_agent_f.json b/.trinity/seals/agents_agent_f.json index ff8d8f14b0..a741067769 100644 --- a/.trinity/seals/agents_agent_f.json +++ b/.trinity/seals/agents_agent_f.json @@ -1,17 +1,17 @@ { - "built_by": "t27c-bootstrap@0.5.2+d2c8e7d41", + "built_by": "t27c-bootstrap@0.5.2+0fba674ef", "closure": [], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:b3146f16cb01aba180261a20509c5f20f16f3ca22d3741c44db4ca7fb9f31e41", - "gen_hash_rust": "sha256:a877c81316b8563e4913fb14c0e262227a3b9230601c1ccacc637f46496c478f", - "gen_hash_verilog": "sha256:8914f4c7017f680a49bb9e8f1339778821fe507e003f3c7a0a2ebacd6763d38b", - "gen_hash_zig": "sha256:54cf260469083cb3b633b3b7ba89675557e53a497ff9330cfa881f3d7d7b6b87", + "gen_hash_c": "sha256:888189a7a4fe8757982a72b4bcd5cb60da4263e7ff55c85d35ffdaa8493178f7", + "gen_hash_rust": "sha256:ebf4a6853fa85117a2c8ef020f907b5e03b4de9c9b50afef8a7eac8fbd0214f1", + "gen_hash_verilog": "sha256:e085a20e36d8af89b88ca0c2ab09bd659efc577959c40b5dce54d4ccbb20a076", + "gen_hash_zig": "sha256:d6552780eaf5e178441b41aebbb4fd47ddc325a6c3902707a6a01dc5c7af53cd", "module": "agent_f", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-18T05:03:22Z", + "sealed_at": "2026-10-20T08:03:20Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:ab35cf79efa97f160cd017e366a33c3fd52b8b1654b85f9108da3367311ac67f", + "spec_hash": "sha256:89d0ed566b126a366cefb7dc5c15131bfe5b84c9d1b908837be88b825291a861", "spec_path": "specs/agents/f.t27", "tests": { "failed": 0, @@ -22,7 +22,7 @@ "vacuous": 0 }, "toolchain": { - "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", "zig": "0.16.0" } } \ No newline at end of file diff --git a/.trinity/seals/agents_agent_g.json b/.trinity/seals/agents_agent_g.json index 5494971f24..f96f286cbf 100644 --- a/.trinity/seals/agents_agent_g.json +++ b/.trinity/seals/agents_agent_g.json @@ -1,17 +1,17 @@ { - "built_by": "t27c-bootstrap@0.5.2+d2c8e7d41", + "built_by": "t27c-bootstrap@0.5.2+0fba674ef", "closure": [], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:6e89dd18f9cbdf26e292e82e544aa9009f05c5e095a170c14793b93ef76daae5", - "gen_hash_rust": "sha256:3f834f0713989ebaaac676be929cb63ab1e9f8d53a957418bd5bacf545021d71", - "gen_hash_verilog": "sha256:20f9f526a6f5a9e08467f20e19a11b9a7a85282a22326486f97c02dc74accfa3", - "gen_hash_zig": "sha256:376d638cbc7720647ad5c5c23ff2a0f9a4af49eebb82c9601dce7dcf4625d8b5", + "gen_hash_c": "sha256:c3ff56dc3bce32456b3f13ac9ed0f9851df21a6e663c0444e3abf8403b6f3ccc", + "gen_hash_rust": "sha256:0f2be4a6a938528b6df3828d3f84cddbd6df7fceb1f3cb4c7899abe2800eb325", + "gen_hash_verilog": "sha256:0492d39d07bda19a75c8523ff1b6126a376f56c1fa007e4f58dcfc55a7715e1b", + "gen_hash_zig": "sha256:50314a46fc7e2bcda7174c39682446adb4b103c265ea4412a897b3bcf14210de", "module": "agent_g", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-18T05:03:22Z", + "sealed_at": "2026-10-20T08:03:24Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:1615b2385157e2609df09dec68f308738d3fcb956dd85ac9134493fc48dd3ccc", + "spec_hash": "sha256:806540cb9aedcf795c2eb96bf9eaded0cab615ea979cfde6c6dca4ef8cbd912d", "spec_path": "specs/agents/g.t27", "tests": { "failed": 0, @@ -22,7 +22,7 @@ "vacuous": 0 }, "toolchain": { - "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", "zig": "0.16.0" } } \ No newline at end of file diff --git a/.trinity/seals/agents_agent_h.json b/.trinity/seals/agents_agent_h.json index addf440840..0db08ce856 100644 --- a/.trinity/seals/agents_agent_h.json +++ b/.trinity/seals/agents_agent_h.json @@ -1,17 +1,17 @@ { - "built_by": "t27c-bootstrap@0.5.2+d2c8e7d41", + "built_by": "t27c-bootstrap@0.5.2+0fba674ef", "closure": [], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:29f18fdddd32b6f5ca8fc36654af5cda211783e52cfc9ab0b12241adfcdf0241", - "gen_hash_rust": "sha256:d5136a5c2be6f7bb8a58ab88277a8bbee310c83c0cff8472d30a4ab6fbbe1756", - "gen_hash_verilog": "sha256:ea683f5aed65766cd230bdd8d29ec8da2a1d31a7f87ed2ec609520d66efcfc4a", - "gen_hash_zig": "sha256:56376579b2f6269ab005e0ebd51f4f738c0d749a9a9402ba313fa1bb17b9b78d", + "gen_hash_c": "sha256:42eab46246b2099afad1fe227f6dda402ba3393413d8b376aec0896d2d8c21aa", + "gen_hash_rust": "sha256:2e107fcc546bdfbda8cd534d410fb40d86d23f5e6cc4e003687454684ee1d121", + "gen_hash_verilog": "sha256:5ff33809b1019a9e98488ec1bdbaaea9fbeff7f379b326bd67300e264156e322", + "gen_hash_zig": "sha256:87446b8743dae80d3d6b4a5cd23969122e2e82a29da516feccbb7662af39f1f8", "module": "agent_h", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-18T05:03:23Z", + "sealed_at": "2026-10-20T08:03:28Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:89bcc9b4849a54f07947e8346890b98c025377ddc3e44efaf6c6e54fd8abe20f", + "spec_hash": "sha256:a97fedb301e35f049a36c77280dc673ab6ffb0f51589ad74584af0055554aa9f", "spec_path": "specs/agents/h.t27", "tests": { "failed": 0, @@ -22,7 +22,7 @@ "vacuous": 0 }, "toolchain": { - "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", "zig": "0.16.0" } } \ No newline at end of file diff --git a/.trinity/seals/agents_agent_i.json b/.trinity/seals/agents_agent_i.json index 79bdf35def..27fdeba880 100644 --- a/.trinity/seals/agents_agent_i.json +++ b/.trinity/seals/agents_agent_i.json @@ -1,17 +1,17 @@ { - "built_by": "t27c-bootstrap@0.5.2+d2c8e7d41", + "built_by": "t27c-bootstrap@0.5.2+0fba674ef", "closure": [], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:fc6fdab875bd71f91c8a71024bc2df1acb3e2a2fd2cbc1242344f6c2873dd6dd", - "gen_hash_rust": "sha256:03727afe6f4691e994f345e8c3a20fe64e4126e9262a426bdaf2dd351fa564b7", - "gen_hash_verilog": "sha256:2b0cee0ec524007d958dee324fd3290ab3dfb3510b9db549dd11cc0a421f64ea", - "gen_hash_zig": "sha256:70184c84d01ed0b691dffbab150b95d6a57d4e41d56f5f983ea8a1f7ce4ab0dc", + "gen_hash_c": "sha256:8d2e6a20c6fe069a423561c56b1f4f0bab5e0caaacc5d102b0b3f9ac0e3d1e51", + "gen_hash_rust": "sha256:648a61eea7fc5f6f910809ad9e504b8113f130f36178ed82e8648162c564a843", + "gen_hash_verilog": "sha256:403b17e5076d2d3f82b291463ca841b3a256d640db4086660ea4411f6817b4b4", + "gen_hash_zig": "sha256:30c29fd1a4834a583b285441428fa55c7fe40cb647a44ef063c0e30f20e09a36", "module": "agent_i", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-18T05:03:23Z", + "sealed_at": "2026-10-20T08:03:32Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:177df145bff04f55575c6f8d23ef8938b95f4e5591b276ed37a97d9a93cce5cb", + "spec_hash": "sha256:7fa218c5efac1cf75afe481291ccd496e96d491f01a024c0bc3e5ce4b9144241", "spec_path": "specs/agents/i.t27", "tests": { "failed": 0, @@ -22,7 +22,7 @@ "vacuous": 0 }, "toolchain": { - "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", "zig": "0.16.0" } } \ No newline at end of file diff --git a/.trinity/seals/agents_agent_j.json b/.trinity/seals/agents_agent_j.json index 5b15a66493..6397c9a667 100644 --- a/.trinity/seals/agents_agent_j.json +++ b/.trinity/seals/agents_agent_j.json @@ -1,17 +1,17 @@ { - "built_by": "t27c-bootstrap@0.5.2+d2c8e7d41", + "built_by": "t27c-bootstrap@0.5.2+0fba674ef", "closure": [], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:dc3cf272974f95cb49f08136a7f07b3039cdd1e600a44ccdcd7a9675d02852e3", - "gen_hash_rust": "sha256:4328ef427f3cb2ca101c1533923490aac1f55aab49a0892a3a19d83d291fd43f", - "gen_hash_verilog": "sha256:db7f6e4f0f43b44fe1b33ef0ea26b99faf71f407d314c8f4fcafac07d01a58fd", - "gen_hash_zig": "sha256:8cd793b1da61ad8778d790b0a02e90c0a42940b70b9de505b09c9bf2c30cf07e", + "gen_hash_c": "sha256:6be768c5aff124012d0139c65893552af017de93df68e0304e5fbd62a9d679a0", + "gen_hash_rust": "sha256:bd708f066f22650fd10e286076c6928632bafccf272cc254bb7104df472ec966", + "gen_hash_verilog": "sha256:aab69f0f0cbfbe7dfa51783f33cc9e8d64aae39baaf9c2ac367c3fec76119737", + "gen_hash_zig": "sha256:3438b504d47db0d131c22da6fb9ccc76874e882e188a1c4b0ab43cd432b7a329", "module": "agent_j", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-18T05:03:24Z", + "sealed_at": "2026-10-20T08:03:35Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:da87356db35dc4fa3909b89899fc28a4fa79172a7ef9a59e529cfa5eb51b11a2", + "spec_hash": "sha256:19a1e9e83be1132715201cb1743176c6c247360d2a8c77fcbbf97bc08a80e512", "spec_path": "specs/agents/j.t27", "tests": { "failed": 0, @@ -22,7 +22,7 @@ "vacuous": 0 }, "toolchain": { - "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", "zig": "0.16.0" } } \ No newline at end of file diff --git a/.trinity/seals/agents_agent_k.json b/.trinity/seals/agents_agent_k.json index afd0d4770e..38c6111693 100644 --- a/.trinity/seals/agents_agent_k.json +++ b/.trinity/seals/agents_agent_k.json @@ -1,17 +1,17 @@ { - "built_by": "t27c-bootstrap@0.5.2+d2c8e7d41", + "built_by": "t27c-bootstrap@0.5.2+0fba674ef", "closure": [], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:afbc26ad3196401b54bfa947930f55966c5a7ff375a7d4397cce9039898fbbf8", - "gen_hash_rust": "sha256:0512fb013073e01cc3ee3a0dcdd75996c2806ba69dc57fc0317f554dff7482c3", - "gen_hash_verilog": "sha256:fcae19bebca6a0bb71adfc95d1c618a3d87840c4c4eb1fc624d5acaaa61c89c7", - "gen_hash_zig": "sha256:a45177b6ec942258b3a9372d6c248bf7a5832a7c73bc75ff22bef63e644f8601", + "gen_hash_c": "sha256:285a44561f10f34f6769b47736e1d54afb146116cbaa96cf48312d546d663182", + "gen_hash_rust": "sha256:d22bd503e3aa1ded876a6eda3b541333c6cc9c43e6870bddeabe92f4e7f75c02", + "gen_hash_verilog": "sha256:d8138c8c1e78170fad07046375b90dcc3078c812ab67b9f178996f68fcc926aa", + "gen_hash_zig": "sha256:4c1173942757cf85ddbe8875a192f1922da6332aec935a2422184cb3a3ab398f", "module": "agent_k", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-18T05:03:24Z", + "sealed_at": "2026-10-20T08:03:38Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:c6e23e7c2f05fffcc433cb9ec7c4a83caf7869d7f70105cc4d6f4d942c846388", + "spec_hash": "sha256:e82068f30a63fb812274633f63cbbea3f4a7116738b467f8e6711718f50306ae", "spec_path": "specs/agents/k.t27", "tests": { "failed": 0, @@ -22,7 +22,7 @@ "vacuous": 0 }, "toolchain": { - "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", "zig": "0.16.0" } } \ No newline at end of file diff --git a/.trinity/seals/agents_agent_l.json b/.trinity/seals/agents_agent_l.json index 09264fe457..1a49185ff8 100644 --- a/.trinity/seals/agents_agent_l.json +++ b/.trinity/seals/agents_agent_l.json @@ -1,17 +1,17 @@ { - "built_by": "t27c-bootstrap@0.5.2+d2c8e7d41", + "built_by": "t27c-bootstrap@0.5.2+0fba674ef", "closure": [], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:e0d08482cf83b329423f9536daaa32756f642a7debb0abd2f06210f7ed88c2b5", - "gen_hash_rust": "sha256:eba2647a8acfe3afbe39cae785a25e29ae952b4383266d7ba0156c17faba0cd3", - "gen_hash_verilog": "sha256:4a471b909847e29f2ed6642ad8105cff692fd5b8da0f00efd76cd0b5ae2a5cf0", - "gen_hash_zig": "sha256:c5a8f4962e458b56317974f22427fcea11e004b108f21388fd50823d16d9c744", + "gen_hash_c": "sha256:46f1dc4ed89977eec7b834a946f16fbe52a522f5d3a6f76fec08cacd14218b0c", + "gen_hash_rust": "sha256:69886723e50852b2a7edd6901b2abdec485f7ce60f19f345fc3678448aef1cd5", + "gen_hash_verilog": "sha256:f174e94916b08ecff45f3f3eb319ac2511a865883fa5e253c923990049097223", + "gen_hash_zig": "sha256:3867fee94da070e5efe0e89026bb174f922cc72837682f770a4d4ad49ede03be", "module": "agent_l", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-18T05:03:25Z", + "sealed_at": "2026-10-20T08:03:42Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:d5f5baf56ff73b73605def30bfc8dce3b14a743f8368aee32fa013c7e1da6183", + "spec_hash": "sha256:5bbe1c2e22adce9dc93e2665ca2b0797bb5c1352527bbe48b7b681074d265075", "spec_path": "specs/agents/l.t27", "tests": { "failed": 0, @@ -22,7 +22,7 @@ "vacuous": 0 }, "toolchain": { - "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", "zig": "0.16.0" } } \ No newline at end of file diff --git a/.trinity/seals/agents_agent_m.json b/.trinity/seals/agents_agent_m.json index 7682a51219..bc01e63501 100644 --- a/.trinity/seals/agents_agent_m.json +++ b/.trinity/seals/agents_agent_m.json @@ -1,17 +1,17 @@ { - "built_by": "t27c-bootstrap@0.5.2+d2c8e7d41", + "built_by": "t27c-bootstrap@0.5.2+0fba674ef", "closure": [], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:3df9ddef92a1af5f3e9fb02e7328c598f601630dd57dcbccf8a93337d1c79301", - "gen_hash_rust": "sha256:c97b18f6e90539248711cc311e7d6a3eb475d0c03f05d2fa57cc14f9052e9664", - "gen_hash_verilog": "sha256:914e3e94632bd0641e0e6762d91cc2642d106795d4291d02a6d91f8cfb02a6f2", - "gen_hash_zig": "sha256:da36e1189e0d155bb919098b4a73e8407c6ff1e63fa40152d7535550dd459fa9", + "gen_hash_c": "sha256:27281ad5180de2598bdc239d2b434d69c61d135a24a7ce76403c787500a78594", + "gen_hash_rust": "sha256:4c61e9a4dc67f8238e79e52c988bd0193810de58c21679926c25a31fdc003839", + "gen_hash_verilog": "sha256:cbb0c9b0b4b5e9a08bdc9ee6af95ea528f1d194b382197ed73b62126ff7a7965", + "gen_hash_zig": "sha256:72d54e9e5a76aa72e759d24816f5fc3bd15bb240c419f185379db2b0d6fd94f9", "module": "agent_m", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-18T05:03:25Z", + "sealed_at": "2026-10-20T08:03:45Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:fa41d2235f5f3b6716af43a1ce6b56dbec88ca701b001159b34993da8c07f70f", + "spec_hash": "sha256:cac35a8c2519a6933c199ef87887f5a032f8628ddd6ec0282fe3823bce2012aa", "spec_path": "specs/agents/m.t27", "tests": { "failed": 0, @@ -22,7 +22,7 @@ "vacuous": 0 }, "toolchain": { - "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", "zig": "0.16.0" } } \ No newline at end of file diff --git a/.trinity/seals/agents_agent_n.json b/.trinity/seals/agents_agent_n.json index adf14f053b..f418ab77ab 100644 --- a/.trinity/seals/agents_agent_n.json +++ b/.trinity/seals/agents_agent_n.json @@ -1,17 +1,17 @@ { - "built_by": "t27c-bootstrap@0.5.2+d2c8e7d41", + "built_by": "t27c-bootstrap@0.5.2+0fba674ef", "closure": [], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:c4d4cf73dff3decd0871660a14ba36a47dc78ef3a6c3f1265c2a5036a68513cb", - "gen_hash_rust": "sha256:436d956831e5440e81454c2c4a602ec4a7eb1c8c81c57ef32376b615a7cd0167", - "gen_hash_verilog": "sha256:234b276b6cb1add4ed9c9064baa6097e22d11c79a55c171011374dbab166317f", - "gen_hash_zig": "sha256:519fd8382d8be89c54cea6c76b8657b38874dc88a4c530028ac7f3f9ba72a63d", + "gen_hash_c": "sha256:ec3f54f27664b36b67e5890d265130cdf99a7c3c3a425a588c1d54863a19f658", + "gen_hash_rust": "sha256:e40d97221389835bab9537aa6034eb8fa635c23484936960687ece7d7243d161", + "gen_hash_verilog": "sha256:1203d242ca8ee58e659ae9a54107462cca2b163bc134e1ecf7beb4b272b9eee3", + "gen_hash_zig": "sha256:342efc25e11dd9cef1fea552f31efed569d9ccbfedab6f0fa3dec7aa6c5f236b", "module": "agent_n", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-18T05:03:26Z", + "sealed_at": "2026-10-20T08:03:49Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:289b1a4dc8ee9d993c1b0c34acecd652d2e91cb559d42e5474b285743ffc9fd0", + "spec_hash": "sha256:c948efc8889539ad0b950e69d9b5614ca7ac7342571bdd3c4bddc36a08c54410", "spec_path": "specs/agents/n.t27", "tests": { "failed": 0, @@ -22,7 +22,7 @@ "vacuous": 0 }, "toolchain": { - "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", "zig": "0.16.0" } } \ No newline at end of file diff --git a/.trinity/seals/agents_agent_o.json b/.trinity/seals/agents_agent_o.json index c00fb10680..2ea347b99c 100644 --- a/.trinity/seals/agents_agent_o.json +++ b/.trinity/seals/agents_agent_o.json @@ -1,17 +1,17 @@ { - "built_by": "t27c-bootstrap@0.5.2+d2c8e7d41", + "built_by": "t27c-bootstrap@0.5.2+0fba674ef", "closure": [], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:7d2927844057152d0dbcf2536d4ff968723c19d33eb668082c775e702b92fdde", - "gen_hash_rust": "sha256:880dcfdf6569dd7367b3dd6160994998a20433ccd2eacb5e24cf1fd5621da532", - "gen_hash_verilog": "sha256:6a6e4b03280f726ff3ed7cadb4f8f2626886f1efd6bb1c05bef8f1148c350c1c", - "gen_hash_zig": "sha256:ed2a732a141258a1c9109f2f49c126bebac53e5d56b1943b06d80e0cb936a37d", + "gen_hash_c": "sha256:a6207a4b6bb904b8ca5570e608c916385aa1d6df129d3680b835ce79a2881e21", + "gen_hash_rust": "sha256:c624bc44191f64f6833ece4b67dcfce4fc2a1b8593985007b3191e2ed3ebff21", + "gen_hash_verilog": "sha256:a81ce607a84a20aa0f4a03f3d429f21fd962c7f26ec634a8b0c7ea5a431d4b17", + "gen_hash_zig": "sha256:51c725b138f6bb15717b9033a957fb22fcfa545d2b68b4b28a2c611710224c5a", "module": "agent_o", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-18T05:03:26Z", + "sealed_at": "2026-10-20T08:03:53Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:30c748bb8b850571564cc259f3a62f48f206ba5af1a53e94dcbcf0b19e2461e3", + "spec_hash": "sha256:35f50c61565c8f3e2547d2fbb230c1d54971555e7c610e2c098322674c67f633", "spec_path": "specs/agents/o.t27", "tests": { "failed": 0, @@ -22,7 +22,7 @@ "vacuous": 0 }, "toolchain": { - "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", "zig": "0.16.0" } } \ No newline at end of file diff --git a/.trinity/seals/agents_agent_p.json b/.trinity/seals/agents_agent_p.json index 07e22e7135..c2e17a3ac8 100644 --- a/.trinity/seals/agents_agent_p.json +++ b/.trinity/seals/agents_agent_p.json @@ -1,17 +1,17 @@ { - "built_by": "t27c-bootstrap@0.5.2+d2c8e7d41", + "built_by": "t27c-bootstrap@0.5.2+0fba674ef", "closure": [], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:3edce7faa1dc66649e6ef734f62676cad1fc45e86cbd1322eba6ca83d2db31e0", - "gen_hash_rust": "sha256:47c14c707c15ca688bd3cb8ae4aa920b6d29967cbc61d705758baf6f30d34836", - "gen_hash_verilog": "sha256:5a6c2f99bb3c04124d2e748fbe08b586ea0f97cbc4f74d623cc99080df6ae3c6", - "gen_hash_zig": "sha256:b8b0d43925147d8411f3525055779e70dc55ed3f67a4a9d564d5e0e32a04bfdf", + "gen_hash_c": "sha256:101fe098435a2d0a73b788ee86b84a9c9f6e7596e3668f17feff8d00080301aa", + "gen_hash_rust": "sha256:83b61eb5999b71ce81e5f6a6974f310945c49c23bac38c4cac7307448f2f7c34", + "gen_hash_verilog": "sha256:2950b3034762dbe2df4c5f319a9e1562953d6a288e2c50adde8b505752636c95", + "gen_hash_zig": "sha256:ab175b5af5e51fed6e95521f5df57df8474d309be5758083029e3b14626a2ae0", "module": "agent_p", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-18T05:03:26Z", + "sealed_at": "2026-10-20T08:03:56Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:2b345b6719c1f5c62e706dedba5f3d451e68a45d99b135660b397e75c5a3636c", + "spec_hash": "sha256:0127006e75eb4dfa92433cf63538ecaec31d868fec0b7ed47f9c8d33e37d8f43", "spec_path": "specs/agents/p.t27", "tests": { "failed": 0, @@ -22,7 +22,7 @@ "vacuous": 0 }, "toolchain": { - "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", "zig": "0.16.0" } } \ No newline at end of file diff --git a/.trinity/seals/agents_agent_q.json b/.trinity/seals/agents_agent_q.json index 813f088b13..f26080acca 100644 --- a/.trinity/seals/agents_agent_q.json +++ b/.trinity/seals/agents_agent_q.json @@ -1,17 +1,17 @@ { - "built_by": "t27c-bootstrap@0.5.2+d2c8e7d41", + "built_by": "t27c-bootstrap@0.5.2+0fba674ef", "closure": [], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:3d7e7c20943d82ac0ec61151bee03e6a2bae8ee7f22d55f820a54f10af0d982a", - "gen_hash_rust": "sha256:822a095e8f9129ffb1bc299da9fd343c0f7577b2d7ed02b845685b7b6bc3c940", - "gen_hash_verilog": "sha256:86a8f52d3e90006ea263e9abb4cbbac5feea3f3da8eff43dff5361731b953226", - "gen_hash_zig": "sha256:170aa3729f4788efa9abe5e4d29ec0c4714bf6a49931859e46f44b4f8b3dd674", + "gen_hash_c": "sha256:a6f81a7b49fd06cea6205f901b723e105aefd54349705012526474d2f6ffa77d", + "gen_hash_rust": "sha256:54335f3c64c900fc04d15cc6d3a909848039b59eec9cf2fcbfed20f603a76441", + "gen_hash_verilog": "sha256:5b40a7b3d39b74409935a5c6bb7aeb0b1b1ec24f4b0546d0ae0c8ead50c6ee74", + "gen_hash_zig": "sha256:e73492aa1fae49fa1bd1c9cc909dee83ad5c4c470ffdc6908b1583ebcaa5c955", "module": "agent_q", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-18T05:03:27Z", + "sealed_at": "2026-10-20T08:04:00Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:05f51000eca17e32e4ff13b523c748dfbcc01b53b4729351b584811073da00ee", + "spec_hash": "sha256:46b1b94f892a1975da1c58ed5d4ac42017ac93295b300bf9d49361f8177be23c", "spec_path": "specs/agents/q.t27", "tests": { "failed": 0, @@ -22,7 +22,7 @@ "vacuous": 0 }, "toolchain": { - "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", "zig": "0.16.0" } } \ No newline at end of file diff --git a/.trinity/seals/agents_agent_r.json b/.trinity/seals/agents_agent_r.json index 9a44f44b51..d50b6f7a87 100644 --- a/.trinity/seals/agents_agent_r.json +++ b/.trinity/seals/agents_agent_r.json @@ -1,17 +1,17 @@ { - "built_by": "t27c-bootstrap@0.5.2+d2c8e7d41", + "built_by": "t27c-bootstrap@0.5.2+0fba674ef", "closure": [], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:699650a07912fde7c2a3e1615f7592fd79c4e1f71855ee6776f8feacf9ca9985", - "gen_hash_rust": "sha256:5a4016f69d29b28d98d40658d6a70df283a6183ac1959419beed776bddb1e068", - "gen_hash_verilog": "sha256:b60df55208885dc606d66eeec86b7fe6bc1115243de9c5b9bc6aa76751a67000", - "gen_hash_zig": "sha256:911bd11ad6c58fb9b613ff5088ce21ef66b3a2a7583efb523a2a1ecdb3a77835", + "gen_hash_c": "sha256:dbe8517040346d90288732071aeebec6e020c41028577a14eb38196434a7b15e", + "gen_hash_rust": "sha256:d65975475b7ead22ff0531fe67dffe44ec2886c20c3da7a98500607ffb125da3", + "gen_hash_verilog": "sha256:ebaea759f5902ba64c0bdbea6e150615fa4f315f9580060ba767c00c2b020a4d", + "gen_hash_zig": "sha256:62776478638d04293c55ce70f5cffe6559ed655d1e998a3e1e0c8ebb86728a43", "module": "agent_r", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-18T05:03:27Z", + "sealed_at": "2026-10-20T08:04:03Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:d488a5f1a126db86d6f76c76178882ab43e4c6f353df6ffab7d4d58adee3d1d4", + "spec_hash": "sha256:5a21c4267bbc8a3aa2db475cd22754573eb57ba58064bfd031c5edf41061bc34", "spec_path": "specs/agents/r.t27", "tests": { "failed": 0, @@ -22,7 +22,7 @@ "vacuous": 0 }, "toolchain": { - "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", "zig": "0.16.0" } } \ No newline at end of file diff --git a/.trinity/seals/agents_agent_s.json b/.trinity/seals/agents_agent_s.json index 4127038f0c..44558e8fc8 100644 --- a/.trinity/seals/agents_agent_s.json +++ b/.trinity/seals/agents_agent_s.json @@ -1,17 +1,17 @@ { - "built_by": "t27c-bootstrap@0.5.2+d2c8e7d41", + "built_by": "t27c-bootstrap@0.5.2+0fba674ef", "closure": [], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:b83ed0e3895f0676c8d34152ba813aa96acf8bcb5873c9c06f7bb898bd71c341", - "gen_hash_rust": "sha256:eef9e7a6dca53c9c5b2f8f87c32ab4fbddc93d690e0947c74362482aa88d3973", - "gen_hash_verilog": "sha256:fbaf7eb971927e396880040f7c76a06779e84f1087deddb2591c41244fc87ce6", - "gen_hash_zig": "sha256:f96ed266ad6956a1363ac3367605cacb2cc985f97d7b12956a426fd976755515", + "gen_hash_c": "sha256:a460861e10ee080851b61854f4902e6be1d4e771652902c9de79a4a21dcb35b2", + "gen_hash_rust": "sha256:4704347408122c365c17d272b6eb9c0101796c0ce46579c023bf98b2358b40fc", + "gen_hash_verilog": "sha256:38cba0b0cbd44d18e05f4dc82a7362620a0b853f717b648b24662d03913c2ac1", + "gen_hash_zig": "sha256:dfef949c6654fbe81d584298d34bec209ddcb61bab85aa784346f80abb22768c", "module": "agent_s", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-18T05:03:27Z", + "sealed_at": "2026-10-20T08:04:07Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:032cfe3de150867bc7bcca6e3c627fdbd47ec7803e2da890adc2a0a2ab45ace9", + "spec_hash": "sha256:508650aba8906d902ffe9136bc5dda8917b61ec5c0769d48e35eec3639707826", "spec_path": "specs/agents/s.t27", "tests": { "failed": 0, @@ -22,7 +22,7 @@ "vacuous": 0 }, "toolchain": { - "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", "zig": "0.16.0" } } \ No newline at end of file diff --git a/.trinity/seals/agents_agent_t.json b/.trinity/seals/agents_agent_t.json index d1d24e3886..ca5f5e3301 100644 --- a/.trinity/seals/agents_agent_t.json +++ b/.trinity/seals/agents_agent_t.json @@ -1,17 +1,17 @@ { - "built_by": "t27c-bootstrap@0.5.2+c948f4df7", + "built_by": "t27c-bootstrap@0.5.2+0fba674ef", "closure": [], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:59a7e2cd1d0c279f90252bf66dd41af9fde6f6d458bfc0c9be5004c858badf78", - "gen_hash_rust": "sha256:d6316d17a3e5fd1df32596d458c64e073529316bdcd04c00807d6741208b98ed", - "gen_hash_verilog": "sha256:e1a8ab00c37fe34cdd480d48efa3ecea24ffd335cb8323d0b18ec6c440354b39", - "gen_hash_zig": "sha256:ba9b5398228da5436ea31f099fd19e7c5015c51da1c42a0b94b2d991837ff2e7", + "gen_hash_c": "sha256:e1f19b8c33ec5a23abf245a68d3317ad9ba6bee0dcd063af0feb1c6769af9b22", + "gen_hash_rust": "sha256:cfcfc83227e6699dc436dd4bdddfffc3019b06931737d999f9085ab554af969a", + "gen_hash_verilog": "sha256:8da006c7502f93e08e64f3dbf9f60ba786d039426f29b6c07fafaed08911218c", + "gen_hash_zig": "sha256:952ef80782d51299752429afb4fc410a6206b1b9c0fd0fe5c064b008818cc3a0", "module": "agent_t", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-19T13:41:23Z", + "sealed_at": "2026-10-20T08:04:11Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:8e55cf12bc610ca3c909b56f763d35dc5f579226f26f29ffa5becf8ba65d6cfe", + "spec_hash": "sha256:bde07f7cf48db224775861dbae70aede72a5f5a0d66fb11c13cea65bb7863b9e", "spec_path": "specs/agents/t.t27", "tests": { "failed": 0, diff --git a/.trinity/seals/agents_agent_ti.json b/.trinity/seals/agents_agent_ti.json index 534606b227..5f160d0279 100644 --- a/.trinity/seals/agents_agent_ti.json +++ b/.trinity/seals/agents_agent_ti.json @@ -1,17 +1,17 @@ { - "built_by": "t27c-bootstrap@0.5.2+d2c8e7d41", + "built_by": "t27c-bootstrap@0.5.2+0fba674ef", "closure": [], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:1ff735e7ed750ab62a46d494e04ec6ad53d7af51807b74e1c738eadf12719b16", - "gen_hash_rust": "sha256:3b12f0e19af9163e3ed33def2e9255b533c510ef3999fde25ea7ed4fc2ce3e0f", - "gen_hash_verilog": "sha256:e698c1b2831bae317f87734fd10c24d943d6e455f862a1fac95e738b2c448eb6", - "gen_hash_zig": "sha256:a752a7bfabc4011a3555ee37738fdcdf4eb4cd15bbcf7990b63b077baf69abaa", + "gen_hash_c": "sha256:9d2c2129ca6ffe88a5167383958a80988d24de821a6e850aaee90463f136b1af", + "gen_hash_rust": "sha256:d7dead6420d92618d4a534132203589fb46adf3c12ae318586afa562beb6721a", + "gen_hash_verilog": "sha256:d271ff3e5b16a87b51f7d3525626444f2c401eeef840663ef4d3ca3c320b540f", + "gen_hash_zig": "sha256:90f0651adfc1bc2a405475d44f35a08ff6f53db90bdb140c399c05aa955605d9", "module": "agent_ti", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-18T05:03:28Z", + "sealed_at": "2026-10-20T08:04:38Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:94f3185a06d150c3328091bf995803c6b674923688c449a8da6e30169be6f1c3", + "spec_hash": "sha256:e1a9a47c245dc019e2b2acfd3d15579ae8e952fca97274d28eccea5785f7b565", "spec_path": "specs/agents/ti.t27", "tests": { "failed": 0, @@ -22,7 +22,7 @@ "vacuous": 0 }, "toolchain": { - "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", "zig": "0.16.0" } } \ No newline at end of file diff --git a/.trinity/seals/agents_agent_u.json b/.trinity/seals/agents_agent_u.json index 4ed231532e..b191a00d6a 100644 --- a/.trinity/seals/agents_agent_u.json +++ b/.trinity/seals/agents_agent_u.json @@ -1,17 +1,17 @@ { - "built_by": "t27c-bootstrap@0.5.2+d2c8e7d41", + "built_by": "t27c-bootstrap@0.5.2+0fba674ef", "closure": [], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:1f732d05e6ab60712154e2bafabad80c3d764f5daaaec165c1e199905b6c3512", - "gen_hash_rust": "sha256:247a78b30981ad6f41c654f0c456b5e663f4e799bcc7a143f29ffda48758ebc0", - "gen_hash_verilog": "sha256:738e86fc43d8fa3b16ee81654bedc03f35c0432c1d41ee1afff416861645871f", - "gen_hash_zig": "sha256:7c8d0c5dd9e3fe2bdd23b3a475fd562351697b26167d5aa8674c6d185835d72b", + "gen_hash_c": "sha256:2e1eba7f34e78d018ca0a081be1fb4ba4c708c658e922fee71c075da8f98a7aa", + "gen_hash_rust": "sha256:2d4fe819de84f8f07fdfedb49db3eefd463cbeedfe3b52b3030be1354a325348", + "gen_hash_verilog": "sha256:554a101af58689a41a50c1680c41fafba6dbad8ebb0268d4d948b1812de20dac", + "gen_hash_zig": "sha256:6c92f9f17a11571a267459ee3afe18909065363df45eb60838f0f98a6df5f6dc", "module": "agent_u", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-18T05:03:29Z", + "sealed_at": "2026-10-20T08:04:15Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:c0fc120805d428ce1159ffb18d2e75e475af2499e88b6ee2c9ca7217c1d8d183", + "spec_hash": "sha256:5caff4497d35c7f26071eae44a0c5fffa265d05276fc063a8981c86c6c682474", "spec_path": "specs/agents/u.t27", "tests": { "failed": 0, @@ -22,7 +22,7 @@ "vacuous": 0 }, "toolchain": { - "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", "zig": "0.16.0" } } \ No newline at end of file diff --git a/.trinity/seals/agents_agent_v.json b/.trinity/seals/agents_agent_v.json index 89cf59cb6b..5c3619a36d 100644 --- a/.trinity/seals/agents_agent_v.json +++ b/.trinity/seals/agents_agent_v.json @@ -1,17 +1,17 @@ { - "built_by": "t27c-bootstrap@0.5.2+d2c8e7d41", + "built_by": "t27c-bootstrap@0.5.2+0fba674ef", "closure": [], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:fa392823c87b5fed3359a22eedbf15a26c5f3bd41ea4f719a267e6a4cb46d42c", - "gen_hash_rust": "sha256:cfb43c873f9b8f081385d28d13be89b6cca1d46868aa88f1e506b532f7402a29", - "gen_hash_verilog": "sha256:8bc89d9ab866061cf5b23582c5ed8d59837b7f2d7dab8f883dc0f5823321610f", - "gen_hash_zig": "sha256:0784a74b0006fef0591cd7cbfcb0b9e75223cb07f9fa96745e8dd0717108a050", + "gen_hash_c": "sha256:3be5e3d2f1e82c9a54ac5451564c11f172161df4e53b108219d9f5089c91999d", + "gen_hash_rust": "sha256:f16adf74e1c9d56e87cb8d747d05673090d814de3444b822603ccc04744e6a79", + "gen_hash_verilog": "sha256:e7c5936b1aaf7b158f57d0f444e7c88f1bd37a7665712e0f2ae0ea5feaf184f4", + "gen_hash_zig": "sha256:47ce63ebbcde8a5b975312801b5bb816f0bb10b4c86537501f08fdfc4e22414e", "module": "agent_v", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-18T05:03:29Z", + "sealed_at": "2026-10-20T08:04:18Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:e915aee8f0acf69cb8ff3b2d1e8344f62f45b513293ac0ab64470df74fa369bd", + "spec_hash": "sha256:21f65ef6c34dae06a37ad711672f8d1f40edfce2c1dedcf8df6f5bf336353be4", "spec_path": "specs/agents/v.t27", "tests": { "failed": 0, @@ -22,7 +22,7 @@ "vacuous": 0 }, "toolchain": { - "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", "zig": "0.16.0" } } \ No newline at end of file diff --git a/.trinity/seals/agents_agent_w.json b/.trinity/seals/agents_agent_w.json index 8313c8e17e..8d5e532bd6 100644 --- a/.trinity/seals/agents_agent_w.json +++ b/.trinity/seals/agents_agent_w.json @@ -1,17 +1,17 @@ { - "built_by": "t27c-bootstrap@0.5.2+d2c8e7d41", + "built_by": "t27c-bootstrap@0.5.2+0fba674ef", "closure": [], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:79ff3cc9a465dca30e5de0226e90b25de035dc08108086795235b4548f18dd6a", - "gen_hash_rust": "sha256:6a3e3619a7f5a5ba0077551e62b3aa7bd61edf4f7fe4713b5f9816697a572ceb", - "gen_hash_verilog": "sha256:c30f3bd5cc3ec5ad80091dba07a8c3cf0beb1c0393c554c742d3b9fb0a0eeb3d", - "gen_hash_zig": "sha256:a8b07421114a3287b9101d0866b51ad2fb01ff01a3ccdcbce08c87646fffb7bc", + "gen_hash_c": "sha256:f62a45ca664ec3b786cd134d99d16b274342ea6547baeb7466c56a65a6f3a1c5", + "gen_hash_rust": "sha256:b7b9884a93e356e9743330b1878bca6de2a7c0d2e622c1c8ec4613b068503049", + "gen_hash_verilog": "sha256:9898a23ccf57c033d097769bd33d7c074ef86707f1eb80242bb87976e325aa52", + "gen_hash_zig": "sha256:768c4613c9b51071cb8da717f45ed9306b7a190fdc34c138a9e0f209f89e9674", "module": "agent_w", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-18T05:03:33Z", + "sealed_at": "2026-10-20T08:04:22Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:ca853859ae5f5110ce8562aeeef387c0a1d07e03ee3788d04972e9587604be4b", + "spec_hash": "sha256:79e419da5848b15ce4084de28ddc793302a8055ac5ca0ecfe2cfc8d98663fa1c", "spec_path": "specs/agents/w.t27", "tests": { "failed": 0, @@ -22,7 +22,7 @@ "vacuous": 0 }, "toolchain": { - "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", "zig": "0.16.0" } } \ No newline at end of file diff --git a/.trinity/seals/agents_agent_x.json b/.trinity/seals/agents_agent_x.json index 99613a8314..f891be6eb1 100644 --- a/.trinity/seals/agents_agent_x.json +++ b/.trinity/seals/agents_agent_x.json @@ -1,17 +1,17 @@ { - "built_by": "t27c-bootstrap@0.5.2+d2c8e7d41", + "built_by": "t27c-bootstrap@0.5.2+0fba674ef", "closure": [], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:5cfa6ff899e38e573e0de103c9977c85869d5f4768913f71afa2e301d90e42a6", - "gen_hash_rust": "sha256:5c4b127800384866b335a305782ab37b770a51b600aa77f0dfe9d96fe418819c", - "gen_hash_verilog": "sha256:24fdf68f1265e95235dbc299ebe8006611491ae5f9254ea0a11481f4510b9023", - "gen_hash_zig": "sha256:308e748dfa6a8b55984ef5f7fade3247f3bd7ee5df36d4a36558f45eb7a79ce7", + "gen_hash_c": "sha256:fc51c0c1c8f83c5cd39e2c9d5fbe65c47f3f1b463c57c8af7eb9e6488135fb9f", + "gen_hash_rust": "sha256:b9da916b8283cdd7c53e755fc8e8660834b05e356b72305215159e139dd0b40e", + "gen_hash_verilog": "sha256:5ba7aff22055c7787b7f99d966be3a16692dce5fc673157a7e490e0fab7455ca", + "gen_hash_zig": "sha256:1229e89a3a9de8793b1c8c4ae45b4a4bff8e7a68e5470151bb5f17d5f4a35d27", "module": "agent_x", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-18T05:03:33Z", + "sealed_at": "2026-10-20T08:04:26Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:e58a0bf357ec82918cfc44da256a1e98a6bc5d5e47d5d85a7ba159a434e3604c", + "spec_hash": "sha256:d734fa0256af5711e86c0cdd0f732b143dea36284e143b3e599bc77e3764b7b8", "spec_path": "specs/agents/x.t27", "tests": { "failed": 0, @@ -22,7 +22,7 @@ "vacuous": 0 }, "toolchain": { - "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", "zig": "0.16.0" } } \ No newline at end of file diff --git a/.trinity/seals/agents_agent_y.json b/.trinity/seals/agents_agent_y.json index db7615e8ac..507cf97b96 100644 --- a/.trinity/seals/agents_agent_y.json +++ b/.trinity/seals/agents_agent_y.json @@ -1,17 +1,17 @@ { - "built_by": "t27c-bootstrap@0.5.2+d2c8e7d41", + "built_by": "t27c-bootstrap@0.5.2+0fba674ef", "closure": [], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:1a262ca1104b6f8a123fcad3c2b75fb092fea8db683e0e63cc57e7788c31fd91", - "gen_hash_rust": "sha256:0085b953cbbd019e0b16a0dd8e348d4a28cee6ebe85d497b665460941e0c3c90", - "gen_hash_verilog": "sha256:7031873ab35a7fff4655c99cabe10de241612093a1b1bb8532756ef4e9de91b6", - "gen_hash_zig": "sha256:a4b1f1492f9c31a861d87064f7a24919403ff59fc11e7608002cc7a3f04b456d", + "gen_hash_c": "sha256:db9eb1d141c4c47ccc6dc78ed8bf30c5805757be24535504628110c02a1c26e1", + "gen_hash_rust": "sha256:a257f0c209cce4184318de8efd9b65305629ea73ac3e1743787932ccd9806893", + "gen_hash_verilog": "sha256:330599d16b4cd8ef0a3f11a2910f9a51733a43ac3cf73b66b0a2039e52712f85", + "gen_hash_zig": "sha256:2e7200f73177b4c4b8b1b776a04fb5612a017e7cf3ea76fdfcdca4484d070fbd", "module": "agent_y", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-18T05:03:33Z", + "sealed_at": "2026-10-20T08:04:30Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:8f872ea0907d9797851b17914e3fb7c7b0df7ad14a003ab16fb01677bf194f0d", + "spec_hash": "sha256:e9aa140f821bcd31c16349627d603c9bdb410b9e763fa177ccc544f3b4ccf799", "spec_path": "specs/agents/y.t27", "tests": { "failed": 0, @@ -22,7 +22,7 @@ "vacuous": 0 }, "toolchain": { - "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", "zig": "0.16.0" } } \ No newline at end of file diff --git a/.trinity/seals/agents_agent_z.json b/.trinity/seals/agents_agent_z.json index daa4e9659a..e197c06168 100644 --- a/.trinity/seals/agents_agent_z.json +++ b/.trinity/seals/agents_agent_z.json @@ -1,17 +1,17 @@ { - "built_by": "t27c-bootstrap@0.5.2+d2c8e7d41", + "built_by": "t27c-bootstrap@0.5.2+0fba674ef", "closure": [], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:939ffe12dbc32fae1cdf7e74cddf5832cb80c82a81986ebf8afad51fe5aacf37", - "gen_hash_rust": "sha256:69c5a91bd5f464b7b03938803753d9cf06c4317c857e24e1c6e6f5bf88e76d09", - "gen_hash_verilog": "sha256:7c448e5ad28e56b67fe1405f750855d08f5e858c9d632f143b5123b5dc4c5ab8", - "gen_hash_zig": "sha256:af806308e445a1f37ad7242dddfbe5ebc2e0abe3e407b61011954b0c3b7e9a37", + "gen_hash_c": "sha256:30b092364e05dc47785f706ab899b7cb0debbbd90aa2cde7533fe1c4e49c9b41", + "gen_hash_rust": "sha256:10612242b47e9c70eda3951735758920a6478c8fbfd0e1254cadb878558e898f", + "gen_hash_verilog": "sha256:f5c334ac82c48d2acdd2b48c57e854e4dbef18d149837895c77f1e8d64508f78", + "gen_hash_zig": "sha256:1e71a1862f8c229bfbbcd67ba425808f22227a439ff8eae9bacf45fb3dd463f9", "module": "agent_z", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-18T05:03:34Z", + "sealed_at": "2026-10-20T08:04:34Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:d471ff2a47567f4982c43743210cbf518f13a6f3a0a322b413e38406a79e1fc4", + "spec_hash": "sha256:96550f45cbda31fab1dcdc6f685612e74c6cfea8c4ecb99c83b567e2ddec23dd", "spec_path": "specs/agents/z.t27", "tests": { "failed": 0, @@ -22,7 +22,7 @@ "vacuous": 0 }, "toolchain": { - "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", "zig": "0.16.0" } } \ No newline at end of file diff --git a/.trinity/seals/lab_LabJobKind.json b/.trinity/seals/lab_LabJobKind.json index 3d110c2005..35eada27b8 100644 --- a/.trinity/seals/lab_LabJobKind.json +++ b/.trinity/seals/lab_LabJobKind.json @@ -47,7 +47,7 @@ }, { "spec": "specs/t27f/petal_menu.t27", - "spec_hash": "sha256:9e90e3afe418dc36e5087b01566e97115d6e809e1e42bbec686e572ec19554b1" + "spec_hash": "sha256:4969faf791c56e12c78e18c7bc0d39cfb4b89997cc662b9613cadccc82b5369f" }, { "spec": "specs/trinet/node-work-credit.t27", @@ -67,16 +67,16 @@ } ], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:c7e6bdbb24d42e7fb1f6effee13e471f73c2d120d3378d629cf85ecc8edbcdd3", - "gen_hash_rust": "sha256:2b570638d4d94dfd551e8bff976e58d48b2a4de2a9cbadd87ac8646bf5ee3b5b", - "gen_hash_verilog": "sha256:364431e262b73b0f826730718deda22b885ec04015fa3a2e252e71c04e3a0d23", - "gen_hash_zig": "sha256:45ee6959f3a7b45542f8c30180224caa1ff0fafab76e4d8bf07ae3074177fd72", + "gen_hash_c": "sha256:21afeed24425bae05cb9a2475c9ea212dc965a8281e3f2a9f503357845974677", + "gen_hash_rust": "sha256:f8fa7253e5a0cbb93785f96615a86d2cc4c6d0dbf00c4041c3cd067739774216", + "gen_hash_verilog": "sha256:3b38ae9190a1f1384f083e9972245e5d94c2b0d739a234fbfe1cfe41c56e9515", + "gen_hash_zig": "sha256:590736ff2881285718c52222f0679e1cfa6edc2b60c23ce5c45ae9123868c7dc", "module": "LabJobKind", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-20T07:25:30Z", + "sealed_at": "2026-10-20T08:36:46Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:ae6064a2e267dcc06583f8a47efd71bb42e6951262f0aa84e5762f6eb9eab590", + "spec_hash": "sha256:947aa46e23f8f61b6c7d291d15cec5ca6ad1eba836f31b99edff7af3f9f370f7", "spec_path": "specs/lab/job_kind.t27", "tests": { "failed": 0, diff --git a/.trinity/seals/land_TriGoldenLand.json b/.trinity/seals/land_TriGoldenLand.json new file mode 100644 index 0000000000..2adb6ae0a8 --- /dev/null +++ b/.trinity/seals/land_TriGoldenLand.json @@ -0,0 +1,57 @@ +{ + "built_by": "t27c-bootstrap@0.5.2+24a3abcad", + "closure": [ + { + "spec": "specs/queen/actors.t27", + "spec_hash": "sha256:73c4135bfcb8442a96bfc64977048d75055bb55e5c3e04e49a20966dada4b722" + }, + { + "spec": "specs/queen/golden_chain.t27", + "spec_hash": "sha256:cd0a5164c3fc14104160cb235162a4d0f856b31e0ff31a8fa415d3b0a8a618e6" + }, + { + "spec": "specs/tri/lab/receipt.t27", + "spec_hash": "sha256:00d9eec8d7be84dde596c9990b3a5574de24aff9f7a94579fc620eb585712441" + }, + { + "spec": "specs/tri/land/land.t27", + "spec_hash": "sha256:d58d3cc5684ab086cab355b8ae81eb677c6b2e787adba26c663edb370dab94bb" + }, + { + "spec": "specs/tri/lanes/lanes.t27", + "spec_hash": "sha256:aac192b3e3250196262a9b8467d4a9cef8147438c11c0744c66c3f6b175c9342" + }, + { + "spec": "specs/tri/pr/preflight.t27", + "spec_hash": "sha256:f9b7b84c125b47c1b5285bb98ab5532987bdef785319c0a3c60bbf7e87f5b6f6" + }, + { + "spec": "specs/tri/remote/remote_job.t27", + "spec_hash": "sha256:84a153bd92bbc94bc57f1d030114507bd99de2f3ac3f800db3fe5880df950479" + } + ], + "config": "gen=c,rust,verilog,zig;tests=zig", + "gen_hash_c": "sha256:5db152177716a1f5404d74518b0b63c9eecb33f3f91561b8243899ccf4817005", + "gen_hash_rust": "sha256:b565feb3facca1f94f8be038c11844b302ff5aeb6072aa1eecfa1a4615d835e7", + "gen_hash_verilog": "sha256:9119982035543fbfc3edeb91091640b7700a0f99841e5598f5f51a3d9adc57ca", + "gen_hash_zig": "sha256:aa88502568c418aa4842b7c378a53259ed044617c656508279dda608b130d7ef", + "module": "TriGoldenLand", + "ring": 12, + "seal_schema": 2, + "sealed_at": "2026-10-20T12:49:11Z", + "sealed_by": "t27c-bootstrap@0.5.2", + "spec_hash": "sha256:320b56c776d1da37a9156d717080e8da4aad84a09081136cb0446b985636eeef", + "spec_path": "specs/tri/land/golden_land.t27", + "tests": { + "failed": 0, + "failing": [], + "forced": false, + "passed": 28, + "total": 28, + "vacuous": 0 + }, + "toolchain": { + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", + "zig": "0.16.0" + } +} \ No newline at end of file diff --git a/.trinity/seals/queen_QueenGoldenChain.json b/.trinity/seals/queen_QueenGoldenChain.json index 9efaaf897e..47398795ee 100644 --- a/.trinity/seals/queen_QueenGoldenChain.json +++ b/.trinity/seals/queen_QueenGoldenChain.json @@ -1,5 +1,5 @@ { - "built_by": "t27c-bootstrap@0.5.2+ea1bccd77", + "built_by": "t27c-bootstrap@0.5.2+24a3abcad", "closure": [ { "spec": "specs/queen/actors.t27", @@ -7,27 +7,27 @@ } ], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:df0a8f7d757715fe52575c1cc54a1c3db4685d7210efec98b42fc9607d9b11d6", - "gen_hash_rust": "sha256:d4a3a68b6eed54db0d2942805c65d6d1a899f4ff5773555132910e222369319b", - "gen_hash_verilog": "sha256:6bbdf606a3cf8255f8b0a8643f8e70d361b9847f068568d1587d5eddd8ac17a6", - "gen_hash_zig": "sha256:40b61b4dee455694642d9b1367ff0debdd5de039a07cccab6e82bdeaa3fd13a5", + "gen_hash_c": "sha256:8e6b8ee44674b6feb85ab81c8354109c5d504528e1b3113c40d5f79c1d157f18", + "gen_hash_rust": "sha256:969f62debaf795b03e00c31bb20fc66f0da090c24d44fcc7db18194bfd822004", + "gen_hash_verilog": "sha256:a015d55586bb0d19c7d6e0867a26eb33b0258508728992384924b5d73c616431", + "gen_hash_zig": "sha256:69acaecc2ba99833e46d9c2e400c554d3cf607a793b21a8e629e17ddb923fc48", "module": "QueenGoldenChain", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-11T13:09:12Z", + "sealed_at": "2026-10-20T12:54:19Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:0bb25490b6bf346e5bed2839f5b866ecc7b93926d16515b2d0db21226799ee91", + "spec_hash": "sha256:6ef1997a7f2657467b42a4c976bebe8ce0a3eb4ffbaa62023c341c73381105ef", "spec_path": "specs/queen/golden_chain.t27", "tests": { "failed": 0, "failing": [], "forced": false, - "passed": 19, - "total": 19, + "passed": 31, + "total": 31, "vacuous": 0 }, "toolchain": { - "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", "zig": "0.16.0" } } \ No newline at end of file diff --git a/.trinity/seals/t27f_t27f_widget_factory.json b/.trinity/seals/t27f_t27f_widget_factory.json new file mode 100644 index 0000000000..5d15fea665 --- /dev/null +++ b/.trinity/seals/t27f_t27f_widget_factory.json @@ -0,0 +1,45 @@ +{ + "built_by": "t27c-bootstrap@0.5.2+0fba674ef", + "closure": [ + { + "spec": "specs/t27f/ui_plan.t27", + "spec_hash": "sha256:383fdfb78aa13f2e48172a20e9863d91aa5147b63b1685be8e3a40667243a3f1" + }, + { + "spec": "specs/t27mcp/service.t27", + "spec_hash": "sha256:3d7957837be8ac3de14b18ce1c0ecdde805f3f71d4d801b62d476b2e82f4a1ae" + }, + { + "spec": "specs/t27os/agent_parts.t27", + "spec_hash": "sha256:f95d437cc07db5249fea5567d3b89a8dba5b9ec6c7de92270aee2fce4acf5fe3" + }, + { + "spec": "specs/widgets/catalog.t27", + "spec_hash": "sha256:bdef999dc17e89132397a83238d265aec8b1bfd1de6d2b7a8412c4c8ca8f81f6" + } + ], + "config": "gen=c,rust,verilog,zig;tests=zig", + "gen_hash_c": "sha256:98fa4f12b9f9fbd9268901c310f5e8cf6d741156261f0d216a43edd76cff8fe7", + "gen_hash_rust": "sha256:6114f139565f8aa879c88d2bece6e0121134a0798478c0eea8012e515de11feb", + "gen_hash_verilog": "sha256:82efe14ab64d02614a00470c869d55714b5b0af23b1bd7c5fd5a8599fbd1fa9b", + "gen_hash_zig": "sha256:e4b9042ee8bd12f98bf7f231f32966c41f25c6d900c01f7f88d973b67b4e53a5", + "module": "t27f_widget_factory", + "ring": 12, + "seal_schema": 2, + "sealed_at": "2026-10-20T08:07:27Z", + "sealed_by": "t27c-bootstrap@0.5.2", + "spec_hash": "sha256:a097c058d68c2791a6f6f80402ce0a60b54315cc0e670a1b0398a01ef949cc98", + "spec_path": "specs/t27f/widget_factory.t27", + "tests": { + "failed": 0, + "failing": [], + "forced": false, + "passed": 9, + "total": 9, + "vacuous": 0 + }, + "toolchain": { + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", + "zig": "0.16.0" + } +} \ No newline at end of file diff --git a/.trinity/seals/t27os_T27osAgentParts.json b/.trinity/seals/t27os_T27osAgentParts.json new file mode 100644 index 0000000000..15295be7fb --- /dev/null +++ b/.trinity/seals/t27os_T27osAgentParts.json @@ -0,0 +1,41 @@ +{ + "built_by": "t27c-bootstrap@0.5.2+24a3abcad", + "closure": [ + { + "spec": "specs/t27f/petal_menu.t27", + "spec_hash": "sha256:4969faf791c56e12c78e18c7bc0d39cfb4b89997cc662b9613cadccc82b5369f" + }, + { + "spec": "specs/t27os/homes.t27", + "spec_hash": "sha256:13c91e583c79a270b01bf765147a3661eba1093aec077f11d41266076df65926" + }, + { + "spec": "specs/t27os/session_store.t27", + "spec_hash": "sha256:a2cb4e21d67129c1180a411b3e987116a279b17075ad22cf111c1892d292f178" + } + ], + "config": "gen=c,rust,verilog,zig;tests=zig", + "gen_hash_c": "sha256:c8564a2225cd12bb40d0d93773c485792770cd98d4f3c1b8aa379009a8d59fab", + "gen_hash_rust": "sha256:981e049513c939bdece31db2e14dfbdaaaf5e322685d10d9082b45a00a8ce0e9", + "gen_hash_verilog": "sha256:2880207b59b63f73ce6872403d8b751efa735a873a862c6011529971b473e18b", + "gen_hash_zig": "sha256:d4bbbbc16fe2504467d4fcd188285d91b8dedd5920be166f2246277c2a72f46e", + "module": "T27osAgentParts", + "ring": 12, + "seal_schema": 2, + "sealed_at": "2026-10-20T13:15:49Z", + "sealed_by": "t27c-bootstrap@0.5.2", + "spec_hash": "sha256:a510c31ec18c143f6444949c4604d39c7a3fc6cd05e00182bfe46cd496094b9f", + "spec_path": "specs/t27os/agent_parts.t27", + "tests": { + "failed": 0, + "failing": [], + "forced": false, + "passed": 20, + "total": 20, + "vacuous": 0 + }, + "toolchain": { + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", + "zig": "0.16.0" + } +} \ No newline at end of file diff --git a/.trinity/seals/t27os_T27osBench.json b/.trinity/seals/t27os_T27osBench.json index 2813932b34..717f54c21f 100644 --- a/.trinity/seals/t27os_T27osBench.json +++ b/.trinity/seals/t27os_T27osBench.json @@ -3,7 +3,7 @@ "closure": [ { "spec": "specs/verified/bench_boards.t27", - "spec_hash": "sha256:16e3572c3a25b6ac813a84bed798480c2012d1f98479fb58216e542032c72b12" + "spec_hash": "sha256:0f8e21722d37409f254e887074f7f271056816f2507172e53716a271d76aa7f4" }, { "spec": "specs/verified/die_binding.t27", @@ -11,23 +11,23 @@ } ], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:b5af44a6442da7fa6b68ba6346f34a39637f1115010f643e1e83bab25b757baa", - "gen_hash_rust": "sha256:c022dab59db938a1dbd45a855372673cc2e4843cd5f574a95a52b5d5f38b4e8a", - "gen_hash_verilog": "sha256:80cc90c6f3734ebcca2469a15ae9109f36eb4cc3b5e7f410459860872494d3f7", - "gen_hash_zig": "sha256:92185e2fe5bead082718783be0bf512ba571fc05bf93426a1d776f670705735d", + "gen_hash_c": "sha256:888ba13242c457f7e845c1064c0fb17c6721e28a6c6a0b79cf37076b19790a46", + "gen_hash_rust": "sha256:42b5d4161f3bfcf3f4cef219baa29cb9ac5f90ad06e4a718efc0916a3961a975", + "gen_hash_verilog": "sha256:abce9265dece41123f91b857ebfe5c2d64fd8ae4598377e5e15326c3bd4795e4", + "gen_hash_zig": "sha256:dfedbc858e597188405403fcd84904abb0e46aae7288f371c623db64cc0e364f", "module": "T27osBench", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-20T07:42:18Z", + "sealed_at": "2026-10-20T09:29:19Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:8f2fc839e8ec94b5dce167b8b1209edf4462177265983785dd6acc5a8d1be4d4", + "spec_hash": "sha256:51f1e78823ffc7643815e62a55b081fc301905220b7f0dd75c76fbee47b8755b", "spec_path": "specs/t27os/bench.t27", "tests": { "failed": 0, "failing": [], "forced": false, - "passed": 13, - "total": 13, + "passed": 18, + "total": 18, "vacuous": 0 }, "toolchain": { diff --git a/.trinity/seals/t27os_T27osDomainPulse.json b/.trinity/seals/t27os_T27osDomainPulse.json new file mode 100644 index 0000000000..0c8be252bd --- /dev/null +++ b/.trinity/seals/t27os_T27osDomainPulse.json @@ -0,0 +1,137 @@ +{ + "built_by": "t27c-bootstrap@0.5.2+0fba674ef", + "closure": [ + { + "spec": "specs/agents/A.t27", + "spec_hash": "sha256:71906ca0bc81a48947f323c86cd7942a89b2c66ac2300c6008b5eb2038593a02" + }, + { + "spec": "specs/agents/B.t27", + "spec_hash": "sha256:cc5f65d684a810470f4806a05c90e78ee22cdcce96236e791aedce3b94ddbab9" + }, + { + "spec": "specs/agents/C.t27", + "spec_hash": "sha256:edc4a17e399d5f35d98626411f2df555474932fe44fdd490526e23d76767de55" + }, + { + "spec": "specs/agents/D.t27", + "spec_hash": "sha256:1ec256e780c1923ca5786484b5abd30de463fae370e9502b9a97cc6586331206" + }, + { + "spec": "specs/agents/E.t27", + "spec_hash": "sha256:1fe9e8ac254611a3acf8dafe9d348e722fc7780d284899d9b2790da588b337d9" + }, + { + "spec": "specs/agents/F.t27", + "spec_hash": "sha256:89d0ed566b126a366cefb7dc5c15131bfe5b84c9d1b908837be88b825291a861" + }, + { + "spec": "specs/agents/G.t27", + "spec_hash": "sha256:806540cb9aedcf795c2eb96bf9eaded0cab615ea979cfde6c6dca4ef8cbd912d" + }, + { + "spec": "specs/agents/H.t27", + "spec_hash": "sha256:a97fedb301e35f049a36c77280dc673ab6ffb0f51589ad74584af0055554aa9f" + }, + { + "spec": "specs/agents/I.t27", + "spec_hash": "sha256:7fa218c5efac1cf75afe481291ccd496e96d491f01a024c0bc3e5ce4b9144241" + }, + { + "spec": "specs/agents/J.t27", + "spec_hash": "sha256:19a1e9e83be1132715201cb1743176c6c247360d2a8c77fcbbf97bc08a80e512" + }, + { + "spec": "specs/agents/K.t27", + "spec_hash": "sha256:e82068f30a63fb812274633f63cbbea3f4a7116738b467f8e6711718f50306ae" + }, + { + "spec": "specs/agents/L.t27", + "spec_hash": "sha256:5bbe1c2e22adce9dc93e2665ca2b0797bb5c1352527bbe48b7b681074d265075" + }, + { + "spec": "specs/agents/M.t27", + "spec_hash": "sha256:cac35a8c2519a6933c199ef87887f5a032f8628ddd6ec0282fe3823bce2012aa" + }, + { + "spec": "specs/agents/N.t27", + "spec_hash": "sha256:c948efc8889539ad0b950e69d9b5614ca7ac7342571bdd3c4bddc36a08c54410" + }, + { + "spec": "specs/agents/O.t27", + "spec_hash": "sha256:35f50c61565c8f3e2547d2fbb230c1d54971555e7c610e2c098322674c67f633" + }, + { + "spec": "specs/agents/P.t27", + "spec_hash": "sha256:0127006e75eb4dfa92433cf63538ecaec31d868fec0b7ed47f9c8d33e37d8f43" + }, + { + "spec": "specs/agents/Q.t27", + "spec_hash": "sha256:46b1b94f892a1975da1c58ed5d4ac42017ac93295b300bf9d49361f8177be23c" + }, + { + "spec": "specs/agents/R.t27", + "spec_hash": "sha256:5a21c4267bbc8a3aa2db475cd22754573eb57ba58064bfd031c5edf41061bc34" + }, + { + "spec": "specs/agents/S.t27", + "spec_hash": "sha256:508650aba8906d902ffe9136bc5dda8917b61ec5c0769d48e35eec3639707826" + }, + { + "spec": "specs/agents/T.t27", + "spec_hash": "sha256:bde07f7cf48db224775861dbae70aede72a5f5a0d66fb11c13cea65bb7863b9e" + }, + { + "spec": "specs/agents/U.t27", + "spec_hash": "sha256:5caff4497d35c7f26071eae44a0c5fffa265d05276fc063a8981c86c6c682474" + }, + { + "spec": "specs/agents/V.t27", + "spec_hash": "sha256:21f65ef6c34dae06a37ad711672f8d1f40edfce2c1dedcf8df6f5bf336353be4" + }, + { + "spec": "specs/agents/W.t27", + "spec_hash": "sha256:79e419da5848b15ce4084de28ddc793302a8055ac5ca0ecfe2cfc8d98663fa1c" + }, + { + "spec": "specs/agents/X.t27", + "spec_hash": "sha256:d734fa0256af5711e86c0cdd0f732b143dea36284e143b3e599bc77e3764b7b8" + }, + { + "spec": "specs/agents/Y.t27", + "spec_hash": "sha256:e9aa140f821bcd31c16349627d603c9bdb410b9e763fa177ccc544f3b4ccf799" + }, + { + "spec": "specs/agents/Z.t27", + "spec_hash": "sha256:96550f45cbda31fab1dcdc6f685612e74c6cfea8c4ecb99c83b567e2ddec23dd" + }, + { + "spec": "specs/agents/ti.t27", + "spec_hash": "sha256:e1a9a47c245dc019e2b2acfd3d15579ae8e952fca97274d28eccea5785f7b565" + } + ], + "config": "gen=c,rust,verilog,zig;tests=zig", + "gen_hash_c": "sha256:7bbf500f75e5c17bc6f3867a54aa3337f89e625bb58c46f0f193c692f6e9e7e0", + "gen_hash_rust": "sha256:60df33fd91e8b596a8d27054a92f575cd550af40dc4029f044dad0d36ab7eafe", + "gen_hash_verilog": "sha256:6d1a815625e72c3bb8e37939b78627bc56768566691c782c0f7c3dcf75112375", + "gen_hash_zig": "sha256:95263c9480509d9ec6a96656d9408045eea3692f6012f033851ad47b29b99eaa", + "module": "T27osDomainPulse", + "ring": 12, + "seal_schema": 2, + "sealed_at": "2026-10-20T08:36:49Z", + "sealed_by": "t27c-bootstrap@0.5.2", + "spec_hash": "sha256:6e7b81ce7766cdf3818300e501a594f721860135af9bace874a3e9346df43e3f", + "spec_path": "specs/t27os/domain_pulse.t27", + "tests": { + "failed": 0, + "failing": [], + "forced": false, + "passed": 10, + "total": 10, + "vacuous": 0 + }, + "toolchain": { + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", + "zig": "0.16.0" + } +} \ No newline at end of file diff --git a/.trinity/seals/t27os_T27osEvolve.json b/.trinity/seals/t27os_T27osEvolve.json new file mode 100644 index 0000000000..8f8481eb49 --- /dev/null +++ b/.trinity/seals/t27os_T27osEvolve.json @@ -0,0 +1,37 @@ +{ + "built_by": "t27c-bootstrap@0.5.2+24a3abcad", + "closure": [ + { + "spec": "specs/t27os/queen.t27", + "spec_hash": "sha256:43c3392f72a89d1b799ea74f323d71f6166a4017b1af3aa6b5e1e412a8116af2" + }, + { + "spec": "specs/t27os/queen_board.t27", + "spec_hash": "sha256:fa258e2d75d86732dbb5dfae3ce9d251e1b8e83b261922b4dcdd1d3b3ab060f9" + } + ], + "config": "gen=c,rust,verilog,zig;tests=zig", + "gen_hash_c": "sha256:cc6842e937135a53ff2592a9d35f0a497b297b864e988f714571ec8ab1045a77", + "gen_hash_rust": "sha256:ea4d50249487b61bef769987e0baf67f081cce0b8be0dbdcc08926b43898f579", + "gen_hash_verilog": "sha256:7802e7d122360efdd38de829eefe11e92e6e41dab0a1706446657e0aae536385", + "gen_hash_zig": "sha256:d43437a22bb740462488a1a8df46bdf9944f7b2b85714126fcbcedb77e0dd092", + "module": "T27osEvolve", + "ring": 12, + "seal_schema": 2, + "sealed_at": "2026-10-20T13:16:12Z", + "sealed_by": "t27c-bootstrap@0.5.2", + "spec_hash": "sha256:c0b9980d3e50b2b2af30521874c3056fa8037184bfe3cda698be1d9982c5b5c7", + "spec_path": "specs/t27os/evolve.t27", + "tests": { + "failed": 0, + "failing": [], + "forced": false, + "passed": 8, + "total": 8, + "vacuous": 0 + }, + "toolchain": { + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", + "zig": "0.16.0" + } +} \ No newline at end of file diff --git a/.trinity/seals/t27os_T27osFanout.json b/.trinity/seals/t27os_T27osFanout.json new file mode 100644 index 0000000000..3efe990ba4 --- /dev/null +++ b/.trinity/seals/t27os_T27osFanout.json @@ -0,0 +1,57 @@ +{ + "built_by": "t27c-bootstrap@0.5.2+24a3abcad", + "closure": [ + { + "spec": "specs/queen/actors.t27", + "spec_hash": "sha256:73c4135bfcb8442a96bfc64977048d75055bb55e5c3e04e49a20966dada4b722" + }, + { + "spec": "specs/queen/golden_chain.t27", + "spec_hash": "sha256:c3417dda3e99e69adf80577de610c3a1a01ba036e9c7ca1ea7cea938ab6a5e0f" + }, + { + "spec": "specs/t27os/dash.t27", + "spec_hash": "sha256:744fdd4d0c58bbb4dc5a5c39b103a7e72bc409fa8de385fb39c3eef8456cf4d1" + }, + { + "spec": "specs/t27os/evolve.t27", + "spec_hash": "sha256:c0b9980d3e50b2b2af30521874c3056fa8037184bfe3cda698be1d9982c5b5c7" + }, + { + "spec": "specs/t27os/kernel.t27", + "spec_hash": "sha256:4315475d65c02966c8c7ffc43a6c0d60f22416a67339d1f58ac88f18fda3954c" + }, + { + "spec": "specs/t27os/keys.t27", + "spec_hash": "sha256:7ae1302004eb8cef406a74314dea2b644f7ec82e21fd0558bfb3326991b0572b" + }, + { + "spec": "specs/t27os/session_store.t27", + "spec_hash": "sha256:a2cb4e21d67129c1180a411b3e987116a279b17075ad22cf111c1892d292f178" + } + ], + "config": "gen=c,rust,verilog,zig;tests=zig", + "gen_hash_c": "sha256:774e00ddb7bb9205fc8ace5fa98855c79719ee2df04d911d1aac3030be05a80d", + "gen_hash_rust": "sha256:d7399e0892ba7a62bc388fe6a19fb9c39551a2d1111b383b187affe53b1ab498", + "gen_hash_verilog": "sha256:d35bdc525f442c4d8ed4faaa7e2f44a8125b3fb8935c5c8b756d5a9e2d1767c5", + "gen_hash_zig": "sha256:2eaea98d34efe28ea79688071cf4d203b7cef249e1172ec363bd2e59ded999d1", + "module": "T27osFanout", + "ring": 12, + "seal_schema": 2, + "sealed_at": "2026-10-20T13:16:20Z", + "sealed_by": "t27c-bootstrap@0.5.2", + "spec_hash": "sha256:730f37443addab3cd6b7cb7494909aac6819d6753f04cb0d42f30dd0a8f3827d", + "spec_path": "specs/t27os/fanout.t27", + "tests": { + "failed": 0, + "failing": [], + "forced": false, + "passed": 16, + "total": 16, + "vacuous": 0 + }, + "toolchain": { + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", + "zig": "0.16.0" + } +} \ No newline at end of file diff --git a/.trinity/seals/t27os_T27osHomes.json b/.trinity/seals/t27os_T27osHomes.json index 8693f6d675..7701ba2a5b 100644 --- a/.trinity/seals/t27os_T27osHomes.json +++ b/.trinity/seals/t27os_T27osHomes.json @@ -1,33 +1,33 @@ { - "built_by": "t27c-bootstrap@0.5.2+1417e3bee", + "built_by": "t27c-bootstrap@0.5.2+24a3abcad", "closure": [ { "spec": "specs/t27f/petal_menu.t27", - "spec_hash": "sha256:9e90e3afe418dc36e5087b01566e97115d6e809e1e42bbec686e572ec19554b1" + "spec_hash": "sha256:4969faf791c56e12c78e18c7bc0d39cfb4b89997cc662b9613cadccc82b5369f" }, { "spec": "specs/t27os/session_store.t27", - "spec_hash": "sha256:7d05d7dda14587458a77291c3d587e3f08516d8e678f0a4bd06dcaab7f6d1f2f" + "spec_hash": "sha256:a2cb4e21d67129c1180a411b3e987116a279b17075ad22cf111c1892d292f178" } ], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:71f5484d1ab3f4efef7d5eb15c8563fcd013ca63fed542bdd098dbfba451197b", - "gen_hash_rust": "sha256:f7343b7a3d7ec218278cb37233a70d5a4f19f12e261425d0294098c2f8c6934e", - "gen_hash_verilog": "sha256:07ad7d52d1b53258d6552a7b7089b55f53430f34f593b9e98008f6a7ea81f3ff", - "gen_hash_zig": "sha256:e4ca80e7ba45434ec2575086c4a0d88fc41fae619c6b601f8e263415ba456ce6", + "gen_hash_c": "sha256:c65029b4a29fdd332d1e0d5472e90c50be1bfe173da688e77d881d18e8c5805d", + "gen_hash_rust": "sha256:bf20b7cb1a5f2073bd6479be2dd857b3161f875bd1663a610bc1cfc028812cc4", + "gen_hash_verilog": "sha256:796d7ea7016f1aa30ac91ff6f011b6a1728cb25b1d4dfed6935d1dfb68663c8a", + "gen_hash_zig": "sha256:8d6a97487cece26773f6d4d8c946f6b6bd00aa1bbf1461a28df73b95a3211103", "module": "T27osHomes", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-20T07:58:27Z", + "sealed_at": "2026-10-20T13:15:52Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:b6e84467e629c9b21de72f520f18b48136447f3616d498604b4a0eca60ce74b0", + "spec_hash": "sha256:13c91e583c79a270b01bf765147a3661eba1093aec077f11d41266076df65926", "spec_path": "specs/t27os/homes.t27", "tests": { "failed": 0, "failing": [], "forced": false, - "passed": 26, - "total": 26, + "passed": 27, + "total": 27, "vacuous": 0 }, "toolchain": { diff --git a/.trinity/seals/t27os_T27osKanban.json b/.trinity/seals/t27os_T27osKanban.json new file mode 100644 index 0000000000..ae96c6bba0 --- /dev/null +++ b/.trinity/seals/t27os_T27osKanban.json @@ -0,0 +1,37 @@ +{ + "built_by": "t27c-bootstrap@0.5.2+24a3abcad", + "closure": [ + { + "spec": "specs/t27os/dash.t27", + "spec_hash": "sha256:744fdd4d0c58bbb4dc5a5c39b103a7e72bc409fa8de385fb39c3eef8456cf4d1" + }, + { + "spec": "specs/t27os/evolve.t27", + "spec_hash": "sha256:c0b9980d3e50b2b2af30521874c3056fa8037184bfe3cda698be1d9982c5b5c7" + } + ], + "config": "gen=c,rust,verilog,zig;tests=zig", + "gen_hash_c": "sha256:4404755e6d9c75c25b93a3ef69dc88dbd963208685ccde3d7c7e75e46854a01f", + "gen_hash_rust": "sha256:3f449749e0e1e74db857c0bbc9a233e50ac5e5bcc6ada95b9adc3996e8a16437", + "gen_hash_verilog": "sha256:ee62cd271f3aaa022b455f3466c0aa77249ca217fc0f442a5679f28858507a29", + "gen_hash_zig": "sha256:50a3d215cea69022c0fe101c99818ee2db5dc241708cb91760e5597167cc6f65", + "module": "T27osKanban", + "ring": 12, + "seal_schema": 2, + "sealed_at": "2026-10-20T13:16:23Z", + "sealed_by": "t27c-bootstrap@0.5.2", + "spec_hash": "sha256:0bf129b955a3c1f6d3886b97977e77d6e5b7ae87507619a784cb7c037c36d978", + "spec_path": "specs/t27os/kanban.t27", + "tests": { + "failed": 0, + "failing": [], + "forced": false, + "passed": 12, + "total": 12, + "vacuous": 0 + }, + "toolchain": { + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", + "zig": "0.16.0" + } +} \ No newline at end of file diff --git a/.trinity/seals/t27os_T27osKernel.json b/.trinity/seals/t27os_T27osKernel.json new file mode 100644 index 0000000000..8c6bac851e --- /dev/null +++ b/.trinity/seals/t27os_T27osKernel.json @@ -0,0 +1,45 @@ +{ + "built_by": "t27c-bootstrap@0.5.2+24a3abcad", + "closure": [ + { + "spec": "specs/queen/actors.t27", + "spec_hash": "sha256:73c4135bfcb8442a96bfc64977048d75055bb55e5c3e04e49a20966dada4b722" + }, + { + "spec": "specs/t27os/dash.t27", + "spec_hash": "sha256:744fdd4d0c58bbb4dc5a5c39b103a7e72bc409fa8de385fb39c3eef8456cf4d1" + }, + { + "spec": "specs/t27os/evolve.t27", + "spec_hash": "sha256:c0b9980d3e50b2b2af30521874c3056fa8037184bfe3cda698be1d9982c5b5c7" + }, + { + "spec": "specs/t27os/session_store.t27", + "spec_hash": "sha256:a2cb4e21d67129c1180a411b3e987116a279b17075ad22cf111c1892d292f178" + } + ], + "config": "gen=c,rust,verilog,zig;tests=zig", + "gen_hash_c": "sha256:cf1445c0e90e6fc55a2181327b75b84f91ab799c7cdf9c923d470ee43ff7a31f", + "gen_hash_rust": "sha256:94be21477cbb18c3edb600675f5a67a30860e95dd188a451617ffb14c309cac5", + "gen_hash_verilog": "sha256:b70fffd39a184a013b99a1a1181cbffddc2c0234b9d9a6fe0aec936491e0d96a", + "gen_hash_zig": "sha256:4364752000e76dbb14f5beb7cdfe58431f882176fa9106f710b48dd9f1ebff0e", + "module": "T27osKernel", + "ring": 12, + "seal_schema": 2, + "sealed_at": "2026-10-20T13:15:55Z", + "sealed_by": "t27c-bootstrap@0.5.2", + "spec_hash": "sha256:4315475d65c02966c8c7ffc43a6c0d60f22416a67339d1f58ac88f18fda3954c", + "spec_path": "specs/t27os/kernel.t27", + "tests": { + "failed": 0, + "failing": [], + "forced": false, + "passed": 10, + "total": 10, + "vacuous": 0 + }, + "toolchain": { + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", + "zig": "0.16.0" + } +} \ No newline at end of file diff --git a/.trinity/seals/t27os_T27osKeys.json b/.trinity/seals/t27os_T27osKeys.json index 7c73d5903e..1f18b515e8 100644 --- a/.trinity/seals/t27os_T27osKeys.json +++ b/.trinity/seals/t27os_T27osKeys.json @@ -1,5 +1,5 @@ { - "built_by": "t27c-bootstrap@0.5.2+2d3203e90", + "built_by": "t27c-bootstrap@0.5.2+24a3abcad", "closure": [ { "spec": "specs/t27os/agents.t27", @@ -15,23 +15,23 @@ } ], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:449de8d2408f2cdff9e62a3be15a35575e03e970b3ba7939d6e2cf3668af1e5d", - "gen_hash_rust": "sha256:908212d0d6510d83871d3893bbac1496d7d6cb41361419339efc3c9f25c73e45", - "gen_hash_verilog": "sha256:44fd4c25080a84a490d6dd5f1ecaac37e6648f59fa5abb133d314e21a499dbee", - "gen_hash_zig": "sha256:d24916d8dcdbf6eb0b6eecd5745ea6912a6ea382c87ec320db2f74be596778a1", + "gen_hash_c": "sha256:f424460de3af503f03b7004ac608ab8cae31ce80861e576f3baeaf837dac79bf", + "gen_hash_rust": "sha256:f5ce9a6b3eb28e0b7694d607649ce58709a644feebd27726b251464ee7a73a62", + "gen_hash_verilog": "sha256:23f59b37d44cdb80950f8da62251b8eaf59d97d36e4136aeed54b2bff807cc20", + "gen_hash_zig": "sha256:dfac6769061d9d316dd408a087afb9ce63f0571ae80b0bcd8dd3571093614cbb", "module": "T27osKeys", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-20T09:28:17Z", + "sealed_at": "2026-10-20T13:15:59Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:0ac09b2a849736c10bad4c68b726ef7b8f6bf899df91380b0aa0117b534cad8d", + "spec_hash": "sha256:7ae1302004eb8cef406a74314dea2b644f7ec82e21fd0558bfb3326991b0572b", "spec_path": "specs/t27os/keys.t27", "tests": { "failed": 0, "failing": [], "forced": false, - "passed": 91, - "total": 91, + "passed": 102, + "total": 102, "vacuous": 0 }, "toolchain": { diff --git a/.trinity/seals/t27os_T27osPtyLogic.json b/.trinity/seals/t27os_T27osPtyLogic.json index 59d829a4d5..1b1ba8b33d 100644 --- a/.trinity/seals/t27os_T27osPtyLogic.json +++ b/.trinity/seals/t27os_T27osPtyLogic.json @@ -1,5 +1,5 @@ { - "built_by": "t27c-bootstrap@0.5.2+2d3203e90", + "built_by": "t27c-bootstrap@0.5.2+24a3abcad", "closure": [ { "spec": "specs/t27os/agents.t27", @@ -11,7 +11,7 @@ }, { "spec": "specs/t27os/keys.t27", - "spec_hash": "sha256:0ac09b2a849736c10bad4c68b726ef7b8f6bf899df91380b0aa0117b534cad8d" + "spec_hash": "sha256:7ae1302004eb8cef406a74314dea2b644f7ec82e21fd0558bfb3326991b0572b" }, { "spec": "specs/t27os/pane_state.t27", @@ -27,23 +27,23 @@ } ], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:74cc35dfad9496e6cf61f952e3a4e9a676bdd2e180998adc36fb840b4639067e", - "gen_hash_rust": "sha256:f3972f4a7a3a6483ba1311954cafb3c4dd40212e3cc4bb2303581f20746a6bce", - "gen_hash_verilog": "sha256:193ea5bf25afd43f786f524f5949d3d50d2f9cc5f54e74f8b21782b6078f0b5d", - "gen_hash_zig": "sha256:64258e57817aaddf5151d600352a96b7a6af21486de196d508029a18f61a9049", + "gen_hash_c": "sha256:50540d12ad38aca7c26bfbb2949afcef24efe46b6ee84f4730413464bdd95d8a", + "gen_hash_rust": "sha256:06bfd8cc1b54ae0abdc31c304afe4411e92860a9de08cf85252b4b51491b6913", + "gen_hash_verilog": "sha256:04b6cac172b4a27a5dca420d2d4a45ba4e09f0b3c8d0c06ac87dcf07ce4e1b1f", + "gen_hash_zig": "sha256:b53b7a9436691b03637121114b136451b2a77265067be825a3e7aae77dbeadc6", "module": "T27osPtyLogic", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-20T09:28:23Z", + "sealed_at": "2026-10-20T13:16:02Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:538e4fc3ac08006b5b483666065ed32ceb4602577a548abed98b9b0110876052", + "spec_hash": "sha256:d5f43766bbba3a606ad31a211533bd2a89cf8fbd18d9457d788adea9fca05cd8", "spec_path": "specs/t27os/pty_logic.t27", "tests": { "failed": 0, "failing": [], "forced": false, - "passed": 48, - "total": 48, + "passed": 53, + "total": 53, "vacuous": 0 }, "toolchain": { diff --git a/.trinity/seals/t27os_T27osQueen.json b/.trinity/seals/t27os_T27osQueen.json new file mode 100644 index 0000000000..4e1741e0d3 --- /dev/null +++ b/.trinity/seals/t27os_T27osQueen.json @@ -0,0 +1,41 @@ +{ + "built_by": "t27c-bootstrap@0.5.2+24a3abcad", + "closure": [ + { + "spec": "specs/t27os/agents.t27", + "spec_hash": "sha256:c4b89c1bd1dcb751fada2ae1f0330e250d17c5085ea73acdc38fac13548277ee" + }, + { + "spec": "specs/t27os/session_store.t27", + "spec_hash": "sha256:a2cb4e21d67129c1180a411b3e987116a279b17075ad22cf111c1892d292f178" + }, + { + "spec": "specs/t27os/windows.t27", + "spec_hash": "sha256:eddbb0bab395f5d23c89d2e43ee081a2e0c0281ff112151aa61c7fab0ee875af" + } + ], + "config": "gen=c,rust,verilog,zig;tests=zig", + "gen_hash_c": "sha256:2ab4ff109bdaf06c9137a9444c45cefba983f35c9c2e7f41332b87fbddbea81c", + "gen_hash_rust": "sha256:e07e408d7e7a7e10a26ff47213877cabe4cc8bd5bac94107e33f2fa1838e22cd", + "gen_hash_verilog": "sha256:8095c3f663b6ee5ae2526fe108b2a71944b3abee00f599242df3eb40b42d0256", + "gen_hash_zig": "sha256:abf03b9cb409e9669f4ec3f7b77c384241ea8dcbb12b377d57c499c5e37c04bf", + "module": "T27osQueen", + "ring": 12, + "seal_schema": 2, + "sealed_at": "2026-10-20T13:16:04Z", + "sealed_by": "t27c-bootstrap@0.5.2", + "spec_hash": "sha256:43c3392f72a89d1b799ea74f323d71f6166a4017b1af3aa6b5e1e412a8116af2", + "spec_path": "specs/t27os/queen.t27", + "tests": { + "failed": 0, + "failing": [], + "forced": false, + "passed": 12, + "total": 12, + "vacuous": 0 + }, + "toolchain": { + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", + "zig": "0.16.0" + } +} \ No newline at end of file diff --git a/.trinity/seals/t27os_T27osQueenBoard.json b/.trinity/seals/t27os_T27osQueenBoard.json new file mode 100644 index 0000000000..242fb10f28 --- /dev/null +++ b/.trinity/seals/t27os_T27osQueenBoard.json @@ -0,0 +1,49 @@ +{ + "built_by": "t27c-bootstrap@0.5.2+24a3abcad", + "closure": [ + { + "spec": "specs/t27os/agent_parts.t27", + "spec_hash": "sha256:a510c31ec18c143f6444949c4604d39c7a3fc6cd05e00182bfe46cd496094b9f" + }, + { + "spec": "specs/t27os/dash.t27", + "spec_hash": "sha256:744fdd4d0c58bbb4dc5a5c39b103a7e72bc409fa8de385fb39c3eef8456cf4d1" + }, + { + "spec": "specs/t27os/evolve.t27", + "spec_hash": "sha256:c0b9980d3e50b2b2af30521874c3056fa8037184bfe3cda698be1d9982c5b5c7" + }, + { + "spec": "specs/t27os/queen.t27", + "spec_hash": "sha256:43c3392f72a89d1b799ea74f323d71f6166a4017b1af3aa6b5e1e412a8116af2" + }, + { + "spec": "specs/tools/t27o/rust_std.t27", + "spec_hash": "sha256:e72606b4b3d7611883db25ed9fdaecb4353fe6a1fef551500b3cd1cc18cceb79" + } + ], + "config": "gen=c,rust,verilog,zig;tests=zig", + "gen_hash_c": "sha256:fb5e0e8e175251b1d39f155098f02290e915f7919597c7e8772107cb25e3a651", + "gen_hash_rust": "sha256:bcc00ec111b1d649e9f139d7bdada83acc86b87dbadf155b16df790d261d154c", + "gen_hash_verilog": "sha256:976b150c482418070e0e8533beabf4012bd7633c2e01d843de71d84a4f6c95f4", + "gen_hash_zig": "sha256:89675b5fde57d63f925df33305c493629cfcf8b82d914b658c2412743b2aa73c", + "module": "T27osQueenBoard", + "ring": 12, + "seal_schema": 2, + "sealed_at": "2026-10-20T13:16:09Z", + "sealed_by": "t27c-bootstrap@0.5.2", + "spec_hash": "sha256:fa258e2d75d86732dbb5dfae3ce9d251e1b8e83b261922b4dcdd1d3b3ab060f9", + "spec_path": "specs/t27os/queen_board.t27", + "tests": { + "failed": 0, + "failing": [], + "forced": false, + "passed": 13, + "total": 13, + "vacuous": 0 + }, + "toolchain": { + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", + "zig": "0.16.0" + } +} \ No newline at end of file diff --git a/.trinity/seals/t27os_T27osShare.json b/.trinity/seals/t27os_T27osShare.json new file mode 100644 index 0000000000..0cc82b982e --- /dev/null +++ b/.trinity/seals/t27os_T27osShare.json @@ -0,0 +1,57 @@ +{ + "built_by": "t27c-bootstrap@0.5.2+24a3abcad", + "closure": [ + { + "spec": "specs/claw/github_surface.t27", + "spec_hash": "sha256:42b1a9b096476016af0458355081812f3ca055550ebe12c8c8fcc30e215f0f4c" + }, + { + "spec": "specs/claw/recipes.t27", + "spec_hash": "sha256:429ab03319e9d59f6a4aad6945693a83374d6d805e32ad72836bf7aff23d6cb3" + }, + { + "spec": "specs/lab/paths.t27", + "spec_hash": "sha256:c8606e3c5593f26dde3796256a019fe2d178d6da20511a46685a91561f8f6b63" + }, + { + "spec": "specs/sync/schema.t27", + "spec_hash": "sha256:b24fd2c313180aae99e393a756bcd42ca69b4034c432329fd7dae56ced1cf3f8" + }, + { + "spec": "specs/t27os/evolve.t27", + "spec_hash": "sha256:c0b9980d3e50b2b2af30521874c3056fa8037184bfe3cda698be1d9982c5b5c7" + }, + { + "spec": "specs/t27os/kanban.t27", + "spec_hash": "sha256:0bf129b955a3c1f6d3886b97977e77d6e5b7ae87507619a784cb7c037c36d978" + }, + { + "spec": "specs/t27os/session_store.t27", + "spec_hash": "sha256:a2cb4e21d67129c1180a411b3e987116a279b17075ad22cf111c1892d292f178" + } + ], + "config": "gen=c,rust,verilog,zig;tests=zig", + "gen_hash_c": "sha256:9df3b988ac5794292c53f5824864fdd32d474087a7be81dbf88de3436a7a3eec", + "gen_hash_rust": "sha256:ef1cd537924ee92fc3239f67e143c22aa1a4a7c2d3a8c8b89d6181a89ffe029d", + "gen_hash_verilog": "sha256:262456a001966d2df6bc457430d74a94b42e3d6f500404f23b801da97a65da5a", + "gen_hash_zig": "sha256:57be622226848d5d58c15136d56081b62e31a740429b920ad23929fc17f82433", + "module": "T27osShare", + "ring": 12, + "seal_schema": 2, + "sealed_at": "2026-10-20T13:16:07Z", + "sealed_by": "t27c-bootstrap@0.5.2", + "spec_hash": "sha256:0ace395bed15b03e6dd9732d239edff08b2603df594d145d44bb64b657743a13", + "spec_path": "specs/t27os/share.t27", + "tests": { + "failed": 0, + "failing": [], + "forced": false, + "passed": 18, + "total": 18, + "vacuous": 0 + }, + "toolchain": { + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", + "zig": "0.16.0" + } +} \ No newline at end of file diff --git a/.trinity/seals/verified_VerifiedBenchBoards.json b/.trinity/seals/verified_VerifiedBenchBoards.json index 67ac671d12..8004e1645d 100644 --- a/.trinity/seals/verified_VerifiedBenchBoards.json +++ b/.trinity/seals/verified_VerifiedBenchBoards.json @@ -1,9 +1,9 @@ { - "built_by": "t27c-bootstrap@0.5.2+ddb84c5f5", + "built_by": "t27c-bootstrap@0.5.2+0fba674ef", "closure": [ { "spec": "specs/verified/corpus_receipt.t27", - "spec_hash": "sha256:e0d40083b6171c1e31f06f2e9c196ee5987930ec135241ed99661bef9808a4bd" + "spec_hash": "sha256:4aa2b8255db10c18dd43cadedc81e23a3b0363d79d7a36fcf1e86db678106de2" }, { "spec": "specs/verified/device_dna.t27", @@ -15,27 +15,27 @@ } ], "config": "gen=c,rust,verilog,zig;tests=zig", - "gen_hash_c": "sha256:6d4e895b76556d203e6defbbc7e77f0412976a137ead3ac853eda1095a8431ac", - "gen_hash_rust": "sha256:adc58adeb455a476dbdd5dff1be07b0cf6088e3e0d956bab0acff3bfe25aea88", - "gen_hash_verilog": "sha256:a72d9cf93c900f72acd7de1697caafd40adb675eadadd460f4223257ee61e966", - "gen_hash_zig": "sha256:335d9084567b2ae296770df3b4251241a206a77f9918edc1840ee75ccd5ea850", + "gen_hash_c": "sha256:b6bf17e5270b029271f568ee1ec4eda8531238332796fda317b7fc96c534f036", + "gen_hash_rust": "sha256:2625a19cf184a5bac4eba6be46fd2875bec660759db01a58e11c12c493f86758", + "gen_hash_verilog": "sha256:0d54b4ea7609ef23d7da40c57fbc83cc006872489a4404cfe7f937d61383dfa3", + "gen_hash_zig": "sha256:4fca810e35b69f6c8df4bf8ca027f96df8a7c0c83b84a9a9f95aaa6fedb66b6e", "module": "VerifiedBenchBoards", "ring": 12, "seal_schema": 2, - "sealed_at": "2026-10-10T20:15:31Z", + "sealed_at": "2026-10-20T09:29:25Z", "sealed_by": "t27c-bootstrap@0.5.2", - "spec_hash": "sha256:16e3572c3a25b6ac813a84bed798480c2012d1f98479fb58216e542032c72b12", + "spec_hash": "sha256:0f8e21722d37409f254e887074f7f271056816f2507172e53716a271d76aa7f4", "spec_path": "specs/verified/bench_boards.t27", "tests": { "failed": 0, "failing": [], "forced": false, - "passed": 19, - "total": 19, + "passed": 20, + "total": 20, "vacuous": 0 }, "toolchain": { - "test_runner": "sha256:1982ae948e24d00c5d221ccec12dfce1eb01204b53e37b0f857f93b16d64629d", + "test_runner": "sha256:8512b80da533b79289110fca4a65f3336abd7a059ca4b72ea595d5e33ff8ad10", "zig": "0.16.0" } } \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md index e77b42ea37..3b94c18ed4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -37,6 +37,12 @@ Supporting: [`CONTRIBUTING.md`](CONTRIBUTING.md), [`SECURITY.md`](SECURITY.md), Use **domain directories**, not “one folder per agent.” Primary contact for a path is the **Primary** listed in the nearest `OWNERS.md`. +- **Every path, spec, widget and app surface has its agents** (owner rule, 2026-10-20). A path maps + to one letter through the agents' `KEY_FILES` (`specs/t27os/domain_pulse.t27`); a widget or app + surface carries its owner letter plus the letters that feed it (`specs/t27os/agent_parts.t27`), + and the widget factory refuses a widget without them. Keep `KEY_FILES` pointing at the + directories a domain really changes: stale entries make the domain look idle. + --- ## 3. Non-negotiables for changes @@ -216,6 +222,41 @@ models). The spec says what the runner must do; it does not rewrite the runner. Of 45 numbers printed in three papers, 34 recompute, 8 do not, 3 cannot be checked. +### T27 OS: the instrument panel, and GOD MODE evolution + +T27 OS is the native window through which the project is worked on and watched: a spaceship +instrument panel, written in `.t27` and built by `t27b` (`specs/t27os/`, run recipe +`docs/t27os/run-local.md`). The image, the per-letter registry, the build traps and the plan live +in one place, the skill [`.claude/skills/t27os/SKILL.md`](.claude/skills/t27os/SKILL.md); this +section only states the rules. + +- **27 petals = 27 letters.** Petal index p is letter p + 1; the rings are the alphabet layers + (A-I, J-R, S-Ti). Petal 3 (C) holds the chat and the commander. A letter's tool t27 + leads where one exists (t27a, t27b, t27c, t27f frontend, t27o, t27t, t27u); never name a tool + before its artifact exists (`specs/compiler/theory/toolchain.t27`). +- **T27 OS is an ergonomic project-management system, 100% widgets** (owner, 2026-10-20: "our + signature"). Each letter is a tool t27 with its own page -- t27k the kanban board, t27c + the chat and commander, t27i issues, t27p pull requests and CI, t27q the Queen; the table is + `TOOL_NAMES` in `specs/t27os/agent_parts.t27`. Every pixel of every page belongs to a widget. +- **Everything is a widget; widgets contain widgets.** A letter's console holds its instruments: + its widgets and app surfaces, its specs, its tri commands, MCP tools and skills, its live + domain pulse. No text-only screens. +- **Every number shows where it came from**: measured (with the command), derived, or not + measured; a missing value prints "not measured", never 0 (`specs/t27os/bench.t27`). Every + fact about a domain is labelled CONFIRMED or GUESS (`domain_pulse.t27`). +- **GOD MODE evolves the system, inside the same laws.** With GOD MODE on, the Queen + (`specs/t27os/queen.t27`) picks the next gap from the panel itself -- a letter with no + instrument, a stale seal, a red check on a domain's path, an unowned path, a spec no widget + shows -- turns it into an issue with the 4-heading shape, starts one agent in a PTY pane on a + fresh lane worktree (Sonnet; at most three at once on an 8 GB Mac), and lands the result only + through the normal gates (`Closes #N`, tests, seals, Only t27, DRY). The decision rule is + `specs/t27os/evolve.t27`. GOD MODE never does what CLAUDE.md "Proactivity" forbids + (passwords, payments, secrets, public claims without a measurement, owner-only gate files); + those become requests to the owner. Cmd+J, Escape or the switch stops it; every action is in + the RECENT log on screen. +- **Two lanes edit `glass_window.t27`.** Merge master before every push to it; reseal + `specs/t27os/homes.t27` after master moves. + ### Hot reload and incremental work: only what changed is redone The rule: key every result on the content hash of its inputs and reuse it while @@ -284,6 +325,14 @@ Each gate is a spec. Read the spec, not a summary of it. --- +## Landing work: our git first, GitHub once per batch (owner rule, 2026-10-20, #11275) + +GitHub's Actions queue is the bottleneck (800-900 queued runs, #10644). A lane verifies locally first +(`t27b test`, `t27c test-report`, seal, gen), lands on the local golden head by compare-and-swap, and +GitHub receives one batched push per window, never one per commit; a red batch is bisected locally by +verdicts. The law is `specs/queen/golden_chain.t27` section 6; the practices, commands and traps are +the skill `own-git-landing`. + ## Only t27 (owner hard rule, 2026-10-05) Owner's rule, 2026-10-05: no new commit or pull request may contain code in diff --git a/cli/tri/src/main.rs b/cli/tri/src/main.rs index 2ce00f5de6..f6abee330c 100644 --- a/cli/tri/src/main.rs +++ b/cli/tri/src/main.rs @@ -54,6 +54,8 @@ mod raw; // t27c gen-rust of specs/analog/raw_writer.t27: every rule of `tri raw mod rj; // t27c gen-rust of specs/tri/remote/remote_job.t27: every command line of `tri remote` (#11020) #[path = "../../../gen/rust/tri/land/land.rs"] #[allow(dead_code, unused_parens, unused_mut, unused_variables, non_snake_case, unused_assignments, clippy::all)] mod land; // t27c gen-rust of specs/tri/land/land.t27: every rule and command line of `tri land` (#11068) +#[path = "../../../gen/rust/tri/land/golden_land.rs"] #[allow(dead_code, unused_parens, unused_mut, unused_variables, non_snake_case, unused_assignments, clippy::all)] +mod golden_land; // t27c gen-rust of specs/tri/land/golden_land.t27: the verbs and command lines of `tri golden` (#11275) #[path = "../../../gen/rust/t27os/release.rs"] #[allow(dead_code, unused_parens, unused_mut, unused_variables, non_snake_case, unused_assignments, clippy::all)] mod t27rel; // t27c gen-rust of specs/t27os/release.t27: the release plan and manifest text of `tri t27os` (#11315) #[path = "../../../bootstrap/gen/rust/verified/gen_hash_cache.rs"] #[allow(dead_code, unused_parens, unused_mut, unused_variables, non_snake_case, clippy::all)] @@ -455,6 +457,8 @@ enum Commands { T27oSweep { paths: Vec, #[arg(long, default_value = t27o_ledger::SWEEP_HERE)] repo: String, #[arg(long, default_value_t = t27o_ledger::SWEEP_MAX_BYTES)] max_bytes: usize, #[arg(long)] cache: Option, #[arg(long, default_value_t = t27o_ledger::SWEEP_TOP)] top: usize }, /// Land a lane branch (default: the current one): merge master, then tests with no vacuous pass, seals, types ratchet, dupe subset, push, pull request, auto-merge; the first failing gate stops it with a named code (CONFLICT, TESTS_NOT_CLEAN, SEAL_NOT_CLEAN, TYPES_NOT_CLEAN, DUPE_NOT_SUBSET, PUSH_NOT_CONFIRMED) and a second run prints ALREADY_LANDED; --plan prints the steps and runs none, --check stops before the commit; T27C names the t27c program (default t27c on PATH) (rules: specs/tri/land/land.t27, #11068). Land { branch: Option, #[arg(long, default_value_t = 0)] issue: u64, #[arg(long)] plan: bool, #[arg(long)] check: bool }, + /// The golden chain's front door: `land` verifies each queued claude/evolve-* branch locally and lands the ready ones on refs/golden/head by compare-and-swap (--plan writes nothing), `push` sends the unpushed links to GitHub as one `tri land` pull request when the window is due (--queued N overrides the Actions queue count, --issue N is the Closes #N), `status` prints head, queue and the next push time (rules: specs/queen/golden_chain.t27 section 6, hosted by specs/tri/land/golden_land.t27, #11275). + Golden { action: String, #[arg(long)] plan: bool, #[arg(long)] queued: Option, #[arg(long, default_value_t = 0)] issue: u64 }, /// T27 OS release front door: `release --plan` prints the numbered steps (build, zip, sha256, manifest, notes, `gh release create`) and runs none; `manifest --build N --sha H` prints the release manifest, a t27 module the app reads; anything that would publish is refused (rules: specs/t27os/release.t27, #11315). T27os { action: String, #[arg(long)] plan: bool, #[arg(long, default_value_t = 0)] build: u32, #[arg(long, default_value = "")] sha: String }, /// Coverage of a set of specs by five facts each, one row per spec and a total: the tests ran and none failed, no vacuous pass, a test named for a refusal or a planted fault (negative control), the seal verifies, and the header names an issue. PATTERN is a path or a glob (`specs/lab/*.t27`); the last line is `gaps G coverage N/D`, never rounded. Exit 0 once printed, 1 on a gap with --strict, 2 when no spec matched; --t27c names the t27c program (default target/release/t27c, then t27c on PATH) (rules: specs/tri/coverage/coverage.t27, #11061). @@ -1380,6 +1384,7 @@ fn main() -> Result<()> { Commands::Payout { epoch, pool, json } => std::process::exit(payout::cmd(*epoch, *pool, *json)? as i32), Commands::T27o { file, name, oracle, check, t27c, emit_cell } => std::process::exit(cmd_t27o(file, name.as_deref(), *oracle, *check, t27c.as_deref(), *emit_cell)? as i32), Commands::T27oSweep { paths, repo, max_bytes, cache, top } => std::process::exit(cmd_t27o_sweep(paths, repo, *max_bytes, cache.as_deref(), *top)? as i32), + Commands::Golden { action, plan, queued, issue } => std::process::exit(cmd_golden(action, *plan, *queued, *issue)? as i32), Commands::T27os { action, plan, build, sha } => std::process::exit(cmd_t27os(action, *plan, *build, sha)), Commands::Land { branch, issue, plan, check } => std::process::exit(cmd_land(branch.as_deref(), *issue, *plan, *check)? as i32), Commands::Coverage { patterns, strict, t27c } => std::process::exit(seals::coverage_run(patterns, *strict, t27c.as_deref())?), @@ -1539,6 +1544,38 @@ fn cmd_land(branch: Option<&str>, issue: u64, plan: bool, check: bool) -> Result Ok(land::exit_code(code)) } +/// `tri golden land|push|status` (#11275): every rule is specs/tri/land/golden_land.t27 over golden_chain.t27; this runs the slot it names next as an argument vector (no shell), appends the run as evidence and stops where the spec says. +fn cmd_golden(action: &str, plan: bool, queued: Option, issue: u64) -> Result { + use golden_land as g; + let leak = |s: String| -> &'static str { s.leak() }; + let env = |k: &str, d: &str| leak(std::env::var(k).unwrap_or(d.into())); + let r = g::GolReq { verb: g::verb_of(leak(action.into())), plan, queued: queued.unwrap_or(g::ABSENT), issue, scratch: leak(std::env::temp_dir().join(format!("tri-golden-{}", std::process::id())).display().to_string()), t27b: env("T27B", "t27b"), t27c: env("T27C", "t27c"), tri: leak(std::env::current_exe()?.display().to_string()) }; + let (mut ev, mut buf, mut code, mut wo, mut wp) = ("", vec![0u8; g::CMD_MAX], g::request_refusal(r), vec![0u8; 16], vec![0usize; 16]); + let run = |b: &[u8], dir: &str| { let a: Vec = String::from_utf8_lossy(b).lines().map(str::to_string).collect(); match Command::new(&a[0]).args(&a[1..]).current_dir(dir).output() { Ok(o) => (o.status.code().unwrap_or(255) as u64, format!("{}{}", String::from_utf8_lossy(&o.stdout), String::from_utf8_lossy(&o.stderr))), Err(e) => (127, e.to_string()) } }; + if code == g::C_OK { std::fs::create_dir_all(r.scratch)?; } + while code == g::C_OK { + let s = g::next_slot(r, ev, &mut wo, &mut wp); + if s >= g::DONE { break; } + let (k, n) = ((s / g::SLOT) as u8, s % g::SLOT); + let m = g::step_argv(r, ev, k, n, &mut buf, &mut wo, &mut wp); + anyhow::ensure!(m > 0, "tri golden: step {k} does not fit"); + let argv = buf[..m].to_vec(); + let c = g::step_cwd(r, k, n, &mut buf); + let (x, text) = run(&argv, if c > 0 { leak(String::from_utf8_lossy(&buf[..c]).into()) } else { "." }); + let (text, mut sec) = (leak(text.clone()), vec![0u8; text.len() + 128]); + let w = g::emit_section(&mut sec, 0, k, n, x, text); + ev = leak(format!("{ev}{}", String::from_utf8_lossy(&sec[..w]))); + code = g::judge(r, ev, k, n); + eprintln!("golden {k}.{n} exit {x}"); + } + let m = g::step_argv(r, ev, g::G_PRUNE, 0, &mut buf, &mut wo, &mut wp); + if m > 0 { let _ = std::fs::remove_dir_all(r.scratch); run(&buf[..m].to_vec(), "."); } + let mut out = vec![0u8; 16384]; + let w = g::report(r, ev, code, &mut out, &mut wo, &mut wp, &mut buf); + print!("{}", String::from_utf8_lossy(&out[..w])); + Ok(g::exit_code(code)) +} + /// `tri t27os release --plan` | `manifest` (#11315): the text is specs/t27os/release.t27; this prints it and publishes nothing. fn cmd_t27os(action: &str, plan: bool, build: u32, sha: &str) -> i32 { let (mut buf, v) = (vec![0u8; 4096], t27rel::current_version()); diff --git a/docs/agents/AGENTS_ALPHABET.md b/docs/agents/AGENTS_ALPHABET.md index 2aca975e92..09b7ca6eaa 100644 --- a/docs/agents/AGENTS_ALPHABET.md +++ b/docs/agents/AGENTS_ALPHABET.md @@ -119,33 +119,33 @@ Any major operation (NUMERIC-STANDARD-001, SACRED-PHYSICS-001, De-Zig, GoldenFlo | Agent | Letter | Domain (core) | Archetype | Key files | Entry invariant | Exit invariant | AR role | |-------|--------|---------------|----------|-----------|-----------------|----------------|------------| -| **A** | Alpha α | Architecture / ADR / SOUL | Bull — leader, primary force | `SOUL.md`, `architecture/ADR-*.md` | `SOUL.md` exists and is ASCII-only | All ADRs reviewed and consistent | TA1: AR Architecture Design | -| **B** | Beta β | Build / Pipeline | House — container, dwelling | `build.tri`, `src/tri/pipeline/` | Build system is clean | All tests pass in pipeline | — | -| **C** | Gamma γ | Compiler Core | Camel — carrier across borders | `t27/compiler/parser/`, `specs/ar/*.t27` | Parser can handle all AR specs | Generated code compiles | TA1: AR Language Implementation | -| **D** | Delta δ | De-Zigfication | Door — transition between worlds | `docs/migration-map.md`, `specs/ar/*.t27` | Legacy Zig modules mapped | All AR specs in .t27 format | — | -| **E** | Epsilon ε | Experience / Mistakes | Window — view into the past | `.trinity/experience/`, `specs/ar/explainability.t27` | Episode log is append-only | Lessons learned catalogued | TA2: AR Explanation & XAI | -| **F** | Phi φ | Formal Conformance | Nail — connection, binding | `t27/conformance/*.json` | All sacred vectors valid | Conformance > 95% | — | -| **G** | Gamma (var.) | Graph / ArchBench | Return — feedback | `architecture/graph_v2.json` | Graph is acyclic | All edges satisfied | — | -| **H** | Eta η | HSLM / NN Architectures | Fence — boundary, life | `t27/specs/nn/hslm.t27` | HSLM layers defined | Attention traces validated | — | -| **I** | Iota ι | ISA / Registers | Hand — action, point | `t27/specs/isa/registers.t27` | 27 registers defined | Coptic mapping verified | — | -| **J** | Iota‑extended | Jobs / Task Routing | Hand with grip — dispatcher | `src/tri/dev_commands.zig` | Task queue exists | No stuck jobs > 1 hour | — | -| **K** | Kappa κ | Kernel / FPGA MAC | Palm — open hand | `t27/specs/fpga/mac.t27` | MAC spec is zero-DSP | Synthesis verified | — | -| **L** | Lambda λ | Language / Syntax vNEXT | Staff — teacher, guide | `docs/nona-02-organism/TRI_SYNTAX_VNEXT.md` | Syntax vNEXT documented | Parser implements vNEXT | — | -| **M** | Mu μ | Metrics / Telemetry | Water — flow of data | `.trinity/bench/` | Bench baseline exists | No regression > 5% | — | -| **N** | Nu ν | Numeric / GoldenFloat Family | Fish — offspring, multiplication | `t27/specs/numeric/` | GF family defined | φ identity validated | — | -| **O** | Omicron ο | Orchestration / Phases | Eye — all-seeing oko | `src/tri/pipeline/`, `specs/ar/composition.t27` | All phases defined | Orchestration completes | TA2: AR Composition Engine | -| **P** | Pi π | Physics / SacredPhysics | Mouth — speech of universe | `t27/specs/math/sacred_physics.t27` | φ² + φ⁻² = 3 holds | Sacred constants verified | TA1: AR Theoretical Foundations | -| **Q** | Theta θ | Queue / Scheduling | Needle's eye — bottleneck | `src/tri/dev_commands.zig` | Queue is not empty | MNL pattern enforced | — | -| **R** | Rho ρ | Runtime | Head — beginning of execution | `t27/compiler/runtime/` | Runtime initialized | No memory leaks | — | -| **S** | Sigma σ | Specs / Standardization | Teeth — sharpness, flame | `specs/`, `specs/ar/*.t27`, `docs/NUMERIC-*.md` | All specs have TDD | Standards are consistent | TA1: AR Spec Standards | -| **T** | Tau τ | TRINITY Queen / Lotus | CROSS — seal, signature, truth | `t27/specs/queen/lotus.t27` | Queen health >= 0.9 | All rings sealed | TA2: AR Orchestrator | -| **U** | Upsilon υ | Universe Levels / Domains | Fork — branching | `t27/domains/` | Domain boundaries defined | No circular dependencies | — | -| **V** | Vau (var.) | Verdict / Bench | Hook — connection, conjunction | `src/tri/verdict.zig`, `specs/ar/proof_trace.t27` | Verdict engine ready | Toxicity detected | TA2: AR Validation & Proof | -| **W** | Double‑Vav | Workflow / tri cell | Double hook — double seal | `src/tri/cell.zig` | Cell is sealed | Hash verified | — | -| **X** | Chi χ | eXternal Bindings / Interop | Intersection — point of exchange | `bindings/` | Bindings documented | Interop tests pass | — | -| **Y** | Psi ψ | Yield / DePIN / Fitness | Merging paths — evolutionary selection | `deploy/contracts/` | DePIN nodes defined | Fitness score > 0.8 | — | -| **Z** | Zeta ζ | Zero‑Touch UX / Docs | Sword — cutting edge, point | `docs/` | All docs in English | DX score > 4/5 | — | -| **27th** | Ti Ϯ | Reserve / Security | Egyptian cross — "sacred gift" | — | Security policy exists | AAIF compliance ready | — | +| **A** | Alpha α | Architecture / ADR / SOUL | Bull — leader, primary force | `SOUL.md`, `AGENTS.md`, `CLAUDE.md`, `architecture/ADR-*.md`, `architecture/CANON_DE_ZIGFICATION.md`, `docs/T27-CONSTITUTION.md`, `specs/agents/` | `SOUL.md` exists and is ASCII-only | All ADRs reviewed and consistent | TA1: AR Architecture Design | +| **B** | Beta β | Build / Pipeline | House — container, dwelling | `.github/workflows/`, `.trinity/seals/`, `scripts/`, `tools/`, `tools/policy/`, `specs/claw/`, `specs/ci/`, `specs/pipeline/`, `specs/policy/`, `Cargo.toml`, `Cargo.lock`, `.trinity/` | Build system is clean | All tests pass in pipeline | — | +| **C** | Gamma γ | Compiler Core | Camel — carrier across borders | `bootstrap/src/`, `bootstrap/tests/`, `bootstrap/gen/`, `bootstrap/src/compiler.rs`, `specs/compiler/`, `gen/rust/`, `gen/c/`, `specs/ar/*.t27` | Parser can handle all AR specs | Generated code compiles | TA1: AR Language Implementation | +| **D** | Delta δ | De-Zigfication | Door — transition between worlds | `specs/port/`, `specs/ar/*.t27` | Legacy Zig modules mapped | All AR specs in .t27 format | — | +| **E** | Epsilon ε | Experience / Mistakes | Window — view into the past | `.trinity/experience/`, `docs/now/`, `specs/experience/`, `specs/memory/`, `specs/brain/`, `specs/ar/explainability.t27` | Episode log is append-only | Lessons learned catalogued | TA2: AR Explanation & XAI | +| **F** | Phi φ | Formal Conformance | Nail — connection, binding | `conformance/`, `conformance/*.json`, `conformance/FORMAT-SPEC-001.json`, `specs/conformance/`, `specs/test_framework/` | All sacred vectors valid | Conformance > 95% | — | +| **G** | Gamma (var.) | Graph / ArchBench | Return — feedback | `architecture/graph_v2.json`, `architecture/graph.tri`, `specs/graph/` | Graph is acyclic | All edges satisfied | — | +| **H** | Eta η | HSLM / NN Architectures | Fence — boundary, life | `specs/ml/`, `specs/nn/`, `specs/hslm/`, `specs/igla/`, `specs/neural/`, `specs/dataset/`, `specs/nn/hslm.t27`, `specs/nn/attention.t27` | HSLM layers defined | Attention traces validated | — | +| **I** | Iota ι | ISA / Registers | Hand — action, point | `specs/isa/`, `specs/isa/registers.t27` | 27 registers defined | Coptic mapping verified | — | +| **J** | Iota‑extended | Jobs / Task Routing | Hand with grip — dispatcher | `specs/automation/`, `specs/functions/`, `specs/jobs/` | Task queue exists | No stuck jobs > 1 hour | — | +| **K** | Kappa κ | Kernel / FPGA MAC | Palm — open hand | `cli/t27b/`, `specs/tri/t27b/`, `specs/fpga/`, `fpga/`, `gen/verilog/`, `tools/jtag/`, `specs/xilinx7/`, `specs/math/e8_lie_algebra.t27`, `tests/fixtures/t27b-fuzz/` | MAC spec is zero-DSP | Synthesis verified | — | +| **L** | Lambda λ | Language / Syntax vNEXT | Staff — teacher, guide | `specs/tri/`, `specs/basics/`, `specs/tutorial/`, `specs/lsp/`, `docs/nona-02-organism/TRI_SYNTAX_VNEXT.md` | Syntax vNEXT documented | Parser implements vNEXT | — | +| **M** | Mu μ | Metrics / Telemetry | Water — flow of data | `docs/reports/`, `tools/census/`, `specs/benchmarks/`, `benchmarks/`, `bench/`, `docs/qualification/TVP.md` | Bench baseline exists | No regression > 5% | — | +| **N** | Nu ν | Numeric / GoldenFloat Family | Fish — offspring, multiplication | `specs/numeric/` | GF family defined | φ identity validated | — | +| **O** | Omicron ο | Orchestration / Phases | Eye — all-seeing oko | `cli/tri/`, `specs/tools/tri/`, `specs/rings/`, `specs/ar/composition.t27` | All phases defined | Orchestration completes | TA2: AR Composition Engine | +| **P** | Pi π | Physics / SacredPhysics | Mouth — speech of universe | `specs/physics/`, `specs/math/`, `specs/analog/`, `specs/sacred/`, `specs/math/sacred_physics.t27`, `specs/physics/su2_chern_simons.t27` | φ² + φ⁻² = 3 holds | Sacred constants verified | TA1: AR Theoretical Foundations | +| **Q** | Theta θ | Queue / Scheduling | Needle's eye — bottleneck | `specs/crons/` | Queue is not empty | MNL pattern enforced | — | +| **R** | Rho ρ | Runtime | Head — beginning of execution | `bootstrap/stage0/`, `bootstrap/`, `specs/runtime/`, `specs/vm/`, `specs/jit/`, `bootstrap/src/compiler.rs`, `specs/effects/`, `specs/base/` | Runtime initialized | No memory leaks | — | +| **S** | Sigma σ | Specs / Standardization | Teeth — sharpness, flame | `specs/`, `specs/ar/*.t27` | All specs have TDD | Standards are consistent | TA1: AR Spec Standards | +| **T** | Tau τ | TRINITY Queen / Lotus | CROSS — seal, signature, truth | `specs/queen/`, `tools/queen/`, `tools/bees/`, `specs/queen/lotus.t27` | Queen health >= 0.9 | All rings sealed | TA2: AR Orchestrator | +| **U** | Upsilon υ | Universe Levels / Domains | Fork — branching | `specs/ternary/`, `specs/scenes/`, `specs/vsa/` | Domain boundaries defined | No circular dependencies | — | +| **V** | Vau (var.) | Verdict / Bench | Hook — connection, conjunction | `specs/verified/`, `specs/ar/proof_trace.t27`, `specs/ar/restraint.t27`, `.trinity/receipts/` | Verdict engine ready | Toxicity detected | TA2: AR Validation & Proof | +| **W** | Double‑Vav | Workflow / tri cell | Double hook — double seal | `.claude/skills/`, `specs/skills/`, `specs/shell/`, `.claude/agents/` | Cell is sealed | Hash verified | — | +| **X** | Chi χ | eXternal Bindings / Interop | Intersection — point of exchange | `bindings/`, `specs/tools/mcp/`, `specs/tools/trinity/`, `specs/mcp/`, `specs/t27mcp/`, `specs/api/`, `specs/interop/`, `specs/providers/`, `specs/provider/`, `specs/server/`, `specs/trinity/` | Bindings documented | Interop tests pass | — | +| **Y** | Psi ψ | Yield / DePIN / Fitness | Merging paths — evolutionary selection | `specs/depin/`, `specs/hosting/`, `specs/network/`, `specs/trinet/` | DePIN nodes defined | Fitness score > 0.8 | — | +| **Z** | Zeta ζ | Zero‑Touch UX / Docs | Sword — cutting edge, point | `specs/t27os/`, `specs/t27f/`, `specs/widgets/`, `specs/ui/`, `specs/tools/t27o/`, `specs/i18n/`, `docs/`, `specs/docs/` | All docs in English | DX score > 4/5 | — | +| **27th** | Ti Ϯ | Reserve / Security | Egyptian cross — "sacred gift" | `specs/sandbox/`, `specs/vault/` | Security policy exists | AAIF compliance ready | — | --- diff --git a/fpga/HARDWARE_SSOT.md b/fpga/HARDWARE_SSOT.md index 82a4e6d4d6..19162ea4b4 100644 --- a/fpga/HARDWARE_SSOT.md +++ b/fpga/HARDWARE_SSOT.md @@ -10,6 +10,42 @@ --- +## 2026-10-20 - a Zynq-7000 XC7Z020 is on a second cable; the AX7203 cable shows an empty chain + +Measured by the orchestrator on this Mac (openFPGALoader from /opt/homebrew/bin), unix time +1792488364 (recorded when the lines were entered). Every row below is a command and its output. + +| Command | Output | +|---|---| +| `openFPGALoader --scan-usb` | `000 004 0x0403:0x6014 ft232H Digilent 210512180081 Digilent USB Device` and `000 002 0x0403:0x6010 FTDI2232 Digilent 210203859289 Digilent Adept USB Device` | +| `openFPGALoader -c digilent --busdev-num 0:2 --detect` (same with `-c digilent_hs2`) | index 0: idcode `0x4ba00477`, type ARM cortex A9, irlength 4; index 1: idcode `0x3727093` | +| `openFPGALoader -c digilent_hs2 --busdev-num 0:4 --detect` | an empty chain (only `Jtag frequency : requested 6.00MHz -> real 6.00MHz`) | + +Serial ports: `/dev/cu.usbserial-130`, `/dev/cu.usbserial-2102038592890`, +`/dev/cu.usbserial-2102038592891`, `/dev/cu.usbserial-210512180081`. + +- **New board, measured:** a Zynq-7000 XC7Z020. PL IDCODE `0x03727093` (version nibble masked), ARM DAP + `0x4ba00477`, chain from TDI: ARM first, PL second. Cable: Digilent Adept FTDI2232 `0x0403:0x6010`, + serial `210203859289`, at **busdev 0:2** (`-c digilent` and `-c digilent_hs2` both answer). Its UARTs are + `/dev/cu.usbserial-2102038592890` and `-2102038592891` (the cable's two FTDI channels; which channel + carries a design's UART is not established). +- **The AX7203 cable shows an EMPTY chain.** HS2 cable `210512180081` (FT232H) is at **busdev 0:4** and + `--detect` lists no device. Board unpowered or board disconnected: **not measured which**; the cable + itself is seen by `--scan-usb`. One cable is listed now, not three: the earlier 1:6 and 1:8 are not in the scan. +- **Bus numbering moved from 1 to 0** since this file's busdev tables were written: `1:4 / 1:6 / 1:8` + now answer "device not found". A busdev is a value the host reads from `--scan-usb` each time, never a + constant (`specs/t27os/bench.t27`, `bench_busdev_*`). +- **Board model is a GUESS.** An XC7Z020 in clg400-1 sits on the Arty Z7-20, PYNQ-Z1, Zybo Z7-20 and the + PZSDR P201/P203 Mini (die D, `specs/verified/bench_boards.t27` row `pzsdr-d`, #8635); JTAG cannot tell them + apart, and the package `xc7z020clg400-1` is assumed, not read. The same cable serial `210203859289` + and the same PL part are die D's of 2026-10-10, so this is most likely die D again; that is not proven. + What would establish it: a photo of the PCB marking through the camera (skill board-eye), or the cable's + own USB product string / EEPROM read with `openFPGALoader --scan-usb` verbose or `ioreg`. +- **Open discrepancy:** the 2026-10-10 die D read recorded the full IDCODE `0x23727093` (version 2); today's + `--detect` prints `0x3727093` (version 0). `idcode_is_row` masks the version, so the row still matches, but + the reader's `-expected-id` (`tap_idcode`) uses version 2. Either the silicon revision differs (a different + board behind the same cable serial) or one of the two transcriptions dropped the nibble. Not resolved. + ## 2026-10-10 - the TRI-NET node answers jobs over UDP on the AX7203 What ran: EthRx, TrinetNode and EthReply (`specs/fpga/eth_rx.t27`, `trinet_node.t27`, diff --git a/gen/rust/tri/land/golden_land.rs b/gen/rust/tri/land/golden_land.rs new file mode 100644 index 0000000000..722fdd1581 --- /dev/null +++ b/gen/rust/tri/land/golden_land.rs @@ -0,0 +1,3013 @@ +// Generated from .t27 spec +// DO NOT EDIT — generated by t27c +// NOT LOWERED BY THIS BACKEND: 28 test(s), 1 invariant(s), 0 function(s) with no body. +// This backend emits declarations only. The spec's checks live in +// the Zig and Verilog outputs; do not read this file as verified. + +pub const GL_Q_MAX: usize = 16; + +pub const GL_E_MAX: usize = 32; + +pub const CMD_MAX: usize = 4096; + +pub const SLOT: u64 = 100000; + +pub const DONE: u64 = 99999999999; + +pub const ABSENT: u64 = 1000000000000000000; + +pub const TGT: u64 = 1000; + +pub const GL_PUSHED: &'static str = "refs/golden/pushed"; + +pub const GL_QUEUE_FILE: &'static str = ".trinity/golden/queue"; + +pub const G_REFS: u8 = 0; + +pub const G_FILE: u8 = 1; + +pub const G_HEAD: u8 = 2; + +pub const G_BASE: u8 = 3; + +pub const G_TIP: u8 = 4; + +pub const G_MB: u8 = 5; + +pub const G_CHG: u8 = 6; + +pub const G_LOG: u8 = 7; + +pub const G_WT: u8 = 8; + +pub const G_SEAL: u8 = 9; + +pub const G_TEST: u8 = 10; + +pub const G_RM: u8 = 11; + +pub const G_MT: u8 = 12; + +pub const G_CT: u8 = 13; + +pub const G_CAS: u8 = 14; + +pub const P_PUSHED: u8 = 15; + +pub const P_AHEAD: u8 = 16; + +pub const P_STAMP: u8 = 17; + +pub const P_NOW: u8 = 18; + +pub const P_QUEUED: u8 = 19; + +pub const P_BRANCH: u8 = 20; + +pub const P_LAND: u8 = 21; + +pub const P_MARK: u8 = 22; + +pub const G_PRUNE: u8 = 23; + +pub const R_ANC: u8 = 24; + +pub const R_MT: u8 = 25; + +pub const R_CT: u8 = 26; + +pub const R_UH: u8 = 27; + +pub const R_UP: u8 = 28; + +pub const S_SAVE: u8 = 29; + +pub const S_RECHECK: u8 = 30; + +pub const S_STAGE: u8 = 31; + +pub const S_DIFF: u8 = 32; + +pub const S_COMMIT: u8 = 33; + +pub const S_TIP: u8 = 34; + +pub const GL_STEPS: u8 = 35; + +pub const SEAL_DIR: &'static str = ".trinity/seals/"; + +pub const V_LAND: u8 = 0; + +pub const V_PUSH: u8 = 1; + +pub const V_STATUS: u8 = 2; + +pub const V_BAD: u8 = 3; + +pub const C_OK: u8 = 0; + +pub const C_BAD_VERB: u8 = 1; + +pub const C_BAD_ISSUE: u8 = 2; + +pub const C_BAD_PATH: u8 = 3; + +pub const C_NO_BASE: u8 = 4; + +pub const C_MERGE: u8 = 5; + +pub const C_STEP: u8 = 6; + +pub const C_HEAD_MOVED: u8 = 7; + +pub const C_LAND_REFUSED: u8 = 8; + +pub const C_ODDS: u8 = 9; + +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct GolReq { + pub verb: u8, + pub plan: bool, + pub queued: u64, + pub issue: u64, + pub scratch: &'static str, + pub t27b: &'static str, + pub t27c: &'static str, + pub tri: &'static str, +} + +pub fn verb_of(a: &'static str) -> u8 { + if span_is(a, 0, a.len(), "land") { + return V_LAND; + } + if span_is(a, 0, a.len(), "push") { + return V_PUSH; + } + if span_is(a, 0, a.len(), "status") { + return V_STATUS; + } + return V_BAD; +} + +pub fn request_refusal(r: GolReq) -> u8 { + if (r.verb >= V_BAD) { + return C_BAD_VERB; + } + if (((r.verb == V_PUSH) && (r.plan == false)) && ((r.issue == 0) || (r.issue > 999999999))) { + return C_BAD_ISSUE; + } + if ((((safe_path(r.scratch) == false) || (safe_path(r.t27b) == false)) || (safe_path(r.t27c) == false)) || (safe_path(r.tri) == false)) { + return C_BAD_PATH; + } + return C_OK; +} + +pub fn ran(ev: &'static str, k: u8, n: u64) -> bool { + return (exit_of(ev, k, n) != NO_EXIT); +} + +pub fn is_hex(b: u8) -> bool { + return (((b >= 48) && (b <= 57)) || ((b >= 97) && (b <= 102))); +} + +pub fn sha_ok(ev: &'static str, k: u8, n: u64) -> bool { + if (exit_of(ev, k, n) != 0) { + return false; + } + let f: usize = body_from(ev, k, n); + if (body_to(ev, k, n) < (f + 40)) { + return false; + } + let mut i: usize = 0; + while (i < 40) { + if (is_hex(ev.as_bytes()[((f + i)) as usize]) == false) { + return false; + } + i = (i + 1); + } + return true; +} + +pub fn hex15(ev: &'static str, f: usize) -> u64 { + let mut v: u64 = 0; + let mut i: usize = 0; + while (i < 15) { + let mut c: u8 = ev.as_bytes()[((f + i)) as usize]; + let mut d: u64 = ((c as u64) - 48); + if (c >= 97) { + d = ((c as u64) - 87); + } + v = ((v * 16) + d); + i = (i + 1); + } + return v; +} + +pub fn id_of(ev: &'static str, k: u8, n: u64) -> u64 { + if (sha_ok(ev, k, n) == false) { + return 0; + } + return hex15(ev, body_from(ev, k, n)); +} + +pub fn same_sha(ev: &'static str, a: u8, b: u8) -> bool { + let mut i: usize = 0; + while (i < 40) { + if (ev.as_bytes()[((body_from(ev, a, 0) + i)) as usize] != ev.as_bytes()[((body_from(ev, b, 0) + i)) as usize]) { + return false; + } + i = (i + 1); + } + return true; +} + +pub fn rb_action(ev: &'static str) -> u8 { + if (((sha_ok(ev, G_HEAD, 0) == false) || (sha_ok(ev, P_PUSHED, 0) == false)) || (ran(ev, R_ANC, 0) == false)) { + return GC_RB_NONE; + } + return gc_rebase(true, true, same_sha(ev, G_HEAD, P_PUSHED), (exit_of(ev, R_ANC, 0) != 1)); +} + +pub fn head_off(ev: &'static str) -> usize { + let a: u8 = rb_action(ev); + if (a == GC_RB_RESET) { + return body_from(ev, G_BASE, 0); + } + if ((a == GC_RB_CARRY) && sha_ok(ev, R_CT, 0)) { + return body_from(ev, R_CT, 0); + } + if sha_ok(ev, G_HEAD, 0) { + return body_from(ev, G_HEAD, 0); + } + return ev.len(); +} + +pub fn pushed_off(ev: &'static str) -> usize { + if (rb_action(ev) != GC_RB_NONE) { + return body_from(ev, G_BASE, 0); + } + if sha_ok(ev, P_PUSHED, 0) { + return body_from(ev, P_PUSHED, 0); + } + return ev.len(); +} + +pub fn has_head(ev: &'static str) -> bool { + return (head_off(ev) < ev.len()); +} + +pub fn has_pushed(ev: &'static str) -> bool { + return (pushed_off(ev) < ev.len()); +} + +pub fn cur0(ev: &'static str) -> usize { + if has_head(ev) { + return head_off(ev); + } + return body_from(ev, G_BASE, 0); +} + +pub fn starts(t: &'static str, a: usize, b: usize, lit: &'static str) -> bool { + return ((b >= (a + lit.len())) && span_is(t, a, (a + lit.len()), lit)); +} + +pub fn ends(t: &'static str, a: usize, b: usize, lit: &'static str) -> bool { + return ((b >= (a + lit.len())) && span_is(t, (b - lit.len()), b, lit)); +} + +pub fn name_byte(b: u8) -> bool { + return ((((((((b >= 97) && (b <= 122)) || ((b >= 65) && (b <= 90))) || ((b >= 48) && (b <= 57))) || (b == 46)) || (b == 95)) || (b == 47)) || (b == 45)); +} + +pub fn name_ok(t: &'static str, a: usize, b: usize) -> bool { + if ((b <= a) || ((b - a) > 100)) { + return false; + } + if ((t.as_bytes()[(a) as usize] == 45) || (t.as_bytes()[(a) as usize] == 46)) { + return false; + } + if ((span_is(t, a, b, "master") || span_is(t, a, b, "main")) || span_is(t, a, b, "HEAD")) { + return false; + } + let mut k: usize = a; + while (k < b) { + if (name_byte(t.as_bytes()[(k) as usize]) == false) { + return false; + } + if ((((k + 1) < b) && (t.as_bytes()[(k) as usize] == 46)) && (t.as_bytes()[((k + 1)) as usize] == 46)) { + return false; + } + k = (k + 1); + } + return true; +} + +pub fn line_in(ev: &'static str, a: usize, b: usize, f: usize, t: usize) -> bool { + let mut p: usize = f; + while (p < t) { + let e: usize = line_end(ev, p); + if (((e - p) == (b - a)) && (span_is(ev, p, e, "") == false)) { + let mut k: usize = 0; + while ((k < (b - a)) && (ev.as_bytes()[((p + k)) as usize] == ev.as_bytes()[((a + k)) as usize])) { + k = (k + 1); + } + if (k == (b - a)) { + return true; + } + } + p = (e + 1); + } + return false; +} + +pub fn q_start(ev: &'static str, i: usize) -> usize { + let mut c: usize = 0; + let mut p: usize = body_from(ev, G_REFS, 0); + let t: usize = body_to(ev, G_REFS, 0); + while (p < t) { + let e: usize = line_end(ev, p); + if name_ok(ev, p, e) { + if (c == i) { + return p; + } + c = (c + 1); + } + p = (e + 1); + } + if (exit_of(ev, G_FILE, 0) != 0) { + return ev.len(); + } + p = body_from(ev, G_FILE, 0); + let ft: usize = body_to(ev, G_FILE, 0); + while (p < ft) { + let e: usize = line_end(ev, p); + if (name_ok(ev, p, e) && (line_in(ev, p, e, body_from(ev, G_REFS, 0), t) == false)) { + if (c == i) { + return p; + } + c = (c + 1); + } + p = (e + 1); + } + return ev.len(); +} + +pub fn q_count(ev: &'static str) -> usize { + let mut c: usize = 0; + while ((c < GL_Q_MAX) && (q_start(ev, c) < ev.len())) { + c = (c + 1); + } + return c; +} + +pub fn ch_line(ev: &'static str, n: u64, i: usize) -> usize { + let mut p: usize = body_from(ev, G_CHG, n); + let t: usize = body_to(ev, G_CHG, n); + let mut c: usize = 0; + while (p < t) { + if (c == i) { + return p; + } + p = (line_end(ev, p) + 1); + c = (c + 1); + } + return ev.len(); +} + +pub fn ch_count(ev: &'static str, n: u64) -> usize { + let mut c: usize = 0; + while (ch_line(ev, n, c) < ev.len()) { + c = (c + 1); + } + return c; +} + +pub fn path_ok(ev: &'static str, ls: usize) -> bool { + let e: usize = line_end(ev, ls); + if ((e < (ls + 3)) || (ev.as_bytes()[((ls + 1)) as usize] != 9)) { + return false; + } + if (ev.as_bytes()[((ls + 2)) as usize] == 45) { + return false; + } + let mut k: usize = (ls + 2); + let seal: bool = starts(ev, (ls + 2), e, SEAL_DIR); + while (k < e) { + if (((name_byte(ev.as_bytes()[(k) as usize]) == false) && (ev.as_bytes()[(k) as usize] != 43)) && ((seal == false) || (ev.as_bytes()[(k) as usize] != 58))) { + return false; + } + if ((((k + 1) < e) && (ev.as_bytes()[(k) as usize] == 46)) && (ev.as_bytes()[((k + 1)) as usize] == 46)) { + return false; + } + k = (k + 1); + } + return true; +} + +pub fn is_target(ev: &'static str, ls: usize) -> bool { + if (ls >= ev.len()) { + return false; + } + let e: usize = line_end(ev, ls); + if ((ev.as_bytes()[(ls) as usize] != 65) && (ev.as_bytes()[(ls) as usize] != 77)) { + return false; + } + return (((path_ok(ev, ls) && starts(ev, (ls + 2), e, "specs/")) && ends(ev, (ls + 2), e, ".t27")) && (starts(ev, (ls + 2), e, "specs/scratch/") == false)); +} + +pub fn is_derived(ev: &'static str, ls: usize) -> bool { + let e: usize = line_end(ev, ls); + return (starts(ev, (ls + 2), e, "gen/") || starts(ev, (ls + 2), e, ".trinity/seals/")); +} + +pub fn targets(ev: &'static str, n: u64) -> usize { + let mut c: usize = 0; + let mut i: usize = 0; + let m: usize = ch_count(ev, n); + while (i < m) { + if is_target(ev, ch_line(ev, n, i)) { + c = (c + 1); + } + i = (i + 1); + } + return c; +} + +pub fn spec_ok(ev: &'static str, n: u64) -> bool { + if (((sha_ok(ev, G_TIP, n) == false) || (sha_ok(ev, G_MB, n) == false)) || (exit_of(ev, G_CHG, n) != 0)) { + return false; + } + let m: usize = ch_count(ev, n); + if ((m == 0) || (m > GL_E_MAX)) { + return false; + } + let mut i: usize = 0; + while (i < m) { + if (path_ok(ev, ch_line(ev, n, i)) == false) { + return false; + } + i = (i + 1); + } + return true; +} + +pub fn gen_ok(ev: &'static str, n: u64) -> bool { + let m: usize = ch_count(ev, n); + let mut derived: bool = false; + let mut i: usize = 0; + while (i < m) { + let ls: usize = ch_line(ev, n, i); + if starts(ev, (ls + 2), line_end(ev, ls), "gen/") { + derived = true; + } + i = (i + 1); + } + return ((derived == false) || (targets(ev, n) > 0)); +} + +pub fn seal_clean(ev: &'static str, k: u8, n: u64) -> bool { + let f: usize = body_from(ev, k, n); + let t: usize = body_to(ev, k, n); + return (((exit_of(ev, k, n) == 0) && (find_in(ev, f, t, "all hashes MATCH") <= t)) && (find_in(ev, f, t, "VERIFICATION FAILED") > t)); +} + +pub fn seal_state(ev: &'static str, n: u64) -> u8 { + if (ran(ev, G_SEAL, n) == false) { + return 1; + } + if seal_clean(ev, G_SEAL, n) { + return 0; + } + if (ran(ev, S_RECHECK, n) == false) { + return 1; + } + if seal_clean(ev, S_RECHECK, n) { + return 0; + } + return 2; +} + +pub fn saved_any(ev: &'static str, link: u64) -> bool { + let m: usize = ch_count(ev, link); + let mut i: usize = 0; + while (i < m) { + if (is_target(ev, ch_line(ev, link, i)) && ran(ev, S_SAVE, ((link * TGT) + (i as u64)))) { + return true; + } + i = (i + 1); + } + return false; +} + +pub fn diff_changed(ev: &'static str, link: u64) -> usize { + return lines_in(ev, body_from(ev, S_DIFF, (link * TGT)), body_to(ev, S_DIFF, (link * TGT))); +} + +pub fn diff_seals(ev: &'static str, link: u64) -> usize { + let mut c: usize = 0; + let mut p: usize = body_from(ev, S_DIFF, (link * TGT)); + let t: usize = body_to(ev, S_DIFF, (link * TGT)); + while (p < t) { + let e: usize = line_end(ev, p); + if ((e > p) && starts(ev, p, e, SEAL_DIR)) { + c = (c + 1); + } + p = (e + 1); + } + return c; +} + +pub fn diff_ok(ev: &'static str, link: u64) -> bool { + return ((exit_of(ev, S_DIFF, (link * TGT)) == 0) && gc_regen_ok(diff_changed(ev, link), diff_seals(ev, link))); +} + +pub fn regen_verdict(ev: &'static str, link: u64) -> u8 { + if (saved_any(ev, link) == false) { + return GC_V_PASS; + } + let lk: u64 = (link * TGT); + if (ran(ev, S_STAGE, lk) == false) { + return GC_V_NONE; + } + if (exit_of(ev, S_STAGE, lk) != 0) { + return GC_V_FAIL; + } + if (ran(ev, S_DIFF, lk) == false) { + return GC_V_NONE; + } + if (diff_ok(ev, link) == false) { + return GC_V_FAIL; + } + if (diff_changed(ev, link) == 0) { + return GC_V_PASS; + } + if (ran(ev, S_COMMIT, lk) == false) { + return GC_V_NONE; + } + if (exit_of(ev, S_COMMIT, lk) != 0) { + return GC_V_FAIL; + } + if (ran(ev, S_TIP, lk) == false) { + return GC_V_NONE; + } + if sha_ok(ev, S_TIP, lk) { + return GC_V_PASS; + } + return GC_V_FAIL; +} + +pub fn regen_tip_ok(ev: &'static str, link: u64) -> bool { + return sha_ok(ev, S_TIP, (link * TGT)); +} + +pub fn tip_off(ev: &'static str, link: u64) -> usize { + if regen_tip_ok(ev, link) { + return body_from(ev, S_TIP, (link * TGT)); + } + return body_from(ev, G_TIP, link); +} + +pub fn test_clean(ev: &'static str, n: u64) -> bool { + let f: usize = body_from(ev, G_TEST, n); + let t: usize = body_to(ev, G_TEST, n); + return (((exit_of(ev, G_TEST, n) == 0) && (find_in(ev, f, t, ", 0 failed") <= t)) && (find_in(ev, f, t, ": 0 passed,") > t)); +} + +pub fn all_targets(ev: &'static str, link: u64, k: u8) -> u8 { + let m: usize = ch_count(ev, link); + let mut i: usize = 0; + let mut open: bool = false; + while (i < m) { + if is_target(ev, ch_line(ev, link, i)) { + let n: u64 = ((link * TGT) + (i as u64)); + if (ran(ev, k, n) == false) { + open = true; + } else { + if ((k == G_TEST) && (test_clean(ev, n) == false)) { + return 2; + } + } + if (k == G_SEAL) { + let sst: u8 = seal_state(ev, n); + if (sst == 2) { + return 2; + } + if (sst == 1) { + open = true; + } + } + } + i = (i + 1); + } + if open { + return 1; + } + return 0; +} + +pub fn verdict(ev: &'static str, n: u64, st: u8) -> u8 { + if (ran(ev, G_TIP, n) == false) { + return GC_V_NONE; + } + if (st == 0) { + if spec_ok(ev, n) { + return GC_V_PASS; + } + return GC_V_FAIL; + } + if ((((st == 3) && (targets(ev, n) > 0)) && (all_targets(ev, n, G_TEST) == 2)) && (verdict(ev, n, 1) == GC_V_PASS)) { + return GC_V_FAIL; + } + if ((verdict(ev, n, (st - 1)) != GC_V_PASS) && (verdict(ev, n, (st - 1)) != GC_V_REUSED)) { + return GC_V_NONE; + } + if (st == 1) { + if gen_ok(ev, n) { + return GC_V_PASS; + } + return GC_V_FAIL; + } + if ((st == 2) || (st == 3)) { + if (targets(ev, n) == 0) { + return GC_V_REUSED; + } + let mut k: u8 = G_SEAL; + if (st == 3) { + k = G_TEST; + } + let a: u8 = all_targets(ev, n, k); + if ((a == 0) && (st == 2)) { + return regen_verdict(ev, n); + } + if (a == 0) { + return GC_V_PASS; + } + if (a == 2) { + return GC_V_FAIL; + } + return GC_V_NONE; + } + if (ran(ev, G_LOG, n) == false) { + return GC_V_NONE; + } + if (find_in(ev, body_from(ev, G_LOG, n), body_to(ev, G_LOG, n), "Closes #") <= body_to(ev, G_LOG, n)) { + return GC_V_PASS; + } + return GC_V_FAIL; +} + +pub fn path_hash(ev: &'static str, a: usize, b: usize) -> u64 { + let mut h: u64 = 7; + let mut k: usize = a; + while (k < b) { + h = (((h * 131) + (ev.as_bytes()[(k) as usize] as u64)) % 1000000000000037); + k = (k + 1); + } + return h; +} + +pub fn plan(ev: &'static str, n: usize, out: &mut [u8], pos: &mut [usize]) -> usize { + let mut id: [u64; 16] = [0; 16]; + let mut parent: [u64; 16] = [0; 16]; + let mut v: [u8; 80] = [0; 80]; + let mut eo: [usize; 512] = [0; 512]; + let mut ep: [u64; 512] = [0; 512]; + let mut ek: [u8; 512] = [0; 512]; + let mut el: [u32; 512] = [0; 512]; + let mut eh: [u32; 512] = [0; 512]; + let mut io: [usize; 1] = [0; 1]; + let mut ip: [u64; 1] = [0; 1]; + let mut m: usize = 0; + let mut i: usize = 0; + while (i < n) { + id[(i) as usize] = id_of(ev, G_TIP, (i as u64)); + parent[(i) as usize] = id_of(ev, G_MB, (i as u64)); + let mut st: u8 = 0; + while (st < 5) { + v[(((i * 5) + (st as usize))) as usize] = verdict(ev, (i as u64), st); + st = (st + 1); + } + if (exit_of(ev, G_MT, (i as u64)) == 1) { + v[((i * 5)) as usize] = GC_V_FAIL; + } + let mut c: usize = ch_count(ev, (i as u64)); + if (c > GL_E_MAX) { + c = GL_E_MAX; + } + let mut j: usize = 0; + while (j < c) { + let ls: usize = ch_line(ev, (i as u64), j); + eo[(m) as usize] = i; + ep[(m) as usize] = path_hash(ev, (ls + 2), line_end(ev, ls)); + ek[(m) as usize] = EDIT_WHOLE; + if is_derived(ev, ls) { + ek[(m) as usize] = GC_EDIT_DERIVED; + } + m = (m + 1); + j = (j + 1); + } + i = (i + 1); + } + let mut head: u64 = 0; + if (cur0(ev) < ev.len()) { + head = hex15(ev, cur0(ev)); + } + return gc_land(n, &(id), &(parent), &(v), m, &(eo), &(ep), &(ek), &(el), &(eh), 0, &(io), &(ip), head, out, pos); +} + +pub fn landing_at(ev: &'static str, n: usize, r: usize, wo: &mut [u8], wp: &mut [usize]) -> usize { + let landed: usize = plan(ev, n, wo, wp); + let mut j: usize = 0; + while (j < n) { + if ((wp[(j) as usize] == r) && (r < landed)) { + return j; + } + j = (j + 1); + } + return n; +} + +pub fn new_of(ev: &'static str, j: u64, outcome: u8) -> usize { + if (outcome == GC_OUT_FORWARD) { + return tip_off(ev, j); + } + return body_from(ev, G_CT, j); +} + +pub fn cur_for(ev: &'static str, j: usize, wo: &mut [u8], wp: &mut [usize]) -> usize { + let n: usize = q_count(ev); + let landed: usize = plan(ev, n, wo, wp); + let at: usize = wp[(j) as usize]; + if ((at == 0) || (at >= landed)) { + return cur0(ev); + } + let prev: usize = landing_at(ev, n, (at - 1), wo, wp); + return new_of(ev, (prev as u64), wo[(prev) as usize]); +} + +pub fn lines_in(ev: &'static str, f: usize, t: usize) -> usize { + let mut c: usize = 0; + let mut p: usize = f; + while (p < t) { + if (line_end(ev, p) > p) { + c = (c + 1); + } + p = (line_end(ev, p) + 1); + } + return c; +} + +pub fn ahead_count(ev: &'static str) -> usize { + return lines_in(ev, body_from(ev, P_AHEAD, 0), body_to(ev, P_AHEAD, 0)); +} + +pub fn batch_len(ev: &'static str) -> usize { + let a: usize = ahead_count(ev); + if (a > GC_BATCH_MAX) { + return GC_BATCH_MAX; + } + return a; +} + +pub fn minutes_since(ev: &'static str) -> u32 { + if ((exit_of(ev, P_STAMP, 0) != 0) || (exit_of(ev, P_NOW, 0) != 0)) { + return 100000; + } + let s: u64 = digits(ev, body_from(ev, P_STAMP, 0), line_end(ev, body_from(ev, P_STAMP, 0))); + let t: u64 = digits(ev, body_from(ev, P_NOW, 0), line_end(ev, body_from(ev, P_NOW, 0))); + if ((s == 0) || (t < s)) { + return 100000; + } + return (((t - s) / 60) as u32); +} + +pub fn queued_runs(r: GolReq, ev: &'static str) -> u32 { + if (r.queued != ABSENT) { + return (r.queued as u32); + } + if (exit_of(ev, P_QUEUED, 0) != 0) { + return 0; + } + let mut c: u32 = 0; + let mut p: usize = body_from(ev, P_QUEUED, 0); + let t: usize = body_to(ev, P_QUEUED, 0); + while (p < t) { + let at: usize = find_in(ev, p, t, "databaseId"); + if (at > t) { + return c; + } + c = (c + 1); + p = (at + 10); + } + return c; +} + +pub fn decision(r: GolReq, ev: &'static str) -> u8 { + return when_to_push(minutes_since(ev), batch_len(ev), queued_runs(r, ev)); +} + +pub fn target_off(ev: &'static str) -> usize { + let mut p: usize = body_from(ev, P_AHEAD, 0); + let mut c: usize = 1; + while (c < batch_len(ev)) { + p = (line_end(ev, p) + 1); + c = (c + 1); + } + return p; +} + +pub fn slot_of(k: u8, n: u64) -> u64 { + return (((k as u64) * SLOT) + n); +} + +pub fn wants_global(r: GolReq, ev: &'static str, k: u8) -> bool { + if ((k == G_REFS) || (k == G_FILE)) { + return (r.verb != V_PUSH); + } + if ((k == G_HEAD) || (k == G_BASE)) { + return true; + } + if (r.verb == V_LAND) { + return false; + } + if (has_head(ev) == false) { + return false; + } + if (k == P_STAMP) { + return sha_ok(ev, P_PUSHED, 0); + } + if (k == P_QUEUED) { + return (r.queued == ABSENT); + } + return true; +} + +pub fn link_slot(ev: &'static str, j: u64) -> u64 { + if (ran(ev, G_TIP, j) == false) { + return slot_of(G_TIP, j); + } + if (sha_ok(ev, G_TIP, j) == false) { + return DONE; + } + if (ran(ev, G_MB, j) == false) { + return slot_of(G_MB, j); + } + if (sha_ok(ev, G_MB, j) == false) { + return DONE; + } + if (ran(ev, G_CHG, j) == false) { + return slot_of(G_CHG, j); + } + if (exit_of(ev, G_CHG, j) != 0) { + return DONE; + } + if (ran(ev, G_LOG, j) == false) { + return slot_of(G_LOG, j); + } + if (((spec_ok(ev, j) == false) || (gen_ok(ev, j) == false)) || (targets(ev, j) == 0)) { + return DONE; + } + if (ran(ev, G_WT, j) == false) { + return slot_of(G_WT, j); + } + if (exit_of(ev, G_WT, j) == 0) { + let m: usize = ch_count(ev, j); + let lk: u64 = (j * TGT); + let mut i: usize = 0; + while (i < m) { + if (is_target(ev, ch_line(ev, j, i)) && (ran(ev, G_SEAL, (lk + (i as u64))) == false)) { + return slot_of(G_SEAL, (lk + (i as u64))); + } + i = (i + 1); + } + i = 0; + while (i < m) { + if (is_target(ev, ch_line(ev, j, i)) && (ran(ev, G_TEST, (lk + (i as u64))) == false)) { + return slot_of(G_TEST, (lk + (i as u64))); + } + i = (i + 1); + } + if (all_targets(ev, j, G_TEST) == 0) { + i = 0; + while (i < m) { + let n: u64 = (lk + (i as u64)); + if ((is_target(ev, ch_line(ev, j, i)) && (seal_clean(ev, G_SEAL, n) == false)) && (ran(ev, S_SAVE, n) == false)) { + return slot_of(S_SAVE, n); + } + i = (i + 1); + } + i = 0; + while (i < m) { + let n: u64 = (lk + (i as u64)); + if ((is_target(ev, ch_line(ev, j, i)) && ran(ev, S_SAVE, n)) && (ran(ev, S_RECHECK, n) == false)) { + return slot_of(S_RECHECK, n); + } + i = (i + 1); + } + if (saved_any(ev, j) && (all_targets(ev, j, G_SEAL) == 0)) { + if (ran(ev, S_STAGE, lk) == false) { + return slot_of(S_STAGE, lk); + } + if ((exit_of(ev, S_STAGE, lk) == 0) && (ran(ev, S_DIFF, lk) == false)) { + return slot_of(S_DIFF, lk); + } + if (((exit_of(ev, S_STAGE, lk) == 0) && diff_ok(ev, j)) && (diff_changed(ev, j) > 0)) { + if (ran(ev, S_COMMIT, lk) == false) { + return slot_of(S_COMMIT, lk); + } + if ((exit_of(ev, S_COMMIT, lk) == 0) && (ran(ev, S_TIP, lk) == false)) { + return slot_of(S_TIP, lk); + } + } + } + } + if (ran(ev, G_RM, j) == false) { + return slot_of(G_RM, j); + } + } + return DONE; +} + +pub fn next_slot(r: GolReq, ev: &'static str, wo: &mut [u8], wp: &mut [usize]) -> u64 { + let mut k: u8 = G_REFS; + while (k <= G_BASE) { + if (wants_global(r, ev, k) && (ran(ev, k, 0) == false)) { + return slot_of(k, 0); + } + k = (k + 1); + } + if (sha_ok(ev, G_BASE, 0) == false) { + return DONE; + } + if sha_ok(ev, G_HEAD, 0) { + if (ran(ev, P_PUSHED, 0) == false) { + return slot_of(P_PUSHED, 0); + } + if sha_ok(ev, P_PUSHED, 0) { + if (ran(ev, R_ANC, 0) == false) { + return slot_of(R_ANC, 0); + } + let rb: u8 = rb_action(ev); + if (rb == GC_RB_CARRY) { + if (ran(ev, R_MT, 0) == false) { + return slot_of(R_MT, 0); + } + if (sha_ok(ev, R_MT, 0) == false) { + return DONE; + } + if (ran(ev, R_CT, 0) == false) { + return slot_of(R_CT, 0); + } + if (sha_ok(ev, R_CT, 0) == false) { + return DONE; + } + } + if (((rb != GC_RB_NONE) && (r.plan == false)) && (r.verb != V_STATUS)) { + if (ran(ev, R_UH, 0) == false) { + return slot_of(R_UH, 0); + } + if (exit_of(ev, R_UH, 0) != 0) { + return DONE; + } + if (ran(ev, R_UP, 0) == false) { + return slot_of(R_UP, 0); + } + if (exit_of(ev, R_UP, 0) != 0) { + return DONE; + } + } + } + } + if (r.verb == V_LAND) { + let n: usize = q_count(ev); + let mut j: usize = 0; + while (j < n) { + let s: u64 = link_slot(ev, (j as u64)); + if (s < DONE) { + return s; + } + j = (j + 1); + } + let landed: usize = plan(ev, n, wo, wp); + let mut q: usize = 0; + while (q < landed) { + let l: usize = landing_at(ev, n, q, wo, wp); + if (wo[(l) as usize] != GC_OUT_FORWARD) { + if (ran(ev, G_MT, (l as u64)) == false) { + return slot_of(G_MT, (l as u64)); + } + if (sha_ok(ev, G_MT, (l as u64)) == false) { + return DONE; + } + if (ran(ev, G_CT, (l as u64)) == false) { + return slot_of(G_CT, (l as u64)); + } + if (sha_ok(ev, G_CT, (l as u64)) == false) { + return DONE; + } + } + if ((r.plan == false) && (ran(ev, G_CAS, (l as u64)) == false)) { + return slot_of(G_CAS, (l as u64)); + } + if ((r.plan == false) && (exit_of(ev, G_CAS, (l as u64)) != 0)) { + return DONE; + } + q = (q + 1); + } + return DONE; + } + k = P_PUSHED; + while (k <= P_QUEUED) { + if (wants_global(r, ev, k) && (ran(ev, k, 0) == false)) { + return slot_of(k, 0); + } + k = (k + 1); + } + if (((r.verb == V_PUSH) && (r.plan == false)) && (decision(r, ev) == GC_PUSH_NOW)) { + k = P_BRANCH; + while (k <= P_MARK) { + if (ran(ev, k, 0) == false) { + return slot_of(k, 0); + } + if (exit_of(ev, k, 0) != 0) { + return DONE; + } + k = (k + 1); + } + } + return DONE; +} + +pub fn judge(r: GolReq, ev: &'static str, k: u8, n: u64) -> u8 { + let x: u64 = exit_of(ev, k, n); + if ((k == G_BASE) && (sha_ok(ev, G_BASE, 0) == false)) { + return C_NO_BASE; + } + if ((k == G_MT) && (x > 1)) { + return C_STEP; + } + if ((k == R_MT) && (x == 1)) { + return C_MERGE; + } + if ((k == R_MT) && (x > 1)) { + return C_STEP; + } + if ((k == R_CT) && (sha_ok(ev, R_CT, 0) == false)) { + return C_STEP; + } + if (((k == R_UH) || (k == R_UP)) && (x != 0)) { + return C_HEAD_MOVED; + } + if ((k == G_CT) && (sha_ok(ev, G_CT, n) == false)) { + return C_STEP; + } + if ((k == G_CAS) && (x != 0)) { + return C_HEAD_MOVED; + } + if ((k == P_LAND) && (x != 0)) { + return C_LAND_REFUSED; + } + if (((k == P_BRANCH) || (k == P_MARK)) && (x != 0)) { + return C_STEP; + } + return C_OK; +} + +pub fn in_scratch(k: u8) -> bool { + return (((k == G_SEAL) || (k == G_TEST)) || ((k >= S_SAVE) && (k <= S_TIP))); +} + +pub fn arg(out: &mut [u8], w0: usize, s: &'static str) -> usize { + let w: usize = emit_str(out, w0, s); + return emit_byte(out, w, 10); +} + +pub fn put_span(out: &mut [u8], w: usize, t: &'static str, a: usize, b: usize) -> usize { + let mut p: usize = w; + let mut k: usize = a; + while (k < b) { + p = emit_byte(out, p, t.as_bytes()[(k) as usize]); + k = (k + 1); + } + return p; +} + +pub fn arg_span(out: &mut [u8], w0: usize, t: &'static str, a: usize, b: usize) -> usize { + let w: usize = put_span(out, w0, t, a, b); + return emit_byte(out, w, 10); +} + +pub fn sha_arg(out: &mut [u8], w: usize, t: &'static str, a: usize) -> usize { + return arg_span(out, w, t, a, (a + 40)); +} + +pub fn zero_arg(out: &mut [u8], w: usize) -> usize { + let mut p: usize = w; + let mut k: usize = 0; + while (k < 40) { + p = emit_byte(out, p, 48); + k = (k + 1); + } + return emit_byte(out, p, 10); +} + +pub fn git(out: &mut [u8], w0: usize, a: &'static str) -> usize { + let w: usize = arg(out, w0, "git"); + return arg(out, w, a); +} + +pub fn emit_wt(out: &mut [u8], w0: usize, r: GolReq, j: u64) -> usize { + let mut w: usize = emit_str(out, w0, r.scratch); + w = emit_str(out, w, "/gl-"); + return emit_dec(out, w, j); +} + +pub fn step_cwd(r: GolReq, k: u8, n: u64, out: &mut [u8]) -> usize { + if (in_scratch(k) == false) { + return 0; + } + let w: usize = emit_wt(out, 0, r, (n / TGT)); + return fits(out, w); +} + +pub fn emit_push_branch(out: &mut [u8], w: usize, ev: &'static str) -> usize { + let mut p: usize = emit_str(out, w, "claude/golden-"); + let f: usize = target_off(ev); + let mut k: usize = 0; + while (k < 10) { + p = emit_byte(out, p, ev.as_bytes()[((f + k)) as usize]); + k = (k + 1); + } + return p; +} + +pub fn args_ready(ev: &'static str, k: u8, j: u64) -> bool { + let cur: bool = (has_head(ev) || sha_ok(ev, G_BASE, 0)); + if (k == G_TIP) { + return (q_start(ev, (j as usize)) < ev.len()); + } + if ((k == G_MB) || (k == G_MT)) { + return ((sha_ok(ev, G_TIP, j) && cur) && ((k == G_MB) || sha_ok(ev, G_MB, j))); + } + if ((k == G_CHG) || (k == G_LOG)) { + return (sha_ok(ev, G_TIP, j) && sha_ok(ev, G_MB, j)); + } + if (k == G_WT) { + return sha_ok(ev, G_TIP, j); + } + if (k == G_CT) { + return (sha_ok(ev, G_MT, j) && (q_start(ev, (j as usize)) < ev.len())); + } + if (k == G_CAS) { + return (sha_ok(ev, G_TIP, j) && cur); + } + if (k == P_AHEAD) { + return (has_head(ev) && (has_pushed(ev) || sha_ok(ev, G_BASE, 0))); + } + if ((k == R_ANC) || (k == R_MT)) { + return ((sha_ok(ev, G_HEAD, 0) && sha_ok(ev, P_PUSHED, 0)) && sha_ok(ev, G_BASE, 0)); + } + if (k == R_CT) { + return (sha_ok(ev, R_MT, 0) && sha_ok(ev, G_BASE, 0)); + } + if (k == R_UH) { + return ((sha_ok(ev, G_HEAD, 0) && sha_ok(ev, G_BASE, 0)) && ((rb_action(ev) != GC_RB_CARRY) || sha_ok(ev, R_CT, 0))); + } + if (k == R_UP) { + return (sha_ok(ev, P_PUSHED, 0) && sha_ok(ev, G_BASE, 0)); + } + if ((k >= S_SAVE) && (k <= S_TIP)) { + return sha_ok(ev, G_TIP, (j / TGT)); + } + if (((k == P_BRANCH) || (k == P_LAND)) || (k == P_MARK)) { + return ((ahead_count(ev) > 0) && has_head(ev)); + } + return true; +} + +pub fn step_argv(r: GolReq, ev: &'static str, k: u8, n: u64, out: &mut [u8], wo: &mut [u8], wp: &mut [usize]) -> usize { + let mut w: usize = 0; + let j: u64 = (n % SLOT); + if (args_ready(ev, k, j) == false) { + return 0; + } + if (k == G_REFS) { + w = git(out, w, "for-each-ref"); + w = arg(out, w, "--format=%(refname:short)"); + w = arg(out, w, "refs/heads/claude/evolve-*"); + } + if (k == G_FILE) { + w = arg(out, w, "cat"); + w = arg(out, w, GL_QUEUE_FILE); + } + if (k == G_HEAD) { + w = git(out, w, "rev-parse"); + w = arg(out, w, "--verify"); + w = arg(out, w, "-q"); + w = arg(out, w, GC_REF); + } + if (k == G_BASE) { + w = git(out, w, "rev-parse"); + w = arg(out, w, "--verify"); + w = arg(out, w, "-q"); + w = arg(out, w, "refs/remotes/origin/master"); + } + if (k == G_TIP) { + let a: usize = q_start(ev, (j as usize)); + if (a >= ev.len()) { + return 0; + } + w = git(out, w, "rev-parse"); + w = arg(out, w, "--verify"); + w = arg(out, w, "-q"); + w = emit_str(out, w, "refs/heads/"); + w = arg_span(out, w, ev, a, line_end(ev, a)); + } + if (k == G_MB) { + w = git(out, w, "merge-base"); + w = sha_arg(out, w, ev, body_from(ev, G_TIP, j)); + w = sha_arg(out, w, ev, cur0(ev)); + } + if (k == G_CHG) { + w = git(out, w, "diff"); + w = arg(out, w, "--name-status"); + w = arg(out, w, "--no-renames"); + w = sha_arg(out, w, ev, body_from(ev, G_MB, j)); + w = sha_arg(out, w, ev, body_from(ev, G_TIP, j)); + } + if (k == G_LOG) { + w = git(out, w, "log"); + w = arg(out, w, "--format=%B"); + w = put_span(out, w, ev, body_from(ev, G_MB, j), (body_from(ev, G_MB, j) + 40)); + w = emit_str(out, w, ".."); + w = sha_arg(out, w, ev, body_from(ev, G_TIP, j)); + } + if (k == G_WT) { + w = git(out, w, "worktree"); + w = arg(out, w, "add"); + w = arg(out, w, "-q"); + w = arg(out, w, "--detach"); + w = emit_wt(out, w, r, j); + w = emit_byte(out, w, 10); + w = sha_arg(out, w, ev, body_from(ev, G_TIP, j)); + } + if ((((k == G_SEAL) || (k == G_TEST)) || (k == S_SAVE)) || (k == S_RECHECK)) { + let link: u64 = (j / TGT); + let ls: usize = ch_line(ev, link, ((j % TGT) as usize)); + if (is_target(ev, ls) == false) { + return 0; + } + if (k != G_TEST) { + w = arg(out, w, r.t27c); + w = arg(out, w, "seal"); + w = arg_span(out, w, ev, (ls + 2), line_end(ev, ls)); + if (k == S_SAVE) { + w = arg(out, w, "--save"); + } else { + w = arg(out, w, "--verify"); + } + } else { + w = arg(out, w, r.t27b); + w = arg(out, w, "test"); + w = arg_span(out, w, ev, (ls + 2), line_end(ev, ls)); + } + } + if (k == S_STAGE) { + w = git(out, w, "add"); + w = arg(out, w, "-A"); + } + if (k == S_DIFF) { + w = git(out, w, "diff"); + w = arg(out, w, "--cached"); + w = arg(out, w, "--name-only"); + } + if (k == S_COMMIT) { + w = git(out, w, "-c"); + w = arg(out, w, "user.name=tri-golden"); + w = arg(out, w, "-c"); + w = arg(out, w, "user.email=golden@t27.ai"); + w = arg(out, w, "-c"); + w = arg(out, w, "commit.gpgsign=false"); + w = arg(out, w, "commit"); + w = arg(out, w, "--no-verify"); + w = arg(out, w, "-q"); + w = arg(out, w, "-m"); + w = emit_str(out, w, "golden: save the seals again on the tip (t27c seal --save, tests clean)"); + if (r.issue > 0) { + w = emit_str(out, w, " (Closes #"); + w = emit_dec(out, w, r.issue); + w = emit_str(out, w, ")"); + } + w = emit_byte(out, w, 10); + } + if (k == S_TIP) { + w = git(out, w, "rev-parse"); + w = arg(out, w, "--verify"); + w = arg(out, w, "-q"); + w = arg(out, w, "HEAD"); + } + if (k == R_ANC) { + w = git(out, w, "merge-base"); + w = arg(out, w, "--is-ancestor"); + w = sha_arg(out, w, ev, body_from(ev, G_HEAD, 0)); + w = sha_arg(out, w, ev, body_from(ev, G_BASE, 0)); + } + if (k == R_MT) { + w = git(out, w, "merge-tree"); + w = arg(out, w, "--write-tree"); + w = emit_str(out, w, "--merge-base="); + w = sha_arg(out, w, ev, body_from(ev, P_PUSHED, 0)); + w = sha_arg(out, w, ev, body_from(ev, G_BASE, 0)); + w = sha_arg(out, w, ev, body_from(ev, G_HEAD, 0)); + } + if (k == R_CT) { + w = git(out, w, "commit-tree"); + w = sha_arg(out, w, ev, body_from(ev, R_MT, 0)); + w = arg(out, w, "-p"); + w = sha_arg(out, w, ev, body_from(ev, G_BASE, 0)); + w = arg(out, w, "-m"); + w = emit_str(out, w, "golden: carry the unpushed links onto origin/master"); + if (r.issue > 0) { + w = emit_str(out, w, " (Closes #"); + w = emit_dec(out, w, r.issue); + w = emit_str(out, w, ")"); + } + w = emit_byte(out, w, 10); + } + if (k == R_UH) { + w = git(out, w, "update-ref"); + w = arg(out, w, GC_REF); + if (rb_action(ev) == GC_RB_CARRY) { + w = sha_arg(out, w, ev, body_from(ev, R_CT, 0)); + } else { + w = sha_arg(out, w, ev, body_from(ev, G_BASE, 0)); + } + w = sha_arg(out, w, ev, body_from(ev, G_HEAD, 0)); + } + if (k == R_UP) { + w = git(out, w, "update-ref"); + w = arg(out, w, "--create-reflog"); + w = arg(out, w, GL_PUSHED); + w = sha_arg(out, w, ev, body_from(ev, G_BASE, 0)); + w = sha_arg(out, w, ev, body_from(ev, P_PUSHED, 0)); + } + if (k == G_RM) { + w = git(out, w, "worktree"); + w = arg(out, w, "remove"); + w = arg(out, w, "--force"); + w = emit_wt(out, w, r, j); + w = emit_byte(out, w, 10); + } + if (((k == G_MT) || (k == G_CT)) || (k == G_CAS)) { + let cur: usize = cur_for(ev, (j as usize), wo, wp); + if (k == G_MT) { + w = git(out, w, "merge-tree"); + w = arg(out, w, "--write-tree"); + w = emit_str(out, w, "--merge-base="); + w = sha_arg(out, w, ev, body_from(ev, G_MB, j)); + w = sha_arg(out, w, ev, cur); + w = sha_arg(out, w, ev, tip_off(ev, j)); + } + if (k == G_CT) { + w = git(out, w, "commit-tree"); + w = sha_arg(out, w, ev, body_from(ev, G_MT, j)); + w = arg(out, w, "-p"); + w = sha_arg(out, w, ev, cur); + w = arg(out, w, "-m"); + w = emit_str(out, w, "golden: carry "); + w = put_span(out, w, ev, q_start(ev, (j as usize)), line_end(ev, q_start(ev, (j as usize)))); + if (r.issue > 0) { + w = emit_str(out, w, " (Closes #"); + w = emit_dec(out, w, r.issue); + w = emit_str(out, w, ")"); + } + w = emit_byte(out, w, 10); + } + if (k == G_CAS) { + let landed: usize = plan(ev, q_count(ev), wo, wp); + w = git(out, w, "update-ref"); + w = arg(out, w, GC_REF); + w = sha_arg(out, w, ev, new_of(ev, j, wo[((j as usize)) as usize])); + if ((wp[((j as usize)) as usize] == 0) && (has_head(ev) == false)) { + w = zero_arg(out, w); + } else { + w = sha_arg(out, w, ev, cur); + } + } + } + if (k == P_PUSHED) { + w = git(out, w, "rev-parse"); + w = arg(out, w, "--verify"); + w = arg(out, w, "-q"); + w = arg(out, w, GL_PUSHED); + } + if (k == P_AHEAD) { + w = git(out, w, "rev-list"); + w = arg(out, w, "--first-parent"); + w = arg(out, w, "--reverse"); + if has_pushed(ev) { + w = put_span(out, w, ev, pushed_off(ev), (pushed_off(ev) + 40)); + } else { + w = put_span(out, w, ev, body_from(ev, G_BASE, 0), (body_from(ev, G_BASE, 0) + 40)); + } + w = emit_str(out, w, ".."); + w = sha_arg(out, w, ev, head_off(ev)); + } + if (k == P_STAMP) { + w = git(out, w, "reflog"); + w = arg(out, w, "show"); + w = arg(out, w, "--format=%ct"); + w = arg(out, w, "-n"); + w = arg(out, w, "1"); + w = arg(out, w, GL_PUSHED); + } + if (k == P_NOW) { + w = arg(out, w, "date"); + w = arg(out, w, "+%s"); + } + if (k == P_QUEUED) { + w = arg(out, w, "gh"); + w = arg(out, w, "run"); + w = arg(out, w, "list"); + w = arg(out, w, "--status"); + w = arg(out, w, "queued"); + w = arg(out, w, "--limit"); + w = arg(out, w, "300"); + w = arg(out, w, "--json"); + w = arg(out, w, "databaseId"); + } + if (k == P_BRANCH) { + w = git(out, w, "branch"); + w = arg(out, w, "-f"); + w = emit_push_branch(out, w, ev); + w = emit_byte(out, w, 10); + w = sha_arg(out, w, ev, target_off(ev)); + } + if (k == P_LAND) { + w = arg(out, w, r.tri); + w = arg(out, w, "land"); + w = emit_push_branch(out, w, ev); + w = emit_byte(out, w, 10); + w = arg(out, w, "--issue"); + w = emit_dec(out, w, r.issue); + w = emit_byte(out, w, 10); + } + if (k == P_MARK) { + w = git(out, w, "update-ref"); + w = arg(out, w, "--create-reflog"); + w = arg(out, w, GL_PUSHED); + w = sha_arg(out, w, ev, target_off(ev)); + if has_pushed(ev) { + w = sha_arg(out, w, ev, pushed_off(ev)); + } else { + w = zero_arg(out, w); + } + } + if (k == G_PRUNE) { + w = git(out, w, "worktree"); + w = arg(out, w, "prune"); + } + return fits(out, w); +} + +pub fn exit_code(c: u8) -> u8 { + return c; +} + +pub fn code_name(c: u8) -> &'static str { + if (c == C_OK) { + return "OK"; + } + if (c == C_BAD_VERB) { + return "BAD_VERB"; + } + if (c == C_BAD_ISSUE) { + return "BAD_ISSUE"; + } + if (c == C_BAD_PATH) { + return "BAD_PATH"; + } + if (c == C_NO_BASE) { + return "NO_BASE"; + } + if (c == C_MERGE) { + return "MERGE_CONFLICT"; + } + if (c == C_STEP) { + return "STEP_FAILED"; + } + if (c == C_HEAD_MOVED) { + return "HEAD_MOVED"; + } + if (c == C_LAND_REFUSED) { + return "LAND_REFUSED"; + } + return "ODDS"; +} + +pub fn code_why(c: u8) -> &'static str { + if (c == C_OK) { + return "done"; + } + if (c == C_BAD_VERB) { + return "the verbs are land, push and status"; + } + if (c == C_BAD_ISSUE) { + return "a push opens a pull request: pass --issue N (L1)"; + } + if (c == C_BAD_PATH) { + return "a program or scratch path has bytes a command line should not carry"; + } + if (c == C_NO_BASE) { + return "refs/remotes/origin/master is missing: git fetch origin master"; + } + if (c == C_MERGE) { + return "git merge-tree met a conflict carrying the unpushed links onto origin/master: a person decides"; + } + if (c == C_STEP) { + return "a git step failed; its output is the evidence"; + } + if (c == C_HEAD_MOVED) { + return "refs/golden/head is not where this pass read it: nothing was forced, run it again"; + } + if (c == C_LAND_REFUSED) { + return "tri land refused the batch branch; refs/golden/pushed did not move"; + } + return "unknown"; +} + +pub fn outcome_name(o: u8) -> &'static str { + if (o == GC_OUT_FORWARD) { + return "FORWARD"; + } + if (o == GC_OUT_CARRIED) { + return "CARRIED"; + } + if (o == GC_OUT_REGEN) { + return "REGEN"; + } + if (o == GC_OUT_REJUDGE) { + return "REJUDGE"; + } + if (o == GC_OUT_CONFLICT) { + return "CONFLICT"; + } + if (o == GC_OUT_NOT_GOLDEN) { + return "NOT_GOLDEN"; + } + return "UNKNOWN_PARENT"; +} + +pub fn outcome_shown(ev: &'static str, i: usize, o: u8) -> u8 { + if (exit_of(ev, G_MT, (i as u64)) == 1) { + return GC_OUT_CONFLICT; + } + return gc_regen_view(o, regen_tip_ok(ev, (i as u64))); +} + +pub fn verdict_letter(v: u8) -> u8 { + if (v == GC_V_PASS) { + return 112; + } + if (v == GC_V_REUSED) { + return 114; + } + if (v == GC_V_FAIL) { + return 102; + } + return 45; +} + +pub fn off_if(ev: &'static str, k: u8, n: u64) -> usize { + if sha_ok(ev, k, n) { + return body_from(ev, k, n); + } + return ev.len(); +} + +pub fn put_sha12(out: &mut [u8], w: usize, ev: &'static str, a: usize) -> usize { + if (a >= ev.len()) { + return emit_str(out, w, "none"); + } + return put_span(out, w, ev, a, (a + 12)); +} + +pub fn put_cmd(out: &mut [u8], w: usize, r: GolReq, ev: &'static str, k: u8, n: u64, wo: &mut [u8], wp: &mut [usize], tmp: &mut [u8]) -> usize { + let m: usize = step_argv(r, ev, k, n, tmp, wo, wp); + let mut p: usize = emit_str(out, w, " would run: "); + let mut i: usize = 0; + while ((i + 1) < m) { + if (tmp[(i) as usize] == 10) { + p = emit_byte(out, p, 32); + } else { + p = emit_byte(out, p, tmp[(i) as usize]); + } + i = (i + 1); + } + return emit_byte(out, p, 10); +} + +pub fn report_land(r: GolReq, ev: &'static str, out: &mut [u8], wo: &mut [u8], wp: &mut [usize], tmp: &mut [u8]) -> usize { + let mut w: usize = emit_str(out, 0, "golden head: "); + w = put_sha12(out, w, ev, head_off(ev)); + if (has_head(ev) == false) { + w = emit_str(out, w, " (base "); + w = put_sha12(out, w, ev, off_if(ev, G_BASE, 0)); + w = emit_byte(out, w, 41); + } + let n: usize = q_count(ev); + w = emit_byte(out, w, 10); + w = emit_str(out, w, "queue: "); + w = emit_dec(out, w, (n as u64)); + w = emit_byte(out, w, 10); + let landed: usize = plan(ev, n, wo, wp); + let mut i: usize = 0; + while (i < n) { + w = put_span(out, w, ev, q_start(ev, i), line_end(ev, q_start(ev, i))); + w = emit_byte(out, w, 32); + let mut st: u8 = 0; + while (st < 5) { + w = emit_byte(out, w, verdict_letter(verdict(ev, (i as u64), st))); + st = (st + 1); + } + w = emit_byte(out, w, 32); + w = emit_str(out, w, outcome_name(outcome_shown(ev, i, wo[(i) as usize]))); + w = emit_byte(out, w, 10); + i = (i + 1); + } + w = emit_str(out, w, "landed: "); + w = emit_dec(out, w, (landed as u64)); + w = emit_str(out, w, " of "); + w = emit_dec(out, w, (n as u64)); + w = emit_str(out, w, "\n"); + let mut q: usize = 0; + while (q < landed) { + let l: usize = landing_at(ev, n, q, wo, wp); + if r.plan { + w = put_cmd(out, w, r, ev, G_CAS, (l as u64), wo, wp, tmp); + } + q = (q + 1); + } + return w; +} + +pub fn report_push(r: GolReq, ev: &'static str, out: &mut [u8], wo: &mut [u8], wp: &mut [usize], tmp: &mut [u8]) -> usize { + let mut w: usize = emit_str(out, 0, "golden head: "); + w = put_sha12(out, w, ev, head_off(ev)); + w = emit_str(out, w, "\npushed: "); + w = put_sha12(out, w, ev, pushed_off(ev)); + w = emit_str(out, w, "\nunpushed links: "); + w = emit_dec(out, w, (ahead_count(ev) as u64)); + w = emit_str(out, w, " (batch "); + w = emit_dec(out, w, (batch_len(ev) as u64)); + w = emit_str(out, w, ", cap 8)\nminutes since the last push: "); + w = emit_dec(out, w, (minutes_since(ev) as u64)); + w = emit_str(out, w, "\nactions queued: "); + w = emit_dec(out, w, (queued_runs(r, ev) as u64)); + w = emit_str(out, w, "\nnext push: "); + let d: u8 = decision(r, ev); + if (d == GC_PUSH_NOTHING) { + w = emit_str(out, w, "NOTHING (no link is waiting)\n"); + } + if (d == GC_PUSH_NOW) { + w = emit_str(out, w, "NOW\n"); + } + if (d == GC_PUSH_WAIT) { + let mut window: u32 = GC_WINDOW_MIN; + if (queued_runs(r, ev) >= GC_CI_JAM) { + window = (GC_WINDOW_MIN * 2); + } + w = emit_str(out, w, "WAIT "); + w = emit_dec(out, w, ((window - minutes_since(ev)) as u64)); + w = emit_str(out, w, " more minute(s)\n"); + } + if ((r.verb == V_PUSH) && (d == GC_PUSH_NOW)) { + let mut k: u8 = P_BRANCH; + while (k <= P_MARK) { + if r.plan { + w = put_cmd(out, w, r, ev, k, 0, wo, wp, tmp); + } + k = (k + 1); + } + } + return w; +} + +pub fn report_queue_status(ev: &'static str, out: &mut [u8], w0: usize) -> usize { + let mut w: usize = emit_str(out, w0, "queue:"); + let mut i: usize = 0; + while (i < q_count(ev)) { + w = emit_byte(out, w, 32); + w = put_span(out, w, ev, q_start(ev, i), line_end(ev, q_start(ev, i))); + i = (i + 1); + } + return emit_byte(out, w, 10); +} + +pub fn report(r: GolReq, ev: &'static str, code: u8, out: &mut [u8], wo: &mut [u8], wp: &mut [usize], tmp: &mut [u8]) -> usize { + let mut w: usize = 0; + if ((code >= C_BAD_VERB) && (code <= C_NO_BASE)) { + w = emit_str(out, 0, "refused: "); + w = emit_str(out, w, code_name(code)); + w = emit_str(out, w, ": "); + w = emit_str(out, w, code_why(code)); + w = emit_byte(out, w, 10); + return fits(out, w); + } + if (r.verb == V_LAND) { + w = report_land(r, ev, out, wo, wp, tmp); + } + if (r.verb != V_LAND) { + w = report_push(r, ev, out, wo, wp, tmp); + } + if (r.verb == V_STATUS) { + w = report_queue_status(ev, out, w); + } + if (code != C_OK) { + w = emit_str(out, w, "refused: "); + w = emit_str(out, w, code_name(code)); + w = emit_str(out, w, ": "); + w = emit_str(out, w, code_why(code)); + w = emit_byte(out, w, 10); + } + return fits(out, w); +} + +pub const FB: &'static str = "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1"; + +pub const FT1: &'static str = "b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2"; + +pub const FT2: &'static str = "c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3"; + +pub const FH: &'static str = "d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4"; + +pub const FNEW: &'static str = "e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5"; + +pub const FTREE: &'static str = "f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6"; + +pub const EV_G: &'static str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n"; + +pub const EV_TIP0: &'static str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n"; + +pub const EV_MB0: &'static str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n"; + +pub const EV_CHG0: &'static str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n"; + +pub const EV_LOG0: &'static str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n"; + +pub const EV_WT0: &'static str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n"; + +pub const EV_SEAL0: &'static str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n"; + +pub const EV_TEST0: &'static str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 0 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n"; + +pub const EV_RM0: &'static str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 0 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 11 0 0 0\n\n"; + +pub const EV_V: &'static str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 0 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 11 0 0 0\n\n@@ preflight 4 1 0 41\nc3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3\n\n@@ preflight 5 1 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 1 0 16\nA\tspecs/x/b.t27\n\n@@ preflight 7 1 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 1 0 0\n\n@@ preflight 9 1000 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 1000 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 11 1 0 0\n\n"; + +pub const EV_C0: &'static str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 0 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 11 0 0 0\n\n@@ preflight 4 1 0 41\nc3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3\n\n@@ preflight 5 1 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 1 0 16\nA\tspecs/x/b.t27\n\n@@ preflight 7 1 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 1 0 0\n\n@@ preflight 9 1000 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 1000 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 11 1 0 0\n\n@@ preflight 14 0 0 0\n\n"; + +pub const EV_M1: &'static str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 0 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 11 0 0 0\n\n@@ preflight 4 1 0 41\nc3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3\n\n@@ preflight 5 1 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 1 0 16\nA\tspecs/x/b.t27\n\n@@ preflight 7 1 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 1 0 0\n\n@@ preflight 9 1000 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 1000 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 11 1 0 0\n\n@@ preflight 14 0 0 0\n\n@@ preflight 12 1 0 41\nf6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6\n\n"; + +pub const EV_K1: &'static str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 0 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 11 0 0 0\n\n@@ preflight 4 1 0 41\nc3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3\n\n@@ preflight 5 1 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 1 0 16\nA\tspecs/x/b.t27\n\n@@ preflight 7 1 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 1 0 0\n\n@@ preflight 9 1000 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 1000 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 11 1 0 0\n\n@@ preflight 14 0 0 0\n\n@@ preflight 12 1 0 41\nf6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6\n\n@@ preflight 13 1 0 41\ne5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5\n\n"; + +pub const EV_ALL: &'static str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 0 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 11 0 0 0\n\n@@ preflight 4 1 0 41\nc3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3\n\n@@ preflight 5 1 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 1 0 16\nA\tspecs/x/b.t27\n\n@@ preflight 7 1 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 1 0 0\n\n@@ preflight 9 1000 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 1000 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 11 1 0 0\n\n@@ preflight 14 0 0 0\n\n@@ preflight 12 1 0 41\nf6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6\n\n@@ preflight 13 1 0 41\ne5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5\n\n@@ preflight 14 1 0 0\n\n"; + +pub const EV_PLAN_K1: &'static str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 0 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 11 0 0 0\n\n@@ preflight 4 1 0 41\nc3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3\n\n@@ preflight 5 1 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 1 0 16\nA\tspecs/x/b.t27\n\n@@ preflight 7 1 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 1 0 0\n\n@@ preflight 9 1000 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 1000 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 11 1 0 0\n\n@@ preflight 12 1 0 41\nf6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6\n\n@@ preflight 13 1 0 41\ne5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5\n\n"; + +pub const EV_CONF: &'static str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 0 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 11 0 0 0\n\n@@ preflight 4 1 0 41\nc3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3\n\n@@ preflight 5 1 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 1 0 16\nM\tspecs/x/a.t27\n\n@@ preflight 7 1 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 1 0 0\n\n@@ preflight 9 1000 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 1000 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 11 1 0 0\n\n"; + +pub const EV_SEALBAD: &'static str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 1 20\nVERIFICATION FAILED\n\n@@ preflight 10 0 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 29 0 0 0\n\n@@ preflight 30 0 1 20\nVERIFICATION FAILED\n\n@@ preflight 11 0 0 0\n\n@@ preflight 4 1 0 41\nc3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3\n\n@@ preflight 5 1 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 1 0 16\nA\tspecs/x/b.t27\n\n@@ preflight 7 1 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 1 0 0\n\n@@ preflight 9 1000 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 1000 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 11 1 0 0\n\n"; + +pub const EV_NOCLOSES: &'static str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 7\ngolden\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 0 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 11 0 0 0\n\n@@ preflight 4 1 0 41\nc3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3\n\n@@ preflight 5 1 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 1 0 16\nA\tspecs/x/b.t27\n\n@@ preflight 7 1 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 1 0 0\n\n@@ preflight 9 1000 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 1000 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 11 1 0 0\n\n"; + +pub const EV_GENONLY: &'static str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nM\tgen/rust/x.rs\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n"; + +pub const EV_VACUOUS: &'static str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 0 0 34\nTriX: 0 passed, 0 failed, 0 total\n\n@@ preflight 11 0 0 0\n\n@@ preflight 4 1 0 41\nc3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3\n\n@@ preflight 5 1 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 1 0 16\nA\tspecs/x/b.t27\n\n@@ preflight 7 1 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 1 0 0\n\n@@ preflight 9 1000 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 1000 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 11 1 0 0\n\n"; + +pub const EV_MOVED: &'static str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 0 41\nd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 0 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 11 0 0 0\n\n@@ preflight 4 1 0 41\nc3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3\n\n@@ preflight 5 1 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 1 0 16\nA\tspecs/x/b.t27\n\n@@ preflight 7 1 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 1 0 0\n\n@@ preflight 9 1000 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 1000 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 11 1 0 0\n\n@@ preflight 15 0 1 0\n\n"; + +pub const EV_HEADED: &'static str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 0 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 11 0 0 0\n\n@@ preflight 15 0 1 0\n\n"; + +pub const EV_NOBASE: &'static str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 1 0\n\n"; + +pub const EV_Q: &'static str = "@@ preflight 0 0 0 26\nclaude/evolve-a\nmaster\n-x\n\n@@ preflight 1 0 0 39\n# comment\nclaude/evolve-a\nlanes/extra\n\n\n"; + +pub const EV_P_FIRST: &'static str = "@@ preflight 2 0 0 41\nd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 15 0 1 0\n\n@@ preflight 16 0 0 123\n1010101010101010101010101010101010101010\n1111111111111111111111111111111111111111\n1212121212121212121212121212121212121212\n\n@@ preflight 18 0 0 11\n1760000000\n\n"; + +pub const EV_P_NONE: &'static str = "@@ preflight 2 0 0 41\nd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 15 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 16 0 0 0\n\n@@ preflight 17 0 0 11\n1759994000\n\n@@ preflight 18 0 0 11\n1760000000\n\n@@ preflight 24 0 0 0\n\n"; + +pub const EV_P_20: &'static str = "@@ preflight 2 0 0 41\nd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 15 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 16 0 0 123\n1010101010101010101010101010101010101010\n1111111111111111111111111111111111111111\n1212121212121212121212121212121212121212\n\n@@ preflight 17 0 0 11\n1759998800\n\n@@ preflight 18 0 0 11\n1760000000\n\n@@ preflight 24 0 0 0\n\n"; + +pub const EV_P_30: &'static str = "@@ preflight 2 0 0 41\nd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 15 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 16 0 0 123\n1010101010101010101010101010101010101010\n1111111111111111111111111111111111111111\n1212121212121212121212121212121212121212\n\n@@ preflight 17 0 0 11\n1759998200\n\n@@ preflight 18 0 0 11\n1760000000\n\n@@ preflight 24 0 0 0\n\n"; + +pub const EV_P_TEN: &'static str = "@@ preflight 2 0 0 41\nd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 15 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 16 0 0 410\n1010101010101010101010101010101010101010\n1111111111111111111111111111111111111111\n1212121212121212121212121212121212121212\n1313131313131313131313131313131313131313\n1414141414141414141414141414141414141414\n1515151515151515151515151515151515151515\n1616161616161616161616161616161616161616\n1717171717171717171717171717171717171717\n1818181818181818181818181818181818181818\n1919191919191919191919191919191919191919\n\n@@ preflight 17 0 0 11\n1759998200\n\n@@ preflight 18 0 0 11\n1760000000\n\n@@ preflight 24 0 0 0\n\n"; + +pub const EV_P_GH: &'static str = "@@ preflight 2 0 0 41\nd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 15 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 16 0 0 123\n1010101010101010101010101010101010101010\n1111111111111111111111111111111111111111\n1212121212121212121212121212121212121212\n\n@@ preflight 17 0 0 11\n1759998800\n\n@@ preflight 18 0 0 11\n1760000000\n\n@@ preflight 19 0 0 53\n[{\"databaseId\":1},{\"databaseId\":2},{\"databaseId\":3}]\n\n@@ preflight 24 0 0 0\n\n"; + +pub const EV_P_BRANCHED: &'static str = "@@ preflight 2 0 0 41\nd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 15 0 1 0\n\n@@ preflight 16 0 0 123\n1010101010101010101010101010101010101010\n1111111111111111111111111111111111111111\n1212121212121212121212121212121212121212\n\n@@ preflight 18 0 0 11\n1760000000\n\n@@ preflight 20 0 0 0\n\n"; + +pub const EV_P_LANDED: &'static str = "@@ preflight 2 0 0 41\nd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 15 0 1 0\n\n@@ preflight 16 0 0 123\n1010101010101010101010101010101010101010\n1111111111111111111111111111111111111111\n1212121212121212121212121212121212121212\n\n@@ preflight 18 0 0 11\n1760000000\n\n@@ preflight 20 0 0 0\n\n@@ preflight 21 0 0 7\nLANDED\n\n"; + +pub const EV_P_REFUSED: &'static str = "@@ preflight 2 0 0 41\nd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 15 0 1 0\n\n@@ preflight 16 0 0 123\n1010101010101010101010101010101010101010\n1111111111111111111111111111111111111111\n1212121212121212121212121212121212121212\n\n@@ preflight 18 0 0 11\n1760000000\n\n@@ preflight 20 0 0 0\n\n@@ preflight 21 0 10 16\nTESTS_NOT_CLEAN\n\n"; + +pub const FS0: &'static str = "1010101010101010101010101010101010101010"; + +pub const FS1: &'static str = "1111111111111111111111111111111111111111"; + +pub const FS7: &'static str = "1717171717171717171717171717171717171717"; + +pub const FS8: &'static str = "1818181818181818181818181818181818181818"; + +pub const FS9: &'static str = "1919191919191919191919191919191919191919"; + +pub const EV_RG1: &'static str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 1 20\nVERIFICATION FAILED\n\n"; + +pub const EV_RG2: &'static str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 1 20\nVERIFICATION FAILED\n\n@@ preflight 10 0 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n"; + +pub const EV_RG3: &'static str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 1 20\nVERIFICATION FAILED\n\n@@ preflight 10 0 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 29 0 0 0\n\n"; + +pub const EV_RG4: &'static str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 1 20\nVERIFICATION FAILED\n\n@@ preflight 10 0 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 29 0 0 0\n\n@@ preflight 30 0 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n"; + +pub const EV_RG5: &'static str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 1 20\nVERIFICATION FAILED\n\n@@ preflight 10 0 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 29 0 0 0\n\n@@ preflight 30 0 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 31 0 0 0\n\n"; + +pub const EV_RG6: &'static str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 1 20\nVERIFICATION FAILED\n\n@@ preflight 10 0 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 29 0 0 0\n\n@@ preflight 30 0 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 31 0 0 0\n\n@@ preflight 32 0 0 30\n.trinity/seals/specs_x_a.json\n\n"; + +pub const EV_RG7: &'static str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 1 20\nVERIFICATION FAILED\n\n@@ preflight 10 0 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 29 0 0 0\n\n@@ preflight 30 0 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 31 0 0 0\n\n@@ preflight 32 0 0 30\n.trinity/seals/specs_x_a.json\n\n@@ preflight 33 0 0 0\n\n"; + +pub const EV_RG8: &'static str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 1 20\nVERIFICATION FAILED\n\n@@ preflight 10 0 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 29 0 0 0\n\n@@ preflight 30 0 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 31 0 0 0\n\n@@ preflight 32 0 0 30\n.trinity/seals/specs_x_a.json\n\n@@ preflight 33 0 0 0\n\n@@ preflight 34 0 0 41\n9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a\n\n"; + +pub const EV_REGEN: &'static str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 1 20\nVERIFICATION FAILED\n\n@@ preflight 10 0 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 29 0 0 0\n\n@@ preflight 30 0 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 31 0 0 0\n\n@@ preflight 32 0 0 30\n.trinity/seals/specs_x_a.json\n\n@@ preflight 33 0 0 0\n\n@@ preflight 34 0 0 41\n9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a\n\n@@ preflight 11 0 0 0\n\n"; + +pub const EV_RGBAD: &'static str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 1 20\nVERIFICATION FAILED\n\n@@ preflight 10 0 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 29 0 0 0\n\n@@ preflight 30 0 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 31 0 0 0\n\n@@ preflight 32 0 0 48\n.trinity/seals/specs_x_a.json\nspecs/x/other.t27\n\n@@ preflight 11 0 0 0\n\n"; + +pub const EV_RGTEST: &'static str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 1 20\nVERIFICATION FAILED\n\n@@ preflight 10 0 1 34\nTriX: 2 passed, 1 failed, 3 total\n\n@@ preflight 11 0 0 0\n\n"; + +pub const EV_RGOK: &'static str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 0 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 11 0 0 0\n\n"; + +pub const EV_RB_RESET: &'static str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 0 41\nd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 15 0 0 41\nd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4\n\n@@ preflight 24 0 1 0\n\n"; + +pub const EV_RB_RESET_UH: &'static str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 0 41\nd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 15 0 0 41\nd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4\n\n@@ preflight 24 0 1 0\n\n@@ preflight 27 0 0 0\n\n"; + +pub const EV_RB_HELD: &'static str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 0 41\nd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 15 0 0 41\nd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4\n\n@@ preflight 24 0 0 0\n\n"; + +pub const EV_RB_CARRY: &'static str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 0 41\nd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 15 0 0 41\n9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b\n\n@@ preflight 24 0 1 0\n\n"; + +pub const EV_RB_CMT: &'static str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 0 41\nd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 15 0 0 41\n9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b\n\n@@ preflight 24 0 1 0\n\n@@ preflight 25 0 0 41\nf6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6\n\n"; + +pub const EV_RB_CCT: &'static str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 0 41\nd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 15 0 0 41\n9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b\n\n@@ preflight 24 0 1 0\n\n@@ preflight 25 0 0 41\nf6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6\n\n@@ preflight 26 0 0 41\ne5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5\n\n"; + +pub const EV_RB_CONFLICT: &'static str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 0 41\nd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 15 0 0 41\n9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b\n\n@@ preflight 24 0 1 0\n\n@@ preflight 25 0 1 60\nf6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6\nCONFLICT (content)\n\n"; + +pub const EV_C1X: &'static str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 0 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 11 0 0 0\n\n@@ preflight 4 1 0 41\nc3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3\n\n@@ preflight 5 1 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 1 0 16\nA\tspecs/x/b.t27\n\n@@ preflight 7 1 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 1 0 0\n\n@@ preflight 9 1000 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 1000 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 11 1 0 0\n\n@@ preflight 14 0 0 0\n\n@@ preflight 12 1 1 57\ncccc\nCONFLICT (content): Merge conflict in specs/x/b.t27\n\n"; + +pub const T_ZERO: &'static str = "0000000000000000000000000000000000000000"; + +pub fn rq(verb: u8, plan: bool, queued: u64) -> GolReq { + return GolReq { verb: verb, plan: plan, queued: queued, issue: 11275, scratch: "/tmp/s", t27b: "t27b", t27c: "t27c", tri: "/tmp/tri" }; +} + +pub fn argv_is(r: GolReq, ev: &'static str, k: u8, n: u64, want: &'static str, buf: &mut [u8], wo: &mut [u8], wp: &mut [usize]) -> bool { + let m: usize = step_argv(r, ev, k, n, buf, wo, wp); + return ((m > 0) && buf_is(buf, m, want)); +} + +pub fn name_is(ev: &'static str, i: usize, want: &'static str) -> bool { + return span_is(ev, q_start(ev, i), line_end(ev, q_start(ev, i)), want); +} + +pub fn buf_has_not(buf: &[u8], n: usize, want: &'static str) -> bool { + let mut at: usize = 0; + while ((at + want.len()) <= n) { + let mut k: usize = 0; + while ((k < want.len()) && (buf[((at + k)) as usize] == want.as_bytes()[(k) as usize])) { + k = (k + 1); + } + if (k == want.len()) { + return false; + } + at = (at + 1); + } + return true; +} + +pub const RGS: &'static str = "9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a"; + +pub const PUS: &'static str = "9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b"; + +pub const CTS: &'static str = "e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5"; + +pub const EDIT_APPEND: u8 = 2; + +pub const EDIT_LINES: u8 = 1; + +pub const EDIT_WHOLE: u8 = 0; + +pub fn edits_meet(a_kind: u8, a_start: u32, a_end: u32, b_kind: u8, b_start: u32, b_end: u32) -> bool { + if ((a_kind == EDIT_APPEND) && (b_kind == EDIT_APPEND)) { + return false; + } + if (a_kind != EDIT_LINES) { + return true; + } + if (b_kind != EDIT_LINES) { + return true; + } + if (a_start >= a_end) { + return true; + } + if (b_start >= b_end) { + return true; + } + return ((a_start <= b_end) && (b_start <= a_end)); +} + +pub const GC_ADV_DISORDER: u8 = 3; + +pub const GC_ADV_FORWARD: u8 = 0; + +pub const GC_ADV_NOT_GOLDEN: u8 = 2; + +pub const GC_ADV_STALE: u8 = 1; + +pub const GC_BATCH_MAX: usize = 8; + +pub const GC_CI_JAM: u32 = 200; + +pub const GC_DONE: u8 = 5; + +pub const GC_EDIT_DERIVED: u8 = 3; + +pub const GC_OUT_CARRIED: u8 = 1; + +pub const GC_OUT_CONFLICT: u8 = 4; + +pub const GC_OUT_FORWARD: u8 = 0; + +pub const GC_OUT_NOT_GOLDEN: u8 = 5; + +pub const GC_OUT_REGEN: u8 = 2; + +pub const GC_OUT_REJUDGE: u8 = 3; + +pub const GC_OUT_UNKNOWN_PARENT: u8 = 6; + +pub const GC_PAIR_APART: u8 = 0; + +pub const GC_PAIR_MEET: u8 = 2; + +pub const GC_PAIR_REGEN: u8 = 1; + +pub const GC_PUSH_NOTHING: u8 = 0; + +pub const GC_PUSH_NOW: u8 = 2; + +pub const GC_PUSH_WAIT: u8 = 1; + +pub const GC_RB_CARRY: u8 = 2; + +pub const GC_RB_NONE: u8 = 0; + +pub const GC_RB_RESET: u8 = 1; + +pub const GC_RECEIPT: u8 = 4; + +pub const GC_REF: &'static str = "refs/golden/head"; + +pub const GC_SPEC: u8 = 0; + +pub const GC_STAGES: usize = 5; + +pub const GC_V_FAIL: u8 = 3; + +pub const GC_V_NONE: u8 = 0; + +pub const GC_V_PASS: u8 = 1; + +pub const GC_V_REUSED: u8 = 2; + +pub const GC_WINDOW_MIN: u32 = 15; + +pub fn gc_advance(head: u64, parent: u64, v: &[u8], i: usize) -> u8 { + if (gc_ordered(v, i) == false) { + return GC_ADV_DISORDER; + } + if (gc_next_stage(v, i) != GC_DONE) { + return GC_ADV_NOT_GOLDEN; + } + if (parent != head) { + return GC_ADV_STALE; + } + return GC_ADV_FORWARD; +} + +pub fn gc_carry_one(i: usize, j: usize, m: usize, e_owner: impl AsRef<[usize]>, e_path: impl AsRef<[u64]>, e_kind: &[u8], e_lo: impl AsRef<[u32]>, e_hi: impl AsRef<[u32]>, q: usize, i_owner: impl AsRef<[usize]>, i_path: impl AsRef<[u64]>) -> u8 { + let e_owner: &[usize] = e_owner.as_ref(); + let e_path: &[u64] = e_path.as_ref(); + let e_lo: &[u32] = e_lo.as_ref(); + let e_hi: &[u32] = e_hi.as_ref(); + let i_owner: &[usize] = i_owner.as_ref(); + let i_path: &[u64] = i_path.as_ref(); + let mut worst: u8 = GC_OUT_CARRIED; + let mut a: usize = 0; + while (a < m) { + if (e_owner[(a) as usize] == i) { + let mut b: usize = 0; + while (b < m) { + if (e_owner[(b) as usize] == j) { + let mut p: u8 = gc_pair(e_path[(a) as usize], e_kind[(a) as usize], e_lo[(a) as usize], e_hi[(a) as usize], e_path[(b) as usize], e_kind[(b) as usize], e_lo[(b) as usize], e_hi[(b) as usize]); + if (p == GC_PAIR_MEET) { + return GC_OUT_CONFLICT; + } + if ((p == GC_PAIR_REGEN) && (worst < GC_OUT_REGEN)) { + worst = GC_OUT_REGEN; + } + } + b = (b + 1); + } + } + a = (a + 1); + } + let mut k: usize = 0; + while (k < q) { + let mut o: usize = i_owner[(k) as usize]; + if ((o == i) || (o == j)) { + let mut other: usize = j; + if (o == j) { + other = i; + } + let mut e: usize = 0; + while (e < m) { + if (((e_owner[(e) as usize] == other) && (e_kind[(e) as usize] != GC_EDIT_DERIVED)) && (e_path[(e) as usize] == i_path[(k) as usize])) { + worst = GC_OUT_REJUDGE; + } + e = (e + 1); + } + } + k = (k + 1); + } + return worst; +} + +pub fn gc_golden(v: u8) -> bool { + return ((v == GC_V_PASS) || (v == GC_V_REUSED)); +} + +pub fn gc_land(n: usize, id: impl AsRef<[u64]>, parent: impl AsRef<[u64]>, v: &[u8], m: usize, e_owner: impl AsRef<[usize]>, e_path: impl AsRef<[u64]>, e_kind: &[u8], e_lo: impl AsRef<[u32]>, e_hi: impl AsRef<[u32]>, q: usize, i_owner: impl AsRef<[usize]>, i_path: impl AsRef<[u64]>, head: u64, out: &mut [u8], pos: &mut [usize]) -> usize { + let id: &[u64] = id.as_ref(); + let parent: &[u64] = parent.as_ref(); + let e_owner: &[usize] = e_owner.as_ref(); + let e_path: &[u64] = e_path.as_ref(); + let e_lo: &[u32] = e_lo.as_ref(); + let e_hi: &[u32] = e_hi.as_ref(); + let i_owner: &[usize] = i_owner.as_ref(); + let i_path: &[u64] = i_path.as_ref(); + let mut landed: usize = 0; + let mut cur: u64 = head; + let mut i: usize = 0; + while (i < n) { + pos[(i) as usize] = n; + let mut adv: u8 = gc_advance(cur, parent[(i) as usize], v, i); + if (adv == GC_ADV_FORWARD) { + out[(i) as usize] = GC_OUT_FORWARD; + } else { + if (adv != GC_ADV_STALE) { + out[(i) as usize] = GC_OUT_NOT_GOLDEN; + } else { + let mut since: usize = n; + if (parent[(i) as usize] == head) { + since = 0; + } else { + let mut jj: usize = 0; + while (jj < i) { + if ((id[(jj) as usize] == parent[(i) as usize]) && (pos[(jj) as usize] < n)) { + since = (pos[(jj) as usize] + 1); + jj = i; + } else { + jj = (jj + 1); + } + } + } + if ((since == n) && (parent[(i) as usize] != 0)) { + since = 0; + } + if (since == n) { + out[(i) as usize] = GC_OUT_UNKNOWN_PARENT; + } else { + let mut worst: u8 = GC_OUT_CARRIED; + let mut j: usize = 0; + while (j < i) { + if ((pos[(j) as usize] < n) && (pos[(j) as usize] >= since)) { + let mut c: u8 = gc_carry_one(i, j, m, e_owner, e_path, e_kind, e_lo, e_hi, q, i_owner, i_path); + if (c > worst) { + worst = c; + } + } + j = (j + 1); + } + out[(i) as usize] = worst; + } + } + } + if gc_landed(out[(i) as usize]) { + pos[(i) as usize] = landed; + landed = (landed + 1); + cur = id[(i) as usize]; + } + i = (i + 1); + } + return landed; +} + +pub fn gc_landed(out: u8) -> bool { + return (((out == GC_OUT_FORWARD) || (out == GC_OUT_CARRIED)) || (out == GC_OUT_REGEN)); +} + +pub fn gc_next_stage(v: &[u8], i: usize) -> u8 { + let mut st: u8 = 0; + let mut k: usize = (i * GC_STAGES); + while (st < GC_DONE) { + if (gc_golden(v[(k) as usize]) == false) { + return st; + } + st = (st + 1); + k = (k + 1); + } + return GC_DONE; +} + +pub fn gc_ordered(v: &[u8], i: usize) -> bool { + let mut gap: bool = false; + let mut k: usize = 0; + while (k < GC_STAGES) { + let mut g: bool = gc_golden(v[(((i * GC_STAGES) + k)) as usize]); + if (g && gap) { + return false; + } + if (g == false) { + gap = true; + } + k = (k + 1); + } + return true; +} + +pub fn gc_pair(a_path: u64, a_kind: u8, a_lo: u32, a_hi: u32, b_path: u64, b_kind: u8, b_lo: u32, b_hi: u32) -> u8 { + if (a_path != b_path) { + return GC_PAIR_APART; + } + if ((a_kind == GC_EDIT_DERIVED) && (b_kind == GC_EDIT_DERIVED)) { + return GC_PAIR_REGEN; + } + if edits_meet(a_kind, a_lo, a_hi, b_kind, b_lo, b_hi) { + return GC_PAIR_MEET; + } + return GC_PAIR_APART; +} + +pub fn gc_rebase(head_ok: bool, pushed_ok: bool, same: bool, master_has_head: bool) -> u8 { + if (((head_ok == false) || (pushed_ok == false)) || master_has_head) { + return GC_RB_NONE; + } + if same { + return GC_RB_RESET; + } + return GC_RB_CARRY; +} + +pub fn gc_regen_ok(changed: usize, seals: usize) -> bool { + return (changed == seals); +} + +pub fn gc_regen_view(out: u8, resealed: bool) -> u8 { + if (resealed && ((out == GC_OUT_FORWARD) || (out == GC_OUT_CARRIED))) { + return GC_OUT_REGEN; + } + return out; +} + +pub fn when_to_push(minutes_since_last: u32, batch_len: usize, ci_queued: u32) -> u8 { + if (batch_len == 0) { + return GC_PUSH_NOTHING; + } + let mut window: u32 = GC_WINDOW_MIN; + let mut jam: bool = (ci_queued >= GC_CI_JAM); + if jam { + window = (GC_WINDOW_MIN * 2); + } + if (minutes_since_last >= window) { + return GC_PUSH_NOW; + } + if ((batch_len >= GC_BATCH_MAX) && (jam == false)) { + return GC_PUSH_NOW; + } + return GC_PUSH_WAIT; +} + +#[allow(non_upper_case_globals)] +pub const tri_lab_receipt__V_BEHIND: u8 = 2; + +#[allow(non_upper_case_globals)] +pub const tri_lab_receipt__V_BLOCKED: u8 = 3; + +pub const V_NOT_RUN: u8 = 4; + +#[allow(non_upper_case_globals)] +pub const tri_lab_receipt__V_RED: u8 = 5; + +pub const V_VACUOUS: u8 = 1; + +pub const V_VERIFIED: u8 = 0; + +pub fn line_end(text: &'static str, at: usize) -> usize { + let mut k: usize = at; + while ((k < text.len()) && (text.as_bytes()[(k) as usize] != 10)) { + k = (k + 1); + } + return k; +} + +pub fn tri_lab_receipt__name(v: u8) -> &'static str { + if (v == V_VERIFIED) { + return "verified"; + } + if (v == V_VACUOUS) { + return "vacuous"; + } + if (v == tri_lab_receipt__V_BEHIND) { + return "behind"; + } + if (v == tri_lab_receipt__V_BLOCKED) { + return "blocked"; + } + if (v == V_NOT_RUN) { + return "not-run"; + } + if (v == tri_lab_receipt__V_RED) { + return "RED"; + } + return "UNREAD"; +} + +pub fn span_is(text: &'static str, from: usize, to: usize, lit: &'static str) -> bool { + if ((to < from) || ((to - from) != lit.len())) { + return false; + } + let mut k: usize = 0; + while (k < lit.len()) { + if (text.as_bytes()[((from + k)) as usize] != lit.as_bytes()[(k) as usize]) { + return false; + } + k = (k + 1); + } + return true; +} + +pub const L_HEAD: u8 = 27; + +pub const L_PUSH: u8 = 28; + +pub const L_REMOTE_HEAD: u8 = 29; + +pub fn emit_section(out: &mut [u8], w: usize, k: u8, n: u64, x: u64, body: &'static str) -> usize { + let mut p: usize = emit_str(out, w, "@@ preflight "); + p = emit_dec(out, p, (k as u64)); + p = emit_byte(out, p, 32); + p = emit_dec(out, p, n); + p = emit_byte(out, p, 32); + p = emit_dec(out, p, x); + p = emit_byte(out, p, 32); + p = emit_dec(out, p, (body.len() as u64)); + p = emit_byte(out, p, 10); + p = emit_str(out, p, body); + return emit_byte(out, p, 10); +} + +pub fn pushed(ev: &'static str) -> bool { + let hf: usize = body_from(ev, L_HEAD, 0); + let rf: usize = body_from(ev, L_REMOTE_HEAD, 0); + if (((exit_of(ev, L_PUSH, 0) != 0) || (exit_of(ev, L_HEAD, 0) != 0)) || (exit_of(ev, L_REMOTE_HEAD, 0) != 0)) { + return false; + } + if ((sha_at(ev, hf, body_to(ev, L_HEAD, 0)) == false) || (sha_at(ev, rf, body_to(ev, L_REMOTE_HEAD, 0)) == false)) { + return false; + } + return same(ev, hf, ev, rf, (rf + 40)); +} + +pub fn sha_at(ev: &'static str, from: usize, to: usize) -> bool { + if ((from + 40) > to) { + return false; + } + let mut i: usize = from; + while (i < (from + 40)) { + let c: u8 = ev.as_bytes()[(i) as usize]; + if ((((c >= 48) && (c <= 57)) || ((c >= 97) && (c <= 102))) == false) { + return false; + } + i = (i + 1); + } + return (((((from + 40) == to) || (ev.as_bytes()[((from + 40)) as usize] == 10)) || (ev.as_bytes()[((from + 40)) as usize] == 9)) || (ev.as_bytes()[((from + 40)) as usize] == 32)); +} + +pub fn digits(text: &'static str, from: usize, to: usize) -> u64 { + if ((to <= from) || ((to - from) > 18)) { + return 0; + } + let mut n: u64 = 0; + let mut i: usize = from; + while (i < to) { + if ((text.as_bytes()[(i) as usize] < 48) || (text.as_bytes()[(i) as usize] > 57)) { + return 0; + } + n = ((n * 10) + ((text.as_bytes()[(i) as usize] as u64) - 48)); + i = (i + 1); + } + return n; +} + +pub const MAX_DIGITS: usize = 18; + +pub const NO_EXIT: u64 = 256; + +pub fn body_from(ev: &'static str, k: u8, n: u64) -> usize { + let s: usize = section(ev, k, n); + if (s >= ev.len()) { + return ev.len(); + } + return header_end(ev, s); +} + +pub fn body_to(ev: &'static str, k: u8, n: u64) -> usize { + let s: usize = section(ev, k, n); + if (s >= ev.len()) { + return ev.len(); + } + return (header_end(ev, s) + (field(ev, s, 3) as usize)); +} + +pub fn digits_end(text: &'static str, at: usize, to: usize) -> usize { + let mut k: usize = at; + while (((k < to) && (text.as_bytes()[(k) as usize] >= 48)) && (text.as_bytes()[(k) as usize] <= 57)) { + k = (k + 1); + } + return k; +} + +pub fn entry_at(c: &'static str, n: u64) -> usize { + let mut at: usize = 0; + let mut seen: u64 = 0; + while (at < c.len()) { + let end: usize = line_end(c, at); + if (end > at) { + if (seen == n) { + return at; + } + seen = (seen + 1); + } + at = (end + 1); + } + return c.len(); +} + +pub fn exit_of(ev: &'static str, k: u8, n: u64) -> u64 { + let s: usize = section(ev, k, n); + if (s >= ev.len()) { + return NO_EXIT; + } + return field(ev, s, 2); +} + +pub fn field(ev: &'static str, at: usize, f: u64) -> u64 { + let mut k: usize = (at + 13); + let mut i: u64 = 0; + while (i < f) { + k = (digits_end(ev, k, ev.len()) + 1); + i = (i + 1); + } + return digits(ev, k, digits_end(ev, k, ev.len())); +} + +pub fn find_in(text: &'static str, from: usize, to: usize, lit: &'static str) -> usize { + let mut k: usize = from; + while ((k + lit.len()) <= to) { + if span_is(text, k, (k + lit.len()), lit) { + return k; + } + k = (k + 1); + } + return (text.len() + 1); +} + +pub fn header_end(ev: &'static str, at: usize) -> usize { + if (at >= ev.len()) { + return (ev.len() + 1); + } + let end: usize = line_end(ev, at); + if ((end >= ev.len()) || (opens_with(ev, at, end, "@@ preflight ") == false)) { + return (ev.len() + 1); + } + let mut k: usize = (at + 13); + let mut f: u64 = 0; + while (f < 4) { + let e: usize = digits_end(ev, k, end); + if ((e == k) || ((e - k) > MAX_DIGITS)) { + return (ev.len() + 1); + } + k = e; + if (f < 3) { + if ((k >= end) || (ev.as_bytes()[(k) as usize] != 32)) { + return (ev.len() + 1); + } + k = (k + 1); + } + f = (f + 1); + } + if (k != end) { + return (ev.len() + 1); + } + return (end + 1); +} + +pub fn next_section(ev: &'static str, at: usize) -> usize { + let he: usize = header_end(ev, at); + if (he > ev.len()) { + return (ev.len() + 1); + } + let len: u64 = field(ev, at, 3); + if (len >= ((ev.len() - he) as u64)) { + return (ev.len() + 1); + } + let to: usize = (he + (len as usize)); + if (ev.as_bytes()[(to) as usize] != 10) { + return (ev.len() + 1); + } + return (to + 1); +} + +pub fn opens_with(text: &'static str, at: usize, end: usize, lit: &'static str) -> bool { + if (at > end) { + return false; + } + if (lit.len() > (end - at)) { + return false; + } + return span_is(text, at, (at + lit.len()), lit); +} + +pub fn same(a: &'static str, af: usize, b: &'static str, bf: usize, bt: usize) -> bool { + if ((bt < bf) || ((af + (bt - bf)) > a.len())) { + return false; + } + let mut i: usize = 0; + while (i < (bt - bf)) { + if (a.as_bytes()[((af + i)) as usize] != b.as_bytes()[((bf + i)) as usize]) { + return false; + } + i = (i + 1); + } + return true; +} + +pub fn section(ev: &'static str, k: u8, n: u64) -> usize { + let mut at: usize = 0; + while (at < ev.len()) { + let nx: usize = next_section(ev, at); + if (nx > ev.len()) { + return (ev.len() + 1); + } + if ((field(ev, at, 0) == (k as u64)) && (field(ev, at, 1) == n)) { + return at; + } + at = nx; + } + return (ev.len() + 1); +} + +pub fn status(c: &'static str, n: u64) -> u8 { + let at: usize = entry_at(c, n); + if (at >= c.len()) { + return 0; + } + return c.as_bytes()[(at) as usize]; +} + +pub const GOLDEN_FETCH: &'static str = "cd /work && curl -sSfL -o top.spice "; + +pub const GOLDEN_INSERT: &'static str = " && printf 'pre_osdi /opt/osdi/psp103.osdi\\npre_osdi /opt/osdi/psp103_nqs.osdi\\npre_osdi /opt/osdi/r3_cmc.osdi\\npre_osdi /opt/osdi/mosvar.osdi\\npre_osdi /opt/osdi/cap_cmomi.osdi\\npre_osdi /opt/osdi/cap_cmomf.osdi\\noption klu\\nset num_threads="; + +pub const GOLDEN_SPLICE: &'static str = "\\n' > ins.txt && sed -e 's,/usr/local/share/OpenPDKs/IHP-Open-PDK,/opt/IHP-Open-PDK,g' -e '/^\\.control/r ins.txt' top.spice > run.spice && ngspice -b run.spice > ngspice.log 2>&1"; + +pub const HOST_MAX: usize = 96; + +pub const IMAGE_MAX: usize = 128; + +pub const NAME_MAX: usize = 64; + +pub const OP_FETCH: u8 = 6; + +pub const OP_LOGS: u8 = 4; + +pub const OP_NONE: u8 = 0; + +pub const OP_PING: u8 = 1; + +pub const OP_RUN: u8 = 2; + +pub const OP_STATUS: u8 = 3; + +pub const PATH_MAX: usize = 256; + +pub const R_BAD_DEST: u8 = 9; + +pub const R_BAD_FILE: u8 = 8; + +pub const R_BAD_HOST: u8 = 3; + +pub const R_BAD_IMAGE: u8 = 4; + +pub const R_BAD_NAME: u8 = 6; + +pub const R_BAD_THREADS: u8 = 12; + +pub const R_BAD_URL: u8 = 11; + +pub const R_NO_ACTION: u8 = 1; + +pub const R_NO_FILE: u8 = 7; + +pub const R_NO_HOST: u8 = 2; + +pub const R_NO_NAME: u8 = 5; + +pub const R_NO_URL: u8 = 10; + +pub const R_OK: u8 = 0; + +pub const R_UNEXPECTED: u8 = 13; + +pub const SCRIPT_PLAIN: &'static str = "cd /work && ngspice -b deck.cir > ngspice.log 2>&1"; + +pub const STEM_MAX: usize = 40; + +pub const THREADS_MAX: u64 = 16; + +pub const THREADS_MIN: u64 = 1; + +#[derive(Debug, Clone, Copy)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct TriRemoteReq { + pub op: u8, + pub host: &'static str, + pub image: &'static str, + pub name: &'static str, + pub file: &'static str, + pub dest: &'static str, + pub url: &'static str, + pub threads: u64, + pub counter: u64, + pub golden: bool, +} + +pub const URL_MAX: usize = 200; + +pub fn base_start(p: &'static str) -> usize { + let mut b: usize = 0; + let mut i: usize = 0; + while (i < p.len()) { + if (p.as_bytes()[(i) as usize] == 47) { + b = (i + 1); + } + i = (i + 1); + } + return b; +} + +pub fn buf_is(buf: &[u8], n: usize, want: &'static str) -> bool { + if ((n != want.len()) || (n > buf.len())) { + return false; + } + let mut k: usize = 0; + while (k < n) { + if (buf[(k) as usize] != want.as_bytes()[(k) as usize]) { + return false; + } + k = (k + 1); + } + return true; +} + +pub fn command(r: TriRemoteReq, step: u8, out: &mut [u8]) -> usize { + if (refusal(r) != R_OK) { + return 0; + } + let mut w: usize = 0; + if (r.op == OP_PING) { + if (step != 0) { + return 0; + } + w = emit_ssh(out, w, r.host); + w = emit_str(out, w, "true\n"); + return fits(out, w); + } + if (r.op == OP_RUN) { + let mut run_step: u8 = 2; + if r.golden { + run_step = 1; + } + if (step == 0) { + w = emit_ssh(out, w, r.host); + w = emit_str(out, w, "mkdir -p $HOME/t27-jobs/"); + w = emit_id(out, w, r); + w = emit_byte(out, w, 10); + return fits(out, w); + } + if ((step == 1) && (r.golden == false)) { + w = emit_scp(out, w); + w = emit_str(out, w, r.name); + w = emit_byte(out, w, 10); + w = emit_str(out, w, r.host); + w = emit_str(out, w, ":t27-jobs/"); + w = emit_id(out, w, r); + w = emit_str(out, w, "/deck.cir\n"); + return fits(out, w); + } + if (step == run_step) { + w = emit_run_head(out, w, r); + w = emit_script(out, w, r); + w = emit_str(out, w, "\"\n"); + return fits(out, w); + } + return 0; + } + if (step != 0) { + return 0; + } + if (r.op == OP_FETCH) { + w = emit_scp(out, w); + w = emit_str(out, w, r.host); + w = emit_str(out, w, ":t27-jobs/"); + w = emit_str(out, w, r.name); + w = emit_byte(out, w, 47); + w = emit_str(out, w, r.file); + w = emit_byte(out, w, 10); + if (r.dest.len() == 0) { + w = emit_byte(out, w, 46); + } else { + w = emit_str(out, w, r.dest); + } + w = emit_str(out, w, "/\n"); + return fits(out, w); + } + w = emit_ssh(out, w, r.host); + if (r.op == OP_STATUS) { + w = emit_str(out, w, "docker ps -a --filter \"name=^t27job-"); + w = emit_str(out, w, r.name); + w = emit_str(out, w, "$\" --format \"{{.Names}} {{.Status}}\"\n"); + return fits(out, w); + } + if (r.op == OP_LOGS) { + w = emit_str(out, w, "docker logs --tail 40 t27job-"); + w = emit_str(out, w, r.name); + w = emit_str(out, w, "; tail -n 40 $HOME/t27-jobs/"); + w = emit_str(out, w, r.name); + w = emit_str(out, w, "/ngspice.log\n"); + return fits(out, w); + } + w = emit_str(out, w, "docker rm -f t27job-"); + w = emit_str(out, w, r.name); + w = emit_byte(out, w, 10); + return fits(out, w); +} + +pub fn emit_byte(out: &mut [u8], w: usize, c: u8) -> usize { + if (w < out.len()) { + out[(w) as usize] = c; + } + return (w + 1); +} + +pub fn emit_dec(out: &mut [u8], w: usize, v: u64) -> usize { + let mut div: u64 = 1; + while ((v / div) >= 10) { + div = (div * 10); + } + let mut p: usize = w; + while (div > 0) { + p = emit_byte(out, p, (48 + (((v / div) % 10) as u8))); + div = (div / 10); + } + return p; +} + +pub fn emit_id(out: &mut [u8], w: usize, r: TriRemoteReq) -> usize { + if (r.op != OP_RUN) { + return emit_str(out, w, r.name); + } + let mut p: usize = emit_stem(out, w, r.name); + p = emit_byte(out, p, 45); + return emit_dec(out, p, r.counter); +} + +pub fn emit_run_head(out: &mut [u8], w: usize, r: TriRemoteReq) -> usize { + let mut p: usize = emit_ssh(out, w, r.host); + p = emit_str(out, p, "docker run -d --name t27job-"); + p = emit_id(out, p, r); + p = emit_str(out, p, " -v $HOME/t27-jobs/"); + p = emit_id(out, p, r); + p = emit_str(out, p, ":/work "); + p = emit_str(out, p, r.image); + return emit_str(out, p, " sh -c \""); +} + +pub fn emit_scp(out: &mut [u8], w: usize) -> usize { + return emit_str(out, w, "scp\n-o\nBatchMode=yes\n"); +} + +pub fn emit_script(out: &mut [u8], w: usize, r: TriRemoteReq) -> usize { + if (r.golden == false) { + return emit_str(out, w, SCRIPT_PLAIN); + } + let mut p: usize = emit_str(out, w, GOLDEN_FETCH); + p = emit_str(out, p, r.url); + p = emit_str(out, p, GOLDEN_INSERT); + p = emit_dec(out, p, r.threads); + return emit_str(out, p, GOLDEN_SPLICE); +} + +pub fn emit_ssh(out: &mut [u8], w: usize, host: &'static str) -> usize { + let mut p: usize = emit_str(out, w, "ssh\n-o\nBatchMode=yes\n"); + p = emit_str(out, p, host); + return emit_byte(out, p, 10); +} + +pub fn emit_stem(out: &mut [u8], w: usize, deck: &'static str) -> usize { + let b: usize = base_start(deck); + let mut e: usize = deck.len(); + let mut i: usize = (b + 1); + while (i < deck.len()) { + if (deck.as_bytes()[(i) as usize] == 46) { + e = i; + } + i = (i + 1); + } + let mut p: usize = w; + let mut k: usize = b; + while ((k < e) && ((k - b) < STEM_MAX)) { + if (deck.as_bytes()[(k) as usize] == 46) { + p = emit_byte(out, p, 95); + } else { + p = emit_byte(out, p, deck.as_bytes()[(k) as usize]); + } + k = (k + 1); + } + return p; +} + +pub fn emit_str(out: &mut [u8], w: usize, s: &'static str) -> usize { + let mut p: usize = w; + let mut i: usize = 0; + while (i < s.len()) { + p = emit_byte(out, p, s.as_bytes()[(i) as usize]); + i = (i + 1); + } + return p; +} + +pub fn fits(out: impl AsRef<[u8]>, w: usize) -> usize { + let out: &[u8] = out.as_ref(); + if (w > out.len()) { + return 0; + } + return w; +} + +pub fn is_alnum(c: u8) -> bool { + return ((((c >= 97) && (c <= 122)) || ((c >= 65) && (c <= 90))) || ((c >= 48) && (c <= 57))); +} + +pub fn name_span_ok(text: &'static str, from: usize, to: usize) -> bool { + if ((to <= from) || ((to - from) > NAME_MAX)) { + return false; + } + if ((text.as_bytes()[(from) as usize] == 45) || (text.as_bytes()[(from) as usize] == 46)) { + return false; + } + let mut i: usize = from; + while (i < to) { + let c: u8 = text.as_bytes()[(i) as usize]; + if ((((is_alnum(c) == false) && (c != 95)) && (c != 45)) && (c != 46)) { + return false; + } + i = (i + 1); + } + return true; +} + +pub fn refusal(r: TriRemoteReq) -> u8 { + if ((r.op == OP_NONE) || (r.op > OP_FETCH)) { + return R_NO_ACTION; + } + if (r.host.len() == 0) { + return R_NO_HOST; + } + if (safe_host(r.host) == false) { + return R_BAD_HOST; + } + if (r.golden && (r.op != OP_RUN)) { + return R_UNEXPECTED; + } + if ((r.op == OP_PING) && (r.name.len() > 0)) { + return R_UNEXPECTED; + } + if ((r.op != OP_FETCH) && ((r.file.len() > 0) || (r.dest.len() > 0))) { + return R_UNEXPECTED; + } + if ((r.op != OP_PING) && (r.name.len() == 0)) { + return R_NO_NAME; + } + if (r.op == OP_RUN) { + if ((r.golden == false) && (safe_deck(r.name) == false)) { + return R_BAD_NAME; + } + if (r.golden && (safe_name(r.name) == false)) { + return R_BAD_NAME; + } + if (safe_image(r.image) == false) { + return R_BAD_IMAGE; + } + if (r.golden && (r.url.len() == 0)) { + return R_NO_URL; + } + if (r.golden && (safe_url(r.url) == false)) { + return R_BAD_URL; + } + if (r.golden && ((r.threads < THREADS_MIN) || (r.threads > THREADS_MAX))) { + return R_BAD_THREADS; + } + return R_OK; + } + if ((r.op != OP_PING) && (safe_id(r.name) == false)) { + return R_BAD_NAME; + } + if (r.op == OP_FETCH) { + if (r.file.len() == 0) { + return R_NO_FILE; + } + if (safe_name(r.file) == false) { + return R_BAD_FILE; + } + if ((r.dest.len() > 0) && (safe_path(r.dest) == false)) { + return R_BAD_DEST; + } + } + return R_OK; +} + +pub fn safe_deck(p: &'static str) -> bool { + return (safe_path(p) && name_span_ok(p, base_start(p), p.len())); +} + +pub fn safe_host(h: &'static str) -> bool { + if (((((h.len() == 0) || (h.len() > HOST_MAX)) || (h.as_bytes()[0] == 45)) || (h.as_bytes()[0] == 64)) || (h.as_bytes()[((h.len() - 1)) as usize] == 64)) { + return false; + } + let mut ats: usize = 0; + let mut i: usize = 0; + while (i < h.len()) { + let c: u8 = h.as_bytes()[(i) as usize]; + if (c == 64) { + ats = (ats + 1); + } + if (((((is_alnum(c) == false) && (c != 95)) && (c != 45)) && (c != 46)) && (c != 64)) { + return false; + } + i = (i + 1); + } + return (ats <= 1); +} + +pub fn safe_id(s: &'static str) -> bool { + if (safe_name(s) == false) { + return false; + } + let mut i: usize = 0; + while (i < s.len()) { + if (s.as_bytes()[(i) as usize] == 46) { + return false; + } + i = (i + 1); + } + return true; +} + +pub fn safe_image(s: &'static str) -> bool { + if (((s.len() == 0) || (s.len() > IMAGE_MAX)) || (is_alnum(s.as_bytes()[0]) == false)) { + return false; + } + let mut i: usize = 0; + while (i < s.len()) { + let c: u8 = s.as_bytes()[(i) as usize]; + if ((((((is_alnum(c) == false) && (c != 95)) && (c != 45)) && (c != 46)) && (c != 58)) && (c != 47)) { + return false; + } + i = (i + 1); + } + return true; +} + +pub fn safe_name(s: &'static str) -> bool { + return name_span_ok(s, 0, s.len()); +} + +pub fn safe_path(p: &'static str) -> bool { + if (((p.len() == 0) || (p.len() > PATH_MAX)) || (p.as_bytes()[0] == 45)) { + return false; + } + let mut i: usize = 0; + while (i < p.len()) { + let c: u8 = p.as_bytes()[(i) as usize]; + if (((((is_alnum(c) == false) && (c != 95)) && (c != 45)) && (c != 46)) && (c != 47)) { + return false; + } + if (((c == 46) && ((i + 1) < p.len())) && (p.as_bytes()[((i + 1)) as usize] == 46)) { + return false; + } + i = (i + 1); + } + return true; +} + +pub fn safe_url(s: &'static str) -> bool { + if (((s.len() <= 8) || (s.len() > URL_MAX)) || (span_is(s, 0, 8, "https://") == false)) { + return false; + } + let mut i: usize = 8; + while (i < s.len()) { + let c: u8 = s.as_bytes()[(i) as usize]; + if (((((is_alnum(c) == false) && (c != 95)) && (c != 45)) && (c != 46)) && (c != 47)) { + return false; + } + i = (i + 1); + } + return true; +} + +pub const EVIDENCE_OTHER_RUN: u8 = 5; + +pub const EVIDENCE_UNBOUND: u8 = 4; + +pub const LINKED: u8 = 0; + +pub const NO_CHANGE: u8 = 1; + +pub const NO_LOGS: u8 = 8; + +pub const NO_OUTPUTS: u8 = 6; + +pub const NO_TESTS: u8 = 7; + +pub const RUN_OTHER_CHANGE: u8 = 3; + +pub const RUN_UNBOUND: u8 = 2; + +pub const SEAL_MATCH: u8 = 1; + +pub const SEAL_NOT_VERIFIED: u8 = 9; + +pub fn chain(change: u64, run_change: u64, run: u64, ev_run: u64, has_outputs: bool, has_tests: bool, has_logs: bool, seal: u8) -> u8 { + if (change == 0) { + return NO_CHANGE; + } + if ((run == 0) || (run_change == 0)) { + return RUN_UNBOUND; + } + if (run_change != change) { + return RUN_OTHER_CHANGE; + } + if (ev_run == 0) { + return EVIDENCE_UNBOUND; + } + if (ev_run != run) { + return EVIDENCE_OTHER_RUN; + } + if (has_outputs == false) { + return NO_OUTPUTS; + } + if (has_tests == false) { + return NO_TESTS; + } + if (has_logs == false) { + return NO_LOGS; + } + if (seal != SEAL_MATCH) { + return SEAL_NOT_VERIFIED; + } + return LINKED; +} + +pub const FAIL: u8 = 1; + +pub const PASS: u8 = 0; + diff --git a/scripts/ultra_engine_v3.py b/scripts/ultra_engine_v3.py deleted file mode 100755 index dcf3d89d93..0000000000 --- a/scripts/ultra_engine_v3.py +++ /dev/null @@ -1,249 +0,0 @@ -#!/usr/bin/env python3 -""" -ULTRA ENGINE v3.0 — Trinity Formula Discovery Engine -phi^2 + 1/phi^2 = 3 | TRINITY -""" - -import math -import sys -from typing import List, Dict, Tuple - -# Trinity constants -PHI = 1.6180339887498948 -PI = math.pi -E = math.e - -# PDG 2024 targets -PDG_TARGETS = { - 'gamma': 0.23607, - 'alpha_s': 0.118034, - 'alpha_inv': 137.036, - 'theta_C': 0.22651, - 'V_ud': 0.97435, - 'V_us': 0.22431, - 'V_cb': 0.04100, - 'V_td': 0.00868, - 'V_cs': 0.97548, - 'V_ub': 0.0037, - 'sin2theta12': 0.307, - 'sin2theta13': 0.02195, - 'sin2theta23': 0.547, - 'delta_CP': 196.965, - 'delta_CP_rad': 3.438299, - 'mH_mZ': 1.37354, - 'W_mass': 80.377, - 'Z_mass': 91.1876, - 'top_mass': 172.69, - 'ns': 0.9649, - 'Omega_b': 0.04897, - 'Tc': 156.5, -} - -def pattern_search(threshold: float = 0.01, verbose: bool = True) -> List[Dict]: - """Pattern search: try n*phi^i*pi^j*e^k patterns.""" - results = [] - - for target_name, target_val in PDG_TARGETS.items(): - if target_val == 0: - continue - - # Search bounds - for n_val in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 16]: - for i in range(-6, 7): - # Pattern 1: n * phi^i - val1 = n_val * PHI**i - error_pct = abs(val1 - target_val) / abs(target_val) * 100.0 - if error_pct < threshold: - results.append({ - 'expr': f'{n_val}*phi^{i}', - 'target_name': target_name, - 'target_value': target_val, - 'chimera_value': val1, - 'error_pct': error_pct, - 'status': 'APPROX' if error_pct < 0.1 else 'CANDIDATE' - }) - - for j in range(-6, 7): - # Pattern 2: n * phi^i * pi^j - val2 = n_val * PHI**i * PI**j - error_pct = abs(val2 - target_val) / abs(target_val) * 100.0 - if error_pct < threshold: - results.append({ - 'expr': f'{n_val}*phi^{i}*pi^{j}', - 'target_name': target_name, - 'target_value': target_val, - 'chimera_value': val2, - 'error_pct': error_pct, - 'status': 'APPROX' if error_pct < 0.1 else 'CANDIDATE' - }) - - for k in range(-6, 7): - # Pattern 3: n * phi^i * pi^j * e^k - val3 = n_val * PHI**i * PI**j * E**k - error_pct = abs(val3 - target_val) / abs(target_val) * 100.0 - if error_pct < threshold: - results.append({ - 'expr': f'{n_val}*phi^{i}*pi^{j}*e^{k}', - 'target_name': target_name, - 'target_value': target_val, - 'chimera_value': val3, - 'error_pct': error_pct, - 'status': 'APPROX' if error_pct < 0.1 else 'CANDIDATE' - }) - - # Pattern 4: n * phi^i / (pi^j * e^k) - val4 = n_val * PHI**i / (PI**j * E**k) if (PI**j * E**k) != 0 else 0 - if val4 != 0: - error_pct = abs(val4 - target_val) / abs(target_val) * 100.0 - if error_pct < threshold: - results.append({ - 'expr': f'{n_val}*phi^{i}/(pi^{j}*e^{k})', - 'target_name': target_name, - 'target_value': target_val, - 'chimera_value': val4, - 'error_pct': error_pct, - 'status': 'APPROX' if error_pct < 0.1 else 'CANDIDATE' - }) - - results.sort(key=lambda x: x['error_pct']) - return results - - -def chimera_search(base_formulas: List[Tuple[str, float]], threshold: float = 0.1) -> List[Dict]: - """Run chimera search: combine base formulas with operations.""" - results = [] - ops = ['*', '/', '+', '-'] - - for i in range(len(base_formulas)): - for j in range(i + 1, len(base_formulas)): - f1_name, f1_val = base_formulas[i] - f2_name, f2_val = base_formulas[j] - - for op in ops: - chimera_val = 0.0 - if op == '*': - chimera_val = f1_val * f2_val - elif op == '/': - if abs(f2_val) < 1e-15: - continue - chimera_val = f1_val / f2_val - elif op == '+': - chimera_val = f1_val + f2_val - elif op == '-': - chimera_val = f1_val - f2_val - - # Check against all targets - for target_name, target_val in PDG_TARGETS.items(): - if target_val == 0: - continue - - error_pct = abs(chimera_val - target_val) / abs(target_val) * 100.0 - - if error_pct < threshold: - status = 'APPROX' if error_pct < 0.1 else 'CANDIDATE' - - results.append({ - 'expr': f'{f1_name} {op} {f2_name}', - 'target_name': target_name, - 'target_value': target_val, - 'chimera_value': chimera_val, - 'error_pct': error_pct, - 'status': status - }) - - results.sort(key=lambda x: x['error_pct']) - return results - - -def trig_search(threshold: float = 0.1) -> List[Dict]: - """Search with trigonometric functions.""" - results = [] - - for target_name, target_val in PDG_TARGETS.items(): - if target_val == 0: - continue - - # Try sin/cos of various phi powers - for i in range(-4, 5): - base = PHI**i - - # sin(base), cos(base) - for val in [math.sin(base), math.cos(base)]: - error_pct = abs(val - target_val) / abs(target_val) * 100.0 - if error_pct < threshold: - results.append({ - 'expr': f'sin(phi^{i})' if val == math.sin(base) else f'cos(phi^{i})', - 'target_name': target_name, - 'target_value': target_val, - 'chimera_value': val, - 'error_pct': error_pct, - 'status': 'CANDIDATE' - }) - - results.sort(key=lambda x: x['error_pct']) - return results - - -def main(): - import argparse - - parser = argparse.ArgumentParser(description='ULTRA ENGINE v3.0 — Trinity Formula Discovery Engine') - parser.add_argument('--threshold', type=float, default=0.01, help='Error threshold in percent') - parser.add_argument('--pattern-only', action='store_true') - parser.add_argument('--chimera-only', action='store_true') - parser.add_argument('--trig-only', action='store_true') - parser.add_argument('--all', action='store_true', help='Run all discovery methods') - - args = parser.parse_args() - - all_results = [] - - if not args.chimera_only and not args.trig_only: - print("=== PATTERN SEARCH ===") - pattern_results = pattern_search(threshold=args.threshold) - all_results.extend(pattern_results) - print(f"Found {len(pattern_results)} pattern matches") - - if not args.pattern_only and not args.trig_only: - print("\n=== CHIMERA SEARCH ===") - base_formulas = [ - ("gamma", PHI**-3), - ("alpha_s", 1/(PHI**4 + PHI)), - ("delta_CP", 9*PHI**-2*180/PI), - ("V_ud", 0.97431), - ] - chimera_results = chimera_search(base_formulas, threshold=args.threshold) - all_results.extend(chimera_results) - print(f"Found {len(chimera_results)} chimera matches") - - if not args.pattern_only and not args.chimera_only: - print("\n=== TRIG SEARCH ===") - trig_results = trig_search(threshold=args.threshold) - all_results.extend(trig_results) - print(f"Found {len(trig_results)} trig matches") - - # Sort all results by error - all_results.sort(key=lambda x: x['error_pct']) - - # Output unique results (deduplicate by expr+target) - seen = set() - unique_results = [] - for r in all_results: - key = (r['expr'], r['target_name']) - if key not in seen: - seen.add(key) - unique_results.append(r) - - print(f"\n=== SUMMARY: {len(unique_results)} UNIQUE DISCOVERIES ===") - print() - print("| Target | Formula | Value | PDG | Delta% | Status |") - print("|--------|---------|-------|-----|--------|--------|") - - for r in unique_results[:50]: # Top 50 - print(f"| {r['target_name']:15} | {r['expr']:30} | {r['chimera_value']:10.6f} | {r['target_value']:8.5f} | {r['error_pct']:6.3f}% | {r['status']:8} |") - - return 0 - - -if __name__ == '__main__': - sys.exit(main()) diff --git a/specs/agents/a.t27 b/specs/agents/a.t27 index 685cc102e9..ff278a0c2a 100644 --- a/specs/agents/a.t27 +++ b/specs/agents/a.t27 @@ -28,7 +28,8 @@ pub const SOUL : str = "SOUL.md"; pub const AGENTS_DOC : str = "AGENTS.md"; pub const ALPHABET : str = "docs/agents/AGENTS_ALPHABET.md"; ; Key files as the alphabet lists them (paths and globs relative to the t27 root). -pub const KEY_FILES : [3]str = ["SOUL.md", "architecture/ADR-*.md", "architecture/CANON_DE_ZIGFICATION.md"]; +; Key files = where this domain really changes in the repo (14-day git history, #11275): Constitution/ADR/SOUL files are architecture. +pub const KEY_FILES : [7]str = ["SOUL.md", "AGENTS.md", "CLAUDE.md", "architecture/ADR-*.md", "architecture/CANON_DE_ZIGFICATION.md", "docs/T27-CONSTITUTION.md", "specs/agents/"]; pub const ENTRY_INVARIANT : str = "SOUL.md exists, is ASCII-only, and defines L1-L7 laws"; pub const EXIT_INVARIANT : str = "All ADRs reviewed, consistent with constitution, no conflicts"; ; AR role from the alphabet. diff --git a/specs/agents/b.t27 b/specs/agents/b.t27 index 7543226d51..8f145e2ce9 100644 --- a/specs/agents/b.t27 +++ b/specs/agents/b.t27 @@ -28,7 +28,8 @@ pub const SOUL : str = "SOUL.md"; pub const AGENTS_DOC : str = "AGENTS.md"; pub const ALPHABET : str = "docs/agents/AGENTS_ALPHABET.md"; ; Key files as the alphabet lists them (paths and globs relative to the t27 root). -pub const KEY_FILES : [2]str = ["build.tri", "src/tri/pipeline/"]; +; Key files = where this domain really changes in the repo (14-day git history, #11275): CI, workflows, gates, claw checks, root scripts and seals (a build output of every spec) are build/pipeline. +pub const KEY_FILES : [12]str = [".github/workflows/", ".trinity/seals/", "scripts/", "tools/", "tools/policy/", "specs/claw/", "specs/ci/", "specs/pipeline/", "specs/policy/", "Cargo.toml", "Cargo.lock", ".trinity/"]; pub const ENTRY_INVARIANT : str = "Build system is clean, no stale artifacts"; pub const EXIT_INVARIANT : str = "All tests pass, CI pipeline is green"; ; The alphabet lists no AR role for this agent ("-"). diff --git a/specs/agents/c.t27 b/specs/agents/c.t27 index 56890d258f..c2aca52012 100644 --- a/specs/agents/c.t27 +++ b/specs/agents/c.t27 @@ -28,7 +28,8 @@ pub const SOUL : str = "SOUL.md"; pub const AGENTS_DOC : str = "AGENTS.md"; pub const ALPHABET : str = "docs/agents/AGENTS_ALPHABET.md"; ; Key files as the alphabet lists them (paths and globs relative to the t27 root). -pub const KEY_FILES : [3]str = ["t27/compiler/parser/", "bootstrap/src/compiler.rs", "specs/ar/*.t27"]; +; Key files = where this domain really changes in the repo (14-day git history, #11275): the Rust bootstrap compiler, specs/compiler and its generated Rust/C are the compiler core. +pub const KEY_FILES : [8]str = ["bootstrap/src/", "bootstrap/tests/", "bootstrap/gen/", "bootstrap/src/compiler.rs", "specs/compiler/", "gen/rust/", "gen/c/", "specs/ar/*.t27"]; pub const ENTRY_INVARIANT : str = "Parser can handle all AR syntax (ternary logic, rules, proofs)"; pub const EXIT_INVARIANT : str = "Generated code compiles, AST is valid"; ; AR role from the alphabet. diff --git a/specs/agents/d.t27 b/specs/agents/d.t27 index 39b9203b7f..9a7f644925 100644 --- a/specs/agents/d.t27 +++ b/specs/agents/d.t27 @@ -28,7 +28,8 @@ pub const SOUL : str = "SOUL.md"; pub const AGENTS_DOC : str = "AGENTS.md"; pub const ALPHABET : str = "docs/agents/AGENTS_ALPHABET.md"; ; Key files as the alphabet lists them (paths and globs relative to the t27 root). -pub const KEY_FILES : [2]str = ["docs/migration-map.md", "specs/ar/*.t27"]; +; Key files = where this domain really changes in the repo (14-day git history, #11275): specs/port is the migration of foreign code into .t27 (more specific than S). +pub const KEY_FILES : [2]str = ["specs/port/", "specs/ar/*.t27"]; pub const ENTRY_INVARIANT : str = "Legacy Zig modules are catalogued and mapped"; pub const EXIT_INVARIANT : str = "All AR specs are in .t27 format, no Zig hand-editing on AR"; ; The alphabet lists no AR role for this agent ("-"). diff --git a/specs/agents/e.t27 b/specs/agents/e.t27 index 163a0db234..1822909267 100644 --- a/specs/agents/e.t27 +++ b/specs/agents/e.t27 @@ -28,7 +28,8 @@ pub const SOUL : str = "SOUL.md"; pub const AGENTS_DOC : str = "AGENTS.md"; pub const ALPHABET : str = "docs/agents/AGENTS_ALPHABET.md"; ; Key files as the alphabet lists them (paths and globs relative to the t27 root). -pub const KEY_FILES : [3]str = [".trinity/experience/", "specs/ar/explainability.t27", "specs/ar/proof_trace.t27"]; +; Key files = where this domain really changes in the repo (14-day git history, #11275): docs/now diary, experience and memory specs are the learning record. +pub const KEY_FILES : [6]str = [".trinity/experience/", "docs/now/", "specs/experience/", "specs/memory/", "specs/brain/", "specs/ar/explainability.t27"]; pub const ENTRY_INVARIANT : str = "Episode log is append-only, no deletions"; pub const EXIT_INVARIANT : str = "Lessons learned are catalogued, explanations are generated"; ; AR role from the alphabet. diff --git a/specs/agents/f.t27 b/specs/agents/f.t27 index 1570f6636b..7a61e61117 100644 --- a/specs/agents/f.t27 +++ b/specs/agents/f.t27 @@ -28,7 +28,8 @@ pub const SOUL : str = "SOUL.md"; pub const AGENTS_DOC : str = "AGENTS.md"; pub const ALPHABET : str = "docs/agents/AGENTS_ALPHABET.md"; ; Key files as the alphabet lists them (paths and globs relative to the t27 root). -pub const KEY_FILES : [2]str = ["t27/conformance/*.json", "conformance/FORMAT-SPEC-001.json"]; +; Key files = where this domain really changes in the repo (14-day git history, #11275): conformance vectors and specs. +pub const KEY_FILES : [5]str = ["conformance/", "conformance/*.json", "conformance/FORMAT-SPEC-001.json", "specs/conformance/", "specs/test_framework/"]; pub const ENTRY_INVARIANT : str = "All sacred vectors (G, OmegaLambda, phi) are valid"; pub const EXIT_INVARIANT : str = "Conformance > 95%, all sacred physics verified"; ; The alphabet lists no AR role for this agent ("-"). diff --git a/specs/agents/g.t27 b/specs/agents/g.t27 index a8280ecfd4..2b98cb8072 100644 --- a/specs/agents/g.t27 +++ b/specs/agents/g.t27 @@ -28,7 +28,8 @@ pub const SOUL : str = "SOUL.md"; pub const AGENTS_DOC : str = "AGENTS.md"; pub const ALPHABET : str = "docs/agents/AGENTS_ALPHABET.md"; ; Key files as the alphabet lists them (paths and globs relative to the t27 root). -pub const KEY_FILES : [2]str = ["architecture/graph_v2.json", "architecture/graph.tri"]; +; Key files = where this domain really changes in the repo (14-day git history, #11275): graph files. +pub const KEY_FILES : [3]str = ["architecture/graph_v2.json", "architecture/graph.tri", "specs/graph/"]; pub const ENTRY_INVARIANT : str = "Graph is acyclic, topological sort exists"; pub const EXIT_INVARIANT : str = "All edges satisfied, no broken dependencies"; ; The alphabet lists no AR role for this agent ("-"). diff --git a/specs/agents/h.t27 b/specs/agents/h.t27 index 015dadbdc1..a9d42bff89 100644 --- a/specs/agents/h.t27 +++ b/specs/agents/h.t27 @@ -28,7 +28,8 @@ pub const SOUL : str = "SOUL.md"; pub const AGENTS_DOC : str = "AGENTS.md"; pub const ALPHABET : str = "docs/agents/AGENTS_ALPHABET.md"; ; Key files as the alphabet lists them (paths and globs relative to the t27 root). -pub const KEY_FILES : [2]str = ["t27/specs/nn/hslm.t27", "t27/specs/nn/attention.t27"]; +; Key files = where this domain really changes in the repo (14-day git history, #11275): neural nets, HSLM, IGLA own-model training. +pub const KEY_FILES : [8]str = ["specs/ml/", "specs/nn/", "specs/hslm/", "specs/igla/", "specs/neural/", "specs/dataset/", "specs/nn/hslm.t27", "specs/nn/attention.t27"]; pub const ENTRY_INVARIANT : str = "HSLM layers are defined, connections are valid"; pub const EXIT_INVARIANT : str = "Attention traces are validated, sacred attention holds"; ; The alphabet lists no AR role for this agent ("-"). diff --git a/specs/agents/i.t27 b/specs/agents/i.t27 index 560aafeb6a..46ef829fd3 100644 --- a/specs/agents/i.t27 +++ b/specs/agents/i.t27 @@ -28,7 +28,8 @@ pub const SOUL : str = "SOUL.md"; pub const AGENTS_DOC : str = "AGENTS.md"; pub const ALPHABET : str = "docs/agents/AGENTS_ALPHABET.md"; ; Key files as the alphabet lists them (paths and globs relative to the t27 root). -pub const KEY_FILES : [1]str = ["t27/specs/isa/registers.t27"]; +; Key files = where this domain really changes in the repo (14-day git history, #11275): instruction set. +pub const KEY_FILES : [2]str = ["specs/isa/", "specs/isa/registers.t27"]; pub const ENTRY_INVARIANT : str = "27 registers are defined, Coptic mapping exists"; pub const EXIT_INVARIANT : str = "Coptic mapping is verified (bijection), all registers accessible"; ; The alphabet lists no AR role for this agent ("-"). diff --git a/specs/agents/j.t27 b/specs/agents/j.t27 index 49b1b98fbd..b54871506b 100644 --- a/specs/agents/j.t27 +++ b/specs/agents/j.t27 @@ -28,7 +28,8 @@ pub const SOUL : str = "SOUL.md"; pub const AGENTS_DOC : str = "AGENTS.md"; pub const ALPHABET : str = "docs/agents/AGENTS_ALPHABET.md"; ; Key files as the alphabet lists them (paths and globs relative to the t27 root). -pub const KEY_FILES : [1]str = ["src/tri/dev_commands.zig"]; +; Key files = where this domain really changes in the repo (14-day git history, #11275): automation, functions and jobs are task routing. +pub const KEY_FILES : [3]str = ["specs/automation/", "specs/functions/", "specs/jobs/"]; pub const ENTRY_INVARIANT : str = "Task queue exists, scheduler is running"; pub const EXIT_INVARIANT : str = "No stuck jobs > 1 hour, all jobs assigned"; ; The alphabet lists no AR role for this agent ("-"). diff --git a/specs/agents/k.t27 b/specs/agents/k.t27 index 948d73784e..c3efdb494c 100644 --- a/specs/agents/k.t27 +++ b/specs/agents/k.t27 @@ -28,7 +28,8 @@ pub const SOUL : str = "SOUL.md"; pub const AGENTS_DOC : str = "AGENTS.md"; pub const ALPHABET : str = "docs/agents/AGENTS_ALPHABET.md"; ; Key files as the alphabet lists them (paths and globs relative to the t27 root). -pub const KEY_FILES : [2]str = ["t27/specs/fpga/mac.t27", "specs/math/e8_lie_algebra.t27"]; +; Key files = where this domain really changes in the repo (14-day git history, #11275): native backend t27b, FPGA and Verilog are the kernel (hardware letter). +pub const KEY_FILES : [9]str = ["cli/t27b/", "specs/tri/t27b/", "specs/fpga/", "fpga/", "gen/verilog/", "tools/jtag/", "specs/xilinx7/", "specs/math/e8_lie_algebra.t27", "tests/fixtures/t27b-fuzz/"]; pub const ENTRY_INVARIANT : str = "MAC spec is zero-DSP (no digital signal processors)"; pub const EXIT_INVARIANT : str = "Synthesis is verified, E8 kernel integration works"; ; The alphabet lists no AR role for this agent ("-"). diff --git a/specs/agents/l.t27 b/specs/agents/l.t27 index 1e160dbf7c..10d9aae310 100644 --- a/specs/agents/l.t27 +++ b/specs/agents/l.t27 @@ -28,7 +28,8 @@ pub const SOUL : str = "SOUL.md"; pub const AGENTS_DOC : str = "AGENTS.md"; pub const ALPHABET : str = "docs/agents/AGENTS_ALPHABET.md"; ; Key files as the alphabet lists them (paths and globs relative to the t27 root). -pub const KEY_FILES : [1]str = ["docs/nona-02-organism/TRI_SYNTAX_VNEXT.md"]; +; Key files = where this domain really changes in the repo (14-day git history, #11275): the tri language library and tutorial are the language (specs/tri/t27b is K). +pub const KEY_FILES : [5]str = ["specs/tri/", "specs/basics/", "specs/tutorial/", "specs/lsp/", "docs/nona-02-organism/TRI_SYNTAX_VNEXT.md"]; pub const ENTRY_INVARIANT : str = "Syntax vNEXT is documented, BNF grammar exists"; pub const EXIT_INVARIANT : str = "Parser implements vNEXT, all syntax tests pass"; ; The alphabet lists no AR role for this agent ("-"). diff --git a/specs/agents/m.t27 b/specs/agents/m.t27 index 4dd81ea495..c841fed1af 100644 --- a/specs/agents/m.t27 +++ b/specs/agents/m.t27 @@ -28,7 +28,8 @@ pub const SOUL : str = "SOUL.md"; pub const AGENTS_DOC : str = "AGENTS.md"; pub const ALPHABET : str = "docs/agents/AGENTS_ALPHABET.md"; ; Key files as the alphabet lists them (paths and globs relative to the t27 root). -pub const KEY_FILES : [2]str = [".trinity/bench/", "docs/qualification/TVP.md"]; +; Key files = where this domain really changes in the repo (14-day git history, #11275): reports, census and benchmarks are metrics. +pub const KEY_FILES : [6]str = ["docs/reports/", "tools/census/", "specs/benchmarks/", "benchmarks/", "bench/", "docs/qualification/TVP.md"]; pub const ENTRY_INVARIANT : str = "Bench baseline exists, telemetry is running"; pub const EXIT_INVARIANT : str = "No performance regression > 5%, all benchmarks current"; ; The alphabet lists no AR role for this agent ("-"). diff --git a/specs/agents/n.t27 b/specs/agents/n.t27 index 2edba7182f..1f30d60082 100644 --- a/specs/agents/n.t27 +++ b/specs/agents/n.t27 @@ -28,7 +28,8 @@ pub const SOUL : str = "SOUL.md"; pub const AGENTS_DOC : str = "AGENTS.md"; pub const ALPHABET : str = "docs/agents/AGENTS_ALPHABET.md"; ; Key files as the alphabet lists them (paths and globs relative to the t27 root). -pub const KEY_FILES : [2]str = ["t27/specs/numeric/", "docs/NUMERIC-STANDARD-001.md"]; +; Key files = where this domain really changes in the repo (14-day git history, #11275): numeric formats. +pub const KEY_FILES : [1]str = ["specs/numeric/"]; pub const ENTRY_INVARIANT : str = "GF family is defined (GF4, GF8, GF12, GF16, GF20, GF24, GF32)"; pub const EXIT_INVARIANT : str = "phi identity validated, all numeric tests pass"; ; The alphabet lists no AR role for this agent ("-"). diff --git a/specs/agents/o.t27 b/specs/agents/o.t27 index 02b0a24e8a..94b231b04f 100644 --- a/specs/agents/o.t27 +++ b/specs/agents/o.t27 @@ -28,7 +28,8 @@ pub const SOUL : str = "SOUL.md"; pub const AGENTS_DOC : str = "AGENTS.md"; pub const ALPHABET : str = "docs/agents/AGENTS_ALPHABET.md"; ; Key files as the alphabet lists them (paths and globs relative to the t27 root). -pub const KEY_FILES : [2]str = ["src/tri/pipeline/", "specs/ar/composition.t27"]; +; Key files = where this domain really changes in the repo (14-day git history, #11275): the tri CLI and its tool specs run the phases. +pub const KEY_FILES : [4]str = ["cli/tri/", "specs/tools/tri/", "specs/rings/", "specs/ar/composition.t27"]; pub const ENTRY_INVARIANT : str = "All phases (1-6) are defined, phase transitions valid"; pub const EXIT_INVARIANT : str = "Orchestration completes, no stuck phases"; ; AR role from the alphabet. diff --git a/specs/agents/p.t27 b/specs/agents/p.t27 index 4f7fdad486..866e7b9256 100644 --- a/specs/agents/p.t27 +++ b/specs/agents/p.t27 @@ -28,7 +28,8 @@ pub const SOUL : str = "SOUL.md"; pub const AGENTS_DOC : str = "AGENTS.md"; pub const ALPHABET : str = "docs/agents/AGENTS_ALPHABET.md"; ; Key files as the alphabet lists them (paths and globs relative to the t27 root). -pub const KEY_FILES : [2]str = ["t27/specs/math/sacred_physics.t27", "specs/physics/su2_chern_simons.t27"]; +; Key files = where this domain really changes in the repo (14-day git history, #11275): physics, math and analog circuit emulation (SPICE) are physics. +pub const KEY_FILES : [6]str = ["specs/physics/", "specs/math/", "specs/analog/", "specs/sacred/", "specs/math/sacred_physics.t27", "specs/physics/su2_chern_simons.t27"]; pub const ENTRY_INVARIANT : str = "phi^2 + phi^-2 = 3 holds in all calculations"; pub const EXIT_INVARIANT : str = "Sacred constants (G, OmegaLambda, phi, tpresent) are verified"; ; AR role from the alphabet. @@ -58,6 +59,6 @@ test agent_p_constants { assert LAYER == "Spiritual"; assert ENABLED == true; assert AR_ROLE == "AR Theoretical Foundations - provides mathematical basis for AR reasoning"; - assert KEY_FILES[0] == "t27/specs/math/sacred_physics.t27"; - assert KEY_FILES[1] == "specs/physics/su2_chern_simons.t27"; + assert KEY_FILES[0] == "specs/physics/"; + assert KEY_FILES[1] == "specs/math/"; } diff --git a/specs/agents/q.t27 b/specs/agents/q.t27 index 035f699e70..ca2709922c 100644 --- a/specs/agents/q.t27 +++ b/specs/agents/q.t27 @@ -28,7 +28,8 @@ pub const SOUL : str = "SOUL.md"; pub const AGENTS_DOC : str = "AGENTS.md"; pub const ALPHABET : str = "docs/agents/AGENTS_ALPHABET.md"; ; Key files as the alphabet lists them (paths and globs relative to the t27 root). -pub const KEY_FILES : [1]str = ["src/tri/dev_commands.zig"]; +; Key files = where this domain really changes in the repo (14-day git history, #11275): cron specs are scheduling. +pub const KEY_FILES : [1]str = ["specs/crons/"]; pub const ENTRY_INVARIANT : str = "Queue is not empty (work exists)"; pub const EXIT_INVARIANT : str = "MNL (Most-Numerous-Loss) pattern enforced, no starvation"; ; The alphabet lists no AR role for this agent ("-"). diff --git a/specs/agents/r.t27 b/specs/agents/r.t27 index 6f48bb4023..72bc927fab 100644 --- a/specs/agents/r.t27 +++ b/specs/agents/r.t27 @@ -28,7 +28,8 @@ pub const SOUL : str = "SOUL.md"; pub const AGENTS_DOC : str = "AGENTS.md"; pub const ALPHABET : str = "docs/agents/AGENTS_ALPHABET.md"; ; Key files as the alphabet lists them (paths and globs relative to the t27 root). -pub const KEY_FILES : [2]str = ["t27/compiler/runtime/", "bootstrap/src/compiler.rs"]; +; Key files = where this domain really changes in the repo (14-day git history, #11275): bootstrap stage0 and runtime/VM specs (bootstrap/src is C). +pub const KEY_FILES : [8]str = ["bootstrap/stage0/", "bootstrap/", "specs/runtime/", "specs/vm/", "specs/jit/", "bootstrap/src/compiler.rs", "specs/effects/", "specs/base/"]; pub const ENTRY_INVARIANT : str = "Runtime is initialized, ABI is defined"; pub const EXIT_INVARIANT : str = "No memory leaks, no resource exhaustion"; ; The alphabet lists no AR role for this agent ("-"). @@ -58,6 +59,6 @@ test agent_r_constants { assert LAYER == "Spiritual"; assert ENABLED == true; assert AR_ROLE == ""; - assert KEY_FILES[0] == "t27/compiler/runtime/"; - assert KEY_FILES[1] == "bootstrap/src/compiler.rs"; + assert KEY_FILES[0] == "bootstrap/stage0/"; + assert KEY_FILES[1] == "bootstrap/"; } diff --git a/specs/agents/s.t27 b/specs/agents/s.t27 index f106bc2ebd..f7ffaa64ac 100644 --- a/specs/agents/s.t27 +++ b/specs/agents/s.t27 @@ -28,7 +28,8 @@ pub const SOUL : str = "SOUL.md"; pub const AGENTS_DOC : str = "AGENTS.md"; pub const ALPHABET : str = "docs/agents/AGENTS_ALPHABET.md"; ; Key files as the alphabet lists them (paths and globs relative to the t27 root). -pub const KEY_FILES : [3]str = ["specs/", "specs/ar/*.t27", "docs/NUMERIC-*.md"]; +; Key files = where this domain really changes in the repo (14-day git history, #11275): fallback for any other spec. +pub const KEY_FILES : [2]str = ["specs/", "specs/ar/*.t27"]; pub const ENTRY_INVARIANT : str = "All specs have TDD (test/invariant/bench)"; pub const EXIT_INVARIANT : str = "Standards are consistent, naming rules followed"; ; AR role from the alphabet. @@ -60,5 +61,4 @@ test agent_s_constants { assert AR_ROLE == "AR Spec Standards - defines and enforces AR spec conventions"; assert KEY_FILES[0] == "specs/"; assert KEY_FILES[1] == "specs/ar/*.t27"; - assert KEY_FILES[2] == "docs/NUMERIC-*.md"; } diff --git a/specs/agents/t.t27 b/specs/agents/t.t27 index 28876d9cf8..0b80485960 100644 --- a/specs/agents/t.t27 +++ b/specs/agents/t.t27 @@ -28,7 +28,8 @@ pub const SOUL : str = "SOUL.md"; pub const AGENTS_DOC : str = "AGENTS.md"; pub const ALPHABET : str = "docs/agents/AGENTS_ALPHABET.md"; ; Key files as the alphabet lists them (paths and globs relative to the t27 root). -pub const KEY_FILES : [1]str = ["t27/specs/queen/lotus.t27"]; +; Key files = where this domain really changes in the repo (14-day git history, #11275): the queen, her bees and dispatch. +pub const KEY_FILES : [4]str = ["specs/queen/", "tools/queen/", "tools/bees/", "specs/queen/lotus.t27"]; pub const ENTRY_INVARIANT : str = "Queen health >= 0.9, graph is loaded"; pub const EXIT_INVARIANT : str = "All rings are sealed, verdict is clean"; ; AR role from the alphabet. @@ -58,5 +59,5 @@ test agent_t_constants { assert LAYER == "Physical"; assert ENABLED == true; assert AR_ROLE == "AR Orchestrator - coordinates all AR agents"; - assert KEY_FILES[0] == "t27/specs/queen/lotus.t27"; + assert KEY_FILES[0] == "specs/queen/"; } diff --git a/specs/agents/ti.t27 b/specs/agents/ti.t27 index 15414d8256..1b8f753b86 100644 --- a/specs/agents/ti.t27 +++ b/specs/agents/ti.t27 @@ -28,7 +28,8 @@ pub const SOUL : str = "SOUL.md"; pub const AGENTS_DOC : str = "AGENTS.md"; pub const ALPHABET : str = "docs/agents/AGENTS_ALPHABET.md"; ; The alphabet lists no key files for the reserved 27th seat ("- (future)"). -pub const KEY_FILES : [0]str = []; +; Key files = where this domain really changes in the repo (14-day git history, #11275): security: sandbox enforcement and the vault are security. +pub const KEY_FILES : [2]str = ["specs/sandbox/", "specs/vault/"]; pub const ENTRY_INVARIANT : str = "Security policy exists"; pub const EXIT_INVARIANT : str = "AAIF compliance ready, no vulnerabilities"; ; The alphabet lists no AR role for this agent ("-"). diff --git a/specs/agents/u.t27 b/specs/agents/u.t27 index 23722f29d5..9ed0b119ef 100644 --- a/specs/agents/u.t27 +++ b/specs/agents/u.t27 @@ -28,7 +28,8 @@ pub const SOUL : str = "SOUL.md"; pub const AGENTS_DOC : str = "AGENTS.md"; pub const ALPHABET : str = "docs/agents/AGENTS_ALPHABET.md"; ; Key files as the alphabet lists them (paths and globs relative to the t27 root). -pub const KEY_FILES : [1]str = ["t27/domains/"]; +; Key files = where this domain really changes in the repo (14-day git history, #11275): ternary universe levels and scenes. +pub const KEY_FILES : [3]str = ["specs/ternary/", "specs/scenes/", "specs/vsa/"]; pub const ENTRY_INVARIANT : str = "Domain boundaries are defined"; pub const EXIT_INVARIANT : str = "No circular dependencies, domains are orthogonal"; ; The alphabet lists no AR role for this agent ("-"). @@ -58,5 +59,5 @@ test agent_u_constants { assert LAYER == "Physical"; assert ENABLED == true; assert AR_ROLE == ""; - assert KEY_FILES[0] == "t27/domains/"; + assert KEY_FILES[0] == "specs/ternary/"; } diff --git a/specs/agents/v.t27 b/specs/agents/v.t27 index 226e2aa57e..8bf2c61ac6 100644 --- a/specs/agents/v.t27 +++ b/specs/agents/v.t27 @@ -28,7 +28,8 @@ pub const SOUL : str = "SOUL.md"; pub const AGENTS_DOC : str = "AGENTS.md"; pub const ALPHABET : str = "docs/agents/AGENTS_ALPHABET.md"; ; Key files as the alphabet lists them (paths and globs relative to the t27 root). -pub const KEY_FILES : [3]str = ["src/tri/verdict.zig", "specs/ar/proof_trace.t27", "specs/ar/restraint.t27"]; +; Key files = where this domain really changes in the repo (14-day git history, #11275): verified specs and verdict proofs are verdict (seals are build output, B). +pub const KEY_FILES : [4]str = ["specs/verified/", "specs/ar/proof_trace.t27", "specs/ar/restraint.t27", ".trinity/receipts/"]; pub const ENTRY_INVARIANT : str = "Verdict engine is ready, toxic thresholds defined"; pub const EXIT_INVARIANT : str = "Toxicity is detected, proof traces are generated"; ; AR role from the alphabet. diff --git a/specs/agents/w.t27 b/specs/agents/w.t27 index 416593d23c..e72078bd95 100644 --- a/specs/agents/w.t27 +++ b/specs/agents/w.t27 @@ -28,7 +28,8 @@ pub const SOUL : str = "SOUL.md"; pub const AGENTS_DOC : str = "AGENTS.md"; pub const ALPHABET : str = "docs/agents/AGENTS_ALPHABET.md"; ; Key files as the alphabet lists them (paths and globs relative to the t27 root). -pub const KEY_FILES : [1]str = ["src/tri/cell.zig"]; +; Key files = where this domain really changes in the repo (14-day git history, #11275): skills are workflows. +pub const KEY_FILES : [4]str = [".claude/skills/", "specs/skills/", "specs/shell/", ".claude/agents/"]; pub const ENTRY_INVARIANT : str = "Cell is sealed, hash is computed"; pub const EXIT_INVARIANT : str = "Hash is verified, commit is signed"; ; The alphabet lists no AR role for this agent ("-"). diff --git a/specs/agents/x.t27 b/specs/agents/x.t27 index e18cb578ea..2bb0a6628f 100644 --- a/specs/agents/x.t27 +++ b/specs/agents/x.t27 @@ -28,7 +28,8 @@ pub const SOUL : str = "SOUL.md"; pub const AGENTS_DOC : str = "AGENTS.md"; pub const ALPHABET : str = "docs/agents/AGENTS_ALPHABET.md"; ; Key files as the alphabet lists them (paths and globs relative to the t27 root). -pub const KEY_FILES : [1]str = ["bindings/"]; +; Key files = where this domain really changes in the repo (14-day git history, #11275): MCP, API, providers, bindings and the trinity bridge are external interop. +pub const KEY_FILES : [11]str = ["bindings/", "specs/tools/mcp/", "specs/tools/trinity/", "specs/mcp/", "specs/t27mcp/", "specs/api/", "specs/interop/", "specs/providers/", "specs/provider/", "specs/server/", "specs/trinity/"]; pub const ENTRY_INVARIANT : str = "Bindings are documented"; pub const EXIT_INVARIANT : str = "Interop tests pass, external APIs are current"; ; The alphabet lists no AR role for this agent ("-"). diff --git a/specs/agents/y.t27 b/specs/agents/y.t27 index 44337b6fe3..26abd7ac38 100644 --- a/specs/agents/y.t27 +++ b/specs/agents/y.t27 @@ -28,7 +28,8 @@ pub const SOUL : str = "SOUL.md"; pub const AGENTS_DOC : str = "AGENTS.md"; pub const ALPHABET : str = "docs/agents/AGENTS_ALPHABET.md"; ; Key files as the alphabet lists them (paths and globs relative to the t27 root). -pub const KEY_FILES : [1]str = ["deploy/contracts/"]; +; Key files = where this domain really changes in the repo (14-day git history, #11275): DePIN, hosting, network and trinet ledger are yield. +pub const KEY_FILES : [4]str = ["specs/depin/", "specs/hosting/", "specs/network/", "specs/trinet/"]; pub const ENTRY_INVARIANT : str = "DePIN nodes are defined"; pub const EXIT_INVARIANT : str = "Fitness score > 0.8, yield is optimized"; ; The alphabet lists no AR role for this agent ("-"). diff --git a/specs/agents/z.t27 b/specs/agents/z.t27 index c6e96914a7..f9dfd9832c 100644 --- a/specs/agents/z.t27 +++ b/specs/agents/z.t27 @@ -28,7 +28,8 @@ pub const SOUL : str = "SOUL.md"; pub const AGENTS_DOC : str = "AGENTS.md"; pub const ALPHABET : str = "docs/agents/AGENTS_ALPHABET.md"; ; Key files as the alphabet lists them (paths and globs relative to the t27 root). -pub const KEY_FILES : [1]str = ["docs/"]; +; Key files = where this domain really changes in the repo (14-day git history, #11275): UX: T27 OS, T27F, widgets and ui are the user surface; docs/ is the fallback. +pub const KEY_FILES : [8]str = ["specs/t27os/", "specs/t27f/", "specs/widgets/", "specs/ui/", "specs/tools/t27o/", "specs/i18n/", "docs/", "specs/docs/"]; pub const ENTRY_INVARIANT : str = "All docs are in English, ASCII-only"; pub const EXIT_INVARIANT : str = "DX score > 4/5, all docs are current"; ; The alphabet lists no AR role for this agent ("-"). diff --git a/specs/lab/job_kind.t27 b/specs/lab/job_kind.t27 index 2d735e7941..bea4144c55 100644 --- a/specs/lab/job_kind.t27 +++ b/specs/lab/job_kind.t27 @@ -78,7 +78,7 @@ ; The home. T27 OS shows a level and a streak read off the same rows: a level is LJK_LEVEL_XP XP of balance ; (credits less slashes, statement.t27 balance_after_entry), a streak is the run of epochs (one day each, ; statement.t27) with at least one credited gate. A streak changes no amount. The card belongs to the -; DePIN NODE petal of t27f/petal_menu.t27 (LJK_HOME_PETAL). +; Y YIELD petal of t27f/petal_menu.t27 (LJK_HOME_PETAL). ; ASCII only (L3). ; phi^2 + 1/phi^2 = 3 | TRINITY @@ -469,8 +469,8 @@ pub fn ljk_streak_shown(streak: u32, last_epoch: u64, now_epoch: u64) -> u32 { return streak; } -/// The petal of the home whose menu holds the reward card: DePIN NODE, a petal of t27f/petal_menu.t27. -pub const LJK_HOME_PETAL : u32 = 16; +/// The petal that holds the reward card: Y YIELD (Yield / DePIN, agent ordinal 25 = petal 24) of t27f/petal_menu.t27. +pub const LJK_HOME_PETAL : u32 = 24; /// Whether the world named at index `petal` of a comma-separated list is `word`. pub fn ljk_world_is(names: string, petal: u32, word: string) -> bool { @@ -868,15 +868,15 @@ test an_xp_credit_is_pending_through_the_challenge_window_and_then_final { assert ljk_xp_final(CHALLENGE_WINDOW_MINUTES * 10, true) == false; } -test the_reward_card_belongs_to_the_depin_node_petal_of_the_home { +test the_reward_card_belongs_to_the_yield_petal { assert PETAL_COUNT == 27; assert petal_ok(LJK_HOME_PETAL); - assert ljk_world_is(WORLD_NAMES, LJK_HOME_PETAL, "DePIN NODE"); + assert ljk_world_is(WORLD_NAMES, LJK_HOME_PETAL, "YIELD"); assert ljk_world_is(WORLD_NAMES, 2, "CHAT"); - assert ljk_world_is(WORLD_NAMES, 0, "LOGS"); - assert ljk_world_is(WORLD_NAMES, 26, "ABOUT"); - assert ljk_world_is(WORLD_NAMES, LJK_HOME_PETAL, "DePIN") == false; - assert ljk_world_is(WORLD_NAMES, 27, "ABOUT") == false; + assert ljk_world_is(WORLD_NAMES, 0, "ISA"); + assert ljk_world_is(WORLD_NAMES, 26, "SECURITY"); + assert ljk_world_is(WORLD_NAMES, LJK_HOME_PETAL, "Y") == false; + assert ljk_world_is(WORLD_NAMES, 27, "SECURITY") == false; } test the_gates_k_of_n_is_a_safe_quorum_by_the_networks_own_rule { diff --git a/specs/queen/golden_chain.t27 b/specs/queen/golden_chain.t27 index b149e761d4..bd8d6b8e57 100644 --- a/specs/queen/golden_chain.t27 +++ b/specs/queen/golden_chain.t27 @@ -30,8 +30,9 @@ ; actors.t27 section 10 decides who may START an edit. This file decides what may LAND. ; Section 5 is the merge side of a declared region (policy/issue_shape.t27): a bee whose diff left ; its region onto live work does not land (gHashTag/t27#9021, 2026-10-11). -; Not in this card: the benchmark against the GitHub loop on the same input (the actors rule, -; gHashTag/t27#7851) and the wiring into the Queen's merger behind a flag. +; Section 6 is the landing rule agents follow (#11275). Not in this card: the benchmark against the GitHub +; loop on the same input (the actors rule, gHashTag/t27#7851) and the wiring into the Queen's merger +; behind a flag. ; ASCII only (L3). ; phi^2 + 1/phi^2 = 3 | TRINITY @@ -148,8 +149,8 @@ pub fn gc_pair(a_path: u64, a_kind: u8, a_lo: u32, a_hi: u32, b_path: u64, b_kin /// FORWARD: its parent was the head. CARRIED: carried past what landed, nothing re-run. REGEN: a /// derived file both wrote is recomputed on the new head and sealed again; the other verdicts carry. /// REJUDGE: back to GEN, the frontier picks the nodes. CONFLICT: a source edit met one that landed; -/// back to a bee. NOT_GOLDEN: never landed. UNKNOWN_PARENT: its parent is neither the pass's head nor -/// a link that landed in the pass; it fails closed. +/// back to a bee. NOT_GOLDEN: never landed. UNKNOWN_PARENT: it has no parent id at all (no merge-base) +/// and fails closed. A parent that is neither the pass's head nor a link of the pass is carried (#11361). pub const GC_OUT_FORWARD : u8 = 0; pub const GC_OUT_CARRIED : u8 = 1; pub const GC_OUT_REGEN : u8 = 2; @@ -242,7 +243,27 @@ pub fn gc_land(n: usize, id: []const u64, parent: []const u64, v: []const u8, m: if (adv != GC_ADV_STALE) { out[i] = GC_OUT_NOT_GOLDEN; } else { - var since : usize = gc_since(i, n, id, parent[i], head, pos); + // gc_since inlined: a read-only slice passed on from a written one is a move in generated Rust + var since : usize = n; + if (parent[i] == head) { + since = 0; + } else { + var jj : usize = 0; + while (jj < i) { + if (id[jj] == parent[i] and pos[jj] < n) { + since = pos[jj] + 1; + jj = i; + } else { + jj = jj + 1; + } + } + } + // A parent the chain never saw (a branch cut from an older master) is carried past every link that landed in + // this pass; the host's merge-tree then judges it against the real head (#11361). Only a link with no parent + // id at all (no merge-base) stays UNKNOWN_PARENT. + if (since == n and parent[i] != 0) { + since = 0; + } if (since == n) { out[i] = GC_OUT_UNKNOWN_PARENT; } else { @@ -350,6 +371,155 @@ pub fn gc_region_counts(answer: u8) -> bool { return answer == GC_RV_SEND_BACK; } +; --- 6. Landing: our git first, GitHub once per batch (#11275) ----------------------------------------- +; Owner, 2026-10-20 (translated): "GitHub is our bottleneck. Build the best practices into the agents' +; context so they use OUR git for the full build." +; Where the time goes (measured, issues #10644 #10588 #6595 #3134): push -> Actions queue (800 to 900 runs +; on 2026-10-18; the merger waited about 1 h for a runner) -> required checks -> rebase -> every check +; again. Sections 1 to 5 already judge a link without GitHub; this section is the rule an agent follows. +; - A lane VERIFIES LOCALLY first: t27b test on its changed specs and their importers, reseal, gen, the +; verdict-reuse keys. It records the verdicts of its link. lane_ready is true only when every stage is +; golden and in order (sections 1 and 2). +; - A lane LANDS on the local golden head, a git ref (GC_REF) in the shared repository, advanced only by +; gc_land (compare-and-swap on the parent, section 2). No lane writes the ref by hand. +; - GitHub receives ONE batched push per window from the golden head, never one per commit. The batch is +; at most GC_BATCH_MAX ready links, the window is GC_WINDOW_MIN minutes, doubled while the Actions +; queue holds GC_CI_JAM runs or more (a push into a jam only goes to the back of it, push_gate.t27). +; - Required checks run once per batch. A derived-only rebase (CARRIED or REGEN) re-runs nothing. +; - The chain follows master (#11361). GitHub squash-merges a batch, so the head is then no ancestor of +; origin/master: gc_rebase resets head and pushed to master when nothing is unpushed, and carries the +; unpushed links onto master as one commit otherwise. A link cut from an older master is carried, not +; refused (gc_land); a seal saved by an older t27c is saved again after the tests (gc_regen_ok, REGEN). +; - A red batch is bisected here, by verdicts: gc_bisect finds the first link whose prefix is red from +; locally replayed prefix verdicts; it is not found by re-running GitHub. + +pub const GC_REF : str = "refs/golden/head"; +pub const GC_BATCH_MAX : usize = 8; +pub const GC_WINDOW_MIN : u32 = 15; +pub const GC_CI_JAM : u32 = 200; + +pub const GC_PUSH_NOTHING : u8 = 0; +pub const GC_PUSH_WAIT : u8 = 1; +pub const GC_PUSH_NOW : u8 = 2; + +/// Things a lane may try. The first two are the landing path; the rest are forbidden. +pub const GC_ACT_LAND_LOCAL : u8 = 0; +pub const GC_ACT_BATCH_PUSH : u8 = 1; +pub const GC_ACT_PUSH_PER_COMMIT : u8 = 2; +pub const GC_ACT_UPDATE_BRANCH : u8 = 3; +pub const GC_ACT_RERUN_AFTER_DERIVED_REBASE : u8 = 4; +pub const GC_ACT_WRITE_HEAD_BY_HAND : u8 = 5; +pub const GC_ACT_PUSH_NOT_GOLDEN : u8 = 6; + +/// Whether link i may be queued for landing: every stage golden, in chain order. +pub fn lane_ready(v: []const u8, i: usize) -> bool { + return gc_ordered(v, i) and gc_next_stage(v, i) == GC_DONE; +} + +/// The batch from a queue of n links: indexes of the ready ones in queue order, at most GC_BATCH_MAX, +/// written to out. Returns how many. A link that is not ready stays in the queue and never blocks one +/// behind it. +pub fn batch_from(n: usize, v: []const u8, out: []usize) -> usize { + var i : usize = 0; + var b : usize = 0; + while (i < n and b < GC_BATCH_MAX) { + if (lane_ready(v, i)) { + out[b] = i; + b = b + 1; + } + i = i + 1; + } + return b; +} + +/// Whether to push the golden head to GitHub now. NOTHING: no ready link. NOW: the window has passed +/// (doubled in a jam), or the batch is full and there is no jam. Otherwise WAIT. +pub fn when_to_push(minutes_since_last: u32, batch_len: usize, ci_queued: u32) -> u8 { + if (batch_len == 0) { + return GC_PUSH_NOTHING; + } + var window : u32 = GC_WINDOW_MIN; + var jam : bool = ci_queued >= GC_CI_JAM; + if (jam) { + window = GC_WINDOW_MIN * 2; + } + if (minutes_since_last >= window) { + return GC_PUSH_NOW; + } + if (batch_len >= GC_BATCH_MAX and jam == false) { + return GC_PUSH_NOW; + } + return GC_PUSH_WAIT; +} + +/// Whether a lane may do `act`. Only landing locally and the batched push are allowed. +pub fn lane_may(act: u8) -> bool { + return act == GC_ACT_LAND_LOCAL or act == GC_ACT_BATCH_PUSH; +} + +/// Whether a carry outcome sends the link back through local stages. FORWARD, CARRIED and REGEN +/// re-run nothing, here or on GitHub; REJUDGE re-runs from GEN locally; CONFLICT goes back to a bee. +pub fn gc_rerun_local(out: u8) -> bool { + return out >= GC_OUT_REJUDGE; +} + +/// After GitHub squash-merges a batch, refs/golden/head is no longer an ancestor of origin/master, so every link +/// cut from origin/master would read as a stranger (#11361). The chain follows master: +/// NONE: no head, no pushed mark, or origin/master already holds the head: nothing to do. +/// RESET: nothing is unpushed (pushed is the head) and master does not hold it: head and pushed both move to master. +/// CARRY: links are unpushed and master does not hold the head: they are carried onto master as one commit +/// (merge-tree with the pushed mark as the base), then head moves to it and pushed to master. +pub const GC_RB_NONE : u8 = 0; +pub const GC_RB_RESET : u8 = 1; +pub const GC_RB_CARRY : u8 = 2; + +pub fn gc_rebase(head_ok: bool, pushed_ok: bool, same: bool, master_has_head: bool) -> u8 { + if (head_ok == false or pushed_ok == false or master_has_head) { + return GC_RB_NONE; + } + if (same) { + return GC_RB_RESET; + } + return GC_RB_CARRY; +} + +/// A link whose seals were saved by an older t27c has them saved again on its own tree (t27c seal --save, then +/// --verify), after its tests passed. Only .trinity/seals/ may change: every changed path must be a seal. +pub fn gc_regen_ok(changed: usize, seals: usize) -> bool { + return changed == seals; +} + +/// The outcome to show: FORWARD or CARRIED become REGEN when the link's seals were re-saved; any other stands. +pub fn gc_regen_view(out: u8, resealed: bool) -> u8 { + if (resealed and (out == GC_OUT_FORWARD or out == GC_OUT_CARRIED)) { + return GC_OUT_REGEN; + } + return out; +} + +/// The first red prefix of a batch, by binary search. red[j-1] is true when the golden head after the +/// first j links of the batch is red (replayed locally; monotone: once red, red). Returns j, the link +/// that turned it red, or 0 when no prefix is red. +pub fn gc_bisect(n: usize, red: []const bool) -> usize { + if (n == 0) { + return 0; + } + if (red[n - 1] == false) { + return 0; + } + var lo : usize = 1; + var hi : usize = n; + while (lo < hi) { + var mid : usize = lo + (hi - lo) / 2; + if (red[mid - 1]) { + hi = mid; + } else { + lo = mid + 1; + } + } + return lo; +} + ; --- Tests --------------------------------------------------------------------------------------------- ; Verdict rows below: 1 = GC_V_PASS, 2 = GC_V_REUSED, 0 = GC_V_NONE, 3 = GC_V_FAIL. Path ids are any ; distinct numbers; the host's are path hashes. The pass's head is 1 in every landing test. @@ -477,9 +647,9 @@ test negative_control_a_link_that_is_not_golden_never_lands_and_moves_nothing { assert out[2] == GC_OUT_CARRIED; } -test negative_control_a_parent_nobody_landed_fails_closed { +test negative_control_a_link_with_no_parent_id_fails_closed { var id : [2]u64 = [2]u64{ 11, 12 }; - var parent : [2]u64 = [2]u64{ 1, 99 }; + var parent : [2]u64 = [2]u64{ 1, 0 }; var v : [10]u8 = [10]u8{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; var eo : [2]usize = [2]usize{ 0, 1 }; var ep : [2]u64 = [2]u64{ 100, 200 }; @@ -620,3 +790,122 @@ invariant five_stages_and_done_after_them { assert GC_STAGES == 5; assert GC_DONE == GC_RECEIPT + 1; } + +; --- Section 6 tests ----------------------------------------------------------------------------------- + +test the_chain_follows_master_after_a_squash_merge { + // nothing unpushed and master lost the head: reset both refs to master + assert gc_rebase(true, true, true, false) == GC_RB_RESET; + // links unpushed and master lost the head: carry them onto master + assert gc_rebase(true, true, false, false) == GC_RB_CARRY; + // master holds the head, or a ref is missing: leave everything alone + assert gc_rebase(true, true, true, true) == GC_RB_NONE; + assert gc_rebase(true, true, false, true) == GC_RB_NONE; + assert gc_rebase(false, true, true, false) == GC_RB_NONE; + assert gc_rebase(true, false, false, false) == GC_RB_NONE; +} + +test re_saved_seals_may_change_only_seals_and_land_as_regen { + assert gc_regen_ok(3, 3) == true and gc_regen_ok(0, 0) == true; + // a changed path that is not a seal refuses the re-save + assert gc_regen_ok(3, 2) == false; + assert gc_regen_view(GC_OUT_FORWARD, true) == GC_OUT_REGEN and gc_regen_view(GC_OUT_CARRIED, true) == GC_OUT_REGEN; + assert gc_regen_view(GC_OUT_FORWARD, false) == GC_OUT_FORWARD; + // a refusal is never dressed as a landing + assert gc_regen_view(GC_OUT_CONFLICT, true) == GC_OUT_CONFLICT and gc_regen_view(GC_OUT_NOT_GOLDEN, true) == GC_OUT_NOT_GOLDEN; +} + +test a_parent_the_chain_never_saw_is_carried_and_a_meeting_edit_still_conflicts { + var id : [3]u64 = [3]u64{ 11, 12, 13 }; + var parent : [3]u64 = [3]u64{ 1, 99, 98 }; + var v : [15]u8 = [15]u8{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + var eo : [3]usize = [3]usize{ 0, 1, 2 }; + var ep : [3]u64 = [3]u64{ 100, 200, 100 }; + var ek : [3]u8 = [3]u8{ EDIT_WHOLE, EDIT_WHOLE, EDIT_WHOLE }; + var el : [3]u32 = [3]u32{ 0, 0, 0 }; + var eh : [3]u32 = [3]u32{ 0, 0, 0 }; + var io : [1]usize = [1]usize{ 0 }; + var ip : [1]u64 = [1]u64{ 900 }; + var out : [3]u8 = undefined; + var pos : [3]usize = undefined; + assert gc_land(3, &id, &parent, &v, 3, &eo, &ep, &ek, &el, &eh, 0, &io, &ip, 1, &out, &pos) == 2; + assert out[0] == GC_OUT_FORWARD and out[1] == GC_OUT_CARRIED; + // link 2 wrote the path link 0 wrote: carrying it past link 0 meets + assert out[2] == GC_OUT_CONFLICT; +} + +test a_lane_is_ready_only_when_every_stage_is_golden_in_order { + var v : [15]u8 = [15]u8{ 1, 2, 1, 1, 1, 1, 1, 1, 3, 0, 1, 0, 1, 1, 1 }; + assert lane_ready(&v, 0) == true; + assert lane_ready(&v, 1) == false; + assert lane_ready(&v, 2) == false; +} + +test batch_takes_ready_links_in_order_and_skips_the_rest { + var v : [15]u8 = [15]u8{ 1, 1, 1, 1, 1, 1, 1, 3, 0, 0, 2, 2, 2, 2, 2 }; + var out : [8]usize = [8]usize{ 9, 9, 9, 9, 9, 9, 9, 9 }; + assert batch_from(3, &v, &out) == 2; + assert out[0] == 0; + assert out[1] == 2; +} + +test batch_stops_at_the_cap { + var v : [50]u8 = [50]u8{ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; + var out : [8]usize = [8]usize{ 0, 0, 0, 0, 0, 0, 0, 0 }; + assert batch_from(10, &v, &out) == GC_BATCH_MAX; + assert out[7] == 7; +} + +test push_waits_for_the_window_then_goes_once { + assert when_to_push(3, 2, 0) == GC_PUSH_WAIT; + assert when_to_push(15, 2, 0) == GC_PUSH_NOW; + assert when_to_push(0, 8, 0) == GC_PUSH_NOW; +} + +test a_jammed_queue_doubles_the_window_and_holds_a_full_batch { + assert when_to_push(20, 3, 200) == GC_PUSH_WAIT; + assert when_to_push(30, 3, 200) == GC_PUSH_NOW; + assert when_to_push(0, 8, 500) == GC_PUSH_WAIT; +} + +test negative_control_nothing_ready_pushes_nothing_however_long_it_waited { + assert when_to_push(600, 0, 0) == GC_PUSH_NOTHING; +} + +test negative_control_a_lane_may_only_land_locally_or_push_a_batch { + assert lane_may(GC_ACT_LAND_LOCAL) == true; + assert lane_may(GC_ACT_BATCH_PUSH) == true; + assert lane_may(GC_ACT_PUSH_PER_COMMIT) == false; + assert lane_may(GC_ACT_UPDATE_BRANCH) == false; + assert lane_may(GC_ACT_RERUN_AFTER_DERIVED_REBASE) == false; + assert lane_may(GC_ACT_WRITE_HEAD_BY_HAND) == false; + assert lane_may(GC_ACT_PUSH_NOT_GOLDEN) == false; +} + +test a_derived_only_rebase_reruns_nothing { + assert gc_rerun_local(GC_OUT_FORWARD) == false; + assert gc_rerun_local(GC_OUT_CARRIED) == false; + assert gc_rerun_local(GC_OUT_REGEN) == false; + assert gc_rerun_local(GC_OUT_REJUDGE) == true; + assert gc_rerun_local(GC_OUT_CONFLICT) == true; +} + +test bisect_finds_the_first_red_prefix_and_none_when_green { + var green : [8]bool = [8]bool{ false, false, false, false, false, false, false, false }; + var r5 : [8]bool = [8]bool{ false, false, false, false, true, true, true, true }; + var r1 : [8]bool = [8]bool{ true, true, true, true, true, true, true, true }; + var r8 : [8]bool = [8]bool{ false, false, false, false, false, false, false, true }; + assert gc_bisect(8, &green) == 0; + assert gc_bisect(8, &r5) == 5; + assert gc_bisect(8, &r1) == 1; + assert gc_bisect(8, &r8) == 8; + assert gc_bisect(0, &green) == 0; +} + +invariant landing_constants_are_a_small_batch_in_a_real_window { + assert GC_BATCH_MAX >= 2; + assert GC_WINDOW_MIN > 0; + assert GC_CI_JAM > 0; + assert GC_PUSH_NOTHING < GC_PUSH_WAIT; + assert GC_PUSH_WAIT < GC_PUSH_NOW; +} diff --git a/specs/t27f/petal_menu.t27 b/specs/t27f/petal_menu.t27 index 63f7607461..363c6b16a6 100644 --- a/specs/t27f/petal_menu.t27 +++ b/specs/t27f/petal_menu.t27 @@ -113,12 +113,13 @@ pub const QY : [108]i64 = [ 272637, 248279, 287010, 349402, ]; -/// L2. The 27 worlds, in petal order. Owner, 2026-10-20: CHAT is the THIRD petal (index 2, Cmd+3; the pair -/// chat + commander lives there) and LOGS takes index 0; petals 19..25 are the seven homes t27a, t27b, t27c, -/// t27f, t27o, t27t, t27u (specs/t27os/homes.t27 maps a home to its petal and reads its names from here, so -/// this string is the ONE definition). Before: TriangleLogo.worlds, from sacred_worlds.zig:99-141, with CHAT -/// first and REELS .. COMMUNITY at 19..25. -pub const WORLD_NAMES : str = "LOGS,CODE,CHAT,DEBUG,REVIEW,TRANSLATE,VIBEE,VOICE,COMPOSE,FILES,EDITOR,BUILD,TEST,TERMINAL,GIT,DEPLOY,DePIN NODE,SETTINGS,DOCS,T27A,T27B,T27C,T27F,T27O,T27T,T27U,ABOUT"; +/// L2. The 27 petals ARE the 27 agent letters (owner, 2026-10-20): petal p is agent ORDINAL p + 1, A .. Z then Ti, +/// so the three realms are the three LAYERs of specs/agents: RAZUM 0..8 = Archetypal A-I, MATERIYA 9..17 = +/// Spiritual J-R, DUKH 18..26 = Physical S-Ti. CHAT lives on the third petal, C COMPILER (chat + console). +/// Each name is the plain letter, plus the name of the tool t27 where that tool exists (t27a, t27b, t27c, +/// t27f, t27o, t27t, t27u); no domain words. This string is the ONE definition (swap the table here): T27 OS reads the tooltip and the screen labels from it. Before: the nineteen worlds of +/// TriangleLogo.worlds (sacred_worlds.zig:99-141) with LOGS first and CHAT third, and the seven homes at 19..25. +pub const WORLD_NAMES : str = "ISA,BENCH,CHAT,HEALTH,EXPERIENCE,FRONTEND,ARENA,HIVE,ISSUES,JOBS,KANBAN,LAB,MAP,PULSE,OUROBOROS,PULLS,QUEEN,ROADMAP,SPECS,TRAINER,UNIVERSITY,VERDICTS,WIDGETS,EXTERNAL,YIELD,DOCS,SECURITY"; pub const REALM_NAMES : str = "RAZUM,MATERIYA,DUKH"; /// L3. Colours as 0xAARRGGBB. Realm colours from sacred_worlds.zig: gold, cyan, purple. diff --git a/specs/t27f/widget_factory.t27 b/specs/t27f/widget_factory.t27 index 6ffefd4b06..d145d6848a 100644 --- a/specs/t27f/widget_factory.t27 +++ b/specs/t27f/widget_factory.t27 @@ -49,6 +49,8 @@ use t27f::ui_plan::WIDGET_UNKNOWN; use t27mcp::service::home_of; use t27mcp::service::has_prefix; use t27mcp::service::H_T27; +use t27os::agent_parts::agents_of_widget; +use t27os::agent_parts::WIDGET_COUNT; /// The stages, in order. A stage reads only the one before it. pub const ST_SPEC : u8 = 0; @@ -242,3 +244,35 @@ test the_logic_stage_removes_exactly_the_logic_file { assert wave_of(after_logic(1000, 51)) == 2; assert port_gate(PORT_WORDS, PORT_LOGIC, true, true, 1432, after_logic(1432, 370)); } + +/// THE AGENTS RULE (owner, 2026-10-20, mandatory): every widget carries its pinned agents, the owner letter +/// plus the letters that feed it, so a human can see whose work a page shows. The data is +/// t27os/agent_parts.t27 (agents_of_widget); it is referenced here, not copied. A widget passes the `spec` +/// stage only with an owner entry there. +pub fn has_agents(owner: u8) -> bool { + return owner >= 1 and owner <= 27; +} + +/// The gate of the spec stage for gallery widget i: it has an owner and at least one feeder pinned. +pub fn spec_stage_ok(i: u32) -> bool { + if (i >= WIDGET_COUNT) { + return false; + } + return has_agents(agents_of_widget(i, 0)) and has_agents(agents_of_widget(i, 1)); +} + +test the_spec_stage_refuses_a_widget_without_pinned_agents { + assert has_agents(0) == false; + assert has_agents(28) == false; + assert has_agents(11); + assert spec_stage_ok(0); + assert spec_stage_ok(194); + assert spec_stage_ok(195) == false; + var i : u32 = 0; + var ok : u32 = 0; + while (i < WIDGET_COUNT) { + if (spec_stage_ok(i)) { ok = ok + 1; } + i = i + 1; + } + assert ok == WIDGET_COUNT; +} diff --git a/specs/t27os/agent_parts.t27 b/specs/t27os/agent_parts.t27 new file mode 100644 index 0000000000..5abb7b4832 --- /dev/null +++ b/specs/t27os/agent_parts.t27 @@ -0,0 +1,1023 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/t27os/agent_parts.t27 -- each letter of the 27-agent alphabet gets its own parts: widgets and app surfaces +; Owner, 2026-10-20: T27 OS is a spaceship instrument panel for working on the project; study all agents and +; give each letter widgets chosen from its DOMAIN. Closes #11275. ASCII only (L3), English only (LANG-EN). +; Inputs, read 2026-10-20: the DOMAIN of specs/agents/.t27 (ORDINAL 1..27, Ti = 27) and the 195 ids +; of the widget gallery (gHashTag/trinity apps/website/public/widgets/gallery.t27), by gallery index; titles +; read from public/term//meta.json where the id alone did not say what the page shows. +; This is the per-widget refinement of homes.t27 (which maps widgets to 8 HOMES): a home is a product, a +; letter is a domain. It adds no widget and no claim about what a page does. +; Decisions worth knowing: golden-* lessons go to N (their DOMAIN is GoldenFloat) not Z; basics-* lessons go +; to Z (Zero-Touch UX / Docs) except those whose subject is another domain (tests F, backends C, tri W). +; An honest 0: 13 letters have no widget in the gallery. They are listed in NO_WIDGETS with a suggestion +; (data, not built); no widget is forced onto a letter to fill its console. +; phi^2 + 1/phi^2 = 3 | TRINITY + +; Second input (owner extension, 2026-10-20: split the whole app.t27.ai/game idea into 27 parts): every SURFACE of the +; app, read from gHashTag/trinity apps/website/src: the 21 HUD_VIEWS of components/queenHud.ts, the 10 screens of the +; TRI app (lib/triScreens.ts TRI_SCREENS, the inner tabs of the TRI view) and every page routed in main.tsx. +; Kinds: hud_view, hud_tab, page. The inner tabs of other views (e.g. the SPECS layers) are the view's own +; and are not split further: a layer shares its view's letter. + +module T27osAgentParts; + +use t27os::homes::home_of_agent; +use t27os::homes::HOME_IDS; +use t27os::homes::AGENT_LETTERS; +use t27os::session_store::csv_field; +use t27f::petal_menu::WORLD_NAMES; + +pub const WIDGET_COUNT : u32 = 195; +pub const LETTER_COUNT : u32 = 27; +pub const NO_LETTER : u8 = 0; + +; Index = ORDINAL - 1. Derived from DOMAIN, 2-3 words, for the console title. +pub const INSTRUMENT_LABEL : [27]str = [ + "ARCHITECTURE ADR SOUL", "BUILD PIPELINE", "COMPILER CORE", "DE-ZIG MIGRATION", "EXPERIENCE XAI", "FORMAL CONFORMANCE", + "GRAPH ARCHBENCH", "HSLM NN ARCHITECTURES", "ISA REGISTERS", "JOBS TASK ROUTING", "KERNEL FPGA MAC", "LANGUAGE SYNTAX", + "METRICS TELEMETRY", "NUMERIC GOLDENFLOAT", "ORCHESTRATION PHASES", "SACRED PHYSICS", "QUEUE SCHEDULING", "RUNTIME BOOTSTRAP", + "SPECS STANDARDS", "QUEEN LOTUS", "UNIVERSE LEVELS", "VERDICT BENCH", "WORKFLOW TRI CELL", "EXTERNAL BINDINGS", + "YIELD DEPIN", "ZERO-TOUCH UX DOCS", "SECURITY RESERVE", +]; + +; NO_WIDGETS: the letters with an honest 0, each with the existing t27 widget or spec that could become its +; first instrument. Empty string = the letter has widgets. +pub const NO_WIDGETS : [27]str = [ + "specs/t27os/agents.t27 plus docs/adr/: an ADR index card", + "", + "", + "the t27c-stages page re-run on a Zig-free tree: a migration burn-down", + "specs/pipeline/experience_save.t27: lessons saved per ring", + "", + "specs/graph/knowledge_graph.t27 and the T27F graph page: the knowledge graph widget", + "", + "specs/isa/registers.t27: a register file readout of the 27 Coptic registers", + "", + "", + "specs/t27f/glyph57.t27 or the basics-the-module-line lesson: a syntax-vNEXT sampler", + "", + "", + "specs/queen/lotus.t27 and specs/t27os/queen.t27: the phase ring of the loop", + "specs/physics/sacred_lattice.t27: a lattice view", + "specs/queen/drain.t27: queue depth and drain rate", + "specs/runtime/execute.t27: a boot-step ladder", + "", + "", + "specs/t27f/deps_card.t27 or specs/t27f/frontend.t27: the 27 petals as a level map", + "", + "", + "specs/interop/gf_cross_language.t27: a language-binding parity table", + "specs/depin/prove.t27: a DePIN proof readout", + "", + "", +]; + +; LETTER_OF_WIDGET[i] = ordinal 1..27 of the agent that serves widget i, in gallery order. One comment line +; per group names the words that decided it. +; Groups in array order (index range, then the DOMAIN words that decided it); the array rows follow the same order. +; 0..8 x7-board fasm mutate perf2-loop rgmii jtag pins devkit-flow play-verilog: bitstream, FPGA, JTAG, pins -> K Kernel / FPGA MAC +; 9..9 bit-diff: bitstream diff -> K FPGA +; 10..10 build-receipt: receipt of a build -> B Build / Pipeline +; 11..11 cost-curves: resource cost curves -> M Metrics / Telemetry +; 12..12 gf16-calc: GoldenFloat calculator -> N Numeric / GoldenFloat +; 13..18 idcode pin-map placement lut-explain resource-bars slack-waterfall (timing): FPGA readouts -> K +; 19..19 test-waves: waveforms of a test run -> F Formal Conformance +; 20..24 synth-smells bit-autopsy fasm-skyline vcd-wrapped fits-on: synthesis, FASM, bitstream -> K +; 25..25 stat-card: a figure card -> M Metrics / Telemetry +; 26..28 lut-depth fsm-sketch lut-treemap: LUT and FSM mapping -> K +; 29..29 t27-cell: the tri cell -> W Workflow / tri cell +; 30..30 t27-vs-nvfp4: number format against FP4 -> N Numeric +; 31..31 uart-bucket: UART on the board -> K +; 32..36 tri-fpga-specs tools wire batch status: the FPGA wire and tools -> K +; 37..37 tri-fpga-tmpcheck: pinned bench files -> K +; 38..38 tri-fpga-keycheck: wire keys -> Ti Security / Reserve +; 39..39 tri-fpga-selftest: self-test checks -> F Formal Conformance +; 40..57 tri-x7-refs audit mutate, t27c clock-domain clocks wukong cts mmcm timing reset-sync mtbf fifo xdc rxcap: FPGA clocking -> K +; 58..58 t27-tri-competitors: audit of a comparison table -> V Verdict / Bench +; 59..59 t27-tri-discard: what the parser throws away -> C Compiler Core +; 60..64 tri-fpga-txhold rxcap rxer steps ioclients: FPGA Ethernet bench -> K +; 65..67 tri-fpga-claim release next: take, free and pick the bench lock -> J Jobs / Task Routing +; 68..69 t27c-memory t27c-spi-tb: FPGA memory and SPI -> K +; 70..75 t27c-check-*: conformance checks of a peripheral spec -> F +; 76..81 t27c-verilog-* uart spi apb axi4 bridge top: Verilog for the FPGA -> K +; 82..88 t27c-verilog-*-tb: testbenches -> F Formal Conformance +; 89..91 t27c-hir-*: high-level IR -> C Compiler Core +; 92..93 t27c-eth-crc t27c-rgmii: Ethernet on the FPGA -> K +; 94..94 jtag-verdict: a verdict -> V Verdict / Bench +; 95..98 play-hello-world rust js, t27c-hello-world: compile and run a spec -> C +; 99..99 t27-tri-misread: code that compiles and is wrong -> F Formal Conformance +; 100..100 test-touch: tests -> F +; 101..101 t27c-stages: compiler stages -> C Compiler Core +; 102..111 e8m0 ocp-mx gfternary golden-sieve tnf17 activation-quantizer: number formats -> N +; 112..116 bitnet majority neuron mlp, ternary-dot, ternary-mac: NN architectures -> H HSLM / NN +; 117..119 t27c-gft-smul sadd signed-mac: GoldenFloat arithmetic -> N +; 120..120 t27c-gft-relu: NN activation -> H +; 121..121 t27c-gft-exp2: GoldenFloat exp2 -> N +; 122..125 t27c-gft-argmax4 nll sgd-step xornet: NN loss, update, net -> H +; 126..134 basics-what-a-spec-is .. expressions: lessons -> Z Zero-Touch UX / Docs +; 135..136 basics-a-test-block many-tests: test blocks -> F +; 137..144 basics-functions .. use-other-modules: lessons -> Z +; 145..145 basics-invariants: invariants -> F Formal Conformance +; 146..148 basics-gen-ts seven-backends stages-of-t27c: backends and stages -> C Compiler Core +; 149..149 basics-tri-commands: tri commands -> W Workflow / tri cell +; 150..152 basics-reading-errors a-small-program where-next: lessons -> Z +; 153..179 golden-* (split phi lucas radix gf4..gf1024 gft8..32 decode): GoldenFloat -> N Numeric / GoldenFloat +; 180..180 t27-tri-vectors: tests that are prose -> F +; 181..181 t27-tri-census: counted populations -> M Metrics +; 182..183 t27-tri-seals t27-tri-prose: seals and prose in specs -> S Specs / Standardization +; 184..184 t27-tri-lean: proofs -> F Formal Conformance +; 185..186 tri-selftest play-terminal-gates: tri commands and gates -> W Workflow / tri cell +; 187..188 wars tri-game-tick: the arena and the loop tick -> T Queen / Lotus Orchestrator +; 189..189 tri-game-vault: content-addressed integrity of the loop skills -> Ti Security +; 190..192 perf2-knockouts race-the-bee gatle: the agent game -> T Queen +; 193..193 tri-blog-list: the published posts -> Z Docs +; 194..194 tri-game-selftest: the game self-test -> T Queen +pub const LETTER_OF_WIDGET : [195]u8 = [ + 11, 11, 11, 11, 11, 11, 11, 11, 11, + 11, + 2, + 13, + 14, + 11, 11, 11, 11, 11, 11, + 6, + 11, 11, 11, 11, 11, + 13, + 11, 11, 11, + 23, + 14, + 11, + 11, 11, 11, 11, 11, + 11, + 27, + 6, + 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, + 22, + 3, + 11, 11, 11, 11, 11, + 10, 10, 10, + 11, 11, + 6, 6, 6, 6, 6, 6, + 11, 11, 11, 11, 11, 11, + 6, 6, 6, 6, 6, 6, 6, + 3, 3, 3, + 11, 11, + 22, + 3, 3, 3, 3, + 6, + 6, + 3, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 8, 8, 8, 8, 8, + 14, 14, 14, + 8, + 14, + 8, 8, 8, 8, + 26, 26, 26, 26, 26, 26, 26, 26, 26, + 6, 6, + 26, 26, 26, 26, 26, 26, 26, 26, + 6, + 3, 3, 3, + 23, + 26, 26, 26, + 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, 14, + 6, + 13, + 19, 19, + 6, + 23, 23, + 20, 20, + 27, + 20, 20, 20, + 26, + 20, +]; + +pub fn letter_of_widget(i: u32) -> u8 { + if (i >= WIDGET_COUNT) { + return 0; + } + return LETTER_OF_WIDGET[i]; +} + +pub fn widgets_of_letter(letter: u32) -> u32 { + var n : u32 = 0; + var i : u32 = 0; + while (i < WIDGET_COUNT) { + if (LETTER_OF_WIDGET[i] == letter) { n = n + 1; } + i = i + 1; + } + return n; +} + +; The gallery index of the k-th (0-based) widget of a letter; WIDGET_COUNT when there is none. +pub fn nth_widget_of_letter(letter: u32, k: u32) -> u32 { + var seen : u32 = 0; + var i : u32 = 0; + while (i < WIDGET_COUNT) { + if (LETTER_OF_WIDGET[i] == letter) { + if (seen == k) { return i; } + seen = seen + 1; + } + i = i + 1; + } + return WIDGET_COUNT; +} + +pub fn label_of_letter(letter: u32) -> []const u8 { + if (letter < 1 or letter > LETTER_COUNT) { + return ""; + } + return INSTRUMENT_LABEL[letter - 1]; +} + +pub fn letters_without_widgets() -> u32 { + var n : u32 = 0; + var l : u32 = 1; + while (l <= LETTER_COUNT) { + if (widgets_of_letter(l) == 0) { n = n + 1; } + l = l + 1; + } + return n; +} + +; Spot checks: gallery id at an index and the letter that must serve it (10 of the 195). +pub const ANCHOR_IDX : [10]u32 = [0, 10, 12, 19, 65, 94, 101, 117, 126, 153]; +pub const ANCHOR_ID : [10]str = ["x7-board", "build-receipt", "gf16-calc", "test-waves", "tri-fpga-claim", "jtag-verdict", "t27c-stages", "t27c-gft-smul", "basics-what-a-spec-is", "golden-split"]; +pub const ANCHOR_LETTER : [10]u8 = [11, 2, 14, 6, 10, 22, 3, 14, 26, 14]; + +test the_per_letter_counts_add_up_to_the_whole_gallery { + var total : u32 = 0; + var l : u32 = 1; + while (l <= LETTER_COUNT) { + total = total + widgets_of_letter(l); + l = l + 1; + } + assert total == 195; + assert WIDGET_COUNT == 195; +} + +test no_widget_is_unassigned_and_every_letter_is_in_range { + var i : u32 = 0; + while (i < WIDGET_COUNT) { + assert letter_of_widget(i) >= 1; + assert letter_of_widget(i) <= 27; + i = i + 1; + } +} + +test every_letter_has_a_label_and_a_no_widgets_row { + var l : u32 = 1; + while (l <= LETTER_COUNT) { + assert label_of_letter(l) != ""; + l = l + 1; + } + assert INSTRUMENT_LABEL[10] == "KERNEL FPGA MAC"; + assert INSTRUMENT_LABEL[26] == "SECURITY RESERVE"; +} + +test letters_with_zero_widgets_are_exactly_those_with_a_suggestion { + var l : u32 = 1; + var empty : u32 = 0; + while (l <= LETTER_COUNT) { + if (widgets_of_letter(l) == 0) { + assert NO_WIDGETS[l - 1] != ""; + empty = empty + 1; + } else { + assert NO_WIDGETS[l - 1] == ""; + } + l = l + 1; + } + assert empty == letters_without_widgets(); + assert empty == 13; +} + +test nth_widget_round_trips_for_every_letter { + var l : u32 = 1; + while (l <= LETTER_COUNT) { + var k : u32 = 0; + while (k < widgets_of_letter(l)) { + var w : u32 = nth_widget_of_letter(l, k); + assert w < WIDGET_COUNT; + assert letter_of_widget(w) == l; + if (k > 0) { assert w > nth_widget_of_letter(l, k - 1); } + k = k + 1; + } + assert nth_widget_of_letter(l, k) == WIDGET_COUNT; + l = l + 1; + } +} + +test ten_spot_checks_by_gallery_index { + var a : u32 = 0; + while (a < 10) { + assert letter_of_widget(ANCHOR_IDX[a]) == ANCHOR_LETTER[a]; + a = a + 1; + } + assert letter_of_widget(0) == 11; + assert letter_of_widget(194) == 20; + assert widgets_of_letter(11) == 64; + assert widgets_of_letter(14) == 43; +} + +test negative_control_out_of_range_has_no_letter_and_no_widget { + assert letter_of_widget(195) == 0; + assert letter_of_widget(4000000) == 0; + assert letter_of_widget(194) != 0; + assert widgets_of_letter(0) == 0; + assert widgets_of_letter(28) == 0; + assert nth_widget_of_letter(1, 0) == WIDGET_COUNT; + assert nth_widget_of_letter(11, 64) == WIDGET_COUNT; + assert label_of_letter(0) == ""; + assert label_of_letter(28) == ""; + assert letter_of_widget(10) != 11; +} + +; --- SURFACES of app.t27.ai/game, by letter --- +pub const SURFACE_COUNT : u32 = 65; +pub const SURFACE_IDS : [65]str = [ + "comb", "specs", "kanban", "map", "factory", "research", + "skills", "crons", "agents", "functions", "tools", "project", + "tri", "passport", "browser", "roadmap", "leaderboard", "wars", + "token", "providers", "widgets", "tri:feed", "tri:chat", "tri:script", + "tri:audio", "tri:image", "tri:avatar", "tri:video", "tri:editor", "tri:profile", + "tri:crm", "App", "ProductionDashboard", "TechTreePage", "Start", "FormatSelection", + "HardwareVerification", "Course", "FpgaTraining", "CaseStudies", "GFT", "Licensing", + "Proof", "Trinity", "AboutAuthor", "Blog", "Resources", "Foundry", + "DevKit", "QueenUniverse", "Passport", "TrinityCanvas", "TrinityCanvasWasm", "QuantumLab", + "Playground", "CosmicChat", "SpecExplorer", "SkillExplorer", "CronExplorer", "AgentExplorer", + "FunctionExplorer", "ToolExplorer", "ProviderExplorer", "SystemDocs", "ClientsConsole", +]; +pub const SURFACE_KINDS : [65]str = [ + "hud_view", "hud_view", "hud_view", "hud_view", "hud_view", "hud_view", + "hud_view", "hud_view", "hud_view", "hud_view", "hud_view", "hud_view", + "hud_view", "hud_view", "hud_view", "hud_view", "hud_view", "hud_view", + "hud_view", "hud_view", "hud_view", "hud_tab", "hud_tab", "hud_tab", + "hud_tab", "hud_tab", "hud_tab", "hud_tab", "hud_tab", "hud_tab", + "hud_tab", "page", "page", "page", "page", "page", + "page", "page", "page", "page", "page", "page", + "page", "page", "page", "page", "page", "page", + "page", "page", "page", "page", "page", "page", + "page", "page", "page", "page", "page", "page", + "page", "page", "page", "page", "page", +]; +; One reason per surface, the DOMAIN words that decided it. +pub const SURFACE_WHY : [65]str = [ + "the Queen board home: Queen / Lotus Orchestrator", + "the .t27 spec cards: Specs / Standardization", + "issues and lanes on a board: Jobs / Task Routing", + "the map of levels and domains: Universe Levels / Domains", + "the widget factory turning tools into wasm: Build / Pipeline", + "research notes kept as memory: Experience / XAI", + "skills are the workflow steps: Workflow / tri cell", + "cron jobs: Queue / Scheduling", + "the agent cards and their phases: Orchestration / Phases", + "functions of the language: Language / Syntax", + "MCP tools offered to others: External Bindings / Interop", + "the project, rules and system in detail: Architecture / ADR / SOUL", + "the TRI app embedded in the game: External Bindings / Interop", + "the record a result must carry: Verdict / Bench", + "the person's remote browser pod: External Bindings / Interop", + "the whole stack rewritten in .t27: De-Zigfication / Migration", + "who lends the swarm a lane and what bees did: Yield / Fitness", + "agent configurations compared on pinned issues: Graph / ArchBench", + "TRI on TON testnet and who earned it: Yield / DePIN", + "who sells and who rents out compute: Yield / DePIN", + "shareable widgets with embed code: Zero-Touch UX / Docs", + "the feed: Zero-Touch UX", + "the chat: Zero-Touch UX", + "AI pipeline stage 1: Build / Pipeline", + "AI pipeline stage: Build / Pipeline", + "AI pipeline stage: Build / Pipeline", + "AI pipeline stage: Build / Pipeline", + "AI pipeline stage: Build / Pipeline", + "AI pipeline stage, the editor: Build / Pipeline", + "the person's identity and keys: Security / Reserve", + "the owner's CRM of outside clients: External Bindings", + "/ the home page: Zero-Touch UX / Docs", + "/dashboard: Metrics / Telemetry", + "/tree the tech tree: Architecture", + "/start: Zero-Touch UX", + "/select a number format: Numeric / GoldenFloat", + "/verification: Kernel / FPGA", + "/course /t27-basics /ai-numbers /goldenfloat: lessons, Docs", + "/fpga-training: Kernel / FPGA MAC", + "/cases measured cases: Verdict / Bench", + "/gft GoldenFloat ternary: Numeric / GoldenFloat", + "/ip licensing: Security / Reserve", + "/proof: Formal Conformance", + "/trinity the project page: Architecture", + "/about: Docs", + "/blog and /blog/:slug: Docs", + "/resources: Docs", + "/foundry (club): Yield / Fitness", + "/devkit FPGA dev kit: Kernel / FPGA MAC", + "/queen the Queen board shell: Queen / Lotus", + "/passport and /passport/research: Verdict / Bench", + "/canvas: Universe", + "/wasm canvas in wasm: Runtime / Bootstrap", + "/quantum and /lab: Physics", + "/play compile a spec in the browser: Compiler Core", + "/chat: Zero-Touch UX", + "/specs: Specs", + "/skills: Workflow", + "/crons: Queue / Scheduling", + "/agents: Orchestration", + "/functions: Language", + "/tools: External Bindings", + "/providers: Yield / DePIN", + "/docs system documentation: Architecture / ADR / SOUL", + "/clients: External Bindings", +]; +pub const LETTER_OF_SURFACE : [65]u8 = [ + 20, 19, 10, 21, 2, 5, 23, 17, 15, 12, 24, 1, 24, 22, 24, 4, + 25, 7, 25, 25, 26, 26, 26, 2, 2, 2, 2, 2, 2, 27, 24, 26, + 13, 1, 26, 14, 11, 26, 11, 22, 14, 27, 6, 1, 26, 26, 26, 25, + 11, 20, 22, 21, 18, 16, 3, 26, 19, 23, 17, 15, 12, 24, 25, 1, + 24, +]; + +pub fn letter_of_surface(i: u32) -> u8 { + if (i >= SURFACE_COUNT) { + return 0; + } + return LETTER_OF_SURFACE[i]; +} + +pub fn surfaces_of_letter(letter: u32) -> u32 { + var n : u32 = 0; + var i : u32 = 0; + while (i < SURFACE_COUNT) { + if (LETTER_OF_SURFACE[i] == letter) { n = n + 1; } + i = i + 1; + } + return n; +} + +pub fn nth_surface_of_letter(letter: u32, k: u32) -> u32 { + var seen : u32 = 0; + var i : u32 = 0; + while (i < SURFACE_COUNT) { + if (LETTER_OF_SURFACE[i] == letter) { + if (seen == k) { return i; } + seen = seen + 1; + } + i = i + 1; + } + return SURFACE_COUNT; +} + +; The parts of a letter: surfaces + widgets. A letter with 0 parts is an honest empty console. +pub fn parts_of_letter(letter: u32) -> u32 { + return surfaces_of_letter(letter) + widgets_of_letter(letter); +} + +; The home (homes.t27 HOME_IDS index) a letter's console lives in. +pub fn home_of_letter(letter: u32) -> u32 { + return home_of_agent(letter); +} + +pub fn letters_without_parts() -> u32 { + var n : u32 = 0; + var l : u32 = 1; + while (l <= LETTER_COUNT) { + if (parts_of_letter(l) == 0) { n = n + 1; } + l = l + 1; + } + return n; +} + +test every_surface_is_assigned_and_the_letter_counts_add_up { + var total : u32 = 0; + var l : u32 = 1; + while (l <= LETTER_COUNT) { + total = total + surfaces_of_letter(l); + l = l + 1; + } + assert total == SURFACE_COUNT; + var i : u32 = 0; + while (i < SURFACE_COUNT) { + assert letter_of_surface(i) >= 1; + assert letter_of_surface(i) <= 27; + i = i + 1; + } +} + +test the_21_hud_views_and_10_tri_screens_are_all_present { + var v : u32 = 0; + var t : u32 = 0; + var i : u32 = 0; + while (i < SURFACE_COUNT) { + if (SURFACE_KINDS[i] == "hud_view") { v = v + 1; } + if (SURFACE_KINDS[i] == "hud_tab") { t = t + 1; } + i = i + 1; + } + assert v == 21; + assert t == 10; + assert SURFACE_IDS[0] == "comb"; + assert SURFACE_IDS[20] == "widgets"; +} + +test every_letter_lists_its_parts_and_a_home { + var l : u32 = 1; + var seen_widgets : u32 = 0; + while (l <= LETTER_COUNT) { + assert label_of_letter(l) != ""; + assert home_of_letter(l) <= 8; + var k : u32 = 0; + while (k < surfaces_of_letter(l)) { + assert letter_of_surface(nth_surface_of_letter(l, k)) == l; + k = k + 1; + } + assert nth_surface_of_letter(l, k) == SURFACE_COUNT; + seen_widgets = seen_widgets + widgets_of_letter(l); + l = l + 1; + } + assert seen_widgets == 195; + assert HOME_IDS[0] == "t27a"; +} + +test surface_spot_checks_and_negative_control { + assert letter_of_surface(0) == 20; + assert letter_of_surface(1) == 19; + assert letter_of_surface(7) == 17; + assert letter_of_surface(7) != 19; + assert letter_of_surface(SURFACE_COUNT) == 0; + assert surfaces_of_letter(0) == 0; + assert surfaces_of_letter(28) == 0; + assert nth_surface_of_letter(8, 0) == SURFACE_COUNT; +} + +; --- PINNED AGENTS: every widget and surface carries its owner plus up to 3 FEEDERS, the letters whose tool, spec or data produces what it shows --- +; Owner rule 2026-10-20: the work of the system is transparent to a human. Packed: feeder 1 in byte 0, feeder 2 in byte 1, feeder 3 in byte 2, 0 = none. +; Reasons, one line per group of widgets in array order (index range, then why): +; 0..9: K Kernel/FPGA owns pins and JTAG; C emits the bitstream via t27c silicon, V holds the verdict of the run +; 10..10: B Build/Pipeline owns the receipt; C is the build, V checks it +; 11..11: M Metrics owns the curve; K supplies the resource numbers, C the build that measured them +; 12..12: N GoldenFloat owns the calculator; S holds the format spec, C compiles it +; 13..18: K reads the board; C builds the design, V gives the verdict +; 19..19: F owns the test waves; C runs the compile, V the verdict +; 20..24: K owns synthesis readouts; C builds, V verdicts +; 25..25: M owns the figure card; V supplies the figure, S the spec it is read from +; 26..28: K owns LUT and FSM mapping; C maps, L holds the syntax of the FSM +; 29..29: W owns the tri cell; L syntax and S specs are what a cell holds +; 30..30: N owns the format comparison; S has the format spec, V the verdict +; 31..31: K owns the UART; C builds it, V verdicts +; 32..36: K owns the FPGA wire; C emits it, J routes the bench job +; 37..37: K owns pinned bench files; J holds the lock, V verdicts +; 38..38: Ti owns wire keys; K is the wire, X is the binding +; 39..39: F owns the self-test; K is the board under test, V the verdict +; 40..57: K owns FPGA clocking; C emits the constraints, V verdicts +; 58..58: V audits a comparison table; S holds the standard, N the numbers compared +; 59..59: C owns the parser; L syntax and S specs are what it discards +; 60..64: K owns the Ethernet bench; C builds, V verdicts +; 65..67: J owns the bench lock; K is the board, Q the queue +; 68..69: K owns memory and SPI; C builds, F conforms +; 70..75: F owns peripheral checks; C compiles, S the spec, K the peripheral +; 76..81: K owns Verilog for the FPGA; C emits it, F tests it +; 82..88: F owns testbenches; C emits them, K the hardware, S the spec +; 89..91: C owns the HIR; L syntax and S specs are its input +; 92..93: K owns Ethernet on the FPGA; C builds, V verdicts +; 94..94: V owns the verdict; K is the board, C the build +; 95..98: C compiles and runs a spec; L syntax, Z the lesson +; 99..99: F owns the misread; C compiles, L parses +; 100..100: F owns the test touch; W runs it, C compiles +; 101..101: C owns the stages; L is the syntax stage, S the specs +; 102..111: N owns formats; S holds the specs, C compiles +; 112..116: H owns NN; N the number formats, F the tests +; 117..119: N owns GoldenFloat arithmetic; C compiles, F tests +; 120..120: H owns the activation; N the number format +; 121..121: N owns exp2; C compiles, F tests +; 122..125: H owns loss and update; N numbers, F tests +; 126..134: Z lessons: the spec (S) and its syntax (L) are what they open +; 135..136: F owns test blocks; Z lessons, L syntax +; 137..144: Z lessons on functions and modules; L syntax, C compiles +; 145..145: F owns invariants; L syntax, Z lesson +; 146..148: C owns backends and stages; Z lesson, L syntax +; 149..149: W owns tri commands; Z lesson, C the compiler +; 150..152: Z lessons on errors and programs; L syntax, C compiler +; 153..179: N owns golden-*; Z lesson, S format spec +; 180..180: F owns vectors; S specs, L syntax +; 181..181: M owns the census; S specs counted, V verdicts +; 182..183: S owns seals and prose; L syntax, V verdict +; 184..184: F owns proofs; L syntax, S specs +; 185..186: W owns tri commands and gates; F tests, C compiler +; 187..188: T owns the arena and tick; W workflow, Q queue +; 189..189: Ti owns the vault; T the loop skills, V verdict +; 190..192: T owns the agent game; V verdict, G agent configurations compared +; 193..193: Z owns the posts; E experience written up, S specs quoted +; 194..194: T owns the game self-test; F tests, W workflow +pub const FEEDERS_OF_WIDGET : [195]u32 = [ + 5635, 5635, 5635, 5635, 5635, 5635, 5635, 5635, + 5635, 5635, 5635, 779, 787, 5635, 5635, 5635, + 5635, 5635, 5635, 5635, 5635, 5635, 5635, 5635, + 5635, 4886, 3075, 3075, 3075, 4876, 5651, 5635, + 2563, 2563, 2563, 2563, 2563, 5642, 6155, 5643, + 5635, 5635, 5635, 5635, 5635, 5635, 5635, 5635, + 5635, 5635, 5635, 5635, 5635, 5635, 5635, 5635, + 5635, 5635, 3603, 4876, 5635, 5635, 5635, 5635, + 5635, 4363, 4363, 4363, 1539, 1539, 725763, 725763, + 725763, 725763, 725763, 725763, 1539, 1539, 1539, 1539, + 1539, 1539, 1248003, 1248003, 1248003, 1248003, 1248003, 1248003, + 1248003, 4876, 4876, 4876, 5635, 5635, 779, 6668, + 6668, 6668, 6668, 3075, 791, 4876, 787, 787, + 787, 787, 787, 787, 787, 787, 787, 787, + 1550, 1550, 1550, 1550, 1550, 1539, 1539, 1539, + 14, 1539, 1550, 1550, 1550, 1550, 3091, 3091, + 3091, 3091, 3091, 3091, 3091, 3091, 3091, 3098, + 3098, 780, 780, 780, 780, 780, 780, 780, + 780, 6668, 3098, 3098, 3098, 794, 780, 780, + 780, 4890, 4890, 4890, 4890, 4890, 4890, 4890, + 4890, 4890, 4890, 4890, 4890, 4890, 4890, 4890, + 4890, 4890, 4890, 4890, 4890, 4890, 4890, 4890, + 4890, 4890, 4890, 4890, 3091, 5651, 5644, 5644, + 4876, 774, 774, 4375, 4375, 5652, 1814, 1814, + 1814, 4869, 5894, +]; + +; Surfaces by SURFACE_IDS index: a view takes the feeders of the data it draws; the AI pipeline tabs (stages) are fed by X bindings and Z docs, a page by the spec or verdict it shows. +pub const FEEDERS_OF_SURFACE : [65]u32 = [ + 1446154, 780, 4372, 3347, 1708803, 3350, 5139, 2580, + 5139, 787, 5891, 1299, 791, 2819, 6932, 4867, + 1814, 5652, 3355, 4376, 1245954, 3352, 5144, 6668, + 6680, 6680, 6680, 6680, 1704716, 5656, 3348, 4884, + 1311254, 5395, 791, 5651, 790, 4876, 6659, 3595, + 787, 275, 3094, 5139, 5, 4869, 6163, 5652, + 6659, 5909, 6915, 5138, 5379, 5390, 4876, 6164, + 3075, 5139, 2580, 5139, 787, 5891, 5656, 4890, + 5145, +]; + +pub fn agents_of_widget(i: u32, k: u32) -> u8 { + if (i >= WIDGET_COUNT) { + return 0; + } + if (k == 0) { + return LETTER_OF_WIDGET[i]; + } + if (k > 3) { + return 0; + } + return ((FEEDERS_OF_WIDGET[i] >> ((k - 1) * 8)) & 255) as u8; +} + +pub fn agents_of_surface(i: u32, k: u32) -> u8 { + if (i >= SURFACE_COUNT) { + return 0; + } + if (k == 0) { + return LETTER_OF_SURFACE[i]; + } + if (k > 3) { + return 0; + } + return ((FEEDERS_OF_SURFACE[i] >> ((k - 1) * 8)) & 255) as u8; +} + +; How many widgets (surfaces) letter is pinned to as owner (k == 0) or as a feeder (k 1..3). +pub fn pinned_widgets(letter: u32, feeder: bool) -> u32 { + var n : u32 = 0; + var i : u32 = 0; + while (i < WIDGET_COUNT) { + var k : u32 = 0; + if (feeder) { k = 1; } + while (k < 4) { + if (agents_of_widget(i, k) == letter) { n = n + 1; } + if (feeder == false) { k = 4; } else { k = k + 1; } + } + i = i + 1; + } + return n; +} + +pub fn pinned_surfaces(letter: u32, feeder: bool) -> u32 { + var n : u32 = 0; + var i : u32 = 0; + while (i < SURFACE_COUNT) { + var k : u32 = 0; + if (feeder) { k = 1; } + while (k < 4) { + if (agents_of_surface(i, k) == letter) { n = n + 1; } + if (feeder == false) { k = 4; } else { k = k + 1; } + } + i = i + 1; + } + return n; +} + +; A letter that is pinned to nothing at all. +pub fn letters_never_pinned() -> u32 { + var n : u32 = 0; + var l : u32 = 1; + while (l <= LETTER_COUNT) { + if (pinned_widgets(l, false) + pinned_widgets(l, true) + pinned_surfaces(l, false) + pinned_surfaces(l, true) == 0) { n = n + 1; } + l = l + 1; + } + return n; +} + +test every_widget_and_surface_has_an_owner_pinned { + var i : u32 = 0; + while (i < WIDGET_COUNT) { + assert agents_of_widget(i, 0) != 0; + assert agents_of_widget(i, 0) == letter_of_widget(i); + i = i + 1; + } + var s : u32 = 0; + while (s < SURFACE_COUNT) { + assert agents_of_surface(s, 0) != 0; + s = s + 1; + } +} + +test no_feeder_equals_the_owner_or_repeats_and_zeros_are_trailing { + var i : u32 = 0; + while (i < WIDGET_COUNT) { + var a : u8 = agents_of_widget(i, 1); + var b : u8 = agents_of_widget(i, 2); + var c : u8 = agents_of_widget(i, 3); + assert a != 0; + assert a != agents_of_widget(i, 0); + if (b != 0) { assert b != a; assert b != agents_of_widget(i, 0); } + if (c != 0) { assert b != 0; assert c != a; assert c != b; assert c != agents_of_widget(i, 0); } + assert a <= 27; + assert b <= 27; + assert c <= 27; + i = i + 1; + } + var s : u32 = 0; + while (s < SURFACE_COUNT) { + var a : u8 = agents_of_surface(s, 1); + var b : u8 = agents_of_surface(s, 2); + var c : u8 = agents_of_surface(s, 3); + assert a != 0; + assert a != agents_of_surface(s, 0); + if (b != 0) { assert b != a; assert b != agents_of_surface(s, 0); } + if (c != 0) { assert b != 0; assert c != a; assert c != b; assert c != agents_of_surface(s, 0); } + assert a <= 27; + assert b <= 27; + assert c <= 27; + s = s + 1; + } +} + +test negative_control_past_the_end_is_nobody { + assert agents_of_widget(195, 0) == 0; + assert agents_of_widget(0, 4) == 0; + assert agents_of_widget(0, 0) == 11; + assert agents_of_widget(0, 1) == 3; + assert agents_of_widget(0, 2) == 22; + assert agents_of_widget(0, 3) == 0; + assert agents_of_surface(65, 0) == 0; + assert agents_of_surface(0, 0) == 20; + assert agents_of_surface(0, 3) == 22; + assert agents_of_surface(0, 4) == 0; +} + +test per_letter_counts_as_owner_and_as_feeder_are_pinned { + assert pinned_widgets(1, false) == 0; + assert pinned_widgets(1, true) == 0; + assert pinned_widgets(2, false) == 1; + assert pinned_widgets(2, true) == 0; + assert pinned_widgets(3, false) == 12; + assert pinned_widgets(3, true) == 111; + assert pinned_widgets(4, false) == 0; + assert pinned_widgets(4, true) == 0; + assert pinned_widgets(5, false) == 0; + assert pinned_widgets(5, true) == 1; + assert pinned_widgets(6, false) == 22; + assert pinned_widgets(6, true) == 24; + assert pinned_widgets(7, false) == 0; + assert pinned_widgets(7, true) == 3; + assert pinned_widgets(8, false) == 10; + assert pinned_widgets(8, true) == 0; + assert pinned_widgets(9, false) == 0; + assert pinned_widgets(9, true) == 0; + assert pinned_widgets(10, false) == 3; + assert pinned_widgets(10, true) == 6; + assert pinned_widgets(11, false) == 64; + assert pinned_widgets(11, true) == 20; + assert pinned_widgets(12, false) == 0; + assert pinned_widgets(12, true) == 44; + assert pinned_widgets(13, false) == 3; + assert pinned_widgets(13, true) == 0; + assert pinned_widgets(14, false) == 43; + assert pinned_widgets(14, true) == 11; + assert pinned_widgets(15, false) == 0; + assert pinned_widgets(15, true) == 0; + assert pinned_widgets(16, false) == 0; + assert pinned_widgets(16, true) == 0; + assert pinned_widgets(17, false) == 0; + assert pinned_widgets(17, true) == 5; + assert pinned_widgets(18, false) == 0; + assert pinned_widgets(18, true) == 0; + assert pinned_widgets(19, false) == 2; + assert pinned_widgets(19, true) == 73; + assert pinned_widgets(20, false) == 6; + assert pinned_widgets(20, true) == 1; + assert pinned_widgets(21, false) == 0; + assert pinned_widgets(21, true) == 0; + assert pinned_widgets(22, false) == 2; + assert pinned_widgets(22, true) == 60; + assert pinned_widgets(23, false) == 4; + assert pinned_widgets(23, true) == 4; + assert pinned_widgets(24, false) == 0; + assert pinned_widgets(24, true) == 1; + assert pinned_widgets(25, false) == 0; + assert pinned_widgets(25, true) == 0; + assert pinned_widgets(26, false) == 21; + assert pinned_widgets(26, true) == 38; + assert pinned_widgets(27, false) == 2; + assert pinned_widgets(27, true) == 0; + assert pinned_surfaces(1, false) == 4; + assert pinned_surfaces(1, true) == 1; + assert pinned_surfaces(2, false) == 7; + assert pinned_surfaces(2, true) == 2; + assert pinned_surfaces(3, false) == 1; + assert pinned_surfaces(3, true) == 19; + assert pinned_surfaces(4, false) == 1; + assert pinned_surfaces(4, true) == 0; + assert pinned_surfaces(5, false) == 1; + assert pinned_surfaces(5, true) == 3; + assert pinned_surfaces(6, false) == 1; + assert pinned_surfaces(6, true) == 0; + assert pinned_surfaces(7, false) == 1; + assert pinned_surfaces(7, true) == 1; + assert pinned_surfaces(8, false) == 0; + assert pinned_surfaces(8, true) == 0; + assert pinned_surfaces(9, false) == 0; + assert pinned_surfaces(9, true) == 0; + assert pinned_surfaces(10, false) == 1; + assert pinned_surfaces(10, true) == 3; + assert pinned_surfaces(11, false) == 3; + assert pinned_surfaces(11, true) == 2; + assert pinned_surfaces(12, false) == 2; + assert pinned_surfaces(12, true) == 7; + assert pinned_surfaces(13, false) == 1; + assert pinned_surfaces(13, true) == 5; + assert pinned_surfaces(14, false) == 2; + assert pinned_surfaces(14, true) == 2; + assert pinned_surfaces(15, false) == 2; + assert pinned_surfaces(15, true) == 0; + assert pinned_surfaces(16, false) == 1; + assert pinned_surfaces(16, true) == 0; + assert pinned_surfaces(17, false) == 2; + assert pinned_surfaces(17, true) == 3; + assert pinned_surfaces(18, false) == 1; + assert pinned_surfaces(18, true) == 1; + assert pinned_surfaces(19, false) == 2; + assert pinned_surfaces(19, true) == 22; + assert pinned_surfaces(20, false) == 2; + assert pinned_surfaces(20, true) == 18; + assert pinned_surfaces(21, false) == 2; + assert pinned_surfaces(21, true) == 4; + assert pinned_surfaces(22, false) == 3; + assert pinned_surfaces(22, true) == 11; + assert pinned_surfaces(23, false) == 2; + assert pinned_surfaces(23, true) == 5; + assert pinned_surfaces(24, false) == 6; + assert pinned_surfaces(24, true) == 11; + assert pinned_surfaces(25, false) == 5; + assert pinned_surfaces(25, true) == 1; + assert pinned_surfaces(26, false) == 10; + assert pinned_surfaces(26, true) == 10; + assert pinned_surfaces(27, false) == 2; + assert pinned_surfaces(27, true) == 3; +} + +test letters_never_pinned_are_exactly_those_reported { + assert letters_never_pinned() == 1; +} + +; --- TOOLS: each letter is a tool t27 with its own page ------------------------------------------- +; Owner, 2026-10-20 (decided): T27 OS is an ergonomic project-management system; every letter is a tool +; t27 with its own page, and everything on every page is a widget. The rule of toolchain.t27 holds: a +; tool is never named before its artifact exists, so TOOL_ARTIFACTS cites, per letter, the existing path or +; app view the page is drawn from (checked 2026-10-20). A page that has only a HUD view as artifact says so. +; TOOL_PAGES are the short petal labels of t27f::petal_menu::WORLD_NAMES (a test below holds them equal; the +; check lives here because petal_menu cannot import this module, which imports homes, which imports petal_menu). +; Index = ORDINAL - 1, as INSTRUMENT_LABEL. +pub const TOOL_NAMES : [27]str = [ + "t27a", "t27b", "t27c", "t27d", "t27e", "t27f", "t27g", "t27h", "t27i", "t27j", "t27k", "t27l", "t27m", "t27n", + "t27o", "t27p", "t27q", "t27r", "t27s", "t27t", "t27u", "t27v", "t27w", "t27x", "t27y", "t27z", "t27ti", +]; +pub const TOOL_PAGES : [27]str = [ + "ISA", "BENCH", "CHAT", "HEALTH", "EXPERIENCE", "FRONTEND", "ARENA", "HIVE", "ISSUES", "JOBS", "KANBAN", "LAB", "MAP", + "PULSE", "OUROBOROS", "PULLS", "QUEEN", "ROADMAP", "SPECS", "TRAINER", "UNIVERSITY", "VERDICTS", "WIDGETS", "EXTERNAL", + "YIELD", "DOCS", "SECURITY", +]; +pub const TOOL_ARTIFACTS : [27]str = [ + "specs/isa/t27a.t27", + "cli/t27b and specs/t27os/bench.t27", + "the chat of the window (specs/t27os/glass_window.t27) and bootstrap/ (t27c)", + "specs/t27mcp/dashboard.t27 (MCP t27_dashboard)", + ".trinity/experience (tri experience)", + "specs/t27f/frontend.t27", + "HUD views wars and leaderboard (SURFACE_IDS)", + "HUD view comb (SURFACE_IDS) and specs/queen/actors.t27", + "MCP t27_issues and t27_epics, tri issues", + "specs/automation and the lab jobs (specs/lab/job_kind.t27)", + "HUD view kanban (SURFACE_IDS), drawn by specs/t27os/kanban.t27", + "specs/lab", + "HUD view map (SURFACE_IDS)", + "docs/now and specs/t27os/domain_pulse.t27", + "specs/tools/t27o", + "tri pr, tri red and specs/claw", + "specs/t27os/queen.t27 and specs/t27os/evolve.t27", + "HUD views research and roadmap (SURFACE_IDS)", + "HUD view specs (SURFACE_IDS), MCP t27_specs and .trinity/seals", + "specs/t27t/t27t.t27", + "the 7 courses of specs/t27os/homes.t27 (trinity courses.t27)", + "t27c verdict and tri verdict (HUD passport)", + "specs/t27f/widget_factory.t27 and the HUD view widgets (SURFACE_IDS)", + "HUD view providers (SURFACE_IDS) and specs/t27os/keys.t27", + "specs/depin/prove.t27 and the HUD views token and passport (SURFACE_IDS)", + "docs/ and specs/blog", + "specs/policy/secret_gate.t27 (gitleaks) and lefthook.yml", +]; + +/// Letter 1..27 -> index 0..26 into the TOOL_ tables, 27 (none) when out of range. +pub fn tool_index(l: u32) -> u32 { + if (l < 1 or l > LETTER_COUNT) { return LETTER_COUNT; } + return l - 1; +} + +pub fn tool_of_letter(l: u32) -> []const u8 { + if (l < 1 or l > LETTER_COUNT) { return ""; } + return TOOL_NAMES[l - 1]; +} + +pub fn page_of_letter(l: u32) -> []const u8 { + if (l < 1 or l > LETTER_COUNT) { return ""; } + return TOOL_PAGES[l - 1]; +} + +pub fn artifact_of_letter(l: u32) -> []const u8 { + if (l < 1 or l > LETTER_COUNT) { return ""; } + return TOOL_ARTIFACTS[l - 1]; +} + +test every_letter_has_a_tool_a_page_and_an_existing_artifact { + var l : u32 = 1; + while (l <= LETTER_COUNT) { + assert tool_of_letter(l).len >= 4; + assert tool_of_letter(l)[0] == 116; + assert page_of_letter(l).len >= 2; + assert artifact_of_letter(l).len >= 8; + l = l + 1; + } + assert tool_of_letter(1) == "t27a"; + assert tool_of_letter(11) == "t27k"; + assert tool_of_letter(27) == "t27ti"; + assert page_of_letter(11) == "KANBAN"; + assert page_of_letter(3) == "CHAT"; + assert page_of_letter(9) == "ISSUES"; + assert artifact_of_letter(1) == "specs/isa/t27a.t27"; +} + +test the_tool_name_is_t27_plus_the_lowercased_agent_letter { + var l : u32 = 1; + while (l <= LETTER_COUNT) { + const letter : []const u8 = AGENT_LETTERS[l - 1]; + const t : []const u8 = tool_of_letter(l); + assert t.len == letter.len + 3; + assert t[1] == 50; + assert t[2] == 55; + var i : usize = 0; + while (i < letter.len) { + assert t[3 + i] == (letter[i] | 32); + i = i + 1; + } + l = l + 1; + } +} + +test the_pages_are_unique_and_equal_the_petal_labels_of_the_menu { + var p : u32 = 0; + var b : [32]u8 = undefined; + while (p < LETTER_COUNT) { + const n : usize = csv_field(WORLD_NAMES, p, b[0..32]); + const page : []const u8 = page_of_letter(p + 1); + assert n == page.len; + var i : usize = 0; + while (i < n) { + assert b[i] == page[i]; + i = i + 1; + } + var q : u32 = p + 1; + while (q < LETTER_COUNT) { + assert page_of_letter(q + 1) != page; + q = q + 1; + } + p = p + 1; + } +} + +test negative_control_out_of_range_letter_has_no_tool { + assert tool_of_letter(0) == ""; + assert tool_of_letter(28) == ""; + assert page_of_letter(0) == ""; + assert artifact_of_letter(4000000) == ""; + assert tool_index(0) == LETTER_COUNT; + assert tool_index(27) == 26; + assert page_of_letter(10) != page_of_letter(11); +} diff --git a/specs/t27os/bench.t27 b/specs/t27os/bench.t27 index 854bc01a47..d8b7998f3c 100644 --- a/specs/t27os/bench.t27 +++ b/specs/t27os/bench.t27 @@ -88,6 +88,8 @@ pub const ST_IDCODE : u64 = 2; pub const ST_LOADED : u64 = 4; pub const ST_LED_KNOWN : u64 = 8; pub const ST_UART_KNOWN : u64 = 16; +pub const ST_CHAIN_READ : u64 = 32; // --detect ran against a cable that is seen +pub const ST_PORT : u64 = 64; // a serial device node of this board exists pub const ST_LED_UNIT : u64 = 256; pub const ST_UART_UNIT : u64 = 65536; pub const ST_LOADED_AT_UNIT : u64 = 4294967296; @@ -117,10 +119,15 @@ pub const DIE_NO_CABLE : u8 = 0; pub const DIE_CABLE_ONLY : u8 = 1; pub const DIE_READY : u8 = 2; pub const DIE_RUNNING : u8 = 3; +/// The cable is seen and --detect ran, and it listed no device (board unpowered or disconnected). +pub const DIE_CHAIN_EMPTY : u8 = 4; pub fn die_status(a: u64) -> u8 { if (st_has(a, ST_CABLE) == false) { return DIE_NO_CABLE; } - if (st_has(a, ST_IDCODE) == false) { return DIE_CABLE_ONLY; } + if (st_has(a, ST_IDCODE) == false) { + if (st_has(a, ST_CHAIN_READ)) { return DIE_CHAIN_EMPTY; } + return DIE_CABLE_ONLY; + } if (st_has(a, ST_LOADED) == false) { return DIE_READY; } return DIE_RUNNING; } @@ -128,6 +135,7 @@ pub fn die_status(a: u64) -> u8 { pub fn die_status_text(s: u8) -> string { if (s == DIE_NO_CABLE) { return "no cable"; } if (s == DIE_CABLE_ONLY) { return "cable, idcode not read"; } + if (s == DIE_CHAIN_EMPTY) { return "chain empty"; } if (s == DIE_READY) { return "ready"; } return "running"; } @@ -152,7 +160,8 @@ pub const SRC_YOSYS : u8 = 8; pub const SRC_RECEIPT : u8 = 9; pub const SRC_DATASHEET : u8 = 10; pub const SRC_STOPWATCH : u8 = 11; -pub const SRC_COUNT : u8 = 12; +pub const SRC_LS_TTY : u8 = 12; +pub const SRC_COUNT : u8 = 13; pub fn src_command(s: u8) -> string { if (s == SRC_SCAN_USB) { return "openFPGALoader -c digilent_hs2 --scan-usb"; } @@ -166,6 +175,7 @@ pub fn src_command(s: u8) -> string { if (s == SRC_RECEIPT) { return "t27c silicon receipt"; } if (s == SRC_DATASHEET) { return "Xilinx DS180 Table 1"; } if (s == SRC_STOPWATCH) { return "wall clock of the build"; } + if (s == SRC_LS_TTY) { return "ls /dev/cu.usbserial-*"; } return ""; } @@ -496,6 +506,372 @@ pub fn uart_lines(text: string) -> u64 { return n; } +// ---------------------------------------------------------------- the BENCH cards (2026-10-20) +// One card per board row. Card 0 is the AX7203 (registry row ax7203), card 1 the Zynq XC7Z020 (registry +// row pzsdr-d: same cable serial and same PL part as die D of 2026-10-10; the board MODEL is a GUESS, see +// fpga/HARDWARE_SSOT.md 2026-10-20). The window only draws what these functions say. +pub const CARD_AX7203 : u8 = 0; +pub const CARD_ZYNQ : u8 = 1; +pub const CARD_COUNT : u8 = 2; + +pub fn card_board(c: u8) -> u8 { + if (c == CARD_ZYNQ) { return BOARD_PZSDR_D; } + return BOARD_AX7203; +} + +pub fn card_title(c: u8) -> string { + if (c == CARD_ZYNQ) { return "Zynq XC7Z020"; } + return "AX7203"; +} + +/// Whether the board's MODEL is established. The AX7203 is (camera, 2026-10-08); the Zynq's is not. +pub fn card_model_known(c: u8) -> bool { return c != CARD_ZYNQ; } + +pub fn card_model_text(c: u8) -> string { + if (c == CARD_ZYNQ) { return "model not established (guess)"; } + return "ALINX AX7203 (camera)"; +} + +pub fn card_cable_text(c: u8) -> string { + if (c == CARD_ZYNQ) { return "Digilent Adept FTDI2232"; } + return "Digilent HS2 FT232H"; +} + +pub fn card_serial(c: u8) -> string { return board_serial(card_board(c)); } + +/// The chain diagram: one box per TAP from TDI. The PL box is the registry's PL position. +pub fn card_chain_len(c: u8) -> u32 { return chain_len(card_board(c)); } + +pub fn box_label(c: u8, k: u32) -> string { + if (k == pl_pos(card_board(c))) { return "PL"; } + return "ARM A9"; +} + +/// The IDCODE measured on 2026-10-20 at position k of the card, or HEX_NONE (not measured today: the +/// AX7203's chain was empty). +pub fn card_idcode_measured(c: u8, k: u32) -> u64 { + if (c == CARD_ZYNQ) { + if (k == 0) { return 0x4ba00477; } + if (k == 1) { return 0x3727093; } + } + return HEX_NONE; +} + +// --- Provenance of the 2026-10-20 values: command text and time are the measurement itself. +pub const CARD_MEASURED_AT : u64 = 1792488364; +pub const FLD_CABLE : u8 = 0; +pub const FLD_CHAIN : u8 = 1; +pub const FLD_IDCODE : u8 = 2; +pub const FLD_PORT : u8 = 3; +pub const FLD_COUNT : u8 = 4; + +pub fn card_measured_command(c: u8, f: u8) -> string { + if (f == FLD_CABLE) { return "openFPGALoader --scan-usb"; } + if (f == FLD_PORT) { return "ls /dev/cu.usbserial-*"; } + if (c == CARD_ZYNQ) { return "openFPGALoader -c digilent --busdev-num 0:2 --detect"; } + return "openFPGALoader -c digilent_hs2 --busdev-num 0:4 --detect"; +} + +pub fn card_prov(c: u8, f: u8) -> u64 { + if (f == FLD_CABLE) { return prov_make(PV_MEASURED, SRC_SCAN_USB, CARD_MEASURED_AT); } + if (f == FLD_PORT) { return prov_make(PV_MEASURED, SRC_LS_TTY, CARD_MEASURED_AT); } + if (f == FLD_CHAIN) { return prov_make(PV_MEASURED, SRC_DETECT, CARD_MEASURED_AT); } + return prov_of(card_idcode_measured(c, pl_pos(card_board(c))) != HEX_NONE, false, SRC_DETECT, CARD_MEASURED_AT); +} + +// --- Busdev is a value the host reads from the scan, never a constant (the bus moved from 1 to 0). +pub const BUSDEV_NONE : u64 = 1099511627776; + +/// The start of the line that holds text[at]. +pub fn line_start(text: string, at: u32) -> u32 { + var i : u32 = at; + while (i > 0) { + if ((text[i - 1] as u32) == 10) { return i; } + i = i - 1; + } + return 0; +} + +/// The row's cable bus*1000+device from `--scan-usb` ("000 004 0x..." or "001:004"), or BUSDEV_NONE +/// unless exactly one cable matches the row. +pub fn scan_busdev(scan: string, b: u8) -> u64 { + if (cables_matching(scan, b) != 1) { return BUSDEV_NONE; } + var at : u32 = text_next(scan, SCAN_VID, 0); + while (at != NOT_FOUND) { + if (text_hex_at(scan, at + (SCAN_VID.len as u32)) == board_pid(b)) { + if (line_has_serial(scan, at, b)) { + var s : u32 = line_start(scan, at); + var bus : u64 = num_at(scan, s); + if (bus == HEX_NONE) { return BUSDEV_NONE; } + var e : u32 = num_end(scan, s); + while (e < (scan.len as u32) && ((scan[e] as u32) == 32 || (scan[e] as u32) == 58)) { e = e + 1; } + var dev : u64 = num_at(scan, e); + if (dev == HEX_NONE) { return BUSDEV_NONE; } + return bus * 1000 + dev; + } + } + at = text_next(scan, SCAN_VID, at + 1); + } + return BUSDEV_NONE; +} + +pub const CH_EMPTY : u8 = 0; +pub const CH_OK : u8 = 1; +pub const CH_OTHER : u8 = 2; + +/// What a multi-device `--detect` says about the board: no device listed, the PL at the row's position +/// answers the row's IDCODE (version masked), or something else is there. +pub fn chain_state(detect: string, b: u8) -> u8 { + if (detect_count(detect) == 0) { return CH_EMPTY; } + if (idcode_is_row(detect_idcode(detect, pl_pos(b)), b)) { + if (detect_count(detect) == chain_len(b)) { return CH_OK; } + } + return CH_OTHER; +} + +/// A card's state word from what the host read: the scan, the --detect text (ran or not), the ls text. +pub fn card_state(c: u8, scan: string, detect: string, detect_ran: bool, ls: string) -> u64 { + var b : u8 = card_board(c); + var cable : bool = cables_matching(scan, b) >= 1; + var a : u64 = st_pack_a(cable, cable && chain_state(detect, b) == CH_OK && detect_ran, false, false, 0, false, 0); + if (cable && detect_ran) { a = a + ST_CHAIN_READ; } + if (port_count(ls, b) > 0) { a = a + ST_PORT; } + return a; +} + +// --- Serial ports: a device node belongs to a board when its name ends with the board's cable serial +// plus at most one channel digit. /dev/cu.usbserial-130 names no serial of ours and belongs to nobody. +pub const PORT_PREFIX : str = "/dev/cu.usbserial-"; + +pub fn port_belongs(ls: string, at: u32, b: u8) -> bool { + var e : u32 = line_end(ls, at); + var p : u32 = text_next(ls, PORT_PREFIX, at); + if (p == NOT_FOUND) { return false; } + if (p >= e) { return false; } + var serial : string = board_serial(b); + var n : u32 = serial.len as u32; + if (n == 0) { return false; } + var s : u32 = p + (PORT_PREFIX.len as u32); + var k : u32 = 0; + while (k < n) { + if (s + k >= e) { return false; } + if ((ls[s + k] as u32) != (serial[k] as u32)) { return false; } + k = k + 1; + } + var rest : u32 = e - (s + n); + if (rest == 0) { return true; } + if (rest > 1) { return false; } + var d : u32 = ls[s + n] as u32; + return d >= 48 && d <= 57; +} + +/// How many device nodes in `ls /dev/cu.usbserial-*` are the board's, and where the n-th line starts. +pub fn port_count(ls: string, b: u8) -> u32 { + var n : u32 = 0; + var at : u32 = 0; + while (at < (ls.len as u32)) { + if (port_belongs(ls, at, b)) { n = n + 1; } + at = line_end(ls, at) + 1; + } + return n; +} + +pub fn port_nth_start(ls: string, b: u8, n: u32) -> u32 { + var seen : u32 = 0; + var at : u32 = 0; + while (at < (ls.len as u32)) { + if (port_belongs(ls, at, b)) { + if (seen == n) { return at; } + seen = seen + 1; + } + at = line_end(ls, at) + 1; + } + return NOT_FOUND; +} + +// --- The lamp and the layout. Rows and columns are in text cells; the window multiplies by its cell. +pub const LAMP_GREY : u8 = 0; // no cable +pub const LAMP_RED : u8 = 1; // cable seen, chain empty +pub const LAMP_AMBER : u8 = 2; // cable seen, chain not read or not the board's +pub const LAMP_GREEN : u8 = 3; // ready +pub const LAMP_BLUE : u8 = 4; // running + +pub fn lamp_of(s: u8) -> u8 { + if (s == DIE_NO_CABLE) { return LAMP_GREY; } + if (s == DIE_CHAIN_EMPTY) { return LAMP_RED; } + if (s == DIE_CABLE_ONLY) { return LAMP_AMBER; } + if (s == DIE_READY) { return LAMP_GREEN; } + return LAMP_BLUE; +} + +pub const ROW_TITLE : u8 = 0; +pub const ROW_CHAIN : u8 = 1; +pub const ROW_CABLE : u8 = 2; +pub const ROW_SERIAL : u8 = 3; +pub const ROW_BUSDEV : u8 = 4; +pub const ROW_UART : u8 = 5; +pub const ROW_STATUS : u8 = 6; +pub const ROW_BUTTONS : u8 = 7; +pub const ROW_COUNT : u8 = 8; +pub const BOX_W : u32 = 14; +pub const BOX_GAP : u32 = 3; +pub const BTN_W : u32 = 9; +pub const BTN_GAP : u32 = 2; + +pub fn box_x(k: u32) -> u32 { return k * (BOX_W + BOX_GAP); } +pub fn chain_width(c: u8) -> u32 { return card_chain_len(c) * BOX_W + (card_chain_len(c) - 1) * BOX_GAP; } +pub fn btn_x(i: u32) -> u32 { return i * (BTN_W + BTN_GAP); } + +pub fn row_label(r: u8) -> string { + if (r == ROW_CHAIN) { return "chain"; } + if (r == ROW_CABLE) { return "cable"; } + if (r == ROW_SERIAL) { return "serial"; } + if (r == ROW_BUSDEV) { return "busdev"; } + if (r == ROW_UART) { return "uart"; } + if (r == ROW_STATUS) { return "status"; } + return ""; +} + +// --- The buttons. Their commands go to a PTY pane as text pieces; none runs unless the card allows it. +pub const ACT_DETECT : u8 = 0; +pub const ACT_FLASH : u8 = 1; +pub const ACT_UART : u8 = 2; +pub const ACT_COUNT : u8 = 3; +pub const CMD_CAP : u32 = 200; +pub const UART_BAUD_TEXT : str = " 115200\n"; // a default of this file, not a measured baud + +pub fn act_label(a: u8) -> string { + if (a == ACT_DETECT) { return "DETECT"; } + if (a == ACT_FLASH) { return "FLASH"; } + if (a == ACT_UART) { return "UART"; } + return ""; +} + +/// FLASH loads SRAM (no -f): reversible. It needs an explicit bitstream path and a confirm, every time. +pub fn act_needs_confirm(a: u8) -> bool { return a == ACT_FLASH; } + +pub fn act_enabled(st: u64, a: u8) -> bool { + var s : u8 = die_status(st); + if (a == ACT_DETECT) { return s != DIE_NO_CABLE; } + if (a == ACT_FLASH) { return s == DIE_READY || s == DIE_RUNNING; } + if (a == ACT_UART) { return st_has(st, ST_PORT); } + return false; +} + +/// Only path characters: no space, quote, semicolon, ampersand, pipe, dollar, backtick or newline reaches a shell. +pub fn arg_safe(arg: string) -> bool { + var n : u32 = arg.len as u32; + if (n == 0) { return false; } + var i : u32 = 0; + while (i < n) { + var ch : u32 = arg[i] as u32; + var ok : bool = (ch >= 48 && ch <= 57) || (ch >= 65 && ch <= 90) || (ch >= 97 && ch <= 122); + if (ch == 45 || ch == 46 || ch == 47 || ch == 95 || ch == 43 || ch == 126) { ok = true; } + if (ok == false) { return false; } + i = i + 1; + } + return true; +} + +pub fn ends_with(t: string, suffix: string) -> bool { + var n : u32 = t.len as u32; + var m : u32 = suffix.len as u32; + if (n < m) { return false; } + var k : u32 = 0; + while (k < m) { + if ((t[n - m + k] as u32) != (suffix[k] as u32)) { return false; } + k = k + 1; + } + return true; +} + +pub fn cmd_allowed(c: u8, a: u8, st: u64, bus: u64, arg: string, confirm: bool) -> bool { + if (c >= CARD_COUNT) { return false; } + if (act_enabled(st, a) == false) { return false; } + if (a != ACT_UART) { if (bus == BUSDEV_NONE) { return false; } } + if (a != ACT_DETECT) { + if (arg_safe(arg) == false) { return false; } + } + if (a == ACT_FLASH) { + if (confirm == false) { return false; } + if (ends_with(arg, ".bit") == false) { return false; } + } + if (a == ACT_UART) { + if (text_find(arg, PORT_PREFIX, 0) != 0) { return false; } + } + return cmd_raw_len(c, a, bus, arg) <= CMD_CAP; +} + +/// Pieces 0..8: text, cable name, text, bus digits, ":", device digits, tail, the path, the end. +pub const CMD_PIECES : u32 = 9; + +pub fn cmd_text(c: u8, a: u8, p: u32, arg: string) -> string { + var uart : bool = a == ACT_UART; + if (p == 0) { if (uart) { return "screen "; } return "openFPGALoader -c "; } + if (p == 1) { if (uart) { return ""; } return board_cable(card_board(c)); } + if (p == 2) { if (uart) { return ""; } return " --busdev-num "; } + if (p == 4) { if (uart) { return ""; } return ":"; } + if (p == 6) { + if (a == ACT_DETECT) { return " --detect\n"; } + if (a == ACT_FLASH) { return " "; } + return ""; + } + if (p == 7) { if (a == ACT_DETECT) { return ""; } return arg; } + if (p == 8) { + if (a == ACT_FLASH) { return "\n"; } + if (uart) { return UART_BAUD_TEXT; } + return ""; + } + return ""; +} + +pub fn cmd_piece_len(c: u8, a: u8, bus: u64, arg: string, p: u32) -> u32 { + if (p == 3) { if (a == ACT_UART) { return 0; } return dec_digits(bus / 1000); } + if (p == 5) { if (a == ACT_UART) { return 0; } return dec_digits(bus % 1000); } + var t : string = cmd_text(c, a, p, arg); + return t.len as u32; +} + +pub fn cmd_raw_len(c: u8, a: u8, bus: u64, arg: string) -> u32 { + var n : u32 = 0; + var p : u32 = 0; + while (p < CMD_PIECES) { n = n + cmd_piece_len(c, a, bus, arg, p); p = p + 1; } + return n; +} + +/// The command as its length and k-th character; length 0 when the card does not allow it. +pub fn cmd_len(c: u8, a: u8, st: u64, bus: u64, arg: string, confirm: bool) -> u32 { + if (cmd_allowed(c, a, st, bus, arg, confirm) == false) { return 0; } + return cmd_raw_len(c, a, bus, arg); +} + +pub fn cmd_char(c: u8, a: u8, bus: u64, arg: string, k: u32) -> u32 { + var at : u32 = k; + var p : u32 = 0; + while (p < CMD_PIECES) { + var n : u32 = cmd_piece_len(c, a, bus, arg, p); + if (at < n) { + if (p == 3) { return dec_char(bus / 1000, n, at); } + if (p == 5) { return dec_char(bus % 1000, n, at); } + var t : string = cmd_text(c, a, p, arg); + return t[at] as u32; + } + at = at - n; + p = p + 1; + } + return 0; +} + +pub fn cmd_is(c: u8, a: u8, st: u64, bus: u64, arg: string, confirm: bool, want: string) -> bool { + if (cmd_len(c, a, st, bus, arg, confirm) != (want.len as u32)) { return false; } + var k : u32 = 0; + while (k < (want.len as u32)) { + if (cmd_char(c, a, bus, arg, k) != (want[k] as u32)) { return false; } + k = k + 1; + } + return true; +} + // ---------------------------------------------------------------- tests test three_dice_share_one_row_and_differ_by_busdev { @@ -568,7 +944,8 @@ test every_provenance_kind_carries_its_command_and_time { assert PV_KIND_COUNT == 3; assert src_command(SRC_SCAN_USB) == "openFPGALoader -c digilent_hs2 --scan-usb"; assert src_command(SRC_NONE) == ""; - assert SRC_COUNT == 12; + assert SRC_COUNT == 13; + assert src_command(SRC_LS_TTY) == "ls /dev/cu.usbserial-*"; } test the_summary_row_knows_its_part { @@ -662,3 +1039,120 @@ test the_uart_counter_counts_lines { assert uart_lines("a\nb\nc\n") == 3; assert uart_lines("no newline yet") == 0; } + +test the_2026_10_20_bench_two_cables_on_bus_zero { + const scan : string = "found 2 USB device\nBus device vid:pid probe type manufacturer serial product\n000 004 0x0403:0x6014 ft232H Digilent 210512180081 Digilent USB Device\n000 002 0x0403:0x6010 FTDI2232 Digilent 210203859289 Digilent Adept USB Device\n"; + assert scan_busdev(scan, BOARD_AX7203) == 4; + assert scan_busdev(scan, BOARD_PZSDR_D) == 2; + assert scan_busdev("001:006 0x0403:0x6014 ft232H Digilent 210512180081 x\n", BOARD_AX7203) == 1006; + // three QMTech cables share one serial: no single busdev + const three : string = "001 004 0x0403:0x6014 a 210512180081 b\n001 006 0x0403:0x6014 a 210512180081 b\n001 008 0x0403:0x6014 a 210512180081 b\n"; + assert scan_busdev(three, BOARD_AX7203) == BUSDEV_NONE; + assert scan_busdev("", BOARD_PZSDR_D) == BUSDEV_NONE; + assert line_start("ab\ncd", 4) == 3; + assert line_start("ab\ncd", 1) == 0; +} + +test a_two_device_chain_and_an_empty_chain_are_told_apart { + const zynq : string = "index 0:\n\tidcode 0x4ba00477\n\ttype ARM cortex A9\n\tirlength 4\nindex 1:\n\tidcode 0x3727093\n"; + const empty : string = "Jtag frequency : requested 6.00MHz -> real 6.00MHz\n"; + assert chain_state(zynq, BOARD_PZSDR_D) == CH_OK; + assert chain_state(empty, BOARD_AX7203) == CH_EMPTY; + assert chain_state(zynq, BOARD_AX7203) == CH_OTHER; + assert chain_state("index 0:\n\tidcode 0x3636093\n", BOARD_AX7203) == CH_OK; + const scan : string = "000 004 0x0403:0x6014 ft232H Digilent 210512180081 x\n000 002 0x0403:0x6010 FTDI2232 Digilent 210203859289 y\n"; + const ls : string = "/dev/cu.usbserial-130\n/dev/cu.usbserial-2102038592890\n/dev/cu.usbserial-2102038592891\n/dev/cu.usbserial-210512180081\n"; + const z : u64 = card_state(CARD_ZYNQ, scan, zynq, true, ls); + const x : u64 = card_state(CARD_AX7203, scan, empty, true, ls); + assert die_status(z) == DIE_READY; + assert die_status_text(die_status(z)) == "ready"; + assert st_has(z, ST_PORT); + assert die_status(x) == DIE_CHAIN_EMPTY; + assert die_status_text(die_status(x)) == "chain empty"; + assert die_status(x) != DIE_NO_CABLE; + // no cable at all is a different word + assert die_status(card_state(CARD_ZYNQ, "", "", false, "")) == DIE_NO_CABLE; + // a cable that --detect has not been run against is not an empty chain + assert die_status(card_state(CARD_AX7203, scan, "", false, ls)) == DIE_CABLE_ONLY; + assert lamp_of(DIE_NO_CABLE) == LAMP_GREY; + assert lamp_of(DIE_CHAIN_EMPTY) == LAMP_RED; + assert lamp_of(DIE_CABLE_ONLY) == LAMP_AMBER; + assert lamp_of(DIE_READY) == LAMP_GREEN; + assert lamp_of(DIE_RUNNING) == LAMP_BLUE; +} + +test serial_ports_belong_by_the_cable_serial { + const ls : string = "/dev/cu.usbserial-130\n/dev/cu.usbserial-2102038592890\n/dev/cu.usbserial-2102038592891\n/dev/cu.usbserial-210512180081\n"; + assert port_count(ls, BOARD_PZSDR_D) == 2; + assert port_count(ls, BOARD_AX7203) == 1; + assert port_nth_start(ls, BOARD_PZSDR_D, 0) == 22; + assert port_nth_start(ls, BOARD_PZSDR_D, 1) == 54; + assert port_nth_start(ls, BOARD_PZSDR_D, 2) == NOT_FOUND; + // a longer digit tail is another serial + assert port_count("/dev/cu.usbserial-21020385928912\n", BOARD_PZSDR_D) == 0; + assert port_count("", BOARD_AX7203) == 0; +} + +test the_card_states_its_chain_provenance_and_layout { + assert CARD_COUNT == 2; + assert card_title(CARD_ZYNQ) == "Zynq XC7Z020"; + assert card_chain_len(CARD_ZYNQ) == 2; + assert box_label(CARD_ZYNQ, 0) == "ARM A9"; + assert box_label(CARD_ZYNQ, 1) == "PL"; + assert box_label(CARD_AX7203, 0) == "PL"; + assert card_idcode_measured(CARD_ZYNQ, 0) == 0x4ba00477; + assert card_idcode_measured(CARD_ZYNQ, 1) == 0x3727093; + assert card_idcode_measured(CARD_AX7203, 0) == HEX_NONE; + assert card_serial(CARD_ZYNQ) == "210203859289"; + assert card_model_known(CARD_ZYNQ) == false; + assert card_model_known(CARD_AX7203); + // a measured value carries its command and time; the AX7203's idcode today is not measured, not 0 + assert prov_kind(card_prov(CARD_ZYNQ, FLD_IDCODE)) == PV_MEASURED; + assert prov_at(card_prov(CARD_ZYNQ, FLD_IDCODE)) == 1792488364; + assert prov_kind(card_prov(CARD_AX7203, FLD_IDCODE)) == PV_NOT_MEASURED; + assert prov_kind(card_prov(CARD_AX7203, FLD_CHAIN)) == PV_MEASURED; + assert card_measured_command(CARD_ZYNQ, FLD_CHAIN) == "openFPGALoader -c digilent --busdev-num 0:2 --detect"; + assert card_measured_command(CARD_AX7203, FLD_CHAIN) == "openFPGALoader -c digilent_hs2 --busdev-num 0:4 --detect"; + assert box_x(1) == 17; + assert chain_width(CARD_ZYNQ) == 31; + assert chain_width(CARD_AX7203) == 14; + assert btn_x(2) == 22; + assert ROW_COUNT == 8; + assert row_label(ROW_BUSDEV) == "busdev"; +} + +test button_commands_are_text_pieces_under_the_cap_and_flash_needs_a_path_and_a_confirm { + const scan : string = "000 004 0x0403:0x6014 ft232H Digilent 210512180081 x\n000 002 0x0403:0x6010 FTDI2232 Digilent 210203859289 y\n"; + const zynq : string = "index 0:\n\tidcode 0x4ba00477\nindex 1:\n\tidcode 0x3727093\n"; + const ls : string = "/dev/cu.usbserial-130\n/dev/cu.usbserial-2102038592890\n/dev/cu.usbserial-2102038592891\n/dev/cu.usbserial-210512180081\n"; + const z : u64 = card_state(CARD_ZYNQ, scan, zynq, true, ls); + const bus : u64 = scan_busdev(scan, BOARD_PZSDR_D); + assert cmd_is(CARD_ZYNQ, ACT_DETECT, z, bus, "", false, "openFPGALoader -c digilent --busdev-num 0:2 --detect\n"); + assert cmd_is(CARD_ZYNQ, ACT_UART, z, bus, "/dev/cu.usbserial-2102038592891", false, "screen /dev/cu.usbserial-2102038592891 115200\n"); + // FLASH: no path, no confirm, a wrong suffix, or a shell character each give no command + assert cmd_len(CARD_ZYNQ, ACT_FLASH, z, bus, "", true) == 0; + assert cmd_len(CARD_ZYNQ, ACT_FLASH, z, bus, "/tmp/a.bit", false) == 0; + assert cmd_len(CARD_ZYNQ, ACT_FLASH, z, bus, "/tmp/a.txt", true) == 0; + assert cmd_len(CARD_ZYNQ, ACT_FLASH, z, bus, "/tmp/a.bit; rm -rf x", true) == 0; + assert cmd_is(CARD_ZYNQ, ACT_FLASH, z, bus, "/tmp/a.bit", true, "openFPGALoader -c digilent --busdev-num 0:2 /tmp/a.bit\n"); + assert act_needs_confirm(ACT_FLASH); + assert act_needs_confirm(ACT_DETECT) == false; + // the UART button takes only a serial node + assert cmd_len(CARD_ZYNQ, ACT_UART, z, bus, "/etc/passwd", false) == 0; + // an empty chain still allows DETECT, never FLASH + const x : u64 = card_state(CARD_AX7203, scan, "Jtag frequency : requested 6.00MHz\n", true, ls); + assert act_enabled(x, ACT_DETECT); + assert act_enabled(x, ACT_FLASH) == false; + assert cmd_is(CARD_AX7203, ACT_DETECT, x, 4, "", false, "openFPGALoader -c digilent_hs2 --busdev-num 0:4 --detect\n"); + assert cmd_len(CARD_AX7203, ACT_FLASH, x, 4, "/tmp/a.bit", true) == 0; + // no cable, nothing runs; no bus read, no openFPGALoader command + assert cmd_len(CARD_ZYNQ, ACT_DETECT, 0, bus, "", false) == 0; + assert cmd_len(CARD_ZYNQ, ACT_DETECT, z, BUSDEV_NONE, "", false) == 0; + // over the 200-byte cap is refused + const long : string = "/tmp/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.bit"; + assert cmd_len(CARD_ZYNQ, ACT_FLASH, z, bus, long, true) == 0; + assert cmd_len(CARD_ZYNQ, ACT_FLASH, z, bus, "/tmp/a.bit", true) <= CMD_CAP; + assert act_label(ACT_DETECT) == "DETECT"; + assert act_label(ACT_FLASH) == "FLASH"; + assert act_label(ACT_UART) == "UART"; +} diff --git a/specs/t27os/dash.t27 b/specs/t27os/dash.t27 new file mode 100644 index 0000000000..cf5e183cd3 --- /dev/null +++ b/specs/t27os/dash.t27 @@ -0,0 +1,1020 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/t27os/dash.t27 -- the instrument panel of a home tab: the widget tree, its packer, the gallery +; parse, the cache paths and the hit tests. glass_window only calls these and draws what they place. +; Owner, 2026-10-20: the home tab is a spaceship's INSTRUMENT PANEL built from widgets that contain widgets +; (modelled on the website course and the widgets gallery of gHashTag/trinity): SCREEN > HOME > HERO (title, +; gauge widgets, a segment bar) + one CONSOLE per agent letter > INSTRUMENT (a gallery widget's card.png). +; For t27u, COURSE MAP widgets (7 courses x 9 modules x 3 lessons) follow the consoles. +; THE TREE: a node is a kind and a rule that places the children inside the node's rectangle. There is ONE +; rule, the packer: pack_cols / cell_w / dash_cell_x / dash_cell_y / pack_h place n equal children of a parent of +; width w in rows. Every level calls it: the hero's gauges, a console's instruments, the idle consoles, a +; module's three lessons, the segment bar. A console is a widget whose children are widgets. +; Coordinates are page points: x from the left edge of the page column, y from the top of the page. +; Which letter owns a widget is homes::letter_of_widget (a stub until agent_widgets.t27 lands). Which widget a +; lesson opens is homes::course_widget. Ids, pages and kinds come from the parsed gallery.t27 text, never a +; copy. Cards are cached in ~/.t27os/cards/.png and fetched in the background by /usr/bin/curl. +; ASCII only (L3), English only (LANG-EN). +; phi^2 + 1/phi^2 = 3 | TRINITY + +module T27osDash; + +use t27f::petal_glass::fmax; +use t27f::petal_glass::fmin; +use t27os::homes::H_T27U; +use t27os::homes::AGENT_LETTERS; +use t27os::homes::agents_in; +use t27os::homes::nth_agent; +use t27os::agent_parts::INSTRUMENT_LABEL; +use t27os::agent_parts::letter_of_surface; +use t27os::homes::HOME_COUNT; +use t27os::homes::WIDGET_COUNT; +use t27os::homes::COURSE_COUNT; +use t27os::homes::LESSONS_PER_COURSE; +use t27os::agent_parts::home_of_letter; +use t27os::agent_parts::widgets_of_letter; +use t27os::agent_parts::parts_of_letter; +use t27os::agent_parts::nth_surface_of_letter; +use t27os::agent_parts::SURFACE_COUNT; +use t27os::agent_parts::nth_widget_of_letter; +use t27os::agent_parts::letter_of_widget; +use t27os::agent_parts::LETTER_COUNT; +use t27os::homes::course_widget; +use t27os::session_store::put_bytes; +use t27os::session_store::put_dec; + +; --- N0. Node kinds -------------------------------------------------------------------------------------- + +pub const NK_SCREEN : u32 = 0; +pub const NK_HOME : u32 = 1; +pub const NK_HERO : u32 = 2; +pub const NK_GAUGE : u32 = 3; +pub const NK_CONSOLE : u32 = 4; +pub const NK_INSTRUMENT : u32 = 5; +pub const NK_COURSE_MAP : u32 = 6; +pub const NK_LESSON : u32 = 7; +pub const NK_KINDS : u32 = 8; +pub const NK_NAMES : [8]str = ["SCREEN", "HOME", "HERO", "GAUGE", "CONSOLE", "INSTRUMENT", "COURSE MAP", "LESSON"]; + +/// The kinds a node of kind k holds as children (the tree: SCREEN > HOME > HERO, CONSOLE, COURSE_MAP; a HERO +/// holds GAUGEs, a CONSOLE holds INSTRUMENTs, a COURSE_MAP holds LESSONs); NK_KINDS for a leaf. +pub fn child_kind(k: u32, i: u32) -> u32 { + if (k == NK_SCREEN and i == 0) { return NK_HOME; } + if (k == NK_HOME and i == 0) { return NK_HERO; } + if (k == NK_HOME and i == 1) { return NK_CONSOLE; } + if (k == NK_HOME and i == 2) { return NK_COURSE_MAP; } + if (k == NK_HERO and i == 0) { return NK_GAUGE; } + if (k == NK_CONSOLE and i == 0) { return NK_INSTRUMENT; } + if (k == NK_COURSE_MAP and i == 0) { return NK_LESSON; } + return NK_KINDS; +} + +; --- N1. The packer -------------------------------------------------------------------------------------- + +fn fl(x: u32) -> f64 { return @as(f64, @floatFromInt(x)); } + +pub fn clampf(v: f64, lo: f64, hi: f64) -> f64 { + return fmin(fmax(v, lo), hi); +} + +/// How many children of at least minw (and gap apart) fit in a row of width w; one at least. +pub fn pack_cols(w: f64, minw: f64, gap: f64) -> u32 { + const c : f64 = (w + gap) / (minw + gap); + if (c < 1.0) { return 1; } + return @as(u32, @intFromFloat(c)); +} + +/// The width of one child when cols fill the row exactly. +pub fn cell_w(w: f64, cols: u32, gap: f64) -> f64 { + return (w - fl(cols - 1) * gap) / fl(cols); +} + +pub fn dash_cell_x(i: u32, w: f64, cols: u32, gap: f64) -> f64 { + return fl(i % cols) * (cell_w(w, cols, gap) + gap); +} + +pub fn dash_cell_y(i: u32, cols: u32, rowh: f64, gap: f64) -> f64 { + return fl(i / cols) * (rowh + gap); +} + +pub fn pack_rows(n: u32, cols: u32) -> u32 { + return (n + cols - 1) / cols; +} + +/// The height n children take in rows of rowh, 0 for none. +pub fn pack_h(n: u32, cols: u32, rowh: f64, gap: f64) -> f64 { + if (n == 0) { return 0.0; } + const r : u32 = pack_rows(n, cols); + return fl(r) * rowh + fl(r - 1) * gap; +} + +/// The child under (x, y) in a packed area (relative to its corner), n when none. Cells are cell_w wide and +/// rowh high; the gaps between them hit nothing. +pub fn pack_at(n: u32, w: f64, cols: u32, rowh: f64, gap: f64, x: f64, y: f64) -> u32 { + if (x < 0.0 or y < 0.0 or x >= w) { return n; } + const cw : f64 = cell_w(w, cols, gap); + const col : u32 = @as(u32, @intFromFloat(x / (cw + gap))); + const row : u32 = @as(u32, @intFromFloat(y / (rowh + gap))); + if (col >= cols) { return n; } + if (x - fl(col) * (cw + gap) > cw) { return n; } + if (y - fl(row) * (rowh + gap) > rowh) { return n; } + const i : u32 = row * cols + col; + if (i >= n) { return n; } + return i; +} + +; --- N2. Constants --------------------------------------------------------------------------------------- + +pub const GAP : f64 = 6.0; +pub const CGAP : f64 = 8.0; +pub const CPAD : f64 = 8.0; +pub const CON_HEAD : f64 = 24.0; +pub const TILE_MIN : f64 = 128.0; +pub const TTL_H : f64 = 14.0; +pub const CARD_W : f64 = 1200.0; +pub const CARD_H : f64 = 630.0; +pub const SEG : f64 = 6.0; +pub const SEG_GAP : f64 = 3.0; +pub const CHIP_H : f64 = 36.0; +pub const CHIP_MIN : f64 = 150.0; +pub const GAUGES : u32 = 4; +pub const GAUGE_H : f64 = 26.0; +pub const GAUGE_AREA : f64 = 216.0; +pub const GAUGE_GAP : f64 = 4.0; +pub const WIDE_HERO : f64 = 560.0; +pub const CM_HEAD : f64 = 26.0; +pub const CM_ROW : f64 = 30.0; +pub const CM_GAP : f64 = 4.0; +pub const CM_NUM_W : f64 = 44.0; +pub const MODULES : u32 = 9; +pub const DASH_LESSONS_PER_MODULE : u32 = 3; +pub const COURSES_HEAD : f64 = 34.0; +pub const END_PAD : f64 = 28.0; +pub const NO_WIDGET : u32 = 195; + +; --- N3. The hero: title, gauges, segment bar ------------------------------------------------------------- + +/// The characters of the big title of letter o: "C COMPILER CORE". +pub fn title_len(o: u32) -> u32 { + if (o < 1 or o > LETTER_COUNT) { return 1; } + return @as(u32, @intCast(AGENT_LETTERS[o - 1].len)) + 2 + @as(u32, @intCast(INSTRUMENT_LABEL[o - 1].len)); +} + +/// The title fills the room left of the gauges (all the width when they sit below), 28 to 64 pt. +pub fn title_size(o: u32, pw: f64) -> f64 { + var avail : f64 = pw; + if (gauge_below(pw) == false) { avail = pw - GAUGE_AREA - 14.0; } + return clampf(avail / (0.64 * fl(title_len(o))), 28.0, 64.0); +} + +pub fn gauge_below(pw: f64) -> bool { return pw < WIDE_HERO; } + +/// The band of the title (and, when the page is wide, the gauges to its right). +pub fn band_h(o: u32, pw: f64) -> f64 { + const t : f64 = title_size(o, pw) + 6.0; + if (gauge_below(pw)) { return t + GAUGE_H + 10.0; } + if (t < 2.0 * GAUGE_H + GAUGE_GAP) { return 2.0 * GAUGE_H + GAUGE_GAP; } + return t; +} + +pub fn band_top() -> f64 { return 24.0; } + +/// Segments of the bar: one per part (widget or app surface) of the letter. +pub fn seg_count(o: u32) -> u32 { return parts_of_letter(o); } + +/// The gallery index of segment s of letter o (WIDGET_COUNT for a surface or past the end). +pub fn seg_widget(o: u32, s: u32) -> u32 { return con_widget(o, s); } + +pub fn seg_cols(n: u32, pw: f64) -> u32 { + const c : u32 = pack_cols(pw, SEG, SEG_GAP); + if (n > 0 and n < c) { return n; } + return c; +} + +pub fn bar_h(n: u32, pw: f64) -> f64 { return pack_h(n, seg_cols(n, pw), SEG, SEG_GAP); } + +pub fn bar_top(o: u32, pw: f64) -> f64 { return band_top() + band_h(o, pw) + 26.0; } + +pub fn hero_h(o: u32, pw: f64) -> f64 { + return bar_top(o, pw) + bar_h(seg_count(o), pw) + 12.0; +} + +/// Gauge g (0..3): AGENTS, WIDGETS, COURSES, CACHED. The 2x2 block at the right of a wide hero, or a row of +/// four under the title in a narrow one. +pub fn gauge_x(g: u32, pw: f64) -> f64 { + if (gauge_below(pw)) { return dash_cell_x(g, pw, GAUGES, GAUGE_GAP); } + return pw - GAUGE_AREA + dash_cell_x(g, GAUGE_AREA, 2, GAUGE_GAP); +} + +pub fn gauge_y(g: u32, o: u32, pw: f64) -> f64 { + if (gauge_below(pw)) { return band_top() + title_size(o, pw) + 16.0; } + return band_top() + 0.5 * (band_h(o, pw) - 2.0 * GAUGE_H - GAUGE_GAP) + dash_cell_y(g, 2, GAUGE_H, GAUGE_GAP); +} + +pub fn gauge_w(pw: f64) -> f64 { + if (gauge_below(pw)) { return cell_w(pw, GAUGES, GAUGE_GAP); } + return cell_w(GAUGE_AREA, 2, GAUGE_GAP); +} + +pub const GAUGE_NAMES : [4]str = ["HOME", "WIDGETS", "SURFACES", "CACHED"]; + +; --- N4. Consoles ---------------------------------------------------------------------------------------- +; ncon[k] is the number of widgets the k-th letter agent of the home owns; the page is a function of it. + +/// Instrument j of the console of letter o: its widgets first (a gallery card), then its app surfaces +/// (agent_parts.t27, no card). con_widget is the gallery index, WIDGET_COUNT for a surface or past the end. +pub fn con_widget(o: u32, j: u32) -> u32 { + if (j >= widgets_of_letter(o)) { return WIDGET_COUNT; } + return nth_widget_of_letter(o, j); +} + +/// The surface index of instrument j (SURFACE_COUNT when it is a widget or past the end). +pub fn con_surface(o: u32, j: u32) -> u32 { + const nw : u32 = widgets_of_letter(o); + if (j < nw) { return SURFACE_COUNT; } + return nth_surface_of_letter(o, j - nw); +} + +/// Fill ncon for the console of letter o: one console, ncon[0] instruments (widgets + surfaces); returns 1. +pub fn plan(o: u32, ncon: []u32) -> u32 { + ncon[0] = parts_of_letter(o); + return 1; +} + +pub fn heavy_count(ncon: []const u32, na: u32) -> u32 { + var n : u32 = 0; + var k : u32 = 0; + while (k < na) { + if (ncon[k] > 0) { n = n + 1; } + k = k + 1; + } + return n; +} + +/// The agent index k of the j-th console that holds instruments (na when none). +pub fn heavy_k(ncon: []const u32, na: u32, j: u32) -> u32 { + var seen : u32 = 0; + var k : u32 = 0; + while (k < na) { + if (ncon[k] > 0) { + if (seen == j) { return k; } + seen = seen + 1; + } + k = k + 1; + } + return na; +} + +pub fn idle_k(ncon: []const u32, na: u32, j: u32) -> u32 { + var seen : u32 = 0; + var k : u32 = 0; + while (k < na) { + if (ncon[k] == 0) { + if (seen == j) { return k; } + seen = seen + 1; + } + k = k + 1; + } + return na; +} + +pub fn inner_w(pw: f64) -> f64 { return pw - 2.0 * CPAD; } +pub fn tile_cols(pw: f64) -> u32 { return pack_cols(inner_w(pw), TILE_MIN, GAP); } +pub fn tile_w(pw: f64) -> f64 { return cell_w(inner_w(pw), tile_cols(pw), GAP); } +pub fn tile_h(pw: f64) -> f64 { return tile_w(pw) * CARD_H / CARD_W; } +/// A cell: the card and the title line under it. +pub fn cell_h(pw: f64) -> f64 { return tile_h(pw) + TTL_H; } + +pub fn con_h(n: u32, pw: f64) -> f64 { + return CON_HEAD + pack_h(n, tile_cols(pw), cell_h(pw), GAP) + CPAD; +} + +/// Top of the j-th console that holds instruments. +pub fn con_top(o: u32, ncon: []const u32, na: u32, pw: f64, j: u32) -> f64 { + var y : f64 = hero_h(o, pw) + CGAP; + var i : u32 = 0; + while (i < j) { + y = y + con_h(ncon[heavy_k(ncon, na, i)], pw) + CGAP; + i = i + 1; + } + return y; +} + +pub fn idle_top(o: u32, ncon: []const u32, na: u32, pw: f64) -> f64 { + return con_top(o, ncon, na, pw, heavy_count(ncon, na)); +} + +pub fn idle_cols(pw: f64) -> u32 { return pack_cols(pw, CHIP_MIN, CGAP); } + +pub fn idle_h(o: u32, ncon: []const u32, na: u32, pw: f64) -> f64 { + return pack_h(na - heavy_count(ncon, na), idle_cols(pw), CHIP_H, CGAP); +} + +; --- N5. Course maps (t27u) ------------------------------------------------------------------------------ + +pub fn cm_h() -> f64 { + return CM_HEAD + fl(MODULES) * CM_ROW + fl(MODULES - 1) * CM_GAP + CPAD; +} + +pub fn courses_top(o: u32, ncon: []const u32, na: u32, pw: f64) -> f64 { + var y : f64 = idle_top(o, ncon, na, pw) + idle_h(o, ncon, na, pw); + if (idle_h(o, ncon, na, pw) > 0.0) { y = y + CGAP; } + return y; +} + +pub fn cm_top(o: u32, ncon: []const u32, na: u32, pw: f64, c: u32) -> f64 { + return courses_top(o, ncon, na, pw) + COURSES_HEAD + fl(c) * (cm_h() + CGAP); +} + +pub fn cm_row_y(m: u32) -> f64 { return CM_HEAD + fl(m) * (CM_ROW + CM_GAP); } +pub fn cm_lessons_x() -> f64 { return CPAD + CM_NUM_W + CGAP; } +pub fn cm_lessons_w(pw: f64) -> f64 { return pw - cm_lessons_x() - CPAD; } + +/// The height of the whole page of home h at page width pw. +pub fn page_h(o: u32, ncon: []const u32, na: u32, pw: f64) -> f64 { + if (o < 1 or o > LETTER_COUNT) { return 0.0; } + var y : f64 = courses_top(o, ncon, na, pw); + if (home_of_letter(o) == H_T27U) { y = cm_top(o, ncon, na, pw, COURSE_COUNT); } + return y + END_PAD; +} + +pub fn dash_scroll_max(total: f64, view: f64) -> u32 { + const over : f64 = total - view; + if (over <= 0.0) { return 0; } + return @as(u32, @intFromFloat(over)) + 1; +} + +pub fn dash_scroll_clamp(off: u32, dy: i64, total: f64, view: f64) -> u32 { + var v : i64 = @as(i64, off) + dy; + if (v < 0) { v = 0; } + const m : i64 = @as(i64, dash_scroll_max(total, view)); + if (v > m) { v = m; } + return @as(u32, @intCast(v)); +} + +; --- N6. Hit test ---------------------------------------------------------------------------------------- + +/// The gallery index of the instrument or lesson under the page point (px, py), NO_WIDGET when none. +pub fn dash_hit(o: u32, ncon: []const u32, na: u32, pw: f64, px: f64, py: f64) -> u32 { + if (o < 1 or o > LETTER_COUNT) { return NO_WIDGET; } + const nh : u32 = heavy_count(ncon, na); + var j : u32 = 0; + while (j < nh) { + const k : u32 = heavy_k(ncon, na, j); + const top : f64 = con_top(o, ncon, na, pw, j); + if (py >= top and py < top + con_h(ncon[k], pw)) { + const t : u32 = pack_at(ncon[k], inner_w(pw), tile_cols(pw), cell_h(pw), GAP, px - CPAD, py - top - CON_HEAD); + if (t < ncon[k]) { return con_widget(o, t); } + return NO_WIDGET; + } + j = j + 1; + } + if (home_of_letter(o) == H_T27U) { + var c : u32 = 0; + while (c < COURSE_COUNT) { + const top : f64 = cm_top(o, ncon, na, pw, c); + if (py >= top and py < top + cm_h()) { + var m : u32 = 0; + while (m < MODULES) { + const ry : f64 = top + cm_row_y(m); + if (py >= ry and py < ry + CM_ROW) { + const l : u32 = pack_at(DASH_LESSONS_PER_MODULE, cm_lessons_w(pw), DASH_LESSONS_PER_MODULE, CM_ROW, GAP, px - cm_lessons_x(), py - ry); + if (l < DASH_LESSONS_PER_MODULE) { return course_widget(c, m * DASH_LESSONS_PER_MODULE + l); } + } + m = m + 1; + } + return NO_WIDGET; + } + c = c + 1; + } + } + return NO_WIDGET; +} + +; --- N6a. The agents pinned to an instrument (a required field of every tile) -------------------------------- +; Owner, 2026-10-20: every instrument carries its AGENTS, shown on screen. dash_agents_of_widget(i, k): k = 0 is the +; owner letter (agent_parts LETTER_OF_WIDGET); k > 0 are the contributing letters. STUB: agent_parts.t27 has no +; contributor list yet, so k > 0 is none. TO SWITCH: when it lands, return its k-th contributor here (0 past the +; end) and nothing else changes: the tile and the live header draw until the first 0. + +pub const MAX_AGENTS : u32 = 4; + +pub fn dash_agents_of_widget(i: u32, k: u32) -> u32 { + if (k == 0) { return letter_of_widget(i); } + return 0; +} + +/// The k-th agent of instrument t of the console of letter o: a widget's, or (a surface) its owner letter. +pub fn agents_of_instrument(o: u32, t: u32, k: u32) -> u32 { + const wi : u32 = con_widget(o, t); + if (wi < WIDGET_COUNT) { return dash_agents_of_widget(wi, k); } + const s : u32 = con_surface(o, t); + if (s < SURFACE_COUNT and k == 0) { return letter_of_surface(s); } + return 0; +} + +pub fn agents_count(o: u32, t: u32) -> u32 { + var k : u32 = 0; + while (k < MAX_AGENTS and agents_of_instrument(o, t, k) != 0) { k = k + 1; } + return k; +} + +; --- N6b. The sidebar of letters grouped by home, and the CHAT / CONSOLE switch ------------------------------------- +; Eight groups (t27a .. t27u, Queen), each a heading and its letters as chips: SIDE_COLS to a row. + +pub const SIDE_W : f64 = 272.0; +pub const SIDE_PAD : f64 = 12.0; +pub const SIDE_TOP : f64 = 30.0; +pub const SIDE_HEAD : f64 = 14.0; +pub const CHIP_W : f64 = 26.0; +pub const CHIP_H2 : f64 = 24.0; +pub const CHIP_GAP : f64 = 2.0; +pub const SIDE_COLS : u32 = 8; + +pub fn side_rows(h: u32) -> u32 { + const n : u32 = agents_in(h); + if (n == 0) { return 0; } + return (n + SIDE_COLS - 1) / SIDE_COLS; +} + +/// Top (from the window top) of the heading of group h; ws_top is the top of the workspace. +pub fn side_group_top(h: u32, ws_top: f64) -> f64 { + var y : f64 = ws_top + SIDE_TOP; + var g : u32 = 0; + while (g < h) { + y = y + SIDE_HEAD + fl(side_rows(g)) * (CHIP_H2 + CHIP_GAP) + 4.0; + g = g + 1; + } + return y; +} + +pub fn side_chip_x(k: u32) -> f64 { return SIDE_PAD + fl(k % SIDE_COLS) * (CHIP_W + CHIP_GAP); } +pub fn side_chip_y(h: u32, k: u32, ws_top: f64) -> f64 { + return side_group_top(h, ws_top) + SIDE_HEAD + fl(k / SIDE_COLS) * (CHIP_H2 + CHIP_GAP); +} + +/// The letter (ORDINAL 1..27) whose chip is under (x, y), 0 when none. +pub fn side_letter_at(x: f64, y: f64, ws_top: f64) -> u32 { + var h : u32 = 0; + while (h < HOME_COUNT) { + const n : u32 = agents_in(h); + var k : u32 = 0; + while (k < n) { + const cx : f64 = side_chip_x(k); + const cy : f64 = side_chip_y(h, k, ws_top); + if (x >= cx and x < cx + CHIP_W and y >= cy and y < cy + CHIP_H2) { return nth_agent(h, k); } + k = k + 1; + } + h = h + 1; + } + return 0; +} + +pub fn side_end(ws_top: f64) -> f64 { return side_group_top(HOME_COUNT, ws_top); } + +/// The CHAT / CONSOLE switch of the C petal: 84 x 22 at the right end of the workspace header. +pub fn on_switch(x: f64, y: f64, sw: f64, ws_top: f64) -> bool { + return x >= sw - 108.0 and x <= sw - 24.0 and y >= ws_top + 11.0 and y <= ws_top + 33.0; +} + +; --- N7. The live page of one widget ---------------------------------------------------------------------- + +pub const LIVE_HEAD : f64 = 112.0; +pub const LIVE_NONE : u64 = 0; + +/// The page area under the lesson-style header, in scene points: x, y (from the top), w, o. +pub fn live_x(cx: f64) -> f64 { return cx + 32.0; } +pub fn live_y(vt: f64) -> f64 { return vt + LIVE_HEAD; } +pub fn live_w(sw: f64, cx: f64) -> f64 { return sw - live_x(cx) - 32.0; } +pub fn live_h(sh: f64, vt: f64) -> f64 { return sh - live_y(vt) - 16.0; } + +/// The back crumb in the header: "< BACK", 76 x 20 at the header's top left. +pub fn on_live_back(mx: f64, my: f64, cx: f64, vt: f64) -> bool { + return mx >= cx + 32.0 and mx <= cx + 32.0 + 76.0 and my >= vt + 10.0 and my <= vt + 30.0; +} + +/// Scene (y down) to the AppKit view of a window that fits a scene of sw x sh at scale s into vw x vh. +pub fn view_x(sx: f64, vw: f64, sw: f64, s: f64) -> f64 { return 0.5 * (vw - sw * s) + sx * s; } +pub fn view_y(sy: f64, rh: f64, vh: f64, sh: f64, s: f64) -> f64 { return 0.5 * (vh - sh * s) + (sh - (sy + rh)) * s; } + +; --- N8. The gallery text, its parse and the strings built from it --------------------------------------- +; The gallery file is read into a byte buffer. gal_parse writes, for each of the arrays WIDGET_IDS, WIDGET_KINDS +; and WIDGET_PAGES, the start and length of every quoted string: tab[(a * cap + i) * 2] and +1. + +pub const GAL_IDS : u32 = 0; +pub const GAL_KINDS : u32 = 1; +pub const GAL_PAGES : u32 = 2; +pub const GAL_MAX : usize = 49152; +pub const GAL_CAP : u32 = 200; +pub const GAL_TAB : usize = 1200; + +/// The index of the first occurrence of pat in buf[from..n], n when there is none. +pub fn gal_find(buf: []const u8, n: usize, from: usize, pat: []const u8) -> usize { + var i : usize = from; + while (i + pat.len <= n) { + var k : usize = 0; + while (k < pat.len and buf[i + k] == pat[k]) { k = k + 1; } + if (k == pat.len) { return i; } + i = i + 1; + } + return n; +} + +/// Parse one array whose declaration starts at `at`: the strings after the first `= [` up to the closing `]`. +/// Returns how many strings were recorded into tab at array slot a. +fn gal_array(buf: []const u8, n: usize, at: usize, a: u32, tab: []u32) -> u32 { + var i : usize = at; + while (i < n and buf[i] != 61) { i = i + 1; } + while (i < n and buf[i] != 91) { i = i + 1; } + i = i + 1; + var cnt : u32 = 0; + while (i < n and buf[i] != 93 and cnt < GAL_CAP) { + if (buf[i] == 34) { + const st : usize = i + 1; + var e : usize = st; + while (e < n and buf[e] != 34) { e = e + 1; } + tab[(a * GAL_CAP + cnt) * 2] = @as(u32, @intCast(st)); + tab[(a * GAL_CAP + cnt) * 2 + 1] = @as(u32, @intCast(e - st)); + cnt = cnt + 1; + i = e; + } + i = i + 1; + } + return cnt; +} + +/// Parse the gallery text; returns the number of widgets all three arrays agree on, 0 when one is missing. +pub fn gal_parse(buf: []const u8, n: usize, tab: []u32) -> u32 { + const p0 : usize = gal_find(buf, n, 0, "pub const WIDGET_IDS"); + const p1 : usize = gal_find(buf, n, 0, "pub const WIDGET_KINDS"); + const p2 : usize = gal_find(buf, n, 0, "pub const WIDGET_PAGES"); + if (p0 >= n or p1 >= n or p2 >= n) { return 0; } + const a : u32 = gal_array(buf, n, p0, GAL_IDS, tab); + const b : u32 = gal_array(buf, n, p1, GAL_KINDS, tab); + const c : u32 = gal_array(buf, n, p2, GAL_PAGES, tab); + if (a != b or a != c) { return 0; } + return a; +} + +pub fn gal_start(tab: []const u32, a: u32, i: u32) -> usize { return @as(usize, tab[(a * GAL_CAP + i) * 2]); } +pub fn gal_len(tab: []const u32, a: u32, i: u32) -> usize { return @as(usize, tab[(a * GAL_CAP + i) * 2 + 1]); } + +pub const SITE : str = "https://t27.ai/"; + +/// The page of widget i as a URL: the page the site itself embeds. +pub fn put_page_url(buf: []const u8, tab: []const u32, i: u32, out: []u8) -> usize { + var n : usize = put_bytes(out, 0, SITE); + const s : usize = gal_start(tab, GAL_PAGES, i); + n = put_bytes(out, n, buf[s .. s + gal_len(tab, GAL_PAGES, i)]); + out[n] = 0; + return n; +} + +/// The URL of widget i's card.png, 0 bytes when its page is not a directory (a Queen tab has no card). +pub fn put_card_url(buf: []const u8, tab: []const u32, i: u32, out: []u8) -> usize { + const s : usize = gal_start(tab, GAL_PAGES, i); + const l : usize = gal_len(tab, GAL_PAGES, i); + if (l == 0 or buf[s + l - 1] != 47) { return 0; } + var n : usize = put_bytes(out, 0, SITE); + n = put_bytes(out, n, buf[s .. s + l]); + n = put_bytes(out, n, "card.png"); + out[n] = 0; + return n; +} + +/// ~/.t27os/cards/.png, NUL-terminated (the NUL is not counted). +pub fn put_card_path(home: []const u8, buf: []const u8, tab: []const u32, i: u32, out: []u8) -> usize { + var n : usize = put_bytes(out, 0, home); + n = put_bytes(out, n, "/.t27os/cards/"); + const s : usize = gal_start(tab, GAL_IDS, i); + n = put_bytes(out, n, buf[s .. s + gal_len(tab, GAL_IDS, i)]); + n = put_bytes(out, n, ".png"); + out[n] = 0; + return n; +} + +pub fn put_gallery_path(home: []const u8, out: []u8) -> usize { + var n : usize = put_bytes(out, 0, home); + n = put_bytes(out, n, "/.t27os/gallery.t27"); + out[n] = 0; + return n; +} + +pub fn put_gallery_url(out: []u8) -> usize { + var n : usize = put_bytes(out, 0, SITE); + n = put_bytes(out, n, "widgets/gallery.t27"); + out[n] = 0; + return n; +} + +/// path + ".part", NUL-terminated: the file curl writes before the rename. +pub fn put_part(path: []const u8, n: usize, out: []u8) -> usize { + var m : usize = put_bytes(out, 0, path[0 .. n]); + m = put_bytes(out, m, ".part"); + out[m] = 0; + return m; +} + +/// The argv of the fetch packed into buf (NUL-terminated words); offs[0..6] get the start of each word: +/// curl -sfL --max-time 20 -o . Returns the bytes used. +pub fn put_argv(tmp: []const u8, tn: usize, url: []const u8, un: usize, buf: []u8, offs: []u32) -> usize { + var n : usize = 0; + offs[0] = 0; + n = put_bytes(buf, n, "curl"); + buf[n] = 0; + n = n + 1; + offs[1] = @as(u32, @intCast(n)); + n = put_bytes(buf, n, "-sfL"); + buf[n] = 0; + n = n + 1; + offs[2] = @as(u32, @intCast(n)); + n = put_bytes(buf, n, "--max-time"); + buf[n] = 0; + n = n + 1; + offs[3] = @as(u32, @intCast(n)); + n = put_bytes(buf, n, "20"); + buf[n] = 0; + n = n + 1; + offs[4] = @as(u32, @intCast(n)); + n = put_bytes(buf, n, "-o"); + buf[n] = 0; + n = n + 1; + offs[5] = @as(u32, @intCast(n)); + n = put_bytes(buf, n, tmp[0 .. tn]); + buf[n] = 0; + n = n + 1; + offs[6] = @as(u32, @intCast(n)); + n = put_bytes(buf, n, url[0 .. un]); + buf[n] = 0; + return n + 1; +} + +; --- N9. Widget states and the card cache ----------------------------------------------------------------- + +pub const WS_NEW : u64 = 0; +pub const WS_FETCH : u64 = 1; +pub const WS_LOADED : u64 = 2; +pub const WS_FAILED : u64 = 3; +pub const WS_WANT : u64 = 4; +pub const CACHE_SLOTS : u32 = 128; +pub const FETCH_SLOTS : u32 = 6; + +/// The slot holding widget wi (keys hold wi + 1; 0 is empty), n when none. +pub fn cache_find(keys: []const u64, n: u32, wi: u32) -> u32 { + var s : u32 = 0; + while (s < n) { + if (keys[s] == @as(u64, wi) + 1) { return s; } + s = s + 1; + } + return n; +} + +pub fn cache_free(keys: []const u64, n: u32) -> u32 { + var s : u32 = 0; + while (s < n) { + if (keys[s] == 0) { return s; } + s = s + 1; + } + return n; +} + +/// The slot to evict when none is free: round robin. +pub fn cache_victim(next: u64, n: u32) -> u32 { return @as(u32, @intCast(next % @as(u64, n))); } + +; --- N10. Text of the page ------------------------------------------------------------------------------- + +/// The readout of a console: "12 INSTRUMENTS" (or "1 INSTRUMENT", "IDLE"). +pub fn put_readout(n: u32, out: []u8) -> usize { + if (n == 0) { return put_bytes(out, 0, "IDLE"); } + const c : usize = put_dec(out, 0, @as(u64, n)); + if (n == 1) { return put_bytes(out, c, " INSTRUMENT"); } + return put_bytes(out, c, " INSTRUMENTS"); +} + +/// "04/12": a count of two numbers for a gauge. +pub fn put_ratio(a: u32, b: u32, out: []u8) -> usize { + var n : usize = put_dec(out, 0, @as(u64, a)); + n = put_bytes(out, n, "/"); + return put_dec(out, n, @as(u64, b)); +} + +/// "01".."09": module numbers of a course map. +pub fn put_two(v: u32, out: []u8) -> usize { + var n : usize = 0; + if (v < 10) { n = put_bytes(out, 0, "0"); } + return put_dec(out, n, @as(u64, v)); +} + +; --- Tests ----------------------------------------------------------------------------------------------- + +test the_packer_places_children_edge_to_edge { + assert pack_cols(672.0, 128.0, 6.0) == 5; + assert pack_cols(10.0, 128.0, 6.0) == 1; + const w : f64 = cell_w(672.0, 5, 6.0); + assert w > 128.0; + assert dash_cell_x(0, 672.0, 5, 6.0) == 0.0; + assert dash_cell_x(4, 672.0, 5, 6.0) + w <= 672.0 + 0.001; + assert dash_cell_x(4, 672.0, 5, 6.0) + w >= 672.0 - 0.001; + assert dash_cell_y(5, 5, 82.0, 6.0) == 88.0; + assert pack_rows(11, 5) == 3; + assert pack_h(0, 5, 82.0, 6.0) == 0.0; + assert pack_h(11, 5, 82.0, 6.0) == 3.0 * 82.0 + 2.0 * 6.0; +} + +test pack_at_hits_cells_and_not_gaps { + assert pack_at(11, 672.0, 5, 82.0, 6.0, 1.0, 1.0) == 0; + assert pack_at(11, 672.0, 5, 82.0, 6.0, 140.0, 1.0) == 1; + assert pack_at(11, 672.0, 5, 82.0, 6.0, 135.0, 1.0) == 11; + assert pack_at(11, 672.0, 5, 82.0, 6.0, 1.0, 90.0) == 5; + assert pack_at(11, 672.0, 5, 82.0, 6.0, 1.0, 85.0) == 11; + assert pack_at(11, 672.0, 5, 82.0, 6.0, 140.0, 2.0 * 88.0 + 1.0) == 11; + assert pack_at(11, 672.0, 5, 82.0, 6.0, 0.0 - 1.0, 1.0) == 11; +} + +test the_tree_nests_widgets_in_widgets { + assert child_kind(NK_SCREEN, 0) == NK_HOME; + assert child_kind(NK_HOME, 1) == NK_CONSOLE; + assert child_kind(NK_CONSOLE, 0) == NK_INSTRUMENT; + assert child_kind(NK_HERO, 0) == NK_GAUGE; + assert child_kind(NK_COURSE_MAP, 0) == NK_LESSON; + assert child_kind(NK_INSTRUMENT, 0) == NK_KINDS; +} + +test the_hero_is_a_compact_strip { + assert title_size(3, 688.0) >= 28.0 and title_size(3, 688.0) <= 64.0; + assert title_len(3) == 1 + 2 + 13; + assert title_size(3, 688.0) * 0.64 * fl(title_len(3)) <= 688.0 - GAUGE_AREA - 14.0 + 0.01 or title_size(3, 688.0) == 28.0; + assert gauge_below(400.0); + assert gauge_below(688.0) == false; + assert gauge_x(1, 688.0) + gauge_w(688.0) <= 688.0 + 0.001; + assert gauge_x(1, 688.0) + gauge_w(688.0) >= 688.0 - 0.001; + assert gauge_y(2, 3, 688.0) > gauge_y(0, 3, 688.0); + assert hero_h(3, 688.0) < 200.0; + assert hero_h(3, 688.0) >= bar_top(3, 688.0); + assert seg_count(11) >= 64; + assert seg_count(11) == parts_of_letter(11); + assert seg_widget(11, 0) == 0; + assert seg_widget(11, 64) == WIDGET_COUNT or seg_widget(11, 64) < WIDGET_COUNT; + assert seg_widget(11, parts_of_letter(11)) == WIDGET_COUNT; + assert con_surface(11, 0) == SURFACE_COUNT; + assert letter_of_widget(seg_widget(26, 0)) == 26; +} + +test a_console_holds_every_widget_of_its_letter { + var ncon : [32]u32 = undefined; + var o : u32 = 1; + var total : u32 = 0; + while (o <= LETTER_COUNT) { + const na : u32 = plan(o, ncon[0..32]); + assert na == 1; + assert ncon[0] == parts_of_letter(o); + total = total + ncon[0]; + assert heavy_count(ncon[0..32], na) + (na - heavy_count(ncon[0..32], na)) == na; + o = o + 1; + } + assert total == WIDGET_COUNT + SURFACE_COUNT; +} + +test the_page_height_is_a_sum_of_its_widgets { + var ncon : [32]u32 = undefined; + const na : u32 = plan(3, ncon[0..32]); + const pw : f64 = 688.0; + assert heavy_count(ncon[0..32], na) == 1; + assert con_top(3, ncon[0..32], na, pw, 0) == hero_h(3, pw) + CGAP; + assert idle_top(3, ncon[0..32], na, pw) == con_top(3, ncon[0..32], na, pw, 0) + con_h(ncon[0], pw) + CGAP; + assert page_h(3, ncon[0..32], na, pw) >= idle_top(3, ncon[0..32], na, pw) + END_PAD; + assert tile_cols(pw) == 5; + assert tile_h(pw) / tile_w(pw) < 0.526 and tile_h(pw) / tile_w(pw) > 0.524; + assert page_h(0, ncon[0..32], na, pw) == 0.0; + var e : u32 = 1; + while (e <= LETTER_COUNT and parts_of_letter(e) > 0) { e = e + 1; } + if (e <= LETTER_COUNT) { + const na2 : u32 = plan(e, ncon[0..32]); + assert heavy_count(ncon[0..32], na2) == 0; + assert idle_h(e, ncon[0..32], na2, pw) == CHIP_H; + } +} + +test a_wider_page_packs_more_instruments_per_row { + assert tile_cols(1200.0) > tile_cols(688.0); + assert tile_cols(688.0) >= 4; + assert tile_cols(200.0) == 1; +} + +test every_instrument_is_hit_at_its_centre { + var ncon : [32]u32 = undefined; + const pw : f64 = 688.0; + var o : u32 = 1; + while (o <= LETTER_COUNT) { + const na : u32 = plan(o, ncon[0..32]); + if (heavy_count(ncon[0..32], na) == 1) { + const top : f64 = con_top(o, ncon[0..32], na, pw, 0); + var t : u32 = 0; + while (t < ncon[0]) { + const x : f64 = CPAD + dash_cell_x(t, inner_w(pw), tile_cols(pw), GAP) + 0.5 * tile_w(pw); + const y : f64 = top + CON_HEAD + dash_cell_y(t, tile_cols(pw), cell_h(pw), GAP) + 0.5 * tile_h(pw); + assert dash_hit(o, ncon[0..32], na, pw, x, y) == con_widget(o, t); + t = t + 1; + } + } + o = o + 1; + } + assert dash_hit(11, ncon[0..32], plan(11, ncon[0..32]), pw, 5.0, 5.0) == NO_WIDGET; + assert dash_hit(0, ncon[0..32], 0, pw, 5.0, 5.0) == NO_WIDGET; +} + +test a_lesson_cell_opens_its_widget { + var ncon : [32]u32 = undefined; + const na : u32 = plan(26, ncon[0..32]); + const pw : f64 = 688.0; + const x : f64 = cm_lessons_x() + 4.0; + const y : f64 = cm_top(26, ncon[0..32], na, pw, 1) + cm_row_y(2) + 4.0; + assert dash_hit(26, ncon[0..32], na, pw, x, y) == course_widget(1, 6); + const y0 : f64 = cm_top(26, ncon[0..32], na, pw, 0) + cm_row_y(0) + 4.0; + assert dash_hit(26, ncon[0..32], na, pw, x, y0) == course_widget(0, 0); + assert cm_top(26, ncon[0..32], na, pw, 7) > cm_top(26, ncon[0..32], na, pw, 6); + assert page_h(26, ncon[0..32], na, pw) == cm_top(26, ncon[0..32], na, pw, 7) + END_PAD; + assert cm_h() == 26.0 + 9.0 * 30.0 + 8.0 * 4.0 + 8.0; + const nb : u32 = plan(3, ncon[0..32]); + assert dash_hit(3, ncon[0..32], nb, pw, x, y) == NO_WIDGET or dash_hit(3, ncon[0..32], nb, pw, x, y) < WIDGET_COUNT; + assert page_h(3, ncon[0..32], nb, pw) < page_h(26, ncon[0..32], na, pw) + 4000.0; +} + +test every_letter_has_one_chip_in_its_home_group { + var o : u32 = 1; + var seen : u32 = 0; + while (o <= LETTER_COUNT) { + var h : u32 = 0; + while (h < HOME_COUNT) { + var k : u32 = 0; + while (k < agents_in(h)) { + if (nth_agent(h, k) == o) { + seen = seen + 1; + const x : f64 = side_chip_x(k) + 4.0; + const y : f64 = side_chip_y(h, k, 35.0) + 4.0; + assert side_letter_at(x, y, 35.0) == o; + assert home_of_letter(o) == h; + } + k = k + 1; + } + h = h + 1; + } + o = o + 1; + } + assert seen == LETTER_COUNT; + assert side_letter_at(2.0, 2.0, 35.0) == 0; + assert side_letter_at(side_chip_x(0) + CHIP_W + 1.0, side_chip_y(0, 0, 35.0) + 4.0, 35.0) == 0; + assert side_end(35.0) > side_group_top(7, 35.0); + assert side_end(35.0) < 450.0; + assert side_chip_x(SIDE_COLS - 1) + CHIP_W + SIDE_PAD <= SIDE_W; +} + +test no_instrument_is_laid_out_without_an_agent { + var o : u32 = 1; + while (o <= LETTER_COUNT) { + var t : u32 = 0; + while (t < parts_of_letter(o)) { + assert agents_of_instrument(o, t, 0) != 0; + assert agents_count(o, t) >= 1; + t = t + 1; + } + o = o + 1; + } + var i : u32 = 0; + while (i < WIDGET_COUNT) { + assert dash_agents_of_widget(i, 0) != 0; + i = i + 1; + } + assert dash_agents_of_widget(0, 0) == letter_of_widget(0); + assert dash_agents_of_widget(0, 1) == 0; + assert dash_agents_of_widget(WIDGET_COUNT, 0) == 0; +} + +test the_chat_console_switch_sits_in_the_header { + assert on_switch(900.0, 60.0, 960.0, 44.0); + assert on_switch(900.0, 100.0, 960.0, 44.0) == false; + assert on_switch(800.0, 60.0, 960.0, 44.0) == false; +} + +test scrolling_stays_inside_the_page { + assert dash_scroll_max(400.0, 500.0) == 0; + assert dash_scroll_max(1000.0, 500.0) == 501; + assert dash_scroll_clamp(0, 0 - 5, 1000.0, 500.0) == 0; + assert dash_scroll_clamp(500, 90, 1000.0, 500.0) == 501; + assert dash_scroll_clamp(10, 20, 1000.0, 500.0) == 30; +} + +test the_live_page_sits_under_its_header { + assert live_y(45.0) == 45.0 + LIVE_HEAD; + assert live_w(960.0, 272.0) == 960.0 - 304.0 - 32.0; + assert live_h(540.0, 45.0) == 540.0 - 45.0 - LIVE_HEAD - 16.0; + assert on_live_back(310.0, 60.0, 272.0, 45.0); + assert on_live_back(310.0, 100.0, 272.0, 45.0) == false; + assert view_x(0.0, 960.0, 960.0, 1.0) == 0.0; + assert view_y(0.0, 540.0, 540.0, 540.0, 1.0) == 0.0; + assert view_y(45.0, 100.0, 540.0, 540.0, 1.0) == 395.0; + assert view_x(100.0, 1000.0, 960.0, 1.0) == 120.0; +} + +fn put_q(out: []u8, at: usize, s: []const u8) -> usize { + out[at] = 34; + const n : usize = put_bytes(out, at + 1, s); + out[n] = 34; + return n + 1; +} + +fn put_decl(out: []u8, at: usize, name: []const u8, a: []const u8, b: []const u8, c: []const u8) -> usize { + var n : usize = put_bytes(out, at, "pub const "); + n = put_bytes(out, n, name); + n = put_bytes(out, n, " : [3]str = ["); + n = put_q(out, n, a); + n = put_bytes(out, n, ", "); + n = put_q(out, n, b); + n = put_bytes(out, n, ", "); + n = put_q(out, n, c); + return put_bytes(out, n, "];\n"); +} + +test the_gallery_text_is_parsed_not_copied { + var buf : [1024]u8 = undefined; + var tab : [1200]u32 = undefined; + var n : usize = put_bytes(buf[0..1024], 0, "; a comment line about WIDGET_IDS\n"); + n = put_decl(buf[0..1024], n, "WIDGET_IDS", "x7-board", "bit-diff", "wars"); + n = put_decl(buf[0..1024], n, "WIDGET_KINDS", "cast", "tool", "tab"); + n = put_decl(buf[0..1024], n, "WIDGET_PAGES", "term/x7-board/", "widgets/bit-diff/", "wars"); + assert gal_parse(buf[0..1024], n, tab[0..1200]) == 3; + assert gal_len(tab[0..1200], GAL_IDS, 1) == 8; + const s : usize = gal_start(tab[0..1200], GAL_IDS, 1); + assert buf[s] == 98; + assert buf[s + 7] == 102; + assert gal_len(tab[0..1200], GAL_KINDS, 2) == 3; + assert gal_len(tab[0..1200], GAL_PAGES, 0) == 14; + var u : [256]u8 = undefined; + const un : usize = put_page_url(buf[0..1024], tab[0..1200], 1, u[0..256]); + assert un == 15 + 17; + assert u[un] == 0; + assert u[8] == 116; + const cn : usize = put_card_url(buf[0..1024], tab[0..1200], 0, u[0..256]); + assert cn == 15 + 14 + 8; + assert u[cn - 1] == 103; + assert put_card_url(buf[0..1024], tab[0..1200], 2, u[0..256]) == 0; + const pn : usize = put_card_path("/Users/me", buf[0..1024], tab[0..1200], 1, u[0..256]); + assert pn == 9 + 14 + 8 + 4; + assert u[pn] == 0; + assert gal_parse(buf[0..1024], 10, tab[0..1200]) == 0; +} + +test a_gallery_with_one_array_missing_parses_to_zero { + var buf : [512]u8 = undefined; + var tab : [1200]u32 = undefined; + var n : usize = put_decl(buf[0..512], 0, "WIDGET_IDS", "a", "b", "c"); + n = put_decl(buf[0..512], n, "WIDGET_KINDS", "cast", "tool", "tab"); + assert gal_parse(buf[0..512], n, tab[0..1200]) == 0; +} + +test the_fetch_command_is_packed_with_its_offsets { + var b : [256]u8 = undefined; + var o : [8]u32 = undefined; + const n : usize = put_argv("/t/a.png.part", 13, "https://t27.ai/x/", 17, b[0..256], o[0..8]); + assert o[0] == 0; + assert o[1] == 5; + assert b[o[1]] == 45; + assert b[o[4]] == 45; + assert b[o[5]] == 47; + assert b[o[6]] == 104; + assert n == o[6] + 17 + 1; + assert b[o[6] - 1] == 0; + var p : [64]u8 = undefined; + const pn : usize = put_part("/t/a.png", 8, p[0..64]); + assert pn == 13; + assert p[13] == 0; + assert put_gallery_path("/h", p[0..64]) == 2 + 19; + assert put_gallery_url(p[0..64]) == 15 + 19; +} + +test the_card_cache_finds_frees_and_evicts { + var keys : [4]u64 = undefined; + keys[0] = 6; + keys[1] = 0; + keys[2] = 9; + keys[3] = 0; + assert cache_find(keys[0..4], 4, 5) == 0; + assert cache_find(keys[0..4], 4, 8) == 2; + assert cache_find(keys[0..4], 4, 7) == 4; + assert cache_free(keys[0..4], 4) == 1; + keys[1] = 1; + keys[3] = 2; + assert cache_free(keys[0..4], 4) == 4; + assert cache_victim(5, 4) == 1; + assert cache_victim(128, 128) == 0; +} + +test the_readouts_are_plain_mono_text { + var b : [32]u8 = undefined; + assert put_readout(0, b[0..32]) == 4; + assert put_readout(1, b[0..32]) == 12; + assert put_readout(12, b[0..32]) == 14; + assert put_ratio(4, 12, b[0..32]) == 4; + assert put_two(3, b[0..32]) == 2; + assert b[0] == 48; + assert put_two(12, b[0..32]) == 2; +} diff --git a/specs/t27os/domain_pulse.t27 b/specs/t27os/domain_pulse.t27 new file mode 100644 index 0000000000..b0be6822c8 --- /dev/null +++ b/specs/t27os/domain_pulse.t27 @@ -0,0 +1,784 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/t27os/domain_pulse.t27 -- end-to-end analytics by agent domain for T27 OS: what is happening in the project. +; Owner, 2026-10-20: T27 OS is a spaceship instrument panel; one glance shows which of the 27 agent domains moves, +; which is quiet, which is blocked. This file is the data model only (pure, tested); the window draws it later. +; -- +; HOW PATHS MAP (DRY): by `use`, not by a second table. Every agent module specs/agents/.t27 already owns +; KEY_FILES and DOMAIN; this file reads them (use agents::a; ... a::KEY_FILES). A pattern is an exact file +; ("SOUL.md"), a directory prefix (".github/workflows/") or a glob with one '*' ("architecture/ADR-*.md"). A leading +; "t27/" of a pattern is dropped (the alphabet predates the repo root being t27). The LONGEST matching pattern wins, +; ties go to the lowest letter. Letter code: 1 = A .. 26 = Z, 27 = Ti, 0 = UNOWNED (counted, never dropped). +; -- +; LABELS (reverse-engineering discipline: every claim is CONFIRMED or GUESS): +; CONFIRMED read from git output (commit time, subject, file list) or matched by an EXACT KEY_FILES path. +; GUESS inferred: a path owned only because a directory prefix or glob of KEY_FILES covers it. +; OPEN per letter, the files touched whose owner is a GUESS or nobody: the open questions of the domain. +; -- +; WHAT THE HOST MUST RUN AND WRITE (this file runs no command): +; log git log --since=7.days --name-only --format=%x1e%H%x1f%ct%x1f%s +; record separator 0x1e, field separator 0x1f; after the subject a newline, a blank line, then one path per line. +; now unix seconds of the host clock +; red one path per line: the paths of the failing CI checks (empty text = nothing red) +; prs one line per (PR, file): "pr \n"; the host writes it from `gh pr list --state open +; --json number,files` (the JSON is plumbing, this file does not parse it) +; Commits in the log that touch no owned path count under letter 0. +; ASCII only (L3), English only (LANG-EN). +; phi^2 + 1/phi^2 = 3 | TRINITY + +module T27osDomainPulse; + +use agents::A; +use agents::B; +use agents::C; +use agents::D; +use agents::E; +use agents::F; +use agents::G; +use agents::H; +use agents::I; +use agents::J; +use agents::K; +use agents::L; +use agents::M; +use agents::N; +use agents::O; +use agents::P; +use agents::Q; +use agents::R; +use agents::S; +use agents::T; +use agents::U; +use agents::V; +use agents::W; +use agents::X; +use agents::Y; +use agents::Z; +use agents::ti; + +pub const AGENT_COUNT : u32 = 27; +pub const UNOWNED : u32 = 0; +pub const DAY : u64 = 86400; +pub const WEEK : u64 = 604800; +pub const BUCKETS : u32 = 7; + +pub const RS : u32 = 30; +pub const US : u32 = 31; +pub const NL : u32 = 10; +pub const NONE : u32 = 4294967295; + +; kind of a path match +pub const K_NONE : u32 = 0; +pub const K_EXACT : u32 = 1; +pub const K_PREFIX : u32 = 2; + +; labels +pub const LABEL_CONFIRMED : u32 = 0; +pub const LABEL_GUESS : u32 = 1; + +; lamp +pub const ST_QUIET : u32 = 1; +pub const ST_WARM : u32 = 2; +pub const ST_ACTIVE : u32 = 3; +pub const ST_BLOCKED : u32 = 4; + +; what scan() computes +pub const W_COMMITS : u32 = 0; +pub const W_FILES : u32 = 1; +pub const W_LAST : u32 = 2; +pub const W_BUCKET : u32 = 3; +pub const W_LAST_REC : u32 = 4; +pub const W_OPEN : u32 = 5; + +// ------------------------------------------------------------ the agents, read by use + +pub fn key_n(ii: u32) -> u32 { + if (ii == 0) { return A::KEY_FILES.len as u32; } + if (ii == 1) { return B::KEY_FILES.len as u32; } + if (ii == 2) { return C::KEY_FILES.len as u32; } + if (ii == 3) { return D::KEY_FILES.len as u32; } + if (ii == 4) { return E::KEY_FILES.len as u32; } + if (ii == 5) { return F::KEY_FILES.len as u32; } + if (ii == 6) { return G::KEY_FILES.len as u32; } + if (ii == 7) { return H::KEY_FILES.len as u32; } + if (ii == 8) { return I::KEY_FILES.len as u32; } + if (ii == 9) { return J::KEY_FILES.len as u32; } + if (ii == 10) { return K::KEY_FILES.len as u32; } + if (ii == 11) { return L::KEY_FILES.len as u32; } + if (ii == 12) { return M::KEY_FILES.len as u32; } + if (ii == 13) { return N::KEY_FILES.len as u32; } + if (ii == 14) { return O::KEY_FILES.len as u32; } + if (ii == 15) { return P::KEY_FILES.len as u32; } + if (ii == 16) { return Q::KEY_FILES.len as u32; } + if (ii == 17) { return R::KEY_FILES.len as u32; } + if (ii == 18) { return S::KEY_FILES.len as u32; } + if (ii == 19) { return T::KEY_FILES.len as u32; } + if (ii == 20) { return U::KEY_FILES.len as u32; } + if (ii == 21) { return V::KEY_FILES.len as u32; } + if (ii == 22) { return W::KEY_FILES.len as u32; } + if (ii == 23) { return X::KEY_FILES.len as u32; } + if (ii == 24) { return Y::KEY_FILES.len as u32; } + if (ii == 25) { return Z::KEY_FILES.len as u32; } + if (ii == 26) { return ti::KEY_FILES.len as u32; } + return 0; +} + +pub fn key_at(ii: u32, jj: u32) -> string { + if (ii == 0) { if (jj < A::KEY_FILES.len as u32) { return A::KEY_FILES[jj]; } } + if (ii == 1) { if (jj < B::KEY_FILES.len as u32) { return B::KEY_FILES[jj]; } } + if (ii == 2) { if (jj < C::KEY_FILES.len as u32) { return C::KEY_FILES[jj]; } } + if (ii == 3) { if (jj < D::KEY_FILES.len as u32) { return D::KEY_FILES[jj]; } } + if (ii == 4) { if (jj < E::KEY_FILES.len as u32) { return E::KEY_FILES[jj]; } } + if (ii == 5) { if (jj < F::KEY_FILES.len as u32) { return F::KEY_FILES[jj]; } } + if (ii == 6) { if (jj < G::KEY_FILES.len as u32) { return G::KEY_FILES[jj]; } } + if (ii == 7) { if (jj < H::KEY_FILES.len as u32) { return H::KEY_FILES[jj]; } } + if (ii == 8) { if (jj < I::KEY_FILES.len as u32) { return I::KEY_FILES[jj]; } } + if (ii == 9) { if (jj < J::KEY_FILES.len as u32) { return J::KEY_FILES[jj]; } } + if (ii == 10) { if (jj < K::KEY_FILES.len as u32) { return K::KEY_FILES[jj]; } } + if (ii == 11) { if (jj < L::KEY_FILES.len as u32) { return L::KEY_FILES[jj]; } } + if (ii == 12) { if (jj < M::KEY_FILES.len as u32) { return M::KEY_FILES[jj]; } } + if (ii == 13) { if (jj < N::KEY_FILES.len as u32) { return N::KEY_FILES[jj]; } } + if (ii == 14) { if (jj < O::KEY_FILES.len as u32) { return O::KEY_FILES[jj]; } } + if (ii == 15) { if (jj < P::KEY_FILES.len as u32) { return P::KEY_FILES[jj]; } } + if (ii == 16) { if (jj < Q::KEY_FILES.len as u32) { return Q::KEY_FILES[jj]; } } + if (ii == 17) { if (jj < R::KEY_FILES.len as u32) { return R::KEY_FILES[jj]; } } + if (ii == 18) { if (jj < S::KEY_FILES.len as u32) { return S::KEY_FILES[jj]; } } + if (ii == 19) { if (jj < T::KEY_FILES.len as u32) { return T::KEY_FILES[jj]; } } + if (ii == 20) { if (jj < U::KEY_FILES.len as u32) { return U::KEY_FILES[jj]; } } + if (ii == 21) { if (jj < V::KEY_FILES.len as u32) { return V::KEY_FILES[jj]; } } + if (ii == 22) { if (jj < W::KEY_FILES.len as u32) { return W::KEY_FILES[jj]; } } + if (ii == 23) { if (jj < X::KEY_FILES.len as u32) { return X::KEY_FILES[jj]; } } + if (ii == 24) { if (jj < Y::KEY_FILES.len as u32) { return Y::KEY_FILES[jj]; } } + if (ii == 25) { if (jj < Z::KEY_FILES.len as u32) { return Z::KEY_FILES[jj]; } } + if (ii == 26) { if (jj < ti::KEY_FILES.len as u32) { return ti::KEY_FILES[jj]; } } + return ""; +} + +pub fn domain_of(letter: u32) -> string { + if (letter == 1) { return A::DOMAIN; } + if (letter == 2) { return B::DOMAIN; } + if (letter == 3) { return C::DOMAIN; } + if (letter == 4) { return D::DOMAIN; } + if (letter == 5) { return E::DOMAIN; } + if (letter == 6) { return F::DOMAIN; } + if (letter == 7) { return G::DOMAIN; } + if (letter == 8) { return H::DOMAIN; } + if (letter == 9) { return I::DOMAIN; } + if (letter == 10) { return J::DOMAIN; } + if (letter == 11) { return K::DOMAIN; } + if (letter == 12) { return L::DOMAIN; } + if (letter == 13) { return M::DOMAIN; } + if (letter == 14) { return N::DOMAIN; } + if (letter == 15) { return O::DOMAIN; } + if (letter == 16) { return P::DOMAIN; } + if (letter == 17) { return Q::DOMAIN; } + if (letter == 18) { return R::DOMAIN; } + if (letter == 19) { return S::DOMAIN; } + if (letter == 20) { return T::DOMAIN; } + if (letter == 21) { return U::DOMAIN; } + if (letter == 22) { return V::DOMAIN; } + if (letter == 23) { return W::DOMAIN; } + if (letter == 24) { return X::DOMAIN; } + if (letter == 25) { return Y::DOMAIN; } + if (letter == 26) { return Z::DOMAIN; } + if (letter == 27) { return ti::DOMAIN; } + return "UNOWNED"; +} + +pub fn letter_name(letter: u32) -> string { + if (letter == 1) { return A::LETTER; } + if (letter == 2) { return B::LETTER; } + if (letter == 3) { return C::LETTER; } + if (letter == 4) { return D::LETTER; } + if (letter == 5) { return E::LETTER; } + if (letter == 6) { return F::LETTER; } + if (letter == 7) { return G::LETTER; } + if (letter == 8) { return H::LETTER; } + if (letter == 9) { return I::LETTER; } + if (letter == 10) { return J::LETTER; } + if (letter == 11) { return K::LETTER; } + if (letter == 12) { return L::LETTER; } + if (letter == 13) { return M::LETTER; } + if (letter == 14) { return N::LETTER; } + if (letter == 15) { return O::LETTER; } + if (letter == 16) { return P::LETTER; } + if (letter == 17) { return Q::LETTER; } + if (letter == 18) { return R::LETTER; } + if (letter == 19) { return S::LETTER; } + if (letter == 20) { return T::LETTER; } + if (letter == 21) { return U::LETTER; } + if (letter == 22) { return V::LETTER; } + if (letter == 23) { return W::LETTER; } + if (letter == 24) { return X::LETTER; } + if (letter == 25) { return Y::LETTER; } + if (letter == 26) { return Z::LETTER; } + if (letter == 27) { return ti::LETTER; } + return "-"; +} + +// ------------------------------------------------------------ path matching + +/// Skip a leading "t27/" of a pattern. +pub fn pat_skip(pat: string) -> u32 { + if ((pat.len as u32) > 4) { + if ((pat[0] as u32) == 116) { if ((pat[1] as u32) == 50) { if ((pat[2] as u32) == 55) { if ((pat[3] as u32) == 47) { return 4; } } } } + } + return 0; +} + +/// Where the '*' is in pat (from p0), or NONE. +pub fn pat_star(pat: string, p0: u32) -> u32 { + var kk : u32 = p0; + while (kk < (pat.len as u32)) { + if ((pat[kk] as u32) == 42) { return kk; } + kk = kk + 1; + } + return NONE; +} + +/// Does text[a..b) start with pat[p0..p1) ? +pub fn starts_at(text: string, aa: u32, bb: u32, pat: string, p0: u32, p1: u32) -> bool { + if (bb - aa < p1 - p0) { return false; } + var kk : u32 = 0; + while (kk < p1 - p0) { + if ((text[aa + kk] as u32) != (pat[p0 + kk] as u32)) { return false; } + kk = kk + 1; + } + return true; +} + +/// Does text[a..b) end with pat[p0..p1) ? +pub fn ends_at(text: string, aa: u32, bb: u32, pat: string, p0: u32, p1: u32) -> bool { + if (bb - aa < p1 - p0) { return false; } + var kk : u32 = 0; + while (kk < p1 - p0) { + if ((text[bb - (p1 - p0) + kk] as u32) != (pat[p0 + kk] as u32)) { return false; } + kk = kk + 1; + } + return true; +} + +/// How pat matches the path text[a..b): K_NONE, K_EXACT, K_PREFIX (directory prefix or glob). +pub fn match_kind(text: string, aa: u32, bb: u32, pat: string) -> u32 { + const p0 : u32 = pat_skip(pat); + const pn : u32 = pat.len as u32; + if (pn == p0) { return K_NONE; } + const star : u32 = pat_star(pat, p0); + if (star != NONE) { + if (bb - aa < star - p0 + (pn - star - 1)) { return K_NONE; } + if (starts_at(text, aa, bb, pat, p0, star)) { + if (ends_at(text, aa, bb, pat, star + 1, pn)) { return K_PREFIX; } + } + return K_NONE; + } + if ((pat[pn - 1] as u32) == 47) { + if (starts_at(text, aa, bb, pat, p0, pn)) { return K_PREFIX; } + return K_NONE; + } + if (bb - aa == pn - p0) { + if (starts_at(text, aa, bb, pat, p0, pn)) { return K_EXACT; } + } + return K_NONE; +} + +/// Specificity of a pattern: its length after the "t27/" skip, exact beats prefix on equal length. +pub fn pat_score(pat: string, kind: u32) -> u32 { + var sc : u32 = ((pat.len as u32) - pat_skip(pat)) * 2; + if (kind == K_EXACT) { sc = sc + 1; } + return sc; +} + +/// Owner of the path text[a..b) as letter * 4 + kind (0 = unowned). +pub fn owner_range(text: string, aa: u32, bb: u32) -> u32 { + var best : u32 = 0; + var best_score : u32 = 0; + var ii : u32 = 0; + while (ii < AGENT_COUNT) { + var jj : u32 = 0; + const nn : u32 = key_n(ii); + while (jj < nn) { + const pat : string = key_at(ii, jj); + const kd : u32 = match_kind(text, aa, bb, pat); + if (kd != K_NONE) { + const sc : u32 = pat_score(pat, kd); + if (sc > best_score) { best_score = sc; best = (ii + 1) * 4 + kd; } + } + jj = jj + 1; + } + ii = ii + 1; + } + return best; +} + +pub fn letter_of_path(path: string) -> u32 { + return owner_range(path, 0, path.len as u32) / 4; +} + +pub fn kind_of_path(path: string) -> u32 { + return owner_range(path, 0, path.len as u32) % 4; +} + +/// CONFIRMED when the owner is an exact KEY_FILES path, GUESS when a prefix or glob (or nobody) decided it. +pub fn path_label(path: string) -> u32 { + if (kind_of_path(path) == K_EXACT) { return LABEL_CONFIRMED; } + return LABEL_GUESS; +} + +pub fn label_text(ll: u32) -> string { + if (ll == LABEL_CONFIRMED) { return "CONFIRMED"; } + return "GUESS"; +} + +// ------------------------------------------------------------ parsing the log + +/// Start of the next record (a 0x1e byte) at or after at, or the text length. +pub fn rec_next(log: string, at: u32) -> u32 { + var ii : u32 = at; + while (ii < (log.len as u32)) { + if ((log[ii] as u32) == RS) { return ii; } + ii = ii + 1; + } + return log.len as u32; +} + +/// Start of the byte after the first byte equal to c, from at, bounded by end; NONE if absent. +pub fn after_byte(log: string, at: u32, end: u32, cc: u32) -> u32 { + var ii : u32 = at; + while (ii < end) { + if ((log[ii] as u32) == cc) { return ii + 1; } + ii = ii + 1; + } + return NONE; +} + +/// The commit time of the record that starts at rs (the 0x1e byte), 0 if malformed. +pub fn rec_time(log: string, rs: u32, end: u32) -> u64 { + const hh : u32 = after_byte(log, rs + 1, end, US); + if (hh == NONE) { return 0; } + var ii : u32 = hh; + var vv : u64 = 0; + while (ii < end) { + const cc : u32 = log[ii] as u32; + if (cc < 48) { return vv; } + if (cc > 57) { return vv; } + vv = vv * 10 + ((cc - 48) as u64); + ii = ii + 1; + } + return vv; +} + +/// Where the subject of the record starts (after the second 0x1f), or NONE. +pub fn subject_start(log: string, rs: u32) -> u32 { + const end : u32 = rec_next(log, rs + 1); + const hh : u32 = after_byte(log, rs + 1, end, US); + if (hh == NONE) { return NONE; } + return after_byte(log, hh, end, US); +} + +/// Length of the subject: up to the first newline. +pub fn subject_len(log: string, rs: u32) -> u32 { + const ss : u32 = subject_start(log, rs); + if (ss == NONE) { return 0; } + const end : u32 = rec_next(log, rs + 1); + var ii : u32 = ss; + while (ii < end) { + if ((log[ii] as u32) == NL) { return ii - ss; } + ii = ii + 1; + } + return end - ss; +} + +/// Subject length shown in one line: trailing spaces dropped, then cut to max - 3 if longer than max (the window adds "..."). +pub fn trim_len(log: string, rs: u32, max: u32) -> u32 { + const ss : u32 = subject_start(log, rs); + var nn : u32 = subject_len(log, rs); + while (nn > 0) { + if ((log[ss + nn - 1] as u32) != 32) { break; } + nn = nn - 1; + } + if (nn > max) { return max - 3; } + return nn; +} + +pub fn trim_dots(log: string, rs: u32, max: u32) -> bool { + const ss : u32 = subject_start(log, rs); + if (ss == NONE) { return false; } + return subject_len(log, rs) > max; +} + +/// Start of the file list of the record: after the subject line and the blank line. +pub fn files_start(log: string, rs: u32) -> u32 { + const end : u32 = rec_next(log, rs + 1); + const ss : u32 = subject_start(log, rs); + if (ss == NONE) { return end; } + var ii : u32 = ss + subject_len(log, rs); + while (ii < end) { + if ((log[ii] as u32) != NL) { return ii; } + ii = ii + 1; + } + return end; +} + +/// One pass over the log for letter L (0 = unowned); what picks the result: +/// W_COMMITS commits no older than arg seconds that touch L +/// W_FILES files of L in those commits +/// W_LAST time of the newest commit touching L (arg unused) +/// W_BUCKET commits touching L in day bucket arg (0 = last 24 h, 6 = 144..168 h) +/// W_LAST_REC start offset of the newest commit touching L, or NONE +/// W_OPEN files of L in the last 7 d whose owner is a GUESS (prefix or glob) or nobody +pub fn scan(log: string, now: u64, letter: u32, what: u32, arg: u64) -> u64 { + var acc : u64 = 0; + var best_rec : u64 = NONE as u64; + var rs : u32 = rec_next(log, 0); + const total : u32 = log.len as u32; + while (rs < total) { + const end : u32 = rec_next(log, rs + 1); + const tt : u64 = rec_time(log, rs, end); + var hit : u64 = 0; + var open : u64 = 0; + var pp : u32 = files_start(log, rs); + while (pp < end) { + var qq : u32 = pp; + while (qq < end) { + if ((log[qq] as u32) == NL) { break; } + qq = qq + 1; + } + if (qq > pp) { + const oo : u32 = owner_range(log, pp, qq); + if (oo / 4 == letter) { + hit = hit + 1; + if (oo % 4 != K_EXACT) { open = open + 1; } + } + } + pp = qq + 1; + } + if (hit > 0) { + if (what == W_COMMITS) { if (now - tt < arg) { acc = acc + 1; } } + if (what == W_FILES) { if (now - tt < arg) { acc = acc + hit; } } + if (what == W_LAST) { if (tt > acc) { acc = tt; } } + if (what == W_BUCKET) { + if (now - tt >= arg * DAY) { if (now - tt < (arg + 1) * DAY) { acc = acc + 1; } } + } + if (what == W_LAST_REC) { if (tt > acc) { acc = tt; best_rec = rs as u64; } } + if (what == W_OPEN) { if (now - tt < WEEK) { acc = acc + open; } } + } + rs = end; + } + if (what == W_LAST_REC) { return best_rec; } + return acc; +} + +pub fn commits_24h(log: string, now: u64, letter: u32) -> u64 { return scan(log, now, letter, W_COMMITS, DAY); } +pub fn commits_7d(log: string, now: u64, letter: u32) -> u64 { return scan(log, now, letter, W_COMMITS, WEEK); } +pub fn files_7d(log: string, now: u64, letter: u32) -> u64 { return scan(log, now, letter, W_FILES, WEEK); } +pub fn last_active(log: string, now: u64, letter: u32) -> u64 { return scan(log, now, letter, W_LAST, 0); } +pub fn spark(log: string, now: u64, letter: u32, day: u32) -> u64 { return scan(log, now, letter, W_BUCKET, day as u64); } +pub fn open_count(log: string, now: u64, letter: u32) -> u64 { return scan(log, now, letter, W_OPEN, 0); } +pub fn last_rec(log: string, now: u64, letter: u32) -> u64 { return scan(log, now, letter, W_LAST_REC, 0); } + +/// Day bucket (0 = last 24 h) of an age in seconds; BUCKETS when older than a week. +pub fn bucket_of(age: u64) -> u32 { + if (age >= WEEK) { return BUCKETS; } + return (age / DAY) as u32; +} + +/// Label of the per-letter attribution: CONFIRMED only when files were touched and none is open. +pub fn letter_label(files: u64, open: u64) -> u32 { + if (files > 0) { if (open == 0) { return LABEL_CONFIRMED; } } + return LABEL_GUESS; +} + +// ------------------------------------------------------------ lamp + +/// Is a path of letter L named in the red list (one path per line)? +pub fn is_blocked(red: string, letter: u32) -> bool { + var pp : u32 = 0; + const nn : u32 = red.len as u32; + while (pp < nn) { + var qq : u32 = pp; + while (qq < nn) { + if ((red[qq] as u32) == NL) { break; } + qq = qq + 1; + } + if (qq > pp) { if (owner_range(red, pp, qq) / 4 == letter) { return true; } } + pp = qq + 1; + } + return false; +} + +/// ACTIVE under 24 h, WARM under 7 d, else QUIET; BLOCKED wins. last = 0 means never. +pub fn status_of(now: u64, last: u64, blocked: bool) -> u32 { + if (blocked) { return ST_BLOCKED; } + if (last == 0) { return ST_QUIET; } + if (now - last < DAY) { return ST_ACTIVE; } + if (now - last < WEEK) { return ST_WARM; } + return ST_QUIET; +} + +pub fn status_text(ss: u32) -> string { + if (ss == ST_ACTIVE) { return "ACTIVE"; } + if (ss == ST_WARM) { return "WARM"; } + if (ss == ST_BLOCKED) { return "BLOCKED"; } + return "QUIET"; +} + +/// Default action verb of the lamp; the window appends action_target(letter) after "run tri test on". +pub fn action_verb(ss: u32) -> string { + if (ss == ST_BLOCKED) { return "fix the red check in"; } + if (ss == ST_ACTIVE) { return "open last PR"; } + if (ss == ST_WARM) { return "open last PR"; } + return "run tri test on"; +} + +pub fn action_target(letter: u32) -> string { + if (letter == 0) { return "-"; } + if (key_n(letter - 1) == 0) { return "-"; } + return key_at(letter - 1, 0); +} + +// ------------------------------------------------------------ open PRs: lines "pr " + +/// End of the number token after "pr " on the line that starts at p, or NONE. +pub fn pr_num_end(prs: string, pp: u32, nn: u32) -> u32 { + if (pp + 3 > nn) { return NONE; } + if ((prs[pp] as u32) != 112) { return NONE; } + if ((prs[pp + 1] as u32) != 114) { return NONE; } + if ((prs[pp + 2] as u32) != 32) { return NONE; } + var kk : u32 = pp + 3; + while (kk < nn) { + if ((prs[kk] as u32) == 32) { return kk; } + if ((prs[kk] as u32) == NL) { return NONE; } + kk = kk + 1; + } + return NONE; +} + +pub fn same_token(tt: string, aa: u32, bb: u32, cc: u32, dd: u32) -> bool { + if (bb - aa != dd - cc) { return false; } + var kk : u32 = 0; + while (kk < bb - aa) { + if ((tt[aa + kk] as u32) != (tt[cc + kk] as u32)) { return false; } + kk = kk + 1; + } + return true; +} + +/// Open PRs touching letter L: each PR number counted once. +pub fn pr_count(prs: string, letter: u32) -> u32 { + var cnt : u32 = 0; + var pp : u32 = 0; + const nn : u32 = prs.len as u32; + while (pp < nn) { + var qq : u32 = pp; + while (qq < nn) { + if ((prs[qq] as u32) == NL) { break; } + qq = qq + 1; + } + const ne : u32 = pr_num_end(prs, pp, qq); + if (ne != NONE) { + if (owner_range(prs, ne + 1, qq) / 4 == letter) { + var seen : bool = false; + var rr : u32 = 0; + while (rr < pp) { + var ss : u32 = rr; + while (ss < pp) { + if ((prs[ss] as u32) == NL) { break; } + ss = ss + 1; + } + const e2 : u32 = pr_num_end(prs, rr, ss); + if (e2 != NONE) { + if (same_token(prs, pp + 3, ne, rr + 3, e2)) { + if (owner_range(prs, e2 + 1, ss) / 4 == letter) { seen = true; } + } + } + rr = ss + 1; + } + if (!seen) { cnt = cnt + 1; } + } + } + pp = qq + 1; + } + return cnt; +} + +// ------------------------------------------------------------ tests + +test path_letter_known { + assert letter_of_path("SOUL.md") == 1; + assert kind_of_path("SOUL.md") == K_EXACT; + assert letter_of_path("architecture/ADR-0001.md") == 1; + assert kind_of_path("architecture/ADR-0001.md") == K_PREFIX; + assert letter_of_path("architecture/ADR.md") == 0; + assert letter_of_path(".github/workflows/ci.yml") == 2; + assert letter_of_path("specs/automation/x.t27") == 10; + assert letter_of_path("scripts/x.py") == 2; + assert letter_of_path("bootstrap/src/compiler.rs") == 3; + assert letter_of_path("bindings/py/x.py") == 24; + assert letter_of_path("specs/depin/prove.t27") == 25; + assert letter_of_path("docs/readme.md") == 26; + assert letter_of_path("specs/isa/registers.t27") == 9; + assert letter_of_path("specs/t27os/bench.t27") == 26; + assert kind_of_path("specs/t27os/bench.t27") == K_PREFIX; + assert letter_of_path("nothing/here.txt") == 0; + assert letter_of_path("") == 0; +} + +/// The 15 directories that changed most in 14 days of git history (git log origin/master --since=14.days +/// --name-only), one path in each: every one is owned by some real letter (the KEY_FILES are not stale). +test top_directories_are_owned { + assert letter_of_path(".trinity/seals/x.json") == 2; + assert letter_of_path("docs/now/x.md") == 5; + assert letter_of_path("specs/port/x.t27") == 4; + assert letter_of_path("bootstrap/tests/x.rs") == 3; + assert letter_of_path("specs/tri/x.t27") == 12; + assert letter_of_path("scripts/x.py") == 2; + assert letter_of_path("specs/analog/x.t27") == 16; + assert letter_of_path("specs/tools/x.t27") == 19; + assert letter_of_path("gen/rust/x.rs") == 3; + assert letter_of_path("specs/ml/x.t27") == 8; + assert letter_of_path("specs/t27f/x.t27") == 26; + assert letter_of_path("specs/fpga/x.t27") == 11; + assert letter_of_path("gen/c/x.c") == 3; + assert letter_of_path("specs/queen/x.t27") == 20; + assert letter_of_path("specs/widgets/x.t27") == 26; + assert letter_of_path("cli/t27b/x.rs") == 11; + assert letter_of_path("specs/t27os/x.t27") != 0; +} + +test path_labels { + assert path_label("SOUL.md") == LABEL_CONFIRMED; + assert path_label("specs/t27os/bench.t27") == LABEL_GUESS; + assert path_label("nothing/here.txt") == LABEL_GUESS; + assert label_text(LABEL_CONFIRMED) == "CONFIRMED"; + assert label_text(LABEL_GUESS) == "GUESS"; + assert letter_label(3, 0) == LABEL_CONFIRMED; + assert letter_label(3, 1) == LABEL_GUESS; + assert letter_label(0, 0) == LABEL_GUESS; +} + +test agents_by_use { + assert key_n(0) == 7; + assert key_n(26) == 2; + assert key_at(0, 0) == "SOUL.md"; + assert domain_of(1) == "Architecture / ADR / SOUL"; + assert domain_of(0) == "UNOWNED"; + assert letter_name(1) == "A"; + assert action_target(2) == ".github/workflows/"; + assert action_target(0) == "-"; + assert action_target(27) == "specs/sandbox/"; + assert action_target(28) == "-"; +} + +test buckets_and_status { + assert bucket_of(0) == 0; + assert bucket_of(86399) == 0; + assert bucket_of(86400) == 1; + assert bucket_of(604799) == 6; + assert bucket_of(604800) == 7; + assert status_of(1000000, 999000, false) == ST_ACTIVE; + assert status_of(1000000, 1000000 - 86400, false) == ST_WARM; + assert status_of(1000000, 1000000 - 604799, false) == ST_WARM; + assert status_of(1000000, 1000000 - 604800, false) == ST_QUIET; + assert status_of(1000000, 0, false) == ST_QUIET; + assert status_of(1000000, 999999, true) == ST_BLOCKED; + assert status_text(ST_BLOCKED) == "BLOCKED"; + assert status_text(ST_ACTIVE) == "ACTIVE"; + assert action_verb(ST_ACTIVE) == "open last PR"; + assert action_verb(ST_QUIET) == "run tri test on"; + assert action_verb(ST_BLOCKED) == "fix the red check in"; +} + +test red_and_prs { + assert is_blocked("scripts/a.py\nREADME-none.txt\n", 2) == true; + assert is_blocked("scripts/a.py\n", 1) == false; + assert is_blocked("", 2) == false; + assert is_blocked("nothing/here.txt\n", 0) == true; + const prs : string = "pr 7 scripts/a.py\npr 7 scripts/x.py\npr 8 scripts/a.py\npr 8 SOUL.md\npr 9 nothing/z\n"; + assert pr_count(prs, 2) == 2; + assert pr_count(prs, 1) == 1; + assert pr_count(prs, 0) == 1; + assert pr_count(prs, 3) == 0; + assert pr_count("", 2) == 0; +} + +test trim_subject { + const lg : string = "\x1eabc\x1f100\x1fshort subject \n\nSOUL.md\n\x1edef\x1f200\x1f0123456789\n\nscripts/a.py\n"; + assert subject_len(lg, 0) == 16; + assert trim_len(lg, 0, 40) == 13; + assert trim_dots(lg, 0, 40) == false; + assert subject_len(lg, 35) == 10; + assert trim_len(lg, 35, 8) == 5; + assert trim_dots(lg, 35, 8) == true; + assert rec_time(lg, 0, rec_next(lg, 1)) == 100; + assert commits_7d(lg, 350, 1) == 1; + assert commits_7d(lg, 350, 2) == 1; + assert last_active(lg, 350, 2) == 200; + assert last_active(lg, 350, 3) == 0; +} + + +test real_sample_two_days { + // git log origin/master --since=2.days, 9 records (the 374-file gen-c record left out), as of now = 1792482900 + const log : string = "\x1e1417e3bee41ce45285895098c9c9e6f7503c4614\x1f1792482823\x1fT27 OS keys: read and write through /usr/bin/security (no Keychain dialogs after rebuilds), several keys per provider in the KEYS tab (#11276)\n\n.trinity/seals/t27os_T27osKeys.json\n.trinity/seals/t27os_T27osPtyLogic.json\ndocs/t27os/keys-orchestration.md\ndocs/t27os/run-local.md\nspecs/t27os/glass_window.t27\nspecs/t27os/keys.t27\nspecs/t27os/keys_host.t27\nspecs/t27os/pty_logic.t27\n\x1ee3bb9ffec885a75ec411d05cf602d07620d1d0cb\x1f1792482450\x1ft27os: bench.t27 -- the boards, provenance of every number and the project summary a BENCH tab draws (Closes #11264) (#11267)\n\n.trinity/seals/t27os_T27osBench.json\nspecs/t27os/bench.t27\n\x1e10eeeb572d83e76b573f3a78d6e7b356ee0d7249\x1f1792482279\x1fT27 OS: matte glass everywhere in a window, black in full screen, panels narrow the terminal, KEYS foot rows, terminal clicks stay in the terminal (#11266)\n\n.trinity/seals/t27os_T27osEditors.json\n.trinity/seals/t27os_T27osTermPane.json\nspecs/t27os/editors.t27\nspecs/t27os/glass_window.t27\n\x1e339a2d31b481b8cb163ec8e07acfc25203dc7987\x1f1792481450\x1ft27os: CHAT is the third petal; homes registry and the home tabs t27a..t27u (Closes #11260) (#11261)\n\n.trinity/seals/lab_LabJobKind.json\n.trinity/seals/t27os_T27osHomes.json\nspecs/lab/job_kind.t27\nspecs/t27f/petal_menu.t27\nspecs/t27os/glass_window.t27\nspecs/t27os/homes.t27\n\x1eae97ff06d79f1ba6918d9ee71a813210e5b02f40\x1f1792480490\x1ft27o: second golden port -- csr_map.rs to specs/host/csr_map.t27 (Closes #11235) (#11255)\n\n.trinity/seals/host_HostCsrMap.json\nbootstrap/gen/rust/host/csr_map.rs\nbootstrap/src/host/csr_map.rs\nbootstrap/src/host/mod.rs\nspecs/host/csr_map.t27\n\x1e156e4c6c2c48a19051f99ebd269b443f6ce6ebd7\x1f1792480444\x1fseals: keys.t27 and pty_logic.t27 resealed with the current compiler (gen_hash_c drift on master) (Closes #11241) (#11257)\n\n.trinity/seals/t27os_T27osKeys.json\n.trinity/seals/t27os_T27osPtyLogic.json\n\x1eef2e7ee45ac24638026cd9f6bc808ab6c7199b0b\x1f1792479843\x1fT27 OS keys: every stored key in rotation, failover on overload, restored panes wait for their keys (#11256)\n\n.trinity/seals/t27os_T27osKeys.json\n.trinity/seals/t27os_T27osPtyLogic.json\nspecs/t27os/glass_window.t27\nspecs/t27os/keys.t27\nspecs/t27os/keys_host.t27\nspecs/t27os/pty_logic.t27\nspecs/t27os/pty_server.t27\n\x1ee10a1bb7cead1b9f8406469848efb26db9bfbbbb\x1f1792478937\x1fT27 OS: key manager and KEYS tab, key orchestration, glass window, 27 editors and the editor inside the window (#11254)\n\ndocs/reports/t27b_expectations.json\ndocs/t27os/keys-orchestration.md\nspecs/t27os/doctor.t27\nspecs/t27os/editors.t27\nspecs/t27os/editors_host.t27\nspecs/t27os/glass_window.t27\nspecs/t27os/keys.t27\nspecs/t27os/keys_host.t27\nspecs/t27os/pty_client.t27\nspecs/t27os/pty_logic.t27\nspecs/t27os/pty_proto.t27\nspecs/t27os/pty_server.t27\nspecs/t27os/pty_sys.t27\nspecs/t27os/queen.t27\nspecs/t27os/router.t27\nspecs/t27os/term_pane.t27\ntools/verilog_width_baseline.txt\n\x1e9a3c8394a6e365b12a26999c56746de506bf2eb5\x1f1792478563\x1fhive-proxy.t27: who may spend a stored provider key over the Anthropic protocol; queen-contributor-keys v3 (#11253)\n\n.trinity/seals/automation_automation::hive_proxy.json\n.trinity/seals/automation_automation::queen_contributor_keys.json\nspecs/automation/hive-proxy.t27\nspecs/automation/queen-contributor-keys.t27\n"; + const now : u64 = 1792482900; + // unowned is empty now: every one of the 55 files has a letter (before the KEY_FILES refresh: 18 unowned, 33 S, 4 Z) + assert commits_24h(log, now, 0) == 0; + assert files_7d(log, now, 0) == 0; + assert open_count(log, now, 0) == 0; + assert last_active(log, now, 0) == 0; + // B (2): seals and tools + assert commits_24h(log, now, 2) == 9; + assert commits_7d(log, now, 2) == 9; + assert files_7d(log, now, 2) == 15; + assert last_active(log, now, 2) == 1792482823; + assert spark(log, now, 2, 0) == 9; + // C (3): bootstrap/src/host, bootstrap/gen/rust/host + assert commits_7d(log, now, 3) == 1; + assert files_7d(log, now, 3) == 3; + assert last_active(log, now, 3) == 1792480490; + // J (10): specs/automation + assert commits_7d(log, now, 10) == 1; + assert files_7d(log, now, 10) == 2; + // M (13): docs/reports + assert commits_7d(log, now, 13) == 1; + assert files_7d(log, now, 13) == 1; + // S (19): the fallback now holds only specs/lab and specs/host + assert commits_7d(log, now, 19) == 2; + assert files_7d(log, now, 19) == 2; + assert last_active(log, now, 19) == 1792481450; + // Z (26): specs/t27os, specs/t27f, docs/t27os + assert commits_24h(log, now, 26) == 6; + assert commits_7d(log, now, 26) == 6; + assert files_7d(log, now, 26) == 32; + assert last_active(log, now, 26) == 1792482823; + assert spark(log, now, 26, 0) == 6; + assert open_count(log, now, 26) == 32; + assert status_of(now, last_active(log, now, 26), false) == ST_ACTIVE; + assert status_of(now, last_active(log, now, 7), false) == ST_QUIET; + assert commits_7d(log, now, 7) == 0; + assert spark(log, now, 26, 1) == 0; +} + +test real_sample_what_is_happening { + const log : string = "\x1e1417e3bee41ce45285895098c9c9e6f7503c4614\x1f1792482823\x1fT27 OS keys: read and write through /usr/bin/security (no Keychain dialogs after rebuilds), several keys per provider in the KEYS tab (#11276)\n\n.trinity/seals/t27os_T27osKeys.json\n.trinity/seals/t27os_T27osPtyLogic.json\ndocs/t27os/keys-orchestration.md\ndocs/t27os/run-local.md\nspecs/t27os/glass_window.t27\nspecs/t27os/keys.t27\nspecs/t27os/keys_host.t27\nspecs/t27os/pty_logic.t27\n\x1ee3bb9ffec885a75ec411d05cf602d07620d1d0cb\x1f1792482450\x1ft27os: bench.t27 -- the boards, provenance of every number and the project summary a BENCH tab draws (Closes #11264) (#11267)\n\n.trinity/seals/t27os_T27osBench.json\nspecs/t27os/bench.t27\n\x1e10eeeb572d83e76b573f3a78d6e7b356ee0d7249\x1f1792482279\x1fT27 OS: matte glass everywhere in a window, black in full screen, panels narrow the terminal, KEYS foot rows, terminal clicks stay in the terminal (#11266)\n\n.trinity/seals/t27os_T27osEditors.json\n.trinity/seals/t27os_T27osTermPane.json\nspecs/t27os/editors.t27\nspecs/t27os/glass_window.t27\n\x1e339a2d31b481b8cb163ec8e07acfc25203dc7987\x1f1792481450\x1ft27os: CHAT is the third petal; homes registry and the home tabs t27a..t27u (Closes #11260) (#11261)\n\n.trinity/seals/lab_LabJobKind.json\n.trinity/seals/t27os_T27osHomes.json\nspecs/lab/job_kind.t27\nspecs/t27f/petal_menu.t27\nspecs/t27os/glass_window.t27\nspecs/t27os/homes.t27\n\x1eae97ff06d79f1ba6918d9ee71a813210e5b02f40\x1f1792480490\x1ft27o: second golden port -- csr_map.rs to specs/host/csr_map.t27 (Closes #11235) (#11255)\n\n.trinity/seals/host_HostCsrMap.json\nbootstrap/gen/rust/host/csr_map.rs\nbootstrap/src/host/csr_map.rs\nbootstrap/src/host/mod.rs\nspecs/host/csr_map.t27\n\x1e156e4c6c2c48a19051f99ebd269b443f6ce6ebd7\x1f1792480444\x1fseals: keys.t27 and pty_logic.t27 resealed with the current compiler (gen_hash_c drift on master) (Closes #11241) (#11257)\n\n.trinity/seals/t27os_T27osKeys.json\n.trinity/seals/t27os_T27osPtyLogic.json\n\x1eef2e7ee45ac24638026cd9f6bc808ab6c7199b0b\x1f1792479843\x1fT27 OS keys: every stored key in rotation, failover on overload, restored panes wait for their keys (#11256)\n\n.trinity/seals/t27os_T27osKeys.json\n.trinity/seals/t27os_T27osPtyLogic.json\nspecs/t27os/glass_window.t27\nspecs/t27os/keys.t27\nspecs/t27os/keys_host.t27\nspecs/t27os/pty_logic.t27\nspecs/t27os/pty_server.t27\n\x1ee10a1bb7cead1b9f8406469848efb26db9bfbbbb\x1f1792478937\x1fT27 OS: key manager and KEYS tab, key orchestration, glass window, 27 editors and the editor inside the window (#11254)\n\ndocs/reports/t27b_expectations.json\ndocs/t27os/keys-orchestration.md\nspecs/t27os/doctor.t27\nspecs/t27os/editors.t27\nspecs/t27os/editors_host.t27\nspecs/t27os/glass_window.t27\nspecs/t27os/keys.t27\nspecs/t27os/keys_host.t27\nspecs/t27os/pty_client.t27\nspecs/t27os/pty_logic.t27\nspecs/t27os/pty_proto.t27\nspecs/t27os/pty_server.t27\nspecs/t27os/pty_sys.t27\nspecs/t27os/queen.t27\nspecs/t27os/router.t27\nspecs/t27os/term_pane.t27\ntools/verilog_width_baseline.txt\n\x1e9a3c8394a6e365b12a26999c56746de506bf2eb5\x1f1792478563\x1fhive-proxy.t27: who may spend a stored provider key over the Anthropic protocol; queen-contributor-keys v3 (#11253)\n\n.trinity/seals/automation_automation::hive_proxy.json\n.trinity/seals/automation_automation::queen_contributor_keys.json\nspecs/automation/hive-proxy.t27\nspecs/automation/queen-contributor-keys.t27\n"; + const now : u64 = 1792482900; + const rec : u64 = last_rec(log, now, 26); + assert rec != 4294967295; + assert subject_len(log, rec as u32) == 142; + assert (log[subject_start(log, rec as u32)] as u32) == 84; + assert last_rec(log, now, 7) == 4294967295; +} + +// The pin for specs/t27os/kanban.t27: kanban cannot import this module (its module-qualified KEY_FILES reads do +// not splice into an importer), so the host fills the letters field of each card from letter_of_path and this test +// holds the paths of kanban's SAMPLE to the letters written on those lines. +test kanban_sample_paths_are_owned_by_the_letters_of_its_lines { + assert letter_of_path("specs/t27os/agent_parts.t27") == 26; + assert letter_of_path("specs/t27os/homes.t27") == 26; + assert letter_of_path("specs/automation/x.t27") == 10; + assert letter_of_path("specs/t27os/pty_server.t27") == 26; + assert letter_of_path("specs/t27os/keys.t27") == 26; + assert letter_of_path("specs/t27f/widget_factory.t27") == 26; + assert letter_of_path("bootstrap/src/compiler.rs") == 3; + assert letter_of_path(".trinity/seals/t27os_T27osHomes.json") == 2; + assert letter_of_path("docs/now/x.md") == 5; +} diff --git a/specs/t27os/evolve.t27 b/specs/t27os/evolve.t27 new file mode 100644 index 0000000000..3100c59f2d --- /dev/null +++ b/specs/t27os/evolve.t27 @@ -0,0 +1,425 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/t27os/evolve.t27 -- the rule GOD MODE runs: gaps from the panel, one priority, guardrails, one lane per task +; Owner, 2026-10-20: tie everything into one working T27 OS; in GOD MODE the system evolves by itself. The law is +; AGENTS.md "T27 OS: the instrument panel, and GOD MODE evolution". Closes #11275. ASCII only (L3), English (LANG-EN). +; Pure: the host (glass_window) reads the panel and supplies plain integers; this module only decides. Letters are +; 1..27 (agent_parts), a set of letters is a u32 mask with bit (letter - 1). It cannot start anything itself. +; GAPS what the panel shows is missing, in priority order (G_*): red on master, a BLOCKED domain, a stale +; seal, unowned paths, a letter with no instrument (agent_parts NO_WIDGETS), a letter never pinned, +; a spec no widget shows (the census count). One gap -> one TASK for one letter. +; GUARDRAILS never when GOD MODE is off or the panel is closed (queen.t27 rule); at most MAX_AGENTS running; +; one task per letter in flight; at most MAX_PER_HOUR tasks an hour; a gap that touches a forbidden +; class (CLAUDE.md "Proactivity": owner-only gate files, secrets, payments, public claims without a +; measurement, passwords and keys) becomes an OWNER REQUEST, never a task. +; LANE worktree /tmp/claude-501/lanes/-, branch claude/evolve--, pane agent 9 (bash), +; one `claude -p --model sonnet` line under the pty server's 200-byte cap (run-local.md section 6). +; Landing is only a PR with `Closes #N` and the normal gates, never a push to master. +; Decisions: red on master has no letter, it goes to B (build pipeline); unowned paths go to S (specs and +; standards, who owns ownership). Both are guesses, labelled so; the host may override by masks. +; queen.t27 cannot call this (evolve uses its ring packing, so a call back would be a cycle): the window calls +; next_action and passes the result to the suggestion strip. + +module T27osEvolve; + +use t27os::queen::ev_pack; +use t27os::queen_board::quiet; +use t27os::queen::ev_control; +use t27os::queen::ev_arg; +use t27os::queen::ev_sec; +use t27os::queen::by_queen; +use t27os::queen::VIEW_HOME; +use t27os::queen::C_QUEEN_STARTED; +use t27os::queen::C_QUEEN_ASKED; + +pub const MAX_AGENTS : u32 = 3; +pub const MAX_PER_HOUR : u32 = 4; +pub const LETTERS : u32 = 27; +pub const PTY_CAP : u32 = 200; +pub const LANE_AGENT : u32 = 9; +pub const LETTER_RED : u32 = 2; +pub const LETTER_UNOWNED : u32 = 19; + +// gap kinds, most urgent first +pub const G_NONE : u32 = 0; +pub const G_RED : u32 = 1; +pub const G_BLOCKED : u32 = 2; +pub const G_STALE : u32 = 3; +pub const G_UNOWNED : u32 = 4; +pub const G_NO_INSTRUMENT : u32 = 5; +pub const G_NEVER_PINNED : u32 = 6; +pub const G_NO_WIDGET : u32 = 7; + +// what the rule decides +pub const D_NONE : u32 = 0; +pub const D_TASK : u32 = 1; +pub const D_REQUEST : u32 = 2; + +// why it did nothing +pub const R_OK : u32 = 0; +pub const R_GOD_OFF : u32 = 1; +pub const R_PANEL_CLOSED : u32 = 2; +pub const R_AGENT_CAP : u32 = 3; +pub const R_BUDGET : u32 = 4; +pub const R_NO_GAP : u32 = 5; + +// forbidden classes (bits) +pub const F_GATE : u32 = 1; +pub const F_SECRET : u32 = 2; +pub const F_PAYMENT : u32 = 4; +pub const F_PUBLIC : u32 = 8; +pub const F_KEYS : u32 = 16; +pub const F_ALL : u32 = 31; + +pub fn bit(letter: u32) -> u32 { + if (letter == 0 or letter > LETTERS) { return 0; } + return @as(u32, 1) << (letter - 1); +} + +pub fn has(mask: u32, letter: u32) -> bool { + return mask & bit(letter) != 0; +} + +/// Lowest letter in the mask, 0 if empty (ties by letter order). +pub fn first_letter(mask: u32) -> u32 { + var l : u32 = 1; + while (l <= LETTERS) { + if (has(mask, l)) { return l; } + l = l + 1; + } + return 0; +} + +pub fn touches_forbidden(classes: u32) -> bool { + return classes & F_ALL != 0; +} + +/// The gap state the host reads off the panel. Masks are letters with that gap. +pub struct Panel { + master_red: bool, + blocked: u32, + stale: u32, + unowned_count: u32, + no_instrument: u32, + never_pinned: u32, + no_widget_count: u32, + no_widget: u32, +} + +/// The most urgent gap not already in flight: its kind (letter via gap_letter). +pub fn gap_kind(p: Panel, inflight: u32) -> u32 { + if (p.master_red and has(inflight, LETTER_RED) == false) { return G_RED; } + if (first_letter(p.blocked & ~inflight) != 0) { return G_BLOCKED; } + if (first_letter(p.stale & ~inflight) != 0) { return G_STALE; } + if (p.unowned_count > 0 and has(inflight, LETTER_UNOWNED) == false) { return G_UNOWNED; } + if (first_letter(p.no_instrument & ~inflight) != 0) { return G_NO_INSTRUMENT; } + if (first_letter(p.never_pinned & ~inflight) != 0) { return G_NEVER_PINNED; } + if (p.no_widget_count > 0 and first_letter(p.no_widget & ~inflight) != 0) { return G_NO_WIDGET; } + return G_NONE; +} + +pub fn gap_letter(p: Panel, inflight: u32, kind: u32) -> u32 { + if (kind == G_RED) { return LETTER_RED; } + if (kind == G_BLOCKED) { return first_letter(p.blocked & ~inflight); } + if (kind == G_STALE) { return first_letter(p.stale & ~inflight); } + if (kind == G_UNOWNED) { return LETTER_UNOWNED; } + if (kind == G_NO_INSTRUMENT) { return first_letter(p.no_instrument & ~inflight); } + if (kind == G_NEVER_PINNED) { return first_letter(p.never_pinned & ~inflight); } + if (kind == G_NO_WIDGET) { return first_letter(p.no_widget & ~inflight); } + return 0; +} + +/// Why nothing may start now, R_OK if a task may. The gates of queen.t27 first, then the caps. +pub fn why_not(god: bool, panel_open: bool, running: u32, started_hour: u32) -> u32 { + return why_not_cap(god, panel_open, running, started_hour, MAX_AGENTS); +} + +/// The same with the agent cap as an input: kernel.t27 computes it from the machine's resources and passes it +/// (kernel uses evolve, so evolve cannot call kernel: that would be a cycle). MAX_AGENTS is the fallback cap. +pub fn why_not_cap(god: bool, panel_open: bool, running: u32, started_hour: u32, cap: u32) -> u32 { + if (god == false) { return R_GOD_OFF; } + if (panel_open == false) { return R_PANEL_CLOSED; } + if (running >= cap) { return R_AGENT_CAP; } + if (started_hour >= MAX_PER_HOUR) { return R_BUDGET; } + return R_OK; +} + +/// The decision packed in a u32: action + 4 * kind + 64 * letter. +pub fn dec_pack(action: u32, kind: u32, letter: u32) -> u32 { + return action + 4 * kind + 64 * letter; +} +pub fn dec_action(d: u32) -> u32 { return d % 4; } +pub fn dec_kind(d: u32) -> u32 { return d / 4 % 16; } +pub fn dec_letter(d: u32) -> u32 { return d / 64; } + +/// The one entry point: what GOD MODE does next. `risk_mask` are letters whose gap touches `risk_class`. +/// A request needs only the gates; a task also needs a free agent slot and budget. +pub fn next_action(p: Panel, inflight: u32, god: bool, panel_open: bool, running: u32, started_hour: u32, risk_mask: u32, risk_class: u32) -> u32 { + if (god == false or panel_open == false) { return D_NONE; } + const kind : u32 = gap_kind(p, inflight); + if (kind == G_NONE) { return D_NONE; } + const letter : u32 = gap_letter(p, inflight, kind); + if (has(risk_mask, letter) and touches_forbidden(risk_class)) { return dec_pack(D_REQUEST, kind, letter); } + if (why_not(god, panel_open, running, started_hour) != R_OK) { return D_NONE; } + return dec_pack(D_TASK, kind, letter); +} + +/// The reason next_action did nothing, for the log: R_NO_GAP when only the gap is missing. +pub fn idle_reason(p: Panel, inflight: u32, god: bool, panel_open: bool, running: u32, started_hour: u32) -> u32 { + const w : u32 = why_not(god, panel_open, running, started_hour); + if (w != R_OK) { return w; } + if (gap_kind(p, inflight) == G_NONE) { return R_NO_GAP; } + return R_OK; +} + +// ---- the Queen's own log entry (RECENT ring) ---- + +/// An action logged as hers: C_QUEEN_STARTED for a task, C_QUEEN_ASKED for an owner request; arg = letter. +pub fn log_event(d: u32, sec: u64) -> u64 { + if (dec_action(d) == D_TASK) { return ev_pack(VIEW_HOME, C_QUEEN_STARTED, dec_letter(d), sec); } + if (dec_action(d) == D_REQUEST) { return ev_pack(VIEW_HOME, C_QUEEN_ASKED, dec_letter(d), sec); } + return 0; +} + +// ---- the task: title and the 4-heading issue body, as text pieces the host joins ---- + +pub fn kind_phrase(kind: u32) -> string { + if (kind == G_RED) { return "fix the red check on master"; } + if (kind == G_BLOCKED) { return "unblock the BLOCKED domain"; } + if (kind == G_STALE) { return "reseal the stale seals"; } + if (kind == G_UNOWNED) { return "give the unowned paths an owner"; } + if (kind == G_NO_INSTRUMENT) { return "give the letter its first instrument"; } + if (kind == G_NEVER_PINNED) { return "pin an instrument of the letter in the panel"; } + if (kind == G_NO_WIDGET) { return "show a spec no widget shows"; } + return "nothing"; +} + +pub fn letter_char(l: u32) -> string { + if (l == 1) { return "a"; } if (l == 2) { return "b"; } if (l == 3) { return "c"; } if (l == 4) { return "d"; } + if (l == 5) { return "e"; } if (l == 6) { return "f"; } if (l == 7) { return "g"; } if (l == 8) { return "h"; } + if (l == 9) { return "i"; } if (l == 10) { return "j"; } if (l == 11) { return "k"; } if (l == 12) { return "l"; } + if (l == 13) { return "m"; } if (l == 14) { return "n"; } if (l == 15) { return "o"; } if (l == 16) { return "p"; } + if (l == 17) { return "q"; } if (l == 18) { return "r"; } if (l == 19) { return "s"; } if (l == 20) { return "t"; } + if (l == 21) { return "u"; } if (l == 22) { return "v"; } if (l == 23) { return "w"; } if (l == 24) { return "x"; } + if (l == 25) { return "y"; } if (l == 26) { return "z"; } if (l == 27) { return "ti"; } + return "-"; +} + +pub const TITLE_HEAD : string = "t27os evolve: "; +pub const TITLE_MID : string = " -- letter "; + +/// Title = TITLE_HEAD + kind_phrase + TITLE_MID + letter_char; its length for a counted buffer. +pub fn title_len(kind: u32, letter: u32) -> u32 { + return (TITLE_HEAD.len as u32) + (kind_phrase(kind).len as u32) + (TITLE_MID.len as u32) + (letter_char(letter).len as u32); +} + +pub const H_BOUNDARY : string = "Boundary"; +pub const H_SCENARIO : string = "User Scenario"; +pub const H_REQUIREMENT : string = "Requirement"; +pub const H_SUCCESS : string = "Success Criteria"; + +/// Heading i (0..3) of the issue body. +pub fn heading(i: u32) -> string { + if (i == 0) { return H_BOUNDARY; } + if (i == 1) { return H_SCENARIO; } + if (i == 2) { return H_REQUIREMENT; } + return H_SUCCESS; +} + +pub fn body_boundary(kind: u32) -> string { + if (kind == G_RED) { return "only the files of the failing check; no gate files"; } + if (kind == G_BLOCKED) { return "only the files of the blocked domain path"; } + if (kind == G_STALE) { return "the stale seals under .trinity/seals and their specs"; } + if (kind == G_UNOWNED) { return "OWNERS.md of the unowned paths"; } + if (kind == G_NO_WIDGET) { return "the spec and the widget catalog entry"; } + return "specs/t27os/ and the letter's entry in agent_parts.t27"; +} + +pub fn body_scenario(kind: u32) -> string { + if (kind == G_NONE) { return "-"; } + return "GOD MODE found this gap on the panel and opened this issue."; +} + +pub fn body_requirement(kind: u32) -> string { + return kind_phrase(kind); +} + +pub const BODY_SUCCESS : string = "Tests pass with no vacuous test, seals refreshed, Only t27, DRY, merged by PR."; +pub const LAND_FOOTER : string = "Closes #"; + +pub fn body_success(kind: u32) -> string { + if (kind == G_NONE) { return "-"; } + return BODY_SUCCESS; +} + +// ---- the lane ---- + +pub const LANE_DIR : string = "/tmp/claude-501/lanes/"; +pub const BRANCH_HEAD : string = "claude/evolve-"; +pub const WORKTREE_CMD : string = "git worktree add -b "; +pub const CMD_A : string = "claude -p --model sonnet < "; +pub const CMD_B : string = ".prompt > "; +pub const CMD_C : string = ".out 2>&1; echo EXIT=$?"; + +pub fn digits(n: u32) -> u32 { + if (n < 10) { return 1; } + if (n < 100) { return 2; } + return 3; +} + +/// - as used in the path and the branch. +pub fn lane_id_len(letter: u32, n: u32) -> u32 { + return (letter_char(letter).len as u32) + 1 + digits(n); +} + +pub fn lane_path_len(letter: u32, n: u32) -> u32 { + return (LANE_DIR.len as u32) + lane_id_len(letter, n); +} + +pub fn branch_len(letter: u32, n: u32) -> u32 { + return (BRANCH_HEAD.len as u32) + lane_id_len(letter, n); +} + +/// The one line written into the bash pane (agent 9): stdin from the prompt file, output to a file, a sentinel. +pub fn agent_cmd_len(letter: u32, n: u32) -> u32 { + return (CMD_A.len as u32) + lane_path_len(letter, n) + (CMD_B.len as u32) + lane_path_len(letter, n) + (CMD_C.len as u32); +} + +/// The line that makes the worktree: git worktree add -b master. +pub fn worktree_cmd_len(letter: u32, n: u32) -> u32 { + return (WORKTREE_CMD.len as u32) + branch_len(letter, n) + 1 + lane_path_len(letter, n) + 7; +} + +pub fn fits_pty(len: u32) -> bool { + return len <= PTY_CAP; +} + +/// No lane ever pushes to master; the only landing is a PR whose body carries LAND_FOOTER and the gates. +pub fn may_push_master() -> bool { + return false; +} + +pub fn lands_by_pr(has_closes: bool, gates_green: bool) -> bool { + return has_closes and gates_green; +} + +// ---- tests ---- + +test priority_runs_from_red_on_master_down_to_a_spec_no_widget_shows { + var p : Panel = quiet(); + assert gap_kind(p, 0) == G_NONE; + p.no_widget_count = 5; p.no_widget = bit(9); + assert gap_kind(p, 0) == G_NO_WIDGET; + p.never_pinned = bit(4); + assert gap_kind(p, 0) == G_NEVER_PINNED; + p.no_instrument = bit(7); + assert gap_kind(p, 0) == G_NO_INSTRUMENT; + p.unowned_count = 3; + assert gap_kind(p, 0) == G_UNOWNED; + p.stale = bit(12); + assert gap_kind(p, 0) == G_STALE; + p.blocked = bit(3); + assert gap_kind(p, 0) == G_BLOCKED; + p.master_red = true; + assert gap_kind(p, 0) == G_RED; + assert gap_letter(p, 0, G_RED) == LETTER_RED; +} + +test ties_go_to_the_lowest_letter_and_a_task_has_a_kind_letter_and_title { + var p : Panel = quiet(); + p.no_instrument = bit(20) + bit(7) + bit(11); + assert gap_kind(p, 0) == G_NO_INSTRUMENT; + assert gap_letter(p, 0, G_NO_INSTRUMENT) == 7; + assert first_letter(0) == 0; + assert bit(0) == 0 and bit(28) == 0 and bit(27) == 67108864; + // the title is counted: head + phrase + mid + letter + assert title_len(G_NO_INSTRUMENT, 7) == 14 + 36 + 11 + 1; + assert title_len(G_NO_INSTRUMENT, 27) == 14 + 36 + 11 + 2; + assert (heading(0).len as u32) == 8 and (heading(1).len as u32) == 13; + assert (heading(2).len as u32) == 11 and (heading(3).len as u32) == 16; + assert (body_boundary(G_STALE).len as u32) > 0; + assert (body_scenario(G_RED).len as u32) > 0 and (body_success(G_RED).len as u32) > 0; + assert (body_requirement(G_STALE).len as u32) == (kind_phrase(G_STALE).len as u32); + assert (LAND_FOOTER.len as u32) == 8; +} + +test one_task_per_letter_in_flight { + var p : Panel = quiet(); + p.no_instrument = bit(7) + bit(11); + assert dec_letter(next_action(p, 0, true, true, 0, 0, 0, 0)) == 7; + // letter 7 already runs: the next letter, never a second task on 7 + assert dec_letter(next_action(p, bit(7), true, true, 1, 1, 0, 0)) == 11; + assert next_action(p, bit(7) + bit(11), true, true, 2, 2, 0, 0) == D_NONE; + // red on master is also one task: in flight (letter B) it is skipped + p.master_red = true; + assert dec_kind(next_action(p, bit(LETTER_RED), true, true, 1, 1, 0, 0)) == G_NO_INSTRUMENT; +} + +test never_when_god_mode_is_off_or_the_panel_is_closed { + var p : Panel = quiet(); + p.master_red = true; + assert next_action(p, 0, false, true, 0, 0, 0, 0) == D_NONE; + assert next_action(p, 0, true, false, 0, 0, 0, 0) == D_NONE; + assert dec_action(next_action(p, 0, true, true, 0, 0, 0, 0)) == D_TASK; + assert why_not(false, true, 0, 0) == R_GOD_OFF; + assert why_not(true, false, 0, 0) == R_PANEL_CLOSED; + assert idle_reason(quiet(), 0, true, true, 0, 0) == R_NO_GAP; +} + +test at_most_three_agents_and_a_budget_per_hour { + var p : Panel = quiet(); + p.stale = bit(5); + assert dec_action(next_action(p, 0, true, true, 2, 0, 0, 0)) == D_TASK; + assert next_action(p, 0, true, true, MAX_AGENTS, 0, 0, 0) == D_NONE; + assert next_action(p, 0, true, true, 9, 0, 0, 0) == D_NONE; + assert why_not(true, true, MAX_AGENTS, 0) == R_AGENT_CAP; + assert dec_action(next_action(p, 0, true, true, 0, MAX_PER_HOUR - 1, 0, 0)) == D_TASK; + assert next_action(p, 0, true, true, 0, MAX_PER_HOUR, 0, 0) == D_NONE; + assert why_not(true, true, 0, MAX_PER_HOUR) == R_BUDGET; + assert MAX_AGENTS == 3; +} + +test a_forbidden_class_becomes_an_owner_request_not_a_task { + var p : Panel = quiet(); + p.blocked = bit(11); + // the gap touches a gate file: a request, even with all agent slots busy (it starts no agent) + const d : u32 = next_action(p, 0, true, true, MAX_AGENTS, MAX_PER_HOUR, bit(11), F_GATE); + assert dec_action(d) == D_REQUEST and dec_letter(d) == 11 and dec_kind(d) == G_BLOCKED; + assert dec_action(next_action(p, 0, true, true, 0, 0, bit(11), F_SECRET)) == D_REQUEST; + assert dec_action(next_action(p, 0, true, true, 0, 0, bit(11), F_PAYMENT)) == D_REQUEST; + assert dec_action(next_action(p, 0, true, true, 0, 0, bit(11), F_PUBLIC)) == D_REQUEST; + assert dec_action(next_action(p, 0, true, true, 0, 0, bit(11), F_KEYS)) == D_REQUEST; + // a risk on another letter, or no class, stays a task + assert dec_action(next_action(p, 0, true, true, 0, 0, bit(4), F_GATE)) == D_TASK; + assert dec_action(next_action(p, 0, true, true, 0, 0, bit(11), 0)) == D_TASK; + // still never when off or closed + assert next_action(p, 0, false, true, 0, 0, bit(11), F_GATE) == D_NONE; + assert next_action(p, 0, true, false, 0, 0, bit(11), F_GATE) == D_NONE; + assert touches_forbidden(0) == false and touches_forbidden(F_KEYS); +} + +test every_action_is_logged_as_the_queens_own { + const t : u32 = dec_pack(D_TASK, G_STALE, 12); + const e : u64 = log_event(t, 1234); + assert by_queen(ev_control(e)) and ev_control(e) == C_QUEEN_STARTED; + assert ev_arg(e) == 12 and ev_sec(e) == 1234; + const r : u64 = log_event(dec_pack(D_REQUEST, G_RED, 2), 99); + assert by_queen(ev_control(r)) and ev_control(r) == C_QUEEN_ASKED and ev_arg(r) == 2; + assert log_event(D_NONE, 5) == 0; + assert dec_action(t) == D_TASK and dec_kind(t) == G_STALE and dec_letter(t) == 12; +} + +test the_lane_fits_the_pty_cap_and_lands_only_by_pr { + assert LANE_AGENT == 9; + assert (LANE_DIR.len as u32) == 22 and (BRANCH_HEAD.len as u32) == 14; + assert lane_path_len(3, 1) == 22 + 3 and branch_len(3, 1) == 14 + 3; + assert lane_path_len(27, 100) == 22 + 2 + 1 + 3; + // the worst case (letter ti, three digits) stays under 200 bytes, both lines + assert agent_cmd_len(3, 1) == 27 + 25 + 10 + 25 + 23; + assert fits_pty(agent_cmd_len(27, 999)) and fits_pty(worktree_cmd_len(27, 999)); + assert agent_cmd_len(27, 999) < PTY_CAP; + // the cap really bites: a 201-byte line does not fit + assert fits_pty(PTY_CAP) and fits_pty(PTY_CAP + 1) == false; + assert may_push_master() == false; + assert lands_by_pr(true, true) and lands_by_pr(false, true) == false and lands_by_pr(true, false) == false; + assert (letter_char(27).len as u32) == 2 and (letter_char(0).len as u32) == 1; +} diff --git a/specs/t27os/fanout.t27 b/specs/t27os/fanout.t27 new file mode 100644 index 0000000000..f633eab0c4 --- /dev/null +++ b/specs/t27os/fanout.t27 @@ -0,0 +1,1087 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/t27os/fanout.t27 -- actors start actors: a lane splits a task, runs the parts in parallel, gathers the answers. +; Owner, 2026-10-20: "actors must be able to start other actors when a task gets more complex and something has to be +; done in parallel; the answers must be gathered back to the actor/agent that started the task. So we can parallelize +; decomposed GitHub issues and solve them faster. Look how Claude Code visualizes it." Closes #11275. ASCII only (L3), +; English only (LANG-EN). A sibling of kernel.t27 (which stays the tree and the resource formula); this file is the fan. +; -- +; DRY: nothing is restated. Links, monitors, exit signals, mailboxes and edits_meet are actors.t27 (used); the lane +; supervisor's restart decision, the global cap, the hold, the mailbox caps and the tree/state/view constants are +; kernel.t27 (used); disjointness of edits, the landing pass (gc_land) and the ready test are golden_chain.t27 (used); +; the 4-heading issue shape is evolve.t27 (used); the traffic light codes are keys.t27 (used); the packer is dash.t27 +; (used, read only). New here: the spawn gate, the decomposition rule, the RESULT message, the join rule, the view. +; -- +; SPAWN BY MONITOR, NOT BY LINK. A child is started under the lanes supervisor (kernel.t27: transient, restarted until +; MaxR) and MONITORED by the parent. A link would kill the parent when the child crashes (on_exit_signal(X_CRASH, +; non-trapping) = ACT_DIE) and the parent would lose every sibling's work; a monitor sends a DOWN message and nothing +; else happens to the parent (MONITOR_KILLS_WATCHER = false). The parent therefore survives a child crash and receives +; the child's end as a message. A crash the supervisor heals (KD_RESTART) is invisible to the parent; the parent sees +; only the child's FINAL end: a RESULT message, or a DOWN with no RESULT. +; -- +; THE FAN, in order: spawn_admit (limits and capacity) -> decompose (disjoint path sets) -> put_* (one sub-issue text +; per child, "Part of #N", never a closing keyword) -> each child sends RESULT to the parent's mailbox -> join_decide +; (all done -> land; failed -> retry, bounded; forbidden or too many failures or timeout -> owner request) -> +; fanin_land (gc_land, children in index order, so the order does not depend on who finished first). + +module T27osFanout; + +use queen::actors::MBOX_SLOTS; +use queen::actors::mbox_push; +use queen::actors::mbox_tag; +use queen::actors::mbox_len; +use queen::actors::mbox_remove; +use queen::actors::X_NORMAL; +use queen::actors::X_SHUTDOWN; +use queen::actors::X_CRASH; +use queen::actors::X_KILL; +use queen::actors::X_NONE; +use queen::actors::ACT_DIE; +use queen::actors::on_exit_signal; +use queen::actors::MONITOR_KILLS_WATCHER; +use queen::actors::attach_answer; +use queen::actors::monitors_after_monitor; +use queen::actors::TURN_MAX_SECONDS; +use queen::actors::hung_turn_signal; +use queen::actors::D_QUEUED; +use queen::golden_chain::GC_STAGES; +use queen::golden_chain::GC_V_PASS; +use queen::golden_chain::GC_EDIT_DERIVED; +use queen::golden_chain::GC_PAIR_REGEN; +use queen::golden_chain::GC_PAIR_MEET; +use queen::golden_chain::GC_BATCH_MAX; +use queen::golden_chain::GC_OUT_FORWARD; +use queen::golden_chain::GC_OUT_CARRIED; +use queen::golden_chain::GC_OUT_REGEN; +use queen::golden_chain::GC_OUT_CONFLICT; +use queen::golden_chain::gc_pair; +use queen::golden_chain::gc_land; +use queen::golden_chain::gc_landed; +use queen::golden_chain::lane_ready; +use t27os::kernel::LANES_MAX_R; +use t27os::kernel::KD_RESTART; +use t27os::kernel::KD_OWNER_REQUEST; +use t27os::kernel::KN_LANE; +use t27os::kernel::K_STOP; +use t27os::kernel::K_TASK; +use t27os::kernel::lane_decision; +use t27os::kernel::may_spawn; +use t27os::kernel::kernel_deliver; +use t27os::kernel::kernel_lane; +use t27os::kernel::min2; +use t27os::kernel::NS_RUNNING; +use t27os::kernel::NS_RESTARTING; +use t27os::kernel::NS_HELD; +use t27os::kernel::NS_STOPPED; +use t27os::kernel::NS_ESCALATED; +use t27os::kernel::KV_ROW_H; +use t27os::kernel::KV_INDENT; +use t27os::evolve::heading; +use t27os::evolve::LANE_DIR; +use t27os::keys::L_NONE; +use t27os::keys::L_GREEN; +use t27os::keys::L_YELLOW; +use t27os::keys::L_RED; +use t27os::dash::pack_cols; +use t27os::dash::cell_w; +use t27os::dash::dash_cell_x; +use t27os::dash::dash_cell_y; +use t27os::dash::pack_h; +use t27os::dash::pack_at; +use t27os::dash::GAP; +use t27os::session_store::put_bytes; +use t27os::session_store::put_dec; + +; --- 1. Fan-out: the spawn gate -------------------------------------------------------------------------------- + +/// Limits. Chosen, not measured. A top-level lane has depth 0; its child depth 1; FAN_DEPTH_MAX = 2 allows a +/// grandchild (a part that is itself too big) and no more. A parent has at most FAN_CHILDREN_MAX children in all +/// (retries reuse a child's place): the same 8 as one landing pass (GC_BATCH_MAX) and one actors.t27 model queue +/// (MBOX_SLOTS), so the waiting children always fit the parent's queue. At most FAN_LIVE_MAX run at once: the global +/// agent_cap (kernel.t27) binds first on a small machine; this binds one parent on a big one, so a single issue does +/// not take the whole machine. +pub const FAN_DEPTH_MAX : u32 = 2; +pub const FAN_CHILDREN_MAX : u32 = 8; +pub const FAN_LIVE_MAX : u32 = 4; +/// A parent whose children all wait for capacity and none runs would wait for lanes it holds itself (cap 1: the +/// parent IS the only lane). After this long it does the part itself, one after the other. +pub const FAN_SELF_WAIT_SECONDS : u32 = 600; + +pub const SP_START : u32 = 0; +pub const SP_WAIT : u32 = 1; +pub const SP_REFUSE_DEPTH : u32 = 2; +pub const SP_REFUSE_COUNT : u32 = 3; +pub const SP_SELF : u32 = 4; + +/// May the parent at `parent_depth` start one more child now. `spawned`: children it has made so far. `live`: of +/// those, running now. `running`/`cap`/`hold`: the global lane count, agent_cap and spawn_hold of kernel.t27. +/// `waited`: seconds the next child has waited for capacity. A child that does not fit WAITS in the parent's queue +/// (fan_queue_*), it is not dropped. +pub fn spawn_admit(parent_depth: u32, spawned: u32, live: u32, running: u32, cap: u32, hold: u32, waited: u32) -> u32 { + if (parent_depth + 1 > FAN_DEPTH_MAX) { return SP_REFUSE_DEPTH; } + if (spawned >= FAN_CHILDREN_MAX) { return SP_REFUSE_COUNT; } + if (live >= FAN_LIVE_MAX) { return SP_WAIT; } + if (may_spawn(cap, running, hold) == false) { + if (live == 0 and running >= cap and waited >= FAN_SELF_WAIT_SECONDS) { return SP_SELF; } + return SP_WAIT; + } + return SP_START; +} + +/// The parent is a plain (non-trapping) watcher: a link would kill it, a monitor does not. +pub fn child_attach_kills_parent(by_link: bool, reason: u8) -> bool { + if (by_link) { return on_exit_signal(reason, false) == ACT_DIE; } + return MONITOR_KILLS_WATCHER; +} + +/// The parent's queue of children that did not fit: the actors.t27 model mailbox, subtask j stored as tag j + 1, +/// oldest first. fan_queue_head is 0 for an empty queue, else the subtask + 1 to start next. +pub fn fan_queue_push(box: u64, subtask: u32) -> u64 { return mbox_push(box, @as(u64, subtask) + 1); } +pub fn fan_queue_head(box: u64) -> u32 { return @as(u32, @intCast(mbox_tag(box, 0))); } +pub fn fan_queue_pop(box: u64) -> u64 { return mbox_remove(box, 0); } +pub fn fan_queue_len(box: u64) -> u32 { return @as(u32, @intCast(mbox_len(box))); } + +; --- 2. Decomposition of an issue by its Boundary -------------------------------------------------------------- + +/// The Boundary is n entries: path hash, kind (EDIT_*, or GC_EDIT_DERIVED for a path the chain recomputes), the +/// region (lo, hi) of a line range, and for a derived path the hash of the source it is computed from (0 if none). +/// Split rule, in this order: +/// 1. ONE PATH, ONE CHILD: every entry of a path goes to the same child, even two line ranges that edits_meet would +/// let run apart. The owner asked for disjoint PATH sets; the finer split stays with file_admit (actors.t27). +/// 2. A DERIVED path follows its source: if the source is in the Boundary the derived entry joins its child (the +/// child regenerates it with the spec); if not, it is SHARED (FAN_SHARED): no child owns it, the parent +/// recomputes it after the join, which is the REGEN carry of gc_land. +/// 3. Groups are dealt to at most maxc children (maxc <= FAN_CHILDREN_MAX), each to the child with the fewest +/// entries so far, the lowest index on a tie: deterministic, and as even as the groups allow. +pub const FAN_SHARED : u8 = 255; +pub const FAN_PATHS_MAX : usize = 32; + +fn source_in(n: usize, path: []const u64, kind: []const u8, s: u64) -> bool { + var k : usize = 0; + while (k < n) { + if (path[k] == s and kind[k] != GC_EDIT_DERIVED) { return true; } + k = k + 1; + } + return false; +} + +fn fan_key(i: usize, path: []const u64, kind: []const u8, src: []const u64) -> u64 { + if (kind[i] == GC_EDIT_DERIVED) { return src[i]; } + return path[i]; +} + +/// Fills child[i] (the child index of entry i, or FAN_SHARED). `load` is scratch of FAN_CHILDREN_MAX u32. +/// Returns how many children there are (1 means: do not split). +pub fn decompose(n: usize, path: []const u64, kind: []const u8, src: []const u64, maxc: u32, child: []u8, load: []u32) -> u32 { + const mc : u32 = min2(maxc, FAN_CHILDREN_MAX); + var z : usize = 0; + while (z < 8) { load[z] = 0; z = z + 1; } + var groups : u32 = 0; + var i : usize = 0; + while (i < n and i < FAN_PATHS_MAX) { + if (kind[i] == GC_EDIT_DERIVED and source_in(n, path, kind, src[i]) == false) { + child[i] = FAN_SHARED; + } else { + const key : u64 = fan_key(i, path, kind, src); + var found : bool = false; + var j : usize = 0; + while (j < i) { + if (found == false and child[j] != FAN_SHARED and fan_key(j, path, kind, src) == key) { + child[i] = child[j]; + found = true; + } + j = j + 1; + } + if (found == false) { + groups = groups + 1; + var c : usize = 0; + var b : usize = 1; + while (b < @as(usize, mc)) { + if (load[b] < load[c]) { c = b; } + b = b + 1; + } + child[i] = @as(u8, @intCast(c)); + } + load[@as(usize, child[i])] = load[@as(usize, child[i])] + 1; + } + i = i + 1; + } + return min2(groups, mc); +} + +/// The invariant of a split: the number of entry pairs in DIFFERENT children that name the same path and are not +/// both derived (those are carried by REGEN). Must be 0. Uses gc_pair for the derived case. +pub fn fan_overlaps(n: usize, path: []const u64, kind: []const u8, lo: []const u32, hi: []const u32, child: []const u8) -> u32 { + var bad : u32 = 0; + var a : usize = 0; + while (a < n) { + var b : usize = a + 1; + while (b < n) { + if (child[a] != FAN_SHARED and child[b] != FAN_SHARED and child[a] != child[b] and path[a] == path[b]) { + if (gc_pair(path[a], kind[a], lo[a], hi[a], path[b], kind[b], lo[b], hi[b]) != GC_PAIR_REGEN) { bad = bad + 1; } + } + b = b + 1; + } + a = a + 1; + } + return bad; +} + +/// Entries per child, for the view and the tests. +pub fn child_entries(n: usize, child: []const u8, c: u32) -> u32 { + var k : u32 = 0; + var i : usize = 0; + while (i < n) { + if (@as(u32, child[i]) == c and child[i] != FAN_SHARED) { k = k + 1; } + i = i + 1; + } + return k; +} + +/// The sub-issue text of child c (0-based) of `of`, in the 4-heading shape of evolve.t27 (heading(0..3)). The host +/// writes the paths between the head and the tail with put_path_line. "Part of #N" names the parent; the text +/// holds NO closing keyword (a closing keyword would close the parent's issue when a child lands). +pub fn put_title(out: []u8, at: usize, parent: u64, c: u32, of: u32) -> usize { + var n : usize = put_bytes(out, at, "t27os fanout: part "); + n = put_dec(out, n, @as(u64, c + 1)); + n = put_bytes(out, n, " of "); + n = put_dec(out, n, @as(u64, of)); + n = put_bytes(out, n, " of #"); + return put_dec(out, n, parent); +} + +pub fn put_body_head(out: []u8, at: usize, parent: u64, c: u32, of: u32) -> usize { + var n : usize = put_bytes(out, at, "Part of #"); + n = put_dec(out, n, parent); + n = put_bytes(out, n, " -- child "); + n = put_dec(out, n, @as(u64, c + 1)); + n = put_bytes(out, n, " of "); + n = put_dec(out, n, @as(u64, of)); + n = put_bytes(out, n, ", run in parallel with its siblings.\n\n## "); + n = put_bytes(out, n, heading(0)); + return put_bytes(out, n, "\n"); +} + +pub fn put_path_line(out: []u8, at: usize, p: []const u8) -> usize { + var n : usize = put_bytes(out, at, "- "); + n = put_bytes(out, n, p); + return put_bytes(out, n, "\n"); +} + +pub fn put_body_tail(out: []u8, at: usize, parent: u64) -> usize { + var n : usize = put_bytes(out, at, "\n## "); + n = put_bytes(out, n, heading(1)); + n = put_bytes(out, n, "\nThe lane of #"); + n = put_dec(out, n, parent); + n = put_bytes(out, n, " split its Boundary into disjoint path sets and started one lane per part.\n\n## "); + n = put_bytes(out, n, heading(2)); + n = put_bytes(out, n, "\nDo this part only and touch no path outside the Boundary above. Part of #"); + n = put_dec(out, n, parent); + n = put_bytes(out, n, ". When done, send the RESULT message to the parent lane.\n\n## "); + n = put_bytes(out, n, heading(3)); + return put_bytes(out, n, "\nTests pass with no vacuous test, seals refreshed, Only t27, DRY; the branch is golden from SPEC to RECEIPT so the parent can land it.\n"); +} + +/// The guard a skeleton must pass: no word that starts with close, fix or resolv (any case) anywhere in it. +pub fn closing_keyword_in(buf: []const u8) -> bool { + var i : usize = 0; + while (i + 3 <= buf.len) { + const a : u8 = buf[i] | 32; + const b : u8 = buf[i + 1] | 32; + const c : u8 = buf[i + 2] | 32; + if (a == 102 and b == 105 and c == 120) { return true; } + if (i + 5 <= buf.len and a == 99 and b == 108 and c == 111 and (buf[i + 3] | 32) == 115 and (buf[i + 4] | 32) == 101) { return true; } + if (i + 6 <= buf.len and a == 114 and b == 101 and c == 115 and (buf[i + 3] | 32) == 111 and (buf[i + 4] | 32) == 108 and (buf[i + 5] | 32) == 118) { return true; } + i = i + 1; + } + return false; +} + +/// Where a child works: branch claude/fan--, worktree evolve.t27 LANE_DIR + fan--. +pub fn put_child_branch(out: []u8, at: usize, parent: u64, c: u32) -> usize { + var n : usize = put_bytes(out, at, "claude/fan-"); + n = put_dec(out, n, parent); + n = put_bytes(out, n, "-"); + return put_dec(out, n, @as(u64, c + 1)); +} + +pub fn put_child_worktree(out: []u8, at: usize, parent: u64, c: u32) -> usize { + var n : usize = put_bytes(out, at, LANE_DIR); + n = put_bytes(out, n, "fan-"); + n = put_dec(out, n, parent); + n = put_bytes(out, n, "-"); + return put_dec(out, n, @as(u64, c + 1)); +} + +; --- 3. Fan-in: the RESULT message and the join ----------------------------------------------------------------- + +/// Kernel message tags beside K_TASK (1), K_STOP (8), K_ESCALATE (9): both ride the data lane, after any control tag. +pub const K_RESULT : u64 = 10; +pub const K_DOWN : u64 = 11; + +/// A child's status in its RESULT. +pub const ST_DONE : u32 = 0; +pub const ST_FAILED : u32 = 1; +pub const ST_FORBIDDEN : u32 = 2; + +pub const SUMMARY_MAX : u32 = 2047; + +/// The RESULT, in one u64: status (2 bits) | link verdicts SPEC..RECEIPT (2 bits each, GC_V_*, 10 bits) << 2 | +/// summary length in bytes (11) << 12 | files changed (10) << 23 | lines added (14) << 33 | lines removed (14) << 47. +/// The branch (its head id) and the summary TEXT travel beside it in the store row; the length is counted here so +/// the parent can bound what it reads. Fields clamp at their width. +pub fn res_pack(status: u32, verdicts: u32, summary: u32, files: u32, plus: u32, minus: u32) -> u64 { + return @as(u64, status & 3) | (@as(u64, verdicts & 1023) << 2) | (@as(u64, min2(summary, SUMMARY_MAX)) << 12) | (@as(u64, min2(files, 1023)) << 23) | (@as(u64, min2(plus, 16383)) << 33) | (@as(u64, min2(minus, 16383)) << 47); +} + +pub fn res_status(r: u64) -> u32 { return @as(u32, @intCast(r & 3)); } +pub fn res_verdicts(r: u64) -> u32 { return @as(u32, @intCast((r >> 2) & 1023)); } +pub fn res_summary(r: u64) -> u32 { return @as(u32, @intCast((r >> 12) & 2047)); } +pub fn res_files(r: u64) -> u32 { return @as(u32, @intCast((r >> 23) & 1023)); } +pub fn res_plus(r: u64) -> u32 { return @as(u32, @intCast((r >> 33) & 16383)); } +pub fn res_minus(r: u64) -> u32 { return @as(u32, @intCast((r >> 47) & 16383)); } +/// Verdict of chain stage s (0 SPEC .. 4 RECEIPT). +pub fn res_verdict(r: u64, s: u32) -> u32 { return (res_verdicts(r) >> (2 * s)) & 3; } + +/// All verdicts passing (GC_V_PASS) as a packed value; the golden chain's "every stage golden". +pub fn verdicts_all_pass() -> u32 { + var v : u32 = 0; + var s : u32 = 0; + while (s < @as(u32, GC_STAGES)) { v = v | (@as(u32, GC_V_PASS) << (2 * s)); s = s + 1; } + return v; +} + +/// A RESULT that says DONE but whose chain is not golden is not done: fail closed to FAILED. golden_chain.t27 +/// lane_ready is the test (every stage golden, in order). +pub fn res_effective(r: u64) -> u32 { + if (res_status(r) != ST_DONE) { return res_status(r); } + var v : [5]u8 = undefined; + var s : u32 = 0; + while (s < 5) { v[@as(usize, s)] = @as(u8, @intCast(res_verdict(r, s))); s = s + 1; } + if (lane_ready(&v, 0)) { return ST_DONE; } + return ST_FAILED; +} + +/// The parent's mailbox is a lane mailbox (kernel.t27 LANE_MBOX): a RESULT into a full one is a dead letter, and +/// the child's DOWN still arrives, so the parent sees an end with no RESULT and treats it as FAILED (retry). +pub fn result_delivery(reached: bool, queued: u32) -> u8 { return kernel_deliver(KN_LANE, reached, queued); } + +/// What a child's FINAL end means to the parent (a DOWN, plus the RESULT if one was delivered). +pub const CE_RUNNING : u32 = 0; +pub const CE_DONE : u32 = 1; +pub const CE_FAILED : u32 = 2; +pub const CE_FORBIDDEN : u32 = 3; +pub const CE_REQUEUE : u32 = 4; + +/// `earlier`: restarts the lanes supervisor already made inside its window. A crash it will still restart is not an +/// end (CE_RUNNING). A shed (X_SHUTDOWN, kernel.t27 shedding) is not a failure: the child goes back to the queue and +/// no try is spent. A crash whose restart budget is spent, a normal end with no RESULT, and a RESULT that is not +/// golden are FAILED; FORBIDDEN is a RESULT of that status. +pub fn child_end(reason: u8, earlier: u32, got_result: bool, result: u64) -> u32 { + if (reason == X_SHUTDOWN) { return CE_REQUEUE; } + if (reason != X_NORMAL) { + if (lane_decision(reason, earlier) == KD_RESTART) { return CE_RUNNING; } + return CE_FAILED; + } + if (got_result == false) { return CE_FAILED; } + const s : u32 = res_effective(result); + if (s == ST_DONE) { return CE_DONE; } + if (s == ST_FORBIDDEN) { return CE_FORBIDDEN; } + return CE_FAILED; +} + +/// Child slot states in the parent. +pub const CS_QUEUED : u8 = 0; +pub const CS_RUNNING : u8 = 1; +pub const CS_DONE : u8 = 2; +pub const CS_FAILED : u8 = 3; +pub const CS_FORBIDDEN : u8 = 4; +pub const CS_NAMES : [5]str = ["QUEUED", "RUNNING", "DONE", "FAILED", "FORBIDDEN"]; + +/// Bounded retries: a failed child is started again at most this often (its place in the fan is reused). +pub const FAN_RETRY_MAX : u32 = 2; +/// The join waits at most two turns (a retry gets a second one) before it asks the owner. Chosen, not measured. +pub const FAN_JOIN_TIMEOUT_SECONDS : u32 = 2 * TURN_MAX_SECONDS; + +/// A slot after an end: DONE, FAILED, FORBIDDEN, back to QUEUED (a shed), or unchanged while the supervisor heals it. +pub fn slot_after_end(state: u8, ce: u32) -> u8 { + if (ce == CE_DONE) { return CS_DONE; } + if (ce == CE_FAILED) { return CS_FAILED; } + if (ce == CE_FORBIDDEN) { return CS_FORBIDDEN; } + if (ce == CE_REQUEUE) { return CS_QUEUED; } + return state; +} + +/// A slot after a retry decision: back in the queue, one try spent. +pub fn tries_after_retry(tries: u32) -> u32 { return tries + 1; } + +pub fn count_state(n: usize, st: []const u8, s: u8) -> u32 { + var k : u32 = 0; + var i : usize = 0; + while (i < n) { + if (st[i] == s) { k = k + 1; } + i = i + 1; + } + return k; +} + +/// What the parent does at a join. Partial results are always visible (count_state, the RESULT words): the parent +/// reads them while it waits, and the view draws them. +/// JA_WAIT some child still queued or running, inside the timeout. +/// JA_LAND every child DONE: the parent continues and lands the branches (fanin_land). +/// JA_RETRY a child FAILED with tries left: start it again (join_retry_which says which). +/// JA_ASK the owner is asked (Queen's owner request, kernel.t27 KD_OWNER_REQUEST): a child FORBIDDEN, a child +/// FAILED with no tries left, or the timeout. Retries of other children go on meanwhile. +/// Precedence: ask, then retry, then land, then wait. The default if the request goes unanswered is owner_default. +pub const JA_WAIT : u32 = 0; +pub const JA_LAND : u32 = 1; +pub const JA_RETRY : u32 = 2; +pub const JA_ASK : u32 = 3; + +pub fn join_decide(n: usize, st: []const u8, tries: []const u8, age_seconds: u32) -> u32 { + var asks : bool = false; + var retry : bool = false; + var open : u32 = 0; + var i : usize = 0; + while (i < n) { + if (st[i] == CS_FORBIDDEN) { asks = true; } + if (st[i] == CS_FAILED) { + if (@as(u32, tries[i]) >= FAN_RETRY_MAX) { asks = true; } else { retry = true; } + } + if (st[i] == CS_QUEUED or st[i] == CS_RUNNING) { open = open + 1; } + i = i + 1; + } + if (asks) { return JA_ASK; } + if (retry) { return JA_RETRY; } + if (open == 0 and n > 0) { return JA_LAND; } + if (age_seconds >= FAN_JOIN_TIMEOUT_SECONDS) { return JA_ASK; } + return JA_WAIT; +} + +/// The lowest child that FAILED with tries left, or n. +pub fn join_retry_which(n: usize, st: []const u8, tries: []const u8) -> usize { + var i : usize = 0; + while (i < n) { + if (st[i] == CS_FAILED and @as(u32, tries[i]) < FAN_RETRY_MAX) { return i; } + i = i + 1; + } + return n; +} + +/// The default of an unanswered owner request (kernel.t27 REQUEST_DEFAULT_SECONDS, then it stands): the children +/// that are DONE land (their paths are disjoint, so nothing waits on the others) and the parent issue stays OPEN with +/// the rest as sub-issues in the backlog; with none done, the task goes back to the backlog. +pub const FD_LAND_PARTIAL : u32 = 0; +pub const FD_BACKLOG : u32 = 1; + +pub fn owner_default(done: u32) -> u32 { + if (done > 0) { return FD_LAND_PARTIAL; } + return FD_BACKLOG; +} + +/// The landing pass of a finished join: gc_land over the children in INDEX order (never in arrival order, so two +/// runs of one fan land the same way). Every child was built on `base`. Edits are the Boundary entries with +/// owner = child[i] (shared entries have owner n_children, a link that does not exist, and are left to the parent's +/// own regeneration). out[c] says what happened to child c (GC_OUT_*), pos[c] its place in the landing order. +/// Returns how many landed. At most FAN_CHILDREN_MAX children. +pub fn fanin_land(nc: usize, id: []const u64, res: []const u64, base: u64, m: usize, e_owner: []const usize, e_path: []const u64, e_kind: []const u8, e_lo: []const u32, e_hi: []const u32, out: []u8, pos: []usize) -> usize { + var parent : [8]u64 = [8]u64{ 0, 0, 0, 0, 0, 0, 0, 0 }; + var v : [40]u8 = undefined; + var io : [1]usize = [1]usize{ 0 }; + var ip : [1]u64 = [1]u64{ 0 }; + var c : usize = 0; + while (c < nc and c < 8) { + parent[c] = base; + var s : usize = 0; + while (s < 5) { + v[c * 5 + s] = @as(u8, @intCast(res_verdict(res[c], @as(u32, @intCast(s))))); + s = s + 1; + } + c = c + 1; + } + return gc_land(nc, id, &parent, &v, m, e_owner, e_path, e_kind, e_lo, e_hi, 0, &io, &ip, base, out, pos); +} + +; --- 4. The view for the window ---------------------------------------------------------------------------------- +; Like Claude Code: a SIDEBAR TREE of lanes (a parent with its children nested under it, a branch glyph and a child +; count on the parent's row), the main pane the selected lane's transcript (the window's, not here), and a right +; INSPECTOR with Environment (machine, repo, branch, worktree), Context (parent task, sub-issue), Outputs (files +; written) and Changes (PR, lines, failing checks, ready). Colors and glyph drawing stay in the window; this section +; gives rows, order, packed fields and every x, y, w, h. The window's own lane list is `parent[]` (the lane index of +; the parent, FAN_NONE for a top-level lane; a parent always has a LOWER index than its children). + +pub const FAN_NONE : u32 = 255; +pub const LANES_VIEW_MAX : usize = 32; + +/// Depth-first order of the tree (a parent, then each child's subtree, siblings in index order). order[r] is the +/// lane drawn in row r; dep[i] the depth of lane i. Returns the row count (n). +pub fn tree_order(n: usize, parent: []const u32, order: []u32, dep: []u32) -> usize { + var count : usize = 0; + var i : usize = 0; + while (i < n and i < LANES_VIEW_MAX) { + if (parent[i] == FAN_NONE) { + dep[i] = 0; + order[count] = @as(u32, @intCast(i)); + count = count + 1; + } else { + const p : u32 = parent[i]; + dep[i] = dep[@as(usize, p)] + 1; + // after the last row of p's subtree: the rows after p with a greater depth + var at : usize = 0; + while (order[at] != p) { at = at + 1; } + at = at + 1; + while (at < count and dep[@as(usize, order[at])] > dep[@as(usize, p)]) { at = at + 1; } + var k : usize = count; + while (k > at) { order[k] = order[k - 1]; k = k - 1; } + order[at] = @as(u32, @intCast(i)); + count = count + 1; + } + i = i + 1; + } + return count; +} + +pub fn child_count(n: usize, parent: []const u32, i: u32) -> u32 { + var k : u32 = 0; + var j : usize = 0; + while (j < n) { + if (parent[j] == i) { k = k + 1; } + j = j + 1; + } + return k; +} + +/// The branch glyph of a row: GL_NONE for a top-level lane, GL_MID for a child with a later sibling, GL_LAST for the +/// last child of its parent. (A parent shows GL_NONE or its own place plus its child count.) +pub const GL_NONE : u32 = 0; +pub const GL_MID : u32 = 1; +pub const GL_LAST : u32 = 2; +pub const GLYPH_NAMES : [3]str = ["none", "mid", "last"]; + +pub fn branch_glyph(n: usize, parent: []const u32, i: u32) -> u32 { + const p : u32 = parent[@as(usize, i)]; + if (p == FAN_NONE) { return GL_NONE; } + var j : usize = @as(usize, i) + 1; + while (j < n) { + if (parent[j] == p) { return GL_MID; } + j = j + 1; + } + return GL_LAST; +} + +/// The light of a parent row: the worst of its own and its children's (RED over YELLOW over GREEN, keys.t27 order), +/// so a red child shows on the collapsed parent. `light` is the keys.t27 light_of value of each lane (an input). +pub fn row_light(n: usize, parent: []const u32, light: []const u32, i: u32) -> u32 { + var w : u32 = light[@as(usize, i)]; + var j : usize = 0; + while (j < n) { + if (parent[j] == i and light[j] > w) { w = light[j]; } + j = j + 1; + } + return w; +} + +/// One tree row in a u64: state NS_* (3 bits) | light (2) << 3 | glyph (2) << 5 | depth (2) << 7 | children (4) << 9 | +/// children done (4) << 13 | children failed (4) << 17 | children waiting (4) << 21 | restarts (4) << 25. +pub fn lv_pack(state: u32, light: u32, glyph: u32, depth: u32, kids: u32, done: u32, failed: u32, waiting: u32, restarts: u32) -> u64 { + return @as(u64, state & 7) | (@as(u64, light & 3) << 3) | (@as(u64, glyph & 3) << 5) | (@as(u64, depth & 3) << 7) | (@as(u64, min2(kids, 15)) << 9) | (@as(u64, min2(done, 15)) << 13) | (@as(u64, min2(failed, 15)) << 17) | (@as(u64, min2(waiting, 15)) << 21) | (@as(u64, min2(restarts, 15)) << 25); +} + +pub fn lv_state(v: u64) -> u32 { return @as(u32, @intCast(v & 7)); } +pub fn lv_light(v: u64) -> u32 { return @as(u32, @intCast((v >> 3) & 3)); } +pub fn lv_glyph(v: u64) -> u32 { return @as(u32, @intCast((v >> 5) & 3)); } +pub fn lv_depth(v: u64) -> u32 { return @as(u32, @intCast((v >> 7) & 3)); } +pub fn lv_kids(v: u64) -> u32 { return @as(u32, @intCast((v >> 9) & 15)); } +pub fn lv_done(v: u64) -> u32 { return @as(u32, @intCast((v >> 13) & 15)); } +pub fn lv_failed(v: u64) -> u32 { return @as(u32, @intCast((v >> 17) & 15)); } +pub fn lv_waiting(v: u64) -> u32 { return @as(u32, @intCast((v >> 21) & 15)); } +pub fn lv_restarts(v: u64) -> u32 { return @as(u32, @intCast((v >> 25) & 15)); } + +/// The state lamp of a lane from what the parent knows: the kernel.t27 node states. +pub fn lane_state(slot: u8, restarting: bool, escalated: bool) -> u32 { + if (escalated) { return NS_ESCALATED; } + if (slot == CS_QUEUED) { return NS_HELD; } + if (slot == CS_RUNNING and restarting) { return NS_RESTARTING; } + if (slot == CS_RUNNING) { return NS_RUNNING; } + if (slot == CS_DONE) { return NS_STOPPED; } + return NS_ESCALATED; +} + +/// The row of lane i, from the parent's slot arrays: `st`/`restart`/`esc` are per lane (a top-level lane passes +/// CS_RUNNING), `light` the keys.t27 light of each lane. Child counts come from the tree, outcomes from the slots. +pub fn lv_row(n: usize, parent: []const u32, dep: []const u32, st: []const u8, restarts: []const u32, esc: []const bool, light: []const u32, i: u32) -> u64 { + var done : u32 = 0; + var failed : u32 = 0; + var waiting : u32 = 0; + var j : usize = 0; + while (j < n) { + if (parent[j] == i) { + if (st[j] == CS_DONE) { done = done + 1; } + if (st[j] == CS_FAILED or st[j] == CS_FORBIDDEN) { failed = failed + 1; } + if (st[j] == CS_QUEUED) { waiting = waiting + 1; } + } + j = j + 1; + } + const ii : usize = @as(usize, i); + return lv_pack(lane_state(st[ii], restarts[ii] > 0 and st[ii] == CS_RUNNING, esc[ii]), row_light(n, parent, light, i), branch_glyph(n, parent, i), dep[ii], child_count(n, parent, i), done, failed, waiting, restarts[ii]); +} + +; ---- the inspector of the selected lane -------------------------------------------------------------------------- + +/// Sections and their fields. The host fills the TEXT of machine, repo, branch, worktree, parent task and sub-issue +/// (put_child_branch / put_child_worktree give the last two for a child); this file fixes the rows. +pub const IS_ENV : u32 = 0; +pub const IS_CONTEXT : u32 = 1; +pub const IS_OUTPUTS : u32 = 2; +pub const IS_CHANGES : u32 = 3; +pub const SECTION_NAMES : [4]str = ["Environment", "Context", "Outputs", "Changes"]; + +pub const IF_MACHINE : u32 = 0; +pub const IF_REPO : u32 = 1; +pub const IF_BRANCH : u32 = 2; +pub const IF_WORKTREE : u32 = 3; +pub const IF_PARENT_TASK : u32 = 4; +pub const IF_SUB_ISSUE : u32 = 5; +pub const IF_PR : u32 = 6; +pub const IF_LINES : u32 = 7; +pub const IF_CHECKS : u32 = 8; +pub const IF_READY : u32 = 9; +pub const FIELD_NAMES : [10]str = ["Computer", "Repo", "Branch", "Worktree", "Parent task", "Sub-issue", "PR", "Lines", "Failing checks", "Mark ready"]; + +/// Outputs lists the files written, at most this many rows and one "+N more" row. +pub const OUT_ROWS_MAX : u32 = 6; + +/// Rows of the inspector for a lane that wrote `files` files: 4 headers, 4 + 2 + 4 fields, the file rows. +pub fn ins_rows(files: u32) -> u32 { + var f : u32 = min2(files, OUT_ROWS_MAX); + if (files > OUT_ROWS_MAX) { f = f + 1; } + return 4 + 4 + 2 + 4 + f; +} + +/// What row r shows: 1000 + section for a header, a field IF_*, 200 + k for the k-th file, 299 for "+N more". +pub fn ins_code(r: u32, files: u32) -> u32 { + if (r == 0) { return 1000 + IS_ENV; } + if (r <= 4) { return r - 1; } + if (r == 5) { return 1000 + IS_CONTEXT; } + if (r <= 7) { return IF_PARENT_TASK + (r - 6); } + if (r == 8) { return 1000 + IS_OUTPUTS; } + const shown : u32 = min2(files, OUT_ROWS_MAX); + if (r < 9 + shown) { return 200 + (r - 9); } + var next : u32 = 9 + shown; + if (files > OUT_ROWS_MAX) { + if (r == next) { return 299; } + next = next + 1; + } + if (r == next) { return 1000 + IS_CHANGES; } + return IF_PR + (r - next - 1); +} + +/// Changes in a u64: PR number (24 bits) | lines added (14) << 24 | lines removed (14) << 38 | checks failing (8) << 52 +/// | ready (1) << 60. PR 0 = none yet. +pub fn ch_pack(pr: u64, plus: u32, minus: u32, failing: u32, ready: bool) -> u64 { + var r : u64 = pr & 16777215; + r = r | (@as(u64, min2(plus, 16383)) << 24) | (@as(u64, min2(minus, 16383)) << 38) | (@as(u64, min2(failing, 255)) << 52); + if (ready) { r = r | (@as(u64, 1) << 60); } + return r; +} + +pub fn ch_pr(c: u64) -> u64 { return c & 16777215; } +pub fn ch_plus(c: u64) -> u32 { return @as(u32, @intCast((c >> 24) & 16383)); } +pub fn ch_minus(c: u64) -> u32 { return @as(u32, @intCast((c >> 38) & 16383)); } +pub fn ch_failing(c: u64) -> u32 { return @as(u32, @intCast((c >> 52) & 255)); } +pub fn ch_ready(c: u64) -> bool { return ((c >> 60) & 1) == 1; } + +/// "Mark ready" is offered when a PR exists, no check fails, and the lane's chain is golden SPEC..RECEIPT +/// (golden_chain.t27 lane_ready on the verdicts of its RESULT). +pub fn may_mark_ready(pr: u64, failing: u32, result: u64) -> bool { + if (pr == 0 or failing > 0) { return false; } + return res_effective(result) == ST_DONE; +} + +; ---- geometry (dash.t27's packer) ---- + +pub const FV_PANE_MIN : f64 = 240.0; +pub const FV_SECTION_GAP : f64 = 6.0; + +fn ffl(x: u32) -> f64 { return @as(f64, @floatFromInt(x)); } + +/// Two panes, the tree and the inspector, side by side when each fits FV_PANE_MIN, stacked when not. +pub fn fv_cols(w: f64) -> u32 { return min2(pack_cols(w, FV_PANE_MIN, GAP), 2); } +pub fn fv_pane_w(w: f64) -> f64 { return cell_w(w, fv_cols(w), GAP); } +pub fn fv_pane_x(p: u32, w: f64) -> f64 { return dash_cell_x(p, w, fv_cols(w), GAP); } +/// The inspector pane sits below the tree when stacked; `tree_h` is fv_tree_h. +pub fn fv_pane_y(p: u32, w: f64, tree_h: f64) -> f64 { + if (p == 0 or fv_cols(w) == 2) { return 0.0; } + return tree_h + GAP; +} + +pub fn fv_row_x(dep: u32) -> f64 { return ffl(dep) * KV_INDENT; } +pub fn fv_row_y(r: u32) -> f64 { return dash_cell_y(r, 1, KV_ROW_H, GAP); } +pub fn fv_tree_h(rows: u32) -> f64 { return pack_h(rows, 1, KV_ROW_H, GAP); } +pub fn fv_ins_y(r: u32) -> f64 { return dash_cell_y(r, 1, KV_ROW_H, GAP); } +pub fn fv_ins_h(files: u32) -> f64 { return pack_h(ins_rows(files), 1, KV_ROW_H, GAP); } + +/// The lane row under (x, y) of the tree pane (origin at the pane), or FAN_NONE. A click left of the row's indent +/// hits nothing, as in kernel.t27 kv_at. +pub fn fv_tree_at(rows: u32, order: []const u32, dep: []const u32, w: f64, x: f64, y: f64) -> u32 { + const r : u32 = pack_at(rows, w, 1, KV_ROW_H, GAP, x, y); + if (r >= rows) { return FAN_NONE; } + const lane : u32 = order[@as(usize, r)]; + if (x < fv_row_x(dep[@as(usize, lane)])) { return FAN_NONE; } + return lane; +} + +; --- 5. Tests ------------------------------------------------------------------------------------------------------ + +test a_child_is_spawned_by_monitor_so_the_parent_survives_its_crash_and_a_link_would_kill_it { + assert child_attach_kills_parent(true, X_CRASH); + assert child_attach_kills_parent(false, X_CRASH) == false; + assert child_attach_kills_parent(true, X_NORMAL) == false; + assert MONITOR_KILLS_WATCHER == false; + assert monitors_after_monitor(2) == 3; + // monitoring a child that is already dead is answered at once, not silently + assert attach_answer(false) != X_NONE and attach_answer(true) == X_NONE; +} + +test the_spawn_gate_applies_depth_the_per_parent_limits_the_global_cap_and_the_hold { + // cap 3, parent is the 1 running lane + assert spawn_admit(0, 0, 0, 1, 3, 0, 0) == SP_START; + assert spawn_admit(1, 2, 1, 2, 3, 0, 0) == SP_START; + // depth: top lane 0 -> child 1 -> grandchild 2 -> no great-grandchild + assert spawn_admit(1, 0, 0, 2, 3, 0, 0) == SP_START; + assert spawn_admit(2, 0, 0, 2, 3, 0, 0) == SP_REFUSE_DEPTH; + // the per-parent total of 8 + assert spawn_admit(0, 7, 0, 1, 16, 0, 0) == SP_START; + assert spawn_admit(0, 8, 0, 1, 16, 0, 0) == SP_REFUSE_COUNT; + // a child that does not fit waits: cap reached, hold, or the parent already runs FAN_LIVE_MAX + assert spawn_admit(0, 2, 2, 3, 3, 0, 0) == SP_WAIT; + assert spawn_admit(0, 2, 1, 2, 3, 1, 0) == SP_WAIT; + assert spawn_admit(0, 5, 4, 5, 16, 0, 0) == SP_WAIT; + assert spawn_admit(0, 5, 3, 5, 16, 0, 0) == SP_START; + // depth is named before the count, the count before capacity + assert spawn_admit(2, 8, 4, 9, 3, 1, 0) == SP_REFUSE_DEPTH; + assert spawn_admit(1, 8, 4, 9, 3, 1, 0) == SP_REFUSE_COUNT; + // a parent that holds the only lane (cap 1) would wait for itself: after FAN_SELF_WAIT_SECONDS it does the part + assert spawn_admit(0, 0, 0, 1, 1, 0, 0) == SP_WAIT; + assert spawn_admit(0, 0, 0, 1, 1, 0, FAN_SELF_WAIT_SECONDS - 1) == SP_WAIT; + assert spawn_admit(0, 0, 0, 1, 1, 0, FAN_SELF_WAIT_SECONDS) == SP_SELF; + // never self while a child runs (one of them will free a place), never for a hold (that passes) + assert spawn_admit(0, 1, 1, 2, 2, 0, 9999) == SP_WAIT; + assert spawn_admit(0, 0, 0, 1, 3, 1, 9999) == SP_WAIT; + assert FAN_CHILDREN_MAX == GC_BATCH_MAX and FAN_CHILDREN_MAX <= MBOX_SLOTS; +} + +test children_that_do_not_fit_wait_in_the_parents_queue_oldest_first_and_start_as_places_free { + var q : u64 = 0; + q = fan_queue_push(q, 3); + q = fan_queue_push(q, 0); + q = fan_queue_push(q, 5); + assert fan_queue_len(q) == 3; + assert fan_queue_head(q) == 4; + q = fan_queue_pop(q); + assert fan_queue_head(q) == 1; + q = fan_queue_pop(q); + assert fan_queue_head(q) == 6 and fan_queue_len(q) == 1; + q = fan_queue_pop(q); + assert fan_queue_head(q) == 0 and fan_queue_len(q) == 0; + // all FAN_CHILDREN_MAX children fit the queue at once + var f : u64 = 0; + var k : u32 = 0; + while (k < FAN_CHILDREN_MAX) { f = fan_queue_push(f, k); k = k + 1; } + assert fan_queue_len(f) == FAN_CHILDREN_MAX; +} + +test an_eight_path_boundary_splits_into_four_children_with_disjoint_path_sets { + // 0 fanout.t27 | 1,2 kernel.t27 two ranges | 3 gen of fanout | 4 evolve.t27 | 5 gen of evolve | 6 shared seal index | 7 append-only log + var path : [8]u64 = [8]u64{ 10, 20, 20, 11, 30, 12, 99, 40 }; + var kind : [8]u8 = [8]u8{ 0, 1, 1, 3, 0, 3, 3, 2 }; + var src : [8]u64 = [8]u64{ 0, 0, 0, 10, 0, 30, 0, 0 }; + var lo : [8]u32 = [8]u32{ 0, 100, 600, 0, 0, 0, 0, 0 }; + var hi : [8]u32 = [8]u32{ 0, 140, 640, 0, 0, 0, 0, 0 }; + var child : [8]u8 = undefined; + var load : [8]u32 = undefined; + assert decompose(8, &path, &kind, &src, 4, &child, &load) == 4; + // one path, one child: both kernel.t27 ranges together, although edits_meet would let them run apart + assert child[1] == child[2]; + // a derived path follows its source + assert child[3] == child[0] and child[5] == child[4]; + // a derived path whose source is not in the Boundary is the parent's + assert child[6] == FAN_SHARED; + assert child[0] == 0 and child[1] == 1 and child[4] == 2 and child[7] == 3; + assert child_entries(8, &child, 0) == 2 and child_entries(8, &child, 1) == 2; + assert child_entries(8, &child, 2) == 2 and child_entries(8, &child, 3) == 1; + assert fan_overlaps(8, &path, &kind, &lo, &hi, &child) == 0; + // fewer children than groups: the extra group goes to the least loaded child, the lowest index on a tie + assert decompose(8, &path, &kind, &src, 3, &child, &load) == 3; + assert child[7] == 0; + assert fan_overlaps(8, &path, &kind, &lo, &hi, &child) == 0; + // maxc 1: no split at all + assert decompose(8, &path, &kind, &src, 1, &child, &load) == 1; + assert child_entries(8, &child, 0) == 7; + // one path only is one group: nothing to parallelize + var p1 : [2]u64 = [2]u64{ 5, 5 }; + var k1 : [2]u8 = [2]u8{ 1, 1 }; + var s1 : [2]u64 = [2]u64{ 0, 0 }; + var c1 : [2]u8 = undefined; + assert decompose(2, &p1, &k1, &s1, 4, &c1, &load) == 1; + // negative control: put the two kernel.t27 ranges in different children by hand and the invariant sees it + var bad : [8]u8 = [8]u8{ 0, 1, 2, 0, 2, 2, 255, 3 }; + assert fan_overlaps(8, &path, &kind, &lo, &hi, &bad) == 1; + // two derived entries of one path in two children are REGEN, not an overlap + var pd : [2]u64 = [2]u64{ 7, 7 }; + var kd : [2]u8 = [2]u8{ 3, 3 }; + var cd : [2]u8 = [2]u8{ 0, 1 }; + var zl : [2]u32 = [2]u32{ 0, 0 }; + assert fan_overlaps(2, &pd, &kd, &zl, &zl, &cd) == 0; +} + +test the_sub_issue_skeleton_is_four_headings_part_of_the_parent_and_never_a_closing_keyword { + var out : [900]u8 = undefined; + var n : usize = put_title(&out, 0, 11275, 1, 4); + assert n == 35; + assert out[0] == 116 and out[n - 1] == 53; + var t : usize = put_body_head(&out, 0, 11275, 1, 4); + t = put_path_line(&out, t, "specs/t27os/kernel.t27"); + t = put_body_tail(&out, t, 11275); + assert t > 400 and t < 900; + assert closing_keyword_in(out[0..t]) == false; + // "Part of #11275" opens it; the four headings are evolve.t27's, in order + assert out[0] == 80 and out[8] == 35 and out[9] == 49; + var c : u32 = 0; + var i : usize = 0; + while (i + 3 < t) { + if (out[i] == 10 and out[i + 1] == 35 and out[i + 2] == 35 and out[i + 3] == 32) { c = c + 1; } + i = i + 1; + } + assert c == 4; + // negative controls: the guard sees each closing keyword, in any case + assert closing_keyword_in("Closes #5") and closing_keyword_in("fixes #5") and closing_keyword_in("RESOLVED #5"); + assert closing_keyword_in("Part of #5 -- child 1 of 4") == false; + // the branch and worktree of a child + var b : [64]u8 = undefined; + assert put_child_branch(&b, 0, 11275, 0) == 18; + var w : [96]u8 = undefined; + assert put_child_worktree(&w, 0, 11275, 2) == 33; +} + +test a_result_packs_status_verdicts_summary_files_and_lines_and_a_not_golden_done_fails_closed { + const r : u64 = res_pack(ST_DONE, verdicts_all_pass(), 480, 7, 312, 41); + assert res_status(r) == ST_DONE and res_summary(r) == 480 and res_files(r) == 7; + assert res_plus(r) == 312 and res_minus(r) == 41; + assert res_verdict(r, 0) == GC_V_PASS and res_verdict(r, 4) == GC_V_PASS; + assert res_effective(r) == ST_DONE; + // DONE with the SEAL stage still unknown is not done + const half : u64 = res_pack(ST_DONE, verdicts_all_pass() - (1 << 4), 10, 1, 1, 1); + assert res_effective(half) == ST_FAILED; + assert res_effective(res_pack(ST_FORBIDDEN, 0, 0, 0, 0, 0)) == ST_FORBIDDEN; + assert res_effective(res_pack(ST_FAILED, verdicts_all_pass(), 0, 0, 0, 0)) == ST_FAILED; + // fields clamp, they do not spill into the next one + const c : u64 = res_pack(ST_DONE, verdicts_all_pass(), 99999, 99999, 99999, 99999); + assert res_summary(c) == SUMMARY_MAX and res_files(c) == 1023 and res_plus(c) == 16383 and res_minus(c) == 16383; + assert res_status(c) == ST_DONE; +} + +test the_result_rides_the_data_lane_into_a_bounded_parent_mailbox { + assert kernel_lane(K_RESULT) == kernel_lane(K_TASK); + assert kernel_lane(K_STOP) != kernel_lane(K_RESULT); + assert result_delivery(true, 0) == D_QUEUED; + assert result_delivery(true, 16) != D_QUEUED; + assert result_delivery(false, 0) != D_QUEUED; + // a lost RESULT leaves a normal DOWN with no result: FAILED, so the parent retries and nothing is lost silently + assert child_end(X_NORMAL, 0, false, 0) == CE_FAILED; +} + +test what_a_childs_end_means_a_healed_crash_is_invisible_a_shed_is_not_a_failure { + const ok : u64 = res_pack(ST_DONE, verdicts_all_pass(), 1, 1, 1, 1); + assert child_end(X_NORMAL, 0, true, ok) == CE_DONE; + assert child_end(X_NORMAL, 0, true, res_pack(ST_FORBIDDEN, 0, 0, 0, 0, 0)) == CE_FORBIDDEN; + assert child_end(X_NORMAL, 0, true, res_pack(ST_FAILED, 0, 0, 0, 0, 0)) == CE_FAILED; + // a crash the lanes supervisor still restarts is not an end + assert child_end(X_CRASH, 0, false, 0) == CE_RUNNING; + assert child_end(X_CRASH, LANES_MAX_R - 1, false, 0) == CE_RUNNING; + assert child_end(X_CRASH, LANES_MAX_R, false, 0) == CE_FAILED; + assert lane_decision(X_CRASH, LANES_MAX_R) == KD_OWNER_REQUEST; + // a shed child (kernel.t27 shedding) goes back to the queue with no try spent + assert child_end(X_SHUTDOWN, 0, false, 0) == CE_REQUEUE; + assert slot_after_end(CS_RUNNING, CE_REQUEUE) == CS_QUEUED; + assert slot_after_end(CS_RUNNING, CE_RUNNING) == CS_RUNNING; + assert slot_after_end(CS_RUNNING, CE_DONE) == CS_DONE; + assert hung_turn_signal(TURN_MAX_SECONDS - 1) == X_NONE and hung_turn_signal(TURN_MAX_SECONDS) == X_KILL; +} + +test three_children_all_done_means_land_in_child_order_whoever_finished_first { + var st : [3]u8 = [3]u8{ CS_RUNNING, CS_RUNNING, CS_RUNNING }; + var tr : [3]u8 = [3]u8{ 0, 0, 0 }; + assert join_decide(3, &st, &tr, 0) == JA_WAIT; + st[2] = CS_DONE; + assert join_decide(3, &st, &tr, 100) == JA_WAIT; + assert count_state(3, &st, CS_DONE) == 1 and count_state(3, &st, CS_RUNNING) == 2; + st[0] = CS_DONE; + st[1] = CS_DONE; + assert join_decide(3, &st, &tr, 200) == JA_LAND; + // the landing: children 0..2 edit disjoint paths, all built on head 1 + var id : [3]u64 = [3]u64{ 11, 12, 13 }; + var rs : [3]u64 = [3]u64{ res_pack(ST_DONE, verdicts_all_pass(), 5, 2, 30, 4), res_pack(ST_DONE, verdicts_all_pass(), 5, 1, 10, 0), res_pack(ST_DONE, verdicts_all_pass(), 5, 3, 99, 9) }; + var eo : [3]usize = [3]usize{ 0, 1, 2 }; + var ep : [3]u64 = [3]u64{ 100, 200, 300 }; + var ek : [3]u8 = [3]u8{ 0, 0, 0 }; + var el : [3]u32 = [3]u32{ 0, 0, 0 }; + var eh : [3]u32 = [3]u32{ 0, 0, 0 }; + var out : [3]u8 = undefined; + var pos : [3]usize = undefined; + assert fanin_land(3, &id, &rs, 1, 3, &eo, &ep, &ek, &el, &eh, &out, &pos) == 3; + assert out[0] == GC_OUT_FORWARD and out[1] == GC_OUT_CARRIED and out[2] == GC_OUT_CARRIED; + assert pos[0] == 0 and pos[1] == 1 and pos[2] == 2; + // the same fan again lands the same way: the pass takes child order, not arrival order + var out2 : [3]u8 = undefined; + var pos2 : [3]usize = undefined; + assert fanin_land(3, &id, &rs, 1, 3, &eo, &ep, &ek, &el, &eh, &out2, &pos2) == 3; + assert out2[0] == out[0] and out2[1] == out[1] and out2[2] == out[2] and pos2[2] == pos[2]; + // totals for the parent's summary line + assert res_files(rs[0]) + res_files(rs[1]) + res_files(rs[2]) == 6; +} + +test a_shared_derived_file_is_regenerated_and_a_split_that_overlaps_conflicts_at_the_landing { + var id : [2]u64 = [2]u64{ 11, 12 }; + var rs : [2]u64 = [2]u64{ res_pack(ST_DONE, verdicts_all_pass(), 1, 1, 1, 1), res_pack(ST_DONE, verdicts_all_pass(), 1, 1, 1, 1) }; + // both children recompute the derived file 500: REGEN + var eo : [4]usize = [4]usize{ 0, 0, 1, 1 }; + var ep : [4]u64 = [4]u64{ 100, 500, 200, 500 }; + var ek : [4]u8 = [4]u8{ 0, 3, 0, 3 }; + var el : [4]u32 = [4]u32{ 0, 0, 0, 0 }; + var eh : [4]u32 = [4]u32{ 0, 0, 0, 0 }; + var out : [2]u8 = undefined; + var pos : [2]usize = undefined; + assert fanin_land(2, &id, &rs, 1, 4, &eo, &ep, &ek, &el, &eh, &out, &pos) == 2; + assert out[1] == GC_OUT_REGEN and gc_landed(out[1]); + // negative control: the same SOURCE path in both children (a bad split) is a conflict, and nothing of it lands + var ep2 : [4]u64 = [4]u64{ 100, 500, 100, 500 }; + var out3 : [2]u8 = undefined; + var pos3 : [2]usize = undefined; + assert fanin_land(2, &id, &rs, 1, 4, &eo, &ep2, &ek, &el, &eh, &out3, &pos3) == 1; + assert out3[1] == GC_OUT_CONFLICT and gc_landed(out3[1]) == false; +} + +test one_crash_the_supervisor_gave_up_on_is_retried_by_the_parent_at_most_twice_then_the_owner_is_asked { + var st : [3]u8 = [3]u8{ CS_DONE, CS_RUNNING, CS_DONE }; + var tr : [3]u8 = [3]u8{ 0, 0, 0 }; + // the supervisor heals the first crashes: the parent sees nothing + assert child_end(X_CRASH, 1, false, 0) == CE_RUNNING; + assert join_decide(3, &st, &tr, 50) == JA_WAIT; + // its restart budget is spent: the end reaches the parent as FAILED + st[1] = slot_after_end(CS_RUNNING, child_end(X_CRASH, LANES_MAX_R, false, 0)); + assert st[1] == CS_FAILED; + assert join_decide(3, &st, &tr, 60) == JA_RETRY and join_retry_which(3, &st, &tr) == 1; + // retry: back in the queue, one try spent; the others stay done and are not redone + st[1] = CS_QUEUED; + tr[1] = @as(u8, @intCast(tries_after_retry(@as(u32, tr[1])))); + assert tr[1] == 1 and st[0] == CS_DONE and st[2] == CS_DONE; + assert join_decide(3, &st, &tr, 70) == JA_WAIT; + st[1] = CS_RUNNING; + st[1] = CS_DONE; + assert join_decide(3, &st, &tr, 900) == JA_LAND; + // second failure, second retry, third failure: no tries left, the owner is asked + st[1] = CS_FAILED; + assert join_decide(3, &st, &tr, 900) == JA_RETRY; + tr[1] = 2; + assert join_decide(3, &st, &tr, 900) == JA_ASK; + assert join_retry_which(3, &st, &tr) == 3; +} + +test a_forbidden_child_asks_the_owner_at_once_while_the_others_stay_visible_and_the_default_lands_the_done_ones { + var st : [3]u8 = [3]u8{ CS_DONE, CS_FORBIDDEN, CS_RUNNING }; + var tr : [3]u8 = [3]u8{ 0, 0, 0 }; + assert join_decide(3, &st, &tr, 10) == JA_ASK; + // partial results are visible while it waits + assert count_state(3, &st, CS_DONE) == 1 and count_state(3, &st, CS_RUNNING) == 1 and count_state(3, &st, CS_FORBIDDEN) == 1; + // forbidden is never retried + assert join_retry_which(3, &st, &tr) == 3; + // a failed sibling that still has tries is retried alongside the request + st[2] = CS_FAILED; + assert join_decide(3, &st, &tr, 10) == JA_ASK and join_retry_which(3, &st, &tr) == 2; + assert owner_default(1) == FD_LAND_PARTIAL; + assert owner_default(0) == FD_BACKLOG; +} + +test the_join_times_out_after_two_turns_and_asks_the_owner_and_an_empty_fan_never_lands { + var st : [2]u8 = [2]u8{ CS_DONE, CS_RUNNING }; + var tr : [2]u8 = [2]u8{ 0, 0 }; + assert join_decide(2, &st, &tr, FAN_JOIN_TIMEOUT_SECONDS - 1) == JA_WAIT; + assert join_decide(2, &st, &tr, FAN_JOIN_TIMEOUT_SECONDS) == JA_ASK; + assert FAN_JOIN_TIMEOUT_SECONDS == 7200; + // a queued child (waiting for capacity) keeps the join open too + st[1] = CS_QUEUED; + assert join_decide(2, &st, &tr, 5) == JA_WAIT; + assert join_decide(0, &st, &tr, 5) == JA_WAIT; + // timeout does not hide a retry + st[1] = CS_FAILED; + assert join_decide(2, &st, &tr, 99999) == JA_RETRY; +} + +test the_lane_tree_nests_children_under_their_parent_with_glyphs_counts_and_the_worst_light { + // lanes: 0 issue A (top) | 1 issue B (top) | 2,3,4 children of 0 | 5 grandchild of 3 + var parent : [6]u32 = [6]u32{ 255, 255, 0, 0, 0, 3 }; + var order : [6]u32 = undefined; + var dep : [6]u32 = undefined; + assert tree_order(6, &parent, &order, &dep) == 6; + // depth first: 0, 2, 3, 5, 4, then 1 + assert order[0] == 0 and order[1] == 2 and order[2] == 3 and order[3] == 5 and order[4] == 4 and order[5] == 1; + assert dep[0] == 0 and dep[2] == 1 and dep[5] == 2 and dep[1] == 0; + assert child_count(6, &parent, 0) == 3 and child_count(6, &parent, 3) == 1 and child_count(6, &parent, 1) == 0; + assert branch_glyph(6, &parent, 0) == GL_NONE and branch_glyph(6, &parent, 2) == GL_MID; + assert branch_glyph(6, &parent, 4) == GL_LAST and branch_glyph(6, &parent, 5) == GL_LAST; + // lights come from keys.t27: the parent shows its worst child + var light : [6]u32 = [6]u32{ L_GREEN, L_NONE, L_GREEN, L_RED, L_YELLOW, L_GREEN }; + assert row_light(6, &parent, &light, 0) == L_RED; + assert row_light(6, &parent, &light, 4) == L_YELLOW and row_light(6, &parent, &light, 1) == L_NONE; + // the row: counts of done, failed, waiting children and the lamp from the parent's slots + var st : [6]u8 = [6]u8{ CS_RUNNING, CS_RUNNING, CS_DONE, CS_FAILED, CS_QUEUED, CS_RUNNING }; + var rst : [6]u32 = [6]u32{ 0, 0, 0, 3, 0, 1 }; + var esc : [6]bool = [6]bool{ false, false, false, false, false, false }; + const row : u64 = lv_row(6, &parent, &dep, &st, &rst, &esc, &light, 0); + assert lv_kids(row) == 3 and lv_done(row) == 1 and lv_failed(row) == 1 and lv_waiting(row) == 1; + assert lv_state(row) == NS_RUNNING and lv_light(row) == L_RED and lv_depth(row) == 0 and lv_glyph(row) == GL_NONE; + assert lv_state(lv_row(6, &parent, &dep, &st, &rst, &esc, &light, 4)) == NS_HELD; + assert lv_state(lv_row(6, &parent, &dep, &st, &rst, &esc, &light, 2)) == NS_STOPPED; + assert lv_state(lv_row(6, &parent, &dep, &st, &rst, &esc, &light, 5)) == NS_RESTARTING; + assert lv_restarts(lv_row(6, &parent, &dep, &st, &rst, &esc, &light, 3)) == 3; + assert lv_glyph(lv_row(6, &parent, &dep, &st, &rst, &esc, &light, 4)) == GL_LAST; + assert lane_state(CS_FAILED, false, false) == NS_ESCALATED and lane_state(CS_RUNNING, false, true) == NS_ESCALATED; +} + +test the_inspector_has_environment_context_outputs_and_changes_rows_and_mark_ready_needs_a_golden_pr { + assert ins_rows(0) == 14 and ins_rows(3) == 17 and ins_rows(6) == 20 and ins_rows(9) == 21; + assert ins_code(0, 3) == 1000 + IS_ENV and ins_code(1, 3) == IF_MACHINE and ins_code(4, 3) == IF_WORKTREE; + assert ins_code(5, 3) == 1000 + IS_CONTEXT and ins_code(6, 3) == IF_PARENT_TASK and ins_code(7, 3) == IF_SUB_ISSUE; + assert ins_code(8, 3) == 1000 + IS_OUTPUTS and ins_code(9, 3) == 200 and ins_code(11, 3) == 202; + assert ins_code(12, 3) == 1000 + IS_CHANGES and ins_code(13, 3) == IF_PR and ins_code(16, 3) == IF_READY; + // no files: Changes follows the Outputs header at once; more than 6: a "+N more" row + assert ins_code(9, 0) == 1000 + IS_CHANGES and ins_code(13, 0) == IF_READY; + assert ins_code(15, 9) == 299 and ins_code(16, 9) == 1000 + IS_CHANGES and ins_code(20, 9) == IF_READY; + const c : u64 = ch_pack(11290, 312, 41, 2, false); + assert ch_pr(c) == 11290 and ch_plus(c) == 312 and ch_minus(c) == 41 and ch_failing(c) == 2 and ch_ready(c) == false; + assert ch_ready(ch_pack(5, 1, 1, 0, true)); + const ok : u64 = res_pack(ST_DONE, verdicts_all_pass(), 1, 1, 1, 1); + assert may_mark_ready(11290, 0, ok); + assert may_mark_ready(11290, 1, ok) == false and may_mark_ready(0, 0, ok) == false; + assert may_mark_ready(11290, 0, res_pack(ST_FAILED, verdicts_all_pass(), 1, 1, 1, 1)) == false; +} + +test the_layout_uses_the_dash_packer_two_panes_side_by_side_or_stacked_and_hit_tests_the_tree { + assert fv_cols(800.0) == 2 and fv_cols(400.0) == 1; + assert fv_pane_x(0, 800.0) == 0.0 and fv_pane_x(1, 800.0) > fv_pane_w(800.0); + assert fv_pane_y(1, 800.0, 100.0) == 0.0 and fv_pane_y(1, 400.0, 100.0) == 100.0 + GAP; + assert fv_row_y(0) == 0.0 and fv_row_y(2) == 2.0 * (KV_ROW_H + GAP); + assert fv_row_x(2) == 2.0 * KV_INDENT; + assert fv_tree_h(6) == 6.0 * KV_ROW_H + 5.0 * GAP; + assert fv_ins_h(0) == 14.0 * KV_ROW_H + 13.0 * GAP; + var parent : [6]u32 = [6]u32{ 255, 255, 0, 0, 0, 3 }; + var order : [6]u32 = undefined; + var dep : [6]u32 = undefined; + assert tree_order(6, &parent, &order, &dep) == 6; + // row 1 is lane 2 (depth 1), row 3 is lane 5 (depth 2) + assert fv_tree_at(6, &order, &dep, 300.0, 60.0, fv_row_y(1) + 3.0) == 2; + assert fv_tree_at(6, &order, &dep, 300.0, 60.0, fv_row_y(3) + 3.0) == 5; + assert fv_tree_at(6, &order, &dep, 300.0, 5.0, fv_row_y(3) + 3.0) == FAN_NONE; + assert fv_tree_at(6, &order, &dep, 300.0, 60.0, fv_row_y(1) + KV_ROW_H + 2.0) == FAN_NONE; + assert fv_tree_at(6, &order, &dep, 300.0, 400.0, 5.0) == FAN_NONE; +} diff --git a/specs/t27os/glass_window.t27 b/specs/t27os/glass_window.t27 index 321ad3f16b..9533011479 100644 --- a/specs/t27os/glass_window.t27 +++ b/specs/t27os/glass_window.t27 @@ -58,6 +58,8 @@ module T27osGlassWindow; +use t27f::petal_glass::clamp01; +use t27f::petal_glass::scale; use t27os::layout::ROW_ICON_H; use t27os::layout::ROW_GAP; use t27os::layout::row_next; @@ -329,10 +331,165 @@ use t27os::homes::HOME_COUNT; use t27os::homes::HOME_NAMES; use t27os::homes::HOME_ROLES; use t27os::homes::home_of_petal; -use t27os::homes::home_cards; -use t27os::homes::card_title; -use t27os::homes::card_line; -use t27os::homes::card_note; +use t27os::homes::HOME_LAWS; +use t27os::homes::HOME_RUNNERS; +use t27os::homes::IK_H_AGENTS; +use t27os::homes::IK_H_WIDGETS; +use t27os::homes::IK_H_COURSES; +use t27os::homes::IK_AGENT; +use t27os::homes::IK_NAMED; +use t27os::homes::IK_RUN; +use t27os::homes::IK_NO_WIDGETS; +use t27os::homes::IK_COURSE; +use t27os::homes::letter_of_petal; +use t27os::homes::petal_of_letter; +use t27os::homes::HOME_IDS; +use t27os::homes::nth_agent; +use t27os::agent_parts::home_of_letter; +use t27os::agent_parts::INSTRUMENT_LABEL; +use t27os::agent_parts::NO_WIDGETS; +use t27os::agent_parts::SURFACE_IDS; +use t27os::agent_parts::SURFACE_KINDS; +use t27os::agent_parts::parts_of_letter; +use t27os::agent_parts::widgets_of_letter; +use t27os::agent_parts::surfaces_of_letter; +use t27os::agent_parts::LETTER_COUNT; +use t27os::dash::dash_hit; +use t27os::dash::con_surface; +use t27os::dash::agents_of_instrument; +use t27os::dash::dash_agents_of_widget; +use t27os::dash::agents_count; +use t27os::dash::MAX_AGENTS; +use t27os::dash::title_len; +use t27os::dash::side_group_top; +use t27os::dash::side_chip_x; +use t27os::dash::side_chip_y; +use t27os::dash::side_letter_at; +use t27os::dash::on_switch; +use t27os::dash::CHIP_W; +use t27os::dash::CHIP_H2; +use t27os::homes::item_count; +use t27os::homes::item_kind; +use t27os::homes::item_top; +use t27os::homes::kind_h; +use t27os::homes::content_h; +use t27os::homes::scroll_max; +use t27os::homes::scroll_clamp; +use t27os::homes::home_title; +use t27os::homes::home_kicker; +use t27os::homes::home_line; +use t27os::homes::home_count_text; +use t27os::homes::item_title; +use t27os::homes::item_line; +use t27os::homes::item_note; +use t27os::homes::item_badge; +use t27os::homes::HEAD_H; +use t27os::homes::agent_domain; +use t27os::homes::AGENT_LETTERS; +use t27os::homes::agents_in; +use t27os::homes::widgets_in; +use t27os::homes::courses_in; +use t27os::homes::COURSE_IDS; +use t27os::homes::WIDGET_COUNT; +use t27os::homes::COURSE_COUNT; +use t27os::homes::course_widget; +use t27os::homes::H_T27U; +use t27os::dash::plan; +use t27os::dash::heavy_count; +use t27os::dash::heavy_k; +use t27os::dash::idle_k; +use t27os::dash::con_widget; +use t27os::dash::con_top; +use t27os::dash::con_h; +use t27os::dash::idle_top; +use t27os::dash::idle_cols; +use t27os::dash::courses_top; +use t27os::dash::cm_top; +use t27os::dash::cm_h; +use t27os::dash::cm_row_y; +use t27os::dash::cm_lessons_x; +use t27os::dash::cm_lessons_w; +use t27os::dash::page_h; +use t27os::dash::hero_h; +use t27os::dash::band_top; +use t27os::dash::band_h; +use t27os::dash::bar_top; +use t27os::dash::title_size; +use t27os::dash::gauge_below; +use t27os::dash::gauge_x; +use t27os::dash::gauge_y; +use t27os::dash::gauge_w; +use t27os::dash::GAUGE_NAMES; +use t27os::dash::GAUGE_AREA; +use t27os::dash::GAUGE_H; +use t27os::dash::seg_count; +use t27os::dash::seg_widget; +use t27os::dash::seg_cols; +use t27os::dash::SEG; +use t27os::dash::SEG_GAP; +use t27os::dash::GAL_KINDS; +use t27os::dash::CGAP; +use t27os::dash::CPAD; +use t27os::dash::GAP; +use t27os::dash::CON_HEAD; +use t27os::dash::CHIP_H; +use t27os::dash::MODULES; +use t27os::dash::DASH_LESSONS_PER_MODULE; +use t27os::dash::COURSES_HEAD; +use t27os::dash::NO_WIDGET; +use t27os::dash::dash_cell_x; +use t27os::dash::dash_cell_y; +use t27os::dash::cell_w; +use t27os::dash::inner_w; +use t27os::dash::tile_cols; +use t27os::dash::tile_w; +use t27os::dash::tile_h; +use t27os::dash::cell_h; +use t27os::dash::put_readout; +use t27os::dash::put_ratio; +use t27os::dash::put_two; +use t27os::dash::dash_scroll_max; +use t27os::dash::dash_scroll_clamp; +use t27os::dash::live_x; +use t27os::dash::live_y; +use t27os::dash::live_w; +use t27os::dash::live_h; +use t27os::dash::on_live_back; +use t27os::dash::view_x; +use t27os::dash::view_y; +use t27os::dash::GAL_IDS; +use t27os::dash::GAL_PAGES; +use t27os::dash::GAL_MAX; +use t27os::dash::gal_parse; +use t27os::dash::gal_start; +use t27os::dash::gal_len; +use t27os::dash::put_page_url; +use t27os::dash::put_card_url; +use t27os::dash::put_card_path; +use t27os::dash::put_gallery_path; +use t27os::dash::put_gallery_url; +use t27os::dash::put_part; +use t27os::dash::put_argv; +use t27os::dash::WS_NEW; +use t27os::dash::WS_FETCH; +use t27os::dash::WS_LOADED; +use t27os::dash::WS_FAILED; +use t27os::dash::WS_WANT; +use t27os::dash::CACHE_SLOTS; +use t27os::dash::FETCH_SLOTS; +use t27os::dash::cache_find; +use t27os::dash::cache_free; +use t27os::dash::cache_victim; +use t27os::session_store::put_bytes; +use t27os::session_store::put_dec; +use t27os::pty_sys::sys_spawn; +use t27os::pty_sys::sys_waitpid; +use t27os::pty_sys::sys_open_ro; +use t27os::pty_sys::sys_read; +use t27os::pty_sys::sys_mkdir; +use t27os::pty_sys::sys_rename; +use t27os::pty_sys::sys_unlink; +use t27os::pty_sys::sys_addr; use t27os::doctor::ROOT_HOME; use t27os::doctor::ROOT_TMP; use t27os::doctor::ROOT_TMP_PATH; @@ -565,6 +722,8 @@ pub const CROWN_WHITE : f64 = 0.70; pub const WEIGHT_MEDIUM : f64 = 0.23; pub const WEIGHT_SEMIBOLD : f64 = 0.3; pub const WEIGHT_BOLD : f64 = 0.4; +/// NSFontWeightHeavy: the website's course headings are weight 800. +pub const WEIGHT_HEAVY : f64 = 0.56; // ---- T title bar, H home, S screen, C chat (trios layout, points) ---- @@ -811,6 +970,7 @@ fn cg_color(r: f64, g: f64, b: f64, a: f64) -> u64 { return @extern("CGColorCrea fn cg_color_release(col: u64) -> void { @extern("CGColorRelease"); } fn cg_shadow(c: u64, ox: f64, oy: f64, blur: f64, col: u64) -> void { @extern("CGContextSetShadowWithColor"); } fn cg_save(c: u64) -> void { @extern("CGContextSaveGState"); } +fn cg_clip_rect(c: u64, x: f64, y: f64, w: f64, h: f64) -> void { @extern("CGContextClipToRect"); } fn cg_restore(c: u64) -> void { @extern("CGContextRestoreGState"); } fn cg_blend(c: u64, mode: u32) -> void { @extern("CGContextSetBlendMode"); } fn cg_text_pos(c: u64, x: f64, y: f64) -> void { @extern("CGContextSetTextPosition"); } @@ -819,6 +979,11 @@ fn ct_width(line: u64, ascent: u64, descent: u64, leading: u64) -> f64 { return fn ct_draw(line: u64, c: u64) -> void { @extern("CTLineDraw"); } fn ct_truncate(line: u64, width: f64, kind: u32, token: u64) -> u64 { return @extern("CTLineCreateTruncatedLine"); } fn msg_chars(o: u64, s: u64, buf: [1]u64, n: u64) -> u64 { return @extern("objc_msgSend"); } +fn cg_clip(c: u64) -> void { @extern("CGContextClip"); } +fn cg_alpha(c: u64, a: f64) -> void { @extern("CGContextSetAlpha"); } +fn cg_text_mode(c: u64, m: u32) -> void { @extern("CGContextSetTextDrawingMode"); } +fn cg_draw_image(c: u64, x: f64, y: f64, w: f64, h: f64, img: u64) -> void { @extern("CGContextDrawImage"); } +fn msg_rect_obj(o: u64, s: u64, x: f64, y: f64, w: f64, h: f64, a: u64) -> u64 { return @extern("objc_msgSend"); } // ---- small helpers ---- @@ -870,23 +1035,12 @@ pub fn chan(c: u32, k: u32) -> f64 { return @as(f64, @floatFromInt(v)) / 255.0; } -pub fn clamp01(x: f64) -> f64 { - if (x < 0.0) { return 0.0; } - if (x > 1.0) { return 1.0; } - return x; -} - /// S. One step of the screen's opacity towards shown (1) or gone (0). pub fn wave(a: f64, open: bool) -> f64 { if (open) { return clamp01(a + WAVE_STEP); } return clamp01(a - WAVE_STEP); } -/// Viewport pixels per box milli-unit x 1000, from petal_menu's fit. -pub fn scale() -> f64 { - return @as(f64, @floatFromInt(fit_permille(960, 540))) / 1000.0; -} - pub fn cen_x(p: u32) -> f64 { const i : u32 = p * 4; return @as(f64, @floatFromInt(QX[i] + QX[i + 1] + QX[i + 2] + QX[i + 3])) / 4.0; @@ -1635,6 +1789,35 @@ fn side_row(c: u64, y: f64, h: f64, selected: bool, hover: bool, a: f64) -> void } } +/// W. The sidebar's ground and the divider on its right edge: the chat's and every home tab's. +fn draw_side_frame(c: u64, a: f64, sh: f64, full: bool) -> void { + cg_fill_rgba(c, 0.0, 0.0, 0.0, surface_alpha(SURF_SIDEBAR, full) * a); + cg_fill_rect(c, 0.0, WS_TOP, SIDE_W, sh - WS_TOP); + cg_fill_rgba(c, 1.0, 1.0, 1.0, LINE_WHITE * a); + cg_fill_rect(c, SIDE_W, WS_TOP, HAIR, sh - WS_TOP); +} + +/// W. The sidebar's foot, shared by the chat and the home tabs. +fn draw_side_foot(c: u64, kit: [48]u64, a: f64, mx: f64, my: f64, sh: f64, ws: u32, collapsed: bool, dlabel: u64, dlit: bool, full: bool) -> void { + // W. the foot: a hairline, then OPEN SOURCE T27, which opens the repository (windows.t27 REPO_URL). + // Its ground hides a long list under it; in a window it is the sidebar's glass again, not an opaque strip + draw_ground(c, 0.0, sh - FOOT_H, SIDE_W, FOOT_H, SURF_SIDEBAR, full, a); + cg_fill_rgba(c, 1.0, 1.0, 1.0, LINE_WHITE * a); + cg_fill_rect(c, 12.0, sh - FOOT_H, SIDE_W - 24.0, HAIR); + var la : f64 = LABEL_WHITE; + if (on_repo_link(mx, my, sh, ws, collapsed)) { la = TEXT_WHITE; } + cg_fill_rgba(c, 1.0, 1.0, 1.0, la * a); + const lw : f64 = draw_text(c, kit, kit[K_REPO], 14.0, sh - 13.0, 9.5, WEIGHT_SEMIBOLD, false); + if (on_repo_link(mx, my, sh, ws, collapsed)) { + cg_fill_rect(c, 14.0, sh - 10.5, lw, HAIR); + } + // D. DOCTOR n at the right end of the foot opens the DOCTOR panel (doctor.t27) + var da : f64 = LABEL_WHITE; + if (dlit or on_doctor_link(mx, my, sh, ws, collapsed)) { da = TEXT_WHITE; } + cg_fill_rgba(c, 1.0, 1.0, 1.0, da * a); + draw_text(c, kit, dlabel, SIDE_W - 14.0 - DOC_LINK_W + 22.0, sh - 13.0, 9.5, WEIGHT_SEMIBOLD, false); +} + /// C. Chat workspace: sidebar (unless collapsed), divider, content with header, divider and cards. /// K. Compact (ws 1): no sidebar, the OPEN DASHBOARD bar instead of the header. Forced dashboard (ws 2): /// the expanded layout with a zero-width sidebar and the Close Dashboard button in the header. @@ -1645,10 +1828,7 @@ fn draw_chat(c: u64, kit: [48]u64, shown: u32, task: u32, space: u32, sp: u32, s cg_fill_rect(c, 0.0, WS_TOP, HAIR, sh - WS_TOP); } if (ws == WS_EXPANDED and collapsed == false) { - cg_fill_rgba(c, 0.0, 0.0, 0.0, surface_alpha(SURF_SIDEBAR, full) * a); - cg_fill_rect(c, 0.0, WS_TOP, SIDE_W, sh - WS_TOP); - cg_fill_rgba(c, 1.0, 1.0, 1.0, LINE_WHITE * a); - cg_fill_rect(c, SIDE_W, WS_TOP, HAIR, sh - WS_TOP); + draw_side_frame(c, a, sh, full); // W. three groups (windows.t27): QUEEN, SPACES, AGENTS -- herdr's sidebar with the Queen on top const n : u32 = spaces_shown(sp); cg_fill_rgba(c, 1.0, 1.0, 1.0, LABEL_WHITE * a); @@ -1742,24 +1922,7 @@ fn draw_chat(c: u64, kit: [48]u64, shown: u32, task: u32, space: u32, sp: u32, s } r = r + 1; } - // W. the foot: a hairline, then OPEN SOURCE T27, which opens the repository (windows.t27 REPO_URL). - // Its ground hides a long list of chats under it; in a window it is the sidebar's glass again, not - // an opaque black strip - draw_ground(c, 0.0, sh - FOOT_H, SIDE_W, FOOT_H, SURF_SIDEBAR, full, a); - cg_fill_rgba(c, 1.0, 1.0, 1.0, LINE_WHITE * a); - cg_fill_rect(c, 12.0, sh - FOOT_H, SIDE_W - 24.0, HAIR); - var la : f64 = LABEL_WHITE; - if (on_repo_link(mx, my, sh, ws, collapsed)) { la = TEXT_WHITE; } - cg_fill_rgba(c, 1.0, 1.0, 1.0, la * a); - const lw : f64 = draw_text(c, kit, kit[K_REPO], 14.0, sh - 13.0, 9.5, WEIGHT_SEMIBOLD, false); - if (on_repo_link(mx, my, sh, ws, collapsed)) { - cg_fill_rect(c, 14.0, sh - 10.5, lw, HAIR); - } - // D. DOCTOR n at the right end of the foot opens the DOCTOR panel (doctor.t27) - var da : f64 = LABEL_WHITE; - if (dlit or on_doctor_link(mx, my, sh, ws, collapsed)) { da = TEXT_WHITE; } - cg_fill_rgba(c, 1.0, 1.0, 1.0, da * a); - draw_text(c, kit, dlabel, SIDE_W - 14.0 - DOC_LINK_W + 22.0, sh - 13.0, 9.5, WEIGHT_SEMIBOLD, false); + draw_side_foot(c, kit, a, mx, my, sh, ws, collapsed, dlabel, dlit, full); } cg_fill_rgba(c, 0.0, 0.0, 0.0, surface_alpha(SURF_CONTENT, full) * a); cg_fill_rect(c, cx, WS_TOP, sw - cx, sh - WS_TOP); @@ -1773,12 +1936,10 @@ fn draw_chat(c: u64, kit: [48]u64, shown: u32, task: u32, space: u32, sp: u32, s if (tup == false) { draw_cards(c, kit, task, a, collapsed, mx, my, sw, sh, ws); } } -/// C. The 44 pt header: sidebar.left toggle, Close Dashboard when forced open, the title, the divider. -fn draw_header(c: u64, kit: [48]u64, shown: u32, a: f64, collapsed: bool, mx: f64, my: f64, sw: f64, ws: u32) -> void { - const cx : f64 = content_x(ws, collapsed); - // header: sidebar.left toggle, then the title +/// C. The sidebar.left toggle at the left of the 44 pt header; a faint box behind it while the pointer is on it. +fn draw_side_toggle(c: u64, a: f64, cx: f64, lit: bool) -> void { const ty : f64 = WS_TOP + 0.5 * (HEADER_H - TOGGLE_PT); - if (on_side_toggle(mx, my, ws, collapsed)) { + if (lit) { round_rect(c, cx + 14.0, ty, TOGGLE_PT, TOGGLE_PT, 6.0); cg_fill_rgba(c, 1.0, 1.0, 1.0, HIGHLIGHT_WHITE * a); cg_fill_path(c); @@ -1791,6 +1952,14 @@ fn draw_header(c: u64, kit: [48]u64, shown: u32, a: f64, collapsed: bool, mx: f6 cg_line(c, ix + 5.5, iy + 13.0); cg_stroke_rgba(c, 1.0, 1.0, 1.0, MUTED_WHITE * a); cg_stroke_path(c); +} + +/// C. The 44 pt header: sidebar.left toggle, Close Dashboard when forced open, the title, the divider. +fn draw_header(c: u64, kit: [48]u64, shown: u32, a: f64, collapsed: bool, mx: f64, my: f64, sw: f64, ws: u32) -> void { + const cx : f64 = content_x(ws, collapsed); + // header: sidebar.left toggle, then the title + const ty : f64 = WS_TOP + 0.5 * (HEADER_H - TOGGLE_PT); + draw_side_toggle(c, a, cx, on_side_toggle(mx, my, ws, collapsed)); var title_x : f64 = cx + 14.0 + TOGGLE_PT + 12.0; if (ws == WS_DASHBOARD) { // K. rectangle.compress.vertical, grokMuted: two arrows closing on a line @@ -2052,92 +2221,626 @@ fn draw_cards(c: u64, kit: [48]u64, task: u32, a: f64, collapsed: bool, mx: f64, } } -/// H. The body of a home tab is the chat's card column with no sidebar: the same slots, the same honeycomb. -pub fn home_card_at(x: f64, y: f64, sw: f64, sh: f64, p: u32) -> u32 { - const k : u32 = card_at(x, y, sw, sh, WS_EXPANDED, true); - if (k < home_cards(home_of_petal(p))) { return k; } - return CARD_SLOTS; -} - /// H. Text from a table constant, as bytes. fn draw_str(c: u64, kit: [48]u64, s: []const u8, x: f64, base: f64, size: f64, weight: f64) -> f64 { return draw_bytes(c, kit, s, s.len, x, base, size, weight, false); } -/// H. One line of a card, centred on kx and cut with an ellipsis where it would pass the cell's width. -fn draw_mid(c: u64, kit: [48]u64, s: []const u8, n: usize, kx: f64, base: f64, size: f64, weight: f64, room: f64) -> void { - if (n == 0) { return; } +/// H. A home tab shares the chat's frame: a sidebar of 272 pt unless collapsed, the 44 pt header, the content. +/// The chat's forced-dashboard layout has no meaning there, so every width but the narrow one is the expanded one. +pub fn home_ws(ws: u32) -> u32 { + if (ws == WS_COMPACT) { return WS_COMPACT; } + return WS_EXPANDED; +} + +/// H. The workspace whose sidebar the screen of petal p shows: the chat's own, or a home tab's. +pub fn side_ws(p: u32, ws: u32) -> u32 { + if (p == PETAL_CHAT) { return ws; } + return home_ws(ws); +} + +/// H. The scrolling page starts under the header's divider and fills the rest of the window. +pub fn home_view_top() -> f64 { return WS_TOP + HEADER_H + 1.0; } + +pub fn home_view_h(sh: f64) -> f64 { return sh - home_view_top(); } + +pub fn on_home_view(x: f64, y: f64, ws: u32, collapsed: bool) -> bool { + return y >= home_view_top() and x >= content_x(home_ws(ws), collapsed); +} + +/// H. The scrollbar: a line 4 pt in from both ends of the view, its thumb as tall as the view is of the page +/// (28 pt at least), placed by the offset. +pub fn thumb_h(view: f64, total: f64) -> f64 { + var t : f64 = (view - 8.0) * view / total; + if (t < 28.0) { t = 28.0; } + if (t > view - 8.0) { t = view - 8.0; } + return t; +} + +pub fn thumb_y(off: u32, top: u32, view: f64, th: f64) -> f64 { + if (top == 0) { return 4.0; } + return 4.0 + (view - 8.0 - th) * @as(f64, @floatFromInt(off)) / @as(f64, @floatFromInt(top)); +} + +/// H. Text from bytes, cut with an ellipsis where it would pass `right` (not cut when right is 0). +fn draw_fit(c: u64, kit: [48]u64, s: []const u8, n: usize, x: f64, base: f64, size: f64, weight: f64, mono: bool, right: f64) -> f64 { + if (n == 0) { return 0.0; } const ns : u64 = cf_bytes(0, s, n, UTF8, false); - if (ns == 0) { return; } - const w : f64 = text_w(kit, ns, size, weight, false); - if (w <= room) { - draw_text(c, kit, ns, kx - 0.5 * w, base, size, weight, false); + if (ns == 0) { return 0.0; } + var w : f64 = 0.0; + if (right > 0.0) { + w = draw_text_fit(c, kit, ns, x, base, size, weight, mono, right); } else { - draw_text_fit(c, kit, ns, kx - 0.5 * room, base, size, weight, false, kx + 0.5 * room); + w = draw_text(c, kit, ns, x, base, size, weight, mono); } cf_release(ns); + return w; } -/// H. The screen of a petal that is not the CHAT petal (t27 OS: black and white, shape and text only). The -/// title bar below is the chat's; the header names the petal and, for a home, the home and its role; the body -/// is the 3 x 3 honeycomb with the cards homes.t27 lists for that home (agents, widget counts, courses). A petal -/// that is no home shows the nine empty cells (petal_menu: every menu empty). -fn draw_home(c: u64, kit: [48]u64, p: u32, a: f64, mx: f64, my: f64, sw: f64, sh: f64, full: bool) -> void { - const h : u32 = home_of_petal(p); +/// H. Where the first of two lines of s ends: n when it fits in `room`, else the last space that leaves it fitting. +fn wrap_at(kit: [48]u64, s: []const u8, n: usize, size: f64, weight: f64, room: f64) -> usize { + if (bytes_w(kit, s, n, size, weight, false) <= room) { return n; } + var k : usize = n; + while (k > 0) { + k = k - 1; + if (s[k] == 32 and bytes_w(kit, s, k, size, weight, false) <= room) { return k; } + } + return n; +} + +// ---- the instrument panel of a home tab (dash.t27 decides, this draws) ---- +// dc is the dashboard's state: counters and handles in the first slots, one state per widget from D_ST, the +// image cache (widget keys from D_KEYS, NSImage handles from D_IMGS). + +pub const D_GEN : u32 = 0; +pub const D_GN : u32 = 2; +pub const D_LIVE : u32 = 3; +pub const D_WEB : u32 = 4; +pub const D_BUDGET : u32 = 5; +pub const D_NEXT : u32 = 6; +pub const D_LAST : u32 = 7; +pub const D_GTRY : u32 = 8; +pub const D_DEFER : u32 = 9; +pub const D_SEL_INIT : u32 = 10; +pub const D_SEL_CG : u32 = 11; +pub const D_CLS_IMG : u32 = 12; +pub const D_SEL_ALLOC : u32 = 13; +pub const D_ST : u32 = 16; +pub const D_KEYS : u32 = 256; +pub const D_IMGS : u32 = 384; +pub const D_SIZE : usize = 512; +pub const DECODES_PER_FRAME : u64 = 3; + +/// The CGImage of widget wi's card, 0 while it is not on disk yet. Reads the file at most DECODES_PER_FRAME +/// times a frame; a missing file asks for a background fetch (state WS_WANT), a page without a card fails. +fn card_img(dc: []u64, gal: []const u8, gt: []const u32, hb: []const u8, wi: u32) -> u64 { + if (dc[D_GN] == 0 or wi >= WIDGET_COUNT) { return 0; } + const st : u64 = dc[D_ST + wi]; + if (st == WS_LOADED) { + const s0 : u32 = cache_find(dc[D_KEYS .. D_KEYS + 128], CACHE_SLOTS, wi); + if (s0 < CACHE_SLOTS) { return msg3(dc[D_IMGS + s0], dc[D_SEL_CG], 0, 0, 0); } + dc[D_ST + wi] = WS_NEW; + return 0; + } + if (st != WS_NEW) { return 0; } + if (dc[D_BUDGET] == 0) { + dc[D_DEFER] = dc[D_DEFER] + 1; + return 0; + } + var pb : [512]u8 = undefined; + const pn : usize = put_card_path(hb, gal, gt, wi, pb[0..512]); + const fd : i32 = sys_open_ro(pb[0..512], 0); + if (fd < 0) { + var ub : [320]u8 = undefined; + if (put_card_url(gal, gt, wi, ub[0..320]) == 0) { + dc[D_ST + wi] = WS_FAILED; + } else { + dc[D_ST + wi] = WS_WANT; + } + return 0; + } + sys_close(fd); + dc[D_BUDGET] = dc[D_BUDGET] - 1; + const ps : u64 = cf_bytes(0, pb[0..512], pn, UTF8, false); + const ns : u64 = msg1(msg0(dc[D_CLS_IMG], dc[D_SEL_ALLOC]), dc[D_SEL_INIT], ps); + cf_release(ps); + if (ns == 0) { + dc[D_ST + wi] = WS_FAILED; + return 0; + } + var s : u32 = cache_free(dc[D_KEYS .. D_KEYS + 128], CACHE_SLOTS); + if (s >= CACHE_SLOTS) { + // evict round robin: the evicted widget will be read again when it is next seen + s = cache_victim(dc[D_NEXT], CACHE_SLOTS); + dc[D_NEXT] = dc[D_NEXT] + 1; + dc[D_ST + @as(u32, @intCast(dc[D_KEYS + s] - 1))] = WS_NEW; + msg0(dc[D_IMGS + s], sel("release")); + } + dc[D_KEYS + s] = @as(u64, wi) + 1; + dc[D_IMGS + s] = ns; + dc[D_ST + wi] = WS_LOADED; + return msg3(ns, dc[D_SEL_CG], 0, 0, 0); +} + +/// A thin 1 pt frame with brighter corner brackets: the frame of every console, gauge and chip. +fn bracket_frame(c: u64, x: f64, y: f64, w: f64, h: f64, br: f64, a: f64, hot: bool) -> void { + var k : f64 = 0.20; + if (hot) { k = 0.55; } + cg_fill_rgba(c, 1.0, 1.0, 1.0, k * a); + cg_fill_rect(c, x, y, w, 1.0); + cg_fill_rect(c, x, y + h - 1.0, w, 1.0); + cg_fill_rect(c, x, y, 1.0, h); + cg_fill_rect(c, x + w - 1.0, y, 1.0, h); + cg_fill_rgba(c, 1.0, 1.0, 1.0, 0.85 * a); + cg_fill_rect(c, x, y, br, 2.0); + cg_fill_rect(c, x, y, 2.0, br); + cg_fill_rect(c, x + w - br, y, br, 2.0); + cg_fill_rect(c, x + w - 2.0, y, 2.0, br); + cg_fill_rect(c, x, y + h - 2.0, br, 2.0); + cg_fill_rect(c, x, y + h - br, 2.0, br); + cg_fill_rect(c, x + w - br, y + h - 2.0, br, 2.0); + cg_fill_rect(c, x + w - 2.0, y + h - br, 2.0, br); +} + +/// An instrument's face: the card (rounded, clipped), a dark plate while it is missing, a 1 pt #2a2a2a border. +fn draw_card(c: u64, x: f64, y: f64, w: f64, h: f64, img: u64, hot: bool, a: f64) -> void { + cg_fill_rgba(c, 0.07, 0.07, 0.07, a); + round_rect(c, x, y, w, h, 4.0); + cg_fill_path(c); + if (img != 0) { + cg_save(c); + round_rect(c, x, y, w, h, 4.0); + cg_clip(c); + cg_alpha(c, a); + cg_translate(c, x, y + h); + cg_scale(c, 1.0, -1.0); + cg_draw_image(c, 0.0, 0.0, w, h, img); + cg_restore(c); + } + round_rect(c, x, y, w, h, 4.0); + cg_line_width(c, 1.0); + if (hot) { + cg_stroke_rgba(c, 1.0, 1.0, 1.0, 0.85 * a); + } else { + cg_stroke_rgba(c, 0.165, 0.165, 0.165, a); + } + cg_stroke_path(c); +} + +/// The label of widget wi: its id from the parsed gallery, "WIDGET n" while the gallery is not here yet. +fn widget_label(dc: []u64, gal: []const u8, gt: []const u32, wi: u32, out: []u8) -> usize { + if (dc[D_GN] == 0 or wi >= WIDGET_COUNT) { + const n : usize = put_bytes(out, 0, "WIDGET "); + return put_dec(out, n, @as(u64, wi + 1)); + } + const s : usize = gal_start(gt, GAL_IDS, wi); + return put_bytes(out, 0, gal[s .. s + gal_len(gt, GAL_IDS, wi)]); +} + +/// Text drawn as an outline only (the lesson number of the course): stroke mode, 1.5 pt white. +fn draw_outline(c: u64, kit: [48]u64, s: []const u8, n: usize, x: f64, base: f64, size: f64, a: f64) -> f64 { + cg_line_width(c, 1.5); + cg_stroke_rgba(c, 1.0, 1.0, 1.0, a); + cg_text_mode(c, 1); + const w : f64 = draw_fit(c, kit, s, n, x, base, size, WEIGHT_HEAVY, false, 0.0); + cg_text_mode(c, 0); + return w; +} + +/// One gauge widget: a framed label over a mono readout. +fn draw_gauge(c: u64, kit: [48]u64, x: f64, y: f64, w: f64, label: []const u8, v: []const u8, vn: usize, a: f64) -> void { + bracket_frame(c, x, y, w, GAUGE_H, 4.0, a, false); + cg_fill_rgba(c, 1.0, 1.0, 1.0, LABEL_WHITE * a); + draw_fit(c, kit, label, label.len, x + 7.0, y + 10.0, 7.5, WEIGHT_SEMIBOLD, true, x + w - 4.0); + cg_fill_rgba(c, 1.0, 1.0, 1.0, a); + draw_fit(c, kit, v, vn, x + 7.0, y + 21.5, 11.5, WEIGHT_BOLD, true, x + w - 4.0); +} + +/// The widget the page point (mx, my) is over, NO_WIDGET when none (also off the page). +fn dash_pick(o: u32, mx: f64, my: f64, sw: f64, cx: f64, hs: u32) -> u32 { + if (my < home_view_top() or mx < cx or o < 1 or o > LETTER_COUNT) { return NO_WIDGET; } + var nc : [32]u32 = undefined; + const na : u32 = plan(o, nc[0..32]); + const lx : f64 = cx + 24.0; + return dash_hit(o, nc[0..32], na, sw - 24.0 - lx, mx - lx, my - (home_view_top() - @as(f64, @floatFromInt(hs)))); +} + +/// The total height of home h's page for a window sw wide with the content column at cx. +fn dash_total(o: u32, sw: f64, cx: f64) -> f64 { + var nc : [32]u32 = undefined; + const na : u32 = plan(o, nc[0..32]); + if (o < 1 or o > LETTER_COUNT) { return 0.0; } + return page_h(o, nc[0..32], na, sw - 24.0 - (cx + 24.0)); +} + +/// The agents pinned to an instrument: a row of small letter boxes, the owner first (filled), then the letters +/// that feed it (outlined). Drawn on every tile; agents_of_instrument never returns 0 for the owner. +fn draw_badges(c: u64, kit: [48]u64, x: f64, y: f64, o: u32, t: u32, a: f64) -> void { + var k : u32 = 0; + while (k < MAX_AGENTS) { + const l : u32 = agents_of_instrument(o, t, k); + if (l == 0) { return; } + const bx : f64 = x + @as(f64, @floatFromInt(k)) * 15.0; + round_rect(c, bx, y, 14.0, 12.0, 2.5); + if (k == 0) { + cg_fill_rgba(c, 1.0, 1.0, 1.0, 0.95 * a); + cg_fill_path(c); + cg_fill_rgba(c, 0.0, 0.0, 0.0, a); + } else { + cg_fill_rgba(c, 0.0, 0.0, 0.0, 0.75 * a); + cg_fill_path(c); + round_rect(c, bx, y, 14.0, 12.0, 2.5); + cg_line_width(c, 1.0); + cg_stroke_rgba(c, 1.0, 1.0, 1.0, 0.8 * a); + cg_stroke_path(c); + cg_fill_rgba(c, 1.0, 1.0, 1.0, a); + } + const lw : f64 = bytes_w(kit, AGENT_LETTERS[l - 1], AGENT_LETTERS[l - 1].len, 7.5, WEIGHT_BOLD, true); + draw_fit(c, kit, AGENT_LETTERS[l - 1], AGENT_LETTERS[l - 1].len, bx + 7.0 - 0.5 * lw, y + 9.0, 7.5, WEIGHT_BOLD, true, 0.0); + k = k + 1; + } +} + +/// The CHAT / CONSOLE switch of petal C at the right end of the header. +fn draw_switch(c: u64, kit: [48]u64, label: []const u8, sw: f64, a: f64, mx: f64, my: f64) -> void { + const hot : bool = on_switch(mx, my, sw, WS_TOP); + round_rect(c, sw - 108.0, WS_TOP + 11.0, 84.0, 22.0, 5.0); + cg_line_width(c, 1.0); + if (hot) { + cg_stroke_rgba(c, 1.0, 1.0, 1.0, 0.85 * a); + } else { + cg_stroke_rgba(c, 1.0, 1.0, 1.0, 0.35 * a); + } + cg_stroke_path(c); + cg_fill_rgba(c, 1.0, 1.0, 1.0, a); + const w : f64 = bytes_w(kit, label, label.len, 9.5, WEIGHT_BOLD, true); + draw_fit(c, kit, label, label.len, sw - 66.0 - 0.5 * w, WS_TOP + 25.5, 9.5, WEIGHT_BOLD, true, 0.0); +} + +/// The live page of widget dc[D_LIVE] - 1: crumbs, a big outlined number, the title; the web view sits under it. +fn draw_live(c: u64, kit: [48]u64, dc: []u64, gal: []const u8, gt: []const u32, o: u32, a: f64, cx: f64, mx: f64, my: f64, sw: f64, sh: f64) -> void { + const vt : f64 = home_view_top(); + const wi : u32 = @as(u32, @intCast(dc[D_LIVE] - 1)); + const lx : f64 = live_x(cx); + var tb : [256]u8 = undefined; + var n : usize = 0; + if (on_live_back(mx, my, cx, vt)) { + cg_fill_rgba(c, 1.0, 1.0, 1.0, a); + } else { + cg_fill_rgba(c, 1.0, 1.0, 1.0, TEXT_WHITE * a); + } + draw_fit(c, kit, "< BACK", 6, lx, vt + 24.0, 10.5, WEIGHT_SEMIBOLD, true, 0.0); + cg_fill_rgba(c, 1.0, 1.0, 1.0, LABEL_WHITE * a); + n = put_bytes(tb[0..256], 0, "/ "); + n = put_bytes(tb[0..256], n, INSTRUMENT_LABEL[o - 1]); + n = put_bytes(tb[0..256], n, " / WIDGETS"); + draw_fit(c, kit, tb[0..256], n, lx + 88.0, vt + 24.0, 10.5, 0.0, true, sw - 32.0); + // the pinned agents, in words: AGENTS: K owner . C ... + n = put_bytes(tb[0..256], 0, "AGENTS: "); + var ak : u32 = 0; + while (ak < MAX_AGENTS and dash_agents_of_widget(wi, ak) != 0) { + const al : u32 = dash_agents_of_widget(wi, ak); + if (ak > 0) { n = put_bytes(tb[0..256], n, " . "); } + n = put_bytes(tb[0..256], n, AGENT_LETTERS[al - 1]); + n = put_bytes(tb[0..256], n, " "); + n = put_bytes(tb[0..256], n, INSTRUMENT_LABEL[al - 1]); + if (ak == 0) { n = put_bytes(tb[0..256], n, " (owner)"); } + ak = ak + 1; + } + cg_fill_rgba(c, 1.0, 1.0, 1.0, TEXT_WHITE * a); + draw_fit(c, kit, tb[0..256], n, lx, vt + 44.0, 10.0, WEIGHT_SEMIBOLD, true, sw - 32.0); + cg_fill_rgba(c, 1.0, 1.0, 1.0, a); + n = put_two(wi + 1, tb[0..256]); + if (wi + 1 >= 100) { n = put_dec(tb[0..256], 0, @as(u64, wi + 1)); } + const nw : f64 = draw_outline(c, kit, tb[0..256], n, lx, vt + 100.0, 50.0, a); + cg_fill_rgba(c, 1.0, 1.0, 1.0, a); + n = widget_label(dc, gal, gt, wi, tb[0..256]); + draw_fit(c, kit, tb[0..256], n, lx + nw + 18.0, vt + 86.0, 28.0, WEIGHT_HEAVY, false, sw - 32.0); + cg_fill_rgba(c, 1.0, 1.0, 1.0, MUTED_WHITE * a); + if (dc[D_GN] > 0) { + n = put_page_url(gal, gt, wi, tb[0..256]); + draw_fit(c, kit, tb[0..256], n, lx + nw + 18.0, vt + 102.0, 10.0, 0.0, true, sw - 32.0); + } + bracket_frame(c, lx, live_y(vt), live_w(sw, cx), live_h(sh, vt), 10.0, a, false); +} + +/// H. The screen of a petal that is not the CHAT petal (t27 OS: black and white, shape and text only), in the +/// style of the chat: the 272 pt sidebar lists the homes (the open one lit, a tap switches to it) over the +/// chat's foot; the content has the 44 pt header and the sidebar toggle, then a page that scrolls: a big heavy +/// title, a larger muted lead, the law and the runner, and under bold headings EVERY agent, EVERY widget group +/// and (t27u) EVERY course of the home (homes.t27 L7). A petal that is no home shows the header only. +fn draw_home(c: u64, kit: [48]u64, p: u32, a: f64, collapsed: bool, mx: f64, my: f64, sw: f64, sh: f64, ws: u32, hs: u32, dlabel: u64, dlit: bool, full: bool, dc: []u64, gal: []const u8, gt: []const u32) -> void { + const o : u32 = letter_of_petal(p); + const h : u32 = home_of_letter(o); + const hws : u32 = home_ws(ws); + const cx : f64 = content_x(hws, collapsed); + var tb : [256]u8 = undefined; + if (hws == WS_EXPANDED and collapsed == false) { + // the sidebar: the 27 letters as chips, grouped by home, each with a lamp (parts at all) and its count + draw_side_frame(c, a, sh, full); + var hv : u32 = 0; + if (a >= 1.0) { hv = side_letter_at(mx, my, WS_TOP); } + cg_fill_rgba(c, 1.0, 1.0, 1.0, LABEL_WHITE * a); + draw_str(c, kit, "AGENTS BY HOME", 14.0, WS_TOP + 18.0, 9.5, WEIGHT_SEMIBOLD); + var g : u32 = 0; + while (g < HOME_COUNT) { + const gy : f64 = side_group_top(g, WS_TOP); + cg_fill_rgba(c, 1.0, 1.0, 1.0, LABEL_WHITE * a); + draw_fit(c, kit, HOME_IDS[g], HOME_IDS[g].len, 14.0, gy + 9.5, 8.5, WEIGHT_SEMIBOLD, true, 0.0); + cg_fill_rgba(c, 1.0, 1.0, 1.0, MUTED_WHITE * a); + draw_fit(c, kit, HOME_NAMES[g], HOME_NAMES[g].len, 14.0 + 40.0, gy + 9.5, 8.5, 0.0, false, SIDE_W - 14.0); + var k : u32 = 0; + while (k < agents_in(g)) { + const l : u32 = nth_agent(g, k); + const bx : f64 = side_chip_x(k); + const by : f64 = side_chip_y(g, k, WS_TOP); + round_rect(c, bx, by, CHIP_W, CHIP_H2, 4.0); + if (l == o) { + cg_fill_rgba(c, 1.0, 1.0, 1.0, 0.18 * a); + cg_fill_path(c); + } else if (l == hv) { + cg_fill_rgba(c, 1.0, 1.0, 1.0, 0.09 * a); + cg_fill_path(c); + } + round_rect(c, bx, by, CHIP_W, CHIP_H2, 4.0); + cg_line_width(c, 1.0); + if (l == o) { + cg_stroke_rgba(c, 1.0, 1.0, 1.0, 0.8 * a); + } else { + cg_stroke_rgba(c, 1.0, 1.0, 1.0, 0.22 * a); + } + cg_stroke_path(c); + cg_fill_rgba(c, 1.0, 1.0, 1.0, a); + const lw : f64 = bytes_w(kit, AGENT_LETTERS[l - 1], AGENT_LETTERS[l - 1].len, 11.0, WEIGHT_BOLD, true); + draw_fit(c, kit, AGENT_LETTERS[l - 1], AGENT_LETTERS[l - 1].len, bx + 0.5 * CHIP_W - 0.5 * lw, by + 11.0, 11.0, WEIGHT_BOLD, true, 0.0); + const pc : u32 = parts_of_letter(l); + n_lamp(c, bx + CHIP_W - 5.0, by + 4.5, pc > 0, a); + const cn : usize = put_dec(tb[0..256], 0, @as(u64, pc)); + const cw : f64 = bytes_w(kit, tb[0..256], cn, 7.0, 0.0, true); + cg_fill_rgba(c, 1.0, 1.0, 1.0, MUTED_WHITE * a); + draw_fit(c, kit, tb[0..256], cn, bx + 0.5 * CHIP_W - 0.5 * cw, by + 21.0, 7.0, 0.0, true, 0.0); + k = k + 1; + } + g = g + 1; + } + draw_side_foot(c, kit, a, mx, my, sh, hws, collapsed, dlabel, dlit, full); + } cg_fill_rgba(c, 0.0, 0.0, 0.0, surface_alpha(SURF_CONTENT, full) * a); - cg_fill_rect(c, 0.0, WS_TOP, sw, sh - WS_TOP); - const base : f64 = WS_TOP + 0.5 * HEADER_H + 4.2; + cg_fill_rect(c, cx, WS_TOP, sw - cx, sh - WS_TOP); + var tx : f64 = 14.0; + if (hws == WS_EXPANDED) { + draw_side_toggle(c, a, cx, on_side_toggle(mx, my, hws, collapsed)); + tx = cx + 14.0 + TOGGLE_PT + 12.0; + } cg_fill_rgba(c, 1.0, 1.0, 1.0, TEXT_WHITE * a); - const tw : f64 = draw_text(c, kit, world(kit, p), 14.0, base, 12.0, WEIGHT_SEMIBOLD, false); - if (h < HOME_COUNT) { + draw_text(c, kit, world(kit, p), tx, WS_TOP + 0.5 * HEADER_H + 4.2, 12.0, WEIGHT_SEMIBOLD, false); + cg_fill_rgba(c, 1.0, 1.0, 1.0, LINE_WHITE * a); + cg_fill_rect(c, cx, WS_TOP + HEADER_H, sw - cx, HAIR); + if (o < 1 or o > LETTER_COUNT) { return; } + if (p == PETAL_CHAT) { draw_switch(c, kit, "< CHAT", sw, a, mx, my); } + const vt : f64 = home_view_top(); + const vh : f64 = home_view_h(sh); + const y0 : f64 = vt - @as(f64, @floatFromInt(hs)); + const lx : f64 = cx + 24.0; + const rx : f64 = sw - 24.0; + const pw : f64 = rx - lx; + cg_save(c); + cg_clip_rect(c, cx, vt, sw - cx, vh); + if (dc[D_LIVE] != 0) { + draw_live(c, kit, dc, gal, gt, o, a, cx, mx, my, sw, sh); + cg_restore(c); + return; + } + dc[D_BUDGET] = DECODES_PER_FRAME; + var hb : [256]u8 = undefined; + const hn : usize = ns_bytes(home_dir(), hb[0..256]); + var nc : [32]u32 = undefined; + const na : u32 = plan(o, nc[0..32]); + var hov_w : u32 = NO_WIDGET; + if (a >= 1.0 and my >= vt and mx >= cx) { hov_w = dash_hit(o, nc[0..32], na, pw, mx - lx, my - y0); } + // HERO: kicker, the big letter and domain, the lead, four gauge widgets, the segment bar + const nseg : u32 = seg_count(o); + var loaded : u32 = 0; + var sg : u32 = 0; + while (sg < nseg) { + const sw0 : u32 = seg_widget(o, sg); + if (sw0 < WIDGET_COUNT and dc[D_ST + sw0] == WS_LOADED) { loaded = loaded + 1; } + sg = sg + 1; + } + cg_fill_rgba(c, 1.0, 1.0, 1.0, LABEL_WHITE * a); + var n : usize = put_bytes(tb[0..256], 0, "LETTER "); + n = put_dec(tb[0..256], n, @as(u64, o)); + n = put_bytes(tb[0..256], n, " OF 27 // REALM "); + n = put_dec(tb[0..256], n, @as(u64, (o - 1) / 9 + 1)); + n = put_bytes(tb[0..256], n, " // "); + n = put_bytes(tb[0..256], n, HOME_NAMES[h]); + draw_fit(c, kit, tb[0..256], n, lx, y0 + 18.0, 9.0, WEIGHT_SEMIBOLD, true, rx); + const ts : f64 = title_size(o, pw); + var toff : f64 = 0.0; + var tright : f64 = rx; + if (gauge_below(pw) == false) { + toff = 0.5 * (band_h(o, pw) - ts - 6.0); + tright = rx - GAUGE_AREA - 14.0; + } + cg_fill_rgba(c, 1.0, 1.0, 1.0, a); + n = put_bytes(tb[0..256], 0, AGENT_LETTERS[o - 1]); + n = put_bytes(tb[0..256], n, " "); + n = put_bytes(tb[0..256], n, INSTRUMENT_LABEL[o - 1]); + draw_fit(c, kit, tb[0..256], n, lx, y0 + band_top() + toff + 0.84 * ts, ts, WEIGHT_HEAVY, false, tright); + cg_fill_rgba(c, 1.0, 1.0, 1.0, 0.92 * a); + draw_fit(c, kit, HOME_ROLES[h], HOME_ROLES[h].len, lx, y0 + band_top() + band_h(o, pw) + 15.0, 14.0, WEIGHT_MEDIUM, false, rx); + var g2 : u32 = 0; + while (g2 < 4) { + var vn : usize = 0; + if (g2 == 0) { vn = put_bytes(tb[0..256], 0, HOME_IDS[h]); } + if (g2 == 1) { vn = put_dec(tb[0..256], 0, @as(u64, widgets_of_letter(o))); } + if (g2 == 2) { vn = put_dec(tb[0..256], 0, @as(u64, surfaces_of_letter(o))); } + if (g2 == 3) { vn = put_ratio(loaded, nseg, tb[0..256]); } + draw_gauge(c, kit, lx + gauge_x(g2, pw), y0 + gauge_y(g2, o, pw), gauge_w(pw), GAUGE_NAMES[g2], tb[0..256], vn, a); + g2 = g2 + 1; + } + const scols : u32 = seg_cols(nseg, pw); + sg = 0; + while (sg < nseg) { + const sx : f64 = lx + @as(f64, @floatFromInt(sg % scols)) * (SEG + SEG_GAP); + const sy : f64 = y0 + bar_top(o, pw) + @as(f64, @floatFromInt(sg / scols)) * (SEG + SEG_GAP); + const sw1 : u32 = seg_widget(o, sg); + if (sw1 < WIDGET_COUNT and dc[D_ST + sw1] == WS_LOADED) { + cg_fill_rgba(c, 1.0, 1.0, 1.0, 0.92 * a); + } else { + cg_fill_rgba(c, 0.11, 0.11, 0.11, a); + } + cg_fill_rect(c, sx, sy, SEG, SEG); + sg = sg + 1; + } + // THE CONSOLE: this letter's instruments (its widgets' cards, then its app surfaces), each with its agents + const nh : u32 = heavy_count(nc[0..32], na); + if (nh > 0) { + const cnt : u32 = nc[0]; + const top : f64 = y0 + con_top(o, nc[0..32], na, pw, 0); + const ch : f64 = con_h(cnt, pw); + if (top + ch >= vt and top <= sh) { + bracket_frame(c, lx, top, pw, ch, 10.0, a, false); + cg_fill_rgba(c, 1.0, 1.0, 1.0, a); + const lw : f64 = draw_fit(c, kit, AGENT_LETTERS[o - 1], AGENT_LETTERS[o - 1].len, lx + 12.0, top + 18.0, 17.0, WEIGHT_HEAVY, true, 0.0); + cg_fill_rgba(c, 1.0, 1.0, 1.0, TEXT_WHITE * a); + n = agent_domain(o, tb[0..256]); + draw_fit(c, kit, tb[0..256], n, lx + 12.0 + lw + 10.0, top + 16.0, 11.0, WEIGHT_SEMIBOLD, true, rx - 150.0); + n = put_readout(cnt, tb[0..256]); + const rw : f64 = bytes_w(kit, tb[0..256], n, 9.0, 0.0, true); + cg_fill_rgba(c, 1.0, 1.0, 1.0, MUTED_WHITE * a); + draw_fit(c, kit, tb[0..256], n, rx - 12.0 - rw, top + 16.0, 9.0, 0.0, true, 0.0); + const tcol : u32 = tile_cols(pw); + var t : u32 = 0; + while (t < cnt) { + const tx2 : f64 = lx + CPAD + dash_cell_x(t, inner_w(pw), tcol, GAP); + const ty : f64 = top + CON_HEAD + dash_cell_y(t, tcol, cell_h(pw), GAP); + if (ty + cell_h(pw) >= vt and ty <= sh) { + const wi : u32 = con_widget(o, t); + const hot : bool = wi == hov_w and wi < WIDGET_COUNT; + var img : u64 = 0; + if (wi < WIDGET_COUNT) { img = card_img(dc, gal, gt, hb[0..hn], wi); } + draw_card(c, tx2, ty, tile_w(pw), tile_h(pw), img, hot, a); + if (hot) { + cg_fill_rgba(c, 1.0, 1.0, 1.0, a); + } else { + cg_fill_rgba(c, 1.0, 1.0, 1.0, 0.72 * a); + } + if (wi < WIDGET_COUNT) { + n = widget_label(dc, gal, gt, wi, tb[0..256]); + } else { + const sf : u32 = con_surface(o, t); + n = put_bytes(tb[0..256], 0, SURFACE_IDS[sf]); + cg_fill_rgba(c, 1.0, 1.0, 1.0, LABEL_WHITE * a); + draw_fit(c, kit, "APP SURFACE", 11, tx2 + 6.0, ty + tile_h(pw) - 22.0, 7.5, WEIGHT_SEMIBOLD, true, tx2 + tile_w(pw) - 4.0); + cg_fill_rgba(c, 1.0, 1.0, 1.0, 0.9 * a); + draw_fit(c, kit, SURFACE_KINDS[sf], SURFACE_KINDS[sf].len, tx2 + 6.0, ty + tile_h(pw) - 10.0, 8.5, 0.0, true, tx2 + tile_w(pw) - 4.0); + cg_fill_rgba(c, 1.0, 1.0, 1.0, 0.72 * a); + } + draw_fit(c, kit, tb[0..256], n, tx2, ty + tile_h(pw) + 11.0, 9.0, 0.0, true, tx2 + tile_w(pw)); + draw_badges(c, kit, tx2 + 3.0, ty + 3.0, o, t, a); + } + t = t + 1; + } + } + } else { + // an honest empty console: the letter has no widget and no surface yet; the suggestion from agent_parts + const iy : f64 = y0 + idle_top(o, nc[0..32], na, pw); + bracket_frame(c, lx, iy, pw, CHIP_H, 5.0, a, false); + cg_fill_rgba(c, 1.0, 1.0, 1.0, a); + const lw : f64 = draw_fit(c, kit, AGENT_LETTERS[o - 1], AGENT_LETTERS[o - 1].len, lx + 10.0, iy + 23.0, 15.0, WEIGHT_HEAVY, true, 0.0); + cg_fill_rgba(c, 1.0, 1.0, 1.0, LABEL_WHITE * a); + draw_fit(c, kit, "IDLE", 4, lx + 10.0 + lw + 10.0, iy + 22.0, 9.0, WEIGHT_SEMIBOLD, true, 0.0); cg_fill_rgba(c, 1.0, 1.0, 1.0, MUTED_WHITE * a); - const nw : f64 = draw_str(c, kit, HOME_NAMES[h], 14.0 + tw + 12.0, base, 11.0, WEIGHT_MEDIUM); + draw_fit(c, kit, NO_WIDGETS[o - 1], NO_WIDGETS[o - 1].len, lx + 10.0 + lw + 56.0, iy + 22.0, 9.0, 0.0, true, rx - 10.0); + } + // COURSE MAPS: the letters whose home is t27u carry the 7 courses, rows of 9 modules, three lessons each + if (h == H_T27U) { + const ctop : f64 = y0 + courses_top(o, nc[0..32], na, pw); + cg_fill_rgba(c, 1.0, 1.0, 1.0, a); + draw_fit(c, kit, "COURSE MAPS", 11, lx, ctop + 20.0, 13.0, WEIGHT_HEAVY, true, 0.0); cg_fill_rgba(c, 1.0, 1.0, 1.0, LABEL_WHITE * a); - const rx : f64 = 14.0 + tw + 12.0 + nw + 12.0; - const rn : usize = HOME_ROLES[h].len; - const rs : u64 = cf_bytes(0, HOME_ROLES[h], rn, UTF8, false); - if (rs != 0) { - draw_text_fit(c, kit, rs, rx, base, 10.0, 0.0, false, sw - 14.0); - cf_release(rs); + draw_fit(c, kit, "7 COURSES / 9 MODULES / 3 LESSONS", 33, lx + 130.0, ctop + 20.0, 9.0, 0.0, true, rx); + var cc : u32 = 0; + while (cc < COURSE_COUNT) { + const mtop : f64 = y0 + cm_top(o, nc[0..32], na, pw, cc); + if (mtop + cm_h() >= vt and mtop <= sh) { + bracket_frame(c, lx, mtop, pw, cm_h(), 10.0, a, false); + cg_fill_rgba(c, 1.0, 1.0, 1.0, TEXT_WHITE * a); + n = put_bytes(tb[0..256], 0, "COURSE "); + n = put_dec(tb[0..256], n, @as(u64, cc + 1)); + n = put_bytes(tb[0..256], n, " "); + n = put_bytes(tb[0..256], n, COURSE_IDS[cc]); + draw_fit(c, kit, tb[0..256], n, lx + 12.0, mtop + 17.0, 10.5, WEIGHT_SEMIBOLD, true, rx - 100.0); + var m : u32 = 0; + while (m < MODULES) { + const ry : f64 = mtop + cm_row_y(m); + if (ry + 30.0 >= vt and ry <= sh) { + n = put_two(m + 1, tb[0..256]); + draw_outline(c, kit, tb[0..256], n, lx + CPAD + 2.0, ry + 24.0, 24.0, a); + var l : u32 = 0; + while (l < DASH_LESSONS_PER_MODULE) { + const lwi : u32 = course_widget(cc, m * DASH_LESSONS_PER_MODULE + l); + const lcw : f64 = cell_w(cm_lessons_w(pw), DASH_LESSONS_PER_MODULE, GAP); + const lcx : f64 = lx + cm_lessons_x() + dash_cell_x(l, cm_lessons_w(pw), DASH_LESSONS_PER_MODULE, GAP); + const hot : bool = lwi == hov_w and lwi < WIDGET_COUNT; + round_rect(c, lcx, ry, lcw, 30.0, 4.0); + cg_line_width(c, 1.0); + if (hot) { + cg_stroke_rgba(c, 1.0, 1.0, 1.0, 0.85 * a); + } else { + cg_stroke_rgba(c, 0.165, 0.165, 0.165, a); + } + cg_stroke_path(c); + cg_fill_rgba(c, 1.0, 1.0, 1.0, LABEL_WHITE * a); + n = put_dec(tb[0..256], 0, @as(u64, m * DASH_LESSONS_PER_MODULE + l + 1)); + draw_fit(c, kit, tb[0..256], n, lcx + 6.0, ry + 11.0, 7.5, 0.0, true, 0.0); + if (lwi < WIDGET_COUNT) { + const ow : u32 = dash_agents_of_widget(lwi, 0); + if (ow > 0) { + draw_fit(c, kit, AGENT_LETTERS[ow - 1], AGENT_LETTERS[ow - 1].len, lcx + lcw - 18.0, ry + 11.0, 7.5, WEIGHT_BOLD, true, 0.0); + } + } + if (hot) { + cg_fill_rgba(c, 1.0, 1.0, 1.0, a); + } else { + cg_fill_rgba(c, 1.0, 1.0, 1.0, 0.8 * a); + } + n = widget_label(dc, gal, gt, lwi, tb[0..256]); + draw_fit(c, kit, tb[0..256], n, lcx + 6.0, ry + 23.0, 9.5, 0.0, true, lcx + lcw - 6.0); + l = l + 1; + } + } + m = m + 1; + } + } + cc = cc + 1; } } - cg_fill_rgba(c, 1.0, 1.0, 1.0, LINE_WHITE * a); - cg_fill_rect(c, 0.0, WS_TOP + HEADER_H, sw, HAIR); - const l : f64 = body_left(sw, WS_EXPANDED, true); - const t : f64 = body_top(WS_EXPANDED); - const w : f64 = body_w(sw, WS_EXPANDED, true); - const bh : f64 = body_h(sw, sh, WS_EXPANDED, true); - const r : f64 = hex_r(w, bh); - const room : f64 = SQRT3 * r - 16.0; - var hk : u32 = CARD_SLOTS; - if (a >= 1.0) { hk = home_card_at(mx, my, sw, sh, p); } - var tb : [64]u8 = undefined; - var k : u32 = 0; - while (k < CARD_SLOTS) { - const ka : f64 = card_alpha(a, k); - var kf : f64 = STRICT_CARD_FILL; - if (k == hk) { kf = STRICT_HOVER_FILL; } - const kx : f64 = hex_c(k, 0, l, t, w, bh); - const ky : f64 = hex_c(k, 1, l, t, w, bh) + (1.0 - ka) * 6.0; - hex_path(c, kx, ky, r - 0.25); - cg_fill_rgba(c, 1.0, 1.0, 1.0, kf * ka); + // the scrollbar: a faint line with a brighter thumb, only when the page is taller than the view + const total : f64 = page_h(o, nc[0..32], na, pw); + const smax : u32 = dash_scroll_max(total, vh); + if (smax > 0) { + const th : f64 = thumb_h(vh, total); + cg_fill_rgba(c, 1.0, 1.0, 1.0, LINE_WHITE * a); + cg_fill_rect(c, sw - 8.0, vt + 4.0, 2.0, vh - 8.0); + cg_fill_rgba(c, 1.0, 1.0, 1.0, 0.45 * a); + cg_fill_rect(c, sw - 8.0, vt + thumb_y(hs, smax, vh, th), 2.0, th); + } + cg_restore(c); +} + +/// A lamp: a small disc, lit when the letter has parts, a ring when it has none. +fn n_lamp(c: u64, x: f64, y: f64, lit: bool, a: f64) -> void { + if (lit) { + cg_fill_rgba(c, 1.0, 1.0, 1.0, 0.95 * a); + round_rect(c, x - 2.0, y - 2.0, 4.0, 4.0, 2.0); cg_fill_path(c); - cg_line_width(c, cell_line(k)); - hex_path(c, kx, ky, r - 0.25); - cg_stroke_rgba(c, 1.0, 1.0, 1.0, cell_edge(k, hk, CARD_SLOTS) * ka); + } else { + cg_stroke_rgba(c, 1.0, 1.0, 1.0, 0.35 * a); + cg_line_width(c, 0.8); + round_rect(c, x - 2.0, y - 2.0, 4.0, 4.0, 2.0); cg_stroke_path(c); - if (h < HOME_COUNT and k < home_cards(h)) { - cg_fill_rgba(c, 1.0, 1.0, 1.0, TEXT_WHITE * ka); - const n1 : usize = card_title(h, k, tb[0..64]); - draw_mid(c, kit, tb[0..64], n1, kx, ky - 3.0, 11.0, WEIGHT_SEMIBOLD, room); - cg_fill_rgba(c, 1.0, 1.0, 1.0, MUTED_WHITE * ka); - const n2 : usize = card_line(h, k, tb[0..64]); - draw_mid(c, kit, tb[0..64], n2, kx, ky + 12.0, 9.5, 0.0, room); - cg_fill_rgba(c, 1.0, 1.0, 1.0, LABEL_WHITE * ka); - const n3 : usize = card_note(h, k, tb[0..64]); - draw_mid(c, kit, tb[0..64], n3, kx, ky + 25.0, 8.5, 0.0, room); - } - k = k + 1; } } @@ -2755,7 +3458,7 @@ pub fn on_keys_foot(x: f64, y: f64, sh: f64, ws: u32, collapsed: bool) -> bool { return in_side(x, ws, collapsed) and on_keys_link(x, y, SIDE_W, sh, FOOT_H, DOC_LINK_W); } -fn draw(c: u64, kit: [48]u64, frame: u32, state: u32, hov: u32, ma: f64, shown: u32, task: u32, space: u32, sp: u32, ss: u64, cur: u32, collapsed: bool, mx: f64, my: f64, z: f64, sw: f64, sh: f64, ws: u32, full: bool, qp: bool, ring: [8]u64, n: u32, now: u64, sug: u32, god: bool, glow: f64, dk: [128]u64, dp: bool, darmed: u32, doff: u32, dgo: u32, dlive: bool, dlabel: u64, tst: []const u32, tcells: []const u32, topen: u32, tcur: bool, kyp: bool, kstored: []const u32, kmask: []const u8, kmlen: []const u32, kst: []const u32, kfail: []const u32, kopen: []const u64, kedit: u32, kbuf: []const u8, klen: u32, karm: u32, kbusy: u32, kemask: u32, kewhy: u32, ktpick: u32) -> void { +fn draw(c: u64, kit: [48]u64, frame: u32, state: u32, hov: u32, ma: f64, shown: u32, task: u32, space: u32, sp: u32, ss: u64, cur: u32, collapsed: bool, mx: f64, my: f64, z: f64, sw: f64, sh: f64, ws: u32, full: bool, qp: bool, ring: [8]u64, n: u32, now: u64, sug: u32, god: bool, glow: f64, dk: [128]u64, dp: bool, darmed: u32, doff: u32, dgo: u32, dlive: bool, dlabel: u64, tst: []const u32, tcells: []const u32, topen: u32, tcur: bool, kyp: bool, kstored: []const u32, kmask: []const u8, kmlen: []const u32, kst: []const u32, kfail: []const u32, kopen: []const u64, kedit: u32, kbuf: []const u8, klen: u32, karm: u32, kbusy: u32, kemask: u32, kewhy: u32, ktpick: u32, hs: u32, cons: bool, dc: []u64, gal: []const u8, gt: []const u32) -> void { draw_backdrop(c, sw, sh, full); const ha : f64 = 1.0 - ma; if (ha > 0.0) { @@ -2765,11 +3468,12 @@ fn draw(c: u64, kit: [48]u64, frame: u32, state: u32, hov: u32, ma: f64, shown: if (ma > 0.0) { // the screen of petal `shown`: the chat workspace for the CHAT petal (index 2), a home tab for t27a .. // t27u, an empty frame with the petal's name for every other petal - if (shown == PETAL_CHAT) { + if (shown == PETAL_CHAT and cons == false) { draw_chat(c, kit, shown, task, space, sp, ss, cur, ma, collapsed, mx, my, sw, sh, ws, dlabel, dp, full, topen != PANE_NONE); if (topen != PANE_NONE) { draw_term(c, kit, tst, tcells, tcur, ma, collapsed, sw, sh, ws, full, qp or dp or kyp); } + draw_switch(c, kit, "CONSOLE >", sw, ma, mx, my); } else { - draw_home(c, kit, shown, ma, mx, my, sw, sh, full); + draw_home(c, kit, shown, ma, collapsed, mx, my, sw, sh, ws, hs, dlabel, dp, full, dc, gal, gt); } } // exactly one right-hand panel is drawn (the Queen's, else DOCTOR's, else KEYS'): its ground first @@ -2777,7 +3481,7 @@ fn draw(c: u64, kit: [48]u64, frame: u32, state: u32, hov: u32, ma: f64, shown: if (qp) { draw_queen_panel(c, kit, sw, sh, mx, my, view_of(menu_open(state), shown), space, ss, cur, ring, n, now, sug, god); } if (dp and qp == false) { draw_doctor_panel(c, kit, dk, sw, sh, mx, my, darmed, doff, dgo, dlive); } if (kyp and qp == false and dp == false) { draw_keys_panel(c, kit, sw, sh, mx, my, kstored, kmask, kmlen, kst, kfail, kopen, now, kedit, kbuf, klen, karm, kbusy, kemask, kewhy, ktpick); } - if (ma > 0.0 and shown == PETAL_CHAT and ws == WS_EXPANDED and collapsed == false) { + if (ma > 0.0 and shown == PETAL_CHAT and cons == false and ws == WS_EXPANDED and collapsed == false) { // K. KEYS in the foot, left of DOCTOR var ka : f64 = LABEL_WHITE; if (kyp or on_keys_foot(mx, my, sh, ws, collapsed)) { ka = TEXT_WHITE; } @@ -3440,6 +4144,172 @@ fn draw_doctor_panel(c: u64, kit: [48]u64, dk: [128]u64, sw: f64, sh: f64, mx: f } } +// ---- the dashboard's background work: the gallery text, the card files, the live web view ---- + +fn gallery_load(dc: []u64, gal: []u8, gt: []u32, hb: []const u8) -> void { + var pb : [512]u8 = undefined; + put_gallery_path(hb, pb[0..512]); + const fd : i32 = sys_open_ro(pb[0..512], 0); + if (fd < 0) { return; } + const r : i64 = sys_read(fd, gal, @as(u64, GAL_MAX)); + sys_close(fd); + if (r <= 0) { return; } + const g : u32 = gal_parse(gal, @as(usize, @intCast(r)), gt); + if (g == WIDGET_COUNT) { + dc[D_GN] = @as(u64, g); + dc[D_GEN] = dc[D_GEN] + 1; + } +} + +/// Start `/usr/bin/curl -sfL -o .part ` for widget wi (WIDGET_COUNT: the gallery) in slot `slot` +/// without waiting for it. +fn dash_spawn(dc: []u64, gal: []const u8, gt: []const u32, hb: []const u8, jp: []u32, jwi: []u32, slot: u32, wi: u32) -> void { + var fb : [512]u8 = undefined; + var ub : [320]u8 = undefined; + var pb : [520]u8 = undefined; + var fnn : usize = 0; + var un : usize = 0; + if (wi == WIDGET_COUNT) { + fnn = put_gallery_path(hb, fb[0..512]); + un = put_gallery_url(ub[0..320]); + } else { + fnn = put_card_path(hb, gal, gt, wi, fb[0..512]); + un = put_card_url(gal, gt, wi, ub[0..320]); + } + if (un == 0) { + if (wi < WIDGET_COUNT) { dc[D_ST + wi] = WS_FAILED; } + return; + } + const pn : usize = put_part(fb[0..512], fnn, pb[0..520]); + var ab : [1024]u8 = undefined; + var offs : [8]u32 = undefined; + put_argv(pb[0..520], pn, ub[0..320], un, ab[0..1024], offs[0..8]); + var argv : [8]u64 = undefined; + var k : u32 = 0; + while (k < 7) { + argv[k] = sys_addr(ab[@as(usize, offs[k]) .. 1024], ab[0..1], 0); + k = k + 1; + } + argv[7] = 0; + var cp : [16]u8 = undefined; + put_bytes(cp[0..16], 0, "/usr/bin/curl"); + cp[13] = 0; + var pid : [2]u32 = undefined; + pid[0] = 0; + const rc : i32 = sys_spawn(pid, cp[0..16], 0, 0, argv, 0); + if (rc != 0 or pid[0] == 0) { + if (wi < WIDGET_COUNT) { dc[D_ST + wi] = WS_FAILED; } + return; + } + jp[slot] = pid[0]; + jwi[slot] = wi; + if (wi < WIDGET_COUNT) { dc[D_ST + wi] = WS_FETCH; } +} + +/// Once a second: reap finished fetches (rename the .part file on success), read the gallery when it is +/// there, ask for it when it is not, and start the fetches the draw asked for (at most FETCH_SLOTS at once). +fn dash_tick(dc: []u64, gal: []u8, gt: []u32, hb: []const u8, jp: []u32, jwi: []u32, now: u64) -> void { + if (dc[D_LAST] == now) { return; } + dc[D_LAST] = now; + var st : [2]u32 = undefined; + var busy_gallery : bool = false; + var j : u32 = 0; + while (j < FETCH_SLOTS) { + if (jp[j] != 0) { + st[0] = 0; + const r : i32 = sys_waitpid(@as(i32, @intCast(jp[j])), st, 1); + if (r != 0) { + const wi : u32 = jwi[j]; + var fb : [512]u8 = undefined; + var pb : [520]u8 = undefined; + var fnn : usize = 0; + if (wi == WIDGET_COUNT) { + fnn = put_gallery_path(hb, fb[0..512]); + } else { + fnn = put_card_path(hb, gal, gt, wi, fb[0..512]); + } + put_part(fb[0..512], fnn, pb[0..520]); + const ok : bool = r > 0 and st[0] == 0; + if (ok) { sys_rename(pb[0..520], fb[0..512]); } else { sys_unlink(pb[0..520]); } + if (wi == WIDGET_COUNT) { + if (ok) { gallery_load(dc, gal, gt, hb); } + } else if (ok) { + dc[D_ST + wi] = WS_NEW; + } else { + dc[D_ST + wi] = WS_FAILED; + } + dc[D_GEN] = dc[D_GEN] + 1; + jp[j] = 0; + } else if (jwi[j] == WIDGET_COUNT) { + busy_gallery = true; + } + } + j = j + 1; + } + if (dc[D_GN] == 0) { + gallery_load(dc, gal, gt, hb); + if (dc[D_GN] == 0 and busy_gallery == false and now >= dc[D_GTRY] + 10) { + dc[D_GTRY] = now; + j = 0; + while (j < FETCH_SLOTS) { + if (jp[j] == 0) { + dash_spawn(dc, gal, gt, hb, jp, jwi, j, WIDGET_COUNT); + j = FETCH_SLOTS; + } else { + j = j + 1; + } + } + } + return; + } + var wi2 : u32 = 0; + j = 0; + while (wi2 < WIDGET_COUNT and j < FETCH_SLOTS) { + if (dc[D_ST + wi2] == WS_WANT) { + while (j < FETCH_SLOTS and jp[j] != 0) { j = j + 1; } + if (j < FETCH_SLOTS) { dash_spawn(dc, gal, gt, hb, jp, jwi, j, wi2); } + } + wi2 = wi2 + 1; + } +} + +/// True while a fetch is running or asked for: the loop keeps ticking. +fn dash_busy(dc: []u64, jp: []const u32) -> bool { + var j : u32 = 0; + while (j < FETCH_SLOTS) { + if (jp[j] != 0) { return true; } + j = j + 1; + } + return false; +} + +fn live_hide(dc: []u64) -> void { + if (dc[D_WEB] != 0) { + msg0(dc[D_WEB], sel("removeFromSuperview")); + msg0(dc[D_WEB], sel("release")); + dc[D_WEB] = 0; + } + dc[D_LIVE] = 0; +} + +/// Open widget wi live: one WKWebView over the content area, loading the page the site itself embeds. +fn live_show(dc: []u64, glass: u64, gal: []const u8, gt: []const u32, wi: u32) -> void { + if (dc[D_GN] == 0 or wi >= WIDGET_COUNT) { return; } + live_hide(dc); + const cfg : u64 = msg0(cls("WKWebViewConfiguration"), sel("new")); + const wv : u64 = msg_rect_obj(msg0(cls("WKWebView"), sel("alloc")), sel("initWithFrame:configuration:"), 0.0, 0.0, 100.0, 100.0, cfg); + if (wv == 0) { return; } + msg1(glass, sel("addSubview:"), wv); + var ub : [320]u8 = undefined; + const un : usize = put_page_url(gal, gt, wi, ub[0..320]); + const us : u64 = cf_bytes(0, ub[0..320], un, UTF8, false); + const url : u64 = msg1(cls("NSURL"), sel("URLWithString:"), us); + cf_release(us); + msg1(wv, sel("loadRequest:"), msg1(cls("NSURLRequest"), sel("requestWithURL:"), url)); + dc[D_WEB] = wv; + dc[D_LIVE] = @as(u64, wi) + 1; +} + // ---- the app ---- pub fn main() -> void { @@ -3630,6 +4500,7 @@ pub fn main() -> void { const k_ss : u64 = cf("t27os.sessions2"); const k_sp : u64 = cf("t27os.spaces"); const k_cur : u64 = cf("t27os.cur"); + const k_hs : u64 = cf("t27os.hs"); const k_flags : u64 = cf("t27os.flags"); const k_evn : u64 = cf("t27os.evn"); var k_ev : [8]u64 = undefined; @@ -3836,6 +4707,48 @@ pub fn main() -> void { var darmed : u32 = ARMED_NONE; var doff : u32 = 0; var dacc : f64 = 0.0; + // a home tab's page: the scroll offset in whole points, the wheel's fraction, the home it belongs to + var hs : u32 = 0; + var hacc : f64 = 0.0; + var hs_for : u32 = 0; + // petal C shows the chat, or (this flag) the cockpit of letter C + var cons2 : bool = false; + // the instrument panel: its state, the gallery text and the offsets parsed from it, the fetches in flight + var dcv : [512]u64 = undefined; + var gal : [49152]u8 = undefined; + var gt : [1200]u32 = undefined; + var jpid : [6]u32 = undefined; + var jwi : [6]u32 = undefined; + var dz : usize = 0; + while (dz < 512) { + dcv[dz] = 0; + dz = dz + 1; + } + dz = 0; + while (dz < 1200) { + gt[dz] = 0; + dz = dz + 1; + } + dz = 0; + while (dz < 6) { + jpid[dz] = 0; + jwi[dz] = 0; + dz = dz + 1; + } + dcv[D_SEL_INIT] = sel("initWithContentsOfFile:"); + dcv[D_SEL_CG] = sel("CGImageForProposedRect:context:hints:"); + dcv[D_CLS_IMG] = cls("NSImage"); + dcv[D_SEL_ALLOC] = s_alloc; + var dhb : [256]u8 = undefined; + const dhn : usize = ns_bytes(home_dir(), dhb[0..256]); + var dpath : [300]u8 = undefined; + put_bytes(dpath[0..300], put_bytes(dpath[0..300], 0, dhb[0..dhn]), "/.t27os"); + dpath[dhn + 7] = 0; + sys_mkdir(dpath[0..300], 493); + put_bytes(dpath[0..300], put_bytes(dpath[0..300], 0, dhb[0..dhn]), "/.t27os/cards"); + dpath[dhn + 13] = 0; + sys_mkdir(dpath[0..300], 493); + gallery_load(dcv[0..512], gal[0..49152], gt[0..1200], dhb[0..dhn]); var dgo : u32 = 0; var dnow : bool = true; // the KEYS panel: open, which numbers each provider holds (a bit mask: bit k = key k+1) and the masked text @@ -3942,6 +4855,7 @@ pub fn main() -> void { if (spaces_shown(sp) == 0) { sp = SPACES_SEED; } if (space_is_open(sp, space) == false) { space = space_at_row(sp, 0); } cur = @as(u32, @intCast(msg1(defs, sel("integerForKey:"), k_cur))); + hs = @as(u32, @intCast(msg1(defs, sel("integerForKey:"), k_hs))); if (cur >= session_count(ss)) { cur = SESSION_NONE; } const fl : u64 = msg1(defs, sel("integerForKey:"), k_flags); collapsed = fl % 2 == 1; @@ -3959,6 +4873,7 @@ pub fn main() -> void { if (menu_open(state)) { ma = 1.0; } msg2(defs, sel("setInteger:forKey:"), 0, k_hot); } + hs_for = shown; while (msg_test(win, s_visible)) { const pool : u64 = pool_push(); msg_point(msg1(win, s_value, k_mouse), s_get, pt); @@ -3982,8 +4897,12 @@ pub fn main() -> void { const home_live : bool = menu_open(state) == false and ma == 0.0 and my > AREA_TOP; // the CHAT petal's screen carries every chat control; a home tab (any other petal) carries only the // title bar's back chevron and its cards - const screen : bool = menu_open(state) and ma >= 0.5 and shown == PETAL_CHAT; - const hscreen : bool = menu_open(state) and ma >= 0.5 and shown != PETAL_CHAT; + const screen : bool = menu_open(state) and ma >= 0.5 and shown == PETAL_CHAT and cons2 == false; + const hscreen : bool = menu_open(state) and ma >= 0.5 and (shown != PETAL_CHAT or cons2); + // the sidebar a screen shows: the chat's, or a home tab's + var sws : u32 = ws; + if (shown != PETAL_CHAT or cons2) { sws = home_ws(ws); } + const hh : u32 = letter_of_petal(shown); now = unix_time(0); var sug : u32 = shown_suggestion(suggest_for(sessions_in(ss, space), cur != SESSION_NONE, screen and ktpick != TERM_NONE, edone, space), dismissed); var ev : u64 = msg_event(app, s_next, 0xffffffffffffffff, msg_f(date_cls, s_wait, fwait), mode, true); @@ -4125,17 +5044,39 @@ pub fn main() -> void { } docked = docked == false; } else if ((screen or hscreen) and on_nav_back(mx, my, full)) { - state = on_back(state); - ctl = C_HOME; + if (hscreen and dcv[D_LIVE] != 0) { + live_hide(dcv[0..512]); + } else { + state = on_back(state); + ctl = C_HOME; + } } else if (screen and on_open_dash(mx, my, sw, ws)) { dash = true; ctl = C_DASHBOARD; } else if (screen and on_close_dash(mx, my, ws, collapsed)) { dash = false; ctl = C_DASHBOARD; - } else if (screen and on_side_toggle(mx, my, ws, collapsed)) { + } else if ((screen or hscreen) and on_side_toggle(mx, my, sws, collapsed)) { collapsed = collapsed == false; ctl = C_SIDEBAR; + } else if (screen and on_switch(mx, my, sw, WS_TOP)) { + // the C petal's header: CONSOLE opens the cockpit of letter C + cons2 = true; + } else if (hscreen and shown == PETAL_CHAT and on_switch(mx, my, sw, WS_TOP)) { + cons2 = false; + } else if (hscreen and in_side(mx, home_ws(ws), collapsed) and side_letter_at(mx, my, WS_TOP) != 0) { + // H. a letter's chip in the sidebar opens that letter's petal (C opens its console, the chat is one switch away) + const hp : u32 = petal_of_letter(side_letter_at(mx, my, WS_TOP)); + state = hp; + shown = hp; + cons2 = hp == PETAL_CHAT; + ctl = C_WORLD; + carg = hp; + } else if (hscreen and dcv[D_LIVE] != 0 and on_live_back(mx, my, content_x(home_ws(ws), collapsed), home_view_top())) { + live_hide(dcv[0..512]); + } else if (hscreen and dcv[D_LIVE] == 0 and dash_pick(hh, mx, my, sw, content_x(home_ws(ws), collapsed), hs) != NO_WIDGET) { + // an instrument or a lesson cell opens its widget live, in place of the panel + live_show(dcv[0..512], glass, gal[0..49152], gt[0..1200], dash_pick(hh, mx, my, sw, content_x(home_ws(ws), collapsed), hs)); } else if (screen and on_new_chat(mx, my, sw, ws)) { // N. "+" opens one more chat of the selected agent in this space and selects it const nk : u32 = new_chat_agent(task); @@ -4185,12 +5126,12 @@ pub fn main() -> void { ktpick = term_pick(term_mask()); kewhy = OPEN_NONE; } - } else if (screen and on_doctor_link(mx, my, sh, ws, collapsed)) { + } else if ((screen or hscreen) and on_doctor_link(mx, my, sh, sws, collapsed)) { // D. DOCTOR in the foot opens or closes the panel; it takes the Queen panel's place dp = dp == false; if (dp) { qp = false; god = false; kyp = false; } darmed = ARMED_NONE; - } else if (screen and on_repo_link(mx, my, sh, ws, collapsed)) { + } else if ((screen or hscreen) and on_repo_link(mx, my, sh, sws, collapsed)) { // W. OPEN SOURCE T27 opens the repository in the default browser const url : u64 = msg1(cls("NSURL"), sel("URLWithString:"), cf(REPO_URL)); msg1(msg0(cls("NSWorkspace"), sel("sharedWorkspace")), sel("openURL:"), url); @@ -4249,7 +5190,7 @@ pub fn main() -> void { } } else if (home_live) { const p : u32 = on_tap(STATE_HOME, to_box(vx, 480, BOX_CX, sc), to_box(vy, 270, BOX_CY, sc)); - if (menu_open(p)) { state = p; shown = p; ctl = C_WORLD; carg = p; } + if (menu_open(p)) { state = p; shown = p; cons2 = false; ctl = C_WORLD; carg = p; } } } if (ty == EV_SCROLL and dp and on_panel(mx, my, sw, WS_TOP)) { @@ -4267,6 +5208,16 @@ pub fn main() -> void { } used = true; } + if (ty == EV_SCROLL and hscreen and dcv[D_LIVE] == 0 and on_home_view(mx, my, ws, collapsed) and ((qp or kyp or dp) and on_panel(mx, my, sw, WS_TOP)) == false) { + // H. a home tab's page scrolls with the wheel or trackpad; the offset stays between the top and the end + var hdy : f64 = msg_real(ev, s_scroll_y); + if (msg0(ev, s_precise) % 256 == 0) { hdy = hdy * 16.0; } + hacc = hacc + hdy; + const hwhole : i64 = @as(i64, @intFromFloat(hacc)); + hacc = hacc - @as(f64, @floatFromInt(hwhole)); + hs = dash_scroll_clamp(hs, 0 - hwhole, dash_total(hh, sw, content_x(home_ws(ws), collapsed)), home_view_h(sh)); + used = true; + } if (ty == EV_KEY_DOWN) { const key : u64 = msg0(ev, s_key); const flags : u64 = msg0(ev, s_flags); @@ -4274,7 +5225,12 @@ pub fn main() -> void { const digit : u32 = digit_of(key); const kp : u32 = petal_for_key(digit, flags); used = true; - if (key == KEY_J and cmd) { + if (hscreen and dcv[D_LIVE] != 0 and key != KEY_ESCAPE and cmd == false) { + // the live page owns the keyboard; Escape closes it + used = false; + } else if (hscreen and dcv[D_LIVE] != 0 and key == KEY_ESCAPE) { + live_hide(dcv[0..512]); + } else if (key == KEY_J and cmd) { // Q. Cmd+J opens or closes the Queen panel anywhere; with GOD MODE on it stops it const r : u32 = on_queen_key(qp, god); if (god) { ctl = C_GOD_OFF; } else { ctl = C_QUEEN_KEY; } @@ -4376,6 +5332,7 @@ pub fn main() -> void { } else if (kp != PETAL_NONE and menu_open(state) == false) { state = kp; shown = kp; + cons2 = false; } else if (key == KEY_S and cmd and has_mod(flags, MOD_SHIFT)) { collapsed = collapsed == false; } else if (key == KEY_F and cmd and has_mod(flags, MOD_CONTROL)) { @@ -4394,6 +5351,25 @@ pub fn main() -> void { if (used == false) { msg1(app, s_send, ev); } ev = msg_event(app, s_next, 0xffffffffffffffff, 0, mode, true); } + // H. another home starts at its top; a resized window holds the offset inside its page + if (shown != hs_for) { + live_hide(dcv[0..512]); + hs = 0; + hacc = 0.0; + hs_for = shown; + } + hs = dash_scroll_clamp(hs, 0, dash_total(letter_of_petal(shown), sw, content_x(home_ws(ws), collapsed)), home_view_h(sh)); + if (menu_open(state) == false) { cons2 = false; } + if (dcv[D_LIVE] != 0 and (menu_open(state) == false or (shown == PETAL_CHAT and cons2 == false))) { live_hide(dcv[0..512]); } + if (menu_open(state) and (shown != PETAL_CHAT or cons2)) { + dash_tick(dcv[0..512], gal[0..49152], gt[0..1200], dhb[0..dhn], jpid[0..6], jwi[0..6], now); + if (dcv[D_WEB] != 0) { + const wcx : f64 = content_x(home_ws(ws), collapsed); + const wvt : f64 = home_view_top(); + const wsc : f64 = fit(fr[2], fr[3], sw, sh); + msg_rect(dcv[D_WEB], sel("setFrame:"), view_x(live_x(wcx), fr[2], sw, wsc), view_y(live_y(wvt), live_h(sh, wvt), fr[3], sh, wsc), live_w(sw, wcx) * wsc, live_h(sh, wvt) * wsc); + } + } // K. a check button: the frame after the click shows "checking", then one number of the provider is // read and probed per frame (a probe blocks up to PROBE_TIMEOUT_S); its HTTP answer sets that key's status // and feeds the circuit breaker (keys.t27). Every stored key of the provider is checked, none is skipped. @@ -4678,6 +5654,11 @@ pub fn main() -> void { if (hscreen and on_nav_back(mx, my, full)) { on_control = true; } if (screen and (on_nav_back(mx, my, full) or on_side_toggle(mx, my, ws, collapsed))) { on_control = true; } if (screen and (on_open_dash(mx, my, sw, ws) or on_close_dash(mx, my, ws, collapsed))) { on_control = true; } + if (hscreen and (on_side_toggle(mx, my, sws, collapsed) or on_repo_link(mx, my, sh, sws, collapsed) or on_doctor_link(mx, my, sh, sws, collapsed))) { on_control = true; } + if (hscreen and ma >= 1.0 and in_side(mx, home_ws(ws), collapsed) and side_letter_at(mx, my, WS_TOP) != 0) { on_control = true; } + if ((screen or (hscreen and shown == PETAL_CHAT)) and ma >= 1.0 and on_switch(mx, my, sw, WS_TOP)) { on_control = true; } + if (hscreen and ma >= 1.0 and dcv[D_LIVE] == 0 and dash_pick(hh, mx, my, sw, content_x(home_ws(ws), collapsed), hs) != NO_WIDGET) { on_control = true; } + if (hscreen and ma >= 1.0 and dcv[D_LIVE] != 0 and on_live_back(mx, my, content_x(home_ws(ws), collapsed), home_view_top())) { on_control = true; } if (screen and ma >= 1.0 and card_live(term_up(screen, topen), mx, my, sw, sh, ws, collapsed) != CARD_SLOTS) { on_control = true; } if (screen and ma >= 1.0 and in_side(mx, ws, collapsed) and (row_at(WS_TOP, GROUP_QUEEN, spaces_shown(sp), 1, my) == 0 or row_at(WS_TOP, GROUP_SPACES, spaces_shown(sp), spaces_shown(sp), my) != ROW_NONE or row_at(WS_TOP, GROUP_AGENTS, spaces_shown(sp), sessions_in(ss, space), my) != ROW_NONE)) { on_control = true; } if (screen and on_new_chat(mx, my, sw, ws)) { on_control = true; } @@ -4693,6 +5674,8 @@ pub fn main() -> void { fk = fg_mix(fg_mix(fg_mix(fg_mix(fk, @as(u64, cur)), fg_flag(collapsed)), fg_mouse(mx, sw)), fg_mouse(my, sh)); fk = fg_mix(fg_mix(fg_mix(fg_mix(fk, fg_cell(sw)), fg_cell(sh)), @as(u64, ws)), fg_flag(full)); fk = fg_mix(fg_mix(fg_mix(fk, fg_flag(qp)), @as(u64, evn)), @as(u64, sug)); + fk = fg_mix(fg_mix(fk, @as(u64, hs)), fg_flag(cons2)); + fk = fg_mix(fg_mix(fg_mix(fk, dcv[D_GEN]), dcv[D_DEFER]), dcv[D_LIVE]); // the clock shows only in the queen panel's ages; outside it a ticking second must not count as a change if (qp) { fk = fg_mix(fk, now); } fk = fg_mix(fg_mix(fg_mix(fg_mix(fk, fg_flag(god)), fg_level(glow)), fg_flag(dp)), @as(u64, darmed)); @@ -4718,14 +5701,14 @@ pub fn main() -> void { fwait = fg_wait(fquiet, tlink == PANE_LIVE and topen != PANE_NONE, FRAME_S); msg_flag(tx_cls, s_quiet, true); if (fdraw and narrow) { - draw(cc, kit, frame, state, hov, ma, shown, task, space, sp, ss, cur, collapsed, mx, my, z, sw, sh, ws, full, qp, ring, evn, now, sug, god, glow, dk, dp, darmed, doff, dgo, walking, dlabel, tst[0..10], tcells[0..48000], topen, tcur, kyp, kstored[0..9], kmask[0..144], kmlen[0..9], kst[0..72], kfail[0..9], kopen[0..9], kedit, kbuf[0..256], klen, karm, kbusy, kemask, kewhy, ktpick); + draw(cc, kit, frame, state, hov, ma, shown, task, space, sp, ss, cur, collapsed, mx, my, z, sw, sh, ws, full, qp, ring, evn, now, sug, god, glow, dk, dp, darmed, doff, dgo, walking, dlabel, tst[0..10], tcells[0..48000], topen, tcur, kyp, kstored[0..9], kmask[0..144], kmlen[0..9], kst[0..72], kfail[0..9], kopen[0..9], kedit, kbuf[0..256], klen, karm, kbusy, kemask, kewhy, ktpick, hs, cons2, dcv[0..512], gal[0..49152], gt[0..1200]); const whole : u64 = cg_image(cc); const img : u64 = cg_crop(whole, 0.0, 0.0, sw * RETINA, sh * RETINA); msg1(layer, s_contents, img); cg_image_release(img); cg_image_release(whole); } else if (fdraw) { - draw(c, kit, frame, state, hov, ma, shown, task, space, sp, ss, cur, collapsed, mx, my, z, sw, sh, ws, full, qp, ring, evn, now, sug, god, glow, dk, dp, darmed, doff, dgo, walking, dlabel, tst[0..10], tcells[0..48000], topen, tcur, kyp, kstored[0..9], kmask[0..144], kmlen[0..9], kst[0..72], kfail[0..9], kopen[0..9], kedit, kbuf[0..256], klen, karm, kbusy, kemask, kewhy, ktpick); + draw(c, kit, frame, state, hov, ma, shown, task, space, sp, ss, cur, collapsed, mx, my, z, sw, sh, ws, full, qp, ring, evn, now, sug, god, glow, dk, dp, darmed, doff, dgo, walking, dlabel, tst[0..10], tcells[0..48000], topen, tcur, kyp, kstored[0..9], kmask[0..144], kmlen[0..9], kst[0..72], kfail[0..9], kopen[0..9], kedit, kbuf[0..256], klen, karm, kbusy, kemask, kewhy, ktpick, hs, cons2, dcv[0..512], gal[0..49152], gt[0..1200]); const whole : u64 = cg_image(c); const img : u64 = cg_crop(whole, 0.0, 0.0, sw * WIDE_RETINA, sh * WIDE_RETINA); msg1(layer, s_contents, img); @@ -4734,7 +5717,7 @@ pub fn main() -> void { } // K. keep what the window shows: write it once a change has held still (window_keep.t27) const wk_fl : u64 = wk_flags(collapsed, dash, qp, god, dp, auto_open); - const wk_k : u64 = wk_key(state, shown, task, space, ss, sp, cur, wk_fl, evn); + const wk_k : u64 = fg_mix(wk_key(state, shown, task, space, ss, sp, cur, wk_fl, evn), @as(u64, hs)); wk_n = wk_still(wk_n, wk_k, wk_last); wk_last = wk_k; if (frame == 0) { wk_saved = wk_k; } @@ -4752,6 +5735,7 @@ pub fn main() -> void { msg2(defs, sel("setInteger:forKey:"), ss, k_ss); msg2(defs, sel("setInteger:forKey:"), @as(u64, sp), k_sp); msg2(defs, sel("setInteger:forKey:"), @as(u64, cur), k_cur); + msg2(defs, sel("setInteger:forKey:"), @as(u64, hs), k_hs); msg2(defs, sel("setInteger:forKey:"), wk_fl, k_flags); msg2(defs, sel("setInteger:forKey:"), 1, k_kept); msg0(defs, sel("synchronize")); @@ -4788,6 +5772,7 @@ pub fn main() -> void { msg2(defs, sel("setInteger:forKey:"), ss, k_ss); msg2(defs, sel("setInteger:forKey:"), @as(u64, sp), k_sp); msg2(defs, sel("setInteger:forKey:"), @as(u64, cur), k_cur); + msg2(defs, sel("setInteger:forKey:"), @as(u64, hs), k_hs); msg2(defs, sel("setInteger:forKey:"), fl, k_flags); msg2(defs, sel("setInteger:forKey:"), 1, k_hot); msg0(defs, sel("synchronize")); @@ -5354,63 +6339,96 @@ test chat_is_the_third_petal_and_cmd_3_opens_it { assert PETAL_CHAT != 0; } -test each_home_petal_draws_its_own_home_and_the_rest_draw_the_empty_frame { - var p : u32 = 19; - while (p <= 25) { - assert home_of_petal(p) == p - 19; - assert home_cards(home_of_petal(p)) >= 2; +test each_petal_is_a_letter_and_draws_its_console { + var p : u32 = 0; + while (p < 27) { + assert letter_of_petal(p) == p + 1; + assert dash_total(letter_of_petal(p), 960.0, content_x(WS_EXPANDED, false)) > 0.0; p = p + 1; } - assert home_of_petal(PETAL_CHAT) == 7; - assert home_of_petal(0) == HOME_COUNT; - assert home_of_petal(1) == HOME_COUNT; - assert home_of_petal(26) == HOME_COUNT; - assert home_of_petal(18) == HOME_COUNT; - assert home_cards(home_of_petal(26)) == 0; -} - -test a_card_in_a_home_tab_is_hit_where_it_is_drawn_and_only_for_cards_that_exist { - const l : f64 = body_left(W_PT, WS_EXPANDED, true); - const t : f64 = body_top(WS_EXPANDED); - const w : f64 = body_w(W_PT, WS_EXPANDED, true); - const bh : f64 = body_h(W_PT, H_PT, WS_EXPANDED, true); - var p : u32 = 19; - while (p <= 25) { - const n : u32 = home_cards(home_of_petal(p)); - var hits : u32 = 0; + assert letter_of_petal(27) == 0; + assert dash_total(0, 960.0, content_x(WS_EXPANDED, false)) == 0.0; + assert letter_of_petal(PETAL_CHAT) == 3; +} + +test a_home_tab_keeps_the_chats_frame_and_the_narrow_window_keeps_its_own { + assert home_ws(WS_EXPANDED) == WS_EXPANDED; + assert home_ws(WS_DASHBOARD) == WS_EXPANDED; + assert home_ws(WS_COMPACT) == WS_COMPACT; + assert side_ws(PETAL_CHAT, WS_DASHBOARD) == WS_DASHBOARD; + assert side_ws(21, WS_DASHBOARD) == WS_EXPANDED; + assert side_ws(21, WS_COMPACT) == WS_COMPACT; + // the sidebar toggle sits where the chat's does, and answers on a home tab in the expanded and forced layouts + assert on_side_toggle(content_x(home_ws(WS_EXPANDED), false) + 20.0, WS_TOP + 22.0, home_ws(WS_EXPANDED), false); + assert on_side_toggle(20.0, WS_TOP + 22.0, home_ws(WS_EXPANDED), true); + assert on_side_toggle(20.0, WS_TOP + 22.0, home_ws(WS_COMPACT), false) == false; + assert on_nav_back(home_x(false) + 2.0, 0.5 * TITLE_H, false); + // the page starts under the header's divider + assert home_view_top() == WS_TOP + HEADER_H + 1.0; + assert home_view_h(H_PT) == H_PT - WS_TOP - HEADER_H - 1.0; + assert HEAD_H > 200.0; + assert WEIGHT_HEAVY > WEIGHT_BOLD; + assert on_home_view(SIDE_W + 40.0, home_view_top() + 5.0, WS_EXPANDED, false); + assert on_home_view(SIDE_W - 40.0, home_view_top() + 5.0, WS_EXPANDED, false) == false; + assert on_home_view(SIDE_W - 40.0, home_view_top() + 5.0, WS_EXPANDED, true); + assert on_home_view(SIDE_W + 40.0, WS_TOP + 10.0, WS_EXPANDED, false) == false; +} + +test a_letter_chip_in_the_sidebar_is_hit_where_it_is_drawn_and_opens_that_letters_petal { + var h : u32 = 0; + var seen : u32 = 0; + while (h < HOME_COUNT) { var k : u32 = 0; - while (k < CARD_SLOTS) { - const got : u32 = home_card_at(hex_c(k, 0, l, t, w, bh), hex_c(k, 1, l, t, w, bh), W_PT, H_PT, p); - if (k < n) { assert got == k; hits = hits + 1; } - if (k >= n) { assert got == CARD_SLOTS; } + while (k < agents_in(h)) { + const l : u32 = nth_agent(h, k); + const x : f64 = side_chip_x(k) + 0.5 * CHIP_W; + const y : f64 = side_chip_y(h, k, WS_TOP) + 0.5 * CHIP_H2; + assert side_letter_at(x, y, WS_TOP) == l; + assert petal_of_letter(l) == l - 1; + assert home_of_petal(petal_of_letter(l)) == h; + assert in_side(x, home_ws(WS_DASHBOARD), false); + seen = seen + 1; k = k + 1; } - assert hits == n; - p = p + 1; + h = h + 1; } - // a petal that is no home has no card to hit, and neither has the empty corner of the body - assert home_card_at(hex_c(0, 0, l, t, w, bh), hex_c(0, 1, l, t, w, bh), W_PT, H_PT, 26) == CARD_SLOTS; - assert home_card_at(1.0, 1.0, W_PT, H_PT, 21) == CARD_SLOTS; -} - -test the_home_tab_cards_do_not_overlap_and_hold_nine_slots { - const l : f64 = body_left(W_PT, WS_EXPANDED, true); - const t : f64 = body_top(WS_EXPANDED); - const w : f64 = body_w(W_PT, WS_EXPANDED, true); - const bh : f64 = body_h(W_PT, H_PT, WS_EXPANDED, true); - const r : f64 = hex_r(w, bh); - var a : u32 = 0; - while (a < CARD_SLOTS) { - var b : u32 = a + 1; - while (b < CARD_SLOTS) { - const dx : f64 = hex_c(a, 0, l, t, w, bh) - hex_c(b, 0, l, t, w, bh); - const dy : f64 = hex_c(a, 1, l, t, w, bh) - hex_c(b, 1, l, t, w, bh); - assert dx * dx + dy * dy > 4.0 * 0.75 * r * r; - b = b + 1; + assert seen == 27; + assert in_side(x_far(), WS_EXPANDED, false) == false; + assert side_letter_at(1.0, WS_TOP + 1.0, WS_TOP) == 0; +} + +fn x_far() -> f64 { return SIDE_W + 20.0; } + +test the_sidebar_of_letters_ends_above_the_foot { + assert dash_side_end() <= H_PT - FOOT_H; +} + +fn dash_side_end() -> f64 { return side_group_top(HOME_COUNT, WS_TOP); } + +test the_scroll_of_a_console_ends_with_the_page_and_the_thumb_stays_on_its_line { + const vh : f64 = home_view_h(H_PT); + const cx : f64 = content_x(WS_EXPANDED, false); + var o : u32 = 1; + var long : u32 = 0; + while (o <= LETTER_COUNT) { + const tot : f64 = dash_total(o, 960.0, cx); + const m : u32 = dash_scroll_max(tot, vh); + assert dash_scroll_clamp(m, 5000, tot, vh) == m; + assert dash_scroll_clamp(0, -5000, tot, vh) == 0; + assert tot - @as(f64, @floatFromInt(m)) <= vh + 1.0; + if (m > 0) { + long = long + 1; + const th : f64 = thumb_h(vh, tot); + assert th >= 28.0; + assert th <= vh - 8.0; + assert thumb_y(0, m, vh, th) == 4.0; + assert @abs(thumb_y(m, m, vh, th) + th - (vh - 4.0)) < 0.001; + assert thumb_y(m / 2, m, vh, th) > 4.0; } - a = a + 1; + o = o + 1; } - assert CARD_SLOTS == 9; - // the title bar's back chevron answers on a home tab exactly as on the chat - assert on_nav_back(home_x(false) + 2.0, 0.5 * TITLE_H, false); + // the letters with many instruments (K, Z with the courses) scroll + assert long >= 2; + assert dash_scroll_max(dash_total(26, 960.0, cx), vh) > dash_scroll_max(dash_total(3, 960.0, cx), vh); + assert thumb_y(0, 0, vh, 28.0) == 4.0; } diff --git a/specs/t27os/homes.t27 b/specs/t27os/homes.t27 index 6fcb561c78..c878726b78 100644 --- a/specs/t27os/homes.t27 +++ b/specs/t27os/homes.t27 @@ -23,11 +23,9 @@ ; fasm, pins, JTAG, timing, Verilog and every `t27c-` page are t27c's; GoldenFloat and training pieces ; are t27t's; a lesson widget that exists only for a course (basics-*, golden-*) is t27u's; the Queen's ; loop and arena are the Queen's. A lesson may open any widget: that is COURSE_WIDGETS, a second link. -; - Petals: T27 OS has 27 petals in 3 realms of 9 (specs/t27f/petal_menu.t27). Owner: CHAT moves to the -; THIRD petal (index 2) and the pair chat + commander lives there; each home gets a petal tab. Default: -; CHAT and LOGS swap places, and the homes take petals 19..25 of the third realm in the order t27a, t27b, -; t27c, t27f, t27o, t27t, t27u. The Queen's tab is the CHAT petal. ABOUT stays at 26. Moving a home is one -; edit to HOME_PETAL and to WORLD_NAMES in petal_menu.t27 (the one definition of the names). The seven worlds REELS .. COMMUNITY give up their petals. +; - Petals: T27 OS has 27 petals in 3 realms of 9 (specs/t27f/petal_menu.t27). Owner, 2026-10-20: the petals ARE +; the 27 agent letters, petal p = ordinal p + 1; CHAT (with the commander) is the third petal, C COMPILER. +; The homes are sidebar groups of letters, not petals. ; Not established from evidence (so not guessed): what "commander" is, since the repository names no such ; component; whether the tri-fpga-* casts belong to t27c or to the `tri` CLI (filed under t27c because ; `tri fpga` drives `t27c silicon`); which widget a t27a, t27b, t27f or t27o page would be (the gallery has @@ -297,44 +295,50 @@ pub fn widgets_without_course() -> u32 { ; --- L6. Petals ------------------------------------------------------------------------------------------ pub const PETAL_CHAT : u32 = 2; -pub const HOME_PETAL : [8]u32 = [19, 20, 21, 22, 23, 24, 25, 2]; -/// The 27 world names are WORLD_NAMES of t27f::petal_menu (the single definition): CHAT at index 2, the seven -/// product homes at 19..25 where REELS .. COMMUNITY stood, ABOUT at 26. The tests below pin it against HOME_PETAL. - -pub fn petal_of_home(h: u32) -> u32 { - if (home_ok(h) == false) { - return PETAL_NONE; - } - return HOME_PETAL[h]; +/// Owner, 2026-10-20: the 27 petals ARE the 27 agent letters. Petal p is agent ORDINAL p + 1 (A .. Z, Ti = 27); +/// WORLD_NAMES of t27f::petal_menu holds the 27 short labels (the single definition). The eight homes are no +/// longer petals: they are the groups of the sidebar, each listing its letters (AGENT_HOME). +pub fn letter_of_petal(p: u32) -> u32 { + if (p >= PETAL_COUNT) { return 0; } + return p + 1; } -pub fn home_of_petal(p: u32) -> u32 { - var h : u32 = 0; - while (h < HOME_COUNT) { - if (HOME_PETAL[h] == p) { return h; } - h = h + 1; - } - return HOME_COUNT; +pub fn petal_of_letter(o: u32) -> u32 { + if (o < 1 or o > AGENT_COUNT) { return PETAL_NONE; } + return o - 1; } -; --- L7. The cards of a home tab ------------------------------------------------------------------------- -; A home tab is a 3 x 3 grid of cards (petal_menu CARD_SLOTS). The order is fixed: the agents that live in -; the home (letter + name; four cards at most, the fourth reads "+N agents" when there are more), then one -; card with the home's widget total, then the widget groups (the runs of RUN_START that belong to the home; -; four at most, the last "+N groups" when there are more), then, for t27u only, the seven courses. A home -; with fewer than 9 cards leaves the rest empty. Counts are measured from the tables above, never typed. - -pub const HOME_CARD_SLOTS : u32 = 9; -pub const MAX_AGENT_CARDS : u32 = 4; -pub const MAX_GROUP_CARDS : u32 = 4; - -pub const CK_NONE : u32 = 0; -pub const CK_AGENT : u32 = 1; -pub const CK_AGENT_MORE : u32 = 2; -pub const CK_WIDGETS : u32 = 3; -pub const CK_GROUP : u32 = 4; -pub const CK_GROUP_MORE : u32 = 5; -pub const CK_COURSE : u32 = 6; +/// The home whose group holds the letter of petal p. +pub fn home_of_petal(p: u32) -> u32 { return home_of_agent(letter_of_petal(p)); } + +; --- L7. The list of a home tab -------------------------------------------------------------------------- +; Owner, 2026-10-20: ugly design, no limit of cells, make everything in the style of the chat, bold headings +; like the course pages. A home tab is a scrolling list under a big title, and it lists EVERYTHING: every +; agent letter of the home, every named agent, every widget group (the runs of RUN_START), and for t27u every +; course. Item i of home h has a kind (what to draw) and an argument (which agent, group or course). The +; heights are fixed per kind, so the height of the whole page is a sum over the tables below and never typed. +; Counts are measured from the tables above, never typed. homes.t27 holds indices and anchors, not the 195 +; widget ids: those stay in the gallery of gHashTag/trinity. + +pub const IK_NONE : u32 = 0; +pub const IK_H_AGENTS : u32 = 1; +pub const IK_AGENT : u32 = 2; +pub const IK_NAMED : u32 = 3; +pub const IK_H_WIDGETS : u32 = 4; +pub const IK_RUN : u32 = 5; +pub const IK_NO_WIDGETS : u32 = 6; +pub const IK_H_COURSES : u32 = 7; +pub const IK_COURSE : u32 = 8; + +/// Points above the first item: the kicker, the title, the lead (two lines), the law and the runner. +pub const HEAD_H : f64 = 214.0; +/// Points below the last item. +pub const END_PAD : f64 = 56.0; +pub const H2_H : f64 = 64.0; +pub const AGENT_ITEM_H : f64 = 56.0; +pub const RUN_ITEM_H : f64 = 56.0; +pub const EMPTY_ITEM_H : f64 = 44.0; +pub const COURSE_ITEM_H : f64 = 84.0; pub fn runs_in(h: u32) -> u32 { var n : u32 = 0; @@ -381,55 +385,142 @@ pub fn nth_agent(h: u32, n: u32) -> u32 { return 0; } -pub fn agent_cards(h: u32) -> u32 { - const a : u32 = agents_in(h); - if (a > MAX_AGENT_CARDS) { return MAX_AGENT_CARDS; } - return a; +/// The index into NAMED_AGENTS of the n-th named agent of home h, NAMED_COUNT when there is none. +pub fn nth_named(h: u32, n: u32) -> u32 { + var seen : u32 = 0; + var k : u32 = 0; + while (k < NAMED_COUNT) { + if (NAMED_HOME[k] == h) { + if (seen == n) { return k; } + seen = seen + 1; + } + k = k + 1; + } + return NAMED_COUNT; +} + +/// The domain of agent ordinal o in capitals, "COMPILER CORE" for C: the name part of its AGENT_NOTES line. +pub fn agent_domain(o: u32, out: []u8) -> usize { + if (o < 1 or o > AGENT_COUNT) { return 0; } + const n : usize = put_note_name(out, 0, AGENT_NOTES[o - 1]); + var i : usize = 0; + while (i < n) { + if (out[i] >= 97 and out[i] <= 122) { out[i] = out[i] - 32; } + i = i + 1; + } + return n; +} + +pub fn courses_in(h: u32) -> u32 { + if (h == H_T27U) { return COURSE_COUNT; } + return 0; +} + +/// How many lessons of course c open a widget that is in the gallery. +pub fn course_lesson_widgets(c: u32) -> u32 { + var n : u32 = 0; + var l : u32 = 0; + while (l < LESSONS_PER_COURSE) { + if (course_widget(c, l) < WIDGET_COUNT) { n = n + 1; } + l = l + 1; + } + return n; } -pub fn group_cards(h: u32) -> u32 { - if (h == H_T27U) { return 0; } - const g : u32 = runs_in(h); - if (g > MAX_GROUP_CARDS) { return MAX_GROUP_CARDS; } - return g; +/// Rows in the WIDGETS section: one per group, or one row saying there are none. +pub fn widget_rows(h: u32) -> u32 { + const r : u32 = runs_in(h); + if (r == 0) { return 1; } + return r; } -pub fn home_cards(h: u32) -> u32 { +pub fn item_count(h: u32) -> u32 { if (home_ok(h) == false) { return 0; } - var n : u32 = agent_cards(h) + 1 + group_cards(h); - if (h == H_T27U) { n = n + COURSE_COUNT; } + var n : u32 = 1 + agents_in(h) + named_in(h) + 1 + widget_rows(h); + if (courses_in(h) > 0) { n = n + 1 + courses_in(h); } return n; } -pub fn card_kind(h: u32, k: u32) -> u32 { - if (k >= home_cards(h)) { return CK_NONE; } - const na : u32 = agent_cards(h); - if (k < na) { - if (agents_in(h) > MAX_AGENT_CARDS and k + 1 == na) { return CK_AGENT_MORE; } - return CK_AGENT; - } - if (k == na) { return CK_WIDGETS; } - const g : u32 = group_cards(h); - if (k < na + 1 + g) { - if (runs_in(h) > MAX_GROUP_CARDS and k == na + g) { return CK_GROUP_MORE; } - return CK_GROUP; - } - return CK_COURSE; -} - -/// What a card points at: the agent ordinal, the number of agents or groups folded into "+N", the widget -/// total, the run index, or the course index. -pub fn card_arg(h: u32, k: u32) -> u32 { - const kind : u32 = card_kind(h, k); - const na : u32 = agent_cards(h); - const g : u32 = group_cards(h); - if (kind == CK_AGENT) { return nth_agent(h, k); } - if (kind == CK_AGENT_MORE) { return agents_in(h) - (na - 1); } - if (kind == CK_WIDGETS) { return widgets_in(h); } - if (kind == CK_GROUP) { return nth_run(h, k - na - 1); } - if (kind == CK_GROUP_MORE) { return runs_in(h) - (g - 1); } - if (kind == CK_COURSE) { return k - na - 1 - g; } - return 0; +/// Item i of home h as kind * 256 + argument: the agent ordinal, the NAMED index, the run index or the course +/// index; IK_NONE past the end. +fn item_pos(h: u32, i: u32) -> u32 { + if (i >= item_count(h)) { return 0; } + var j : u32 = i; + if (j == 0) { return IK_H_AGENTS * 256; } + j = j - 1; + const na : u32 = agents_in(h); + if (j < na) { return IK_AGENT * 256 + nth_agent(h, j); } + j = j - na; + const nn : u32 = named_in(h); + if (j < nn) { return IK_NAMED * 256 + nth_named(h, j); } + j = j - nn; + if (j == 0) { return IK_H_WIDGETS * 256; } + j = j - 1; + const wr : u32 = widget_rows(h); + if (j < wr) { + if (runs_in(h) == 0) { return IK_NO_WIDGETS * 256; } + return IK_RUN * 256 + nth_run(h, j); + } + j = j - wr; + if (j == 0) { return IK_H_COURSES * 256; } + return IK_COURSE * 256 + (j - 1); +} + +pub fn item_kind(h: u32, i: u32) -> u32 { return item_pos(h, i) / 256; } +pub fn item_arg(h: u32, i: u32) -> u32 { return item_pos(h, i) % 256; } + +pub fn kind_h(kind: u32) -> f64 { + if (kind == IK_AGENT or kind == IK_NAMED) { return AGENT_ITEM_H; } + if (kind == IK_RUN) { return RUN_ITEM_H; } + if (kind == IK_NO_WIDGETS) { return EMPTY_ITEM_H; } + if (kind == IK_COURSE) { return COURSE_ITEM_H; } + if (kind == IK_NONE) { return 0.0; } + return H2_H; +} + +/// Top of item i, from the top of the page. +pub fn item_top(h: u32, i: u32) -> f64 { + var y : f64 = HEAD_H; + var k : u32 = 0; + while (k < i and k < item_count(h)) { + y = y + kind_h(item_kind(h, k)); + k = k + 1; + } + return y; +} + +/// The height of the whole page of home h. +pub fn content_h(h: u32) -> f64 { + if (home_ok(h) == false) { return 0.0; } + return item_top(h, item_count(h)) + END_PAD; +} + +/// The largest scroll offset, in whole points, for a view view_h tall. +pub fn scroll_max(h: u32, view_h: f64) -> u32 { + const over : f64 = content_h(h) - view_h; + if (over <= 0.0) { return 0; } + return @as(u32, @intFromFloat(over)) + 1; +} + +/// A scroll offset moved by dy points, held between 0 and the end of the page. +pub fn scroll_clamp(off: u32, dy: i64, h: u32, view_h: f64) -> u32 { + var v : i64 = @as(i64, off) + dy; + if (v < 0) { v = 0; } + const m : i64 = @as(i64, scroll_max(h, view_h)); + if (v > m) { v = m; } + return @as(u32, @intCast(v)); +} + +/// The item under the page point y (from the top of the page), item_count when it is none. +pub fn item_at(h: u32, y: f64) -> u32 { + const n : u32 = item_count(h); + var k : u32 = 0; + while (k < n) { + const t : f64 = item_top(h, k); + if (y >= t and y < t + kind_h(item_kind(h, k))) { return k; } + k = k + 1; + } + return n; } /// "1 widget", "27 lessons": the count, a space, then the singular or the plural word. @@ -454,73 +545,136 @@ fn put_note_name(out: []u8, at: usize, note: []const u8) -> usize { return n; } -/// The first line of card k of home h, as bytes in out; returns the length (0 for an empty slot). -pub fn card_title(h: u32, k: u32, out: []u8) -> usize { - const kind : u32 = card_kind(h, k); - const arg : u32 = card_arg(h, k); - if (kind == CK_AGENT) { - var n : usize = put_bytes(out, 0, AGENT_LETTERS[arg - 1]); - n = put_bytes(out, n, " "); - return put_note_name(out, n, AGENT_NOTES[arg - 1]); +/// The text of an AGENT_NOTES line after the colon and its space: where the agent lives and why. +fn put_note_rest(out: []u8, at: usize, note: []const u8) -> usize { + var i : usize = 0; + while (i < note.len and note[i] != 58) { i = i + 1; } + i = i + 1; + while (i < note.len and note[i] == 32) { i = i + 1; } + var n : usize = at; + while (i < note.len) { + out[n] = note[i]; + n = n + 1; + i = i + 1; + } + return n; +} + +/// ASCII upper case of s. +fn put_upper(out: []u8, at: usize, s: []const u8) -> usize { + var i : usize = 0; + while (i < s.len) { + var ch : u8 = s[i]; + if (ch >= 97 and ch <= 122) { ch = ch - 32; } + out[at + i] = ch; + i = i + 1; } - if (kind == CK_AGENT_MORE) { - const c : usize = put_bytes(out, 0, "+"); - return put_counted(out, c, arg, "agent", "agents"); + return at + s.len; +} + +/// The big title of the page: the home name in capitals, "T27C COMPILER". +pub fn home_title(h: u32, out: []u8) -> usize { + if (home_ok(h) == false) { return 0; } + return put_upper(out, 0, HOME_NAMES[h]); +} + +/// The small line over the title: "HOME 3 OF 8". +pub fn home_kicker(h: u32, out: []u8) -> usize { + if (home_ok(h) == false) { return 0; } + var n : usize = put_bytes(out, 0, "HOME "); + n = put_dec(out, n, @as(u64, h + 1)); + n = put_bytes(out, n, " OF "); + return put_dec(out, n, @as(u64, HOME_COUNT)); +} + +/// The second line of a home's row in the sidebar: "13 agents, 127 widgets", with the courses for t27u. +pub fn home_line(h: u32, out: []u8) -> usize { + if (home_ok(h) == false) { return 0; } + var n : usize = put_counted(out, 0, agents_in(h) + named_in(h), "agent", "agents"); + n = put_bytes(out, n, ", "); + n = put_counted(out, n, widgets_in(h), "widget", "widgets"); + if (courses_in(h) > 0) { + n = put_bytes(out, n, ", "); + n = put_counted(out, n, courses_in(h), "course", "courses"); } - if (kind == CK_WIDGETS) { return put_counted(out, 0, arg, "widget", "widgets"); } - if (kind == CK_GROUP) { return put_bytes(out, 0, RUN_ANCHOR[arg]); } - if (kind == CK_GROUP_MORE) { - const c : usize = put_bytes(out, 0, "+"); - return put_counted(out, c, arg, "group", "groups"); + return n; +} + +/// The count at the right end of a home's row in the sidebar: its widgets. +pub fn home_count_text(h: u32, out: []u8) -> usize { + if (home_ok(h) == false) { return 0; } + return put_dec(out, 0, @as(u64, widgets_in(h))); +} + +/// The badge of item i (a letter, "@", a count, a number); empty for a heading. +pub fn item_badge(h: u32, i: u32, out: []u8) -> usize { + const kind : u32 = item_kind(h, i); + const arg : u32 = item_arg(h, i); + if (kind == IK_AGENT) { return put_bytes(out, 0, AGENT_LETTERS[arg - 1]); } + if (kind == IK_NAMED) { return put_bytes(out, 0, "@"); } + if (kind == IK_RUN) { return put_dec(out, 0, @as(u64, run_size(arg))); } + if (kind == IK_COURSE) { return put_dec(out, 0, @as(u64, arg + 1)); } + return 0; +} + +/// The first line of item i: a heading's word, or the row's name. +pub fn item_title(h: u32, i: u32, out: []u8) -> usize { + const kind : u32 = item_kind(h, i); + const arg : u32 = item_arg(h, i); + if (kind == IK_H_AGENTS) { return put_bytes(out, 0, "AGENTS"); } + if (kind == IK_H_WIDGETS) { return put_bytes(out, 0, "WIDGETS"); } + if (kind == IK_H_COURSES) { return put_bytes(out, 0, "COURSES"); } + if (kind == IK_AGENT) { return put_note_name(out, 0, AGENT_NOTES[arg - 1]); } + if (kind == IK_NAMED) { return put_bytes(out, 0, NAMED_AGENTS[arg]); } + if (kind == IK_RUN) { + const n : usize = put_counted(out, 0, run_size(arg), "widget", "widgets"); + const f : usize = put_bytes(out, n, " from "); + return put_bytes(out, f, RUN_ANCHOR[arg]); } - if (kind == CK_COURSE) { return put_bytes(out, 0, COURSE_IDS[arg]); } + if (kind == IK_NO_WIDGETS) { return put_bytes(out, 0, "none in the gallery"); } + if (kind == IK_COURSE) { return put_bytes(out, 0, COURSE_IDS[arg]); } return 0; } -/// The second line of card k of home h. -pub fn card_line(h: u32, k: u32, out: []u8) -> usize { - const kind : u32 = card_kind(h, k); - const arg : u32 = card_arg(h, k); - if (kind == CK_AGENT) { - var n : usize = put_bytes(out, 0, "agent "); - n = put_dec(out, n, @as(u64, arg)); +/// The second line of item i: a heading's total (shown at the right of the heading), or the row's domain. +pub fn item_line(h: u32, i: u32, out: []u8) -> usize { + const kind : u32 = item_kind(h, i); + const arg : u32 = item_arg(h, i); + if (kind == IK_H_AGENTS) { + var n : usize = put_counted(out, 0, agents_in(h), "letter agent", "letter agents"); + n = put_bytes(out, n, ", "); + return put_counted(out, n, named_in(h), "named agent", "named agents"); + } + if (kind == IK_H_WIDGETS) { + var n : usize = put_counted(out, 0, widgets_in(h), "widget", "widgets"); + n = put_bytes(out, n, " in "); + return put_counted(out, n, runs_in(h), "group", "groups"); + } + if (kind == IK_H_COURSES) { + var n : usize = put_counted(out, 0, COURSE_COUNT, "course", "courses"); n = put_bytes(out, n, " of "); - return put_dec(out, n, @as(u64, AGENT_COUNT)); - } - if (kind == CK_AGENT_MORE) { - var n : usize = 0; - var i : u32 = agent_cards(h) - 1; - while (i < agents_in(h)) { - if (i > agent_cards(h) - 1) { n = put_bytes(out, n, " "); } - n = put_bytes(out, n, AGENT_LETTERS[nth_agent(h, i) - 1]); - i = i + 1; - } - return n; - } - if (kind == CK_WIDGETS) { - if (arg == 0) { return put_bytes(out, 0, "none in the gallery"); } - const c : usize = put_bytes(out, 0, "in "); - return put_counted(out, c, runs_in(h), "group", "groups"); - } - if (kind == CK_GROUP) { return put_counted(out, 0, run_size(arg), "widget", "widgets"); } - if (kind == CK_GROUP_MORE) { - var total : u32 = 0; - var i : u32 = runs_in(h) - arg; - while (i < runs_in(h)) { - total = total + run_size(nth_run(h, i)); - i = i + 1; - } - return put_counted(out, 0, total, "widget", "widgets"); + return put_counted(out, n, LESSONS_PER_COURSE, "lesson", "lessons"); + } + if (kind == IK_AGENT) { return put_note_rest(out, 0, AGENT_NOTES[arg - 1]); } + if (kind == IK_NAMED) { return put_bytes(out, 0, NAMED_NOTES[arg]); } + if (kind == IK_RUN) { return put_bytes(out, 0, RUN_NOTES[arg]); } + if (kind == IK_NO_WIDGETS) { return put_bytes(out, 0, "The gallery holds no page of this home."); } + if (kind == IK_COURSE) { + var n : usize = put_counted(out, 0, LESSONS_PER_COURSE, "lesson", "lessons"); + n = put_bytes(out, n, ", "); + return put_counted(out, n, course_lesson_widgets(arg), "lesson widget", "lesson widgets"); } - if (kind == CK_COURSE) { return put_counted(out, 0, LESSONS_PER_COURSE, "lesson", "lessons"); } return 0; } -/// The third line: only a course has one, its share page under t27.ai. -pub fn card_note(h: u32, k: u32, out: []u8) -> usize { - if (card_kind(h, k) == CK_COURSE) { - const n : usize = put_bytes(out, 0, "t27.ai/"); - return put_bytes(out, n, COURSE_SHARE[card_arg(h, k)]); +/// The third line: only a course has one, its share page under t27.ai and its spec. +pub fn item_note(h: u32, i: u32, out: []u8) -> usize { + if (item_kind(h, i) == IK_COURSE) { + const arg : u32 = item_arg(h, i); + var n : usize = put_bytes(out, 0, "t27.ai/"); + n = put_bytes(out, n, COURSE_SHARE[arg]); + n = put_bytes(out, n, " "); + return put_bytes(out, n, COURSE_SPECS[arg]); } return 0; } @@ -727,47 +881,33 @@ test six_widgets_are_opened_by_no_lesson { assert course_widget(0, 27) == WIDGET_COUNT; } -test chat_is_the_third_petal_and_the_homes_take_nineteen_to_twenty_five { +test the_27_petals_are_the_27_agent_letters { assert PETAL_CHAT == 2; - assert petal_of_home(H_T27A) == 19; - assert petal_of_home(H_T27B) == 20; - assert petal_of_home(H_T27C) == 21; - assert petal_of_home(H_T27F) == 22; - assert petal_of_home(H_T27O) == 23; - assert petal_of_home(H_T27T) == 24; - assert petal_of_home(H_T27U) == 25; - assert petal_of_home(H_QUEEN) == PETAL_CHAT; - assert petal_of_home(HOME_COUNT) == PETAL_NONE; -} - -test home_of_petal_inverts_petal_of_home_and_the_rest_have_no_home { - var h : u32 = 0; - while (h < HOME_COUNT) { - assert home_of_petal(petal_of_home(h)) == h; - h = h + 1; - } - var n : u32 = 0; + assert letter_of_petal(0) == 1; + assert letter_of_petal(2) == 3; + assert letter_of_petal(26) == 27; + assert letter_of_petal(27) == 0; + assert petal_of_letter(27) == 26; + assert petal_of_letter(0) == PETAL_NONE; + assert petal_of_letter(28) == PETAL_NONE; var p : u32 = 0; while (p < PETAL_COUNT) { - if (home_of_petal(p) < HOME_COUNT) { n = n + 1; } + assert petal_of_letter(letter_of_petal(p)) == p; + assert home_of_petal(p) < HOME_COUNT; p = p + 1; } - assert n == 8; - assert home_of_petal(26) == HOME_COUNT; - assert home_of_petal(0) == HOME_COUNT; - assert home_of_petal(18) == HOME_COUNT; + assert home_of_petal(2) == H_T27C; + assert home_of_petal(8) == H_T27A; + assert home_of_petal(25) == H_T27U; assert home_of_petal(27) == HOME_COUNT; } -test the_product_homes_sit_in_the_third_realm_and_about_stays_last { - var h : u32 = 0; - while (h < H_QUEEN) { - assert HOME_PETAL[h] >= 19; - assert HOME_PETAL[h] <= 25; - assert HOME_PETAL[h] / 9 == 2; - h = h + 1; - } - assert home_of_petal(26) != H_T27U; +test the_realms_are_the_three_layers_of_nine_letters { + assert petal_of_letter(9) / 9 == 0; + assert petal_of_letter(10) / 9 == 1; + assert petal_of_letter(18) / 9 == 1; + assert petal_of_letter(19) / 9 == 2; + assert petal_of_letter(27) / 9 == 2; } /// Is field `petal` of the comma-separated list `names` exactly `word`? (the tests' reader of WORLD_NAMES) @@ -783,24 +923,28 @@ fn world_is(names: []const u8, petal: u32, word: []const u8) -> bool { return true; } -test the_world_names_of_petal_menu_put_chat_third_and_name_every_home { +test the_world_names_of_petal_menu_are_the_page_titles { + // Owner, 2026-10-20: the petal label is the short title of the letter's page (KANBAN, CHAT, ISSUES), not "K t27k". + // The 27 titles are checked against agent_parts::TOOL_PAGES there (that module imports this one). assert world_is(WORLD_NAMES, PETAL_CHAT, "CHAT"); - assert world_is(WORLD_NAMES, 0, "LOGS"); - assert world_is(WORLD_NAMES, 19, "T27A"); - assert world_is(WORLD_NAMES, 20, "T27B"); - assert world_is(WORLD_NAMES, 21, "T27C"); - assert world_is(WORLD_NAMES, 22, "T27F"); - assert world_is(WORLD_NAMES, 23, "T27O"); - assert world_is(WORLD_NAMES, 24, "T27T"); - assert world_is(WORLD_NAMES, 25, "T27U"); - assert world_is(WORLD_NAMES, 26, "ABOUT"); - assert world_is(WORLD_NAMES, 16, "DePIN NODE"); - assert world_is(WORLD_NAMES, 1, "CODE"); - assert world_is(WORLD_NAMES, 18, "DOCS"); - assert world_is(WORLD_NAMES, 27, "ABOUT") == false; - assert world_is(WORLD_NAMES, 2, "LOGS") == false; - assert world_is(WORLD_NAMES, 0, "CHAT") == false; - assert world_is(WORLD_NAMES, 19, "REELS") == false; + assert world_is(WORLD_NAMES, 0, "ISA"); + assert world_is(WORLD_NAMES, 5, "FRONTEND"); + assert world_is(WORLD_NAMES, 10, "KANBAN"); + assert world_is(WORLD_NAMES, 25, "DOCS"); + assert world_is(WORLD_NAMES, 26, "SECURITY"); + assert world_is(WORLD_NAMES, 19, "TRAINER"); + assert world_is(WORLD_NAMES, 20, "UNIVERSITY"); + assert world_is(WORLD_NAMES, 27, "SECURITY") == false; + assert world_is(WORLD_NAMES, 2, "C t27c") == false; + var p : u32 = 0; + var b : [32]u8 = undefined; + while (p < PETAL_COUNT) { + const n : usize = csv_field(WORLD_NAMES, p, b[0..32]); + assert n >= 2; + assert b[0] >= 65; + assert b[0] <= 90; + p = p + 1; + } } fn same(out: []const u8, n: usize, want: []const u8) -> bool { @@ -813,45 +957,6 @@ fn same(out: []const u8, n: usize, want: []const u8) -> bool { return true; } -test every_home_has_a_card_count_that_fits_nine_slots { - var h : u32 = 0; - while (h < HOME_COUNT) { - assert home_cards(h) >= 1; - assert home_cards(h) <= HOME_CARD_SLOTS; - h = h + 1; - } - assert home_cards(H_T27A) == 2; - assert home_cards(H_T27B) == 2; - assert home_cards(H_T27C) == 9; - assert home_cards(H_T27F) == 2; - assert home_cards(H_T27O) == 3; - assert home_cards(H_T27T) == 6; - assert home_cards(H_T27U) == 9; - assert home_cards(H_QUEEN) == 8; - assert home_cards(HOME_COUNT) == 0; - assert card_kind(H_T27A, 2) == CK_NONE; - assert card_kind(H_T27U, 9) == CK_NONE; -} - -test the_card_kinds_follow_the_fixed_order { - assert card_kind(H_T27A, 0) == CK_AGENT; - assert card_kind(H_T27A, 1) == CK_WIDGETS; - assert card_kind(H_T27C, 0) == CK_AGENT; - assert card_kind(H_T27C, 2) == CK_AGENT; - assert card_kind(H_T27C, 3) == CK_AGENT_MORE; - assert card_kind(H_T27C, 4) == CK_WIDGETS; - assert card_kind(H_T27C, 5) == CK_GROUP; - assert card_kind(H_T27C, 7) == CK_GROUP; - assert card_kind(H_T27C, 8) == CK_GROUP_MORE; - assert card_kind(H_T27U, 0) == CK_AGENT; - assert card_kind(H_T27U, 1) == CK_WIDGETS; - assert card_kind(H_T27U, 2) == CK_COURSE; - assert card_kind(H_T27U, 8) == CK_COURSE; - assert card_kind(H_QUEEN, 3) == CK_AGENT_MORE; - assert card_kind(H_QUEEN, 5) == CK_GROUP; - assert card_kind(H_QUEEN, 7) == CK_GROUP; -} - test the_run_walk_covers_the_gallery { var total : u32 = 0; var r : u32 = 0; @@ -883,68 +988,203 @@ test the_run_walk_covers_the_gallery { } } -test the_agent_cards_name_the_letter_and_the_name { - var b : [64]u8 = undefined; - var n : usize = card_title(H_T27B, 0, b[0..64]); - assert same(b[0..64], n, "K Kernel / FPGA MAC"); - n = card_line(H_T27B, 0, b[0..64]); - assert same(b[0..64], n, "agent 11 of 27"); - n = card_title(H_T27A, 0, b[0..64]); - assert same(b[0..64], n, "I ISA"); - n = card_title(H_T27U, 0, b[0..64]); - assert same(b[0..64], n, "Z Zero-Touch UX / Docs"); - n = card_title(H_T27C, 3, b[0..64]); - assert same(b[0..64], n, "+3 agents"); - n = card_line(H_T27C, 3, b[0..64]); - assert same(b[0..64], n, "L R V"); - n = card_title(H_QUEEN, 3, b[0..64]); - assert same(b[0..64], n, "+10 agents"); -} - -test the_widget_cards_say_the_real_counts_including_zero { - var b : [64]u8 = undefined; - var n : usize = card_title(H_T27A, 1, b[0..64]); - assert same(b[0..64], n, "0 widgets"); - n = card_line(H_T27A, 1, b[0..64]); - assert same(b[0..64], n, "none in the gallery"); - n = card_title(H_T27C, 4, b[0..64]); - assert same(b[0..64], n, "127 widgets"); - n = card_line(H_T27C, 4, b[0..64]); - assert same(b[0..64], n, "in 6 groups"); - n = card_title(H_T27C, 5, b[0..64]); - assert same(b[0..64], n, "x7-board"); - n = card_line(H_T27C, 5, b[0..64]); - assert same(b[0..64], n, "12 widgets"); - n = card_title(H_T27C, 8, b[0..64]); - assert same(b[0..64], n, "+3 groups"); - n = card_title(H_T27T, 2, b[0..64]); - assert same(b[0..64], n, "6 widgets"); - n = card_title(H_T27U, 1, b[0..64]); - assert same(b[0..64], n, "54 widgets"); - n = card_line(H_T27U, 1, b[0..64]); - assert same(b[0..64], n, "in 1 group"); -} - -test the_course_cards_give_id_lessons_and_share_path { - var b : [64]u8 = undefined; - var n : usize = card_title(H_T27U, 2, b[0..64]); - assert same(b[0..64], n, "t27-basics"); - n = card_line(H_T27U, 2, b[0..64]); - assert same(b[0..64], n, "27 lessons"); - n = card_note(H_T27U, 2, b[0..64]); - assert same(b[0..64], n, "t27.ai/learn/"); - n = card_title(H_T27U, 8, b[0..64]); - assert same(b[0..64], n, "buses-and-peripherals"); - n = card_note(H_T27U, 8, b[0..64]); - assert same(b[0..64], n, "t27.ai/learn/buses-and-peripherals/"); - n = card_note(H_T27U, 1, b[0..64]); +test every_agent_group_and_course_of_every_home_is_listed { + var agents : u32 = 0; + var named : u32 = 0; + var runs : u32 = 0; + var courses : u32 = 0; + var h : u32 = 0; + while (h < HOME_COUNT) { + var a : u32 = 0; + var nm : u32 = 0; + var r : u32 = 0; + var c : u32 = 0; + var w : u32 = 0; + var heads : u32 = 0; + var none : u32 = 0; + var i : u32 = 0; + while (i < item_count(h)) { + const k : u32 = item_kind(h, i); + assert k != IK_NONE; + if (k == IK_AGENT) { a = a + 1; } + if (k == IK_NAMED) { nm = nm + 1; } + if (k == IK_RUN) { r = r + 1; w = w + run_size(item_arg(h, i)); } + if (k == IK_COURSE) { c = c + 1; } + if (k == IK_H_AGENTS or k == IK_H_WIDGETS or k == IK_H_COURSES) { heads = heads + 1; } + if (k == IK_NO_WIDGETS) { none = none + 1; } + i = i + 1; + } + assert a == agents_in(h); + assert nm == named_in(h); + assert r == runs_in(h); + assert c == courses_in(h); + assert w == widgets_in(h); + assert none + r == widget_rows(h); + assert item_kind(h, 0) == IK_H_AGENTS; + if (h == H_T27U) { assert heads == 3; } else { assert heads == 2; } + agents = agents + a; + named = named + nm; + runs = runs + r; + courses = courses + c; + h = h + 1; + } + assert agents == AGENT_COUNT; + assert named == NAMED_COUNT; + assert runs == RUN_COUNT; + assert courses == COURSE_COUNT; + assert item_count(H_T27A) == 4; + assert item_count(H_T27C) == 19; + assert item_count(H_T27U) == 12; + assert item_count(H_QUEEN) == 25; + assert item_count(HOME_COUNT) == 0; + assert item_kind(H_T27A, 3) == IK_NO_WIDGETS; + assert item_kind(H_T27A, 4) == IK_NONE; + assert item_kind(H_T27U, 4) == IK_H_COURSES; + assert item_kind(H_T27U, 11) == IK_COURSE; + assert item_arg(H_T27U, 11) == 6; +} + +test every_course_opens_twenty_seven_lesson_widgets_in_the_gallery { + var c : u32 = 0; + while (c < COURSE_COUNT) { + assert course_lesson_widgets(c) == LESSONS_PER_COURSE; + c = c + 1; + } +} + +test the_page_height_is_the_sum_of_its_items_and_no_item_overlaps_another { + var h : u32 = 0; + while (h < HOME_COUNT) { + var i : u32 = 0; + while (i < item_count(h)) { + assert item_top(h, i) + kind_h(item_kind(h, i)) == item_top(h, i + 1); + assert kind_h(item_kind(h, i)) > 0.0; + assert item_at(h, item_top(h, i)) == i; + assert item_at(h, item_top(h, i + 1) - 0.5) == i; + i = i + 1; + } + assert item_top(h, 0) == HEAD_H; + assert content_h(h) == item_top(h, item_count(h)) + END_PAD; + assert item_at(h, HEAD_H - 1.0) == item_count(h); + assert item_at(h, content_h(h)) == item_count(h); + h = h + 1; + } + assert content_h(H_T27A) == 498.0; + assert content_h(H_T27C) == 1350.0; + assert content_h(H_T27U) == 1162.0; + assert content_h(H_QUEEN) == 1686.0; + assert content_h(HOME_COUNT) == 0.0; +} + +test the_scroll_is_held_between_the_top_and_the_end_of_the_page { + assert scroll_max(H_T27A, 1000.0) == 0; + assert scroll_max(H_T27C, 400.0) == 951; + assert scroll_clamp(0, -50, H_T27C, 400.0) == 0; + assert scroll_clamp(10, 25, H_T27C, 400.0) == 35; + assert scroll_clamp(10, -4, H_T27C, 400.0) == 6; + assert scroll_clamp(900, 100000, H_T27C, 400.0) == 951; + assert scroll_clamp(5, 100, H_T27A, 1000.0) == 0; + assert scroll_clamp(5, 100, HOME_COUNT, 400.0) == 0; + var h : u32 = 0; + while (h < HOME_COUNT) { + assert scroll_clamp(0, 1000000, h, 460.0) == scroll_max(h, 460.0); + assert scroll_max(h, 460.0) + 460 >= @as(u32, @intFromFloat(content_h(h))); + h = h + 1; + } +} + +test the_headings_and_rows_read_with_the_real_counts { + var b : [256]u8 = undefined; + var n : usize = home_title(H_T27C, b[0..256]); + assert same(b[0..256], n, "T27C COMPILER"); + n = home_title(H_T27U, b[0..256]); + assert same(b[0..256], n, "T27U UNIVERSITY"); + n = home_kicker(H_T27C, b[0..256]); + assert same(b[0..256], n, "HOME 3 OF 8"); + n = home_line(H_T27C, b[0..256]); + assert same(b[0..256], n, "11 agents, 127 widgets"); + n = home_line(H_T27U, b[0..256]); + assert same(b[0..256], n, "1 agent, 54 widgets, 7 courses"); + n = home_line(H_T27A, b[0..256]); + assert same(b[0..256], n, "1 agent, 0 widgets"); + n = home_count_text(H_T27C, b[0..256]); + assert same(b[0..256], n, "127"); + n = item_title(H_T27C, 0, b[0..256]); + assert same(b[0..256], n, "AGENTS"); + n = item_line(H_T27C, 0, b[0..256]); + assert same(b[0..256], n, "6 letter agents, 5 named agents"); + n = item_badge(H_T27C, 1, b[0..256]); + assert same(b[0..256], n, "B"); + n = item_title(H_T27C, 1, b[0..256]); + assert same(b[0..256], n, "Build"); + n = item_title(H_T27C, 7, b[0..256]); + assert same(b[0..256], n, "agent-c-compiler"); + n = item_badge(H_T27C, 7, b[0..256]); + assert same(b[0..256], n, "@"); + n = item_title(H_T27C, 12, b[0..256]); + assert same(b[0..256], n, "WIDGETS"); + n = item_line(H_T27C, 12, b[0..256]); + assert same(b[0..256], n, "127 widgets in 6 groups"); + n = item_title(H_T27C, 13, b[0..256]); + assert same(b[0..256], n, "12 widgets from x7-board"); + n = item_badge(H_T27C, 13, b[0..256]); + assert same(b[0..256], n, "12"); + n = item_line(H_T27A, 2, b[0..256]); + assert same(b[0..256], n, "0 widgets in 0 groups"); + n = item_title(H_T27A, 3, b[0..256]); + assert same(b[0..256], n, "none in the gallery"); + n = item_title(H_T27U, 3, b[0..256]); + assert same(b[0..256], n, "54 widgets from basics-what-a-spec-is"); + n = item_line(H_T27U, 4, b[0..256]); + assert same(b[0..256], n, "7 courses of 27 lessons"); + n = item_title(H_T27U, 5, b[0..256]); + assert same(b[0..256], n, "t27-basics"); + n = item_line(H_T27U, 5, b[0..256]); + assert same(b[0..256], n, "27 lessons, 27 lesson widgets"); + n = item_note(H_T27U, 5, b[0..256]); + assert same(b[0..256], n, "t27.ai/learn/ specs/course/t27-basics.t27"); + n = item_title(H_T27U, 11, b[0..256]); + assert same(b[0..256], n, "buses-and-peripherals"); + n = item_badge(H_T27U, 11, b[0..256]); + assert same(b[0..256], n, "7"); + n = item_note(H_T27U, 1, b[0..256]); assert n == 0; } -test negative_control_an_empty_slot_has_no_text { +test every_line_of_every_home_fits_its_buffer { + var b : [256]u8 = undefined; + var h : u32 = 0; + while (h < HOME_COUNT) { + var i : u32 = 0; + while (i < item_count(h)) { + assert item_title(h, i, b[0..256]) < 200; + assert item_title(h, i, b[0..256]) > 0; + assert item_line(h, i, b[0..256]) < 200; + assert item_line(h, i, b[0..256]) > 0; + assert item_note(h, i, b[0..256]) < 200; + assert item_badge(h, i, b[0..256]) < 8; + i = i + 1; + } + h = h + 1; + } +} + +test negative_control_past_the_end_there_is_no_text { + var b : [256]u8 = undefined; + assert item_title(H_T27A, 9, b[0..256]) == 0; + assert item_line(H_T27A, 9, b[0..256]) == 0; + assert item_note(H_T27A, 9, b[0..256]) == 0; + assert item_badge(H_T27A, 9, b[0..256]) == 0; + assert home_title(HOME_COUNT, b[0..256]) == 0; + assert home_line(HOME_COUNT, b[0..256]) == 0; +} + +test agent_domain_names_the_letters { var b : [64]u8 = undefined; - assert card_title(H_T27A, 5, b[0..64]) == 0; - assert card_line(H_T27A, 5, b[0..64]) == 0; - assert card_note(H_T27A, 5, b[0..64]) == 0; - assert card_title(HOME_COUNT, 0, b[0..64]) == 0; + const n : usize = agent_domain(3, b[0..64]); + assert n == 13; + assert b[0] == 67; + assert agent_domain(0, b[0..64]) == 0; + assert agent_domain(27, b[0..64]) == 18; } + diff --git a/specs/t27os/kanban.t27 b/specs/t27os/kanban.t27 new file mode 100644 index 0000000000..6081868dd3 --- /dev/null +++ b/specs/t27os/kanban.t27 @@ -0,0 +1,712 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/t27os/kanban.t27 -- t27k, the KANBAN board of T27 OS: issues and pull requests as cards, 100% widgets +; Owner, 2026-10-20 (decided): T27 OS is an ergonomic project-management system; each letter is a tool t27 +; with its own page; K is t27k, the kanban board, and everything on the page is a widget. Closes #11275. +; ASCII only (L3), English only (LANG-EN). Artifact it draws (toolchain.t27 rule): the HUD view kanban of the app +; (agent_parts SURFACE_IDS "kanban", letter 10 there, hence the page moves to petal K) and `tri issues`. +; -- +; The tree. BOARD holds 5 COLUMNs (BACKLOG, TODO, DOING, REVIEW, DONE); a COLUMN holds CARDs; a CARD holds +; CHIPs (the pinned agents, owner first) and BADGEs (the labels). Layout is the packer of dash.t27 (used, not +; copied): the columns are one row of 5, the cards of a column are one column of 1. +; -- +; The host feeds LINES, LF separated, fields TAB separated, no JSON in the spec (the host formats): +; issue TAB num TAB state TAB title TAB labels TAB paths TAB letters +; gh issue list --state all --limit 200 --json number,state,title,labels,body,closedAt +; the host prints state as open, or closed/; labels and paths are comma lists; +; paths are the Boundary paths of the issue body (specs/policy/issue_shape.t27), the first is the main one. +; pr TAB num TAB state TAB closes TAB checks TAB title TAB paths TAB letters +; gh pr list --state all --limit 200 --json number,state,title,closingIssuesReferences,statusCheckRollup, +; reviewRequests,files,mergedAt +; state is open, merged/ or closed/; closes is the issue number or 0; checks is one of +; running, review (a review is requested), pass, fail, none; paths are the files the PR changes. +; -- +; COLUMN RULE (column()): an open issue with no open PR is TODO, BACKLOG when labelled backlog; an open issue with +; an open PR is DOING; an open PR with checks running or a review requested is REVIEW (its issue folds into the PR +; card and is not drawn twice); a PR whose checks failed or are absent stays with its issue in DOING (folded); a +; merged PR, or a closed issue with no merged PR, is DONE for 7 days after closing; a PR closed unmerged is not drawn. +; -- +; AGENTS of a card: the LAST field of its line, a comma list of letters 1..27, owner first, distinct, at most +; MAX_CHIPS used. The host fills it with domain_pulse::letter_of_path over each path (the Boundary paths of an +; issue, the files of a PR). The spec does not call domain_pulse itself: that module reads the 27 agent specs by +; module-qualified path, which neither gen nor t27b can splice into an importer (checked 2026-10-20), so the pin +; between paths and letters is the test `kanban_sample_paths_are_owned_by_the_letters_of_its_lines` in +; domain_pulse.t27, where those paths are resolved. A card whose list is empty is pinned to +; FALLBACK_LETTER (S, who owns ownership, as evolve.t27 decides for unowned paths): every card has >= 1 agent. +; The card's TOOL letter is its first agent (card_tool); the tool name and page come from agent_parts. +; phi^2 + 1/phi^2 = 3 | TRINITY + +module T27osKanban; + +use t27os::dash::cell_w; +use t27os::dash::dash_cell_x; +use t27os::dash::dash_cell_y; +use t27os::dash::pack_h; +use t27os::dash::pack_at; +use t27os::evolve::why_not; +use t27os::evolve::R_AGENT_CAP; +use t27os::evolve::LETTER_UNOWNED; + +// ---- node kinds of the tree ---- +pub const KK_BOARD : u32 = 0; +pub const KK_COLUMN : u32 = 1; +pub const KK_CARD : u32 = 2; +pub const KK_CHIP : u32 = 3; +pub const KK_BADGE : u32 = 4; +pub const KK_KINDS : u32 = 5; + +/// Kind of the i-th child kind of a node of kind k; KK_KINDS for none. +pub fn child_kind(k: u32, i: u32) -> u32 { + if (k == KK_BOARD and i == 0) { return KK_COLUMN; } + if (k == KK_COLUMN and i == 0) { return KK_CARD; } + if (k == KK_CARD and i == 0) { return KK_CHIP; } + if (k == KK_CARD and i == 1) { return KK_BADGE; } + return KK_KINDS; +} + +// ---- columns ---- +pub const COL_BACKLOG : u32 = 0; +pub const COL_TODO : u32 = 1; +pub const COL_DOING : u32 = 2; +pub const COL_REVIEW : u32 = 3; +pub const COL_DONE : u32 = 4; +pub const COL_COUNT : u32 = 5; +pub const COL_HIDDEN : u32 = 5; + +pub const DONE_DAYS : u32 = 7; +pub const WIP_DOING : u32 = 3; +pub const MAX_CHIPS : u32 = 3; +pub const FALLBACK_LETTER : u32 = 19; +pub const NONE : u32 = 4294967295; + +pub fn column_name(c: u32) -> string { + if (c == COL_BACKLOG) { return "BACKLOG"; } + if (c == COL_TODO) { return "TODO"; } + if (c == COL_DOING) { return "DOING"; } + if (c == COL_REVIEW) { return "REVIEW"; } + if (c == COL_DONE) { return "DONE"; } + return ""; +} + +/// WIP limit of a column, 0 = none. DOING is capped by the agent cap of GOD MODE (evolve.t27 MAX_AGENTS = 3, held equal by a test). +pub fn wip_limit(c: u32) -> u32 { + if (c == COL_DOING) { return WIP_DOING; } + return 0; +} + +pub fn wip_full(c: u32, count: u32) -> bool { + const lim : u32 = wip_limit(c); + if (lim == 0) { return false; } + return count >= lim; +} + +// ---- reading the lines ---- +pub const NL : u32 = 10; +pub const TAB : u32 = 9; + +/// End of the line that starts at ls: the index of its LF, or the text length. +pub fn line_end(log: string, ls: u32) -> u32 { + var ii : u32 = ls; + while (ii < (log.len as u32)) { + if ((log[ii] as u32) == NL) { return ii; } + ii = ii + 1; + } + return log.len as u32; +} + +/// Start of field f of the line at ls, NONE when the line has fewer fields. +pub fn fstart(log: string, ls: u32, f: u32) -> u32 { + const le : u32 = line_end(log, ls); + var ii : u32 = ls; + var ff : u32 = 0; + while (ff < f) { + while (ii < le and (log[ii] as u32) != TAB) { ii = ii + 1; } + if (ii >= le) { return NONE; } + ii = ii + 1; + ff = ff + 1; + } + return ii; +} + +/// End of the field that starts at fs (its TAB, or the line end). +pub fn fend(log: string, fs: u32) -> u32 { + var ii : u32 = fs; + while (ii < (log.len as u32)) { + const c : u32 = log[ii] as u32; + if (c == TAB or c == NL) { return ii; } + ii = ii + 1; + } + return log.len as u32; +} + +/// Length of field f (0 when absent): the counted string of a title, a label list, a path list. +pub fn flen(log: string, ls: u32, f: u32) -> u32 { + const fs : u32 = fstart(log, ls, f); + if (fs == NONE) { return 0; } + return fend(log, fs) - fs; +} + +/// Decimal value of log[a..b), stops at the first non-digit. +pub fn dec(log: string, a: u32, b: u32) -> u32 { + var ii : u32 = a; + var vv : u32 = 0; + while (ii < b) { + const c : u32 = log[ii] as u32; + if (c < 48 or c > 57) { return vv; } + vv = vv * 10 + (c - 48); + ii = ii + 1; + } + return vv; +} + +pub fn is_pr(log: string, ls: u32) -> bool { + return (log[ls] as u32) == 112; +} + +pub fn card_num(log: string, ls: u32) -> u32 { + const fs : u32 = fstart(log, ls, 1); + if (fs == NONE) { return 0; } + return dec(log, fs, fend(log, fs)); +} + +/// First byte of the state: o(pen), c(losed), m(erged). +pub fn state_byte(log: string, ls: u32) -> u32 { + const fs : u32 = fstart(log, ls, 2); + if (fs == NONE) { return 0; } + return log[fs] as u32; +} + +/// Days since closing: the digits after the slash of the state, 0 when none. +pub fn state_days(log: string, ls: u32) -> u32 { + const fs : u32 = fstart(log, ls, 2); + if (fs == NONE) { return 0; } + const fe : u32 = fend(log, fs); + var ii : u32 = fs; + while (ii < fe) { + if ((log[ii] as u32) == 47) { return dec(log, ii + 1, fe); } + ii = ii + 1; + } + return 0; +} + +/// Field index of the title, of the paths, and of the labels (a PR has none). +pub fn title_field(log: string, ls: u32) -> u32 { + if (is_pr(log, ls)) { return 5; } + return 3; +} + +pub fn paths_field(log: string, ls: u32) -> u32 { + if (is_pr(log, ls)) { return 6; } + return 5; +} + +pub fn letters_field(log: string, ls: u32) -> u32 { + return paths_field(log, ls) + 1; +} + +pub fn title_len(log: string, ls: u32) -> u32 { + return flen(log, ls, title_field(log, ls)); +} + +/// Is the comma list of field f of the line made of exactly the word w among its items? +pub fn list_has(log: string, ls: u32, f: u32, w: string) -> bool { + const fs : u32 = fstart(log, ls, f); + if (fs == NONE) { return false; } + const fe : u32 = fend(log, fs); + var ii : u32 = fs; + var st : u32 = fs; + while (ii <= fe) { + if (ii == fe or (log[ii] as u32) == 44) { + if (ii - st == (w.len as u32)) { + var kk : u32 = 0; + while (kk < (w.len as u32) and log[st + kk] == w[kk]) { kk = kk + 1; } + if (kk == (w.len as u32)) { return true; } + } + st = ii + 1; + } + ii = ii + 1; + } + return false; +} + +pub fn has_label(log: string, ls: u32, w: string) -> bool { + if (is_pr(log, ls)) { return false; } + return list_has(log, ls, 4, w); +} + +/// checks of a PR line: running or review means it is being looked at. +pub fn pr_in_review(log: string, ls: u32) -> bool { + const fs : u32 = fstart(log, ls, 4); + if (fs == NONE) { return false; } + const c : u32 = log[fs] as u32; + return c == 114; +} + +/// Start of the next line after the one at ls, NONE at the end. +pub fn next_line(log: string, ls: u32) -> u32 { + const le : u32 = line_end(log, ls); + if (le + 1 >= (log.len as u32)) { return NONE; } + return le + 1; +} + +/// Start of the open PR that closes issue num, NONE if there is none. +pub fn open_pr_of(log: string, num: u32) -> u32 { + var ls : u32 = 0; + if (log.len == 0) { return NONE; } + while (ls != NONE) { + if (is_pr(log, ls) and state_byte(log, ls) == 111) { + const fs : u32 = fstart(log, ls, 3); + if (fs != NONE and dec(log, fs, fend(log, fs)) == num) { return ls; } + } + ls = next_line(log, ls); + } + return NONE; +} + +/// Is there a merged PR that closes issue num? +pub fn merged_pr_of(log: string, num: u32) -> bool { + var ls : u32 = 0; + if (log.len == 0) { return false; } + while (ls != NONE) { + if (is_pr(log, ls) and state_byte(log, ls) == 109) { + const fs : u32 = fstart(log, ls, 3); + if (fs != NONE and dec(log, fs, fend(log, fs)) == num) { return true; } + } + ls = next_line(log, ls); + } + return false; +} + +/// Is there an open issue num in the log (a PR folds into it)? +pub fn open_issue_of(log: string, num: u32) -> bool { + var ls : u32 = 0; + if (log.len == 0 or num == 0) { return false; } + while (ls != NONE) { + if (is_pr(log, ls) == false and state_byte(log, ls) == 111 and card_num(log, ls) == num) { return true; } + ls = next_line(log, ls); + } + return false; +} + +/// THE COLUMN RULE (see the header). +pub fn column(log: string, ls: u32) -> u32 { + const sb : u32 = state_byte(log, ls); + const num : u32 = card_num(log, ls); + if (is_pr(log, ls) == false) { + if (sb == 111) { + const p : u32 = open_pr_of(log, num); + if (p != NONE) { + if (pr_in_review(log, p)) { return COL_HIDDEN; } + return COL_DOING; + } + if (has_label(log, ls, "backlog")) { return COL_BACKLOG; } + return COL_TODO; + } + if (merged_pr_of(log, num)) { return COL_HIDDEN; } + if (state_days(log, ls) <= DONE_DAYS) { return COL_DONE; } + return COL_HIDDEN; + } + if (sb == 111) { + if (pr_in_review(log, ls)) { return COL_REVIEW; } + const fs : u32 = fstart(log, ls, 3); + if (fs != NONE and open_issue_of(log, dec(log, fs, fend(log, fs)))) { return COL_HIDDEN; } + return COL_DOING; + } + if (sb == 109 and state_days(log, ls) <= DONE_DAYS) { return COL_DONE; } + return COL_HIDDEN; +} + +/// How many cards the board draws in column c. +pub fn count_in(log: string, c: u32) -> u32 { + var ls : u32 = 0; + var n : u32 = 0; + if (log.len == 0) { return 0; } + while (ls != NONE) { + if (column(log, ls) == c) { n = n + 1; } + ls = next_line(log, ls); + } + return n; +} + +/// Start of the k-th card of column c, in log order; NONE past the end. +pub fn nth_card(log: string, c: u32, k: u32) -> u32 { + var ls : u32 = 0; + var n : u32 = 0; + if (log.len == 0) { return NONE; } + while (ls != NONE) { + if (column(log, ls) == c) { + if (n == k) { return ls; } + n = n + 1; + } + ls = next_line(log, ls); + } + return NONE; +} + +// ---- the pinned agents ---- + +/// The k-th letter of the card's letters field (comma list); 0 past the end, for a letter out of 1..27 and for a +/// repeat of an earlier letter (the list is distinct by contract; a repeat is skipped, not trusted). +pub fn raw_agent_at(log: string, ls: u32, k: u32) -> u32 { + const fs : u32 = fstart(log, ls, letters_field(log, ls)); + if (fs == NONE) { return 0; } + const fe : u32 = fend(log, fs); + var ii : u32 = fs; + var st : u32 = fs; + var seen : u32 = 0; + var n : u32 = 0; + while (ii <= fe) { + if (ii == fe or (log[ii] as u32) == 44) { + if (ii > st) { + const l : u32 = dec(log, st, ii); + if (l >= 1 and l <= 27 and (seen & (1 << (l - 1))) == 0) { + if (n == k) { return l; } + seen = seen | (1 << (l - 1)); + n = n + 1; + } + } + st = ii + 1; + } + ii = ii + 1; + } + return 0; +} + +/// How many agents the card pins: 1..MAX_CHIPS, never 0. +pub fn agent_count(log: string, ls: u32) -> u32 { + var n : u32 = 0; + while (n < MAX_CHIPS and raw_agent_at(log, ls, n) != 0) { n = n + 1; } + if (n == 0) { return 1; } + return n; +} + +/// Agent k of the card, owner first; the fallback letter when no path has an owner. +pub fn agent_at(log: string, ls: u32, k: u32) -> u32 { + if (k >= agent_count(log, ls)) { return 0; } + const l : u32 = raw_agent_at(log, ls, k); + if (l == 0) { return FALLBACK_LETTER; } + return l; +} + +/// The tool letter of the card: its owner. +pub fn card_tool(log: string, ls: u32) -> u32 { + return agent_at(log, ls, 0); +} + +// ---- drag and drop ---- +pub const ACT_NONE : u32 = 0; +pub const ACT_REFUSED : u32 = 1; +pub const ACT_PARK : u32 = 2; +pub const ACT_START_AGENT : u32 = 3; +pub const ACT_PAUSE : u32 = 4; +pub const ACT_REQUEST_REVIEW : u32 = 5; +pub const ACT_REWORK : u32 = 6; +pub const ACT_WIP_FULL : u32 = 7; +pub const ACT_MERGE_ONLY : u32 = 8; + +/// What dropping a card from column `from` onto column `to` does; doing = cards now in DOING. +/// BACKLOG <-> TODO ACT_PARK: add or remove the label backlog (no agent moves). +/// TODO -> DOING ACT_START_AGENT: one agent on a fresh lane worktree, the lane plan of evolve.t27 (Sonnet, +/// Closes #N); ACT_WIP_FULL when DOING already holds the cap (WIP_DOING = 3 = MAX_CHIPS). +/// DOING -> TODO ACT_PAUSE: stop the agent, the lane worktree stays. +/// DOING -> REVIEW ACT_REQUEST_REVIEW: open the PR of the lane and ask for review. +/// REVIEW -> DOING ACT_REWORK: hand the findings back to the agent (counts against the cap). +/// REVIEW -> DONE ACT_MERGE_ONLY: refused. A card reaches DONE only when the PR merges through the gates +/// (merge_judge); a drop never merges, never forces a close. +/// Every other move, any move out of DONE or onto BACKLOG from DOING, is ACT_REFUSED. Same column: ACT_NONE. +pub fn drag_action(from: u32, to: u32, doing: u32) -> u32 { + if (from >= COL_COUNT or to >= COL_COUNT) { return ACT_REFUSED; } + if (from == to) { return ACT_NONE; } + if (from == COL_BACKLOG and to == COL_TODO) { return ACT_PARK; } + if (from == COL_TODO and to == COL_BACKLOG) { return ACT_PARK; } + if (from == COL_TODO and to == COL_DOING) { + if (wip_full(COL_DOING, doing)) { return ACT_WIP_FULL; } + return ACT_START_AGENT; + } + if (from == COL_DOING and to == COL_TODO) { return ACT_PAUSE; } + if (from == COL_DOING and to == COL_REVIEW) { return ACT_REQUEST_REVIEW; } + if (from == COL_REVIEW and to == COL_DOING) { + if (wip_full(COL_DOING, doing)) { return ACT_WIP_FULL; } + return ACT_REWORK; + } + if (from == COL_REVIEW and to == COL_DONE) { return ACT_MERGE_ONLY; } + return ACT_REFUSED; +} + +pub fn drag_allowed(from: u32, to: u32, doing: u32) -> bool { + const a : u32 = drag_action(from, to, doing); + return a != ACT_NONE and a != ACT_REFUSED and a != ACT_WIP_FULL and a != ACT_MERGE_ONLY; +} + +// ---- layout: nested rects, the packer of dash.t27 ---- +pub const PAD : f64 = 8.0; +pub const COL_GAP : f64 = 6.0; +pub const COL_HEAD : f64 = 28.0; +pub const CARD_PAD : f64 = 4.0; +pub const CARD_H : f64 = 64.0; +pub const CARD_GAP : f64 = 6.0; +pub const CHIP_W : f64 = 22.0; +pub const CHIP_H : f64 = 16.0; +pub const CHIP_GAP : f64 = 2.0; + +fn fl(x: u32) -> f64 { return @as(f64, @floatFromInt(x)); } + +pub fn col_w(w: f64) -> f64 { return cell_w(w - 2.0 * PAD, COL_COUNT, COL_GAP); } +pub fn col_x(c: u32, w: f64) -> f64 { return PAD + dash_cell_x(c, w - 2.0 * PAD, COL_COUNT, COL_GAP); } +pub fn card_x(c: u32, w: f64) -> f64 { return col_x(c, w) + CARD_PAD; } +pub fn card_w(w: f64) -> f64 { return col_w(w) - 2.0 * CARD_PAD; } +pub fn card_y(i: u32) -> f64 { return PAD + COL_HEAD + dash_cell_y(i, 1, CARD_H, CARD_GAP); } +pub fn chip_x(c: u32, w: f64, i: u32) -> f64 { return card_x(c, w) + CARD_PAD + fl(i) * (CHIP_W + CHIP_GAP); } + +/// Height of a column holding n cards. +pub fn col_h(n: u32) -> f64 { return COL_HEAD + pack_h(n, 1, CARD_H, CARD_GAP) + PAD; } + +/// Height of the board when the tallest column holds n cards. +pub fn board_h(n: u32) -> f64 { return PAD + col_h(n) + PAD; } + +/// The column under x of a board of width w, COL_HIDDEN (5) when none (a gap or the padding). +pub fn column_at(w: f64, x: f64) -> u32 { + const rel : f64 = x - PAD; + if (rel < 0.0) { return COL_HIDDEN; } + return pack_at(COL_COUNT, w - 2.0 * PAD, COL_COUNT, 1.0e9, COL_GAP, rel, 0.0); +} + +/// The card under (x, y) of column c of a board of width w holding n cards, n when none. +pub fn card_at(n: u32, w: f64, c: u32, x: f64, y: f64) -> u32 { + return pack_at(n, card_w(w), 1, CARD_H, CARD_GAP, x - card_x(c, w), y - card_y(0)); +} + +// ---- tests: ten lines shaped on this repository's recent log ---- +// Numbers and titles come from the log (#11275 the epic, #11264/#11278 the PTY server, #11265/#11277 the homes +// reseal, #11260 the KEY_FILES issue, #10938/#11104 the widgets, #11063 the night loop); the states, the labels, the +// paths and the PR number 11280 are SAMPLE data, made to hit every rule once. +pub const SAMPLE : string = "issue\t11275\topen\tT27 OS: ergonomic project management, one tool per letter\tt27os,epic\tspecs/t27os/agent_parts.t27,specs/t27os/homes.t27\t26\nissue\t11260\topen\tagents: KEY_FILES point at the directories each domain changes\tbacklog\tspecs/automation/x.t27\t10\nissue\t11264\topen\tT27 OS: the PTY server reads the Keychain itself\tt27os\tspecs/t27os/pty_server.t27\t26\npr\t11278\topen\t11264\trunning\tT27 OS: the PTY server reads the Keychain itself\tspecs/t27os/pty_server.t27,specs/t27os/keys.t27\t26\nissue\t10938\topen\twidgets: gatle drawn natively\twidgets\tspecs/t27f/widget_factory.t27,bootstrap/src/compiler.rs\t26,3\npr\t11280\topen\t10938\tfail\twidgets: gatle drawn natively\tspecs/t27f/widget_factory.t27,bootstrap/src/compiler.rs\t26,3\nissue\t11265\tclosed/2\tseals: homes.t27 resealed\tseals\t.trinity/seals/t27os_T27osHomes.json\t2\npr\t11277\tmerged/2\t11265\tpass\tseals: homes.t27 resealed (gen-drift)\t.trinity/seals/t27os_T27osHomes.json\t2\nissue\t11063\tclosed/20\tnight loop epic\tepic\tdocs/now/x.md\t5\nissue\t11104\topen\twidgets: catalog records every tool at PORT_DRAW\twidgets\t\t"; + +test the_sample_has_ten_lines_and_the_rule_puts_each_card_in_its_column { + const s : string = SAMPLE; + var ls : u32 = 0; + var lines : u32 = 0; + while (ls != NONE) { + lines = lines + 1; + ls = next_line(s, ls); + } + assert lines == 10; + ls = 0; + assert column(s, ls) == COL_TODO; + ls = next_line(s, ls); + assert column(s, ls) == COL_BACKLOG; + ls = next_line(s, ls); + assert card_num(s, ls) == 11264; + assert column(s, ls) == COL_HIDDEN; + ls = next_line(s, ls); + assert is_pr(s, ls); + assert column(s, ls) == COL_REVIEW; + ls = next_line(s, ls); + assert card_num(s, ls) == 10938; + assert column(s, ls) == COL_DOING; + ls = next_line(s, ls); + assert column(s, ls) == COL_HIDDEN; + ls = next_line(s, ls); + assert column(s, ls) == COL_HIDDEN; + ls = next_line(s, ls); + assert card_num(s, ls) == 11277; + assert column(s, ls) == COL_DONE; + ls = next_line(s, ls); + assert state_days(s, ls) == 20; + assert column(s, ls) == COL_HIDDEN; + ls = next_line(s, ls); + assert card_num(s, ls) == 11104; + assert column(s, ls) == COL_TODO; +} + +test the_column_counts_of_the_sample { + const s : string = SAMPLE; + assert count_in(s, COL_BACKLOG) == 1; + assert count_in(s, COL_TODO) == 2; + assert count_in(s, COL_DOING) == 1; + assert count_in(s, COL_REVIEW) == 1; + assert count_in(s, COL_DONE) == 1; + assert count_in(s, COL_HIDDEN) == 4; + assert count_in(s, COL_BACKLOG) + count_in(s, COL_TODO) + count_in(s, COL_DOING) + count_in(s, COL_REVIEW) + count_in(s, COL_DONE) + count_in(s, COL_HIDDEN) == 10; + assert count_in("", COL_TODO) == 0; +} + +test every_drawn_card_has_at_least_one_agent_owner_first { + const s : string = SAMPLE; + var c : u32 = 0; + var cards : u32 = 0; + while (c < COL_COUNT) { + var k : u32 = 0; + while (k < count_in(s, c)) { + const ls : u32 = nth_card(s, c, k); + assert ls != NONE; + assert agent_count(s, ls) >= 1; + assert agent_count(s, ls) <= MAX_CHIPS; + assert agent_at(s, ls, 0) >= 1; + assert agent_at(s, ls, 0) <= 27; + assert agent_at(s, ls, agent_count(s, ls)) == 0; + assert card_tool(s, ls) == agent_at(s, ls, 0); + cards = cards + 1; + k = k + 1; + } + c = c + 1; + } + assert cards == 6; +} + +test agents_come_from_the_letters_field_owner_first_distinct { + const s : string = SAMPLE; + const epic : u32 = nth_card(s, COL_TODO, 0); + assert card_num(s, epic) == 11275; + assert agent_count(s, epic) == 1; + assert agent_at(s, epic, 0) == 26; + const pr : u32 = nth_card(s, COL_REVIEW, 0); + assert card_num(s, pr) == 11278; + assert agent_count(s, pr) == 1; + const wid : u32 = nth_card(s, COL_DOING, 0); + assert card_num(s, wid) == 10938; + assert agent_count(s, wid) == 2; + assert agent_at(s, wid, 0) == 26; + assert agent_at(s, wid, 1) == 3; + assert agent_at(s, wid, 2) == 0; + const job : u32 = nth_card(s, COL_BACKLOG, 0); + assert agent_at(s, job, 0) == 10; + // no paths at all: pinned to the fallback letter, a card is never agentless + const bare : u32 = nth_card(s, COL_TODO, 1); + assert card_num(s, bare) == 11104; + assert raw_agent_at(s, bare, 0) == 0; + assert agent_count(s, bare) == 1; + assert agent_at(s, bare, 0) == FALLBACK_LETTER; + assert agent_at(s, bare, 1) == 0; + // a repeat, a zero and a letter past 27 are skipped; at most MAX_CHIPS are used + const odd : string = "issue\t1\topen\tt\t\tdocs/a.md\t3,3,0,28,10,27,5,7\n"; + assert agent_count(odd, 0) == 3; + assert agent_at(odd, 0, 0) == 3; + assert agent_at(odd, 0, 1) == 10; + assert agent_at(odd, 0, 2) == 27; + assert agent_at(odd, 0, 3) == 0; +} + +test the_counted_title_and_labels_are_read_without_copying { + const s : string = SAMPLE; + const epic : u32 = nth_card(s, COL_TODO, 0); + assert title_len(s, epic) == 57; + assert has_label(s, epic, "epic"); + assert has_label(s, epic, "t27os"); + assert has_label(s, epic, "epi") == false; + assert has_label(s, epic, "backlog") == false; + assert has_label(s, nth_card(s, COL_REVIEW, 0), "epic") == false; + assert card_num(s, nth_card(s, COL_DONE, 0)) == 11277; + assert state_byte(s, nth_card(s, COL_DONE, 0)) == 109; +} + +test the_done_window_is_seven_days_and_a_closed_issue_without_pr_is_done { + const a : string = "issue\t5\tclosed/7\tx\t\tdocs/a.md\t5\n"; + const b : string = "issue\t6\tclosed/8\tx\t\tdocs/a.md\t5\n"; + const c : string = "pr\t7\tclosed/1\t0\tpass\tx\tdocs/a.md\t5\n"; + const d : string = "pr\t8\topen\t0\tnone\tx\tdocs/a.md\t5\n"; + const e : string = "pr\t9\topen\t0\treview\tx\tdocs/a.md\t5\n"; + assert column(a, 0) == COL_DONE; + assert column(b, 0) == COL_HIDDEN; + assert column(c, 0) == COL_HIDDEN; + assert column(d, 0) == COL_DOING; + assert column(e, 0) == COL_REVIEW; +} + +test drag_rules_each_allowed_move_implies_one_action { + assert drag_action(COL_BACKLOG, COL_TODO, 0) == ACT_PARK; + assert drag_action(COL_TODO, COL_BACKLOG, 0) == ACT_PARK; + assert drag_action(COL_TODO, COL_DOING, 0) == ACT_START_AGENT; + assert drag_action(COL_TODO, COL_DOING, 2) == ACT_START_AGENT; + assert drag_action(COL_DOING, COL_TODO, 3) == ACT_PAUSE; + assert drag_action(COL_DOING, COL_REVIEW, 3) == ACT_REQUEST_REVIEW; + assert drag_action(COL_REVIEW, COL_DOING, 1) == ACT_REWORK; + assert drag_action(COL_TODO, COL_TODO, 0) == ACT_NONE; + assert drag_allowed(COL_TODO, COL_DOING, 0); + assert drag_allowed(COL_DOING, COL_REVIEW, 1); +} + +test merge_is_never_forced_and_done_is_never_left { + assert drag_action(COL_REVIEW, COL_DONE, 0) == ACT_MERGE_ONLY; + assert drag_allowed(COL_REVIEW, COL_DONE, 0) == false; + var f : u32 = 0; + while (f < COL_COUNT) { + // nothing is dropped into DONE except through the merge (refused or merge-only), and DONE is a dead end + if (f != COL_DONE) { assert drag_allowed(f, COL_DONE, 0) == false; } + if (f != COL_DONE) { assert drag_action(COL_DONE, f, 0) == ACT_REFUSED; } + f = f + 1; + } + assert drag_action(COL_BACKLOG, COL_DOING, 0) == ACT_REFUSED; + assert drag_action(COL_DOING, COL_BACKLOG, 0) == ACT_REFUSED; + assert drag_action(COL_TODO, COL_REVIEW, 0) == ACT_REFUSED; + assert drag_action(COL_REVIEW, COL_TODO, 0) == ACT_REFUSED; + assert drag_action(9, COL_TODO, 0) == ACT_REFUSED; + assert drag_action(COL_TODO, COL_HIDDEN, 0) == ACT_REFUSED; +} + +test the_wip_limit_of_doing_is_the_agent_cap { + assert WIP_DOING == 3; + assert WIP_DOING == MAX_CHIPS; + // the cap is the one of evolve.t27: GOD MODE refuses a 4th agent at exactly WIP_DOING running + assert why_not(true, true, WIP_DOING - 1, 0) != R_AGENT_CAP; + assert why_not(true, true, WIP_DOING, 0) == R_AGENT_CAP; + assert FALLBACK_LETTER == LETTER_UNOWNED; + assert wip_limit(COL_DOING) == WIP_DOING; + assert wip_limit(COL_TODO) == 0; + assert wip_full(COL_DOING, 2) == false; + assert wip_full(COL_DOING, 3); + assert wip_full(COL_DOING, 4); + assert wip_full(COL_TODO, 400) == false; + assert drag_action(COL_TODO, COL_DOING, 3) == ACT_WIP_FULL; + assert drag_action(COL_REVIEW, COL_DOING, 3) == ACT_WIP_FULL; + assert drag_allowed(COL_TODO, COL_DOING, 3) == false; + // pausing or reviewing frees a slot and is never blocked by the cap + assert drag_action(COL_DOING, COL_REVIEW, 3) == ACT_REQUEST_REVIEW; + // the sample holds 1 card in DOING: room for two more starts + assert wip_full(COL_DOING, count_in(SAMPLE, COL_DOING)) == false; +} + +test the_tree_is_board_column_card_chip_and_badge { + assert child_kind(KK_BOARD, 0) == KK_COLUMN; + assert child_kind(KK_COLUMN, 0) == KK_CARD; + assert child_kind(KK_CARD, 0) == KK_CHIP; + assert child_kind(KK_CARD, 1) == KK_BADGE; + assert child_kind(KK_CHIP, 0) == KK_KINDS; + assert child_kind(KK_BADGE, 0) == KK_KINDS; + assert child_kind(KK_BOARD, 1) == KK_KINDS; + assert column_name(COL_REVIEW) == "REVIEW"; + assert column_name(COL_HIDDEN) == ""; +} + +test the_layout_nests_five_columns_of_cards_with_the_dash_packer { + const w : f64 = 1000.0; + // 5 columns, 4 gaps of 6, padding 8 each side: (1000 - 16 - 24) / 5 = 192 + assert col_w(w) == 192.0; + assert col_x(0, w) == 8.0; + assert col_x(1, w) == 206.0; + assert col_x(4, w) == 8.0 + 4.0 * 198.0; + assert card_w(w) == 184.0; + assert card_x(0, w) == 12.0; + assert card_y(0) == 36.0; + assert card_y(1) == 106.0; + assert col_h(0) == 36.0; + assert col_h(2) == 28.0 + 134.0 + 8.0; + assert board_h(2) == 8.0 + col_h(2) + 8.0; + assert chip_x(0, w, 1) == 12.0 + 4.0 + 24.0; + // the rects stay inside the board + assert col_x(4, w) + col_w(w) + PAD == w; +} + +test hit_testing_finds_the_column_then_the_card_and_misses_in_the_gaps { + const w : f64 = 1000.0; + assert column_at(w, 10.0) == COL_BACKLOG; + assert column_at(w, 210.0) == COL_TODO; + assert column_at(w, 500.0) == COL_DOING; + assert column_at(w, 990.0) == COL_DONE; + assert column_at(w, 203.0) == COL_HIDDEN; + assert column_at(w, 2.0) == COL_HIDDEN; + assert card_at(3, w, 1, card_x(1, w) + 5.0, card_y(0) + 5.0) == 0; + assert card_at(3, w, 1, card_x(1, w) + 5.0, card_y(1) + 5.0) == 1; + assert card_at(3, w, 1, card_x(1, w) + 5.0, card_y(0) + CARD_H + 2.0) == 3; + assert card_at(3, w, 1, card_x(1, w) + 5.0, card_y(3) + 5.0) == 3; + assert card_at(3, w, 1, card_x(1, w) + 5.0, 5.0) == 3; +} diff --git a/specs/t27os/kernel.t27 b/specs/t27os/kernel.t27 new file mode 100644 index 0000000000..b042fd3feb --- /dev/null +++ b/specs/t27os/kernel.t27 @@ -0,0 +1,669 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/t27os/kernel.t27 -- the Queen's kernel for T27 OS: the BEAM actor model of specs/queen/actors.t27 built +; into the OS, to manage and SCALE it by the computer's resources. +; Owner, 2026-10-20: "we built a BEAM-like actor model in .t27 -- build it natively into T27 OS, into the Queen's +; kernel, to manage and scale the system depending on the computer's resources." Closes #11275. ASCII only (L3), +; English only (LANG-EN). AGENTS.md "Actors, not threads": everything that runs at once is an actor under a supervisor. +; -- +; DRY: nothing of the actor model is restated. Restart types and strategies, intensity (MaxR/MaxT), backoff, stop +; order, mailbox delivery, the control lane and its priority all come from actors.t27 (used); the cap check of the +; GOD MODE rule from evolve.t27 (used); the packer from dash.t27 (used, read only); number printing from +; session_store.t27 (used). Only what is new lives here: the tree of T27 OS as data, the resource formula, the +; shedding plan, the kernel's message tags and the view model. +; -- +; THE TREE (indices are the node ids; lanes are dynamic children, node 9 + j for lane j, oldest first): +; 0 ROOT one_for_one MaxR/MaxT = ROOT_MAX_RESTARTS/ROOT_PERIOD_SECONDS (actors.t27) +; 1 QUEEN permanent, one_for_one, supervises 2 and 3 +; 2 LANES permanent supervisor, one_for_one, MaxR/MaxT = DOMAIN_* (actors.t27); children are the agent +; lanes, TRANSIENT, one per pane/task. A lane that ends normally or by shutdown (a shed) stays down. +; 3 SERVICES permanent supervisor, one_for_one, MaxR/MaxT = SERVICES_MAX_R/SERVICES_PERIOD; children are +; permanent: 4 pty server link, 5 card fetcher, 6 bench poller, 7 kanban poller, 8 pulse poller. +; A lane that crashes more than MaxR times in MaxT escalates (KD_OWNER_REQUEST): the Queen does not restart it again; +; she records an OWNER REQUEST (what, options, default, due) and the task stays held until the default time. +; -- +; WHAT THE HOST MUST READ, natively, with no foreign code (macOS libSystem; a pty_sys-like glue file adds the +; @extern imports; NOTHING here calls them, this spec only decides). The sandbox of the session that wrote this denied +; `sysctl hw.ncpu`, so NO NUMBER BELOW IS MEASURED ON THIS MAC; the tests use stated machines, not a probe: +; mem_bytes sysctlbyname("hw.memsize", &u64, &len, 0, 0) -> u64 bytes +; ncpu sysctlbyname("hw.ncpu", &i32, &len, 0, 0) -> logical cpus +; load1x100 getloadavg(double[3], 3) -> loadavg[0] * 100 as u32 (vm.loadavg is the same data) +; free_bytes host_statistics64(mach_host_self(), HOST_VM_INFO64, vm_statistics64*, &count) then +; (free_count + inactive_count) * page size; page size from sysctlbyname("hw.pagesize") +; on_battery IOPSCopyPowerSourcesInfo + IOPSGetProvidingPowerSourceType (IOKit, OPTIONAL): "Battery Power" -> +; true. If IOKit is not linked the host passes false, and the cap is merely not halved. +; per lane proc_pidinfo(pid, PROC_PIDTASKINFO) pti_resident_size, to REPLACE LANE_MB by a measurement. +; Read cadence: once at start for mem_bytes and ncpu; every KERNEL_TICK_SECONDS for load, free and power. +; -- +; WHAT THE WINDOW MUST DRAW (the Q page): the view of section 6 -- a row of GAUGES (used, free, load, cap), the +; cap-reason strip, then one row per tree node indented by depth, each with kind, state, restarts in window, mailbox +; depth and RSS estimate. kv_* give every x, y, w, h; kv_at hit-tests. Colors and glyphs stay in the window. + +module T27osKernel; + +use queen::actors::STRAT_ONE_FOR_ONE; +use queen::actors::RESTART_PERMANENT; +use queen::actors::RESTART_TRANSIENT; +use queen::actors::X_NORMAL; +use queen::actors::X_SHUTDOWN; +use queen::actors::X_CRASH; +use queen::actors::SUP_NOTHING; +use queen::actors::SUP_RESTART; +use queen::actors::SUP_GIVE_UP; +use queen::actors::ROOT_MAX_RESTARTS; +use queen::actors::ROOT_PERIOD_SECONDS; +use queen::actors::DOMAIN_MAX_RESTARTS; +use queen::actors::DOMAIN_PERIOD_SECONDS; +use queen::actors::MAILBOX_CAP; +use queen::actors::D_QUEUED; +use queen::actors::D_DROPPED_FULL; +use queen::actors::LANE_DATA; +use queen::actors::LANE_CONTROL; +use queen::actors::LANE_NONE; +use queen::actors::M_TASK_OFFER; +use queen::actors::on_child_exit; +use queen::actors::should_restart; +use queen::actors::stop_rank; +use queen::actors::backoff_seconds; +use queen::actors::deliver; +use queen::actors::next_lane; +use queen::actors::ctl_post; +use queen::actors::ctl_next; +use queen::actors::ctl_take; +use queen::actors::ctl_pending; +use t27os::evolve::why_not_cap; +use t27os::evolve::why_not; +use t27os::dash::pack_cols; +use t27os::dash::cell_w; +use t27os::dash::dash_cell_x; +use t27os::dash::dash_cell_y; +use t27os::dash::pack_h; +use t27os::dash::pack_at; +use t27os::dash::GAP; +use t27os::session_store::put_bytes; +use t27os::session_store::put_dec; + +; --- 1. The supervision tree as data --------------------------------------------------------------------- + +pub const N_ROOT : u32 = 0; +pub const N_QUEEN : u32 = 1; +pub const N_LANES : u32 = 2; +pub const N_SERVICES : u32 = 3; +pub const N_SERVICE0 : u32 = 4; +pub const SERVICE_COUNT : u32 = 5; +pub const N_LANE0 : u32 = 9; +pub const FIXED_NODES : u32 = 9; +pub const N_NONE : u32 = 255; + +/// Service slots, in start order. +pub const SV_PTY : u32 = 0; +pub const SV_CARDS : u32 = 1; +pub const SV_BENCH : u32 = 2; +pub const SV_KANBAN : u32 = 3; +pub const SV_PULSE : u32 = 4; +pub const SERVICE_NAMES : [5]str = ["PTY LINK", "CARDS", "BENCH", "KANBAN", "PULSE"]; + +/// What a node is. +pub const KN_ROOT : u32 = 0; +pub const KN_QUEEN : u32 = 1; +pub const KN_SUP : u32 = 2; +pub const KN_SERVICE : u32 = 3; +pub const KN_LANE : u32 = 4; +pub const KN_NONE : u32 = 99; +pub const KIND_NAMES : [5]str = ["ROOT", "QUEEN", "SUP", "SERVICE", "LANE"]; + +pub const LANES_MAX_R : u32 = DOMAIN_MAX_RESTARTS; +pub const LANES_PERIOD_SECONDS : u32 = DOMAIN_PERIOD_SECONDS; +/// Services are polls and links that fail for outside reasons (a network blip): a little more room than a lane. +/// Chosen, not measured. +pub const SERVICES_MAX_R : u32 = 5; +pub const SERVICES_PERIOD_SECONDS : u32 = 300; +pub const QUEEN_MAX_R : u32 = ROOT_MAX_RESTARTS; +pub const QUEEN_PERIOD_SECONDS : u32 = ROOT_PERIOD_SECONDS; + +pub fn node_count(lanes: u32) -> u32 { + return FIXED_NODES + lanes; +} + +pub fn node_kind(i: u32, lanes: u32) -> u32 { + if (i == N_ROOT) { return KN_ROOT; } + if (i == N_QUEEN) { return KN_QUEEN; } + if (i == N_LANES or i == N_SERVICES) { return KN_SUP; } + if (i >= N_SERVICE0 and i < N_SERVICE0 + SERVICE_COUNT) { return KN_SERVICE; } + if (i >= N_LANE0 and i < node_count(lanes)) { return KN_LANE; } + return KN_NONE; +} + +pub fn node_parent(i: u32, lanes: u32) -> u32 { + const k : u32 = node_kind(i, lanes); + if (k == KN_QUEEN) { return N_ROOT; } + if (k == KN_SUP) { return N_QUEEN; } + if (k == KN_SERVICE) { return N_SERVICES; } + if (k == KN_LANE) { return N_LANES; } + return N_NONE; +} + +pub fn node_depth(i: u32, lanes: u32) -> u32 { + var d : u32 = 0; + var n : u32 = i; + while (node_parent(n, lanes) != N_NONE) { + n = node_parent(n, lanes); + d = d + 1; + } + return d; +} + +/// How a node's parent restarts it. Lanes are transient; everything else is permanent. +pub fn node_restart(i: u32, lanes: u32) -> u8 { + if (node_kind(i, lanes) == KN_LANE) { return RESTART_TRANSIENT; } + return RESTART_PERMANENT; +} + +/// A supervisor's own strategy over its children: one_for_one at every level, for the reason actors.t27 gives for +/// the Queen's tree (no child holds state another needs). STRAT_NONE for a node that supervises nothing. +pub const STRAT_NONE : u8 = 255; + +pub fn node_strategy(i: u32, lanes: u32) -> u8 { + const k : u32 = node_kind(i, lanes); + if (k == KN_ROOT or k == KN_QUEEN or k == KN_SUP) { return STRAT_ONE_FOR_ONE; } + return STRAT_NONE; +} + +/// MaxR and MaxT of the supervisor i (the intensity of ITS children's restarts); 0 for a worker. +pub fn node_max_r(i: u32, lanes: u32) -> u32 { + if (i == N_ROOT) { return ROOT_MAX_RESTARTS; } + if (i == N_QUEEN) { return QUEEN_MAX_R; } + if (i == N_LANES) { return LANES_MAX_R; } + if (i == N_SERVICES) { return SERVICES_MAX_R; } + return 0; +} + +pub fn node_max_t(i: u32, lanes: u32) -> u32 { + if (i == N_ROOT) { return ROOT_PERIOD_SECONDS; } + if (i == N_QUEEN) { return QUEEN_PERIOD_SECONDS; } + if (i == N_LANES) { return LANES_PERIOD_SECONDS; } + if (i == N_SERVICES) { return SERVICES_PERIOD_SECONDS; } + return 0; +} + +/// What a lane's exit does. A crash is restarted until MaxR is exceeded; then the lanes supervisor gives up and the +/// Queen records an owner request instead of restarting forever. A normal end or a shed (X_SHUTDOWN) restarts nothing. +pub const KD_NOTHING : u32 = 0; +pub const KD_RESTART : u32 = 1; +pub const KD_OWNER_REQUEST : u32 = 2; + +pub fn lane_decision(reason: u8, earlier_restarts: u32) -> u32 { + const d : u8 = on_child_exit(RESTART_TRANSIENT, reason, earlier_restarts, LANES_MAX_R); + if (d == SUP_GIVE_UP) { return KD_OWNER_REQUEST; } + if (d == SUP_RESTART) { return KD_RESTART; } + return KD_NOTHING; +} + +/// A service is permanent: it restarts on any exit, and when the services supervisor gives up the Queen restarts that +/// supervisor in turn (a permanent child of hers), the root deciding last by ROOT_*. +pub fn service_decision(reason: u8, earlier_restarts: u32) -> u32 { + const d : u8 = on_child_exit(RESTART_PERMANENT, reason, earlier_restarts, SERVICES_MAX_R); + if (d == SUP_GIVE_UP) { return KD_OWNER_REQUEST; } + return KD_RESTART; +} + +/// The wait before a lane's restart number n of an unstable streak (actors.t27 backoff). +pub fn lane_restart_wait(streak: u32) -> u32 { + return backoff_seconds(streak); +} + +/// An owner request the Queen recorded is chased once; the default stands at this age (CLAUDE.md "request, do not +/// ask"): the task is dropped back to the backlog, not retried. +pub const REQUEST_DEFAULT_SECONDS : u32 = 3600; + +pub fn request_default_due(age_seconds: u32) -> bool { + return age_seconds >= REQUEST_DEFAULT_SECONDS; +} + +; --- 2. Resource-aware scaling --------------------------------------------------------------------------- + +/// Cost estimates in MB. NOT MEASURED: chosen constants (the owner named ~450 MB for a Claude Code pane). A lane is +/// its pane PLUS the build or test process it spawns (cargo, t27c, zig), which is why a lane is counted at twice the +/// pane. HOST_BASE_MB is macOS, the window, the browser and the editors already resident on a developer Mac. Replace +/// by proc_pidinfo readings (header) before quoting any of them as a measurement. +pub const PANE_MB : u32 = 450; +pub const LANE_MB : u32 = 900; +pub const SERVICE_MB : u32 = 60; +pub const QUEEN_MB : u32 = 150; +pub const HOST_BASE_MB : u32 = 3072; +/// Never let free memory (free + inactive pages) fall under 1.5 GB. +pub const FLOOR_MB : u32 = 1536; +/// Held lanes resume only after free memory is back above the floor plus this margin (no flapping). +pub const RESUME_MARGIN_MB : u32 = 512; +pub const LANE_HARD_MAX : u32 = 16; +/// Load above ncpu * 80 / 100 holds new spawns. +pub const LOAD_HOLD_PERCENT : u32 = 80; +pub const KERNEL_TICK_SECONDS : u32 = 5; + +pub fn min2(a: u32, b: u32) -> u32 { + if (a < b) { return a; } + return b; +} + +pub fn mb_of(bytes: u64) -> u32 { + return @as(u32, @intCast(bytes >> 20)); +} + +/// What the kernel itself keeps resident: the Queen and the five services. +pub fn kernel_mb() -> u32 { + return QUEEN_MB + SERVICE_COUNT * SERVICE_MB; +} + +/// Lanes the memory of the machine carries: (mem - base - kernel - floor) / LANE_MB. Zero when it is negative. +pub fn mem_lanes(mem_mb: u32) -> u32 { + const fixed : u32 = HOST_BASE_MB + kernel_mb() + FLOOR_MB; + if (mem_mb <= fixed) { return 0; } + return (mem_mb - fixed) / LANE_MB; +} + +/// Lanes the cpus carry: all but one, one left for the window and the pty server. +pub fn cpu_lanes(ncpu: u32) -> u32 { + if (ncpu < 2) { return 1; } + return ncpu - 1; +} + +/// The cap on running lanes. min(memory, cpu, LANE_HARD_MAX), halved on battery, never below 1 (the Queen always +/// keeps one lane), and 1 when free memory could not be read (free_bytes == 0, the host's failure value). +/// load1x100 does not change the cap, it HOLDS new spawns (spawn_hold): a busy machine keeps its lanes. +pub fn agent_cap(mem_bytes: u64, free_bytes: u64, ncpu: u32, load1x100: u32, on_battery: bool) -> u32 { + var c : u32 = min2(min2(mem_lanes(mb_of(mem_bytes)), cpu_lanes(ncpu)), LANE_HARD_MAX); + if (on_battery) { c = c / 2; } + if (free_bytes == 0) { c = min2(c, 1); } + if (c < 1) { return 1; } + return c; +} + +/// Why a cap is what it is, for the strip of the view. +pub const CR_MEM : u32 = 0; +pub const CR_CPU : u32 = 1; +pub const CR_HARD : u32 = 2; +pub const CR_BATTERY : u32 = 3; +pub const CR_UNREAD : u32 = 4; +pub const CR_MIN : u32 = 5; + +pub fn cap_reason(mem_bytes: u64, free_bytes: u64, ncpu: u32, on_battery: bool) -> u32 { + if (free_bytes == 0) { return CR_UNREAD; } + if (on_battery) { return CR_BATTERY; } + const m : u32 = mem_lanes(mb_of(mem_bytes)); + const c : u32 = cpu_lanes(ncpu); + if (m < 1) { return CR_MIN; } + if (m <= c and m <= LANE_HARD_MAX) { return CR_MEM; } + if (c <= LANE_HARD_MAX) { return CR_CPU; } + return CR_HARD; +} + +/// Why new spawns are held now (H_NONE if they may start). +pub const H_NONE : u32 = 0; +pub const H_MEMORY : u32 = 1; +pub const H_LOAD : u32 = 2; + +pub fn spawn_hold(free_mb: u32, ncpu: u32, load1x100: u32) -> u32 { + if (free_mb < FLOOR_MB + RESUME_MARGIN_MB) { return H_MEMORY; } + if (load1x100 > ncpu * LOAD_HOLD_PERCENT) { return H_LOAD; } + return H_NONE; +} + +/// May a new lane start: under the cap and not held. +pub fn may_spawn(cap: u32, running: u32, hold: u32) -> bool { + if (hold != H_NONE) { return false; } + return running < cap; +} + +/// The GOD MODE rule's reason for doing nothing, with the cap this machine allows instead of evolve's fixed +/// MAX_AGENTS (R_OK if a task may start). The hold is the kernel's own and is checked by may_spawn. +pub fn lane_gate(god: bool, panel_open: bool, running: u32, started_hour: u32, mem_bytes: u64, free_bytes: u64, ncpu: u32, load1x100: u32, on_battery: bool) -> u32 { + return why_not_cap(god, panel_open, running, started_hour, agent_cap(mem_bytes, free_bytes, ncpu, load1x100, on_battery)); +} + +; --- 3. Backpressure and shedding ------------------------------------------------------------------------ + +/// Lanes to stop now: each stopped lane frees about LANE_MB, so ceil((floor - free) / LANE_MB), at most `running`. +/// Zero while free memory is at or above the floor. Never the Queen, never a service: only lanes are counted. +pub fn lanes_to_shed(free_mb: u32, running: u32) -> u32 { + if (free_mb >= FLOOR_MB) { return 0; } + const need : u32 = (FLOOR_MB - free_mb + LANE_MB - 1) / LANE_MB; + return min2(need, running); +} + +/// The lane (0 = oldest) with shed rank nth: the NEWEST first, which is actors.t27's stop order. +pub fn shed_lane(count: u32, nth: u32) -> u32 { + var i : u32 = 0; + while (i < count) { + if (stop_rank(i, count) == nth) { return i; } + i = i + 1; + } + return count; +} + +/// A shed lane ends with X_SHUTDOWN, a transient lane's quiet end: not restarted, not counted toward MaxR; its task +/// is HELD, not failed. Held tasks come back, oldest first, when resume_ok. +pub const SHED_REASON : u8 = X_SHUTDOWN; + +pub fn shed_restarts() -> bool { + return should_restart(RESTART_TRANSIENT, SHED_REASON); +} + +pub fn resume_ok(free_mb: u32, held: u32, running: u32, cap: u32, load_hold: u32) -> bool { + if (held == 0) { return false; } + if (free_mb < FLOOR_MB + RESUME_MARGIN_MB) { return false; } + return may_spawn(cap, running, load_hold); +} + +/// Mailbox limits per kind. A lane takes a few offers and cancels; the Queen takes the whole board's events +/// (actors.t27 MAILBOX_CAP). A full mailbox drops the message and counts a dead letter (deliver). +pub const LANE_MBOX : u32 = 16; +pub const SERVICE_MBOX : u32 = 64; +pub const QUEEN_MBOX : u32 = MAILBOX_CAP; + +pub fn mbox_cap(kind: u32) -> u32 { + if (kind == KN_LANE) { return LANE_MBOX; } + if (kind == KN_SERVICE) { return SERVICE_MBOX; } + return QUEEN_MBOX; +} + +pub fn kernel_deliver(kind: u32, reached: bool, queued: u32) -> u8 { + return deliver(reached, queued, mbox_cap(kind)); +} + +/// Kernel message tags. STOP and ESCALATE ride the control lane (taken before any offer, whatever arrived first) and +/// the lower tag first, so a STOP is received before an escalation, and both before a task offer (data lane). +pub const K_STOP : u64 = 8; +pub const K_ESCALATE : u64 = 9; +pub const K_TASK : u64 = M_TASK_OFFER; + +pub fn kernel_lane(tag: u64) -> u8 { + if (tag == K_STOP or tag == K_ESCALATE) { return LANE_CONTROL; } + return LANE_DATA; +} + +; --- 4. The view of the kernel for the Q page -------------------------------------------------------------- + +/// Node states. +pub const NS_RUNNING : u32 = 0; +pub const NS_RESTARTING : u32 = 1; +pub const NS_HELD : u32 = 2; +pub const NS_STOPPED : u32 = 3; +pub const NS_ESCALATED : u32 = 4; +pub const STATE_NAMES : [5]str = ["RUNNING", "RESTARTING", "HELD", "STOPPED", "ESCALATED"]; + +/// One node's row packed in a u64: state (3 bits) | restarts in window (4) << 3 | mailbox depth (10) << 7 | +/// RSS estimate in MB (14) << 17. +pub fn nv_pack(state: u32, restarts: u32, mbox: u32, rss_mb: u32) -> u64 { + return @as(u64, state & 7) | (@as(u64, restarts & 15) << 3) | (@as(u64, mbox & 1023) << 7) | (@as(u64, rss_mb & 16383) << 17); +} + +pub fn nv_state(v: u64) -> u32 { return @as(u32, @intCast(v & 7)); } +pub fn nv_restarts(v: u64) -> u32 { return @as(u32, @intCast((v >> 3) & 15)); } +pub fn nv_mbox(v: u64) -> u32 { return @as(u32, @intCast((v >> 7) & 1023)); } +pub fn nv_rss(v: u64) -> u32 { return @as(u32, @intCast((v >> 17) & 16383)); } + +/// The RSS estimate of a node (the constants above; NOT MEASURED). +pub fn node_rss_mb(i: u32, lanes: u32) -> u32 { + const k : u32 = node_kind(i, lanes); + if (k == KN_QUEEN) { return QUEEN_MB; } + if (k == KN_SERVICE) { return SERVICE_MB; } + if (k == KN_LANE) { return LANE_MB; } + return 0; +} + +/// Estimated RSS of the whole kernel and its lanes. +pub fn tree_rss_mb(lanes: u32) -> u32 { + return kernel_mb() + lanes * LANE_MB; +} + +/// Gauges: 0 used, 1 free, 2 load, 3 cap. Each is a fill 0..100. +pub const GA_USED : u32 = 0; +pub const GA_FREE : u32 = 1; +pub const GA_LOAD : u32 = 2; +pub const GA_CAP : u32 = 3; +pub const GAUGE_COUNT : u32 = 4; +pub const GAUGE_NAMES : [4]str = ["MEM USED", "MEM FREE", "LOAD", "LANES / CAP"]; + +pub fn gauge_fill(g: u32, mem_mb: u32, free_mb: u32, ncpu: u32, load1x100: u32, running: u32, cap: u32) -> u32 { + if (mem_mb == 0 or ncpu == 0 or cap == 0) { return 0; } + if (g == GA_USED) { return (mem_mb - min2(free_mb, mem_mb)) * 100 / mem_mb; } + if (g == GA_FREE) { return min2(free_mb, mem_mb) * 100 / mem_mb; } + if (g == GA_LOAD) { return min2(load1x100 / ncpu, 100); } + if (g == GA_CAP) { return min2(running * 100 / cap, 100); } + return 0; +} + +/// The cap-reason strip: "cap 3: 8 GB, 1.5 GB floor" and its siblings. Writes ASCII at `at`, returns the end. +pub fn put_reason(out: []u8, at: usize, cap: u32, reason: u32, mem_mb: u32, ncpu: u32) -> usize { + var n : usize = put_bytes(out, at, "cap "); + n = put_dec(out, n, @as(u64, cap)); + n = put_bytes(out, n, ": "); + if (reason == CR_MEM) { + n = put_dec(out, n, @as(u64, (mem_mb + 512) / 1024)); + return put_bytes(out, n, " GB, 1.5 GB floor"); + } + if (reason == CR_CPU) { + n = put_dec(out, n, @as(u64, ncpu)); + return put_bytes(out, n, " cpu, one kept free"); + } + if (reason == CR_HARD) { return put_bytes(out, n, "hard max"); } + if (reason == CR_BATTERY) { return put_bytes(out, n, "on battery, halved"); } + if (reason == CR_UNREAD) { return put_bytes(out, n, "free memory unread, one lane"); } + return put_bytes(out, n, "small machine, one lane"); +} + +pub fn put_hold(out: []u8, at: usize, hold: u32) -> usize { + if (hold == H_MEMORY) { return put_bytes(out, at, "hold: free memory near the floor"); } + if (hold == H_LOAD) { return put_bytes(out, at, "hold: load above 80% of cpus"); } + return at; +} + +; --- 5. Layout (dash.t27's packer) ----------------------------------------------------------------------- + +pub const KV_GAUGE_H : f64 = 26.0; +pub const KV_GAUGE_MIN : f64 = 128.0; +pub const KV_STRIP_H : f64 = 22.0; +pub const KV_ROW_H : f64 = 22.0; +pub const KV_INDENT : f64 = 14.0; + +fn kfl(x: u32) -> f64 { return @as(f64, @floatFromInt(x)); } + +pub fn kv_cols(w: f64) -> u32 { + return min2(pack_cols(w, KV_GAUGE_MIN, GAP), GAUGE_COUNT); +} + +pub fn kv_gauge_x(g: u32, w: f64) -> f64 { return dash_cell_x(g, w, kv_cols(w), GAP); } +pub fn kv_gauge_y(g: u32, w: f64) -> f64 { return dash_cell_y(g, kv_cols(w), KV_GAUGE_H, GAP); } +pub fn kv_gauge_w(w: f64) -> f64 { return cell_w(w, kv_cols(w), GAP); } +pub fn kv_gauges_h(w: f64) -> f64 { return pack_h(GAUGE_COUNT, kv_cols(w), KV_GAUGE_H, GAP); } +pub fn kv_strip_y(w: f64) -> f64 { return kv_gauges_h(w) + GAP; } +pub fn kv_tree_y(w: f64) -> f64 { return kv_strip_y(w) + KV_STRIP_H + GAP; } +pub fn kv_row_x(i: u32, lanes: u32) -> f64 { return kfl(node_depth(i, lanes)) * KV_INDENT; } +pub fn kv_row_y(i: u32, w: f64) -> f64 { return kv_tree_y(w) + kfl(i) * (KV_ROW_H + GAP); } +pub fn kv_row_w(i: u32, lanes: u32, w: f64) -> f64 { return w - kv_row_x(i, lanes); } + +/// The page height: the tree is one row per node, in node order (parents before children). +pub fn kv_height(lanes: u32, w: f64) -> f64 { + return kv_tree_y(w) + pack_h(node_count(lanes), 1, KV_ROW_H, GAP); +} + +/// What is under (x, y): a gauge g (0..3), a node N_LANE... as 100 + i, 99 for the strip, 255 for nothing. +pub fn kv_at(lanes: u32, w: f64, x: f64, y: f64) -> u32 { + if (x < 0.0 or x >= w or y < 0.0) { return 255; } + if (y < kv_gauges_h(w)) { + const g : u32 = pack_at(GAUGE_COUNT, w, kv_cols(w), KV_GAUGE_H, GAP, x, y); + if (g < GAUGE_COUNT) { return g; } + return 255; + } + if (y >= kv_strip_y(w) and y < kv_strip_y(w) + KV_STRIP_H) { return 99; } + if (y < kv_tree_y(w)) { return 255; } + const n : u32 = node_count(lanes); + const r : u32 = pack_at(n, w, 1, KV_ROW_H, GAP, x, y - kv_tree_y(w)); + if (r < n and x >= kv_row_x(r, lanes)) { return 100 + r; } + return 255; +} + +; --- 6. Tests ---------------------------------------------------------------------------------------------- + +test the_tree_has_the_shape_root_queen_two_supervisors_five_services_and_one_node_per_lane { + assert node_count(0) == 9 and node_count(3) == 12; + assert node_kind(N_ROOT, 3) == KN_ROOT and node_kind(N_QUEEN, 3) == KN_QUEEN; + assert node_kind(N_LANES, 3) == KN_SUP and node_kind(N_SERVICES, 3) == KN_SUP; + assert node_kind(SERVICE_COUNT + 3, 3) == KN_SERVICE and node_kind(8, 3) == KN_SERVICE; + assert node_kind(9, 3) == KN_LANE and node_kind(11, 3) == KN_LANE and node_kind(12, 3) == KN_NONE; + assert node_parent(N_ROOT, 3) == N_NONE and node_parent(N_QUEEN, 3) == N_ROOT; + assert node_parent(N_LANES, 3) == N_QUEEN and node_parent(N_SERVICES, 3) == N_QUEEN; + assert node_parent(8, 3) == N_SERVICES and node_parent(10, 3) == N_LANES; + assert node_depth(N_ROOT, 3) == 0 and node_depth(N_QUEEN, 3) == 1; + assert node_depth(N_LANES, 3) == 2 and node_depth(9, 3) == 3 and node_depth(8, 3) == 3; + assert node_restart(9, 3) == RESTART_TRANSIENT and node_restart(N_QUEEN, 3) == RESTART_PERMANENT; + assert node_restart(4, 3) == RESTART_PERMANENT and node_restart(N_LANES, 3) == RESTART_PERMANENT; + assert node_strategy(N_ROOT, 3) == STRAT_ONE_FOR_ONE and node_strategy(N_LANES, 3) == STRAT_ONE_FOR_ONE; + assert node_strategy(9, 3) == STRAT_NONE and node_strategy(4, 3) == STRAT_NONE; + assert node_max_r(N_ROOT, 3) == ROOT_MAX_RESTARTS and node_max_t(N_ROOT, 3) == ROOT_PERIOD_SECONDS; + assert node_max_r(N_LANES, 3) == 3 and node_max_t(N_LANES, 3) == 300; + assert node_max_r(N_SERVICES, 3) == 5 and node_max_t(N_SERVICES, 3) == 300; + assert node_max_r(9, 3) == 0; +} + +test a_lane_that_crashes_more_than_maxr_times_escalates_and_the_queen_records_a_request { + assert lane_decision(X_CRASH, 0) == KD_RESTART; + assert lane_decision(X_CRASH, 2) == KD_RESTART; + assert lane_decision(X_CRASH, 3) == KD_OWNER_REQUEST; + assert lane_decision(X_NORMAL, 9) == KD_NOTHING; + assert lane_decision(X_SHUTDOWN, 9) == KD_NOTHING; + assert service_decision(X_NORMAL, 0) == KD_RESTART; + assert service_decision(X_CRASH, 4) == KD_RESTART; + assert service_decision(X_CRASH, 5) == KD_OWNER_REQUEST; + assert lane_restart_wait(1) == 10 and lane_restart_wait(2) == 20 and lane_restart_wait(9) == 300; + assert request_default_due(3599) == false and request_default_due(3600); +} + +test the_cap_on_the_eight_gb_mac_is_three_whatever_its_cpu_count_from_four_up { + // 8 GB: (8192 - 3072 - 450 - 1536) / 900 = 3 lanes by memory + assert mem_lanes(8192) == 3; + assert agent_cap(8589934592, 3221225472, 4, 100, false) == 3; + assert agent_cap(8589934592, 3221225472, 6, 100, false) == 3; + assert agent_cap(8589934592, 3221225472, 8, 300, false) == 3; + assert agent_cap(8589934592, 3221225472, 10, 100, false) == 3; + assert agent_cap(8589934592, 3221225472, 8, 100, false) == 3; + assert cap_reason(8589934592, 3221225472, 8, false) == CR_MEM; +} + +test bigger_machines_get_larger_caps_and_the_cpu_and_hard_max_bind_in_turn { + assert mem_lanes(16384) == 12 and mem_lanes(32768) == 30; + assert agent_cap(17179869184, 6442450944, 10, 100, false) == 9; + assert cap_reason(17179869184, 6442450944, 10, false) == CR_CPU; + assert agent_cap(17179869184, 6442450944, 32, 100, false) == 12; + assert cap_reason(17179869184, 6442450944, 32, false) == CR_MEM; + assert agent_cap(34359738368, 12884901888, 12, 100, false) == 11; + assert agent_cap(34359738368, 12884901888, 64, 100, false) == 16; + assert cap_reason(34359738368, 12884901888, 64, false) == CR_HARD; + assert agent_cap(8589934592, 3221225472, 2, 100, false) == 1; + assert agent_cap(4294967296, 1073741824, 8, 100, false) == 1; + assert cap_reason(4294967296, 1073741824, 8, false) == CR_MIN; +} + +test battery_halves_unread_memory_means_one_lane_and_load_holds_new_spawns_without_changing_the_cap { + assert agent_cap(17179869184, 6442450944, 10, 100, true) == 4; + assert agent_cap(8589934592, 3221225472, 8, 100, true) == 1; + assert cap_reason(17179869184, 6442450944, 10, true) == CR_BATTERY; + assert agent_cap(17179869184, 0, 10, 100, false) == 1; + assert cap_reason(17179869184, 0, 10, false) == CR_UNREAD; + // the cap ignores load; the hold reads it: 8 cpus hold above 640 + assert agent_cap(8589934592, 3221225472, 8, 900, false) == 3; + assert spawn_hold(3072, 8, 640) == H_NONE; + assert spawn_hold(3072, 8, 641) == H_LOAD; + assert spawn_hold(2048, 8, 0) == H_NONE; + assert spawn_hold(2047, 8, 0) == H_MEMORY and spawn_hold(2000, 8, 0) == H_MEMORY; + assert may_spawn(3, 2, H_NONE) and may_spawn(3, 3, H_NONE) == false; + assert may_spawn(3, 0, H_LOAD) == false and may_spawn(3, 0, H_MEMORY) == false; +} + +test the_god_mode_gate_takes_the_machines_cap_not_the_fixed_one { + // on the 8 GB Mac the cap is evolve's fixed MAX_AGENTS (why_not); on 16 GB a fourth lane may start + assert lane_gate(true, true, 3, 0, 8589934592, 3221225472, 8, 100, false) == why_not(true, true, 3, 0); + assert lane_gate(true, true, 3, 0, 8589934592, 3221225472, 8, 100, false) != why_not(true, true, 0, 0); + assert lane_gate(true, true, 3, 0, 17179869184, 6442450944, 10, 100, false) == why_not(true, true, 0, 0); + assert lane_gate(true, true, 9, 0, 17179869184, 6442450944, 10, 100, false) != why_not(true, true, 0, 0); +} + +test shedding_stops_the_newest_lane_first_never_more_than_needed_and_resumes_with_a_margin { + assert lanes_to_shed(1536, 3) == 0 and lanes_to_shed(4000, 3) == 0; + assert lanes_to_shed(1535, 3) == 1; + assert lanes_to_shed(636, 3) == 1; + assert lanes_to_shed(635, 3) == 2; + assert lanes_to_shed(0, 3) == 2 and lanes_to_shed(0, 1) == 1 and lanes_to_shed(0, 0) == 0; + // lanes 0 (oldest), 1, 2 (newest): the newest goes first, then the next newest + assert shed_lane(3, 0) == 2 and shed_lane(3, 1) == 1 and shed_lane(3, 2) == 0; + assert shed_lane(3, 3) == 3; + assert shed_restarts() == false; + assert lane_decision(SHED_REASON, 0) == KD_NOTHING; + // the shed lane is a lane node; the Queen and the services are not in the lane count + assert node_kind(N_LANE0 + shed_lane(3, 0), 3) == KN_LANE; + assert resume_ok(2047, 1, 2, 3, H_NONE) == false; + assert resume_ok(2048, 1, 2, 3, H_NONE); + assert resume_ok(4096, 0, 2, 3, H_NONE) == false; + assert resume_ok(4096, 2, 3, 3, H_NONE) == false; + assert resume_ok(4096, 2, 2, 3, H_LOAD) == false; +} + +test mailboxes_are_bounded_per_kind_and_stop_and_escalation_are_received_before_any_task { + assert mbox_cap(KN_LANE) == 16 and mbox_cap(KN_SERVICE) == 64 and mbox_cap(KN_QUEEN) == MAILBOX_CAP; + assert kernel_deliver(KN_LANE, true, 15) == D_QUEUED; + assert kernel_deliver(KN_LANE, true, 16) == D_DROPPED_FULL; + assert kernel_deliver(KN_SERVICE, true, 63) == D_QUEUED and kernel_deliver(KN_SERVICE, true, 64) == D_DROPPED_FULL; + assert kernel_deliver(KN_QUEEN, true, 255) == D_QUEUED and kernel_deliver(KN_QUEEN, true, 256) == D_DROPPED_FULL; + assert kernel_lane(K_STOP) == LANE_CONTROL and kernel_lane(K_ESCALATE) == LANE_CONTROL; + assert kernel_lane(K_TASK) == LANE_DATA; + // a control message beats a pending offer; between two control messages the lower tag wins + assert next_lane(true, true) == LANE_CONTROL and next_lane(false, true) == LANE_DATA; + const ctl : u64 = ctl_post(ctl_post(0, K_ESCALATE), K_STOP); + assert ctl_next(ctl) == K_STOP; + assert ctl_next(ctl_take(ctl)) == K_ESCALATE; + assert ctl_pending(ctl_take(ctl_take(ctl)), K_ESCALATE) == false; +} + +test the_view_row_packs_state_restarts_mailbox_and_rss_and_the_gauges_read_the_machine { + const v : u64 = nv_pack(NS_RESTARTING, 2, 15, 900); + assert nv_state(v) == NS_RESTARTING and nv_restarts(v) == 2 and nv_mbox(v) == 15 and nv_rss(v) == 900; + assert node_rss_mb(9, 3) == 900 and node_rss_mb(N_QUEEN, 3) == 150 and node_rss_mb(4, 3) == 60; + assert node_rss_mb(N_ROOT, 3) == 0; + assert kernel_mb() == 450 and tree_rss_mb(3) == 450 + 2700; + assert gauge_fill(GA_USED, 8192, 3072, 8, 400, 3, 3) == 62; + assert gauge_fill(GA_FREE, 8192, 3072, 8, 400, 3, 3) == 37; + assert gauge_fill(GA_LOAD, 8192, 3072, 8, 400, 3, 3) == 50; + assert gauge_fill(GA_LOAD, 8192, 3072, 8, 4000, 3, 3) == 100; + assert gauge_fill(GA_CAP, 8192, 3072, 8, 400, 1, 3) == 33; + assert gauge_fill(GA_CAP, 0, 0, 0, 0, 0, 0) == 0; + var out : [64]u8 = undefined; + const e : usize = put_reason(out[0..], 0, 3, CR_MEM, 8192, 8); + assert e == 25; + assert out[0] == 99 and out[4] == 51 and out[7] == 56 and out[8] == 32 and out[9] == 71; + assert put_reason(out[0..], 0, 1, CR_BATTERY, 8192, 8) == 25; + assert put_hold(out[0..], 5, H_NONE) == 5 and put_hold(out[0..], 0, H_LOAD) > 0; +} + +test the_view_layout_uses_the_dash_packer_one_gauge_row_a_strip_then_one_indented_row_per_node { + // wide: the four gauges share one row; narrow: two per row + assert kv_cols(800.0) == 4; + assert kv_cols(200.0) == 1; + assert kv_gauges_h(800.0) == KV_GAUGE_H; + assert kv_gauges_h(270.0) == 2.0 * KV_GAUGE_H + GAP; + assert kv_gauge_x(0, 800.0) == 0.0 and kv_gauge_y(3, 800.0) == 0.0; + assert kv_gauge_x(1, 800.0) > kv_gauge_w(800.0); + assert kv_strip_y(800.0) == KV_GAUGE_H + GAP; + assert kv_tree_y(800.0) == KV_GAUGE_H + GAP + KV_STRIP_H + GAP; + assert kv_row_x(N_ROOT, 3) == 0.0 and kv_row_x(N_QUEEN, 3) == 14.0 and kv_row_x(9, 3) == 42.0; + assert kv_row_y(1, 800.0) == kv_tree_y(800.0) + KV_ROW_H + GAP; + assert kv_row_w(9, 3, 800.0) == 758.0; + assert kv_height(3, 800.0) == kv_tree_y(800.0) + 12.0 * KV_ROW_H + 11.0 * GAP; + // hit tests: a gauge, the strip, a node row, the gap between rows, outside + assert kv_at(3, 800.0, 10.0, 5.0) == 0; + assert kv_at(3, 800.0, 790.0, 5.0) == 3; + assert kv_at(3, 800.0, 10.0, kv_strip_y(800.0) + 3.0) == 99; + assert kv_at(3, 800.0, 100.0, kv_tree_y(800.0) + 3.0) == 100 + N_ROOT; + assert kv_at(3, 800.0, 100.0, kv_row_y(10, 800.0) + 3.0) == 110; + assert kv_at(3, 800.0, 5.0, kv_row_y(10, 800.0) + 3.0) == 255; + assert kv_at(3, 800.0, 100.0, kv_row_y(2, 800.0) + KV_ROW_H + 2.0) == 255; + assert kv_at(3, 800.0, 900.0, 5.0) == 255; +} diff --git a/specs/t27os/keys.t27 b/specs/t27os/keys.t27 index 7ff15815bd..cc9100c3ab 100644 --- a/specs/t27os/keys.t27 +++ b/specs/t27os/keys.t27 @@ -701,6 +701,15 @@ pub const TIER_VARS : str = "ANTHROPIC_MODEL,ANTHROPIC_SMALL_FAST_MODEL,ANTHROPI /// list's, not a measurement. Measure each id with one 1-token call the day a key is stored and reorder this /// table as the z.ai one was. Every tier of the profile names the default free id (the tier columns below are /// empty for it); the paid `~anthropic/...` names OpenRouter also serves are not used by the free tier. +/// MEASURED 2026-10-20T09:50Z on the bench Mac by the keys-lane session, with the owner's OpenRouter key (Keychain +/// account openrouter; no key text anywhere): GET /api/v1/auth/key HTTP 200, is_free_tier false, key limit 50, +/// usage 0, through the profile's own endpoint (base https://openrouter.ai/api, POST /v1/messages, Anthropic format): +/// minimax/minimax-m3.1-flash:free HTTP 200, text 'PONG' at max_tokens 16 -> first, the default +/// nvidia/nemotron-3.5-super-vl:free HTTP 200, 'PONG' but it thinks first (content types thinking,text, 31 +/// output tokens): max_tokens 16 gives EMPTY text -> second, floor +/// THINKING_MIN_TOKENS (model_min_tokens) +/// nvidia/nemotron-3.5-lightning:free HTTP 200 twice with no parsable message body -> NOT verified, dropped +/// from MODELS_THIRD and never a default. /// Fields: zai, anthropic-api, openrouter, ollama-cloud. Ollama's page names one cloud model, used for every tier. pub const MODELS_DEFAULT : str = "glm-4.5-flash,,minimax/minimax-m3.1-flash:free,glm-5.3-flash"; /// The second model of each profile's tier (empty = the profile has one model): what a pane moves to when @@ -708,13 +717,23 @@ pub const MODELS_DEFAULT : str = "glm-4.5-flash,,minimax/minimax-m3.1-flash:free pub const MODELS_SECOND : str = "glm-4.7-flash,,nvidia/nemotron-3.5-super-vl:free,"; /// The third model of each profile's tier (empty = the profile has fewer models). A profile that names a third /// model names a second one too (profile_models counts up to the first empty step). -pub const MODELS_THIRD : str = ",,nvidia/nemotron-3.5-lightning:free,"; +pub const MODELS_THIRD : str = ",,,"; /// How many models one profile's tier map can hold: step 0 is MODELS_DEFAULT, step 1 is MODELS_SECOND, step 2 /// is MODELS_THIRD. pub const MODEL_STEPS : u32 = 3; +/// A request that must return text needs at least this many output tokens on a model that thinks first +/// (nemotron-3.5-super-vl: 31 tokens of thinking, measured 2026-10-20); the other models answer at 16. +pub const THINKING_MIN_TOKENS : u32 = 64; +pub const PLAIN_MIN_TOKENS : u32 = 16; pub const MODELS_HAIKU : str = ",,,"; pub const MODELS_SONNET : str = ",,,"; pub const MODELS_OPUS : str = ",,,"; +/// The OpenAI-compatible base URL of each profile, for the agents that do not speak the Anthropic protocol +/// (OpenCode, Crush); empty = the profile has none for them. z.ai general endpoint /api/paas/v4 (the owner named +/// it); OpenRouter https://openrouter.ai/api/v1. anthropic-api needs none (both agents read ANTHROPIC_API_KEY +/// themselves) and Ollama cloud's OpenAI route is not documented on a page this spec cites, so Ollama stays out of +/// the chain of OpenCode and Crush (agent_profile_ok). +pub const OAI_BASE_URLS : str = "https://api.z.ai/api/paas/v4,,https://openrouter.ai/api/v1,"; /// The Anthropic-protocol base URL of each profile; empty = Claude Code's own (the direct API). pub const PROFILE_BASE_URLS : str = "https://api.z.ai/api/anthropic,,https://openrouter.ai/api,https://ollama.com"; /// The variable that carries the Keychain key: a bearer token for a gateway, the API key for Anthropic itself. @@ -737,9 +756,16 @@ pub const PS_NONE : u32 = 255; pub const CLAUDE_STRIPS : str = "ANTHROPIC_API_KEY,ANTHROPIC_AUTH_TOKEN,ANTHROPIC_BASE_URL,ANTHROPIC_MODEL,ANTHROPIC_SMALL_FAST_MODEL,ANTHROPIC_DEFAULT_HAIKU_MODEL,ANTHROPIC_DEFAULT_SONNET_MODEL,ANTHROPIC_DEFAULT_OPUS_MODEL,ANTHROPIC_CUSTOM_HEADERS"; pub const CLAUDE_STRIP_COUNT : u32 = 9; -/// Does agent a (cell order of agents.t27) get a profile? Only Claude Code (cell 0). +/// Agent cells (agents.t27 order) that take the profile chain: Claude Code (0, Anthropic protocol), OpenCode (3) +/// and Crush (5), both through the OpenAI-compatible endpoints below (gHashTag/t27#11275). Codex, Gemini, Cursor, +/// Aider and Copilot are not installed on this Mac; the owner decides installs, so they have no profile. +pub const AG_CLAUDE : u32 = 0; +pub const AG_OPENCODE : u32 = 3; +pub const AG_CRUSH : u32 = 5; + +/// Does agent a (cell order of agents.t27) get a profile? pub fn profile_agent(a: u32) -> bool { - return a == 0; + return a == AG_CLAUDE or a == AG_OPENCODE or a == AG_CRUSH; } pub fn profile_name(pr: u32, out: []u8) -> usize { @@ -964,6 +990,276 @@ pub fn pick_profile(stored: u32, bad: u32) -> u32 { return next_profile(PR_NONE, stored, bad); } +// ---- OpenCode and Crush take the same chain (gHashTag/t27#11275) ------------------------------------------- +// Read from the installed versions on 2026-10-20 (OpenCode 1.3.15, Crush 0.92.0); neither `--help` lists an +// environment variable, so the names below come from what the binaries contain and what their pages document: +// OpenCode `opencode [project]`, `-m, --model model to use in the format of provider/model`; the binary +// names OPENCODE_CONFIG_CONTENT (an inline JSON config), OPENCODE_CONFIG, OPENCODE_CONFIG_DIR, and the +// `{env:VAR_NAME}` reference a config value may use, and the package @ai-sdk/openai-compatible for a +// custom OpenAI-compatible provider. So the profile is one inline config that holds no key (the key +// is `{env:ZAI_API_KEY}`) plus the key variable. +// Crush `crush run -m, --model` exists, but the interactive `crush` has no model flag; the binary names +// CRUSH_GLOBAL_CONFIG (a directory holding crush.json), CRUSH_GLOBAL_DATA, and its built-in providers +// read $ZAI_API_KEY, $OPENROUTER_API_KEY, $ANTHROPIC_API_KEY, $OPENAI_API_KEY. A model needs a config +// file: the host writes crush_config_text to /.t27os/crush/-/crush.json (no +// key in it, `$ZAI_API_KEY` is expanded by Crush) and sets CRUSH_GLOBAL_CONFIG to that directory. +// What each prints on 401 or 429 neither page says and no key was spent to find out, so the failure +// classifier is the generic one (classify_line reads any line with "rror" and a status code, 1113, 1305). +// The two agents take the zai, anthropic-api and openrouter profiles; the Ollama cloud profile has no +// OpenAI-compatible URL on a page cited above, so it is out of their chain (agent_profile_ok). + +/// May agent a run profile pr? Claude Code any; OpenCode and Crush the zai, anthropic-api and openrouter ones. +pub fn agent_profile_ok(a: u32, pr: u32) -> bool { + if (pr >= PROFILE_COUNT) { return false; } + if (a == AG_CLAUDE) { return true; } + if (a == AG_OPENCODE or a == AG_CRUSH) { return pr == PR_ZAI or pr == PR_ANTHROPIC or pr == PR_OPENROUTER; } + return false; +} + +/// The stored-provider mask narrowed to the providers of the profiles agent a may run: what the chain walk of +/// a pane of agent a sees as stored. +pub fn agent_stored(a: u32, stored: u32) -> u32 { + var m : u32 = 0; + var pr : u32 = 0; + while (pr < PROFILE_COUNT) { + const bit : u32 = PROFILE_PROVIDER[pr]; + if (agent_profile_ok(a, pr) and (stored >> bit) % 2 == 1 and (m >> bit) % 2 == 0) { m = m + (1 << bit); } + pr = pr + 1; + } + return m; +} + +/// The model's output-token floor for a request that must return text: the thinking models need +/// THINKING_MIN_TOKENS (OpenRouter step 1, nemotron-3.5-super-vl), the rest PLAIN_MIN_TOKENS. +pub fn model_min_tokens(pr: u32, step: u32) -> u32 { + if (pr == PR_OPENROUTER and step == 1) { return THINKING_MIN_TOKENS; } + return PLAIN_MIN_TOKENS; +} + +/// The OpenAI-compatible base URL of profile pr for OpenCode and Crush (0 = none). +pub fn oai_base_url(pr: u32, out: []u8) -> usize { + if (pr >= PROFILE_COUNT) { return 0; } + return csv_field(OAI_BASE_URLS, pr, out); +} + +/// OpenCode's inline config (OPENCODE_CONFIG_CONTENT) for profile pr at model step `step`: one OpenAI-compatible +/// provider named t27 whose key is read from the provider's variable, and the model. 0 for a profile with no +/// OpenAI-compatible URL (anthropic-api needs no config). 600 bytes are enough. Holds no key. +pub fn opencode_config(pr: u32, step: u32, out: []u8) -> usize { + var url : [64]u8 = undefined; + var m : [64]u8 = undefined; + var ev : [40]u8 = undefined; + const ul : usize = oai_base_url(pr, url[0..64]); + const ml : usize = tier_model_step(pr, TI_DEFAULT, step, m[0..64]); + if (ul == 0 or ml == 0) { return 0; } + const el : usize = provider_env(PROFILE_PROVIDER[pr], ev[0..40]); + var n : usize = put_bytes(out, 0, "{\"$schema\":\"https://opencode.ai/config.json\",\"provider\":{\"t27\":{\"npm\":\"@ai-sdk/openai-compatible\",\"name\":\"T27\",\"options\":{\"baseURL\":\""); + n = put_bytes(out, n, url[0..ul]); + n = put_bytes(out, n, "\",\"apiKey\":\"{env:"); + n = put_bytes(out, n, ev[0..el]); + n = put_bytes(out, n, "}\"},\"models\":{\""); + n = put_bytes(out, n, m[0..ml]); + n = put_bytes(out, n, "\":{\"name\":\""); + n = put_bytes(out, n, m[0..ml]); + n = put_bytes(out, n, "\"}}}},\"model\":\"t27/"); + n = put_bytes(out, n, m[0..ml]); + return put_bytes(out, n, "\"}"); +} + +/// Crush's crush.json for profile pr at model step `step`: the same provider, large and small model the one id. +/// 0 for a profile with no OpenAI-compatible URL. 800 bytes are enough. Holds no key (`$ZAI_API_KEY` is Crush's +/// own variable reference). +pub fn crush_config_text(pr: u32, step: u32, out: []u8) -> usize { + var url : [64]u8 = undefined; + var m : [64]u8 = undefined; + var ev : [40]u8 = undefined; + const ul : usize = oai_base_url(pr, url[0..64]); + const ml : usize = tier_model_step(pr, TI_DEFAULT, step, m[0..64]); + if (ul == 0 or ml == 0) { return 0; } + const el : usize = provider_env(PROFILE_PROVIDER[pr], ev[0..40]); + var n : usize = put_bytes(out, 0, "{\"$schema\":\"https://charm.land/crush.json\",\"providers\":{\"t27\":{\"name\":\"T27\",\"type\":\"openai-compat\",\"base_url\":\""); + n = put_bytes(out, n, url[0..ul]); + n = put_bytes(out, n, "\",\"api_key\":\"$"); + n = put_bytes(out, n, ev[0..el]); + n = put_bytes(out, n, "\",\"models\":[{\"id\":\""); + n = put_bytes(out, n, m[0..ml]); + n = put_bytes(out, n, "\",\"name\":\""); + n = put_bytes(out, n, m[0..ml]); + n = put_bytes(out, n, "\",\"context_window\":128000,\"default_max_tokens\":8192}]}},\"models\":{\"large\":{\"provider\":\"t27\",\"model\":\""); + n = put_bytes(out, n, m[0..ml]); + n = put_bytes(out, n, "\"},\"small\":{\"provider\":\"t27\",\"model\":\""); + n = put_bytes(out, n, m[0..ml]); + return put_bytes(out, n, "\"}},\"options\":{\"disable_provider_auto_updates\":true}}"); +} + +/// The directory the host writes crush_config_text into for profile pr at `step`: +/// "/.t27os/crush/-". 0 when pr is no profile. +pub fn crush_config_dir(home: []const u8, pr: u32, step: u32, out: []u8) -> usize { + if (pr >= PROFILE_COUNT or step >= MODEL_STEPS) { return 0; } + var nm : [32]u8 = undefined; + const nl : usize = profile_name(pr, nm[0..32]); + var n : usize = put_bytes(out, 0, home); + n = put_bytes(out, n, "/.t27os/crush/"); + n = put_bytes(out, n, nm[0..nl]); + n = put_bytes(out, n, "-"); + return put_dec(out, n, @as(u64, step)); +} + +/// Does agent a's pane need a config written before it starts (Crush with an OpenAI-compatible profile)? +pub fn agent_config_file(a: u32, pr: u32) -> bool { + var t : [64]u8 = undefined; + return a == AG_CRUSH and agent_profile_ok(a, pr) and oai_base_url(pr, t[0..64]) > 0; +} + +/// The number of variables a pane of agent a gets for profile pr: Claude Code the Anthropic-protocol set; OpenCode +/// and Crush the provider's key variable plus, when the profile has an OpenAI-compatible URL, one config variable +/// (OPENCODE_CONFIG_CONTENT, CRUSH_GLOBAL_CONFIG). 0 for a profile the agent may not run. +pub fn agent_var_count(a: u32, pr: u32) -> u32 { + if (a == AG_CLAUDE) { return profile_var_count(pr); } + if (agent_profile_ok(a, pr) == false) { return 0; } + var tmp : [64]u8 = undefined; + if (oai_base_url(pr, tmp[0..64]) > 0) { return 2; } + return 1; +} + +/// The name of variable k (40 bytes are enough). +pub fn agent_var_name(a: u32, pr: u32, k: u32, out: []u8) -> usize { + if (a == AG_CLAUDE) { return profile_var_name(pr, k, out); } + if (k >= agent_var_count(a, pr)) { return 0; } + if (k == 0) { return provider_env(PROFILE_PROVIDER[pr], out); } + if (a == AG_OPENCODE) { return put_bytes(out, 0, "OPENCODE_CONFIG_CONTENT"); } + return put_bytes(out, 0, "CRUSH_GLOBAL_CONFIG"); +} + +/// True when variable k carries the Keychain key. +pub fn agent_var_is_key(a: u32, pr: u32, k: u32) -> bool { + if (a == AG_CLAUDE) { return profile_var_is_key(pr, k); } + return k == 0 and k < agent_var_count(a, pr); +} + +/// The constant value of variable k at model step `step` (the key variable has none). `home` is the user's home +/// directory, for Crush's config directory. 700 bytes are enough. +pub fn agent_var_text_step(a: u32, pr: u32, k: u32, step: u32, home: []const u8, out: []u8) -> usize { + if (a == AG_CLAUDE) { return profile_var_text_step(pr, k, step, out); } + if (k != 1 or k >= agent_var_count(a, pr)) { return 0; } + if (a == AG_OPENCODE) { return opencode_config(pr, step, out); } + return crush_config_dir(home, pr, step, out); +} + +/// What OpenCode and Crush must not inherit from the shell that started the server: every key, endpoint and +/// config variable of the providers they are given. +pub const AGENT_STRIPS : str = "ZAI_API_KEY,OPENROUTER_API_KEY,ANTHROPIC_API_KEY,OPENAI_API_KEY,OPENCODE_CONFIG_CONTENT,OPENCODE_CONFIG,CRUSH_GLOBAL_CONFIG,OPENAI_BASE_URL,ANTHROPIC_BASE_URL"; +pub const AGENT_STRIP_COUNT : u32 = 9; + +/// Is `name` a variable a pane of agent a starts without? Claude Code: claude_strips. +pub fn agent_strips(a: u32, name: []const u8) -> bool { + if (a == AG_CLAUDE) { return claude_strips(name); } + if (a != AG_OPENCODE and a != AG_CRUSH) { return false; } + var tmp : [40]u8 = undefined; + var k : u32 = 0; + while (k < AGENT_STRIP_COUNT) { + const n : usize = csv_field(AGENT_STRIPS, k, tmp[0..40]); + if (same_bytes(tmp[0..n], name)) { return true; } + k = k + 1; + } + return false; +} + +// ---- an agent that is not installed (gHashTag/t27#11275) --------------------------------------------------- +// The host supplies a bit mask of the agent cells whose binary it found (agents.t27 AGENT_BINS on PATH); the +// window shows "not installed" for a missing one, never "stopped". Nothing is installed by T27 OS. +pub const AS_NOT_INSTALLED : u32 = 0; +pub const AS_STOPPED : u32 = 1; +pub const AS_RUNNING : u32 = 2; +pub const AS_NO_KEY : u32 = 3; + +pub fn agent_installed(mask: u32, a: u32) -> bool { + return a < AGENT_COUNT and (mask >> a) % 2 == 1; +} + +/// What the window says about agent a's pane: not installed (mask), running, no key (a profile agent whose +/// chain holds no stored key), else stopped. +pub fn agent_state(mask: u32, a: u32, live: bool, stored: u32) -> u32 { + if (agent_installed(mask, a) == false) { return AS_NOT_INSTALLED; } + if (live) { return AS_RUNNING; } + if (profile_agent(a) and pick_profile(agent_stored(a, stored), 0) == PR_NONE) { return AS_NO_KEY; } + return AS_STOPPED; +} + +pub const AS_WORDS : str = "not installed,stopped,running,no key stored"; + +// ---- the traffic light of an agent pane (gHashTag/t27#11314) ------------------------------------------------- +// Owner, 2026-10-20: one light per agent pane. GREEN = the pane runs a paid profile (CT_PAID), YELLOW = it runs +// a free tier (CT_ZAI_FREE or CT_OR_FREE), RED = no balance: the chain gave up, or every usable key rests +// (1113 empty account, 401/403 rejected). An editor pane (and every cell without a profile) has no light. The +// codes are ordered so that the worst light is the largest one: RED 3 > YELLOW 2 > GREEN 1 > NONE 0. +pub const L_NONE : u32 = 0; +pub const L_GREEN : u32 = 1; +pub const L_YELLOW : u32 = 2; +pub const L_RED : u32 = 3; +pub const L_NAMES : str = "none,green,yellow,red"; +/// The tier argument of light_of for a pane with no profile (an editor, a shell, a pane with no key yet). +pub const CT_NO_TIER : u32 = 255; + +/// A key whose rest ends within this many seconds still counts as usable: a throttled key (30 s) or an +/// overloaded one (60 s) is busy, not out of balance; an empty (600 s) or rejected (3600 s) one is out. +pub const LIGHT_SOON_S : u64 = 60; + +/// The light of a pane: `tier` the chain tier of its profile (CT_*, CT_NO_TIER for none), `gave_up` the chain +/// is over (FA_GIVE_UP), `usable` how many keys of its provider are free or rest less than LIGHT_SOON_S. +pub fn light_of(tier: u32, gave_up: bool, usable: u32) -> u32 { + if (tier == CT_NO_TIER) { return L_NONE; } + if (tier >= CT_COUNT) { return L_NONE; } + if (gave_up or usable == 0) { return L_RED; } + if (tier == CT_PAID) { return L_GREEN; } + return L_YELLOW; +} + +/// The worst light of the panes in `live` (bit i set = pane i runs) given each pane's light: RED over YELLOW +/// over GREEN; NONE when none runs or none has a light. For the sidebar foot. +pub fn worst_light(lights: []const u32, live: u32) -> u32 { + var w : u32 = L_NONE; + var i : usize = 0; + while (i < lights.len and i < 32) { + if ((live >> @as(u32, @intCast(i))) % 2 == 1 and lights[i] > w) { w = lights[i]; } + i = i + 1; + } + return w; +} + +pub fn light_name(l: u32, out: []u8) -> usize { + return csv_field(L_NAMES, l, out); +} + +/// The tooltip of a light, 96 bytes are enough: "free: ", "paid: ", "no balance: all keys rest"; +/// nothing for NONE. +pub fn light_tip(l: u32, model: []const u8, out: []u8) -> usize { + if (l == L_GREEN) { + const n : usize = put_bytes(out, 0, "paid: "); + return put_bytes(out, n, model); + } + if (l == L_YELLOW) { + const n : usize = put_bytes(out, 0, "free: "); + return put_bytes(out, n, model); + } + if (l == L_RED) { return put_bytes(out, 0, "no balance: all keys rest"); } + return 0; +} + +/// The LISTING `shown` byte with the light in its bits 6 and 7 (the state is below 64); the light of an old +/// server is 0 = none. +pub fn shown_with_light(shown: u32, l: u32) -> u32 { + return shown % 64 + 64 * (l % 4); +} + +pub fn shown_state_of(b: u32) -> u32 { + return b % 64; +} + +pub fn shown_light_of(b: u32) -> u32 { + return b / 64 % 4; +} + /// How many keys one provider may hold, numbered from 1 (Free Claude Code: "key 1 first"). Eight, since the /// owner stores seven z.ai keys and rotates over all of them (gHashTag/t27#11241). pub const MAX_KEYS : u32 = 8; @@ -1423,14 +1719,14 @@ test the_default_z_ai_model_is_the_free_flash_that_answered_on_every_key { test a_profile_knows_how_many_models_its_tier_holds { assert profile_models(PR_ZAI) == 2; assert profile_models(PR_ANTHROPIC) == 1; - assert profile_models(PR_OPENROUTER) == 3; + assert profile_models(PR_OPENROUTER) == 2; assert profile_models(PR_OLLAMA_CLOUD) == 1; assert profile_models(PR_NONE) == 0; assert model_left(PR_ZAI, 0); assert model_left(PR_ZAI, 1) == false; assert model_left(PR_ANTHROPIC, 0) == false; assert model_left(PR_OPENROUTER, 0); - assert model_left(PR_OPENROUTER, 1); + assert model_left(PR_OPENROUTER, 1) == false; assert model_left(PR_OPENROUTER, 2) == false; assert model_left(PR_NONE, 0) == false; assert MODEL_STEPS == 3; @@ -1486,8 +1782,9 @@ test a_restored_or_failed_over_pane_runs_the_model_its_profile_serves_never_the_ assert n == 31 and begins_with(o[0..n], "minimax/minimax-m3.1-flash:free"); n = model_for_profile(PR_OPENROUTER, 1, "glm-4.7-flash", o[0..64]); assert n == 33 and begins_with(o[0..n], "nvidia/nemotron-3.5-super-vl:free"); + // the unverified lightning id is no third step any more n = model_for_profile(PR_OPENROUTER, 2, "", o[0..64]); - assert n == 34 and begins_with(o[0..n], "nvidia/nemotron-3.5-lightning:free"); + assert n == 0; // a free id of OpenRouter's has no place on the Anthropic API a pane fails over to after it n = model_for_profile(PR_ANTHROPIC, 0, "minimax/minimax-m3.1-flash:free", o[0..64]); assert n == 0; @@ -1549,8 +1846,7 @@ test the_openrouter_profile_reaches_claude_code_by_the_same_variables_and_names_ assert n == 31 and begins_with(o[0..n], "minimax/minimax-m3.1-flash:free"); n = profile_var_text_step(PR_OPENROUTER, k, 1, o[0..64]); assert n == 33 and begins_with(o[0..n], "nvidia/nemotron-3.5-super-vl:free"); - n = profile_var_text_step(PR_OPENROUTER, k, 2, o[0..64]); - assert n == 34 and begins_with(o[0..n], "nvidia/nemotron-3.5-lightning:free"); + assert profile_var_text_step(PR_OPENROUTER, k, 2, o[0..64]) == 0; assert profile_var_text_step(PR_OPENROUTER, k, MODEL_STEPS, o[0..64]) == 0; k = k + 1; } @@ -1564,8 +1860,10 @@ test every_openrouter_model_id_is_a_free_one_and_no_other_profile_names_one { var ti : u32 = 0; while (ti < TIER_COUNT) { const n : usize = tier_model_step(PR_OPENROUTER, ti, st, o[0..64]); - assert n > 6; - assert o[n - 5] == 58 and o[n - 4] == 102 and o[n - 3] == 114 and o[n - 2] == 101 and o[n - 1] == 101; + if (n > 0) { + assert n > 6; + assert o[n - 5] == 58 and o[n - 4] == 102 and o[n - 3] == 114 and o[n - 2] == 101 and o[n - 1] == 101; + } ti = ti + 1; } st = st + 1; @@ -1978,7 +2276,8 @@ test each_agent_cli_gets_the_variable_of_every_provider_it_reads_and_no_other { assert agent_uses(8, P_ANTHROPIC) == false; assert agent_uses(6, P_GITHUB) == false; // Claude Code is the one cell with profile variables, and its z.ai profile reads the z.ai provider's keys - assert profile_agent(0) and profile_agent(5) == false; + assert profile_agent(0) and profile_agent(3) and profile_agent(5); + assert profile_agent(1) == false and profile_agent(2) == false and profile_agent(4) == false and profile_agent(6) == false; assert PROFILE_PROVIDER[PR_ZAI] == P_ZAI; // every provider a cell reads has a variable name var a : u32 = 0; @@ -2157,10 +2456,9 @@ test a_pane_with_no_key_left_on_its_model_takes_the_next_model_of_the_tier_befor assert fo_next_model(FC_NONE, PR_ZAI, 0, 3, false) == false; assert fo_next_model(FC_OTHER, PR_ZAI, 0, 3, false) == false; assert fo_next_model(FC_NO_BALANCE, PR_ZAI, 0, MAX_FAILOVERS, false) == false; - // OpenRouter's free tier holds three models: the next one at steps 0 and 1, none after the last + // OpenRouter's free tier holds the two verified models: the next one at step 0, none after the last assert fo_next_model(FC_RATE_LIMITED, PR_OPENROUTER, 0, 3, false); - assert fo_next_model(FC_OVERLOADED, PR_OPENROUTER, 1, 3, false); - assert fo_next_model(FC_RATE_LIMITED, PR_OPENROUTER, 2, 3, false) == false; + assert fo_next_model(FC_OVERLOADED, PR_OPENROUTER, 1, 3, false) == false; assert fo_next_model(FC_RATE_LIMITED, PR_OPENROUTER, 0, 3, true) == false; // the cap leaves room for every key of each free provider on each model of its tier, then every profile assert MAX_FAILOVERS >= MAX_KEYS * (profile_models(PR_ZAI) + profile_models(PR_OPENROUTER)) + PROFILE_COUNT; @@ -2176,10 +2474,9 @@ test a_pane_whose_zai_keys_all_rest_moves_to_openrouter_and_after_its_free_ids_t assert fo_next_model(FC_OVERLOADED, PR_ZAI, 1, 5, false) == false; assert fo_action(FC_OVERLOADED, PR_ZAI, 57, 0, 5, false) == FA_RESPAWN_NEXT; assert fo_target(FC_OVERLOADED, PR_ZAI, 57, 0, 5, false) == PR_OPENROUTER; - // OpenRouter's only key failed: its three free ids in turn, then the paid profile + // OpenRouter's only key failed: its two verified free ids in turn, then the paid profile assert fo_next_model(FC_RATE_LIMITED, PR_OPENROUTER, 0, 6, false); - assert fo_next_model(FC_RATE_LIMITED, PR_OPENROUTER, 1, 7, false); - assert fo_next_model(FC_RATE_LIMITED, PR_OPENROUTER, 2, 8, false) == false; + assert fo_next_model(FC_RATE_LIMITED, PR_OPENROUTER, 1, 7, false) == false; assert fo_action(FC_RATE_LIMITED, PR_OPENROUTER, 57, 16, 8, false) == FA_RESPAWN_NEXT; assert fo_target(FC_RATE_LIMITED, PR_OPENROUTER, 57, 16, 8, false) == PR_ANTHROPIC; // with no OpenRouter key stored (stored 49 = z.ai, Anthropic, Ollama): z.ai goes to the paid profile @@ -2195,6 +2492,197 @@ test a_pane_whose_zai_keys_all_rest_moves_to_openrouter_and_after_its_free_ids_t assert g < 128; } +test the_openrouter_models_measured_on_a_key_are_the_only_ones_and_the_thinking_one_has_a_token_floor { + var o : [64]u8 = undefined; + assert tier_model_step(PR_OPENROUTER, TI_DEFAULT, 0, o[0..64]) == 31; + assert begins_with(o[0..31], "minimax/minimax-m3.1-flash:free"); + assert tier_model_step(PR_OPENROUTER, TI_DEFAULT, 1, o[0..64]) == 33; + assert begins_with(o[0..33], "nvidia/nemotron-3.5-super-vl:free"); + assert tier_model_step(PR_OPENROUTER, TI_DEFAULT, 2, o[0..64]) == 0; + assert csv_field(MODELS_THIRD, PR_OPENROUTER, o[0..64]) == 0; + // super-vl thinks first (31 output tokens measured): a text request needs more than max_tokens 16 + assert THINKING_MIN_TOKENS == 64 and THINKING_MIN_TOKENS > 31; + assert model_min_tokens(PR_OPENROUTER, 1) == THINKING_MIN_TOKENS; + assert model_min_tokens(PR_OPENROUTER, 0) == PLAIN_MIN_TOKENS; + assert model_min_tokens(PR_ZAI, 0) == PLAIN_MIN_TOKENS; + assert model_min_tokens(PR_ZAI, 1) == PLAIN_MIN_TOKENS; +} + +test opencode_and_crush_take_the_zai_anthropic_and_openrouter_profiles_and_not_ollama { + assert agent_profile_ok(AG_OPENCODE, PR_ZAI) and agent_profile_ok(AG_OPENCODE, PR_ANTHROPIC) and agent_profile_ok(AG_OPENCODE, PR_OPENROUTER); + assert agent_profile_ok(AG_CRUSH, PR_ZAI) and agent_profile_ok(AG_CRUSH, PR_OPENROUTER); + assert agent_profile_ok(AG_OPENCODE, PR_OLLAMA_CLOUD) == false and agent_profile_ok(AG_CRUSH, PR_OLLAMA_CLOUD) == false; + assert agent_profile_ok(AG_CLAUDE, PR_OLLAMA_CLOUD); + assert agent_profile_ok(1, PR_ZAI) == false and agent_profile_ok(AG_CRUSH, PR_NONE) == false; + // only the ollama bit (5) stored: Claude Code has a key, OpenCode and Crush have none + assert agent_stored(AG_CLAUDE, 32) == 32; + assert agent_stored(AG_OPENCODE, 32) == 0 and pick_profile(agent_stored(AG_CRUSH, 32), 0) == PR_NONE; + assert agent_stored(AG_CRUSH, 57) == 25; + // the chain of a Crush pane with every key stored goes z.ai, OpenRouter, anthropic-api, then ends + const st : u32 = agent_stored(AG_CRUSH, 511); + assert pick_profile(st, 0) == PR_ZAI; + assert next_profile(PR_ZAI, st, 0) == PR_OPENROUTER; + assert next_profile(PR_OPENROUTER, st, 0) == PR_ANTHROPIC; + assert next_profile(PR_ANTHROPIC, st, 0) == PR_NONE; +} + +test an_opencode_pane_gets_the_key_variable_and_one_inline_config_that_holds_no_key { + var o : [700]u8 = undefined; + var m : [64]u8 = undefined; + assert agent_var_count(AG_OPENCODE, PR_ZAI) == 2 and agent_var_count(AG_OPENCODE, PR_OPENROUTER) == 2; + assert agent_var_count(AG_OPENCODE, PR_ANTHROPIC) == 1 and agent_var_count(AG_OPENCODE, PR_OLLAMA_CLOUD) == 0; + var n : usize = agent_var_name(AG_OPENCODE, PR_ZAI, 0, m[0..64]); + assert n == 11 and begins_with(m[0..n], "ZAI_API_KEY"); + assert agent_var_is_key(AG_OPENCODE, PR_ZAI, 0) and agent_var_is_key(AG_OPENCODE, PR_ZAI, 1) == false; + n = agent_var_name(AG_OPENCODE, PR_ZAI, 1, m[0..64]); + assert n == 23 and begins_with(m[0..n], "OPENCODE_CONFIG_CONTENT"); + n = agent_var_name(AG_OPENCODE, PR_OPENROUTER, 0, m[0..64]); + assert begins_with(m[0..n], "OPENROUTER_API_KEY"); + n = agent_var_name(AG_OPENCODE, PR_ANTHROPIC, 0, m[0..64]); + assert begins_with(m[0..n], "ANTHROPIC_API_KEY"); + assert agent_var_name(AG_OPENCODE, PR_ANTHROPIC, 1, m[0..64]) == 0; + // z.ai: the OpenAI-compatible endpoint, glm-4.5-flash first, the key by reference to its variable + n = agent_var_text_step(AG_OPENCODE, PR_ZAI, 1, 0, "/h", o[0..700]); + assert n > 0 and n < 700; + assert has_bytes(o[0..n], "\"baseURL\":\"https://api.z.ai/api/paas/v4\""); + assert has_bytes(o[0..n], "\"apiKey\":\"{env:ZAI_API_KEY}\""); + assert has_bytes(o[0..n], "\"model\":\"t27/glm-4.5-flash\""); + assert has_bytes(o[0..n], "@ai-sdk/openai-compatible"); + n = agent_var_text_step(AG_OPENCODE, PR_ZAI, 1, 1, "/h", o[0..700]); + assert has_bytes(o[0..n], "\"model\":\"t27/glm-4.7-flash\""); + assert agent_var_text_step(AG_OPENCODE, PR_ZAI, 1, 2, "/h", o[0..700]) == 0; + n = agent_var_text_step(AG_OPENCODE, PR_OPENROUTER, 1, 0, "/h", o[0..700]); + assert has_bytes(o[0..n], "\"baseURL\":\"https://openrouter.ai/api/v1\""); + assert has_bytes(o[0..n], "{env:OPENROUTER_API_KEY}"); + assert has_bytes(o[0..n], "\"model\":\"t27/minimax/minimax-m3.1-flash:free\""); + n = agent_var_text_step(AG_OPENCODE, PR_OPENROUTER, 1, 1, "/h", o[0..700]); + assert has_bytes(o[0..n], "t27/nvidia/nemotron-3.5-super-vl:free"); + // the key variable has no constant text, and no config exists for the profile with none + assert agent_var_text_step(AG_OPENCODE, PR_ZAI, 0, 0, "/h", o[0..700]) == 0; + assert opencode_config(PR_ANTHROPIC, 0, o[0..700]) == 0 and opencode_config(PR_NONE, 0, o[0..700]) == 0; + // Claude Code keeps its own variables through the same door + assert agent_var_count(AG_CLAUDE, PR_ZAI) == 7 and agent_var_is_key(AG_CLAUDE, PR_ZAI, 1); + n = agent_var_text_step(AG_CLAUDE, PR_ZAI, 0, 0, "/h", o[0..700]); + assert n == 30; +} + +test a_crush_pane_gets_the_key_variable_and_the_directory_of_a_key_free_crush_json { + var o : [800]u8 = undefined; + var m : [64]u8 = undefined; + assert agent_var_count(AG_CRUSH, PR_ZAI) == 2 and agent_var_count(AG_CRUSH, PR_OPENROUTER) == 2; + assert agent_var_count(AG_CRUSH, PR_ANTHROPIC) == 1 and agent_var_count(AG_CRUSH, PR_OLLAMA_CLOUD) == 0; + var n : usize = agent_var_name(AG_CRUSH, PR_ZAI, 1, m[0..64]); + assert n == 19 and begins_with(m[0..n], "CRUSH_GLOBAL_CONFIG"); + n = agent_var_text_step(AG_CRUSH, PR_ZAI, 1, 0, "/Users/x", o[0..800]); + assert n == 27 and begins_with(o[0..n], "/Users/x/.t27os/crush/zai-0"); + n = agent_var_text_step(AG_CRUSH, PR_OPENROUTER, 1, 1, "/Users/x", o[0..800]); + assert begins_with(o[0..n], "/Users/x/.t27os/crush/openrouter-1"); + assert crush_config_dir("/h", PR_ZAI, MODEL_STEPS, o[0..800]) == 0; + assert agent_config_file(AG_CRUSH, PR_ZAI) and agent_config_file(AG_CRUSH, PR_OPENROUTER); + assert agent_config_file(AG_CRUSH, PR_ANTHROPIC) == false and agent_config_file(AG_OPENCODE, PR_ZAI) == false; + n = crush_config_text(PR_ZAI, 0, o[0..800]); + assert n > 0 and n < 800; + assert has_bytes(o[0..n], "\"base_url\":\"https://api.z.ai/api/paas/v4\""); + assert has_bytes(o[0..n], "\"api_key\":\"$ZAI_API_KEY\""); + assert has_bytes(o[0..n], "\"large\":{\"provider\":\"t27\",\"model\":\"glm-4.5-flash\"}"); + assert has_bytes(o[0..n], "\"type\":\"openai-compat\""); + n = crush_config_text(PR_OPENROUTER, 1, o[0..800]); + assert has_bytes(o[0..n], "\"api_key\":\"$OPENROUTER_API_KEY\""); + assert has_bytes(o[0..n], "\"small\":{\"provider\":\"t27\",\"model\":\"nvidia/nemotron-3.5-super-vl:free\"}"); + assert crush_config_text(PR_ANTHROPIC, 0, o[0..800]) == 0; +} + +test opencode_and_crush_start_without_the_keys_and_configs_of_their_parent { + assert agent_strips(AG_OPENCODE, "ZAI_API_KEY") and agent_strips(AG_CRUSH, "OPENROUTER_API_KEY"); + assert agent_strips(AG_OPENCODE, "OPENCODE_CONFIG_CONTENT") and agent_strips(AG_CRUSH, "CRUSH_GLOBAL_CONFIG"); + assert agent_strips(AG_CRUSH, "ANTHROPIC_API_KEY") and agent_strips(AG_OPENCODE, "OPENAI_API_KEY"); + assert agent_strips(AG_CRUSH, "PATH") == false and agent_strips(AG_CRUSH, "ZAI_API_KEYS") == false; + assert agent_strips(AG_CLAUDE, "ANTHROPIC_AUTH_TOKEN") and agent_strips(AG_CLAUDE, "ZAI_API_KEY") == false; + assert agent_strips(1, "ZAI_API_KEY") == false; + assert csv_fields(AGENT_STRIPS) == AGENT_STRIP_COUNT; +} + +test the_generic_classifier_reads_the_error_rows_of_opencode_and_crush_too { + // neither agent documents what it prints on 401 or 429; the rows below are the shapes the classifier reads + assert classify_line("Error: 429 Too Many Requests") == FC_RATE_LIMITED; + assert classify_line("Error: 401 Unauthorized") == FC_LOGIN_REJECTED; + assert classify_line("AI_APICallError: Insufficient balance or no resource package (1113)") == FC_NO_BALANCE; + assert classify_line("Error: provider is overloaded") == FC_OVERLOADED; + assert classify_line("Reading 429 files") == FC_NONE; +} + +test an_agent_that_is_not_installed_says_so_instead_of_stopped { + // mask: Claude Code (0), OpenCode (3) and Crush (5) installed + const mask : u32 = 1 + 8 + 32; + assert agent_installed(mask, 0) and agent_installed(mask, 3) and agent_installed(mask, 5); + assert agent_installed(mask, 1) == false and agent_installed(mask, 8) == false and agent_installed(mask, AGENT_COUNT) == false; + assert agent_state(mask, 1, false, 511) == AS_NOT_INSTALLED; + assert agent_state(mask, 1, true, 511) == AS_NOT_INSTALLED; + assert agent_state(mask, 6, false, 0) == AS_NOT_INSTALLED; + assert agent_state(mask, 3, true, 16) == AS_RUNNING; + assert agent_state(mask, 3, false, 16) == AS_STOPPED; + // an installed profile agent with no key it can use says so; a z.ai key is enough for OpenCode now + assert agent_state(mask, 3, false, 0) == AS_NO_KEY; + assert agent_state(mask, 5, false, 32) == AS_NO_KEY; + assert agent_state(mask, 5, false, 16) == AS_STOPPED; + var o : [32]u8 = undefined; + assert csv_field(AS_WORDS, AS_NOT_INSTALLED, o[0..32]) == 13; + assert csv_fields(AS_WORDS) == 4; +} + +test the_traffic_light_is_green_on_paid_yellow_on_free_and_red_without_balance { + assert light_of(CT_PAID, false, 2) == L_GREEN; + assert light_of(CT_ZAI_FREE, false, 3) == L_YELLOW; + assert light_of(CT_OR_FREE, false, 1) == L_YELLOW; + // the chain gave up, or no key can serve: red on every tier + assert light_of(CT_PAID, true, 2) == L_RED; + assert light_of(CT_ZAI_FREE, true, 7) == L_RED; + assert light_of(CT_OR_FREE, false, 0) == L_RED; + assert light_of(CT_PAID, false, 0) == L_RED; + // no profile (an editor, a shell): no light + assert light_of(CT_NO_TIER, false, 5) == L_NONE; + assert light_of(CT_NO_TIER, true, 0) == L_NONE; + assert light_of(CT_COUNT, false, 1) == L_NONE; + // the profile tiers feed it + assert PROFILE_TIER[PR_ZAI] == CT_ZAI_FREE and PROFILE_TIER[PR_OPENROUTER] == CT_OR_FREE and PROFILE_TIER[PR_ANTHROPIC] == CT_PAID; + var o : [16]u8 = undefined; + assert light_name(L_NONE, o[0..16]) == 4 and light_name(L_GREEN, o[0..16]) == 5; + assert light_name(L_YELLOW, o[0..16]) == 6 and light_name(L_RED, o[0..16]) == 3; + assert begins_with(o[0..3], "red"); +} + +test the_worst_light_over_the_running_panes_is_red_then_yellow_then_green { + var l : [6]u32 = [L_GREEN, L_YELLOW, L_RED, L_NONE, L_GREEN, L_YELLOW]; + assert worst_light(l[0..6], 63) == L_RED; + // a pane that does not run does not count + assert worst_light(l[0..6], 63 - 4) == L_YELLOW; + assert worst_light(l[0..6], 1 + 16) == L_GREEN; + assert worst_light(l[0..6], 8) == L_NONE; + assert worst_light(l[0..6], 0) == L_NONE; + assert worst_light(l[0..0], 63) == L_NONE; + assert L_RED > L_YELLOW and L_YELLOW > L_GREEN and L_GREEN > L_NONE; +} + +test the_tooltip_pieces_name_the_model_and_the_reason { + var o : [96]u8 = undefined; + var n : usize = light_tip(L_YELLOW, "glm-4.5-flash", o[0..96]); + assert n == 19 and begins_with(o[0..n], "free: glm-4.5-flash"); + n = light_tip(L_GREEN, "claude-sonnet-5-5", o[0..96]); + assert begins_with(o[0..n], "paid: claude-sonnet-5-5"); + n = light_tip(L_RED, "glm-4.5-flash", o[0..96]); + assert n == 25 and begins_with(o[0..n], "no balance: all keys rest"); + assert light_tip(L_NONE, "x", o[0..96]) == 0; +} + +test the_light_travels_in_the_top_bits_of_the_listing_state_byte { + assert shown_with_light(5, L_RED) == 5 + 192; + assert shown_state_of(shown_with_light(5, L_RED)) == 5 and shown_light_of(shown_with_light(5, L_RED)) == L_RED; + assert shown_light_of(shown_with_light(63, L_YELLOW)) == L_YELLOW and shown_state_of(shown_with_light(63, L_YELLOW)) == 63; + // an old server sends the state alone: no light + assert shown_light_of(5) == L_NONE and shown_state_of(5) == 5; + assert shown_with_light(5, L_NONE) == 5; +} + test the_profile_tables_have_one_field_per_profile { assert csv_fields(PROFILE_NAMES) == PROFILE_COUNT; assert csv_fields(MODELS_DEFAULT) == PROFILE_COUNT; diff --git a/specs/t27os/pty_logic.t27 b/specs/t27os/pty_logic.t27 index 2f396a230f..60847fd8e3 100644 --- a/specs/t27os/pty_logic.t27 +++ b/specs/t27os/pty_logic.t27 @@ -109,6 +109,22 @@ use t27os::keys::FC_OTHER; use t27os::keys::FC_LOGIN_REJECTED; use t27os::keys::profile_agent; use t27os::keys::pick_profile; +use t27os::keys::agent_profile_ok; +use t27os::keys::agent_stored; +use t27os::keys::light_of; +use t27os::keys::LIGHT_SOON_S; +use t27os::keys::CT_NO_TIER; +use t27os::keys::PROFILE_TIER; +use t27os::keys::shown_with_light; +use t27os::keys::shown_state_of; +use t27os::keys::shown_light_of; +use t27os::keys::L_YELLOW; +use t27os::keys::L_GREEN; +use t27os::keys::agent_var_count; +use t27os::keys::agent_var_name; +use t27os::keys::agent_var_text_step; +use t27os::keys::L_NONE; +use t27os::keys::L_RED; use t27os::keys::rotate_pick_soft; use t27os::keys::rotate_pick_except; use t27os::keys::key_rest_after; @@ -187,6 +203,10 @@ pub const G_KLOAD : usize = 228; /// 16^i: 0 = the default model of the pane's profile, 1 = the second model of its tier (gHashTag/t27#11284). /// Word 229 was free; the connection tables begin at G_CFD. pub const G_MSTEP : usize = 229; +/// The agent cell + 1 of every pane (0 = unset = Claude Code), one nibble per pane, pane i at 16^i: plan_pane_keys +/// writes it, so a failover (which is given no agent) walks the chain of the right agent (keys.t27 agent_stored). +/// Word 230 was free; the connection tables begin at G_CFD. +pub const G_AGENTS : usize = 230; pub const G_KLAST : usize = 256; pub const G_KREST : usize = 328; pub const G_PANEK : usize = 400; @@ -605,6 +625,19 @@ pub fn pane_key_set(st: []u64, i: usize, p: u32, v: u32) -> void { st[at] = st[at] - @as(u64, pane_key_get(st, i, p)) * m + @as(u64, v) * m; } +/// The agent cell pane i was last planned for (keys.t27 AG_*); Claude Code when none was. +pub fn pane_agent_get(st: []const u64, i: usize) -> u32 { + const v : u32 = @as(u32, @intCast(st[G_AGENTS] / pow16(@as(u32, @intCast(i))) % 16)); + if (v == 0) { return 0; } + return v - 1; +} + +pub fn pane_agent_set(st: []u64, i: usize, a: u32) -> void { + const m : u64 = pow16(@as(u32, @intCast(i))); + const old : u64 = st[G_AGENTS] / m % 16; + st[G_AGENTS] = st[G_AGENTS] - old * m + @as(u64, a + 1) * m; +} + /// The key number + 1 a LIST shows for pane i (0 = none): for the claude cell the key of its profile's provider, /// for every other agent the first provider it reads that it holds a key for. pub fn pane_key_shown(st: []const u64, i: usize, a: u32) -> u32 { @@ -622,6 +655,34 @@ pub fn pane_key_shown(st: []const u64, i: usize, a: u32) -> u32 { return 0; } +/// How many keys of provider p can serve at `now_ms`: present and free, or resting less than LIGHT_SOON_S more. +pub fn keys_usable(st: []const u64, p: u32, now_ms: u64) -> u32 { + var c : u32 = 0; + var k : u32 = 0; + while (k < MAX_KEYS) { + if (key_present(st, p, k) and st[G_KREST + key_index(p, k)] <= now_ms / 1000 + LIGHT_SOON_S) { c = c + 1; } + k = k + 1; + } + return c; +} + +/// The traffic light of pane i (agent a) at `now_ms` (keys.t27 light_of): its profile's tier, whether the chain +/// gave up (the screen is quiet after a give-up, PK_STOP in the future), and the keys of its provider that can +/// still serve. A cell with no profile (an editor, a shell) has none; a profile agent with no profile yet is RED. +pub fn pane_light(st: []const u64, i: usize, a: u32, now_ms: u64) -> u32 { + if (profile_agent(a) == false) { return L_NONE; } + const pk : usize = G_PANEK + i * PK; + const w : u64 = st[pk + PK_PROF]; + if (w >= @as(u64, PROFILE_COUNT)) { return L_RED; } + const pr : u32 = @as(u32, @intCast(w)); + return light_of(PROFILE_TIER[pr], st[pk + PK_STOP] > now_ms, keys_usable(st, PROFILE_PROVIDER[pr], now_ms)); +} + +/// The LIST `shown` byte of pane i: the state with the light in bits 6 and 7. +pub fn list_shown(st: []const u64, i: usize, a: u32, shown: u32, now_ms: u64) -> u32 { + return shown_with_light(shown, pane_light(st, i, a, now_ms)); +} + /// The flags byte of a LIST entry with the pane's key number + 1 in its high nibble (0 to 8). Bits 0 to 2 are /// list_flags as before, so a client that reads only those is not disturbed. pub fn list_flags_key(live: bool, w: u32, key1: u32) -> u32 { @@ -637,12 +698,13 @@ pub fn plan_pane_keys(st: []u64, i: usize, a: u32, now_ms: u64) -> void { const pk : usize = G_PANEK + i * PK; st[pk + PK_KEYS] = 0; st[pk + PK_STOP] = 0; + if (a < AGENT_COUNT) { pane_agent_set(st, i, a); } if (profile_agent(a)) { var pr : u32 = PR_NONE; - if (st[pk + PK_PROF] < @as(u64, PROFILE_COUNT)) { + if (st[pk + PK_PROF] < @as(u64, PROFILE_COUNT) and agent_profile_ok(a, @as(u32, @intCast(st[pk + PK_PROF])))) { pr = @as(u32, @intCast(st[pk + PK_PROF])); } else { - pr = pick_profile(keys_stored(st), @as(u32, @intCast(st[pk + PK_BAD]))); + pr = pick_profile(agent_stored(a, keys_stored(st)), @as(u32, @intCast(st[pk + PK_BAD]))); } var skip : u32 = MAX_KEYS; if (st[pk + PK_SKIP] > 0) { skip = @as(u32, @intCast(st[pk + PK_SKIP])) - 1; } @@ -695,7 +757,7 @@ pub fn plan_failover(st: []u64, i: usize, class: u32, now_ms: u64, why: []u8, wl const pk : usize = G_PANEK + i * PK; var pr : u32 = PR_NONE; if (st[pk + PK_PROF] < @as(u64, PROFILE_COUNT)) { pr = @as(u32, @intCast(st[pk + PK_PROF])); } - const stored : u32 = keys_stored(st); + const stored : u32 = agent_stored(pane_agent_get(st, i), keys_stored(st)); const bad : u32 = @as(u32, @intCast(st[pk + PK_BAD])); const done : u32 = @as(u32, @intCast(st[pk + PK_DONE])); var cur : u32 = MAX_KEYS; @@ -882,7 +944,7 @@ pub fn needs_key(a: u32) -> bool { /// provider (z.ai, Anthropic, OpenRouter, Ollama cloud), every other agent a provider of AGENT_PROVIDERS. pub fn has_key_for(st: []const u64, a: u32) -> bool { const stored : u32 = keys_stored(st); - if (profile_agent(a)) { return pick_profile(stored, 0) != PR_NONE; } + if (profile_agent(a)) { return pick_profile(agent_stored(a, stored), 0) != PR_NONE; } return spawn_mask(a, stored) != 0; } @@ -958,7 +1020,7 @@ pub fn load_mask_agent(a: u32) -> u32 { var r : u32 = 0; while (r < PROFILE_COUNT) { const prov : u32 = PROFILE_PROVIDER[r]; - if ((m >> prov) % 2 == 0) { m = m + (1 << prov); } + if (agent_profile_ok(a, r) and (m >> prov) % 2 == 0) { m = m + (1 << prov); } r = r + 1; } return m; @@ -1383,8 +1445,8 @@ test every_other_agent_gets_a_key_of_each_provider_it_reads { t_zai3(st[0..512]); t_key(st[0..512], P_OPENAI, 0); t_key(st[0..512], P_OPENAI, 1); - // OpenCode (cell 3) reads Anthropic, OpenAI, OpenRouter: OpenRouter holds none - plan_pane_keys(st[0..512], 0, 3, 1000000); + // Aider (cell 7) reads Anthropic, OpenAI, Gemini, OpenRouter: OpenRouter holds none + plan_pane_keys(st[0..512], 0, 7, 1000000); assert pane_key_get(st[0..512], 0, P_ANTHROPIC) == 1; assert pane_key_get(st[0..512], 0, P_OPENAI) == 1; assert pane_key_get(st[0..512], 0, 3) == 0; @@ -1394,10 +1456,11 @@ test every_other_agent_gets_a_key_of_each_provider_it_reads { assert pane_key_get(st[0..512], 1, P_OPENAI) == 2; assert pane_key_get(st[0..512], 1, P_ANTHROPIC) == 0; assert pane_key_shown(st[0..512], 1, 1) == 2; - // Crush (cell 5) reads ZAI_API_KEY among others + // Crush (cell 5) takes the profile chain now (gHashTag/t27#11275): the z.ai profile with a z.ai key, no OpenAI one plan_pane_keys(st[0..512], 2, 5, 1000200); assert pane_key_get(st[0..512], 2, P_ZAI) > 0; - assert pane_key_get(st[0..512], 2, P_OPENAI) > 0; + assert pane_key_get(st[0..512], 2, P_OPENAI) == 0; + assert t_prof(st[0..512], 2) == PR_ZAI; // a shell and an editor read none; an agent with no key of its provider gets nothing plan_pane_keys(st[0..512], 3, 9, 1000300); assert pane_key_shown(st[0..512], 3, 9) == 0; @@ -1844,21 +1907,17 @@ test when_every_zai_key_rests_the_pane_moves_to_openrouters_free_ids_and_then_to assert pane_key_get(st[0..512], 0, P_OPENROUTER) == 1; n = launch_model(st[0..512], 0, 0, false, "minimax/minimax-m3.1-flash:free", o[0..64]); assert n == 33 and begins_with(o[0..n], "nvidia/nemotron-3.5-super-vl:free"); - assert t_fail(st[0..512], FC_RATE_LIMITED, 1008000) == FA_RESPAWN_MODEL; - assert pane_mstep_get(st[0..512], 0) == 2; - n = launch_model(st[0..512], 0, 0, false, "nvidia/nemotron-3.5-super-vl:free", o[0..64]); - assert n == 34 and begins_with(o[0..n], "nvidia/nemotron-3.5-lightning:free"); // both free tiers rest: only now the paid profile, whose model is not an OpenRouter id assert t_fail(st[0..512], FC_RATE_LIMITED, 1009000) == FA_RESPAWN_NEXT; assert t_prof(st[0..512], 0) == PR_ANTHROPIC; assert pane_mstep_get(st[0..512], 0) == 0; assert pane_key_get(st[0..512], 0, P_ANTHROPIC) == 1; assert st[G_PANEK + PK_BAD] == 24; - assert st[G_PANEK + PK_DONE] == 9; - assert launch_model(st[0..512], 0, 0, false, "nvidia/nemotron-3.5-lightning:free", o[0..64]) == 0; + assert st[G_PANEK + PK_DONE] == 8; + assert launch_model(st[0..512], 0, 0, false, "nvidia/nemotron-3.5-super-vl:free", o[0..64]) == 0; // the paid key fails too: no tier is left and the pane says so once assert t_fail(st[0..512], FC_RATE_LIMITED, 1010000) == FA_GIVE_UP; - assert st[G_PANEK + PK_DONE] == 9; + assert st[G_PANEK + PK_DONE] == 8; assert st[G_PANEK + PK_STOP] == 1010000 + GIVEUP_QUIET_MS; } @@ -1902,9 +1961,8 @@ test with_no_openrouter_key_stored_the_pane_skips_that_tier_to_the_paid_profile_ plan_pane_keys(st[0..512], 0, 0, 1000000); assert t_prof(st[0..512], 0) == PR_OPENROUTER; assert t_fail(st[0..512], FC_NO_BALANCE, 1001000) == FA_RESPAWN_MODEL; - assert t_fail(st[0..512], FC_NO_BALANCE, 1002000) == FA_RESPAWN_MODEL; assert t_fail(st[0..512], FC_NO_BALANCE, 1003000) == FA_GIVE_UP; - assert st[G_PANEK + PK_DONE] == 2; + assert st[G_PANEK + PK_DONE] == 1; } test a_pane_on_openrouter_names_the_free_id_on_its_command_line_and_in_its_environment { @@ -2110,9 +2168,9 @@ test the_server_reads_the_providers_a_spawn_can_start_with { // every other agent reads the providers keys.t27 AGENT_PROVIDERS names assert load_mask_agent(1) == 2; assert load_mask_agent(2) == 4; - assert load_mask_agent(3) == 11; + assert load_mask_agent(3) == 25; assert load_mask_agent(4) == 1; - assert load_mask_agent(5) == 63; + assert load_mask_agent(5) == 25; assert load_mask_agent(6) == 128; assert load_mask_agent(7) == 15; assert load_mask_agent(8) == 64; @@ -2170,3 +2228,143 @@ test a_read_of_the_keychain_fills_the_table_like_a_window_push { key_put(st[0..512], P_ZAI, 2, true, false); assert key_count(st[0..512], P_ZAI) == 3; } + +test an_opencode_pane_and_a_crush_pane_get_a_profile_and_their_own_variables_by_name { + var st : [512]u64 = undefined; + var o : [700]u8 = undefined; + t_zai3(st[0..512]); + t_key(st[0..512], P_OPENROUTER, 0); + // OpenCode (cell 3): z.ai first, key 1 by rotation, the key variable and the inline config by name + plan_pane_keys(st[0..512], 0, 3, 1000000); + assert t_prof(st[0..512], 0) == PR_ZAI; + assert pane_key_get(st[0..512], 0, P_ZAI) == 1; + assert pane_agent_get(st[0..512], 0) == 3; + assert agent_var_count(3, t_prof(st[0..512], 0)) == 2; + var n : usize = agent_var_name(3, PR_ZAI, 0, o[0..40]); + assert n == 11 and begins_with(o[0..n], "ZAI_API_KEY"); + n = agent_var_name(3, PR_ZAI, 1, o[0..40]); + assert begins_with(o[0..n], "OPENCODE_CONFIG_CONTENT"); + // Crush (cell 5) on pane 1: the next z.ai key, the config directory named from the pane's model step + plan_pane_keys(st[0..512], 1, 5, 1000100); + assert t_prof(st[0..512], 1) == PR_ZAI; + assert pane_key_get(st[0..512], 1, P_ZAI) == 2; + assert pane_agent_get(st[0..512], 1) == 5 and pane_agent_get(st[0..512], 0) == 3; + n = agent_var_name(5, PR_ZAI, 1, o[0..40]); + assert begins_with(o[0..n], "CRUSH_GLOBAL_CONFIG"); + // the model each starts with: glm-4.5-flash at step 0, glm-4.7-flash at step 1 + assert agent_var_text_step(5, PR_ZAI, 1, pane_mstep_get(st[0..512], 1), "/h", o[0..700]) == 21; + n = launch_model(st[0..512], 1, 5, false, "x", o[0..64]); + assert n == 13 and begins_with(o[0..n], "glm-4.5-flash"); + // a profile agent reads its error rows; a Codex pane (cell 1) still does not + assert scan_applies(3) and scan_applies(5) and scan_applies(1) == false; + assert has_key_for(st[0..512], 3) and has_key_for(st[0..512], 5); + assert load_mask_agent(3) == 25 and load_mask_agent(5) == 25; + assert needs_key(3) and needs_key(5); +} + +test an_opencode_pane_rotates_keys_models_and_profiles_like_claude_code_and_never_to_ollama { + var st : [512]u64 = undefined; + var why : [128]u8 = undefined; + var wl : [2]u64 = undefined; + t_table(st[0..512]); + t_key(st[0..512], P_ZAI, 0); + t_key(st[0..512], P_ZAI, 1); + t_key(st[0..512], P_OPENROUTER, 0); + t_key(st[0..512], P_OLLAMA, 0); + plan_pane_keys(st[0..512], 0, 3, 1000000); + assert pane_key_get(st[0..512], 0, P_ZAI) == 1; + // key 1 is throttled: key 2, same profile + assert plan_failover(st[0..512], 0, FC_RATE_LIMITED, 1001000, why[0..128], wl[0..2]) == FA_RESPAWN_KEY; + plan_pane_keys(st[0..512], 0, 3, 1001500); + assert pane_key_get(st[0..512], 0, P_ZAI) == 2; + // key 2 too: no key left on glm-4.5-flash, the next model of the tier + assert plan_failover(st[0..512], 0, FC_RATE_LIMITED, 1002000, why[0..128], wl[0..2]) == FA_RESPAWN_MODEL; + assert pane_mstep_get(st[0..512], 0) == 1; + plan_pane_keys(st[0..512], 0, 3, 1002500); + var o : [64]u8 = undefined; + const n : usize = launch_model(st[0..512], 0, 3, false, "glm-4.5-flash", o[0..64]); + assert n == 13 and begins_with(o[0..n], "glm-4.7-flash"); + // both keys fail on it: OpenRouter, step 0, with the OpenAI-compatible base URL + assert plan_failover(st[0..512], 0, FC_RATE_LIMITED, 1003000, why[0..128], wl[0..2]) == FA_RESPAWN_KEY; + plan_pane_keys(st[0..512], 0, 3, 1003500); + assert plan_failover(st[0..512], 0, FC_NO_BALANCE, 1004000, why[0..128], wl[0..2]) == FA_RESPAWN_NEXT; + assert t_prof(st[0..512], 0) == PR_OPENROUTER; + assert pane_mstep_get(st[0..512], 0) == 0; + plan_pane_keys(st[0..512], 0, 3, 1004500); + assert pane_key_get(st[0..512], 0, P_OPENROUTER) == 1; + // OpenRouter's key fails on both ids; the Ollama key is no OpenCode profile, so the chain ends + assert plan_failover(st[0..512], 0, FC_RATE_LIMITED, 1005000, why[0..128], wl[0..2]) == FA_RESPAWN_MODEL; + plan_pane_keys(st[0..512], 0, 3, 1005500); + assert plan_failover(st[0..512], 0, FC_RATE_LIMITED, 1006000, why[0..128], wl[0..2]) == FA_GIVE_UP; + assert st[G_PANEK + PK_STOP] > 1006000; + // Claude Code in the same table still reaches Ollama cloud after OpenRouter + plan_pane_keys(st[0..512], 1, 0, 1007000); + assert pane_agent_get(st[0..512], 1) == 0; + assert agent_profile_ok(0, PR_OLLAMA_CLOUD); +} + +test a_pane_with_only_an_ollama_key_is_keyless_for_opencode_and_crush { + var st : [512]u64 = undefined; + t_table(st[0..512]); + t_key(st[0..512], P_OLLAMA, 0); + assert has_key_for(st[0..512], 0); + assert has_key_for(st[0..512], 3) == false and has_key_for(st[0..512], 5) == false; + assert restore_waits(st[0..512], 3); + plan_pane_keys(st[0..512], 0, 3, 1000000); + assert t_prof(st[0..512], 0) == PR_NONE; + assert pane_light(st[0..512], 0, 3, 1000000) == L_RED; +} + +test the_light_of_a_pane_follows_its_tier_its_keys_and_its_give_up { + var st : [512]u64 = undefined; + var why : [128]u8 = undefined; + var wl : [2]u64 = undefined; + t_table(st[0..512]); + t_key(st[0..512], P_ZAI, 0); + t_key(st[0..512], P_ANTHROPIC, 0); + plan_pane_keys(st[0..512], 0, 0, 1000000); + // a free z.ai profile with a usable key: yellow + assert pane_light(st[0..512], 0, 0, 1000000) == L_YELLOW; + assert pane_light(st[0..512], 0, 3, 1000000) == L_YELLOW; + // an editor, a shell and a cell with no profile have none + assert pane_light(st[0..512], 0, SHELL_CELL, 1000000) == L_NONE; + assert pane_light(st[0..512], 0, 1, 1000000) == L_NONE; + // the z.ai key has no balance on both flash models: the pane moves to the paid profile and is green + assert plan_failover(st[0..512], 0, FC_NO_BALANCE, 1001000, why[0..128], wl[0..2]) == FA_RESPAWN_MODEL; + plan_pane_keys(st[0..512], 0, 0, 1001200); + assert pane_light(st[0..512], 0, 0, 1001200) == L_YELLOW; + assert plan_failover(st[0..512], 0, FC_NO_BALANCE, 1001300, why[0..128], wl[0..2]) == FA_RESPAWN_NEXT; + plan_pane_keys(st[0..512], 0, 0, 1001500); + assert t_prof(st[0..512], 0) == PR_ANTHROPIC; + assert pane_light(st[0..512], 0, 0, 1001500) == L_GREEN; + // the paid key is out of balance too (rests 600 s): every usable key rests, and the chain gave up: red + assert plan_failover(st[0..512], 0, FC_NO_BALANCE, 1002000, why[0..128], wl[0..2]) == FA_GIVE_UP; + assert pane_light(st[0..512], 0, 0, 1002000) == L_RED; + // a throttled key (30 s) is busy, not empty: its pane stays yellow while the key is soon free + t_table(st[0..512]); + t_key(st[0..512], P_ZAI, 0); + plan_pane_keys(st[0..512], 0, 0, 2000000); + key_rest(st[0..512], P_ZAI, 0, 2000 + 30); + assert keys_usable(st[0..512], P_ZAI, 2000000) == 1; + assert pane_light(st[0..512], 0, 0, 2000000) == L_YELLOW; + // an empty account (600 s) leaves no usable key: red; the clock far past the rest frees it again + key_rest(st[0..512], P_ZAI, 0, 2000 + 600); + assert keys_usable(st[0..512], P_ZAI, 2000000) == 0; + assert pane_light(st[0..512], 0, 0, 2000000) == L_RED; + assert pane_light(st[0..512], 0, 0, 2000000 + 590000) == L_YELLOW; + // a profile agent with no profile yet is red + t_table(st[0..512]); + plan_pane_keys(st[0..512], 2, 5, 1000000); + assert pane_light(st[0..512], 2, 5, 1000000) == L_RED; +} + +test the_listing_state_byte_carries_the_light_and_an_old_reader_can_mask_it { + var st : [512]u64 = undefined; + t_table(st[0..512]); + t_key(st[0..512], P_ZAI, 0); + plan_pane_keys(st[0..512], 0, 0, 1000000); + const b : u32 = list_shown(st[0..512], 0, 0, 3, 1000000); + assert b == 3 + 64 * L_YELLOW; + assert shown_state_of(b) == 3 and shown_light_of(b) == L_YELLOW; + assert list_shown(st[0..512], 0, SHELL_CELL, 3, 1000000) == 3; +} diff --git a/specs/t27os/pty_server.t27 b/specs/t27os/pty_server.t27 index ffb9f5a575..798e1544de 100644 --- a/specs/t27os/pty_server.t27 +++ b/specs/t27os/pty_server.t27 @@ -217,10 +217,18 @@ use t27os::keys::key_fits; use t27os::keys::profile_agent; use t27os::keys::claude_strips; use t27os::keys::PROFILE_PROVIDER; -use t27os::keys::profile_var_count; -use t27os::keys::profile_var_name; -use t27os::keys::profile_var_is_key; -use t27os::keys::profile_var_text_step; +use t27os::keys::agent_var_count; +use t27os::keys::agent_var_name; +use t27os::keys::agent_var_is_key; +use t27os::keys::agent_var_text_step; +use t27os::keys::agent_strips; +use t27os::keys::agent_config_file; +use t27os::keys::crush_config_text; +use t27os::keys::crush_config_dir; +use t27os::keys::light_name; +use t27os::keys::shown_state_of; +use t27os::keys::shown_light_of; +use t27os::pty_logic::list_shown; use t27os::pty_client::sockaddr_un; use t27os::pty_client::client_connect; use t27os::pty_client::client_list; @@ -435,7 +443,7 @@ fn strip_claude_env(mem: []u8, agent: u32) -> void { sys_peek(mem[O_TMP2 + 16 .. O_TMP2 + 120], ent, n); var e : usize = 0; while (e < @as(usize, @intCast(n)) and mem[O_TMP2 + 16 + e] != 61) { e = e + 1; } - if (e < @as(usize, @intCast(n)) and (env_stripped(mem[O_TMP2 + 16 .. O_TMP2 + 16 + e]) or (profile_agent(agent) and claude_strips(mem[O_TMP2 + 16 .. O_TMP2 + 16 + e])))) { + if (e < @as(usize, @intCast(n)) and (env_stripped(mem[O_TMP2 + 16 .. O_TMP2 + 16 + e]) or (profile_agent(agent) and agent_strips(agent, mem[O_TMP2 + 16 .. O_TMP2 + 16 + e])))) { mem[O_TMP2 + 16 + e] = 0; sys_unsetenv(mem[O_TMP2 + 16 .. O_TMP2 + 16 + e + 1]); again = true; @@ -451,7 +459,7 @@ fn strip_claude_env(mem: []u8, agent: u32) -> void { /// (pty_logic plan_pane_keys: pick_profile over the providers that hold a key, or the profile a failover moved /// to) with the key of the number it picked by rotation. No usable key leaves the pane without one, and none of /// the other profiles' variables is set. The key is read from its slot; its number is all the pane record holds. -fn inject_profile(st: []const u64, mem: []u8, i: usize) -> void { +fn inject_profile(st: []const u64, mem: []u8, i: usize, agent: u32) -> void { const pr64 : u64 = st[G_PANEK + i * PK + PK_PROF]; if (pr64 >= @as(u64, PROFILE_COUNT)) { return; } const pr : u32 = @as(u32, @intCast(pr64)); @@ -460,26 +468,54 @@ fn inject_profile(st: []const u64, mem: []u8, i: usize) -> void { if (kn == 0) { return; } const at : usize = key_slot(prov, kn - 1); var k : u32 = 0; - while (k < profile_var_count(pr)) { - const nl : usize = profile_var_name(pr, k, mem[O_TMP2 .. O_TMP2 + 40]); + while (k < agent_var_count(agent, pr)) { + const nl : usize = agent_var_name(agent, pr, k, mem[O_TMP2 .. O_TMP2 + 40]); mem[O_TMP2 + nl] = 0; - if (profile_var_is_key(pr, k)) { + if (agent_var_is_key(agent, pr, k)) { sys_setenv(mem[O_TMP2 .. O_TMP2 + nl + 1], mem[at + 1 .. at + 2 + @as(usize, mem[at])], 1); } else { - const vl : usize = profile_var_text_step(pr, k, pane_mstep_get(st, i), mem[O_TMP2 + 64 .. O_TMP2 + 200]); - mem[O_TMP2 + 64 + vl] = 0; - sys_setenv(mem[O_TMP2 .. O_TMP2 + nl + 1], mem[O_TMP2 + 64 .. O_TMP2 + 64 + vl + 1], 1); + const vl : usize = agent_var_text_step(agent, pr, k, pane_mstep_get(st, i), home_slice(st, mem), mem[O_TMP2 + 3072 .. O_TMP2 + 4000]); + mem[O_TMP2 + 3072 + vl] = 0; + sys_setenv(mem[O_TMP2 .. O_TMP2 + nl + 1], mem[O_TMP2 + 3072 .. O_TMP2 + 3072 + vl + 1], 1); } k = k + 1; } } +/// In the parent, before the fork, for a Crush pane: write the key-free crush.json of the pane's profile and model +/// step into /.t27os/crush/-/ (keys.t27 crush_config_text; the pane gets that directory as +/// CRUSH_GLOBAL_CONFIG). The file holds no key, so rewriting it at every start is harmless. +fn write_agent_config(st: []const u64, mem: []u8, i: usize, agent: u32) -> void { + const pr64 : u64 = st[G_PANEK + i * PK + PK_PROF]; + if (pr64 >= @as(u64, PROFILE_COUNT)) { return; } + const pr : u32 = @as(u32, @intCast(pr64)); + if (agent_config_file(agent, pr) == false) { return; } + const step : u32 = pane_mstep_get(st, i); + const home : []const u8 = home_slice(st, mem); + var hn : usize = put_bytes(mem[O_TMP2 + 4200 .. O_TMP2 + 4400], 0, home); + hn = put_bytes(mem[O_TMP2 + 4200 .. O_TMP2 + 4400], hn, "/.t27os/crush"); + mem[O_TMP2 + 4200 + hn] = 0; + sys_mkdir(mem[O_TMP2 + 4200 .. O_TMP2 + 4200 + hn + 1], 493); + const dn : usize = crush_config_dir(home, pr, step, mem[O_TMP2 + 4200 .. O_TMP2 + 4400]); + mem[O_TMP2 + 4200 + dn] = 0; + sys_mkdir(mem[O_TMP2 + 4200 .. O_TMP2 + 4200 + dn + 1], 493); + var fnn : usize = put_bytes(mem[O_TMP2 + 4500 .. O_TMP2 + 4800], 0, mem[O_TMP2 + 4200 .. O_TMP2 + 4200 + dn]); + fnn = put_bytes(mem[O_TMP2 + 4500 .. O_TMP2 + 4800], fnn, "/crush.json"); + mem[O_TMP2 + 4500 + fnn] = 0; + const tn : usize = crush_config_text(pr, step, mem[O_TMP2 + 4800 .. O_TMP2 + 5800]); + const fd : i32 = sys_creat(mem[O_TMP2 + 4500 .. O_TMP2 + 4500 + fnn + 1], 420); + if (fd >= 0) { + sys_write(fd, mem[O_TMP2 + 4800 .. O_TMP2 + 4800 + tn], @as(u64, tn)); + sys_close(fd); + } +} + /// In the child: set the environment variable (keys.t27 PROVIDER_ENVS) of each provider the agent reads /// (AGENT_PROVIDERS) to the key plan_pane_keys picked for pane i from that provider's numbered keys. A /// provider that holds no key leaves its variable unset. fn inject_keys(st: []const u64, mem: []u8, i: usize, agent: u32) -> void { if (profile_agent(agent)) { - inject_profile(st, mem, i); + inject_profile(st, mem, i, agent); return; } var p : u32 = 0; @@ -512,6 +548,7 @@ fn launch(st: []u64, mem: []u8, i: usize, plan: u32, spawned: bool) -> bool { // the keys of this start: the profile and the key number of each provider the agent reads, by rotation // over the numbered keys (a failover has set what to skip); the unfinished screen row of the old process goes plan_pane_keys(st, i, agent, now_ms(mem)); + write_agent_config(st, mem, i, agent); // the model of this start, from the profile just chosen; the pane's slot (and so its record) takes it const mn : usize = launch_model(st, i, agent, spawned, model_of(mem, i), mem[O_TMP2 + 2048 .. O_TMP2 + 2112]); mem[O_MODEL + i * MODEL_CAP] = 0; @@ -759,7 +796,7 @@ fn handle_frame(st: []u64, mem: []u8, fd: i32, off: usize) -> void { const w : u32 = @as(u32, @intCast(st[b + PF_STATUS])); // the flags byte carries the pane's key number + 1 in its high nibble (0 = no key); bits 0 to 2 as before const kshown : u32 = pane_key_shown(st, @as(usize, i), @as(u32, @intCast(st[b + PF_AGENT]))); - at = put_list_entry(mem[O_OUT .. O_OUT + 128], at - O_OUT, i, @as(u32, @intCast(st[b + PF_AGENT])), shown_state(w), list_flags_key(st[b + PF_LIVE] != 0, w, kshown)) + O_OUT; + at = put_list_entry(mem[O_OUT .. O_OUT + 128], at - O_OUT, i, @as(u32, @intCast(st[b + PF_AGENT])), list_shown(st, @as(usize, i), @as(u32, @intCast(st[b + PF_AGENT])), shown_state(w), now_ms(mem)), list_flags_key(st[b + PF_LIVE] != 0, w, kshown)) + O_OUT; } i = i + 1; } @@ -1222,7 +1259,10 @@ fn run_ctl(st: []u64, mem: []u8, verb: u8) -> void { say(" agent "); print_dec(@as(u64, out[at + 1]), mem); say(" state "); - print_dec(@as(u64, out[at + 2]), mem); + print_dec(@as(u64, shown_state_of(@as(u32, out[at + 2]))), mem); + say(" light "); + const ln : usize = light_name(shown_light_of(@as(u32, out[at + 2])), mem[O_TMP1 + 3900 .. O_TMP1 + 3930]); + sys_write(1, mem[O_TMP1 + 3900 .. O_TMP1 + 3900 + ln], @as(u64, ln)); // bits 0 to 2 of the flags byte as before; its high nibble is the key number (1 to 8) the pane holds say(" flags "); print_dec(@as(u64, out[at + 3] % 16), mem); diff --git a/specs/t27os/pty_sys.t27 b/specs/t27os/pty_sys.t27 index 5cbae7fc5f..faf777c5b4 100644 --- a/specs/t27os/pty_sys.t27 +++ b/specs/t27os/pty_sys.t27 @@ -49,6 +49,9 @@ pub fn sys_random(b: []u8, n: u64) -> void { @extern("arc4random_buf"); } /// The address of the `environ` variable (a char***): read twice to walk the process environment. pub fn sys_environ() -> u64 { return @extern("_NSGetEnviron"); } pub fn sys_unsetenv(name: []const u8) -> i32 { return @extern("unsetenv"); } +/// posix_spawn(&pid, path, file_actions, attr, argv, envp): starts a program without waiting for it. pid is the +/// 4-byte result (pid[0]); argv holds up to 7 words and a 0; file_actions, attr and envp are 0 for none. +pub fn sys_spawn(pid: [2]u32, path: []const u8, fa: u64, attr: u64, argv: [8]u64, envp: u64) -> i32 { return @extern("posix_spawn"); } pub fn sys_exe_path(b: []u8, size: [2]u32) -> i32 { return @extern("_NSGetExecutablePath"); } test the_imports_are_declared { diff --git a/specs/t27os/queen.t27 b/specs/t27os/queen.t27 index bbdec42db4..6fd01460ce 100644 --- a/specs/t27os/queen.t27 +++ b/specs/t27os/queen.t27 @@ -76,9 +76,11 @@ pub const C_NEW_SPACE : u32 = 17; pub const C_CLOSE_SPACE : u32 = 18; pub const C_REPO_LINK : u32 = 19; pub const C_QUEEN_EDITED : u32 = 20; -pub const C_COUNT : u32 = 21; +pub const C_QUEEN_STARTED : u32 = 21; +pub const C_QUEEN_ASKED : u32 = 22; +pub const C_COUNT : u32 = 23; pub const C_NONE : u32 = 4294967295; -pub const CONTROL_NAMES : str = "tapped a cell,+ new chat,closed a chat,Queen row,picked a space,picked a chat,sidebar,dashboard,Queen panel,confirmed,dismissed,GOD MODE on,GOD MODE off,she opened,she picked,went home,opened a screen,+ new space,closed a space,opened the repo,she opened the editor"; +pub const CONTROL_NAMES : str = "tapped a cell,+ new chat,closed a chat,Queen row,picked a space,picked a chat,sidebar,dashboard,Queen panel,confirmed,dismissed,GOD MODE on,GOD MODE off,she opened,she picked,went home,opened a screen,+ new space,closed a space,opened the repo,she opened the editor,she started a lane,she asked the owner"; /// What an event's argument names. pub const ARG_NONE : u32 = 0; @@ -265,7 +267,7 @@ pub fn arg_kind(control: u32) -> u32 { /// The Queen's own actions, so the log can tell them from the user's. pub fn by_queen(control: u32) -> bool { - return control == C_QUEEN_OPENED or control == C_QUEEN_PICKED or control == C_QUEEN_EDITED; + return control == C_QUEEN_OPENED or control == C_QUEEN_PICKED or control == C_QUEEN_EDITED or control == C_QUEEN_STARTED or control == C_QUEEN_ASKED; } /// The view a frame is on: the open world, or home. diff --git a/specs/t27os/queen_board.t27 b/specs/t27os/queen_board.t27 new file mode 100644 index 0000000000..a93cd677ee --- /dev/null +++ b/specs/t27os/queen_board.t27 @@ -0,0 +1,1068 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/t27os/queen_board.t27 -- the live board of the Q page: see the Queen work and improve the system in GOD MODE +; Owner, 2026-10-20: in GOD MODE we must SEE her working and improving the system live. Data model only (pure, +; tested); the window draws it later. Everything on the page is a widget (nested rects, see LAYOUT). Closes #11275. +; ASCII only (L3), English only (LANG-EN). +; -- +; DRY: nothing is copied. Gaps, priority, why-not and the caps come from evolve.t27 (used); the ring and its +; controls from queen.t27 (used); the lamp aggregates from domain_pulse.t27 (used); the counters of the meter from +; agent_parts.t27 (used); the packer from dash.t27 (used, read only). Only genuinely new data lives here: the feed +; kinds, the board rows, the meter's provenance and the page layout. +; -- +; WHAT THE HOST MUST FEED (this file runs no command), and when: +; RING the queen.t27 ring (8 u64 events), read from memory each frame; no command. +; LANE LOG lines "sec TAB kind TAB letter TAB text LF", kind = FK_* below (task opened, lane started, test +; verdict, landed); written by the host when a lane changes state; text is a profile name or a short +; sentence, NEVER a key (profile_ok refuses a key-looking text). Newest line last in the file. +; PANEL the evolve.t27 Panel (masks), the in-flight mask, running, started_hour, risk mask+class: each time +; the panel refreshes (same cadence as the lamps). +; LAMPS `git log --since=7.days --name-only --format=%x1e%H%x1f%ct%x1f%s` plus the red-path list, ONCE A +; MINUTE, only while the window is open (lamp_refresh_due). domain_pulse.t27 is used directly. +; METER six counters at each refresh, commands in meter_cmd; the first refresh after LOCAL midnight is the +; "start of day" snapshot (snapshot_due). +; SAFETY tasks this hour, agents running, god flag, and the key tier name as a TEXT ENUM from the keys lane +; (never a key, never a prefix of a key). + +module T27osQueenBoard; + +use tools::t27o::rust_std::alnum; +use t27os::queen::ev_control; +use t27os::queen::ev_arg; +use t27os::queen::ev_sec; +use t27os::queen::C_QUEEN_PICKED; +use t27os::queen::C_QUEEN_STARTED; +use t27os::queen::C_QUEEN_ASKED; +use t27os::queen::C_QUEEN_OPENED; +use t27os::queen::C_QUEEN_EDITED; +use t27os::queen::C_GOD_ON; +use t27os::queen::C_GOD_OFF; +use t27os::queen::C_NONE; +use t27os::queen::ring_len; +use t27os::queen::ring_at; +use t27os::queen::ring_next; +use t27os::queen::ev_pack; +use t27os::queen::VIEW_HOME; +use t27os::evolve::Panel; +use t27os::evolve::bit; +use t27os::evolve::has; +use t27os::evolve::why_not; +use t27os::evolve::next_action; +use t27os::evolve::touches_forbidden; +use t27os::evolve::dec_kind; +use t27os::evolve::dec_letter; +use t27os::evolve::dec_action; +use t27os::evolve::kind_phrase; +use t27os::evolve::letter_char; +use t27os::evolve::LETTERS; +use t27os::evolve::LETTER_RED; +use t27os::evolve::LETTER_UNOWNED; +use t27os::evolve::G_RED; +use t27os::evolve::G_BLOCKED; +use t27os::evolve::G_STALE; +use t27os::evolve::G_UNOWNED; +use t27os::evolve::G_NO_INSTRUMENT; +use t27os::evolve::G_NEVER_PINNED; +use t27os::evolve::G_NO_WIDGET; +use t27os::evolve::D_TASK; +use t27os::evolve::D_REQUEST; +use t27os::evolve::D_NONE; +use t27os::evolve::R_OK; +use t27os::evolve::R_GOD_OFF; +use t27os::evolve::R_PANEL_CLOSED; +use t27os::evolve::R_AGENT_CAP; +use t27os::evolve::R_BUDGET; +use t27os::agent_parts::letters_without_widgets; +use t27os::dash::pack_cols; +use t27os::dash::cell_w; +use t27os::dash::dash_cell_x; +use t27os::dash::dash_cell_y; +use t27os::dash::pack_h; +use t27os::dash::pack_at; +use t27os::dash::clampf; + +pub const NL : u32 = 10; +pub const TAB : u32 = 9; +pub const NOT_MEASURED : u32 = 4294967295; + +fn fl(x: u32) -> f64 { return @as(f64, @floatFromInt(x)); } + +pub fn digits(n: u32) -> u32 { + var d : u32 = 1; + var v : u32 = n; + while (v >= 10) { v = v / 10; d = d + 1; } + return d; +} + +// ================================================================ 1. FEED + +// kinds of a feed entry, in FEED_WORDS order +pub const FK_NONE : u32 = 0; +pub const FK_PICKED : u32 = 1; +pub const FK_TASK : u32 = 2; +pub const FK_LANE : u32 = 3; +pub const FK_PASS : u32 = 4; +pub const FK_FAIL : u32 = 5; +pub const FK_LANDED : u32 = 6; +pub const FK_ASKED : u32 = 7; +pub const FK_GOD : u32 = 8; +pub const FK_WORKED : u32 = 9; +pub const FK_COUNT : u32 = 10; +pub const FEED_WORDS : str = "-,picked a gap,opened a task,started a lane,tests passed,tests failed,landed,asked the owner,GOD MODE,worked"; +pub const FEED_TEXT_MAX : u32 = 56; +pub const SRC_RING : u32 = 0; +pub const SRC_LANE : u32 = 1; +pub const SRC_NONE : u32 = 2; + +/// The feed kind of a ring control; FK_NONE for the user's own actions (the feed is hers only). +pub fn feed_kind_of_control(c: u32) -> u32 { + if (c == C_QUEEN_PICKED) { return FK_PICKED; } + if (c == C_QUEEN_STARTED) { return FK_LANE; } + if (c == C_QUEEN_ASKED) { return FK_ASKED; } + if (c == C_GOD_ON or c == C_GOD_OFF) { return FK_GOD; } + if (c == C_QUEEN_OPENED or c == C_QUEEN_EDITED) { return FK_WORKED; } + return FK_NONE; +} + +/// The letter of a ring event: the argument for her lane and her request (evolve.log_event), else 0. +pub fn feed_letter_of_event(e: u64) -> u32 { + const c : u32 = ev_control(e); + if (c == C_QUEEN_STARTED or c == C_QUEEN_ASKED) { return ev_arg(e); } + return 0; +} + +/// Fixed text of a ring entry (the ring carries no text), counted: head + letter_char. +pub fn ring_text_head(kind: u32) -> string { + if (kind == FK_PICKED) { return "picked a gap"; } + if (kind == FK_LANE) { return "started a lane for letter "; } + if (kind == FK_ASKED) { return "asked the owner about letter "; } + if (kind == FK_GOD) { return "GOD MODE switched"; } + if (kind == FK_WORKED) { return "worked in the editor"; } + return "-"; +} + +pub fn ring_text_len(kind: u32, letter: u32) -> u32 { + var n : u32 = ring_text_head(kind).len as u32; + if (letter != 0 and (kind == FK_LANE or kind == FK_ASKED)) { n = n + (letter_char(letter).len as u32); } + return n; +} + +/// Newest first merge of the two sources: SRC_RING or SRC_LANE for the newer next entry, SRC_NONE when both ran +/// out. A sec of 0 with `left == 0` means that source is empty. Ties go to the lane log (it is the more specific). +pub fn merge_pick(ring_left: u32, ring_sec: u64, lane_left: u32, lane_sec: u64) -> u32 { + if (ring_left == 0 and lane_left == 0) { return SRC_NONE; } + if (ring_left == 0) { return SRC_LANE; } + if (lane_left == 0) { return SRC_RING; } + if (ring_sec > lane_sec) { return SRC_RING; } + return SRC_LANE; +} + +/// Length of the shown text (counted) and whether "..." follows. +pub fn feed_text_len(len: u32) -> u32 { + if (len > FEED_TEXT_MAX) { return FEED_TEXT_MAX; } + return len; +} +pub fn feed_dots(len: u32) -> bool { return len > FEED_TEXT_MAX; } + +fn has_sub(t: string, p: string) -> bool { + const n : u32 = t.len as u32; + const m : u32 = p.len as u32; + if (m == 0 or n < m) { return false; } + var i : u32 = 0; + while (i + m <= n) { + var j : u32 = 0; + while (j < m) { + if ((t[i + j] as u32) != (p[j] as u32)) { break; } + j = j + 1; + } + if (j == m) { return true; } + i = i + 1; + } + return false; +} + +/// A text that looks like a key or a token: known prefixes, a "key=" form, or 32+ letters/digits in a row. +pub fn looks_like_key(t: string) -> bool { + if (has_sub(t, "sk-") or has_sub(t, "gsk_") or has_sub(t, "ghp_") or has_sub(t, "github_pat_")) { return true; } + if (has_sub(t, "Bearer ") or has_sub(t, "api_key") or has_sub(t, "token=") or has_sub(t, "KEY=")) { return true; } + var run : u32 = 0; + var i : u32 = 0; + const n : u32 = t.len as u32; + while (i < n) { + if (alnum(t[i])) { run = run + 1; if (run >= 32) { return true; } } else { run = 0; } + i = i + 1; + } + return false; +} + +/// Only a profile name or a plain sentence enters the feed; the window shows the placeholder otherwise. +pub fn profile_ok(t: string) -> bool { return looks_like_key(t) == false; } +pub const KEY_PLACEHOLDER : string = "[profile hidden]"; + +fn line_end(log: string, p: u32) -> u32 { + const n : u32 = log.len as u32; + var q : u32 = p; + while (q < n) { + if ((log[q] as u32) == NL) { break; } + q = q + 1; + } + return q; +} + +/// End of the TAB field that starts at p, bounded by the line end e. +fn field_end(log: string, p: u32, e: u32) -> u32 { + var q : u32 = p; + while (q < e) { + if ((log[q] as u32) == TAB) { break; } + q = q + 1; + } + return q; +} + +fn num_in(log: string, a: u32, b: u32) -> u64 { + var v : u64 = 0; + var i : u32 = a; + while (i < b) { + const c : u32 = log[i] as u32; + if (c >= 48 and c <= 57) { v = v * 10 + @as(u64, c - 48); } + i = i + 1; + } + return v; +} + +/// Start offset of lane line k (0 = the newest = the last line), or NOT_MEASURED when there is none. +pub fn lane_line_start(log: string, k: u32) -> u32 { + const n : u32 = log.len as u32; + if (n == 0) { return NOT_MEASURED; } + var end : u32 = n; + if ((log[n - 1] as u32) == NL) { end = n - 1; } + var left : u32 = k; + while (true) { + var s : u32 = end; + while (s > 0) { + if ((log[s - 1] as u32) == NL) { break; } + s = s - 1; + } + if (s == end) { return NOT_MEASURED; } + if (left == 0) { return s; } + if (s == 0) { return NOT_MEASURED; } + left = left - 1; + end = s - 1; + } + return NOT_MEASURED; +} + +pub fn lane_lines(log: string) -> u32 { + var k : u32 = 0; + while (lane_line_start(log, k) != NOT_MEASURED) { k = k + 1; if (k > 4096) { break; } } + return k; +} + +pub fn lane_sec(log: string, s: u32) -> u64 { return num_in(log, s, field_end(log, s, line_end(log, s))); } + +pub fn lane_kind(log: string, s: u32) -> u32 { + const e : u32 = line_end(log, s); + const a : u32 = field_end(log, s, e) + 1; + return num_in(log, a, field_end(log, a, e)) as u32; +} + +pub fn lane_letter(log: string, s: u32) -> u32 { + const e : u32 = line_end(log, s); + const a : u32 = field_end(log, s, e) + 1; + const b : u32 = field_end(log, a, e) + 1; + return num_in(log, b, field_end(log, b, e)) as u32; +} + +/// Start of the text field and its length; the text is the rest of the line after the third TAB. +pub fn lane_text_start(log: string, s: u32) -> u32 { + const e : u32 = line_end(log, s); + const a : u32 = field_end(log, s, e) + 1; + const b : u32 = field_end(log, a, e) + 1; + return field_end(log, b, e) + 1; +} + +pub fn lane_text_len(log: string, s: u32) -> u32 { + const e : u32 = line_end(log, s); + const t : u32 = lane_text_start(log, s); + if (t >= e) { return 0; } + return e - t; +} + +/// A lane text is shown only when it carries no key; the length shown otherwise is the placeholder's. +pub fn lane_text_shown_len(log: string, s: u32) -> u32 { + const t : u32 = lane_text_start(log, s); + const n : u32 = lane_text_len(log, s); + var i : u32 = 0; + var run : u32 = 0; + var bad : bool = false; + while (i < n) { + if (alnum(log[t + i])) { run = run + 1; if (run >= 32) { bad = true; } } else { run = 0; } + i = i + 1; + } + if (bad) { return KEY_PLACEHOLDER.len as u32; } + return feed_text_len(n); +} + +// ================================================================ 2. GAPS BOARD + +pub const GAP_ROWS : u32 = 10; +pub const P_CRIT : u32 = 0; +pub const P_HIGH : u32 = 1; +pub const P_MED : u32 = 2; +pub const P_LOW : u32 = 3; +pub const CLASS_WORDS : str = "P0,P1,P2,P3"; + +// why a row is or is not started now +pub const WN_NEXT : u32 = 0; +pub const WN_QUEUED : u32 = 1; +pub const WN_INFLIGHT : u32 = 2; +pub const WN_GOD_OFF : u32 = 3; +pub const WN_PANEL : u32 = 4; +pub const WN_CAP : u32 = 5; +pub const WN_BUDGET : u32 = 6; +pub const WN_OWNER : u32 = 7; +pub const WN_COUNT : u32 = 8; +pub const WN_WORDS : str = "next,queued,in flight,GOD MODE off,panel closed,agent cap 3,budget per hour,owner request"; + +pub fn class_of_kind(kind: u32) -> u32 { + if (kind == G_RED) { return P_CRIT; } + if (kind == G_BLOCKED) { return P_HIGH; } + if (kind == G_STALE or kind == G_UNOWNED) { return P_MED; } + return P_LOW; +} + +/// The letters that have a gap of this kind (evolve.t27 gap_kind order). +pub fn kind_mask(p: Panel, kind: u32) -> u32 { + if (kind == G_RED) { if (p.master_red) { return bit(LETTER_RED); } return 0; } + if (kind == G_BLOCKED) { return p.blocked; } + if (kind == G_STALE) { return p.stale; } + if (kind == G_UNOWNED) { if (p.unowned_count > 0) { return bit(LETTER_UNOWNED); } return 0; } + if (kind == G_NO_INSTRUMENT) { return p.no_instrument; } + if (kind == G_NEVER_PINNED) { return p.never_pinned; } + if (kind == G_NO_WIDGET) { if (p.no_widget_count > 0) { return p.no_widget; } return 0; } + return 0; +} + +pub fn popcount(m: u32) -> u32 { + var n : u32 = 0; + var l : u32 = 1; + while (l <= LETTERS) { if (has(m, l)) { n = n + 1; } l = l + 1; } + return n; +} + +/// The k-th set letter of a mask (0 = lowest), 0 when none. +pub fn nth_letter(m: u32, k: u32) -> u32 { + var left : u32 = k; + var l : u32 = 1; + while (l <= LETTERS) { + if (has(m, l)) { + if (left == 0) { return l; } + left = left - 1; + } + l = l + 1; + } + return 0; +} + +/// The n-th gap, most urgent first: kind + 16 * letter, 0 when there is none. All gaps, in flight or not. +pub fn nth_gap(p: Panel, n: u32) -> u32 { + var left : u32 = n; + var k : u32 = G_RED; + while (k <= G_NO_WIDGET) { + const m : u32 = kind_mask(p, k); + const c : u32 = popcount(m); + if (left < c) { return k + 16 * nth_letter(m, left); } + left = left - c; + k = k + 1; + } + return 0; +} + +pub fn gap_kind_of(g: u32) -> u32 { return g % 16; } +pub fn gap_letter_of(g: u32) -> u32 { return g / 16; } + +pub fn gap_total(p: Panel) -> u32 { + var n : u32 = 0; + var k : u32 = G_RED; + while (k <= G_NO_WIDGET) { n = n + popcount(kind_mask(p, k)); k = k + 1; } + return n; +} + +/// Row shown on the board for the n-th gap: false past the end or past the top-10. +pub fn row_exists(p: Panel, n: u32) -> bool { + if (n >= GAP_ROWS) { return false; } + return nth_gap(p, n) != 0; +} + +/// Index of the first gap not in flight, i.e. the one next_action would take, or NOT_MEASURED when none. +pub fn next_index(p: Panel, inflight: u32) -> u32 { + var n : u32 = 0; + while (nth_gap(p, n) != 0) { + if (has(inflight, gap_letter_of(nth_gap(p, n))) == false) { return n; } + n = n + 1; + } + return NOT_MEASURED; +} + +/// Why row n is not started now (WN_*), mirroring evolve.t27 exactly: the gates first, then in flight, then a +/// forbidden class (an owner request, which needs no slot), then the caps, then next / queued. +pub fn row_why(p: Panel, n: u32, inflight: u32, god: bool, panel_open: bool, running: u32, started_hour: u32, risk_mask: u32, risk_class: u32) -> u32 { + const g : u32 = nth_gap(p, n); + if (g == 0) { return WN_QUEUED; } + const letter : u32 = gap_letter_of(g); + const w : u32 = why_not(god, panel_open, running, started_hour); + if (w == R_GOD_OFF) { return WN_GOD_OFF; } + if (w == R_PANEL_CLOSED) { return WN_PANEL; } + if (has(inflight, letter)) { return WN_INFLIGHT; } + if (has(risk_mask, letter) and touches_forbidden(risk_class)) { return WN_OWNER; } + if (w == R_AGENT_CAP) { return WN_CAP; } + if (w == R_BUDGET) { return WN_BUDGET; } + if (n == next_index(p, inflight)) { return WN_NEXT; } + return WN_QUEUED; +} + +/// Reason text of a row = kind_phrase of evolve; its counted length. +pub fn row_reason_len(g: u32) -> u32 { return kind_phrase(gap_kind_of(g)).len as u32; } + +// ================================================================ 3. DOMAIN LAMPS LIVE + +pub const LAMP_COUNT : u32 = 27; +pub const LAMP_PERIOD : u32 = 60; +pub const LAMP_STALE_AFTER : u32 = 180; + +/// Refresh rule: at most once per 60 s and only while the window is open. last = 0 means never refreshed; +/// a clock that went back refreshes at once. +pub fn lamp_refresh_due(window_open: bool, now: u64, last: u64) -> bool { + if (window_open == false) { return false; } + if (last == 0) { return true; } + if (now < last) { return true; } + return now - last >= (LAMP_PERIOD as u64); +} + +/// Seconds to the next refresh, 0 when due or the window is closed. +pub fn lamp_next_in(window_open: bool, now: u64, last: u64) -> u32 { + if (lamp_refresh_due(window_open, now, last)) { return 0; } + if (window_open == false) { return 0; } + return (LAMP_PERIOD as u64 - (now - last)) as u32; +} + +/// The strip's data went three periods without a refresh: the window dims it and says so. +pub fn lamps_stale(now: u64, last: u64) -> bool { + if (last == 0 or now < last) { return false; } + return now - last >= (LAMP_STALE_AFTER as u64); +} + +// domain_pulse.t27 cannot be `use`d here: it splices 27 agents::X::KEY_FILES modules and the splice drops the +// constants of every other `use` (MAX_AGENTS "undeclared", measured 2026-10-20). So the host (glass_window, which +// does link domain_pulse) feeds the PER-LETTER AGGREGATES, one line each, and this file decides nothing twice: +// "letter TAB status TAB commits24 TAB guess LF" +// status = domain_pulse.status_of(...) as 1 QUIET, 2 WARM, 3 ACTIVE, 4 BLOCKED (its ST_* values, unchanged) +// commits24 = domain_pulse.commits_24h; guess = 1 when domain_pulse.letter_label is GUESS (OPEN files exist) +pub const LS_NONE : u32 = 0; +pub const LS_QUIET : u32 = 1; +pub const LS_WARM : u32 = 2; +pub const LS_ACTIVE : u32 = 3; +pub const LS_BLOCKED : u32 = 4; + +/// Start offset of the line of this letter in the aggregate text, or NOT_MEASURED (the lamp is drawn dark). +pub fn lamp_line(agg: string, letter: u32) -> u32 { + const n : u32 = agg.len as u32; + var s : u32 = 0; + while (s < n) { + const e : u32 = line_end(agg, s); + if (num_in(agg, s, field_end(agg, s, e)) == (letter as u64)) { return s; } + s = e + 1; + } + return NOT_MEASURED; +} + +fn lamp_field(agg: string, letter: u32, k: u32) -> u64 { + const s : u32 = lamp_line(agg, letter); + if (s == NOT_MEASURED) { return 0; } + const e : u32 = line_end(agg, s); + var a : u32 = s; + var i : u32 = 0; + while (i < k) { a = field_end(agg, a, e) + 1; i = i + 1; } + return num_in(agg, a, field_end(agg, a, e)); +} + +pub fn lamp_status(agg: string, letter: u32) -> u32 { + if (lamp_line(agg, letter) == NOT_MEASURED) { return LS_NONE; } + return lamp_field(agg, letter, 1) as u32; +} +pub fn lamp_commits(agg: string, letter: u32) -> u64 { return lamp_field(agg, letter, 2); } +pub fn lamp_is_guess(agg: string, letter: u32) -> bool { return lamp_field(agg, letter, 3) != 0; } + +// ================================================================ 4. SELF-IMPROVEMENT METER + +pub const M_LINKS : u32 = 0; +pub const M_TESTS : u32 = 1; +pub const M_UNOWNED : u32 = 2; +pub const M_NO_INSTRUMENT : u32 = 3; +pub const M_UNASSIGNED : u32 = 4; +pub const M_STALE : u32 = 5; +pub const M_COUNT : u32 = 6; +pub const M_LABELS : str = "links landed today,tests,unowned paths,letters with no instrument,unassigned widgets,stale seals"; +pub const M_UNITS : str = "links,tests,unowned,no instrument,unassigned,stale"; + +/// Higher is better for links and tests; lower is better for the other four. +pub fn m_up_is_good(m: u32) -> bool { return m == M_LINKS or m == M_TESTS; } + +/// Provenance: how the host measures it. Every number is MEASURED by this command, never typed. +pub fn meter_cmd(m: u32) -> string { + if (m == M_LINKS) { return "git log master --since=midnight --grep='Closes #' --format=%H | wc -l"; } + if (m == M_TESTS) { return "grep -rc '^test ' specs --include=*.t27, summed"; } + if (m == M_UNOWNED) { return "domain_pulse open_count of letter 0 over the git log of 7 days"; } + if (m == M_NO_INSTRUMENT) { return "agent_parts letters_without_widgets()"; } + if (m == M_UNASSIGNED) { return "gallery ids (tri widget list) not in agent_parts LETTER_OF_WIDGET"; } + if (m == M_STALE) { return "t27c suite --repo-root . seal rows reported stale"; } + return "-"; +} + +/// The only counter this module can read itself, from the used spec. +pub fn live_no_instrument() -> u32 { return letters_without_widgets(); } + +/// The local day number of a time: the first refresh after local midnight starts a new day. +pub fn local_day(now: u64, tz_offset_s: i64) -> u64 { + const t : i64 = @as(i64, @intCast(now)) + tz_offset_s; + if (t < 0) { return 0; } + return @as(u64, @intCast(t)) / 86400; +} + +/// Take the start-of-day snapshot at this refresh? Never taken, or the local day changed since. +pub fn snapshot_due(now: u64, snap_at: u64, tz_offset_s: i64) -> bool { + if (snap_at == 0) { return true; } + return local_day(now, tz_offset_s) != local_day(snap_at, tz_offset_s); +} + +pub const D_SAME : u32 = 0; +pub const D_BETTER : u32 = 1; +pub const D_WORSE : u32 = 2; +pub const D_UNKNOWN : u32 = 3; + +/// Better, worse, same or unknown ("not measured": either side missing). +pub fn delta_state(m: u32, before: u32, after: u32) -> u32 { + if (before == NOT_MEASURED or after == NOT_MEASURED) { return D_UNKNOWN; } + if (before == after) { return D_SAME; } + if (after > before) { if (m_up_is_good(m)) { return D_BETTER; } return D_WORSE; } + if (m_up_is_good(m)) { return D_WORSE; } + return D_BETTER; +} + +pub fn delta_abs(before: u32, after: u32) -> u32 { + if (after >= before) { return after - before; } + return before - after; +} + +/// The sign character of the delta text: "+", "-" or "=". +pub fn delta_sign(before: u32, after: u32) -> string { + if (after > before) { return "+"; } + if (after < before) { return "-"; } + return "="; +} + +pub const TEXT_NOT_MEASURED : string = "not measured"; + +/// "+3 tests" = sign + digits + " " + unit; its counted length. Unit words are M_UNITS entries (host splits). +pub fn delta_text_len(m: u32, before: u32, after: u32, unit_len: u32) -> u32 { + if (before == NOT_MEASURED or after == NOT_MEASURED) { return TEXT_NOT_MEASURED.len as u32; } + if (before == after) { return 1 + 1 + 1 + unit_len; } + return 1 + digits(delta_abs(before, after)) + 1 + unit_len; +} + +/// The unit word length per counter (M_UNITS entry), so the text can be counted without splitting the CSV. +pub fn unit_len(m: u32) -> u32 { + if (m == M_LINKS) { return 5; } + if (m == M_TESTS) { return 5; } + if (m == M_UNOWNED) { return 7; } + if (m == M_NO_INSTRUMENT) { return 13; } + if (m == M_UNASSIGNED) { return 10; } + return 5; +} + +/// How many of the six moved the right way since the start of the day. +pub fn better_count(b0: u32, b1: u32, b2: u32, b3: u32, b4: u32, b5: u32, a0: u32, a1: u32, a2: u32, a3: u32, a4: u32, a5: u32) -> u32 { + var n : u32 = 0; + if (delta_state(M_LINKS, b0, a0) == D_BETTER) { n = n + 1; } + if (delta_state(M_TESTS, b1, a1) == D_BETTER) { n = n + 1; } + if (delta_state(M_UNOWNED, b2, a2) == D_BETTER) { n = n + 1; } + if (delta_state(M_NO_INSTRUMENT, b3, a3) == D_BETTER) { n = n + 1; } + if (delta_state(M_UNASSIGNED, b4, a4) == D_BETTER) { n = n + 1; } + if (delta_state(M_STALE, b5, a5) == D_BETTER) { n = n + 1; } + return n; +} + +// ================================================================ 5. SAFETY STRIP + +// key tier in use, a text enum from the keys lane (never the key) +pub const KT_NONE : u32 = 0; +pub const KT_FREE_ZAI : u32 = 1; +pub const KT_OR_FREE : u32 = 2; +pub const KT_PAID : u32 = 3; +pub const KT_WORDS : str = "no key,free z.ai flash,OpenRouter free,paid"; + +/// A paid tier is the only one the strip paints as a warning. +pub fn tier_is_warning(t: u32) -> bool { return t == KT_PAID; } +pub fn tier_ok(t: u32) -> bool { return t <= KT_PAID; } + +/// The caps are read off evolve.why_not, not copied: dash.t27 also declares MAX_AGENTS, so a bare use of the +/// name is ambiguous after the splice ("UNRESOLVED MAX_AGENTS ... ambiguous"). +pub fn agents_cap() -> u32 { + var n : u32 = 0; + while (n < 64) { + if (why_not(true, true, n, 0) == R_AGENT_CAP) { return n; } + n = n + 1; + } + return 0; +} +pub fn budget_cap() -> u32 { + var n : u32 = 0; + while (n < 64) { + if (why_not(true, true, 0, n) == R_BUDGET) { return n; } + n = n + 1; + } + return 0; +} +pub fn tasks_left(started_hour: u32) -> u32 { + if (started_hour >= budget_cap()) { return 0; } + return budget_cap() - started_hour; +} +pub fn agents_free(running: u32) -> u32 { + if (running >= agents_cap()) { return 0; } + return agents_cap() - running; +} + +/// Fill of a bar 0..1: used / cap. +pub fn bar_fill(used: u32, cap: u32) -> f64 { + if (cap == 0) { return 0.0; } + return clampf(fl(used) / fl(cap), 0.0, 1.0); +} + +/// STOP is GOD MODE off, the same as Cmd+J while she acts: it returns the control to log (C_GOD_OFF), or C_NONE +/// when GOD MODE is already off (nothing to stop). +pub fn stop_control(god: bool) -> u32 { + if (god) { return C_GOD_OFF; } + return C_NONE; +} + +/// What STOP changes: GOD MODE goes off, so evolve.next_action returns D_NONE and no new lane starts. It kills +/// nothing that already runs. +pub fn stop_effect(god: bool) -> string { + if (god) { return "GOD MODE off: no new lane starts; lanes already running are left alone"; } + return "GOD MODE is already off"; +} + +// ================================================================ 6. LAYOUT of the Q page (nested rects) + +pub const QN_PAGE : u32 = 0; +pub const QN_LAMPS : u32 = 1; +pub const QN_FEED : u32 = 2; +pub const QN_GAPS : u32 = 3; +pub const QN_METER : u32 = 4; +pub const QN_SAFETY : u32 = 5; +pub const QN_COUNT : u32 = 6; +pub const QN_NAMES : str = "Q PAGE,LAMPS,FEED,GAPS,METER,SAFETY"; + +pub const Q_PAD : f64 = 12.0; +pub const Q_GAP : f64 = 8.0; +pub const Q_HEAD : f64 = 20.0; +pub const LAMP_MIN_W : f64 = 44.0; +pub const LAMP_H : f64 = 40.0; +pub const LAMP_GAP : f64 = 4.0; +pub const BOTTOM_H : f64 = 132.0; +pub const FEED_FRAC : f64 = 0.42; +pub const FEED_ROW_H : f64 = 34.0; +pub const GAP_ROW_MIN : f64 = 30.0; +pub const METER_FRAC : f64 = 0.6; +pub const METER_COLS : u32 = 3; +pub const SAFETY_COLS : u32 = 2; +pub const SAFETY_CELLS : u32 = 4; +pub const SC_TASKS : u32 = 0; +pub const SC_AGENTS : u32 = 1; +pub const SC_TIER : u32 = 2; +pub const SC_STOP : u32 = 3; + +pub fn inner_w(pw: f64) -> f64 { return pw - 2.0 * Q_PAD; } + +pub fn lamp_cols(pw: f64) -> u32 { return pack_cols(inner_w(pw), LAMP_MIN_W, LAMP_GAP); } +pub fn lamps_h(pw: f64) -> f64 { return Q_HEAD + pack_h(LAMP_COUNT, lamp_cols(pw), LAMP_H, LAMP_GAP); } + +/// The body (FEED + GAPS) takes what is left between the lamp strip and the bottom row. +pub fn body_h(pw: f64, ph: f64) -> f64 { + const h : f64 = ph - 2.0 * Q_PAD - lamps_h(pw) - BOTTOM_H - 2.0 * Q_GAP; + if (h < 120.0) { return 120.0; } + return h; +} + +pub fn body_y(pw: f64) -> f64 { return Q_PAD + lamps_h(pw) + Q_GAP; } +pub fn bottom_y(pw: f64, ph: f64) -> f64 { return body_y(pw) + body_h(pw, ph) + Q_GAP; } + +pub fn feed_w(pw: f64) -> f64 { return (inner_w(pw) - Q_GAP) * FEED_FRAC; } +pub fn gaps_w(pw: f64) -> f64 { return inner_w(pw) - Q_GAP - feed_w(pw); } +pub fn meter_w(pw: f64) -> f64 { return (inner_w(pw) - Q_GAP) * METER_FRAC; } +pub fn safety_w(pw: f64) -> f64 { return inner_w(pw) - Q_GAP - meter_w(pw); } + +pub fn node_x(n: u32, pw: f64) -> f64 { + if (n == QN_PAGE) { return 0.0; } + if (n == QN_GAPS) { return Q_PAD + feed_w(pw) + Q_GAP; } + if (n == QN_SAFETY) { return Q_PAD + meter_w(pw) + Q_GAP; } + return Q_PAD; +} + +pub fn node_y(n: u32, pw: f64, ph: f64) -> f64 { + if (n == QN_PAGE) { return 0.0; } + if (n == QN_LAMPS) { return Q_PAD; } + if (n == QN_FEED or n == QN_GAPS) { return body_y(pw); } + return bottom_y(pw, ph); +} + +pub fn node_w(n: u32, pw: f64) -> f64 { + if (n == QN_PAGE) { return pw; } + if (n == QN_LAMPS) { return inner_w(pw); } + if (n == QN_FEED) { return feed_w(pw); } + if (n == QN_GAPS) { return gaps_w(pw); } + if (n == QN_METER) { return meter_w(pw); } + return safety_w(pw); +} + +pub fn node_h(n: u32, pw: f64, ph: f64) -> f64 { + if (n == QN_PAGE) { return ph; } + if (n == QN_LAMPS) { return lamps_h(pw); } + if (n == QN_FEED or n == QN_GAPS) { return body_h(pw, ph); } + return BOTTOM_H; +} + +/// The widget under (x, y) of the page, QN_PAGE when on none. +pub fn node_at(x: f64, y: f64, pw: f64, ph: f64) -> u32 { + var n : u32 = QN_LAMPS; + while (n < QN_COUNT) { + const nx : f64 = node_x(n, pw); + const ny : f64 = node_y(n, pw, ph); + if (x >= nx and x < nx + node_w(n, pw) and y >= ny and y < ny + node_h(n, pw, ph)) { return n; } + n = n + 1; + } + return QN_PAGE; +} + +// children, relative to the corner of their parent, packed with dash.t27 +pub fn lamp_x(i: u32, pw: f64) -> f64 { return dash_cell_x(i, inner_w(pw), lamp_cols(pw), LAMP_GAP); } +pub fn lamp_y(i: u32, pw: f64) -> f64 { return Q_HEAD + dash_cell_y(i, lamp_cols(pw), LAMP_H, LAMP_GAP); } +pub fn lamp_w(pw: f64) -> f64 { return cell_w(inner_w(pw), lamp_cols(pw), LAMP_GAP); } +pub fn lamp_at(x: f64, y: f64, pw: f64) -> u32 { + return pack_at(LAMP_COUNT, inner_w(pw), lamp_cols(pw), LAMP_H, LAMP_GAP, x, y - Q_HEAD); +} + +/// Feed rows that fit under the head of the FEED column. +pub fn feed_rows(pw: f64, ph: f64) -> u32 { + const room : f64 = body_h(pw, ph) - Q_HEAD; + if (room < FEED_ROW_H) { return 0; } + return @as(u32, @intFromFloat((room + LAMP_GAP) / (FEED_ROW_H + LAMP_GAP))); +} +pub fn feed_row_y(i: u32) -> f64 { return Q_HEAD + fl(i) * (FEED_ROW_H + LAMP_GAP); } + +/// GAPS rows: always GAP_ROWS, as tall as the column allows (never under GAP_ROW_MIN, then the column scrolls). +pub fn gap_row_h(pw: f64, ph: f64) -> f64 { + const h : f64 = (body_h(pw, ph) - Q_HEAD - fl(GAP_ROWS - 1) * LAMP_GAP) / fl(GAP_ROWS); + if (h < GAP_ROW_MIN) { return GAP_ROW_MIN; } + return h; +} +pub fn gap_row_y(i: u32, pw: f64, ph: f64) -> f64 { return Q_HEAD + fl(i) * (gap_row_h(pw, ph) + LAMP_GAP); } + +pub fn meter_cell_h() -> f64 { return (BOTTOM_H - Q_HEAD - LAMP_GAP) / 2.0; } +pub fn meter_cell_x(i: u32, pw: f64) -> f64 { return dash_cell_x(i, meter_w(pw), METER_COLS, LAMP_GAP); } +pub fn meter_cell_y(i: u32) -> f64 { return Q_HEAD + dash_cell_y(i, METER_COLS, meter_cell_h(), LAMP_GAP); } +pub fn meter_cell_w(pw: f64) -> f64 { return cell_w(meter_w(pw), METER_COLS, LAMP_GAP); } + +pub fn safety_cell_x(i: u32, pw: f64) -> f64 { return dash_cell_x(i, safety_w(pw), SAFETY_COLS, LAMP_GAP); } +pub fn safety_cell_y(i: u32) -> f64 { return Q_HEAD + dash_cell_y(i, SAFETY_COLS, meter_cell_h(), LAMP_GAP); } +pub fn safety_cell_w(pw: f64) -> f64 { return cell_w(safety_w(pw), SAFETY_COLS, LAMP_GAP); } + +/// Is (x, y) (relative to the SAFETY widget) on the STOP button? +pub fn on_stop(x: f64, y: f64, pw: f64) -> bool { + return pack_at(SAFETY_CELLS, safety_w(pw), SAFETY_COLS, meter_cell_h(), LAMP_GAP, x, y - Q_HEAD) == SC_STOP; +} + +// ================================================================ tests + +pub fn quiet() -> Panel { + return Panel { master_red: false, blocked: 0, stale: 0, unowned_count: 0, no_instrument: 0, never_pinned: 0, no_widget_count: 0, no_widget: 0 }; +} + +fn busy() -> Panel { + var p : Panel = quiet(); + p.master_red = true; + p.blocked = bit(3) + bit(11); + p.stale = bit(12); + p.unowned_count = 4; + p.no_instrument = bit(7) + bit(20); + p.never_pinned = bit(4); + p.no_widget_count = 2; + p.no_widget = bit(9); + return p; +} + +test feed_keeps_only_her_actions_and_the_ring_merges_newest_first_with_the_lane_log { + assert feed_kind_of_control(C_QUEEN_STARTED) == FK_LANE; + assert feed_kind_of_control(C_QUEEN_ASKED) == FK_ASKED; + assert feed_kind_of_control(C_QUEEN_PICKED) == FK_PICKED; + assert feed_kind_of_control(C_GOD_ON) == FK_GOD and feed_kind_of_control(C_GOD_OFF) == FK_GOD; + assert feed_kind_of_control(0) == FK_NONE; + assert feed_kind_of_control(7) == FK_NONE; + const e : u64 = ev_pack(VIEW_HOME, C_QUEEN_STARTED, 12, 1000); + assert feed_letter_of_event(e) == 12 and ev_sec(e) == 1000; + assert feed_letter_of_event(ev_pack(VIEW_HOME, C_QUEEN_PICKED, 9, 5)) == 0; + assert ring_text_len(FK_LANE, 12) == 26 + 1; + assert ring_text_len(FK_LANE, 27) == 26 + 2; + assert ring_text_len(FK_GOD, 0) == 17; + assert merge_pick(2, 100, 2, 90) == SRC_RING; + assert merge_pick(2, 90, 2, 100) == SRC_LANE; + assert merge_pick(2, 100, 2, 100) == SRC_LANE; + assert merge_pick(0, 0, 3, 5) == SRC_LANE; + assert merge_pick(3, 5, 0, 0) == SRC_RING; + assert merge_pick(0, 0, 0, 0) == SRC_NONE; + assert ring_len(11) == 8 and ring_at(11, 0) == 2 and ring_next(10) == 2; + assert feed_text_len(10) == 10 and feed_text_len(200) == FEED_TEXT_MAX; + assert feed_dots(57) and feed_dots(56) == false; +} + +test the_lane_log_reads_newest_first_with_kind_letter_and_counted_text { + const log : string = "100\t2\t7\topened issue 11300\n130\t3\t7\tz.ai-flash\n190\t4\t7\t12 tests pass\n250\t6\t7\tlanded in PR 11301\n"; + assert lane_lines(log) == 4; + const s0 : u32 = lane_line_start(log, 0); + assert lane_sec(log, s0) == 250 and lane_kind(log, s0) == FK_LANDED and lane_letter(log, s0) == 7; + assert lane_text_len(log, s0) == 18; + const s2 : u32 = lane_line_start(log, 2); + assert lane_sec(log, s2) == 130 and lane_kind(log, s2) == FK_LANE; + assert lane_text_len(log, s2) == 10; + const s3 : u32 = lane_line_start(log, 3); + assert lane_kind(log, s3) == FK_TASK and lane_sec(log, s3) == 100; + assert lane_line_start(log, 4) == NOT_MEASURED; + assert lane_lines("") == 0; + assert lane_line_start("", 0) == NOT_MEASURED; + // a profile name is shown, a key-looking text is replaced by the placeholder + assert lane_text_shown_len(log, s2) == 10; + const bad : string = "300\t3\t7\tsk-abcdef0123\n"; + assert profile_ok("z.ai-flash") and profile_ok("OpenRouter free"); + assert looks_like_key("sk-abcdef0123") and looks_like_key("Bearer abc") and looks_like_key("ghp_abc"); + assert looks_like_key("api_key") and looks_like_key("token=1"); + assert looks_like_key("0123456789abcdef0123456789abcdef") and looks_like_key("0123456789abcdef") == false; + assert profile_ok("sk-abcdef0123") == false; + assert (KEY_PLACEHOLDER.len as u32) == 16; + const bad2 : string = "300\t3\t7\t0123456789abcdef0123456789abcdef0123\n"; + assert lane_text_shown_len(bad2, lane_line_start(bad2, 0)) == 16; + assert lane_text_shown_len(bad, lane_line_start(bad, 0)) == 13; +} + +test the_gaps_board_lists_the_top_ten_in_evolve_order { + const p : Panel = busy(); + assert gap_total(p) == 9; + assert nth_gap(p, 0) == G_RED + 16 * LETTER_RED; + assert nth_gap(p, 1) == G_BLOCKED + 16 * 3; + assert nth_gap(p, 2) == G_BLOCKED + 16 * 11; + assert nth_gap(p, 3) == G_STALE + 16 * 12; + assert nth_gap(p, 4) == G_UNOWNED + 16 * LETTER_UNOWNED; + assert nth_gap(p, 5) == G_NO_INSTRUMENT + 16 * 7; + assert nth_gap(p, 6) == G_NO_INSTRUMENT + 16 * 20; + assert nth_gap(p, 7) == G_NEVER_PINNED + 16 * 4; + assert nth_gap(p, 8) == G_NO_WIDGET + 16 * 9; + assert nth_gap(p, 9) == 0 and row_exists(p, 8) and row_exists(p, 9) == false; + assert gap_kind_of(nth_gap(p, 5)) == G_NO_INSTRUMENT and gap_letter_of(nth_gap(p, 6)) == 20; + assert gap_total(quiet()) == 0 and nth_gap(quiet(), 0) == 0; + assert popcount(bit(1) + bit(27)) == 2 and nth_letter(bit(5) + bit(9), 1) == 9; + assert class_of_kind(G_RED) == P_CRIT and class_of_kind(G_BLOCKED) == P_HIGH; + assert class_of_kind(G_STALE) == P_MED and class_of_kind(G_UNOWNED) == P_MED; + assert class_of_kind(G_NO_WIDGET) == P_LOW; + assert row_reason_len(nth_gap(p, 3)) == (kind_phrase(G_STALE).len as u32); +} + +test the_next_row_is_exactly_what_evolve_next_action_takes { + const p : Panel = busy(); + // nothing in flight: row 0 (red on master, letter B) + var inflight : u32 = 0; + var k : u32 = 0; + while (k < 4) { + const n : u32 = next_index(p, inflight); + const d : u32 = next_action(p, inflight, true, true, 0, 0, 0, 0); + assert dec_action(d) == D_TASK; + assert gap_kind_of(nth_gap(p, n)) == dec_kind(d); + assert gap_letter_of(nth_gap(p, n)) == dec_letter(d); + assert row_why(p, n, inflight, true, true, 0, 0, 0, 0) == WN_NEXT; + inflight = inflight + bit(dec_letter(d)); + k = k + 1; + } + assert next_index(quiet(), 0) == NOT_MEASURED; +} + +test a_row_says_why_it_is_not_started_now { + const p : Panel = busy(); + // row 0 is next, row 1 is queued + assert row_why(p, 0, 0, true, true, 0, 0, 0, 0) == WN_NEXT; + assert row_why(p, 1, 0, true, true, 0, 0, 0, 0) == WN_QUEUED; + // in flight + assert row_why(p, 0, bit(LETTER_RED), true, true, 1, 1, 0, 0) == WN_INFLIGHT; + assert row_why(p, 1, bit(LETTER_RED), true, true, 1, 1, 0, 0) == WN_NEXT; + // GOD MODE off / panel closed beat everything + assert row_why(p, 0, 0, false, true, 0, 0, 0, 0) == WN_GOD_OFF; + assert row_why(p, 0, 0, true, false, 0, 0, 0, 0) == WN_PANEL; + // caps: 3 agents, 4 tasks an hour + assert row_why(p, 0, 0, true, true, agents_cap(), 0, 0, 0) == WN_CAP; + assert row_why(p, 0, 0, true, true, 0, budget_cap(), 0, 0) == WN_BUDGET; + // forbidden class: an owner request, even with the caps hit (it starts no agent) + assert row_why(p, 1, 0, true, true, agents_cap(), budget_cap(), bit(3), 1) == WN_OWNER; + assert row_why(p, 2, 0, true, true, agents_cap(), budget_cap(), bit(3), 1) == WN_CAP; + assert row_why(p, 1, 0, false, true, 0, 0, bit(3), 1) == WN_GOD_OFF; + // a request row matches evolve: the decision is D_REQUEST + assert dec_action(next_action(quiet(), 0, true, true, 0, 0, 0, 0)) == D_NONE; + var b : Panel = quiet(); + b.blocked = bit(11); + assert dec_action(next_action(b, 0, true, true, 3, 4, bit(11), 1)) == D_REQUEST; + assert row_why(b, 0, 0, true, true, 3, 4, bit(11), 1) == WN_OWNER; + assert (WN_COUNT == 8) and WN_OWNER < WN_COUNT; +} + +test lamps_refresh_at_most_once_a_minute_and_only_while_the_window_is_open { + assert lamp_refresh_due(false, 1000, 0) == false; + assert lamp_refresh_due(true, 1000, 0); + assert lamp_refresh_due(true, 1059, 1000) == false; + assert lamp_refresh_due(true, 1060, 1000); + assert lamp_refresh_due(true, 900, 1000); + assert lamp_refresh_due(false, 5000, 1000) == false; + assert lamp_next_in(true, 1010, 1000) == 50; + assert lamp_next_in(true, 1060, 1000) == 0 and lamp_next_in(false, 1010, 1000) == 0; + assert lamps_stale(1179, 1000) == false and lamps_stale(1180, 1000); + assert lamps_stale(5, 0) == false; + assert LAMP_COUNT == 27; +} + +test lamps_read_the_per_letter_aggregates_the_host_feeds { + const agg : string = "1\t3\t4\t0\n2\t4\t0\t1\n27\t1\t0\t0\n"; + assert lamp_status(agg, 1) == LS_ACTIVE and lamp_commits(agg, 1) == 4 and lamp_is_guess(agg, 1) == false; + assert lamp_status(agg, 2) == LS_BLOCKED and lamp_is_guess(agg, 2); + assert lamp_status(agg, 27) == LS_QUIET; + // a letter the host did not feed is dark, not quiet + assert lamp_status(agg, 5) == LS_NONE and lamp_commits(agg, 5) == 0; + assert lamp_line("", 1) == NOT_MEASURED; + // letter 1 must not match line "10" or "12" + assert lamp_status("10\t3\t1\t0\n12\t2\t1\t0\n", 1) == LS_NONE; +} + +test the_meter_counts_before_and_after_with_the_right_direction { + assert m_up_is_good(M_LINKS) and m_up_is_good(M_TESTS); + assert m_up_is_good(M_UNOWNED) == false and m_up_is_good(M_STALE) == false; + assert delta_state(M_TESTS, 100, 103) == D_BETTER; + assert delta_state(M_UNOWNED, 40, 28) == D_BETTER; + assert delta_state(M_UNOWNED, 28, 40) == D_WORSE; + assert delta_state(M_LINKS, 5, 5) == D_SAME; + assert delta_state(M_STALE, 3, NOT_MEASURED) == D_UNKNOWN; + assert delta_abs(40, 28) == 12 and delta_abs(100, 103) == 3; + assert delta_sign(100, 103) == "+" and delta_sign(40, 28) == "-" and delta_sign(5, 5) == "="; + // '+3 tests' = 1 + 1 + 1 + 5 ; '-12 unowned' = 1 + 2 + 1 + 7 + assert delta_text_len(M_TESTS, 100, 103, unit_len(M_TESTS)) == 8; + assert delta_text_len(M_UNOWNED, 40, 28, unit_len(M_UNOWNED)) == 11; + assert delta_text_len(M_STALE, 3, NOT_MEASURED, unit_len(M_STALE)) == 12; + assert (TEXT_NOT_MEASURED.len as u32) == 12; + assert better_count(0, 100, 40, 20, 5, 3, 2, 103, 28, 20, 5, 4) == 3; + assert better_count(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1) == 0; + assert (meter_cmd(M_LINKS).len as u32) > 0 and (meter_cmd(M_STALE).len as u32) > 0; + assert (meter_cmd(M_COUNT).len as u32) == 1; + assert live_no_instrument() == letters_without_widgets(); + assert digits(0) == 1 and digits(9) == 1 and digits(10) == 2 and digits(1000) == 4; +} + +test the_start_of_day_snapshot_is_the_first_refresh_after_local_midnight { + const tz : i64 = 7200; + // local midnight of day 20000 at UTC+2 is 20000 * 86400 - 7200 in UTC seconds + const mid : u64 = 20000 * 86400 - 7200; + assert local_day(mid, tz) == 20000 and local_day(mid - 1, tz) == 19999; + assert snapshot_due(mid, 0, tz); + // the last minute before midnight and the first one after: a new snapshot + assert snapshot_due(mid, mid - 60, tz); + // later the same local day: none + assert snapshot_due(mid + 60, mid, tz) == false; + assert snapshot_due(mid + 86399, mid, tz) == false; + assert snapshot_due(mid + 86400, mid, tz); + // UTC day and local day differ by the offset + assert local_day(mid, 0) + 1 == local_day(mid, tz); + // a time before the epoch plus a big negative offset does not wrap + assert local_day(100, -86400 * 2) == 0; +} + +test the_safety_strip_counts_the_hour_the_agents_and_names_the_tier_without_a_key { + assert tasks_left(0) == budget_cap() and tasks_left(3) == 1 and tasks_left(9) == 0; + assert agents_free(0) == agents_cap() and agents_free(2) == 1 and agents_free(5) == 0; + assert bar_fill(2, 4) == 0.5 and bar_fill(9, 4) == 1.0 and bar_fill(1, 0) == 0.0; + assert tier_is_warning(KT_PAID) and tier_is_warning(KT_FREE_ZAI) == false; + assert tier_is_warning(KT_OR_FREE) == false and tier_ok(KT_PAID) and tier_ok(4) == false; + assert agents_cap() == 3 and budget_cap() == 4; +} + +test stop_is_god_mode_off_and_starts_nothing_afterwards { + assert stop_control(true) == C_GOD_OFF; + assert stop_control(false) == C_NONE; + assert (stop_effect(true).len as u32) > (stop_effect(false).len as u32); + // the effect, as the rule sees it: before STOP a task starts, after STOP evolve decides nothing + const p : Panel = busy(); + assert dec_action(next_action(p, 0, true, true, 0, 0, 0, 0)) == D_TASK; + assert next_action(p, 0, false, true, 0, 0, 0, 0) == D_NONE; + // the feed shows the same control as GOD MODE + assert feed_kind_of_control(stop_control(true)) == FK_GOD; +} + +test the_q_page_nests_lamps_on_top_feed_left_gaps_right_meter_and_safety_below { + const pw : f64 = 1200.0; + const ph : f64 = 800.0; + // lamps: a strip across the top, inside the padding + assert node_y(QN_LAMPS, pw, ph) == Q_PAD and node_w(QN_LAMPS, pw) == pw - 2.0 * Q_PAD; + assert lamp_cols(pw) >= 9; + // feed left, gaps right, same row, same height, no overlap + assert node_x(QN_FEED, pw) < node_x(QN_GAPS, pw); + assert node_x(QN_FEED, pw) + node_w(QN_FEED, pw) + Q_GAP == node_x(QN_GAPS, pw); + assert node_y(QN_FEED, pw, ph) == node_y(QN_GAPS, pw, ph) and node_h(QN_FEED, pw, ph) == node_h(QN_GAPS, pw, ph); + assert node_y(QN_FEED, pw, ph) >= node_y(QN_LAMPS, pw, ph) + node_h(QN_LAMPS, pw, ph); + // meter and safety below, side by side, ending inside the page + assert node_y(QN_METER, pw, ph) == node_y(QN_SAFETY, pw, ph); + assert node_y(QN_METER, pw, ph) >= node_y(QN_FEED, pw, ph) + node_h(QN_FEED, pw, ph); + assert node_x(QN_METER, pw) + node_w(QN_METER, pw) + Q_GAP == node_x(QN_SAFETY, pw); + assert node_y(QN_SAFETY, pw, ph) + node_h(QN_SAFETY, pw, ph) + Q_PAD <= ph; + assert node_x(QN_SAFETY, pw) + node_w(QN_SAFETY, pw) + Q_PAD == pw; + // hit test + assert node_at(30.0, 30.0, pw, ph) == QN_LAMPS; + assert node_at(30.0, node_y(QN_FEED, pw, ph) + 5.0, pw, ph) == QN_FEED; + assert node_at(node_x(QN_GAPS, pw) + 5.0, node_y(QN_GAPS, pw, ph) + 5.0, pw, ph) == QN_GAPS; + assert node_at(30.0, node_y(QN_METER, pw, ph) + 5.0, pw, ph) == QN_METER; + assert node_at(node_x(QN_SAFETY, pw) + 5.0, node_y(QN_SAFETY, pw, ph) + 5.0, pw, ph) == QN_SAFETY; + assert node_at(2.0, 2.0, pw, ph) == QN_PAGE; +} + +test the_children_are_packed_by_dash_and_stay_inside_their_parents { + const pw : f64 = 1200.0; + const ph : f64 = 800.0; + var i : u32 = 0; + while (i < LAMP_COUNT) { + assert lamp_x(i, pw) >= 0.0 and lamp_x(i, pw) + lamp_w(pw) <= node_w(QN_LAMPS, pw) + 0.001; + assert lamp_y(i, pw) + LAMP_H <= node_h(QN_LAMPS, pw, ph) + 0.001; + i = i + 1; + } + assert lamp_at(lamp_x(5, pw) + 1.0, lamp_y(5, pw) + 1.0, pw) == 5; + assert lamp_at(-1.0, 0.0, pw) == LAMP_COUNT; + assert feed_rows(pw, ph) >= 8; + assert feed_row_y(feed_rows(pw, ph) - 1) + FEED_ROW_H <= node_h(QN_FEED, pw, ph) + 0.001; + assert gap_row_h(pw, ph) >= GAP_ROW_MIN; + assert gap_row_y(GAP_ROWS - 1, pw, ph) + gap_row_h(pw, ph) <= node_h(QN_GAPS, pw, ph) + 0.001; + i = 0; + while (i < M_COUNT) { + assert meter_cell_x(i, pw) + meter_cell_w(pw) <= node_w(QN_METER, pw) + 0.001; + assert meter_cell_y(i) + meter_cell_h() <= BOTTOM_H + 0.001; + i = i + 1; + } + i = 0; + while (i < SAFETY_CELLS) { + assert safety_cell_x(i, pw) + safety_cell_w(pw) <= node_w(QN_SAFETY, pw) + 0.001; + assert safety_cell_y(i) + meter_cell_h() <= BOTTOM_H + 0.001; + i = i + 1; + } + // the STOP button is the fourth safety cell + assert on_stop(safety_cell_x(SC_STOP, pw) + 2.0, safety_cell_y(SC_STOP) + 2.0, pw); + assert on_stop(safety_cell_x(SC_TASKS, pw) + 2.0, safety_cell_y(SC_TASKS) + 2.0, pw) == false; + // a small window still gets a body at least 120 high + assert body_h(600.0, 500.0) >= 120.0; +} diff --git a/specs/t27os/share.t27 b/specs/t27os/share.t27 new file mode 100644 index 0000000000..91b90ed9fd --- /dev/null +++ b/specs/t27os/share.t27 @@ -0,0 +1,887 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/t27os/share.t27 -- share any session, document or "finish this task" through the TRI CLAW GitHub App, and +; the sign-in that starts the game. +; Owner, 2026-10-20: "we can SHARE any session or document, or share 'finish this task' in Claude Code; login goes +; through the GitHub App -- signing in starts the game, connecting TRI CLAW." Closes #11275. ASCII only (L3), English. +; Decisions only; the host (glass_window, pty server) moves bytes, calls GitHub and the Keychain. Nothing here reads a +; secret, and no test holds a real one (fake secrets are built at run time from a prefix and filler bytes). +; -- +; REUSE (no parallel spec): +; claw::github_surface every GitHub write a share makes names a ROW of that table and mints only that row's narrow +; token (APP_ID 5166765, 'tri-claw'): TASK new issue = findings-issues (issues write); TASK +; comment and SESSION comment = control-comments (issues write); TASK branch ref = autofix-commit +; (contents write); DOCUMENT permalink = spec-read (contents read). A gist is NOT an App surface +; (an installation token cannot create one), so SESSION goes out as an issue comment. +; t27os::session_store sid_ok (the one id charset), put_bytes. A pane's sid, cwd and model come from its .rec file. +; t27os::kanban COL_TODO: a received TASK lands there. +; t27os::evolve PTY_CAP, F_ALL. The share class F_SHARE (32) is added HERE by a use-level predicate, +; evolve_forbids(): evolve.t27 is untouched. Its touches_forbidden(F_SHARE) stays false while +; F_ALL is 31, so the host must call evolve_forbids() on the risk class before next_action; the +; one-line durable fix (F_ALL = 63) is evolve's owner's, and a test pins this gap. +; t27os::keys_host token storage is the Keychain via /usr/bin/security only; this spec only decides WHERE. +; Not found in the tree: a passport/game account spec for app.t27.ai (only hosting/topology.t27 names the host). The +; PASSPORT and PLAYING states are decisions about the server's answer; the server side is a gap (see the report). +; LINK https://app.t27.ai/s// kind = session | doc | task, id = sid_ok characters (letters, digits, "-"). +; LOGIN GitHub App device flow: POST github.com/login/device/code, user opens github.com/login/device and types the +; user code, POST github.com/login/oauth/access_token polled; then GET /user/installations decides whether the +; tri-claw App is installed; if not, a button opens github.com/apps/tri-claw/installations/new -- that IS 'connecting +; TRI CLAW'. Then the passport is found or created, then the first quest starts. + +module T27osShare; + +use lab::paths::lab_is_alnum; +use sync::schema::bytes_equal; +use claw::github_surface::row_of; +use claw::github_surface::perm_of; +use claw::github_surface::level_of; +use claw::github_surface::L_READ; +use claw::github_surface::L_WRITE; +use claw::recipes::str_eq; +use t27os::session_store::sid_ok; +use t27os::kanban::COL_TODO; +use t27os::evolve::F_ALL; +use t27os::evolve::PTY_CAP; +use t27os::evolve::touches_forbidden; + +// ---------------------------------------------------------------- 1. kinds, surfaces, link + +pub const K_SESSION : u32 = 0; +pub const K_DOCUMENT : u32 = 1; +pub const K_TASK : u32 = 2; +pub const K_COUNT : u32 = 3; +pub const K_BAD : u32 = 4294967295; + +pub const LINK_PREFIX : str = "https://app.t27.ai/s/"; +pub const ID_MAX : usize = 64; +pub const EXCERPT_MAX : usize = 4000; + +/// The GitHub App surface (a row of claw::github_surface) a share of this kind writes through. +/// existing: the TASK already has an issue (comment on it instead of opening one). +pub fn surface_of(kind: u32, existing: bool) -> str { + if (kind == K_TASK) { + if (existing) { return "control-comments"; } + return "findings-issues"; + } + if (kind == K_SESSION) { return "control-comments"; } + if (kind == K_DOCUMENT) { return "spec-read"; } + return ""; +} + +/// A TASK share also pushes its branch; that is the App's contents-write row. +pub const BRANCH_SURFACE : str = "autofix-commit"; + +/// An installation token cannot create a gist; SESSION goes out as an issue comment. +pub fn gist_possible_with_app() -> bool { + return false; +} + +/// The issue a new TASK share opens has exactly these four headings (the Queen's issue shape). +pub const ISSUE_HEADINGS : str = "Goal,Boundary,Acceptance,State"; + +pub fn kind_word(kind: u32) -> str { + if (kind == K_SESSION) { return "session"; } + if (kind == K_DOCUMENT) { return "doc"; } + if (kind == K_TASK) { return "task"; } + return ""; +} + +fn starts(s: []const u8, at: usize, lit: []const u8) -> bool { + if (lit.len == 0 or at + lit.len > s.len) { return false; } + var i : usize = 0; + while (i < lit.len) { + if (s[at + i] != lit[i]) { return false; } + i = i + 1; + } + return true; +} + +fn put1(out: []u8, o: usize, c: u8) -> usize { + if (o < out.len) { out[o] = c; return o + 1; } + return o; +} + +fn putn(out: []u8, o: usize, lit: []const u8) -> usize { + var p : usize = o; + var i : usize = 0; + while (i < lit.len) { + p = put1(out, p, lit[i]); + i = i + 1; + } + return p; +} + +/// Link ids are session_store's id charset, at most ID_MAX long: nothing a URL parser or a shell reads as more. +pub fn id_ok(id: []const u8) -> bool { + return id.len <= ID_MAX and sid_ok(id); +} + +/// Write the share link; returns its length, 0 when the kind or id is bad or out is too small. +pub fn emit_link(kind: u32, id: []const u8, out: []u8) -> usize { + if (kind >= K_COUNT or !id_ok(id)) { return 0; } + const w : str = kind_word(kind); + const need : usize = LINK_PREFIX.len + w.len + 1 + id.len; + if (need > out.len) { return 0; } + var o : usize = putn(out, 0, LINK_PREFIX); + o = putn(out, o, w); + o = put1(out, o, 47); + o = putn(out, o, id); + return o; +} + +/// Parse a share link into f = [kind, id_at, id_len]. Exact host, https only, no query, no fragment, no extra segment. +pub fn parse_link(s: []const u8, f: []u64) -> bool { + if (!starts(s, 0, LINK_PREFIX)) { return false; } + var at : usize = LINK_PREFIX.len; + var k : u32 = 0; + while (k < K_COUNT) { + const w : str = kind_word(k); + if (starts(s, at, w) and at + w.len < s.len and s[at + w.len] == 47) { + const id_at : usize = at + w.len + 1; + const id_len : usize = s.len - id_at; + if (!id_ok(s[id_at..s.len])) { return false; } + f[0] = k; + f[1] = id_at; + f[2] = id_len; + return true; + } + k = k + 1; + } + return false; +} + +// ---------------------------------------------------------------- 2. redaction + +pub const R_KEY : usize = 0; +pub const R_ENV : usize = 1; +pub const R_HOME : usize = 2; +pub const R_EMAIL : usize = 3; +pub const R_OVER : usize = 4; +pub const R_KINDS : usize = 5; +pub const RUN_MIN : usize = 32; +pub const MARK : str = "[REDACTED]"; +pub const MARK_EMAIL : str = "[email]"; + +/// Share sections. The keys.t27 profile (provider names, key counts, any key) is never a section of any share. +pub const SEC_AGENT : u32 = 0; +pub const SEC_MODEL : u32 = 1; +pub const SEC_REPO : u32 = 2; +pub const SEC_SID : u32 = 3; +pub const SEC_EXCERPT : u32 = 4; +pub const SEC_KEYS_PROFILE : u32 = 5; + +pub fn section_allowed(sec: u32) -> bool { + return sec < SEC_KEYS_PROFILE; +} + +fn is_digit(c: u8) -> bool { + return c >= 48 and c <= 57; +} + +fn is_hex(c: u8) -> bool { + return (c >= 48 and c <= 57) or (c >= 65 and c <= 70) or (c >= 97 and c <= 102); +} + +fn is_space(c: u8) -> bool { + return c == 32 or c == 9 or c == 10 or c == 13; +} + +fn upper(c: u8) -> u8 { + if (c >= 97 and c <= 122) { return c - 32; } + return c; +} + +/// Length of a key-like token starting at i: a known vendor prefix plus at least 8 token characters, else 0. +fn token_len(src: []const u8, i: usize) -> usize { + var pre : usize = 0; + if (starts(src, i, "sk-")) { pre = 3; } + if (starts(src, i, "ghp_") or starts(src, i, "gho_") or starts(src, i, "ghs_") or starts(src, i, "ghu_") or starts(src, i, "ghr_")) { pre = 4; } + if (starts(src, i, "github_pat_")) { pre = 11; } + if (starts(src, i, "xoxb-") or starts(src, i, "xoxp-")) { pre = 5; } + if (pre == 0) { return 0; } + var j : usize = i + pre; + while (j < src.len and (lab_is_alnum(src[j]) or src[j] == 95 or src[j] == 45)) { j = j + 1; } + if (j - (i + pre) < 8) { return 0; } + return j - i; +} + +/// A long base64-ish run (letters, digits, "+", then up to two "=") that has a digit. A bare 40- or 64-digit hex run is a +/// git sha and is kept. Returns the length to drop, 0 to keep. +fn run_len(src: []const u8, i: usize) -> usize { + var j : usize = i; + var digit : bool = false; + var hex : bool = true; + while (j < src.len and (lab_is_alnum(src[j]) or src[j] == 43)) { + if (is_digit(src[j])) { digit = true; } + if (!is_hex(src[j])) { hex = false; } + j = j + 1; + } + const n : usize = j - i; + if (n < RUN_MIN or !digit) { return 0; } + if (hex and (n == 40 or n == 64)) { return 0; } + var pad : usize = 0; + while (pad < 2 and j < src.len and src[j] == 61) { j = j + 1; pad = pad + 1; } + return j - i; +} + +/// "/Users/" or "/home/" at i: the length of that prefix (the name has at least one character), else 0. +fn home_len(src: []const u8, i: usize) -> usize { + var pre : usize = 0; + if (starts(src, i, "/Users/")) { pre = 7; } + if (starts(src, i, "/home/")) { pre = 6; } + if (pre == 0) { return 0; } + var j : usize = i + pre; + while (j < src.len and src[j] != 47 and !is_space(src[j])) { j = j + 1; } + if (j == i + pre) { return 0; } + return j - i; +} + +fn name_char(c: u8) -> bool { + return lab_is_alnum(c) or c == 95; +} + +fn contains_ci(name: []const u8, lit: []const u8) -> bool { + if (name.len < lit.len) { return false; } + var s : usize = 0; + while (s + lit.len <= name.len) { + var k : usize = 0; + while (k < lit.len and upper(name[s + k]) == lit[k]) { k = k + 1; } + if (k == lit.len) { return true; } + s = s + 1; + } + return false; +} + +/// The name before the "=" at i (letters, digits, "_") names a secret: it contains KEY, TOKEN or SECRET in any case. +fn secret_name_at(src: []const u8, i: usize) -> bool { + var j : usize = i; + while (j > 0 and name_char(src[j - 1])) { j = j - 1; } + const nm : []const u8 = src[j..i]; + return contains_ci(nm, "KEY") or contains_ci(nm, "TOKEN") or contains_ci(nm, "SECRET"); +} + +fn local_char(c: u8) -> bool { + return lab_is_alnum(c) or c == 46 or c == 95 or c == 37 or c == 43 or c == 45; +} + +/// Write src to out with secrets removed; returns the bytes written. counts[R_*] says how many of each kind were +/// removed (counts must hold R_KINDS). Rules: vendor-prefixed keys (sk-, ghp_ ... github_pat_ ..., xoxb-), long base64 runs, +/// the value of NAME=value when NAME holds KEY/TOKEN/SECRET, "/Users/" and "/home/" -> "~", e-mail addresses +/// (a bare "git@host" ssh remote is kept). R_OVER is 1 when out was too small and the tail was cut. +pub fn redact(src: []const u8, out: []u8, counts: []u32) -> usize { + var k : usize = 0; + while (k < R_KINDS) { counts[k] = 0; k = k + 1; } + var i : usize = 0; + var o : usize = 0; + while (i < src.len) { + const c : u8 = src[i]; + const edge : bool = i == 0 or !lab_is_alnum(src[i - 1]); + if (edge) { + const tl : usize = token_len(src, i); + if (tl > 0) { o = putn(out, o, MARK); counts[R_KEY] = counts[R_KEY] + 1; i = i + tl; continue; } + const rl : usize = run_len(src, i); + if (rl > 0) { o = putn(out, o, MARK); counts[R_KEY] = counts[R_KEY] + 1; i = i + rl; continue; } + const hl : usize = home_len(src, i); + if (hl > 0) { o = put1(out, o, 126); counts[R_HOME] = counts[R_HOME] + 1; i = i + hl; continue; } + } + if (c == 61 and i + 1 < src.len and !is_space(src[i + 1]) and secret_name_at(src, i)) { + var j : usize = i + 1; + const q : u8 = src[j]; + if (q == 34 or q == 39) { + j = j + 1; + while (j < src.len and src[j] != q) { j = j + 1; } + if (j < src.len) { j = j + 1; } + } else { + while (j < src.len and !is_space(src[j])) { j = j + 1; } + } + o = put1(out, o, 61); + o = putn(out, o, MARK); + counts[R_ENV] = counts[R_ENV] + 1; + i = j; + continue; + } + if (c == 64 and o > 0 and i + 1 < src.len and lab_is_alnum(src[i + 1])) { + var p : usize = o; + while (p > 0 and local_char(out[p - 1])) { p = p - 1; } + var q2 : usize = i + 1; + var dot : bool = false; + while (q2 < src.len and (lab_is_alnum(src[q2]) or src[q2] == 45 or src[q2] == 46)) { + if (src[q2] == 46) { dot = true; } + q2 = q2 + 1; + } + while (q2 > i + 1 and src[q2 - 1] == 46) { q2 = q2 - 1; } + const git : bool = o - p == 3 and out[p] == 103 and out[p + 1] == 105 and out[p + 2] == 116; + if (o > p and dot and q2 - (i + 1) >= 4 and !git) { + o = putn(out, p, MARK_EMAIL); + counts[R_EMAIL] = counts[R_EMAIL] + 1; + i = q2; + continue; + } + } + o = put1(out, o, c); + i = i + 1; + } + if (o >= out.len and i >= src.len and out.len > 0 and src.len > out.len) { counts[R_OVER] = 1; } + return o; +} + +pub fn redacted_total(counts: []const u32) -> u32 { + return counts[R_KEY] + counts[R_ENV] + counts[R_HOME] + counts[R_EMAIL]; +} + +/// The counted note a share shows when redaction removed anything; 0 bytes when nothing was removed. +pub fn note_text(counts: []const u32, out: []u8) -> usize { + const n : u32 = redacted_total(counts); + if (n == 0) { return 0; } + var o : usize = putn(out, 0, "redacted "); + o = put_dec_local(out, o, n); + return putn(out, o, " item(s) before sharing"); +} + +fn put_dec_local(out: []u8, at: usize, v: u32) -> usize { + var digits : u32 = 1; + var t : u32 = v; + while (t >= 10) { t = t / 10; digits = digits + 1; } + var x : u32 = v; + var i : u32 = digits; + while (i > 0) { + out[at + i - 1] = @as(u8, @intCast(48 + x % 10)); + x = x / 10; + i = i - 1; + } + return at + digits; +} + +// ---------------------------------------------------------------- 3. consent + +pub const F_SHARE : u32 = 32; + +pub const V_PRIVATE : u32 = 0; +pub const V_PUBLIC : u32 = 1; +pub const V_UNKNOWN : u32 = 2; + +pub const G_OK : u32 = 0; +pub const G_NEED_CLICK : u32 = 1; +pub const G_NEED_PREVIEW : u32 = 2; +pub const G_NEED_PUBLIC_OK : u32 = 3; +pub const G_NEED_NOTE_SEEN : u32 = 4; +pub const G_NEED_LOGIN : u32 = 5; + +/// GOD MODE may prepare a TASK share (a draft in the panel) and nothing else. +pub fn god_may_prepare(kind: u32) -> bool { + return kind == K_TASK; +} + +/// GOD MODE never sends a share: sending is the user's click. +pub fn god_may_send() -> bool { + return false; +} + +/// The evolve rule's forbidden check plus the share class. evolve::F_ALL is 31, so evolve's own touches_forbidden() does +/// not know F_SHARE; the host calls this one on the risk class and turns a hit into an OWNER REQUEST. +pub fn evolve_forbids(classes: u32) -> bool { + return touches_forbidden(classes) or (classes & F_SHARE) != 0; +} + +/// May the share go out? Gates in order. user_click: the user pressed the Share button widget (GOD MODE and loops pass +/// false). preview: the target repo and its visibility were shown. vis: V_*; unknown counts as public. public_ok: the +/// user confirmed a public target. redacted/note_seen: a redacted share needs its counted note seen. signed_in: login +/// state may_share(). +pub fn send_gate(signed_in: bool, user_click: bool, preview: bool, vis: u32, public_ok: bool, redacted: u32, note_seen: bool) -> u32 { + if (!user_click) { return G_NEED_CLICK; } + if (!signed_in) { return G_NEED_LOGIN; } + if (!preview) { return G_NEED_PREVIEW; } + if (vis != V_PRIVATE and !public_ok) { return G_NEED_PUBLIC_OK; } + if (redacted > 0 and !note_seen) { return G_NEED_NOTE_SEEN; } + return G_OK; +} + +// ---------------------------------------------------------------- 4. login = start the game + +pub const L_OUT : u32 = 0; +pub const L_REQUESTING : u32 = 1; +pub const L_CODE_SHOWN : u32 = 2; +pub const L_AUTHORIZED : u32 = 3; +pub const L_NEED_INSTALL : u32 = 4; +pub const L_CONNECTED : u32 = 5; +pub const L_PASSPORT : u32 = 6; +pub const L_PLAYING : u32 = 7; +pub const L_EXPIRED : u32 = 8; +pub const L_DENIED : u32 = 9; +pub const L_FAILED : u32 = 10; + +pub const E_START : u32 = 0; +pub const E_CODE_OK : u32 = 1; +pub const E_CODE_FAIL : u32 = 2; +pub const E_PENDING : u32 = 3; +pub const E_SLOW : u32 = 4; +pub const E_TOKEN_STORED : u32 = 5; +pub const E_TOKEN_REFUSED : u32 = 6; +pub const E_EXPIRED : u32 = 7; +pub const E_DENIED : u32 = 8; +pub const E_APP_FOUND : u32 = 9; +pub const E_APP_MISSING : u32 = 10; +pub const E_PASSPORT_OK : u32 = 11; +pub const E_PASSPORT_FAIL : u32 = 12; +pub const E_QUEST_STARTED : u32 = 13; +pub const E_SIGN_OUT : u32 = 14; + +/// GitHub's device-flow code lives 900 s; poll every 5 s, +5 s for each slow_down answer. +pub const DEVICE_EXPIRES_S : u32 = 900; +pub const POLL_BASE_S : u32 = 5; +pub const POLL_SLOW_ADD : u32 = 5; +pub const APP_SLUG : str = "tri-claw"; +pub const INSTALL_URL : str = "https://github.com/apps/tri-claw/installations/new"; +pub const DEVICE_URL : str = "https://github.com/login/device"; + +/// The one transition table. An event that makes no sense in a state leaves it unchanged. +pub fn step(state: u32, ev: u32) -> u32 { + if (ev == E_SIGN_OUT) { return L_OUT; } + if (ev == E_START and (state == L_OUT or state == L_EXPIRED or state == L_DENIED or state == L_FAILED)) { return L_REQUESTING; } + if (state == L_REQUESTING) { + if (ev == E_CODE_OK) { return L_CODE_SHOWN; } + if (ev == E_CODE_FAIL) { return L_FAILED; } + } + if (state == L_CODE_SHOWN) { + if (ev == E_TOKEN_STORED) { return L_AUTHORIZED; } + if (ev == E_TOKEN_REFUSED) { return L_FAILED; } + if (ev == E_EXPIRED) { return L_EXPIRED; } + if (ev == E_DENIED) { return L_DENIED; } + } + if (state == L_AUTHORIZED or state == L_NEED_INSTALL) { + if (ev == E_APP_FOUND) { return L_CONNECTED; } + if (ev == E_APP_MISSING) { return L_NEED_INSTALL; } + } + if (state == L_CONNECTED) { + if (ev == E_PASSPORT_OK) { return L_PASSPORT; } + if (ev == E_PASSPORT_FAIL) { return L_FAILED; } + } + if (state == L_PASSPORT and ev == E_QUEST_STARTED) { return L_PLAYING; } + return state; +} + +pub fn shows_user_code(state: u32) -> bool { + return state == L_CODE_SHOWN; +} + +pub fn shows_install_button(state: u32) -> bool { + return state == L_NEED_INSTALL; +} + +/// TRI CLAW is connected once the App is installed on the user's account. +pub fn connected(state: u32) -> bool { + return state == L_CONNECTED or state == L_PASSPORT or state == L_PLAYING; +} + +pub fn game_started(state: u32) -> bool { + return state == L_PLAYING; +} + +/// Sending a share needs the App token, so it needs a connected TRI CLAW. +pub fn may_share(state: u32) -> bool { + return connected(state); +} + +pub fn code_expired(now: u32, issued: u32, expires_in: u32) -> bool { + return now >= issued + expires_in; +} + +pub fn next_poll_s(slow_downs: u32) -> u32 { + return POLL_BASE_S + POLL_SLOW_ADD * slow_downs; +} + +pub const A_PENDING : u32 = 0; +pub const A_SLOW : u32 = 1; +pub const A_GRANTED : u32 = 2; +pub const A_EXPIRED : u32 = 3; +pub const A_DENIED : u32 = 4; +pub const A_OTHER : u32 = 5; + +/// The event for one poll answer. The clock wins: past the code's life every answer is an expiry. +pub fn poll_event(now: u32, issued: u32, expires_in: u32, answer: u32, stored: bool) -> u32 { + if (code_expired(now, issued, expires_in)) { return E_EXPIRED; } + if (answer == A_GRANTED) { + if (stored) { return E_TOKEN_STORED; } + return E_TOKEN_REFUSED; + } + if (answer == A_SLOW) { return E_SLOW; } + if (answer == A_PENDING) { return E_PENDING; } + if (answer == A_EXPIRED) { return E_EXPIRED; } + if (answer == A_DENIED) { return E_DENIED; } + return E_CODE_FAIL; +} + +pub const W_KEYCHAIN : u32 = 0; +pub const W_FILE : u32 = 1; +pub const W_ENV : u32 = 2; +pub const W_LINK : u32 = 3; +pub const W_LOG : u32 = 4; + +/// Where a user token may live: the Keychain, nowhere else (written through keys_host's /usr/bin/security path). +pub fn token_home_ok(w: u32) -> bool { + return w == W_KEYCHAIN; +} + +// ---------------------------------------------------------------- 5. receive + +pub const RT_BAD_LINK : u32 = 0; +pub const RT_LOGIN : u32 = 1; +pub const RT_INSTALL : u32 = 2; +pub const RT_KANBAN_TODO : u32 = 3; +pub const RT_TRANSCRIPT_RO : u32 = 4; +pub const RT_SPEC_PAGE : u32 = 5; + +/// Where an opened share link goes. A DOCUMENT is a public permalink and needs no login; TASK and SESSION read through +/// the App, so they need a connected TRI CLAW (not signed in -> login, signed in without the App -> install). +pub fn route(kind: u32, state: u32) -> u32 { + if (kind >= K_COUNT) { return RT_BAD_LINK; } + if (kind == K_DOCUMENT) { return RT_SPEC_PAGE; } + if (state == L_AUTHORIZED or state == L_NEED_INSTALL) { return RT_INSTALL; } + if (!connected(state)) { return RT_LOGIN; } + if (kind == K_TASK) { return RT_KANBAN_TODO; } + return RT_TRANSCRIPT_RO; +} + +/// The kanban column a received TASK lands in. +pub fn task_column() -> u32 { + return COL_TODO; +} + +/// The received transcript is read-only; "resume a copy" starts a NEW session seeded with the redacted transcript and +/// never resumes the sender's session id (it is not on this machine, and a foreign sid must not reach --resume). +pub fn transcript_writable() -> bool { + return false; +} + +pub fn resume_uses_sender_sid() -> bool { + return false; +} + +/// "Continue in Claude Code": needs the branch to exist and a key from the key master (keys_host); the context +/// reaches the agent as a file, never on the command line. +pub fn continue_ok(has_branch: bool, key_present: bool) -> bool { + return has_branch and key_present; +} + +/// The pane's command line: claude "$(cat ~/.t27os/shares/.md)". Returns its length; 0 when the id is bad or the +/// line would pass the pty server's cap. +pub fn continue_line(id: []const u8, out: []u8) -> usize { + if (!id_ok(id)) { return 0; } + var o : usize = putn(out, 0, "claude \"$(cat ~/.t27os/shares/"); + o = putn(out, o, id); + o = putn(out, o, ".md)\""); + if (o > PTY_CAP) { return 0; } + return o; +} + +// ---------------------------------------------------------------- tests + +fn rep(out: []u8, at: usize, c: u8, n: usize) -> usize { + var p : usize = at; + var i : usize = 0; + while (i < n) { p = put1(out, p, c); i = i + 1; } + return p; +} + +fn run_redact(src: []const u8, out: []u8, counts: []u32) -> usize { + return redact(src, out, counts); +} + +test every_share_kind_writes_through_a_row_of_the_app_table { + assert str_eq(surface_of(K_TASK, false), "findings-issues"); + assert str_eq(surface_of(K_TASK, true), "control-comments"); + assert str_eq(surface_of(K_SESSION, false), "control-comments"); + assert str_eq(surface_of(K_DOCUMENT, false), "spec-read"); + assert str_eq(surface_of(K_BAD, false), ""); + // each named row exists and mints a narrow token: issues write, contents read, contents write for the branch + assert row_of("findings-issues") >= 0 and row_of("control-comments") >= 0 and row_of("spec-read") >= 0; + assert row_of(BRANCH_SURFACE) >= 0; + assert str_eq(perm_of("findings-issues", false), "issues") and level_of("findings-issues", false) == L_WRITE; + assert str_eq(perm_of("control-comments", false), "issues") and level_of("control-comments", false) == L_WRITE; + assert str_eq(perm_of("spec-read", false), "contents") and level_of("spec-read", false) == L_READ; + assert str_eq(perm_of(BRANCH_SURFACE, false), "contents") and level_of(BRANCH_SURFACE, false) == L_WRITE; + // a document share is read-only: it never asks for a write level + assert level_of(surface_of(K_DOCUMENT, false), false) == L_READ; + assert gist_possible_with_app() == false; + // negative control: a row that is not in the table is not a surface + assert row_of("gist-create") < 0; +} + +test a_link_round_trips_for_every_kind { + var buf : [128]u8 = undefined; + var f : [3]u64 = undefined; + var k : u32 = 0; + while (k < K_COUNT) { + const n : usize = emit_link(k, "5f1c-9A-11275", buf[0..128]); + assert n > 0; + assert parse_link(buf[0..n], f[0..3]); + assert f[0] == k and f[2] == 13; + assert buf[f[1]] == 53 and buf[f[1] + f[2] - 1] == 53; + k = k + 1; + } + const n2 : usize = emit_link(K_TASK, "11275", buf[0..128]); + assert bytes_equal(buf[0..n2], "https://app.t27.ai/s/task/11275"); + assert bytes_equal(buf[0..emit_link(K_SESSION, "ab-1", buf[0..128])], "https://app.t27.ai/s/session/ab-1"); + assert bytes_equal(buf[0..emit_link(K_DOCUMENT, "9f8e7d6c", buf[0..128])], "https://app.t27.ai/s/doc/9f8e7d6c"); +} + +test a_bad_link_is_refused_and_a_bad_id_is_never_emitted { + var buf : [128]u8 = undefined; + var f : [3]u64 = undefined; + assert parse_link("https://app.t27.ai/s/task/11275", f[0..3]); + assert parse_link("http://app.t27.ai/s/task/11275", f[0..3]) == false; + assert parse_link("https://app.t27.ai.evil.com/s/task/11275", f[0..3]) == false; + assert parse_link("https://evil.com/https://app.t27.ai/s/task/1", f[0..3]) == false; + assert parse_link("https://app.t27.ai/s/task/", f[0..3]) == false; + assert parse_link("https://app.t27.ai/s/task", f[0..3]) == false; + assert parse_link("https://app.t27.ai/s/other/1", f[0..3]) == false; + assert parse_link("https://app.t27.ai/s/task/11275/extra", f[0..3]) == false; + assert parse_link("https://app.t27.ai/s/task/11275?x=1", f[0..3]) == false; + assert parse_link("https://app.t27.ai/s/task/..", f[0..3]) == false; + assert parse_link("https://app.t27.ai/s/task/a b", f[0..3]) == false; + assert parse_link("", f[0..3]) == false; + assert emit_link(K_TASK, "", buf[0..128]) == 0; + assert emit_link(K_TASK, "a/b", buf[0..128]) == 0; + assert emit_link(K_TASK, "a;b", buf[0..128]) == 0; + assert emit_link(K_COUNT, "ok", buf[0..128]) == 0; + assert emit_link(K_TASK, "ok", buf[0..10]) == 0; +} + +test key_like_strings_are_dropped_and_counted { + var src : [200]u8 = undefined; + var out : [200]u8 = undefined; + var cn : [5]u32 = undefined; + // fake keys built at run time: a prefix and filler, never a real secret + var s : usize = putn(src[0..200], 0, "use "); + s = putn(src[0..200], s, "sk-"); + s = rep(src[0..200], s, 97, 24); + s = putn(src[0..200], s, " and "); + s = putn(src[0..200], s, "ghp_"); + s = rep(src[0..200], s, 98, 36); + s = putn(src[0..200], s, " ok"); + const n : usize = run_redact(src[0..s], out[0..200], cn[0..5]); + assert bytes_equal(out[0..n], "use [REDACTED] and [REDACTED] ok"); + assert cn[R_KEY] == 2 and redacted_total(cn[0..5]) == 2; + // github_pat_ and a long base64 run with a digit + var s2 : usize = putn(src[0..200], 0, "github_pat_"); + s2 = rep(src[0..200], s2, 67, 20); + s2 = putn(src[0..200], s2, " "); + s2 = rep(src[0..200], s2, 81, 40); + s2 = putn(src[0..200], s2, "7=="); + const n2 : usize = run_redact(src[0..s2], out[0..200], cn[0..5]); + assert bytes_equal(out[0..n2], "[REDACTED] [REDACTED]"); + assert cn[R_KEY] == 2; +} + +test key_shaped_but_harmless_text_is_kept { + var src : [200]u8 = undefined; + var out : [200]u8 = undefined; + var cn : [5]u32 = undefined; + // negative controls: a word that merely ends in "sk-", a too-short sk- tail, a git sha, a uuid, a long word with no digit + const a : []const u8 = "ask-me-anything and task-list and sk-short"; + assert bytes_equal(out[0..run_redact(a, out[0..200], cn[0..5])], a); + assert redacted_total(cn[0..5]) == 0; + var s : usize = putn(src[0..200], 0, "commit "); + s = rep(src[0..200], s, 97, 40); + s = putn(src[0..200], s, " session 5f1c2c6e-9a41-4f0b-8c5d-0d3e5a7b9c11 "); + s = rep(src[0..200], s, 122, 40); + const n : usize = run_redact(src[0..s], out[0..200], cn[0..5]); + assert bytes_equal(out[0..n], src[0..s]); + assert redacted_total(cn[0..5]) == 0; + // a sha of 41 hex characters is not a sha: the run is dropped + var s2 : usize = rep(src[0..200], 0, 49, 41); + assert bytes_equal(out[0..run_redact(src[0..s2], out[0..200], cn[0..5])], "[REDACTED]"); + // a 64 digit hex run (sha256) is kept + s2 = rep(src[0..200], 0, 50, 64); + assert redact(src[0..s2], out[0..200], cn[0..5]) == 64 and redacted_total(cn[0..5]) == 0; +} + +test secret_env_values_are_dropped_but_other_env_is_kept { + var out : [200]u8 = undefined; + var cn : [5]u32 = undefined; + const a : []const u8 = "export ANTHROPIC_API_KEY=hunter2 PATH=/usr/bin GH_TOKEN='a b' my_secret=\"x y\" z"; + const n : usize = run_redact(a, out[0..200], cn[0..5]); + assert bytes_equal(out[0..n], "export ANTHROPIC_API_KEY=[REDACTED] PATH=/usr/bin GH_TOKEN=[REDACTED] my_secret=[REDACTED] z"); + assert cn[R_ENV] == 3; + const b : []const u8 = "HOME=/x EDITOR=vim a=b KEY= none"; + assert bytes_equal(out[0..run_redact(b, out[0..200], cn[0..5])], b); + assert redacted_total(cn[0..5]) == 0; +} + +test home_paths_and_emails_are_rewritten { + var out : [200]u8 = undefined; + var cn : [5]u32 = undefined; + const a : []const u8 = "cd /Users/alice/t27 && ls /home/bob/x; mail dev@example.com now"; + const n : usize = run_redact(a, out[0..200], cn[0..5]); + assert bytes_equal(out[0..n], "cd ~/t27 && ls ~/x; mail [email] now"); + assert cn[R_HOME] == 2 and cn[R_EMAIL] == 1 and redacted_total(cn[0..5]) == 3; + // negative controls: a ssh remote, a handle, an absolute path that is not a home, a trailing @ + const b : []const u8 = "git@github.com:gHashTag/t27.git @alice /usr/local/bin /mnt/Users/x end@"; + assert bytes_equal(out[0..run_redact(b, out[0..200], cn[0..5])], b); + assert redacted_total(cn[0..5]) == 0; + // a name-less home is not a home path + const c : []const u8 = "/Users/ and /home/"; + assert bytes_equal(out[0..run_redact(c, out[0..200], cn[0..5])], c); +} + +test a_share_that_lost_something_shows_a_counted_note { + var out : [200]u8 = undefined; + var note : [64]u8 = undefined; + var cn : [5]u32 = undefined; + const n : usize = run_redact("see /Users/alice/x and bob@example.org, GH_TOKEN=abc", out[0..200], cn[0..5]); + assert bytes_equal(out[0..n], "see ~/x and [email], GH_TOKEN=[REDACTED]"); + assert redacted_total(cn[0..5]) == 3; + const m : usize = note_text(cn[0..5], note[0..64]); + assert bytes_equal(note[0..m], "redacted 3 item(s) before sharing"); + // nothing removed: no note + const n2 : usize = run_redact("plain text", out[0..200], cn[0..5]); + assert n2 == 10 and note_text(cn[0..5], note[0..64]) == 0; + // a too small output is flagged, never silently cut + var tiny : [8]u8 = undefined; + run_redact("0123456789abcdef", tiny[0..8], cn[0..5]); + assert cn[R_OVER] == 1; +} + +test the_keys_profile_is_never_a_share_section { + assert section_allowed(SEC_AGENT) and section_allowed(SEC_MODEL) and section_allowed(SEC_REPO); + assert section_allowed(SEC_SID) and section_allowed(SEC_EXCERPT); + assert section_allowed(SEC_KEYS_PROFILE) == false; + assert section_allowed(SEC_KEYS_PROFILE + 1) == false; + assert EXCERPT_MAX == 4000; +} + +test consent_gates_run_in_order_and_god_mode_never_sends { + // all satisfied: private repo, nothing redacted + assert send_gate(true, true, true, V_PRIVATE, false, 0, false) == G_OK; + // no button click (GOD MODE, a loop, a script): never + assert send_gate(true, false, true, V_PRIVATE, true, 0, true) == G_NEED_CLICK; + assert god_may_send() == false; + assert god_may_prepare(K_TASK) and god_may_prepare(K_SESSION) == false and god_may_prepare(K_DOCUMENT) == false; + // not signed in + assert send_gate(false, true, true, V_PRIVATE, false, 0, false) == G_NEED_LOGIN; + // the target must be shown first + assert send_gate(true, true, false, V_PRIVATE, false, 0, false) == G_NEED_PREVIEW; + // public and unknown visibility need an explicit yes + assert send_gate(true, true, true, V_PUBLIC, false, 0, false) == G_NEED_PUBLIC_OK; + assert send_gate(true, true, true, V_UNKNOWN, false, 0, false) == G_NEED_PUBLIC_OK; + assert send_gate(true, true, true, V_PUBLIC, true, 0, false) == G_OK; + // a redacted share needs its note seen + assert send_gate(true, true, true, V_PRIVATE, false, 2, false) == G_NEED_NOTE_SEEN; + assert send_gate(true, true, true, V_PRIVATE, false, 2, true) == G_OK; +} + +test the_share_class_is_forbidden_to_god_mode_at_use_level { + // evolve's own F_ALL (31) does not cover bit 32: the gap this predicate closes without editing evolve.t27 + assert touches_forbidden(F_SHARE) == false; + assert evolve_forbids(F_SHARE); + assert evolve_forbids(F_SHARE | 1); + assert evolve_forbids(F_ALL); + assert evolve_forbids(0) == false; + assert F_SHARE == F_ALL + 1; +} + +test signing_in_walks_to_the_game { + var s : u32 = L_OUT; + s = step(s, E_START); + assert s == L_REQUESTING; + s = step(s, E_CODE_OK); + assert s == L_CODE_SHOWN and shows_user_code(s); + s = step(s, E_PENDING); + s = step(s, E_SLOW); + assert s == L_CODE_SHOWN; + s = step(s, E_TOKEN_STORED); + assert s == L_AUTHORIZED and may_share(s) == false; + s = step(s, E_APP_FOUND); + assert s == L_CONNECTED and connected(s) and may_share(s) and game_started(s) == false; + s = step(s, E_PASSPORT_OK); + assert s == L_PASSPORT; + s = step(s, E_QUEST_STARTED); + assert s == L_PLAYING and game_started(s) and connected(s); + assert step(s, E_SIGN_OUT) == L_OUT; +} + +test a_missing_app_shows_the_install_button_until_it_is_found { + var s : u32 = step(L_AUTHORIZED, E_APP_MISSING); + assert s == L_NEED_INSTALL and shows_install_button(s) and connected(s) == false and may_share(s) == false; + // the user installs and presses check again: still missing, then found + s = step(s, E_APP_MISSING); + assert s == L_NEED_INSTALL; + s = step(s, E_APP_FOUND); + assert s == L_CONNECTED and shows_install_button(s) == false; + assert INSTALL_URL.len > 0 and APP_SLUG.len == 8; +} + +test an_expired_or_denied_code_restarts_and_nothing_skips_a_step { + assert step(L_CODE_SHOWN, E_EXPIRED) == L_EXPIRED; + assert step(L_CODE_SHOWN, E_DENIED) == L_DENIED; + assert step(L_EXPIRED, E_START) == L_REQUESTING; + assert step(L_DENIED, E_START) == L_REQUESTING; + assert step(L_FAILED, E_START) == L_REQUESTING; + // a refused Keychain write fails the login: no fallback to a file + assert step(L_CODE_SHOWN, E_TOKEN_REFUSED) == L_FAILED; + assert step(L_REQUESTING, E_CODE_FAIL) == L_FAILED; + assert step(L_CONNECTED, E_PASSPORT_FAIL) == L_FAILED; + // out-of-order events are ignored + assert step(L_OUT, E_TOKEN_STORED) == L_OUT; + assert step(L_OUT, E_QUEST_STARTED) == L_OUT; + assert step(L_CODE_SHOWN, E_APP_FOUND) == L_CODE_SHOWN; + assert step(L_AUTHORIZED, E_QUEST_STARTED) == L_AUTHORIZED; + assert step(L_CONNECTED, E_QUEST_STARTED) == L_CONNECTED; + assert step(L_PLAYING, E_START) == L_PLAYING; + assert step(L_CODE_SHOWN, E_START) == L_CODE_SHOWN; +} + +test the_device_code_clock_wins_over_the_answer { + assert code_expired(899, 0, DEVICE_EXPIRES_S) == false; + assert code_expired(900, 0, DEVICE_EXPIRES_S); + assert poll_event(10, 0, 900, A_PENDING, false) == E_PENDING; + assert poll_event(10, 0, 900, A_SLOW, false) == E_SLOW; + assert poll_event(10, 0, 900, A_GRANTED, true) == E_TOKEN_STORED; + assert poll_event(10, 0, 900, A_GRANTED, false) == E_TOKEN_REFUSED; + assert poll_event(10, 0, 900, A_DENIED, false) == E_DENIED; + assert poll_event(10, 0, 900, A_EXPIRED, false) == E_EXPIRED; + assert poll_event(10, 0, 900, A_OTHER, false) == E_CODE_FAIL; + // past the life of the code even a granted answer is an expiry + assert poll_event(900, 0, 900, A_GRANTED, true) == E_EXPIRED; + assert next_poll_s(0) == 5 and next_poll_s(2) == 15; +} + +test a_token_lives_only_in_the_keychain { + assert token_home_ok(W_KEYCHAIN); + assert token_home_ok(W_FILE) == false; + assert token_home_ok(W_ENV) == false; + assert token_home_ok(W_LINK) == false; + assert token_home_ok(W_LOG) == false; +} + +test a_received_share_is_routed_by_kind_and_login { + // a document needs no login + assert route(K_DOCUMENT, L_OUT) == RT_SPEC_PAGE; + // task and session: login first, install second, then the instrument + assert route(K_TASK, L_OUT) == RT_LOGIN; + assert route(K_SESSION, L_CODE_SHOWN) == RT_LOGIN; + assert route(K_TASK, L_AUTHORIZED) == RT_INSTALL; + assert route(K_SESSION, L_NEED_INSTALL) == RT_INSTALL; + assert route(K_TASK, L_CONNECTED) == RT_KANBAN_TODO; + assert route(K_TASK, L_PLAYING) == RT_KANBAN_TODO; + assert route(K_SESSION, L_PASSPORT) == RT_TRANSCRIPT_RO; + assert route(K_COUNT, L_PLAYING) == RT_BAD_LINK; + assert task_column() == COL_TODO; + assert transcript_writable() == false and resume_uses_sender_sid() == false; +} + +test continue_in_claude_code_needs_a_branch_a_key_and_a_short_line { + var out : [256]u8 = undefined; + assert continue_ok(true, true); + assert continue_ok(false, true) == false; + assert continue_ok(true, false) == false; + const n : usize = continue_line("11275-ab", out[0..256]); + assert bytes_equal(out[0..n], "claude \"$(cat ~/.t27os/shares/11275-ab.md)\""); + assert n <= PTY_CAP; + assert continue_line("a;rm", out[0..256]) == 0; + assert continue_line("", out[0..256]) == 0; + // an id at the cap still fits the pty line; one over is refused + var long : [80]u8 = undefined; + rep(long[0..80], 0, 97, 80); + assert continue_line(long[0..64], out[0..256]) > 0; + assert continue_line(long[0..65], out[0..256]) == 0; +} diff --git a/specs/tri/land/golden_land.t27 b/specs/tri/land/golden_land.t27 new file mode 100644 index 0000000000..c75a00f248 --- /dev/null +++ b/specs/tri/land/golden_land.t27 @@ -0,0 +1,1986 @@ +// SPDX-License-Identifier: Apache-2.0 +; specs/tri/land/golden_land.t27 -- `tri golden land | push | status`: the front door of the golden chain (#11275) +; Owner, 2026-10-20: remove the GitHub traffic jams; agents build with OUR git, and GitHub receives one batch per window. +; The law is specs/queen/golden_chain.t27 (links, golden head by compare-and-swap, CARRY, section 6 landing rule). This file has +; NO rule of its own about what lands or when it is pushed: it calls gc_land, when_to_push and the GC_* constants, and adds only +; what no one had: the order of the git commands, the readers of their output, and the words the command prints. `tri land` +; (specs/tri/land/land.t27) stays the GitHub leg for ONE branch; `tri golden push` runs it once, on a branch at the golden head. +; - +; THE HOST (cli/tri/src/main.rs, cmd_golden) is glue: it asks next_slot which step runs next, runs step_argv in step_cwd as an +; argument vector with no shell, appends the run to the evidence (the @@ preflight format of preflight.t27), asks judge, and at the +; end prints report. Same shape as `tri land`. +; - +; THE VERBS +; land [--plan] read the queue, verify each link locally, land the ready ones on refs/golden/head by compare-and-swap. +; push [--plan] [--queued N] [--issue N] when_to_push over the unpushed golden links; when due, `tri land` on a branch at +; the batch's last link (one pull request, auto-merge), then refs/golden/pushed moves to it. +; status [--queued N] head, queue, unpushed links, minutes since the last push, when the next push is due. +; THE QUEUE is the local branches named claude/evolve-* and the lines of .trinity/golden/queue (one branch per line, # comments). +; A LINK is one queued branch. Its five stage verdicts (golden_chain.t27 GC_SPEC..GC_RECEIPT), judged here from git's own output: +; SPEC the diff against the merge-base reads: 1 to GL_E_MAX paths, all of safe bytes. +; GEN a change under gen/ comes with a change to a spec (L2: gen/ is generated, never alone). +; SEAL `t27c seal --verify` in a worktree of the tip, for every spec the link adds or modifies: all hashes MATCH. +; A seal that does not match is saved again (`--save`, then `--verify`) once the link's tests are clean (#11361): the seals of +; an older t27c are not a reason to refuse a link. Only .trinity/seals/ may change (git add -A, then git diff --cached lists +; nothing else); the re-saved seals are one commit on the link's tip and the link lands as REGEN. A failing test, a spec +; that does not compile, or any other changed path still refuses the link. +; TEST `t27b test ` in the same worktree: exit 0, no failure, not a vacuous 0 passed. +; RECEIPT a commit message of the link carries `Closes #N` (L1). +; A link with no spec has nothing to seal or test: those two stages are REUSED (the verdict of nothing to judge). +; THE LANDING. gc_land says FORWARD, CARRIED, REGEN or a refusal per link. A link whose parent is the head lands as its own tip; +; a carried one (also a link cut from an older master, #11361) is a new commit on the head with the tree of `git merge-tree +; --write-tree` (no worktree touched); a link that merge-tree cannot merge is CONFLICT and the pass goes on with the next one. Every landing +; is `git update-ref refs/golden/head `: git refuses when the head is not , which is the compare-and-swap, and the +; command stops with HEAD_MOVED. With --plan the read-only steps run (merge-tree and commit-tree only add unreferenced objects) +; and every update-ref is printed, none run: the head is written by no plan. +; THE CHAIN FOLLOWS MASTER (#11361). GitHub squash-merges a batch, so refs/golden/head is then no ancestor of origin/master and every new +; link would read as a stranger. At the start of land, push and status gc_rebase decides from refs/golden/head, refs/golden/pushed and +; `git merge-base --is-ancestor head origin/master`: RESET (nothing unpushed) moves head and pushed to origin/master, CARRY (links +; unpushed) makes ONE commit of them on origin/master by merge-tree with the pushed mark as the base, then moves head to it and pushed +; to origin/master. Both moves are update-ref with the old value (compare-and-swap); a plan and status write nothing. +; THE HONEST LIMITS. (1) Edits are whole files (no hunk regions): two links that edit one source file conflict. (2) Importers of a +; changed spec are not re-judged here: no import rows go to gc_land. (3) No verdict-reuse key is read: a stage is PASS or FAIL or +; the nothing-to-judge REUSED. (4) The queue is capped at GL_Q_MAX links per pass. (5) A link merge-tree finds in conflict is +; CONFLICT and the pass goes on; a conflict while carrying the unpushed links onto master stops the pass (a person decides). (6) The +; stages of a link are judged on its tip (seals re-saved on the tip); merge-tree decides whether it carries onto the head. +; ASCII only (L3) +; phi^2 + 1/phi^2 = 3 | TRINITY + +module TriGoldenLand; + +use queen::golden_chain::gc_land; +use queen::golden_chain::when_to_push; +use queen::golden_chain::gc_golden; +use queen::golden_chain::GC_V_NONE; +use queen::golden_chain::GC_V_PASS; +use queen::golden_chain::GC_V_REUSED; +use queen::golden_chain::GC_V_FAIL; +use queen::golden_chain::GC_OUT_FORWARD; +use queen::golden_chain::GC_OUT_CARRIED; +use queen::golden_chain::GC_OUT_REGEN; +use queen::golden_chain::GC_OUT_REJUDGE; +use queen::golden_chain::GC_OUT_CONFLICT; +use queen::golden_chain::GC_OUT_NOT_GOLDEN; +use queen::golden_chain::GC_OUT_UNKNOWN_PARENT; +use queen::golden_chain::GC_EDIT_DERIVED; +use queen::golden_chain::GC_BATCH_MAX; +use queen::golden_chain::GC_WINDOW_MIN; +use queen::golden_chain::GC_CI_JAM; +use queen::golden_chain::GC_PUSH_NOTHING; +use queen::golden_chain::GC_PUSH_WAIT; +use queen::golden_chain::GC_PUSH_NOW; +use queen::golden_chain::GC_REF; +use queen::golden_chain::gc_rebase; +use queen::golden_chain::gc_regen_ok; +use queen::golden_chain::gc_regen_view; +use queen::golden_chain::GC_RB_NONE; +use queen::golden_chain::GC_RB_RESET; +use queen::golden_chain::GC_RB_CARRY; +use queen::actors::EDIT_WHOLE; +use tri::lab::receipt::line_end; +use tri::lab::receipt::span_is; +use tri::lanes::lanes::digits; +use tri::pr::preflight::NO_EXIT; +use tri::pr::preflight::exit_of; +use tri::pr::preflight::body_from; +use tri::pr::preflight::body_to; +use tri::pr::preflight::find_in; +use tri::remote::remote_job::emit_byte; +use tri::remote::remote_job::emit_str; +use tri::remote::remote_job::emit_dec; +use tri::remote::remote_job::fits; +use tri::remote::remote_job::safe_path; +use tri::remote::remote_job::buf_is; +use tri::land::land::emit_section; + +pub const GL_Q_MAX : usize = 16; +pub const GL_E_MAX : usize = 32; +pub const CMD_MAX : usize = 4096; +pub const SLOT : u64 = 100000; +pub const DONE : u64 = 99999999999; +pub const ABSENT : u64 = 1000000000000000000; +// A slot of a per-link step is link * TGT + line of the link's diff. +pub const TGT : u64 = 1000; +pub const GL_PUSHED : str = "refs/golden/pushed"; +pub const GL_QUEUE_FILE : str = ".trinity/golden/queue"; + +// The steps. 0-3 once per run, 4-11 per link, 12-14 per landed link, 15-19 read the push state, 20-22 the push, 23 the host's. +pub const G_REFS : u8 = 0; +pub const G_FILE : u8 = 1; +pub const G_HEAD : u8 = 2; +pub const G_BASE : u8 = 3; +pub const G_TIP : u8 = 4; +pub const G_MB : u8 = 5; +pub const G_CHG : u8 = 6; +pub const G_LOG : u8 = 7; +pub const G_WT : u8 = 8; +pub const G_SEAL : u8 = 9; +pub const G_TEST : u8 = 10; +pub const G_RM : u8 = 11; +pub const G_MT : u8 = 12; +pub const G_CT : u8 = 13; +pub const G_CAS : u8 = 14; +pub const P_PUSHED : u8 = 15; +pub const P_AHEAD : u8 = 16; +pub const P_STAMP : u8 = 17; +pub const P_NOW : u8 = 18; +pub const P_QUEUED : u8 = 19; +pub const P_BRANCH : u8 = 20; +pub const P_LAND : u8 = 21; +pub const P_MARK : u8 = 22; +pub const G_PRUNE : u8 = 23; +// 24-28 make the chain follow master (once per run); 29-34 save the seals of a link again (#11361). +pub const R_ANC : u8 = 24; +pub const R_MT : u8 = 25; +pub const R_CT : u8 = 26; +pub const R_UH : u8 = 27; +pub const R_UP : u8 = 28; +pub const S_SAVE : u8 = 29; +pub const S_RECHECK : u8 = 30; +pub const S_STAGE : u8 = 31; +pub const S_DIFF : u8 = 32; +pub const S_COMMIT : u8 = 33; +pub const S_TIP : u8 = 34; +pub const GL_STEPS : u8 = 35; +pub const SEAL_DIR : str = ".trinity/seals/"; + +pub const V_LAND : u8 = 0; +pub const V_PUSH : u8 = 1; +pub const V_STATUS : u8 = 2; +pub const V_BAD : u8 = 3; + +// What the command ends with. +pub const C_OK : u8 = 0; +pub const C_BAD_VERB : u8 = 1; +pub const C_BAD_ISSUE : u8 = 2; +pub const C_BAD_PATH : u8 = 3; +pub const C_NO_BASE : u8 = 4; +pub const C_MERGE : u8 = 5; +pub const C_STEP : u8 = 6; +pub const C_HEAD_MOVED : u8 = 7; +pub const C_LAND_REFUSED : u8 = 8; +pub const C_ODDS : u8 = 9; + +pub struct GolReq { + verb: u8, + plan: bool, + queued: u64, + issue: u64, + scratch: string, + t27b: string, + t27c: string, + tri: string, +} + +invariant the_steps_are_numbered_in_the_order_they_run { + assert G_REFS < G_FILE and G_FILE < G_HEAD and G_HEAD < G_BASE and G_BASE < G_TIP and G_TIP < G_MB and G_MB < G_CHG; + assert G_CHG < G_LOG and G_LOG < G_WT and G_WT < G_SEAL and G_SEAL < G_TEST and G_TEST < G_RM and G_RM < G_MT; + assert G_MT < G_CT and G_CT < G_CAS and G_CAS < P_PUSHED and P_PUSHED < P_AHEAD and P_AHEAD < P_STAMP; + assert P_STAMP < P_NOW and P_NOW < P_QUEUED and P_QUEUED < P_BRANCH and P_BRANCH < P_LAND and P_LAND < P_MARK; + assert P_MARK + 1 == G_PRUNE and G_PRUNE + 1 == R_ANC and R_ANC < R_MT and R_MT < R_CT and R_CT < R_UH and R_UH < R_UP; + assert R_UP + 1 == S_SAVE and S_SAVE < S_RECHECK and S_RECHECK < S_STAGE and S_STAGE < S_DIFF and S_DIFF < S_COMMIT and S_COMMIT < S_TIP and S_TIP + 1 == GL_STEPS; + assert (GL_Q_MAX as u64) * TGT < SLOT; +} + +// --- the request --------------------------------------------------------------------------------- + +pub fn verb_of(a: string) -> u8 { + if (span_is(a, 0, a.len, "land")) { return V_LAND; } + if (span_is(a, 0, a.len, "push")) { return V_PUSH; } + if (span_is(a, 0, a.len, "status")) { return V_STATUS; } + return V_BAD; +} + +/// Why a request may not run (C_OK says it may). A push that is not a plan opens a pull request, so it needs its issue (L1). +pub fn request_refusal(r: GolReq) -> u8 { + if (r.verb >= V_BAD) { return C_BAD_VERB; } + if (r.verb == V_PUSH and r.plan == false and (r.issue == 0 or r.issue > 999999999)) { return C_BAD_ISSUE; } + if (safe_path(r.scratch) == false or safe_path(r.t27b) == false or safe_path(r.t27c) == false or safe_path(r.tri) == false) { return C_BAD_PATH; } + return C_OK; +} + +// --- reading the evidence ------------------------------------------------------------------------ + +pub fn ran(ev: string, k: u8, n: u64) -> bool { + return exit_of(ev, k, n) != NO_EXIT; +} + +pub fn is_hex(b: u8) -> bool { + return (b >= 48 and b <= 57) or (b >= 97 and b <= 102); +} + +/// Did step k, entry n, exit 0 and print a 40-digit object name first? +pub fn sha_ok(ev: string, k: u8, n: u64) -> bool { + if (exit_of(ev, k, n) != 0) { return false; } + const f : usize = body_from(ev, k, n); + if (body_to(ev, k, n) < f + 40) { return false; } + var i : usize = 0; + while (i < 40) { + if (is_hex(ev[f + i]) == false) { return false; } + i = i + 1; + } + return true; +} + +/// The first 15 hex digits at ev[f..] as a number: the link's id for golden_chain.t27 (it only compares ids). +pub fn hex15(ev: string, f: usize) -> u64 { + var v : u64 = 0; + var i : usize = 0; + while (i < 15) { + var c : u8 = ev[f + i]; + var d : u64 = (c as u64) - 48; + if (c >= 97) { d = (c as u64) - 87; } + v = v * 16 + d; + i = i + 1; + } + return v; +} + +pub fn id_of(ev: string, k: u8, n: u64) -> u64 { + if (sha_ok(ev, k, n) == false) { return 0; } + return hex15(ev, body_from(ev, k, n)); +} + +/// Do the 40 digits of step a and step b (entry 0 each) read the same? +pub fn same_sha(ev: string, a: u8, b: u8) -> bool { + var i : usize = 0; + while (i < 40) { + if (ev[body_from(ev, a, 0) + i] != ev[body_from(ev, b, 0) + i]) { return false; } + i = i + 1; + } + return true; +} + +/// What the chain does to follow master (golden_chain.t27 gc_rebase), read from the evidence. master_has_head is exit 0 of +/// `merge-base --is-ancestor`; any exit but 1 (an error) reads as "has it" and writes nothing. +pub fn rb_action(ev: string) -> u8 { + if (sha_ok(ev, G_HEAD, 0) == false or sha_ok(ev, P_PUSHED, 0) == false or ran(ev, R_ANC, 0) == false) { return GC_RB_NONE; } + return gc_rebase(true, true, same_sha(ev, G_HEAD, P_PUSHED), exit_of(ev, R_ANC, 0) != 1); +} + +/// Where the sha of the golden head begins; ev.len when there is none. After a decided rebase it is what the head becomes (the +/// refs are written by R_UH and R_UP, a plan or status writes none), so every later step reads one head. +pub fn head_off(ev: string) -> usize { + const a : u8 = rb_action(ev); + if (a == GC_RB_RESET) { return body_from(ev, G_BASE, 0); } + if (a == GC_RB_CARRY and sha_ok(ev, R_CT, 0)) { return body_from(ev, R_CT, 0); } + if (sha_ok(ev, G_HEAD, 0)) { return body_from(ev, G_HEAD, 0); } + return ev.len; +} + +/// The same for refs/golden/pushed: master once the chain follows master. +pub fn pushed_off(ev: string) -> usize { + if (rb_action(ev) != GC_RB_NONE) { return body_from(ev, G_BASE, 0); } + if (sha_ok(ev, P_PUSHED, 0)) { return body_from(ev, P_PUSHED, 0); } + return ev.len; +} + +pub fn has_head(ev: string) -> bool { + return head_off(ev) < ev.len; +} + +pub fn has_pushed(ev: string) -> bool { + return pushed_off(ev) < ev.len; +} + +/// Where the sha of the commit new links are judged against begins: the golden head, else master of origin. +pub fn cur0(ev: string) -> usize { + if (has_head(ev)) { return head_off(ev); } + return body_from(ev, G_BASE, 0); +} + +pub fn starts(t: string, a: usize, b: usize, lit: string) -> bool { + return b >= a + lit.len and span_is(t, a, a + lit.len, lit); +} + +pub fn ends(t: string, a: usize, b: usize, lit: string) -> bool { + return b >= a + lit.len and span_is(t, b - lit.len, b, lit); +} + +// ---- the queue: refs first, then the file; a name is safe bytes, not a trunk, not twice ---------- + +pub fn name_byte(b: u8) -> bool { + return (b >= 97 and b <= 122) or (b >= 65 and b <= 90) or (b >= 48 and b <= 57) or b == 46 or b == 95 or b == 47 or b == 45; +} + +pub fn name_ok(t: string, a: usize, b: usize) -> bool { + if (b <= a or b - a > 100) { return false; } + if (t[a] == 45 or t[a] == 46) { return false; } + if (span_is(t, a, b, "master") or span_is(t, a, b, "main") or span_is(t, a, b, "HEAD")) { return false; } + var k : usize = a; + while (k < b) { + if (name_byte(t[k]) == false) { return false; } + if (k + 1 < b and t[k] == 46 and t[k + 1] == 46) { return false; } + k = k + 1; + } + return true; +} + +/// Is the line ev[a..b] one of the lines of the body ev[f..t]? +pub fn line_in(ev: string, a: usize, b: usize, f: usize, t: usize) -> bool { + var p : usize = f; + while (p < t) { + const e : usize = line_end(ev, p); + if (e - p == b - a and span_is(ev, p, e, "") == false) { + var k : usize = 0; + while (k < b - a and ev[p + k] == ev[a + k]) { k = k + 1; } + if (k == b - a) { return true; } + } + p = e + 1; + } + return false; +} + +/// Where queue entry i begins in the evidence; ev.len when there is none. +pub fn q_start(ev: string, i: usize) -> usize { + var c : usize = 0; + var p : usize = body_from(ev, G_REFS, 0); + const t : usize = body_to(ev, G_REFS, 0); + while (p < t) { + const e : usize = line_end(ev, p); + if (name_ok(ev, p, e)) { + if (c == i) { return p; } + c = c + 1; + } + p = e + 1; + } + if (exit_of(ev, G_FILE, 0) != 0) { return ev.len; } + p = body_from(ev, G_FILE, 0); + const ft : usize = body_to(ev, G_FILE, 0); + while (p < ft) { + const e : usize = line_end(ev, p); + if (name_ok(ev, p, e) and line_in(ev, p, e, body_from(ev, G_REFS, 0), t) == false) { + if (c == i) { return p; } + c = c + 1; + } + p = e + 1; + } + return ev.len; +} + +pub fn q_count(ev: string) -> usize { + var c : usize = 0; + while (c < GL_Q_MAX and q_start(ev, c) < ev.len) { c = c + 1; } + return c; +} + +// ---- one link's diff: lines `Spath` --------------------------------------------------------- + +pub fn ch_line(ev: string, n: u64, i: usize) -> usize { + var p : usize = body_from(ev, G_CHG, n); + const t : usize = body_to(ev, G_CHG, n); + var c : usize = 0; + while (p < t) { + if (c == i) { return p; } + p = line_end(ev, p) + 1; + c = c + 1; + } + return ev.len; +} + +pub fn ch_count(ev: string, n: u64) -> usize { + var c : usize = 0; + while (ch_line(ev, n, c) < ev.len) { c = c + 1; } + return c; +} + +pub fn path_ok(ev: string, ls: usize) -> bool { + const e : usize = line_end(ev, ls); + if (e < ls + 3 or ev[ls + 1] != 9) { return false; } + if (ev[ls + 2] == 45) { return false; } + var k : usize = ls + 2; + const seal : bool = starts(ev, ls + 2, e, SEAL_DIR); + while (k < e) { + if (name_byte(ev[k]) == false and ev[k] != 43 and (seal == false or ev[k] != 58)) { return false; } + if (k + 1 < e and ev[k] == 46 and ev[k + 1] == 46) { return false; } + k = k + 1; + } + return true; +} + +/// A spec the link adds or modifies: a .t27 under specs/, scratch aside, of safe bytes. +pub fn is_target(ev: string, ls: usize) -> bool { + if (ls >= ev.len) { return false; } + const e : usize = line_end(ev, ls); + if (ev[ls] != 65 and ev[ls] != 77) { return false; } + return path_ok(ev, ls) and starts(ev, ls + 2, e, "specs/") and ends(ev, ls + 2, e, ".t27") and starts(ev, ls + 2, e, "specs/scratch/") == false; +} + +pub fn is_derived(ev: string, ls: usize) -> bool { + const e : usize = line_end(ev, ls); + return starts(ev, ls + 2, e, "gen/") or starts(ev, ls + 2, e, ".trinity/seals/"); +} + +pub fn targets(ev: string, n: u64) -> usize { + var c : usize = 0; + var i : usize = 0; + const m : usize = ch_count(ev, n); + while (i < m) { + if (is_target(ev, ch_line(ev, n, i))) { c = c + 1; } + i = i + 1; + } + return c; +} + +// ---- the stage verdicts of link n ---------------------------------------------------------------- + +pub fn spec_ok(ev: string, n: u64) -> bool { + if (sha_ok(ev, G_TIP, n) == false or sha_ok(ev, G_MB, n) == false or exit_of(ev, G_CHG, n) != 0) { return false; } + const m : usize = ch_count(ev, n); + if (m == 0 or m > GL_E_MAX) { return false; } + var i : usize = 0; + while (i < m) { + if (path_ok(ev, ch_line(ev, n, i)) == false) { return false; } + i = i + 1; + } + return true; +} + +pub fn gen_ok(ev: string, n: u64) -> bool { + const m : usize = ch_count(ev, n); + var derived : bool = false; + var i : usize = 0; + while (i < m) { + const ls : usize = ch_line(ev, n, i); + if (starts(ev, ls + 2, line_end(ev, ls), "gen/")) { derived = true; } + i = i + 1; + } + return derived == false or targets(ev, n) > 0; +} + +pub fn seal_clean(ev: string, k: u8, n: u64) -> bool { + const f : usize = body_from(ev, k, n); + const t : usize = body_to(ev, k, n); + return exit_of(ev, k, n) == 0 and find_in(ev, f, t, "all hashes MATCH") <= t and find_in(ev, f, t, "VERIFICATION FAILED") > t; +} + +/// One target's seal: 0 matches (as it was, or after a save), 1 not settled yet, 2 does not match even after a save. +pub fn seal_state(ev: string, n: u64) -> u8 { + if (ran(ev, G_SEAL, n) == false) { return 1; } + if (seal_clean(ev, G_SEAL, n)) { return 0; } + if (ran(ev, S_RECHECK, n) == false) { return 1; } + if (seal_clean(ev, S_RECHECK, n)) { return 0; } + return 2; +} + +pub fn saved_any(ev: string, link: u64) -> bool { + const m : usize = ch_count(ev, link); + var i : usize = 0; + while (i < m) { + if (is_target(ev, ch_line(ev, link, i)) and ran(ev, S_SAVE, link * TGT + (i as u64))) { return true; } + i = i + 1; + } + return false; +} + +/// The paths `git diff --cached --name-only` printed for a link's worktree after its seals were saved, and how many are seals. +pub fn diff_changed(ev: string, link: u64) -> usize { + return lines_in(ev, body_from(ev, S_DIFF, link * TGT), body_to(ev, S_DIFF, link * TGT)); +} + +pub fn diff_seals(ev: string, link: u64) -> usize { + var c : usize = 0; + var p : usize = body_from(ev, S_DIFF, link * TGT); + const t : usize = body_to(ev, S_DIFF, link * TGT); + while (p < t) { + const e : usize = line_end(ev, p); + if (e > p and starts(ev, p, e, SEAL_DIR)) { c = c + 1; } + p = e + 1; + } + return c; +} + +pub fn diff_ok(ev: string, link: u64) -> bool { + return exit_of(ev, S_DIFF, link * TGT) == 0 and gc_regen_ok(diff_changed(ev, link), diff_seals(ev, link)); +} + +/// The state of the re-saved seals of a link whose targets all match: PASS when nothing was saved or nothing changed, NONE while +/// the commit is not made, FAIL when the stage failed, the diff holds a path that is not a seal, or the commit failed. +pub fn regen_verdict(ev: string, link: u64) -> u8 { + if (saved_any(ev, link) == false) { return GC_V_PASS; } + const lk : u64 = link * TGT; + if (ran(ev, S_STAGE, lk) == false) { return GC_V_NONE; } + if (exit_of(ev, S_STAGE, lk) != 0) { return GC_V_FAIL; } + if (ran(ev, S_DIFF, lk) == false) { return GC_V_NONE; } + if (diff_ok(ev, link) == false) { return GC_V_FAIL; } + if (diff_changed(ev, link) == 0) { return GC_V_PASS; } + if (ran(ev, S_COMMIT, lk) == false) { return GC_V_NONE; } + if (exit_of(ev, S_COMMIT, lk) != 0) { return GC_V_FAIL; } + if (ran(ev, S_TIP, lk) == false) { return GC_V_NONE; } + if (sha_ok(ev, S_TIP, lk)) { return GC_V_PASS; } + return GC_V_FAIL; +} + +pub fn regen_tip_ok(ev: string, link: u64) -> bool { + return sha_ok(ev, S_TIP, link * TGT); +} + +/// Where the sha of what the link lands as begins: the commit with its seals saved again, else its own tip. +pub fn tip_off(ev: string, link: u64) -> usize { + if (regen_tip_ok(ev, link)) { return body_from(ev, S_TIP, link * TGT); } + return body_from(ev, G_TIP, link); +} + +pub fn test_clean(ev: string, n: u64) -> bool { + const f : usize = body_from(ev, G_TEST, n); + const t : usize = body_to(ev, G_TEST, n); + return exit_of(ev, G_TEST, n) == 0 and find_in(ev, f, t, ", 0 failed") <= t and find_in(ev, f, t, ": 0 passed,") > t; +} + +/// 0 when every target's section of step k ran and is clean; 1 when one has not run yet; 2 when one ran unclean. +pub fn all_targets(ev: string, link: u64, k: u8) -> u8 { + const m : usize = ch_count(ev, link); + var i : usize = 0; + var open : bool = false; + while (i < m) { + if (is_target(ev, ch_line(ev, link, i))) { + const n : u64 = link * TGT + (i as u64); + if (ran(ev, k, n) == false) { + open = true; + } else { + if (k == G_TEST and test_clean(ev, n) == false) { return 2; } + } + if (k == G_SEAL) { + const sst : u8 = seal_state(ev, n); + if (sst == 2) { return 2; } + if (sst == 1) { open = true; } + } + } + i = i + 1; + } + if (open) { return 1; } + return 0; +} + +pub fn verdict(ev: string, n: u64, st: u8) -> u8 { + if (ran(ev, G_TIP, n) == false) { return GC_V_NONE; } + if (st == 0) { + if (spec_ok(ev, n)) { return GC_V_PASS; } + return GC_V_FAIL; + } + // a failing test is shown as such even while the seals wait for it (they are saved again only after the tests are clean) + if (st == 3 and targets(ev, n) > 0 and all_targets(ev, n, G_TEST) == 2 and verdict(ev, n, 1) == GC_V_PASS) { return GC_V_FAIL; } + if (verdict(ev, n, st - 1) != GC_V_PASS and verdict(ev, n, st - 1) != GC_V_REUSED) { return GC_V_NONE; } + if (st == 1) { + if (gen_ok(ev, n)) { return GC_V_PASS; } + return GC_V_FAIL; + } + if (st == 2 or st == 3) { + if (targets(ev, n) == 0) { return GC_V_REUSED; } + var k : u8 = G_SEAL; + if (st == 3) { k = G_TEST; } + const a : u8 = all_targets(ev, n, k); + if (a == 0 and st == 2) { return regen_verdict(ev, n); } + if (a == 0) { return GC_V_PASS; } + if (a == 2) { return GC_V_FAIL; } + return GC_V_NONE; + } + if (ran(ev, G_LOG, n) == false) { return GC_V_NONE; } + if (find_in(ev, body_from(ev, G_LOG, n), body_to(ev, G_LOG, n), "Closes #") <= body_to(ev, G_LOG, n)) { return GC_V_PASS; } + return GC_V_FAIL; +} + +// --- the pass: gc_land over the queue --------------------------------------------------------------- + +pub fn path_hash(ev: string, a: usize, b: usize) -> u64 { + var h : u64 = 7; + var k : usize = a; + while (k < b) { + h = (h * 131 + (ev[k] as u64)) % 1000000000000037; + k = k + 1; + } + return h; +} + +/// Land the n queued links on the head (golden_chain.t27 gc_land). out[i] is what happened to link i, pos[i] its place in the +/// landing order (n when it did not land). Returns how many land. +pub fn plan(ev: string, n: usize, out: []u8, pos: []usize) -> usize { + var id : [16]u64 = [0] ** 16; + var parent : [16]u64 = [0] ** 16; + var v : [80]u8 = [0] ** 80; + var eo : [512]usize = [0] ** 512; + var ep : [512]u64 = [0] ** 512; + var ek : [512]u8 = [0] ** 512; + var el : [512]u32 = [0] ** 512; + var eh : [512]u32 = [0] ** 512; + var io : [1]usize = [0] ** 1; + var ip : [1]u64 = [0] ** 1; + var m : usize = 0; + var i : usize = 0; + while (i < n) { + id[i] = id_of(ev, G_TIP, i as u64); + parent[i] = id_of(ev, G_MB, i as u64); + var st : u8 = 0; + while (st < 5) { + v[i * 5 + (st as usize)] = verdict(ev, i as u64, st); + st = st + 1; + } + // merge-tree found the link in conflict with the head it was to land on: it does not land, the pass goes on + if (exit_of(ev, G_MT, i as u64) == 1) { v[i * 5] = GC_V_FAIL; } + var c : usize = ch_count(ev, i as u64); + if (c > GL_E_MAX) { c = GL_E_MAX; } + var j : usize = 0; + while (j < c) { + const ls : usize = ch_line(ev, i as u64, j); + eo[m] = i; + ep[m] = path_hash(ev, ls + 2, line_end(ev, ls)); + ek[m] = EDIT_WHOLE; + if (is_derived(ev, ls)) { ek[m] = GC_EDIT_DERIVED; } + m = m + 1; + j = j + 1; + } + i = i + 1; + } + var head : u64 = 0; + if (cur0(ev) < ev.len) { head = hex15(ev, cur0(ev)); } + return gc_land(n, &id, &parent, &v, m, &eo, &ep, &ek, &el, &eh, 0, &io, &ip, head, out, pos); +} + +/// The link that lands r-th in a fresh pass over n links; n when none does. wo and wp hold the pass afterwards. +pub fn landing_at(ev: string, n: usize, r: usize, wo: []u8, wp: []usize) -> usize { + const landed : usize = plan(ev, n, wo, wp); + var j : usize = 0; + while (j < n) { + if (wp[j] == r and r < landed) { return j; } + j = j + 1; + } + return n; +} + +/// Where the sha of what the head becomes when link j lands begins: its own tip when it was FORWARD, else the commit made for it. +pub fn new_of(ev: string, j: u64, outcome: u8) -> usize { + if (outcome == GC_OUT_FORWARD) { return tip_off(ev, j); } + return body_from(ev, G_CT, j); +} + +/// Where the sha of the head link j lands on begins. +pub fn cur_for(ev: string, j: usize, wo: []u8, wp: []usize) -> usize { + const n : usize = q_count(ev); + const landed : usize = plan(ev, n, wo, wp); + const at : usize = wp[j]; + if (at == 0 or at >= landed) { return cur0(ev); } + const prev : usize = landing_at(ev, n, at - 1, wo, wp); + return new_of(ev, prev as u64, wo[prev]); +} + +// --- the push state --------------------------------------------------------------------------------- + +pub fn lines_in(ev: string, f: usize, t: usize) -> usize { + var c : usize = 0; + var p : usize = f; + while (p < t) { + if (line_end(ev, p) > p) { c = c + 1; } + p = line_end(ev, p) + 1; + } + return c; +} + +pub fn ahead_count(ev: string) -> usize { + return lines_in(ev, body_from(ev, P_AHEAD, 0), body_to(ev, P_AHEAD, 0)); +} + +pub fn batch_len(ev: string) -> usize { + const a : usize = ahead_count(ev); + if (a > GC_BATCH_MAX) { return GC_BATCH_MAX; } + return a; +} + +/// Minutes since refs/golden/pushed last moved; 100000 when it never did (a push is then due the moment there is a link). +pub fn minutes_since(ev: string) -> u32 { + if (exit_of(ev, P_STAMP, 0) != 0 or exit_of(ev, P_NOW, 0) != 0) { return 100000; } + const s : u64 = digits(ev, body_from(ev, P_STAMP, 0), line_end(ev, body_from(ev, P_STAMP, 0))); + const t : u64 = digits(ev, body_from(ev, P_NOW, 0), line_end(ev, body_from(ev, P_NOW, 0))); + if (s == 0 or t < s) { return 100000; } + return ((t - s) / 60) as u32; +} + +/// The Actions queue length: the number the caller passed, else the databaseId entries of `gh run list`, else 0. +pub fn queued_runs(r: GolReq, ev: string) -> u32 { + if (r.queued != ABSENT) { return r.queued as u32; } + if (exit_of(ev, P_QUEUED, 0) != 0) { return 0; } + var c : u32 = 0; + var p : usize = body_from(ev, P_QUEUED, 0); + const t : usize = body_to(ev, P_QUEUED, 0); + while (p < t) { + const at : usize = find_in(ev, p, t, "databaseId"); + if (at > t) { return c; } + c = c + 1; + p = at + 10; + } + return c; +} + +pub fn decision(r: GolReq, ev: string) -> u8 { + return when_to_push(minutes_since(ev), batch_len(ev), queued_runs(r, ev)); +} + +/// Where the sha of the batch's last link begins: the batch_len-th line of the unpushed list, oldest first. +pub fn target_off(ev: string) -> usize { + var p : usize = body_from(ev, P_AHEAD, 0); + var c : usize = 1; + while (c < batch_len(ev)) { + p = line_end(ev, p) + 1; + c = c + 1; + } + return p; +} + +// --- which slot runs next ------------------------------------------------------------------------------ + +pub fn slot_of(k: u8, n: u64) -> u64 { + return (k as u64) * SLOT + n; +} + +pub fn wants_global(r: GolReq, ev: string, k: u8) -> bool { + if (k == G_REFS or k == G_FILE) { return r.verb != V_PUSH; } + if (k == G_HEAD or k == G_BASE) { return true; } + if (r.verb == V_LAND) { return false; } + if (has_head(ev) == false) { return false; } + if (k == P_STAMP) { return sha_ok(ev, P_PUSHED, 0); } + if (k == P_QUEUED) { return r.queued == ABSENT; } + return true; +} + +/// The next slot of link j's verification, or DONE. +pub fn link_slot(ev: string, j: u64) -> u64 { + if (ran(ev, G_TIP, j) == false) { return slot_of(G_TIP, j); } + if (sha_ok(ev, G_TIP, j) == false) { return DONE; } + if (ran(ev, G_MB, j) == false) { return slot_of(G_MB, j); } + if (sha_ok(ev, G_MB, j) == false) { return DONE; } + if (ran(ev, G_CHG, j) == false) { return slot_of(G_CHG, j); } + if (exit_of(ev, G_CHG, j) != 0) { return DONE; } + if (ran(ev, G_LOG, j) == false) { return slot_of(G_LOG, j); } + if (spec_ok(ev, j) == false or gen_ok(ev, j) == false or targets(ev, j) == 0) { return DONE; } + if (ran(ev, G_WT, j) == false) { return slot_of(G_WT, j); } + if (exit_of(ev, G_WT, j) == 0) { + const m : usize = ch_count(ev, j); + const lk : u64 = j * TGT; + var i : usize = 0; + while (i < m) { + if (is_target(ev, ch_line(ev, j, i)) and ran(ev, G_SEAL, lk + (i as u64)) == false) { return slot_of(G_SEAL, lk + (i as u64)); } + i = i + 1; + } + i = 0; + while (i < m) { + if (is_target(ev, ch_line(ev, j, i)) and ran(ev, G_TEST, lk + (i as u64)) == false) { return slot_of(G_TEST, lk + (i as u64)); } + i = i + 1; + } + // the tests are clean: a seal that does not match is saved again and verified again (#11361), then the changed seals + // are one commit on the tip. Nothing but .trinity/seals/ may be in it. + if (all_targets(ev, j, G_TEST) == 0) { + i = 0; + while (i < m) { + const n : u64 = lk + (i as u64); + if (is_target(ev, ch_line(ev, j, i)) and seal_clean(ev, G_SEAL, n) == false and ran(ev, S_SAVE, n) == false) { return slot_of(S_SAVE, n); } + i = i + 1; + } + i = 0; + while (i < m) { + const n : u64 = lk + (i as u64); + if (is_target(ev, ch_line(ev, j, i)) and ran(ev, S_SAVE, n) and ran(ev, S_RECHECK, n) == false) { return slot_of(S_RECHECK, n); } + i = i + 1; + } + if (saved_any(ev, j) and all_targets(ev, j, G_SEAL) == 0) { + if (ran(ev, S_STAGE, lk) == false) { return slot_of(S_STAGE, lk); } + if (exit_of(ev, S_STAGE, lk) == 0 and ran(ev, S_DIFF, lk) == false) { return slot_of(S_DIFF, lk); } + if (exit_of(ev, S_STAGE, lk) == 0 and diff_ok(ev, j) and diff_changed(ev, j) > 0) { + if (ran(ev, S_COMMIT, lk) == false) { return slot_of(S_COMMIT, lk); } + if (exit_of(ev, S_COMMIT, lk) == 0 and ran(ev, S_TIP, lk) == false) { return slot_of(S_TIP, lk); } + } + } + } + if (ran(ev, G_RM, j) == false) { return slot_of(G_RM, j); } + } + return DONE; +} + +/// The next slot to run: the first one in order that is wanted and has no section yet; DONE when there is none. It never names +/// G_PRUNE, which the host runs by itself at the end. +pub fn next_slot(r: GolReq, ev: string, wo: []u8, wp: []usize) -> u64 { + var k : u8 = G_REFS; + while (k <= G_BASE) { + if (wants_global(r, ev, k) and ran(ev, k, 0) == false) { return slot_of(k, 0); } + k = k + 1; + } + if (sha_ok(ev, G_BASE, 0) == false) { return DONE; } + // The chain follows master (#11361): decided from the head, the pushed mark and whether master holds the head. The writes are + // update-ref with the old value, and not for a plan or a status. + if (sha_ok(ev, G_HEAD, 0)) { + if (ran(ev, P_PUSHED, 0) == false) { return slot_of(P_PUSHED, 0); } + if (sha_ok(ev, P_PUSHED, 0)) { + if (ran(ev, R_ANC, 0) == false) { return slot_of(R_ANC, 0); } + const rb : u8 = rb_action(ev); + if (rb == GC_RB_CARRY) { + if (ran(ev, R_MT, 0) == false) { return slot_of(R_MT, 0); } + if (sha_ok(ev, R_MT, 0) == false) { return DONE; } + if (ran(ev, R_CT, 0) == false) { return slot_of(R_CT, 0); } + if (sha_ok(ev, R_CT, 0) == false) { return DONE; } + } + if (rb != GC_RB_NONE and r.plan == false and r.verb != V_STATUS) { + if (ran(ev, R_UH, 0) == false) { return slot_of(R_UH, 0); } + if (exit_of(ev, R_UH, 0) != 0) { return DONE; } + if (ran(ev, R_UP, 0) == false) { return slot_of(R_UP, 0); } + if (exit_of(ev, R_UP, 0) != 0) { return DONE; } + } + } + } + if (r.verb == V_LAND) { + const n : usize = q_count(ev); + var j : usize = 0; + while (j < n) { + const s : u64 = link_slot(ev, j as u64); + if (s < DONE) { return s; } + j = j + 1; + } + const landed : usize = plan(ev, n, wo, wp); + var q : usize = 0; + while (q < landed) { + const l : usize = landing_at(ev, n, q, wo, wp); + if (wo[l] != GC_OUT_FORWARD) { + if (ran(ev, G_MT, l as u64) == false) { return slot_of(G_MT, l as u64); } + if (sha_ok(ev, G_MT, l as u64) == false) { return DONE; } + if (ran(ev, G_CT, l as u64) == false) { return slot_of(G_CT, l as u64); } + if (sha_ok(ev, G_CT, l as u64) == false) { return DONE; } + } + if (r.plan == false and ran(ev, G_CAS, l as u64) == false) { return slot_of(G_CAS, l as u64); } + if (r.plan == false and exit_of(ev, G_CAS, l as u64) != 0) { return DONE; } + q = q + 1; + } + return DONE; + } + k = P_PUSHED; + while (k <= P_QUEUED) { + if (wants_global(r, ev, k) and ran(ev, k, 0) == false) { return slot_of(k, 0); } + k = k + 1; + } + if (r.verb == V_PUSH and r.plan == false and decision(r, ev) == GC_PUSH_NOW) { + k = P_BRANCH; + while (k <= P_MARK) { + if (ran(ev, k, 0) == false) { return slot_of(k, 0); } + if (exit_of(ev, k, 0) != 0) { return DONE; } + k = k + 1; + } + } + return DONE; +} + +/// What a finished run of slot (k, n) means for the rest: C_OK goes on, anything else stops the pass. +pub fn judge(r: GolReq, ev: string, k: u8, n: u64) -> u8 { + const x : u64 = exit_of(ev, k, n); + if (k == G_BASE and sha_ok(ev, G_BASE, 0) == false) { return C_NO_BASE; } + // exit 1 of a link's merge-tree is a conflict: the link is CONFLICT and the pass goes on; any other failure stops it + if (k == G_MT and x > 1) { return C_STEP; } + if (k == R_MT and x == 1) { return C_MERGE; } + if (k == R_MT and x > 1) { return C_STEP; } + if (k == R_CT and sha_ok(ev, R_CT, 0) == false) { return C_STEP; } + if ((k == R_UH or k == R_UP) and x != 0) { return C_HEAD_MOVED; } + if (k == G_CT and sha_ok(ev, G_CT, n) == false) { return C_STEP; } + if (k == G_CAS and x != 0) { return C_HEAD_MOVED; } + if (k == P_LAND and x != 0) { return C_LAND_REFUSED; } + if ((k == P_BRANCH or k == P_MARK) and x != 0) { return C_STEP; } + return C_OK; +} + +pub fn in_scratch(k: u8) -> bool { + return k == G_SEAL or k == G_TEST or (k >= S_SAVE and k <= S_TIP); +} + +// --- the commands ------------------------------------------------------------------------------------ + +pub fn arg(out: []u8, w0: usize, s: string) -> usize { + const w : usize = emit_str(out, w0, s); + return emit_byte(out, w, 10); +} + +pub fn put_span(out: []u8, w: usize, t: string, a: usize, b: usize) -> usize { + var p : usize = w; + var k : usize = a; + while (k < b) { + p = emit_byte(out, p, t[k]); + k = k + 1; + } + return p; +} + +pub fn arg_span(out: []u8, w0: usize, t: string, a: usize, b: usize) -> usize { + const w : usize = put_span(out, w0, t, a, b); + return emit_byte(out, w, 10); +} + +pub fn sha_arg(out: []u8, w: usize, t: string, a: usize) -> usize { + return arg_span(out, w, t, a, a + 40); +} + +pub fn zero_arg(out: []u8, w: usize) -> usize { + var p : usize = w; + var k : usize = 0; + while (k < 40) { + p = emit_byte(out, p, 48); + k = k + 1; + } + return emit_byte(out, p, 10); +} + +pub fn git(out: []u8, w0: usize, a: string) -> usize { + const w : usize = arg(out, w0, "git"); + return arg(out, w, a); +} + +/// The worktree of link j: /gl-. +pub fn emit_wt(out: []u8, w0: usize, r: GolReq, j: u64) -> usize { + var w : usize = emit_str(out, w0, r.scratch); + w = emit_str(out, w, "/gl-"); + return emit_dec(out, w, j); +} + +/// The directory step k, entry n, runs in; 0 bytes means the caller's own. +pub fn step_cwd(r: GolReq, k: u8, n: u64, out: []u8) -> usize { + if (in_scratch(k) == false) { return 0; } + const w : usize = emit_wt(out, 0, r, n / TGT); + return fits(out, w); +} + +/// The branch of the push: claude/golden-<10 hex of the last link of the batch>. +pub fn emit_push_branch(out: []u8, w: usize, ev: string) -> usize { + var p : usize = emit_str(out, w, "claude/golden-"); + const f : usize = target_off(ev); + var k : usize = 0; + while (k < 10) { + p = emit_byte(out, p, ev[f + k]); + k = k + 1; + } + return p; +} + +/// Does the evidence hold what step k, entry j, writes into its command? Asked before any byte is written. +pub fn args_ready(ev: string, k: u8, j: u64) -> bool { + const cur : bool = has_head(ev) or sha_ok(ev, G_BASE, 0); + if (k == G_TIP) { return q_start(ev, j as usize) < ev.len; } + if (k == G_MB or k == G_MT) { return sha_ok(ev, G_TIP, j) and cur and (k == G_MB or sha_ok(ev, G_MB, j)); } + if (k == G_CHG or k == G_LOG) { return sha_ok(ev, G_TIP, j) and sha_ok(ev, G_MB, j); } + if (k == G_WT) { return sha_ok(ev, G_TIP, j); } + if (k == G_CT) { return sha_ok(ev, G_MT, j) and q_start(ev, j as usize) < ev.len; } + if (k == G_CAS) { return sha_ok(ev, G_TIP, j) and cur; } + if (k == P_AHEAD) { return has_head(ev) and (has_pushed(ev) or sha_ok(ev, G_BASE, 0)); } + if (k == R_ANC or k == R_MT) { return sha_ok(ev, G_HEAD, 0) and sha_ok(ev, P_PUSHED, 0) and sha_ok(ev, G_BASE, 0); } + if (k == R_CT) { return sha_ok(ev, R_MT, 0) and sha_ok(ev, G_BASE, 0); } + if (k == R_UH) { return sha_ok(ev, G_HEAD, 0) and sha_ok(ev, G_BASE, 0) and (rb_action(ev) != GC_RB_CARRY or sha_ok(ev, R_CT, 0)); } + if (k == R_UP) { return sha_ok(ev, P_PUSHED, 0) and sha_ok(ev, G_BASE, 0); } + if (k >= S_SAVE and k <= S_TIP) { return sha_ok(ev, G_TIP, j / TGT); } + if (k == P_BRANCH or k == P_LAND or k == P_MARK) { return ahead_count(ev) > 0 and has_head(ev); } + return true; +} + +/// One step's argument vector, one argument per line, no shell. The byte count, 0 when it does not fit or the slot has no command. +pub fn step_argv(r: GolReq, ev: string, k: u8, n: u64, out: []u8, wo: []u8, wp: []usize) -> usize { + var w : usize = 0; + const j : u64 = n % SLOT; + if (args_ready(ev, k, j) == false) { return 0; } + if (k == G_REFS) { + w = git(out, w, "for-each-ref"); + w = arg(out, w, "--format=%(refname:short)"); + w = arg(out, w, "refs/heads/claude/evolve-*"); + } + if (k == G_FILE) { + w = arg(out, w, "cat"); + w = arg(out, w, GL_QUEUE_FILE); + } + if (k == G_HEAD) { + w = git(out, w, "rev-parse"); + w = arg(out, w, "--verify"); + w = arg(out, w, "-q"); + w = arg(out, w, GC_REF); + } + if (k == G_BASE) { + w = git(out, w, "rev-parse"); + w = arg(out, w, "--verify"); + w = arg(out, w, "-q"); + w = arg(out, w, "refs/remotes/origin/master"); + } + if (k == G_TIP) { + const a : usize = q_start(ev, j as usize); + if (a >= ev.len) { return 0; } + w = git(out, w, "rev-parse"); + w = arg(out, w, "--verify"); + w = arg(out, w, "-q"); + w = emit_str(out, w, "refs/heads/"); + w = arg_span(out, w, ev, a, line_end(ev, a)); + } + if (k == G_MB) { + w = git(out, w, "merge-base"); + w = sha_arg(out, w, ev, body_from(ev, G_TIP, j)); + w = sha_arg(out, w, ev, cur0(ev)); + } + if (k == G_CHG) { + w = git(out, w, "diff"); + w = arg(out, w, "--name-status"); + w = arg(out, w, "--no-renames"); + w = sha_arg(out, w, ev, body_from(ev, G_MB, j)); + w = sha_arg(out, w, ev, body_from(ev, G_TIP, j)); + } + if (k == G_LOG) { + w = git(out, w, "log"); + w = arg(out, w, "--format=%B"); + w = put_span(out, w, ev, body_from(ev, G_MB, j), body_from(ev, G_MB, j) + 40); + w = emit_str(out, w, ".."); + w = sha_arg(out, w, ev, body_from(ev, G_TIP, j)); + } + if (k == G_WT) { + w = git(out, w, "worktree"); + w = arg(out, w, "add"); + w = arg(out, w, "-q"); + w = arg(out, w, "--detach"); + w = emit_wt(out, w, r, j); + w = emit_byte(out, w, 10); + w = sha_arg(out, w, ev, body_from(ev, G_TIP, j)); + } + if (k == G_SEAL or k == G_TEST or k == S_SAVE or k == S_RECHECK) { + const link : u64 = j / TGT; + const ls : usize = ch_line(ev, link, (j % TGT) as usize); + if (is_target(ev, ls) == false) { return 0; } + if (k != G_TEST) { + w = arg(out, w, r.t27c); + w = arg(out, w, "seal"); + w = arg_span(out, w, ev, ls + 2, line_end(ev, ls)); + if (k == S_SAVE) { w = arg(out, w, "--save"); } else { w = arg(out, w, "--verify"); } + } else { + w = arg(out, w, r.t27b); + w = arg(out, w, "test"); + w = arg_span(out, w, ev, ls + 2, line_end(ev, ls)); + } + } + if (k == S_STAGE) { + w = git(out, w, "add"); + w = arg(out, w, "-A"); + } + if (k == S_DIFF) { + w = git(out, w, "diff"); + w = arg(out, w, "--cached"); + w = arg(out, w, "--name-only"); + } + if (k == S_COMMIT) { + w = git(out, w, "-c"); + w = arg(out, w, "user.name=tri-golden"); + w = arg(out, w, "-c"); + w = arg(out, w, "user.email=golden@t27.ai"); + w = arg(out, w, "-c"); + w = arg(out, w, "commit.gpgsign=false"); + w = arg(out, w, "commit"); + w = arg(out, w, "--no-verify"); + w = arg(out, w, "-q"); + w = arg(out, w, "-m"); + w = emit_str(out, w, "golden: save the seals again on the tip (t27c seal --save, tests clean)"); + if (r.issue > 0) { + w = emit_str(out, w, " (Closes #"); + w = emit_dec(out, w, r.issue); + w = emit_str(out, w, ")"); + } + w = emit_byte(out, w, 10); + } + if (k == S_TIP) { + w = git(out, w, "rev-parse"); + w = arg(out, w, "--verify"); + w = arg(out, w, "-q"); + w = arg(out, w, "HEAD"); + } + if (k == R_ANC) { + w = git(out, w, "merge-base"); + w = arg(out, w, "--is-ancestor"); + w = sha_arg(out, w, ev, body_from(ev, G_HEAD, 0)); + w = sha_arg(out, w, ev, body_from(ev, G_BASE, 0)); + } + if (k == R_MT) { + w = git(out, w, "merge-tree"); + w = arg(out, w, "--write-tree"); + w = emit_str(out, w, "--merge-base="); + w = sha_arg(out, w, ev, body_from(ev, P_PUSHED, 0)); + w = sha_arg(out, w, ev, body_from(ev, G_BASE, 0)); + w = sha_arg(out, w, ev, body_from(ev, G_HEAD, 0)); + } + if (k == R_CT) { + w = git(out, w, "commit-tree"); + w = sha_arg(out, w, ev, body_from(ev, R_MT, 0)); + w = arg(out, w, "-p"); + w = sha_arg(out, w, ev, body_from(ev, G_BASE, 0)); + w = arg(out, w, "-m"); + w = emit_str(out, w, "golden: carry the unpushed links onto origin/master"); + if (r.issue > 0) { + w = emit_str(out, w, " (Closes #"); + w = emit_dec(out, w, r.issue); + w = emit_str(out, w, ")"); + } + w = emit_byte(out, w, 10); + } + if (k == R_UH) { + w = git(out, w, "update-ref"); + w = arg(out, w, GC_REF); + if (rb_action(ev) == GC_RB_CARRY) { w = sha_arg(out, w, ev, body_from(ev, R_CT, 0)); } else { w = sha_arg(out, w, ev, body_from(ev, G_BASE, 0)); } + w = sha_arg(out, w, ev, body_from(ev, G_HEAD, 0)); + } + if (k == R_UP) { + w = git(out, w, "update-ref"); + w = arg(out, w, "--create-reflog"); + w = arg(out, w, GL_PUSHED); + w = sha_arg(out, w, ev, body_from(ev, G_BASE, 0)); + w = sha_arg(out, w, ev, body_from(ev, P_PUSHED, 0)); + } + if (k == G_RM) { + w = git(out, w, "worktree"); + w = arg(out, w, "remove"); + w = arg(out, w, "--force"); + w = emit_wt(out, w, r, j); + w = emit_byte(out, w, 10); + } + if (k == G_MT or k == G_CT or k == G_CAS) { + const cur : usize = cur_for(ev, j as usize, wo, wp); + if (k == G_MT) { + w = git(out, w, "merge-tree"); + w = arg(out, w, "--write-tree"); + w = emit_str(out, w, "--merge-base="); + w = sha_arg(out, w, ev, body_from(ev, G_MB, j)); + w = sha_arg(out, w, ev, cur); + w = sha_arg(out, w, ev, tip_off(ev, j)); + } + if (k == G_CT) { + w = git(out, w, "commit-tree"); + w = sha_arg(out, w, ev, body_from(ev, G_MT, j)); + w = arg(out, w, "-p"); + w = sha_arg(out, w, ev, cur); + w = arg(out, w, "-m"); + w = emit_str(out, w, "golden: carry "); + w = put_span(out, w, ev, q_start(ev, j as usize), line_end(ev, q_start(ev, j as usize))); + if (r.issue > 0) { + w = emit_str(out, w, " (Closes #"); + w = emit_dec(out, w, r.issue); + w = emit_str(out, w, ")"); + } + w = emit_byte(out, w, 10); + } + if (k == G_CAS) { + const landed : usize = plan(ev, q_count(ev), wo, wp); + w = git(out, w, "update-ref"); + w = arg(out, w, GC_REF); + w = sha_arg(out, w, ev, new_of(ev, j, wo[j as usize])); + if (wp[j as usize] == 0 and has_head(ev) == false) { + w = zero_arg(out, w); + } else { + w = sha_arg(out, w, ev, cur); + } + } + } + if (k == P_PUSHED) { + w = git(out, w, "rev-parse"); + w = arg(out, w, "--verify"); + w = arg(out, w, "-q"); + w = arg(out, w, GL_PUSHED); + } + if (k == P_AHEAD) { + w = git(out, w, "rev-list"); + w = arg(out, w, "--first-parent"); + w = arg(out, w, "--reverse"); + if (has_pushed(ev)) { + w = put_span(out, w, ev, pushed_off(ev), pushed_off(ev) + 40); + } else { + w = put_span(out, w, ev, body_from(ev, G_BASE, 0), body_from(ev, G_BASE, 0) + 40); + } + w = emit_str(out, w, ".."); + w = sha_arg(out, w, ev, head_off(ev)); + } + if (k == P_STAMP) { + w = git(out, w, "reflog"); + w = arg(out, w, "show"); + w = arg(out, w, "--format=%ct"); + w = arg(out, w, "-n"); + w = arg(out, w, "1"); + w = arg(out, w, GL_PUSHED); + } + if (k == P_NOW) { + w = arg(out, w, "date"); + w = arg(out, w, "+%s"); + } + if (k == P_QUEUED) { + w = arg(out, w, "gh"); + w = arg(out, w, "run"); + w = arg(out, w, "list"); + w = arg(out, w, "--status"); + w = arg(out, w, "queued"); + w = arg(out, w, "--limit"); + w = arg(out, w, "300"); + w = arg(out, w, "--json"); + w = arg(out, w, "databaseId"); + } + if (k == P_BRANCH) { + w = git(out, w, "branch"); + w = arg(out, w, "-f"); + w = emit_push_branch(out, w, ev); + w = emit_byte(out, w, 10); + w = sha_arg(out, w, ev, target_off(ev)); + } + if (k == P_LAND) { + w = arg(out, w, r.tri); + w = arg(out, w, "land"); + w = emit_push_branch(out, w, ev); + w = emit_byte(out, w, 10); + w = arg(out, w, "--issue"); + w = emit_dec(out, w, r.issue); + w = emit_byte(out, w, 10); + } + if (k == P_MARK) { + w = git(out, w, "update-ref"); + w = arg(out, w, "--create-reflog"); + w = arg(out, w, GL_PUSHED); + w = sha_arg(out, w, ev, target_off(ev)); + if (has_pushed(ev)) { + w = sha_arg(out, w, ev, pushed_off(ev)); + } else { + w = zero_arg(out, w); + } + } + if (k == G_PRUNE) { + w = git(out, w, "worktree"); + w = arg(out, w, "prune"); + } + return fits(out, w); +} + +// --- what the command prints --------------------------------------------------------------------------- + +pub fn exit_code(c: u8) -> u8 { + return c; +} + +pub fn code_name(c: u8) -> string { + if (c == C_OK) { return "OK"; } + if (c == C_BAD_VERB) { return "BAD_VERB"; } + if (c == C_BAD_ISSUE) { return "BAD_ISSUE"; } + if (c == C_BAD_PATH) { return "BAD_PATH"; } + if (c == C_NO_BASE) { return "NO_BASE"; } + if (c == C_MERGE) { return "MERGE_CONFLICT"; } + if (c == C_STEP) { return "STEP_FAILED"; } + if (c == C_HEAD_MOVED) { return "HEAD_MOVED"; } + if (c == C_LAND_REFUSED) { return "LAND_REFUSED"; } + return "ODDS"; +} + +pub fn code_why(c: u8) -> string { + if (c == C_OK) { return "done"; } + if (c == C_BAD_VERB) { return "the verbs are land, push and status"; } + if (c == C_BAD_ISSUE) { return "a push opens a pull request: pass --issue N (L1)"; } + if (c == C_BAD_PATH) { return "a program or scratch path has bytes a command line should not carry"; } + if (c == C_NO_BASE) { return "refs/remotes/origin/master is missing: git fetch origin master"; } + if (c == C_MERGE) { return "git merge-tree met a conflict carrying the unpushed links onto origin/master: a person decides"; } + if (c == C_STEP) { return "a git step failed; its output is the evidence"; } + if (c == C_HEAD_MOVED) { return "refs/golden/head is not where this pass read it: nothing was forced, run it again"; } + if (c == C_LAND_REFUSED) { return "tri land refused the batch branch; refs/golden/pushed did not move"; } + return "unknown"; +} + +pub fn outcome_name(o: u8) -> string { + if (o == GC_OUT_FORWARD) { return "FORWARD"; } + if (o == GC_OUT_CARRIED) { return "CARRIED"; } + if (o == GC_OUT_REGEN) { return "REGEN"; } + if (o == GC_OUT_REJUDGE) { return "REJUDGE"; } + if (o == GC_OUT_CONFLICT) { return "CONFLICT"; } + if (o == GC_OUT_NOT_GOLDEN) { return "NOT_GOLDEN"; } + return "UNKNOWN_PARENT"; +} + +/// The outcome to print: CONFLICT when merge-tree found the link in conflict, REGEN when its seals were saved again and it landed. +pub fn outcome_shown(ev: string, i: usize, o: u8) -> u8 { + if (exit_of(ev, G_MT, i as u64) == 1) { return GC_OUT_CONFLICT; } + return gc_regen_view(o, regen_tip_ok(ev, i as u64)); +} + +pub fn verdict_letter(v: u8) -> u8 { + if (v == GC_V_PASS) { return 112; } + if (v == GC_V_REUSED) { return 114; } + if (v == GC_V_FAIL) { return 102; } + return 45; +} + +/// Where the sha of step k begins when it printed one, else ev.len. +pub fn off_if(ev: string, k: u8, n: u64) -> usize { + if (sha_ok(ev, k, n)) { return body_from(ev, k, n); } + return ev.len; +} + +pub fn put_sha12(out: []u8, w: usize, ev: string, a: usize) -> usize { + if (a >= ev.len) { return emit_str(out, w, "none"); } + return put_span(out, w, ev, a, a + 12); +} + +/// The command a slot runs, on one line ("would run: ..."): the argument vector with blanks for its newlines. +pub fn put_cmd(out: []u8, w: usize, r: GolReq, ev: string, k: u8, n: u64, wo: []u8, wp: []usize, tmp: []u8) -> usize { + const m : usize = step_argv(r, ev, k, n, tmp, wo, wp); + var p : usize = emit_str(out, w, " would run: "); + var i : usize = 0; + while (i + 1 < m) { + if (tmp[i] == 10) { p = emit_byte(out, p, 32); } else { p = emit_byte(out, p, tmp[i]); } + i = i + 1; + } + return emit_byte(out, p, 10); +} + +pub fn report_land(r: GolReq, ev: string, out: []u8, wo: []u8, wp: []usize, tmp: []u8) -> usize { + var w : usize = emit_str(out, 0, "golden head: "); + w = put_sha12(out, w, ev, head_off(ev)); + if (has_head(ev) == false) { + w = emit_str(out, w, " (base "); + w = put_sha12(out, w, ev, off_if(ev, G_BASE, 0)); + w = emit_byte(out, w, 41); + } + const n : usize = q_count(ev); + w = emit_byte(out, w, 10); + w = emit_str(out, w, "queue: "); + w = emit_dec(out, w, n as u64); + w = emit_byte(out, w, 10); + const landed : usize = plan(ev, n, wo, wp); + var i : usize = 0; + while (i < n) { + w = put_span(out, w, ev, q_start(ev, i), line_end(ev, q_start(ev, i))); + w = emit_byte(out, w, 32); + var st : u8 = 0; + while (st < 5) { w = emit_byte(out, w, verdict_letter(verdict(ev, i as u64, st))); st = st + 1; } + w = emit_byte(out, w, 32); + w = emit_str(out, w, outcome_name(outcome_shown(ev, i, wo[i]))); + w = emit_byte(out, w, 10); + i = i + 1; + } + w = emit_str(out, w, "landed: "); + w = emit_dec(out, w, landed as u64); + w = emit_str(out, w, " of "); + w = emit_dec(out, w, n as u64); + w = emit_str(out, w, "\n"); + var q : usize = 0; + while (q < landed) { + const l : usize = landing_at(ev, n, q, wo, wp); + if (r.plan) { w = put_cmd(out, w, r, ev, G_CAS, l as u64, wo, wp, tmp); } + q = q + 1; + } + return w; +} + +pub fn report_push(r: GolReq, ev: string, out: []u8, wo: []u8, wp: []usize, tmp: []u8) -> usize { + var w : usize = emit_str(out, 0, "golden head: "); + w = put_sha12(out, w, ev, head_off(ev)); + w = emit_str(out, w, "\npushed: "); + w = put_sha12(out, w, ev, pushed_off(ev)); + w = emit_str(out, w, "\nunpushed links: "); + w = emit_dec(out, w, ahead_count(ev) as u64); + w = emit_str(out, w, " (batch "); + w = emit_dec(out, w, batch_len(ev) as u64); + w = emit_str(out, w, ", cap 8)\nminutes since the last push: "); + w = emit_dec(out, w, minutes_since(ev) as u64); + w = emit_str(out, w, "\nactions queued: "); + w = emit_dec(out, w, queued_runs(r, ev) as u64); + w = emit_str(out, w, "\nnext push: "); + const d : u8 = decision(r, ev); + if (d == GC_PUSH_NOTHING) { w = emit_str(out, w, "NOTHING (no link is waiting)\n"); } + if (d == GC_PUSH_NOW) { w = emit_str(out, w, "NOW\n"); } + if (d == GC_PUSH_WAIT) { + var window : u32 = GC_WINDOW_MIN; + if (queued_runs(r, ev) >= GC_CI_JAM) { window = GC_WINDOW_MIN * 2; } + w = emit_str(out, w, "WAIT "); + w = emit_dec(out, w, (window - minutes_since(ev)) as u64); + w = emit_str(out, w, " more minute(s)\n"); + } + if (r.verb == V_PUSH and d == GC_PUSH_NOW) { + var k : u8 = P_BRANCH; + while (k <= P_MARK) { + if (r.plan) { w = put_cmd(out, w, r, ev, k, 0, wo, wp, tmp); } + k = k + 1; + } + } + return w; +} + +pub fn report_queue_status(ev: string, out: []u8, w0: usize) -> usize { + var w : usize = emit_str(out, w0, "queue:"); + var i : usize = 0; + while (i < q_count(ev)) { + w = emit_byte(out, w, 32); + w = put_span(out, w, ev, q_start(ev, i), line_end(ev, q_start(ev, i))); + i = i + 1; + } + return emit_byte(out, w, 10); +} + +/// The whole text the command prints at the end, then the refusal if there is one. The byte count (0 when it does not fit). +pub fn report(r: GolReq, ev: string, code: u8, out: []u8, wo: []u8, wp: []usize, tmp: []u8) -> usize { + var w : usize = 0; + if (code >= C_BAD_VERB and code <= C_NO_BASE) { + w = emit_str(out, 0, "refused: "); + w = emit_str(out, w, code_name(code)); + w = emit_str(out, w, ": "); + w = emit_str(out, w, code_why(code)); + w = emit_byte(out, w, 10); + return fits(out, w); + } + if (r.verb == V_LAND) { w = report_land(r, ev, out, wo, wp, tmp); } + if (r.verb != V_LAND) { w = report_push(r, ev, out, wo, wp, tmp); } + if (r.verb == V_STATUS) { w = report_queue_status(ev, out, w); } + if (code != C_OK) { + w = emit_str(out, w, "refused: "); + w = emit_str(out, w, code_name(code)); + w = emit_str(out, w, ": "); + w = emit_str(out, w, code_why(code)); + w = emit_byte(out, w, 10); + } + return fits(out, w); +} + +// --- fixtures: the evidence of real runs, shortened (shas are repeated bytes) --------------------------------- +const FB : str = "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1"; +const FT1 : str = "b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2"; +const FT2 : str = "c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3"; +const FH : str = "d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4"; +const FNEW : str = "e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5"; +const FTREE : str = "f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6"; +const EV_G : str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n"; +const EV_TIP0 : str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n"; +const EV_MB0 : str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n"; +const EV_CHG0 : str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n"; +const EV_LOG0 : str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n"; +const EV_WT0 : str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n"; +const EV_SEAL0 : str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n"; +const EV_TEST0 : str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 0 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n"; +const EV_RM0 : str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 0 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 11 0 0 0\n\n"; +const EV_V : str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 0 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 11 0 0 0\n\n@@ preflight 4 1 0 41\nc3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3\n\n@@ preflight 5 1 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 1 0 16\nA\tspecs/x/b.t27\n\n@@ preflight 7 1 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 1 0 0\n\n@@ preflight 9 1000 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 1000 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 11 1 0 0\n\n"; +const EV_C0 : str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 0 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 11 0 0 0\n\n@@ preflight 4 1 0 41\nc3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3\n\n@@ preflight 5 1 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 1 0 16\nA\tspecs/x/b.t27\n\n@@ preflight 7 1 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 1 0 0\n\n@@ preflight 9 1000 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 1000 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 11 1 0 0\n\n@@ preflight 14 0 0 0\n\n"; +const EV_M1 : str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 0 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 11 0 0 0\n\n@@ preflight 4 1 0 41\nc3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3\n\n@@ preflight 5 1 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 1 0 16\nA\tspecs/x/b.t27\n\n@@ preflight 7 1 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 1 0 0\n\n@@ preflight 9 1000 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 1000 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 11 1 0 0\n\n@@ preflight 14 0 0 0\n\n@@ preflight 12 1 0 41\nf6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6\n\n"; +const EV_K1 : str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 0 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 11 0 0 0\n\n@@ preflight 4 1 0 41\nc3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3\n\n@@ preflight 5 1 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 1 0 16\nA\tspecs/x/b.t27\n\n@@ preflight 7 1 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 1 0 0\n\n@@ preflight 9 1000 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 1000 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 11 1 0 0\n\n@@ preflight 14 0 0 0\n\n@@ preflight 12 1 0 41\nf6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6\n\n@@ preflight 13 1 0 41\ne5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5\n\n"; +const EV_ALL : str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 0 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 11 0 0 0\n\n@@ preflight 4 1 0 41\nc3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3\n\n@@ preflight 5 1 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 1 0 16\nA\tspecs/x/b.t27\n\n@@ preflight 7 1 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 1 0 0\n\n@@ preflight 9 1000 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 1000 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 11 1 0 0\n\n@@ preflight 14 0 0 0\n\n@@ preflight 12 1 0 41\nf6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6\n\n@@ preflight 13 1 0 41\ne5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5\n\n@@ preflight 14 1 0 0\n\n"; +const EV_PLAN_K1 : str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 0 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 11 0 0 0\n\n@@ preflight 4 1 0 41\nc3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3\n\n@@ preflight 5 1 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 1 0 16\nA\tspecs/x/b.t27\n\n@@ preflight 7 1 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 1 0 0\n\n@@ preflight 9 1000 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 1000 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 11 1 0 0\n\n@@ preflight 12 1 0 41\nf6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6\n\n@@ preflight 13 1 0 41\ne5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5\n\n"; +const EV_CONF : str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 0 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 11 0 0 0\n\n@@ preflight 4 1 0 41\nc3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3\n\n@@ preflight 5 1 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 1 0 16\nM\tspecs/x/a.t27\n\n@@ preflight 7 1 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 1 0 0\n\n@@ preflight 9 1000 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 1000 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 11 1 0 0\n\n"; +const EV_SEALBAD : str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 1 20\nVERIFICATION FAILED\n\n@@ preflight 10 0 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 29 0 0 0\n\n@@ preflight 30 0 1 20\nVERIFICATION FAILED\n\n@@ preflight 11 0 0 0\n\n@@ preflight 4 1 0 41\nc3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3\n\n@@ preflight 5 1 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 1 0 16\nA\tspecs/x/b.t27\n\n@@ preflight 7 1 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 1 0 0\n\n@@ preflight 9 1000 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 1000 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 11 1 0 0\n\n"; +const EV_NOCLOSES : str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 7\ngolden\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 0 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 11 0 0 0\n\n@@ preflight 4 1 0 41\nc3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3\n\n@@ preflight 5 1 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 1 0 16\nA\tspecs/x/b.t27\n\n@@ preflight 7 1 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 1 0 0\n\n@@ preflight 9 1000 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 1000 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 11 1 0 0\n\n"; +const EV_GENONLY : str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nM\tgen/rust/x.rs\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n"; +const EV_VACUOUS : str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 0 0 34\nTriX: 0 passed, 0 failed, 0 total\n\n@@ preflight 11 0 0 0\n\n@@ preflight 4 1 0 41\nc3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3\n\n@@ preflight 5 1 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 1 0 16\nA\tspecs/x/b.t27\n\n@@ preflight 7 1 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 1 0 0\n\n@@ preflight 9 1000 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 1000 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 11 1 0 0\n\n"; +const EV_MOVED : str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 0 41\nd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 0 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 11 0 0 0\n\n@@ preflight 4 1 0 41\nc3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3\n\n@@ preflight 5 1 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 1 0 16\nA\tspecs/x/b.t27\n\n@@ preflight 7 1 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 1 0 0\n\n@@ preflight 9 1000 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 1000 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 11 1 0 0\n\n@@ preflight 15 0 1 0\n\n"; +const EV_HEADED : str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 0 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 11 0 0 0\n\n@@ preflight 15 0 1 0\n\n"; +const EV_NOBASE : str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 1 0\n\n"; +const EV_Q : str = "@@ preflight 0 0 0 26\nclaude/evolve-a\nmaster\n-x\n\n@@ preflight 1 0 0 39\n# comment\nclaude/evolve-a\nlanes/extra\n\n\n"; +const EV_P_FIRST : str = "@@ preflight 2 0 0 41\nd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 15 0 1 0\n\n@@ preflight 16 0 0 123\n1010101010101010101010101010101010101010\n1111111111111111111111111111111111111111\n1212121212121212121212121212121212121212\n\n@@ preflight 18 0 0 11\n1760000000\n\n"; +const EV_P_NONE : str = "@@ preflight 2 0 0 41\nd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 15 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 16 0 0 0\n\n@@ preflight 17 0 0 11\n1759994000\n\n@@ preflight 18 0 0 11\n1760000000\n\n@@ preflight 24 0 0 0\n\n"; +const EV_P_20 : str = "@@ preflight 2 0 0 41\nd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 15 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 16 0 0 123\n1010101010101010101010101010101010101010\n1111111111111111111111111111111111111111\n1212121212121212121212121212121212121212\n\n@@ preflight 17 0 0 11\n1759998800\n\n@@ preflight 18 0 0 11\n1760000000\n\n@@ preflight 24 0 0 0\n\n"; +const EV_P_30 : str = "@@ preflight 2 0 0 41\nd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 15 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 16 0 0 123\n1010101010101010101010101010101010101010\n1111111111111111111111111111111111111111\n1212121212121212121212121212121212121212\n\n@@ preflight 17 0 0 11\n1759998200\n\n@@ preflight 18 0 0 11\n1760000000\n\n@@ preflight 24 0 0 0\n\n"; +const EV_P_TEN : str = "@@ preflight 2 0 0 41\nd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 15 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 16 0 0 410\n1010101010101010101010101010101010101010\n1111111111111111111111111111111111111111\n1212121212121212121212121212121212121212\n1313131313131313131313131313131313131313\n1414141414141414141414141414141414141414\n1515151515151515151515151515151515151515\n1616161616161616161616161616161616161616\n1717171717171717171717171717171717171717\n1818181818181818181818181818181818181818\n1919191919191919191919191919191919191919\n\n@@ preflight 17 0 0 11\n1759998200\n\n@@ preflight 18 0 0 11\n1760000000\n\n@@ preflight 24 0 0 0\n\n"; +const EV_P_GH : str = "@@ preflight 2 0 0 41\nd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 15 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 16 0 0 123\n1010101010101010101010101010101010101010\n1111111111111111111111111111111111111111\n1212121212121212121212121212121212121212\n\n@@ preflight 17 0 0 11\n1759998800\n\n@@ preflight 18 0 0 11\n1760000000\n\n@@ preflight 19 0 0 53\n[{\"databaseId\":1},{\"databaseId\":2},{\"databaseId\":3}]\n\n@@ preflight 24 0 0 0\n\n"; +const EV_P_BRANCHED : str = "@@ preflight 2 0 0 41\nd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 15 0 1 0\n\n@@ preflight 16 0 0 123\n1010101010101010101010101010101010101010\n1111111111111111111111111111111111111111\n1212121212121212121212121212121212121212\n\n@@ preflight 18 0 0 11\n1760000000\n\n@@ preflight 20 0 0 0\n\n"; +const EV_P_LANDED : str = "@@ preflight 2 0 0 41\nd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 15 0 1 0\n\n@@ preflight 16 0 0 123\n1010101010101010101010101010101010101010\n1111111111111111111111111111111111111111\n1212121212121212121212121212121212121212\n\n@@ preflight 18 0 0 11\n1760000000\n\n@@ preflight 20 0 0 0\n\n@@ preflight 21 0 0 7\nLANDED\n\n"; +const EV_P_REFUSED : str = "@@ preflight 2 0 0 41\nd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 15 0 1 0\n\n@@ preflight 16 0 0 123\n1010101010101010101010101010101010101010\n1111111111111111111111111111111111111111\n1212121212121212121212121212121212121212\n\n@@ preflight 18 0 0 11\n1760000000\n\n@@ preflight 20 0 0 0\n\n@@ preflight 21 0 10 16\nTESTS_NOT_CLEAN\n\n"; +const FS0 : str = "1010101010101010101010101010101010101010"; +const FS1 : str = "1111111111111111111111111111111111111111"; +const FS7 : str = "1717171717171717171717171717171717171717"; +const FS8 : str = "1818181818181818181818181818181818181818"; +const FS9 : str = "1919191919191919191919191919191919191919"; +const EV_RG1 : str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 1 20\nVERIFICATION FAILED\n\n"; +const EV_RG2 : str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 1 20\nVERIFICATION FAILED\n\n@@ preflight 10 0 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n"; +const EV_RG3 : str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 1 20\nVERIFICATION FAILED\n\n@@ preflight 10 0 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 29 0 0 0\n\n"; +const EV_RG4 : str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 1 20\nVERIFICATION FAILED\n\n@@ preflight 10 0 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 29 0 0 0\n\n@@ preflight 30 0 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n"; +const EV_RG5 : str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 1 20\nVERIFICATION FAILED\n\n@@ preflight 10 0 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 29 0 0 0\n\n@@ preflight 30 0 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 31 0 0 0\n\n"; +const EV_RG6 : str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 1 20\nVERIFICATION FAILED\n\n@@ preflight 10 0 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 29 0 0 0\n\n@@ preflight 30 0 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 31 0 0 0\n\n@@ preflight 32 0 0 30\n.trinity/seals/specs_x_a.json\n\n"; +const EV_RG7 : str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 1 20\nVERIFICATION FAILED\n\n@@ preflight 10 0 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 29 0 0 0\n\n@@ preflight 30 0 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 31 0 0 0\n\n@@ preflight 32 0 0 30\n.trinity/seals/specs_x_a.json\n\n@@ preflight 33 0 0 0\n\n"; +const EV_RG8 : str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 1 20\nVERIFICATION FAILED\n\n@@ preflight 10 0 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 29 0 0 0\n\n@@ preflight 30 0 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 31 0 0 0\n\n@@ preflight 32 0 0 30\n.trinity/seals/specs_x_a.json\n\n@@ preflight 33 0 0 0\n\n@@ preflight 34 0 0 41\n9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a\n\n"; +const EV_REGEN : str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 1 20\nVERIFICATION FAILED\n\n@@ preflight 10 0 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 29 0 0 0\n\n@@ preflight 30 0 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 31 0 0 0\n\n@@ preflight 32 0 0 30\n.trinity/seals/specs_x_a.json\n\n@@ preflight 33 0 0 0\n\n@@ preflight 34 0 0 41\n9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a\n\n@@ preflight 11 0 0 0\n\n"; +const EV_RGBAD : str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 1 20\nVERIFICATION FAILED\n\n@@ preflight 10 0 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 29 0 0 0\n\n@@ preflight 30 0 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 31 0 0 0\n\n@@ preflight 32 0 0 48\n.trinity/seals/specs_x_a.json\nspecs/x/other.t27\n\n@@ preflight 11 0 0 0\n\n"; +const EV_RGTEST : str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 1 20\nVERIFICATION FAILED\n\n@@ preflight 10 0 1 34\nTriX: 2 passed, 1 failed, 3 total\n\n@@ preflight 11 0 0 0\n\n"; +const EV_RGOK : str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 0 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 11 0 0 0\n\n"; +const EV_RB_RESET : str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 0 41\nd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 15 0 0 41\nd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4\n\n@@ preflight 24 0 1 0\n\n"; +const EV_RB_RESET_UH : str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 0 41\nd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 15 0 0 41\nd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4\n\n@@ preflight 24 0 1 0\n\n@@ preflight 27 0 0 0\n\n"; +const EV_RB_HELD : str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 0 41\nd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 15 0 0 41\nd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4\n\n@@ preflight 24 0 0 0\n\n"; +const EV_RB_CARRY : str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 0 41\nd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 15 0 0 41\n9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b\n\n@@ preflight 24 0 1 0\n\n"; +const EV_RB_CMT : str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 0 41\nd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 15 0 0 41\n9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b\n\n@@ preflight 24 0 1 0\n\n@@ preflight 25 0 0 41\nf6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6\n\n"; +const EV_RB_CCT : str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 0 41\nd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 15 0 0 41\n9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b\n\n@@ preflight 24 0 1 0\n\n@@ preflight 25 0 0 41\nf6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6\n\n@@ preflight 26 0 0 41\ne5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5\n\n"; +const EV_RB_CONFLICT : str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 0 41\nd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 15 0 0 41\n9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b\n\n@@ preflight 24 0 1 0\n\n@@ preflight 25 0 1 60\nf6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6\nCONFLICT (content)\n\n"; +const EV_C1X : str = "@@ preflight 0 0 0 32\nclaude/evolve-a\nclaude/evolve-b\n\n@@ preflight 1 0 1 54\ncat: .trinity/golden/queue: No such file or directory\n\n@@ preflight 2 0 1 0\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 4 0 0 41\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n\n@@ preflight 5 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 0 0 16\nA\tspecs/x/a.t27\n\n@@ preflight 7 0 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 0 0 0\n\n@@ preflight 9 0 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 0 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 11 0 0 0\n\n@@ preflight 4 1 0 41\nc3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3\n\n@@ preflight 5 1 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 6 1 0 16\nA\tspecs/x/b.t27\n\n@@ preflight 7 1 0 22\ngolden\n\nCloses #11275\n\n@@ preflight 8 1 0 0\n\n@@ preflight 9 1000 0 36\ngen_hash_c: MATCH\n\nall hashes MATCH\n\n@@ preflight 10 1000 0 34\nTriX: 3 passed, 0 failed, 3 total\n\n@@ preflight 11 1 0 0\n\n@@ preflight 14 0 0 0\n\n@@ preflight 12 1 1 57\ncccc\nCONFLICT (content): Merge conflict in specs/x/b.t27\n\n"; + +// --- tests ----------------------------------------------------------------------------------------------- + +const T_ZERO : str = "0000000000000000000000000000000000000000"; + +pub fn rq(verb: u8, plan: bool, queued: u64) -> GolReq { + return GolReq { verb: verb, plan: plan, queued: queued, issue: 11275, scratch: "/tmp/s", t27b: "t27b", t27c: "t27c", tri: "/tmp/tri" }; +} + +pub fn argv_is(r: GolReq, ev: string, k: u8, n: u64, want: string, buf: []u8, wo: []u8, wp: []usize) -> bool { + const m : usize = step_argv(r, ev, k, n, buf, wo, wp); + return m > 0 and buf_is(buf, m, want); +} + +pub fn name_is(ev: string, i: usize, want: string) -> bool { + return span_is(ev, q_start(ev, i), line_end(ev, q_start(ev, i)), want); +} + +test the_verbs_and_the_request_are_checked_before_any_step { + var wo : [16]u8 = [0] ** 16; + var wp : [16]usize = [0] ** 16; + var wt : [1024]u8 = [0] ** 1024; + assert verb_of("land") == V_LAND and verb_of("push") == V_PUSH and verb_of("status") == V_STATUS; + assert verb_of("pull") == V_BAD and verb_of("") == V_BAD; + assert request_refusal(rq(V_LAND, false, ABSENT)) == C_OK; + assert request_refusal(rq(V_BAD, false, ABSENT)) == C_BAD_VERB; + const noissue : GolReq = GolReq { verb: V_PUSH, plan: false, queued: ABSENT, issue: 0, scratch: "/tmp/s", t27b: "t27b", t27c: "t27c", tri: "/tmp/tri" }; + assert request_refusal(noissue) == C_BAD_ISSUE; + const planned : GolReq = GolReq { verb: V_PUSH, plan: true, queued: ABSENT, issue: 0, scratch: "/tmp/s", t27b: "t27b", t27c: "t27c", tri: "/tmp/tri" }; + assert request_refusal(planned) == C_OK; + const badpath : GolReq = GolReq { verb: V_LAND, plan: true, queued: ABSENT, issue: 1, scratch: "/tmp/a b", t27b: "t27b", t27c: "t27c", tri: "/tmp/tri" }; + assert request_refusal(badpath) == C_BAD_PATH; +} + +test the_queue_is_the_evolve_branches_then_the_file_without_trunks_comments_or_twins { + var wo : [16]u8 = [0] ** 16; + var wp : [16]usize = [0] ** 16; + var wt : [1024]u8 = [0] ** 1024; + assert q_count(EV_G) == 2; + assert name_is(EV_G, 0, "claude/evolve-a") and name_is(EV_G, 1, "claude/evolve-b"); + // master, a leading dash, a comment, a blank line and a name already listed are not links + assert q_count(EV_Q) == 2; + assert name_is(EV_Q, 0, "claude/evolve-a") and name_is(EV_Q, 1, "lanes/extra"); + assert q_count("") == 0; +} + +test the_slots_run_in_a_fixed_order_link_by_link { + var wo : [16]u8 = [0] ** 16; + var wp : [16]usize = [0] ** 16; + var wt : [1024]u8 = [0] ** 1024; + const r : GolReq = rq(V_LAND, false, ABSENT); + assert next_slot(r, "", &wo, &wp) == slot_of(G_REFS, 0); + assert next_slot(r, EV_Q, &wo, &wp) == slot_of(G_HEAD, 0); + assert next_slot(r, EV_G, &wo, &wp) == slot_of(G_TIP, 0); + assert next_slot(r, EV_TIP0, &wo, &wp) == slot_of(G_MB, 0); + assert next_slot(r, EV_MB0, &wo, &wp) == slot_of(G_CHG, 0); + assert next_slot(r, EV_CHG0, &wo, &wp) == slot_of(G_LOG, 0); + assert next_slot(r, EV_LOG0, &wo, &wp) == slot_of(G_WT, 0); + assert next_slot(r, EV_WT0, &wo, &wp) == slot_of(G_SEAL, 0); + assert next_slot(r, EV_SEAL0, &wo, &wp) == slot_of(G_TEST, 0); + assert next_slot(r, EV_TEST0, &wo, &wp) == slot_of(G_RM, 0); + assert next_slot(r, EV_RM0, &wo, &wp) == slot_of(G_TIP, 1); +} + +test the_landing_follows_the_verification_and_ends_when_every_landed_link_is_written { + var wo : [16]u8 = [0] ** 16; + var wp : [16]usize = [0] ** 16; + var wt : [1024]u8 = [0] ** 1024; + const r : GolReq = rq(V_LAND, false, ABSENT); + assert next_slot(r, EV_V, &wo, &wp) == slot_of(G_CAS, 0); + assert next_slot(r, EV_C0, &wo, &wp) == slot_of(G_MT, 1); + assert next_slot(r, EV_M1, &wo, &wp) == slot_of(G_CT, 1); + assert next_slot(r, EV_K1, &wo, &wp) == slot_of(G_CAS, 1); + assert next_slot(r, EV_ALL, &wo, &wp) == DONE; +} + +test a_plan_runs_the_read_only_steps_and_writes_no_head { + var wo : [16]u8 = [0] ** 16; + var wp : [16]usize = [0] ** 16; + var wt : [1024]u8 = [0] ** 1024; + const r : GolReq = rq(V_LAND, true, ABSENT); + assert next_slot(r, EV_V, &wo, &wp) == slot_of(G_MT, 1); + assert next_slot(r, EV_M1, &wo, &wp) == slot_of(G_CT, 1); + assert next_slot(r, EV_PLAN_K1, &wo, &wp) == DONE; + // a plan never names the compare-and-swap, whatever the evidence says + assert next_slot(r, EV_ALL, &wo, &wp) == DONE; +} + +test the_stage_verdicts_come_from_git_s_own_output { + var wo : [16]u8 = [0] ** 16; + var wp : [16]usize = [0] ** 16; + var wt : [1024]u8 = [0] ** 1024; + assert verdict(EV_V, 0, 0) == GC_V_PASS and verdict(EV_V, 0, 1) == GC_V_PASS and verdict(EV_V, 0, 2) == GC_V_PASS; + assert verdict(EV_V, 0, 3) == GC_V_PASS and verdict(EV_V, 0, 4) == GC_V_PASS; + // a failed seal: SEAL fails, and nothing after it is judged + assert verdict(EV_SEALBAD, 0, 2) == GC_V_FAIL and verdict(EV_SEALBAD, 0, 3) == GC_V_NONE and verdict(EV_SEALBAD, 0, 4) == GC_V_NONE; + // a vacuous pass (0 passed) is not a pass + assert verdict(EV_VACUOUS, 0, 3) == GC_V_FAIL and verdict(EV_VACUOUS, 0, 2) == GC_V_PASS; + // no Closes #N in the link's messages: RECEIPT fails (L1) + assert verdict(EV_NOCLOSES, 0, 3) == GC_V_PASS and verdict(EV_NOCLOSES, 0, 4) == GC_V_FAIL; + // a change to gen/ with no spec beside it: GEN fails (L2), and no worktree is made for it + assert verdict(EV_GENONLY, 0, 0) == GC_V_PASS and verdict(EV_GENONLY, 0, 1) == GC_V_FAIL and verdict(EV_GENONLY, 0, 2) == GC_V_NONE; + assert next_slot(rq(V_LAND, false, ABSENT), EV_GENONLY, &wo, &wp) == slot_of(G_TIP, 1); + // a branch that is not there: nothing is judged + assert verdict(EV_G, 0, 0) == GC_V_NONE; + // a seal file under .trinity is a path; a dash first or a .. inside is not + assert path_ok("M\t.trinity/seals/a.json\n", 0) == true; + assert path_ok("M\t../x\n", 0) == false and path_ok("M\t-x\n", 0) == false and path_ok("M x\n", 0) == false; +} + +test two_links_on_the_base_land_forward_then_carried { + var wo : [16]u8 = [0] ** 16; + var wp : [16]usize = [0] ** 16; + var wt : [1024]u8 = [0] ** 1024; + var o : [16]u8 = [0] ** 16; + var p : [16]usize = [0] ** 16; + assert plan(EV_V, 2, &o, &p) == 2; + assert o[0] == GC_OUT_FORWARD and o[1] == GC_OUT_CARRIED; + assert p[0] == 0 and p[1] == 1; + assert landing_at(EV_V, 2, 1, &o, &p) == 1 and landing_at(EV_V, 2, 5, &o, &p) == 2 and landing_at(EV_V, 2, 0, &o, &p) == 0; +} + +test negative_control_a_link_that_is_not_golden_lands_nowhere_and_the_next_one_still_does { + var wo : [16]u8 = [0] ** 16; + var wp : [16]usize = [0] ** 16; + var wt : [1024]u8 = [0] ** 1024; + var o : [16]u8 = [0] ** 16; + var p : [16]usize = [0] ** 16; + assert plan(EV_SEALBAD, 2, &o, &p) == 1; + assert o[0] == GC_OUT_NOT_GOLDEN and o[1] == GC_OUT_FORWARD; + assert next_slot(rq(V_LAND, false, ABSENT), EV_SEALBAD, &wo, &wp) == slot_of(G_CAS, 1); + assert plan(EV_NOCLOSES, 2, &o, &p) == 1 and o[0] == GC_OUT_NOT_GOLDEN; +} + +test negative_control_two_links_on_one_file_conflict_and_only_the_first_lands { + var wo : [16]u8 = [0] ** 16; + var wp : [16]usize = [0] ** 16; + var wt : [1024]u8 = [0] ** 1024; + var o : [16]u8 = [0] ** 16; + var p : [16]usize = [0] ** 16; + assert plan(EV_CONF, 2, &o, &p) == 1; + assert o[0] == GC_OUT_FORWARD and o[1] == GC_OUT_CONFLICT and p[1] == 2; + assert next_slot(rq(V_LAND, false, ABSENT), EV_CONF, &wo, &wp) == slot_of(G_CAS, 0); +} + +test negative_control_a_head_that_moved_under_a_link_lands_nothing { + var wo : [16]u8 = [0] ** 16; + var wp : [16]usize = [0] ** 16; + var wt : [1024]u8 = [0] ** 1024; + var o : [16]u8 = [0] ** 16; + var p : [16]usize = [0] ** 16; + // links cut from an older master than the head are carried onto it (#11361); merge-tree decides + assert plan(EV_MOVED, 2, &o, &p) == 2; + assert o[0] == GC_OUT_CARRIED and o[1] == GC_OUT_CARRIED; + assert next_slot(rq(V_LAND, false, ABSENT), EV_MOVED, &wo, &wp) == slot_of(G_MT, 0); +} + +test the_compare_and_swap_is_refused_when_git_says_the_head_moved { + var wo : [16]u8 = [0] ** 16; + var wp : [16]usize = [0] ** 16; + var wt : [1024]u8 = [0] ** 1024; + const r : GolReq = rq(V_LAND, false, ABSENT); + assert judge(r, "@@ preflight 14 0 128 0\n\n", G_CAS, 0) == C_HEAD_MOVED; + assert judge(r, EV_C0, G_CAS, 0) == C_OK; + // exit 1 is a conflict of the link, not of the pass; any other failure of merge-tree stops the pass + assert judge(r, "@@ preflight 12 1 1 0\n\n", G_MT, 1) == C_OK; + assert judge(r, "@@ preflight 12 1 128 0\n\n", G_MT, 1) == C_STEP; + assert judge(r, EV_M1, G_MT, 1) == C_OK; + assert judge(r, "@@ preflight 13 1 0 4\nnope\n", G_CT, 1) == C_STEP; + assert judge(r, EV_K1, G_CT, 1) == C_OK; + assert judge(r, EV_NOBASE, G_BASE, 0) == C_NO_BASE; + assert judge(r, EV_G, G_BASE, 0) == C_OK; + // the pass stops at a refused swap: nothing after it is asked + assert next_slot(r, "@@ preflight 14 0 128 0\n\n", &wo, &wp) == slot_of(G_REFS, 0); + assert next_slot(r, EV_NOBASE, &wo, &wp) == DONE; +} + +test the_first_landing_creates_the_head_and_a_later_one_swaps_from_the_old_value { + var wo : [16]u8 = [0] ** 16; + var wp : [16]usize = [0] ** 16; + var wt : [1024]u8 = [0] ** 1024; + var buf : [4096]u8 = [0] ** 4096; + const r : GolReq = rq(V_LAND, false, ABSENT); + const first : string = "git\nupdate-ref\nrefs/golden/head\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n0000000000000000000000000000000000000000\n"; + assert argv_is(r, EV_V, G_CAS, 0, first, &buf, &wo, &wp); + assert argv_is(r, EV_K1, G_CAS, 1, "git\nupdate-ref\nrefs/golden/head\ne5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n", &buf, &wo, &wp); + // the head exists: its value is the old value of the first swap + assert argv_is(r, EV_HEADED, G_CAS, 0, "git\nupdate-ref\nrefs/golden/head\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n", &buf, &wo, &wp); +} + +test the_reading_and_verifying_steps_pin_their_bytes { + var wo : [16]u8 = [0] ** 16; + var wp : [16]usize = [0] ** 16; + var wt : [1024]u8 = [0] ** 1024; + var buf : [4096]u8 = [0] ** 4096; + const r : GolReq = rq(V_LAND, false, ABSENT); + assert argv_is(r, "", G_REFS, 0, "git\nfor-each-ref\n--format=%(refname:short)\nrefs/heads/claude/evolve-*\n", &buf, &wo, &wp); + assert argv_is(r, "", G_FILE, 0, "cat\n.trinity/golden/queue\n", &buf, &wo, &wp); + assert argv_is(r, "", G_HEAD, 0, "git\nrev-parse\n--verify\n-q\nrefs/golden/head\n", &buf, &wo, &wp); + assert argv_is(r, "", G_BASE, 0, "git\nrev-parse\n--verify\n-q\nrefs/remotes/origin/master\n", &buf, &wo, &wp); + assert argv_is(r, EV_G, G_TIP, 1, "git\nrev-parse\n--verify\n-q\nrefs/heads/claude/evolve-b\n", &buf, &wo, &wp); + assert argv_is(r, EV_TIP0, G_MB, 0, "git\nmerge-base\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n", &buf, &wo, &wp); + assert argv_is(r, EV_MB0, G_CHG, 0, "git\ndiff\n--name-status\n--no-renames\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n", &buf, &wo, &wp); + assert argv_is(r, EV_CHG0, G_LOG, 0, "git\nlog\n--format=%B\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1..b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n", &buf, &wo, &wp); + assert argv_is(r, EV_LOG0, G_WT, 0, "git\nworktree\nadd\n-q\n--detach\n/tmp/s/gl-0\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n", &buf, &wo, &wp); + assert argv_is(r, EV_WT0, G_SEAL, 0, "t27c\nseal\nspecs/x/a.t27\n--verify\n", &buf, &wo, &wp); + assert argv_is(r, EV_SEAL0, G_TEST, 0, "t27b\ntest\nspecs/x/a.t27\n", &buf, &wo, &wp); + assert argv_is(r, EV_TEST0, G_RM, 0, "git\nworktree\nremove\n--force\n/tmp/s/gl-0\n", &buf, &wo, &wp); + // the seal and the test of link 1 run in link 1's worktree + assert step_cwd(r, G_SEAL, 1000, &buf) == 11 and buf_is(&buf, 11, "/tmp/s/gl-1"); + assert step_cwd(r, G_TIP, 0, &buf) == 0; +} + +test the_landing_steps_pin_their_bytes { + var wo : [16]u8 = [0] ** 16; + var wp : [16]usize = [0] ** 16; + var wt : [1024]u8 = [0] ** 1024; + var buf : [4096]u8 = [0] ** 4096; + const r : GolReq = rq(V_LAND, false, ABSENT); + assert argv_is(r, EV_C0, G_MT, 1, "git\nmerge-tree\n--write-tree\n--merge-base=a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\nc3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3c3\n", &buf, &wo, &wp); + assert argv_is(r, EV_M1, G_CT, 1, "git\ncommit-tree\nf6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6\n-p\nb2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n-m\ngolden: carry claude/evolve-b (Closes #11275)\n", &buf, &wo, &wp); + // no word is a force, no step writes a branch of origin, and the head is written only by update-ref with an old value + var k : u8 = 0; + while (k < GL_STEPS) { + const m : usize = step_argv(r, EV_K1, k, 1, &buf, &wo, &wp); + assert buf_has_not(&buf, m, "git\npush\n"); + assert k == G_RM or buf_has_not(&buf, m, "--force"); + k = k + 1; + } +} + +pub fn buf_has_not(buf: []const u8, n: usize, want: string) -> bool { + var at : usize = 0; + while (at + want.len <= n) { + var k : usize = 0; + while (k < want.len and buf[at + k] == want[k]) { k = k + 1; } + if (k == want.len) { return false; } + at = at + 1; + } + return true; +} + +test the_push_state_is_read_from_git_s_output { + var wo : [16]u8 = [0] ** 16; + var wp : [16]usize = [0] ** 16; + var wt : [1024]u8 = [0] ** 1024; + const r : GolReq = rq(V_PUSH, false, 0); + assert ahead_count(EV_P_TEN) == 10 and batch_len(EV_P_TEN) == GC_BATCH_MAX; + assert ahead_count(EV_P_NONE) == 0 and batch_len(EV_P_NONE) == 0; + assert minutes_since(EV_P_20) == 20 and minutes_since(EV_P_30) == 30; + // a pushed ref that never moved: the push is due the moment a link waits + assert minutes_since(EV_P_FIRST) == 100000; + assert queued_runs(rq(V_PUSH, false, 250), EV_P_20) == 250; + assert queued_runs(rq(V_PUSH, false, ABSENT), EV_P_GH) == 3; + assert queued_runs(rq(V_PUSH, false, ABSENT), EV_P_20) == 0; + assert decision(r, EV_P_FIRST) == GC_PUSH_NOW; +} + +test nothing_ready_pushes_nothing_a_jam_doubles_the_window { + var wo : [16]u8 = [0] ** 16; + var wp : [16]usize = [0] ** 16; + var wt : [1024]u8 = [0] ** 1024; + // the rule is golden_chain.t27 when_to_push; this checks the host feeds it the right three numbers + assert decision(rq(V_PUSH, false, 0), EV_P_NONE) == GC_PUSH_NOTHING; + assert decision(rq(V_PUSH, false, 0), EV_P_20) == GC_PUSH_NOW; + assert decision(rq(V_PUSH, false, 250), EV_P_20) == GC_PUSH_WAIT; + assert decision(rq(V_PUSH, false, 250), EV_P_30) == GC_PUSH_NOW; + assert decision(rq(V_PUSH, false, ABSENT), EV_P_GH) == GC_PUSH_NOW; +} + +test the_push_runs_tri_land_once_on_a_branch_at_the_batch_s_last_link { + var wo : [16]u8 = [0] ** 16; + var wp : [16]usize = [0] ** 16; + var wt : [1024]u8 = [0] ** 1024; + var buf : [4096]u8 = [0] ** 4096; + const r : GolReq = rq(V_PUSH, false, 0); + assert next_slot(r, "", &wo, &wp) == slot_of(G_HEAD, 0); + assert next_slot(r, EV_P_FIRST, &wo, &wp) == slot_of(P_BRANCH, 0); + assert next_slot(r, EV_P_BRANCHED, &wo, &wp) == slot_of(P_LAND, 0); + assert next_slot(r, EV_P_LANDED, &wo, &wp) == slot_of(P_MARK, 0); + // nothing waiting, or not yet due: no push + assert next_slot(r, EV_P_NONE, &wo, &wp) == DONE; + assert next_slot(rq(V_PUSH, false, 250), EV_P_20, &wo, &wp) == DONE; + // a plan reads the state and runs none of the three + assert next_slot(rq(V_PUSH, true, 0), EV_P_FIRST, &wo, &wp) == DONE; + // the batch is the first eight links: the branch is cut at the eighth + assert argv_is(r, EV_P_TEN, P_BRANCH, 0, "git\nbranch\n-f\nclaude/golden-1717171717\n1717171717171717171717171717171717171717\n", &buf, &wo, &wp); + assert argv_is(r, EV_P_TEN, P_LAND, 0, "/tmp/tri\nland\nclaude/golden-1717171717\n--issue\n11275\n", &buf, &wo, &wp); + assert argv_is(r, EV_P_FIRST, P_BRANCH, 0, "git\nbranch\n-f\nclaude/golden-1212121212\n1212121212121212121212121212121212121212\n", &buf, &wo, &wp); +} + +test negative_control_a_refused_land_does_not_move_the_pushed_ref { + var wo : [16]u8 = [0] ** 16; + var wp : [16]usize = [0] ** 16; + var wt : [1024]u8 = [0] ** 1024; + const r : GolReq = rq(V_PUSH, false, 0); + assert judge(r, EV_P_REFUSED, P_LAND, 0) == C_LAND_REFUSED; + assert next_slot(r, EV_P_REFUSED, &wo, &wp) == DONE; + assert judge(r, EV_P_LANDED, P_LAND, 0) == C_OK; +} + +test the_push_state_steps_pin_their_bytes { + var wo : [16]u8 = [0] ** 16; + var wp : [16]usize = [0] ** 16; + var wt : [1024]u8 = [0] ** 1024; + var buf : [4096]u8 = [0] ** 4096; + const r : GolReq = rq(V_PUSH, false, ABSENT); + assert argv_is(r, EV_P_FIRST, P_PUSHED, 0, "git\nrev-parse\n--verify\n-q\nrefs/golden/pushed\n", &buf, &wo, &wp); + assert argv_is(r, EV_P_FIRST, P_AHEAD, 0, "git\nrev-list\n--first-parent\n--reverse\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1..d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4\n", &buf, &wo, &wp); + assert argv_is(r, EV_P_20, P_AHEAD, 0, "git\nrev-list\n--first-parent\n--reverse\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1..d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4\n", &buf, &wo, &wp); + assert argv_is(r, "", P_STAMP, 0, "git\nreflog\nshow\n--format=%ct\n-n\n1\nrefs/golden/pushed\n", &buf, &wo, &wp); + assert argv_is(r, "", P_NOW, 0, "date\n+%s\n", &buf, &wo, &wp); + assert argv_is(r, "", P_QUEUED, 0, "gh\nrun\nlist\n--status\nqueued\n--limit\n300\n--json\ndatabaseId\n", &buf, &wo, &wp); + assert argv_is(r, EV_P_FIRST, P_MARK, 0, "git\nupdate-ref\n--create-reflog\nrefs/golden/pushed\n1212121212121212121212121212121212121212\n0000000000000000000000000000000000000000\n", &buf, &wo, &wp); + assert argv_is(r, EV_P_20, P_MARK, 0, "git\nupdate-ref\n--create-reflog\nrefs/golden/pushed\n1212121212121212121212121212121212121212\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n", &buf, &wo, &wp); + assert argv_is(r, "", G_PRUNE, 0, "git\nworktree\nprune\n", &buf, &wo, &wp); +} + +test the_report_says_what_a_plan_would_do { + var wo : [16]u8 = [0] ** 16; + var wp : [16]usize = [0] ** 16; + var wt : [1024]u8 = [0] ** 1024; + var out : [2048]u8 = [0] ** 2048; + const n : usize = report(rq(V_LAND, true, ABSENT), EV_PLAN_K1, C_OK, &out, &wo, &wp, &wt); + const want : string = "golden head: none (base a1a1a1a1a1a1)\nqueue: 2\nclaude/evolve-a ppppp FORWARD\nclaude/evolve-b ppppp CARRIED\nlanded: 2 of 2\n would run: git update-ref refs/golden/head b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2 0000000000000000000000000000000000000000\n would run: git update-ref refs/golden/head e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5 b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2\n"; + assert n == want.len and buf_is(&out, n, want); + const m : usize = report(rq(V_LAND, false, ABSENT), EV_NOBASE, C_NO_BASE, &out, &wo, &wp, &wt); + assert buf_is(&out, m, "refused: NO_BASE: refs/remotes/origin/master is missing: git fetch origin master\n"); +} + +test the_status_says_when_the_next_push_is_due { + var wo : [16]u8 = [0] ** 16; + var wp : [16]usize = [0] ** 16; + var wt : [1024]u8 = [0] ** 1024; + var out : [2048]u8 = [0] ** 2048; + const n : usize = report(rq(V_STATUS, false, 250), EV_P_20, C_OK, &out, &wo, &wp, &wt); + const want : string = "golden head: d4d4d4d4d4d4\npushed: a1a1a1a1a1a1\nunpushed links: 3 (batch 3, cap 8)\nminutes since the last push: 20\nactions queued: 250\nnext push: WAIT 10 more minute(s)\nqueue:\n"; + assert n == want.len and buf_is(&out, n, want); + const m : usize = report(rq(V_STATUS, false, 0), EV_P_NONE, C_OK, &out, &wo, &wp, &wt); + assert buf_is(&out, 0, "") and m > 0; +} + +const RGS : str = "9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a"; +const PUS : str = "9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b"; +const CTS : str = "e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5"; + +test a_seal_saved_by_an_older_t27c_is_saved_again_after_the_tests_and_the_link_lands_as_regen { + var wo : [16]u8 = [0] ** 16; + var wp : [16]usize = [0] ** 16; + var wt : [1024]u8 = [0] ** 1024; + var buf : [4096]u8 = [0] ** 4096; + var o : [16]u8 = [0] ** 16; + var p : [16]usize = [0] ** 16; + const r : GolReq = rq(V_LAND, false, ABSENT); + // the order: seal verify, the tests, then save, verify again, stage, diff, commit, the new tip, remove + assert next_slot(r, EV_WT0, &wo, &wp) == slot_of(G_SEAL, 0); + assert next_slot(r, EV_RG1, &wo, &wp) == slot_of(G_TEST, 0); + assert next_slot(r, EV_RG2, &wo, &wp) == slot_of(S_SAVE, 0); + assert next_slot(r, EV_RG3, &wo, &wp) == slot_of(S_RECHECK, 0); + assert next_slot(r, EV_RG4, &wo, &wp) == slot_of(S_STAGE, 0); + assert next_slot(r, EV_RG5, &wo, &wp) == slot_of(S_DIFF, 0); + assert next_slot(r, EV_RG6, &wo, &wp) == slot_of(S_COMMIT, 0); + assert next_slot(r, EV_RG7, &wo, &wp) == slot_of(S_TIP, 0); + assert next_slot(r, EV_RG8, &wo, &wp) == slot_of(G_RM, 0); + assert verdict(EV_RG4, 0, 2) == GC_V_NONE and verdict(EV_RG8, 0, 2) == GC_V_PASS and verdict(EV_REGEN, 0, 3) == GC_V_PASS and verdict(EV_REGEN, 0, 4) == GC_V_PASS; + // the link lands as its re-sealed commit, and is shown as REGEN + assert plan(EV_REGEN, 1, &o, &p) == 1 and o[0] == GC_OUT_FORWARD; + assert outcome_shown(EV_REGEN, 0, o[0]) == GC_OUT_REGEN and outcome_shown(EV_PLAN_K1, 0, GC_OUT_FORWARD) == GC_OUT_FORWARD; + assert span_is(EV_REGEN, tip_off(EV_REGEN, 0), tip_off(EV_REGEN, 0) + 40, RGS); + assert argv_is(r, EV_REGEN, G_CAS, 0, "git\nupdate-ref\nrefs/golden/head\n9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a9a\n0000000000000000000000000000000000000000\n", &buf, &wo, &wp); + // a seal that matched is never saved again + assert verdict(EV_RGOK, 0, 2) == GC_V_PASS and regen_tip_ok(EV_RGOK, 0) == false and next_slot(r, EV_RGOK, &wo, &wp) == slot_of(G_TIP, 1); +} + +test the_seal_steps_pin_their_bytes { + var wo : [16]u8 = [0] ** 16; + var wp : [16]usize = [0] ** 16; + var wt : [1024]u8 = [0] ** 1024; + var buf : [4096]u8 = [0] ** 4096; + const r : GolReq = rq(V_LAND, false, ABSENT); + assert argv_is(r, EV_RG2, S_SAVE, 0, "t27c\nseal\nspecs/x/a.t27\n--save\n", &buf, &wo, &wp); + assert argv_is(r, EV_RG3, S_RECHECK, 0, "t27c\nseal\nspecs/x/a.t27\n--verify\n", &buf, &wo, &wp); + assert argv_is(r, EV_RG4, S_STAGE, 0, "git\nadd\n-A\n", &buf, &wo, &wp); + assert argv_is(r, EV_RG5, S_DIFF, 0, "git\ndiff\n--cached\n--name-only\n", &buf, &wo, &wp); + assert argv_is(r, EV_RG6, S_COMMIT, 0, "git\n-c\nuser.name=tri-golden\n-c\nuser.email=golden@t27.ai\n-c\ncommit.gpgsign=false\ncommit\n--no-verify\n-q\n-m\ngolden: save the seals again on the tip (t27c seal --save, tests clean) (Closes #11275)\n", &buf, &wo, &wp); + assert argv_is(r, EV_RG7, S_TIP, 0, "git\nrev-parse\n--verify\n-q\nHEAD\n", &buf, &wo, &wp); + // they run in the link's worktree, like the seal and the test + assert step_cwd(r, S_SAVE, 1000, &buf) == 11 and step_cwd(r, S_COMMIT, 2000, &buf) == 11 and buf_is(&buf, 11, "/tmp/s/gl-2"); +} + +test negative_control_a_failing_test_or_a_path_that_is_not_a_seal_refuses_the_link { + var wo : [16]u8 = [0] ** 16; + var wp : [16]usize = [0] ** 16; + var wt : [1024]u8 = [0] ** 1024; + var o : [16]u8 = [0] ** 16; + var p : [16]usize = [0] ** 16; + const r : GolReq = rq(V_LAND, false, ABSENT); + // the test failed: the seal is not saved, the worktree is removed, the link does not land + assert next_slot(r, EV_RGTEST, &wo, &wp) == slot_of(G_TIP, 1); + assert verdict(EV_RGTEST, 0, 3) == GC_V_FAIL and verdict(EV_RGTEST, 0, 2) == GC_V_NONE; + assert plan(EV_RGTEST, 1, &o, &p) == 0 and o[0] == GC_OUT_NOT_GOLDEN; + // the diff holds a spec besides the seal: FAIL, no commit + assert diff_changed(EV_RGBAD, 0) == 2 and diff_seals(EV_RGBAD, 0) == 1 and diff_ok(EV_RGBAD, 0) == false; + assert verdict(EV_RGBAD, 0, 2) == GC_V_FAIL; + assert next_slot(r, EV_RGBAD, &wo, &wp) == slot_of(G_TIP, 1); + assert plan(EV_RGBAD, 1, &o, &p) == 0 and o[0] == GC_OUT_NOT_GOLDEN; + // a seal that still does not match after the save: SEAL fails (the old EV_SEALBAD) + assert verdict(EV_SEALBAD, 0, 2) == GC_V_FAIL; + // a seal file may carry :: in its name; a spec path may not + assert path_ok("M\t.trinity/seals/tools_port::trios::x.json\n", 0) == true and path_ok("M\tspecs/a::b.t27\n", 0) == false; +} + +test the_chain_follows_master_by_reset_when_nothing_is_unpushed { + var wo : [16]u8 = [0] ** 16; + var wp : [16]usize = [0] ** 16; + var wt : [1024]u8 = [0] ** 1024; + var buf : [4096]u8 = [0] ** 4096; + const r : GolReq = rq(V_LAND, false, ABSENT); + assert next_slot(r, EV_RB_RESET, &wo, &wp) == slot_of(R_UH, 0); + assert next_slot(r, EV_RB_RESET_UH, &wo, &wp) == slot_of(R_UP, 0); + assert argv_is(r, EV_RB_RESET, R_ANC, 0, "git\nmerge-base\n--is-ancestor\nd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n", &buf, &wo, &wp); + assert argv_is(r, EV_RB_RESET, R_UH, 0, "git\nupdate-ref\nrefs/golden/head\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\nd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4\n", &buf, &wo, &wp); + assert argv_is(r, EV_RB_RESET, R_UP, 0, "git\nupdate-ref\n--create-reflog\nrefs/golden/pushed\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\nd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4\n", &buf, &wo, &wp); + // both refs read as master from now on: links are judged against it + assert rb_action(EV_RB_RESET) == GC_RB_RESET and span_is(EV_RB_RESET, head_off(EV_RB_RESET), head_off(EV_RB_RESET) + 40, FB) and span_is(EV_RB_RESET, pushed_off(EV_RB_RESET), pushed_off(EV_RB_RESET) + 40, FB); + // a plan and a status write no ref: the links are judged next + assert next_slot(rq(V_LAND, true, ABSENT), EV_RB_RESET, &wo, &wp) == slot_of(G_TIP, 0); + assert next_slot(rq(V_STATUS, false, ABSENT), EV_RB_RESET, &wo, &wp) == slot_of(P_AHEAD, 0); + // master holds the head: nothing moves + assert rb_action(EV_RB_HELD) == GC_RB_NONE and next_slot(r, EV_RB_HELD, &wo, &wp) == slot_of(G_TIP, 0); + // the pushed mark is not read yet: it is the next thing to read + assert next_slot(r, "@@ preflight 2 0 0 41\nd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4\n\n@@ preflight 3 0 0 41\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n\n@@ preflight 0 0 0 0\n\n@@ preflight 1 0 1 0\n\n", &wo, &wp) == slot_of(P_PUSHED, 0); +} + +test the_chain_follows_master_by_carrying_unpushed_links_as_one_commit { + var wo : [16]u8 = [0] ** 16; + var wp : [16]usize = [0] ** 16; + var wt : [1024]u8 = [0] ** 1024; + var buf : [4096]u8 = [0] ** 4096; + const r : GolReq = rq(V_PUSH, false, 0); + assert rb_action(EV_RB_CARRY) == GC_RB_CARRY; + assert next_slot(rq(V_LAND, false, ABSENT), EV_RB_CARRY, &wo, &wp) == slot_of(R_MT, 0); + assert next_slot(rq(V_LAND, false, ABSENT), EV_RB_CMT, &wo, &wp) == slot_of(R_CT, 0); + assert next_slot(rq(V_LAND, false, ABSENT), EV_RB_CCT, &wo, &wp) == slot_of(R_UH, 0); + assert argv_is(r, EV_RB_CARRY, R_MT, 0, "git\nmerge-tree\n--write-tree\n--merge-base=9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\nd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4\n", &buf, &wo, &wp); + assert argv_is(r, EV_RB_CMT, R_CT, 0, "git\ncommit-tree\nf6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6\n-p\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n-m\ngolden: carry the unpushed links onto origin/master (Closes #11275)\n", &buf, &wo, &wp); + // the head becomes the carried commit, the pushed mark becomes master + assert argv_is(r, EV_RB_CCT, R_UH, 0, "git\nupdate-ref\nrefs/golden/head\ne5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5\nd4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4d4\n", &buf, &wo, &wp); + assert argv_is(r, EV_RB_CCT, R_UP, 0, "git\nupdate-ref\n--create-reflog\nrefs/golden/pushed\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1\n9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b9b\n", &buf, &wo, &wp); + assert span_is(EV_RB_CCT, head_off(EV_RB_CCT), head_off(EV_RB_CCT) + 40, CTS) and span_is(EV_RB_CCT, pushed_off(EV_RB_CCT), pushed_off(EV_RB_CCT) + 40, FB); + // the unpushed list is then master..carried commit: one link + assert argv_is(r, EV_RB_CCT, P_AHEAD, 0, "git\nrev-list\n--first-parent\n--reverse\na1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1..e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5e5\n", &buf, &wo, &wp); + // a conflict while carrying stops the run and nothing is written + assert judge(r, EV_RB_CONFLICT, R_MT, 0) == C_MERGE; + assert next_slot(rq(V_LAND, false, ABSENT), EV_RB_CONFLICT, &wo, &wp) == DONE; + assert judge(r, "@@ preflight 27 0 1 0\n\n", R_UH, 0) == C_HEAD_MOVED and judge(r, "@@ preflight 28 0 128 0\n\n", R_UP, 0) == C_HEAD_MOVED; +} + +test a_link_in_conflict_with_the_head_is_skipped_and_the_pass_goes_on { + var wo : [16]u8 = [0] ** 16; + var wp : [16]usize = [0] ** 16; + var wt : [1024]u8 = [0] ** 1024; + var o : [16]u8 = [0] ** 16; + var p : [16]usize = [0] ** 16; + var out : [2048]u8 = [0] ** 2048; + const r : GolReq = rq(V_LAND, false, ABSENT); + // link 1 was to land second; merge-tree says conflict: it is CONFLICT, link 0 stays landed, the pass ends without a stop + assert plan(EV_C1X, 2, &o, &p) == 1 and o[0] == GC_OUT_FORWARD and o[1] == GC_OUT_NOT_GOLDEN; + assert outcome_shown(EV_C1X, 1, o[1]) == GC_OUT_CONFLICT; + assert judge(r, EV_C1X, G_MT, 1) == C_OK; + assert next_slot(r, EV_C1X, &wo, &wp) == DONE; + const n : usize = report(rq(V_LAND, true, ABSENT), EV_C1X, C_OK, &out, &wo, &wp, &wt); + const want : string = "golden head: none (base a1a1a1a1a1a1)\nqueue: 2\nclaude/evolve-a ppppp FORWARD\nclaude/evolve-b ppppp CONFLICT\nlanded: 1 of 2\n would run: git update-ref refs/golden/head b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2 0000000000000000000000000000000000000000\n"; + assert n == want.len and buf_is(&out, n, want); +} + +test the_worst_case_fits { + var wo : [16]u8 = [0] ** 16; + var wp : [16]usize = [0] ** 16; + var wt : [1024]u8 = [0] ** 1024; + var buf : [4096]u8 = [0] ** 4096; + const r : GolReq = GolReq { verb: V_LAND, plan: false, queued: ABSENT, issue: 999999999, scratch: "/tmp/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", t27b: "t27b", t27c: "t27c", tri: "/tmp/tri" }; + var k : u8 = 0; + while (k < GL_STEPS) { + const m : usize = step_argv(r, EV_K1, k, 1, &buf, &wo, &wp); + assert m < 600; + k = k + 1; + } + assert CMD_MAX == 4096; +} diff --git a/specs/verified/bench_boards.t27 b/specs/verified/bench_boards.t27 index dfe8af6227..49497b0cbc 100644 --- a/specs/verified/bench_boards.t27 +++ b/specs/verified/bench_boards.t27 @@ -29,7 +29,12 @@ ; Its reader is the old DNA_READER with one read added: the PL's IDCODE, tagged PL_IDCODE. ; - ax7203 is the AX7203 on this bench, picked by serial. Its package is fbg484-2 (a camera photo, ; 2026-10-08); a pin-free design built for artix200t's fbg676-1 loads on it too, same die. -; - pzsdr-d is die D. Its package (clg400) is assumed, not read from a marking (#8635); the chipdb +; - pzsdr-d is die D. Measured again 2026-10-20 (fpga/HARDWARE_SSOT.md): the same cable serial 210203859289 +; at bus 000 (the busdev moved from bus 1 to bus 0), a two-device chain, ARM DAP 0x4ba00477 then PL 0x3727093 +; (version nibble 0; the 2026-10-10 read below has 2). The board MODEL behind the part is a GUESS (Arty Z7-20, +; PYNQ-Z1, Zybo Z7-20 and the PZSDR P201/P203 Mini are all xc7z020clg400-1); no second row, one chain, one die. +; Busdev is not a column here: it is read from the scan each time (specs/t27os/bench.t27). +; - pzsdr-d, continued. Its package (clg400) is assumed, not read from a marking (#8635); the chipdb ; build/fpga/openxc7/xc7z020clg400-1.bin was built from prjxray-db's zynq7 on the t27c lab. ; -- ; The rule, in judgment order (dna_pick, then reader_first_wrong on the reader's output): @@ -839,3 +844,22 @@ test "sites are distinct and ascending, and the last chain line wins" { assert text_next("abcabc", "bc", 5) == NOT_FOUND; assert text_next("ab", "abc", 0) == NOT_FOUND; } + +test "the bench of 2026-10-20: bus 0, a two-device Zynq chain, and an AX7203 cable with an empty chain" { + // openFPGALoader --scan-usb and --detect, as printed on 2026-10-20 (fpga/HARDWARE_SSOT.md) + var scan : string = "000 004 0x0403:0x6014 ft232H Digilent 210512180081 Digilent USB Device\n000 002 0x0403:0x6010 FTDI2232 Digilent 210203859289 Digilent Adept USB Device\n"; + var zynq : string = "index 0:\n\tidcode 0x4ba00477\n\ttype ARM cortex A9\n\tirlength 4\nindex 1:\n\tidcode 0x3727093\n"; + var empty : string = "Jtag frequency : requested 6.00MHz -> real 6.00MHz\n"; + assert cables_matching(scan, BOARD_PZSDR_D) == 1; + assert cables_matching(scan, BOARD_AX7203) == 1; + assert detect_count(zynq) == 2; + assert detect_idcode(zynq, 0) == 0x4ba00477; + assert detect_idcode(zynq, 1) == 0x3727093; + assert idcode_is_row(detect_idcode(zynq, pl_pos(BOARD_PZSDR_D)), BOARD_PZSDR_D); + assert idcode_is_row(detect_idcode(zynq, 0), BOARD_PZSDR_D) == false; + assert dna_pick(scan, zynq, BOARD_PZSDR_D) == PICK_OK; + assert dna_pick(scan, zynq, BOARD_ARTIX200T) == PICK_WRONG_DIE; + assert detect_count(empty) == 0; + assert detect_idcode(empty, 0) == HEX_NONE; + assert dna_pick(scan, empty, BOARD_AX7203) == PICK_WRONG_DIE; +}