fix(consume): the compound-word miss is a degenerate signal's tie-break, behind a flag
The consumer's question about `vann- og frostsikring` in a subsea tunnel
delivered 0 of the 16 concepts covering it, best of them at fused rank 14.
Reproduced with the denominator, then decomposed per signal before anything
was built.
It is not a matcher miss. `normalise("vann- og frostsikring")` already returns
`('vann', 'frostsikring')` on HEAD, the prefix rule already bridges the
inflections, and the best covering concept already answers 7 of 7 question
tokens -- more than any delivered one. A tokeniser rule had nothing to widen.
It is the fusion, but not a weight. RRF ranks every concept in every signal,
including a signal that scored them all the same, and the declared
`(-score, concept_id)` tie-break then orders that group by id. On N500 the
document prior has TWO distinct values over 270 concepts, so the third signal
contributed alphabetical UUID order spread from 1/61 to 1/329 -- enough to put
a concept leading the body signal behind concepts sharing only `tunnel` and
`vann`.
`--tie-shared-rank` lets concepts a signal scores equally share that group's
first rank. The miss closes: best covering 14 -> 3, 2 of 16 delivered. OFF BY
DEFAULT, by the order's own rule: the three requirement lookups hold at rank 1
and the K2 digest holds, but hit@8 over the six published questions falls 5 of
6 to 4 of 6. Decomposed rather than guessed -- K2's prior is coarse (6 values
over 39 documents) rather than degenerate, and one gold sat early in its tie
group. That benefit was never a measurement, but it is a published row.
`--withheld-titles` gives each withheld entry the concept's title, so a reader
can see WHAT was withheld without reading the bundle. 11 lines of code; the
bytes are why it is off. It grows an N500 payload 37.9 % and takes the
629-concept K2 bundle's BOOKKEEPING to 122 704 B -- past the 120 000-byte limit
itself -- which would falsify the breaking point published in the tracked
`skills/okf-consume/SKILL.md` on the day it shipped.
Defaults measured, not asserted: six payload digests built from a frozen
`ff79cfa` (`git archive`, `__file__` checked) and from this tree with both
flags omitted are 6 of 6 identical, and `okf_skill.py` output is identical
apart from the paths each copy writes about itself. Contract checker exit 0 on
eight payloads, both values.
One known-positive did not reproduce and is reported rather than matched: the
order's S7 literal `2ae46f68`/169 573 B is stale by three excerpt-form commits;
HEAD measures `c759a657`/171 614 B.
Suite 1388 -> 1397. Report: docs/2026-09-08-rangeringsbom-sammensatte-ord.md
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
ff79cfa19b
commit
c3b645bccf
5 changed files with 630 additions and 6 deletions
290
docs/2026-09-08-rangeringsbom-sammensatte-ord.md
Normal file
290
docs/2026-09-08-rangeringsbom-sammensatte-ord.md
Normal file
|
|
@ -0,0 +1,290 @@
|
|||
# The compound-word ranking miss: a degenerate signal, not a matcher, 2026-09-08
|
||||
|
||||
A consumer asked the N500 bundle about `vann- og frostsikring` in a subsea
|
||||
tunnel and got eight excerpts, none of which covered the subject; the bundle
|
||||
covers it on 16 concepts. The consumer behaved as the contract asks — it
|
||||
answered `[sourced-not-sufficient]` and named its denominator — so the miss is
|
||||
the ranker's (`docs/2026-09-08-claude-code-skill-vilkaarlig-bundle.md` § 5).
|
||||
|
||||
The order proposed two sites for one rule: the tokeniser, or the fusion. The
|
||||
measurement chose for us, and it chose neither of the two mechanisms the order
|
||||
named. **The tokeniser already resolves the compound**, and **the fusion's
|
||||
defect is not a weight — it is the tie-break in a signal that separates
|
||||
nothing.**
|
||||
|
||||
---
|
||||
|
||||
## 0. What IS measured, and what is NOT
|
||||
|
||||
**Measured.** The miss reproduced with a command and a denominator; the three
|
||||
per-signal ranks of the covering concepts; the document prior's distinctness on
|
||||
two corpora; the rule's effect on four cases; every known-negative the order
|
||||
names, both values; six default payload digests against a frozen HEAD; the
|
||||
contract checker on eight payloads; the skill generator's output before and
|
||||
after; the full suite.
|
||||
|
||||
**Known-positive, run first.** Every figure this session builds on was
|
||||
reproduced on this machine before any after-column was read:
|
||||
|
||||
| # | known-positive | source | measured here |
|
||||
|---|---|---|---|
|
||||
| 1 | files matching `frostsikring` | C1 § 5 | **17**, case-insensitively, `index.md` among them |
|
||||
| 2 | files matching `vann- og frostsikring` | C1 § 5 | **10** |
|
||||
| 3 | files matching `undersjøisk` | C1 § 5 | **13** |
|
||||
| 4 | the miss itself: covering concepts in the cut | C1 § 5 | **0** |
|
||||
| 5 | hit@8 over the six published questions | 2026-09-07 § hit@k | **5 of 6**, ranks `1,1,1,1,1,–` |
|
||||
| 6 | the three requirement lookups, rank of 8 | O2c § 3 | **1, 1, 1** |
|
||||
| 7 | K2 bundle, whole tree | Arm C § "did not move" | **1108 files**, `9cd74519…` |
|
||||
| 8 | suite before the session | STATE (round 110) | **1388 passed** |
|
||||
|
||||
**One known-positive did NOT reproduce, and the deviation is the order's own
|
||||
literal.** The order names the S7 control payload as `2ae46f68…`, 169 573 B.
|
||||
Measured on frozen `ff79cfa` (`git archive` into a scratch tree, `__file__`
|
||||
asserted), that command produces **`c759a657…`, 171 614 B**. The literal is not
|
||||
wrong, it is *stale*: it was published at `116d3e1`, and `a37d5ce`, `17c49fc`
|
||||
and `c95d189` each widened the excerpt form afterwards. No document restates it.
|
||||
The row is therefore measured as **"byte-identical against HEAD"**, which is
|
||||
what it exists to prove, and the stale literal is reported rather than quietly
|
||||
matched.
|
||||
|
||||
**NOT measured.**
|
||||
|
||||
- **Whether a live model answers better.** This measures a ranking. Delivered
|
||||
is not read.
|
||||
- **`o200k_base` token counts.** No encoder is installed and the
|
||||
single-dependency rule does not admit one. Every figure is in `BUDGET_UNIT`
|
||||
plus `sha256`.
|
||||
- **Whether the rule helps a third corpus class.** Two were measured (the three
|
||||
N-bundles and K2). A corpus whose document prior is neither degenerate nor
|
||||
coarse is unmeasured, and the flag's default reflects that.
|
||||
|
||||
---
|
||||
|
||||
## 1. The reproduction, with the command and the numbers
|
||||
|
||||
The order asks for the question C1 § 5 used, quoted. **It is not in that
|
||||
document** — § 5 describes the question in prose ("about water and frost
|
||||
protection in a subsea tunnel") and quotes no line. The question below is the
|
||||
order's own wording, and it is marked as such rather than presented as C1's:
|
||||
|
||||
```
|
||||
python3 tools/okf_consume.py ~/repos/vegnormal-okf/build/ferdig/n500-2024 \
|
||||
--question "Hvilke krav gjelder vann- og frostsikring i undersjøisk tunnel?" \
|
||||
--out /tmp/o4-baseline.json
|
||||
```
|
||||
|
||||
270 concepts, 8 delivered, 262 withheld. **0 of the 16 covering concepts** — the
|
||||
concepts whose title or body says `frostsikring`, case-insensitively — is in the
|
||||
cut. The best of them fuses to **rank 14**.
|
||||
|
||||
The eight delivered, and the three covering concepts nearest the cut, with the
|
||||
per-signal ranks that decide the fusion:
|
||||
|
||||
| | fused | body | title+id | doc prior | lexical | title |
|
||||
|---|---|---|---|---|---|---|
|
||||
| delivered 1 | 1 | 17 | 6 | 4 | 6 | Krav 4.3—1 Tunnelportaler |
|
||||
| delivered 2 | 2 | 24 | 1 | 50 | 7 | Krav 3.2—2 Utslipp av vann fra tunnel |
|
||||
| delivered 3 | 3 | 138 | 5 | 1 | 4 | Krav 5.3.6—1 Fjernstyrte bommer |
|
||||
| **covering 1** | **14** | **6** | 33 | **130** | **7** | Krav 8.4.2—1 Frostisolering med PE-skum eller XPS |
|
||||
| covering 2 | 39 | 34 | 30 | 121 | 6 | Krav 9.3.2—2 Fundament, omfylling og frostsikring |
|
||||
| covering 3 | 44 | 25 | 101 | 63 | 5 | Krav 1.1—2 Generelle bestemmelser |
|
||||
|
||||
The best covering concept answers **7 of 7** question tokens — more than any
|
||||
delivered one — and leads the body signal at rank 6. It is not invisible to the
|
||||
matcher. It is behind on one signal: the document prior, at rank 130.
|
||||
|
||||
---
|
||||
|
||||
## 2. Why it is not a matcher miss (the order's alternative A, falsified)
|
||||
|
||||
Three measurements, each of which alone rules the tokeniser out as the site:
|
||||
|
||||
1. **The compound is already resolved.** `normalise("vann- og frostsikring")`
|
||||
returns `('vann', 'frostsikring')` on HEAD. The hyphen splits, `og` falls
|
||||
below `MIN_TOKEN_LENGTH`. There is no coordination left for a rule to undo.
|
||||
2. **The prefix rule already bridges the inflections.**
|
||||
`tokens_match('frostsikring', 'frostsikringen')`,
|
||||
`('vann', 'vannsikring')` and `('undersjøisk', 'undersjøiske')` are all
|
||||
`True` at `MIN_SHARED_PREFIX = 4`.
|
||||
3. **The covering concept already scores the maximum.** Its lexical overlap is
|
||||
7 of 7 question tokens. A rule widening the tokeniser cannot raise 7.
|
||||
|
||||
Recorded as a test rather than as a memory:
|
||||
`tests/test_tie_shared_rank.py::test_the_hyphen_and_og_coordination_is_already_resolved_by_the_tokeniser`,
|
||||
green on HEAD and labelled a characterisation.
|
||||
|
||||
One thing the same measurement DID surface, and it is not fixed here: the
|
||||
prefix rule over-matches in the other direction. `undersjøisk` matches `under`,
|
||||
so its document frequency is **159 of 270** — a term that should be one of the
|
||||
rarest in the question reads as one of the commonest. That belongs to the
|
||||
rarity weight (`--rarity-weight`, already shipped and already off), and this
|
||||
session was allowed one rule.
|
||||
|
||||
---
|
||||
|
||||
## 3. What it is: a signal that separates nothing still orders everything
|
||||
|
||||
RRF consumes ranks only, so `concept_scores` produces a rank for every concept
|
||||
in every signal — including a signal that gave them all the same score. The
|
||||
declared tie-break, `(-score, concept_id)`, then orders that group **by
|
||||
`concept_id`**, and the fusion reads the result as though it were a
|
||||
measurement.
|
||||
|
||||
On N500 the concept ids are UUIDs and the document prior is degenerate:
|
||||
|
||||
| corpus | documents | distinct prior values | concepts sharing one value |
|
||||
|---|---|---|---|
|
||||
| N500 | 2 | **2** | **269 of 270** |
|
||||
| K2 | 39 | 6 | — |
|
||||
|
||||
So on N500 the third signal contributed alphabetical UUID order, spread from
|
||||
`1/61` down to `1/329`. That spread is worth more than several positions on
|
||||
the two signals that did the measuring, and it is what put a concept answering
|
||||
7 of 7 tokens behind concepts sharing only `tunnel` and `vann`.
|
||||
|
||||
**The rule.** `tie_shared_rank`: every concept a signal scores equally takes
|
||||
that score group's **first** position, so a signal that separates nothing
|
||||
contributes an identical constant to every concept and orders none of them. It
|
||||
is general — no word list, no corpus constant, no new number except the group
|
||||
boundary the scores themselves define.
|
||||
|
||||
The first position rather than the middle, and that was measured too: the
|
||||
midpoint variant put the best covering concept at rank 5 where the first puts
|
||||
it at **3**, and neither changed the three lookups. First is kept because under
|
||||
it a non-separating signal contributes an identical constant, which is the
|
||||
whole claim; the midpoint still varies with the size of the group a concept
|
||||
lands in.
|
||||
|
||||
---
|
||||
|
||||
## 4. The known-negative table, both values
|
||||
|
||||
Flag `--tie-shared-rank`, off and on. Every row measured in one run,
|
||||
`/tmp/o4/kn.py`.
|
||||
|
||||
| row | off | on | verdict |
|
||||
|---|---|---|---|
|
||||
| N100 lookup `Krav 3.3.1-13`, rank of 8 | 1 | **1** | holds |
|
||||
| N200 lookup `Krav 2.9.2-12`, rank of 8 | 1 | **1** | holds |
|
||||
| N500 lookup `Krav 10.2-2`, rank of 8 | 1 | **1** | holds |
|
||||
| hit@8 over the six published questions | **5 of 6** | **4 of 6** | **FALLS** |
|
||||
| — per-question rank | `1,1,1,1,1,–` | `1,–,1,1,1,–` | row 2 lost |
|
||||
| S7 control question, default command | `c759a657…` 171 614 B | `5f4cd665…` 145 228 B | moves (on purpose) |
|
||||
| K2 bundle, whole tree | `9cd74519…` 1108 files | `9cd74519…` 1108 files | holds |
|
||||
| `okf_contract_check`, 8 payloads | exit 0 | exit 0 | holds |
|
||||
| **the O4 miss: best covering rank** | **14** | **3** | **closes** |
|
||||
| **the O4 miss: covering concepts in the cut** | **0 of 16** | **2 of 16** | **closes** |
|
||||
|
||||
The two concepts the rule delivers are `Krav 8.4.2—1 Frostisolering med PE-skum
|
||||
eller XPS` and `Krav 9.3.2—2 Fundament, omfylling og frostsikring for
|
||||
ledninger`.
|
||||
|
||||
**Default stays OFF**, by the order's own rule: it requires every row to hold
|
||||
*and* the miss to close. The miss closes; the hit@8 row does not.
|
||||
|
||||
### Why row 2 falls, decomposed rather than guessed
|
||||
|
||||
| row | gold concepts | best fused, off | best fused, on |
|
||||
|---|---|---|---|
|
||||
| 1 | 1 | 1 | 1 |
|
||||
| **2** | 49 | **1** | **28** |
|
||||
| 3 | 20 | 1 | 1 |
|
||||
| 4 | 43 | 1 | 1 |
|
||||
| 5 | 11 | 1 | 1 |
|
||||
| 6 | 5 | 11 (not delivered) | 91 (not delivered) |
|
||||
|
||||
K2's document prior is **not** degenerate: 6 distinct values over 39 documents.
|
||||
There the tie-break groups are large but the signal does separate, and row 2's
|
||||
gold happened to sit early in its group — that is, the noise was, on that one
|
||||
question, pointing the right way. Removing the noise removed a benefit that was
|
||||
never a measurement. That is an honest reason to keep the rule available and an
|
||||
equally honest reason not to make it the default: **a rule that is right in
|
||||
principle and loses a published row is a caller's choice, not a library's.**
|
||||
|
||||
Row 6 was already the known 1-of-6 shortfall and is not delivered either way.
|
||||
|
||||
---
|
||||
|
||||
## 5. The default did not move, measured against a frozen HEAD
|
||||
|
||||
Six payloads built from `git archive ff79cfa` into `/tmp/o4/head` (with
|
||||
`__file__` asserted to be under that tree, so an editable install cannot be
|
||||
read by accident) and from this working tree with both new flags omitted:
|
||||
|
||||
| payload | digest |
|
||||
|---|---|
|
||||
| N100 lookup | `a2a91d8b99157a0c`, 58 082 B |
|
||||
| N200 lookup | `0c828a5f8c432864`, 144 932 B |
|
||||
| N500 lookup | `ad2b35a556278553`, 41 364 B |
|
||||
| N500 O4 miss | `d4a75828c147d9e9`, 47 056 B |
|
||||
| S7 control | `c759a657dbb41d71`, 171 614 B |
|
||||
| K2 price question | `f17c8725ee6bbe1a`, 167 360 B |
|
||||
|
||||
**6 of 6 identical.** No consumer's bytes move because this session happened.
|
||||
|
||||
`tools/okf_skill.py` likewise: `SKILL.md` and `references/example-payload.json`
|
||||
are byte-identical apart from the absolute paths each copy writes about itself.
|
||||
|
||||
---
|
||||
|
||||
## 6. `withheld` names what was dropped — behind a flag, and here is why
|
||||
|
||||
C1 § 5's second observation: a `withheld` entry carries `concept_id` and `rule`
|
||||
and no title, so a reader told 262 concepts were withheld cannot tell *what*
|
||||
without reading the bundle, which § 2.2 forbids.
|
||||
|
||||
The code is cheap — 11 lines, well inside the order's 20-line bar. **The bytes
|
||||
are not**, and that is what moved the decision:
|
||||
|
||||
| | without title | with title |
|
||||
|---|---|---|
|
||||
| N500 payload | 41 364 B | 57 023 B (**+37.9 %**) |
|
||||
| N100 payload | 58 082 B | 88 251 B |
|
||||
| N200 payload | 144 932 B | 215 148 B |
|
||||
| K2 bookkeeping (629 concepts, everything not an excerpt) | ~75 KB | **122 704 B** |
|
||||
|
||||
The last row is the one that decided it. `skills/okf-consume/SKILL.md` — a
|
||||
**tracked** file — publishes the breaking point as "at 629 concepts it is ~75 KB
|
||||
… at roughly **8 000 concepts** the `withheld` list alone approaches the
|
||||
120 000-byte limit". With titles on by default that sentence becomes false
|
||||
immediately: at 629 concepts the bookkeeping is already **past** the limit. The
|
||||
generated skill's own figure moved from a 1206-concept ceiling to 762.
|
||||
|
||||
So `--withheld-titles`, off by default. On, every one of N500's 262 withheld
|
||||
entries carries its title; off, the entries are `{concept_id, rule}` exactly as
|
||||
before, and the eight contract-checked payloads all exit 0 either way.
|
||||
|
||||
---
|
||||
|
||||
## 7. What did NOT close
|
||||
|
||||
- **14 of the 16 covering concepts are still outside the cut** with the flag on.
|
||||
Rank 3 is not rank 1, and `k = 8` over 270 concepts is a narrow door.
|
||||
- **The over-matching prefix.** `undersjøisk` reads as 159-of-270 common. One
|
||||
rule per session; this was not it.
|
||||
- **hit@8 row 6** — unchanged, not delivered either way, both before and after.
|
||||
- **The default.** Two flags shipped this session and both are off. Whether
|
||||
either should be on is in the session's decision block, not decided here.
|
||||
|
||||
---
|
||||
|
||||
## 8. Tests and gates
|
||||
|
||||
New file `tests/test_tie_shared_rank.py`, six tests: two characterisations
|
||||
(green on HEAD, marked as such), and four that were **red on HEAD** — the
|
||||
ranking difference (`8` vs `1` on a synthetic bundle, two different numbers
|
||||
rather than one predicate two branches share), the byte-identity of the default,
|
||||
the delivered set under the flag, and the CLI defaults.
|
||||
|
||||
Three tests added to `tests/test_okf_consume.py` for `--withheld-titles`: the
|
||||
titled form, the untitled default (`set(entry) == {"concept_id", "rule"}`), and
|
||||
the byte cost. One existing assertion was widened from a whole-dict equality to
|
||||
the two fields it is actually about, so an unrelated widening of the withheld
|
||||
form cannot fail a cost-vocabulary test.
|
||||
|
||||
| gate | before | after |
|
||||
|---|---|---|
|
||||
| `pytest -q` | 1388 passed | **1397 passed** |
|
||||
| `ruff check src tests tools` | clean | clean |
|
||||
| `ruff format --check` | clean | clean (92 files) |
|
||||
| `mypy --strict src` | 17 files, clean | **17 files, clean** |
|
||||
Loading…
Add table
Add a link
Reference in a new issue