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 |
…in with stored_expression subtracted (objectui#13309) objectstack-ai/objectstack#24778 added a fifth arm, `stored_expression`, to `DroppedFieldsEventSchema.reason`. `_ReasonIsTheEnum` stated the union as exactly four members, so the Spec Shape Gate (objectui main against the spec built from objectstack main) reds on TS2344. Apply the objectui#11206 shape: subtract the new arm, a no-op at the pin; drop it at the pin bump that carries the arm. Co-authored-by: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB
…red expression replaces is refused, and nothing is written (#25086) Closes #25000 Clause-②: no (narrowing: under `strictReadonlyWrites`, a write whose value a stored expression replaces is refused instead of reported; it ships now under the amended 「甲」, #6015 `6303865025`) ## What changes `strictReadonlyWrites` refuses every drop that `onFieldsDropped` reports. Since the `stored_expression` reason landed (#24690, PR #24778 `7532ea3c08`), one reported drop was left out of the refusal: a caller value for a stored (non-`formula`) field that declares an `expression`, which the engine replaces with the computed value. Under strict such a write completed and was only reported. The amended ruling 「甲」 (#6015 `6303865025`) ships the refusal in 18.x as a BREAKING minor, and triage re-timed this card to 18.x (`6305854988`). - **Insert (single, batch, `insertMany`).** The strict check in `engine.insert` now judges `insertDrops`, the one event list that includes the stored-expression replacement, after the compute pass. Before, it judged only the strips' drops, ahead of that pass. A replaced value throws `ReadonlyFieldRejectedError` (`operation: 'insert'`) before validation and before any driver dispatch, and no listener fires. `insertMany` delegates to `insert`, so it refuses the whole call, as it does for the other strict drops. - **Update by id and predicate update.** The `stored_expression` report now goes through `emitDrop`, the channel every strip reports through, and no longer calls `notifyDrop` directly. On both branches the one `assertNoStrictDrops()` call now comes after the stored-expression pass. So under strict the write is refused before the statement and nothing is written. An object that declares no stored expression skips that pass, so its refusal happens exactly where it did before. - **One refusal names everything.** A payload that carries another strip's drop as well as a replaced value is now refused once, naming both, with `stored_expression` last. Before, it was refused for the other drop alone. - **Wording.** `REASON_PHRASE.stored_expression` (written by #24690 ahead of this refusal) is reused. The per-reason message gains a `stored_expression` remedy (leave the field out, or send the computed value). That remedy is spoken only when the reason is present, so every other refusal's message is unchanged byte for byte, by the same rule the `computed` remedy follows. - **Docs.** The TSDoc in `packages/spec/src/contracts/data-engine.ts` (`strictReadonlyWrites`) and `packages/spec/src/data/data-engine.zod.ts` (`DroppedFieldsEvent.reason`) no longer names `stored_expression` as the arm strict does not refuse. The objectql comments in `engine.ts` and `readonly-strict-errors.ts` follow. No `.describe()` text moved: `check:generated` reports all 14 artifacts current, so the generated reference docs have nothing to regenerate. **Unchanged:** a strict write that sends the computed value, or no value, is not refused. A stored expression field that is also `readonly` is still refused once, as `readonly`. Without strict, the write completes with the computed value and is reported. The error code is unchanged. ## Tests `packages/objectql/src/engine-stored-expression-dropped-fields.test.ts`: the strict block changes from "reported, not refused" to "refused, nothing written". It covers update by id, insert single, batch, `insertMany` and predicate update. Each refusal asserts the `code`, `drops` (and `operation`/`fields` where they matter), that the store is unchanged, and that the listener stayed silent. A non-strict control sits beside each refusal (the write succeeds and is reported), together with the strict controls for "computed value sent / no value". The union case is also covered (`readonly_when` + `stored_expression` in one refusal), and so is the `readonly`-only insert refusal. All runs were taken on the final code commit `a8e9d3611c`: - `pnpm --filter @objectstack/objectql test`: 455 files / 8569 tests passed. `pnpm --filter @objectstack/objectql typecheck`: `tsc --noEmit` clean, and `check:test-typecheck` OK (ledger held). - `pnpm --filter @objectstack/spec build && check:generated`: all 14 generated artifacts up to date. `spec test`: 705 files / 21424 passed (1 todo). `spec typecheck`: OK. `spec test:repo`: 72 files / 1237 passed. - `node scripts/pm/dispatch-gates.mjs --commands` derived 94 commands from merge base `688052e14`, and all 94 exited 0. On the first pass, `check:dual-build-cjs-loads` and `check:type-check-debt` printed PREREQUISITE NOT MET (exit 3, nothing measured). I re-ran `check:type-check-debt` under the verify lock: its closure build ran and it re-measured OK, `none above its recorded number`. That full build also produced the missing `dist/` trees, and `check:dual-build-cjs-loads` then passed (112 entry points across 69 packages load). `dispatch-gates --ran` reconciles 94 derived, 94 run, 0 NOT-MEASURED, 0 UNRUN. The changeset gates are among the 94: `check-adr-0087-registration`, `check-changeset-no-major`, `check-empty-changeset`, `check:changeset-gate-self-tests`. **Ablation.** The fix was committed first. Each leg went through `scripts/ablation-replace.mjs`, with an anchor hit of 1 to 0 and the blob proven back to the HEAD blob `3e8c0ec2bd1a` with `git diff HEAD` empty. The suite imports `./engine.js` from source, so no build is involved. - A, update paths: `emitDrop` lets `stored_expression` past strict. Three tests go red (update by id, predicate update, the union case), and the other 22 stay green. - B, insert: the strict check ignores a `stored_expression`-only list. Three tests go red (insert single, batch, `insertMany`), and the other 22 stay green. ## Producers measured No non-test caller in this repository passes `strictReadonlyWrites`: the examples, `packages/qa` dogfood and the runtime and REST doors pass none. The option is also not on the serializable options bag, so no wire caller can set it. No in-repo producer moves. In the objectql suite, the in-package strict tests on stored-expression objects (`engine-stored-expression-write.test.ts`, `engine-write-formula-hydration.test.ts`, `engine-formula-write-strip.test.ts`) stay green unchanged. ## Changeset `.changeset/25000-strict-stored-expression-refusal.md`: `@objectstack/objectql` `minor`, with a BREAKING section that maps FROM (completed, reported) TO (refused, nothing written), gives the remedy, and carries the claim's `Clause-②` line and an ADR-0087 `not-required (no-migration-prescription)` marker (the precedent shape of `bb4f5cc005`). `@objectstack/spec` is not listed: only TSDoc comments moved there, and no `.describe()` text or export did. ## Acceptance notes - The release-owned 18.x upgrade checklist (`content/docs/releases/`) is not edited here. The changeset carries the text that release compilation reads. - The pending `.changeset/24690-stored-expression-dropped-fields.md` (in the open Version Packages PR) says strict "is unchanged" and that the refusal is deferred to 19.0. That file is outside this card's claimed file surface, so this PR leaves it alone. The new changeset says it supersedes that bullet. The report names the one-line amendment for the seat to decide. - `content/docs/data-modeling/validation-rules.mdx` (the `expression` on a stored field paragraph) still says a sent value is "not reported as dropped", which has been false since #24690. That is a hand-written doc outside this card's surface, and it is noted in the report. Open PRs touching the same files were checked: #25072 and #24893 edit `engine.ts` in disjoint hunks (lines 4628–6300 and 9043–12250). No open PR touches `data-engine.zod.ts`, `contracts/data-engine.ts` or `readonly-strict-errors.ts`. --- _Generated by [Claude Code](https://claude.ai/code/session_01D6F9axfoPzR7t8G1J7Ytzz)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
… the pin declares it objectui#13178 widened STRIPPED_LINE's key type by `| 'stored_expression'` ahead of the pin, and spec-symbol-batch6's `_ReasonIsTheEnum` subtracted the arm, both until the pin carried objectstack-ai/objectstack#24778. The pin move to objectstack 830a63a6 does, and the tripwire in writeWarningToast.test.ts fired as designed. The key type is the spec union again, `_ReasonIsTheEnum` pins the exact five-member union, the `stored_expression` wording test drives the declared arm without the `string` spelling, and the tripwire goes with the widening it guarded. Claude-Session: https://claude.ai/code/session_016qAuAwFXMw94v5vFqTVcw2 Co-authored-by: Claude <noreply@anthropic.com>
… the spec pin (objectui#13178) (objectstack-ai#13233) Fixes objectstack-ai#13178 Clause-②: no ## What objectstack-ai/objectstack#24778 (draft) adds a fifth arm, `stored_expression`, to `DroppedFieldsEvent['reason']`: a stored field whose declared expression the engine computes on every write keeps the computed value, not the one the caller sent (the field IS written, unlike `computed`). `STRIPPED_LINE` in `packages/app-shell/src/providers/writeWarningToast.ts` is keyed by that union, so the objectstack Console Pin Gate, which builds the pinned objectui against objectstack's own spec, would fail on a missing key once #24778 lands. This lands the row first, in the objectui#11206 shape. - `STRIPPED_LINE`'s key type is a `Record` keyed by `DroppedFieldsEvent['reason'] | 'stored_expression'`. Against the published pin (which does not declare the arm) the required key set is the pin's four plus the literal; against #24778's spec it is the five it declares. No excess-key error on either side, and the next arm upstream adds is still a missing key here. The docblock records why, and the tripwire: delete the widening at the pin bump that declares the value. - One row, `stored_expression`, through a new key `detail.writeStrippedStoredExpression` with `{{fields}}`. English: "Computed from the record, so the computed value was saved instead of the one sent: FIELDS". A real translation in all ten packs under `packages/i18n/src/locales/`, never "read-only". - Changeset `.changeset/13178-stripped-stored-expression.md`: `@object-ui/app-shell` and `@object-ui/i18n` patch. ## Pins (`writeWarningToast.test.ts`) - (a) New case `words reason stored_expression as a computed value saved in place of the one sent, in en and zh (objectui#13178)`. It drives `emitWriteWarning` through a real `createI18n` instance in `en` and in `zh` (zh catalogue loaded with `loadBuiltInLocale`), asserts the key `detail.writeStrippedStoredExpression` was asked for, and asserts both full lines with the field labels. - (b) The objectstack-ai#3935 test `gives every reason the pinned spec declares its own wording` stays green against today's pin. It now drives the installed spec's reasons plus `stored_expression`, because today's pin does not declare it. - (c) CONTROL: `names the fields and claims no cause for a reason ahead of the spec pin (objectstack-ai#3935)` is unchanged and green. An undeclared value still takes the skew arm. - Tripwire, the objectui#11206 shape: `reminds the pin bump that carries stored_expression to retire the widening (objectui#13178)` asserts the installed spec does not declare `stored_expression`. When it does, the message names both edits: delete `| 'stored_expression'`, and make `_ReasonIsTheEnum` in `spec-symbol-batch6.test.ts` the five-member union. ## Ablation (one-shot, nothing kept) The fix was committed first (`fbb8c133e4`). `node scripts/ablation-replace.mjs` (objectstack checkout) renamed the row's key `stored_expression` to `stored_expression_ablated`, keeping the widened type, so the table's lookup misses. Anchor 1 to 0, blob `5e4d1f9dd772` to `c1abca187de5`. The run was `writeWarningToast.test.ts` under the lock: `Tests 2 failed | 19 passed (21)`. The two that failed were (a) and (b); (c) stayed green. The restore was `git checkout HEAD -- PATH` under an EXIT/INT/TERM trap. Proof: `git hash-object` after restore = `5e4d1f9dd772f1ccdc6c21c37fcb3f847530a0e3` = the HEAD blob, and `git diff HEAD` is empty. ## Tests and gates (head `fbb8c133e4`, worktree root, exit captured before any pipe) - `pnpm exec vitest run --maxWorkers=1` over `writeWarningToast.test.ts` and `saveAdvisoryToast.test.ts` (the files naming the module): 2 files, 37 tests passed. - The same over the i18n locale families that read every pack: `all-locales-key-parity`, `en-zh-key-parity`, `de-quote-pairing-3876`, `ar-participle-orthography-6610`, `ellipsis-glyph-3878`, `fallback-placeholder-spelling-3512` and `machineLocaleCensus-9909`. Result: 7 files, 93 tests passed. - `pnpm --filter '@object-ui/app-shell^...' build` exit 0. Then `pnpm --filter @object-ui/app-shell --filter @object-ui/i18n run type-check` exit 0. Both scripts run `tsc --noEmit && tsc -p tsconfig.test.json`. `tsc -p tsconfig.test.json --listFilesOnly` lists `writeWarningToast.test.ts`. - ESLint, `--no-inline-config --format json`, over the 12 changed `.ts` files: exit 0, 0 errors, 0 warnings, 12 files in the JSON. `eslint.config.js` enables no type-aware linting, so this diff cannot change the verdict on any file it does not touch. The repo-wide `pnpm lint` runs in CI. - These exited 0: `check:phantom-deps`, `check:self-import`, `check:unreferenced-sources`, `check:doc-example-readers`, `check:handler-key-reads`, `check:side-effects-array`, `check:element-data-source-declaration`, `check:spec-symbols`, `check:action-forward-parity`, `check:designer-field-key-parity`, `check:icon-record-names`, `check:i18n-keys`, `check:i18n-drift`, `check:i18n-designer-parity`, `check:control-bytes`, `check:new-line-citations`, `scripts/check-hand-rolled-comment-mask.mjs`, `scripts/check-changeset-fixed.mjs` and `scripts/check-changeset-presence.mjs`. - `check:spec-floors`: NOT MEASURED. It exited 1 with `no-artifact`, because it needs a whole-workspace build, which was not run here. - Full package directories `packages/app-shell/` and `packages/i18n/` are left to CI. Only the files above ran here. ## Acceptance notes - **Where the card's two failures surface.** The objectstack Console Pin Gate (`scripts/build-console.sh`) only runs builds: the console dependency closure, then the console. So the missing key is a type error in the app-shell `tsc` build, and that is what this unblocks. The objectstack-ai#3935 test and `_ReasonIsTheEnum` (`packages/data-objectstack/src/spec-symbol-batch6.test.ts`, a type-level pin of the exact union) are not reached by that gate. Both go red only on objectui's own spec bump to a release that declares the arm. At that bump, the tripwire above names both edits. `spec-symbol-batch6.test.ts` is outside this claim's file surface and is not touched. - **When a user first sees the sentence.** The adapter judges a reported reason against the installed spec's enum. Until the pin declares `stored_expression`, such a report from a newer server is still shown on the skew arm: "Not applied by the server: FIELDS". The changeset says so. Session: `https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB` --- _Generated by [Claude Code](https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB)_ Co-authored-by: Claude <noreply@anthropic.com>
…in with stored_expression subtracted (objectui#13309) (objectstack-ai#13311) Fixes objectstack-ai#13309 Clause-②: no ## What was red objectstack-ai/objectstack#24778 (merge `7532ea3c08`) added a fifth arm, `stored_expression`, to `DroppedFieldsEventSchema.reason`. The batch-6 pin `_ReasonIsTheEnum` in `packages/data-objectstack/src/spec-symbol-batch6.test.ts` stated the union as exactly four members, so objectstack's hourly Spec Shape Gate (objectui `main` compiled against the spec built from objectstack `main`) reports `TS2344` on it. ## The change The objectui#11206 shape, as the card prescribes: `_ReasonIsTheEnum` now asserts `Equal` of (`DroppedFieldsEvent['reason']` with `'stored_expression'` removed by `Exclude`) against `'readonly' | 'readonly_when' | 'primary_key' | 'computed'`. `_ReasonIsNotString` is kept unchanged. The comment above it says: the `Exclude` is a no-op at the pin; on objectstack `main` it subtracts exactly the new arm, so any other new arm is still a remainder `Equal` refuses; at the pin bump that carries `stored_expression` the `Exclude` is dropped and the union becomes five members (the objectui#13178 tripwire in `writeWarningToast.test.ts` fails on that bump to say so). No source file changed. `.objectstack-sha` did not move. A `.changeset/13309-dropped-fields-reason-pin.md` with an empty frontmatter is added because `check-changeset-presence` counts a test file under `src/` as published source (it exits 1 without it, measured). ## Measurements (test file blob identical at `7f58b8c400` and head `6ee70cebc3`; the head commit adds only the changeset) | spec compiled against | tree | `pnpm --filter @object-ui/data-objectstack run type-check` | |:--|:--|:--| | pinned spec (`.objectstack-sha` `06cab4849a`) | this branch | exit 0 | | pinned spec | base `2794254099` | exit 0 (control) | | spec built from objectstack `main` `28c9a2cb66` | base `2794254099` | exit 2, `TS2344` on `_ReasonIsTheEnum` (the card's red, reproduced) | | spec built from objectstack `main` `28c9a2cb66` | this branch | exit 0 | The `main` side reproduces the gate's own wiring: an objectstack worktree at `origin/main`, `pnpm install --frozen-lockfile --filter @objectstack/spec...`, `pnpm --filter @objectstack/spec build`, `npm pack --ignore-scripts`, then `node scripts/ci/spec-shape-gate.mjs inject` into this worktree (marker file present in the resolved copy, `stored_expression` in its declarations). The closure (`@object-ui/data-objectstack^...`) was rebuilt against it before the type-check. Afterwards the injected copy was removed and `pnpm run setup` reinstalled the pin (no marker left, no `stored_expression` in the installed declarations), and the pin row above was re-measured after a fresh closure rebuild. **Sixth-arm probe (one-shot, not kept):** a local alias `_ProbeReason` = the reason union plus `'sixth_arm_probe'` was put in place of `DroppedFieldsEvent['reason']` inside the `Exclude`. On-disk anchor count checked before compiling (1). Result: `TS2344` on `_ReasonIsTheEnum`, exit 2, against both the `main`-built spec and the pinned spec. Restored with `git checkout HEAD -- PATH` under an `EXIT INT TERM` trap; proof is the HEAD blob hash comparison (match) and `git diff HEAD` empty. ## Gates (local, at the branch head) - vitest, root form, `--maxWorkers=1`: `spec-symbol-batch6.test.ts` + `writeWarningToast.test.ts` 2 files / 36 tests passed; `deleteViewContract.types.test.ts` + `queryDataset.test.ts` (the other tests naming the file) 2 files / 38 tests passed. The full `packages/data-objectstack/` and `packages/app-shell/` directories are declared to CI. - `check:phantom-deps`, `check:self-import`, `check:unreferenced-sources`, `check:doc-example-readers`, `check:handler-key-reads`, `check:side-effects-array`, `check:element-data-source-declaration`, `check:spec-symbols`, `check:action-forward-parity`, `check:designer-field-key-parity`, `check:icon-record-names`, `check:i18n-keys`, `check:i18n-drift`, `check:i18n-designer-parity`, `check:control-bytes`, `scripts/check-hand-rolled-comment-mask.mjs`, `scripts/check-changeset-fixed.mjs`, `scripts/check-changeset-presence.mjs`: all exit 0. - `check:spec-floors`: exit 1, the known prerelease-floor reading for `@object-ui/types` on `main`; not touched here. - Lint, narrowed: `eslint --no-inline-config --format json` on the one changed `.ts` file: 1 file, 0 errors, 0 warnings. The package's `lint` is `eslint .`, so the file is in its population; type-aware linting is off (no `project` / `projectService` in the printed config), so this diff cannot move any untouched file's verdict. ## Acceptance notes - objectstack-ai/objectstack#25055 (the objectstack half) has PR objectstack-ai/objectstack#25091 open, not merged, registering this diagnostic in `scripts/ci/spec-shape-retirements.json` with this card as `downstream_card`. Once this lands, that entry reads "matched nothing in this run" and is deleted when this card closes. --- _Generated by [Claude Code](https://claude.ai/code/session_01XVp19E1MYUTFM3dnkwz8gB)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
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