Caught by the audit rerun: with cache-warm binaries, 04 fires its
first SELECT faster than queryd's 500ms refresh tick — Q1 returned
400 ("table not found") even though 03_ingest had registered the
manifest. Subsequent queries (after the next tick) succeeded.
This is an eventual-consistency wait, not a retry — queryd's
contract is that views appear within one tick of catalogd having the
manifest. Production code does not need changing.
Added to lib/http.sh:
proof_wait_for_sql <budget_sec> <sql>
polls a SQL probe until it returns 200 or budget elapses; emits
no evidence (test setup, not a claim).
Used in 04_query_correctness:
Wait up to 5s for queryd to have the view before running the 5
SQL assertions. Skip-with-loud-reason if the view never appears.
Verified: integration mode back to 104 pass / 0 fail / 1 skip after
fix. The skip is the unchanged GOLAKE-085 informational record.
This is exactly the kind of finding the harness was designed to
surface — the regression existed in the codebase the moment Phase D
shipped, but only fired when the next compare run hit cache-warm
timing. Without the harness, it would have surfaced on a CI run
weeks from now and been hard to bisect.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
152 lines
5.3 KiB
Bash
152 lines
5.3 KiB
Bash
#!/usr/bin/env bash
|
|
# lib/http.sh — curl wrappers that capture latency, status, body.
|
|
#
|
|
# Each request emits a JSON file under raw/http/<case_id>/<probe>.json
|
|
# describing the round-trip. Cases consume the JSON via assert.sh.
|
|
#
|
|
# Why JSON files instead of bash variables: gives the final report a
|
|
# diffable, replayable record. Future runs can compare on disk without
|
|
# re-executing the case.
|
|
#
|
|
# Functions:
|
|
# proof_get <case_id> <probe_name> <url> [extra-curl-args...]
|
|
# proof_post <case_id> <probe_name> <url> <content-type> <body> [extra-curl-args...]
|
|
# proof_put <case_id> <probe_name> <url> <content-type> <body|@file> [extra-curl-args...]
|
|
# proof_delete<case_id> <probe_name> <url> [extra-curl-args...]
|
|
#
|
|
# Returns 0 always (capture is independent of HTTP outcome).
|
|
# Stores result at: $PROOF_REPORT_DIR/raw/http/<case_id>/<probe>.json
|
|
# Stores body at: $PROOF_REPORT_DIR/raw/http/<case_id>/<probe>.body
|
|
|
|
_proof_http_emit() {
|
|
local case_id="$1" probe="$2" method="$3" url="$4" status="$5" latency_ms="$6" body_path="$7" headers_path="$8"
|
|
local dir="${PROOF_REPORT_DIR}/raw/http/${case_id}"
|
|
mkdir -p "$dir"
|
|
local body_sha=""
|
|
[ -s "$body_path" ] && body_sha="$(sha256sum "$body_path" | awk '{print $1}')"
|
|
cat > "${dir}/${probe}.json" <<JSON
|
|
{
|
|
"case_id": "${case_id}",
|
|
"probe": "${probe}",
|
|
"method": "${method}",
|
|
"url": "${url}",
|
|
"status": ${status},
|
|
"latency_ms": ${latency_ms},
|
|
"body_path": "raw/http/${case_id}/${probe}.body",
|
|
"body_sha256": "${body_sha}",
|
|
"headers_path": "raw/http/${case_id}/${probe}.headers"
|
|
}
|
|
JSON
|
|
}
|
|
|
|
# Internal common runner — populates a temp body+headers file, times
|
|
# the request, emits the per-probe JSON, prints the body to stdout for
|
|
# convenience (cases can capture or discard).
|
|
_proof_http_run() {
|
|
local case_id="$1" probe="$2" method="$3" url="$4"; shift 4
|
|
local dir="${PROOF_REPORT_DIR}/raw/http/${case_id}"
|
|
mkdir -p "$dir"
|
|
local body_path="${dir}/${probe}.body"
|
|
local headers_path="${dir}/${probe}.headers"
|
|
local start_ms end_ms
|
|
start_ms=$(date +%s%3N)
|
|
local status
|
|
status=$(curl -sS -X "$method" -o "$body_path" -D "$headers_path" -w "%{http_code}" "$@" "$url" 2>/dev/null || echo 0)
|
|
end_ms=$(date +%s%3N)
|
|
local latency_ms=$((end_ms - start_ms))
|
|
_proof_http_emit "$case_id" "$probe" "$method" "$url" "$status" "$latency_ms" "$body_path" "$headers_path"
|
|
cat "$body_path"
|
|
}
|
|
|
|
proof_get() {
|
|
local case_id="$1" probe="$2" url="$3"; shift 3
|
|
_proof_http_run "$case_id" "$probe" GET "$url" "$@"
|
|
}
|
|
|
|
proof_post() {
|
|
local case_id="$1" probe="$2" url="$3" content_type="$4" body="$5"; shift 5
|
|
_proof_http_run "$case_id" "$probe" POST "$url" \
|
|
-H "Content-Type: ${content_type}" \
|
|
--data "$body" \
|
|
"$@"
|
|
}
|
|
|
|
# proof_put accepts either an inline body or @-prefixed file path
|
|
# (curl --upload-file semantics for streaming).
|
|
proof_put() {
|
|
local case_id="$1" probe="$2" url="$3" content_type="$4" body="$5"; shift 5
|
|
if [[ "$body" == @* ]]; then
|
|
local file="${body#@}"
|
|
_proof_http_run "$case_id" "$probe" PUT "$url" \
|
|
-H "Content-Type: ${content_type}" \
|
|
--upload-file "$file" \
|
|
"$@"
|
|
else
|
|
_proof_http_run "$case_id" "$probe" PUT "$url" \
|
|
-H "Content-Type: ${content_type}" \
|
|
--data "$body" \
|
|
"$@"
|
|
fi
|
|
}
|
|
|
|
proof_delete() {
|
|
local case_id="$1" probe="$2" url="$3"; shift 3
|
|
_proof_http_run "$case_id" "$probe" DELETE "$url" "$@"
|
|
}
|
|
|
|
# proof_call: escape hatch for cases that need full control of curl
|
|
# args — multipart uploads (-F), custom headers, --form-string, etc.
|
|
# proof_post / proof_put add a Content-Type header and --data body
|
|
# that conflict with -F multipart, so use this for those cases.
|
|
#
|
|
# proof_call <case_id> <probe> <method> <url> [curl-args...]
|
|
#
|
|
# Example multipart POST:
|
|
# proof_call "$CASE_ID" "ingest" POST "$URL" -F "file=@${PATH}"
|
|
proof_call() {
|
|
local case_id="$1" probe="$2" method="$3" url="$4"; shift 4
|
|
_proof_http_run "$case_id" "$probe" "$method" "$url" "$@"
|
|
}
|
|
|
|
# proof_wait_for_sql: wait for a SQL probe to return 200, up to budget
|
|
# seconds. Use when a case follows an ingest and queryd's view-refresh
|
|
# (default 500ms tick) may not have fired yet. NOT a retry — a wait
|
|
# for a known eventual-consistency event. No evidence emitted (this
|
|
# is test setup, not a claim).
|
|
#
|
|
# proof_wait_for_sql <budget_sec> <sql>
|
|
#
|
|
# Returns 0 if the probe succeeded; 1 on timeout.
|
|
proof_wait_for_sql() {
|
|
local budget="${1:-10}" sql="$2"
|
|
local deadline=$(($(date +%s) + budget))
|
|
local body
|
|
body=$(jq -nc --arg s "$sql" '{sql:$s}')
|
|
while [ "$(date +%s)" -lt "$deadline" ]; do
|
|
if curl -sf --max-time 1 -X POST \
|
|
-H 'Content-Type: application/json' \
|
|
-d "$body" \
|
|
"${PROOF_GATEWAY_URL}/v1/sql" >/dev/null 2>&1; then
|
|
return 0
|
|
fi
|
|
sleep 0.1
|
|
done
|
|
return 1
|
|
}
|
|
|
|
# Helper accessors — reads the per-probe JSON.
|
|
proof_status_of() {
|
|
local case_id="$1" probe="$2"
|
|
jq -r '.status' "${PROOF_REPORT_DIR}/raw/http/${case_id}/${probe}.json"
|
|
}
|
|
|
|
proof_body_of() {
|
|
local case_id="$1" probe="$2"
|
|
cat "${PROOF_REPORT_DIR}/raw/http/${case_id}/${probe}.body"
|
|
}
|
|
|
|
proof_latency_of() {
|
|
local case_id="$1" probe="$2"
|
|
jq -r '.latency_ms' "${PROOF_REPORT_DIR}/raw/http/${case_id}/${probe}.json"
|
|
}
|