Skip to content

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

Merged
objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-22481-attached-block-served-rows
Oct 9, 2026
Merged

objectstack-fleet[bot] merged 5 commits into
mainfrom
claude/issue-22481-attached-block-served-rows

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #22481

Clause-②: no

What this changes

ObjectSchema.attachedOnRead declares the blocks a service attaches to the rows it serves, computed per caller and never stored. Until now @objectstack/lint put every declared block into the field-existence set of every expression site bound to the object. So os validate accepted a flow condition such as record.viewer.can_act == true on sys_approval_request, and the flow then faulted with No such key: viewer on every run, because a flow's record is 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 record is a served row.

  • buildFieldIndex holds the object's columns again: authored fields plus the injected system columns. That is what every stored-row site reads.
  • SERVED_ROW_SITES in packages/lint/src/validate-expressions.ts is the one enumerated list of served-row sites: an action's visible and disabled. The check closure adds the object's blocks to the field-existence set, and passes the attachedOnRead leaf 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.
  • The field-formula judge no longer passes the hint. A formula is computed on the stored row.
  • No new rule, no new refusal code, no export or signature change. @objectstack/formula is 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 on main and merged into this branch at 90e34944, does not move them. The lint suite was re-run on the merged head.
  • @objectstack/spec: the .describe() of ObjectSchema.attachedOnRead and the AttachedOnReadSchema docblock (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's visible and disabled; every stored-row site refuses record.BLOCK as an unknown field. check:generated --fix proved only check:docs stale and regenerated the three reference pages that render the describe. The changeset gains an @objectstack/spec patch entry.

Every site that received the block, classified

Site record binds Evidence Verdict now
action visible / disabled (object and stack-level) the row the surface fetched. The console reads sys_approval_request through the approvals routes, whose rows carry viewer sys-approval-request.object.ts (the attachedOnRead comment); objectui apps/console/src/services/approvalRequestsDataSource.ts header served row: blocks and leaves judged
flow node config.condition / edge condition stored row plus the write's payload record-change-trigger.ts seeds { ...priorBase, ...inputData, ...after }; the card's run measured No such key: viewer stored: columns only
flow shadowed-field warning same index, same flow scope warnShadowedFieldReads reads fieldIndex stored: columns only
validation rule condition / when / nested stored row merged with the payload, on the write rule-validator.ts evaluateValidationRules stored: columns only
field requiredWhen / readonlyWhen the same merged row, on the write rule-validator.ts (readonlyWhenBindings) stored: columns only
option visibleWhen the same merged row: the server's write gate refuses a write whose option predicate faults rule-validator.ts evaluateOptionVisibility stored: columns only
field formula expression the stored row judgeFieldFormula stored: columns only
sharing-rule condition stored rows compiled to a row filter stored: columns only
hook condition the write's record hook-wrappers.ts stored: columns only
field visibleWhen not classified cleanly: render-only, evaluated over whatever row the surface holds rule-validator.ts header: render side only, fail-open columns only (the default); see Acceptance notes

Measured

All readings in this section are at 26088deb, the first round. The patch round's readings at 64277b3f are in their own section below.

Through the built CLI (node packages/cli/bin/run.js validate, after pnpm turbo run build --filter=@objectstack/cli...). The fixture configs import the shipped SysApprovalRequest.

  • Pin. sys_approval_request plus a record_change flow whose start condition is record.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` .
  • Control. The shipped object alone, eight action predicates reading the block → exit 0, ✓ Validation passed, no finding naming viewer.
  • Control. One shipped action predicate rewritten to 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 with git diff HEAD empty).

  • Leg 1, unit. check re-applies the block at every site (the servedRowSite gate dropped) → validate-expressions.attached-on-read.test.ts 10 failed / 6 passed of 16. All 8 stored-row sites check serves 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.
  • Leg 2, unit. The formula judge re-applies the block → 1 failed / 15 passed (the formula pin). Restored.
  • Leg 1 through the CLI. Same mutation. pnpm --filter @objectstack/lint build: the JS bundles were emitted, and the DTS step failed on the now-unused servedRowSite parameter (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 --noEmit clean, and check:test-typecheck OK. pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2: 130 files, 5954 tests passed.
  • pnpm --filter @objectstack/plugin-approvals exec vitest run on sys-approval-request-attached-on-read.test.ts, sys-approval-request-viewer.conformance.test.ts and action-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/objectstack at 26088deb derived 69 commands. I ran each one and captured its exit code before any pipe: 68 exit 0, and 1 exit 3. That one is pnpm check:dual-build-cjs-loads, PREREQUISITE NOT MET: it reads every package's dist/, 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 json on the three changed .ts files: 3 files, 0 errors, 0 warnings.

  • --print-config gives a config for each of the three .ts files and none for the .json and .md files, so the three are the whole linted population of this diff.
  • The effective parserOptions are { ecmaVersion: 'latest', sourceType: 'module' }, with no project or projectService. Type-aware linting is off, so this diff cannot change the verdict on any untouched file.
  • The repository-wide pnpm lint is CI's.

Prose that PR #22479 made stale (carried here by the card)

  • packages/spec/liveness/object.json, the attachedOnRead row. producer and note now describe the served-row reading and name the landed conformance test sys-approval-request-viewer.conformance.test.ts. The evidence sentence about buildFieldIndex had become false with this change, so it now cites SERVED_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 64277b3f

  • Merge. bash scripts/pm/os-regen-merge.sh merged origin/main at 446c8b2a as 90e34944. 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).
  • Regeneration. pnpm --filter @objectstack/spec build, then check:generated: 1 of 15 artifacts stale (check:docs, content/docs/references/**). check:generated --fix regenerated only that one. The three pages differ from main only on the attachedOnRead row.
  • Tests.
    • The @objectstack/spec tests for object.zod.ts (object, object-attached-on-read, object-image-field, object-strictness-batch20, metadata-form-zod-reconciliation and the two compose-stacks pins): 7 files, 433 passed.
    • Spec --project repo: 54 files, 915 passed.
    • @objectstack/lint typecheck: clean, and check:test-typecheck OK.
    • @objectstack/lint vitest: 131 files, 5985 passed, 5 skipped. The 5 are skipIf on an unbuilt lint dist/ (lazy-deps, runtime-lazy-deps). After pnpm --filter @objectstack/lint build those 2 files gave 10 passed.
  • Spec gates. check:liveness exit 0, with 894 of 894 symbol anchors resolved. check:generated: all 15 up to date. check:docs, check:api-surface, check:authorable-surface and check-spec-docblock-symbol-anchors: exit 0 each.
  • Gates. dispatch-gates --commands at 64277b3f derived 110 commands: 106 exit 0, and 4 exit 3, which are NOT MEASURED.
    • check:dual-build-cjs-loads, check:lean-entry-closure and spec check:skill-examples need dist/ 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).
  • Artifact rosters. The no-path derivation lists 51 commands. 47 exit 0 as run. 3 printed NOT WIRED without PR context, and with PR_NUMBER=22509 and this body all 3 exit 0: check-closing-target-claim, check-partof-closing-keyword, check-single-claim-paths. check:published-readme-exports exits 3, NOT MEASURED: it needs every package's built .d.ts. check:error-code-provenance exits 0 (335 stamp sites: 316 listed, 19 waived, every waiver live).

Acceptance notes

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

claude added 2 commits October 9, 2026 15:03
… 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>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Oct 9, 2026
@github-actions

github-actions Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/lint, @objectstack/spec, touching 8 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/spec/liveness/object.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/deployment/validating-metadata.mdx (via AUTHORING_RULES (symbol, a top-level const object))
What this run could not see
  • 1 changed file(s) yielded no anchor (packages/spec/liveness/object.json) — pages documenting those are invisible to this run
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 139 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 446c8b2a6420a61a2862e6f5140dda71a53316d1 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 897b6914e4d7cfda013a1be9c63f419938daf779 — the merge of head 64277b3f28734efc9504f428584e496afdaff6a1 into base 446c8b2a6420a61a2862e6f5140dda71a53316d1, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 446c8b2a6420a61a2862e6f5140dda71a53316d1 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

claude added 2 commits October 9, 2026 16:22
…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>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 64277b3f28734efc9504f428584e496afdaff6a1
Local-runs: none

Reviewed from the card (#22481: body and all six comments), PR #22509 (body, file list, and the net diff of refs/pm/pr-22509 against its merge-base with origin/main, 446c8b2a64, which equals the PR's base sha: 9 files, +300 / −75, identical to the PR's own file list), the maintainer ruling record 6070963704 on #22211 read at its source, and the 46 check-runs on this head: 39 success, 7 skipped, 0 failures, with all seven required contexts success (Lint & Repo Gates, TypeScript Type Check, Test Core, Dogfood Regression Gate, Build Core, Temporal Conformance (live PG + MySQL), Governed Surface Queue Guard). The branch carries a merge of main (90e34944); every judgment below is on the merge-base diff, so nothing main brought is attributed to this PR.

① Derived judgments

Each 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:

  1. Stored-row sites refuse record.BLOCK on a declaring object — right. buildFieldIndex drops the block names (attachedBlockNames is deleted and no reference survives on the branch), so every site that reads fieldIndex resolves columns only: flow node and edge conditions, validation rules, field requiredWhen / readonlyWhen / visibleWhen, option visibleWhen, field formulas, sharing-rule and hook conditions. The verdict there is the existing unknown-field error, the one an undeclared object gets, which is the fault the expression hits at run time (No such key). Pinned by the nine-row STORED_ROW_SITES it.each (flow start, flow edge, validation rule, requiredWhen, field visibleWhen, option visibleWhen, field formula, sharing rule, hook). readonlyWhen has no row of its own but enters through the same declared-slot ledger arm and the same check closure, so the verdict follows by construction.
  2. Served-row sites keep the block and its leaf judgment — right. SERVED_ROW_SITES = ['action visible', 'action disabled'] is one module-private as const list; the check closure takes a seventh optional servedRowSite typed by it, and only a call naming an entry adds the blocks (withAttachedBlocks, appended last so columns keep their did-you-mean rank) and passes the attachedOnRead leaf hint. On the branch exactly two calls name an entry, the action pass at validate-expressions.ts:2770 and :2772; the two undefined they pass land on fieldRuleVerdictIssued and traversalHydration, both previously omitted, so those sites change nothing else. A declared leaf is accepted and a misspelt leaf refused naming the declared leaves (existing pins kept; a new disabled pin added). A new call site is fields-only unless it names an entry, the right default for the field-rule slot in flight on spec(data): a date or datetime field declares its deadline semantic — dueLike with settledWhen (per-record CEL), so overdue wording and colour derive from one declaration and the name-pattern guess retires (objectui#11815 ruled D) #22227.
  3. The field-formula judge no longer passes the hint — right. A formula is computed on the stored row; pinned as the a field formula row, which replaces the old test that pinned the opposite.
  4. Block names are did-you-mean candidates only at served-row sites — right, and pinned (viewr at a flow start names no viewer).
  5. The flow shadowed-field warning (warnShadowedFieldReads) is columns-only now — right. It reads fieldIndex in flow scope only; a flow variable named like a block no longer warns as shadowing a field, and a flow's record never carries the block. Not separately pinned; it is an advisory warning, not an accept/reject verdict, and it follows from judgment 1.
  6. No public surface moves in @objectstack/lint — right. SERVED_ROW_SITES, ServedRowSite, withAttachedBlocks and the removed attachedBlockNames are all module-private, and the check closure is local to runStackExpressionPasses. No new rule id, no new refusal code. @objectstack/formula is untouched by this PR's delta (the fix(formula)!: the unknown-field check judges every member spelling of record / previous, not only the dot (#22428) #22494 change reaches the branch only through the merge of main).
  7. @objectstack/spec: prose only, the schema unchanged — right. The .describe() of ObjectSchema.attachedOnRead and the AttachedOnReadSchema docblock now state the served-row reading; the three reference pages that render the describe are regenerated as their own commit (+1 / −1 each, migration.mdx twice). Parse verdicts, the authorable surface and the api surface do not move; the old sentence occurs nowhere on the branch; the spec artifact gates are green on this head. The new describe carries no tracker number.
  8. The two stale prose sites the card carried — right. The liveness row (evidence, producer, note) cites validate-expressions.ts#SERVED_ROW_SITES and the landed conformance test, and Spec property liveness is green on this head; the authoring-rules.ts pass-4 note now says the object declares the block and the build and the door accept all eight predicates.
  9. The changeset's object-save-door sentence — right by construction, not separately pinned here. The rule is registered CLI_AND_RUNTIME with runtimeTypes including object, so the door runs the same check over an object's own slots; the plugin-approvals door test the dev re-ran covers the positive control (eight shipped predicates pass).

Read against the grain: a row fetched anywhere but the declaring service's routes carries no block at an action predicate either; the SERVED_ROW_SITES docblock says so and names the has() guard. That is the ruling's own residual, not one this PR adds. Field visibleWhen is the one site whose binding depends on the surface; its fields-only default refuses loudly (pinned), which is the safe side.

② Semver level

③ Boundary flags

Every dev flag and every open_questions entry from both os-dev-report comments (6084801288, 6087692896), answered or escalated:

  • OQ1 (round 1), field visibleWhen: answered A, fields-only — right. The ruling's served-row subject is the action predicates; a site whose binding depends on the surface cannot be declared served; zero field predicates read a block; the fields-only default is pinned in STORED_ROW_SITES. Adding it later is one list entry plus binding evidence.
  • OQ2 (round 1), the spec text: answered A, carried here — right, and done. Judgment ① 7 covers it; the regeneration is its own commit.
  • The card's own flag ("A narrows reader (1) of ruling 6070963704"): judged within the ruling, not escalated. The ruling defines a block as attached to the rows a service serves, computed per caller, never stored; a stored-row site therefore carries no block by the ruling's own definition. The ruling's Not ruled line forbids a validator exception for viewer and keeps triage's order (no new rule, no new refusal code, the declaration is what the existing rule reads): this PR adds no rule and no code, gates by site rather than by name, and the existing unknown-field rule still reads the declaration. The ruling's subject and measured refusals (the eight action visible predicates) are preserved. Making the build refuse what the runtime faults on is Prime Directive Add comprehensive test suite for Zod schema validation #12. The only wording a maintainer could read the other way is reader (1)'s "adds each declared block name to the field-existence set", written against buildFieldIndex before any site was distinguished; that sentence is now executed at the served-row sites and the describe says where. If the maintainer reads it literally, the reversal is one entry in SERVED_ROW_SITES and a decision card, not a redesign.
  • Round-1 deviations. evidence rewrite: same row as the claimed fields, accepted. Extra ablation legs: more evidence, not less. Spec text left unedited: resolved in the patch round. check:dual-build-cjs-loads not measured locally: Lint & Repo Gates is green on this head. Transient pnpm install failure: environment. Model-free attribution per AGENTS.md: correct, AGENTS.md is the repo's instruction of record.
  • Round-2 deviations. Merge of main first, spec edit, regeneration as its own commit: the §11 sequence, and the merge-base diff shows nothing main brought is attributed here. 588abcb4 pushed red before regeneration: the WIP-push rule; this head supersedes it. Docblock stale clause replaced inside the widened surface: accepted. check:type-check-debt killed locally: Type Check · debt ledger is green on this head. Five families NOT MEASURED locally (dual-build-cjs-loads, lean-entry-closure, published-readme-exports, spec check:skill-examples, type-check-debt): each is CI-owned and its job (Lint & Repo Gates, TypeScript Type Check, Type Check · debt ledger) is success on this head. The three PR-context roster gates judged locally against the pre-edit body: the same three contexts re-ran on this head after the body edit and are success.
  • Out-of-scope findings. (a) ExprSchemaHint.attachedOnRead's doc sentence in packages/formula/src/validate.ts ("a caller lists each block name there too (@objectstack/lint's field index does)") now holds at served-row sites only; it ships in formula's .d.ts. Escalated: the seat's cross-seat note 6084861204 on formula: the unknown-field check reads only the dot spelling record.FIELD, so record['typo'] and previous['typo'] pass os build and the object save door at every record-scoped slot #22428 exists and names the edit; its original carrier, PR fix(formula)!: the unknown-field check judges every member spelling of record / previous, not only the dot (#22428) #22494, has landed without it, so the note is the live carrier. If formula: the unknown-field check reads only the dot spelling record.FIELD, so record['typo'] and previous['typo'] pass os build and the object save door at every record-scoped slot #22428 closes without that sentence, a docs-only card is owed. (b) The MCP expression tool's fields-only record set predates this card, is recorded in the liveness note, and is outside the ruling's named reader. (c) The unreleased spec(data): ObjectSchema.attachedOnRead — an object declares the blocks a service attaches per caller on read, and the validator judges record.<block>.<leaf> against it (#22211 ruling A, spec half) #22386 lint changeset says "at every site"; this PR's entry states the narrowing, and the two compile in order into one release's notes. All three named, none blocking.
  • Governed surfaces: the file list touches none (docs/adr/**, docs/NORTH-STAR.md, .claude/**, skills/**, AGENTS.md, CLAUDE.md); Governed Surface Queue Guard is green. No ADR names attachedOnRead, and no ADR anchor covers validate-expressions.ts. Size 375 changed lines, under the 3,000-line threshold. The head repo is the base repo.

Implemented-by: claude/issue-22481-attached-block-served-rows
Reviewed-by: session_01KNKBCRDJCu5tGy3TEbvtrF

VERDICT: PASS


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation protocol:data size/m tests tooling

Projects

None yet

2 participants