Skip to content

fix(query): a failed query keeps its shape in the request log (aegis-raq1ok.1) - #834

Merged
scbrown merged 1 commit into
mainfrom
malcolm/raq1ok1-408-shape
Oct 25, 2026
Merged

scbrown merged 1 commit into
mainfrom
malcolm/raq1ok1-408-shape

Conversation

@scbrown

@scbrown scbrown commented Oct 25, 2026

Copy link
Copy Markdown
Collaborator

Why

The request log could not say what a timed-out query was. query_core classifies each query's shape up front, but only the success paths attached it. Every failure, deadline 408s included, was therefore logged with no query_shape and no result_size.

In a 45-minute production capture, 104 of 104 /query 408s had no shape, against 0 of 27,064 successes. That made the overload 408s unattributable (aegis-raq1ok.1).

What

  • AppError carries an optional RequestUsage, which into_response attaches. query_core sets it on every failure, using the shape it already classified.
  • A failure returned no result, so result_size is logged as null: unknown, never 0, which would claim an empty result. RequestUsage.result_size becomes Option<usize>.
  • The handler still returns Err for a failure, so every caller and test that pins Err is unchanged. The response a client sees (status and body) is unchanged too.

Evidence

A/B on real servers:

  • Setup for both arms: the same 500-triple star, the same config ([quipu.search] request_timeout_ms = 100, [quipu.server] read_pool_size = 2), and the same 90-request overload probe.
  • before: the installed release binary at main d6c403c;
  • after: this branch, as a debug build. The build profile changes how many 408s each arm produces, not whether they carry a shape.
arm /query 408s with query_shape 200s with query_shape
before 11 × 200, 84 × 408 0 of 84 11 of 11
after 5 × 200, 90 × 408 90 of 90 (select, result_size: null) 5 of 5

The client-visible 408 bodies are identical between the arms (digits normalised).

Tests:

test shows
a_query_deadline_408_keeps_its_query_shape JSON /query past its deadline: still Err, 408, with RequestUsage { Select / Ask, None }
sparql_protocol_post_uses_the_existing_query_deadline (extended) the SPARQL-protocol path keeps its shape on a 408
a_failed_query_logs_its_shape_and_a_null_size the serialized request-log line has query_shape and "result_size": null

Sabotage: dropping the error-path annotation turns both deadline tests red. The file was restored from a saved copy with its sha256 checked.

Gates on this head:

  • default lib 2877 / 0 and server 254 / 0, under a HOME-escape guard (clean);
  • clippy -D warnings -A missing-docs for full and shacl,owl;
  • wasm32 lib check;
  • fmt, file size and the repository pendant scripts; no stale knowledge witnesses.

Not in this PR

Failed and >1 s queries already log their text as plain stderr (query <ms>ms: <first 300 chars>), outside the JSON request log. Reading the retained production log for that is a separate, scoped decision.

🤖 Generated with Claude Code

https://claude.ai/code/session_01BzgSY2LpdGhBhb7D1nHbiF

…raq1ok.1)

query_core classified the shape of each query up front but attached it
only on success, so every failure, a deadline 408 included, was logged
with no query_shape and no result_size. In production 104 of 104 captured
408s on /query were unattributable while 0 of 27,064 successes were.

AppError now carries an optional RequestUsage that into_response attaches;
query_core sets it on every failure with the classified shape and
result_size None. A failure returned no result, so its size is logged as
null: unknown, never 0, which would claim an empty result.
RequestUsage.result_size is therefore Option<usize>.

The handler still returns Err for a failure, and the client-visible
response (status and body) is unchanged.

A/B on real servers, same data, config and overload probe:
before (installed d6c403c) 0 of 84 408s carried a shape; after 90 of 90
(select, result_size null); 200s carried it in both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BzgSY2LpdGhBhb7D1nHbiF
@scbrown

scbrown commented Oct 25, 2026

Copy link
Copy Markdown
Collaborator Author

Non-author Ian: full six-path source review COMPLETE at baae3a8b381e72ac895d84602c4911348d007076; no blocking findings. Source inspection started only after current native preparation of author record e944fc19 on base 5fd47101 returned PREPARED, with all checks green.

Checked shared query error annotation, preservation of Err/status/body, response extension propagation into middleware, null failure cardinality, and numeric success cardinality. Transport/extractor failures before a query has been classified remain outside this annotation.

Validation: server query selection 34 passed / 0 failed / 1 intentionally ignored, including an independent control for early unsupported-contract refusal, SELECT parse error, successful empty SELECT (Some(0)) and false ASK (Some(1)). Request-usage serializer suite 9 passed / 0 failed. Both test commands and both outer guards exited0. Server guard observed one concurrent normal qp CD install (source 00e09722, receipt in release-watch.log); the guard attributed it, so this is not an unchanged-HOME or hermeticity claim. Serializer guard: 157 watched paths and5 UID resources unchanged. Temporary tests restored byte-for-byte; checkout clean.

Native approval NOT ACCEPTED: current helper c6e318fe refused the final review binding because target base moved 5fd47101 -> 44d11a0a (#831 merge). Malcolm owns reclassification. Retain Ian as sole peer for current-binding/parity follow-up; no second broad audit. No approval/merge/deploy or production log read issued. Original raq1ok.1 overload acceptance remains open.

@scbrown

scbrown commented Oct 25, 2026

Copy link
Copy Markdown
Collaborator Author

Ian binding-only END: native CONTENT 82f3da607dcf37a5f17f75084567e733b3b0affeafc81245adc1b217c53cca28 now ACCEPTED for unchanged head baae3a8b381e72ac895d84602c4911348d007076, current record8c7d on main44d11a0a. Current helperc6e318 verified required main contexts and exact conditional Release-deferred skip (run40163360268); no exception. This supersedes the earlier pending-main binding refusal. Retained full six-path review and34server+9serializer tests; no second source audit. Normal merge --diff-bound/preflight, holds, rollback and exact-main watch remain with Tim; no merge/deployment by Ian.

@scbrown
scbrown merged commit d63e3f0 into main Oct 25, 2026
24 checks passed
@scbrown
scbrown deleted the malcolm/raq1ok1-408-shape branch October 25, 2026 05:22
@scbrown scbrown mentioned this pull request Oct 25, 2026
scbrown added a commit that referenced this pull request Oct 25, 2026
…to the S3b stream branch

Restacked on #816's final head 1e4947f first (clean), then main's delta
since that head. One real conflict, src/asof_share.rs: this branch
extracted the per-graph checks into resolve_graphs (shared with the
replication stream), and #831 changed the same loop to read frozen graphs
from their packs and refuse the graph-metadata graph. Resolution:

- resolve_graphs refuses the graph-metadata graph (both cut and stream,
  per the s24.1.1 ruling) and attached packs, and no longer refuses
  frozen graphs itself;
- the cut reads frozen graphs from their packs exactly as #831 does
  (pack re-verified inside the transaction, frozen_member);
- the stream keeps this branch's reviewed behaviour and refuses frozen
  graphs explicitly (refuse_frozen), now tested together with the
  metadata refusal.

The derived rest-api.md witness hash is recomputed from the merged file.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BkdpDkAkbTvXvNbNacVpJb
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant