Skip to content

feat(spec): userActions.edit/delete accept per-record CEL predicates (objectui#2614)#3076

Merged
os-zhuang merged 6 commits into
mainfrom
claude/row-crud-cel-predicates-uzb7pz
Jul 16, 2026
Merged

feat(spec): userActions.edit/delete accept per-record CEL predicates (objectui#2614)#3076
os-zhuang merged 6 commits into
mainfrom
claude/row-crud-cel-predicates-uzb7pz

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Summary

Spec slice of objectstack-ai/objectui#2614 — the built-in row CRUD actions (Edit/Delete) could only be gated by object-level booleans, so an author could not hide/disable the Edit button per record (e.g. frozen snapshot rows).

userActions.edit / userActions.delete now accept, in addition to the plain boolean, an object form (RowCrudActionOverrideSchema):

userActions: {
  edit:   { disabledWhen: 'record.frozen == true' },          // grey out per row
  delete: { visibleWhen: 'record.frozen != true' },           // hide per row
  // enabled?: boolean — same meaning as the bare boolean form
}
  • Predicates are ExpressionInputSchema (CEL; string shorthand normalizes to the { dialect: 'cel', source } envelope). Unknown keys are rejected (.strict()), so e.g. a hideWhen typo fails at parse time.
  • Semantics documented on the schema: visibleWhen false → button not rendered (fail-closed); disabledWhen true → rendered disabled (fail-soft). Advisory UI gating only — server enforcement stays with permissions/hooks.
  • resolveCrudAffordances() keeps returning resolved booleans (enabled falls back to the managedBy bucket default) and now surfaces the predicates as editPredicates / deletePredicates, passed through as authored. Boolean-only inputs produce byte-identical output — zero behavior change for existing schemas.
  • clampManagedObjectWrites (ADR-0092 D2 /me/permissions hint clamp) treats the object form by its explicit enabled flag only: per-record predicates are not a write grant, so managed objects stay fail-closed unless enabled === true.

The UI consumption (grid row menu + related-list data-table) lands in the companion objectui PR on the same branch name.

Tests

  • packages/spec/src/data/object.test.ts — boolean back-compat, string-shorthand normalization, predicate pass-through with bucket-default enabled, boolean-equivalent object form, unknown-key rejection.
  • packages/plugins/plugin-hono-server/src/fold-wildcard-superuser.test.ts — object form counts as opt-in only via explicit enabled: true.
  • Full spec suite: 254 files / 6888 tests green; @objectstack/spec + @objectstack/plugin-hono-server build clean (dts included).

🤖 Generated with Claude Code

https://claude.ai/code/session_01HnCZhYpQjRyg2E44RHBiNE


Generated by Claude Code

…(objectui#2614)

Extend the object-level userActions.edit / userActions.delete flags from
plain booleans to a union with an object form
{ enabled?, visibleWhen?, disabledWhen? } (RowCrudActionOverrideSchema),
so the built-in row Edit/Delete affordances can be hidden or disabled per
record via CEL predicates — the same evaluation contract custom actions
already use. Semantics: visibleWhen false → not rendered (fail-closed);
disabledWhen true → rendered disabled (fail-soft). The predicates are
advisory UI gating; server enforcement stays with permissions/hooks.

resolveCrudAffordances() keeps returning the resolved booleans (enabled
falls back to the managedBy bucket default) and now surfaces the
predicates as editPredicates / deletePredicates, pass-through as
authored. Boolean-only inputs produce byte-identical output — zero
behavior change for existing schemas.

clampManagedObjectWrites (ADR-0092 D2 hint clamp) treats the object form
by its explicit enabled flag only: per-record predicates are not a write
grant, so a managed object stays fail-closed unless enabled === true.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HnCZhYpQjRyg2E44RHBiNE
@vercel

vercel Bot commented Jul 16, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jul 16, 2026 4:46pm

Request Review

@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 3 package(s): @objectstack/plugin-hono-server, packages/qa, @objectstack/spec.

100 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 @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/spec)
  • content/docs/api/index.mdx (via @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 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 @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/troubleshooting.mdx (via @objectstack/spec)
  • 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/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/email-service.mdx (via packages/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 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/spec)
  • content/docs/permissions/authentication.mdx (via @objectstack/plugin-hono-server)
  • content/docs/permissions/authorization.mdx (via packages/qa, @objectstack/spec)
  • content/docs/permissions/delegated-administration.mdx (via packages/qa)
  • 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/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/plugin-hono-server, @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/plugin-hono-server, @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 @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/runtime-capabilities.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via packages/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/releases/implementation-status.mdx (via @objectstack/plugin-hono-server, @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v9.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)

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.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HnCZhYpQjRyg2E44RHBiNE
@github-actions github-actions Bot added documentation Improvements or additions to documentation tooling labels Jul 16, 2026
…e exports

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HnCZhYpQjRyg2E44RHBiNE
…58 expression ledger

The new data/object.zod.ts:visibleWhen / :disabledWhen surfaces
(objectui#2614) tripped the conformance ratchet. Two rows, matching
their actual fail policies: visibleWhen is fail-closed (a faulting
predicate hides the row button), disabledWhen is fail-soft-log (a
faulting predicate leaves it enabled; server hooks are the boundary).
Both interpret on the canonical celEngine via objectui useRowPredicate.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HnCZhYpQjRyg2E44RHBiNE
… (objectui#2614)

A PAID invoice's fields already freeze via readonlyWhen; the built-in
row actions now follow the same truth: Edit renders DISABLED on paid
rows (disabledWhen), Delete is HIDDEN outright (visibleWhen). Draft and
sent invoices keep the untouched menu. Browser-verified against the
objectui HMR console: paid row menu = greyed Edit only; sent row menu =
enabled Edit + Delete.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HnCZhYpQjRyg2E44RHBiNE
@os-zhuang
os-zhuang marked this pull request as ready for review July 16, 2026 16:44
@os-zhuang
os-zhuang merged commit 627f225 into main Jul 16, 2026
17 checks passed
@os-zhuang
os-zhuang deleted the claude/row-crud-cel-predicates-uzb7pz branch July 16, 2026 16:50
os-zhuang added a commit that referenced this pull request Jul 17, 2026
content/docs/references/** is generated from packages/spec and committed, but no CI
job ever regenerated and diffed it, so the public reference docs drifted silently
while main stayed green. #3076 added RowCrudActionOverride to the spec and the docs
never learned the type existed.

Regenerate: 7 files, every change traced to a spec change that shipped without
re-running the generator — RowCrudActionOverride and ServiceSelfInfo missing outright,
dashboard filterBindings/name missing, readonly (#2948/#3003) and allowTransfer (#3004)
stale, and connector ADR-0096 → ADR-0097 (both ADRs exist and are distinct, so the
published docs were pointing readers at the wrong one). Verified deterministic: two
consecutive runs produce byte-identical output.

Gate: build-docs.ts --check, following the sibling convention. Every write goes through
emit() and every wiped folder through manageDir(), so check and write run identical
generation logic and differ only in the final disposition — it cannot pass on output a
real run would not produce. Verified output-identical to the previous generator across
all 258 files, and proven to fail on stale content, a missing page, a stale leftover
page, and a vacuous no-schema run. Not `git diff --exit-code`: that misses untracked
files.

Placement: lint.yml's "TypeScript Type Check" — no paths filter and a required status
check, so the gate can neither go dormant nor be merged past. ci.yml's "Build Docs" is
gated on a `docs` filter excluding packages/spec/**, so it skips exactly the spec-only
PRs that cause this drift.

Also un-dormants the sibling gates: check:spec-changes / check:upgrade-guide read the
ADR-0087 registries but ran under a filter listing only skills/**, and that filter
watched content/docs/guides/skills.mdx, a path #2584 moved. Job renamed
check-skill-docs → check-generated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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 protocol:data size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants