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:
parent
404bed26e2
commit
1c97e57212
3 changed files with 66 additions and 11 deletions
|
|
@ -1409,7 +1409,7 @@ No security functionality is reimplemented here.
|
|||
`--gate` takes `guard-trusted-source` (the default), `guard-user-upload` or
|
||||
`none`, and the name is written into the bundle's `log.md` either way, so a
|
||||
consumer holding a bundle can tell a screened one from an unscreened one
|
||||
without asking. `okf project` has no such flag and takes the default.
|
||||
without asking. `okf project` takes the same `--gate` with the same default: it is the one flag there that may move a bundle's bytes, and it is there because a command that cannot reach the gate screens by a default nothing said was a choice.
|
||||
|
||||
That paragraph is new, and the sentence above it was true of our own command
|
||||
until 2026-09-15: `okf build` injected a permissive stub and no argument
|
||||
|
|
|
|||
|
|
@ -1,10 +1,18 @@
|
|||
"""One folder of documents in, one questionable project out, in one command.
|
||||
|
||||
`okf project <folder>` is `okf build` followed by `okf skill`, plus the summary
|
||||
a person needs in order to know what they just got. It adds no rule of its own
|
||||
and owns no flag that changes a bundle's bytes: the build runs on THIS
|
||||
package's default, so a project bundle and an `okf build` bundle of the same
|
||||
folder at the same stamp are the same bytes.
|
||||
a person needs in order to know what they just got. It adds no rule of its
|
||||
own: the build runs on THIS package's defaults, so a project bundle and an
|
||||
`okf build` bundle of the same folder at the same stamp are the same bytes.
|
||||
|
||||
**One flag here DOES move a bundle's bytes, and it is stated rather than
|
||||
implied: `--gate`.** Every other flag `okf build` owns is deliberately absent,
|
||||
for the reason above -- two build paths would leave every measurement report
|
||||
pinned to a bundle nobody produces. The gate is different in kind: it is not a
|
||||
rule about how a document is cut but a screen about whether a document may be
|
||||
persisted at all, and a command that cannot reach it screens by the package
|
||||
default while saying nothing about it. The default is `okf build`'s default,
|
||||
so an unflagged `okf project` is the bytes it always was.
|
||||
|
||||
**Why a third command rather than a documented three-step.** The three-step
|
||||
existed and was measured on a reader: set `PYTHONPATH`, take a snapshot of a
|
||||
|
|
@ -34,8 +42,8 @@ import unicodedata
|
|||
from pathlib import Path
|
||||
|
||||
from . import consume, skill
|
||||
from .cli import DEFAULT_STAMP, build
|
||||
from .corpus import CorpusReport
|
||||
from .cli import DEFAULT_GATE, DEFAULT_STAMP, build
|
||||
from .corpus import GATE_NAMES, CorpusReport
|
||||
from .errors import IngestError
|
||||
from .inbox import walk_inbox
|
||||
from .profiles import SEGMENTED_OKF_V0_2
|
||||
|
|
@ -176,6 +184,7 @@ def create(
|
|||
out: Path,
|
||||
bundle_id: str | None = None,
|
||||
ingested_at: str = DEFAULT_STAMP,
|
||||
gate: str = DEFAULT_GATE,
|
||||
force: bool = False,
|
||||
) -> tuple[Path, Path, str]:
|
||||
"""Build the bundle, generate the skill, return both paths and the summary.
|
||||
|
|
@ -191,6 +200,7 @@ def create(
|
|||
ingested_at=ingested_at,
|
||||
bundle_id=identity,
|
||||
okf_version=PROJECT_OKF_VERSION,
|
||||
gate=gate,
|
||||
)
|
||||
if report.conservation_failed:
|
||||
raise IngestError(
|
||||
|
|
@ -230,6 +240,16 @@ def parse_args(argv: list[str] | None) -> argparse.Namespace:
|
|||
default=DEFAULT_STAMP,
|
||||
help=f"stamped verbatim. Default {DEFAULT_STAMP}: deterministic, never the clock",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--gate",
|
||||
choices=GATE_NAMES,
|
||||
default=DEFAULT_GATE,
|
||||
help=(
|
||||
"the persist gate every concept body passes before it is written, "
|
||||
f"as `okf build` takes it. Default {DEFAULT_GATE}. `none` screens "
|
||||
"NOTHING; the name is written into the bundle's log.md either way"
|
||||
),
|
||||
)
|
||||
parser.add_argument(
|
||||
"--force", action="store_true", help="replace an existing SKILL.md at the destination"
|
||||
)
|
||||
|
|
@ -248,6 +268,7 @@ def main(argv: list[str] | None = None) -> int:
|
|||
out=out,
|
||||
bundle_id=args.bundle_id,
|
||||
ingested_at=args.ingested_at,
|
||||
gate=args.gate,
|
||||
force=args.force,
|
||||
)
|
||||
except (IngestError, consume.ConsumeError, skill.SkillError) as exc:
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue