feat(prepass): --prepass-payload with six refusals by name, README and the hosted surface untouched

Steg 6 og 7 av planen. Flagget lastes fail-fast ved siden av `--mandate` (samme
try/except, saa manglende/ugyldig fil lander paa `run refused:` uten traceback) og
traades inn i BEGGE `run_project`-dispatchene -- dry-run og full kjoering. En egen arm
SPIONERER paa argumentet, ikke paa exit-koden: et flagg som parses, valideres og
droppes er F4-klassen, og de to utfallene er samme rc.

SEKS NEKTER, hver ved NAVN, hver med en rc-0-kontroll paa en argv som ellers ville
blitt AKSEPTERT:
- `report_forbidden` -- report-modus returnerer OVER hver dispatch, saa en utelatelse
  er et stille DROPP. Kontrollen bruker en JSON-ARRAY-ledger; et objekt ville gjort
  armen roed av feil grunn (maalt i oekt 89).
- `single_only` -- navngir `--portfolio`, ALDRI det delte `--prepass-payload`-tokenet:
  en droppet rad faller gjennom til `--bundle-dir`-kravet, hvis melding ogsaa navngir
  flagget, saa en arm paa det delte tokenet ville staatt groenn mot sin egen mutasjon.
- krever `--bundle-dir`; nektet med `--proposals-from-mandate` (returnerer over
  debatten, saa flagget ville vaert stille inert), med `--dimension-config` (pre-passet
  kuttet uten aa kjenne dimensjoner, saa aa aere skopet ville droppe utdrag
  deklarasjonen teller som LEVERT -- da er nevnerne feil for kjoeringen som publiserte
  dem) og med `--explore` (utforskningen leser HELE basen med de fire verktoeyene
  payloadet trekker, saa kjoeringen som helhet ville lest langt utenfor kuttet den
  erklaerer).

Blokka ligger paa FUNKSJONS-nivaa etter mode-dispatchen, aldri nestet under en annen
grens -- under en av dem ville en bar kombinasjon falt rett gjennom.

`hosting.py` er BEVISST URØRT (briefens non-goal, MAJOR-4/S7b-presedensen): feltet
kommer inn i ingen av de tre settene, saa den generiske `unknown field(s)`-400-en
svarer alt, og Fase 4es to halvdeler staar. Gatet av en testarm i stedet for en
redigering -- inkludert den negative halvdelen (hvert videresendt felt ER en
`run_project`-parameter, hvert konsumert er det ikke).

README-blokka navngir alle seks partnerne, uttrykker seg i kundevendt terminologi
(aldri "OKF bundle") og sier BEGGE aerlighets-grensene hoeyt: dette kjoeper et
DEKLARERT kutt, ikke en billigere kjoering; og en TOM leveranse er bevis for fravaer
mens en FULL ikke er bevis for tilstedevaerelse. Uttrekkeren tar BLOKKA (ikke en
delstreng over hele fila -- `--portfolio` og `--report` staar overalt), med
`--plan-review`-blokka som kjent-positiv kontroll.

1466 passed / 5 skipped (fra 1446/5, +20, 0 fjernet). ruff + mypy rene. Golden
`shasum -a 1` av INNHOLDET = ea8c534773acdbe41ae68f2c55724d69aaf8be4f, BYTE-UENDRET.

Co-Authored-By: Claude <claude-opus-5>
This commit is contained in:
Kjell Tore Guttormsen 2026-09-07 11:27:35 +02:00
commit ca98888358
3 changed files with 535 additions and 0 deletions

View file

@ -120,6 +120,7 @@ from portfolio_optimiser.validator import (
validate_proposal,
)
from portfolio_optimiser import hitl, okf, outbox, prepass
from portfolio_optimiser.prepass import load_prepass_payload
from portfolio_optimiser.semretrieval import (
SEMANTIC_WEIGHT_DEFAULT,
Embedder,
@ -2378,6 +2379,22 @@ def main(argv: list[str] | None = None) -> int:
"round trip is recorded in {run_id}-exploration.json, feedback verbatim. Input that ends "
"without an answer is an error, NEVER a sign-off",
)
parser.add_argument(
"--prepass-payload",
default=None,
metavar="FILE",
help="Hand the debate a DECLARED CUT of the knowledge base instead of the pointer and the "
"four navigator tools. FILE is one contract-conformant OKF consumption pre-pass payload "
"(okf-consumption/1): the delivered excerpts plus the three denominators — how many "
"concepts were considered, how many withheld, and by which rule. Every excerpt is verified "
"against the mounted document (identity, sha256, and the delivered text re-derived from "
"the base) before a single model call, so a payload cannot deliver bytes the base does not "
"hold; the verdict layer and the dimension scope are refused here exactly as the navigator "
"tools refuse them. The cut is recorded in {run_id}-prepass.json and printed. This makes "
"the run's cut DECLARED rather than emergent; it does not make it cheaper. Requires "
"--bundle-dir; refused with --portfolio, --report, --proposals-from-mandate, "
"--dimension-config and --explore.",
)
parser.add_argument(
"--proposal-review",
action="store_true",
@ -2634,6 +2651,10 @@ def main(argv: list[str] | None = None) -> int:
# MAJOR-2, and for the identical reason: report mode returns above the run dispatch, so
# an omission here is a SILENT DROP — the door would be accepted and never asked.
"--proposal-review": args.proposal_review,
# The pre-pass door, listed for the same reason as its neighbours: report mode returns
# ABOVE every dispatch, so an omission here is a silent DROP — the operator would be
# told nothing and the declared cut would simply never happen.
"--prepass-payload": args.prepass_payload is not None,
# The three U12 flags, listed for exactly that reason: report mode returns before the
# resume dispatch, so an omission here is a silent drop, not a refusal.
"--checkpoint-dir": args.checkpoint_dir is not None,
@ -2717,6 +2738,12 @@ def main(argv: list[str] | None = None) -> int:
# them apart. BY NAME, like its neighbours: falling through to a --bundle-dir
# requirement would tell the operator to add a flag this mode also refuses.
"--proposal-review": args.proposal_review,
# ONE payload is a cut of ONE base at ONE ref, and --bundle-dir (its only source of a
# base here) is already single-project-only. BY NAME rather than falling through to
# the --bundle-dir requirement below, whose message ALSO names --prepass-payload: an
# operator who wrote --portfolio --prepass-payload has to hear which of the two is
# wrong, and an arm asserting on the shared token could not tell the two apart.
"--prepass-payload": args.prepass_payload,
# And the asynchronous half of the same door, on the same side of the partition and by
# NAME for the same reason.
"--checkpoint-dir": args.checkpoint_dir,
@ -2767,6 +2794,54 @@ def main(argv: list[str] | None = None) -> int:
)
return 1
# The pre-pass door's four remaining refusals, at FUNCTION level and never nested under
# another flag's branch: under one, a bare combination would fall straight through to a
# dispatch that drops the payload in silence (the F4 class).
if not args.portfolio and args.prepass_payload is not None:
if args.bundle_dir is None:
print(
"run refused: --prepass-payload requires --bundle-dir (a payload is a cut OF a "
"knowledge base, and every excerpt in it is verified against the mounted "
"document before the run starts; the road path has no base to verify against)",
file=sys.stderr,
)
return 1
if args.proposals_from_mandate:
# That mode settles a commission against the derived schedule and RETURNS above the
# debate entirely, so a payload there would be accepted and silently inert.
print(
"run refused: --prepass-payload is not used by --proposals-from-mandate (that "
"mode builds candidates deterministically from the commission and the derived "
"schedule, and returns before any debate — so the cut would be declared and then "
"never read)",
file=sys.stderr,
)
return 1
if args.dimension_config is not None:
# The pre-pass has no dimension concept, so its CUT is unscoped. Discarding delivered
# excerpts here would make the payload's own denominators wrong for the run that
# published them, which is the denominator failure this whole seam exists to remove.
print(
"run refused: --prepass-payload and --dimension-config cannot be combined (the "
"pre-pass cut the base without knowing about dimensions, so honouring the scope "
"would drop excerpts its declaration counts as delivered — the denominators would "
"then be wrong for the run that published them; cut for the dimension instead, or "
"run unscoped)",
file=sys.stderr,
)
return 1
if args.explore:
# The exploration reads the WHOLE base with all four navigator tools and then shapes
# the mandate the debate is told it must not go outside. That is precisely the ground
# on which the debate's own tools are withdrawn, one caller over.
print(
"run refused: --prepass-payload and --explore cannot be combined (the exploration "
"navigates the whole knowledge base with the same four tools the payload "
"withdraws, so the run as a whole would read far outside the cut it declares)",
file=sys.stderr,
)
return 1
# S7b: the deterministic candidate source needs BOTH halves of its input, and each missing half
# is refused by its own name. Neither is inferable — a commission is what a person wrote, and
# the derived schedule is the only thing that can supply a quantity and a price — so a run that
@ -3127,6 +3202,18 @@ def main(argv: list[str] | None = None) -> int:
print(f"run refused: {exc}", file=sys.stderr)
return 1
# The declared cut, loaded fail-fast alongside the commission and for the same reason: a
# payload that cannot be read is not a run to start with a navigating debate instead. Missing,
# not JSON, or not the shape the models require — all three land on the refusal surface with
# rc 1 and no traceback, which is what ``PrepassRefused`` subclasses ``ValueError`` for.
prepass_payload: prepass.PrepassPayload | None = None
if args.prepass_payload is not None:
try:
prepass_payload = load_prepass_payload(args.prepass_payload)
except (FileNotFoundError, ValidationError, ValueError) as exc:
print(f"run refused: {exc}", file=sys.stderr)
return 1
# The egress config, loaded fail-fast alongside the commission. Degrading a broken one to "no
# external services" would make the announcement describe a run nobody configured, and a
# partially-parsed one could contact a subset nobody chose.
@ -3499,6 +3586,7 @@ def main(argv: list[str] | None = None) -> int:
),
outbox_dir=args.outbox_dir,
run_id=args.run_id,
prepass_payload=prepass_payload,
verdict_input=_verdict_input_from_args(args),
derive_cost_baseline=args.derive_cost_baseline,
mcp_servers=mcp_servers,
@ -3567,6 +3655,7 @@ def main(argv: list[str] | None = None) -> int:
),
outbox_dir=args.outbox_dir,
run_id=args.run_id,
prepass_payload=prepass_payload,
verdict_input=_verdict_input_from_args(args),
semantic_retrieval=args.semantic_retrieval,
derive_cost_baseline=args.derive_cost_baseline,