chat: decode Bifrost chat_pending.v4 reply_to (lenient ReplyLink, opt-in request) #53

Merged
eureka merged 14 commits from chat/reply-link-v4 into main 2026-11-11 17:26:06 +00:00
Owner

Executes gamecult-chat:cut_spec:cut-chat-reply-link-decode.r4 (rulings gamecult-chat:ruling:reply-link-drop-bad-quote, superseding reply-link-reject-document). Base 1c412ebb (main had not moved).

Huginn decodes Bifrost chat_pending.v4 reply_to (delivery tuple index 14) and shows a valid quote to a Mind as one organ-framed reply_to data line after its message.

  • chat_client.rs: ChatDelivery.reply_to: ReplyLink { Absent, Quote(ReplyTo), Dropped(ReplyViolation) } decoded by a hand-written visitor over the whole element. A reply_to outside the contract is dropped whole, never repaired or clipped, and never fails the poll. Sender and event id are held to Matrix id shapes; excerpt is bounded at 240 code points as sent, then stripped of invisible characters. reply_warnings words the once-per-event warning (event id and class only).
  • chat_rudp.rs: RudpChat::with_reply_link (off by default). On: asks chat_pending_request.v3 first and steps down v4 -> v2 -> v1 on UnknownKind. Off: today's exact request. reply_defects() counts dropped events; warnings go to stderr as mind-chat: reply_to dropped event=... violation=....
  • persona.rs: quote_line (the spec called it reply_line, which already names the Self-reply line) and one header sentence. Dropped links never render.
  • mind-persona.rs: BIFROST_CHAT_PENDING_V4, exactly 1 is on.
  • Fixtures persona-chat-v8/ are byte copies of Bifrost persona-chat-v6/*.msgpack at dc35c4a2.
  • New direct dependency unicode-general-category = "1" on mind-body (already in Cargo.lock and the build graph via eureka-state).

Nothing installed or deployed. Flag stays off; the flip belongs to follow-up chat-reply-link-flip.

Verification: pinned rust container (sha256:4c2fd73e...), cargo test --locked -p mind-body as root: 1026 passed, 0 failed (the control/persona tests need root-owned temp dirs and fail for any non-root uid, at base too). cargo clippy --no-deps -p mind-body --all-targets shows only pre-existing lints.

🤖 Generated with Claude Code

Executes `gamecult-chat:cut_spec:cut-chat-reply-link-decode.r4` (rulings `gamecult-chat:ruling:reply-link-drop-bad-quote`, superseding `reply-link-reject-document`). Base `1c412ebb` (main had not moved). Huginn decodes Bifrost `chat_pending.v4` `reply_to` (delivery tuple index 14) and shows a valid quote to a Mind as one organ-framed `reply_to` data line after its message. - `chat_client.rs`: `ChatDelivery.reply_to: ReplyLink { Absent, Quote(ReplyTo), Dropped(ReplyViolation) }` decoded by a hand-written visitor over the whole element. A reply_to outside the contract is dropped whole, never repaired or clipped, and never fails the poll. Sender and event id are held to Matrix id shapes; excerpt is bounded at 240 code points as sent, then stripped of invisible characters. `reply_warnings` words the once-per-event warning (event id and class only). - `chat_rudp.rs`: `RudpChat::with_reply_link` (off by default). On: asks `chat_pending_request.v3` first and steps down v4 -> v2 -> v1 on `UnknownKind`. Off: today's exact request. `reply_defects()` counts dropped events; warnings go to stderr as `mind-chat: reply_to dropped event=... violation=...`. - `persona.rs`: `quote_line` (the spec called it `reply_line`, which already names the Self-reply line) and one header sentence. Dropped links never render. - `mind-persona.rs`: `BIFROST_CHAT_PENDING_V4`, exactly `1` is on. - Fixtures `persona-chat-v8/` are byte copies of Bifrost `persona-chat-v6/*.msgpack` at dc35c4a2. - New direct dependency `unicode-general-category = "1"` on mind-body (already in Cargo.lock and the build graph via eureka-state). Nothing installed or deployed. Flag stays off; the flip belongs to follow-up `chat-reply-link-flip`. Verification: pinned rust container (sha256:4c2fd73e...), `cargo test --locked -p mind-body` as root: 1026 passed, 0 failed (the control/persona tests need root-owned temp dirs and fail for any non-root uid, at base too). `cargo clippy --no-deps -p mind-body --all-targets` shows only pre-existing lints. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Byte copies of Bifrost tests/fixtures/persona-chat-v6/*.msgpack at
dc35c4a2542e868df6d5c06f2166732ac6be37ac (reply-to, no-reply-to,
reply-to-redacted answers; chat_pending_request.v3). Spec:
gamecult-chat:cut_spec:cut-chat-reply-link-decode.r2

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
ChatDelivery gains reply_to (tuple index 14): Absent, Quote(ReplyTo) or
Dropped(ReplyViolation). A reply_to outside the contract is dropped whole and
never fails the poll; reply_warnings words and deduplicates the organ-side
warning. Sender and event id are held to Matrix id shapes; the excerpt is
bounded at 240 code points as sent, then stripped of invisible characters.
Adds the v4 consts and the unicode-general-category direct dependency
(already in Cargo.lock).

Spec: gamecult-chat:cut_spec:cut-chat-reply-link-decode.r4

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
RudpChat::with_reply_link asks the v4 kind first and steps down one rung per
UnknownKind refusal (v4 -> v2 -> v1); flag off sends today's bytes. The
dropped-reply warning prints once per event id and reply_defects counts them.

Spec: gamecult-chat:cut_spec:cut-chat-reply-link-decode.r4

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The quote is data on its own line after its message, every variable part
quoted, role derived from the sender id. Dropped links never render.

Spec: gamecult-chat:cut_spec:cut-chat-reply-link-decode.r4

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Exactly "1" is on, anything else is off, nothing is echoed.

Spec: gamecult-chat:cut_spec:cut-chat-reply-link-decode.r4

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Author
Owner

Merged origin/main (b373c2a) into the branch (merge, no rebase, plain push). Fixtures persona-chat-v8 are byte-identical to Bifrost tests/fixtures/persona-chat-v6/ at e12b8e22 (the four .msgpack files; the .hex siblings are not vendored). The decode already holds the reply_to contract: sender and event id shape plus length cap 255, bad link dropped whole with a named warning, variation selectors and Cf/Co stripped from the excerpt. Verify on Yggdrasil was unavailable (ssh host ygg does not resolve from this body); local cargo test -p mind-body: all chat_client and reply-link persona tests pass, 270 other failures are the sandbox uid 985 refusing non-root /tmp (control/persona store tests).

🤖 Generated with Claude Code

Merged origin/main (b373c2a) into the branch (merge, no rebase, plain push). Fixtures persona-chat-v8 are byte-identical to Bifrost tests/fixtures/persona-chat-v6/ at e12b8e22 (the four .msgpack files; the .hex siblings are not vendored). The decode already holds the reply_to contract: sender and event id shape plus length cap 255, bad link dropped whole with a named warning, variation selectors and Cf/Co stripped from the excerpt. Verify on Yggdrasil was unavailable (ssh host ygg does not resolve from this body); local cargo test -p mind-body: all chat_client and reply-link persona tests pass, 270 other failures are the sandbox uid 985 refusing non-root /tmp (control/persona store tests). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- ElementVisitor reads newtype-struct, some and enum shapes (msgpack ext, timestamp) as Other, so that delivery's reply link drops with the
  warning and the message is delivered unchanged. Lists inside the link are skipped, not recursed into.
- The excerpt strip also removes U+034F, U+115F, U+1160, U+17B4, U+17B5, U+180B-180D, U+2800, U+3164; the header and the ReplyTo doc say an
  emptied excerpt reads as no text.
- trace_inputs records the quote line she was shown under the message.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A mouth that restarts keeps its seq counter but loses its pending, so its unacked events come back from the homeserver under fresh,
higher seqs. The client keyed everything on seq and told them twice. Progress now remembers the answered event ids; a delivery whose event
was answered is dropped from the poll and acked through (leading run only).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Author
Owner

Final state of the branch (head ec8ba4b), verified on Yggdrasil: cargo test -p mind-body green, 1032 passed, 0 failed, 4 skipped.

  • reply_to decode (earlier commits): sender and event id shape plus 255-byte cap, bad link dropped whole with a named warning, variation selectors, tag block, Cf/Co stripped from the excerpt.
  • msgpack ext values (fixext, ext8, timestamp ext) anywhere in reply_to drop that delivery's link, never the poll; lists nested inside the link are skipped, not recursed. Depth past rmp-serde's limit (1024) still fails the document (serde cannot resume).
  • Excerpt strip also removes U+034F, U+115F, U+1160, U+17B4, U+17B5, U+180B-180D, U+2800, U+3164; the header says no text can also mean nothing but invisible characters.
  • The turn trace records the rendered quote line under the message.
  • Mouth restart: its seq counter survives but its pending does not, so unacked events return under higher seqs. Progress keeps the answered event ids (recorded once the ack lands) and drops them from the poll, acking through; the process keeps the told event ids so an event told and not yet answered is not told again under a new seq. A told-but-unacked batch is still retold by the next process. Tests: events_a_restarted_mouth_renumbers_are_told_once_and_nothing_new_is_lost, an_event_told_and_unanswered_that_the_mouth_renumbers_is_not_told_again (killed by removing the told_events filter).

🤖 Generated with Claude Code

Final state of the branch (head ec8ba4b), verified on Yggdrasil: `cargo test -p mind-body` green, 1032 passed, 0 failed, 4 skipped. - reply_to decode (earlier commits): sender and event id shape plus 255-byte cap, bad link dropped whole with a named warning, variation selectors, tag block, Cf/Co stripped from the excerpt. - msgpack ext values (fixext, ext8, timestamp ext) anywhere in reply_to drop that delivery's link, never the poll; lists nested inside the link are skipped, not recursed. Depth past rmp-serde's limit (1024) still fails the document (serde cannot resume). - Excerpt strip also removes U+034F, U+115F, U+1160, U+17B4, U+17B5, U+180B-180D, U+2800, U+3164; the header says no text can also mean nothing but invisible characters. - The turn trace records the rendered quote line under the message. - Mouth restart: its seq counter survives but its pending does not, so unacked events return under higher seqs. Progress keeps the answered event ids (recorded once the ack lands) and drops them from the poll, acking through; the process keeps the told event ids so an event told and not yet answered is not told again under a new seq. A told-but-unacked batch is still retold by the next process. Tests: events_a_restarted_mouth_renumbers_are_told_once_and_nothing_new_is_lost, an_event_told_and_unanswered_that_the_mouth_renumbers_is_not_told_again (killed by removing the told_events filter). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
eureka merged commit bf9a8a7f13 into main 2026-11-11 17:26:06 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
GameCult/Huginn!53
No description provided.