Privacy-Period-Tracker/docs/WORK_CYCLE.md

272 lines
14 KiB
Markdown
Raw Normal View History

chore: adopt the project template and add the Kotlin/Compose skeleton Period was a bare directory holding one 2,527-line specification, with no git repository, no tracker and no documentation convention. This is the adoption from Projects/Template/START-HERE-New-Project.md, plus a project that compiles so the hooks and future guards have something real to run against. Documents. scaffold.sh created 19 paths, 0 skipped. The specification moved to docs/planning/PRODUCT_PLAN.md unchanged in substance, with a status header; the capitalised Docs/ is gone. Every scaffolded document was filled in for Period. docs/OPERATIONS.md deleted — an offline app is not a deployed service. DOC_TRUST_MAP.md written last, describing what is actually here, including what this project deliberately does not have. Code. Four Gradle modules. domain/cycle and domain/prediction are kotlin("jvm") and cannot see the Android SDK, so the engine is testable without an emulator — 17 tests pass, 12 of them the acceptance cases from PRODUCT_PLAN.md §51. BaselinePredictionEngine is a robust-median prototype and explicitly not the product; it exists so Batch 02's replacement can be shown to be better rather than merely different. Versions verified against their official sources today rather than inherited from the specification's own numbers, which that document asks for: Kotlin 2.4.10, AGP 9.3.1, Gradle 9.7.0, Compose BOM 2026.08.00, Room 2.8.4, Hilt 2.60.1. AGP 9 ships Kotlin built in, so org.jetbrains.kotlin.android is no longer applied. compileSdk is 37 because current AndroidX requires it; targetSdk stays 36, Play's floor from 2026-08-31, and the difference is deliberate. Six scripts taken into scripts/; the rest declined and named in docs/TOOLS.md. Three hooks in .githooks/, with pre-commit adapted to Gradle. closes #1 closes #2
2026-08-18 02:16:47 -05:00
# The work cycle — what happens at the end of a piece of work
```
Status: Current
Owner: _null
Last reviewed: 2026-08-18
Governs: what must be true before a piece of work counts as finished
Review trigger: Any change to what the Command Center reads, or to which of
those channels a person rather than an agent has to write
```
> **This is a procedure, not a status board.** Nothing here records what is open,
> what is next, or what is blocked — those live in the tracker and on
> privacyllc.dev, and a copy of them in this file would be the second disagreeing
> record that `DOC_TRUST_MAP.md` exists to prevent.
## Why this file exists
A piece of work ends in more than one place. The code is committed; the issue
that asked for it is still open; the document the change contradicts still says
the old thing; and the project screen at
[privacyllc.dev](https://privacyllc.dev) still shows last week's next action to
whoever opens it.
None of those catch up on their own. Two of them cannot be caught up later by
anybody but the person who did the work, because by then nobody knows what the
next action was meant to be.
## The cycle
Run all of it, in this order, every time. It is short on purpose.
1. **Close what you finished.** `closes #N` in the commit that does the work, so
the record comes from the thing that happened rather than a date typed
afterwards. If 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.*
2. **File what you found.** A defect noticed on the way past is an issue with a
severity label and the build SHA it was seen at, not a memory. Filing it costs
a minute; the alternative is finding it again from scratch, or shipping it.
3. **Close the milestone if the batch landed.** A milestone with every issue
closed and itself still open reads as a batch still in progress — see
[Open and closed are not bookkeeping](#open-and-closed-are-not-bookkeeping).
4. **Update the documents this change triggered.** Read the `Review trigger`
lines: a new module, a changed data shape, a new migration, a new boundary
something crosses. Those edits go in **the same commit as the code**, for the
reason in [Docs travel with the push](#docs-travel-with-the-push).
5. **Push.** Nothing above is visible off this machine until you do, and step 7
reports on what was pushed.
6. **Write the log entry** in `docs/history/DEVELOPMENT_LOG.md`: what changed,
what it proved, **Next action**, and **Blockers**. Dated, append-only, newest
first.
7. **Tell the Command Center**, which is three calls: reconcile, so it re-reads
the tracker and the pushed docs; `PATCH` the project's summary and next
action, which nothing else writes; and a check-in if what changed is worth a
timestamped note. Commands are [at the bottom](#the-commands).
Steps 6 and 7 are the two that get skipped, and they are the two nobody else can
do afterwards.
## Next action — where it lives
| Copy | Where | What it is |
| --- | --- | --- |
| The current one | `nextAction` on the project, privacyllc.dev | the live answer to "what happens next", shown to whoever opens the project screen |
| The dated one | the newest entry in `DEVELOPMENT_LOG.md` | what the next action was **at that point** — history, not status |
These are not two records of the same thing, and the distinction is worth
holding on to. The field is overwritten every time and always describes now. The
log entry is never edited and describes a moment — which is what makes it safe
to keep, and why an old entry naming a next action that has since been done is
not stale, it is a receipt.
Write the next action as an **action**: the thing a person would start on
Monday, specific enough to begin without asking a question. "Continue the work"
is not one. If the honest answer is that you do not know, that is a real answer —
say what has to be decided and by whom.
**Filing an issue can change what the project card says next, without anyone
choosing that.** The dashboard's next action is the *newest open issue in the
current milestone* — not the most severe one; severity labels have no influence
on it at all. So a routine `P2` filed into the batch you are working in replaces
whatever the card was showing, and it will keep showing that until something
newer arrives. The `nextAction` field is the only way to say something different
on purpose, which is most of why step 7 exists.
`currentSummary`, `nextAction` and `description` are **write-only**: they are
deliberately absent from every API response, because free text can name a
customer or an unannounced product. Only the admin screen shows them back. Never
report them as empty because a `GET` did not return them.
## Blockers — where they live
A blocker is recorded in up to three places, and **an agent can write only the
first and the third**:
| Where | What it holds | Who writes it |
| --- | --- | --- |
| The tracker | the work itself — an issue labelled `release-blocker`, or `P0` when it ships broken | anyone, including an agent |
| The blockers table on privacyllc.dev | the business-facing blocker, with a severity, an owner and a resolution note | **a human, in the admin UI** |
| A check-in's `blockers` field | narrative: what is stuck, said in a timestamped note | anyone, including an agent |
The agent API has no route that creates, edits or resolves a blocker on the
site. Posting a check-in whose `blockers` field says something is stuck records
*narrative* — it does not touch the blockers table, and it does not clear
anything. An agent that reports "blocker filed" after a check-in has told the
truth about the note and a falsehood about the table.
So: **file the issue** — that is the copy the work is actually tracked in — and
when it belongs in front of a stakeholder, say so plainly and let a human enter
it. Before treating a site blocker as resolved, restate its title and status and
get explicit confirmation; the admin UI requires a resolution note that the API
cannot supply.
If the work is blocked and nothing is filed anywhere, the project simply looks
slow.
## Open and closed are not bookkeeping
Every open issue is a denominator, and not in the abstract: the percentage on
the project screen **is** closed issues over all issues in this tracker. Nothing
else produces it. That has three consequences worth stating in full:
- **An issue left open after the work is done** understates the project
permanently, and the understatement compounds — a fortnight of finished work
with unclosed issues reads as a fortnight of no progress.
- **An issue closed without evidence** cannot be reopened with confidence,
because nothing in it says what "fixed" meant. That is why the close comment
carries the path, symbol, test or command.
- **Invented future work** makes every percentage wrong, permanently and in one
direction. Do not pad the tracker. If the real answer is one milestone and
three issues, file exactly that.
Milestones are the same argument at batch scale, and they are counted the same
way: milestones closed over milestones total is the second figure on the project
screen. Closing the last issue under a milestone does not close the milestone,
so a tracker full of complete-but-open batches reports a project as less
finished than it is — and cannot answer "what shipped".
**File every issue into a milestone.** One filed outside still counts against
the headline percentage while being invisible to the milestone figure, which is
how two readings of the same project come to describe different amounts of work.
The site measures the gap rather than ignoring it.
One trap that costs an afternoon, and it is about a *card* rather than a figure:
the Milestones list on privacyllc.dev reads the Command Center's own table,
which only an admin can write. A repository whose milestones are being counted
in the figure above can still show *"No milestones have been added yet"* in that
list. It is not a sync failure and no amount of reconciling changes it.
## Docs travel with the push
The Command Center reads this repository's documents at a commit, and stores the
SHA it read them from. When that SHA falls behind the repository's newest
commit, the docs report is marked **stale** — not wrong, not missing, *stale*,
which is the honest description of a document that was accurate at a commit
nobody is running any more.
Two ways to produce it, and both are ordinary carelessness rather than bad luck:
- **Code pushed, documents not updated.** The report is recomputed at the new
SHA against prose describing the old behaviour. Nothing flags this; the
document is simply confidently wrong now, and its `Last reviewed` line still
looks recent.
- **Documents updated, not pushed.** The site keeps reporting the old ones. A
correction that exists only on your machine has not been made.
Hence step 4's insistence that documentation edits ride in the same commit as
the change that caused them. It is not tidiness — a doc commit that comes "after
this next thing" is the commit that does not get written, and the manual's
migration table in the project this template came from sat six migrations behind
before anybody noticed. Every reader in between trusted it.
## What the Command Center reads, and who writes it
Four independent channels, and **only two of them are automatic**:
| 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 — issues that sit in no milestone | the repository's tracker | 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 |
The first three are why steps 1 and 3 are steps: **closing an issue and closing
a milestone each move a figure a stakeholder can see, that day.** The last two
are lists rather than figures, and they are the ones an agent cannot write — a
repository whose milestones are counted in the second figure can still show
*"No milestones have been added yet"* on the card, because that card reads a
table only an admin fills in.
A repository that has not adopted the four label names is reported as *not
adopted* rather than as zero defects, and one with its tracker switched off is
reported as switched off rather than as an empty backlog. Absence is never
rendered as a measurement — which is exactly why a real backlog nobody filed
looks like nothing at all.
## The commands
The tracker is the Forgejo instance at
**[dream.scheller.ltd](https://dream.scheller.ltd)**, and steps 1 to 3 happen
there. Its credentials, the Cloudflare trap and the reason it has to be that
instance rather than any tracker are in the project README's *Where the tracker
is* — one copy, named once:
```bash
set -a; . ~/.openclaw/docker-registry.env; set +a
docs: correct 57 claims the code and tracker disagreed with Every document in the tree audited against the source, the tracker and git history, each finding then given to a second reader tasked with refuting it. 74 raised, 12 refuted, 57 applied. No code changed. THE README DESCRIBED A SKELETON Its Status table — the one place a claim about what is built is allowed to live — still read "there is no usable app yet", with Not built against Room, the four core screens, fertility and notifications, and No round run against QA. Five batches had shipped and three QA rounds had run. TWO DOCUMENTS WERE SILENTLY NEVER FIRING architecture/README.md and design/README.md wrote Governs: as prose ("the Gradle module graph", "the design tokens in core/designsystem"). Neither contains a path token, so doc-triggers.py reduced them to globs matching nothing, and one real glob apiece made them look path-governing rather than subject-governing — the state the script's own header calls invisible. Editing a Room entity never fired the document owning the migration table. Both now fire, proved by running the script. SECURITY.md CLAIMED FOUR UNBUILT PROTECTIONS App lock listed among what works offline; biometric/PIN gating described as protecting app launch; the incognito launcher as existing; Play Billing in the third parties table without the "not yet integrated" marker its neighbours carry. All are Batch 06/07 work. The advertising boundary was overstated in SECURITY.md and the README alike: both said the ads module declares no dependency and a guard proves it. There is no ads module. The pre-declared ":core:ads" to emptySet() rule is stricter than the sentence it replaced and matches nothing until Batch 07, which is why the guard is proved by injection rather than trusted. SMALLER, EACH A REAL TRAP WORK_CYCLE.md pointed at docs/architecture/scripts/forgejo-issue.py, a template path absent here — missed by doc-claims.sh, which reads backticked prose and not fenced blocks. ClaudeReport.md's Round notes said "No rounds yet" after three rounds because ClaudeQAPlan.md's after-a-round list never named that section; the playbook is fixed first. The instrumented-test count was eight in three places and is four. HISTORY.md said the repository had no code and that nothing had been tried and dropped, when three approaches had. DELIBERATELY UNCHANGED ClaudeReport.md's last verified build SHA stays at 0451fbe — no round has run since, and moving it would claim a verification nobody performed. Every DEVELOPMENT_LOG entry stays as written. Guards: ./gradlew check, schema-guard.sh, doc-claims.sh (235 claimed paths, all present), doc-triggers.py, and a link sweep over 21 markdown files. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 16:33:14 -05:00
python3 scripts/forgejo-issue.py list
python3 scripts/forgejo-issue.py close 42 "Fixed in a1b2c3d; PredictionAcceptanceTest covers it."
chore: adopt the project template and add the Kotlin/Compose skeleton Period was a bare directory holding one 2,527-line specification, with no git repository, no tracker and no documentation convention. This is the adoption from Projects/Template/START-HERE-New-Project.md, plus a project that compiles so the hooks and future guards have something real to run against. Documents. scaffold.sh created 19 paths, 0 skipped. The specification moved to docs/planning/PRODUCT_PLAN.md unchanged in substance, with a status header; the capitalised Docs/ is gone. Every scaffolded document was filled in for Period. docs/OPERATIONS.md deleted — an offline app is not a deployed service. DOC_TRUST_MAP.md written last, describing what is actually here, including what this project deliberately does not have. Code. Four Gradle modules. domain/cycle and domain/prediction are kotlin("jvm") and cannot see the Android SDK, so the engine is testable without an emulator — 17 tests pass, 12 of them the acceptance cases from PRODUCT_PLAN.md §51. BaselinePredictionEngine is a robust-median prototype and explicitly not the product; it exists so Batch 02's replacement can be shown to be better rather than merely different. Versions verified against their official sources today rather than inherited from the specification's own numbers, which that document asks for: Kotlin 2.4.10, AGP 9.3.1, Gradle 9.7.0, Compose BOM 2026.08.00, Room 2.8.4, Hilt 2.60.1. AGP 9 ships Kotlin built in, so org.jetbrains.kotlin.android is no longer applied. compileSdk is 37 because current AndroidX requires it; targetSdk stays 36, Play's floor from 2026-08-31, and the difference is deliberate. Six scripts taken into scripts/; the rest declined and named in docs/TOOLS.md. Three hooks in .githooks/, with pre-commit adapted to Gradle. closes #1 closes #2
2026-08-18 02:16:47 -05:00
```
Step 7 talks to the Command Center instead:
```bash
# The token lives in this machine's credential store, never in the repository.
TOKEN="$PRIVACY_LLC_TOKEN"
BASE="https://privacyllc.dev/api/internal/v1"
AUTH="Authorization: Bearer $TOKEN"
```
**Reconcile** — re-read the tracker and the pushed documents. Do this after the
push, not before:
```bash
curl -sk -X POST -H "$AUTH" -H "Content-Type: application/json" \
-d '{"kind":"reconcile_all"}' "$BASE/agent/jobs"
```
**Summary and next action** — read the project first for its `version`, then
send a flat body carrying that version. A stale version is rejected rather than
silently overwriting somebody else's edit:
```bash
curl -sk -H "$AUTH" "$BASE/agent/projects/<slug>" # for version + state
curl -sk -X PATCH -H "$AUTH" -H "Content-Type: application/json" \
-d '{"version":<n>,"health":"on_track",
"currentSummary":"<where the project stands, in a sentence or two>",
"nextAction":"<the next concrete thing, specific enough to start>"}' \
"$BASE/agent/projects/<slug>"
```
**A check-in** — a timestamped "what changed", when there is something real to
report. `summary` is required and must say something; the API refuses an empty
"no change" update, deliberately:
```bash
curl -sk -X POST -H "$AUTH" -H "Content-Type: application/json" \
-d '{"projectId":"<slug>","health":"on_track",
"summary":"<what changed>","accomplishments":"<what landed>",
"blockers":"<what is stuck, or omit>","nextActions":"<what is next>"}' \
"$BASE/agent/updates"
```
Health is one of `on_track`, `caution`, `off_track`, `unknown`. Never invent
one, and never report progress the API did not return.
## What this file is not
- **Not the work list.** That is the tracker: milestones are batches, issues are
deliverables.
docs: correct 57 claims the code and tracker disagreed with Every document in the tree audited against the source, the tracker and git history, each finding then given to a second reader tasked with refuting it. 74 raised, 12 refuted, 57 applied. No code changed. THE README DESCRIBED A SKELETON Its Status table — the one place a claim about what is built is allowed to live — still read "there is no usable app yet", with Not built against Room, the four core screens, fertility and notifications, and No round run against QA. Five batches had shipped and three QA rounds had run. TWO DOCUMENTS WERE SILENTLY NEVER FIRING architecture/README.md and design/README.md wrote Governs: as prose ("the Gradle module graph", "the design tokens in core/designsystem"). Neither contains a path token, so doc-triggers.py reduced them to globs matching nothing, and one real glob apiece made them look path-governing rather than subject-governing — the state the script's own header calls invisible. Editing a Room entity never fired the document owning the migration table. Both now fire, proved by running the script. SECURITY.md CLAIMED FOUR UNBUILT PROTECTIONS App lock listed among what works offline; biometric/PIN gating described as protecting app launch; the incognito launcher as existing; Play Billing in the third parties table without the "not yet integrated" marker its neighbours carry. All are Batch 06/07 work. The advertising boundary was overstated in SECURITY.md and the README alike: both said the ads module declares no dependency and a guard proves it. There is no ads module. The pre-declared ":core:ads" to emptySet() rule is stricter than the sentence it replaced and matches nothing until Batch 07, which is why the guard is proved by injection rather than trusted. SMALLER, EACH A REAL TRAP WORK_CYCLE.md pointed at docs/architecture/scripts/forgejo-issue.py, a template path absent here — missed by doc-claims.sh, which reads backticked prose and not fenced blocks. ClaudeReport.md's Round notes said "No rounds yet" after three rounds because ClaudeQAPlan.md's after-a-round list never named that section; the playbook is fixed first. The instrumented-test count was eight in three places and is four. HISTORY.md said the repository had no code and that nothing had been tried and dropped, when three approaches had. DELIBERATELY UNCHANGED ClaudeReport.md's last verified build SHA stays at 0451fbe — no round has run since, and moving it would claim a verification nobody performed. Every DEVELOPMENT_LOG entry stays as written. Guards: ./gradlew check, schema-guard.sh, doc-claims.sh (235 claimed paths, all present), doc-triggers.py, and a link sweep over 21 markdown files. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 16:33:14 -05:00
- **Not the release procedure.** This project has no `release.sh` — a release here is
a signed AAB and a Play Console submission, so
[`security/SECURITY_CHECKLIST.md`](security/SECURITY_CHECKLIST.md) carries it.
chore: adopt the project template and add the Kotlin/Compose skeleton Period was a bare directory holding one 2,527-line specification, with no git repository, no tracker and no documentation convention. This is the adoption from Projects/Template/START-HERE-New-Project.md, plus a project that compiles so the hooks and future guards have something real to run against. Documents. scaffold.sh created 19 paths, 0 skipped. The specification moved to docs/planning/PRODUCT_PLAN.md unchanged in substance, with a status header; the capitalised Docs/ is gone. Every scaffolded document was filled in for Period. docs/OPERATIONS.md deleted — an offline app is not a deployed service. DOC_TRUST_MAP.md written last, describing what is actually here, including what this project deliberately does not have. Code. Four Gradle modules. domain/cycle and domain/prediction are kotlin("jvm") and cannot see the Android SDK, so the engine is testable without an emulator — 17 tests pass, 12 of them the acceptance cases from PRODUCT_PLAN.md §51. BaselinePredictionEngine is a robust-median prototype and explicitly not the product; it exists so Batch 02's replacement can be shown to be better rather than merely different. Versions verified against their official sources today rather than inherited from the specification's own numbers, which that document asks for: Kotlin 2.4.10, AGP 9.3.1, Gradle 9.7.0, Compose BOM 2026.08.00, Room 2.8.4, Hilt 2.60.1. AGP 9 ships Kotlin built in, so org.jetbrains.kotlin.android is no longer applied. compileSdk is 37 because current AndroidX requires it; targetSdk stays 36, Play's floor from 2026-08-31, and the difference is deliberate. Six scripts taken into scripts/; the rest declined and named in docs/TOOLS.md. Three hooks in .githooks/, with pre-commit adapted to Gradle. closes #1 closes #2
2026-08-18 02:16:47 -05:00
- **Not the QA procedure.** That is `docs/qa/ClaudeQAPlan.md`, which ends in its
own version of step 7.
- **Not a place to record status.** If you are about to add "current state" or a
list of outstanding items below this line, the tracker is where it goes.