portfolio-optimiser/src/portfolio_optimiser
Kjell Tore Guttormsen 59f35fde22
fix(b-gate): every denominator answers to a source outside the gate, and a name is no longer a door [skip-docs]
Row 1's M was a curated list of 13 in the gate's OWN b_gate.json. It is now DERIVED: run_path_calls
reads what run.py::run_project actually calls (41 po-functions, re-counted here against PM's 39 —
the two that differ are generate_via_llm and fresh_workflow, held out because they need a chat
client). A call that is neither a declared step nor named-with-a-reason counts in the denominator
WITHOUT a door, so the number cannot shrink in silence: removing a step from the contract now
leaves N unchanged and names the orphan. The four outbox writers the run path uses and nobody had
declared (write_prepass, write_parse_failures, write_proposal_reviews, write_debate_tools) are
steps now; 28 calls are held out, each with its reason printed under the row.

A door must be REGISTERED and PROVEN. entry["kind"] is read (it was read 0 times before) and must
be one of three kinds the gate has code to verify: console-script in pyproject, module-main with
its own __main__ guard, subcommand registered in the module's argparse. On top of that every step
needs a named probe that calls the door and reads the artefact. The MCP sentence is struck from
EXTERNAL_DOOR: it claimed a capability with no code behind it.

Row 3 now scans the repo manifest (git ls-files, or the tree itself in an extract), not a hand
list of 11 roots: 512 published files instead of 433, so main.py, examples/, spikes/, contexts/,
CLAUDE.md and llms.txt are inside the guard for the first time. Three new patterns catch the
indirect invocations that walked past the old six — absolute path, list in a variable, constant,
shell string — 5 of 5 of PM's variants are refused now, with 0 false positives measured over the
whole surface. An empty surface is IKKE MAALT, not GREEN: the row demands a sentinel file and
prints the file count and the manifest it read.

Rows 4, 5 and 6 get sources for their denominators. Row 4 counts only checks whose named source
symbol resolves in the code. Row 5 counts each structural requirement on its own (2 profile
members + factory + seam + probe = 5) instead of collapsing three into one unit. Row 6's N comes
from the artefacts the contract names, and the attestation is VALIDATED: it must name the
contract's runbook, carry its sha256, say who ran it, and bear a real ISO date that is not in the
future — v1_gate's own date rule, reused, BOM tolerated as there.

Measured in a scratch clone (/tmp/claude-po/bgate-mut): 12 of 12 mutants felled, control 65 of 65.
All six of PM's broken attacks reproduced as refused, with the rc-0 control green.
No row got greener: 3 of 17 (was 3 of 13), 0 of 2, GREEN, 0 of 3, 4 of 5 (was 1 of 2), IKKE MAALT.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 19:40:42 +02:00
..
data feat(validator): anchor the deterministic gate to the project's real cost baseline (S4.0) 2026-08-03 17:19:31 +02:00
evals fix(b-gate): every denominator answers to a source outside the gate, and a name is no longer a door [skip-docs] 2026-09-19 19:40:42 +02:00
__init__.py feat(3): flaten sier sant om seg selv, og to gater måler at den fortsetter å gjøre det 2026-08-14 06:57:25 +02:00
backends.py feat(4b): AZURE-profilen leser miljøet sitt, ikke operatørens laptop 2026-08-13 22:27:21 +02:00
budget.py feat(budget): enforce a global portfolio token cap before the call, not after it (S3.4/F10) 2026-07-31 21:34:48 +02:00
contracts.py feat(hitl): ekspertdommen kan ikke oppstaa av stillhet (F2, ORDRE 20260825T214801Z) 2026-08-27 01:22:07 +02:00
costsim.py docs(s54): --report CLI surface + truthful costsim kost_mot_verdi seam note 2026-07-24 01:38:10 +02:00
datasource.py feat(prepass): the debate is handed the declared cut and the navigator tools are withdrawn [skip-docs] 2026-09-07 11:10:54 +02:00
dimension.py feat(s53): load_dimension fail-fast loader (MAF-free, mirrors load_goal_config) 2026-07-23 21:31:32 +02:00
explore.py feat(row6): a proposal whose approach declared no requirement is unsupported 2026-09-17 16:40:54 +02:00
frozen_bundles.json test(frozen-pin): an added or removed file, and listing order, pinned by tests; drift named apart from missing 2026-09-18 18:49:54 +02:00
frozen_bundles.py feat(frozen-bundles): the measurements read a pinned copy, not another repo's build dir 2026-09-18 03:22:22 +02:00
generate.py feat(p20): the requirement that is RIGHT, and a clause number that is not a price 2026-09-15 06:02:46 +02:00
hitl.py feat(explore): plan-reviewen kan besvares over DAGER (U12 + asynkron U13, rad 3) 2026-08-26 12:26:04 +02:00
hosting.py feat(row6): a proposal whose approach declared no requirement is unsupported 2026-09-17 16:40:54 +02:00
ingest.py feat(ingest): P2/S1.b — innholdsgaten står, rundt materialize og ikke i den 2026-08-09 22:58:00 +02:00
ingest_mcp.py fix(okf): close the inert ingest-stamp guard, then land okf 0.8.5 -- and read the block sources form all four bases actually write 2026-09-12 23:02:38 +02:00
ir.py feat(okf): derive a cost baseline from a priced schedule, or refuse 2026-09-03 03:15:39 +02:00
ledger.py fix(money): quantize NOK to øre in one order, from one source (kø-p) 2026-08-03 20:08:59 +02:00
mandate.py feat(row6): a proposal whose approach declared no requirement is unsupported 2026-09-17 16:40:54 +02:00
mcp_tools.py feat(provenance): a run records which external service it actually called 2026-08-05 21:37:29 +02:00
notify.py fix(s52): resolve ConsoleNotifier default stream at call time 2026-07-17 03:18:00 +02:00
okf.py feat(p22): a refusal names the DOCUMENTS when the ancestor has no subdirectories 2026-09-16 01:17:39 +02:00
outbox.py feat(row6): a proposal whose approach declared no requirement is unsupported 2026-09-17 16:40:54 +02:00
persona.py feat(persona): load the falsification skill from commons at call time 2026-09-02 21:31:32 +02:00
preflight.py fix(5): preflight kjenner samme endepunkt-variabler som kjørestien [skip-docs] 2026-08-14 10:48:45 +02:00
prepass.py feat(prepass): name the delivered excerpt nobody named, never refuse it [skip-docs] 2026-09-10 08:03:38 +02:00
proposal_review.py feat(major2): terminal proposal reviewer - text never repr, closed vocabulary, EOF is never a sign-off [skip-docs] 2026-09-05 06:59:54 +02:00
provenance.py feat(p19): a cost code must have a FORM where the input offers forms 2026-09-15 02:11:29 +02:00
reference_domain.py feat(hitl): ekspertdommen kan ikke oppstaa av stillhet (F2, ORDRE 20260825T214801Z) 2026-08-27 01:22:07 +02:00
retrieval.py fix(retrieval): fail-closed on uncanonicalisable paths (embedded null byte) 2026-07-21 08:00:11 +02:00
run.py feat(row6): a proposal whose approach declared no requirement is unsupported 2026-09-17 16:40:54 +02:00
semretrieval.py fix(semretrieval): refuse a non-finite embedding instead of scoring it (kø-(l)/S3.1 MINOR) 2026-08-03 21:48:50 +02:00
shared_root.py feat(4a): wheelen bærer shared/ som pakkede data — arbeidstreet er overstyringen 2026-08-13 21:13:08 +02:00
simulation.py feat(p19): a direction must NAME the requirement that binds it, and have READ it 2026-09-15 01:21:47 +02:00
stress.py feat(frozen-bundles): the measurements read a pinned copy, not another repo's build dir 2026-09-18 03:22:22 +02:00
tools.py feat(okf): adjudication_for names the unknown state and a tool carries it 2026-09-02 20:36:54 +02:00
tracing.py fix(maf): en vakt som gikk inert i STILLHET, funnet ved aa loefte pinnen (F15, ORDRE 20260829T155150Z) 2026-09-02 19:35:49 +02:00
validator.py feat(row6): a proposal whose approach declared no requirement is unsupported 2026-09-17 16:40:54 +02:00
value_report.py feat(s54): value_report text + deterministic JSON formatters 2026-07-24 01:29:31 +02:00
verdicts.py fix(v1-gate): row 2 binds a round to the run's own artefact family, and says out loud what it still cannot prove 2026-09-18 15:32:14 +02:00
workflow.py feat(fase3): make the maker-checker checker actually gate the reasoning 2026-06-30 07:24:30 +02:00