Skip to content

feat(spec): accept boolean / toggle for sum / avg / min / max in the aggregate × field-type table (#16685) - #16750

Merged
zhuangjianguo merged 3 commits into
mainfrom
claude/issue-16685-boolean-aggregate-members
Sep 8, 2026
Merged

feat(spec): accept boolean / toggle for sum / avg / min / max in the aggregate × field-type table (#16685)#16750
zhuangjianguo merged 3 commits into
mainfrom
claude/issue-16685-boolean-aggregate-members

Conversation

@claude

@claude claude Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Fixes #16685

What changed

AGGREGATE_FIELD_TYPE_COMPATIBILITY (packages/spec/src/data/aggregate-field-type-compatibility.ts) accepts the boolean class — boolean, toggle — on its four arithmetic / order rows: sum, avg, min, max. Nothing else in the table moves; count / count_distinct were already total over FieldType.

Executing the director-seat ruling of decision batch #80 (2026-09-08, comment 5577120138 on the card, maintainer verbatim 「其他同意」, option A): batch #59's "every other pair: refused" was a blanket default that never named booleans; ruling #11152 (2026-08-28) — booleans aggregate as NUMBERS on every backend, no per-aggregate exception — is the specific ruling and stands. The spec's own AGGREGATION_CASES pins sum(flag)=3, avg(flag)=0.5, min(flag)=0, max(flag)=1 on six backends and driver-sql casts the aggregand on Postgres (#11635) to make that hold. AGGREGATION_CASES and the driver cast are untouched (「nothing else moves」). Option B is not taken.

Diff, four files:

  • packages/spec/src/data/aggregate-field-type-compatibility.ts — a BOOLEAN_AGGREGATE_FIELD_TYPES constant in the file's existing spelled-out-class idiom, spread into the four rows. The module TSDoc argued the opposite of the ruling in two places (the "everything else" bullet listed booleans among the refused; a dedicated passage recorded the two-ruling collision as unresolved). Both are rewritten to the ruling: the rule table names the boolean class on each row, a boolean bullet cites [finding] AGGREGATION_ROWS has no boolean column, so the cross-driver aggregation conformance family cannot see a boolean aggregand on any face #11152 as the authority and batch Add modern field types (slider, qrcode, geolocation) with configuration schemas #80 as the ruling that resolved the collision, and the "overrides" passage now records only the one override that remains (the string classes on min / max).
  • packages/spec/src/data/aggregate-field-type-compatibility.test.ts — the literal row pins gain the boolean members; the numeric / temporal bucket pins exclude the boolean class before comparing to NUMERIC_VALUE_TYPES / the temporal classes; a new pin holds the boolean bucket equal to BOOLEAN_VALUE_TYPES and present on all four rows; the boolean refusal pins become accept pins; and a cross-pin against AGGREGATION_CASES asserts that every boolean case the conformance suite requires a backend to answer is a pair this table accepts — the two spec tables can no longer contradict each other.
  • .changeset/aggregate-boolean-members.md — new, @objectstack/spec: minor (reasoning below).
  • .changeset/aggregate-field-type-compatibility.md — the table's own PENDING changeset (still in .changeset/, so not yet in any CHANGELOG) said "Booleans are refused … the row ships exactly as batch Validation Protocol: Cross-Field, Async, and Conditional validation #59 stated it" and listed sum × boolean among the refused examples. Both would have published a falsified statement in the same release this lands in, which is the argument the card itself makes about the TSDoc. Corrected in place: the boolean sentences now point at [finding] AGGREGATION_ROWS has no boolean column, so the cross-driver aggregation conformance family cannot see a boolean aggregand on any face #11152 / batch Add modern field types (slider, qrcode, geolocation) with configuration schemas #80; two example pairs in the narrowing sentence are replaced by pairs that remain genuine refusals (sum × booleansum × text, min × textmin × json); the string-classes sentence is kept verbatim while the paragraph's lead moves from "Two refused rows…" to "One refused row…". Declared here as a deviation from the card's two-file surface; the edit is prose only.

Changeset level — minor, judged from the diff

No export is added, removed or renamed: pnpm --filter @objectstack/spec check:generated reports all 15 artifacts up to date at 499d718 (api-surface/, export-origins/, declaration-map/, authorable-surface/, docs references all unchanged) — the exported declarations are unchanged (the table's declared type is the same; only its value gains members) and the new BOOLEAN_AGGREGATE_FIELD_TYPES constant is module-private — absent from dist/*.d.ts and from the bundles' export lists (it does ship inside the bundles as a non-exported binding, measured at review). The shipped dist/*.d.ts is NOT byte-identical: it carries the rewritten module TSDoc (a contract-review measurement at 499d718; the first version of this section claimed byte-identity on the strength of check:generated, which compares export listings and signatures, not .d.ts bytes — retracted). The accept set only widens (every pair accepted before is still accepted), and widening a published accept set is the same class of change the table's introduction was (decision batch #35 puts additive widening at minor). It rides the same release as the parent changeset, so the version outcome is identical either way; minor is the honest classification. Not breaking, so no ADR-0087 marker is required (check-adr-0087-registration --base origin/main exit 0).

Clause-② — yes

Adding members widens a published accept set; the card's claim declares Clause-②: yes and this PR carries needs:contract-review on both carriers. check-widening-tells would not fire (declaration is yes), and the widening is exactly the ruled one: two members, four rows.

Verification

Three commits on the branch: 499d718 (the implementation), 299ed0b (contract-review patch round 1: a changeset paragraph, a test header comment, one assertion line), bee6b0b (patch round 2: one changeset sentence). The full derivation and suite below ran at 499d718; the two patch rounds ran the families that read the paths they touched, recorded here so the narrowing is visible on the PR and not only in a report.

Gate narrowing on the patch rounds — declared. dispatch-gates.mjs --commands derives 75 families for the branch's whole change set and has no per-path narrowing flag, so the patch-round subsets were hand-selected. At 299ed0b, 22 commands, all exit 0: check-adr-0087-registration.mjs, check-changeset-no-major.mjs, check-empty-changeset.mjs (each --base origin/main and --self-test), check:changeset-gate-self-tests, check:objectui-changeset, check-closing-keyword-parity.mjs (+ --self-test), check-comment-mask-adoption.mjs (+ --self-test), check-comment-mask-corpus.mjs, check-keyed-text-bounds.mjs (+ --self-test), check:doc-authoring, check:nul-bytes, and the test-file families check:cross-package-test-inputs, check:test-source-alias, check:where-matcher, check:objectql-double-limit, check:type-source-resolution; plus the pin file (Tests 21 passed (21)) and pnpm --filter @objectstack/spec typecheck (exit 0). At bee6b0b, the 17 of those that read .changeset/** or repo-wide text (the same list minus the five test-file families), all exit 0. The other 53 derived families are keyed on the packages/spec/src/** export / docs / liveness / authorable surfaces, on built output (check:dts-closure, check:dual-build-cjs-loads, check:sourcemap-no-sources-content), or on packages/** source scans — a .changeset/*.md edit reaches none of them, and the packages/** scans that do read a test file ran in CI: at 299ed0b all six required contexts (Lint & Repo Gates, TypeScript Type Check, Test Core, Build Core, Dogfood Regression Gate, Temporal Conformance (live PG + MySQL)) are success. At 499d718 all 75 ran: 73 exit 0, 2 NOT MEASURED (prerequisite not met, CI-owned) — itemised below.

  • pnpm --filter @objectstack/spec buildVERDICT command-exit 0 (under os-verify-lock.sh).
  • pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 src/data/aggregate-field-type-compatibility.test.tsTest Files 1 passed (1), Tests 21 passed (21).
  • pnpm --filter @objectstack/spec test (the local vitest project, the package's own suite) — Test Files 465 passed (465), Tests 12955 passed (12955), 411s, VERDICT command-exit 0.
  • pnpm --filter @objectstack/spec typecheck — exit 0 (tsc --noEmit, check:scripts-typecheck, check:test-typecheck all green; the test file is inside tsconfig.test.json's program).
  • pnpm --filter @objectstack/spec check:generatedAll 15 generated artifacts are up to date (exit 0); nothing regenerated, nothing to commit.
  • Gate derivation: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths — the change set is taken from the merge base, 4 paths) printed 75 commands; every one was run with the printed spelling recorded byte-for-byte; --ran reconciles 75 derived, 75 run, 0 NOT-MEASURED, 0 UNRUN (exit 0). 73 exit 0. Two are NOT MEASURED, both exit 3 PREREQUISITE NOT MET and both needing the whole 87-package closure built, which is CI's run (Build Core, lint.yml): pnpm check:dual-build-cjs-loads and pnpm check:type-check-debt (--re-measure). Two others were cleared after their prerequisite was met: check-plugin-teardown-shape.mjs --self-test (exit 1 on the shallow clone, exit 0 after fetching its pinned fixture commit 621a487, 47 cases) and @objectstack/lint check:doc-formula-expressions (exit 3 before, exit 0 after building formula + lint).
  • Consumer radius: git grep for the table and predicate over packages/** finds only this module, its test and the data/index.ts barrel — the two consumer legs (No layer refuses an incoherent aggregate / field-type pair — a dataset measure avg over a datetime works on SQLite and errors on Postgres #16099, the devx lint leg) are not landed, so no downstream test is owed; the public surface listings (api-surface/, export-origins/) are unchanged (see the changeset section), so no import-side re-test is owed either.

Reverse verification — the pins fail when the boolean class is emptied

Committed first, then mutated. Subject resolved through the relative ./aggregate-field-type-compatibility import (source, not dist/), so no rebuild is part of the loop. Mutation: the BOOLEAN_AGGREGATE_FIELD_TYPES literal replaced with an empty array carrying an ABLATION-16685 marker. On-disk proof by anchored counts: removed text 1 → 0, marker 0 → 1, blob 92a0a4c1…98e329ae…. Run: Tests 6 failed | 15 passed (21) — the six failing are exactly the boolean pins (sum row, avg row, min/max row, the boolean-bucket pin, the four-aggregate accept pin, and the AGGREGATION_CASES cross-pin); the totality pins stay green as they should. Expected direction: red; observed: red. Restore: git checkout HEAD -- ABSOLUTE_PATH spelled with HEAD under a trap … EXIT INT TERM; proof git diff HEAD --stat empty, git status --porcelain empty, git hash-object of the restored file = 92a0a4c1f7aae1ec61f4b9064c90985b7a102651 = the HEAD blob, marker count 0.

验收备注

  • noted, not filed: the card cites the driver-sql boolean cast at sql-driver.ts:8497-8516; on origin/main 5e53d73 it sits at :8670-8712. Line drift only, content exactly as the card describes. 承接者:无。
  • noted, not filed: the PM's mechanism assumption that generated artifacts (api-surface, export-origins, authorable-surface, docs references) would need regeneration was measured false — no export or .describe() changed; check:generated is green with nothing to regenerate.
  • Adjacent open questions the card explicitly keeps separate and this PR does not touch: whether progress stays in sum, and count_distinct × percent (isIncoherentAggregate flags it, the table accepts it — pinned as a visible divergence). No layer refuses an incoherent aggregate / field-type pair — a dataset measure avg over a datetime works on SQLite and errors on Postgres #16099 remains open; the devx lint leg remains open.
  • Fence honoured: packages/spec/src/data/analytics.zod.ts, runtime/src/domains/analytics.ts and packages/spec/src/data/seed.zod.ts are untouched.

🤖 Generated with Claude Code


Generated by Claude Code

…aggregate × field-type table (#16685)

Decision batch #80 (2026-09-08) holds ruling #11152 - booleans aggregate as
numbers on every backend, no per-aggregate exception - over batch #59's
blanket "every other pair refused", which never named booleans. The four
arithmetic / order rows gain the BOOLEAN_VALUE_TYPES members; the module
TSDoc and the table's pending changeset no longer publish the refusal; the
pins hold the boolean rows both literally and against AGGREGATION_CASES.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x
@github-actions github-actions Bot added the size/m label Sep 8, 2026
@github-actions

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

2 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to listnot a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • 4 name(s) were too generic to anchor anything (single lowercase words)
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 130 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json cf33adbd8ea74306f60e1f0b9c827787aeaa383dpackageMentionDocs.

Which tree this was computed on

This run read content/docs from 754fb97f65a777b26361966e8d94dc5e4aad6a15 — the merge of head bee6b0b091a3240e880416cb212ee91d41247168 into base cf33adbd8ea74306f60e1f0b9c827787aeaa383d, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 754fb97f65a777b26361966e8d94dc5e4aad6a15 && git checkout 754fb97f65a777b26361966e8d94dc5e4aad6a15
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin cf33adbd8ea74306f60e1f0b9c827787aeaa383d bee6b0b091a3240e880416cb212ee91d41247168 && git checkout -B drift-repro cf33adbd8ea74306f60e1f0b9c827787aeaa383d && git merge --no-ff bee6b0b091a3240e880416cb212ee91d41247168

node scripts/docs-audit/affected-docs.mjs --json cf33adbd8ea74306f60e1f0b9c827787aeaa383d

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Copy link
Copy Markdown
Contributor

Contract review (CONTRACT_REVIEW_TIER, isolated seat) — PR #16750 @ 499d718

Verdict: PASS WITH FINDINGS (nothing below blocks; F5 is a CI wait, not a defect)
Ruling implemented: yes — option A of #16685 comment 5577120138, exactly and only.

Reviewed against the ruling verbatim: "The compatibility table adds the boolean and toggle members to sum / avg / min / max. Nothing else moves." Read from the PR head fetched into a throwaway ref and from origin/main; nothing in this review is taken from the PR body's own account.

1. Did exactly the ruled pairs move? — yes, measured cell-by-cell

I rebuilt both tables (merge base 5e53d73 → head 499d718) from their source constants and diffed every AggregationFunction × FieldType cell: 294 cells, 8 changed, all false → true:

aggregate boolean toggle
sum refused → accepted refused → accepted
avg refused → accepted refused → accepted
min refused → accepted refused → accepted
max refused → accepted refused → accepted

count / count_distinct stay at 49/49 (ANY_FIELD_TYPE, untouched). Row sizes: sum 6→8, avg 7→9, min 10→12, max 10→12. The mechanism is one new constant BOOLEAN_AGGREGATE_FIELD_TYPES = ['boolean','toggle'] (packages/spec/src/data/aggregate-field-type-compatibility.ts:142-144) spread into the four rows (:161-168); NUMERIC_ / ADDITIVE_ / TEMPORAL_ are byte-identical to base. AGGREGATION_CASES, driver-sql, analytics.zod.ts, seed.zod.ts are not in the diff. None of the five relevant files moved on main between the merge base and current main (8ccf7a1), so the base reading is current. "Nothing else moves" holds.

2. Does the table agree with what ruling #11152's suite actually asserts? — yes, with one attribution caveat (F1)

Read packages/spec/src/data/aggregation-conformance.ts on main, not the PR body. The flag column (AGGREGATION_ROWS, 3 true / 3 false, :230-235) is exercised by seven cases at :358-430: sum(flag)=3, avg(flag)=0.5, min(flag)=0, max(flag)=1, count(flag)=6, count_distinct(flag)=2, and min(flag) grouped by region (east=1, west=0). Every one of those pairs is accepted by the head table. The six enrolled faces listed at :87-118 each iterate for (const c of AGGREGATION_CASES) with no flag filter (verified in all six harness files; driver-sql's runs on SQLite + PG + MySQL cells when provisioned, no case dialect-gated). The ruled driver-sql Postgres cast is at sql-driver.ts:8670-8691 on current main (the card's :8497 is line drift only).

3. Consumer legs — not landed, not touched, not owed

git grep at the PR head for AGGREGATE_FIELD_TYPE_COMPATIBILITY / isAggregateCompatibleWithFieldType outside the module and its test finds only the data/index.ts barrel and the generated api-surface / export-origins entries. No hit under packages/lint/**, services/**, packages/objectql/**, runtime/**. #16099 is open and pm:blocked; the devx lint leg has no file yet. So the PR lands the widened table before either consumer executes it — which is the whole point of the ruling's time box. Nothing downstream to keep green.

4. Changeset and governed paths

  • .changeset/aggregate-boolean-members.md: "@objectstack/spec": minor — package name matches packages/spec/package.json; the body states the before (refused via batch Validation Protocol: Cross-Field, Async, and Conditional validation #59's default) and after (accepted, with the row table) explicitly. Not breaking (accept set only widens; dist/*.d.ts type unchanged), so no ADR-0087 marker owed; Check Changeset green. minor is correct.
  • .changeset/aggregate-field-type-compatibility.md: edited outside the claim's two-file surface. Verified it is still pending (0 hits for the export name in packages/spec/CHANGELOG.md on main), so this corrects unreleased text rather than rewriting history; the diff is two prose lines and the string-class override paragraph is verbatim. Declared in the PR body. Acceptable.
  • Governed paths: none touched. No docs/adr/**, .claude/**, skills/**, AGENTS.md, CLAUDE.md, content/docs/releases/** in the diff. Governed Surface Queue Guard green. This is not a maintainer-only merge on governed grounds.

5. Tests — what the file actually pins (re-read, not trusted)

packages/spec/src/data/aggregate-field-type-compatibility.test.ts at head. If the boolean members are removed again (whole class, or either spelling, or from any one of the four rows), these six it blocks redden:

  • :76-79 sum literal equals ADDITIVE ∪ BOOLEAN
  • :81-83 avg literal equals NUMERIC ∪ BOOLEAN
  • :85-88 min and max literals equal NUMERIC ∪ TEMPORAL ∪ BOOLEAN
  • :108-114 BOOLEAN_VALUE_TYPES literally equals ['boolean','toggle'] and each of the four rows' boolean members equals that set
  • :131-136 predicate accepts all four aggregates over both spellings
  • :138-151 cross-pin: the set of functions over flag in AGGREGATION_CASES equals all six, and each such function is accepted for boolean and toggle

The cross-pin also reddens in the other direction — if someone later removes the flag cases from AGGREGATION_CASES (option B), :144-146 fails. The old refusal pins are gone (:153-159 now lists only computed/text/structured types; :206-213 keeps only the string-class override). The six-test count the PR body reports for its ablation is consistent with the file; I could not execute it locally (no node_modules in this container), so execution rests on CI, where the spec suite runs inside Test Core, all six shards green.

Findings (none blocking)

  • F1 — toggle is not directly pinned by the conformance suite. Every harness declares flag as type: 'boolean'; no harness has a toggle column. The cross-pin's comment (test.ts:139-142, "each case over it is a pair [finding] AGGREGATION_ROWS has no boolean column, so the cross-driver aggregation conformance family cannot see a boolean aggregand on any face #11152 pins on six backends") is accurate for boolean and over-attributes for toggle. The toggle cells are still sound: toggle ∈ BOOLEAN_VALUE_TYPES (field-value.zod.ts:68-70), and driver-sql collects toggle into booleanFields alongside boolean (sql-driver.ts:9636, column mapping :2364), so the Postgres cast(?? as int) at :8685-8691 fires for it. The ruling names toggle explicitly, so the table is right to carry it. Expectation for this PR: none required ("nothing else moves" forbids touching AGGREGATION_CASES); an optional one-line comment fix at test.ts:139-142 saying the toggle half rests on the boolean-class membership rather than a suite row. A toggle conformance row is a driver-family card, not this one.
  • F2 — "six backends" carries the suite's own caveat. aggregation-conformance.ts:97-105 records that the driver-mongodb cell is lowering-only (in-process evaluator, "does not answer 'does MongoDB agree?'"). The head TSDoc (:59-65) and the changeset repeat "enrolled on six backends" without that qualifier. Informational; it is not a table-cell claim and it restates the suite's own enrolment list.
  • F3 — bucket pins were weakened and re-strengthened correctly. The numeric pin (test.ts:90-97) and temporal pin (:99-106) now subtract the boolean class before comparing; the new boolean-bucket pin (:108-114) holds the class literally, so a type joining any of the three field-value.zod classes elsewhere still reddens this file. Verified, no action.
  • F4 — surface deviation, accepted. Parent changeset edit (see §4): pending, prose-only, declared.
  • F5 — CI. 38 check runs at 499d718: 30 success, 7 skipped (opt-in / path-filtered), 0 failure; Lint & Repo Gates still in_progress at time of review (running ~25 min). This verdict is conditional on that gate finishing green; the rest of the matrix — Build Core, all six Test Core shards, all Type Check jobs, Temporal Conformance (live PG + MySQL), Check Changeset ×2, Governed Surface Queue Guard, Spec property liveness — is green.

No approval, merge, label or file action taken by this seat.


Generated by Claude Code

…rs changeset; derive the flag-case vocabulary pin from AggregationFunction (#16685)

Contract-review patch round. The changeset claimed dist/*.d.ts was
byte-identical; it is not - the rewritten module TSDoc ships in
dist/data/index.d.ts. It now states what holds: the exported declarations
are unchanged, the private BOOLEAN_AGGREGATE_FIELD_TYPES constant is absent
from the bundle, and api-surface / export-origins are untouched. The test
header claims only what the cross-pin reaches (the boolean axis) and the
flag-case vocabulary is derived from AggregationFunction.options instead of
a literal six-member list.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x
…— dist/*.d.ts and the bundles' export lists, not the bundle (#16685)

Contract-review patch round 2. The constant does ship inside the bundles as
a non-exported binding, so "absent from the bundle" over-claimed; the
changeset now says only what was measured: absent from dist/*.d.ts and
from the bundles' export lists.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x
@zhuangjianguo
zhuangjianguo marked this pull request as ready for review September 8, 2026 04:37
@zhuangjianguo
zhuangjianguo added this pull request to the merge queue Sep 8, 2026
Merged via the queue into main with commit ed7243d Sep 8, 2026
39 checks passed
@zhuangjianguo
zhuangjianguo deleted the claude/issue-16685-boolean-aggregate-members branch September 8, 2026 05:12
os-trump pushed a commit that referenced this pull request Sep 8, 2026
Brings #16750 (boolean/toggle rows added to sum/avg/min/max in the
aggregate x field-type compatibility table), which settles the boolean
half of the collision this branch reported.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zTkyNHJ7TkuN2oXtP5x37
os-trump pushed a commit that referenced this pull request Sep 8, 2026
…class it actually withdraws

The compile leg was scoped to TEMPORAL source fields in 80ec9f2, but the
changeset and the ADR-0087 ledger entry still described the pre-scoping
full-table gate. A breaking-change record that overstates what changed tells
every consumer reading the release notes that behaviour was withdrawn which
was not.

F1 — the breaking declaration:
- changeset FROM/TO: the two `percent` rows dropped (`sum` x `percent` is a
  table row this leg does not execute; `avg` x `percent` was never a
  migration at all), and the surviving `avg` row widened to name all three
  temporal members it does refuse.
- changeset: a new scope section states the temporal scope outright, and that
  the string rows sit under #16785 (ruled C - the table is to be AMENDED to
  accept them), the boolean rows were settled as ACCEPT by #16685 / #16750,
  and `sum` x `percent` is not executed here.
- ledger `surface`: scoped to the temporal class; "sum over a percent" removed.
- ledger `replacement`: the "`avg` for a `percent`" prescription dropped with
  the surface rows it belonged to.
- ledger `acceptanceCriteria`: qualified to a measure over a
  `date` / `datetime` / `time` field, and says outright that a field of any
  other class is neither refused nor certified by this leg.
- `registry.ts` REGENERATED with `pnpm --filter @objectstack/spec
  gen:migration-registry`, never hand-edited; two consecutive runs are
  byte-identical and the diff is confined to this entry's block.
- also corrected: the changeset said "four contradictory annotations"; the
  sweep reconciled seven source sites plus two test narratives.

F2 — the boolean collision is settled, so stop narrating it as live. #16685
was ruled A and #16750 added `boolean` / `toggle` to the four arithmetic /
order rows, so the table ACCEPTS them. Reworded in `dataset-compiler.ts`'s
scope docblock, the refusal suite's module header, and `measure-result-type.ts`
(whose boolean paragraph still referred a missing refusal onward). All three
now also record #16785 C for the string rows.

F3 — the ledger `reason` presented both dialect halves as measured alike. The
SQLite half is pinned by a live `sql.js` suite; the Postgres 42883 half was
measured in-session and is pinned by nothing. Said so where it is stated.

F4 — the scope-boundary test asserted `isAggregateCompatibleWithFieldType(
'min', 'text') === false`, a verdict #16785 C is about to amend. Dropped: the
case now pins only what this PR owns - a non-temporal field is not judged, so
the measure compiles and SQL is emitted. Refutability is carried by a second
case on `sum` x `text`, a row no ruling is moving, plus a non-vacuity
assertion that SQL reached the driver in both.

F5 — the changeset now names the two uncovered faces: `/analytics/query` and
any `compileDataset` caller wiring no `declaredFieldType` probe.

Refs #16737. Review: PR #16778 contract review, comment 5580295870.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zTkyNHJ7TkuN2oXtP5x37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

3 participants