ANY_HEADING_RE (src/bmad_loop/deferredwork.py:26) bounds a canonical entry:
ANY_HEADING_RE = re.compile(r"^#{1,6} ", re.MULTILINE)
It requires the #s at column zero and a single literal space after them. But the
persisted format promises more than that — deferred-work-format.md:53 says an entry
ends at "any other # .. ###### heading" — and CommonMark counts three shapes
this regex does not: up to three leading spaces, a tab as the separator, and an empty
heading with nothing after the #s.
When the boundary is missed, the entry span runs on past the section header, and
everything below is read as part of the preceding entry.
Repro
A ledger whose open DW-1 is followed by a section heading and then a column-zero
gate: 3-2:
### DW-1: an unlanded entry
status: open
summary: s
evidence: e
<HEADING>
gate: 3-2
[tok for e in parse_ledger(text) for tok in gates(e).tokens]:
<HEADING> |
result |
|
## Notes |
[] |
control — bounded correctly |
## Notes |
['3-2'] |
absorbed |
## Notes |
['3-2'] |
absorbed |
## Notes |
['3-2'] |
absorbed |
## Notes |
['3-2'] |
correct — 4 spaces is an indented code block, not a heading |
##\tNotes |
['3-2'] |
absorbed |
## |
['3-2'] |
absorbed — valid empty ATX heading |
## |
[] |
bounded |
## Notes ## |
[] |
bounded |
The column-zero row is the control that proves the repro is not vacuous, and the
4-space row is the control in the other direction: it must keep absorbing, because
CommonMark makes that an indented code block rather than a heading. Any fix has to
leave both rows unchanged.
Consequence
DW-1 is open, so the absorbed gate: 3-2 makes it gate story 3-2. Validation and
dispatch both then refuse a story that, read against the documented format, that entry
never gated. It fails closed rather than open, but it is a spurious hard pause on the
run, and the reason names an entry the operator will not find a gate in.
Scope
Pre-existing and not introduced by #502 — the regex is byte-identical on main
(deferredwork.py:24 there) and that PR does not touch it. What #502 changes is the
severity: before the gate: field, a mis-bounded span mis-attributed closes: or a
status read; now it can block dispatch.
Worth noting the two halves of the module already disagree — _LINE_HEADING_RE
(deferredwork.py:944, the legacy side) is ^(#{1,6})[ \t]+(.*?)[ \t]*$, so it accepts
a tab separator that the canonical side rejects.
No test pins the current column-zero/space-only shape, so tightening the regex is not
blocked by an existing expectation.
Fix shape
Allow {0,3} of leading indentation and [ \t] as the separator, and admit the empty
heading — while keeping the 4-space row absorbed. Sibling parser-scope defect: #327.
ANY_HEADING_RE(src/bmad_loop/deferredwork.py:26) bounds a canonical entry:It requires the
#s at column zero and a single literal space after them. But thepersisted format promises more than that —
deferred-work-format.md:53says an entryends at "any other
#..######heading" — and CommonMark counts three shapesthis regex does not: up to three leading spaces, a tab as the separator, and an empty
heading with nothing after the
#s.When the boundary is missed, the entry span runs on past the section header, and
everything below is read as part of the preceding entry.
Repro
A ledger whose open
DW-1is followed by a section heading and then a column-zerogate: 3-2:[tok for e in parse_ledger(text) for tok in gates(e).tokens]:<HEADING>## Notes[]## Notes['3-2']## Notes['3-2']## Notes['3-2']## Notes['3-2']##\tNotes['3-2']##['3-2']##[]## Notes ##[]The column-zero row is the control that proves the repro is not vacuous, and the
4-space row is the control in the other direction: it must keep absorbing, because
CommonMark makes that an indented code block rather than a heading. Any fix has to
leave both rows unchanged.
Consequence
DW-1is open, so the absorbedgate: 3-2makes it gate story3-2. Validation anddispatch both then refuse a story that, read against the documented format, that entry
never gated. It fails closed rather than open, but it is a spurious hard pause on the
run, and the reason names an entry the operator will not find a gate in.
Scope
Pre-existing and not introduced by #502 — the regex is byte-identical on
main(
deferredwork.py:24there) and that PR does not touch it. What #502 changes is theseverity: before the
gate:field, a mis-bounded span mis-attributedcloses:or astatus read; now it can block dispatch.
Worth noting the two halves of the module already disagree —
_LINE_HEADING_RE(
deferredwork.py:944, the legacy side) is^(#{1,6})[ \t]+(.*?)[ \t]*$, so it acceptsa tab separator that the canonical side rejects.
No test pins the current column-zero/space-only shape, so tightening the regex is not
blocked by an existing expectation.
Fix shape
Allow
{0,3}of leading indentation and[ \t]as the separator, and admit the emptyheading — while keeping the 4-space row absorbed. Sibling parser-scope defect: #327.