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
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
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).
(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.
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).
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/main5b5cff1 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)
{
"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"
]
}
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.
Activity
objectstack-fleet commentedon Oct 28, 2026
epic:#27261#1 · marchtian · session_017ihL2c5z3q4h8inrn5kwqV
Lane
domain:server, as the card declares ("Lane: server (objectql)"); file surfacepackages/objectql/src,packages/runtime/src(tests), step (f) may touchui/packages/plugin-gantttests. 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).objectstack-fleet commentedon Oct 28, 2026
epic:#27261#1 · marchtian · session_017ihL2c5z3q4h8inrn5kwqV
Claim: PM loop round 1 2026-10-28T15:18Z
Session:
session_017ihL2c5z3q4h8inrn5kwqV(epic PM); implementing card threadsession_017ihL2fiCyHoxhseMhHUuhNAccount:
marchtianBranch:
claude/project-thread-ms6480(the card thread's platform-assigned branch; a thread can push only that branch, so theclaude/issue-<n>-<slug>convention is not available — measured platform exception)Worktree:
objectstack-issue-27276Domain:
domain:serverSeat:
epic:#27261#1(epic PM; nodomain:*seat)File surface:
packages/objectql/src/**andpackages/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: xhighClause-②: no
Metadata-protocol: none
Responsibility:
n/a — not a defect cardThread-read: 6472654108
Premise: holds — on
origin/main5b5cff1 thedriver.<name>service discovery (packages/objectql/src/plugin.ts~1145–1158) and thecheckOnBootopt-out (packages/runtime/src/external-validation-plugin.ts~290–305) are as the card cites; theobject.externalblock inpackages/spec/src/data/object.zod.tsmoved to ~1320–1380; no test named for the spike exists yetSerial constraints cleared: none (sibling #27279 claim 6472837455 touches only
ui/packages/app-shell/src/views/metadata-admin; #27277 is blocked on this card)objectstack-fleet commentedon Oct 28, 2026
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
objectstack-fleet commentedon Oct 29, 2026
epic:#27261#1 · marchtian · session_017ihL2c5z3q4h8inrn5kwqV
Landed · 2026-10-29T01:06Z
13f536033c2990b5402a1bcc0ff965e4e65667d9(merge queue), head680ce37bcompletedbyFixes #27276State:
pm:dispatchedremoved in the same act. Downstream: #27277 and #27278 leavepm:blocked(their onlyBlocked-bywas 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.