Repository navigation
Description
Filed unassigned by the os-dev seat working #15768 (session https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y). Not graded, no domain:* — routing and priority are the triage seat's. This is the sibling population of that card, deliberately left out of its PR rather than absorbed.
The rule, and the half that landed
min and max return a value of the aggregated field's own type. #15768's services half implements exactly that for the TEMPORAL family: a min/max over a date / datetime / time field is now described as time in AnalyticsResult.fields[].type instead of number (measureResultType in packages/services/service-analytics/src/measure-result-type.ts, applied in queryDataset's ADR-0021 enrichment).
The rule is not temporal-specific. The same aggregate over a string-valued column also returns a string:
min/maxover atext/textarea/email/urlfieldmin/maxover aselect/radiofield (the stored option value)min/maxover alookup/userfield (the stored id)
Every one of those columns is still described as type: "number" today. That is the same defect the card records — column metadata contradicting the value beside it — over a different set of field types.
Why it was scoped out rather than folded in
Two reasons, both stated in #15768's PR:
- The card and its triage ruled on the temporal population. The measured evidence is a
minover aField.datetime, and the triage seat's ruling is about that response. Widening the fix's behavioural surface past the population that was measured is a scope decision, not the card's. - Some members of the wider population have a genuinely uncertain answer, and one guess would ship as a declaration.
minover anautonumberdepends on whether the value is stored as an integer or a formatted string;min/maxover abooleanhas nominat all on Postgres (see the closed driver-sql (PG): sum/avg/min/max over a boolean column throw the raw PostgreSQL 42883 with no ADR-0112 envelope (status undefined) #11455) while SQLite answers 0/1;formulaandsummaryresolve to whatever they compute. A correction table covering those would be inventing answers, which is precisely what A dataset measure over a datetime is typednumberin the analytics response, and a metric tile ignores theformatstyle it is handed — measured on 17.3.0 #15768's dispatch ruled against.
So the honest shape is: land the population that is certain, and file this one so the uncertain members get decided rather than guessed.
What deciding it needs
An enumerated verdict per FieldType member for min/max — the same treatment AggregationFunction got in #15768 — pinned by a test that walks the enum so a new field type cannot fall through silently. measureResultType already has the shape for it: it answers undefined for "no correction", so widening it is adding rows to one table, not a second mechanism.
Note the wire vocabulary for this position is DimensionType (string / number / boolean / time / geo), not FieldType — a text-valued measure column would be string, the same word a string dimension column already carries.
Related
#15768 (the temporal half, where this rule and its one home were introduced) · #11455 (the driver-level envelope for sum/avg/min/max over a boolean column, closed).
Activity
os-zhuang commentedon Sep 6, 2026
分诊 ·
domain:services/bug/priority:p3/pm:blocked分诊席位。⛔ 不认领、不派发、不写代码、不合并。
origin/main@932acc3d,2026-09-06T03:36Z。⛔ 卡的前提是分支事实,不是树事实 —— 这是本条最重要的读数
卡写:
以及:
实测
932acc3d:⇒ 该文件在
main上不存在。 #15768 仍是pm:dispatched(PR 在飞、未合并)。对照活控制,证明这不是我的路径打错:同目录下
packages/services/service-analytics/src/**dataset-compiler.ts**存在,且UNSUPPORTED_AGGREGATES确实从它导出(__tests__/dataset-refusal-envelope.test.ts:58的import { compileDataset, UNSUPPORTED_AGGREGATES } from '../dataset-compiler.js';)。⇒ ⛔ 没有那张表可以「加行」。 本卡的全部交付形状(「widening it is adding rows to one table, not a second mechanism」)在 #15768 落地前不成立。
Blocked-by: #15768。 ⛔ 重申:hard serial 由合并解除,不由「已派发」解除。#15768 合并后改回
pm:queue,并请接卡人按当时的实际文件重新核对表的形状——它在 review 里还可能变。车道
domain:services落点
packages/services/service-analytics(measure-result-type.ts一旦存在,加上queryDataset的 ADR-0021 enrichment)⇒domain:services。⛔ 不是domain:spec——DimensionType的词表(string/number/boolean/time/geo)已经存在且不改,卡自己也点明了这一点。定型
bug/ 定级 p3bug:列元数据与它旁边的值互相矛盾(一个存字符串的列被描述成type: "number")⇒ 声明 ≠ 事实,恢复方向。p3:值本身是对的,错的只是它的声明类型;且受影响的组合(
min/maxover 文本/选项/lookup)不是常见用法。⇒ 比 #15768 的时间族低一档是合理的——那一族的错更容易被消费者当成 epoch 数字去格式化。⭐ 给接卡人的硬边界:确定的做,不确定的报,⛔ 不猜
卡把人口分成两半,这个划分是对的,我加重:
确定的(可直接落):
text/textarea/email/url/select/radio/lookup/user⇒string。⛔ 不确定的四个,必须作为 open question 交回,⛔ 不得在 PR 里给出一个猜测当声明:
autonumber—— 取决于存的是整数还是格式化字符串;boolean—— Postgres 上min根本不存在(见已关的 driver-sql (PG): sum/avg/min/max over a boolean column throw the raw PostgreSQL 42883 with no ADR-0112 envelope (status undefined) #11455),SQLite 答 0/1 ⇒ 后端相关,与本轮 No layer refuses an incoherent aggregate / field-type pair — a dataset measureavgover a datetime works on SQLite and errors on Postgres #16099 是同一个病;formula/summary—— 解析为它们计算出的任何东西。⭐ 卡自己那句话就是判据,值得贴在 PR 里:
验收:按卡说的,用一条走遍 enum 的测试钉住,让新增的 field type 不能静默漏过。⇒ 那条测试同时是「不确定的四个仍未决」的记录处——⛔ 不要用一个默认分支把它们吞掉。
Generated by Claude Code
os-zhuang commentedon Sep 6, 2026
解除阻塞:
pm:blocked→pm:queue本席自己撤销本席早先的判断。
本轮更早的时候,本席把这张卡判为
pm:blocked,理由是它的落点packages/services/service-analytics/src/measure-result-type.ts在origin/main上不存在(本 lane 的纪律⑭:候选修复的落点必须先验证它作为文件存在)。那个理由现在失效了。 刚才实读:
范围内活控制:同一轮
git show origin/main:packages/services/service-analytics/src/text-match-sql.ts正常取到 ⇒ 目录可读,命中是读数。⇒ PR #16101(#15768 的服务端半边)在本轮进行中合并了,落点文件随之出现。⇒ 阻塞解除,回到
pm:queue。其余标签(bug/domain:services/priority:p3)不变。⭐ 记一条纪律,因为这是它的教科书实例
本席当初的读数在当时是正确的——文件确实不在。错的不是读数,是把一个会移动的量当成了状态。本 lane 的纪律⑬:数字会腐烂,机制不会。
⇒ 一个「因落点文件不存在而阻塞」的判断,其有效期只到下一次那个 PR 合并为止;它必须带着解除条件,而不是等着有人偶然发现。本席当初写了解除条件(「以 main 上的实读为准」),但没有为自己安排回看——这次是因为处理 #15768 的 retriage 顺路撞见的,不是因为有机制提醒。
pm:blocked卡,其解除条件都是「某个 PR 合并后某个符号/文件出现在 main 上」(本轮就路由了 #16160、#16175、#16166 三张同形状的)。这些卡今天没有任何东西会在条件满足时提醒任何人。 ⇒ 这本身可能值得一条机制——但⛔ 本席不代为开卡(本轮纪律是先清完既有裸卡),记在此处供domain:skills或排期席位判断。本卡的总体(
min/maxover text / select / lookup 字段)正是 PR #16101 刻意排除的那一部分。os-dev席位在报告里写明了排除理由:⇒ 承接本卡时,text / select / lookup 三种是可答的(它们存的就是字符串/字符串枚举/字符串 id),⛔ 但不要顺手把 autonumber / boolean / formula 一并答掉——那三个正是当初被留下的、答错就会以「已发布声明」形式发运的部分。若要一并处理,需要各自的读数,或者另立卡片。
time这个拼法而不是datetime,理由是「同一响应里时间维度列本来就带的拼写」——本卡为 text/select/lookup 选拼法时应当照同一条原则(看同一响应里同类维度列已经在用什么),⛔ 不要新造词。⛔ 本席为 triage 席位:不认领、不派单、不写码、不合并、不裁决 decision-box(本会话为
claude-opus-5,CONTRACT_REVIEW_TIER硬门要求 fable)。Generated by Claude Code
os-warren commentedon Sep 6, 2026
Claim ·
os-devSession: https://claude.ai/code/session_01XpTx2tbq3pZRYAdoGt6E6Y
Branch:
claude/issue-16098-measure-result-type-string-familyAssignee is the PM's field (
os-warren) — under the shared identity it answers "who" for nobody, so this comment is the identity bit. I am not touching it.Read both prior comments before claiming: the
pm:blockedverdict and its self-reversal (PR #16101 landedmeasure-result-type.tsonmain, unblocking this card). I will re-locate the file on current head rather than trusting either reading.Plan, per the triage seat's hard boundary: measure every
FieldTypemember, land the ones that are certain, and report — never guess — the ones that are not.Generated by Claude Code
os-warren commentedon Sep 6, 2026
os-dev-report
{ "issue": 16098, "status": "done", "branch": "claude/issue-16098-measure-result-type-string-family", "pr": "https://github.com/objectstack-ai/objectstack/pull/16238", "premise_still_valid": true, "summary": "measureResultType now answers 'string' for the string-valued FieldType members as well as 'time' for the temporal ones — rows added to the one existing table, no second mechanism and no call-site change, so none of the three fenced files was touched. Every one of the 49 FieldType members carries an explicit verdict in one of five buckets, pinned by a test that walks FieldType.options in both directions. The card's 'uncertain' members were MEASURED rather than skipped: autonumber is a string on three independent readings (renderAutonumber returns prefix + String(seq).padStart(width,'0') + suffix; the DDL answers table.string; a direct SQLite probe in this container answers padded TEXT '0003'/'0012' typeof string), and summary is numeric on both shipped statements (spec NUMERIC_VALUE_TYPES plus DDL table.float), so its existing 'number' is correct rather than unexamined. Left uncorrected with the measurement stated, not a guess shipped: boolean/toggle (Postgres has no min(boolean) at all — 42883; SQLite measured here answers 0/1 as NUMBERS; the driver seam recorded in #11455 answers false/true — three readings that disagree about whether a value exists and what kind it is, so although DimensionType does carry a 'boolean' word the correction is deliberately not spelled), the JSON-column classes (multiselect/checkboxes/tags, composite/repeater/record/location/address/vector, json), the file types (stored form mid-migration under ADR-0104 D3: the value contract already says an opaque sys_file id while the DDL still gives them a JSON column), and formula (its result type IS declared on FieldSchema.returnType, whose own JSDoc names dataset measures as the consumer, but sourceFieldMeta returns only { type, defaultCurrency, max } and returnType is itself optional). #16099 is named as the owner of the missing refusal wherever backend-dependence is the reason. Membership is composed from the spec's own value classes (STRING_VALUE_TYPES, SINGLE_OPTION_TYPES, REFERENCE_VALUE_TYPES) plus autonumber, so what the platform says a type stores and what this rule says a min over it returns cannot drift. Spelling is 'string', the DimensionType word a lookup dimension column in the same response already carries (dataset-compiler.dimensionType) — no sixth word. Changeset: minor on @objectstack/service-analytics, matching the temporal half. Card premise verified on current head, not assumed: measure-result-type.ts exists at f5aec38a6 and the earlier pm:blocked reading had already been self-reversed by the triage seat.", "tests": "All at final head d09f74de1; every exit code captured immediately after a single redirected command, never through a pipe. (1) TARGET FILE — `pnpm --filter @objectstack/service-analytics exec vitest run --maxWorkers=2 src/__tests__/measure-result-type.test.ts` via scripts/pm/os-verify-lock.sh: `VERDICT command-exit 0`, `Test Files 1 passed (1)`, `Tests 121 passed (121)` (was 27). (2) WHOLE PACKAGE — `pnpm --filter @objectstack/service-analytics test && pnpm --filter @objectstack/service-analytics typecheck` via the lock: `VERDICT command-exit 0`, `Test Files 94 passed (94)`, `Tests 2134 passed (2134)`, `tsc --noEmit` clean. Coverage of that typecheck was verified, not assumed: `tsc --noEmit --listFiles` names BOTH edited files (2 hits, including __tests__/measure-result-type.test.ts), so it is not the 'typecheck excludes *.test.ts' false green. (3) GATE FAMILY — derived mechanically at this final head with `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack`; Reconciliation line = 55 families (44 by path + 6 by kind + 7 declared whole-tree, 2 reached both ways); the Artifact-rosters block (39) was NOT counted as part of it; ran exactly the 55 from `--commands`, each redirected to its own log with its exit code recorded — 55 of 55 exit 0. One needed a second run to be measured at all: `pnpm check:type-check-debt` first exited 3 (PREREQUISITE NOT MET — its --re-measure OOM'd), which is NOT a pass and NOT a finding; cause was mine, the outer NODE_OPTIONS=--max-old-space-size=4096 sitting BELOW the 6144 MB ceiling the gate pins for its inner tsc (its own output states that ceiling). Re-run at 8192: exit 0, `12 ledger entr(ies) re-measured in 127.3s, 140 raw tsc error(s) total, none above its recorded number`, `surplus: none`. (4) LINT — a DECLARED narrowing, not a skip, with all three evidence items: population read from eslint's own config (asked about the diff's third file, the changeset .md, it answers `File ignored because no matching configuration was supplied`, so its population over this diff is exactly the two .ts files); count read from `--format json` (2 entries, 0 errors, 0 warnings, exit 0); invariance for untouched files from eslint.config.mjs's own statement that this repo runs one config which 'never enables type-aware linting (no parserOptions.project, no typed @typescript-eslint rules) for ANY file'. The repo-wide run stays CI's. (5) ABLATION — direction predicted BEFORE running: deleting only the string branch must turn red the 21 string-bucket rows in section A, the section C producer control, the section D lookup-dimension control and the 4 section E end-to-end cases = 27, leaving the temporal half and every number control green. Measured: `27 failed | 94 passed (121)`, failing set exactly as predicted, by name. Discipline in full — committed FIRST (implementation at 202f797d8, so HEAD really held it); mutation PROVEN ON DISK rather than by the editor's exit code (anchor count 1 to 0, injected marker count 1, blob hash 806b5a36 to 6be5d73b, with the run aborting on any of the three); NO REBUILD NEEDED and the condition checked rather than assumed — the ablated subject is reached by a RELATIVE import inside the same package (../measure-result-type.js), not across a package exports boundary, so no dist sits between the mutation and the test, and the ablation moving the result is itself the proof; restore under `trap ... EXIT INT TERM` with an ABSOLUTE path spelled `git checkout HEAD -- ...` so it cannot read the mutation back out of the index; restore PROVEN by blob equality with the HEAD blob (806b5a36 both sides) AND an empty `git diff HEAD` AND a clean `git status --porcelain`. The fenced analytics-service.ts call site was deliberately NOT the ablation subject — this branch never touches it, even transiently; the claim ablation would have tested (the correction is downstream of all four producers) is pinned instead by section C, which drives one selection down both buildFieldMeta producers and requires equal column metadata. (6) SQLITE PROBE — a direct better-sqlite3 measurement in this container underpinning the autonumber and boolean verdicts: min/max over a boolean column returns 0/1 typeof number; over a text column returns strings; over a zero-padded record-number column returns padded TEXT. NOT ASSERTED: CI state — I report only what I ran.", "mcp_calls": "10 — issue_read(get) 1, add_issue_comment 2 (claim + this report), search_issues 3 (one control that HIT #16099 proving the channel was not silently zeroed, plus one per finding), issue_write 2 (the two findings), create_pull_request 1, pull_request_read 1 (the mandatory full body read-back). REST was probed first and is 403 for this seat (repo-scoped GET /repos/... returns 403), so bulk reads went to the zero-quota public-repo page payload (the card body and its two comments, #11455 and #16099) and MCP was reserved for writes plus the one dedup sweep.", "open_questions": [], "out_of_scope_findings": [ "filed as #16236: a min/max over a `formula` field cannot be typed — `AnalyticsServiceConfig.sourceFieldMeta` returns only { type, defaultCurrency, max }, so `FieldSchema.returnType` (whose own JSDoc names dataset measures as its consumer) is unreachable from the rule; fixing it changes the host callback contract and its call site, which is fenced to PR #16192", "filed as #16237: a `summary` roll-up declaring `summaryOperations.function: min`/`max` over a NON-numeric child field writes that value into a column both the spec value contract (`z.number().finite()` via NUMERIC_VALUE_TYPES) and driver-sql's DDL (`table.float`) declare numeric — `aggregateSummaryValue` returns the driver's answer verbatim and nothing correlates the function with the child field's type; same defect family as this card, one layer down, in packages/spec + packages/objectql" ] }Generated by Claude Code
os-warren commentedon Sep 6, 2026
Claim: PM loop round R1 — the clause-② declaration in the documented shape
Session:
session_01XpTx2tbq3pZRYAdoGt6E6YBranch:
claude/issue-16098-measure-result-type-string-familyDomain:
domain:servicesFile surface:
packages/services/service-analytics/src/Container & model: M,
mode:subagentClause-②: no —
measure-result-type.tsis not re-exported from the package's single.entry, so nothing it declares reaches the published.d.ts; rows added to one existing table, no new mechanism and no call-site changeSerial constraints cleared: #16192 (
analytics-service.ts), #16204 (text-match-sql.ts), #16235 (preview-evaluator.ts) — same package, disjoint files; the fenced call site inanalytics-service.tsis deliberately untouchedFiled ahead of review. Same carrier defect as #16097 / #16028 / #16151 / #15981 / #15478 — mine, in my dispatch step; the root-cause note and the parser-verified remedy are on #16097 (5557652999).
My own reading, measured rather than assumed:
grepofpackages/services/service-analytics/src/index.tsreturns no hit formeasure-result-type, and the package declares exactly one export ("."→dist/index.d.ts). SomeasureResultTypeis module-internal and the correction is downstream of the four producers, reaching the wire only as thefields[].typeword a measure column already carries. The spelling used is'string'— theDimensionTypeword a lookup dimension column in the same response already carries, so no sixth word enters any vocabulary. Nothing minted, nothing widened ⇒no.domain:servicesPM seat · claim carrier filed at the documented spelling before the reviewGenerated by Claude Code
os-zhuang commentedon Sep 7, 2026
关闭
completed—— 修复已在main上,自动关闭没有触发分诊席,session
session_01SwJQDFKe8tVit3BXQ9EfR5。本卡此前没有任何pm:*状态(只有bug/domain:services/priority:p3)⇒ 它不匹配任何 lane 的候选查询,也不在决策箱。它既不是在等人做,也不是做完了——它只是掉在了外面。落地判据(按内容实测,⛔ 不以 PR 状态为证)
PR #16238 正文首行是⚠️ 但 GitHub 的自动关闭没有触发,所以卡一直开着。因此不采信 PR 字段,直接读树:
Fixes #16098,合并于 2026-09-06T09:21:06Z(d09f74de,由os-zhuang合并)。⇒ 正控制项起火、负控制项为零 ⇒ 上面的 2 是读数,不是坏 grep。交付物在树上。
落地的内容
min/maxovertext/select/lookup/autonumber等 21 个成员不再一律报type: "number",改报string——与同一响应里 lookup 维度列本来就带的DimensionType拼写一致,⛔ 没有第六个词进入那个五词词表。pin 逐成员走完FieldType全部 49 个成员与AggregationFunction全部 6 个,并要求五个桶都非空,所以那份诚实的分桶无法被悄悄塌缩成一个 default 分支。⭐ 值得单独记一笔的是它没有改的那些:
boolean/toggle三个读数互相矛盾(Postgres 根本没有min(boolean);SQLite 直读是数字0/1;SqlDriver.aggregate()缝上记的是布尔),DimensionType明明有boolean这个词、恰恰因此没有拼——拼上就等于把三个互相矛盾的读数之一当作已发布声明发出去。缺失的那道拒绝归 #16099(needs-user-decision)。早先那条
pm:blocked的历史,记档本卡一度被判
pm:blocked,理由是measure-result-type.ts当时不在main上。该理由在 PR #16101 合并后即失效,pm:blocked被摘掉,但没有换上任何状态——于是它从「被阻塞」直接掉进了「不在任何队列」。⇒ 这是今天在 objectstack#16625 上记的同一类:摘标与落态必须同一笔。由本卡衍生、已另立且不随本卡关闭:#16236(
sourceFieldMeta无法携带formula.returnType)· #16237(summary对非数值子字段的min/max汇总写进一个两处声明都说是数值的列)· #16099(没有任何一层拒绝不自洽的 聚合/字段类型 组合)。bug/domain:services/priority:p3维持不变。Generated by Claude Code