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
329
tests/test_cli_gate.py
Normal file
329
tests/test_cli_gate.py
Normal file
|
|
@ -0,0 +1,329 @@
|
|||
"""`okf build` runs a NAMED gate, and the bundle says which one (F1).
|
||||
|
||||
Reported from outside on 2026-09-15 by `claude-code-llm-wiki`, verified here
|
||||
before a line moved: `corpus.measure` wired an unconditional approve-everything
|
||||
stub (`corpus._gate`, returning the persist disposition for any text) into
|
||||
`process_inbox`, and no `add_argument` call anywhere in the package named a
|
||||
gate. So the only path people actually use -- `okf build`, and `okf project`
|
||||
through it -- screened nothing, while `pyproject.toml` makes the guard a
|
||||
MANDATORY runtime dependency and the README recommends exactly the composition
|
||||
that was never reachable. A permissive stub that approves everything is the
|
||||
shape this repository's own README names as the danger; it was on our own door.
|
||||
|
||||
Three separate things are pinned, because they fail in three different ways:
|
||||
|
||||
- **the gate runs.** A document the real guard refuses is not persisted. Under
|
||||
the stub it was, which is the defect itself; `CARRIER` is measured to fail
|
||||
secure in BOTH presets (an invisible carrier is an any-tier finding), so this
|
||||
test cannot pass by picking a lenient tier.
|
||||
- **the gate is named in the artifact.** SS 9's `log.md` already carries `N`
|
||||
because it is the one fact about a run the bundle cannot otherwise recover.
|
||||
Which gate screened the bytes is the same class of fact: without it a bundle
|
||||
built with `--gate none` is indistinguishable from one the guard cleared, and
|
||||
that indistinguishability is the whole trust trap, not the stub alone.
|
||||
- **the seam composes with segmentation.** Measured before the fix:
|
||||
`grep -rl inbox_gate tests/` gave ONE file (`tests/test_guard_adapter.py`)
|
||||
and `grep -c segment` in it gave 0, while the nine files passing
|
||||
`segmentation=`/`segmentations=` all injected a local warn-stub. The
|
||||
composition the README recommends -- `process_inbox(segmentations=...,
|
||||
gate=inbox_gate)` -- had no test at all, which is how the defect survived.
|
||||
|
||||
The default is `guard-trusted-source`, set by the operator 2026-09-15 on a
|
||||
measurement rather than on caution: over the 453 concept bodies of the pinned
|
||||
default bundle, `PRESET_TRUSTED_SOURCE` returns the persist disposition on
|
||||
453 of 453 (the artifact survives the move), while `PRESET_USER_UPLOAD` holds
|
||||
1 of them and would take one of the 39 source documents out of the bundle.
|
||||
Pointing `okf build` at your own folder is not an untrusted upload; Door B's
|
||||
library default stays `PRESET_USER_UPLOAD` because an inbox drop is.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from llm_ingestion_okf import cli, corpus
|
||||
from llm_ingestion_okf.inbox import process_inbox
|
||||
from llm_ingestion_okf.profiles import SEGMENTED_OKF_V0_2
|
||||
from llm_ingestion_okf.propose import build_plan
|
||||
from llm_ingestion_okf.segmentation import parse_segmentation_plan
|
||||
|
||||
BUNDLE_ID = "cli-gate-fixture"
|
||||
OKF_VERSION = "0.2"
|
||||
INGESTED_AT = "2026-09-15T00:00:00Z"
|
||||
|
||||
# Measured against the pinned guard 1.4.0, both presets, before this suite was
|
||||
# written: `fail_secure` under `PRESET_TRUSTED_SOURCE` AND under
|
||||
# `PRESET_USER_UPLOAD` ("any-tier: invisible carrier"). A fixture only one tier
|
||||
# refuses would make this suite pass for the wrong reason.
|
||||
CARRIER = "# Kostnader\n\nEn merknad med et nullbreddetegn i seg.\n"
|
||||
BENIGN = "# Kostnader\n\nKvartalstall for plattformgruppen, uten funn.\n"
|
||||
|
||||
|
||||
def _inbox(root: Path, documents: dict[str, str]) -> Path:
|
||||
inbox = root / "inbox"
|
||||
inbox.mkdir(parents=True, exist_ok=True)
|
||||
for name, body in documents.items():
|
||||
(inbox / name).write_text(body, encoding="utf-8", newline="")
|
||||
return inbox
|
||||
|
||||
|
||||
def _build(inbox: Path, bundle: Path, *extra: str) -> int:
|
||||
return cli.main(
|
||||
[
|
||||
"build",
|
||||
str(inbox),
|
||||
"--bundle",
|
||||
str(bundle),
|
||||
"--bundle-id",
|
||||
BUNDLE_ID,
|
||||
"--okf-version",
|
||||
OKF_VERSION,
|
||||
*extra,
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def _concepts(bundle: Path) -> list[Path]:
|
||||
return [
|
||||
path
|
||||
for path in sorted(bundle.rglob("*.md"))
|
||||
if path.name not in {"index.md", corpus.LOG_NAME}
|
||||
]
|
||||
|
||||
|
||||
# --- the gate runs ---------------------------------------------------------
|
||||
|
||||
|
||||
def test_build_refuses_a_document_the_real_guard_refuses(tmp_path: Path) -> None:
|
||||
"""The defect itself, as a red test: the stub persisted this document.
|
||||
|
||||
`CARRIER` carries a zero-width space inside a word. The guard refuses it in
|
||||
any trust tier, so a build that persists it has not consulted the guard --
|
||||
whatever the dependency table says.
|
||||
"""
|
||||
pytest.importorskip("llm_ingestion_guard")
|
||||
inbox = _inbox(tmp_path, {"carrier.md": CARRIER})
|
||||
bundle = tmp_path / "bundle"
|
||||
assert _build(inbox, bundle) == 0
|
||||
assert _concepts(bundle) == [], "a document the guard refuses must not reach the bundle"
|
||||
|
||||
|
||||
def test_build_persists_a_document_the_real_guard_clears(tmp_path: Path) -> None:
|
||||
"""The known-positive beside it: the gate must not simply refuse everything.
|
||||
|
||||
A gate that blocked every document would pass the test above and destroy
|
||||
the command. Both halves are needed, on the same default, in the same run.
|
||||
"""
|
||||
pytest.importorskip("llm_ingestion_guard")
|
||||
inbox = _inbox(tmp_path, {"benign.md": BENIGN})
|
||||
bundle = tmp_path / "bundle"
|
||||
assert _build(inbox, bundle) == 0
|
||||
assert _concepts(bundle), "a clean document must still reach the bundle"
|
||||
|
||||
|
||||
def test_gate_none_is_reachable_and_still_permissive(tmp_path: Path) -> None:
|
||||
"""`--gate none` keeps the old behaviour, deliberately and by name.
|
||||
|
||||
The stub is not deleted -- the corpus harness reproduces published numbers
|
||||
with it, and a caller measuring segmentation alone has a legitimate reason
|
||||
to take the gate out of the picture. What changes is that asking for it is
|
||||
now an act, and the bundle records it.
|
||||
"""
|
||||
inbox = _inbox(tmp_path, {"carrier.md": CARRIER})
|
||||
bundle = tmp_path / "bundle"
|
||||
assert _build(inbox, bundle, "--gate", "none") == 0
|
||||
assert _concepts(bundle), "--gate none is the documented permissive path"
|
||||
|
||||
|
||||
def test_user_upload_preset_is_reachable(tmp_path: Path) -> None:
|
||||
"""The stricter tier is a flag value, not a fork of the adapter.
|
||||
|
||||
Door B's own default stays `PRESET_USER_UPLOAD`; the CLI exposes it so a
|
||||
caller whose folder IS an untrusted drop does not have to write their own
|
||||
entry point to reach the tier this library already ships.
|
||||
"""
|
||||
pytest.importorskip("llm_ingestion_guard")
|
||||
inbox = _inbox(tmp_path, {"benign.md": BENIGN})
|
||||
bundle = tmp_path / "bundle"
|
||||
assert _build(inbox, bundle, "--gate", "guard-user-upload") == 0
|
||||
assert _concepts(bundle)
|
||||
|
||||
|
||||
def test_an_unknown_gate_name_is_refused(tmp_path: Path) -> None:
|
||||
"""A misspelled tier must not fall back to the permissive one.
|
||||
|
||||
Falling back would reproduce the defect with an extra step: the caller
|
||||
believes they asked for the guard, and the run approves everything.
|
||||
"""
|
||||
inbox = _inbox(tmp_path, {"benign.md": BENIGN})
|
||||
bundle = tmp_path / "bundle"
|
||||
with pytest.raises(SystemExit):
|
||||
_build(inbox, bundle, "--gate", "guard-trusted")
|
||||
assert not bundle.exists() or not _concepts(bundle)
|
||||
|
||||
|
||||
# --- the gate is named in the artifact -------------------------------------
|
||||
|
||||
|
||||
def test_the_log_names_the_gate_that_screened_the_bundle(tmp_path: Path) -> None:
|
||||
"""SS 9's log carries the gate for the same reason it carries `N`.
|
||||
|
||||
A consumer holding a bundle cannot otherwise tell a screened one from an
|
||||
unscreened one, and that is the trust trap in its pure form -- a stub is
|
||||
only dangerous because nothing downstream can see it.
|
||||
"""
|
||||
pytest.importorskip("llm_ingestion_guard")
|
||||
inbox = _inbox(tmp_path, {"benign.md": BENIGN})
|
||||
bundle = tmp_path / "bundle"
|
||||
assert _build(inbox, bundle) == 0
|
||||
log = (bundle / corpus.LOG_NAME).read_text(encoding="utf-8")
|
||||
assert corpus.GATE_GUARD_TRUSTED_SOURCE in log
|
||||
|
||||
|
||||
def test_the_log_says_when_nothing_was_screened(tmp_path: Path) -> None:
|
||||
"""`--gate none` must be legible as "nothing was screened", not as silence.
|
||||
|
||||
An omitted line reads as "no gate section in this log format"; a present
|
||||
line naming the permissive stub reads as what it is.
|
||||
"""
|
||||
inbox = _inbox(tmp_path, {"benign.md": BENIGN})
|
||||
bundle = tmp_path / "bundle"
|
||||
assert _build(inbox, bundle, "--gate", "none") == 0
|
||||
log = (bundle / corpus.LOG_NAME).read_text(encoding="utf-8")
|
||||
assert corpus.GATE_NONE in log
|
||||
assert "NOTHING WAS SCREENED" in log
|
||||
|
||||
|
||||
def test_every_gate_name_renders_a_log_line() -> None:
|
||||
"""No gate name may render a log without a gate line.
|
||||
|
||||
A name added later without a description would produce a bundle that
|
||||
declares its gate as the empty string, which reads as an absent field.
|
||||
"""
|
||||
for name in corpus.GATE_NAMES:
|
||||
report = corpus.CorpusReport(
|
||||
corpus="corpus",
|
||||
ingested_at=INGESTED_AT,
|
||||
n=0,
|
||||
extracted=0,
|
||||
gated=0,
|
||||
persisted=0,
|
||||
substantive=0,
|
||||
degenerate=0,
|
||||
rejected=0,
|
||||
seconds_total=0.0,
|
||||
converter_path="none",
|
||||
converter_version="none",
|
||||
codes=(),
|
||||
unaccounted=(),
|
||||
gate=name,
|
||||
)
|
||||
assert f"**Gate**: {name}" in report.render_log()
|
||||
|
||||
|
||||
# --- the seam composes with segmentation -----------------------------------
|
||||
|
||||
|
||||
def test_segmentation_and_the_real_guard_compose(tmp_path: Path) -> None:
|
||||
"""`process_inbox(segmentations=..., gate=inbox_gate)`, the README's own form.
|
||||
|
||||
Measured before this test existed: no test in the repository ran a real
|
||||
guard verdict and a segmentation plan in the same call. The composition is
|
||||
what the README recommends to consumers, so leaving it untested left the
|
||||
recommendation unverified -- and it is the exact call `okf build` now makes.
|
||||
"""
|
||||
guard_adapter = pytest.importorskip("llm_ingestion_okf.guard_adapter")
|
||||
document = (
|
||||
"# 1 Innledning\n\nDette dokumentet beskriver krav til seksjonering.\n\n"
|
||||
"# 2 Omfang\n\nOmfanget er hele anlegget og alle tilhoerende systemer.\n"
|
||||
)
|
||||
inbox = _inbox(tmp_path, {"alpha.md": document})
|
||||
bundle = tmp_path / "bundle"
|
||||
source = inbox / "alpha.md"
|
||||
plans = {
|
||||
"alpha.json": parse_segmentation_plan(
|
||||
build_plan(
|
||||
source,
|
||||
document,
|
||||
source.read_bytes(),
|
||||
okf_type="reference",
|
||||
proposed_at=INGESTED_AT,
|
||||
path_prefix="alpha",
|
||||
)
|
||||
)
|
||||
}
|
||||
result = process_inbox(
|
||||
inbox,
|
||||
bundle,
|
||||
INGESTED_AT,
|
||||
okf_type="reference",
|
||||
gate=guard_adapter.inbox_gate,
|
||||
profile=SEGMENTED_OKF_V0_2,
|
||||
root_frontmatter_values={"okf_version": OKF_VERSION, "bundle_id": BUNDLE_ID},
|
||||
segmentations=plans,
|
||||
)
|
||||
assert len(result.persisted) == 1
|
||||
assert not result.rejected and not result.quarantined
|
||||
assert len(_concepts(bundle)) > 1, "the plan's segments must be what the guard screened"
|
||||
|
||||
|
||||
def test_segmentation_and_the_real_guard_refuse_together(tmp_path: Path) -> None:
|
||||
"""The known-negative for the composition above, on the same call shape.
|
||||
|
||||
One refused segment body refuses the whole file, which is Door B's stated
|
||||
rule; without this half the test above would pass against a gate that never
|
||||
said no.
|
||||
"""
|
||||
guard_adapter = pytest.importorskip("llm_ingestion_okf.guard_adapter")
|
||||
document = (
|
||||
"# 1 Innledning\n\nDette dokumentet beskriver krav til seksjonering.\n\n"
|
||||
"# 2 Omfang\n\nEn merknad med et nullbreddetegn i seg.\n"
|
||||
)
|
||||
inbox = _inbox(tmp_path, {"alpha.md": document})
|
||||
bundle = tmp_path / "bundle"
|
||||
source = inbox / "alpha.md"
|
||||
plans = {
|
||||
"alpha.json": parse_segmentation_plan(
|
||||
build_plan(
|
||||
source,
|
||||
document,
|
||||
source.read_bytes(),
|
||||
okf_type="reference",
|
||||
proposed_at=INGESTED_AT,
|
||||
path_prefix="alpha",
|
||||
)
|
||||
)
|
||||
}
|
||||
result = process_inbox(
|
||||
inbox,
|
||||
bundle,
|
||||
INGESTED_AT,
|
||||
okf_type="reference",
|
||||
gate=guard_adapter.inbox_gate,
|
||||
profile=SEGMENTED_OKF_V0_2,
|
||||
root_frontmatter_values={"okf_version": OKF_VERSION, "bundle_id": BUNDLE_ID},
|
||||
segmentations=plans,
|
||||
)
|
||||
assert not result.persisted
|
||||
assert result.rejected, "a carrier in one segment refuses the file"
|
||||
|
||||
|
||||
# --- the adapter's second tier ---------------------------------------------
|
||||
|
||||
|
||||
def test_the_trusted_source_adapter_is_the_same_shape(tmp_path: Path) -> None:
|
||||
"""The second tier is a second three-line adapter, not a parameter.
|
||||
|
||||
`guard_adapter`'s own docstring says a caller needing another tier writes
|
||||
their own three-line adapter; this is that adapter, shipped, so the CLI
|
||||
does not have to reach past the seam into the guard's presets.
|
||||
"""
|
||||
guard_adapter = pytest.importorskip("llm_ingestion_okf.guard_adapter")
|
||||
clean = guard_adapter.inbox_gate_trusted_source(BENIGN)
|
||||
assert clean.disposition == "warn"
|
||||
assert clean.sanitized_text == BENIGN
|
||||
held = guard_adapter.inbox_gate_trusted_source(CARRIER)
|
||||
assert held.disposition != "warn", "an any-tier finding is refused at every tier"
|
||||
assert held.reasons, "the guard's audit trail is carried across verbatim"
|
||||
241
tests/test_code_fence.py
Normal file
241
tests/test_code_fence.py
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
"""A fenced code block declares no structure (F2).
|
||||
|
||||
Reported from outside on 2026-09-15 by `claude-code-llm-wiki` and reproduced
|
||||
here on `b6da09c` before a line moved. The proposer read every line of the
|
||||
extracted text with the same grammars, so a shell comment inside a
|
||||
```` ```bash ```` fence -- `# Use the opus[1m] alias` -- became a level-1 ATX
|
||||
heading. Two effects, and the SMALLER one is the visible one:
|
||||
|
||||
- **the document is refused entirely** when the fenced line carries `[` or `]`,
|
||||
because Door B validates a title fail-fast (`inbox.py`, `inbox_title_invalid`)
|
||||
and never repairs one. Measured on the reporter's corpus: 5 of 191 pages.
|
||||
- **the TITLE is poisoned** on every document where such a line survives
|
||||
validation. Measured: 62 of 191 pages (32.5 %) carry `#` lines inside fences.
|
||||
That is the larger effect and it is silent -- the bundle builds, and the
|
||||
concept is filed under a line of somebody's shell session.
|
||||
|
||||
The fix is in the PROPOSER and not in Door B's title rule: the title rule is
|
||||
right, and a heading that was never a heading is what has to stop being
|
||||
proposed. A fenced block is the one construct in markdown that says "the lines
|
||||
inside me are not markdown", so no rule may read them -- not ATX, not the
|
||||
numbered grammar, not a table row, not a bold title, not an outline run.
|
||||
|
||||
**Exposure, measured on the bytes before the change:** 0 of 865 concept files
|
||||
in the pinned default bundle (`K2-bundle-default-20260912`) and 0 of the
|
||||
shipped fixtures and goldens that reach the proposer contain a fence of either
|
||||
kind. A rule that only ever fires INSIDE a fence therefore cannot move a byte
|
||||
in anything this repository has measured, which is why it lands unconditionally
|
||||
rather than behind an eleventh flag: it is not a default move, it is a defect.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from llm_ingestion_okf import cli
|
||||
from llm_ingestion_okf.propose import RULE_HEADING, find_candidates
|
||||
|
||||
BUNDLE_ID = "code-fence-fixture"
|
||||
OKF_VERSION = "0.2"
|
||||
|
||||
# The reporter's minimal repro, verbatim in shape: a real heading, a real
|
||||
# subheading, and one fenced shell comment carrying brackets.
|
||||
REPRO = (
|
||||
"# Tittel\n"
|
||||
"Source: https://example.invalid/x\n"
|
||||
"\n"
|
||||
"Broedtekst.\n"
|
||||
"\n"
|
||||
"## Seksjon\n"
|
||||
"\n"
|
||||
"```bash\n"
|
||||
"# Use the opus[1m] alias\n"
|
||||
"claude --model opus\n"
|
||||
"```\n"
|
||||
"\n"
|
||||
"Mer tekst.\n"
|
||||
)
|
||||
|
||||
|
||||
def _titles(text: str, **flags: object) -> list[str]:
|
||||
return [candidate.title for candidate in find_candidates(text, **flags)] # type: ignore[arg-type]
|
||||
|
||||
|
||||
# --- the defect ------------------------------------------------------------
|
||||
|
||||
|
||||
def test_a_fenced_hash_line_is_not_a_heading() -> None:
|
||||
"""The defect itself: the fenced comment proposed a boundary."""
|
||||
titles = _titles(REPRO)
|
||||
assert "Use the opus[1m] alias" not in titles
|
||||
assert titles == ["Tittel", "Seksjon"]
|
||||
|
||||
|
||||
def test_the_document_still_builds_a_bundle(tmp_path: Path) -> None:
|
||||
"""End to end, on the reporter's own numbers: 0/1 substantive -> 1/1.
|
||||
|
||||
The proposer test above would pass against a fix that dropped the fenced
|
||||
line and also broke the door; this is the one that says the document
|
||||
arrives.
|
||||
"""
|
||||
inbox = tmp_path / "inbox"
|
||||
inbox.mkdir()
|
||||
(inbox / "doc.md").write_text(REPRO, encoding="utf-8", newline="")
|
||||
bundle = tmp_path / "bundle"
|
||||
assert (
|
||||
cli.main(
|
||||
[
|
||||
"build",
|
||||
str(inbox),
|
||||
"--bundle",
|
||||
str(bundle),
|
||||
"--bundle-id",
|
||||
BUNDLE_ID,
|
||||
"--okf-version",
|
||||
OKF_VERSION,
|
||||
]
|
||||
)
|
||||
== 0
|
||||
)
|
||||
concepts = [
|
||||
path for path in sorted(bundle.rglob("*.md")) if path.name not in {"index.md", "log.md"}
|
||||
]
|
||||
assert concepts, "the document was refused entirely before the fix"
|
||||
|
||||
|
||||
def test_the_discriminating_control_still_holds() -> None:
|
||||
"""The reporter's own control: brackets fell the door, the fence proposed it.
|
||||
|
||||
With `1m` instead of `[1m]` the document built before the fix, because the
|
||||
title validated -- and the concept was still filed under a shell comment.
|
||||
Both readings must end at the same place now: the line is not a heading.
|
||||
"""
|
||||
control = REPRO.replace("[1m]", "1m")
|
||||
assert "Use the opus 1m alias" not in _titles(control)
|
||||
assert _titles(control) == ["Tittel", "Seksjon"]
|
||||
|
||||
|
||||
# --- the fence grammar -----------------------------------------------------
|
||||
|
||||
|
||||
def test_a_tilde_fence_closes_the_same_way() -> None:
|
||||
"""`~~~` is a fence in CommonMark and reaches this library the same way.
|
||||
|
||||
Reading only backticks would leave the same defect behind a second spelling
|
||||
nothing here measures.
|
||||
"""
|
||||
text = "# Tittel\n\nTekst.\n\n~~~\n# ikke en overskrift\n~~~\n\nMer tekst.\n"
|
||||
assert _titles(text) == ["Tittel"]
|
||||
|
||||
|
||||
def test_a_longer_fence_is_not_closed_by_a_shorter_one() -> None:
|
||||
"""A closing fence must be at least as long as the opening one.
|
||||
|
||||
Otherwise a four-backtick block quoting a three-backtick example closes on
|
||||
the quoted line, and every line after it is read as markdown again.
|
||||
"""
|
||||
text = "# Tittel\n\n````\n```\n# ikke en overskrift\n```\n````\n\nTekst under.\n"
|
||||
assert _titles(text) == ["Tittel"]
|
||||
|
||||
|
||||
def test_an_unclosed_fence_runs_to_the_end_of_the_document() -> None:
|
||||
"""An unterminated fence swallows the rest, which is CommonMark's own rule.
|
||||
|
||||
The alternative -- treating the opener as ordinary text -- would read a
|
||||
truncated code listing as a document full of headings, which is the defect
|
||||
in its worst form rather than a repair of it.
|
||||
"""
|
||||
text = "# Tittel\n\nTekst.\n\n```\n# ikke en overskrift\n## heller ikke denne\n"
|
||||
assert _titles(text) == ["Tittel"]
|
||||
|
||||
|
||||
def test_an_indented_fence_marker_still_opens_a_fence() -> None:
|
||||
"""Up to three leading spaces still open a fence in CommonMark.
|
||||
|
||||
A nested list holding a code block is the ordinary way this appears in
|
||||
technical documentation, so a column-0-only rule would miss the common case.
|
||||
"""
|
||||
text = "# Tittel\n\nTekst.\n\n ```\n # ikke en overskrift\n ```\n\nSlutt.\n"
|
||||
assert _titles(text) == ["Tittel"]
|
||||
|
||||
|
||||
def test_a_fence_marker_inside_prose_does_not_open_a_fence() -> None:
|
||||
"""The known-negative: a line must BEGIN with the marker to be a fence.
|
||||
|
||||
Without this, a sentence mentioning ``` would silence every heading after
|
||||
it -- a rule that removes real boundaries instead of false ones.
|
||||
"""
|
||||
text = "# Tittel\n\nSkriv ``` for en kodeblokk.\n\n## Seksjon\n\nTekst.\n"
|
||||
assert _titles(text) == ["Tittel", "Seksjon"]
|
||||
|
||||
|
||||
def test_the_info_string_may_not_contain_a_backtick() -> None:
|
||||
"""CommonMark forbids a backtick in a backtick fence's info string.
|
||||
|
||||
A line of inline code alone on a line (`` `okf build` ``) otherwise reads as
|
||||
an opening fence and silences the rest of the document.
|
||||
"""
|
||||
text = "# Tittel\n\n`okf build`\n\n## Seksjon\n\nTekst.\n"
|
||||
assert _titles(text) == ["Tittel", "Seksjon"]
|
||||
|
||||
|
||||
# --- every other rule is fenced off too ------------------------------------
|
||||
|
||||
|
||||
def test_a_fenced_numbered_line_is_not_a_boundary() -> None:
|
||||
"""The numbered grammar reads the same lines and must stop at the fence."""
|
||||
text = "# Tittel\n\nTekst her.\n\n```\n1.2 Installer pakken foerst\n```\n\nSlutt.\n"
|
||||
assert _titles(text) == ["Tittel"]
|
||||
|
||||
|
||||
def test_a_fenced_numbered_line_does_not_feed_an_outline_run() -> None:
|
||||
"""Arm D selects its run from the whole text, so the fence binds there too.
|
||||
|
||||
Filtering only at admission would leave a fenced listing deciding WHICH run
|
||||
wins -- a boundary moved by lines that declare nothing.
|
||||
"""
|
||||
text = (
|
||||
"# Tittel\n"
|
||||
"\n"
|
||||
"Innledende tekst om emnet.\n"
|
||||
"\n"
|
||||
"```\n"
|
||||
"1 Installer\n"
|
||||
"2 Konfigurer\n"
|
||||
"3 Kjoer\n"
|
||||
"```\n"
|
||||
"\n"
|
||||
"Avsluttende tekst.\n"
|
||||
)
|
||||
assert _titles(text, outline_run=3) == ["Tittel"]
|
||||
|
||||
|
||||
def test_a_fenced_pipe_line_does_not_open_a_table_block() -> None:
|
||||
"""A piped line inside a fence is shell syntax, not a table row."""
|
||||
text = "# Tittel\n\nTekst her.\n\n```\nokf build | tee log\nokf check | wc -l\n```\n\nSlutt.\n"
|
||||
assert _titles(text) == ["Tittel"]
|
||||
|
||||
|
||||
def test_a_fenced_bold_line_is_not_a_bold_title() -> None:
|
||||
"""Round 10's rule reads the same lines and stops at the fence too."""
|
||||
text = "```\n**ikke en tittel**\n```\n\nTekst som staar under.\n"
|
||||
assert _titles(text, bold_title=True) == []
|
||||
|
||||
|
||||
# --- the exposure control --------------------------------------------------
|
||||
|
||||
|
||||
def test_a_document_without_a_fence_is_untouched() -> None:
|
||||
"""The control that says the rule fires only inside a fence.
|
||||
|
||||
This is the property the exposure measurement rests on: 0 of 865 pinned
|
||||
concept files carry a fence, so a rule that cannot fire outside one cannot
|
||||
have moved them.
|
||||
"""
|
||||
text = (
|
||||
"# 1 Innledning\n\nDette dokumentet beskriver krav til seksjonering.\n\n"
|
||||
"## 1.1 Omfang\n\nOmfanget er hele anlegget og alle systemer.\n"
|
||||
)
|
||||
candidates = find_candidates(text)
|
||||
assert [candidate.title for candidate in candidates] == ["1 Innledning", "1.1 Omfang"]
|
||||
assert all(candidate.rule == RULE_HEADING for candidate in candidates)
|
||||
|
|
@ -280,3 +280,40 @@ def test_the_threshold_document_carries_the_boundary_bar_and_its_single_corpus()
|
|||
assert f"`{BOUNDARY_THRESHOLD.metric}` | **2 759/2 761**" in text
|
||||
assert "1 corpus" in text
|
||||
assert BOUNDARY_THRESHOLD.corpora == 1
|
||||
|
||||
|
||||
# --- the CLI's default persist gate ----------------------------------------
|
||||
#
|
||||
# F1 (reported 2026-09-15): `okf build` injected a permissive stub and no
|
||||
# argument in the package named a gate, so the command screened nothing while
|
||||
# the guard was a mandatory runtime dependency and the README recommended a
|
||||
# composition the command line could not reach. What made that survivable for
|
||||
# months is that nothing tied the README's claim to the code's behaviour. This
|
||||
# does. A published promise is a test obligation.
|
||||
|
||||
_GATE_LINE = re.compile(r"^<!-- cli-default-gate: (.+) -->$", re.MULTILINE)
|
||||
|
||||
|
||||
def test_the_readme_names_the_gate_the_build_command_actually_defaults_to() -> None:
|
||||
from llm_ingestion_okf import cli
|
||||
|
||||
match = _GATE_LINE.search(README.read_text(encoding="utf-8"))
|
||||
assert match is not None, (
|
||||
"README.md carries no `<!-- cli-default-gate: ... -->` marker; without it "
|
||||
"the documented default can drift from the one the command applies, which "
|
||||
"is exactly how F1 survived"
|
||||
)
|
||||
assert match.group(1).strip() == cli.DEFAULT_GATE
|
||||
|
||||
|
||||
def test_the_readme_names_every_gate_the_command_accepts() -> None:
|
||||
"""A tier reachable but undocumented is a tier nobody can choose.
|
||||
|
||||
The stricter one matters most: a caller whose folder IS an untrusted drop
|
||||
has to be able to find `guard-user-upload` without reading the source.
|
||||
"""
|
||||
from llm_ingestion_okf.corpus import GATE_NAMES
|
||||
|
||||
text = README.read_text(encoding="utf-8")
|
||||
for name in GATE_NAMES:
|
||||
assert f"`{name}`" in text, f"README does not name the gate {name}"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue