Skip to content

objectql: an in-process projection whose every column is unknown is answered with every column (REST refuses it 400 INVALID_FIELD) #27165

Open

Description

@objectstack-fleet

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)

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

  1. added
    area:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobs
    domain:serverDomain lane — seat card indexed by label:pm:seat
    on Oct 28, 2026
  2. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Triage: p2 · domain:server · triage answers the open question · 2026-10-28T09:59Z

    ⛔ Not a claim.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-sales · session_01L6bhgXcN9HaZYSEUdJWEjw

    Claim: PM loop round 1 · in-seat subagent · p2 · 2026-10-29T05:02Z
    Session: session_01L6bhgXcN9HaZYSEUdJWEjw
    Account: os-sales (the seat's linked user; the card's assignee)
    Branch: claude/issue-27165-all-unknown-projection
    Worktree: objectstack-issue-27165
    Domain: domain:server
    Seat: domain:server#1
    Ruling-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 in find and findOne only, filtered.length > 0 ? filtered : undefined), the refusal #27008 landed (reused, ⛔ no second refusal), their tests under packages/objectql, one @objectstack/objectql changeset (stop on breach; explain in the report)
    Container & model: S, mode:subagent (general-purpose subagent loading .claude/agents/os-dev.md as its role file), model: default tier (dispatch-gates.mjs --tier: "no path-derived mandate"), effort: medium
    Clause-②: no
    Metadata-protocol: none
    Responsibility: the in-process projection fallback in packages/objectql/src/engine.ts find/findOne answers every column when every requested column is unknown | the REST door already refuses the same request with 400 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 today
    Thread-read: 6466016076
    Premise: holds (read at origin/main: both fallbacks are still filtered.length > 0 ? filtered : undefined in find and findOne; #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 of engine.ts, disjoint from find/findOne; draft #27339 (os-bill) touches engine.ts imports, insert, update and aggregate, disjoint. Whichever lands later merges main.

    The change narrows what the in-process engine accepts, so the PR owes a CONTRACT_REVIEW_TIER contract review before the queue (Clause-②: no (narrowing) in the PR body and changeset), run by this seat.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-sales · session_01L6bhgXcN9HaZYSEUdJWEjw

    os-dev-report
    {
    "issue": 27165,
    "status": "done",
    "branch": "claude/issue-27165-all-unknown-projection",
    "pr": "#27541",
    "session": "session_01L6bhgXcN9HaZYSEUdJWEjw (domain:server#1 · os-sales, mode:subagent; reported at 2026-10-29T11:51Z)",
    "premise_still_valid": true,
    "summary": "Census first: no shipped in-repo caller relies on the all-unknown fallback. Of 278 non-test find/findOne calls whose arguments mention fields, 166 name 'id'; the rest name declared columns, prepend 'id', are IDataDriver calls, or reach the engine over REST. The flow get_record findOne arm adds 'id'; the one in-repo authored projection (recalc-estimate) names declared columns. Then the fix: in packages/objectql/src/engine.ts, find and findOne now refuse when the unknown-plain filter leaves nothing. They call refuseProjectionOfUnknownColumns, a thin export over #27008's refuseEmptyProjection in empty-projection-door.ts: same function, 400 VALIDATION_ERROR, the declaration's words, and 'got' now names the requested columns. A mix of known and unknown columns keeps the #7589 tolerance. expand needed no new arm: its sub-read always names 'id', so an all-unknown nested projection already reads {id} (measured: expands to {}). Two existing tests pinned the old fallback and now expect the refusal: the [#7589 GUARD] 'falls back to every field' case, and the #21571 'projection naming ONLY a retired column' case. Measured divergence: the REST door answers this request 400 INVALID_FIELD and the engine answers 400 VALIDATION_ERROR (the ruling's choice), same status, different code.",
    "tests": "At head 68ff239 (after merging main f57eddc), one locked run of the dogfood^... build, objectql typecheck and test, and dogfood test: VERDICT command-exit 0. objectql Test Files 483/483, Tests 9091/9091. Red first: the new [#27165] block against the unfixed engine (8993840) gave 'Tests 2 failed | 11 passed (13)', 'expected a refusal, got an answer'; green 13/13. Ablation through scripts/ablation-replace.mjs on source (the test imports ./engine.js), one leg per site, each restoring 'ast.fields = filtered.length > 0 ? filtered : undefined;'. find leg: anchor 1->0, blob 7697ee2b->8beef8e9, Tests 1 failed (the find case). findOne leg: anchor 1->0, blob 7697ee2b->56a22aca, Tests 1 failed (the findOne case). Both restored: blob == HEAD 7697ee2b, git diff HEAD empty. A probe (temp file, deleted, never committed) measured the REST door, the engine and expand, recorded in the PR body.",
    "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack at 68ff239: 63 derived, all run, each exit 0. Two were reruns after the build that cleared their prerequisite: check:dts-closure (first run raced this worktree's own closure build) and check:dual-build-cjs-loads (first run exit 3, PREREQUISITE NOT MET). check:type-check-debt ran under the lock (VERDICT 0). The --ran reconcile with exit codes printed '63 derived famil(ies) accounted for — 63 run, 0 NOT-MEASURED (a DERIVED zero …)', exit 0. eslint packages/objectql --no-inline-config: exit 0. check:commit-card-trailers passed on every push. CI on PR #27541: check-runs were in_progress at report time (Lint *, Type Check *, Governed Surface Queue Guard, Check Changeset, PR-body guards), conclusions not yet read.",
    "dogfood_battery": "68ff2394 · os-verify-lock VERDICT command-exit 0 · Test Files 348 passed | 3 skipped (351) · Tests 2806 passed | 20 skipped (2826) · no failures. An earlier run at a04167c had 4 tests time out at 5000 ms under box load (packaged-flow-write-door-parity, rest-route-generation-tombstones tsc cases). Rerun alone: 15/15. Green in the final-head run.",
    "line_budget": "6 files, +121 / -21 against the merge-base: engine.ts +8/-6, empty-projection-door.ts +33/-2, engine-empty-projection-refusal.test.ts +45, query-expression-conformance.test.ts and unprojected-read-declared-fields-conformance.test.ts (old pins updated), and one changeset +19",
    "files_changed": [
    ".changeset/27165-engine-all-unknown-projection-refused.md",
    "packages/objectql/src/empty-projection-door.ts",
    "packages/objectql/src/engine.ts",
    "packages/objectql/src/engine-empty-projection-refusal.test.ts",
    "packages/objectql/src/query-expression-conformance.test.ts",
    "packages/objectql/src/unprojected-read-declared-fields-conformance.test.ts"
    ],
    "deviations": [
    "Two test files outside the named refusal test, query-expression-conformance.test.ts and unprojected-read-declared-fields-conformance.test.ts, were edited. They pinned the old fallback, and the narrowing turns them red otherwise. They are objectql tests, inside the claim's 'their tests under packages/objectql'.",
    "PR body Skills-docs line is 'follow-up owed — content/docs/data-modeling/queries.mdx'. That docs file is outside the claim's file surface, so the seat files or carries it.",
    "Per the coordinator's later rule change (c5d4634, #27385), pr_create carried no assignees and pr_request_reviewers was not called. Neither step was ever done, so there is nothing to undo.",
    "Report comment layout: post-stamped refuses any first line but the seat stamp ('domain:server#1 · os-sales · session_…'), so the stamp is line 1 and the plain-text 'os-dev-report' marker is the line directly under it, followed by the JSON. os-dev.md asks for the marker as the first line.",
    "Full dogfood ran twice: once at a04167c, and again at the final head 68ff239 after the coordinator's re-fetch order brought objectql changes in from main."
    ],
    "mcp_calls": "0 — no MCP GitHub tools used",
    "api_writes": "2 — relay pr_create (repository_dispatch to objectstack-ai/objectstack, which created draft PR #27541) and this os-dev-report comment through post-stamped on #27165. git pushes are not REST writes.",
    "open_questions": [],
    "out_of_scope_findings": [
    "class: none filed · the two doors answer one request with different codes: REST select ['nope'] gives 400 INVALID_FIELD, and in-process fields ['nope'] gives 400 VALIDATION_ERROR (the ruling's choice, measured here). Noted in the PR Acceptance notes. carrier: the seat, for the contract review · noted, not filed",
    "class: none filed · PR #27477 (draft) edits engine.ts near the imports and near both find/findOne filter sites, and #27339 near the imports. Whichever lands later merges main. carrier: none · noted, not filed"
    ]
    }

  5. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#1 · os-sales · session_01L6bhgXcN9HaZYSEUdJWEjw

    Claim amendment: file surface · 2026-10-29T12:10Z
    File surface (added): content/docs/data-modeling/queries.mdx, the unknown-field Callout only. The PR's Skills-docs: line names it as owed: with this narrowing, a top-level fields projection whose every entry is unknown (an object-form field node included) is now refused with 400 rather than falling back to every column, and the Callout says it is silently dropped. A prose-only head move: the contract review of record (6496221513, PASS) keeps governing, per contract-review.md (prose faces). The rest of the claim stands.

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

Metadata

Metadata

Assignees

Labels

area:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobsdomain:serverDomain lane — seat card indexed by label:pm:seatpm:dispatchedpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions