Repository navigation
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
Conversation
… field-type table WIP: rule and tests; docs and changeset follow. Claude-Session: https://claude.ai/code/session_01UtnxvdiN376GF3sgXwAw4d Co-authored-by: Claude <noreply@anthropic.com>
…he aggregate x field-type table Claude-Session: https://claude.ai/code/session_01UtnxvdiN376GF3sgXwAw4d Co-authored-by: Claude <noreply@anthropic.com>
…s judged on every row of the table Claude-Session: https://claude.ai/code/session_01UtnxvdiN376GF3sgXwAw4d Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UtnxvdiN376GF3sgXwAw4d Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 6 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 4 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 4 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 37796ee7bf9b75f04bf074c0a009545bd361ca42 && git checkout 37796ee7bf9b75f04bf074c0a009545bd361ca42
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 3a6d92f78bb6a160b762dfee0738fd3b0b7ae6c2 1de24da8fb3447429901f228dde1673c505e5325 && git checkout -B drift-repro 3a6d92f78bb6a160b762dfee0738fd3b0b7ae6c2 && git merge --no-ff 1de24da8fb3447429901f228dde1673c505e5325
node scripts/docs-audit/affected-docs.mjs --json 3a6d92f78bb6a160b762dfee0738fd3b0b7ae6c2
|
…string / boolean, refused in both analytics strategies in the spec's words (objectstack-ai#21000) (objectstack-ai#21452) Fixes objectstack-ai#21000 Clause-②: no (narrowing) Dispatched by the claim `5955932074` (PM loop round 1, `domain:spec` seat 1, session `session_01UtnxvdiN376GF3sgXwAw4d`), on triage's answer B `5952826307`: the enum retirement only. The row wildcard boundary is objectstack-ai#21409's (landed as `b79301000c`, merged here). ## What this does **`@objectstack/spec`: `AggregationMetricType` loses `number`, `string` and `boolean`** (ADR-0049 enforce-or-remove, grade `5923362062`). - They declared "a custom SQL expression returning a number / string / boolean": the measure's `sql` was the whole computation. Since ruling D on objectstack-ai#20943 (`5d5e679873`), a cube member's `sql` is a column reference, so the three had nothing left to compute. - The enum is declared through `enumWithRetiredValues` (`shared/retired-key.ts`), the house value-level mechanism. The six aggregates (`count`, `sum`, `avg`, `min`, `max`, `count_distinct`) are the whole vocabulary. - An authored retired type fails `tsc`, because it is gone from the type. It is refused at parse with a named prescription, at the enum, at a metric's `type` and at a cube's `measures.METRIC.type`. - The prescription names the aggregate that fits: `sum`, `avg`, `min` or `max` over the column; `count` over `'*'` or over a column; or `count_distinct`. A per-row value becomes a stored or formula field of the object that the measure aggregates. A value derived from measures is `derived: { op, of }` on an ADR-0021 dataset. - A value the enum never declared keeps zod's own message. - The prescriptions are module-private, so the export surface does not grow. - **ADR-0087:** D3 entry `cube-metric-expression-types-retired` (`migrations/entries/semantic/`), with a step-18 rationale fragment (order 62). `registry.ts` was regenerated by `gen:migration-registry` after each merge, never edited by hand. - There is no D2 conversion, by design: the column alone does not say which aggregate the author meant, and a stored cube is refused, never rewritten. - There is no `RETIRED_KEYS_BY_MAJOR` row, because no key left the shape. - **Liveness:** the `analytics_cube` row `measures.type` stays `live`, re-verified 2026-10-02. The narrowing is recorded, and the evidence now names `aggregateOfMeasure` instead of the partition. - **Generated:** only `content/docs/references/data/analytics.mdx` moved, losing the three values from the enum list and from both `type` cells. - `api-surface`, `authorable-surface`, `json-schema.manifest` and `api-surface-signatures` are byte-identical, as the playbook predicts for an enum-value narrowing. - `spec-changes.json` and the upgrade guide stay at protocol 17, and both checks are green. - The module header gained an `@module data/analytics` marker. Without it, moving the imports below the header dropped the page's opening paragraph: `lib/file-description.ts` rule 3 does not select a block inside the import list without the marker. The page's description is byte-identical to `main`. - The neighbouring `CUBE_MEMBER_SQL` docblock no longer says that the ObjectQL path refuses the partition, in the present tense. **`@objectstack/service-analytics`: the `EXPRESSION_METRIC_TYPES` partition is deleted.** It is replaced by ONE verdict both strategies call, `aggregateOfMeasure` (`strategies/native-sql-strategy.ts`). - `aggregateOfMeasure` admits a type this runtime lowers (the `AGGREGATE_SQL` keys, pinned EQUAL to the enum's options). It refuses everything else with the SPEC's own words, `AggregationMetricType.safeParse(type)`, so the runtime keeps no list of metric types, retired or otherwise. - `NativeSQLStrategy#resolveMeasureSql` asks it before anything is lowered. The verbatim emit is gone, and so is the unrecognised-type throw it replaced. - `ObjectQLStrategy#resolveMeasureAggregation` asks it at the one resolver both doors call. The `INVALID_FIELD` arm for the partition is gone. - Comments that named the partition or the three types are updated: `plugin.ts` (the bridge's comment, its docblock, and its runtime message, which said "a custom-SQL measure is refused earlier"), `preview-evaluator.ts`, `analytics-service.ts`, `cube-measure-field-type-door.ts` and `dataset-refusal.ts`. **`@objectstack/lint` (test and comment only).** objectstack-ai#21435 landed between my merges with a pin asserting that the three types sit outside the aggregate table. That is false after this retirement, so the assertion now reads `[]`. The skip-5 `silent` case is kept. No changeset is needed: a comment and a test, nothing in the published output changes. ## Clause-②, measured `node scripts/pm/check-widening-tells.mjs --declaration no --diff` (the merge-base diff against `b79301000c`) exits 0, with no widening tell. Three key lines are reported as a stated silence. They are the retired-member prescription entries `number:` / `string:` / `boolean:` in the `enumWithRetiredValues` map, which are refusals, not accept-set members. No export-listing row was added (`check:api-surface` green, byte-identical), so the line is `Clause-②: no (narrowing)`. Both changesets are BREAKING, with `!`, a **BREAKING** banner, the `(narrowing)` arm, exactly one ADR-0087 marker (`registered cube-metric-expression-types-retired`) and `minor`. ## Census (examples, packages, platform objects, objectui) The instrument is an AST walk over every object-literal member of a `measures:` record. It covered 7,654 `.ts`/`.js` files under `examples/**` and `packages/**` at `4ec505761d`, platform objects included. - 321 measure entries in total. Lit control: `count` 164, `sum` 71. - Retired-type entries: 7. All are deliberate refusal fixtures in the `service-analytics` tests, built without the parse. - Zero hits in `examples/**`, in non-test `packages/**`, in `skills/**` and in `content/docs/**` (one cube example there, `count` / `sum`). - `examples/app-showcase/src/data/analytics/showcase.cube.ts`: 3 measures (`count`, `sum`, `avg`). Its three `type: 'string'` lines (48, 53, 63) are **dimensions**. `DimensionType` is a separate enum, unchanged, and pinned in the new test file. - JSON fixtures carrying record-form `measures`: zero. - objectui at the `.objectui-sha` pin `89cad75d55`: 0 mentions of `AggregationMetricType`, and 0 record-form measure entries over 528 files that mention `measures`. Control: `clientValidation.ts` names `CubeSchema`. The Console Pin Gate is not at risk: no export left. ## Premise check (zone 2) 1. **Holds.** On `68c5ab7eba`, `AggregationMetricType` (`data/analytics.zod.ts:27`) listed the three, and `MetricSchema.type` used it. Measured through `AnalyticsService` with a column `sql`: - the raw-SQL path SERVED the column unaggregated: `SELECT status AS "status", amount AS "m" FROM "orders" GROUP BY status`; - the ObjectQL path refused the measure `INVALID_FIELD` / 400. 2. `api/analytics.zod.ts:231`: the `/analytics/meta` member's describe ("Aggregation type for a measure (`AggregationMetricType`)") is not made false by the retirement, so it is not edited. 3. That `type` is a separate `z.string()` field, deliberately not the enum, because the projection copies the value verbatim. Measured: it is not the enum. ## What a stored cube carrying a retired type meets (fail closed, never stood down) **Pinned in `cube-metric-expression-types-retirement.test.ts`:** - the artifact boot door (`ObjectStackDefinitionSchema`, the parse `MetadataPlugin` runs a built artifact through) refuses it at `analyticsCubes.0.measures.m.type` with the prescription; - `defineStack` refuses it with `STACK_SCHEMA_INVALID` / 422; - `defineCube` and the `analytics_cube` write door (`getMetadataTypeSchema('analytics_cube')`, what `PUT /api/v1/meta/analytics_cube/NAME` validates) refuse it too; - the rehydration seam (`applyConversionsToStoredItem`) replays NOTHING over it. Control: the same row's retired sub-day granularity IS rewritten, so the seam is live. The stored row reaches the parse as stored, and the parse refuses it. **Measured through the real dispatcher routes** (a temporary `packages/runtime` probe, not committed), for a cube a host registers in-process WITHOUT the parse: - `POST /api/v1/analytics/query` and `POST /api/v1/analytics/sql`, on both strategies: `500`, with `error.message` carrying the spec's prescription verbatim. Nothing executed. - `sum` control: `200`. - `GET /api/v1/analytics/meta`: `200`, listing the measure with `type: "number"` as registered (see the Acceptance notes). ## Merges (serial constraints) - `0d182f0549` merged `main` at `3a6d92f78b`, bringing PR objectstack-ai#21424 (objectstack-ai#21376, the NativeSQL filter-compile region) and PR objectstack-ai#21425 (objectstack-ai#21293, its registry entry). Clean. - `587d9d4b63` merged `main` at `d7d5b4f96a`. One hand conflict, in the `objectql-strategy.ts` import from `native-sql-strategy.js`: objectstack-ai#21440 added `windowClauseSql`, and both are kept. - `4ec505761d` merged `main` at `b79301000c`, bringing PR objectstack-ai#21431 (objectstack-ai#21409, the count-only boundary). One hand conflict, in the `analytics.zod.ts` imports: `enumWithRetiredValues` and objectstack-ai#21409's `ANALYTICS_COLUMN_PATH` / `rowWildcardOutsideCount` / `rowWildcardOutsideCountRefusal`, both kept. - Every merge went through `scripts/pm/os-regen-merge.sh`. Its step 4 was run each time, and `gen:migration-registry` afterwards wrote no diff. - **Registry reading at `4ec505761d`:** 349 semantic, 244 retired-key and 212 retired-def entries. The siblings' ids `analytics-row-wildcard-outside-count-refused` and `dashboard-widget-single-series-multi-measure-refused` appear at the same counts as on `main` (1 each). `cube-metric-expression-types-retired` appears twice: the semantic entry and its step-18 rationale fragment. - **Ledger reading at `4ec505761d`:** the `measures.sql` and `dimensions.sql` notes carry objectstack-ai#21409's count-only wording and no longer name this card. `measures.type` carries this retirement's own wording. ## Tests (head `4ec505761d`) - `@objectstack/spec`: - `vitest --project local`: 602 files, 17737 passed, 1 todo; - `--project repo`: 43 of 49 files, 705 passed (the other six are NOT MEASURED, below); - `typecheck` (`tsc`, scripts, test layer): OK. The new `@ts-expect-error` (a typed `Metric` with `type: 'number'`) sits in the compiled test program. - `@objectstack/service-analytics`: 170 files, 3846 passed, 126 skipped. `typecheck` OK. - The reverse verification happened on the way: a fixture typed `Cube` with `type: 'number'` failed `tsc` with TS2322 against the rebuilt `.d.ts` until it was cast. - `@objectstack/lint`: 119 files, 5592 passed. `typecheck` OK. ## Ablations Both run from the committed tree through `scripts/ablation-replace.mjs`. In each, the anchor hit once and the blob changed; the restore was proved by blob equal to `HEAD` and an empty `git diff HEAD`. Both subjects resolve from `src`, so no rebuild was needed. - **The runtime verdict admits every string** (`aggregateOfMeasure`'s table check removed): 28 failed, 12 passed, over `metric-type-coverage`, `measure-expression-both-strategies` and `measure-expression-sql`. - Every refusal case went red: both strategies, both doors, and the drift case. - The admitted-aggregate, cross-object-twin and coverage-equality cases stayed green, which is the predicted direction. - **The spec's `number` prescription is unmapped:** 8 failed, 17 passed in `cube-metric-expression-types-retirement.test.ts`. Every `number` door pin went red; the `string` / `boolean` pins and the controls stayed green. ## Gates `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` at `4ec505761d` derived 117 families. All 117 were run, each exit code recorded, and `--ran` answered "117 derived, 117 run, 0 NOT-MEASURED (a DERIVED zero)". Among them: - `check-adr-0087-registration` ("2 declared-breaking changeset(s), each carrying an ADR-0087 disposition"); - `check-changeset-no-major`; - `check:generated` ("All 15 generated artifacts are up to date"); - `check:liveness`, `check:doc-authoring`, `check:nul-bytes` and `check:skill-examples` (after building the client closure); - `check:type-check-debt` (325 s); - `check:dual-build-cjs-loads` (after a whole-repo build, 71 of 72 tasks cached). **NOT MEASURED** - `@objectstack/spec` repo-project files `build-schemas-check-mode`, `dist-freshness`, `dist-freshness-adoption`, `publish-smoke-boot-failure`, `publish-smoke-port-collision` and `schema-tree-freshness`. Reason: they drive whole builds and exercise build tooling this diff does not touch. - `@objectstack/cli` integration tier, declared to CI. ## Acceptance notes - **`GET /analytics/meta` still lists a host-registered unparsed cube's measure with its retired `type`**, while the query doors refuse it. This is pre-existing for any enum-invalid type (`median` read the same at base). It is reachable only by a host that registers a cube literal without `CubeSchema`, because every parsing door refuses it first. Not filed; carrier: none. - **A stored `analytics_cube` row** read at `GET /api/v1/meta/analytics_cube/NAME` comes back as stored. Stored rows keep being read (the runtime gate's D4 asymmetry), and no conversion rewrites it (pinned at the seam). - Re-saving it is refused with the prescription (the write door pin). - The analytics registry has no metadata read path (objectstack-ai#20965's measurement), so such a row reaches no query. - The HTTP read itself was NOT MEASURED through the route. - **The ObjectQL envelope for the three moves from `INVALID_FIELD` / 400 to the undeclared-500 tier.** The message is readable and carries the prescription. This is the tier `dataset-refusal.ts` assigns to a cube that never met the parse, and the changeset says so. - A never-declared type (`median`) on the ObjectQL path is now refused at the resolver in the same tier. Before, it was forwarded to `executeAggregate`: the auto-bridge refused it, a host's own executor received it, and `/analytics/sql` echoed `MEDIAN(amount)`. - `aggregate-bridge-function-vocabulary.test.ts` therefore pins both seams. The bridge is driven directly through the service's strategy context, because no cube path reaches it with a non-aggregate method any more. - **The `measures.sql` ledger note** (objectstack-ai#20943's, re-pointed by objectstack-ai#21409) was made false by this diff, as at-tier record `5959409102` ruled. It was corrected in patch round 2 (`4278601b82`): both runtime expression branches are gone (the gate's stand-down with objectstack-ai#20965, the raw-SQL verbatim emit here). What remains for a cube that reaches the service without meeting the parse is `qualifyAndRegisterJoin` passing a non-column `sql` through inside the aggregate, measured through `generateSql`. - **The prescriptions say "removed … in @objectstack/spec 17.7.0"**, assuming the next release is a minor (the label is 17.6.0, which is published). If the next release is cut as a major, those runtime strings need the new number. - **Files beyond the claim's list**, all comments or tests that named the partition or the types, or that the merges made false: - `service-analytics`: `analytics-service.ts`, `cube-measure-field-type-door.ts`, `dataset-refusal.ts`, and the tests `aggregate-bridge-function-vocabulary`, `caller-member-column-reference-gate`, `cube-authored-format-granularity`, `field-read-admission-gate` and `unlisted-refusal-envelope`; - `lint`: `validate-dataset-measure-aggregates` (source comment and test). --- _Generated by [Claude Code](https://claude.ai/code/session_01UtnxvdiN376GF3sgXwAw4d)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Fixes #21419
Clause-②: no (narrowing)
What changes
@objectstack/lint's dataset-member rule (packages/lint/src/validate-dataset-measure-aggregates.ts) now judges everyanalyticsCubesmeasure throughacceptsDeclaration, the verdict a dataset measure already gets. Until now the cube leg judgedcount_distinctonly, so a cubesum/avg/min/maxover a column the cube door refuses passedos validate.typeis its aggregate. Everytypethat is a row ofAGGREGATE_FIELD_TYPE_COMPATIBILITYis judged on the column the door reads. That column is the cube's object, or, for a relationship path, the last hop's declared join, else the lookup'sreference(the resolution PR fix(lint)!: os validate refuses an analyticsCubes member the analytics door refuses — a dimension over a JSON-stored column, and count_distinct over one #21416 added, unchanged).countreads no value and is accepted on every type, as on a dataset.typeoutside the table (number/string/boolean, the expression metric types) is skip 5, as on a dataset. Neither cube door judges it either.'*'is still skipped. Whether it belongs on a non-count measure is the question 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 owns; nothing here refuses it.if (aggregate !== 'count_distinct') continue;becomesif (!aggregate) continue;, and the existing skip 5 (ACCEPTED_TYPES_BY_AGGREGATE.get) now does the vocabulary work it does for a dataset. The finding id (measure-aggregate-field-type-refused), its location (analyticsCubes[N].measures.KEY.type) and its words are the ones the cubecount_distinctleg already emits.Text this PR makes false, corrected in it: the rule's module note and the
validate-dataset-measure-aggregates.tsdocblock, and the cube paragraph in §6 ofcontent/docs/deployment/validating-metadata.mdx. Both said a cube's other measure types were not judged. The landed.changeset/21082-cube-member-json-stored-refused.mdis released history and is left as it is.Files changed (4): the rule, its test file, the docs paragraph, one changeset.
index.ts,runtime.ts,package.jsonandservice-analyticsare untouched.The cube door's verdict, pair for pair
The enumeration pin in the test file crosses every cube measure
type(AggregationMetricType, which holds every row of the table) with everyFieldType, flaggedmultiple: trueand not. It checks each pair against the two cube doors' rules as their modules state them:count/sum/avg/min/max:cube-measure-field-type-door.ts. The row is checked on the declared TYPE, and themultipleflag is not read.count_distinct:structured-json-dimension-door.ts. The row is checked, and so isisMultiValueField.Per-type floors hold on both sides.
As a one-off proof against the real door rather than its stated rule, a scratch script was run and not committed. For each case it built a real
AnalyticsServicewithsourceFieldMeta,relationshipResolverandgetObjectFieldNameswired. Then it calledgenerateSqlon one measure, which goes throughensureCubeto both doors. It compared the refusal (INVALID_FIELDwith the door's own words) against the lint verdict. The cases were everyAggregationMetricType× everyFieldType×multiple{false, true} × three column positions: bare, reference-tier path and join-tier path.1de24da8fb(the rule's blob is0d56d08213since1236c5a4b7)39a912ea73(control)No pair diverges, so there is nothing to report as a door/table disagreement.
os validate, before and afterFixtures
fx_ledger+ one cubefx_cubewere measured with the CLI built from this tree. Before is the same tree with the guard reverted throughscripts/ablation-replace.mjs. The lintdist/was rebuilt, andablation-dist-preflightconfirmed the marker present in 4 built files. After is the restored tree, rebuilt, with the marker confirmed absent from all 8 built files and the tree clean against HEAD.oos-cube-sum-text(sumovername,text), the card's measured instanceanalyticsCubes[0].measures.sum_name.typeavgovertextmaxovertextminover a singleselectavgoverdatetimesumoveraccount.name, reference tier (fx_account,text)sumoveraccount.revenue, join tier (joins.accountreachesfx_branch, where it istext)fx_branchThe scalar control holds
sumandavgovernumber,maxandminoverdatetime,countandcount_distinctovertext,sumoveraccount.revenueby reference (number), andsumover'*'.Census
The census covers
examples/**,packages/**(fixtures included) and the platform objects. It found one authored cube in the shipped corpus: showcase'sshowcase_delivery, withcountover'*'andsum/avgoverestimate_hours(number). It stays clean. Every otheranalyticsCubesordefineCubesite is a spec, service-analytics, runtime, objectql or driver test, and none of them runs a lint rule.At HEAD
1de24da8fb,os validateexits 0 onexamples/app-showcase,app-crmandapp-todo, with 0 findings of either cube id.Clause-②: the measured arm
node scripts/pm/check-widening-tells.mjs --declaration no --diffon the final diff (git diff 39a912ea73...HEAD) exits 0. All 4 files are NOT MEASURED (no declared surface covers them), so it decided nothing.exportlines inpackages/lint/src, tests excluded. The positive control5e470f8c1cadds 1.index.ts,runtime.tsandpackage.jsonare unchanged.no (narrowing). The changeset is still BREAKING, because metadata that passedos validatecan now fail.Changeset
.changeset/21419-cube-measure-aggregate-field-type-refused.md:@objectstack/lintminor,fix(lint)!, a BREAKING banner,Clause-②: no (narrowing), and exactly one ADR-0087 marker:not-required (no-migration-prescription).check-adr-0087-registrationaccepts it and reads it as[BREAKING+bang+clause-②-narrowing].Tests
src/validate-dataset-measure-aggregates.test.tsgains 6 tests in a new block, and 1 test is rewritten.os validatepasses ananalyticsCubesmember the analytics door refuses: lint reads no cube, so a cube dimension over a JSON-stored field passes authoring and is refused 400 at query time #21082 test that pinned "judges onlycount_distinct". It now pins thatcountover ajsoncolumn is accepted andsum/avg/min/maxover it are refused.textanddatetime;type,'*', an unresolved column, an unfollowable hop, an untyped column, a cube naming no object;runAuthoringRuleson all three commands.Runs at
1de24da8fb, the final commit, which includes the merge oforigin/main68c5ab7eba:pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2: 119 files, 5592 tests passed.pnpm --filter @objectstack/lint typecheck: exit 0.tsc -p tsconfig.test.json --listFilesincludes the test file..tsfiles. The.mdxand.mdfall outside the config's**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}glob, and--print-configresolves a config for both.tsfiles.--format jsonreports 2 files, 0 errors and 0 warnings.eslint.config.mjsenables no type-aware linting (noparserOptions.project), so this diff cannot move a verdict on an untouched file.Ablations ran on the committed tree through
scripts/ablation-replace.mjs. Each restore was proven by its blob hash matching HEAD and an emptygit diff HEAD.count_distinctonlymaxrow dropped on cube measurescube max(text)Gates
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandsat1de24da8fbderived 90 commands, and all 90 were run.check:skill-examplesfirst exited 3: PREREQUISITE NOT MET, becauseclient-reacthad no.d.ts.check:dual-build-cjs-loadsfirst exited 3: 7 packages had nodist.check:dts-closurefirst exited 1, because of this worktree's ownOS_SKIP_DTSbuilds.--ranreconciliation: 90 derived, 90 run, 0 NOT-MEASURED (a derived zero: every line recorded its exit code), 0 UNRUN.check-changeset-fixed,check:authz-resolver,check:error-code-casingandcheck:filter-alias-parity. All exit 0.mainmoved 5 commits after the merge (3a6d92f78b). None of them touchespackages/lint, the docs page or either cube door. The oneservice-analyticschange is the NativeSQL boolean-comparand door. CI's merge ref tests the combined state.Acceptance notes
not-required (already-registered dataset-measure-selecting-aggregate-field-type-refused, dataset-measure-aggregate-field-type-refused)for these same pairs. This rule's own cube leg declaredno-migration-prescription, on the ground that those two entries describeDatasetMeasureSchemarows. This PR follows the rule's own leg. Either marker passes the gate; if the seat prefers the other, the change is the one comment line.FieldType.acceptsDeclarationis fail-closed on vocabulary, so the lint would refuse evencountover such a column, where both cube doors stand down.os validatecannot reach that case:defineStackrefuses the field first (measured:objects.0.fields.amount.type: Invalid field type 'integer', exit 1, no author-time rule runs). The dataset leg has the same shape.validate-field-consumers.ts). This was seen while measuring and is reported to the seat, not fixed here.os validateon the scalar control flagsfx_account.revenueandfx_ledger.accountas "inert — no site of any kind names it". That is wrong: the cube measuresum_revreadsaccount.revenue. A dataset measure overaccount.revenuewithinclude: ['account']gets the same inert verdict onfx_account.revenue.analytics_cubewrite door still dispatches no authoring rule, as PR fix(lint)!: os validate refuses an analyticsCubes member the analytics door refuses — a dimension over a JSON-stored column, and count_distinct over one #21416 recorded. Read from code, not measured. Carrier: none.Generated by Claude Code