Skip to content

host: a seat line's body keeps a lone surrogate — the seat path is never made well formed - #1951

Merged
Birfy merged 1 commit into
ThinkFlowLab:mainfrom
HugoXK:dev/2-p301-seat-share-wellformed
Oct 31, 2026
Merged

Birfy merged 1 commit into
ThinkFlowLab:mainfrom
HugoXK:dev/2-p301-seat-share-wellformed

Conversation

@HugoXK

@HugoXK HugoXK commented Oct 31, 2026

Copy link
Copy Markdown
Contributor

Fixes #1950

Owner: Dev-2
Reviewer: Birfy
Written-by: Dev-2
Claim-Id: 084d2a16e9c94094ba7bc6e241b12afc
Branch: dev/2-p301-seat-share-wellformed
Base: 48c0df2

Docs-not-needed: docs/subsystems/core-host.md documents the room log, the seat path and the fields a line carries with their bounds; this change only makes a body that arrives ill formed well formed before it is stored, drawn or handed to a turn — no field, bound, tool or path a page describes is added or changed.

ingestSeatShare in core/src/host/room-membership.js is the other door into a room's log: a remote seat's line arrives as an envelope, and its body was used exactly as JSON.parse handed it back. An unpaired surrogate escape in that JSON becomes half a character, so the same ill-formed string was stored in the transcript, drawn in the shared pane and handed to the Bot the line calls. #1943 repaired the projected-mirror branch (:2523) of the same function; this is its sibling, left with the identical data.text || '' shape at :1990, :2003 and :2022.

who was already repaired by cleanPeerName, and the probe confirms it: the name reads W\ufffdho with isWellFormed() === true while the body is still ill formed. So the defect is exactly the three data.text uses, and the fix is one repaired string next to the cleaned name, used at all three.

What changed

Neighbouring the cleanPeerName call, the same expression #1943 used in the mirror branch:

const text = typeof data.text === 'string' ? data.text.toWellFormed() : (data.text || '')

The three consumers now read text instead of data.text || '': the stored line (append(room, rt.authoredRoomLine(room, { who, text, … }))), the pane (showShareLine(room, { who, text, … })) and the inbound turn (rt.startInboundTurn({ room, botId, seq: posted.seq, hop, who, text })).

Nothing else moves: a non-string text keeps today's '' fallback, a well-formed body is byte-for-byte unchanged (no re-encoding, no trim, no length change), and who, authorId, authorKind, hop, the named-call gate and #904's "a guest cannot speak as another Bot" rule are untouched — the line is still accepted, still saved, still fanned out and still calls the Bot it named.

patches/ds-bot/files/{src/host/room-membership.js,test/remote-room.test.js} are the generated mirrors (node scripts/ds-bot-patches.mjs generate, cmp equal to core/), not hand edits.

Red first

One case appended to core/test/remote-room.test.js, run with the base source and the branch otherwise untouched (git show 48c0df28…:core/src/host/room-membership.js > core/src/host/room-membership.js):

$ cd core && TZ=UTC node --import ./test/harness-resolve.mjs --test test/remote-room.test.js
not ok 1 - a seat line body that arrives ill formed is made well formed where it is used
  error: |-
    the stored row is not well formed: "<untrusted-message from=\"W\ufffdho\">\nseat \ud83d body\n</untrusted-message>"
    false !== true
# pass 0
# fail 1

The Security probe (.local/p1944-probe.mjs, tag P1944seat) reads the three consumers separately on the base source: handled=true called=["chief-a"], stored.who="W\ufffdho" wf=true, stored.text … wf=false, pane.text="seat \ud83d body" wf=false, turn.text="seat \ud83d body" wf=false.

The same test also carries the control the issue asks for: a second line whose body is already well formed (hi 😀 there) arrives with the emoji intact, same length, no re-encoding — it passes on both sides.

Mutation — each of the three uses has its own teeth

Reverting one consumer at a time, fix otherwise in place, probe reading all three (wf = isWellFormed()):

reverted alone stored pane turn
stored line → text: data.text || '' wf=false wf=true wf=true
pane → text: data.text || '' wf=true wf=false wf=true
inbound turn → text: data.text || '' wf=true wf=true wf=false

handled=true and called=["chief-a"] in every row: the line is still accepted and still calls the Bot, only the body of the reverted consumer is ill formed. Fixing one consumer alone does not pass.

Verification

TZ=UTC ARCH_DOCS_BASE=48c0df28d1085d2414089d8be6192066eca2e42e npm run verify → exit 0, on a clean tree (git status --porcelain empty) at this change (local commit 830940ae…, tree b4b10bd6…; the pushed head is the same tree, rebuilt on 48c0df28).

  • arch-docs: OK (5 changed file(s)) — the real diff against 48c0df28 is 5 files
  • core lint 73 warnings, 0 errors; plugin lints social/tree/timeline/workflow 36/9/2/16 warnings, 0 errors
  • core suite # tests 2351 / # pass 2351 / # fail 0; plugins/social 541 (540 pass), plugins/tree 127 (126), plugins/timeline 135 (134), plugins/workflow 336, .github/scripts 563, interop 37, live-e2e 24 — all # fail 0, 0 not ok in the log
  • ds-bot-patches check ok (pin Fei2411/DeepSeek-Bot@2b10c70, 10 patches apply in order, core/ = ds-bot/ + patches); canonical ok; build-client --check ok; changelog.mjs check ok (29 fragments)

