Grep across docs/** found zero mentions of security headers, token storage, account enumeration, audit trail, test environment or rate limiting. The standing list had four entries and stopped at the boundary of the repository. Grouped by the question each group answers rather than listed flat, because the grouping is the argument: - Authorisation, the three questions login does not answer. Logged-out callers refused, objects and lists scoped to the caller, privileged routes checking a role. Login is the front door; every room inside needs its own lock. - What the browser is handed. No secret in the built bundle, session tokens in HttpOnly cookies rather than localStorage, a CSP and a frame policy with nothing on plain HTTP. - What a stranger can learn or exhaust. Responses that do not confirm whether an account exists, and *(precautionary)* rate limits on authentication and on anything costing money per request. - The compliance bar, which is not the launch bar: a record of who changed what and when, and an environment that is not production to test against. Called out as a different bar on purpose -- the rest of the list gets a release out of the door, those two get it through the first compliance review. Each entry says what it proves, per this file's own rule that a check whose purpose is unstated gets skipped the first time it is inconvenient. closes #4 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|---|---|---|
| docs | ||
| .gitignore | ||
| README.md | ||
| Template.code-workspace | ||
| project-readme-template.md | ||
README.md
Project Template
A working documentation skeleton, the tracker convention that goes with it, and
twenty 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.
Status: Current
Owner: _null
Last reviewed: 2026-08-12
Governs: Projects/Template/**
Review trigger: Any change to the tracker conventions or the QA run-state
grammar in the Privacy LLC Command Center's src/lib/; any
change to which project fields the agent API can write, or to
what reconcile reads; any script added to or removed from
docs/architecture/scripts/
Why this exists
The Command Center at privacyllc.dev reports on a repository: how much is done, what the QA verdict is, and what the next milestone entails. It can only do that for repositories that keep those things somewhere it knows to look.
Eight repositories were surveyed before this was rewritten. Exactly one kept the markdown batch ledger the previous version of this template asked for. Three had invented their own dialects; one had pushed nothing but a README. Meanwhile, with no convention at all, those same repositories had accumulated 69, 54 and 205 issues — because an issue tracker has structure built into it and a convention has only whoever remembers it.
The split
| Kind of truth | Lives in | Why |
|---|---|---|
| Narrative documents — vision, trust map, QA playbook, QA verdict, security | git, as markdown | prose with an argument, versioned with the code, readable at any sha |
| Work items — batches, tasks, defects, blockers | the issue tracker | built for concurrent uncoordinated writers, atomic state, closes #N from a commit, real timestamps |
Nothing belongs in both. A to-do list in markdown and a tracker with the same items is two records that will disagree, and neither will say which is right.
What the Command Center actually reads
| Source | What is read |
|---|---|
| The repository's issue tracker | issue counts excluding pull requests, milestones with their own counters, and issues carrying the four severity labels |
docs/qa/ClaudeReport.md |
the three run-state lines — round, build SHA, and the overall sentence, quoted verbatim |
docs/data/img/ |
icon.webp, logo.webp, banner.webp — fetched and rendered on the project page |
Everything else in this tree is here because a project needs it, not because something parses it. Rename, split or extend those freely.
A repository that has not adopted the labels is reported as not adopted rather than as zero defects, and one whose tracker is switched off is reported as switched off rather than as an empty backlog. Absence is never rendered as a measurement.
The files in that table are read at a commit, and the commit is remembered.
When the SHA they were read from falls behind the repository's newest commit,
the docs report is marked stale — which is the accurate description of a
document that described the code at a commit nobody is running any more. It is
also what an unpushed correction looks like from outside. Documentation edits
therefore ride in the same commit as the change that caused them, and reach the
site on the same push. See docs/WORK_CYCLE.md.
What updates itself, and what does not
The numbers on the project screen come out of this repository's tracker. The
percentage is defined rather than picked: closed issues over all issues, asked
with type=issues so pull requests are excluded and state=all rather than the
API's default of open. Beside it sits a second figure counted from the
repository's own milestones, closed over all — this deployment keeps
milestones in the tracker and never mirrors them into the site's table, so that
is where the milestone reading comes from.
| On the project screen | Source | How it gets there |
|---|---|---|
| Headline % — issues closed / all issues | the repository's tracker | automatic — on reconcile, and immediately on a webhook delivery |
| Second figure — milestones closed / all milestones | the repository's milestones | same read, same moment |
| Milestone coverage | how many issues sit inside a milestone at all | same read |
| QA verdict — round, build SHA, overall sentence | docs/qa/ClaudeReport.md |
push the repository, then reconcile |
| Current summary / Next action | manual fields on the project | PATCH /agent/projects/<slug> — nothing else writes them |
| The Milestones card, and a typed weighted plan | the Command Center's own milestone table | admin UI only |
| Blockers | the Command Center's own blockers table | admin UI only |
So closing an issue and closing a milestone each move a number a stakeholder can see, the same day, with no further step. That is the whole reason the convention insists on both.
The last two rows are the ones that surprise people, and they are lists rather than figures. The Milestones card reads the site's own table, which is empty on this deployment by design — a repository can carry twenty-nine milestones, have them counted in the figure above, and still show "No milestones have been added yet" on that card. It is not a sync failure and reconciling will not change it.
Blockers have a sharper version of the same split. The agent API can narrate a
blocker in a check-in's blockers field, and it has no route that creates or
resolves one in the table. A run that posts the check-in and reports "blocker
filed" has told the truth about the note and a falsehood about the record.
An issue in no milestone is counted in the headline and invisible to the
milestone figure. The site measures that gap on purpose — it is the reason
Verify: lines and milestones are per-issue requirements here rather than
housekeeping. Portfolio-wide when this was written, 272 of 650 issues belonged
to no milestone at all, including every one of the 205 in the repository that
had never adopted the convention.
The digest is the part somebody actually reads
A worker polls the site every sixty seconds and sends a project digest to
Discord. It is where this convention stops being bookkeeping: the digest's
per-project figures come from issues opened and closed and from pushes, and
its next-step line comes, by rule, from nextAction or the live milestone's
open issues — never from a guess.
Three consequences, each the direct form of a step in the cycle below:
- Work with no issue opened or closed produces a push and nothing else. The project reads as quiet on a day it was not.
- A project with no
nextActionand no open milestone gives the digest nothing to say about what happens next, and the digest will not invent one. - An opened issue labelled
P0raises an immediate alert — but only from a repository whose webhook is registered. Without one, the site sees the repository through the periodic sweep alone, and a P0 filed at nine is news at the next reconcile rather than at nine.
After each piece of work
docs/WORK_CYCLE.md is the seven-step version, with the argument for each step.
The short form:
close what you finished, with the evidence · file what you found · close the milestone if the batch landed · update the documents the change triggered, in the same commit · push · log the entry, with Next action and Blockers · then reconcile and write the summary and next action.
The last two steps are the ones that get skipped, and the only ones nobody can do for you later — by the time somebody notices the project screen is a week stale, the person who knew what came next has forgotten.
Where each of those lives is deliberate and worth reading once: the next action has a live copy (the project field) and a dated copy (the newest log entry) and they are different kinds of fact; a blocker is an issue in the tracker first, and a row on the site only when a human puts it there.
The tracker convention
Where the tracker is
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.
Credentials are in ~/.openclaw/docker-registry.env, which carries
FORGEJO_REGISTRY, FORGEJO_REGISTRY_USER and FORGEJO_REGISTRY_TOKEN:
set -a; . ~/.openclaw/docker-registry.env; set +a
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.
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
urllib among them — so every request needs User-Agent: curl/8.5.0;
forgejo-issue.py already sends it and anything new must too, or the failure
arrives as a Cloudflare HTML page that looks nothing like a Forgejo error. And
/issues returns pull requests too unless type=issues is passed, which is
why the counts here exclude them and a hand-rolled query disagrees by exactly
the number of open PRs.
forgejo-issue.py takes the repository from this checkout's git remote, so it
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 word rather than a version number: the dashboard reads a leading version token as the whole phase and drops the rest, so0.2 Paymentsdisplays as0.2and the batch loses its name on the project card. No comma either — a comma breaks themilestones=filter and the card shows the wrong next action. The description says what the batch is for and how anybody will know it landed. A due date means a commitment — leave it empty rather than inventing one. - Issue = deliverable. One per item, in its milestone, ending with a
Verify:line stating the acceptance check. - Labels:
P0ships broken or loses data ·P1materially wrong but shippable ·P2cosmetic or low impact ·release-blockermeans a release built today would be wrong rather than merely incomplete. Exactly these names — they are queried by name. - Defects are issues with a severity label. The QA verdict stays in
ClaudeReport.md; the counts come from the tracker. - Close with
closes #Nin the commit that does the work, so the record comes from the thing that happened rather than from a date typed afterwards. Where no single commit finished it, close it by hand with the evidence — a path, a symbol, a test name, or the command that proves it. "Done" is not a close, because a close with nothing in it cannot be reopened with confidence. - Close the milestone when the batch lands. Closing the last issue under it does not, and a milestone that is complete but open reads as a batch still in progress. It is how a batch becomes visible as shipped rather than merely finished — and it is counted: milestones closed over milestones total is one of the two figures the project screen shows.
- Every issue belongs to a milestone. An issue filed outside one still counts against the headline percentage and is invisible to the milestone figure, so the two readings describe different amounts of work. The site measures that gap rather than ignoring it.
The rule that keeps the numbers honest
Do not pad the tracker. Every open issue is a denominator. If the real answer is "one milestone, three issues", file exactly that. Invented future work makes every percentage drawn from the tracker wrong, permanently and in the same direction.
The same arithmetic runs the other way, and this half is the one that actually happens: an issue left open after the work is done understates the project for as long as it stays open, and the understatement compounds. A fortnight of finished work with unclosed issues reads, from outside, as a fortnight of no progress at all.
What comes with it, besides the documents
docs/architecture/scripts/ holds working code, not examples. Each takes its
configuration from the environment and hard-codes nothing about any deployment,
so a script copied from here cannot quietly point at the project it came from.
docs/architecture/README.md has the full table; the ones worth knowing about
on day one:
release.sh |
version bump, guards, build, verify, push. Refuses to build on a half-run test suite or a malformed public origin — both of which had shipped in every release of the project this came from. |
verify.sh |
every check the project has, in one command, with one table saying which passed, which failed, and which did not run |
check-env.sh |
which variables are set and which are missing, before anything reads them |
secrets.sh |
credential shapes in a staged diff, using the project's own patterns where it has written them down |
prove-guard.sh |
breaks what a guard protects and requires the guard to go red, then restores the file from a trap. GUARDS.md §1 as a command — the rule that a guard nobody has seen fail is not yet evidence. |
doc-claims.sh |
every file a document names must exist — and --covers, whether every file that exists is named. The second is the one that catches a list quietly missing rows. |
duplication.py dead-code.py |
code that exists twice; exports nothing imports and assets nothing renders |
forgejo-issue.py |
files and closes issues in the convention below, with every rule of the convention above as a check the script refuses to break |
deploy.py |
updates a running stack to a published image. Publishing and deploying are separate decisions; release.sh makes the first and this makes the second. |
release-notes.mjs |
tags the release and writes the notes: sections from the commit types the hook enforces, the image and its digest, and the batches the release finished. Set RELEASE_DEPLOY_NOTE to say how this project deploys, or empty where publishing and deploying are one act. |
docs/architecture/githooks/ holds three hooks — typecheck and tests before a
commit, a conventional type in the message, and a push afterwards so a guarded
commit does not sit unpushed. They live in the repository rather than in
.git/hooks, which is not versioned; see their README for the one command that
points git at them.
docs/architecture/GUARDS.md is worth reading before you write a check.
Every rule in it was learned from a guard that had been green for months over
something broken, and the first — prove a guard fails before believing it
passes — is the one that costs thirty seconds and catches the rest.
Adopting it
cp -r Projects/Template/docs <your-project>/docs- Fill in the status header on each file — especially Review trigger, which is the line that keeps a document from going quietly stale.
- Create the four labels, one milestone, and an issue per deliverable you can actually see ahead of you — in the tracker named above, not whichever one the host happens to offer.
- Point git at the hooks:
cp docs/architecture/githooks/{pre-commit,commit-msg,post-commit} .githooks/ && chmod +x .githooks/* && git config core.hooksPath .githooks. It is per-clone, so every checkout runs it once. - Run
bash docs/architecture/scripts/check-env.shandbash docs/architecture/scripts/secrets.sh --tracked— the first says what is not configured, the second is the one-time audit for what is already committed. - Map the repository on the project's edit screen in the Command Center, then
press Pull. One thing does not follow from mapping it and is silent when
missing: register the repository's webhook and confirm a delivery
arrives. Without one the site sees this repository only through the periodic
sweep — the counts still arrive, a day late, and an opened
P0raises no alert at all. - Skim
docs/TOOLS.md— the fixed path every project has, saying which scripts can stop you and where to start in a fresh clone. Then readdocs/WORK_CYCLE.mdonce, now rather than at the end of the first session. It is what happens every time a piece of work finishes, and the two steps it exists for — recording the next action and the blockers, and telling the site what changed — are the two that are invisible when they are skipped.
There is also a generated prompt for doing all of this with an agent, on the Docs template screen of the Command Center — editable before you copy it.