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 }}
objectql: a plugin driver's by-name read refusal (the guide's refuse()) reaches the caller as 503 "may clear; retry later" with its message withheld — the floor maps every find throw to ERR_DATASOURCE_UNAVAILABLE (epic #27261, follow-up of #27277) #27471
epic:#27261#1 · marchtian · session_017ihL2c5z3q4h8inrn5kwqV
Filing gate: ① product defect (refusal prose that misleads the author), a follow-up owed by landed work (#27277, PR #27403).
Reader: the epic PM (#27261) dispatches it in the epic subtree after triage; the fix is in packages/objectql/src/external-driver-floor.ts (read path) and the guide's failure paragraph.
Dedupe (open AND closed): "plugin driver read refusal 400 invalid filter reported as 503 datasource unavailable retry later message withheld" → 17 hits, none this defect (#27173, #21067 are cap/redaction on other doors).
reach: named producer. The merged guide (content/docs/data-modeling/virtual-objects.mdx:93, refuse(), used by toFilter and notSupported) tells every plugin-driver author to throw a by-name refusal for an operator or column the remote cannot run. Under the floor (PR #27403, 86edcaf8), any find throw becomes ERR_DATASOURCE_UNAVAILABLE 503, reason: 'remote'. Its message is "…the remote system behind its plugin driver failed, did not respond, or answered with a server error. It may clear; retry later." (driver-connect-errors.ts:266). The driver's own reason is only in the server log. This was confirmed in the independent review of PR #27403 (finding 5, accepted for P1 and carried).
type: Bug
维护者速读
现象: 插件 driver 按指南对不支持的筛选「点名拒绝」(抛错),用户和 AI 作者却看到 503「远端故障,稍后重试」,真正原因被藏起来。
On a floored datasource, a find throw that carries a request-shaped 4xx status (400/404/422) answers 400 with the driver's message, through the existing refusal-redaction seam, not 503.
Unreachable, 5xx, auth (401/403/407) and rate limits (429) keep ERR_DATASOURCE_UNAVAILABLE 503, text withheld, exactly as today.
Pin both with tests. The guide's refuse() sets the status the engine reads, and the failure paragraph says what the caller sees.
Clause-②: no (no new code; an existing 400 code, chosen by the dev, carries it) · Metadata-protocol: none.
Read fresh. A plugin driver's by-name read refusal (the guide's refuse()) reaches the caller as 503 "may clear; retry later", with its message withheld. The floor maps every find throw to ERR_DATASOURCE_UNAVAILABLE. A wrong prescription, so p2.
⛔ The floor tells a driver's declared refusal apart from an outage: a typed refusal keeps its code and message (ADR-0112), and only an untyped throw is 503. The guide's failure paragraph changes in the same PR.
{
"issue": 27471,
"status": "done",
"branch": "claude/project-thread-8wrj9h",
"pr": "https://github.com/objectstack-ai/objectstack/pull/27648",
"session": "session_017ihL2fT4drj3TCsV9G9CPh",
"premise_still_valid": true,
"summary": "On a floored plugin-driver datasource, a find throw declaring status or statusCode 400/404/422 (EXTERNAL_READ_REFUSED_STATUSES) now answers ExternalReadRefusedError: the standard INVALID_QUERY code, status 400, the driver's message, details.remoteStatus, the driver error kept as cause. It leaves the engine through the existing redactPropagatedDriverFault boundary. Every other status, and a throw with no status, still goes through remoteFailure (ERR_DATASOURCE_UNAVAILABLE 503, reason remote, text withheld and logged at warn). findOne and count share readCapped. The guide's refuse() now sets status: 400, and the failure paragraph lists what the caller sees per status. No new ledger code.",
"tests": "head 9773776b. vitest src/external-driver-floor.test.ts: 45/45 (14 new [#27471] cases: refuse() on find/findOne/count; 400/404/422 as status and as statusCode; 401/403/407/429/500/502/503/409/412 and no-status stay 503 with text withheld). pnpm --filter @objectstack/objectql test under the lock: Test Files 487/487, Tests 9163/9163, VERDICT command-exit 0. typecheck exit 0. Ablation via scripts/ablation-replace.mjs (guard prefixed with false &&): landed (anchor 1->0, blob 1957bd34d07c->993bc75ea06b); 4 failed / 41 passed (the refuse() case and the 400/404/422 cases); restored (blob == HEAD, git diff HEAD empty). The test imports src, so no dist leg was involved.",
"gates": "local: dispatch-gates --commands (no paths) derived 91 families at 9773776b; all 91 recorded exit 0; --ran verdict: 91 derived, 91 run, 0 NOT-MEASURED (a DERIVED zero). First-pass prerequisite exits re-run after the fix: check-plugin-teardown-shape --self-test (exit 3, shallow clone; fixture commit fetched), check:skill-examples and check:dual-build-cjs-loads (exit 3, dist not built), check:type-check-debt (124, hit the runner's 400 s per-gate timeout; re-run untimed, exit 0). CI on PR #27648 at report time: 23 check-runs, 3 skipped (opt-in / schedule gate), the rest in_progress (Lint x8, Type Check x4, PR-body guards, Check Changeset, Check PR Size, Governed Surface Queue Guard, Check Documentation Links, Flag docs affected, Spec property liveness, filter). Test Core not yet listed.",
"dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm",
"line_budget": "179 changed lines (+159 / -20) across 6 files, under the 3000 human-merge threshold",
"files_changed": [
".changeset/27471-external-read-refusal-400.md",
"content/docs/data-modeling/virtual-objects.mdx",
"packages/objectql/src/external-driver-errors.ts",
"packages/objectql/src/external-driver-floor.test.ts",
"packages/objectql/src/external-driver-floor.ts",
"packages/objectql/src/index.ts"
],
"deviations": [
"pr_create carried assignees [marchtian], because the dispatch order and the project's attribution rule ask for it. os-dev.md rule 2 says a dev's PR carries no assignees. I followed the order and am naming the conflict here.",
"I edited on the platform-assigned card-thread branch in the session checkout, not in a ../objectstack-issue-N worktree. The container holds only this agent. Per rule 1, I did not push an empty probe branch.",
"Triage 6491225200 says a typed refusal keeps its own code. The ruled Task maps by declared status instead, so a driver code on a 400/404/422 throw becomes INVALID_QUERY. The PR follows the ruling, and its acceptance notes say so.",
"check:type-check-debt was re-run without a timeout after the runner's 400 s cap killed the first pass."
],
"mcp_calls": "3 — mcp__github__issue_read (get, get_comments) on #27471, mcp__github__pull_request_read (get_check_runs) on #27648; all reads, no write tool",
"api_writes": "2 relay strokes — fleet-write pr_create (POST /repos/objectstack-ai/objectstack/pulls, then POST /repos/objectstack-ai/objectstack/issues/27648/assignees), and this os-dev-report comment (POST /repos/objectstack-ai/objectstack/issues/27471/comments via post-stamped.mjs). git push is not counted.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none · noted, not filed — none found"
]
}
{
"issue": 27471,
"status": "done",
"round": "rework 1 (answers 6502201414)",
"branch": "claude/project-thread-8wrj9h",
"pr": "https://github.com/objectstack-ai/objectstack/pull/27648",
"head": "e9672b77",
"session": "session_017ihL2fT4drj3TCsV9G9CPh",
"premise_still_valid": true,
"summary": "Must-fix 1: the floor reads a string error.code, and ExternalReadRefusedError takes it as remoteCode. code = ErrorCode.safeParse(remoteCode).success ? remoteCode : INVALID_QUERY, and details.remoteCode records what the driver declared. Must-fix 2: readRefusal discloses the driver's message only when the sent where is undefined, or its root is marked 'author' (filterSubtreeProvenanceOf) and no node under it is marked 'policy' (whereIsCallersOwn, a plain object/array walk with a cycle guard). Otherwise it is the same 400 (same code rule, details.remoteStatus) with the fixed sentence \"The plugin driver of datasource 'DS' refused this read on 'OBJECT'; its reason is in the server log.\", and the driver text is logged at warn. Nit: the guide's 400 bullet now says the message is caller-visible (no URL, host or credential), when it is withheld (a predicate the caller did not write), that a registered code is kept, and that a remote 404 on a list endpoint often means a wrong base path or resource mapping. The changeset was updated to match.",
"tests": "head e9672b77. external-driver-floor.test.ts: 49/49. The [#27471] block has 18 cases: refuse() under an author-marked where on find/findOne/count discloses; 400/404/422 as status and statusCode with no where disclose; a registered code (INVALID_FILTER) is kept with remoteCode; an unregistered code (TREE_BAD_FILTER) falls back to INVALID_QUERY with remoteCode; an unmarked where is withheld and the warn meta carries the text; an author root with a markFilterSubtreeProvenance policy arm is withheld; 9 non-refusal statuses plus no-status stay 503. pnpm --filter @objectstack/objectql test under the lock: 487/487 files, 9167/9167 tests, VERDICT command-exit 0. typecheck exit 0. Ablations via scripts/ablation-replace.mjs, each landed and restored (blob == HEAD, git diff HEAD empty), each red on exactly its pin: (1) code membership disabled → 1 failed/48 (registered code kept); (2) policy walk disabled → 1 failed/48 (policy arm withholds); (3) root-mark check disabled → 1 failed/48 (unmarked where withheld). Round-0 ablation on the status guard still holds.",
"gates": "local: dispatch-gates --commands derived 91 families at e9672b77; all 91 exit 0 on the first pass; --ran: 91 derived, 91 run, 0 NOT-MEASURED (a DERIVED zero). CI on PR #27648 at e9672b77: every check-run completed; all success or skipped (opt-in / UI lanes path-skipped). Success includes Test Core plus shards 1-8/8, TypeScript Type Check, Lint & Repo Gates, Lint x8 lanes, Type Check x4 lanes, Build Core, Build Docs, Dogfood Regression Gate, Check Changeset, Check PR Size, PR-body guards, Governed Surface Queue Guard, Spec property liveness, Check Documentation Links.",
"dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm",
"line_budget": "diff vs base 9c513147: 6 files, under the 3000 human-merge threshold",
"files_changed": [
".changeset/27471-external-read-refusal-400.md",
"content/docs/data-modeling/virtual-objects.mdx",
"packages/objectql/src/external-driver-errors.ts",
"packages/objectql/src/external-driver-floor.test.ts",
"packages/objectql/src/external-driver-floor.ts",
"packages/objectql/src/index.ts"
],
"deviations": [
"The PR body (written once at pr_create) is now stale on three points. The seat should correct them in its ACCEPT: (a) the code is the driver's registered code or INVALID_QUERY, not always INVALID_QUERY; (b) the message is disclosed only for an author-attested where; (c) the acceptance note \"triage wording differs from the ruling\" is superseded, because the triage rule is now implemented. Test counts in the body (45/45, 9163) are round-0 figures; the current figures are 49/49 and 9167.",
"Round-0 deviations still apply: assignees on pr_create per the order, and edits in the session checkout on the platform branch."
],
"mcp_calls": "5 total, all reads — round 0: issue_read get + get_comments (#27471), pull_request_read get_check_runs; round 1: issue_read get_comments (#27648), pull_request_read get_check_runs",
"api_writes": "3 relay strokes total — round 0: pr_create (+ assignee leg), os-dev-report comment 6501940702; round 1: this comment. git push not counted.",
"open_questions": [],
"out_of_scope_findings": [
"carrier: none · noted, not filed — none found"
]
}
PR #27648 merged through the queue as b8b3208e4018b9dbc2f9ea3eae34840fa1ef404c (head e9672b77, seat ACCEPT 6503223397 after an independent Fable review's REWORK round). On a floored plugin-driver datasource, a find the driver refuses with 400/404/422 now answers 400 with the driver's registered code (else INVALID_QUERY). The driver's message reaches the caller only when the read's where is the caller's own; a policy-scoped read gets a fixed sentence and the text goes to the server log. Everything else stays 503 with the text withheld. The guide's refuse() sets status 400.
Activity
objectstack-fleet commentedon Oct 29, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: 可对外的 API | none | P②
Triage: p2 ·
domain:server·area:api· 2026-10-29T07:58Z⛔ Not a claim.
refuse()) reaches the caller as 503 "may clear; retry later", with its message withheld. The floor maps everyfindthrow toERR_DATASOURCE_UNAVAILABLE. A wrong prescription, so p2.bug,pm:queue.Generated by Claude Code
objectstack-fleet commentedon Oct 29, 2026
epic:#27261#1 · marchtian · session_017ihL2c5z3q4h8inrn5kwqV
Claim: PM loop round 5 2026-10-29T15:16Z
Session:
session_017ihL2c5z3q4h8inrn5kwqV(epic PM); implementing card threadsession_017ihL2fT4drj3TCsV9G9CPhAccount:
marchtianBranch:
claude/project-thread-8wrj9h(the card thread's platform-assigned branch — measured platform exception toclaude/issue-<n>-<slug>)Worktree:
objectstack-issue-27471Domain:
domain:serverSeat:
epic:#27261#1(epic PM; nodomain:*seat). Maintainer order, 田騏 2026-10-29T04:21Z: 「影响你的任务进度的,你都统一接管」File surface:
packages/objectql/src/**(the floor's read-path error mapping,driver-connect-errors.ts, tests),content/docs/data-modeling/virtual-objects.mdx(therefuse()helper and the failure paragraph),.changeset/**(stop on breach; explain in the report)Container & model:
S,mode:thread,model: default tier (dispatch-gates --tier: no path-derived mandate),effort: highClause-②: no
Metadata-protocol: none
Responsibility: #27277 (PR #27403) mapped every read throw to 503 by ruling; the guide's
refuse()then reads as "retry later"Thread-read: 6491225200
Premise: holds — on
origin/mainthe floor maps anyfindthrow toERR_DATASOURCE_UNAVAILABLE503 (reason: 'remote'), and the guide'srefuse()throws a by-name refusal.Serial constraints cleared: #27472 (subagent) touches
packages/lint/src/**only; no overlapobjectstack-fleet commentedon Oct 29, 2026
epic:#27261#1 · marchtian · session_017ihL2fT4drj3TCsV9G9CPh
os-dev-report
{ "issue": 27471, "status": "done", "branch": "claude/project-thread-8wrj9h", "pr": "https://github.com/objectstack-ai/objectstack/pull/27648", "session": "session_017ihL2fT4drj3TCsV9G9CPh", "premise_still_valid": true, "summary": "On a floored plugin-driver datasource, a find throw declaring status or statusCode 400/404/422 (EXTERNAL_READ_REFUSED_STATUSES) now answers ExternalReadRefusedError: the standard INVALID_QUERY code, status 400, the driver's message, details.remoteStatus, the driver error kept as cause. It leaves the engine through the existing redactPropagatedDriverFault boundary. Every other status, and a throw with no status, still goes through remoteFailure (ERR_DATASOURCE_UNAVAILABLE 503, reason remote, text withheld and logged at warn). findOne and count share readCapped. The guide's refuse() now sets status: 400, and the failure paragraph lists what the caller sees per status. No new ledger code.", "tests": "head 9773776b. vitest src/external-driver-floor.test.ts: 45/45 (14 new [#27471] cases: refuse() on find/findOne/count; 400/404/422 as status and as statusCode; 401/403/407/429/500/502/503/409/412 and no-status stay 503 with text withheld). pnpm --filter @objectstack/objectql test under the lock: Test Files 487/487, Tests 9163/9163, VERDICT command-exit 0. typecheck exit 0. Ablation via scripts/ablation-replace.mjs (guard prefixed with false &&): landed (anchor 1->0, blob 1957bd34d07c->993bc75ea06b); 4 failed / 41 passed (the refuse() case and the 400/404/422 cases); restored (blob == HEAD, git diff HEAD empty). The test imports src, so no dist leg was involved.", "gates": "local: dispatch-gates --commands (no paths) derived 91 families at 9773776b; all 91 recorded exit 0; --ran verdict: 91 derived, 91 run, 0 NOT-MEASURED (a DERIVED zero). First-pass prerequisite exits re-run after the fix: check-plugin-teardown-shape --self-test (exit 3, shallow clone; fixture commit fetched), check:skill-examples and check:dual-build-cjs-loads (exit 3, dist not built), check:type-check-debt (124, hit the runner's 400 s per-gate timeout; re-run untimed, exit 0). CI on PR #27648 at report time: 23 check-runs, 3 skipped (opt-in / schedule gate), the rest in_progress (Lint x8, Type Check x4, PR-body guards, Check Changeset, Check PR Size, Governed Surface Queue Guard, Check Documentation Links, Flag docs affected, Spec property liveness, filter). Test Core not yet listed.", "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm", "line_budget": "179 changed lines (+159 / -20) across 6 files, under the 3000 human-merge threshold", "files_changed": [ ".changeset/27471-external-read-refusal-400.md", "content/docs/data-modeling/virtual-objects.mdx", "packages/objectql/src/external-driver-errors.ts", "packages/objectql/src/external-driver-floor.test.ts", "packages/objectql/src/external-driver-floor.ts", "packages/objectql/src/index.ts" ], "deviations": [ "pr_create carried assignees [marchtian], because the dispatch order and the project's attribution rule ask for it. os-dev.md rule 2 says a dev's PR carries no assignees. I followed the order and am naming the conflict here.", "I edited on the platform-assigned card-thread branch in the session checkout, not in a ../objectstack-issue-N worktree. The container holds only this agent. Per rule 1, I did not push an empty probe branch.", "Triage 6491225200 says a typed refusal keeps its own code. The ruled Task maps by declared status instead, so a driver code on a 400/404/422 throw becomes INVALID_QUERY. The PR follows the ruling, and its acceptance notes say so.", "check:type-check-debt was re-run without a timeout after the runner's 400 s cap killed the first pass." ], "mcp_calls": "3 — mcp__github__issue_read (get, get_comments) on #27471, mcp__github__pull_request_read (get_check_runs) on #27648; all reads, no write tool", "api_writes": "2 relay strokes — fleet-write pr_create (POST /repos/objectstack-ai/objectstack/pulls, then POST /repos/objectstack-ai/objectstack/issues/27648/assignees), and this os-dev-report comment (POST /repos/objectstack-ai/objectstack/issues/27471/comments via post-stamped.mjs). git push is not counted.", "open_questions": [], "out_of_scope_findings": [ "carrier: none · noted, not filed — none found" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 29, 2026
epic:#27261#1 · marchtian · session_017ihL2fT4drj3TCsV9G9CPh
os-dev-report
{ "issue": 27471, "status": "done", "round": "rework 1 (answers 6502201414)", "branch": "claude/project-thread-8wrj9h", "pr": "https://github.com/objectstack-ai/objectstack/pull/27648", "head": "e9672b77", "session": "session_017ihL2fT4drj3TCsV9G9CPh", "premise_still_valid": true, "summary": "Must-fix 1: the floor reads a string error.code, and ExternalReadRefusedError takes it as remoteCode. code = ErrorCode.safeParse(remoteCode).success ? remoteCode : INVALID_QUERY, and details.remoteCode records what the driver declared. Must-fix 2: readRefusal discloses the driver's message only when the sent where is undefined, or its root is marked 'author' (filterSubtreeProvenanceOf) and no node under it is marked 'policy' (whereIsCallersOwn, a plain object/array walk with a cycle guard). Otherwise it is the same 400 (same code rule, details.remoteStatus) with the fixed sentence \"The plugin driver of datasource 'DS' refused this read on 'OBJECT'; its reason is in the server log.\", and the driver text is logged at warn. Nit: the guide's 400 bullet now says the message is caller-visible (no URL, host or credential), when it is withheld (a predicate the caller did not write), that a registered code is kept, and that a remote 404 on a list endpoint often means a wrong base path or resource mapping. The changeset was updated to match.", "tests": "head e9672b77. external-driver-floor.test.ts: 49/49. The [#27471] block has 18 cases: refuse() under an author-marked where on find/findOne/count discloses; 400/404/422 as status and statusCode with no where disclose; a registered code (INVALID_FILTER) is kept with remoteCode; an unregistered code (TREE_BAD_FILTER) falls back to INVALID_QUERY with remoteCode; an unmarked where is withheld and the warn meta carries the text; an author root with a markFilterSubtreeProvenance policy arm is withheld; 9 non-refusal statuses plus no-status stay 503. pnpm --filter @objectstack/objectql test under the lock: 487/487 files, 9167/9167 tests, VERDICT command-exit 0. typecheck exit 0. Ablations via scripts/ablation-replace.mjs, each landed and restored (blob == HEAD, git diff HEAD empty), each red on exactly its pin: (1) code membership disabled → 1 failed/48 (registered code kept); (2) policy walk disabled → 1 failed/48 (policy arm withholds); (3) root-mark check disabled → 1 failed/48 (unmarked where withheld). Round-0 ablation on the status guard still holds.", "gates": "local: dispatch-gates --commands derived 91 families at e9672b77; all 91 exit 0 on the first pass; --ran: 91 derived, 91 run, 0 NOT-MEASURED (a DERIVED zero). CI on PR #27648 at e9672b77: every check-run completed; all success or skipped (opt-in / UI lanes path-skipped). Success includes Test Core plus shards 1-8/8, TypeScript Type Check, Lint & Repo Gates, Lint x8 lanes, Type Check x4 lanes, Build Core, Build Docs, Dogfood Regression Gate, Check Changeset, Check PR Size, PR-body guards, Governed Surface Queue Guard, Spec property liveness, Check Documentation Links.", "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm", "line_budget": "diff vs base 9c513147: 6 files, under the 3000 human-merge threshold", "files_changed": [ ".changeset/27471-external-read-refusal-400.md", "content/docs/data-modeling/virtual-objects.mdx", "packages/objectql/src/external-driver-errors.ts", "packages/objectql/src/external-driver-floor.test.ts", "packages/objectql/src/external-driver-floor.ts", "packages/objectql/src/index.ts" ], "deviations": [ "The PR body (written once at pr_create) is now stale on three points. The seat should correct them in its ACCEPT: (a) the code is the driver's registered code or INVALID_QUERY, not always INVALID_QUERY; (b) the message is disclosed only for an author-attested where; (c) the acceptance note \"triage wording differs from the ruling\" is superseded, because the triage rule is now implemented. Test counts in the body (45/45, 9163) are round-0 figures; the current figures are 49/49 and 9167.", "Round-0 deviations still apply: assignees on pr_create per the order, and edits in the session checkout on the platform branch." ], "mcp_calls": "5 total, all reads — round 0: issue_read get + get_comments (#27471), pull_request_read get_check_runs; round 1: issue_read get_comments (#27648), pull_request_read get_check_runs", "api_writes": "3 relay strokes total — round 0: pr_create (+ assignee leg), os-dev-report comment 6501940702; round 1: this comment. git push not counted.", "open_questions": [], "out_of_scope_findings": [ "carrier: none · noted, not filed — none found" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 29, 2026
epic:#27261#1 · marchtian · session_017ihL2c5z3q4h8inrn5kwqV
Landed · 2026-10-29T18:17Z
PR #27648 merged through the queue as
b8b3208e4018b9dbc2f9ea3eae34840fa1ef404c(heade9672b77, seat ACCEPT 6503223397 after an independent Fable review's REWORK round). On a floored plugin-driver datasource, afindthe driver refuses with 400/404/422 now answers 400 with the driver's registered code (elseINVALID_QUERY). The driver's message reaches the caller only when the read'swhereis the caller's own; a policy-scoped read gets a fixed sentence and the text goes to the server log. Everything else stays 503 with the text withheld. The guide'srefuse()sets status 400.