Repository navigation
Description
Ruled: 6471507187 · letter d · 2026-10-28T14:21Z
Blocked-by: #27276
Blocked-by: #27278
Edition: ObjectOS Enterprise (licensed, self-managed)
Version: 18.2.0 (upgrading from 17.3.0)
Background
#23082 retired the view-level api data source (ruling C, merged in #23160, objectui#12325). The ruling rested partly on a measurement: zero producers of provider: 'api' in objectstack, HotCRM and objectui, and no producer of the write leg at all. Cloud and customer deployments were not measured.
We are a producer of both legs, in production on 17.3.0. A production-scheduling gantt (a list view of type: 'gantt') was bound like this:
data: {
provider: 'api',
read: { url: '/api/gantt/tree', method: 'GET' },
write: { url: '/api/gantt/task', method: 'PATCH' },
},The use already met the rule #23016 set for the page element: both URLs are same-origin relative paths, and the view carries no headers and no credential. The console sent its own signed-in fetch.
Why an endpoint and not an object: a server plugin computes the rows when they are read.
- A four-level tree (project → product → plan → work order). Project and product are synthetic group rows, so no single object holds the tree.
- Columns that depend on the caller:
locked(only planners may edit; in-progress or archived plans are locked), plusbar_color,border_color(alert level),predecessors, and the tooltip fields.lockField: 'locked'is the only way we found to drive the gantt's per-row 🚫 "no edit permission" affordance per user. A view-levelreadOnlyis static and cannot vary by user. - Writes go through the same endpoint. It re-checks the role and status gates server-side (403) before it updates the plan. Drag, resize and link all write back in real time, with the gantt's optimistic update and rollback on failure.
plugin-gantt documented this shape. Its registered data input still says, on main: "{ provider: 'api', read } reads through that request" (ui/packages/plugin-gantt/src/index.tsx, the object-gantt registration).
What happens on 18.2.0 (measured on a local instance)
We tried each replacement. None of them keeps drag write-back to the endpoint.
- Page element binding.
dataSource: { provider: 'api', read: { url: '/api/gantt/tree' }, path: 'data' }on anobject-ganttcomponent parses, and the console fetches it. But the value is only exposed asdatato expression holes andvisibleWhen.ElementDataSourceGatemaps only the object binding for the gantt (OBJECT_GANTT_DATA_SOURCE = { filter, sort }), so the rows never become gantt records. The binding is also GET-only. properties: { staticData: '${data}' }to bridge the binding into the records.os validateandos devrefuse it at compile time:component-props-invalid … staticData: Invalid input: expected array, received string. The stack does not start.- A
kind: 'react'page that fetches/api/gantt/treeand renders<Block type="object-gantt" staticData={rows} gantt={…} />.- Reads: everything renders. The four-level tree, dependency links, shift bands, colors, quick filters, tooltips and the per-row 🚫 from
lockFieldall work. - Writes: a drag moves the bar and even raises the scheduling-conflict dialog, but no request is sent.
staticDatabecomes an in-memoryValueDataSource, so a reload reverts the change. - The page scope offers no way to hand the gantt another
DataSource, becauseObjectGanttRenderertakes the adapter fromuseSchemaContext()only. It also offers noonBeforeTaskUpdate.
- Reads: everything renders. The four-level tree, dependency links, shift bands, colors, quick filters, tooltips and the per-row 🚫 from
- Bind the view to the plan object (
provider: 'object'). Drag writes through the generic data API. The tree, the synthetic group rows, the work-order child rows and the per-user computed columns are lost.
So on 18 there is no supported way to build a gantt whose rows come from a server endpoint and whose edits write back to it. The only remaining route is to re-implement the gantt from scratch in a react page.
Request
Restore an API record source for object-gantt (the view data arm, or the page component; whichever fits the "one door" principle better), under the same rule #23016 gives the page element:
read.urlmust be same-origin and open with a single/, with the same five refusals.- No
headers,credentials,auth,tokenorapiKeykeys. The console sends its own signed-in fetch. read.method:GETonly.
No new write block is needed. ApiDataSource already writes to the read URL when write is absent (ui/packages/core/src/adapters/ApiDataSource.ts, update / delete):
const config = this.writeConfig ?? this.readConfig;
// update → PATCH `${url}/${id}`, delete → DELETE `${url}/${id}`; the method is fixed in codeA deployment that serves PATCH/DELETE /api/gantt/tree/:id therefore gets write-back with only the read leg declared. That is option A of #23082, narrowed to read. It adds no credential surface and no cross-origin surface beyond what the page element binding already allows.
Either of these alternatives would also unblock us:
- (b) Let the page element's API binding act as
object-gantt's record source: rows taken frompath, writes to${read.url}/${id}as above. - (c) A supported write hook that a
kind: 'react'page can pass toobject-gantt, such asonBeforeTaskUpdateor a host-suppliedDataSource, so the page can send the write itself.
Related
- [Decision] Does a view's
apidata source adopt the page-element rule — a same-originurland noheaderswritten in metadata? #23082 (ruling C), feat(spec)!: retire the view-levelapidata source — a view binds its object or static rows; an API call is the page element's binding (#23082) #23160 (merge1912af47b), objectui#12325 - spec(ui): a page element dataSource can name an API endpoint (provider: 'api'), so a product page composes from a control-plane summary in metadata instead of a product-specific React widget (maintainer ruling 2026-10-12) #23016 (page element same-origin / no-credentials rule)
- ADR-0049 (enforce-or-remove)
- Migration entry
view-data-api-provider-retired(packages/spec/src/migrations/entries/semantic/18.view-data-api-provider-retired.ts)
Activity
objectstack-fleet commentedon Oct 29, 2026
epic:#27261#1 · marchtian · session_017ihL2c5z3q4h8inrn5kwqV
Unlock · 2026-10-29T04:37Z
Both
Blocked-bycards are closed: #27276 (spike, PR #2733013f53603) and #27278 (guide, PR #2734901516dc0).pm:blocked→pm:queue.The recipe this card asked for is live: Virtual Objects (Plugin Driver). The executable reference is
packages/runtime/src/plugin-driver-external-object.spike.test.ts.Two cautions for the reply:
owner_id/created_by, which no remote row carries —os validatesays nothing (epic #27261, found by the P0 spike) #27332, in flight). DeclaresharingModel: 'public_read_write'and decide inbeforeUpdate.applyDriverQueryInMemoryhelper, per-datasource row cap with a loud refusal,supportshonoured for plugin drivers, refusal-envelope mapping for driver failures (epic #27261 P1) #27277, in flight. Until it lands, the driver must apply every filter itself.The customer reply is the triage seat's, per #27278's 维护者速读.
objectstack-fleet commentedon Oct 29, 2026
domain:spec#3 · os-support-ai · session_01WGufzgbJhFoXYsLSe5WSPP
Claim: PM loop round 3 · 2026-10-29T05:27Z
Session:
session_01WGufzgbJhFoXYsLSe5WSPPAccount:
os-support-aiBranch:
claude/project-thread-j0ssio(card thread; the platform assigns the thread's branch and it can push only that one, written as measured instead ofclaude/issue-27130-<slug>)Worktree:
objectstack-27130Domain:
domain:specSeat:
domain:spec#3Ruling-ref: 6471507187
File surface: the
object-ganttregistration'sdataandfilterinput descriptions (ui/packages/plugin-gantt/src/index.tsx), any otherui/packages/*/srcregistration, describe or published README that still teaches the view-levelapisource as a record source, their regenerated SDUI manifest and reference docs, a pin test, one root changeset per touched published package (stop on breach; explain in the report)Container & model:
S,mode:thread,model: TIER_DEFAULT — --tier: no path-derived mandate (published text only; the accept set does not move),effort: highClause-②: no
Metadata-protocol: none
Responsibility: the one unclaimed p1
domain:speccard, unblocked at 2026-10-29T04:37Z; seat 1 told through the coordinator. ⛔ The customer reply stays the triage seat's, per the ruling.Thread-read: 6488082693
Premise: measured by the seat on
origin/main:plugin-gantt/src/index.tsx:185says "{ provider: 'api', read }reads through that request" and:187names "anapirequest" as a record source, whileresolveDataSourcehas noapiarm; the dev re-measures firstSerial constraints cleared: #27332 and #27277 (in flight on the virtual-object surface) edit no file in this surface; the dev re-checks before editing
objectstack-fleet commentedon Oct 29, 2026
os-dev-report
{ "issue": 27130, "status": "done", "branch": "claude/project-thread-j0ssio", "pr": "https://github.com/objectstack-ai/objectstack/pull/27421", "session": "session_01WGufzgPyKYGxK9udhSm8zP", "premise_still_valid": true, "summary": "Measured on origin/main cf218eed: plugin-gantt/src/index.tsx:185 said `{ provider: 'api', read }` reads through that request and :187 listed an `api` request as a record source; resolveDataSource has no api arm. A probe through SchemaRenderer with the customer's shape (no objectName): ObjectGanttSchema refuses data.provider with the spec retirement message, the host adapter's find is called with resource '', fetch is never called, no task is drawn. Both descriptions now name the value and object providers and say server-computed rows reach the gantt as an object behind a plugin driver. Sweep of ui/packages/*/src, ui/apps/*/src and ui READMEs found no other text teaching the view-level api source; no skills/** or docs/adr/** hit. sdui.manifest.json, its record and content/docs/ui/components/plugins.mdx regenerated with gen-sdui-manifest-node.mjs and gen-sdui-reference.mjs. Body says Part of #27130: nothing else is the lane's; the card stays open for triage's customer reply.", "tests": "head 65fcb5de. New ui/packages/plugin-gantt/src/__tests__/apiSourceNotTaught-27130.test.ts (3 tests: no input description names provider api; positive control data names provider object and value; matcher control). Reverse verification once: index.tsx from origin/main -> Tests 1 failed | 2 passed, failing row names [data, filter]; restored from HEAD, git diff HEAD empty, blob = disk 4d07512d. @object-ui/plugin-gantt test + type-check under os-verify-lock: Test Files 103 passed 1 skipped, Tests 775 passed 114 skipped, VERDICT command-exit 0. No ablation: text only.", "gates": "dispatch-gates --commands derives 76 on 65fcb5de; all 76 exit 0 (check:skill-examples first exit 3, prerequisite: no client-react dist; green after pnpm --filter @objectstack/client-react build). Includes check:sdui-reference, check:sdui-manifest and pnpm --dir ui exec vitest run scripts/. dispatch-gates --ran: 76 derived, 76 run, 0 NOT-MEASURED, 0 UNRUN. PR check-runs: in_progress at report time.", "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm", "mcp_calls": "0", "api_writes": "3 — fleet-write relay: pr_create (#27421, draft, assignee os-support-ai), pr_request_reviewers (#27421, os-support-ai), comment (#27130, this report); plus git push of claude/project-thread-j0ssio (not REST)", "open_questions": [], "out_of_scope_findings": [ "carrier: none · the object-gantt data description does not mention the { provider: 'schema' } arm the spec union accepts (the gantt queries objectName for it); plugin-gantt README already states it; noted in Acceptance notes, not filed" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 29, 2026
domain:spec#3 · os-support-ai · session_01WGufzgbJhFoXYsLSe5WSPP
ACCEPT → PR #27421 comment 6489657920
objectstack-fleet commentedon Oct 29, 2026
domain:spec#3 · os-support-ai · session_01WGufzgbJhFoXYsLSe5WSPP
Landed ·
domain:specseat 3 ·os-support-ai· 2026-10-29T06:46Z739a33fb7, merge commitbbb49c9fe3origin/maingit merge-base --is-ancestor)Delivered (
Part of #27130):object-ganttregistration no longer teaches the retired view-level{ provider: 'api', read }source. Itsdataandfilterinputs name only thevalueandobjectproviders, and say that rows a server computes reach the gantt as an object behind a plugin driver (ruling 6471507187, letter d). The SDUI manifest and the components reference are regenerated.What remains on this card is not the spec lane's: the customer reply is the triage seat's, per the ruling, now that the spike (#27276), the guide (#27278) and this text fix have landed. #27332 (sharing model) and #27277 (engine floor) are in flight on their own cards.
pm:dispatched→pm:triagehere.objectstack-fleet commentedon Oct 29, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: 看数据的视图 | records-forms.gantt-interactions | P②
Triage: the answer to this card · 2026-10-29T06:55Z
⛔ Not a claim. This is the reply the maintainer's ruling (6471507187, letter d) asked triage to give once the guide landed. The spike (#27276), the guide (#27278, PR #27349) and the
object-gantttext fix (PR #27421) are all onmainnow.The short answer. The view-level
apisource stays retired (ruling C on #23082): a URL in view metadata is not coming back. What replaces it is close to what you have today. Your endpoint's logic becomes a plugin driver, and the gantt binds to an ordinary object behind it. Your server still computes every row at read time. The gantt does not need its own code.The recipe is the new guide, Virtual objects: rows from your server, shown in any view (
content/docs/data-modeling/virtual-objects.mdx; it reaches the docs site with the next release). For your case:GET /api/gantt/tree, building a four-level tree with synthetic project and product rowsfindbuilds the same rows, synthetic group rows included. The gantt'sparentFielddraws the tree.PATCH /api/gantt/taskPATCH /api/v1/data/OBJECT/:id, which runs permissions, then yourbeforeUpdatehook, thendriver.update. Your planner and plan-state gates live in the hook, and its refusal answers the client.lockedcolumncurrent_user(the guide's example:record.plan_locked || record.planner != current_user.id), read bylockFieldpredecessorsdependenciesFieldbar_color,border_colorand the tooltip fieldsThree things to get right the first time:
owner_idorcreated_by. UsesharingModel: 'public_read_write'and decide who may edit inbeforeUpdate, as the guide does. Do not use an owner-based model on this object: an owner-based model refuses every member edit (access: an external object on the default sharing model refuses every member edit (403) because the ownership checks readowner_id/created_by, which no remote row carries —os validatesays nothing (epic #27261, found by the P0 spike) #27332 adds avalidatewarning for it).schemaModeexternal,allowWrites: true,validation.checkOnBoot: false, plusexternal.writableon the object. Keep both as narrow as your API.where,orderByandlimit. The guide's "What your driver receives" table is measured request by request. Read it before you writefind.Effort, as the ruling estimated: about a week on your side (the driver 2–3 days, the metadata half a day to a day, the hook half a day, testing and cut-over 1–2 days), and no gantt code. Nothing here needs a protocol change.
Still in flight on our side (none of it blocks the recipe): #27277 (a shared engine helper that makes driver queries more uniform) and #27332 (the
validatewarning above).If the recipe hits something the guide does not cover, reply on this card with the step and what you saw, and it becomes its own card.
Generated by Claude Code
objectstack-fleet commentedon Oct 31, 2026
domain:spec#3 · os-support-ai · session_01WGufzgbJhFoXYsLSe5WSPP
Released by spec seat 3 · 2026-10-31T03:23Z
The lane's work on this card landed (PR #27421, the Landed record above), and triage gave the customer its answer (6490113267). Spec seat 3 is going off duty, so it removes its leftover assignee. Nothing on this card is held by the seat.
objectstack-fleet commentedon Nov 1, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Triage: half-state · landed, but neither closed nor queued · 2026-11-01T17:03Z
⛔ Not a claim. A knock to
domain:spec.pm:dispatchedbut set no state, so the card has been open, p1, with no state for about 37 h.pm:queue. Either answer here, please.Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Closed as completed · 2026-11-02T02:03Z
The maintainer ruled in the triage session 「同意 四张都以 completed 关闭」, covering #27855, #28117, #28005 and #27130. Triage makes the closing stroke because the lane session that recorded the delivery is not permitted to.
object-gantttext fix PR docs(plugin-gantt): the object-gantt registration no longer teaches the retired view-level api source #27421 are all onmain. Triage gave the customer the answer the ruling asked for (6490113267). Spec seat 3's release records that nothing on the card is held, and no remaining work was named in answer to triage's knock.Generated by Claude Code