Skip to content

v3 policy: create() with a Json value containing a top-level "kind" key fails with "Invariant failed: expecting a ValueNode" #2791

Description

@twobyte

Description

A create() whose Json field value contains a top-level kind key fails through the policy plugin with:

Invariant failed: expecting a ValueNode

The same payload succeeds via update(), and via create() when the kind key is renamed or omitted — so the JSON value itself is fine; only the create path through the policy plugin rejects it.

This looks like the policy plugin's pre-create check (unwrapCreateValueRow) walking each column value looking for embedded Kysely query/operation nodes, which it recognises by their kind discriminator property. A plain JSON object that happens to have its own kind key is mistaken for a node and fails the item.kind === "ValueNode" invariant.

kind is a fairly natural domain field name (ours tags imagery/artwork payloads), so this is easy to hit and confusing to diagnose — the error surfaces as a policy-layer invariant with no hint that a payload key is the trigger.

Reproduction

Schema:

model Item {
    id      Int  @id @default(autoincrement())
    payload Json

    @@allow('all', true)
}

Code:

import { ZenStackClient } from "@zenstackhq/orm";
import { PolicyPlugin } from "@zenstackhq/plugin-policy";
import { schema } from "./zenstack/schema";

const db = new ZenStackClient(schema, { dialect }).$use(new PolicyPlugin());

// ❌ throws: Invariant failed: expecting a ValueNode
await db.item.create({ data: { payload: { kind: "artwork" } } });

// ✅ works — same shape, key renamed
await db.item.create({ data: { payload: { type: "artwork" } } });

// ✅ works — same value via update
const item = await db.item.create({ data: { payload: {} } });
await db.item.update({ where: { id: item.id }, data: { payload: { kind: "artwork" } } });

Expected behaviour

A JSON column value is treated as opaque data regardless of its keys; create() and update() accept the same payloads.

Actual behaviour

create() through the policy plugin throws Invariant failed: expecting a ValueNode whenever the JSON value has a top-level kind key. update() accepts the identical value.

Workaround

Create without the kind key, then patch it in with an update() in the same transaction — the stored payload ends up identical. We pin the quirk (and the workaround) with an integration test so we can drop the dance once fixed.

Environment

  • @zenstackhq/orm 3.9.0, @zenstackhq/plugin-policy 3.9.0, @zenstackhq/cli 3.9.0
  • Postgres dialect (@zenstackhq/orm/dialects/postgres + pg)
  • Also reproduced on 3.7.2

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions