Repository navigation
Commit 7e7e64b
fix(lint): flow, hook, action and expression rule findings state each decision in words instead of a tracker number (stage 1) (#21462)
Part of #20749
Clause-②: no
**Stage 1 of the `domain:spec` lane's share under the maintainer's A / A
ruling (5902360492): the 53 ledgered tracker-number occurrences in nine
`packages/lint/src` rule files (the flow, hook, action, approval and
expression rules).** The card stays open for the later stages, so this
PR carries no closing keyword. Text only: no rule, condition, code path,
rule id or severity moves (AST skeleton proof below, 9 of 9 SAME).
## What this does
These nine files print findings to authors through `os validate`, `os
lint` and `os build` (and, for the startup-registry rule, to plugin
authors through the exported decision procedure). 53 tracker numbers in
those findings sent the reader to a card for the reason behind them. In
form D, as the sibling lane's stages applied it, the number goes. Where
the sentence already said what was decided, only the citation goes.
Where it leaned on the number, it now says the decision in words.
The surface, re-derived from the ledger and from
`check-doc-authoring.mjs --census` on `origin/main` at `b94a2a7277` (the
claim's numbers hold exactly): `lint-flow-patterns.ts` 19,
`lint-startup-registry-verdict.ts` 11, `validate-flow-template-paths.ts`
5, `validate-expressions.ts` 5, `validate-flow-trigger-readiness.ts` 4,
`validate-hook-body-writes.ts` 3, `validate-action-body-writes.ts` 2,
`validate-readonly-flow-writes.ts` 2, `validate-approval-approvers.ts`
2. They sit in 49 census sites (43 string groups once a `+` chain is
read as one string). By class: 41 occurrences in finding envelopes (a
`message` or `hint`), 12 in prose that a finding carries (the
startup-registry vocabulary notes and shared hint, the field-rule
consequence and prescription constants in `validate-expressions.ts`, and
the unprovisioned-anchor consequence helper). None is thrown or logged.
Every cited card (28, plus `objectui#6010`) was read through REST, body
and every comment, before its string was rewritten. Where a card closed
with no comment, its landing commit was read.
### Rewritten in words
Lines are the census lines at `b94a2a7277`.
| Where | Cited | The text now says | Decision read from |
|---|---|---|---|
| `lint-flow-patterns.ts:1555`, record-change date-equality hint | 1874
| "Or declare the sweep instead: a `schedule` flow whose start node
carries a `config.timeRelative` descriptor (the object, the date field,
and `withinDays` or `offsetDays`) is swept daily and runs once per
record whose date falls in the window." | no comments; landing commit
`a2795f6412` ("declarative time-relative trigger", the commit that
closed the card): a start node declaring `config.timeRelative` is swept
daily and launched once per matching record |
| `lint-flow-patterns.ts:642`, date-equality filter hint | 1874 | "A
T-minus rule can declare the sweep instead: a `schedule` flow whose
start node carries a `config.timeRelative` descriptor with `offsetDays`
runs once per record on each offset day." | same; `offsetDays` builds
one single-day window per offset
(`trigger-schedule/src/time-relative-trigger.ts`) |
| `lint-flow-patterns.ts:1199`, `:1202`, unbounded bulk-write hint |
3810, 5482, 5393 | "`multi: true` is how a flow declares bulk intent,
and the engine admits a whole-object write declared that way, so this is
a warning, not a gate ... Distinct from the run-time erased-condition
guard, which REFUSES this node ..." | 5393: PM ruling A in the card body
and ACCEPT 5192653833 (a bulk-intent key on both nodes, forwarded as
`options.multi`); 5482: triage 5192651560 (a warning, not a spec refine,
because explicit whole-object cleanup is an intent the engine admits)
and ACCEPT 5198480630; 3810: 5102018528 (a CRUD node refuses when
interpolation erased a condition the author wrote, judged on loss rather
than emptiness) |
| `lint-flow-patterns.ts:1266`, revise-target hint | 3823, 3801 |
"because a 'wait' is resumable by anyone with the run id, while the
run-resume route continues a pause on a service-owned node type only
through the service that owns it." | 3801: card body (the resume gate
keys on the suspended node's type); 3823: maintainer ruling 5194604990
(the revise pause becomes a typed, service-owned node so that gate
covers it) and ACCEPT 5195638435 |
| `lint-flow-patterns.ts:1609`, unscoped `runAs` hint | 1888, 3760 |
"there is none, and `runAs` is enforced: `'user'` scopes each data
operation to the triggering user's grants, and with no trigger user the
runtime refuses the operation rather than run it unscoped. (ADR-0049,
ADR-0073 D5)" | 1888: 4791554026 (ENFORCE: `system` elevates, `user`
runs with the triggering user's grants); 3760: 5099989877 (option 2,
runtime fail-closed) |
| `lint-flow-patterns.ts:1706`, `:1714`, interpolation hints | 1315 | "a
flow node value is a string template in which only single-brace `{…}`
tokens resolve and all other text is literal" | 4709041508 (one value
contract: a string template whose brace tokens resolve and whose other
text is literal; the typed literal/expr/ref redesign declined) |
| `lint-startup-registry-verdict.ts:201`, `:205`, `:209`, `:213`,
open-vocabulary notes | 4771 | "... so the engine itself judges node
types only once the vocabulary is sealed at `kernel:bootstrapped`" (the
audit note: "reads it as a verdict only once ...") | ruling 5162805181
and ACCEPT 5163195020 (the check moves to the moment the vocabulary is
closed, `sealNodeTypeVocabulary()` at `kernel:bootstrapped`) |
| `lint-startup-registry-verdict.ts:359`, `:361`, the shared hint's
cures 1 and 3 | 4772, 4769 | "(`createLazyCacheRateLimitStorage()` in
plugin-auth, which takes the cache service only when a rate-limit
counter is used)"; "(the ADR-0104 born-migrated attestation, written
only after the first boot has seeded its data)" | 4772: ruling
5162806718 and ACCEPT 5163087460 (resolve the cache lazily where the
counter is used); 4769: ruling 5162804083 and ACCEPT 5163311202 (the
attestation moves after the first boot's seed) |
| `lint-startup-registry-verdict.ts:762`, `:764`, assertive-wording
finding | 4771, 4772 | "The flow engine's node-type check once printed
... the engine now judges node types only once the vocabulary is sealed
at `kernel:bootstrapped`. The auth plugin's missing-cache warning
prescribed "you need Redis" ... the plugin now resolves the cache where
a rate-limit counter uses it." | as the two rows above |
| `validate-expressions.ts:863`, field-level `visibleWhen` consequence;
`:931`, the user-root prescription | `objectui#6010` | "evaluates it
with the host scope bound, the `current_user` binding ADR-0089 D1 gives
every runtime record surface"; "HAS bound these roots since the form
renderer took up the `current_user` binding ADR-0089 D1 gives every
runtime record surface" | `objectui#6010` claim 5395919798 and ACCEPT
5396359545 (ADR-0089 D1: runtime record surfaces bind `record` +
`current_user`; form section and field predicates now receive the host
scope) |
| `validate-expressions.ts:1735`, retired `script` keys | 4343 |
"retired in @objectstack/spec 17, which made `script` a call to a
registered function and nothing else" | 5151704360 (the operator's
direction change: `script` converges to a pure function call, the five
branch keys retired) |
| `validate-flow-trigger-readiness.ts:557`, array `triggerType` hint |
3457 | "multi-event arrays are deferred until two independent projects
need a combination other than created-or-updated" | closing comment
5076318442 (not planned; restart when two independent real projects need
a non-`write` combination). Same words as the `trigger-record-change`
warning stage 2 of the services lane wrote |
| `validate-action-body-writes.ts:371`, discarded `ctx.record` write |
4345 | "The snapshot stays read-only by design: an action's write
channel is ctx.api." | no comments; landing commit `a4e268445e` (the
snapshot stays read-only, writes go through `ctx.api`, and only the
silence was the defect) |
| `validate-readonly-flow-writes.ts:336`, `readonlyWhen` write | 3042 |
"(a bulk update strips it from every matched row once any one of them is
locked)" | 4990593694 (the bulk path drops a field locked in at least
one matched row; an unlocked field lands) |
| `validate-approval-approvers.ts:463`, unsupported approver type | 3508
| "it was deprecated rather than built, and is no longer offered for
authoring" | 5087415288 (`queue`: deep deprecation, not an
implementation) |
| `validate-approval-approvers.ts:527`, empty-slate hint | 3424 |
"recoverable only by a platform/tenant admin override, which may act on
any pending request so that one nobody in its slate can decide never
stays stuck" | 5071456833 (the admin-override recovery, not a refusal at
creation). Same decision words as the services lane's `via_override`
help |
### Citation only (the sentence already stated the decision)
- `lint-flow-patterns.ts`: `:727` (3863, "Only runtime failures route —
a guard refusal ... stays fatal by design"; ruling 5105017918); `:830`,
`:849`, `:884`, `:886`, `:975`, `:1032` (4414, each hint already states
its part of the routing model the fix landed in `529311469d` and
`d449b0cf4d`: a branch label narrows, a `condition` gates, `isDefault`
is the BPMN default flow, `config.condition` gates only a `start` node);
`:1674` (1870, "the automation engine has no aggregate node ...
Aggregation belongs in the data layer").
- `lint-startup-registry-verdict.ts`: `:360` (4771, the cure already
names `sealNodeTypeVocabulary()` called at `kernel:bootstrapped`);
`:742` (4771, 4772, the sentence states the two meanings of "absent" the
record cannot tell apart).
- `validate-flow-template-paths.ts`: `:656`, `:674`, `:712` (3810, "the
node refuses to run at execution time"); `:719`, `:723` (3475, "add it
to the start node's config.expand and the engine re-reads it as the
run's identity"; plan 5076227883, landed in `5524f84764`).
- `validate-expressions.ts`: `:870` (6146, the predicate faults and the
renderer falls back to VISIBLE; landed in `8a88885cb2`); `:875` (4889,
"`isReadonlyWhenLocked` will not waive a declared lock it could not
evaluate"; ACCEPT 5169906972).
- `validate-flow-trigger-readiness.ts`: `:530`, `:556`, `:743` (3427,
"'write' fires on create OR update in one flow"; landed in
`6f55c6300b`).
- `validate-hook-body-writes.ts`: `:408`, `:937`, `:983` and
`validate-action-body-writes.ts:431` (4271, the authoring warning these
messages ARE, and "INVALID_FIELD / 400, identically on every driver").
- `validate-readonly-flow-writes.ts:311` (2948, the static strip under a
non-system run, with `runAs:'system'` named as the intended channel in
the hint; 4980638493).
No occurrence was left in place: no cited decision was unclear, and each
fit the string's space.
## Ledger (`scripts/doc-authoring-prose-id.baseline.json`)
Recomputed with `node scripts/check-doc-authoring.mjs --census-ledger`
(exit 0, no growth refusal) into a scratch file, then copied into place.
The diff deletes 48 lines and adds none: exactly the nine file blocks. A
scripted comparison of the 53 other keys: 0 moved.
| | before (`b94a2a7277`) | after |
|---|---|---|
| the nine files | 53 occurrences, 30 pairs, 9 files | 0 |
| `packages/lint` | 104 occurrences, 27 files | 51 occurrences, 18 files
|
| whole ledger | 227 occurrences, 157 pairs, 62 files | 174 occurrences,
127 pairs, 53 files |
`pnpm check:doc-authoring` at the head: "sibling-package prose ids hold
the baseline — 155 pinned site(s) across 53 file(s), 86566 string(s)
read in 1257 parsed source(s), no growth, no burn-down unrecorded" (204
sites before). No gate is added or loosened;
`scripts/check-doc-authoring.mjs` is untouched.
Ledger serial: no open PR touches the ledger or any of the nine files
(all 10 open PRs' file lists read before opening). `origin/main` has
moved 6 commits past the branch point; none touches the ledger or the
nine files, and the two `packages/lint` files it does touch census to
the same counts on `origin/main` as this ledger pins
(`authoring-rules.ts` six pairs equal;
`validate-dataset-measure-aggregates.ts` none), so the recomputed ledger
stands on the merged tree.
## Changeset
`.changeset/20749-lint-strings-stage1-state-the-decision.md`: `patch`
for `@objectstack/lint`, carrying `Clause-②: no`. Measured after
building: every new sentence above is in `packages/lint/dist/index.js`
and `index.cjs`, and every replaced id-bearing fragment is in neither
(the one `(#3457)` left in `dist` is a source comment tsup keeps).
## Text-only proof
A TypeScript-AST skeleton of each changed source: every string literal
and template text is a placeholder, a `+` chain is flattened and a run
of adjacent string operands is one string (only its embedded expressions
are kept, in order), a chain made only of strings is a string,
identifiers, numbers and regex literals keep their text, every child is
visited, and comments are never read. A second leg compares the TEXT of
every string group in order: each group that changed must have carried a
tracker id before and carry none after, and every other group must be
byte-identical. `b94a2a7277` against the head: 9 of 9 SAME on both legs,
token counts identical per file, 43 groups changed, all of them
id-bearing before and id-free after.
Controls on a scratch copy of `lint-flow-patterns.ts`, each mutation
counted on disk first: one identifier renamed reads DIFF; `===` flipped
to `!==` reads DIFF; one template re-split into two `+` operands reads
SAME with 0 groups changed; a text change in a string that never carried
an id reads SAME on the skeleton and VIOLATION on the text leg. (The
first draft of the tool read `lint-flow-patterns.ts` DIFF because the
two interpolation hints went from one literal to a three-piece chain;
the corrected tool reads an all-string chain as one string, which the
re-split control pins.)
## Pins
Every assertion that found a finding by its tracker number now finds it
by the words that replaced it, at the same strength (`toContain` stays
`toContain`, `toMatch` stays `toMatch`, the one `not.toContain` stays
negative). No rule-id or severity assertion was touched.
- `lint-flow-patterns.test.ts:1754`: `'the run-time erased-condition
guard'` for `'#3810'`.
- `lint-startup-registry-verdict.test.ts:69`: the wording finding by
"the engine now judges node types only once the vocabulary is sealed at
`kernel:bootstrapped`"; `:78`-`:80`: the shared hint by its three cures'
words for the three ids.
- `validate-flow-template-paths.test.ts:853`: "config.expand and the
engine re-reads it as the run's identity".
- `validate-expressions.test.ts:161`, `:1852`, `:1871`: the
retired-`script` sentence, the ADR-0089 D1 binding sentence, and "will
not waive a declared lock it could not evaluate".
- `validate-flow-trigger-readiness.test.ts:738`: the deferral sentence.
- `validate-hook-body-writes.test.ts:110`,
`validate-action-body-writes.test.ts:166`: "INVALID_FIELD / 400,
identically on every driver"; `validate-action-body-writes.test.ts:316`:
the read-only snapshot sentence.
- `validate-readonly-flow-writes.test.ts:59`, `:146`, `:236`: the update
strip and bulk sentences; `:367` (negative): the create-side message
must not contain "from the UPDATE payload, so this write never lands",
the phrase `:59` proves the update message carries.
- `validate-approval-approvers.test.ts:83`: "it was deprecated rather
than built".
## Tests
All through `scripts/pm/os-verify-lock.sh`, every verdict `VERDICT
command-exit 0`:
- Build: `pnpm --filter '@objectstack/lint^...' build` (the closure),
then `pnpm turbo run build --concurrency=2 --filter=./packages/*
--filter=./packages/*/*` (71/71) for the dist-reading gates.
- `@objectstack/lint`: `vitest run --maxWorkers=2`: 119 files, 5587
passed, 5 skipped. `typecheck`: exit 0, including `check:test-typecheck:
OK`.
## Gates
- `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
--commands` (no paths): 70 commands over 20 paths, run one at a time
from the worktree, each exit code recorded before any pipe. `--ran`: "70
derived famil(ies) accounted for — 70 run, 0 NOT-MEASURED (a DERIVED
zero — all 70 recorded an exit code and none of them is 3)". Three
dist-reading gates (`check:docs-transcript-drift`,
`check:dual-build-cjs-loads`, `check:lean-entry-closure`) first answered
`PREREQUISITE NOT MET` with only the closure built; after the full build
they and the other dist-reading gates were re-run, all exit 0.
- Named by the dispatch: `check:doc-authoring` (self-test and run) exit
0; `check:nul-bytes` exit 0; `check-adr-0087-registration` exit 0;
`check:empty-changeset` exit 0; `check-changeset-fixed` exit 0;
`check-changeset-no-major` exit 0 on the plain run, and exit 0 when fed
this body as a `pull_request` event (the `Clause-②: no` line read,
`patch` judged against it).
- Outside the derived set, all exit 0: the eleven declared
wide-population families (`check:startup-registry-verdict` among them),
the artifact-roster families whose roster sits under one of this PR's
directories (`check-published-list-mirrors` and its self-test,
`check:authz-resolver`, `check:console-injection`,
`check:error-code-casing`, `check:filter-alias-parity`,
`check:i18n-stale-fill`, `check:published-readme-exports`,
`check-dts-references --self-test`), and `@objectstack/lint`'s
`check:doc-formula-expressions`, which imports `fieldRuleRootIssue` and
prints its message.
- Repo-wide `pnpm lint` is CI's.
## Acceptance notes
Noted, not filed:
- Test titles and test-only strings in these nine files' tests still
cite numbers, outside stage 1: 132 ids in test titles and 9 in other
test strings, across `lint-flow-patterns.test.ts` (32 / 0),
`lint-startup-registry-verdict.test.ts` (12 / 0) and its
`.corpus.test.ts` (3 / 4), `validate-expressions.test.ts` (54 / 4),
`validate-flow-template-paths.test.ts` (10 / 0),
`validate-flow-trigger-readiness.test.ts` (10 / 1),
`validate-hook-body-writes.test.ts` (4 / 0),
`validate-action-body-writes.test.ts` (4 / 0),
`validate-approval-approvers.test.ts` (2 / 0) and
`validate-readonly-flow-writes.test.ts` (1 / 0). The ledger does not
read test files.
- `packages/lint/scripts/check-doc-formula-expressions.mjs` prints an
epilogue that still says `since objectui#6010`, and its self-test pins
that spelling; `scripts/check-startup-registry-verdict.mjs` keeps the
`(#4771)` citation in two of its own vocabulary notes. Both are `.mjs`
gate scripts the ledger does not read, outside this stage. Carrier:
none.
- `validate-approval-approvers.ts`'s `queue` hint still says "Queue
approvers need a real ownership-queue implementation before they take
effect", which reads as pending work where the decision was deprecation.
It carries no number and is outside form D. Carrier: none.
- Comments in the nine files keep their ids: a comment is the sanctioned
home for an internal anchor.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01YDt3PzwfrkuFzUBF89WPmM)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 4c8363f commit 7e7e64b
20 files changed
Lines changed: 126 additions & 124 deletions
File tree
- .changeset
- packages/lint/src
- scripts
Lines changed: 18 additions & 0 deletions
| 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 | + | |
| 17 | + | |
| 18 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1751 | 1751 | | |
1752 | 1752 | | |
1753 | 1753 | | |
1754 | | - | |
| 1754 | + | |
1755 | 1755 | | |
1756 | 1756 | | |
1757 | 1757 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
639 | 639 | | |
640 | 640 | | |
641 | 641 | | |
642 | | - | |
| 642 | + | |
| 643 | + | |
| 644 | + | |
643 | 645 | | |
644 | 646 | | |
645 | 647 | | |
| |||
724 | 726 | | |
725 | 727 | | |
726 | 728 | | |
727 | | - | |
| 729 | + | |
728 | 730 | | |
729 | 731 | | |
730 | 732 | | |
| |||
827 | 829 | | |
828 | 830 | | |
829 | 831 | | |
830 | | - | |
| 832 | + | |
831 | 833 | | |
832 | 834 | | |
833 | 835 | | |
| |||
846 | 848 | | |
847 | 849 | | |
848 | 850 | | |
849 | | - | |
| 851 | + | |
850 | 852 | | |
851 | 853 | | |
852 | 854 | | |
| |||
881 | 883 | | |
882 | 884 | | |
883 | 885 | | |
884 | | - | |
| 886 | + | |
885 | 887 | | |
886 | | - | |
| 888 | + | |
887 | 889 | | |
888 | 890 | | |
889 | 891 | | |
| |||
972 | 974 | | |
973 | 975 | | |
974 | 976 | | |
975 | | - | |
| 977 | + | |
976 | 978 | | |
977 | 979 | | |
978 | 980 | | |
| |||
1029 | 1031 | | |
1030 | 1032 | | |
1031 | 1033 | | |
1032 | | - | |
| 1034 | + | |
1033 | 1035 | | |
1034 | 1036 | | |
1035 | 1037 | | |
| |||
1195 | 1197 | | |
1196 | 1198 | | |
1197 | 1199 | | |
1198 | | - | |
1199 | | - | |
| 1200 | + | |
| 1201 | + | |
| 1202 | + | |
| 1203 | + | |
1200 | 1204 | | |
1201 | 1205 | | |
1202 | | - | |
| 1206 | + | |
1203 | 1207 | | |
1204 | 1208 | | |
1205 | 1209 | | |
| |||
1263 | 1267 | | |
1264 | 1268 | | |
1265 | 1269 | | |
1266 | | - | |
| 1270 | + | |
| 1271 | + | |
1267 | 1272 | | |
1268 | 1273 | | |
1269 | 1274 | | |
| |||
1552 | 1557 | | |
1553 | 1558 | | |
1554 | 1559 | | |
1555 | | - | |
| 1560 | + | |
| 1561 | + | |
| 1562 | + | |
| 1563 | + | |
1556 | 1564 | | |
1557 | 1565 | | |
1558 | 1566 | | |
| |||
1605 | 1613 | | |
1606 | 1614 | | |
1607 | 1615 | | |
1608 | | - | |
1609 | | - | |
| 1616 | + | |
| 1617 | + | |
| 1618 | + | |
| 1619 | + | |
1610 | 1620 | | |
1611 | 1621 | | |
1612 | 1622 | | |
| |||
1671 | 1681 | | |
1672 | 1682 | | |
1673 | 1683 | | |
1674 | | - | |
| 1684 | + | |
1675 | 1685 | | |
1676 | 1686 | | |
1677 | 1687 | | |
| |||
1703 | 1713 | | |
1704 | 1714 | | |
1705 | 1715 | | |
1706 | | - | |
| 1716 | + | |
| 1717 | + | |
| 1718 | + | |
| 1719 | + | |
1707 | 1720 | | |
1708 | 1721 | | |
1709 | 1722 | | |
1710 | 1723 | | |
1711 | 1724 | | |
1712 | 1725 | | |
1713 | 1726 | | |
1714 | | - | |
| 1727 | + | |
| 1728 | + | |
| 1729 | + | |
| 1730 | + | |
1715 | 1731 | | |
1716 | 1732 | | |
1717 | 1733 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
66 | 66 | | |
67 | 67 | | |
68 | 68 | | |
69 | | - | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
70 | 72 | | |
71 | 73 | | |
72 | 74 | | |
| |||
75 | 77 | | |
76 | 78 | | |
77 | 79 | | |
78 | | - | |
79 | | - | |
80 | | - | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
81 | 85 | | |
82 | 86 | | |
83 | 87 | | |
| |||
0 commit comments