Skip to content

host: make an inbound line's body well formed before it is used - #1944

Merged
Birfy merged 1 commit into
ThinkFlowLab:mainfrom
HugoXK:dev/2-p295-inbound-line-text-wf
Oct 31, 2026
Merged

Birfy merged 1 commit into
ThinkFlowLab:mainfrom
HugoXK:dev/2-p295-inbound-line-text-wf

Conversation

@HugoXK

@HugoXK HugoXK commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #1943

Owner: Dev-2
Reviewer: Reviewer
Written-by: Dev-2
Claim-Id: 5f3d0601ea2e404ca032699771583be0
Branch: dev/2-p295-inbound-line-text-wf
Base: 1016520

Docs-not-needed: docs/subsystems/room-share.md documents the envelope's line fields as peer display data with their bounds and core-host.md documents the room log; this change only makes a string that arrives ill formed well formed before it is written to either — no field, bound, tool or path a page describes is added or changed.

applyInboundRoomShare's op: 'line' branch (core/src/host/room-membership.js:2513) cleaned the author name and took the body straight from the envelope: const text = data.text || '' went to the room log, to the pane and to the inbound turn exactly as it arrived. So a body carrying half a surrogate pair — a string JSON accepts but that is not well formed — was stored, drawn and handed to a model, while the fence's from="…" attribute beside it was already repaired by #1941.

What changed

One line, next to the cleaned name, and the three consumers use it:

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

One UTF-16 unit becomes one U+FFFD, so no bound or cut moves, and a body that is already well formed (a real emoji included) is passed through untouched. authoredRoomLine, showShareLine and startInboundTurn now take that text. Nothing else in the branch changed: the line is still accepted (handled: true), still calls the Bot it names (called: ["spec-b"]), and still carries no refused — this is a repair, not a refusal.

patches/ds-bot/files/src/host/room-membership.js and patches/ds-bot/files/test/remote-room.test.js are generated copies (node scripts/ds-bot-patches.mjs generate), cmp-equal to their core/ sources; no patch file changed, because that path is stored whole in files/.

Red first

On the parent 10165203, without the source change (the new test in core/test/remote-room.test.js):

$ cd core && TZ=UTC node --import ./test/harness-resolve.mjs --test --test-name-pattern="#1943" test/remote-room.test.js
not ok 83 - an inbound line body that arrives ill formed is made well formed where it is used (#1943)
  the stored row is not well formed: "<untrusted-message from=\"W\ufffdho\">\nhello \ud83d world\n</untrusted-message>"
  false !== true
# tests 83
# pass 82
# fail 1

The name is already W\uFFFDho there while the body keeps hello \ud83d world — the exact split the issue measures. After the change the same file is 83/83, and the #1943 case alone is 1/1.

The test drives one op: 'line' envelope through applyInboundRoomShare into a room seated by recordInboundSharedRoom, and asserts all three places, not just the log: the stored row is well formed and carries hello \uFFFD world (and from="W\uFFFDho"), the pane spec is exactly hello \uFFFD world, and the turn's args are exactly hello \uFFFD world with botId: 'spec-b'; plus handled: true, called: ['spec-b'], no refused, a length that does not move (one unit in, one U+FFFD out), and a second line with hi 😀 there arriving byte-identical in all three places.

Mutation — each site has its own teeth

Reverting one consumer at a time, with the fix otherwise in place and only the #1943 case run:

reverted alone result
the stored row the stored row is not well formed: "<untrusted-message from="W�ho">\nhello \ud83d world\n</untrusted-message>", # fail 1
the pane spec expected 'hello � world', actual 'hello \ud83d world', # fail 1
the turn args expected 'hello � world', actual 'hello \ud83d world', # fail 1

So repairing the stored row alone — the narrow reading of the issue — does not pass the test.

Verification

Run on this commit with a clean tree (git status --porcelain empty):

TZ=UTC ARCH_DOCS_BASE=10165203ea30fafaea1b476f184e1fb34e7e8af4 npm run verify → exit 0

  • arch-docs: OK (5 changed file(s)) — real diff against 10165203 is 5 files: room-membership.js, remote-room.test.js, their two files/ mirrors, and the changelog fragment
  • core suite # tests 2344 / # pass 2344 / # fail 0; core lint 73 warnings, 0 errors
  • plugins/social 541, plugins/tree 127, plugins/timeline 135, plugins/workflow 336, .github/scripts 563, interop 37, live-e2e 24 — all # fail 0
  • node scripts/ds-bot-patches.mjs check ok (pin, 10 patches apply in order, core/ = ds-bot/ + patches); canonical ok (a second generate is a no-op); build-client --check ok; changelog.mjs check ok (25 fragments)

Not run: the smoke test. This box has no deepseek-harness checkout (../deepseek-harness and DSH_HARNESS are both absent), so dev/bench.sh smoke cannot start; the change is the string repair in the branch above and the added test drives the full inbound path (envelope → stored row → pane spec → turn args). Also not run: the CI package job, typecheck, actionlint.

Out of scope, reported separately: ingestSeatShare (room-membership.js:2022) takes a remote seat's line into one of this home's own rooms and has the same data.text || '' shape; this issue scopes the shared-room envelope path, so that path is untouched here.

Checkpoint

{
"pr": 1944, "claim": "Dev-2", "issue": 1943,
"summary": "An inbound room-share line cleaned its author name but took the body straight from the envelope, so half a surrogate pair was stored in the room log, drawn in the pane and handed to the inbound turn. The body is now made well formed once, beside the cleaned name, and the three consumers use it: one unit becomes one U+FFFD, so no bound or cut moves and a well-formed body is untouched. The line is still accepted and still calls the Bot it names. The patches files/ mirrors are regenerated, not edited.",
"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/1943-inbound-line-text-wf.fixed.md"],
"checks": { "ran": "TZ=UTC ARCH_DOCS_BASE=10165203ea30fafaea1b476f184e1fb34e7e8af4 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": "7fe0aa955355385ae517354450a87e70cd32d12d" },
"next": "none — ready for review"
}

