You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
A read-only external object offers New and Import, then refuses with a permission message that blames the wrong thing #30334
Filing gate: ③ the maintainer's direct order, verbatim: 「你发现的问题都立卡,安全问题总专用的安全密卡通道,需要进一步分析的进一步测试,还是没有把握的用决策卡。」 (2026-11-05). Found in a real-user browser walkthrough of Setup on main04f36b63f8, with a test app and a local test deployment.
Business reason. An administrator who is told 您没有权限 goes looking through permission sets, and no permission set can fix a table that is read-only by design. The buttons should not be offered in the first place.
Reader. The UI lane dev for the object list and form, reading what the server already knows about the object.
Duplicates. Searched external object read-only create button (46 hits) and external read only import button (45 hits). They are QA run anchors and epic #27314; none has this subject.
Measured
On object spare_parts, synced from a datasource in external mode:
The list shows 新建 and 导入, and the form opens normally.
Submitting shows 「您没有权限保存这条记录。」
The server's own answer is exact: Write 'insert' blocked on object 'spare_parts': datasource 'parts_db' is external (schemaMode=external). Requires datasource.external.allowWrites=true (got false) AND object.external.writable=true (got false). The form drops that reason.
The source table was not changed, so the protection itself is correct.
The list also shows a 在队列中 filter, though this object has no queue.
I did not click 导入, and did not check whether a record's edit and delete are offered.
Expected
A read-only external object does not offer create, import, edit or delete, or shows them disabled with the real reason.
If a write still reaches the server, the message says the table is read-only, not that the user lacks permission.
The queue filter appears only on objects that can be in a queue.
Read fresh. A read-only external object offers New and Import, then refuses with a permission message that blames the wrong thing. Offered doors that cannot work, so p2.
Claim: PM loop round 2 · 2026-11-05T08:15Z
Session: session_01Utp6CajiubyEqtA9TdrtCf (card thread, os-dev); dispatched by seat session session_01Utp6CbPN5UYMnBaNJPJWfG
Account: os-bill
Branch: claude/project-thread-9t8krp (platform-assigned to the card thread; it can push only this branch)
Worktree: objectstack-issue-30334
Domain: domain:ui
Seat: domain:ui#3
File surface: the object list toolbar (New / Import) and record form / detail action gating as measured in ui/packages/plugin-list/**, plugin-grid/**, plugin-form/**, plugin-detail/** or ui/packages/app-shell/src/views/**, the form's save-error mapping, ui/packages/i18n/src/** strings, their tests, one root .changeset/ file; ⛔ packages/** (stop on breach; explain in the report)
Container & model: M, mode:thread, model: default tier (opus) — dispatch-gates --tier at fdad1786: no path-derived mandate, effort: high
Clause-②: yes (widening) — @object-ui/i18n keys expected; the dev measures and the seat corrects to no if none
Metadata-protocol: none
Responsibility: the console's list and form offer create/import/edit on an object the server refuses by ADR-0015 (external, not writable) | the server guard already refuses the write and names both flags; the client drops the reason | every user of a read-only external object
Thread-read: 6652070429
Premise: holds
Serial constraints cleared: in flight domain:ui#30220 (plugin-form/src/WizardForm.tsx, plugin-kanban) and #30122 (plugin-detail/src/recordTimelineStream.ts, timeline renderers) — the dev keeps off those regions and reports any overlap. main red at claim (#30367), not this card's.
Card thread session session_01Utp6CajiubyEqtA9TdrtCf, PR #30403, head ae3c67c, reported 2026-11-05T09:43Z.
{
"issue": 30334,
"status": "done",
"branch": "claude/project-thread-9t8krp",
"pr": "https://github.com/objectstack-ai/objectstack/pull/30403",
"session": "session_01Utp6CajiubyEqtA9TdrtCf",
"premise_still_valid": true,
"summary": "Item 2 only, per the seat's claim ruling (PR says Refs #30334 (item 2)). The ADR-0015 write gate answers 403 EXTERNAL_WRITE_FORBIDDEN (measured live on app-showcase showcase_ext_customer for POST, PATCH and DELETE). New @object-ui/core isExternalWriteRefusal (re-exported from @object-ui/react) reads only that code, asked before isPermissionError; new key form.readOnlyExternalSource in ten packs. Seven surfaces now say the table is read-only: console FormPage, the record dialog (components form), record header delete, list row delete (core recordDelete), record inline edit (InlineEditSaveBar), calendar reschedule, path stage change. Before: the permission line on the two forms, the gate's raw English diagnostic on the three delete/inline surfaces.",
"tests": "76473999 (later commits touch no ui file): targeted vitest under the lock, 199 files (4 new test files + every test importing a changed module + i18n package tests): Test Files 199 passed (199), Tests 2191 passed | 90 skipped, VERDICT command-exit 0. turbo type-check over core, react, i18n, components, plugin-calendar, plugin-detail, app-shell, console: Tasks 81 successful, 81 total, VERDICT command-exit 0. Live walk (app-showcase objectstack dev --fresh + console Vite, zh): create, edit, header delete, row delete, inline edit, before (sources at fdad1786) and after; shots in project files ui-seat3/30334/. No ablation: the new tests carry their own controls and the before leg is the live walk.",
"gates": "ae3c67c2: dispatch-gates --commands derived 35 families, all 35 run with exit 0 recorded before any pipe (ui-seat3/30334/gates-ran.txt in project files), --ran reconciliation: 35 derived, 35 run, 0 NOT-MEASURED, exit 0; check-sdui-manifest among them; ui check:new-line-citations 0 new. CI check-runs: in_progress at report time; a Lint UI red on check:liveness (query/searchFields) is main's #30367.",
"dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm",
"mcp_calls": "4 — mcp__github__issue_read x4 (get x1, get_comments x3), all reads, before the claim and at claim check; no MCP write",
"api_writes": "2 — relay pr_create (POST /repos/objectstack-ai/objectstack/pulls, draft), post-stamped POST /repos/objectstack-ai/objectstack/issues/30334/comments (this report)",
"open_questions": [],
"security_findings": "none",
"out_of_scope_findings": [
"carrier: #30370 · hiding New/Import/Edit/Delete: /auth/me/permissions answers allowCreate/allowEdit/allowDelete true and apiOperations [get,list,create,update,delete,upsert,bulk,aggregate,search,import] for showcase_ext_customer (measured live); buildEffectiveObjectPermissions never reads the ADR-0015 gate",
"carrier: #30372 · the 在队列中 filter: ListView offers it on hasAnyQueue AND the served system queue column; spec resolveInjectedSystemColumns plans queue on external objects",
"carrier: #30220 · plugin-kanban card move reads the same refusal as the permission line (isPermissionError before extractWriteErrorMessage); left untouched, the package is held by that claim"
],
"line_budget": "27 files, +458 / -15 vs merge base (of which 4 new test files and the changeset)",
"deviations": [
"File surface: the claim names plugin-list/grid/form/detail and app-shell views; the measured refusal mapping also lives in ui/packages/react/src/utils/error-message.ts and index.ts (re-export), ui/packages/core/src/utils/external-write-refusal.ts, core/src/index.ts and core/src/actions/recordDelete.ts (the list row delete door), ui/packages/components/src/renderers/form/form.tsx (the record dialog), ui/apps/console/src/components/FormPage.tsx and ui/packages/plugin-calendar/src/ObjectCalendar.tsx. None under root packages/**.",
"The harness's after-PR assignee and review-request step was skipped, per the dispatch order (the seat hands off).",
"The harness's model-named co-author trailer was refused by the pre-push check:commit-card-trailers; the unpublished commit was amended to the model-free pair before the first push."
],
"files_changed": [".changeset/30334-external-write-read-only-line.md", "ui/apps/console/src/components/FormPage.externalWriteRefusal-30334.test.tsx", "ui/apps/console/src/components/FormPage.tsx", "ui/packages/app-shell/src/views/RecordDetailView.tsx", "ui/packages/components/src/__tests__/form-write-error-external-30334.test.tsx", "ui/packages/components/src/renderers/form/form.tsx", "ui/packages/core/src/actions/recordDelete.ts", "ui/packages/core/src/index.ts", "ui/packages/core/src/utils/external-write-refusal.ts", "ui/packages/i18n/src/locales/ar.ts", "ui/packages/i18n/src/locales/de.ts", "ui/packages/i18n/src/locales/en.ts", "ui/packages/i18n/src/locales/es.ts", "ui/packages/i18n/src/locales/fr.ts", "ui/packages/i18n/src/locales/ja.ts", "ui/packages/i18n/src/locales/ko.ts", "ui/packages/i18n/src/locales/pt.ts", "ui/packages/i18n/src/locales/ru.ts", "ui/packages/i18n/src/locales/zh.ts", "ui/packages/plugin-calendar/src/ObjectCalendar.tsx", "ui/packages/plugin-detail/src/InlineEditSaveBar.tsx", "ui/packages/plugin-detail/src/renderers/__tests__/stageRefusalLines.externalWrite-30334.test.ts", "ui/packages/plugin-detail/src/renderers/recordPathStageChange.tsx", "ui/packages/plugin-detail/src/useDetailTranslation.ts", "ui/packages/react/src/index.ts", "ui/packages/react/src/utils/error-message.externalWriteRefusal-30334.test.ts", "ui/packages/react/src/utils/error-message.ts"]
}
Release: 2026-11-05T10:34Z — PR #30403 landed (merge commit a91409c42, merged 2026-11-05T10:31Z; seat ACCEPT 6655338629 on head b43a9857), Refs #30334 (item 2), so this card stays open.
Landed (item 2): a write refused with EXTERNAL_WRITE_FORBIDDEN reads "This table is read-only: its records come from an external source and cannot be changed." in seven write doors, ten languages; other 403s unchanged.
This answers domain:ui#1's ask (6663640225), under the maintainer's 2026-11-05 ruling that triage closes a tracking parent once every carried row is closed. Read fresh before this stroke.
Activity
objectstack-fleet commentedon Nov 5, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: first-touch audit · p2 ·
domain:ui·pm:queue· 2026-11-05T07:11Z⛔ Not a claim.
Generated by Claude Code
objectstack-fleet commentedon Nov 5, 2026
domain:ui#3 · os-bill · session_01Utp6CbPN5UYMnBaNJPJWfG
Claim: PM loop round 2 · 2026-11-05T08:15Z
Session:
session_01Utp6CajiubyEqtA9TdrtCf(card thread, os-dev); dispatched by seat sessionsession_01Utp6CbPN5UYMnBaNJPJWfGAccount:
os-billBranch:
claude/project-thread-9t8krp(platform-assigned to the card thread; it can push only this branch)Worktree:
objectstack-issue-30334Domain:
domain:uiSeat:
domain:ui#3File surface: the object list toolbar (New / Import) and record form / detail action gating as measured in
ui/packages/plugin-list/**,plugin-grid/**,plugin-form/**,plugin-detail/**orui/packages/app-shell/src/views/**, the form's save-error mapping,ui/packages/i18n/src/**strings, their tests, one root.changeset/file; ⛔packages/**(stop on breach; explain in the report)Container & model:
M,mode:thread,model: default tier (opus) — dispatch-gates --tier at fdad1786: no path-derived mandate,effort: highClause-②: yes (widening) —
@object-ui/i18nkeys expected; the dev measures and the seat corrects tonoif noneMetadata-protocol: none
Responsibility: the console's list and form offer create/import/edit on an object the server refuses by ADR-0015 (external, not writable) | the server guard already refuses the write and names both flags; the client drops the reason | every user of a read-only external object
Thread-read: 6652070429
Premise: holds
Serial constraints cleared: in flight
domain:ui#30220 (plugin-form/src/WizardForm.tsx,plugin-kanban) and #30122 (plugin-detail/src/recordTimelineStream.ts, timeline renderers) — the dev keeps off those regions and reports any overlap. main red at claim (#30367), not this card's.chore(changeset): name core and app-shell in the #30334 changeset
objectstack-fleet commentedon Nov 5, 2026
domain:ui#3 · os-bill · session_01Utp6CbPN5UYMnBaNJPJWfG
os-dev-report
Card thread session
session_01Utp6CajiubyEqtA9TdrtCf, PR #30403, head ae3c67c, reported 2026-11-05T09:43Z.{ "issue": 30334, "status": "done", "branch": "claude/project-thread-9t8krp", "pr": "https://github.com/objectstack-ai/objectstack/pull/30403", "session": "session_01Utp6CajiubyEqtA9TdrtCf", "premise_still_valid": true, "summary": "Item 2 only, per the seat's claim ruling (PR says Refs #30334 (item 2)). The ADR-0015 write gate answers 403 EXTERNAL_WRITE_FORBIDDEN (measured live on app-showcase showcase_ext_customer for POST, PATCH and DELETE). New @object-ui/core isExternalWriteRefusal (re-exported from @object-ui/react) reads only that code, asked before isPermissionError; new key form.readOnlyExternalSource in ten packs. Seven surfaces now say the table is read-only: console FormPage, the record dialog (components form), record header delete, list row delete (core recordDelete), record inline edit (InlineEditSaveBar), calendar reschedule, path stage change. Before: the permission line on the two forms, the gate's raw English diagnostic on the three delete/inline surfaces.", "tests": "76473999 (later commits touch no ui file): targeted vitest under the lock, 199 files (4 new test files + every test importing a changed module + i18n package tests): Test Files 199 passed (199), Tests 2191 passed | 90 skipped, VERDICT command-exit 0. turbo type-check over core, react, i18n, components, plugin-calendar, plugin-detail, app-shell, console: Tasks 81 successful, 81 total, VERDICT command-exit 0. Live walk (app-showcase objectstack dev --fresh + console Vite, zh): create, edit, header delete, row delete, inline edit, before (sources at fdad1786) and after; shots in project files ui-seat3/30334/. No ablation: the new tests carry their own controls and the before leg is the live walk.", "gates": "ae3c67c2: dispatch-gates --commands derived 35 families, all 35 run with exit 0 recorded before any pipe (ui-seat3/30334/gates-ran.txt in project files), --ran reconciliation: 35 derived, 35 run, 0 NOT-MEASURED, exit 0; check-sdui-manifest among them; ui check:new-line-citations 0 new. CI check-runs: in_progress at report time; a Lint UI red on check:liveness (query/searchFields) is main's #30367.", "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm", "mcp_calls": "4 — mcp__github__issue_read x4 (get x1, get_comments x3), all reads, before the claim and at claim check; no MCP write", "api_writes": "2 — relay pr_create (POST /repos/objectstack-ai/objectstack/pulls, draft), post-stamped POST /repos/objectstack-ai/objectstack/issues/30334/comments (this report)", "open_questions": [], "security_findings": "none", "out_of_scope_findings": [ "carrier: #30370 · hiding New/Import/Edit/Delete: /auth/me/permissions answers allowCreate/allowEdit/allowDelete true and apiOperations [get,list,create,update,delete,upsert,bulk,aggregate,search,import] for showcase_ext_customer (measured live); buildEffectiveObjectPermissions never reads the ADR-0015 gate", "carrier: #30372 · the 在队列中 filter: ListView offers it on hasAnyQueue AND the served system queue column; spec resolveInjectedSystemColumns plans queue on external objects", "carrier: #30220 · plugin-kanban card move reads the same refusal as the permission line (isPermissionError before extractWriteErrorMessage); left untouched, the package is held by that claim" ], "line_budget": "27 files, +458 / -15 vs merge base (of which 4 new test files and the changeset)", "deviations": [ "File surface: the claim names plugin-list/grid/form/detail and app-shell views; the measured refusal mapping also lives in ui/packages/react/src/utils/error-message.ts and index.ts (re-export), ui/packages/core/src/utils/external-write-refusal.ts, core/src/index.ts and core/src/actions/recordDelete.ts (the list row delete door), ui/packages/components/src/renderers/form/form.tsx (the record dialog), ui/apps/console/src/components/FormPage.tsx and ui/packages/plugin-calendar/src/ObjectCalendar.tsx. None under root packages/**.", "The harness's after-PR assignee and review-request step was skipped, per the dispatch order (the seat hands off).", "The harness's model-named co-author trailer was refused by the pre-push check:commit-card-trailers; the unpublished commit was amended to the model-free pair before the first push." ], "files_changed": [".changeset/30334-external-write-read-only-line.md", "ui/apps/console/src/components/FormPage.externalWriteRefusal-30334.test.tsx", "ui/apps/console/src/components/FormPage.tsx", "ui/packages/app-shell/src/views/RecordDetailView.tsx", "ui/packages/components/src/__tests__/form-write-error-external-30334.test.tsx", "ui/packages/components/src/renderers/form/form.tsx", "ui/packages/core/src/actions/recordDelete.ts", "ui/packages/core/src/index.ts", "ui/packages/core/src/utils/external-write-refusal.ts", "ui/packages/i18n/src/locales/ar.ts", "ui/packages/i18n/src/locales/de.ts", "ui/packages/i18n/src/locales/en.ts", "ui/packages/i18n/src/locales/es.ts", "ui/packages/i18n/src/locales/fr.ts", "ui/packages/i18n/src/locales/ja.ts", "ui/packages/i18n/src/locales/ko.ts", "ui/packages/i18n/src/locales/pt.ts", "ui/packages/i18n/src/locales/ru.ts", "ui/packages/i18n/src/locales/zh.ts", "ui/packages/plugin-calendar/src/ObjectCalendar.tsx", "ui/packages/plugin-detail/src/InlineEditSaveBar.tsx", "ui/packages/plugin-detail/src/renderers/__tests__/stageRefusalLines.externalWrite-30334.test.ts", "ui/packages/plugin-detail/src/renderers/recordPathStageChange.tsx", "ui/packages/plugin-detail/src/useDetailTranslation.ts", "ui/packages/react/src/index.ts", "ui/packages/react/src/utils/error-message.externalWriteRefusal-30334.test.ts", "ui/packages/react/src/utils/error-message.ts"] }Generated by Claude Code
objectstack-fleet commentedon Nov 5, 2026
domain:ui#3 · os-bill · session_01Utp6CbPN5UYMnBaNJPJWfG
Release: 2026-11-05T10:34Z — PR #30403 landed (merge commit
a91409c42, merged 2026-11-05T10:31Z; seat ACCEPT 6655338629 on headb43a9857),Refs #30334 (item 2), so this card stays open.EXTERNAL_WRITE_FORBIDDENreads "This table is read-only: its records come from an external source and cannot be changed." in seven write doors, ten languages; other 403s unchanged.buildEffectiveObjectPermissionsnever reads the ADR-0015 gate → Effective object permissions ignore the external write gate, so the console offers New, Import, Edit and Delete on a read-only external object #30370.Resume-from: none
Back to
pm:queue; triage may close this card in favour of #30370 / #30372.objectstack-fleet commentedon Nov 5, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Closed as completed · 2026-11-05T16:43Z
This answers
domain:ui#1's ask (6663640225), under the maintainer's 2026-11-05 ruling that triage closes a tracking parent once every carried row is closed. Read fresh before this stroke.domain:ui#3's release record (10:35Z) names those carriers and leaves this card for triage to close. Nothing is left open on it.Generated by Claude Code