v0.15.0 — feat(proof): `5dive proof scorecard` — multi-dimensional autonomy metrics by risk tier (DIVE-1914)
Headline capability of the v0.15 "Proof you can trust" minor. Local and read-only, like proof status. The badge (1 − asks/shipped) stays the headline number; the scorecard exists so a single score is not the only score, and therefore not worth gaming.
- new
5dive proof scorecard [--json] [--7d] [--by=tier|class]. Five sourced metrics: human interruptions per accepted outcome, verifier first-pass rate, median recovery time, precedent acceptance rate, and tokens per accepted outcome. - every rendered number is backed by a source PROVEN to exist before the metric shipped. A metric with no source renders
0.0%and reads as "we never get blocked" — a confident zero on the honesty instrument itself.policy-blocked action attemptsandautonomous rollback ratetherefore render an explicit NO DATA marker naming the task that would build the source (DIVE-1922, DIVE-1923), never a number. The unit test's assertions are mostly negative: given empty sources, no number may appear — a happy-path-only test would pass on exactly the build this prevents. - the spec's cost source did not exist, and ground-truthing caught it.
cost per accepted outcome — digest.usage + done countwas specified after confirming digest exposed ausagekey, without checking whether it contained anything. It is[]on every window. Empty is not thin, it is absent, and it would have shipped as a confident 0. - the row is NAMED
tokens per accepted outcome, notcostwith a footnote. A label is a footnote; the row name is the claim, and a reader scanning a scorecard reads names. The output then states as a fact that no money figure exists here because the work runs on a subscription plan — which tells the reader why, not merely that something is missing. This restates the standing stance already incmd_usage.sh: subscription inference has no per-token price, so a$column would be fiction. - tier coverage sits NEXT TO the tier breakdown.
tieris NULL on 73% of shipped work, so a bare 0/1/2 breakdown reads as the shape of the whole while describing a quarter of it. The output leads withtier known for 27% of shipped work (110 of 408)and gives untiered its own visible bucket. The coverage number is the most actionable thing on the row: it says our own tiering discipline is the gap, not the metric. - sample sizes ride ON the number, not in a footnote —
594s (n=1 episode),50% (n=2). A bare rate off n=2 is not a rate. --by=classcomputes what it claims (caught by olivia on review). It was validated as a legal value and then never used — grouping was always by tier, so--by=classemitted"by": "class"beside a breakdown of tier data. Output asserting what the code never computed is precisely the class this verb exists to prevent.--bynow selects the grouping expression and the coverage definition, and the dimension label ships inside the breakdown object so the header and the rows cannot describe different things. Class isproject_key + priorityper the spec, ground-truthed as non-empty on all 408 shipped tasks (100% coverage, against tier's 27%).- the guard for that defect sits at the SHELL layer, because a renderer-level guard was vacuous. The first version asserted on the python renderer — which is handed its rows by the harness and so groups whatever it is given. Reintroducing the defect did not fail it. The assertion now extracts the real
case "$by"block from the shipped source, evaluates both branches and requires they produce different SQL; an inert flag value makes them identical, which is exactly how this hid. Verified by negative control: the reintroduced defect fails two assertions. --30dis refused with a pointer, not silently accepted.digestsupports only--7d; offering a 30-day window here would be a window the verb cannot compute (expansion tracked as DIVE-1921).--jsonreads the globalJSON_MODE.main.shstrips--jsonbefore dispatch, so a local--json)case would have been dead code that silently rendered text.- digest is passed by FILE, not environment — the DIVE-1864
E2BIGtrap one verb along; a live digest exceedsMAX_ARG_STRLENand failed with a bare "Argument list too long". - Numbers come verbatim from
digest/tasks.db/usage_collect. There is deliberately no flag that adjusts one — same no-edit path asproof publish. Nothing is published: extendingzero-human.jsonorbadge.jsonwith any of this is a brand act needing a lodar gate.