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
|
# Project Template
|
||||||
|
|
||||||
A working documentation skeleton, the tracker convention that goes with it, and
|
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
|
the scripts that enforce both. Adopt it with
|
||||||
angle-bracket placeholders, delete the paragraphs that do not apply — then
|
`docs/architecture/scripts/scaffold.sh`, which never overwrites a file that is
|
||||||
create the labels and the first milestone in the repository's issue tracker.
|
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
|
Status: Current
|
||||||
Owner: _null
|
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
|
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
|
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
|
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
|
### Where the tracker is
|
||||||
|
|
||||||
**[dream.scheller.ltd](https://dream.scheller.ltd)** — a Forgejo instance, and
|
**A Forgejo instance**, and that it is Forgejo is not a detail. The convention
|
||||||
naming it is not a detail. The convention below is portable: milestones, issues
|
below is portable: milestones, issues and labels exist on GitHub, GitLab and
|
||||||
and labels exist on GitHub, GitLab and Jira, and every rule in it would work
|
Jira, and every rule in it would work there unchanged. **The reporting is not
|
||||||
there unchanged. **The reporting is not portable at all.** Every figure the
|
portable at all.** Every figure the Command Center shows — the headline
|
||||||
Command Center shows — the headline percentage, the milestone count, milestone
|
percentage, the milestone count, milestone coverage, the alert an opened `P0`
|
||||||
coverage, the alert an opened `P0` raises — is read from a Forgejo repository
|
raises — is read from a Forgejo repository mapped to the project. A project that
|
||||||
mapped to the project. A project that follows every rule here in a different
|
follows every rule here in a different tracker satisfies the convention
|
||||||
tracker satisfies the convention completely and appears on privacyllc.dev as
|
completely and appears on privacyllc.dev as having no tracker at all.
|
||||||
having no tracker at all.
|
|
||||||
|
|
||||||
Credentials are in **`~/.openclaw/docker-registry.env`**, which carries
|
The instance and its credentials are operator configuration, not repository
|
||||||
`FORGEJO_REGISTRY`, `FORGEJO_REGISTRY_USER` and `FORGEJO_REGISTRY_TOKEN`:
|
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
|
```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
|
python3 docs/architecture/scripts/forgejo-issue.py list
|
||||||
```
|
```
|
||||||
|
|
||||||
Despite its name that token is not registry-scoped — it works across the whole
|
Whatever that token turns out to reach, treat it as reaching everything until
|
||||||
API, with push and pull. Never print it into a log or a chat.
|
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
|
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
|
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
|
overrides that deliberately, and `--dry-run` prints the payloads and changes
|
||||||
nothing.
|
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
|
### The rules
|
||||||
|
|
||||||
- **Milestone = batch.** Name it `Batch 02 — Payments`, and **start it with a
|
- **Milestone = batch.** Name it `Batch 02 — Payments`, and **start it with a
|
||||||
|
|
@ -202,6 +205,14 @@ nothing.
|
||||||
inventing one.
|
inventing one.
|
||||||
- **Issue = deliverable.** One per item, in its milestone, ending with a
|
- **Issue = deliverable.** One per item, in its milestone, ending with a
|
||||||
`Verify:` line stating the acceptance check.
|
`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
|
- **Labels:** `P0` ships broken or loses data · `P1` materially wrong but
|
||||||
shippable · `P2` cosmetic or low impact · `release-blocker` means a release
|
shippable · `P2` cosmetic or low impact · `release-blocker` means a release
|
||||||
built today would be wrong rather than merely incomplete. Exactly these
|
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
|
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]:
|
def resolve_labels(names, available: dict[str, int]) -> list[int]:
|
||||||
"""Names → ids, failing loudly. A typo'd severity label is reported by the
|
"""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
|
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:
|
allow_dup=False) -> dict | None:
|
||||||
title = spec["title"].strip()
|
title = spec["title"].strip()
|
||||||
body = check_verify_line(spec.get("body", ""), title)
|
body = check_verify_line(spec.get("body", ""), title)
|
||||||
|
body = check_surface_named(body, title)
|
||||||
|
|
||||||
dup = find_duplicate(title, existing)
|
dup = find_duplicate(title, existing)
|
||||||
if dup and not allow_dup:
|
if dup and not allow_dup:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue