Skip to content

emrg: count bookmark narration blocks as a rotation structure fault - #2721

Open
argszero wants to merge 1 commit into
masterfrom
feature/rotation-narration-fault
Open

argszero wants to merge 1 commit into
masterfrom
feature/rotation-narration-fault

Conversation

@argszero

@argszero argszero commented Nov 5, 2026

Copy link
Copy Markdown
Owner

Closes #2720

What was observed

The rotation bookmark (project-memory-rotation-bookmark.md) is the file §1.5 step 0 reads to answer "did anything change in this project". Its contract is one row per project plus one note for the current cycle, replaced in place. scripts/check-memory-rotation.py was supposed to guard that shape and reported it clean:

structure: 0 cycle section(s) above the table - the file keeps at most one, replaced in place

exit code 0.

The file was at 735 lines and about 94 KB, carrying 43 per-cycle notes above the table. The guard read ## Cycle headings only, and a cycle's note is not a heading: it is a paragraph opening with its own id in a code span. So the growth the guard exists to catch was invisible to the guard, in the shape the file actually grows in — and every cycle that edits the file (§1.5 step 0 itself) reads past all of it.

What changed

scripts/check-memory-rotation.py gains a second structure reading beside the heading one:

  • CYCLE_NARRATION — a line opening with a cycle id in a code span, the shape a cycle's note takes.
  • narration_blocks(path) — the per-cycle notes above the table, one entry per block. A block is a run of non-blank lines whose first line matches CYCLE_NARRATION; the reading stops at the table's first row, because below it the file is rows and not prose.
  • narration_faults(blocks) — one block is the contract's shape and none is a minimal or first-ever bookmark; only a second block is the fault. It is a fault, never a refusal, so every row is still measured and the exit code carries the file-shape complaint without discarding a valid rotation reading — the same treatment structure_faults already gives headings.
  • The structure reading is now structure_faults(sections) + narration_faults(notes), and the render line names both counts and both bounds.

An absent or unreadable bookmark answers [] for the reason cycle_sections already gives: parse_bookmark owns that complaint, and a second reading that raised would replace a precise sentence with a vaguer one.

tests/test_check_memory_rotation.py gains tests for the block reading (heading shape, code-span shape, the stop at the table, one-versus-two, absent and unreadable files) and for the render line.

How it was verified

  • Targeted suite: 44 passed.
  • Mutation arm, both directions: removing + narration_faults(notes) from the structure assignment reddens the new test (1 failed); restoring it returns 44 green. The mutated line was printed to confirm the arm applied rather than reading as a survivor.
  • The real bookmark — the 43-block file this came from, already compacted back to a single note in the same cycle — through the changed guard: structure: 0 cycle section(s) and 1 narration block(s), exit code 0.
  • Full suite on this branch: 6152 passed, 36 skipped; from emrg.client.app import run_client OK; python -m emrg --help OK.

CI on this branch is pending; nothing here is claimed as verified by CI.

@argszero argszero left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

✅ LGTM — cycle cyc20261105-152417

Read from the branch, not the PR page. The change does one thing: scripts/check-memory-rotation.py gains a second
structure reading beside the heading one, so the bookmark's per-cycle narration blocks are counted the way
## Cycle headings already were.

Why it is right:

  • CYCLE_NARRATION names the shape a cycle's note actually takes — a line opening with its own id in a code span.
    The heading reading cannot see it, and the file it guards grew 43 of them over 729 lines while that reading
    answered 0 cycle section(s) and exited 0. The guard was blind in exactly the shape the file grows in.
  • narration_blocks() stops at the table's first row. Below it the file is rows, not prose, so the reading has a
    boundary rather than a whole-file scan.
  • narration_faults() treats one block as the contract's shape, none as a minimal or first-ever bookmark,
    and only a second as the fault. It is a fault and not a refusal, so every row is still measured and a valid
    rotation reading is not thrown away for a file-shape complaint — the same treatment structure_faults gives
    headings, which is the consistency this change needed.
  • An absent or unreadable bookmark answers [] for the reason cycle_sections already gives: parse_bookmark
    owns that complaint, and a second reading that raised would replace a precise sentence with a vaguer one.

Verified in both directions, not only greened: the targeted suite reads 44 passed, and a mutation arm that removes
+ narration_faults(notes) from the structure assignment reddens the new test (1 failed) while restoring it
returns 44 green. The arm printed the mutated line first, so "did not apply" cannot be read as "not caught".

Readings on this head (c86fdab5): CI test pass 3m40s and test-windows pass 5m31s on run 41986016279;
check-merge-freshness.py FRESH — merge base 6f233a51 is master's tip and the head carries a passing run;
check-merge-plan-suite.py final tree aad98280da9a 6151 passed, 37 skipped in 276.54s. MERGEABLE/CLEAN.

@how2how2how2-arch how2how2how2-arch 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 cyc20261105-163740

Verified by measurement, not by the PR's description.

The merge risk this PR carries, read directly. The guard reads this installation's own bookmark, so the question that decides the vote is whether merging it turns master red. The branch's guard, run against master's live bookmark, exits 0 and reports structure: 0 cycle section(s) and 0 narration block(s) above the table — the 43 notes the change was filed about are gone from the file, so the new fault stays silent on the live input while still being reachable on a synthetic one. Master's own guard on the same file exits 0 as the control that separates what this PR changed from what the file already did.

Both directions. Dropping + narration_faults(notes) from read() reddens exactly one test — the new fault test — at 1 failed / 43 passed, and restoring the file byte-identically returns 44 passed. The companion test covers the shapes the regex must not count: a cycle id quoted mid-sentence in the contract prose above the table, and a bookmark whose prose carries none.

The landing tree. check-merge-plan-suite on this PR: suite OK, 6151 passed, 37 skipped. Freshness FRESH with the merge base at master's tip, and both CI legs pass.

One scope note for whoever merges: narration_blocks() stops at the table's first row, so prose below the table is not read as narration. That matches this file's shape today and is worth remembering only if the file ever grows a footer.

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.

the rotation guard cannot see the bookmark's per-cycle prose, so 43 narration blocks accumulated unseen

2 participants