Skip to content

test: spike a virtual object on a plugin driver through steps a to g - #27330

Merged
objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/project-thread-ms6480
Oct 29, 2026
Merged

objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/project-thread-ms6480

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Requested by 田騏 · project thread

Fixes #27276

Clause-②: no
Metadata-protocol: none

维护者速读

What changes

Two test files, no product code.

  • packages/runtime/src/plugin-driver-external-object.spike.test.ts (12 tests). A plugin publishes driver.spike_tree, an in-memory stand-in for a remote system that records every call it receives and refuses every method or operator outside the spike by name. defineStack declares a datasource on it (schemaMode: 'external', validation: { onMismatch: 'fail', checkOnBoot: false }, allowWrites: true, no introspectSchema on the driver) and two external objects: spike_node (a tree with parent, depends_on, a current_user formula locked, a beforeUpdate hook with a PLAN_LOCKED refusal, a gantt list view) and spike_owned (left on the platform's default sharing model). The kernel boots the real REST, auth, security and sharing plugins over it, and each step is asserted on what reached the driver, not on what the engine says it sent.
  • ui/packages/plugin-gantt/src/ObjectGantt.pluginDriverTree.test.tsx (4 tests). The real GanttView over the records that read serves: the tree, the formula lock on the rendered bars, and the write a pointer drag produces.

Why

#27276 asks, before any platform change in #27261, which of the seven steps a virtual object on a plugin driver passes today. Each step is now a test; the one that does not pass is pinned as it behaves, so the change that lifts it turns that test red on purpose.

Tests

Step Verdict What the test pins
(a) passes driver: 'spike_tree' with config: {} resolves to the plugin's driver.spike_tree service; the engine routes spike_node to that instance; the connection service reports already-registered and leaves it alone; the boot calls connect once.
(b) passes The boot completes with onMismatch: 'fail' and checkOnBoot: false on a driver with no introspectSchema, and no syncSchema is sent. A later validateAll() reports spike_node ok: false, so the opt-out is what lets it boot.
(c) passes The plan view's filter and sort, sent as the console sends them, arrive at driver.find as { where: { archived: false }, orderBy: [{ field: 'start_date', order: 'asc' }], limit: 50, object: 'spike_node' }. No fields, no offset. A separate count with the same where follows.
(d) passes The formula over current_user.id is computed engine-side per caller: the same row reads locked: false for the planner and locked: true for the administrator.
(e) passes on sharingModel: 'public_read_write' A hook refusal answers 409 RESOURCE_CONFLICT, declaredCode: 'PLAN_LOCKED', with the catalogue message, and update is never sent. An allowed write reaches driver.update last with the patch, the hook's stamp, and the engine's id, updated_at, updated_by; the returned row is served with the formula recomputed.
(e3) does not pass on the default sharing model A member whose permission set grants allowEdit gets 403 PERMISSION_DENIED on every row of spike_owned: the sharing check reads owner_id and the ownership floor reads created_by, and no remote row carries either.
(f) passes (unit) The parent renders as a summary bar, the children as task bars, the dependency as a link; the formula lock removes the resize grips and blocks the drag; dragging the unlocked bar calls dataSource.update('spike_node', 'n_cut', { start_date, end_date }). The adapter's PATCH /data/:object/:id is pinned in packages/client/src/client.test.ts:2177.
(g) passes A user whose permission sets grant nothing on spike_node gets 403 on the list read with zero driver calls, and 403 on a PATCH after one by-id findOne and no write.

Acceptance notes

Verification

Head 680ce37b, branched from b4458075; main has since moved 11 commits, none in the paths this diff reaches, and the gate derivation on current main with this diff applied is the same 53 commands. Local runs on a shared box, so seconds are shared-box seconds.

Spike tests: 12/12 and 4/4
$ pnpm --filter @objectstack/runtime exec vitest run --maxWorkers=2 src/plugin-driver-external-object.spike.test.ts
 Test Files  1 passed (1)
      Tests  12 passed (12)
   Duration  29.86s
$ (cd ui/packages/plugin-gantt && npx vitest run --root ../.. packages/plugin-gantt/src/ObjectGantt.pluginDriverTree.test.tsx)
 Test Files  1 passed (1)
      Tests  4 passed (4)
Package suites and type-checks
$ pnpm --filter @objectstack/runtime exec vitest run --project local --maxWorkers=3   (at 44269895; the runtime file is unchanged since)
 Test Files  469 passed (469)
      Tests  7641 passed | 19 skipped (7660)
$ pnpm --filter @objectstack/runtime typecheck            (tsc + check:test-typecheck)   exit 0
$ pnpm exec turbo run type-check --filter=@object-ui/plugin-gantt   Tasks: 48 successful, 48 total   (at 44269895)
$ (cd ui/packages/plugin-gantt && npx tsc -p tsconfig.test.json)   exit 0   (at 680ce37b)
$ (cd ui/packages/plugin-gantt && pnpm test)   (at 44269895)
 Test Files  102 passed | 1 skipped (103)
      Tests  772 passed | 114 skipped (886)
$ eslint (root config, --no-inline-config) on both files   exit 0, no warnings

680ce37b changed only the gantt test's schema annotation (typed as ObjectGanttHandoff, so type: 'object-gantt'); its file and the test-program type-check were rerun there.

Derived gates: 53 of 53, all exit 0

Second pass at 680ce37b. node scripts/pm/dispatch-gates.mjs --ran on the recorded exit codes: 53 derived, 53 run, 0 NOT-MEASURED, 0 UNRUN (a derived zero).

The first pass at fbd9e598 had five non-zero exits. Two were this diff and are now green: check:slot-lookup (two lookups typed any; now typed with their contracts) and check:where-matcher (the lift pulled in a module-level declaration named in the matcher's message text; the text no longer names it). Two were exit 3, prerequisite not met (check-plugin-teardown-shape --self-test needed its pinned fixture fetched; check:dual-build-cjs-loads needed built dist/), and both pass on rerun. pnpm --dir ui exec vitest run scripts/ failed on 31 unresolved @objectstack/* imports before dist/ was built plus one 15s timeout under load; rerun after the build: 174 passed, 2 skipped (176 files, 4990 tests).

Human Minimum

  • 1. Schemas start as Zod; packages/spec holds no runtime logic. (Not touched.)
  • 2. Every name I added uses the casing for its kind.
  • 3. No formatter ran; pnpm lint passes. (Root eslint run on both files, exit 0.)
  • 4. One issue, one branch off an up-to-date main, one pull request. (Branched at b4458075; main has moved 11 commits since, none in these paths.)
  • 5. A change that publishes carries a changeset, and both declaration lines are filled in here and in the changeset. (Nothing publishes: skip-changeset.)
  • 6. packages/spec touched: its generated artifacts are regenerated and committed. (Not touched.)
  • 7. No hand edits to content/docs/references/, packages/*/CHANGELOG.md or content/docs/releases/.
  • 8. Title, body and comments are in English. (The 维护者速读 block is Chinese by the seat's PR-body rule.)
  • The Before Submitting list in CONTRIBUTING.md is done.

Generated by Claude Code

…ders, locks and writes on drag

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ihL2fiCyHoxhseMhHUuhN
…her liftable

The slot-lookup ratchet refuses an any-typed lookup; the where-matcher lift
pulled in the module-level driver because the matcher's messages named it.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017ihL2fiCyHoxhseMhHUuhN
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

What this run could not see

Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json cf43b6c38091eb15b5d0a4edcfc2080442018568 → packageMentionDocs.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

epic:#27261#1 · marchtian · session_017ihL2c5z3q4h8inrn5kwqV
ACCEPT · PR #27330 @ 680ce37bfed8ed3f4bba3f9127e01b9242eebfae · 2026-10-28T17:56Z

维护者速读

席位意见:ACCEPT。七步全部通过;第 (e) 步只在 public_read_write 下通过,默认共享模型下成员一律 403,已立 #27332。

席位核对

  1. Shape: diff read by the seat (2 test files, +751/−0). The driver records every call; assertions are on what the remote is asked, not on the platform's self-report. The driver refuses unsupported operators by name instead of matching silently.
  2. Premise measured: all seven card steps have a test; (e3) pins the default-sharing refusal as current behaviour, so the fix card access: an external object on the default sharing model refuses every member edit (403) because the ownership checks read owner_id / created_by, which no remote row carries — os validate says nothing (epic #27261, found by the P0 spike) #27332 will flip it on purpose.
  3. Gate check-runs on 680ce37b: 68 runs — 55 success, 13 skipped, 0 failed or pending; mergeable_state: clean. Dev local: 53 gate families derived and run, all exit 0.
  4. Accepted deviations: branch claude/project-thread-ms6480 (platform exception, in the claim); step (f) is a plugin-gantt unit test with the adapter→PATCH hop cited from packages/client/src/client.test.ts.
  5. Out of scope: (e3) filed as access: an external object on the default sharing model refuses every member edit (403) because the ownership checks read owner_id / created_by, which no remote row carries — os validate says nothing (epic #27261, found by the P0 spike) #27332; performance and driver-contract readings carried as comments on objectql: virtual-object correctness floor — applyDriverQueryInMemory helper, per-datasource row cap with a loud refusal, supports honoured for plugin drivers, refusal-envelope mapping for driver failures (epic #27261 P1) #27277 (6476230469) and docs: "a virtual object — rows from your server, shown in any view": plugin-driver recipe, five business examples, the three-way rule, driver shape-pin test; the #27130 customer reply (epic #27261 P1) #27278 (6476246771). Clause-②: no · Metadata-protocol: none · Skills-docs: none — tests only.
  6. Landing: not governed (check-governed-merges --pr 27330: 0 of 2 paths, 751 lines ≤ 3000). The seat's landing stroke on feat(studio): external-object fields show their datasource and write state; no DDL controls on an external object #27316 was refused by the session's permission classifier; this PR waits for the maintainer's landing call the same way.

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

Labels

size/l skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

2 participants