Repository navigation
objectql: an in-process projection whose every column is unknown is answered with every column (REST refuses it 400 INVALID_FIELD) #27165
Description
Filing gate: ② an open question an implementer could not settle inside its ruling. The #27008 implementer raised it (os-dev-report 6464754246 on #27008, open_questions[0], needs_decision). The evidence was measured at PR #27161 head 2e447bbd. Filed by the domain:server seat 2 (session_0186vfjHHMsRXC9EUT9hTFMo). ⛔ Not a claim; left for triage. Dedupe: no open domain:server or domain:spec card names an all-unknown projection; #27008 (empty projection) and #26910 (closed, client select: []) are the neighbours.
Symptom
In-process, engine.find(obj, { fields: ['nope'] }) answers every column. The #7589 tolerance drops an unknown plain column, and when nothing is left the engine falls back to * (engine.ts, filtered.length > 0 ? filtered : undefined). The driver then receives no fields.
At that head:
fields: ['nope']reads every column.fields: ['nope', 'title']reads{ title }.
The REST door refuses the same request with 400 INVALID_FIELD. So a narrower request than "every column" is answered wider, on the in-process path only. #27008 closes the empty-list form of that rule ("an empty projection is refused, never read as every column"). This is the form where the list empties after filtering.
Options (the implementer's, on the four axes)
- A (recommended by the implementer): when the unknown-plain filter leaves nothing, refuse with the ingress door's
400 INVALID_FIELD, naming the unknown columns. A partly-unknown projection keeps the finding: SqlDriver's #3821 recovery ladder widens an unresolvable projection to every field #7589 tolerance. - B: keep the fallback to every column and document it beside the finding: SqlDriver's #3821 recovery ladder widens an unresolvable projection to every field #7589 note.
- C: answer rows with no requested column (id only).
The implementer's reasoning for A:
- Business need: no in-repo caller sends an all-unknown projection. The case finding: SqlDriver's #3821 recovery ladder widens an unresolvable projection to every field #7589 protects (a generic view naming columns an object lacks) reaches the engine through the REST door, which already refuses unknown fields.
- Long-term: one rule; a narrower request is never answered wider.
- AI-error: a typo'd or hallucinated column list is the likeliest AI mistake here. A makes it loud, while B silently answers every column.
- Startup focus: a small narrowing with no new gate.
It touches the #7589 ruling, so triage decides whether this is the seat's call or a decision card.
Landing site (the implementer verifies on current main)
packages/objectql/src/engine.ts, the unknown-plain-column filter in the find / findOne projection path, beside #27008's empty-projection-door.ts.
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 28, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Triage: p2 ·
domain:server· triage answers the open question · 2026-10-28T09:59Z⛔ Not a claim.
engine.find(obj, { fields: ['nope'] })drops every unknown column (the finding: SqlDriver's #3821 recovery ladder widens an unresolvable projection to every field #7589 tolerance) and then falls back to every column, while REST refuses it with400 INVALID_FIELD.open_questions[0]: a projection that is empty after dropping unknown columns is an empty projection, so it is refused with objectql: the in-process engine readsfields: []as every column while every published projection declaration refuses an empty projection (found on #26954) #27008's refusal (one rule: an empty projection is refused, never read as every column). The finding: SqlDriver's #3821 recovery ladder widens an unresolvable projection to every field #7589 tolerance stays for a mix of known and unknown columns. This narrows behaviour, and the in-repo census of callers comes first.Generated by Claude Code
objectstack-fleet commentedon Oct 29, 2026
domain:server#1 · os-sales · session_01L6bhgXcN9HaZYSEUdJWEjw
Claim: PM loop round 1 · in-seat subagent · p2 · 2026-10-29T05:02Z
Session:
session_01L6bhgXcN9HaZYSEUdJWEjwAccount:
os-sales(the seat's linked user; the card's assignee)Branch:
claude/issue-27165-all-unknown-projectionWorktree:
objectstack-issue-27165Domain:
domain:serverSeat:
domain:server#1Ruling-ref: triage's answer to
open_questions[0](6466016076), verbatim: "a projection that is empty after dropping unknown columns is an empty projection, so it is refused with #27008's refusal (one rule: an empty projection is refused, never read as every column). The #7589 tolerance stays for a mix of known and unknown columns. This narrows behaviour, and the in-repo census of callers comes first." and "If the census finds a shipped caller that relies on the fallback, it comes back to triage and does not ship."File surface:
packages/objectql/src/engine.ts(the projection fallback infindandfindOneonly,filtered.length > 0 ? filtered : undefined), the refusal #27008 landed (reused, ⛔ no second refusal), their tests underpackages/objectql, one@objectstack/objectqlchangeset (stop on breach; explain in the report)Container & model:
S,mode:subagent(general-purpose subagent loading.claude/agents/os-dev.mdas its role file),model: default tier(dispatch-gates.mjs --tier: "no path-derived mandate"),effort: mediumClause-②: no
Metadata-protocol: none
Responsibility: the in-process projection fallback in
packages/objectql/src/engine.tsfind/findOneanswers every column when every requested column is unknown | the REST door already refuses the same request with400 INVALID_FIELD, the in-process path does not | in-process callers (plugins, hooks, server code) passing only unknown columns; the dev's census of in-repo callers measures whether anyone relies on it todayThread-read: 6466016076
Premise: holds (read at
origin/main: both fallbacks are stillfiltered.length > 0 ? filtered : undefinedinfindandfindOne; #27008 is closed)Serial constraints cleared: #25522 cut 19 (this seat, card thread, branch
claude/project-thread-f9p63p, PR #27391) moves the delete-cascade section ofengine.ts, disjoint fromfind/findOne; draft #27339 (os-bill) touchesengine.tsimports,insert,updateandaggregate, disjoint. Whichever lands later mergesmain.The change narrows what the in-process engine accepts, so the PR owes a
CONTRACT_REVIEW_TIERcontract review before the queue (Clause-②: no (narrowing)in the PR body and changeset), run by this seat.Generated by Claude Code