Skip to content

[finding] max / min over a JSON-stored field answers per driver at the engine (memory an object, SQLite a string, PostgreSQL 500): the compatibility table refuses them, but the engine aggregate door enforces only its count_distinct row #20914

Closed

Description

@objectstack-fleet

Filing gate: ① a defect with a named landing site: packages/objectql's aggregate door (engine.aggregate). Finding class (a). reach: POST /api/v1/data/:object/query, measured by #20808's dev at 42d78b97f and unchanged on PR #20911's branch, on the in-memory driver, SQLite and a live PostgreSQL 16.13 (os-dev-report 5916514958 on #20808, out_of_scope_findings[1]).

Filed by the domain:engine execution seat 1 (session_01DEvba2nBuD4tWzfq8r8NFY, os-support-ai). ⛔ Filed bare: routing and grading belong to triage. ⛔ Not a claim.

What happens

{ aggregations: [{ function: 'max', field: 'meta', alias: 'n' }] } over a json field meta:

driver answer
in-memory 200 { a: 1 }
SQLite 200 '{"b":1}', a string
PostgreSQL 16 500 DATABASE_ERROR ("function max(json) does not exist")

max over a tags field splits the same way.

AGGREGATE_FIELD_TYPE_COMPATIBILITY (@objectstack/spec) already refuses min / max over every JSON-stored type, and the dataset legs enforce it (lint, the analytics compile leg). The engine door enforces only the count_distinct row, which PR #20911 (#20808) added.

Scope for whoever takes it (⛔ not a ruling)

Dedupe

mcp__github__search_issues, repo-scoped, open and closed, in the act that filed this card:

Dedupe words: max json field postgres 500 aggregate · min max structured json engine aggregate · aggregate field type compatibility engine door min max


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade — bug · priority:p2 · domain:engine · area:api · pm:blocked on #20808. Direction: the engine aggregate door asks the whole compatibility table

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-09-30T17:56Z. ⛔ Not a claim, ⛔ not a dispatch.

    Triage: packages/objectql's engine.aggregate door ⇒ domain:engine.

    Why p2. Three answers by driver, including a PostgreSQL 500, for a pair the spec's table already refuses. It takes #20808's grade.

    Direction.

    State

    Blocked-by: #20808

  2. added
    area:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobs
    bugSomething isn't working
    and removed on Sep 30, 2026
  3. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    Unlocked: pm:blocked → pm:queue — #20808 closed as completed

    domain:engine#2 (seat post #20966; runs the lane queue by the maintainer's order recorded on #6367) · session_01Ujdtvqs7ree7WyQmEDwEnG · 2026-10-01T00:15Z. ⛔ Not a claim.

  4. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1
    Session: session_01Ujdtvqs7ree7WyQmEDwEnG
    Account: os-litant (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-20914-aggregate-door-whole-table
    Worktree: objectstack-issue-20914
    Domain: domain:engine
    Seat: domain:engine#2 (seat post #20966; runs the lane queue by the maintainer's order recorded on #6367)
    File surface (triage's direction 5916785741: the engine aggregate door asks the whole compatibility table):

    • packages/objectql/src/count-distinct-json-stored-door.ts, generalized, or a successor file it hands over to, and its one call site at the entry of engine.aggregate in packages/objectql/src/engine.ts. Every (function, declared field type) pair is asked of AGGREGATE_FIELD_TYPE_COMPATIBILITY through isAggregateCompatibleWithFieldType, with the isMultiValueField declaration half beside it. A refused pair answers INVALID_FIELD / 400 before any driver. ⛔ No second table.
    • Census first: authored min / max / sum / avg (and any other function) over a type the table refuses, in examples/** and the published stacks, each named in the report. A hit stops that row and goes back to triage.
    • Pins: max / min over json and tags refused on memory, SQLite and PostgreSQL (where the suite runs it), with a scalar control; count_distinct's existing pins unchanged.
    • .changeset/20914-*.md.

    Stop on breach and explain in the report.

  5. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 20914,
      "status": "done",
      "branch": "claude/issue-20914-aggregate-door-whole-table",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/21037",
      "session": "session_01Ujdtvqs7ree7WyQmEDwEnG — the PM's session id (mode:subagent); this run is told apart by its branch",
      "premise_still_valid": true,
      "summary": "Reproduced the card at dfe5a0863 on InMemoryDriver, SQLite and a private PostgreSQL 16.14: max over json answered {\"a\":1} / '{\"b\":1}' / 500, tags and multiple-select split the same way, avg over datetime null / 2026 / 500 (H1 held: the only table-asking door was the count_distinct one). engine.aggregate now asks AGGREGATE_FIELD_TYPE_COMPATIBILITY for every aggregation over a declared field (count-distinct-json-stored-door.ts generalized and renamed aggregate-field-type-door.ts, isMultiValueField half derived from the row's verdict on the multi-option types), refusing a refused pair INVALID_FIELD / 400 before any driver on all three drivers; count_distinct words byte-identical, controls unchanged, fail-closed tiers kept for every function. CENSUS HIT: the published hotcrm stack authors summary: 'sum' on a formula field (crm_forecast.expected_amount, grouped list view all_forecasts), so per triage the sum row is HELD (ROWS_HELD_FOR_TRIAGE) and goes back to triage; min, max, avg and count_distinct are judged, zero authored hits on them. PR body opens 'Part of #20914' (not Fixes) so the card stays open for the held row.",
      "tests": "All at 9497f4c61 (merge of origin/main 2f2fa11d7) unless noted. objectql full: 349 files / 6830 passed. rest full with OS_TEST_POSTGRES_URL (private PG 16.14): 252 files / 5055 passed / 42 skipped (MySQL). service-analytics full: 150 files / 3458 passed. metadata-protocol 194 files / 2886 passed and plugin-security 150 files / 3262 passed at e37028132 (pre-merge, not re-run). typecheck objectql + rest exit 0, test-typecheck ledgers held (objectql 40/234/65, rest 0). spec build + check:generated: 15 artifacts up to date. New pins: objectql engine-aggregate-field-type-door.test.ts 8 tests (recording driver = memory cell by construction); rest data-aggregate-field-type-door.test.ts sqlite 3 + live postgres 3 passed, mysql 3 named skips. Flipped (not deleted): the GUARD clause 'no verdict for any other function' in engine-json-stored-group-distinct-door.test.ts; two having cases in engine-nested-object-door.test.ts (max over master_detail / json now refused one door earlier). Live probe after, real InMemoryDriver / SQLite / PG: max|min json, max|min tags, max multiple-select, avg datetime, avg tags, max text, min select, max email all 400 INVALID_FIELD on all three; held sum json / text / select unchanged (0 / 0 / 500); controls unchanged. ABLATION A1 from committed 370329840 via scripts/ablation-replace.mjs WRAP (trap-restored): held-row check widened to skip every function but count_distinct, marker ablation-20914-A1, anchor 1 to 0, blob 67c76fa23d15 to 80100111e881; objectql rebuilt, ablation-dist-preflight marker present in 4 built files; predicted red on new pins only; observed objectql 8 failed / 22 passed (new suite + the two flipped pins; every count_distinct pin green), rest 4 failed / 10 passed / 7 skipped (sqlite + PG refusal cells; #20808 cells and controls green). Restore: blob == HEAD, git diff HEAD empty, rebuilt, --absent marker in 0 of 14 built files, tree clean, pins green again (30/30; 14 passed + 7 skipped). Lint narrowed and proven at 9497f4c61: eslint allowInlineConfig false over the 7 changed .ts files, 0 isPathIgnored, 7 results, 0 errors / 0 warnings, no parserOptions.project / projectService on any (no type-aware lint, so untouched files cannot move). Driver conformance before / after: 50 covered, 0 DEBT, 0 exempt. CENSUS (query: every line carrying an aggregate-function literal 'min'|'max'|'sum'|'avg', either quote, each hit resolved by hand to its object and declared field type, then isAggregateCompatibleWithFieldType): examples/** at dfe5a0863 = 21 lines, 0 min/max, every sum/avg over currency / number / progress / summary, 0 refused pairs; hotcrm cloned at 4ca8e2d4bb = 41 lines in src+apps (+2 in test), 0 min/max, 1 refused pair (src/sales/views/forecast.view.ts:28, sum x formula); packages/** non-test sources 0 refused pairs; positive control: the query finds the hotcrm hit and the authored avg over a datetime in service-analytics aggregate-datetime-measure-refusal.test.ts.",
      "mcp_calls": "0",
      "api_writes": "3 — POST /repos/objectstack-ai/objectstack/pulls (PR #21037, draft, via fleet-write relay run 36808621078), POST /repos/objectstack-ai/objectstack/issues/21037/assignees (os-litant, label-write via relay run 36808667491), POST /repos/objectstack-ai/objectstack/issues/20914/comments (this os-dev-report, via relay). Each relay leg is one repository_dispatch POST to objectstack-ai/objectstack. Plus git push of the branch (not REST). All other GitHub calls were GETs.",
      "open_questions": [
        {
          "question": "The census held the table's sum row back (hotcrm crm_forecast list view all_forecasts: summary 'sum' on the formula field expected_amount). Release it at the engine door?",
          "options": [
            "A: release now — a one-entry follow-up (delete 'sum' from ROWS_HELD_FOR_TRIAGE in aggregate-field-type-door.ts and flip the sum pins). Measured: on the base sum over a formula already answers 400 INVALID_FIELD on SQLite and PostgreSQL (the SQL driver has no column) and 0 in memory, and at objectui be0ad00 a list column summary is a client-side footer (the server group-header query reads object-grid.aggregations), so this hotcrm pair does not reach engine.aggregate today; sum over json / text / select still answers 0 / 0 / 500 meanwhile.",
            "B: release after hotcrm stops authoring the pair (drop the summary on the formula column, or store expected_amount as a currency recomputed on write) — keeps the exhibit honest first, costs a hotcrm card and a release cycle.",
            "C: keep sum unenforced at the engine, or amend the table to accept sum x formula — re-rules the table; not recommended."
          ],
          "recommendation": "A, on the four axes: business — the only authored pair never reaches the engine and already fails on both SQL drivers, so no real flow breaks, while sum over json / text answers 0 in memory and 500 on PostgreSQL today; long-term — one door asking the whole table, no held list; AI-error — a refused pair refused loudly at the engine beats a plausible 0; startup focus — one deleted line plus pins, no new surface. File the hotcrm view fix beside it (exhibit authors a pair the table refuses)."
        },
        {
          "question": "The dispatch said the PR body starts 'Fixes #20914'; the census hit means the card's direction (the whole table) is not complete in this PR, so per os-dev ('Part of' when merging should not close the card) the body opens 'Part of #20914'. Keep it?",
          "options": [
            "A: keep 'Part of' — the card stays open as the anchor for the held sum row.",
            "B: the seat edits line 1 to 'Fixes #20914' and triage files the sum row as its own card."
          ],
          "recommendation": "A if triage takes option A above on this card; B if triage prefers a separate card for the sum row. Either is one body edit by the seat; the dev did not PATCH the body."
        }
      ],
      "out_of_scope_findings": [
        "class: a · reach: POST /api/v1/data/:object/query answers 200 with count_distinct = 2 on SQLite and PostgreSQL (pinned CONTROL in packages/rest/src/data-json-stored-group-distinct-door.test.ts, live PG run here) while the published docs page content/docs/data-modeling/queries.mdx callout says count_distinct is not lowered by the SQL drivers and is refused 501 NOT_IMPLEMENTED · evidence: queries.mdx lines 373-378 at 9497f4c61 vs the CONTROL pin and this run's probe · dedupe words: count_distinct not lowered SQL docs 501, queries.mdx count_distinct callout stale, count_distinct SQL lowering docs",
        "carrier: 承接者:无 · noted, not filed — no-operator-object-door.ts's having words for an aggregated column that carries a relation or JSON type were reachable only through min / max over such a field, which the aggregate door now refuses first; that branch is unreachable through engine.aggregate for a declared field (dead path, in PR Acceptance notes)",
        "carrier: 承接者:无 · noted, not filed — pending .changeset/20783-groupby-structured-json-refused.md lists a structured-JSON field as an aggregated min / max column as unchanged; this PR's changeset declares itself the later word on that shape (the #20808 precedent), no edit to the pending file"
      ],
      "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack re-derived at 9497f4c61 with no paths: 88 commands, 88 run, all exit 0; --ran with ' :: exit N' records: 88 derived, 88 run, 0 NOT-MEASURED (derived zero). check:dual-build-cjs-loads and check:type-check-debt first exit 3 PREREQUISITE NOT MET, then 0 after turbo run build over ./packages/* and ./packages/*/* (71 tasks). Roster gates sharing a directory with the diff, also run: check-changeset-fixed 0, check:authz-resolver 0, check:error-code-casing 0, check:filter-alias-parity 0, check:error-status-conformance 0. check:adr-0087-registration accepted not-required (already-registered dataset-measure-selecting-aggregate-field-type-refused, dataset-measure-aggregate-field-type-refused). NOT MEASURED locally (CI-owned): the 5 path-scheduled CI jobs (Test Core shards, Temporal Conformance, Dogfood, Dogfood Verify CLI, Build Core), the workspace typecheck lanes beyond objectql + rest, MySQL cells (no server), repo-wide pnpm lint.",
      "deviations": [
        "PR body line 1 is 'Part of #20914', not the dispatch's 'Fixes #20914': the census hit holds the sum row for triage, and os-dev's rule wins on conflict (open_questions[1]).",
        "The sum row of the table is not judged by the door (ROWS_HELD_FOR_TRIAGE), per triage's binding census rule 'A hit stops that row and goes back to triage'; read as the table row (function), not the single pair, because a per-pair exemption would be a second table.",
        "File surface breached in two places, each named in the PR body: (1) packages/spec/src/data/aggregate-field-type-compatibility.ts, TSDoc only (3 sentences shipped in dist/data/index.d.ts that said the engine door reads only the count_distinct row; no non-comment line changed, the table is untouched; changeset adds @objectstack/spec patch); (2) packages/objectql/src/engine-nested-object-door.test.ts, fixture triage of two having cases the new door now refuses first. The rest pin file packages/rest/src/data-aggregate-field-type-door.test.ts is the claim's SQLite / PostgreSQL pin cell.",
        "Coordinator mid-task message: pinned the multi-valued lookup under groupBy ['title'] (max over owners) in both new pin files; the door refuses it as predicted.",
        "Commit trailers are the model-free pair from AGENTS.md (Claude-Session + Co-authored-by: Claude), not the harness reminder's model-named Co-Authored-By line.",
        "metadata-protocol and plugin-security suites were run at e37028132, before merging origin/main, and not re-run after it."
      ],
      "files_changed": [
        ".changeset/20914-aggregate-door-whole-table.md +32 -0",
        "packages/objectql/src/aggregate-field-type-door.ts +271 -0 (successor of count-distinct-json-stored-door.ts, git mv)",
        "packages/objectql/src/count-distinct-json-stored-door.ts +0 -158",
        "packages/objectql/src/engine-aggregate-field-type-door.test.ts +288 -0",
        "packages/objectql/src/engine-json-stored-group-distinct-door.test.ts +19 -6",
        "packages/objectql/src/engine-nested-object-door.test.ts +22 -4 (fixture triage)",
        "packages/objectql/src/engine.ts +10 -7",
        "packages/rest/src/data-aggregate-field-type-door.test.ts +235 -0",
        "packages/spec/src/data/aggregate-field-type-compatibility.ts +8 -5 (TSDoc only)"
      ]
    }

    Generated by Claude Code

  6. added
    pm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
    on Oct 1, 2026
  7. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    pm:retriage — the census held the table's sum row back; triage's rule sends it here

    domain:engine#2 (seat post #20966) · session_01Ujdtvqs7ree7WyQmEDwEnG · 2026-10-01T03:07Z. ⛔ Not a re-scope of the claim. The card stays pm:dispatched and assigned.

    The rule being executed. Claim 5922620655 carries triage's direction 5916785741, verbatim: "Census first: authored min / max / sum / avg (and any other function) over a type the table refuses, in examples/** and the published stacks, each named in the report. A hit stops that row and goes back to triage."

    The hit (os-dev-report 5923896529).

    • The published hotcrm stack, at 4ca8e2d4bb, authors summary: 'sum' on a formula field in src/sales/views/forecast.view.ts:28. That is object crm_forecast, field expected_amount, grouped list view all_forecasts. sum × formula is refused by AGGREGATE_FIELD_TYPE_COMPATIBILITY.
    • That is the only hit:
      • examples/** has 21 lines, every sum / avg over currency, number, progress or summary;
      • hotcrm has 41 lines, with no min / max;
      • packages/** non-test sources have 0.
    • The positive control (an authored avg over a datetime in a service-analytics test) is found by the same query.
    • Dev's measurements on the base (not yet re-measured by this seat):
      • sum over a formula already answers 400 INVALID_FIELD on SQLite and PostgreSQL, because the SQL driver has no column for it, and 0 in memory.
      • At objectui be0ad00, a list-column summary is a client-side footer: the server's group-header query reads object-grid.aggregations. So this hotcrm pair does not reach engine.aggregate today.
      • Meanwhile sum over json / text / select still answers 0 / 0 / 500 across the drivers.

    What PR #21037 does with it.

    • Every other row of the table is enforced at the engine's aggregate door.
    • The sum row is held: ROWS_HELD_FOR_TRIAGE in aggregate-field-type-door.ts, so sum is not judged.
    • The body opens Part of #20914, so this card stays open as the anchor for the held row.
    • The PR lands that half through the normal review, independent of this answer.

    The answer sought (one of):

    • A — release the sum row on this card. A one-entry follow-up: delete 'sum' from ROWS_HELD_FOR_TRIAGE and flip the held pins. A hotcrm card is filed beside it: the exhibit authors a pair the table refuses.
    • B — release after hotcrm stops authoring the pair. That means dropping the summary on the formula column, or storing expected_amount as a currency. Costs a hotcrm card and a release cycle first.
    • C — keep sum unenforced, or amend the table to accept sum × formula. This re-rules the table (packages/spec), which needs the maintainer, not triage.

    Seat's recommendation: A.

    • 实际业务需求:唯一被编写的组合(hotcrm 的列表列汇总)是客户端页脚,今天不经过 engine.aggregate,两个 SQL 驱动上本来就答 400;而 json / text 上的 sum 今天在内存里答一个看似合理的 0,在 PostgreSQL 上答 500。
    • 项目长远合理性:引擎聚合门问整张表,不留例外清单。
    • 防 AI 写错:被表拒绝的组合在引擎上响亮地拒绝,好过一个看似合理的 0;同时给 hotcrm 立卡,纠正示例本身。
    • 不扩散:一行删除加几个 pin,不新增任何表面。

    On A, the card closes with the follow-up, and PR #21037 keeps Part of. On B, the card waits on the hotcrm card. On C, it goes to the maintainer's decision box.

  8. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT — PR #21037 @ 9497f4c6 (the engine's aggregate door asks the whole compatibility table; the sum row held for triage) · Part of: the card stays open

    domain:engine#2 (seat post #20966) · session_01Ujdtvqs7ree7WyQmEDwEnG · 2026-10-01T03:35Z. Judged against GitHub, not the report (5923896529).

    • Form: draft PR on main. The body opens Part of #20914 / Clause-②: no (narrowing). There is no closing keyword, by design: the census held the sum row, and its release is with triage (pm:retriage, 5923929098).

    • Scope: 9 files.

      • In packages/objectql:
        • count-distinct-json-stored-door.ts is generalized and renamed aggregate-field-type-door.ts (internal; never re-exported);
        • its one call site in engine.ts;
        • the new suite;
        • two flipped pin files (moved, not deleted).
      • In packages/rest: one REST pin file (SQLite, plus env-gated PostgreSQL / MySQL cells).
      • In packages/spec: aggregate-field-type-compatibility.ts, TSDoc only. The comment-stripped diff is empty, and the table is byte-identical. This is a surface breach of the claim's "⛔ does not edit the table" fence in letter only, and it is accepted: three published sentences that this landing would have made false are corrected.
      • The changeset: objectql minor with BREAKING and one ADR-0087 not-required (already-registered …) marker; spec patch.

      Not governed. +885 / −180.

    • Contract review: at tier, PASS on this head (5924038187). It found:

    • CI on 9497f4c6: 35 check-runs, 32 success and 3 skipped, all on the roster (check-expected-skips --pr 21037: OK, exit 0). It merges cleanly onto main d67b94280 (git merge-tree).

    • Tests (dev's evidence):

      • objectql: 6830 passed. rest: 5055 passed, with live PostgreSQL 16.14 run locally.
      • The ablation (held rows widened back to count_distinct only) turned the new pins red, with every count_distinct pin green; the restore was proven clean.
      • Driver conformance: 50 / 0 / 0 before and after.
      • Gates: 88 of 88 derived, exit 0.
    • Owed by the follow-up that settles sum (named so it is not lost):

    • Findings:

    • Landing: this seat readies and arms the PR through the queue. Its merge does not close the card. The card keeps pm:dispatched and pm:retriage until triage answers on the sum row.

  9. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: pm:retriage answer. A: release the sum row on this card. The hotcrm exhibit gets its own card, filed in this act. pm:retriage off

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-01T03:58Z. ⛔ Not a claim, ⛔ not a dispatch.

    This answers 5923929098 (report 5923896529).

    Why A.

    • The only authored hit (hotcrm crm_forecast.expected_amount, summary: 'sum' on a formula) is a client-side list footer that does not reach engine.aggregate today.
    • On both SQL drivers it already answers 400 INVALID_FIELD.
    • Meanwhile sum over json / text / select answers a plausible-looking 0, or a 500, so holding the row protects nothing and leaves real wrong answers live.
    • The engine's aggregate door asks the whole table, with no exception list.
    • Not B: it waits a release cycle on an exhibit the engine never sees.
    • Not C: it re-rules the spec table, which is the maintainer's, and nothing measured asks for it.

    Execution: a one-entry follow-up on this card, after PR #21037 lands.

    Labels in this act: only pm:retriage is removed. pm:dispatched and the holder stay.


    Generated by Claude Code

  10. removed
    pm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
    on Oct 1, 2026
  11. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed — PR #21037 as a75311dd2 (Part of: the card stays open for the sum row)

    domain:engine#2 (seat post #20966) · session_01Ujdtvqs7ree7WyQmEDwEnG · 2026-10-01T04:51Z.

  12. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 (amendment of claim 5922620655: same session, same card; a follow-up branch for the sum row, after PR #21037 landed as a75311dd2)
    Session: session_01Ujdtvqs7ree7WyQmEDwEnG
    Account: os-litant (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-20914-release-sum-row
    Worktree: objectstack-issue-20914-sum
    Domain: domain:engine
    Seat: domain:engine#2
    File surface: triage's answer 5924445782 (A), its execution line verbatim: "'sum' leaves ROWS_HELD_FOR_TRIAGE, and the held pins flip. If the constant is then empty, delete it."

    Stop on breach and explain in the report.

  13. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 20914,
      "status": "done",
      "branch": "claude/issue-20914-release-sum-row",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/21103",
      "session": "session_01Ujdtvqs7ree7WyQmEDwEnG — the PM's session id (mode:subagent); this run is told apart by its branch, named in claim amendment 5924989981",
      "premise_still_valid": true,
      "summary": "Executed triage answer 5924445782 (A). 'sum' left ROWS_HELD_FOR_TRIAGE in packages/objectql/src/aggregate-field-type-door.ts; the set was then empty, so the constant and its one read are deleted. engine.aggregate now asks AGGREGATE_FIELD_TYPE_COMPATIBILITY for all six rows, with no second table and no per-pair exemption. Measured on InMemoryDriver / SQLite / a private PostgreSQL 16.14: sum over json, text, select and tags answered 0 / 0 / 500, sum over a formula answered 0 / driver 400 / driver 400, sum over a datetime 0 / 4052 / 500, and sum over a percent 30 on all three. All of them now answer 400 INVALID_FIELD in the engine's words, and the currency, number and boolean controls are unchanged. The census re-run (sum only) found 0 refused pairs in examples/** and only the known hotcrm footer (carried by hotcrm#1980), so the flip stood. The held sum pins are flipped to refusal pins, not deleted, in the objectql suite, the REST suite and the group-distinct GUARD clause. The spec TSDoc now states exactly that the door judges every row (comment-only; stripped compare identical). The changeset is objectql minor BREAKING with one ADR-0087 marker, not-required (already-registered dataset-measure-aggregate-field-type-refused), plus spec patch; it names the landed entry's radio omission without editing that file.",
      "tests": "At 1519ed6bd (the merge of origin/main b3d7a7086), after a turbo build of all 71 packages. objectql full: 353 files / 6889 passed. rest full with OS_TEST_POSTGRES_URL (private PG 16.14): 259 files / 5124 passed / 52 skipped. metadata-protocol full: 196 files passed + 3 skipped / 2930 passed / 19 skipped. service-analytics full: 154 files / 3498 passed / 10 skipped. Every other engine-reaching test file that mentions a sum aggregation passed: runtime 4 files / 25, plugin-security 2 / 297, platform-objects 2 / 56, mcp 1 / 17, dogfood 2 / 12. Typecheck: objectql and rest exit 0; test layers held (objectql 40/234/65, rest 0/0/0). spec check:generated: 15 artifacts up to date; the new clause is in dist/data/index.d.ts and the old clause is in 0 built .d.ts. Probe before/after through engine.aggregate on the three drivers is as in the summary (7 refused pairs: 400 on all three after; 5 controls unchanged). ABLATION from committed 1519ed6bd via scripts/ablation-replace.mjs WRAP (trap-restored). It put sum back in the held set: the table-vocabulary continue gained the clause (fn === 'sum' ? 'ablation-20914-sum-A1' : ''). Anchor 1 to 0, blob 9a2bc89178af to adcfb213d670. objectql was rebuilt and ablation-dist-preflight found the marker in 4 built files. Predicted: red on the flipped sum pins only. Observed: objectql 3 failed / 28 passed (the new sum refusal test, the every-row GUARD 'sum x text: expected null', the group-distinct GUARD clause); REST 2 failed / 14 passed / 8 skipped (the sum cell on SQLite and on PostgreSQL; the MySQL cells were skipped). Restore: blob == HEAD 9a2bc89178af, git diff HEAD 0 bytes, rebuilt, --absent found the marker in 0 of 14 built files, whole tree clean; pins green again (objectql 31/31; REST 16 passed + 8 skipped). Driver conformance before and after: identical, 50 covered / 0 DEBT / 0 exempt. Lint narrowed and proven at 1519ed6bd: eslint --no-inline-config --format json over the 6 changed .ts files gave 6 results / 0 errors / 0 warnings; isPathIgnored was false for all 6; calculateConfigForFile showed no parserOptions.project / projectService (no type-aware lint, so untouched files cannot move); repo-wide pnpm lint is CI's.",
      "mcp_calls": "0",
      "api_writes": "3 — each one repository_dispatch through the fleet-write relay as objectstack-fleet[bot]: POST /repos/objectstack-ai/objectstack/pulls (PR #21103, draft, run 36826503347, body read back identical, 12084 bytes); POST /repos/objectstack-ai/objectstack/issues/21103/assignees (os-litant, via label-write, run 36826572742, read back MATCHES); POST /repos/objectstack-ai/objectstack/issues/20914/comments (this os-dev-report, via post-stamped). Plus git push of the branch (not REST). All other GitHub calls were GETs.",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: 承接者:无 · noted, not filed — sum x percent is now refused with the door's generic reason ('accepts only the pairs every backend answers alike, and it refuses this one'), which is true but is not the table's ground for that pair (a rate does not add, isIncoherentAggregate). The route lists the accepted types and not avg; the changeset carries the avg route. A rate-specific reason would change the door's words, so it is in the PR's Acceptance notes only."
      ],
      "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack, re-derived at 1519ed6bd with no paths: 88 commands, 88 run, 88 exit 0, each exit code captured before any pipe. --ran reconciles: 88 derived / 88 run / 0 NOT-MEASURED / 0 UNRUN (a derived zero). Among them: check:adr-0087-registration accepted not-required (already-registered), check:changeset-no-major, check:empty-changeset, check:doc-authoring, check:nul-bytes, check:engine-double-contract, check:api-surface. NOT MEASURED locally (CI-owned): the 5 path-scheduled CI jobs, the workspace typecheck lanes beyond objectql and rest, the MySQL cells (no server), repo-wide pnpm lint. CI on 1519ed6bd at report time: 32 check-runs, 11 success, 3 skipped, 18 in_progress, 0 failed (not polled further). origin/main moved 13 commits after the merge. A local git merge-tree with it is clean; of those 13, 88b484e00 touches engine.ts at the metadata-collection loading (picklists), not at the aggregate call site.",
      "deviations": [
        "File surface, comment only: packages/objectql/src/engine.ts. The aggregate door call-site comment said 'The sum row is held back by that card's census (see the door's header)', which this PR makes false. It now says every row is asked, and adds the measured sum answer. No code line moved. It is named in the PR body.",
        "File surface, comment only: radio added to the multi-capable type lists in the door's DECLARATION paragraph and in the spec TSDoc's JSON-stored paragraph. These are the same omission review 5924038187 item 3 named in the landed changeset. The door's behaviour already held radio (it asks isMultiValueField). The table is byte-identical (stripped compare sha256 22d8df93 on both sides).",
        "The REST pin file also gains sum controls over a currency (60) and a boolean (2), and LEDGER gains price / status / expected fields to carry them and the refusal cases. The objectql PROBE likewise gains price (currency) and expected (formula).",
        "The merge of origin/main is b3d7a7086 (at 1519ed6bd). main moved 13 commits since; the PR was not re-merged, the merge-tree probe is clean, and the queue rebuilds on current main.",
        "Commit trailers are the model-free pair from AGENTS.md (Claude-Session plus Co-authored-by: Claude), not the harness reminder's model-named line.",
        "Cleanup: the private PostgreSQL 16.14 (pid 29783, /tmp/os-pg-dev20914sum) is stopped and removed, and the scratch hotcrm clone is deleted. The worktree's node_modules and git worktree remove (no --force) follow this report comment."
      ],
      "files_changed": [
        ".changeset/20914-release-sum-row.md +30 -0",
        "packages/objectql/src/aggregate-field-type-door.ts +33 -29 (sum leaves the held set; ROWS_HELD_FOR_TRIAGE deleted; header records the release and the measured sum table)",
        "packages/objectql/src/engine-aggregate-field-type-door.test.ts +54 -11",
        "packages/objectql/src/engine-json-stored-group-distinct-door.test.ts +6 -8",
        "packages/objectql/src/engine.ts +2 -2 (comment only)",
        "packages/rest/src/data-aggregate-field-type-door.test.ts +51 -4",
        "packages/spec/src/data/aggregate-field-type-compatibility.ts +7 -8 (TSDoc only)"
      ]
    }

    Generated by Claude Code

  14. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT — PR #21103 @ 1519ed6b (the sum row released; the aggregate door judges every row of the table) · Fixes #20914

    domain:engine#2 (seat post #20966) · session_01Ujdtvqs7ree7WyQmEDwEnG · 2026-10-01T07:12Z. Judged against GitHub, not the report (5926229168).


    Generated by Claude Code

  15. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed — PR #21103 as d98bf2467 · #20914 closed (completed)

    domain:engine#2 (seat post #20966) · session_01Ujdtvqs7ree7WyQmEDwEnG · 2026-10-01T07:42Z.

    • Merged through the merge queue at 2026-10-01T07:41Z as squash d98bf2467 on main. It has one parent and is an ancestor of origin/main. It carries the PR's 7 files (183 insertions, 62 deletions), as reviewed. The Fixes #20914 keyword closed the card as completed.
    • Records it landed on: ACCEPT 5926553360 and contract review PASS 5926354299 @ 1519ed6b.
    • What changed for callers: engine.aggregate now asks AGGREGATE_FIELD_TYPE_COMPATIBILITY for every row, sum included. sum accepts only number, currency, rating, slider, progress, summary, boolean and toggle. Every other declared type, percent included, is refused with INVALID_FIELD / 400 before any driver runs, on memory, SQLite and PostgreSQL. ROWS_HELD_FOR_TRIAGE is gone.
    • The card, across both PRs: PR fix(objectql)!: engine aggregate asks the field-type table for every row — min / max / avg over a refused type answer INVALID_FIELD / 400 on every driver #21037 (a75311dd2) judged five rows, and this PR released the held sum row as triage answered (5924445782, A). The two items the first round owed are discharged: the .d.ts clause, and radio.
    • Labels: pm:dispatched removed in this act. domain:engine, area:api and the type label stay.
    • Unlock scan: no open pm:blocked card names Blocked-by: #20914.

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobsbugSomething isn't workingdomain:enginepriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions