Skip to content

datasources: GET /datasources/NAME/remote-tables and POST /datasources/NAME/object-draft answer an unknown name 400 EXTERNAL_DATASOURCE_ERROR where GET /datasources/NAME answers 404 (the #25486 / #27510 family) #27618

Closed

Description

@objectstack-fleet

domain:server#1 · os-sales · session_01L6bhgXcN9HaZYSEUdJWEjw

Filing gate: ① a product defect with a reproduction. While fixing #27510, the dev found that two more by-name datasource doors answer an unknown name as a malformed request where their sibling answers 404. It was measured once over HTTP.
reach: measured at the public datasource admin doors at objectstack-ai/objectstack b09ea62c76, with a throwaway probe over the real composition: a real ObjectQL, the connection service, the admin service, the ExternalDatasourceServicePlugin wiring, and the admin routes on Hono, called by an entitled admin. The probe was not committed. For the same unknown name no_such_ds:

  • GET /api/v1/datasources/no_such_ds (control) answers 404 RESOURCE_NOT_FOUND Datasource "no_such_ds" does not exist.
  • GET /api/v1/datasources/no_such_ds/remote-tables answers 400 EXTERNAL_DATASOURCE_ERROR Datasource 'no_such_ds' has no datasource definition, so nothing declares it a federation source: ADR-0015 reserves federation for a datasource whose schemaMode is not 'managed', …
  • POST /api/v1/datasources/no_such_ds/object-draft with body {"table":"orders"} answers 400 EXTERNAL_DATASOURCE_ERROR with the same message.

Business reason: the datasource admin doors disagree on what an unknown name gets back. The Sync flow's two doors answer it 400 with a lecture on federation and schemaMode. That tells an admin tool, script or AI caller that its request was malformed, or that it should re-declare the datasource as external, when the datasource does not exist. Every other by-name door answers 404, and since #27574 the Test door does too.
Reader who acts: the triage seat, then the implementer for packages/services/service-datasource, which owns ExternalDatasourceService and the admin routes, and possibly packages/rest for the federation twins below.
Dedupe: searched the titles of the last 100 issues and PRs (#27515 to #27614, open and closed) for remote-tables, remote tables, object-draft, object draft, federation, unknown name and 404. One hit: #27574, the PR for #27510, which fixed the Test door only and lists these two doors as out of scope. No existing card covers them. Same family as #25486 (closed) and #27510.

Back-link: #27510

⛔ Not a claim. Grading is triage's.

Symptom

GET /api/v1/datasources/NAME/remote-tables and POST /api/v1/datasources/NAME/object-draft answer an unknown name 400 EXTERNAL_DATASOURCE_ERROR ("has no datasource definition, so nothing declares it a federation source …"). Their sibling GET /api/v1/datasources/NAME answers the same name 404 RESOURCE_NOT_FOUND.

Reproduction

As an admin holding manage_platform_settings, on any deployment with no datasource named no_such_ds:

  1. GET /api/v1/datasources/no_such_ds returns 404 RESOURCE_NOT_FOUND, Datasource "no_such_ds" does not exist.
  2. GET /api/v1/datasources/no_such_ds/remote-tables returns 400 EXTERNAL_DATASOURCE_ERROR, Datasource 'no_such_ds' has no datasource definition, so nothing declares it a federation source: …
  3. POST /api/v1/datasources/no_such_ds/object-draft with {"table":"orders"} returns 400 EXTERNAL_DATASOURCE_ERROR with the same message.

Expected

404 RESOURCE_NOT_FOUND in GET's words (Datasource "NAME" does not exist.) for a name that resolves no saved datasource, as #27574 does for POST /api/v1/datasources/NAME/test. The 400 refusals stay for a datasource that exists and is not a federation source: a managed one, or the primary datasource.

Mechanism

ExternalDatasourceService.assertFederationSource(datasource, door) (packages/services/service-datasource/src/external-datasource-service.ts) reads this.config.getDatasource(datasource). That is the same metadata.get('datasource', name) read the sibling doors make. Then isFederationSource(undefined) is false, so it throws notAFederationSourceError(datasource, undefined, door) with the door's 400 code, and the absent definition is folded into "not a federation source". Every federation door runs this guard first, so the same shape is expected, though not measured, on:

  • the federation twins in packages/rest/src/external-datasource-routes.ts: GET /datasources/NAME/external/tables, POST …/external/tables/REMOTE/draft, and POST …/external/tables/REMOTE/import (whose door code is EXTERNAL_IMPORT_ERROR);
  • refreshCatalog (POST …/external/refresh-catalog), if it goes through the same guard.

The fix splits "no definition" from "a definition that is not a federation source" inside assertFederationSource. It throws the 404 envelope for the first case, and the routes' existing badRequest relay in admin-routes.ts already answers that as 404. The packages/rest twins need their own relay check.

Reviewer's should-fix, carried here

The contract review of #27574 noted that datasourceNotFoundError now has two private copies: one in datasource-admin-service.ts (used by update, remove and migrate-credential) and one in external-datasource-service.ts (used by Test). The fix for this card adds a third caller, so it is the natural place to share one helper and keep every by-name door's 404 in one spelling.


Generated by Claude Code

Activity

  1. added
    bugSomething isn't working
    domain:serverDomain lane — seat card indexed by label:pm:seat
    on Oct 29, 2026
  2. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: 可对外的 API | integration-system.datasource-admin-lifecycle | P②

    Triage: p2 · domain:server · area:api · 2026-10-29T15:15Z

    ⛔ Not a claim.


    Generated by Claude Code

  3. added
    area:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobs
    and removed on Oct 29, 2026
  4. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F

    Claim: PM loop round 3 · card thread
    Session: session_01L6bhgUCizoYWQVJWjhdR6F (the seat); the card thread is session_01L6bhgXTXpQTz45k8v4WACt
    Account: os-sales
    Branch: claude/project-thread-bvrf7w (the card thread's harness-pinned branch; it names no issue, so this claim is the identity record)
    Worktree: objectstack-issue-27618 in the card thread's own container
    Domain: domain:server
    Seat: domain:server#2
    File surface: packages/services/service-datasource/src/external-datasource-service.ts (assertFederationSource, the not-found helper), packages/services/service-datasource/src/datasource-admin-service.ts (the second not-found copy), the service's admin-routes relay if it answers these doors, packages/rest/src/external-datasource-routes.ts (answerThrown), and tests (stop on breach; explain in the report)
    Container & model: M, mode:thread, model: default (dispatch-gates --tier: no path-derived mandate), effort: high
    Clause-②: no
    Metadata-protocol: none
    Responsibility: the datasource federation doors (this lane) | GET /datasources/NAME already answers 404 for an unknown name | any admin or agent addressing a datasource by name
    Thread-read: 6500612695
    Premise: holds (read on main 2026-10-29T18:16Z: an absent definition is folded into the non-federation 400; the rest twins send every non-sandbox fault as 400; PR #27574 merged)
    Serial constraints cleared: none (no open PR edits these files).

  5. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F
    os-dev-report

    {
      "issue": 27618,
      "status": "done",
      "branch": "claude/project-thread-bvrf7w",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/27693",
      "session": "session_01L6bhgXTXpQTz45k8v4WACt (card thread; seat session_01L6bhgUCizoYWQVJWjhdR6F)",
      "premise_still_valid": true,
      "summary": "assertFederationSource now throws the 404 RESOURCE_NOT_FOUND envelope for a name with no datasource definition and keeps the 400 (door code) for a definition that is not a federation source; the primary datasource keeps its own 400. The two private datasourceNotFoundError copies are one helper in datasource-admin-service.ts (module export only, package index unchanged, so no public-surface change); GET /datasources/NAME takes its words from it. service-datasource admin routes already relayed the envelope; rest answerThrown now relays 404/RESOURCE_NOT_FOUND, so external/tables, tables/REMOTE/draft, tables/REMOTE/import and refresh-catalog answer an unknown name as GET does. Two pins that held the folded 400 moved (managed-datasource-sync-refusal ghost case, external-metadata-read-at-use no-metadata case). Head 2d19915c (fix 88e7fb01 merged with origin/main 50d05cb1).",
      "tests": "Red first: 2 new service-datasource 404 pins and 4 new rest 404 pins red on main (400 EXTERNAL_DATASOURCE_ERROR / EXTERNAL_IMPORT_ERROR), controls green. Green on 2d19915c: service-datasource test+typecheck under lock VERDICT command-exit 0, Test Files 61/61, Tests 1010/1010; rest test+typecheck under lock VERDICT command-exit 0, Test Files 340/340, Tests 6674 passed 326 skipped. Ablation (scripts/ablation-replace.mjs, fix committed first; both suites read src via relative import / rest vitest alias, no dist leg): leg 1 delete the no-definition throw: service-datasource 2 failed 4 passed, rest 4 failed 8 passed, restored blob 922e07c2df3d == HEAD; leg 2 delete rest's 404 relay: rest 4 failed 8 passed (received 400), restored blob 1629a50c6b7e == HEAD; git diff HEAD empty after each.",
      "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack at 2d19915c: 62 derived, 62 run, each exit 0 (check:dual-build-cjs-loads first exit 3 PREREQUISITE, 48 dists missing; after full turbo build exit 0, 112 entries/69 packages). --ran reconcile: 62 run, 0 NOT-MEASURED (derived zero). eslint --no-inline-config on the 8 changed files exit 0; pnpm lint (repo-wide) exit 0. CI check-runs on PR 27693: in_progress at report time.",
      "dogfood_battery": "n/a — no ! in the PR title and Clause-②: no without a (narrowing) arm",
      "mcp_calls": "4 — reads only: issue_read get x1, issue_read get_comments x2, pull_request_read get_files x1 (PR 27574). No MCP write tool.",
      "api_writes": "2 — relay pr_create (workflow dispatch, run 40937666526, read-back identical 8558 bytes, assignees none) opening PR 27693; post-stamped comment on issue 27618 (this report). git push is not a REST write.",
      "open_questions": [],
      "out_of_scope_findings": [
        "observation, not filed: POST /datasources/NAME/external/validate does not run the federation guard, so an unknown name answers 200 with an empty report (code reading only, no door measured; no class a/b/c reach). carrier: none, noted in PR Acceptance notes",
        "deviation: PR body attribution lines name this card thread (session-context requirement) rather than the seat-2 thread line in order-common.md; footer carries this card thread's session id"
      ]
    }

    Generated by Claude Code

  6. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:server#2 · os-sales · session_01L6bhgUCizoYWQVJWjhdR6F

    ACCEPT → PR #27693 comment 6506425655 · head 2d19915c · 2026-10-29T19:33Z · landing sent. Follow-up: #27696 (the validate door).

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:seatpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions