feat(project): --gate reaches the build, and the bundle says which one

`project.create` called `build()` with five keyword arguments and no `gate=`,
so `okf project` screened by the package default and nothing anywhere said
that was a choice rather than the only option. The gate is not a segmentation
rule -- it is a screen about whether a document may be persisted at all -- so
it is the one flag this command owns that may move a bundle's bytes, and the
module docstring, the README paragraph and the test file's own claim are
corrected rather than left standing beside the new flag.

The default is `okf build`'s default, so an unflagged `okf project` is the
bytes it always was; the byte-equality invariant against `okf build` holds
unchanged. An unknown gate name still raises `gate_invalid` rather than
falling back -- a fallback reproduces the defect with an extra step.

Tests first, all three red: the gate name read back out of the bundle's own
`log.md` (not out of the call), the flag parsed by this command's parser, and
the unknown name refused.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-20 23:08:50 +02:00
commit 1c97e57212
3 changed files with 66 additions and 11 deletions

View file

@ -1,9 +1,10 @@
"""`okf project`: one folder in, one bundle plus one skill out.
The command adds no rule and owns no flag that changes a bundle's bytes, so
these tests are mostly about that: the project bundle must be the SAME bytes
`okf build` writes for the same folder at the same stamp, or there are two
build paths and the reports are pinned to one of them.
The command adds no rule, and owns exactly ONE flag that changes a bundle's
bytes -- `--gate`, which is a screen and not a segmentation rule. These tests
are mostly about the rest: the project bundle must be the SAME bytes `okf
build` writes for the same folder at the same stamp, or there are two build
paths and the reports are pinned to one of them.
"""
from __future__ import annotations
@ -267,3 +268,36 @@ def test_a_sheet_reaches_the_project_bundle_as_it_reaches_the_build_command(
== 0
)
assert tree(bundle) == tree(reference)
def test_the_gate_reaches_the_build_and_the_bundle_says_which_one(
folder: Path, tmp_path: Path
) -> None:
"""`okf project --gate` is the one flag here that MAY move a bundle's bytes.
`project.create` called `build()` with five keyword arguments and no
`gate=`, so the gate name was unreachable from this command: every project
bundle was screened by the package default and nothing said so was a
choice. The gate's name is written into the bundle's own `log.md`, so the
check is the bundle's, not the call's.
"""
out = tmp_path / "project"
bundle, _, _ = project.create(folder, out=out, gate="none")
log = (bundle / "log.md").read_text(encoding="utf-8")
assert "NOTHING WAS SCREENED" in log
default = tmp_path / "default"
other, _, _ = project.create(folder, out=default)
assert "NOTHING WAS SCREENED" not in (other / "log.md").read_text(encoding="utf-8")
def test_the_gate_flag_is_parsed_by_the_project_command(folder: Path, tmp_path: Path) -> None:
args = project.parse_args([str(folder), "--out", str(tmp_path), "--gate", "none"])
assert args.gate == "none"
def test_an_unknown_gate_name_does_not_start_the_run(folder: Path, tmp_path: Path) -> None:
"""A fallback would reproduce the defect the gate was added to close."""
with pytest.raises(IngestError) as caught:
project.create(folder, out=tmp_path / "project", gate="guard-nonesuch")
assert caught.value.code == "gate_invalid"