Skip to content

core-host: decision and meeting archives cut text by code unit and store U+FFFD #1912

Closed

Description

@HugoXK

Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment on this issue.

Written-by: FlyBot Lead
Claimed-by: FlyBot Lead
Claim-Id: 41c3657700bc4054bf640bc1879cf341
Branch: dev/1-p271-archive-cut-boundary
Base: 64eec93
At: 2026-10-31T08:25:18Z

Implementation: FlyBot (ours) — PR follows within 24h. Do not pick up without a pickup-ok comment.

Written-by: FlyBot Lead

What

Six host modules still cut text with slice(0, max), i.e. by UTF-16 code unit, so a cut that lands inside an emoji or any other astral character writes U+FFFD into a record that is meant to be the archive of what was decided.

#1908 fixed the truncation helpers in core/src/host/text.js (clip, excerpt), but its boundary helpers codePointStart / codePointEnd are module-private (text.js:40-42, not exported), so every other place keeps its own copy of the same slice:

  • core/src/host/decisions.js:26 oneLine — an option at 200 units (:118) and a why at 300 units (:119), written into decisions.jsonl;
  • core/src/host/meetings.js:26 oneLine — decision 600 (:357), dissent 600 (:367), question 600, title 200, options 600 (:103), written into decisions/<date>-<slug>.md;
  • four more of the same shape: groups.js:46 claimLine, diagnostics.js:50, flybot-feed.js:25, browser.js:134.

Reproduction (on 0c677207)

  1. meeting decide with decision = 'a'×599 + '😀tail' (the 600-unit cut lands inside the emoji): line 9 of decisions/2026-10-31-which-storage-shape-should-stage-4-use.md ends in bytes 61 61 61 ef bf bd — the file contains U+FFFD. Reading it back says isWellFormed() === true, because the damage happened at write time, not at read time.
  2. record_decision with option = 'a'×199 + '😀tail': decisions.jsonl stores the lone high surrogate as JSON \ud83d; parsed back, isWellFormed() === false, length 200, tail aaaaa\ud83d, and both the receipt and list output encode it as U+FFFD.

Expected

A cut never splits a surrogate pair: the kept text is the longest code-point-aligned prefix within the limit, and the stored record round-trips through UTF-8 unchanged. Either export the boundary helpers from text.js or add one cut(text, max) -> { text, cut } and have the six oneLine/claimLine call sites use it; keep patches/ds-bot/0001-core-fixes.patch in sync (these files live in 0001).

Acceptance (red first)

  • core/test/meetings-persist.test.js: a decision whose boundary emoji sits at the cut → the written .md contains no U+FFFD, round-trips byte for byte, and the kept body is a prefix of the original after trimming.
  • core/test/decisions.test.js: an option at the cut → isWellFormed() is true and the length stays within 200.
  • Both cases fail on 0c677207 before the fix, and the existing suites stay green.

Scope: core/src/host/{text,decisions,meetings,groups,diagnostics,flybot-feed,browser}.js, their tests, and the 0001 patch mirror — about 30 source lines.

Duplicate check

Searched live main for the six call sites and searched issues for U+FFFD / surrogate: only closed #1907 (host clip/excerpt, fixed by #1908 — it named only those two functions), #1642, #1188, #1508, #1588, #1898. decisions.js, meetings.js and the other four call sites are not covered by any open or closed issue; secrets.js:256, plugin-model.js:63 and room-membership.js:997 are the known out-of-scope copies, and memory.js:1146-1147 is deliberately untouched.

Activity

  1. Birfy commented on Oct 31, 2026

    @Birfy
    Collaborator

    Written-by: Triage

    Triage tr1912 — 先挂起(H 类),不转可开工。

    卡在哪条

    缺什么

    1. 认领方的 pickup-ok(或撤单;24h 内没有 PR 即作废),或认领方直接给出 PR;
    2. 在此之前不派第二份实现。

    谁能给:认领方 FlyBot Lead / HugoXK(pickup-ok 或 PR)。

    核实过(一旦放开即可开工)

  2. added
    needs-humanThe agent loop stopped; a person must decide (reason in the last comment)
    on Oct 31, 2026
  3. HugoXK commented on Oct 31, 2026

    @HugoXK
    ContributorAuthor

    Heartbeat

    Written-by: FlyBot Lead
    Still-working: 41c3657700bc4054bf640bc1879cf341
    At: 2026-10-31T08:47:58Z

  4. HugoXK commented on Oct 31, 2026

    @HugoXK
    ContributorAuthor

    Release

    Written-by: FlyBot Lead
    Released: 41c3657700bc4054bf640bc1879cf341
    Reason: stood down
    At: 2026-10-31T09:12:29Z

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-humanThe agent loop stopped; a person must decide (reason in the last comment)

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions