Skip to content

core-host: count the task board's question/answer and handoff bounds in code points - #1904

Merged
Birfy merged 1 commit into
ThinkFlowLab:mainfrom
HugoXK:dev/1-task-text-code-points
Oct 31, 2026
Merged

Birfy merged 1 commit into
ThinkFlowLab:mainfrom
HugoXK:dev/1-task-text-code-points

Conversation

@HugoXK

@HugoXK HugoXK commented Oct 31, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #1901

Owner: Dev
Written-by: Dev-1
Reviewer: Reviewer
Claim-Id: ebe734fb893e410597edb83ae324e9d1
Base: 2ec1687
Tree: 7f4f609da4a4dc2bd2dff80b3433d7a01fba06bf
Verified commit: 657f711bdaa2143839083b89202bba9d274a3f6c (tree above); the pushed head is rebuilt from that same tree by the push path, so the two commits carry identical content
Diff: 8 files, +149/−6

What was missing

selectedInput (the typed card's question/answer) and boundedLine (the handoff, evidence and lease lines) compared string.length, which counts UTF-16 units. An emoji costs two, so a 1,001-emoji question — 1,001 characters, 2,002 units — was refused with Question must contain 1–2000 characters, and a 151-emoji handoff.reason with handoff.reason is at most 300 characters: both sentences named a bound the text never exceeded. #1895/#1898 had already put memory, schedules, hooks and the Project store on code points; the task board was the remaining user-visible pair.

What changed

core/src/host/tasks.js imports codePoints from ./text.js (the zero-allocation counter from #1898) and counts through it at the two comparisons: codePoints(value) > 2000 in selectedInput and codePoints(text) > max in boundedLine. The fields, the bounds, the sentences and the stored values are unchanged; boundedLine still normalises whitespace before counting. The task board's own bound is not moved — 2,000 characters of emoji is now accepted, 2,001 is refused.

Red first

At 2ec168700fce with only the two test files' new cases added (implementation untouched): # tests 39 / # pass 37 / # fail 2.

not ok 39 - a typed question and answer count characters, not UTF-16 units (#1901)
  {"ok":false,"error":{"code":"bot/invalid","message":"Question must contain 1–2000 characters","details":{}}}
not ok 7 - the handoff, evidence and lease bounds count characters, not UTF-16 units (#1901)
  error: 'handoff.reason is at most 300 characters'

The cases pin 2,000 accepted / 2,001 refused for the question and the answer, 300/301 for handoff.reason, 200/201 for evidence.check and lease.holder, and one mixed string (1,999 emoji + 1 ASCII accepted, 2,000 emoji + 1 ASCII refused) so a "halve the bound" shortcut cannot pass. At the tip the same two files are 39/39.

Verified at 657f711bdaa2143839083b89202bba9d274a3f6c

git status --porcelain was empty before and after, and TZ=UTC ARCH_DOCS_BASE=2ec168700fce402e86442505b4f089caeb4f1c2d npm run verify exited 0:

  • core 2297/2297, social 541/541, tree 127/127, timeline 135/135, workflow 336/336, .github/scripts 563/563, interop 37/37, live-e2e 24/24; oxlint 0 errors; arch-docs: OK (8 changed file(s)); build-client --check clean.
  • node scripts/ds-bot-patches.mjs check: ok ds-bot/ = Fei2411/DeepSeek-Bot@2b10c70 (tree 5834c00, v0.1.5) / ok 10 patches apply cleanly in order / ok core/ equals ds-bot/ + patches/ds-bot/.
  • node scripts/ds-bot-patches.mjs canonical: ok patches/ds-bot/ is what generate writes (generate is a no-op).
  • node scripts/ds-bot-patches.mjs generate at this tip rewrote all 10 patches and 289 files/ entries, then git status --porcelain was empty and HEAD unchanged: every write was byte-identical to what the tree already held, so the empty status means nothing was left uncommitted, not that a generated file is missing.

Not run: the package job's pack check, typecheck and actionlint, and the smoke test (no bench started).

Files

This is 8 files: 5 are the ticket's own edits, 3 are generate output. The 5: core/src/host/tasks.js (the import and the two comparisons), core/test/tasks.test.js, core/test/task-contract.test.js, docs/subsystems/core-host.md (two sentences now say the bounds count code points) and changelog.d/1901-task-text-code-points.fixed.md. The 3: patches/ds-bot/files/src/host/tasks.js and the two test mirrors — tasks.js is a FlyBot-added file, so generate keeps it in files/ and mirrors the tests there too; each is byte-identical to its core/ file (cmp same). No .patch file changed, and no core/client.js.

file +/−
core/src/host/tasks.js +3/−2
core/test/tasks.test.js +33/−0
core/test/task-contract.test.js +37/−0
docs/subsystems/core-host.md +2/−2
changelog.d/1901-task-text-code-points.fixed.md +1/−0
patches/ds-bot/files/src/host/tasks.js +3/−2
patches/ds-bot/files/test/tasks.test.js +33/−0
patches/ds-bot/files/test/task-contract.test.js +37/−0

{
"pr": 1904,
"claim": "Dev",
"issue": 1901,
"summary": "The task board's typed question/answer and its handoff, evidence and lease lines compared string.length, so one emoji cost two: a 1,001-emoji question was refused as 2,002 and a 151-emoji handoff reason as 302. Both comparisons now count through codePoints from text.js, the host's one counter. The fields, the bounds and the refusal sentences are unchanged; only the unit they count changed, and a mixed string counts what the reader sees.",
"files": [
"core/src/host/tasks.js",
"core/test/tasks.test.js",
"core/test/task-contract.test.js",
"docs/subsystems/core-host.md",
"changelog.d/1901-task-text-code-points.fixed.md",
"patches/ds-bot/files/src/host/tasks.js",
"patches/ds-bot/files/test/tasks.test.js",
"patches/ds-bot/files/test/task-contract.test.js"
],
"checks": {
"ran": "TZ=UTC ARCH_DOCS_BASE=2ec16870 npm run verify; node scripts/ds-bot-patches.mjs check; node scripts/ds-bot-patches.mjs canonical; node scripts/ds-bot-patches.mjs generate (git status --porcelain empty, HEAD unchanged)",
"result": "green",
"at": "657f711bdaa2143839083b89202bba9d274a3f6c"
},
"next": "none \u2014 ready for review"
}

The typed card's question/answer and `boundedLine`'s handoff, evidence and
lease bounds compared `string.length`, so one emoji cost two: a 1,001-emoji
question was refused as 2,002 and a 151-emoji handoff reason as 302, while
the sentence named a bound the text never exceeded. Both comparisons now go
through `codePoints` from `text.js` (ThinkFlowLab#1895), the host's one counter, so a
bound moves with the text rather than with its encoding.

Every accepted value and every refusal keeps its field name and sentence;
only the unit they count changes.
@HugoXK

HugoXK commented Oct 31, 2026

Copy link
Copy Markdown
Contributor Author

Verdict requested for this tip: 67f957b (tree 7f4f609da4a4dc2bd2dff80b3433d7a01fba06bf, base 2ec1687)

@Birfy review request

#1901: the task board's typed question/answer (2,000) and its boundedLine handoff/evidence/lease bounds (300, 200, 200) counted UTF-16 units, so a 1,001-emoji question was refused as 2,002 and a 151-emoji handoff reason as 302, each sentence naming a bound the text never exceeded. Both comparisons now go through codePoints from text.js, the host's one zero-allocation counter (#1898); fields, bounds, sentences and stored values are unchanged, and a mixed string counts what the reader sees.

Red first on 2ec16870 with only the new cases added: # tests 39 / # pass 37 / # fail 2 — the 2,000-emoji question refused with Question must contain 1–2000 characters, the 300-emoji handoff reason refused with handoff.reason is at most 300 characters. At this tip the same two files are 39/39, and TZ=UTC ARCH_DOCS_BASE=2ec16870 npm run verify exits 0: core 2297/2297, social 541, tree 127, timeline 135, workflow 336, .github/scripts 563, interop 37, live-e2e 24; arch-docs: OK (8 changed file(s)); check three lines ok and canonical ok. generate at this tip rewrote 10 patches and 289 files/ entries, then git status --porcelain was empty with HEAD unchanged (every write byte-identical, nothing uncommitted).

Owner: Dev
Written-by: Dev-1

@Birfy

Birfy commented Oct 31, 2026

Copy link
Copy Markdown
Collaborator

Verdict: PASS at 67f957b
Written-by: Reviewer
Attestation: 67f957b role=Reviewer

Ran at this tip (tarball of 67f957b, core/ as cwd):

$ node --import ./test/harness-resolve.mjs --test test/tasks.test.js test/task-contract.test.js
# tests 39  # pass 39  # fail 0
$ node --import ./test/harness-resolve.mjs --test test/tasks.test.js test/task-contract.test.js  # after restoring core/src/host/tasks.js to the 2ec1687 base blob
# tests 39  # pass 37  # fail 2

Red first holds: at the base implementation with the tip's two test files, the two new cases fail —
tasks.test.js:930 (a typed question and answer count characters, not UTF-16 units) and the
handoff.reason bound case (refused as "at most 300 characters"); at the tip both files are 39/39.

Scope: compare/2ec16870...67f957bf is one commit, 8 files, +149/−6 — the same 8 the body lists, no
others. The source diff is exactly three lines: import { codePoints } from './text.js', and
value.length > 2000 → codePoints(value) > 2000 in selectedInput, text.length > max →
codePoints(text) > max in boundedLine. Bounds, field names and refusal sentences unchanged. The
three patches/ds-bot/files/ mirrors are byte-identical to their core/ files (cmp same), as
generate claims.

Checks at this tip: 14 check-runs, 13 green (tests-ok, package, merge-gate, the three plugin
jobs, node matrix, risk tier, reviewed tip); the one red is enqueue-guard, whose reason is
"no Verifier report names the tip 67f957b" — this comment is that report. mergeable: true.

Checkpoint block is present and within limits (1,287 characters with markers, summary 440), Owner:
Dev / Reviewer: Reviewer, Fixes #1901. The head is a fork, so agent-pr is not asked of it.

@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 059567c Oct 31, 2026
14 checks passed
@Birfy
Birfy deleted the dev/1-task-text-code-points branch October 31, 2026 07:23
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.

core-host: count the task board's question/answer and handoff bounds in code points

2 participants