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 så 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 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]:
@ -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 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
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("::")
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"
)