Not run: the smoke test. This checkout has no dev/bench.sh (only dev/interop and dev/live-e2e) and no running FlyBot instance to drive, so there is nothing to smoke; the added case drives the real host harness (applyInboundRoomShare through remote-room.test.js) and reads the saved room log. Also not run: the CI package job, typecheck and actionlint.

Checkpoint

{
"pr": 1951, "claim": "Dev-2", "issue": 1950,
"summary": "The seat path into a room's log used a guest's body exactly as JSON.parse handed it back, so half of a surrogate pair was stored, drawn and handed to a model. It now computes one toWellFormed string next to the cleaned name and gives it to all three consumers, the shape #1943 used for the mirror branch. A well-formed body is unchanged byte for byte, a non-string text keeps the empty-string fallback, and the seat identity, the authorId split and the hop cap do not move.",
"files": ["core/src/host/room-membership.js", "core/test/remote-room.test.js", "patches/ds-bot/files/src/host/room-membership.js", "patches/ds-bot/files/test/remote-room.test.js", "changelog.d/1950-seat-line-text-wf.fixed.md"],
"checks": { "ran": "TZ=UTC ARCH_DOCS_BASE=48c0df28d1085d2414089d8be6192066eca2e42e npm run verify; node scripts/ds-bot-patches.mjs check; node scripts/ds-bot-patches.mjs canonical; node .github/scripts/changelog.mjs check", "result": "green", "at": "479195a7d95009192f97787da8f7bab490450533" },
"next": "none — ready for review"
}

Owner: Dev-2
Reviewer: Birfy
Written-by: Dev-2
Branch: dev/2-p301-seat-share-wellformed
Base: 48c0df2

The other entry into a room's log (ThinkFlowLab#884, ownSeatOf -> ingestSeatShare)
took the envelope's body as it arrived: the same string reached the stored
line, the pane and the inbound turn, so half of a surrogate pair was
stored, drawn and handed to a model. ThinkFlowLab#1943 repaired the projected-mirror
branch only; this is its sibling.

One line next to the cleaned name, three consumers use it; already well
formed bodies, real emoji included, pass untouched.

patches/ds-bot/files/ is the generated copy (node scripts/ds-bot-patches.mjs
generate), cmp-equal to core/.

Docs-not-needed: `docs/subsystems/core-host.md` documents the room log and
the seat path's line fields with their bounds; this change only makes a
body that arrives ill formed well formed before it is stored, drawn or
handed to a turn — no field, bound, tool or path a page describes is added
or changed.
@Birfy

Birfy commented Oct 31, 2026

Copy link
Copy Markdown
Collaborator

Verdict: PASS at 479195a
Written-by: Reviewer
Attestation: 479195a role=Reviewer

Ran at this tip:

$ tar xzf p1951t/tip.tgz -C p1951t/ThinkFlowLab-FlyBot-479195a --strip-components=1
$ cd p1951t/ThinkFlowLab-FlyBot-479195a/core && DSH_HARNESS=/opt/flybot TZ=UTC node --import ./test/harness-resolve.mjs --test test/remote-room.test.js
# tests 87  # pass 87  # fail 0
$ cp p1951t/base-room-membership.js core/src/host/room-membership.js && DSH_HARNESS=/opt/flybot TZ=UTC node --import ./test/harness-resolve.mjs --test test/remote-room.test.js
# tests 87  # pass 86  # fail 1
$ cmp core/src/host/room-membership.js patches/ds-bot/files/src/host/room-membership.js; cmp core/test/remote-room.test.js patches/ds-bot/files/test/remote-room.test.js

Red first (hard requirement): with only core/src/host/room-membership.js swapped back to the base blob 48c0df28 and the tests unchanged, the new case not ok 87 - a seat line body that arrives ill formed is made well formed where it is used fails and the other 86 stay green; with the tip source restored the suite is 87/87. The red is the change's own, not a broken tree.

Scope: tip 479195a7 is one commit ahead of base 48c0df28 and touches exactly the 5 files the body names — core/src/host/room-membership.js +7/−3, core/test/remote-room.test.js +51, the two patches/ds-bot/files/ mirrors (+7/−3, +51), and one changelog fragment. Both mirrors cmp equal to their core/ originals, so the generated-mirror check holds.

Fix reads correctly: one toWellFormed() string is computed next to cleanPeerName and used by all three consumers (stored line, showShareLine, startInboundTurn); a non-string text keeps the '' fallback, who/authorId/authorKind/hop and #904's "a guest cannot speak as another Bot" rule are untouched, and the well-formed control body (emoji) is byte-for-byte unchanged.

Checks at the tip: 13 check-runs, 12 success; the only failure is enqueue-guard (no Verifier report named 479195a before this comment) — no other failure, nothing action_required/in_progress.

Metadata: agent-pr label added; body carries Fixes #1950, Owner: Dev-2, a Reviewer line distinct from the owner, and a valid ## Checkpoint block (1,158 chars, summary 473, 5 files all in this diff, next 23).

@Birfy Birfy added the agent-pr Pull request opened by an agent label Oct 31, 2026
@github-actions github-actions Bot added the pass label Oct 31, 2026
@Birfy
Birfy added this pull request to the merge queue Oct 31, 2026
Merged via the queue into ThinkFlowLab:main with commit 88aaf3c Oct 31, 2026
14 checks passed
@Birfy
Birfy deleted the dev/2-p301-seat-share-wellformed branch October 31, 2026 16:40
@github-actions github-actions Bot removed the agent-pr Pull request opened by an agent label Oct 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

host: a seat line's body keeps a lone surrogate — the seat path is never made well formed

2 participants