Repository navigation
refactor(shared): four modules reinvent the success/not-found/error triple as ad-hoc dicts #17740
Description
Activity
- addedenhancementNew feature or requestNew feature or request
on Sep 28, 2026 B10 spec — the API returning a shape that loses a distinction the caller needs
Batch B10 = #17694 (self-contained, first) → #17740 (four modules, quieter slot). Full spec at
~/b10-spec-2026-09-28.md.#17694 — two defects, and the first is a familiar shape
llc/api/sprints.py:698—body.model_dump(exclude_none=True)drops everyNone, so "set this field to null" and "do not change this field" are the same request. One field, two meanings — the same shape asrole_detector.py:114(#17742) and theor 0.0class (#17660).
Change:exclude_unset=True, which is the distinction Pydantic already tracks andexclude_nonethrows away. Second defect: validate the status in the request model so FastAPI answers 422 before the handler runs, instead of a 500 from inside the session.
Assertions: an explicitnullclears · an omitted field is unchanged · an invalid status is 422 naming the field.
Mutations: revert toexclude_none=True→ the clears case reddens while unchanged stays green, which is what makes the pair necessary; remove the enum validation → 500 returns.
Do not lose the IDOR guard at:696-697(#10148) — a rewrite of the dump loop is exactly where it gets dropped.
Test home:llc/tests/already hastest_sprints_idor.pyandtest_sprints_enrichment.py; a newtest_sprints_patch_semantics_17694.pykeeps both concerns together.#17740 — and the trap is that a shared type nobody adopts is a fifth vocabulary
Four modules, measured in the issue itself: 8 non-test files return
"status": "not_found", 4 of those also return"status": "error"— the second set is the subject. Credit where the issue gives it: one of the four is its author's own, from #17546/#17533 — "a pattern I reproduced, not one I only noticed."Change: define the triple once in
autobot_shared/as a typed result, error carrying its reason, and convert all four call sites. Convert all four, or convert one and record why the others are not migrated — adding a type and hoping produces a fifth vocabulary, which is #17693's warning.
Mutation: collapse not-found into error in the shared type → the distinguishability tests redden; revert one module to its dict → only that module's test reddens, which proves the conversion is asserted per call site rather than in aggregate.
Scope boundary to state in the PR: the other 4 of the 8not_foundreturners are not in scope — they do not also return"error", so they are not instances. Say so, or the next sweep counts 8 and reads the PR as half-finished.
Problem
Four service modules have independently invented the same three-state result and expressed it as a
string-keyed dict with locally chosen key names:
Measured on
origin/main: 8 non-test files return"status": "not_found"; 4 of those also return"status": "error", which is the shape this issue is about. The fourth is mine, added in #17546 and#17533 — this is a pattern I reproduced, not one I only noticed.
The triple is always the same distinction, and it is always the one that matters:
Collapsing the last two is a recurring defect class in this repo, not a style question.
search_by_metadatareturnsfact_ids: []for both a genuine miss and an internal failure,separated only by
status; a caller reading the list reports "nothing to remove" for a lookup thatnever happened. #17546 fixed one instance of that; nothing stops the next one.
What a shared type would give
llc/services/workspace_disposal.pyalready models this properly, as a typed enum plus aNamedTuplecarrying the evidence:with a
may_disposeproperty so both refusals refuse, and the reason they are kept apart writtenwhere a future simplifier will read it. That shape is worth having once, not four times:
not_foundanderrorcannot be compared to the same falsy valueAcceptance criteria
autobot_shared/expressing success / not-found / error with amandatory detail on the two non-success states.
is not the same thing as a service return, and some of these may be the former — establish
which before converting).
Not in scope
The other 4 files that use
"status": "not_found"without an"error"twin. They may be HTTPresponse bodies, where a status string is the correct idiom. Converting them because they matched a
grep would be the same error this issue is about — acting on a match rather than on the question.
Related
Found while implementing #16818, #17546 and #17533. The
DisposalVerdictreference implementationis in PR #17725.