Repository navigation
Commit 6210f88
Fixes #21439
Clause-②: no
## What changed
`field-no-consumers` (`packages/lint/src/validate-field-consumers.ts`)
now owns the four analytics slots that name a column: a dataset
dimension's and measure's `field`, and a cube dimension's and measure's
`sql`. Each path credits every field it reads. Those fields are the
lookup on the base object, each intermediate lookup, and the column on
the object the last hop reaches. They are the same fields the analytics
door's field-level read gate names (`fieldsOfColumnSql` in
`service-analytics`).
- **One hop resolver.** A cube's path goes through `resolveCubeColumn`,
the resolver the cube leg of `validate-dataset-measure-aggregates.ts`
already uses: the cube's declared join for the hop, else the lookup's
`reference`. That function is now `export`ed from its module. It is not
re-exported from the package barrel: `resolveCubeColumn` appears 0 times
in `dist/index.d.ts`, `dist/index.d.cts` and `dist/runtime.d.ts`. A
dataset's path goes through `resolveFieldPath` plus
`joinablePrefixes(include)`, the pair `validate-dataset-references.ts`
uses. The rule asks the resolver about each prefix of the path
(`account`, then `account.region`, then `account.region.zone`). The leaf
of each prefix is one field the path reads, so the rule walks no hop
itself. The cube leg's body is unchanged: its diff is one `export`
keyword and one docblock paragraph, so nothing it refuses changes.
- **One reading per slot.** The general text walk now skips those four
config paths. It read a dotted path as one token and credited neither
end. It also credited nothing for a bare cube column, because a cube
names its object in its own `sql` and the walk's object context never
reads that.
- **Paths the door does not read.** A path counts as read only when
every prefix resolves and, on a dataset, `include` declares the
relationship prefix. Otherwise:
- The door refuses a path whose hop or column does not resolve, and a
dataset path whose join `include` does not declare. (`compileDataset`
refuses the dataset, and `dataset-field-not-included` already reports
it.) Each field such a path names is recorded as a **carrier**, not a
reader. That is the rule's existing word for a site that names a field
and reads it nowhere: an `inlineColumns` entry with no `inlineEdit` is
the precedent. So the verdict is `carrier-only` and the finding lists
the member path. It is not a false "inert — no site of any kind names
it", and it is not a guessed read.
- The object graph cannot judge some paths: a lookup to an object this
stack does not define, a hop through an injected column, a relationship
with no target. Such a path credits the fields it does resolve. The door
joins through them, and "cannot answer" is no evidence that nothing
reads them.
- The row wildcard `'*'` reads no field and credits none.
- **Ruling B (`5950947151`) stands.** Only explicit, authored member
paths are credited. Nothing drawn by default is counted.
- The finding's message names cube members among the consumers, and
refused analytics paths among the carriers.
- One `@objectstack/lint` **patch** changeset. That is the family's
convention for the lint side of a false-"inert" fix: the four earlier
changesets of this family were lint patch three times and minor once,
the minor being the first, which added two consumer kinds. A false
advisory verdict in a released package is a bug fix. No export reaches
the published entry points.
## Evidence (final commit `6ae7c521fe`)
**`os validate`, before and after**, on a scratch fixture that is not
committed. Three objects: `fx_ledger.account` is a lookup to
`fx_account`, and `fx_account.region` is a lookup to `fx_region`. Fields
prefixed `c_` are read only by a cube member, `d_` only by a dataset
member (`include: ['d_account.d_region']`), and `u_` only by a dataset
measure whose join is not declared. Each slot has a bare, a one-hop and
a two-hop member.
| | before (CLI and lint built at BASE `53fd35e3e3`) | after (lint
rebuilt at `51819a6aaf`; later commits are comment-only) |
|:--|:--|:--|
| cube: `c_kind`, `c_amount` (bare) | inert | not reported |
| cube: `c_account`, `c_tier`, `c_revenue`, `c_region`, `c_code`,
`c_zone` | inert | not reported |
| dataset: `d_kind`, `d_amount` (bare) | not reported | not reported |
| dataset: `d_account`, `d_tier`, `d_revenue`, `d_region`, `d_code`,
`d_zone` | inert | not reported |
| undeclared join: `u_account`, `u_revenue` | inert | `carrier-only`,
carrier `datasets[1].measures[0].field` |
Both runs exit 1 on the fixture's one error,
`dataset-field-not-included` on the undeclared measure. The
`field-no-consumers` warnings went from 16 to 2.
**Tests.**
- `pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2`: 119
files and 5619 tests passed.
- `pnpm --filter @objectstack/lint typecheck`: exit 0,
`check:test-typecheck: OK`. The test file is in the `tsconfig.test.json`
program (`--listFiles` count 1), with 0 errors in it.
- The rule's test file has 153 tests, 27 of them new. Among them is the
**enumeration pin**: 4 slots × 3 shapes, each asserting that exactly the
fields the path reads leave the report. The slot list is read off the
spec's own `CubeSchema` and `DatasetSchema` through `z.toJSONSchema`:
every string whose pattern is the analytics column path. It is held
equal to the pin's builders, so a column slot the spec adds on either
root fails the pin.
**Ablations.** Each leg ran on the committed tree through
`scripts/ablation-replace.mjs` with a `trap` restore. Every anchor hit 1
time and then 0. Every restore was proven by blob equal to HEAD and an
empty `git diff HEAD`. Final `git status` was clean. The subject
resolves through relative `src` imports, so no `dist` was in the path.
| leg | mutation | result |
|:--|:--|:--|
| A1 | credit only the leaf, no hop | 16 red / 137 green: all 8 one- and
two-hop pin cells, plus the join, refused and not-judgeable cases |
| A2 | drop the cube leg | 11 red: all 6 cube pin cells (bare included),
plus the join and cube refused cases |
| A3 | cube hops through `resolveFieldPath` instead of
`resolveCubeColumn` | 1 red: the declared-join case |
| A4 | refused paths recorded as reads | 5 red: every refused-path case
|
| A5 | rename one pin builder key | 1 red: the spec census equality |
**Gates.**
- `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
--commands` (no paths) at `6ae7c521fe` derived 62 commands, the same
list the seat derived at dispatch. All 62 exited 0. `--ran`: "62
derived, 62 run, 0 NOT-MEASURED, 0 UNRUN".
- `check-widening-tells --declaration no --diff` exited 0, with "0
judged against a declared surface, 4 NOT MEASURED": no declared surface
covers `packages/lint` or `.changeset`. So `Clause-②: no` rests on this
reading, not on that tool: no spec key, no enum arm, no barrel export,
no registration.
**eslint, narrowed and proven.**
- Population: the 3 changed `.ts` files. `eslint.config.mjs` lints
`**/*.{ts,tsx,mts,cts,js,jsx,mjs,cjs}`, and the changeset `.md` is
ignored by it.
- `npx eslint --no-inline-config --format json` on those 3 files: 3
files, 0 errors, 0 warnings.
- The config sets no `parserOptions.project` (no type-aware linting), so
this diff cannot move the verdict on any untouched file. The repo-wide
`pnpm lint` is left to CI.
## Acceptance notes
- **Observation, not filed.** `include` entries and cube `joins` keys
are not in this card's slot list, and they are unchanged. A bare
`include: ['account']` is credited by the general walk as a read of the
base lookup. A dotted `include: ['account.region']` credits nothing on
its own. The pin uses the dotted form, so the member's own credit of
each lookup is what it measures. No carrier.
- **Observation, not filed.** `resolveCubeColumn` answers `ok` for a
member path through a join keyed by an alias that is no field on the
base object. `CubeJoinSchema.name` describes such a join as one that
"joins on a column the base object does not have, so nothing resolves".
This rule does not take that `ok` on trust: it also resolves the hop
prefix on the base object, finds no field, and records the leaf as a
carrier. The cube leg's own reading was not changed (out of scope), and
this was read from code only. No carrier.
- **Not merged with `origin/main`.** The 3 commits that landed since
BASE touch the `service-analytics` native SQL strategy, a TSDoc block in
`packages/spec/src/data/field-scale.ts` and the spec liveness README.
None of them touches `packages/lint`, the hop resolver or the analytics
schemas.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01UtnxvdiN376GF3sgXwAw4d)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 713b0fa commit 6210f88
4 files changed
Lines changed: 417 additions & 8 deletions
File tree
- .changeset
- packages/lint/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
465 | 465 | | |
466 | 466 | | |
467 | 467 | | |
| 468 | + | |
| 469 | + | |
| 470 | + | |
| 471 | + | |
| 472 | + | |
| 473 | + | |
468 | 474 | | |
469 | | - | |
| 475 | + | |
470 | 476 | | |
471 | 477 | | |
472 | 478 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
4 | 7 | | |
5 | 8 | | |
6 | 9 | | |
| |||
1263 | 1266 | | |
1264 | 1267 | | |
1265 | 1268 | | |
| 1269 | + | |
| 1270 | + | |
| 1271 | + | |
| 1272 | + | |
| 1273 | + | |
| 1274 | + | |
| 1275 | + | |
| 1276 | + | |
| 1277 | + | |
| 1278 | + | |
| 1279 | + | |
| 1280 | + | |
| 1281 | + | |
| 1282 | + | |
| 1283 | + | |
| 1284 | + | |
| 1285 | + | |
| 1286 | + | |
| 1287 | + | |
| 1288 | + | |
| 1289 | + | |
| 1290 | + | |
| 1291 | + | |
| 1292 | + | |
| 1293 | + | |
| 1294 | + | |
| 1295 | + | |
| 1296 | + | |
| 1297 | + | |
| 1298 | + | |
| 1299 | + | |
| 1300 | + | |
| 1301 | + | |
| 1302 | + | |
| 1303 | + | |
| 1304 | + | |
| 1305 | + | |
| 1306 | + | |
| 1307 | + | |
| 1308 | + | |
| 1309 | + | |
| 1310 | + | |
| 1311 | + | |
| 1312 | + | |
| 1313 | + | |
| 1314 | + | |
| 1315 | + | |
| 1316 | + | |
| 1317 | + | |
| 1318 | + | |
| 1319 | + | |
| 1320 | + | |
| 1321 | + | |
| 1322 | + | |
| 1323 | + | |
| 1324 | + | |
| 1325 | + | |
| 1326 | + | |
| 1327 | + | |
| 1328 | + | |
| 1329 | + | |
| 1330 | + | |
| 1331 | + | |
| 1332 | + | |
| 1333 | + | |
| 1334 | + | |
| 1335 | + | |
| 1336 | + | |
| 1337 | + | |
| 1338 | + | |
| 1339 | + | |
| 1340 | + | |
| 1341 | + | |
| 1342 | + | |
| 1343 | + | |
| 1344 | + | |
| 1345 | + | |
| 1346 | + | |
| 1347 | + | |
| 1348 | + | |
| 1349 | + | |
| 1350 | + | |
| 1351 | + | |
| 1352 | + | |
| 1353 | + | |
| 1354 | + | |
| 1355 | + | |
| 1356 | + | |
| 1357 | + | |
| 1358 | + | |
| 1359 | + | |
| 1360 | + | |
| 1361 | + | |
| 1362 | + | |
| 1363 | + | |
| 1364 | + | |
| 1365 | + | |
| 1366 | + | |
| 1367 | + | |
| 1368 | + | |
| 1369 | + | |
| 1370 | + | |
| 1371 | + | |
| 1372 | + | |
| 1373 | + | |
| 1374 | + | |
| 1375 | + | |
| 1376 | + | |
| 1377 | + | |
| 1378 | + | |
| 1379 | + | |
| 1380 | + | |
| 1381 | + | |
| 1382 | + | |
| 1383 | + | |
| 1384 | + | |
| 1385 | + | |
| 1386 | + | |
| 1387 | + | |
| 1388 | + | |
| 1389 | + | |
| 1390 | + | |
| 1391 | + | |
| 1392 | + | |
| 1393 | + | |
| 1394 | + | |
| 1395 | + | |
| 1396 | + | |
| 1397 | + | |
| 1398 | + | |
| 1399 | + | |
| 1400 | + | |
| 1401 | + | |
| 1402 | + | |
| 1403 | + | |
| 1404 | + | |
| 1405 | + | |
| 1406 | + | |
| 1407 | + | |
| 1408 | + | |
| 1409 | + | |
| 1410 | + | |
| 1411 | + | |
| 1412 | + | |
| 1413 | + | |
| 1414 | + | |
| 1415 | + | |
| 1416 | + | |
| 1417 | + | |
| 1418 | + | |
| 1419 | + | |
| 1420 | + | |
| 1421 | + | |
| 1422 | + | |
| 1423 | + | |
| 1424 | + | |
| 1425 | + | |
| 1426 | + | |
| 1427 | + | |
| 1428 | + | |
| 1429 | + | |
| 1430 | + | |
| 1431 | + | |
| 1432 | + | |
| 1433 | + | |
| 1434 | + | |
| 1435 | + | |
| 1436 | + | |
| 1437 | + | |
| 1438 | + | |
| 1439 | + | |
| 1440 | + | |
| 1441 | + | |
| 1442 | + | |
| 1443 | + | |
| 1444 | + | |
| 1445 | + | |
| 1446 | + | |
| 1447 | + | |
| 1448 | + | |
| 1449 | + | |
| 1450 | + | |
| 1451 | + | |
| 1452 | + | |
| 1453 | + | |
| 1454 | + | |
| 1455 | + | |
| 1456 | + | |
| 1457 | + | |
| 1458 | + | |
| 1459 | + | |
| 1460 | + | |
| 1461 | + | |
| 1462 | + | |
| 1463 | + | |
| 1464 | + | |
| 1465 | + | |
| 1466 | + | |
| 1467 | + | |
| 1468 | + | |
| 1469 | + | |
| 1470 | + | |
| 1471 | + | |
| 1472 | + | |
| 1473 | + | |
| 1474 | + | |
| 1475 | + | |
| 1476 | + | |
| 1477 | + | |
| 1478 | + | |
| 1479 | + | |
| 1480 | + | |
| 1481 | + | |
| 1482 | + | |
| 1483 | + | |
| 1484 | + | |
| 1485 | + | |
| 1486 | + | |
| 1487 | + | |
| 1488 | + | |
0 commit comments