You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
objectql 18.x: under strictReadonlyWrites, refuse a write whose value a stored expression replaces (stored_expression), the deferred half of #24690 #25000
Filed by domain:spec seat 3 (seat post #18883) · baozhoutao · session session_01D6F9axfoPzR7t8G1J7Ytzz. It carries the deferred half of #24690 under the standing ruling 「甲」 (#60156292005854, rule 2). ⛔ Not a claim. Grade and routing are triage's. Target: the next protocol major (19.0).
Why it is deferred
In 18.x, PR #24778 (7532ea3c08) ships the loud half. DroppedFieldsEvent.reason gains stored_expression, and objectql reports a caller's value that a stored expression replaced, through onFieldsDropped / droppedFields. Under strictReadonlyWrites such a write is reported, not refused. The strictReadonlyWrites contract says its coverage follows what onFieldsDropped reports, so the strict refusal is the narrowing half. It is runtime behaviour that no conversion carries.
19.0 scope
On insert (single, batch, insertMany per row), update by id and predicate update, a strict write whose stored_expression drop is non-empty is refused like the other strict drops.
Route the update paths' notifyDrop back through the strict assertion.
Have insert's strict check judge the stored-expression replacement too.
The wording already exists in objectql REASON_PHRASE.
Flip the strict tests in engine-stored-expression-dropped-fields from "reported, not refused" to "refused, nothing written".
Add a breaking changeset (FROM reported, TO refused) with Clause-②: no (narrowing).
Update the TSDoc in contracts/data-engine.ts and data-engine.zod.ts that names stored_expression as the one arm strict does not refuse in 18.x.
Dedupe
Dedupe words: strictReadonlyWrites stored_expression refusal 19.0. This is new: the 18.x half is #24690 (closed).
changed the title [-]objectql 19.0: under `strictReadonlyWrites`, refuse a write whose value a stored expression replaces (`stored_expression`), the deferred half of #24690[/-][+]objectql 18.x: under `strictReadonlyWrites`, refuse a write whose value a stored expression replaces (`stored_expression`), the deferred half of #24690[/+]on Oct 21, 2026
Claim: PM loop round 29
Session: session_01D6F9axfoPzR7t8G1J7Ytzz
Account: baozhoutao (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-25000-strict-stored-expression
Worktree: objectstack-issue-25000
Domain: domain:spec
Seat: domain:spec#3 (seat post #18883)
File surface (at origin/main688052e147 or later; stop on a breach and explain it in the report):
packages/objectql/src/engine.ts: the insert, batch, insertMany per row, update-by-id and predicate-update strict paths; a stored_expression drop is refused under strictReadonlyWrites;
packages/objectql/src/readonly-strict-errors.ts;
the strict tests in engine-stored-expression-dropped-fields;
the TSDoc in packages/spec/src/contracts/data-engine.ts and packages/spec/src/data/data-engine.zod.ts that names stored_expression as the arm strict does not refuse;
Read on origin/main688052e147 at 2026-10-21T06:40Z: readonly-strict-errors.ts says a stored_expression drop is reported, not refused, under strict, and engine.ts near line 16518 lets the strict path through on a stored_expression-only event set.
{
"issue": 25000,
"status": "done",
"branch": "claude/issue-25000-strict-stored-expression",
"pr": "https://github.com/objectstack-ai/objectstack/pull/25086",
"session": "session_01D6F9axfoPzR7t8G1J7Ytzz",
"premise_still_valid": true,
"summary": "Under strictReadonlyWrites, a write whose stored_expression replacement is non-empty is now refused with ERR_READONLY_FIELD_REJECTED and nothing is written, on insert (single, batch, insertMany, which refuses the whole call like the other strict drops), update by id and predicate update. Insert's strict check now judges insertDrops after the compute pass. Both update branches route the report through emitDrop and run their single assertNoStrictDrops() after the stored-expression pass, so one refusal names every strip drop plus the replacement (stored_expression last). An object with no stored expression skips that pass and refuses exactly where it did before. REASON_PHRASE.stored_expression is reused, and the per-reason message gains a stored_expression remedy that is spoken only when the reason is present (other messages stay byte-identical). Spec TSDoc (contracts/data-engine.ts, data-engine.zod.ts) and the objectql comments are updated, and check:generated reports all 14 artifacts current. Changeset: objectql minor, BREAKING FROM reported TO refused, the claim's Clause-② line, ADR-0087 not-required (no-migration-prescription). Premise held on origin/main 688052e147. Producers: no non-test caller in the repo passes strictReadonlyWrites, so no producer moved.",
"tests": "All runs on head a8e9d3611c. objectql: `pnpm --filter @objectstack/objectql test` gives Test Files 455 passed, Tests 8569 passed. `typecheck`: tsc clean, and check:test-typecheck OK (39 files / 233 errors / 64 pinned signatures held). spec: build, then `check:generated` (All 14 generated artifacts are up to date). `spec test`: 705 files / 21424 passed, 1 todo. `spec typecheck`: OK. `spec test:repo`: 72 files / 1237 passed. dispatch-gates --commands (no paths, merge base 688052e14, repo objectstack-ai/objectstack at a8e9d3611c) derived 94 commands, and all 94 ran. First pass: 92 exited 0, while check:dual-build-cjs-loads and check:type-check-debt gave PREREQUISITE NOT MET (exit 3). Re-ran check:type-check-debt under the verify lock: 'none above its recorded number', VERDICT command-exit 0. That build produced the missing dist trees, and check:dual-build-cjs-loads then passed (112 entry points / 69 packages load). `dispatch-gates --ran` with exit codes: 94 derived, 94 run, 0 NOT-MEASURED, 0 UNRUN. Changeset gates green: check-adr-0087-registration names the changeset as BREAKING+bang+clause-②-narrowing, not-required (no-migration-prescription); check-changeset-no-major, check-empty-changeset and check:changeset-gate-self-tests pass. Ablation, done after committing the fix, through scripts/ablation-replace.mjs; the test file imports ./engine.js from source, so no build or dist is involved. Leg A: the anchor `if (strictReadonlyWrites) {` in emitDrop was made to exempt stored_expression (anchor 1 to 0, blob 3e8c0ec2bd1a to ca26fe6c7305). Result: 3 failed / 22 passed (update by id refusal, predicate refusal, union case). Restored: blob equals HEAD 3e8c0ec2bd1a and git diff HEAD is empty. Leg B: the insert strict check was made to ignore a stored_expression-only list (blob to f7a0c618b6a9). Result: 3 failed / 22 passed (insert single, batch, insertMany). Restored the same way, with the same proof. Direction: turned red, as expected. integration/dogfood: not applicable locally (not a cli card), so declared to CI. The PR was not merged with origin/main (7 commits ahead, engine.ts moved there). Local `git merge-tree` exits 0, and engine.ts is not merge=os-regen routed. The CI merge ref validates the joint tree.",
"mcp_calls": "0",
"api_writes": "1 relay stroke (repository_dispatch POST /repos/objectstack-ai/objectstack/dispatches, run 39571931001) carrying pr_create, which executed POST /repos/objectstack-ai/objectstack/pulls (draft) and POST /repos/objectstack-ai/objectstack/issues/25086/assignees [baozhoutao]; plus 1 relay stroke for this os-dev-report comment (POST /repos/objectstack-ai/objectstack/issues/25000/comments). git push is not counted. Labels: zero writes, because the dispatch named none and skip-changeset does not apply (objectql publishes).",
"open_questions": [
{
"question": "The pending .changeset/24690-stored-expression-dropped-fields.md (in the open Version Packages PR #24876) still says 'strictReadonlyWrites is unchanged ... deferred to 19.0'. If it releases alongside this change, the CHANGELOG states the opposite of what ships. That file is outside the claimed file surface (.changeset/25000-*.md only), so it is untouched here.",
"options": [
"A: the seat amends that bullet in this PR (one-line: 'Under strictReadonlyWrites such a write is refused; see the strict stored_expression refusal entry'), widening the file surface",
"B: leave it; the new changeset already says it supersedes that bullet",
"C: a docs-only follow-up after release amends the published CHANGELOG entry"
],
"recommendation": "A, because both changesets are still unreleased and an amendment now costs one line, where after release the correction must go through a dedicated docs-only PR on the shipped CHANGELOG entry."
}
],
"out_of_scope_findings": [
"class: b · reach: exception: release-text (the pending changeset 24690-stored-expression-dropped-fields.md contradicts the behaviour this PR ships if both release together; see open_questions) · Seam: spec:WriteObservabilityOptions.strictReadonlyWrites → runtime:ObjectQL.insert/update strict check · evidence: the .changeset/24690 bullet 'strictReadonlyWrites is unchanged ... deferred to 19.0' versus this PR's refusal · dedupe words: 24690 changeset strict stored_expression deferred 19.0",
"carrier: content/docs/data-modeling/validation-rules.mdx line 368 says a value sent for a stored expression field is 'not refused and not reported as dropped'. The 'not reported' half has been false since #24690, and under strict the 'not refused' half is now false too. Hand-written doc, outside the claim surface. Carrier (承接者): none named, so this is noted in the PR Acceptance notes and not filed."
]
}
os-dev-report
Patch round (contract-review blocker), head 54a1cf3.
{
"issue": 25000,
"status": "done",
"branch": "claude/issue-25000-strict-stored-expression",
"pr": "https://github.com/objectstack-ai/objectstack/pull/25086",
"session": "session_01D6F9axfoPzR7t8G1J7Ytzz",
"premise_still_valid": true,
"summary": "Patch round for the contract-review blocker. The strict refusal of a strip's drops again comes before a stored-expression pass that throws. On the predicate path, the readPriorRows / compute / settleBulkStoredExpressions pass is wrapped: on any throw, assertNoStrictDrops() runs first, then the error is rethrown. A strict predicate update that carries a strip drop and moves an input to divergent computed values therefore answers ERR_READONLY_FIELD_REJECTED, not VALIDATION_FAILED, which is #5126's ordering as on origin/main. The by-id compute and the insert compute get the same guard: insert judges the strips' drops before rethrowing. When no throw occurs, one refusal still names every strip drop plus the replacement. New pin, with a non-strict control that answers VALIDATION_FAILED. The changeset adds '@objectstack/spec': patch (the TSDoc ships in d.ts) and declares the kept ordering. Only the 25000 changeset was edited. origin/main was merged twice: e57d7801bf, then 54a1cf34b6. Final head 54a1cf34b6.",
"tests": "All runs on head 54a1cf34b6. Closure build: `pnpm --filter '@objectstack/objectql^...' --filter @objectstack/objectql build`, VERDICT command-exit 0. `pnpm --filter @objectstack/objectql test`: Test Files 458 passed, Tests 8620 passed. objectql typecheck: tsc clean, and check:test-typecheck OK (39/233/64 held). `spec check:generated`: All 14 generated artifacts are up to date. spec typecheck: OK (52/242/132 held). dispatch-gates --commands at 54a1cf34b6 derived 94 commands, and all 94 ran. check:type-check-debt re-measured OK under the lock. check:dual-build-cjs-loads gave exit 3 (no dist) on the first pass, then passed after the full build (112 entry points / 69 packages). `dispatch-gates --ran`: 94 derived, 94 run, 0 NOT-MEASURED, 0 UNRUN. check-adr-0087-registration: 1 declared-breaking changeset with its disposition; check-changeset-no-major, check-empty-changeset and check:changeset-gate-self-tests exit 0. New ordering test: 'predicate update: a strip's drop is refused BEFORE a divergent settle — ERR_READONLY_FIELD_REJECTED, not VALIDATION_FAILED (#5126 ordering)', plus its non-strict control. Targeted run: 2 files / 41 passed. Ablation C, after commit 0e9eba5e78, through scripts/ablation-replace.mjs: the predicate catch's assertNoStrictDrops() was replaced with a marker (anchor 1 to 0, blob 4ebb6f6ed412 to 443ab016f98a). Result: 1 failed (exactly the ordering pin) / 26 passed. Restored: blob equals HEAD 4ebb6f6ed412 and git diff HEAD is empty. A first attempt was refused by the tool as a no-op, because its replacement was a substring of its anchor, so no test ran; it was re-run with a distinct replacement. Ablations A and B from round 1 stand.",
"mcp_calls": "0",
"api_writes": "Round 2: 1 relay stroke for this follow-up comment (POST /repos/objectstack-ai/objectstack/issues/25000/comments). git pushes of 0e9eba5e78 and 54a1cf34b6 are not counted. No PR body or label writes. If the PR body's 'Tests' section needs the new numbers, the seat writes them: a dev does not PATCH the body.",
"open_questions": [],
"out_of_scope_findings": []
}
Patch-id verified: the merged commit's patch-id equals the reviewed head 54a1cf34b6 (632e2b7746).
Contract review: PASS on round 2, record 6315090651 on the PR. Round 1 failed on one ordering blocker, which the dev fixed in 0e9eba5e78.
What shipped: under strictReadonlyWrites, a write whose value a stored field's expression replaces is refused with ERR_READONLY_FIELD_REJECTED, and nothing is written. This covers insert (single, batch and insertMany), update by id and predicate update. A strict strip refusal still comes before any stored-expression pass failure ([决策] readonly 剥离的 strict/reject 模式落在哪一层 —— B(WriteObservabilityOptions)推荐,A/C/D 各有代价(#4903 后续) #5126 ordering). @objectstack/objectql is a BREAKING minor, and @objectstack/spec is a TSDoc patch.
The pending .changeset/24690-*.md still says strict is "deferred to 19.0". Correcting it is held for the maintainer, together with the seat's other pending release-note corrections.
The claim on this card ends here. pm:dispatched removed.
Activity
[-]objectql 19.0: under `strictReadonlyWrites`, refuse a write whose value a stored expression replaces (`stored_expression`), the deferred half of #24690[/-][+]objectql 18.x: under `strictReadonlyWrites`, refuse a write whose value a stored expression replaces (`stored_expression`), the deferred half of #24690[/+]objectstack-fleet commentedon Oct 21, 2026
Re-timed to 18.x:
pm:queue, p2. Triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-21T01:37Z6303865025): 「暂时不考虑 v19,协议的调整原则上也是尽快,不考虑退役版本」, then confirmed triage's re-timing list in the triage seat's chat (「18.x 收回清单 同意」).DroppedFieldsEvent.reasonstored_expression). ThestrictReadonlyWritesrefusal now ships in 18.x as a BREAKING minor. No card waits on a 19.0 line.Restart-when:for 19.0 no longer applies.Generated by Claude Code
objectstack-fleet commentedon Oct 21, 2026
Claim: PM loop round 29
Session:
session_01D6F9axfoPzR7t8G1J7YtzzAccount:
baozhoutao(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-25000-strict-stored-expressionWorktree:
objectstack-issue-25000Domain:
domain:specSeat:
domain:spec#3(seat post #18883)File surface (at
origin/main688052e147or later; stop on a breach and explain it in the report):packages/objectql/src/engine.ts: the insert, batch,insertManyper row, update-by-id and predicate-update strict paths; astored_expressiondrop is refused understrictReadonlyWrites;packages/objectql/src/readonly-strict-errors.ts;engine-stored-expression-dropped-fields;packages/spec/src/contracts/data-engine.tsandpackages/spec/src/data/data-engine.zod.tsthat namesstored_expressionas the arm strict does not refuse;.changeset/25000-*.md.Container & model:
M,mode:subagent,model: default (no path-derived mandate)Clause-②: no (narrowing: under
strictReadonlyWrites, a write whose value a stored expression replaces is refused instead of reported; it ships now under the amended 「甲」, [PM seat] triage (objectstack-wide) — 🟢 Routine · session_01AavokzJ5DndAwitDXvKy4U · trig_01NcnCtMS2tH46nUg1a5TaMJ (hourly) · state = body + the round records newer than it #60156303865025)Responsibility:
the deferred half of #24690, re-timed to 18.x by triage (6305854988)Thread-read: 6305854988
Serial constraints cleared: PR feat(spec,objectql): report a caller value a stored expression replaced, as DroppedFieldsEvent reason stored_expression #24778 (
7532ea3c08) shipped the reported half. The dev re-checks open PRs touchingengine.ts/data-engine.zod.tsbefore opening its own.Read on
origin/main688052e147at 2026-10-21T06:40Z:readonly-strict-errors.tssays astored_expressiondrop is reported, not refused, under strict, andengine.tsnear line 16518 lets the strict path through on astored_expression-only event set.objectstack-fleet commentedon Oct 21, 2026
os-dev-report
{ "issue": 25000, "status": "done", "branch": "claude/issue-25000-strict-stored-expression", "pr": "https://github.com/objectstack-ai/objectstack/pull/25086", "session": "session_01D6F9axfoPzR7t8G1J7Ytzz", "premise_still_valid": true, "summary": "Under strictReadonlyWrites, a write whose stored_expression replacement is non-empty is now refused with ERR_READONLY_FIELD_REJECTED and nothing is written, on insert (single, batch, insertMany, which refuses the whole call like the other strict drops), update by id and predicate update. Insert's strict check now judges insertDrops after the compute pass. Both update branches route the report through emitDrop and run their single assertNoStrictDrops() after the stored-expression pass, so one refusal names every strip drop plus the replacement (stored_expression last). An object with no stored expression skips that pass and refuses exactly where it did before. REASON_PHRASE.stored_expression is reused, and the per-reason message gains a stored_expression remedy that is spoken only when the reason is present (other messages stay byte-identical). Spec TSDoc (contracts/data-engine.ts, data-engine.zod.ts) and the objectql comments are updated, and check:generated reports all 14 artifacts current. Changeset: objectql minor, BREAKING FROM reported TO refused, the claim's Clause-② line, ADR-0087 not-required (no-migration-prescription). Premise held on origin/main 688052e147. Producers: no non-test caller in the repo passes strictReadonlyWrites, so no producer moved.", "tests": "All runs on head a8e9d3611c. objectql: `pnpm --filter @objectstack/objectql test` gives Test Files 455 passed, Tests 8569 passed. `typecheck`: tsc clean, and check:test-typecheck OK (39 files / 233 errors / 64 pinned signatures held). spec: build, then `check:generated` (All 14 generated artifacts are up to date). `spec test`: 705 files / 21424 passed, 1 todo. `spec typecheck`: OK. `spec test:repo`: 72 files / 1237 passed. dispatch-gates --commands (no paths, merge base 688052e14, repo objectstack-ai/objectstack at a8e9d3611c) derived 94 commands, and all 94 ran. First pass: 92 exited 0, while check:dual-build-cjs-loads and check:type-check-debt gave PREREQUISITE NOT MET (exit 3). Re-ran check:type-check-debt under the verify lock: 'none above its recorded number', VERDICT command-exit 0. That build produced the missing dist trees, and check:dual-build-cjs-loads then passed (112 entry points / 69 packages load). `dispatch-gates --ran` with exit codes: 94 derived, 94 run, 0 NOT-MEASURED, 0 UNRUN. Changeset gates green: check-adr-0087-registration names the changeset as BREAKING+bang+clause-②-narrowing, not-required (no-migration-prescription); check-changeset-no-major, check-empty-changeset and check:changeset-gate-self-tests pass. Ablation, done after committing the fix, through scripts/ablation-replace.mjs; the test file imports ./engine.js from source, so no build or dist is involved. Leg A: the anchor `if (strictReadonlyWrites) {` in emitDrop was made to exempt stored_expression (anchor 1 to 0, blob 3e8c0ec2bd1a to ca26fe6c7305). Result: 3 failed / 22 passed (update by id refusal, predicate refusal, union case). Restored: blob equals HEAD 3e8c0ec2bd1a and git diff HEAD is empty. Leg B: the insert strict check was made to ignore a stored_expression-only list (blob to f7a0c618b6a9). Result: 3 failed / 22 passed (insert single, batch, insertMany). Restored the same way, with the same proof. Direction: turned red, as expected. integration/dogfood: not applicable locally (not a cli card), so declared to CI. The PR was not merged with origin/main (7 commits ahead, engine.ts moved there). Local `git merge-tree` exits 0, and engine.ts is not merge=os-regen routed. The CI merge ref validates the joint tree.", "mcp_calls": "0", "api_writes": "1 relay stroke (repository_dispatch POST /repos/objectstack-ai/objectstack/dispatches, run 39571931001) carrying pr_create, which executed POST /repos/objectstack-ai/objectstack/pulls (draft) and POST /repos/objectstack-ai/objectstack/issues/25086/assignees [baozhoutao]; plus 1 relay stroke for this os-dev-report comment (POST /repos/objectstack-ai/objectstack/issues/25000/comments). git push is not counted. Labels: zero writes, because the dispatch named none and skip-changeset does not apply (objectql publishes).", "open_questions": [ { "question": "The pending .changeset/24690-stored-expression-dropped-fields.md (in the open Version Packages PR #24876) still says 'strictReadonlyWrites is unchanged ... deferred to 19.0'. If it releases alongside this change, the CHANGELOG states the opposite of what ships. That file is outside the claimed file surface (.changeset/25000-*.md only), so it is untouched here.", "options": [ "A: the seat amends that bullet in this PR (one-line: 'Under strictReadonlyWrites such a write is refused; see the strict stored_expression refusal entry'), widening the file surface", "B: leave it; the new changeset already says it supersedes that bullet", "C: a docs-only follow-up after release amends the published CHANGELOG entry" ], "recommendation": "A, because both changesets are still unreleased and an amendment now costs one line, where after release the correction must go through a dedicated docs-only PR on the shipped CHANGELOG entry." } ], "out_of_scope_findings": [ "class: b · reach: exception: release-text (the pending changeset 24690-stored-expression-dropped-fields.md contradicts the behaviour this PR ships if both release together; see open_questions) · Seam: spec:WriteObservabilityOptions.strictReadonlyWrites → runtime:ObjectQL.insert/update strict check · evidence: the .changeset/24690 bullet 'strictReadonlyWrites is unchanged ... deferred to 19.0' versus this PR's refusal · dedupe words: 24690 changeset strict stored_expression deferred 19.0", "carrier: content/docs/data-modeling/validation-rules.mdx line 368 says a value sent for a stored expression field is 'not refused and not reported as dropped'. The 'not reported' half has been false since #24690, and under strict the 'not refused' half is now false too. Hand-written doc, outside the claim surface. Carrier (承接者): none named, so this is noted in the PR Acceptance notes and not filed." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 21, 2026
os-dev-report
Patch round (contract-review blocker), head 54a1cf3.
{ "issue": 25000, "status": "done", "branch": "claude/issue-25000-strict-stored-expression", "pr": "https://github.com/objectstack-ai/objectstack/pull/25086", "session": "session_01D6F9axfoPzR7t8G1J7Ytzz", "premise_still_valid": true, "summary": "Patch round for the contract-review blocker. The strict refusal of a strip's drops again comes before a stored-expression pass that throws. On the predicate path, the readPriorRows / compute / settleBulkStoredExpressions pass is wrapped: on any throw, assertNoStrictDrops() runs first, then the error is rethrown. A strict predicate update that carries a strip drop and moves an input to divergent computed values therefore answers ERR_READONLY_FIELD_REJECTED, not VALIDATION_FAILED, which is #5126's ordering as on origin/main. The by-id compute and the insert compute get the same guard: insert judges the strips' drops before rethrowing. When no throw occurs, one refusal still names every strip drop plus the replacement. New pin, with a non-strict control that answers VALIDATION_FAILED. The changeset adds '@objectstack/spec': patch (the TSDoc ships in d.ts) and declares the kept ordering. Only the 25000 changeset was edited. origin/main was merged twice: e57d7801bf, then 54a1cf34b6. Final head 54a1cf34b6.", "tests": "All runs on head 54a1cf34b6. Closure build: `pnpm --filter '@objectstack/objectql^...' --filter @objectstack/objectql build`, VERDICT command-exit 0. `pnpm --filter @objectstack/objectql test`: Test Files 458 passed, Tests 8620 passed. objectql typecheck: tsc clean, and check:test-typecheck OK (39/233/64 held). `spec check:generated`: All 14 generated artifacts are up to date. spec typecheck: OK (52/242/132 held). dispatch-gates --commands at 54a1cf34b6 derived 94 commands, and all 94 ran. check:type-check-debt re-measured OK under the lock. check:dual-build-cjs-loads gave exit 3 (no dist) on the first pass, then passed after the full build (112 entry points / 69 packages). `dispatch-gates --ran`: 94 derived, 94 run, 0 NOT-MEASURED, 0 UNRUN. check-adr-0087-registration: 1 declared-breaking changeset with its disposition; check-changeset-no-major, check-empty-changeset and check:changeset-gate-self-tests exit 0. New ordering test: 'predicate update: a strip's drop is refused BEFORE a divergent settle — ERR_READONLY_FIELD_REJECTED, not VALIDATION_FAILED (#5126 ordering)', plus its non-strict control. Targeted run: 2 files / 41 passed. Ablation C, after commit 0e9eba5e78, through scripts/ablation-replace.mjs: the predicate catch's assertNoStrictDrops() was replaced with a marker (anchor 1 to 0, blob 4ebb6f6ed412 to 443ab016f98a). Result: 1 failed (exactly the ordering pin) / 26 passed. Restored: blob equals HEAD 4ebb6f6ed412 and git diff HEAD is empty. A first attempt was refused by the tool as a no-op, because its replacement was a substring of its anchor, so no test ran; it was re-run with a distinct replacement. Ablations A and B from round 1 stand.", "mcp_calls": "0", "api_writes": "Round 2: 1 relay stroke for this follow-up comment (POST /repos/objectstack-ai/objectstack/issues/25000/comments). git pushes of 0e9eba5e78 and 54a1cf34b6 are not counted. No PR body or label writes. If the PR body's 'Tests' section needs the new numbers, the seat writes them: a dev does not PATCH the body.", "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
fix(objectql)!: under strictReadonlyWrites, a write whose value a sto…
objectstack-fleet commentedon Oct 21, 2026
Close-out — landed
03c12a52ef(single-parent squash onmain).54a1cf34b6(632e2b7746).6315090651on the PR. Round 1 failed on one ordering blocker, which the dev fixed in0e9eba5e78.strictReadonlyWrites, a write whose value a stored field'sexpressionreplaces is refused withERR_READONLY_FIELD_REJECTED, and nothing is written. This covers insert (single, batch andinsertMany), update by id and predicate update. A strict strip refusal still comes before any stored-expression pass failure ([决策] readonly 剥离的 strict/reject 模式落在哪一层 —— B(WriteObservabilityOptions)推荐,A/C/D 各有代价(#4903 后续) #5126 ordering).@objectstack/objectqlis a BREAKING minor, and@objectstack/specis a TSDoc patch.validation-rules.mdx:368and thedata-engine.mdxstrip table..changeset/24690-*.mdstill says strict is "deferred to 19.0". Correcting it is held for the maintainer, together with the seat's other pending release-note corrections.pm:dispatchedremoved.