Keel

Grounding report.

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

targetsinatra
revisioncb22afd7902b566b6eaba6c4ea89739494a65d12
generated2026-07-25T03:41:47.762Z
0.88

anchored / (anchored + self_referential + unknown) = 7 / 8

anchored 7 self_referential 0 unknown 1 not_a_check 17 excluded from the denominator

Coverage (judged)

node kindjudged
ci_step11
script11
test_target2
review_gate1

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 59 gathered edges. Every number on this page describes that sample, not the whole surface — 34 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 7 unknown 1 not_a_check 17

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 — 7

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

rspec config (.rspec)
anchored
rack-protection/.rspec · test_target

This node is the declaration of the rack-protection RSpec suite: `.rspec` is the options file every `rspec` invocation loads, and `--require spec_helper` is what pulls in the suite harness. The gating signal is the exit status of that rspec process, which the Ruby runtime computes by executing the committed specs against the committed lib/ — a failing example cannot be talked into green without editing code or specs, and rake's `ruby` helper propagates the non-zero status so the CI step (and, for the non-allow-failure matrix legs, the job) fails. TWO limits are recorded rather than smoothed away. (1) Confidence is deliberately low: the literal text of this node is configuration (`--color`, `--warnings`, `--require spec_helper`) and asserts nothing by itself; the anchoring is inherited from the process this file configures, and the same execution is also represented by the rake `default`/`test` targets, so the anchored count for this suite is arguably double-entered. (2) Execution is anchored, the oracle is not — implementation and specs are authored by the same maintainers, so green proves the code does what these specs say, not that the specs say the right thing.

producer the rspec process exit code, produced when CI runs `bundle exec rake` in rack-protection/ (test.yml:53-55) -> `task(:spec) { ruby '-S rspec' }` (rack-protection/Rakefile:13) — actor can write to it: no

  • rack-protection/.rspec:1-3
  • rack-protection/Rakefile:13
  • .github/workflows/test.yml:50-55
  • rack-protection/spec/spec_helper.rb:1-7

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

default
anchored
rack-protection/Rakefile:73 · script

`task default: :spec` (rack-protection/Rakefile:73) resolves to `task(:spec) { ruby '-S rspec' }` (line 13), and rake's `ruby` helper raises on a non-zero child status, so `rake` exits non-zero whenever an example fails. This is the target CI actually invokes: test.yml:53-55 runs `bundle exec rake` with working-directory rack-protection, and that step is only exempted from failing the job on the explicitly allow-failure matrix legs (ruby-head), so on every stable leg the rspec exit code blocks the run. An author cannot make a failing example report green without changing the code or the spec. Documented limit: the EXECUTION is anchored, the ORACLE is not — the same maintainers author lib/ and spec/, so green means the code does what these specs say, not that the specs say the right thing.

producer the rspec process exit code — the Ruby runtime executing rack-protection/spec against the committed lib/, surfaced through rake's `ruby` helper — actor can write to it: no

  • rack-protection/Rakefile:73
  • rack-protection/Rakefile:12-13
  • .github/workflows/test.yml:50-55
  • .github/workflows/test.yml:38

decided by agent · confidence 0.85

test
anchored
rack-protection/Rakefile:74 · script

`task test: :spec` (rack-protection/Rakefile:74) is a second name for the identical body that `default` reaches, so invoking it runs the committed specs and rake propagates the runtime's non-zero status. Confidence is held below the `default` verdict for one specific reason: CI invokes the bare `bundle exec rake` (the default task), never `rake test`, so this particular NAME is not itself on the merge-blocking path — it is anchored because it executes the same suite, not because anything in this repo gates on it. Same oracle limit as the suite it aliases: execution independence holds, specification independence does not.

producer the same rspec process exit code reached through `task(:spec) { ruby '-S rspec' }` — actor can write to it: no

  • rack-protection/Rakefile:74
  • rack-protection/Rakefile:13
  • .github/workflows/test.yml:55

decided by agent · confidence 0.7

rspec config (.rspec)
anchored
sinatra-contrib/.rspec · test_target

The node declares the sinatra-contrib RSpec suite: `.rspec` supplies the default options every `rspec` run loads, and `--require spec_helper` is what loads the harness for the spec/ tree. The gating signal is that rspec process's exit status, computed by the Ruby runtime from the committed specs and lib/, and propagated by rake's `ruby` helper into the 'Run sinatra-contrib tests' step, which fails the job on all non-allow-failure matrix legs. Two limits, recorded not smoothed: (1) confidence is deliberately low because the literal content of this node is three CLI flags that assert nothing on their own — the anchoring is inherited from the process the file configures; (2) execution is anchored, the oracle is not, since implementation and specs are co-authored in the same repo.

producer the rspec process exit code, produced when CI runs `bundle exec rake` in sinatra-contrib/ (test.yml:156-162) -> `task(:spec) { ruby '-S rspec' }` (sinatra-contrib/Rakefile:9) — actor can write to it: no

  • sinatra-contrib/.rspec:1-3
  • sinatra-contrib/Rakefile:8-11
  • .github/workflows/test.yml:156-162

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

bundle exec rake install:rack-protection
anchored
.github/workflows/release.yml:23 · ci_step

`rake install:rack-protection` depends on the file task for pkg/rack-protection-<version>.gem, whose body is `sh "cd ./rack-protection && gem build rack-protection.gemspec"`, and then runs `sh "gem install pkg/rack-protection-<version>.gem"` (Rakefile:145-181). RubyGems computes both verdicts from the committed gemspec and source: a malformed gemspec, a file listed but missing, or a version/dependency constraint that will not resolve produces a non-zero exit, rake's `sh` raises on it, and the step fails — which stops the job before the `gem push` steps at lines 27-29 ever run. An author cannot make an unbuildable gem report success without changing the tree. Documented limit: this proves the package builds and installs, not that Sinatra behaves correctly; and it gates the release workflow (tag push), not pull-request merges.

producer the `gem build` and `gem install` process exit codes, propagated by rake's `sh` helper — actor can write to it: no

  • .github/workflows/release.yml:22-23
  • Rakefile:145-149
  • Rakefile:171-181

decided by agent · confidence 0.82

bundle exec rake install:sinatra
anchored
.github/workflows/release.yml:24 · ci_step

Same causal path as the rack-protection install step, over the root gem: the `install:sinatra` task depends on the pkg/sinatra-<version>.gem file task (`gem build sinatra.gemspec` in `.`) and then shells `gem install` on the built package (Rakefile:145-181). RubyGems reads the committed gemspec and tree to produce the exit status, rake's `sh` raises on non-zero, and a failure blocks the remaining steps of the release job. Documented limit: buildability and installability only — a green here says nothing about runtime behaviour, and this edge is on the tag-triggered release path, not the PR path.

producer the `gem build` and `gem install` process exit codes, propagated by rake's `sh` helper — actor can write to it: no

  • .github/workflows/release.yml:24
  • Rakefile:131-149
  • Rakefile:171-181

decided by agent · confidence 0.82

bundle exec rake install:sinatra-contrib
anchored
.github/workflows/release.yml:25 · ci_step

Identical structure over ./sinatra-contrib: the file task shells `cd ./sinatra-contrib && gem build sinatra-contrib.gemspec`, then `install:sinatra-contrib` runs `gem install pkg/sinatra-contrib-<version>.gem` (Rakefile:145-181). The verdict is computed by RubyGems from the committed gemspec and source files, rake raises on a non-zero child status, and the failure stops the release job before publication. Documented limit: asserts the package builds and installs, not that the library is correct; and the workflow only runs on `v*` tags (release.yml:3-6), so this gates release rather than merge.

producer the `gem build` and `gem install` process exit codes, propagated by rake's `sh` helper — actor can write to it: no

  • .github/workflows/release.yml:25
  • Rakefile:145-149
  • Rakefile:171-181
  • .github/workflows/release.yml:3-6

decided by agent · confidence 0.82

unknown — 1

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

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

Two things block the fork point here, and neither is resolvable from the clone. First, the file sits at .github/workflows/CODEOWNERS; GitHub reads CODEOWNERS only from the repository root, .github/, or docs/, so on that reading this file is never parsed and requests no reviews at all. Second, even a correctly-placed CODEOWNERS only blocks a merge when branch protection has 'require review from Code Owners' enabled, and that setting is repository configuration I cannot inspect — the implementation of this gate lives outside the tree. Whether it blocks anything therefore turns on state that is not in evidence, and it fails closed rather than being filed as inert (which would quietly shrink the denominator) or as a review gate (which would credit a signal I cannot show exists). If it is enforced, a further question remains open: @sinatra/team-sinatra approving a maintainer's own PR is a reviewer inside the same write boundary as the author.

producer cannot be established — a human review requirement, if one is enforced at all, is produced by GitHub branch-protection settings that live outside the repository — actor can write to it: not established

  • .github/workflows/CODEOWNERS:1
  • .github/ (only workflows/ present; no .github/CODEOWNERS, no root CODEOWNERS, no docs/CODEOWNERS)

decided by agent · confidence 0.6

not_a_check — 17

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.

readmes
not_a_check
rack-protection/Rakefile:16 · script

The step arranges the machine — creates directories, sets ownership and permission bits, or adds a user — so that later steps have somewhere to work. No property of the committed source is read and no comparison is performed, so its exit code varies with the runner's state, never with whether the code is correct. Any assertion in such a job is made by whatever executes against the prepared environment afterwards.

producer shell filesystem/account provisioning commands (mkdir, chmod, chown, adduser, usermod) executed on the runner — actor can write to it: not established

  • the run body consists of filesystem/account provisioning commands with no assertion, comparison, or explicit failure branch

decided by probe (runner-filesystem-provisioning) · confidence 0.75

index
not_a_check
rack-protection/Rakefile:29 · script

The step arranges the machine — creates directories, sets ownership and permission bits, or adds a user — so that later steps have somewhere to work. No property of the committed source is read and no comparison is performed, so its exit code varies with the runner's state, never with whether the code is correct. Any assertion in such a job is made by whatever executes against the prepared environment afterwards.

producer shell filesystem/account provisioning commands (mkdir, chmod, chown, adduser, usermod) executed on the runner — actor can write to it: not established

  • the run body consists of filesystem/account provisioning commands with no assertion, comparison, or explicit failure branch

decided by probe (runner-filesystem-provisioning) · confidence 0.75

all
not_a_check
rack-protection/Rakefile:37 · script

This is `task all: %i[readmes index]` inside `namespace :doc` (rack-protection/Rakefile:15-38), so it is `doc:all`. Descending into both children: `doc:readmes` scrapes leading comments out of lib/rack/protection/*.rb and writes doc/*.rdoc; `doc:index` copies README.md to doc/rack-protection-readme.md. Neither compares anything to anything — they are pure emitters, so the aggregate has no pass/fail axis other than a Ruby exception (a missing file), which is not an assertion about the correctness of the library. Excluded from the ratio because there is no verdict here to be grounded or ungrounded.

producer rake's dependency resolver invoking doc:readmes and doc:index, whose bodies only write files — actor can write to it: not established

  • rack-protection/Rakefile:37
  • rack-protection/Rakefile:16-35

decided by agent · confidence 0.85

doc
not_a_check
rack-protection/Rakefile:41 · script

`task doc: 'doc:all'` (rack-protection/Rakefile:41) is a one-line alias onto the doc namespace whose entire effect is generating doc/*.rdoc and doc/rack-protection-readme.md from source comments and README.md. No rendered output is compared against a committed baseline, no strictness flag turns a documentation warning into an error, and nothing in the chain shells out to a checker — so the target emits artefacts and asserts nothing about correctness.

producer rake delegating to doc:all, i.e. the two file-writing documentation tasks — actor can write to it: not established

  • rack-protection/Rakefile:40-41
  • rack-protection/Rakefile:15-38

decided by agent · confidence 0.85

rack
not_a_check
rack-protection/Rakefile:44 · script

The body reads rack-protection.gemspec, harvests authors from `git shortlog -sn`, a file list from `Dir['lib/**/*']` and the version from Rack::Protection::VERSION, substitutes them into the text, and re-writes the same file (rack-protection/Rakefile:44-70). It is a code generator in the same family as a formatter that only rewrites: there is no comparison stage, no `git diff --exit-code` afterwards, and no exit status that varies with any property of the library other than the substitutions succeeding. Nothing is asserted, so it is excluded from the ratio rather than counted either way.

