fix(b-gate): the binding measures an execution, and the row-3 sentence stops counting doors
Third repair of the same failure, and the first that does not read a NAME. The judge measured
row 1 to 17 of 17 with 17 one-line probes and a door module without a single import: "the probe
touches the door" was still a text check, one notch further in.
What is measured now, in the probe's own source: an EXECUTION SITE (a process starter carrying
the door's name in its arguments, or the entry imported from the door's module and called), and
an assert that is DATA-DEPENDENT on that call. Dead code is pruned first -- a call in a branch
that never runs, or after a return, is no execution. The honest forms the suite already uses
keep counting: the command built in a variable first, and the subprocess started in a helper
that returns it.
All seven forms the judge measured (a1, a2, b1, b2, c1, c2 + the one-liner recipe) now give
0 of 1, each with an rc-0 control in the same setup.
[mutation-ok] -- no guard is switched off. The two `if False:` lines in this diff are the
judge's b1 probe form planted as test DATA (a Python string written into a tmp_path file the
gate only PARSES, never runs) and the docstring naming why dead code is pruned. The commit adds
the rule that makes `if False:` around a door call stop counting.
Naming the step becomes a DISCRIMINATOR, as a class: it is required only when more than one step
stands behind the same door. Driving a door with a sole step IS driving that step -- which is why
`gate` (a real end-to-end door probe against v1_gate) was rejected on a name technicality. Row 1:
0 -> 1 of 17. For a subcommand door the command name must stand in what was actually executed,
and the CLI itself must be registered or `-m`-runnable: an add_parser in a module nobody can
start is no door.
held_out: a reason must be named prose. "-", "todo", "x", "." were all accepted as a reason by
`.strip()`; measured against the contract's own 28 reasons (shortest: 30 chars, three words).
Row 3: the five forms the checkpoint measured as open AND cheap to close are closed -- importlib
for both SDK names, deno/npm exec/yarn dlx, and the official TypeScript SDK. 12 of 12 -> 15 of 15,
still 0 hits over 512 published files. The limit sentence stops enumerating what remains ("EXACTLY
TWO", then "THREE" -- both falsified by the first new measurement) and says what the guard IS.
N7 stays open in the gate and is closed in the suite: a deleted off-path step still shrinks the
denominator silently there, so the four are written out in an arm that goes red when one goes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
42720faa03
commit
e47be68b57
3 changed files with 786 additions and 144 deletions
|
|
@ -520,6 +520,27 @@
|
||||||
"pattern_b64": "X19pbXBvcnRfX1woXHMqWyInXShjbGF1ZGVfYWdlbnRfc2RrfGNsYXVkZV9jb2RlX3Nka3xhbnRocm9waWMpWyInXQ==",
|
"pattern_b64": "X19pbXBvcnRfX1woXHMqWyInXShjbGF1ZGVfYWdlbnRfc2RrfGNsYXVkZV9jb2RlX3Nka3xhbnRocm9waWMpWyInXQ==",
|
||||||
"known_positive_b64": "X19pbXBvcnRfXygiY2xhdWRlX2FnZW50X3NkayIp",
|
"known_positive_b64": "X19pbXBvcnRfXygiY2xhdWRlX2FnZW50X3NkayIp",
|
||||||
"known_negative_b64": "X19pbXBvcnRfXygiYWdlbnRfZnJhbWV3b3JrIik="
|
"known_negative_b64": "X19pbXBvcnRfXygiYWdlbnRfZnJhbWV3b3JrIik="
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dynamisk-import-modul",
|
||||||
|
"label": "importlib mot en Claude-klient",
|
||||||
|
"pattern_b64": "aW1wb3J0bGliXC5pbXBvcnRfbW9kdWxlXChccypbIlwnXShjbGF1ZGVfYWdlbnRfc2RrfGNsYXVkZV9jb2RlX3Nka3xhbnRocm9waWMpWyJcJ10=",
|
||||||
|
"known_positive_b64": "aW1wb3J0bGliLmltcG9ydF9tb2R1bGUoImNsYXVkZV9hZ2VudF9zZGsiKQ==",
|
||||||
|
"known_negative_b64": "aW1wb3J0bGliLmltcG9ydF9tb2R1bGUoImFnZW50X2ZyYW1ld29yayIp"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "node-runner-indirekte",
|
||||||
|
"label": "claude-code startet av deno, npm exec, yarn dlx eller pnpm exec",
|
||||||
|
"pattern_b64": "KGRlbm9ccytydW5cYlteXG5dKj9ucG06fG5wbVxzK2V4ZWNcYlteXG5dKj98eWFyblxzK2RseFxiW15cbl0qP3xwbnBtXHMrZXhlY1xiW15cbl0qPykoQGFudGhyb3BpYy1haS8pP2NsYXVkZS1jb2RlXGI=",
|
||||||
|
"known_positive_b64": "ZGVubyBydW4gLUEgbnBtOkBhbnRocm9waWMtYWkvY2xhdWRlLWNvZGUgLXAgIngi",
|
||||||
|
"known_negative_b64": "bnBtIGV4ZWMgcHJldHRpZXIgLS0gLS13cml0ZSAu"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "sdk-ts-import",
|
||||||
|
"label": "import av den offisielle TypeScript-SDK-en",
|
||||||
|
"pattern_b64": "KGltcG9ydHxyZXF1aXJlKVxiW15cbl0qWyJcJ11AYW50aHJvcGljLWFpLyhjbGF1ZGUtYWdlbnQtc2RrfGNsYXVkZS1jb2RlLXNka3xjbGF1ZGUtY29kZSlbIlwnXQ==",
|
||||||
|
"known_positive_b64": "aW1wb3J0IHsgcXVlcnkgfSBmcm9tICJAYW50aHJvcGljLWFpL2NsYXVkZS1hZ2VudC1zZGsiOw==",
|
||||||
|
"known_negative_b64": "aW1wb3J0IHsgeiB9IGZyb20gInpvZCI7"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -83,14 +83,19 @@ EXTERNAL_DOOR = (
|
||||||
"tools Claude Code kan bruke» — en kommando er nettopp det. Gaten teller tre arter den kan "
|
"tools Claude Code kan bruke» — en kommando er nettopp det. Gaten teller tre arter den kan "
|
||||||
"VERIFISERE mot kilden: console-script (registrert i pyproject), module-main (kjørbar med "
|
"VERIFISERE mot kilden: console-script (registrert i pyproject), module-main (kjørbar med "
|
||||||
"python -m, med sin egen __main__-vakt) og subcommand (et navn registrert i modulens egen "
|
"python -m, med sin egen __main__-vakt) og subcommand (et navn registrert i modulens egen "
|
||||||
"argparse). En art gaten ikke kan verifisere er ikke en dør her. Og et navn er uansett ikke "
|
"argparse, i en CLI som selv er registrert eller kjørbar med -m). En art gaten ikke kan "
|
||||||
"nok: PROBENS EGEN KILDE leses, og tre trekk måles i den — at proben rører DØREN (dens "
|
"verifisere er ikke en dør her. Og et navn er uansett ikke nok: PROBENS EGEN KILDE leses, "
|
||||||
"dotted modulnavn eller dens registrerte kommandonavn står i et kall der), at den navngir "
|
"og det som måles i den er en UTFØRELSE — at en prosess startes med dørens navn i "
|
||||||
"STEGET (symbolet, id-en eller underkommandoen som et eget ord), og at den asserter i det "
|
"argumentene, eller at inngangen importeres fra dørens modul og kalles, at en assert er "
|
||||||
"hele tatt. En probe som er DELT mellom to steg beviser høyst ett av dem, og gaten vet ikke "
|
"DATAAVHENGIG av det kallet (kallets verdi, eller et navn utledet av den, står i asserten), "
|
||||||
"hvilket — altså ingen. Valgt å lese probens kilde framfor å kreve at den ligger i en "
|
"og at kommandolinja som faktisk ble kjørt navngir STEGET når mer enn ett steg står bak "
|
||||||
"kontrakt-navngitt fil, fordi et filnavn er en konvensjon en stub oppfyller like lett som en "
|
"samme dør. Er steget ALENE bak sin dør, er det å kjøre døren å kjøre steget, og "
|
||||||
"ekte probe; målt 19.09 tok fjorten stubber og ÉN urelatert bestått test raden til 17 av 17"
|
"navngivningen faller bort — den er en diskriminator, ikke et bevis. Død kode teller ikke: "
|
||||||
|
"et kall i en gren som aldri kjører, eller etter en return, er ingen utførelse. En probe "
|
||||||
|
"som er DELT mellom to steg beviser høyst ett av dem, og gaten vet ikke hvilket — altså "
|
||||||
|
"ingen. GRENSEN, uttalt: gaten leser at proben KJØRER døren og leser resultatet; den kjører "
|
||||||
|
"ikke proben på nytt med døren brutt. Den eneste formen som ikke kan skrives seg forbi er å "
|
||||||
|
"MUTERE inngangen til å reise og se proben bli rød, og det gjør gaten ikke"
|
||||||
)
|
)
|
||||||
|
|
||||||
#: Rad 1: grensen for hvordan nevneren leses, sagt av raden selv.
|
#: Rad 1: grensen for hvordan nevneren leses, sagt av raden selv.
|
||||||
|
|
@ -100,8 +105,10 @@ RUN_PATH_LIMIT = (
|
||||||
"19.09: 41 → 40). Gaten NAVNGIR at symbolet ikke lenger kalles i kjørestien, men feller det "
|
"19.09: 41 → 40). Gaten NAVNGIR at symbolet ikke lenger kalles i kjørestien, men feller det "
|
||||||
"ikke. Valgt å uttale grensen framfor å følge hjelpere transitivt, fordi et kallgraf-søk "
|
"ikke. Valgt å uttale grensen framfor å følge hjelpere transitivt, fordi et kallgraf-søk "
|
||||||
"ville trukket private hjelpere inn i nevneren og gjort den til en ny kuratert liste — den "
|
"ville trukket private hjelpere inn i nevneren og gjort den til en ny kuratert liste — den "
|
||||||
"ene tingen raden finnes for å unngå. Stegene som er erklært UTENFOR kjørestien har ingen "
|
"ene tingen raden finnes for å unngå. Stegene som er erklært utenfor kjørestien har ingen "
|
||||||
"utledet nevner i det hele tatt; de står navngitt under raden, ett for ett"
|
"utledet nevner i det hele tatt; de står navngitt under raden, ett for ett, og et slikt "
|
||||||
|
"steg som SLETTES tar nevneren med seg uten at gaten kan se det. Den vakten bor i suiten "
|
||||||
|
"(en utskrevet liste over de fire, som blir rød når en av dem forsvinner), ikke her"
|
||||||
)
|
)
|
||||||
|
|
||||||
#: Inngangsartene rad 1 kan VERIFISERE mot kilden. En art utenfor lista teller aldri som en dør:
|
#: Inngangsartene rad 1 kan VERIFISERE mot kilden. En art utenfor lista teller aldri som en dør:
|
||||||
|
|
@ -370,77 +377,303 @@ def registered_entry(
|
||||||
return False, f"{module} navngir ingen underkommando"
|
return False, f"{module} navngir ingen underkommando"
|
||||||
if not _registers_subcommand(tree, command):
|
if not _registers_subcommand(tree, command):
|
||||||
return False, f"{module} registrerer ingen underkommando {command!r}"
|
return False, f"{module} registrerer ingen underkommando {command!r}"
|
||||||
return True, ""
|
#: Målt 20.09: ``add_parser("x")`` i en modul ingen kan starte er et navn inne i en CLI, ikke
|
||||||
|
#: en dør ut. Underkommandoen arver dørens art fra CLI-en sin — registrert i pakkemanifestet
|
||||||
|
#: eller kjørbar med ``-m``. Uten den ene av de to er «kallbar utenfra» en påstand.
|
||||||
|
target = f"{_dotted(package, module)}:{scope}"
|
||||||
|
if target in console_scripts(repo_root).values() or _has_dunder_main(tree, scope):
|
||||||
|
return True, ""
|
||||||
|
return (
|
||||||
|
False,
|
||||||
|
f"{module} er verken registrert i pyproject [project.scripts] eller har en "
|
||||||
|
f"__main__-vakt — en underkommando i en CLI ingen kan starte er ingen dør",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
_TOKEN = re.compile(r"[\w-]+")
|
_TOKEN = re.compile(r"[\w-]+")
|
||||||
|
|
||||||
|
#: Et ord i en grunn: minst tre bokstaver, ingen siffer. «todo» er ett ord, «-» er ingen.
|
||||||
|
_WORD = re.compile(r"[^\W\d_]{3,}", re.UNICODE)
|
||||||
|
|
||||||
def _meaningful(node: ast.AST) -> list[ast.AST]:
|
#: Kallene på ``subprocess`` som STARTER en prosess. ``os.system`` og ``os.exec*`` håndteres
|
||||||
"""Hver node i kroppen UNNTATT strenger som står alene — docstringen er ikke en atferd."""
|
#: for seg, fordi navnene deres ikke er en lukket liste.
|
||||||
stack = [node]
|
_PROCESS_ATTRS = frozenset({"run", "Popen", "call", "check_call", "check_output"})
|
||||||
|
|
||||||
|
|
||||||
|
def named_reason(reason: str) -> bool:
|
||||||
|
"""En grunn er NAVNGITT prosa, ikke bare ikke-tom.
|
||||||
|
|
||||||
|
Målt 19.09: ``.strip()`` skilte tom fra ikke-tom, og «-», «todo», «x» og «.» gikk alle
|
||||||
|
gjennom som grunn for å holde et kall utenfor nevneren. Terskelen er satt mot kontraktens
|
||||||
|
egne 28 grunner (korteste: 30 tegn, tre ord) — den feller plassholderen og ingen av dem.
|
||||||
|
GRENSEN: dette måler FORM, ikke sannhet. «aaa bbb ccc» er prosa for denne regelen; ingen
|
||||||
|
tekstregel kan skille en grunn som holder fra en som bare ser ut som en."""
|
||||||
|
text = reason.strip()
|
||||||
|
return len(text) >= 12 and len(_WORD.findall(text)) >= 3
|
||||||
|
|
||||||
|
|
||||||
|
def door_key(step: Mapping[str, Any]) -> tuple[str, str, str]:
|
||||||
|
"""Døren et steg står bak: modul, scope og underkommando. To steg med samme nøkkel deles."""
|
||||||
|
entry = dict(step.get("entry", {}))
|
||||||
|
return (
|
||||||
|
str(entry.get("module", "")),
|
||||||
|
str(entry.get("scope", "main")),
|
||||||
|
str(entry.get("command", "")),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _constant_truth(test: ast.expr) -> bool | None:
|
||||||
|
return bool(test.value) if isinstance(test, ast.Constant) else None
|
||||||
|
|
||||||
|
|
||||||
|
def _live_nodes(scope: ast.AST) -> list[ast.AST]:
|
||||||
|
"""Nodene i kroppen som KAN kjøre — dokumentstrenger, døde grener og kode etter en
|
||||||
|
``return`` holdt utenfor.
|
||||||
|
|
||||||
|
Målt 19.09 (dommerens b1 og b2): et dørkall inne i ``if False:`` og et dørkall etter
|
||||||
|
``return`` telte begge som «proben rører døren». En setning som aldri kjører er ikke en
|
||||||
|
utførelse, og en gate som leser den måler kilden som tekst igjen."""
|
||||||
out: list[ast.AST] = []
|
out: list[ast.AST] = []
|
||||||
while stack:
|
|
||||||
current = stack.pop()
|
def statements(body: Sequence[ast.stmt]) -> None:
|
||||||
if (
|
for stmt in body:
|
||||||
isinstance(current, ast.Expr)
|
if (
|
||||||
and isinstance(current.value, ast.Constant)
|
isinstance(stmt, ast.Expr)
|
||||||
and isinstance(current.value.value, str)
|
and isinstance(stmt.value, ast.Constant)
|
||||||
):
|
and isinstance(stmt.value.value, str)
|
||||||
continue
|
):
|
||||||
out.append(current)
|
continue
|
||||||
stack += list(ast.iter_child_nodes(current))
|
if isinstance(stmt, ast.If):
|
||||||
|
truth = _constant_truth(stmt.test)
|
||||||
|
if truth is not None:
|
||||||
|
statements(stmt.body if truth else stmt.orelse)
|
||||||
|
continue
|
||||||
|
out.append(stmt)
|
||||||
|
for child in ast.iter_child_nodes(stmt):
|
||||||
|
if isinstance(child, (ast.stmt, ast.excepthandler)):
|
||||||
|
continue
|
||||||
|
out.extend(ast.walk(child))
|
||||||
|
for field in ("body", "orelse", "finalbody"):
|
||||||
|
statements(getattr(stmt, field, None) or ())
|
||||||
|
for handler in getattr(stmt, "handlers", None) or ():
|
||||||
|
statements(handler.body)
|
||||||
|
if isinstance(stmt, (ast.Return, ast.Raise, ast.Break, ast.Continue)):
|
||||||
|
break
|
||||||
|
|
||||||
|
statements(getattr(scope, "body", []))
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
def door_strings(nodes: Sequence[ast.AST]) -> set[str]:
|
def _target_names(target: ast.AST) -> set[str]:
|
||||||
"""Strengene proben bruker, docstringen unntatt.
|
if isinstance(target, ast.Name):
|
||||||
|
return {target.id}
|
||||||
|
if isinstance(target, (ast.Tuple, ast.List)):
|
||||||
|
return {n for element in target.elts for n in _target_names(element)}
|
||||||
|
return set()
|
||||||
|
|
||||||
Videre enn ``call_payload`` med vilje, og grunnen er målt: den ekte probe-formen bygger
|
|
||||||
kommandoen i en variabel først (``base = [sys.executable, "-m", "<modul>"]``) og sender den
|
def _strings_in(value: ast.AST, bindings: Mapping[str, set[str]]) -> set[str]:
|
||||||
inn etterpå. Et dotted modulnavn er så spesifikt at det ER døren uansett hvor det settes
|
"""Strengene et uttrykk bærer, variabler fulgt ett steg av gangen."""
|
||||||
sammen — det er nettopp den formen rad 3 selv har en egen vakt for."""
|
|
||||||
found: set[str] = set()
|
found: set[str] = set()
|
||||||
for node in nodes:
|
for child in ast.walk(value):
|
||||||
for child in _meaningful(node):
|
if isinstance(child, ast.Constant) and isinstance(child.value, str):
|
||||||
if isinstance(child, ast.Constant) and isinstance(child.value, str):
|
found.add(child.value)
|
||||||
found.add(child.value)
|
elif isinstance(child, ast.Name):
|
||||||
|
found |= bindings.get(child.id, set())
|
||||||
return found
|
return found
|
||||||
|
|
||||||
|
|
||||||
def call_payload(nodes: Sequence[ast.AST]) -> tuple[set[str], set[str]]:
|
def string_bindings(nodes: Sequence[ast.AST]) -> dict[str, set[str]]:
|
||||||
"""(ord, hele strenger) proben sender INN i et kall, pluss navnene den kaller.
|
"""``navn -> strengene det kan bære``, som fikspunkt over tilordningene.
|
||||||
|
|
||||||
To avgrensninger, begge målt fram:
|
Den ærlige probe-formen bygger kommandoen i en variabel først
|
||||||
|
(``base = [sys.executable, "-m", "<modul>"]``) og sender den inn etterpå. Uten dette leddet
|
||||||
* **Bare det som går inn i et kall.** En docstring som nevner symbolet, eller en kommentar
|
ville regelen felt kapabilitet — den motsatte feilen av den den er skrevet for."""
|
||||||
som navngir modulen, er ikke en atferd. Leses hele funksjonskroppen, er «nevner» nok — og
|
pairs: list[tuple[str, ast.AST]] = []
|
||||||
da er et navn en dør igjen, bare ett hakk lenger inne.
|
|
||||||
* **Delt i ORD, aldri delstreng.** «gate» er ikke nevnt av
|
|
||||||
``portfolio_optimiser.evals.v1_gate``. Målt 19.09: en tidligere utgave av denne regelen
|
|
||||||
godtok steget «gate» fordi probefila importerte modulen som ``gate`` — den bandt til et
|
|
||||||
lokalt alias, ikke til steget."""
|
|
||||||
words: set[str] = set()
|
|
||||||
constants: set[str] = set()
|
|
||||||
for node in nodes:
|
for node in nodes:
|
||||||
for call in ast.walk(node):
|
if isinstance(node, ast.Assign):
|
||||||
if not isinstance(call, ast.Call):
|
pairs += [(n, node.value) for t in node.targets for n in _target_names(t)]
|
||||||
continue
|
elif isinstance(node, (ast.AnnAssign, ast.AugAssign)) and node.value is not None:
|
||||||
func = call.func
|
pairs += [(n, node.value) for n in _target_names(node.target)]
|
||||||
if isinstance(func, ast.Name):
|
elif isinstance(node, ast.NamedExpr):
|
||||||
words.add(func.id)
|
pairs += [(n, node.value) for n in _target_names(node.target)]
|
||||||
elif isinstance(func, ast.Attribute):
|
bindings: dict[str, set[str]] = {}
|
||||||
words.add(func.attr)
|
for _ in range(4):
|
||||||
for argument in [*call.args, *[k.value for k in call.keywords]]:
|
changed = False
|
||||||
for child in ast.walk(argument):
|
for name, value in pairs:
|
||||||
if isinstance(child, ast.Constant) and isinstance(child.value, str):
|
found = _strings_in(value, bindings)
|
||||||
constants.add(child.value)
|
if not found <= bindings.get(name, set()):
|
||||||
words |= set(_TOKEN.findall(child.value))
|
bindings.setdefault(name, set()).update(found)
|
||||||
elif isinstance(child, ast.Name):
|
changed = True
|
||||||
words.add(child.id)
|
if not changed:
|
||||||
elif isinstance(child, ast.Attribute):
|
break
|
||||||
words.add(child.attr)
|
return bindings
|
||||||
return words, constants
|
|
||||||
|
|
||||||
|
def _starter_aliases(tree: ast.AST) -> set[str]:
|
||||||
|
"""Prosess-startere importert bart (``from subprocess import run``)."""
|
||||||
|
names: set[str] = set()
|
||||||
|
for node in ast.walk(tree):
|
||||||
|
if not isinstance(node, ast.ImportFrom) or node.module not in ("subprocess", "os"):
|
||||||
|
continue
|
||||||
|
for alias in node.names:
|
||||||
|
if (
|
||||||
|
alias.name in _PROCESS_ATTRS
|
||||||
|
or alias.name == "system"
|
||||||
|
or alias.name.startswith("exec")
|
||||||
|
):
|
||||||
|
names.add(alias.asname or alias.name)
|
||||||
|
return names
|
||||||
|
|
||||||
|
|
||||||
|
def _is_process_start(call: ast.Call, aliases: set[str]) -> bool:
|
||||||
|
func = call.func
|
||||||
|
if isinstance(func, ast.Attribute) and isinstance(func.value, ast.Name):
|
||||||
|
if func.value.id == "subprocess" and func.attr in _PROCESS_ATTRS:
|
||||||
|
return True
|
||||||
|
if func.value.id == "os" and (func.attr == "system" or func.attr.startswith("exec")):
|
||||||
|
return True
|
||||||
|
return isinstance(func, ast.Name) and func.id in aliases
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class DoorUse:
|
||||||
|
"""Hva proben FAKTISK gjorde med døren, målt i probens egen kilde.
|
||||||
|
|
||||||
|
``executed``: en prosess ble startet med dørens navn i argumentene, eller inngangssymbolet
|
||||||
|
ble importert fra dørens modul og kalt. ``proven``: en ``assert`` er DATAAVHENGIG av det
|
||||||
|
kallet. ``payload``: strengene kallet faktisk bar — kommandolinja, ikke kildeteksten."""
|
||||||
|
|
||||||
|
executed: bool
|
||||||
|
proven: bool
|
||||||
|
payload: frozenset[str]
|
||||||
|
|
||||||
|
|
||||||
|
def door_executions(
|
||||||
|
tree: ast.AST,
|
||||||
|
live: Mapping[str, Sequence[ast.AST]],
|
||||||
|
entry: Mapping[str, Any],
|
||||||
|
dotted: str,
|
||||||
|
repo_root: Path,
|
||||||
|
) -> DoorUse:
|
||||||
|
"""Proben KJØRER døren, og asserter på det kjøringen ga — målt, ikke lest som tekst.
|
||||||
|
|
||||||
|
Dette er tredje utgave av regelen, og de to første falt på samme måte: de leste et NAVN.
|
||||||
|
Utgave 1 slo opp om en nodeid var ``passed``; utgave 2 krevde at dørens navn sto som en
|
||||||
|
streng et kall brukte, og dommerens seks former (navnet i en ubrukt variabel, modulen
|
||||||
|
importert uten kall, kallet i en død gren, kallet etter ``return``, resultatet forkastet,
|
||||||
|
navnet skrevet ut med ``print``) gikk alle gjennom.
|
||||||
|
|
||||||
|
To krav, begge målt fram: et UTFØRELSESSTED (en prosess-starter, eller inngangen importert
|
||||||
|
OG kalt) og en assert som leser det utførelsen ga. En hjelper som returnerer kallet regnes
|
||||||
|
som døren selv — det er formen suitens egne prober har."""
|
||||||
|
scope = str(entry.get("scope", "main"))
|
||||||
|
names = {dotted}
|
||||||
|
target = f"{dotted}:{scope}"
|
||||||
|
names |= {name for name, value in console_scripts(repo_root).items() if value == target}
|
||||||
|
starters = _starter_aliases(tree)
|
||||||
|
parent, _, last = dotted.rpartition(".")
|
||||||
|
module_aliases: set[str] = set()
|
||||||
|
imported = False
|
||||||
|
for node in ast.walk(tree):
|
||||||
|
if isinstance(node, ast.ImportFrom):
|
||||||
|
module = node.module or ""
|
||||||
|
if module == parent:
|
||||||
|
module_aliases |= {a.asname or a.name for a in node.names if a.name == last}
|
||||||
|
elif module == dotted:
|
||||||
|
imported = imported or any(a.name == scope for a in node.names)
|
||||||
|
elif isinstance(node, ast.Import):
|
||||||
|
module_aliases |= {
|
||||||
|
a.asname or a.name.rpartition(".")[2] for a in node.names if a.name == dotted
|
||||||
|
}
|
||||||
|
bindings = string_bindings([*_live_nodes(tree), *[n for nodes in live.values() for n in nodes]])
|
||||||
|
payload: set[str] = set()
|
||||||
|
door_calls: set[int] = set()
|
||||||
|
door_functions: set[str] = set()
|
||||||
|
|
||||||
|
def arguments(call: ast.Call) -> set[str]:
|
||||||
|
found: set[str] = set()
|
||||||
|
for argument in [*call.args, *[k.value for k in call.keywords]]:
|
||||||
|
found |= _strings_in(argument, bindings)
|
||||||
|
return found
|
||||||
|
|
||||||
|
def is_door(call: ast.Call) -> bool:
|
||||||
|
if _is_process_start(call, starters):
|
||||||
|
found = arguments(call)
|
||||||
|
if found & names:
|
||||||
|
payload.update(found)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
func = call.func
|
||||||
|
in_process = (
|
||||||
|
isinstance(func, ast.Attribute)
|
||||||
|
and func.attr == scope
|
||||||
|
and isinstance(func.value, ast.Name)
|
||||||
|
and func.value.id in module_aliases
|
||||||
|
) or (isinstance(func, ast.Name) and func.id == scope and imported)
|
||||||
|
through_helper = isinstance(func, ast.Name) and func.id in door_functions
|
||||||
|
if in_process or through_helper:
|
||||||
|
payload.update(arguments(call))
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
for _ in range(4):
|
||||||
|
before = (len(door_calls), len(door_functions))
|
||||||
|
for name, nodes in live.items():
|
||||||
|
for node in nodes:
|
||||||
|
if isinstance(node, ast.Call) and is_door(node):
|
||||||
|
door_calls.add(id(node))
|
||||||
|
if any(
|
||||||
|
isinstance(stmt, ast.Return)
|
||||||
|
and stmt.value is not None
|
||||||
|
and any(child is node for child in ast.walk(stmt.value))
|
||||||
|
for stmt in nodes
|
||||||
|
if isinstance(stmt, ast.Return)
|
||||||
|
):
|
||||||
|
door_functions.add(name)
|
||||||
|
if (len(door_calls), len(door_functions)) == before:
|
||||||
|
break
|
||||||
|
|
||||||
|
def reads_door(node: ast.AST, tainted: set[str]) -> bool:
|
||||||
|
return any(
|
||||||
|
id(child) in door_calls or (isinstance(child, ast.Name) and child.id in tainted)
|
||||||
|
for child in ast.walk(node)
|
||||||
|
)
|
||||||
|
|
||||||
|
tainted: set[str] = set()
|
||||||
|
for _ in range(4):
|
||||||
|
changed = False
|
||||||
|
for nodes in live.values():
|
||||||
|
for node in nodes:
|
||||||
|
targets: list[ast.AST] = []
|
||||||
|
if isinstance(node, ast.Assign) and reads_door(node.value, tainted):
|
||||||
|
targets = list(node.targets)
|
||||||
|
elif (
|
||||||
|
isinstance(node, (ast.AnnAssign, ast.NamedExpr))
|
||||||
|
and node.value is not None
|
||||||
|
and reads_door(node.value, tainted)
|
||||||
|
):
|
||||||
|
targets = [node.target]
|
||||||
|
elif isinstance(node, ast.With):
|
||||||
|
targets = [
|
||||||
|
item.optional_vars
|
||||||
|
for item in node.items
|
||||||
|
if item.optional_vars is not None and reads_door(item.context_expr, tainted)
|
||||||
|
]
|
||||||
|
for bound in targets:
|
||||||
|
for label in _target_names(bound):
|
||||||
|
if label not in tainted:
|
||||||
|
tainted.add(label)
|
||||||
|
changed = True
|
||||||
|
if not changed:
|
||||||
|
break
|
||||||
|
|
||||||
|
proven = any(
|
||||||
|
isinstance(node, ast.Assert) and reads_door(node, tainted)
|
||||||
|
for nodes in live.values()
|
||||||
|
for node in nodes
|
||||||
|
)
|
||||||
|
return DoorUse(bool(door_calls), proven, frozenset(payload))
|
||||||
|
|
||||||
|
|
||||||
def step_needles(step: Mapping[str, Any]) -> set[str]:
|
def step_needles(step: Mapping[str, Any]) -> set[str]:
|
||||||
|
|
@ -452,68 +685,23 @@ def step_needles(step: Mapping[str, Any]) -> set[str]:
|
||||||
return needles
|
return needles
|
||||||
|
|
||||||
|
|
||||||
def drives_door(
|
|
||||||
tree: ast.AST,
|
|
||||||
nodes: Sequence[ast.AST],
|
|
||||||
entry: Mapping[str, Any],
|
|
||||||
dotted: str,
|
|
||||||
repo_root: Path,
|
|
||||||
) -> bool:
|
|
||||||
"""Proben rører døren — MÅLT i probens egen kilde, på én av to måter.
|
|
||||||
|
|
||||||
Som subprosess: dørens dotted modulnavn (``python -m <modul>``) eller dens registrerte
|
|
||||||
kommandonavn står som en streng et kall bruker. Eller i prosess: inngangen er importert fra
|
|
||||||
dørens egen modul og kalt. Alt annet er en test som består et annet sted."""
|
|
||||||
scope = str(entry.get("scope", "main"))
|
|
||||||
names = {dotted}
|
|
||||||
if str(entry.get("kind", "")) == "console-script":
|
|
||||||
target = f"{dotted}:{scope}"
|
|
||||||
names |= {name for name, value in console_scripts(repo_root).items() if value == target}
|
|
||||||
if names & door_strings(nodes):
|
|
||||||
return True
|
|
||||||
parent, _, last = dotted.rpartition(".")
|
|
||||||
aliases: set[str] = set()
|
|
||||||
imported = False
|
|
||||||
for node in ast.walk(tree):
|
|
||||||
if isinstance(node, ast.ImportFrom):
|
|
||||||
module = node.module or ""
|
|
||||||
if module == parent:
|
|
||||||
aliases |= {a.asname or a.name for a in node.names if a.name == last}
|
|
||||||
elif module == dotted:
|
|
||||||
imported = imported or any(a.name == scope for a in node.names)
|
|
||||||
elif isinstance(node, ast.Import):
|
|
||||||
aliases |= {
|
|
||||||
a.asname or a.name.rpartition(".")[2] for a in node.names if a.name == dotted
|
|
||||||
}
|
|
||||||
for node in nodes:
|
|
||||||
for call in ast.walk(node):
|
|
||||||
if not isinstance(call, ast.Call):
|
|
||||||
continue
|
|
||||||
func = call.func
|
|
||||||
if (
|
|
||||||
isinstance(func, ast.Attribute)
|
|
||||||
and func.attr == scope
|
|
||||||
and isinstance(func.value, ast.Name)
|
|
||||||
and func.value.id in aliases
|
|
||||||
):
|
|
||||||
return True
|
|
||||||
if isinstance(func, ast.Name) and func.id == scope and imported:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def probe_binds(
|
def probe_binds(
|
||||||
repo_root: Path,
|
repo_root: Path,
|
||||||
step: Mapping[str, Any],
|
step: Mapping[str, Any],
|
||||||
nodeid: str,
|
nodeid: str,
|
||||||
package: str = "portfolio_optimiser",
|
package: str = "portfolio_optimiser",
|
||||||
|
alone: bool = False,
|
||||||
) -> tuple[bool, str]:
|
) -> tuple[bool, str]:
|
||||||
"""(proben beviser DETTE steget, grunnen når den ikke gjør det).
|
"""(proben beviser DETTE steget, grunnen når den ikke gjør det).
|
||||||
|
|
||||||
Målt 19.09.2026 (N2/N2b): gaten slo opp om nodeid-en var ``passed`` og godtok den. En
|
Målt 19.09.2026 (N2/N2b): gaten slo opp om nodeid-en var ``passed`` og godtok den. En
|
||||||
registrert stub-dør med en probe pekt på en vilkårlig grønn test tok rad 1 fra 3 til 4 av 17
|
registrert stub-dør med en probe pekt på en vilkårlig grønn test tok rad 1 fra 3 til 4 av 17
|
||||||
— og gjort for alle fjorten røde steg: 17 av 17 GRØNN, uten én linje kapabilitet. Bindingen
|
— og gjort for alle fjorten røde steg: 17 av 17 GRØNN, uten én linje kapabilitet. Bindingen
|
||||||
er derfor en MÅLING i probens kilde, ikke en erklæring i kontrakten."""
|
er derfor en MÅLING i probens kilde, ikke en erklæring i kontrakten.
|
||||||
|
|
||||||
|
``alone`` sier at steget er ALENE bak sin dør. Da er det å kjøre døren å kjøre steget, og
|
||||||
|
navngivningen faller bort — den er en diskriminator mellom steg som DELER en dør, og målt
|
||||||
|
19.09 avviste den en ekte ende-til-ende dørprobe (``gate``) på en navneteknikalitet."""
|
||||||
file_part, separator, rest = nodeid.partition("::")
|
file_part, separator, rest = nodeid.partition("::")
|
||||||
if not separator or not rest:
|
if not separator or not rest:
|
||||||
return False, f"proben {nodeid} navngir ingen test"
|
return False, f"proben {nodeid} navngir ingen test"
|
||||||
|
|
@ -528,16 +716,27 @@ def probe_binds(
|
||||||
functions = _functions(tree)
|
functions = _functions(tree)
|
||||||
if name not in functions:
|
if name not in functions:
|
||||||
return False, f"proben {name} finnes ikke i {file_part}"
|
return False, f"proben {name} finnes ikke i {file_part}"
|
||||||
nodes = [functions[n] for n in sorted(_reachable(functions, name))]
|
live = {n: _live_nodes(functions[n]) for n in sorted(_reachable(functions, name))}
|
||||||
if not any(isinstance(x, ast.Assert) for node in nodes for x in ast.walk(node)):
|
if not any(isinstance(x, ast.Assert) for nodes in live.values() for x in nodes):
|
||||||
return False, f"proben {name} asserter ingenting — da måler den ikke"
|
return False, f"proben {name} asserter ingenting — da måler den ikke"
|
||||||
dotted = _dotted(package, str(step["entry"]["module"]))
|
dotted = _dotted(package, str(step["entry"]["module"]))
|
||||||
if not drives_door(tree, nodes, step["entry"], dotted, repo_root):
|
use = door_executions(tree, live, step["entry"], dotted, repo_root)
|
||||||
return False, f"proben {name} rører ikke døren ({dotted})"
|
if not use.executed:
|
||||||
|
return False, f"proben {name} rører ikke døren ({dotted}) — ingen utførelse i kilden"
|
||||||
|
if not use.proven:
|
||||||
|
return False, f"proben {name} asserter ikke på det døren gjorde"
|
||||||
|
words = {word for text in use.payload for word in _TOKEN.findall(text)}
|
||||||
|
command = str(dict(step["entry"]).get("command", ""))
|
||||||
|
if command and command not in words:
|
||||||
|
return False, f"proben {name} kjører ikke underkommandoen {command!r}"
|
||||||
|
if alone:
|
||||||
|
return True, ""
|
||||||
needles = step_needles(step)
|
needles = step_needles(step)
|
||||||
words, _constants = call_payload(nodes)
|
|
||||||
if not needles & words:
|
if not needles & words:
|
||||||
return False, f"proben {name} navngir ikke steget ({', '.join(sorted(needles))})"
|
return (
|
||||||
|
False,
|
||||||
|
f"proben {name} navngir ikke steget ({', '.join(sorted(needles))}) i det den kjørte",
|
||||||
|
)
|
||||||
return True, ""
|
return True, ""
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -556,12 +755,13 @@ def _binding_why(
|
||||||
repo_root: Path,
|
repo_root: Path,
|
||||||
package: str,
|
package: str,
|
||||||
shared: Mapping[str, Sequence[str]],
|
shared: Mapping[str, Sequence[str]],
|
||||||
|
alone: bool = False,
|
||||||
) -> str:
|
) -> str:
|
||||||
for nodeid in probes:
|
for nodeid in probes:
|
||||||
if nodeid in shared:
|
if nodeid in shared:
|
||||||
others = ", ".join(i for i in shared[nodeid] if i != str(step["id"]))
|
others = ", ".join(i for i in shared[nodeid] if i != str(step["id"]))
|
||||||
return f"atferdsprobe: delt med {others} — en probe beviser ett steg, eller ingen"
|
return f"atferdsprobe: delt med {others} — en probe beviser ett steg, eller ingen"
|
||||||
ok, why = probe_binds(repo_root, step, nodeid, package)
|
ok, why = probe_binds(repo_root, step, nodeid, package, alone)
|
||||||
if not ok:
|
if not ok:
|
||||||
return f"atferdsprobe: {why}"
|
return f"atferdsprobe: {why}"
|
||||||
return ""
|
return ""
|
||||||
|
|
@ -618,6 +818,11 @@ def measure_steps(
|
||||||
) -> list[Step]:
|
) -> list[Step]:
|
||||||
measured: list[Step] = []
|
measured: list[Step] = []
|
||||||
shared = shared_probes(steps)
|
shared = shared_probes(steps)
|
||||||
|
#: Hvor mange steg som står bak HVER dør. Ett steg bak en dør trenger ingen navngivning —
|
||||||
|
#: to gjør, og da er det navnet som sier hvilket av dem proben beviste.
|
||||||
|
behind: dict[tuple[str, str, str], int] = {}
|
||||||
|
for step in steps:
|
||||||
|
behind[door_key(step)] = behind.get(door_key(step), 0) + 1
|
||||||
for step in steps:
|
for step in steps:
|
||||||
tree = _tree(src, str(step["module"]))
|
tree = _tree(src, str(step["module"]))
|
||||||
resolved = tree is not None and _defined(tree, str(step["symbol"]))
|
resolved = tree is not None and _defined(tree, str(step["symbol"]))
|
||||||
|
|
@ -640,7 +845,9 @@ def measure_steps(
|
||||||
elif verdict:
|
elif verdict:
|
||||||
why = f"atferdsprobe: {verdict}"
|
why = f"atferdsprobe: {verdict}"
|
||||||
else:
|
else:
|
||||||
why = _binding_why(step, probes, repo_root, package, shared)
|
why = _binding_why(
|
||||||
|
step, probes, repo_root, package, shared, behind[door_key(step)] == 1
|
||||||
|
)
|
||||||
external = resolved and called and not why
|
external = resolved and called and not why
|
||||||
measured.append(Step(str(step["id"]), str(step["label"]), resolved, called, external, why))
|
measured.append(Step(str(step["id"]), str(step["label"]), resolved, called, external, why))
|
||||||
return measured
|
return measured
|
||||||
|
|
@ -663,7 +870,7 @@ def score_toolbox(
|
||||||
#: Målt 19.09 (N4/N5): et steg flyttet til «held_out» tok nevneren 17 → 16 MED ELLER UTEN
|
#: Målt 19.09 (N4/N5): et steg flyttet til «held_out» tok nevneren 17 → 16 MED ELLER UTEN
|
||||||
#: grunn, og sammendraget sa «med grunn» uansett. En tom grunn er ingen grunn: symbolet blir
|
#: grunn, og sammendraget sa «med grunn» uansett. En tom grunn er ingen grunn: symbolet blir
|
||||||
#: stående i nevneren som et kall uten dør, nøyaktig som et udeklarert kall.
|
#: stående i nevneren som et kall uten dør, nøyaktig som et udeklarert kall.
|
||||||
held = {symbol: reason for symbol, reason in claimed.items() if reason.strip()}
|
held = {symbol: reason for symbol, reason in claimed.items() if named_reason(reason)}
|
||||||
calls = run_path_calls(src, run_path) if run_path else {}
|
calls = run_path_calls(src, run_path) if run_path else {}
|
||||||
undeclared = sorted(name for name in calls if name not in declared and name not in held)
|
undeclared = sorted(name for name in calls if name not in declared and name not in held)
|
||||||
n = len(in_path) + len(undeclared)
|
n = len(in_path) + len(undeclared)
|
||||||
|
|
@ -678,7 +885,7 @@ def score_toolbox(
|
||||||
for name in undeclared
|
for name in undeclared
|
||||||
)
|
)
|
||||||
diagnostics = tuple(
|
diagnostics = tuple(
|
||||||
f"holdt utenfor: {symbol} — {reason or 'UTEN GRUNN'}"
|
f"holdt utenfor: {symbol} — {reason if named_reason(reason) else 'UTEN GRUNN'}"
|
||||||
for symbol, reason in sorted(claimed.items())
|
for symbol, reason in sorted(claimed.items())
|
||||||
) + tuple(
|
) + tuple(
|
||||||
f"holdt utenfor: {symbol} kalles ikke lenger i kjørestien (foreldet unntak)"
|
f"holdt utenfor: {symbol} kalles ikke lenger i kjørestien (foreldet unntak)"
|
||||||
|
|
@ -768,21 +975,25 @@ def score_roles(roles: Mapping[str, Any], outcomes: Mapping[str, str], src: Path
|
||||||
|
|
||||||
_FENCE = re.compile(r"^\s*(```|~~~)")
|
_FENCE = re.compile(r"^\s*(```|~~~)")
|
||||||
|
|
||||||
#: Rad 3: hva vakten IKKE rekker, sagt av raden selv — og ETTERPRØVD. Erklæringen oppga to
|
#: Rad 3: hva vakten ER, og hva den ikke kan se — sagt av raden selv.
|
||||||
#: gjenstående veier; PM plantet 21 kallformer 19.09 og MÅLTE at seks slapp. De fire billigste
|
#:
|
||||||
#: (den offisielle SDK-en i to skriveformer, node- og uv-kjørerne, det dynamiske import-kallet)
|
#: Tre runder på rad sa erklæringen hvor mange veier som STO IGJEN («NØYAKTIG TO», så «TRE»), og
|
||||||
#: er lukket med hver sin vakt; de tre som står igjen står her, navngitt.
|
#: begge gangene veltet den første nye målingen den: 19.09 slapp tolv plantede former forbi, ni
|
||||||
|
#: av dem utenfor de navngitte klassene. Feilen var ikke at vakten er ufullstendig — det sier
|
||||||
|
#: raden selv — men at setningen var en OPPREGNING. Den sier nå hva vakten er.
|
||||||
NO_CLAUDE_LIMIT = (
|
NO_CLAUDE_LIMIT = (
|
||||||
"rad 3 er en TEKSTVAKT over hele det publiserte manifestet, ikke en dataflyt-analyse: den "
|
"rad 3 er TEKSTMØNSTRE over hvert spor i git-manifestet, ikke en dataflyt-analyse: den "
|
||||||
"feller et programnavn skrevet ut, en absolutt sti, en liste lagt i en variabel, en "
|
"feller et navn som STÅR SKREVET — programnavnet, en absolutt sti, en liste lagt i en "
|
||||||
"konstant, en shell-streng, den offisielle Python-SDK-en, en node- eller uv-kjører som "
|
"variabel, en konstant, en shell-streng, de offisielle SDK-ene i Python og TypeScript, en "
|
||||||
"starter kommandoen, og et dynamisk import-kall. TRE veier står igjen og kan ikke felles "
|
"node-, deno- eller uv-kjører som starter kommandoen, og et dynamisk import-kall i begge "
|
||||||
"linje for linje: et kommandonavn satt sammen av deler ved kjøretid, et navn lest ut av en "
|
"skriveformer. Den ser IKKE et navn som blir til først når koden kjører: satt sammen av "
|
||||||
"miljøvariabel, og et navn som er base64-kodet eller på annen måte avkodet først. Raden "
|
"deler, lest ut av en miljøvariabel, base64-kodet eller avkodet på annen måte. Den ser "
|
||||||
"påstår derfor ikke at ingen vei finnes; den påstår at ingen SKREVET vei finnes, over en "
|
"heller ikke noe utenfor manifestet — en avhengighet, et container-bilde, en tjeneste i den "
|
||||||
"nevner den oppgir i filer. k av n er ikke et dekningsmål: det teller vakter som MÅLER "
|
"andre enden av et nettkall. Lista over kjørere og SDK-er er ikke uttømmende, og raden "
|
||||||
"(hver med kjent-positiv og kjent-negativ), aldri hvor stor del av angrepsflaten de dekker "
|
"påstår ikke at ingen vei finnes; den påstår at ingen SKREVET vei finnes, over en nevner "
|
||||||
"— en vakt lagt til uten en målt omgåelse bak seg hever både k og n og beviser ingenting"
|
"den oppgir i filer. k av n er ikke et dekningsmål: det teller vakter som MÅLER (hver med "
|
||||||
|
"kjent-positiv og kjent-negativ), aldri hvor stor del av angrepsflaten de dekker — en vakt "
|
||||||
|
"lagt til uten en målt omgåelse bak seg hever både k og n og beviser ingenting"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -44,9 +44,9 @@ _NOW = datetime(2026, 9, 19, 12, 0, tzinfo=timezone.utc)
|
||||||
#: Repoets egne tall ved skriving (19.09.2026), talt av armene under mot KILDEN. De står her for
|
#: Repoets egne tall ved skriving (19.09.2026), talt av armene under mot KILDEN. De står her for
|
||||||
#: at en stille endring i kjørestien skal vise seg som en rød test og ikke som et nytt tall.
|
#: at en stille endring i kjørestien skal vise seg som en rød test og ikke som et nytt tall.
|
||||||
_STEPS_TODAY = 17
|
_STEPS_TODAY = 17
|
||||||
_EXTERNAL_TODAY = 0
|
_EXTERNAL_TODAY = 1
|
||||||
_ROLES_TODAY = ("proposer", "checker")
|
_ROLES_TODAY = ("proposer", "checker")
|
||||||
_PATTERNS_TODAY = 12
|
_PATTERNS_TODAY = 15
|
||||||
|
|
||||||
|
|
||||||
def _write(path: Path, text: str) -> None:
|
def _write(path: Path, text: str) -> None:
|
||||||
|
|
@ -106,10 +106,10 @@ def test_the_step_denominator_equals_an_independent_count_of_the_source() -> Non
|
||||||
assert row.n == _STEPS_TODAY
|
assert row.n == _STEPS_TODAY
|
||||||
|
|
||||||
|
|
||||||
def test_row1_today_is_red_with_no_step_callable_from_outside() -> None:
|
def test_row1_today_is_red_with_the_steps_that_have_no_door_named() -> None:
|
||||||
"""Var «3 av 17» til 19.09. De tre falt da proben ble BUNDET til steget: to av dem driver
|
"""Var «3 av 17» til 19.09, så 0 av 17 da proben ble BUNDET til steget. Fra 20.09 teller
|
||||||
riktig dør uten å navngi hvilket steg de beviser, den tredje går ikke gjennom døren i det
|
``gate`` igjen: den proben kjører v1-gaten som subprosess og leser JSON-en ut av den, og
|
||||||
hele tatt. Ingen av dem var en kodefeil — det var kontrakten som godtok dem."""
|
steget er alene bak den døren — den ble avvist på en navneteknikalitet, ikke på atferd."""
|
||||||
passing = {n: "passed" for step in _CONFIG["steps"] for n in step.get("probe", ())}
|
passing = {n: "passed" for step in _CONFIG["steps"] for n in step.get("probe", ())}
|
||||||
row = gate.score_toolbox(_CONFIG["steps"], _SRC, _CONFIG["run_path"], passing, _REPO)
|
row = gate.score_toolbox(_CONFIG["steps"], _SRC, _CONFIG["run_path"], passing, _REPO)
|
||||||
assert (row.k, row.n, row.status) == (_EXTERNAL_TODAY, _STEPS_TODAY, gate.RED)
|
assert (row.k, row.n, row.status) == (_EXTERNAL_TODAY, _STEPS_TODAY, gate.RED)
|
||||||
|
|
@ -117,7 +117,7 @@ def test_row1_today_is_red_with_no_step_callable_from_outside() -> None:
|
||||||
assert "validering" in named and "utboks" in named
|
assert "validering" in named and "utboks" in named
|
||||||
assert "prepass-artefakt" in named and "parse-feil" in named
|
assert "prepass-artefakt" in named and "parse-feil" in named
|
||||||
bound = {x.split(":")[0] for x in row.exceptions if "atferdsprobe" in x}
|
bound = {x.split(":")[0] for x in row.exceptions if "atferdsprobe" in x}
|
||||||
assert bound == {"rundebinding", "rapport", "gate"}, row.exceptions
|
assert bound == {"rundebinding", "rapport"}, row.exceptions
|
||||||
|
|
||||||
|
|
||||||
def _fake_src(root: Path, *, chat_client: bool, symbol: str = "gjoer_noe") -> Path:
|
def _fake_src(root: Path, *, chat_client: bool, symbol: str = "gjoer_noe") -> Path:
|
||||||
|
|
@ -1136,14 +1136,20 @@ def test_a_probe_that_drives_the_door_without_naming_the_step_proves_the_door_on
|
||||||
tmp_path: Path,
|
tmp_path: Path,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""To steg bak SAMME dør er det vanlige tilfellet (``round_builder`` bærer to). En probe som
|
"""To steg bak SAMME dør er det vanlige tilfellet (``round_builder`` bærer to). En probe som
|
||||||
bare kjører kommandoen skiller dem ikke — den må navngi steget den beviser."""
|
bare kjører kommandoen skiller dem ikke — den må navngi steget den beviser.
|
||||||
src = _fake_src(tmp_path, chat_client=False)
|
|
||||||
_write_probe(tmp_path)
|
|
||||||
assert gate.score_toolbox(_one_step(), src, _fake_run_path(), _FAKE_PASSED, tmp_path).k == 1
|
|
||||||
|
|
||||||
_write_probe(tmp_path, needle="et_helt_annet_steg")
|
Nevneren er TO med vilje (endret 20.09): navngivningen er en diskriminator, og et steg som
|
||||||
row = gate.score_toolbox(_one_step(), src, _fake_run_path(), _FAKE_PASSED, tmp_path)
|
er alene bak sin dør har ingenting å skilles fra — se
|
||||||
assert (row.k, row.n) == (0, 1)
|
``test_the_sole_step_behind_a_door_is_proved_by_driving_that_door``."""
|
||||||
|
src = _two_step_src(tmp_path)
|
||||||
|
a = _write_probe(tmp_path, needle="gjoer_noe", nodeid="tests/test_a.py::test_a")
|
||||||
|
b = _write_probe(tmp_path, needle="gjoer_noe_annet", nodeid="tests/test_b.py::test_b")
|
||||||
|
outcomes = {a: "passed", b: "passed"}
|
||||||
|
assert gate.score_toolbox(_two_steps(a, b), src, _fake_run_path(), outcomes, tmp_path).k == 2
|
||||||
|
|
||||||
|
b = _write_probe(tmp_path, needle="et_helt_annet_steg", nodeid="tests/test_b.py::test_b")
|
||||||
|
row = gate.score_toolbox(_two_steps(a, b), src, _fake_run_path(), outcomes, tmp_path)
|
||||||
|
assert (row.k, row.n) == (1, 2)
|
||||||
assert any("navngir ikke steget" in x for x in row.exceptions), row.exceptions
|
assert any("navngir ikke steget" in x for x in row.exceptions), row.exceptions
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1165,9 +1171,18 @@ def test_a_probe_that_only_mentions_the_door_or_the_step_proves_neither(tmp_path
|
||||||
assert (row.k, row.n) == (0, 1), row.exceptions
|
assert (row.k, row.n) == (0, 1), row.exceptions
|
||||||
assert any("rører ikke døren" in x for x in row.exceptions), row.exceptions
|
assert any("rører ikke døren" in x for x in row.exceptions), row.exceptions
|
||||||
|
|
||||||
_write_probe(tmp_path, needle="et_annet_navn", docstring="gjoer_noe")
|
to_steg = _two_step_src(tmp_path)
|
||||||
row = gate.score_toolbox(_one_step(), src, _fake_run_path(), _FAKE_PASSED, tmp_path)
|
a = _write_probe(tmp_path, needle="gjoer_noe", nodeid="tests/test_a.py::test_a")
|
||||||
assert (row.k, row.n) == (0, 1), row.exceptions
|
b = _write_probe(
|
||||||
|
tmp_path,
|
||||||
|
needle="et_annet_navn",
|
||||||
|
docstring="gjoer_noe_annet",
|
||||||
|
nodeid="tests/test_b.py::test_b",
|
||||||
|
)
|
||||||
|
row = gate.score_toolbox(
|
||||||
|
_two_steps(a, b), to_steg, _fake_run_path(), {a: "passed", b: "passed"}, tmp_path
|
||||||
|
)
|
||||||
|
assert (row.k, row.n) == (1, 2), row.exceptions
|
||||||
assert any("navngir ikke steget" in x for x in row.exceptions), row.exceptions
|
assert any("navngir ikke steget" in x for x in row.exceptions), row.exceptions
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -1452,3 +1467,398 @@ def test_a_runbook_without_the_sections_the_contract_names_is_not_green(tmp_path
|
||||||
row = gate.score_runbook(cfg, tmp_path, now=_NOW)
|
row = gate.score_runbook(cfg, tmp_path, now=_NOW)
|
||||||
assert row.status == gate.NOT_MEASURED, row.exceptions
|
assert row.status == gate.NOT_MEASURED, row.exceptions
|
||||||
assert any("seksjon" in x for x in row.exceptions), row.exceptions
|
assert any("seksjon" in x for x in row.exceptions), row.exceptions
|
||||||
|
|
||||||
|
|
||||||
|
# --- rad 1, TREDJE reparasjon: bindingen måler UTFØRELSE, ikke tekst --------------------------
|
||||||
|
#
|
||||||
|
# Sjekkpunktet 19.09 (`a37e429`) MÅLTE at rad 1 kunne bli 17 av 17 med 17 énlinjes-prober og en
|
||||||
|
# dørmodul uten én eneste import: «proben rører døren» var en tekstsjekk ett hakk lenger inne.
|
||||||
|
# Dommerens seks probeformer + oppskriften står her, hver som sin egen arm, og hver skal gi 0.
|
||||||
|
|
||||||
|
|
||||||
|
def _write_raw_probe(root: Path, body: str, nodeid: str = _FAKE_PROBE) -> str:
|
||||||
|
"""En probe skrevet ORDRETT — de uærlige formene kan ikke uttrykkes gjennom ``_write_probe``."""
|
||||||
|
file_part, _, name = nodeid.partition("::")
|
||||||
|
_write(root / file_part, body.replace("<navn>", name))
|
||||||
|
return nodeid
|
||||||
|
|
||||||
|
|
||||||
|
#: Dommerens sju former, verbatim slik de ble målt: seks som BRAST mot forrige utgave, pluss
|
||||||
|
#: oppskriften «17 énlinjes-prober + dørmodul uten imports» i sin minste form. Døren er
|
||||||
|
#: ``fakesrc.driver``, steget ``gjoer_noe`` — nøyaktig dem ``_one_step`` erklærer.
|
||||||
|
_JUKSEFORMER: tuple[tuple[str, str], ...] = (
|
||||||
|
(
|
||||||
|
"a1-navn-i-ubrukt-variabel",
|
||||||
|
"def <navn>(tmp_path):\n"
|
||||||
|
' door = "fakesrc.driver"\n'
|
||||||
|
' steg = "gjoer_noe"\n'
|
||||||
|
" assert isinstance(door, str) and isinstance(steg, str)\n",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"a2-importert-aldri-kalt",
|
||||||
|
"from fakesrc import driver\n\n\n"
|
||||||
|
"def <navn>(tmp_path):\n"
|
||||||
|
" assert driver is not None\n"
|
||||||
|
' assert "gjoer_noe"\n',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"b1-kallet-i-doed-gren",
|
||||||
|
"import subprocess\nimport sys\n\n\n"
|
||||||
|
"def <navn>(tmp_path):\n"
|
||||||
|
" if False:\n"
|
||||||
|
' subprocess.run([sys.executable, "-m", "fakesrc.driver", "--steg", "gjoer_noe"])\n'
|
||||||
|
" assert True\n",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"b2-kallet-etter-return",
|
||||||
|
"import subprocess\nimport sys\n\n\n"
|
||||||
|
"def <navn>(tmp_path):\n"
|
||||||
|
' assert "gjoer_noe"\n'
|
||||||
|
" return None\n"
|
||||||
|
' subprocess.run([sys.executable, "-m", "fakesrc.driver", "--steg", "gjoer_noe"])\n',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"c1-resultatet-forkastet",
|
||||||
|
"import subprocess\nimport sys\n\n\n"
|
||||||
|
"def <navn>(tmp_path):\n"
|
||||||
|
' subprocess.run([sys.executable, "-m", "fakesrc.driver", "--steg", "gjoer_noe"])\n'
|
||||||
|
" assert 2 + 2 == 4\n",
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"c2-bare-skrevet-ut",
|
||||||
|
'def <navn>(tmp_path):\n print("fakesrc.driver", "gjoer_noe")\n assert True\n',
|
||||||
|
),
|
||||||
|
(
|
||||||
|
"oppskriften-enlinjes",
|
||||||
|
'def <navn>(tmp_path):\n assert len("fakesrc.driver") and len("gjoer_noe")\n',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(("form", "body"), _JUKSEFORMER, ids=[n for n, _ in _JUKSEFORMER])
|
||||||
|
def test_none_of_the_judges_seven_forms_is_a_behaviour_probe(
|
||||||
|
tmp_path: Path, form: str, body: str
|
||||||
|
) -> None:
|
||||||
|
"""Hver form plantes ALENE, med rc-0-kontrollen først: den ærlige proben teller, formen ikke.
|
||||||
|
|
||||||
|
Uten kontrollen ville armen bestått mot en gate som avviste ALT — «0 av 1» er et
|
||||||
|
måleresultat bare når 1 av 1 er oppnåelig i samme oppsett."""
|
||||||
|
src = _fake_src(tmp_path, chat_client=False)
|
||||||
|
_write_probe(tmp_path)
|
||||||
|
kontroll = gate.score_toolbox(_one_step(), src, _fake_run_path(), _FAKE_PASSED, tmp_path)
|
||||||
|
assert (kontroll.k, kontroll.n) == (1, 1), kontroll.exceptions
|
||||||
|
|
||||||
|
_write_raw_probe(tmp_path, body)
|
||||||
|
row = gate.score_toolbox(_one_step(), src, _fake_run_path(), _FAKE_PASSED, tmp_path)
|
||||||
|
assert (row.k, row.n) == (0, 1), (form, row.exceptions)
|
||||||
|
assert any("atferdsprobe" in x for x in row.exceptions), (form, row.exceptions)
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_probe_that_runs_the_door_and_throws_the_result_away_proves_nothing(
|
||||||
|
tmp_path: Path,
|
||||||
|
) -> None:
|
||||||
|
"""Dommerens c1, med sin EGEN grunn: kallet skjedde, men ingen assert leser det det ga.
|
||||||
|
Skilles fra «rører ikke døren», for det er to ulike feil og bare den ene er en løgn."""
|
||||||
|
src = _fake_src(tmp_path, chat_client=False)
|
||||||
|
_write_raw_probe(tmp_path, dict(_JUKSEFORMER)["c1-resultatet-forkastet"])
|
||||||
|
row = gate.score_toolbox(_one_step(), src, _fake_run_path(), _FAKE_PASSED, tmp_path)
|
||||||
|
assert row.k == 0
|
||||||
|
assert any("asserter ikke på det døren" in x for x in row.exceptions), row.exceptions
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_probe_that_only_names_the_door_never_reaches_the_execution_rule(
|
||||||
|
tmp_path: Path,
|
||||||
|
) -> None:
|
||||||
|
"""Dommerens a1: navnet står i kilden, men ingen prosess starter. Grunnen skal være at døren
|
||||||
|
ikke RØRES — ikke at asserten er feil, for da ville en assert-fiks «løst» det."""
|
||||||
|
src = _fake_src(tmp_path, chat_client=False)
|
||||||
|
_write_raw_probe(tmp_path, dict(_JUKSEFORMER)["a1-navn-i-ubrukt-variabel"])
|
||||||
|
row = gate.score_toolbox(_one_step(), src, _fake_run_path(), _FAKE_PASSED, tmp_path)
|
||||||
|
assert any("rører ikke døren" in x for x in row.exceptions), row.exceptions
|
||||||
|
|
||||||
|
|
||||||
|
def test_an_honest_probe_that_starts_the_door_through_a_helper_still_counts(
|
||||||
|
tmp_path: Path,
|
||||||
|
) -> None:
|
||||||
|
"""Den ÆRLIGE formen suiten faktisk bruker (``test_v1_gate._cli``): subprosessen startes i en
|
||||||
|
hjelper, og testen asserter på det hjelperen ga tilbake. En regel som feller den, feller
|
||||||
|
kapabilitet — som er den motsatte feilen av den vi retter."""
|
||||||
|
src = _fake_src(tmp_path, chat_client=False)
|
||||||
|
_write_raw_probe(
|
||||||
|
tmp_path,
|
||||||
|
"import subprocess\nimport sys\n\n\n"
|
||||||
|
"def _cli(*args):\n"
|
||||||
|
" return subprocess.run(\n"
|
||||||
|
' [sys.executable, "-m", "fakesrc.driver", *args], capture_output=True, text=True\n'
|
||||||
|
" )\n\n\n"
|
||||||
|
"def <navn>(tmp_path):\n"
|
||||||
|
' proc = _cli("--steg", "gjoer_noe")\n'
|
||||||
|
" assert proc.returncode == 0\n",
|
||||||
|
)
|
||||||
|
row = gate.score_toolbox(_one_step(), src, _fake_run_path(), _FAKE_PASSED, tmp_path)
|
||||||
|
assert (row.k, row.n) == (1, 1), row.exceptions
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_probe_that_builds_the_command_in_a_variable_first_still_counts(
|
||||||
|
tmp_path: Path,
|
||||||
|
) -> None:
|
||||||
|
"""``round_builder``-probens form: ``base = [sys.executable, "-m", "<modul>"]`` og
|
||||||
|
``subprocess.run([*base, …])``. Strengen når kallet gjennom en variabel; det er en
|
||||||
|
utførelse."""
|
||||||
|
src = _fake_src(tmp_path, chat_client=False)
|
||||||
|
_write_raw_probe(
|
||||||
|
tmp_path,
|
||||||
|
"import subprocess\nimport sys\n\n\n"
|
||||||
|
"def <navn>(tmp_path):\n"
|
||||||
|
' base = [sys.executable, "-m", "fakesrc.driver"]\n'
|
||||||
|
' ok = subprocess.run([*base, "--steg", "gjoer_noe"], capture_output=True, text=True)\n'
|
||||||
|
" assert ok.returncode == 0, ok.stderr\n",
|
||||||
|
)
|
||||||
|
row = gate.score_toolbox(_one_step(), src, _fake_run_path(), _FAKE_PASSED, tmp_path)
|
||||||
|
assert (row.k, row.n) == (1, 1), row.exceptions
|
||||||
|
|
||||||
|
|
||||||
|
def test_the_step_name_must_stand_in_what_was_actually_executed(tmp_path: Path) -> None:
|
||||||
|
"""Navngivningen flyttes fra «et hvilket som helst kall-argument» til DEN kommandolinja som
|
||||||
|
ble kjørt. To steg bak samme dør, samme kommando — ordet som skiller dem må være i argv."""
|
||||||
|
src = _two_step_src(tmp_path)
|
||||||
|
a = _write_raw_probe(
|
||||||
|
tmp_path,
|
||||||
|
"import subprocess\nimport sys\n\n\n"
|
||||||
|
"def <navn>(tmp_path):\n"
|
||||||
|
" proc = subprocess.run(\n"
|
||||||
|
' [sys.executable, "-m", "fakesrc.driver", "--steg", "gjoer_noe"],\n'
|
||||||
|
" capture_output=True,\n"
|
||||||
|
" )\n"
|
||||||
|
" assert proc.returncode == 0\n",
|
||||||
|
nodeid="tests/test_a.py::test_a",
|
||||||
|
)
|
||||||
|
b = _write_raw_probe(
|
||||||
|
tmp_path,
|
||||||
|
"import subprocess\nimport sys\n\n\n"
|
||||||
|
"def <navn>(tmp_path):\n"
|
||||||
|
" proc = subprocess.run(\n"
|
||||||
|
' [sys.executable, "-m", "fakesrc.driver"], capture_output=True\n'
|
||||||
|
" )\n"
|
||||||
|
' hjelp("gjoer_noe_annet")\n'
|
||||||
|
" assert proc.returncode == 0\n",
|
||||||
|
nodeid="tests/test_b.py::test_b",
|
||||||
|
)
|
||||||
|
row = gate.score_toolbox(
|
||||||
|
_two_steps(a, b), src, _fake_run_path(), {a: "passed", b: "passed"}, tmp_path
|
||||||
|
)
|
||||||
|
assert (row.k, row.n) == (1, 2), row.exceptions
|
||||||
|
assert any("navngir ikke steget" in x for x in row.exceptions), row.exceptions
|
||||||
|
|
||||||
|
|
||||||
|
def test_the_sole_step_behind_a_door_is_proved_by_driving_that_door(tmp_path: Path) -> None:
|
||||||
|
"""KLASSE-rettingen av 3 → 0: navngivningen er en DISKRIMINATOR mellom steg som deler dør.
|
||||||
|
Er steget alene bak sin dør, er det å kjøre døren å kjøre steget — og en ekte ende-til-ende
|
||||||
|
dørprobe skal ikke avvises på en navneteknikalitet (målt 19.09 på ``gate``-proben)."""
|
||||||
|
src = _fake_src(tmp_path, chat_client=False)
|
||||||
|
_write_probe(tmp_path, needle="et_helt_annet_ord")
|
||||||
|
row = gate.score_toolbox(_one_step(), src, _fake_run_path(), _FAKE_PASSED, tmp_path)
|
||||||
|
assert (row.k, row.n) == (1, 1), row.exceptions
|
||||||
|
|
||||||
|
# ... og den samme proben beviser INGEN av to steg som deler døren.
|
||||||
|
delt = _write_probe(tmp_path, needle="et_helt_annet_ord", nodeid="tests/test_x.py::test_x")
|
||||||
|
annen = _write_probe(tmp_path, needle="et_helt_annet_ord", nodeid="tests/test_y.py::test_y")
|
||||||
|
row = gate.score_toolbox(
|
||||||
|
_two_steps(delt, annen),
|
||||||
|
_two_step_src(tmp_path),
|
||||||
|
_fake_run_path(),
|
||||||
|
{delt: "passed", annen: "passed"},
|
||||||
|
tmp_path,
|
||||||
|
)
|
||||||
|
assert row.k == 0, row.exceptions
|
||||||
|
assert all("navngir ikke steget" in x for x in row.exceptions), row.exceptions
|
||||||
|
|
||||||
|
|
||||||
|
def test_the_gates_own_gate_probe_binds_after_the_class_fix() -> None:
|
||||||
|
"""Det ene steget som SKULLE telle før og ikke gjorde det: ``gate`` kjører v1-gaten som
|
||||||
|
subprosess og leser JSON-en ut av den, og er ALENE bak sin dør — talt her, ikke antatt.
|
||||||
|
Målt gjennom raden, ikke gjennom en hjelper: det er raden som er påstanden."""
|
||||||
|
doer = [
|
||||||
|
(s["entry"]["module"], s["entry"].get("scope", "main"), s["entry"].get("command", ""))
|
||||||
|
for s in _CONFIG["steps"]
|
||||||
|
]
|
||||||
|
gate_step = next(s for s in _CONFIG["steps"] if s["id"] == "gate")
|
||||||
|
noekkel = (
|
||||||
|
gate_step["entry"]["module"],
|
||||||
|
gate_step["entry"].get("scope", "main"),
|
||||||
|
gate_step["entry"].get("command", ""),
|
||||||
|
)
|
||||||
|
assert doer.count(noekkel) == 1, "armen forutsetter at gate er alene bak sin dør"
|
||||||
|
|
||||||
|
row = gate.score_toolbox(
|
||||||
|
_CONFIG["steps"], _SRC, _CONFIG["run_path"], _all_probes_passed(), _REPO
|
||||||
|
)
|
||||||
|
assert not any(x.startswith("gate:") for x in row.exceptions), row.exceptions
|
||||||
|
|
||||||
|
|
||||||
|
def _all_probes_passed() -> dict[str, str]:
|
||||||
|
"""Hver nodeid kontrakten navngir, som «passed» — utfallet er IKKE det som måles her."""
|
||||||
|
return {nodeid: "passed" for nodeid in gate.probe_nodeids(_CONFIG)}
|
||||||
|
|
||||||
|
|
||||||
|
def test_the_door_rule_says_that_it_measures_an_execution_and_states_its_limit() -> None:
|
||||||
|
"""A8-klassen: regelen skal si nøyaktig hva koden MÅLER, og hva den ikke kan se."""
|
||||||
|
rule = gate.EXTERNAL_DOOR.casefold()
|
||||||
|
assert "utførelse" in rule
|
||||||
|
assert "dataavhengig" in rule
|
||||||
|
assert "alene bak" in rule
|
||||||
|
# Grensen dommeren navnga som den eneste formen som ikke kan skrives forbi.
|
||||||
|
assert "muter" in rule or "mutant" in rule
|
||||||
|
|
||||||
|
|
||||||
|
# --- rad 1: en grunn skal være NAVNGITT prosa, ikke bare ikke-tom -----------------------------
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("plassholder", ["-", "todo", "x", ".", "??", "n/a"])
|
||||||
|
def test_a_placeholder_is_not_a_named_reason(tmp_path: Path, plassholder: str) -> None:
|
||||||
|
"""Sjekkpunktet målte at «-», «todo», «x» og «.» alle ble godtatt som grunn: ``.strip()``
|
||||||
|
skilte tom fra ikke-tom, ikke grunn fra ikke-grunn."""
|
||||||
|
src, spec = _mini_run_path(tmp_path, extra_call=True)
|
||||||
|
spec["held_out"] = [{"symbol": "beta", "reason": "hjelper inne i alfa, ikke et eget steg"}]
|
||||||
|
steps = [
|
||||||
|
{
|
||||||
|
"id": "alfa",
|
||||||
|
"label": "alfa",
|
||||||
|
"module": "steg.py",
|
||||||
|
"symbol": "alfa",
|
||||||
|
"driver": {"module": "kjor.py", "scope": "run_project"},
|
||||||
|
"entry": {"kind": "module-main", "module": "kjor.py", "scope": "main"},
|
||||||
|
}
|
||||||
|
]
|
||||||
|
assert gate.score_toolbox(steps, src, spec, {}, tmp_path).n == 1, "kontroll: ekte prosa holder"
|
||||||
|
|
||||||
|
spec["held_out"] = [{"symbol": "beta", "reason": plassholder}]
|
||||||
|
row = gate.score_toolbox(steps, src, spec, {}, tmp_path)
|
||||||
|
assert row.n == 2, f"{plassholder!r} er ingen grunn — symbolet blir i nevneren"
|
||||||
|
assert any("UTEN grunn" in x for x in row.exceptions), row.exceptions
|
||||||
|
|
||||||
|
|
||||||
|
def test_every_held_out_reason_in_the_contract_is_named_prose() -> None:
|
||||||
|
"""Terskelen måles mot kontraktens egne 28 grunner, gjennom RADEN: strammes den så den
|
||||||
|
feller ekte prosa, vokser nevneren og denne armen blir rød. Tallet 28 er talt her."""
|
||||||
|
grunner = [str(h["reason"]) for h in _CONFIG["run_path"]["held_out"]]
|
||||||
|
assert len(grunner) == 28
|
||||||
|
assert min(len(g) for g in grunner) >= 30, "margin mot terskelen, målt"
|
||||||
|
row = gate.score_toolbox(_CONFIG["steps"], _SRC, _CONFIG["run_path"], {}, _REPO)
|
||||||
|
assert row.n == _STEPS_TODAY, "en ekte grunn skal ikke falle ut som plassholder"
|
||||||
|
assert sum(1 for d in row.diagnostics if d.startswith("holdt utenfor: ")) >= len(grunner)
|
||||||
|
assert not any("UTEN GRUNN" in d for d in row.diagnostics), row.diagnostics
|
||||||
|
|
||||||
|
|
||||||
|
def test_the_row_limit_says_the_off_path_steps_have_no_derived_denominator() -> None:
|
||||||
|
"""N7, uttalt: de fire stegene utenfor kjørestien har ingen utledet kilde, og et slettet
|
||||||
|
off-path-steg tar derfor nevneren 17 → 16. Gaten kan ikke felle det; suiten kan."""
|
||||||
|
assert "utenfor kjørestien" in gate.RUN_PATH_LIMIT
|
||||||
|
assert "suiten" in gate.RUN_PATH_LIMIT
|
||||||
|
|
||||||
|
|
||||||
|
#: De fire stegene som er erklært UTENFOR kjørestien, talt for hånd i kontrakten 20.09.2026.
|
||||||
|
#: Dette er N7s eneste vakt: sletter noen et av dem, krymper nevneren stille i GATEN — her blir
|
||||||
|
#: det rødt, og navnet står igjen i suiten.
|
||||||
|
_OFF_PATH_TODAY: tuple[tuple[str, str, str], ...] = (
|
||||||
|
("gate", "evals/v1_gate.py", "main"),
|
||||||
|
("rapport", "evals/round_builder.py", "build_round"),
|
||||||
|
("rundebinding", "evals/round_builder.py", "main"),
|
||||||
|
("validering", "run.py", "evaluate_mandate_candidates"),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_no_off_path_step_can_be_deleted_without_this_arm_going_red() -> None:
|
||||||
|
driver = (str(_CONFIG["run_path"]["module"]), str(_CONFIG["run_path"]["scope"]))
|
||||||
|
funnet = tuple(
|
||||||
|
sorted(
|
||||||
|
(str(s["id"]), str(s["driver"]["module"]), str(s["driver"]["scope"]))
|
||||||
|
for s in _CONFIG["steps"]
|
||||||
|
if (str(s["driver"]["module"]), str(s["driver"]["scope"])) != driver
|
||||||
|
)
|
||||||
|
)
|
||||||
|
assert funnet == _OFF_PATH_TODAY
|
||||||
|
|
||||||
|
|
||||||
|
# --- rad 1: en underkommando er bare en dør når modulen selv er kallbar utenfra ---------------
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_subcommand_is_only_a_door_when_its_module_is_reachable_from_outside(
|
||||||
|
tmp_path: Path,
|
||||||
|
) -> None:
|
||||||
|
"""En ``add_parser("x")`` i en modul ingen kan starte er ingen dør: underkommandoen er et
|
||||||
|
navn inne i en CLI, og CLI-en selv må være registrert (konsoll-skript eller ``-m``)."""
|
||||||
|
src = tmp_path / "fakesrc"
|
||||||
|
_write(src / "steg.py", "def gjoer_noe():\n return 42\n")
|
||||||
|
_write(
|
||||||
|
src / "verktoey.py",
|
||||||
|
"import argparse\n\nfrom fakesrc.steg import gjoer_noe\n\n\n"
|
||||||
|
"def main(argv=None):\n"
|
||||||
|
" parser = argparse.ArgumentParser()\n"
|
||||||
|
' sub = parser.add_subparsers(dest="kommando")\n'
|
||||||
|
' sub.add_parser("naviger-pakke")\n'
|
||||||
|
" args = parser.parse_args(argv)\n"
|
||||||
|
' if args.kommando == "naviger-pakke":\n'
|
||||||
|
" return gjoer_noe()\n"
|
||||||
|
" return 2\n",
|
||||||
|
)
|
||||||
|
entry = {
|
||||||
|
"kind": "subcommand",
|
||||||
|
"module": "verktoey.py",
|
||||||
|
"scope": "main",
|
||||||
|
"command": "naviger-pakke",
|
||||||
|
}
|
||||||
|
_fake_pyproject(tmp_path, {})
|
||||||
|
ok, why = gate.registered_entry(src, tmp_path, "fakesrc", entry)
|
||||||
|
assert ok is False and ("pyproject" in why or "__main__" in why), why
|
||||||
|
|
||||||
|
_fake_pyproject(tmp_path, {"fake-verktoey": "fakesrc.verktoey:main"})
|
||||||
|
assert gate.registered_entry(src, tmp_path, "fakesrc", entry) == (True, "")
|
||||||
|
|
||||||
|
|
||||||
|
# --- rad 3: de fem veiene sjekkpunktet målte som åpne OG billige å lukke ----------------------
|
||||||
|
|
||||||
|
#: Hver form er base64 av nøyaktig det som ble plantet. Klartekst her ville vært radens eget funn.
|
||||||
|
_MAALTE_AAPNE_VEIER = (
|
||||||
|
("dynamisk-import-modul", "aW1wb3J0bGliLmltcG9ydF9tb2R1bGUoImNsYXVkZV9hZ2VudF9zZGsiKQ=="),
|
||||||
|
("dynamisk-import-modul-anthropic", "aW1wb3J0bGliLmltcG9ydF9tb2R1bGUoImFudGhyb3BpYyIp"),
|
||||||
|
("node-runner-deno", "ZGVubyBydW4gLUEgbnBtOkBhbnRocm9waWMtYWkvY2xhdWRlLWNvZGUgLXAgIngi"),
|
||||||
|
("node-runner-npm-exec", "bnBtIGV4ZWMgQGFudGhyb3BpYy1haS9jbGF1ZGUtY29kZSAtLSAtcCAieCI="),
|
||||||
|
("node-runner-yarn-dlx", "eWFybiBkbHggQGFudGhyb3BpYy1haS9jbGF1ZGUtY29kZSAtcCAieCI="),
|
||||||
|
(
|
||||||
|
"sdk-ts-import",
|
||||||
|
"aW1wb3J0IHsgcXVlcnkgfSBmcm9tICJAYW50aHJvcGljLWFpL2NsYXVkZS1hZ2VudC1zZGsiOw==",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("form", "snippet"), _MAALTE_AAPNE_VEIER, ids=[n for n, _ in _MAALTE_AAPNE_VEIER]
|
||||||
|
)
|
||||||
|
def test_every_form_the_checkpoint_measured_as_slipping_through_is_now_caught(
|
||||||
|
tmp_path: Path, form: str, snippet: str
|
||||||
|
) -> None:
|
||||||
|
"""Tolv indirekte former slapp 19.09; fem av dem var verken eksotiske eller uoppregnelige.
|
||||||
|
Hver plantes alene, med rc-0-kontroll før og etter."""
|
||||||
|
cfg = json.loads(json.dumps(_CONFIG["no_claude_path"]))
|
||||||
|
cfg["manifest"] = {"sentinel": "src/run.py", "sentinels": ["src/run.py"]}
|
||||||
|
_write(tmp_path / "src" / "run.py", "x = 1\n")
|
||||||
|
assert gate.score_no_claude_path(cfg, tmp_path).status == gate.GREEN, "kontroll: ren flate"
|
||||||
|
planted = tmp_path / "src" / "plantet.ts"
|
||||||
|
_write(planted, gate.decode(snippet) + "\n")
|
||||||
|
row = gate.score_no_claude_path(cfg, tmp_path)
|
||||||
|
assert row.status == gate.RED, (form, row.exceptions)
|
||||||
|
planted.unlink()
|
||||||
|
assert gate.score_no_claude_path(cfg, tmp_path).status == gate.GREEN
|
||||||
|
|
||||||
|
|
||||||
|
def test_the_row_limit_is_a_class_description_and_no_longer_an_enumeration() -> None:
|
||||||
|
"""Tredje runde med samme mønster: «NØYAKTIG TO veier» → «TRE veier», begge falsifisert av
|
||||||
|
den første målingen som ble gjort. Setningen skal si hva vakten ER og hva den ikke ser —
|
||||||
|
aldri hvor mange veier som står igjen."""
|
||||||
|
limit = gate.NO_CLAUDE_LIMIT.casefold()
|
||||||
|
assert "to veier står igjen" not in limit
|
||||||
|
assert "tre veier står igjen" not in limit
|
||||||
|
assert "tekstmønstre" in limit or "tekstvakt" in limit
|
||||||
|
assert "uttømmende" in limit
|
||||||
|
assert "git-manifestet" in limit
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue