Skip to content

docs(read): retract "not reproducible" — #513's coercion is live, and FlexString does not make the value correct - #556

Merged
ZacxDev merged 1 commit into
mainfrom
fix/flexstring-doc-coercion-confirmed
Sep 11, 2026
Merged

docs(read): retract "not reproducible" — #513's coercion is live, and FlexString does not make the value correct#556
ZacxDev merged 1 commit into
mainfrom
fix/flexstring-doc-coercion-confirmed

Conversation

@ZacxDev

@ZacxDev ZacxDev commented Sep 11, 2026

Copy link
Copy Markdown
Contributor

Item 37's evidence carried two false claims and was missing one consequence. All three are now measured.

1. RETRACTED: "not reproducible"

pkg/civitai/flexstring.go:24-28 and claudedocs/decisions/37-numeric-username.md both stated the reported shape was not reproducible, and that /api/v1/images "could not be exercised at all".

It reproduces on every request reaching the Meilisearch feed branch of runImageSearch. Measured 2026-09-11 across 55 digit-username accounts with public images: 51/51 cache-busted responses unquoted, 0 quoted.

?username=2428023993&limit=3   cf-cache-status: MISS   ->  "username":2428023993   UNQUOTED
?imageId=1446527                                       ->  "username":"2428023993" QUOTED

Same user, same route, different internal path (image-search.service.ts:127).

2. Why the earlier negative was wrong — the part worth keeping

Two independent causes, either sufficient alone:

  1. No all-digit account it sampled had public images, so the path was never exercised.
  2. 🔴 The quoted readings were Cloudflare cache HITs, not origin responses. s-maxage=300. On a fresh cache key, 38 of 38 "quoted" readings flipped to unquoted.

That probe read raw bytes, avoided jq, and ran a positive control — everything our rules ask — and none of those checks can see a CDN. The doc now says to require cf-cache-status: MISS before believing any negative about this API.

The retracted paragraph is preserved verbatim in a <details> block rather than deleted, because it guessed the mechanism correctly ("embeds the user through a different serialiser") and was defeated only by being unable to measure it. The inference was sound; the measurement was missing. That is worth a reader seeing.

Note the original justification is untouched and still stands on its own — a client must not hard-fail a 200 whose body it can read. The fix was right while the evidence for it was wrong, which is why nothing about FlexString itself changes here.

3. The mechanism narrows a residual without closing it

The "MECHANISM is unidentified, so the FIELD SET is a guess" residual is updated. The mechanism is Meili's dynamic JSON typing on the stored image document.

  • Narrows: the old worst case was "any all-digit string column on the way out". TagItem.Name — the old text's worked example — is not on that document and is no longer implicated.
  • Does not close: any other all-digit-capable field on the same document has identical exposure by construction. civitai/civitai#4768 asks the platform side to enumerate it.
  • Trigger to widen is unchanged: an observation, not the plausibility of the mechanism.

4. 🔴 The new residual, and the reason this is more than bookkeeping

The coercion is numeric, so leading zeros are destroyed upstream:

?imageId=622901         ->  "username":"0222"     the real name
?username=0222&limit=4  ->  "username":222        what we receive
?username=222&limit=4   ->  0 items               matches no account

FlexString decodes this cleanly and cannot fix it. It made the failure quieter, not absent — an opaque exit 1 became a confident, wrong username. Item 37 must not be read as "#513 is solved"; the decode half is solved, the value half is a server bug.

Generalised in the doc: a fix on the consumer side of a lossy producer should state which half it does not cover.

Tracking

Verification

  • make ci → green, 22 packages tested (positive control: package count > 0).
  • golangci-lint run ./...0 issues (run under nix-shell; make lint errors on this host as golangci-lint is not on PATH, and per AGENTS.md lint reports without gating, so it is worth running locally).
  • Docs-only plus one comment block; no behaviour change, no test change.

The blocks/images endpoint is described in #4768 as suspected, not verified — it returns 401 Block token required, so I could not exercise it and said so there rather than asserting it.

… FlexString does not make the value correct

Two claims in item 37's evidence were false, and one consequence was undocumented.

RETRACTED: `flexstring.go` and `37-numeric-username.md` both said the reported
shape was not reproducible and that /api/v1/images "could not be exercised".
Measured 2026-09-11: it coerces on every request reaching the Meilisearch feed
branch of runImageSearch — 51/51 cache-busted responses unquoted, across 55
digit-username accounts with public images.

WHY THE EARLIER NEGATIVE WAS WRONG, which is the transferable part: two
independent causes. No account it sampled had public images, so the path was
never exercised; and the quoted readings it did get were Cloudflare cache HITs,
not origin responses. On a fresh cache key, 38 of 38 "quoted" readings flipped.
That probe read raw bytes, avoided jq and ran a positive control — none of which
can see a CDN. The retracted paragraph is preserved verbatim in a <details>
block, because it guessed the mechanism correctly and was defeated only by not
being able to measure it.

MECHANISM, now known: the Meilisearch image index stored user.username under
Meili's dynamic JSON typing. This NARROWS the field-set residual — TagItem.Name
is not on that document and is no longer implicated — without closing it, since
any other all-digit-capable field on the same document has the same exposure by
construction. The trigger to widen is unchanged: an observation, not an argument.

NEW RESIDUAL, and the reason this matters beyond bookkeeping: the coercion is
NUMERIC, so leading zeros are destroyed upstream. A user named "0222" arrives as
222, and ?username=222 returns 0 items. FlexString decodes it cleanly — its
whole job — but cannot recover digits the wire dropped, so it made the failure
quieter rather than absent: an opaque exit 1 became a confident wrong username.
Item 37 must not be read as "#513 is solved"; the decode half is.

Server side filed as civitai/civitai#4768 with a closing condition. #513
stays open deliberately, and the reporter was told this rather than left to infer
it from a merged PR.

Refs #513

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TRHayuSRJFuHKZXk4GXstU
@ZacxDev
ZacxDev merged commit d0d1805 into main Sep 11, 2026
13 checks passed
@ZacxDev
ZacxDev deleted the fix/flexstring-doc-coercion-confirmed branch September 11, 2026 20:18
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