Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .github/ISSUE_TEMPLATE/config.yml
Original file line number Diff line number Diff line change
@@ -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.
47 changes: 47 additions & 0 deletions .github/ISSUE_TEMPLATE/swarm-task.md
Original file line number Diff line number Diff line change
@@ -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: ''
---

<!--
THE BOUNDARY SECTION IS NOT PAPERWORK. IT IS WHAT LETS A BEE TAKE THIS ISSUE.

The Queen reserves files before she dispatches, so that two workers never edit
the same path at once. An issue that does not say which paths it touches cannot
have anything reserved for it, so it is skipped -- silently, forever.

Measured 2026-09-23: 563 of 654 open issues had no Boundary section, and the
Queen's own tick reported `missingBoundary` for 565 candidates while the swarm
sat at 2 of 20 lanes. The backlog was not empty. It was unreachable.

Delete these comments before submitting.
-->

## Summary

What is wrong, missing, or hand-written where a spec should be. One paragraph.

## Boundary

<!--
One path per line. A bee may create or edit files under these paths and nowhere
else; anything it changes outside them is named in the review rather than
discarded. Directories are fine, and so is a single file.
-->

- `specs/port/tools/example.t27`
- `tools/example.py`

## Acceptance

<!--
How the work is judged. Prefer something that can be run over something that
has to be believed: a test that passes, a command that exits zero, a generated
file that matches.
-->

- [ ] The spec compiles and its own `test` blocks pass
- [ ] `Closes #N` is in the pull request (law L1)
54 changes: 54 additions & 0 deletions .github/workflows/needs-boundary.yml
Original file line number Diff line number Diff line change
@@ -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' || '' }}
15 changes: 10 additions & 5 deletions docs/T27-CONSTITUTION.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand All @@ -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 |
Expand All @@ -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
Expand Down
16 changes: 16 additions & 0 deletions docs/now/2026-09-23-law-l0-and-the-unreachable-backlog.md
Original file line number Diff line number Diff line change
@@ -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.
190 changes: 190 additions & 0 deletions tools/queen/needs_boundary.py
Original file line number Diff line number Diff line change
@@ -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())
Loading