Skip to content

feat(spec,rest): batch recordIds on security/explain (#8326) - #8452

Merged
qq9340100 merged 1 commit into
mainfrom
claude/issue-8326-security-explain-batch
Aug 13, 2026
Merged

feat(spec,rest): batch recordIds on security/explain (#8326)#8452
qq9340100 merged 1 commit into
mainfrom
claude/issue-8326-security-explain-batch

Conversation

@qq9340100

Copy link
Copy Markdown
Collaborator

Fixes#8326

Maintainer-approved batch form for security/explain (ruling comment 5278876009, 2026-08-13): recordIds: string[] on the EXISTING request shape — not a sibling endpoint — answering the per-record decision.record.visible map for one (object, operation) pair in one round trip.

Contract (spec)

  • ExplainRequestSchema gains recordIds: string[] — min 1, cap 200 (exported as EXPLAIN_BATCH_MAX_RECORD_IDS; a consumer with more records paginates under it). Over-cap and empty arrays are refused at validation, never truncated.
  • Mutually exclusive with recordId via superRefine: a request carrying both spellings is a loud 400, never a silent precedence.
  • ExplainDecisionSchema gains an optional records array. The per-record verdict object is hoisted into one shared (non-exported) schema used by both the singular record and each records[] entry, so the two shapes can never drift.
  • Ordering (documented in TSDoc and the generated reference page): records[i] answers recordIds[i] — same order, same length, duplicates answered per position.
  • Missing records: an id that does not resolve under a system read fail-closes to { recordId, visible: false } with decidedBy omitted — the same answer the singular form gives.
  • The singular and object-level request/response wire shapes are byte-compatible (pinned by tests asserting records is absent on singular responses, and the pre-batch fixtures unchanged).

Handler (rest)

Same layering semantics by construction: the handler runs one object-level explain for the shared trace, then the SINGULAR record-grained evaluation per unique id, relaying each verdict verbatim into records[] — the service never sees recordIds, so batch ≡ N singular answers is a property of the code, and the agreement test pins it from regressing. A service predating record-grained explain fail-closes each entry to visible: false (a hidden button beats a shown-then-403). GET wraps a lone repeated recordIds query param (a query string cannot spell a one-element array); a bare string in a POST body stays a 400. All refusals ride the existing 400 VALIDATION_FAILED ADR-0112 envelope — no new error codes, no ledger changes.

Coverage

  • Agreement test: batch answer through the real handler equals N singular answers through the real handler for the same records, against a deterministic fake service.
  • Cap refusal: 201 ids asserted as status 400 AND error.code === 'VALIDATION_FAILED'; service never called. Exactly 200 parses.
  • Missing-record: fail-closed verdict, decidedBy omitted, pinned at schema and handler level.
  • Exclusivity, empty batch, non-string members, GET wrap, POST no-wrap, singular byte-compat.

Regenerated artifacts: authorable-surface/security.json, api-surface/security.json + signatures, export-origins/security.json, content/docs/references/security/explain.mdx (generated reference page — not a release note).

Changeset: @objectstack/spec minor + @objectstack/rest minor (additive public surface).

On merge, objectui#4296 (the list-row kebab consumer card, Blocked-by this one) unblocks — the list page folds the verdict map into the grid's per-row predicate channel.

Refs: ADR-0090 D6, ADR-0095 C2.


Generated by Claude Code

- spec: ExplainRequestSchema gains recordIds (1-200, mutually exclusive
with recordId, cap exported as EXPLAIN_BATCH_MAX_RECORD_IDS); the
decision gains an optional records[] of per-record verdicts sharing
the singular record verdict schema. Ordering, cap and missing-record
behaviour documented in TSDoc; singular/object-level shapes untouched.
- rest: the explain handler answers recordIds by one object-level pass
plus the singular record-grained evaluation per unique id, relayed
verbatim into records[i] answering recordIds[i] — batch ≡ N singular
answers by construction. GET wraps a lone repeated query param; POST
strings stay 400. All refusals ride the existing 400 VALIDATION_FAILED
envelope.
- regenerated: authorable-surface, api-surface, export-origins, docs
reference page.
- tests: schema round-trip/cap/exclusivity pins; handler ordering,
agreement, cap envelope (code+status), missing-record fail-closed,
pre-C2 service fail-closed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012MNV7ZSCjNfA38eDCjsXQL
@vercel

vercelBot commented Aug 13, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
ProjectDeploymentActionsUpdated (UTC)
objectstackIgnoredIgnoredAug 13, 2026 1:38pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/rest, @objectstack/spec.

108 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/connect-mcp.mdx(via @objectstack/rest)
  • content/docs/ai/skills-reference.mdx(via @objectstack/spec)
  • content/docs/ai/skills.mdx(via @objectstack/spec)
  • content/docs/api/client-sdk.mdx(via @objectstack/spec)
  • content/docs/api/environment-routing.mdx(via @objectstack/spec)
  • content/docs/api/error-catalog.mdx(via @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 @objectstack/spec)
  • content/docs/automation/connectors.mdx(via @objectstack/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 packages/spec)
  • content/docs/concepts/north-star.mdx(via @objectstack/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 @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/cli.mdx(via @objectstack/spec)
  • content/docs/deployment/tenancy-modes.mdx(via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx(via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx(via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.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/your-first-project.mdx(via @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 @objectstack/spec)
  • content/docs/kernel/index.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/data-service.mdx(via @objectstack/spec)
  • content/docs/kernel/runtime-services/email-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/examples.mdx(via @objectstack/spec)
  • content/docs/kernel/runtime-services/index.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx(via @objectstack/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx(via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx(via @objectstack/spec)
  • content/docs/kernel/services-checklist.mdx(via @objectstack/spec)
  • content/docs/kernel/services.mdx(via @objectstack/spec)
  • content/docs/permissions/authentication.mdx(via @objectstack/rest)
  • 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/permissions/system-context.mdx(via packages/rest, packages/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/rest, @objectstack/spec)
  • content/docs/plugins/packages.mdx(via @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/http-protocol.mdx(via @objectstack/rest, @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx(via packages/rest, @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx(via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.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/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/ui/actions.mdx(via @objectstack/spec)
  • content/docs/ui/apps.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/field-grouping-and-order.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)

7 release-owned page(s) also reference the affected code. These are read-only:

  • content/docs/releases/implementation-status.mdx(via @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/spec)
  • content/docs/releases/v17.mdx(via @objectstack/rest, @objectstack/spec)
  • content/docs/releases/v9.mdx(via @objectstack/spec)

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

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.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation tests tooling labels Aug 13, 2026
@qq9340100
qq9340100 marked this pull request as ready for review August 13, 2026 15:24
@qq9340100
qq9340100 added this pull request to the merge queueAug 13, 2026
Merged via the queue into main with commit 27358d5Aug 13, 2026
27 checks passed
@qq9340100
qq9340100 deleted the claude/issue-8326-security-explain-batch branch August 13, 2026 15:51
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

security/explain needs a batch form (recordIds[]) — the list row kebab cannot consume the record-level verdict without 2N probes per page

2 participants

@qq9340100@claude