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:
Kjell Tore Guttormsen 2026-09-20 07:48:34 +02:00
commit e47be68b57
Signed by: ktg
SSH key fingerprint: SHA256:JakMjO6FTBBzN0Bhfj9saOoEjaFxlSdYuZQQpM/lF9Q
3 changed files with 786 additions and 144 deletions

View file

@ -520,6 +520,27 @@
"pattern_b64": "X19pbXBvcnRfX1woXHMqWyInXShjbGF1ZGVfYWdlbnRfc2RrfGNsYXVkZV9jb2RlX3Nka3xhbnRocm9waWMpWyInXQ==",
"known_positive_b64": "X19pbXBvcnRfXygiY2xhdWRlX2FnZW50X3NkayIp",
"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"
}
]
},

View file

@ -83,14 +83,19 @@ EXTERNAL_DOOR = (
"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 "
"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 "
"nok: PROBENS EGEN KILDE leses, og tre trekk måles i den — at proben rører DØREN (dens "
"dotted modulnavn eller dens registrerte kommandonavn står i et kall der), at den navngir "
"STEGET (symbolet, id-en eller underkommandoen som et eget ord), og at den asserter i det "
"hele tatt. En probe som er DELT mellom to steg beviser høyst ett av dem, og gaten vet ikke "
"hvilket — altså ingen. Valgt å lese probens kilde framfor å kreve at den ligger i en "
"kontrakt-navngitt fil, fordi et filnavn er en konvensjon en stub oppfyller like lett som en "
"ekte probe; målt 19.09 tok fjorten stubber og ÉN urelatert bestått test raden til 17 av 17"
"argparse, i en CLI som selv er registrert eller kjørbar med -m). En art gaten ikke kan "
"verifisere er ikke en dør her. Og et navn er uansett ikke nok: PROBENS EGEN KILDE leses, "
"og det som måles i den er en UTFØRELSE — at en prosess startes med dørens navn i "
"argumentene, eller at inngangen importeres fra dørens modul og kalles, at en assert er "
"DATAAVHENGIG av det kallet (kallets verdi, eller et navn utledet av den, står i asserten), "
"og at kommandolinja som faktisk ble kjørt navngir STEGET når mer enn ett steg står bak "
"samme dør. Er steget ALENE bak sin dør, er det å kjøre døren å kjøre steget, og "
"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.
@ -100,8 +105,10 @@ RUN_PATH_LIMIT = (
"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 "
"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 "
"utledet nevner i det hele tatt; de står navngitt under raden, ett for ett"
"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, 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:
@ -370,77 +377,303 @@ def registered_entry(
return False, f"{module} navngir ingen underkommando"
if not _registers_subcommand(tree, command):
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-]+")
#: 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]:
"""Hver node i kroppen UNNTATT strenger som står alene — docstringen er ikke en atferd."""
stack = [node]
#: Kallene på ``subprocess`` som STARTER en prosess. ``os.system`` og ``os.exec*`` håndteres
#: for seg, fordi navnene deres ikke er en lukket liste.
_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] = []
while stack:
current = stack.pop()
if (
isinstance(current, ast.Expr)
and isinstance(current.value, ast.Constant)
and isinstance(current.value.value, str)
):
continue
out.append(current)
stack += list(ast.iter_child_nodes(current))
def statements(body: Sequence[ast.stmt]) -> None:
for stmt in body:
if (
isinstance(stmt, ast.Expr)
and isinstance(stmt.value, ast.Constant)
and isinstance(stmt.value.value, str)
):
continue
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
def door_strings(nodes: Sequence[ast.AST]) -> set[str]:
"""Strengene proben bruker, docstringen unntatt.
def _target_names(target: ast.AST) -> set[str]:
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
inn etterpå. Et dotted modulnavn er spesifikt at det ER døren uansett hvor det settes
sammen det er nettopp den formen rad 3 selv har en egen vakt for."""
def _strings_in(value: ast.AST, bindings: Mapping[str, set[str]]) -> set[str]:
"""Strengene et uttrykk bærer, variabler fulgt ett steg av gangen."""
found: set[str] = set()
for node in nodes:
for child in _meaningful(node):
if isinstance(child, ast.Constant) and isinstance(child.value, str):
found.add(child.value)
for child in ast.walk(value):
if isinstance(child, ast.Constant) and isinstance(child.value, str):
found.add(child.value)
elif isinstance(child, ast.Name):
found |= bindings.get(child.id, set())
return found
def call_payload(nodes: Sequence[ast.AST]) -> tuple[set[str], set[str]]:
"""(ord, hele strenger) proben sender INN i et kall, pluss navnene den kaller.
def string_bindings(nodes: Sequence[ast.AST]) -> dict[str, set[str]]:
"""``navn -> strengene det kan bære``, som fikspunkt over tilordningene.
To avgrensninger, begge målt fram:
* **Bare det som går inn i et kall.** En docstring som nevner symbolet, eller en kommentar
som navngir modulen, er ikke en atferd. Leses hele funksjonskroppen, er «nevner» nok og
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()
Den ærlige probe-formen bygger kommandoen i en variabel først
(``base = [sys.executable, "-m", "<modul>"]``) og sender den inn etterpå. Uten dette leddet
ville regelen felt kapabilitet den motsatte feilen av den den er skrevet for."""
pairs: list[tuple[str, ast.AST]] = []
for node in nodes:
for call in ast.walk(node):
if not isinstance(call, ast.Call):
continue
func = call.func
if isinstance(func, ast.Name):
words.add(func.id)
elif isinstance(func, ast.Attribute):
words.add(func.attr)
for argument in [*call.args, *[k.value for k in call.keywords]]:
for child in ast.walk(argument):
if isinstance(child, ast.Constant) and isinstance(child.value, str):
constants.add(child.value)
words |= set(_TOKEN.findall(child.value))
elif isinstance(child, ast.Name):
words.add(child.id)
elif isinstance(child, ast.Attribute):
words.add(child.attr)
return words, constants
if isinstance(node, ast.Assign):
pairs += [(n, node.value) for t in node.targets for n in _target_names(t)]
elif isinstance(node, (ast.AnnAssign, ast.AugAssign)) and node.value is not None:
pairs += [(n, node.value) for n in _target_names(node.target)]
elif isinstance(node, ast.NamedExpr):
pairs += [(n, node.value) for n in _target_names(node.target)]
bindings: dict[str, set[str]] = {}
for _ in range(4):
changed = False
for name, value in pairs:
found = _strings_in(value, bindings)
if not found <= bindings.get(name, set()):
bindings.setdefault(name, set()).update(found)
changed = True
if not changed:
break
return bindings
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 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]:
@ -452,68 +685,23 @@ def step_needles(step: Mapping[str, Any]) -> set[str]:
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(
repo_root: Path,
step: Mapping[str, Any],
nodeid: str,
package: str = "portfolio_optimiser",
alone: bool = False,
) -> tuple[bool, str]:
"""(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
registrert stub-dør med en probe pekt 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
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``) en navneteknikalitet."""
file_part, separator, rest = nodeid.partition("::")
if not separator or not rest:
return False, f"proben {nodeid} navngir ingen test"
@ -528,16 +716,27 @@ def probe_binds(
functions = _functions(tree)
if name not in functions:
return False, f"proben {name} finnes ikke i {file_part}"
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)):
live = {n: _live_nodes(functions[n]) for n in sorted(_reachable(functions, name))}
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"
dotted = _dotted(package, str(step["entry"]["module"]))
if not drives_door(tree, nodes, step["entry"], dotted, repo_root):
return False, f"proben {name} rører ikke døren ({dotted})"
use = door_executions(tree, live, step["entry"], dotted, repo_root)
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)
words, _constants = call_payload(nodes)
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, ""
@ -556,12 +755,13 @@ def _binding_why(
repo_root: Path,
package: str,
shared: Mapping[str, Sequence[str]],
alone: bool = False,
) -> str:
for nodeid in probes:
if nodeid in shared:
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"
ok, why = probe_binds(repo_root, step, nodeid, package)
ok, why = probe_binds(repo_root, step, nodeid, package, alone)
if not ok:
return f"atferdsprobe: {why}"
return ""
@ -618,6 +818,11 @@ def measure_steps(
) -> list[Step]:
measured: list[Step] = []
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:
tree = _tree(src, str(step["module"]))
resolved = tree is not None and _defined(tree, str(step["symbol"]))
@ -640,7 +845,9 @@ def measure_steps(
elif verdict:
why = f"atferdsprobe: {verdict}"
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
measured.append(Step(str(step["id"]), str(step["label"]), resolved, called, external, why))
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
#: 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.
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 {}
undeclared = sorted(name for name in calls if name not in declared and name not in held)
n = len(in_path) + len(undeclared)
@ -678,7 +885,7 @@ def score_toolbox(
for name in undeclared
)
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())
) + tuple(
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*(```|~~~)")
#: Rad 3: hva vakten IKKE rekker, sagt av raden selv — og ETTERPRØVD. Erklæringen oppga to
#: 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)
#: er lukket med hver sin vakt; de tre som står igjen står her, navngitt.
#: Rad 3: hva vakten ER, og hva den ikke kan se — sagt av raden selv.
#:
#: Tre runder på rad sa erklæringen hvor mange veier som STO IGJEN («NØYAKTIG TO», så «TRE»), og
#: 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 = (
"rad 3 er en TEKSTVAKT over hele det publiserte manifestet, ikke en dataflyt-analyse: den "
"feller et programnavn skrevet ut, en absolutt sti, en liste lagt i en variabel, en "
"konstant, en shell-streng, den offisielle Python-SDK-en, en node- eller uv-kjører som "
"starter kommandoen, og et dynamisk import-kall. TRE veier står igjen og kan ikke felles "
"linje for linje: et kommandonavn satt sammen av deler ved kjøretid, et navn lest ut av en "
"miljøvariabel, og et navn som er base64-kodet eller på annen måte avkodet først. Raden "
"påstår derfor ikke at ingen vei finnes; den påstår at ingen SKREVET vei finnes, over en "
"nevner 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"
"rad 3 er TEKSTMØNSTRE over hvert spor i git-manifestet, ikke en dataflyt-analyse: den "
"feller et navn som STÅR SKREVET — programnavnet, en absolutt sti, en liste lagt i en "
"variabel, en konstant, en shell-streng, de offisielle SDK-ene i Python og TypeScript, en "
"node-, deno- eller uv-kjører som starter kommandoen, og et dynamisk import-kall i begge "
"skriveformer. Den ser IKKE et navn som blir til først når koden kjører: satt sammen av "
"deler, lest ut av en miljøvariabel, base64-kodet eller avkodet på annen måte. Den ser "
"heller ikke noe utenfor manifestet — en avhengighet, et container-bilde, en tjeneste i den "
"andre enden av et nettkall. Lista over kjørere og SDK-er er ikke uttømmende, og raden "
"påstår ikke at ingen vei finnes; den påstår at ingen SKREVET vei finnes, over en nevner "
"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"
)