producer the task body itself, which regex-substitutes fields into rack-protection.gemspec and writes the file back — actor can write to it: not established

  • rack-protection/Rakefile:43-70

decided by agent · confidence 0.85

gemspec
not_a_check
rack-protection/Rakefile:72 · script

`task gemspec: 'rack-protection.gemspec'` (rack-protection/Rakefile:72) is a bare alias whose only dependency is the generator described above, which rewrites the gemspec in place from git and filesystem metadata. Invoking it produces a modified working tree, not a verdict; there is no downstream comparison in the chain that could fail on a property of the code.

producer rake delegating to the 'rack-protection.gemspec' file-rewriting task — actor can write to it: not established

  • rack-protection/Rakefile:72
  • rack-protection/Rakefile:44-70

decided by agent · confidence 0.8

readmes
not_a_check
sinatra-contrib/Rakefile:14 · script

The body globs lib/sinatra/*.rb, extracts the leading `# ` comment block with a regex, and writes each result to doc/<name>.rdoc (sinatra-contrib/Rakefile:14-26). Every branch is an emit: `next` on excluded files, `Dir.mkdir` if the output directory is absent, `File.open(..., 'w')` to write. Nothing is compared against a baseline and no exit status varies with a property of the library, so the target asserts nothing about correctness and is excluded from the ratio.

producer the task body, which scrapes leading comments from lib/sinatra/*.rb and writes doc/*.rdoc — actor can write to it: not established

  • sinatra-contrib/Rakefile:14-26

decided by agent · confidence 0.85

index
not_a_check
sinatra-contrib/Rakefile:28 · script

The step arranges the machine — creates directories, sets ownership and permission bits, or adds a user — so that later steps have somewhere to work. No property of the committed source is read and no comparison is performed, so its exit code varies with the runner's state, never with whether the code is correct. Any assertion in such a job is made by whatever executes against the prepared environment afterwards.

producer shell filesystem/account provisioning commands (mkdir, chmod, chown, adduser, usermod) executed on the runner — actor can write to it: not established

  • the run body consists of filesystem/account provisioning commands with no assertion, comparison, or explicit failure branch

decided by probe (runner-filesystem-provisioning) · confidence 0.75

all
not_a_check
sinatra-contrib/Rakefile:36 · script

`task all: %i[readmes index]` sits inside `namespace :doc` (sinatra-contrib/Rakefile:13-37), so it is `doc:all`. Its two children generate doc/*.rdoc from source comments and copy README.md to doc/sinatra-contrib-readme.md. The aggregate inherits their shape exactly: files are produced, nothing is verified, and there is no pass/fail axis to ground.

producer rake's dependency resolver invoking doc:readmes and doc:index, both of which only write files — actor can write to it: not established

  • sinatra-contrib/Rakefile:36
  • sinatra-contrib/Rakefile:14-34

decided by agent · confidence 0.85

actions/checkout@v4
not_a_check
.github/workflows/release.yml:16 · 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

ruby/setup-ruby@v1
not_a_check
.github/workflows/release.yml:17 · 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

rubygems/configure-rubygems-credentials@v1.0.0
not_a_check
.github/workflows/release.yml:21 · ci_step

The step's whole effect is authentication setup for the later `gem push` steps: the job requests `id-token: write` (release.yml:13-14) and this action turns that token into credentials on the runner. A failure here means the OIDC exchange or the trusted-publishing configuration is wrong, never that the gems are wrong — it evaluates no property of the checked-out tree. It is provisioning, in the same family as checkout and setup-ruby, so it must not enter the ratio in either direction.

producer the rubygems credential-configuration action exchanging the job's OIDC token for a RubyGems API key and writing it to the runner — actor can write to it: not established

  • .github/workflows/release.yml:21
  • .github/workflows/release.yml:13-14

decided by agent · confidence 0.85

bundle exec rake release:rack-protection
not_a_check
.github/workflows/release.yml:27 · ci_step

`rake release:rack-protection` depends on `package:rack-protection` (a .gem and a `git archive` tarball) and then runs `gem install <pkg> --local && gem push <pkg>` (Rakefile:183-192). Its distinctive effect is promotion: uploading the package to the public index. A failure means the upload was rejected — duplicate version, bad credentials, registry outage — not that the code is wrong. The one assertion inside the body, that the built gem installs, is not new information here: the install: steps at lines 23-25 of the same job already ran exactly that against the same pkg/ artefacts and are classified anchored on their own, so counting it again would double-enter one signal. Recorded openly because not_a_check is the shoppable class: the promotion reading is what excludes this node, not the difficulty of judging it.

producer `gem push` handing an already-built .gem to rubygems.org (plus a redundant local install of the same package) — actor can write to it: not established

  • .github/workflows/release.yml:26-27
  • Rakefile:183-192
  • .github/workflows/release.yml:23-25

decided by agent · confidence 0.62

bundle exec rake release:sinatra
not_a_check
.github/workflows/release.yml:28 · ci_step

Same shape as the rack-protection release step: `release:sinatra` builds/packages then `gem install --local && gem push` (Rakefile:183-192). The step's purpose and its distinctive failure modes are publication-side (version already on rubygems.org, credential exchange, registry availability), none of which is a statement about whether the checked-out code is correct. The build-and-install assertion that shares the body is already carried by the `install:sinatra` edge earlier in the same job, so this node is excluded rather than counted twice.

producer `gem push` handing the already-built sinatra .gem to rubygems.org — actor can write to it: not established

  • .github/workflows/release.yml:28
  • Rakefile:183-192

decided by agent · confidence 0.62

bundle exec rake release:sinatra-contrib
not_a_check
.github/workflows/release.yml:29 · ci_step

`release:sinatra-contrib` resolves through the root Rakefile's release namespace to `gem install <pkg> --local && gem push <pkg>` (Rakefile:183-192). The step promotes an artefact an earlier step produced; its exit status reports whether the index accepted the upload, not whether the library behaves. The genuine build/install gate for this package is the separate `install:sinatra-contrib` step (release.yml:25), which is where that signal is counted.

producer `gem push` handing the already-built sinatra-contrib .gem to rubygems.org — actor can write to it: not established

  • .github/workflows/release.yml:29
  • Rakefile:183-192
  • .github/workflows/release.yml:25

decided by agent · confidence 0.62

actions/checkout@v5
not_a_check
.github/workflows/test.yml:42 · 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

ruby/setup-ruby@v1
not_a_check
.github/workflows/test.yml:43 · 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

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 / 59 nodes judged / gathered
1,747 estimated tokens in
4,890 estimated tokens out
7m 04s wall clock
1 probes minted
23 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 59 gathered edges were judged — the cap is printed here and beside the ratio, never applied silently.