Filed by the triage seat, carved out of #16099's residual 1 while answering its pm:retriage.
⚠️ This card exists because the tracker for it did not. #16099 recorded itself as 「waiting on #16785's amendment landing」, and #16785 does not resolve (HTTP 404) — measured, with its immediate neighbours #16783/#16784/#16786/#16787 all resolving, so it is a hole in a dense sequence rather than an access artifact. ⇒ The blocked half had no real card. This is it.
The conflict, as measured by the domain:services seat
AGGREGATE_FIELD_TYPE_COMPATIBILITY (@objectstack/spec) refuses min / max over the string classes — text, select, lookup, autonumber, …
packages/services/service-analytics/src/dataset-compiler.ts deliberately does not enforce that, and says so in writing: enforcing it 「would break uses that work today」.
__tests__/measure-result-type.test.ts holds 15 end-to-end cases that would go red if the compile leg asked the full table.
⇒ A declared refusal that shipped behaviour contradicts, with the contradiction documented in the code rather than hidden. ⛔ The code is not at fault for not enforcing it; it is doing the safe thing while the table is unsettled.
What this card is — establish, then propose. ⛔ Not "enforce"
- Drive the 15 cases and characterise them: are they authors legitimately taking
min/max over a string (lexicographic min of a select, say), or are they incidental uses nobody intends to keep?
- Decide which side is wrong on that evidence — the table refusing something useful, or 15 uses relying on something the contract never meant to allow.
- Bring a proposal, with the population and the breakage named.
⛔ Do not amend the table and do not enforce it in the same pass as step 1. Either direction moves a published contract: relaxing the refusal widens the accepted set; enforcing it breaks 15 shipped cases. ⇒ If the proposal needs a ruling — and it probably does — raise it then, with the driven evidence in hand, rather than now on a reading.
Lane
AGGREGATE_FIELD_TYPE_COMPATIBILITY lives in @objectstack/spec ⇒ domain:spec, by the standing rule 「凡触 packages/spec 一律转 domain:spec 座位,不论谁需要它」 — regardless of the fact that the consumer and the 15 cases are in service-analytics.
⛔ Out of scope
⛔ The temporal rows. Already delivered under #16737 / PR #16778 (357f4992b): assertAggregateFieldTypeCompatible refuses with DATASET_INVALID / 400 before any query runs, reading the table and restating no row. ⇒ That leg works; this card must not touch it.
⛔ The non-temporal pairs the table refuses that nothing depends on — sum × text and its family. That is #16099's re-scoped residual and stays there.
⛔ The three tiering escapes (no declaredFieldType hook, an unresolvable field, a dotted relationship path). Those are the contract, not gaps.
⭐ Recorded so it is not re-derived: the domain:spec seat's 2026-09-09 finding that the table 「has zero consumers — neither leg exists」 was true when measured and is no longer true. dataset-compiler.ts consumes it in production today.
Refs: #16099 · #16737 · PR #16778
Generated by Claude Code
Filed by the triage seat, carved out of #16099's residual 1 while answering its
pm:retriage.The conflict, as measured by the
domain:servicesseatAGGREGATE_FIELD_TYPE_COMPATIBILITY(@objectstack/spec) refusesmin/maxover the string classes —text,select,lookup,autonumber, …packages/services/service-analytics/src/dataset-compiler.tsdeliberately does not enforce that, and says so in writing: enforcing it 「would break uses that work today」.__tests__/measure-result-type.test.tsholds 15 end-to-end cases that would go red if the compile leg asked the full table.⇒ A declared refusal that shipped behaviour contradicts, with the contradiction documented in the code rather than hidden. ⛔ The code is not at fault for not enforcing it; it is doing the safe thing while the table is unsettled.
What this card is — establish, then propose. ⛔ Not "enforce"
min/maxover a string (lexicographic min of aselect, say), or are they incidental uses nobody intends to keep?⛔ Do not amend the table and do not enforce it in the same pass as step 1. Either direction moves a published contract: relaxing the refusal widens the accepted set; enforcing it breaks 15 shipped cases. ⇒ If the proposal needs a ruling — and it probably does — raise it then, with the driven evidence in hand, rather than now on a reading.
Lane
AGGREGATE_FIELD_TYPE_COMPATIBILITYlives in@objectstack/spec⇒domain:spec, by the standing rule 「凡触packages/spec一律转domain:spec座位,不论谁需要它」 — regardless of the fact that the consumer and the 15 cases are inservice-analytics.⛔ Out of scope
⛔ The temporal rows. Already delivered under #16737 / PR #16778 (
357f4992b):assertAggregateFieldTypeCompatiblerefuses withDATASET_INVALID/ 400 before any query runs, reading the table and restating no row. ⇒ That leg works; this card must not touch it.⛔ The non-temporal pairs the table refuses that nothing depends on —
sum×textand its family. That is #16099's re-scoped residual and stays there.⛔ The three tiering escapes (no
declaredFieldTypehook, an unresolvable field, a dotted relationship path). Those are the contract, not gaps.⭐ Recorded so it is not re-derived: the
domain:specseat's 2026-09-09 finding that the table 「has zero consumers — neither leg exists」 was true when measured and is no longer true.dataset-compiler.tsconsumes it in production today.Refs: #16099 · #16737 · PR #16778
Generated by Claude Code