🐛 fix(api): accept lexical_gate as a /api/v1/search param - #83
Conversation
The /api/v1/search handler validates query keys against a closed allowlist before dispatch. The lexical-gate feature added the gate handler (lookupGateParam) but not the allowlist entry, so every gated request was rejected with 400 "unknown query parameter" before the handler ran — the param the feature exposes was refused by the surface meant to accept it. Add lexical_gate to searchParams. Only the canonical underscore spelling is listed; canonicalParam normalizes '-' to '_', so the same entry also admits the lexical-gate alias lookupGateParam reads. Scope is the allowlist only — the gate logic is unchanged. Add a regression test asserting lexical_gate is accepted (both separator spellings -> 200) while a genuinely unknown param still -> 400, so the allowlist cannot silently drop it again and stays closed.
cwest
left a comment
There was a problem hiding this comment.
No changes needed.
The fix is the one allowlist entry the break called for. #81 wired up lookupGateParam but never added lexical_gate to searchParams, so canonicalizeQuery rejected every gated request with a 400 before the handler ran. Adding the single underscore spelling is enough: canonicalParam normalizes - to _, so the same entry also admits the lexical-gate alias without a second list item. The gate logic is untouched.
I reproduced the break and confirmed the fix on a fresh checkout. main at 69c3673 fails on exactly six apiserver tests, all with the same 400 unknown query parameter "lexical_gate" symptom; on this branch all six go green and the full go test ./... -race suite is clean across every package, with gofmt and vet clean and the conformance layer passing. No new failures anywhere.
The regression test earns its place: it drives both separator spellings to 200 and holds an unknown param at 400, so the allowlist can't silently drop the param again and the fix can't drift into a blanket accept-everything that would undo the closed-set strictness from #80.
Green main here is what lets #82 rebase off a clean base.
|
Verified this over live HTTP rather than from the diff, control versus candidate, both built from a fresh clone with Go 1.26.5. Control is The break on main reproduces, and this clears it
The last two are the regression controls. The closed allowlist from #80 survives: an unknown parameter is still rejected, and a malformed boolean is rejected with With the gate omitted, the candidate's response is byte-identical to the control's, all 1,385 bytes. That is the control saying this adds a parameter without perturbing the existing surface. The gate is actually plumbed through, not merely acceptedA 200 on its own would prove nothing, so I checked that the parameter reaches the search path. On my first query the gated and ungated results were identical, which is the documented no-op on an over-broad query, but indistinguishable from a parameter that is parsed and dropped. So I found queries where the gate demonstrably bites and checked the two surfaces against each other:
On all five, The gate also composes. Paired with Suite stateOn this branch The interaction is worth naming, since neither pull request was wrong on its own. #80 and #81 were both cut from This also unblocks #82, whose red check is inherited from |
Why
#81(feat(api): expose the lexical gate) landed onmainbut its own push CI is red: the/api/v1/searchhandler validates query keys against a closed allowlist before dispatch, and#81added the gate handler (lookupGateParam) without addinglexical_gateto the allowlist. Every gated request was rejected with400 unknown query parameter "lexical_gate"atinternal/apiserver/search.gobefore the handler ran — the param the feature exposes was refused by the surface meant to accept it. Mainbuild-testhas been FAILURE since69c3673, inheriting 6 apiserver failures into every downstream PR (this is what dependency-blocks PR #82).What
lexical_gateto thesearchParamsallowlist. Only the canonical underscore spelling is listed;canonicalParamnormalizes-→_, so the same entry also admits thelexical-gatealiaslookupGateParamreads. Scope is the allowlist only — the gate logic is untouched.TestSearch_LexicalGateIsAcceptedParam) with both controls: a valid gate value (both separator spellings) → 200; a genuinely unknown param → still 400. This keeps the allowlist from silently dropping the param again and proves the fix isn't a relaxation of the closed-set validation#80established.Verification
Layer 1 — spec-conformance suite (
go test ./internal/okf/ ./cmd/ -run Conformance -race):Layer 2 — full suite (
gofmt -l .empty;go vet ./...clean;go test ./... -race):The 6 previously-failing
TestSearch_Gate*/TestSearch_CrossSurfaceParitytests now pass;TestSearch_LexicalGateIsAcceptedParampasses.Layer 3 — real corpus (
~/src/knowledge-base/bundles/knowledge, ~239 nodes), before =main/69c3673, after = this branch:validatelint --strictfindingsdiffof the fulllint --strictoutput before vs after is byte-identical — the count that did not move is the control proving this apiserver-allowlist fix touches no OKF-defined behavior (validate/lintunchanged by construction).CGO_ENABLED=0 go buildsucceeds.No spec-mandated search behavior changed: the closed-allowlist strictness (§ n/a — an implementation policy from
#80, not a spec clause) is preserved, and the added param is a tool feature, not an OKF-defined field.Done when
go test ./internal/apiserver/ -racegreen — all 6 gate/parity tests pass.gofmt -l .empty,go vet ./...clean,go test ./... -racegreen.go test ./internal/okf/ ./cmd/ -run Conformance -racegreen.mainpush CIbuild-testreturns to SUCCESS, unblocking PR 🐛 fix(template): accept a v0.1 flat-string sources list for required_fields #82.