Skip to content

feat(BA-6889): add kernel scheduling-history query conditions and orders#12867

Draft
jopemachine wants to merge 5 commits into
mainfrom
feat/BA-6889-kernel-history-conditions-orders
Draft

feat(BA-6889): add kernel scheduling-history query conditions and orders#12867
jopemachine wants to merge 5 commits into
mainfrom
feat/BA-6889-kernel-history-conditions-orders

Conversation

@jopemachine

@jopemachine jopemachine commented Jul 15, 2026

Copy link
Copy Markdown
Member

📚 Stacked PRs

Merge in order, bottom-up. Each PR is based on the one above it, so its diff shows only its own layer.

What

The models-layer query building blocks the rest of the kernel scheduling-history stack builds on, mirroring the session equivalents:

  • KernelSchedulingHistoryConditions — MatchSpec filter variants for id / kernel_id / session_id; string match variants (contains / equals / starts_with / ends_with / in) for phase, error_code, message; from/to status, result, and timestamp conditions.
  • orders.pyKERNEL_ORDER_FIELD_MAP, default forward/backward orders, tiebreaker order, resolve_kernel_order.
  • KernelHistoryOrderField — the DTO enum orders.py resolves against. Only this enum is added here; the rest of the kernel DTOs land in feat(BA-6891): add kernel scheduling-history v2 DTOs and service actions #12869.
  • EntityType.KERNEL_HISTORY / KERNEL_SCOPED_HISTORY.

Review notes

Bulky but mechanical: it mirrors SessionSchedulingHistoryConditions axis for axis. The interesting part is the tests.

Each factory returns a lazy closure, so a wrong column name raises AttributeError only when the condition is finally evaluated — not at import, and not under type checking. tests/unit/manager/models/test_scheduling_history_conditions.py therefore compiles every condition and asserts the column it targets. That is the same class of fault #12857 fixes, and the coverage Copilot asked for there.

Nothing calls any of this yet — the repository scope that consumes it is #12868.

Size

~429 lines, slightly over the 300–400 target. conditions.py is a single indivisible block of sibling factories; splitting it would cut across one class for no reviewer benefit.

🤖 Generated with Claude Code

jopemachine and others added 3 commits July 15, 2026 18:38
Add the models-layer query building blocks that the rest of the kernel
scheduling-history stack builds on, mirroring the session equivalents:

- KernelSchedulingHistoryConditions: MatchSpec filter variants for id,
  kernel_id and session_id; string match variants for phase, error_code and
  message; from/to status, result and timestamp conditions.
- orders.py: KERNEL_ORDER_FIELD_MAP, default forward/backward orders,
  tiebreaker order and resolve_kernel_order.
- KernelHistoryOrderField, which orders.py resolves against.
- EntityType.KERNEL_HISTORY and KERNEL_SCOPED_HISTORY.

Tests pin each condition to the column it compiles against; the factories
build lazy closures, so a wrong column name surfaces nowhere else.

Nothing calls these yet. The repository scope lands in BA-6890.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…edulingHistoryID

The kernel scheduling-history record had no dedicated identifier type, so its
primary key travelled the stack as a bare uuid.UUID. Introduce the NewType and
thread it through the row, the domain model, and the by_ids query condition.

NewType is identity at runtime and GUID's subtype argument only satisfies the
type checker, so the column mapping and emitted SQL are unchanged and no
migration is needed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…y module

The rebase onto main left two modules covering KernelSchedulingHistoryConditions:
the one merged upstream under models/scheduling_history/, and the flat module this
branch added. Both defined their own _ConditionCase and both covered
by_from_status/by_to_status.

Move the cases the flat module uniquely covered (from/to statuses, kernel_id,
session_id, their filter-spec variants, result, error_code) into the per-entity
module and drop the flat one, matching the layout the sibling entities already use.

GUID resolves to CHAR(16) on the default dialect and has no literal processor, so
UUID columns cannot render inline there. Carry the dialect on the case and pass it
to compile(), which keeps the existing cases and their expectations untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@jopemachine
jopemachine force-pushed the feat/BA-6889-kernel-history-conditions-orders branch from 1d85def to 657f1a6 Compare July 15, 2026 09:40
The kernel string-filter conditions hoisted the column into `col` and then
rebound it to `sa.func.lower(col)`. That rebinding is the only reason the
`cast(sa.ColumnElement[...], ...)` calls existed: mypy fixes a variable's type
at its first assignment, so `Function[Any]` does not fit an
`InstrumentedAttribute[str | None]`.

The cast was also untrue. `InstrumentedAttribute` is not a `ColumnElement` —
both descend from `SQLColumnExpression`, so they are siblings. `cast` is
unchecked, so it silenced a mismatch mypy rejects outright when written as an
annotation.

No other conditions module does this. Of the 47 `conditions.py` files, this was
the only one casting (48 times); 33 use `.ilike()` and never rebind the column
variable. `login_client_type` shows nullability is not the reason either: it
wraps a nullable column in `coalesce` once and calls `.ilike()` on it.

So the kernel filters now build the condition per branch, as the convention
does. The casts on the session / deployment / route blocks are pre-existing and
left alone.

Case-insensitive matching moves from `lower(col) LIKE lower_pattern` to `ILIKE`
on postgresql. The two are equivalent here and no index is affected: `phase`,
`error_code` and `message` carry no index, functional or otherwise. Tests are
updated to the SQL the convention emits.
by_from_status, by_to_status and by_error_code have no production caller. Every
filter reaches the conditions through the adapter, which uses the plural and
MatchSpec factories instead: by_from_statuses / by_to_statuses for status, and
convert_string_filter(equals_factory=by_error_code_equals, ...) for error_code.
The only thing referencing the three was the test written for them.

They are a strict subset of what replaces them — by_error_code_equals with a
non-negated, case-sensitive spec compiles to the same `error_code = 'ERR'`, and
also covers the case-insensitive and negated variants the plain one cannot.

The from_status / to_status cases are dropped outright: by_from_statuses and
by_to_statuses already pin the same columns. The error_code case moves onto
by_error_code_equals rather than going away, so the column stays covered — a
factory returns a lazy closure, so a wrong column only fails when compiled,
which is the fault #12857 fixed and the reason these tests compile each
condition.

The identical factories on the session / deployment / route classes are equally
dead but out of scope here.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp:common Related to Common component comp:manager Related to Manager component size:L 100~500 LoC

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant