Skip to content

Run metrics ​

Every finished run writes one flat metrics point into a Workers Analytics Engine dataset, beside the one commit its record already makes (decision 0063). The bot computes the point (pointOf in the node-free src/core/runMetrics.ts) and sends it beside the record on POST /runs/put and POST /runs/finish; the state Worker's RunHistoryDO — the only writer of finished records — decides whether that write is the run's terminal close on an unlatched run (pointEmits, record 0097) and writes the point to the dataset AFTER its transaction, so a run is counted at most once however many times its record is written (the tombstone, a restarting close, the finish, a review-artifact rewrite, a retry), and an unclaimed restart is closed by the store's own sweep. The binding is optional on the Worker and the whole path is advisory: without it, without a point, or with a sink that throws, every answer is byte-identical. The record itself is untouched — the point travels beside it, never on it (run-history.md items 27, 33 and 44 name the three writes this rule governs). The price table is costs.md's costs.prices. On the read side, metrics trend answers the trend report on every command surface from the dataset over the Analytics Engine SQL API — three weighted queries per read, no snapshot — behind the metrics capability (items 7–9), while the review tabs render the distinct metrics reviews report through the protected review adapter. The retained generic trend adapter remains a separate compatibility contract (item 10); the index selects the review adapter for /metrics, /metrics/health and /metrics.json.

Behavior ​

  1. The point. RunMetricsPoint = { indexes: [string]; blobs: string[]; doubles: number[] } — the platform's own positional shape. POINT_COLUMNS is the ONE ordered table naming every position: twenty blobs (schema — the literal "1" — agent, preset, model, status, error type, dominant friction, channel, repository, machine class, route class, reply, lineage, requester, identity, run id, and, appended by record 0097 so no position changes meaning, surface, workspace, setup reason, outcome) and twenty doubles (wall = finishedAt − (receivedAt ?? startedAt); the seven partition terms of diagnosis.shape, zero when absent; turns; the four token counts summed over usage.byModel; dollars = llmUsdOfUsage(usage, prices).usd; steps; tool calls; events = eventCount; unpriced tokens = llmUsdOfUsage(...).unpricedTokens; minutes — the profile's, zero when absent; finished at), the index the agent name (unknown for a record naming none). It is typed so a position added without a name fails to compile, and blobColumn/doubleColumn resolve a name to its blob<n>/double<n> — a reader's query never carries a magic number. pointOf(record, prices?) returns undefined for a record with provisional: true and for nothing else; an absent optional field is the empty string (blobs) or zero (doubles); route class is routed when the record carries a route, else chosen; reply is ok/failed/none off replyOk; lineage is child when parentRunId is present, else root; dominant friction is the FrictionCategory with the largest durationMs among those with a count (dominantFriction), empty when none has one. outcome and error type are outcomeOf(record): the terminal status decides first, then the first matching row of record 0097's table — cancellation for a stop; error with restart_unclaimed or interrupted for an interrupted run; for a failed run failure with policy_refusal, else error with the failure kind, the setup result's lost word, reply_failed, else timeout with a budget ending, else error with unclassified; for a completed run timeout with a budget ending, error with answer_interrupted, cancellation after a stop, else success. Error type is empty exactly for success and cancellation. surface is the channel's namespace when it is slack, mcp, web, http or cli (surfaceOf), else empty; workspace folds the setup result into one word (workspaceWordOf: resident, resident_restored, sandbox_snapshot, sandbox_fresh, sandbox_seed_failed, sandbox_cold, sandbox_blank, local or e2b for a run on that backend, none for a lost word, empty without a setup result); setup reason is the setup result's reason or empty, so a cancellation keeps it. Every blob and the index are capped at 96 bytes, and pointOf never reads activity, label, events, verdict, handoff, dispositions or diagnosis.verdict — no free text reaches the dataset. isRunMetricsPoint accepts exactly one index, sixteen or twenty strings (RUN_POINT_BLOB_WIDTHS: this item's width and the width record 0097 widens the row to, accepted together so a deploy's old and new writers both land) each at most 96 bytes, twenty finite numbers, and nothing else; the Worker validates an incoming point with it and answers 400 by name (point must be a RunMetricsPoint) for anything else.

  2. The emission rule: the terminal close (record 0097, amending 0063's first non-provisional close). A close is terminal when it is neither provisional nor restarting (isTerminalClose). pointEmits(latched, stored) is true exactly when stored is a terminal close and the run's emission latch is unset — there is deliberately no finishedAt > startedAt term (a plain put of an interrupted record over no row is a run counted once). The latch is the runs row's metrics_emitted column, set in the same transaction as the write that emits and never cleared by a later write; rows a store kept before the column existed start latched unless they are provisional, since 0063's rule had already counted them. upsertInTransaction reads the row's provisional and restarting alone from summary_json (never the whole summary) and returns emits beside stored, retained and rewritten. put and finish call sink.write(point) AFTER their transaction — the sendRunFinished placement — when emits && stored and a point was sent; a restarting close, a review-artifact rewrite (run-history.md item 44) and an identical retry write no point, and a record the write itself trimmed (stored: false) writes none. A restarting close's point is kept on its row (restart_point_json) for the sweep in item 3a.

  3. The guard: a provisional record never overwrites a non-provisional row, and a restarting record never overwrites a terminal one. upsertInTransaction answers either as { ok, stored: true, retained, rewritten: false, emits: false } and writes NOTHING — the row, its events and the sessions table stay exactly as they were. This is the store-side floor under run-history.md item 27's in-process stand-down (which is unchanged): without it, a tombstone or drain write landing after a close would replace the run's end, or erase a restarting row's deadline and kept point so the sweep could never count it, and a restarting close a refused ledger finish falls back to put with, landing after the successor's finish, would replace the run's terminal close. 3a. An unclaimed restart is closed by the store. The store's alarm (closeUnclaimedRestarts) finds each unlatched restarting row whose restartUntil has passed while no live_runs row holds its id, and in one transaction per row — re-checking the same deadline and the absent live row — rewrites it as an interrupted terminal close with failure.kind: restart_unclaimed, restarting and restartUntil removed, latched. After the commit it writes the point the restarting close carried, with status interrupted and failure kind restart_unclaimed; the store never prices a record itself. A successor still holding the id keeps the sweep a no-op, and its own terminal close emits the run's only point. 3b. The review row and the store's failed writes (record 0097, "Rows and columns"). A review run's terminal close with a verdict also writes a review row (reviewPointOf), under the run row's emission decision and latch: index review, its own schema word review-1 (so 0063's reader, filtering on "1", never counts it), eight blobs (schema, run id, repository, pull request number, head revision, verdict, requester, surface) and seven doubles (finished at, duration, findings, blocking, major, minor, nit) named in REVIEW_POINT_COLUMNS. The target is the run record's own — repo, targetPr (the repository context's pull request, resolved before the model starts), reviewHead falling back to headSha — never reviewPost.target; a branch review with no pull request writes an empty number. The verdict reads approved or changes_requested. The store computes the row from the record it committed — its clamped stamps, never the input's — after the commit (it needs no price), so an older client's runs get one too. Every row the sink refuses is counted by kind (run, review) in metrics_write_failures, and POST /runs/metrics-health {storeKey} answers { failedWrites: { run, review } }: delivery stays at most once and best effort, and a drop after an accepted write is not detectable here.

  4. The wire. POST /runs/put and POST /runs/finish accept an optional point beside record (parseRunPut); a body without one is exactly today's, and the answers of both routes are byte-identical with and without the binding, with and without a point, and when the sink's write throws (emits stays internal — the put route strips it). WorkerRunStore.put and WorkerRunLedger.finish compute pointOf(record, prices) and send it when defined; buildRunStore and buildRunLedger take prices?: ModelPriceTable in their deps and src/index.ts passes the same table RunsService prices with. InMemoryRunStore and FileRunStore are unchanged — they send no point.

  5. The off state and the sink. RunMetricsSink (write(point): void) has two implementations: AnalyticsEngineSink over the optional Env.RUN_METRICS binding and NullSink, selected at the object's construction — the SHIP_COORDINATOR? shape: the Worker deploys without the binding and nothing changes. The write sits in a try whose catch is ONE console.warn line carrying the run id and the error's constructor name, never the point's contents; it can fail no put and no finish. GET /healthz's features gains runMetrics:<dataset> when the binding is present (featuresOf), the name from the RUN_METRICS_DATASET var rendered beside the binding, so the bot's boot probe can compare names.

  6. The binding in the deploy, and the name held on both sides. The deployment profile's optional metrics: { dataset } (release-and-deploy.md item 14; Analytics Engine's own dataset-name rule — 1–64 letters, digits and underscores, not starting with a digit — refused by field) is what the state Worker template's {{#if metrics}} block renders analytics_engine_datasets: [{ binding: "RUN_METRICS", dataset }] and the RUN_METRICS_DATASET var from, the {{#if artifacts}} shape: a profile without one renders today's file byte for byte, and the committed example names none. deploy plan names the binding in the memory step when the profile names a dataset; no resource is created ahead of the deploy — the platform creates a dataset on first write — and the step needs no extra credential capability. On the other side, the bot's boot probe of the state Worker (the same /healthz read that checks runs) compares its configured metrics.dataset (the metrics: config block; only dataset is read until the reader lands) with the Worker's runMetrics:<dataset> feature (metricsDatasetWarning) and logs ONE [runs] warning when the two differ or when only one side names one — saying which side is missing what — and nothing when both agree or both are absent; neither side refuses to start.

  7. The reader's config and capability. metrics: { dataset, days? } is a config block, validated at load by parseMetricsConfig and refused by field: dataset under the platform's dataset-name rule (item 6's), days a whole number in 1..90 (default 30). The metrics capability is on exactly when the block names a dataset AND the costs block with its Cloudflare analytics token are present — the reader queries the SQL API on the same read-only credential the spend dashboard holds, so no new secret exists (capabilities.md item 1). The three installation fixtures carry the axis (cloud-full on, the others off).

  8. The source and the three queries. MetricsSource is query(sql) → Promise<MetricsRow[]> with three implementations: AnalyticsEngineSqlSource POSTs the text to the account's analytics_engine/sql endpoint with the bearer in the header ONLY and FORMAT JSON appended, parses data, and turns a non-2xx into a MetricsSourceError carrying the status and never the token; InMemoryMetricsSource(points) evaluates the same texts over held points (each stamped at its finished at double, weight 1); NullMetricsSource is the off state. metricsQueries({ sinceMs, untilMs, agent? }, dataset) returns three texts — byDayStatus (day, status, sum(_sample_interval)), byAgent (runs, sumIf(_sample_interval, … = 'failed'), quantileExactWeighted(0.5/0.95) of the wall, dollars, unpriced tokens and turns as sum(double × _sample_interval)), byDayAgentP50 — each over timestamp >= toDateTime(<since>) AND timestamp < toDateTime(<until>); every aggregate is _sample_interval-weighted (no query counts rows or averages a raw column) and every column position resolves from POINT_COLUMNS. The agent filter is the ONLY user string a query can carry: validated against the agent-name rule before interpolation and quoted; the range is two integers.

  9. The report and the command. buildMetricsReport(rows, range) is pure: the tiles (runs, failed, failure rate, p50/p95 wall — the runs-weighted mean of the per-agent quantiles — dollars, unpriced tokens, turns), byDay zero-filled for every UTC day in range with a count per status, byAgent largest first, p50ByDayAgent, and a range naming bucket: "write time", retentionDays: 90, pricing: "at finish", completeness: "at most once"; an empty result is a zero-filled report, never an error. metrics.trend is a registry command (action metrics:read — the costs.by grant shape: a browser session's baseline, a grant for a Slack user or a token, never a chat baseline — effect read, enabledWhen: caps.metrics), options --days 1..90 and --agent; derived forms metrics trend in chat and on the CLI, GET /api/metrics.trend, the metrics_trend MCP tool; the JSON is the report exactly, and the text render is a header (range, agent filter, dataset), the tiles as one ·-joined bullet, one line per agent row largest first, then the footer sentence (bucket, pricing, completeness). Off is unavailable with METRICS_OFF_MESSAGE; a source error is unavailable naming the error's class. MetricsService.report({ days?, agent? }) reads whole UTC days ending today (today included, still filling), the default range from config; NullMetricsService is the off state the catalogue binds without the capability.

  10. The review dashboard and retained trend adapter. The current browser routes are GET /metrics (Usage), GET /metrics/health (System health), GET /metrics/week (Week over week), GET /metrics.json and GET /metrics/week.json, selected by createReviewsMetricsViewHandler in src/channels/metricsView.ts. They are GET-only and use the same protected current-actor and global Mine/Person/Everyone view as other work pages. from and to are whole UTC finish days, both included, at most 90 days; malformed dates or an inverted range return 400 before reading. Every tab and JSON link preserves the exact range. The reviewsMetrics seed and JSON twin carry the same scoped ReviewsReport (item 12); the Week over week seed and /metrics/week.json add its comparison, read with the same scope; filtering happens before aggregation, and current proof is checked again before output. Known access loss returns 403; a provider/source failure or an off reader returns 503 without private details or global totals.

    Usage renders review-run counts, successful-duration populations, daily surface counts, requester/surface durations, the histogram, verdicts and findings. System health renders recorded ratios with good/total denominators, outcomes, workspace/setup/error facts and supplied duration populations. It does not infer uptime. Week over week renders the comparison's two windows, six tiles with the earlier value and change, a side-by-side table (counts and run time change in percent, ratios in percentage points, durations as a signed difference, no data when either side has no population), runs per day beside the same weekday one shift earlier, and runs per requester; an earlier window outside retention is said, never compared with zero. Sampling, legacy/unclassified outcomes, missing populations and unavailable diagnostics stay explicit. No model-price totals or averaged per-agent quantiles are presented as review metrics. These pages use the shared charts and theme; the primary sidebar offers Metrics only when its capability is on. Captures cover the three tabs on desktop and phone in both themes.

    The previous all-agent trend presentation, agent selector and 7/30/90 range pills are retired. The generic metrics.trend command, global service math, shared trend chart/formatting helpers and createMetricsViewHandler remain supported compatibility contracts, with their own days/agent validation and MetricsSeed/JSON parity. They are not the selected browser review dashboard.

  11. Intake telemetry. An opted-in POST /runs/intake emits one intake-1 point to the existing RUN_METRICS binding after a measured receipt is inserted. Both addressed and silent decisions are emitted; receipt replay, legacy callers and unmeasured receipts emit nothing. Sink failures cannot change the committed decision. New clients opt in with telemetry:true only after their run-metrics reader isolates schema 1, so the memory-first rollout cannot contaminate an older bot's completed-run reports. Points carry experiment, arm, model, coordinates, verdict, failure source, latency, usage, estimated cost and Jev probabilities; no prompt, message text or model-written reason. Probe rows are marked separately. Analytics Engine retains telemetry independently of the receipt cleanup policy. The intake report uses sampling weights for event counts, sums and percentiles, names distinct threads as observed when sampling can omit them, and returns unknown cost as null. Optional samples are bounded; explicit thread exclusions keep operator tests separate from ordinary traffic.

  12. The review report (record 0097, "The reader and the page"; src/core/reviewsReport.ts, src/core/reviewsService.ts). reviewsQueries(range, dataset) builds the eight query texts for a half-open range of at most 90 days over the review agent's run rows (schema = "1", index review) and the review rows (review-1): runs by finish day, outcome, surface and requester with the time spent and the largest sample interval; weighted p50/p95 duration of successes by requester, by surface and overall; a whole-minute duration histogram with an open last bucket; review rows by repository, pull request and verdict with findings by severity; runs by finish day, workspace, setup reason and error type; and p50 duration and time to ready by workspace. Every count is sum(_sample_interval) and every quantile quantileExactWeighted, and every function a query calls is one Analytics Engine's SQL reference documents (the histogram's open bucket is an if); every aggregate filters and groups on the row's own finished at, and the scan is bounded by write time a day wider on each side. buildReviewsReport is pure over their rows: a usage section (runs, successes, time spent, durations, by day and surface, by requester, by surface, the histogram, pull requests, reviews per pull request — absent when any row was sampled — verdicts and findings) and a system section (four tiles as good over total — run success rate without cancellations and unrecorded outcomes, warm-start share over runs that asked for a resident and got a workspace (a local or E2B run whose setup reason is a fallback word included), lost at setup, reviews with a verdict — the unclassified and unrecorded counts, outcomes, workspaces by day, setup reasons by class, error types, and the per-workspace durations). Setup reasons are classed by setupReasonClass: every lost word is a failure, record 0097's design words are design, every other word a defect. createReviewsService runs the eight reads in parallel and reuses run aggregates for five minutes while its range includes today (REVIEWS_CACHE_OPEN_MS) and a day once it ended, holds at most 32 ranges, and never reuses a failed read. Enabled human feedback uses the existing retained feedback store and report builder on every read, including closed ranges. A feedback-store failure marks only feedback unavailable. Retained feedback has no requester facts, so Mine, Person and other narrower predicates receive unavailable feedback; unscoped and exact Everyone reports retain feedback under the current host authorization; NullReviewsService refuses with METRICS_OFF_MESSAGE. metrics.reviews (action metrics:read, hidden when caps.metrics is off) answers the report on every surface — metrics reviews in chat and on the CLI, GET /api/metrics.reviews, the metrics_reviews MCP tool — for --from and --to, whole UTC days with both included (default the seven days ending today, at most 90); a day the calendar does not have, a range that ends before it starts or one that runs past 90 days is invalid_input before any read, the off state and a failing source are unavailable, and its text render is a header, a usage bullet, the four tiles as good of total, one line per requester and the footer, chat-safe. With --compare the answer adds comparison (reviewsComparisonOf, src/core/reviewsComparison.ts): the range beside the same range whole weeks earlier (shiftDays, seven for a range of one to seven days), so weekdays line up. A range that includes today is cut in both windows at the latest five-minute mark (REVIEWS_COMPARE_CUT_MS), so a partial day is compared with the same partial day. Both windows are ordinary reports from the same service and cache, without dataset or feedback. An earlier window that starts more than 90 days back is unavailable: "before_retention" and is not read. The text render adds one week-over-week line; a rate whose tile has no eligible runs reads no data, never a measured 0%.

  13. Exact human review feedback. metrics.feedback.enabled opts the bot into a minute refresh loop over the existing durable run-history Worker. excludedActorIds names automation User node IDs and excludedPullRequests names disposable owner/repository#number acceptance cohorts. The native review invitation asks whether the review helped decide what to change. The original run's matching pending-to-accepted transaction retains a compact receipt, including missing optional native identity as a tracking gap, independently of ordinary run retention. Only accepted invitation-bearing publications belong to this cohort; old receipts and uncertain writes do not. The retained receipt is the index and repair source, unique per repository/native review (record 0107).

    The feedback seam is src/core/reviewFeedback.ts, with the in-memory implementation and src/core/reviewFeedbackWorker.ts; deploy/cloudflare-memory/reviewFeedbackStorage.ts owns SQLite retention, leases and snapshots through the Worker's authenticated /runs/review-feedback/{list,claim,complete,cooldown} routes. src/execution/githubReviewFeedback.ts reads GitHub using the bot credential, never a model tool. It verifies the exact review ID, repository, PR, head and body, then fetches every reaction page before replacing the current votes. Partial errors, changed body, inaccessible nodes and pagination failures preserve the previous complete snapshot with an unavailable/changed status. Shared rate-limit cooldown and fenced, expiring collection leases survive restart. Recent publications refresh hourly for seven days; older retained publications refresh daily. Each pass and request is bounded by src/core/budgets.ts. Page loads never read GitHub. Reporting clamps to 90 days; physical cleanup obeys the existing run-history maintenance gate. Required acceptance-journal failure keeps the existing uncertain/no-repost behavior; optional reaction collection cannot change publication or approval.

  14. Human usefulness on the existing metrics report. src/core/reviewFeedbackReport.ts builds exact counts over accepted publications in the same half-open UTC range as the operational report. Only complete snapshots within the report's explicit freshness limits enter usefulness ratios. Each review counts once: positive-only, negative-only or mixed when effective votes exist; otherwise ambiguous-only, unknown-only or unrated. A human using both signs on a review is ambiguous and supplies no effective vote. Bot and configured automation reactions are excluded; missing actor identities remain unknown. Helpful share among clear ratings is positive-only / (positive-only + negative-only), with mixed reviews alongside it. Vote positivity is secondary. Rating coverage is rated reviews / complete fresh reviews; native-link and complete-sync coverage use all eligible tracked publications, including missing identity and failed reads. No denominator yields null, never a zero quality score. Last-known ratings on stale, inaccessible or edited reviews stay separate. Configured acceptance PRs are excluded before any count.

    The same optional feedback object rides MetricsService.report, the Access-gated /metrics page and /metrics.json, and metrics trend on command surfaces. Page loads read the durable store, never GitHub. Feedback-store failure marks only this section unavailable, leaving operational metrics usable. An agent filter other than review makes feedback not applicable. Weekly groups use Monday-start publication weeks in UTC and format version; later votes/removals revise those groups. Repository and findings-bearing/clean groups reuse the same count rules. The detail list shows at most 100 latest reviews while totals cover the complete range; each available link targets the native review body. The page uses Pacific time for refresh/publication display, labels the cohort start and freshness, shows sample counts without quality targets, and exposes gaps rather than treating them as no votes. web/src/components/metrics/ReviewFeedback.vue renders the stored report under the existing range controls.

  15. Feedback investigation through MCP. metrics.feedback is a read-only command under the existing metrics:read grant and metrics capability, exposed as metrics_feedback on MCP and metrics feedback on other command surfaces. It reads exact retained feedback directly, without Analytics Engine queries, a GitHub write or a model run. List queries use the full publication cohort rather than the dashboard's latest-100 detail limit. days selects whole UTC publication days; repo/pr select a target, and rating/state select evidence. Positive/negative filters include mixed and last-known effective votes; state: complete selects current data. Bots, configured automation actors and ambiguous pairs do not count as effective votes. The summary uses the repo/PR/day scope; matching and the page apply rating/state filters. Results use publication order, bounded pages and nextCursor; a cursor that no longer belongs to the same mutable query refuses explicitly rather than silently skipping reviews.

    run retrieves one retained publication and its current/last-known raw reaction snapshot over the full 90-day retention. It is separate from list filters. Exact detail includes native review identity and URL, original review run ID, reviewed head and body hash, read/completion stamps, reaction IDs, actor IDs/types and signs, plus the automation actor IDs excluded from counts. Missing/expired/excluded publications return detail: null, not an empty complete reaction snapshot. Thumb reactions contain no person's explanation; the evidence limit is stated. An investigator can read the native review/PR and ask runs_get for the original run when available, then send a normal authorized Switchboard investigation request with those exact references. Reading feedback does not start that investigation or authorize a fix; repository/run access and dispatch authorization remain their existing gates. Collection must be enabled and the durable store accessible; unavailable data refuses by name. Unexpected read failures emit the report's bounded, redacted server warning while callers receive a generic unavailable response; invalid cursors remain input errors.

Validation criteria ​

CriterionProof
15: the public command finds negative feedback beyond the dashboard limit, returns exact retained reaction evidence by run ID, supports target filters and ordered pagination, marks last-known evidence, and refuses invalid or unavailable queries[unit] src/core/commands/metricsFeedback.test.ts::metrics.feedback::*
15: MCP advertises the new read tool and returns exact native identity only to the metrics-read caller; other command surfaces share parsing, output and authorization[unit] src/core/commandConformance.test.ts::command conformance — catalogue fences::MCP exposes exact review feedback by run ID only after the metrics read grant; src/core/commandConformance.test.ts::command conformance — metrics.feedback::*
15 live: the advertised metrics_feedback tool lists an accepted reaction-bearing review, exact lookup returns its snapshot and native identity, and a read-only investigation follows those references[gap] Requires the containing bot build, enabled collection and a retained publication; local command/adapters prove the contract only.
14: every ratio uses the complete fresh subset; review-level counts, per-review/actor ambiguity, unknown/bot exclusions, old daily freshness, mutable weekly cohorts, acceptance exclusions and bounded details retain exact totals[unit] src/core/reviewFeedbackReport.test.ts::human review feedback report::*
14: the shared service reads stored feedback, isolates its failure, logs a bounded/redacted server-side reason without returning it, keeps an empty cohort's scores null and respects the selected agent[unit] src/core/metricsService.test.ts::createMetricsService::serves exact stored feedback on the shared report and isolates its failures from operational metrics
14: commands preserve the feedback JSON object and distinguish unrated from unavailable feedback in text[unit] src/core/commands/metrics.test.ts::metrics.trend::carries exact feedback into JSON and distinguishes an unrated cohort from unavailable feedback in text
14: the active review page renders the shared human-feedback component with exact ratings and native links, distinguishes an empty cohort from unavailable feedback, and retains review usage tiles[unit] web/green/pages/metrics.test.ts::Green review metrics::shows current human feedback, empty ratings and unavailable feedback beside review usage
14: each review report reads current retained votes even when its closed-range run queries are cached; Mine and Person cannot receive global feedback totals, and feedback failure can recover without invalidating healthy run aggregates[unit] src/core/reviewsService.test.ts::createReviewsService::refreshes exact retained feedback while reusing the closed range's run queries; src/core/reviewsService.test.ts::createReviewsService::keeps feedback storage failure separate from a healthy report and recovers without cached failure
14 live: a disposable review-body vote added and removed changes the same dashboard/JSON cohort once at the deployed build; a real person's first rating confirms ingestion[gap] Requires the configured App, enabled collection and excluded disposable PR at the deployed build. Local fixtures and CI do not prove live ingestion or human usefulness.
13: only the original owner's matching pending-to-accepted transition retains one compact cohort row; native identity is optional, uncertain writes excluded, removed votes replace only complete reads, stale collection owners refused and shared backoff survives the next pass[unit] src/core/reviewFeedback.test.ts::exact review feedback storage::*
13: authenticated Worker routes retain feedback beyond ordinary run deletion, fence late collection owners, preserve previous votes on failure, and refuse malformed input[integration] deploy/cloudflare-memory/reviewFeedback.test.ts::review feedback through the state Worker::*
13: the durable client reads all pages and refuses malformed or looping responses[unit] src/core/reviewFeedbackWorker.test.ts::durable feedback client::*
13: the restart fixture checkpoints the feedback store independently of the run ledger, retaining its own data and constructing a fresh clock rather than serializing functions[unit] src/channels/slackGoldenFlow.test.ts::Slack golden flow::*
13: the native reader verifies review/repository/PR/head/body, bounds token acquisition within the read deadline, cancels an opted-in mint, preserves unknown actors, ignores other emoji, refuses partial reads and honors primary, secondary and HTTP-200 GraphQL rate limits[unit] src/execution/githubReviewFeedback.test.ts::native review-body reactions::*; src/execution/githubApp.test.ts::resolveGithubToken::cancels an opted-in mint request and can mint again after cancellation
13 live: the configured App reads an actual review's reactions; adding/removing a disposable account vote, restart and refresh change the retained snapshot once, without another publication[gap] Requires the additive state Worker, enabled collection, configured App credential and disposable PR at the deployed build.
10: the review page retains successful-run duration qualifiers and marks sampled rows, unproved surface quantiles and unavailable PR distributions explicitly[unit] web/green/pages/metrics.test.ts::Green review metrics::shows review counts and successful duration samples without model cost claims; web/green/pages/metrics.test.ts::Green review metrics::keeps sampled PR distribution and unproved surface quantiles unavailable
10: Review tiles identify recorded counts and successful-duration populations without inventing model prices; shared generic trend and USD formatters remain separate[unit] web/green/pages/metrics.test.ts::Green review metrics::shows review counts and successful duration samples without model cost claims, web/src/lib/metrics.test.ts::the tile and cell words::*
11: intake points omit free text, include either verdict and full coordinates; the report isolates schema and experiment, bounds the decision window, excludes probes by default and weights counts and quantiles; sampled thread counts and unpriced costs are explicit[unit] src/core/intakeMetrics.test.ts::intake telemetry::*
11: a committed receipt emits at most one point, legacy callers and duplicate writes emit nothing, and a throwing sink cannot change receipt persistence; completed-run queries exclude the intake schema[integration] deploy/cloudflare-memory/runLedger.test.ts::run ledger — intake receipts (item 59)::emits telemetry after one committed receipt, never on replay or legacy writes, and contains sink failures; [unit] src/core/metrics.test.ts::metricsQueries — the three texts, pinned::excludes intake telemetry from completed-run aggregates
1: a final record fills every blob and double in POINT_COLUMNS order, the agent as the index; a ship child with usage.turns 14 and a price table yields double14 equal to llmUsdOfUsage of the same inputs and blob13 = "child"[unit] src/core/runMetrics.test.ts::pointOf — every position by its POINT_COLUMNS name::fills the twenty blobs and twenty doubles…, ::a ship child with usage.turns 14…
1: a provisional record yields undefined; a final finishedAt === startedAt record yields a zero wall; an absent optional field is the empty string or zero[unit] src/core/runMetrics.test.ts::pointOf — every position by its POINT_COLUMNS name::a provisional record yields undefined…
1: route present yields blob11 = "routed", absent "chosen"; a failed run carries its status and failure kind[unit] src/core/runMetrics.test.ts::pointOf — every position by its POINT_COLUMNS name::a route on the record yields blob11…, ::a failed run carries its status and failure kind…
1: no free text reaches a blob — a sentinel in activity, label, diagnosis.verdict and an event body appears in no blob; every blob and the index are capped at 96 bytes[unit] src/core/runMetrics.test.ts::pointOf — every position by its POINT_COLUMNS name::free text never reaches a blob…, ::caps every blob and the index at 96 bytes
1: an unpriced model's tokens land in unpriced tokens and dollars counts only the priced ones[unit] src/core/runMetrics.test.ts::pointOf — every position by its POINT_COLUMNS name::an unpriced model's tokens land in unpriced tokens…
1: dominantFriction picks slow_tool { count 3, durationMs 9000 } over failed_tool { count 5, durationMs 800 }; all zero counts yield ""[unit] src/core/runMetrics.test.ts::dominantFriction::picks the category with the largest durationMs…
1: isRunMetricsPoint rejects seventeen blobs, a 97-byte blob, a NaN double, a second index; the Worker answers a malformed point 400 point must be a RunMetricsPoint on put and finish, before any write[unit] src/core/runMetrics.test.ts::isRunMetricsPoint::accepts what pointOf builds and rejects…, deploy/cloudflare-memory/runs.test.ts::run metrics — the point, the guard and the emission rule::a malformed point is refused by name on put and finish…
1: isRunMetricsPoint accepts sixteen and twenty blobs and refuses fifteen, seventeen, nineteen and twenty-one; the Worker stores and writes a twenty-column point beside a sixteen-column one[unit] src/core/runMetrics.test.ts::isRunMetricsPoint::accepts the sixteen-column row and the twenty-column row…, deploy/cloudflare-memory/runs.test.ts::run metrics — the point, the guard and the emission rule::a twenty-column point is stored and written beside a sixteen-column one…
1: outcomeOf decides every status and ending combination record 0097's table lists, and leaves the error type empty exactly for success and cancellation[unit] src/core/runMetrics.test.ts::outcomeOf — the terminal status decides first, then the first matching row::*
1: surfaceOf reads the five namespaces and nothing else; workspaceWordOf names every setup shape, and a local or E2B backend by its own word[unit] src/core/runMetrics.test.ts::surfaceOf::reads the channel's namespace, and nothing for one it does not know, src/core/runMetrics.test.ts::workspaceWordOf — the setup result folded into one word::names each shape the record's table lists, src/core/runMetrics.test.ts::workspaceWordOf — the setup result folded into one word::names a local or E2B backend for every shape it ran, never a Cloudflare sandbox, and keeps a lost workspace none
1: the four appended columns land at blob17–20 with the error type still blob6; a cancellation keeps its setup reason; the sweep's relabel sets error type and outcome[unit] src/core/runMetrics.test.ts::pointOf — the four columns record 0097 appends::*, deploy/cloudflare-memory/runs.test.ts::run metrics — the point, the guard and the emission rule::the terminal close (record 0097)::the sweep closes an unclaimed restart past its deadline as interrupted with restart_unclaimed, writing the run's one point
2: a close is terminal when neither provisional nor restarting; it emits exactly on an unlatched run[unit] src/core/runMetrics.test.ts::the terminal close and the emission latch (record 0097)::a close is terminal when it is neither provisional nor restarting, src/core/runMetrics.test.ts::the terminal close and the emission latch (record 0097)::emits exactly when a terminal close lands on a run whose latch is unset
2: inside workerd — a restarting close writes no point and the successor's terminal close under the same id writes the run's only one; rows kept before the latch count as emitted unless provisional[unit] deploy/cloudflare-memory/runs.test.ts::run metrics — the point, the guard and the emission rule::the terminal close (record 0097)::a restarting close writes no point; the successor's terminal close under the same id writes the run's only one, deploy/cloudflare-memory/runs.test.ts::run metrics — the point, the guard and the emission rule::the terminal close (record 0097)::rows a store wrote before the latch existed count as emitted unless provisional, so a later terminal rewrite writes no second point
2: inside workerd — a tombstone then the finish writes ONE point; a review-artifact rewrite answers rewritten: true, emits: false and an identical retry writes none more[unit] deploy/cloudflare-memory/runs.test.ts::run metrics — the point, the guard and the emission rule::a tombstone then the finish writes one point…
2: a plain put of an interrupted record over no row writes one point; a record the write itself trimmed (stored: false) writes none[unit] deploy/cloudflare-memory/runs.test.ts::run metrics — the point, the guard and the emission rule::a plain put of an interrupted record over no row writes one point…
3: a provisional put over a final row answers stored: true, rewritten: false, emits: false, writes nothing to the row, its events or the sessions table, and a following get returns the final record unchanged[unit] deploy/cloudflare-memory/runs.test.ts::run metrics — the point, the guard and the emission rule::a provisional put over a final row writes nothing…
3: a late restarting write over a terminal row stores nothing and writes no point[unit] deploy/cloudflare-memory/runs.test.ts::run metrics — the point, the guard and the emission rule::the terminal close (record 0097)::a late restarting write never replaces a terminal row, and writes no point
3: a late provisional write over a restarting row stores nothing, so its deadline and kept point survive and the sweep still closes it[unit] deploy/cloudflare-memory/runs.test.ts::run metrics — the point, the guard and the emission rule::the terminal close (record 0097)::a late tombstone over a restarting row writes nothing, so the sweep still closes the orphaned restart
3a: the sweep closes an unclaimed restart past its deadline as interrupted with restart_unclaimed and writes its one point; it leaves a restart before its deadline and one a successor's live row holds[unit] deploy/cloudflare-memory/runs.test.ts::run metrics — the point, the guard and the emission rule::the terminal close (record 0097)::the sweep closes an unclaimed restart past its deadline as interrupted with restart_unclaimed, writing the run's one point, deploy/cloudflare-memory/runs.test.ts::run metrics — the point, the guard and the emission rule::the terminal close (record 0097)::the sweep leaves a restart alone before its deadline, and while a successor's live row holds the id
3b: a review run's terminal close writes its review row beside its run row, once; the row is written without a client point and not without a verdict[unit] deploy/cloudflare-memory/runs.test.ts::run metrics — the point, the guard and the emission rule::the review row (record 0097)::a review run's terminal close writes its run row and its review row; a later write writes neither again, deploy/cloudflare-memory/runs.test.ts::run metrics — the point, the guard and the emission rule::the review row (record 0097)::a review row is written even when the client sent no run point, and not for a run without a verdict
3b: the review row's columns come from the run record's target, never the post; an approval reads approved; a branch review writes an empty number; no row for a non-review, a verdict-less or a non-terminal close[unit] src/core/runMetrics.test.ts::reviewPointOf — the review row::*
3b: the review row is built from the committed record, so a finish dated past the skew limit is written at its clamped time and duration[unit] deploy/cloudflare-memory/runs.test.ts::run metrics — the point, the guard and the emission rule::the review row (record 0097)::the review row is the committed record's: a finish dated past the skew limit is written at the clamped time
3b: a throwing sink is counted by row kind and served on /runs/metrics-health[unit] deploy/cloudflare-memory/runs.test.ts::run metrics — the point, the guard and the emission rule::the review row (record 0097)::a throwing sink is counted by row kind and served on /runs/metrics-health
4: WorkerRunStore.put sends pointOf(record, prices) beside the record and none for a provisional one; WorkerRunLedger.finish does the same; buildRunStore carries deps.prices onto the client[unit] src/core/runStoreWorker.test.ts::WorkerRunStore::put sends the record's metrics point beside it…, src/core/runLedgerWorker.test.ts::WorkerRunLedger::finish posts the record's metrics point beside it…, src/core/runStore.test.ts::…::worker with its bearer → WorkerRunStore…
4, 5: the JSON answers of put and finish are deep-equal across a recording sink, a throwing sink and a Worker without the binding; the throw is one console.warn with the run id and the error's constructor name[unit] deploy/cloudflare-memory/runs.test.ts::run metrics — the point, the guard and the emission rule::a throwing sink leaves the JSON answers deep-equal…
5: features carries runMetrics:<dataset> exactly when the binding is present[unit] deploy/cloudflare-memory/runs.test.ts::run metrics — the point, the guard and the emission rule::features names the dataset only when the binding is present
12: the query texts weight every count and quantile, bound the scan by write time a day wider each side, filter on finish time, read run rows of the review agent and review rows by their own schema word, and refuse a bad dataset, an inverted range or one over 90 days[unit] src/core/reviewsReport.test.ts::reviewsQueries::*
12: every query calls only functions Analytics Engine's SQL reference documents[unit] src/core/reviewsReport.test.ts::reviewsQueries — only documented SQL functions::calls no function outside Analytics Engine's SQL reference
12: setup reasons class as failure, design or defect[unit] src/core/reviewsReport.test.ts::setupReasonClass::names every lost word a failure, the record's design words design, and every other fallback word a defect
12: the report zero-fills days, sums usage, counts reviews per pull request without branch reviews, states each tile as good over total (a local or E2B stand-in for a resident in the warm-start total, a cold one not), classes setup reasons, and suppresses reviews per pull request when sampled[unit] src/core/reviewsReport.test.ts::buildReviewsReport::*
12: eight reads per report; a range including today is reused five minutes and an ended one a day; a failed read is never reused and a bad range reads nothing; the cache is bounded; the off state refuses by name[unit] src/core/reviewsService.test.ts::createReviewsService::*, src/core/reviewsService.test.ts::NullReviewsService::refuses with the metrics-off sentence
12: metrics reviews asks for whole UTC days with --to included and defaults to the seven ending today; refuses a day the calendar does not have, a backwards or an over-90-day range before reading; off and a failing source are unavailable; renders the range, usage, the four tiles and the requesters chat-safe[unit] src/core/commands/metrics.test.ts::metrics.reviews::*
12: --compare shifts by whole weeks, cuts a range that includes today at the same five-minute mark in both windows, compares a closed range whole, keeps a non-empty window after midnight, and names an earlier window outside retention without reading it[unit] src/core/reviewsComparison.test.ts::reviewsComparisonRangesOf::*, src/core/reviewsComparison.test.ts::reviewsComparisonOf::*, src/core/commands/metrics.test.ts::metrics.reviews::--compare adds the same range a week earlier, read through the same service, and one text line, src/core/commands/metrics.test.ts::metrics.reviews::--compare says no data for a rate with no eligible runs on either side, and keeps a measured 0%
10: Week over week reads the scoped report and both comparison windows, its seed and /metrics/week.json agree exactly, and the other tabs carry no comparison[unit] src/channels/metricsView.test.ts::protected review metrics HTTP::serves Week over week as the scoped report and a comparison a week earlier, with exact JSON parity
10: Week over week renders only the seed's comparison (windows, tiles, side by side, days, requesters) and says when the earlier range is out of retention[unit] web/green/pages/metrics.test.ts::Green review metrics::compares the range with a week earlier: tiles, side by side, days and requesters, all from the seed, web/green/pages/metrics.test.ts::Green review metrics::says the earlier range is out of retention instead of comparing with zero, and links the third tab
12 live: the eight texts run against the production dataset, including toDateTime(intDiv(…)) over the finish double, and a 90-day report's uncached latency is recorded[gap] needs the reader's metrics:read grant live (infrastructure PR 195); the first receipt on the tracker
6: metrics.dataset is optional in the profile, kept when valid, refused by field when it breaks the dataset-name rule; the committed example names none[unit] src/deploy/profile.test.ts::the profile's image mode::\metrics.dataset` is optional, kept when valid, and refused by field when it breaks the dataset-name rule`
6: the state Worker template renders the binding and the RUN_METRICS_DATASET var exactly when the profile names a dataset; without one the render equals today's file byte for byte[unit] src/deploy/wranglerTemplate.test.ts::templateView / renderTemplate for a bot-only profile::the committed state Worker template binds the run-metrics dataset (analytics_engine_datasets + RUN_METRICS_DATASET) exactly when the profile names one
6: deploy plan names the RUN_METRICS binding in the memory step exactly when the profile names a dataset[unit] src/deploy/plan.test.ts::planDeploy::the memory step names the RUN_METRICS binding exactly when the profile names a metrics dataset
6: the boot probe warns once on a name mismatch and on a one-sided name — naming both sides — and says nothing when both agree or both are absent[unit] src/core/runStoreWorker.test.ts::metricsDatasetWarning — the name held on both sides::*
7: parseMetricsConfig keeps a well-formed block (30-day default), refuses a bad dataset name or days by field, and validateConfig refuses the block at load[unit] src/core/metrics.test.ts::parseMetricsConfig::*, src/config.test.ts::metrics config::loads a well-formed block and refuses a bad dataset name or days by field
7: the metrics capability is on exactly when the block names a dataset and the costs block's credential is in the env; the three installation fixtures carry the axis[unit] src/core/capabilities.test.ts::capabilitiesFrom — every axis, on and off::metrics: a metrics block naming the dataset AND the costs block with its token…, src/core/capabilitySurfaces.test.ts::capability fixtures — real configurations::*
8: the three query texts are pinned — each _sample_interval-weighted, columns resolved from POINT_COLUMNS (double1, double9, double14, blob5, index1), the range two integers; the agent filter is validated against the agent-name rule and quoted[unit] src/core/metrics.test.ts::metricsQueries — the three texts, pinned::*
8: AnalyticsEngineSqlSource posts the text with the bearer and FORMAT JSON; a 403 is a MetricsSourceError carrying the status and never the token[unit] src/core/metrics.test.ts::AnalyticsEngineSqlSource::*
9: buildMetricsReport over rows from the in-memory source equals the report over the same points computed by hand; a 7-day range over points on three of the days yields seven byDay rows with four zero rows; an agent filter leaves other agents' points out of every table; an empty window is a zero-filled report[unit] src/core/metrics.test.ts::buildMetricsReport over the in-memory source::*
9: the service reads whole UTC days ending today, the default range from config, --days/--agent through to the queries, 1..90 held; the Null Object answers the off message[unit] src/core/metricsService.test.ts::createMetricsService::*, src/core/metricsService.test.ts::NullMetricsService::answers the off message…
9: metrics trend on every surface with the catalogue's guarantees — metrics:read held by a browser session's baseline and by the grant, hidden without the metrics capability; off is unavailable with the off message, a source error unavailable naming its class; the render heads with the range, filter and dataset, one bullet of tiles (a failed share of 2 in 20 renders as 10%), one line per agent largest first, the footer sentence[unit] src/core/commandConformance.test.ts::command conformance — metrics.trend::*, src/core/authz/policy.test.ts::POLICY coverage::*, src/core/commands/metrics.test.ts::metrics.trend::*
10: the retained generic trend adapter matches /metrics and /metrics.json (and nothing else), GET-only; the page carries the report as the seed and the twin answers it exactly; ?days/?agent pass through validated, a malformed one is 400 before any read[unit] src/channels/metricsView.test.ts::parseMetricsRoute::matches the page and its JSON twin, and nothing else, src/channels/metricsView.test.ts::createMetricsViewHandler::*
10: the retained generic trend adapter answers off as 503 with METRICS_OFF_MESSAGE on its page and twin; a failing source is 503 naming the error's class and never its detail[unit] src/channels/metricsView.test.ts::createMetricsViewHandler::answers 503 with the off message when the reader is off — the Null service — on the page and the twin, src/channels/metricsView.test.ts::createMetricsViewHandler::answers 503 naming the error's class when a source fails, never its detail
10: the generic trend helpers retain their weighted chart and provenance mapping; the review page uses the distinct review report below[unit] web/src/lib/metrics.test.ts::runsChartModelOf — the stacked day series::*, web/src/lib/metrics.test.ts::the footer and the hrefs::*
9, 10: shared generic trend helpers draw a gap where a day has no value, one line per agent in the table's order; the tile words are the command's (2 in 20 → 10%, seconds under two minutes)[unit] web/src/lib/metrics.test.ts::trendChartModelOf — the day lines::*, web/src/lib/metrics.test.ts::the tile and cell words::*
10: Metrics is offered only when its capability is on; in-app navigation loads its seed[unit] web/src/components/AppNav.test.ts::Global shell navigation::removes unavailable report and schedule paths while keeping Home, run details, Settings, and docs, web/src/lib/seedRouting.test.ts::installSeedRouting::an in-app navigation to /metrics resolves with its seed
CriterionProof
Hovering a day in the replacement Metrics review chart shows the total and each surface as run counts, without currency formatting. Shared cost charts retain their USD formatter.[unit] web/green/pages/metrics.test.ts::Green review metrics::shows hovered surface totals as counts and preserves the range on the health tab, web/src/pages/costs.test.ts::CostsPage::hovering a day shows a compact tooltip: the day and its total on top, then one row per component, largest first, the estimate tagged; leaving hides it

Current-user page aggregates ​

The retained generic trend adapter and its JSON twin accept the current run-read predicate and global work view before aggregation. Mine selects current proved owner identities; Person keeps the selected person's read ceiling; Everyone retains the actual actor's current permission boundary. Query parameters cannot choose another owner. The existing generic metrics command keeps its unscoped behavior. No global fallback replaces an unavailable personal read, and shared system health remains unavailable to personal views when it cannot be attributed.

The SQL reader compiles this common predicate against stored requester, channel and repository fields. Missing visibility facts remain unknown. The in-memory reader receives the same predicate through the existing query seam, requires a matching canonical query, and applies the reference predicate evaluator before any sums or quantiles. A missing or mismatched scoped predicate fails closed. Point schema, immutable writers, outcome fields, model and pricing formats remain unchanged.

CriterionProof
Mine, read-only Person, authorized Everyone and a denied reader return literal distinct-owner aggregate totals on both page and JSON; forged owner/all query knobs cannot retarget the view.[unit] src/channels/metricsView.test.ts::current work metrics projection::filters Mine, read-only Person and authorized Everyone before aggregation on the page and JSON twin
Unavailable scoped source reads return 503 without global totals or provider details.[unit] src/channels/metricsView.test.ts::current work metrics projection::does not substitute global totals when scoped source reads are unavailable
Stored owner/repository scope filters before aggregation, missing or mismatched scope refuses, and unscoped queries preserve their existing totals.[unit] src/core/metrics.test.ts::scoped metrics aggregation::filters stored owner and repository facts before aggregating and refuses missing or mismatched scope
An empty conjunction remains denied in both SQL and the stored-point evaluator.[unit] src/core/metrics.test.ts::scoped metrics aggregation::keeps an empty conjunction denied in both SQL and stored-point aggregation

Scoped review report reader ​

Browser review reports receive the same current run-read predicate and global data view as other work pages. Every one of the existing eight queries applies that predicate before aggregation. Run and review rows use their own stored requester and repository columns. Scoped repository comparisons are case-insensitive; an empty stored repository is absent, including in exclusion predicates. The legacy global query and default run-metrics translator stay unchanged; a constraint whose fact is absent from the table denies that branch. No point schema or permission rule changes. A scoped service cache includes the actual caller, dataset, range and compiled predicate. Missing scoped proof refuses rather than using the legacy global default; existing unscoped CLI and MCP report calls retain that default. Weighted successful surface counts accompany quantiles so a known lack of samples differs from a measured zero duration; old rows without a population remain unknown.

CriterionEvidence
Every aggregate uses the table's exact requester/repository columns; absent facts deny, supported OR branches remain usable and scope strings cannot alter the SQL[unit] src/core/reviewsReport.test.ts::scoped reviewsQueries::*; src/core/metrics.test.ts::table-specific metrics read predicate::*
Public service reads produce literal separate Mine, Person and Everyone totals over SQLite-filtered stored rows, preserve cache separation across actual callers, datasets and permission changes, exclude missing or case-variant forbidden repositories, allow case-variant authorized repositories, and refuse missing scope[unit] src/core/reviewsService.test.ts::scoped review reports::keeps literal Mine, Person and Everyone totals separate across caller, dataset and permission changes
Failed scoped reads are evicted and can recover without a global fallback[unit] src/core/reviewsService.test.ts::scoped review reports::evicts scoped failed reads without poisoning the same caller's recovery
Surface duration populations distinguish no successful sample from a measured zero[unit] src/core/reviewsReport.test.ts::review duration population::*

Review report tabs ​

Usage and System health render one protected reviewsMetrics seed. They do not combine the generic metrics.trend report with review data. The host must supply the selected current-user scope before aggregation; the page cannot choose another requester. A missing report has no numeric tiles. These frontend proofs use fictional reports, not live Analytics Engine reads.

CriterionProof
Usage renders recorded review counts, successful-duration populations, verdicts and findings without model-price claims; histogram uses the recorded open bucket.[unit] web/green/pages/metrics.test.ts::Green review metrics::shows review counts and successful duration samples without model cost claims
Both tabs retain the exact UTC finish range and JSON address; ninety days render ninety buckets without network reads, with at-most-once/best-effort provenance.[unit] web/green/pages/metrics.test.ts::Green review metrics::renders a ninety-day report without fetching and keeps finish-time provenance, web/green/lib/reviews.test.ts::Review report display::keeps the exact finish range on each tab and preserves supplied daily totals
Sampled PR distribution is unavailable when omitted; missing successful-sample counts remain unknown. Zero duration with a proved population remains valid.[unit] web/green/pages/metrics.test.ts::Green review metrics::keeps sampled PR distribution and unproved surface quantiles unavailable, web/green/lib/reviews.test.ts::Review report display::keeps missing populations unknown, empty populations separate, and zero duration valid
Health shows report ratios with good/total, unclassified and legacy outcomes, and no inferred uptime, write-failure counts or rollout date. Missing/mismatched reports remain separate from successful empty reports.[unit] web/green/pages/metrics.test.ts::Green review metrics::renders health ratios with denominators, legacy outcomes and unavailable diagnostics, web/green/pages/metrics.test.ts::Green review metrics::distinguishes a successful empty report from missing and mismatched seeds
Requester names are display-only and the page contains no local owner selector or mutation controls.[unit] web/green/pages/metrics.test.ts::Green review metrics::renders only the supplied scoped requester rows with protected display names and no owner selector
Actual index wiring, native proof provider, Analytics Engine reads and final theme/mobile captures are qualified on the composed host.[gap] page rendering and injected HTTP reader proofs are local; live composition and captures remain separate integration work

Protected review HTTP adapter ​

The review handler is additive to the retained generic trend adapter. It reads only the authenticated host's actual actor and attached global work view. A fresh native actor/view check precedes aggregation and response. URL knobs never choose a requester or cache subject. Both tabs share the report-only JSON twin. Person requests stay data-only.

CriterionProof
Usage, System health and JSON return literal distinct-owner Mine/Person/authorized Everyone totals with exact report parity; forged owner/all parameters do not retarget the view and non-GET requests are refused.[unit] src/channels/metricsView.test.ts::protected review metrics HTTP::routes the three tabs and their JSON twins without claiming unrelated addresses, src/channels/metricsView.test.ts::protected review metrics HTTP::renders Mine, read-only Person and authorized Everyone with exact JSON parity and ignores forged owner knobs
Missing/unknown actual subjects, absent attached scope, denied readers, unauthorized Everyone and missing or mismatched native proofs refuse before querying.[unit] src/channels/metricsView.test.ts::protected review metrics HTTP::refuses missing, unknown, unscoped, denied and unauthorized Everyone callers before reading
UTC date validation reuses the registry helper, includes the final day, and refuses invalid/duplicate/backward/over-90-day input before reading.[unit] src/channels/metricsView.test.ts::protected review metrics HTTP::keeps UTC ranges exact and refuses invalid dates before any reader call
Final native proof/view loss withholds the completed report; off and provider failures produce unavailable responses with no provider details or global fallback.[unit] src/channels/metricsView.test.ts::protected review metrics HTTP::withholds finished reports after scope loss or native proof failure and hides provider details
Display names include returned requester IDs only, and the JSON twin remains the exact report object.[unit] src/channels/metricsView.test.ts::protected review metrics HTTP::adds names only for returned requesters while the JSON twin remains the exact report

Production review dashboard wiring ​

The browser entry point serves Usage, System health and JSON through the same scoped review service. Each protected read refreshes the original credential through the existing browser context and organization read guard. Names are looked up for returned ids only. The generic trend command remains separate.

CriterionProof
Usage and System health retain desktop and phone captures in both themes[unit] src/docs/screenshotManifest.test.ts::expectedFiles::is one PNG per surface per theme, named <surface>-<theme>.png, src/docs/screenshotManifest.test.ts::capture profiles::preserves all existing desktop outputs and themes
Actual server wiring serves both tabs and JSON for the current native account, with current proof before and after the reader[agent] On the final staging candidate, use an authenticated account to open both tabs and JSON, preserve the date range, verify current-user scope and then authorized selected-person/Everyone scopes. Confirm a lost or unavailable native proof returns no report. Local handler proofs do not establish this live callback.

Local review preview parity ​

CriterionProof
The marked local review preview's two pages and JSON twin carry the same fictional report. The preview transport intercepts the twin before the retained generic fixture.[unit] web/green/frontend.test.ts::Green browser frontend::keeps the marked review preview page and its JSON twin identical
Snapshot evaluation receives one finite initial green fixture session frame; ordinary browser fixture SSE remains open and retains the same literal actor/messages. Production streams are unchanged.[unit] web/green/frontend.test.ts::Green browser frontend::ends only snapshot preview event reads after their real initial session frame