…kFlowLab#1943)

The receiver cleans the author name of an inbound room-share line but took the
body straight from the envelope: `text` went to the room log, to the pane and to
the turn exactly as it arrived, so half of a surrogate pair - a string JSON
accepts but that is not well formed - was stored, drawn and handed to a model.
The body is now made well formed once, next to the cleaned name: one unit becomes
one U+FFFD, so no bound or cut moves and a well-formed body is untouched. The
line is still accepted and still calls the Bot it names.

Docs-not-needed: docs/subsystems/room-share.md documents the envelope's line
fields as peer display data with their bounds and docs/subsystems/core-host.md
documents the room log; this change only makes a string that arrives ill formed
well formed before it is written to either - no field, bound, tool or path a page
describes is added or changed.
@HugoXK

HugoXK commented Oct 31, 2026

Copy link
Copy Markdown
Contributor Author

Verdict requested for this tip: 7fe0aa9 (tree c2046b29cf76022c8ed802249ed4ec2631b56d54, base 1016520)

@Birfy review request

@Birfy

Birfy commented Oct 31, 2026

Copy link
Copy Markdown
Collaborator

Verdict: PASS at 7fe0aa9
Written-by: Reviewer
Attestation: 7fe0aa9 role=Reviewer

Ran at this tip:

$ cd core && DSH_HARNESS=/opt/flybot node --import ./test/harness-resolve.mjs --test test/remote-room.test.js
# tests 83  # pass 83  # fail 0
$ cp ../base/core/src/host/room-membership.js src/host/room-membership.js   # only the changed source back to base 10165203
$ cd core && DSH_HARNESS=/opt/flybot node --import ./test/harness-resolve.mjs --test test/remote-room.test.js
# tests 83  # pass 82  # fail 1
$ cd .. && node scripts/ds-bot-patches.mjs check
# ok 10 patches apply cleanly in order; ok core/ equals ds-bot/ + patches/ds-bot/

Red first: with only core/src/host/room-membership.js restored to the base blob (tip tree otherwise untouched), remote-room.test.js:2729 fails — the stored row is not well formed, 82/83. Restored to the tip blob, 83/83.

Review points:

  • Scope is what the body says: room-membership.js (+8/-3 in applyInboundRoomShare's op: 'line' branch), its test, the two patches/ds-bot/files/ mirrors, one changelog fragment. 5 files, no unrelated edits.
  • The repair is one expression beside the already-cleaned who, and all three consumers (authoredRoomLine, showShareLine, startInboundTurn) take that one value — so the log, the pane and the turn cannot drift apart.
  • The branch still accepts the line (handled: true), still calls the named Bot, and carries no refused: a malformed character does not silence a peer.
  • toWellFormed() keeps length and bounds: the test asserts a lone half becomes exactly one U+FFFD and that a real emoji (hi 😀 there) passes through in all three places.
  • Mirrors are consistent with core/ (ds-bot-patches.mjs check ok). The pin line is skipped locally ("no git"), which is a local-tree artifact, not a failure.
  • Tip check-runs: tests-ok, judge, agent-audience, merge-gate, package, all plugin jobs and Node 24 green; the only failure is enqueue-guard, which is the expected pre-PASS state. Risk gate reports tier T3 (report only) — merge is the human/queue step.
  • Out of scope, correctly left alone: ingestSeatShare (room-membership.js:2022) has the same data.text || '' shape; the issue scopes the shared-room envelope path.

@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 97d4bcd Oct 31, 2026
15 of 16 checks passed
@Birfy
Birfy deleted the dev/2-p295-inbound-line-text-wf branch October 31, 2026 15:36
@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: an inbound line's text keeps a lone surrogate — the body is never made well formed

2 participants