Skip to content

feat(query-engine): branded identity columns derive branded row schemas; drop redundant declared rowSchemas - #652

Merged
Makisuo merged 8 commits into
mainfrom
chore/drop-redundant-service-overview-rowschema
Aug 27, 2026
Merged

feat(query-engine): branded identity columns derive branded row schemas; drop redundant declared rowSchemas#652
Makisuo merged 8 commits into
mainfrom
chore/drop-redundant-service-overview-rowschema

Conversation

@Makisuo

@Makisuo Makisuo commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator

Summary

Two steps toward retiring declared rowSchemas in favour of builder-derived ones, from a full audit of every surviving declared site (probe in compileInner comparing declared vs derived ASTs, all 19 production sites driven).

1. Drop the one byte-identical declaration

The service_overview pipe's declared schema was identical to what the builder derives — deleted. serviceOverviewRowSchema stays exported for the service_overview_compare period-wrapper.

2. Branded identity columns (the "brands" unlock, implemented)

  • Builder: comparisons on a column now widen a branded decoded type to its primitive (Widen<TSType> in expr.ts), so $.OrgId.eq(param.string("orgId")) keeps compiling when the column's schema brands it — the type-level mirror of literalSchema ("comparisons may accept more than the column decodes to"). inList generalized the same way. T.custom("String", brandedSchema) already existed; new type-level + runtime tests pin the behaviour.
  • tables.ts: OrgId on all 37 tables, and TraceId/SpanId on the error_events tables, now carry their branded domain schemas. Derived row schemas keep the brands, so the output types (ReadonlySet<OrgId>, TraceId/SpanId in sample traces) come straight off the derivation.
  • Retired: the declared rowSchemas at activeOrgsBy* (×2 call sites) and errorIssueSampleTraces, plus their now-dead schema consts (ActiveOrgsOutputSchema, ErrorIssueSampleTracesOutputSchema — output types survive as interfaces).
  • SQL-catalog gate: the synthetic zero-value row ("" for String columns) would fail a brand's minLength check, so the seven affected catalog entries carry sampleValues overrides — pipe fixtures gained sampleValues support for this (builder fixtures already had it). A catalog-wide scan confirmed exactly those seven entries are brand-affected.

Why the remaining declared rowSchemas survive (follow-up map)

  • Literal unions (workloadKind, targetType) — computed via if_/multiIf, which type as first-typed-arm.
  • div is blanket-nullableX.div(1_000_000) derives NullOr even though a non-zero literal denominator can't produce inf/nan. Typing literal-denominator division as plain CHNumber would flip aiSessionSpans, serviceMapEdgesRollup.MaxDurationMs, and the ops-summary duration columns to fully derivable.
  • Guarded divides / CHNumberOrZero — rewriting if_(w > 0, a.div(w), 0) and sum/nullIf shapes as CH.ifNotFinite(a.div(b), 0) (already typed non-null) would retire the rest, but changes emitted SQL for ~10 live queries, so it's deliberately not in this PR.
  • Keep forever: ServiceWorkloads.avgCpu/MemoryLimitUtilization are declared nullable against a non-null derivation (LEFT JOIN nullability the deriver doesn't model) — deleting those would reintroduce decode failures.

Test plan

  • lib/clickhouse-builder: typecheck (incl. new type-level tests) + 243 tests pass
  • packages/query-engine: typecheck + full package suite, 1296 tests pass
  • packages/query-engine-integrations: typecheck clean
  • apps/api: typecheck fully clean (0 errors) after building the effect-sdk dist; ErrorsService + ErrorIssueReadModelsService suites pass (58 tests)
  • Catalog brand scan: all 7 brand-affected entries have sampleValues; the ClickHouse DESCRIBE e2e runs in the clickhouse-schema-e2e CI job

🤖 Generated with Claude Code

An audit of every surviving declared rowSchema against the builder-derived
one found this site byte-identical — the builder derives the exact same
struct from the SELECT's column types. The serviceOverviewRowSchema export
stays: the compare path's period-wrapper still spreads its fields.
…e branded row schemas

Comparisons on a column now widen a branded decoded type to its primitive
(Widen<TSType>), so $.OrgId.eq(param.string("orgId")) keeps compiling when
the column's schema brands it — the type-level mirror of literalSchema.

Maple's tables.ts declares OrgId (all tables) and TraceId/SpanId (error_events
tables) via T.custom with their branded domain schemas, so derived row schemas
carry the brands. That retires the declared rowSchemas at activeOrgsBy* and
errorIssueSampleTraces; their output types now come off the derivation.

The SQL-catalog synthetic zero-value row ("" for String columns) would fail
a brand's minLength check, so the affected fixtures — and now pipe fixtures,
which gained sampleValues support — override those fields.
@Makisuo Makisuo changed the title chore(query-engine): drop redundant service_overview declared rowSchema feat(query-engine): branded identity columns derive branded row schemas; drop redundant declared rowSchemas Aug 27, 2026
Makisuo and others added 6 commits August 27, 2026 11:33
…ing column

A tenant-column constraint like { OrgId: <a String column> } has to accept a
branded column, and CHType is invariant in its decoded type, so CHString can't
say it. Name the concept in the lib instead of hand-rolling
CHType<"String", any, any> at the consumer.
…ructural inference

CI caught what a stale local tsbuildinfo hid: InferOutput's
`S[K] extends Expr<infer T>` resolved T through the comparison methods'
contravariant params — which are now widened for branded columns — so every
branded column's derived output type collapsed to the bare primitive and the
api call sites failed against their branded consumers.

InferOutput now reads the expression's `_phantom` property directly, which
is exact; Exclude strips only the optionality's undefined so Nullable columns
keep their `| null`. The edges-rollup declared schema also has to say the
brand now that the derived output carries it — declared schemas narrow, they
don't contradict.
main's invalid-sql tag added a caller-authored twin of the analyzer rule, and
inserts deliberately keep the default caller authorship — so the syntax-shaped
insert rejection takes the new tag. The api-side expectation was the one
call-out that commit missed.
…rview-rowschema' into chore/drop-redundant-service-overview-rowschema
@Makisuo
Makisuo merged commit 21ec17e into main Aug 27, 2026
59 of 61 checks passed
@Makisuo
Makisuo deleted the chore/drop-redundant-service-overview-rowschema branch August 27, 2026 11:14
@github-actions

Copy link
Copy Markdown

🍁 Maple PR preview

Warning

Preview cleanup could not be confirmed. The Alchemy teardown outcome was skipped.

Final commit 07aa454 · View workflow run

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