Skip to content

Release 1.11.0 — a failed query no longer looks like an empty one

Choose a tag to compare

@Robbie1977 Robbie1977 released this 02 Aug 17:47
· 7 commits to main since this release
a1a6563

A failed query no longer looks like an empty one

run_query returns count: -1 when the query did not run — Owlery errored, or the retries were exhausted. It is not a "too many to count" sentinel, and it is unrelated to the preview -1 in get_term_info. A 400 Bad Request returns count: 0, so zero really is zero.

To a model, count: 0, rows: [] and count: -1, rows: [] differ by one character and mean opposite things: no matching rows versus we have no idea. Worse, VFBquery reports the failure over HTTP 200, so the cache in front of it stores that failure as a valid answer for CACHE_STALE_TIME — six months. One transient Owlery blip could be served as a confident "there are none" for half a year.

count_status

Every run_query result now carries count_status:

value meaning
exact the upstream count is a real total
row_count no total was supplied; the count is the number of rows returned
unavailable the query failed — the count is meaningless

The raw count is passed through unaltered, so any consumer already guarding on count >= 0 behaves exactly as before. count_status is what new consumers should read.

A failed result also gains an unmissable _note stating in plain terms that this is not an empty result and must not be reported as "0", "none" or "no results".

Two shaping bugs fixed

  • The paging note was gated on arithmetic that -1 fails. It was emitted only when total > offset + returned; for a failure that is -1 > 25, which is false. The one result that most needed an explanation was the one that got none. There is now no paging arithmetic at all on a failed result, so nothing ever renders "of -1".
  • The payload was rebuilt from a fixed key list, silently dropping capped, truncated, warnings and anything VFBquery adds in future — precisely the flags that qualify how much of a result you are actually looking at. Shaping now destructures-and-spreads, so unknown top-level keys survive. Scalars and _note stay ordered ahead of the bulk rows; a caveat printed after 25 rows of data is a caveat that gets skimmed past.

force_refresh

get_term_info and run_query both accept an optional force_refresh, which sets X-Force-Refresh on the upstream call. run_query also retries a -1 once, automatically — re-running the identical call would only replay the cached failure.

Important

Deployment prerequisite. nginx honours X-Force-Refresh only from a whitelisted client IP. Verified from outside the whitelist: a forced GET still returns x-cache-status: HIT — the header is silently ignored. Unless this server's egress IP is on v3-cached's whitelist, force_refresh and the automatic retry are no-ops. They are harmless when ignored, and the annotation half of this release works regardless, but the recovery half does not. A forced call that comes back as a cache HIT now logs a warning, so this is visible rather than mysterious.

Tests

Shaping moved to src/runQueryShape.ts so it can be tested at all — importing dist/index.js starts a server as a side effect. npm test now compiles and runs 15 tests covering the failure cases, key survival, key ordering, paging notes, image stripping and a full serialise round trip.

README.md, TECHNICAL.md and LLM_GUIDANCE.md are updated.

Compatibility

Minor rather than patch: this adds a tool parameter and a result field. Both are additive — no existing field changed meaning or shape.

Full changelog: v1.10.0...v1.11.0