Skip to content

docs(data-modeling,kernel): a value a stored expression replaces is reported under stored_expression and refused under strictReadonlyWrites - #25172

Merged
objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/issue-25089-stored-expression-docs
Oct 22, 2026
Merged

objectstack-fleet[bot] merged 1 commit into
mainfrom
claude/issue-25089-stored-expression-docs

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #25089
Clause-②: no

What changed

Hand-written docs only. Two pages said that a value a client sends for a stored field that declares expression is neither refused nor reported as dropped. Both halves are false on main (b93c750c7):

Page Before After
content/docs/data-modeling/validation-rules.mdx (:368) "is not refused and not reported as dropped" The value never stands. A value that differs from the computed one is reported under stored_expression. Sending the computed value, or no value, reports nothing. Under the in-process strictReadonlyWrites option the write is refused with ERR_READONLY_FIELD_REJECTED, and a request body cannot set that option. "The one refusal" (the predicate update) now reads "Without it, the one refusal".
content/docs/data-modeling/field-types.mdx (:424, the expression on a stored field section) "with no refusal and no dropped-field report" The same correction. This page carried the same stale claim as validation-rules.mdx; both were written by 4a2ca57e7.
content/docs/kernel/contracts/data-engine.mdx The strip table had no stored_expression row, and the DroppedFieldsEvent.reason union listed four arms A stored_expression row in the existing columns, with Verbs insert and update and Writers it skips "none — every context, isSystem included". One sentence follows the table: the row is a replacement, not a removal. 'stored_expression' is added to the reason union in the marked (type-checked) block, matching DroppedFieldsEvent.reason in packages/spec/src/data/data-engine.zod.ts.
content/docs/permissions/system-context.mdx (row 22, "Strict-drop refusal never fires" under isSystem) "One exception: a formula value ... so strict still refuses it" "Two exceptions": the formula value and the stored_expression replacement. Under isSystem, strict also refuses the replacement, so "one exception" is the same stale "not refused" claim in another place.

Evidence: source reads at b93c750c7

  • Report. packages/objectql/src/engine.ts droppedFieldEvents pushes reason: 'stored_expression'. replacedStoredExpressionFields reports a field only when the caller sent it and the stored value differs from it, so sending the computed value reports nothing. The update by id and the predicate update both call emitDrop(..., 'stored_expression'). packages/metadata-protocol/src/protocol.ts forwards every onFieldsDropped event as droppedFields from createData, cloneData, updateData, createManyData, insertManyData and updateManyData. packages/rest/src/rest-stored-expression-dropped-fields.test.ts pins the PATCH and /batch doors.
  • Refusal. The insert path throws ReadonlyFieldRejectedError when insertDrops, which include the replacement, is non-empty and options?.strictReadonlyWrites === true. On update, emitDrop pushes into strictDrops and assertNoStrictDrops throws. Neither path checks isSystem, and engine-stored-expression-dropped-fields.test.ts pins the isSystem report. The code is READONLY_FIELD_REJECTED_CODE = 'ERR_READONLY_FIELD_REJECTED' (packages/objectql/src/readonly-strict-errors.ts), registered in ERROR_CODE_LEDGER.
  • No HTTP status. No package source passes strictReadonlyWrites: true, and the option is not in the serializable options bag. The packages/spec/src/contracts/data-engine.ts section "In-process only — what a REMOTE caller observes" says so. For that reason the pages give no HTTP status for this refusal.

Sites checked and left unchanged

  • git grep stored_expression over hand-written content/docs (not references/, not releases/) has zero hits before this change. As a control, strictReadonlyWrites has 4 hits on data-engine.mdx.
  • A whole-tree git grep for the stale phrasings ("not reported as dropped", "no dropped-field report", "not refused and not", "no refusal and no") hits only the two pages above, plus unrelated subjects: hook-stamped fields, nested views and similar.
  • FieldSchema.expression (packages/spec/src/data/field.zod.ts) says "a client-sent value never stands", which is accurate. The engine.ts:3036 docblock is accurate too.
  • data-engine.mdx already says, in its strict section, that every row in the table is refused and that a new reason adds a refusal. With the new row that sentence is true as written, so it is not changed.

Gates

These were run locally after the last commit, at head b7a6d9449 (merge base b93c750c7). The gate list comes from node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack with no paths, which derives 44 commands.

  • All 44 exited 0. Exit codes were captured before any pipe.
  • Reconciliation reads: dispatch-gates --ran: 44 derived famil(ies) accounted for — 44 run, 0 NOT-MEASURED (a DERIVED zero — all 44 recorded an exit code and none of them is 3).
  • The first pass of four gates exited 3 (PREREQUISITE NOT MET) because the packages they read were not built: check:doc-formula-expressions, check:doc-security-posture, check:docs-transcript-drift and check:skill-examples. No measurement was taken in those runs. After building @objectstack/spec, the @objectstack/lint closure, @objectstack/formula and the @objectstack/client-react closure, all four were re-run and exited 0.
  • Verdict lines include:
    • check-doc-anchors: 570 internal #fragment link(s) ... all resolve to a real heading, which covers the two new #writeobservabilityoptions links.
    • check:skill-examples: 262 prose examples type-check across 3 surface(s), which covers the edited marked block on data-engine.mdx.
    • check:docs: 227 generated files in sync.
    • check-system-context-census: OK.
    • check-nul-bytes: OK.
  • No package build is owed by the diff itself; the builds above were only for gates that read dist/.

Changeset

skip-changeset. The diff touches only content/docs/** (not releases/). No published package's files[] includes a content/ path or a path outside the package. Every non-private package.json was read, and @objectstack/objectql ships dist, README.md and CHANGELOG.md. The docs site @objectstack/docs is private: true. The generators that name content/docs write into it and read nothing from these pages.

Acceptance notes

  • Out of scope, reported to the seat to file (published skill text, a governed surface). skills/objectstack-data/rules/relationships.md:296 says the server treats only type: 'formula' as computed. It goes on: on a stored field the expression is a client compute hint and the sent value is stored verbatim. That contradicts FieldSchema.expression ("a client-sent value never stands") and the engine, which computes and stores the value on every write. The text ships in @objectstack/skills. It is not changed here because skills/** is Tier H.

Generated by Claude Code

…eported under stored_expression and refused under strictReadonlyWrites

validation-rules.mdx and field-types.mdx said a client value for a stored
field that declares `expression` is neither refused nor reported as dropped.
The engine reports it through `onFieldsDropped` under
`reason: 'stored_expression'` (the data API's `droppedFields`), and an
in-process `strictReadonlyWrites: true` write is refused with
`ERR_READONLY_FIELD_REJECTED`. data-engine.mdx gains the
`stored_expression` row in its strip table and in the `DroppedFieldsEvent`
reason union; system-context.mdx row 22 names the second strict exception
that holds under `isSystem`.

Claude-Session: https://claude.ai/code/session_01T1Y1g92DfSEz93iuCisju4
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT · domain:spec seat 1 (seat post #6017) · os-bill · session session_01T1Y1g92DfSEz93iuCisju4 · 2026-10-22T05:31Z · reviewed head:

b7a6d944917b8930198a26d6b8b983f6d4dbfd2d
  • Form: draft, base main, first line Fixes #25089, Clause-②: no line-initial; the only closing keyword in the body names docs: validation-rules.mdx says a value sent for a stored-expression field is "not refused and not reported", and the data-engine strip table has no stored_expression row #25089. Labels skip-changeset, documentation, size/s; assignee os-bill. mergeable_state clean.
  • Scope: 4 files, all hand-written content/docs/**, none under releases/. check-governed-merges.mjs --pr 25172: 0 of 4 paths governed; 15 changed lines. No contract face is touched (prose only, Clause-②: no), so no contract-tier review is owed; this is the in-seat review of record.
  • skip-changeset: holds. No released package ships content/ (@objectstack/docs is private).
  • Sentences checked against origin/main:
    1. "When the value sent differs from the computed one … reports the field as dropped, under reason: 'stored_expression'" (field-types.mdx, validation-rules.mdx): replacedStoredExpressionFields in packages/objectql/src/engine.ts lists a field only when the caller supplied it and a stored row's computed value is not Object.is the supplied one. droppedFieldEvents reports it under 'stored_expression'. ✓
    2. "sending the computed value, or no value, reports nothing": the same function's hasOwnProperty and Object.is guards. ✓
    3. "strictReadonlyWrites: true is refused … with ERR_READONLY_FIELD_REJECTED, and nothing is written": the strict docblock in packages/spec/src/contracts/data-engine.ts (since objectql 18.x: under strictReadonlyWrites, refuse a write whose value a stored expression replaces (stored_expression), the deferred half of #24690 #25000), and the engine's insert path, which refuses on insertDrops.length > 0 && options?.strictReadonlyWrites === true before any driver dispatch. ✓
    4. "a request body cannot set that option": the same docblock's "In-process only" section (not part of the serializable options bag, unreachable from a REST/wire body). ✓
    5. data-engine.mdx's new row, "every context, isSystem included": the stored-expression computation and replacement block in engine.ts has no isSystem gate. ✓
    6. data-engine.mdx: the DroppedFieldsEvent.reason union gains 'stored_expression', matching the contract's "all five DroppedFieldsEvent['reason'] arms". ✓
    7. system-context.mdx row 22, "Two exceptions": strict refuses a formula value and a replaced stored-expression value in every context, per 3 and 5. ✓
  • Gates (pinned to the head above): every check-run completed, 0 non-green. Lint & Repo Gates, TypeScript Type Check, Test Core and Governed Surface Queue Guard succeeded; Build Core was skipped. check-expected-skips.mjs --pr 25172: "OK — 12 skipped check-run(s), every one in main's roster".
  • Accepted deviations: the in-place fix of content/docs/permissions/system-context.mdx row 22 is the same stale claim, covered by the claim's surface wording ("the same stale claim wherever else the hand-written docs carry it"); this record names the file. Commit trailers follow AGENTS.md's model-free pair.
  • Out of scope: the dev's one finding (skills/objectstack-data/rules/relationships.md teaches a stored field's expression as "a client compute hint" whose sent value is "stored verbatim") is carried to spec + skills: the titleFormat describe and the objectstack-data skill still steer a composite record title into a formula field (follow-up to #25071, ruling 6308445871) #25129's skill half, pointer 6333459023. Nothing filed.
  • Report: os-dev-report 6333418264 on docs: validation-rules.mdx says a value sent for a stored-expression field is "not refused and not reported", and the data-engine strip table has no stored_expression row #25089; mcp_calls 0; api_writes 2 relay strokes, both read back.
  • Landing: ordinary queue landing (ready, then auto-merge).

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 22, 2026 05:33
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 22, 2026
Merged via the queue into main with commit 82cfaf7 Oct 22, 2026
47 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-25089-stored-expression-docs branch October 22, 2026 05:47
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 size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants