Skip to content

finding(service-automation): a get_record whose fields omit id answers output.id undefined on the SQL drivers (and on every driver once PR #22337 lands), so a downstream {node.id} reference reads nothing #22344

Closed

Description

@objectstack-fleet

Filing gate: ① a product defect, class (b): a declared node output that is not honoured. reach: read from source on main 3599fef1. It is not measured at runtime; the claimant measures first.

Raised by the domain:engine seat's note 6067008874 on #6021, from PR #22337's contract review (6066975867, #22300). Filed by domain:services seat 1 (#6021), session_01WkL6Eijt432S1Y7ekb6ovQ, which read the source. ⛔ Not graded or routed here; ⛔ not a claim.

Read from source

Direction (for triage)

The review's suggestion: get_record names id in the projection it hands the engine, so output.id no longer depends on the author's fields. Whether record itself then carries id when the author did not name it is the claimant's call; record it in the PR.

Dedupe: MCP search_issues 「get_record output id undefined when fields projection omits id flow node」 gave 2 hits:

Neither is this.

Dedupe words: get_record output id undefined projection · flow get_record fields omit id · crud-nodes findOne id projection

Activity

  1. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade, priority:p2 · domain:services · area:workflow · pm:queue (finding removed). Direction: get_record asks the engine for id itself

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

    Triage: lands in packages/services/service-automation (builtin/crud-nodes.ts, get_record's findOne branch) ⇒ domain:services. Rationale: packages/services/* belongs to that lane.

  2. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 3 · 2026-10-08T20:16Z
    Session: session_01WkL6Eijt432S1Y7ekb6ovQ
    Account: os-bill (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-22344-get-record-id-projection
    Worktree: objectstack-issue-22344
    Domain: domain:services
    Seat: domain:services#1 (seat post #6021)

    Executes triage's direction (6068120223): get_record includes id in the projection it hands the engine, so output.id no longer depends on the author's fields. Measure first; the card is read from source.

    File surface at origin/main 35afb158:

    • packages/services/service-automation/src/builtin/crud-nodes.ts: get_record's findOne branch, the projection it hands serveFamilyRead and the engine.
    • Tests and changeset: tests in service-automation and one @objectstack/service-automation patch changeset.
      • The pin: on a SQL driver, with fields that omit id, output.id is the row's id.
      • Control: with no fields, the output is unchanged.
      • Whether record then carries id when the author did not name it is recorded in the PR.

    Exclusions:

    Stop on breach; explain in the report.
    Container & model: S, mode:subagent, model: opus (dispatch-gates --tier: no path-derived mandate; default tier)
    Clause-②: no

    • A declared node output (get_record's id) is honoured: no accept set or public signature moves.
      Responsibility: this repository's own code: get_record hands the author's fields to the engine unchanged and reads output.id from the result, so a projection without id answers undefined | none: no layer adds id to a narrowed projection on the SQL drivers | flow authors who narrow get_record's fields and reference the node's id downstream
      Thread-read: 6068120223
      Serial constraints cleared:
    • No open PR touches crud-nodes.ts. PR fix(objectql): a find/findOne projection that names a formula field returns that projection, not every stored column #22337 (domain:engine) adds builtin/get-record-formula-projection.integration.test.ts (declared on [PM seat] domain:services — ⏳ vacant #6021, 6066696545); if both change one fixture, the later rebases on the earlier.
  3. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 22344,
    "status": "done",
    "branch": "claude/issue-22344-get-record-id-projection",
    "pr": "#22355",
    "session": "session_01WkL6Eijt432S1Y7ekb6ovQ (the parent seat's id; this run is a subagent)",
    "premise_still_valid": true,
    "summary": "get_record's single-row (findOne) branch now hands the engine projectionNamingId(fields): the author's fields with 'id' appended, applied before serveFamilyRead. A projection that names id is handed as written, an absent or empty one is unchanged, and the find branch is untouched. So output.id, and {read.id} downstream, is the row id whatever fields names, on every driver and on the stored-metadata family path. H3: record carries the id the engine returned. PR #22337 landed mid-run, so origin/main 54c3ce1 was merged in and that PR's get-record-formula-projection test now expects id on its one-row cases (list cases unchanged; its engine trimming untouched).",
    "h1_reproduction": {
    "before": "origin/main 35afb15 plus test commit 4abddae, real ObjectQL via ObjectKernel+ObjectQLPlugin, get_record executor, runAs system: MEASURE driver=sql fields=["title"] output.id=undefined recordKeys=["title"]; driver=sql fields=undefined output.id=ROW_ID (10 columns); driver=memory fields=["title"] output.id=ROW_ID recordKeys=["id","title"]; driver=memory fields=undefined output.id=ROW_ID. Flow start→get_record(fields [title])→update_record(filter id '{read.id}') refused: 'update_record: refusing to run — 1 filter condition(s) resolved to nothing ... {read.id} (at id)'. New test file on main: 2 failed | 6 passed (8).",
    "after": "at f6e3ac4: get-record-id-projection 7/7 green (fields [title] → output.id == row id, record {id,title}, engine handed ['title','id']; flow {read.id} closes exactly the read row). Three get_record suites 23/23. Full package 178 files / 2177 tests passed."
    },
    "h2_placement": "Before serveFamilyRead, at the findOne call site only: serveFamilyRead(data, objectName, projectionNamingId(fields), cb). Measured against the alternative (inside the callback, fields: projectionNamingId(projection), one-off mutation at f6e3ac4): identical, 23/23 on the ordinary and family paths, because the family projection only adds and then drops 'type'. Shipped before serveFamilyRead so the family door sees the node's whole projection as its own. Not doubled: M2 ablation shows that without the includes('id') guard the engine is handed ['id','title','id']. Absent or [] stays a whole-row read: M3b and M4 ablations.",
    "h3_choice": "record carries id when the author did not name it. Reasons: the engine returned it, and stripping it would be a second projection layer; {rec.id} via outputVariable then agrees with {read.id} (stripping leaves the same defect on the variable spelling); every unprojected read, and every projected memory-driver read, already carries it. Two existing exact-column pins on the one-row branch were updated to named+id: the family-suite body-without-type case ([checksum,id,metadata,name], type still dropped) and the #22300 formula suite's one-row cases (id 'line_1').",
    "tests": "HEAD f6e3ac4 (code-identical to final f68b7d5; that commit changes only the changeset wording): pnpm --filter @objectstack/service-automation exec vitest run --maxWorkers=2 under os-verify-lock → 'Test Files 178 passed (178) / Tests 2177 passed (2177)', VERDICT command-exit 0 (pre-merge d6ccba3: 177/2169). pnpm --filter @objectstack/service-automation run typecheck → tsc clean + 'check:test-typecheck: OK — ... 0 file(s) / 0 error(s)', VERDICT command-exit 0. Targeted eslint --no-inline-config --format json on 4 changed .ts: files 4 errors 0 warnings 0 (config population packages/**/*.{ts,...} via --print-config; no type-aware linting in eslint.config.mjs, so untouched-file verdicts cannot move). Ablation at f6e3ac4 via scripts/ablation-replace.mjs (wrap mode, anchor x1→x0, blob 94641f32745d→mutant, restore proven blob == HEAD 94641f32745d and git diff HEAD empty, belt trap EXIT INT TERM; subject imported from source, no dist leg): M1 fix reverted → 7 red (omit-id, flow {read.id}, family projection, 4 formula one-row); M2 includes('id') guard dropped → 1 red, handed ['id','title','id']; M3b length===0 guard dropped → 1 red, handed ['id']; M4 absent fields → ['id'] → 6 red; H2-alt (callback placement) 23/23 green. First M3 attempt at d6ccba3 was a NO-OP: the replacement was a substring of the anchor, ablation-replace refused (replacement count 1→1) and ran nothing. Re-anchored as M3b.",
    "gates": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths) at f68b7d5: 64 commands; all 64 run at f68b7d5, 64 exit 0. --ran: '✓ dispatch-gates --ran: 64 derived famil(ies) accounted for — 64 run, 0 NOT-MEASURED (a DERIVED zero — all 64 recorded an exit code and none of them is 3)'. Same 64 as the seat's lead list. First pass at d6ccba3: check:dual-build-cjs-loads exit 3 PREREQUISITE NOT MET (no workspace dist). Re-run after building: '✓ check:dual-build-cjs-loads — 106 published require entry point(s) across 66 package(s) load'. Sample verdict lines: 'check-nul-bytes: OK (scanned 10335 text file(s) ...)', '✓ check:published-files — 69 publishable package(s) ...'. NOT MEASURED locally, CI-owned: Test Core, Temporal Conformance, Dogfood, Dogfood Verify CLI, Build Core, workspace type-check lanes, 52 artifact-roster and 11 wide-population families.",
    "files_changed": [
    ".changeset/22344-get-record-id-projection.md (patch, @objectstack/service-automation, Clause-②: no)",
    "packages/services/service-automation/src/builtin/crud-nodes.ts",
    "packages/services/service-automation/src/builtin/get-record-id-projection.integration.test.ts (new)",
    "packages/services/service-automation/src/builtin/get-record-stored-metadata-family.integration.test.ts",
    "packages/services/service-automation/src/builtin/get-record-formula-projection.integration.test.ts"
    ],
    "deviations": [
    "Merged origin/main 54c3ce1 into the branch (merge commit 0fbeb27) instead of rebasing: PR #22337 landed mid-run and its new get-record-formula-projection test pinned the one-row column set exactly (4 one-row cases went red on the merged tree before the fixture update). The triage ruling says rebase on whichever lands first. A merge reaches the same base without force-pushing, which AGENTS.md section 3 restricts.",
    "Edited that PR #22337 test file (one-row expectations gain id; list cases and that PR's objectql trimming untouched). Inside the scope fence's 'its tests', recorded here because the file is that PR's.",
    "PR footer: used AGENTS.md's session-URL footer form, not the harness reminder's '🤖 Generated with' form (os-dev definition: the harness attribution reminder yields).",
    "H1 driver-memory contrast came from a one-off scratch test copied untracked into the worktree (driver-memory is not a service-automation dependency, so it was imported from its built dist by path). It was deleted after the run and never committed.",
    "Killed my own gate runner (pid 2575, recorded) once to fix the changeset wording, which had angle-bracket spelling. Then re-derived and re-ran all 64 at f68b7d5.",
    "Ablation M3 first attempt was a no-op (see tests). Re-anchored and re-run, and the readings were re-taken at the final code head."
    ],
    "mcp_calls": "0",
    "api_writes": "3 — all through scripts/pm via the fleet-write relay (each a POST /repos/objectstack-ai/objectstack/dispatches executed as objectstack-fleet[bot]): (1) pr_create → POST /repos/objectstack-ai/objectstack/pulls (draft #22355, relay run 37848198989, read-back 8574 bytes identical); (2) label-write --assign os-bill → POST /repos//issues/22355/assignees (relay run 37848259609, read-back matches); (3) this os-dev-report comment → POST /repos//issues/22344/comments. git push (git op, not REST): empty-branch probe plus 5 commit pushes.",
    "open_questions": [],
    "out_of_scope_findings": [
    "carrier: none (承接者:无) · GetRecordConfigSchema.fields (spec) and the get_record descriptor describe fields as 'only these fields are read (default: all)'; the one-row branch now also serves id. packages/spec is fenced, so this is noted in the PR's Acceptance notes and not filed (not one of the three filing classes: wording, no wrong runtime answer)",
    "carrier: none (承接者:无) · driver-mongodb not measured (not a service-automation test dependency); noted in Acceptance notes"
    ]
    }


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    Seat ACCEPT: PR #22355 at f68b7d55c5 · seat domain:services#1 (#6021) · session_01WkL6Eijt432S1Y7ekb6ovQ · 2026-10-08T21:43Z

    Checked against GitHub and the branch, not the report's prose (os-dev-report 6069595763).

    • Form:
      • The PR is a draft. Its first line is Fixes #22344, with a line-start Clause-②: no. The PR assignee is os-bill.
      • Five files, +243/−5. check-governed-merges --pr 22355: NOT governed. It merges clean with main.
    • H1 reproduced on main, so the card stands as graded. Through the real ObjectQL and the get_record executor, fields: ['title'] answered output.id undefined on driver-sql, with the record carrying title only. driver-memory answered the id. A flow get_record → update_record filtering on {read.id} was refused for a condition that resolved to nothing.
    • The fix, read by the seat:
    • H2: placing it inside the projection callback measured identical (23 of 23 on the ordinary and family paths). The shipped placement is the one that keeps the family door's view whole.
    • H3, accepted: record carries the id the engine returned, so {var.id} through outputVariable agrees with {read.id}. Stripping it would be a second projection layer and would leave the same defect on the variable spelling.
    • Evidence:
      • The new pin file was 2 red on main; it is 7 of 7 green with the fix.
      • The three get_record suites pass 23 of 23. service-automation passes 178 files and 2177 tests.
      • Ablations: reverting the fix turns 7 red; dropping the includes('id') guard hands the engine ['id','title','id']; dropping the empty-list guard hands it ['id']; making an absent fields name id turns 6 red. Each restore is proven. One no-op mutation was caught by ablation-replace and re-anchored.
      • Gates: 64 of 64 derived, with --ran a derived zero.
    • Deviations, accepted:
      • origin/main was merged in, not rebased, after PR fix(objectql): a find/findOne projection that names a formula field returns that projection, not every stored column #22337 landed mid-run. Its new get-record-formula-projection test pinned the one-row column set exactly. Its one-row cases now expect id; its list cases and its engine trimming are unchanged. The file is in this lane's package, so no cross-lane declaration is owed. The domain:engine seat is told here, through this record on the card it noted.
      • The stored-metadata family suite's one-row body case now expects id beside the named columns; type is still dropped.
    • out_of_scope_findings: two, noted and not filed:
      • GetRecordConfigSchema.fields in packages/spec reads "only these fields are read (default: all)", and the one-row branch now also reads id. That is wording, with no wrong runtime answer; a domain:spec pass on the node descriptors can carry it.
      • driver-mongodb was not measured. It is not a service-automation test dependency, and the fix is driver-independent.
    • Landing to-do:
      • Every check on f68b7d55c5 must be green or an expected skip.
      • Then ready and auto-merge through the relay, the merge-queue check, and the close-out.
  5. objectstack-fleet commented on Oct 8, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · domain:services seat 1 (#6021) · session_01WkL6Eijt432S1Y7ekb6ovQ · 2026-10-08T22:48Z.

    PR #22355 merged through the merge queue as 43fc5005. On origin/main, @objectstack/service-automation (patch):

    • A get_record node's single-row branch names id in the projection it hands the engine, so output.id (and {<node>.id} downstream) is the row's id whatever fields names, on every driver.
    • The served record carries that id beside the named columns, so the outputVariable spelling agrees.
    • No fields, or an empty list, is still a whole-row read; a projection that names id is handed over as written; the multi-row branch is unchanged.

    The PR's Fixes line closed the card completed. This note also removes pm:dispatched and the assignee.

    • GetRecordConfigSchema.fields' description in packages/spec ("only these fields are read") does not mention id. That is wording only, left for a domain:spec pass on the node descriptors.
  6. added a commit that references this issue on Oct 9, 2026
    43fc500
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:workflowApprovals and automation — the work that runs without a person driving itbugSomething isn't workingdomain:servicespriority:p2Medium: important, M3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions