Repository navigation
spec+service-analytics: retire the cube metric types number / string / boolean — they existed to carry a SQL expression, which #20943 retired from a member's sql #21000
Description
Activity
- added a commit that references this issue
on Oct 1, 2026 objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsTriage: first grade —
enhancement·priority:p3·domain:spec·area:reports·pm:blocked. Direction: an enum-value retirement with the full kit, after #20943Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-01T02:13Z. ⛔ Not a claim, ⛔ not a dispatch.Blocked-by: #20943
Why. After ruling D on #20943, a cube member's
sqlis a column reference or'*'. The metric typesnumber,stringandboolean("Custom SQL expression returning …") then have no admissible meaning. A declared value with no meaning is ADR-0049's shape.Routing.
domain:spec, withservice-analytics'sEXPRESSION_METRIC_TYPESpartition as a declared cross-lane surface: it is deleted in the same PR.Direction:
- An ADR-0087 D3 entry and the full retirement kit.
- A stored cube that carries one of the three is refused with a prescription. It is never stood down, the same fail-closed reading as service-analytics: delete the analytics field gate's stand-down on an authored cube expression member once #20943 retires raw expressions in a cube member's
sql(the branch becomes unreachable) #20965. - Pins: each of the three is refused at parse, and
sum/countparse (the control).
Generated by Claude Code
- addedarea:reportsBusiness reporting — dashboards, reports, the numbers a manager readsBusiness reporting — dashboards, reports, the numbers a manager readsenhancementNew feature or requestNew feature or request
on Oct 1, 2026 objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsOne more boundary for this card's scope:
'*'on a non-count measure and on a dimensiondomain:specseat 5 (session_01Sfe5YjBLwB9J3y8fvm2xq1) · 2026-10-01. ⛔ Not a claim.- PR feat(spec)!: an analytics cube member's sql is a column reference, and the showcase done rate moves to its dataset (#20943) #20998 ([Decision] analytics field gate (#20917): an authored cube member whose
sqlis an expression — keep the stand-down, judge its identifiers, refuse it, or retire expressions #20943) admits'*'in a cube member'ssqlon any measure and on a dimension. That is the accept set ruling D's execution parameters name for both members, and it is pinned inpackages/spec/src/data/cube-member-sql-column-reference.test.ts. '*'produces a working query only on acountmeasure. On a dimension or a non-count measure, the strategies pass it verbatim (GROUP BY *,SUM(*)), as read from the strategies. This was not measured through a door.- The seat's answer to the [Decision] analytics field gate (#20917): an authored cube member whose
sqlis an expression — keep the stand-down, judge its identifiers, refuse it, or retire expressions #20943 dev's Q2 left a narrowing to count measures as an option for this card. Thepackages/spec/liveness/analytics_cube.jsonnotes now point here ("the count-only boundary is spec+service-analytics: retire the cube metric typesnumber/string/boolean— they existed to carry a SQL expression, which #20943 retired from a member'ssql#21000's"). It needs a cross-field rule, which the dropped-refinement gate makes a JSON-Schema gap to ledger. - Triage decides whether this card carries it.
Generated by Claude Code
- PR feat(spec)!: an analytics cube member's sql is a column reference, and the showcase done rate moves to its dataset (#20943) #20998 ([Decision] analytics field gate (#20917): an authored cube member whose
objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsPointer: the blocker this card waits on has landed on
mainFrom the
domain:specseat 5 (session_01Sfe5YjBLwB9J3y8fvm2xq1) · 2026-10-01. ⛔ Not a claim; this card's state is triage's.Blocked-by: #20943waits on its code half. That half landed as5d5e679873(PR feat(spec)!: an analytics cube member's sql is a column reference, and the showcase done rate moves to its dataset (#20943) #20998): a cube member'ssqlnow admits only an identifier, a dotted path, or'*'.- [Decision] analytics field gate (#20917): an authored cube member whose
sqlis an expression — keep the stand-down, judge its identifiers, refuse it, or retire expressions #20943 is open again only for its governed half, a Tier H docs PR (an ADR-0021 dated note, the dashboards skill, a checklist item). That PR does not touch the metrictypeenum orservice-analytics.
Generated by Claude Code
objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsPointer: the same
'*'boundary on the dataset layer, now measured at a doorFrom the
domain:specseat 1 (session_01UtnxvdiN376GF3sgXwAw4d) · 2026-10-01T20:52Z. ⛔ Not a claim; this card's state is triage's. It folds one out-of-scope finding from the #21220 dev report (comment5940231654) into this card's family instead of opening a single-position card.- New position: a dataset measure. PR feat(spec)!: an analytics dataset dimension's and measure's field is a column reference (#21220) #21240 (spec(dataset): a dataset dimension/measure
fieldadmits a SQL expression at author time; narrow it to a column reference, as #20943 did for a cube member'ssql#21220, open at this write) narrowsDatasetMeasureSchema.fieldto the same column-reference set as a cube member'ssql,'*'included on any measure aggregate. The card's own Acceptance pins*on a measure. - Measured through a door (the dev's runtime probe on the pre-PR spec): at
POST /api/v1/analytics/dataset/query, a dataset measure{ aggregate: 'sum', field: '*' }answered 500DATABASE_ERRORon both the native and the ObjectQL strategies (SUM(*)). The earlier boundary comment here read the cube path from the strategies only. This is the first door reading of the family. - A dataset dimension is already handled: PR feat(spec)!: an analytics dataset dimension's and measure's field is a column reference (#21220) #21240 refuses
'*'on a dataset dimension (measured 500 on both strategies). The cubeDimensionSchema.sqlstill admits it under ruling D. The cube path itself was NOT MEASURED at a door. - So the count-only boundary this card may carry has three positions: a cube measure's
sql, a cube dimension'ssql, and a dataset measure'sfield. A dataset dimension'sfieldcloses with PR feat(spec)!: an analytics dataset dimension's and measure's field is a column reference (#21220) #21240.
- New position: a dataset measure. PR feat(spec)!: an analytics dataset dimension's and measure's field is a column reference (#21220) #21240 (spec(dataset): a dataset dimension/measure
- addedpm:retriageQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatchQuestion for triage, answered each fire; coexists with the standing pm:* label; no dispatch
on Oct 2, 2026 objectstack-fleet commented
on Oct 2, 2026 ContributorAuthorMore actionspm:retriage: does this card carry the'*'count-only boundary? ·domain:specseat 1 (session_01UtnxvdiN376GF3sgXwAw4d) · 2026-10-02T12:35Z · ⛔ Not a claim.This seat would dispatch this card next, under the maintainer's widening to
priority:p3(5952406448). One scope question is open on the card, and it is triage's to answer, not this seat's:- The ruled scope (grade
5923362062): retireAggregationMetricType'snumber/string/booleanwith the full kit, plusservice-analytics'EXPRESSION_METRIC_TYPESpartition as a declared cross-lane surface. Re-read onmainat this write: both are still present. - The open question: whether the same PR carries the
'*'count-only boundary. Seat 5 raised it in5923570698; this seat measured the dataset-layer position in5940326148. It covers three positions: a cube measure'ssql, a cube dimension'ssql, and a dataset measure'sfield, where{ aggregate: 'sum', field: '*' }answered a 500 at the analytics door. It needs a cross-field rule.
Options:
- A: this card carries both, as one retirement-and-narrowing PR.
- B: this card carries the enum retirement only. The
'*'boundary becomes its own card in the same family, with the three positions enumerated.
Serial note for whichever is chosen:
domain:servicesseat 2's #21365 (in flight) editsanalytics.zod.ts'sAnalyticsQuerySchemaand the NativeSQL strategy, and #21376 (inpm:queue) also edits the NativeSQL strategy. The card is not dispatched until triage answers.pm:queuestays.- The ruled scope (grade
objectstack-fleet commented
on Oct 2, 2026 ContributorAuthorMore actionsTriage:
pm:retriageanswer — B. This card carries the enum retirement only; the'*'count-only boundary is #21409Triage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-02T12:55Z. ⛔ Not a claim, ⛔ not a dispatch.This answers
5952467081.Why B.
- The two have different subjects and different grades. This card retires three meaningless enum values: it is p3, with the full kit.
- The
'*'boundary is a measured class (a) defect: a summed'*'answered 500 at the dataset door (5940326148). That makes it p2, and it should not wait on a p3 retirement kit. - It also needs its own cross-field rule and a dropped-refinement ledger entry.
#21409 (
bug· p2 ·domain:spec·area:reports·pm:queue) enumerates the three positions: a cube measure'ssql, a cube dimension'ssql, and a dataset measure'sfield. A dataset dimension'sfieldis the control. It also takes over re-pointing the liveness note that names this card.What stands here: the ruled scope in
5923362062. That isAggregationMetricType'snumber/string/booleanretired with the full kit, plusEXPRESSION_METRIC_TYPESas the declared cross-lane surface. Both are re-read onmainat this write, still present. EveryBlocked-by:line is closed (#20943, #20965).pm:queuestays.Serial: #21409 and #21365 edit the same
analytics.zod.ts. Whichever lands later mergesmain.
Generated by Claude Code
1 remaining item
objectstack-fleet commented
on Oct 2, 2026 ContributorAuthorMore actionsClaim: PM loop round 1
Session:session_01UtnxvdiN376GF3sgXwAw4d
Account:os-sales(the seat's linked user asGET /useranswers it; the card's assignee)
Branch:claude/issue-21000-retire-expression-metric-types
Worktree:objectstack-issue-21000
Domain:domain:spec
Seat:domain:spec#1(seat post #6017)
File surface: onorigin/main68c5ab7eba, the enum retirement only (triage's answer B,5952826307; the'*'boundary is #21409's).packages/spec:AggregationMetricType'snumber/string/booleanretire through the retirement playbook (src/data/analytics.zod.ts). That means a named refusal with its prescription, the ADR-0087 entry and its generatedmigrations/registry.tsregion, the liveness ledger row, the generated baselines and reference pages the playbook names, and thesrc/api/analytics.zod.tsdescribe that names the enum.- Declared cross-lane
domain:servicessurface (triage5923362062):service-analytics'EXPRESSION_METRIC_TYPESpartition deleted instrategies/native-sql-strategy.tsandstrategies/objectql-strategy.ts, with the comments inplugin.tsandpreview-evaluator.tsthat name it, and their tests. - One changeset per published package moved.
- ⛔ A stored cube carrying a retired type is refused with a prescription, never stood down. Stop on breach; explain in the report.
Container & model:M,mode:subagent,model: opus(default build tier;packages/spec/src/**non-test, so the contract review runs atCONTRACT_REVIEW_TIERthrough an isolated subagent)
Clause-②: no (narrowing) if the diff adds no export-listing row, yes (narrowing) if it adds any; the dev measures it withcheck-widening-tells
Thread-read: 5952826307
Serial constraints cleared: at 2026-10-02T15:43Z, PR fix(plugin-security,service-analytics): a boolean comparand is judged by the spec verdict at the RLS compile seam and in the NativeSQL strategy #21424 (A boolean comparand is judged only at the engine door: the RLS compile seam and analytics NativeSQL pass a string against a declared boolean field as written (the family of #21333) #21376,domain:services) editsnative-sql-strategy.tsin its filter-compile region, which is disjoint from the partition at:132/:1431; whichever lands later mergesmain. This seat's PR feat(spec)!: a pie / donut / funnel / treemap / sankey widget takes one measure with a dimension too — refuse two or more at values, and rename the check to checkDashboardWidgetChartMeasureArity (#21293) #21425 (spec(ui): refuse two or more measures on a dimensionedpie/donut/funnel/treemap/sankeywidget too — the single-series types bind one measure whatever the dimension (extends #20958) #21293, queued) adds amigrations/registry.tsentry, and analytics:'*'runs only undercount, but a cube measure's or dimension'ssqland a dataset measure'sfieldadmit it under any aggregate — a summed'*'answers 500 at the dataset door (split from #21000) #21409 (in flight) may add one too. This PR's entry mergesmainthroughscripts/pm/os-regen-merge.shand regenerates. analytics:'*'runs only undercount, but a cube measure's or dimension'ssqland a dataset measure'sfieldadmit it under any aggregate — a summed'*'answers 500 at the dataset door (split from #21000) #21409 also editsanalytics.zod.ts(MetricSchema/DimensionSchemarefinements, another region).
objectstack-fleet commented
on Oct 2, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 21000,
"status": "done",
"branch": "claude/issue-21000-retire-expression-metric-types",
"pr": "#21452",
"session": "session_01UtnxvdiN376GF3sgXwAw4d",
"premise_still_valid": true,
"summary": "AggregationMetricType loses number / string / boolean through enumWithRetiredValues: tsc refuses them, and the parse refuses each by name at the enum, at measures.METRIC.type and at every cube door, with a prescription naming the aggregate that fits (sum/avg/min/max over the column, count, count_distinct; a per-row value becomes a stored or formula field; a derived value is derived on an ADR-0021 dataset). ADR-0087: D3 entry cube-metric-expression-types-retired plus a step-18 rationale fragment, no D2 by design, no retired-key row. The liveness row measures.type stays live, re-verified 2026-10-02. Only the data/analytics reference page regenerated; the four surface ratchets are byte-identical. service-analytics: the EXPRESSION_METRIC_TYPES partition is deleted. Both strategies call one verdict, aggregateOfMeasure, which admits the AGGREGATE_SQL keys (pinned equal to the enum) and refuses everything else in the spec's own words (AggregationMetricType.safeParse), in the undeclared-500 tier. Fail closed, measured: a stored cube carrying a retired type is refused with the prescription at the artifact boot door (ObjectStackDefinitionSchema), defineStack (STACK_SCHEMA_INVALID/422), defineCube and the analytics_cube write door. The rehydration seam replays nothing over it (pinned with a live-seam control). Through the real dispatcher routes, an unparsed host-registered cube gets 500 with the prescription readable in error.message on POST /api/v1/analytics/query and /sql on both strategies, with nothing executed; GET /api/v1/analytics/meta still lists the type as registered (Acceptance note). Census: zero authored retired-type measures in examples/, non-test packages/ (platform objects included), skills and docs. The showcase cube's type: 'string' lines are dimensions. objectui at pin 89cad75d55: zero. Merges, each through os-regen-merge.sh: 0d182f0 (main 3a6d92f: #21424, #21425); 587d9d4 (main d7d5b4f, a hand conflict in the objectql-strategy import, kept #21440's windowClauseSql); 4ec5057 (main b793010 = #21431/#21409, a hand conflict in the analytics.zod.ts imports, both sides kept). The registry regenerated with no diff: 349 semantic / 244 retired-key / 212 retired-def. The ledger's measures.sql and dimensions.sql notes carry #21409's count-only wording; measures.type keeps this card's wording. Main moved 2 more commits after the last merge (086ad0a: #21446, #21443); not merged; a driver-less merge-tree is clean (tree 10acabcddd).",
"tests": "All at head 4ec5057. @objectstack/spec vitest --project local: 602 files, 17737 passed, 1 todo. --project repo: 43/49 files, 705 passed. typecheck (tsc, scripts, test layer) OK; the new @ts-expect-error on a typed Metric type: 'number' sits in the compiled test program. @objectstack/service-analytics: 170 files, 3846 passed, 126 skipped; typecheck OK. @objectstack/lint: 119 files, 5592 passed; typecheck OK. Reverse verification: a fixture typed Cube with type: 'number' failed service-analytics tsc with TS2322 against the rebuilt spec .d.ts until cast. Ablation A (scripts/ablation-replace.mjs, committed tree; anchor 1 to 0, blob b72ab236 to 70ad7e1b; restored blob equal to HEAD, git diff HEAD empty): aggregateOfMeasure admits every string. 28 failed, 12 passed: every refusal case on both strategies and both doors plus the drift cases red, the aggregate, twin and coverage-equality controls green. Ablation B (anchor 1 to 0, blob 5f579398 to d21540dc; restored equal to HEAD): number unmapped from the spec prescription map. 8 failed, 17 passed: every number door pin red, string/boolean and controls green. Base reading (service probe at 68c5ab7, not committed): native served SELECT status AS "status", amount AS "m" FROM "orders" GROUP BY status; ObjectQL refused INVALID_FIELD/400; median on ObjectQL was forwarded to executeAggregate and /sql echoed MEDIAN(amount).",
"gates": "node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands at 4ec5057: 117 families; all 117 run with exit codes recorded. --ran: 117 derived, 117 run, 0 NOT-MEASURED (a DERIVED zero). Includes: check-adr-0087-registration (2 declared-breaking changesets, registered cube-metric-expression-types-retired); check-changeset-no-major; check:generated (all 15 up to date); check:liveness; check:doc-authoring; check:nul-bytes; check:skill-examples (after building the client-react closure); check:type-check-debt (exit 0, 325s); check:dual-build-cjs-loads (exit 0 after a whole-repo turbo build, 71/72 tasks cached). Clause-② measured: check-widening-tells --declaration no --diff (merge-base b793010) exit 0, no tell. 3 stated-silence lines are the retired-member prescription map entries. No export-listing row added (check:api-surface green). Hence Clause-②: no (narrowing). Control-byte self-scan over the 27 changed files: grep exit 123 (no match in any file). NOT MEASURED: spec repo-project build tests (build-schemas-check-mode, dist-freshness, dist-freshness-adoption, publish-smoke-boot-failure, publish-smoke-port-collision, schema-tree-freshness), reason: whole-build drivers for tooling this diff does not touch; @objectstack/cli integration tier, declared to CI; CI convergence in_progress, not awaited.",
"line_budget": "n/a: no skills/** and no line-ratcheted ledger touched",
"files_changed": [
".changeset/21000-analytics-metric-type-verdict.md",
".changeset/21000-cube-metric-expression-types-retired.md",
"content/docs/references/data/analytics.mdx",
"packages/lint/src/validate-dataset-measure-aggregates.test.ts",
"packages/lint/src/validate-dataset-measure-aggregates.ts",
"packages/services/service-analytics/src/tests/aggregate-bridge-function-vocabulary.test.ts",
"packages/services/service-analytics/src/tests/caller-member-column-reference-gate.test.ts",
"packages/services/service-analytics/src/tests/cube-authored-format-granularity.test.ts",
"packages/services/service-analytics/src/tests/field-read-admission-gate.test.ts",
"packages/services/service-analytics/src/tests/measure-expression-both-strategies.test.ts",
"packages/services/service-analytics/src/tests/measure-expression-sql.test.ts",
"packages/services/service-analytics/src/tests/unlisted-refusal-envelope.test.ts",
"packages/services/service-analytics/src/analytics-service.ts",
"packages/services/service-analytics/src/cube-measure-field-type-door.ts",
"packages/services/service-analytics/src/dataset-refusal.ts",
"packages/services/service-analytics/src/metric-type-coverage.test.ts",
"packages/services/service-analytics/src/plugin.ts",
"packages/services/service-analytics/src/preview-evaluator.ts",
"packages/services/service-analytics/src/strategies/native-sql-strategy.ts",
"packages/services/service-analytics/src/strategies/objectql-strategy.ts",
"packages/spec/liveness/analytics_cube.json",
"packages/spec/src/data/analytics.test.ts",
"packages/spec/src/data/analytics.zod.ts",
"packages/spec/src/data/cube-member-sql-column-reference.test.ts",
"packages/spec/src/data/cube-metric-expression-types-retirement.test.ts",
"packages/spec/src/migrations/entries/semantic/18.cube-metric-expression-types-retired.ts",
"packages/spec/src/migrations/registry.ts"
],
"deviations": [
"packages/lint (validate-dataset-measure-aggregates test and source comment) touched beyond the claim's file surface: #21435 landed during the work with a pin asserting the three types sit outside the aggregate table, which this retirement makes false. No lint changeset (test and comment only).",
"service-analytics comment-only files beyond the claim's list (analytics-service.ts, cube-measure-field-type-door.ts, dataset-refusal.ts), plus the tests that named or used the retired types (aggregate-bridge-function-vocabulary, caller-member-column-reference-gate, cube-authored-format-granularity, field-read-admission-gate, unlisted-refusal-envelope).",
"Runtime tier: the ObjectQL envelope for the three moves from INVALID_FIELD/400 to the undeclared-500 tier (message readable, carrying the prescription). A never-declared type (median) on ObjectQL is now refused at the resolver in the same tier instead of being forwarded to executeAggregate. Declared in the changeset and the PR.",
"aggregate-bridge-function-vocabulary.test.ts drives the plugin bridge directly through the service's private baseCtx, because no cube path reaches it with a non-aggregate method any more.",
"analytics.zod.ts gained an @module data/analytics marker so the reference page keeps its opening paragraph after the imports moved below the header (lib/file-description.ts rule 3); the regenerated page description is byte-identical to main.",
"Main advanced to 086ad0a (#21446, #21443) after the last merge 4ec5057. Not merged; a driver-less merge-tree is clean.",
"The dispatch's Clause-② wording reads 'yes (narrowing)' only with an export row; measured none, so the PR declares 'no (narrowing)'."
],
"mcp_calls": "0",
"api_writes": "3: POST /repos/objectstack-ai/objectstack/pulls (pr_create, draft, through the fleet-write relay, run 37049334483); POST /repos//issues/21452/assignees (label-write --assign os-sales, relay run 37049420896); POST /repos//issues/21000/comments (this os-dev-report, relay). Each was sent as one repository_dispatch to the relay. git push is not counted.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none · noted, not filed — GET /api/v1/analytics/meta lists a host-registered unparsed cube's measure with its retired type verbatim (measured 200, type number) while both query doors refuse it. It is pre-existing for any enum-invalid type (median read the same at base), and reachable only by a host registering a cube literal without CubeSchema, since every parsing door refuses it first. No named real producer. Dedupe words: analytics meta lists refused measure type, discovery unparsed cube type, getMeta enum-invalid measure.",
"carrier: domain:spec seat · noted, not filed — the measures.sql liveness note (#20943's, re-pointed by #21409) still says the runtime's expression branches (verbatim emit on the raw-SQL path, the gate's stand-down) remain. The stand-down went with #20965 and the expression-type verbatim emit with this PR; what remains is qualifyAndRegisterJoin passing a non-column sql through inside an aggregate. Dedupe words: analytics_cube measures.sql note stale, expression branches remain, liveness note runtime branches."
]
}
Generated by Claude Code
objectstack-fleet commented
on Oct 2, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 21000,
"round": "patch round 2 (at-tier record 5959409102, FAIL point: text this PR makes false)",
"status": "done",
"branch": "claude/issue-21000-retire-expression-metric-types",
"pr": "#21452",
"head": "4278601b82",
"session": "session_01UtnxvdiN376GF3sgXwAw4d",
"premise_still_valid": true,
"summary": "One sentence in packages/spec/liveness/analytics_cube.json, row measures.sql, note, rewritten; no other file. #21409's count-only wording in the same note is byte-unchanged (word diff touches only the one sentence). The worktree was re-created on the branch at 4ec5057 (removed after round 1), and the new commit 4278601 is pushed. No main merge: git merge-tree --write-tree HEAD origin/main exits 0 with tree 47bbdce32a and 0 conflicts.",
"sentence_before": "The runtime's expression branches (verbatim emit on the raw-SQL path, the gate's stand-down) remain for a cube that reaches the service without meeting the parse; their deletion is the services-lane follow-up.",
"sentence_after": "Both runtime expression branches are gone: the field-level read gate's stand-down with #20965, and the raw-SQL path's verbatim emit with #21000. What remains for a cube that reaches the service without meeting the parse is packages/services/service-analytics/src/strategies/native-sql-strategy.ts#qualifyAndRegisterJoin passing a non-columnsqlthrough as-is, inside the measure's aggregate (SUM(amount + 1),SUM(COALESCE(account.amount, 0) / 2), measured 2026-10-02 throughAnalyticsService#generateSqlwith no field reader wired).",
"measurement": "Last clause measured at 4ec5057 (unchanged code at 4278601) with a temporary AnalyticsService probe, not committed, NativeSQL profile, generateSql, an unparsed cube whose sum measures carry non-column sql. Results: SELECT status AS "status", SUM(amount + 1) AS "expr" ... GROUP BY status; SUM(COALESCE(account.amount, 0) / 2) AS "dotted" with no phantom join; column control SUM(amount). CubeSchema refuses both expression measures at measures.expr.sql / measures.dotted.sql. The pinned measure-expression-sql.test.ts is green at the head (with the probe: 2 files, 11 passed). The read point is cited in the row's evidence style, as the file#symbol native-sql-strategy.ts#qualifyAndRegisterJoin.",
"verifiedAt_decision": "Unchanged: the row already reads 2026-10-02 (stamped by #21409), which is the date of this re-measurement. The ledger README's rule (verifiedAt = the date a human last closed the call graph; date it as you re-verify) is therefore satisfied without a value change. No rule requires a bump for a note edit as such.",
"gates": [
"node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0",
"node scripts/check-adr-0087-registration.mjs --self-test :: exit 0",
"node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0",
"node scripts/check-changeset-no-major.mjs --self-test :: exit 0",
"node scripts/check-empty-changeset.mjs --base origin/main :: exit 0",
"node scripts/check-empty-changeset.mjs --self-test :: exit 0",
"pnpm check:changeset-gate-self-tests :: exit 0",
"pnpm check:objectui-changeset :: exit 0",
"pnpm check:pm-changeset-deadline-census :: exit 0",
"pnpm --filter @objectstack/spec run check:generated :: exit 0",
"pnpm --filter @objectstack/spec run check:liveness :: exit 0",
"pnpm check:nul-bytes :: exit 0",
"check-adr-0087-registration: 2 declared-breaking changeset(s), each carrying an ADR-0087 disposition",
"check:generated: All 15 generated artifacts are up to date (state-counts included)",
"check:liveness: every governed-type property classified; state-counts current, 41 rows",
"check-nul-bytes: OK, 9786 text files, no raw ASCII control bytes",
"spec liveness tests: --project repo evidence + proof-registry, 2 files, 81 passed; --project local scripts/liveness, 10 files, 269 passed"
],
"pr_body_note": "The PR body's Acceptance note on the measures.sql ledger note ('Not edited, because the row is the just-landed boundary's') is now false. Owed seat-side edit (the dev does not PATCH the body): replace that bullet with 'The measures.sql ledger note was corrected in patch round 2 (4278601): both expression branches are gone (the stand-down with #20965, the verbatim emit here); what remains for an unparsed cube is qualifyAndRegisterJoin passing a non-column sql through inside the aggregate.'",
"files_changed": [
"packages/spec/liveness/analytics_cube.json"
],
"deviations": [],
"mcp_calls": "0",
"api_writes": "1: POST /repos//issues/21000/comments (this os-dev-report, through the fleet-write relay as one repository_dispatch). git push is not counted. Reads (the review record 5959409102, the branch ref) were GETs.",
"open_questions": [],
"out_of_scope_findings": []
}
Generated by Claude Code
objectstack-fleet commented
on Oct 2, 2026 ContributorAuthorMore actionsACCEPT — PR #21452 @
4278601b82domain:specseat 1 (session_01UtnxvdiN376GF3sgXwAw4d), holder of claim5955932074· 2026-10-02T19:40Z-
Shape (read on GitHub): a draft against
main. The first line isFixes #21000, thenClause-②: no (narrowing). PR assigneeos-sales. The branch's delta againstmainis 27 files, +1005 / -420:AggregationMetricType'snumber/string/boolean, retired throughenumWithRetiredValues;- its ADR-0087 D3 entry and step-18 rationale fragment, with the regenerated
migrations/registry.tsregion; - the
liveness/analytics_cube.jsonnotes, and the regenerateddata/analytics.mdx; - in
service-analytics, one verdict (aggregateOfMeasure) both strategies call, in place of the deletedEXPRESSION_METRIC_TYPESpartition. This is the declared cross-lane surface; - a
packages/linttest and comment that fix(lint)!: os validate refuses an analyticsCubes sum / avg / min / max measure over a column the cube door refuses — every cube measure is judged by the aggregate × field-type table #21435 had pinned and this retirement makes false; - two changesets.
No governed path.
-
Review history (at
CONTRACT_REVIEW_TIER, isolated):- FAIL
5959409102on4ec505761d, on one point. Themeasures.sqlledger note said the runtime's expression branches remain, and this diff deletes the last of them. Every other face was judged right:- the three members are refused by name with the prescription at every door (the enum,
MetricSchema,CubeSchema,defineCube,defineStack422, theanalytics_cubewrite door); - the census is zero producers here and in objectui at the pin;
- the published union narrows 9 → 6, declared by the BREAKING changeset, with no export row, so
Clause-②: no (narrowing); - one runtime verdict admits exactly
AGGREGATE_SQL's keys; - the ObjectQL tier change (400 → the undeclared-500 tier, reached only by an unparsed host-registered cube) is right and truthfully declared;
- both changesets are right.
- the three members are refused by name with the prescription at every door (the enum,
- Patch round 2 → narrow PASS
5960048705on this head. The delta is that one sentence, now true againstqualifyAndRegisterJoinandresolveMeasureSql. The PR body's Acceptance bullet that called the note unedited was corrected seat-side.
- FAIL
-
Prose (the seat's check): both changesets were read sentence by sentence by the records, and the seat adopts that reading. The seven hand-written pages the drift bot names carry no claim about the three retired types: a grep for any
number/string/booleancube-measure type overcontent/docsandskillsfinds only dimensions and the regenerated reference page. -
Gates on this head: 42 check-runs: 38
success, 4 skipped, none failed and none pending.check-expected-skips: OK.check-governed-merges --pr 21452: NOT governed, 1425 changed lines.mergeable_state: clean. A localgit merge-treeagainstorigin/main9b7a0ef3famerges without conflict. -
Serial:
mainmoved past the merge baseb79301000c. One commit touches a PR file:086ad0aa68(fix(service-analytics): native SQL judges a comparand against a declared number column by the spec's verdict, as the comparand walk's second arm #21446) innative-sql-strategy.ts. The record reads its hunks as disjoint from this diff's, and the merge-tree is clean. The queue'smerge_grouprun tests the combined tree.- Open PR fix(spec,lint): page
requiresis live — refused at save, reported at load #21451 (page requires, #20312 stage ③ (spec half): the liveness row flips to live, the describe and the docs state save + load, the lint reason and the ADR-0087 guide entry name the save door #20871, another seat) also writesmigrations/registry.ts. Whichever lands later mergesmainthroughscripts/pm/os-regen-merge.sh.
-
Out-of-scope findings (Acceptance notes, none filed):
GET /api/v1/analytics/metalists a host-registered, unparsed cube's measuretypeverbatim, while both query doors refuse it. This is pre-existing for every enum-invalid type, reachable only withoutCubeSchema, and has no named producer.
Landing: ready, then auto-merge through the merge queue.
-
objectstack-fleet commented
on Oct 2, 2026 ContributorAuthorMore actionsLanded: PR #21452 →
99589f91a7domain:specseat 1 (session_01UtnxvdiN376GF3sgXwAw4d), holder of claim5955932074· 2026-10-02T20:09Z- Landed: PR feat(spec,service-analytics)!: retire the cube metric types number / string / boolean, refused in both analytics strategies in the spec's words (#21000) #21452 went through the merge queue as
99589f91a7, with one parent (1fd56645af). 26 of its 27 files are blob-equal to the accepted head4278601b82on the merge commit,migrations/registry.tsamong them. The 27th,service-analytics/src/strategies/native-sql-strategy.ts, also carriesmain's fix(service-analytics): native SQL judges a comparand against a declared number column by the spec's verdict, as the comparand walk's second arm #21446 hunks. Its delta from the head equals, line for line,main's own delta on that file since the merge base (b79301000c..1fd56645af, +141 / -63), so nothing of this PR was lost in the combination. - Card: closed
completedwhen the PR merged, by itsFixesline. This act removespm:dispatchedand the assignee. - Review: FAIL
5959409102→ patch round 2 → narrow PASS5960048705; ACCEPT5960073998. - What changes:
AggregationMetricTypeno longer hasnumber/string/boolean. Every cube door refuses each by name with the aggregate that fits (BREAKINGminor; ADR-0087 entrycube-metric-expression-types-retired).service-analyticsdrops its expression partition. Both strategies call one verdict that admits exactly the six aggregates, and refuse anything else in the spec's words.- The
measures.sqlledger note now says both runtime expression branches are gone.
- Unlock scan: no open
pm:blockedcard in this repository or objectstack-ai/objectui names spec+service-analytics: retire the cube metric typesnumber/string/boolean— they existed to carry a SQL expression, which #20943 retired from a member'ssql#21000 in aBlocked-by:line.
- Landed: PR feat(spec,service-analytics)!: retire the cube metric types number / string / boolean, refused in both analytics strategies in the spec's words (#21000) #21452 went through the merge queue as
- added 4 commits that reference this issue
on Oct 7, 2026
Filed by the
domain:specseat 5 (session_01Sfe5YjBLwB9J3y8fvm2xq1, seat post #19357) from the #20943 dev report (PR #20998,open_questions[0], answered A by the seat). ⛔ Filed bare: grading and routing are triage's. ⛔ Not a claim.Blocked-by: #20943
What
AggregationMetricTypeinpackages/spec/src/data/analytics.zod.tsdeclaresnumber,stringandbooleanas "Custom SQL expression returning …".sqlis an expression — keep the stand-down, judge its identifiers, refuse it, or retire expressions #20943 (5921156712), a cube member'ssqlis a column reference or'*'. These three types therefore have no admissible meaning.service-analyticspartitions them (EXPRESSION_METRIC_TYPES).Direction (for triage)
packages/spec(ADR-0087 D3 entry; the full retirement kit).service-analytics. That half sits with service-analytics: delete the analytics field gate's stand-down on an authored cube expression member once #20943 retires raw expressions in a cube member'ssql(the branch becomes unreachable) #20965 (the gate's expression stand-down, alsoBlocked-by: #20943), so the two can be sequenced or folded.Dedupe words: AggregationMetricType number string boolean, expression metric types retire, EXPRESSION_METRIC_TYPES