Skip to content

serve: /api/v1/artifacts silently truncates at limit=1000 (repo has 1017) — no truncation signal in the response #832

Description

@avrabe

Summary

/api/v1/artifacts caps limit at 1000 (rivet-cli/src/serve/api.rs:459, same
at :630). The rivet repo now holds 1017 artifacts. A client asking for
everything gets 1000 rows back with no indication that 17 were dropped.

Reproduce

$ rivet serve --port 8791 &
$ curl -s "http://127.0.0.1:8791/api/v1/artifacts?limit=1000&origin=all" \
    | python3 -c "import sys,json; d=json.load(sys.stdin); \
        print('total:', d['total'], 'returned:', len(d['artifacts']))"
total: 1017 returned: 1000

Why this is a weak-green defect, not just a cap

A cap is fine. A silent cap is not. The response carries total: 1017 next to
a 1000-element array, so the data needed to detect truncation is present — but
nothing in the response says "this is a partial view", and every consumer that
reads artifacts as the full set is silently wrong. This is the same shape as
the v0.33.0 gate work: the call succeeds, the payload looks complete, and the
missing part is invisible.

Two concrete consequences already visible:

  1. Tests can encode a false premise. serve_integration.rs:569 asserts
    externals_unscoped > 0 on origin=all&limit=1000, i.e. it assumes that
    window contains every artifact. It does not. The externals happen to sit at
    positions 0–3 today, so it passes — by luck of ordering, not by construction.
  2. Ordering is non-deterministic on main, so which 17 fall off the end
    varies run to run. Measured over 3 identical runs against the same store, the
    external block came back PROC, REQ, SYS, THR / REQ, THR, SYS, PROC /
    PROC, REQ, SYS, THR. (Overlapping sources make every id collide with itself (184 phantom errors masked 6 real); non-deterministic output order breaks report diffing #746 / fix(validate): dedupe overlapping sources + deterministic diagnostic order (#746) #759 addresses the determinism half.)

Combine the two and you get a silently truncated, non-deterministically ordered
"give me everything" response — which is a bad foundation for a dashboard, an
export, or an assertion.

Suggested fix

Cheapest honest version, in order:

  1. Say so in the payload. Add truncated: true (or returned alongside
    total) whenever artifacts.len() < total. Consumers can then fail loudly
    instead of guessing.
  2. Make the window deterministic so truncation is at least reproducible —
    stable sort by id when no explicit sort is requested. (Overlapping sources make every id collide with itself (184 phantom errors masked 6 real); non-deterministic output order breaks report diffing #746 / fix(validate): dedupe overlapping sources + deterministic diagnostic order (#746) #759 does this
    for the source/diagnostic side.)
  3. Decide the cap deliberately. Either keep 1000 and document it as a
    paging boundary with a working offset/cursor, or let limit=0/limit=all
    mean "no cap" for the machine-readable endpoint.

Step 1 alone converts this from silent to detectable and is worth doing on its
own.

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions