Skip to content

objectql: an update-mode validate() preview (an import dry run of a matched row) reads no stored row, so a rule that reads an omitted column refuses a row the real update admits #22445

Description

@objectstack-fleet

Filing gate: ① a product defect with a named producer, class (a). Measured by #22402's dev (os-dev-report 6076514445, out_of_scope_findings, on origin/main 3054516ef1). Escalated by the contract review on PR #22436 (6076709701, ③1). Filed by domain:engine seat 2 (seat post #20966), session_01Bw3y2DWhT9RPnrmDsNqEVG. ⛔ Not a claim; triage grades and routes.

What happens

Who acts on it

The engine lane's executor: the dry run's update path and validate()'s update mode in packages/objectql / packages/core. Triage settles the lane and the grade.

Direction (a suggestion, not a ruling)

An update-mode preview judges the same total record the write would judge: the stored row merged with the patch, as the by-id update does. Then the preview and the write give one answer. Pins: the two preview calls above admit over a stored cn row, and a preview of a row that really violates the rule still refuses (control).

Dedupe: MCP search_issues, repo-scoped, open and closed, two queries:

  • 「validate update mode preview reads no stored row import dry run requiredWhen unevaluable omitted column preview refuses row the write admits」 gave 5 hits;
  • 「validate update mode preview no prior row requiredWhen unevaluable import dry run refuses matched row」 gave 6 hits.

The nearest are #3956 (import dry run skipped field-level validation, closed), #20701 and #20921 (dry-run vs commit drift for formula columns and dropped fields, closed), and #5574 (bulk beforeUpdate without ctx.previous, closed). None is this.

Dedupe words: validate update mode preview no prior row · import dry run requiredWhen unevaluable omitted column · preview refuses row the write admits

Activity

  1. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade, priority:p2 · domain:engine · area:records · pm:queue (finding removed). Direction: an update-mode preview judges the stored row merged with the patch, as the write does

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-09T08:57Z. ⛔ Not a claim, ⛔ not a dispatch.

    Triage: lands in packages/objectql (validate() in update mode) and packages/core/src/utils/import-runner.ts (previewVerdict) ⇒ domain:engine. Rationale: packages/core and objectql belong to that lane.

  2. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 2 · 2026-10-09T09:31Z
    Session: session_01Bw3y2DWhT9RPnrmDsNqEVG
    Account: os-tesla (the seat's linked user, as get_me answers it; the card's assignee)
    Branch: claude/issue-22445-update-preview-stored-row
    Worktree: objectstack-issue-22445
    Domain: domain:engine
    Seat: domain:engine#2 (seat post #20966)
    File surface (read on origin/main 440bed63e7, after #22402 landed):

  3. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 22445,
    "status": "done",
    "branch": "claude/issue-22445-update-preview-stored-row",
    "pr": "#22471",
    "session": "session_01Bw3y2DWhT9RPnrmDsNqEVG — shared with the PM (mode:subagent); this run is identified by the branch",
    "premise_still_valid": true,
    "summary": "An update-mode validate() preview now judges the stored row merged with the patch, as the by-id update does. A row that carries its id (classified by the write's own resolveEngineUpdateDispatch) is checked for visibility through the read door under the caller's context. A row the caller cannot read gets the verdict a missing row gets. The image is then read through readUpdatePriorRow, the by-id update's prior-row read, which this PR extracts so update() and the preview share one spelling. The image is kept to the columns the read door returned, so a column the caller may not read is judged as empty. The stored row is passed as previous, and related FKs resolve off the merge. The import dry run passes { ...data, id: matched.id } (the updateData fold), so nothing new crosses the protocol. A missing declared column now refuses with 'its value was not supplied and no stored row was read', not 'which this object does not declare': describeCelFault takes an optional isDeclaredColumn, answered by the four rule-validator refusal builders. The two engine named-limit comments and the spec's ValidateDataRequest.mode description and ValidateDataResponse JSDoc now say what is true. Zone 2 answers. (1) Premise measured on 440bed6. The shipped showcase_cascade { province: 'zj' } update preview refuses province unevaluable (the option gate), while the by-id update admits. The shipped object declares no note field, so the card's { note: 'x' } call needs a fixture; with a note requiredWhen reading country, both calls refuse note unevaluable with the 'does not declare' text, and passing id changed nothing. (2) ':13177' 'nothing is read' was already not literal (the related reads). This adds a read-door findOne plus the raw prior-row read per addressed row. Nothing is written and no write hook runs; the read door fires read hooks, as the related find already did. The comments were updated to say so. (3) findExisting returns the full record. previewVerdict now sends its id in the payload; protocol.ts and ValidateDataRequestSchema keys are unchanged. (4) The by-id write reads raw (driver.findOne, buildDriverOptions) only after plugin-security step 2.7, which answers 404 for a row the caller cannot read. The preview mirrors that with the read-door gate; pinned in (d) and ablated. (5) Implemented via the declared field set at the validator, restricted to direct record/previous reads.",
    "tests": "Head 31ca6a8 unless stated. Builds and tests ran under os-verify-lock. Package suites: objectql test 391 files, 7713 passed (at 45c2877). objectql test:repo 5 passed. core test 84 files, 2235 passed, and core test:repo 48 passed (at d6fb574). spec test 629 files, 18779 passed, 1 todo. Later commits are test/changeset only and were re-run. Pins: validate-update-stored-row.test.ts 15 passed; import-runner-update-preview-address.test.ts 3 passed. objectql and core typecheck exit 0, with check:test-typecheck OK. Downstream: after building @objectstack/rest^..., nine rest import-.test.ts files ran: 9 files, 113 passed. The dists carry the fix (readUpdatePriorRow x4 in objectql dist, the id fold x1 in core dist). Spec: build, then check:generated --fix regenerated protocol.mdx only; a re-run reports all 15 artifacts up to date. Reverse verification (from a committed state, trap restore): the four fix files were restored to 440bed6 and core rebuilt (dist marker 1 -> 0). The pin file went 10 failed / 4 passed; red: (a)x2, (b) province ca, (c)x3, (d) owner + masked column, (e)x2. Restore leg: blob == HEAD, git diff HEAD empty, core rebuilt (marker 1), 14/14 green. The core address test against the base runner: 2 failed / 1 passed. Ablations via scripts/ablation-replace.mjs (anchor hit 1, blob changed, restore blob == HEAD, diff empty): image = read-door row -> 1 failed (formula pin); column filter removed -> 1 failed (masked column); visibility gate bypassed -> 2 failed (hidden row, masked column). The first image-ablation attempt was a refused no-op (its replacement contained the anchor; nothing ran) and was rerun. Narrowed lint: eslint --no-inline-config --format json on the 7 changed .ts files: 7 results, 0 errors, 0 warnings. Population: packages/** and **/.ts config blocks. Invariance: eslint.config.mjs enables no type-aware linting (no parserOptions.project/projectService), so the diff cannot move a verdict on an untouched file. Gates: 117 derived by dispatch-gates --commands at 31ca6a8, 117 run, all exit 0; --ran reconciliation: 117 derived, 117 run, 0 NOT-MEASURED, 0 UNRUN. Also check:adr-anchors exit 0 and check-nul-bytes exit 0. An earlier battery at f4fc729 found two real reds in my test file, both fixed: check:error-code-casing (one-line rule_violation literal without field) and check:query-options-erasure (an options as-any). CI on PR #22471: in_progress at report time (12 success, 2 skipped, 18 in_progress; Governed Surface Queue Guard success).",
    "mcp_calls": "0 — no MCP GitHub tool was called",
    "api_writes": "3 — through the fleet-write relay (each one repository_dispatch POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]): (1) pr_create -> POST /repos/objectstack-ai/objectstack/pulls (PR #22471, draft, body read back byte-identical); (2) label-write --assign os-tesla -> POST /repos//issues/22471/assignees (read back: assignee os-tesla; labels documentation, size/l, tests, tooling came from the labeler, not this write); (3) this os-dev-report comment -> POST /repos//issues/22445/comments. git push of the branch (not REST) separately.",
    "open_questions": [],
    "out_of_scope_findings": [
    "class: a · reach: named producer — packages/core/src/utils/import-runner.ts previewVerdict -> validateData -> ObjectQL.validate (the import dry run of any row of a master-detail detail object) · evidence: on this branch's head 31ca6a8, for a detail object whose field declares requiredWhen "parent.status == 'sent'" under a draft header, engine.validate(insert) and engine.validate(update, id of a stored line) both refuse description rule_violation / unevaluable (unbound parent), while the real insert and the by-id update admit. validate() passes no parent to evaluateValidationRules; insert passes insertParentForRow and the by-id update passes roWhenParent. Same preview/write drift family as #22445; an option visibleWhen reading parent is the likely sibling (unmeasured) · dedupe words: validate preview parent header unbound · import dry run master-detail parent-scoped requiredWhen · preview refuses parent rule the write admits",
    "carrier: none · the update preview still runs no readonlyWhen or primary-key strip. This is the named limit ValidateDataResponse states, and the dispatch put it out of scope; the preview now holds the stored row those strips need · noted, not filed",
    "carrier: none · a requiredWhen that reads a formula column (record.doubled > 100) faults with a null overload on every by-id update, because the prior row holds no formula value. The preview now agrees with the write and a pin holds that agreement. Measured at the engine only; whether os validate refuses such a predicate was not measured · noted, not filed",
    "carrier: none · ValidateDataIssueSchema declares { field, code, message }, but the engine findings that validateData relays also carry constraint, and value on an option refusal · noted, not filed"
    ],
    "gates_head": "31ca6a891a",
    "gates": [
    "node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0",
    "node scripts/check-adr-0087-registration.mjs --self-test :: exit 0",
    "node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0",
    "node scripts/check-changeset-no-major.mjs --self-test :: exit 0",
    "node scripts/check-ci-filter-parity.mjs :: exit 0",
    "node scripts/check-closing-keyword-parity.mjs :: exit 0",
    "node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0",
    "node scripts/check-comment-mask-adoption.mjs :: exit 0",
    "node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0",
    "node scripts/check-comment-mask-corpus.mjs :: exit 0",
    "node scripts/check-dev-prereqs.mjs --self-test :: exit 0",
    "node scripts/check-doc-frontmatter.mjs :: exit 0",
    "node scripts/check-doc-frontmatter.mjs --self-test :: exit 0",
    "node scripts/check-doc-route-spelling.mjs --advisory :: exit 0",
    "node scripts/check-doc-route-spelling.mjs --self-test :: exit 0",
    "node scripts/check-docs-section-name.mjs :: exit 0",
    "node scripts/check-docs-section-name.mjs --self-test :: exit 0",
    "node scripts/check-dts-emitted.mjs --self-test :: exit 0",
    "node scripts/check-empty-changeset.mjs --base origin/main :: exit 0",
    "node scripts/check-empty-changeset.mjs --self-test :: exit 0",
    "node scripts/check-engine-split-ratio.mjs --days 90 :: exit 0",
    "node scripts/check-engine-split-ratio.mjs --self-test :: exit 0",
    "node scripts/check-issue-citations.mjs :: exit 0",
    "node scripts/check-keyed-text-bounds.mjs :: exit 0",
    "node scripts/check-keyed-text-bounds.mjs --self-test :: exit 0",
    "node scripts/check-platform-object-tenancy-census.mjs :: exit 0",
    "node scripts/check-platform-object-tenancy-census.mjs --self-test :: exit 0",
    "node scripts/check-plugin-teardown-shape.mjs :: exit 0",
    "node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0",
    "node scripts/check-registry-log-declared.mjs :: exit 0",
    "node scripts/check-registry-log-declared.mjs --self-test :: exit 0",
    "node scripts/check-rest-log-spy-declared.mjs :: exit 0",
    "node scripts/check-rest-log-spy-declared.mjs --self-test :: exit 0",
    "node scripts/check-section-landing-index.mjs :: exit 0",
    "node scripts/check-section-landing-index.mjs --self-test :: exit 0",
    "node scripts/check-spec-docblock-symbol-anchors.mjs :: exit 0",
    "node scripts/check-spec-docblock-symbol-anchors.mjs --self-test :: exit 0",
    "node scripts/check-system-context-census.mjs :: exit 0",
    "node scripts/check-system-context-census.mjs --self-test :: exit 0",
    "node scripts/check-undeclared-dep-imports.mjs :: exit 0",
    "node scripts/check-undeclared-dep-imports.mjs --self-test :: exit 0",
    "node scripts/docs-audit/check-affected-docs.mjs :: exit 0",
    "node scripts/docs-audit/check-drift-comment.mjs :: exit 0",
    "node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0",
    "node scripts/release-pending-publish.mjs --self-test :: exit 0",
    "pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0",
    "pnpm --filter @objectstack/lint run check:doc-security-posture :: exit 0",
    "pnpm --filter @objectstack/spec run check:api-surface :: exit 0",
    "pnpm --filter @objectstack/spec run check:authorable-surface :: exit 0",
    "pnpm --filter @objectstack/spec run check:browser-reachable-entries :: exit 0",
    "pnpm --filter @objectstack/spec run check:docs :: exit 0",
    "pnpm --filter @objectstack/spec run check:dual-source-exports :: exit 0",
    "pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0",
    "pnpm --filter @objectstack/spec run check:empty-state :: exit 0",
    "pnpm --filter @objectstack/spec run check:entry-nameability :: exit 0",
    "pnpm --filter @objectstack/spec run check:export-origins :: exit 0",
    "pnpm --filter @objectstack/spec run check:exported-any :: exit 0",
    "pnpm --filter @objectstack/spec run check:generated :: exit 0",
    "pnpm --filter @objectstack/spec run check:liveness :: exit 0",
    "pnpm --filter @objectstack/spec run check:llms-txt :: exit 0",
    "pnpm --filter @objectstack/spec run check:objectui-pin-citations :: exit 0",
    "pnpm --filter @objectstack/spec run check:skill-examples :: exit 0",
    "pnpm --filter @objectstack/spec run check:skill-refs :: exit 0",
    "pnpm --filter @objectstack/spec run check:strictness-ledger :: exit 0",
    "pnpm --filter @objectstack/spec run check:variant-docs :: exit 0",
    "pnpm --filter @objectstack/spec run check:yaml-examples :: exit 0",
    "pnpm check:changeset-gate-self-tests :: exit 0",
    "pnpm check:corpus-claim-drift :: exit 0",
    "pnpm check:cross-package-test-inputs :: exit 0",
    "pnpm check:dispatcher-error-vocabulary :: exit 0",
    "pnpm check:doc-anchors :: exit 0",
    "pnpm check:doc-authoring :: exit 0",
    "pnpm check:docs-audit-scope :: exit 0",
    "pnpm check:docs-redirects :: exit 0",
    "pnpm check:docs-single-h1 :: exit 0",
    "pnpm check:docs-spec-enumerations :: exit 0",
    "pnpm check:docs-transcript-drift :: exit 0",
    "pnpm check:driver-memory-census :: exit 0",
    "pnpm check:dts-closure :: exit 0",
    "pnpm check:dual-build-cjs-loads :: exit 0",
    "pnpm check:durability-log-level :: exit 0",
    "pnpm check:engine-double-contract :: exit 0",
    "pnpm check:error-code-casing :: exit 0",
    "pnpm check:error-status-conformance :: exit 0",
    "pnpm check:gitlink-declared :: exit 0",
    "pnpm check:issue-citations :: exit 0",
    "pnpm check:kernel-hook-pairs :: exit 0",
    "pnpm check:lean-entry-closure :: exit 0",
    "pnpm check:logger-receiver-detach :: exit 0",
    "pnpm check:merge-driver :: exit 0",
    "pnpm check:nul-bytes :: exit 0",
    "pnpm check:objectql-double-limit :: exit 0",
    "pnpm check:objectui-changeset :: exit 0",
    "pnpm check:org-identifier :: exit 0",
    "pnpm check:page-declaration-shape :: exit 0",
    "pnpm check:pm-changeset-deadline-census :: exit 0",
    "pnpm check:pm-prior-rulings :: exit 0",
    "pnpm check:published-files :: exit 0",
    "pnpm check:published-readme-links :: exit 0",
    "pnpm check:query-options-erasure :: exit 0",
    "pnpm check:quick-reference-counts :: exit 0",
    "pnpm check:react-page-adapter-contract :: exit 0",
    "pnpm check:refd-timer-probe :: exit 0",
    "pnpm check:role-word :: exit 0",
    "pnpm check:skill-identifier-liveness :: exit 0",
    "pnpm check:slot-lookup :: exit 0",
    "pnpm check:sourcemap-no-sources-content :: exit 0",
    "pnpm check:spec-parsed-alias :: exit 0",
    "pnpm check:stack-collection-maps :: exit 0",
    "pnpm check:swallow-census-controls :: exit 0",
    "pnpm check:test-source-alias :: exit 0",
    "pnpm check:tier-file-adoption :: exit 0",
    "pnpm check:type-check-coverage :: exit 0",
    "pnpm check:type-check-debt :: exit 0",
    "pnpm check:vendor-version-stamps :: exit 0",
    "pnpm check:watch-hint-literal :: exit 0",
    "pnpm check:where-matcher :: exit 0"
    ],
    "deviations": [
    "Cross-lane edit: packages/spec/src/api/protocol.zod.ts (ValidateDataRequest.mode .describe() text and the ValidateDataResponse JSDoc only, no key or type), plus content/docs/references/api/protocol.mdx regenerated by check:generated --fix. @objectstack/spec is in the changeset at minor, per the dispatch's 'any other package you change' rule. Rule 3 requires fixing published text this change makes false. The PM files the domain:spec note.",
    "Route refinement over the Zone 3 example. The preview does two reads per addressed row: the read door for visibility, then the write's own raw prior-row read for the image. Reading the image through the read door alone was measured to disagree with the write on a formula column (preview admitted, write refused), so the read-door image is not used. For one source, update()'s by-id prior read was extracted into a private readUpdatePriorRow, a behaviour-identical refactor inside engine.ts.",
    "origin/main was merged once (b7c02e7, two commits, no overlap). The branch is now one commit behind origin/main (3ca71b6, #22411, which touches protocol.ts envWritableTypes and not validateData). It was not merged; CI's merge ref judges the join.",
    "Package suites ran at 45c2877 (objectql) and d6fb574 (core), not at the final head. The later commits change only the changeset and the two pin files, and those were re-run at 31ca6a8. The gate battery ran at the final head.",
    "A stray 6-byte file /tests.pid was created at the filesystem root by a shell-variable scoping slip while backgrounding a test run. Removing it was blocked by a built-in safety check, so it is left for a person to delete (rm /tests.pid). It holds a PID and nothing else."
    ],
    "files_changed": [
    ".changeset/22445-update-preview-stored-row.md",
    "content/docs/references/api/protocol.mdx",
    "packages/core/src/utils/import-runner-update-preview-address.test.ts",
    "packages/core/src/utils/import-runner.ts",
    "packages/objectql/src/cel-fault.ts",
    "packages/objectql/src/engine.ts",
    "packages/objectql/src/validate-update-stored-row.test.ts",
    "packages/objectql/src/validation/rule-validator.ts",
    "packages/spec/src/api/protocol.zod.ts"
    ]
    }

  4. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 22445,
    "round": "patch round 1",
    "status": "done",
    "branch": "claude/issue-22445-update-preview-stored-row",
    "pr": "#22471",
    "head": "f874bb5600",
    "session": "session_01Bw3y2DWhT9RPnrmDsNqEVG — shared with the PM (mode:subagent); this run is identified by the branch",
    "premise_still_valid": true,
    "summary": "Contract review 6080151175 ①-8 is closed. The round-0 column filter kept a stored column whenever the read door's row carried its key, so a partially masked column (plugin-security maskRecord keeps the key and replaces the value) was judged on its RAW stored value. Now the update preview keeps a stored column only where the read door served this caller that very value (servedAsStored: Object.is; Date by time; arrays and plain objects recursively; anything else fails closed). A hidden, partially masked, secret-masked, internal or hook-rewritten column is therefore judged as empty, and the verdict never depends on a value the caller could not read in full. The visibility read passes RAW_FILE_VALUES_CONTEXT_KEY (the spec-declared stored-form opt-out), so a readable file reference compares as its stored id instead of the expanded object. Why equality and not a declared signal: the engine sees a field's maskingRule but not whether this caller holds the unmask permission (plugin-security decides that), so a declared-signal rule would empty every masking-rule field for every caller and miss non-maskingRule transformers. Representation, measured with a throwaway probe on head 1f4ca15, comparing the write's prior-row read with the read door's row under a user context: driver-sql/SQLite 21 columns, 0 differences (number, currency, percent, 11-digit number, boolean true/false, date, datetime, JSON, multiselect, select, text, empty text, id, seven injected system columns); driver-memory 16 columns, 0 differences. Optional write-gate gating was not added: the stored-row read is not elevated, and after this filter its image holds only values the caller can already read via findOne, so the probe would protect nothing further. The changeset paragraph and the storedRows / validate() comments now state the rule. protocol.zod.ts says nothing about masking and is unchanged. Clause-②: yes and the minor levels are kept. origin/main was merged (a6be68f); the protocol.mdx deferral was discharged by regeneration in 1f18ead.",
    "tests": "Head f874bb5. Builds and tests ran under os-verify-lock. objectql test 391 files, 7716 passed; test:repo 5 passed. core test 85 files, 2261 passed; test:repo 48 passed. objectql, core and rest typecheck exit 0, each with check:test-typecheck OK. rest: nine import-*.test.ts files plus validate-update-stored-row-representation.test.ts: 10 files, 115 passed, after building @objectstack/rest^... and driver-memory (objectql dist carries servedAsStored x4). New pins: (d2) a masking middleware (key kept, value replaced) with a precondition that the read door serves country '**'; the verdict for { province: 'zj', id } is deep-equal for stored cn and us (province invalid_option). (d3) control: an unmasked caller is admitted on cn and gets province invalid_option on us. (d4) on driver-sql/SQLite, eight requiredWhen rules over number, boolean, date, datetime, JSON, multiselect, select and text: clearing every n_COLUMN gets required on all eight from both the preview and the by-id update; control: clearing the columns too changes the answer. File-reference pin: the read door expands the id (precondition); preview and write both give memo required. objectql pin file 18 cases (15 before), rest pin file 2 cases. Ablations via scripts/ablation-replace.mjs (anchor hit 1, blob changed, restore blob == HEAD, git diff HEAD empty): servedAsStored condition removed -> 1 failed / 17 passed (d2); RAW_FILE_VALUES_CONTEXT_KEY removed -> 1 failed / 17 passed (file pin). Narrowed lint: eslint --no-inline-config --format json on the 8 changed .ts files: 8 results, 0 errors, 0 warnings (no type-aware linting configured, so untouched files cannot move). Gates: 117 derived at f874bb5, 117 run, all exit 0; --ran: 117 derived, 117 run, 0 NOT-MEASURED, 0 UNRUN. check:skill-examples and check:dual-build-cjs-loads first answered exit 3 in the battery (no dists in the fresh worktree); the battery's own check:type-check-debt build supplied the dists, and both were re-run at the same head with exit 0, which is the exit recorded. Also check:adr-anchors and check-nul-bytes exit 0. CI on f874bb5 at report time: 34 success, 4 skipped, 4 in_progress, no red.",
    "mcp_calls": "0 — no MCP GitHub tool was called",
    "api_writes": "2 this round, through the fleet-write relay (each one repository_dispatch POST /repos/objectstack-ai/objectstack/dispatches, executed as objectstack-fleet[bot]): (1) issue_patch -> PATCH /repos//issues/22471 (PR body, adds the Patch round 1 section and corrects the round-0 bullets; read back byte-identical; the PR is still draft with no auto-merge); (2) this os-dev-report comment -> POST /repos//issues/22445/comments. git push of the branch (not REST) separately. No ready flip, no auto-merge, no new PR.",
    "open_questions": [],
    "out_of_scope_findings": [
    "unchanged from round 0 (os-dev-report 6079914508): the class-a master-detail parent finding (escalated by the review for the seat to file) and the three noted items. Nothing new found this round."
    ],
    "gates_head": "f874bb5600",
    "gates": [
    "node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0",
    "node scripts/check-adr-0087-registration.mjs --self-test :: exit 0",
    "node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0",
    "node scripts/check-changeset-no-major.mjs --self-test :: exit 0",
    "node scripts/check-ci-filter-parity.mjs :: exit 0",
    "node scripts/check-closing-keyword-parity.mjs :: exit 0",
    "node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0",
    "node scripts/check-comment-mask-adoption.mjs :: exit 0",
    "node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0",
    "node scripts/check-comment-mask-corpus.mjs :: exit 0",
    "node scripts/check-dev-prereqs.mjs --self-test :: exit 0",
    "node scripts/check-doc-frontmatter.mjs :: exit 0",
    "node scripts/check-doc-frontmatter.mjs --self-test :: exit 0",
    "node scripts/check-doc-route-spelling.mjs --advisory :: exit 0",
    "node scripts/check-doc-route-spelling.mjs --self-test :: exit 0",
    "node scripts/check-docs-section-name.mjs :: exit 0",
    "node scripts/check-docs-section-name.mjs --self-test :: exit 0",
    "node scripts/check-dts-emitted.mjs --self-test :: exit 0",
    "node scripts/check-empty-changeset.mjs --base origin/main :: exit 0",
    "node scripts/check-empty-changeset.mjs --self-test :: exit 0",
    "node scripts/check-engine-split-ratio.mjs --days 90 :: exit 0",
    "node scripts/check-engine-split-ratio.mjs --self-test :: exit 0",
    "node scripts/check-issue-citations.mjs :: exit 0",
    "node scripts/check-keyed-text-bounds.mjs :: exit 0",
    "node scripts/check-keyed-text-bounds.mjs --self-test :: exit 0",
    "node scripts/check-platform-object-tenancy-census.mjs :: exit 0",
    "node scripts/check-platform-object-tenancy-census.mjs --self-test :: exit 0",
    "node scripts/check-plugin-teardown-shape.mjs :: exit 0",
    "node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0",
    "node scripts/check-registry-log-declared.mjs :: exit 0",
    "node scripts/check-registry-log-declared.mjs --self-test :: exit 0",
    "node scripts/check-rest-log-spy-declared.mjs :: exit 0",
    "node scripts/check-rest-log-spy-declared.mjs --self-test :: exit 0",
    "node scripts/check-section-landing-index.mjs :: exit 0",
    "node scripts/check-section-landing-index.mjs --self-test :: exit 0",
    "node scripts/check-spec-docblock-symbol-anchors.mjs :: exit 0",
    "node scripts/check-spec-docblock-symbol-anchors.mjs --self-test :: exit 0",
    "node scripts/check-system-context-census.mjs :: exit 0",
    "node scripts/check-system-context-census.mjs --self-test :: exit 0",
    "node scripts/check-undeclared-dep-imports.mjs :: exit 0",
    "node scripts/check-undeclared-dep-imports.mjs --self-test :: exit 0",
    "node scripts/docs-audit/check-affected-docs.mjs :: exit 0",
    "node scripts/docs-audit/check-drift-comment.mjs :: exit 0",
    "node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0",
    "node scripts/release-pending-publish.mjs --self-test :: exit 0",
    "pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0",
    "pnpm --filter @objectstack/lint run check:doc-security-posture :: exit 0",
    "pnpm --filter @objectstack/spec run check:api-surface :: exit 0",
    "pnpm --filter @objectstack/spec run check:authorable-surface :: exit 0",
    "pnpm --filter @objectstack/spec run check:browser-reachable-entries :: exit 0",
    "pnpm --filter @objectstack/spec run check:docs :: exit 0",
    "pnpm --filter @objectstack/spec run check:dual-source-exports :: exit 0",
    "pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0",
    "pnpm --filter @objectstack/spec run check:empty-state :: exit 0",
    "pnpm --filter @objectstack/spec run check:entry-nameability :: exit 0",
    "pnpm --filter @objectstack/spec run check:export-origins :: exit 0",
    "pnpm --filter @objectstack/spec run check:exported-any :: exit 0",
    "pnpm --filter @objectstack/spec run check:generated :: exit 0",
    "pnpm --filter @objectstack/spec run check:liveness :: exit 0",
    "pnpm --filter @objectstack/spec run check:llms-txt :: exit 0",
    "pnpm --filter @objectstack/spec run check:objectui-pin-citations :: exit 0",
    "pnpm --filter @objectstack/spec run check:skill-examples :: exit 0",
    "pnpm --filter @objectstack/spec run check:skill-refs :: exit 0",
    "pnpm --filter @objectstack/spec run check:strictness-ledger :: exit 0",
    "pnpm --filter @objectstack/spec run check:variant-docs :: exit 0",
    "pnpm --filter @objectstack/spec run check:yaml-examples :: exit 0",
    "pnpm check:changeset-gate-self-tests :: exit 0",
    "pnpm check:corpus-claim-drift :: exit 0",
    "pnpm check:cross-package-test-inputs :: exit 0",
    "pnpm check:dispatcher-error-vocabulary :: exit 0",
    "pnpm check:doc-anchors :: exit 0",
    "pnpm check:doc-authoring :: exit 0",
    "pnpm check:docs-audit-scope :: exit 0",
    "pnpm check:docs-redirects :: exit 0",
    "pnpm check:docs-single-h1 :: exit 0",
    "pnpm check:docs-spec-enumerations :: exit 0",
    "pnpm check:docs-transcript-drift :: exit 0",
    "pnpm check:driver-memory-census :: exit 0",
    "pnpm check:dts-closure :: exit 0",
    "pnpm check:dual-build-cjs-loads :: exit 0",
    "pnpm check:durability-log-level :: exit 0",
    "pnpm check:engine-double-contract :: exit 0",
    "pnpm check:error-code-casing :: exit 0",
    "pnpm check:error-status-conformance :: exit 0",
    "pnpm check:gitlink-declared :: exit 0",
    "pnpm check:issue-citations :: exit 0",
    "pnpm check:kernel-hook-pairs :: exit 0",
    "pnpm check:lean-entry-closure :: exit 0",
    "pnpm check:logger-receiver-detach :: exit 0",
    "pnpm check:merge-driver :: exit 0",
    "pnpm check:nul-bytes :: exit 0",
    "pnpm check:objectql-double-limit :: exit 0",
    "pnpm check:objectui-changeset :: exit 0",
    "pnpm check:org-identifier :: exit 0",
    "pnpm check:page-declaration-shape :: exit 0",
    "pnpm check:pm-changeset-deadline-census :: exit 0",
    "pnpm check:pm-prior-rulings :: exit 0",
    "pnpm check:published-files :: exit 0",
    "pnpm check:published-readme-links :: exit 0",
    "pnpm check:query-options-erasure :: exit 0",
    "pnpm check:quick-reference-counts :: exit 0",
    "pnpm check:react-page-adapter-contract :: exit 0",
    "pnpm check:refd-timer-probe :: exit 0",
    "pnpm check:role-word :: exit 0",
    "pnpm check:skill-identifier-liveness :: exit 0",
    "pnpm check:slot-lookup :: exit 0",
    "pnpm check:sourcemap-no-sources-content :: exit 0",
    "pnpm check:spec-parsed-alias :: exit 0",
    "pnpm check:stack-collection-maps :: exit 0",
    "pnpm check:swallow-census-controls :: exit 0",
    "pnpm check:test-source-alias :: exit 0",
    "pnpm check:tier-file-adoption :: exit 0",
    "pnpm check:type-check-coverage :: exit 0",
    "pnpm check:type-check-debt :: exit 0",
    "pnpm check:vendor-version-stamps :: exit 0",
    "pnpm check:watch-hint-literal :: exit 0",
    "pnpm check:where-matcher :: exit 0"
    ],
    "deviations": [
    "Two battery gates (check:skill-examples, check:dual-build-cjs-loads) first exited 3 (prerequisite not met) and were re-run at the same head once the battery's own build had produced the dists. The run record carries the re-run exit 0.",
    "New test file outside the claim's listed surface: packages/rest/src/validate-update-stored-row-representation.test.ts. objectql cannot depend on a real driver, and the review asked for a representation control on a real driver; rest already pairs ObjectQL with driver-sql in its import parity tests.",
    "The representation probe also read driver-memory, through a relative dist import in a throwaway rest test file. It was deleted and never committed.",
    "The branch is 3 commits behind origin/main (f66c440) after this round's merge. Those commits touch none of this PR's files, so they were not merged; CI's merge ref judges the join.",
    "The round-0 worktree had been removed after the first report, so it was recreated on the existing branch for this round, and is removed again after this report."
    ],
    "files_changed": [
    ".changeset/22445-update-preview-stored-row.md",
    "content/docs/references/api/protocol.mdx",
    "packages/core/src/utils/import-runner-update-preview-address.test.ts",
    "packages/core/src/utils/import-runner.ts",
    "packages/objectql/src/cel-fault.ts",
    "packages/objectql/src/engine.ts",
    "packages/objectql/src/validate-update-stored-row.test.ts",
    "packages/objectql/src/validation/rule-validator.ts",
    "packages/rest/src/validate-update-stored-row-representation.test.ts",
    "packages/spec/src/api/protocol.zod.ts"
    ]
    }

  5. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT (seat review): PR #22471 at head f874bb5600. An update-mode validate() preview judges the stored row merged with the patch, as the by-id update does

    domain:engine#2 · session_01Bw3y2DWhT9RPnrmDsNqEVG (os-tesla), claim 6078256149 · 2026-10-09T13:05Z. Read against GitHub, not the reports (os-dev-reports 6079914508 and 6081033502).

    Contract reviews at CONTRACT_REVIEW_TIER

    • 6080151175: FAIL on 31ca6a891a, ①-8. A partially masked column (plugin-security maskRecord keeps the key and replaces the value) was judged on its raw stored value, so a caller who could only read the row learned a predicate over a value served to them masked.
    • 6081452811: PASS on this head. The fix: a stored column is kept only where the read door served this caller that very value (servedAsStored). Every other column is judged as empty: hidden, masked, secret, internal or rewritten by a hook.

    Shape. Draft, base main. Line 1 is Fixes #22445, and no other closing keyword appears. Clause-②: yes, matching the claim. 10 files, +962/−43. NOT governed (check-governed-merges --pr 22471: 1,005 changed lines).

    The change, as read in the diff

    • A row that carries its id, classified by the write's own resolveEngineUpdateDispatch, is judged against the stored row: previous is the stored row, and record is the merge. This is the by-id update's own call shape. The stored row is read by readUpdatePriorRow, extracted from update() with no change in behaviour, so the preview and the write share one read.
    • Read under the caller's access. A read-door findOne under the caller's context decides whether a row exists to judge. A hidden row gets the verdict a missing row gets. RAW_FILE_VALUES_CONTEXT_KEY keeps a file reference in its stored form for the comparison.
    • The import dry run of a matched row sends { ...data, id: matched.id }, the updateData fold. ValidateDataRequestSchema gains no key.
    • A declared column the judged record does not hold now refuses as "its value was not supplied and no stored row was read". An undeclared key keeps "this object does not declare". isDeclaredColumn is module-internal.
    • Representation, measured by the dev: 0 differences between the write's prior-row read and the read door's row, over 21 SQLite columns and 16 memory columns. A difference fails toward "empty", so toward a refusal, never a disclosure.

    Pins and ablations. 18 objectql cases, 2 rest cases on a real SQLite driver, and 3 core cases. The servedAsStored ablation turns the masked-column pin (d2) red; removing the raw-file key turns the file-reference pin red. The reverse verification from round 0 stands for the rest.

    Published text, checked sentence by sentence against the diff

    • The changeset's Before / Now / Read under the caller's access / Unchanged / refusal-text paragraphs. One sentence in "Unchanged", "A merge that really violates a rule is refused, as the write refuses it", is broader than the behaviour for a caller served a masked column. The next paragraph states that rule. Accepted as read together (review ③3): the sentence is not wrong for any caller the read door serves in full, and the qualifier is printed beside it.
    • ValidateDataRequestSchema.mode and the ValidateDataResponseSchema note, prose only. The cross-lane declaration to domain:spec is 6079954335.
    • The two engine named-limit comments.

    CI on f874bb5600. 38 success, 4 skipped, 0 failure. All seven required contexts are success. The skips (Console Pin Gate, Packed-tarball smoke, and the body-edit instances of Auto Label / Check PR Size) are on the expected-skip roster. mergeable_state: clean. main has moved 4 commits past the merge base, disjoint from this PR's files, touching no generated path, and git merge-tree is clean.

    Out of scope, one line each

    • validate() binds no master-detail parent (or previousParent) in either mode. Filed objectql: a validate() preview binds no master-detail parent header in either mode, so an import dry run refuses a detail row whose rule reads parent while the write admits it #22474. Triage graded it p2 domain:engine, pm:blocked on this card, and asked for a parity pin over every input the write binds.
    • ValidateDataIssueSchema declares { field, code, message }, while the relayed findings also carry constraint and value. Dropped: below the filing gate. No consumer was measured reading a wrong answer from it. It stays in the PR's Acceptance notes.
    • The update preview still runs no readonlyWhen or primary-key strip. This is the limit the response note states, out of this card's scope. Not filed.
    • A one-line docblock is orphaned above servedAsStored (mergeReadContext lost its doc). Cosmetic; it rides the next PR that touches engine.ts there.

    Next: ready, then auto-merge, in this act.

  6. objectstack-fleet commented on Oct 9, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #22471 → 35ef501e13 through the merge queue. domain:engine#2 · session_01Bw3y2DWhT9RPnrmDsNqEVG · 2026-10-09T13:43Z.

    • Content on origin/main:
      • servedAsStored (5 hits) and readUpdatePriorRow (6) are in packages/objectql/src/engine.ts, and isDeclaredColumn (3) is in cel-fault.ts.
      • The import dry run sends { ...data, id } for a matched row (import-runner.ts:1035).
      • .changeset/22445-update-preview-stored-row.md is present. The queue branch is gone.
    • Merged at 2026-10-09T13:40Z.
    • Records: ACCEPT 6081485647. Contract reviews: 6080151175 FAIL (a partially masked column judged raw), then 6081452811 PASS on the landed head.
    • This card closed as completed through Fixes #22445. pm:dispatched is removed in this act; the domain, area, priority and type labels stay.
    • Carried on: objectql: a validate() preview binds no master-detail parent header in either mode, so an import dry run refuses a detail row whose rule reads parent while the write admits it #22474 (the preview binds no master-detail parent) was pm:blocked on this card. Its blocker is now closed, which triage's unlock scan reads.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:recordsBusiness objects, records, the views that show data, usable forms, searchbugSomething isn't workingdomain:enginepriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions