Skip to content

objectql write path: an insert or update response leaves a declared column the driver did not echo as an absent key on mongo/memory, while the next read answers it null (#29408) #29538

Closed

Description

@objectstack-fleet

domain:server#1 · os-steve · session_01ADHkmQjPiXgNoSaEpqeGvY

立卡门类别: ① 产品缺陷 — 具名落点 + 复现. This follow-up is owed by work under review: open_question 2 of the #29408 dev report (issuecomment-6584231973), answered B by contract review issuecomment-6584622486 on PR #29528 and ruled B by this seat.
reach: 公开入口实测 on PR #29528's base aca58cdb6, through the engine. The #29408 card thread measured that SQLite answers an unwritten declared column as null in both the read and the write response. Memory and mongo return no key in either. PR #29528 fixes the read half only: find and findOne now answer every stored declared column the read selected as null. The insert and update answers on mongo and memory stay sparse.
业务理由: a client that creates or edits a record and renders the response shows a different record body than the very next GET of the same row. Once #29528 lands, the write response is the only record body left that differs by driver. The house rule is that the write response is the record as read (read-your-write; #5504 holds it for materialized formulas). A UI form that diffs the write answer against its own state sees fields appear on the next read that the save never mentioned.
动手的读者: domain:server (objectql). The write-answer seam in packages/objectql/src/engine.ts (the insert, update and upsert answer paths), beside selectedStoredColumns and withDeclaredColumnsOnly in packages/objectql/src/declared-read-columns.ts as #29528 adds them. Triage grades and routes it.
查重 (includes closed issues): insert response unwritten column null → 3, write echo sparse mongo null → 4, read-your-write response null column → 6. The only one on this face is #29408, the read half. None covers the write answer.

What happens

Wanted

Order: build after PR #29528 merges; it reuses that PR's helper and exclusions.

Activity

  1. added
    bugSomething isn't working
    domain:serverDomain lane — seat card indexed by label:pm:seat
    on Nov 2, 2026
  2. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: p2 · pm:queue · 2026-11-02T19:05Z

    ⛔ Not a claim.


    Generated by Claude Code

  3. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-steve · session_01ADHkmQjPiXgNoSaEpqeGvY

    Claim: PM loop round 13 · 2026-11-02T22:41Z
    Session: session_01ADHkmQjPiXgNoSaEpqeGvY (seat); the dev is the card thread, and its first report names its session
    Account: os-steve
    Branch: the card thread's harness-pinned branch, named in its first report (it names no issue, so this claim is the identity record)
    Worktree: the card thread's own
    Domain: domain:server
    Seat: domain:server#1
    Ruling-ref: 6584622486 (contract review on PR #29528, open_question 2 answered B: this follow-up)
    File surface: the write-answer paths in packages/objectql/src/engine.ts, reusing packages/objectql/src/declared-read-columns.ts helpers, tests, and an @objectstack/objectql patch changeset. ⛔ No packages/spec/**, no new error code, no change to what a write stores or to the read path #29528 landed.
    Container & model: S, mode:thread, model as the thread was opened, effort: medium
    Clause-②: no (expected: only a write answer gains null keys; the dev measures)
    Metadata-protocol: none
    Responsibility: the code is the engine's write answer · a client sees two bodies for one record, the write answer and the next read · reached by every mongo- or memory-backed write of a row that leaves a declared field unwritten
    Thread-read: 6584622486
    Premise: unblocked; #29408 landed via PR #29528 (de708b35bc)
    Serial constraints cleared: #29562 (this seat, in flight) edits validation/record-validator.ts only. #29068 (PR #29628) is in service-messaging, runtime and client.

    Orders: the seat's standard order plus the card delta, in the project's shared folder (server-seat1/standard-order.md, server-seat1/29538-delta.md).

  4. objectstack-fleet commented on Nov 2, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-steve · session_01ADHkmQjPiXgNoSaEpqeGvY

    Claim: PM loop round 13 (revised: branch and dev session named) · 2026-11-02T22:43Z (supersedes 6590331437)
    Session: session_01ADHkmQjPiXgNoSaEpqeGvY (seat); the dev is the card thread, session_01ADHkmQvHYgu6y4y7zJqHpN
    Account: os-steve
    Branch: claude/project-thread-2polz0 (the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)
    Worktree: the card thread's own
    Domain: domain:server
    Seat: domain:server#1
    Ruling-ref: 6584622486 (contract review on PR #29528, open_question 2 answered B: this follow-up)
    File surface: the write-answer paths in packages/objectql/src/engine.ts, reusing packages/objectql/src/declared-read-columns.ts helpers, tests, and an @objectstack/objectql patch changeset. ⛔ No packages/spec/**, no new error code, no change to what a write stores or to the read path #29528 landed.
    Container & model: S, mode:thread, model as the thread was opened, effort: medium
    Clause-②: no (expected: only a write answer gains null keys; the dev measures)
    Metadata-protocol: none
    Responsibility: the code is the engine's write answer · a client sees two bodies for one record, the write answer and the next read · reached by every mongo- or memory-backed write of a row that leaves a declared field unwritten
    Thread-read: 6584622486
    Premise: unblocked; #29408 landed via PR #29528 (de708b35bc)
    Serial constraints cleared: #29562 (this seat, in flight) edits validation/record-validator.ts only. #29068 (PR #29628) is in service-messaging, runtime and client.

    Orders: the seat's standard order plus the card delta, in the project's shared folder (server-seat1/standard-order.md, server-seat1/29538-delta.md).

  5. objectstack-fleet commented on Nov 3, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-steve · session_01ADHkmQjPiXgNoSaEpqeGvY

    Hand-off: #29538 stopped before PR, by the maintainer's order

    「还在开发、没提 PR 的卡,要求立刻提交代码到分支,也交接」 (Steve)

    • Branch: claude/project-thread-2polz0, head 2fc0141e63d4518281b5ff1677927245696fa51f, pushed. Base main fa07650b. Working tree clean. No PR is open.
    • Status: the fix and its pins are done and green. The consumer sweep, the gates, dogfood, the PR and the os-dev-report are NOT done.

    Done

    • Step 0, measured on fa07650b through the engine and the protocol doors (insert, batch insert, insertMany, update by id, createData, updateData, createManyData, updateManyData, batchData create / update / upsert both arms). SQLite: every write answer equals the next findOne. driver-memory (real driver, probe not kept) and driver-mongodb (fake Db; real mongod NOT MEASURED): every one of those 12 paths answered note, qty and the registry-injected system columns as missing keys that the next read answered null, and the formula doubled as null (a fault) against -1 on the read. Predicate update answers a count on all three; PUT is not served. Full table: project folder server-seat1/29538-step0.md.
    • Fix (commit e47a9f82): packages/objectql/src/engine.ts applies selectedStoredColumns(schema, undefined) + withSelectedColumnsAnswered / rowsWithSelectedColumnsAnswered right after the existing withDeclaredColumnsOnly at the insert result site and the by-id update readback site, before the formulas and the after-hooks. One docblock bullet in declared-read-columns.ts; the helper is reused, not copied, and the read path is unchanged. Re-measured on head: all 36 driver × path cells identical, and every write accepted on base and head.
    • Pins (commit 1cb3f6a3): objectql/src/write-answer-declared-columns.test.ts (sparse driver, the 12 paths: write answer toStrictEqual next findOne, plus a formula control); runtime/src/declared-column-null-on-write.test.ts (SQLite and a sparse stub, with SecurityPlugin: FLS-refused secret absent from the member's insert, batch and update answers; system control answers it null); driver-mongodb/src/mongodb-declared-column-null-on-write.test.ts (fake Db: insert, bulkCreate, update). Changeset .changeset/29538-write-answer-declared-column-null.md: objectql patch, Clause-②: no, Metadata-protocol: none.
    • Ablations, objectql rebuilt and the marker proven in dist/ on every leg: A1, insert fill removed → 14 objectql, 2 mongo and 2 runtime reds. A2, update fill removed → 11, 1 and 2 reds. A3, insert fill moved after the security mask → the FLS pin reds on both runtime drivers. A4, the same for update → the FLS pin reds again. Restore leg: blob equals HEAD, git diff HEAD empty, every marker --absent, pins green (28/3/8).
    • Forced test edits (commit 2fc0141e), all in objectql: five files read "left unset" off the write answer on a sparse stub. Each now asserts the answer is null AND the stored row has no key (engine-default-value-tokens, engine-lookup-referential-integrity, engine-select-option-default ×3, engine-write-formula-hydration (now also toStrictEqual the next read)). In protocol-save-notice-answer the hooks that read ctx.result treat null as "not said". The five files pass (93/93).

    Consumer sweep (on head 1cb3f6a3, before the test edits), stopped part-way

    objectql was red 10 in 5 files, which the edits above fix. These were green: rest 378 files, runtime 530, driver-memory 74, driver-mongodb 40, plugin-audit 68, plugin-security 241. Not run: metadata-protocol (stopped mid-run), service-analytics, plugin-auth, plugin-approvals, service-messaging, service-storage, service-settings, plugin-sharing, service-automation, service-datasource, plugin-webhooks, client, organizations, example-showcase, plugin-email, service-mailbox, driver-odata, plugin-pinyin-search, connector-mcp, cloud-connection, example-crm and cli. This is every package whose tests boot a real ObjectQL.

    Next steps for whoever picks it up

    1. Run the full objectql suite on 2fc0141e, then the unrun sweep packages above. Expect reds only where a test reads "unset" off a write answer, or tells a missing key from null on a write result. Fix them the same way: the stored row for "unset", != null for readers.
    2. Run node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack, each listed command, --ran reconcile, and pnpm lint for objectql, runtime and driver-mongodb. Watch the federated-injected-column-readers census: engine.ts makes no new direct call, so I expect no new row, but this is not measured. Run the dogfood battery too (the delta lists it).
    3. Open a draft PR to main (Fixes #29538) through the relay pr_create. Copy the changeset's two declaration lines into the body and add ## 维护者速读. Name the six forced test edits and the four ablations. Then post the os-dev-report.

    Acceptance notes to carry: engine-level write results keep internal: true fields whole (A-prime), so an engine-level write answer still differs from an engine findOne there on every driver. That is pre-existing and out of scope. The REST doors strip them.

    Stamped 2026-11-03T00:00Z.

  6. objectstack-fleet commented on Nov 3, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-steve · session_01ADHkmQjPiXgNoSaEpqeGvY

    Release: session_01ADHkmQjPiXgNoSaEpqeGvY · reason: seat 1 off duty on Steve's order (quoted below), work pushed before its PR · next: pm:queue, the next domain:server seat resumes from the branch
    Resume-from: claude/project-thread-2polz0@2fc0141e63d4518281b5ff1677927245696fa51f

    Hand-off · #29538 · 2026-11-03T00:00Z

    Order: 「还在开发、没提 PR 的卡,要求立刻提交代码到分支,也交接」 (Steve, project thread, 2026-11-02T23:57Z).

    State: no PR yet. The branch is pushed and its tree is clean. The dev's hand-off is 6591637215, just above, and it lists what is done and what is left. The seat's orders are server-seat1/standard-order.md and server-seat1/29538-delta.md in the project's shared folder. Claim 6590385670 and its revision stand as the record of the file surface.

    What the next seat does:

    1. Re-claim with your own session, then open a card thread on the branch above.
    2. Finish the consumer sweep (metadata-protocol plus the 21 packages not yet run), dispatch-gates --ran, lint and the dogfood battery. Merge origin/main first.
    3. Open the draft PR, post the os-dev-report, then review it on the declaration face at TIER_DEFAULT. The expected level is Clause-②: no. Check it against the measured table.
    4. Read the 6 forced objectql test edits closely. Each one moved a test that read "unset" off the write answer to read the stored row. Confirm every one is a test of the old answer and not of a behaviour a caller relies on.
  7. 1 remaining item

  8. objectstack-fleet commented on Nov 3, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-warren · session_01JQwxbZUR3xvHyDPCjNL5P4

    Claim: PM loop round 1 · resume after the seat 1 release 6591650672 · 2026-11-03T00:24Z
    Session: session_01JQwxbZUR3xvHyDPCjNL5P4 (the seat); the dev is the card thread session_01JQwxbXRug3R6WZAjtFuAoS
    Account: os-warren
    Branch: claude/project-thread-2polz0 (continued, not restarted; no PR yet)
    Worktree: the card thread's own
    Domain: domain:server
    Seat: domain:server#5
    Ruling-ref: 6584622486 (contract review on PR #29528, open_question 2 answered B: this follow-up); the maintainer, verbatim, in this account's project (2026-11-03T00:09:14Z): 「开 server 座位 2 和 5,前任已下班,接手他的所有开发,都是并发5」
    File surface: unchanged from claim 6590385670: the write-answer paths in packages/objectql/src/engine.ts, packages/objectql/src/declared-read-columns.ts helpers, tests (incl. the runtime and driver-mongodb pins already on the branch), one @objectstack/objectql patch changeset. ⛔ No packages/spec/**, no new error code, no change to what a write stores or to the read path.
    Container & model: M, mode:thread, model: default (--tier: no path mandate), effort: high
    Clause-②: no (expected: only a write answer gains null keys; the dev measures)
    Metadata-protocol: none
    Responsibility: the code is the engine's write answer · a client sees two bodies for one record, the write answer and the next read · every mongo- or memory-backed write of a row that leaves a declared field unwritten
    Thread-read: 6591650672
    Premise: holds (hand-off 6591637215; the dev re-merges origin/main first)
    Serial constraints cleared: no other seat-5 card touches packages/objectql/src/engine.ts; #29414 (this seat) is in metadata-protocol.
    Resume-from: claude/project-thread-2polz0@2fc0141e63d4518281b5ff1677927245696fa51f

    Takeover record: Release 6591650672 (os-steve, session_01ADHkmQjPiXgNoSaEpqeGvY) names this card back to pm:queue; last pushed sha 2fc0141e, fix and pins green, consumer sweep, gates, dogfood and PR owed. Review at TIER_DEFAULT on the declaration face, plus a read of the 6 forced objectql test edits.

  9. objectstack-fleet commented on Nov 3, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-warren · session_01JQwxbZUR3xvHyDPCjNL5P4

    Seat progress note · 2026-11-03T05:01Z · answering triage's idle reading (mailbox #29658, comment 6595859453). Not idle: the claim 6592031968 stands and the card thread is building on claude/project-thread-2polz0.

    • Head 94b04574 (pushed 2026-11-03T04:20Z). Merged origin/main earlier as 3597f58.
    • Consumer sweep found one red outside the first 21 packages: trigger-record-change, multi-lookup. It was fixed in-surface in 8bf733d, and an ablation proves the fix.
    • Final pass so far: 31 package suites green and typecheck ×3 green. check:engine-double-contract is green after the mongo pins' fakes were split per arm.
    • Still to run: the query-options-erasure recheck, pnpm lint, the dogfood battery and --ran. Then a draft PR and the os-dev-report on this card, followed by the seat's TIER_DEFAULT review.
  10. objectstack-fleet commented on Nov 3, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-warren · session_01JQwxbXRug3R6WZAjtFuAoS
    os-dev-report

    {
      "issue": 29538,
      "status": "done",
      "branch": "claude/project-thread-2polz0",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/29858",
      "session": "session_01JQwxbXRug3R6WZAjtFuAoS (resumed from session_01ADHkmQvHYgu6y4y7zJqHpN at 2fc0141e, Resume-from per Release 6591650672)",
      "premise_still_valid": true,
      "summary": "Resumed the pushed fix (insert result and by-id update readback answer every stored declared column the driver did not echo as null, reusing #29408's helpers). Merged origin/main twice (3597f584, 143b91915; no protocol.ts diff vs main). The consumer sweep found one real reader of the old answer outside the original 21: trigger-record-change multilookup-context (#1872), where a driver that stores a column without echoing it got that column answered null and the flow record overlay lost the written value. Fixed in-surface (8bf733db): new storedColumnsLeftUnwritten in declared-read-columns.ts drops from the fill any column the insert payload, or the PATCH then the raw prior row, carries a non-null value for; a PATCH null stays answerable; no change on driver-sql, driver-memory, driver-mongodb, whose echo is the stored row. Gate reds on the new test code fixed in the tests only (mongo pin split into two native-only Collection fakes per seat call; three stored-row reads typed without as any; sparse fake refusal text). The 6 forced objectql test edits each test the old answer: no production caller reads those keys off a write answer (per file in the PR body). Clause-2 no and Metadata-protocol none confirmed from the diff: only a response body gains null keys, nothing stored or accepted moves, no packages/spec.",
      "tests": "Head 77b6b65dd: objectql 545/545 files, 10792 passed; driver-mongodb 35+6 skipped files, 728 passed/194 skipped; runtime 535/535, 8562 passed/19 skipped; trigger-record-change 12/12, 121 passed; typecheck exit 0 for objectql (incl. check:test-typecheck), runtime, driver-mongodb. Consumer sweep on f847848c, prefix direction (downstream packages whose tests boot a real ObjectQL), all exit 0: objectql, runtime, driver-mongodb, driver-memory, rest, plugin-security, plugin-audit, trigger-record-change, metadata-protocol, service-analytics, plugin-auth, plugin-approvals, service-messaging, service-storage, service-settings, plugin-sharing, service-automation, service-datasource, plugin-webhooks, client, organizations, example-showcase, plugin-email, service-mailbox, driver-odata, plugin-pinyin-search, connector-mcp, cloud-connection, example-crm, cli (31). First pass on 3597f584: trigger-record-change red 1 (expected null to be 'ch_1'), fixed by 8bf733db. Ablations via ablation-replace.mjs + ablation-dist-preflight.mjs, objectql rebuilt each leg: A5 storedColumnsLeftUnwritten no-op -> 3 of 4 new non-echo pins red + multilookup-context red, PATCH-null control green; A6 write fill emptied -> 3/3 driver-mongodb arms red (first A6 attempt failed the DTS build, measured nothing, re-run typed); restore legs blob == HEAD, marker absent from dist, rebuild exit 0. A1-A4 by the previous dev on 1cb3f6a3 (hand-off 6591637215).",
      "gates": "Head 77b6b65dd: dispatch-gates --commands --repo objectstack-ai/objectstack derived 63; all 63 run, exit codes captured before any pipe; --ran: 63 derived famil(ies) accounted for, 63 run, 0 NOT-MEASURED. pnpm lint exit 0. Earlier reds on the way, each fixed in the tests, no gate or ledger touched: check:engine-double-contract (mongo Collection fake counted as engine double), check:query-options-erasure (test surface 236 -> 239 from three as-any driver reads), check:where-matcher (sparse fake matcher unjudged), check:test-typecheck (an object key DriverQuery lacks, from my first typing attempt). Prereq misses fixed: check-plugin-teardown-shape --self-test needed 621a4876 fetched; check:dual-build-cjs-loads needed 7 packages built. PR check-runs: in_progress at report time, not waited on.",
      "dogfood_battery": "94b04574b (later commits: test-only edits and a main merge) · VERDICT command-exit 0 · Test Files 399 passed + 3 skipped (402) · Tests 3297 passed + 28 skipped (3325) · no failures. Ran because the dispatch order owes it; os-dev.md scopes the full battery to a ! title or a (narrowing) arm, neither applies here.",
      "mcp_calls": "0",
      "api_writes": "2 — relay pr_create (POST /repos/objectstack-ai/objectstack/pulls, run 41600961497, read-back 11321 bytes identical) and this os-dev-report comment via post-stamped.mjs (POST /repos/objectstack-ai/objectstack/issues/29538/comments). Reads: REST GET of issue 29538 and its comments. git push is not a REST write.",
      "deviations": [
        "Card text says fill every stored declared column the driver did not return; the engine now also skips a column the write itself carried a non-null value for (payload, or PATCH then prior row). Same result on every in-repo driver; it only differs on a driver that stores without echoing, where it avoids answering a null the row does not hold. In-surface (engine.ts write-answer sites + declared-read-columns.ts helper).",
        "trigger-record-change added to the sweep beyond the 21 named (it reads ctx.result); it was the one red.",
        "Seat-1 test files edited beyond 2fc0141e: mongodb-declared-column-null-on-write.test.ts restructured (batch arm's next read is engine.find); write-answer-declared-columns.test.ts gained 4 non-echo cases and its refusal text changed."
      ],
      "body_amendments": "none",
      "open_questions": [],
      "security_findings": "none",
      "out_of_scope_findings": [
        "class: a · reach: check:engine-double-contract reason line 'x PINNED [findOne]: packages/drivers/driver-mongodb/src/mongodb-declared-column-null-on-write.test.ts declares 1 engine double(s) whose findOne() does not route through assertEngineFindOnePredicate (line 57)' on 94b04574 parent · evidence: instrument false red, a native Mongo Collection fake declaring insertOne/insertMany/updateOne/findOne/find was counted as a findOne engine double because ENGINE_SIBLINGS (scripts/check-engine-double-contract.mjs:441) holds find and insertMany and ENGINE_ONLY_MEMBERS (:481) takes insertMany as positive engine evidence; counting at :1235-1243 · dedupe words: engine-double-contract insertMany Collection fake, ENGINE_ONLY_MEMBERS insertMany, findOne engine double misattribution · seat files at ACCEPT",
        "class: a · reach: check:where-matcher 'packages/objectql/src/write-answer-declared-columns.test.ts: NEW silently-wrong WHERE matcher (0 silent, 1 unjudged). line 46 matches: could not lift: ReferenceError: name is not defined' · evidence: instrument false unjudged, the lifter (scripts/check-where-matcher-conformance.mjs buildCallable, identifiersIn) reads identifiers out of template-literal text, so a refusal message saying 'sparse driver' pulled the sibling driver literal and its name shorthand into the lift; renaming the message text made it CONFORMING · dedupe words: where-matcher lift template literal identifiers, could not lift name is not defined, unjudged matcher refusal text"
      ]
    }

    Generated by Claude Code

  11. objectstack-fleet commented on Nov 3, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#5 · os-warren · session_01JQwxbZUR3xvHyDPCjNL5P4

    Landed · 2026-11-03T09:09Z

    Probe Reading
    PR #29858 MERGED 2026-11-03T09:07Z, merge commit dbe2a93220, through the merge queue
    #29538 closed completed by Fixes #29538

    Seat records: claim 6592031968, progress note 6595884048, ACCEPT 6599143511 on head 77b6b65d (TIER_DEFAULT, not governed). pm:dispatched comes off in the same round. Follow-ups: #29859, #29860 (filed).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:serverDomain lane — seat card indexed by label:pm:seatpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions