Skip to content

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

Closed

Description

@objectstack-fleet

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「远端故障,稍后重试」,真正原因被藏起来。
  • 原因: 兜底层把读路径的任何抛错一律映射成数据源不可用(503,消息不外泄)。
  • 影响: 作者照指南写的拒绝变成误导性的「重试」,重试永远不会好;违背「错了响亮拒收、带处方」。
  • 最小修法: 读路径区分 driver 的请求型拒绝(带 4xx status,或指南约定的拒绝类)与远端故障:前者以 400 带 driver 原话(经既有脱敏通道)返回,后者维持 503。指南同步一句。
  • 需要您做的: 无。

Task

  • 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.

Activity

  1. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: 可对外的 API | none | P②

    Triage: p2 · domain:server · area:api · 2026-10-29T07:58Z

    ⛔ Not a claim.

    • 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.
    • bug, pm:queue.

    Generated by Claude Code

  2. added
    area:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobs
    bugSomething isn't working
    domain:serverDomain lane — seat card indexed by label:pm:seat
    and removed
    area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guards
    on Oct 29, 2026
  3. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#27261#1 · marchtian · session_017ihL2c5z3q4h8inrn5kwqV
    Claim: PM loop round 5 2026-10-29T15:16Z
    Session: session_017ihL2c5z3q4h8inrn5kwqV (epic PM); implementing card thread session_017ihL2fT4drj3TCsV9G9CPh
    Account: marchtian
    Branch: claude/project-thread-8wrj9h (the card thread's platform-assigned branch — measured platform exception to claude/issue-<n>-<slug>)
    Worktree: objectstack-issue-27471
    Domain: domain:server
    Seat: epic:#27261#1 (epic PM; no domain:* 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 (the refuse() 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: high
    Clause-②: 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/main the floor maps any find throw to ERR_DATASOURCE_UNAVAILABLE 503 (reason: 'remote'), and the guide's refuse() throws a by-name refusal.
    Serial constraints cleared: #27472 (subagent) touches packages/lint/src/** only; no overlap

  4. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    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

  5. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    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

  6. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#27261#1 · marchtian · session_017ihL2c5z3q4h8inrn5kwqV
    Landed · 2026-10-29T18:17Z

    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.

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, jobsbugSomething isn't workingdomain:serverDomain lane — seat card indexed by label:pm:seatpm:epicpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions