Skip to content

feat(discovery): broadcast transactionalBatch capability bit so clients negotiate atomic batch declaratively (#3298)#3345

Merged
os-zhuang merged 2 commits into
mainfrom
feat/discovery-transactional-batch
Jul 20, 2026
Merged

feat(discovery): broadcast transactionalBatch capability bit so clients negotiate atomic batch declaratively (#3298)#3345
os-zhuang merged 2 commits into
mainfrom
feat/discovery-transactional-batch

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Closes #3298.

Problem

The atomic cross-object batch endpoint (POST {basePath}/batch, #1604 / ADR-0034 item 4) and its typed SDK surface (client.data.batchTransaction, #3271) already shipped — but discovery never told a client whether a backend supports it. Consumers (notably ObjectUI's ObjectStackAdapter) had to runtime-probe: fire a /batch, read 404/405 (no route) or 501 (no runtime transaction), and only then fall back to non-atomic client-side simulation.

That is "find out by calling", not declarative capability negotiation — it can't be decided at connect time, and can't serve as the "minimum backend has /batch" gate that's currently blocking the hard-delete of ObjectUI's non-atomic fallback (objectui#2679).

Change

Add a required transactionalBatch: boolean to WellKnownCapabilitiesSchema, and fill it honestly in every discovery producer (declared === enforced) so it never becomes a declared-but-unpopulated bit — the exact failure mode #3271 avoided by not adding a batch key with mismatched producers.

Producer Value Rationale
@objectstack/metadata-protocol (getDiscovery) typeof engine.transaction === 'function' The /batch handler runs inside engine.transaction(), which degrades to a non-atomic passthrough / 501 without one.
@objectstack/rest (/discovery) protocol signal AND api.enableBatch ANDs the runtime signal with whether it actually mounts the route, so a batch-disabled server reports false even on a tx-capable engine (never advertise a route that 404s).
@objectstack/plugin-hono-server (standalone discovery) false This minimal surface mounts CRUD only, not /batch (that ships with @objectstack/rest). Under-reporting is the safe direction — the client keeps its correct-but-slower fallback rather than losing atomicity.
@objectstack/client (exposed) Already normalizes hierarchical capabilities → flat booleans, so client.capabilities.transactionalBatch is exposed and now typed.

Semantics match the existing capability flags: true ⟺ the /batch route is mounted and the runtime can honour a transaction — precisely when the endpoint returns 200 rather than 404/405/501.

Acceptance (from #3298)

  • discovery schema adds the batch capability bit (WellKnownCapabilitiesSchema.transactionalBatch)
  • rest-server / hono-plugin / metadata-protocol producers all populate it
  • tests assert GET /discovery returns the bit
  • @objectstack/client exposes the capability for consumers (the optional item)

Tests

  • spectransactionalBatch is a required field (a payload missing only it fails), described, accepted true/false.
  • metadata-protocol (via objectql protocol-discovery.test.ts) — reports {enabled:true} on the real engine (has transaction()), {enabled:false} on an engine without one.
  • resttrue when runtime supports tx + /batch mounted; false when api.enableBatch off; false when the engine can't honour a tx; always populated even if the protocol omitted capabilities.
  • hono — standalone discovery advertises false, and there is genuinely no POST /batch route on that surface.
  • client — hierarchical {enabled:true} → flat true.

Regenerated content/docs/references/api/discovery.mdx (the only committed generated artifact affected; json-schema/ is gitignored). Changeset added (minor × 5).

Note (out of scope)

pnpm gen:spec-changes / gen:upgrade-guide surfaced pre-existing drift unrelated to this change — the protocol 15→16 DashboardWidgetSchema .strict() migration (dashboard-widget-strict-unknown-keys, from #3251) whose committed projection is stale. I reverted those regenerated files so this PR doesn't absorb #3251's changelog; that drift belongs to #3251's follow-up.

🤖 Generated with Claude Code

os-zhuang and others added 2 commits July 20, 2026 09:20
fix(list): route remaining system-field groupings through shared classifier (#2706)

objectui@3b2e4d98d904d695a8372c394d46b81673011270
…egotiate atomic batch declaratively (#3298)

The atomic cross-object batch endpoint (POST {basePath}/batch, #1604 / ADR-0034
item 4) and its typed SDK surface (client.data.batchTransaction, #3271) shipped,
but discovery never told a client whether a backend supports it. Consumers had to
probe — fire a /batch, read 404/405 (no route) or 501 (no runtime transaction),
then fall back to non-atomic client-side simulation. That is "find out by
calling", not capability negotiation, and it blocks hard-deleting ObjectUI's
non-atomic fallback (objectui#2679).

Add a required `transactionalBatch: boolean` to WellKnownCapabilitiesSchema and
fill it honestly in every discovery producer (declared === enforced), so it is
never a declared-but-unpopulated bit:

- metadata-protocol (getDiscovery): true iff the runtime engine can honour a
  transaction (typeof engine.transaction === 'function'). engine.transaction()
  degrades to a non-atomic passthrough / 501 without one.
- rest-server (/discovery): ANDs that with api.enableBatch — the gate that mounts
  the /batch route — so batch-disabled servers report false even on a tx-capable
  engine (never advertise a route that 404s).
- plugin-hono-server (standalone discovery): false — this minimal surface mounts
  CRUD only, not /batch. Under-reporting is the safe direction (client keeps its
  correct-but-slower fallback).
- client: already normalizes hierarchical capabilities → flat booleans, so
  client.capabilities.transactionalBatch is exposed and now typed.

Tests assert the bit across all producers (spec schema required-field, protocol
engine-tx true/false, rest enableBatch AND-ing + always-populated, hono false,
client hierarchical→flat). Regenerated content/docs/references/api/discovery.mdx.
Additive and behavior-preserving; only the discovery payload gains a field.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 20, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
spec Building Building Preview, Comment Jul 20, 2026 3:09am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Jul 20, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 6 package(s): @objectstack/client, @objectstack/metadata-protocol, @objectstack/objectql, @objectstack/plugin-hono-server, @objectstack/rest, @objectstack/spec.

111 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via packages/client, @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/client, @objectstack/spec)
  • content/docs/api/data-flow.mdx (via @objectstack/client)
  • content/docs/api/environment-routing.mdx (via @objectstack/client, @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/client, @objectstack/rest, @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/rest, @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/rest, @objectstack/spec)
  • content/docs/automation/approvals.mdx (via packages/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via @objectstack/metadata-protocol, @objectstack/objectql, packages/spec)
  • content/docs/concepts/north-star.mdx (via packages/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via packages/objectql, @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/migration-from-objectql.mdx (via @objectstack/objectql)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/vercel.mdx (via @objectstack/objectql)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/cli.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/client, @objectstack/plugin-hono-server, @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via packages/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/data-service.mdx (via packages/client)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/client, packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/objectql)
  • content/docs/permissions/authentication.mdx (via @objectstack/client, @objectstack/objectql, @objectstack/plugin-hono-server)
  • content/docs/permissions/authorization.mdx (via @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/objectql, @objectstack/plugin-hono-server, @objectstack/rest, @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/client, @objectstack/objectql, @objectstack/plugin-hono-server, @objectstack/rest, @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via packages/rest, @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/realtime-protocol.mdx (via @objectstack/client)
  • content/docs/protocol/kernel/runtime-capabilities.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/client, @objectstack/objectql, @objectstack/plugin-hono-server, @objectstack/rest, @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/rest, @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/client, @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/objectql, @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@os-zhuang
os-zhuang merged commit bfa3c3f into main Jul 20, 2026
16 of 17 checks passed
@os-zhuang
os-zhuang deleted the feat/discovery-transactional-batch branch July 20, 2026 03:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

discovery 广播「跨对象原子 batch」能力位(让客户端声明式协商,取代 404/405/501 运行时探测)

1 participant