Repository navigation
feat(spec,objectql): report a caller value a stored expression replaced, as DroppedFieldsEvent reason stored_expression - #24778
Conversation
…der DroppedFieldsEvent reason stored_expression (WIP) Claude-Session: https://claude.ai/code/session_01D6F9axfoPzR7t8G1J7Ytzz Co-authored-by: Claude <noreply@anthropic.com>
…hrough the composed stack (WIP) Claude-Session: https://claude.ai/code/session_01D6F9axfoPzR7t8G1J7Ytzz Co-authored-by: Claude <noreply@anthropic.com>
… stored, and add the changeset Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D6F9axfoPzR7t8G1J7Ytzz
…opped-fields-recomputed
…eldsEvent reason Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D6F9axfoPzR7t8G1J7Ytzz
…her than casting to any Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D6F9axfoPzR7t8G1J7Ytzz
📓 Docs Drift CheckThis PR changes 3 package(s): 2 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 — 144 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 9bdfc8677ae2f6792e880c7e891d1f224e213654 && git checkout 9bdfc8677ae2f6792e880c7e891d1f224e213654
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin a7199ef9744edf0c3ef0e381bb95a328545a3da9 56cac787cbe56defaa3853fd178e7865bf6ea889 && git checkout -B drift-repro a7199ef9744edf0c3ef0e381bb95a328545a3da9 && git merge --no-ff 56cac787cbe56defaa3853fd178e7865bf6ea889
node scripts/docs-audit/affected-docs.mjs --json a7199ef9744edf0c3ef0e381bb95a328545a3da9
|
…opped-fields-recomputed
…donlyWrites instead of refusing it, deferring the refusal to 19.0 Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D6F9axfoPzR7t8G1J7Ytzz
|
Pin moved, for this PR's seat ·
This PR's landing is the Generated by Claude Code |
Contract reviewServed-tier: Read: card #24690 (body and every comment, claim ① Derived judgments
② Semver levelChangeset ③ Boundary flags
Implemented-by: VERDICT: PASS |
Closes #24690
Clause-②: yes (widening)
What
PR #24673 (
c0803b04d2) made the engine compute a stored (non-formula) field's declaredexpressionon every write, so a value the caller sends for such a field never stands. Before this PR nothing said so.onFieldsDroppedstayed silent, so the PATCH and/batchdoors answered with nodroppedFields, and a flow'supdate_recordreported success over a row that holds the computed value.Scope follows the maintainer's ruling 「甲」 (#6015 comment 6292005854, applied on decision card #24854): in 18.x only the loud half ships. That half is the new
reasonvalue, its emit, and its report. ThestrictReadonlyWritesrefusal is runtime behaviour that no conversion can carry, so it waits for 19.0.packages/spec/src/data/data-engine.zod.ts):DroppedFieldsEventSchema.reasongainsstored_expression, with its describe. The field IS written, with the computed value in place of the caller's, socomputed(aformulawith no column, whose key is dropped) would be untrue of it. The name follows the existing arms, which name the field's role (primary_key,computed). Thefieldsdescribe and the schema describe now also cover a field written with the computed value. Incontracts/data-engine.ts, thestrictReadonlyWritesTSDoc namesstored_expressionas the one arm outside its coverage in 18.x.engine.ts):replacedStoredExpressionFieldsreports a field when three things hold: the caller sent it, no other strip took it, and the value stored differs from the caller's. It runs on insert (single, batch, andinsertMany's per-rowdroppedFields), on update by id and on predicate update, in every context,isSystemincluded. A client that sends the computed value (the console grid's consistent save) or no value is told nothing. A value a staticreadonlystrip already took is reported once, asreadonly.notifyDrop). On insert, the strict check judges only the strips' own drops. Both strict checks sit exactly where they sit onmain, before the computation. So a strict write whose only drop is astored_expressionreplacement completes with the computed value and is reported toonFieldsDropped. A strict write that also carries a refusable drop is refused for that drop alone, writes nothing, and fires no listener.readonly-strict-errors.tsgains only theREASON_PHRASErow its exhaustive key type requires. No refusal carries that row in 18.x.crud-nodes.ts):DROPPED_REASON_LABELis an exhaustiveRecordover the reason union, so it needs the new row. For this reason the step warning says the requested value(s) were NOT stored, and that the write succeeded with the computed value(s). The other reasons keep their exact text.content/docs/references/**regenerated withcheck:generated --fix.Clause-②: yes (widening).@objectstack/specand@objectstack/objectqlareminor,@objectstack/service-automationispatch. Not breaking. It records that the strict refusal is deferred to 19.0 per 「甲」.Landing order: the objectui pin must move first
objectui's
STRIPPED_LINE(packages/app-shell/src/providers/writeWarningToast.ts) is aRecordkeyed byDroppedFieldsEvent['reason'].app-shellbuilds withtsc, andscripts/build-console.shinstalls this tree's@objectstack/*into the pinned objectui. The pin onmaintoday is616df6598b. It does not contain objectui PR 13233 (objectstack-ai/objectui#13178), which adds thestored_expressionrow ahead of the spec pin: merge commit8803ca54ac269643101f98dd0cc24ab25feb55a0, which is 36 commits ahead of616df6598band is on objectuimain. Landing this PR at today's pin would turn the full-batteryConsole Pin Gatered.The pin is not bumped here.
docs/releases-maintenance.mdsays the pin moves by hand, in its own PR, withscripts/bump-objectui.sh, to a revision chosen in an objectstack issue. This PR must not land before that pin PR does. The pin PR must:scripts/bump-objectui.sh 8803ca54ac269643101f98dd0cc24ab25feb55a0(or a later objectuimaincommit). It writes.objectui-shaand the@objectstack/consolechangeset for616df6598b..NEW.pnpm objectui:build, thennode scripts/gen-sdui-manifest-node.mjs. That rewritessdui.manifest.jsonandscripts/sdui-manifest.record.json, andscripts/check-sdui-manifest.mjsmust be green.stored_expression, so the pinned objectui compiles againstmain's spec both before and after this PR.Tests (on the head named in the latest report)
packages/objectql/src/engine-stored-expression-dropped-fields.test.tscovers by-id update (the PATCH door and each by-id/batchop), insert single, batch andinsertMany, and predicate update. It has controls for an equal value and an unsent value, and checks the single report when thereadonlystrip took the value. UnderstrictReadonlyWritesit checks three things. By id, on insert and on a predicate update, the write completes and is reported. A write that also has areadonly_whendrop is refused for that drop alone, writes nothing and fires no listener. An insert with a refusable drop is refused before the computation, carrying nostored_expressiondrop.packages/rest/src/rest-stored-expression-dropped-fields.test.tsruns through the composed stack (RestServer, then the protocol, thenObjectQL, then a realSqlDriveron better-sqlite3). PATCH answersdroppedFieldsand theX-ObjectStack-Dropped-Fieldsheader./batchtags the op index. Each has a consistent-value control.crud-dropped-fields.test.ts(service-automation) pins the new warning.data-engine.test.ts(spec) pins the five-value enum.scripts/ablation-replace.mjs. The tool restored and verified the file each time (blob equal to HEAD,git diff HEADempty).return [], and 12 of 30 objectql tests failed (first round).notifyDropwas routed back through the strict-or-listeneremitDrop, and the strict by-id report test failed (1 of 17).stored_expressionrow commented out ofDROPPED_REASON_LABEL, service-automationtscfailed with TS2741.Acceptance notes
Object.isis the comparison, as in the compute passes, over caller values normalized before the snapshot. Take a stored expression whose computed value has a different JS type from an equal value the caller sends, such as a date expression computing aDateagainst an ISO string: it would be reported. This was not measured.ImportRowResult.droppedFields(api/export),/batchand protocoldroppedFieldsdescribes still say "caller-supplied fields the engine LEGALLY strips". They carry the fifth reason through the shared enum, and their prose was left alone because it is outside this card's file surface.stored_expressionreplacement understrictReadonlyWrites, needs its own card, filed by the seat.Generated by Claude Code