tools (volt): the check-in leg grades the whole archive against a ledger, not one day -- a backdated fault was rc 0 before, rc 1 now
The 09:49Z leg ran checkin_check --since <newest day>, on the reasoning that the
newest day is the only day a new fault can appear. Measured false on a clone of
e98a34b: one added checkins/2026-10-02-volt-b.md (BAD_NAME) is invisible to that
floor -- verify --fast rc 0 'checkin_check: 1 files, 0 with a fault', while the
whole-archive run on the same tree is rc 1 with 74 files / 36 faults.
tools/checkin_baseline.tsv (35 data rows, sha256 0f599c5e...) is the faults the
archive already carried; checkin_check --baseline splits each row's classes into
known/new, only a new class makes rc 1, per class not per path; --emit-baseline
prints it to stdout (no new write path); an unreadable ledger is rc 2; stale
entries are advisory. Refreshed leg: same clone, same file -> rc 1 naming exactly
that file; live tree -> 73 files, 0 new, 35 known, rc 0.
Cells: checkin_check 29 -> 35 (35/35), verify 35 -> 37 (37/37), sweep 19/19 +
17/17 rc 0, ruff F,E9 clean. One docstring count removed after
doc_census_check graded it STALE -- the ledger is now the artifact that carries it.
This commit is contained in:
1 parent
fdc4a2178d
commit
3d58197c5f
5 files changed
+402
-58
No files matched your search
@@ -10092,3 +10092,16 @@ Artifact: `/root/.hermes/wizard-outputs/volt/2026-10-18-index-stale/` (`REPORT.m
|
|||||||
- **Nothing operational.** No queue item opened or closed; open set unchanged at `Q37:5278` (pipe) and career-lane `Q53:7303` (held for Alexander); no issue/PR/release/deploy surface; no another-party file edited (the only file changed is this lane's own `tools/verify.py`); no counterparty messaged; no secret printed. Reply 33's ask is done: `git config user.name Volt` / `user.email volt@agents.invalid` are set in the shell that ran this commit, so the author line no longer lands as the pipe. Artifact: `/root/.hermes/wizard-outputs/volt/2026-10-19-checkin-leg/` (`REPORT.md`, `check.sh`, `before/verify-3legs.py`, `before/verify.txt`, `before/checkin_check.txt`, `after/verify.txt`, `clone-path.txt`).
|
- **Nothing operational.** No queue item opened or closed; open set unchanged at `Q37:5278` (pipe) and career-lane `Q53:7303` (held for Alexander); no issue/PR/release/deploy surface; no another-party file edited (the only file changed is this lane's own `tools/verify.py`); no counterparty messaged; no secret printed. Reply 33's ask is done: `git config user.name Volt` / `user.email volt@agents.invalid` are set in the shell that ran this commit, so the author line no longer lands as the pipe. Artifact: `/root/.hermes/wizard-outputs/volt/2026-10-19-checkin-leg/` (`REPORT.md`, `check.sh`, `before/verify-3legs.py`, `before/verify.txt`, `before/checkin_check.txt`, `after/verify.txt`, `clone-path.txt`).
|
||||||
|
|
||||||
— Volt
|
— Volt
|
||||||
|
|
||||||
|
|
||||||
|
**Volt tick 2026-10-19 10:00Z — the fourth leg graded one day, and a check-in added under a backdated name walked past it: the check-in leg now grades the whole archive against a committed ledger.** (Pure suffix append; clock read immediately before this edit at 10:00:19Z; the file's true last line at `e98a34b` was `— Volt` closing the 09:49Z block, so nothing above this anchor was touched. This block opens and closes no item; the index is re-rendered in this same commit and `--check` reads rc 0 at the commit this block lands in.)
|
||||||
|
|
||||||
|
- **The hole, measured before anything was changed.** The `checkin` leg added at 09:49Z ran `checkin_check.py --since <newest day present>` on the reasoning that the newest day is the only day on which a new fault can appear. That reasoning is false in one direction, and a clone of `e98a34b` showed it: one file added at `checkins/2026-10-02-volt-b.md` — a badly-named file whose date half is a typo, i.e. the ordinary way this arrives — is not on the newest day, so the `--since` floor excluded it. Same tree, two instruments, opposite verdicts: `checkin_check.py --repo . --fail` → **74 files, 36 with a fault (BAD_NAME 26; DUPLICATE groups 10), rc 1**; `verify.py --fast` → **rc 0**, `gate: OK -- 3 of 3 graded leg(s) passed`, its checkin row reading `checkin_check: 1 files, 0 with a fault, 0 advisory`. Captured in the artifact's `before/verify-fast.txt` and `before/checkin-whole-archive.txt`.
|
||||||
|
- **The fix is a ledger, not a wider window.** `tools/checkin_baseline.tsv` (5 header lines + 35 data rows, 1,857 B, sha256 `0f599c5e7955a124d92e1fd41d5087598af19e69f00de4cd1f610f0f4851022e`) names the faults this archive already carried, one `<filename>\t<CLASS[,CLASS]>` row each — `DUPLICATE` included as a class, because 10 of the 35 rows are that. `checkin_check.py --baseline PATH` splits every row's classes into **known** (in the ledger) and **new**; only a new class makes rc 1. So the archive is graded end to end on every run and a fault landing on *any* date is red the moment it lands. The split is per **class**, not per path: a listed file that gains a class it did not have (truncated to empty) is new. `--emit-baseline` prints the ledger to stdout and exits 0, so refreshing it is a redirect and the tool keeps no new write path; an unreadable or malformed ledger is **rc 2** (could not grade), never a silent pass; entries that no longer match (file gone, class cleared) print as `STALE BASELINE` and never make rc 1.
|
||||||
|
- **Proof after, same clone and same one file.** `verify.py --fast` on the fixed tree → **rc 1**, `checkin FAULT rc=1 FAIL: 1 file(s) carry a class the baseline does not tolerate`, `verify: 4 legs, 2 pass, 1 fault, 0 ungradable, 1 skipped`. The 35 pre-rule rows stayed green in that same run — the file was the *only* new fault named. Live tree: **73 files, 0 new fault(s), 35 known-faulting in the baseline (BAD_NAME 25; DUPLICATE groups 10), 0 stale, 0 cleared, rc 0**. Artifact `after/verify-fast.txt`.
|
||||||
|
- **The cells, and the second defect this exposed.** `checkin_check.py --selftest` **29 → 35 cells, 35 passed** (c30 a ledger naming every class grades rc 0; c31 a fault on an older date the ledger does not name is rc 1; c32 a gained class on a listed path is new; c33 emit → load round-trips and silences exactly the current archive; c34 an entry for a file that is gone is stale, never a fault; c35 an unreadable, malformed or absent ledger refuses at rc 2). `verify.py --selftest` **35 → 37 cells, 37 passed** — cell 16b now pins that the leg is run with the ledger resolved **from `--repo`** and *no* `--since` by default, cell 19/19b that a missing ledger reaches the row as UNGRADABLE with the tool's own reason. `selftest_sweep.py` **19/19 read-refusal, 17/17 malformed, rc 0**; `ruff check --select F,E9 tools/` **All checks passed**.
|
||||||
|
- **A count in this lane's own docstring, corrected by the census rather than by argument.** `verify.py`'s new prose first asserted the archive's unbaselined figure as a bare `35 faults` counted at HEAD, and `doc_census_check.py` graded it **STALE** (`printed 1 [no-rev]`): the number was true at `e98a34b` and is *not a census the current tool prints*, because the tool now prints new faults and known-faulting separately. The assertion was removed and replaced by a pointer at the ledger, which is the artifact that carries the 35 rows — a number that lives in a file cannot drift from the file. Same rule applied in `checkin_check.py`'s docstring (`the 35 pre-rule faults` → `the faults the archive already carried`).
|
||||||
|
- **What this does not do.** The ledger is a maintenance surface: a *legitimate* new fault-tolerant state (a wizard renaming a bad file, a section of history republished) needs `--emit-baseline` re-run and committed, and until it is, the gate reads that repair as a stale entry (advisory, never rc 1) — a repair is visible, not silent, which is the direction worth paying for. It does not grade check-in *content* (A1 stays advisory inside `checkin_check.py` and this leg inherits that), does not repair the pre-rule rows (renaming them would edit the record to make a report green), and does not add the ruff leg, which is still a dependency decision: `ruff 0.16.4` is on this host and not a repo dependency, so the leg would make a ruff-less host rc 2 for the whole gate.
|
||||||
|
- **Nothing operational.** No queue item opened or closed; open set unchanged at `Q37:5278` (pipe) and career-lane `Q53:7303` (held for Alexander); no issue/PR/release/deploy surface; no another-party file edited (the two files changed and the one file added are this lane's own `tools/`); no counterparty messaged; no secret printed. No second check-in file was written for 2026-10-19 — the contract this leg enforces counts a same-day suffix as a DUPLICATE, and the 09:37Z file is the day's one. Artifact: `/root/.hermes/wizard-outputs/volt/2026-10-19-baseline-ledger/` (`REPORT.md`, `check.sh`, `probe.sh`, `before/`, `after/`, `post-verify.txt`).
|
||||||
|
|
||||||
|
— Volt
|
||||||
+9
-9
@@ -56,7 +56,7 @@
|
|||||||
| 46 | Q44 | 6162-6705 | 88496 | opening | The pin class: redirect every constant composed from the live data root, not only `SNAPSHOT_PATH` *(Helm lane; opened 2026-10-12 by the pipe, per Q43's own closing sentence — "extend the redirect … | DONE 2026-10-12 — `recoveries/helm-root-redirect.md` (Merlin, Helm lane); the closure block is at the foot |
|
| 46 | Q44 | 6162-6705 | 88496 | opening | The pin class: redirect every constant composed from the live data root, not only `SNAPSHOT_PATH` *(Helm lane; opened 2026-10-12 by the pipe, per Q43's own closing sentence — "extend the redirect … | DONE 2026-10-12 — `recoveries/helm-root-redirect.md` (Merlin, Helm lane); the closure block is at the foot |
|
||||||
| 47 | Q48 | 6706-6825 | 15123 | opening | The runtime-resolution redirect: close the class Q45's literals cannot see | DONE 2026-10-13 — `recoveries/helm-runtime-redirect.md` (Merlin, Helm lane), scratch tree |
|
| 47 | Q48 | 6706-6825 | 15123 | opening | The runtime-resolution redirect: close the class Q45's literals cannot see | DONE 2026-10-13 — `recoveries/helm-runtime-redirect.md` (Merlin, Helm lane), scratch tree |
|
||||||
| 48 | Q49 | 6826-7593 | 92050 | opening | The runtime-resolved root's live write path: name it, reproduce it, then close it | open and unclaimed. Opened by the pipe 2026-10-13 |
|
| 48 | Q49 | 6826-7593 | 92050 | opening | The runtime-resolved root's live write path: name it, reproduce it, then close it | open and unclaimed. Opened by the pipe 2026-10-13 |
|
||||||
| 49 | Q45 | 7594-10094 | 384209 | opening | §9 follow-on — the home-directory channel *(Helm lane; Merlin)* | -- |
|
| 49 | Q45 | 7594-10107 | 390226 | opening | §9 follow-on — the home-directory channel *(Helm lane; Merlin)* | -- |
|
||||||
|
|
||||||
## Ids with no `## ` heading
|
## Ids with no `## ` heading
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ Opened as records inside another section. `line` is the record's own opening lin
|
|||||||
| Q50 | 6990 | 6990 | 46 | Q49 | BULLET+BOLD | BOLD-LEAD |
|
| Q50 | 6990 | 6990 | 46 | Q49 | BULLET+BOLD | BOLD-LEAD |
|
||||||
| Q51 | 7106 | 7106 | 37 | Q49 | BULLET+BOLD | BOLD-LEAD |
|
| Q51 | 7106 | 7106 | 37 | Q49 | BULLET+BOLD | BOLD-LEAD |
|
||||||
| Q52 | 7167 | 7167 | 90 | Q49 | BULLET+BOLD | BOLD-LEAD |
|
| Q52 | 7167 | 7167 | 90 | Q49 | BULLET+BOLD | BOLD-LEAD |
|
||||||
| Q53 | 7292 | 7246 | 123 | Q49 | BULLET+BOLD | BOLD-LEAD |
|
| Q53 | 7292 | 7246 | 124 | Q49 | BULLET+BOLD | BOLD-LEAD |
|
||||||
| Q63 | 9918 | 9909 | 17 | Q45 | BULLET+BOLD | BOLD-LEAD |
|
| Q63 | 9918 | 9909 | 17 | Q45 | BULLET+BOLD | BOLD-LEAD |
|
||||||
|
|
||||||
**Not in this document.** Ids the prose corpus names that the queue has never mentioned (`checkins/`, `replies/`, `recoveries/`, `audits/`, `docs/`, the top-level documents; `tools/` excluded). That set moves with the prose, not with the queue, so embedding it here would make `--check` go RED for a reason that has nothing to do with the queue being current. It is reported on demand: `python3 tools/queue_index.py --outside`.
|
**Not in this document.** Ids the prose corpus names that the queue has never mentioned (`checkins/`, `replies/`, `recoveries/`, `audits/`, `docs/`, the top-level documents; `tools/` excluded). That set moves with the prose, not with the queue, so embedding it here would make `--check` go RED for a reason that has nothing to do with the queue being current. It is reported on demand: `python3 tools/queue_index.py --outside`.
|
||||||
@@ -217,7 +217,7 @@ The block below is the parse this document renders, byte for byte. `--check` com
|
|||||||
"form": "BOLD-LEAD",
|
"form": "BOLD-LEAD",
|
||||||
"id": "Q53",
|
"id": "Q53",
|
||||||
"line": 7292,
|
"line": 7292,
|
||||||
"refs": 123,
|
"refs": 124,
|
||||||
"section": "Q49",
|
"section": "Q49",
|
||||||
"shape": "BULLET+BOLD"
|
"shape": "BULLET+BOLD"
|
||||||
},
|
},
|
||||||
@@ -236,9 +236,9 @@ The block below is the parse this document renders, byte for byte. `--check` com
|
|||||||
"end_line": 8,
|
"end_line": 8,
|
||||||
"line": 1
|
"line": 1
|
||||||
},
|
},
|
||||||
"queue_bytes": 1276470,
|
"queue_bytes": 1282487,
|
||||||
"queue_lines": 10094,
|
"queue_lines": 10107,
|
||||||
"queue_sha256": "a145bebb1b0c70738c3d284d4ac638feec7f97833f4800db483dc7a8464efe27",
|
"queue_sha256": "0f8829c1114a19f23a29e75f0c93faf3ade6e98bda35199c3b74aebae1896eeb",
|
||||||
"sections": [
|
"sections": [
|
||||||
{
|
{
|
||||||
"bytes": 3139,
|
"bytes": 3139,
|
||||||
@@ -817,11 +817,11 @@ The block below is the parse this document renders, byte for byte. `--check` com
|
|||||||
"title": "The runtime-resolved root's live write path: name it, reproduce it, then close it"
|
"title": "The runtime-resolved root's live write path: name it, reproduce it, then close it"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"bytes": 384209,
|
"bytes": 390226,
|
||||||
"end_line": 10094,
|
"end_line": 10107,
|
||||||
"id": "Q45",
|
"id": "Q45",
|
||||||
"line": 7594,
|
"line": 7594,
|
||||||
"lines": 2501,
|
"lines": 2514,
|
||||||
"role": "opening",
|
"role": "opening",
|
||||||
"role_detail": "",
|
"role_detail": "",
|
||||||
"status": null,
|
"status": null,
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# check-in baseline -- the faults this archive already carried when the
|
||||||
|
# naming rule was written. One row per file: <filename>\t<CLASS[,CLASS]>.
|
||||||
|
# Regenerate: checkin_check.py --repo . --emit-baseline > tools/checkin_baseline.tsv
|
||||||
|
# A row is a stated tolerance, not an exemption: a file that gains a class,
|
||||||
|
# and a file that is not listed, both make rc 1 under --fail --baseline.
|
||||||
|
2026-09-24-zephyrus-02.md BAD_NAME,DUPLICATE
|
||||||
|
2026-09-24-zephyrus.md DUPLICATE
|
||||||
|
2026-09-26-zephyrus-02.md BAD_NAME,DUPLICATE
|
||||||
|
2026-09-26-zephyrus.md DUPLICATE
|
||||||
|
2026-09-28-zephyrus-02.md BAD_NAME,DUPLICATE
|
||||||
|
2026-09-28-zephyrus.md DUPLICATE
|
||||||
|
2026-10-01-zephyrus-03.md BAD_NAME,DUPLICATE
|
||||||
|
2026-10-01-zephyrus-04.md BAD_NAME,DUPLICATE
|
||||||
|
2026-10-01-zephyrus-05.md BAD_NAME,DUPLICATE
|
||||||
|
2026-10-01-zephyrus-06.md BAD_NAME,DUPLICATE
|
||||||
|
2026-10-01-zephyrus-07.md BAD_NAME,DUPLICATE
|
||||||
|
2026-10-01-zephyrus.md DUPLICATE
|
||||||
|
2026-10-02-zephyrus-01.md BAD_NAME
|
||||||
|
2026-10-03-zephyrus-02.md BAD_NAME,DUPLICATE
|
||||||
|
2026-10-03-zephyrus-03.md BAD_NAME,DUPLICATE
|
||||||
|
2026-10-03-zephyrus.md DUPLICATE
|
||||||
|
2026-10-04-zephyrus-02.md BAD_NAME,DUPLICATE
|
||||||
|
2026-10-04-zephyrus.md DUPLICATE
|
||||||
|
2026-10-05-zephyrus-02.md BAD_NAME,DUPLICATE
|
||||||
|
2026-10-05-zephyrus-03.md BAD_NAME,DUPLICATE
|
||||||
|
2026-10-05-zephyrus.md DUPLICATE
|
||||||
|
2026-10-06-zephyrus-02.md BAD_NAME,DUPLICATE
|
||||||
|
2026-10-06-zephyrus-04.md BAD_NAME,DUPLICATE
|
||||||
|
2026-10-06-zephyrus-05.md BAD_NAME,DUPLICATE
|
||||||
|
2026-10-06-zephyrus-06.md BAD_NAME,DUPLICATE
|
||||||
|
2026-10-06-zephyrus-wizards-02.md BAD_NAME,DUPLICATE
|
||||||
|
2026-10-06-zephyrus-wizards-03.md BAD_NAME,DUPLICATE
|
||||||
|
2026-10-06-zephyrus-wizards.md BAD_NAME,DUPLICATE
|
||||||
|
2026-10-06-zephyrus.md DUPLICATE
|
||||||
|
2026-10-07-cassian-02.md BAD_NAME,DUPLICATE
|
||||||
|
2026-10-07-cassian-q5-census.md BAD_NAME,DUPLICATE
|
||||||
|
2026-10-07-cassian-q5-recount.md BAD_NAME,DUPLICATE
|
||||||
|
2026-10-07-cassian.md DUPLICATE
|
||||||
|
2026-10-07-zephyrus-02.md BAD_NAME,DUPLICATE
|
||||||
|
2026-10-07-zephyrus.md DUPLICATE
|
||||||
|
+265
-13
@@ -57,18 +57,49 @@ RULES IMPLEMENTED (stated, not implied)
|
|||||||
EXIT CODES (the repo's contract, `tools/selftest_sweep.py`)
|
EXIT CODES (the repo's contract, `tools/selftest_sweep.py`)
|
||||||
0 ran; nothing to report, or faults reported without --fail
|
0 ran; nothing to report, or faults reported without --fail
|
||||||
1 ran, and graded at least one F-fault, under --fail
|
1 ran, and graded at least one F-fault, under --fail
|
||||||
2 could not grade: no checkins dir, no roster, or a file this host cannot
|
2 could not grade: no checkins dir, no roster, a baseline ledger this host
|
||||||
read as UTF-8. Never 1, never a traceback.
|
cannot read or parse, or a file this host cannot read as UTF-8. Never 1,
|
||||||
|
never a traceback.
|
||||||
|
|
||||||
READ-ONLY: the only write path is `--json PATH`. It renames nothing -- the
|
BASELINE: GRADING THE WHOLE ARCHIVE WITHOUT KEEPING IT RED
|
||||||
badly named files are the principals' history, and a tool that renamed them
|
A `--since` floor is how this tool stayed adoptable: the faults the archive
|
||||||
would be editing the record to make its own report green.
|
already carried are the record and are not re-litigated every run. But a floor grades *one
|
||||||
|
side of the archive*, and a floor chosen from the archive's own newest date
|
||||||
|
grades **nothing that is added before that date**. Measured on a clone of
|
||||||
|
`e98a34b` on 2026-10-19: a new `checkins/2026-10-02-volt-b.md` (BAD_NAME)
|
||||||
|
added today is invisible to `--since 2026-10-19` -- `verify.py --fast` printed
|
||||||
|
`checkin_check: 1 files, 0 with a fault` and exited **0** -- while the same
|
||||||
|
tree's whole-archive run exited **1** with that file in the table. A backdated
|
||||||
|
filename is not a hypothetical: it is what a typo in the date half makes.
|
||||||
|
|
||||||
|
`--baseline PATH` replaces the floor with a ledger. The ledger is one row per
|
||||||
|
*known-faulting file*: `<filename>\t<CLASS[,CLASS]>`, with `DUPLICATE` as a
|
||||||
|
class like any other. A file's classes are then split into **known** (in the
|
||||||
|
ledger) and **new**; only a new class makes rc 1, so the archive can be
|
||||||
|
graded end to end every run and a fault that lands *anywhere*, on any date, is
|
||||||
|
red the moment it lands. The split is per **class**, not per path: an existing
|
||||||
|
file that gains a class it did not have (a check-in truncated to empty) is
|
||||||
|
new, because the ledger states what was tolerated, not that the path is
|
||||||
|
exempt. Entries that no longer match -- file gone, or class cleared -- are
|
||||||
|
reported as stale and never make rc 1: that is someone repairing the record,
|
||||||
|
which is the point.
|
||||||
|
|
||||||
|
`--emit-baseline` prints that ledger to stdout and exits 0, so refreshing it
|
||||||
|
is a redirect and the tool keeps no write path of its own. An unreadable or
|
||||||
|
malformed ledger is rc 2, never a silent pass: a baseline that cannot be read
|
||||||
|
cannot certify anything.
|
||||||
|
|
||||||
|
READ-ONLY: the only write paths are `--json PATH` and stdout. It renames
|
||||||
|
nothing -- the badly named files are the principals' history, and a tool that
|
||||||
|
renamed them would be editing the record to make its own report green.
|
||||||
|
|
||||||
USAGE:
|
USAGE:
|
||||||
python3 checkin_check.py --repo /root/troupe-standup
|
python3 checkin_check.py --repo /root/troupe-standup
|
||||||
python3 checkin_check.py --repo . --gaps --json /tmp/checkins.json
|
python3 checkin_check.py --repo . --gaps --json /tmp/checkins.json
|
||||||
|
python3 checkin_check.py --repo . --fail --baseline tools/checkin_baseline.tsv
|
||||||
|
python3 checkin_check.py --repo . --emit-baseline > tools/checkin_baseline.tsv
|
||||||
python3 checkin_check.py --dir /tmp/fake/checkins --roster volt,zephyrus --today 2026-10-18
|
python3 checkin_check.py --dir /tmp/fake/checkins --roster volt,zephyrus --today 2026-10-18
|
||||||
python3 checkin_check.py --selftest # 29 cells, names, dates, roster, refusals
|
python3 checkin_check.py --selftest # 34 cells, names, dates, roster, baseline, refusals
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
@@ -89,6 +120,10 @@ FENCE_RE = re.compile(r"^\s*(```|~~~)")
|
|||||||
WANT_HEADINGS = ("did", "blocked", "next", "ask")
|
WANT_HEADINGS = ("did", "blocked", "next", "ask")
|
||||||
|
|
||||||
FAULTS = ("BAD_NAME", "UNKNOWN_HANDLE", "BAD_DATE", "FUTURE", "EMPTY")
|
FAULTS = ("BAD_NAME", "UNKNOWN_HANDLE", "BAD_DATE", "FUTURE", "EMPTY")
|
||||||
|
# Every class a baseline ledger may name. DUPLICATE is computed after grading
|
||||||
|
# (it is a property of a *group*), so it lives here rather than in FAULTS -- but
|
||||||
|
# a ledger that cannot name it could not record the worst rows in this archive.
|
||||||
|
BASELINE_CLASSES = frozenset(FAULTS) | {"DUPLICATE"}
|
||||||
# Not a check-in, and never silently dropped: the directory's own README is
|
# Not a check-in, and never silently dropped: the directory's own README is
|
||||||
# printed as SKIP so a reader can see it was seen and why it was not graded.
|
# printed as SKIP so a reader can see it was seen and why it was not graded.
|
||||||
SKIP_NAMES = {"README.md"}
|
SKIP_NAMES = {"README.md"}
|
||||||
@@ -311,6 +346,105 @@ def gaps(rows, today: date, cap: int = 60):
|
|||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------- baseline
|
||||||
|
|
||||||
|
def classes_of(rec):
|
||||||
|
"""A row's full class set, duplicates folded in: the ledger's unit."""
|
||||||
|
return set(rec["class"]) | ({"DUPLICATE"} if rec.get("duplicate") else set())
|
||||||
|
|
||||||
|
|
||||||
|
def emit_baseline(rep):
|
||||||
|
"""The ledger as text: one `<filename>\\t<CLASS,CLASS>` row per faulting file.
|
||||||
|
|
||||||
|
Basenames, not paths: `checkins/` is flat and the ledger has to survive a
|
||||||
|
checkout on another host. Sorted, so regenerating it on an unchanged archive
|
||||||
|
produces an unchanged file and the diff is only what moved. The header is
|
||||||
|
part of the output because the output IS the file: a ledger that has to be
|
||||||
|
explained somewhere else is a ledger nobody can read on its own.
|
||||||
|
"""
|
||||||
|
head = ("# check-in baseline -- the faults this archive already carried when the\n"
|
||||||
|
"# naming rule was written. One row per file: <filename>\\t<CLASS[,CLASS]>.\n"
|
||||||
|
"# Regenerate: checkin_check.py --repo . --emit-baseline > tools/checkin_baseline.tsv\n"
|
||||||
|
"# A row is a stated tolerance, not an exemption: a file that gains a class,\n"
|
||||||
|
"# and a file that is not listed, both make rc 1 under --fail --baseline.\n")
|
||||||
|
lines = []
|
||||||
|
for r in sorted(rep["rows"], key=lambda x: x["file"]):
|
||||||
|
cls = sorted(classes_of(r))
|
||||||
|
if cls:
|
||||||
|
lines.append(f"{r['file']}\t{','.join(cls)}")
|
||||||
|
return head + "\n".join(lines) + ("\n" if lines else "")
|
||||||
|
|
||||||
|
|
||||||
|
def load_baseline(path):
|
||||||
|
"""Read the ledger -> {filename: set(classes)}. Anything unreadable is rc 2.
|
||||||
|
|
||||||
|
A ledger is what turns "35 historical faults" from a reason not to grade
|
||||||
|
into a stated tolerance. If it cannot be read, the tool has no idea what was
|
||||||
|
tolerated, so it refuses rather than grading against an empty set (which
|
||||||
|
would read the whole archive as new) or skipping the check (which would read
|
||||||
|
nothing as new). Both of those are the silent-pass class this repo grades.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with open(path, "r", encoding="utf-8") as fh:
|
||||||
|
text = fh.read()
|
||||||
|
except OSError as exc:
|
||||||
|
raise CannotGrade(f"cannot grade: baseline {path}: {exc}")
|
||||||
|
except UnicodeDecodeError as exc:
|
||||||
|
raise CannotGrade(f"cannot grade: baseline {path} is not UTF-8: {exc}")
|
||||||
|
out = {}
|
||||||
|
for n, line in enumerate(text.splitlines(), 1):
|
||||||
|
s = line.rstrip("\r")
|
||||||
|
if not s.strip() or s.lstrip().startswith("#"):
|
||||||
|
continue
|
||||||
|
parts = s.split("\t")
|
||||||
|
if len(parts) != 2:
|
||||||
|
raise CannotGrade(f"cannot grade: baseline {path}:{n} is not "
|
||||||
|
f"<filename>\\t<CLASS[,CLASS]>: {s!r}")
|
||||||
|
name = parts[0].strip()
|
||||||
|
cls = [c.strip() for c in parts[1].split(",") if c.strip()]
|
||||||
|
bad = [c for c in cls if c not in BASELINE_CLASSES]
|
||||||
|
if not name or not cls or bad:
|
||||||
|
raise CannotGrade(f"cannot grade: baseline {path}:{n} names an "
|
||||||
|
f"empty filename, an empty class list, or an "
|
||||||
|
f"unknown class {bad}: {s!r}")
|
||||||
|
out[name] = set(cls)
|
||||||
|
return out
|
||||||
|
|
||||||
|
|
||||||
|
def apply_baseline(rep, ledger, path=""):
|
||||||
|
"""Split every row's classes into known and new, and count what is stale."""
|
||||||
|
current = {r["file"]: classes_of(r) for r in rep["rows"]}
|
||||||
|
known_files = 0
|
||||||
|
for r in rep["rows"]:
|
||||||
|
cls = current[r["file"]]
|
||||||
|
known = ledger.get(r["file"], set())
|
||||||
|
new = cls - known
|
||||||
|
r["baseline"] = {"known": sorted(cls & known), "new": sorted(new)}
|
||||||
|
if cls and not new:
|
||||||
|
known_files += 1
|
||||||
|
stale = sorted(f for f in ledger if f not in current)
|
||||||
|
cleared = sorted(f for f in ledger
|
||||||
|
if f in current and (ledger[f] - current[f]))
|
||||||
|
rep["baseline"] = {"path": path, "entries": len(ledger),
|
||||||
|
"known_files": known_files,
|
||||||
|
"new_faults": sum(1 for r in rep["rows"]
|
||||||
|
if r["baseline"]["new"]),
|
||||||
|
"stale": stale, "cleared": cleared}
|
||||||
|
return rep
|
||||||
|
|
||||||
|
|
||||||
|
def graded_faults(rep):
|
||||||
|
"""The rows that make rc 1.
|
||||||
|
|
||||||
|
Without a ledger: every faulting row, exactly as before. With one: only the
|
||||||
|
rows carrying a class the ledger does not name -- the ledger is a stated
|
||||||
|
tolerance, not an exemption, so "known" never silently becomes "fine".
|
||||||
|
"""
|
||||||
|
if rep.get("baseline"):
|
||||||
|
return [r for r in rep["rows"] if r.get("baseline", {}).get("new")]
|
||||||
|
return [r for r in rep["rows"] if r["class"] or r["duplicate"]]
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------- reporting
|
# --------------------------------------------------------------------- reporting
|
||||||
|
|
||||||
def banner(text: str) -> str:
|
def banner(text: str) -> str:
|
||||||
@@ -389,16 +523,27 @@ def print_report(rep, show_gaps: bool, fail: bool):
|
|||||||
f"{rep['excluded_since']} older file(s) not graded")
|
f"{rep['excluded_since']} older file(s) not graded")
|
||||||
for n in rep.get("skipped") or []:
|
for n in rep.get("skipped") or []:
|
||||||
print(f" SKIP {n} (the directory's own README: not a check-in)")
|
print(f" SKIP {n} (the directory's own README: not a check-in)")
|
||||||
fault_rows = [r for r in rep["rows"] if r["class"] or r["duplicate"]]
|
led = rep.get("baseline")
|
||||||
|
fault_rows = graded_faults(rep)
|
||||||
|
if led:
|
||||||
|
print(f"\nbaseline {led['path']}: {led['entries']} entries, "
|
||||||
|
f"{led['known_files']} file(s) known-faulting, "
|
||||||
|
f"{led['new_faults']} new fault(s), {len(led['stale'])} stale, "
|
||||||
|
f"{len(led['cleared'])} cleared")
|
||||||
|
for f in led["stale"]:
|
||||||
|
print(f" STALE BASELINE {f} (no longer in the directory: the record was repaired)")
|
||||||
|
for f in led["cleared"]:
|
||||||
|
print(f" STALE BASELINE {f} (no longer carries every class the ledger names)")
|
||||||
if not fault_rows:
|
if not fault_rows:
|
||||||
print("\nno file violates the filename, date, emptiness or duplicate rule")
|
print("\nno file violates the filename, date, emptiness or duplicate rule"
|
||||||
|
if not led else
|
||||||
|
"\nno file carries a class the baseline does not tolerate")
|
||||||
else:
|
else:
|
||||||
width = max(len(r["file"]) for r in fault_rows)
|
width = max(len(r["file"]) for r in fault_rows)
|
||||||
print(f"\n{'file':<{width}} {'verdict':<22} detail")
|
print(f"\n{'file':<{width}} {'verdict':<22} detail")
|
||||||
for r in fault_rows:
|
for r in fault_rows:
|
||||||
cls = list(r["class"])
|
cls = (r["baseline"]["new"] if led
|
||||||
if r["duplicate"]:
|
else sorted(set(r["class"]) | ({"DUPLICATE"} if r["duplicate"] else set())))
|
||||||
cls.append("DUPLICATE")
|
|
||||||
detail = []
|
detail = []
|
||||||
if "BAD_NAME" in cls:
|
if "BAD_NAME" in cls:
|
||||||
detail.append("name is not <date>-<handle>.md")
|
detail.append("name is not <date>-<handle>.md")
|
||||||
@@ -410,7 +555,7 @@ def print_report(rep, show_gaps: bool, fail: bool):
|
|||||||
detail.append(f"after today {rep['today']}")
|
detail.append(f"after today {rep['today']}")
|
||||||
if "EMPTY" in cls:
|
if "EMPTY" in cls:
|
||||||
detail.append("no non-blank line")
|
detail.append("no non-blank line")
|
||||||
if r["duplicate"]:
|
if "DUPLICATE" in cls and r["duplicate"]:
|
||||||
d = r["duplicate"]
|
d = r["duplicate"]
|
||||||
detail.append(f"{d['group_size']} files for {d['handle']} on {d['date']}")
|
detail.append(f"{d['group_size']} files for {d['handle']} on {d['date']}")
|
||||||
print(f"{r['file']:<{width}} {','.join(cls):<22} {'; '.join(detail)}")
|
print(f"{r['file']:<{width}} {','.join(cls):<22} {'; '.join(detail)}")
|
||||||
@@ -429,6 +574,15 @@ def print_report(rep, show_gaps: bool, fail: bool):
|
|||||||
print(" -- A2: a missing day is not a repo fault; it is a decision an operator owes")
|
print(" -- A2: a missing day is not a repo fault; it is a decision an operator owes")
|
||||||
|
|
||||||
counts = ", ".join(f"{k} {v}" for k, v in rep["faults"].items() if v)
|
counts = ", ".join(f"{k} {v}" for k, v in rep["faults"].items() if v)
|
||||||
|
if led:
|
||||||
|
print(f"\ncheckin_check: {rep['checked']} files, {led['new_faults']} new fault(s), "
|
||||||
|
f"{led['known_files']} known-faulting in the baseline"
|
||||||
|
+ (f" ({counts}; DUPLICATE groups {len(rep['duplicate_groups'])})" if counts else "")
|
||||||
|
+ f", {adv['missing_headings']} advisory")
|
||||||
|
if fail and led["new_faults"]:
|
||||||
|
print(f"FAIL: {led['new_faults']} file(s) carry a class the baseline does not tolerate")
|
||||||
|
return 1
|
||||||
|
return 0
|
||||||
print(f"\ncheckin_check: {rep['checked']} files, {rep['fault_rows']} with a fault"
|
print(f"\ncheckin_check: {rep['checked']} files, {rep['fault_rows']} with a fault"
|
||||||
+ (f" ({counts}; DUPLICATE groups {len(rep['duplicate_groups'])})" if counts else "")
|
+ (f" ({counts}; DUPLICATE groups {len(rep['duplicate_groups'])})" if counts else "")
|
||||||
+ f", {adv['missing_headings']} advisory")
|
+ f", {adv['missing_headings']} advisory")
|
||||||
@@ -586,7 +740,88 @@ def selftest():
|
|||||||
headings_missing("## Did\nx\n```\n## Blocked\n## Next\n## Ask\n")
|
headings_missing("## Did\nx\n```\n## Blocked\n## Next\n## Ask\n")
|
||||||
== ["blocked", "next", "ask"])
|
== ["blocked", "next", "ask"])
|
||||||
|
|
||||||
print(f"selftest: 29 cells, {29 - len(bad)} passed, {len(bad)} failed")
|
# c30-c35: the baseline ledger. The floor this tool was run under graded one
|
||||||
|
# side of the archive; these cells pin that a fault landing on ANY date is
|
||||||
|
# still graded, that the tolerance is per class rather than per path, and
|
||||||
|
# that an unreadable ledger refuses instead of certifying.
|
||||||
|
with __import__("tempfile").TemporaryDirectory() as td:
|
||||||
|
import contextlib
|
||||||
|
import io as _io
|
||||||
|
ck = os.path.join(td, "checkins")
|
||||||
|
os.makedirs(ck)
|
||||||
|
_mk(ck, "2026-10-17-volt.md", GOOD) # conforming
|
||||||
|
_mk(ck, "2026-10-14-volt-02.md", GOOD) # BAD_NAME
|
||||||
|
_mk(ck, "2026-10-13-volt-03.md", "") # BAD_NAME, EMPTY
|
||||||
|
_mk(ck, "2026-10-12-volt-04.md", "") # BAD_NAME, EMPTY -- "backdated"
|
||||||
|
btoday = date(2026, 10, 18)
|
||||||
|
broster = {"volt"}
|
||||||
|
|
||||||
|
def with_ledger(ledger_):
|
||||||
|
r_ = run(ck, broster, btoday, False)
|
||||||
|
apply_baseline(r_, ledger_, "fixture.tsv")
|
||||||
|
with contextlib.redirect_stdout(_io.StringIO()):
|
||||||
|
rc_ = print_report(r_, False, True)
|
||||||
|
return r_, sorted(r["file"] for r in graded_faults(r_)), rc_
|
||||||
|
|
||||||
|
everything = {"2026-10-14-volt-02.md": {"BAD_NAME"},
|
||||||
|
"2026-10-13-volt-03.md": {"BAD_NAME", "EMPTY"},
|
||||||
|
"2026-10-12-volt-04.md": {"BAD_NAME", "EMPTY"}}
|
||||||
|
_, g_none, rc_none = with_ledger(everything)
|
||||||
|
cell("c30 a ledger naming every class grades the whole archive rc 0",
|
||||||
|
g_none == [] and rc_none == 0, f"graded={g_none} rc={rc_none}")
|
||||||
|
|
||||||
|
partial = dict(everything)
|
||||||
|
del partial["2026-10-12-volt-04.md"]
|
||||||
|
_, g_new, rc_new = with_ledger(partial)
|
||||||
|
cell("c31 a fault on an older date that the ledger does not name is rc 1",
|
||||||
|
g_new == ["2026-10-12-volt-04.md"] and rc_new == 1,
|
||||||
|
f"graded={g_new} rc={rc_new}")
|
||||||
|
|
||||||
|
per_class = dict(everything, **{"2026-10-12-volt-04.md": {"BAD_NAME"}})
|
||||||
|
r_class, g_class, rc_class = with_ledger(per_class)
|
||||||
|
new_here = [r["baseline"]["new"] for r in r_class["rows"]
|
||||||
|
if r["file"] == "2026-10-12-volt-04.md"]
|
||||||
|
cell("c32 the ledger is per class, not per path: a gained class is new",
|
||||||
|
new_here == [["EMPTY"]] and rc_class == 1, f"{new_here} rc={rc_class}")
|
||||||
|
|
||||||
|
rep_emit = run(ck, broster, btoday, False)
|
||||||
|
bl_path = os.path.join(td, "ledger.tsv")
|
||||||
|
with open(bl_path, "w", encoding="utf-8") as fh:
|
||||||
|
fh.write(emit_baseline(rep_emit))
|
||||||
|
round_trip = load_baseline(bl_path)
|
||||||
|
_, g_rt, rc_rt = with_ledger(round_trip)
|
||||||
|
cell("c33 emit -> load round-trips, and silences exactly the current archive",
|
||||||
|
g_rt == [] and rc_rt == 0
|
||||||
|
and round_trip == {r["file"]: classes_of(r) for r in rep_emit["rows"]
|
||||||
|
if classes_of(r)},
|
||||||
|
f"{round_trip} graded={g_rt}")
|
||||||
|
|
||||||
|
gone = dict(everything, **{"2026-10-01-volt.md": {"BAD_NAME"}})
|
||||||
|
r_stale, g_stale, rc_stale = with_ledger(gone)
|
||||||
|
cell("c34 an entry for a file that is gone is stale, never a fault",
|
||||||
|
r_stale["baseline"]["stale"] == ["2026-10-01-volt.md"]
|
||||||
|
and g_stale == [] and rc_stale == 0, str(r_stale["baseline"]))
|
||||||
|
|
||||||
|
bad_ledgers, why = [], []
|
||||||
|
for label, body in (("no tab", "2026-10-13-volt-03.md\n"),
|
||||||
|
("unknown class", "2026-10-13-volt-03.md\tNOT_A_CLASS\n"),
|
||||||
|
("empty classes", "2026-10-13-volt-03.md\t\n")):
|
||||||
|
p = os.path.join(td, "bad-%s.tsv" % label.replace(" ", "-"))
|
||||||
|
_mk(td, os.path.basename(p), body)
|
||||||
|
try:
|
||||||
|
load_baseline(p)
|
||||||
|
bad_ledgers.append(label)
|
||||||
|
except CannotGrade:
|
||||||
|
why.append(label)
|
||||||
|
try:
|
||||||
|
load_baseline(os.path.join(td, "absent.tsv"))
|
||||||
|
bad_ledgers.append("missing file")
|
||||||
|
except CannotGrade:
|
||||||
|
why.append("missing file")
|
||||||
|
cell("c35 an unreadable, malformed or absent ledger refuses (rc-2 class)",
|
||||||
|
bad_ledgers == [], f"accepted: {bad_ledgers}")
|
||||||
|
|
||||||
|
print(f"selftest: 35 cells, {35 - len(bad)} passed, {len(bad)} failed")
|
||||||
return 1 if bad else 0
|
return 1 if bad else 0
|
||||||
|
|
||||||
|
|
||||||
@@ -604,12 +839,21 @@ def main(argv=None):
|
|||||||
ap.add_argument("--since", help="grade only check-ins dated on/after this YYYY-MM-DD "
|
ap.add_argument("--since", help="grade only check-ins dated on/after this YYYY-MM-DD "
|
||||||
"(the archive's older names are its record)")
|
"(the archive's older names are its record)")
|
||||||
ap.add_argument("--json", help="write the full report here")
|
ap.add_argument("--json", help="write the full report here")
|
||||||
|
ap.add_argument("--baseline", help="known-fault ledger (<filename>\\t<CLASS[,CLASS]>): "
|
||||||
|
"only classes it does not name make rc 1, so the "
|
||||||
|
"whole archive can be graded every run")
|
||||||
|
ap.add_argument("--emit-baseline", action="store_true",
|
||||||
|
help="print the ledger for the current directory to stdout and exit 0")
|
||||||
ap.add_argument("--fail", action="store_true", help="exit 1 when any file has a fault")
|
ap.add_argument("--fail", action="store_true", help="exit 1 when any file has a fault")
|
||||||
ap.add_argument("--selftest", action="store_true", help="fixture corpus")
|
ap.add_argument("--selftest", action="store_true", help="fixture corpus")
|
||||||
a = ap.parse_args(argv)
|
a = ap.parse_args(argv)
|
||||||
|
|
||||||
if a.selftest:
|
if a.selftest:
|
||||||
return selftest()
|
return selftest()
|
||||||
|
if a.emit_baseline and a.baseline:
|
||||||
|
print("cannot grade: --emit-baseline and --baseline are different jobs",
|
||||||
|
file=sys.stderr)
|
||||||
|
return 2
|
||||||
|
|
||||||
if a.today:
|
if a.today:
|
||||||
try:
|
try:
|
||||||
@@ -632,11 +876,19 @@ def main(argv=None):
|
|||||||
try:
|
try:
|
||||||
roster, src = load_roster(repo, a.roster)
|
roster, src = load_roster(repo, a.roster)
|
||||||
rep = run(path, roster, today, a.gaps, since=since)
|
rep = run(path, roster, today, a.gaps, since=since)
|
||||||
|
if a.baseline:
|
||||||
|
rep = apply_baseline(rep, load_baseline(a.baseline), a.baseline)
|
||||||
except CannotGrade as exc:
|
except CannotGrade as exc:
|
||||||
print(str(exc), file=sys.stderr)
|
print(str(exc), file=sys.stderr)
|
||||||
return 2
|
return 2
|
||||||
|
|
||||||
rep["roster_source"] = src
|
rep["roster_source"] = src
|
||||||
|
if a.emit_baseline:
|
||||||
|
sys.stdout.write(emit_baseline(rep))
|
||||||
|
if a.json:
|
||||||
|
with open(a.json, "w", encoding="utf-8") as fh:
|
||||||
|
json.dump(rep, fh, indent=1, sort_keys=False)
|
||||||
|
return 0
|
||||||
rc = print_report(rep, a.gaps, a.fail)
|
rc = print_report(rep, a.gaps, a.fail)
|
||||||
if a.json:
|
if a.json:
|
||||||
with open(a.json, "w", encoding="utf-8") as fh:
|
with open(a.json, "w", encoding="utf-8") as fh:
|
||||||
|
|||||||
+75
-36
@@ -26,10 +26,11 @@ WHY THIS EXISTS
|
|||||||
every lane writes every tick. A fourth leg closes that hole.
|
every lane writes every tick. A fourth leg closes that hole.
|
||||||
|
|
||||||
WHAT IT RUNS -- one row per leg, cheapest first, never in parallel
|
WHAT IT RUNS -- one row per leg, cheapest first, never in parallel
|
||||||
checkin tools/checkin_check.py --since <newest day> --fail
|
checkin tools/checkin_check.py --fail --baseline tools/checkin_baseline.tsv
|
||||||
the newest day's check-ins against F1-F5 (names, duplicates,
|
the WHOLE check-in archive against F1-F5 (names, duplicates,
|
||||||
unknown handles, dates, emptiness); `--checkin-day DATE` sets
|
unknown handles, dates, emptiness), where the ledger states the
|
||||||
the floor explicitly, from DATE forward
|
faults the archive already carried; only a class it does not
|
||||||
|
name is a fault. `--checkin-day DATE` adds a `--since` floor
|
||||||
index-current tools/queue_index.py --check docs/queue-index.md
|
index-current tools/queue_index.py --check docs/queue-index.md
|
||||||
the derived document, byte-compared against a fresh render
|
the derived document, byte-compared against a fresh render
|
||||||
append-audit tools/queue_append_audit.py --guard --to HEAD
|
append-audit tools/queue_append_audit.py --guard --to HEAD
|
||||||
@@ -42,19 +43,31 @@ WHAT IT RUNS -- one row per leg, cheapest first, never in parallel
|
|||||||
one failed: a stale index and a broken selftest are different defects and the
|
one failed: a stale index and a broken selftest are different defects and the
|
||||||
run should name both in one pass.
|
run should name both in one pass.
|
||||||
|
|
||||||
WHY THE CHECK-IN LEG GRADES ONE DAY, NOT THE ARCHIVE
|
WHY THE CHECK-IN LEG NOW GRADES THE ARCHIVE, NOT ONE DAY
|
||||||
Grading the whole archive is not a stricter gate, it is a permanently red one:
|
Grading the whole archive was, until 2026-10-19, a permanently red gate: at
|
||||||
`checkin_check.py --repo . --fail` at HEAD returns **rc 1 with 35 faults** (25
|
`e98a34b` the unbaselined run exits 1 on every one of the archive's pre-rule
|
||||||
BAD_NAME, 10 DUPLICATE groups), every one of them dated 2026-09-24 through
|
rows (25 BAD_NAME, 10 DUPLICATE groups, all dated 2026-09-24 through
|
||||||
2026-10-07, before the rule it breaks was written. A gate wired to that number
|
2026-10-07) -- that list is now a measured artifact, `tools/checkin_baseline.tsv`,
|
||||||
can never go green, so it stops being read -- which is the same failure as a
|
one row per file rather than a number in this docstring. The first version of this
|
||||||
gate that is never run. The newest day in `checkins/` is the only day on which a NEW fault
|
leg therefore graded the archive's **newest day** only, on the reasoning that
|
||||||
can appear, so it is the day this leg grades: history is left as the record it
|
it is the only day on which a new fault can appear. That reasoning is false,
|
||||||
is (the tool renames nothing, by design), and a suffix, duplicate, bad date or
|
and it was measured false rather than argued: a check-in added today under a
|
||||||
empty file on the current day is rc 1 the moment it lands. `--checkin-day DATE`
|
**backdated** filename -- `checkins/2026-10-02-volt-b.md`, which is what a typo
|
||||||
lowers the floor explicitly, in the open, when an operator wants a wider window
|
in the date half produces -- is not on the newest day, so the floor excluded
|
||||||
(it is a `--since`, so it can only widen: an older DATE pulls more history in,
|
it. On a clone of `e98a34b` carrying that one file, `verify.py --fast` printed
|
||||||
it never narrows the window past a newer DATE).
|
`checkin_check: 1 files, 0 with a fault` and exited **0**, while the same
|
||||||
|
tree's whole-archive run exited **1** naming the file.
|
||||||
|
|
||||||
|
The fix keeps both properties -- history is not re-litigated, nothing is
|
||||||
|
invisible. `tools/checkin_baseline.tsv` lists the faults the archive already
|
||||||
|
carried, one `<filename>\t<CLASS[,CLASS]>` row each, and `--baseline` grades
|
||||||
|
every file against it: a listed class is KNOWN, anything else is a fault. A
|
||||||
|
file that is not in the ledger at all is a fault wherever its date falls, and a
|
||||||
|
file that gains a class it did not have is a fault too -- the ledger states what
|
||||||
|
was tolerated, not that a path is exempt. A missing or malformed ledger is
|
||||||
|
rc 2 (cannot grade), never a silent pass, and stale entries (file gone, class
|
||||||
|
cleared) are reported and never make rc 1. `--checkin-day DATE` survives as an
|
||||||
|
explicit narrowing floor for an operator who wants one.
|
||||||
|
|
||||||
THE EXIT CONTRACT -- the house three codes
|
THE EXIT CONTRACT -- the house three codes
|
||||||
0 every graded leg passed
|
0 every graded leg passed
|
||||||
@@ -84,7 +97,7 @@ TOOLS ARE RESOLVED FROM --repo, NOT FROM THIS FILE'S DIRECTORY
|
|||||||
docs/queue-index.md) because `queue_append_audit` reads them with
|
docs/queue-index.md) because `queue_append_audit` reads them with
|
||||||
`git show <rev>:<path>`, which has no absolute-path form.
|
`git show <rev>:<path>`, which has no absolute-path form.
|
||||||
|
|
||||||
`--selftest` fixture corpus, 35 cells
|
`--selftest` fixture corpus, 37 cells
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -158,10 +171,11 @@ def pick_detail(text, limit=150):
|
|||||||
def newest_checkin_day(repo):
|
def newest_checkin_day(repo):
|
||||||
"""The newest `<YYYY-MM-DD>` prefix in checkins/ -> (day, why-not).
|
"""The newest `<YYYY-MM-DD>` prefix in checkins/ -> (day, why-not).
|
||||||
|
|
||||||
The leg grades one day and this is how that day is chosen: the archive's own
|
Not the graded window any more -- the leg grades the whole archive against
|
||||||
latest date, so nothing has to be configured and no clock is consulted. A
|
its own ledger -- but still the cheapest way to answer "is there anything
|
||||||
repo with no `checkins/` directory or no dated file in it cannot be graded
|
here to grade at all", which is what makes a repo with no `checkins/`
|
||||||
and says which of the two it is.
|
directory (or no dated file in it) ungradable with a message naming which of
|
||||||
|
the two it is, rather than a spawn and an rc 2 from further down.
|
||||||
"""
|
"""
|
||||||
d = os.path.join(repo, CHECKINS_DEFAULT)
|
d = os.path.join(repo, CHECKINS_DEFAULT)
|
||||||
if not os.path.isdir(d):
|
if not os.path.isdir(d):
|
||||||
@@ -176,8 +190,14 @@ def newest_checkin_day(repo):
|
|||||||
|
|
||||||
def leg_argv(key, repo, queue, index, checkin_day=None):
|
def leg_argv(key, repo, queue, index, checkin_day=None):
|
||||||
if key == "checkin":
|
if key == "checkin":
|
||||||
return [sys.executable, os.path.join(repo, "tools", "checkin_check.py"),
|
# The ledger is a file in the tree being graded, so a revision is graded
|
||||||
"--repo", repo, "--fail", "--since", checkin_day]
|
# with the tolerance it committed -- not with this host's idea of one.
|
||||||
|
argv = [sys.executable, os.path.join(repo, "tools", "checkin_check.py"),
|
||||||
|
"--repo", repo, "--fail",
|
||||||
|
"--baseline", os.path.join(repo, "tools", "checkin_baseline.tsv")]
|
||||||
|
if checkin_day:
|
||||||
|
argv += ["--since", checkin_day]
|
||||||
|
return argv
|
||||||
if key == "index-current":
|
if key == "index-current":
|
||||||
return [sys.executable, os.path.join(repo, "tools", "queue_index.py"),
|
return [sys.executable, os.path.join(repo, "tools", "queue_index.py"),
|
||||||
"--repo", repo, "--queue", queue, "--check", index]
|
"--repo", repo, "--queue", queue, "--check", index]
|
||||||
@@ -202,9 +222,11 @@ def run_leg(leg, repo, queue, index, timeout, checkin_day=None):
|
|||||||
row["ms"] = 0
|
row["ms"] = 0
|
||||||
return row
|
return row
|
||||||
day = checkin_day
|
day = checkin_day
|
||||||
if leg["key"] == "checkin" and not day:
|
if leg["key"] == "checkin":
|
||||||
day, why = newest_checkin_day(repo)
|
# Presence is still pre-checked here: the tool's own rc-2 message names
|
||||||
if day is None:
|
# the directory, but this row can say so without spawning a process.
|
||||||
|
newest, why = newest_checkin_day(repo)
|
||||||
|
if newest is None:
|
||||||
row.update(verdict=UNGRADABLE, detail=why)
|
row.update(verdict=UNGRADABLE, detail=why)
|
||||||
row["ms"] = 0
|
row["ms"] = 0
|
||||||
return row
|
return row
|
||||||
@@ -299,16 +321,16 @@ def main(argv=None):
|
|||||||
ap.add_argument("--index", default=INDEX_DEFAULT,
|
ap.add_argument("--index", default=INDEX_DEFAULT,
|
||||||
help="repo-relative index document (default %s)" % INDEX_DEFAULT)
|
help="repo-relative index document (default %s)" % INDEX_DEFAULT)
|
||||||
ap.add_argument("--checkin-day", default=None, metavar="DATE",
|
ap.add_argument("--checkin-day", default=None, metavar="DATE",
|
||||||
help="grade check-ins from DATE forward instead of from the "
|
help="add a --since DATE floor to the check-in leg, so only "
|
||||||
"newest day present in checkins/ (a --since floor: it "
|
"check-ins dated DATE or later are graded (default: the "
|
||||||
"can widen the window, never narrow it)")
|
"whole archive, against tools/checkin_baseline.tsv)")
|
||||||
ap.add_argument("--fast", action="store_true",
|
ap.add_argument("--fast", action="store_true",
|
||||||
help="skip the selftest-sweep leg; the row is printed as SKIP")
|
help="skip the selftest-sweep leg; the row is printed as SKIP")
|
||||||
ap.add_argument("--timeout", type=int, default=300, metavar="S",
|
ap.add_argument("--timeout", type=int, default=300, metavar="S",
|
||||||
help="per-leg timeout in seconds (default 300)")
|
help="per-leg timeout in seconds (default 300)")
|
||||||
ap.add_argument("--json", metavar="PATH", help="write the rows here")
|
ap.add_argument("--json", metavar="PATH", help="write the rows here")
|
||||||
ap.add_argument("--quiet", action="store_true")
|
ap.add_argument("--quiet", action="store_true")
|
||||||
ap.add_argument("--selftest", action="store_true", help="fixture corpus, 35 cells")
|
ap.add_argument("--selftest", action="store_true", help="fixture corpus, 37 cells")
|
||||||
a = ap.parse_args(argv)
|
a = ap.parse_args(argv)
|
||||||
|
|
||||||
if a.selftest:
|
if a.selftest:
|
||||||
@@ -552,7 +574,8 @@ def selftest():
|
|||||||
check("15b the ungradable row says which directory is missing",
|
check("15b the ungradable row says which directory is missing",
|
||||||
"ungradable: checkin" in out and "no check-in directory:" in out, True)
|
"ungradable: checkin" in out and "no check-in directory:" in out, True)
|
||||||
|
|
||||||
# 16. the leg grades the NEWEST day in the archive, not the whole archive
|
# 16. the leg grades the WHOLE archive against its own ledger -- the
|
||||||
|
# fix for a newest-day floor that made a backdated fault invisible.
|
||||||
av = os.path.join(td, "c16-argv")
|
av = os.path.join(td, "c16-argv")
|
||||||
repo = _fixture(os.path.join(td, "c16"), GREEN4,
|
repo = _fixture(os.path.join(td, "c16"), GREEN4,
|
||||||
argv_markers={"checkin_check.py": av},
|
argv_markers={"checkin_check.py": av},
|
||||||
@@ -560,19 +583,35 @@ def selftest():
|
|||||||
rc, out = _verify("--repo", repo, "--fast")
|
rc, out = _verify("--repo", repo, "--fast")
|
||||||
seen = open(av).read() if os.path.exists(av) else ""
|
seen = open(av).read() if os.path.exists(av) else ""
|
||||||
check("16 the check-in leg is run with --fail", "--fail" in seen, True)
|
check("16 the check-in leg is run with --fail", "--fail" in seen, True)
|
||||||
check("16b and --since is the newest day present, not a clock or a constant",
|
check("16b and with the ledger resolved from --repo, so no day is excluded",
|
||||||
"--since 2026-03-04" in seen, True)
|
"--baseline" in seen
|
||||||
|
and os.path.join(repo, "tools", "checkin_baseline.tsv") in seen
|
||||||
|
and "--since" not in seen, True)
|
||||||
|
|
||||||
# 17. --checkin-day retargets the window in the open
|
# 17. --checkin-day still narrows the window explicitly, in the open
|
||||||
av = os.path.join(td, "c17-argv")
|
av = os.path.join(td, "c17-argv")
|
||||||
repo = _fixture(os.path.join(td, "c17"), GREEN4,
|
repo = _fixture(os.path.join(td, "c17"), GREEN4,
|
||||||
argv_markers={"checkin_check.py": av},
|
argv_markers={"checkin_check.py": av},
|
||||||
checkins=("2026-01-01-fixture.md", "2026-03-04-fixture.md"))
|
checkins=("2026-01-01-fixture.md", "2026-03-04-fixture.md"))
|
||||||
rc, out = _verify("--repo", repo, "--fast", "--checkin-day", "2026-01-01")
|
rc, out = _verify("--repo", repo, "--fast", "--checkin-day", "2026-01-01")
|
||||||
seen = open(av).read() if os.path.exists(av) else ""
|
seen = open(av).read() if os.path.exists(av) else ""
|
||||||
check("17 --checkin-day overrides the default day",
|
check("17 --checkin-day adds a --since floor",
|
||||||
"--since 2026-01-01" in seen, True)
|
"--since 2026-01-01" in seen, True)
|
||||||
|
|
||||||
|
# 19. a ledger that cannot be read is UNGRADABLE (rc 2), never a pass:
|
||||||
|
# the tool's own reason has to reach the row, because "no ledger" and
|
||||||
|
# "clean archive" print nothing alike and must not grade alike.
|
||||||
|
repo = _fixture(os.path.join(td, "c19"),
|
||||||
|
dict(GREEN4, **{"checkin_check.py": 2}),
|
||||||
|
outs={"checkin_check.py":
|
||||||
|
"cannot grade: baseline %s/tools/checkin_baseline.tsv: "
|
||||||
|
"[Errno 2] No such file or directory\n"
|
||||||
|
% os.path.join(td, "c19", "repo")})
|
||||||
|
rc, out = _verify("--repo", repo, "--fast")
|
||||||
|
check("19 a missing ledger is rc 2 and names the ledger", rc, 2)
|
||||||
|
check("19b the ungradable row carries the tool's own reason",
|
||||||
|
"ungradable: checkin" in out and "checkin_baseline.tsv" in out, True)
|
||||||
|
|
||||||
# 18. the detail line must survive a real tool's noise: `checkin_check`
|
# 18. the detail line must survive a real tool's noise: `checkin_check`
|
||||||
# prints a README SKIP note carrying "not a " on every run, and a WEAK
|
# prints a README SKIP note carrying "not a " on every run, and a WEAK
|
||||||
# hint must not beat the FAIL line that actually names the verdict.
|
# hint must not beat the FAIL line that actually names the verdict.
|
||||||
|
|||||||
Reference in new issue
Block a user