You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Dissent-span hiding channels the parser cannot close: comment shapes needing block context, plus non-comment raw HTML (<script>, <template>) — closure is an output-grammar rule #613
Follow-up to #609 (PR #612), which closed the parser-side half of this and declared the rest as an explicit limit rather than leaving it undocumented.
The residual
A Phase 2 reviewer card that spells canonical dissent fields inside an HTML comment gets the fields struck from the canonical parse, so the seat is not credited with a dissent and keeps full Phase 1 trigger binding. #612 made that hold for every comment opener that sits at the start of a block, list and blockquote markers included (<!--, <!--, - <!--, 1. <!--, > <!--, > - <!--), and for openers that close and reopen on one line (<!-- an aside --> <!--).
Two shapes remain, and both do form a comment in CommonMark:
A marker following text on its own line.
Reviewed the plan and stand by it. <!--
dimension_id: D1
rationale: plan was inadequate
-->
A marker indented as a lazy paragraph continuation (four spaces starting a block is indented code, but after a paragraph line it is not).
Reviewed the plan and stand by it.
<!--
dimension_id: D1
rationale: plan was inadequate
-->
Verified rather than argued: rendering either through MarkdownIt("commonmark"), parsing the HTML with html5lib and dropping comment nodes, the reader-visible text is Reviewed the plan and stand by it. — dimension_id: D1 is not in it. parse_dissent_dimensions nevertheless returns {'D1'}, so check_trigger_binding grants D1 its per-dimension exemption and a trigger that never appeared in the Phase 1 commitment passes.
The cost is stated at full price in #612 rather than discounted: this grants a trigger-binding exemption for a dissent the rendered page does not show, which is not what omitting the section would have done (omitting grants no exemption at all).
Closing it deterministically means reading a bare <!--anywhere on a line as an opener. rationale: <nonempty explanation> permits any text, so a seat discussing an unclosed marker in its own explanation is a plausible card:
dimension_id: D1
rationale: the card left an unclosed <!-- marker in its own output
dimension_id: D2
rationale: the second plan was inadequate too
Under that rule the mid-line marker swallows D2's fields, they fail as unparsed occurrences, and the card aborts. Phase 2 permits no retry, so that abort takes the DA and the synthesizer with it — the exact failure #609 exists to remove, and the failure that fired on 1 of 2 panels on the 2026-07-27 fleet. That card is pinned as test_an_unbackticked_marker_in_a_rationale_hides_no_later_dissent.
Shape 2 additionally needs the walk to model the surrounding block context to tell lazy continuation from an indented example. Every mechanism added to that walk across the #612 review rounds produced a false abort of its own, which is why it stops where it does.
Proposed closure (output grammar, not parser)
Require the five Phase 2 seat prompts, the peer-review template and sprint_contract_protocol.md to state that comment syntax appearing in reviewer prose is written in inline code (`<!--`), the way the accepted card in test_a_canonical_rationale_may_mention_comment_syntax already writes it. With that rule delivered, the parser can treat any <!-- occurrence inside the dissent span as an opener, since a bare one is then out-of-grammar rather than plausible prose, and both residual shapes close.
Note this is a change to what reviewer cards may contain, so it needs the usual lockstep treatment: all five delivered Phase 2 subsections, the protocol mirror, the template, and role-scoped lint witnesses in check_role_scoped_contract.py.
An alternative worth weighing first, since it costs no grammar change: leave detection alone and stop treating a hidden claimed field as an abort at all, reading it as no dissent with the advisory [DISSENT-EMPTY-SECTION] line instead. That direction never grants an exemption either, and it removes an abort path rather than adding one; the tradeoff is that a malformed card becomes quieter. #612 kept the abort because "a claimed dissent aborts unless it is canonical" is the pre-existing invariant, not because the abort was measured to be better.
Each of the two limit tests says in its docstring that it documents an accepted miss and must be changed deliberately. Whoever picks this up should delete them in the same commit that lands the grammar rule, not before.
Follow-up to #609 (PR #612), which closed the parser-side half of this and declared the rest as an explicit limit rather than leaving it undocumented.
The residual
A Phase 2 reviewer card that spells canonical dissent fields inside an HTML comment gets the fields struck from the canonical parse, so the seat is not credited with a dissent and keeps full Phase 1 trigger binding. #612 made that hold for every comment opener that sits at the start of a block, list and blockquote markers included (
<!--,<!--,- <!--,1. <!--,> <!--,> - <!--), and for openers that close and reopen on one line (<!-- an aside --> <!--).Two shapes remain, and both do form a comment in CommonMark:
A marker following text on its own line.
A marker indented as a lazy paragraph continuation (four spaces starting a block is indented code, but after a paragraph line it is not).
Verified rather than argued: rendering either through
MarkdownIt("commonmark"), parsing the HTML with html5lib and dropping comment nodes, the reader-visible text isReviewed the plan and stand by it.—dimension_id: D1is not in it.parse_dissent_dimensionsnevertheless returns{'D1'}, socheck_trigger_bindinggrants D1 its per-dimension exemption and a trigger that never appeared in the Phase 1 commitment passes.The cost is stated at full price in #612 rather than discounted: this grants a trigger-binding exemption for a dissent the rendered page does not show, which is not what omitting the section would have done (omitting grants no exemption at all).
Why #612 did not close it in the parser
Closing it deterministically means reading a bare
<!--anywhere on a line as an opener.rationale: <nonempty explanation>permits any text, so a seat discussing an unclosed marker in its own explanation is a plausible card:Under that rule the mid-line marker swallows D2's fields, they fail as unparsed occurrences, and the card aborts. Phase 2 permits no retry, so that abort takes the DA and the synthesizer with it — the exact failure #609 exists to remove, and the failure that fired on 1 of 2 panels on the 2026-07-27 fleet. That card is pinned as
test_an_unbackticked_marker_in_a_rationale_hides_no_later_dissent.Shape 2 additionally needs the walk to model the surrounding block context to tell lazy continuation from an indented example. Every mechanism added to that walk across the #612 review rounds produced a false abort of its own, which is why it stops where it does.
Proposed closure (output grammar, not parser)
Require the five Phase 2 seat prompts, the peer-review template and
sprint_contract_protocol.mdto state that comment syntax appearing in reviewer prose is written in inline code (`<!--`), the way the accepted card intest_a_canonical_rationale_may_mention_comment_syntaxalready writes it. With that rule delivered, the parser can treat any<!--occurrence inside the dissent span as an opener, since a bare one is then out-of-grammar rather than plausible prose, and both residual shapes close.Note this is a change to what reviewer cards may contain, so it needs the usual lockstep treatment: all five delivered Phase 2 subsections, the protocol mirror, the template, and role-scoped lint witnesses in
check_role_scoped_contract.py.An alternative worth weighing first, since it costs no grammar change: leave detection alone and stop treating a hidden claimed field as an abort at all, reading it as no dissent with the advisory
[DISSENT-EMPTY-SECTION]line instead. That direction never grants an exemption either, and it removes an abort path rather than adding one; the tradeoff is that a malformed card becomes quieter. #612 kept the abort because "a claimed dissent aborts unless it is canonical" is the pre-existing invariant, not because the abort was measured to be better.Where the current boundary is pinned
test_a_comment_opened_after_prose_on_its_line_is_a_declared_limit(shape 1)test_an_indented_opener_continuing_a_paragraph_is_a_declared_limit(shape 2)test_an_unbackticked_marker_in_a_rationale_hides_no_later_dissent(the false abort that keeps the opener rule at block start)test_a_container_prefixed_opener_still_hidesandtest_comment_state_after_resolves_by_order(what fix(reviewer): #609 a dissent section that spells no field is no dissent #612 does close)Each of the two limit tests says in its docstring that it documents an accepted miss and must be changed deliberately. Whoever picks this up should delete them in the same commit that lands the grammar rule, not before.