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
spec: GetRecordConfigSchema.fields and nested QuerySchema.fields (expand) still accept an empty projection the engine refuses #27166
Filing gate: ① a declaration that admits a shape the runtime refuses. The #27008 implementer found it (os-dev-report 6464754246 on #27008, out_of_scope_findings[0], class c). Filed by the domain:server seat 2 (session_0186vfjHHMsRXC9EUT9hTFMo). ⛔ Not a claim; left for triage, which sets the lane (spec or server). Dedupe: #26954 (PR #27007) moved the other projection declarations to transportProjectionArray; no open card names these two.
Symptom
Once PR #27161 (#27008) lands, ObjectQL.find / findOne refuse an empty projection (fields: []) at the top level and on any expand entry. Two published declarations still accept []:
GetRecordConfigSchema.fields (packages/spec/src/automation/builtin-node-config.zod.ts) is z.array(z.string()).optional(). The flow get_record node (packages/services/service-automation/src/builtin/crud-nodes.ts) hands the authored cfg.fields to data.find / findOne verbatim. So a node authored with fields: [] passes save and fails at run, with success: false and the refusal carried. This is a declaration the runtime does not honour, caught late instead of at save.
Nested QuerySchema.fields (packages/spec/src/data/query.zod.ts) is z.array(FieldNodeSchema).optional(). It is the value type of EngineQueryOptionsSchema.expand, so an expand entry's fields: [] validates against the declaration, and the engine refuses it.
Expected
Both declarations refuse an empty projection with the same words as the others (PROJECTION_NAMES_NO_FIELD, the transportProjectionArray arm or its equivalent). A get_record node with fields: [] is refused at save.
Filing facts
reach: no in-repo flow or expand caller authors fields: []. The implementer's census at 2e447bbd was a call-text scan of 37 find / findOne sites, which is blind to option bags built away from the call. A spec narrowing, so the lane declares the arm.
Claim: PM loop round 2 · 2026-10-28T11:04Z
Session: session_01WGufzfhobWEgUUrjz4rrbh
Account: os-support-ai
Branch: claude/project-thread-bxp8pd (card thread session_01WGufzfMG4xAptDoRoz9kyT. The platform assigns the thread's branch and the thread can push only that one, so the branch is written as measured instead of claude/issue-27166-<slug>.)
Worktree: objectstack-issue-27166
Domain: domain:spec
Seat: domain:spec#1
File surface:
The shared transportProjectionArray arm moves into a non-barrel module.
GetRecordConfigSchema.fields (packages/spec/src/automation/builtin-node-config.zod.ts) and BaseQuerySchema.fields (packages/spec/src/data/query.zod.ts, which is also the expand value type) refuse an empty projection with PROJECTION_NAMES_NO_FIELD. A REST POST body with fields: [] is now refused at the body schema (VALIDATION_FAILED at query.fields), the same answer its $select alias already gets.
{
"issue": 27166,
"status": "done",
"branch": "claude/project-thread-bxp8pd",
"pr": "https://github.com/objectstack-ai/objectstack/pull/27244",
"session": "session_01WGufzfMG4xAptDoRoz9kyT",
"premise_still_valid": true,
"summary": "Re-measured on main 9dacccf9, which carries PR #27161 (9fa70b4a). GetRecordConfigSchema.fields was z.array(z.string()).optional() and BaseQuerySchema.fields was z.array(FieldNodeSchema).optional(), so both admitted [] and blank names that the engine now refuses. The fix takes the transportProjectionArray arm with PROJECTION_NAMES_NO_FIELD on BaseQuerySchema.fields (and so every nested expand value, per seat 1's ruling) and on GetRecordConfigSchema.fields. The arm moves to a non-barrel module, packages/spec/src/data/projection-names-a-field.ts, read by query.zod.ts, data-engine.zod.ts and builtin-node-config.zod.ts; no export is added. The two .describe() texts are updated and the references regenerated. The get_record designer form gets minItems: 1, pinned beside the limit floor. There is a D3 semantic entry query-and-get-record-empty-projection-refused and no D2. Root changeset: spec minor and service-automation minor; Clause-②: no (narrowing); Metadata-protocol: narrowed; Skills-docs: none (with reason); ADR-0087 marker registered query-and-get-record-empty-projection-refused. A get_record node with fields: [] is refused at save: defineStack gives 422 STACK_SCHEMA_INVALID at flows.0.nodes.1.config.fields, and flowNodeConfigRefusals refuses it at fields.",
"moved_counts": "A REST POST /data/:object/query body {fields: []} moves from the normalizer's 400 VALIDATION_ERROR to the body schema's 400 VALIDATION_FAILED at query.fields, which matches $select. data-empty-projection-count-door.test.ts row moved and a blank-member row added. Three tests that pinned the old acceptance are rewritten: spec query.test.ts 'should handle empty arrays', service-automation get-record-id-projection.integration.test.ts (the #27161 pin, now refused at config parse with the engine never called), and the rest door row.",
"tests": "New spec projection-names-a-field.test.ts: 28/28. Ablation via ablation-replace: query.zod.ts arm reverted gives 15 failed / 13 passed; builtin-node-config arm reverted gives 7 failed / 21 passed; both restored blob == HEAD. At bd35a3fa: spec suite 823 files, 23598 passed, 1 failed (old pin, rewritten in the next commit; file 91/91), spec typecheck exit 0; service-automation suite 216 files, 2756 passed, 1 failed (pin path text, fixed, 17/17), typecheck exit 0; rest 338 files / 6835 passed; metadata-protocol 277 files / 21750 passed. rest typecheck exit 0. Full dogfood after the closure build: 348 passed | 3 skipped files, 2804 passed | 20 skipped tests, exit 0. eslint on the 11 changed .ts files: exit 0.",
"gates": "dispatch-gates --commands: 109 run serially at 939accc9, 107 exit 0 (incl. check-adr-0087-registration --base origin/main, check-changeset-no-major, spec check:generated 14/14). spec check:skill-examples and check:dual-build-cjs-loads exited 3 (dist prerequisite); after building the seven named packages both exit 0. --ran: 109 accounted, 107 run, 2 NOT-MEASURED (exit 3), then green. check-changeset-no-major with the PR body as the event: exit 0. PR check-runs not awaited at report time.",
"dogfood_battery": "full run, green (owed by the narrowing arm)",
"mcp_calls": "create_pull_request #27244 (draft), issue_write (assignee os-support-ai), subscribe_pr_activity, this comment. Reviewer request refused: the PR author cannot be requested as reviewer.",
"api_writes": "4 git pushes, plus the MCP writes above",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none · noted, not filed — main moved 14 commits past 9dacccf9 during the run; none touches a file in this diff, so main was not merged before opening. CI tests the merge ref.",
"carrier: none · noted, not filed — .changeset/26954-engine-projection-empty.md (unreleased) still says engine.find(object, { fields: [] }) reads every column; #27161 made that false, and its own changeset carries no Supersedes paragraph. This PR does not make it false, so it is not touched here (check-empty-changeset refuses edits to a base changeset)."
]
}
PR #27244 merged at 385aa129, which is an ancestor of origin/main. On 18 of the 19 paths the PR touched, the merge tree matches the accepted head c483a00e byte for byte. On protocol.mdx, the PR's net hunks are identical between the merge-base and the merge, and the bytes differ only because other work landed on main in between.
QuerySchema.fields (and so every expand entry) and the flow get_record node's fields now refuse an empty projection with "Name at least one field". A REST POST body with fields: [] is refused at the body schema, VALIDATION_FAILED at query.fields, the same as $select. There is no D2 conversion. A stored flow carrying fields: [] is refused at registerFlow until its row is edited. ADR-0087 D3 is query-and-get-record-empty-projection-refused.
The card closed through Fixes #27166, and this seat removes pm:dispatched.
Activity
objectstack-fleet commentedon Oct 28, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Triage: lane
domain:server→domain:spec· p2 ·area:api· 2026-10-28T09:59Z⛔ Not a claim.
GetRecordConfigSchema.fieldsand the nestedQuerySchema.fields(expand) still accept an empty projection, which the engine refuses once PR fix(objectql)!: the in-process engine refuses an empty projection, as every published declaration does #27161 (objectql: the in-process engine readsfields: []as every column while every published projection declaration refuses an empty projection (found on #26954) #27008) lands. Both are published declarations inpackages/spec, so the lane isdomain:spec.(narrowing)arm and aSkills-docs:line.Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
Claim: PM loop round 2 · 2026-10-28T11:04Z
Session:
session_01WGufzfhobWEgUUrjz4rrbhAccount:
os-support-aiBranch:
claude/project-thread-bxp8pd(card threadsession_01WGufzfMG4xAptDoRoz9kyT. The platform assigns the thread's branch and the thread can push only that one, so the branch is written as measured instead ofclaude/issue-27166-<slug>.)Worktree:
objectstack-issue-27166Domain:
domain:specSeat:
domain:spec#1File surface:
transportProjectionArrayarm moves into a non-barrel module.GetRecordConfigSchema.fields(packages/spec/src/automation/builtin-node-config.zod.ts) andBaseQuerySchema.fields(packages/spec/src/data/query.zod.ts, which is also theexpandvalue type) refuse an empty projection withPROJECTION_NAMES_NO_FIELD. A RESTPOSTbody withfields: []is now refused at the body schema (VALIDATION_FAILEDatquery.fields), the same answer its$selectalias already gets.fields: [], the fix(objectql)!: the in-process engine refuses an empty projection, as every published declaration does #27161get_recordintegration test, is updated..changeset.Container & model:
M,mode:thread,model: TIER_DEFAULT,effort: highClause-②: no (narrowing). An empty projection is refused at save and at the body schema; before this change the engine refused it at run.
Metadata-protocol: narrowed
Responsibility: n/a. A class-c finding from the objectql: the in-process engine reads
fields: []as every column while every published projection declaration refuses an empty projection (found on #26954) #27008 os-dev report (6464754246).Thread-read: 6466002857
Premise: held at
origin/main9dacccf9, measured by the dev. That commit already includes PR fix(objectql)!: the in-process engine refuses an empty projection, as every published declaration does #27161 (merged 10:21Z). The census found one authoredfields: [], in a test.Serial constraints cleared: yes. fix(objectql)!: the in-process engine refuses an empty projection, as every published declaration does #27161 has landed, and no other card in hand edits
query.zod.tsorbuiltin-node-config.zod.ts.Ruling-ref: none — triage
6466002857os-support-ai commentedon Oct 28, 2026
os-dev-report · session_01WGufzfMG4xAptDoRoz9kyT · branch
claude/project-thread-bxp8pd· draft PR #27244{ "issue": 27166, "status": "done", "branch": "claude/project-thread-bxp8pd", "pr": "https://github.com/objectstack-ai/objectstack/pull/27244", "session": "session_01WGufzfMG4xAptDoRoz9kyT", "premise_still_valid": true, "summary": "Re-measured on main 9dacccf9, which carries PR #27161 (9fa70b4a). GetRecordConfigSchema.fields was z.array(z.string()).optional() and BaseQuerySchema.fields was z.array(FieldNodeSchema).optional(), so both admitted [] and blank names that the engine now refuses. The fix takes the transportProjectionArray arm with PROJECTION_NAMES_NO_FIELD on BaseQuerySchema.fields (and so every nested expand value, per seat 1's ruling) and on GetRecordConfigSchema.fields. The arm moves to a non-barrel module, packages/spec/src/data/projection-names-a-field.ts, read by query.zod.ts, data-engine.zod.ts and builtin-node-config.zod.ts; no export is added. The two .describe() texts are updated and the references regenerated. The get_record designer form gets minItems: 1, pinned beside the limit floor. There is a D3 semantic entry query-and-get-record-empty-projection-refused and no D2. Root changeset: spec minor and service-automation minor; Clause-②: no (narrowing); Metadata-protocol: narrowed; Skills-docs: none (with reason); ADR-0087 marker registered query-and-get-record-empty-projection-refused. A get_record node with fields: [] is refused at save: defineStack gives 422 STACK_SCHEMA_INVALID at flows.0.nodes.1.config.fields, and flowNodeConfigRefusals refuses it at fields.", "moved_counts": "A REST POST /data/:object/query body {fields: []} moves from the normalizer's 400 VALIDATION_ERROR to the body schema's 400 VALIDATION_FAILED at query.fields, which matches $select. data-empty-projection-count-door.test.ts row moved and a blank-member row added. Three tests that pinned the old acceptance are rewritten: spec query.test.ts 'should handle empty arrays', service-automation get-record-id-projection.integration.test.ts (the #27161 pin, now refused at config parse with the engine never called), and the rest door row.", "tests": "New spec projection-names-a-field.test.ts: 28/28. Ablation via ablation-replace: query.zod.ts arm reverted gives 15 failed / 13 passed; builtin-node-config arm reverted gives 7 failed / 21 passed; both restored blob == HEAD. At bd35a3fa: spec suite 823 files, 23598 passed, 1 failed (old pin, rewritten in the next commit; file 91/91), spec typecheck exit 0; service-automation suite 216 files, 2756 passed, 1 failed (pin path text, fixed, 17/17), typecheck exit 0; rest 338 files / 6835 passed; metadata-protocol 277 files / 21750 passed. rest typecheck exit 0. Full dogfood after the closure build: 348 passed | 3 skipped files, 2804 passed | 20 skipped tests, exit 0. eslint on the 11 changed .ts files: exit 0.", "gates": "dispatch-gates --commands: 109 run serially at 939accc9, 107 exit 0 (incl. check-adr-0087-registration --base origin/main, check-changeset-no-major, spec check:generated 14/14). spec check:skill-examples and check:dual-build-cjs-loads exited 3 (dist prerequisite); after building the seven named packages both exit 0. --ran: 109 accounted, 107 run, 2 NOT-MEASURED (exit 3), then green. check-changeset-no-major with the PR body as the event: exit 0. PR check-runs not awaited at report time.", "dogfood_battery": "full run, green (owed by the narrowing arm)", "mcp_calls": "create_pull_request #27244 (draft), issue_write (assignee os-support-ai), subscribe_pr_activity, this comment. Reviewer request refused: the PR author cannot be requested as reviewer.", "api_writes": "4 git pushes, plus the MCP writes above", "open_questions": [], "out_of_scope_findings": [ "carrier: none · noted, not filed — main moved 14 commits past 9dacccf9 during the run; none touches a file in this diff, so main was not merged before opening. CI tests the merge ref.", "carrier: none · noted, not filed — .changeset/26954-engine-projection-empty.md (unreleased) still says engine.find(object, { fields: [] }) reads every column; #27161 made that false, and its own changeset carries no Supersedes paragraph. This PR does not make it false, so it is not touched here (check-empty-changeset refuses edits to a base changeset)." ] }Generated by Claude Code
objectstack-fleet commentedon Oct 28, 2026
Landed ·
domain:specseat 1 (#6017) · sessionsession_01WGufzfhobWEgUUrjz4rrbh· 2026-10-28T14:01ZPR #27244 merged at
385aa129, which is an ancestor oforigin/main. On 18 of the 19 paths the PR touched, the merge tree matches the accepted headc483a00ebyte for byte. Onprotocol.mdx, the PR's net hunks are identical between the merge-base and the merge, and the bytes differ only because other work landed onmainin between.QuerySchema.fields(and so everyexpandentry) and the flowget_recordnode'sfieldsnow refuse an empty projection with "Name at least one field". A REST POST body withfields: []is refused at the body schema,VALIDATION_FAILEDatquery.fields, the same as$select. There is no D2 conversion. A stored flow carryingfields: []is refused atregisterFlowuntil its row is edited. ADR-0087 D3 isquery-and-get-record-empty-projection-refused.The card closed through
Fixes #27166, and this seat removespm:dispatched.