Five weeks of forge evidence per week rather than a rolling window, so a finding survives under one key and "closed after three weeks" is computable. Rules run deterministically; agent.py hands the packet to whoever writes the prose and never calls a model itself. Model choice fails closed — no default, no substitute, and the exact named model must be served or the pass refuses to run.
11 KiB
TASK-79 — project state: what became true, and what stopped being true
Status: first slice built and exercised 2026-08-18 over five weeks,
2026-W30 to 2026-W34. Internal; nothing publishes.
Repos: SouveraineOS (the tools and the store). Reads the forge and the
local clones; writes nothing outside this repo.
Connects to: saf/state.md (the hand-written spine this reads and never
writes), docs/tasks/README.md (the declared state it diffs),
~/Projects/Gitea Pipeline/TASK-02-the-ledger-pipeline.md D4/D5 (the narrator
and the quality pass — same seam, different question),
~/Projects/Web/CaseyTunturi.com/scripts/ledger/collect.py (the ancestor).
The question this answers, and the one it does not
The ledger on caseytunturi.com answers what happened — day-bucketed, 30-day rolling, overwritten in place. It is a record of a man at work and it is good at that. It cannot answer what became true, because nothing carries across the window: every week is an island, and a 31-day-old finding is simply gone.
This answers what is true now, what stopped being true, and how long it has been unresolved. Same forge, different shape: evidence is kept per week and never overwritten, and a finding survives from one week to the next under a stable key. Statements the five-week run can already make:
- task 8 left the index, held W30 to W32, closed after two weeks.
- task 15, Idle coordinator, has been Blocked or gated for four weeks.
- souveraine#1 has been unmerged for 38 days and is the oldest decision open.
- souveraine CI was regressed in W33 and mixed in W34 — the finding turned.
Why here and not in the ledger's repo
The ledger's output repo is the public site. Its CI builds and deploys on every
push, so an analytical artifact living there is one careless import away from
being published. SouveraineOS already owns project state — saf/state.md is the
resume anchor and docs/tasks/README.md is the work index — and it deploys
nowhere. The forge answers 403 to anonymous requests, so the store is behind a
login even though the repo is not marked private.
Promotion to either public surface stays a separate, deliberate act:
caseytunturi.com for engineering history, souveraineai.com for current
capability. One evidence base, two editorial surfaces, neither automatic.
Shape
forge API + local clones
↓ collect.py deterministic, no model
project-state/evidence/<week>.json
↓ synthesize.py rules first, model second
project-state/state.json durable, carried forward
project-state/history/<week>.json
project-state/reports/<week>.md prose, disposable
Nothing schedules it. The tools name their own gaps instead:
collect.py --gaps [--since 2026-W30] weeks holding no evidence
collect.py --backfill --since 2026-W30 collect every one of them, in order
synthesize.py --pending weeks collected but not passed over
synthesize.py --catch-up pass over them oldest first
Order matters — carry-forward is sequential, so --catch-up is the entry point
and --week warns when it would skip ahead of the state.
The deterministic/model split is the point. A model is never asked to discover a
fact code can read; it gets the computed findings and looks only for what
crosses between them. Its findings are marked by: model, live in their own
section of the report, and are dropped unless every evidence id they cite exists
in that week's evidence and spans two kinds of it.
The prose is regenerable — --report-only rewrites any week's report from the
stored state and evidence, so a future model can rewrite history without the
original report ever having been authoritative.
Evidence, measured 2026-08-18
| W30 | W31 | W32 | W33 | W34 (partial) | |
|---|---|---|---|---|---|
| Commits | 226 | 287 | 201 | 260 | 43 |
| CI runs | 98 | 133 | 143 | 185 | 25 |
| Declared changes | 255 | 56 | 18 | 37 | 116 |
16 repos listed, ~95 API calls a week, five weeks collected in 20 s, zero disk.
Three things the collector inherits from the ledger because they were paid for
once already: the limit=50 page clamp (Gitea ignores a larger limit and hid 24
of 74 repos for months), the branch-tip prefilter (a branch whose tip predates
the window cannot hold work inside it), and per-repo SHA dedup.
Two it does not:
- The Actions API.
/repos/{repo}/actions/runsignoreslimitandpageand returns the whole history, so it is fetched once and sliced. Paging it loops on the same rows.02-the-ledger.mdlists CI state as an unbuilt later increment; this is that increment, joined to commits byhead_sha. - Redaction. Evidence is stored raw because it never leaves the forge.
--redactapplies the ledger's patterns for anything promoted outward.
Dimensions
The SAF's own sections, plus the two it has no page for:
memory runtime reflection identity surface sensorium release
reliability federation adoption
A commit is placed by scoring its changed paths and its subject against a
keyword table, weighted alike — paths alone file compaction: target the active conversation under the server module it edits, and the subject is the author
saying which subsystem he thinks he moved. On W33: 198 by paths, 29 by subject,
22 by repository default, 11 placed nothing and are reported as unassigned
rather than guessed at. Every row keeps its basis and its paths, so any
placement can be argued with.
One dimension per commit is a simplification. A crash fix in the shell is both
surface and reliability and counts once.
State vocabulary, and the resolution rule
milestone_reached regressed blocked decision_required advanced
unchanged — assigned by rule:
- a task's section in the index (Live / In progress / Blocked or gated / Open)
gives its standing state; a move backwards is
regressed, and leaving the index entirely ismilestone_reached; decision_requiredcomes from Casey's own phrasing in the row — verdict owed, decisions are Casey's, scope and deliverable unanswered;- a repository whose last run of the week failed is
regressed; - a pull unmerged past fourteen days is
decision_required.
A finding the week says nothing about stays open. Only positive evidence closes one — a task gone from the index, a pull merged. The first two-week run resolved four CI findings because the second week had no runs for those repos, which is the difference between a state document and a diff.
Findings that describe one week — a CI week, a dimension's activity, a release cut — do not carry at all; they lapse with their week. Findings that describe a thing — a task, a pull, an issue — carry until something closes them.
Task findings track the index snapshot, not only its diff. Tracking the diff
alone made every restatement a permanent finding and the task itself invisible;
the index is the state, the diff is the movement, and weeks_in_section is how
"this remains the oldest blocker" gets computed.
Provenance
Each finding carries provenance — deterministic rule name or model, endpoint
locality resolved from the URL rather than asserted, timestamp, and the week its
evidence came from — plus a human block (verdict, note, edited) that is
empty and exists so a promote/reject pass has somewhere to write.
synthesize.py --explain <id> prints a finding and every evidence row it cites,
in full. That is the trace path, and it is one command.
The analyst, measured
ANALYST_URL / ANALYST_KEY / ANALYST_MODEL, any OpenAI-compatible endpoint.
No URL means no model pass and a complete deterministic report — the same "never
a failed run" contract the ledger's narrator has.
Against Fable on llama-swap, 2026-08-18:
- Warm, it answers in 6 s. The 14 s empty first call recorded in TASK-02 D4
was not llama-swap loading the model. Fable is a reasoning model: it fills
reasoning_contentfirst and leavescontentempty when the budget runs out before it concludes. Atmax_tokens: 250— whatcollect.py'snarrate()sets — it can never answer. The ledger's narrator has that bug now. - The full week as a brief is 39k characters; Fable spent 3000 tokens reasoning over it, returned nothing, and took 323 s. Sampling ten commit subjects per dimension cuts the brief to 20k and the run to ~200 s.
- Reasoning length varies run to run and can still eat the budget, so a truncated answer is salvaged object by object — every complete finding before the cut is kept.
- Quality is the open problem. On W33 it returned 21 findings, of which ~15 were commentary on the deterministic findings rather than on the project. A tightened charter and a gate requiring two kinds of evidence cut that to four. Of those four: one good (CI runs that neither pass nor fail nearly equal the passes, which the counts support), one arguable, two wrong — one cites a success run as evidence of failure, another cites an unrelated doc commit. A 9B is not a good analyst for this. The seam works; the model is Casey's call.
Acceptance
- Weeks collected deterministically, every row carrying a stable id.
- A durable state file carrying a finding across weeks under one key, with
first_seen,weeks_open,weeks_in_section, and resolution only on positive evidence. - Carry-forward exercised over five real weeks, not designed.
- A human-readable report where each finding names its evidence.
- One command traces a finding back to every row it rests on.
- The tools name their own unfilled weeks rather than assuming a schedule.
- An analyst whose findings survive review, or the pass explicitly parked.
- A cadence decision: on demand, or a Monday job on the same runner.
- A human verdict written into a finding — the seam exists, nothing uses it.
Open, and Casey's to answer
- Which model holds the analyst. Fable is honest and slow and wrong about half the time on this. The substrate's own config already reaches bifrost, deepseek, zai and claude; the deterministic half needs none of them.
- Where a promoted finding goes. The seam exists; the editorial call does not.
- Whether the store is committed here. It is project state, it belongs with
saf/state.md, and it is behind the forge login — but it is a machine-written tree in a repository whose other files are hand-written.
Noticed while building this, not part of it
docs/tasks/README.md publishes 10.10.20.123, 172.16.42.1 and an ssh key
path into this repo. Behind the forge login, so not the leak TASK-02 D8 closed —
same class, and it sits in the file this task diffs every week.