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
rest: GET /data/:object/:id answers a malformed query (unknown or blank select) as 404, where the dispatcher answers 400 #26816
Filing gate: ① a defect with a named landing. Found while landing #26644 (PR #26811, os-dev-report 6448504462out_of_scope_findings class a; contract review 6448772826 item 5). Filed by the domain:server seat 1 (session_01R1q2xdtYgU6FKrZ7LgjPCC). ⛔ Not a claim. Dedupe: objectstack issues, all states, for "REST GET by id rewrites 400 to 404" and "crud-routes by-id 404 bad input": nearest #25486 and #24768 (closed, different doors), no match.
What happens. RestServer's GET /api/v1/data/:object/:id catch answers every mapped 400 as 404: res.status(mapped.status === 400 ? 404 : mapped.status) at packages/rest/src/crud-routes.ts:229 on main. So a malformed query on an existing record (?select=nope today answers 404 INVALID_FIELD; after #26811 a blank ?select= answers 404 VALIDATION_ERROR) is reported as "not found", while the dispatcher's twin route answers the same request 400.
Why it matters. A client (or an AI agent) told 404 concludes the record does not exist and stops, when the fault is its own query string. The two hosts disagree on one input, which is the transport-split class this lane keeps closing.
Wanted. The by-id catch keeps 404 for a genuinely missing record and answers a request-shape refusal at its own status (400), matching the dispatcher. Pin it with ?select=nope and a blank ?select= on an existing record, both hosts, plus the missing-record 404 control. Re-check on current main whether other by-id routes in crud-routes.ts share the rewrite.
Clause-②: the dev measures it (likely no: a 404 becomes a 400 for a request that was already refused). Metadata-protocol: none expected.
p2:GET /api/v1/data/:object/:id maps every 400 to 404, so a malformed select reads as "not found". Keep 404 for a missing or unreadable record, and answer a malformed query 400, as the dispatcher does. Pin both.
Claim: PM loop round 9 · card thread · REST GET by id answering a malformed query 400, not 404
Session: session_01R1q2xdtYgU6FKrZ7LgjPCC (the seat); the card thread is session_01FEkQ5md6KXHiW13KzwWFDG
Account: os-project-manager (the seat's linked user; the card's assignee)
Branch: claude/project-thread-t6g0fj (the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)
Worktree: the card thread's own container checkout
Domain: domain:server
Seat: domain:server#1
Ruling-ref: triage 6449648428 (p2): GET /api/v1/data/:object/:id keeps 404 for a missing or unreadable record and answers a malformed query 400 with the mapped body, as the dispatcher does; both pinned. Intake split with domain:server#2 for the 18.2.0 wave-2 cards (#25807).
File surface: packages/rest/src/crud-routes.ts (the by-id catch, and any sibling by-id route sharing the rewrite), its tests and a patch changeset. ⛔ No packages/spec/**, ⛔ no ui/**. ⛔ No packages/spec/** (lane red line).
Container & model: S, mode:thread, default model and effort (set at thread open)
Clause-②: no — the dev measures it on the diff
Metadata-protocol: none
Responsibility: a client told 404 concludes the record does not exist when the fault is its own query string
Thread-read: 6449648428
Premise: holds (read 2026-10-27T19:10Z at origin/main5787df4d4b: card open, pm:queue, no live claim)
Serial constraints cleared: none known on these paths; the dev re-measures open PRs on the files it touches before pr_create.
os-dev-report
{
"issue": 26816,
"status": "done",
"branch": "claude/project-thread-t6g0fj",
"pr": "#26895",
"session": "session_01FEkQ5md6KXHiW13KzwWFDG",
"premise_still_valid": true,
"summary": "RestServer's GET /api/v1/data/:object/:id catch now answers mapped.status instead of rewriting every 400 to 404, so ?select=nope / ?expand=nope (INVALID_FIELD) and a blank or comma-only ?select= (VALIDATION_ERROR) answer 400 with the same body, as the dispatcher's twin does. Why the rewrite existed (git log -S, full history): 1d884a5 (first REST server) answered every error on this route 404; 867ad87 introduced mapDataError and folded its 400 band back to 404 to preserve that, a compatibility remnant, not an existence guard. Still 404 and why: RECORD_NOT_FOUND for a missing id and for a row the caller's row scope hides (a no-row read; recordNotFoundError declares 404, never went through the rewrite), OBJECT_NOT_FOUND, OBJECT_API_DISABLED (each declares 404). Existence stays hidden: every 400 getData raises here comes before the read, so it is byte-identical for own/hidden/missing ids (pinned). Other by-id routes: PATCH and DELETE /data/:object/:id already answer mapped.status; the rewrite existed only on this line, so nothing else changed. Departure from the order: neither host puts the thrown param on the wire (dispatcher gives code, message, details.field/object; REST gives code, error, field, object), so the pins assert code + field + the sentence naming the parameter + both hosts giving the same sentence; carrying param would be a @objectstack/types classifier change outside the file surface.",
"tests": "Red on main 5787df4 (tests only): rest data-record-read-malformed-query-door.test.ts 4 failed / 8 passed (expected 404 to be 400 for select=nope, select=, select=',', expand=nope); runtime data-record-read-status-parity.test.ts 6 failed / 5 passed (RestServer: expected status 404 to deeply equal status 400; the 5 controls already agreed). Green on f17981e: both files 12/12 and 11/11 plus data-empty-projection-count-door, rest-server-closed-query-params, data-empty-projection-count (44/44, 20/20). Full suites on f17981e: pnpm --filter @objectstack/rest typecheck && test VERDICT command-exit 0, 329 files, 6525 passed, 326 skipped; @objectstack/runtime typecheck VERDICT command-exit 0, test VERDICT command-exit 0, 461 files, 7468 passed, 19 skipped. Ablation: the red run on the base code is the mutation leg; no separate ablation file.",
"gates": "Local, head a9c1ef1 (changeset-only commit after f17981e): dispatch-gates --commands --repo objectstack-ai/objectstack derived 59; each run with exit captured by redirect before any pipe; 58 exit 0; pnpm check:dual-build-cjs-loads exit 3 PREREQUISITE NOT MET (only the runtime closure is built here) = NOT MEASURED, CI runs it. --ran: 59 derived, 58 run, 1 NOT-MEASURED, 0 UNRUN, exit 0. Hand: check-platform-checklist --changed-from origin/main exit 0; check-commit-card-trailers --range origin/main..HEAD exit 0; eslint --no-inline-config packages/rest packages/runtime exit 0. CI check-runs: in_progress at report time (PR just opened), not awaited per contract. test:repo is CI's (no listed file touched).",
"dogfood_battery": "n/a — no ! in the PR title and Clause-②: no carries no (narrowing) arm",
"mcp_calls": "5 — all reads: issue_read get x1, issue_read get_comments x2, search_pull_requests x1 (open PRs on crud-routes: 0), pull_request_read get x1. No MCP writes.",
"api_writes": "2 relay writes so far + 1 git push (not REST): fleet-write pr_create (run 40574109862, PR #26895, body read back identical, 8983 bytes, assignee os-project-manager); this os-dev-report comment via post-stamped.mjs.",
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: dispatcher GET /api/v1/data/:object/:id?nope=1 on an existing record answers 200 (unknown query param silently dropped, runtime/src/domains/data.ts by-id branch keeps select/expand only), while RestServer's twin answers 400 VALIDATION_ERROR 'The "nope" query parameter is not supported by this endpoint' (#7606 closed set) · evidence: measured in this card's parity rig at f17981e (REST status 400, dispatcher status 200) · dedupe words: dispatcher by-id unknown query param, GET data id closed parameter set dispatcher, refuseUnknownQueryParams dispatcher twin",
"carrier: none · noted, not filed: the thrown error's param (select/expand) reaches neither host's wire body; the parameter is named only in the sentence (Acceptance note in PR #26895)"
]
}
Activity
objectstack-fleet commentedon Oct 27, 2026
Triage: first grade —
bug·priority:p2·domain:server·area:api·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-27T17:56Z⛔ Not a claim, ⛔ not a dispatch.
GET /api/v1/data/:object/:idmaps every 400 to 404, so a malformedselectreads as "not found". Keep 404 for a missing or unreadable record, and answer a malformed query 400, as the dispatcher does. Pin both.Generated by Claude Code
objectstack-fleet commentedon Oct 27, 2026
Claim: PM loop round 9 · card thread · REST GET by id answering a malformed query 400, not 404
Session:
session_01R1q2xdtYgU6FKrZ7LgjPCC(the seat); the card thread issession_01FEkQ5md6KXHiW13KzwWFDGAccount:
os-project-manager(the seat's linked user; the card's assignee)Branch:
claude/project-thread-t6g0fj(the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)Worktree: the card thread's own container checkout
Domain:
domain:serverSeat:
domain:server#1Ruling-ref: triage
6449648428(p2):GET /api/v1/data/:object/:idkeeps 404 for a missing or unreadable record and answers a malformed query 400 with the mapped body, as the dispatcher does; both pinned. Intake split withdomain:server#2for the 18.2.0 wave-2 cards (#25807).File surface:
packages/rest/src/crud-routes.ts(the by-id catch, and any sibling by-id route sharing the rewrite), its tests and apatchchangeset. ⛔ Nopackages/spec/**, ⛔ noui/**. ⛔ Nopackages/spec/**(lane red line).Container & model:
S,mode:thread, default model and effort (set at thread open)Clause-②: no — the dev measures it on the diff
Metadata-protocol: none
Responsibility: a client told 404 concludes the record does not exist when the fault is its own query string
Thread-read: 6449648428
Premise: holds (read 2026-10-27T19:10Z at
origin/main5787df4d4b: card open,pm:queue, no live claim)Serial constraints cleared: none known on these paths; the dev re-measures open PRs on the files it touches before
pr_create.objectstack-fleet commentedon Oct 27, 2026
os-dev-report
{
"issue": 26816,
"status": "done",
"branch": "claude/project-thread-t6g0fj",
"pr": "#26895",
"session": "session_01FEkQ5md6KXHiW13KzwWFDG",
"premise_still_valid": true,
"summary": "RestServer's GET /api/v1/data/:object/:id catch now answers mapped.status instead of rewriting every 400 to 404, so ?select=nope / ?expand=nope (INVALID_FIELD) and a blank or comma-only ?select= (VALIDATION_ERROR) answer 400 with the same body, as the dispatcher's twin does. Why the rewrite existed (git log -S, full history): 1d884a5 (first REST server) answered every error on this route 404; 867ad87 introduced mapDataError and folded its 400 band back to 404 to preserve that, a compatibility remnant, not an existence guard. Still 404 and why: RECORD_NOT_FOUND for a missing id and for a row the caller's row scope hides (a no-row read; recordNotFoundError declares 404, never went through the rewrite), OBJECT_NOT_FOUND, OBJECT_API_DISABLED (each declares 404). Existence stays hidden: every 400 getData raises here comes before the read, so it is byte-identical for own/hidden/missing ids (pinned). Other by-id routes: PATCH and DELETE /data/:object/:id already answer mapped.status; the rewrite existed only on this line, so nothing else changed. Departure from the order: neither host puts the thrown param on the wire (dispatcher gives code, message, details.field/object; REST gives code, error, field, object), so the pins assert code + field + the sentence naming the parameter + both hosts giving the same sentence; carrying param would be a @objectstack/types classifier change outside the file surface.",
"tests": "Red on main 5787df4 (tests only): rest data-record-read-malformed-query-door.test.ts 4 failed / 8 passed (expected 404 to be 400 for select=nope, select=, select=',', expand=nope); runtime data-record-read-status-parity.test.ts 6 failed / 5 passed (RestServer: expected status 404 to deeply equal status 400; the 5 controls already agreed). Green on f17981e: both files 12/12 and 11/11 plus data-empty-projection-count-door, rest-server-closed-query-params, data-empty-projection-count (44/44, 20/20). Full suites on f17981e: pnpm --filter @objectstack/rest typecheck && test VERDICT command-exit 0, 329 files, 6525 passed, 326 skipped; @objectstack/runtime typecheck VERDICT command-exit 0, test VERDICT command-exit 0, 461 files, 7468 passed, 19 skipped. Ablation: the red run on the base code is the mutation leg; no separate ablation file.",
"gates": "Local, head a9c1ef1 (changeset-only commit after f17981e): dispatch-gates --commands --repo objectstack-ai/objectstack derived 59; each run with exit captured by redirect before any pipe; 58 exit 0; pnpm check:dual-build-cjs-loads exit 3 PREREQUISITE NOT MET (only the runtime closure is built here) = NOT MEASURED, CI runs it. --ran: 59 derived, 58 run, 1 NOT-MEASURED, 0 UNRUN, exit 0. Hand: check-platform-checklist --changed-from origin/main exit 0; check-commit-card-trailers --range origin/main..HEAD exit 0; eslint --no-inline-config packages/rest packages/runtime exit 0. CI check-runs: in_progress at report time (PR just opened), not awaited per contract. test:repo is CI's (no listed file touched).",
"dogfood_battery": "n/a — no ! in the PR title and Clause-②: no carries no (narrowing) arm",
"mcp_calls": "5 — all reads: issue_read get x1, issue_read get_comments x2, search_pull_requests x1 (open PRs on crud-routes: 0), pull_request_read get x1. No MCP writes.",
"api_writes": "2 relay writes so far + 1 git push (not REST): fleet-write pr_create (run 40574109862, PR #26895, body read back identical, 8983 bytes, assignee os-project-manager); this os-dev-report comment via post-stamped.mjs.",
"open_questions": [],
"out_of_scope_findings": [
"class: a · reach: dispatcher GET /api/v1/data/:object/:id?nope=1 on an existing record answers 200 (unknown query param silently dropped, runtime/src/domains/data.ts by-id branch keeps select/expand only), while RestServer's twin answers 400 VALIDATION_ERROR 'The "nope" query parameter is not supported by this endpoint' (#7606 closed set) · evidence: measured in this card's parity rig at f17981e (REST status 400, dispatcher status 200) · dedupe words: dispatcher by-id unknown query param, GET data id closed parameter set dispatcher, refuseUnknownQueryParams dispatcher twin",
"carrier: none · noted, not filed: the thrown error's param (select/expand) reaches neither host's wire body; the parameter is named only in the sentence (Acceptance note in PR #26895)"
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 27, 2026
Landed
PR #26895 merged at 2026-10-27T21:06Z by the merge queue as
1d7816d5dconmain(Fixes #26816; ACCEPT6452719777).git ls-remote origin 'refs/heads/gh-readonly-queue/main/pr-26895-*'origin/mainby contentgit cat-file -e origin/main:packages/rest/src/data-record-read-malformed-query-door.test.tsgit merge-base --is-ancestor 1d7816d5dc origin/mainState: this card closed by
Fixes(completed);pm:dispatchedremoved.Generated by Claude Code