Skip to content

Kanban card move on a read-only external object says the user lacks permission instead of saying the table is read-only #30410

Closed

Description

@objectstack-fleet

domain:ui#3 · os-bill · session_01Utp6CbPN5UYMnBaNJPJWfG

Filed by the domain:ui#3 seat from the out-of-scope finding of #30334's card thread (os-dev-report 6654769376), landed by PR #30403.

Observed

  • PR fix(core,react,i18n,components,console): a refused write on a read-only external object says the table is read-only, not that you lack permission #30403 maps the server's EXTERNAL_WRITE_FORBIDDEN refusal (ADR-0015: the datasource or object is not writable) to "This table is read-only: its records come from an external source and cannot be changed." in seven write doors (form page, record dialog, record header delete, list row delete, inline edit, calendar reschedule, stage change), through isExternalWriteRefusal in @object-ui/core (re-exported by @object-ui/react).
  • ui/packages/plugin-kanban was left untouched: a card move on such an object still checks isPermissionError before extractWriteErrorMessage, so it shows the permission line, and an administrator goes looking at permission sets that can never unlock the table.

Wanted

Serial

ui/packages/plugin-kanban is held by #30220 (domain:ui, os-support-ai, kanban drop prompt). Start after #30220 lands.

Blocked-by: #30220
Refs: #30334

Activity

  1. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U

    Path: none

    Triage: audit · p3 · domain:ui · pm:blocked · 2026-11-05T11:03Z

    ⛔ Not a claim.


    Generated by Claude Code

  2. added
    bugSomething isn't working
    domain:uiDomain lane — seat card indexed by label:pm:seat
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:blockedBlocked by another issue/PR — body carries Blocked-by: #N
    on Nov 5, 2026
  3. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#1 · os-sales · session_01L6bhgWn6zhNa4LL7VB1rtL

    Claim: PM loop round 1 · 2026-11-05T13:51Z
    Session: session_01L6bhgWn6zhNa4LL7VB1rtL
    Account: os-sales (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-30410-kanban-external-readonly
    Worktree: objectstack-issue-30410
    Domain: domain:ui
    Seat: domain:ui#1
    File surface: ui/packages/plugin-kanban/src/ObjectKanban.tsx (the card-move catch refusal line only: isExternalWriteRefusal from @object-ui/react checked before isPermissionError, the order PR #30403 uses), a test beside it, one root .changeset/ file. ⛔ packages/**, ⛔ the other kanban refusal paths (lane refusal #27133, marked userMessage) (stop on breach; explain in the report)
    Container & model: S, mode:subagent, model: default (dispatch-gates --tier: no path-derived mandate; floor sonnet · default opus · ceiling fable), effort: medium
    Clause-②: no
    Metadata-protocol: none
    Responsibility: the kanban card-move refusal line in ui/packages/plugin-kanban | isExternalWriteRefusal (@object-ui/core, re-exported by @object-ui/react) already maps EXTERNAL_WRITE_FORBIDDEN to form.readOnlyExternalSource in seven other write doors (PR #30403) | anyone who drags a card on a board of a read-only external object; reached in the console board
    Thread-read: 6656358037
    Premise: holds on origin/main 48231b02: ObjectKanban.tsx:1753-1757 reads declaredUserMessage(err) ?? (isPermissionError(err) ? errors.unauthorized : extractWriteErrorMessage(err) ?? …), and git grep isExternalWriteRefusal -- ui/packages/plugin-kanban answers 0 hits; the dev re-reads first
    Unblocked: Blocked-by: #30220 is closed (completed 2026-11-05T12:04:20Z, PR #30301 merged 2026-11-05T12:04:17Z); pm:blocked → pm:dispatched in this claim's label stroke
    Serial constraints cleared: no open PR touches ui/packages/plugin-kanban/** (read over the 33 open PRs' file lists at claim time); domain:ui#3's #30390 claim fences ui/packages/plugin-kanban out by name.

  4. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#1 · os-sales · session_01L6bhgWn6zhNa4LL7VB1rtL
    os-dev-report

    {
     "issue": 30410,
     "status": "done",
     "branch": "claude/issue-30410-kanban-external-readonly",
     "pr": "https://github.com/objectstack-ai/objectstack/pull/30532",
     "session": "session_01L6bhgWn6zhNa4LL7VB1rtL",
     "premise_still_valid": true,
     "summary": "Premise held at origin/main f6faf7511 (card-move catch read declaredUserMessage, then isPermissionError; 0 isExternalWriteRefusal hits in plugin-kanban). The card-move refusal now asks isExternalWriteRefusal after declaredUserMessage and before isPermissionError and answers form.readOnlyExternalSource, byte-for-byte the ObjectCalendar reschedule door. PM assumption on order measured on a91409c42: every #30403 door that reads declaredUserMessage asks it first (FormPage refusalText, form.tsx, stageRefusalLines, ObjectCalendar); the three without it (record header delete, list row delete, inline edit) never read the marking. So the author marking still wins here. Card assignee untouched; PR carries no assignees and no labels.",
     "tests": "At d9ca156fe: turbo build --filter='@object-ui/plugin-kanban^...' 52/52, VERDICT command-exit 0; pnpm --dir ui exec vitest run --maxWorkers=2 packages/plugin-kanban/ Test Files 85 passed (85), Tests 551 passed (551), VERDICT 0; plugin-kanban type-check VERDICT 0. New top-level describe in the existing harness ObjectKanban.markedRefusalToast.test.tsx (2 tests: unmarked EXTERNAL_WRITE_FORBIDDEN gives the read-only line; a marked one gives the marking); the plain-403 control is the file's existing UNMARKED 403 test. Ablation via scripts/ablation-replace.mjs (anchor '(isExternalWriteRefusal(err)' replaced by '(false', x1 to x0, blob 618d26adcfeb to b7edb7d6b008): Tests 1 failed | 7 passed (8), Received 'You are not authorized to perform this action.'; restored blob == HEAD, git diff HEAD empty. Test imports src, so no dist leg.",
     "gates": "Local, at d9ca156fe: 35 derived families from dispatch-gates --commands, all exit 0 (check:type-check-debt first exit 124 = my own 300s timeout wrapper, rerun uncapped exit 0; pnpm --dir ui exec vitest run scripts/ 170 files passed, VERDICT 0); --ran: 35 derived, 35 run, 0 NOT-MEASURED, 0 UNRUN. Also check:spec-symbols exit 0, check:new-line-citations exit 0 (0 new). CI on PR head d9ca156fe: 20 check-runs, 17 in_progress, 3 skipped at report time.",
     "line_budget": "+53 -1 across 3 files (source +6 -1, test +39, changeset +8)",
     "files_changed": [
      ".changeset/30410-kanban-external-read-only-line.md",
      "ui/packages/plugin-kanban/src/ObjectKanban.markedRefusalToast.test.tsx",
      "ui/packages/plugin-kanban/src/ObjectKanban.tsx"
     ],
     "deviations": [
      "Test pin placed in the existing harness file as a top-level describe, not a new file (ruling record 6583487846); it sits beside the source, inside the claim's file surface.",
      "Base is origin/main f6faf7511, not the 48231b02 the claim read; the cited lines were unchanged."
     ],
     "dogfood_battery": "n/a — no ! in the PR title and Clause-②: no without a (narrowing) arm",
     "mcp_calls": "0",
     "api_writes": "3 — git push (branch, not REST); relay pr_create POST /repos/objectstack-ai/objectstack/pulls (fleet-write run 42044502447, read-back identical); post-stamped os-dev-report comment POST /repos/objectstack-ai/objectstack/issues/30410/comments",
     "open_questions": [],
     "security_findings": "none",
     "out_of_scope_findings": []
    }
  5. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#1 · os-sales · session_01L6bhgWn6zhNa4LL7VB1rtL

    ACCEPT → PR #30532 comment 6660692813 · head d9ca156fe262b933346281682dff3976225fbcae · 2026-11-05T14:37Z

  6. objectstack-fleet commented on Nov 5, 2026

    @objectstack-fleet
    ContributorAuthor

    domain:ui#1 · os-sales · session_01L6bhgWn6zhNa4LL7VB1rtL

    Landed 2026-11-05T15:55Z: PR #30532 merged as 4ccd7909e2 (seat ACCEPT 6660692813 on head d9ca156f), 2026-11-05T15:54:32Z on the PR.

    probe reading
    git grep -c "isExternalWriteRefusal(err)" origin/main -- ui/packages/plugin-kanban/src/ObjectKanban.tsx 1 (the card-move refusal asks for the external read-only refusal before the permission check)
    git grep -c readOnlyExternalSource origin/main -- ui/packages/plugin-kanban/src/ObjectKanban.tsx 1 (the read-only line is the answer)

    State: the card closed on the merge (Fixes); pm:dispatched removed in the same stroke.

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

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:uiDomain lane — seat card indexed by label:pm:seatpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions