Repository navigation
fix(lint): a declared read attachment resolves only where record is a served row, so os validate refuses a flow condition that reads one - #22509
Conversation
… sites, so a flow condition reading it is refused The field-existence set every expression site reads is the object's columns again. A declared attachedOnRead block joins it, and its leaves are judged, only at the sites listed in SERVED_ROW_SITES: an action's visible and disabled predicates, whose record is the row the surface fetched. Flow conditions, validation rules, field-rule slots, option visibleWhen, field formulas, sharing rules and hooks bind the stored row, which never carries a block, and refuse record.BLOCK as an unknown field. Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude <noreply@anthropic.com>
…ate the served-row reading; changeset The liveness row's evidence, producer and note named a field index that added every declared block at every site, and a conformance test that had not landed. They now name SERVED_ROW_SITES and the landed plugin-approvals conformance test. The pass-4 measurement note in authoring-rules.ts no longer says the object does not declare its viewer block. Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 2 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 139 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 897b6914e4d7cfda013a1be9c63f419938daf779 && git checkout 897b6914e4d7cfda013a1be9c63f419938daf779
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 446c8b2a6420a61a2862e6f5140dda71a53316d1 64277b3f28734efc9504f428584e496afdaff6a1 && git checkout -B drift-repro 446c8b2a6420a61a2862e6f5140dda71a53316d1 && git merge --no-ff 64277b3f28734efc9504f428584e496afdaff6a1
node scripts/docs-audit/affected-docs.mjs --json 446c8b2a6420a61a2862e6f5140dda71a53316d1
|
…tached-block-served-rows
…ved-row reading ObjectSchema.attachedOnRead's describe and the AttachedOnReadSchema docblock said record.BLOCK resolves wherever the validator reads the object. The validator now reads a declared block only in an action's visible and disabled predicates; every other site binds the stored row and refuses record.BLOCK as an unknown field. Both texts now say so. The changeset gains the @objectstack/spec patch entry. Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude <noreply@anthropic.com>
…cription Output of `pnpm --filter @objectstack/spec check:generated --fix`, which proved only content/docs/references/** stale (check:docs) after the describe change; the three pages that render ObjectSchema.attachedOnRead. Claude-Session: https://claude.ai/code/session_01KNKBCRDJCu5tGy3TEbvtrF Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Reviewed from the card (#22481: body and all six comments), PR #22509 (body, file list, and the net diff of ① Derived judgmentsEach accept-set and public-surface change the diff implies, judged against the ruling's definition (a block is attached to the rows a service SERVES, computed per caller, never stored) and read on the branch tree, not from the reports:
Read against the grain: a row fetched anywhere but the declaring service's routes carries no block at an action predicate either; the ② Semver level
③ Boundary flagsEvery dev flag and every
Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #22481
Clause-②: no
What this changes
ObjectSchema.attachedOnReaddeclares the blocks a service attaches to the rows it serves, computed per caller and never stored. Until now@objectstack/lintput every declared block into the field-existence set of every expression site bound to the object. Soos validateaccepted a flow condition such asrecord.viewer.can_act == trueonsys_approval_request, and the flow then faulted withNo such key: vieweron every run, because a flow'srecordis the stored row.This PR follows the maintainer's ruling (record 6070963704) and the triage reading of it (record 6081468590): a declared block resolves only where
recordis a served row.buildFieldIndexholds the object's columns again: authored fields plus the injected system columns. That is what every stored-row site reads.SERVED_ROW_SITESinpackages/lint/src/validate-expressions.tsis the one enumerated list of served-row sites: an action'svisibleanddisabled. Thecheckclosure adds the object's blocks to the field-existence set, and passes theattachedOnReadleaf hint, only for a call that names an entry of that list. Only the action pass names one. A new call site is fields-only unless it names an entry, so the field-rule slot in flight on another card lands fields-only without any change here.@objectstack/formulais untouched. The pins use the dot spelling only, so the member-spelling change of PR fix(formula)!: the unknown-field check judges every member spelling of record / previous, not only the dot (#22428) #22494, now onmainand merged into this branch at90e34944, does not move them. The lint suite was re-run on the merged head.@objectstack/spec: the.describe()ofObjectSchema.attachedOnReadand theAttachedOnReadSchemadocblock (packages/spec/src/data/object.zod.ts) now state the served-row reading. A declared block resolves, and its leaves are judged, only in an action'svisibleanddisabled; every stored-row site refusesrecord.BLOCKas an unknown field.check:generated --fixproved onlycheck:docsstale and regenerated the three reference pages that render the describe. The changeset gains an@objectstack/specpatchentry.Every site that received the block, classified
recordbindsvisible/disabled(object and stack-level)sys_approval_requestthrough the approvals routes, whose rows carryviewersys-approval-request.object.ts(theattachedOnReadcomment); objectuiapps/console/src/services/approvalRequestsDataSource.tsheaderconfig.condition/ edgeconditionrecord-change-trigger.tsseeds{ ...priorBase, ...inputData, ...after }; the card's run measuredNo such key: viewerwarnShadowedFieldReadsreadsfieldIndexcondition/when/ nestedrule-validator.tsevaluateValidationRulesrequiredWhen/readonlyWhenrule-validator.ts(readonlyWhenBindings)visibleWhenrule-validator.tsevaluateOptionVisibilityexpressionjudgeFieldFormulaconditionconditionhook-wrappers.tsvisibleWhenrule-validator.tsheader: render side only, fail-openMeasured
All readings in this section are at
26088deb, the first round. The patch round's readings at64277b3fare in their own section below.Through the built CLI (
node packages/cli/bin/run.js validate, afterpnpm turbo run build --filter=@objectstack/cli...). The fixture configs import the shippedSysApprovalRequest.sys_approval_requestplus arecord_changeflow whose start condition isrecord.viewer.can_act == true→ exit 1:flow 'viewer_gate' · node 'start' (start) condition: unknown field `viewer` on `sys_approval_request` — source: `record.viewer.can_act == true`.✓ Validation passed, no finding namingviewer.record.viewer.can_actt→ exit 1:unknown field `viewer.can_actt` on `sys_approval_request` (the read attachment `viewer` declares `can_act`, `can_override`, `is_submitter`) — did you mean `viewer.can_act`?Ablation (each leg run on the committed tree through
scripts/ablation-replace.mjs; the anchor hit 1 → 0 on disk and the blob changed; each restore was proven blob == HEAD withgit diff HEADempty).checkre-applies the block at every site (theservedRowSitegate dropped) →validate-expressions.attached-on-read.test.ts10 failed / 6 passed of 16. All 8 stored-row sitescheckserves went red, plus the did-you-mean pin and the one-object-two-sites pin. The served-row tests and the formula pin stayed green, as predicted: the formula judge is a separate call. Restored, 16/16.pnpm --filter @objectstack/lint build: the JS bundles were emitted, and the DTS step failed on the now-unusedservedRowSiteparameter (TS6133, an artifact of the mutation).ablation-dist-preflight.mjs @objectstack/lint 'objectName ? attachedOnReadIndex.get(objectName)'→ marker present in 4 built files (index.js,index.cjs,runtime.js,runtime.cjs). The pin config then gave exit 0,✓ Validation passed: the card's original reading. Restore leg: lint rebuilt with exit 0; preflight--absent→ the marker is absent from all 20 built files and the tree is clean; the pin config gives exit 1 again with the refusal above.The unit suites import the subject by relative path (
./validate-expressions.js), so no dist sits between the source and those runs. The dist preflight applies to the CLI leg, and that leg ran it.Tests and type check
pnpm --filter @objectstack/lint typecheck:tsc --noEmitclean, andcheck:test-typecheckOK.pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2: 130 files, 5954 tests passed.pnpm --filter @objectstack/plugin-approvals exec vitest runonsys-approval-request-attached-on-read.test.ts,sys-approval-request-viewer.conformance.test.tsandaction-predicate-sparse-face.test.ts: 3 files, 22 tests passed. The shipped action predicates pass the build pair and the object save door, a misspelt leaf is refused at all three, and the conformance test is green.pnpm --filter @objectstack/spec exec vitest run --project repo: 54 files, 915 tests passed.pnpm --filter @objectstack/spec run check:liveness: exit 0,894 pointer(s) written path#symbol, 894 naming a symbol the cited file contains.Gates.
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackat26088debderived 69 commands. I ran each one and captured its exit code before any pipe: 68 exit 0, and 1 exit 3. That one ispnpm check:dual-build-cjs-loads, PREREQUISITE NOT MET: it reads every package'sdist/, and nine packages unrelated to this diff are not built in this worktree. NOT MEASURED locally; CI measures it. Reconciliation:✓ dispatch-gates --ran: 69 derived famil(ies) accounted for — 68 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3).Lint, narrowed.
eslint --no-inline-config --format jsonon the three changed.tsfiles: 3 files, 0 errors, 0 warnings.--print-configgives a config for each of the three.tsfiles and none for the.jsonand.mdfiles, so the three are the whole linted population of this diff.parserOptionsare{ ecmaVersion: 'latest', sourceType: 'module' }, with noprojectorprojectService. Type-aware linting is off, so this diff cannot change the verdict on any untouched file.pnpm lintis CI's.Prose that PR #22479 made stale (carried here by the card)
packages/spec/liveness/object.json, theattachedOnReadrow.producerandnotenow describe the served-row reading and name the landed conformance testsys-approval-request-viewer.conformance.test.ts. Theevidencesentence aboutbuildFieldIndexhad become false with this change, so it now citesSERVED_ROW_SITES. That sentence is in the same row as the two named fields.packages/lint/src/authoring-rules.ts, the pass-4 note. It no longer says the object does not declare its block. It now says the object declares the block, an action predicate is a served-row site, and the build and the door accept all 8 predicates.Patch round: the spec text, at
64277b3fbash scripts/pm/os-regen-merge.shmergedorigin/mainat446c8b2aas90e34944. Before the merge the branch held no generated artifact, so no os-regen path had two sides. Then came the spec edit (588abcb4) and the regeneration as its own commit (64277b3f).pnpm --filter @objectstack/spec build, thencheck:generated: 1 of 15 artifacts stale (check:docs,content/docs/references/**).check:generated --fixregenerated only that one. The three pages differ frommainonly on theattachedOnReadrow.@objectstack/spectests forobject.zod.ts(object,object-attached-on-read,object-image-field,object-strictness-batch20,metadata-form-zod-reconciliationand the two compose-stacks pins): 7 files, 433 passed.--project repo: 54 files, 915 passed.@objectstack/linttypecheck: clean, andcheck:test-typecheckOK.@objectstack/lintvitest: 131 files, 5985 passed, 5 skipped. The 5 areskipIfon an unbuilt lintdist/(lazy-deps,runtime-lazy-deps). Afterpnpm --filter @objectstack/lint buildthose 2 files gave 10 passed.check:livenessexit 0, with 894 of 894 symbol anchors resolved.check:generated: all 15 up to date.check:docs,check:api-surface,check:authorable-surfaceandcheck-spec-docblock-symbol-anchors: exit 0 each.dispatch-gates --commandsat64277b3fderived 110 commands: 106 exit 0, and 4 exit 3, which are NOT MEASURED.check:dual-build-cjs-loads,check:lean-entry-closureand speccheck:skill-examplesneeddist/of packages this diff does not touch, and those are not built.check:type-check-debt: the spec build it starts itself was killed (exit 137) on the shared box.--ran:✓ dispatch-gates --ran: 110 derived famil(ies) accounted for — 106 run, 4 NOT-MEASURED (4 DERIVED from a recorded exit 3).PR_NUMBER=22509and this body all 3 exit 0:check-closing-target-claim,check-partof-closing-keyword,check-single-claim-paths.check:published-readme-exportsexits 3, NOT MEASURED: it needs every package's built.d.ts.check:error-code-provenanceexits 0 (335 stamp sites: 316 listed, 19 waived, every waiver live).Acceptance notes
ExprSchemaHint.attachedOnReaddoc (packages/formula/src/validate.ts) says a caller lists each block name infields, "(@objectstack/lint's field index does)". That now holds at served-row sites only. PR fix(formula)!: the unknown-field check judges every member spelling of record / previous, not only the dot (#22428) #22494 landed without changing that sentence; the seat carries a note to formula: the unknown-field check reads only the dot spellingrecord.FIELD, sorecord['typo']andprevious['typo']passos buildand the object save door at every record-scoped slot #22428.visibleWhen, the one site not classified cleanly. It is render-only and evaluated over whatever row the surface holds. The console's approvals data source routes everysys_approval_requestread, list and detail, through the approvals routes, so a record detail view there holds a served row. It stays columns-only here, per the ruling's served list. No field predicate reads a block today. Adding it later is one entry inSERVED_ROW_SITES, together with its binding evidence.22386-validator-attached-on-read-leaf.mdsays the field index adds each block to the names arecordmember resolves to, at every site. This PR's changeset states the narrowing and refers to that entry. Neither has been released.Size
9 files, +300 / -75 = 375 changed lines against the merge base
446c8b2a. Of those, the generated files are the three reference pages, +4 / -4. Below the 3,000-line human-merge threshold.Generated by Claude Code