Skip to content

fix(bug-campaign 4): naive as_of is a 422, never a 500 (XWING-2440) - #334

Merged
smith-xyz merged 3 commits into
developmentfrom
fix/bug-campaign-4
Nov 2, 2026
Merged

smith-xyz merged 3 commits into
developmentfrom
fix/bug-campaign-4

Conversation

@smith-xyz

Copy link
Copy Markdown
Contributor

Proven bugs (red → green) — naive-as_of REST family, stacked on #333

Four REST doors accepted a query as_of without a UTC offset; the value reached Python-side comparisons against the database's always-aware UtcTimestamp and raised a bare TypeError (HTTP 500). A shared UtcDatetime pydantic type refuses the naive value at parse time — the caller gets a 422, never a 500.

route fix
GET /v1/teams/{id}/repositories?as_of= UtcDatetime
GET /v1/exposure/summary?as_of= and /timeline UtcDatetime
GET /v1/validation-attempts?as_of= UtcDatetime
GET /v1/insights/{name}?as_of= UtcDatetime

The type is a custom pydantic core schema (pydantic_core.core_schema), the same pattern as HttpsRepoUrl in models/values.py — the layering allowlist gains pydantic_core for rest with that precedent cited.

Full suite 8,472 green. Merges after #333.

Coverage

traust-bug-campaign proof queue: 87 candidates remain; ledger in progress-tracker tracking/bug-campaign/traust-core/.

…not undeclare a boundary (XWING-2440)

The compliance-scope trio, each red on unmodified development:
- _recorded read the scope's assessments from only the alphabetically-first
  member's current default head, so a routine re-observation (a new ref, the
  old one keeps history) made recorded posture vanish from latest/summary.
  It now lists across every ref of every member repository.
- set_scope's replace-guard consulted only the windowed _declarations read
  (newest SCOPE_HISTORY rows), so a boundary older than the window read as
  new and was silently superseded. _ever_declared widens the window until it
  stops growing, reading each declaration once — the exhaustive scan lives
  on the rare declare path, not the read paths.
- _assessed read a doc whose target omitted repos as 'assessed zero repos':
  the producer can legally write one (product/environment-scoped runs skip
  --repos), so the record gate now refuses it loudly instead of recording an
  assessment of nothing that reads back as never-assessed.
Red: test_a_scope_assessment_is_still_found_after_its_first_members_head_moves,
test_set_scope_still_demands_replace_when_the_boundary_fell_out_of_the_history_window,
test_record_refuses_an_assessment_whose_target_names_no_repos.
…r skips (XWING-2440)

The crash cluster, each red on unmodified development:
- _line_count: '①'/'²' pass isdigit() but int() raises — isdecimal() reads
  them as junk (absent), per the 'return None for junk' contract
- fix_links: a reference urlsplit cannot parse ('Invalid IPv6 URL' on
  'https://[foo/commit/…') crashed the whole below-range prepare; the link
  skips now
- ComplianceMapping.check: an unknown check_id raised bare StopIteration;
  now a typed ValidationError
- _predicate_errors: an unhashable op (op: [] from YAML) made 'op not in
  set(PredicateOp)' raise TypeError instead of rejecting the card; the
  validator's contract is a reason, never a crash
- _k8s: a parseable doc with a scalar spec (spec: true) crashed the whole
  config matrix with AttributeError; the shape miss skips, same contract
  as malformed YAML
- _tally: the resolved branch used str(None) as a SeverityCounts key,
  pydantic-refusing the whole insight rollup when an unrated finding is
  resolved (the common OSS Checkov case); the None guard the open branch
  already has now applies to both
- _rank: an unrated finding's effective_severity=None raised KeyError in
  the candidates door; unrated now sorts last (below informational)
Red: all seven tests fail on unmodified development for the predicted
reasons.
… a 500 (XWING-2440)

Four REST doors (teams, exposure, validation-attempts, insights) accepted a
datetime without a UTC offset; the value reached Python-side comparisons
against the database's always-aware UtcTimestamp and raised a bare TypeError
(HTTP 500). A shared UtcDatetime pydantic type (custom core schema, the
same pattern as HttpsRepoUrl) refuses the naive value at parse time — the
caller gets a 422 naming the field, never a 500.

The layering allowlist gains pydantic_core for rest (one custom type,
mirroring the existing precedent for HttpsRepoUrl in models/values).
@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider

Great, no issues found!

Qodo reviewed your code and found no material issues that require review

Grey Divider

Tip of the day
💡 Did you know, you can copy the agent prompt from any finding and feed it to your IDE agent

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

@qodo-for-traust-security

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)

Great, no issues found!

Qodo reviewed your code and found no material issues that require review

Powered by Qodo

@smith-xyz
smith-xyz changed the base branch from fix/bug-campaign-3 to development November 2, 2026 18:36
@smith-xyz
smith-xyz requested a review from chubirka as a code owner November 2, 2026 18:36
@smith-xyz
smith-xyz merged commit 5f5b8f3 into development Nov 2, 2026
6 checks passed
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