Keel

Grounding report.

Every verification edge in requests, classified by who produces the signal and whether the actor being verified can write to that producer.

targetrequests
revision69f84847045bef7a849cc994a26fe7ba8a169e95
generated2026-07-25T03:13:36.804Z
0.92

anchored / (anchored + self_referential + unknown) = 11 / 12

anchored 11 self_referential 0 unknown 1 not_a_check 13 excluded from the denominator

Coverage (judged)

node kindjudged
ci_step10
script10
test_target3
review_gate2

Nodes by kind, over the 25 node(s) carried in this report. Surfaces the gatherer cannot read are absent here rather than counted as unknown, which makes non-coverage Keel's own shoppable class — so it is stated, not implied.

ε-audit — not run

No verdict in this report carries a readable audit block, so nothing was re-decided and there is no agreement rate to report. This is not agreement, and it is not full agreement either: a rate over zero comparisons would state a measurement that was never taken. (“Readable” is load-bearing: a block can be present and state no comparison, and this state covers that too — see below if so.)

This run carries 7 probe-decided verdict(s), none of them contributing a readable comparison. (“Readable” rather than “audited”, because a block can arrive and state nothing — if any did, they are counted below.) Audit coverage is itself shoppable — audit none of the library and no disagreement can ever be found — so the absence is printed here rather than left to be inferred from a missing section.

This run judged 25 of 106 gathered edges. Every number on this page describes that sample, not the whole surface — 81 gathered edges are absent from all of it. A cap is disclosed rather than smoothed: an undisclosed sample is how a ratio gets shopped.

Scope. Keel measures the shape of verification, not its quality. A repo can be 100% anchored with terrible tests. Anchoring says the signal comes from outside; it does not say the signal is sufficient.

Node graph — 25 node(s), in gather order

anchored 11 unknown 1 not_a_check 13

2 marks are ringed in the unknown hue: an anchored verdict asserted below confidence 0.6. The ring says the claim is closer to unknown than its colour suggests. Hover a mark for its node.

anchored — 11

The producer sits outside the write boundary of the actor being verified.

pre-commit hooks (.pre-commit-config.yaml)
anchored
.pre-commit-config.yaml · review_gate

This hook list is not inert declaration: .github/workflows/lint.yml:21-24 installs a pinned pre-commit==4.6.0 and runs `pre-commit run --show-diff-on-failure --all-files` on every push and pull_request, so each hook here executes against the checked-out source and a non-zero hook exit fails the job. ruff-check and ruff-format carry `--fix`/rewrite semantics, but under pre-commit a hook that MODIFIES a file is reported as a failure (file hashes change), so an unformatted or lint-violating file cannot report clean without being changed. The hook implementations are fetched from external repos at pinned revs, so an author cannot alter what check-yaml or ruff decides without moving the pin. Limit: the rev pins and the exclude regex ('docs/|ext/') live in this file inside the write boundary, so the SCOPE of enforcement is author-controlled even though each verdict is computed outside it.

producer the `pre-commit` process exit code in the Lint workflow, which is the aggregate of pinned third-party hook binaries (pre-commit-hooks v6.0.0, ruff v0.15.21) run over the committed tree — actor can write to it: no

  • .pre-commit-config.yaml:5
  • .pre-commit-config.yaml:16-21
  • .github/workflows/lint.yml:21-24

decided by agent · confidence 0.82

test
anchored
Makefile:4 · script

The recipe body is a direct `python -m pytest tests` invocation, so the target's exit status IS the interpreter's verdict on running the committed test modules against src/requests; a failing assertion or an import error cannot be talked into green without editing the code or the tests. Two limits that must travel with this: (1) the ORACLE is inside the write boundary — the same maintainers author src/requests and tests/, so a green run proves the code does what these tests say, not that the tests say the right thing; (2) this particular target is NOT the one CI invokes — .github/workflows/run-tests.yml:37-39 runs `make ci`, so `make test` gates no merge on its own and its value is developer-local execution.

producer the pytest process exit code — CPython executing tests/ and returning non-zero on a failed assertion, error or collection failure — actor can write to it: no

  • Makefile:4-5
  • .github/workflows/run-tests.yml:37-39

decided by agent · confidence 0.78

ci
anchored
Makefile:7 · script

`make ci` is `python -m pytest tests --junitxml=report.xml`, and it is the command every test job in run-tests.yml actually runs (lines 37-39, 59-61, 81-83) across a 3-OS x 8-interpreter matrix plus the no-chardet and urllib3-1.x jobs, on push and pull_request. The pass/fail signal is the interpreter's exit status while executing real code — requests' suite drives a live pytest-httpbin server and real TLS sockets, so failures come from actual socket/HTTP behaviour, not from a recorded claim. The `--junitxml` file is only a report artefact; nothing gates on its contents. Limit: execution is anchored, the oracle is not — implementation and assertions are co-authored in this repo, so drift in both directions stays green.

producer the pytest process exit code produced by CPython executing the tests/ suite inside the GitHub Actions runner — actor can write to it: no

  • Makefile:7-8
  • .github/workflows/run-tests.yml:37-39
  • .github/workflows/run-tests.yml:59-61
  • .github/workflows/run-tests.yml:81-83
  • requirements-dev.txt (pytest-httpbin==2.1.0, trustme)

decided by agent · confidence 0.9

coverage
anchored
Makefile:13 · script

The recipe runs the real suite under coverage; the target's exit status is pytest's verdict on executing the committed tests, and the coverage numbers themselves are derived from the interpreter's line-execution trace, which no one in the repo authors. Limits, all load-bearing here: (1) .coveragerc declares only `omit`, no `fail_under`, so the coverage measurement itself gates NOTHING — it prints a table and writes coverage.xml and the only failure path is a test failing; (2) coverage measures reach, not correctness; (3) no workflow invokes `make coverage`, so this edge blocks no merge — CI runs `make ci` instead.

producer the pytest process exit code (with coverage.py tracing attached) — CPython executing tests/ and coverage.py recording which lines the interpreter actually reached — actor can write to it: no

  • Makefile:13-14
  • .coveragerc:1-2
  • .github/workflows/run-tests.yml:37-39

decided by agent · confidence 0.7

linter/type-checker config ([tool.ruff])
anchored
pyproject.toml:86 · ci_step

This table is not inert: ruff-check and ruff-format read it on every `pre-commit run --all-files` in .github/workflows/lint.yml:21-24, and target-version/src/exclude change which files are analysed and which pyupgrade rewrites apply — so a py310-incompatible construct in src/requests produces a non-zero ruff exit that an author cannot argue away without changing the file. Limit, and it is the sharp one for a config node: the analysis is executed outside the write boundary but the SCOPE is declared inside it — widening `exclude` (already 'docs/', 'ext/') silently shrinks what the gate can ever see, and this table is one of the paths that does that.

producer the ruff binary (pinned at ruff-pre-commit v0.15.21) parsing the committed source under this configuration, and its exit status inside the Lint job — actor can write to it: no

  • pyproject.toml:86-90
  • .pre-commit-config.yaml:16-21
  • .github/workflows/lint.yml:21-24

decided by agent · confidence 0.55 · confidence below 0.6 on an anchored verdict

linter/type-checker config ([tool.ruff.lint])
anchored
pyproject.toml:91 · ci_step

The `select` list here is what ruff-check enforces when pre-commit runs it over --all-files in lint.yml on every push and PR; pyflakes F catches undefined names and unused imports, T10 catches a forgotten breakpoint()/pdb.set_trace(). The verdict for a given file is computed by the pinned ruff binary from the committed bytes, so a violating file cannot report clean without being edited. Limit: `ignore` and `per-file-ignores` sit in this same table inside the write boundary — the author chooses which rules exist, and only then does an outside process decide whether they hold.

producer ruff's exit code — a pinned external analyser deciding, from the source text, whether a selected rule (E/W/F/I/UP/T10) is violated — actor can write to it: no

  • pyproject.toml:91-102
  • .pre-commit-config.yaml:18-19
  • .github/workflows/lint.yml:21-24

decided by agent · confidence 0.78

linter/type-checker config ([tool.ruff.lint.isort])
anchored
pyproject.toml:103 · ci_step

`known-first-party = ["requests"]` parameterises the I rules that .github/workflows/lint.yml runs through ruff-check; with it, an import block ordered wrongly relative to first-party/third-party grouping is rewritten by `--fix`, pre-commit sees a modified file and fails the job. The decision about whether a given file's imports are correctly grouped is made by ruff executing over the file, not by any claim in the repo. Limit: this is a one-line scoping declaration for a rule enforced elsewhere, so it contributes almost nothing on its own — its anchoring is entirely borrowed from the ruff-check invocation, and I have kept confidence low to say so.

producer ruff's isort (I) rule evaluation inside the pinned ruff binary, surfaced as a pre-commit hook failure — actor can write to it: no

  • pyproject.toml:103-104
  • .pre-commit-config.yaml:18-19
  • .github/workflows/lint.yml:21-24

decided by agent · confidence 0.55 · confidence below 0.6 on an anchored verdict

pytest config ([tool.pytest.ini_options])
anchored
pyproject.toml:111 · test_target

Every `make ci` run in the CI matrix loads this table, so it directly determines what the interpreter executes and therefore what the exit code reflects. `addopts = "--doctest-modules"` is a genuine widening of the executed oracle: examples written in docstrings are run and their printed output compared to the literal transcript, so a documented example that stops matching real behaviour fails the suite. Limits: `testpaths = ["tests"]` is author-controlled scope — narrowing it silently shrinks what the anchored runner can ever see — and the doctest transcripts are still authored in-repo alongside the code they document.

producer the pytest collector/runner inside CPython, which this table configures — testpaths decides what is collected and --doctest-modules makes docstring examples executable assertions — actor can write to it: no

  • pyproject.toml:111-116
  • Makefile:7-8
  • .github/workflows/run-tests.yml:37-39

decided by agent · confidence 0.72

linter/type-checker config ([tool.pyright])
anchored
pyproject.toml:118 · ci_step

This table is the operative configuration for `python -m pyright src/requests/` at .github/workflows/typecheck.yml:31-32, which runs on every push and pull_request against Python 3.10 and 3.14. Strict mode makes pyright report unknown/partially-unknown types, so an unannotated or type-incoherent change produces a non-zero exit computed by the checker from the source and the installed stubs — nothing an author can assert their way past without changing the code or loosening this table. Limit: the four `report*: false` suppressions and the `include` list are declared here, inside the write boundary, so the strictness that gets enforced is author-chosen; the verdict under that choice is not.

producer the pyright type checker's process exit code — deterministic static analysis of src/requests under `typeCheckingMode = "strict"` — actor can write to it: no

  • pyproject.toml:118-125
  • .github/workflows/typecheck.yml:31-32
  • .github/workflows/typecheck.yml:3

decided by agent · confidence 0.85

Build dists
anchored
.github/workflows/publish.yml:38 · ci_step

The step shells out to a build or packaging command (python -m build, poetry/hatch/flit build, cargo build, go build, dotnet build, mvn package) which compiles or assembles the committed source. The verdict is computed by that toolchain from the tree: malformed metadata, an unresolvable dependency specifier, a type/compile error, or a layout that does not resolve all produce a non-zero exit, and an author cannot make an unbuildable tree report success without changing the tree. Documented limit: a green build proves the artefact compiles and packages, not that it behaves correctly — it says nothing about runtime semantics.

producer the language's build/packaging backend executing over the checked-out tree, and its process exit code — actor can write to it: no

  • .github/workflows/publish.yml:47-49

decided by probe (packaging-build-invocation) · confidence 0.82

pytest config (tox.ini)
anchored
tox.ini · test_target

`commands = pytest {posargs:tests}` is a direct runner invocation, and tox additionally builds and installs the package into an isolated env before running it, so packaging mistakes (a module missing from the sdist, an unsatisfiable extra) surface as failures too. The verdict is the interpreter's, not a claim the repo makes about itself. Limits: the oracle is co-authored with the implementation as always; and nothing in .github/workflows invokes tox — every CI job shells `make` then `make ci` — so this envlist gates no merge and its 3.10-3.14 matrix is duplicated, not enforced, by run-tests.yml.

producer the pytest process exit code inside each tox-managed virtualenv — CPython 3.10-3.14 executing tests/ against a freshly installed requests plus its security/socks extras — actor can write to it: no

  • tox.ini:1-9
  • .github/workflows/run-tests.yml:33-39

decided by agent · confidence 0.75

unknown — 1

The fork point could not be established. Fails closed — counts against the ratio exactly like self_referential.

code owners (CODEOWNERS)
unknown
.github/CODEOWNERS · review_gate

The file declares that changes to .github/workflows/, CODEOWNERS, __version__.py, HISTORY.md and pyproject.toml belong to two named maintainers, but a CODEOWNERS file only blocks a merge when branch protection has 'Require review from Code Owners' switched on, and that setting lives in GitHub repository configuration that is not in the tree and cannot be read from a clone. So the fork point is untraceable: if protection is on, the producer is a second human's approval outside the PR author's write boundary; if it is off, the file is documentation and produces no signal at all. Failing closed rather than guessing anchored, per the rule for checks whose implementation lives outside the repo.

producer cannot be established from the repository — either a second maintainer's review approval, or nothing at all — actor can write to it: not established

  • .github/CODEOWNERS:4-8

decided by agent · confidence 0.7

not_a_check — 13

Asserts nothing about correctness, so it is excluded from the denominator. This is the one shoppable class: mis-filing a real check here shrinks the denominator and inflates the score, so each row carries the same burden of argument as any other verdict.

init
not_a_check
Makefile:2 · script

The command installs declared dependencies so that later steps can run. Without a lockfile-freezing flag the resolver will refresh a stale lock rather than error, so the step cannot report a discrepancy between the manifest and the lock; its non-zero exits mean the network or the index failed, not that the repository is wrong. The assertion in such a job lives in whatever executes against the installed environment afterwards.

producer a package manager's dependency resolver populating an environment — actor can write to it: not established

  • the run command is a bare dependency install with no --locked/--frozen equivalent

decided by probe (unfrozen-dependency-install) · confidence 0.8

test-readme
not_a_check
Makefile:10 · script

The body is `python setup.py check --restructuredtext --strict && ([ $$? -eq 0 ] && echo ok) || echo "Invalid markup ..."`. If setup.py check exits non-zero the `&&` short-circuits and control falls to `|| echo "Invalid markup..."`, which succeeds — so the recipe's exit status is 0 whether the markup is valid or broken, and the only evidence of failure is a line of stdout nobody reads. A signal that cannot vary carries no information, which is what disqualifies it as a check rather than anything about the producer. Compounding it: the message names README.rst/HISTORY.rst, which no longer exist (README.md, HISTORY.md), and no workflow invokes this target.

producer `echo` — the recipe's last command in every branch, so the shell always hands make a zero exit status — actor can write to it: no

  • Makefile:10-11
  • README.md
  • HISTORY.md

decided by agent · confidence 0.85

publish
not_a_check
Makefile:20 · script

The step's entire effect is promotion: it takes distributions an earlier step produced and uploads them to an index (PyPI, npm, crates.io, a container registry, a GitHub release). It evaluates nothing about the artefact — a failure means the upload was rejected (duplicate version, bad credentials, registry outage), not that the code is wrong. Whatever verification exists lives in the job's `needs:` / environment condition, which is a different edge, so this one must not enter the ratio in either direction.

producer a release/upload command handing a already-built artefact to a package registry — actor can write to it: not established

  • .github/workflows/publish.yml:50-51

decided by probe (artifact-publish-upload) · confidence 0.8

docs
not_a_check
Makefile:25 · script

This target generates a documentation artefact; it asserts nothing about whether requests behaves correctly. SPHINXOPTS is empty in docs/Makefile:5, so sphinx runs without `-W` and its usual findings — broken cross-references, stale autodoc targets, missing toctree entries — are warnings that still exit 0. Nothing downstream consumes the output either: Read the Docs builds from .readthedocs.yaml with its own sphinx invocation rather than this recipe, and no workflow calls `make docs`. Filed here as generation/provisioning rather than `unknown` because the code path is fully readable, not because it was hard to judge.

producer sphinx-build, invoked through docs/Makefile's html target, writing HTML into docs/_build — actor can write to it: no

  • Makefile:25-27
  • docs/Makefile:5
  • .readthedocs.yaml:14-17

decided by agent · confidence 0.62

linter/type-checker config ([tool.ruff.format])
not_a_check
pyproject.toml:106 · ci_step

This table selects a style dialect (quote-style, indent-style) for a REWRITER. Nothing about correctness is asserted by either value: any program is equally valid with either quote character, and swapping them changes no behaviour the interpreter can observe. The pass/fail edge that exists in this repo lives in the pre-commit invocation (a formatter rewrite becomes a hook failure) and is credited there under .pre-commit-config.yaml — counting this table again would credit the same gate twice while the table itself asserts nothing.

producer ruff format's rewriter, which restyles source text to double quotes and space indentation — actor can write to it: no

  • pyproject.toml:106-109
  • .pre-commit-config.yaml:20-21

decided by agent · confidence 0.7

pytest config (conftest.py)
not_a_check
tests/conftest.py · test_target

There is not one assertion in this file: every top-level definition is a fixture that PROVISIONS the environment the real tests run in (a live local HTTP server, a no-SAN certificate, a clean proxy environment). It cannot pass or fail a claim about requests' behaviour; a broken fixture surfaces as an error in whichever test consumes it, and that test's edge is the one already credited under `make ci`. Worth recording rather than dismissing: this provisioning is exactly what makes the pytest edge strong here — a genuine TLS handshake against a real socket is what the runtime gets to decide — but the assertion lives in tests/*.py, not here.

producer pytest fixture functions — an httpbin URL helper, a monkeypatch that clears proxy env vars, and a threaded TLS HTTPServer built from a trustme CA — actor can write to it: no

  • tests/conftest.py
  • requirements-dev.txt (pytest-httpbin, trustme)

decided by agent · confidence 0.68

server.key
not_a_check
tests/certs/valid/server/Makefile:3 · script

The recipe creates a test fixture — a key file — and evaluates no property of requests. Key generation succeeds for any tree state, so its exit code carries no information about the code under test; it is provisioning for the TLS fixtures the suite consumes, and the assertions that use those fixtures live in tests/ under the pytest edge.

producer `openssl genrsa`, generating a 2048-bit RSA private key file — actor can write to it: no

  • tests/certs/valid/server/Makefile:3-4

decided by agent · confidence 0.9

server.csr
not_a_check
tests/certs/valid/server/Makefile:6 · script

A CSR-generation step in a test-fixture directory. It transforms one fixture file into another and asserts nothing about requests' behaviour; a non-zero exit here would mean openssl or cert.cnf is broken, not that the library is wrong.

producer `openssl req`, emitting a certificate signing request from the generated key and cert.cnf — actor can write to it: no

  • tests/certs/valid/server/Makefile:6-7

decided by agent · confidence 0.9

server.pem
not_a_check
tests/certs/valid/server/Makefile:9 · script

This mints the server certificate the TLS tests present; it is fixture construction, not verification. The signing operation succeeds regardless of anything in src/requests, so the recipe's exit code says nothing about the library. Note it is fixture MATERIAL for an anchored edge — the actual verification is a real TLS handshake performed inside the pytest run — but the material itself asserts nothing.

producer `openssl x509 -req`, signing the CSR with the checked-in test CA key and concatenating the CA cert into the bundle — actor can write to it: no

  • tests/certs/valid/server/Makefile:9-11

decided by agent · confidence 0.88

Checkout repository
not_a_check
.github/workflows/publish.yml:25 · ci_step

The step's whole effect is to put files or a toolchain on the runner so later steps have something to act on. It reads nothing about the repository's correctness and emits no pass/fail verdict about the artefact — a failure here means the runner could not fetch or install, not that the code is wrong. The check in such a job is whatever executes afterwards, so this edge must not enter the ratio in either direction.

producer a GitHub Actions provisioning action (repository checkout, toolchain setup, or dependency cache restore) — actor can write to it: not established

  • the step body is a bare `uses:` of a checkout/setup/cache action with no command of its own

decided by probe (ci-provisioning-action) · confidence 0.9

Setup Python
not_a_check
.github/workflows/publish.yml:30 · ci_step

The step's whole effect is to put files or a toolchain on the runner so later steps have something to act on. It reads nothing about the repository's correctness and emits no pass/fail verdict about the artefact — a failure here means the runner could not fetch or install, not that the code is wrong. The check in such a job is whatever executes afterwards, so this edge must not enter the ratio in either direction.

producer a GitHub Actions provisioning action (repository checkout, toolchain setup, or dependency cache restore) — actor can write to it: not established

  • the step body is a bare `uses:` of a checkout/setup/cache action with no command of its own

decided by probe (ci-provisioning-action) · confidence 0.9

Install dependencies
not_a_check
.github/workflows/publish.yml:35 · ci_step

The command installs declared dependencies so that later steps can run. Without a lockfile-freezing flag the resolver will refresh a stale lock rather than error, so the step cannot report a discrepancy between the manifest and the lock; its non-zero exits mean the network or the index failed, not that the repository is wrong. The assertion in such a job lives in whatever executes against the installed environment afterwards.

producer a package manager's dependency resolver populating an environment — actor can write to it: not established

  • the run command is a bare dependency install with no --locked/--frozen equivalent

decided by probe (unfrozen-dependency-install) · confidence 0.8

Upload dists
not_a_check
.github/workflows/publish.yml:43 · ci_step

The step's whole effect is to move a file the pipeline itself produced from one job to another. Nothing about the repository is evaluated: with `if-no-files-found: error` the worst it can report is that an earlier step in the same run did not leave the file it claimed to leave, which is a fact about the plumbing, not about whether the code is correct. Whatever assertion exists in such a job is made by the step that produced the artifact or the step that consumes it, so this edge must not enter the ratio in either direction.

producer a GitHub Actions artifact transport action (upload-artifact / download-artifact) moving files between jobs — actor can write to it: not established

  • the step body is a bare `uses:` of an artifact upload/download action with no command of its own

decided by probe (ci-artifact-transport) · confidence 0.7

Crystallization curve

Keel crystallization curve Crystallization curve over 15 runs from <repo>/reports. falls — total fitted change -66.5% of the mean across 15 runs (R^2 0.28, so the line explains less than half the variance — read the raw squares) CRYSTALLIZATION CURVE 15 sequential runs · 345 nodes judged of 1838 gathered · 124 anchored Keel corpus — 15 repositories, 2026-07-24 — measured corpus (declared on the command line) estimated tokens per node estimated tokens / judged node 0 300 600 run 0 · keel · estimated tokens per node = 498 · judged 25 of 32 gathered run 1 · anthropic-sdk-python · estimated tokens per node = 369 · judged 25 of 41 gathered run 2 · openai-python · estimated tokens per node = 398 · judged 25 of 95 gathered run 3 · vercel-ai · estimated tokens per node = 410 · judged 25 of 1014 gathered run 4 · aider · estimated tokens per node = 204 · judged 25 of 62 gathered run 5 · browser-use · estimated tokens per node = 378 · judged 25 of 104 gathered run 6 · mcp-python-sdk · estimated tokens per node = 592 · judged 25 of 121 gathered run 7 · simonw-llm · estimated tokens per node = 189 · judged 25 of 38 gathered run 8 · tiktoken · estimated tokens per node = 240 run 9 · requests · estimated tokens per node = 290 · judged 25 of 106 gathered run 10 · flask · estimated tokens per node = 408 · judged 25 of 59 gathered run 11 · sinatra · estimated tokens per node = 265 · judged 25 of 59 gathered run 12 · commander-js · estimated tokens per node = 316 run 13 · anthropic-quickstarts · estimated tokens per node = 310 · judged 25 of 62 gathered run 14 · tiktoken · estimated tokens per node = 53 0* 1* 2* 3* 4* 5* 6* 7* 8 9* 10* 11* 12 13* 14 seconds per node measured s / judged node 0.0 60.0 120.0 run 0 · keel · seconds per node = 0.1 · judged 25 of 32 gathered run 1 · anthropic-sdk-python · seconds per node = 15.8 · judged 25 of 41 gathered run 2 · openai-python · seconds per node = 15.4 · judged 25 of 95 gathered run 3 · vercel-ai · seconds per node = 105.9 · judged 25 of 1014 gathered run 4 · aider · seconds per node = 21.3 · judged 25 of 62 gathered run 5 · browser-use · seconds per node = 21.7 · judged 25 of 104 gathered run 6 · mcp-python-sdk · seconds per node = 20.1 · judged 25 of 121 gathered run 7 · simonw-llm · seconds per node = 11.8 · judged 25 of 38 gathered run 8 · tiktoken · seconds per node = 23.7 run 9 · requests · seconds per node = 16.8 · judged 25 of 106 gathered run 10 · flask · seconds per node = 20.5 · judged 25 of 59 gathered run 11 · sinatra · seconds per node = 16.9 · judged 25 of 59 gathered run 12 · commander-js · seconds per node = 18.7 run 13 · anthropic-quickstarts · seconds per node = 20.4 · judged 25 of 62 gathered run 14 · tiktoken · seconds per node = 0.1 0* 1* 2* 3* 4* 5* 6* 7* 8 9* 10* 11* 12 13* 14 probe-decided share measured share of decided nodes 0.00 0.50 1.00 run 0 · keel · probe-decided share = 0.00 · judged 25 of 32 gathered run 1 · anthropic-sdk-python · probe-decided share = 0.00 · judged 25 of 41 gathered run 2 · openai-python · probe-decided share = 0.16 · judged 25 of 95 gathered run 3 · vercel-ai · probe-decided share = 0.08 · judged 25 of 1014 gathered run 4 · aider · probe-decided share = 0.56 · judged 25 of 62 gathered run 5 · browser-use · probe-decided share = 0.32 · judged 25 of 104 gathered run 6 · mcp-python-sdk · probe-decided share = 0.00 · judged 25 of 121 gathered run 7 · simonw-llm · probe-decided share = 0.56 · judged 25 of 38 gathered run 8 · tiktoken · probe-decided share = 0.58 run 9 · requests · probe-decided share = 0.28 · judged 25 of 106 gathered run 10 · flask · probe-decided share = 0.00 · judged 25 of 59 gathered run 11 · sinatra · probe-decided share = 0.28 · judged 25 of 59 gathered run 12 · commander-js · probe-decided share = 0.19 run 13 · anthropic-quickstarts · probe-decided share = 0.24 · judged 25 of 62 gathered run 14 · tiktoken · probe-decided share = 1.00 0* 1* 2* 3* 4* 5* 6* 7* 8 9* 10* 11* 12 13* 14 probe library size measured probes in library 0 15 30 run 0 · keel · probe library size = 0 · judged 25 of 32 gathered run 1 · anthropic-sdk-python · probe library size = 3 · judged 25 of 41 gathered run 2 · openai-python · probe library size = 5 · judged 25 of 95 gathered run 3 · vercel-ai · probe library size = 8 · judged 25 of 1014 gathered run 4 · aider · probe library size = 10 · judged 25 of 62 gathered run 5 · browser-use · probe library size = 12 · judged 25 of 104 gathered run 6 · mcp-python-sdk · probe library size = 13 · judged 25 of 121 gathered run 7 · simonw-llm · probe library size = 15 · judged 25 of 38 gathered run 8 · tiktoken · probe library size = 17 run 9 · requests · probe library size = 19 · judged 25 of 106 gathered run 10 · flask · probe library size = 22 · judged 25 of 59 gathered run 11 · sinatra · probe library size = 23 · judged 25 of 59 gathered run 12 · commander-js · probe library size = 26 run 13 · anthropic-quickstarts · probe library size = 29 · judged 25 of 62 gathered run 14 · tiktoken · probe library size = 29 0* 1* 2* 3* 4* 5* 6* 7* 8 9* 10* 11* 12 13* 14 x axis: run index in the recorded corpus order (listed below). Squares are the raw per-run values; the dashed line is an ordinary-least-squares fit and is never shown without them. The fit is clipped to the panel, and withheld entirely (with the panel saying so) where a straight line would predict values the points cannot take — R^2 for every fit is in the trend notes below. * on run 0, 1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 13 = judged fewer nodes than gathered; per-node values are per JUDGED node. Coverage (judged) ci_step 224 · script 64 · test_target 46 · review_gate 10 · deploy_gate 1 Trend estimated tokens per node: falls — total fitted change -66.5% of the mean across 15 runs (R^2 0.28, so the line explains less than half the variance — read the raw squares). raw first 498 -> last 53. seconds per node: falls — total fitted change -72.9% of the mean across 15 runs (R^2 0.04, so the line explains less than half the variance — read the raw squares). raw first 0.1 -> last 0.1. probe-decided share: rises — total fitted change 144.9% of the mean across 15 runs (R^2 0.21, so the line explains less than half the variance — read the raw squares). raw first 0.00 -> last 1.00. probe library size: rises — total fitted change 187.7% of the mean across 15 runs (R^2 0.99). raw first 0 -> last 29. Run order run order derived from Report.generatedAt (ascending, filename as tiebreak) — no order.json manifest present 0 keel -> 1 anthropic-sdk-python -> 2 openai-python -> 3 vercel-ai -> 4 aider -> 5 browser-use -> 6 mcp-python-sdk -> 7 simonw-llm -> 8 tiktoken -> 9 requests -> 10 flask -> 11 sinatra -> 12 commander-js -> 13 anthropic-quickstarts -> 14 tiktoken Shuffle check shuffle check INCOMPLETE — the curve direction ran, the ratio direction did not Permutation (400 draws, seed 20260724): median |delta normalized slope| 0.633, 92% of draws move it by >= 0.15. A permutation reorders ALREADY-RECORDED runs; it cannot reproduce what a genuine re-run in a different order would have cost, because the probe library would have accumulated differently. It is a sanity signal on order-dependence, never a substitute for the empirical re-run. Ratio stability under shuffle was NOT checked: no re-run supplied (--shuffled) and none declared in corpus.meta.json. Permuting recorded runs cannot move a per-target ratio, so the permutation result below says nothing about it. Supply a re-run with --shuffled <dir>. Disclosures - Skipped 3 file(s) that are not usable run reports: corpus-summary.json (no string "target"); curve.json (no string "target"); keel.bindings.json (no "nodes" array). - Run 0 (keel) judged 25 of 32 gathered nodes — a cap. Per-node cost below is per JUDGED node. - Run 1 (anthropic-sdk-python) judged 25 of 41 gathered nodes — a cap. Per-node cost below is per JUDGED node. - Run 2 (openai-python) judged 25 of 95 gathered nodes — a cap. Per-node cost below is per JUDGED node. - Run 3 (vercel-ai) judged 25 of 1014 gathered nodes — a cap. Per-node cost below is per JUDGED node. - Run 4 (aider) judged 25 of 62 gathered nodes — a cap. Per-node cost below is per JUDGED node. - Run 5 (browser-use) judged 25 of 104 gathered nodes — a cap. Per-node cost below is per JUDGED node. - Run 6 (mcp-python-sdk) judged 25 of 121 gathered nodes — a cap. Per-node cost below is per JUDGED node. - Run 7 (simonw-llm) judged 25 of 38 gathered nodes — a cap. Per-node cost below is per JUDGED node. - Run 9 (requests) judged 25 of 106 gathered nodes — a cap. Per-node cost below is per JUDGED node. - Run 10 (flask) judged 25 of 59 gathered nodes — a cap. Per-node cost below is per JUDGED node. - Run 11 (sinatra) judged 25 of 59 gathered nodes — a cap. Per-node cost below is per JUDGED node. - Run 13 (anthropic-quickstarts) judged 25 of 62 gathered nodes — a cap. Per-node cost below is per JUDGED node. - Run 14 (tiktoken): decidedByProbe + decidedByAgent = 10 but nodesSampled = 12. Probe-decided share uses the decided total (10). - Provenance "measured" was declared on the COMMAND LINE (--provenance), not in <repo>/reports/corpus.meta.json. The declaration therefore lives in the invocation and is only as trustworthy as the run sheet that records it. - Token counts are ESTIMATES in 15 of 15 run(s) (RunEconomics.tokensEstimated). No API exposes session token usage to a skill, so the token axis reads "estimated tokens" and nothing here claims a measured token count. Scope. Keel measures the shape of verification, not its quality. A repo can be 100% anchored with terrible tests. Anchoring says the signal comes from outside; it does not say the signal is sufficient.

Run economics

7 decided by probe
18 decided by agent
28% probe-decided share
25 / 106 nodes judged / gathered
2,479 estimated tokens in
4,775 estimated tokens out
6m 59s wall clock
2 probes minted
19 probe library size

Token counts are estimated, and labelled so: a skill running inside an agent session has no API for its own usage, so the figure is ceil(chars/4) over the judgment payloads and responses. Wall clock and probe-decided share are measured directly. 25 of 106 gathered edges were judged — the cap is printed here and beside the ratio, never applied silently.