103 Commits

Author SHA1 Message Date
euvre
fe963e69ba fix: route TokenChunker delimiter_mode "one" to OneChunker in Go ingestion (#18121) 2026-08-14 16:55:32 +08:00
Jack
ae256bcf59 feat(chunker): add ManualChunker for manual-layout PDFs (#18272)
Add `ManualChunker`, the Go port of Python's `manual` doc-type chunk method (`rag/app/manual.py`). Like `GroupTitleChunker` it merges adjacent text records into heading-bounded groups, but it first re-sorts the records into physical reading order before grouping.
2026-08-14 16:55:08 +08:00
Jack
423c8489b5 fix(chunker): carry overlap-head PDF positions into new chunk (#18148) (#18227)
When `TokenChunker` starts a fresh chunk with an overlap prefix (Go `computeOverlapPrefix` / Python visible-text cut), the previous chunk's **tail PDF coordinates were dropped**. As a result, the overlap head of a PDF chunk is displayed but **not highlighted** — the highlight box is shifted/truncated relative to the displayed span (infiniflow/ragflow#18148).
2026-08-13 22:18:33 +08:00
Jin Hai
811f9dd0df Go: refactor embed and rerank interface (#18240)
Signed-off-by: Jin Hai <haijin.chn@gmail.com>
2026-08-13 22:14:23 +08:00
Jin Hai
e2acf3aebb Go: add error logs when fail to init infinity (#18235)
Signed-off-by: Jin Hai <haijin.chn@gmail.com>
2026-08-13 20:17:53 +08:00
Zhichang Yu
0784bef5b0 Port dataset-level structure merge for timeline/graph/mindmap (#18201)
Ports Python dataset-level structure aggregation (timeline, graph, mindmap) to Go. Mindmap emits entity/relation rows and merges like graph. Adds dataset_merge guard, engine gate, resolveDatasetStructureKind, kind-required structure graph GET/DELETE API, per-index task-id fields.
2026-08-13 18:37:47 +08:00
jay77721
0a4cc65db0 refactor(common): unify </think>-strip into shared StripThinkTrailing helper (#18179)
Collapse the nine identical **"cut through the last `</think>`"**
implementations — mirroring Python's `re.sub(r"^.*</think>", "", s,
re.DOTALL)` — into one shared helper `common.StripThinkTrailing`,
preventing future behavior drift between copies.
2026-08-13 13:56:56 +08:00
jay77721
e1b8366ecc refactor: remove resume (CV) workflow DSL template and Go handling code (#18177) 2026-08-13 10:47:37 +08:00
Zhichang Yu
6677f14bdf Port dataset nav and structure graph fixes to Go, merge agents list (#18183)
Fix compilation template config validation for JSONMap; merge template groups into agents list ordered by category/name; install nav service in ingestor; write readable nav cluster/doc names and emit nav_doc leaves;
port tree-to-graph projection and full document structure graph endpoint parity.
2026-08-12 22:46:24 +08:00
Jack
b505437db5 fix(chunker): honor bare (non-backtick) delimiters in TokenChunker (#17723) (#18182)
Go's `TokenChunker` previously ignored **bare (non-backtick)
delimiters** such as `::`, `.`, or `;`. `compileDelimPattern` compiled
them with `keepBare=false`, so a bare delimiter produced a `nil` pattern
and the payload was routed to the single-section merge that never split
on it — diverging from Python's `naive_merge`, which splits on bare
delimiters and then merges by token size.
2026-08-12 22:36:11 +08:00
jay77721
207e2eaf3b fix(ingestion): strip mid-text think blocks in extractor metadata path (#18175) 2026-08-12 19:39:49 +08:00
jay77721
91fd114783 fix(dao): honor tenant-configured context window override (D22/D23) (#18171)
Make `ResolveModelContentLength` honor the per-model custom **context window length** (`content_length`) — stored in the Python-legacy `tenant_model.extra["max_tokens"]` field, whose semantic meaning is the context window, NOT the generation cap — **before** any provider-catalog read, and remove the parallel service-layer implementation so every consumer shares one resolution path.
2026-08-12 19:32:37 +08:00
Jack
a4dbd898bb docs(ingestion): correct stale parser-component scope comment (#18147) 2026-08-12 17:58:05 +08:00
Zhichang Yu
c677e9af36 Port dataset-level knowledge compile to Go with variant dispatch (#18161)
Ports dataset-level knowledge compilation (tree/structure/wiki) to Go:
add compile-type variants to backlog events, route per-variant
dataset-level paths, move dataset-nav to the consumer, add structure
merge and per-variant clean, plus rebuild variant recovery.
2026-08-12 17:24:12 +08:00
jay77721
86b6f64abb fix(ingestion): fit extractor and tagger prompts to model context (#18095)
Trim Extractor call prompts and the automatic tagger prompt to the chat model's context window (`content_length`) before sending, so oversized chunks or tag files are trimmed instead of rejected by the provider with a context-length error.
2026-08-12 15:45:01 +08:00
taek105
492d6d81a9 fix: honor dataset language in Go vision dispatch (#17892)
### Summary

- Propagate the dataset language through Go DOCX, Markdown, PDF
figure-enhancement, and standalone-image vision paths.
- Explicitly render the shared figure prompt's `{{ language }}`
placeholder in Go.
- Use English when the dataset language is empty.
- Make the default standalone-image prompt request the dataset language
while preserving visible text in its original language.
- Add focused tests for caller propagation, language fallback, prompt
rendering, and prompt-cache isolation.
2026-08-11 22:18:04 +08:00
Zhichang Yu
d9ed14ce9c feat: wiki incremental Mode A/B with durable rewrite barrier (#18122)
Port the wiki_incremental dataset-level merge and make its rewrite
barrier durable and concurrency-safe. Wiki pages merge replace-only; the
barrier persists a monotonic numeric generation, and a scheduler-backed
per-dataset lock closes the cross-process TOCTOU window. Adds the
Compiler Plan toggle (frontend) with Mode A grouping.
2026-08-11 22:10:49 +08:00
Jack
a399b93143 Test(chunker): add golden parity harness, fixtures, and live Go<->Python tool (#17735)
Golden-parity test infrastructure for the **Go `TokenChunker` ↔ Python alignment**.
It runs the Go chunker over a committed case set (`testdata/parity/cases/`) and diffs each output against a captured Python golden (`testdata/parity/golden/`), honoring a `known_diffs.json` ratchet (`extra_fields` / `chunk_count` / `chunk_text`) so accepted divergences are tracked rather than silently widening.
2026-08-11 17:46:34 +08:00
Jin Hai
d7661b676d Go: fix context and warnings (#18097)
Signed-off-by: Jin Hai <haijin.chn@gmail.com>
2026-08-11 16:18:49 +08:00
Zhichang Yu
64533e5b5e Refactor splitByTokens and wire wiki incremental compile (#18083)
Port dataset-level wiki incremental compile and refactor splitByTokens
token budgeting. Includes replace-only wiki merge, KNN dedup routing,
and template/config wiring.
2026-08-11 13:46:55 +08:00
jay77721
9f0663d4d0 fix(ingestion): avoid duplicate chunk text injection in Extractor prompts (#18034) 2026-08-11 11:53:34 +08:00
Jack
dc73163908 Fix(tokenizer): pin alignment guards (#18011) 2026-08-11 10:09:07 +08:00
Jin Hai
c697fcff41 Go: fix go context (#18052)
Signed-off-by: Jin Hai <haijin.chn@gmail.com>
2026-08-10 18:04:17 +08:00
jay77721
ca9e62ee59 fix(ingestion): split Extractor call() into callRaw/callText/callStructured (#18038) 2026-08-10 17:18:24 +08:00
Jin Hai
c0bc146fcb Go: fix env variables (#18032)
Signed-off-by: Jin Hai <haijin.chn@gmail.com>
2026-08-10 14:37:05 +08:00
Jack
4b4a6e72f0 fix(chunker): unify TokenChunker merge and strip coord tags in Python JSON path (#18002)
Unifies the Go TokenChunker merge path on a single `mergeUnits` core and
fixes coordinate-tag drift in the Python JSON merge at `overlap > 0`.
Rebased on top of #17979 (delimiter_mode convergence).
2026-08-07 21:55:07 +08:00
Zhichang Yu
f12c0ec08a feat(knowledge_compile): materialize wiki page graph (wiki_entity/wiki_relation) (#17976)
Re-materialize wiki page graph from merged wiki_page rows after each
batch merge. Adds ProjectWikiGraph/DropWikiGraph, full page_type/slug
identity, delete-then-insert, tests.
2026-08-07 17:47:59 +08:00
Jack
1aa4e3c1f3 refactor(chunker): converge delimiter_mode to {delimiter, one}, drop token_size (#17979)
Converge `TokenChunker.delimiter_mode` from three values (`token_size`,
`delimiter`, `one`) to two (`delimiter`, `one`). The unified `delimiter`
mode now carries the old `token_size` semantics: when no active
(backtick) delimiter is present, text/JSON chunks are merged up to
`chunk_token_size`; when a backtick delimiter is present, the text is
split by it and not merged. `one` continues to be handled by the
separate `OneChunker`.
2026-08-07 16:11:42 +08:00
Jack
440fc937d0 fix(go): drop content-less chunks and set chunk_order_int unconditionally in Tokenizer (#17970) 2026-08-07 15:38:56 +08:00
Jack
57cacaddf9 refactor[Go]: remove chunker dead code & fix stale/false descriptors (L1/L2/L3) (#17960) 2026-08-07 11:07:45 +08:00
Jack
e587c56460 refactor(go): clean up TokenChunker delimiter routing and children appliers (#17954) 2026-08-07 11:07:21 +08:00
Jack
16ac94cff5 fix(parser/chunk): dedupe CompileDelimiterPatternList active entries (#17939)
Restore the deduplication that was dropped when #17926 was merged.
`CompileDelimiterPatternList` now keeps a `seen` set and collapses
equivalent active entries (both backtick-inner and bare) into a single
alternation. This PR also removes the dead code that the re-review
surfaced.
2026-08-07 10:06:04 +08:00
Jack
cdef804555 fix(chunker): decide token merge on running sum, not re-tokenized join (#17948) 2026-08-07 09:57:01 +08:00
Jack
60df86bfa2 fix(go): unify children delimiter pattern with backtick-strip + rune order (#17926)
`compileChildrenPattern` re-implemented the delimiter-list compile
inline with two divergences from the shared
`CompileDelimiterListPattern`:

- It never stripped backticks, so a backtick-wrapped
`children_delimiter` like `` `###` `` matched the **literal wrapped
token** rather than the inner `###`.
- It sorted by **byte length** instead of rune count (`sortSlice`), so
multi-byte delimiters could be ordered incorrectly and a longer
delimiter could fail to win over a shorter prefix.
2026-08-06 16:10:30 +08:00
Zhichang Yu
2e37997ab9 Go knowledge compiler with scheduler-driven dataset compilation (#17913)
Ports dataset knowledge compilation (wiki/graph/tree/mindmap) to the Go
scheduler with a status contract, aligns wiki storage/retrieval with
Python, sizes prompts by content_length, and resolves embedding batch
size from provider capability.
2026-08-06 15:54:00 +08:00
Jack
addc5acdc0 fix(tokenizer): align important_kwd split to English comma (DSL parity, A2) (#17928)
## Summary

Port the DSL tokenizer's `important_kwd` splitting into the Go
`Tokenizer` component so the indexed keyword array is byte-compatible
with the Python DSL pipeline and with the keyword-extraction prompt
contract.

- **Problem:** The Go component split `keywords` on the full ASCII+CJK
delimiter set (`utility.SplitKeywords`, regex `[,,;;、\r\n]+`), while the
DSL baseline `rag/flow/tokenizer/tokenizer.py:153` uses
`keywords.split(",")`, and `rag/prompts/keyword_prompt.md` instructs the
LLM to delimit keywords by **ENGLISH COMMA**. For a dataflow canvas that
includes the Tokenizer component, this divergence made Go's indexed
`important_kwd` differ from the Python-DSL-built index (CJK
commas/semicolons were split in Go but kept whole in Python).
- **Fix:** Use `strings.Split(kw, ",")` at `tokenizer.go:701`,
preserving empty middle elements to match Python's `"a,,b".split(",") ==
["a","","b"]`. The indexing fallback layer
(`internal/ingestion/task/indexdoc/process.go`) already mirrors the
Python multi-delimiter fallback (`dataflow_service.py:322`), so only the
component layer diverged and only it is changed.

## Test plan

- `TestTokenizerComponent_ImportantKwd_CommaOnly` (no build tag, default
`go test ./...`): switches the tokenizer to the identity engine (no CGo
pool needed) and asserts `"kw1,kw2;kw3,kw4"` → `["kw1","kw2;kw3,kw4"]`;
also asserts `important_tks` still tokenizes the full keyword string.
- `TestTokenizerComponent_Invoke_KeywordSplitCommaOnly` (`integration`
tag, real CGo analyzer): covers comma-split, CJK/semicolon-not-split,
and empty-middle preservation.
- Both tiers pass (unit `ok`, integration `ok`).

## Regression notes

- Intentional behavior change for canvases that include the Tokenizer
component: keywords containing `;`/`、`/newlines now stay as one keyword
(matching Python DSL) instead of being split. Re-indexing existing
Go-built data will change the `important_kwd` set — expected parity
cost, documented in code comments and commit message.
- Canvases without a Tokenizer component are unaffected (they hit the
unchanged multi-delimiter fallback).
- Other fields (`important_tks`, `questions`, `summary`, `text`) are
untouched; the `utility` import was removed cleanly.
2026-08-06 15:52:50 +08:00
Jack
109b74e410 refactor(go): remove dead atom-split helpers from TokenChunker (#17920)
## Summary
- Remove `splitOversizedUnit`, `splitAtomByTokenBudget` and `atomRE`
from `internal/ingestion/component/chunker/token.go`.
- Delete `split_oversized_guard_test.go` (added by #17740), which
guarded the removed atom-split behaviour.
- Drop the now-unused `wordCount`/`charCount` helpers from
`token_strict_cap_test.go`.
- Add `TestMergeByTokenSize_OversizedUnitStaysWhole` to pin the #17799
contract invariant (over-budget unit stays whole, never atom-split) on
the **text path**. The JSON path is already covered by
`TestMergeByTokenSizeFromJSON_OversizedUnitStaysWhole`.

## Why
The production merge path (`mergeByTokenSize` /
`mergeByTokenSizeFromJSON`) keeps over-budget units whole and relies on
the embedding/rerank layer to truncate them, per the TokenChunker
contract (#17799: remove atom-split, no hard_cap). The deleted helpers
implemented the opposite behaviour and had **no production caller**, so
they contradicted the contract and misled readers into thinking
atom-split was active.

## Parser vs chunker layering
Python's `_split_oversized_unit` lives at the **parser layer**
(pre-split before `naive_merge`), not in the chunker. Go's parser
backends are currently skeletons, so there is no parser-side equivalent
yet; if added later it belongs in `internal/parser/parser/*`, not the
chunker.

## Test plan
`bash build.sh --test ./internal/ingestion/component/chunker/...`
passes; the new text-path test passes and the orphaned atom-split tests
are gone.

## Changes
- 3 files changed, 32 insertions(+), 250 deletions(-)
2026-08-06 15:50:52 +08:00
Jack
e95c81326e test(chunker): lock non-text segments as standalone on merge (closes #17889) (#17896)
## Background

Issue #17889 asks that, when merging adjacent segments, the chunker
first
checks each segment's type and only merges **text** segments —
**table**,
**image**, and any other non-text type must each remain a standalone
chunk
and must never be merged with a neighbouring segment.

## Why this PR closes #17889 (no Go code change required)

After tracing the Go TokenChunker, the requirement is **already
satisfied**
on the structured (JSON / chunks) path. The type-aware rule is enforced
at
three layers in `internal/ingestion/component/chunker/`:

- `common.go:138` `itemDocType` derives the type from `doc_type_kwd`
(`"table"` -> `"table"`, `"image"` -> `"image"`, anything else ->
`"text"`).
It does **not** depend on the `ck_type` field being populated, so the
type
  survives even when only `doc_type_kwd` is set (e.g. upstream
  Title/Group/Hierarchy chunks).
- `token.go:756` `chunkFromItem` emits a non-text item as a single
standalone
  chunk before the merge loop ever runs.
- `token.go:1050` `mergeByTokenSizeFromJSON` forces any non-text chunk
standalone (`if ck.CKType != "text"`); and `token.go:991` starts a
*fresh*
  text chunk after a non-text chunk, so text on either side of a
  table/image is never merged across it.

The only path without type information is the raw markdown/text/html
string
path (`PayloadFormatMarkdown/Text/HTML`), where the input is by contract
an
untyped string and `applyChildrenDelim` hard-codes `CKType: "text"` so
merging is correct. There is no non-text segment to merge there, so this
is
out of #17889's scope (which is about the merge logic).

## Why the Python side is deferred

The Python `naive` parser path does not thread a `ck_type` through to
`merge_paragraphs` / `naive_merge` / `naive_merge_with_images`
(`rag/nlp/__init__.py`): its parsers emit flat `(text, pos)` sections
plus a
parallel `section_images` list, and the type-aware `_merge_cks` rule
(`rag/nlp/__init__.py:1749`) is only wired into the docx path.
Propagating
`ck_type` end-to-end across every Python parser is a large refactor, so
it is
intentionally **not** part of this PR. The Go engine is the active
ingestion
path, and it already honors the rule.

## This PR

Adds a regression-lock (characterization) test, not a fix:

- `TestTokenChunker_InvokeJSONPayload_KeepsNonTextStandalone` feeds a
  `[text, table, text, image, text]` structured payload and asserts it
  produces exactly five standalone chunks in the order
`text, table, text, image, text` — proving tables/images stay standalone
  and text on either side is not merged across them.

Verified green:

```
bash build.sh --test -run TestTokenChunker_InvokeJSONPayload_KeepsNonTextStandalone ./internal/ingestion/component/chunker/...
--- PASS: TestTokenChunker_InvokeJSONPayload_KeepsNonTextStandalone (0.07s)
```

## Related
- Issue #17889
- PR #17808 (chunking refactor, merged)
- Contract doc #17799
2026-08-06 15:50:14 +08:00
Jin Hai
cf13082a1a Revert "feat: Go knowledge compiler with scheduler-driven dataset compilation" (#17897)
Reverts infiniflow/ragflow#17881
2026-08-05 21:50:28 +08:00
Zhichang Yu
14b943a04a feat: Go knowledge compiler with scheduler-driven dataset compilation (#17881)
Ports the dataset knowledge compilation (wiki/graph/tree/mindmap) to the
Go scheduler with a status contract, aligns wiki storage/retrieval with
Python, and sizes prompts by content_length.
2026-08-05 20:00:42 +08:00
bigfish-emily
2403988b6d fix(chunker): drop dead atom-split path in mergeByTokenSizeFromJSON (#17873) 2026-08-05 19:58:51 +08:00
Jack
2fcc34904b fix(chunker): keep oversize text/markdown unit whole (OVER_CAP alignment) (#17854)
The Go `TokenChunker` text/markdown path (`mergeByTokenSize`)
unconditionally
called `splitOversizedUnit` on any unit that exceeded
`chunk_token_size`,
emitting Go-only sub-chunks. Python's `naive_merge`
(`_merge_paragraph_groups`,
`rag/nlp/__init__.py`) never atom-splits an oversize unit under either
`OVER_CAP` or `UNDER_CAP`: a paragraph larger than the budget becomes
its own
standalone chunk and the model layer truncates it later.

This aligns the text/markdown path with the **structured JSON path**
(`invokeJSONPayload` → `mergeByTokenSizeFromJSON(...,
subSplitOversize=false)`,
#17739). It completes the OVER_CAP alignment started in #17835.
2026-08-05 18:53:59 +08:00
Jack
0227b2684e fix(chunker): drop delimiter from chunk text on primary and children splits (#17868)
Go's `TokenChunker` kept the captured delimiter glued to the preceding
segment on **both** the primary (`chunkFromItem`) and secondary
(`children_delimiters`) split paths, while Python's reference
`token_chunker` drops it via `_split_text_by_pattern`
(`token_chunker.py:79-93`, used by both `_build_json_chunks` and
`_split_chunk_docs_by_children`). The divergence leaked the delimiter
into every emitted chunk's `text`.
2026-08-05 17:26:36 +08:00
Jin Hai
583ba3cb97 Go: add context for DB access (#17861)
Signed-off-by: Jin Hai <haijin.chn@gmail.com>
2026-08-05 16:22:36 +08:00
Jack
310275208b refactor(chunker): replace allowBoundaryOverflow bool with MergeStrategy enum (#17851)
## Summary

Follow-up to #17835 (merged). The OVER_CAP / UNDER_CAP merge strategy
was threaded through `mergeDecision` and `mergeByTokenSizeFromJSON` as
an inlined `allowBoundaryOverflow bool` derived from `!c.param.UnderCap`
at three call sites. This replaces that with a named
`schema.MergeStrategy` enum.

## Why

- The `!c.param.UnderCap` inversion was hand-written in three places, so
a future strategy addition could silently drift between the JSON path
(`invokeTextPayload` / `invokeJSONPayload`) and the text path
(`mergeByTokenSize`) — no compile error, and the existing tests don't
cover all three sites with both strategies.
- The strategy concept was never named; `allowBoundaryOverflow` (true =
OVER_CAP) is a double-negation of `UnderCap` and reads opaquely at the
5th positional argument.

## What changed

- Add `schema.MergeStrategy` (`MergeOverCap` / `MergeUnderCap`)
mirroring Python's `rag/nlp/__init__.py` `MergeStrategy`, so Go and
Python stay on the same vocabulary.
- Expose `TokenChunkerParam.MergeStrategy()` derived from the
wire-facing `UnderCap bool` (existing `"under_cap"` configs keep working
— no schema break).
- `mergeDecision` and `mergeByTokenSizeFromJSON` now take
`schema.MergeStrategy` instead of `allowBoundaryOverflow bool`; the
three call sites pass `c.param.MergeStrategy()` (no `!`).
- Tests updated to pass the enum; added a guard test for the `UnderCap`
-> `MergeStrategy` mapping and an end-to-end test for UNDER_CAP on the
JSON path.

No behavior change: default remains OVER_CAP, `under_cap=true` still
selects UNDER_CAP.

## Test plan

`bash build.sh --test ./internal/ingestion/component/chunker/...
./internal/ingestion/component/schema/...` — all green, including
`TestMergeByTokenSizeFromJSON_UnderCapNoOverflow`,
`TestMergeByTokenSize_UnderCapNoOverflow`,
`TestInvokeJSONPayload_UnderCapEndToEnd`, and
`TestTokenChunkerParamMergeStrategy`.

## Related issues

- Relates to #17835 — wired UNDER_CAP as a tested merge-strategy seam
(merged)
- Relates to #17799 — contract doc for token-chunker cap/delimiter
alignment
- Relates to #17808 — related chunker alignment work

---------

Co-authored-by: CodeBuddy <noreply@cnb.cool>
2026-08-05 14:59:53 +08:00
Jack
17bafb363f fix(chunker): align Go token merge with Python OVER_CAP and delimiter boundary (#17835)
Consolidates the Go chunker work that syncs `TokenChunker` with the Python reference (`rag/nlp.naive_merge` / `rag/flow/chunker/token_chunker.py`)
2026-08-05 13:58:45 +08:00
Jin Hai
c1f960cd47 Go: introduce content_length and max_output (#17807)
Signed-off-by: Jin Hai <haijin.chn@gmail.com>
2026-08-04 19:08:31 +08:00
Jin Hai
bf1e98e584 Remove docs (#17783)
Signed-off-by: Jin Hai <haijin.chn@gmail.com>
2026-08-04 14:24:01 +08:00
Jack
3fd4ead26b fix(chunker): JSON path merges globally and keeps over-budget items whole (#17739)
Fixes two TokenChunker **json-path** over-segmentation bugs that diverge
from Python's `rag/app` chunkers (tracked as `go_bug` known-diffs).
2026-08-03 22:17:59 +08:00
Jack
39ba8ae0bb test(chunker): self-contained guard for splitOversizedUnit running-sum flush (#17740)
## Summary

Adds a self-contained regression guard for `splitOversizedUnitWith`
after PR #17729
aligned it with Python's `rag/nlp._split_oversized_unit` running-sum
flush.

#17729 shipped a `slack=1` relaxation in `token_strict_cap_test.go` (the
oversized
unit is now sub-split with the same running-sum flush Python uses, which
can leave a
piece one token over the nominal budget due to cl100k non-additivity).
This PR adds
the missing positive proof that the sub-split boundaries are correct, so
the relaxed
assertion is no longer unguarded.

## Changes

- `split_oversized_guard_test.go` (new, self-contained — no harness
loader, no
  `testdata`; the Python oracle is inlined):
- `TestSplitOversizedUnitRunningSumMatchesPython`: asserts the exact
piece
boundaries (live tokenizer) match Python's `_split_oversized_unit`,
compensating
    the `slack=1` relaxation from #17729.
- `TestSplitOversizedUnitDeadTokenizerCollapses`: asserts a
zero-counting tokenizer
collapses the B1 paragraph into exactly one chunk, catching a silently
dead encoder
    that a non-empty-result check would miss.

## Notes

- Test function names are deliberately distinct from PR #17735's
`TestSplitOversizedUnitMatchesPython`, so the two PRs verify
independently and do not
  conflict at merge time.
- PR #17735 (golden parity harness) is intentionally left unchanged per
the split plan.

## Test plan

`bash build.sh --test ./internal/ingestion/component/chunker/` — green,
including both
new tests.

Co-authored-by: CodeBuddy <noreply@cnb.cool>
2026-08-03 22:15:41 +08:00