From 8327540b48a1230b7f86809937492dfc0b6521c9 Mon Sep 17 00:00:00 2001 From: Dmitriy Vasilev Date: Wed, 23 Sep 2026 13:09:37 +0700 Subject: [PATCH] docs: adopt L0 PURPOSE, and make the backlog no bee can take visible The board's source cited "trios CLAUDE.md, law L0" in two places. That file carries L1-L9 about shell scripts, Closes #N, clippy, tests, port 9005, a GB fallback, an experience log and push-first. There is no L0 in it, in this repository, or anywhere else - the citation did not resolve. The cause is a real gap rather than a typo. L1-L7 govern how work ENTERS the repository and each one has a live gate; none of them says what the work is FOR. The .t27 rewrite is the stated goal, measured on the ROADMAP view and named as the win condition in onboarding.t27, and it had no constitutional standing. So L0 PURPOSE is adopted as an ADDITION, not a rewrite: everything below the interface is written once in .t27 and generated to its target, with two named exceptions - the seed (t27c stays hand-written Rust) and the interface (Swift for trios, TSX for the web). It sits OUTSIDE the Asimov ordering on purpose and may never be cited to excuse breaking L1-L7. Work that reaches the goal faster by merging untraced, hand-editing generated files or shipping an untested spec has not reached it. Enforcement is a measurement that already exists rather than a new one: the .t27 share of the stack, computed by roadmap-stack.mjs from the GitHub tree API and published at /roadmap/stack.json, must not fall. A law nobody can check is decoration, which the honesty rule forbids. THE BACKLOG WAS UNREACHABLE, NOT EMPTY. Measured today: 563 of 654 open issues carry no `## Boundary` section, so nothing can be reserved for them and the Queen skips every one - her tick reported missingBoundary for 565 candidates while the swarm sat at 2 of 20 lanes. .github/ISSUE_TEMPLATE/swarm-task.md carries the section, so the class stops growing, and explains why it is not paperwork. tools/queen/needs_boundary.py labels the ones missing it: 553 to label, with the 10 roadmap goals and epics held back, because a goal is not a task. Its self-test passes and its dry run was taken against the live repository. Neither writes a boundary into an issue body: which paths a task may touch is a judgement about the work, and a WRONG boundary is worse than a missing one because it reserves the wrong files. The existing issue-template config is added to, not replaced: blank_issues_enabled stays false and the SOUL.md link stays, because an issue opened outside a template is an issue with no boundary. Closes #4605 Co-Authored-By: Claude Opus 5 --- .github/ISSUE_TEMPLATE/config.yml | 8 + .github/ISSUE_TEMPLATE/swarm-task.md | 47 +++++ .github/workflows/needs-boundary.yml | 54 +++++ docs/T27-CONSTITUTION.md | 15 +- ...9-23-law-l0-and-the-unreachable-backlog.md | 16 ++ tools/queen/needs_boundary.py | 190 ++++++++++++++++++ 6 files changed, 325 insertions(+), 5 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/swarm-task.md create mode 100644 .github/workflows/needs-boundary.yml create mode 100644 docs/now/2026-09-23-law-l0-and-the-unreachable-backlog.md create mode 100644 tools/queen/needs_boundary.py diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 4048ec9f3b..8e49b3b5c9 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,5 +1,13 @@ +# blank_issues_enabled stays false: an issue opened outside a template is an +# issue with no Boundary section, and the Queen cannot reserve anything for it. blank_issues_enabled: false contact_links: - name: Trinity Documentation url: https://github.com/gHashTag/t27/blob/master/docs/nona-03-manifest/SOUL.md about: Read SOUL.md before creating issues + - name: How the game works, and how to join + url: https://t27.ai/blog/how-to-join-the-swarm/ + about: What the swarm is, the two ways in, the board tab by tab, and the laws. + - name: The board + url: https://app.t27.ai/queen/ + about: Live: what the bees are doing, the roadmap, and who contributed. diff --git a/.github/ISSUE_TEMPLATE/swarm-task.md b/.github/ISSUE_TEMPLATE/swarm-task.md new file mode 100644 index 0000000000..6763bfbdbf --- /dev/null +++ b/.github/ISSUE_TEMPLATE/swarm-task.md @@ -0,0 +1,47 @@ +--- +name: Task for the swarm +about: Work a bee can pick up. The Boundary section is what makes that possible. +title: '' +labels: '' +assignees: '' +--- + + + +## Summary + +What is wrong, missing, or hand-written where a spec should be. One paragraph. + +## Boundary + + + +- `specs/port/tools/example.t27` +- `tools/example.py` + +## Acceptance + + + +- [ ] The spec compiles and its own `test` blocks pass +- [ ] `Closes #N` is in the pull request (law L1) diff --git a/.github/workflows/needs-boundary.yml b/.github/workflows/needs-boundary.yml new file mode 100644 index 0000000000..f77ee0d32f --- /dev/null +++ b/.github/workflows/needs-boundary.yml @@ -0,0 +1,54 @@ +name: Needs boundary (make the unreachable backlog visible) + +# AN ISSUE WITHOUT A BOUNDARY IS INVISIBLE WORK. +# +# The Queen reserves paths before she dispatches, so two workers never edit the +# same file at once. An issue that never says which paths it touches has nothing +# to reserve, so she skips it - and says so only as a number in a tick summary +# nobody reads. +# +# Measured 2026-09-23: 563 of 654 open issues had no `## Boundary` section, and +# the tick reported `missingBoundary` for 565 candidates while the swarm sat at +# 2 of 20 lanes. The backlog was not empty. It was unreachable, and nothing in +# the issue list said which ones or why. +# +# This labels them. A label is a filter, a count and a worklist: the 563 stop +# being an invisible number and become a page somebody can work through. It +# never edits an issue body - what the boundary should be is a judgement about +# the work, and a wrong boundary is worse than none, because it reserves the +# wrong files. +# +# The parser is the Queen's: a line beginning `## Boundary` or `## Границы` +# (agent-server queen-tick.ts boundaryPathsOf, and its Swift twin in queend). +# If that rule ever changes, this must follow it or the label will lie. + +on: + schedule: + - cron: '43 5 * * *' + workflow_dispatch: + inputs: + dry_run: + description: 'Count and print, label nothing' + required: false + type: boolean + default: false + +concurrency: + group: needs-boundary + cancel-in-progress: false + +permissions: + issues: write + +jobs: + label: + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v4 + + - name: Label the issues no bee can take + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + DRY_RUN: ${{ inputs.dry_run }} + run: python3 tools/queen/needs_boundary.py ${{ inputs.dry_run && '--dry-run' || '' }} diff --git a/docs/T27-CONSTITUTION.md b/docs/T27-CONSTITUTION.md index 73da99120b..3af45ed26b 100644 --- a/docs/T27-CONSTITUTION.md +++ b/docs/T27-CONSTITUTION.md @@ -64,12 +64,15 @@ Grandfathered non-English paths are listed only in **`docs/.legacy-non-english-d ## § 2 — Invariant Laws (never change without constitutional amendment) -These seven laws are the **constitutional bedrock** of Trinity S³AI / t27. They govern behavior, not formats or scientific claims. Amendments require explicit consensus and version bump. +These laws are the **constitutional bedrock** of Trinity S³AI / t27. They govern behavior, not formats or scientific claims. Amendments require explicit consensus and version bump. -### Law Table (L1–L7) +**L0 was adopted on 2026-09-23, and it is an addition rather than a rewrite.** L1-L7 say how work enters the repository - traced, generated, ASCII, tested - and every one of them is carried by a live gate. What they never said is what the work is *for*. That gap was already being filled informally: the board's source cited "trios CLAUDE.md, law L0" for the rewrite goal, and no such law existed in that file or anywhere else. `trios/CLAUDE.md` carries its own L1-L9 about shell scripts, clippy and ports. A citation that does not resolve is precisely what the honesty rule forbids, so the choice was to delete the claim or to make it true. It was made true. + +### Law Table (L0–L7) | Law # | Name | Body | Enforcement | |-------|------|------|-------------| +| **L0** | **PURPOSE** | Everything below the interface is written once, in `.t27`, and generated to its target - Rust for servers, Zig, C and Verilog for the core and silicon. Two exceptions, both named: the seed (`t27c` itself stays hand-written Rust) and the interface (Swift for trios, TSX for the web) | The measured `.t27` share of the stack must not fall: `apps/website/scripts/roadmap-stack.mjs` in gHashTag/trinity, published at `/roadmap/stack.json` and drawn by the ROADMAP view | | **L1** | **TRACEABILITY** | No code merged without `Closes #N` — every PR must reference a GitHub issue | `.github/workflows/issue-gate.yml` | | **L2** | **GENERATION** | Files under `gen/` are generated; edit the `.t27` spec instead | `./target/release/t27c validate-gen-headers` | | **L3** | **PURITY** | All `.t27` / `.zig` / `.v` / `.c` source — ASCII-only identifiers & comments | `SOUL.md`, `ADR-004`, build.rs language checks | @@ -78,10 +81,11 @@ These seven laws are the **constitutional bedrock** of Trinity S³AI / t27. They | **L6** | **CEILING** | `conformance/FORMAT-SPEC-001.json` + `specs/numeric/gf16.t27` are the numeric ceiling — never forked | SSOT: seal coverage CI | | **L7** | **UNITY** | No new `*.sh` on the critical path for validation / gen / data | `SOUL.md` Article VIII; `t27c` + `tri` only | -### Alias Index (legacy → L1–L7) +### Alias Index (legacy → L0–L7) | Legacy name | New name | |-------------|----------| +| law L0 (cited in gHashTag/trinity board source before it existed) | L0 PURPOSE | | ISSUE-GATE | L1 TRACEABILITY | | NO-HAND-EDIT-GEN | L2 GENERATION | | SOUL-ASCII | L3 PURITY | @@ -92,9 +96,10 @@ These seven laws are the **constitutional bedrock** of Trinity S³AI / t27. They ### Law Priority -Laws follow **Asimov-style priority** (L1 > L2 > … > L7): +Laws follow **Asimov-style priority** (L1 > L2 > … > L7). **L0 sits outside that ordering on purpose**: it is the destination, not a rule about the journey, and it may never be cited to excuse breaking L1-L7. Work that reaches L0 faster by merging untraced, hand-editing generated files or shipping an untested spec has not reached it at all. -1. **L1 TRACEABILITY** (highest) — Without issue linkage, nothing enters the repository +0. **L0 PURPOSE** (the goal, not a tie-breaker) — the stack below the interface becomes `.t27` +1. **L1 TRACEABILITY** — Without issue linkage, nothing enters the repository 2. **L2 GENERATION** — Generated files are output, not source 3. **L3 PURITY** — Language policy enables universal tooling 4. **L4 TESTABILITY** — TDD ensures specifications are verifiable diff --git a/docs/now/2026-09-23-law-l0-and-the-unreachable-backlog.md b/docs/now/2026-09-23-law-l0-and-the-unreachable-backlog.md new file mode 100644 index 0000000000..2ed2a96e7a --- /dev/null +++ b/docs/now/2026-09-23-law-l0-and-the-unreachable-backlog.md @@ -0,0 +1,16 @@ +# NOW -- L0 PURPOSE adopted, and the backlog no bee could take made visible (2026-09-23) + +## The law the board cited before it existed (Closes #4605) + +- `QueenRoadmap.tsx:2` and `roadmap-stack.mjs:5` both cited "trios CLAUDE.md, law L0" for the rewrite goal. `trios/CLAUDE.md` carries L1-L9 about shell scripts, `Closes #N`, clippy, tests, port 9005, a GB fallback, an experience log and push-first. There is no L0 in that file, in this repository, or anywhere else. +- The cause is a real gap, not a typo: L1-L7 govern how work ENTERS the repository and each has a live gate, but none of them says what the work is FOR. The `.t27` rewrite is the stated goal, measured on the ROADMAP view and named as the win condition in `onboarding.t27`, and it had no constitutional standing at all. +- **L0 PURPOSE** is therefore adopted as an ADDITION, not a rewrite: everything below the interface is written once in `.t27` and generated to its target, with two named exceptions (the seed `t27c` stays hand-written Rust; the interface stays Swift for trios and TSX for the web). +- It sits OUTSIDE the Asimov ordering on purpose and may never be cited to excuse breaking L1-L7: work that reaches the goal faster by merging untraced, hand-editing generated files or shipping an untested spec has not reached it. +- Enforcement is a measurement that already exists rather than a new one: the `.t27` share of the stack, computed by `apps/website/scripts/roadmap-stack.mjs` from the GitHub tree API and published at `/roadmap/stack.json`, must not fall. A law nobody can check is decoration. + +## The backlog was unreachable, not empty + +- Measured today: 563 of 654 open issues carry no `## Boundary` section, so the Queen can reserve no paths for them and skips every one; her tick reported `missingBoundary` for 565 candidates while the swarm sat at 2 of 20 lanes. +- `.github/ISSUE_TEMPLATE/swarm-task.md` carries the section, so the class stops growing, and links the tutorial and the board from the issue chooser. +- `tools/queen/needs_boundary.py` plus `.github/workflows/needs-boundary.yml` label the ones missing it: 553 to label, with the 10 roadmap goals and epics held back because a goal is not a task. Self-test passes and the dry run was taken against the live repository. +- Neither writes a boundary into an issue body. Which paths a task may touch is a judgement about the work, and a WRONG boundary is worse than a missing one because it reserves the wrong files. diff --git a/tools/queen/needs_boundary.py b/tools/queen/needs_boundary.py new file mode 100644 index 0000000000..724527e85b --- /dev/null +++ b/tools/queen/needs_boundary.py @@ -0,0 +1,190 @@ +#!/usr/bin/env python3 +"""Label the open issues no bee can take, because they declare no boundary. + +WHY + +The Queen reserves paths before she dispatches, so that two workers never edit +the same file at once. An issue that never says which paths it touches has +nothing to reserve, so she skips it -- and says so only as a number inside a +tick summary nobody reads. + +Measured 2026-09-23: 563 of 654 open issues had no `## Boundary` section, while +the Queen's tick reported `missingBoundary` for 565 candidates and the swarm sat +at 2 of 20 lanes. The backlog was not empty. It was unreachable, and the issue +list said nothing about which ones or why. + +WHAT IT DOES, AND WHAT IT REFUSES TO DO + +It adds `needs-boundary` to open issues without the section, and removes it from +issues that have since gained one. That is all. It never writes a boundary into +an issue body: which paths a task may touch is a judgement about the work, and a +WRONG boundary is worse than a missing one, because it reserves the wrong files +and blocks whatever really owns them. + +THE PARSER IS THE QUEEN'S, NOT A NEW ONE + +A boundary is a line beginning `## Boundary` or `## Границы` -- the rule in +`boundaryPathsOf` (BrowserOS trios/agent-server queen-tick.ts) and its Swift +twin in queend. This file matches that rule and nothing wider: if it accepted a +heading the Queen does not, the label would say "dispatchable" about an issue +she will keep skipping. + +Usage: + python3 tools/queen/needs_boundary.py --dry-run + python3 tools/queen/needs_boundary.py + python3 tools/queen/needs_boundary.py --self-test +""" +from __future__ import annotations + +import argparse +import json +import re +import subprocess +import sys + +REPO = "gHashTag/t27" +LABEL = "needs-boundary" +LABEL_COLOR = "d93f0b" +LABEL_ABOUT = ( + "No '## Boundary' section, so the Queen can reserve nothing for it and no " + "bee can take it" +) + +# The Queen's rule, matched exactly: a level-two heading that starts with +# Boundary or the Russian heading her parser also accepts. +BOUNDARY = re.compile(r"^##[ \t]+(Boundary|Границы)", re.MULTILINE) + + +def has_boundary(body: str | None) -> bool: + """Whether the Queen would find a boundary section in this issue body.""" + return bool(BOUNDARY.search(body or "")) + + +def gh(args: list[str]) -> str: + done = subprocess.run(["gh", *args], capture_output=True, text=True) + if done.returncode != 0: + raise RuntimeError(done.stderr.strip()[:400]) + return done.stdout + + +def open_issues() -> list[dict]: + """Every open issue, pull requests excluded: a PR is not dispatchable work.""" + rows: list[dict] = [] + out = gh([ + "api", "--paginate", + f"repos/{REPO}/issues?state=open&per_page=100", + "--jq", + "[.[] | select(.pull_request == null) " + "| {number, body, labels: [.labels[].name]}]", + ]) + for line in out.strip().splitlines(): + if line.strip(): + rows.extend(json.loads(line)) + return rows + + +# A goal is not a task. The roadmap stages and the epics are deliberately not +# dispatchable -- they are containers for the issues that are -- so labelling +# them "needs-boundary" would put eight permanent entries at the top of a list +# whose whole value is that everything on it is worth fixing. +NOT_TASKS = frozenset({"roadmap", "epic"}) + + +def decide(issues: list[dict]) -> tuple[list[int], list[int]]: + """(to label, to unlabel). Pure, so --self-test can drive it.""" + add, drop = [], [] + for issue in issues: + labels = issue.get("labels", []) + labelled = LABEL in labels + if NOT_TASKS & set(labels): + # A goal that somehow carries the label still gets it removed. + if labelled: + drop.append(issue["number"]) + continue + if has_boundary(issue.get("body")): + if labelled: + drop.append(issue["number"]) + elif not labelled: + add.append(issue["number"]) + return add, drop + + +def ensure_label() -> None: + try: + gh(["label", "create", LABEL, "--repo", REPO, + "--color", LABEL_COLOR, "--description", LABEL_ABOUT]) + except RuntimeError as error: + # Already there is the normal case after the first run. + if "already exists" not in str(error): + raise + + +def self_test() -> int: + cases = [ + ("## Boundary\n- a.py\n", True, "the heading the Queen accepts"), + ("## Границы\n- a.py\n", True, "the Russian heading she also accepts"), + ("## Boundary: files\n", True, "a heading with a trailing colon"), + ("### Boundary\n", False, "a level-three heading is not the one she reads"), + ("## boundary\n", False, "she matches the capital B"), + ("Boundary: src/\n", False, "prose is not a section"), + ("text\n\n## Boundary\n- a\n", True, "not only at the top of the body"), + ("", False, "an empty body"), + (None, False, "no body at all"), + ] + failures = 0 + for body, want, why in cases: + got = has_boundary(body) + if got != want: + print(f"FAIL ({why}): {got} != {want}") + failures += 1 + + add, drop = decide([ + {"number": 1, "body": "nothing", "labels": []}, + {"number": 2, "body": "## Boundary\n- a", "labels": [LABEL]}, + {"number": 3, "body": "## Boundary\n- a", "labels": []}, + {"number": 4, "body": "nothing", "labels": [LABEL]}, + # A goal is not a task: never labelled, and cleared if it ever was. + {"number": 5, "body": "nothing", "labels": ["roadmap"]}, + {"number": 6, "body": "nothing", "labels": ["epic", LABEL]}, + ]) + if add != [1] or drop != [2, 6]: + print(f"FAIL (decide): add={add} drop={drop}, expected add=[1] drop=[2, 6]") + failures += 1 + + print("needs_boundary self-test:", "PASS" if failures == 0 else f"{failures} FAILED") + return 1 if failures else 0 + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--dry-run", action="store_true") + parser.add_argument("--self-test", action="store_true") + args = parser.parse_args() + if args.self_test: + return self_test() + + issues = open_issues() + add, drop = decide(issues) + goals = sum(1 for i in issues if NOT_TASKS & set(i.get("labels", []))) + print( + f"open issues: {len(issues)}; without a boundary: " + f"{sum(1 for i in issues if not has_boundary(i.get('body')))}; " + f"goals held back: {goals}; to label: {len(add)}; to unlabel: {len(drop)}" + ) + if args.dry_run: + print("dry run: nothing was changed") + print("would label:", ", ".join(f"#{n}" for n in add[:25]) or "(none)") + return 0 + + if add: + ensure_label() + for number in add: + gh(["issue", "edit", str(number), "--repo", REPO, "--add-label", LABEL]) + for number in drop: + gh(["issue", "edit", str(number), "--repo", REPO, "--remove-label", LABEL]) + print(f"labelled {len(add)}, cleared {len(drop)}") + return 0 + + +if __name__ == "__main__": + sys.exit(main())