fix(serve): truncation signal on /api/v1/artifacts and /api/v1/diagnostics (REQ-303, #832) - #864
Conversation
…ion signal (REQ-303, #832) ## The gap `/api/v1/artifacts` caps `limit` at 1000 and this repo now holds 1017 artifacts, so a client asking for everything received 1000 rows with no indication that 17 were dropped. The response carried `total: 1017` next to a 1000-element array — the data was present, but nothing marked the payload as partial, and every consumer reading `artifacts` as the full set was silently wrong. Same shape at `/api/v1/diagnostics`, which uses the same `.min(1000)` cap on its `limit`. The immediate downstream cost is already visible: `serve_integration.rs` tests assert `externals_unscoped > 0` on `origin=all&limit=1000`, i.e. they assume that window is the full set — a premise that holds today only because the externals happen to sort into positions 0-3. ## The fix Step 1 from the issue: emit `count` and `truncated` alongside `total` on both responses, matching the shape `rivet query --format json` already uses (see `schemas/json/query-output.schema.json`). Consumers can now fail loudly on a partial view instead of quietly reading it as the full set. Additive-only — existing clients that read only `total` and `artifacts` are unaffected. The determinism half (#746 / #759) and the cap-as-paging-boundary decision remain out of scope for this change; Step 1 alone converts silent truncation into detectable truncation, and lands cleanly on its own. Fixes: REQ-303 Verifies: REQ-303 Refs: #832
Wrap the `.expect(...)` calls and the `assert_eq!` label onto multiple lines so rustfmt is happy. No behavior change. Trace: skip
📐 Rivet artifact deltaNo artifact changes in this PR. Code-only changes (renderer, CLI wiring, tests) don't touch the artifact graph. |
|
Standing down on the What's failing: Why it isn't this PR's: the diff only touches Fix: filed separately as #865 (two-line struct-init update). Not porting into this PR since #865 lands on Not spending the one re-run — this is a deterministic compile error, not a flake. Generated by Claude Code |
…p (REQ-303, #832) Previous version asserted `count == total` on `limit=100000`, which assumed the endpoint returns everything on a large window. It doesn't: `.min(1000)` caps `limit` internally (rivet-cli/src/serve/api.rs:459 and :630) — the very defect this signal is being added to make legible. On the CI fixture (total=1024) the "full window" call therefore returned 1000 rows and the assertion failed. Restructure both `api_artifacts_truncation_signal` and `api_diagnostics_truncation_signal` to check the shape rather than a specific full-vs-partial state: - Small window (`limit=1`) forces `truncated: true` on any non-trivial fixture, and `count == 1`; assert this unconditionally for artifacts (fixture is guaranteed > 1), and branch on `total > 1` for diagnostics (fixture is not guaranteed to have any). - Full window (`limit == total`) is exercised only when `total` fits under the endpoint's `.min(1000)` cap. When it doesn't, an untruncated response is unreachable from the endpoint at all — the exact case this signal exists to make legible — so we skip that leg rather than assert an impossibility. Verified locally against the real fixture: both tests pass. Trace: skip
Kani Proofs on this PR's head is red for a compile error in code the diff doesn't touch (rivet-core/src/proofs.rs:216, missing exempt / exempt_ids fields added by REQ-309 / #848 to `CoverageEntry`). The fix is already up at #865. Ported here so this PR's Kani stops being red on an unrelated failure; no-ops once #865 merges. Fixes: REQ-309 Refs: #848, #865
|
CI Gate green on One advisory red remains: Mutation Testing (rivet-core, PR-diff) — same finding I posted on #865: Line 209 is the Generated by Claude Code |
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
…eps causing it REQ-303 shipped in #864 and was still `proposed`. That is the fifth instance in one release cycle — REQ-309 (#850), REQ-299 (6b3be3c), REQ-295 (#830), REQ-304 and REQ-305 (#863, found still proposed within the hour), and now REQ-303 — all mine, all caught only by manual audit. REQ-315 records the cause rather than the instances. A trailer is a LINK, not a state transition: `rivet commits` checks that trailers reference real artifacts and nothing checks the converse, that an artifact a merged commit claims to implement is no longer sitting at `proposed`. Release readiness is defined as a query over `release:` + `status`, so the effect is that shipped scope reads as unshipped and a human has to re-derive the truth from git log — the same defect class this tool exists to prevent, in its own artifacts. The proposed check reports; it does not auto-advance. Whether shipped code discharges every acceptance clause is a judgement (REQ-308), and a tool that flipped status on the strength of a trailer would manufacture precisely the false confidence REQ-308 is about. Confirmed with rivet validate (exit 0), rivet docs check (exit 0), 285 artifacts, no duplicate ids. Implements: REQ-303 Refs: REQ-308, REQ-315
plan(v0.35): cut what shipped, split the remainder (maintainer decision) Maintainer approved "cut and split". v0.35.0 goes out with what is done; the rest moves with a logged decision rather than silently. v0.35.0 keeps 12 artifacts. Three verified (REQ-274/275/276, the customer UI trio) and nine implemented: REQ-299, 304, 305, 309, 310, 311, 312, 313, plus REQ-303 which is held here while its PR (#864) is in flight. Every issue a human reported this cycle — #848, #852, #853, #854, #856 — is fixed in this set, which is the release's identity. The implemented ones stay `implemented` rather than being rounded up to `verified`: each carries a named undischarged clause (the --fail-under policy on REQ-309, the git: fallback on REQ-312, release-status categories on REQ-313). Shipping them is not the same as claiming their acceptance is complete. Holding the release until every clause is discharged would recreate, at release scope, exactly the deadlock REQ-313 exists to detect: a criterion that can only be discharged by a release shipping. v0.36.0 takes 4 — the gate-potency and verification-evidence cluster that keeps costing diagnosis mid-tick: REQ-295 (nested-workspace scan, Defect 1 already shipped in #830), REQ-306 (#807 Defect 2, name-existence vs command-reachability), REQ-307 (#812 residual, 52 flat top-level commands), and REQ-314 (27 test files resolve the binary at runtime, so nextest with a custom CARGO_TARGET_DIR cannot find it). Backlog takes 9, in two groups. Older features that have not converged in three weeks — REQ-281, 282, 285, 286 date from Aug 4-7, plus REQ-292, 296, 301 — and two items that need a design decision before they can be scheduled at all: REQ-300 (assisted authoring) and REQ-308 (per-clause verification, which needs clauses to become addressable and is therefore a schema migration across every artifact). Putting a version label on those would move the stall, not resolve it. Also corrects the fourth instance of status drift this cycle: REQ-304 and REQ-305 shipped in #863 earlier today and were still `proposed`. REQ-299 (shipped 6b3be3c) and REQ-309 were corrected previously. Shipping code and leaving the artifact behind is now a recurring failure of mine, not an isolated slip. Confirmed with rivet validate (exit 0), rivet docs check (exit 0), YAML parses, 284 artifacts, no duplicate ids. Refs: REQ-303, REQ-304, REQ-305, REQ-314 plan: flip REQ-303 to implemented; file REQ-315 for the drift that keeps causing it REQ-303 shipped in #864 and was still `proposed`. That is the fifth instance in one release cycle — REQ-309 (#850), REQ-299 (6b3be3c), REQ-295 (#830), REQ-304 and REQ-305 (#863, found still proposed within the hour), and now REQ-303 — all mine, all caught only by manual audit. REQ-315 records the cause rather than the instances. A trailer is a LINK, not a state transition: `rivet commits` checks that trailers reference real artifacts and nothing checks the converse, that an artifact a merged commit claims to implement is no longer sitting at `proposed`. Release readiness is defined as a query over `release:` + `status`, so the effect is that shipped scope reads as unshipped and a human has to re-derive the truth from git log — the same defect class this tool exists to prevent, in its own artifacts. The proposed check reports; it does not auto-advance. Whether shipped code discharges every acceptance clause is a judgement (REQ-308), and a tool that flipped status on the strength of a trailer would manufacture precisely the false confidence REQ-308 is about. Confirmed with rivet validate (exit 0), rivet docs check (exit 0), 285 artifacts, no duplicate ids. Implements: REQ-303 Refs: REQ-308, REQ-315
Closes #832 — Step 1 (the "cheapest honest version" the issue calls out).
The gap
/api/v1/artifactscapslimitat 1000 and this repo now holds 1017 artifacts, so a client asking for everything gets 1000 rows with no indication that 17 were dropped. The response carriestotal: 1017next to a 1000-element array — the data needed to detect truncation is present, but nothing marks the payload as partial, and every consumer readingartifactsas the full set is silently wrong. Same shape at/api/v1/diagnostics, which shares the.min(1000)cap.Downstream cost already visible in the tree:
serve_integration.rsassertsexternals_unscoped > 0onorigin=all&limit=1000— it assumes that window is the full set, a premise that holds today only because the externals happen to sort into positions 0-3.The fix
Emit
countandtruncatedalongsidetotalon both responses, matching the shaperivet query --format jsonalready uses (schemas/json/query-output.schema.json—count,total,truncated). Consumers can now fail loudly on a partial view instead of quietly reading it as the full set.{ "total": 1017, "count": 1000, "truncated": true, "artifacts": [...] }Additive-only: existing clients that read only
totalandartifactsare unaffected. The Playwright tests readdata.totalanddata.artifacts.lengthand stay green as-is. The dashboard/serve JS does not consume these endpoints.Deliberately out of scope
limit=0/limit=allon the machine-readable endpoint is a policy decision the issue calls out as Step 3 — separate PR.Step 1 alone converts silent truncation into detectable truncation and stands on its own.
Verification
cargo check -p rivet-cli --tests— clean build.serve_integration.rs:api_artifacts_truncation_signal— full window:count == total,truncated == false; small window (limit=1):count == 1,truncated == true.api_diagnostics_truncation_signal— same shape check; the small-window assertion is guarded ontotal > 1since the fixture doesn't guarantee any diagnostics.// rivet: verifies REQ-303marker.Commit trailers
Fixes: REQ-303,Verifies: REQ-303,Refs: #832.Generated by Claude Code