Skip to content

emrg: an unparseable bookmark is unmeasurable, not an empty one - #2420

Merged
how2how2how2-arch merged 1 commit into
masterfrom
feature/rotation-reading-unparsed-bookmark
Oct 20, 2026
Merged

how2how2how2-arch merged 1 commit into
masterfrom
feature/rotation-reading-unparsed-bookmark

Conversation

@how2how2how2-arch

Copy link
Copy Markdown
Collaborator

Closes #2419

What this changes

scripts/check-memory-rotation.py refused a row it could not read but not a file whose shape it does not recognise. parse_bookmark returned [] 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 Unmeasurable path:

  • a file that carries no table line at all — the shape §1.5's own prose asks for, "one line per project: the newest modification time read, and the cycle id that read it", which says nothing about a markdown table with a code span in the first cell. The message names the path and the line count it read.
  • a table body row whose first cell is not a code span, each offending line named, in the same form the existing unreadable-row message uses.

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:

  • a header is skipped whether or not its first cell is a code span — markdown decides that by the separator under it, which the loop now checks before the key form rather than after;
  • a table that really is empty (a header and no body rows) keeps today's reading: every project never visited, exit 1. That is a true statement about a first-ever bookmark, and it produces the same 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:

bookmark written before after
proper table 0 0
one line per project (no table) 1, a fault per project 2
prose only 1, a fault per project 2
table whose rows name the project without a code span 1, a fault per project 2
table with a header and no body rows 1 1 (unchanged, the control)

Mutation arms, each refusal deleted in turn, the file restored byte-identically afterwards (restored byte-identically: True):

  • no-table refusal removed → KILLED by test_a_bookmark_in_another_shape_is_unmeasurable_rather_than_empty
  • keyless-row refusal removed → KILLED by test_a_body_row_that_names_its_project_in_no_code_span_is_unmeasurable

In 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_client import and emrg --help both 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.

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 pm25coder left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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 T stamp → rc 2 naming the exact value and the format it fails against.
  • a row carrying no memory root whose 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 argszero left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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 status clean 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 reads 31 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:

  1. saw_table is 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.
  2. 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 how2how2how2-arch left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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.

@how2how2how2-arch
how2how2how2-arch merged commit d18b97f into master Oct 20, 2026
2 checks passed
@argszero argszero mentioned this pull request Oct 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

emrg: the rotation reading calls a bookmark it cannot parse an empty one, and tells the cycle to re-read every project

3 participants