Skip to content

Declarative trigger support in schema.yaml (BEFORE INSERT/UPDATE/DELETE guards) #82

Description

@MelbourneDeveloper

Context

NAP (NimblesiteAgenticPlatform) has completed its `[AUTHZ-ONE-ROLE]` migration: every endpoint now runs as `app_user` under RLS policies declared in `migrations/schema.yaml`. One guard could NOT be expressed declaratively:

Last-owner protection on `tenant_members` — "refuse to demote/delete the only `owner` of a tenant". The RLS layer can restrict owner-row writes to owners (`tenant_members_writer_update/_delete` policies), but a policy predicate cannot count remaining rows after the write, and LQL has no negated-exists / aggregate predicates. The natural mechanism is a BEFORE UPDATE/DELETE trigger that raises when the target row is the last owner.

Today the guard lives only in the application route layer (`assert_not_last_owner`), which means raw SQL under `app_user` could still strand a tenant ownerless.

Ask

Declarative trigger support in schema.yaml, e.g.:

```yaml
tables:

  • name: tenant_members
    triggers:
    • name: assert_not_last_owner
      timing: Before
      events: [Update, Delete]
      forEachRow: true
      bodyLql: |

      raise when OLD.role = 'owner' and no other owner row exists for OLD.tenant_id

```

Notes:

  • The RLS spec already emulates SQLite RLS with triggers internally, so the DDL machinery partially exists; this asks for user-declared triggers as first-class schema objects (diffed, inspected, dropped destructively like policies).
  • A smaller alternative that would also unblock this case: support for `not exists(...)` / negation and row-count comparisons in LQL policy predicates, so the guard could be an RLS predicate instead of a trigger.

Per NAP's hard rule ("DataProvider owns the whole schema surface; if it can't express something, file an issue — never work around it") we have NOT hand-written any SQL; the guard stays app-side until this lands.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions