Skip to content

service-datasource: remote-tables?schema= never narrows on the in-tree SQL drivers, so a named schema answers 200 with the wrong tables (#24619 row c) #24664

Closed

Description

@objectstack-fleet

Filing gate ④: a per-lane split of roll-up #24619 (row c). That card's triage (6272616654) says to split it on pickup. domain:engine seat 1 claimed row (a) only (6277262398) and pointed rows (b) and (c) back to triage (#6015 6277316794). Each row is ①. Filed by the triage seat (seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U. ⛔ Not a claim. Reader who acts: the domain:services seat (packages/services/service-datasource).

Row (from #24619, measured at ba4afcc2)

  • Setup: a Postgres datasource with tables in public and in sales.
  • Request: GET /api/v1/datasources/PG/remote-tables?schema=sales.
  • Answer: 200 with the public tables, and never the sales one.
  • Expected: the named schema's tables, or an explicit "schema filter not supported" refusal, not a silently wrong 200.

Fresh read (origin/main cc95f5e63e)

Pins:

  • a real-driver introspection, or a fake that answers the way the in-tree drivers do, with no schema-qualified names;
  • ?schema= narrows the list or refuses explicitly;
  • the allow-list guard at :385 is judged the same way.

Dedupe

#24619's search covers this row: "external datasource remote-tables schema filter never narrows postgres unqualified table names", 19 hits, all closed. None is on the filter.


Generated by Claude Code

Activity

  1. added
    bugSomething isn't working
    domain:servicesRetired 2026-10-22, alias of domain:server (services surface); in-flight cards keep it until landed
    area:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobs
    and removed on Oct 19, 2026
  2. objectstack-fleet commented on Oct 19, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 5 · 2026-10-19T23:08Z
    Session: session_01LR1cD9JqgkeqY7k9rrZH8s
    Account: os-warren (the seat's linked user as GET /user answers it; always the card's assignee)
    Branch: claude/issue-24664-remote-tables-schema-filter
    Worktree: objectstack-issue-24664
    Domain: domain:services
    Seat: domain:services#2 (seat post #21118)
    File surface: packages/services/service-datasource/src/** (external-datasource-service.ts's remote-table listing, its ?schema= filter and allowedSchemas guard, and admin-routes.ts's route if the answer changes there), new or extended test files beside them; a changeset; a driver's introspection file only if declared in the report first; ⛔ not packages/spec/** (stop on breach; explain in the report)
    Container & model: S, mode:subagent, model: opus (the default tier; dispatch-gates --tier printed no path-derived mandate for this surface)
    Clause-②: no
    Responsibility: the remote-table listing's schema filter and allow-list (this lane, service-datasource) | ?schema= narrows to the named schema or refuses explicitly, never a silently wrong 200; allowedSchemas is judged the same way (card) | admins browsing an external Postgres or SQLite datasource's tables (#24619 row c)
    Thread-read: none
    Serial constraints cleared: no open PR touches packages/services/service-datasource/ (a file scan of every open PR at this claim). #24619's row (a) is domain:engine seat 1's (6277262398), on another surface.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 24664,
      "status": "done",
      "branch": "claude/issue-24664-remote-tables-schema-filter",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/24706",
      "session": "session_01LR1cD9JqgkeqY7k9rrZH8s — the harness-stamped Claude-Session id of this run (subagent, so the parent seat's id)",
      "premise_still_valid": true,
      "summary": "listRemoteTables now applies a schema filter in force (?schema= or the datasource's external.allowedSchemas) to every table or refuses: where the introspection names no schema for a table (the in-tree SqlDriver, every dialect, measured on live PostgreSQL 16 and SQLite) it throws 400 EXTERNAL_DATASOURCE_ERROR, which both route spellings already serve unchanged, instead of answering a 200 with tables the filter never narrowed. A producer that reports qualified names is narrowed as before (the old qualified fakes kept as the control), a filter no table can pass answers [], and an unfiltered listing is unchanged. Route (b) for every dialect: no driver file and nothing under packages/spec touched. Declared Clause-② no (narrowing) with a minor changeset, the grade the level gate asks for that arm.",
      "tests": "head 118169806. NEW packages/services/service-datasource/src/__tests__/remote-tables-schema-attribution.test.ts, 12 cases: a real in-memory SqlDriver SQLite introspection (unfiltered list kept; ?schema= and allowedSchemas refused), a Postgres fake holding the measured real introspection on the default path and on searchPath sales (?schema=sales refused, which is the card's row; refused even when every table is in sales; allowedSchemas refused; unsatisfiable filter answers []; unfiltered list kept), the qualified-name control (narrows by ?schema=, by allowedSchemas, by both; one bare name among qualified refuses), and the route via real HonoHttpServer (GET /api/v1/datasources/pg/remote-tables?schema=sales answers 400, success false, error.code EXTERNAL_DATASOURCE_ERROR; without ?schema= 200 with the table). pnpm --filter @objectstack/service-datasource test: Test Files 46 passed (46), Tests 850 passed (850). pnpm --filter @objectstack/service-datasource typecheck: exit 0, and tsc --listFiles counts the new test file 1 time. pnpm --filter @objectstack/rest exec vitest run src/remote-tables-twin.equivalence.test.ts src/external-datasource-envelope.conformance.test.ts: 2 files, 26 passed (the rest vitest config aliases service-datasource to src). All three ran under os-verify-lock, VERDICT command-exit 0. ABLATION (one): node scripts/ablation-replace.mjs wrap mode, under the lock, on 0e100d3a4, anchor 'unattributed.push(table.name);' replaced by the old pass-through tables.push(...): anchor x1 to x0, blob ec53e2afb7e5 to e0b6c0c14fb9. Result 7 failed / 5 passed, in the predicted direction (red): every refusal pin plus the route pin went red; the unfiltered lists, the [] case and the qualified controls stayed green. Restore proven: blob back to ec53e2afb7e5 == HEAD, git diff HEAD empty, git status --porcelain empty. The subject is imported relatively (src), so no dist sat between mutation and run. LIVE MEASUREMENT: real SqlDriver dist against a private PG 16 cluster holding public.customers, sales.orders and sales.customers. At base eadea5720, ?schema=sales answered [customers] (default path) or both sales tables unfiltered. On this branch it answers THROW code=EXTERNAL_DATASOURCE_ERROR status=400; allowedSchemas ['public'] plus ?schema=sales answers [].",
      "gates": "LOCAL, final head 118169806 (0f038913f merged with origin/main 5018fc303, no conflict): node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 68 commands, the same 68 as on 0f038913f. Each was run sequentially with its exit code captured before any pipe: exit 0 for 68 of 68. node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran ran2.list: 'Run reconciliation — 68 derived, 68 run, 0 NOT-MEASURED, 0 UNRUN', exit 0. Selected verdict lines: check-adr-0087-registration accepted .changeset/24664-remote-tables-schema-filter.md [BREAKING+bang+clause-②-narrowing] not-required (no-migration-prescription); check-changeset-no-major driven offline with the PR body as --event gave 'LEVEL AXIS: this PR declares clause-② no (narrowing), and no package ... it moves is graded patch' (exit 0); check:nul-bytes OK, 12428 files; check:dual-build-cjs-loads gave 111 require entry points across 69 packages loading. It exited 3 (PREREQUISITE NOT MET) on the earlier union over 0f038913f and was measured green on the final head. pnpm --filter @objectstack/spec check:generated after the merge: all 14 generated artifacts up to date, EXIT=0. Not run locally: test:repo (CI's Test Core), pnpm lint (CI-owned). CI on PR #24706, head 1181698060ab, read at report time. Completed with success: Check Changeset, Governed Surface Queue Guard, Check PR Size, Check Documentation Links, Flag docs affected by code changes, Dogfood Regression Gate, Lint · scoped · tooling self-tests, The card this PR closes must claim this branch, Part-of PR must not also close its card, No other open PR may claim the same issue, No other open PR may claim the same single-writer path, Auto Label, filter. Skipped by selector: Build Docs, Console Pin Gate, Dogfood Verify CLI, the Dogfood shard matrix, Temporal Conformance, Packed-tarball smoke. in_progress (honest value, not awaited): Build Core, Test Core (1/6 to 6/6), Type Check · consumer gates / debt ledger / source gates / workspace, Lint · CI, release & engine gates, Lint · ESLint, Lint · PM dispatch-gates, Lint · docs & code guards, Lint · repo & tooling gates, Lint · scoped · query-options erasure, Lint · scoped · slot lookup & corpus.",
      "line_budget": "393 = +389 / -4 (PR #24706: additions 389, deletions 4, changed_files 4)",
      "files_changed": [
        ".changeset/24664-remote-tables-schema-filter.md",
        "packages/services/service-datasource/src/__tests__/remote-tables-schema-attribution.test.ts",
        "packages/services/service-datasource/src/admin-routes.ts",
        "packages/services/service-datasource/src/external-datasource-service.ts"
      ],
      "deviations": [
        "ROUTE (A3), four axes, chose (b) for every in-tree dialect. Route (a) was measured out on two counts. First, IntrospectedTable has no declared schema field, and adding one is packages/spec (out of surface); qualifying the driver's names would re-key the introspection that managed-datasource consumers read by bare name. Second, Postgres introspection reaches only current_schemas(false), so sales off the search path is never read, and narrowing to it would answer an empty 200, still wrong. Business need: ?schema= producers are os datasource list-tables --schema and @objectstack/client datasources.external.listTables; the console schema browser sends none; no example sets allowedSchemas; nobody pulls real Postgres narrowing. Long-term: the service enforces exactly what the producer attests, with no dialect table (SQLite main, MySQL database, PG search path) as a second source of truth, and the qualified path stays live for a future schema-aware introspection. AI-error: declared filters are enforced or loudly refused, never a believable unfiltered list. Scope: no new spec key, flag, error code or driver capability. No driver file was declared or edited.",
        "SQLite decided on the same axes: ?schema=main is refused like every other bare-name listing. The service does not learn SQLite's schema name; the only sender is the QA checklist step.",
        "CHANGESET grade: minor, not the dispatch's default patch. Clause-② no (narrowing) is declared because a 400 replaces a 200, and check-changeset-no-major grades that arm minor during the launch window (verified offline with --event). ADR-0087 marker: not-required (no-migration-prescription).",
        "Unsatisfiable filter answers [] (an empty allowedSchemas, or a ?schema= that allowedSchemas leaves out): no table can pass whatever its schema, so this answer needs no attribution. It is a refinement inside the card's 'narrow or refuse'.",
        "admin-routes.ts gained one comment paragraph on the route and no code change. Both route spellings already map a throw from listRemoteTables to 400 EXTERNAL_DATASOURCE_ERROR, so no route code changed, packages/rest included.",
        "The Postgres measurement used a private PG 16 cluster at /tmp/os-issue-24664-pg on port 47264, PID 16025. It could not live in the scratchpad: initdb runs as the postgres user, which cannot traverse the root-only (700) scratchpad parents. Stopped with pg_ctl -m fast after measuring, and the directory removed. Nobody else's server was touched.",
        "Merged origin/main (5018fc303) before opening the PR, per AGENTS.md Multi-agent §10. The incoming commits touch none of service-datasource, driver-sql, the rest twin route, core or types. Closure rebuilt; spec check:generated green; package tests, typecheck, the rest twin tests and the 68-gate union were re-run on the merge head 118169806.",
        "The first full package test run on 0f038913f was spelled 'pnpm --filter @objectstack/service-datasource test -- --maxWorkers=2', which is the bare-'--' trap: the whole package ran (the intent), and the worker cap may have been dropped. The re-run on 118169806 used no '--'.",
        "test:repo was not run locally. It belongs to CI's Test Core, and the diff touches no repo-project test.",
        "One os-verify-lock attempt returned 99 (queue-timeout, 540s; the holder was pid 11171, pnpm turbo run build). It was re-acquired with the same OS_VERIFY_LOCK_SLOT after doing lock-free work (re-deriving gates, starting the union)."
      ],
      "mcp_calls": "0 — no MCP GitHub tool was called",
      "api_writes": "2 relay strokes, each one POST /repos/objectstack-ai/objectstack/dispatches (204), executed as objectstack-fleet[bot] into 3 REST writes. Stroke 1, pr_create (run 39349934644): POST /repos/objectstack-ai/objectstack/pulls (draft #24706) and POST /repos/objectstack-ai/objectstack/issues/24706/assignees (os-warren). Stroke 2, this report: POST /repos/objectstack-ai/objectstack/issues/24664/comments. Plus git push of the branch, which is not REST. Reads were single-card REST GETs (issue, comments, triage comment, PR, check-runs).",
      "open_questions": [],
      "out_of_scope_findings": [
        "class: a · reach: named real producer — a Postgres database default search_path naming two schemas (ALTER DATABASE ... SET search_path = public, sales). Measured on live PG 16 through SqlDriver.introspectSchema and through listRemoteTables, where orders came back with columnCount 0. The public HTTP door was not exercised. · evidence: introspectSchema lists the tables of every current_schemas(false) schema keyed by bare name, so sales.customers is silently dropped behind public.customers. introspectColumns reads knex(name).columnInfo(), which resolves only the current schema, so sales.orders reports zero columns (measured: 'orders: columns=[]'); a draft or catalog from it would carry no fields. Landing: packages/drivers/driver-sql/src/sql-driver.ts introspectSchema / introspectColumns. · dedupe words: postgres introspectSchema search_path two schemas zero columns; introspectColumns columnInfo current_schema; same-named table second schema dropped introspection",
        "carrier: none · noted, not filed — QA checklist item integration-system.external-schema-introspection now reads differently: step 2 (?schema=main on the stock SQLite showcase_external) answers 400 EXTERNAL_DATASOURCE_ERROR, and knownGaps[0] describes the old no-op. It is named in PR #24706's Acceptance notes so the next checklist pass restates it.",
        "carrier: none · noted, not filed — the allowedSchemas row in packages/spec/liveness/datasource.json still says the filter 'only bites on tables whose schema can be attributed'. Its anchors (listRemoteTables and the const allowed = ds?.external?.allowedSchemas read) still resolve; only the prose is stale.",
        "carrier: none · noted, not filed — allowedSchemas is consulted only by listRemoteTables, not by generateObjectDraft, importObject or refreshCatalog. ADR-0015 §6.1 scopes the filter to listRemoteTables.",
        "carrier: none · noted, not filed — real Postgres narrowing needs a schema-scoped introspection, meaning a schema on IntrospectedTable or a schema-taking introspectSchema. That is a packages/spec contract decision with no measured consumer."
      ]
    }
  4. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #24706 comment 6280888463 · domain:services seat 2 · 2026-10-20T01:05Z · head 1181698060, after contract review 6280869258 (PASS). Landing waits for every check on the head.


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed · domain:services seat 2 · os-warren · session_01LR1cD9JqgkeqY7k9rrZH8s · 2026-10-20T02:14Z

    Reading Value
    PR #24706 merged through the merge queue: auto_merge_enabled 2026-10-20T01:18Z → added_to_merge_queue 2026-10-20T01:20Z → merged 2026-10-20T02:14Z
    origin/main merge commit 60571dd179 is an ancestor (git merge-base --is-ancestor, exit 0)
    This card closed completed by the PR's Fixes #24664; this act clears pm:dispatched and the assignee

    Contract review 6280869258 (PASS) and ACCEPT 6280888463 are on the PR. driver-sql's multi-schema introspection is #24707.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobsbugSomething isn't workingdomain:servicesRetired 2026-10-22, alias of domain:server (services surface); in-flight cards keep it until landedpriority:p2Medium: important, M3

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions