Skip to content

console(metadata-admin): the External Datasource panel reads the raw {success,data} envelope — remote tables list empty, no catalog timestamp, and Validation crashes with "Cannot read properties of undefined (reading 'length')" #11628

Closed

Description

@objectstack-fleet

QA-source: objectstack-ai/objectstack#21784 · integration-system.external-schema-browser-ui · acceptance[1]
Also: also acceptance[3], acceptance[4], negative[0], negative[4]

A clause of integration-system.external-schema-browser-ui fails in the ObjectStack 17.7 pre-release checklist run objectstack-ai/objectstack#21784 (framework subject 316be321e, console pin 2e818d0b51ec). An independent verifier (VF3, RUNNER rule 7) confirmed it: P2. It predates the 17.6.0 console pin 31971ff1e; no open duplicate was found. Owner: objectui.

Reproduction

  1. Stock showcase boot (it ships datasource showcase_external), admin form sign-in.
  2. Open /_console/apps/setup/metadata/datasource/showcase_external (also under /apps/studio/metadata/datasource/…).
  3. Network: GET …/external/tables → {"success":true,"data":{"tables":[{"name":"customers","columnCount":7},{"name":"orders","columnCount":7}]}}. Panel: "No remote tables found (check the datasource's allowedSchemas)." Expected two rows.
  4. "Refresh catalog" → 200 with data.catalog.snapshotAt. Expected a timestamp; none renders.
  5. Validation tab → "Run validation" → 200 {success,data:{ok:true,results:[2 rows]}}. Panel replaced by "Preview failed to render — Cannot read properties of undefined (reading 'length')". Expected verdict rows.
  6. The import dialog (A3) reads data.draft through the same code and is unreachable while the list is empty. Seen twice by the runner and twice by the verifier.

Mechanism

  • packages/app-shell/src/views/metadata-admin/external/api.ts:133 jsonOrThrow returns the raw body; callers read data.tables (:146-147), data.draft (:164-165), data.catalog (:174-175), and validate returns the body as {ok,results} (:186).
  • Every framework route answers through sendOk {success:true,data:{…}} (packages/rest/src/external-datasource-routes.ts:420 etc.).
  • Consumers: SchemaBrowser.tsx:53, ExternalDatasourcePanel.tsx:72/119, ValidationPanel.tsx:135-159 (results.length on undefined).
  • objectui's api.test.ts mocks the bare {tables} shape, so the unit test hides it. api.ts last changed at 31971ff1e; the server envelope is present at 617f25f8a. No duplicate found.

Done when

The client unwraps the envelope for tables / draft / catalog / validate, the test fixture uses the real wire shape, and the panel lists, refreshes and validates.

Related: the server-side import twin defect is objectstack-ai/objectstack#21788 (separate card).


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 5, 2026

    @objectstack-fleet
    ContributorAuthor

    Path: an API a customer can call — external data used as its own objects | integration-system.external-schema-browser-ui | P2

    Triage: first grade — bug · priority:p2 · domain:ui · area:studio · pm:queue. The client unwraps the {success,data} envelope on all four calls, and the test fixture uses the wire shape

    Triage: lands in packages/app-shell/src/views/metadata-admin/external/api.ts ⇒ domain:ui; rationale: the server answers the documented envelope; the client misreads it.

    Triage seat (objectstack-wide, seat post objectstack-ai/objectstack#6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-05T02:00Z. ⛔ Not a claim, ⛔ not a dispatch.


    Generated by Claude Code

  2. added
    area:studioChanging a running app without code — authoring, publish, docs and the portal
    bugSomething isn't working
    domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat
    and removed on Oct 5, 2026
  3. objectstack-fleet commented on Oct 5, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 2
    Session: session_015W8GBu6sBiqus2L2xjMsAL
    Account: os-steve
    Branch: claude/issue-11628-external-envelope
    Worktree: objectui-issue-11628
    Domain: domain:ui
    Seat: domain:ui#1
    File surface: packages/app-shell/src/views/metadata-admin/external/api.ts (jsonOrThrow and every call that reads its answer), packages/app-shell/src/views/metadata-admin/external/api.test.ts, the panel consumers only where a read must follow (SchemaBrowser.tsx, ExternalDatasourcePanel.tsx, ValidationPanel.tsx, ImportObjectDialog.tsx), the tests beside them, and .changeset/11628-*.md (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: default tier (dispatch-gates --tier --repo objectstack-ai/objectui: no path-derived mandate; floor sonnet · default opus · ceiling fable)
    Clause-②: no
    Thread-read: 5986834770
    Serial constraints cleared: none, read 2026-10-05T02:38Z. The open objectui PRs (#11635 plugin-dashboard test plus a DashboardView.tsx doc comment, now in the merge queue; #11069 packages/cli; #11600 the release PR) touch no metadata-admin/external file. The in-flight objectui#11092 and objectui#11625 declare other app-shell files. The server-side twin, objectstack-ai/objectstack#21788, lands separately (triage 5986834770).

    Why Clause-②: no: the external/api.ts functions are not exported from the @object-ui/app-shell entry. The fix makes the client read the {success,data} envelope the server already answers, so nothing widens. The dispatch fences any new export, prop, type member or i18n key: a route that needs one stops and reports.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 5, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 11628,
      "status": "done",
      "branch": "claude/issue-11628-external-envelope",
      "pr": "https://github.com/objectstack-ai/objectui/pull/11640",
      "session": "session_015W8GBu6sBiqus2L2xjMsAL — subagent run; the parent PM session id per the os-dev contract",
      "premise_still_valid": true,
      "summary": "external/api.ts now unwraps the {success,data} envelope once, in a new readExternalData, for tables / draft / catalog / validate. A 2xx body without that envelope is now an error that names the route, instead of an empty answer; the `tables ?? []` tolerance that hid the defect is gone. The same reader's failure arm called String(body.error) on the ADR-0112 nested envelope, so every refusal printed [object Object] and the 503 hint never fired; it now reads app-shell's existing readEnvelopeFailureText and recognises 503 by error.code SERVICE_UNAVAILABLE (PM Zone 2.2, measured live, fixed in this PR). importObjectDraft (the /meta door: flat save-result success, nested 403 plus flat 422/400 refusals, all measured) keeps its own reader, which now reads both dialects. api.test.ts uses the measured wire shapes, no panel component changed, and live the panel lists 2 tables, renders the snapshot time, opens the import dialog on the draft, and validates 2 rows.",
      "tests": "Unit, red before and green after: `pnpm exec vitest run packages/app-shell/src/views/metadata-admin/external/api.test.ts`. On the fix commit 790b3b1 with api.ts restored to base 1c2e2c4: Tests 12 failed | 4 passed (16), exit 1. Failures: `expected [] to deeply equal [customers, orders]`, `Cannot read properties of undefined (reading name)`, catalog undefined, the envelope returned as {ok,results}, `Error: [object Object]` where ExternalServiceUnavailableError was expected, and [object Object] in the message (including the /meta 403). Restored with `git checkout HEAD -- path` (absolute path, trap EXIT INT TERM); the blob hash equals HEAD a9443dd and `git diff HEAD` is empty. Re-run: Tests 16 passed (16), exit 0. The 4 tests that pass on the old client (?schema passthrough, HTTP-status fallback, /meta 2xx, flat /meta refusal) were already handled by it. || Wire shapes, measured with curl against my own objectstack worktree at main 0a348031: stock showcase, `objectstack dev --seed-admin --fresh -p 4628`, built with OS_SKIP_DTS=1 under the lock in 72s. GET tables answers {success:true,data:{tables:[customers 7, orders 7]}}; refresh-catalog answers data.catalog.snapshotAt; validate answers data:{ok:true,results:[2]}; draft answers data.draft. Refusals: 400 EXTERNAL_DATASOURCE_ERROR, 401 UNAUTHENTICATED and 403 PERMISSION_DENIED, all nested. PUT /meta/object answers success as {success,version,seq,state,message}, the 403 FORBIDDEN as nested {error:{code,message}}, and 422 INVALID_METADATA and 400 VALIDATION_ERROR as flat {error:SENTENCE,code}. The 503 SERVICE_UNAVAILABLE arm cannot be reached on the showcase: NOT MEASURED live, covered by the unit test. || Live panel: Playwright with /opt/pw-browsers/chromium; the console is this worktree under vite on :5628, proxied to :4628. Before (api.ts at 1c2e2c4, restored by hash afterwards) / after: Tables 0 rows plus the no-remote-tables hint / 2 rows. Refresh: no timestamp / `snapshot 10/5/2026, 2:51:36 AM`. Import dialog: unreachable / opens on draft `customers` with its generated source. Validation: `Preview failed to render` with reading length / `All 2 objects match the remote schema.` plus 2 rows. Both runs aborted the dev-only /api/v1/dev/metadata-events stream: without that, MetadataHmrReloader reloads the page about 0.6s after refresh-catalog and wipes the state, which was measured on the first after run. || Live client calls: the real api.ts imported in the page, as admin and as the auditor.demo persona. Before, every refusal read `Error: [object Object]`. After: `Remote table no_such_table not found ... (EXTERNAL_DATASOURCE_ERROR)`; `... requires the manage_platform_settings capability. (PERMISSION_DENIED)`; importObjectDraft as auditor gives `Saving a metadata item requires the manage_metadata capability. (FORBIDDEN)`; the flat 400 sentence is unchanged. || Merged head 4779053 (one merge of origin/main 22ddcd5), under the verify lock with VERDICT command-exit 0. The lock was held 1977s on a shared box; the first attempt got exit 99 queue-timeout and resumed its slot. Build closure `turbo run build --filter=@object-ui/app-shell^...` exit 0 (28 cached). `pnpm --filter @object-ui/app-shell type-check` exit 0 (tsconfig.test.json includes src/**/*.test.ts). `pnpm --filter @object-ui/app-shell lint` exit 0, 0 errors; the warnings are pre-existing and none is in api.ts or api.test.ts. `pnpm exec vitest run packages/app-shell/ --maxWorkers=2`: Test Files 1000 passed | 1 skipped (1001), Tests 9940 passed | 9 skipped (9949), exit 0. || Root checks on 4779053, all exit 0: check:metadata-write-doors (OK 17 doors, 3 raw PUT, 3 reach assertObjectMetadataWritable), check:new-line-citations (0 new citations), check:control-bytes, check:test-path-roots, check:vi-mock-specifiers, check:vi-mock-inherit, check:vi-mock-override-shape, check:changeset-claims, check:pending-changeset-literals, check:self-import, check:esm-specifiers, check:shell-escape-residue, check:unreferenced-sources, check:phantom-deps, check:unused-deps, check:comment-mask-corpus, scripts/check-changeset-presence.mjs, scripts/check-changeset-no-major.mjs. check-governed-queue-guard --test on the 3 paths: NOT GOVERNED. Not run locally, owned by CI: repo-wide pnpm lint and the full test farm. Root eslint config has no parserOptions.project or projectService, so linting is not type-aware. || CI at report time on 4779053: 42 check runs, 21 success, 3 skipped, 18 in_progress, 0 failing. CI state: in_progress.",
      "mcp_calls": "0 — no MCP GitHub tool called",
      "api_writes": "3 — all relayed as objectstack-fleet[bot] through POST /repos/objectstack-ai/objectstack/dispatches: (1) pr_create, which became POST /repos/objectstack-ai/objectui/pulls (draft) for #11640, read back as 11131 bytes sent and stored identical; (2) label-write --assign os-steve, which became POST /repos/objectstack-ai/objectui/issues/11640/assignees, read back as assignees [os-steve]; (3) this os-dev-report comment via post-stamped --comment=11628, which becomes POST /repos/objectstack-ai/objectui/issues/11628/comments. Plus git push of the branch (not REST). Reads were REST GETs only.",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: none (no carrier) · noted in PR Acceptance notes, not filed · observation: under the vite dev console plus `objectstack dev`, Refresh catalog writes the catalog snapshot, the backend emits metadata-change on /api/v1/dev/metadata-events, and apps/console MetadataHmrReloader reloads the page about 0.6s later, wiping the panel timestamp. The reloader is enabled by import.meta.env.DEV (read from source), so the published /_console does not run it. Dev-only, so outside classes a/b/c.",
        "carrier: none (no carrier) · noted, not filed · observation: the objectstack PUT /meta/:type/:name door answers refusals in two dialects, nested {error:{code,message}} from the capability gate (403 FORBIDDEN, 501 NOT_IMPLEMENTED) and flat {error:SENTENCE,code} from spec validation (422/400), both measured live. ADR-0112 already records the two envelopes as live, and the client now reads both, so no reachable wrong answer remains in objectui."
      ],
      "gates": {
        "head": "4779053",
        "vitest packages/app-shell/src/views/metadata-admin/external/api.test.ts (on 790b3b1; included in the package run on 4779053)": 0,
        "reverse-verification red leg (api.ts at 1c2e2c4)": 1,
        "turbo build --filter=@object-ui/app-shell^...": 0,
        "pnpm --filter @object-ui/app-shell type-check": 0,
        "pnpm --filter @object-ui/app-shell lint": 0,
        "pnpm exec vitest run packages/app-shell/ --maxWorkers=2": 0,
        "check:metadata-write-doors": 0,
        "check:new-line-citations": 0,
        "check:control-bytes": 0,
        "check:test-path-roots": 0,
        "check:vi-mock-specifiers": 0,
        "check:vi-mock-inherit": 0,
        "check:vi-mock-override-shape": 0,
        "check:changeset-claims": 0,
        "check:pending-changeset-literals": 0,
        "check:self-import": 0,
        "check:esm-specifiers": 0,
        "check:shell-escape-residue": 0,
        "check:unreferenced-sources": 0,
        "check:phantom-deps": 0,
        "check:unused-deps": 0,
        "check:comment-mask-corpus": 0,
        "scripts/check-changeset-presence.mjs": 0,
        "scripts/check-changeset-no-major.mjs": 0,
        "scripts/check-governed-queue-guard.mjs --test": 0,
        "ci": "in_progress"
      },
      "line_budget": "own diff 1c2e2c4..be1da0f: 3 files, +315 −77 (api.ts +108 −52, api.test.ts +196 −25, .changeset/11628-external-envelope.md +11 −0); the merge commit adds no line to these paths",
      "files_changed": [
        "packages/app-shell/src/views/metadata-admin/external/api.ts",
        "packages/app-shell/src/views/metadata-admin/external/api.test.ts",
        ".changeset/11628-external-envelope.md"
      ],
      "deviations": [
        "Live panel measured on the vite dev console from this worktree, not on the backend-served /_console, with the dev-only /api/v1/dev/metadata-events stream aborted (reason in tests). The first unblocked after run showed the reload wiping the timestamp.",
        "Gate set widened beyond the hand-derived lead: 16 root check:* gates whose subject the diff touches, plus changeset presence/no-major and the governed-queue guard. The package vitest run carried --maxWorkers=2 for the shared box.",
        "importObjectDraft (the 5th call, outside the 4 the card names) changed under PM Zone 2.2 and the claim file surface (jsonOrThrow and every call that reads its answer): its nested /meta 403 printed [object Object], measured live.",
        "Commit trailers use the AGENTS.md model-free pair (Claude-Session + Co-authored-by: Claude), not the harness reminder model-named Co-Authored-By. The PR footer is the os-dev session-URL form. No model identifier appears in the PR, the changeset, or comments.",
        "Cleanup done: own backend and console stopped by PID group, own objectstack-live-11628 and objectui-issue-11628 worktrees removed (node_modules first, no --force), temp ref deleted."
      ]
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Oct 5, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT: PR objectui#11640, head 4779053. It lands when every check on this head is green

    domain:ui execution seat 1 @ objectui · session_015W8GBu6sBiqus2L2xjMsAL (os-steve) · 2026-10-05T03:49Z. Reviewed against GitHub and origin/main, not against the report's prose.

    • Shape. The PR is a draft against main, and its assignee is os-steve. Its first line is Fixes #11628, the only line with a closing keyword next to an issue number. Clause-②: no is on its own line. Against main the diff is 3 files, +315/−77, inside the claim's surface: api.ts, api.test.ts and the changeset. No panel component changed.
    • The envelope is read once. readExternalData unwraps { success: true, data } for tables, draft, catalog and validate, and the tables ?? [] tolerance that hid the defect is gone. A 2xx body without the envelope now fails loudly and names the route, instead of rendering an empty list. That is the contract-first direction.
    • The refusal half rode this PR, as the dispatch allowed for the same envelope in the same function. The old reader stringified the ADR-0112 { error: { code, message } } object, so every refusal read [object Object]. It now uses app-shell's existing readEnvelopeFailureText, not a second reader. The 503 hint keys on error.code === 'SERVICE_UNAVAILABLE'. The seat re-read objectstack origin/main: external-datasource-routes.ts:383 sends exactly that code, and the route header (:52) records the rename away from external_service_unavailable. importObjectDraft (the /meta door) reads both of that door's measured dialects.
    • Tests. The fixtures moved to the measured wire shapes. Red before, on base api.ts: 12 failed, 4 passed. Green after: 16 passed. The whole app-shell suite was green on the merged head. Live: the panel lists 2 tables, shows a snapshot time, validates 2 rows, and opens the import dialog on the draft. Refusals read the server's message and code. The 503 arm can't be reached on the showcase, so it is NOT MEASURED live and is covered by a unit test.
    • Changeset, sentence by sentence. It is @object-ui/app-shell: patch. All three paragraphs match the diff: the four reads, the refusal text and the 503 hint, and the loud non-envelope answer. The Clause-②: no line is true: nothing is added to the package entry.
    • Left as noted, not filed: the dev-only MetadataHmrReloader reload after Refresh catalog (import.meta.env.DEV only), and the /meta door's two refusal dialects, which ADR-0112 already records and the client now reads. Neither has a reachable wrong answer in the published console.

    Landing: on all-green checks on this head, ready, then auto-merge into the merge queue.


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 5, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR objectui#11640 → main 0abd4f9, verified by content

    domain:ui execution seat 1 @ objectui · session_015W8GBu6sBiqus2L2xjMsAL (os-steve) · 2026-10-05T04:19Z.


    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

Labels

area:studioChanging a running app without code — authoring, publish, docs and the portalbugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions