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
Description
A
create()whose Json field value contains a top-levelkindkey fails through the policy plugin with:The same payload succeeds via
update(), and viacreate()when thekindkey 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 theirkinddiscriminator property. A plain JSON object that happens to have its ownkindkey is mistaken for a node and fails theitem.kind === "ValueNode"invariant.kindis 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:
Code:
Expected behaviour
A JSON column value is treated as opaque data regardless of its keys;
create()andupdate()accept the same payloads.Actual behaviour
create()through the policy plugin throwsInvariant failed: expecting a ValueNodewhenever the JSON value has a top-levelkindkey.update()accepts the identical value.Workaround
Create without the
kindkey, then patch it in with anupdate()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/orm3.9.0,@zenstackhq/plugin-policy3.9.0,@zenstackhq/cli3.9.0@zenstackhq/orm/dialects/postgres+pg)