Skip to content

journal: detect and recover collapse shape C, bare opening field tags with no closers - #1013

Merged
laynepenney merged 1 commit into
devfrom
fix/journal-collapse-open-tags
Aug 27, 2026
Merged

journal: detect and recover collapse shape C, bare opening field tags with no closers#1013
laynepenney merged 1 commit into
devfrom
fix/journal-collapse-open-tags

Conversation

@laynepenney

Copy link
Copy Markdown
Member

What was wrong

A third collapse shape, found in a real store on 2026-08-26: the emitter dropped every closing tag, so sibling fields arrived as bare opening tags inside the unclosed value. Two consecutive writes, one of them a clean three-parameter call; done and decisions both empty, next_steps carrying <decisions>... and <done>... items. Shapes A and B never fire on it: there is no closing tag and no parameter marker anywhere.

What changed

  • Detection: a bare <focus|done|decisions|next_steps> opener is now a signature, so the write guard refuses it, the serving filter hides it, and carry-forward does not propagate it.
  • Recovery: the value is split at each bare opener and each piece goes to the field it names.
  • Repair (v2): every recovered field lands on the corrective entry's own surface for that field. done keeps the contaminated values first and verbatim, because they are the loop-breaking marker, followed by the clean recovered done values; recovered focus is appended to the repair's focus as a scalar suffix.

v2, after the r2 read on v1

The v1 body said the swallowed tail is split "so repair can put the content back where it belongs." That was false for a swallowed <done>: recover_collapsed parsed it correctly and repair_journal then built the corrective entry with only the contaminated marker in done, so the report counted a field the entry never carried. Parsing a field is not recovering it. The same omission existed for a swallowed <focus>. Both are fixed in this version and the claim above is now true.

Witnesses

witness pins
test_bare_opening_tags_are_detected / test_clean_prose_with_angle_brackets_still_not_flagged the signature and its control (comparisons, generic angle brackets, near-miss names, case)
test_recovery_splits_on_bare_openers / test_recovery_never_loses_content_shape_c the split and that no content is lost
test_guard_refuses_shape_c_and_names_the_field the write guard
test_serving_never_shows_a_shape_c_step the serving filter, with a clean-step control
test_repair_serves_a_swallowed_done_and_focus (v2) the served fruit end to end: marker kept verbatim, recovered done and focus present in the latest entry and in the session-start rendering, no opener in the served text

Under mutation: reverting only the done concatenation in repair_journal reds test_repair_serves_a_swallowed_done_and_focus and nothing else (33 to 32 pass); restore verified by grep.

Verification

  • tests/recall/test_journal_collapse.py: 33 passed, import path pinned and printed as the branch's src/synapt/recall/journal.py.
  • Full recall suite run recorded in the gate request.

Premium boundary: recall is OSS because this is the journal store's own write guard, serving filter, and append-only repair. No identity, no org state.

… with no closers

Found in a real store 2026-08-26: the emitter dropped EVERY closing tag, so
sibling fields arrived as bare opening tags inside the unclosed value. Two
consecutive writes, one a clean three-parameter call; done and decisions both
empty, next_steps carrying <decisions>... and <done>... items. Shapes A and B
never fire on it: no closing tag, no parameter marker.

Detection: a bare <focus|done|decisions|next_steps> opener is a signature.
Recovery: the value is split at each bare opener and each piece goes to the
field it names.

v2, after the r2 read on v1: recover_collapsed parsed a swallowed <done>
correctly and repair_journal then dropped it, building the corrective entry
with only the contaminated marker in done, so the report counted a field the
entry never carried. Now every recovered field lands on the corrective entry
for that field: done keeps the contaminated values first and verbatim (the
loop-breaking marker) followed by the clean recovered done values; recovered
focus is appended to the repair focus as a scalar suffix. The new witness
asserts the SERVED output end to end; reverting only the done concatenation
reds that witness and nothing else (33 -> 32 pass).

Co-Authored-By: Claude <noreply@anthropic.com>
@laynepenney
laynepenney merged commit 253fbb2 into dev Aug 27, 2026
12 checks passed
@laynepenney
laynepenney deleted the fix/journal-collapse-open-tags branch August 27, 2026 10:26
@github-actions github-actions Bot locked and limited conversation to collaborators Aug 27, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant