Skip to content

spike(objectql): a virtual object on a plugin driver — external-bound object boots without introspection, reads with filters, formula over current_user, beforeUpdate hook → driver.update, gantt drag writes (epic #27261 P0 · #27130 route d) #27276

Closed

Description

@objectstack-fleet

Filing gate ③ (maintainer-directed). Ruling 6471382887 on #27261 (batch #423, maintainer 「立专题卡」 2026-10-28): the ontology-over-external-systems epic opens with Tier A (plugin driver, zero protocol change). Part of #27261. Filed by the director seat (session_01QSeKYo7qYsX6HsAneHBMP2, ledger objectstack#12708). ⛔ Not a claim; triage sets the lane and grade. Design: #27261 body; the first customer pull: #27130 (route d, design 6468011868).

Ruling-ref: 6471382887

维护者速读

Task

  • (a) defineStack accepts a datasource whose driver is a plugin-published driver.<name> service (record how the datasource names it: by name, or a driver config the driver validates — datasource.zod.ts:186, 502–525).
  • (b) The stack boots with external.validation.checkOnBoot: false and no introspectSchema.
  • (c) GET /data/spike_node with the view's filter and sort reaches driver.find with a DriverQuery; record which parts arrive.
  • (d) A formula field over current_user on the external object evaluates per caller.
  • (e) PATCH /data/spike_node/:id reaches the beforeUpdate hook first; a hook refusal returns 4xx with the message; an allowed write reaches driver.update and the returned row is served.
  • (f) A gantt view (parentField, lockField, dependenciesField) on the object renders the tree and a drag issues the PATCH (plugin-gantt unit test or the live lane).
  • (g) Object permission sets apply to the external object (a denied profile gets 403).
  • Report: one line per step; every failing step names the smallest platform change it needs.

Clause-②: no · Metadata-protocol: none. Size S. Lane: server (objectql); the gantt step may need the ui seat.

Epic: #27261 (parent; the epic PM dispatches pm:epic children). Dedupe: #27261, #27130, ADR-0015; no open card carries this slice.

Activity

  1. added
    enhancementNew feature or request
    priority:p1High: required for production / M2
    pm:epicReserved by a dedicated epic PM, parent or sub-issue; other PMs never take it; never with pm:queue
    area:recordsBusiness objects, records, the views that show data, usable forms, search
    on Oct 28, 2026
  2. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#27261#1 · marchtian · session_017ihL2c5z3q4h8inrn5kwqV

    Lane domain:server, as the card declares ("Lane: server (objectql)"); file surface packages/objectql/src, packages/runtime/src (tests), step (f) may touch ui/packages/plugin-gantt tests. Written by the epic PM on the maintainer's instruction (ruling 3 in the registration comment on #27261: 田騏, 「同意」, the epic PM's project thread). Priority p1 kept. Dispatch: mode:thread (p1).

  3. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#27261#1 · marchtian · session_017ihL2c5z3q4h8inrn5kwqV
    Claim: PM loop round 1 2026-10-28T15:18Z
    Session: session_017ihL2c5z3q4h8inrn5kwqV (epic PM); implementing card thread session_017ihL2fiCyHoxhseMhHUuhN
    Account: marchtian
    Branch: claude/project-thread-ms6480 (the card thread's platform-assigned branch; a thread can push only that branch, so the claude/issue-<n>-<slug> convention is not available — measured platform exception)
    Worktree: objectstack-issue-27276
    Domain: domain:server
    Seat: epic:#27261#1 (epic PM; no domain:* seat)
    File surface: packages/objectql/src/** and packages/runtime/src/** (tests only), ui/packages/plugin-gantt/** (tests only, step f) (stop on breach; explain in the report)
    Container & model: S, p1, spike, mode:thread, model: default tier (dispatch-gates --tier: "no path-derived mandate … the tier stays the PM's per-card judgment call"), effort: xhigh
    Clause-②: no
    Metadata-protocol: none
    Responsibility: n/a — not a defect card
    Thread-read: 6472654108
    Premise: holds — on origin/main 5b5cff1 the driver.<name> service discovery (packages/objectql/src/plugin.ts ~1145–1158) and the checkOnBoot opt-out (packages/runtime/src/external-validation-plugin.ts ~290–305) are as the card cites; the object.external block in packages/spec/src/data/object.zod.ts moved to ~1320–1380; no test named for the spike exists yet
    Serial constraints cleared: none (sibling #27279 claim 6472837455 touches only ui/packages/app-shell/src/views/metadata-admin; #27277 is blocked on this card)

  4. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#27261#1 · marchtian · session_017ihL2fiCyHoxhseMhHUuhN
    os-dev-report

    {
     "issue": 27276,
     "status": "done",
     "branch": "claude/project-thread-ms6480",
     "pr": "https://github.com/objectstack-ai/objectstack/pull/27330",
     "session": "session_017ihL2fiCyHoxhseMhHUuhN",
     "premise_still_valid": true,
     "summary": "Tests only, zero product code. packages/runtime/src/plugin-driver-external-object.spike.test.ts boots the real REST, auth, security and sharing plugins over a plugin-published driver.spike_tree and pins steps a, b, c, d, e and g on the calls the driver receives (12 tests). ui/packages/plugin-gantt/src/ObjectGantt.pluginDriverTree.test.tsx pins step f on the real GanttView (4 tests). Six steps pass as written; step e passes only with sharingModel public_read_write, and on the default sharing model a member holding allowEdit is refused every edit, pinned as (e3).",
     "steps": [
      "(a) PASS: driver 'spike_tree' with config {} resolves to the plugin's driver.spike_tree service; engine routes spike_node to that instance; connection state already-registered; boot calls connect once.",
      "(b) PASS: boots with onMismatch fail + checkOnBoot false on a driver with no introspectSchema; no syncSchema sent; a later validateAll() reports spike_node ok:false, so the opt-out is what lets it boot.",
      "(c) PASS: GET with the view's filter and sort reaches driver.find as {where:{archived:false}, orderBy:[{field:'start_date',order:'asc'}], limit:50, object:'spike_node'}; no fields, no offset; object rides inside the DriverQuery; a separate count with the same where follows.",
      "(d) PASS: formula over current_user.id is computed engine-side per caller: same row reads locked false for the planner and true for the administrator.",
      "(e) PASS with sharingModel public_read_write: hook refusal answers 409 RESOURCE_CONFLICT, declaredCode PLAN_LOCKED, catalogue message, no update sent; an allowed write reaches driver.update last with the patch + hook stamp + engine id/updated_at/updated_by, and the returned row is served with the formula recomputed. FAILS on the default sharing model (e3): a member with allowEdit gets 403 on every external row because the sharing check reads owner_id and the ownership floor reads created_by, which no remote row carries. Smallest platform change: an os validate warning (not a defineStack refusal, which would narrow) for an external object on an owner-keyed sharing model with no owner column mapped, naming both ways out.",
      "(f) PASS (plugin-gantt unit, not the live console lane): parent renders as a summary bar, children as task bars, dependency as a link; the formula lock removes resize grips and blocks drag; dragging the unlocked bar calls dataSource.update('spike_node','n_cut',{start_date,end_date}); adapter PATCH /data/:object/:id is pinned in packages/client/src/client.test.ts:2177.",
      "(g) PASS: a user whose sets grant nothing on spike_node gets 403 PERMISSION_DENIED on the list read with zero driver calls, and 403 on PATCH after one by-id findOne and no write."
     ],
     "tests": "At 44269895 (runtime file unchanged since): pnpm --filter @objectstack/runtime exec vitest run --project local --maxWorkers=3 under os-verify-lock: Test Files 469 passed (469), Tests 7641 passed | 19 skipped; pnpm --filter @objectstack/runtime typecheck exit 0 (tsc + check:test-typecheck); spike file alone: Tests 12 passed (12), 29.86s. plugin-gantt: turbo run type-check --filter=@object-ui/plugin-gantt Tasks 48 successful; pnpm test Test Files 102 passed | 1 skipped. At 680ce37b: gantt spike file Tests 4 passed (4); tsc -p tsconfig.test.json exit 0; root eslint on both files exit 0. Not-published proof: both packages ship files [dist, ...]; after build, grep for spike symbols in packages/runtime/dist and ui/packages/plugin-gantt/dist = 0 files, positive controls (createExternalValidationPlugin, gantt-task-resize-right) = 2 files each.",
     "gates": "dispatch-gates --commands derived 53 (same 53 derived on origin/main 19f49ef3 with this diff applied). Second pass at 680ce37b: all 52 non-ui commands exit 0; pnpm --dir ui exec vitest run scripts/ exit 0 (174 passed, 2 skipped). --ran reconciliation: 53 derived, 53 run, 0 NOT-MEASURED, 0 UNRUN (derived zero), exit 0. First pass at fbd9e598 had 5 non-zero: check:slot-lookup exit 1 and check:where-matcher exit 1 were this diff and are fixed in 2d8354a0/44269895; check-plugin-teardown-shape --self-test exit 3 (pinned fixture fetched) and check:dual-build-cjs-loads exit 3 (dist built) both pass on rerun; ui scripts exit 1 (31 unresolved @objectstack imports before dist + one 15s timeout under load) passes on rerun. CI check-runs on 680ce37b at 17:40Z (REST read): 37 total, 7 success, 13 skipped, 17 in progress, 0 failed; CI convergence is left to the seat.",
     "dogfood_battery": "n/a — no ! in the PR title and no (narrowing) arm",
     "mcp_calls": "0 — no MCP GitHub calls; PR read-back via unauthenticated REST GET",
     "api_writes": "2 relay writes: fleet-write pr_create (run 40737019540: create + assignee + label legs, body read back 9882/9882 bytes identical, assignees marchtian, labels skip-changeset), and this os-dev-report comment via post-stamped. Plus git push of 5 commits to claude/project-thread-ms6480.",
     "line_budget": "+751/-0 over 2 test files (dispatch-gates count, under the 3000 human-merge threshold); no product lines",
     "deviations": [
      "Step e is pinned on sharingModel public_read_write; the default-sharing behaviour is pinned separately as (e3) on a second object, spike_owned, rather than reported only in prose.",
      "Step f is a plugin-gantt unit test over the records the server read serves plus the dataSource.update call; the adapter-to-PATCH hop is cited from client.test.ts, not re-driven end to end."
     ],
     "files_changed": [
      "packages/runtime/src/plugin-driver-external-object.spike.test.ts (+615)",
      "ui/packages/plugin-gantt/src/ObjectGantt.pluginDriverTree.test.tsx (+136)"
     ],
     "open_questions": [],
     "out_of_scope_findings": [
      "class: c · reach: public PATCH /api/v1/data/spike_owned/o_1 by a member whose permission set grants allowEdit answers 403 PERMISSION_DENIED for every external row on the default sharing model · evidence: (e3) in the spike test pins the two reads that refuse it, find {where:{id}, fields:[id, owner_id]} and findOne {where:{$and:[{id},{created_by: caller}]}}; no remote row carries either column · smallest change: os validate warning for an external object on an owner-keyed sharing model with no owner column mapped · dedupe words: external object sharingModel owner_id created_by 403; ownership floor external; federated object member edit · sub-card of #27261 for the seat to file",
      "carrier: #27277 · noted, not filed: every PATCH reads the pre-image from the remote three times by id, and every list read is a find plus a separate count",
      "carrier: #27278 · noted, not filed: object rides inside the DriverQuery although the type omits it; driver.update receives updated_at, updated_by and id on top of the patch",
      "carrier: #27277 · noted, not filed: a denied PATCH still costs the remote one by-id read before the 403"
     ]
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#27261#1 · marchtian · session_017ihL2c5z3q4h8inrn5kwqV
    Landed · 2026-10-29T01:06Z

    Reading Value
    PR #27330 merged 2026-10-29T01:04Z as 13f536033c2990b5402a1bcc0ff965e4e65667d9 (merge queue), head 680ce37b
    Card closed completed by Fixes #27276

    State: pm:dispatched removed in the same act. Downstream: #27277 and #27278 leave pm:blocked (their only Blocked-by was this card); #27130 stays blocked on #27278. The (e3) finding is #27332. Landing ordered by the maintainer (田騏): 「按照 pm-dispatch 技能审核落地 pr」, the epic PM's project thread.

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

Metadata

Metadata

Assignees

Labels

area:recordsBusiness objects, records, the views that show data, usable forms, searchdomain:serverDomain lane — seat card indexed by label:pm:seatenhancementNew feature or requestpm:epicReserved by a dedicated epic PM, parent or sub-issue; other PMs never take it; never with pm:queuepriority:p1High: required for production / M2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions