fix(gate,propose): okf build runs a real guard; a code fence declares no structure
Two defects reported from outside by claude-code-llm-wiki (order 20260915T202332Z-228694739), both reproduced against this tree before anything moved. F1 -- the packaged CLI never ran the guard. corpus.measure wired an unconditional approve-everything stub into process_inbox and 0 of 90 add_argument calls named a gate, so the one path most people use screened nothing while pyproject.toml made the guard a mandatory runtime dependency and the README recommended a composition the command line could not reach. --gate takes guard-trusted-source (default), guard-user-upload or none. corpus.resolve_gate is the one name->callable map, with the guard imported lazily so importing the package still does not pull it in; an unknown name RAISES rather than falling back, because a fallback reproduces the defect with an extra step. The gate's NAME goes into the section 9 log.md -- a stub is only dangerous when nothing downstream can see it -- and --gate none renders NOTHING WAS SCREENED. The default was chosen on a measurement: over the 453 concept bodies of the pinned reference bundle, PRESET_TRUSTED_SOURCE persists 453 of 453 and PRESET_USER_UPLOAD holds 1, costing that concept's whole source document. Neither tier waves anything through -- an invisible carrier and a CRITICAL finding fail secure at both. Door B's library default is UNCHANGED at PRESET_USER_UPLOAD: an inbox drop is an untrusted upload, an operator pointing this command at their own folder is not. The second tier ships as guard_adapter.inbox_gate_trusted_source, the three-line adapter that module's docstring already described, never a preset parameter. process_inbox(segmentations=..., gate=inbox_gate) now has a test. Before this, `grep -rl inbox_gate tests/` gave 1 file with 0 occurrences of `segment` -- the recommended composition was untested, which is how the defect survived. F2 -- a fenced code block declared structure. `# Use the opus[1m] alias` inside a ```bash fence became a level-1 ATX heading: the document was refused entirely where the line carried [ or ] (5 of 191 pages of the reporter's corpus), and the concept TITLE came from somebody's shell session on 62 of 191 (32.5 %). The fix is in the proposer and never in Door B's title rule -- that rule is right, and a heading that was never a heading is what has to stop being proposed. propose.fenced_lines is computed once per text and no rule reads a fenced line, including Arm D's outline RUN, which selects from the whole line list. Backtick and tilde fences, three leading spaces, a closing fence at least as long as its opener, and no backtick in a backtick fence's info string -- that last one keeps a line holding only `okf build` from silencing a document. MEASURED ON THE BYTES, and this is the number that decides: the 43-document reference corpus built atb6da09c(from git archive, never the editable tree) and rebuilt at the shipped defaults differ in log.md alone, by the one added bullet. 865 concept files on both sides, every concept byte-identical. Found by that control and NOT caused by this work: the pinned artifact K2-bundle-default-20260912 was written 2026-09-09, two days beforeed0418fchanged title: quoting, so it differs from what HEAD produces on 42 concept files. test_default_bundle_pin stays green because it pins the count and the hit@8 ranks, not the bytes. Re-pinning is the operator's call. Suite 1896 passed / 1 skipped (+27 from 1869). ruff, ruff format and mypy --strict clean. No version bump, no tag, no push. Report: docs/2026-09-15-f1-f2-gaten-og-kodefencen.md Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
b6da09cc97
commit
332961a19c
11 changed files with 1194 additions and 10 deletions
186
docs/2026-09-15-f1-f2-gaten-og-kodefencen.md
Normal file
186
docs/2026-09-15-f1-f2-gaten-og-kodefencen.md
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
# F1 and F2: the gate the CLI never ran, and the fence the proposer never saw
|
||||
|
||||
**Date:** 2026-09-15
|
||||
**Order:** `20260915T202332Z-228694739-from-claude-code-llm-wiki`
|
||||
**Base:** `b6da09c` (v0.9.0 + one unpushed commit)
|
||||
|
||||
Two defects, both reported from outside by `claude-code-llm-wiki` after a
|
||||
rebuild spike over 20 documents through okf v0.9.0 with guard v1.4.0, and both
|
||||
reproduced here against this repository's own code before a line moved. Neither
|
||||
was touched from outside; the reporter's full numbers live in their own repo at
|
||||
`e3107a4`, `docs/gjenoppbygging-2026-09-16.md` (private).
|
||||
|
||||
This document records what was measured, in the order it was measured, names
|
||||
the control that decided it, and names what the measurement does NOT cover.
|
||||
|
||||
## 0. The reporter's premises, re-measured here first
|
||||
|
||||
An incoming claim is a premise, not a fact, and both of F1's load-bearing ones
|
||||
were checked against this tree before anything was designed.
|
||||
|
||||
| Claim | Command | Result |
|
||||
| --- | --- | --- |
|
||||
| The stub is wired in unconditionally | read `corpus.py:250`, `:304` | confirmed; `measure()` took no gate parameter at all |
|
||||
| No CLI path reaches the real guard | `grep` every `add_argument` | confirmed; 0 named a gate |
|
||||
| One test file names `inbox_gate` | `grep -rl inbox_gate tests/` | **1** file, `tests/test_guard_adapter.py` |
|
||||
| That file never segments | `grep -c segment` on it | **0** |
|
||||
|
||||
The third and fourth are the ones that explain how F1 survived: the composition
|
||||
the README recommends — `process_inbox(segmentations=..., gate=inbox_gate)` —
|
||||
had no test anywhere in the suite.
|
||||
|
||||
## 1. F1: the exposure, measured before the default was chosen
|
||||
|
||||
The question that decides the fix is not "should the guard run" but "what does
|
||||
running it cost", and that is a number, not an opinion. It was taken over the
|
||||
453 concept bodies of the pinned reference bundle
|
||||
(`~/corpora/okf-telling-20260829/K2-bundle-default-20260912`, 39 source
|
||||
documents) — the exact granularity at which Door B gates in the segmented path.
|
||||
|
||||
| Preset | Persist (`warn`) | Held | Documents lost |
|
||||
| --- | --- | --- | --- |
|
||||
| `PRESET_TRUSTED_SOURCE` | **453 of 453** | 0 | 0 of 39 |
|
||||
| `PRESET_USER_UPLOAD` | 452 of 453 | 1 (`quarantine_review`, MEDIUM under low-trust) | 1 of 39 |
|
||||
|
||||
One refused segment body refuses the whole file, which is Door B's stated rule,
|
||||
so the held concept costs its whole source document.
|
||||
|
||||
**The operator chose `guard-trusted-source` as `okf build`'s default on that
|
||||
table**, 2026-09-15. The reasoning is not that the stricter tier is wrong but
|
||||
that the two tiers answer different questions: an inbox drop is an untrusted
|
||||
upload and Door B's library default stays `PRESET_USER_UPLOAD`; an operator
|
||||
pointing `okf build` at their own folder is the trusted-source case. Neither
|
||||
tier waves anything through — measured against guard 1.4.0, an invisible
|
||||
carrier and a CRITICAL finding are `fail_secure` at **both**.
|
||||
|
||||
## 2. F1: what shipped
|
||||
|
||||
- `guard_adapter.inbox_gate_trusted_source`, the three-line second adapter that
|
||||
module's own docstring already describes. **Not** a preset parameter on
|
||||
`inbox_gate` — the reporter explicitly did not ask for one, and the seam is
|
||||
what the injected gate exists for.
|
||||
- `corpus.GATE_NAMES` / `corpus.resolve_gate`: ONE place maps a name to a
|
||||
callable, with the guard imported lazily inside the branch so importing the
|
||||
package still does not pull the dependency in. **An unknown name raises**
|
||||
(`gate_invalid`) rather than resolving to the stub: a fallback would
|
||||
reproduce F1 with an extra step, the caller believing they had asked for the
|
||||
guard while the run approved everything.
|
||||
- `CorpusReport.gate` and a `**Gate**:` bullet in the section 9 `log.md`. This
|
||||
is the half of the defect that is not about the stub at all. A stub is only
|
||||
dangerous because nothing downstream can see it; the log already carries `N`
|
||||
precisely because it is the one fact about a run the bundle cannot otherwise
|
||||
recover, and which gate screened the bytes is the same class of fact.
|
||||
`--gate none` renders `NOTHING WAS SCREENED` in the artifact.
|
||||
- `okf build --gate` and the corpus harness's `--gate`, with the **same
|
||||
default**, deliberately: a test holds the two paths byte-equal, and two
|
||||
different defaults would make that equality depend on which command you ran.
|
||||
- `okf project` takes no `--gate`. It owns no flag that moves a bundle's bytes.
|
||||
|
||||
## 3. F2: the fence, reproduced and bounded
|
||||
|
||||
The reporter's 12-line repro reproduces exactly, at both levels:
|
||||
|
||||
find_candidates(REPRO)
|
||||
-> 'Tittel', 'Seksjon', 'Use the opus[1m] alias' <- three, the last fenced
|
||||
okf build ... -> substantive 0/1, inbox_title_invalid 1/1
|
||||
|
||||
The discriminating control is theirs and it holds: with `1m` for `[1m]` the
|
||||
document builds — and the concept is still filed under a line of somebody's
|
||||
shell session. **The brackets are why the document is refused; the fence is why
|
||||
the line was read as a heading at all**, and the second is the defect. Their
|
||||
blast radius on the Claude Code documentation: **62 of 191 pages (32.5 %)**
|
||||
carry `#` lines inside fences and get poisoned titles, **5 of 191** are refused
|
||||
outright.
|
||||
|
||||
**The fix is in the proposer, never in Door B's title rule.** The title rule is
|
||||
right — a title is rendered verbatim into `- [title](target)` and into
|
||||
line-oriented frontmatter, so `[` and `]` are met fail-fast and never repaired.
|
||||
What has to stop is proposing a heading that was never a heading.
|
||||
|
||||
Four details of CommonMark § 4.5 are load-bearing, and each is a way to get
|
||||
this wrong in the direction that removes REAL boundaries:
|
||||
|
||||
- up to three leading spaces still open a fence (a code block inside a list);
|
||||
- a backtick fence's info string may not contain a backtick, or a line holding
|
||||
only `` `okf build` `` opens a fence and silences the rest of the document;
|
||||
- a closing fence must be at least as long as its opener, or a four-backtick
|
||||
block quoting a three-backtick example closes on the quoted line;
|
||||
- an unclosed fence runs to the end, which is CommonMark's own rule — the
|
||||
alternative reads a truncated listing as a document full of headings.
|
||||
|
||||
No rule reads a fenced line now, including Arm D's outline run, which selects
|
||||
from the whole line list: filtering only at admission would leave a fenced
|
||||
install listing deciding WHICH run wins, moving a boundary in prose it never
|
||||
touched.
|
||||
|
||||
## 4. F2: exposure, on the bytes
|
||||
|
||||
| Set | Fences (``` or ~~~) |
|
||||
| --- | --- |
|
||||
| Pinned default bundle, 865 concept files | **0** |
|
||||
| `examples/`, `tests/fixtures/`, `skills/` reaching the proposer | **0** |
|
||||
|
||||
A rule that can only fire INSIDE a fence cannot have moved anything this
|
||||
repository has measured. That is why it lands unconditionally rather than as an
|
||||
eleventh flag: it is a defect, not a default move.
|
||||
|
||||
## 5. The control: both changes, on the 43-document reference corpus
|
||||
|
||||
The exposure numbers above are predictions from a scan. This is the measurement
|
||||
on the bytes, and it is the one that decides. Two full builds of the pinned
|
||||
reference corpus (`~/corpora/okf-telling-20260829/K2/trinn1`, N = 43,
|
||||
39 persisted / 4 coded rejections on both):
|
||||
|
||||
- **BASE**, commit `b6da09c`, built from `git archive` into a clean tree with
|
||||
`PYTHONPATH` — never the editable checkout, which reads `src/` live and would
|
||||
have measured the "before" run against the "after" code.
|
||||
- **AFTER**, this work, at the shipped defaults, no flag.
|
||||
|
||||
diff -rq K2-base K2-rebuild-gate
|
||||
-> Files K2-base/log.md and K2-rebuild-gate/log.md differ
|
||||
(nothing else)
|
||||
|
||||
diff -r ... | grep '^[<>]'
|
||||
-> * **Gate**: guard-trusted-source (llm-ingestion-guard, PRESET_TRUSTED_SOURCE). ...
|
||||
(one line, the added bullet)
|
||||
|
||||
**865 concept files on both sides, and every concept byte-identical.** The two
|
||||
changes together move exactly one line in one file, and it is the line they
|
||||
were meant to add. The 453-of-453 prediction held.
|
||||
|
||||
## 6. An unrelated finding, found by this control
|
||||
|
||||
The same run says something about the pinned artifact itself, and it is NOT
|
||||
caused by this work. Rebuilding the corpus at HEAD and diffing against
|
||||
`~/corpora/okf-telling-20260829/K2-bundle-default-20260912` gives **43 differing
|
||||
files**: 42 concept files differing ONLY in `title:` quoting
|
||||
(`title: **Avvik nr. 1**` against `title: "**Avvik nr. 1**"`), plus `log.md`.
|
||||
|
||||
That quoting is K3-22's, from `ed0418f` (2026-09-11 11:09). Every file in the
|
||||
pinned artifact was written **2026-09-09 21:38** — two days earlier, and not on
|
||||
the date its directory is named for. **The pinned artifact no longer equals what
|
||||
HEAD produces**, and `tests/test_default_bundle_pin.py` stays green because it
|
||||
pins the concept count and the per-row hit@8 ranks, not the bytes. Neither
|
||||
number moved, so nothing went red.
|
||||
|
||||
This is the operator's call, not this order's: re-pin the artifact at HEAD, or
|
||||
leave it and say in the pin what it is a pin OF. Recorded here rather than
|
||||
acted on.
|
||||
|
||||
## 7. What this does NOT cover
|
||||
|
||||
- **The trusted-source default is measured on ONE corpus, N = 1.** 453 of 453
|
||||
is a fact about this reference bundle, not a property of the tier. A folder
|
||||
whose documents carry findings the reference corpus does not will lose them,
|
||||
and that is the gate working, not a regression.
|
||||
- **F2's exposure denominator is ours, not the reporter's.** 0 of 865 says the
|
||||
fix cannot have moved OUR pinned artifact. It says nothing about how many
|
||||
boundaries the fix RECOVERS on a markdown corpus; that number belongs to the
|
||||
reporter's 191-page corpus and has not been re-measured here.
|
||||
- **`--gate` compares what a run DECLARES, not what a bundle contains.** A
|
||||
`log.md` naming `guard-trusted-source` is this library's statement about the
|
||||
run that produced the bundle. A consumer who did not run it takes that on the
|
||||
same trust as `N`.
|
||||
- **The reporter's `--unit-fold` finding is untouched, as they asked.** Their
|
||||
boundary recall was 42/799 with it on and 792/799 with it off; the default is
|
||||
tuned for tender documents, it is documented, and the flag works.
|
||||
Loading…
Add table
Add a link
Reference in a new issue