Skip to content

Land wave: phrase-map zero-row guard (#195), array-uint32 400 golden (#196), atomic trustedProxies cache (#197)#198

Merged
SyniRon merged 6 commits into
developfrom
wave/195-196-197
Jun 8, 2026
Merged

Land wave: phrase-map zero-row guard (#195), array-uint32 400 golden (#196), atomic trustedProxies cache (#197)#198
SyniRon merged 6 commits into
developfrom
wave/195-196-197

Conversation

@SyniRon

@SyniRon SyniRon commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

Lands the #194 swarm-review follow-up cluster — three independent hardening/coverage items, no behavior change to healthy paths. All three branches are disjoint ({datastores, referencecache} / {contract} / {rest, servers}) and merged without conflict.

What's in the wave

Closes #195datastores: warn on zero-row read of a must-be-populated phrase family.
loadPhraseMap now surfaces a zero-row read of a must-be-populated family (status/priority) loudly via the package Warn logger, naming the family, instead of silently warming a blank map that blanks statusName/priorityName on healthy 200s. The prefix family stays exempt (may be legitimately sparse). It does not hard-fail the refresh (one drifted family can't take down the rest of the reference cache), genuine query errors still propagate, and per-id miss tolerance is preserved. Covered by a unit test asserting the signal and a live MariaDB harness test.

Closes #196contract: replay golden for over-ceiling repeated-uint32 filter 400.
Adds tickets/list_category_over_uint32 (?category_id=4294967296, read:tickets) witnessing the response-level 400 {"code":3,"message":"… value out of range"} on the array binder path (parsing list "category_id", distinct from the scalar parsing field). Closes the replay half of the two-way contract coverage that TestSpec_Uint32ParamsBounded only declared. Structural spec test and binder behavior untouched.

Closes #197rest: make the trustedProxies once-write/read-after invariant structural.
trustedProxies becomes an atomic.Pointer[[]*net.IPNet] published by InitTrustedProxies and read via an unexported accessor, removing the unsynchronized package-level slice and the theoretical data race. InitTrustedProxies's signature, servers/server.go, the composition-root wiring guard, and resolveClientIP's purity are all unchanged. A concurrency test exercises the publish/read path under -race.

Gate (verified locally on the composed tree)

  • go build ./..., go vet ./... — clean
  • go test ./... — green
  • go test -race ./... — green
  • make test-integration (MariaDB harness) — green (datastores 14.489s, live)
  • go mod tidy — no diff (#197 uses stdlib sync/atomic)

Review

Each branch was reviewed by a best-fit pr-review-toolkit agent (silent-failure-hunter / pr-test-analyzer / type-design-analyzer) — all APPROVE/ACCEPT, no blocking or should-fix findings. #197 rated 9/9/8/9 with a positive-control proving the old pattern trips -race and the new one is clean.

This was generated by AI

SyniRon added 6 commits June 8, 2026 08:53
… 400 (#196)

Add battery case tickets/list_category_over_uint32 — GET
/api/v1/tickets?category_id=4294967296 with read:tickets auth — and record
its golden. The repeated categoryId[] filter's array items declare
maximum: 4294967295, pinned structurally by TestSpec_Uint32ParamsBounded,
but libopenapi-validator does not enforce numeric maximum on array items and
no golden replayed the binder's response-level 400 for an over-ceiling
value. This case witnesses status 400, code 3, and the binder's
"parsing list \"category_id\": ... value out of range", closing the
declare+replay coverage gap for the array surface (the repeated-filter
complement of the scalar list_modified_since_over_uint32 case).

Coverage only — no binder behavior change; the structural spec test is
untouched.

This was generated by AI
Wrap the trusted-proxy cache in atomic.Pointer[[]*net.IPNet] behind an
unexported loadTrustedProxies accessor. InitTrustedProxies now publishes via
an atomic Store rather than a direct assignment; clientIP loads via the
accessor. This turns the "write once before any listener serves, read-only
after" invariant from a comment-only convention into a structural one and
removes the theoretical data race surfaced in the #194 type-design review.

Behavior is identical: empty/unset still trusts nothing, a non-empty
malformed value is still a fatal startup error (made fatal by the composition
root), and a read before init loads the zero value (nil) -> trust-nothing,
the safe direction. resolveClientIP stays pure and unchanged.
InitTrustedProxies keeps its exported `() error` signature; servers/server.go
is untouched and its wiring guard still passes.

Adjust the test helper that poked the cache directly to use the atomic API,
and add a concurrent-reads-during-publish test the race detector exercises.

This was generated by AI
…e family (#195)

loadPhraseMap returned (emptyMap, nil) whenever the xf_phrase query
succeeded but matched zero rows, so the reference cache warmed a fully
blank status/priority map and every statusName/priorityName resolved to
"" on healthy 200s with nothing in logs, metrics, or errors — the exact
silent-degradation mode #189 fixed at the title-prefix level, still
latent one layer down (any future drift: another add-on phrase rename, a
collation/charset change, a language_id regression).

Introduce phraseFamily{prefix, name, mustBePopulated} for the three NF
Tickets families (status/priority must be populated; prefix may be
legitimately sparse). After the read, warnIfEmpty surfaces a zero-row
must-be-populated family at Warn severity, naming the family, and STILL
returns the empty map with a nil error — the refresh must not hard-fail
on one drifted family and take down the rest of the cache (categories
etc.). The healthy populated path is unchanged, and per-id misses inside
a populated family still resolve to "" (the guard only fires on the
all-or-nothing empty case). The documented status/priority/prefix title
asymmetry (the CAUTION block) is preserved, not normalised.

Tests: datastores/phrasemap_test.go pins the decision (empty status and
priority warn naming the family; empty prefix is silent; any populated
map never warns) capturing datastores.Warn. tickets_harness_test.go adds
an end-to-end MariaDB-harness test that deletes the seeded status
phrases, asserts LoadStatusNames returns an empty map with nil error and
emits a Warn naming the family.

This was generated by AI
@SyniRon SyniRon merged commit 2c31021 into develop Jun 8, 2026
3 checks passed
@SyniRon SyniRon deleted the wave/195-196-197 branch June 8, 2026 13:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment