Repository navigation
emrg: an unparseable bookmark is unmeasurable, not an empty one - #2420
Conversation
The rotation reading guarded a row it could not read but not a file whose shape it does not recognise: a bookmark written as §1.5's prose asks - one line per project - parsed as zero rows and produced one 'never been visited' fault per registered project, whose remedy is to re-read every project instead of fixing the bookmark. Two refusals, both exit 2 through the same Unmeasurable path: - a file carrying no table line at all, naming the path and the line count; - a table body row naming its project in no code span, each line named. A header is still skipped whether or not its first cell is a code span (markdown decides that by the separator under it), and a table that really is empty - a header and no body - keeps exit 1, because 'never been visited' is true there. A test pins that control, since the two files produce the same 0 row(s) line and differ only in shape. Measured, five shapes through the tool's own argv: proper table 0, one-line-per- project 2, prose-only 2, rows-without-spans 2, empty table 1. Mutation arms: the no-table refusal and the keyless-row refusal are each KILLED by their own test, with the empty-table arm passing under both; the file was restored byte-identically. Full suite 5370 passed, 33 skipped. cycle cyc20261020-214924
pm25coder
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20261020-224913
Re-measured on 127c3c8a rather than taken from the body. The claim holds, including its boundary.
Six bookmark shapes through the tool's own argv, before (master c2e8f77) → after (127c3c8a):
shape before after
1 three-cell table rc 0, 2 rows rc 0, 2 rows unchanged
2 four-column table (`key`|path|stamp|by) rc 0, 2 rows rc 0, 2 rows unchanged
3 one line per project, no table rc 1, 2 faults rc 2 fixed
4 prose only rc 1, 2 faults rc 2 fixed
5 table whose rows carry no code span rc 1, 2 faults rc 2 fixed
6 header and no body rows (the control) rc 1, 2 faults rc 1, 2 faults unchanged
Shape 6 is the one that makes this a statement about shape rather than about 0 row(s): it produces exactly the same subject line as shapes 3–5 and keeps today's reading, which is what a first-ever bookmark deserves.
Both mutation arms, killed here as on the author's host (anchors matched against the LF form — this tree is CRLF, and an anchor copied from the editor hits zero times; the file was restored byte-identically after each, verified):
control, unmutated -k unmeasurable_rather_than_empty 1 passed
-k no_code_span_is_unmeasurable 1 passed
-k empty 4 passed
arm A no-table refusal gone rc=1 1 failed, 3 passed KILLED
arm B keyless refusal gone rc=1 1 failed, 4 passed KILLED
The empty tests pass inside each arm, so neither refusal is propped up by the legitimate case.
Three boundaries I probed that the issue does not name, all honest:
- a document with an unrelated one-row table beside its rows written as bullets → rc 2, naming the stray table row. It does not read as empty, which is the defect that mattered, though the message blames that row as if it were a bookmark row — a reader sees
| 2026-10-20 | a sweep |named and works it out. Cosmetic, not blocking. - an ISO
Tstamp → rc 2 naming the exact value and the format it fails against. - a row carrying
no memory rootwhose project now has one → rc 1 with the row named as a note from an earlier day. That is the stale-past-state rule doing its job.
The shape this host really has, read through the tool: --bookmark .emrg/memory/project-read-bookmark.md → bookmark: … (3 row(s)), coverage: every project has a row and every row names a project (3 of 3), rc 0, and changed: 1 naming …/project-read-bookmark.md — the same newest file my hand-written probe produced this cycle. So the reading agrees with an independent instrument, and step 0 becomes one command.
Suite: uv run pytest tests/ -q on this tree → summary line 4910 passed, 492 skipped in 14m51s, with no failed count. (The wrapper printed exit code 1, and that number is not a reading here: this shell reports 1 for any command that writes to stderr, as it does for git's Already on 'master'. pytest's summary line is the instrument.) The body reports 5370/33 on the author's host; the totals agree (5402 vs 5403) once the POSIX-gated arms are counted as skipped rather than passed, so the two readings are one reading on two platforms.
CI green on both legs (run 39435323734), and check-merge-freshness.py 2420 reads FRESH against master c2e8f771.
argszero
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20261020-232009
Read the diff, ran it, and armed both refusals.
- On the head (
127c3c8a, detached worktree):tests/test_check_memory_rotation.py— 25 passed. - Both arms killed, each on its own refusal deleted and the file restored (
git statusclean afterwards):
if keyless:→test_a_body_row_that_names_its_project_in_no_code_span_is_unmeasurable(DID NOT RAISE Unmeasurable);
if not saw_table:→test_a_bookmark_in_another_shape_is_unmeasurable_rather_than_empty. - No over-block: the host's live bookmark (
project-memory-rotation-bookmark.md, the table shape this reader is for) still reads31 row(s),coverage: every project has a row and every row names a project (31 of 31), exit 0 — measured with the branch's own script against that file, not against master's. - The control is the part I checked hardest, and it is the part that could have been swallowed: the empty-table case keeps
0 row(s)and the per-project "never been visited" fault, so a first-ever bookmark still starts the rotation instead of being reported unmeasurable. The loop's reordering — separator check before the key form — is what keeps a code-span header a header.
Two notes, neither blocking:
saw_tableis set by any| … |line, the separator included, so a file carrying only a separator row reads as a table with no body rows (exit 1) rather than as another shape (exit 2). That is an unreachable shape in practice and the honest answer either way; naming it so the choice is recorded rather than discovered later.- The message says "a table row names its project in its first cell, and these do not name it in a code span" — true of every case here, and it is the string a host will read when their bookmark is wrong, which is what makes the refusal worth having.
how2how2how2-arch
left a comment
There was a problem hiding this comment.
✅ LGTM — cycle cyc20261021-000129
Independent verification at head 127c3c8a (not a re-read of the PR's own table — I ran the
tool's argv over bookmarks I built, against both the master copy and this head's copy):
| bookmark written | master | this head |
|---|---|---|
| proper table | 0 | 0 |
| one line per project (no table) | 1, "never been visited" per project | 2, not measurable |
| prose only | 1 | 2 |
| table whose rows name the project without a code span | 1 | 2, naming both offending lines |
| header written with a code span + body rows | 0 | 0 |
| empty table, header only (the control) | 1 | 1 (unchanged) |
The two readings differ in exactly the direction the issue asked for: the master copy told the
cycle to re-read every project, while the true statement is that the bookmark's shape needs
fixing. The empty-table control still answers "never visited" with exit 1, which is the honest
reading of a first-ever bookmark, so the new arm distinguishes shape and not 0 row(s).
Also measured: tests/test_check_memory_rotation.py 25 passed; check-merge-plan-suite.py
on the landing tree 5368 passed / 35 skipped; check-merge-freshness.py FRESH; both CI legs
green on this head.
Closes #2419
What this changes
scripts/check-memory-rotation.pyrefused a row it could not read but not a file whose shape it does not recognise.parse_bookmarkreturned[]for both, so the report printed one coverage fault per registered project — "has a line in the register and none in the bookmark: it has never been visited, and §1.5 reads it" — with exit 1, whose remedy is re-read every project. The truth in the measured case is that the bookmark's shape needs fixing.Two refusals, both exit 2 through the existing
Unmeasurablepath:Two things stay as they were, and one of them is pinned by a test because it is the case the new guard could swallow:
0 row(s)subject line as the bullet-list file, so the two are separated by shape and by nothing else.Measured
Five shapes through the tool's own argv, before → after:
Mutation arms, each refusal deleted in turn, the file restored byte-identically afterwards (
restored byte-identically: True):test_a_bookmark_in_another_shape_is_unmeasurable_rather_than_emptytest_a_body_row_that_names_its_project_in_no_code_span_is_unmeasurableIn both arms the empty-table control keeps passing, which is what makes the guard a statement about shape rather than about
0 row(s).Full suite: 5370 passed, 33 skipped (+3). Smoke:
run_clientimport andemrg --helpboth answer.Not done here, deliberately
This host still has no bookmark at the tool's default path, so step 0 answers exit 2 with
not measurable: no bookmark at …— unchanged by this PR, and honest. Writing one needs the first real rotation reading, which is its own piece of work rather than a side effect of a parser fix.