docs(tracker): an issue that produces a data shape names its surface
A batch of twenty-eight issues in one adopting project could close in full — every P1 included — with nothing new visible to anybody using the product. Cost per project, hours with coverage bounds, model attribution and an algorithm version on every stored figure: all of it verifiable through a JSON flag, none of it required to appear on a screen. Two of those issues exist specifically to correct what somebody believes when they look at the admin — wall-clock is not summed effort, and figures computed by different algorithm versions are not comparable. Satisfied in JSON, they correct nobody. So the rule, and the check that carries it: README.md — a schema, a stored figure, a computed field or an API response is not a deliverable by itself. Say which screen shows it, link the issue that provides one, or record that it is deliberately not shown. forgejo-issue.py — warns when the acceptance check's subject is the data (retrievable, returned, reported, recorded) and nothing in it requires a person to see the result. Both tests read the `Verify:` line rather than the body. A first attempt read the whole body and fired on 44 of 73 real open issues, because bodies say "page" and "renders" incidentally; a warning at that rate is one nobody reads, and it would have taken the `Verify:`-not-last warning down with it. Narrowed to the acceptance line, it fires on 8 of the same 73 — all eight in the batch that prompted this, none anywhere else. A warning and not a refusal. API-only is a legitimate decision; it has to be a decision rather than an omission. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
c839f1eb72
commit
694b161bb1
55
README.md
55
README.md
|
|
@ -1,14 +1,17 @@
|
|||
# Project Template
|
||||
|
||||
A working documentation skeleton, the tracker convention that goes with it, and
|
||||
the scripts that enforce both. Copy `docs/` into a new project, replace the
|
||||
angle-bracket placeholders, delete the paragraphs that do not apply — then
|
||||
create the labels and the first milestone in the repository's issue tracker.
|
||||
the scripts that enforce both. Adopt it with
|
||||
`docs/architecture/scripts/scaffold.sh`, which never overwrites a file that is
|
||||
already there — the two *Start Here* documents below say which path to take.
|
||||
Then replace the angle-bracket placeholders, delete the paragraphs that do not
|
||||
apply, and create the labels and the first milestone in the repository's issue
|
||||
tracker.
|
||||
|
||||
```
|
||||
Status: Current
|
||||
Owner: _null
|
||||
Last reviewed: 2026-08-12
|
||||
Last reviewed: 2026-08-22
|
||||
Governs: README.md, START-HERE-New-Project.md, START-HERE-Existing-Project.md
|
||||
Review trigger: Any change to how a project adopts this template — scaffold.sh's
|
||||
file list, the tracker conventions or the label names, or what
|
||||
|
|
@ -151,26 +154,30 @@ and a row on the site only when a human puts it there.
|
|||
|
||||
### Where the tracker is
|
||||
|
||||
**[dream.scheller.ltd](https://dream.scheller.ltd)** — a Forgejo instance, and
|
||||
naming it is not a detail. The convention below is portable: milestones, issues
|
||||
and labels exist on GitHub, GitLab and Jira, and every rule in it would work
|
||||
there unchanged. **The reporting is not portable at all.** Every figure the
|
||||
Command Center shows — the headline percentage, the milestone count, milestone
|
||||
coverage, the alert an opened `P0` raises — is read from a Forgejo repository
|
||||
mapped to the project. A project that follows every rule here in a different
|
||||
tracker satisfies the convention completely and appears on privacyllc.dev as
|
||||
having no tracker at all.
|
||||
**A Forgejo instance**, and that it is Forgejo is not a detail. The convention
|
||||
below is portable: milestones, issues and labels exist on GitHub, GitLab and
|
||||
Jira, and every rule in it would work there unchanged. **The reporting is not
|
||||
portable at all.** Every figure the Command Center shows — the headline
|
||||
percentage, the milestone count, milestone coverage, the alert an opened `P0`
|
||||
raises — is read from a Forgejo repository mapped to the project. A project that
|
||||
follows every rule here in a different tracker satisfies the convention
|
||||
completely and appears on privacyllc.dev as having no tracker at all.
|
||||
|
||||
Credentials are in **`~/.openclaw/docker-registry.env`**, which carries
|
||||
`FORGEJO_REGISTRY`, `FORGEJO_REGISTRY_USER` and `FORGEJO_REGISTRY_TOKEN`:
|
||||
The instance and its credentials are operator configuration, not repository
|
||||
content: they live in this machine's credential store and are loaded into the
|
||||
environment before the call. **Nothing here names either**, and neither should
|
||||
anything you add — a template is copied into repositories that may end up
|
||||
public, and a document naming a host, a credential file and what that token can
|
||||
reach is a map even when it holds no secret.
|
||||
|
||||
```bash
|
||||
set -a; . ~/.openclaw/docker-registry.env; set +a
|
||||
# Load the tracker credentials from wherever this machine keeps them, then:
|
||||
python3 docs/architecture/scripts/forgejo-issue.py list
|
||||
```
|
||||
|
||||
Despite its name that token is not registry-scoped — it works across the whole
|
||||
API, with push and pull. Never print it into a log or a chat.
|
||||
Whatever that token turns out to reach, treat it as reaching everything until
|
||||
somebody has checked: an API token named for one job is routinely scoped for
|
||||
none. Never print it into a log or a chat.
|
||||
|
||||
Two things that cost an hour each otherwise. **Cloudflare fronts the instance
|
||||
and 1010-blocks clients that do not look like a browser or curl** — Python's
|
||||
|
|
@ -186,10 +193,6 @@ cannot file into another project's tracker by accident. `--repo owner/name`
|
|||
overrides that deliberately, and `--dry-run` prints the payloads and changes
|
||||
nothing.
|
||||
|
||||
> This block names one instance and one path on the machine that runs it. If
|
||||
> this repository will be public, replace both with your own before the first
|
||||
> push.
|
||||
|
||||
### The rules
|
||||
|
||||
- **Milestone = batch.** Name it `Batch 02 — Payments`, and **start it with a
|
||||
|
|
@ -202,6 +205,14 @@ nothing.
|
|||
inventing one.
|
||||
- **Issue = deliverable.** One per item, in its milestone, ending with a
|
||||
`Verify:` line stating the acceptance check.
|
||||
- **An issue that produces a data shape names its surface.** A schema, a stored
|
||||
figure, a computed field or an API response is not a deliverable by itself —
|
||||
somebody has to be able to see it. Say which screen shows it, link the issue
|
||||
that provides one, or record the decision that it is deliberately not shown.
|
||||
Skip it and the `Verify:` line drifts toward *"a caller can retrieve it"*,
|
||||
which is satisfiable with nothing visible: a batch of twenty-eight issues
|
||||
closed that way leaves the product exactly as it was, and every figure it
|
||||
built is reachable only by whoever knows the JSON.
|
||||
- **Labels:** `P0` ships broken or loses data · `P1` materially wrong but
|
||||
shippable · `P2` cosmetic or low impact · `release-blocker` means a release
|
||||
built today would be wrong rather than merely incomplete. Exactly these
|
||||
|
|
|
|||
|
|
@ -214,6 +214,36 @@ def check_verify_line(body: str, title: str) -> str:
|
|||
return body
|
||||
|
||||
|
||||
def check_surface_named(body: str, title: str) -> str:
|
||||
"""An issue that produces a data shape names its surface.
|
||||
|
||||
Both tests read the `Verify:` line, not the body — bodies say "page" and
|
||||
"renders" incidentally all the time, and the acceptance check is the thing
|
||||
that decides whether the issue can close with nothing visible.
|
||||
|
||||
Deliberately narrow. It fires only when the acceptance check's subject is
|
||||
the data itself — retrievable, returned, reported, recorded — and nothing in
|
||||
it requires a person to see the result. A warning that fires on most issues
|
||||
is one nobody reads, and it would take the `Verify:`-not-last warning down
|
||||
with it.
|
||||
|
||||
A warning rather than a refusal: API-only is a legitimate decision, it just
|
||||
has to be a decision rather than an omission."""
|
||||
lines = [l for l in body.strip().splitlines() if l.strip()]
|
||||
verify = next((l for l in reversed(lines) if l.strip().startswith("Verify:")), "")
|
||||
data_is_the_subject = re.search(
|
||||
r"(retrievable|returns the|--json\b|a caller|is stored|recorded with|"
|
||||
r"\breports\b|available (?:from|via) the API)", verify, re.I)
|
||||
a_person_sees_it = re.search(
|
||||
r"\b(admin|screen|page|shown|shows|displayed|visible|sees|operator|"
|
||||
r"renders|dialog|button|UI)\b", verify, re.I)
|
||||
if data_is_the_subject and not a_person_sees_it:
|
||||
print(f'warning: "{title}" accepts on the data being retrievable and never on\n'
|
||||
" anyone seeing it. Name the screen, link the issue that provides\n"
|
||||
" one, or state that it is deliberately API-only.",
|
||||
file=sys.stderr)
|
||||
return body
|
||||
|
||||
def resolve_labels(names, available: dict[str, int]) -> list[int]:
|
||||
"""Names → ids, failing loudly. A typo'd severity label is reported by the
|
||||
Command Center as *not adopted*, not as zero defects — silently dropping it
|
||||
|
|
@ -390,6 +420,7 @@ def create_one(api: Api, spec: dict, labels_map, ms_map, existing,
|
|||
allow_dup=False) -> dict | None:
|
||||
title = spec["title"].strip()
|
||||
body = check_verify_line(spec.get("body", ""), title)
|
||||
body = check_surface_named(body, title)
|
||||
|
||||
dup = find_duplicate(title, existing)
|
||||
if dup and not allow_dup:
|
||||
|
|
|
|||
Loading…
Reference in New Issue